commit 81b4f3f49ae6eddfb4e460f85a323ea47715141f
parent 5bf772ce3ffc905db1ca33fd61b7fa24fea4678b
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Fri, 29 May 2026 13:48:33 -0700
aa64 disasm: decode FP, bitfield, DP1, and register-offset ldst
The aa64 backend emits scalar FP data-processing, bitfield (sbfm/ubfm),
1-source DP (clz/rbit/rev/rev16), and register-offset loads/stores, but
none had rows in aa64_insn_table, so the disassembler rendered them all as
'.inst 0xXXXX'. Any aa64 function touching float/double, an int<->long cast
or shift, a clz/ctz builtin, or array indexing showed opaque .inst lines in
objdump and the JIT debugger — on all three aa64 OSes.
Add the formats + descriptor rows + operand printers (single source of bit
knowledge, mirroring native.c's encoders):
- FP_DP2 fmul/fdiv/fadd/fsub/fmax/fmin/fnmul (s/d/h via ftype)
- FP_DP1 fmov/fabs/fneg/fsqrt
- FP_CMP fcmp
- FP_CVT fcvt (precision change; src ftype, dst opc)
- FP_INT_CVT scvtf/ucvtf/fcvtzs/fcvtzu + fmov gpr<->fp (direction from opcode)
- DP1 rbit/rev16/rev(32&64)/clz
- BITFIELD sbfm/ubfm (raw form; alias display is a separate nicety)
- LDST_REGOFF ldr/str/ldrb/ldrh/strb/strh [Xn, Xm{, LSL #s}]
Verified byte-for-byte against llvm-objdump across a float/cast/shift/index C
program (0 .inst/.byte) and a hand-assembled corpus of every new family; the
only display delta is cfree's existing raw-form/decimal-immediate convention
(ubfm vs ubfx, #16 vs #0x10). Adds an aa64 decode corpus case to the default
suite. test-isa + test-asm green.
Diffstat:
5 files changed, 232 insertions(+), 0 deletions(-)
diff --git a/src/arch/aa64/isa.c b/src/arch/aa64/isa.c
@@ -378,6 +378,63 @@ const AA64InsnDesc aa64_insn_table[] = {
{MN("dsb"), 0xD503309Fu, 0xFFFFF0FFu, AA64_FMT_BARRIER, 0, {0, 0}},
{MN("isb"), 0xD50330DFu, 0xFFFFF0FFu, AA64_FMT_BARRIER, 0, {0, 0}},
{MN("clrex"), 0xD503305Fu, 0xFFFFF0FFu, AA64_FMT_BARRIER, 0, {0, 0}},
+
+ /* ----- Data-processing (1 source): RBIT / REV16 / REV / CLZ -----
+ * sf (bit31) free; opcode2 in bits[15:10] selects the operation. REV has
+ * a 32-bit (opcode2=000010) and 64-bit (000011) encoding, both "rev". */
+ {MN("rbit"), 0x5AC00000u, 0x7FFFFC00u, AA64_FMT_DP1, 0, {0, 0}},
+ {MN("rev16"), 0x5AC00400u, 0x7FFFFC00u, AA64_FMT_DP1, 0, {0, 0}},
+ {MN("rev"), 0x5AC00800u, 0x7FFFFC00u, AA64_FMT_DP1, 0, {0, 0}},
+ {MN("rev"), 0x5AC00C00u, 0x7FFFFC00u, AA64_FMT_DP1, 0, {0, 0}},
+ {MN("clz"), 0x5AC01000u, 0x7FFFFC00u, AA64_FMT_DP1, 0, {0, 0}},
+
+ /* ----- Bitfield move (SBFM / UBFM) -----
+ * sf/N free (read for W/X); opc (bits30:29) selects sbfm vs ubfm. Aliases
+ * (lsl/lsr/asr/ubfx/sxtb/...) decode to the raw sbfm/ubfm form. */
+ {MN("sbfm"), 0x13000000u, 0x7F800000u, AA64_FMT_BITFIELD, 0, {0, 0}},
+ {MN("ubfm"), 0x53000000u, 0x7F800000u, AA64_FMT_BITFIELD, 0, {0, 0}},
+
+ /* ----- Load/store, register offset [Xn, Xm{, LSL #s}] (V=0) -----
+ * Family bits[29:24]=111000, bit21=1, bits[11:10]=10; per-size opc rows. */
+ {MN("strb"), 0x38200800u, 0xFFE00C00u, AA64_FMT_LDST_REGOFF, 0, {0, 0}},
+ {MN("ldrb"), 0x38600800u, 0xFFE00C00u, AA64_FMT_LDST_REGOFF, 0, {0, 0}},
+ {MN("strh"), 0x78200800u, 0xFFE00C00u, AA64_FMT_LDST_REGOFF, 0, {0, 0}},
+ {MN("ldrh"), 0x78600800u, 0xFFE00C00u, AA64_FMT_LDST_REGOFF, 0, {0, 0}},
+ {MN("str"), 0xB8200800u, 0xFFE00C00u, AA64_FMT_LDST_REGOFF, 0, {0, 0}},
+ {MN("ldr"), 0xB8600800u, 0xFFE00C00u, AA64_FMT_LDST_REGOFF, 0, {0, 0}},
+ {MN("str"), 0xF8200800u, 0xFFE00C00u, AA64_FMT_LDST_REGOFF, 0, {0, 0}},
+ {MN("ldr"), 0xF8600800u, 0xFFE00C00u, AA64_FMT_LDST_REGOFF, 0, {0, 0}},
+
+ /* ----- FP data-processing (2 source): FMUL / FDIV / FADD / FSUB -----
+ * ftype (bits23:22) free, read for the s/d/h register prefix. */
+ {MN("fmul"), 0x1E200800u, 0xFF20FC00u, AA64_FMT_FP_DP2, 0, {0, 0}},
+ {MN("fdiv"), 0x1E201800u, 0xFF20FC00u, AA64_FMT_FP_DP2, 0, {0, 0}},
+ {MN("fadd"), 0x1E202800u, 0xFF20FC00u, AA64_FMT_FP_DP2, 0, {0, 0}},
+ {MN("fsub"), 0x1E203800u, 0xFF20FC00u, AA64_FMT_FP_DP2, 0, {0, 0}},
+ {MN("fmax"), 0x1E204800u, 0xFF20FC00u, AA64_FMT_FP_DP2, 0, {0, 0}},
+ {MN("fmin"), 0x1E205800u, 0xFF20FC00u, AA64_FMT_FP_DP2, 0, {0, 0}},
+ {MN("fnmul"), 0x1E208800u, 0xFF20FC00u, AA64_FMT_FP_DP2, 0, {0, 0}},
+
+ /* ----- FP compare (FCMP, register form) ----- */
+ {MN("fcmp"), 0x1E202000u, 0xFF20FC1Fu, AA64_FMT_FP_CMP, 0, {0, 0}},
+
+ /* ----- FP precision convert (FCVT single<->double<->half) ----- */
+ {MN("fcvt"), 0x1E224000u, 0xFF3E7C00u, AA64_FMT_FP_CVT, 0, {0, 0}},
+
+ /* ----- FP data-processing (1 source): FMOV / FABS / FNEG / FSQRT ----- */
+ {MN("fmov"), 0x1E204000u, 0xFF3FFC00u, AA64_FMT_FP_DP1, 0, {0, 0}},
+ {MN("fabs"), 0x1E20C000u, 0xFF3FFC00u, AA64_FMT_FP_DP1, 0, {0, 0}},
+ {MN("fneg"), 0x1E214000u, 0xFF3FFC00u, AA64_FMT_FP_DP1, 0, {0, 0}},
+ {MN("fsqrt"), 0x1E21C000u, 0xFF3FFC00u, AA64_FMT_FP_DP1, 0, {0, 0}},
+
+ /* ----- FP<->int convert + FMOV gpr<->fp -----
+ * sf (bit31) and ftype free; opcode (bits20:16) selects op + direction. */
+ {MN("scvtf"), 0x1E220000u, 0x7F3FFC00u, AA64_FMT_FP_INT_CVT, 0, {0, 0}},
+ {MN("ucvtf"), 0x1E230000u, 0x7F3FFC00u, AA64_FMT_FP_INT_CVT, 0, {0, 0}},
+ {MN("fcvtzs"), 0x1E380000u, 0x7F3FFC00u, AA64_FMT_FP_INT_CVT, 0, {0, 0}},
+ {MN("fcvtzu"), 0x1E390000u, 0x7F3FFC00u, AA64_FMT_FP_INT_CVT, 0, {0, 0}},
+ {MN("fmov"), 0x1E260000u, 0x7F3FFC00u, AA64_FMT_FP_INT_CVT, 0, {0, 0}},
+ {MN("fmov"), 0x1E270000u, 0x7F3FFC00u, AA64_FMT_FP_INT_CVT, 0, {0, 0}},
};
#undef MN
@@ -820,6 +877,123 @@ static void print_barrier(StrBuf* sb, u32 w, const AA64InsnDesc* desc) {
}
}
+/* FP scalar size from the 2-bit ftype field: 00=single, 01=double, 11=half. */
+static char aa64_ftype_prefix(u32 ftype) {
+ return ftype == 0u ? 's' : (ftype == 1u ? 'd' : 'h');
+}
+
+/* FADD/FSUB/FMUL/FDIV: Vd, Vn, Vm (all the same ftype). */
+static void print_fp_dp2(StrBuf* sb, u32 w) {
+ char p = aa64_ftype_prefix((w >> 22) & 3u);
+ emit_vreg(sb, w & 0x1fu, p);
+ strbuf_puts(sb, ", ");
+ emit_vreg(sb, (w >> 5) & 0x1fu, p);
+ strbuf_puts(sb, ", ");
+ emit_vreg(sb, (w >> 16) & 0x1fu, p);
+}
+
+/* FMOV(reg)/FNEG/FABS/FSQRT: Vd, Vn (same ftype). */
+static void print_fp_dp1(StrBuf* sb, u32 w) {
+ char p = aa64_ftype_prefix((w >> 22) & 3u);
+ emit_vreg(sb, w & 0x1fu, p);
+ strbuf_puts(sb, ", ");
+ emit_vreg(sb, (w >> 5) & 0x1fu, p);
+}
+
+/* FCMP: Vn, Vm. */
+static void print_fp_cmp(StrBuf* sb, u32 w) {
+ char p = aa64_ftype_prefix((w >> 22) & 3u);
+ emit_vreg(sb, (w >> 5) & 0x1fu, p);
+ strbuf_puts(sb, ", ");
+ emit_vreg(sb, (w >> 16) & 0x1fu, p);
+}
+
+/* FCVT precision change: Vd has the destination type (opc, bits 16:15), Vn the
+ * source type (ftype, bits 23:22). */
+static void print_fp_cvt(StrBuf* sb, u32 w) {
+ char src = aa64_ftype_prefix((w >> 22) & 3u);
+ char dst = aa64_ftype_prefix((w >> 15) & 3u);
+ emit_vreg(sb, w & 0x1fu, dst);
+ strbuf_puts(sb, ", ");
+ emit_vreg(sb, (w >> 5) & 0x1fu, src);
+}
+
+/* SCVTF/UCVTF/FCVTZS/FCVTZU and FMOV gpr<->fp. The opcode (bits 20:16)
+ * selects the direction: fcvtzs/fcvtzu and fmov-to-gpr produce a GPR dst with
+ * an FP src; scvtf/ucvtf and fmov-to-fp produce an FP dst with a GPR src.
+ * sf (bit 31) is the GPR width, ftype (bits 23:22) the FP size. */
+static void print_fp_int_cvt(StrBuf* sb, u32 w) {
+ u32 opcode = (w >> 16) & 0x1fu;
+ int sf = (int)((w >> 31) & 1u);
+ char fp = aa64_ftype_prefix((w >> 22) & 3u);
+ u32 rd = w & 0x1fu, rn = (w >> 5) & 0x1fu;
+ int gpr_dst = (opcode == 0x18u /*fcvtzs*/ || opcode == 0x19u /*fcvtzu*/ ||
+ opcode == 0x06u /*fmov fp->gpr*/);
+ if (gpr_dst) {
+ emit_reg(sb, rd, sf, 0);
+ strbuf_puts(sb, ", ");
+ emit_vreg(sb, rn, fp);
+ } else {
+ emit_vreg(sb, rd, fp);
+ strbuf_puts(sb, ", ");
+ emit_reg(sb, rn, sf, 0);
+ }
+}
+
+/* RBIT/REV16/REV32/REV/CLZ: Rd, Rn (sf = bit 31). */
+static void print_dp1(StrBuf* sb, u32 w) {
+ int sf = (int)((w >> 31) & 1u);
+ emit_reg(sb, w & 0x1fu, sf, 0);
+ strbuf_puts(sb, ", ");
+ emit_reg(sb, (w >> 5) & 0x1fu, sf, 0);
+}
+
+/* SBFM/UBFM: Rd, Rn, #immr, #imms. */
+static void print_bitfield(StrBuf* sb, u32 w) {
+ int sf = (int)((w >> 31) & 1u);
+ emit_reg(sb, w & 0x1fu, sf, 0);
+ strbuf_puts(sb, ", ");
+ emit_reg(sb, (w >> 5) & 0x1fu, sf, 0);
+ strbuf_puts(sb, ", #");
+ strbuf_put_u64(sb, (u64)((w >> 16) & 0x3fu));
+ strbuf_puts(sb, ", #");
+ strbuf_put_u64(sb, (u64)((w >> 10) & 0x3fu));
+}
+
+/* Register-offset load/store: Rt, [Xn, Xm{, LSL #s}]. Rt width follows the
+ * size field (X for 64-bit accesses, W otherwise); the index extend is LSL
+ * for option=011 (UXTX), with shift amount S ? size : 0. */
+static void print_ldst_regoff(StrBuf* sb, u32 w) {
+ u32 size = (w >> 30) & 3u;
+ u32 option = (w >> 13) & 7u;
+ u32 s_bit = (w >> 12) & 1u;
+ int xt = (size == 3u);
+ emit_reg(sb, w & 0x1fu, xt, 0);
+ strbuf_puts(sb, ", [");
+ emit_reg(sb, (w >> 5) & 0x1fu, /*sf=*/1, /*sp_means_sp=*/1);
+ strbuf_puts(sb, ", ");
+ /* UXTW/SXTW use a W index; UXTX/SXTX (LSL) use an X index. */
+ emit_reg(sb, (w >> 16) & 0x1fu, (option & 1u) ? 1 : 0, 0);
+ if (option != 3u) {
+ /* Register-offset extends: only UXTW(010)/SXTW(110)/SXTX(111) are valid
+ * besides LSL/UXTX(011). */
+ static const char* ext[8] = {0, 0, "uxtw", 0, 0, 0, "sxtw", "sxtx"};
+ const char* e = ext[option];
+ if (e) {
+ strbuf_puts(sb, ", ");
+ strbuf_puts(sb, e);
+ if (s_bit) {
+ strbuf_puts(sb, " #");
+ strbuf_put_u64(sb, (u64)size);
+ }
+ }
+ } else if (s_bit) {
+ strbuf_puts(sb, ", lsl #");
+ strbuf_put_u64(sb, (u64)size);
+ }
+ strbuf_putc(sb, ']');
+}
+
void aa64_print_operands(StrBuf* sb, const AA64InsnDesc* desc, u32 word,
u64 vaddr) {
switch ((AA64Format)desc->fmt) {
@@ -882,6 +1056,30 @@ void aa64_print_operands(StrBuf* sb, const AA64InsnDesc* desc, u32 word,
case AA64_FMT_BARRIER:
print_barrier(sb, word, desc);
break;
+ case AA64_FMT_DP1:
+ print_dp1(sb, word);
+ break;
+ case AA64_FMT_BITFIELD:
+ print_bitfield(sb, word);
+ break;
+ case AA64_FMT_LDST_REGOFF:
+ print_ldst_regoff(sb, word);
+ break;
+ case AA64_FMT_FP_DP2:
+ print_fp_dp2(sb, word);
+ break;
+ case AA64_FMT_FP_DP1:
+ print_fp_dp1(sb, word);
+ break;
+ case AA64_FMT_FP_CMP:
+ print_fp_cmp(sb, word);
+ break;
+ case AA64_FMT_FP_CVT:
+ print_fp_cvt(sb, word);
+ break;
+ case AA64_FMT_FP_INT_CVT:
+ print_fp_int_cvt(sb, word);
+ break;
}
}
diff --git a/src/arch/aa64/isa.h b/src/arch/aa64/isa.h
@@ -57,6 +57,15 @@ typedef enum AA64Format {
AA64_FMT_EXCEPT, /* exception generation (BRK / SVC / HVC / ...) */
AA64_FMT_HINT, /* hint (NOP / YIELD / ...) */
AA64_FMT_BARRIER, /* memory barrier (DMB / DSB / ISB / CLREX) */
+ AA64_FMT_DP1, /* data-processing, 1 source (RBIT/REV/REV16/CLZ) */
+ AA64_FMT_BITFIELD, /* bitfield move (SBFM / UBFM): Rd, Rn, #immr, #imms */
+ AA64_FMT_LDST_REGOFF, /* load/store, register offset [Xn, Xm{, LSL #s}] */
+ AA64_FMT_FP_DP2, /* FP data-processing 2-source (FADD/FSUB/FMUL/FDIV) */
+ AA64_FMT_FP_DP1, /* FP data-processing 1-source (FMOV/FNEG/FABS/FSQRT) */
+ AA64_FMT_FP_CMP, /* FP compare (FCMP) */
+ AA64_FMT_FP_CVT, /* FP precision convert (FCVT single<->double) */
+ AA64_FMT_FP_INT_CVT, /* FP<->int convert + FMOV gpr<->fp
+ * (SCVTF/UCVTF/FCVTZS/FCVTZU/FMOV) */
} AA64Format;
/* ---- AsmFlags column on AA64InsnDesc ----
diff --git a/test/asm/decode/aa64_fp_bitfield_dp1.expected.txt b/test/asm/decode/aa64_fp_bitfield_dp1.expected.txt
@@ -0,0 +1,21 @@
+0: clz x0, x1
+4: clz w0, w1
+8: rbit x2, x3
+c: rev x4, x5
+10: rev w6, w7
+14: rev16 x8, x9
+18: fmov d0, d1
+1c: fmov s2, s3
+20: fabs d4, d5
+24: fsqrt s6, s7
+28: fcmp d0, d1
+2c: fcmp s2, s3
+30: fmov x0, d8
+34: fmov d9, x10
+38: fmov w11, s12
+3c: fmov s13, w14
+40: scvtf s0, w1
+44: ucvtf d2, x3
+48: fcvtzu w4, d5
+4c: ubfm x0, x1, #4, #20
+50: ret
diff --git a/test/asm/decode/aa64_fp_bitfield_dp1.hex b/test/asm/decode/aa64_fp_bitfield_dp1.hex
@@ -0,0 +1 @@
+2010c0da2010c05a6200c0daa40cc0dae608c05a2805c0da2040601e6240201ea4c0601ee6c0211e0020611e4020231e0001669e4901679e8b01261ecd01271e2000221e6200639ea400791e205044d3c0035fd6
+\ No newline at end of file
diff --git a/test/asm/decode/aa64_fp_bitfield_dp1.targets b/test/asm/decode/aa64_fp_bitfield_dp1.targets
@@ -0,0 +1 @@
+aa64
+\ No newline at end of file