asm.c (57362B)
1 /* GNU-as compatible assembler driver — arch-agnostic. 2 * 3 * Reads tokens from an AsmLexer, dispatches directives, manages labels and 4 * section state, and forwards mnemonic lines to the per-arch instruction 5 * parser. Output goes through MCEmitter against an ObjBuilder. 6 * 7 * AsmLexer quirks worked around here: 8 * - `#` is both the immediate marker in asm and the token used for 9 * preprocessed-assembler line markers. 10 * `#` at BOL is a cpp linemarker → skip to next newline; elsewhere 11 * the per-arch parser treats it as the immediate prefix. 12 * - composite mnemonics (`b.eq`, `b.ne`, ...) arrive as IDENT '.' IDENT 13 * and are reassembled before dispatch. 14 * - `.text` etc. arrive as PUNCT('.') + IDENT and are stitched here. 15 * 16 * Symbol bookkeeping: a Sym→ObjSymId map records the symbols introduced 17 * by labels, `.globl`, and operand references so a forward reference 18 * (`b foo` before `foo:`) shares one symbol with its later definition. 19 * A second Sym→AsmEqu map carries `.set`/`.equ` constants. */ 20 21 #include "asm/asm.h" 22 23 #include <stdarg.h> 24 #include <string.h> 25 26 #include "arch/arch.h" 27 #include "asm/asm_helpers.h" 28 #include "asm/asm_lex.h" 29 #include "core/arena.h" 30 #include "core/hashmap.h" 31 #include "core/heap.h" 32 #include "core/pool.h" 33 #include "core/slice.h" 34 #include "core/vec.h" 35 #include "obj/obj.h" 36 #include "obj/reloc_apply.h" 37 38 HASHMAP_DEFINE(SymSecMap, Sym, ObjSecId, hash_u32); 39 HASHMAP_DEFINE(SymSymMap, Sym, ObjSymId, hash_u32); 40 HASHMAP_DEFINE(SymU8Map, Sym, u8, hash_u32); 41 42 typedef struct AsmEqu { 43 i64 value; 44 ObjSymId sym; /* nonzero when value is `sym + offset` */ 45 u8 has_sym; 46 u8 pad[3]; 47 } AsmEqu; 48 HASHMAP_DEFINE(SymEquMap, Sym, AsmEqu, hash_u32); 49 50 /* A `sym1 - sym2` data difference (e.g. `.quad _end - _start`) recorded at 51 * emit time and resolved after the parse, once every label is defined. The 52 * field at <section,offset> already holds the net constant addend. */ 53 typedef struct AsmDiff { 54 ObjSecId section; /* fixup site section */ 55 u32 offset; /* fixup site offset within `section` */ 56 u32 width; /* 1/2/4/8 — the data width */ 57 ObjSymId minuend; /* sym1 */ 58 ObjSymId subtrahend; /* sym2 */ 59 i64 addend; /* net constant addend already written into the field */ 60 SrcLoc loc; /* the directive's location, for late diagnostics */ 61 } AsmDiff; 62 63 struct AsmDriver { 64 Compiler* c; 65 AsmLexer* lex; 66 MCEmitter* mc; 67 ObjBuilder* ob; 68 Pool* pool; 69 Heap* heap; 70 71 AsmTok cur; 72 int has_cur; 73 74 /* OBJ_SEC_NONE until first emit / explicit `.text` etc. */ 75 ObjSecId cur_sec; 76 77 SymSecMap sec_map; 78 SymSymMap sym_map; 79 SymU8Map thumb_func_map; 80 SymEquMap equ_map; 81 82 /* Deferred `sym1 - sym2` data differences (resolve_sym_diffs). */ 83 AsmDiff* diffs; 84 u32 ndiffs; 85 u32 diffs_cap; 86 87 Sym n_text, n_data, n_rodata, n_bss; 88 u8 pending_thumb_func; 89 90 ArchAsm* arch_asm; 91 }; 92 93 /* ---- token plumbing ---- */ 94 95 static AsmTok d_peek(AsmDriver* d) { 96 if (!d->has_cur) { 97 d->cur = asm_lex_next(d->lex); 98 d->has_cur = 1; 99 } 100 return d->cur; 101 } 102 103 static AsmTok d_next(AsmDriver* d) { 104 AsmTok t = d_peek(d); 105 d->has_cur = 0; 106 return t; 107 } 108 109 static int d_is_eol(AsmDriver* d) { 110 AsmTok t = d_peek(d); 111 return t.kind == ASM_TOK_NEWLINE || t.kind == ASM_TOK_EOF; 112 } 113 114 static void d_skip_to_eol(AsmDriver* d) { 115 while (!d_is_eol(d)) (void)d_next(d); 116 } 117 118 static void d_eat_eol(AsmDriver* d) { 119 AsmTok t = d_peek(d); 120 if (t.kind == ASM_TOK_NEWLINE) (void)d_next(d); 121 } 122 123 static SrcLoc d_loc(AsmDriver* d) { 124 if (d->has_cur) return d->cur.loc; 125 return asm_lex_loc(d->lex); 126 } 127 128 _Noreturn static void d_panicf(AsmDriver* d, const char* fmt, ...) { 129 va_list ap; 130 va_start(ap, fmt); 131 compiler_panicv(d->c, d_loc(d), fmt, ap); 132 /* unreachable; va_end omitted because compiler_panicv is _Noreturn */ 133 } 134 135 /* ---- spelling helpers ---- */ 136 137 static const char* asm_str(AsmDriver* d, Sym s, size_t* nout) { 138 Slice sl = pool_slice(d->pool, s); 139 if (nout) *nout = sl.len; 140 return sl.s; 141 } 142 143 static int sym_eq(AsmDriver* d, Sym s, const char* lit) { 144 size_t n = 0; 145 const char* p = asm_str(d, s, &n); 146 size_t i; 147 if (!p) return 0; 148 for (i = 0; i < n; ++i) { 149 if (!lit[i] || p[i] != lit[i]) return 0; 150 } 151 return lit[n] == '\0'; 152 } 153 154 static int starts_with(AsmDriver* d, Sym s, const char* prefix) { 155 size_t n = 0; 156 const char* p = asm_str(d, s, &n); 157 size_t i; 158 if (!p) return 0; 159 for (i = 0; prefix[i]; ++i) { 160 if (i >= n || p[i] != prefix[i]) return 0; 161 } 162 return 1; 163 } 164 165 /* ---- section management ---- */ 166 167 static ObjSecId ensure_section_ex(AsmDriver* d, Sym name, SecKind kind, u16 sem, 168 u16 flags, u32 align) { 169 ObjSecId* hit = SymSecMap_get(&d->sec_map, name); 170 ObjSecId id; 171 if (hit) return *hit; 172 id = obj_section_ex(d->ob, name, kind, sem, flags, align, 0, OBJ_SEC_NONE, 0); 173 SymSecMap_set(&d->sec_map, name, id); 174 return id; 175 } 176 177 static ObjSecId ensure_section(AsmDriver* d, Sym name, SecKind kind, u16 flags, 178 u32 align) { 179 /* A .bss section is NOBITS: it stores no bytes, only a size. Create it that 180 * way (codegen does the same via obj_section_ex) so the ELF emitter writes 181 * SHT_NOBITS and `.zero`/labels track bss_size, not a byte buffer — matching 182 * `cc -c` so the round-tripped object isn't a writable-but-loaded .bss. */ 183 return ensure_section_ex(d, name, kind, 184 kind == SEC_BSS ? SSEM_NOBITS : SSEM_PROGBITS, flags, 185 align); 186 } 187 188 static void set_section(AsmDriver* d, Sym name, SecKind kind, u16 flags, 189 u32 align) { 190 ObjSecId id = ensure_section(d, name, kind, flags, align); 191 d->cur_sec = id; 192 mc_set_section(d->mc, id); 193 } 194 195 /* ---- symbol management ---- */ 196 197 static ObjSymId intern_sym(AsmDriver* d, Sym name) { 198 ObjSymId* hit = SymSymMap_get(&d->sym_map, name); 199 if (hit) return *hit; 200 ObjSymId id = obj_symbol_find(d->ob, name); 201 if (id == OBJ_SYM_NONE) { 202 id = obj_symbol_ex(d->ob, name, SB_LOCAL, SV_DEFAULT, SK_NOTYPE, 203 OBJ_SEC_NONE, 0, 0, 0); 204 } 205 SymSymMap_set(&d->sym_map, name, id); 206 return id; 207 } 208 209 static ObjSym* sym_mut(AsmDriver* d, ObjSymId id) { 210 /* obj.h gives us a const view via obj_symbol_get; the underlying 211 * record lives in the builder's arena and is safe to mutate 212 * pre-finalize. Wrapping the cast keeps the const-stripping in 213 * one place. */ 214 return (ObjSym*)obj_symbol_get(d->ob, id); 215 } 216 217 /* 1 if the active arch tags function symbols with a Thumb (LSB) ISA-state 218 * bit. Replaces the former `target.arch == KIT_ARCH_ARM_32` identity check: 219 * the behavior now hangs off the arch's ArchAsmOps vtable (set only on arm32), 220 * so generic assembler code stops switching on arch identity. */ 221 static int asm_thumb_func_syms(const AsmDriver* d) { 222 const ArchImpl* a = d->c ? arch_for_compiler(d->c) : NULL; 223 return a && a->asm_ops && a->asm_ops->thumb_function_symbols; 224 } 225 226 static int asm_sym_is_thumb_func(AsmDriver* d, Sym name) { 227 u8* hit = SymU8Map_get(&d->thumb_func_map, name); 228 return hit && *hit; 229 } 230 231 static void mark_thumb_func(AsmDriver* d, Sym name) { 232 ObjSymId id; 233 ObjSym* s; 234 if (!asm_thumb_func_syms(d)) return; 235 (void)SymU8Map_set(&d->thumb_func_map, name, 1u); 236 id = intern_sym(d, name); 237 s = sym_mut(d, id); 238 if (!s) return; 239 s->kind = (u16)SK_FUNC; 240 if (s->section_id != OBJ_SEC_NONE) s->value |= 1u; 241 } 242 243 /* GNU `as` makes any symbol that is referenced but neither defined nor 244 * declared `.local` an undefined *global* (a local UNDEF is meaningless 245 * in ELF and won't pull a member out of an archive at link time). 246 * intern_sym mints every new symbol SB_LOCAL/SK_NOTYPE, so after the 247 * parse we promote the ones that stayed undefined to global SK_UNDEF. 248 * Defined locals (labels), `.local` decls, and absolute/common symbols 249 * are left untouched. */ 250 static void promote_undef_externs(AsmDriver* d) { 251 ObjSymIter* it = obj_symiter_new(d->ob); 252 ObjSymEntry e; 253 while (obj_symiter_next(it, &e)) { 254 /* The iterator visits tombstoned slots too (see obj.h). Deferred 255 * anonymous const-data / jump-table symbols (obj_symbol_defer) sit as 256 * LOCAL/SK_OBJ/no-section tombstones until opt_whole_module_finalize 257 * materializes them — which, when a file-scope `asm` block (e.g. a 258 * FreeBSD header's `.symver`) replays before that finalize step, is 259 * *after* this pass runs. Promoting them here would resurface them as 260 * defined GLOBALs and collide at link (`duplicate definition of global 261 * symbol '.Lkit_ro.0'`), so skip tombstones like every other consumer. */ 262 if (e.sym->removed) continue; 263 if (e.sym->section_id != OBJ_SEC_NONE) continue; /* defined here */ 264 if (e.sym->bind != SB_LOCAL) continue; 265 if (e.sym->kind == SK_ABS || e.sym->kind == SK_COMMON) continue; 266 obj_symbol_set_bind(d->ob, e.id, SB_GLOBAL); 267 sym_mut(d, e.id)->kind = (u16)SK_UNDEF; 268 } 269 obj_symiter_free(it); 270 } 271 272 /* ---- expression evaluator (constants + sym ± const) ---- */ 273 274 typedef struct AsmExpr { 275 ObjSymId sym; /* minuend, or the sole symbol */ 276 ObjSymId sym2; /* subtrahend: nonzero => value is `(sym - sym2) + const` */ 277 i64 value; 278 u8 is_here; /* the location-counter token `.` (no sym, no value yet) */ 279 u8 pcrel; /* `sym - .`: emit a PC-relative data reloc instead of absolute */ 280 } AsmExpr; 281 282 static AsmExpr expr_c(i64 v) { 283 AsmExpr e = {OBJ_SYM_NONE, OBJ_SYM_NONE, v, 0, 0}; 284 return e; 285 } 286 static AsmExpr expr_s(ObjSymId s, i64 v) { 287 AsmExpr e = {s, OBJ_SYM_NONE, v, 0, 0}; 288 return e; 289 } 290 static AsmExpr expr_here(void) { 291 AsmExpr e = {OBJ_SYM_NONE, OBJ_SYM_NONE, 0, 1, 0}; 292 return e; 293 } 294 295 static int tok_is_punct(AsmTok t, u32 p) { 296 return t.kind == ASM_TOK_PUNCT && t.v.punct == p; 297 } 298 299 static i64 lit_to_i64(AsmDriver* d, Sym spelling) { 300 size_t n = 0; 301 const char* p = asm_str(d, spelling, &n); 302 u64 v = 0; 303 int base = 10; 304 size_t i = 0; 305 if (!p || !n) return 0; 306 if (n >= 2 && p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { 307 base = 16; 308 i = 2; 309 } else if (n >= 2 && p[0] == '0' && (p[1] == 'b' || p[1] == 'B')) { 310 base = 2; 311 i = 2; 312 } else if (n >= 1 && p[0] == '0') { 313 base = 8; 314 i = 1; 315 } 316 for (; i < n; ++i) { 317 char c = p[i]; 318 u32 dv; 319 if (c == 'u' || c == 'U' || c == 'l' || c == 'L') break; 320 if (c >= '0' && c <= '9') 321 dv = (u32)(c - '0'); 322 else if (c >= 'a' && c <= 'f') 323 dv = 10 + (u32)(c - 'a'); 324 else if (c >= 'A' && c <= 'F') 325 dv = 10 + (u32)(c - 'A'); 326 else 327 d_panicf(d, "asm: bad digit in integer literal"); 328 if (dv >= (u32)base) d_panicf(d, "asm: digit out of base"); 329 v = v * (u64)base + dv; 330 } 331 return (i64)v; 332 } 333 334 static AsmExpr parse_expr(AsmDriver*); 335 static AsmExpr parse_unary(AsmDriver*); 336 337 static AsmExpr parse_primary(AsmDriver* d) { 338 AsmTok t = d_peek(d); 339 if (t.kind == ASM_TOK_NUM) { 340 (void)d_next(d); 341 return expr_c(lit_to_i64(d, t.spelling)); 342 } 343 if (t.kind == ASM_TOK_IDENT) { 344 (void)d_next(d); 345 AsmEqu* eq = SymEquMap_get(&d->equ_map, t.v.ident); 346 if (eq) { 347 if (eq->has_sym) return expr_s(eq->sym, eq->value); 348 return expr_c(eq->value); 349 } 350 return expr_s(intern_sym(d, t.v.ident), 0); 351 } 352 if (tok_is_punct(t, '(')) { 353 (void)d_next(d); 354 AsmExpr e = parse_expr(d); 355 AsmTok cl = d_peek(d); 356 if (!tok_is_punct(cl, ')')) d_panicf(d, "asm: expected ')'"); 357 (void)d_next(d); 358 return e; 359 } 360 /* Lone `.` is the location counter (used in `sym - .` PC-relative data). */ 361 if (tok_is_punct(t, '.')) { 362 (void)d_next(d); 363 return expr_here(); 364 } 365 d_panicf(d, "asm: expected expression"); 366 } 367 368 static AsmExpr parse_unary(AsmDriver* d) { 369 AsmTok t = d_peek(d); 370 if (tok_is_punct(t, '-')) { 371 (void)d_next(d); 372 AsmExpr e = parse_unary(d); 373 if (e.sym) d_panicf(d, "asm: unary '-' on symbol"); 374 /* Unsigned negate so `$-9223372036854775808` (negating INT64_MIN) is 375 * well-defined 2's-complement, not signed-overflow UB. */ 376 return expr_c((i64)(0u - (u64)e.value)); 377 } 378 if (tok_is_punct(t, '+')) { 379 (void)d_next(d); 380 return parse_unary(d); 381 } 382 if (tok_is_punct(t, '~')) { 383 (void)d_next(d); 384 AsmExpr e = parse_unary(d); 385 if (e.sym) d_panicf(d, "asm: unary '~' on symbol"); 386 return expr_c(~e.value); 387 } 388 return parse_primary(d); 389 } 390 391 static AsmExpr parse_mul(AsmDriver* d) { 392 AsmExpr a = parse_unary(d); 393 for (;;) { 394 AsmTok t = d_peek(d); 395 if (!tok_is_punct(t, '*') && !tok_is_punct(t, '/') && !tok_is_punct(t, '%')) 396 return a; 397 u32 op = t.v.punct; 398 (void)d_next(d); 399 AsmExpr b = parse_unary(d); 400 if (a.sym || b.sym || a.is_here || b.is_here) 401 d_panicf(d, "asm: '*/%%' on symbolic operand"); 402 if (op == '*') 403 a.value *= b.value; 404 else if (op == '/') { 405 if (!b.value) d_panicf(d, "asm: division by zero"); 406 a.value /= b.value; 407 } else { 408 if (!b.value) d_panicf(d, "asm: modulo by zero"); 409 a.value %= b.value; 410 } 411 } 412 } 413 414 static AsmExpr parse_add(AsmDriver* d) { 415 AsmExpr a = parse_mul(d); 416 for (;;) { 417 AsmTok t = d_peek(d); 418 if (!tok_is_punct(t, '+') && !tok_is_punct(t, '-')) return a; 419 u32 op = t.v.punct; 420 (void)d_next(d); 421 AsmExpr b = parse_mul(d); 422 /* `sym - .`: a PC-relative data reference. `.` is the location of the 423 * field being emitted, so the relocation's P equals its own offset and the 424 * RELA addend stays `a.value` (typically 0). */ 425 if (op == '-' && b.is_here) { 426 if (!a.sym) d_panicf(d, "asm: '- .' requires a symbol operand"); 427 a.pcrel = 1; 428 continue; 429 } 430 if (a.is_here || b.is_here) 431 d_panicf(d, "asm: '.' location counter only valid as `sym - .`"); 432 if (op == '+') { 433 if (a.sym && b.sym) d_panicf(d, "asm: cannot add two symbols"); 434 if (b.sym) { 435 /* const + (b.sym [- b.sym2] + k): adopt b's symbol(s). */ 436 a.sym = b.sym; 437 a.sym2 = b.sym2; 438 a.value += b.value; 439 } else 440 a.value += b.value; 441 } else { 442 if (b.sym) { 443 /* sym1 - sym2: a symbol difference. Resolves to a link-time constant 444 * (both symbols in one section) or a relocation pair otherwise. */ 445 if (!a.sym) d_panicf(d, "asm: cannot subtract symbol from constant"); 446 if (a.sym2 || b.sym2) 447 d_panicf(d, "asm: more than one symbol difference in expression"); 448 a.sym2 = b.sym; 449 a.value -= b.value; 450 } else 451 a.value -= b.value; 452 } 453 } 454 } 455 456 static AsmExpr parse_shift(AsmDriver* d) { 457 AsmExpr a = parse_add(d); 458 for (;;) { 459 AsmTok t = d_peek(d); 460 if (!tok_is_punct(t, ASM_P_SHL) && !tok_is_punct(t, ASM_P_SHR)) return a; 461 u32 op = t.v.punct; 462 (void)d_next(d); 463 AsmExpr b = parse_add(d); 464 if (a.sym || b.sym) d_panicf(d, "asm: shift on symbolic operand"); 465 if (op == ASM_P_SHL) 466 a.value = (i64)((u64)a.value << (b.value & 63)); 467 else 468 a.value = a.value >> (b.value & 63); 469 } 470 } 471 472 static AsmExpr parse_band(AsmDriver* d) { 473 AsmExpr a = parse_shift(d); 474 for (;;) { 475 AsmTok t = d_peek(d); 476 if (!tok_is_punct(t, '&')) return a; 477 (void)d_next(d); 478 AsmExpr b = parse_shift(d); 479 if (a.sym || b.sym) d_panicf(d, "asm: '&' on symbolic operand"); 480 a.value &= b.value; 481 } 482 } 483 484 static AsmExpr parse_bxor(AsmDriver* d) { 485 AsmExpr a = parse_band(d); 486 for (;;) { 487 AsmTok t = d_peek(d); 488 if (!tok_is_punct(t, '^')) return a; 489 (void)d_next(d); 490 AsmExpr b = parse_band(d); 491 if (a.sym || b.sym) d_panicf(d, "asm: '^' on symbolic operand"); 492 a.value ^= b.value; 493 } 494 } 495 496 static AsmExpr parse_bor(AsmDriver* d) { 497 AsmExpr a = parse_bxor(d); 498 for (;;) { 499 AsmTok t = d_peek(d); 500 if (!tok_is_punct(t, '|')) return a; 501 (void)d_next(d); 502 AsmExpr b = parse_bxor(d); 503 if (a.sym || b.sym) d_panicf(d, "asm: '|' on symbolic operand"); 504 a.value |= b.value; 505 } 506 } 507 508 static AsmExpr parse_expr(AsmDriver* d) { return parse_bor(d); } 509 510 /* ---- public helpers exposed to per-arch parser ---- */ 511 512 AsmTok asm_driver_peek(AsmDriver* d) { return d_peek(d); } 513 AsmTok asm_driver_next(AsmDriver* d) { return d_next(d); } 514 int asm_driver_at_eol(AsmDriver* d) { return d_is_eol(d); } 515 SrcLoc asm_driver_loc(AsmDriver* d) { return d_loc(d); } 516 MCEmitter* asm_driver_mc(AsmDriver* d) { return d->mc; } 517 ObjBuilder* asm_driver_ob(AsmDriver* d) { return d->ob; } 518 Compiler* asm_driver_compiler(AsmDriver* d) { return d->c; } 519 Pool* asm_driver_pool(AsmDriver* d) { return d->pool; } 520 521 _Noreturn void asm_driver_panic(AsmDriver* d, const char* fmt, ...) { 522 va_list ap; 523 va_start(ap, fmt); 524 compiler_panicv(d->c, d_loc(d), fmt, ap); 525 } 526 527 ObjSymId asm_driver_intern_sym(AsmDriver* d, Sym name) { 528 return intern_sym(d, name); 529 } 530 531 ObjSecId asm_driver_cur_section(AsmDriver* d) { 532 if (d->cur_sec == OBJ_SEC_NONE) { 533 if (!d->n_text) d->n_text = pool_intern_slice(d->pool, SLICE_LIT(".text")); 534 d->cur_sec = 535 ensure_section(d, d->n_text, SEC_TEXT, (u16)(SF_ALLOC | SF_EXEC), 4); 536 mc_set_section(d->mc, d->cur_sec); 537 } 538 return d->cur_sec; 539 } 540 541 int asm_driver_eat_comma(AsmDriver* d) { 542 AsmTok t = d_peek(d); 543 if (tok_is_punct(t, ',')) { 544 (void)d_next(d); 545 return 1; 546 } 547 return 0; 548 } 549 550 int asm_driver_eat_punct(AsmDriver* d, u32 p) { 551 AsmTok t = d_peek(d); 552 if (tok_is_punct(t, p)) { 553 (void)d_next(d); 554 return 1; 555 } 556 /* `#` arrives as ASM_TOK_HASH from the C lexer; accept it as the 557 * immediate-prefix punctuator here. */ 558 if (p == '#' && t.kind == ASM_TOK_HASH) { 559 (void)d_next(d); 560 return 1; 561 } 562 return 0; 563 } 564 565 void asm_driver_expect_punct(AsmDriver* d, u32 p, const char* what) { 566 if (!asm_driver_eat_punct(d, p)) { 567 /* Single-char punctuators (incl. '#') carry their ASCII value in `p`; 568 * print it directly. Multi-char puncts fall back to the description. */ 569 char ch = (char)p; 570 if (p >= 0x20 && p < 0x7f) 571 d_panicf(d, "asm: expected '%c' (%.*s)", ch, 572 SLICE_ARG(slice_from_cstr(what))); 573 d_panicf(d, "asm: expected punctuator (%.*s)", 574 SLICE_ARG(slice_from_cstr(what))); 575 } 576 } 577 578 i64 asm_driver_parse_const(AsmDriver* d) { 579 AsmExpr e = parse_expr(d); 580 if (e.sym) d_panicf(d, "asm: constant expression expected"); 581 return e.value; 582 } 583 584 void asm_driver_parse_sym_expr(AsmDriver* d, ObjSymId* sym_out, i64* off_out) { 585 AsmExpr e = parse_expr(d); 586 *sym_out = e.sym; 587 *off_out = e.value; 588 } 589 590 int asm_driver_tok_is_punct(AsmTok t, u32 p) { 591 if (tok_is_punct(t, p)) return 1; 592 /* `#` arrives as ASM_TOK_HASH from the C lexer. */ 593 if (p == '#' && t.kind == ASM_TOK_HASH) return 1; 594 return 0; 595 } 596 597 /* ---- string-literal decoding ---- */ 598 599 /* Decode a string literal's spelling into raw bytes. *out is the heap buffer 600 * (allocated with size *cap_out, which the caller must pass back to free — 601 * the decoded length *nout <= *cap_out once escapes collapse). */ 602 static void decode_string(AsmDriver* d, Sym spelling, u8** out, u32* nout, 603 size_t* cap_out) { 604 size_t n = 0; 605 const char* p = asm_str(d, spelling, &n); 606 /* Skip any encoding prefix (L/u/u8/U). */ 607 while (n && (*p == 'L' || *p == 'u' || *p == 'U' || *p == '8')) { 608 ++p; 609 --n; 610 } 611 if (n < 2 || p[0] != '"' || p[n - 1] != '"') 612 d_panicf(d, "asm: malformed string literal"); 613 size_t cap = n ? n : 1; 614 u8* buf = (u8*)d->heap->alloc(d->heap, cap, 1); 615 u32 k = 0; 616 for (size_t i = 1; i + 1 < n; ++i) { 617 char c = p[i]; 618 if (c != '\\') { 619 buf[k++] = (u8)c; 620 continue; 621 } 622 ++i; 623 if (i + 1 >= n) break; 624 char e = p[i]; 625 switch (e) { 626 case 'n': 627 buf[k++] = '\n'; 628 break; 629 case 't': 630 buf[k++] = '\t'; 631 break; 632 case 'r': 633 buf[k++] = '\r'; 634 break; 635 case '\\': 636 buf[k++] = '\\'; 637 break; 638 case '"': 639 buf[k++] = '"'; 640 break; 641 case '\'': 642 buf[k++] = '\''; 643 break; 644 case '0': 645 buf[k++] = 0; 646 break; 647 case 'b': 648 buf[k++] = 8; 649 break; 650 case 'f': 651 buf[k++] = 12; 652 break; 653 case 'v': 654 buf[k++] = 11; 655 break; 656 case 'a': 657 buf[k++] = 7; 658 break; 659 case 'x': { 660 u32 v = 0; 661 int dn = 0; 662 while (i + 2 < n) { 663 char h = p[i + 1]; 664 int dv; 665 if (h >= '0' && h <= '9') 666 dv = h - '0'; 667 else if (h >= 'a' && h <= 'f') 668 dv = 10 + (h - 'a'); 669 else if (h >= 'A' && h <= 'F') 670 dv = 10 + (h - 'A'); 671 else 672 break; 673 v = v * 16 + (u32)dv; 674 ++i; 675 if (++dn >= 2) break; 676 } 677 buf[k++] = (u8)v; 678 break; 679 } 680 default: 681 if (e >= '0' && e <= '7') { 682 u32 v = (u32)(e - '0'); 683 int dn = 1; 684 while (dn < 3 && i + 2 < n) { 685 char h = p[i + 1]; 686 if (h < '0' || h > '7') break; 687 v = v * 8 + (u32)(h - '0'); 688 ++i; 689 ++dn; 690 } 691 buf[k++] = (u8)v; 692 } else { 693 buf[k++] = (u8)e; 694 } 695 break; 696 } 697 } 698 *out = buf; 699 *nout = k; 700 *cap_out = cap; 701 } 702 703 /* ---- directives ---- */ 704 705 static Sym expect_ident(AsmDriver* d, const char* what) { 706 AsmTok t = d_peek(d); 707 if (t.kind != ASM_TOK_IDENT) 708 d_panicf(d, "asm: %.*s: expected identifier", 709 SLICE_ARG(slice_from_cstr(what))); 710 (void)d_next(d); 711 return t.v.ident; 712 } 713 714 static void emit_le(AsmDriver* d, u64 v, u32 width) { 715 u8 buf[8]; 716 for (u32 i = 0; i < width; ++i) buf[i] = (u8)(v >> (8 * i)); 717 (void)asm_driver_cur_section(d); 718 mc_emit_bytes(d->mc, buf, width); 719 } 720 721 /* Record a `sym1 - sym2` data difference for resolution after the parse, 722 * once every label is defined. The field already holds the net addend. */ 723 static void record_sym_diff(AsmDriver* d, ObjSecId sec, u32 ofs, u32 width, 724 ObjSymId minuend, ObjSymId subtrahend, i64 addend) { 725 if (VEC_GROW(d->heap, d->diffs, d->diffs_cap, d->ndiffs + 1)) 726 d_panicf(d, "asm: out of memory recording symbol difference"); 727 AsmDiff* df = &d->diffs[d->ndiffs++]; 728 df->section = sec; 729 df->offset = ofs; 730 df->width = width; 731 df->minuend = minuend; 732 df->subtrahend = subtrahend; 733 df->addend = addend; 734 df->loc = d_loc(d); 735 } 736 737 static void emit_int_directive(AsmDriver* d, u32 width) { 738 for (;;) { 739 AsmExpr e = parse_expr(d); 740 if (e.sym && e.sym2) { 741 /* `sym1 - sym2` difference: emit the net addend as a placeholder and 742 * defer resolution until all labels are defined (a forward-referenced 743 * minuend like `_end` is still undefined here). */ 744 (void)asm_driver_cur_section(d); 745 u32 ofs = mc_pos(d->mc); 746 emit_le(d, (u64)e.value, width); 747 record_sym_diff(d, d->cur_sec, ofs, width, e.sym, e.sym2, e.value); 748 } else if (e.sym) { 749 RelocKind k; 750 if (e.pcrel) { 751 /* `sym - .`: PC-relative data. Only the 32/64-bit widths codegen 752 * emits via kit_cg_data_pcrel are supported. */ 753 if (width == 4) 754 k = R_PC32; 755 else if (width == 8) 756 k = R_PC64; 757 else 758 d_panicf(d, "asm: PC-relative `sym - .` needs .long/.quad"); 759 } else if (width == 4) 760 k = R_ABS32; 761 else if (width == 8) 762 k = R_ABS64; 763 else 764 d_panicf(d, "asm: symbolic .byte/.hword not supported"); 765 (void)asm_driver_cur_section(d); 766 u32 ofs = mc_pos(d->mc); 767 /* Write the addend into the data field, not zero. Mach-O relocations 768 * carry the addend implicitly in the relocated field (REL form); writing 769 * zero loses it (every `.quad sym+N` would resolve to sym+0 — a switch 770 * jump table dispatching into hyperspace). codegen pre-writes the addend 771 * the same way. On ELF (RELA) the linker overwrites the field with S+A, 772 * so the pre-written value is harmless there. */ 773 emit_le(d, (u64)e.value, width); 774 mc_emit_reloc_at(d->mc, d->cur_sec, ofs, k, e.sym, e.value, 1, 0); 775 } else { 776 emit_le(d, (u64)e.value, width); 777 } 778 if (!asm_driver_eat_comma(d)) break; 779 } 780 } 781 782 static void do_directive(AsmDriver* d, Sym name) { 783 if (sym_eq(d, name, "text")) { 784 if (!d->n_text) d->n_text = pool_intern_slice(d->pool, SLICE_LIT(".text")); 785 set_section(d, d->n_text, SEC_TEXT, (u16)(SF_ALLOC | SF_EXEC), 4); 786 d_skip_to_eol(d); 787 return; 788 } 789 if (sym_eq(d, name, "data")) { 790 if (!d->n_data) d->n_data = pool_intern_slice(d->pool, SLICE_LIT(".data")); 791 set_section(d, d->n_data, SEC_DATA, (u16)(SF_ALLOC | SF_WRITE), 8); 792 d_skip_to_eol(d); 793 return; 794 } 795 if (sym_eq(d, name, "rodata")) { 796 if (!d->n_rodata) 797 d->n_rodata = pool_intern_slice(d->pool, SLICE_LIT(".rodata")); 798 set_section(d, d->n_rodata, SEC_RODATA, (u16)SF_ALLOC, 8); 799 d_skip_to_eol(d); 800 return; 801 } 802 if (sym_eq(d, name, "bss")) { 803 if (!d->n_bss) d->n_bss = pool_intern_slice(d->pool, SLICE_LIT(".bss")); 804 set_section(d, d->n_bss, SEC_BSS, (u16)(SF_ALLOC | SF_WRITE), 8); 805 d_skip_to_eol(d); 806 return; 807 } 808 if (sym_eq(d, name, "section")) { 809 Sym sname = 0; 810 AsmTok t = d_peek(d); 811 if (t.kind == ASM_TOK_STR) { 812 size_t n = 0; 813 const char* p = asm_str(d, t.spelling, &n); 814 if (n >= 2 && p[0] == '"') 815 sname = pool_intern_slice(d->pool, (Slice){.s = p + 1, .len = n - 2}); 816 (void)d_next(d); 817 } else if (t.kind == ASM_TOK_IDENT || tok_is_punct(t, '.')) { 818 /* A bare section name. The lexer breaks a dotted name like 819 * `.rodata.toy.merge` into PUNCT('.')+IDENT segments (the `.`+digit 820 * identifier rule does not glue `.`+letter), so reassemble the full 821 * dotted spelling by consuming each adjacent `.segment`. Stops at the 822 * `, "flags"` operands (the next token is then a comma). */ 823 char buf[128]; 824 size_t bn = 0; 825 int leading_dot = tok_is_punct(t, '.'); 826 if (leading_dot) { 827 (void)d_next(d); 828 buf[bn++] = '.'; 829 } 830 AsmTok id = d_next(d); 831 if (id.kind != ASM_TOK_IDENT) d_panicf(d, "asm: .section: bad name"); 832 for (;;) { 833 size_t ni = 0; 834 const char* nm = asm_str(d, id.spelling, &ni); 835 if (bn + ni >= sizeof buf) d_panicf(d, "asm: .section: name too long"); 836 for (size_t i = 0; i < ni; ++i) buf[bn++] = nm[i]; 837 /* Glue a following `.<ident>` (or `.<num>`) segment, no whitespace. */ 838 if (!tok_is_punct(d_peek(d), '.')) break; 839 (void)d_next(d); /* '.' */ 840 AsmTok seg = d_peek(d); 841 if (seg.kind != ASM_TOK_IDENT && seg.kind != ASM_TOK_NUM) { 842 /* A lone trailing '.' is not part of the name; put it back is not 843 * supported, but section names never end in '.', so this is a 844 * malformed directive. */ 845 d_panicf(d, "asm: .section: bad name"); 846 } 847 if (bn + 1 >= sizeof buf) d_panicf(d, "asm: .section: name too long"); 848 buf[bn++] = '.'; 849 id = d_next(d); 850 } 851 sname = pool_intern_slice(d->pool, (Slice){.s = buf, .len = bn}); 852 } else { 853 d_panicf(d, "asm: .section: expected name"); 854 } 855 SecKind kind = SEC_OTHER; 856 u16 sem = SSEM_PROGBITS; 857 u16 flags = 0; 858 u32 entsize = 0; 859 int have_flags = 0; 860 int macho_2pos = 0; 861 862 /* Mach-O `.section segname,sectname[,type[,attrs]]`: the token after the 863 * first comma is a bare sectname IDENT (vs GNU's "flags" STRING). kit as 864 * parses the dialect of its target only (no hybrid), so this branch is 865 * gated on the Mach-O object format. Rebuild the comma-joined "seg,sect" 866 * name that the Mach-O writer's name_to_seg_sect splits back. */ 867 if (d->c->target.obj == KIT_OBJ_MACHO && tok_is_punct(d_peek(d), ',')) { 868 (void)d_next(d); /* eat ',' */ 869 AsmTok sect = d_next(d); 870 size_t sgn = 0, scn = 0; 871 const char* sgp; 872 const char* scp; 873 char buf[128]; 874 if (sect.kind != ASM_TOK_IDENT) 875 d_panicf(d, "asm: .section: expected Mach-O sectname after ','"); 876 sgp = asm_str(d, sname, &sgn); 877 scp = asm_str(d, sect.v.ident, &scn); 878 if (sgn + 1 + scn >= sizeof buf) 879 d_panicf(d, "asm: .section: name too long"); 880 memcpy(buf, sgp, sgn); 881 buf[sgn] = ','; 882 memcpy(buf + sgn + 1, scp, scn); 883 sname = 884 pool_intern_slice(d->pool, (Slice){.s = buf, .len = sgn + 1 + scn}); 885 macho_2pos = 1; 886 /* Optional trailing Mach-O type/attribute fields (regular, 887 * cstring_literals, …): accept and consume; map the few affecting 888 * flags. */ 889 while (asm_driver_eat_comma(d)) { 890 AsmTok ty = d_peek(d); 891 if (ty.kind == ASM_TOK_IDENT) { 892 size_t tn = 0; 893 const char* tp = asm_str(d, ty.v.ident, &tn); 894 if (tn == 16 && memcmp(tp, "cstring_literals", 16) == 0) 895 flags |= SF_STRINGS; 896 (void)d_next(d); 897 } else if (ty.kind == ASM_TOK_NUM) { 898 (void)d_next(d); 899 } else { 900 break; 901 } 902 } 903 } 904 905 /* Optional GNU-as operands: , "flags" [, @type [, entsize]]. The emitter 906 * (src/api/asm_emit.c) writes these for SEC_OTHER named sections; parse 907 * them back so a global's section flags/entsize round-trip faithfully. */ 908 if (!macho_2pos && asm_driver_eat_comma(d)) { 909 AsmTok ft = d_peek(d); 910 if (ft.kind == ASM_TOK_STR) { 911 size_t fn = 0; 912 const char* fp = asm_str(d, ft.spelling, &fn); 913 size_t fi; 914 for (fi = 0; fp && fi < fn; ++fi) { 915 switch (fp[fi]) { 916 case 'a': 917 flags |= SF_ALLOC; 918 break; 919 case 'w': 920 flags |= SF_WRITE; 921 break; 922 case 'x': 923 flags |= SF_EXEC; 924 break; 925 case 'M': 926 flags |= SF_MERGE; 927 break; 928 case 'S': 929 flags |= SF_STRINGS; 930 break; 931 case 'T': 932 flags |= SF_TLS; 933 break; 934 case 'R': 935 flags |= SF_RETAIN; 936 break; 937 default: 938 break; /* surrounding quotes / unknown letters */ 939 } 940 } 941 have_flags = 1; 942 (void)d_next(d); 943 if (asm_driver_eat_comma(d)) { 944 AsmTok ty = d_peek(d); 945 Sym tag = 0; 946 if (tok_is_punct(ty, '@')) { 947 (void)d_next(d); 948 AsmTok ti = d_next(d); /* the @type ident (progbits/nobits/note) */ 949 if (ti.kind == ASM_TOK_IDENT) tag = ti.v.ident; 950 } else if (ty.kind == ASM_TOK_IDENT) { 951 tag = d_next(d).v.ident; 952 } 953 if (tag) { 954 if (sym_eq(d, tag, "note")) { 955 sem = SSEM_NOTE; 956 } else if (sym_eq(d, tag, "nobits")) { 957 sem = SSEM_NOBITS; 958 } else if (sym_eq(d, tag, "init_array")) { 959 sem = SSEM_INIT_ARRAY; 960 } else if (sym_eq(d, tag, "fini_array")) { 961 sem = SSEM_FINI_ARRAY; 962 } else if (sym_eq(d, tag, "preinit_array")) { 963 sem = SSEM_PREINIT_ARRAY; 964 } 965 } 966 if (asm_driver_eat_comma(d)) { 967 AsmTok es = d_peek(d); 968 if (es.kind == ASM_TOK_NUM) { 969 entsize = (u32)lit_to_i64(d, es.spelling); 970 (void)d_next(d); 971 } 972 } 973 } 974 } 975 } 976 977 { 978 size_t nn = 0; 979 const char* p = asm_str(d, sname, &nn); 980 if (macho_2pos) { 981 /* Canonical Apple seg,sect → SecKind via the shared inverse of the 982 * writer's name_to_seg_sect; unrecognized spellings (e.g. 983 * __TEXT,__eh_frame) fall back to SEC_OTHER. Flags are derived from the 984 * resolved kind: every Mach-O section is allocated (so SEC_OTHER keeps 985 * SF_ALLOC), and __TEXT,__cstring additionally carries SF_STRINGS. The 986 * comma name is preserved so the writer round-trips seg/sect verbatim. 987 */ 988 if (!obj_macho_seckind_for_secname(p, nn, &kind)) kind = SEC_OTHER; 989 switch (kind) { 990 case SEC_TEXT: 991 flags |= (u16)(SF_ALLOC | SF_EXEC); 992 break; 993 case SEC_RODATA: 994 flags |= (u16)SF_ALLOC; 995 if (nn == 16 && memcmp(p, "__TEXT,__cstring", 16) == 0) 996 flags |= (u16)SF_STRINGS; 997 break; 998 case SEC_DATA: 999 case SEC_BSS: 1000 flags |= (u16)(SF_ALLOC | SF_WRITE); 1001 break; 1002 case SEC_DEBUG: 1003 break; 1004 default: 1005 flags |= (u16)SF_ALLOC; 1006 break; 1007 } 1008 } else if (have_flags) { 1009 /* Explicit flags: a canonical name keeps its kind; any other name is a 1010 * SEC_OTHER named section (matching codegen for section(...) globals). 1011 */ 1012 if (p && nn == 5 && memcmp(p, ".text", 5) == 0) 1013 kind = SEC_TEXT; 1014 else if (p && nn == 7 && memcmp(p, ".rodata", 7) == 0) 1015 kind = SEC_RODATA; 1016 else if (p && nn == 5 && memcmp(p, ".data", 5) == 0) 1017 kind = SEC_DATA; 1018 else if (p && nn == 4 && memcmp(p, ".bss", 4) == 0) 1019 kind = SEC_BSS; 1020 else 1021 kind = SEC_OTHER; 1022 } else if (p) { 1023 /* No flag string: infer kind+flags from a canonical name prefix. */ 1024 if (nn >= 5 && memcmp(p, ".text", 5) == 0) { 1025 kind = SEC_TEXT; 1026 flags = (u16)(SF_ALLOC | SF_EXEC); 1027 } else if (nn >= 7 && memcmp(p, ".rodata", 7) == 0) { 1028 kind = SEC_RODATA; 1029 flags = (u16)SF_ALLOC; 1030 } else if (nn >= 5 && memcmp(p, ".data", 5) == 0) { 1031 kind = SEC_DATA; 1032 flags = (u16)(SF_ALLOC | SF_WRITE); 1033 } else if (nn >= 4 && memcmp(p, ".bss", 4) == 0) { 1034 kind = SEC_BSS; 1035 flags = (u16)(SF_ALLOC | SF_WRITE); 1036 } 1037 } 1038 } 1039 if (kind == SEC_BSS) sem = SSEM_NOBITS; 1040 if (sem == SSEM_NOTE) kind = SEC_OTHER; 1041 1042 /* Consume any remaining operands (e.g. ,unique,N or group fields). */ 1043 d_skip_to_eol(d); 1044 { 1045 ObjSecId sid = ensure_section_ex(d, sname, kind, sem, flags, 1); 1046 if (entsize) obj_section_set_entsize(d->ob, sid, entsize); 1047 d->cur_sec = sid; 1048 mc_set_section(d->mc, sid); 1049 } 1050 return; 1051 } 1052 if (sym_eq(d, name, "globl") || sym_eq(d, name, "global")) { 1053 Sym n = expect_ident(d, ".globl"); 1054 sym_mut(d, intern_sym(d, n))->bind = (u16)SB_GLOBAL; 1055 d_skip_to_eol(d); 1056 return; 1057 } 1058 if (sym_eq(d, name, "local")) { 1059 Sym n = expect_ident(d, ".local"); 1060 sym_mut(d, intern_sym(d, n))->bind = (u16)SB_LOCAL; 1061 d_skip_to_eol(d); 1062 return; 1063 } 1064 /* `.weak_definition` is the Mach-O spelling for a weak *defined* symbol 1065 * (clang rejects GNU `.weak` on Mach-O). It pairs with a `.globl`; kit 1066 * collapses both to SB_WEAK, which the Mach-O emitter turns into 1067 * N_EXT|N_WEAK_DEF and ELF into STB_WEAK. */ 1068 if (sym_eq(d, name, "weak") || sym_eq(d, name, "weak_definition")) { 1069 Sym n = expect_ident(d, ".weak"); 1070 sym_mut(d, intern_sym(d, n))->bind = (u16)SB_WEAK; 1071 d_skip_to_eol(d); 1072 return; 1073 } 1074 if (sym_eq(d, name, "hidden")) { 1075 Sym n = expect_ident(d, ".hidden"); 1076 sym_mut(d, intern_sym(d, n))->vis = (u8)SV_HIDDEN; 1077 d_skip_to_eol(d); 1078 return; 1079 } 1080 if (sym_eq(d, name, "protected")) { 1081 Sym n = expect_ident(d, ".protected"); 1082 sym_mut(d, intern_sym(d, n))->vis = (u8)SV_PROTECTED; 1083 d_skip_to_eol(d); 1084 return; 1085 } 1086 if (sym_eq(d, name, "internal")) { 1087 Sym n = expect_ident(d, ".internal"); 1088 sym_mut(d, intern_sym(d, n))->vis = (u8)SV_INTERNAL; 1089 d_skip_to_eol(d); 1090 return; 1091 } 1092 if (sym_eq(d, name, "type")) { 1093 Sym n = expect_ident(d, ".type"); 1094 ObjSymId id = intern_sym(d, n); 1095 if (!asm_driver_eat_comma(d)) d_panicf(d, "asm: .type: expected ','"); 1096 AsmTok t = d_next(d); 1097 Sym tag = 0; 1098 if (tok_is_punct(t, '@') || tok_is_punct(t, '%')) { 1099 AsmTok ti = d_next(d); 1100 if (ti.kind != ASM_TOK_IDENT) d_panicf(d, "asm: .type: tag"); 1101 tag = ti.v.ident; 1102 } else if (t.kind == ASM_TOK_IDENT) { 1103 tag = t.v.ident; 1104 } else if (t.kind == ASM_TOK_STR) { 1105 size_t sn = 0; 1106 const char* sp = asm_str(d, t.spelling, &sn); 1107 if (sn >= 2 && sp[0] == '"' && sp[sn - 1] == '"') 1108 tag = pool_intern_slice(d->pool, (Slice){.s = sp + 1, .len = sn - 2}); 1109 } else { 1110 d_panicf(d, "asm: .type: tag"); 1111 } 1112 if (tag && sym_eq(d, tag, "function")) { 1113 sym_mut(d, id)->kind = (u16)SK_FUNC; 1114 if (asm_sym_is_thumb_func(d, n)) sym_mut(d, id)->value |= 1u; 1115 } else if (tag && sym_eq(d, tag, "object")) 1116 sym_mut(d, id)->kind = (u16)SK_OBJ; 1117 else if (tag && sym_eq(d, tag, "tls_object")) 1118 sym_mut(d, id)->kind = (u16)SK_TLS; 1119 else if (tag && sym_eq(d, tag, "gnu_indirect_function")) 1120 sym_mut(d, id)->kind = (u16)SK_IFUNC; 1121 d_skip_to_eol(d); 1122 return; 1123 } 1124 if (sym_eq(d, name, "thumb_func")) { 1125 if (asm_thumb_func_syms(d) && !d_is_eol(d)) { 1126 mark_thumb_func(d, expect_ident(d, ".thumb_func")); 1127 } else if (asm_thumb_func_syms(d)) { 1128 d->pending_thumb_func = 1; 1129 } 1130 d_skip_to_eol(d); 1131 return; 1132 } 1133 if (sym_eq(d, name, "size")) { 1134 Sym n = expect_ident(d, ".size"); 1135 ObjSymId id = intern_sym(d, n); 1136 if (!asm_driver_eat_comma(d)) d_panicf(d, "asm: .size: expected ','"); 1137 /* Recognize `. - NAME`. */ 1138 AsmTok t = d_peek(d); 1139 i64 sz = 0; 1140 if (tok_is_punct(t, '.')) { 1141 (void)d_next(d); 1142 if (tok_is_punct(d_peek(d), '-')) { 1143 (void)d_next(d); 1144 AsmTok rid = d_peek(d); 1145 if (rid.kind == ASM_TOK_IDENT && rid.v.ident == n) { 1146 (void)d_next(d); 1147 const ObjSym* os = obj_symbol_get(d->ob, id); 1148 if (os && os->section_id == d->cur_sec) { 1149 u64 start = os->value; 1150 if (asm_thumb_func_syms(d) && os->kind == SK_FUNC) 1151 start &= ~(u64)1; 1152 sz = (i64)mc_pos(d->mc) - (i64)start; 1153 } 1154 } 1155 } 1156 } else { 1157 AsmExpr e = parse_expr(d); 1158 if (!e.sym) sz = e.value; 1159 } 1160 if (sz < 0) sz = 0; 1161 sym_mut(d, id)->size = (u64)sz; 1162 d_skip_to_eol(d); 1163 return; 1164 } 1165 if (sym_eq(d, name, "byte")) { 1166 emit_int_directive(d, 1); 1167 d_skip_to_eol(d); 1168 return; 1169 } 1170 if (sym_eq(d, name, "hword") || sym_eq(d, name, "short") || 1171 sym_eq(d, name, "2byte")) { 1172 emit_int_directive(d, 2); 1173 d_skip_to_eol(d); 1174 return; 1175 } 1176 if (sym_eq(d, name, "word") || sym_eq(d, name, "long") || 1177 sym_eq(d, name, "int") || sym_eq(d, name, "4byte")) { 1178 emit_int_directive(d, 4); 1179 d_skip_to_eol(d); 1180 return; 1181 } 1182 if (sym_eq(d, name, "quad") || sym_eq(d, name, "8byte") || 1183 sym_eq(d, name, "dword") || sym_eq(d, name, "xword")) { 1184 emit_int_directive(d, 8); 1185 d_skip_to_eol(d); 1186 return; 1187 } 1188 /* .inst WORD[, WORD...] — emit raw 32-bit instruction word(s), little-endian 1189 * (AArch64/RISC-V are fixed 4-byte). This is how `cc -S` round-trips an 1190 * instruction the disassembler can't decode yet (`.inst 0x<word>`); silently 1191 * dropping it — the old behavior — deletes the instruction and miscompiles. 1192 * Matches GNU as / llvm-mc, which emit the word. */ 1193 if (sym_eq(d, name, "inst")) { 1194 (void)asm_driver_cur_section(d); 1195 for (;;) { 1196 i64 v = asm_driver_parse_const(d); 1197 emit_le(d, (u64)v, 4); 1198 if (!asm_driver_eat_comma(d)) break; 1199 } 1200 d_skip_to_eol(d); 1201 return; 1202 } 1203 if (sym_eq(d, name, "ascii") || sym_eq(d, name, "asciz") || 1204 sym_eq(d, name, "string")) { 1205 int term = !sym_eq(d, name, "ascii"); 1206 for (;;) { 1207 AsmTok t = d_peek(d); 1208 if (t.kind != ASM_TOK_STR) 1209 d_panicf(d, "asm: .ascii/.string: expected string"); 1210 (void)d_next(d); 1211 u8* buf = NULL; 1212 u32 n = 0; 1213 size_t cap = 0; 1214 decode_string(d, t.spelling, &buf, &n, &cap); 1215 (void)asm_driver_cur_section(d); 1216 mc_emit_bytes(d->mc, buf, n); 1217 if (term) emit_le(d, 0, 1); 1218 d->heap->free(d->heap, buf, cap); 1219 if (!asm_driver_eat_comma(d)) break; 1220 } 1221 d_skip_to_eol(d); 1222 return; 1223 } 1224 if (sym_eq(d, name, "zero") || sym_eq(d, name, "skip") || 1225 sym_eq(d, name, "space")) { 1226 i64 n = asm_driver_parse_const(d); 1227 i64 fill = 0; 1228 if (asm_driver_eat_comma(d)) fill = asm_driver_parse_const(d); 1229 if (n > 0) { 1230 (void)asm_driver_cur_section(d); 1231 mc_emit_fill(d->mc, (size_t)n, (u8)fill); 1232 } 1233 d_skip_to_eol(d); 1234 return; 1235 } 1236 if (sym_eq(d, name, "fill")) { 1237 i64 n = asm_driver_parse_const(d); 1238 i64 size = 1, val = 0; 1239 if (asm_driver_eat_comma(d)) size = asm_driver_parse_const(d); 1240 if (asm_driver_eat_comma(d)) val = asm_driver_parse_const(d); 1241 if (size < 1 || size > 8) d_panicf(d, "asm: .fill: size out of range"); 1242 (void)asm_driver_cur_section(d); 1243 for (i64 i = 0; i < n; ++i) emit_le(d, (u64)val, (u32)size); 1244 d_skip_to_eol(d); 1245 return; 1246 } 1247 if (sym_eq(d, name, "align") || sym_eq(d, name, "balign")) { 1248 i64 a = asm_driver_parse_const(d); 1249 i64 fill = 0; 1250 if (asm_driver_eat_comma(d)) fill = asm_driver_parse_const(d); 1251 if (a <= 0 || (a & (a - 1))) d_panicf(d, "asm: .align: not a power of 2"); 1252 (void)asm_driver_cur_section(d); 1253 mc_emit_align(d->mc, (u32)a, (u8)fill); 1254 d_skip_to_eol(d); 1255 return; 1256 } 1257 if (sym_eq(d, name, "p2align")) { 1258 i64 lg = asm_driver_parse_const(d); 1259 i64 fill = 0; 1260 if (asm_driver_eat_comma(d)) fill = asm_driver_parse_const(d); 1261 if (lg < 0 || lg > 16) d_panicf(d, "asm: .p2align: out of range"); 1262 (void)asm_driver_cur_section(d); 1263 mc_emit_align(d->mc, 1u << (u32)lg, (u8)fill); 1264 d_skip_to_eol(d); 1265 return; 1266 } 1267 if (sym_eq(d, name, "set") || sym_eq(d, name, "equ")) { 1268 Sym n = expect_ident(d, ".set"); 1269 if (!asm_driver_eat_comma(d)) d_panicf(d, "asm: .set: expected ','"); 1270 AsmExpr e = parse_expr(d); 1271 AsmEqu eq; 1272 eq.value = e.value; 1273 eq.sym = e.sym; 1274 eq.has_sym = e.sym ? 1 : 0; 1275 eq.pad[0] = eq.pad[1] = eq.pad[2] = 0; 1276 SymEquMap_set(&d->equ_map, n, eq); 1277 d_skip_to_eol(d); 1278 return; 1279 } 1280 1281 /* .comm/.lcomm NAME, SIZE[, ALIGN] — declare a common symbol. Previously 1282 * skipped, which silently produced no symbol and reserved no space. Model 1283 * both as SK_COMMON (the linker allocates .bss space); .comm is global, 1284 * .lcomm local. */ 1285 if (sym_eq(d, name, "comm") || sym_eq(d, name, "lcomm")) { 1286 int is_local = sym_eq(d, name, "lcomm"); 1287 Sym nm = expect_ident(d, ".comm"); 1288 i64 size = 0, align = 1; 1289 if (!asm_driver_eat_comma(d)) d_panicf(d, "asm: .comm: expected ','"); 1290 size = asm_driver_parse_const(d); 1291 if (asm_driver_eat_comma(d)) align = asm_driver_parse_const(d); 1292 if (size < 0) size = 0; 1293 if (align < 1) align = 1; 1294 { 1295 ObjSym* s = sym_mut(d, intern_sym(d, nm)); 1296 s->kind = (u16)SK_COMMON; 1297 s->bind = (u16)(is_local ? SB_LOCAL : SB_GLOBAL); 1298 s->size = (u64)size; 1299 s->common_align = (u64)align; 1300 } 1301 d_skip_to_eol(d); 1302 return; 1303 } 1304 /* .uleb128/.sleb128 VALUE[, VALUE...] — emit LEB128-encoded bytes. 1305 * Previously skipped, which emitted nothing and corrupted any hand-written 1306 * DWARF / exception tables that follow. */ 1307 if (sym_eq(d, name, "uleb128") || sym_eq(d, name, "sleb128")) { 1308 int sgn = sym_eq(d, name, "sleb128"); 1309 (void)asm_driver_cur_section(d); 1310 for (;;) { 1311 i64 v = asm_driver_parse_const(d); 1312 u8 buf[16]; 1313 u32 n = 0; 1314 if (sgn) { 1315 int more = 1; 1316 while (more) { 1317 u8 b = (u8)((u64)v & 0x7fu); 1318 v >>= 7; /* arithmetic right shift keeps the sign */ 1319 if ((v == 0 && !(b & 0x40u)) || (v == -1 && (b & 0x40u))) 1320 more = 0; 1321 else 1322 b |= 0x80u; 1323 buf[n++] = b; 1324 } 1325 } else { 1326 u64 uv = (u64)v; 1327 do { 1328 u8 b = (u8)(uv & 0x7fu); 1329 uv >>= 7; 1330 if (uv) b |= 0x80u; 1331 buf[n++] = b; 1332 } while (uv); 1333 } 1334 mc_emit_bytes(d->mc, buf, n); 1335 if (!asm_driver_eat_comma(d)) break; 1336 } 1337 d_skip_to_eol(d); 1338 return; 1339 } 1340 1341 /* CFI block + accepted-but-ignored directives. Keep parser 1342 * forward-progress without aborting the whole TU. */ 1343 if (starts_with(d, name, "cfi_") || sym_eq(d, name, "file") || 1344 sym_eq(d, name, "loc") || sym_eq(d, name, "ident") || 1345 sym_eq(d, name, "popsection") || sym_eq(d, name, "pushsection") || 1346 sym_eq(d, name, "previous") || 1347 sym_eq(d, name, "subsections_via_symbols") || sym_eq(d, name, "macro") || 1348 sym_eq(d, name, "endm") || sym_eq(d, name, "if") || 1349 sym_eq(d, name, "endif") || sym_eq(d, name, "else") || 1350 sym_eq(d, name, "include") || 1351 /* RISC-V `.option rvc/norvc/relax/norelax/push/pop/...`: kit's own 1352 * cc -S emits `.option norvc`/`.option norelax` to pin its fixed 1353 * instruction layout (see rv64_file_prologue). kit-as never compresses 1354 * or relaxes, so it already honors these implicitly — accept and ignore 1355 * rather than treat as an unknown directive. */ 1356 sym_eq(d, name, "option")) { 1357 d_skip_to_eol(d); 1358 return; 1359 } 1360 1361 /* Unknown directive — recover. */ 1362 d_skip_to_eol(d); 1363 } 1364 1365 /* ---- deferred symbol-difference resolution ---- 1366 * 1367 * `.quad sym1 - sym2` (and the narrower data widths) is recorded at emit time 1368 * and lowered here, once the parse has defined every label. Three outcomes, 1369 * matching GNU as: 1370 * 1371 * (a) Both symbols defined in one section — the difference is a pure 1372 * assembly-time constant (the section base cancels). Fold it into the 1373 * field; no relocation. Works for any width. 1374 * (b) The subtrahend is defined in the fixup's own section (the minuend may 1375 * be anywhere, including undefined / linker-script-provided like `_end`). 1376 * Reduce to a PC-relative relocation against the minuend: at the fixup 1377 * site P, `M + A' - P` with `A' = addend + offset - sym2.value` equals 1378 * `M - sym2` because `P - offset` is the section base and 1379 * `base + sym2.value == sym2`. This is the arm64/riscv kernel 1380 * `image_size = _end - _start` case. Needs .long/.quad. 1381 * (c) Otherwise (cross-section, or an undefined subtrahend) — an R_ADD/R_SUB 1382 * relocation pair the linker applies additively to the field (which holds 1383 * the net addend): `field += minuend; field -= subtrahend`. Emitted 1384 * adjacently so the Mach-O writer fuses them into a SUBTRACTOR pair and 1385 * RISC-V ELF maps each to the R_RISCV_ADD / R_RISCV_SUB families. */ 1386 static void patch_le(AsmDriver* d, ObjSecId sec, u32 ofs, u64 v, u32 width) { 1387 u8 buf[8]; 1388 for (u32 i = 0; i < width; ++i) buf[i] = (u8)(v >> (8 * i)); 1389 obj_patch(d->ob, sec, ofs, buf, width); 1390 } 1391 1392 static void resolve_sym_diffs(AsmDriver* d) { 1393 for (u32 i = 0; i < d->ndiffs; ++i) { 1394 AsmDiff* df = &d->diffs[i]; 1395 const ObjSym* m = obj_symbol_get(d->ob, df->minuend); 1396 const ObjSym* s = obj_symbol_get(d->ob, df->subtrahend); 1397 int m_def = m && m->section_id != OBJ_SEC_NONE; 1398 int s_def = s && s->section_id != OBJ_SEC_NONE; 1399 1400 /* (a) constant fold */ 1401 if (m_def && s_def && m->section_id == s->section_id) { 1402 i64 c = (i64)m->value - (i64)s->value + df->addend; 1403 patch_le(d, df->section, df->offset, (u64)c, df->width); 1404 continue; 1405 } 1406 1407 /* (b) subtrahend in the fixup's section -> PC-relative reloc on minuend */ 1408 if (s_def && s->section_id == df->section) { 1409 RelocKind k; 1410 if (df->width == 8) 1411 k = R_PC64; 1412 else if (df->width == 4) 1413 k = R_PC32; 1414 else 1415 compiler_panic(d->c, df->loc, 1416 "asm: PC-relative symbol difference needs .long/.quad"); 1417 i64 ad = df->addend + (i64)df->offset - (i64)s->value; 1418 patch_le(d, df->section, df->offset, (u64)ad, df->width); 1419 mc_emit_reloc_at(d->mc, df->section, df->offset, k, df->minuend, ad, 1, 0); 1420 continue; 1421 } 1422 1423 /* (c) general difference -> R_ADD/R_SUB pair */ 1424 { 1425 RelocKind ka, ks; 1426 switch (df->width) { 1427 case 1: 1428 ka = R_ADD8; 1429 ks = R_SUB8; 1430 break; 1431 case 2: 1432 ka = R_ADD16; 1433 ks = R_SUB16; 1434 break; 1435 case 4: 1436 ka = R_ADD32; 1437 ks = R_SUB32; 1438 break; 1439 case 8: 1440 ka = R_ADD64; 1441 ks = R_SUB64; 1442 break; 1443 default: 1444 compiler_panic(d->c, df->loc, "asm: bad symbol-difference width %u", 1445 (unsigned)df->width); 1446 } 1447 mc_emit_reloc_at(d->mc, df->section, df->offset, ka, df->minuend, 0, 1, 0); 1448 mc_emit_reloc_at(d->mc, df->section, df->offset, ks, df->subtrahend, 0, 1, 1449 0); 1450 } 1451 } 1452 } 1453 1454 /* ---- same-section branch relaxation ---- 1455 * 1456 * The per-arch parser emits a relocation for every symbolic branch target, 1457 * even one that resolves within the current section (a forward reference like 1458 * `b .Lfoo` is only known to be local once `.Lfoo:` is seen). GNU as / llvm-mc 1459 * — and kit's own codegen — instead resolve such intra-section branches at 1460 * assembly time: compute the displacement, patch the instruction, and emit no 1461 * relocation. Matching that is what makes `cc -S | as` reproduce `cc -c`'s 1462 * .text relocation table for control-flow-bearing code (the L1 round-trip 1463 * lane; see doc/TESTING.md). 1464 * 1465 * We relax only PC-relative *branch* relocations (never CALL26 — a call keeps 1466 * its relocation on both sides) whose target is a symbol defined in the same 1467 * section, with local binding, and not a function entry. The "local" guard 1468 * matches GNU as (a global symbol may be interposed, so its branch keeps the 1469 * relocation); the "not a function" guard matches kit codegen, which keeps 1470 * the relocation for an intra-file call/tail-call to a function symbol while 1471 * resolving branches to internal labels. 1472 * 1473 * Restricted to the AArch64 branch kinds for now — the round-trip vertical 1474 * slice is aa64; rv64/x64 keep their current behavior. */ 1475 static int is_relaxable_branch_kind(u16 kind) { 1476 switch (kind) { 1477 case R_AARCH64_JUMP26: 1478 case R_AARCH64_CONDBR19: 1479 case R_AARCH64_TSTBR14: 1480 case R_AARCH64_ADR_PREL_LO21: /* adr to a local code label (&&label) */ 1481 return 1; 1482 default: 1483 return 0; 1484 } 1485 } 1486 1487 static void relax_local_branches(AsmDriver* d) { 1488 u32 total = obj_reloc_total(d->ob), i; 1489 for (i = 0; i < total; ++i) { 1490 const Reloc* r = obj_reloc_at(d->ob, i); 1491 const ObjSym* tgt; 1492 const Section* sec; 1493 u8 insn[4]; 1494 if (!r || r->removed) continue; 1495 if (!is_relaxable_branch_kind(r->kind)) continue; 1496 tgt = obj_symbol_get(d->ob, r->sym); 1497 if (!tgt) continue; 1498 if (tgt->section_id != r->section_id) continue; /* cross-section / undef */ 1499 if (tgt->bind != SB_LOCAL) continue; /* preemptible; keep */ 1500 if (tgt->kind == SK_FUNC) continue; /* call/tail-call; keep */ 1501 /* Read-only access to the in-progress section is fine via the const 1502 * accessor; the byte write goes back through obj_patch so we don't reach 1503 * into Section internals. */ 1504 sec = obj_section_get(d->ob, r->section_id); 1505 if (!sec) continue; 1506 if ((u64)r->offset + 4 > sec->bytes.total) continue; 1507 buf_read(&sec->bytes, r->offset, insn, 4); 1508 /* Section-relative S and P make the base cancel: disp = S + A - P. */ 1509 link_reloc_apply(d->c, (RelocKind)r->kind, insn, tgt->value, r->addend, 1510 r->offset); 1511 obj_patch(d->ob, r->section_id, r->offset, insn, 4); 1512 /* No public per-reloc tombstone setter exists on ObjBuilder; the const 1513 * cast mutates the still-private in-progress object we own outright (we 1514 * built every reloc in this builder). Tolerated until obj exposes an 1515 * obj_reloc_remove() mutator alongside obj_section/symbol_remove. */ 1516 ((Reloc*)r)->removed = 1; 1517 } 1518 } 1519 1520 /* ---- driver loop ---- */ 1521 1522 static void process_label(AsmDriver* d, Sym name) { 1523 ObjSymId id = intern_sym(d, name); 1524 int is_thumb_func = 0; 1525 (void)asm_driver_cur_section(d); 1526 const ObjSym* os = obj_symbol_get(d->ob, id); 1527 if (os && os->section_id != OBJ_SEC_NONE) 1528 d_panicf(d, "asm: symbol defined twice"); 1529 if (asm_thumb_func_syms(d)) { 1530 is_thumb_func = d->pending_thumb_func || asm_sym_is_thumb_func(d, name); 1531 d->pending_thumb_func = 0; 1532 } 1533 obj_symbol_define(d->ob, id, d->cur_sec, 1534 (u64)mc_pos(d->mc) | (is_thumb_func ? 1u : 0u), 0); 1535 /* Promote SK_UNDEF (forward ref via reloc) to SK_NOTYPE so it's a 1536 * real defined symbol; explicit `.type SYM, @function` will refine. */ 1537 if (is_thumb_func) 1538 sym_mut(d, id)->kind = (u16)SK_FUNC; 1539 else if (os && os->kind == SK_UNDEF) 1540 sym_mut(d, id)->kind = (u16)SK_NOTYPE; 1541 } 1542 1543 static Sym maybe_compose_mnemonic(AsmDriver* d, Sym head) { 1544 /* Loops to accept multi-dot mnemonics like RISC-V's `fcvt.w.s` / 1545 * `amoadd.d` — peel one `.ident` per pass, intern the joined token, 1546 * and stop when the next token isn't a touching dot. */ 1547 for (;;) { 1548 AsmTok t = d_peek(d); 1549 if (!tok_is_punct(t, '.')) return head; 1550 if (t.flags & ASM_TF_HAS_SPACE) return head; 1551 (void)d_next(d); 1552 AsmTok rest = d_next(d); 1553 if (rest.kind != ASM_TOK_IDENT) 1554 d_panicf(d, "asm: composite mnemonic: expected ident"); 1555 size_t hn = 0, rn = 0; 1556 const char* hp = asm_str(d, head, &hn); 1557 const char* rp = asm_str(d, rest.v.ident, &rn); 1558 size_t n = hn + 1 + rn; 1559 if (n >= 64) d_panicf(d, "asm: mnemonic too long"); 1560 char buf[64]; 1561 for (size_t i = 0; i < hn; ++i) buf[i] = hp[i]; 1562 buf[hn] = '.'; 1563 for (size_t i = 0; i < rn; ++i) buf[hn + 1 + i] = rp[i]; 1564 head = pool_intern_slice(d->pool, (Slice){.s = buf, .len = n}); 1565 } 1566 } 1567 1568 /* ---- inline-asm driver constructor ---- 1569 * 1570 * Inline-asm template walkers (per-arch) re-lex pre-substituted source 1571 * text through the same per-mnemonic parsers used by the standalone .s 1572 * driver. This constructor builds a minimally-initialized AsmDriver 1573 * around a caller-supplied memory-backed AsmLexer + MCEmitter. 1574 * 1575 * The driver does not own the AsmLexer or MCEmitter, does not allocate a 1576 * default section (inline asm emits into whatever section the wrapping 1577 * cg has selected on its MCEmitter), and skips the standalone driver's 1578 * per-arch handle (`d->arch_asm`) — the caller has already opened its own 1579 * arch asm handle to thread per-block bound state through. */ 1580 AsmDriver* asm_driver_open_inline(Compiler* c, MCEmitter* mc, AsmLexer* lex) { 1581 Heap* heap = (Heap*)c->ctx->heap; 1582 AsmDriver* d = (AsmDriver*)heap->alloc(heap, sizeof *d, _Alignof(AsmDriver)); 1583 memset(d, 0, sizeof *d); 1584 d->c = c; 1585 d->lex = lex; 1586 d->mc = mc; 1587 d->ob = mc->obj; 1588 d->pool = c->global; 1589 d->heap = heap; 1590 /* The MCEmitter's section is whatever cg has set; do not override it. 1591 * cur_sec == OBJ_SEC_NONE means "ask the MCEmitter on demand" — we use 1592 * mc->section_id directly via asm_driver_cur_section's lazy init for 1593 * standalone, but inline asm should never reach that path because the 1594 * MCEmitter already has its section. Pre-seed cur_sec from the 1595 * MCEmitter so emit_reloc_at calls get the right section id. */ 1596 d->cur_sec = mc->section_id; 1597 SymSecMap_init(&d->sec_map, heap); 1598 SymSymMap_init(&d->sym_map, heap); 1599 SymU8Map_init(&d->thumb_func_map, heap); 1600 SymEquMap_init(&d->equ_map, heap); 1601 d->arch_asm = NULL; /* caller owns its own arch asm handle */ 1602 return d; 1603 } 1604 1605 void asm_driver_close_inline(AsmDriver* d) { 1606 if (!d) return; 1607 SymSecMap_fini(&d->sec_map); 1608 SymSymMap_fini(&d->sym_map); 1609 SymU8Map_fini(&d->thumb_func_map); 1610 SymEquMap_fini(&d->equ_map); 1611 if (d->diffs) d->heap->free(d->heap, d->diffs, sizeof(AsmDiff) * d->diffs_cap); 1612 Heap* heap = d->heap; 1613 heap->free(heap, d, sizeof *d); 1614 } 1615 1616 void asm_parse(Compiler* c, AsmLexer* l, MCEmitter* mc) { 1617 AsmDriver d; 1618 memset(&d, 0, sizeof d); 1619 d.c = c; 1620 d.lex = l; 1621 d.mc = mc; 1622 d.ob = mc->obj; 1623 d.pool = c->global; 1624 d.heap = (Heap*)c->ctx->heap; 1625 d.cur_sec = OBJ_SEC_NONE; 1626 SymSecMap_init(&d.sec_map, d.heap); 1627 SymSymMap_init(&d.sym_map, d.heap); 1628 SymU8Map_init(&d.thumb_func_map, d.heap); 1629 SymEquMap_init(&d.equ_map, d.heap); 1630 { 1631 const ArchImpl* arch = arch_for_compiler(c); 1632 if (!arch || !arch->asm_new) { 1633 SrcLoc loc = asm_lex_loc(l); 1634 compiler_panic(c, loc, "asm_parse: unsupported target arch %d", 1635 (int)c->target.arch); 1636 } 1637 d.arch_asm = arch->asm_new(c); 1638 } 1639 1640 for (;;) { 1641 AsmTok t = d_peek(&d); 1642 if (t.kind == ASM_TOK_EOF) break; 1643 if (t.kind == ASM_TOK_NEWLINE) { 1644 (void)d_next(&d); 1645 continue; 1646 } 1647 if (t.kind == ASM_TOK_HASH) { 1648 /* cpp-style linemarker; skip the whole line. */ 1649 d_skip_to_eol(&d); 1650 continue; 1651 } 1652 if (tok_is_punct(t, '.')) { 1653 (void)d_next(&d); 1654 AsmTok id = d_next(&d); 1655 if (id.kind != ASM_TOK_IDENT) 1656 d_panicf(&d, "asm: expected directive name after '.'"); 1657 do_directive(&d, id.v.ident); 1658 d_eat_eol(&d); 1659 continue; 1660 } 1661 if (t.kind == ASM_TOK_IDENT) { 1662 Sym head = t.v.ident; 1663 (void)d_next(&d); 1664 AsmTok nxt = d_peek(&d); 1665 if (tok_is_punct(nxt, ':')) { 1666 (void)d_next(&d); 1667 process_label(&d, head); 1668 continue; 1669 } 1670 Sym mnemonic = maybe_compose_mnemonic(&d, head); 1671 d.arch_asm->insn(d.arch_asm, &d, mnemonic); 1672 d_skip_to_eol(&d); 1673 continue; 1674 } 1675 /* Anything else: recover by skipping the line. */ 1676 d_skip_to_eol(&d); 1677 } 1678 1679 resolve_sym_diffs(&d); 1680 promote_undef_externs(&d); 1681 relax_local_branches(&d); 1682 1683 if (d.arch_asm && d.arch_asm->destroy) d.arch_asm->destroy(d.arch_asm); 1684 SymSecMap_fini(&d.sec_map); 1685 SymSymMap_fini(&d.sym_map); 1686 SymU8Map_fini(&d.thumb_func_map); 1687 SymEquMap_fini(&d.equ_map); 1688 if (d.diffs) d.heap->free(d.heap, d.diffs, sizeof(AsmDiff) * d.diffs_cap); 1689 }