asm.c (55290B)
1 /* RV64 assembler — descriptor-table driven. 2 * 3 * Mnemonic → Rv64InsnDesc via rv64_asm_find; operand parsing dispatches 4 * on the format kind. The descriptor's `match` field already carries 5 * the funct3/funct7/opcode bits; the parser only needs to fill in the 6 * register operands and immediate. 7 * 8 * Aliases (li, mv, ret, jr, j, nop, sext.w, beqz, bnez) are recognized 9 * by their alias rows in the descriptor table and rewritten to the 10 * canonical encoding here. Inline rv_* encoders in isa.h remain the 11 * hot path for codegen; the assembler uses them to assemble the 12 * machine word once it has the operand values. */ 13 14 #include "arch/riscv/asm.h" 15 16 #include <string.h> 17 18 #include "arch/riscv/isa.h" 19 #include "arch/riscv/regs.h" 20 #include "arch/riscv/rv64.h" 21 #include "arch/riscv/variant.h" 22 #include "asm/asm_helpers.h" 23 #include "core/arena.h" 24 #include "core/pool.h" 25 #include "core/slice.h" 26 #include "core/strbuf.h" 27 #include "obj/obj.h" 28 29 struct Rv64Asm { 30 ArchAsm base; 31 Compiler* c; 32 33 /* Inline-asm bound state (set by rv64_inline_bind, cleared otherwise). 34 * Operand indexing per GCC convention: 0..nout-1 are outputs, then 35 * nout..nout+nin-1 are inputs. Templates address into this combined 36 * list via %N / %zN / %aN / %w[name] / %x[name]. */ 37 const AsmConstraint* outs; 38 Operand* out_ops; 39 const AsmConstraint* ins; 40 const Operand* in_ops; 41 const Sym* clobbers; 42 u32 nout; 43 u32 nin; 44 u32 nclob; 45 }; 46 47 typedef struct Rv64Asm Rv64Asm; 48 49 /* Relocation modifier on a 12-bit immediate offset (`%lo`/`%pcrel_lo`). 50 * RV_MEMMOD_NONE means a plain numeric displacement in `disp`. */ 51 typedef enum RvMemMod { 52 RV_MEMMOD_NONE = 0, 53 RV_MEMMOD_LO, 54 RV_MEMMOD_PCREL_LO, 55 } RvMemMod; 56 57 typedef struct Rv64Mem { 58 i32 disp; 59 u32 base; 60 RvMemMod mod; /* reloc modifier on the offset, or RV_MEMMOD_NONE */ 61 ObjSymId sym; /* symbol when mod != NONE */ 62 i64 off; /* addend when mod != NONE */ 63 } Rv64Mem; 64 65 static int sym_to_cstr(AsmDriver* d, Sym s, char* out, size_t cap) { 66 Slice sl = pool_slice(asm_driver_pool(d), s); 67 if (!sl.s || sl.len >= cap) return 0; 68 memcpy(out, sl.s, sl.len); 69 out[sl.len] = '\0'; 70 return 1; 71 } 72 73 /* True if `s` begins with the NUL-terminated literal `pfx` (length-explicit). 74 */ 75 static bool slice_has_prefix_cstr(Slice s, const char* pfx, size_t n) { 76 return s.len >= n && memcmp(s.s, pfx, n) == 0; 77 } 78 79 static int rv_reg_from_name(AsmDriver* d, Sym s, u32* reg_out, int* fp_out) { 80 char name[16]; 81 uint32_t dwarf = 0; 82 if (!sym_to_cstr(d, s, name, sizeof name)) return 0; 83 if (rv64_register_index(name, &dwarf) != 0) return 0; 84 if (reg_out) *reg_out = dwarf & 31u; 85 if (fp_out) *fp_out = dwarf >= 32u; 86 return 1; 87 } 88 89 static u32 parse_reg(AsmDriver* d, int* fp_out) { 90 AsmTok t = asm_driver_next(d); 91 u32 r; 92 if (t.kind != ASM_TOK_IDENT || !rv_reg_from_name(d, t.v.ident, &r, fp_out)) 93 asm_driver_panic(d, "rv64 asm: bad register"); 94 return r; 95 } 96 97 static u32 parse_xreg(AsmDriver* d) { 98 int fp = 0; 99 u32 r = parse_reg(d, &fp); 100 if (fp) asm_driver_panic(d, "rv64 asm: expected integer register"); 101 return r; 102 } 103 104 static u32 parse_freg(AsmDriver* d) { 105 int fp = 0; 106 u32 r = parse_reg(d, &fp); 107 if (!fp) asm_driver_panic(d, "rv64 asm: expected float register"); 108 return r; 109 } 110 111 static void expect_comma(AsmDriver* d) { 112 if (!asm_driver_eat_comma(d)) asm_driver_panic(d, "rv64 asm: expected ','"); 113 } 114 115 /* Parse a CSR operand: a standard CSR name (mstatus, mtvec, ...) or a bare 116 * numeric expression. Returns the 12-bit CSR number. */ 117 static u32 parse_csr(AsmDriver* d) { 118 AsmTok t = asm_driver_peek(d); 119 if (t.kind == ASM_TOK_IDENT) { 120 u16 num; 121 if (rv64_csr_num_from_name(pool_slice(asm_driver_pool(d), t.v.ident), 122 &num)) { 123 (void)asm_driver_next(d); /* consume the name */ 124 return (u32)num & 0xfffu; 125 } 126 } 127 return (u32)asm_driver_parse_const(d) & 0xfffu; 128 } 129 130 /* Position of a `%mod(sym)` relocation operand: the 20-bit upper field of 131 * lui/auipc, or a 12-bit I-type (addi/load) or S-type (store) immediate. */ 132 typedef enum RvModPos { 133 RV_MODPOS_HI20, 134 RV_MODPOS_LO_I, 135 RV_MODPOS_LO_S, 136 } RvModPos; 137 138 /* Map a relocation-modifier name (`hi`, `lo`, `pcrel_hi`, `pcrel_lo`, 139 * `got_pcrel_hi`) to the RelocKind appropriate for `pos`. Panics on a name 140 * that is not valid at this operand position. */ 141 static RelocKind rv_mod_to_reloc(AsmDriver* d, Slice name, RvModPos pos) { 142 if (pos == RV_MODPOS_HI20) { 143 if (slice_eq_cstr(name, "hi")) return R_RV_HI20; 144 if (slice_eq_cstr(name, "pcrel_hi")) return R_RV_PCREL_HI20; 145 if (slice_eq_cstr(name, "got_pcrel_hi")) return R_RV_GOT_HI20; 146 } else { 147 int store = (pos == RV_MODPOS_LO_S); 148 if (slice_eq_cstr(name, "lo")) return store ? R_RV_LO12_S : R_RV_LO12_I; 149 if (slice_eq_cstr(name, "pcrel_lo")) 150 return store ? R_RV_PCREL_LO12_S : R_RV_PCREL_LO12_I; 151 } 152 asm_driver_panic(d, "rv64 asm: relocation modifier not valid here"); 153 } 154 155 /* If the next token is `%`, parse a `%mod(sym{+off})` relocation operand, 156 * emit the relocation at the current emit position (where the about-to-be- 157 * returned instruction word will land), and return 1. The caller encodes a 158 * zero placeholder in the immediate field. Returns 0 if there is no modifier 159 * (leaving the stream untouched for normal constant parsing). A leading `%` 160 * is unambiguous here: modulo is infix and never starts an operand. */ 161 static int rv_parse_mod_reloc(AsmDriver* d, RvModPos pos, ObjSymId* sym_out, 162 i64* off_out, RelocKind* kind_out) { 163 if (!asm_driver_tok_is_punct(asm_driver_peek(d), '%')) return 0; 164 (void)asm_driver_next(d); /* eat '%' */ 165 AsmTok name = asm_driver_next(d); 166 if (name.kind != ASM_TOK_IDENT) 167 asm_driver_panic(d, "rv64 asm: expected relocation modifier name"); 168 Slice nm = pool_slice(asm_driver_pool(d), name.v.ident); 169 asm_driver_expect_punct(d, '(', "'(' after relocation modifier"); 170 ObjSymId sym = OBJ_SYM_NONE; 171 i64 off = 0; 172 asm_driver_parse_sym_expr(d, &sym, &off); 173 asm_driver_expect_punct(d, ')', "')' after %mod(sym)"); 174 RelocKind k = rv_mod_to_reloc(d, nm, pos); 175 if (sym_out) *sym_out = sym; 176 if (off_out) *off_out = off; 177 if (kind_out) *kind_out = k; 178 return 1; 179 } 180 181 /* Parse a RISC-V rounding-mode mnemonic (the comma is already consumed) into 182 * its 3-bit funct3 value. cc -S emits this suffix on fcvt/fsqrt when the mode 183 * isn't the default `dyn`, so the round-trip (and clang) re-encode the exact 184 * mode rather than guessing a default. */ 185 static u32 rv_parse_rm_name(AsmDriver* d) { 186 AsmTok t = asm_driver_next(d); 187 Slice s; 188 if (t.kind != ASM_TOK_IDENT) 189 asm_driver_panic(d, "rv64 asm: expected rounding mode"); 190 s = pool_slice(asm_driver_pool(d), t.v.ident); 191 if (slice_eq_cstr(s, "rne")) return 0u; 192 if (slice_eq_cstr(s, "rtz")) return 1u; 193 if (slice_eq_cstr(s, "rdn")) return 2u; 194 if (slice_eq_cstr(s, "rup")) return 3u; 195 if (slice_eq_cstr(s, "rmm")) return 4u; 196 if (slice_eq_cstr(s, "dyn")) return 7u; 197 asm_driver_panic(d, "rv64 asm: unknown rounding mode"); 198 } 199 200 /* Emit a relocation for a U-type / I-type immediate `%mod(sym)` operand at 201 * the current instruction position; returns 1 if one was present. */ 202 static int rv_emit_imm_mod_reloc(AsmDriver* d, RvModPos pos) { 203 ObjSymId sym; 204 i64 off; 205 RelocKind k; 206 if (!rv_parse_mod_reloc(d, pos, &sym, &off, &k)) return 0; 207 MCEmitter* mc = asm_driver_mc(d); 208 mc_emit_reloc_at(mc, mc->section_id, mc_pos(mc), k, sym, off, 0, 0); 209 return 1; 210 } 211 212 static Rv64Mem parse_mem(AsmDriver* d) { 213 Rv64Mem m; 214 m.disp = 0; 215 m.mod = RV_MEMMOD_NONE; 216 m.sym = OBJ_SYM_NONE; 217 m.off = 0; 218 if (asm_driver_tok_is_punct(asm_driver_peek(d), '%')) { 219 /* `%lo(sym)(base)` / `%pcrel_lo(label)(base)` — record the modifier; the 220 * load/store caller emits the I- or S-type relocation. */ 221 ObjSymId sym; 222 i64 off; 223 RelocKind k; 224 (void)rv_parse_mod_reloc(d, RV_MODPOS_LO_I, &sym, &off, &k); 225 m.mod = (k == R_RV_PCREL_LO12_I) ? RV_MEMMOD_PCREL_LO : RV_MEMMOD_LO; 226 m.sym = sym; 227 m.off = off; 228 } else { 229 m.disp = (i32)asm_driver_parse_const(d); 230 } 231 asm_driver_expect_punct(d, '(', "'(' in rv64 memory operand"); 232 m.base = parse_xreg(d); 233 asm_driver_expect_punct(d, ')', "')' in rv64 memory operand"); 234 return m; 235 } 236 237 /* Emit the I/S-type relocation recorded by parse_mem for a `%lo`/`%pcrel_lo` 238 * memory offset, picking the S-type variant for stores. */ 239 static void rv_emit_mem_mod_reloc(AsmDriver* d, const Rv64Mem* m, 240 int is_store) { 241 if (m->mod == RV_MEMMOD_NONE) return; 242 RelocKind k = (m->mod == RV_MEMMOD_PCREL_LO) 243 ? (is_store ? R_RV_PCREL_LO12_S : R_RV_PCREL_LO12_I) 244 : (is_store ? R_RV_LO12_S : R_RV_LO12_I); 245 MCEmitter* mc = asm_driver_mc(d); 246 mc_emit_reloc_at(mc, mc->section_id, mc_pos(mc), k, m->sym, m->off, 0, 0); 247 } 248 249 /* Fence pred/succ parser — accepts a string like "rw" / "iorw" / "0" / 250 * a numeric literal. Returns the 4-bit mask: bit3=i, bit2=o, bit1=r, 251 * bit0=w. */ 252 static u32 parse_fence_mask(AsmDriver* d) { 253 AsmTok t = asm_driver_peek(d); 254 if (t.kind == ASM_TOK_NUM) { 255 (void)asm_driver_next(d); 256 return (u32)asm_driver_parse_const(d) & 0xfu; 257 } 258 if (t.kind == ASM_TOK_IDENT) { 259 char name[8]; 260 AsmTok tt = asm_driver_next(d); 261 if (!sym_to_cstr(d, tt.v.ident, name, sizeof name)) 262 asm_driver_panic(d, "rv64 asm: bad fence mask"); 263 u32 mask = 0; 264 for (const char* p = name; *p; ++p) { 265 switch (*p) { 266 case 'i': 267 mask |= 8u; 268 break; 269 case 'o': 270 mask |= 4u; 271 break; 272 case 'r': 273 mask |= 2u; 274 break; 275 case 'w': 276 mask |= 1u; 277 break; 278 default: 279 asm_driver_panic(d, "rv64 asm: bad fence char"); 280 } 281 } 282 return mask; 283 } 284 asm_driver_panic(d, "rv64 asm: bad fence operand"); 285 } 286 287 /* The XLEN variant for the assembly target. Reached off the AsmDriver's 288 * Compiler so the stateless encoders can gate rv32-vs-rv64 behavior 289 * (shamt width, addiw availability). */ 290 static const RiscvVariant* rv_asm_variant(AsmDriver* d) { 291 return riscv_variant_for_kind(asm_driver_compiler(d)->target.arch); 292 } 293 294 /* Field overlay onto a descriptor's `match` word. 295 * 296 * For most formats the descriptor's match already pins opcode + 297 * funct3 + funct7. We OR in the per-operand fields. For shift-imm and 298 * AMO families the layouts diverge from the basic R/I templates — we 299 * handle those explicitly below. */ 300 301 static u32 enc_r(u32 match, u32 rd, u32 rs1, u32 rs2) { 302 return match | ((rs2 & 0x1fu) << 20) | ((rs1 & 0x1fu) << 15) | 303 ((rd & 0x1fu) << 7); 304 } 305 static u32 enc_i(u32 match, u32 rd, u32 rs1, i32 imm12) { 306 return match | (((u32)imm12 & 0xfffu) << 20) | ((rs1 & 0x1fu) << 15) | 307 ((rd & 0x1fu) << 7); 308 } 309 static u32 enc_s(u32 match, u32 rs2, u32 rs1, i32 imm12) { 310 return match | rv_imm_s(imm12) | ((rs2 & 0x1fu) << 20) | 311 ((rs1 & 0x1fu) << 15); 312 } 313 static u32 enc_b(u32 match, u32 rs1, u32 rs2, i32 imm13) { 314 return match | rv_imm_b(imm13) | ((rs2 & 0x1fu) << 20) | 315 ((rs1 & 0x1fu) << 15); 316 } 317 static u32 enc_u(u32 match, u32 rd, u32 imm20) { 318 return match | ((imm20 & 0xfffffu) << 12) | ((rd & 0x1fu) << 7); 319 } 320 static u32 enc_j(u32 match, u32 rd, i32 imm21) { 321 return match | rv_imm_j(imm21) | ((rd & 0x1fu) << 7); 322 } 323 static u32 enc_r4(u32 match, u32 rd, u32 rs1, u32 rs2, u32 rs3, u32 rm) { 324 return match | ((rs3 & 0x1fu) << 27) | ((rs2 & 0x1fu) << 20) | 325 ((rs1 & 0x1fu) << 15) | ((rm & 0x7u) << 12) | ((rd & 0x1fu) << 7); 326 } 327 328 /* SLLI/SRLI/SRAI shift-imm. The shamt occupies bits 25:20 on rv64 (6-bit, 329 * funct6 in match) but only bits 24:20 on rv32 (5-bit; bit 25 belongs to 330 * funct7 and MUST stay 0, else the word reads as a different funct7). The 331 * variant's shamt_bits drives the mask; an rv32 shamt >= 32 is rejected. */ 332 static u32 enc_ishift(AsmDriver* d, u32 match, u32 rd, u32 rs1, u32 shamt) { 333 u32 shamt_bits = rv_asm_variant(d)->shamt_bits; 334 u32 shamt_mask = (shamt_bits == 5u) ? 0x1fu : 0x3fu; 335 if (shamt > shamt_mask) 336 asm_driver_panic(d, "rv64 asm: shift amount out of range for target XLEN"); 337 return match | ((shamt & shamt_mask) << 20) | ((rs1 & 0x1fu) << 15) | 338 ((rd & 0x1fu) << 7); 339 } 340 /* RV32 word shift-imm: shamt5 occupies bits 24:20 (funct7 already pinned). */ 341 static u32 enc_ishiftw(u32 match, u32 rd, u32 rs1, u32 shamt) { 342 return match | ((shamt & 0x1fu) << 20) | ((rs1 & 0x1fu) << 15) | 343 ((rd & 0x1fu) << 7); 344 } 345 /* AMO: aq/rl bits 26/25 — we accept them as optional .aq/.rl suffixes 346 * on the mnemonic. For now mnemonics arrive bare. */ 347 static u32 enc_amo(u32 match, u32 aq, u32 rl, u32 rd, u32 rs1, u32 rs2) { 348 return match | ((aq & 1u) << 26) | ((rl & 1u) << 25) | ((rs2 & 0x1fu) << 20) | 349 ((rs1 & 0x1fu) << 15) | ((rd & 0x1fu) << 7); 350 } 351 352 static u32 c_reg3(AsmDriver* d, u32 r) { 353 if (r < 8u || r > 15u) 354 asm_driver_panic(d, 355 "rv64 asm: compressed register must be x8..x15/f8..f15"); 356 return r - 8u; 357 } 358 359 static u32 enc_c_ci(u32 match, u32 rd, i32 imm) { 360 u32 u = (u32)imm & 0x3fu; 361 return match | (((u >> 5) & 1u) << 12) | ((rd & 0x1fu) << 7) | 362 ((u & 0x1fu) << 2); 363 } 364 365 static u32 enc_c_cr(u32 match, u32 rd_rs1, u32 rs2) { 366 return match | ((rd_rs1 & 0x1fu) << 7) | ((rs2 & 0x1fu) << 2); 367 } 368 369 static u32 enc_c_addi16sp(u32 match, i32 imm) { 370 u32 u = (u32)imm & 0x3ffu; 371 return match | (((u >> 9) & 1u) << 12) | (((u >> 4) & 1u) << 6) | 372 (((u >> 6) & 1u) << 5) | (((u >> 7) & 3u) << 3) | 373 (((u >> 5) & 1u) << 2); 374 } 375 376 static u32 enc_c_addi4spn(u32 match, u32 rd3, u32 imm) { 377 u32 enc = (((imm >> 4) & 3u) << 6) | (((imm >> 6) & 0xfu) << 2) | 378 (((imm >> 2) & 1u) << 1) | ((imm >> 3) & 1u); 379 return match | ((enc & 0xffu) << 5) | ((rd3 & 7u) << 2); 380 } 381 382 static u32 enc_c_lwld(u32 match, u32 rd3, u32 rs1_3, u32 off, int wide64) { 383 if (wide64) { 384 return match | (((off >> 3) & 7u) << 10) | ((rs1_3 & 7u) << 7) | 385 (((off >> 6) & 3u) << 5) | ((rd3 & 7u) << 2); 386 } 387 return match | (((off >> 3) & 7u) << 10) | ((rs1_3 & 7u) << 7) | 388 (((off >> 2) & 1u) << 6) | (((off >> 6) & 1u) << 5) | 389 ((rd3 & 7u) << 2); 390 } 391 392 static u32 enc_c_swld(u32 match, u32 rs2_3, u32 rs1_3, u32 off, int wide64) { 393 return enc_c_lwld(match, rs2_3, rs1_3, off, wide64); 394 } 395 396 static u32 enc_c_lwsp(u32 match, u32 rd, u32 off, int wide64) { 397 if (wide64) { 398 return match | (((off >> 5) & 1u) << 12) | ((rd & 0x1fu) << 7) | 399 (((off >> 3) & 3u) << 5) | (((off >> 6) & 7u) << 2); 400 } 401 return match | (((off >> 5) & 1u) << 12) | ((rd & 0x1fu) << 7) | 402 (((off >> 2) & 7u) << 4) | (((off >> 6) & 3u) << 2); 403 } 404 405 static u32 enc_c_swsp(u32 match, u32 rs2, u32 off, int wide64) { 406 u32 imm6; 407 if (wide64) 408 imm6 = (((off >> 3) & 7u) << 3) | ((off >> 6) & 7u); 409 else 410 imm6 = (((off >> 2) & 0xfu) << 2) | ((off >> 6) & 3u); 411 return match | ((imm6 & 0x3fu) << 7) | ((rs2 & 0x1fu) << 2); 412 } 413 414 static u32 enc_c_cb_imm(u32 match, u32 rs1_3, i32 imm) { 415 u32 u = (u32)imm & 0x1ffu; 416 return match | (((u >> 8) & 1u) << 12) | (((u >> 3) & 3u) << 10) | 417 ((rs1_3 & 7u) << 7) | (((u >> 6) & 3u) << 5) | (((u >> 1) & 3u) << 3) | 418 (((u >> 5) & 1u) << 2); 419 } 420 421 static u32 enc_c_cb_alu_imm(u32 match, u32 rd3, i32 imm) { 422 u32 u = (u32)imm & 0x3fu; 423 return match | (((u >> 5) & 1u) << 12) | ((rd3 & 7u) << 7) | 424 ((u & 0x1fu) << 2); 425 } 426 427 static u32 enc_c_cj(u32 match, i32 imm) { 428 u32 u = (u32)imm & 0xfffu; 429 return match | (((u >> 11) & 1u) << 12) | (((u >> 4) & 1u) << 11) | 430 (((u >> 8) & 3u) << 9) | (((u >> 10) & 1u) << 8) | 431 (((u >> 6) & 1u) << 7) | (((u >> 7) & 1u) << 6) | 432 (((u >> 1) & 7u) << 3) | (((u >> 5) & 1u) << 2); 433 } 434 435 /* Parse a branch/jump target operand. With a symbolic target (a label), emit 436 * the relocation at the current position — which is exactly where the caller 437 * is about to write this instruction word — and return 0 as the placeholder 438 * immediate. With a bare constant, return it as the PC-relative byte 439 * displacement (preserving the existing numeric-offset corpus behavior). */ 440 static i32 rv_reloc_target(AsmDriver* d, RelocKind kind) { 441 ObjSymId sym = OBJ_SYM_NONE; 442 i64 off = 0; 443 asm_driver_parse_sym_expr(d, &sym, &off); 444 if (sym != OBJ_SYM_NONE) { 445 MCEmitter* mc = asm_driver_mc(d); 446 mc_emit_reloc_at(mc, mc->section_id, mc_pos(mc), kind, sym, off, 0, 0); 447 return 0; 448 } 449 return (i32)off; 450 } 451 452 /* Per-format parser — reads the operand list off the driver and returns 453 * the encoded 32-bit word, given the matched descriptor. */ 454 static u32 assemble_one(AsmDriver* d, const Rv64InsnDesc* desc) { 455 u32 m = desc->match; 456 u32 rd = 0, rs1 = 0, rs2 = 0; 457 i32 imm = 0; 458 Rv64Mem mem; 459 460 switch ((Rv64Format)desc->fmt) { 461 case RV64_FMT_R: 462 /* Two-operand aliases: snez/neg/negw — rd, rs (rs1=x0). */ 463 if (desc->flags & RV64_ASMFL_ALIAS) { 464 rd = parse_xreg(d); 465 expect_comma(d); 466 rs2 = parse_xreg(d); 467 return enc_r(m, rd, 0u, rs2); 468 } 469 rd = parse_xreg(d); 470 expect_comma(d); 471 rs1 = parse_xreg(d); 472 expect_comma(d); 473 rs2 = parse_xreg(d); 474 return enc_r(m, rd, rs1, rs2); 475 476 case RV64_FMT_R4: { 477 u32 rs3; 478 rd = parse_freg(d); 479 expect_comma(d); 480 rs1 = parse_freg(d); 481 expect_comma(d); 482 rs2 = parse_freg(d); 483 expect_comma(d); 484 rs3 = parse_freg(d); 485 return enc_r4(m, rd, rs1, rs2, rs3, 0x7u); 486 } 487 488 case RV64_FMT_I: 489 /* Aliases first. `li` is handled earlier by rv64_emit_pseudo (it may 490 * need a multi-word expansion), so it never reaches here. */ 491 if (desc->flags & RV64_ASMFL_ALIAS) { 492 if (slice_eq_cstr(desc->mnemonic, "mv")) { 493 /* Standard two-operand `mv rd, rs` = `addi rd, rs, 0`. (A %pcrel_lo 494 * low-half is emitted as the canonical `addi rd, rs, %pcrel_lo(L)`, 495 * not a non-standard 3-operand `mv`, so it lands in the ADDI path 496 * below — matching clang.) */ 497 rd = parse_xreg(d); 498 expect_comma(d); 499 rs1 = parse_xreg(d); 500 return enc_i(m, rd, rs1, 0); 501 } 502 if (slice_eq_cstr(desc->mnemonic, "sext.w")) { 503 rd = parse_xreg(d); 504 expect_comma(d); 505 rs1 = parse_xreg(d); 506 return enc_i(m, rd, rs1, 0); 507 } 508 if (slice_eq_cstr(desc->mnemonic, "seqz") || 509 slice_eq_cstr(desc->mnemonic, "not")) { 510 rd = parse_xreg(d); 511 expect_comma(d); 512 rs1 = parse_xreg(d); 513 /* match already has imm12 + funct3 + op pinned. */ 514 return m | ((rs1 & 0x1fu) << 15) | ((rd & 0x1fu) << 7); 515 } 516 } 517 rd = parse_xreg(d); 518 expect_comma(d); 519 rs1 = parse_xreg(d); 520 expect_comma(d); 521 /* `addi rd, rs1, %lo(sym)` / `%pcrel_lo(label)` → R_RV_LO12_I. */ 522 if (rv_emit_imm_mod_reloc(d, RV_MODPOS_LO_I)) return enc_i(m, rd, rs1, 0); 523 imm = (i32)asm_driver_parse_const(d); 524 return enc_i(m, rd, rs1, imm); 525 526 case RV64_FMT_I_SHIFT: 527 rd = parse_xreg(d); 528 expect_comma(d); 529 rs1 = parse_xreg(d); 530 expect_comma(d); 531 return enc_ishift(d, m, rd, rs1, (u32)asm_driver_parse_const(d)); 532 533 case RV64_FMT_I_SHIFTW: 534 rd = parse_xreg(d); 535 expect_comma(d); 536 rs1 = parse_xreg(d); 537 expect_comma(d); 538 return enc_ishiftw(m, rd, rs1, (u32)asm_driver_parse_const(d)); 539 540 case RV64_FMT_U: 541 rd = parse_xreg(d); 542 expect_comma(d); 543 /* `lui rd, %hi(sym)` → R_RV_HI20; `auipc rd, %pcrel_hi(sym)` → 544 * R_RV_PCREL_HI20 (or %got_pcrel_hi → R_RV_GOT_HI20). */ 545 if (rv_emit_imm_mod_reloc(d, RV_MODPOS_HI20)) return enc_u(m, rd, 0); 546 imm = (i32)asm_driver_parse_const(d); 547 /* LUI/AUIPC immediate is the upper-20 value: the input is interpreted 548 * as the literal 20-bit value (already shifted-out form). */ 549 return enc_u(m, rd, (u32)imm); 550 551 case RV64_FMT_J: 552 /* `j label` / `jal rd, label` accept a symbolic target (R_RV_JAL) or a 553 * bare numeric displacement. */ 554 if ((desc->flags & RV64_ASMFL_ALIAS) && 555 slice_eq_cstr(desc->mnemonic, "j")) { 556 return enc_j(m, 0u, rv_reloc_target(d, R_RV_JAL)); 557 } 558 rd = parse_xreg(d); 559 expect_comma(d); 560 return enc_j(m, rd, rv_reloc_target(d, R_RV_JAL)); 561 562 case RV64_FMT_B: 563 /* `beq rs1, rs2, label` (and beqz/bnez aliases) accept a symbolic target 564 * (R_RV_BRANCH) or a bare numeric displacement. */ 565 if (desc->flags & RV64_ASMFL_ALIAS) { 566 /* beqz / bnez: rs, off. */ 567 rs1 = parse_xreg(d); 568 expect_comma(d); 569 return enc_b(m, rs1, 0u, rv_reloc_target(d, R_RV_BRANCH)); 570 } 571 rs1 = parse_xreg(d); 572 expect_comma(d); 573 rs2 = parse_xreg(d); 574 expect_comma(d); 575 return enc_b(m, rs1, rs2, rv_reloc_target(d, R_RV_BRANCH)); 576 577 case RV64_FMT_LOAD: 578 rd = (desc->flags & RV64_ASMFL_FP) ? parse_freg(d) : parse_xreg(d); 579 expect_comma(d); 580 mem = parse_mem(d); 581 rv_emit_mem_mod_reloc(d, &mem, /*is_store=*/0); 582 return enc_i(m, rd, mem.base, mem.disp); 583 584 case RV64_FMT_FP_LOAD: 585 rd = parse_freg(d); 586 expect_comma(d); 587 mem = parse_mem(d); 588 rv_emit_mem_mod_reloc(d, &mem, /*is_store=*/0); 589 return enc_i(m, rd, mem.base, mem.disp); 590 591 case RV64_FMT_STORE: 592 rs2 = (desc->flags & RV64_ASMFL_FP) ? parse_freg(d) : parse_xreg(d); 593 expect_comma(d); 594 mem = parse_mem(d); 595 rv_emit_mem_mod_reloc(d, &mem, /*is_store=*/1); 596 return enc_s(m, rs2, mem.base, mem.disp); 597 598 case RV64_FMT_FP_STORE: 599 rs2 = parse_freg(d); 600 expect_comma(d); 601 mem = parse_mem(d); 602 rv_emit_mem_mod_reloc(d, &mem, /*is_store=*/1); 603 return enc_s(m, rs2, mem.base, mem.disp); 604 605 case RV64_FMT_JALR: 606 if ((desc->flags & RV64_ASMFL_ALIAS) && 607 slice_eq_cstr(desc->mnemonic, "jr")) { 608 rs1 = parse_xreg(d); 609 return enc_i(m, 0u, rs1, 0); 610 } 611 rd = parse_xreg(d); 612 if (!asm_driver_eat_comma(d)) { 613 if (slice_eq_cstr(desc->mnemonic, "jalr")) 614 return enc_i(m, RV_RA, rd, 0); 615 asm_driver_panic(d, "rv64 asm: expected ','"); 616 } 617 /* Accept both `jalr rd, imm(rs1)` and `jalr rd, rs1, imm`. */ 618 { 619 AsmTok t = asm_driver_peek(d); 620 if (t.kind == ASM_TOK_IDENT) { 621 /* register first → register form */ 622 rs1 = parse_xreg(d); 623 if (asm_driver_eat_comma(d)) { 624 imm = (i32)asm_driver_parse_const(d); 625 } else { 626 imm = 0; 627 } 628 return enc_i(m, rd, rs1, imm); 629 } 630 } 631 mem = parse_mem(d); 632 return enc_i(m, rd, mem.base, mem.disp); 633 634 case RV64_FMT_FENCE: { 635 u32 pred, succ; 636 pred = parse_fence_mask(d); 637 expect_comma(d); 638 succ = parse_fence_mask(d); 639 return m | (pred << 24) | (succ << 20); 640 } 641 642 case RV64_FMT_SYSTEM: 643 /* No operands. nop/ret/ecall/ebreak. */ 644 return m; 645 646 case RV64_FMT_FP_RM: 647 rd = parse_freg(d); 648 expect_comma(d); 649 rs1 = parse_freg(d); 650 expect_comma(d); 651 rs2 = parse_freg(d); 652 /* Use DYN(=7) rounding mode by default. */ 653 return enc_r(m | (0x7u << 12), rd, rs1, rs2); 654 655 case RV64_FMT_FP_R: 656 if (desc->flags & RV64_ASMFL_FP) { 657 rd = parse_freg(d); 658 } else { 659 rd = parse_xreg(d); 660 } 661 expect_comma(d); 662 rs1 = parse_freg(d); 663 expect_comma(d); 664 rs2 = parse_freg(d); 665 return enc_r(m, rd, rs1, rs2); 666 667 case RV64_FMT_FP_CVT: 668 if (desc->flags & RV64_ASMFL_FP) { 669 rd = parse_freg(d); 670 expect_comma(d); 671 /* Source: integer reg for fcvt.s.w etc (no FP flag would 672 * indicate); but since we have ASMFL_FP set on dest, source may 673 * be either. Disambiguate by mnemonic. */ 674 if (slice_has_prefix_cstr(desc->mnemonic, "fcvt.s.", 7) && 675 (desc->mnemonic.s[7] == 'w' || desc->mnemonic.s[7] == 'l')) { 676 rs1 = parse_xreg(d); 677 } else if (slice_has_prefix_cstr(desc->mnemonic, "fcvt.d.", 7) && 678 (desc->mnemonic.s[7] == 'w' || desc->mnemonic.s[7] == 'l')) { 679 rs1 = parse_xreg(d); 680 } else if (slice_eq_cstr(desc->mnemonic, "fmv.w.x") || 681 slice_eq_cstr(desc->mnemonic, "fmv.d.x")) { 682 rs1 = parse_xreg(d); 683 } else { 684 rs1 = parse_freg(d); 685 } 686 } else { 687 rd = parse_xreg(d); 688 expect_comma(d); 689 rs1 = parse_freg(d); 690 } 691 /* match encodes rs2 (type selector); OR in rd/rs1 and the rounding mode. 692 * An explicit `, <rm>` suffix (cc -S emits it for non-default modes, and 693 * clang/gas accept it) takes precedence; otherwise a bare conversion 694 * mnemonic encodes the dynamic rounding mode (DYN=7), matching gas/clang 695 * for hand-written assembly. (Codegen's C float->int truncation is RTZ, 696 * but that path uses the rv_fcvt_* encoders directly and supplies its own 697 * rm; the text assembler must follow the assembler convention.) fmv 698 * bit-moves carry no rounding (rm=0). */ 699 { 700 u32 funct7 = (m >> 25) & 0x7fu; 701 u32 rm; 702 if (asm_driver_eat_comma(d)) { 703 rm = rv_parse_rm_name(d); 704 } else { 705 switch (funct7) { 706 case 0x70: /* fmv.x.w */ 707 case 0x71: /* fmv.x.d */ 708 case 0x78: /* fmv.w.x */ 709 case 0x79: /* fmv.d.x */ 710 rm = 0x0u; 711 break; 712 default: /* fcvt families: DYN (explicit suffix overrides above) */ 713 rm = 0x7u; 714 break; 715 } 716 } 717 return m | (rm << 12) | ((rs1 & 0x1fu) << 15) | ((rd & 0x1fu) << 7); 718 } 719 720 case RV64_FMT_AMO: 721 rd = parse_xreg(d); 722 expect_comma(d); 723 rs2 = parse_xreg(d); 724 expect_comma(d); 725 asm_driver_expect_punct(d, '(', "'(' in rv64 amo operand"); 726 rs1 = parse_xreg(d); 727 asm_driver_expect_punct(d, ')', "')' in rv64 amo operand"); 728 return enc_amo(m, 0u, 0u, rd, rs1, rs2); 729 730 case RV64_FMT_LR: 731 rd = parse_xreg(d); 732 expect_comma(d); 733 asm_driver_expect_punct(d, '(', "'(' in rv64 lr operand"); 734 rs1 = parse_xreg(d); 735 asm_driver_expect_punct(d, ')', "')' in rv64 lr operand"); 736 return enc_amo(m, 0u, 0u, rd, rs1, 0u); 737 738 case RV64_FMT_CSR: { 739 u32 csr; 740 rd = parse_xreg(d); 741 expect_comma(d); 742 csr = parse_csr(d); 743 expect_comma(d); 744 rs1 = parse_xreg(d); 745 return enc_i(m, rd, rs1, (i32)csr); 746 } 747 748 case RV64_FMT_CSRI: { 749 u32 csr; 750 rd = parse_xreg(d); 751 expect_comma(d); 752 csr = parse_csr(d); 753 expect_comma(d); 754 u32 uimm = (u32)asm_driver_parse_const(d) & 0x1fu; 755 return enc_i(m, rd, uimm, (i32)csr); 756 } 757 758 case RV64_FMT_CSR_PSEUDO: { 759 /* 2-operand CSR pseudos. The match word already pins funct3+opcode; we 760 * supply x0 for the implicit rd or rs1 per the mnemonic. */ 761 u32 csr; 762 if (slice_eq_cstr(desc->mnemonic, "csrr")) { 763 /* csrr rd, csr = csrrs rd, csr, x0 */ 764 rd = parse_xreg(d); 765 expect_comma(d); 766 csr = parse_csr(d); 767 return enc_i(m, rd, 0u, (i32)csr); 768 } 769 /* csrw/csrs/csrc csr, rs and csrwi/csrsi/csrci csr, uimm: 770 * destination is x0, csr comes first. */ 771 csr = parse_csr(d); 772 expect_comma(d); 773 if (slice_eq_cstr(desc->mnemonic, "csrwi") || 774 slice_eq_cstr(desc->mnemonic, "csrsi") || 775 slice_eq_cstr(desc->mnemonic, "csrci")) { 776 u32 uimm = (u32)asm_driver_parse_const(d) & 0x1fu; 777 return enc_i(m, 0u, uimm, (i32)csr); 778 } 779 rs1 = parse_xreg(d); 780 return enc_i(m, 0u, rs1, (i32)csr); 781 } 782 783 case RV64_FMT_CR: 784 if (slice_eq_cstr(desc->mnemonic, "c.jr") || 785 slice_eq_cstr(desc->mnemonic, "c.jalr")) { 786 rs1 = parse_xreg(d); 787 return enc_c_cr(m, rs1, 0u); 788 } 789 rd = parse_xreg(d); 790 expect_comma(d); 791 rs2 = parse_xreg(d); 792 return enc_c_cr(m, rd, rs2); 793 794 case RV64_FMT_CI: 795 if (slice_eq_cstr(desc->mnemonic, "c.lwsp") || 796 slice_eq_cstr(desc->mnemonic, "c.ldsp") || 797 slice_eq_cstr(desc->mnemonic, "c.fldsp")) { 798 rd = slice_eq_cstr(desc->mnemonic, "c.fldsp") ? parse_freg(d) 799 : parse_xreg(d); 800 expect_comma(d); 801 mem = parse_mem(d); 802 if (mem.base != RV_SP) 803 asm_driver_panic(d, "rv64 asm: compressed stack load needs sp base"); 804 return enc_c_lwsp(m, rd, (u32)mem.disp, 805 !slice_eq_cstr(desc->mnemonic, "c.lwsp")); 806 } 807 rd = parse_xreg(d); 808 expect_comma(d); 809 imm = (i32)asm_driver_parse_const(d); 810 if (slice_eq_cstr(desc->mnemonic, "c.lui") && ((u32)imm & 0xfffu) == 0) 811 imm >>= 12; 812 if (slice_eq_cstr(desc->mnemonic, "c.addi16sp")) { 813 if (rd != RV_SP) 814 asm_driver_panic(d, "rv64 asm: c.addi16sp needs sp destination"); 815 return enc_c_addi16sp(m, imm); 816 } 817 return enc_c_ci(m, rd, imm); 818 819 case RV64_FMT_CSS: 820 rs2 = (desc->flags & RV64_ASMFL_FP) ? parse_freg(d) : parse_xreg(d); 821 expect_comma(d); 822 mem = parse_mem(d); 823 if (mem.base != RV_SP) 824 asm_driver_panic(d, "rv64 asm: compressed stack store needs sp base"); 825 return enc_c_swsp(m, rs2, (u32)mem.disp, 826 !slice_eq_cstr(desc->mnemonic, "c.swsp")); 827 828 case RV64_FMT_CIW: 829 rd = parse_xreg(d); 830 expect_comma(d); 831 rs1 = parse_xreg(d); 832 expect_comma(d); 833 if (rs1 != RV_SP) 834 asm_driver_panic(d, "rv64 asm: c.addi4spn needs sp source"); 835 imm = (i32)asm_driver_parse_const(d); 836 return enc_c_addi4spn(m, c_reg3(d, rd), (u32)imm); 837 838 case RV64_FMT_CL: 839 rd = (desc->flags & RV64_ASMFL_FP) ? parse_freg(d) : parse_xreg(d); 840 expect_comma(d); 841 mem = parse_mem(d); 842 return enc_c_lwld(m, c_reg3(d, rd), c_reg3(d, mem.base), (u32)mem.disp, 843 !slice_eq_cstr(desc->mnemonic, "c.lw")); 844 845 case RV64_FMT_CS: 846 rs2 = (desc->flags & RV64_ASMFL_FP) ? parse_freg(d) : parse_xreg(d); 847 expect_comma(d); 848 mem = parse_mem(d); 849 return enc_c_swld(m, c_reg3(d, rs2), c_reg3(d, mem.base), (u32)mem.disp, 850 !slice_eq_cstr(desc->mnemonic, "c.sw")); 851 852 case RV64_FMT_CA: 853 rd = parse_xreg(d); 854 expect_comma(d); 855 rs2 = parse_xreg(d); 856 return m | (c_reg3(d, rd) << 7) | (c_reg3(d, rs2) << 2); 857 858 case RV64_FMT_CB: 859 rs1 = parse_xreg(d); 860 expect_comma(d); 861 imm = (i32)asm_driver_parse_const(d); 862 if (slice_eq_cstr(desc->mnemonic, "c.beqz") || 863 slice_eq_cstr(desc->mnemonic, "c.bnez")) { 864 return enc_c_cb_imm(m, c_reg3(d, rs1), imm); 865 } 866 return enc_c_cb_alu_imm(m, c_reg3(d, rs1), imm); 867 868 case RV64_FMT_CJ: 869 imm = (i32)asm_driver_parse_const(d); 870 return enc_c_cj(m, imm); 871 872 case RV64_FMT_C_NONE: 873 return m; 874 875 default: 876 asm_driver_panic(d, "rv64 asm: unsupported format"); 877 } 878 } 879 880 /* ============================================================ 881 * Multi-word pseudo-instruction expansion. 882 * 883 * call/tail/la/lla expand to a PC-relative AUIPC + (JALR | ADDI) pair; 884 * `li` with a constant that does not fit a 12-bit signed immediate 885 * expands to an LUI/ADDI(W)/SLLI chain (no relocations). Each 32-bit 886 * word goes out through rv64_emit32 — the same path assemble_one's 887 * single-word result uses — and relocations are attached via 888 * mc_emit_reloc_at at the appropriate word offset. */ 889 890 /* 12-bit signed immediate range check for li short-circuit. */ 891 static bool rv_fits_i12(i64 v) { return v >= -2048 && v <= 2047; } 892 893 /* Sign-extend the low 12 bits of v. */ 894 static i64 rv_sext12(i64 v) { 895 return (i64)((((u64)v & 0xfffu) ^ 0x800u)) - 0x800; 896 } 897 898 /* Emit an AUIPC rd,0 + a R_RV_PCREL_HI20(sym) reloc, then create a local 899 * `.LpcrelHi` anchor at the AUIPC offset and return that anchor symbol so 900 * the paired low-half reloc can reference it. Mirrors native.c's 901 * rv_emit_global_addr (the non-GOT branch). */ 902 static ObjSymId rv_emit_pcrel_hi(AsmDriver* d, u32 rd, ObjSymId sym, 903 i64 addend) { 904 MCEmitter* mc = asm_driver_mc(d); 905 ObjBuilder* obj = asm_driver_ob(d); 906 Compiler* c = asm_driver_compiler(d); 907 u32 sec = mc->section_id; 908 u32 ap = mc_pos(mc); 909 rv64_emit32(mc, rv_auipc(rd, 0)); 910 mc_emit_reloc_at(mc, sec, ap, R_RV_PCREL_HI20, sym, addend, 0, 0); 911 Sym an = pool_intern_slice(c->global, SLICE_LIT(".LpcrelHi")); 912 return obj_symbol(obj, an, SB_LOCAL, SK_OBJ, sec, (u64)ap, 0); 913 } 914 915 /* call/tail: AUIPC <link>,0 + JALR <rd>,<link>,0 with one R_RV_CALL reloc 916 * at the AUIPC. `link` is the register the AUIPC materializes into and the 917 * JALR's base; `rd` is the JALR link-register (ra for call, zero for 918 * tail). The linker patches both words from the single R_RV_CALL reloc. */ 919 static void rv_emit_call_pseudo(AsmDriver* d, u32 link, u32 rd) { 920 MCEmitter* mc = asm_driver_mc(d); 921 ObjSymId sym = OBJ_SYM_NONE; 922 i64 off = 0; 923 asm_driver_parse_sym_expr(d, &sym, &off); 924 if (sym == OBJ_SYM_NONE) 925 asm_driver_panic(d, "rv64 asm: call/tail target must be a symbol"); 926 u32 sec = mc->section_id; 927 u32 ap = mc_pos(mc); 928 rv64_emit32(mc, rv_auipc(link, 0)); 929 rv64_emit32(mc, rv_jalr(rd, link, 0)); 930 mc_emit_reloc_at(mc, sec, ap, R_RV_CALL, sym, off, 0, 0); 931 } 932 933 /* la/lla rd, sym: AUIPC rd,%pcrel_hi(sym) + ADDI rd,rd,%pcrel_lo(anchor). 934 * kit's static Local-Exec model has no GOT, so `la` == `lla`. */ 935 static void rv_emit_la_pseudo(AsmDriver* d) { 936 MCEmitter* mc = asm_driver_mc(d); 937 u32 rd = parse_xreg(d); 938 expect_comma(d); 939 ObjSymId sym = OBJ_SYM_NONE; 940 i64 off = 0; 941 asm_driver_parse_sym_expr(d, &sym, &off); 942 if (sym == OBJ_SYM_NONE) 943 asm_driver_panic(d, "rv64 asm: la/lla target must be a symbol"); 944 ObjSymId anchor = rv_emit_pcrel_hi(d, rd, sym, off); 945 u32 sec = mc->section_id; 946 u32 lp = mc_pos(mc); 947 rv64_emit32(mc, rv_addi(rd, rd, 0)); 948 mc_emit_reloc_at(mc, sec, lp, R_RV_PCREL_LO12_I, anchor, 0, 0, 0); 949 } 950 951 /* LUI immediate that sign-extends to a negative 32-bit value: bit 19 of 952 * the 20-bit field is set, i.e. Hi20 >= 0x80000. */ 953 #define RV_LUI_HI20_SIGN 0x80000LL 954 955 /* Materialize a constant into `rd` via the LLVM RISCVMatInt sequence: for 956 * values fitting a signed 32-bit range, LUI + ADDI/ADDIW; otherwise a 957 * recursive top-down hi20/lo12 split with SLLI shifts that absorb trailing 958 * zeros. No relocations. 959 * 960 * On rv64, after an LUI the low-half add uses ADDIW only when the LUI value 961 * is negative in 32-bit form (Hi20 >= RV_LUI_HI20_SIGN): there the add must 962 * wrap in 32-bit arithmetic and re-sign-extend to land in range. When the 963 * LUI value is non-negative in its low 32 bits, plain ADDI keeps the 964 * 64-bit result correct (matching LLVM's generateInstSeqImpl). 965 * 966 * On rv32 there is no ADDIW and the GPR is 32 bits wide, so every constant 967 * fits a LUI + ADDI pair (the add already wraps mod 2^32). The variant's 968 * has_w_forms gates both the ADDIW use and the >32-bit recursion below. */ 969 static void rv_emit_li_value(MCEmitter* mc, const RiscvVariant* variant, u32 rd, 970 i64 val) { 971 if (!variant->has_w_forms || (val >= -2147483648LL && val <= 2147483647LL)) { 972 i64 hi20 = ((val + 0x800) >> 12) & 0xfffffLL; 973 i64 lo12 = rv_sext12(val); 974 if (hi20) rv64_emit32(mc, rv_lui(rd, (u32)hi20)); 975 if (lo12 || hi20 == 0) { 976 u32 src = hi20 ? rd : (u32)RV_ZERO; 977 if (variant->has_w_forms && hi20 >= RV_LUI_HI20_SIGN) 978 rv64_emit32(mc, rv_addiw(rd, src, (i32)lo12)); 979 else 980 rv64_emit32(mc, rv_addi(rd, src, (i32)lo12)); 981 } 982 return; 983 } 984 /* >32-bit: split off the low 12 bits, recurse on the (shifted) high 985 * part, then SLLI back and ADD the low bits. The subtraction is done in 986 * unsigned space so it cannot signed-overflow at the int64 extremes 987 * (e.g. val=INT64_MAX, lo12=-1); the result has its low 12 bits clear, 988 * and the arithmetic right shift recovers the sign-extended high part. */ 989 i64 lo12 = rv_sext12(val); 990 i64 hi = (i64)((u64)val - (u64)lo12) >> 12; 991 u32 shift = 12; 992 /* Absorb trailing zeros of the high part into the shift amount. */ 993 while ((hi & 1) == 0) { 994 hi >>= 1; 995 ++shift; 996 } 997 rv_emit_li_value(mc, variant, rd, hi); 998 rv64_emit32(mc, rv_slli(rd, rd, shift)); 999 if (lo12) rv64_emit32(mc, rv_addi(rd, rd, (i32)lo12)); 1000 } 1001 1002 /* Dispatch a multi-word pseudo. Returns true if it consumed the operands 1003 * and emitted its expansion; false to fall through to the single-word 1004 * path. `li` is handled here only when its immediate exceeds the 12-bit 1005 * signed range the alias row encodes directly. */ 1006 static bool rv64_emit_pseudo(AsmDriver* d, const Rv64InsnDesc* desc) { 1007 MCEmitter* mc = asm_driver_mc(d); 1008 if (desc->fmt == RV64_FMT_PSEUDO) { 1009 if (slice_eq_cstr(desc->mnemonic, "call")) { 1010 rv_emit_call_pseudo(d, RV_RA, RV_RA); 1011 return true; 1012 } 1013 if (slice_eq_cstr(desc->mnemonic, "tail")) { 1014 /* Standard RISC-V `tail` materializes the address into t1 (x6). kit 1015 * codegen uses t0 for its own tail-call temp, so a `cc -S`-fused 1016 * `tail sym` re-assembles to t1 not t0 — execution-equivalent (both are 1017 * caller-saved temps clobbered by the tail jump; cross-exec still 1018 * matches), only the byte image differs on tail-call cases. Keeping the 1019 * assembler's `tail` standard preserves clang/gas interop. */ 1020 rv_emit_call_pseudo(d, RV_T1, RV_ZERO); 1021 return true; 1022 } 1023 /* la / lla — identical PC-relative expansion in kit. */ 1024 rv_emit_la_pseudo(d); 1025 return true; 1026 } 1027 if ((desc->flags & RV64_ASMFL_ALIAS) && slice_eq_cstr(desc->mnemonic, "li")) { 1028 /* Peek the immediate without consuming the destination register: the 1029 * single-word alias path re-parses both. We commit to the multi-word 1030 * path only for out-of-range constants, leaving the existing 12-bit 1031 * fast path (and its golden behavior) untouched. */ 1032 u32 rd = parse_xreg(d); 1033 expect_comma(d); 1034 i64 imm = asm_driver_parse_const(d); 1035 if (rv_fits_i12(imm)) { 1036 rv64_emit32(mc, rv_addi(rd, RV_ZERO, (i32)imm)); 1037 } else { 1038 rv_emit_li_value(mc, rv_asm_variant(d), rd, imm); 1039 } 1040 return true; 1041 } 1042 return false; 1043 } 1044 1045 static void rv64_arch_asm_insn(ArchAsm* base, AsmDriver* d, Sym mnemonic) { 1046 MCEmitter* mc = asm_driver_mc(d); 1047 const Rv64InsnDesc* desc; 1048 u8 av = rv_asm_variant(d)->xlen == 32u ? (u8)RV_AV_RV32 : (u8)RV_AV_RV64; 1049 (void)base; 1050 (void)asm_driver_cur_section(d); 1051 desc = rv64_asm_find(pool_slice(asm_driver_pool(d), mnemonic), av); 1052 if (!desc) 1053 asm_driver_panic(d, av == (u8)RV_AV_RV32 1054 ? "rv32 asm: unsupported instruction" 1055 : "rv64 asm: unsupported instruction"); 1056 if (rv64_emit_pseudo(d, desc)) return; 1057 if (desc->flags & RV64_ASMFL_C16) 1058 rv64_emit16(mc, assemble_one(d, desc)); 1059 else 1060 rv64_emit32(mc, assemble_one(d, desc)); 1061 } 1062 1063 static void rv64_arch_asm_destroy(ArchAsm* base) { (void)base; } 1064 1065 /* ---- textual-assembly operand syntax (printer <-> parser) ---------------- 1066 * 1067 * Inverse of the `.s` parsers above (rv_parse_mod_reloc / rv_reloc_target and 1068 * the call/la pseudo expanders): how a relocated rv64 operand is spelled in 1069 * `cc -S` so the same text re-assembles under kit-as. RISC-V uses the same 1070 * `%hi`/`%lo`/`%pcrel_hi`/`%pcrel_lo` operator syntax on every object format, 1071 * so `fmt` is unused. See ArchAsmOps and src/api/asm_emit.c. */ 1072 static int rv64_reloc_operand(u16 kind, KitObjFmt fmt, ArchRelocOperand* out) { 1073 (void)fmt; 1074 out->prefix = ""; 1075 out->suffix = ""; 1076 out->addend_bias = 0; 1077 out->emit_anchor = 0; 1078 out->ref_anchor = 0; 1079 switch (kind) { 1080 case R_RV_PCREL_HI20: 1081 out->surg = ARCH_RELOC_SURG_TAIL; 1082 out->prefix = "%pcrel_hi("; 1083 out->suffix = ")"; 1084 out->emit_anchor = 1; /* define a unique anchor label at this AUIPC */ 1085 return 1; 1086 case R_RV_GOT_HI20: 1087 out->surg = ARCH_RELOC_SURG_TAIL; 1088 out->prefix = "%got_pcrel_hi("; 1089 out->suffix = ")"; 1090 out->emit_anchor = 1; 1091 return 1; 1092 case R_RV_PCREL_LO12_I: 1093 case R_RV_PCREL_LO12_S: 1094 out->surg = ARCH_RELOC_SURG_RV_LO12; 1095 out->prefix = "%pcrel_lo("; 1096 out->suffix = ")"; 1097 out->ref_anchor = 1; /* references the preceding AUIPC's anchor label */ 1098 return 1; 1099 case R_RV_HI20: 1100 out->surg = ARCH_RELOC_SURG_TAIL; 1101 out->prefix = "%hi("; 1102 out->suffix = ")"; 1103 return 1; 1104 case R_RV_LO12_I: 1105 case R_RV_LO12_S: 1106 out->surg = ARCH_RELOC_SURG_RV_LO12; 1107 out->prefix = "%lo("; 1108 out->suffix = ")"; 1109 return 1; 1110 case R_RV_BRANCH: 1111 case R_RV_JAL: 1112 out->surg = ARCH_RELOC_SURG_TAIL; 1113 return 1; 1114 default: 1115 return 0; /* R_ABS*, R_RV_RVC_*, R_RV_RELAX, TLS, ... → keep numeric */ 1116 } 1117 } 1118 1119 /* Intra-section local branches whose target codegen resolved in place (no 1120 * relocation): the disassembler renders the target numerically, so cc -S 1121 * synthesizes a label there. `j`/`jal x0` are JAL aliases; the conditional 1122 * branches are B-type. `call`/`tail` are excluded — they carry R_RV_CALL. */ 1123 static int rv64_is_local_branch(KitSlice m) { 1124 if (m.len == 1 && m.s[0] == 'j') return 1; 1125 if (m.len == 3 && memcmp(m.s, "jal", 3) == 0) return 1; 1126 if (m.len == 3 && memcmp(m.s, "beq", 3) == 0) return 1; 1127 if (m.len == 3 && memcmp(m.s, "bne", 3) == 0) return 1; 1128 if (m.len == 3 && memcmp(m.s, "blt", 3) == 0) return 1; 1129 if (m.len == 3 && memcmp(m.s, "bge", 3) == 0) return 1; 1130 if (m.len == 4 && memcmp(m.s, "bltu", 4) == 0) return 1; 1131 if (m.len == 4 && memcmp(m.s, "bgeu", 4) == 0) return 1; 1132 if (m.len == 4 && memcmp(m.s, "beqz", 4) == 0) return 1; 1133 if (m.len == 4 && memcmp(m.s, "bnez", 4) == 0) return 1; 1134 if (m.len == 4 && memcmp(m.s, "blez", 4) == 0) return 1; 1135 if (m.len == 4 && memcmp(m.s, "bgez", 4) == 0) return 1; 1136 if (m.len == 4 && memcmp(m.s, "bltz", 4) == 0) return 1; 1137 if (m.len == 4 && memcmp(m.s, "bgtz", 4) == 0) return 1; 1138 if (m.len == 6 && memcmp(m.s, "c.beqz", 6) == 0) return 1; 1139 if (m.len == 6 && memcmp(m.s, "c.bnez", 6) == 0) return 1; 1140 if (m.len == 3 && memcmp(m.s, "c.j", 3) == 0) return 1; 1141 return 0; 1142 } 1143 1144 /* R_RV_CALL fuses an AUIPC+JALR pair into a single `call`/`tail sym` pseudo 1145 * (the canonical `.s` spelling the assembler re-expands to the same pair + 1146 * reloc). The reloc sits on the AUIPC; the JALR partner carries no reloc. A 1147 * tail call links into x0 (the JALR's rd is `zero`); a regular call links into 1148 * ra. We read that from the partner JALR's disassembled text. */ 1149 static int rv64_reloc_call_pair(u16 kind, KitSlice pair_mnemonic, 1150 KitSlice pair_ops, const char** mnemonic_out) { 1151 if (kind != R_RV_CALL) return 0; 1152 /* The partner JALR links into ra (regular call) or x0 (tail). The 1153 * disassembler renders the x0-link, zero-immediate form as the `jr rs` 1154 * alias, and the ra form as `jalr ra, 0(ra)`. So a `jr` partner is always a 1155 * tail; a `jalr` partner is a tail iff its link register is `zero`. */ 1156 if (pair_mnemonic.len == 2 && memcmp(pair_mnemonic.s, "jr", 2) == 0) { 1157 *mnemonic_out = "tail"; 1158 return 1; 1159 } 1160 if (pair_mnemonic.len == 4 && memcmp(pair_mnemonic.s, "jalr", 4) == 0) { 1161 if (pair_ops.len >= 4 && memcmp(pair_ops.s, "zero", 4) == 0) 1162 *mnemonic_out = "tail"; 1163 else 1164 *mnemonic_out = "call"; 1165 return 1; 1166 } 1167 return 0; 1168 } 1169 1170 const ArchAsmOps rv64_asm_ops = { 1171 .reloc_operand = rv64_reloc_operand, 1172 .is_local_branch = rv64_is_local_branch, 1173 .reloc_call_pair = rv64_reloc_call_pair, 1174 }; 1175 1176 ArchAsm* rv64_arch_asm_new(Compiler* c) { 1177 Rv64Asm* a = arena_new(c->tu, Rv64Asm); 1178 memset(a, 0, sizeof *a); 1179 a->base.insn = rv64_arch_asm_insn; 1180 a->base.destroy = rv64_arch_asm_destroy; 1181 a->c = c; 1182 return &a->base; 1183 } 1184 1185 /* ============================================================ 1186 * Inline-asm template walker (parallel to aa64 asm.c §"inline-asm 1187 * template walker"). The walker substitutes %N / %[name] / %% / %a%w%x 1188 * placeholders into a per-line StrBuf, then re-lexes each line through 1189 * rv64_arch_asm_insn for assembly. Statement separators recognised are 1190 * '\n' and ';' (outside parens / quoted strings). 1191 * ============================================================ */ 1192 1193 Rv64Asm* rv64_asm_open(Compiler* c) { 1194 Rv64Asm* a = arena_new(c->tu, Rv64Asm); 1195 memset(a, 0, sizeof *a); 1196 a->base.insn = rv64_arch_asm_insn; 1197 a->base.destroy = rv64_arch_asm_destroy; 1198 a->c = c; 1199 return a; 1200 } 1201 1202 void rv64_asm_close(Rv64Asm* a) { (void)a; } 1203 1204 void rv64_inline_bind(Rv64Asm* a, const AsmConstraint* outs, u32 nout, 1205 Operand* out_ops, const AsmConstraint* ins, u32 nin, 1206 const Operand* in_ops, const Sym* clobbers, u32 nclob) { 1207 a->outs = outs; 1208 a->out_ops = out_ops; 1209 a->ins = ins; 1210 a->in_ops = in_ops; 1211 a->clobbers = clobbers; 1212 a->nout = nout; 1213 a->nin = nin; 1214 a->nclob = nclob; 1215 } 1216 1217 /* Per-line rendered buffer cap. Inline asm rarely emits more than a 1218 * handful of insns per block; one substituted line fits comfortably. 1219 * Truncation panics — the operator grammar should never grow a single 1220 * line beyond this without a deliberate reason. */ 1221 #define RV64_INLINE_LINE_CAP 1024 1222 1223 _Noreturn static void inline_panic(Rv64Asm* a, const char* msg) { 1224 SrcLoc loc = {0, 0, 0}; 1225 compiler_panic(a->c, loc, "rv64 inline asm: %.*s", 1226 SLICE_ARG(slice_from_cstr(msg))); 1227 } 1228 1229 /* Render a 5-bit integer register number using its canonical psABI name. */ 1230 static void render_xreg(StrBuf* sb, u32 reg) { 1231 const char* nm = rv64_register_name(reg & 0x1fu); 1232 if (!nm) { 1233 strbuf_putc(sb, 'x'); 1234 if ((reg & 0x1fu) >= 10u) 1235 strbuf_putc(sb, (char)('0' + ((reg & 0x1fu) / 10u))); 1236 strbuf_putc(sb, (char)('0' + ((reg & 0x1fu) % 10u))); 1237 return; 1238 } 1239 strbuf_puts(sb, nm); 1240 } 1241 1242 /* Render an FP register by its canonical psABI name (e.g., fa0). */ 1243 static void render_freg(StrBuf* sb, u32 reg) { 1244 const char* nm = rv64_register_name(32u + (reg & 0x1fu)); 1245 if (!nm) { 1246 strbuf_putc(sb, 'f'); 1247 if ((reg & 0x1fu) >= 10u) 1248 strbuf_putc(sb, (char)('0' + ((reg & 0x1fu) / 10u))); 1249 strbuf_putc(sb, (char)('0' + ((reg & 0x1fu) % 10u))); 1250 return; 1251 } 1252 strbuf_puts(sb, nm); 1253 } 1254 1255 /* Render a signed 64-bit integer. Inline asm immediates appear bare in 1256 * RISC-V (no '#' prefix), matching the standalone .s parser. */ 1257 static void render_imm(StrBuf* sb, i64 v) { strbuf_put_i64(sb, v); } 1258 1259 /* Render addressing form `disp(base)`. */ 1260 static void render_indirect(Rv64Asm* a, StrBuf* sb, Reg base, i32 ofs) { 1261 (void)a; 1262 if (ofs != 0) 1263 strbuf_put_i64(sb, (i64)ofs); 1264 else 1265 strbuf_putc(sb, '0'); 1266 strbuf_putc(sb, '('); 1267 render_xreg(sb, (u32)base); 1268 strbuf_putc(sb, ')'); 1269 } 1270 1271 /* Resolve operand index → render into sb. form: 1272 * 0 = default (per-kind), 1273 * 1 = %wN (width hint; on rv64 same as default xreg form), 1274 * 2 = %xN (force 64-bit reg form — identical to default for rv64), 1275 * 3 = %aN (memory addressing form). 1276 * 4 = %zN (RISC-V GCC: emits "zero" if operand is imm 0, else reg). */ 1277 static void render_operand(Rv64Asm* a, StrBuf* sb, u32 idx, int form) { 1278 u32 ntot = a->nout + a->nin; 1279 if (idx >= ntot) inline_panic(a, "operand index out of range"); 1280 const Operand* op = 1281 (idx < a->nout) ? &a->out_ops[idx] : &a->in_ops[idx - a->nout]; 1282 switch (form) { 1283 case 1: /* %wN — accept any reg/imm; rv64 has no narrower spelling. */ 1284 case 2: /* %xN — same. */ 1285 if (op->kind == RV64_INLINE_OPK_REG) { 1286 if (op->pad[0] == RV64_INLINE_OPCLS_FP) 1287 render_freg(sb, (u32)op->v.local); 1288 else 1289 render_xreg(sb, (u32)op->v.local); 1290 return; 1291 } 1292 if (op->kind == OPK_IMM) { 1293 render_imm(sb, op->v.imm); 1294 return; 1295 } 1296 inline_panic(a, "%w/%x on unsupported operand kind"); 1297 case 3: /* %aN — memory addressing form */ 1298 if (op->kind != OPK_INDIRECT) inline_panic(a, "%a on non-memory operand"); 1299 if (op->v.ind.index != CG_LOCAL_NONE) 1300 inline_panic(a, 1301 "%a on indexed memory operand: rv64 inline asm " 1302 "requires base+disp only"); 1303 render_indirect(a, sb, (Reg)op->v.ind.base, op->v.ind.ofs); 1304 return; 1305 case 4: /* %zN — zero-or-reg */ 1306 if (op->kind == OPK_IMM && op->v.imm == 0) { 1307 strbuf_puts(sb, "zero"); 1308 return; 1309 } 1310 if (op->kind == RV64_INLINE_OPK_REG) { 1311 if (op->pad[0] == RV64_INLINE_OPCLS_FP) 1312 render_freg(sb, (u32)op->v.local); 1313 else 1314 render_xreg(sb, (u32)op->v.local); 1315 return; 1316 } 1317 inline_panic(a, "%z on unsupported operand kind"); 1318 default: 1319 break; 1320 } 1321 switch (op->kind) { 1322 case RV64_INLINE_OPK_REG: 1323 if (op->pad[0] == RV64_INLINE_OPCLS_FP) 1324 render_freg(sb, (u32)op->v.local); 1325 else 1326 render_xreg(sb, (u32)op->v.local); 1327 return; 1328 case OPK_IMM: 1329 render_imm(sb, op->v.imm); 1330 return; 1331 case OPK_INDIRECT: 1332 if (op->v.ind.index != CG_LOCAL_NONE) 1333 inline_panic(a, 1334 "indexed memory operand in inline asm: rv64 requires " 1335 "base+disp only"); 1336 render_indirect(a, sb, (Reg)op->v.ind.base, op->v.ind.ofs); 1337 return; 1338 default: 1339 inline_panic(a, "unsupported operand kind for %N"); 1340 } 1341 } 1342 1343 /* Resolve a `%[name]` operand by looking up `needle` against the 1344 * constraint.name fields on the combined outs+ins list. Returns the 1345 * combined index, or (u32)-1 on miss. */ 1346 static u32 lookup_named(Rv64Asm* a, Sym needle) { 1347 for (u32 k = 0; k < a->nout; ++k) { 1348 if (a->outs[k].name == needle) return k; 1349 } 1350 for (u32 k = 0; k < a->nin; ++k) { 1351 if (a->ins[k].name == needle) return a->nout + k; 1352 } 1353 return (u32)-1; 1354 } 1355 1356 /* Lex one line of substituted asm and dispatch via rv64_arch_asm_insn. */ 1357 static void run_one_line(Rv64Asm* a, MCEmitter* mc, const char* text, 1358 size_t len) { 1359 /* Skip blank lines. */ 1360 size_t i; 1361 for (i = 0; i < len; ++i) { 1362 if (text[i] != ' ' && text[i] != '\t') break; 1363 } 1364 if (i == len) return; 1365 1366 AsmLexer* lx = asm_lex_open_mem(a->c, "<inline-asm>", text, len); 1367 AsmDriver* d = asm_driver_open_inline(a->c, mc, lx); 1368 1369 /* The first non-trivial token must be the mnemonic identifier. */ 1370 AsmTok t = asm_driver_peek(d); 1371 while (t.kind == ASM_TOK_NEWLINE) { 1372 (void)asm_driver_next(d); 1373 t = asm_driver_peek(d); 1374 } 1375 if (t.kind == ASM_TOK_EOF) { 1376 asm_driver_close_inline(d); 1377 asm_lex_close(lx); 1378 return; 1379 } 1380 if (t.kind != ASM_TOK_IDENT) 1381 inline_panic(a, "expected mnemonic at start of inline asm line"); 1382 (void)asm_driver_next(d); 1383 Sym mn = t.v.ident; 1384 /* Compose `fcvt.s.w` etc. — rv64 has dotted mnemonics; the standalone 1385 * lexer already strings them together as a single IDENT in most paths. 1386 * Mirror the aa64 composite handling for safety. */ 1387 AsmTok dot = asm_driver_peek(d); 1388 while (asm_driver_tok_is_punct(dot, '.')) { 1389 (void)asm_driver_next(d); 1390 AsmTok rest = asm_driver_next(d); 1391 if (rest.kind != ASM_TOK_IDENT) 1392 inline_panic(a, "composite mnemonic: expected ident after '.'"); 1393 Slice hsl = pool_slice(asm_driver_pool(d), mn); 1394 Slice rsl = pool_slice(asm_driver_pool(d), rest.v.ident); 1395 size_t hn = hsl.len, rn = rsl.len; 1396 char buf[64]; 1397 if (hn + 1 + rn >= sizeof buf) 1398 inline_panic(a, "composite mnemonic too long"); 1399 for (size_t k = 0; k < hn; ++k) buf[k] = hsl.s[k]; 1400 buf[hn] = '.'; 1401 for (size_t k = 0; k < rn; ++k) buf[hn + 1 + k] = rsl.s[k]; 1402 mn = pool_intern_slice(asm_driver_pool(d), 1403 (Slice){.s = buf, .len = hn + 1 + rn}); 1404 dot = asm_driver_peek(d); 1405 } 1406 rv64_arch_asm_insn(&a->base, d, mn); 1407 asm_driver_close_inline(d); 1408 asm_lex_close(lx); 1409 } 1410 1411 /* Substitute placeholders into one line's StrBuf, then dispatch. */ 1412 static void render_and_run_line(Rv64Asm* a, MCEmitter* mc, StrBuf* sb, 1413 const char* start, const char* end) { 1414 strbuf_reset(sb); 1415 for (const char* p = start; p < end; ++p) { 1416 char c = *p; 1417 if (c != '%') { 1418 strbuf_putc(sb, c); 1419 continue; 1420 } 1421 /* Placeholder. */ 1422 if (p + 1 >= end) inline_panic(a, "trailing '%' in template"); 1423 char n = *(p + 1); 1424 if (n == '%') { 1425 strbuf_putc(sb, '%'); 1426 ++p; 1427 continue; 1428 } 1429 if (n == '[') { 1430 const char* nbeg = p + 2; 1431 const char* nend = nbeg; 1432 while (nend < end && *nend != ']') ++nend; 1433 if (nend == end) inline_panic(a, "unterminated %[name]"); 1434 size_t nlen = (size_t)(nend - nbeg); 1435 Sym needle = 1436 pool_intern_slice(a->c->global, (Slice){.s = nbeg, .len = nlen}); 1437 u32 idx = lookup_named(a, needle); 1438 if (idx == (u32)-1) 1439 inline_panic(a, "%[name] does not match any constraint"); 1440 p = nend; /* loop's ++p steps past the ']' */ 1441 render_operand(a, sb, idx, 0); 1442 continue; 1443 } 1444 int form = 0; /* 0=default, 1=w, 2=x, 3=a, 4=z */ 1445 if (n == 'w' || n == 'x' || n == 'a' || n == 'z') { 1446 form = (n == 'w') ? 1 : (n == 'x') ? 2 : (n == 'a') ? 3 : 4; 1447 ++p; 1448 if (p + 1 >= end) inline_panic(a, "trailing '%' modifier in template"); 1449 n = *(p + 1); 1450 } 1451 if (n == '[') { 1452 const char* nbeg = p + 2; 1453 const char* nend = nbeg; 1454 while (nend < end && *nend != ']') ++nend; 1455 if (nend == end) inline_panic(a, "unterminated %[name]"); 1456 size_t nlen = (size_t)(nend - nbeg); 1457 Sym needle = 1458 pool_intern_slice(a->c->global, (Slice){.s = nbeg, .len = nlen}); 1459 u32 idx = lookup_named(a, needle); 1460 if (idx == (u32)-1) 1461 inline_panic(a, "%[name] does not match any constraint"); 1462 p = nend; 1463 render_operand(a, sb, idx, form); 1464 continue; 1465 } 1466 if (n < '0' || n > '9') inline_panic(a, "expected digit after '%'"); 1467 u32 idx = (u32)(n - '0'); 1468 ++p; 1469 /* GCC syntax permits up to two digits (%0..%99). */ 1470 if (p + 1 < end && *(p + 1) >= '0' && *(p + 1) <= '9') { 1471 idx = idx * 10 + (u32)(*(p + 1) - '0'); 1472 ++p; 1473 } 1474 render_operand(a, sb, idx, form); 1475 } 1476 if (sb->truncated) inline_panic(a, "inline asm line buffer overflow"); 1477 run_one_line(a, mc, strbuf_cstr(sb), strbuf_len(sb)); 1478 } 1479 1480 void rv64_asm_run_template(Rv64Asm* a, MCEmitter* mc, const char* tmpl) { 1481 if (!tmpl || !*tmpl) return; 1482 1483 char buf[RV64_INLINE_LINE_CAP]; 1484 StrBuf sb; 1485 strbuf_init(&sb, buf, sizeof buf); 1486 1487 /* Walk tmpl, splitting on '\n' and ';'. Track paren depth and quote 1488 * state so that a literal ';' inside `( ... )` (memory operand) or a 1489 * quoted string is not mistaken for a statement separator. RISC-V uses 1490 * `disp(base)` for memory, hence we track parens. */ 1491 const char* line_start = tmpl; 1492 int paren = 0; 1493 char quote = 0; 1494 for (const char* p = tmpl;; ++p) { 1495 char c = *p; 1496 if (c == '\0') { 1497 render_and_run_line(a, mc, &sb, line_start, p); 1498 break; 1499 } 1500 if (quote) { 1501 if (c == '\\' && *(p + 1)) { 1502 ++p; 1503 continue; 1504 } 1505 if (c == quote) quote = 0; 1506 continue; 1507 } 1508 if (c == '"' || c == '\'') { 1509 quote = c; 1510 continue; 1511 } 1512 if (c == '(') { 1513 ++paren; 1514 continue; 1515 } 1516 if (c == ')') { 1517 if (paren) --paren; 1518 continue; 1519 } 1520 if (paren == 0 && (c == '\n' || c == ';')) { 1521 render_and_run_line(a, mc, &sb, line_start, p); 1522 line_start = p + 1; 1523 } 1524 } 1525 }