mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-10-28 19:00:52 +08:00
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() });
|
|
}
|