lib/choir/src/backends/aarch64/disassemble/format.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const registers = @import("../registers/root.zig");
  3 
  4 const Encoded = registers.Encoded;
  5 
  6 const GprSize = enum { w, x };
  7 const GprRole = enum { base, other };
  8 
  9 const ConditionName = [_][]const u8{
 10     "eq", "ne", "cs", "cc", "mi", "pl", "vs", "vc",
 11     "hi", "ls", "ge", "lt", "gt", "le", "al", "nv",
 12 };
 13 
 14 pub fn formatInstructionRaw(raw: u32, buf: []u8) []const u8 {
 15     return formatInstructionRawWithDetail(raw, buf, false);
 16 }
 17 
 18 pub fn formatInstructionRawWithDetail(raw: u32, buf: []u8, show_offsets_in_bytes: bool) []const u8 {
 19     var writer = std.Io.Writer.fixed(buf);
 20 
 21     disassembleToWriter(raw, &writer, show_offsets_in_bytes) catch {
 22         return fallbackUnknown(raw, buf);
 23     };
 24 
 25     return writer.buffered();
 26 }
 27 
 28 pub fn disassembleToWriter(raw: u32, writer: anytype, show_offsets_in_bytes: bool) !void {
 29     if (try decodeBranchReg(raw, writer)) return;
 30     if (try decodeBranchCond(raw, writer, show_offsets_in_bytes)) return;
 31     if (try decodeBranchImm(raw, writer, show_offsets_in_bytes)) return;
 32 
 33     if (try decodeLoadStorePair(raw, writer)) return;
 34     if (try decodeLoadStoreRegImm(raw, writer)) return;
 35 
 36     if (try decodeDataProcessingImmediate(raw, writer)) return;
 37     if (try decodeConditionalSelect(raw, writer)) return;
 38     if (try decodeDataProcessingRegister(raw, writer)) return;
 39 
 40     if (try decodeFPImmediate(raw, writer)) return;
 41     if (try decodeFP(raw, writer)) return;
 42 
 43     try writer.print("unknown 0x{x:0>8}", .{raw});
 44 }
 45 
 46 fn fallbackUnknown(raw: u32, buf: []u8) []const u8 {
 47     return std.fmt.bufPrint(buf, "unknown 0x{x:0>8}", .{raw}) catch "unknown";
 48 }
 49 
 50 fn bits(raw: u32, lo: u5, width: u5) u32 {
 51     const mask: u32 = if (width == 32) std.math.maxInt(u32) else (@as(u32, 1) << width) - 1;
 52     return (raw >> lo) & mask;
 53 }
 54 
 55 fn signExtend(value: u32, width: u6) i64 {
 56     const shift: u7 = @as(u7, 64) - @as(u7, width);
 57     return (@as(i64, @intCast(value)) << @intCast(shift)) >> @intCast(shift);
 58 }
 59 
 60 fn gprSizeFromBit(sf: u1) GprSize {
 61     return if (sf == 0) .w else .x;
 62 }
 63 
 64 fn gprScaleBytes(size: GprSize) u32 {
 65     return switch (size) {
 66         .w => 4,
 67         .x => 8,
 68     };
 69 }
 70 
 71 fn condName(cond: u4) []const u8 {
 72     return ConditionName[cond];
 73 }
 74 
 75 fn invertCond(cond: u4) u4 {
 76     return cond ^ 1;
 77 }
 78 
 79 fn formatGpr(buf: []u8, reg: Encoded, size: GprSize, role: GprRole) []const u8 {
 80     const n: u5 = reg.toInt();
 81     if (n == 31) {
 82         return switch (role) {
 83             .base => "sp",
 84             .other => switch (size) {
 85                 .w => "wzr",
 86                 .x => "xzr",
 87             },
 88         };
 89     }
 90 
 91     const prefix: u8 = switch (size) {
 92         .w => 'w',
 93         .x => 'x',
 94     };
 95     return std.fmt.bufPrint(buf, "{c}{d}", .{ prefix, n }) catch "x?";
 96 }
 97 
 98 fn formatFpr(buf: []u8, reg: Encoded, fp_prefix: u8) []const u8 {
 99     return std.fmt.bufPrint(buf, "{c}{d}", .{ fp_prefix, reg.toInt() }) catch "v?";
100 }
101 
102 fn formatUnsignedHex(buf: []u8, value: u64) []const u8 {
103     return std.fmt.bufPrint(buf, "0x{x}", .{value}) catch "0x?";
104 }
105 
106 fn formatSignedHex(buf: []u8, value: i64) []const u8 {
107     if (value < 0) {
108         const mag: u64 = @intCast(-value);
109         return std.fmt.bufPrint(buf, "-0x{x}", .{mag}) catch "-0x?";
110     }
111     return std.fmt.bufPrint(buf, "0x{x}", .{@as(u64, @intCast(value))}) catch "0x?";
112 }
113 
114 fn decodeDataProcessingImmediate(raw: u32, writer: anytype) !bool {
115     const fixed_23_28 = bits(raw, 23, 6);
116 
117     if (fixed_23_28 == 0b100010) {
118         return decodeAddSubImmediate(raw, writer);
119     }
120     if (fixed_23_28 == 0b100101) {
121         return decodeMoveWide(raw, writer);
122     }
123 
124     return false;
125 }
126 
127 fn decodeAddSubImmediate(raw: u32, writer: anytype) !bool {
128     const sf: u1 = @intCast(bits(raw, 31, 1));
129     const op: u1 = @intCast(bits(raw, 30, 1));
130     const s: u1 = @intCast(bits(raw, 29, 1));
131     const sh: u1 = @intCast(bits(raw, 22, 1));
132     const imm12: u12 = @intCast(bits(raw, 10, 12));
133     const rn: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 5, 5)))));
134     const rd: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 0, 5)))));
135 
136     const size = gprSizeFromBit(sf);
137     const imm_value: u32 = if (sh == 1) (@as(u32, imm12) << 12) else imm12;
138 
139     var reg_buf0: [8]u8 = undefined;
140     var reg_buf1: [8]u8 = undefined;
141     var imm_buf: [32]u8 = undefined;
142 
143     const rn_name = formatGpr(&reg_buf0, rn, size, .other);
144     const imm_name = formatUnsignedHex(&imm_buf, imm_value);
145 
146     if (s == 1 and rd.toInt() == 31) {
147         try writer.print("cmp {s}, #{s}", .{ rn_name, imm_name });
148         return true;
149     }
150 
151     const rd_name = formatGpr(&reg_buf1, rd, size, .other);
152 
153     const base = if (op == 0) "add" else "sub";
154     const suffix = if (s == 1) "s" else "";
155     try writer.print("{s}{s} {s}, {s}, #{s}", .{ base, suffix, rd_name, rn_name, imm_name });
156     return true;
157 }
158 
159 fn decodeMoveWide(raw: u32, writer: anytype) !bool {
160     const sf: u1 = @intCast(bits(raw, 31, 1));
161     const opc: u2 = @intCast(bits(raw, 29, 2));
162     const hw: u2 = @intCast(bits(raw, 21, 2));
163     const imm16: u16 = @intCast(bits(raw, 5, 16));
164     const rd: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 0, 5)))));
165 
166     const mnemonic = switch (opc) {
167         0b00 => "movn",
168         0b10 => "movz",
169         0b11 => "movk",
170         else => return false,
171     };
172 
173     const size = gprSizeFromBit(sf);
174     const shift_amount: u32 = @as(u32, hw) * 16;
175 
176     var reg_buf: [8]u8 = undefined;
177     var imm_buf: [32]u8 = undefined;
178 
179     const rd_name = formatGpr(&reg_buf, rd, size, .other);
180     const imm_name = formatUnsignedHex(&imm_buf, imm16);
181 
182     if (shift_amount == 0) {
183         try writer.print("{s} {s}, #{s}", .{ mnemonic, rd_name, imm_name });
184         return true;
185     }
186 
187     try writer.print("{s} {s}, #{s}, lsl #{d}", .{ mnemonic, rd_name, imm_name, shift_amount });
188     return true;
189 }
190 
191 fn decodeDataProcessingRegister(raw: u32, writer: anytype) !bool {
192     const fixed_24_28 = bits(raw, 24, 5);
193 
194     if (fixed_24_28 == 0b01011 and bits(raw, 21, 1) == 0) {
195         return decodeAddSubShiftedRegister(raw, writer);
196     }
197     if (fixed_24_28 == 0b01010) {
198         return decodeLogicalShiftedRegister(raw, writer);
199     }
200     if (bits(raw, 21, 8) == 0b11010110 and bits(raw, 30, 1) == 0) {
201         return decodeDataProcessing2Source(raw, writer);
202     }
203     if (fixed_24_28 == 0b11011) {
204         return decodeDataProcessing3Source(raw, writer);
205     }
206 
207     return false;
208 }
209 
210 fn decodeAddSubShiftedRegister(raw: u32, writer: anytype) !bool {
211     const sf: u1 = @intCast(bits(raw, 31, 1));
212     const op: u1 = @intCast(bits(raw, 30, 1));
213     const s: u1 = @intCast(bits(raw, 29, 1));
214     const shift: u2 = @intCast(bits(raw, 22, 2));
215     const imm6: u6 = @intCast(bits(raw, 10, 6));
216     const rm: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 16, 5)))));
217     const rn: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 5, 5)))));
218     const rd: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 0, 5)))));
219 
220     const size = gprSizeFromBit(sf);
221 
222     var reg_buf0: [8]u8 = undefined;
223     var reg_buf1: [8]u8 = undefined;
224     var reg_buf2: [8]u8 = undefined;
225 
226     const rn_name = formatGpr(&reg_buf0, rn, size, .other);
227     const rm_name = formatGpr(&reg_buf1, rm, size, .other);
228 
229     if (s == 1 and rd.toInt() == 31) {
230         try writer.print("cmp {s}, {s}", .{ rn_name, rm_name });
231         if (imm6 != 0 or shift != 0) {
232             try writeShiftSuffix(writer, shift, imm6);
233         }
234         return true;
235     }
236 
237     const rd_name = formatGpr(&reg_buf2, rd, size, .other);
238 
239     const base = if (op == 0) "add" else "sub";
240     const suffix = if (s == 1) "s" else "";
241 
242     try writer.print("{s}{s} {s}, {s}, {s}", .{ base, suffix, rd_name, rn_name, rm_name });
243     if (imm6 != 0 or shift != 0) {
244         try writeShiftSuffix(writer, shift, imm6);
245     }
246     return true;
247 }
248 
249 fn writeShiftSuffix(writer: anytype, shift: u2, amount: u6) !void {
250     const shift_name = switch (shift) {
251         0b00 => "lsl",
252         0b01 => "lsr",
253         0b10 => "asr",
254         else => "ror",
255     };
256     try writer.print(", {s} #{d}", .{ shift_name, amount });
257 }
258 
259 fn decodeLogicalShiftedRegister(raw: u32, writer: anytype) !bool {
260     const sf: u1 = @intCast(bits(raw, 31, 1));
261     const opc: u2 = @intCast(bits(raw, 29, 2));
262     const shift: u2 = @intCast(bits(raw, 22, 2));
263     const imm6: u6 = @intCast(bits(raw, 10, 6));
264     const n: u1 = @intCast(bits(raw, 21, 1));
265     const rm: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 16, 5)))));
266     const rn: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 5, 5)))));
267     const rd: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 0, 5)))));
268 
269     const size = gprSizeFromBit(sf);
270 
271     var reg_buf0: [8]u8 = undefined;
272     var reg_buf1: [8]u8 = undefined;
273     var reg_buf2: [8]u8 = undefined;
274 
275     const rn_name = formatGpr(&reg_buf0, rn, size, .other);
276     const rm_name = formatGpr(&reg_buf1, rm, size, .other);
277     const rd_name = formatGpr(&reg_buf2, rd, size, .other);
278 
279     if (opc == 0b01 and n == 0 and rn.toInt() == 31 and shift == 0 and imm6 == 0) {
280         try writer.print("mov {s}, {s}", .{ rd_name, rm_name });
281         return true;
282     }
283 
284     const mnemonic = switch (opc) {
285         0b00 => "and",
286         0b01 => "orr",
287         0b10 => "eor",
288         0b11 => "ands",
289     };
290 
291     try writer.print("{s} {s}, {s}, {s}", .{ mnemonic, rd_name, rn_name, rm_name });
292     if (shift != 0 or imm6 != 0) {
293         try writeShiftSuffix(writer, shift, imm6);
294     }
295     return true;
296 }
297 
298 fn decodeDataProcessing2Source(raw: u32, writer: anytype) !bool {
299     const sf: u1 = @intCast(bits(raw, 31, 1));
300     const opcode: u6 = @intCast(bits(raw, 10, 6));
301     const rm: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 16, 5)))));
302     const rn: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 5, 5)))));
303     const rd: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 0, 5)))));
304 
305     const mnemonic = switch (opcode) {
306         0b000010 => "udiv",
307         0b000011 => "sdiv",
308         else => return false,
309     };
310 
311     const size = gprSizeFromBit(sf);
312 
313     var reg_buf0: [8]u8 = undefined;
314     var reg_buf1: [8]u8 = undefined;
315     var reg_buf2: [8]u8 = undefined;
316 
317     const rd_name = formatGpr(&reg_buf0, rd, size, .other);
318     const rn_name = formatGpr(&reg_buf1, rn, size, .other);
319     const rm_name = formatGpr(&reg_buf2, rm, size, .other);
320 
321     try writer.print("{s} {s}, {s}, {s}", .{ mnemonic, rd_name, rn_name, rm_name });
322     return true;
323 }
324 
325 fn decodeDataProcessing3Source(raw: u32, writer: anytype) !bool {
326     const sf: u1 = @intCast(bits(raw, 31, 1));
327     const op54: u2 = @intCast(bits(raw, 29, 2));
328     const op31: u3 = @intCast(bits(raw, 21, 3));
329     const o0: u1 = @intCast(bits(raw, 15, 1));
330     const rm: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 16, 5)))));
331     const ra: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 10, 5)))));
332     const rn: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 5, 5)))));
333     const rd: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 0, 5)))));
334 
335     if (op54 != 0 or op31 != 0) return false;
336 
337     const size = gprSizeFromBit(sf);
338 
339     var reg_buf0: [8]u8 = undefined;
340     var reg_buf1: [8]u8 = undefined;
341     var reg_buf2: [8]u8 = undefined;
342     var reg_buf3: [8]u8 = undefined;
343 
344     const rd_name = formatGpr(&reg_buf0, rd, size, .other);
345     const rn_name = formatGpr(&reg_buf1, rn, size, .other);
346     const rm_name = formatGpr(&reg_buf2, rm, size, .other);
347     const ra_name = formatGpr(&reg_buf3, ra, size, .other);
348 
349     if (o0 == 0 and ra.toInt() == 31) {
350         try writer.print("mul {s}, {s}, {s}", .{ rd_name, rn_name, rm_name });
351         return true;
352     }
353 
354     const mnemonic = if (o0 == 0) "madd" else "msub";
355     try writer.print("{s} {s}, {s}, {s}, {s}", .{ mnemonic, rd_name, rn_name, rm_name, ra_name });
356     return true;
357 }
358 
359 fn decodeConditionalSelect(raw: u32, writer: anytype) !bool {
360     if (bits(raw, 24, 5) != 0b11010) return false;
361     if (bits(raw, 22, 2) != 0b10) return false;
362     if (bits(raw, 21, 1) != 0) return false;
363 
364     const sf: u1 = @intCast(bits(raw, 31, 1));
365     const op: u1 = @intCast(bits(raw, 30, 1));
366     const cond: u4 = @intCast(bits(raw, 12, 4));
367     const op2: u2 = @intCast(bits(raw, 10, 2));
368     const rm: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 16, 5)))));
369     const rn: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 5, 5)))));
370     const rd: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 0, 5)))));
371 
372     if (op != 0) return false;
373 
374     const size = gprSizeFromBit(sf);
375 
376     var reg_buf0: [8]u8 = undefined;
377     var reg_buf1: [8]u8 = undefined;
378     var reg_buf2: [8]u8 = undefined;
379 
380     const rd_name = formatGpr(&reg_buf0, rd, size, .other);
381 
382     if (op2 == 0b01 and rn.toInt() == 31 and rm.toInt() == 31) {
383         const original_cond = invertCond(cond);
384         try writer.print("cset {s}, {s}", .{ rd_name, condName(original_cond) });
385         return true;
386     }
387 
388     const rn_name = formatGpr(&reg_buf1, rn, size, .other);
389     const rm_name = formatGpr(&reg_buf2, rm, size, .other);
390 
391     if (op2 != 0b00) return false;
392 
393     try writer.print("csel {s}, {s}, {s}, {s}", .{ rd_name, rn_name, rm_name, condName(cond) });
394     return true;
395 }
396 
397 fn decodeLoadStoreRegImm(raw: u32, writer: anytype) !bool {
398     if (bits(raw, 24, 2) != 0b01) return false;
399     if (bits(raw, 27, 3) != 0b111) return false;
400 
401     const v: u1 = @intCast(bits(raw, 26, 1));
402     const size_bits: u2 = @intCast(bits(raw, 30, 2));
403     const opc: u2 = @intCast(bits(raw, 22, 2));
404     const imm12: u12 = @intCast(bits(raw, 10, 12));
405     const rn: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 5, 5)))));
406     const rt: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 0, 5)))));
407 
408     const is_load = opc == 0b01;
409     if (!is_load and opc != 0b00) return false;
410 
411     var reg_buf0: [8]u8 = undefined;
412     var reg_buf1: [8]u8 = undefined;
413     var off_buf: [32]u8 = undefined;
414 
415     const mnemonic = if (is_load) "ldr" else "str";
416 
417     if (v == 0) {
418         const size: GprSize = switch (size_bits) {
419             0b10 => .w,
420             0b11 => .x,
421             else => return false,
422         };
423         const scale = gprScaleBytes(size);
424         const byte_offset: u32 = @as(u32, imm12) * scale;
425 
426         const rt_name = formatGpr(&reg_buf0, rt, size, .other);
427         const rn_name = formatGpr(&reg_buf1, rn, size, .base);
428 
429         try writer.print("{s} {s}, [", .{ mnemonic, rt_name });
430         try writer.writeAll(rn_name);
431         if (byte_offset != 0) {
432             const off_name = formatUnsignedHex(&off_buf, byte_offset);
433             try writer.print(", #{s}", .{off_name});
434         }
435         try writer.writeAll("]");
436         return true;
437     }
438 
439     const fp_prefix: u8 = switch (size_bits) {
440         0b10 => 's',
441         0b11 => 'd',
442         else => return false,
443     };
444     const scale: u32 = if (fp_prefix == 's') 4 else 8;
445     const byte_offset: u32 = @as(u32, imm12) * scale;
446 
447     const rt_name = formatFpr(&reg_buf0, rt, fp_prefix);
448     const rn_name = formatGpr(&reg_buf1, rn, .x, .base);
449 
450     try writer.print("{s} {s}, [", .{ mnemonic, rt_name });
451     try writer.writeAll(rn_name);
452     if (byte_offset != 0) {
453         const off_name = formatUnsignedHex(&off_buf, byte_offset);
454         try writer.print(", #{s}", .{off_name});
455     }
456     try writer.writeAll("]");
457     return true;
458 }
459 
460 fn decodeLoadStorePair(raw: u32, writer: anytype) !bool {
461     if (bits(raw, 27, 3) != 0b101) return false;
462 
463     const v: u1 = @intCast(bits(raw, 26, 1));
464     const op2: u3 = @intCast(bits(raw, 23, 3));
465     const l: u1 = @intCast(bits(raw, 22, 1));
466     const imm7_bits: u7 = @intCast(bits(raw, 15, 7));
467     const rt2: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 10, 5)))));
468     const rn: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 5, 5)))));
469     const rt: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 0, 5)))));
470     const opc: u2 = @intCast(bits(raw, 30, 2));
471 
472     const valid_op2 = op2 == 0b001 or op2 == 0b010 or op2 == 0b011;
473     if (!valid_op2) return false;
474 
475     const signed_imm7: i64 = signExtend(imm7_bits, 7);
476 
477     const mnemonic = if (l == 1) "ldp" else "stp";
478 
479     var reg_buf0: [8]u8 = undefined;
480     var reg_buf1: [8]u8 = undefined;
481     var reg_buf2: [8]u8 = undefined;
482     var off_buf: [32]u8 = undefined;
483 
484     const base_name = formatGpr(&reg_buf2, rn, .x, .base);
485 
486     if (v == 0) {
487         const size: GprSize = switch (opc) {
488             0b00 => .w,
489             0b10 => .x,
490             else => return false,
491         };
492         const scale = gprScaleBytes(size);
493         const byte_offset: i64 = signed_imm7 * scale;
494 
495         const rt_name = formatGpr(&reg_buf0, rt, size, .other);
496         const rt2_name = formatGpr(&reg_buf1, rt2, size, .other);
497 
498         try writer.print("{s} {s}, {s}, ", .{ mnemonic, rt_name, rt2_name });
499         try writeAddressingMode(writer, base_name, op2, byte_offset, &off_buf);
500         return true;
501     }
502 
503     const fp_prefix: u8 = switch (opc) {
504         0b00 => 's',
505         0b01 => 'd',
506         0b10 => 'q',
507         else => return false,
508     };
509     const scale: i64 = switch (fp_prefix) {
510         's' => 4,
511         'd' => 8,
512         'q' => 16,
513         else => 8,
514     };
515     const byte_offset: i64 = signed_imm7 * scale;
516 
517     const rt_name = formatFpr(&reg_buf0, rt, fp_prefix);
518     const rt2_name = formatFpr(&reg_buf1, rt2, fp_prefix);
519 
520     try writer.print("{s} {s}, {s}, ", .{ mnemonic, rt_name, rt2_name });
521     try writeAddressingMode(writer, base_name, op2, byte_offset, &off_buf);
522     return true;
523 }
524 
525 fn writeAddressingMode(writer: anytype, base_name: []const u8, op2: u3, byte_offset: i64, off_buf: []u8) !void {
526     const offset_text = if (byte_offset == 0) "" else formatSignedHex(off_buf, byte_offset);
527 
528     switch (op2) {
529         0b010 => {
530             try writer.writeAll("[");
531             try writer.writeAll(base_name);
532             if (byte_offset != 0) {
533                 try writer.print(", #{s}", .{offset_text});
534             }
535             try writer.writeAll("]");
536         },
537         0b011 => {
538             try writer.writeAll("[");
539             try writer.writeAll(base_name);
540             if (byte_offset != 0) {
541                 try writer.print(", #{s}", .{offset_text});
542             }
543             try writer.writeAll("]!");
544         },
545         0b001 => {
546             try writer.writeAll("[");
547             try writer.writeAll(base_name);
548             try writer.writeAll("]");
549             if (byte_offset != 0) {
550                 try writer.print(", #{s}", .{offset_text});
551             }
552         },
553         else => {
554             try writer.print("[{s}]", .{base_name});
555         },
556     }
557 }
558 
559 fn decodeBranchImm(raw: u32, writer: anytype, show_offsets_in_bytes: bool) !bool {
560     if (bits(raw, 26, 5) != 0b00101) return false;
561 
562     const op: u1 = @intCast(bits(raw, 31, 1));
563     const imm26_bits: u26 = @intCast(bits(raw, 0, 26));
564     const signed_imm26: i64 = signExtend(imm26_bits, 26);
565     const offset: i64 = if (show_offsets_in_bytes) signed_imm26 * 4 else signed_imm26;
566 
567     var off_buf: [32]u8 = undefined;
568     const off_name = formatSignedHex(&off_buf, offset);
569 
570     const mnemonic = if (op == 1) "bl" else "b";
571     try writer.print("{s} #{s}", .{ mnemonic, off_name });
572     return true;
573 }
574 
575 fn decodeBranchCond(raw: u32, writer: anytype, show_offsets_in_bytes: bool) !bool {
576     if (bits(raw, 25, 7) != 0b0101010) return false;
577     if (bits(raw, 24, 1) != 0) return false;
578     if (bits(raw, 4, 1) != 0) return false;
579 
580     const cond: u4 = @intCast(bits(raw, 0, 4));
581     const imm19_bits: u19 = @intCast(bits(raw, 5, 19));
582     const signed_imm19: i64 = signExtend(imm19_bits, 19);
583     const offset: i64 = if (show_offsets_in_bytes) signed_imm19 * 4 else signed_imm19;
584 
585     var off_buf: [32]u8 = undefined;
586     const off_name = formatSignedHex(&off_buf, offset);
587 
588     try writer.print("b.{s} #{s}", .{ condName(cond), off_name });
589     return true;
590 }
591 
592 fn decodeBranchReg(raw: u32, writer: anytype) !bool {
593     if (bits(raw, 25, 7) != 0b1101011) return false;
594 
595     const op2: u5 = @intCast(bits(raw, 16, 5));
596     if (op2 != 0b11111) return false;
597 
598     const opc: u4 = @intCast(bits(raw, 21, 4));
599     const rn: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 5, 5)))));
600 
601     var reg_buf: [8]u8 = undefined;
602     const rn_name = formatGpr(&reg_buf, rn, .x, .other);
603 
604     switch (opc) {
605         0b0010 => {
606             if (rn.toInt() == 30) {
607                 try writer.writeAll("ret");
608             } else {
609                 try writer.print("ret {s}", .{rn_name});
610             }
611             return true;
612         },
613         0b0000 => {
614             try writer.print("br {s}", .{rn_name});
615             return true;
616         },
617         0b0001 => {
618             try writer.print("blr {s}", .{rn_name});
619             return true;
620         },
621         else => return false,
622     }
623 }
624 
625 fn decodeFPImmediate(raw: u32, writer: anytype) !bool {
626     if (bits(raw, 23, 9) != 0b000111100) return false;
627     if (bits(raw, 10, 3) != 0b100) return false;
628     if (bits(raw, 5, 5) != 0) return false;
629 
630     const rd: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 0, 5)))));
631     const imm8: u8 = @intCast(bits(raw, 13, 8));
632     const ptype: u2 = @intCast(bits(raw, 21, 2));
633 
634     const fp_prefix: u8 = switch (ptype) {
635         0b00 => 'h',
636         0b01 => 's',
637         0b11 => 'd',
638         else => 'v',
639     };
640 
641     var reg_buf: [8]u8 = undefined;
642     const rd_name = formatFpr(&reg_buf, rd, fp_prefix);
643 
644     try writer.print("fmov {s}, #0x{x}", .{ rd_name, imm8 });
645     return true;
646 }
647 
648 fn decodeFP(raw: u32, writer: anytype) !bool {
649     if (bits(raw, 24, 5) != 0b11110) return false;
650     if (bits(raw, 21, 1) != 1) return false;
651     if (bits(raw, 29, 3) != 0) return false;
652 
653     if (bits(raw, 10, 5) == 0b10000) {
654         return decodeFP1Source(raw, writer);
655     }
656     if (bits(raw, 10, 2) == 0b10) {
657         return decodeFP2Source(raw, writer);
658     }
659     if (bits(raw, 10, 4) == 0b1000) {
660         return decodeFPCompare(raw, writer);
661     }
662     if (bits(raw, 10, 6) == 0 and (bits(raw, 16, 3) == 0b110 or bits(raw, 16, 3) == 0b111)) {
663         return decodeFMovGeneral(raw, writer);
664     }
665 
666     return false;
667 }
668 
669 fn fpPrefixFromType(ptype: u2) u8 {
670     return switch (ptype) {
671         0b00 => 's',
672         0b01 => 'd',
673         else => 'v',
674     };
675 }
676 
677 fn decodeFP1Source(raw: u32, writer: anytype) !bool {
678     const opcode: u6 = @intCast(bits(raw, 15, 6));
679     const ptype: u2 = @intCast(bits(raw, 22, 2));
680     const rn: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 5, 5)))));
681     const rd: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 0, 5)))));
682 
683     const mnemonic = switch (opcode) {
684         0b000000 => "fmov",
685         0b000001 => "fabs",
686         0b000010 => "fneg",
687         0b000011 => "fsqrt",
688         else => return false,
689     };
690 
691     const fp_prefix = fpPrefixFromType(ptype);
692 
693     var reg_buf0: [8]u8 = undefined;
694     var reg_buf1: [8]u8 = undefined;
695 
696     const rd_name = formatFpr(&reg_buf0, rd, fp_prefix);
697     const rn_name = formatFpr(&reg_buf1, rn, fp_prefix);
698 
699     try writer.print("{s} {s}, {s}", .{ mnemonic, rd_name, rn_name });
700     return true;
701 }
702 
703 fn decodeFP2Source(raw: u32, writer: anytype) !bool {
704     const opcode: u4 = @intCast(bits(raw, 12, 4));
705     const ptype: u2 = @intCast(bits(raw, 22, 2));
706     const rm: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 16, 5)))));
707     const rn: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 5, 5)))));
708     const rd: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 0, 5)))));
709 
710     const mnemonic = switch (opcode) {
711         0b0000 => "fmul",
712         0b0001 => "fdiv",
713         0b0010 => "fadd",
714         0b0011 => "fsub",
715         else => return false,
716     };
717 
718     const fp_prefix = fpPrefixFromType(ptype);
719 
720     var reg_buf0: [8]u8 = undefined;
721     var reg_buf1: [8]u8 = undefined;
722     var reg_buf2: [8]u8 = undefined;
723 
724     const rd_name = formatFpr(&reg_buf0, rd, fp_prefix);
725     const rn_name = formatFpr(&reg_buf1, rn, fp_prefix);
726     const rm_name = formatFpr(&reg_buf2, rm, fp_prefix);
727 
728     try writer.print("{s} {s}, {s}, {s}", .{ mnemonic, rd_name, rn_name, rm_name });
729     return true;
730 }
731 
732 fn decodeFPCompare(raw: u32, writer: anytype) !bool {
733     const opcode2: u5 = @intCast(bits(raw, 0, 5));
734     const op: u2 = @intCast(bits(raw, 14, 2));
735     if (op != 0) return false;
736 
737     const ptype: u2 = @intCast(bits(raw, 22, 2));
738     const rm: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 16, 5)))));
739     const rn: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 5, 5)))));
740 
741     const fp_prefix = fpPrefixFromType(ptype);
742 
743     var reg_buf0: [8]u8 = undefined;
744     var reg_buf1: [8]u8 = undefined;
745 
746     const rn_name = formatFpr(&reg_buf0, rn, fp_prefix);
747 
748     if (opcode2 == 0b01000 and rm.toInt() == 0) {
749         try writer.print("fcmp {s}, #0.0", .{rn_name});
750         return true;
751     }
752 
753     const rm_name = formatFpr(&reg_buf1, rm, fp_prefix);
754     try writer.print("fcmp {s}, {s}", .{ rn_name, rm_name });
755     return true;
756 }
757 
758 fn decodeFMovGeneral(raw: u32, writer: anytype) !bool {
759     const opcode: u3 = @intCast(bits(raw, 16, 3));
760     const ptype: u2 = @intCast(bits(raw, 22, 2));
761     const sf: u1 = @intCast(bits(raw, 31, 1));
762     const rn: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 5, 5)))));
763     const rd: Encoded = @fromBackingInt(@intCast(@as(u5, @intCast(bits(raw, 0, 5)))));
764 
765     if (opcode != 0b110 and opcode != 0b111) return false;
766 
767     const fp_prefix = fpPrefixFromType(ptype);
768     const gpr_size = gprSizeFromBit(sf);
769 
770     var reg_buf0: [8]u8 = undefined;
771     var reg_buf1: [8]u8 = undefined;
772 
773     if (opcode == 0b111) {
774         const rd_name = formatFpr(&reg_buf0, rd, fp_prefix);
775         const rn_name = formatGpr(&reg_buf1, rn, gpr_size, .other);
776         try writer.print("fmov {s}, {s}", .{ rd_name, rn_name });
777         return true;
778     }
779 
780     const rd_name = formatGpr(&reg_buf0, rd, gpr_size, .other);
781     const rn_name = formatFpr(&reg_buf1, rn, fp_prefix);
782     try writer.print("fmov {s}, {s}", .{ rd_name, rn_name });
783     return true;
784 }