mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-10-28 19:00:52 +08:00
This tool reads data from loongarch-opcodes.git and generates three data files. encoding.zig contains an enum of mnemonics, a packed union of packed structs to represent bit-fields in instructions and encoder functions for each instruction. inst_formats.zon contains required target feature set for each instruction, which can be used by the backend to, under debug mode, verify if it emits any instruction that is not supported by the compilation target. It also contains information about slots in each instruction format, their type, location and postprocessing, which can be used by disassemblers. decode_tree.zon is the auto-generated decode tree, which will be used by disassemblers. Signed-off-by: xtex <xtex@astrafall.org>
456 lines
16 KiB
Zig
456 lines
16 KiB
Zig
//! Parser for format description files in
|
|
//! https://github.com/loongson-community/loongarch-opcodes.
|
|
|
|
const std = @import("std");
|
|
const mem = std.mem;
|
|
const Allocator = mem.Allocator;
|
|
const Reader = std.Io.Reader;
|
|
|
|
const OpcodeDesc = @This();
|
|
|
|
/// Maximum number of slots in one instruction format.
|
|
const max_slots = 4;
|
|
|
|
opcode: std.ArrayList(Opcode) = .empty,
|
|
format_pool: std.heap.MemoryPool(Format) = .empty,
|
|
format: std.StringArrayHashMapUnmanaged(*Format) = .empty,
|
|
|
|
pub fn deinit(desc: *OpcodeDesc, gpa: Allocator) void {
|
|
desc.opcode.deinit(gpa);
|
|
desc.format.deinit(gpa);
|
|
desc.format_pool.deinit(gpa);
|
|
}
|
|
|
|
/// Instruction format. Slots are filled one by one, ending with reaching max_slots or a .none slot.
|
|
pub const Format = struct {
|
|
name: []const u8,
|
|
slots: [max_slots]Slot,
|
|
|
|
pub fn parse(name: []const u8) !Format {
|
|
var format: Format = .{
|
|
.name = name,
|
|
.slots = .{ .none, .none, .none, .none },
|
|
};
|
|
var reader: Reader = .fixed(name);
|
|
var slot_index: std.math.IntFittingRange(0, max_slots) = 0;
|
|
parse_empty: {
|
|
const str = reader.peekArray(5) catch |err| switch (err) {
|
|
error.EndOfStream => break :parse_empty,
|
|
else => return err,
|
|
};
|
|
if (mem.eql(u8, &str.*, "EMPTY"))
|
|
return format;
|
|
}
|
|
|
|
parse_slots: while (slot_index < max_slots) : (slot_index += 1) {
|
|
switch (reader.takeByte() catch |err| switch (err) {
|
|
error.EndOfStream => break :parse_slots,
|
|
else => return err,
|
|
}) {
|
|
'D' => format.slots[slot_index] = .{ .tag = .reg, .payload = .{ .reg = .{
|
|
.class = .int,
|
|
.index = .d,
|
|
} } },
|
|
'J' => format.slots[slot_index] = .{ .tag = .reg, .payload = .{ .reg = .{
|
|
.class = .int,
|
|
.index = .j,
|
|
} } },
|
|
'K' => format.slots[slot_index] = .{ .tag = .reg, .payload = .{ .reg = .{
|
|
.class = .int,
|
|
.index = .k,
|
|
} } },
|
|
'A' => format.slots[slot_index] = .{ .tag = .reg, .payload = .{ .reg = .{
|
|
.class = .int,
|
|
.index = .a,
|
|
} } },
|
|
'F' => format.slots[slot_index] = .{ .tag = .reg, .payload = .{ .reg = .{
|
|
.class = .fp,
|
|
.index = try .parse(&reader),
|
|
} } },
|
|
'C' => format.slots[slot_index] = .{ .tag = .reg, .payload = .{ .reg = .{
|
|
.class = .fcc,
|
|
.index = try .parse(&reader),
|
|
} } },
|
|
'T' => format.slots[slot_index] = .{ .tag = .reg, .payload = .{ .reg = .{
|
|
.class = .lbt_scratch,
|
|
.index = try .parse(&reader),
|
|
} } },
|
|
'V' => format.slots[slot_index] = .{ .tag = .reg, .payload = .{ .reg = .{
|
|
.class = .lsx,
|
|
.index = try .parse(&reader),
|
|
} } },
|
|
'X' => format.slots[slot_index] = .{ .tag = .reg, .payload = .{ .reg = .{
|
|
.class = .lasx,
|
|
.index = try .parse(&reader),
|
|
} } },
|
|
'S', 'U' => |signedness_ch| {
|
|
const signedness: std.builtin.Signedness = if (signedness_ch == 'S') .signed else .unsigned;
|
|
while (slot_index < max_slots and continue_imm_slot: {
|
|
_ = Slot.Index.fromChar(reader.peekByte() catch |err| switch (err) {
|
|
error.EndOfStream => break :continue_imm_slot false,
|
|
else => return err,
|
|
}) catch break :continue_imm_slot false;
|
|
break :continue_imm_slot true;
|
|
}) : (slot_index += 1) {
|
|
const index: Slot.Index = try .parse(&reader);
|
|
const length = try takeInteger(u5, &reader);
|
|
const post_proc = post_proc: {
|
|
if ('p' == reader.peekByte() catch |err| switch (err) {
|
|
error.EndOfStream => ' ',
|
|
else => return err,
|
|
}) {
|
|
reader.toss(1);
|
|
break :post_proc try Slot.PostProcess.parse(&reader);
|
|
} else break :post_proc Slot.PostProcess.none;
|
|
};
|
|
format.slots[slot_index] = .{ .tag = .imm, .payload = .{ .imm = .{
|
|
.index = index,
|
|
.length = length,
|
|
.signedness = signedness,
|
|
.post_proc = post_proc,
|
|
} } };
|
|
}
|
|
slot_index -= 1;
|
|
},
|
|
else => return error.InvalidCharacter,
|
|
}
|
|
}
|
|
|
|
return format;
|
|
}
|
|
};
|
|
|
|
test "parse format" {
|
|
_ = try Format.parse("DJFmSk12m13ps3");
|
|
_ = try Format.parse("DJSk12m13ps3U16pp1");
|
|
_ = try Format.parse("DJK");
|
|
}
|
|
|
|
pub const Slot = packed struct {
|
|
tag: Slot.Tag,
|
|
payload: Slot.Payload,
|
|
|
|
comptime {
|
|
std.debug.assert(@sizeOf(Slot) == 4);
|
|
}
|
|
|
|
const Payload = packed union {
|
|
none: u16, // unused number, just for padding
|
|
imm: packed struct {
|
|
index: Index,
|
|
length: u5,
|
|
signedness: std.builtin.Signedness,
|
|
post_proc: PostProcess = .none,
|
|
},
|
|
reg: packed struct {
|
|
class: enum(u13) { int, fp, fcc, lbt_scratch, lsx, lasx },
|
|
index: Index,
|
|
},
|
|
};
|
|
|
|
const Tag = enum(u16) { reg, imm, none };
|
|
|
|
pub const none: Slot = .{ .tag = .none, .payload = .{ .none = 0 } };
|
|
|
|
pub const Index = enum(u3) {
|
|
// zig fmt: off
|
|
d, j, k, a, m, n,
|
|
// zig fmt: on
|
|
|
|
pub fn offset(index: Index) u5 {
|
|
return switch (index) {
|
|
.d => 0,
|
|
.j => 5,
|
|
.k => 10,
|
|
.a => 15,
|
|
.m => 16,
|
|
.n => 18,
|
|
};
|
|
}
|
|
|
|
pub fn fromChar(ch: u8) error{UnknownIndexChar}!Index {
|
|
return switch (ch) {
|
|
'd' => .d,
|
|
'j' => .j,
|
|
'k' => .k,
|
|
'a' => .a,
|
|
'm' => .m,
|
|
'n' => .n,
|
|
else => return error.UnknownIndexChar,
|
|
};
|
|
}
|
|
|
|
pub const ParseError = Reader.Error || error{UnknownIndexChar};
|
|
pub fn parse(reader: *Reader) Index.ParseError!Index {
|
|
return fromChar(try reader.takeByte());
|
|
}
|
|
};
|
|
|
|
/// Post-process operations for disassemblying.
|
|
pub const PostProcess = packed struct {
|
|
tag: PostProcess.Tag,
|
|
payload: PostProcess.Payload,
|
|
|
|
const Payload = packed union {
|
|
/// assembly value = encoded value + N
|
|
add: u5,
|
|
/// assembly value = encoded value << N
|
|
shl: u5,
|
|
none: u5, // unused number, for padding
|
|
};
|
|
|
|
const Tag = std.meta.FieldEnum(PostProcess.Payload);
|
|
|
|
pub const none: PostProcess = .{ .tag = .none, .payload = .{ .none = 0 } };
|
|
|
|
pub const ParseError = Reader.Error || std.fmt.ParseIntError;
|
|
pub fn parse(reader: *Reader) PostProcess.ParseError!PostProcess {
|
|
switch (try reader.takeByte()) {
|
|
'p' => return .{
|
|
.tag = .add,
|
|
.payload = .{ .add = try takeInteger(u4, reader) },
|
|
},
|
|
's' => return .{
|
|
.tag = .shl,
|
|
.payload = .{ .shl = try takeInteger(u4, reader) },
|
|
},
|
|
else => return error.InvalidCharacter,
|
|
}
|
|
}
|
|
};
|
|
|
|
pub fn offset(slot: Slot) u5 {
|
|
return switch (slot.tag) {
|
|
.none => unreachable,
|
|
.imm => slot.payload.imm.index.offset(),
|
|
.reg => slot.payload.reg.index.offset(),
|
|
};
|
|
}
|
|
|
|
pub fn width(slot: Slot) u5 {
|
|
return switch (slot.tag) {
|
|
.none => unreachable,
|
|
.imm => slot.payload.imm.length,
|
|
.reg => switch (slot.payload.reg.class) {
|
|
.fcc => 3,
|
|
else => 5,
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn mask(slot: Slot) u32 {
|
|
const off = slot.offset();
|
|
const size = slot.width();
|
|
const msb, const overflow = @addWithOverflow(off, size);
|
|
if (overflow == 1) {
|
|
@branchHint(.unlikely);
|
|
return ~((@as(u32, 1) << off) - 1);
|
|
}
|
|
return ((@as(u32, 1) << msb) - 1) ^ ((@as(u32, 1) << off) - 1);
|
|
}
|
|
};
|
|
|
|
test "mask" {
|
|
try std.testing.expectEqual(0b111100000, (Slot{ .tag = .imm, .payload = .{ .imm = .{
|
|
.index = .j,
|
|
.length = 4,
|
|
.signedness = .unsigned,
|
|
} } }).mask());
|
|
try std.testing.expectEqual(0x7fffffff, (Slot{ .tag = .imm, .payload = .{ .imm = .{
|
|
.index = .d,
|
|
.length = 31,
|
|
.signedness = .unsigned,
|
|
} } }).mask());
|
|
try std.testing.expectEqual(0x7fffffff, (Slot{ .tag = .imm, .payload = .{ .imm = .{
|
|
.index = .d,
|
|
.length = 31,
|
|
.signedness = .unsigned,
|
|
} } }).mask());
|
|
try std.testing.expectEqual(0xffffffe0, (Slot{ .tag = .imm, .payload = .{ .imm = .{
|
|
.index = .j,
|
|
.length = 27,
|
|
.signedness = .unsigned,
|
|
} } }).mask());
|
|
try std.testing.expectEqual(0b111110000000000, (Slot{ .tag = .reg, .payload = .{ .reg = .{
|
|
.class = .int,
|
|
.index = .k,
|
|
} } }).mask());
|
|
}
|
|
|
|
fn takeInteger(comptime T: type, reader: *Reader) (Reader.Error || std.fmt.ParseIntError)!T {
|
|
if (std.math.maxInt(T) < 10) {
|
|
const ch = try reader.takeByte();
|
|
return std.math.cast(T, ch ^ '0') orelse return error.Overflow;
|
|
}
|
|
var v: T = 0;
|
|
|
|
var ch: u8 = try reader.peekByte();
|
|
if (!std.ascii.isDigit(ch)) return error.InvalidCharacter;
|
|
|
|
while (std.ascii.isDigit(ch)) : (ch = reader.peekByte() catch |err| switch (err) {
|
|
error.EndOfStream => break,
|
|
else => return err,
|
|
}) {
|
|
v = try std.math.add(
|
|
T,
|
|
try std.math.add(
|
|
T,
|
|
try std.math.shlExact(T, v, 3),
|
|
try std.math.shlExact(T, v, 1),
|
|
),
|
|
std.math.cast(T, ch ^ '0') orelse return error.Overflow,
|
|
);
|
|
reader.toss(1);
|
|
}
|
|
return v;
|
|
}
|
|
|
|
test takeInteger {
|
|
var reader: std.Io.Reader = undefined;
|
|
|
|
reader = .fixed("123");
|
|
try std.testing.expectEqual(123, try takeInteger(u8, &reader));
|
|
reader = .fixed("123ignored");
|
|
try std.testing.expectEqual(123, try takeInteger(u8, &reader));
|
|
reader = .fixed("bad");
|
|
try std.testing.expectError(error.InvalidCharacter, takeInteger(u8, &reader));
|
|
reader = .fixed("1");
|
|
try std.testing.expectEqual(1, try takeInteger(u1, &reader));
|
|
}
|
|
|
|
pub const Opcode = struct {
|
|
word: u32,
|
|
name: []const u8,
|
|
format: *Format,
|
|
orig_name: []const u8,
|
|
orig_format: *Format,
|
|
required_features: RequiredFeatures,
|
|
|
|
pub const RequiredFeatures = packed struct {
|
|
@"32bit": bool = false,
|
|
@"32s": bool = false,
|
|
@"64bit": bool = false,
|
|
f: bool = false,
|
|
d: bool = false,
|
|
lsx: bool = false,
|
|
lasx: bool = false,
|
|
lbt: bool = false,
|
|
lvz: bool = false,
|
|
};
|
|
};
|
|
|
|
/// Parses a opcode data file.
|
|
/// Caller owns the data string and the data string must live longer
|
|
/// than the OpcodeDesc.
|
|
pub fn parse(desc: *OpcodeDesc, gpa: Allocator, data: []const u8) !void {
|
|
var lines = mem.tokenizeScalar(u8, data, '\n');
|
|
while (lines.next()) |line| {
|
|
if (line[0] == '#') continue; // skip comments, not used by upstream but used in tools/loongarch/extra.txt
|
|
var tokens = mem.tokenizeScalar(u8, line, ' ');
|
|
|
|
const word_buf = tokens.next() orelse return error.UnexpectedEol;
|
|
const word = try std.fmt.parseInt(u32, word_buf, 16);
|
|
const name = tokens.next() orelse return error.UnexpectedEol;
|
|
const format_str = tokens.next() orelse return error.UnexpectedEol;
|
|
const format = try desc.getOrParseFormat(gpa, format_str);
|
|
|
|
const opcode = try desc.opcode.addOne(gpa);
|
|
opcode.* = .{
|
|
.word = word,
|
|
.name = name,
|
|
.format = format,
|
|
.orig_name = name,
|
|
.orig_format = format,
|
|
.required_features = .{},
|
|
};
|
|
|
|
// parse attributes
|
|
while (tokens.next()) |attr| {
|
|
if (attr[0] != '@') return error.MalformedAttribute;
|
|
if (mem.indexOfScalar(u8, attr, '=')) |eql_pos| {
|
|
const attr_name = attr[1..][0 .. eql_pos - 1];
|
|
const attr_val = attr[eql_pos + 1 ..];
|
|
|
|
if (mem.eql(u8, attr_name, "orig_name")) { // manual name
|
|
opcode.orig_name = attr_val;
|
|
} else if (mem.eql(u8, attr_name, "orig_fmt")) { // manual format
|
|
opcode.orig_format = try desc.getOrParseFormat(gpa, attr_val);
|
|
}
|
|
} else {
|
|
const attr_name = attr[1..];
|
|
|
|
if (mem.eql(u8, attr_name, "la32")) { // available in LA32S
|
|
if (!opcode.required_features.@"32bit")
|
|
opcode.required_features.@"32s" = true;
|
|
} else if (mem.eql(u8, attr_name, "primary")) { // available in LA32R
|
|
opcode.required_features.@"32bit" = true;
|
|
opcode.required_features.@"32s" = false;
|
|
} else if (mem.eql(u8, attr_name, "lvz")) { // requires LVZ
|
|
opcode.required_features.lvz = false;
|
|
} else if (mem.eql(u8, attr_name, "lbt")) { // requires LBT
|
|
opcode.required_features.lbt = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// determine based on register usages
|
|
for (opcode.format.slots) |slot| {
|
|
switch (slot.tag) {
|
|
.none => break,
|
|
.imm => {},
|
|
.reg => {
|
|
switch (slot.payload.reg.class) {
|
|
.fp => {
|
|
opcode.required_features.f = true;
|
|
if (mem.eql(u8, opcode.name, ".d")) { // requires double-precision FP
|
|
opcode.required_features.d = true;
|
|
}
|
|
},
|
|
.fcc => opcode.required_features.f = true,
|
|
.lsx => opcode.required_features.lsx = true,
|
|
.lasx => opcode.required_features.lasx = true,
|
|
.lbt_scratch => opcode.required_features.lbt = true,
|
|
else => {},
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
// if there are not any attributes indicating that the instruction requires
|
|
// any other features or supports LA32, we assume that it requires LA64.
|
|
if (opcode.required_features == Opcode.RequiredFeatures{}) {
|
|
opcode.required_features.@"64bit" = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Gets or parses a format string.
|
|
/// The string must live longer than the OpcodeDesc.
|
|
pub fn getOrParseFormat(desc: *OpcodeDesc, gpa: Allocator, format: []const u8) !*Format {
|
|
const gop = try desc.format.getOrPut(gpa, format);
|
|
if (!gop.found_existing) {
|
|
errdefer _ = desc.format.swapRemove(format);
|
|
const format_ptr = try desc.format_pool.create(gpa);
|
|
errdefer desc.format_pool.destroy(format_ptr);
|
|
|
|
format_ptr.* = try Format.parse(format);
|
|
gop.value_ptr.* = format_ptr;
|
|
}
|
|
return gop.value_ptr.*;
|
|
}
|
|
|
|
pub fn sort(desc: *OpcodeDesc) void {
|
|
mem.sort(Opcode, desc.opcode.items, false, struct {
|
|
fn cmp(_: bool, lhs: Opcode, rhs: Opcode) bool {
|
|
return mem.order(u8, lhs.name, rhs.name) == .lt;
|
|
}
|
|
}.cmp);
|
|
desc.format.sort(struct {
|
|
keys: [][]const u8,
|
|
|
|
pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool {
|
|
return mem.order(u8, ctx.keys[a_index], ctx.keys[b_index]) == .lt;
|
|
}
|
|
}{ .keys = desc.format.keys() });
|
|
}
|