kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

isa.c (51748B)


      1 /* AArch64 instruction descriptor table + operand print/parse dispatch.
      2  *
      3  * The table mirrors the inline encoders in aa64_isa.h: each row records
      4  * (mnemonic, match, mask, format, flags) so the disassembler can identify
      5  * a raw 32-bit word with one mask-and-compare and then dispatch on the
      6  * format to extract operand fields via the same unpack functions the
      7  * encoder uses.  Encoder and decoder share the bit knowledge — when an
      8  * opcode value or field position changes, both sides update at one site.
      9  *
     10  * Mask values include the family mask plus the bits that distinguish a
     11  * specific instruction from its siblings in the same family.  sf (bit 31)
     12  * is intentionally a don't-care for formats where both 32- and 64-bit
     13  * forms share one row; the unpacker reads sf separately when printing
     14  * operands.
     15  *
     16  * Row ordering: first-match wins.  Aliases (rows with AA64_ASMFL_ALIAS)
     17  * are tighter masks placed BEFORE the canonical row they alias so the
     18  * disassembler renders the alias spelling.  The assembler accepts both
     19  * spellings — they map to the same encoded word. */
     20 
     21 #include "arch/aa64/isa.h"
     22 
     23 #include <stddef.h>
     24 
     25 /* Mnemonic Slice literal for a static table row (compile-time length). */
     26 #define MN(s) {{(s)}, sizeof(s) - 1}
     27 
     28 /* Load/store unsigned-imm12 opc field values for the V=0 signed sub-word
     29  * loads. opc=00/01 are STR/LDR (AA64_LDST_OPC_STR/LDR in isa.h); the signed
     30  * loads reuse the same size/imm12 layout but sign-extend the loaded value:
     31  *   opc=10 (LDRS_X) sign-extends to the 64-bit X destination,
     32  *   opc=11 (LDRS_W) sign-extends to the 32-bit W destination. */
     33 #define AA64_LDST_OPC_LDRS_X 2u
     34 #define AA64_LDST_OPC_LDRS_W 3u
     35 
     36 static const AA64SysRegName aa64_sysreg_names[] = {
     37     {"tpidr_el0", 3, 3, 13, 0, 2}, {"tpidrro_el0", 3, 3, 13, 0, 3},
     38     {"tpidr_el1", 3, 0, 13, 0, 4}, {"midr_el1", 3, 0, 0, 0, 0},
     39     {"mpidr_el1", 3, 0, 0, 0, 5},  {"ctr_el0", 3, 3, 0, 0, 1},
     40     {"dczid_el0", 3, 3, 0, 0, 7},  {"nzcv", 3, 3, 4, 2, 0},
     41     {"daif", 3, 3, 4, 2, 1},       {"fpcr", 3, 3, 4, 4, 0},
     42     {"fpsr", 3, 3, 4, 4, 1},
     43 };
     44 
     45 static char aa64_lc(char c) {
     46   return (c >= 'A' && c <= 'Z') ? (char)(c + ('a' - 'A')) : c;
     47 }
     48 
     49 static int aa64_icase_eq(const char* a, size_t an, const char* b) {
     50   size_t i = 0;
     51   for (; i < an && b[i]; ++i) {
     52     if (aa64_lc(a[i]) != aa64_lc(b[i])) return 0;
     53   }
     54   return i == an && b[i] == '\0';
     55 }
     56 
     57 static int aa64_sysreg_rd_field(const char* s, size_t n, size_t* i, u32* out) {
     58   u32 v = 0;
     59   if (*i >= n || s[*i] < '0' || s[*i] > '9') return 0;
     60   while (*i < n && s[*i] >= '0' && s[*i] <= '9') {
     61     v = v * 10u + (u32)(s[*i] - '0');
     62     if (v > 15u) return 0;
     63     ++(*i);
     64   }
     65   *out = v;
     66   return 1;
     67 }
     68 
     69 static int aa64_parse_generic_sysreg(const char* s, size_t n, u32* op0,
     70                                      u32* op1, u32* crn, u32* crm, u32* op2) {
     71   size_t i = 0;
     72   if (i >= n || aa64_lc(s[i]) != 's') return 0;
     73   ++i;
     74   if (!aa64_sysreg_rd_field(s, n, &i, op0)) return 0;
     75   if (i >= n || s[i] != '_') return 0;
     76   ++i;
     77   if (!aa64_sysreg_rd_field(s, n, &i, op1)) return 0;
     78   if (i + 1 >= n || s[i] != '_' || aa64_lc(s[i + 1]) != 'c') return 0;
     79   i += 2;
     80   if (!aa64_sysreg_rd_field(s, n, &i, crn)) return 0;
     81   if (i + 1 >= n || s[i] != '_' || aa64_lc(s[i + 1]) != 'c') return 0;
     82   i += 2;
     83   if (!aa64_sysreg_rd_field(s, n, &i, crm)) return 0;
     84   if (i >= n || s[i] != '_') return 0;
     85   ++i;
     86   if (!aa64_sysreg_rd_field(s, n, &i, op2)) return 0;
     87   return i == n && *op0 <= 3u && *op1 <= 7u && *crn <= 15u && *crm <= 15u &&
     88          *op2 <= 7u;
     89 }
     90 
     91 int aa64_sysreg_by_name(const char* s, size_t n, u32* op0, u32* op1, u32* crn,
     92                         u32* crm, u32* op2) {
     93   size_t k;
     94   if (!s || !n) return 0;
     95   for (k = 0; k < sizeof aa64_sysreg_names / sizeof aa64_sysreg_names[0]; ++k) {
     96     if (aa64_icase_eq(s, n, aa64_sysreg_names[k].name)) {
     97       *op0 = aa64_sysreg_names[k].op0;
     98       *op1 = aa64_sysreg_names[k].op1;
     99       *crn = aa64_sysreg_names[k].crn;
    100       *crm = aa64_sysreg_names[k].crm;
    101       *op2 = aa64_sysreg_names[k].op2;
    102       return 1;
    103     }
    104   }
    105   return aa64_parse_generic_sysreg(s, n, op0, op1, crn, crm, op2);
    106 }
    107 
    108 const char* aa64_sysreg_name(u32 op0, u32 op1, u32 crn, u32 crm, u32 op2) {
    109   size_t k;
    110   for (k = 0; k < sizeof aa64_sysreg_names / sizeof aa64_sysreg_names[0]; ++k) {
    111     const AA64SysRegName* r = &aa64_sysreg_names[k];
    112     if (r->op0 == op0 && r->op1 == op1 && r->crn == crn && r->crm == crm &&
    113         r->op2 == op2)
    114       return r->name;
    115   }
    116   return NULL;
    117 }
    118 
    119 const AA64InsnDesc aa64_insn_table[] = {
    120     /* ----- Move-wide immediate (MOVN / MOVZ / MOVK) ----- */
    121     {MN("movn"), 0x12800000u, 0x7F800000u, AA64_FMT_MOVEWIDE, 0, {0, 0}},
    122     {MN("movz"), 0x52800000u, 0x7F800000u, AA64_FMT_MOVEWIDE, 0, {0, 0}},
    123     {MN("movk"), 0x72800000u, 0x7F800000u, AA64_FMT_MOVEWIDE, 0, {0, 0}},
    124 
    125     /* ----- Logical, shifted register -----
    126      * Alias MOV Rd, Rm is ORR Rd, ZR, Rm with shift=0, imm6=0.  The mask
    127      * pins Rn (bits 9:5) to 11111 (ZR) and shift/imm6 to 0 so only the
    128      * MOV spelling matches; broader ORR rows below catch the rest. */
    129     {MN("mov"),
    130      0x2A0003E0u,
    131      0x7FE0FFE0u,
    132      AA64_FMT_LOG_SR,
    133      AA64_ASMFL_ALIAS,
    134      {0, 0}},
    135     /* MVN Rd, Rm  ≡  ORN Rd, ZR, Rm  (logical N=1, Rn=ZR, no shift) */
    136     {MN("mvn"),
    137      0x2A2003E0u,
    138      0x7FE0FFE0u,
    139      AA64_FMT_LOG_SR,
    140      AA64_ASMFL_ALIAS,
    141      {0, 0}},
    142     {MN("and"), 0x0A000000u, 0x7F200000u, AA64_FMT_LOG_SR, 0, {0, 0}},
    143     {MN("bic"), 0x0A200000u, 0x7F200000u, AA64_FMT_LOG_SR, 0, {0, 0}},
    144     {MN("orr"), 0x2A000000u, 0x7F200000u, AA64_FMT_LOG_SR, 0, {0, 0}},
    145     {MN("orn"), 0x2A200000u, 0x7F200000u, AA64_FMT_LOG_SR, 0, {0, 0}},
    146     {MN("eor"), 0x4A000000u, 0x7F200000u, AA64_FMT_LOG_SR, 0, {0, 0}},
    147     {MN("eon"), 0x4A200000u, 0x7F200000u, AA64_FMT_LOG_SR, 0, {0, 0}},
    148     {MN("ands"), 0x6A000000u, 0x7F200000u, AA64_FMT_LOG_SR, 0, {0, 0}},
    149     {MN("bics"), 0x6A200000u, 0x7F200000u, AA64_FMT_LOG_SR, 0, {0, 0}},
    150 
    151     /* ----- Add/Sub, shifted register -----
    152      * NEG Rd, Rm  ≡  SUB Rd, ZR, Rm  (Rn=ZR, shift=0, imm6=0). */
    153     {MN("neg"),
    154      0x4B0003E0u,
    155      0x7FE0FFE0u,
    156      AA64_FMT_ADDSUB_SR,
    157      AA64_ASMFL_ALIAS,
    158      {0, 0}},
    159     {MN("negs"),
    160      0x6B0003E0u,
    161      0x7FE0FFE0u,
    162      AA64_FMT_ADDSUB_SR,
    163      AA64_ASMFL_ALIAS,
    164      {0, 0}},
    165     /* CMP Rn, Rm  ≡  SUBS ZR, Rn, Rm. */
    166     {MN("cmp"),
    167      0x6B00001Fu,
    168      0x7F20001Fu,
    169      AA64_FMT_ADDSUB_SR,
    170      AA64_ASMFL_ALIAS,
    171      {0, 0}},
    172     /* CMN Rn, Rm  ≡  ADDS ZR, Rn, Rm. */
    173     {MN("cmn"),
    174      0x2B00001Fu,
    175      0x7F20001Fu,
    176      AA64_FMT_ADDSUB_SR,
    177      AA64_ASMFL_ALIAS,
    178      {0, 0}},
    179     {MN("add"), 0x0B000000u, 0x7F200000u, AA64_FMT_ADDSUB_SR, 0, {0, 0}},
    180     {MN("adds"), 0x2B000000u, 0x7F200000u, AA64_FMT_ADDSUB_SR, 0, {0, 0}},
    181     {MN("sub"), 0x4B000000u, 0x7F200000u, AA64_FMT_ADDSUB_SR, 0, {0, 0}},
    182     {MN("subs"), 0x6B000000u, 0x7F200000u, AA64_FMT_ADDSUB_SR, 0, {0, 0}},
    183 
    184     /* ----- Data-processing 3-source -----
    185      * MUL Rd, Rn, Rm  ≡  MADD Rd, Rn, Rm, ZR  (Ra=ZR, op31=0, o0=0). */
    186     {MN("mul"),
    187      0x1B007C00u,
    188      0x7FE0FC00u,
    189      AA64_FMT_DP3,
    190      AA64_ASMFL_ALIAS,
    191      {0, 0}},
    192     /* MNEG Rd, Rn, Rm  ≡  MSUB Rd, Rn, Rm, ZR. */
    193     {MN("mneg"),
    194      0x1B00FC00u,
    195      0x7FE0FC00u,
    196      AA64_FMT_DP3,
    197      AA64_ASMFL_ALIAS,
    198      {0, 0}},
    199     {MN("madd"), 0x1B000000u, 0x7FE08000u, AA64_FMT_DP3, 0, {0, 0}},
    200     {MN("msub"), 0x1B008000u, 0x7FE08000u, AA64_FMT_DP3, 0, {0, 0}},
    201 
    202     /* ----- Data-processing 2-source ----- */
    203     {MN("udiv"), 0x1AC00800u, 0x5FE0FC00u, AA64_FMT_DP2, 0, {0, 0}},
    204     {MN("sdiv"), 0x1AC00C00u, 0x5FE0FC00u, AA64_FMT_DP2, 0, {0, 0}},
    205     {MN("lslv"), 0x1AC02000u, 0x5FE0FC00u, AA64_FMT_DP2, 0, {0, 0}},
    206     {MN("lsrv"), 0x1AC02400u, 0x5FE0FC00u, AA64_FMT_DP2, 0, {0, 0}},
    207     {MN("asrv"), 0x1AC02800u, 0x5FE0FC00u, AA64_FMT_DP2, 0, {0, 0}},
    208     {MN("rorv"), 0x1AC02C00u, 0x5FE0FC00u, AA64_FMT_DP2, 0, {0, 0}},
    209 
    210     /* ----- Conditional select -----
    211      * CSET Rd, cond  ≡ CSINC Rd, ZR, ZR, invert(cond).
    212      * CSETM Rd, cond ≡ CSINV Rd, ZR, ZR, invert(cond).
    213      * These aliases are expressible as fixed Rn/Rm=ZR masks, so put them
    214      * before the canonical conditional-select rows. */
    215     {MN("cset"),
    216      0x1A9F07E0u,
    217      0x7FE00C00u | (0x1Fu << 16) | (0x1Fu << 5),
    218      AA64_FMT_CONDSEL,
    219      AA64_ASMFL_ALIAS,
    220      {0, 0}},
    221     {MN("csetm"),
    222      0x5A9F03E0u,
    223      0x7FE00C00u | (0x1Fu << 16) | (0x1Fu << 5),
    224      AA64_FMT_CONDSEL,
    225      AA64_ASMFL_ALIAS,
    226      {0, 0}},
    227     {MN("csel"), 0x1A800000u, 0x7FE00C00u, AA64_FMT_CONDSEL, 0, {0, 0}},
    228     {MN("csinc"), 0x1A800400u, 0x7FE00C00u, AA64_FMT_CONDSEL, 0, {0, 0}},
    229     {MN("csinv"), 0x5A800000u, 0x7FE00C00u, AA64_FMT_CONDSEL, 0, {0, 0}},
    230     {MN("csneg"), 0x5A800400u, 0x7FE00C00u, AA64_FMT_CONDSEL, 0, {0, 0}},
    231 
    232     /* ----- Unconditional branch (register) -----
    233      * RET aliases its no-operand spelling to RET X30 (Rn=11110).  The
    234      * tighter row matches when Rn=30 and prints "ret" without operands;
    235      * the looser row below catches RET Xn for other Rn. */
    236     {MN("ret"),
    237      0xD65F03C0u,
    238      0xFFFFFFFFu,
    239      AA64_FMT_BR_REG,
    240      AA64_ASMFL_ALIAS | AA64_ASMFL_NORN,
    241      {0, 0}},
    242     {MN("br"), 0xD61F0000u, 0xFFFFFC1Fu, AA64_FMT_BR_REG, 0, {0, 0}},
    243     {MN("blr"), 0xD63F0000u, 0xFFFFFC1Fu, AA64_FMT_BR_REG, 0, {0, 0}},
    244     {MN("ret"), 0xD65F0000u, 0xFFFFFC1Fu, AA64_FMT_BR_REG, 0, {0, 0}},
    245 
    246     /* ----- PC-relative addressing ----- */
    247     {MN("adr"), 0x10000000u, 0x9F000000u, AA64_FMT_PCREL_ADR, 0, {0, 0}},
    248     {MN("adrp"), 0x90000000u, 0x9F000000u, AA64_FMT_PCREL_ADR, 0, {0, 0}},
    249 
    250     /* ----- Add/Sub immediate -----
    251      * CMP/CMN immediate are the Rd=ZR aliases of SUBS/ADDS immediate. */
    252     {MN("cmn"),
    253      0x3100001Fu,
    254      0x7F00001Fu,
    255      AA64_FMT_ADDSUB_IMM,
    256      AA64_ASMFL_ALIAS,
    257      {0, 0}},
    258     {MN("cmp"),
    259      0x7100001Fu,
    260      0x7F00001Fu,
    261      AA64_FMT_ADDSUB_IMM,
    262      AA64_ASMFL_ALIAS,
    263      {0, 0}},
    264     {MN("add"), 0x11000000u, 0x7F000000u, AA64_FMT_ADDSUB_IMM, 0, {0, 0}},
    265     {MN("adds"), 0x31000000u, 0x7F000000u, AA64_FMT_ADDSUB_IMM, 0, {0, 0}},
    266     {MN("sub"), 0x51000000u, 0x7F000000u, AA64_FMT_ADDSUB_IMM, 0, {0, 0}},
    267     {MN("subs"), 0x71000000u, 0x7F000000u, AA64_FMT_ADDSUB_IMM, 0, {0, 0}},
    268 
    269     /* ----- Load/store, unsigned 12-bit immediate (scaled) -----
    270      * Mask: family bits 29:27 + 25:24 + size(31:30) + V(26) + opc(23:22).
    271      *
    272      * Signed sub-word loads (LDRSB/LDRSH/LDRSW) share this exact format but
    273      * select opc=10 (sign-extend to X) or opc=11 (sign-extend to W).  They
    274      * are listed BEFORE the unsigned STR/LDR rows so first-match-wins picks
    275      * the signed spelling (the masks are disjoint by opc, so the relative
    276      * order is not load-bearing, but keeping them first documents intent).
    277      *   size=00 opc=10 LDRSB Xt ; size=00 opc=11 LDRSB Wt
    278      *   size=01 opc=10 LDRSH Xt ; size=01 opc=11 LDRSH Wt
    279      *   size=10 opc=10 LDRSW Xt (no Wt form: that opc=11 slot is PRFUM). */
    280     {MN("ldrsb"), 0x39800000u, 0xFFC00000u, AA64_FMT_LDST_UIMM, 0, {0, 0}},
    281     {MN("ldrsb"), 0x39C00000u, 0xFFC00000u, AA64_FMT_LDST_UIMM, 0, {0, 0}},
    282     {MN("ldrsh"), 0x79800000u, 0xFFC00000u, AA64_FMT_LDST_UIMM, 0, {0, 0}},
    283     {MN("ldrsh"), 0x79C00000u, 0xFFC00000u, AA64_FMT_LDST_UIMM, 0, {0, 0}},
    284     {MN("ldrsw"), 0xB9800000u, 0xFFC00000u, AA64_FMT_LDST_UIMM, 0, {0, 0}},
    285     {MN("strb"), 0x39000000u, 0xFFC00000u, AA64_FMT_LDST_UIMM, 0, {0, 0}},
    286     {MN("ldrb"), 0x39400000u, 0xFFC00000u, AA64_FMT_LDST_UIMM, 0, {0, 0}},
    287     {MN("strh"), 0x79000000u, 0xFFC00000u, AA64_FMT_LDST_UIMM, 0, {0, 0}},
    288     {MN("ldrh"), 0x79400000u, 0xFFC00000u, AA64_FMT_LDST_UIMM, 0, {0, 0}},
    289     {MN("str"), 0xB9000000u, 0xFFC00000u, AA64_FMT_LDST_UIMM, 0, {0, 0}}, /* 32
    290                                                                            */
    291     {MN("ldr"), 0xB9400000u, 0xFFC00000u, AA64_FMT_LDST_UIMM, 0, {0, 0}},
    292     {MN("str"),
    293      0xF9000000u,
    294      0xFFC00000u,
    295      AA64_FMT_LDST_UIMM,
    296      AA64_ASMFL_SF1,
    297      {0, 0}}, /* 64 */
    298     {MN("ldr"),
    299      0xF9400000u,
    300      0xFFC00000u,
    301      AA64_FMT_LDST_UIMM,
    302      AA64_ASMFL_SF1,
    303      {0, 0}},
    304     /* SIMD/FP scaled loads/stores (V=1).  size 0..2 select B/H/S; size=3
    305      * selects D; the 128-bit Q form uses size=00 with opc bit 1 set and
    306      * is not yet emitted by codegen. */
    307     {MN("str"), 0x3D000000u, 0xFFC00000u, AA64_FMT_LDST_UIMM, 0, {0, 0}}, /* B
    308                                                                            */
    309     {MN("ldr"), 0x3D400000u, 0xFFC00000u, AA64_FMT_LDST_UIMM, 0, {0, 0}},
    310     {MN("str"), 0x7D000000u, 0xFFC00000u, AA64_FMT_LDST_UIMM, 0, {0, 0}}, /* H
    311                                                                            */
    312     {MN("ldr"), 0x7D400000u, 0xFFC00000u, AA64_FMT_LDST_UIMM, 0, {0, 0}},
    313     {MN("str"), 0xBD000000u, 0xFFC00000u, AA64_FMT_LDST_UIMM, 0, {0, 0}}, /* S
    314                                                                            */
    315     {MN("ldr"), 0xBD400000u, 0xFFC00000u, AA64_FMT_LDST_UIMM, 0, {0, 0}},
    316     {MN("str"),
    317      0xFD000000u,
    318      0xFFC00000u,
    319      AA64_FMT_LDST_UIMM,
    320      AA64_ASMFL_SF1,
    321      {0, 0}}, /* D */
    322     {MN("ldr"),
    323      0xFD400000u,
    324      0xFFC00000u,
    325      AA64_FMT_LDST_UIMM,
    326      AA64_ASMFL_SF1,
    327      {0, 0}},
    328 
    329     /* ----- Load/store, unscaled signed 9-bit immediate (LDUR/STUR) -----
    330      * V=0 first, V=1 next.  Per-row mask narrows size+V+opc; family mask
    331      * pins the high family bits + the SIMM9-vs-other-variant selector. */
    332     {MN("sturb"), 0x38000000u, 0xFFE00C00u, AA64_FMT_LDST_SIMM9, 0, {0, 0}},
    333     {MN("ldurb"), 0x38400000u, 0xFFE00C00u, AA64_FMT_LDST_SIMM9, 0, {0, 0}},
    334     {MN("sturh"), 0x78000000u, 0xFFE00C00u, AA64_FMT_LDST_SIMM9, 0, {0, 0}},
    335     {MN("ldurh"), 0x78400000u, 0xFFE00C00u, AA64_FMT_LDST_SIMM9, 0, {0, 0}},
    336     /* Signed unscaled loads (opc=10 → 64-bit Xt, opc=11 → 32-bit Wt). The
    337      * printer keys the register width on opc; size selects the mnemonic. */
    338     {MN("ldursb"), 0x38800000u, 0xFFE00C00u, AA64_FMT_LDST_SIMM9, 0, {0, 0}},
    339     {MN("ldursb"), 0x38C00000u, 0xFFE00C00u, AA64_FMT_LDST_SIMM9, 0, {0, 0}},
    340     {MN("ldursh"), 0x78800000u, 0xFFE00C00u, AA64_FMT_LDST_SIMM9, 0, {0, 0}},
    341     {MN("ldursh"), 0x78C00000u, 0xFFE00C00u, AA64_FMT_LDST_SIMM9, 0, {0, 0}},
    342     {MN("ldursw"), 0xB8800000u, 0xFFE00C00u, AA64_FMT_LDST_SIMM9, 0, {0, 0}},
    343     {MN("stur"),
    344      0xB8000000u,
    345      0xFFE00C00u,
    346      AA64_FMT_LDST_SIMM9,
    347      0,
    348      {0, 0}}, /* 32 */
    349     {MN("ldur"), 0xB8400000u, 0xFFE00C00u, AA64_FMT_LDST_SIMM9, 0, {0, 0}},
    350     {MN("stur"),
    351      0xF8000000u,
    352      0xFFE00C00u,
    353      AA64_FMT_LDST_SIMM9,
    354      AA64_ASMFL_SF1,
    355      {0, 0}},
    356     {MN("ldur"),
    357      0xF8400000u,
    358      0xFFE00C00u,
    359      AA64_FMT_LDST_SIMM9,
    360      AA64_ASMFL_SF1,
    361      {0, 0}},
    362     {MN("stur"),
    363      0x3C000000u,
    364      0xFFE00C00u,
    365      AA64_FMT_LDST_SIMM9,
    366      0,
    367      {0, 0}}, /* B */
    368     {MN("ldur"), 0x3C400000u, 0xFFE00C00u, AA64_FMT_LDST_SIMM9, 0, {0, 0}},
    369     {MN("stur"),
    370      0x7C000000u,
    371      0xFFE00C00u,
    372      AA64_FMT_LDST_SIMM9,
    373      0,
    374      {0, 0}}, /* H */
    375     {MN("ldur"), 0x7C400000u, 0xFFE00C00u, AA64_FMT_LDST_SIMM9, 0, {0, 0}},
    376     {MN("stur"),
    377      0xBC000000u,
    378      0xFFE00C00u,
    379      AA64_FMT_LDST_SIMM9,
    380      0,
    381      {0, 0}}, /* S */
    382     {MN("ldur"), 0xBC400000u, 0xFFE00C00u, AA64_FMT_LDST_SIMM9, 0, {0, 0}},
    383     {MN("stur"),
    384      0xFC000000u,
    385      0xFFE00C00u,
    386      AA64_FMT_LDST_SIMM9,
    387      AA64_ASMFL_SF1,
    388      {0, 0}}, /* D */
    389     {MN("ldur"),
    390      0xFC400000u,
    391      0xFFE00C00u,
    392      AA64_FMT_LDST_SIMM9,
    393      AA64_ASMFL_SF1,
    394      {0, 0}},
    395 
    396     /* ----- Load/store exclusive + acquire/release ordered -----
    397      * Family bits[29:24]=001000, o1[21]=0 (single register). The word/dword
    398      * mnemonics leave size bit30 free (mask 0xBFE08000) so one row decodes
    399      * both Wt and Xt; the byte/half mnemonics pin the full size (0xFFE08000).
    400      * print_ldst_excl keys the register width on size and the operand shape on
    401      * L[22]/o2[23]. */
    402     {MN("ldxr"), 0x88400000u, 0xBFE08000u, AA64_FMT_LDST_EXCL, 0, {0, 0}},
    403     {MN("ldaxr"), 0x88408000u, 0xBFE08000u, AA64_FMT_LDST_EXCL, 0, {0, 0}},
    404     {MN("ldar"), 0x88C08000u, 0xBFE08000u, AA64_FMT_LDST_EXCL, 0, {0, 0}},
    405     {MN("stxr"), 0x88000000u, 0xBFE08000u, AA64_FMT_LDST_EXCL, 0, {0, 0}},
    406     {MN("stlxr"), 0x88008000u, 0xBFE08000u, AA64_FMT_LDST_EXCL, 0, {0, 0}},
    407     {MN("stlr"), 0x88808000u, 0xBFE08000u, AA64_FMT_LDST_EXCL, 0, {0, 0}},
    408     {MN("ldxrb"), 0x08400000u, 0xFFE08000u, AA64_FMT_LDST_EXCL, 0, {0, 0}},
    409     {MN("ldxrh"), 0x48400000u, 0xFFE08000u, AA64_FMT_LDST_EXCL, 0, {0, 0}},
    410     {MN("stxrb"), 0x08000000u, 0xFFE08000u, AA64_FMT_LDST_EXCL, 0, {0, 0}},
    411     {MN("stxrh"), 0x48000000u, 0xFFE08000u, AA64_FMT_LDST_EXCL, 0, {0, 0}},
    412     {MN("ldaxrb"), 0x08408000u, 0xFFE08000u, AA64_FMT_LDST_EXCL, 0, {0, 0}},
    413     {MN("ldaxrh"), 0x48408000u, 0xFFE08000u, AA64_FMT_LDST_EXCL, 0, {0, 0}},
    414     {MN("ldarb"), 0x08C08000u, 0xFFE08000u, AA64_FMT_LDST_EXCL, 0, {0, 0}},
    415     {MN("ldarh"), 0x48C08000u, 0xFFE08000u, AA64_FMT_LDST_EXCL, 0, {0, 0}},
    416     {MN("stlxrb"), 0x08008000u, 0xFFE08000u, AA64_FMT_LDST_EXCL, 0, {0, 0}},
    417     {MN("stlxrh"), 0x48008000u, 0xFFE08000u, AA64_FMT_LDST_EXCL, 0, {0, 0}},
    418     {MN("stlrb"), 0x08808000u, 0xFFE08000u, AA64_FMT_LDST_EXCL, 0, {0, 0}},
    419     {MN("stlrh"), 0x48808000u, 0xFFE08000u, AA64_FMT_LDST_EXCL, 0, {0, 0}},
    420 
    421     /* ----- Load/store pair, pre-indexed (opc=10 X / opc=01 D) ----- */
    422     {MN("stp"),
    423      0xA9800000u,
    424      0xFFC00000u,
    425      AA64_FMT_LDSTP_PRE,
    426      AA64_ASMFL_SF1,
    427      {0, 0}},
    428     {MN("ldp"),
    429      0xA9C00000u,
    430      0xFFC00000u,
    431      AA64_FMT_LDSTP_PRE,
    432      AA64_ASMFL_SF1,
    433      {0, 0}},
    434     {MN("stp"), 0x6D800000u, 0xFFC00000u, AA64_FMT_LDSTP_PRE, 0, {0, 0}}, /* D
    435                                                                            */
    436     {MN("ldp"), 0x6DC00000u, 0xFFC00000u, AA64_FMT_LDSTP_PRE, 0, {0, 0}},
    437     {MN("stp"),
    438      0xAD800000u,
    439      0xFFC00000u,
    440      AA64_FMT_LDSTP_PRE,
    441      AA64_ASMFL_SF1,
    442      {0, 0}}, /* Q */
    443     {MN("ldp"),
    444      0xADC00000u,
    445      0xFFC00000u,
    446      AA64_FMT_LDSTP_PRE,
    447      AA64_ASMFL_SF1,
    448      {0, 0}},
    449 
    450     /* ----- Load/store pair, signed-offset ----- */
    451     {MN("stp"),
    452      0xA9000000u,
    453      0xFFC00000u,
    454      AA64_FMT_LDSTP_SOFF,
    455      AA64_ASMFL_SF1,
    456      {0, 0}},
    457     {MN("ldp"),
    458      0xA9400000u,
    459      0xFFC00000u,
    460      AA64_FMT_LDSTP_SOFF,
    461      AA64_ASMFL_SF1,
    462      {0, 0}},
    463     {MN("stp"), 0x6D000000u, 0xFFC00000u, AA64_FMT_LDSTP_SOFF, 0, {0, 0}}, /* D
    464                                                                             */
    465     {MN("ldp"), 0x6D400000u, 0xFFC00000u, AA64_FMT_LDSTP_SOFF, 0, {0, 0}},
    466     {MN("stp"),
    467      0xAD000000u,
    468      0xFFC00000u,
    469      AA64_FMT_LDSTP_SOFF,
    470      AA64_ASMFL_SF1,
    471      {0, 0}}, /* Q */
    472     {MN("ldp"),
    473      0xAD400000u,
    474      0xFFC00000u,
    475      AA64_FMT_LDSTP_SOFF,
    476      AA64_ASMFL_SF1,
    477      {0, 0}},
    478 
    479     /* ----- Load/store pair, post-indexed (opc=10 X / opc=01 D) ----- */
    480     {MN("stp"),
    481      0xA8800000u,
    482      0xFFC00000u,
    483      AA64_FMT_LDSTP_POST,
    484      AA64_ASMFL_SF1,
    485      {0, 0}},
    486     {MN("ldp"),
    487      0xA8C00000u,
    488      0xFFC00000u,
    489      AA64_FMT_LDSTP_POST,
    490      AA64_ASMFL_SF1,
    491      {0, 0}},
    492 
    493     /* ----- Unconditional branch (immediate) ----- */
    494     {MN("b"), 0x14000000u, 0xFC000000u, AA64_FMT_BR_IMM, 0, {0, 0}},
    495     {MN("bl"), 0x94000000u, 0xFC000000u, AA64_FMT_BR_IMM, 0, {0, 0}},
    496 
    497     /* ----- Conditional branch (immediate) ----- */
    498     {MN("b.cond"), 0x54000000u, 0xFF000010u, AA64_FMT_BR_COND, 0, {0, 0}},
    499 
    500     /* ----- Compare-and-branch ----- */
    501     {MN("cbz"), 0x34000000u, 0x7F000000u, AA64_FMT_CB, 0, {0, 0}},
    502     {MN("cbnz"), 0x35000000u, 0x7F000000u, AA64_FMT_CB, 0, {0, 0}},
    503 
    504     /* ----- Exception generation ----- */
    505     {MN("svc"), 0xD4000001u, 0xFFE0001Fu, AA64_FMT_EXCEPT, 0, {0, 0}},
    506     {MN("brk"), 0xD4200000u, 0xFFE0001Fu, AA64_FMT_EXCEPT, 0, {0, 0}},
    507     {MN("hlt"), 0xD4400000u, 0xFFE0001Fu, AA64_FMT_EXCEPT, 0, {0, 0}},
    508 
    509     /* ----- Hint ----- */
    510     {MN("nop"), 0xD503201Fu, 0xFFFFFFFFu, AA64_FMT_HINT, 0, {0, 0}},
    511 
    512     /* ----- Memory barriers (DMB / DSB / ISB / CLREX) -----
    513      * Mask covers everything but CRm at bits[11:8]. */
    514     {MN("dmb"), 0xD50330BFu, 0xFFFFF0FFu, AA64_FMT_BARRIER, 0, {0, 0}},
    515     {MN("dsb"), 0xD503309Fu, 0xFFFFF0FFu, AA64_FMT_BARRIER, 0, {0, 0}},
    516     {MN("isb"), 0xD50330DFu, 0xFFFFF0FFu, AA64_FMT_BARRIER, 0, {0, 0}},
    517     {MN("clrex"), 0xD503305Fu, 0xFFFFF0FFu, AA64_FMT_BARRIER, 0, {0, 0}},
    518 
    519     /* ----- System-register move (MRS read / MSR write, register form) -----
    520      * The selector op0:op1:CRn:CRm:op2 and Rt decode from the word; the mask
    521      * pins bits[31:20] (fixes L and op0's high bit), leaving the rest. */
    522     {MN("mrs"),
    523      AA64_MRS_MATCH,
    524      AA64_SYSREG_MOVE_MASK,
    525      AA64_FMT_SYSREG,
    526      0,
    527      {0, 0}},
    528     {MN("msr"),
    529      AA64_MSR_MATCH,
    530      AA64_SYSREG_MOVE_MASK,
    531      AA64_FMT_SYSREG,
    532      0,
    533      {0, 0}},
    534 
    535     /* ----- Data-processing (1 source): RBIT / REV16 / REV / CLZ -----
    536      * sf (bit31) free; opcode2 in bits[15:10] selects the operation. REV has
    537      * a 32-bit (opcode2=000010) and 64-bit (000011) encoding, both "rev". */
    538     {MN("rbit"), 0x5AC00000u, 0x7FFFFC00u, AA64_FMT_DP1, 0, {0, 0}},
    539     {MN("rev16"), 0x5AC00400u, 0x7FFFFC00u, AA64_FMT_DP1, 0, {0, 0}},
    540     {MN("rev"), 0x5AC00800u, 0x7FFFFC00u, AA64_FMT_DP1, 0, {0, 0}},
    541     {MN("rev"), 0x5AC00C00u, 0x7FFFFC00u, AA64_FMT_DP1, 0, {0, 0}},
    542     {MN("clz"), 0x5AC01000u, 0x7FFFFC00u, AA64_FMT_DP1, 0, {0, 0}},
    543 
    544     /* ----- Bitfield move (SBFM / UBFM) -----
    545      * sf/N free (read for W/X); opc (bits30:29) selects sbfm vs ubfm. Aliases
    546      * (lsl/lsr/asr/ubfx/sxtb/...) are selected by the bitfield printer. */
    547     {MN("sbfm"), 0x13000000u, 0x7F800000u, AA64_FMT_BITFIELD, 0, {0, 0}},
    548     {MN("ubfm"), 0x53000000u, 0x7F800000u, AA64_FMT_BITFIELD, 0, {0, 0}},
    549 
    550     /* ----- Logical, immediate (AND / ORR / EOR / ANDS, bitmask form) -----
    551      * sf (bit31) and the N:immr:imms bitmask fields free; opc (bits30:29)
    552      * selects the operation. Family bits[28:23]=100100. */
    553     {MN("and"), 0x12000000u, 0x7F800000u, AA64_FMT_LOG_IMM, 0, {0, 0}},
    554     {MN("orr"), 0x32000000u, 0x7F800000u, AA64_FMT_LOG_IMM, 0, {0, 0}},
    555     {MN("eor"), 0x52000000u, 0x7F800000u, AA64_FMT_LOG_IMM, 0, {0, 0}},
    556     {MN("ands"), 0x72000000u, 0x7F800000u, AA64_FMT_LOG_IMM, 0, {0, 0}},
    557 
    558     /* ----- Load/store, register offset [Xn, Xm{, LSL #s}] (V=0) -----
    559      * Family bits[29:24]=111000, bit21=1, bits[11:10]=10; per-size opc rows. */
    560     {MN("strb"), 0x38200800u, 0xFFE00C00u, AA64_FMT_LDST_REGOFF, 0, {0, 0}},
    561     {MN("ldrb"), 0x38600800u, 0xFFE00C00u, AA64_FMT_LDST_REGOFF, 0, {0, 0}},
    562     {MN("strh"), 0x78200800u, 0xFFE00C00u, AA64_FMT_LDST_REGOFF, 0, {0, 0}},
    563     {MN("ldrh"), 0x78600800u, 0xFFE00C00u, AA64_FMT_LDST_REGOFF, 0, {0, 0}},
    564     {MN("str"), 0xB8200800u, 0xFFE00C00u, AA64_FMT_LDST_REGOFF, 0, {0, 0}},
    565     {MN("ldr"), 0xB8600800u, 0xFFE00C00u, AA64_FMT_LDST_REGOFF, 0, {0, 0}},
    566     {MN("str"), 0xF8200800u, 0xFFE00C00u, AA64_FMT_LDST_REGOFF, 0, {0, 0}},
    567     {MN("ldr"), 0xF8600800u, 0xFFE00C00u, AA64_FMT_LDST_REGOFF, 0, {0, 0}},
    568 
    569     /* ----- FP data-processing (2 source): FMUL / FDIV / FADD / FSUB -----
    570      * ftype (bits23:22) free, read for the s/d/h register prefix. */
    571     {MN("fmul"), 0x1E200800u, 0xFF20FC00u, AA64_FMT_FP_DP2, 0, {0, 0}},
    572     {MN("fdiv"), 0x1E201800u, 0xFF20FC00u, AA64_FMT_FP_DP2, 0, {0, 0}},
    573     {MN("fadd"), 0x1E202800u, 0xFF20FC00u, AA64_FMT_FP_DP2, 0, {0, 0}},
    574     {MN("fsub"), 0x1E203800u, 0xFF20FC00u, AA64_FMT_FP_DP2, 0, {0, 0}},
    575     {MN("fmax"), 0x1E204800u, 0xFF20FC00u, AA64_FMT_FP_DP2, 0, {0, 0}},
    576     {MN("fmin"), 0x1E205800u, 0xFF20FC00u, AA64_FMT_FP_DP2, 0, {0, 0}},
    577     {MN("fnmul"), 0x1E208800u, 0xFF20FC00u, AA64_FMT_FP_DP2, 0, {0, 0}},
    578 
    579     /* ----- FP compare (FCMP, register form) ----- */
    580     {MN("fcmp"), 0x1E202000u, 0xFF20FC1Fu, AA64_FMT_FP_CMP, 0, {0, 0}},
    581 
    582     /* ----- FP precision convert (FCVT single<->double<->half) ----- */
    583     {MN("fcvt"), 0x1E224000u, 0xFF3E7C00u, AA64_FMT_FP_CVT, 0, {0, 0}},
    584 
    585     /* ----- FP data-processing (1 source): FMOV / FABS / FNEG / FSQRT ----- */
    586     {MN("fmov"), 0x1E204000u, 0xFF3FFC00u, AA64_FMT_FP_DP1, 0, {0, 0}},
    587     {MN("fabs"), 0x1E20C000u, 0xFF3FFC00u, AA64_FMT_FP_DP1, 0, {0, 0}},
    588     {MN("fneg"), 0x1E214000u, 0xFF3FFC00u, AA64_FMT_FP_DP1, 0, {0, 0}},
    589     {MN("fsqrt"), 0x1E21C000u, 0xFF3FFC00u, AA64_FMT_FP_DP1, 0, {0, 0}},
    590 
    591     /* ----- FP<->int convert + FMOV gpr<->fp -----
    592      * sf (bit31) and ftype free; opcode (bits20:16) selects op + direction. */
    593     {MN("scvtf"), 0x1E220000u, 0x7F3FFC00u, AA64_FMT_FP_INT_CVT, 0, {0, 0}},
    594     {MN("ucvtf"), 0x1E230000u, 0x7F3FFC00u, AA64_FMT_FP_INT_CVT, 0, {0, 0}},
    595     {MN("fcvtzs"), 0x1E380000u, 0x7F3FFC00u, AA64_FMT_FP_INT_CVT, 0, {0, 0}},
    596     {MN("fcvtzu"), 0x1E390000u, 0x7F3FFC00u, AA64_FMT_FP_INT_CVT, 0, {0, 0}},
    597     {MN("fmov"), 0x1E260000u, 0x7F3FFC00u, AA64_FMT_FP_INT_CVT, 0, {0, 0}},
    598     {MN("fmov"), 0x1E270000u, 0x7F3FFC00u, AA64_FMT_FP_INT_CVT, 0, {0, 0}},
    599 };
    600 
    601 #undef MN
    602 
    603 const u32 aa64_insn_table_n =
    604     (u32)(sizeof aa64_insn_table / sizeof aa64_insn_table[0]);
    605 
    606 const AA64InsnDesc* aa64_disasm_find(u32 word) {
    607   for (u32 i = 0; i < aa64_insn_table_n; ++i) {
    608     const AA64InsnDesc* d = &aa64_insn_table[i];
    609     if ((word & d->mask) == d->match) return d;
    610   }
    611   return NULL;
    612 }
    613 
    614 /* =====================================================================
    615  * Operand print — one helper per format.
    616  *
    617  * Format choices for immediates:
    618  *   - branch displacements, signed add/sub imm, signed ldur/stur ofs:
    619  *     signed decimal.
    620  *   - MOVZ/MOVK halfword, logical bitmask, exception generation #imm:
    621  *     0x-prefixed hex.
    622  *
    623  * Register naming: ZR alias for x31 in places where the encoding treats
    624  * Rd/Rn=31 as the zero register (logical/arith), SP where it treats 31
    625  * as the stack pointer (add/sub imm, ldr/str-uimm Rn, ldp/stp Rn).
    626  *
    627  * vaddr is folded into PC-relative branch operands when nonzero. */
    628 
    629 static void emit_reg(StrBuf* sb, u32 r, int sf, int sp_means_sp) {
    630   if (r == 31u) {
    631     if (sp_means_sp)
    632       strbuf_puts(sb, "sp");
    633     else if (sf)
    634       strbuf_puts(sb, "xzr");
    635     else
    636       strbuf_puts(sb, "wzr");
    637     return;
    638   }
    639   strbuf_putc(sb, sf ? 'x' : 'w');
    640   strbuf_put_u64(sb, (u64)r);
    641 }
    642 
    643 static void emit_vreg(StrBuf* sb, u32 r, char prefix) {
    644   strbuf_putc(sb, prefix);
    645   strbuf_put_u64(sb, (u64)r);
    646 }
    647 
    648 static void emit_cond(StrBuf* sb, u32 cond) {
    649   static const char* names[16] = {"eq", "ne", "cs", "cc", "mi", "pl",
    650                                   "vs", "vc", "hi", "ls", "ge", "lt",
    651                                   "gt", "le", "al", "nv"};
    652   strbuf_puts(sb, names[cond & 0xfu]);
    653 }
    654 
    655 /* Sign-extend an n-bit value held in the low bits of v to i64. */
    656 static i64 sext(u64 v, u32 nbits) {
    657   u64 mask = (nbits >= 64u) ? ~0ull : ((1ull << nbits) - 1ull);
    658   v &= mask;
    659   u64 sign = (nbits == 0u) ? 0ull : (1ull << (nbits - 1u));
    660   if (v & sign) v |= ~mask;
    661   return (i64)v;
    662 }
    663 
    664 static void print_movewide(StrBuf* sb, u32 w) {
    665   AA64MoveWide f = aa64_movewide_unpack(w);
    666   emit_reg(sb, f.Rd, (int)f.sf, /*sp_means_sp=*/0);
    667   strbuf_puts(sb, ", ");
    668   strbuf_put_hex_u64(sb, (u64)f.imm16);
    669   if (f.hw) {
    670     strbuf_puts(sb, ", lsl ");
    671     strbuf_put_u64(sb, (u64)(f.hw * 16u));
    672   }
    673 }
    674 
    675 static void print_logsr(StrBuf* sb, u32 w, const AA64InsnDesc* d) {
    676   AA64LogSR f = aa64_logsr_unpack(w);
    677   if (d->flags & AA64_ASMFL_ALIAS) {
    678     /* MOV / MVN: Rd, Rm */
    679     emit_reg(sb, f.Rd, (int)f.sf, 0);
    680     strbuf_puts(sb, ", ");
    681     emit_reg(sb, f.Rm, (int)f.sf, 0);
    682     return;
    683   }
    684   emit_reg(sb, f.Rd, (int)f.sf, 0);
    685   strbuf_puts(sb, ", ");
    686   emit_reg(sb, f.Rn, (int)f.sf, 0);
    687   strbuf_puts(sb, ", ");
    688   emit_reg(sb, f.Rm, (int)f.sf, 0);
    689   if (f.imm6 || f.shift) {
    690     static const char* sh[4] = {"lsl", "lsr", "asr", "ror"};
    691     strbuf_puts(sb, ", ");
    692     strbuf_puts(sb, sh[f.shift & 3u]);
    693     strbuf_puts(sb, " #");
    694     strbuf_put_u64(sb, (u64)f.imm6);
    695   }
    696 }
    697 
    698 static void print_addsubsr(StrBuf* sb, u32 w, const AA64InsnDesc* d) {
    699   AA64AddSubSR f = aa64_addsubsr_unpack(w);
    700   if (d->flags & AA64_ASMFL_ALIAS) {
    701     /* NEG / NEGS / CMP / CMN. */
    702     if (d->mnemonic.s[0] == 'c') {
    703       /* CMP / CMN — print Rn, Rm */
    704       emit_reg(sb, f.Rn, (int)f.sf, 0);
    705       strbuf_puts(sb, ", ");
    706       emit_reg(sb, f.Rm, (int)f.sf, 0);
    707     } else {
    708       /* NEG / NEGS — print Rd, Rm */
    709       emit_reg(sb, f.Rd, (int)f.sf, 0);
    710       strbuf_puts(sb, ", ");
    711       emit_reg(sb, f.Rm, (int)f.sf, 0);
    712     }
    713     return;
    714   }
    715   emit_reg(sb, f.Rd, (int)f.sf, 0);
    716   strbuf_puts(sb, ", ");
    717   emit_reg(sb, f.Rn, (int)f.sf, 0);
    718   strbuf_puts(sb, ", ");
    719   emit_reg(sb, f.Rm, (int)f.sf, 0);
    720   if (f.imm6 || f.shift) {
    721     static const char* sh[4] = {"lsl", "lsr", "asr", "rsv"};
    722     strbuf_puts(sb, ", ");
    723     strbuf_puts(sb, sh[f.shift & 3u]);
    724     strbuf_puts(sb, " #");
    725     strbuf_put_u64(sb, (u64)f.imm6);
    726   }
    727 }
    728 
    729 static void print_dp3(StrBuf* sb, u32 w, const AA64InsnDesc* d) {
    730   AA64DP3 f = aa64_dp3_unpack(w);
    731   /* MUL / MNEG alias drop Ra (which is ZR for the alias). */
    732   if (d->flags & AA64_ASMFL_ALIAS) {
    733     emit_reg(sb, f.Rd, (int)f.sf, 0);
    734     strbuf_puts(sb, ", ");
    735     emit_reg(sb, f.Rn, (int)f.sf, 0);
    736     strbuf_puts(sb, ", ");
    737     emit_reg(sb, f.Rm, (int)f.sf, 0);
    738     return;
    739   }
    740   emit_reg(sb, f.Rd, (int)f.sf, 0);
    741   strbuf_puts(sb, ", ");
    742   emit_reg(sb, f.Rn, (int)f.sf, 0);
    743   strbuf_puts(sb, ", ");
    744   emit_reg(sb, f.Rm, (int)f.sf, 0);
    745   strbuf_puts(sb, ", ");
    746   emit_reg(sb, f.Ra, (int)f.sf, 0);
    747 }
    748 
    749 static void print_dp2(StrBuf* sb, u32 w) {
    750   AA64DP2 f = aa64_dp2_unpack(w);
    751   emit_reg(sb, f.Rd, (int)f.sf, 0);
    752   strbuf_puts(sb, ", ");
    753   emit_reg(sb, f.Rn, (int)f.sf, 0);
    754   strbuf_puts(sb, ", ");
    755   emit_reg(sb, f.Rm, (int)f.sf, 0);
    756 }
    757 
    758 static void print_condsel(StrBuf* sb, u32 w, const AA64InsnDesc* d) {
    759   AA64CondSel f = aa64_condsel_unpack(w);
    760   if (d->flags & AA64_ASMFL_ALIAS) {
    761     /* CSET / CSETM: Rd, cond.  Encoded condition is inverted. */
    762     emit_reg(sb, f.Rd, (int)f.sf, 0);
    763     strbuf_puts(sb, ", ");
    764     emit_cond(sb, f.cond ^ 1u);
    765     return;
    766   }
    767   emit_reg(sb, f.Rd, (int)f.sf, 0);
    768   strbuf_puts(sb, ", ");
    769   emit_reg(sb, f.Rn, (int)f.sf, 0);
    770   strbuf_puts(sb, ", ");
    771   emit_reg(sb, f.Rm, (int)f.sf, 0);
    772   strbuf_puts(sb, ", ");
    773   emit_cond(sb, f.cond);
    774 }
    775 
    776 static void print_brreg(StrBuf* sb, u32 w, const AA64InsnDesc* d) {
    777   AA64BrReg f = aa64_brreg_unpack(w);
    778   if (d->flags & AA64_ASMFL_NORN) return; /* RET (with implicit X30) */
    779   emit_reg(sb, f.Rn, /*sf=*/1, 0);
    780 }
    781 
    782 static void print_pcrel(StrBuf* sb, u32 w, u64 vaddr) {
    783   AA64PCRelAdr f = aa64_pcrel_adr_unpack(w);
    784   emit_reg(sb, f.Rd, /*sf=*/1, 0);
    785   strbuf_puts(sb, ", ");
    786   i64 imm = sext(((u64)f.immhi << 2) | (u64)f.immlo, 21);
    787   if (f.op == AA64_ADR_OP_ADRP) imm <<= 12;
    788   if (vaddr) {
    789     u64 base = (f.op == AA64_ADR_OP_ADRP) ? (vaddr & ~0xfffull) : vaddr;
    790     strbuf_put_hex_u64(sb, base + (u64)imm);
    791   } else {
    792     strbuf_puts(sb, "#");
    793     strbuf_put_i64(sb, imm);
    794   }
    795 }
    796 
    797 static void print_addsubimm(StrBuf* sb, u32 w) {
    798   AA64AddSubImm f = aa64_addsubimm_unpack(w);
    799   if (f.S && f.Rd == AA64_ZR) {
    800     /* CMP/CMN immediate aliases: Rn|SP, #imm. */
    801     emit_reg(sb, f.Rn, (int)f.sf, 1);
    802     strbuf_puts(sb, ", #");
    803     strbuf_put_u64(sb, (u64)f.imm12);
    804     if (f.sh) strbuf_puts(sb, ", lsl #12");
    805     return;
    806   }
    807   /* For these encodings, Rd/Rn=31 means SP. */
    808   emit_reg(sb, f.Rd, (int)f.sf, 1);
    809   strbuf_puts(sb, ", ");
    810   emit_reg(sb, f.Rn, (int)f.sf, 1);
    811   strbuf_puts(sb, ", #");
    812   strbuf_put_u64(sb, (u64)f.imm12);
    813   if (f.sh) strbuf_puts(sb, ", lsl #12");
    814 }
    815 
    816 static u32 ldst_log2_size(const AA64InsnDesc* d, u32 size_field) {
    817   (void)d;
    818   return size_field & 3u;
    819 }
    820 
    821 static void print_ldst_uimm(StrBuf* sb, u32 w, const AA64InsnDesc* d) {
    822   AA64LdStUimm f = aa64_ldst_uimm_unpack(w);
    823   u32 sz = ldst_log2_size(d, f.size);
    824   /* Pick reg prefix: V=0 picks W/X; V=1 picks B/H/S/D by size.
    825    * For V=0 the destination width follows opc: the signed sub-word loads
    826    * (opc=10 LDRS_X, opc=11 LDRS_W) name an X or W register independent of
    827    * the access size; the plain STR/LDR (opc<=01) use X only for size=11. */
    828   if (f.V == 0) {
    829     int sf = (f.opc == AA64_LDST_OPC_LDRS_X)   ? 1
    830              : (f.opc == AA64_LDST_OPC_LDRS_W) ? 0
    831                                                : (int)(sz == 3u);
    832     emit_reg(sb, f.Rt, sf, 0);
    833   } else {
    834     char p = (sz == 0u) ? 'b' : (sz == 1u) ? 'h' : (sz == 2u) ? 's' : 'd';
    835     emit_vreg(sb, f.Rt, p);
    836   }
    837   strbuf_puts(sb, ", [");
    838   emit_reg(sb, f.Rn, /*sf=*/1, 1);
    839   u32 byte_off = f.imm12 << sz;
    840   if (byte_off) {
    841     strbuf_puts(sb, ", #");
    842     strbuf_put_u64(sb, (u64)byte_off);
    843   }
    844   strbuf_putc(sb, ']');
    845 }
    846 
    847 static void print_ldst_simm9(StrBuf* sb, u32 w, const AA64InsnDesc* d) {
    848   AA64LdStSimm9 f = aa64_ldst_simm9_unpack(w);
    849   u32 sz = f.size & 3u;
    850   (void)d;
    851   if (f.V == 0) {
    852     /* opc 00/01 = STUR/LDUR (register width from size); opc 10/11 = signed
    853      * load LDURS* (width from opc: 10 sign-extends to 64-bit Xt, 11 to
    854      * 32-bit Wt). */
    855     int sf = (f.opc == 2u) ? 1 : (f.opc == 3u) ? 0 : (int)(sz == 3u);
    856     emit_reg(sb, f.Rt, sf, 0);
    857   } else {
    858     char p = (sz == 0u) ? 'b' : (sz == 1u) ? 'h' : (sz == 2u) ? 's' : 'd';
    859     emit_vreg(sb, f.Rt, p);
    860   }
    861   strbuf_puts(sb, ", [");
    862   emit_reg(sb, f.Rn, /*sf=*/1, 1);
    863   i64 off = sext((u64)f.imm9, 9);
    864   if (off) {
    865     strbuf_puts(sb, ", #");
    866     strbuf_put_i64(sb, off);
    867   }
    868   strbuf_putc(sb, ']');
    869 }
    870 
    871 /* Load/store exclusive (LDXR/LDAXR/STXR/STLXR) and acquire/release ordered
    872  * (LDAR/STLR), incl. byte/half variants. Fields: size[31:30] picks the
    873  * transfer register width (Wt for byte/half/word, Xt for dword); o2[23] and
    874  * L[22] select the form; Rs[20:16] is the store-exclusive status register
    875  * (Ws). A store-exclusive (L=0, o2=0) prints `Ws, Rt, [Xn]`; everything else
    876  * (loads + store-release) prints `Rt, [Xn]`. */
    877 static void print_ldst_excl(StrBuf* sb, u32 w) {
    878   u32 size = (w >> 30) & 3u;
    879   u32 o2 = (w >> 23) & 1u;
    880   u32 L = (w >> 22) & 1u;
    881   u32 Rs = (w >> 16) & 0x1fu;
    882   u32 Rn = (w >> 5) & 0x1fu;
    883   u32 Rt = w & 0x1fu;
    884   int sf = (size == 3u);
    885   int store_excl = (L == 0u && o2 == 0u);
    886   if (store_excl) {
    887     emit_reg(sb, Rs, /*sf=*/0, 0); /* Ws: status result, always 32-bit */
    888     strbuf_puts(sb, ", ");
    889   }
    890   emit_reg(sb, Rt, sf, 0);
    891   strbuf_puts(sb, ", [");
    892   emit_reg(sb, Rn, /*sf=*/1, 1);
    893   strbuf_putc(sb, ']');
    894 }
    895 
    896 static void print_ldstp_common(StrBuf* sb, AA64LdStPPre f, int pre) {
    897   /* opc=10 → 64-bit X; opc=00 → 32-bit W; opc=01 (V=1) → D (FP);
    898    * opc=00 (V=1) → S; opc=10 (V=1) → Q (not yet emitted). */
    899   i64 scale;
    900   int is_fp = (f.V == 1);
    901   char fp_prefix = 's';
    902   int sf = 1;
    903   if (is_fp) {
    904     if (f.opc == 0) {
    905       fp_prefix = 's';
    906       scale = 4;
    907     } else if (f.opc == 1) {
    908       fp_prefix = 'd';
    909       scale = 8;
    910     } else {
    911       fp_prefix = 'q';
    912       scale = 16;
    913     }
    914   } else {
    915     sf = (f.opc == 2);
    916     scale = sf ? 8 : 4;
    917   }
    918   if (is_fp) {
    919     emit_vreg(sb, f.Rt, fp_prefix);
    920     strbuf_puts(sb, ", ");
    921     emit_vreg(sb, f.Rt2, fp_prefix);
    922   } else {
    923     emit_reg(sb, f.Rt, sf, 0);
    924     strbuf_puts(sb, ", ");
    925     emit_reg(sb, f.Rt2, sf, 0);
    926   }
    927   strbuf_puts(sb, ", [");
    928   emit_reg(sb, f.Rn, /*sf=*/1, 1);
    929   i64 byte_off = sext((u64)f.imm7, 7) * scale;
    930   if (byte_off) {
    931     strbuf_puts(sb, ", #");
    932     strbuf_put_i64(sb, byte_off);
    933   }
    934   strbuf_putc(sb, ']');
    935   if (pre) strbuf_putc(sb, '!');
    936 }
    937 
    938 static void print_ldstp_pre(StrBuf* sb, u32 w) {
    939   print_ldstp_common(sb, aa64_ldstp_pre_unpack(w), /*pre=*/1);
    940 }
    941 static void print_ldstp_soff(StrBuf* sb, u32 w) {
    942   print_ldstp_common(sb, aa64_ldstp_soff_unpack(w), /*pre=*/0);
    943 }
    944 
    945 /* Post-indexed reuses the pre/soff field layout (same encoder struct). The
    946  * common printer renders `[Rn]` (no offset inside brackets) when pre=0; the
    947  * "#imm, post" form is rendered by appending after the closing bracket. */
    948 static void print_ldstp_post(StrBuf* sb, u32 w) {
    949   AA64LdStPPre f = aa64_ldstp_pre_unpack(w); /* same field layout */
    950   i64 scale = (f.V == 1) ? (f.opc == 0 ? 4 : (f.opc == 1 ? 8 : 16))
    951                          : (f.opc == 2 ? 8 : 4);
    952   /* Render Rt, Rt2, [Rn], #imm — same prefix as the soff form, then a
    953    * post-bracket displacement. Reuse the common printer for the prefix
    954    * by zeroing imm7 in a copy so it omits the `, #imm` inside `[..]`. */
    955   AA64LdStPPre stripped = f;
    956   stripped.imm7 = 0;
    957   print_ldstp_common(sb, stripped, /*pre=*/0);
    958   i64 byte_off = sext((u64)f.imm7, 7) * scale;
    959   if (byte_off) {
    960     strbuf_puts(sb, ", #");
    961     strbuf_put_i64(sb, byte_off);
    962   }
    963 }
    964 
    965 static void print_br_imm(StrBuf* sb, u32 w, u64 vaddr) {
    966   AA64BrImm f = aa64_brimm_unpack(w);
    967   i64 ofs = sext((u64)f.imm26, 26) * 4;
    968   if (vaddr) {
    969     strbuf_put_hex_u64(sb, vaddr + (u64)ofs);
    970   } else {
    971     strbuf_puts(sb, "#");
    972     strbuf_put_i64(sb, ofs);
    973   }
    974 }
    975 
    976 static void print_br_cond(StrBuf* sb, u32 w, u64 vaddr, const AA64InsnDesc* d) {
    977   AA64BrCond f = aa64_brcond_unpack(w);
    978   (void)d;
    979   /* mnemonic is "b.cond"; we'll print cond as a suffix on the target.
    980    * The b.cond row keeps a single mnemonic for printing — for the asm
    981    * spelling to be canonical the writer will need to emit b.<cc>, which
    982    * is the printer's job at the dispatcher level (see aa64_print_operands). */
    983   emit_cond(sb, f.cond);
    984   strbuf_putc(sb, ' ');
    985   i64 ofs = sext((u64)f.imm19, 19) * 4;
    986   if (vaddr) {
    987     strbuf_put_hex_u64(sb, vaddr + (u64)ofs);
    988   } else {
    989     strbuf_puts(sb, "#");
    990     strbuf_put_i64(sb, ofs);
    991   }
    992 }
    993 
    994 static void print_cb(StrBuf* sb, u32 w, u64 vaddr) {
    995   AA64CB f = aa64_cb_unpack(w);
    996   emit_reg(sb, f.Rt, (int)f.sf, 0);
    997   strbuf_puts(sb, ", ");
    998   i64 ofs = sext((u64)f.imm19, 19) * 4;
    999   if (vaddr) {
   1000     strbuf_put_hex_u64(sb, vaddr + (u64)ofs);
   1001   } else {
   1002     strbuf_puts(sb, "#");
   1003     strbuf_put_i64(sb, ofs);
   1004   }
   1005 }
   1006 
   1007 static void print_except(StrBuf* sb, u32 w) {
   1008   AA64Except f = aa64_except_unpack(w);
   1009   strbuf_puts(sb, "#");
   1010   strbuf_put_hex_u64(sb, (u64)f.imm16);
   1011 }
   1012 
   1013 static void print_barrier(StrBuf* sb, u32 w, const AA64InsnDesc* desc) {
   1014   AA64Barrier f = aa64_barrier_unpack(w);
   1015   /* ISB and CLREX with the default CRm=SY (15) print without an
   1016    * operand. DMB/DSB always carry an option. */
   1017   int is_isb = (f.op2 == AA64_BARRIER_OP2_ISB);
   1018   int is_clrex = (f.op2 == AA64_BARRIER_OP2_CLREX);
   1019   if ((is_isb || is_clrex) && f.CRm == AA64_BARRIER_OPT_SY) return;
   1020   const char* opt = NULL;
   1021   switch (f.CRm) {
   1022     case AA64_BARRIER_OPT_OSHLD:
   1023       opt = "oshld";
   1024       break;
   1025     case AA64_BARRIER_OPT_OSHST:
   1026       opt = "oshst";
   1027       break;
   1028     case AA64_BARRIER_OPT_OSH:
   1029       opt = "osh";
   1030       break;
   1031     case AA64_BARRIER_OPT_NSHLD:
   1032       opt = "nshld";
   1033       break;
   1034     case AA64_BARRIER_OPT_NSHST:
   1035       opt = "nshst";
   1036       break;
   1037     case AA64_BARRIER_OPT_NSH:
   1038       opt = "nsh";
   1039       break;
   1040     case AA64_BARRIER_OPT_ISHLD:
   1041       opt = "ishld";
   1042       break;
   1043     case AA64_BARRIER_OPT_ISHST:
   1044       opt = "ishst";
   1045       break;
   1046     case AA64_BARRIER_OPT_ISH:
   1047       opt = "ish";
   1048       break;
   1049     case AA64_BARRIER_OPT_LD:
   1050       opt = (desc && desc->mnemonic.len >= 2 && desc->mnemonic.s[0] == 'd' &&
   1051              desc->mnemonic.s[1] == 'm')
   1052                 ? "ld"
   1053                 : NULL;
   1054       break;
   1055     case AA64_BARRIER_OPT_ST:
   1056       opt = (desc && desc->mnemonic.len >= 2 && desc->mnemonic.s[0] == 'd' &&
   1057              desc->mnemonic.s[1] == 'm')
   1058                 ? "st"
   1059                 : NULL;
   1060       break;
   1061     case AA64_BARRIER_OPT_SY:
   1062       opt = "sy";
   1063       break;
   1064     default:
   1065       break;
   1066   }
   1067   strbuf_putc(sb, ' ');
   1068   if (opt) {
   1069     strbuf_puts(sb, opt);
   1070   } else {
   1071     strbuf_puts(sb, "#");
   1072     strbuf_put_u64(sb, (u64)f.CRm);
   1073   }
   1074 }
   1075 
   1076 static void print_sysreg_name(StrBuf* sb, u32 op0, u32 op1, u32 crn, u32 crm,
   1077                               u32 op2) {
   1078   const char* name = aa64_sysreg_name(op0, op1, crn, crm, op2);
   1079   if (name) {
   1080     strbuf_puts(sb, name);
   1081     return;
   1082   }
   1083   strbuf_putc(sb, 's');
   1084   strbuf_put_u64(sb, (u64)op0);
   1085   strbuf_putc(sb, '_');
   1086   strbuf_put_u64(sb, (u64)op1);
   1087   strbuf_puts(sb, "_c");
   1088   strbuf_put_u64(sb, (u64)crn);
   1089   strbuf_puts(sb, "_c");
   1090   strbuf_put_u64(sb, (u64)crm);
   1091   strbuf_putc(sb, '_');
   1092   strbuf_put_u64(sb, (u64)op2);
   1093 }
   1094 
   1095 static void print_sysreg(StrBuf* sb, u32 w) {
   1096   int is_read = (int)((w >> 21) & 1u);
   1097   u32 op0 = 2u | ((w >> 19) & 1u);
   1098   u32 op1 = (w >> 16) & 7u;
   1099   u32 crn = (w >> 12) & 0xfu;
   1100   u32 crm = (w >> 8) & 0xfu;
   1101   u32 op2 = (w >> 5) & 7u;
   1102   u32 rt = w & 0x1fu;
   1103   if (is_read) {
   1104     emit_reg(sb, rt, /*sf=*/1, /*sp_means_sp=*/0);
   1105     strbuf_puts(sb, ", ");
   1106     print_sysreg_name(sb, op0, op1, crn, crm, op2);
   1107   } else {
   1108     print_sysreg_name(sb, op0, op1, crn, crm, op2);
   1109     strbuf_puts(sb, ", ");
   1110     emit_reg(sb, rt, /*sf=*/1, /*sp_means_sp=*/0);
   1111   }
   1112 }
   1113 
   1114 /* FP scalar size from the 2-bit ftype field: 00=single, 01=double, 11=half. */
   1115 static char aa64_ftype_prefix(u32 ftype) {
   1116   return ftype == 0u ? 's' : (ftype == 1u ? 'd' : 'h');
   1117 }
   1118 
   1119 /* FADD/FSUB/FMUL/FDIV: Vd, Vn, Vm (all the same ftype). */
   1120 static void print_fp_dp2(StrBuf* sb, u32 w) {
   1121   char p = aa64_ftype_prefix((w >> 22) & 3u);
   1122   emit_vreg(sb, w & 0x1fu, p);
   1123   strbuf_puts(sb, ", ");
   1124   emit_vreg(sb, (w >> 5) & 0x1fu, p);
   1125   strbuf_puts(sb, ", ");
   1126   emit_vreg(sb, (w >> 16) & 0x1fu, p);
   1127 }
   1128 
   1129 /* FMOV(reg)/FNEG/FABS/FSQRT: Vd, Vn (same ftype). */
   1130 static void print_fp_dp1(StrBuf* sb, u32 w) {
   1131   char p = aa64_ftype_prefix((w >> 22) & 3u);
   1132   emit_vreg(sb, w & 0x1fu, p);
   1133   strbuf_puts(sb, ", ");
   1134   emit_vreg(sb, (w >> 5) & 0x1fu, p);
   1135 }
   1136 
   1137 /* FCMP: Vn, Vm. */
   1138 static void print_fp_cmp(StrBuf* sb, u32 w) {
   1139   char p = aa64_ftype_prefix((w >> 22) & 3u);
   1140   emit_vreg(sb, (w >> 5) & 0x1fu, p);
   1141   strbuf_puts(sb, ", ");
   1142   emit_vreg(sb, (w >> 16) & 0x1fu, p);
   1143 }
   1144 
   1145 /* FCVT precision change: Vd has the destination type (opc, bits 16:15), Vn the
   1146  * source type (ftype, bits 23:22). */
   1147 static void print_fp_cvt(StrBuf* sb, u32 w) {
   1148   char src = aa64_ftype_prefix((w >> 22) & 3u);
   1149   char dst = aa64_ftype_prefix((w >> 15) & 3u);
   1150   emit_vreg(sb, w & 0x1fu, dst);
   1151   strbuf_puts(sb, ", ");
   1152   emit_vreg(sb, (w >> 5) & 0x1fu, src);
   1153 }
   1154 
   1155 /* SCVTF/UCVTF/FCVTZS/FCVTZU and FMOV gpr<->fp. The opcode (bits 20:16)
   1156  * selects the direction: fcvtzs/fcvtzu and fmov-to-gpr produce a GPR dst with
   1157  * an FP src; scvtf/ucvtf and fmov-to-fp produce an FP dst with a GPR src.
   1158  * sf (bit 31) is the GPR width, ftype (bits 23:22) the FP size. */
   1159 static void print_fp_int_cvt(StrBuf* sb, u32 w) {
   1160   u32 opcode = (w >> 16) & 0x1fu;
   1161   int sf = (int)((w >> 31) & 1u);
   1162   char fp = aa64_ftype_prefix((w >> 22) & 3u);
   1163   u32 rd = w & 0x1fu, rn = (w >> 5) & 0x1fu;
   1164   int gpr_dst = (opcode == 0x18u /*fcvtzs*/ || opcode == 0x19u /*fcvtzu*/ ||
   1165                  opcode == 0x06u /*fmov fp->gpr*/);
   1166   if (gpr_dst) {
   1167     emit_reg(sb, rd, sf, 0);
   1168     strbuf_puts(sb, ", ");
   1169     emit_vreg(sb, rn, fp);
   1170   } else {
   1171     emit_vreg(sb, rd, fp);
   1172     strbuf_puts(sb, ", ");
   1173     emit_reg(sb, rn, sf, 0);
   1174   }
   1175 }
   1176 
   1177 /* RBIT/REV16/REV32/REV/CLZ: Rd, Rn (sf = bit 31). */
   1178 static void print_dp1(StrBuf* sb, u32 w) {
   1179   int sf = (int)((w >> 31) & 1u);
   1180   emit_reg(sb, w & 0x1fu, sf, 0);
   1181   strbuf_puts(sb, ", ");
   1182   emit_reg(sb, (w >> 5) & 0x1fu, sf, 0);
   1183 }
   1184 
   1185 /* SBFM/UBFM: Rd, Rn, #immr, #imms. */
   1186 static int bitfield_width(u32 word, u32* width) {
   1187   u32 sf = (word >> 31) & 1u;
   1188   u32 N = (word >> 22) & 1u;
   1189   if (N != sf) return 0;
   1190   *width = sf ? 64u : 32u;
   1191   return 1;
   1192 }
   1193 
   1194 /* SBFM/UBFM shift aliases. UBFM is LSR when imms is the top bit index and LSL
   1195  * when immr == imms+1 (the encoder's `lsl #s` form); SBFM is ASR when imms is
   1196  * the top bit index. */
   1197 const char* aa64_bitfield_shift_alias(u32 word, u32* shift) {
   1198   u32 opc = (word >> 29) & 3u; /* 0 = SBFM, 2 = UBFM */
   1199   u32 immr = (word >> 16) & 0x3fu;
   1200   u32 imms = (word >> 10) & 0x3fu;
   1201   u32 width, top;
   1202   if (!bitfield_width(word, &width)) return NULL;
   1203   top = width - 1u;
   1204   if (opc == 2u) { /* UBFM */
   1205     if (imms == top) {
   1206       *shift = immr;
   1207       return "lsr";
   1208     }
   1209     if (immr == imms + 1u) {
   1210       *shift = top - imms;
   1211       return "lsl";
   1212     }
   1213   } else if (opc == 0u) { /* SBFM */
   1214     if (imms == top) {
   1215       *shift = immr;
   1216       return "asr";
   1217     }
   1218   }
   1219   return NULL;
   1220 }
   1221 
   1222 const char* aa64_bitfield_extend_alias(u32 word) {
   1223   u32 sf = (word >> 31) & 1u;
   1224   u32 opc = (word >> 29) & 3u; /* 0 = SBFM, 2 = UBFM */
   1225   u32 immr = (word >> 16) & 0x3fu;
   1226   u32 imms = (word >> 10) & 0x3fu;
   1227   u32 width;
   1228   if (!bitfield_width(word, &width) || immr != 0u) return NULL;
   1229   (void)width;
   1230   if (opc == 0u) {
   1231     if (imms == 7u) return "sxtb";
   1232     if (imms == 15u) return "sxth";
   1233     if (sf && imms == 31u) return "sxtw";
   1234   } else if (opc == 2u && !sf) {
   1235     if (imms == 7u) return "uxtb";
   1236     if (imms == 15u) return "uxth";
   1237   }
   1238   return NULL;
   1239 }
   1240 
   1241 const char* aa64_bitfield_extract_alias(u32 word, u32* lsb, u32* width_out) {
   1242   u32 opc = (word >> 29) & 3u; /* 0 = SBFM, 2 = UBFM */
   1243   u32 immr = (word >> 16) & 0x3fu;
   1244   u32 imms = (word >> 10) & 0x3fu;
   1245   u32 width;
   1246   if (!bitfield_width(word, &width)) return NULL;
   1247   (void)width;
   1248   if (opc != 0u && opc != 2u) return NULL;
   1249   if (immr > imms) return NULL;
   1250   *lsb = immr;
   1251   *width_out = imms - immr + 1u;
   1252   return opc == 0u ? "sbfx" : "ubfx";
   1253 }
   1254 
   1255 static void print_bitfield(StrBuf* sb, u32 w) {
   1256   int sf = (int)((w >> 31) & 1u);
   1257   u32 shift, lsb, width;
   1258   if (aa64_bitfield_shift_alias(w, &shift)) {
   1259     /* lsl/lsr/asr Rd, Rn, #shift (mnemonic chosen by the disasm mnemonic
   1260      * writer via the same helper). */
   1261     emit_reg(sb, w & 0x1fu, sf, 0);
   1262     strbuf_puts(sb, ", ");
   1263     emit_reg(sb, (w >> 5) & 0x1fu, sf, 0);
   1264     strbuf_puts(sb, ", #");
   1265     strbuf_put_u64(sb, (u64)shift);
   1266     return;
   1267   }
   1268   if (aa64_bitfield_extend_alias(w)) {
   1269     /* sxtb/sxth/sxtw/uxtb/uxth Rd, Wn.  The source is spelled Wn even when
   1270      * the underlying SBFM has sf=1 (sxtb/sxth/sxtw into Xd). */
   1271     emit_reg(sb, w & 0x1fu, sf, 0);
   1272     strbuf_puts(sb, ", ");
   1273     emit_reg(sb, (w >> 5) & 0x1fu, 0, 0);
   1274     return;
   1275   }
   1276   if (aa64_bitfield_extract_alias(w, &lsb, &width)) {
   1277     emit_reg(sb, w & 0x1fu, sf, 0);
   1278     strbuf_puts(sb, ", ");
   1279     emit_reg(sb, (w >> 5) & 0x1fu, sf, 0);
   1280     strbuf_puts(sb, ", #");
   1281     strbuf_put_u64(sb, (u64)lsb);
   1282     strbuf_puts(sb, ", #");
   1283     strbuf_put_u64(sb, (u64)width);
   1284     return;
   1285   }
   1286   emit_reg(sb, w & 0x1fu, sf, 0);
   1287   strbuf_puts(sb, ", ");
   1288   emit_reg(sb, (w >> 5) & 0x1fu, sf, 0);
   1289   strbuf_puts(sb, ", #");
   1290   strbuf_put_u64(sb, (u64)((w >> 16) & 0x3fu));
   1291   strbuf_puts(sb, ", #");
   1292   strbuf_put_u64(sb, (u64)((w >> 10) & 0x3fu));
   1293 }
   1294 
   1295 /* Decode an AArch64 logical-immediate (N:immr:imms) bitmask to its value, per
   1296  * the ARM ARM DecodeBitMasks. Inverse of aa64_logimm_encode (isa.h). */
   1297 static u64 aa64_logimm_decode(u32 N, u32 immr, u32 imms, int sf) {
   1298   u32 combined = (N << 6) | ((~imms) & 0x3fu);
   1299   int len = -1;
   1300   u32 esize, levels, S, R, i;
   1301   u64 welem, elem, elt_mask, result;
   1302   for (int b = 6; b >= 0; --b) {
   1303     if (combined & (1u << b)) {
   1304       len = b;
   1305       break;
   1306     }
   1307   }
   1308   if (len < 1) return 0; /* reserved encoding */
   1309   esize = 1u << (u32)len;
   1310   levels = esize - 1u;
   1311   S = imms & levels;
   1312   R = immr & levels;
   1313   welem = (S + 1u >= 64u) ? ~(u64)0 : (((u64)1 << (S + 1u)) - 1u);
   1314   elt_mask = (esize >= 64u) ? ~(u64)0 : (((u64)1 << esize) - 1u);
   1315   elem =
   1316       (R == 0u) ? welem : (((welem >> R) | (welem << (esize - R))) & elt_mask);
   1317   result = 0;
   1318   for (i = 0; i < 64u; i += esize) result |= elem << i;
   1319   return sf ? result : (result & 0xFFFFFFFFu);
   1320 }
   1321 
   1322 static void print_logimm(StrBuf* sb, u32 w) {
   1323   int sf = (int)((w >> 31) & 1u);
   1324   u32 opc = (w >> 29) & 3u; /* 0=AND 1=ORR 2=EOR 3=ANDS */
   1325   u32 N = (w >> 22) & 1u;
   1326   u32 immr = (w >> 16) & 0x3fu;
   1327   u32 imms = (w >> 10) & 0x3fu;
   1328   /* AND/ORR/EOR use the SP-or-GP destination; ANDS uses ZR. Rn is always GP. */
   1329   emit_reg(sb, w & 0x1fu, sf, opc != 3u);
   1330   strbuf_puts(sb, ", ");
   1331   emit_reg(sb, (w >> 5) & 0x1fu, sf, 0);
   1332   strbuf_puts(sb, ", #");
   1333   strbuf_put_hex_u64(sb, aa64_logimm_decode(N, immr, imms, sf));
   1334 }
   1335 
   1336 /* Register-offset load/store: Rt, [Xn, Xm{, LSL #s}]. Rt width follows the
   1337  * size field (X for 64-bit accesses, W otherwise); the index extend is LSL
   1338  * for option=011 (UXTX), with shift amount S ? size : 0. */
   1339 static void print_ldst_regoff(StrBuf* sb, u32 w) {
   1340   u32 size = (w >> 30) & 3u;
   1341   u32 option = (w >> 13) & 7u;
   1342   u32 s_bit = (w >> 12) & 1u;
   1343   int xt = (size == 3u);
   1344   emit_reg(sb, w & 0x1fu, xt, 0);
   1345   strbuf_puts(sb, ", [");
   1346   emit_reg(sb, (w >> 5) & 0x1fu, /*sf=*/1, /*sp_means_sp=*/1);
   1347   strbuf_puts(sb, ", ");
   1348   /* UXTW/SXTW use a W index; UXTX/SXTX (LSL) use an X index. */
   1349   emit_reg(sb, (w >> 16) & 0x1fu, (option & 1u) ? 1 : 0, 0);
   1350   if (option != 3u) {
   1351     /* Register-offset extends: only UXTW(010)/SXTW(110)/SXTX(111) are valid
   1352      * besides LSL/UXTX(011). */
   1353     static const char* ext[8] = {0, 0, "uxtw", 0, 0, 0, "sxtw", "sxtx"};
   1354     const char* e = ext[option];
   1355     if (e) {
   1356       strbuf_puts(sb, ", ");
   1357       strbuf_puts(sb, e);
   1358       if (s_bit) {
   1359         strbuf_puts(sb, " #");
   1360         strbuf_put_u64(sb, (u64)size);
   1361       }
   1362     }
   1363   } else if (s_bit) {
   1364     strbuf_puts(sb, ", lsl #");
   1365     strbuf_put_u64(sb, (u64)size);
   1366   }
   1367   strbuf_putc(sb, ']');
   1368 }
   1369 
   1370 void aa64_print_operands(StrBuf* sb, const AA64InsnDesc* desc, u32 word,
   1371                          u64 vaddr) {
   1372   switch ((AA64Format)desc->fmt) {
   1373     case AA64_FMT_MOVEWIDE:
   1374       print_movewide(sb, word);
   1375       break;
   1376     case AA64_FMT_LOG_SR:
   1377       print_logsr(sb, word, desc);
   1378       break;
   1379     case AA64_FMT_ADDSUB_SR:
   1380       print_addsubsr(sb, word, desc);
   1381       break;
   1382     case AA64_FMT_DP3:
   1383       print_dp3(sb, word, desc);
   1384       break;
   1385     case AA64_FMT_DP2:
   1386       print_dp2(sb, word);
   1387       break;
   1388     case AA64_FMT_CONDSEL:
   1389       print_condsel(sb, word, desc);
   1390       break;
   1391     case AA64_FMT_BR_REG:
   1392       print_brreg(sb, word, desc);
   1393       break;
   1394     case AA64_FMT_PCREL_ADR:
   1395       print_pcrel(sb, word, vaddr);
   1396       break;
   1397     case AA64_FMT_ADDSUB_IMM:
   1398       print_addsubimm(sb, word);
   1399       break;
   1400     case AA64_FMT_LDST_UIMM:
   1401       print_ldst_uimm(sb, word, desc);
   1402       break;
   1403     case AA64_FMT_LDSTP_PRE:
   1404       print_ldstp_pre(sb, word);
   1405       break;
   1406     case AA64_FMT_LDSTP_SOFF:
   1407       print_ldstp_soff(sb, word);
   1408       break;
   1409     case AA64_FMT_LDSTP_POST:
   1410       print_ldstp_post(sb, word);
   1411       break;
   1412     case AA64_FMT_LDST_SIMM9:
   1413       print_ldst_simm9(sb, word, desc);
   1414       break;
   1415     case AA64_FMT_LDST_EXCL:
   1416       print_ldst_excl(sb, word);
   1417       break;
   1418     case AA64_FMT_BR_IMM:
   1419       print_br_imm(sb, word, vaddr);
   1420       break;
   1421     case AA64_FMT_BR_COND:
   1422       print_br_cond(sb, word, vaddr, desc);
   1423       break;
   1424     case AA64_FMT_CB:
   1425       print_cb(sb, word, vaddr);
   1426       break;
   1427     case AA64_FMT_EXCEPT:
   1428       print_except(sb, word);
   1429       break;
   1430     case AA64_FMT_HINT:
   1431       break; /* no operands for NOP */
   1432     case AA64_FMT_BARRIER:
   1433       print_barrier(sb, word, desc);
   1434       break;
   1435     case AA64_FMT_DP1:
   1436       print_dp1(sb, word);
   1437       break;
   1438     case AA64_FMT_BITFIELD:
   1439       print_bitfield(sb, word);
   1440       break;
   1441     case AA64_FMT_LOG_IMM:
   1442       print_logimm(sb, word);
   1443       break;
   1444     case AA64_FMT_SYSREG:
   1445       print_sysreg(sb, word);
   1446       break;
   1447     case AA64_FMT_LDST_REGOFF:
   1448       print_ldst_regoff(sb, word);
   1449       break;
   1450     case AA64_FMT_FP_DP2:
   1451       print_fp_dp2(sb, word);
   1452       break;
   1453     case AA64_FMT_FP_DP1:
   1454       print_fp_dp1(sb, word);
   1455       break;
   1456     case AA64_FMT_FP_CMP:
   1457       print_fp_cmp(sb, word);
   1458       break;
   1459     case AA64_FMT_FP_CVT:
   1460       print_fp_cvt(sb, word);
   1461       break;
   1462     case AA64_FMT_FP_INT_CVT:
   1463       print_fp_int_cvt(sb, word);
   1464       break;
   1465   }
   1466 }
   1467 
   1468 /* =====================================================================
   1469  * Operand parse — phase-3 wires this up to the asm token stream.  Phase
   1470  * 2 ships the signature so the assembler bring-up commit doesn't need to
   1471  * touch the descriptor table; the body returns 0 for every format until
   1472  * the per-format grammar is implemented. */
   1473 
   1474 int aa64_parse_operands(struct AA64AsmTok* tok, const AA64InsnDesc* desc,
   1475                         void* fields_out) {
   1476   (void)tok;
   1477   (void)desc;
   1478   (void)fields_out;
   1479   return 0;
   1480 }