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>
527 lines
19 KiB
Zig
527 lines
19 KiB
Zig
//! Example usage:
|
|
//! git clone https://github.com/loongson-community/loongarch-opcodes.git ../loongarch-opcodes
|
|
//! zig run tools/gen_loongarch_encoding.zig -- ../loongarch-opcodes .
|
|
|
|
const std = @import("std");
|
|
const fs = std.fs;
|
|
const Allocator = std.mem.Allocator;
|
|
const print = std.debug.print;
|
|
const Writer = std.Io.Writer;
|
|
const ZonSerializer = std.zon.Serializer;
|
|
|
|
const OpcodeDesc = @import("loongarch/OpcodeDesc.zig");
|
|
const decode_tree = @import("loongarch/decode_tree.zig");
|
|
|
|
pub fn main(init: std.process.Init) !void {
|
|
const arena = init.arena.allocator();
|
|
const io = init.io;
|
|
|
|
var args = try init.minimal.args.iterateAllocator(arena);
|
|
const arg0 = args.next().?;
|
|
const opcodes_path = args.next() orelse usageAndExit(arg0, 0);
|
|
const zig_path = args.next() orelse usageAndExit(arg0, 1);
|
|
args.deinit();
|
|
|
|
var desc: OpcodeDesc = .{};
|
|
defer desc.deinit(arena);
|
|
|
|
var zig_dir = try std.Io.Dir.cwd().openDir(io, zig_path, .{});
|
|
defer zig_dir.close(io);
|
|
|
|
// load opcode data
|
|
{
|
|
print("Loading description files ..\n", .{});
|
|
var opcodes_dir = try std.Io.Dir.cwd().openDir(io, opcodes_path, .{ .iterate = true });
|
|
defer opcodes_dir.close(io);
|
|
var opcodes_iter = opcodes_dir.iterateAssumeFirstIteration();
|
|
while (try opcodes_iter.next(io)) |opcodes_file| {
|
|
if (opcodes_file.kind != .file) continue;
|
|
if (!std.mem.endsWith(u8, opcodes_file.name, ".txt")) continue;
|
|
|
|
print("Loading {s} ...\n", .{opcodes_file.name});
|
|
const data = try opcodes_dir.readFileAlloc(io, opcodes_file.name, arena, .unlimited);
|
|
try desc.parse(arena, data);
|
|
// `data` is intentionally leaked here because it must live longer than `desc`
|
|
// ArenaAllocator should clean them up.
|
|
}
|
|
|
|
print("Loading extra.txt ...\n", .{});
|
|
const data = try zig_dir.readFileAlloc(io, "tools/loongarch/extra.txt", arena, .unlimited);
|
|
try desc.parse(arena, data);
|
|
|
|
print("Loaded {} instructions, {} formats\n", .{ desc.opcode.items.len, desc.format.count() });
|
|
desc.sort();
|
|
print("Sorted data\n", .{});
|
|
}
|
|
|
|
// generate encoding.zig
|
|
{
|
|
print("Writing encoding.zig ...\n", .{});
|
|
var buffer: Writer.Allocating = .init(arena);
|
|
defer buffer.deinit();
|
|
const writer = &buffer.writer;
|
|
|
|
try writer.print(
|
|
\\// DO NOT MODIFY. This file is generated by tools/gen_loongarch_encoding.zig.
|
|
\\const Register = @import("bits.zig").Register;
|
|
\\
|
|
, .{});
|
|
|
|
// mnemonic enum
|
|
{
|
|
try writer.print("\npub const Mnemonic = enum {{\n", .{});
|
|
for (desc.opcode.items) |*opcode| {
|
|
try writer.print(" {f},\n", .{std.zig.fmtIdPU(opcode.name)});
|
|
}
|
|
try writer.print("}};\n", .{});
|
|
}
|
|
|
|
// instruction struct
|
|
{
|
|
try writer.print(
|
|
\\
|
|
\\pub const Instruction = packed union {{
|
|
\\ word: u32,
|
|
\\
|
|
, .{});
|
|
|
|
// format-based variants
|
|
{
|
|
var format_iter = desc.format.iterator();
|
|
while (format_iter.next()) |entry|
|
|
try printFormatStruct(writer, entry.key_ptr.*, entry.value_ptr.*);
|
|
}
|
|
|
|
// format-based encoders
|
|
{
|
|
var format_iter = desc.format.iterator();
|
|
while (format_iter.next()) |entry|
|
|
try printFormatEncoder(writer, entry.key_ptr.*, entry.value_ptr.*);
|
|
}
|
|
|
|
// opcode-based encoders
|
|
for (desc.opcode.items) |*opcode| {
|
|
const encoder_format = if (std.mem.eql(u8, opcode.name, opcode.orig_name)) opcode.orig_format else opcode.format;
|
|
try printInstructionEncoder(writer, opcode, encoder_format);
|
|
}
|
|
|
|
try writer.print("}};\n", .{});
|
|
}
|
|
|
|
try zig_dir.writeFile(io, .{
|
|
.sub_path = "src/codegen/loongarch/encoding.zig",
|
|
.data = buffer.written(),
|
|
});
|
|
}
|
|
|
|
// generate decode_tree.zon
|
|
{
|
|
print("Writing decode_tree.zon ...\n", .{});
|
|
var buffer: Writer.Allocating = .init(arena);
|
|
defer buffer.deinit();
|
|
const writer = &buffer.writer;
|
|
|
|
try writer.print(
|
|
\\// DO NOT MODIFY. This file is generated by tools/gen_loongarch_encoding.zig.
|
|
\\
|
|
, .{});
|
|
|
|
try printDecodeTree(writer, arena, &desc);
|
|
|
|
try writer.writeAll("\n");
|
|
|
|
try zig_dir.writeFile(io, .{
|
|
.sub_path = "src/codegen/loongarch/decode_tree.zon",
|
|
.data = buffer.written(),
|
|
});
|
|
}
|
|
|
|
// generate inst_formats.zon
|
|
{
|
|
print("Writing inst_formats.zon ...\n", .{});
|
|
var buffer: Writer.Allocating = .init(arena);
|
|
defer buffer.deinit();
|
|
const writer = &buffer.writer;
|
|
|
|
try writer.print(
|
|
\\// DO NOT MODIFY. This file is generated by tools/gen_loongarch_encoding.zig.
|
|
\\
|
|
, .{});
|
|
|
|
var s: ZonSerializer = .{
|
|
.writer = writer,
|
|
.options = .{},
|
|
};
|
|
try serializeInstFormats(&s, &desc);
|
|
|
|
try writer.writeAll("\n");
|
|
|
|
try zig_dir.writeFile(io, .{
|
|
.sub_path = "src/codegen/loongarch/inst_formats.zon",
|
|
.data = buffer.written(),
|
|
});
|
|
}
|
|
|
|
print("Done.\n", .{});
|
|
}
|
|
|
|
fn usageAndExit(arg0: []const u8, code: u8) noreturn {
|
|
print(
|
|
\\Usage: {s} /path/loongarch-opcodes /path/zig
|
|
\\
|
|
\\Updates LoongArch encoding data from loongarch-opcodes.git.
|
|
\\
|
|
, .{arg0});
|
|
std.process.exit(code);
|
|
}
|
|
|
|
fn printFormatStruct(writer: *Writer, name: []const u8, format: *const OpcodeDesc.Format) !void {
|
|
if (format.slots[0].tag == .none) return; // skips EMPTY format
|
|
|
|
try writer.print(" /// Fields of a `{s}` instruction.", .{name});
|
|
try writer.print("\n {s}: packed struct {{ ", .{name});
|
|
const Field = union(enum) {
|
|
funct: struct { width: u5 },
|
|
immediate: struct {
|
|
signedness: std.builtin.Signedness,
|
|
width: u5,
|
|
},
|
|
register: struct {
|
|
index: OpcodeDesc.Slot.Index,
|
|
width: u5,
|
|
},
|
|
};
|
|
var fields_buf: [4 * 2 + 1]Field = undefined;
|
|
var fields: std.ArrayList(Field) = .initBuffer(&fields_buf);
|
|
|
|
// collect fields
|
|
var bit_offset: u6 = 0;
|
|
var slots = format.slots;
|
|
std.mem.sort(OpcodeDesc.Slot, &slots, false, struct {
|
|
fn cmp(_: bool, lhs: OpcodeDesc.Slot, rhs: OpcodeDesc.Slot) bool {
|
|
if (lhs.tag == .none) return false;
|
|
if (rhs.tag == .none) return true;
|
|
return lhs.offset() < rhs.offset();
|
|
}
|
|
}.cmp);
|
|
for (slots) |slot| {
|
|
if (slot.tag == .none) break;
|
|
const slot_offset = slot.offset();
|
|
const slot_width = slot.width();
|
|
if (bit_offset != slot_offset)
|
|
fields.appendAssumeCapacity(.{ .funct = .{ .width = @truncate(slot_offset - bit_offset) } });
|
|
|
|
switch (slot.tag) {
|
|
.none => unreachable,
|
|
.imm => {
|
|
fields.appendAssumeCapacity(.{ .immediate = .{
|
|
.signedness = slot.payload.imm.signedness,
|
|
.width = slot_width,
|
|
} });
|
|
},
|
|
.reg => fields.appendAssumeCapacity(.{ .register = .{
|
|
.index = slot.payload.reg.index,
|
|
.width = slot_width,
|
|
} }),
|
|
}
|
|
bit_offset = slot_offset + slot_width;
|
|
}
|
|
if (bit_offset != 32)
|
|
fields.appendAssumeCapacity(.{ .funct = .{ .width = @intCast(@as(u6, 32) - bit_offset) } });
|
|
|
|
// detect shadowed immediate field names
|
|
const imm_shadowed = imm_shadowed: {
|
|
var imm_names: [std.math.maxInt(u6) + 1]bool = @splat(false);
|
|
for (fields.items) |field| {
|
|
switch (field) {
|
|
else => {},
|
|
.immediate => |imm_field| {
|
|
var imm_name_key: u6 = imm_field.width;
|
|
if (imm_field.signedness == .signed) imm_name_key |= std.math.maxInt(u5) + 1;
|
|
if (imm_names[imm_name_key]) break :imm_shadowed true;
|
|
imm_names[imm_name_key] = true;
|
|
},
|
|
}
|
|
}
|
|
break :imm_shadowed false;
|
|
};
|
|
|
|
// print fields
|
|
bit_offset = 0;
|
|
for (fields.items, 0..) |field, field_i| {
|
|
if (field_i != 0) try writer.writeAll(", ");
|
|
switch (field) {
|
|
.funct => |pl| {
|
|
try writer.print("funct{}: u{}", .{ bit_offset, pl.width });
|
|
bit_offset += pl.width;
|
|
},
|
|
.immediate => |pl| {
|
|
if (imm_shadowed) {
|
|
try writer.print("imm{}: {c}{}", .{
|
|
bit_offset,
|
|
@as(u8, switch (pl.signedness) {
|
|
.signed => 'i',
|
|
.unsigned => 'u',
|
|
}),
|
|
pl.width,
|
|
});
|
|
} else {
|
|
try writer.print("{c}i{}: {c}{}", .{
|
|
@as(u8, switch (pl.signedness) {
|
|
.signed => 's',
|
|
.unsigned => 'u',
|
|
}),
|
|
pl.width,
|
|
@as(u8, switch (pl.signedness) {
|
|
.signed => 'i',
|
|
.unsigned => 'u',
|
|
}),
|
|
pl.width,
|
|
});
|
|
}
|
|
bit_offset += pl.width;
|
|
},
|
|
.register => |pl| {
|
|
try writer.print("r{s}: u{}", .{ @tagName(pl.index), pl.width });
|
|
bit_offset += pl.width;
|
|
},
|
|
}
|
|
}
|
|
try writer.print(" }},\n", .{});
|
|
}
|
|
|
|
fn printFormatEncoder(writer: *Writer, name: []const u8, format: *const OpcodeDesc.Format) !void {
|
|
try writer.print("\n /// Encodes a `{s}` instruction.", .{name});
|
|
try writer.print("\n pub inline fn encode{s}(word: u32", .{name});
|
|
for (format.slots, 0..) |slot, slot_i| {
|
|
switch (slot.tag) {
|
|
.none => break,
|
|
.imm => {
|
|
const pl = slot.payload.imm;
|
|
|
|
try writer.print(", p{}: {c}{}", .{
|
|
slot_i,
|
|
@as(u8, switch (pl.signedness) {
|
|
.signed => 'i',
|
|
.unsigned => 'u',
|
|
}),
|
|
pl.length,
|
|
});
|
|
},
|
|
.reg => try writer.print(", p{}: Register", .{slot_i}),
|
|
}
|
|
}
|
|
try writer.print(") Instruction {{\n", .{});
|
|
if (format.slots[0].tag == .none) {
|
|
try writer.print(" return .{{ .word = word }};\n", .{});
|
|
} else {
|
|
try writer.print(" return .{{ .word = word", .{});
|
|
for (format.slots, 0..) |slot, slot_i| {
|
|
switch (slot.tag) {
|
|
.none => break,
|
|
.imm => {
|
|
const pl = slot.payload.imm;
|
|
try writer.print(" |\n (", .{});
|
|
try writer.print("(@as(u32, @as(u{}, @bitCast(p{})))", .{ pl.length, slot_i });
|
|
|
|
switch (pl.post_proc.tag) {
|
|
.none => {},
|
|
.add => try writer.print(" - {}", .{pl.post_proc.payload.add}),
|
|
.shl => try writer.print(" >> {}", .{pl.post_proc.payload.shl}),
|
|
}
|
|
|
|
try writer.print(") << {})", .{pl.index.offset()});
|
|
},
|
|
.reg => {
|
|
const pl = slot.payload.reg;
|
|
try writer.print(" |\n (@as(u32, p{}.encode()) << {})", .{ slot_i, pl.index.offset() });
|
|
},
|
|
}
|
|
}
|
|
try writer.print(" }};\n", .{});
|
|
}
|
|
try writer.print(" }}\n", .{});
|
|
}
|
|
|
|
fn printInstructionEncoder(writer: *Writer, opcode: *OpcodeDesc.Opcode, format: *const OpcodeDesc.Format) !void {
|
|
try writer.print("\n /// Encodes a `{s}` instruction", .{opcode.name});
|
|
if (opcode.required_features != OpcodeDesc.Opcode.RequiredFeatures{}) {
|
|
try writer.writeAll(" (requires ");
|
|
var first = true;
|
|
const feature_fields = comptime std.meta.fieldNames(OpcodeDesc.Opcode.RequiredFeatures);
|
|
inline for (feature_fields) |field| {
|
|
if (@field(opcode.required_features, field)) {
|
|
if (first) first = false else try writer.writeAll(" & ");
|
|
try writer.writeAll(field);
|
|
}
|
|
}
|
|
try writer.writeByte(')');
|
|
}
|
|
try writer.writeByte('.');
|
|
try writer.print("\n pub inline fn {f}(", .{std.zig.fmtIdPU(opcode.name)});
|
|
for (format.slots, 0..) |slot, slot_i| {
|
|
if (slot_i != 0 and slot.tag != .none)
|
|
try writer.print(", ", .{});
|
|
switch (slot.tag) {
|
|
.none => break,
|
|
.imm => {
|
|
const pl = slot.payload.imm;
|
|
|
|
try writer.print("p{}: {c}{}", .{
|
|
slot_i,
|
|
@as(u8, switch (pl.signedness) {
|
|
.signed => 'i',
|
|
.unsigned => 'u',
|
|
}),
|
|
pl.length,
|
|
});
|
|
},
|
|
.reg => try writer.print("p{}: Register", .{slot_i}),
|
|
}
|
|
}
|
|
try writer.print(") Instruction {{\n", .{});
|
|
try writer.print(" return encode{s}(0x{x:0>8}", .{ format.name, opcode.word });
|
|
for (format.slots, 0..) |slot, slot_i| {
|
|
switch (slot.tag) {
|
|
.none => break,
|
|
else => try writer.print(", p{}", .{slot_i}),
|
|
}
|
|
}
|
|
try writer.print(");\n", .{});
|
|
try writer.print(" }}\n", .{});
|
|
}
|
|
|
|
fn printDecodeTree(writer: *Writer, gpa: Allocator, desc: *const OpcodeDesc) !void {
|
|
var arena: std.heap.ArenaAllocator = .init(gpa);
|
|
defer arena.deinit();
|
|
const root_node = try decode_tree.populate(arena.allocator(), desc);
|
|
try printDecodeTreeNode(writer, root_node, 0);
|
|
}
|
|
|
|
fn printDecodeTreeNode(writer: *Writer, node: *const decode_tree.Node, indent: usize) !void {
|
|
if (node.mask == 0) {
|
|
try writer.print(".{{ .instruction = .{f} }}", .{std.zig.fmtId(node.next.instruction.name)});
|
|
} else {
|
|
try writer.print(".{{ .mask = 0x{x:0>8}, .cases = .{{\n", .{node.mask});
|
|
for (node.next.cases) |*case| {
|
|
try printIndentation(writer, indent + 1);
|
|
if (case.catch_all) {
|
|
try writer.print(".{{ .then = ", .{});
|
|
try printDecodeTreeNode(writer, case.child, indent + 1);
|
|
try writer.print(" }},\n", .{});
|
|
} else {
|
|
try writer.print(".{{ .value = 0x{x:0>8}, .then = ", .{case.variant});
|
|
try printDecodeTreeNode(writer, case.child, indent + 1);
|
|
try writer.print(" }},\n", .{});
|
|
}
|
|
}
|
|
try printIndentation(writer, indent);
|
|
try writer.print("}} }}", .{});
|
|
}
|
|
}
|
|
|
|
fn printIndentation(writer: *Writer, indent: usize) !void {
|
|
try writer.splatByteAll(' ', 4 * indent);
|
|
}
|
|
|
|
fn serializeInstFormats(s: *ZonSerializer, desc: *const OpcodeDesc) !void {
|
|
var root_struct = try s.beginStruct(.{});
|
|
|
|
{
|
|
var instructions_s = try root_struct.beginStructField("instructions", .{});
|
|
for (desc.opcode.items) |*opcode| {
|
|
var opcode_s = try instructions_s.beginStructField(opcode.name, .{});
|
|
|
|
try opcode_s.fieldPrefix("word");
|
|
try s.writer.writeAll("0x");
|
|
try s.writer.printInt(opcode.word, 16, .lower, .{
|
|
.alignment = .right,
|
|
.fill = '0',
|
|
.width = 8,
|
|
});
|
|
|
|
try opcode_s.fieldPrefix("format");
|
|
try s.ident(opcode.format.name);
|
|
if (opcode.orig_format != opcode.format) {
|
|
try opcode_s.fieldPrefix("orig_format");
|
|
try s.ident(opcode.orig_format.name);
|
|
}
|
|
|
|
if (opcode.orig_name.ptr != opcode.name.ptr)
|
|
try opcode_s.field("orig_name", opcode.orig_name, .{});
|
|
|
|
const field_names = comptime std.meta.fieldNames(OpcodeDesc.Opcode.RequiredFeatures);
|
|
var num_features: u32 = 0;
|
|
inline for (field_names) |field| {
|
|
if (@field(opcode.required_features, field))
|
|
num_features += 1;
|
|
}
|
|
var features_s = try opcode_s.beginTupleField("features", .{ .whitespace_style = .{ .fields = num_features } });
|
|
inline for (field_names) |field| {
|
|
if (@field(opcode.required_features, field)) {
|
|
try features_s.fieldPrefix();
|
|
try s.ident(field);
|
|
}
|
|
}
|
|
try features_s.end();
|
|
|
|
try opcode_s.end();
|
|
}
|
|
try instructions_s.end();
|
|
}
|
|
|
|
{
|
|
var formats_s = try root_struct.beginStructField("formats", .{});
|
|
for (desc.format.values()) |format| {
|
|
var format_s = try formats_s.beginStructField(format.name, .{ .whitespace_style = .{ .wrap = false } });
|
|
|
|
var slots_s = try format_s.beginTupleField("slots", .{});
|
|
for (format.slots) |slot| {
|
|
if (slot.tag == .none) break;
|
|
var slot_s = try slots_s.beginStructField(.{ .whitespace_style = .{ .wrap = false } });
|
|
switch (slot.tag) {
|
|
.none => unreachable,
|
|
.reg => {
|
|
const pl = slot.payload.reg;
|
|
var reg_s = try slot_s.beginStructField("reg", .{ .whitespace_style = .{ .fields = 2 } });
|
|
try reg_s.field("location", pl.index.offset(), .{});
|
|
|
|
try reg_s.fieldPrefix("class");
|
|
try s.ident(@tagName(pl.class));
|
|
|
|
try reg_s.end();
|
|
},
|
|
.imm => {
|
|
const pl = slot.payload.imm;
|
|
var imm_s = try slot_s.beginStructField("imm", .{ .whitespace_style = .{ .wrap = false } });
|
|
try imm_s.field("location", pl.index.offset(), .{});
|
|
try imm_s.field("length", pl.length, .{});
|
|
|
|
try imm_s.fieldPrefix("signedness");
|
|
try s.ident(@tagName(pl.signedness));
|
|
|
|
if (pl.post_proc.tag != .none) {
|
|
var pp_s = try imm_s.beginStructField("post_proc", .{ .whitespace_style = .{ .fields = 1 } });
|
|
switch (pl.post_proc.tag) {
|
|
.none => unreachable,
|
|
.add => try pp_s.field("add", pl.post_proc.payload.add, .{}),
|
|
.shl => try pp_s.field("shl", pl.post_proc.payload.shl, .{}),
|
|
}
|
|
try pp_s.end();
|
|
}
|
|
|
|
try imm_s.end();
|
|
},
|
|
}
|
|
try slot_s.end();
|
|
}
|
|
try slots_s.end();
|
|
|
|
try format_s.end();
|
|
}
|
|
try formats_s.end();
|
|
}
|
|
|
|
try root_struct.end();
|
|
}
|