kit

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

isa.c (52959B)


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