1const encoding = @import("encoding.zig");
2const Mnemonic = encoding.Mnemonic;
3const Instruction = encoding.Instruction;
4const bits = @import("bits.zig");
5const Register = bits.Register;
6const Disassemble = @This();
7
8const decode_tree = @import("decode_tree.zon");
9const inst_formats = @import("inst_formats.zon");
10
11mnemonic_operands_separator: []const u8 = " ",
12operands_separator: []const u8 = ", ",
13enable_aliases: bool = false,
14preferred_style: Style = .manual,
15
16pub const Style = enum {
17 /// Encoding style, used by loongson-community/loongarch-opcodes.
18 ///
19 /// Output operands are not post-processed, sorted with slot offset.
20 encoding,
21 /// Manual style, used by the official manual and assembly code.
22 ///
23 /// Output operands are post-processed.
24 manual,
25};
26
27pub fn printInstruction(dis: *const Disassemble, inst: Instruction, writer: *std.Io.Writer) std.Io.Writer.Error!void {
28 @setEvalBranchQuota(3000);
29 const mnemonic = decodeMnemonic(inst) orelse return try writer.print("(UNKNOWN: 0x{x:0>8})", .{inst.word});
30
31 inline for (@typeInfo(Mnemonic).@"enum".field_names) |mnemonic_field| try_mnemonic: {
32 if (@field(Mnemonic, mnemonic_field) != mnemonic) break :try_mnemonic;
33
34 const inst_info = @field(inst_formats.instructions, mnemonic_field);
35 const InstInfo = @TypeOf(inst_info);
36
37 switch (dis.preferred_style) {
38 .encoding => {
39 try writer.writeAll(mnemonic_field);
40 const format = inst_info.format;
41 if (format != .EMPTY) try writer.writeAll(dis.mnemonic_operands_separator);
42 try dis.printOperands(@field(inst_formats.formats, @tagName(format)), inst, writer);
43 },
44 .manual => {
45 try writer.writeAll(if (@hasField(InstInfo, "orig_name")) inst_info.orig_name else mnemonic_field);
46 const format = if (@hasField(InstInfo, "orig_format")) inst_info.orig_format else inst_info.format;
47 if (format != .EMPTY) try writer.writeAll(dis.mnemonic_operands_separator);
48 try dis.printOperands(@field(inst_formats.formats, @tagName(format)), inst, writer);
49 },
50 }
51 return;
52 }
53}
54
55pub fn printInstructionAlloc(dis: *const Disassemble, inst: Instruction, gpa: std.mem.Allocator) (std.Io.Writer.Error || std.mem.Allocator.Error)![]u8 {
56 var writer: std.Io.Writer.Allocating = .init(gpa);
57 defer writer.deinit();
58 try dis.printInstruction(inst, &writer.writer);
59 return try writer.toOwnedSlice();
60}
61
62test printInstruction {
63 const dis: Disassemble = .{};
64
65 const testDisasm = struct {
66 fn testDisasm(expected: []const u8, inst: u32) !void {
67 const assembly = try dis.printInstructionAlloc(.{ .word = inst }, std.testing.allocator);
68 defer std.testing.allocator.free(assembly);
69 try std.testing.expectEqualStrings(expected, assembly);
70 }
71 }.testDisasm;
72
73 try testDisasm("fcmp.caf.s $fcc0, $f1, $f2", 0x0c100820);
74 try testDisasm("ertn", 0x06483800);
75 try testDisasm("addi.d $r8, $r0, 0xa", 0x02c02808);
76}
77
78pub fn fmtInstruction(dis: Disassemble, inst: Instruction) struct {
79 dis: Disassemble,
80 inst: Instruction,
81
82 pub fn format(data: @This(), w: *std.Io.Writer) std.Io.Writer.Error!void {
83 try data.dis.printInstruction(data.inst, w);
84 }
85} {
86 return .{ .dis = dis, .inst = inst };
87}
88
89pub fn decodeMnemonic(inst: Instruction) ?Mnemonic {
90 return decodeMnemonicWithTree(decode_tree, inst);
91}
92
93/// Decodes mnemonics with a node in the decode tree.
94fn decodeMnemonicWithTree(comptime tree: anytype, inst: Instruction) ?Mnemonic {
95 const Tree = @TypeOf(tree);
96 if (@hasField(Tree, "instruction")) {
97 return @field(Mnemonic, @tagName(tree.instruction));
98 } else if (@hasField(Tree, "mask")) {
99 const value = inst.word & tree.mask;
100 inline for (tree.cases) |case| try_case: {
101 if (@hasField(@TypeOf(case), "value")) {
102 if (value != case.value) break :try_case;
103 }
104 const then = case.then;
105
106 if (@hasField(@TypeOf(then), "instruction")) {
107 // manually inline here to reduce decoder functions of leaf nodes
108 return @field(Mnemonic, @tagName(then.instruction));
109 } else return decodeMnemonicWithTree(then, inst);
110 }
111 return null;
112 } else {
113 @compileLog("Current decode-tree node:", tree);
114 @compileError("Invalid decode-tree node");
115 }
116}
117
118test decodeMnemonic {
119 try std.testing.expectEqual(Mnemonic.@"fcmp.caf.s", decodeMnemonic(.{ .word = 0x0c100820 }).?);
120 try std.testing.expectEqual(Mnemonic.eret, decodeMnemonic(.{ .word = 0x06483800 }).?);
121 try std.testing.expectEqual(Mnemonic.@"addi.d", decodeMnemonic(.{ .word = 0x02c02808 }).?);
122}
123
124pub fn printOperands(dis: *const Disassemble, comptime format: anytype, inst: Instruction, writer: *std.Io.Writer) std.Io.Writer.Error!void {
125 const word = inst.word;
126 inline for (format.slots, 0..) |slot, slot_i| {
127 if (slot_i != 0) try writer.writeAll(dis.operands_separator);
128
129 const Slot = @TypeOf(slot);
130 if (@hasField(Slot, "reg")) {
131 const location = slot.reg.location;
132 const class = slot.reg.class;
133
134 const reg: u5 = if (class == .fcc)
135 @as(u3, @truncate(word >> location))
136 else
137 @as(u5, @truncate(word >> location));
138
139 try dis.printRegister(writer, class, reg);
140 } else if (@hasField(Slot, "imm")) {
141 const signedness = @field(std.builtin.Signedness, @tagName(slot.imm.signedness));
142 const ImmValue = @Int(signedness, slot.imm.length);
143 const UnsignedImmValue = @Int(.unsigned, slot.imm.length);
144 const Imm32 = @Int(signedness, 32);
145 // extend to 32-bit so postprocess won't overflow
146 var value: Imm32 = @as(ImmValue, @bitCast(@as(UnsignedImmValue, @truncate(word >> slot.imm.location))));
147
148 if (@hasField(@TypeOf(slot.imm), "post_proc")) {
149 const postproc = slot.imm.post_proc;
150 const PostProc = @TypeOf(postproc);
151 if (@hasField(PostProc, "shl")) value <<= postproc.shl;
152 if (@hasField(PostProc, "add")) value += postproc.add;
153 }
154
155 if (signedness == .unsigned) {
156 try writer.print("0x{x}", .{value});
157 } else {
158 if (value >= 0)
159 try writer.print("0x{x}", .{value})
160 else
161 try writer.print("-0x{x}", .{@abs(value)});
162 }
163 } else {
164 @compileLog("Current slot:", slot);
165 @compileError("Invalid operand slot info");
166 }
167 }
168}
169
170fn printRegister(dis: *const Disassemble, writer: *std.Io.Writer, comptime class: anytype, orig_reg: u5) std.Io.Writer.Error!void {
171 var reg = orig_reg;
172 const reg_prefix = prefix: {
173 if (dis.enable_aliases) {
174 switch (class) {
175 .int => switch (reg) {
176 1 => return try writer.print("$ra", .{}),
177 3 => return try writer.print("$sp", .{}),
178 4...11 => {
179 reg -= 4;
180 break :prefix "a";
181 },
182 12...20 => {
183 reg -= 12;
184 break :prefix "t";
185 },
186 22 => return try writer.print("$fp", .{}),
187 23...31 => {
188 reg -= 23;
189 break :prefix "s";
190 },
191 else => {},
192 },
193 .fp => switch (reg) {
194 0...7 => break :prefix "fa",
195 8...23 => {
196 reg -= 8;
197 break :prefix "ft";
198 },
199 24...31 => {
200 reg -= 24;
201 break :prefix "fs";
202 },
203 },
204 else => {},
205 }
206 }
207 break :prefix switch (class) {
208 .int => "r",
209 .fp => "f",
210 .fcc => "fcc",
211 .lsx => "v",
212 .lasx => "x",
213 else => unreachable,
214 };
215 };
216
217 try writer.print("${s}{d}", .{ reg_prefix, reg });
218}
219
220const std = @import("std");