kit

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

isa.h (61921B)


      1 #ifndef KIT_ARCH_AA64_ISA_H
      2 #define KIT_ARCH_AA64_ISA_H
      3 
      4 /* AArch64 ISA descriptors — single source of truth for every instruction
      5  * the encoder, decoder, and disassembler all need to agree on.
      6  *
      7  * Each format declares:
      8  *   - A field struct naming every encoded bitfield.
      9  *   - {pack, unpack} pure functions that round-trip through a u32 word.
     10  *   - A {family_match, family_mask} pair identifying the format.
     11  *   - Per-instruction inline wrappers that bake in the opc bits and
     12  *     return the encoded word; callers emit it via MCEmitter.
     13  *
     14  * A descriptor table at the bottom (aa64_insn_table) maps mnemonic →
     15  * (match, mask, AA64Format), so the disassembler matches a raw word with
     16  *   for (i=0; i<N; ++i) if ((word & desc[i].mask) == desc[i].match) ...
     17  * and then calls the format's unpack to recover the operand fields.
     18  *
     19  * Conventions:
     20  *   - sf = 0 selects the 32-bit (W) form, sf = 1 selects 64-bit (X).
     21  *   - Reg values are the raw 5-bit encoding (0..30 + 31 for ZR/SP).
     22  *   - All wrappers take Rd first, then Rn, Rm, Ra to match the AAPCS
     23  *     "destination first" convention used in the AArch64 manual.
     24  *
     25  * New instructions land as one entry in the table and (typically) one
     26  * inline wrapper in the relevant format section. */
     27 
     28 #include "core/core.h"
     29 #include "core/slice.h"
     30 #include "core/strbuf.h"
     31 
     32 /* ---- common register names ---- */
     33 #define AA64_ZR 31u /* WZR / XZR */
     34 #define AA64_SP 31u /* SP at Rd/Rn slot */
     35 #define AA64_LR 30u /* X30 / link register */
     36 
     37 /* ---- format kinds ---- */
     38 typedef enum AA64Format {
     39   AA64_FMT_MOVEWIDE,
     40   AA64_FMT_LOG_SR,      /* logical, shifted register */
     41   AA64_FMT_ADDSUB_SR,   /* add/sub, shifted register */
     42   AA64_FMT_DP3,         /* data-processing, 3 source */
     43   AA64_FMT_DP2,         /* data-processing, 2 source */
     44   AA64_FMT_CONDSEL,     /* conditional select (CSEL / CSINC / aliases) */
     45   AA64_FMT_BR_REG,      /* unconditional branch (register) */
     46   AA64_FMT_PCREL_ADR,   /* PC-relative ADR / ADRP */
     47   AA64_FMT_ADDSUB_IMM,  /* add/sub, immediate */
     48   AA64_FMT_LDST_UIMM,   /* load/store, unsigned 12-bit immediate offset */
     49   AA64_FMT_LDSTP_PRE,   /* load/store pair, pre-indexed */
     50   AA64_FMT_LDSTP_SOFF,  /* load/store pair, signed-offset */
     51   AA64_FMT_LDSTP_POST,  /* load/store pair, post-indexed */
     52   AA64_FMT_LDST_SIMM9,  /* load/store, unscaled 9-bit signed offset
     53                            (LDUR / STUR, V=0 and V=1) */
     54   AA64_FMT_BR_IMM,      /* unconditional branch (immediate) — B / BL */
     55   AA64_FMT_BR_COND,     /* B.cond (imm19) */
     56   AA64_FMT_CB,          /* compare-and-branch (CBZ / CBNZ) */
     57   AA64_FMT_EXCEPT,      /* exception generation (BRK / SVC / HVC / ...) */
     58   AA64_FMT_HINT,        /* hint (NOP / YIELD / ...) */
     59   AA64_FMT_BARRIER,     /* memory barrier (DMB / DSB / ISB / CLREX) */
     60   AA64_FMT_DP1,         /* data-processing, 1 source (RBIT/REV/REV16/CLZ) */
     61   AA64_FMT_BITFIELD,    /* bitfield move (SBFM / UBFM): Rd, Rn, #immr, #imms */
     62   AA64_FMT_LDST_REGOFF, /* load/store, register offset [Xn, Xm{, LSL #s}] */
     63   AA64_FMT_FP_DP2,      /* FP data-processing 2-source (FADD/FSUB/FMUL/FDIV) */
     64   AA64_FMT_FP_DP1,      /* FP data-processing 1-source (FMOV/FNEG/FABS/FSQRT) */
     65   AA64_FMT_FP_CMP,      /* FP compare (FCMP) */
     66   AA64_FMT_FP_CVT,      /* FP precision convert (FCVT single<->double) */
     67   AA64_FMT_FP_INT_CVT,  /* FP<->int convert + FMOV gpr<->fp
     68                          * (SCVTF/UCVTF/FCVTZS/FCVTZU/FMOV) */
     69   AA64_FMT_LDST_EXCL,   /* load/store exclusive + acquire/release ordered
     70                          * (LDXR/LDAXR/STXR/STLXR/LDAR/STLR + b/h) */
     71   AA64_FMT_LOG_IMM,     /* logical, immediate (AND/ORR/EOR/ANDS #bitmask) */
     72   AA64_FMT_SYSREG,      /* system-register move (MRS Xt,<reg> / MSR <reg>,Xt) */
     73 } AA64Format;
     74 
     75 /* ---- AsmFlags column on AA64InsnDesc ----
     76  *
     77  * Per-row metadata that varies across same-format members.  Most rows
     78  * carry 0.  When the disassembler matches a row whose ALIAS bit is set,
     79  * that's the spelling it prints; the assembler also accepts both the
     80  * alias and the canonical form because both rows live in the table. */
     81 #define AA64_ASMFL_ALIAS                                                      \
     82   0x01u                      /* row is an alias (e.g. MOV → ORR Rd, ZR, Rm) \
     83                               */
     84 #define AA64_ASMFL_SF1 0x02u /* 64-bit form only (sf hard-wired) */
     85 #define AA64_ASMFL_NORN \
     86   0x04u /* hide Rn operand in print (e.g. RET when Rn=30) */
     87 
     88 /* ====================================================================
     89  * Move-wide immediate (MOVN / MOVZ / MOVK)
     90  *   sf  opc(2)  100101  hw(2)  imm16(16)  Rd(5)
     91  *   31  30..29  28..23  22..21 20..5      4..0
     92  * ==================================================================== */
     93 
     94 #define AA64_MOVN_OPC 0u
     95 #define AA64_MOVZ_OPC 2u
     96 #define AA64_MOVK_OPC 3u
     97 
     98 #define AA64_MOVEWIDE_FAMILY_MATCH 0x12800000u
     99 #define AA64_MOVEWIDE_FAMILY_MASK 0x1F800000u /* bits 28:23 */
    100 
    101 typedef struct AA64MoveWide {
    102   u32 sf, opc, hw, imm16, Rd;
    103 } AA64MoveWide;
    104 
    105 static inline u32 aa64_movewide_pack(AA64MoveWide f) {
    106   return ((f.sf & 1u) << 31) | ((f.opc & 3u) << 29) |
    107          AA64_MOVEWIDE_FAMILY_MATCH | ((f.hw & 3u) << 21) |
    108          ((f.imm16 & 0xffffu) << 5) | (f.Rd & 0x1fu);
    109 }
    110 
    111 static inline AA64MoveWide aa64_movewide_unpack(u32 w) {
    112   AA64MoveWide f;
    113   f.sf = (w >> 31) & 1u;
    114   f.opc = (w >> 29) & 3u;
    115   f.hw = (w >> 21) & 3u;
    116   f.imm16 = (w >> 5) & 0xffffu;
    117   f.Rd = w & 0x1fu;
    118   return f;
    119 }
    120 
    121 static inline u32 aa64_movz(u32 sf, u32 Rd, u32 imm16, u32 hw) {
    122   return aa64_movewide_pack((AA64MoveWide){
    123       .sf = sf, .opc = AA64_MOVZ_OPC, .hw = hw, .imm16 = imm16, .Rd = Rd});
    124 }
    125 static inline u32 aa64_movn(u32 sf, u32 Rd, u32 imm16, u32 hw) {
    126   return aa64_movewide_pack((AA64MoveWide){
    127       .sf = sf, .opc = AA64_MOVN_OPC, .hw = hw, .imm16 = imm16, .Rd = Rd});
    128 }
    129 static inline u32 aa64_movk(u32 sf, u32 Rd, u32 imm16, u32 hw) {
    130   return aa64_movewide_pack((AA64MoveWide){
    131       .sf = sf, .opc = AA64_MOVK_OPC, .hw = hw, .imm16 = imm16, .Rd = Rd});
    132 }
    133 
    134 /* ====================================================================
    135  * Logical, shifted register (AND / ORR / EOR / ANDS, with N inverting
    136  * Rm to BIC / ORN / EON / BICS).
    137  *   sf  opc(2)  01010  shift(2)  N(1)  Rm(5)  imm6(6)  Rn(5)  Rd(5)
    138  *   31  30..29  28..24 23..22    21    20..16 15..10   9..5   4..0
    139  * ==================================================================== */
    140 
    141 #define AA64_LOG_AND_OPC 0u
    142 #define AA64_LOG_ORR_OPC 1u
    143 #define AA64_LOG_EOR_OPC 2u
    144 #define AA64_LOG_ANDS_OPC 3u
    145 
    146 #define AA64_LOGSR_FAMILY_MATCH 0x0A000000u
    147 #define AA64_LOGSR_FAMILY_MASK 0x1F000000u /* bits 28:24 */
    148 
    149 typedef struct AA64LogSR {
    150   u32 sf, opc, shift, N, Rm, imm6, Rn, Rd;
    151 } AA64LogSR;
    152 
    153 static inline u32 aa64_logsr_pack(AA64LogSR f) {
    154   return ((f.sf & 1u) << 31) | ((f.opc & 3u) << 29) | AA64_LOGSR_FAMILY_MATCH |
    155          ((f.shift & 3u) << 22) | ((f.N & 1u) << 21) | ((f.Rm & 0x1fu) << 16) |
    156          ((f.imm6 & 0x3fu) << 10) | ((f.Rn & 0x1fu) << 5) | (f.Rd & 0x1fu);
    157 }
    158 
    159 static inline AA64LogSR aa64_logsr_unpack(u32 w) {
    160   AA64LogSR f;
    161   f.sf = (w >> 31) & 1u;
    162   f.opc = (w >> 29) & 3u;
    163   f.shift = (w >> 22) & 3u;
    164   f.N = (w >> 21) & 1u;
    165   f.Rm = (w >> 16) & 0x1fu;
    166   f.imm6 = (w >> 10) & 0x3fu;
    167   f.Rn = (w >> 5) & 0x1fu;
    168   f.Rd = w & 0x1fu;
    169   return f;
    170 }
    171 
    172 static inline u32 aa64_and(u32 sf, u32 Rd, u32 Rn, u32 Rm) {
    173   return aa64_logsr_pack((AA64LogSR){
    174       .sf = sf, .opc = AA64_LOG_AND_OPC, .Rm = Rm, .Rn = Rn, .Rd = Rd});
    175 }
    176 static inline u32 aa64_orr(u32 sf, u32 Rd, u32 Rn, u32 Rm) {
    177   return aa64_logsr_pack((AA64LogSR){
    178       .sf = sf, .opc = AA64_LOG_ORR_OPC, .Rm = Rm, .Rn = Rn, .Rd = Rd});
    179 }
    180 static inline u32 aa64_eor(u32 sf, u32 Rd, u32 Rn, u32 Rm) {
    181   return aa64_logsr_pack((AA64LogSR){
    182       .sf = sf, .opc = AA64_LOG_EOR_OPC, .Rm = Rm, .Rn = Rn, .Rd = Rd});
    183 }
    184 static inline u32 aa64_orn(u32 sf, u32 Rd, u32 Rn, u32 Rm) {
    185   return aa64_logsr_pack((AA64LogSR){
    186       .sf = sf, .opc = AA64_LOG_ORR_OPC, .N = 1, .Rm = Rm, .Rn = Rn, .Rd = Rd});
    187 }
    188 
    189 /* MOV Wd, Wm  ≡  ORR Wd, WZR, Wm */
    190 static inline u32 aa64_mov_reg(u32 sf, u32 Rd, u32 Rm) {
    191   return aa64_orr(sf, Rd, AA64_ZR, Rm);
    192 }
    193 /* MVN Wd, Wm  ≡  ORN Wd, WZR, Wm */
    194 static inline u32 aa64_mvn(u32 sf, u32 Rd, u32 Rm) {
    195   return aa64_orn(sf, Rd, AA64_ZR, Rm);
    196 }
    197 
    198 /* ====================================================================
    199  * Logical, immediate (AND / ORR / EOR / ANDS, bitmask-imm form)
    200  *   sf  opc(2)  100100  N(1)  immr(6)  imms(6)  Rn(5)  Rd(5)
    201  *   31  30..29  28..23  22    21..16   15..10   9..5   4..0
    202  *
    203  * N:immr:imms encodes a repeated-pattern bitmask. The encoder
    204  * aa64_logimm_encode below computes those fields from a literal value;
    205  * this pack just lays the bits out. For 32-bit ops (sf=0), N must be 0;
    206  * for 64-bit ops N can be 0 or 1 and selects whether the pattern
    207  * element is 64 bits (N=1) or 2..32 bits (N=0).
    208  * ==================================================================== */
    209 
    210 #define AA64_LOGIMM_FAMILY_MATCH 0x12000000u
    211 #define AA64_LOGIMM_FAMILY_MASK 0x1F800000u /* bits 28:23 */
    212 
    213 typedef struct AA64LogImm {
    214   u32 sf, opc, N, immr, imms, Rn, Rd;
    215 } AA64LogImm;
    216 
    217 static inline u32 aa64_logimm_pack(AA64LogImm f) {
    218   return ((f.sf & 1u) << 31) | ((f.opc & 3u) << 29) | AA64_LOGIMM_FAMILY_MATCH |
    219          ((f.N & 1u) << 22) | ((f.immr & 0x3fu) << 16) |
    220          ((f.imms & 0x3fu) << 10) | ((f.Rn & 0x1fu) << 5) | (f.Rd & 0x1fu);
    221 }
    222 
    223 static inline u32 aa64_and_imm(u32 sf, u32 Rd, u32 Rn, u32 N, u32 immr,
    224                                u32 imms) {
    225   return aa64_logimm_pack((AA64LogImm){.sf = sf,
    226                                        .opc = AA64_LOG_AND_OPC,
    227                                        .N = N,
    228                                        .immr = immr,
    229                                        .imms = imms,
    230                                        .Rn = Rn,
    231                                        .Rd = Rd});
    232 }
    233 static inline u32 aa64_orr_imm(u32 sf, u32 Rd, u32 Rn, u32 N, u32 immr,
    234                                u32 imms) {
    235   return aa64_logimm_pack((AA64LogImm){.sf = sf,
    236                                        .opc = AA64_LOG_ORR_OPC,
    237                                        .N = N,
    238                                        .immr = immr,
    239                                        .imms = imms,
    240                                        .Rn = Rn,
    241                                        .Rd = Rd});
    242 }
    243 static inline u32 aa64_eor_imm(u32 sf, u32 Rd, u32 Rn, u32 N, u32 immr,
    244                                u32 imms) {
    245   return aa64_logimm_pack((AA64LogImm){.sf = sf,
    246                                        .opc = AA64_LOG_EOR_OPC,
    247                                        .N = N,
    248                                        .immr = immr,
    249                                        .imms = imms,
    250                                        .Rn = Rn,
    251                                        .Rd = Rd});
    252 }
    253 
    254 /* Bitmask-immediate predicate + encoder. Returns 1 and writes N/immr/imms
    255  * if `imm` is encodable as an AArch64 logical immediate of width
    256  * (sf ? 64 : 32); returns 0 otherwise (caller materializes into a
    257  * scratch and uses the shifted-register form).
    258  *
    259  * Algorithm (inverse of ARM ARM "DecodeBitMasks"): an encodable value
    260  * is a non-zero, non-all-ones bitmask made of a repeated `size`-bit
    261  * element (size ∈ {2,4,8,16,32,64}); within one element the pattern is
    262  * a rotation of (0…0 1…1). Find size by detecting the smallest
    263  * repeating period; find the rotation that places the 1-run at the
    264  * LSB; encode size and ones-count into imms per the standard scheme
    265  * (top bits of imms inverted-encode size, low bits are ones-count-1). */
    266 static inline int aa64_logimm_encode(u64 imm, u32 sf, u32* N_out, u32* immr_out,
    267                                      u32* imms_out) {
    268   if (!sf) {
    269     u64 lo = imm & 0xFFFFFFFFu;
    270     u64 hi = imm >> 32;
    271     if (hi != 0 && hi != lo) return 0;
    272     imm = lo | (lo << 32);
    273   }
    274   if (imm == 0 || imm == ~(u64)0) return 0;
    275 
    276   u32 size = 64;
    277   for (u32 s = 32; s >= 2; s >>= 1) {
    278     u64 mask = ((u64)1 << s) - 1u;
    279     if ((imm & mask) != ((imm >> s) & mask)) break;
    280     size = s;
    281   }
    282   u64 elt_mask = (size == 64) ? ~(u64)0 : (((u64)1 << size) - 1u);
    283   u64 elt = imm & elt_mask;
    284   if (elt == 0 || elt == elt_mask) return 0;
    285 
    286   u32 ones = 0;
    287   for (u64 x = elt; x; x >>= 1) ones += (u32)(x & 1u);
    288   if (ones == 0 || ones >= size) return 0;
    289 
    290   u64 aligned = ((u64)1 << ones) - 1u;
    291   u32 rotation = 0xFFFFFFFFu;
    292   for (u32 r = 0; r < size; r++) {
    293     u64 rotated =
    294         r == 0 ? elt : (((elt >> r) | (elt << (size - r))) & elt_mask);
    295     if (rotated == aligned) {
    296       rotation = r;
    297       break;
    298     }
    299   }
    300   if (rotation == 0xFFFFFFFFu) return 0;
    301 
    302   if (size == 64) {
    303     *N_out = 1u;
    304     *imms_out = (ones - 1u) & 0x3Fu;
    305   } else {
    306     *N_out = 0u;
    307     u32 neg_size_shl1 = ((u32)(-(i32)size) << 1) & 0x3Fu;
    308     *imms_out = neg_size_shl1 | ((ones - 1u) & 0x3Fu);
    309   }
    310   *immr_out = rotation ? (size - rotation) & (size - 1u) : 0u;
    311   return 1;
    312 }
    313 
    314 /* Shift-by-immediate field generators for LSL/LSR/ASR (encoded via
    315  * UBFM/SBFM). Predicate: shift < width. The aa64_ubfm / aa64_sbfm
    316  * encoders live in aarch64.c; callers pair these (immr, imms) with the
    317  * matching pack. */
    318 static inline int aa64_lsl_imm_fields(u32 shift, u32 sf, u32* immr_out,
    319                                       u32* imms_out) {
    320   u32 width = sf ? 64u : 32u;
    321   if (shift >= width) return 0;
    322   *immr_out = (width - shift) & (width - 1u);
    323   *imms_out = width - 1u - shift;
    324   return 1;
    325 }
    326 static inline int aa64_lsr_imm_fields(u32 shift, u32 sf, u32* immr_out,
    327                                       u32* imms_out) {
    328   u32 width = sf ? 64u : 32u;
    329   if (shift >= width) return 0;
    330   *immr_out = shift;
    331   *imms_out = width - 1u;
    332   return 1;
    333 }
    334 static inline int aa64_asr_imm_fields(u32 shift, u32 sf, u32* immr_out,
    335                                       u32* imms_out) {
    336   u32 width = sf ? 64u : 32u;
    337   if (shift >= width) return 0;
    338   *immr_out = shift;
    339   *imms_out = width - 1u;
    340   return 1;
    341 }
    342 
    343 /* ====================================================================
    344  * Add/Sub, shifted register (ADD / SUB / ADDS / SUBS)
    345  *   sf  op(1)  S(1)  01011  shift(2)  0  Rm(5)  imm6(6)  Rn(5)  Rd(5)
    346  *   31  30     29    28..24 23..22    21 20..16 15..10   9..5   4..0
    347  * ==================================================================== */
    348 
    349 #define AA64_ADDSUBSR_FAMILY_MATCH 0x0B000000u
    350 #define AA64_ADDSUBSR_FAMILY_MASK 0x1F200000u /* bits 28:24 + bit 21 */
    351 
    352 typedef struct AA64AddSubSR {
    353   u32 sf, op, S, shift, Rm, imm6, Rn, Rd;
    354 } AA64AddSubSR;
    355 
    356 static inline u32 aa64_addsubsr_pack(AA64AddSubSR f) {
    357   return ((f.sf & 1u) << 31) | ((f.op & 1u) << 30) | ((f.S & 1u) << 29) |
    358          AA64_ADDSUBSR_FAMILY_MATCH | ((f.shift & 3u) << 22) |
    359          ((f.Rm & 0x1fu) << 16) | ((f.imm6 & 0x3fu) << 10) |
    360          ((f.Rn & 0x1fu) << 5) | (f.Rd & 0x1fu);
    361 }
    362 
    363 static inline AA64AddSubSR aa64_addsubsr_unpack(u32 w) {
    364   AA64AddSubSR f;
    365   f.sf = (w >> 31) & 1u;
    366   f.op = (w >> 30) & 1u;
    367   f.S = (w >> 29) & 1u;
    368   f.shift = (w >> 22) & 3u;
    369   f.Rm = (w >> 16) & 0x1fu;
    370   f.imm6 = (w >> 10) & 0x3fu;
    371   f.Rn = (w >> 5) & 0x1fu;
    372   f.Rd = w & 0x1fu;
    373   return f;
    374 }
    375 
    376 static inline u32 aa64_add(u32 sf, u32 Rd, u32 Rn, u32 Rm) {
    377   return aa64_addsubsr_pack(
    378       (AA64AddSubSR){.sf = sf, .op = 0, .Rm = Rm, .Rn = Rn, .Rd = Rd});
    379 }
    380 static inline u32 aa64_sub(u32 sf, u32 Rd, u32 Rn, u32 Rm) {
    381   return aa64_addsubsr_pack(
    382       (AA64AddSubSR){.sf = sf, .op = 1, .Rm = Rm, .Rn = Rn, .Rd = Rd});
    383 }
    384 
    385 /* NEG Wd, Wm  ≡  SUB Wd, WZR, Wm */
    386 static inline u32 aa64_neg(u32 sf, u32 Rd, u32 Rm) {
    387   return aa64_sub(sf, Rd, AA64_ZR, Rm);
    388 }
    389 
    390 /* ====================================================================
    391  * Data-processing, 3-source (MADD / MSUB / SMULL / UMULL / ...)
    392  *   sf  op54(2)  11011  op31(3)  Rm(5)  o0(1)  Ra(5)  Rn(5)  Rd(5)
    393  *   31  30..29   28..24 23..21   20..16 15     14..10 9..5   4..0
    394  * ==================================================================== */
    395 
    396 #define AA64_DP3_FAMILY_MATCH 0x1B000000u
    397 #define AA64_DP3_FAMILY_MASK 0x1F000000u /* bits 28:24 */
    398 
    399 typedef struct AA64DP3 {
    400   u32 sf, op54, op31, Rm, o0, Ra, Rn, Rd;
    401 } AA64DP3;
    402 
    403 static inline u32 aa64_dp3_pack(AA64DP3 f) {
    404   return ((f.sf & 1u) << 31) | ((f.op54 & 3u) << 29) | AA64_DP3_FAMILY_MATCH |
    405          ((f.op31 & 7u) << 21) | ((f.Rm & 0x1fu) << 16) | ((f.o0 & 1u) << 15) |
    406          ((f.Ra & 0x1fu) << 10) | ((f.Rn & 0x1fu) << 5) | (f.Rd & 0x1fu);
    407 }
    408 
    409 static inline AA64DP3 aa64_dp3_unpack(u32 w) {
    410   AA64DP3 f;
    411   f.sf = (w >> 31) & 1u;
    412   f.op54 = (w >> 29) & 3u;
    413   f.op31 = (w >> 21) & 7u;
    414   f.Rm = (w >> 16) & 0x1fu;
    415   f.o0 = (w >> 15) & 1u;
    416   f.Ra = (w >> 10) & 0x1fu;
    417   f.Rn = (w >> 5) & 0x1fu;
    418   f.Rd = w & 0x1fu;
    419   return f;
    420 }
    421 
    422 static inline u32 aa64_madd(u32 sf, u32 Rd, u32 Rn, u32 Rm, u32 Ra) {
    423   return aa64_dp3_pack((AA64DP3){
    424       .sf = sf, .op31 = 0, .o0 = 0, .Rm = Rm, .Ra = Ra, .Rn = Rn, .Rd = Rd});
    425 }
    426 static inline u32 aa64_msub(u32 sf, u32 Rd, u32 Rn, u32 Rm, u32 Ra) {
    427   return aa64_dp3_pack((AA64DP3){
    428       .sf = sf, .op31 = 0, .o0 = 1, .Rm = Rm, .Ra = Ra, .Rn = Rn, .Rd = Rd});
    429 }
    430 /* MUL Wd, Wn, Wm  ≡  MADD Wd, Wn, Wm, WZR */
    431 static inline u32 aa64_mul(u32 sf, u32 Rd, u32 Rn, u32 Rm) {
    432   return aa64_madd(sf, Rd, Rn, Rm, AA64_ZR);
    433 }
    434 
    435 /* ====================================================================
    436  * Data-processing, 2-source (UDIV / SDIV / LSLV / LSRV / ASRV / RORV)
    437  *   sf  0  S(1)  11010110  Rm(5)  opcode(6)  Rn(5)  Rd(5)
    438  *   31  30 29    28..21    20..16 15..10     9..5   4..0
    439  * ==================================================================== */
    440 
    441 #define AA64_DP2_UDIV_OP 0x02u
    442 #define AA64_DP2_SDIV_OP 0x03u
    443 #define AA64_DP2_LSLV_OP 0x08u
    444 #define AA64_DP2_LSRV_OP 0x09u
    445 #define AA64_DP2_ASRV_OP 0x0Au
    446 #define AA64_DP2_RORV_OP 0x0Bu
    447 
    448 #define AA64_DP2_FAMILY_MATCH 0x1AC00000u
    449 #define AA64_DP2_FAMILY_MASK 0x5FE00000u /* bit 30 + bits 28:21 */
    450 
    451 typedef struct AA64DP2 {
    452   u32 sf, S, opcode, Rm, Rn, Rd;
    453 } AA64DP2;
    454 
    455 static inline u32 aa64_dp2_pack(AA64DP2 f) {
    456   return ((f.sf & 1u) << 31) | ((f.S & 1u) << 29) | AA64_DP2_FAMILY_MATCH |
    457          ((f.Rm & 0x1fu) << 16) | ((f.opcode & 0x3fu) << 10) |
    458          ((f.Rn & 0x1fu) << 5) | (f.Rd & 0x1fu);
    459 }
    460 
    461 static inline AA64DP2 aa64_dp2_unpack(u32 w) {
    462   AA64DP2 f;
    463   f.sf = (w >> 31) & 1u;
    464   f.S = (w >> 29) & 1u;
    465   f.Rm = (w >> 16) & 0x1fu;
    466   f.opcode = (w >> 10) & 0x3fu;
    467   f.Rn = (w >> 5) & 0x1fu;
    468   f.Rd = w & 0x1fu;
    469   return f;
    470 }
    471 
    472 static inline u32 aa64_udiv(u32 sf, u32 Rd, u32 Rn, u32 Rm) {
    473   return aa64_dp2_pack((AA64DP2){
    474       .sf = sf, .opcode = AA64_DP2_UDIV_OP, .Rm = Rm, .Rn = Rn, .Rd = Rd});
    475 }
    476 static inline u32 aa64_sdiv(u32 sf, u32 Rd, u32 Rn, u32 Rm) {
    477   return aa64_dp2_pack((AA64DP2){
    478       .sf = sf, .opcode = AA64_DP2_SDIV_OP, .Rm = Rm, .Rn = Rn, .Rd = Rd});
    479 }
    480 static inline u32 aa64_lslv(u32 sf, u32 Rd, u32 Rn, u32 Rm) {
    481   return aa64_dp2_pack((AA64DP2){
    482       .sf = sf, .opcode = AA64_DP2_LSLV_OP, .Rm = Rm, .Rn = Rn, .Rd = Rd});
    483 }
    484 static inline u32 aa64_lsrv(u32 sf, u32 Rd, u32 Rn, u32 Rm) {
    485   return aa64_dp2_pack((AA64DP2){
    486       .sf = sf, .opcode = AA64_DP2_LSRV_OP, .Rm = Rm, .Rn = Rn, .Rd = Rd});
    487 }
    488 static inline u32 aa64_asrv(u32 sf, u32 Rd, u32 Rn, u32 Rm) {
    489   return aa64_dp2_pack((AA64DP2){
    490       .sf = sf, .opcode = AA64_DP2_ASRV_OP, .Rm = Rm, .Rn = Rn, .Rd = Rd});
    491 }
    492 static inline u32 aa64_rorv(u32 sf, u32 Rd, u32 Rn, u32 Rm) {
    493   return aa64_dp2_pack((AA64DP2){
    494       .sf = sf, .opcode = AA64_DP2_RORV_OP, .Rm = Rm, .Rn = Rn, .Rd = Rd});
    495 }
    496 
    497 /* ====================================================================
    498  * Conditional select (CSEL / CSINC / CSINV / CSNEG)
    499  *   sf  op  S  11010100  Rm(5)  cond(4)  op2(2)  Rn(5)  Rd(5)
    500  *   31  30  29 28..21    20..16 15..12   11..10  9..5   4..0
    501  *
    502  * The integer forms this backend emits keep S=0.  Aliases such as CSET
    503  * are descriptor-table rows over this same encoding family. */
    504 
    505 #define AA64_CONDSEL_FAMILY_MATCH 0x1A800000u
    506 #define AA64_CONDSEL_FAMILY_MASK 0x1FE00000u /* bits 28:21 fixed */
    507 
    508 typedef struct AA64CondSel {
    509   u32 sf, op, S, Rm, cond, op2, Rn, Rd;
    510 } AA64CondSel;
    511 
    512 static inline u32 aa64_condsel_pack(AA64CondSel f) {
    513   return ((f.sf & 1u) << 31) | ((f.op & 1u) << 30) | ((f.S & 1u) << 29) |
    514          AA64_CONDSEL_FAMILY_MATCH | ((f.Rm & 0x1fu) << 16) |
    515          ((f.cond & 0xfu) << 12) | ((f.op2 & 3u) << 10) |
    516          ((f.Rn & 0x1fu) << 5) | (f.Rd & 0x1fu);
    517 }
    518 
    519 static inline AA64CondSel aa64_condsel_unpack(u32 w) {
    520   AA64CondSel f;
    521   f.sf = (w >> 31) & 1u;
    522   f.op = (w >> 30) & 1u;
    523   f.S = (w >> 29) & 1u;
    524   f.Rm = (w >> 16) & 0x1fu;
    525   f.cond = (w >> 12) & 0xfu;
    526   f.op2 = (w >> 10) & 3u;
    527   f.Rn = (w >> 5) & 0x1fu;
    528   f.Rd = w & 0x1fu;
    529   return f;
    530 }
    531 
    532 static inline u32 aa64_csel_enc(u32 sf, u32 Rd, u32 Rn, u32 Rm, u32 cond) {
    533   return aa64_condsel_pack((AA64CondSel){.sf = sf,
    534                                          .op = 0,
    535                                          .S = 0,
    536                                          .Rm = Rm,
    537                                          .cond = cond,
    538                                          .op2 = 0,
    539                                          .Rn = Rn,
    540                                          .Rd = Rd});
    541 }
    542 static inline u32 aa64_csinc_enc(u32 sf, u32 Rd, u32 Rn, u32 Rm, u32 cond) {
    543   return aa64_condsel_pack((AA64CondSel){.sf = sf,
    544                                          .op = 0,
    545                                          .S = 0,
    546                                          .Rm = Rm,
    547                                          .cond = cond,
    548                                          .op2 = 1,
    549                                          .Rn = Rn,
    550                                          .Rd = Rd});
    551 }
    552 static inline u32 aa64_csinv_enc(u32 sf, u32 Rd, u32 Rn, u32 Rm, u32 cond) {
    553   return aa64_condsel_pack((AA64CondSel){.sf = sf,
    554                                          .op = 1,
    555                                          .S = 0,
    556                                          .Rm = Rm,
    557                                          .cond = cond,
    558                                          .op2 = 0,
    559                                          .Rn = Rn,
    560                                          .Rd = Rd});
    561 }
    562 static inline u32 aa64_csneg_enc(u32 sf, u32 Rd, u32 Rn, u32 Rm, u32 cond) {
    563   return aa64_condsel_pack((AA64CondSel){.sf = sf,
    564                                          .op = 1,
    565                                          .S = 0,
    566                                          .Rm = Rm,
    567                                          .cond = cond,
    568                                          .op2 = 1,
    569                                          .Rn = Rn,
    570                                          .Rd = Rd});
    571 }
    572 
    573 /* ====================================================================
    574  * Unconditional branch (register) — BR / BLR / RET
    575  *   1101011  opc(4)  op2(5)=11111  op3(6)=000000  Rn(5)  op4(5)=00000
    576  *   31..25   24..21  20..16        15..10         9..5   4..0
    577  * ==================================================================== */
    578 
    579 #define AA64_BR_OP_BR 0u
    580 #define AA64_BR_OP_BLR 1u
    581 #define AA64_BR_OP_RET 2u
    582 
    583 #define AA64_BR_REG_FAMILY_MATCH 0xD61F0000u
    584 #define AA64_BR_REG_FAMILY_MASK \
    585   0xFE1FFC1Fu /* everything fixed except opc + Rn */
    586 
    587 typedef struct AA64BrReg {
    588   u32 opc, Rn;
    589 } AA64BrReg;
    590 
    591 static inline u32 aa64_brreg_pack(AA64BrReg f) {
    592   return AA64_BR_REG_FAMILY_MATCH | ((f.opc & 0xfu) << 21) |
    593          ((f.Rn & 0x1fu) << 5);
    594 }
    595 
    596 static inline AA64BrReg aa64_brreg_unpack(u32 w) {
    597   AA64BrReg f;
    598   f.opc = (w >> 21) & 0xfu;
    599   f.Rn = (w >> 5) & 0x1fu;
    600   return f;
    601 }
    602 
    603 static inline u32 aa64_br(u32 Rn) {
    604   return aa64_brreg_pack((AA64BrReg){.opc = AA64_BR_OP_BR, .Rn = Rn});
    605 }
    606 static inline u32 aa64_blr(u32 Rn) {
    607   return aa64_brreg_pack((AA64BrReg){.opc = AA64_BR_OP_BLR, .Rn = Rn});
    608 }
    609 static inline u32 aa64_ret(u32 Rn) {
    610   return aa64_brreg_pack((AA64BrReg){.opc = AA64_BR_OP_RET, .Rn = Rn});
    611 }
    612 
    613 /* ====================================================================
    614  * PC-relative addressing (ADR / ADRP)
    615  *   op(1)  immlo(2)  10000  immhi(19)  Rd(5)
    616  *   31     30..29    28..24 23..5      4..0
    617  *
    618  * op = 0 → ADR  (PC + sign_extend(immhi:immlo))
    619  * op = 1 → ADRP (page(PC) + sign_extend(immhi:immlo) << 12)
    620  *
    621  * The two immediate halves stay split because the linker's
    622  * R_AARCH64_ADR_PREL_PG_HI21 reloc patches them in place; keeping the
    623  * field layout symmetric with the encoded word lets reloc-apply code
    624  * reuse the same pack/unpack helpers.
    625  * ==================================================================== */
    626 
    627 #define AA64_ADR_OP_ADR 0u
    628 #define AA64_ADR_OP_ADRP 1u
    629 
    630 #define AA64_PCREL_ADR_FAMILY_MATCH 0x10000000u
    631 #define AA64_PCREL_ADR_FAMILY_MASK 0x1F000000u /* bits 28:24 */
    632 
    633 typedef struct AA64PCRelAdr {
    634   u32 op, immlo, immhi, Rd;
    635 } AA64PCRelAdr;
    636 
    637 static inline u32 aa64_pcrel_adr_pack(AA64PCRelAdr f) {
    638   return ((f.op & 1u) << 31) | ((f.immlo & 3u) << 29) |
    639          AA64_PCREL_ADR_FAMILY_MATCH | ((f.immhi & 0x7ffffu) << 5) |
    640          (f.Rd & 0x1fu);
    641 }
    642 
    643 static inline AA64PCRelAdr aa64_pcrel_adr_unpack(u32 w) {
    644   AA64PCRelAdr f;
    645   f.op = (w >> 31) & 1u;
    646   f.immlo = (w >> 29) & 3u;
    647   f.immhi = (w >> 5) & 0x7ffffu;
    648   f.Rd = w & 0x1fu;
    649   return f;
    650 }
    651 
    652 static inline u32 aa64_adrp(u32 Rd, u32 immlo, u32 immhi) {
    653   return aa64_pcrel_adr_pack((AA64PCRelAdr){
    654       .op = AA64_ADR_OP_ADRP, .immlo = immlo, .immhi = immhi, .Rd = Rd});
    655 }
    656 static inline u32 aa64_adr(u32 Rd, u32 immlo, u32 immhi) {
    657   return aa64_pcrel_adr_pack((AA64PCRelAdr){
    658       .op = AA64_ADR_OP_ADR, .immlo = immlo, .immhi = immhi, .Rd = Rd});
    659 }
    660 
    661 /* ====================================================================
    662  * Add/Sub, immediate (ADD / SUB / ADDS / SUBS, 12-bit imm with shift)
    663  *   sf  op(1)  S(1)  100010  sh(1)  imm12(12)  Rn(5)  Rd(5)
    664  *   31  30     29    28..23  22     21..10     9..5   4..0
    665  *
    666  * sh selects whether imm12 is left-shifted by 12.  Used by PLT entries
    667  * for `add x16, x16, #lo12(slot)` where sh=0 and imm12 = slot & 0xfff.
    668  * ==================================================================== */
    669 
    670 #define AA64_ADDSUBIMM_FAMILY_MATCH 0x11000000u
    671 #define AA64_ADDSUBIMM_FAMILY_MASK 0x1F000000u /* bits 28:24 */
    672 
    673 typedef struct AA64AddSubImm {
    674   u32 sf, op, S, sh, imm12, Rn, Rd;
    675 } AA64AddSubImm;
    676 
    677 static inline u32 aa64_addsubimm_pack(AA64AddSubImm f) {
    678   return ((f.sf & 1u) << 31) | ((f.op & 1u) << 30) | ((f.S & 1u) << 29) |
    679          AA64_ADDSUBIMM_FAMILY_MATCH | ((f.sh & 1u) << 22) |
    680          ((f.imm12 & 0xfffu) << 10) | ((f.Rn & 0x1fu) << 5) | (f.Rd & 0x1fu);
    681 }
    682 
    683 static inline AA64AddSubImm aa64_addsubimm_unpack(u32 w) {
    684   AA64AddSubImm f;
    685   f.sf = (w >> 31) & 1u;
    686   f.op = (w >> 30) & 1u;
    687   f.S = (w >> 29) & 1u;
    688   f.sh = (w >> 22) & 1u;
    689   f.imm12 = (w >> 10) & 0xfffu;
    690   f.Rn = (w >> 5) & 0x1fu;
    691   f.Rd = w & 0x1fu;
    692   return f;
    693 }
    694 
    695 static inline u32 aa64_add_imm(u32 sf, u32 Rd, u32 Rn, u32 imm12, u32 sh) {
    696   return aa64_addsubimm_pack((AA64AddSubImm){
    697       .sf = sf, .op = 0, .sh = sh, .imm12 = imm12, .Rn = Rn, .Rd = Rd});
    698 }
    699 static inline u32 aa64_sub_imm(u32 sf, u32 Rd, u32 Rn, u32 imm12, u32 sh) {
    700   return aa64_addsubimm_pack((AA64AddSubImm){
    701       .sf = sf, .op = 1, .sh = sh, .imm12 = imm12, .Rn = Rn, .Rd = Rd});
    702 }
    703 /* SUBS imm — sets flags. Used for CMP imm (Rd=ZR) and for branchless
    704  * compares that feed CSET. The 12-bit-shifted form covers 0..0xFFFFF000
    705  * stepped by 0x1000; cg_fold collapses literal-only compares upstream,
    706  * so this encoder is reached for `x cmp const` and `if (x)` patterns. */
    707 static inline u32 aa64_subs_imm12(u32 sf, u32 Rd, u32 Rn, u32 imm12, u32 sh) {
    708   return aa64_addsubimm_pack((AA64AddSubImm){
    709       .sf = sf, .op = 1, .S = 1, .sh = sh, .imm12 = imm12, .Rn = Rn, .Rd = Rd});
    710 }
    711 
    712 /* Predicate: does `imm` fit ADD/SUB/CMP's 12-bit immediate (optionally
    713  * left-shifted by 12)? On success writes the encoded imm12 and sh and
    714  * returns 1; on failure returns 0 and leaves outputs untouched.
    715  *
    716  * The encoding admits 0..4095 directly (sh=0) and multiples of 4096 up
    717  * to 0xFFF000 (sh=1). Negative literals are rejected here — the caller
    718  * (e.g. opt's machinize, or a smarter cg) is free to swap ADD ↔ SUB and
    719  * retry with the negated literal; the bare predicate keeps the contract
    720  * narrow. */
    721 static inline int aa64_addsub_imm_fits(i64 imm, u32* imm12_out, u32* sh_out) {
    722   if (imm < 0) return 0;
    723   u64 u = (u64)imm;
    724   if (u <= 0xFFFu) {
    725     *imm12_out = (u32)u;
    726     *sh_out = 0;
    727     return 1;
    728   }
    729   if ((u & 0xFFFu) == 0 && (u >> 12) <= 0xFFFu) {
    730     *imm12_out = (u32)(u >> 12);
    731     *sh_out = 1;
    732     return 1;
    733   }
    734   return 0;
    735 }
    736 
    737 /* ====================================================================
    738  * Load/store, unsigned 12-bit immediate offset (LDR / STR, scaled)
    739  *   size(2)  111  V(1)  01  opc(2)  imm12(12)  Rn(5)  Rt(5)
    740  *   31..30   29..27 26   25..24 23..22 21..10  9..5   4..0
    741  *
    742  * size=11, V=0, opc=01 → LDR (64-bit, integer).  imm12 is the byte
    743  * offset divided by the access size (8 for LDR Xt), giving a 0..32760
    744  * byte range.
    745  *
    746  * Only the LDR Xt form is needed by the linker today (PLT loads through
    747  * x16/x17); the family encoders cover STR and the smaller widths so
    748  * future callers can drop in without touching this header.
    749  * ==================================================================== */
    750 
    751 #define AA64_LDST_SIZE_64 3u
    752 #define AA64_LDST_OPC_STR 0u
    753 #define AA64_LDST_OPC_LDR 1u
    754 /* opc=10 -> sign-extending load into the 64-bit X register (LDRSB/LDRSH/LDRSW
    755  * Xt); opc=11 is the 32-bit W form. Codegen uses the X form so a narrow signed
    756  * load fills the whole register (the -O0 widening-signed-load lever). */
    757 #define AA64_LDST_OPC_LDRS_X 2u
    758 
    759 #define AA64_LDST_UIMM_FAMILY_MATCH 0x39000000u
    760 #define AA64_LDST_UIMM_FAMILY_MASK 0x3B000000u /* bits 29:27 + bits 25:24 */
    761 
    762 typedef struct AA64LdStUimm {
    763   u32 size, V, opc, imm12, Rn, Rt;
    764 } AA64LdStUimm;
    765 
    766 static inline u32 aa64_ldst_uimm_pack(AA64LdStUimm f) {
    767   return ((f.size & 3u) << 30) | AA64_LDST_UIMM_FAMILY_MATCH |
    768          ((f.V & 1u) << 26) | ((f.opc & 3u) << 22) |
    769          ((f.imm12 & 0xfffu) << 10) | ((f.Rn & 0x1fu) << 5) | (f.Rt & 0x1fu);
    770 }
    771 
    772 static inline AA64LdStUimm aa64_ldst_uimm_unpack(u32 w) {
    773   AA64LdStUimm f;
    774   f.size = (w >> 30) & 3u;
    775   f.V = (w >> 26) & 1u;
    776   f.opc = (w >> 22) & 3u;
    777   f.imm12 = (w >> 10) & 0xfffu;
    778   f.Rn = (w >> 5) & 0x1fu;
    779   f.Rt = w & 0x1fu;
    780   return f;
    781 }
    782 
    783 /* LDR Xt, [Xn, #imm12_scaled].  imm12_scaled is the encoded field —
    784  * callers pass `byte_offset >> 3` for the 64-bit form. */
    785 static inline u32 aa64_ldr64_uimm12(u32 Rt, u32 Rn, u32 imm12_scaled) {
    786   return aa64_ldst_uimm_pack((AA64LdStUimm){.size = AA64_LDST_SIZE_64,
    787                                             .V = 0,
    788                                             .opc = AA64_LDST_OPC_LDR,
    789                                             .imm12 = imm12_scaled,
    790                                             .Rn = Rn,
    791                                             .Rt = Rt});
    792 }
    793 static inline u32 aa64_str64_uimm12(u32 Rt, u32 Rn, u32 imm12_scaled) {
    794   return aa64_ldst_uimm_pack((AA64LdStUimm){.size = AA64_LDST_SIZE_64,
    795                                             .V = 0,
    796                                             .opc = AA64_LDST_OPC_STR,
    797                                             .imm12 = imm12_scaled,
    798                                             .Rn = Rn,
    799                                             .Rt = Rt});
    800 }
    801 
    802 /* ---- Scalar floating-point encoders ----
    803  * ftype: 0=single (Sn), 1=double (Dn), 3=half (Hn). The bit layouts match the
    804  * FP_* decode rows in isa.c and the aa_* encoders in native.c, so encode and
    805  * decode round-trip. The DP2/DP1 `op` and the FP_INT_CVT `opcode` are the
    806  * named field values below. */
    807 #define AA64_FP_DP2_FMUL 0x0800u
    808 #define AA64_FP_DP2_FDIV 0x1800u
    809 #define AA64_FP_DP2_FADD 0x2800u
    810 #define AA64_FP_DP2_FSUB 0x3800u
    811 #define AA64_FP_DP2_FMAX 0x4800u
    812 #define AA64_FP_DP2_FMIN 0x5800u
    813 #define AA64_FP_DP2_FNMUL 0x8800u
    814 #define AA64_FP_DP1_FMOV 0x4000u
    815 #define AA64_FP_DP1_FABS 0xC000u
    816 #define AA64_FP_DP1_FNEG 0x14000u
    817 #define AA64_FP_DP1_FSQRT 0x1C000u
    818 #define AA64_FP_ICVT_SCVTF 0x02u
    819 #define AA64_FP_ICVT_UCVTF 0x03u
    820 #define AA64_FP_ICVT_FCVTZS 0x18u
    821 #define AA64_FP_ICVT_FCVTZU 0x19u
    822 #define AA64_FP_ICVT_FMOV_TO_GPR 0x06u /* fmov Rd, Vn */
    823 #define AA64_FP_ICVT_FMOV_TO_FP 0x07u  /* fmov Vd, Rn */
    824 
    825 static inline u32 aa64_fp_dp2(u32 ftype, u32 op, u32 Rd, u32 Rn, u32 Rm) {
    826   return 0x1E200000u | ((ftype & 3u) << 22) | op | ((Rm & 0x1fu) << 16) |
    827          ((Rn & 0x1fu) << 5) | (Rd & 0x1fu);
    828 }
    829 static inline u32 aa64_fp_dp1(u32 ftype, u32 op, u32 Rd, u32 Rn) {
    830   return 0x1E200000u | ((ftype & 3u) << 22) | op | ((Rn & 0x1fu) << 5) |
    831          (Rd & 0x1fu);
    832 }
    833 static inline u32 aa64_fcmp_reg(u32 ftype, u32 Rn, u32 Rm) {
    834   return 0x1E202000u | ((ftype & 3u) << 22) | ((Rm & 0x1fu) << 16) |
    835          ((Rn & 0x1fu) << 5);
    836 }
    837 static inline u32 aa64_fcvt_prec(u32 src_ftype, u32 dst_ftype, u32 Rd, u32 Rn) {
    838   return 0x1E204000u | ((src_ftype & 3u) << 22) | (1u << 17) |
    839          ((dst_ftype & 3u) << 15) | ((Rn & 0x1fu) << 5) | (Rd & 0x1fu);
    840 }
    841 static inline u32 aa64_fp_int_cvt(u32 sf, u32 ftype, u32 opcode, u32 Rd,
    842                                   u32 Rn) {
    843   return ((sf & 1u) << 31) | 0x1E200000u | ((ftype & 3u) << 22) |
    844          ((opcode & 0x1fu) << 16) | ((Rn & 0x1fu) << 5) | (Rd & 0x1fu);
    845 }
    846 
    847 /* Bitfield move (opc: 0=SBFM, 1=BFM, 2=UBFM). The N bit tracks sf for the
    848  * 32-/64-bit forms. Matches native.c aa_sbfm/aa_ubfm and the BITFIELD row. */
    849 static inline u32 aa64_bitfield(u32 sf, u32 opc, u32 immr, u32 imms, u32 Rd,
    850                                 u32 Rn) {
    851   return ((sf & 1u) << 31) | ((opc & 3u) << 29) | 0x13000000u |
    852          ((sf & 1u) << 22) | ((immr & 0x3fu) << 16) | ((imms & 0x3fu) << 10) |
    853          ((Rn & 0x1fu) << 5) | (Rd & 0x1fu);
    854 }
    855 
    856 /* Data-processing (1 source). opcode2 (bits[15:10]): RBIT=0, REV16=1,
    857  * REV(32)=2, REV(64)=3, CLZ=4. Matches native.c aa_clz/aa_rbit/aa_rev. */
    858 #define AA64_DP1_RBIT 0x00u
    859 #define AA64_DP1_REV16 0x01u
    860 #define AA64_DP1_REV32 0x02u
    861 #define AA64_DP1_REV64 0x03u
    862 #define AA64_DP1_CLZ 0x04u
    863 static inline u32 aa64_dp1(u32 sf, u32 opcode2, u32 Rd, u32 Rn) {
    864   return ((sf & 1u) << 31) | 0x5AC00000u | ((opcode2 & 0x3fu) << 10) |
    865          ((Rn & 0x1fu) << 5) | (Rd & 0x1fu);
    866 }
    867 
    868 /* ====================================================================
    869  * Load/store register pair, pre-indexed (STP / LDP, 64-bit form)
    870  *   opc(2)  101  V(1)  010  L(1)  imm7(7)  Rt2(5)  Rn(5)  Rt(5)
    871  *   31..30  29..27 26   25..23 22  21..15   14..10  9..5   4..0
    872  *
    873  * 64-bit integer form fixes opc=10, V=0.  L=0 → STP, L=1 → LDP.
    874  * imm7 is a signed 7-bit value scaled by 8 (for the 64-bit form): the
    875  * encoded field equals `byte_offset / 8`.  Callers pass the scaled
    876  * value already; the helper masks to 7 bits to handle negative inputs
    877  * sign-extended in i32.
    878  * ==================================================================== */
    879 
    880 #define AA64_LDSTP_PRE_FAMILY_MATCH 0x29800000u
    881 #define AA64_LDSTP_PRE_FAMILY_MASK 0x7FC00000u /* bits 30:23 */
    882 
    883 typedef struct AA64LdStPPre {
    884   u32 opc, V, L, imm7, Rt2, Rn, Rt;
    885 } AA64LdStPPre;
    886 
    887 static inline u32 aa64_ldstp_pre_pack(AA64LdStPPre f) {
    888   return ((f.opc & 3u) << 30) | AA64_LDSTP_PRE_FAMILY_MATCH |
    889          ((f.V & 1u) << 26) | ((f.L & 1u) << 22) | ((f.imm7 & 0x7fu) << 15) |
    890          ((f.Rt2 & 0x1fu) << 10) | ((f.Rn & 0x1fu) << 5) | (f.Rt & 0x1fu);
    891 }
    892 
    893 static inline AA64LdStPPre aa64_ldstp_pre_unpack(u32 w) {
    894   AA64LdStPPre f;
    895   f.opc = (w >> 30) & 3u;
    896   f.V = (w >> 26) & 1u;
    897   f.L = (w >> 22) & 1u;
    898   f.imm7 = (w >> 15) & 0x7fu;
    899   f.Rt2 = (w >> 10) & 0x1fu;
    900   f.Rn = (w >> 5) & 0x1fu;
    901   f.Rt = w & 0x1fu;
    902   return f;
    903 }
    904 
    905 /* STP Xt, Xt2, [Xn, #imm7_scaled]!  — opc=10 selects the 64-bit form.
    906  * imm7_scaled is `byte_offset / 8`; callers pass it pre-scaled (e.g.
    907  * -2 for [sp, #-16]!). */
    908 static inline u32 aa64_stp64_pre(u32 Rt, u32 Rt2, u32 Rn, i32 imm7_scaled) {
    909   return aa64_ldstp_pre_pack((AA64LdStPPre){.opc = 2,
    910                                             .V = 0,
    911                                             .L = 0,
    912                                             .imm7 = (u32)imm7_scaled & 0x7fu,
    913                                             .Rt2 = Rt2,
    914                                             .Rn = Rn,
    915                                             .Rt = Rt});
    916 }
    917 static inline u32 aa64_ldp64_pre(u32 Rt, u32 Rt2, u32 Rn, i32 imm7_scaled) {
    918   return aa64_ldstp_pre_pack((AA64LdStPPre){.opc = 2,
    919                                             .V = 0,
    920                                             .L = 1,
    921                                             .imm7 = (u32)imm7_scaled & 0x7fu,
    922                                             .Rt2 = Rt2,
    923                                             .Rn = Rn,
    924                                             .Rt = Rt});
    925 }
    926 
    927 /* Post-indexed STP/LDP — same field layout as the pre-indexed form, only
    928  * bits[25:23] differ (001 vs 011); reuse AA64LdStPPre. Used for the slim
    929  * prologue's epilogue restore: `ldp x29,x30,[sp],#16`. */
    930 #define AA64_LDSTP_POST_FAMILY_MATCH 0x28800000u
    931 #define AA64_LDSTP_POST_FAMILY_MASK 0x7FC00000u /* bits 30:23 */
    932 
    933 static inline u32 aa64_ldstp_post_pack(AA64LdStPPre f) {
    934   return ((f.opc & 3u) << 30) | AA64_LDSTP_POST_FAMILY_MATCH |
    935          ((f.V & 1u) << 26) | ((f.L & 1u) << 22) | ((f.imm7 & 0x7fu) << 15) |
    936          ((f.Rt2 & 0x1fu) << 10) | ((f.Rn & 0x1fu) << 5) | (f.Rt & 0x1fu);
    937 }
    938 
    939 static inline u32 aa64_stp64_post(u32 Rt, u32 Rt2, u32 Rn, i32 imm7_scaled) {
    940   return aa64_ldstp_post_pack((AA64LdStPPre){.opc = 2,
    941                                              .V = 0,
    942                                              .L = 0,
    943                                              .imm7 = (u32)imm7_scaled & 0x7fu,
    944                                              .Rt2 = Rt2,
    945                                              .Rn = Rn,
    946                                              .Rt = Rt});
    947 }
    948 static inline u32 aa64_ldp64_post(u32 Rt, u32 Rt2, u32 Rn, i32 imm7_scaled) {
    949   return aa64_ldstp_post_pack((AA64LdStPPre){.opc = 2,
    950                                              .V = 0,
    951                                              .L = 1,
    952                                              .imm7 = (u32)imm7_scaled & 0x7fu,
    953                                              .Rt2 = Rt2,
    954                                              .Rn = Rn,
    955                                              .Rt = Rt});
    956 }
    957 
    958 /* ====================================================================
    959  * Hint instructions (NOP / YIELD / WFE / WFI / SEV / SEVL)
    960  *   1101 0101 0000 0011 0010 CRm(4) op2(3) 11111
    961  *   31..16             15..12 11..8 7..5    4..0
    962  *
    963  * NOP encodes CRm=0, op2=0 → 0xD503201F.  The full hint family lives
    964  * inside the system-instruction space; we only expose NOP today since
    965  * that's the only slot the linker fills.
    966  * ==================================================================== */
    967 
    968 #define AA64_HINT_FAMILY_MATCH 0xD503201Fu
    969 #define AA64_HINT_FAMILY_MASK 0xFFFFF01Fu /* CRm + op2 vary */
    970 
    971 /* HINT #N with CRm=0: op2 selects the variant. */
    972 #define AA64_HINT_OP_NOP 0u   /* CRm=0, op2=0 */
    973 #define AA64_HINT_OP_YIELD 1u /* CRm=0, op2=1 */
    974 #define AA64_HINT_OP_WFE 2u   /* CRm=0, op2=2 */
    975 #define AA64_HINT_OP_WFI 3u   /* CRm=0, op2=3 */
    976 #define AA64_HINT_OP_SEV 4u   /* CRm=0, op2=4 */
    977 #define AA64_HINT_OP_SEVL 5u  /* CRm=0, op2=5 */
    978 
    979 typedef struct AA64Hint {
    980   u32 CRm, op2;
    981 } AA64Hint;
    982 
    983 static inline u32 aa64_hint_pack(AA64Hint f) {
    984   return AA64_HINT_FAMILY_MATCH | ((f.CRm & 0xfu) << 8) | ((f.op2 & 7u) << 5);
    985 }
    986 
    987 static inline u32 aa64_hint(u32 op2) {
    988   return aa64_hint_pack((AA64Hint){.CRm = 0, .op2 = op2});
    989 }
    990 
    991 static inline AA64Hint aa64_hint_unpack(u32 w) {
    992   AA64Hint f;
    993   f.CRm = (w >> 8) & 0xfu;
    994   f.op2 = (w >> 5) & 7u;
    995   return f;
    996 }
    997 
    998 static inline u32 aa64_nop(void) {
    999   return aa64_hint_pack((AA64Hint){.CRm = 0, .op2 = AA64_HINT_OP_NOP});
   1000 }
   1001 
   1002 /* ====================================================================
   1003  * Memory barriers (DMB / DSB / ISB / CLREX)
   1004  *   1101 0101 0000 0011 0011 CRm(4) op2(3) 11111
   1005  *   31..16             15..12 11..8 7..5    4..0
   1006  *
   1007  * Shared encoding family with HINT (which uses bits[15:12]=0010);
   1008  * barriers use bits[15:12]=0011. op2 selects the specific instruction:
   1009  *   CLREX=010  DSB=100  DMB=101  ISB=110
   1010  * CRm is the option / domain (SY=15, ISH=11, NSH=7, OSH=3, ...).
   1011  * ==================================================================== */
   1012 
   1013 #define AA64_BARRIER_FAMILY_MATCH 0xD503301Fu
   1014 #define AA64_BARRIER_FAMILY_MASK 0xFFFFF01Fu /* CRm + op2 vary */
   1015 
   1016 #define AA64_BARRIER_OP2_CLREX 2u
   1017 #define AA64_BARRIER_OP2_DSB 4u
   1018 #define AA64_BARRIER_OP2_DMB 5u
   1019 #define AA64_BARRIER_OP2_ISB 6u
   1020 
   1021 /* Common CRm option encodings (ARM ARM C5.1.42). */
   1022 #define AA64_BARRIER_OPT_OSHLD 1u
   1023 #define AA64_BARRIER_OPT_OSHST 2u
   1024 #define AA64_BARRIER_OPT_OSH 3u
   1025 #define AA64_BARRIER_OPT_NSHLD 5u
   1026 #define AA64_BARRIER_OPT_NSHST 6u
   1027 #define AA64_BARRIER_OPT_NSH 7u
   1028 #define AA64_BARRIER_OPT_ISHLD 9u
   1029 #define AA64_BARRIER_OPT_ISHST 10u
   1030 #define AA64_BARRIER_OPT_ISH 11u
   1031 #define AA64_BARRIER_OPT_LD 13u
   1032 #define AA64_BARRIER_OPT_ST 14u
   1033 #define AA64_BARRIER_OPT_SY 15u
   1034 
   1035 typedef struct AA64Barrier {
   1036   u32 CRm, op2;
   1037 } AA64Barrier;
   1038 
   1039 static inline u32 aa64_barrier_pack(AA64Barrier f) {
   1040   return AA64_BARRIER_FAMILY_MATCH | ((f.CRm & 0xfu) << 8) |
   1041          ((f.op2 & 7u) << 5);
   1042 }
   1043 
   1044 static inline AA64Barrier aa64_barrier_unpack(u32 w) {
   1045   AA64Barrier f;
   1046   f.CRm = (w >> 8) & 0xfu;
   1047   f.op2 = (w >> 5) & 7u;
   1048   return f;
   1049 }
   1050 
   1051 static inline u32 aa64_dmb(u32 opt) {
   1052   return aa64_barrier_pack(
   1053       (AA64Barrier){.CRm = opt, .op2 = AA64_BARRIER_OP2_DMB});
   1054 }
   1055 static inline u32 aa64_dsb(u32 opt) {
   1056   return aa64_barrier_pack(
   1057       (AA64Barrier){.CRm = opt, .op2 = AA64_BARRIER_OP2_DSB});
   1058 }
   1059 static inline u32 aa64_isb(u32 opt) {
   1060   return aa64_barrier_pack(
   1061       (AA64Barrier){.CRm = opt, .op2 = AA64_BARRIER_OP2_ISB});
   1062 }
   1063 static inline u32 aa64_clrex(u32 opt) {
   1064   return aa64_barrier_pack(
   1065       (AA64Barrier){.CRm = opt, .op2 = AA64_BARRIER_OP2_CLREX});
   1066 }
   1067 
   1068 /* ====================================================================
   1069  * Interrupt-mask (DAIF) system register access. Used by the IRQ-control
   1070  * intrinsics; privileged at EL0. Only the encodings the backend emits live
   1071  * here (they are not registered in the disassembler's mnemonic table).
   1072  *   MRS Xt, DAIF       : 1101 0101 0011 1011 0100 0010 000 Rt -> 0xD53B4200|Rt
   1073  *   MSR DAIF, Xt       : 1101 0101 0001 1011 0100 0010 000 Rt -> 0xD51B4200|Rt
   1074  *   MSR DAIFSet, #imm4 : op1=011, op2=110 -> 0xD50340DF | (imm4 << 8)
   1075  *   MSR DAIFClr, #imm4 : op1=011, op2=111 -> 0xD50340FF | (imm4 << 8)
   1076  * imm4 = 0xF masks/unmasks D,A,I,F together. ==================== */
   1077 #define AA64_DAIF_ALL 0xfu
   1078 
   1079 static inline u32 aa64_mrs_daif(u32 rt) { return 0xD53B4200u | (rt & 0x1fu); }
   1080 static inline u32 aa64_msr_daif(u32 rt) { return 0xD51B4200u | (rt & 0x1fu); }
   1081 static inline u32 aa64_msr_daifset(u32 imm4) {
   1082   return 0xD50340DFu | ((imm4 & 0xfu) << 8);
   1083 }
   1084 static inline u32 aa64_msr_daifclr(u32 imm4) {
   1085   return 0xD50340FFu | ((imm4 & 0xfu) << 8);
   1086 }
   1087 
   1088 /* ====================================================================
   1089  * Generic system-register move (MRS/MSR register form). A named system
   1090  * register is the 15-bit selector op0:op1:CRn:CRm:op2 (op0's high bit is
   1091  * fixed by the encoding, so only its low bit is a field).
   1092  *   MRS Xt, <sysreg> : 1101 0101 0 0 1 op0lo op1 CRn CRm op2 Rt  (read,  L=1)
   1093  *   MSR <sysreg>, Xt : 1101 0101 0 0 0 op0lo op1 CRn CRm op2 Rt  (write, L=0)
   1094  * e.g. TPIDR_EL0 = (op0=3,op1=3,CRn=13,CRm=0,op2=2):
   1095  *   MSR TPIDR_EL0, X0 -> 0xd51bd040 ; MRS X0, TPIDR_EL0 -> 0xd53bd040. */
   1096 static inline u32 aa64_sysreg_move(int is_read, u32 op0, u32 op1, u32 crn,
   1097                                    u32 crm, u32 op2, u32 rt) {
   1098   return 0xd5000000u | (is_read ? (1u << 21) : 0u) | ((op0 & 3u) << 19) |
   1099          ((op1 & 7u) << 16) | ((crn & 0xfu) << 12) | ((crm & 0xfu) << 8) |
   1100          ((op2 & 7u) << 5) | (rt & 0x1fu);
   1101 }
   1102 
   1103 /* System-register move encoding family: MRS (read, L=1) and MSR (write,
   1104  * L=0). The disassembler matches these; the register selector op0:op1:CRn:
   1105  * CRm:op2 and Rt are decoded from the word. op0's high bit is fixed (bit
   1106  * 20), so the mask pins bits[31:20] and leaves op0lo/op1/CRn/CRm/op2/Rt. */
   1107 #define AA64_MRS_MATCH 0xd5300000u
   1108 #define AA64_MSR_MATCH 0xd5100000u
   1109 #define AA64_SYSREG_MOVE_MASK 0xfff00000u
   1110 
   1111 /* Shared system-register name table (single source for the assembler's
   1112  * name->selector parse and the disassembler's selector->name print). */
   1113 typedef struct AA64SysRegName {
   1114   const char* name;
   1115   u8 op0, op1, crn, crm, op2;
   1116 } AA64SysRegName;
   1117 
   1118 /* Resolve a system-register name (case-insensitive, length n) to its five
   1119  * selector fields. Returns 1 on a hit, 0 otherwise. */
   1120 int aa64_sysreg_by_name(const char* s, size_t n, u32* op0, u32* op1, u32* crn,
   1121                         u32* crm, u32* op2);
   1122 
   1123 /* Reverse lookup: canonical lowercase name for a selector, or NULL when the
   1124  * selector is not in the table (the caller prints the generic Sx_x_Cx_Cx_x
   1125  * spelling instead). */
   1126 const char* aa64_sysreg_name(u32 op0, u32 op1, u32 crn, u32 crm, u32 op2);
   1127 
   1128 /* Condition-code <-> name, the single source of truth shared by the
   1129  * disassembler, the codegen/assembler print path, and the assembler parse.
   1130  * aa64_cond_name returns the canonical lowercase suffix for a 4-bit cond
   1131  * (masked to bits[3:0]); EQ=0..AL=14, NV=15. aa64_cond_from_name parses a
   1132  * name (case-insensitive, length n) back to its index, accepting the HS/LO
   1133  * aliases for CS/CC; it rejects NV. Returns 1 on a hit, 0 otherwise. */
   1134 const char* aa64_cond_name(u32 cond);
   1135 int aa64_cond_from_name(const char* s, size_t n, u32* out);
   1136 
   1137 /* ====================================================================
   1138  * Load/store pair, signed-offset (STP / LDP, no pre/post-increment).
   1139  *   opc(2) 101 V(1) 010 L(1) imm7 Rt2 Rn Rt          (bit 23 = 0)
   1140  *
   1141  * Mirrors the LDSTP_PRE format with bit 23 cleared; the field layout is
   1142  * otherwise identical and the pack/unpack helpers above are reused for
   1143  * pre/post/sign-offset via different family-match constants.  Codegen
   1144  * emits both X (opc=10) and FP-D (opc=01, V=1) variants for callee-save
   1145  * spill/reload (`stp x29,x30,[sp,#16]`, `stp d8,d9,[sp,#32]`). */
   1146 
   1147 #define AA64_LDSTP_SOFF_FAMILY_MATCH 0x29000000u
   1148 #define AA64_LDSTP_SOFF_FAMILY_MASK 0x7FC00000u /* bits 30:23 (bit 23 = 0) */
   1149 
   1150 typedef AA64LdStPPre AA64LdStPSOff;
   1151 
   1152 static inline u32 aa64_ldstp_soff_pack(AA64LdStPSOff f) {
   1153   return ((f.opc & 3u) << 30) | AA64_LDSTP_SOFF_FAMILY_MATCH |
   1154          ((f.V & 1u) << 26) | ((f.L & 1u) << 22) | ((f.imm7 & 0x7fu) << 15) |
   1155          ((f.Rt2 & 0x1fu) << 10) | ((f.Rn & 0x1fu) << 5) | (f.Rt & 0x1fu);
   1156 }
   1157 
   1158 static inline AA64LdStPSOff aa64_ldstp_soff_unpack(u32 w) {
   1159   AA64LdStPSOff f;
   1160   f.opc = (w >> 30) & 3u;
   1161   f.V = (w >> 26) & 1u;
   1162   f.L = (w >> 22) & 1u;
   1163   f.imm7 = (w >> 15) & 0x7fu;
   1164   f.Rt2 = (w >> 10) & 0x1fu;
   1165   f.Rn = (w >> 5) & 0x1fu;
   1166   f.Rt = w & 0x1fu;
   1167   return f;
   1168 }
   1169 
   1170 /* 64-bit integer STP/LDP, signed offset (no writeback). imm7_scaled is
   1171  * byte_offset / 8. Used for the prologue/epilogue frame record and callee-save
   1172  * pairs, which address off a fixed base (x17 / fp). */
   1173 static inline u32 aa64_stp64_soff(u32 Rt, u32 Rt2, u32 Rn, i32 imm7_scaled) {
   1174   return aa64_ldstp_soff_pack((AA64LdStPSOff){.opc = 2,
   1175                                               .V = 0,
   1176                                               .L = 0,
   1177                                               .imm7 = (u32)imm7_scaled & 0x7fu,
   1178                                               .Rt2 = Rt2,
   1179                                               .Rn = Rn,
   1180                                               .Rt = Rt});
   1181 }
   1182 static inline u32 aa64_ldp64_soff(u32 Rt, u32 Rt2, u32 Rn, i32 imm7_scaled) {
   1183   return aa64_ldstp_soff_pack((AA64LdStPSOff){.opc = 2,
   1184                                               .V = 0,
   1185                                               .L = 1,
   1186                                               .imm7 = (u32)imm7_scaled & 0x7fu,
   1187                                               .Rt2 = Rt2,
   1188                                               .Rn = Rn,
   1189                                               .Rt = Rt});
   1190 }
   1191 
   1192 /* ====================================================================
   1193  * Load/store, unscaled 9-bit signed offset (LDUR / STUR, V=0 and V=1).
   1194  *   size(2) 111 V(1) 00 opc(2) 0 imm9(9) 00 Rn(5) Rt(5)
   1195  *   31..30  29..27 26  25..24 23..22 21  20..12  11..10 9..5 4..0
   1196  *
   1197  * size: 00=B, 01=H, 10=W, 11=X (V=0) — D when V=1 selects FP/SIMD.
   1198  * opc: 00=STR, 01=LDR (sign-extension variants set opc bit 1 for the
   1199  *      smaller widths; not used by codegen today). */
   1200 
   1201 #define AA64_LDST_SIMM9_FAMILY_MATCH 0x38000000u
   1202 /* bits 29:27 (=111) + bits 25:24 (=00) + bits 11:10 (=00). size, V, opc,
   1203  * imm9, Rn, Rt all vary; bit 21 is fixed 0 for this variant. */
   1204 #define AA64_LDST_SIMM9_FAMILY_MASK 0x3B200C00u
   1205 
   1206 typedef struct AA64LdStSimm9 {
   1207   u32 size, V, opc, imm9, Rn, Rt;
   1208 } AA64LdStSimm9;
   1209 
   1210 static inline u32 aa64_ldst_simm9_pack(AA64LdStSimm9 f) {
   1211   return ((f.size & 3u) << 30) | AA64_LDST_SIMM9_FAMILY_MATCH |
   1212          ((f.V & 1u) << 26) | ((f.opc & 3u) << 22) | ((f.imm9 & 0x1ffu) << 12) |
   1213          ((f.Rn & 0x1fu) << 5) | (f.Rt & 0x1fu);
   1214 }
   1215 
   1216 static inline AA64LdStSimm9 aa64_ldst_simm9_unpack(u32 w) {
   1217   AA64LdStSimm9 f;
   1218   f.size = (w >> 30) & 3u;
   1219   f.V = (w >> 26) & 1u;
   1220   f.opc = (w >> 22) & 3u;
   1221   f.imm9 = (w >> 12) & 0x1ffu;
   1222   f.Rn = (w >> 5) & 0x1fu;
   1223   f.Rt = w & 0x1fu;
   1224   return f;
   1225 }
   1226 
   1227 /* ====================================================================
   1228  * Load/store, register offset (LDR/STR Rt,[Xn,Rm{,extend{#s}}]).
   1229  *   size(2) 111 V(1) 00 opc(2) 1 Rm(5) option(3) S(1) 10 Rn(5) Rt(5)
   1230  *   31..30  29..27 26 25..24 23..22 21 20..16 15..13  12  11..10 9..5 4..0
   1231  *
   1232  * option selects the index extend: 010=UXTW, 011=LSL/UXTX, 110=SXTW,
   1233  * 111=SXTX. S=1 scales the index by the access size (log2 = size); S=0
   1234  * leaves it unscaled. opc/size match the uimm12 form. */
   1235 
   1236 #define AA64_LDST_REGOFF_FAMILY_MATCH 0x38200800u
   1237 /* bits 29:27 (=111), 25:24 (=00), 21 (=1), 11:10 (=10). */
   1238 #define AA64_LDST_REGOFF_FAMILY_MASK 0x3B200C00u
   1239 
   1240 /* Index-extend option encodings. */
   1241 #define AA64_LDST_OPTION_UXTW 2u
   1242 #define AA64_LDST_OPTION_LSL 3u /* a.k.a. UXTX for 64-bit index */
   1243 #define AA64_LDST_OPTION_SXTW 6u
   1244 #define AA64_LDST_OPTION_SXTX 7u
   1245 
   1246 typedef struct AA64LdStRegOff {
   1247   u32 size, V, opc, Rm, option, S, Rn, Rt;
   1248 } AA64LdStRegOff;
   1249 
   1250 static inline u32 aa64_ldst_regoff_pack(AA64LdStRegOff f) {
   1251   return ((f.size & 3u) << 30) | AA64_LDST_REGOFF_FAMILY_MATCH |
   1252          ((f.V & 1u) << 26) | ((f.opc & 3u) << 22) | ((f.Rm & 0x1fu) << 16) |
   1253          ((f.option & 7u) << 13) | ((f.S & 1u) << 12) | ((f.Rn & 0x1fu) << 5) |
   1254          (f.Rt & 0x1fu);
   1255 }
   1256 
   1257 static inline AA64LdStRegOff aa64_ldst_regoff_unpack(u32 w) {
   1258   AA64LdStRegOff f;
   1259   f.size = (w >> 30) & 3u;
   1260   f.V = (w >> 26) & 1u;
   1261   f.opc = (w >> 22) & 3u;
   1262   f.Rm = (w >> 16) & 0x1fu;
   1263   f.option = (w >> 13) & 7u;
   1264   f.S = (w >> 12) & 1u;
   1265   f.Rn = (w >> 5) & 0x1fu;
   1266   f.Rt = w & 0x1fu;
   1267   return f;
   1268 }
   1269 
   1270 /* ====================================================================
   1271  * Load/store, immediate pre/post-index (writeback).
   1272  *   size(2) 111 V(1) 00 opc(2) 0 imm9(9) idx(2) Rn(5) Rt(5)
   1273  *   31..30  29..27 26 25..24 23..22 21 20..12 11..10 9..5 4..0
   1274  *
   1275  * idx (bits[11:10]) selects: 00=unscaled (LDUR, no writeback — see the
   1276  * SIMM9 helpers above), 01=post-index, 11=pre-index. imm9 is the
   1277  * unscaled signed byte offset (-256..255). */
   1278 
   1279 #define AA64_LDST_IDX_POST 1u
   1280 #define AA64_LDST_IDX_PRE 3u
   1281 
   1282 typedef struct AA64LdStWBack {
   1283   u32 size, V, opc, imm9, idx, Rn, Rt;
   1284 } AA64LdStWBack;
   1285 
   1286 static inline u32 aa64_ldst_wback_pack(AA64LdStWBack f) {
   1287   return ((f.size & 3u) << 30) | AA64_LDST_SIMM9_FAMILY_MATCH |
   1288          ((f.V & 1u) << 26) | ((f.opc & 3u) << 22) | ((f.imm9 & 0x1ffu) << 12) |
   1289          ((f.idx & 3u) << 10) | ((f.Rn & 0x1fu) << 5) | (f.Rt & 0x1fu);
   1290 }
   1291 
   1292 static inline AA64LdStWBack aa64_ldst_wback_unpack(u32 w) {
   1293   AA64LdStWBack f;
   1294   f.size = (w >> 30) & 3u;
   1295   f.V = (w >> 26) & 1u;
   1296   f.opc = (w >> 22) & 3u;
   1297   f.imm9 = (w >> 12) & 0x1ffu;
   1298   f.idx = (w >> 10) & 3u;
   1299   f.Rn = (w >> 5) & 0x1fu;
   1300   f.Rt = w & 0x1fu;
   1301   return f;
   1302 }
   1303 
   1304 /* ====================================================================
   1305  * Load/store exclusive (LDXR/STXR + acquire/release variants).
   1306  *   size(2) 001000 o2(1) L(1) o1(1) Rs(5) o0(1) Rt2(5) Rn(5) Rt(5)
   1307  *   31..30  29..24  23    22   21   20..16 15   14..10 9..5  4..0
   1308  *
   1309  * size: 00=byte,01=half,10=word,11=dword.  o1=0 for the LDXR/STXR
   1310  * single-register family (CAS sets o1=1 via the CAS pack below).
   1311  *   LDXR:  L=1 o0=0 o2=0      STXR:  L=0 o0=0 o2=0
   1312  *   LDAXR: L=1 o0=1 o2=0      STLXR: L=0 o0=1 o2=0
   1313  *   LDAR:  L=1 o0=1 o2=1      STLR:  L=0 o0=1 o2=1
   1314  * For LDXR/LDAXR/LDAR/STLR, Rs and Rt2 are unused (encode 11111). */
   1315 
   1316 #define AA64_LDSTEX_FAMILY_MATCH 0x08000000u
   1317 /* bits 29:24 (=001000). */
   1318 #define AA64_LDSTEX_FAMILY_MASK 0x3F000000u
   1319 
   1320 typedef struct AA64LdStEx {
   1321   u32 size, o2, L, o1, Rs, o0, Rt2, Rn, Rt;
   1322 } AA64LdStEx;
   1323 
   1324 static inline u32 aa64_ldstex_pack(AA64LdStEx f) {
   1325   return ((f.size & 3u) << 30) | AA64_LDSTEX_FAMILY_MATCH |
   1326          ((f.o2 & 1u) << 23) | ((f.L & 1u) << 22) | ((f.o1 & 1u) << 21) |
   1327          ((f.Rs & 0x1fu) << 16) | ((f.o0 & 1u) << 15) |
   1328          ((f.Rt2 & 0x1fu) << 10) | ((f.Rn & 0x1fu) << 5) | (f.Rt & 0x1fu);
   1329 }
   1330 
   1331 static inline AA64LdStEx aa64_ldstex_unpack(u32 w) {
   1332   AA64LdStEx f;
   1333   f.size = (w >> 30) & 3u;
   1334   f.o2 = (w >> 23) & 1u;
   1335   f.L = (w >> 22) & 1u;
   1336   f.o1 = (w >> 21) & 1u;
   1337   f.Rs = (w >> 16) & 0x1fu;
   1338   f.o0 = (w >> 15) & 1u;
   1339   f.Rt2 = (w >> 10) & 0x1fu;
   1340   f.Rn = (w >> 5) & 0x1fu;
   1341   f.Rt = w & 0x1fu;
   1342   return f;
   1343 }
   1344 
   1345 /* ====================================================================
   1346  * Compare and swap (CAS / CASA / CASL / CASAL + b/h variants, LSE).
   1347  *   size(2) 001000 1 L(1) 1 Rs(5) o0(1) 11111 Rn(5) Rt(5)
   1348  *   31..30  29..23 . 22  . 20..16 15    14..10 9..5  4..0
   1349  *
   1350  *   CAS:  L=0 o0=0    CASA:  L=1 o0=0
   1351  *   CASL: L=0 o0=1    CASAL: L=1 o0=1
   1352  * Rt2 (bits[14:10]) is fixed at 11111. */
   1353 
   1354 #define AA64_CAS_FAMILY_MATCH 0x08a07c00u
   1355 /* bits 29:24 (=001000), 23 (=1), 21 (=1), 14:10 (=11111). */
   1356 #define AA64_CAS_FAMILY_MASK 0x3Fa0fc00u
   1357 
   1358 typedef struct AA64Cas {
   1359   u32 size, L, Rs, o0, Rn, Rt;
   1360 } AA64Cas;
   1361 
   1362 static inline u32 aa64_cas_pack(AA64Cas f) {
   1363   return ((f.size & 3u) << 30) | AA64_CAS_FAMILY_MATCH | ((f.L & 1u) << 22) |
   1364          ((f.Rs & 0x1fu) << 16) | ((f.o0 & 1u) << 15) | ((f.Rn & 0x1fu) << 5) |
   1365          (f.Rt & 0x1fu);
   1366 }
   1367 
   1368 static inline AA64Cas aa64_cas_unpack(u32 w) {
   1369   AA64Cas f;
   1370   f.size = (w >> 30) & 3u;
   1371   f.L = (w >> 22) & 1u;
   1372   f.Rs = (w >> 16) & 0x1fu;
   1373   f.o0 = (w >> 15) & 1u;
   1374   f.Rn = (w >> 5) & 0x1fu;
   1375   f.Rt = w & 0x1fu;
   1376   return f;
   1377 }
   1378 
   1379 /* ====================================================================
   1380  * LSE atomic memory operations (SWP / LDADD / LDCLR / LDEOR / LDSET +
   1381  * acquire/release variants and b/h widths).
   1382  *   size(2) 111 V(1) 00 A(1) R(1) 1 Rs(5) o3(1) opc(3) 00 Rn(5) Rt(5)
   1383  *   31..30  29..27 26 25..24 23  22  21 20..16 15   14..12 11..10 9..5 4..0
   1384  *
   1385  * A=acquire (a-suffix), R=release (l-suffix).  o3=1 selects SWP (opc=000);
   1386  * o3=0 with opc in {000=LDADD,001=LDCLR,010=LDEOR,011=LDSET}. */
   1387 
   1388 #define AA64_LSE_ATOMIC_FAMILY_MATCH 0x38200000u
   1389 /* bits 29:27 (=111), 25:24 (=00), 21 (=1), 11:10 (=00). */
   1390 #define AA64_LSE_ATOMIC_FAMILY_MASK 0x3B200C00u
   1391 
   1392 #define AA64_LSE_OPC_LDADD 0u
   1393 #define AA64_LSE_OPC_LDCLR 1u
   1394 #define AA64_LSE_OPC_LDEOR 2u
   1395 #define AA64_LSE_OPC_LDSET 3u
   1396 #define AA64_LSE_OPC_SWP 0u /* paired with o3=1 */
   1397 
   1398 typedef struct AA64LseAtomic {
   1399   u32 size, A, R, Rs, o3, opc, Rn, Rt;
   1400 } AA64LseAtomic;
   1401 
   1402 static inline u32 aa64_lse_atomic_pack(AA64LseAtomic f) {
   1403   return ((f.size & 3u) << 30) | AA64_LSE_ATOMIC_FAMILY_MATCH |
   1404          ((f.A & 1u) << 23) | ((f.R & 1u) << 22) | ((f.Rs & 0x1fu) << 16) |
   1405          ((f.o3 & 1u) << 15) | ((f.opc & 7u) << 12) | ((f.Rn & 0x1fu) << 5) |
   1406          (f.Rt & 0x1fu);
   1407 }
   1408 
   1409 static inline AA64LseAtomic aa64_lse_atomic_unpack(u32 w) {
   1410   AA64LseAtomic f;
   1411   f.size = (w >> 30) & 3u;
   1412   f.A = (w >> 23) & 1u;
   1413   f.R = (w >> 22) & 1u;
   1414   f.Rs = (w >> 16) & 0x1fu;
   1415   f.o3 = (w >> 15) & 1u;
   1416   f.opc = (w >> 12) & 7u;
   1417   f.Rn = (w >> 5) & 0x1fu;
   1418   f.Rt = w & 0x1fu;
   1419   return f;
   1420 }
   1421 
   1422 /* ====================================================================
   1423  * Unconditional branch (immediate) — B / BL
   1424  *   op(1) 00101 imm26(26)
   1425  *   31    30..26 25..0
   1426  *
   1427  * op=0 → B, op=1 → BL.  imm26 is a signed 26-bit word displacement
   1428  * (multiply by 4 to get byte offset).  Codegen emits with imm26=0 paired
   1429  * with a JUMP26 / CALL26 relocation. */
   1430 
   1431 #define AA64_BR_IMM_FAMILY_MATCH 0x14000000u
   1432 #define AA64_BR_IMM_FAMILY_MASK 0x7C000000u /* bits 30:26 (=00101) */
   1433 
   1434 typedef struct AA64BrImm {
   1435   u32 op, imm26;
   1436 } AA64BrImm;
   1437 
   1438 static inline u32 aa64_brimm_pack(AA64BrImm f) {
   1439   return ((f.op & 1u) << 31) | AA64_BR_IMM_FAMILY_MATCH |
   1440          (f.imm26 & 0x3ffffffu);
   1441 }
   1442 
   1443 static inline AA64BrImm aa64_brimm_unpack(u32 w) {
   1444   AA64BrImm f;
   1445   f.op = (w >> 31) & 1u;
   1446   f.imm26 = w & 0x3ffffffu;
   1447   return f;
   1448 }
   1449 
   1450 static inline u32 aa64_b(u32 imm26) {
   1451   return aa64_brimm_pack((AA64BrImm){.op = 0, .imm26 = imm26});
   1452 }
   1453 static inline u32 aa64_bl(u32 imm26) {
   1454   return aa64_brimm_pack((AA64BrImm){.op = 1, .imm26 = imm26});
   1455 }
   1456 
   1457 /* ====================================================================
   1458  * Conditional branch (immediate) — B.cond
   1459  *   0101 0100 imm19(19) 0 cond(4)
   1460  *   31..24    23..5     4 3..0
   1461  *
   1462  * imm19 is a signed 19-bit word displacement; cond is the 4-bit ARM
   1463  * condition code (EQ=0, NE=1, ...). */
   1464 
   1465 #define AA64_BR_COND_FAMILY_MATCH 0x54000000u
   1466 #define AA64_BR_COND_FAMILY_MASK              \
   1467   0xFF000010u /* bits 31:24 fixed + bit 4 = 0 \
   1468                */
   1469 
   1470 typedef struct AA64BrCond {
   1471   u32 imm19, cond;
   1472 } AA64BrCond;
   1473 
   1474 static inline u32 aa64_brcond_pack(AA64BrCond f) {
   1475   return AA64_BR_COND_FAMILY_MATCH | ((f.imm19 & 0x7ffffu) << 5) |
   1476          (f.cond & 0xfu);
   1477 }
   1478 
   1479 static inline AA64BrCond aa64_brcond_unpack(u32 w) {
   1480   AA64BrCond f;
   1481   f.imm19 = (w >> 5) & 0x7ffffu;
   1482   f.cond = w & 0xfu;
   1483   return f;
   1484 }
   1485 
   1486 /* ====================================================================
   1487  * Compare-and-branch — CBZ / CBNZ
   1488  *   sf 011010 op(1) imm19(19) Rt(5)
   1489  *   31 30..25 24    23..5     4..0
   1490  *
   1491  * op=0 → CBZ (branch if zero), op=1 → CBNZ. */
   1492 
   1493 #define AA64_CB_FAMILY_MATCH 0x34000000u
   1494 #define AA64_CB_FAMILY_MASK 0x7E000000u /* bits 30:25 (=011010) */
   1495 
   1496 typedef struct AA64CB {
   1497   u32 sf, op, imm19, Rt;
   1498 } AA64CB;
   1499 
   1500 static inline u32 aa64_cb_pack(AA64CB f) {
   1501   return ((f.sf & 1u) << 31) | AA64_CB_FAMILY_MATCH | ((f.op & 1u) << 24) |
   1502          ((f.imm19 & 0x7ffffu) << 5) | (f.Rt & 0x1fu);
   1503 }
   1504 
   1505 static inline AA64CB aa64_cb_unpack(u32 w) {
   1506   AA64CB f;
   1507   f.sf = (w >> 31) & 1u;
   1508   f.op = (w >> 24) & 1u;
   1509   f.imm19 = (w >> 5) & 0x7ffffu;
   1510   f.Rt = w & 0x1fu;
   1511   return f;
   1512 }
   1513 
   1514 static inline u32 aa64_cbz(u32 sf, u32 Rt, u32 imm19) {
   1515   return aa64_cb_pack((AA64CB){.sf = sf, .op = 0, .imm19 = imm19, .Rt = Rt});
   1516 }
   1517 static inline u32 aa64_cbnz_imm(u32 sf, u32 Rt, u32 imm19) {
   1518   return aa64_cb_pack((AA64CB){.sf = sf, .op = 1, .imm19 = imm19, .Rt = Rt});
   1519 }
   1520 
   1521 /* ====================================================================
   1522  * Exception generation — BRK / SVC / HVC / SMC / HLT / UDF aliases.
   1523  *   1101 0100 opc(3) imm16(16) op2(3) LL(2)
   1524  *   31..24    23..21 20..5     4..2   1..0
   1525  *
   1526  * SVC: opc=000, LL=01.  BRK: opc=001, LL=00.  HVC/SMC/HLT/...: other
   1527  * combos.  Codegen emits BRK today. */
   1528 
   1529 #define AA64_EXCEPT_FAMILY_MATCH 0xD4000000u
   1530 #define AA64_EXCEPT_FAMILY_MASK 0xFF000000u /* bits 31:24 */
   1531 
   1532 typedef struct AA64Except {
   1533   u32 opc, imm16, op2, LL;
   1534 } AA64Except;
   1535 
   1536 static inline u32 aa64_except_pack(AA64Except f) {
   1537   return AA64_EXCEPT_FAMILY_MATCH | ((f.opc & 7u) << 21) |
   1538          ((f.imm16 & 0xffffu) << 5) | ((f.op2 & 7u) << 2) | (f.LL & 3u);
   1539 }
   1540 
   1541 static inline AA64Except aa64_except_unpack(u32 w) {
   1542   AA64Except f;
   1543   f.opc = (w >> 21) & 7u;
   1544   f.imm16 = (w >> 5) & 0xffffu;
   1545   f.op2 = (w >> 2) & 7u;
   1546   f.LL = w & 3u;
   1547   return f;
   1548 }
   1549 
   1550 static inline u32 aa64_brk(u32 imm16) {
   1551   return aa64_except_pack(
   1552       (AA64Except){.opc = 1, .imm16 = imm16, .op2 = 0, .LL = 0});
   1553 }
   1554 static inline u32 aa64_svc(u32 imm16) {
   1555   return aa64_except_pack(
   1556       (AA64Except){.opc = 0, .imm16 = imm16, .op2 = 0, .LL = 1});
   1557 }
   1558 
   1559 /* ====================================================================
   1560  * Disassembler descriptor table.
   1561  * ==================================================================== */
   1562 
   1563 typedef struct AA64InsnDesc {
   1564   Slice mnemonic;
   1565   u32 match;
   1566   u32 mask;
   1567   u8 fmt;   /* AA64Format */
   1568   u8 flags; /* AA64_ASMFL_* */
   1569   u8 pad[2];
   1570 } AA64InsnDesc;
   1571 
   1572 extern const AA64InsnDesc aa64_insn_table[];
   1573 extern const u32 aa64_insn_table_n;
   1574 
   1575 /* Linear-scan lookup. Returns the matching descriptor or NULL. First
   1576  * match wins; ordering in aa64_insn_table.c puts more-specific entries
   1577  * before broader ones (so aliases like MOV/MUL/NEG win over their
   1578  * canonical ORR/MADD/SUB forms). */
   1579 const AA64InsnDesc* aa64_disasm_find(u32 word);
   1580 
   1581 /* ====================================================================
   1582  * Operand print — one entry per AA64Format.
   1583  *
   1584  * aa64_print_operands renders the operand text (everything after the
   1585  * mnemonic) for `word` into `sb`, using `desc->fmt` to dispatch.
   1586  * Mnemonic itself is in `desc->mnemonic`; the caller writes it before
   1587  * calling this helper.  `vaddr` is the instruction's virtual address
   1588  * for PC-relative formats; pass 0 if not known. */
   1589 
   1590 void aa64_print_operands(StrBuf* sb, const AA64InsnDesc* desc, u32 word,
   1591                          u64 vaddr);
   1592 
   1593 /* If `word` is an SBFM/UBFM that has a preferred shift-alias disassembly,
   1594  * return its mnemonic ("lsl"/"lsr"/"asr") and write the shift amount to
   1595  * *shift; return NULL otherwise. Shared by the disassembler's mnemonic and
   1596  * operand printers so the alias decision lives in one place. */
   1597 const char* aa64_bitfield_shift_alias(u32 word, u32* shift);
   1598 
   1599 /* Preferred SBFM/UBFM extension aliases: sxtb/sxth/sxtw/uxtb/uxth.
   1600  * Returns NULL when the bitfield does not match one of those forms. */
   1601 const char* aa64_bitfield_extend_alias(u32 word);
   1602 
   1603 /* Preferred SBFM/UBFM extract aliases: sbfx/ubfx. Writes the least-significant
   1604  * source bit and extracted width when an alias is available. */
   1605 const char* aa64_bitfield_extract_alias(u32 word, u32* lsb, u32* width);
   1606 
   1607 #endif