kit

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

asm.c (108883B)


      1 /* AArch64 standalone .s instruction parser.
      2  *
      3  * Per-mnemonic dispatch: each entry in the mnemonic table names a
      4  * parse function that reads operand tokens through the asm-driver
      5  * surface and emits the encoded word via the inline encoders in
      6  * aa64_isa.h.  Encoders are the single source of truth for bit
      7  * layout — the disassembler shares them through aa64_*_unpack.
      8  *
      9  * Aliases (`mov`, `neg`, `cmp`, `mul`, ...) live in this table as
     10  * dedicated rows that pick the canonical form's encoder with the
     11  * alias-specific operand shape.  When a mnemonic admits multiple
     12  * forms (e.g. `mov` register-vs-immediate, `add` register-vs-
     13  * immediate), the parser branches on operand shape after reading
     14  * the first non-Rd operand. */
     15 
     16 #include "arch/aa64/asm.h"
     17 
     18 #include <string.h>
     19 
     20 #include "arch/aa64/isa.h"
     21 #include "arch/aa64/regs.h"
     22 #include "arch/arch.h"
     23 #include "asm/asm_helpers.h"
     24 #include "asm/asm_lex.h"
     25 #include "cg/type.h"
     26 #include "core/arena.h"
     27 #include "core/pool.h"
     28 #include "core/slice.h"
     29 #include "core/strbuf.h"
     30 #include "obj/obj.h"
     31 
     32 /* ---- public handle ---- */
     33 
     34 struct AA64Asm {
     35   ArchAsm base;
     36   Compiler* c;
     37 
     38   /* Inline-asm bound state (set by aa64_inline_bind, cleared otherwise).
     39    * Operand indexing per GCC convention: 0..nout-1 are outputs, then
     40    * nout..nout+nin-1 are inputs.  Templates address into this combined
     41    * list via %N / %wN / %xN / %aN.  out_ops is mutable (the binder fills
     42    * in result locations); in_ops + constraints + clobbers are read-only
     43    * borrows. */
     44   const AsmConstraint* outs;
     45   Operand* out_ops;
     46   const AsmConstraint* ins;
     47   const Operand* in_ops;
     48   const Sym* clobbers;
     49   u32 nout;
     50   u32 nin;
     51   u32 nclob;
     52 };
     53 
     54 static void aa64_arch_asm_insn(ArchAsm* base, AsmDriver* d, Sym mnemonic);
     55 static void aa64_arch_asm_destroy(ArchAsm* base);
     56 
     57 AA64Asm* aa64_asm_open(Compiler* c) {
     58   AA64Asm* a = arena_new(c->tu, AA64Asm);
     59   memset(a, 0, sizeof *a);
     60   a->base.insn = aa64_arch_asm_insn;
     61   a->base.destroy = aa64_arch_asm_destroy;
     62   a->c = c;
     63   return a;
     64 }
     65 
     66 void aa64_asm_close(AA64Asm* a) { (void)a; }
     67 
     68 ArchAsm* aa64_arch_asm_new(Compiler* c) { return &aa64_asm_open(c)->base; }
     69 
     70 static void aa64_arch_asm_insn(ArchAsm* base, AsmDriver* d, Sym mnemonic) {
     71   aa64_asm_insn((AA64Asm*)base, d, mnemonic);
     72 }
     73 
     74 static void aa64_arch_asm_destroy(ArchAsm* base) {
     75   aa64_asm_close((AA64Asm*)base);
     76 }
     77 
     78 void aa64_inline_bind(AA64Asm* a, const AsmConstraint* outs, u32 nout,
     79                       Operand* out_ops, const AsmConstraint* ins, u32 nin,
     80                       const Operand* in_ops, const Sym* clobbers, u32 nclob) {
     81   a->outs = outs;
     82   a->out_ops = out_ops;
     83   a->ins = ins;
     84   a->in_ops = in_ops;
     85   a->clobbers = clobbers;
     86   a->nout = nout;
     87   a->nin = nin;
     88   a->nclob = nclob;
     89 }
     90 
     91 /* ---- helpers ---- */
     92 
     93 static int tok_punct(AsmTok t, u32 p) { return asm_driver_tok_is_punct(t, p); }
     94 
     95 static int icase_eq(const char* a, size_t an, const char* b) {
     96   size_t i;
     97   for (i = 0; i < an; ++i) {
     98     char x = a[i], y = b[i];
     99     if (x >= 'A' && x <= 'Z') x = (char)(x + ('a' - 'A'));
    100     if (y >= 'A' && y <= 'Z') y = (char)(y + ('a' - 'A'));
    101     if (x != y || !y) return 0;
    102   }
    103   return b[an] == '\0';
    104 }
    105 
    106 /* Parse a register operand.  Returns the 5-bit encoded register number
    107  * via *reg_out and the form via *is64_out.  Recognized forms (case-
    108  * insensitive):
    109  *   w0..w30, wzr            → is64=0, reg=0..30 / 31
    110  *   x0..x30, xzr, lr (=x30) → is64=1, reg=0..30 / 31
    111  *   sp                      → is64=1, reg=31  (sp_means_sp set)
    112  *   wsp                     → is64=0, reg=31  (sp_means_sp set)
    113  * Aliases:
    114  *   fp = x29
    115  *   ip0 = x16, ip1 = x17  (PLT scratch — useful for hand-written PLTs) */
    116 typedef struct AA64Reg {
    117   u32 num;
    118   u8 is64;
    119   u8 is_sp;    /* 1 if the spelling was "sp" / "wsp" */
    120   u8 is_fp;    /* 1 for SIMD/FP register spellings accepted in FP forms */
    121   u8 fp_bytes; /* 8 for Dn, 16 for Qn */
    122 } AA64Reg;
    123 
    124 static int parse_reg_from_ident(AsmDriver* d, Sym ident, AA64Reg* out) {
    125   Slice sl = pool_slice(asm_driver_pool(d), ident);
    126   const char* p = sl.s;
    127   size_t n = sl.len;
    128   if (!p || !n) return 0;
    129   /* "sp" */
    130   if (icase_eq(p, n, "sp")) {
    131     out->num = 31;
    132     out->is64 = 1;
    133     out->is_sp = 1;
    134     out->is_fp = 0;
    135     return 1;
    136   }
    137   if (icase_eq(p, n, "wsp")) {
    138     out->num = 31;
    139     out->is64 = 0;
    140     out->is_sp = 1;
    141     out->is_fp = 0;
    142     return 1;
    143   }
    144   if (icase_eq(p, n, "lr")) {
    145     out->num = 30;
    146     out->is64 = 1;
    147     out->is_sp = 0;
    148     out->is_fp = 0;
    149     return 1;
    150   }
    151   if (icase_eq(p, n, "fp")) {
    152     out->num = 29;
    153     out->is64 = 1;
    154     out->is_sp = 0;
    155     out->is_fp = 0;
    156     return 1;
    157   }
    158   if (icase_eq(p, n, "ip0")) {
    159     out->num = 16;
    160     out->is64 = 1;
    161     out->is_sp = 0;
    162     out->is_fp = 0;
    163     return 1;
    164   }
    165   if (icase_eq(p, n, "ip1")) {
    166     out->num = 17;
    167     out->is64 = 1;
    168     out->is_sp = 0;
    169     out->is_fp = 0;
    170     return 1;
    171   }
    172   if (icase_eq(p, n, "xzr")) {
    173     out->num = 31;
    174     out->is64 = 1;
    175     out->is_sp = 0;
    176     out->is_fp = 0;
    177     return 1;
    178   }
    179   if (icase_eq(p, n, "wzr")) {
    180     out->num = 31;
    181     out->is64 = 0;
    182     out->is_sp = 0;
    183     out->is_fp = 0;
    184     return 1;
    185   }
    186   /* W/X<num> */
    187   if ((p[0] == 'w' || p[0] == 'W' || p[0] == 'x' || p[0] == 'X') && n >= 2) {
    188     u32 r = 0;
    189     size_t i;
    190     for (i = 1; i < n; ++i) {
    191       char c = p[i];
    192       if (c < '0' || c > '9') return 0;
    193       r = r * 10 + (u32)(c - '0');
    194       if (r > 31) return 0;
    195     }
    196     out->num = r;
    197     out->is64 = (p[0] == 'x' || p[0] == 'X') ? 1 : 0;
    198     out->is_sp = 0;
    199     out->is_fp = 0;
    200     return 1;
    201   }
    202   return 0;
    203 }
    204 
    205 static int parse_fp_pair_reg_from_ident(AsmDriver* d, Sym ident, AA64Reg* out) {
    206   Slice sl = pool_slice(asm_driver_pool(d), ident);
    207   const char* p = sl.s;
    208   size_t n = sl.len;
    209   if (!p || n < 2 || (p[0] != 'd' && p[0] != 'D' && p[0] != 'q' && p[0] != 'Q'))
    210     return 0;
    211   u32 r = 0;
    212   for (size_t i = 1; i < n; ++i) {
    213     char c = p[i];
    214     if (c < '0' || c > '9') return 0;
    215     r = r * 10 + (u32)(c - '0');
    216     if (r > 31) return 0;
    217   }
    218   out->num = r;
    219   out->is64 = 1;
    220   out->is_sp = 0;
    221   out->is_fp = 1;
    222   out->fp_bytes = (p[0] == 'q' || p[0] == 'Q') ? 16u : 8u;
    223   return 1;
    224 }
    225 
    226 /* Scalar SIMD/FP transfer register for ldr/str/ldur/stur: b/h/s/d/q with the
    227  * access width in fp_bytes (1/2/4/8/16). Unlike parse_fp_pair_reg_from_ident
    228  * (ldp/stp, d/q only) this accepts the sub-64-bit scalar widths a single-reg
    229  * FP load/store can carry. */
    230 static int parse_fp_scalar_reg_from_ident(AsmDriver* d, Sym ident,
    231                                           AA64Reg* out) {
    232   Slice sl = pool_slice(asm_driver_pool(d), ident);
    233   const char* p = sl.s;
    234   size_t n = sl.len;
    235   u8 bytes;
    236   u32 r = 0;
    237   size_t i;
    238   if (!p || n < 2) return 0;
    239   switch (p[0]) {
    240     case 'b':
    241     case 'B':
    242       bytes = 1;
    243       break;
    244     case 'h':
    245     case 'H':
    246       bytes = 2;
    247       break;
    248     case 's':
    249     case 'S':
    250       bytes = 4;
    251       break;
    252     case 'd':
    253     case 'D':
    254       bytes = 8;
    255       break;
    256     case 'q':
    257     case 'Q':
    258       bytes = 16;
    259       break;
    260     default:
    261       return 0;
    262   }
    263   for (i = 1; i < n; ++i) {
    264     char c = p[i];
    265     if (c < '0' || c > '9') return 0;
    266     r = r * 10 + (u32)(c - '0');
    267     if (r > 31) return 0;
    268   }
    269   out->num = r;
    270   out->is64 = 1;
    271   out->is_sp = 0;
    272   out->is_fp = 1;
    273   out->fp_bytes = bytes;
    274   return 1;
    275 }
    276 
    277 static AA64Reg parse_reg(AsmDriver* d) {
    278   AsmTok t = asm_driver_next(d);
    279   AA64Reg r;
    280   memset(&r, 0, sizeof r);
    281   if (t.kind != ASM_TOK_IDENT || !parse_reg_from_ident(d, t.v.ident, &r))
    282     asm_driver_panic(d, "asm: expected register");
    283   return r;
    284 }
    285 
    286 /* Non-consuming lookahead: is the next operand a register? Used to pick
    287  * between the register and immediate forms of dual-form mnemonics (e.g.
    288  * `and Rd,Rn,Rm` vs `and Rd,Rn,#imm`). */
    289 static int peek_is_reg(AsmDriver* d) {
    290   AsmTok t = asm_driver_peek(d);
    291   AA64Reg r;
    292   return t.kind == ASM_TOK_IDENT && parse_reg_from_ident(d, t.v.ident, &r);
    293 }
    294 
    295 /* ldr/str transfer register: GPR (Wt/Xt) or scalar SIMD/FP (Bt..Qt). */
    296 static AA64Reg parse_ldst_reg(AsmDriver* d) {
    297   AsmTok t = asm_driver_next(d);
    298   AA64Reg r;
    299   memset(&r, 0, sizeof r);
    300   if (t.kind != ASM_TOK_IDENT ||
    301       (!parse_reg_from_ident(d, t.v.ident, &r) &&
    302        !parse_fp_scalar_reg_from_ident(d, t.v.ident, &r)))
    303     asm_driver_panic(d, "asm: ldr/str: expected register");
    304   return r;
    305 }
    306 
    307 /* Resolve the (size, V, opc, scale) load/store encoding fields from the
    308  * transfer register and mnemonic flavor. GPR width comes from fixed_size (the
    309  * sized mnemonics ldrb/ldrsw/…) or the register; FP uses V=1 with size/opc
    310  * keyed on the scalar width (b/h/s/d = size 0/1/2/3, opc store=0/load=1; the
    311  * 128-bit q is size=0 opc=2/3). `scale` is the byte access width for the
    312  * scaled unsigned-imm12 form. */
    313 typedef struct {
    314   u32 size, V, opc, scale;
    315 } AA64LdStEnc;
    316 
    317 static AA64LdStEnc ldst_encoding(AsmDriver* d, AA64Reg rt, int is_load,
    318                                  int fixed_size, int sign_ext) {
    319   AA64LdStEnc e;
    320   if (rt.is_fp) {
    321     if (fixed_size >= 0 || sign_ext)
    322       asm_driver_panic(d, "asm: sized/signed ld/st takes a GPR, not an FP reg");
    323     e.V = 1;
    324     e.scale = rt.fp_bytes;
    325     e.size = (rt.fp_bytes == 1)   ? 0u
    326              : (rt.fp_bytes == 2) ? 1u
    327              : (rt.fp_bytes == 4) ? 2u
    328              : (rt.fp_bytes == 8) ? 3u
    329                                   : 0u; /* 16 (Q): size=0, opc carries width */
    330     e.opc = (rt.fp_bytes == 16) ? (is_load ? 3u : 2u) : (is_load ? 1u : 0u);
    331   } else {
    332     e.V = 0;
    333     e.size = (fixed_size >= 0) ? (u32)fixed_size : (rt.is64 ? 3u : 2u);
    334     e.scale = 1u << e.size;
    335     e.opc = !is_load    ? AA64_LDST_OPC_STR
    336             : !sign_ext ? AA64_LDST_OPC_LDR
    337             : rt.is64   ? 2u  /* LDRS*, 64-bit dst */
    338                         : 3u; /* LDRS*, 32-bit dst */
    339   }
    340   return e;
    341 }
    342 
    343 static AA64Reg parse_ldstp_reg(AsmDriver* d) {
    344   AsmTok t = asm_driver_next(d);
    345   AA64Reg r;
    346   memset(&r, 0, sizeof r);
    347   if (t.kind != ASM_TOK_IDENT ||
    348       (!parse_reg_from_ident(d, t.v.ident, &r) &&
    349        !parse_fp_pair_reg_from_ident(d, t.v.ident, &r))) {
    350     asm_driver_panic(d, "asm: expected register");
    351   }
    352   return r;
    353 }
    354 
    355 static void reject_sp_reg(AsmDriver* d, AA64Reg r, const char* what) {
    356   if (r.is_sp)
    357     asm_driver_panic(d, "asm: %.*s: SP register not allowed",
    358                      SLICE_ARG(slice_from_cstr(what)));
    359 }
    360 
    361 static void require_sp_spelling(AsmDriver* d, AA64Reg r, const char* what) {
    362   if (r.num == 31u && !r.is_sp)
    363     asm_driver_panic(d, "asm: %.*s: zero register not allowed in SP operand",
    364                      SLICE_ARG(slice_from_cstr(what)));
    365 }
    366 
    367 /* Parse "#imm" (with optional + / -) or a bare expression — GNU as is
    368  * lenient about the leading hash.  Returns an i64. */
    369 static i64 parse_imm_const(AsmDriver* d) {
    370   (void)asm_driver_eat_punct(d, '#');
    371   return asm_driver_parse_const(d);
    372 }
    373 
    374 /* Parse a possibly-symbolic operand prefixed by '#'. */
    375 static void parse_imm_sym(AsmDriver* d, ObjSymId* sym_out, i64* val_out) {
    376   (void)asm_driver_eat_punct(d, '#');
    377   asm_driver_parse_sym_expr(d, sym_out, val_out);
    378 }
    379 
    380 /* GNU-as relocation modifier on an aarch64 operand (`:lo12:`, `:got:`,
    381  * `:got_lo12:`).  AA64_RELMOD_NONE means no modifier was present. */
    382 typedef enum AA64RelMod {
    383   AA64_RELMOD_NONE = 0,
    384   AA64_RELMOD_PAGE, /* explicit adrp page reloc (Mach-O `@PAGE`); == bare adrp
    385                      */
    386   AA64_RELMOD_LO12,
    387   AA64_RELMOD_GOT,
    388   AA64_RELMOD_GOT_LO12,
    389 } AA64RelMod;
    390 
    391 /* True when the assembler's target object format is Mach-O, which spells
    392  * operand relocations as `@PAGE`/`@PAGEOFF` suffixes; ELF/COFF spell them as
    393  * `:lo12:`/`:got:` prefixes.  kit as parses the dialect of its target only
    394  * (no hybrid), mirroring what `cc -S` emits for that format. */
    395 static int target_is_macho(AsmDriver* d) {
    396   return asm_driver_compiler(d)->target.obj == KIT_OBJ_MACHO;
    397 }
    398 
    399 /* If the next token is ':', consume a `:name:` relocation modifier prefix and
    400  * return its kind.  A leading ':' is unambiguous at an operand position (a
    401  * label's ':' only appears at end-of-mnemonic).  Returns AA64_RELMOD_NONE and
    402  * leaves the stream untouched when there is no modifier. */
    403 static AA64RelMod parse_reloc_mod(AsmDriver* d) {
    404   if (!tok_punct(asm_driver_peek(d), ':')) return AA64_RELMOD_NONE;
    405   (void)asm_driver_next(d); /* eat ':' */
    406   AsmTok name = asm_driver_next(d);
    407   if (name.kind != ASM_TOK_IDENT)
    408     asm_driver_panic(d, "asm: expected relocation modifier name after ':'");
    409   Slice s = pool_slice(asm_driver_pool(d), name.v.ident);
    410   AA64RelMod mod;
    411   if (icase_eq(s.s, s.len, "lo12"))
    412     mod = AA64_RELMOD_LO12;
    413   else if (icase_eq(s.s, s.len, "got"))
    414     mod = AA64_RELMOD_GOT;
    415   else if (icase_eq(s.s, s.len, "got_lo12"))
    416     mod = AA64_RELMOD_GOT_LO12;
    417   else
    418     asm_driver_panic(d, "asm: unsupported relocation modifier");
    419   asm_driver_expect_punct(d, ':', "':' closing relocation modifier");
    420   return mod;
    421 }
    422 
    423 /* Mach-O operand relocation suffix: after a symbol(+addend), an optional
    424  * `@PAGE` / `@PAGEOFF` / `@GOTPAGE` / `@GOTPAGEOFF`.  Maps to the same
    425  * AA64RelMod the ELF `:mod:` prefix produces, so downstream encoding/reloc
    426  * emission is shared.  `@PAGE` is the explicit spelling of an adrp page reloc
    427  * (a bare adrp on ELF).  Returns AA64_RELMOD_NONE, stream untouched, when the
    428  * next token is not '@'. */
    429 static AA64RelMod parse_reloc_suffix(AsmDriver* d) {
    430   if (!tok_punct(asm_driver_peek(d), '@')) return AA64_RELMOD_NONE;
    431   (void)asm_driver_next(d); /* eat '@' */
    432   AsmTok name = asm_driver_next(d);
    433   if (name.kind != ASM_TOK_IDENT)
    434     asm_driver_panic(d, "asm: expected relocation suffix name after '@'");
    435   Slice s = pool_slice(asm_driver_pool(d), name.v.ident);
    436   if (icase_eq(s.s, s.len, "PAGE")) return AA64_RELMOD_PAGE;
    437   if (icase_eq(s.s, s.len, "PAGEOFF")) return AA64_RELMOD_LO12;
    438   if (icase_eq(s.s, s.len, "GOTPAGE")) return AA64_RELMOD_GOT;
    439   if (icase_eq(s.s, s.len, "GOTPAGEOFF")) return AA64_RELMOD_GOT_LO12;
    440   asm_driver_panic(d, "asm: unsupported relocation suffix");
    441 }
    442 
    443 /* The R_AARCH64_LDST{8,16,32,64}_ABS_LO12_NC reloc for an access log2-size. */
    444 static RelocKind aa64_ldst_lo12_reloc(AsmDriver* d, u32 size) {
    445   switch (size) {
    446     case 0:
    447       return R_AARCH64_LDST8_ABS_LO12_NC;
    448     case 1:
    449       return R_AARCH64_LDST16_ABS_LO12_NC;
    450     case 2:
    451       return R_AARCH64_LDST32_ABS_LO12_NC;
    452     case 3:
    453       return R_AARCH64_LDST64_ABS_LO12_NC;
    454     default:
    455       asm_driver_panic(d,
    456                        "asm: ldr/str: :lo12: not valid for this access size");
    457   }
    458 }
    459 
    460 /* Printer-side inverse of the operand reloc-modifier parsers above: how a
    461  * relocated aarch64 operand is spelled in `cc -S` text for the target object
    462  * format.  ELF uses a `:mod:` prefix; Mach-O uses an `@MOD` suffix — and even
    463  * a bare adrp page reloc needs an explicit `@PAGE` there.  Kept adjacent to
    464  * the `.s` parser (parse_reloc_mod / parse_reloc_suffix and their call sites)
    465  * so the emit and parse spellings stay in lockstep.  See ArchAsmOps. */
    466 static int aa64_reloc_operand(u16 kind, KitObjFmt fmt, ArchRelocOperand* out) {
    467   ArchRelocSurg surg;
    468   const char* elf;   /* `:mod:` prefix */
    469   const char* macho; /* `@MOD` suffix */
    470   switch (kind) {
    471     case R_AARCH64_CALL26:
    472     case R_AARCH64_JUMP26:
    473     case R_AARCH64_CONDBR19:
    474     case R_AARCH64_ADR_PREL_LO21:
    475       surg = ARCH_RELOC_SURG_TAIL, elf = "", macho = "";
    476       break;
    477     case R_AARCH64_ADR_PREL_PG_HI21:
    478       surg = ARCH_RELOC_SURG_TAIL, elf = "", macho = "@PAGE";
    479       break;
    480     case R_AARCH64_ADR_GOT_PAGE:
    481       surg = ARCH_RELOC_SURG_TAIL, elf = ":got:", macho = "@GOTPAGE";
    482       break;
    483     case R_AARCH64_ADD_ABS_LO12_NC:
    484       surg = ARCH_RELOC_SURG_TAIL, elf = ":lo12:", macho = "@PAGEOFF";
    485       break;
    486     case R_AARCH64_LDST8_ABS_LO12_NC:
    487     case R_AARCH64_LDST16_ABS_LO12_NC:
    488     case R_AARCH64_LDST32_ABS_LO12_NC:
    489     case R_AARCH64_LDST64_ABS_LO12_NC:
    490       surg = ARCH_RELOC_SURG_MEM, elf = ":lo12:", macho = "@PAGEOFF";
    491       break;
    492     case R_AARCH64_LD64_GOT_LO12_NC:
    493       surg = ARCH_RELOC_SURG_MEM, elf = ":got_lo12:", macho = "@GOTPAGEOFF";
    494       break;
    495     default:
    496       return 0; /* TLV and anything else: keep the numeric operand */
    497   }
    498   out->surg = surg;
    499   out->addend_bias = 0; /* aarch64 relocs store the symbol offset directly */
    500   if (fmt == KIT_OBJ_MACHO) {
    501     out->prefix = "";
    502     out->suffix = macho;
    503   } else {
    504     out->prefix = elf;
    505     out->suffix = "";
    506   }
    507   return 1;
    508 }
    509 
    510 /* Intra-section local branches whose target codegen resolved in place (no
    511  * relocation): b, b.<cc>, cbz/cbnz, tbz/tbnz, and adr (address-of-label, e.g.
    512  * `&&label`). Excludes bl (a call — always relocated), adrp (page-relative; its
    513  * lo12 partner carries the reloc), and register-form branches. Moved here from
    514  * the printer so branch-mnemonic knowledge is arch-local. */
    515 static int aa64_is_local_branch(KitSlice m) {
    516   if (m.len == 1 && m.s[0] == 'b') return 1;
    517   if (m.len >= 2 && m.s[0] == 'b' && m.s[1] == '.') return 1;
    518   if (m.len == 3 && memcmp(m.s, "cbz", 3) == 0) return 1;
    519   if (m.len == 4 && memcmp(m.s, "cbnz", 4) == 0) return 1;
    520   if (m.len == 3 && memcmp(m.s, "tbz", 3) == 0) return 1;
    521   if (m.len == 4 && memcmp(m.s, "tbnz", 4) == 0) return 1;
    522   return 0;
    523 }
    524 
    525 const ArchAsmOps aa64_asm_ops = {
    526     .reloc_operand = aa64_reloc_operand,
    527     .is_local_branch = aa64_is_local_branch,
    528 };
    529 
    530 static void emit32(AsmDriver* d, u32 word) {
    531   MCEmitter* mc = asm_driver_mc(d);
    532   (void)asm_driver_cur_section(d);
    533   u8 buf[4];
    534   buf[0] = (u8)(word & 0xff);
    535   buf[1] = (u8)((word >> 8) & 0xff);
    536   buf[2] = (u8)((word >> 16) & 0xff);
    537   buf[3] = (u8)((word >> 24) & 0xff);
    538   mc_emit_bytes(mc, buf, 4);
    539 }
    540 
    541 static int parse_cond_from_ident(AsmDriver* d, Sym ident, u32* out) {
    542   Slice sl = pool_slice(asm_driver_pool(d), ident);
    543   return aa64_cond_from_name(sl.s, sl.len, out);
    544 }
    545 
    546 static u32 parse_cond(AsmDriver* d, const char* what) {
    547   AsmTok t = asm_driver_next(d);
    548   u32 cond = 0;
    549   if (t.kind != ASM_TOK_IDENT || !parse_cond_from_ident(d, t.v.ident, &cond))
    550     asm_driver_panic(d, "asm: %.*s: expected condition code",
    551                      SLICE_ARG(slice_from_cstr(what)));
    552   return cond;
    553 }
    554 
    555 static void expect_comma(AsmDriver* d, const char* what) {
    556   if (!asm_driver_eat_comma(d))
    557     asm_driver_panic(d, "asm: expected ',' (%.*s)",
    558                      SLICE_ARG(slice_from_cstr(what)));
    559 }
    560 
    561 /* ---- per-mnemonic parsers ---- */
    562 
    563 /* ret [Xn] — Xn defaults to x30. */
    564 static void p_ret(AsmDriver* d) {
    565   if (asm_driver_at_eol(d)) {
    566     emit32(d, aa64_ret(30));
    567     return;
    568   }
    569   AA64Reg r = parse_reg(d);
    570   if (!r.is64) asm_driver_panic(d, "asm: ret: 64-bit register expected");
    571   emit32(d, aa64_ret(r.num));
    572 }
    573 
    574 static void p_br(AsmDriver* d) {
    575   AA64Reg r = parse_reg(d);
    576   if (!r.is64) asm_driver_panic(d, "asm: br: 64-bit register expected");
    577   emit32(d, aa64_br(r.num));
    578 }
    579 
    580 static void p_blr(AsmDriver* d) {
    581   AA64Reg r = parse_reg(d);
    582   if (!r.is64) asm_driver_panic(d, "asm: blr: 64-bit register expected");
    583   emit32(d, aa64_blr(r.num));
    584 }
    585 
    586 static void p_nop(AsmDriver* d) {
    587   (void)d;
    588   emit32(d, aa64_nop());
    589 }
    590 
    591 /* Memory barriers (DMB / DSB / ISB / CLREX).
    592  *
    593  *   dmb <option>        ; option in {sy, ish, nsh, osh, ld, st, ishld,
    594  *                                    ishst, nshld, nshst, oshld, oshst}
    595  *   dmb #imm4           ; numeric form
    596  *   dsb <option> | #imm4
    597  *   isb [<option>]      ; option defaults to sy when omitted
    598  *   clrex [#imm4]       ; option defaults to sy (15) when omitted */
    599 static u32 parse_barrier_option(AsmDriver* d, int allow_dmb_ld_st) {
    600   if (asm_driver_at_eol(d)) return AA64_BARRIER_OPT_SY;
    601   AsmTok t = asm_driver_peek(d);
    602   if (t.kind == ASM_TOK_IDENT) {
    603     (void)asm_driver_next(d);
    604     Slice sl = pool_slice(asm_driver_pool(d), t.v.ident);
    605     const char* s = sl.s;
    606     size_t n = sl.len;
    607     if (icase_eq(s, n, "sy")) return AA64_BARRIER_OPT_SY;
    608     if (icase_eq(s, n, "ish")) return AA64_BARRIER_OPT_ISH;
    609     if (icase_eq(s, n, "ishld")) return AA64_BARRIER_OPT_ISHLD;
    610     if (icase_eq(s, n, "ishst")) return AA64_BARRIER_OPT_ISHST;
    611     if (icase_eq(s, n, "nsh")) return AA64_BARRIER_OPT_NSH;
    612     if (icase_eq(s, n, "nshld")) return AA64_BARRIER_OPT_NSHLD;
    613     if (icase_eq(s, n, "nshst")) return AA64_BARRIER_OPT_NSHST;
    614     if (icase_eq(s, n, "osh")) return AA64_BARRIER_OPT_OSH;
    615     if (icase_eq(s, n, "oshld")) return AA64_BARRIER_OPT_OSHLD;
    616     if (icase_eq(s, n, "oshst")) return AA64_BARRIER_OPT_OSHST;
    617     if (allow_dmb_ld_st) {
    618       if (icase_eq(s, n, "ld")) return AA64_BARRIER_OPT_LD;
    619       if (icase_eq(s, n, "st")) return AA64_BARRIER_OPT_ST;
    620     }
    621     asm_driver_panic(d, "asm: unknown barrier option");
    622   }
    623   /* Numeric form: '#imm4'. */
    624   i64 imm = parse_imm_const(d);
    625   if (imm < 0 || imm > 15) asm_driver_panic(d, "asm: barrier imm out of range");
    626   return (u32)imm;
    627 }
    628 
    629 static void p_dmb(AsmDriver* d) {
    630   u32 opt = parse_barrier_option(d, /*allow_dmb_ld_st=*/1);
    631   emit32(d, aa64_dmb(opt));
    632 }
    633 static void p_dsb(AsmDriver* d) {
    634   u32 opt = parse_barrier_option(d, /*allow_dmb_ld_st=*/0);
    635   emit32(d, aa64_dsb(opt));
    636 }
    637 static void p_isb(AsmDriver* d) {
    638   u32 opt = parse_barrier_option(d, /*allow_dmb_ld_st=*/0);
    639   emit32(d, aa64_isb(opt));
    640 }
    641 static void p_clrex(AsmDriver* d) {
    642   u32 opt = parse_barrier_option(d, /*allow_dmb_ld_st=*/0);
    643   emit32(d, aa64_clrex(opt));
    644 }
    645 
    646 /* System-register access (MRS/MSR register form).
    647  *
    648  *   mrs Xt, <sysreg>     ; read  system register into Xt
    649  *   msr <sysreg>, Xt     ; write Xt into the system register
    650  *
    651  * A system register is named (resolved against the shared isa.c name table
    652  * via aa64_sysreg_by_name) or given by the architectural generic spelling
    653  * S<op0>_<op1>_C<crn>_C<crm>_<op2> (e.g. s3_3_c13_c0_2 == tpidr_el0), which
    654  * covers any encodable register. The immediate PSTATE forms (msr daifset,
    655  * #imm / spsel, #imm) are not handled. */
    656 
    657 /* Consume the next token as a system-register name into the 5 fields. */
    658 static void parse_sysreg(AsmDriver* d, const char* what, u32* op0, u32* op1,
    659                          u32* crn, u32* crm, u32* op2) {
    660   AsmTok t = asm_driver_peek(d);
    661   if (t.kind != ASM_TOK_IDENT)
    662     asm_driver_panic(d, "asm: expected system register");
    663   (void)asm_driver_next(d);
    664   Slice sl = pool_slice(asm_driver_pool(d), t.v.ident);
    665   if (!aa64_sysreg_by_name(sl.s, sl.len, op0, op1, crn, crm, op2))
    666     asm_driver_panic(d, what);
    667 }
    668 
    669 static void p_mrs_(AsmDriver* d) {
    670   AA64Reg rt = parse_reg(d);
    671   if (!rt.is64 || rt.is_sp)
    672     asm_driver_panic(d, "asm: mrs: destination must be a 64-bit GPR");
    673   expect_comma(d, "mrs");
    674   u32 op0, op1, crn, crm, op2;
    675   parse_sysreg(d, "asm: mrs: unknown system register", &op0, &op1, &crn, &crm,
    676                &op2);
    677   emit32(d, aa64_sysreg_move(/*is_read=*/1, op0, op1, crn, crm, op2, rt.num));
    678 }
    679 
    680 static void p_msr_(AsmDriver* d) {
    681   u32 op0, op1, crn, crm, op2;
    682   parse_sysreg(d,
    683                "asm: msr: unknown system register (immediate PSTATE forms "
    684                "like daifset are unsupported)",
    685                &op0, &op1, &crn, &crm, &op2);
    686   expect_comma(d, "msr");
    687   AA64Reg rt = parse_reg(d);
    688   if (!rt.is64 || rt.is_sp)
    689     asm_driver_panic(d, "asm: msr: source must be a 64-bit GPR");
    690   emit32(d, aa64_sysreg_move(/*is_read=*/0, op0, op1, crn, crm, op2, rt.num));
    691 }
    692 
    693 /* mov:
    694  *   mov Rd, Rm        → ORR Rd, ZR, Rm
    695  *   mov Rd, #imm      → MOVZ (if imm fits in a single halfword unshifted)
    696  *                       MOVN (if ~imm fits)
    697  *                       otherwise: panic (multi-step expansion deferred). */
    698 static void p_mov(AsmDriver* d) {
    699   AA64Reg rd = parse_reg(d);
    700   expect_comma(d, "mov");
    701   AsmTok t = asm_driver_peek(d);
    702   if (t.kind == ASM_TOK_IDENT) {
    703     AA64Reg src;
    704     memset(&src, 0, sizeof src);
    705     if (parse_reg_from_ident(d, t.v.ident, &src)) {
    706       (void)asm_driver_next(d);
    707       if (src.is64 != rd.is64)
    708         asm_driver_panic(d, "asm: mov: register width mismatch");
    709       /* mov involving SP encodes as `ADD Rd, Rsp, #0` per AArch64;
    710        * approximate with that exact form. */
    711       if (rd.is_sp || src.is_sp) {
    712         require_sp_spelling(d, rd, "mov sp");
    713         require_sp_spelling(d, src, "mov sp");
    714         emit32(d, aa64_add_imm(rd.is64, rd.num, src.num, 0, 0));
    715         return;
    716       }
    717       emit32(d, aa64_mov_reg(rd.is64, rd.num, src.num));
    718       return;
    719     }
    720     /* fall through: identifier that is not a register → treat as
    721      * symbol/equate via expression below. */
    722   }
    723   /* Immediate. */
    724   i64 imm = parse_imm_const(d);
    725   if (rd.is_sp) asm_driver_panic(d, "asm: mov: cannot move imm into SP");
    726   u64 uv = (u64)imm;
    727   u64 mask = rd.is64 ? ~0ull : 0xffffffffull;
    728   uv &= mask;
    729   /* Try MOVZ with one of four halfwords. */
    730   for (u32 hw = 0; hw < (rd.is64 ? 4u : 2u); ++hw) {
    731     u64 shift = (u64)hw * 16;
    732     u64 hwmask = 0xffffull << shift;
    733     if ((uv & ~hwmask) == 0) {
    734       u32 v = (u32)((uv >> shift) & 0xffff);
    735       emit32(d, aa64_movz(rd.is64, rd.num, v, hw));
    736       return;
    737     }
    738   }
    739   /* Try MOVN with one halfword (encodes ~imm in that halfword). */
    740   u64 nv = (~uv) & mask;
    741   for (u32 hw = 0; hw < (rd.is64 ? 4u : 2u); ++hw) {
    742     u64 shift = (u64)hw * 16;
    743     u64 hwmask = 0xffffull << shift;
    744     if ((nv & ~hwmask) == 0) {
    745       u32 v = (u32)((nv >> shift) & 0xffff);
    746       emit32(d, aa64_movn(rd.is64, rd.num, v, hw));
    747       return;
    748     }
    749   }
    750   /* Try the ORR-bitmask alias (mov Rd,#imm → ORR Rd,ZR,#bitmask). */
    751   {
    752     u32 N = 0, immr = 0, imms = 0;
    753     if (aa64_logimm_encode(uv, rd.is64, &N, &immr, &imms)) {
    754       emit32(d, aa64_orr_imm(rd.is64, rd.num, AA64_ZR, N, immr, imms));
    755       return;
    756     }
    757   }
    758   asm_driver_panic(d, "asm: mov: immediate cannot be encoded in one insn");
    759 }
    760 
    761 /* mvn Rd, Rm */
    762 static void p_mvn(AsmDriver* d) {
    763   AA64Reg rd = parse_reg(d);
    764   expect_comma(d, "mvn");
    765   AA64Reg rm = parse_reg(d);
    766   if (rd.is64 != rm.is64) asm_driver_panic(d, "asm: mvn: width mismatch");
    767   emit32(d, aa64_mvn(rd.is64, rd.num, rm.num));
    768 }
    769 
    770 /* movz / movn / movk Rd, #imm[, lsl #shift] */
    771 static void p_movwide(AsmDriver* d, u32 opc) {
    772   AA64Reg rd = parse_reg(d);
    773   expect_comma(d, "movz/n/k");
    774   i64 imm = parse_imm_const(d);
    775   u32 hw = 0;
    776   if (asm_driver_eat_comma(d)) {
    777     /* lsl #N (N is 0/16/32/48). */
    778     AsmTok lid = asm_driver_next(d);
    779     if (lid.kind != ASM_TOK_IDENT) asm_driver_panic(d, "asm: expected 'lsl'");
    780     Slice lsl = pool_slice(asm_driver_pool(d), lid.v.ident);
    781     const char* lp = lsl.s;
    782     size_t ln = lsl.len;
    783     if (!lp || !icase_eq(lp, ln, "lsl"))
    784       asm_driver_panic(d, "asm: expected 'lsl'");
    785     i64 sh = parse_imm_const(d);
    786     if (sh % 16 != 0 || sh < 0 || sh > 48)
    787       asm_driver_panic(d, "asm: movz/n/k: bad lsl shift");
    788     hw = (u32)(sh / 16);
    789   }
    790   u32 word = ((rd.is64 & 1u) << 31) | ((opc & 3u) << 29) |
    791              AA64_MOVEWIDE_FAMILY_MATCH | ((hw & 3u) << 21) |
    792              (((u32)imm & 0xffffu) << 5) | (rd.num & 0x1fu);
    793   emit32(d, word);
    794 }
    795 
    796 /* svc / brk / hlt #imm */
    797 static void p_except(AsmDriver* d, u32 form) {
    798   i64 imm = parse_imm_const(d);
    799   switch (form) {
    800     case 0:
    801       emit32(d, aa64_svc((u32)imm));
    802       break;
    803     case 1:
    804       emit32(d, aa64_brk((u32)imm));
    805       break;
    806     case 2: {
    807       /* HLT */
    808       u32 word = AA64_EXCEPT_FAMILY_MATCH | ((u32)2 << 21) |
    809                  (((u32)imm & 0xffffu) << 5);
    810       emit32(d, word);
    811       break;
    812     }
    813     default:
    814       asm_driver_panic(d, "asm: bad exception form");
    815   }
    816 }
    817 
    818 /* Read optional `, lsl|lsr|asr|ror #imm` shift modifier. Returns 1 if
    819  * present. */
    820 static int parse_shift_mod(AsmDriver* d, u32* shift_out, u32* imm6_out) {
    821   AsmTok t = asm_driver_peek(d);
    822   if (t.kind != ASM_TOK_IDENT) return 0;
    823   Slice sl = pool_slice(asm_driver_pool(d), t.v.ident);
    824   const char* p = sl.s;
    825   size_t n = sl.len;
    826   u32 sh;
    827   if (icase_eq(p, n, "lsl"))
    828     sh = 0;
    829   else if (icase_eq(p, n, "lsr"))
    830     sh = 1;
    831   else if (icase_eq(p, n, "asr"))
    832     sh = 2;
    833   else if (icase_eq(p, n, "ror"))
    834     sh = 3;
    835   else
    836     return 0;
    837   (void)asm_driver_next(d);
    838   i64 imm = parse_imm_const(d);
    839   if (imm < 0 || imm > 63)
    840     asm_driver_panic(d, "asm: shift amount out of range");
    841   *shift_out = sh;
    842   *imm6_out = (u32)imm;
    843   return 1;
    844 }
    845 
    846 /* add / sub family.
    847  * Forms:
    848  *   add Rd, Rn, Rm[, lsl #s]   shifted-register
    849  *   add Rd, Rn, #imm           immediate
    850  *   add Rd, Rn, #imm, lsl #12  immediate w/ shift
    851  * S-suffixed (adds/subs) sets flags. */
    852 static void p_addsub(AsmDriver* d, int is_sub, int set_flags) {
    853   AA64Reg rd = parse_reg(d);
    854   expect_comma(d, "add/sub");
    855   AA64Reg rn = parse_reg(d);
    856   expect_comma(d, "add/sub");
    857   AsmTok t = asm_driver_peek(d);
    858   /* `add Rd, Rn, <sym lo12>` — ADD (immediate), zero imm12, plus an
    859    * R_AARCH64_ADD_ABS_LO12_NC relocation (the low-12 PIC/abs sequence). ELF
    860    * spells the modifier as a `:lo12:` prefix (leading ':'); Mach-O spells it
    861    * as a `sym@PAGEOFF` suffix, so the trigger there is a non-register IDENT
    862    * third operand (probe with parse_reg_from_ident so `add x0,x1,x2` stays the
    863    * register path). */
    864   int symbolic = 0;
    865   if (!is_sub && !set_flags) {
    866     if (target_is_macho(d)) {
    867       AA64Reg probe;
    868       memset(&probe, 0, sizeof probe);
    869       symbolic = (t.kind == ASM_TOK_IDENT &&
    870                   !parse_reg_from_ident(d, t.v.ident, &probe));
    871     } else {
    872       symbolic = tok_punct(t, ':');
    873     }
    874   }
    875   if (symbolic) {
    876     AA64RelMod mod;
    877     ObjSymId sym = OBJ_SYM_NONE;
    878     i64 off = 0;
    879     if (target_is_macho(d)) {
    880       parse_imm_sym(d, &sym, &off);
    881       mod = parse_reloc_suffix(d);
    882     } else {
    883       mod = parse_reloc_mod(d);
    884       parse_imm_sym(d, &sym, &off);
    885     }
    886     if (mod != AA64_RELMOD_LO12)
    887       asm_driver_panic(d,
    888                        "asm: add: only :lo12: (ELF) / @PAGEOFF (Mach-O) is "
    889                        "valid here");
    890     if (rd.is64 != rn.is64)
    891       asm_driver_panic(d, "asm: add lo12: width mismatch");
    892     u32 word = aa64_addsubimm_pack((AA64AddSubImm){.sf = rd.is64,
    893                                                    .op = 0,
    894                                                    .S = 0,
    895                                                    .sh = 0,
    896                                                    .imm12 = 0,
    897                                                    .Rn = rn.num,
    898                                                    .Rd = rd.num});
    899     emit32(d, word);
    900     MCEmitter* mc = asm_driver_mc(d);
    901     mc_emit_reloc_at(mc, asm_driver_cur_section(d), mc_pos(mc) - 4,
    902                       R_AARCH64_ADD_ABS_LO12_NC, sym, off, 1, 0);
    903     return;
    904   }
    905   if (tok_punct(t, '#') || t.kind == ASM_TOK_NUM || tok_punct(t, '-') ||
    906       tok_punct(t, '+')) {
    907     /* immediate form */
    908     if (rd.is64 != rn.is64)
    909       asm_driver_panic(d, "asm: add/sub imm: width mismatch");
    910     require_sp_spelling(d, rn, "add/sub imm");
    911     if (set_flags) {
    912       reject_sp_reg(d, rd, "add/sub imm");
    913     } else {
    914       require_sp_spelling(d, rd, "add/sub imm");
    915     }
    916     i64 imm = parse_imm_const(d);
    917     u32 sh = 0;
    918     if (asm_driver_eat_comma(d)) {
    919       AsmTok lid = asm_driver_next(d);
    920       if (lid.kind != ASM_TOK_IDENT)
    921         asm_driver_panic(d, "asm: expected 'lsl #12'");
    922       Slice lsl = pool_slice(asm_driver_pool(d), lid.v.ident);
    923       const char* lp = lsl.s;
    924       size_t ln = lsl.len;
    925       if (!lp || !icase_eq(lp, ln, "lsl"))
    926         asm_driver_panic(d, "asm: expected 'lsl'");
    927       i64 s = parse_imm_const(d);
    928       if (s == 12)
    929         sh = 1;
    930       else if (s == 0)
    931         sh = 0;
    932       else
    933         asm_driver_panic(d, "asm: add/sub imm: lsl must be 0 or 12");
    934     }
    935     if (imm < 0 || imm > 0xfff)
    936       asm_driver_panic(d, "asm: add/sub imm out of range");
    937     u32 word = aa64_addsubimm_pack((AA64AddSubImm){.sf = rd.is64,
    938                                                    .op = (u32)is_sub,
    939                                                    .S = (u32)set_flags,
    940                                                    .sh = sh,
    941                                                    .imm12 = (u32)imm,
    942                                                    .Rn = rn.num,
    943                                                    .Rd = rd.num});
    944     emit32(d, word);
    945     return;
    946   }
    947   /* register form */
    948   AA64Reg rm = parse_reg(d);
    949   reject_sp_reg(d, rd, "add/sub reg");
    950   reject_sp_reg(d, rn, "add/sub reg");
    951   reject_sp_reg(d, rm, "add/sub reg");
    952   if (rd.is64 != rm.is64 || rd.is64 != rn.is64)
    953     asm_driver_panic(d, "asm: add/sub reg: width mismatch");
    954   u32 shift = 0, imm6 = 0;
    955   if (asm_driver_eat_comma(d)) {
    956     if (!parse_shift_mod(d, &shift, &imm6))
    957       asm_driver_panic(d, "asm: add/sub reg: expected shift modifier");
    958   }
    959   u32 word = aa64_addsubsr_pack((AA64AddSubSR){.sf = rd.is64,
    960                                                .op = (u32)is_sub,
    961                                                .S = (u32)set_flags,
    962                                                .shift = shift,
    963                                                .Rm = rm.num,
    964                                                .imm6 = imm6,
    965                                                .Rn = rn.num,
    966                                                .Rd = rd.num});
    967   emit32(d, word);
    968 }
    969 
    970 /* cmp Rn, Rm | cmp Rn, #imm  → SUBS ZR, Rn, ... */
    971 static void p_cmp(AsmDriver* d, int is_neg /* cmn flips op */) {
    972   AA64Reg rn = parse_reg(d);
    973   expect_comma(d, "cmp");
    974   AsmTok t = asm_driver_peek(d);
    975   if (tok_punct(t, '#') || t.kind == ASM_TOK_NUM || tok_punct(t, '-') ||
    976       tok_punct(t, '+')) {
    977     require_sp_spelling(d, rn, "cmp imm");
    978     i64 imm = parse_imm_const(d);
    979     u32 sh = 0;
    980     if (asm_driver_eat_comma(d)) {
    981       AsmTok lid = asm_driver_next(d);
    982       Slice lsl = (lid.kind == ASM_TOK_IDENT)
    983                       ? pool_slice(asm_driver_pool(d), lid.v.ident)
    984                       : SLICE_NULL;
    985       const char* lp = lsl.s;
    986       size_t ln = lsl.len;
    987       if (!lp || !icase_eq(lp, ln, "lsl"))
    988         asm_driver_panic(d, "asm: cmp imm: expected 'lsl'");
    989       i64 s = parse_imm_const(d);
    990       if (s == 12)
    991         sh = 1;
    992       else if (s != 0)
    993         asm_driver_panic(d, "asm: cmp imm: lsl must be 0 or 12");
    994     }
    995     if (imm < 0 || imm > 0xfff)
    996       asm_driver_panic(d, "asm: cmp imm out of range");
    997     u32 word = aa64_addsubimm_pack((AA64AddSubImm){.sf = rn.is64,
    998                                                    .op = (u32)(!is_neg),
    999                                                    .S = 1,
   1000                                                    .sh = sh,
   1001                                                    .imm12 = (u32)imm,
   1002                                                    .Rn = rn.num,
   1003                                                    .Rd = AA64_ZR});
   1004     emit32(d, word);
   1005     return;
   1006   }
   1007   AA64Reg rm = parse_reg(d);
   1008   reject_sp_reg(d, rn, "cmp reg");
   1009   reject_sp_reg(d, rm, "cmp reg");
   1010   if (rm.is64 != rn.is64) asm_driver_panic(d, "asm: cmp: width mismatch");
   1011   u32 shift = 0, imm6 = 0;
   1012   if (asm_driver_eat_comma(d)) parse_shift_mod(d, &shift, &imm6);
   1013   u32 word = aa64_addsubsr_pack((AA64AddSubSR){.sf = rn.is64,
   1014                                                .op = (u32)(!is_neg),
   1015                                                .S = 1,
   1016                                                .shift = shift,
   1017                                                .Rm = rm.num,
   1018                                                .imm6 = imm6,
   1019                                                .Rn = rn.num,
   1020                                                .Rd = AA64_ZR});
   1021   emit32(d, word);
   1022 }
   1023 
   1024 static void p_condsel(AsmDriver* d, u32 op, u32 op2, const char* what) {
   1025   AA64Reg rd = parse_reg(d);
   1026   expect_comma(d, what);
   1027   AA64Reg rn = parse_reg(d);
   1028   expect_comma(d, what);
   1029   AA64Reg rm = parse_reg(d);
   1030   expect_comma(d, what);
   1031   u32 cond = parse_cond(d, what);
   1032   if (rd.is_sp || rn.is_sp || rm.is_sp)
   1033     asm_driver_panic(d, "asm: %.*s: SP register not allowed",
   1034                      SLICE_ARG(slice_from_cstr(what)));
   1035   if (rd.is64 != rn.is64 || rd.is64 != rm.is64)
   1036     asm_driver_panic(d, "asm: %.*s: width mismatch",
   1037                      SLICE_ARG(slice_from_cstr(what)));
   1038   u32 word = aa64_condsel_pack((AA64CondSel){.sf = (u32)rd.is64,
   1039                                              .op = op,
   1040                                              .S = 0,
   1041                                              .Rm = rm.num,
   1042                                              .cond = cond,
   1043                                              .op2 = op2,
   1044                                              .Rn = rn.num,
   1045                                              .Rd = rd.num});
   1046   emit32(d, word);
   1047 }
   1048 
   1049 static void p_cset_like(AsmDriver* d, u32 op, u32 op2, const char* what) {
   1050   AA64Reg rd = parse_reg(d);
   1051   expect_comma(d, what);
   1052   u32 cond = parse_cond(d, what);
   1053   if (rd.is_sp)
   1054     asm_driver_panic(d, "asm: %.*s: SP register not allowed",
   1055                      SLICE_ARG(slice_from_cstr(what)));
   1056   u32 word = aa64_condsel_pack((AA64CondSel){.sf = (u32)rd.is64,
   1057                                              .op = op,
   1058                                              .S = 0,
   1059                                              .Rm = AA64_ZR,
   1060                                              .cond = cond ^ 1u,
   1061                                              .op2 = op2,
   1062                                              .Rn = AA64_ZR,
   1063                                              .Rd = rd.num});
   1064   emit32(d, word);
   1065 }
   1066 
   1067 /* neg / negs Rd, Rm  → SUB / SUBS Rd, ZR, Rm */
   1068 static void p_neg(AsmDriver* d, int set_flags) {
   1069   AA64Reg rd = parse_reg(d);
   1070   expect_comma(d, "neg");
   1071   AA64Reg rm = parse_reg(d);
   1072   reject_sp_reg(d, rd, "neg");
   1073   reject_sp_reg(d, rm, "neg");
   1074   if (rd.is64 != rm.is64) asm_driver_panic(d, "asm: neg: width mismatch");
   1075   u32 shift = 0, imm6 = 0;
   1076   if (asm_driver_eat_comma(d)) parse_shift_mod(d, &shift, &imm6);
   1077   u32 word = aa64_addsubsr_pack((AA64AddSubSR){.sf = rd.is64,
   1078                                                .op = 1,
   1079                                                .S = (u32)set_flags,
   1080                                                .shift = shift,
   1081                                                .Rm = rm.num,
   1082                                                .imm6 = imm6,
   1083                                                .Rn = AA64_ZR,
   1084                                                .Rd = rd.num});
   1085   emit32(d, word);
   1086 }
   1087 
   1088 /* Logical family: shifted-register `<op> Rd,Rn,Rm{,shift}` or, for the
   1089  * non-negated AND/ORR/EOR/ANDS, the bitmask-immediate `<op> Rd,Rn,#imm`.
   1090  * N is the SR-form negate bit (BIC/ORN/EON/BICS); those have no immediate
   1091  * form, so an immediate third operand is only valid when N==0. */
   1092 static void p_log_sr(AsmDriver* d, u32 opc, u32 N) {
   1093   AA64Reg rd = parse_reg(d);
   1094   expect_comma(d, "logical");
   1095   AA64Reg rn = parse_reg(d);
   1096   expect_comma(d, "logical");
   1097   if (!peek_is_reg(d)) {
   1098     /* Bitmask-immediate form. AND/ORR/EOR use the SP-capable destination;
   1099      * ANDS uses ZR. Rn is always a GPR (caller's parse_reg already enforced
   1100      * GP for the two register operands). */
   1101     if (N) asm_driver_panic(d, "asm: logical: immediate form has no negation");
   1102     if (rd.is64 != rn.is64) asm_driver_panic(d, "asm: logical: width mismatch");
   1103     u64 imm = (u64)parse_imm_const(d);
   1104     u32 bN = 0, immr = 0, imms = 0;
   1105     if (!aa64_logimm_encode(imm, rd.is64, &bN, &immr, &imms))
   1106       asm_driver_panic(d, "asm: logical: immediate is not a valid bitmask");
   1107     emit32(d, aa64_logimm_pack((AA64LogImm){.sf = rd.is64,
   1108                                             .opc = opc,
   1109                                             .N = bN,
   1110                                             .immr = immr,
   1111                                             .imms = imms,
   1112                                             .Rn = rn.num,
   1113                                             .Rd = rd.num}));
   1114     return;
   1115   }
   1116   AA64Reg rm = parse_reg(d);
   1117   if (rd.is64 != rn.is64 || rd.is64 != rm.is64)
   1118     asm_driver_panic(d, "asm: logical: width mismatch");
   1119   u32 shift = 0, imm6 = 0;
   1120   if (asm_driver_eat_comma(d)) parse_shift_mod(d, &shift, &imm6);
   1121   u32 word = aa64_logsr_pack((AA64LogSR){.sf = rd.is64,
   1122                                          .opc = opc,
   1123                                          .shift = shift,
   1124                                          .N = N,
   1125                                          .Rm = rm.num,
   1126                                          .imm6 = imm6,
   1127                                          .Rn = rn.num,
   1128                                          .Rd = rd.num});
   1129   emit32(d, word);
   1130 }
   1131 
   1132 /* Data-processing 3-source: madd/msub Rd, Rn, Rm, Ra. */
   1133 static void p_dp3(AsmDriver* d, u32 o0) {
   1134   AA64Reg rd = parse_reg(d);
   1135   expect_comma(d, "dp3");
   1136   AA64Reg rn = parse_reg(d);
   1137   expect_comma(d, "dp3");
   1138   AA64Reg rm = parse_reg(d);
   1139   expect_comma(d, "dp3");
   1140   AA64Reg ra = parse_reg(d);
   1141   if (rd.is64 != rn.is64 || rd.is64 != rm.is64 || rd.is64 != ra.is64)
   1142     asm_driver_panic(d, "asm: dp3: width mismatch");
   1143   u32 word = aa64_dp3_pack((AA64DP3){.sf = rd.is64,
   1144                                      .op31 = 0,
   1145                                      .o0 = o0,
   1146                                      .Rm = rm.num,
   1147                                      .Ra = ra.num,
   1148                                      .Rn = rn.num,
   1149                                      .Rd = rd.num});
   1150   emit32(d, word);
   1151 }
   1152 
   1153 /* mul Rd, Rn, Rm  → MADD Rd, Rn, Rm, ZR */
   1154 static void p_mul(AsmDriver* d, u32 o0) {
   1155   AA64Reg rd = parse_reg(d);
   1156   expect_comma(d, "mul");
   1157   AA64Reg rn = parse_reg(d);
   1158   expect_comma(d, "mul");
   1159   AA64Reg rm = parse_reg(d);
   1160   if (rd.is64 != rn.is64 || rd.is64 != rm.is64)
   1161     asm_driver_panic(d, "asm: mul: width mismatch");
   1162   u32 word = aa64_dp3_pack((AA64DP3){.sf = rd.is64,
   1163                                      .op31 = 0,
   1164                                      .o0 = o0,
   1165                                      .Rm = rm.num,
   1166                                      .Ra = AA64_ZR,
   1167                                      .Rn = rn.num,
   1168                                      .Rd = rd.num});
   1169   emit32(d, word);
   1170 }
   1171 
   1172 /* DP2: udiv/sdiv/lslv/lsrv/asrv/rorv Rd, Rn, Rm. */
   1173 static void p_dp2(AsmDriver* d, u32 opcode) {
   1174   AA64Reg rd = parse_reg(d);
   1175   expect_comma(d, "dp2");
   1176   AA64Reg rn = parse_reg(d);
   1177   expect_comma(d, "dp2");
   1178   AA64Reg rm = parse_reg(d);
   1179   if (rd.is64 != rn.is64 || rd.is64 != rm.is64)
   1180     asm_driver_panic(d, "asm: dp2: width mismatch");
   1181   u32 word = aa64_dp2_pack((AA64DP2){.sf = rd.is64,
   1182                                      .opcode = opcode,
   1183                                      .Rm = rm.num,
   1184                                      .Rn = rn.num,
   1185                                      .Rd = rd.num});
   1186   emit32(d, word);
   1187 }
   1188 
   1189 /* Shift aliases: `<op> Rd, Rn, (Rm | #imm)`.
   1190  *   register form  → LSLV/LSRV/ASRV (DP2 variable shift)
   1191  *   immediate form → UBFM (lsl/lsr) / SBFM (asr) bitfield alias
   1192  * `kind` indexes the three shifts: 0=lsl 1=lsr 2=asr. The immediate aliases
   1193  * are exactly what the disassembler prints for these UBFM/SBFM encodings, so
   1194  * `cc -S | as` round-trips. (ROR's immediate form is EXTR, which the
   1195  * disassembler doesn't decode, so it is left out — `rorv` covers the register
   1196  * rotate.) */
   1197 static void p_shift(AsmDriver* d, u32 kind) {
   1198   static const u32 dp2op[3] = {AA64_DP2_LSLV_OP, AA64_DP2_LSRV_OP,
   1199                                AA64_DP2_ASRV_OP};
   1200   AA64Reg rd = parse_reg(d);
   1201   expect_comma(d, "shift");
   1202   AA64Reg rn = parse_reg(d);
   1203   if (rd.is64 != rn.is64) asm_driver_panic(d, "asm: shift: width mismatch");
   1204   expect_comma(d, "shift");
   1205   if (peek_is_reg(d)) {
   1206     AA64Reg rm = parse_reg(d);
   1207     if (rd.is64 != rm.is64) asm_driver_panic(d, "asm: shift: width mismatch");
   1208     emit32(d, aa64_dp2_pack((AA64DP2){.sf = rd.is64,
   1209                                       .opcode = dp2op[kind],
   1210                                       .Rm = rm.num,
   1211                                       .Rn = rn.num,
   1212                                       .Rd = rd.num}));
   1213     return;
   1214   }
   1215   i64 sv = parse_imm_const(d);
   1216   u32 width = rd.is64 ? 64u : 32u;
   1217   if (sv < 0 || (u64)sv >= width)
   1218     asm_driver_panic(d, "asm: shift: amount out of range");
   1219   u32 shift = (u32)sv, immr = 0, imms = 0;
   1220   if (kind == 2) { /* asr → SBFM */
   1221     aa64_asr_imm_fields(shift, rd.is64, &immr, &imms);
   1222     emit32(d, aa64_bitfield(rd.is64, 0u, immr, imms, rd.num, rn.num));
   1223   } else { /* lsl/lsr → UBFM */
   1224     if (kind == 0)
   1225       aa64_lsl_imm_fields(shift, rd.is64, &immr, &imms);
   1226     else
   1227       aa64_lsr_imm_fields(shift, rd.is64, &immr, &imms);
   1228     emit32(d, aa64_bitfield(rd.is64, 2u, immr, imms, rd.num, rn.num));
   1229   }
   1230 }
   1231 
   1232 /* Branch immediate / conditional / compare-and-branch. */
   1233 
   1234 static void emit_branch_imm(AsmDriver* d, u32 op_bl, ObjSymId target,
   1235                             i64 addend, i64 const_disp) {
   1236   MCEmitter* mc = asm_driver_mc(d);
   1237   /* Emit a B/BL with imm26 = 0; record a CALL26/JUMP26 reloc against
   1238    * either the symbol or the constant displacement. */
   1239   u32 word = aa64_brimm_pack((AA64BrImm){.op = op_bl, .imm26 = 0});
   1240   emit32(d, word);
   1241   u32 ofs = mc_pos(mc) - 4;
   1242   RelocKind k = op_bl ? R_AARCH64_CALL26 : R_AARCH64_JUMP26;
   1243   if (target != OBJ_SYM_NONE) {
   1244     mc_emit_reloc_at(mc, asm_driver_cur_section(d), ofs, k, target, addend, 1,
   1245                       0);
   1246   } else {
   1247     /* Pure constant displacement is rare in real .s; reject it now.
   1248      * The recommended form is to use a label and let the assembler
   1249      * compute the displacement. */
   1250     (void)const_disp;
   1251     asm_driver_panic(d, "asm: branch with pure constant disp not supported");
   1252   }
   1253 }
   1254 
   1255 static void p_b(AsmDriver* d, u32 op_bl) {
   1256   ObjSymId sym = OBJ_SYM_NONE;
   1257   i64 off = 0;
   1258   /* GNU as accepts `b sym`, `bl sym+8`, etc. */
   1259   parse_imm_sym(d, &sym, &off);
   1260   if (sym == OBJ_SYM_NONE)
   1261     asm_driver_panic(d, "asm: b/bl: symbolic target required");
   1262   emit_branch_imm(d, op_bl, sym, off, 0);
   1263 }
   1264 
   1265 static void p_b_cond(AsmDriver* d, u32 cond) {
   1266   ObjSymId sym = OBJ_SYM_NONE;
   1267   i64 off = 0;
   1268   parse_imm_sym(d, &sym, &off);
   1269   if (sym == OBJ_SYM_NONE)
   1270     asm_driver_panic(d, "asm: b.cond: symbolic target required");
   1271   /* Emit the instruction with imm19=0 + R_AARCH64_CONDBR19 reloc. */
   1272   u32 word = aa64_brcond_pack((AA64BrCond){.imm19 = 0, .cond = cond});
   1273   emit32(d, word);
   1274   MCEmitter* mc = asm_driver_mc(d);
   1275   u32 ofs = mc_pos(mc) - 4;
   1276   mc_emit_reloc_at(mc, asm_driver_cur_section(d), ofs, R_AARCH64_CONDBR19, sym,
   1277                     off, 1, 0);
   1278 }
   1279 
   1280 static void p_cbz(AsmDriver* d, u32 op) {
   1281   AA64Reg rt = parse_reg(d);
   1282   expect_comma(d, "cbz");
   1283   ObjSymId sym = OBJ_SYM_NONE;
   1284   i64 off = 0;
   1285   parse_imm_sym(d, &sym, &off);
   1286   if (sym == OBJ_SYM_NONE)
   1287     asm_driver_panic(d, "asm: cbz: symbolic target required");
   1288   u32 word =
   1289       aa64_cb_pack((AA64CB){.sf = rt.is64, .op = op, .imm19 = 0, .Rt = rt.num});
   1290   emit32(d, word);
   1291   MCEmitter* mc = asm_driver_mc(d);
   1292   u32 ofs = mc_pos(mc) - 4;
   1293   mc_emit_reloc_at(mc, asm_driver_cur_section(d), ofs, R_AARCH64_CONDBR19, sym,
   1294                     off, 1, 0);
   1295 }
   1296 
   1297 /* Memory-operand parser.  Recognized shapes:
   1298  *   [Xn]                                base only
   1299  *   [Xn, #imm]                          base + immediate offset
   1300  *   [Xn, #imm]!                         pre-index (writeback)
   1301  *   [Xn], #imm                          post-index (writeback)
   1302  *   [Xn, Xm]                            register offset (LSL #0)
   1303  *   [Xn, Xm, LSL #s]                    register offset, scaled
   1304  *   [Xn, Wm, {U,S}XTW {#s}]             32-bit index, extended
   1305  *   [Xn, Xm, {U,S}XTX {#s}] / SXTX      64-bit index, extended
   1306  *
   1307  * imm is the literal byte offset (no scaling).  When has_index is set,
   1308  * `index` is the index register, `option` its 3-bit extend code, and
   1309  * shift_present records whether an explicit `#s` was written (with the
   1310  * amount in `shift`).  pre_index / post_index flag the writeback forms. */
   1311 typedef struct AA64Mem {
   1312   AA64Reg base;
   1313   AA64Reg index;
   1314   i64 imm; /* byte offset (literal as written) */
   1315   u32 option;
   1316   u32 shift;
   1317   AA64RelMod reloc_mod; /* :lo12: / :got_lo12: on the offset, or NONE */
   1318   ObjSymId reloc_sym;   /* symbol when reloc_mod != NONE */
   1319   i64 reloc_off;        /* addend when reloc_mod != NONE */
   1320   u8 pre_index;
   1321   u8 post_index;
   1322   u8 has_offset;
   1323   u8 has_index;
   1324   u8 shift_present;
   1325   u8 pad[3];
   1326 } AA64Mem;
   1327 
   1328 /* Parse the optional extend/shift modifier of a register-offset memory
   1329  * operand: `LSL #s`, `UXTW {#s}`, `SXTW {#s}`, `UXTX {#s}`, `SXTX {#s}`.
   1330  * The index register width (32 vs 64) must agree with the extend kind.
   1331  * Fills m->option / m->shift / m->shift_present. */
   1332 static void parse_mem_extend(AsmDriver* d, AA64Mem* m) {
   1333   AsmTok t = asm_driver_next(d);
   1334   if (t.kind != ASM_TOK_IDENT)
   1335     asm_driver_panic(d, "asm: ldr/str: expected extend (lsl/sxtw/uxtw/...)");
   1336   Slice sl = pool_slice(asm_driver_pool(d), t.v.ident);
   1337   const char* p = sl.s;
   1338   size_t n = sl.len;
   1339   int need64 = 0; /* index must be 64-bit */
   1340   if (icase_eq(p, n, "lsl") || icase_eq(p, n, "uxtx")) {
   1341     m->option = AA64_LDST_OPTION_LSL;
   1342     need64 = 1;
   1343   } else if (icase_eq(p, n, "sxtx")) {
   1344     m->option = AA64_LDST_OPTION_SXTX;
   1345     need64 = 1;
   1346   } else if (icase_eq(p, n, "uxtw")) {
   1347     m->option = AA64_LDST_OPTION_UXTW;
   1348     need64 = 0;
   1349   } else if (icase_eq(p, n, "sxtw")) {
   1350     m->option = AA64_LDST_OPTION_SXTW;
   1351     need64 = 0;
   1352   } else {
   1353     asm_driver_panic(d, "asm: ldr/str: unknown index extend");
   1354   }
   1355   if (need64 && !m->index.is64)
   1356     asm_driver_panic(d, "asm: ldr/str: index must be 64-bit for this extend");
   1357   if (!need64 && m->index.is64)
   1358     asm_driver_panic(d, "asm: ldr/str: index must be 32-bit for sxtw/uxtw");
   1359   /* LSL requires an explicit shift; the extends accept an optional one. */
   1360   AsmTok nt = asm_driver_peek(d);
   1361   if (tok_punct(nt, '#') || nt.kind == ASM_TOK_NUM) {
   1362     i64 s = parse_imm_const(d);
   1363     if (s < 0) asm_driver_panic(d, "asm: ldr/str: negative index shift");
   1364     m->shift = (u32)s;
   1365     m->shift_present = 1;
   1366   } else if (m->option == AA64_LDST_OPTION_LSL) {
   1367     asm_driver_panic(d, "asm: ldr/str: lsl requires a shift amount");
   1368   }
   1369 }
   1370 
   1371 static AA64Mem parse_mem(AsmDriver* d) {
   1372   AA64Mem m;
   1373   memset(&m, 0, sizeof m);
   1374   if (!asm_driver_eat_punct(d, '[')) asm_driver_panic(d, "asm: expected '['");
   1375   m.base = parse_reg(d);
   1376   if (!m.base.is64)
   1377     asm_driver_panic(d, "asm: ldr/str: base register must be 64-bit");
   1378   require_sp_spelling(d, m.base, "ldr/str base");
   1379   if (asm_driver_eat_comma(d)) {
   1380     /* A relocation offset (ELF `:lo12:sym`/`:got_lo12:sym` prefix, or Mach-O
   1381      * `sym@PAGEOFF`/`sym@GOTPAGEOFF` suffix), a register index, or a plain
   1382      * `#imm`/expression. */
   1383     AsmTok t = asm_driver_peek(d);
   1384     AA64Reg idx;
   1385     memset(&idx, 0, sizeof idx);
   1386     if (!target_is_macho(d) && tok_punct(t, ':')) {
   1387       m.reloc_mod = parse_reloc_mod(d);
   1388       parse_imm_sym(d, &m.reloc_sym, &m.reloc_off);
   1389       m.has_offset = 1; /* imm field stays 0; reloc supplies the low bits */
   1390     } else if (t.kind == ASM_TOK_IDENT &&
   1391                parse_reg_from_ident(d, t.v.ident, &idx)) {
   1392       (void)asm_driver_next(d);
   1393       reject_sp_reg(d, idx, "ldr/str index");
   1394       m.index = idx;
   1395       m.has_index = 1;
   1396       m.option = idx.is64 ? AA64_LDST_OPTION_LSL : AA64_LDST_OPTION_UXTW;
   1397       if (asm_driver_eat_comma(d)) parse_mem_extend(d, &m);
   1398     } else if (target_is_macho(d) && t.kind == ASM_TOK_IDENT) {
   1399       /* Mach-O: `[Xn, sym@PAGEOFF]` / `[Xn, sym@GOTPAGEOFF]`. */
   1400       parse_imm_sym(d, &m.reloc_sym, &m.reloc_off);
   1401       m.reloc_mod = parse_reloc_suffix(d);
   1402       if (m.reloc_mod != AA64_RELMOD_LO12 &&
   1403           m.reloc_mod != AA64_RELMOD_GOT_LO12)
   1404         asm_driver_panic(
   1405             d, "asm: ldr/str: symbol offset needs @PAGEOFF/@GOTPAGEOFF");
   1406       m.has_offset = 1;
   1407     } else {
   1408       m.imm = parse_imm_const(d);
   1409       m.has_offset = 1;
   1410     }
   1411   }
   1412   if (!asm_driver_eat_punct(d, ']')) asm_driver_panic(d, "asm: expected ']'");
   1413   if (asm_driver_eat_punct(d, '!')) {
   1414     if (m.has_index)
   1415       asm_driver_panic(d, "asm: ldr/str: writeback not allowed with index");
   1416     m.pre_index = 1;
   1417   } else if (asm_driver_eat_comma(d)) {
   1418     /* Post-index: `[Xn], #imm`. */
   1419     if (m.has_index || m.has_offset)
   1420       asm_driver_panic(d, "asm: ldr/str: malformed post-index operand");
   1421     m.imm = parse_imm_const(d);
   1422     m.has_offset = 1;
   1423     m.post_index = 1;
   1424   }
   1425   return m;
   1426 }
   1427 
   1428 /* ldr/str Rt, [Xn, #imm] — chooses scaled or unscaled form based on
   1429  * alignment of imm. */
   1430 /* Core load/store. `fixed_size` is the access log2-size (0=byte..3=dword) for
   1431  * ldrb/ldrh/ldrsw etc., or -1 to derive it from the register width (ldr/str).
   1432  * `sign_ext` selects the signed-load opc (10 = sign-extend to 64-bit, 11 = to
   1433  * 32-bit), keyed on the destination register width. */
   1434 static void p_ldst_core(AsmDriver* d, int is_load, int fixed_size,
   1435                         int sign_ext) {
   1436   AA64Reg rt = parse_ldst_reg(d);
   1437   reject_sp_reg(d, rt, "ldr/str");
   1438   expect_comma(d, "ldr/str");
   1439   AA64Mem m = parse_mem(d);
   1440   AA64LdStEnc e = ldst_encoding(d, rt, is_load, fixed_size, sign_ext);
   1441   u32 size = e.size, opc = e.opc, V = e.V;
   1442   if (m.reloc_mod != AA64_RELMOD_NONE) {
   1443     /* [Xn, :lo12:sym] / [Xn, :got_lo12:sym] — unsigned-imm12 form with a zero
   1444      * immediate; the relocation supplies the low 12 bits.  :got_lo12: only
   1445      * applies to a 64-bit `ldr` (the GOT entry is an 8-byte pointer); llvm-mc
   1446      * rejects it on stores, signed loads, and sub-word loads. */
   1447     if (m.reloc_mod == AA64_RELMOD_GOT_LO12 &&
   1448         !(V == 0 && size == 3 && opc == AA64_LDST_OPC_LDR))
   1449       asm_driver_panic(d, "asm: :got_lo12: only valid on a 64-bit ldr");
   1450     u32 word = aa64_ldst_uimm_pack((AA64LdStUimm){.size = size,
   1451                                                   .V = V,
   1452                                                   .opc = opc,
   1453                                                   .imm12 = 0,
   1454                                                   .Rn = m.base.num,
   1455                                                   .Rt = rt.num});
   1456     emit32(d, word);
   1457     RelocKind k = (m.reloc_mod == AA64_RELMOD_GOT_LO12)
   1458                       ? R_AARCH64_LD64_GOT_LO12_NC
   1459                       : aa64_ldst_lo12_reloc(d, size);
   1460     MCEmitter* mc = asm_driver_mc(d);
   1461     mc_emit_reloc_at(mc, asm_driver_cur_section(d), mc_pos(mc) - 4, k,
   1462                       m.reloc_sym, m.reloc_off, 1, 0);
   1463     return;
   1464   }
   1465   if (m.has_index) {
   1466     /* Register-offset form.  The S bit (scale by access size) is set when an
   1467      * explicit shift equal to the access log2-size is written.  An explicit
   1468      * `#0` is legal and stays unscaled (S=0); for byte access #0 == size so it
   1469      * sets S — matching llvm-mc, where the only legal amounts are 0 or size. */
   1470     u32 S = 0;
   1471     if (m.shift_present) {
   1472       if (m.shift == size)
   1473         S = 1;
   1474       else if (m.shift != 0)
   1475         asm_driver_panic(d,
   1476                          "asm: ldr/str: index shift must be 0 or access size");
   1477     }
   1478     u32 word = aa64_ldst_regoff_pack((AA64LdStRegOff){.size = size,
   1479                                                       .V = V,
   1480                                                       .opc = opc,
   1481                                                       .Rm = m.index.num,
   1482                                                       .option = m.option,
   1483                                                       .S = S,
   1484                                                       .Rn = m.base.num,
   1485                                                       .Rt = rt.num});
   1486     emit32(d, word);
   1487     return;
   1488   }
   1489   if (m.pre_index || m.post_index) {
   1490     /* Immediate writeback (unscaled signed imm9). */
   1491     if (m.imm < -256 || m.imm > 255)
   1492       asm_driver_panic(d, "asm: ldr/str: writeback imm9 out of range");
   1493     u32 imm9 = (u32)((u64)m.imm & 0x1ffu);
   1494     u32 idx = m.pre_index ? AA64_LDST_IDX_PRE : AA64_LDST_IDX_POST;
   1495     u32 word = aa64_ldst_wback_pack((AA64LdStWBack){.size = size,
   1496                                                     .V = V,
   1497                                                     .opc = opc,
   1498                                                     .imm9 = imm9,
   1499                                                     .idx = idx,
   1500                                                     .Rn = m.base.num,
   1501                                                     .Rt = rt.num});
   1502     emit32(d, word);
   1503     return;
   1504   }
   1505   {
   1506     /* Try scaled unsigned-imm12 first. */
   1507     u32 scale = e.scale;
   1508     if (m.imm >= 0 && (i64)((u64)m.imm % scale) == 0 &&
   1509         (u64)m.imm / scale <= 0xfff) {
   1510       u32 imm12 = (u32)((u64)m.imm / scale);
   1511       u32 word = aa64_ldst_uimm_pack((AA64LdStUimm){.size = size,
   1512                                                     .V = V,
   1513                                                     .opc = opc,
   1514                                                     .imm12 = imm12,
   1515                                                     .Rn = m.base.num,
   1516                                                     .Rt = rt.num});
   1517       emit32(d, word);
   1518       return;
   1519     }
   1520     /* Fall back to unscaled signed-imm9 (LDUR/STUR). */
   1521     if (m.imm >= -256 && m.imm <= 255) {
   1522       u32 imm9 = (u32)((u64)m.imm & 0x1ffu);
   1523       u32 word = aa64_ldst_simm9_pack((AA64LdStSimm9){.size = size,
   1524                                                       .V = V,
   1525                                                       .opc = opc,
   1526                                                       .imm9 = imm9,
   1527                                                       .Rn = m.base.num,
   1528                                                       .Rt = rt.num});
   1529       emit32(d, word);
   1530       return;
   1531     }
   1532     asm_driver_panic(d, "asm: ldr/str: immediate out of range");
   1533   }
   1534 }
   1535 
   1536 /* ldr/str: access width follows the register (Wt=word, Xt=dword). */
   1537 static void p_ldr_str(AsmDriver* d, int is_load) {
   1538   p_ldst_core(d, is_load, /*fixed_size=*/-1, /*sign_ext=*/0);
   1539 }
   1540 /* Byte/half + signed sub-word loads/stores (fixed access width). */
   1541 static void p_ldrb(AsmDriver* d) { p_ldst_core(d, 1, 0, 0); }
   1542 static void p_strb(AsmDriver* d) { p_ldst_core(d, 0, 0, 0); }
   1543 static void p_ldrh(AsmDriver* d) { p_ldst_core(d, 1, 1, 0); }
   1544 static void p_strh(AsmDriver* d) { p_ldst_core(d, 0, 1, 0); }
   1545 static void p_ldrsb(AsmDriver* d) { p_ldst_core(d, 1, 0, 1); }
   1546 static void p_ldrsh(AsmDriver* d) { p_ldst_core(d, 1, 1, 1); }
   1547 static void p_ldrsw(AsmDriver* d) { p_ldst_core(d, 1, 2, 1); }
   1548 
   1549 /* ldur/stur — unscaled signed-imm9. `fixed_size` is the access log2-size
   1550  * (0=byte..3=dword) for sturb/ldurb/sturh/ldurh/ldursw etc., or -1 to derive
   1551  * it from the register width (stur/ldur). `sign_ext` selects the signed-load
   1552  * opc (ldursb/ldursh/ldursw), keyed on the destination register width — the
   1553  * unscaled mirror of p_ldst_core. */
   1554 static void p_ldur_stur(AsmDriver* d, int is_load, int fixed_size,
   1555                         int sign_ext) {
   1556   AA64Reg rt = parse_ldst_reg(d);
   1557   reject_sp_reg(d, rt, "ldur/stur");
   1558   expect_comma(d, "ldur/stur");
   1559   AA64Mem m = parse_mem(d);
   1560   AA64LdStEnc e = ldst_encoding(d, rt, is_load, fixed_size, sign_ext);
   1561   if (m.imm < -256 || m.imm > 255)
   1562     asm_driver_panic(d, "asm: ldur/stur: imm9 out of range");
   1563   u32 imm9 = (u32)((u64)m.imm & 0x1ffu);
   1564   u32 word = aa64_ldst_simm9_pack((AA64LdStSimm9){.size = e.size,
   1565                                                   .V = e.V,
   1566                                                   .opc = e.opc,
   1567                                                   .imm9 = imm9,
   1568                                                   .Rn = m.base.num,
   1569                                                   .Rt = rt.num});
   1570   emit32(d, word);
   1571 }
   1572 
   1573 /* ldp / stp Rt, Rt2, [Xn, #imm] or [Xn, #imm]! */
   1574 static void p_ldp_stp(AsmDriver* d, int is_load) {
   1575   AA64Reg rt = parse_ldstp_reg(d);
   1576   expect_comma(d, "ldp/stp");
   1577   AA64Reg rt2 = parse_ldstp_reg(d);
   1578   expect_comma(d, "ldp/stp");
   1579   reject_sp_reg(d, rt, "ldp/stp");
   1580   reject_sp_reg(d, rt2, "ldp/stp");
   1581   if (rt.is64 != rt2.is64 || rt.is_fp != rt2.is_fp ||
   1582       rt.fp_bytes != rt2.fp_bytes)
   1583     asm_driver_panic(d, "asm: ldp/stp: width mismatch");
   1584   AA64Mem m = parse_mem(d);
   1585   u32 scale = rt.is_fp ? (u32)rt.fp_bytes : (rt.is64 ? 8u : 4u);
   1586   if ((i64)((u64)m.imm % scale) != 0)
   1587     asm_driver_panic(d, "asm: ldp/stp: imm not scale-aligned");
   1588   i64 imm7 = m.imm / (i64)scale;
   1589   if (imm7 < -64 || imm7 > 63)
   1590     asm_driver_panic(d, "asm: ldp/stp: imm7 out of range");
   1591   AA64LdStPPre f = {
   1592       .opc = rt.is_fp ? (rt.fp_bytes == 16u ? 2u : 1u) : (rt.is64 ? 2u : 0u),
   1593       .V = rt.is_fp ? 1u : 0u,
   1594       .L = is_load ? 1u : 0u,
   1595       .imm7 = (u32)imm7 & 0x7fu,
   1596       .Rt2 = rt2.num,
   1597       .Rn = m.base.num,
   1598       .Rt = rt.num};
   1599   if (m.pre_index)
   1600     emit32(d, aa64_ldstp_pre_pack(f));
   1601   else if (m.post_index)
   1602     emit32(d, aa64_ldstp_post_pack(f));
   1603   else
   1604     emit32(d, aa64_ldstp_soff_pack(f));
   1605 }
   1606 
   1607 /* adr / adrp Rd, sym */
   1608 static void p_adr(AsmDriver* d, int is_adrp) {
   1609   AA64Reg rd = parse_reg(d);
   1610   expect_comma(d, "adr");
   1611   /* adrp page reloc on a symbol: ELF spells a bare symbol (`:got:` selects the
   1612    * GOT page); Mach-O spells `sym@PAGE` / `sym@GOTPAGE`.  adr takes a bare
   1613    * symbol on both.  cc -S emits the form matching the target format. */
   1614   AA64RelMod mod = AA64_RELMOD_NONE;
   1615   ObjSymId sym = OBJ_SYM_NONE;
   1616   i64 off = 0;
   1617   if (target_is_macho(d)) {
   1618     parse_imm_sym(d, &sym, &off);
   1619     mod = parse_reloc_suffix(d);
   1620   } else {
   1621     mod = parse_reloc_mod(d);
   1622     parse_imm_sym(d, &sym, &off);
   1623   }
   1624   if (!is_adrp) {
   1625     if (mod != AA64_RELMOD_NONE)
   1626       asm_driver_panic(d, "asm: adr: no relocation modifier valid here");
   1627   } else if (mod != AA64_RELMOD_NONE && mod != AA64_RELMOD_PAGE &&
   1628              mod != AA64_RELMOD_GOT) {
   1629     asm_driver_panic(d,
   1630                      "asm: adrp: only @PAGE/@GOTPAGE (Mach-O) or :got: "
   1631                      "(ELF) valid here");
   1632   }
   1633   if (sym == OBJ_SYM_NONE)
   1634     asm_driver_panic(d, "asm: adr/adrp: symbol required");
   1635   AA64PCRelAdr f = {.op = is_adrp ? AA64_ADR_OP_ADRP : AA64_ADR_OP_ADR,
   1636                     .immlo = 0,
   1637                     .immhi = 0,
   1638                     .Rd = rd.num};
   1639   emit32(d, aa64_pcrel_adr_pack(f));
   1640   MCEmitter* mc = asm_driver_mc(d);
   1641   u32 ofs = mc_pos(mc) - 4;
   1642   RelocKind k = !is_adrp                 ? R_AARCH64_ADR_PREL_LO21
   1643                 : mod == AA64_RELMOD_GOT ? R_AARCH64_ADR_GOT_PAGE
   1644                                          : R_AARCH64_ADR_PREL_PG_HI21;
   1645   mc_emit_reloc_at(mc, asm_driver_cur_section(d), ofs, k, sym, off, 1, 0);
   1646 }
   1647 
   1648 /* ---- atomics / exclusive ----
   1649  *
   1650  * Every form here addresses a bare base register `[Xn]` (no offset, no
   1651  * index, no writeback).  parse_mem already rejects malformed shapes; we
   1652  * additionally reject any offset/index so `ldxr w0,[x1,#4]` is an error,
   1653  * matching llvm/gas. */
   1654 static AA64Mem parse_mem_bare(AsmDriver* d, const char* what) {
   1655   AA64Mem m = parse_mem(d);
   1656   if (m.has_offset || m.has_index || m.pre_index || m.post_index)
   1657     asm_driver_panic(d, "asm: %.*s: expected bare [Xn] address",
   1658                      SLICE_ARG(slice_from_cstr(what)));
   1659   return m;
   1660 }
   1661 
   1662 /* Map an access log2-size (0..3) onto the GPR width the operand register
   1663  * must have: byte/half/word use Wt (32-bit), dword uses Xt (64-bit). */
   1664 static void require_gpr_width(AsmDriver* d, AA64Reg r, u32 size,
   1665                               const char* what) {
   1666   reject_sp_reg(d, r, what);
   1667   u32 want64 = (size == 3u) ? 1u : 0u;
   1668   if ((u32)r.is64 != want64)
   1669     asm_driver_panic(d, "asm: %.*s: register width mismatch",
   1670                      SLICE_ARG(slice_from_cstr(what)));
   1671 }
   1672 
   1673 /* Load-exclusive / load-acquire: `<op> Wt|Xt, [Xn]`.
   1674  *   o2/o0 select the family member (see aa64_ldstex_pack).  size is the
   1675  *   access log2-size; Rs/Rt2 are fixed to 11111. */
   1676 static void p_ldex(AsmDriver* d, u32 size, u32 o2, u32 o0, const char* what) {
   1677   AA64Reg rt = parse_reg(d);
   1678   require_gpr_width(d, rt, size, what);
   1679   expect_comma(d, what);
   1680   AA64Mem m = parse_mem_bare(d, what);
   1681   emit32(d, aa64_ldstex_pack((AA64LdStEx){.size = size,
   1682                                           .o2 = o2,
   1683                                           .L = 1u,
   1684                                           .o1 = 0u,
   1685                                           .Rs = AA64_ZR,
   1686                                           .o0 = o0,
   1687                                           .Rt2 = AA64_ZR,
   1688                                           .Rn = m.base.num,
   1689                                           .Rt = rt.num}));
   1690 }
   1691 
   1692 /* Store-release without status: `stlr Wt|Xt, [Xn]` (o2=1, L=0, o0=1). */
   1693 static void p_stlr(AsmDriver* d, u32 size, const char* what) {
   1694   AA64Reg rt = parse_reg(d);
   1695   require_gpr_width(d, rt, size, what);
   1696   expect_comma(d, what);
   1697   AA64Mem m = parse_mem_bare(d, what);
   1698   emit32(d, aa64_ldstex_pack((AA64LdStEx){.size = size,
   1699                                           .o2 = 1u,
   1700                                           .L = 0u,
   1701                                           .o1 = 0u,
   1702                                           .Rs = AA64_ZR,
   1703                                           .o0 = 1u,
   1704                                           .Rt2 = AA64_ZR,
   1705                                           .Rn = m.base.num,
   1706                                           .Rt = rt.num}));
   1707 }
   1708 
   1709 /* Store-exclusive with status: `<op> Ws, Wt|Xt, [Xn]` (L=0).  Ws (the
   1710  * 32-bit status result) must be a W register and distinct from Rt/Rn. */
   1711 /* Store-exclusive constraint (ARM ARM): the status register Ws must differ
   1712  * from the stored value Rt and from the base Rn, else the result is
   1713  * UNPREDICTABLE.  The base is exempt when it is SP (reg #31 names SP, not the
   1714  * WZR the status reg would be).  CAS/LSE atomics do NOT share this rule. */
   1715 static void reject_stex_alias(AsmDriver* d, AA64Reg rs, AA64Reg rt, AA64Mem m,
   1716                               const char* what) {
   1717   if (rs.num == rt.num)
   1718     asm_driver_panic(d, "asm: %.*s: status reg cannot be the value reg",
   1719                      SLICE_ARG(slice_from_cstr(what)));
   1720   if (!m.base.is_sp && rs.num == m.base.num)
   1721     asm_driver_panic(d, "asm: %.*s: status reg cannot be the base reg",
   1722                      SLICE_ARG(slice_from_cstr(what)));
   1723 }
   1724 
   1725 static void p_stex(AsmDriver* d, u32 size, u32 o0, const char* what) {
   1726   AA64Reg rs = parse_reg(d);
   1727   reject_sp_reg(d, rs, what);
   1728   if (rs.is64)
   1729     asm_driver_panic(d, "asm: %.*s: status reg must be 32-bit",
   1730                      SLICE_ARG(slice_from_cstr(what)));
   1731   expect_comma(d, what);
   1732   AA64Reg rt = parse_reg(d);
   1733   require_gpr_width(d, rt, size, what);
   1734   expect_comma(d, what);
   1735   AA64Mem m = parse_mem_bare(d, what);
   1736   reject_stex_alias(d, rs, rt, m, what);
   1737   emit32(d, aa64_ldstex_pack((AA64LdStEx){.size = size,
   1738                                           .o2 = 0u,
   1739                                           .L = 0u,
   1740                                           .o1 = 0u,
   1741                                           .Rs = rs.num,
   1742                                           .o0 = o0,
   1743                                           .Rt2 = AA64_ZR,
   1744                                           .Rn = m.base.num,
   1745                                           .Rt = rt.num}));
   1746 }
   1747 
   1748 /* Compare-and-swap: `<op> Ws, Wt, [Xn]` / `<op> Xs, Xt, [Xn]`.  Rs and Rt
   1749  * share the operand width selected by `size` (word or dword). */
   1750 static void p_cas(AsmDriver* d, u32 size, u32 L, u32 o0, const char* what) {
   1751   AA64Reg rs = parse_reg(d);
   1752   require_gpr_width(d, rs, size, what);
   1753   expect_comma(d, what);
   1754   AA64Reg rt = parse_reg(d);
   1755   require_gpr_width(d, rt, size, what);
   1756   expect_comma(d, what);
   1757   AA64Mem m = parse_mem_bare(d, what);
   1758   emit32(d, aa64_cas_pack((AA64Cas){.size = size,
   1759                                     .L = L,
   1760                                     .Rs = rs.num,
   1761                                     .o0 = o0,
   1762                                     .Rn = m.base.num,
   1763                                     .Rt = rt.num}));
   1764 }
   1765 
   1766 /* LSE atomic memory op: `<op> Ws, Wt, [Xn]` / `<op> Xs, Xt, [Xn]`.
   1767  *   o3=1 selects SWP; otherwise opc names LDADD/LDCLR/LDEOR/LDSET. */
   1768 static void p_lse(AsmDriver* d, u32 size, u32 A, u32 R, u32 o3, u32 opc,
   1769                   const char* what) {
   1770   AA64Reg rs = parse_reg(d);
   1771   require_gpr_width(d, rs, size, what);
   1772   expect_comma(d, what);
   1773   AA64Reg rt = parse_reg(d);
   1774   require_gpr_width(d, rt, size, what);
   1775   expect_comma(d, what);
   1776   AA64Mem m = parse_mem_bare(d, what);
   1777   emit32(d, aa64_lse_atomic_pack((AA64LseAtomic){.size = size,
   1778                                                  .A = A,
   1779                                                  .R = R,
   1780                                                  .Rs = rs.num,
   1781                                                  .o3 = o3,
   1782                                                  .opc = opc,
   1783                                                  .Rn = m.base.num,
   1784                                                  .Rt = rt.num}));
   1785 }
   1786 
   1787 /* ---- mnemonic dispatch table ---- */
   1788 
   1789 typedef void (*P_Fn)(AsmDriver*);
   1790 
   1791 typedef struct AA64Mn {
   1792   const char* name;
   1793   P_Fn fn;
   1794 } AA64Mn;
   1795 
   1796 /* Wrapper functions for the discriminator-taking parsers, since the
   1797  * table holds a uniform P_Fn pointer.  Each wraps a single (fn, arg)
   1798  * tuple. */
   1799 static void p_addsub_add(AsmDriver* d) { p_addsub(d, /*is_sub=*/0, 0); }
   1800 static void p_addsub_adds(AsmDriver* d) { p_addsub(d, 0, 1); }
   1801 static void p_addsub_sub(AsmDriver* d) { p_addsub(d, 1, 0); }
   1802 static void p_addsub_subs(AsmDriver* d) { p_addsub(d, 1, 1); }
   1803 static void p_cmp_w(AsmDriver* d) { p_cmp(d, 0); }
   1804 static void p_cmn_w(AsmDriver* d) { p_cmp(d, 1); }
   1805 static void p_csel_(AsmDriver* d) { p_condsel(d, 0, 0, "csel"); }
   1806 static void p_csinc_(AsmDriver* d) { p_condsel(d, 0, 1, "csinc"); }
   1807 static void p_csinv_(AsmDriver* d) { p_condsel(d, 1, 0, "csinv"); }
   1808 static void p_csneg_(AsmDriver* d) { p_condsel(d, 1, 1, "csneg"); }
   1809 static void p_cset_(AsmDriver* d) { p_cset_like(d, 0, 1, "cset"); }
   1810 static void p_csetm_(AsmDriver* d) { p_cset_like(d, 1, 0, "csetm"); }
   1811 static void p_neg_w(AsmDriver* d) { p_neg(d, 0); }
   1812 static void p_negs_w(AsmDriver* d) { p_neg(d, 1); }
   1813 static void p_and_w(AsmDriver* d) { p_log_sr(d, AA64_LOG_AND_OPC, 0); }
   1814 static void p_bic_w(AsmDriver* d) { p_log_sr(d, AA64_LOG_AND_OPC, 1); }
   1815 static void p_orr_w(AsmDriver* d) { p_log_sr(d, AA64_LOG_ORR_OPC, 0); }
   1816 static void p_orn_w(AsmDriver* d) { p_log_sr(d, AA64_LOG_ORR_OPC, 1); }
   1817 static void p_eor_w(AsmDriver* d) { p_log_sr(d, AA64_LOG_EOR_OPC, 0); }
   1818 static void p_eon_w(AsmDriver* d) { p_log_sr(d, AA64_LOG_EOR_OPC, 1); }
   1819 static void p_ands_w(AsmDriver* d) { p_log_sr(d, AA64_LOG_ANDS_OPC, 0); }
   1820 static void p_bics_w(AsmDriver* d) { p_log_sr(d, AA64_LOG_ANDS_OPC, 1); }
   1821 static void p_madd(AsmDriver* d) { p_dp3(d, 0); }
   1822 static void p_msub(AsmDriver* d) { p_dp3(d, 1); }
   1823 static void p_mul_w(AsmDriver* d) { p_mul(d, 0); }
   1824 static void p_mneg_w(AsmDriver* d) { p_mul(d, 1); }
   1825 static void p_udiv_w(AsmDriver* d) { p_dp2(d, AA64_DP2_UDIV_OP); }
   1826 static void p_sdiv_w(AsmDriver* d) { p_dp2(d, AA64_DP2_SDIV_OP); }
   1827 static void p_lslv_w(AsmDriver* d) { p_dp2(d, AA64_DP2_LSLV_OP); }
   1828 static void p_lsrv_w(AsmDriver* d) { p_dp2(d, AA64_DP2_LSRV_OP); }
   1829 static void p_asrv_w(AsmDriver* d) { p_dp2(d, AA64_DP2_ASRV_OP); }
   1830 static void p_rorv_w(AsmDriver* d) { p_dp2(d, AA64_DP2_RORV_OP); }
   1831 static void p_lsl_(AsmDriver* d) { p_shift(d, 0); }
   1832 static void p_lsr_(AsmDriver* d) { p_shift(d, 1); }
   1833 static void p_asr_(AsmDriver* d) { p_shift(d, 2); }
   1834 static void p_b_(AsmDriver* d) { p_b(d, 0); }
   1835 static void p_bl_(AsmDriver* d) { p_b(d, 1); }
   1836 static void p_cbz_(AsmDriver* d) { p_cbz(d, 0); }
   1837 static void p_cbnz_(AsmDriver* d) { p_cbz(d, 1); }
   1838 static void p_movz_(AsmDriver* d) { p_movwide(d, AA64_MOVZ_OPC); }
   1839 static void p_movn_(AsmDriver* d) { p_movwide(d, AA64_MOVN_OPC); }
   1840 static void p_movk_(AsmDriver* d) { p_movwide(d, AA64_MOVK_OPC); }
   1841 static void p_svc_(AsmDriver* d) { p_except(d, 0); }
   1842 static void p_brk_(AsmDriver* d) { p_except(d, 1); }
   1843 static void p_hlt_(AsmDriver* d) { p_except(d, 2); }
   1844 static void p_ldr_(AsmDriver* d) { p_ldr_str(d, 1); }
   1845 static void p_str_(AsmDriver* d) { p_ldr_str(d, 0); }
   1846 static void p_ldur_(AsmDriver* d) { p_ldur_stur(d, 1, -1, 0); }
   1847 static void p_stur_(AsmDriver* d) { p_ldur_stur(d, 0, -1, 0); }
   1848 static void p_ldurb(AsmDriver* d) { p_ldur_stur(d, 1, 0, 0); }
   1849 static void p_sturb(AsmDriver* d) { p_ldur_stur(d, 0, 0, 0); }
   1850 static void p_ldurh(AsmDriver* d) { p_ldur_stur(d, 1, 1, 0); }
   1851 static void p_sturh(AsmDriver* d) { p_ldur_stur(d, 0, 1, 0); }
   1852 static void p_ldursb(AsmDriver* d) { p_ldur_stur(d, 1, 0, 1); }
   1853 static void p_ldursh(AsmDriver* d) { p_ldur_stur(d, 1, 1, 1); }
   1854 static void p_ldursw(AsmDriver* d) { p_ldur_stur(d, 1, 2, 1); }
   1855 static void p_ldp_(AsmDriver* d) { p_ldp_stp(d, 1); }
   1856 static void p_stp_(AsmDriver* d) { p_ldp_stp(d, 0); }
   1857 static void p_adr_(AsmDriver* d) { p_adr(d, 0); }
   1858 static void p_adrp_(AsmDriver* d) { p_adr(d, 1); }
   1859 
   1860 /* b.cond family.  cond codes follow the standard ARMv8 numbering. */
   1861 static void p_b_eq(AsmDriver* d) { p_b_cond(d, 0); }
   1862 static void p_b_ne(AsmDriver* d) { p_b_cond(d, 1); }
   1863 static void p_b_cs(AsmDriver* d) { p_b_cond(d, 2); }
   1864 static void p_b_hs(AsmDriver* d) { p_b_cond(d, 2); }
   1865 static void p_b_cc(AsmDriver* d) { p_b_cond(d, 3); }
   1866 static void p_b_lo(AsmDriver* d) { p_b_cond(d, 3); }
   1867 static void p_b_mi(AsmDriver* d) { p_b_cond(d, 4); }
   1868 static void p_b_pl(AsmDriver* d) { p_b_cond(d, 5); }
   1869 static void p_b_vs(AsmDriver* d) { p_b_cond(d, 6); }
   1870 static void p_b_vc(AsmDriver* d) { p_b_cond(d, 7); }
   1871 static void p_b_hi(AsmDriver* d) { p_b_cond(d, 8); }
   1872 static void p_b_ls(AsmDriver* d) { p_b_cond(d, 9); }
   1873 static void p_b_ge(AsmDriver* d) { p_b_cond(d, 10); }
   1874 static void p_b_lt(AsmDriver* d) { p_b_cond(d, 11); }
   1875 static void p_b_gt(AsmDriver* d) { p_b_cond(d, 12); }
   1876 static void p_b_le(AsmDriver* d) { p_b_cond(d, 13); }
   1877 static void p_b_al(AsmDriver* d) { p_b_cond(d, 14); }
   1878 
   1879 /* ---- Scalar floating-point ----
   1880  * Sn/Dn/Hn are the single/double/half views of the FP register file; the
   1881  * 2-bit ftype (0=s,1=d,3=h) drives both the encoding and the operand text. */
   1882 static int parse_fp_scalar_from_ident(AsmDriver* d, Sym ident, u32* num,
   1883                                       u32* ftype) {
   1884   Slice sl = pool_slice(asm_driver_pool(d), ident);
   1885   const char* p = sl.s;
   1886   size_t n = sl.len;
   1887   u32 ft, r = 0;
   1888   size_t i;
   1889   if (!p || n < 2) return 0;
   1890   if (p[0] == 's' || p[0] == 'S')
   1891     ft = 0u;
   1892   else if (p[0] == 'd' || p[0] == 'D')
   1893     ft = 1u;
   1894   else if (p[0] == 'h' || p[0] == 'H')
   1895     ft = 3u;
   1896   else
   1897     return 0;
   1898   for (i = 1; i < n; ++i) {
   1899     char c = p[i];
   1900     if (c < '0' || c > '9') return 0;
   1901     r = r * 10u + (u32)(c - '0');
   1902     if (r > 31u) return 0;
   1903   }
   1904   *num = r;
   1905   *ftype = ft;
   1906   return 1;
   1907 }
   1908 
   1909 static void parse_fp_scalar(AsmDriver* d, u32* num, u32* ftype) {
   1910   AsmTok t = asm_driver_next(d);
   1911   if (t.kind != ASM_TOK_IDENT ||
   1912       !parse_fp_scalar_from_ident(d, t.v.ident, num, ftype))
   1913     asm_driver_panic(d, "asm: expected FP register (Sn/Dn/Hn)");
   1914 }
   1915 
   1916 /* A register operand that may be either a GPR or a scalar FP register — used
   1917  * by fmov, whose three forms differ only by operand class. */
   1918 typedef struct FpOrGpr {
   1919   int is_fp;
   1920   u32 num;
   1921   u32 ftype; /* when is_fp */
   1922   int is64;  /* when !is_fp */
   1923 } FpOrGpr;
   1924 
   1925 static FpOrGpr parse_fp_or_gpr(AsmDriver* d) {
   1926   AsmTok t = asm_driver_next(d);
   1927   FpOrGpr r;
   1928   AA64Reg g;
   1929   memset(&r, 0, sizeof r);
   1930   if (t.kind == ASM_TOK_IDENT &&
   1931       parse_fp_scalar_from_ident(d, t.v.ident, &r.num, &r.ftype)) {
   1932     r.is_fp = 1;
   1933     return r;
   1934   }
   1935   memset(&g, 0, sizeof g);
   1936   if (t.kind == ASM_TOK_IDENT && parse_reg_from_ident(d, t.v.ident, &g)) {
   1937     r.is_fp = 0;
   1938     r.num = g.num;
   1939     r.is64 = (int)g.is64;
   1940     return r;
   1941   }
   1942   asm_driver_panic(d, "asm: fmov: expected register");
   1943   return r; /* unreachable */
   1944 }
   1945 
   1946 static void p_fp_dp2(AsmDriver* d, u32 op) {
   1947   u32 rd, rn, rm, ftd, ftn, ftm;
   1948   parse_fp_scalar(d, &rd, &ftd);
   1949   expect_comma(d, "fp");
   1950   parse_fp_scalar(d, &rn, &ftn);
   1951   expect_comma(d, "fp");
   1952   parse_fp_scalar(d, &rm, &ftm);
   1953   if (ftd != ftn || ftd != ftm)
   1954     asm_driver_panic(d, "asm: fp: operand type mismatch");
   1955   emit32(d, aa64_fp_dp2(ftd, op, rd, rn, rm));
   1956 }
   1957 static void p_fp_dp1(AsmDriver* d, u32 op) {
   1958   u32 rd, rn, ftd, ftn;
   1959   parse_fp_scalar(d, &rd, &ftd);
   1960   expect_comma(d, "fp");
   1961   parse_fp_scalar(d, &rn, &ftn);
   1962   if (ftd != ftn) asm_driver_panic(d, "asm: fp: operand type mismatch");
   1963   emit32(d, aa64_fp_dp1(ftd, op, rd, rn));
   1964 }
   1965 static void p_fadd(AsmDriver* d) { p_fp_dp2(d, AA64_FP_DP2_FADD); }
   1966 static void p_fsub(AsmDriver* d) { p_fp_dp2(d, AA64_FP_DP2_FSUB); }
   1967 static void p_fmul(AsmDriver* d) { p_fp_dp2(d, AA64_FP_DP2_FMUL); }
   1968 static void p_fdiv(AsmDriver* d) { p_fp_dp2(d, AA64_FP_DP2_FDIV); }
   1969 static void p_fmax(AsmDriver* d) { p_fp_dp2(d, AA64_FP_DP2_FMAX); }
   1970 static void p_fmin(AsmDriver* d) { p_fp_dp2(d, AA64_FP_DP2_FMIN); }
   1971 static void p_fnmul(AsmDriver* d) { p_fp_dp2(d, AA64_FP_DP2_FNMUL); }
   1972 static void p_fneg(AsmDriver* d) { p_fp_dp1(d, AA64_FP_DP1_FNEG); }
   1973 static void p_fabs(AsmDriver* d) { p_fp_dp1(d, AA64_FP_DP1_FABS); }
   1974 static void p_fsqrt(AsmDriver* d) { p_fp_dp1(d, AA64_FP_DP1_FSQRT); }
   1975 
   1976 static void p_fcmp(AsmDriver* d) {
   1977   u32 rn, rm, ftn, ftm;
   1978   parse_fp_scalar(d, &rn, &ftn);
   1979   expect_comma(d, "fcmp");
   1980   parse_fp_scalar(d, &rm, &ftm);
   1981   if (ftn != ftm) asm_driver_panic(d, "asm: fcmp: operand type mismatch");
   1982   emit32(d, aa64_fcmp_reg(ftn, rn, rm));
   1983 }
   1984 static void p_fcvt(AsmDriver* d) {
   1985   u32 rd, rn, ftd, ftn;
   1986   parse_fp_scalar(d, &rd, &ftd);
   1987   expect_comma(d, "fcvt");
   1988   parse_fp_scalar(d, &rn, &ftn);
   1989   emit32(d, aa64_fcvt_prec(ftn /*src*/, ftd /*dst*/, rd, rn));
   1990 }
   1991 /* scvtf/ucvtf: FP dst, GPR src. */
   1992 static void p_cvtf(AsmDriver* d, u32 opcode) {
   1993   u32 fd, ft;
   1994   AA64Reg rn;
   1995   parse_fp_scalar(d, &fd, &ft);
   1996   expect_comma(d, "cvtf");
   1997   rn = parse_reg(d);
   1998   emit32(d, aa64_fp_int_cvt((u32)rn.is64, ft, opcode, fd, rn.num));
   1999 }
   2000 /* fcvtzs/fcvtzu: GPR dst, FP src. */
   2001 static void p_fcvtz(AsmDriver* d, u32 opcode) {
   2002   AA64Reg rd;
   2003   u32 fn, ft;
   2004   rd = parse_reg(d);
   2005   expect_comma(d, "fcvtz");
   2006   parse_fp_scalar(d, &fn, &ft);
   2007   emit32(d, aa64_fp_int_cvt((u32)rd.is64, ft, opcode, rd.num, fn));
   2008 }
   2009 static void p_scvtf(AsmDriver* d) { p_cvtf(d, AA64_FP_ICVT_SCVTF); }
   2010 static void p_ucvtf(AsmDriver* d) { p_cvtf(d, AA64_FP_ICVT_UCVTF); }
   2011 static void p_fcvtzs(AsmDriver* d) { p_fcvtz(d, AA64_FP_ICVT_FCVTZS); }
   2012 static void p_fcvtzu(AsmDriver* d) { p_fcvtz(d, AA64_FP_ICVT_FCVTZU); }
   2013 
   2014 /* Data-processing (1 source): clz/rbit/rev16, and rev (whose opcode2 is the
   2015  * width: 2 for 32-bit, 3 for 64-bit). */
   2016 static void p_dp1_op(AsmDriver* d, u32 opcode2) {
   2017   AA64Reg rd = parse_reg(d);
   2018   AA64Reg rn;
   2019   expect_comma(d, "dp1");
   2020   rn = parse_reg(d);
   2021   if (rd.is64 != rn.is64) asm_driver_panic(d, "asm: dp1: width mismatch");
   2022   emit32(d, aa64_dp1(rd.is64, opcode2, rd.num, rn.num));
   2023 }
   2024 static void p_clz(AsmDriver* d) { p_dp1_op(d, AA64_DP1_CLZ); }
   2025 static void p_rbit(AsmDriver* d) { p_dp1_op(d, AA64_DP1_RBIT); }
   2026 static void p_rev16(AsmDriver* d) { p_dp1_op(d, AA64_DP1_REV16); }
   2027 static void p_rev(AsmDriver* d) {
   2028   AA64Reg rd = parse_reg(d);
   2029   AA64Reg rn;
   2030   expect_comma(d, "rev");
   2031   rn = parse_reg(d);
   2032   if (rd.is64 != rn.is64) asm_driver_panic(d, "asm: rev: width mismatch");
   2033   emit32(d, aa64_dp1(rd.is64, rd.is64 ? AA64_DP1_REV64 : AA64_DP1_REV32, rd.num,
   2034                      rn.num));
   2035 }
   2036 
   2037 /* Bitfield move (opc: 0=sbfm, 1=bfm, 2=ubfm): Rd, Rn, #immr, #imms. */
   2038 static void p_bitfield(AsmDriver* d, u32 opc) {
   2039   AA64Reg rd = parse_reg(d);
   2040   AA64Reg rn;
   2041   i64 immr, imms;
   2042   expect_comma(d, "bitfield");
   2043   rn = parse_reg(d);
   2044   expect_comma(d, "bitfield");
   2045   immr = parse_imm_const(d);
   2046   expect_comma(d, "bitfield");
   2047   imms = parse_imm_const(d);
   2048   if (rd.is64 != rn.is64) asm_driver_panic(d, "asm: bitfield: width mismatch");
   2049   emit32(d, aa64_bitfield(rd.is64, opc, (u32)immr, (u32)imms, rd.num, rn.num));
   2050 }
   2051 static void p_sbfm(AsmDriver* d) { p_bitfield(d, 0u); }
   2052 static void p_bfm(AsmDriver* d) { p_bitfield(d, 1u); }
   2053 static void p_ubfm(AsmDriver* d) { p_bitfield(d, 2u); }
   2054 
   2055 static void p_bfx(AsmDriver* d, u32 opc, const char* what) {
   2056   AA64Reg rd = parse_reg(d);
   2057   AA64Reg rn;
   2058   i64 lsb, width;
   2059   u32 reg_width;
   2060   expect_comma(d, what);
   2061   rn = parse_reg(d);
   2062   reject_sp_reg(d, rd, what);
   2063   reject_sp_reg(d, rn, what);
   2064   if (rd.is64 != rn.is64)
   2065     asm_driver_panic(d, "asm: %.*s: width mismatch",
   2066                      SLICE_ARG(slice_from_cstr(what)));
   2067   expect_comma(d, what);
   2068   lsb = parse_imm_const(d);
   2069   expect_comma(d, what);
   2070   width = parse_imm_const(d);
   2071   reg_width = rd.is64 ? 64u : 32u;
   2072   if (lsb < 0 || width <= 0 || (u64)lsb >= reg_width ||
   2073       (u64)width > (u64)reg_width - (u64)lsb) {
   2074     asm_driver_panic(d, "asm: %.*s: bit range out of bounds",
   2075                      SLICE_ARG(slice_from_cstr(what)));
   2076   }
   2077   emit32(d, aa64_bitfield(rd.is64, opc, (u32)lsb, (u32)(lsb + width - 1),
   2078                           rd.num, rn.num));
   2079 }
   2080 
   2081 static void p_sbfx(AsmDriver* d) { p_bfx(d, 0u, "sbfx"); }
   2082 static void p_ubfx(AsmDriver* d) { p_bfx(d, 2u, "ubfx"); }
   2083 
   2084 static void p_sxt(AsmDriver* d, u32 bits, const char* what) {
   2085   AA64Reg rd = parse_reg(d);
   2086   AA64Reg rn;
   2087   expect_comma(d, what);
   2088   rn = parse_reg(d);
   2089   reject_sp_reg(d, rd, what);
   2090   reject_sp_reg(d, rn, what);
   2091   if (rn.is64)
   2092     asm_driver_panic(d, "asm: %.*s: source must be a W register",
   2093                      SLICE_ARG(slice_from_cstr(what)));
   2094   if (bits == 32u && !rd.is64)
   2095     asm_driver_panic(d, "asm: sxtw: destination must be an X register");
   2096   emit32(d, aa64_bitfield(rd.is64, 0u, 0u, bits - 1u, rd.num, rn.num));
   2097 }
   2098 
   2099 static void p_uxt(AsmDriver* d, u32 bits, const char* what) {
   2100   AA64Reg rd = parse_reg(d);
   2101   AA64Reg rn;
   2102   u32 sf;
   2103   expect_comma(d, what);
   2104   rn = parse_reg(d);
   2105   reject_sp_reg(d, rd, what);
   2106   reject_sp_reg(d, rn, what);
   2107   if (rn.is64)
   2108     asm_driver_panic(d, "asm: %.*s: source must be a W register",
   2109                      SLICE_ARG(slice_from_cstr(what)));
   2110   if (bits == 32u && !rd.is64)
   2111     asm_driver_panic(d, "asm: uxtw: destination must be an X register");
   2112   sf = bits == 32u ? 1u : 0u;
   2113   emit32(d, aa64_bitfield(sf, 2u, 0u, bits - 1u, rd.num, rn.num));
   2114 }
   2115 
   2116 static void p_sxtb(AsmDriver* d) { p_sxt(d, 8u, "sxtb"); }
   2117 static void p_sxth(AsmDriver* d) { p_sxt(d, 16u, "sxth"); }
   2118 static void p_sxtw(AsmDriver* d) { p_sxt(d, 32u, "sxtw"); }
   2119 static void p_uxtb(AsmDriver* d) { p_uxt(d, 8u, "uxtb"); }
   2120 static void p_uxth(AsmDriver* d) { p_uxt(d, 16u, "uxth"); }
   2121 static void p_uxtw(AsmDriver* d) { p_uxt(d, 32u, "uxtw"); }
   2122 
   2123 /* fmov: Vd,Vn (FP reg move) | Rd,Vn (fp->gpr) | Vd,Rn (gpr->fp). */
   2124 static void p_fmov(AsmDriver* d) {
   2125   FpOrGpr a = parse_fp_or_gpr(d);
   2126   FpOrGpr b;
   2127   expect_comma(d, "fmov");
   2128   b = parse_fp_or_gpr(d);
   2129   if (a.is_fp && b.is_fp) {
   2130     if (a.ftype != b.ftype)
   2131       asm_driver_panic(d, "asm: fmov: operand type mismatch");
   2132     emit32(d, aa64_fp_dp1(a.ftype, AA64_FP_DP1_FMOV, a.num, b.num));
   2133   } else if (!a.is_fp && b.is_fp) {
   2134     emit32(d, aa64_fp_int_cvt((u32)a.is64, b.ftype, AA64_FP_ICVT_FMOV_TO_GPR,
   2135                               a.num, b.num));
   2136   } else if (a.is_fp && !b.is_fp) {
   2137     emit32(d, aa64_fp_int_cvt((u32)b.is64, a.ftype, AA64_FP_ICVT_FMOV_TO_FP,
   2138                               a.num, b.num));
   2139   } else {
   2140     asm_driver_panic(d, "asm: fmov: gpr,gpr form not supported (use mov)");
   2141   }
   2142 }
   2143 
   2144 /* ---- atomics / exclusive wrappers ----
   2145  *
   2146  * Access log2-sizes: byte=0, half=1, word=2, dword=3.  The w/x variants
   2147  * share a mnemonic stem (e.g. `ldxr`) and pick the size from the operand
   2148  * register width — the encoders key on the explicit size, so a width-
   2149  * sensing wrapper peeks the operand register before dispatching. */
   2150 #define AA64_ATOMIC_SIZE_B 0u
   2151 #define AA64_ATOMIC_SIZE_H 1u
   2152 #define AA64_ATOMIC_SIZE_W 2u
   2153 #define AA64_ATOMIC_SIZE_X 3u
   2154 
   2155 /* Load-exclusive family: o2,o0 select ldxr/ldaxr/ldar. */
   2156 #define DEF_LDEX(fn, sz, o2, o0, name) \
   2157   static void fn(AsmDriver* d) { p_ldex(d, sz, o2, o0, name); }
   2158 /* ldxr / ldxrb / ldxrh: o2=0 o0=0.  The non-b/h stem derives size from
   2159  * the register width, so we route it through a width-sensing wrapper. */
   2160 static void p_ldxr_wx(AsmDriver* d) {
   2161   /* Peek the destination register to choose word vs dword size. */
   2162   AsmTok t = asm_driver_peek(d);
   2163   AA64Reg r;
   2164   memset(&r, 0, sizeof r);
   2165   if (t.kind != ASM_TOK_IDENT || !parse_reg_from_ident(d, t.v.ident, &r))
   2166     asm_driver_panic(d, "asm: ldxr: expected register");
   2167   p_ldex(d, r.is64 ? AA64_ATOMIC_SIZE_X : AA64_ATOMIC_SIZE_W, 0u, 0u, "ldxr");
   2168 }
   2169 DEF_LDEX(p_ldxrb, AA64_ATOMIC_SIZE_B, 0u, 0u, "ldxrb")
   2170 DEF_LDEX(p_ldxrh, AA64_ATOMIC_SIZE_H, 0u, 0u, "ldxrh")
   2171 static void p_ldaxr_wx(AsmDriver* d) {
   2172   AsmTok t = asm_driver_peek(d);
   2173   AA64Reg r;
   2174   memset(&r, 0, sizeof r);
   2175   if (t.kind != ASM_TOK_IDENT || !parse_reg_from_ident(d, t.v.ident, &r))
   2176     asm_driver_panic(d, "asm: ldaxr: expected register");
   2177   p_ldex(d, r.is64 ? AA64_ATOMIC_SIZE_X : AA64_ATOMIC_SIZE_W, 0u, 1u, "ldaxr");
   2178 }
   2179 DEF_LDEX(p_ldaxrb, AA64_ATOMIC_SIZE_B, 0u, 1u, "ldaxrb")
   2180 DEF_LDEX(p_ldaxrh, AA64_ATOMIC_SIZE_H, 0u, 1u, "ldaxrh")
   2181 static void p_ldar_wx(AsmDriver* d) {
   2182   AsmTok t = asm_driver_peek(d);
   2183   AA64Reg r;
   2184   memset(&r, 0, sizeof r);
   2185   if (t.kind != ASM_TOK_IDENT || !parse_reg_from_ident(d, t.v.ident, &r))
   2186     asm_driver_panic(d, "asm: ldar: expected register");
   2187   p_ldex(d, r.is64 ? AA64_ATOMIC_SIZE_X : AA64_ATOMIC_SIZE_W, 1u, 1u, "ldar");
   2188 }
   2189 DEF_LDEX(p_ldarb, AA64_ATOMIC_SIZE_B, 1u, 1u, "ldarb")
   2190 DEF_LDEX(p_ldarh, AA64_ATOMIC_SIZE_H, 1u, 1u, "ldarh")
   2191 
   2192 /* stlr (no status): width-driven for the non-b/h stem. */
   2193 static void p_stlr_wx(AsmDriver* d) {
   2194   AsmTok t = asm_driver_peek(d);
   2195   AA64Reg r;
   2196   memset(&r, 0, sizeof r);
   2197   if (t.kind != ASM_TOK_IDENT || !parse_reg_from_ident(d, t.v.ident, &r))
   2198     asm_driver_panic(d, "asm: stlr: expected register");
   2199   p_stlr(d, r.is64 ? AA64_ATOMIC_SIZE_X : AA64_ATOMIC_SIZE_W, "stlr");
   2200 }
   2201 static void p_stlrb_(AsmDriver* d) { p_stlr(d, AA64_ATOMIC_SIZE_B, "stlrb"); }
   2202 static void p_stlrh_(AsmDriver* d) { p_stlr(d, AA64_ATOMIC_SIZE_H, "stlrh"); }
   2203 
   2204 /* Store-exclusive family: o0 selects stxr vs stlxr.  Status reg is always
   2205  * 32-bit; the stored value reg drives the size for the non-b/h stem. */
   2206 static void p_stxr_wx(AsmDriver* d) {
   2207   AA64Reg rs = parse_reg(d);
   2208   reject_sp_reg(d, rs, "stxr");
   2209   if (rs.is64) asm_driver_panic(d, "asm: stxr: status reg must be 32-bit");
   2210   expect_comma(d, "stxr");
   2211   AsmTok t = asm_driver_peek(d);
   2212   AA64Reg rt;
   2213   memset(&rt, 0, sizeof rt);
   2214   if (t.kind != ASM_TOK_IDENT || !parse_reg_from_ident(d, t.v.ident, &rt))
   2215     asm_driver_panic(d, "asm: stxr: expected value register");
   2216   u32 size = rt.is64 ? AA64_ATOMIC_SIZE_X : AA64_ATOMIC_SIZE_W;
   2217   rt = parse_reg(d);
   2218   require_gpr_width(d, rt, size, "stxr");
   2219   expect_comma(d, "stxr");
   2220   AA64Mem m = parse_mem_bare(d, "stxr");
   2221   reject_stex_alias(d, rs, rt, m, "stxr");
   2222   emit32(d, aa64_ldstex_pack((AA64LdStEx){.size = size,
   2223                                           .o2 = 0u,
   2224                                           .L = 0u,
   2225                                           .o1 = 0u,
   2226                                           .Rs = rs.num,
   2227                                           .o0 = 0u,
   2228                                           .Rt2 = AA64_ZR,
   2229                                           .Rn = m.base.num,
   2230                                           .Rt = rt.num}));
   2231 }
   2232 static void p_stlxr_wx(AsmDriver* d) {
   2233   AA64Reg rs = parse_reg(d);
   2234   reject_sp_reg(d, rs, "stlxr");
   2235   if (rs.is64) asm_driver_panic(d, "asm: stlxr: status reg must be 32-bit");
   2236   expect_comma(d, "stlxr");
   2237   AsmTok t = asm_driver_peek(d);
   2238   AA64Reg rt;
   2239   memset(&rt, 0, sizeof rt);
   2240   if (t.kind != ASM_TOK_IDENT || !parse_reg_from_ident(d, t.v.ident, &rt))
   2241     asm_driver_panic(d, "asm: stlxr: expected value register");
   2242   u32 size = rt.is64 ? AA64_ATOMIC_SIZE_X : AA64_ATOMIC_SIZE_W;
   2243   rt = parse_reg(d);
   2244   require_gpr_width(d, rt, size, "stlxr");
   2245   expect_comma(d, "stlxr");
   2246   AA64Mem m = parse_mem_bare(d, "stlxr");
   2247   reject_stex_alias(d, rs, rt, m, "stlxr");
   2248   emit32(d, aa64_ldstex_pack((AA64LdStEx){.size = size,
   2249                                           .o2 = 0u,
   2250                                           .L = 0u,
   2251                                           .o1 = 0u,
   2252                                           .Rs = rs.num,
   2253                                           .o0 = 1u,
   2254                                           .Rt2 = AA64_ZR,
   2255                                           .Rn = m.base.num,
   2256                                           .Rt = rt.num}));
   2257 }
   2258 static void p_stxrb_(AsmDriver* d) {
   2259   p_stex(d, AA64_ATOMIC_SIZE_B, 0u, "stxrb");
   2260 }
   2261 static void p_stxrh_(AsmDriver* d) {
   2262   p_stex(d, AA64_ATOMIC_SIZE_H, 0u, "stxrh");
   2263 }
   2264 static void p_stlxrb_(AsmDriver* d) {
   2265   p_stex(d, AA64_ATOMIC_SIZE_B, 1u, "stlxrb");
   2266 }
   2267 static void p_stlxrh_(AsmDriver* d) {
   2268   p_stex(d, AA64_ATOMIC_SIZE_H, 1u, "stlxrh");
   2269 }
   2270 
   2271 /* CAS family: width-driven for the non-b/h stems (Rs/Rt are same width). */
   2272 #define DEF_CAS(fn, L, o0, name)                                             \
   2273   static void fn##_wx(AsmDriver* d) {                                        \
   2274     AsmTok t = asm_driver_peek(d);                                           \
   2275     AA64Reg r;                                                               \
   2276     memset(&r, 0, sizeof r);                                                 \
   2277     if (t.kind != ASM_TOK_IDENT || !parse_reg_from_ident(d, t.v.ident, &r))  \
   2278       asm_driver_panic(d, "asm: " name ": expected register");               \
   2279     p_cas(d, r.is64 ? AA64_ATOMIC_SIZE_X : AA64_ATOMIC_SIZE_W, L, o0, name); \
   2280   }                                                                          \
   2281   static void fn##b(AsmDriver* d) {                                          \
   2282     p_cas(d, AA64_ATOMIC_SIZE_B, L, o0, name "b");                           \
   2283   }                                                                          \
   2284   static void fn##h(AsmDriver* d) {                                          \
   2285     p_cas(d, AA64_ATOMIC_SIZE_H, L, o0, name "h");                           \
   2286   }
   2287 DEF_CAS(p_cas, 0u, 0u, "cas")
   2288 DEF_CAS(p_casa, 1u, 0u, "casa")
   2289 DEF_CAS(p_casl, 0u, 1u, "casl")
   2290 DEF_CAS(p_casal, 1u, 1u, "casal")
   2291 
   2292 /* LSE atomic family: A/R from the suffix, o3/opc from the stem.  Each
   2293  * mnemonic generates a width-driven stem plus b/h wrappers. */
   2294 #define DEF_LSE(fn, A, R, o3, opc, name)                                      \
   2295   static void fn##_wx(AsmDriver* d) {                                         \
   2296     AsmTok t = asm_driver_peek(d);                                            \
   2297     AA64Reg r;                                                                \
   2298     memset(&r, 0, sizeof r);                                                  \
   2299     if (t.kind != ASM_TOK_IDENT || !parse_reg_from_ident(d, t.v.ident, &r))   \
   2300       asm_driver_panic(d, "asm: " name ": expected register");                \
   2301     p_lse(d, r.is64 ? AA64_ATOMIC_SIZE_X : AA64_ATOMIC_SIZE_W, A, R, o3, opc, \
   2302           name);                                                              \
   2303   }                                                                           \
   2304   static void fn##b(AsmDriver* d) {                                           \
   2305     p_lse(d, AA64_ATOMIC_SIZE_B, A, R, o3, opc, name "b");                    \
   2306   }                                                                           \
   2307   static void fn##h(AsmDriver* d) {                                           \
   2308     p_lse(d, AA64_ATOMIC_SIZE_H, A, R, o3, opc, name "h");                    \
   2309   }
   2310 /* SWP (o3=1, opc=000). */
   2311 DEF_LSE(p_swp, 0u, 0u, 1u, AA64_LSE_OPC_SWP, "swp")
   2312 DEF_LSE(p_swpa, 1u, 0u, 1u, AA64_LSE_OPC_SWP, "swpa")
   2313 DEF_LSE(p_swpl, 0u, 1u, 1u, AA64_LSE_OPC_SWP, "swpl")
   2314 DEF_LSE(p_swpal, 1u, 1u, 1u, AA64_LSE_OPC_SWP, "swpal")
   2315 /* LDADD. */
   2316 DEF_LSE(p_ldadd, 0u, 0u, 0u, AA64_LSE_OPC_LDADD, "ldadd")
   2317 DEF_LSE(p_ldadda, 1u, 0u, 0u, AA64_LSE_OPC_LDADD, "ldadda")
   2318 DEF_LSE(p_ldaddl, 0u, 1u, 0u, AA64_LSE_OPC_LDADD, "ldaddl")
   2319 DEF_LSE(p_ldaddal, 1u, 1u, 0u, AA64_LSE_OPC_LDADD, "ldaddal")
   2320 /* LDCLR. */
   2321 DEF_LSE(p_ldclr, 0u, 0u, 0u, AA64_LSE_OPC_LDCLR, "ldclr")
   2322 DEF_LSE(p_ldclra, 1u, 0u, 0u, AA64_LSE_OPC_LDCLR, "ldclra")
   2323 DEF_LSE(p_ldclrl, 0u, 1u, 0u, AA64_LSE_OPC_LDCLR, "ldclrl")
   2324 DEF_LSE(p_ldclral, 1u, 1u, 0u, AA64_LSE_OPC_LDCLR, "ldclral")
   2325 /* LDEOR. */
   2326 DEF_LSE(p_ldeor, 0u, 0u, 0u, AA64_LSE_OPC_LDEOR, "ldeor")
   2327 DEF_LSE(p_ldeora, 1u, 0u, 0u, AA64_LSE_OPC_LDEOR, "ldeora")
   2328 DEF_LSE(p_ldeorl, 0u, 1u, 0u, AA64_LSE_OPC_LDEOR, "ldeorl")
   2329 DEF_LSE(p_ldeoral, 1u, 1u, 0u, AA64_LSE_OPC_LDEOR, "ldeoral")
   2330 /* LDSET. */
   2331 DEF_LSE(p_ldset, 0u, 0u, 0u, AA64_LSE_OPC_LDSET, "ldset")
   2332 DEF_LSE(p_ldseta, 1u, 0u, 0u, AA64_LSE_OPC_LDSET, "ldseta")
   2333 DEF_LSE(p_ldsetl, 0u, 1u, 0u, AA64_LSE_OPC_LDSET, "ldsetl")
   2334 DEF_LSE(p_ldsetal, 1u, 1u, 0u, AA64_LSE_OPC_LDSET, "ldsetal")
   2335 
   2336 static const AA64Mn kTable[] = {
   2337     {"fadd", p_fadd},
   2338     {"fsub", p_fsub},
   2339     {"fmul", p_fmul},
   2340     {"fdiv", p_fdiv},
   2341     {"fmax", p_fmax},
   2342     {"fmin", p_fmin},
   2343     {"fnmul", p_fnmul},
   2344     {"fneg", p_fneg},
   2345     {"fabs", p_fabs},
   2346     {"fsqrt", p_fsqrt},
   2347     {"fmov", p_fmov},
   2348     {"fcmp", p_fcmp},
   2349     {"fcvt", p_fcvt},
   2350     {"scvtf", p_scvtf},
   2351     {"ucvtf", p_ucvtf},
   2352     {"fcvtzs", p_fcvtzs},
   2353     {"fcvtzu", p_fcvtzu},
   2354     {"clz", p_clz},
   2355     {"rbit", p_rbit},
   2356     {"rev", p_rev},
   2357     {"rev16", p_rev16},
   2358     {"sbfm", p_sbfm},
   2359     {"ubfm", p_ubfm},
   2360     {"bfm", p_bfm},
   2361     {"sbfx", p_sbfx},
   2362     {"ubfx", p_ubfx},
   2363     {"sxtb", p_sxtb},
   2364     {"sxth", p_sxth},
   2365     {"sxtw", p_sxtw},
   2366     {"uxtb", p_uxtb},
   2367     {"uxth", p_uxth},
   2368     {"uxtw", p_uxtw},
   2369     {"nop", p_nop},
   2370     {"dmb", p_dmb},
   2371     {"dsb", p_dsb},
   2372     {"isb", p_isb},
   2373     {"clrex", p_clrex},
   2374     {"ret", p_ret},
   2375     {"br", p_br},
   2376     {"blr", p_blr},
   2377     {"mov", p_mov},
   2378     {"mvn", p_mvn},
   2379     {"movz", p_movz_},
   2380     {"movn", p_movn_},
   2381     {"movk", p_movk_},
   2382     {"add", p_addsub_add},
   2383     {"adds", p_addsub_adds},
   2384     {"sub", p_addsub_sub},
   2385     {"subs", p_addsub_subs},
   2386     {"cmp", p_cmp_w},
   2387     {"cmn", p_cmn_w},
   2388     {"csel", p_csel_},
   2389     {"csinc", p_csinc_},
   2390     {"csinv", p_csinv_},
   2391     {"csneg", p_csneg_},
   2392     {"cset", p_cset_},
   2393     {"csetm", p_csetm_},
   2394     {"neg", p_neg_w},
   2395     {"negs", p_negs_w},
   2396     {"and", p_and_w},
   2397     {"bic", p_bic_w},
   2398     {"orr", p_orr_w},
   2399     {"orn", p_orn_w},
   2400     {"eor", p_eor_w},
   2401     {"eon", p_eon_w},
   2402     {"ands", p_ands_w},
   2403     {"bics", p_bics_w},
   2404     {"madd", p_madd},
   2405     {"msub", p_msub},
   2406     {"mul", p_mul_w},
   2407     {"mneg", p_mneg_w},
   2408     {"udiv", p_udiv_w},
   2409     {"sdiv", p_sdiv_w},
   2410     {"lslv", p_lslv_w},
   2411     {"lsrv", p_lsrv_w},
   2412     {"asrv", p_asrv_w},
   2413     {"rorv", p_rorv_w},
   2414     {"lsl", p_lsl_},
   2415     {"lsr", p_lsr_},
   2416     {"asr", p_asr_},
   2417     {"b", p_b_},
   2418     {"bl", p_bl_},
   2419     {"cbz", p_cbz_},
   2420     {"cbnz", p_cbnz_},
   2421     {"svc", p_svc_},
   2422     {"brk", p_brk_},
   2423     {"hlt", p_hlt_},
   2424     {"mrs", p_mrs_},
   2425     {"msr", p_msr_},
   2426     {"ldr", p_ldr_},
   2427     {"str", p_str_},
   2428     {"ldrb", p_ldrb},
   2429     {"strb", p_strb},
   2430     {"ldrh", p_ldrh},
   2431     {"strh", p_strh},
   2432     {"ldrsb", p_ldrsb},
   2433     {"ldrsh", p_ldrsh},
   2434     {"ldrsw", p_ldrsw},
   2435     {"ldur", p_ldur_},
   2436     {"stur", p_stur_},
   2437     {"ldurb", p_ldurb},
   2438     {"sturb", p_sturb},
   2439     {"ldurh", p_ldurh},
   2440     {"sturh", p_sturh},
   2441     {"ldursb", p_ldursb},
   2442     {"ldursh", p_ldursh},
   2443     {"ldursw", p_ldursw},
   2444     {"ldp", p_ldp_},
   2445     {"stp", p_stp_},
   2446     {"adr", p_adr_},
   2447     {"adrp", p_adrp_},
   2448     /* ---- atomics / exclusive ---- */
   2449     {"ldxr", p_ldxr_wx},
   2450     {"ldxrb", p_ldxrb},
   2451     {"ldxrh", p_ldxrh},
   2452     {"ldaxr", p_ldaxr_wx},
   2453     {"ldaxrb", p_ldaxrb},
   2454     {"ldaxrh", p_ldaxrh},
   2455     {"ldar", p_ldar_wx},
   2456     {"ldarb", p_ldarb},
   2457     {"ldarh", p_ldarh},
   2458     {"stxr", p_stxr_wx},
   2459     {"stxrb", p_stxrb_},
   2460     {"stxrh", p_stxrh_},
   2461     {"stlxr", p_stlxr_wx},
   2462     {"stlxrb", p_stlxrb_},
   2463     {"stlxrh", p_stlxrh_},
   2464     {"stlr", p_stlr_wx},
   2465     {"stlrb", p_stlrb_},
   2466     {"stlrh", p_stlrh_},
   2467     {"cas", p_cas_wx},
   2468     {"casb", p_casb},
   2469     {"cash", p_cash},
   2470     {"casa", p_casa_wx},
   2471     {"casab", p_casab},
   2472     {"casah", p_casah},
   2473     {"casl", p_casl_wx},
   2474     {"caslb", p_caslb},
   2475     {"caslh", p_caslh},
   2476     {"casal", p_casal_wx},
   2477     {"casalb", p_casalb},
   2478     {"casalh", p_casalh},
   2479     {"swp", p_swp_wx},
   2480     {"swpb", p_swpb},
   2481     {"swph", p_swph},
   2482     {"swpa", p_swpa_wx},
   2483     {"swpab", p_swpab},
   2484     {"swpah", p_swpah},
   2485     {"swpl", p_swpl_wx},
   2486     {"swplb", p_swplb},
   2487     {"swplh", p_swplh},
   2488     {"swpal", p_swpal_wx},
   2489     {"swpalb", p_swpalb},
   2490     {"swpalh", p_swpalh},
   2491     {"ldadd", p_ldadd_wx},
   2492     {"ldaddb", p_ldaddb},
   2493     {"ldaddh", p_ldaddh},
   2494     {"ldadda", p_ldadda_wx},
   2495     {"ldaddab", p_ldaddab},
   2496     {"ldaddah", p_ldaddah},
   2497     {"ldaddl", p_ldaddl_wx},
   2498     {"ldaddlb", p_ldaddlb},
   2499     {"ldaddlh", p_ldaddlh},
   2500     {"ldaddal", p_ldaddal_wx},
   2501     {"ldaddalb", p_ldaddalb},
   2502     {"ldaddalh", p_ldaddalh},
   2503     {"ldclr", p_ldclr_wx},
   2504     {"ldclrb", p_ldclrb},
   2505     {"ldclrh", p_ldclrh},
   2506     {"ldclra", p_ldclra_wx},
   2507     {"ldclrab", p_ldclrab},
   2508     {"ldclrah", p_ldclrah},
   2509     {"ldclrl", p_ldclrl_wx},
   2510     {"ldclrlb", p_ldclrlb},
   2511     {"ldclrlh", p_ldclrlh},
   2512     {"ldclral", p_ldclral_wx},
   2513     {"ldclralb", p_ldclralb},
   2514     {"ldclralh", p_ldclralh},
   2515     {"ldeor", p_ldeor_wx},
   2516     {"ldeorb", p_ldeorb},
   2517     {"ldeorh", p_ldeorh},
   2518     {"ldeora", p_ldeora_wx},
   2519     {"ldeorab", p_ldeorab},
   2520     {"ldeorah", p_ldeorah},
   2521     {"ldeorl", p_ldeorl_wx},
   2522     {"ldeorlb", p_ldeorlb},
   2523     {"ldeorlh", p_ldeorlh},
   2524     {"ldeoral", p_ldeoral_wx},
   2525     {"ldeoralb", p_ldeoralb},
   2526     {"ldeoralh", p_ldeoralh},
   2527     {"ldset", p_ldset_wx},
   2528     {"ldsetb", p_ldsetb},
   2529     {"ldseth", p_ldseth},
   2530     {"ldseta", p_ldseta_wx},
   2531     {"ldsetab", p_ldsetab},
   2532     {"ldsetah", p_ldsetah},
   2533     {"ldsetl", p_ldsetl_wx},
   2534     {"ldsetlb", p_ldsetlb},
   2535     {"ldsetlh", p_ldsetlh},
   2536     {"ldsetal", p_ldsetal_wx},
   2537     {"ldsetalb", p_ldsetalb},
   2538     {"ldsetalh", p_ldsetalh},
   2539     {"b.eq", p_b_eq},
   2540     {"b.ne", p_b_ne},
   2541     {"b.cs", p_b_cs},
   2542     {"b.hs", p_b_hs},
   2543     {"b.cc", p_b_cc},
   2544     {"b.lo", p_b_lo},
   2545     {"b.mi", p_b_mi},
   2546     {"b.pl", p_b_pl},
   2547     {"b.vs", p_b_vs},
   2548     {"b.vc", p_b_vc},
   2549     {"b.hi", p_b_hi},
   2550     {"b.ls", p_b_ls},
   2551     {"b.ge", p_b_ge},
   2552     {"b.lt", p_b_lt},
   2553     {"b.gt", p_b_gt},
   2554     {"b.le", p_b_le},
   2555     {"b.al", p_b_al},
   2556     {NULL, NULL},
   2557 };
   2558 
   2559 void aa64_asm_insn(AA64Asm* a, AsmDriver* d, Sym mnemonic) {
   2560   (void)a;
   2561   Slice msl = pool_slice(asm_driver_pool(d), mnemonic);
   2562   const char* mp = msl.s;
   2563   size_t mn = msl.len;
   2564   for (const AA64Mn* row = kTable; row->name; ++row) {
   2565     if (icase_eq(mp, mn, row->name)) {
   2566       row->fn(d);
   2567       return;
   2568     }
   2569   }
   2570   asm_driver_panic(d, "asm: unknown mnemonic");
   2571 }
   2572 
   2573 /* ---- inline-asm template walker (Phase 4b Track C) ---- */
   2574 
   2575 /* Per-call rendered-line buffer.  GCC's inline asm rarely emits more
   2576  * than a handful of instructions per block; one line of substituted
   2577  * text fits comfortably inside this. Truncation panics — the operator
   2578  * grammar should never grow a single line beyond this without a
   2579  * deliberate reason. */
   2580 #define AA64_INLINE_LINE_CAP 1024
   2581 
   2582 _Noreturn static void inline_panic(AA64Asm* a, const char* msg);
   2583 
   2584 /* Render a 5-bit register number into the StrBuf using the requested
   2585  * width form.  is64 picks x-form vs w-form; SP / ZR encode as
   2586  * register #31 and we render them as wzr/xzr or wsp/sp depending on
   2587  * caller intent — for inline-asm v1 the bound operand always names a
   2588  * GP register, never SP, so we emit wzr/xzr for #31. */
   2589 static void render_reg(StrBuf* sb, u32 reg, int is64) {
   2590   if (reg == 31u) {
   2591     strbuf_puts(sb, is64 ? "xzr" : "wzr");
   2592     return;
   2593   }
   2594   strbuf_putc(sb, is64 ? 'x' : 'w');
   2595   if (reg >= 10u) strbuf_putc(sb, (char)('0' + (reg / 10u)));
   2596   strbuf_putc(sb, (char)('0' + (reg % 10u)));
   2597 }
   2598 
   2599 static void render_fp_reg(StrBuf* sb, u32 reg, u32 nbytes) {
   2600   strbuf_putc(sb, nbytes <= 4u ? 's' : 'd');
   2601   if (reg >= 10u) strbuf_putc(sb, (char)('0' + (reg / 10u)));
   2602   strbuf_putc(sb, (char)('0' + (reg % 10u)));
   2603 }
   2604 
   2605 static u32 inline_op_size(AA64Asm* a, const Operand* op) {
   2606   if (!op->type) return 8u;
   2607   u64 n = cg_type_size(a->c, op->type);
   2608   if (!n) return 8u;
   2609   if (n > 16u) inline_panic(a, "inline asm operand is too large");
   2610   return (u32)n;
   2611 }
   2612 
   2613 static int inline_op_is_ptr(AA64Asm* a, const Operand* op) {
   2614   return op->type && cg_type_is_ptr(a->c, op->type);
   2615 }
   2616 
   2617 /* Render a signed 64-bit integer prefixed with '#'. */
   2618 static void render_imm(StrBuf* sb, i64 v) {
   2619   strbuf_putc(sb, '#');
   2620   strbuf_put_i64(sb, v);
   2621 }
   2622 
   2623 /* Render an addressing form `[xN, #ofs]` for OPK_INDIRECT. */
   2624 static void render_indirect(StrBuf* sb, Reg base, i32 ofs) {
   2625   strbuf_putc(sb, '[');
   2626   render_reg(sb, (u32)base, /*is64=*/1);
   2627   if (ofs != 0) {
   2628     strbuf_puts(sb, ", ");
   2629     render_imm(sb, (i64)ofs);
   2630   }
   2631   strbuf_putc(sb, ']');
   2632 }
   2633 
   2634 _Noreturn static void inline_panic(AA64Asm* a, const char* msg) {
   2635   SrcLoc loc = {0, 0, 0};
   2636   compiler_panic(a->c, loc, "inline asm: %.*s",
   2637                  SLICE_ARG(slice_from_cstr(msg)));
   2638 }
   2639 
   2640 /* Resolve operand index N → (kind=0 forced default, 1=force-w, 2=force-x,
   2641  * 3=address form `%aN`).  Renders into sb. */
   2642 static void render_operand(AA64Asm* a, StrBuf* sb, u32 idx, int form) {
   2643   u32 ntot = a->nout + a->nin;
   2644   if (idx >= ntot) inline_panic(a, "operand index out of range");
   2645   const Operand* op =
   2646       (idx < a->nout) ? &a->out_ops[idx] : &a->in_ops[idx - a->nout];
   2647   switch (form) {
   2648     case 1: /* %wN — force 32-bit register form */
   2649       if (op->kind != AA64_INLINE_OPK_REG ||
   2650           op->pad[0] != AA64_INLINE_OPCLS_INT)
   2651         inline_panic(a, "%w on non-integer-register operand");
   2652       render_reg(sb, (u32)op->v.local, 0);
   2653       return;
   2654     case 2: /* %xN — force 64-bit register form */
   2655       if (op->kind != AA64_INLINE_OPK_REG ||
   2656           op->pad[0] != AA64_INLINE_OPCLS_INT)
   2657         inline_panic(a, "%x on non-integer-register operand");
   2658       render_reg(sb, (u32)op->v.local, 1);
   2659       return;
   2660     case 3: /* %aN — memory addressing form */
   2661       if (op->kind != OPK_INDIRECT) inline_panic(a, "%a on non-memory operand");
   2662       /* Inline asm consumes a plain pointer-shaped address; the cg
   2663        * contract guarantees no EA index here. */
   2664       if (op->v.ind.index != REG_NONE)
   2665         inline_panic(a, "%a operand has unexpected EA index");
   2666       render_indirect(sb, op->v.ind.base, op->v.ind.ofs);
   2667       return;
   2668     default:
   2669       break;
   2670   }
   2671   /* Default rendering by operand kind. */
   2672   switch (op->kind) {
   2673     case AA64_INLINE_OPK_REG:
   2674       if (op->pad[0] == AA64_INLINE_OPCLS_FP) {
   2675         render_fp_reg(sb, (u32)op->v.local, inline_op_size(a, op));
   2676       } else {
   2677         render_reg(sb, (u32)op->v.local,
   2678                    inline_op_is_ptr(a, op) || inline_op_size(a, op) > 4u);
   2679       }
   2680       return;
   2681     case OPK_IMM:
   2682       render_imm(sb, op->v.imm);
   2683       return;
   2684     case OPK_INDIRECT:
   2685       if (op->v.ind.index != REG_NONE)
   2686         inline_panic(a, "inline-asm operand has unexpected EA index");
   2687       render_indirect(sb, op->v.ind.base, op->v.ind.ofs);
   2688       return;
   2689     default:
   2690       inline_panic(a, "unsupported operand kind for %N");
   2691   }
   2692 }
   2693 
   2694 /* Lex one line of substituted asm and dispatch via aa64_asm_insn. */
   2695 static void run_one_line(AA64Asm* a, MCEmitter* mc, const char* text,
   2696                          size_t len) {
   2697   /* Skip blank lines. */
   2698   size_t i;
   2699   for (i = 0; i < len; ++i) {
   2700     if (text[i] != ' ' && text[i] != '\t') break;
   2701   }
   2702   if (i == len) return;
   2703 
   2704   AsmLexer* lx = asm_lex_open_mem(a->c, "<inline-asm>", text, len);
   2705   AsmDriver* d = asm_driver_open_inline(a->c, mc, lx);
   2706 
   2707   /* The first non-trivial token must be the mnemonic identifier (or a
   2708    * `.directive`, but inline asm doesn't normally use directives — leave
   2709    * that path unsupported until needed). */
   2710   AsmTok t = asm_driver_peek(d);
   2711   while (t.kind == ASM_TOK_NEWLINE || t.kind == ASM_TOK_HASH) {
   2712     (void)asm_driver_next(d);
   2713     if (t.kind == ASM_TOK_HASH) {
   2714       /* Skip cpp linemarker rest of line. */
   2715       while (!asm_driver_at_eol(d)) (void)asm_driver_next(d);
   2716     }
   2717     t = asm_driver_peek(d);
   2718   }
   2719   if (t.kind == ASM_TOK_EOF) {
   2720     asm_driver_close_inline(d);
   2721     asm_lex_close(lx);
   2722     return;
   2723   }
   2724   if (t.kind != ASM_TOK_IDENT)
   2725     inline_panic(a, "expected mnemonic at start of inline asm line");
   2726   (void)asm_driver_next(d);
   2727   Sym mn = t.v.ident;
   2728   /* Compose `b.eq` etc. — same trick as the standalone driver. */
   2729   AsmTok dot = asm_driver_peek(d);
   2730   if (asm_driver_tok_is_punct(dot, '.')) {
   2731     (void)asm_driver_next(d);
   2732     AsmTok rest = asm_driver_next(d);
   2733     if (rest.kind != ASM_TOK_IDENT)
   2734       inline_panic(a, "composite mnemonic: expected ident after '.'");
   2735     Slice hsl = pool_slice(asm_driver_pool(d), mn);
   2736     Slice rsl = pool_slice(asm_driver_pool(d), rest.v.ident);
   2737     size_t hn = hsl.len, rn = rsl.len;
   2738     const char* hp = hsl.s;
   2739     const char* rp = rsl.s;
   2740     char buf[64];
   2741     if (hn + 1 + rn >= sizeof buf)
   2742       inline_panic(a, "composite mnemonic too long");
   2743     for (size_t k = 0; k < hn; ++k) buf[k] = hp[k];
   2744     buf[hn] = '.';
   2745     for (size_t k = 0; k < rn; ++k) buf[hn + 1 + k] = rp[k];
   2746     mn = pool_intern_slice(asm_driver_pool(d),
   2747                            (Slice){.s = buf, .len = hn + 1 + rn});
   2748   }
   2749   aa64_asm_insn(a, d, mn);
   2750   asm_driver_close_inline(d);
   2751   asm_lex_close(lx);
   2752 }
   2753 
   2754 /* Substitute placeholders into one line's StrBuf, then dispatch.
   2755  *
   2756  * The input range is [start, end) inside `tmpl`.  Updates `*line_idx`
   2757  * is not used — the caller resets the StrBuf between lines. */
   2758 static void render_and_run_line(AA64Asm* a, MCEmitter* mc, StrBuf* sb,
   2759                                 const char* start, const char* end) {
   2760   strbuf_reset(sb);
   2761   for (const char* p = start; p < end; ++p) {
   2762     char c = *p;
   2763     if (c != '%') {
   2764       strbuf_putc(sb, c);
   2765       continue;
   2766     }
   2767     /* Placeholder. */
   2768     if (p + 1 >= end) inline_panic(a, "trailing '%' in template");
   2769     char n = *(p + 1);
   2770     if (n == '%') {
   2771       strbuf_putc(sb, '%');
   2772       ++p;
   2773       continue;
   2774     }
   2775     if (n == '[') {
   2776       /* %[name] — scan to the closing ']' and resolve against
   2777        * AsmConstraint.name on the combined outs+ins list. Match by
   2778        * comparing the named-bracket contents against the interned name
   2779        * Sym stored on each constraint. */
   2780       const char* nbeg = p + 2;
   2781       const char* nend = nbeg;
   2782       while (nend < end && *nend != ']') ++nend;
   2783       if (nend == end) inline_panic(a, "unterminated %[name]");
   2784       size_t nlen = (size_t)(nend - nbeg);
   2785       Sym needle =
   2786           pool_intern_slice(a->c->global, (Slice){.s = nbeg, .len = nlen});
   2787       u32 idx = (u32)-1;
   2788       for (u32 k = 0; k < a->nout; ++k) {
   2789         if (a->outs[k].name == needle) {
   2790           idx = k;
   2791           break;
   2792         }
   2793       }
   2794       if (idx == (u32)-1) {
   2795         for (u32 k = 0; k < a->nin; ++k) {
   2796           if (a->ins[k].name == needle) {
   2797             idx = a->nout + k;
   2798             break;
   2799           }
   2800         }
   2801       }
   2802       if (idx == (u32)-1)
   2803         inline_panic(a, "%[name] does not match any constraint");
   2804       p = nend; /* loop's ++p steps past the ']' */
   2805       render_operand(a, sb, idx, 0);
   2806       continue;
   2807     }
   2808     int form = 0; /* 0=default, 1=w, 2=x, 3=a */
   2809     if (n == 'w' || n == 'x' || n == 'a') {
   2810       form = (n == 'w') ? 1 : (n == 'x') ? 2 : 3;
   2811       ++p;
   2812       if (p + 1 >= end) inline_panic(a, "trailing '%' modifier in template");
   2813       n = *(p + 1);
   2814     }
   2815     if (n == '[') {
   2816       /* %w[name] / %x[name] / %a[name] — width modifier + symbolic
   2817        * operand. Resolves the same way as %[name] but renders with the
   2818        * declared form. */
   2819       const char* nbeg = p + 2;
   2820       const char* nend = nbeg;
   2821       while (nend < end && *nend != ']') ++nend;
   2822       if (nend == end) inline_panic(a, "unterminated %[name]");
   2823       size_t nlen = (size_t)(nend - nbeg);
   2824       Sym needle =
   2825           pool_intern_slice(a->c->global, (Slice){.s = nbeg, .len = nlen});
   2826       u32 idx = (u32)-1;
   2827       for (u32 k = 0; k < a->nout; ++k) {
   2828         if (a->outs[k].name == needle) {
   2829           idx = k;
   2830           break;
   2831         }
   2832       }
   2833       if (idx == (u32)-1) {
   2834         for (u32 k = 0; k < a->nin; ++k) {
   2835           if (a->ins[k].name == needle) {
   2836             idx = a->nout + k;
   2837             break;
   2838           }
   2839         }
   2840       }
   2841       if (idx == (u32)-1)
   2842         inline_panic(a, "%[name] does not match any constraint");
   2843       p = nend; /* loop's ++p steps past the ']' */
   2844       render_operand(a, sb, idx, form);
   2845       continue;
   2846     }
   2847     if (n < '0' || n > '9') inline_panic(a, "expected digit after '%'");
   2848     u32 idx = (u32)(n - '0');
   2849     ++p;
   2850     /* GCC syntax permits up to two digits (%0..%99). */
   2851     if (p + 1 < end && *(p + 1) >= '0' && *(p + 1) <= '9') {
   2852       idx = idx * 10 + (u32)(*(p + 1) - '0');
   2853       ++p;
   2854     }
   2855     render_operand(a, sb, idx, form);
   2856   }
   2857   if (sb->truncated) inline_panic(a, "inline asm line buffer overflow");
   2858   run_one_line(a, mc, strbuf_cstr(sb), strbuf_len(sb));
   2859 }
   2860 
   2861 void aa64_asm_run_template(AA64Asm* a, MCEmitter* mc, const char* tmpl) {
   2862   if (!tmpl || !*tmpl) return;
   2863 
   2864   char buf[AA64_INLINE_LINE_CAP];
   2865   StrBuf sb;
   2866   strbuf_init(&sb, buf, sizeof buf);
   2867 
   2868   /* Walk tmpl, splitting on '\n' and ';' line terminators.  Track bracket
   2869    * depth and quote state so that a literal ';' inside `[ ... ]` or a
   2870    * quoted string is not mistaken for a statement separator. */
   2871   const char* line_start = tmpl;
   2872   int bracket = 0;
   2873   char quote = 0;
   2874   for (const char* p = tmpl;; ++p) {
   2875     char c = *p;
   2876     if (c == '\0') {
   2877       render_and_run_line(a, mc, &sb, line_start, p);
   2878       break;
   2879     }
   2880     if (quote) {
   2881       if (c == '\\' && *(p + 1)) {
   2882         ++p;
   2883         continue;
   2884       }
   2885       if (c == quote) quote = 0;
   2886       continue;
   2887     }
   2888     if (c == '"' || c == '\'') {
   2889       quote = c;
   2890       continue;
   2891     }
   2892     if (c == '[') {
   2893       ++bracket;
   2894       continue;
   2895     }
   2896     if (c == ']') {
   2897       if (bracket) --bracket;
   2898       continue;
   2899     }
   2900     if (bracket == 0 && (c == '\n' || c == ';')) {
   2901       render_and_run_line(a, mc, &sb, line_start, p);
   2902       line_start = p + 1;
   2903     }
   2904   }
   2905 }