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 }