kit

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

obj.c (54992B)


      1 /* In-memory ObjBuilder. Section, symbol, group, and reloc storage all
      2  * use segmented arrays (core/segvec.h) so the T* pointers obj_*_get
      3  * returns stay valid as the table grows. Section bytes use the chunked
      4  * Buf type. Index 0 of each id space is reserved as "none".
      5  *
      6  * obj_finalize is the read-side gate: post-finalize, write-side calls
      7  * are still legal (the reader paths use them too) but consumers can
      8  * count on the index spaces being stable. */
      9 
     10 #include "obj/obj.h"
     11 
     12 #include <string.h>
     13 
     14 #include "core/hashmap.h"
     15 #include "core/heap.h"
     16 #include "core/pool.h"
     17 #include "core/segvec.h"
     18 #include "core/vec.h"
     19 
     20 SEGVEC_DEFINE(Sections, Section, 5); /* 32 entries per segment */
     21 SEGVEC_DEFINE(Symbols, ObjSym, 6);   /* 64 entries per segment */
     22 
     23 /* name (interned Sym) -> first defining ObjSymId. The authoritative index for
     24  * obj_symbol_find: the whole-program LTO builder holds every TU's symbols in
     25  * one builder, so the historical linear scan is O(n^2) at decl time. The index
     26  * stores the first id seen for a name (matching the scan's "first match"
     27  * semantics), and obj_symbol_find is a pure O(1) hash lookup with no fallback.
     28  * obj_symbol_rename keeps the index exact (re-homing or dropping a renamed
     29  * symbol's entry), so the lookup never sees a stale hit. */
     30 HASHMAP_DEFINE(SymNameIndex, Sym, ObjSymId, hash_u32);
     31 
     32 /* (name, kind) -> first PROGBITS ObjSecId. obj_section is a find-or-create that
     33  * collapses repeated requests for the same logical section (e.g. one .rodata
     34  * per literal) onto a single Section; without this index that find is a linear
     35  * scan of every section, which is O(n^2) once a build emits many distinct
     36  * sections (e.g. -ffunction-sections). The key packs (name, kind) and is always
     37  * odd so it never collides with the hashmap's 0 empty-slot sentinel. */
     38 HASHMAP_DEFINE(SecKeyIndex, u64, ObjSecId, hash_u64);
     39 SEGVEC_DEFINE(Relocs, Reloc, 6);    /* 64 entries per segment */
     40 SEGVEC_DEFINE(Groups, ObjGroup, 3); /*  8 entries per segment */
     41 SEGVEC_DEFINE(Atoms, ObjAtom, 5);   /* 32 entries per segment */
     42 
     43 /* COFF WEAK_EXTERNAL alias declaration: symbol `sym` is an alias for the
     44  * symbol named `target`. Rare (only import-archive members and the like
     45  * carry these), so a side-vector keyed by ObjSymId keeps ObjSym lean
     46  * instead of growing every symbol. See obj_set_weak_alias. */
     47 typedef struct ObjWeakAlias {
     48   ObjSymId sym;
     49   Sym target;
     50 } ObjWeakAlias;
     51 SEGVEC_DEFINE(WeakAliases, ObjWeakAlias, 3); /* 8 entries per segment */
     52 
     53 #define OBJ_EXT_SLOT_COUNT 6 /* OBJ_EXT_NONE..OBJ_EXT_WASM_IMPORTS */
     54 
     55 typedef struct ObjExtSlot {
     56   void* payload;
     57   ObjExtFreeFn free_fn;
     58 } ObjExtSlot;
     59 
     60 struct KitObjBuilder {
     61   Compiler* c;
     62   Heap* heap;
     63   Sections sections;        /* index 0 reserved as "none" */
     64   Symbols symbols;          /* index 0 reserved as "none" */
     65   Relocs relocs;            /* flat across all sections; filtered on read */
     66   Groups groups;            /* index 0 reserved as "none" */
     67   Atoms atoms;              /* index 0 reserved as "none" */
     68   SymNameIndex sym_by_name; /* name -> first ObjSymId; accelerates find */
     69   SecKeyIndex sec_by_key;   /* (name,kind) -> first PROGBITS ObjSecId */
     70   /* Format-specific ELF e_flags. Set by read_elf to the input's
     71    * e_flags (e.g. on RISC-V, EF_RISCV_RVC | EF_RISCV_FLOAT_ABI_DOUBLE);
     72    * consumed by emit_elf to round-trip. Zero when not set — emit_elf
     73    * derives a sensible default by arch. */
     74   u32 elf_e_flags;
     75   u8 elf_e_flags_set;
     76   /* COFF short-import annotation. Carries the providing DLL name when
     77    * the builder was synthesized by read_coff from a Microsoft short
     78    * import record; zero / unset otherwise. See obj_set_coff_import_dll. */
     79   Sym coff_import_dll;
     80   u8 coff_import_dll_set;
     81   /* COFF short-import IMPORT NAME override. The Microsoft short-import
     82    * NameType field can make the name the loader resolves in the DLL differ
     83    * from the local symbol name (NOPREFIX/UNDECORATE strip decoration;
     84    * EXPORTAS stores an explicit export name). Carries that resolved import
     85    * name when it differs from the symbol name; zero / unset otherwise. The
     86    * local symbol keeps its own name so references still resolve; only the PE
     87    * hint/name-table entry uses this. See obj_set_coff_import_name. */
     88   Sym coff_import_name;
     89   u8 coff_import_name_set;
     90   /* COFF WEAK_EXTERNAL alias declarations read from the input (symbol ->
     91    * fallback/default symbol name). Empty on builders that carry none. See
     92    * obj_set_weak_alias / obj_get_weak_alias. */
     93   WeakAliases weak_aliases;
     94   /* Cached undef extern `__tlv_bootstrap` (Mach-O on-disk name) used by
     95    * obj_define_tls when emitting `_Thread_local` storage on Mach-O.
     96    * Lazily materialized on the first TLV emission; OBJ_SYM_NONE otherwise. */
     97   ObjSymId tlv_bootstrap_sym;
     98   /* Format-specific extension payloads keyed by ObjExtKind. */
     99   ObjExtSlot ext[OBJ_EXT_SLOT_COUNT];
    100   /* Linked-image view (segments + dynamic info). NULL on relocatable
    101    * inputs; lazily created by obj_image_ensure. See obj.h. */
    102   ObjImage* image;
    103   /* Per-section reloc index (lazy cache). Without it, every native emitter
    104    * rescans the whole flat reloc table once per section — O(n_sections *
    105    * n_relocs), a latent superlinear axis that grows with -ffunction-sections
    106    * and literal fan-out. `reloc_index` holds the global indices of live
    107    * relocs grouped by section in ascending global order; `reloc_index_off`
    108    * is the prefix-offset array (len reloc_index_nsec+1) so section `s`'s run
    109    * is reloc_index[off[s] .. off[s+1]). Rebuilt by obj_reloc_index_ensure
    110    * when `reloc_index_dirty` (set on reloc add and on obj_sweep_dead). */
    111   u32* reloc_index;
    112   u32* reloc_index_off;
    113   u32 reloc_index_nsec; /* #sections reloc_index_off was sized for */
    114   u32 reloc_index_len;  /* #live relocs in reloc_index */
    115   u8 reloc_index_dirty;
    116 };
    117 
    118 struct ObjSymIter {
    119   const ObjBuilder* ob;
    120   u32 idx; /* next index to return */
    121 };
    122 
    123 static void obj_image_free_(ObjBuilder*);
    124 
    125 /* ---- lifecycle ---- */
    126 
    127 ObjBuilder* obj_new(Compiler* c) {
    128   Heap* h = (Heap*)c->ctx->heap;
    129   ObjBuilder* ob = (ObjBuilder*)h->alloc(h, sizeof(*ob), _Alignof(ObjBuilder));
    130   if (!ob) return NULL;
    131   memset(ob, 0, sizeof(*ob));
    132   ob->c = c;
    133   ob->heap = h;
    134   Sections_init(&ob->sections, h);
    135   Symbols_init(&ob->symbols, h);
    136   Relocs_init(&ob->relocs, h);
    137   Groups_init(&ob->groups, h);
    138   Atoms_init(&ob->atoms, h);
    139   /* Pre-size the symbol-name index: a typical TU has hundreds of symbols, and
    140    * the default 16-slot map would otherwise resize/rehash ~4 times (16->256)
    141    * while declarations stream in. 256 (a power of two, holds 192 at the 3/4
    142    * load factor) skips that early cascade. Order-preserving — resize reinserts
    143    * and the first-wins set at obj_symbol_make is unchanged. */
    144   SymNameIndex_init_cap(&ob->sym_by_name, h, 256u);
    145   SecKeyIndex_init(&ob->sec_by_key, h);
    146   WeakAliases_init(&ob->weak_aliases, h);
    147 
    148   /* Reserve index 0 in each id space as the "none" sentinel. SegVec
    149    * pushes are zeroed, so the sentinel slots have all-zero fields. */
    150   if (!Sections_push(&ob->sections, NULL) ||
    151       !Symbols_push(&ob->symbols, NULL) || !Groups_push(&ob->groups, NULL) ||
    152       !Atoms_push(&ob->atoms, NULL)) {
    153     obj_free(ob);
    154     return NULL;
    155   }
    156   return ob;
    157 }
    158 
    159 Compiler* obj_compiler(const ObjBuilder* ob) { return ob ? ob->c : NULL; }
    160 
    161 /* Pre-size the symbol-name index for `n` incoming symbols. Object readers know
    162  * the symbol count up front (the symtab header), so calling this before
    163  * streaming symbols in skips the resize cascade off obj_new's 256-slot default
    164  * on a symbol-heavy object (e.g. a large .o the linker ingests). No-op once the
    165  * index is already large enough; order-preserving (resize reinserts). */
    166 void obj_reserve_symbols(ObjBuilder* ob, u32 n) {
    167   if (ob) SymNameIndex_reserve(&ob->sym_by_name, n);
    168 }
    169 
    170 /* Private accessors for the `_tlv_bootstrap` cache used by obj_define_tls.
    171  * Lives in obj.c so the ObjBuilder field doesn't leak through obj.h. */
    172 ObjSymId obj_tlv_bootstrap_get(const ObjBuilder* ob) {
    173   return ob ? ob->tlv_bootstrap_sym : OBJ_SYM_NONE;
    174 }
    175 void obj_tlv_bootstrap_set(ObjBuilder* ob, ObjSymId id) {
    176   if (ob) ob->tlv_bootstrap_sym = id;
    177 }
    178 
    179 void obj_free(ObjBuilder* ob) {
    180   u32 i, n;
    181   if (!ob) return;
    182   for (i = 0; i < OBJ_EXT_SLOT_COUNT; ++i) {
    183     if (ob->ext[i].payload && ob->ext[i].free_fn) {
    184       ob->ext[i].free_fn(ob->c, ob->ext[i].payload);
    185     }
    186     ob->ext[i].payload = NULL;
    187     ob->ext[i].free_fn = NULL;
    188   }
    189   n = Sections_count(&ob->sections);
    190   for (i = 1; i < n; ++i) {
    191     Section* s = Sections_at(&ob->sections, i);
    192     if (s) buf_fini(&s->bytes);
    193   }
    194   n = Groups_count(&ob->groups);
    195   for (i = 1; i < n; ++i) {
    196     ObjGroup* g = Groups_at(&ob->groups, i);
    197     if (g && g->sections) {
    198       ob->heap->free(ob->heap, g->sections, sizeof(ObjSecId) * g->nsections);
    199     }
    200   }
    201   Sections_fini(&ob->sections);
    202   Symbols_fini(&ob->symbols);
    203   Relocs_fini(&ob->relocs);
    204   Groups_fini(&ob->groups);
    205   Atoms_fini(&ob->atoms);
    206   SymNameIndex_fini(&ob->sym_by_name);
    207   SecKeyIndex_fini(&ob->sec_by_key);
    208   WeakAliases_fini(&ob->weak_aliases);
    209   obj_image_free_(ob);
    210   if (ob->reloc_index)
    211     ob->heap->free(ob->heap, ob->reloc_index,
    212                    sizeof(u32) * ob->reloc_index_len);
    213   if (ob->reloc_index_off)
    214     ob->heap->free(ob->heap, ob->reloc_index_off,
    215                    sizeof(u32) * (ob->reloc_index_nsec + 1u));
    216   ob->heap->free(ob->heap, ob, sizeof(*ob));
    217 }
    218 
    219 void obj_set_elf_e_flags(ObjBuilder* ob, u32 e_flags) {
    220   if (!ob) return;
    221   ob->elf_e_flags = e_flags;
    222   ob->elf_e_flags_set = 1;
    223 }
    224 
    225 int obj_get_elf_e_flags(const ObjBuilder* ob, u32* out) {
    226   if (!ob || !ob->elf_e_flags_set) return 0;
    227   if (out) *out = ob->elf_e_flags;
    228   return 1;
    229 }
    230 
    231 void obj_set_coff_import_dll(ObjBuilder* ob, Sym dll_name) {
    232   if (!ob) return;
    233   ob->coff_import_dll = dll_name;
    234   ob->coff_import_dll_set = 1;
    235 }
    236 
    237 int obj_get_coff_import_dll(const ObjBuilder* ob, Sym* out) {
    238   if (!ob || !ob->coff_import_dll_set) return 0;
    239   if (out) *out = ob->coff_import_dll;
    240   return 1;
    241 }
    242 
    243 void obj_set_coff_import_name(ObjBuilder* ob, Sym import_name) {
    244   if (!ob) return;
    245   ob->coff_import_name = import_name;
    246   ob->coff_import_name_set = 1;
    247 }
    248 
    249 int obj_get_coff_import_name(const ObjBuilder* ob, Sym* out) {
    250   if (!ob || !ob->coff_import_name_set) return 0;
    251   if (out) *out = ob->coff_import_name;
    252   return 1;
    253 }
    254 
    255 void obj_set_weak_alias(ObjBuilder* ob, ObjSymId sym, Sym target) {
    256   if (!ob || sym == OBJ_SYM_NONE || target == 0) return;
    257   /* Overwrite an existing entry for this sym rather than duplicating. */
    258   u32 n = WeakAliases_count(&ob->weak_aliases);
    259   for (u32 i = 0; i < n; ++i) {
    260     ObjWeakAlias* a = WeakAliases_at(&ob->weak_aliases, i);
    261     if (a->sym == sym) {
    262       a->target = target;
    263       return;
    264     }
    265   }
    266   ObjWeakAlias* slot = WeakAliases_push(&ob->weak_aliases, NULL);
    267   if (!slot) return; /* OOM: alias recovery falls back to the name heuristic */
    268   slot->sym = sym;
    269   slot->target = target;
    270 }
    271 
    272 Sym obj_get_weak_alias(const ObjBuilder* ob, ObjSymId sym) {
    273   if (!ob || sym == OBJ_SYM_NONE) return 0;
    274   u32 n = WeakAliases_count(&ob->weak_aliases);
    275   for (u32 i = 0; i < n; ++i) {
    276     const ObjWeakAlias* a = WeakAliases_at(&ob->weak_aliases, i);
    277     if (a->sym == sym) return a->target;
    278   }
    279   return 0;
    280 }
    281 
    282 u32 obj_weak_alias_count(const ObjBuilder* ob) {
    283   return ob ? WeakAliases_count(&ob->weak_aliases) : 0;
    284 }
    285 
    286 int obj_weak_alias_at(const ObjBuilder* ob, u32 i, ObjSymId* sym_out,
    287                       Sym* target_out) {
    288   if (!ob || i >= WeakAliases_count(&ob->weak_aliases)) return 0;
    289   const ObjWeakAlias* a = WeakAliases_at(&ob->weak_aliases, i);
    290   if (sym_out) *sym_out = a->sym;
    291   if (target_out) *target_out = a->target;
    292   return 1;
    293 }
    294 
    295 /* ---- linked-image view ---- */
    296 
    297 struct ObjImage {
    298   Heap* heap;
    299   ObjKind kind;
    300   u64 entry;
    301   u64 image_base;
    302   Sym interp;
    303   Sym soname;
    304   ObjSegment* segs;
    305   u32 nsegs, cap_segs;
    306   ObjImageDep* deps;
    307   u32 ndeps, cap_deps;
    308   Sym* rpaths;
    309   u32 nrpaths, cap_rpaths;
    310   ObjImageSym* dynsyms;
    311   u32 ndynsyms, cap_dynsyms;
    312   ObjImageReloc* dynrelocs;
    313   u32 ndynrelocs, cap_dynrelocs;
    314   ObjImageRaw* raws;
    315   u32 nraws, cap_raws;
    316   /* Undefined symbol names a DSO references (interned). Used by the linker's
    317    * --gc-sections pass to keep executable-defined symbols a shared library
    318    * needs (e.g. libc.so.7's `environ` / `__progname`) from being collected. */
    319   Sym* undefs;
    320   u32 nundefs, cap_undefs;
    321 };
    322 
    323 static void obj_image_free_(ObjBuilder* ob) {
    324   ObjImage* im;
    325   if (!ob || !ob->image) return;
    326   im = ob->image;
    327   /* The image owns each dep's imports[] array container (allocated from
    328    * im->heap by the PE reader); the Sym values inside stay interned in the
    329    * global pool and are not freed here. ELF/Mach-O deps carry imports==NULL. */
    330   if (im->deps) {
    331     for (u32 i = 0; i < im->ndeps; ++i) {
    332       const ObjImageDep* d = &im->deps[i];
    333       if (d->imports)
    334         im->heap->free(im->heap, (void*)d->imports,
    335                        sizeof(*d->imports) * d->nimports);
    336     }
    337   }
    338   if (im->segs)
    339     im->heap->free(im->heap, im->segs, sizeof(*im->segs) * im->cap_segs);
    340   if (im->deps)
    341     im->heap->free(im->heap, im->deps, sizeof(*im->deps) * im->cap_deps);
    342   if (im->rpaths)
    343     im->heap->free(im->heap, im->rpaths, sizeof(*im->rpaths) * im->cap_rpaths);
    344   if (im->dynsyms)
    345     im->heap->free(im->heap, im->dynsyms,
    346                    sizeof(*im->dynsyms) * im->cap_dynsyms);
    347   if (im->dynrelocs)
    348     im->heap->free(im->heap, im->dynrelocs,
    349                    sizeof(*im->dynrelocs) * im->cap_dynrelocs);
    350   if (im->raws)
    351     im->heap->free(im->heap, im->raws, sizeof(*im->raws) * im->cap_raws);
    352   if (im->undefs)
    353     im->heap->free(im->heap, im->undefs, sizeof(*im->undefs) * im->cap_undefs);
    354   ob->heap->free(ob->heap, im, sizeof(*im));
    355   ob->image = NULL;
    356 }
    357 
    358 const ObjImage* obj_image(const ObjBuilder* ob) {
    359   return ob ? ob->image : NULL;
    360 }
    361 
    362 ObjImage* obj_image_ensure(ObjBuilder* ob, ObjKind kind) {
    363   ObjImage* im;
    364   if (!ob) return NULL;
    365   if (ob->image) {
    366     ob->image->kind = kind;
    367     return ob->image;
    368   }
    369   im = (ObjImage*)ob->heap->alloc(ob->heap, sizeof(*im), _Alignof(ObjImage));
    370   if (!im) return NULL;
    371   memset(im, 0, sizeof(*im));
    372   im->heap = ob->heap;
    373   im->kind = kind;
    374   ob->image = im;
    375   return im;
    376 }
    377 
    378 void obj_image_set_entry(ObjImage* im, u64 entry) {
    379   if (im) im->entry = entry;
    380 }
    381 void obj_image_set_base(ObjImage* im, u64 image_base) {
    382   if (im) im->image_base = image_base;
    383 }
    384 void obj_image_set_interp(ObjImage* im, Sym interp) {
    385   if (im) im->interp = interp;
    386 }
    387 void obj_image_set_soname(ObjImage* im, Sym soname) {
    388   if (im) im->soname = soname;
    389 }
    390 
    391 void obj_image_add_segment(ObjImage* im, const ObjSegment* seg) {
    392   if (!im || !seg) return;
    393   if (VEC_GROW(im->heap, im->segs, im->cap_segs, im->nsegs + 1)) return;
    394   im->segs[im->nsegs++] = *seg;
    395 }
    396 void obj_image_add_dep(ObjImage* im, const ObjImageDep* dep) {
    397   ObjImageDep d;
    398   if (!im || !dep) return;
    399   if (VEC_GROW(im->heap, im->deps, im->cap_deps, im->ndeps + 1)) return;
    400   d = *dep;
    401   /* Deep-copy the imports[] name array into image-heap-owned memory so the
    402    * reader may pass a transient (scratch/arena) array; obj_image_free_
    403    * releases this copy. The Sym values inside are global-interned and not
    404    * owned here. ELF/Mach-O deps carry imports==NULL (nimports==0). */
    405   if (d.nimports && dep->imports) {
    406     Sym* copy = (Sym*)im->heap->alloc(im->heap, sizeof(Sym) * d.nimports,
    407                                       _Alignof(Sym));
    408     if (!copy) {
    409       d.imports = NULL;
    410       d.nimports = 0;
    411     } else {
    412       memcpy(copy, dep->imports, sizeof(Sym) * d.nimports);
    413       d.imports = copy;
    414     }
    415   } else {
    416     d.imports = NULL;
    417     d.nimports = 0;
    418   }
    419   im->deps[im->ndeps++] = d;
    420 }
    421 void obj_image_add_rpath(ObjImage* im, Sym rpath) {
    422   if (!im) return;
    423   if (VEC_GROW(im->heap, im->rpaths, im->cap_rpaths, im->nrpaths + 1)) return;
    424   im->rpaths[im->nrpaths++] = rpath;
    425 }
    426 void obj_image_add_dynsym(ObjImage* im, const ObjImageSym* sym) {
    427   if (!im || !sym) return;
    428   if (VEC_GROW(im->heap, im->dynsyms, im->cap_dynsyms, im->ndynsyms + 1))
    429     return;
    430   im->dynsyms[im->ndynsyms++] = *sym;
    431 }
    432 void obj_image_add_dynreloc(ObjImage* im, const ObjImageReloc* rel) {
    433   if (!im || !rel) return;
    434   if (VEC_GROW(im->heap, im->dynrelocs, im->cap_dynrelocs, im->ndynrelocs + 1))
    435     return;
    436   im->dynrelocs[im->ndynrelocs++] = *rel;
    437 }
    438 void obj_image_add_undef(ObjImage* im, Sym name) {
    439   if (!im || !name) return;
    440   if (VEC_GROW(im->heap, im->undefs, im->cap_undefs, im->nundefs + 1)) return;
    441   im->undefs[im->nundefs++] = name;
    442 }
    443 void obj_image_add_raw(ObjImage* im, const ObjImageRaw* raw) {
    444   if (!im || !raw) return;
    445   if (VEC_GROW(im->heap, im->raws, im->cap_raws, im->nraws + 1)) return;
    446   im->raws[im->nraws++] = *raw;
    447 }
    448 
    449 ObjKind obj_image_kind(const ObjImage* im) {
    450   return im ? im->kind : OBJ_KIND_REL;
    451 }
    452 u64 obj_image_entry(const ObjImage* im) { return im ? im->entry : 0; }
    453 u64 obj_image_base(const ObjImage* im) { return im ? im->image_base : 0; }
    454 Sym obj_image_interp(const ObjImage* im) { return im ? im->interp : 0; }
    455 Sym obj_image_soname(const ObjImage* im) { return im ? im->soname : 0; }
    456 
    457 u32 obj_image_nsegments(const ObjImage* im) { return im ? im->nsegs : 0; }
    458 const ObjSegment* obj_image_segment(const ObjImage* im, u32 idx) {
    459   return (im && idx < im->nsegs) ? &im->segs[idx] : NULL;
    460 }
    461 u32 obj_image_ndeps(const ObjImage* im) { return im ? im->ndeps : 0; }
    462 const ObjImageDep* obj_image_dep(const ObjImage* im, u32 idx) {
    463   return (im && idx < im->ndeps) ? &im->deps[idx] : NULL;
    464 }
    465 u32 obj_image_nrpaths(const ObjImage* im) { return im ? im->nrpaths : 0; }
    466 Sym obj_image_rpath(const ObjImage* im, u32 idx) {
    467   return (im && idx < im->nrpaths) ? im->rpaths[idx] : 0;
    468 }
    469 u32 obj_image_ndynsyms(const ObjImage* im) { return im ? im->ndynsyms : 0; }
    470 const ObjImageSym* obj_image_dynsym(const ObjImage* im, u32 idx) {
    471   return (im && idx < im->ndynsyms) ? &im->dynsyms[idx] : NULL;
    472 }
    473 u32 obj_image_ndynrelocs(const ObjImage* im) { return im ? im->ndynrelocs : 0; }
    474 const ObjImageReloc* obj_image_dynreloc(const ObjImage* im, u32 idx) {
    475   return (im && idx < im->ndynrelocs) ? &im->dynrelocs[idx] : NULL;
    476 }
    477 u32 obj_image_nundefs(const ObjImage* im) { return im ? im->nundefs : 0; }
    478 Sym obj_image_undef(const ObjImage* im, u32 idx) {
    479   return (im && idx < im->nundefs) ? im->undefs[idx] : 0;
    480 }
    481 u32 obj_image_nraws(const ObjImage* im) { return im ? im->nraws : 0; }
    482 const ObjImageRaw* obj_image_raw(const ObjImage* im, u32 idx) {
    483   return (im && idx < im->nraws) ? &im->raws[idx] : NULL;
    484 }
    485 
    486 void obj_ext_set(ObjBuilder* ob, ObjExtKind kind, void* payload,
    487                  ObjExtFreeFn free_fn) {
    488   if (!ob || (u32)kind >= OBJ_EXT_SLOT_COUNT) return;
    489   if (ob->ext[kind].payload && ob->ext[kind].free_fn &&
    490       ob->ext[kind].payload != payload) {
    491     ob->ext[kind].free_fn(ob->c, ob->ext[kind].payload);
    492   }
    493   ob->ext[kind].payload = payload;
    494   ob->ext[kind].free_fn = free_fn;
    495 }
    496 
    497 void* obj_ext_get(const ObjBuilder* ob, ObjExtKind kind) {
    498   if (!ob || (u32)kind >= OBJ_EXT_SLOT_COUNT) return NULL;
    499   return ob->ext[kind].payload;
    500 }
    501 
    502 void obj_ext_clear(ObjBuilder* ob, ObjExtKind kind) {
    503   if (!ob || (u32)kind >= OBJ_EXT_SLOT_COUNT) return;
    504   if (ob->ext[kind].payload && ob->ext[kind].free_fn) {
    505     ob->ext[kind].free_fn(ob->c, ob->ext[kind].payload);
    506   }
    507   ob->ext[kind].payload = NULL;
    508   ob->ext[kind].free_fn = NULL;
    509 }
    510 
    511 /* ---- write side ---- */
    512 
    513 /* Pack (name, kind) into a nonzero hashmap key. The low bit is always set so
    514  * the key is never 0 (the empty-slot sentinel), and name/kind occupy disjoint
    515  * bit ranges so distinct (name, kind) pairs never collide. */
    516 static u64 sec_progbits_key(Sym name, SecKind kind) {
    517   return (((u64)name) << 17) | (((u64)(u16)kind) << 1) | 1u;
    518 }
    519 
    520 ObjSecId obj_section(ObjBuilder* ob, Sym name, SecKind kind, u16 flags,
    521                      u32 align) {
    522   /* Find-or-create by (name, kind, sem=PROGBITS). Repeated calls for the
    523    * same logical section — e.g. one .rodata per FP/string literal, or one
    524    * .data per static initializer — collapse onto a single Section and
    525    * accumulate bytes into it instead of emitting a fan-out of identically-
    526    * named output sections.  Merge align (max) and flags (union) so a
    527    * stricter requirement from a later caller wins. The PROGBITS find is an
    528    * O(1) index lookup (sec_by_key), kept exact by obj_section_ex. */
    529   ObjSecId* hit =
    530       SecKeyIndex_get(&ob->sec_by_key, sec_progbits_key(name, kind));
    531   if (hit) {
    532     Section* s = Sections_at(&ob->sections, *hit);
    533     if (s) {
    534       if (align > s->align) s->align = align;
    535       s->flags = (u16)(s->flags | flags);
    536       /* Pad to align so the next obj_reserve / obj_write lands at an
    537        * offset that satisfies this caller's alignment.  Without this
    538        * each contribution is laid out at whatever offset the prior
    539        * write happened to leave, so a 4-byte global following a 6-byte
    540        * string lands at .data+6 — and any LDST32 reloc against the
    541        * containing section breaks at link time. */
    542       u32 a = align ? align : 1u;
    543       if (a > 1u) {
    544         u32 cur = buf_pos(&s->bytes);
    545         u32 mis = cur & (a - 1u);
    546         if (mis) {
    547           u32 pad = a - mis;
    548           u8* dst = buf_reserve(&s->bytes, pad);
    549           if (dst) memset(dst, 0, pad);
    550         }
    551       }
    552       return *hit;
    553     }
    554   }
    555   return obj_section_ex(ob, name, kind, SSEM_PROGBITS, flags, align, 0,
    556                         OBJ_SEC_NONE, 0);
    557 }
    558 
    559 ObjSecId obj_section_ex(ObjBuilder* ob, Sym name, SecKind kind, SecSem sem,
    560                         u16 flags, u32 align, u32 entsize, u32 link, u32 info) {
    561   u32 id;
    562   Section* s = Sections_push(&ob->sections, &id);
    563   if (!s) return OBJ_SEC_NONE;
    564   s->name = name;
    565   s->kind = (u16)kind;
    566   s->flags = flags;
    567   s->sem = (u16)sem;
    568   s->ext_kind = OBJ_EXT_NONE;
    569   s->align = align ? align : 1;
    570   s->entsize = entsize;
    571   s->link = (ObjSecId)link;
    572   s->info = info;
    573   s->group_id = OBJ_GROUP_NONE;
    574   s->bss_size = 0;
    575   s->addr = 0;
    576   buf_init(&s->bytes, ob->heap);
    577   /* Index PROGBITS sections so obj_section's find is O(1). try_insert keeps the
    578    * first id for a (name, kind), matching the old scan's first-match semantics
    579    * (and leaving distinct same-named COMDAT sections, created directly here, to
    580    * resolve to the first — exactly as the linear scan did). */
    581   if (sem == SSEM_PROGBITS)
    582     (void)SecKeyIndex_try_insert(&ob->sec_by_key, sec_progbits_key(name, kind),
    583                                  (ObjSecId)id, NULL);
    584   return (ObjSecId)id;
    585 }
    586 
    587 void obj_section_set_addr(ObjBuilder* ob, ObjSecId id, u64 addr) {
    588   Section* s = Sections_at(&ob->sections, id);
    589   if (s && id != OBJ_SEC_NONE) s->addr = addr;
    590 }
    591 
    592 void obj_section_set_flags(ObjBuilder* ob, ObjSecId id, u16 flags) {
    593   Section* s = Sections_at(&ob->sections, id);
    594   if (s && id != OBJ_SEC_NONE) s->flags = flags;
    595 }
    596 
    597 void obj_section_set_entsize(ObjBuilder* ob, ObjSecId id, u32 entsize) {
    598   Section* s = Sections_at(&ob->sections, id);
    599   if (s && id != OBJ_SEC_NONE) s->entsize = entsize;
    600 }
    601 
    602 void obj_section_set_align(ObjBuilder* ob, ObjSecId id, u32 align) {
    603   Section* s = Sections_at(&ob->sections, id);
    604   if (s && id != OBJ_SEC_NONE) s->align = align ? align : 1;
    605 }
    606 
    607 void obj_section_set_group(ObjBuilder* ob, ObjSecId id, ObjGroupId gid) {
    608   Section* s = Sections_at(&ob->sections, id);
    609   if (s && id != OBJ_SEC_NONE) s->group_id = gid;
    610 }
    611 
    612 void obj_section_set_link_info(ObjBuilder* ob, ObjSecId id, ObjSecId link,
    613                                u32 info) {
    614   Section* s;
    615   if (id == OBJ_SEC_NONE) return;
    616   s = Sections_at(&ob->sections, id);
    617   if (!s) return;
    618   s->link = link;
    619   s->info = info;
    620 }
    621 
    622 void obj_section_set_ext(ObjBuilder* ob, ObjSecId id, ObjExtKind ek,
    623                          u32 ext_type, u32 ext_flags) {
    624   Section* s;
    625   if (id == OBJ_SEC_NONE) return;
    626   s = Sections_at(&ob->sections, id);
    627   if (!s) return;
    628   s->ext_kind = (u16)ek;
    629   s->ext_type = ext_type;
    630   s->ext_flags = ext_flags;
    631 }
    632 
    633 /* A NOBITS section (.bss / .tbss) stores no bytes — only a size. decl.c and
    634  * obj_align_to already treat SEC_BSS this way regardless of sem; obj_write and
    635  * obj_pos must agree so the MCEmitter path (the standalone assembler's
    636  * `.zero`/`.skip` fills and label positions) advances and reports the bss_size
    637  * cursor instead of a byte buffer that the emitters then ignore. Codegen never
    638  * writes/positions a BSS section through these (it uses obj_reserve_bss and its
    639  * own counter), so this only affects the assembler's path. */
    640 static int sec_is_nobits(const Section* s) {
    641   return s->sem == SSEM_NOBITS || s->kind == SEC_BSS;
    642 }
    643 
    644 void obj_write(ObjBuilder* ob, ObjSecId id, const void* data, size_t n) {
    645   Section* s;
    646   if (id == OBJ_SEC_NONE) return;
    647   s = Sections_at(&ob->sections, id);
    648   if (!s) return;
    649   if (sec_is_nobits(s)) {
    650     s->bss_size += (u32)n; /* reserve zero-fill space; store nothing */
    651     return;
    652   }
    653   buf_write(&s->bytes, data, n);
    654 }
    655 
    656 /* See obj.h: the byte buffer for a PROGBITS section, NULL for NOBITS/.bss (so
    657  * the caller falls back to obj_write's bss_size path) or invalid ids. Lets the
    658  * MCEmitter hoist the per-emit Sections_at deref + nobits branch out of the hot
    659  * loop, re-resolving only at set_section. */
    660 Buf* obj_section_bytes(ObjBuilder* ob, ObjSecId id) {
    661   Section* s;
    662   if (id == OBJ_SEC_NONE) return NULL;
    663   s = Sections_at(&ob->sections, id);
    664   if (!s || sec_is_nobits(s)) return NULL;
    665   return &s->bytes;
    666 }
    667 
    668 u8* obj_reserve(ObjBuilder* ob, ObjSecId id, size_t n) {
    669   Section* s;
    670   if (id == OBJ_SEC_NONE) return NULL;
    671   s = Sections_at(&ob->sections, id);
    672   return s ? buf_reserve(&s->bytes, n) : NULL;
    673 }
    674 
    675 void obj_reserve_bss(ObjBuilder* ob, ObjSecId id, u32 size, u32 align) {
    676   Section* s;
    677   if (id == OBJ_SEC_NONE) return;
    678   s = Sections_at(&ob->sections, id);
    679   if (!s) return;
    680   s->bss_size = size;
    681   if (align) s->align = align;
    682 }
    683 
    684 u32 obj_align_to(ObjBuilder* ob, ObjSecId id, u32 align) {
    685   Section* s;
    686   u32 a, cur, base, pad;
    687   if (id == OBJ_SEC_NONE) return 0;
    688   s = Sections_at(&ob->sections, id);
    689   if (!s) return 0;
    690   a = align ? align : 1u;
    691   /* Treat SEC_BSS like NOBITS even when sem is the default PROGBITS —
    692    * decl.c creates .bss via the simple obj_section, but emit_macho /
    693    * emit_elf both route SEC_BSS through the zerofill path regardless
    694    * of sem, so the byte buf is ignored on output and only bss_size
    695    * matters. */
    696   if (s->sem == SSEM_NOBITS || s->kind == SEC_BSS) {
    697     base = (s->bss_size + (a - 1u)) & ~(a - 1u);
    698     s->bss_size = base;
    699     if (a > s->align) s->align = a;
    700     return base;
    701   }
    702   cur = buf_pos(&s->bytes);
    703   base = (cur + (a - 1u)) & ~(a - 1u);
    704   pad = base - cur;
    705   if (pad) {
    706     u8* p = buf_reserve(&s->bytes, pad);
    707     if (p) memset(p, 0, pad);
    708   }
    709   if (a > s->align) s->align = a;
    710   return base;
    711 }
    712 
    713 u32 obj_pos(ObjBuilder* ob, ObjSecId id) {
    714   Section* s;
    715   if (id == OBJ_SEC_NONE) return 0;
    716   s = Sections_at(&ob->sections, id);
    717   if (!s) return 0;
    718   return sec_is_nobits(s) ? s->bss_size : buf_pos(&s->bytes);
    719 }
    720 
    721 void obj_patch(ObjBuilder* ob, ObjSecId id, u32 ofs, const void* data,
    722                size_t n) {
    723   Section* s;
    724   if (id == OBJ_SEC_NONE) return;
    725   s = Sections_at(&ob->sections, id);
    726   if (s) buf_patch(&s->bytes, ofs, data, n);
    727 }
    728 
    729 static ObjSymId obj_symbol_make(ObjBuilder* ob, Sym name, SymBind bind,
    730                                 SymVis vis, SymKind kind, ObjSecId section_id,
    731                                 u64 value, u64 size, u64 common_align,
    732                                 int index_name) {
    733   u32 id;
    734   ObjSym* s = Symbols_push(&ob->symbols, &id);
    735   if (!s) return OBJ_SYM_NONE;
    736   s->name = name;
    737   s->bind = (u16)bind;
    738   s->kind = (u16)kind;
    739   s->vis = (u8)vis;
    740   s->ext_kind = OBJ_EXT_NONE;
    741   s->section_id = section_id;
    742   s->value = value;
    743   s->size = size;
    744   s->common_align = common_align;
    745   s->atom_subordinate = 0;
    746   /* First-wins: record the lowest id for this name so obj_symbol_find returns
    747    * the same symbol the linear scan would. Later same-name symbols (legal for
    748    * STB_LOCAL) do not overwrite. */
    749   if (index_name && name && !SymNameIndex_get(&ob->sym_by_name, name))
    750     (void)SymNameIndex_set(&ob->sym_by_name, name, (ObjSymId)id);
    751   return (ObjSymId)id;
    752 }
    753 
    754 ObjSymId obj_symbol(ObjBuilder* ob, Sym name, SymBind bind, SymKind kind,
    755                     ObjSecId section_id, u64 value, u64 size) {
    756   return obj_symbol_ex(ob, name, bind, SV_DEFAULT, kind, section_id, value,
    757                        size, 0);
    758 }
    759 
    760 ObjSymId obj_symbol_ex(ObjBuilder* ob, Sym name, SymBind bind, SymVis vis,
    761                        SymKind kind, ObjSecId section_id, u64 value, u64 size,
    762                        u64 common_align) {
    763   return obj_symbol_make(ob, name, bind, vis, kind, section_id, value, size,
    764                          common_align, 1);
    765 }
    766 
    767 ObjSymId obj_symbol_defer(ObjBuilder* ob, Sym name, SymBind bind, SymVis vis,
    768                           SymKind kind, u64 size) {
    769   ObjSymId id;
    770   ObjSym* s;
    771   id = obj_symbol_make(ob, name, bind, vis, kind, OBJ_SEC_NONE, 0, size, 0, 0);
    772   if (id == OBJ_SYM_NONE) return OBJ_SYM_NONE;
    773   s = Symbols_at(&ob->symbols, id);
    774   if (s) s->removed = 1;
    775   return id;
    776 }
    777 
    778 ObjSymId obj_symbol_find(ObjBuilder* ob, Sym name) {
    779   /* Authoritative O(1) lookup — never a linear scan. Normal/live symbols are
    780    * indexed when created or published, and obj_symbol_rename keeps the index
    781    * exact. Deferred symbols deliberately stay out of this map until published.
    782    */
    783   ObjSymId* hit;
    784   if (!ob || !name) return OBJ_SYM_NONE;
    785   hit = SymNameIndex_get(&ob->sym_by_name, name);
    786   return hit ? *hit : OBJ_SYM_NONE;
    787 }
    788 
    789 void obj_symbol_define(ObjBuilder* ob, ObjSymId id, ObjSecId section_id,
    790                        u64 value, u64 size) {
    791   ObjSym* s;
    792   if (id == OBJ_SYM_NONE) return;
    793   s = Symbols_at(&ob->symbols, id);
    794   if (!s) return;
    795   s->section_id = section_id;
    796   s->value = value;
    797   s->size = size;
    798   if (s->kind == SK_UNDEF) s->kind = SK_OBJ;
    799 }
    800 
    801 void obj_symbol_define_live(ObjBuilder* ob, ObjSymId id, ObjSecId section_id,
    802                             u64 value, u64 size) {
    803   ObjSym* s;
    804   ObjSymId* slot;
    805   obj_symbol_define(ob, id, section_id, value, size);
    806   if (!ob || id == OBJ_SYM_NONE) return;
    807   s = Symbols_at(&ob->symbols, id);
    808   if (!s) return;
    809   s->removed = 0;
    810   if (s->name) {
    811     slot = SymNameIndex_get(&ob->sym_by_name, s->name);
    812     if (!slot || *slot > id)
    813       (void)SymNameIndex_set(&ob->sym_by_name, s->name, id);
    814   }
    815 }
    816 
    817 void obj_symbol_set_flags(ObjBuilder* ob, ObjSymId id, u16 flags) {
    818   ObjSym* s;
    819   if (id == OBJ_SYM_NONE) return;
    820   s = Symbols_at(&ob->symbols, id);
    821   if (!s) return;
    822   s->flags = flags;
    823 }
    824 
    825 void obj_symbol_set_atom_subordinate(ObjBuilder* ob, ObjSymId id,
    826                                      int subordinate) {
    827   ObjSym* s;
    828   if (id == OBJ_SYM_NONE) return;
    829   s = Symbols_at(&ob->symbols, id);
    830   if (!s) return;
    831   s->atom_subordinate = subordinate ? 1u : 0u;
    832 }
    833 
    834 void obj_reloc(ObjBuilder* ob, ObjSecId section_id, u32 offset, RelocKind kind,
    835                ObjSymId sym, i64 addend) {
    836   obj_reloc_ex(ob, section_id, offset, kind, sym, addend, 1, 0);
    837 }
    838 
    839 void obj_reloc_ex(ObjBuilder* ob, ObjSecId section_id, u32 offset,
    840                   RelocKind kind, ObjSymId sym, i64 addend, int explicit_addend,
    841                   int pair) {
    842   Reloc* r = Relocs_push(&ob->relocs, NULL);
    843   if (!r) return;
    844   r->section_id = section_id;
    845   r->offset = offset;
    846   r->kind = (u16)kind;
    847   r->has_explicit_addend = (u8)(explicit_addend ? 1 : 0);
    848   r->pair = (u8)pair;
    849   r->sym = sym;
    850   r->addend = addend;
    851   ob->reloc_index_dirty = 1; /* invalidate the per-section reloc index */
    852   /* Any reloc against this symbol is enough to retain it through the
    853    * emit-time UNDEF prune. See ObjSym::referenced. */
    854   obj_sym_mark_referenced(ob, sym);
    855 }
    856 
    857 void obj_sym_mark_referenced(ObjBuilder* ob, ObjSymId id) {
    858   ObjSym* s;
    859   if (id == OBJ_SYM_NONE) return;
    860   s = Symbols_at(&ob->symbols, id);
    861   if (s) s->referenced = 1;
    862 }
    863 
    864 void obj_sym_set_referenced(ObjBuilder* ob, ObjSymId id, int referenced) {
    865   ObjSym* s;
    866   if (id == OBJ_SYM_NONE) return;
    867   s = Symbols_at(&ob->symbols, id);
    868   if (s) s->referenced = referenced ? 1u : 0u;
    869 }
    870 
    871 ObjAtomId obj_atom_define(ObjBuilder* ob, ObjSecId section_id, u32 offset,
    872                           u32 size, ObjSymId signature, u32 flags) {
    873   u32 id;
    874   ObjAtom* a;
    875   if (!ob || section_id == OBJ_SEC_NONE) return OBJ_ATOM_NONE;
    876   a = Atoms_push(&ob->atoms, &id);
    877   if (!a) return OBJ_ATOM_NONE;
    878   a->section_id = section_id;
    879   a->offset = offset;
    880   a->size = size;
    881   a->signature = signature;
    882   a->flags = flags;
    883   return (ObjAtomId)id;
    884 }
    885 
    886 ObjGroupId obj_group(ObjBuilder* ob, Sym name, ObjSymId signature, u32 flags) {
    887   u32 id;
    888   ObjGroup* g = Groups_push(&ob->groups, &id);
    889   if (!g) return OBJ_GROUP_NONE;
    890   g->name = name;
    891   g->signature = signature;
    892   g->flags = flags;
    893   return (ObjGroupId)id;
    894 }
    895 
    896 void obj_group_add_section(ObjBuilder* ob, ObjGroupId gid, ObjSecId sec) {
    897   ObjGroup* g;
    898   ObjSecId* p;
    899   if (gid == OBJ_GROUP_NONE) return;
    900   g = Groups_at(&ob->groups, gid);
    901   if (!g) return;
    902   /* Linear realloc — group section counts are tiny (handful per group). */
    903   p = (ObjSecId*)ob->heap->realloc(
    904       ob->heap, g->sections, sizeof(ObjSecId) * g->nsections,
    905       sizeof(ObjSecId) * (g->nsections + 1), _Alignof(ObjSecId));
    906   if (!p) return;
    907   p[g->nsections++] = sec;
    908   g->sections = p;
    909 }
    910 
    911 void obj_finalize(ObjBuilder* ob) {
    912   /* No flat-offset patching needed yet — section bytes are read out via
    913    * buf_flatten on demand by emitters. Keep this hook in place: when a
    914    * future writer wants intra-section fixups (e.g. label-to-offset
    915    * resolution after the full section is written), this is where they
    916    * land. */
    917   (void)ob;
    918 }
    919 
    920 /* ---- mutators (strip / objcopy support) ---- */
    921 
    922 void obj_section_remove(ObjBuilder* ob, ObjSecId id) {
    923   Section* s;
    924   if (!ob || id == OBJ_SEC_NONE) return;
    925   s = Sections_at(&ob->sections, id);
    926   if (!s) return;
    927   s->removed = 1;
    928 }
    929 
    930 void obj_symbol_remove(ObjBuilder* ob, ObjSymId id) {
    931   ObjSym* s;
    932   if (!ob || id == OBJ_SYM_NONE) return;
    933   s = Symbols_at(&ob->symbols, id);
    934   if (!s) return;
    935   s->removed = 1;
    936 }
    937 
    938 void obj_group_remove(ObjBuilder* ob, ObjGroupId id) {
    939   ObjGroup* g;
    940   if (!ob || id == OBJ_GROUP_NONE) return;
    941   g = Groups_at(&ob->groups, id);
    942   if (!g) return;
    943   g->removed = 1;
    944 }
    945 
    946 void obj_section_rename(ObjBuilder* ob, ObjSecId id, Sym new_name) {
    947   Section* s;
    948   if (!ob || id == OBJ_SEC_NONE) return;
    949   s = Sections_at(&ob->sections, id);
    950   if (!s) return;
    951   s->name = new_name;
    952 }
    953 
    954 void obj_symbol_rename(ObjBuilder* ob, ObjSymId id, Sym new_name) {
    955   ObjSym* s;
    956   Sym old;
    957   ObjSymId* slot;
    958   if (!ob || id == OBJ_SYM_NONE) return;
    959   s = Symbols_at(&ob->symbols, id);
    960   if (!s) return;
    961   old = s->name;
    962   s->name = new_name;
    963   if (old == new_name) return;
    964   /* Keep the name index exact so obj_symbol_find stays a pure hash lookup.
    965    * If this symbol was the indexed entry for its old name, hand the entry to
    966    * the next-lowest symbol still carrying that name (duplicate STB_LOCAL names
    967    * are legal), or drop it. This is the only scan in the symbol-index path and
    968    * it is confined to obj_symbol_rename — a cold objcopy-style operation, never
    969    * the codegen/find hot path. */
    970   if (old) {
    971     slot = SymNameIndex_get(&ob->sym_by_name, old);
    972     if (slot && *slot == id) {
    973       ObjSymId repl = OBJ_SYM_NONE;
    974       u32 n = Symbols_count(&ob->symbols);
    975       for (u32 i = 1; i < n; ++i) {
    976         ObjSym* t = Symbols_at(&ob->symbols, i);
    977         if (t && (ObjSymId)i != id && t->name == old) {
    978           repl = (ObjSymId)i;
    979           break;
    980         }
    981       }
    982       if (repl != OBJ_SYM_NONE)
    983         (void)SymNameIndex_set(&ob->sym_by_name, old, repl);
    984       else
    985         SymNameIndex_del(&ob->sym_by_name, old);
    986     }
    987   }
    988   /* new_name resolves to the lowest id that carries it (first-match order). A
    989    * rename can give an existing lower-id symbol this name, so lower an existing
    990    * entry when warranted. */
    991   if (new_name) {
    992     slot = SymNameIndex_get(&ob->sym_by_name, new_name);
    993     if (!slot || *slot > id)
    994       (void)SymNameIndex_set(&ob->sym_by_name, new_name, id);
    995   }
    996 }
    997 
    998 void obj_symbol_set_bind(ObjBuilder* ob, ObjSymId id, SymBind bind) {
    999   ObjSym* s;
   1000   if (!ob || id == OBJ_SYM_NONE) return;
   1001   s = Symbols_at(&ob->symbols, id);
   1002   if (!s) return;
   1003   s->bind = (u16)bind;
   1004 }
   1005 
   1006 void obj_symbol_set_vis(ObjBuilder* ob, ObjSymId id, SymVis vis) {
   1007   ObjSym* s;
   1008   if (!ob || id == OBJ_SYM_NONE) return;
   1009   s = Symbols_at(&ob->symbols, id);
   1010   if (!s) return;
   1011   s->vis = (u8)vis;
   1012 }
   1013 
   1014 void obj_section_replace_bytes(ObjBuilder* ob, ObjSecId id, const u8* data,
   1015                                size_t n) {
   1016   Section* s;
   1017   if (!ob || id == OBJ_SEC_NONE) return;
   1018   s = Sections_at(&ob->sections, id);
   1019   if (!s) return;
   1020   /* Drop the old chunked Buf and reinitialize empty, then write the new
   1021    * bytes. Cheaper than scanning + patching when the replacement is
   1022    * different-sized — which it usually is (objcopy --update-section). */
   1023   buf_fini(&s->bytes);
   1024   buf_init(&s->bytes, ob->heap);
   1025   s->bss_size = 0;
   1026   if (data && n) buf_write(&s->bytes, data, n);
   1027 }
   1028 
   1029 void obj_sweep_dead(ObjBuilder* ob) {
   1030   u32 nsec = Sections_count(&ob->sections);
   1031   u32 nsym = Symbols_count(&ob->symbols);
   1032   u32 nrel = Relocs_count(&ob->relocs);
   1033   u32 ngrp = Groups_count(&ob->groups);
   1034   u32 i;
   1035 
   1036   /* Pass 1: cascade removed sections into their defining symbols. Also
   1037    * absorbs the historical UNDEF-prune predicate: any non-referenced
   1038    * global/weak symbol that lacks a defining section (and isn't an ABS
   1039    * or COMMON definition, both of which legitimately have section_id ==
   1040    * OBJ_SEC_NONE) is a spurious extern from a header — drop it.
   1041    *
   1042    * The "no defining section" test matches macho_emit's sym_is_undef,
   1043    * which is stronger than `kind == SK_UNDEF`: frontends mint SK_OBJ /
   1044    * SK_TLS / SK_FUNC entries for extern decls and only set them to
   1045    * SK_UNDEF for true references, so checking section_id catches both. */
   1046   for (i = 1; i < nsym; ++i) {
   1047     ObjSym* s = Symbols_at(&ob->symbols, i);
   1048     if (!s || s->removed) continue;
   1049     if (s->section_id != OBJ_SEC_NONE) {
   1050       const Section* sec = Sections_at(&ob->sections, s->section_id);
   1051       if (sec && sec->removed) {
   1052         s->removed = 1;
   1053         continue;
   1054       }
   1055     }
   1056     if (s->section_id == OBJ_SEC_NONE && s->kind != SK_ABS &&
   1057         s->kind != SK_COMMON && !s->referenced &&
   1058         (s->bind == SB_GLOBAL || s->bind == SB_WEAK)) {
   1059       s->removed = 1;
   1060     }
   1061   }
   1062 
   1063   /* Pass 2: drop relocs that became dangling. A reloc is dead if its
   1064    * containing section, its target symbol, or the symbol's defining
   1065    * section is gone. */
   1066   for (i = 0; i < nrel; ++i) {
   1067     Reloc* r = Relocs_at(&ob->relocs, i);
   1068     if (!r || r->removed) continue;
   1069     if (r->section_id != OBJ_SEC_NONE) {
   1070       const Section* sec = Sections_at(&ob->sections, r->section_id);
   1071       if (!sec || sec->removed) {
   1072         r->removed = 1;
   1073         continue;
   1074       }
   1075     }
   1076     if (r->sym != OBJ_SYM_NONE) {
   1077       const ObjSym* ts = Symbols_at(&ob->symbols, r->sym);
   1078       if (!ts || ts->removed) r->removed = 1;
   1079     }
   1080   }
   1081 
   1082   {
   1083     u32 natom = Atoms_count(&ob->atoms);
   1084     for (i = 1; i < natom; ++i) {
   1085       ObjAtom* a = Atoms_at(&ob->atoms, i);
   1086       const Section* sec;
   1087       const ObjSym* sig;
   1088       if (!a || a->removed) continue;
   1089       sec = Sections_at(&ob->sections, a->section_id);
   1090       if (!sec || sec->removed) {
   1091         a->removed = 1;
   1092         continue;
   1093       }
   1094       if (a->signature != OBJ_SYM_NONE) {
   1095         sig = Symbols_at(&ob->symbols, a->signature);
   1096         if (!sig || sig->removed) a->removed = 1;
   1097       }
   1098     }
   1099   }
   1100 
   1101   /* Pass 3: compact each group's member list to drop removed sections;
   1102    * tombstone the group if its list empties out or its signature symbol
   1103    * is removed. Member list is rewritten in place — the storage stays
   1104    * the same size, the trailing slots just become unused. */
   1105   for (i = 1; i < ngrp; ++i) {
   1106     ObjGroup* g = Groups_at(&ob->groups, i);
   1107     u32 w, r;
   1108     if (!g || g->removed) continue;
   1109     if (g->signature != OBJ_SYM_NONE) {
   1110       const ObjSym* sig = Symbols_at(&ob->symbols, g->signature);
   1111       if (!sig || sig->removed) {
   1112         g->removed = 1;
   1113         continue;
   1114       }
   1115     }
   1116     w = 0;
   1117     for (r = 0; r < g->nsections; ++r) {
   1118       ObjSecId sid = g->sections[r];
   1119       const Section* sec =
   1120           (sid != OBJ_SEC_NONE) ? Sections_at(&ob->sections, sid) : NULL;
   1121       if (sec && !sec->removed) g->sections[w++] = sid;
   1122     }
   1123     g->nsections = w;
   1124     if (w == 0) g->removed = 1;
   1125   }
   1126 
   1127   /* Pass 4: clear Section.link if it now points at a removed section.
   1128    * (Section.info is type-dependent — leave it to the emitter, which
   1129    * already inspects the sem to interpret it.) */
   1130   for (i = 1; i < nsec; ++i) {
   1131     Section* s = Sections_at(&ob->sections, i);
   1132     if (!s || s->removed) continue;
   1133     if (s->link != OBJ_SEC_NONE) {
   1134       const Section* lk = Sections_at(&ob->sections, s->link);
   1135       if (!lk || lk->removed) s->link = OBJ_SEC_NONE;
   1136     }
   1137   }
   1138 
   1139   /* Removed flags changed — the per-section reloc index must rebuild. */
   1140   ob->reloc_index_dirty = 1;
   1141 }
   1142 
   1143 /* ---- read side ---- */
   1144 
   1145 /* (Re)build the per-section reloc index: a counting sort of the live relocs
   1146  * keyed by section_id, preserving ascending global order within each section.
   1147  * O(n_relocs + n_sections). Idempotent while the index stays clean; callers
   1148  * reach it through obj_reloc_count / obj_reloc_section. Cast away const at the
   1149  * call sites — the index is a lazily-filled cache, not logical state. */
   1150 static void obj_reloc_index_ensure(ObjBuilder* ob) {
   1151   u32 nsec, total, i, nlive;
   1152   u32* off;
   1153   if (ob->reloc_index_off && !ob->reloc_index_dirty) return;
   1154   nsec = Sections_count(&ob->sections);
   1155   total = Relocs_count(&ob->relocs);
   1156 
   1157   if (!ob->reloc_index_off || ob->reloc_index_nsec != nsec) {
   1158     if (ob->reloc_index_off)
   1159       ob->heap->free(ob->heap, ob->reloc_index_off,
   1160                      sizeof(u32) * (ob->reloc_index_nsec + 1u));
   1161     off = (u32*)ob->heap->alloc(ob->heap, sizeof(u32) * (nsec + 1u),
   1162                                 _Alignof(u32));
   1163     ob->reloc_index_off = off;
   1164     ob->reloc_index_nsec = nsec;
   1165   } else {
   1166     off = ob->reloc_index_off;
   1167   }
   1168   memset(off, 0, sizeof(u32) * (nsec + 1u));
   1169 
   1170   /* Pass 1: tally live relocs into off[section_id + 1]. */
   1171   nlive = 0;
   1172   for (i = 0; i < total; ++i) {
   1173     const Reloc* r = Relocs_at(&ob->relocs, i);
   1174     if (r->removed) continue;
   1175     ++off[r->section_id + 1u];
   1176     ++nlive;
   1177   }
   1178   /* Prefix-sum: off[s] = start of section s's run; off[nsec] = nlive. */
   1179   for (i = 1; i <= nsec; ++i) off[i] += off[i - 1u];
   1180 
   1181   if (!ob->reloc_index || ob->reloc_index_len != nlive) {
   1182     if (ob->reloc_index)
   1183       ob->heap->free(ob->heap, ob->reloc_index,
   1184                      sizeof(u32) * ob->reloc_index_len);
   1185     ob->reloc_index = nlive ? (u32*)ob->heap->alloc(
   1186                                   ob->heap, sizeof(u32) * nlive, _Alignof(u32))
   1187                             : NULL;
   1188     ob->reloc_index_len = nlive;
   1189   }
   1190 
   1191   /* Pass 2: scatter global indices into per-section runs. A heap-temp cursor
   1192    * (seeded from off[]) keeps the prefix offsets intact for the readers. */
   1193   if (nlive) {
   1194     u32* cur = (u32*)ob->heap->alloc(ob->heap, sizeof(u32) * (nsec + 1u),
   1195                                      _Alignof(u32));
   1196     memcpy(cur, off, sizeof(u32) * (nsec + 1u));
   1197     for (i = 0; i < total; ++i) {
   1198       const Reloc* r = Relocs_at(&ob->relocs, i);
   1199       if (r->removed) continue;
   1200       ob->reloc_index[cur[r->section_id]++] = i;
   1201     }
   1202     ob->heap->free(ob->heap, cur, sizeof(u32) * (nsec + 1u));
   1203   }
   1204   ob->reloc_index_dirty = 0;
   1205 }
   1206 
   1207 u32 obj_section_count(const ObjBuilder* ob) {
   1208   return Sections_count(&ob->sections);
   1209 }
   1210 
   1211 const Section* obj_section_get(const ObjBuilder* ob, ObjSecId id) {
   1212   if (id == OBJ_SEC_NONE) return NULL;
   1213   return Sections_at(&ob->sections, id);
   1214 }
   1215 
   1216 u32 obj_reloc_count(const ObjBuilder* ob, ObjSecId id) {
   1217   obj_reloc_index_ensure((ObjBuilder*)ob);
   1218   if (id >= ob->reloc_index_nsec) return 0;
   1219   return ob->reloc_index_off[id + 1u] - ob->reloc_index_off[id];
   1220 }
   1221 
   1222 const u32* obj_reloc_section(const ObjBuilder* ob, ObjSecId id, u32* out_len) {
   1223   obj_reloc_index_ensure((ObjBuilder*)ob);
   1224   if (id >= ob->reloc_index_nsec) {
   1225     if (out_len) *out_len = 0;
   1226     return NULL;
   1227   }
   1228   if (out_len)
   1229     *out_len = ob->reloc_index_off[id + 1u] - ob->reloc_index_off[id];
   1230   return ob->reloc_index + ob->reloc_index_off[id];
   1231 }
   1232 
   1233 u32 obj_reloc_total(const ObjBuilder* ob) { return Relocs_count(&ob->relocs); }
   1234 
   1235 const Reloc* obj_reloc_at(const ObjBuilder* ob, u32 idx) {
   1236   return Relocs_at(&ob->relocs, idx);
   1237 }
   1238 
   1239 const ObjSym* obj_symbol_get(const ObjBuilder* ob, ObjSymId id) {
   1240   if (id == OBJ_SYM_NONE) return NULL;
   1241   return Symbols_at(&ob->symbols, id);
   1242 }
   1243 
   1244 u32 obj_atom_count(const ObjBuilder* ob) { return Atoms_count(&ob->atoms); }
   1245 
   1246 const ObjAtom* obj_atom_get(const ObjBuilder* ob, ObjAtomId id) {
   1247   if (id == OBJ_ATOM_NONE) return NULL;
   1248   return Atoms_at(&ob->atoms, id);
   1249 }
   1250 
   1251 int obj_section_has_atoms(const ObjBuilder* ob, ObjSecId sid) {
   1252   u32 n;
   1253   if (!ob || sid == OBJ_SEC_NONE) return 0;
   1254   n = Atoms_count(&ob->atoms);
   1255   for (u32 i = 1; i < n; ++i) {
   1256     const ObjAtom* a = Atoms_at(&ob->atoms, i);
   1257     if (a && !a->removed && a->section_id == sid) return 1;
   1258   }
   1259   return 0;
   1260 }
   1261 
   1262 ObjAtomId obj_atom_find(const ObjBuilder* ob, ObjSecId sid, u32 offset) {
   1263   u32 n;
   1264   if (!ob || sid == OBJ_SEC_NONE) return OBJ_ATOM_NONE;
   1265   n = Atoms_count(&ob->atoms);
   1266   for (u32 i = 1; i < n; ++i) {
   1267     const ObjAtom* a = Atoms_at(&ob->atoms, i);
   1268     u64 begin, end;
   1269     if (!a || a->removed || a->section_id != sid) continue;
   1270     begin = a->offset;
   1271     end = begin + a->size;
   1272     if (a->size != 0 && (u64)offset >= begin && (u64)offset < end)
   1273       return (ObjAtomId)i;
   1274   }
   1275   for (u32 i = 1; i < n; ++i) {
   1276     const ObjAtom* a = Atoms_at(&ob->atoms, i);
   1277     if (!a || a->removed || a->section_id != sid) continue;
   1278     if (a->size == 0 && offset == a->offset) return (ObjAtomId)i;
   1279   }
   1280   return OBJ_ATOM_NONE;
   1281 }
   1282 
   1283 ObjAtomId obj_atom_find_symbol(const ObjBuilder* ob, ObjSymId sym) {
   1284   const ObjSym* s;
   1285   ObjAtomId aid;
   1286   u32 n;
   1287   if (!ob || sym == OBJ_SYM_NONE) return OBJ_ATOM_NONE;
   1288   s = obj_symbol_get(ob, sym);
   1289   if (!s || s->section_id == OBJ_SEC_NONE) return OBJ_ATOM_NONE;
   1290   aid = obj_atom_find(ob, s->section_id, (u32)s->value);
   1291   if (aid != OBJ_ATOM_NONE) return aid;
   1292   n = Atoms_count(&ob->atoms);
   1293   for (u32 i = 1; i < n; ++i) {
   1294     const ObjAtom* a = Atoms_at(&ob->atoms, i);
   1295     if (a && !a->removed && a->signature == sym) return (ObjAtomId)i;
   1296   }
   1297   return OBJ_ATOM_NONE;
   1298 }
   1299 
   1300 u32 obj_group_count(const ObjBuilder* ob) { return Groups_count(&ob->groups); }
   1301 
   1302 const ObjGroup* obj_group_get(const ObjBuilder* ob, ObjGroupId id) {
   1303   if (id == OBJ_GROUP_NONE) return NULL;
   1304   return Groups_at(&ob->groups, id);
   1305 }
   1306 
   1307 ObjSymIter* obj_symiter_new(const ObjBuilder* ob) {
   1308   ObjSymIter* it =
   1309       (ObjSymIter*)ob->heap->alloc(ob->heap, sizeof(*it), _Alignof(ObjSymIter));
   1310   if (!it) return NULL;
   1311   it->ob = ob;
   1312   it->idx = 1; /* skip the id-0 sentinel */
   1313   return it;
   1314 }
   1315 
   1316 int obj_symiter_next(ObjSymIter* it, ObjSymEntry* out) {
   1317   const ObjSym* s;
   1318   if (!it) return 0;
   1319   s = Symbols_at(&it->ob->symbols, it->idx);
   1320   if (!s) return 0;
   1321   out->id = it->idx;
   1322   out->sym = s;
   1323   it->idx++;
   1324   return 1;
   1325 }
   1326 
   1327 void obj_symiter_free(ObjSymIter* it) {
   1328   if (!it) return;
   1329   ((Heap*)it->ob->heap)->free((Heap*)it->ob->heap, it, sizeof(*it));
   1330 }
   1331 
   1332 struct ObjGroupIter {
   1333   const ObjBuilder* ob;
   1334   u32 idx; /* next index to return */
   1335 };
   1336 
   1337 ObjGroupIter* obj_groupiter_new(const ObjBuilder* ob) {
   1338   ObjGroupIter* it = (ObjGroupIter*)ob->heap->alloc(ob->heap, sizeof(*it),
   1339                                                     _Alignof(ObjGroupIter));
   1340   if (!it) return NULL;
   1341   it->ob = ob;
   1342   it->idx = 1; /* skip the id-0 sentinel */
   1343   return it;
   1344 }
   1345 
   1346 int obj_groupiter_next(ObjGroupIter* it, ObjGroupEntry* out) {
   1347   const ObjGroup* g;
   1348   if (!it) return 0;
   1349   g = Groups_at(&it->ob->groups, it->idx);
   1350   if (!g) return 0;
   1351   out->id = it->idx;
   1352   out->group = g;
   1353   it->idx++;
   1354   return 1;
   1355 }
   1356 
   1357 void obj_groupiter_free(ObjGroupIter* it) {
   1358   if (!it) return;
   1359   ((Heap*)it->ob->heap)->free((Heap*)it->ob->heap, it, sizeof(*it));
   1360 }
   1361 
   1362 /* Diagnostic spelling for a RelocKind. Drops the leading R_ from the enum
   1363  * spelling so output reads like "RV_CALL" / "AARCH64_CALL26" — the same
   1364  * spelling GNU objdump uses minus its arch prefix. */
   1365 const char* reloc_kind_name(RelocKind k) {
   1366   switch (k) {
   1367 #define _CASE(name) \
   1368   case name:        \
   1369     return &(#name)[2] /* strip "R_" */
   1370     _CASE(R_NONE);
   1371     _CASE(R_ABS32);
   1372     _CASE(R_ABS64);
   1373     _CASE(R_REL32);
   1374     _CASE(R_REL64);
   1375     _CASE(R_PC32);
   1376     _CASE(R_PC64);
   1377     _CASE(R_GOT32);
   1378     _CASE(R_PLT32);
   1379     _CASE(R_ABS8);
   1380     _CASE(R_ABS16);
   1381     _CASE(R_PREL16);
   1382     _CASE(R_TPOFF64);
   1383     _CASE(R_AARCH64_ADR_GOT_PAGE);
   1384     _CASE(R_AARCH64_LD64_GOT_LO12_NC);
   1385     _CASE(R_AARCH64_JUMP26);
   1386     _CASE(R_AARCH64_CALL26);
   1387     _CASE(R_AARCH64_CONDBR19);
   1388     _CASE(R_AARCH64_TSTBR14);
   1389     _CASE(R_AARCH64_LD_PREL_LO19);
   1390     _CASE(R_AARCH64_ADR_PREL_LO21);
   1391     _CASE(R_AARCH64_INTRA_LABEL_ADDR);
   1392     _CASE(R_AARCH64_ADR_PREL_PG_HI21);
   1393     _CASE(R_AARCH64_ADR_PREL_PG_HI21_NC);
   1394     _CASE(R_AARCH64_ADD_ABS_LO12_NC);
   1395     _CASE(R_AARCH64_LDST8_ABS_LO12_NC);
   1396     _CASE(R_AARCH64_LDST16_ABS_LO12_NC);
   1397     _CASE(R_AARCH64_LDST32_ABS_LO12_NC);
   1398     _CASE(R_AARCH64_LDST64_ABS_LO12_NC);
   1399     _CASE(R_AARCH64_LDST128_ABS_LO12_NC);
   1400     _CASE(R_AARCH64_TLVP_LOAD_PAGE21);
   1401     _CASE(R_AARCH64_TLVP_LOAD_PAGEOFF12);
   1402     _CASE(R_AARCH64_TLSLE_ADD_TPREL_HI12);
   1403     _CASE(R_AARCH64_TLSLE_ADD_TPREL_LO12);
   1404     _CASE(R_AARCH64_TLSLE_ADD_TPREL_LO12_NC);
   1405     _CASE(R_AARCH64_TLSLE_LDST8_TPREL_LO12);
   1406     _CASE(R_AARCH64_TLSLE_LDST8_TPREL_LO12_NC);
   1407     _CASE(R_AARCH64_TLSLE_LDST16_TPREL_LO12);
   1408     _CASE(R_AARCH64_TLSLE_LDST16_TPREL_LO12_NC);
   1409     _CASE(R_AARCH64_TLSLE_LDST32_TPREL_LO12);
   1410     _CASE(R_AARCH64_TLSLE_LDST32_TPREL_LO12_NC);
   1411     _CASE(R_AARCH64_TLSLE_LDST64_TPREL_LO12);
   1412     _CASE(R_AARCH64_TLSLE_LDST64_TPREL_LO12_NC);
   1413     _CASE(R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21);
   1414     _CASE(R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC);
   1415     _CASE(R_COFF_ADDR32NB);
   1416     _CASE(R_AARCH64_POINTER_TO_GOT);
   1417     _CASE(R_AARCH64_TLSDESC_ADR_PAGE21);
   1418     _CASE(R_AARCH64_TLSDESC_LD64_LO12);
   1419     _CASE(R_AARCH64_TLSDESC_ADD_LO12);
   1420     _CASE(R_AARCH64_TLSDESC_CALL);
   1421     _CASE(R_RV_TLS_GD_HI20);
   1422     _CASE(R_AARCH64_GLOB_DAT);
   1423     _CASE(R_AARCH64_JUMP_SLOT);
   1424     _CASE(R_AARCH64_RELATIVE);
   1425     _CASE(R_AARCH64_COPY);
   1426     _CASE(R_X64_PC8);
   1427     _CASE(R_X64_32S);
   1428     _CASE(R_X64_PLT32);
   1429     _CASE(R_X64_GOTPCREL);
   1430     _CASE(R_X64_GOTPCRELX);
   1431     _CASE(R_X64_REX_GOTPCRELX);
   1432     _CASE(R_X64_GOTPC32);
   1433     _CASE(R_X64_GOTOFF64);
   1434     _CASE(R_X64_TPOFF32);
   1435     _CASE(R_X64_DTPOFF32);
   1436     _CASE(R_X64_DTPMOD64);
   1437     _CASE(R_X64_DTPOFF64);
   1438     _CASE(R_X64_TLSGD);
   1439     _CASE(R_X64_TLSLD);
   1440     _CASE(R_X64_GOTTPOFF);
   1441     _CASE(R_X64_GLOB_DAT);
   1442     _CASE(R_X64_JUMP_SLOT);
   1443     _CASE(R_X64_RELATIVE);
   1444     _CASE(R_X64_COPY);
   1445     _CASE(R_X64_TLV);
   1446     _CASE(R_RV_HI20);
   1447     _CASE(R_RV_LO12_I);
   1448     _CASE(R_RV_LO12_S);
   1449     _CASE(R_RV_BRANCH);
   1450     _CASE(R_RV_JAL);
   1451     _CASE(R_RV_CALL);
   1452     _CASE(R_RV_PCREL_HI20);
   1453     _CASE(R_RV_PCREL_LO12_I);
   1454     _CASE(R_RV_PCREL_LO12_S);
   1455     _CASE(R_RV_INTRA_AUIPC_ADDI);
   1456     _CASE(R_RV_GOT_HI20);
   1457     _CASE(R_RV_TLS_GOT_HI20);
   1458     _CASE(R_RV_TPREL_HI20);
   1459     _CASE(R_RV_TPREL_LO12_I);
   1460     _CASE(R_RV_TPREL_LO12_S);
   1461     _CASE(R_RV_TPREL_ADD);
   1462     _CASE(R_ADD8);
   1463     _CASE(R_ADD16);
   1464     _CASE(R_ADD32);
   1465     _CASE(R_ADD64);
   1466     _CASE(R_SUB8);
   1467     _CASE(R_SUB16);
   1468     _CASE(R_SUB32);
   1469     _CASE(R_SUB64);
   1470     _CASE(R_RV_ALIGN);
   1471     _CASE(R_RV_RVC_BRANCH);
   1472     _CASE(R_RV_RVC_JUMP);
   1473     _CASE(R_RV_RELAX);
   1474     _CASE(R_SUB6);
   1475     _CASE(R_SET6);
   1476     _CASE(R_SET_ULEB128);
   1477     _CASE(R_SUB_ULEB128);
   1478     _CASE(R_WASM_FUNCIDX);
   1479     _CASE(R_WASM_TABLEIDX);
   1480     _CASE(R_WASM_MEMOFS);
   1481     _CASE(R_WASM_TYPEIDX);
   1482     _CASE(R_COFF_SECREL);
   1483     _CASE(R_COFF_SECTION);
   1484     _CASE(R_COFF_AARCH64_SECREL_LOW12A);
   1485     _CASE(R_COFF_AARCH64_SECREL_HIGH12A);
   1486     _CASE(R_COFF_AARCH64_SECREL_LOW12L);
   1487     _CASE(R_ARM_THM_CALL);
   1488     _CASE(R_ARM_THM_JUMP24);
   1489     _CASE(R_ARM_THM_JUMP19);
   1490     _CASE(R_ARM_THM_MOVW_ABS_NC);
   1491     _CASE(R_ARM_THM_MOVT_ABS);
   1492     _CASE(R_ARM_THM_MOVW_PREL_NC);
   1493     _CASE(R_ARM_THM_MOVT_PREL);
   1494     _CASE(R_ARM_TLS_LE32);
   1495 #undef _CASE
   1496   }
   1497   return "UNKNOWN";
   1498 }
   1499 
   1500 /* ---- shared string-table builder (see obj.h ObjStrtab) ------------------ */
   1501 
   1502 static u32 obj_strtab_hash(const char* s, u32 len) {
   1503   u32 h = 2166136261u; /* FNV-1a */
   1504   for (u32 i = 0; i < len; ++i) {
   1505     h ^= (u8)s[i];
   1506     h *= 16777619u;
   1507   }
   1508   return h;
   1509 }
   1510 
   1511 void obj_strtab_init(ObjStrtab* t, Heap* h, int dedup) {
   1512   memset(t, 0, sizeof *t);
   1513   t->heap = h;
   1514   t->dedup = dedup ? 1u : 0u;
   1515 }
   1516 
   1517 static void obj_strtab_reserve(ObjStrtab* t, u32 extra) {
   1518   u32 ncap;
   1519   u8* nd;
   1520   if (t->len + extra <= t->cap) return;
   1521   ncap = t->cap ? t->cap : 256u;
   1522   while (t->len + extra > ncap) ncap *= 2u;
   1523   nd = (u8*)t->heap->alloc(t->heap, ncap, 1);
   1524   if (t->data) {
   1525     memcpy(nd, t->data, t->len);
   1526     t->heap->free(t->heap, t->data, t->cap);
   1527   }
   1528   t->data = nd;
   1529   t->cap = ncap;
   1530 }
   1531 
   1532 void obj_strtab_put_raw(ObjStrtab* t, const void* bytes, u32 n) {
   1533   if (!n) return;
   1534   obj_strtab_reserve(t, n);
   1535   memcpy(t->data + t->len, bytes, n);
   1536   t->len += n;
   1537 }
   1538 
   1539 static void obj_strtab_index_grow(ObjStrtab* t) {
   1540   u32 ncap = t->scap ? t->scap * 2u : 256u;
   1541   u32 mask = ncap - 1u;
   1542   ObjStrtabEnt* ns = (ObjStrtabEnt*)t->heap->alloc(t->heap, sizeof(*ns) * ncap,
   1543                                                    _Alignof(ObjStrtabEnt));
   1544   memset(ns, 0, sizeof(*ns) * ncap);
   1545   for (u32 i = 0; i < t->scap; ++i) {
   1546     u32 j;
   1547     if (!t->slots[i].len) continue;
   1548     j = t->slots[i].hash & mask;
   1549     while (ns[j].len) j = (j + 1u) & mask;
   1550     ns[j] = t->slots[i];
   1551   }
   1552   if (t->slots) t->heap->free(t->heap, t->slots, sizeof(*t->slots) * t->scap);
   1553   t->slots = ns;
   1554   t->scap = ncap;
   1555 }
   1556 
   1557 static u32 obj_strtab_append(ObjStrtab* t, const char* s, u32 len) {
   1558   u32 off = t->len;
   1559   obj_strtab_reserve(t, len + 1u);
   1560   memcpy(t->data + off, s, len);
   1561   t->data[off + len] = 0;
   1562   t->len += len + 1u;
   1563   return off;
   1564 }
   1565 
   1566 u32 obj_strtab_add(ObjStrtab* t, const char* s, u32 len) {
   1567   u32 h, mask, j, off;
   1568   if (len == 0) return 0;
   1569   if (!t->dedup) return obj_strtab_append(t, s, len);
   1570   if ((t->sused + 1u) * 4u >= t->scap * 3u) obj_strtab_index_grow(t);
   1571   h = obj_strtab_hash(s, len);
   1572   mask = t->scap - 1u;
   1573   j = h & mask;
   1574   while (t->slots[j].len) {
   1575     ObjStrtabEnt* e = &t->slots[j];
   1576     if (e->hash == h && e->len == len && memcmp(t->data + e->off, s, len) == 0)
   1577       return e->off;
   1578     j = (j + 1u) & mask;
   1579   }
   1580   off = obj_strtab_append(t, s, len);
   1581   t->slots[j].hash = h;
   1582   t->slots[j].off = off;
   1583   t->slots[j].len = len;
   1584   t->sused++;
   1585   return off;
   1586 }
   1587 
   1588 u32 obj_strtab_size(const ObjStrtab* t) { return t->len; }
   1589 u8* obj_strtab_data(ObjStrtab* t) { return t->data; }
   1590 
   1591 void obj_strtab_fini(ObjStrtab* t) {
   1592   if (t->data) t->heap->free(t->heap, t->data, t->cap);
   1593   if (t->slots) t->heap->free(t->heap, t->slots, sizeof(*t->slots) * t->scap);
   1594   memset(t, 0, sizeof *t);
   1595 }