asm.c (42671B)
1 /* ARM32 (Thumb-2) textual-assembler frontend. 2 * 3 * Implements the per-instruction `insn` hook the shared assembler driver 4 * (src/asm/asm.c) calls once per source line. Operands are parsed off the 5 * AsmDriver token stream and encoded via the isa.h inline encoders, emitting 6 * each Thumb-2 instruction as little-endian half-words (hw1 first for the 7 * 32-bit forms). Mirrors src/arch/riscv/asm.c (descriptor lookup + format 8 * dispatch) and src/arch/aa64/asm.c (the ArchAsmOps printer-side seam). 9 * 10 * Mnemonic resolution: the driver composes dotted mnemonics (so `mov.w`, 11 * `b.w`, `ldr.w` arrive whole). A condition-code suffix (`bne`, `beq.w`, 12 * `moveq`) is stripped here to recover the base mnemonic + condition. The 13 * `.syntax unified` / `.thumb` directives are consumed by the shared driver. */ 14 #include "arch/arm32/asm.h" 15 16 #include <string.h> 17 18 #include "arch/arm32/isa.h" 19 #include "arch/arm32/regs.h" 20 #include "arch/arch.h" 21 #include "asm/asm_helpers.h" 22 #include "asm/asm_lex.h" 23 #include "cg/cgir.h" 24 #include "core/arena.h" 25 #include "core/pool.h" 26 #include "core/slice.h" 27 #include "core/strbuf.h" 28 #include "obj/obj.h" 29 30 struct Arm32Asm { 31 ArchAsm base; 32 Compiler* c; 33 34 const AsmConstraint* outs; 35 Operand* out_ops; 36 const AsmConstraint* ins; 37 const Operand* in_ops; 38 const Sym* clobbers; 39 u32 nout; 40 u32 nin; 41 u32 nclob; 42 }; 43 44 /* ---- byte emit (LE half-words, hw1 first for 32-bit) ---- */ 45 static void emit_t16(AsmDriver* d, u16 hw) { 46 u8 b[2] = {(u8)(hw & 0xffu), (u8)((hw >> 8) & 0xffu)}; 47 mc_emit_bytes(asm_driver_mc(d), b, sizeof b); 48 } 49 static void emit_t32(AsmDriver* d, u32 instr) { 50 u32 hw1 = (instr >> 16) & 0xffffu, hw2 = instr & 0xffffu; 51 u8 b[4] = {(u8)(hw1 & 0xffu), (u8)((hw1 >> 8) & 0xffu), (u8)(hw2 & 0xffu), 52 (u8)((hw2 >> 8) & 0xffu)}; 53 mc_emit_bytes(asm_driver_mc(d), b, sizeof b); 54 } 55 56 /* Construct a Slice (KitSlice's first member is a union, so the brace-init form 57 * needs nesting; this helper keeps the call sites clean). */ 58 static Slice arm_slice(const char* s, size_t len) { 59 Slice sl; 60 sl.s = s; 61 sl.len = len; 62 return sl; 63 } 64 65 Arm32Asm* arm32_asm_open(Compiler* c) { 66 Arm32Asm* a = arena_new(c->tu, Arm32Asm); 67 memset(a, 0, sizeof *a); 68 a->base.insn = NULL; 69 a->base.destroy = NULL; 70 a->c = c; 71 return a; 72 } 73 74 void arm32_asm_close(Arm32Asm* a) { (void)a; } 75 76 void arm32_inline_bind(Arm32Asm* a, const AsmConstraint* outs, u32 nout, 77 Operand* out_ops, const AsmConstraint* ins, u32 nin, 78 const Operand* in_ops, const Sym* clobbers, u32 nclob) { 79 a->outs = outs; 80 a->out_ops = out_ops; 81 a->ins = ins; 82 a->in_ops = in_ops; 83 a->clobbers = clobbers; 84 a->nout = nout; 85 a->nin = nin; 86 a->nclob = nclob; 87 } 88 89 static void render_reg(StrBuf* sb, u32 r) { 90 strbuf_putc(sb, 'r'); 91 if (r >= 10u) strbuf_putc(sb, (char)('0' + (r / 10u))); 92 strbuf_putc(sb, (char)('0' + (r % 10u))); 93 } 94 95 static void render_imm(StrBuf* sb, i64 v) { 96 strbuf_putc(sb, '#'); 97 strbuf_put_i64(sb, v); 98 } 99 100 static void render_indirect(StrBuf* sb, Reg base, i32 ofs) { 101 strbuf_putc(sb, '['); 102 render_reg(sb, (u32)base); 103 if (ofs != 0) { 104 strbuf_puts(sb, ", "); 105 render_imm(sb, (i64)ofs); 106 } 107 strbuf_putc(sb, ']'); 108 } 109 110 _Noreturn static void inline_panic(Arm32Asm* a, const char* msg) { 111 SrcLoc loc = {0, 0, 0}; 112 compiler_panic(a->c, loc, "inline asm: %.*s", 113 SLICE_ARG(slice_from_cstr(msg))); 114 } 115 116 static u32 lookup_named(Arm32Asm* a, Sym needle) { 117 for (u32 k = 0; k < a->nout; ++k) { 118 if (a->outs[k].name == needle) return k; 119 } 120 for (u32 k = 0; k < a->nin; ++k) { 121 if (a->ins[k].name == needle) return a->nout + k; 122 } 123 return (u32)-1; 124 } 125 126 static void render_operand(Arm32Asm* a, StrBuf* sb, u32 idx, int form) { 127 u32 ntot = a->nout + a->nin; 128 if (idx >= ntot) inline_panic(a, "operand index out of range"); 129 const Operand* op = 130 (idx < a->nout) ? &a->out_ops[idx] : &a->in_ops[idx - a->nout]; 131 if (form == 3) { 132 if (op->kind != OPK_INDIRECT) inline_panic(a, "%a on non-memory operand"); 133 if (op->v.ind.index != CG_LOCAL_NONE) 134 inline_panic(a, "%a on indexed memory operand"); 135 render_indirect(sb, op->v.ind.base, op->v.ind.ofs); 136 return; 137 } 138 if (form != 0) inline_panic(a, "unsupported ARM32 inline asm modifier"); 139 switch (op->kind) { 140 case ARM32_INLINE_OPK_REG: 141 if (op->pad[0] == ARM32_INLINE_OPCLS_FP) 142 inline_panic(a, "arm32 inline asm has no FP register constraints"); 143 render_reg(sb, (u32)op->v.local); 144 return; 145 case OPK_IMM: 146 render_imm(sb, op->v.imm); 147 return; 148 case OPK_INDIRECT: 149 if (op->v.ind.index != CG_LOCAL_NONE) 150 inline_panic(a, "indexed memory operand in inline asm"); 151 render_indirect(sb, op->v.ind.base, op->v.ind.ofs); 152 return; 153 default: 154 inline_panic(a, "unsupported operand kind for %N"); 155 } 156 } 157 158 /* ---- operand parse helpers ---- */ 159 static int sym_to_cstr(AsmDriver* d, Sym s, char* out, size_t cap) { 160 Slice sl = pool_slice(asm_driver_pool(d), s); 161 if (!sl.s || sl.len >= cap) return 0; 162 memcpy(out, sl.s, sl.len); 163 out[sl.len] = '\0'; 164 return 1; 165 } 166 167 static u32 parse_reg(AsmDriver* d) { 168 AsmTok t = asm_driver_next(d); 169 char name[16]; 170 uint32_t idx = 0; 171 if (t.kind != ASM_TOK_IDENT || !sym_to_cstr(d, t.v.ident, name, sizeof name) || 172 arm32_register_index(name, &idx) != 0 || idx > 15u) 173 asm_driver_panic(d, "arm32 asm: bad core register"); 174 return idx; 175 } 176 177 static void expect_comma(AsmDriver* d) { 178 if (!asm_driver_eat_comma(d)) asm_driver_panic(d, "arm32 asm: expected ','"); 179 } 180 181 /* 1 if the next token is a core register name (used to disambiguate the 182 * register vs modified-immediate data-processing forms by operand shape). */ 183 static int peek_is_reg(AsmDriver* d) { 184 AsmTok t = asm_driver_peek(d); 185 char name[16]; 186 uint32_t idx; 187 if (t.kind != ASM_TOK_IDENT) return 0; 188 if (!sym_to_cstr(d, t.v.ident, name, sizeof name)) return 0; 189 return arm32_register_index(name, &idx) == 0 && idx <= 15u; 190 } 191 192 /* Parse a #-prefixed immediate constant. The `#` is optional (GNU as accepts 193 * both); a bare expression is also accepted. */ 194 static i64 parse_imm(AsmDriver* d) { 195 (void)asm_driver_eat_punct(d, '#'); 196 return asm_driver_parse_const(d); 197 } 198 199 /* A modified-immediate operand: encode via thumb_expand_imm_encode, panicking 200 * if the value isn't representable (the assembler does not materialize). */ 201 static u32 parse_modimm(AsmDriver* d) { 202 i64 v = parse_imm(d); 203 u32 out12; 204 if (!thumb_expand_imm_encode((u32)v, &out12)) 205 asm_driver_panic(d, "arm32 asm: immediate not a modified-immediate"); 206 return out12; 207 } 208 209 /* `[Rn]` / `[Rn, #imm]` — returns base in *base_out, signed displacement in 210 * *disp_out. Pre/post-index and shifted-index modes are a follow-on. */ 211 static void parse_mem(AsmDriver* d, u32* base_out, i64* disp_out) { 212 asm_driver_expect_punct(d, '[', "'[' in arm32 memory operand"); 213 *base_out = parse_reg(d); 214 *disp_out = 0; 215 if (asm_driver_eat_comma(d)) *disp_out = parse_imm(d); 216 asm_driver_expect_punct(d, ']', "']' in arm32 memory operand"); 217 } 218 219 /* Register list `{r0, r1, lr}` -> bitmask. */ 220 static u32 parse_reglist(AsmDriver* d) { 221 u32 mask = 0; 222 asm_driver_expect_punct(d, '{', "'{' in arm32 register list"); 223 for (;;) { 224 u32 r = parse_reg(d); 225 mask |= (1u << r); 226 if (!asm_driver_eat_comma(d)) break; 227 } 228 asm_driver_expect_punct(d, '}', "'}' in arm32 register list"); 229 return mask; 230 } 231 232 /* `#:lower16:sym` / `#:upper16:sym` modifier -> 1 + reloc kind, sym, addend. 233 * The leading `#` is optional; returns 0 if no `:modifier:` is present. */ 234 static int parse_movw_mod(AsmDriver* d, RelocKind* kind_out, ObjSymId* sym_out, 235 i64* off_out, int is_movt) { 236 (void)asm_driver_eat_punct(d, '#'); 237 if (!asm_driver_tok_is_punct(asm_driver_peek(d), ':')) return 0; 238 (void)asm_driver_next(d); /* ':' */ 239 { 240 AsmTok name = asm_driver_next(d); 241 Slice s; 242 RelocKind k; 243 if (name.kind != ASM_TOK_IDENT) 244 asm_driver_panic(d, "arm32 asm: expected relocation modifier"); 245 s = pool_slice(asm_driver_pool(d), name.v.ident); 246 if (slice_eq_cstr(s, "lower16")) 247 k = R_ARM_THM_MOVW_ABS_NC; 248 else if (slice_eq_cstr(s, "upper16")) 249 k = R_ARM_THM_MOVT_ABS; 250 else 251 asm_driver_panic(d, "arm32 asm: unsupported relocation modifier"); 252 if ((k == R_ARM_THM_MOVT_ABS) != (is_movt != 0)) 253 asm_driver_panic(d, "arm32 asm: lower16/upper16 mismatched with movw/movt"); 254 asm_driver_expect_punct(d, ':', "':' closing relocation modifier"); 255 { 256 ObjSymId sym = OBJ_SYM_NONE; 257 i64 off = 0; 258 asm_driver_parse_sym_expr(d, &sym, &off); 259 *kind_out = k; 260 *sym_out = sym; 261 *off_out = off; 262 } 263 return 1; 264 } 265 } 266 267 /* Optional shift `, lsl #n` etc. on a register operand; returns the shift type 268 * (0..3) and amount, 0/0 if absent. */ 269 static u32 parse_opt_shift(AsmDriver* d, u32* amount_out) { 270 Slice s; 271 AsmTok t; 272 *amount_out = 0; 273 t = asm_driver_peek(d); 274 if (t.kind != ASM_TOK_IDENT) return 0; 275 s = pool_slice(asm_driver_pool(d), t.v.ident); 276 u32 type; 277 if (slice_eq_cstr(s, "lsl")) 278 type = 0; 279 else if (slice_eq_cstr(s, "lsr")) 280 type = 1; 281 else if (slice_eq_cstr(s, "asr")) 282 type = 2; 283 else if (slice_eq_cstr(s, "ror")) 284 type = 3; 285 else 286 return 0; 287 (void)asm_driver_next(d); /* the shift mnemonic */ 288 *amount_out = (u32)parse_imm(d); 289 return type; 290 } 291 292 /* Barrier option: `sy` (full system) or a bare numeric. */ 293 static u32 parse_barrier_opt(AsmDriver* d) { 294 AsmTok t = asm_driver_peek(d); 295 if (t.kind == ASM_TOK_IDENT) { 296 Slice s = pool_slice(asm_driver_pool(d), t.v.ident); 297 if (slice_eq_cstr(s, "sy")) { 298 (void)asm_driver_next(d); 299 return 0xfu; 300 } 301 } 302 if (asm_driver_at_eol(d)) return 0xfu; /* default SY */ 303 return (u32)asm_driver_parse_const(d) & 0xfu; 304 } 305 306 /* ===================================================================== 307 * Mnemonic resolution: strip a trailing condition-code suffix to recover the 308 * base mnemonic + condition (e.g. `bne`->`b`/ne, `beq.w`->`b.w`/eq, 309 * `moveq`->`mov`/eq). Returns the base descriptor and writes *cond_out 310 * (ARM_CC_AL if unconditional). The condition is encoded only by the branch 311 * formats; DP/etc. conditional forms require an enclosing IT block and the 312 * assembler accepts the suffix but the IT instruction supplies the predicate. 313 * ===================================================================== */ 314 static const Arm32InsnDesc* resolve_mnemonic(AsmDriver* d, Slice mn, 315 u32* cond_out) { 316 const Arm32InsnDesc* desc = arm32_asm_find(mn); 317 *cond_out = ARM_CC_AL; 318 if (desc) return desc; 319 /* Try a trailing ".w" + 2-char cond (e.g. "bne.w" -> base "b.w"). */ 320 if (mn.len >= 4 && mn.s[mn.len - 2] == '.' && mn.s[mn.len - 1] == 'w') { 321 int cv = arm32_cond_from_name(arm_slice(mn.s + mn.len - 4, 2)); 322 if (cv >= 0) { 323 char buf[24]; 324 size_t base = mn.len - 4; 325 if (base + 2 < sizeof buf) { 326 memcpy(buf, mn.s, base); 327 buf[base] = '.'; 328 buf[base + 1] = 'w'; 329 desc = arm32_asm_find(arm_slice(buf, base + 2)); 330 if (desc) { 331 *cond_out = (u32)cv; 332 return desc; 333 } 334 } 335 } 336 } 337 /* Try a trailing 2-char cond (e.g. "bne" -> "b", "moveq" -> "mov"). */ 338 if (mn.len >= 2) { 339 int cv = arm32_cond_from_name(arm_slice(mn.s + mn.len - 2, 2)); 340 if (cv >= 0) { 341 desc = arm32_asm_find(arm_slice(mn.s, mn.len - 2)); 342 if (desc) { 343 *cond_out = (u32)cv; 344 return desc; 345 } 346 } 347 } 348 asm_driver_panic(d, "arm32 asm: unsupported instruction"); 349 } 350 351 /* Encode the third operand of a general `add`/`sub rd, rn, <#imm | rm>` once rd 352 * and rn are parsed and the separating comma consumed. There is no 16-bit 353 * non-flag add/sub-immediate or 3-register form, so a register third operand 354 * takes the 32-bit add.w/sub.w, and an immediate takes the densest of: the 355 * 16-bit sp-adjust / add-from-sp forms, the 32-bit modified-immediate 356 * (add.w/sub.w), then the 12-bit addw/subw. A negative immediate flips the 357 * operation. The 16-bit hi-register `add rdn, rm` (2 operands) is handled at the 358 * call site, not here. */ 359 static void arm_asm_addsub_third(AsmDriver* d, int is_sub, u32 rd, u32 rn) { 360 i64 simm; 361 u32 mag, out12; 362 if (peek_is_reg(d)) { 363 u32 rm = parse_reg(d); 364 emit_t32(d, is_sub ? arm_sub_reg(rd, rn, rm) : arm_add_reg(rd, rn, rm)); 365 return; 366 } 367 simm = parse_imm(d); 368 if (simm < 0) { 369 is_sub = !is_sub; 370 simm = -simm; 371 } 372 mag = (u32)simm; 373 if (rd == 13u && rn == 13u && (mag & 3u) == 0u && (mag >> 2) <= 0x7fu) { 374 emit_t16(d, (u16)((is_sub ? 0xb080u : 0xb000u) | ((mag >> 2) & 0x7fu))); 375 } else if (!is_sub && rn == 13u && rd <= 7u && (mag & 3u) == 0u && 376 (mag >> 2) <= 0xffu) { 377 emit_t16(d, (u16)(0xa800u | ((rd & 7u) << 8) | ((mag >> 2) & 0xffu))); 378 } else if (thumb_expand_imm_encode(mag, &out12)) { 379 emit_t32(d, arm_dp_imm(is_sub ? 13u : 8u, 0u, rd, rn, out12)); 380 } else if (mag <= 0xfffu) { 381 emit_t32(d, is_sub ? arm_sub_imm12(rd, rn, mag) : arm_add_imm12(rd, rn, mag)); 382 } else { 383 asm_driver_panic(d, "arm32 asm: add/sub immediate not encodable"); 384 } 385 } 386 387 /* Encode + emit one instruction for the matched descriptor. */ 388 static void assemble_one(AsmDriver* d, const Arm32InsnDesc* desc, u32 cond) { 389 Slice mn = desc->mnemonic; 390 switch ((Arm32Format)desc->fmt) { 391 case ARM_FMT_NONE: { 392 emit_t16(d, (u16)(desc->match & 0xffffu)); 393 return; 394 } 395 case ARM_FMT_DP_REG: 396 case ARM_FMT_SHIFT_REG: { 397 u32 rd = parse_reg(d); 398 u32 rn, rm; 399 expect_comma(d); 400 rn = parse_reg(d); 401 expect_comma(d); 402 rm = parse_reg(d); 403 /* hw1 = base|rn, hw2 = (rd<<8)|rm (shift-reg uses hw2 0xF000 base). */ 404 if ((Arm32Format)desc->fmt == ARM_FMT_SHIFT_REG) 405 emit_t32(d, desc->match | (rn << 16) | (rd << 8) | rm); 406 else 407 emit_t32(d, desc->match | (rn << 16) | (rd << 8) | rm); 408 return; 409 } 410 case ARM_FMT_DP_IMM: { 411 /* The mnemonic table prefers the modified-immediate row, but `add.w r0, 412 * r1, r2` etc. take a register third operand. Dispatch on shape: a 413 * register operand emits the shifted-register form (hw1 0xEAxx; the 414 * op4/S selector bits sit in the same positions as the imm form). */ 415 u32 rd = parse_reg(d), rn; 416 expect_comma(d); 417 rn = parse_reg(d); 418 expect_comma(d); 419 if (peek_is_reg(d)) { 420 u32 rm = parse_reg(d); 421 u32 base = (desc->match & ~(0xf000u << 16)) | (0xea00u << 16); 422 emit_t32(d, base | (rn << 16) | (rd << 8) | rm); 423 } else { 424 u32 out12 = parse_modimm(d); 425 u32 i = (out12 >> 11) & 1u, imm3 = (out12 >> 8) & 7u, 426 imm8 = out12 & 0xffu; 427 emit_t32(d, desc->match | (i << 26) | (rn << 16) | (imm3 << 12) | 428 (rd << 8) | imm8); 429 } 430 return; 431 } 432 case ARM_FMT_MOV_IMM: { 433 /* `mov.w rd, #imm` (this row) or `mov.w rd, rm` (register form). The 434 * register form is ORR rd, 1111, rm (hw1 0xEA4F for MOV / 0xEA6F MVN). */ 435 u32 rd = parse_reg(d); 436 expect_comma(d); 437 if (peek_is_reg(d)) { 438 u32 rm = parse_reg(d); 439 u32 mvn = slice_eq_cstr(mn, "mvn.w"); 440 emit_t32(d, arm_t32(mvn ? 0xea6fu : 0xea4fu, (rd << 8) | rm)); 441 return; 442 } 443 { 444 u32 out12 = parse_modimm(d); 445 u32 i = (out12 >> 11) & 1u, imm3 = (out12 >> 8) & 7u, 446 imm8 = out12 & 0xffu; 447 emit_t32(d, desc->match | (i << 26) | (imm3 << 12) | (rd << 8) | imm8); 448 } 449 return; 450 } 451 case ARM_FMT_MOV_REG: { 452 u32 rd = parse_reg(d), rm; 453 expect_comma(d); 454 rm = parse_reg(d); 455 emit_t32(d, desc->match | (rd << 8) | rm); 456 return; 457 } 458 case ARM_FMT_CMP_REG: { 459 u32 rn = parse_reg(d), rm; 460 expect_comma(d); 461 rm = parse_reg(d); 462 emit_t32(d, desc->match | (rn << 16) | rm); 463 return; 464 } 465 case ARM_FMT_CMP_IMM: { 466 u32 rn = parse_reg(d), out12; 467 expect_comma(d); 468 if (peek_is_reg(d)) { 469 u32 rm = parse_reg(d); 470 u32 base = (desc->match & ~(0xf000u << 16)) | (0xea00u << 16); 471 emit_t32(d, base | (rn << 16) | rm); 472 return; 473 } 474 out12 = parse_modimm(d); 475 { 476 u32 i = (out12 >> 11) & 1u, imm3 = (out12 >> 8) & 7u, 477 imm8 = out12 & 0xffu; 478 emit_t32(d, desc->match | (i << 26) | (rn << 16) | (imm3 << 12) | imm8); 479 } 480 return; 481 } 482 case ARM_FMT_MOVW: { 483 int is_movt = slice_eq_cstr(mn, "movt"); 484 u32 rd = parse_reg(d); 485 RelocKind k; 486 ObjSymId sym; 487 i64 off; 488 expect_comma(d); 489 if (parse_movw_mod(d, &k, &sym, &off, is_movt)) { 490 MCEmitter* mc = asm_driver_mc(d); 491 u32 base = is_movt ? arm_movt(rd, 0) : arm_movw(rd, 0); 492 mc_emit_reloc_at(mc, mc->section_id, mc_pos(mc), k, sym, off, 0, 0); 493 emit_t32(d, base); 494 } else { 495 u32 imm16 = (u32)parse_imm(d) & 0xffffu; 496 emit_t32(d, is_movt ? arm_movt(rd, imm16) : arm_movw(rd, imm16)); 497 } 498 return; 499 } 500 case ARM_FMT_ADDW: { 501 u32 rd = parse_reg(d), rn, imm12; 502 expect_comma(d); 503 rn = parse_reg(d); 504 expect_comma(d); 505 imm12 = (u32)parse_imm(d) & 0xfffu; 506 emit_t32(d, slice_eq_cstr(mn, "subw") ? arm_sub_imm12(rd, rn, imm12) 507 : arm_add_imm12(rd, rn, imm12)); 508 return; 509 } 510 case ARM_FMT_SHIFT_IMM: { 511 u32 type = (desc->match >> 4) & 3u; /* hw2[5:4] */ 512 u32 rd = parse_reg(d), rm, sh; 513 expect_comma(d); 514 rm = parse_reg(d); 515 expect_comma(d); 516 sh = (u32)parse_imm(d); 517 emit_t32(d, arm_shift_imm(type, rd, rm, sh)); 518 return; 519 } 520 case ARM_FMT_MUL: { 521 u32 rd = parse_reg(d), rn, rm; 522 expect_comma(d); 523 rn = parse_reg(d); 524 expect_comma(d); 525 rm = parse_reg(d); 526 emit_t32(d, arm_mul(rd, rn, rm)); 527 return; 528 } 529 case ARM_FMT_MLA: { 530 u32 rd = parse_reg(d), rn, rm, ra; 531 expect_comma(d); 532 rn = parse_reg(d); 533 expect_comma(d); 534 rm = parse_reg(d); 535 expect_comma(d); 536 ra = parse_reg(d); 537 emit_t32(d, slice_eq_cstr(mn, "mls") ? arm_mls(rd, rn, rm, ra) 538 : arm_mla(rd, rn, rm, ra)); 539 return; 540 } 541 case ARM_FMT_DIV: { 542 u32 rd = parse_reg(d), rn, rm; 543 expect_comma(d); 544 rn = parse_reg(d); 545 expect_comma(d); 546 rm = parse_reg(d); 547 emit_t32(d, slice_eq_cstr(mn, "udiv") ? arm_udiv(rd, rn, rm) 548 : arm_sdiv(rd, rn, rm)); 549 return; 550 } 551 case ARM_FMT_MULL: { 552 u32 rdlo = parse_reg(d), rdhi, rn, rm; 553 expect_comma(d); 554 rdhi = parse_reg(d); 555 expect_comma(d); 556 rn = parse_reg(d); 557 expect_comma(d); 558 rm = parse_reg(d); 559 emit_t32(d, slice_eq_cstr(mn, "smull") ? arm_smull(rdlo, rdhi, rn, rm) 560 : arm_umull(rdlo, rdhi, rn, rm)); 561 return; 562 } 563 case ARM_FMT_EXT: { 564 u32 rd = parse_reg(d), rm; 565 expect_comma(d); 566 rm = parse_reg(d); 567 emit_t32(d, desc->match | (rd << 8) | rm); 568 return; 569 } 570 case ARM_FMT_REV: { 571 u32 rd = parse_reg(d), rm; 572 expect_comma(d); 573 rm = parse_reg(d); 574 /* match has rm in hw1[3:0] and hw2[3:0]; both equal rm for REV/CLZ. */ 575 emit_t32(d, (desc->match & 0xfff0fff0u) | (rm << 16) | (rd << 8) | rm); 576 return; 577 } 578 case ARM_FMT_BFX: { 579 u32 rd = parse_reg(d), rn, lsb, width; 580 expect_comma(d); 581 rn = parse_reg(d); 582 expect_comma(d); 583 lsb = (u32)parse_imm(d); 584 expect_comma(d); 585 width = (u32)parse_imm(d); 586 emit_t32(d, slice_eq_cstr(mn, "ubfx") ? arm_ubfx(rd, rn, lsb, width) 587 : arm_sbfx(rd, rn, lsb, width)); 588 return; 589 } 590 case ARM_FMT_BFI: { 591 u32 rd = parse_reg(d), rn, lsb, width; 592 expect_comma(d); 593 rn = parse_reg(d); 594 expect_comma(d); 595 lsb = (u32)parse_imm(d); 596 expect_comma(d); 597 width = (u32)parse_imm(d); 598 emit_t32(d, arm_bfi(rd, rn, lsb, width)); 599 return; 600 } 601 case ARM_FMT_BFC: { 602 u32 rd = parse_reg(d), lsb, width; 603 expect_comma(d); 604 lsb = (u32)parse_imm(d); 605 expect_comma(d); 606 width = (u32)parse_imm(d); 607 emit_t32(d, arm_bfc(rd, lsb, width)); 608 return; 609 } 610 case ARM_FMT_SAT: { 611 /* SSAT/USAT rd, #sat, rm (no-shift first cut; mnemonic picks signed vs 612 * unsigned). */ 613 u32 rd = parse_reg(d), sat, rm; 614 expect_comma(d); 615 sat = (u32)parse_imm(d); 616 expect_comma(d); 617 rm = parse_reg(d); 618 emit_t32(d, slice_eq_cstr(mn, "usat") ? arm_usat(rd, sat, rm) 619 : arm_ssat(rd, sat, rm)); 620 return; 621 } 622 case ARM_FMT_QADD: { 623 /* QADD/QSUB/QDADD/QDSUB rd, rm, rn (ARM operand order). */ 624 u32 rd = parse_reg(d), rm, rn; 625 expect_comma(d); 626 rm = parse_reg(d); 627 expect_comma(d); 628 rn = parse_reg(d); 629 emit_t32(d, slice_eq_cstr(mn, "qsub") ? arm_qsub(rd, rm, rn) 630 : slice_eq_cstr(mn, "qdadd") ? arm_qdadd(rd, rm, rn) 631 : slice_eq_cstr(mn, "qdsub") ? arm_qdsub(rd, rm, rn) 632 : arm_qadd(rd, rm, rn)); 633 return; 634 } 635 case ARM_FMT_LDST_T3: { 636 u32 rt = parse_reg(d), base; 637 i64 disp; 638 expect_comma(d); 639 parse_mem(d, &base, &disp); 640 if (disp < 0) { 641 /* negative offset -> emit the T4 (±imm8) form. */ 642 u32 t4 = (desc->match & 0xfff00000u) - 0x00100000u; /* not used */ 643 (void)t4; 644 asm_driver_panic(d, "arm32 asm: use the T4 (#-imm8) form for negative offsets"); 645 } 646 emit_t32(d, (desc->match & 0xfff00000u) | (base << 16) | (rt << 12) | 647 ((u32)disp & 0xfffu)); 648 return; 649 } 650 case ARM_FMT_LDST_T4: { 651 u32 rt = parse_reg(d), base; 652 i64 disp; 653 u32 add, imm8; 654 expect_comma(d); 655 parse_mem(d, &base, &disp); 656 add = disp >= 0 ? 1u : 0u; 657 imm8 = (u32)(disp < 0 ? -disp : disp) & 0xffu; 658 emit_t32(d, (desc->match & 0xfff00000u) | (base << 16) | (rt << 12) | 659 0xc00u | (add << 9) | imm8); 660 return; 661 } 662 case ARM_FMT_LDREX: { 663 u32 rt = parse_reg(d), base; 664 i64 disp = 0; 665 expect_comma(d); 666 parse_mem(d, &base, &disp); 667 emit_t32(d, arm_ldrex(rt, base, (u32)(disp / 4))); 668 return; 669 } 670 case ARM_FMT_STREX: { 671 u32 rd = parse_reg(d), rt, base; 672 i64 disp = 0; 673 expect_comma(d); 674 rt = parse_reg(d); 675 expect_comma(d); 676 parse_mem(d, &base, &disp); 677 emit_t32(d, arm_strex(rd, rt, base, (u32)(disp / 4))); 678 return; 679 } 680 case ARM_FMT_PUSHPOP: { 681 u32 list = parse_reglist(d); 682 emit_t32(d, slice_eq_cstr(mn, "pop.w") ? arm_pop_w(list) 683 : arm_push_w(list)); 684 return; 685 } 686 case ARM_FMT_BARRIER: { 687 u32 opt = parse_barrier_opt(d); 688 emit_t32(d, (desc->match & 0xfffffff0u) | (opt & 0xfu)); 689 return; 690 } 691 case ARM_FMT_TB: { 692 u32 rn, rm, sh; 693 asm_driver_expect_punct(d, '[', "'[' in tbb/tbh operand"); 694 rn = parse_reg(d); 695 expect_comma(d); 696 rm = parse_reg(d); 697 (void)parse_opt_shift(d, &sh); /* lsl #1 for tbh — implied by mnemonic */ 698 asm_driver_expect_punct(d, ']', "']' in tbb/tbh operand"); 699 emit_t32(d, slice_eq_cstr(mn, "tbh") ? arm_tbh(rn, rm) : arm_tbb(rn, rm)); 700 return; 701 } 702 case ARM_FMT_BL: { 703 /* Placeholder MUST match codegen's arm_bl(): the THM_CALL reloc is REL and 704 * the patcher preserves the field's J1/J2 (XOR-S) base, so an ad-hoc 705 * placeholder whose J1/J2 imply a nonzero in-field offset corrupts the 706 * applied target (a +0xC00000 skew on cross-object calls). */ 707 emit_t32(d, arm_bl()); /* placeholder (== codegen) */ 708 /* reloc rides the BL we just emitted (offset = its start). */ 709 { 710 MCEmitter* mc = asm_driver_mc(d); 711 ObjSymId sym = OBJ_SYM_NONE; 712 i64 off = 0; 713 u32 pos = mc_pos(mc) - 4u; 714 asm_driver_parse_sym_expr(d, &sym, &off); 715 mc_emit_reloc_at(mc, mc->section_id, pos, R_ARM_THM_CALL, sym, off, 0, 716 0); 717 } 718 return; 719 } 720 case ARM_FMT_BRANCH_T4: { 721 emit_t32(d, arm_b_w()); 722 { 723 MCEmitter* mc = asm_driver_mc(d); 724 ObjSymId sym = OBJ_SYM_NONE; 725 i64 off = 0; 726 u32 pos = mc_pos(mc) - 4u; 727 asm_driver_parse_sym_expr(d, &sym, &off); 728 mc_emit_reloc_at(mc, mc->section_id, pos, R_ARM_THM_JUMP24, sym, off, 0, 729 0); 730 } 731 return; 732 } 733 case ARM_FMT_BRANCH_T3: { 734 emit_t32(d, arm_b_cond_w(cond)); 735 { 736 MCEmitter* mc = asm_driver_mc(d); 737 ObjSymId sym = OBJ_SYM_NONE; 738 i64 off = 0; 739 u32 pos = mc_pos(mc) - 4u; 740 asm_driver_parse_sym_expr(d, &sym, &off); 741 mc_emit_reloc_at(mc, mc->section_id, pos, R_ARM_THM_JUMP19, sym, off, 0, 742 0); 743 } 744 return; 745 } 746 case ARM_FMT_B16: 747 case ARM_FMT_BCC16: { 748 /* 16-bit branches: emit the 32-bit wide form so the relocation has the 749 * full range (the disassembler still round-trips the wide encoding). */ 750 if (cond == ARM_CC_AL) { 751 emit_t32(d, arm_b_w()); 752 { 753 MCEmitter* mc = asm_driver_mc(d); 754 ObjSymId sym = OBJ_SYM_NONE; 755 i64 off = 0; 756 u32 pos = mc_pos(mc) - 4u; 757 asm_driver_parse_sym_expr(d, &sym, &off); 758 mc_emit_reloc_at(mc, mc->section_id, pos, R_ARM_THM_JUMP24, sym, off, 759 0, 0); 760 } 761 } else { 762 emit_t32(d, arm_b_cond_w(cond)); 763 { 764 MCEmitter* mc = asm_driver_mc(d); 765 ObjSymId sym = OBJ_SYM_NONE; 766 i64 off = 0; 767 u32 pos = mc_pos(mc) - 4u; 768 asm_driver_parse_sym_expr(d, &sym, &off); 769 mc_emit_reloc_at(mc, mc->section_id, pos, R_ARM_THM_JUMP19, sym, off, 770 0, 0); 771 } 772 } 773 return; 774 } 775 case ARM_FMT_CBZ: { 776 u32 rn = parse_reg(d); 777 i64 imm; 778 expect_comma(d); 779 (void)asm_driver_eat_punct(d, '#'); 780 imm = asm_driver_parse_const(d); 781 { 782 /* imm is the PC-relative byte offset (must be +4..+130, even). The 783 * encoded imm6 is (off-4)/2 half-words from the branch. */ 784 i64 hw = (imm - 4) / 2; 785 u32 op = slice_eq_cstr(mn, "cbnz") ? 1u : 0u; 786 if (imm < 4 || imm > 130 || (imm & 1)) 787 asm_driver_panic(d, "arm32 asm: cbz/cbnz target out of range"); 788 emit_t16(d, arm_cbz_raw(op, rn, (u32)hw)); 789 } 790 return; 791 } 792 case ARM_FMT_IT: { 793 /* `it<x><y><z> cc` — the suffix letters arrive folded into the mnemonic 794 * (e.g. "itte"); we recover the mask from the suffix + the firstcond. */ 795 AsmTok t = asm_driver_next(d); 796 Slice ccn; 797 int fc; 798 u32 mask; 799 size_t nletters = mn.len - 2; /* letters after "it" */ 800 if (t.kind != ASM_TOK_IDENT) 801 asm_driver_panic(d, "arm32 asm: IT expects a condition"); 802 ccn = pool_slice(asm_driver_pool(d), t.v.ident); 803 fc = arm32_cond_from_name(ccn); 804 if (fc < 0) asm_driver_panic(d, "arm32 asm: bad IT condition"); 805 /* Build the mask: bit3=1; for each of the up-to-3 suffix letters, bit 806 * (3-k) is set to firstcond[0] for 't' (then) or its inverse for 'e'. */ 807 mask = 0x8u; 808 { 809 u32 then = (u32)fc & 1u; 810 for (size_t k = 0; k < nletters && k < 3; ++k) { 811 u32 bit = (mn.s[2 + k] == 't') ? then : (then ^ 1u); 812 mask |= (bit << (3u - (u32)(k + 1))); 813 } 814 /* The lowest set bit position marks the block end. */ 815 mask |= (1u << (3u - (u32)nletters)); 816 } 817 emit_t16(d, arm_it((u32)fc, mask)); 818 return; 819 } 820 case ARM_FMT_MOVHI16: { 821 u32 rd = parse_reg(d), rm; 822 expect_comma(d); 823 /* `mov rd, #imm` selects the 32-bit mov.w (preserving mov's no-flags 824 * semantics; conditionalizable inside an IT block as `moveq`/etc.). */ 825 if (!peek_is_reg(d)) { 826 u32 out12; 827 if (!thumb_expand_imm_encode((u32)parse_imm(d), &out12)) 828 asm_driver_panic(d, "arm32 asm: mov immediate not encodable"); 829 emit_t32(d, arm_mov_imm(rd, out12)); 830 return; 831 } 832 rm = parse_reg(d); 833 emit_t16(d, arm_mov_hi(rd, rm)); 834 return; 835 } 836 case ARM_FMT_BX: { 837 u32 rm = parse_reg(d); 838 emit_t16(d, slice_eq_cstr(mn, "blx") ? arm_blx_reg(rm) : arm_bx(rm)); 839 return; 840 } 841 case ARM_FMT_BKPT: { 842 u32 imm8 = (u32)parse_imm(d) & 0xffu; 843 emit_t16(d, slice_eq_cstr(mn, "udf") ? arm_udf(imm8) : arm_bkpt(imm8)); 844 return; 845 } 846 case ARM_FMT_EXT16: { 847 u32 rd = parse_reg(d), rm; 848 expect_comma(d); 849 rm = parse_reg(d); 850 emit_t16(d, (u16)((desc->match & 0xffc0u) | ((rm & 7u) << 3) | (rd & 7u))); 851 return; 852 } 853 case ARM_FMT_DPI8_16: { 854 u32 rd = parse_reg(d), imm8; 855 expect_comma(d); 856 imm8 = (u32)parse_imm(d) & 0xffu; 857 emit_t16(d, (u16)((desc->match & 0xf800u) | ((rd & 7u) << 8) | imm8)); 858 return; 859 } 860 case ARM_FMT_ADDSUB3_16: { 861 u32 rd = parse_reg(d), rn, imm3; 862 expect_comma(d); 863 rn = parse_reg(d); 864 expect_comma(d); 865 imm3 = (u32)parse_imm(d) & 7u; 866 emit_t16(d, (u16)((desc->match & 0xfe00u) | (imm3 << 6) | ((rn & 7u) << 3) | 867 (rd & 7u))); 868 return; 869 } 870 case ARM_FMT_ADDSUBR_16: { 871 u32 rd = parse_reg(d), rn, rm; 872 expect_comma(d); 873 rn = parse_reg(d); 874 expect_comma(d); 875 rm = parse_reg(d); 876 emit_t16(d, (u16)((desc->match & 0xfe00u) | ((rm & 7u) << 6) | 877 ((rn & 7u) << 3) | (rd & 7u))); 878 return; 879 } 880 case ARM_FMT_SHIFTI_16: { 881 u32 rd = parse_reg(d), rm, imm5; 882 expect_comma(d); 883 rm = parse_reg(d); 884 expect_comma(d); 885 imm5 = (u32)parse_imm(d) & 0x1fu; 886 emit_t16(d, (u16)((desc->match & 0xf800u) | (imm5 << 6) | ((rm & 7u) << 3) | 887 (rd & 7u))); 888 return; 889 } 890 case ARM_FMT_ALU_16: { 891 u32 rdn = parse_reg(d), rm; 892 expect_comma(d); 893 rm = parse_reg(d); 894 if (slice_eq_cstr(mn, "muls")) { 895 /* muls rdm, rn, rdm — skip the (redundant) third operand if present. */ 896 if (asm_driver_eat_comma(d)) (void)parse_reg(d); 897 } 898 emit_t16(d, (u16)((desc->match & 0xffc0u) | ((rm & 7u) << 3) | (rdn & 7u))); 899 return; 900 } 901 case ARM_FMT_HIREG_16: { 902 u32 rdn = parse_reg(d), rm; 903 expect_comma(d); 904 /* `cmp` resolves to this hi-register compare first; an immediate second 905 * operand (`cmp rn, #imm`) selects the modified-immediate compare — the 906 * 16-bit DPI8 form when rn<=r7 and imm8 fits, else the 32-bit cmp.w. */ 907 if (!peek_is_reg(d) && slice_eq_cstr(mn, "cmp")) { 908 i64 imm = parse_imm(d); 909 if (rdn <= 7u && imm >= 0 && imm <= 255) 910 emit_t16(d, (u16)(0x2800u | (rdn << 8) | (u32)(imm & 0xffu))); 911 else { 912 u32 out12; 913 if (!thumb_expand_imm_encode((u32)imm, &out12)) 914 asm_driver_panic(d, "arm32 asm: cmp immediate not encodable"); 915 emit_t32(d, arm_cmp_imm(rdn, out12)); 916 } 917 return; 918 } 919 rm = parse_reg(d); 920 /* A third operand means the general `add rd, rn, <#imm | rm>` (rd=rdn, 921 * rn=rm): there is no 16-bit non-flag add-immediate or 3-register form, so 922 * route it through the shared encoder (which also covers the sp-relative 923 * forms). Without a third operand this is the 2-operand hi-register 924 * `add rdn, rm` below; the old SP-only special case dropped every other 925 * immediate. */ 926 if (slice_eq_cstr(mn, "add") && asm_driver_eat_comma(d)) { 927 arm_asm_addsub_third(d, 0, rdn, rm); 928 return; 929 } 930 emit_t16(d, (u16)((desc->match & 0xff00u) | (((rdn >> 3) & 1u) << 7) | 931 ((rm & 0xfu) << 3) | (rdn & 7u))); 932 return; 933 } 934 case ARM_FMT_LDSTI5_16: { 935 u32 rt = parse_reg(d), base; 936 i64 disp; 937 u32 op, scale, imm5; 938 expect_comma(d); 939 parse_mem(d, &base, &disp); 940 op = (desc->match >> 11) & 0x1fu; 941 scale = (op <= 0x0du) ? 4u : (op <= 0x0fu) ? 1u : 2u; 942 /* A word str/ldr through SP uses the dedicated SP-relative T2 encoding 943 * (0x9000/0x9800, imm8*4); the low-register T1 form here would truncate 944 * sp(r13) to r5. */ 945 if (base == 13u && rt <= 7u && 946 (slice_eq_cstr(mn, "str") || slice_eq_cstr(mn, "ldr"))) { 947 u32 sp_op = slice_eq_cstr(mn, "ldr") ? 0x9800u : 0x9000u; 948 emit_t16(d, (u16)(sp_op | ((rt & 7u) << 8) | (((u32)disp / 4u) & 0xffu))); 949 return; 950 } 951 /* The 16-bit T1 forms only encode low transfer + base registers and a 952 * scaled imm5. A high register (e.g. saving sp/lr in the coroutine switch, 953 * `str lr, [r0, #36]`), a high base, or an out-of-range / unscalable 954 * displacement falls to the 32-bit T3 form (hw1 op|rn, hw2 rt<<12|imm12). */ 955 if (rt > 7u || base > 7u || disp < 0 || (u32)disp > 31u * scale || 956 (u32)disp % scale != 0u) { 957 u32 t3 = (desc->match == 0x6000u) ? 0xf8c0u /* str */ 958 : (desc->match == 0x6800u) ? 0xf8d0u /* ldr */ 959 : (desc->match == 0x7000u) ? 0xf880u /* strb */ 960 : (desc->match == 0x7800u) ? 0xf890u /* ldrb */ 961 : (desc->match == 0x8000u) ? 0xf8a0u /* strh */ 962 : 0xf8b0u; /* ldrh */ 963 if (disp < 0 || disp > 4095) 964 asm_driver_panic(d, "arm32 asm: ldr/str offset out of T3 range"); 965 emit_t32(d, arm_t32(t3 | (base & 0xfu), 966 ((rt & 0xfu) << 12) | ((u32)disp & 0xfffu))); 967 return; 968 } 969 imm5 = ((u32)disp / scale) & 0x1fu; 970 emit_t16(d, (u16)((desc->match & 0xf800u) | (imm5 << 6) | 971 ((base & 7u) << 3) | (rt & 7u))); 972 return; 973 } 974 case ARM_FMT_LDSTSP_16: { 975 u32 rt = parse_reg(d), base; 976 i64 disp; 977 expect_comma(d); 978 parse_mem(d, &base, &disp); 979 emit_t16(d, (u16)((desc->match & 0xf800u) | ((rt & 7u) << 8) | 980 (((u32)disp / 4u) & 0xffu))); 981 return; 982 } 983 case ARM_FMT_ADDSP_16: { 984 u32 rd = parse_reg(d), imm8; 985 expect_comma(d); 986 (void)parse_reg(d); /* sp/pc — implied by the mnemonic/encoding */ 987 expect_comma(d); 988 imm8 = ((u32)parse_imm(d) / 4u) & 0xffu; 989 emit_t16(d, (u16)((desc->match & 0xf800u) | ((rd & 7u) << 8) | imm8)); 990 return; 991 } 992 case ARM_FMT_ADJSP_16: { 993 /* Plain `sub` (and the unreachable plain-`add` row) land here. Parse the 994 * real registers and route through the shared add/sub encoder, which picks 995 * the 16-bit sp-adjust when rd==rn==sp. Accept the 2-operand shorthand 996 * `sub rd, #imm` (== `sub rd, rd, #imm`); the old code assumed sp/sp and 997 * silently mis-encoded `sub rN, rN, #imm` as `sub sp, sp, #...`. */ 998 int is_sub = slice_eq_cstr(mn, "sub"); 999 u32 rd = parse_reg(d), rn; 1000 expect_comma(d); 1001 if (peek_is_reg(d)) { 1002 rn = parse_reg(d); 1003 expect_comma(d); 1004 } else { 1005 rn = rd; 1006 } 1007 arm_asm_addsub_third(d, is_sub, rd, rn); 1008 return; 1009 } 1010 case ARM_FMT_PUSHPOP_16: { 1011 u32 list = parse_reglist(d); 1012 u32 lo = list & 0xffu; 1013 u32 extra = slice_eq_cstr(mn, "pop") ? ((list >> 15) & 1u) 1014 : ((list >> 14) & 1u); 1015 emit_t16(d, (u16)((desc->match & 0xfe00u) | (extra << 8) | lo)); 1016 return; 1017 } 1018 } 1019 asm_driver_panic(d, "arm32 asm: unhandled instruction format"); 1020 } 1021 1022 static void arm32_arch_asm_insn(ArchAsm* base, AsmDriver* d, Sym mnemonic) { 1023 const Arm32InsnDesc* desc; 1024 u32 cond = ARM_CC_AL; 1025 (void)base; 1026 (void)asm_driver_cur_section(d); /* ensure .text exists */ 1027 desc = resolve_mnemonic(d, pool_slice(asm_driver_pool(d), mnemonic), &cond); 1028 assemble_one(d, desc, cond); 1029 } 1030 1031 void arm32_asm_insn(Arm32Asm* a, AsmDriver* d, Sym mnemonic) { 1032 arm32_arch_asm_insn(&a->base, d, mnemonic); 1033 } 1034 1035 static void arm32_arch_asm_destroy(ArchAsm* base) { (void)base; } 1036 1037 static void run_one_line(Arm32Asm* a, MCEmitter* mc, const char* text, 1038 size_t len) { 1039 size_t i; 1040 for (i = 0; i < len; ++i) { 1041 if (text[i] != ' ' && text[i] != '\t') break; 1042 } 1043 if (i == len) return; 1044 1045 AsmLexer* lx = asm_lex_open_mem(a->c, "<inline-asm>", text, len); 1046 AsmDriver* d = asm_driver_open_inline(a->c, mc, lx); 1047 1048 AsmTok t = asm_driver_peek(d); 1049 while (t.kind == ASM_TOK_NEWLINE || t.kind == ASM_TOK_HASH) { 1050 (void)asm_driver_next(d); 1051 if (t.kind == ASM_TOK_HASH) { 1052 while (!asm_driver_at_eol(d)) (void)asm_driver_next(d); 1053 } 1054 t = asm_driver_peek(d); 1055 } 1056 if (t.kind == ASM_TOK_EOF) { 1057 asm_driver_close_inline(d); 1058 asm_lex_close(lx); 1059 return; 1060 } 1061 if (t.kind != ASM_TOK_IDENT) 1062 inline_panic(a, "expected mnemonic at start of inline asm line"); 1063 (void)asm_driver_next(d); 1064 Sym mn = t.v.ident; 1065 AsmTok dot = asm_driver_peek(d); 1066 while (asm_driver_tok_is_punct(dot, '.')) { 1067 (void)asm_driver_next(d); 1068 AsmTok rest = asm_driver_next(d); 1069 if (rest.kind != ASM_TOK_IDENT) 1070 inline_panic(a, "composite mnemonic: expected ident after '.'"); 1071 Slice hsl = pool_slice(asm_driver_pool(d), mn); 1072 Slice rsl = pool_slice(asm_driver_pool(d), rest.v.ident); 1073 char buf[64]; 1074 if (hsl.len + 1u + rsl.len >= sizeof buf) 1075 inline_panic(a, "composite mnemonic too long"); 1076 memcpy(buf, hsl.s, hsl.len); 1077 buf[hsl.len] = '.'; 1078 memcpy(buf + hsl.len + 1u, rsl.s, rsl.len); 1079 mn = pool_intern_slice(asm_driver_pool(d), 1080 (Slice){.s = buf, .len = hsl.len + 1u + rsl.len}); 1081 dot = asm_driver_peek(d); 1082 } 1083 arm32_asm_insn(a, d, mn); 1084 asm_driver_close_inline(d); 1085 asm_lex_close(lx); 1086 } 1087 1088 static void render_and_run_line(Arm32Asm* a, MCEmitter* mc, StrBuf* sb, 1089 const char* start, const char* end) { 1090 strbuf_reset(sb); 1091 for (const char* p = start; p < end; ++p) { 1092 char c = *p; 1093 if (c != '%') { 1094 strbuf_putc(sb, c); 1095 continue; 1096 } 1097 if (p + 1 >= end) inline_panic(a, "trailing '%' in template"); 1098 char n = *(p + 1); 1099 if (n == '%') { 1100 strbuf_putc(sb, '%'); 1101 ++p; 1102 continue; 1103 } 1104 int form = 0; 1105 if (n == 'a') { 1106 form = 3; 1107 ++p; 1108 if (p + 1 >= end) inline_panic(a, "trailing '%' modifier in template"); 1109 n = *(p + 1); 1110 } 1111 if (n == '[') { 1112 const char* nbeg = p + 2; 1113 const char* nend = nbeg; 1114 while (nend < end && *nend != ']') ++nend; 1115 if (nend == end) inline_panic(a, "unterminated %[name]"); 1116 Sym needle = pool_intern_slice( 1117 a->c->global, (Slice){.s = nbeg, .len = (size_t)(nend - nbeg)}); 1118 u32 idx = lookup_named(a, needle); 1119 if (idx == (u32)-1) 1120 inline_panic(a, "%[name] does not match any constraint"); 1121 p = nend; 1122 render_operand(a, sb, idx, form); 1123 continue; 1124 } 1125 if (n < '0' || n > '9') inline_panic(a, "expected digit after '%'"); 1126 u32 idx = (u32)(n - '0'); 1127 ++p; 1128 if (p + 1 < end && *(p + 1) >= '0' && *(p + 1) <= '9') { 1129 idx = idx * 10u + (u32)(*(p + 1) - '0'); 1130 ++p; 1131 } 1132 render_operand(a, sb, idx, form); 1133 } 1134 if (sb->truncated) inline_panic(a, "inline asm line buffer overflow"); 1135 run_one_line(a, mc, strbuf_cstr(sb), strbuf_len(sb)); 1136 } 1137 1138 void arm32_asm_run_template(Arm32Asm* a, MCEmitter* mc, const char* tmpl) { 1139 if (!tmpl || !*tmpl) return; 1140 1141 char buf[512]; 1142 StrBuf sb; 1143 strbuf_init(&sb, buf, sizeof buf); 1144 1145 const char* line_start = tmpl; 1146 int bracket = 0; 1147 char quote = 0; 1148 for (const char* p = tmpl;; ++p) { 1149 char c = *p; 1150 if (c == '\0') { 1151 render_and_run_line(a, mc, &sb, line_start, p); 1152 break; 1153 } 1154 if (quote) { 1155 if (c == '\\' && *(p + 1)) { 1156 ++p; 1157 continue; 1158 } 1159 if (c == quote) quote = 0; 1160 continue; 1161 } 1162 if (c == '"' || c == '\'') { 1163 quote = c; 1164 continue; 1165 } 1166 if (c == '[') { 1167 ++bracket; 1168 continue; 1169 } 1170 if (c == ']' && bracket > 0) { 1171 --bracket; 1172 continue; 1173 } 1174 if ((c == '\n' || c == ';') && bracket == 0) { 1175 render_and_run_line(a, mc, &sb, line_start, p); 1176 line_start = p + 1; 1177 } 1178 } 1179 } 1180 1181 /* ---- textual-assembly operand syntax (printer <-> parser seam) ---- 1182 * Inverse of the `.s` parsers above. ARM uses the GNU-as `:lower16:`/`:upper16:` 1183 * prefix form for MOVW/MOVT symbol halves and bare numeric targets for branches 1184 * (the symbolizer synthesizes labels for the local-branch set). */ 1185 static int arm32_reloc_operand(u16 kind, KitObjFmt fmt, ArchRelocOperand* out) { 1186 (void)fmt; 1187 out->prefix = ""; 1188 out->suffix = ""; 1189 out->addend_bias = 0; 1190 out->emit_anchor = 0; 1191 out->ref_anchor = 0; 1192 switch (kind) { 1193 case R_ARM_THM_MOVW_ABS_NC: 1194 out->surg = ARCH_RELOC_SURG_TAIL; 1195 out->prefix = "#:lower16:"; 1196 return 1; 1197 case R_ARM_THM_MOVT_ABS: 1198 out->surg = ARCH_RELOC_SURG_TAIL; 1199 out->prefix = "#:upper16:"; 1200 return 1; 1201 case R_ARM_THM_CALL: 1202 case R_ARM_THM_JUMP24: 1203 case R_ARM_THM_JUMP19: 1204 out->surg = ARCH_RELOC_SURG_TAIL; 1205 return 1; 1206 default: 1207 return 0; /* R_ABS32 / R_REL32 / TLS -> keep numeric */ 1208 } 1209 } 1210 1211 /* Intra-section local branches whose target the disassembler renders 1212 * numerically; cc -S synthesizes a label there. Calls (bl) are excluded. */ 1213 static int arm32_is_local_branch(KitSlice m) { 1214 if (m.len >= 1 && m.s[0] == 'b') { 1215 if (slice_eq_cstr(m, "b") || slice_eq_cstr(m, "b.w")) return 1; 1216 if (slice_eq_cstr(m, "cbz") || slice_eq_cstr(m, "cbnz")) return 1; 1217 /* b<cond> / b<cond>.w */ 1218 { 1219 size_t base = m.len; 1220 if (m.len >= 3 && m.s[m.len - 2] == '.' && m.s[m.len - 1] == 'w') 1221 base = m.len - 2; 1222 if (base == 3) { 1223 if (arm32_cond_from_name(arm_slice(m.s + 1, 2)) >= 0) return 1; 1224 } 1225 } 1226 } 1227 return 0; 1228 } 1229 1230 const ArchAsmOps arm32_asm_ops = { 1231 .reloc_operand = arm32_reloc_operand, 1232 .is_local_branch = arm32_is_local_branch, 1233 /* ARM EABI: function symbols carry a Thumb (LSB) ISA-state bit; the 1234 * standalone assembler's `.thumb_func` / `.size . - SYM` / label-define 1235 * bookkeeping keys off this flag (see ArchAsmOps). */ 1236 .thumb_function_symbols = 1, 1237 }; 1238 1239 ArchAsm* arm32_arch_asm_new(Compiler* c) { 1240 Arm32Asm* a = arm32_asm_open(c); 1241 a->base.insn = arm32_arch_asm_insn; 1242 a->base.destroy = arm32_arch_asm_destroy; 1243 return &a->base; 1244 }