kit

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

link_jit.c (64459B)


      1 /* JIT mapper. Takes a resolved LinkImage, reserves a fresh writable
      2  * region (via env->execmem), copies segments, applies relocations
      3  * against the runtime base, flips to final permissions, and returns
      4  * an owning KitJit handle.
      5  *
      6  * Lookup is by interned Sym name (object-local handles never escape).
      7  * Inspector entries (kit_jit_view, _addr_to_sym, _sym_iter_*) are
      8  * stubbed for now — the linker can land without dragging the inspector
      9  * surface up. */
     10 
     11 #include <kit/core.h>
     12 #include <kit/jit.h>
     13 #include <kit/object.h>
     14 #include <string.h>
     15 
     16 #include "core/buf.h"
     17 #include "core/bytes.h"
     18 #include "core/heap.h"
     19 #include "core/metrics.h"
     20 #include "core/pool.h"
     21 #include "core/slice.h"
     22 #include "core/util.h"
     23 #include "link/link.h"
     24 #include "link/link_arch.h"
     25 #include "link/link_internal.h"
     26 #include "link/link_reloc_desc.h"
     27 #include "obj/obj.h"
     28 
     29 /* Defined in src/api/objfile.c — exposes the underlying ObjBuilder of a
     30  * KitObjFile and the internal-only helpers for allocating an empty
     31  * KitObjFile (used by the JIT debug-view builder). */
     32 ObjBuilder* kit_objfile_builder(const KitObjFile*);
     33 KitObjFile* kit_objfile_internal_new(const KitContext* ctx,
     34                                      KitTargetSpec target, KitObjFmt fmt);
     35 void kit_objfile_internal_free(KitObjFile*);
     36 #define jit_view_objfile_free(f) kit_objfile_internal_free(f)
     37 
     38 static const KitJitHost* jit_host_from_linker(Linker* l, Compiler* c) {
     39   const KitJitHost* host = l ? l->jit_host : NULL;
     40   if (!host)
     41     compiler_panic(c, SRCLOC_NONE,
     42                    "kit_jit: link jit host is required for JIT");
     43   return host;
     44 }
     45 
     46 static const KitExecMem* require_execmem_h(const KitJitHost* host,
     47                                            Compiler* c) {
     48   const KitExecMem* m = host ? host->execmem : NULL;
     49   if (!m || !m->reserve || !m->protect || !m->release) {
     50     compiler_panic(c, SRCLOC_NONE,
     51                    "kit_jit: jit host execmem is required for JIT");
     52   }
     53   return m;
     54 }
     55 
     56 static u64 jit_page_size_h(const KitJitHost* host, Compiler* c) {
     57   const KitExecMem* m = require_execmem_h(host, c);
     58   return m->page_size ? (u64)m->page_size : 0x4000u;
     59 }
     60 
     61 /* Per-segment runtime placement. Each segment gets its own host
     62  * reservation. Code segments come back as a dual mapping (write alias /
     63  * runtime alias backing the same physical pages); data and rodata are
     64  * single-aliased. We populate via the write alias, encode runtime
     65  * addresses against the runtime alias, then protect the runtime alias to
     66  * its final perms. No virtual page is ever simultaneously writable and
     67  * executable. */
     68 struct KitJit {
     69   Compiler* c;
     70   LinkImage* image;
     71   /* Single contiguous reservation covering every segment.  All segments
     72    * are sub-ranges of this region — runtime/write aliases are derived
     73    * by offsetting against (image-vaddr - image_base).  Keeping them
     74    * inside one mapping guarantees inter-segment displacements stay in
     75    * range for ADRP (±4 GiB) and CALL26 (±128 MiB), which would
     76    * otherwise depend on the OS placing independent mmap'd segments
     77    * close together. */
     78   KitExecMemRegion master;
     79   u64 image_base;         /* page-aligned image vaddr that maps to master.* */
     80   KitExecMemRegion* segs; /* one per image->nsegments; views into master */
     81   u32 nsegs;
     82   /* DWARF view, lazily constructed on first kit_jit_view call.  Built
     83    * over a private Compiler so its string pools and the new ObjBuilder
     84    * are owned end-to-end by the view; freed in kit_jit_free.  NULL
     85    * means "not yet built"; view_built distinguishes "tried and gave up"
     86    * (multi-input v1, etc.) from "untried". */
     87   KitObjFile* view;
     88   Linker* linker; /* kept alive for append-time resolver/input context */
     89   u64 append_cursor[SEG_NBUCKETS];
     90   u64 append_limit[SEG_NBUCKETS];
     91   u64 generation;
     92   /* Borrowed JIT host: execmem vtable.  Mirrors the Linker's
     93    * jit_host so the JIT's lifetime accessors don't need to walk back to
     94    * the Linker (which is still live behind jit->linker but may be
     95    * decoupled in incremental flows). */
     96   const KitJitHost* jit_host;
     97   u8 view_built;
     98   u8 pad[7];
     99 };
    100 
    101 #define JIT_APPEND_RX_SLACK (64ull * 1024ull * 1024ull)
    102 #define JIT_APPEND_R_SLACK (16ull * 1024ull * 1024ull)
    103 #define JIT_APPEND_RW_SLACK (16ull * 1024ull * 1024ull)
    104 #define JIT_APPEND_TLS_SLACK (4ull * 1024ull * 1024ull)
    105 
    106 /* RISC-V PCREL_LO12_I/S target a local "anchor" symbol whose vaddr is
    107  * the address of the paired AUIPC's PCREL_HI20 (or GOT_HI20) site.
    108  * Defined below vaddr_to_runtime. */
    109 static i64 jit_rv_pcrel_lo12_disp(LinkImage* img, KitExecMemRegion* segs,
    110                                   u64 auipc_image_vaddr);
    111 
    112 static int perms_for(u32 secflags) {
    113   int p = KIT_PROT_READ;
    114   if (secflags & SF_EXEC) p |= KIT_PROT_EXEC;
    115   if (secflags & SF_WRITE) p |= KIT_PROT_WRITE;
    116   /* JIT TLS storage is the single live instance (the JIT is single-threaded),
    117    * accessed and mutated in place, so the TLS segment must be writable — even
    118    * though the AOT image treats .tdata/.tbss as a read-only init template that
    119    * each thread copies. */
    120   if (secflags & SF_TLS) p |= KIT_PROT_WRITE;
    121   return p;
    122 }
    123 
    124 /* A reference the single-threaded JIT drops because the format's TLS access
    125  * idiom that materializes its target is relaxed in-image. On Windows/COFF the
    126  * idiom reads TEB.ThreadLocalStoragePointer, indexes it by the module's
    127  * `_tls_index`, then `+ SECREL(var)` — there is no loaded PE for the OS to
    128  * assign a `_tls_index`, so jit_tls_le_relax rewrites that load away and the
    129  * `_tls_index` ADRP/PC32 relocs must not be applied. The format authority
    130  * (obj_format_jit_drops_symbol_ref) owns which symbol that is. */
    131 static int jit_tls_is_index_ref(Compiler* c, const LinkSymbol* tgt) {
    132   return tgt && obj_format_jit_drops_symbol_ref(c, tgt->name);
    133 }
    134 
    135 /* TLS Local-Exec classifier for the JIT reloc passes, entirely flag-driven:
    136  * the per-arch tp-relative idiom and the aa64 COFF SECREL12A pair carry
    137  * RELOC_IS_TLS_LE; x86-64's generic COFF SECREL (RELOC_IS_SECREL) is a TLS
    138  * access exactly when its target is a TLS symbol. */
    139 static int jit_reloc_is_tls_le(Compiler* c, RelocKind k, u8 tgt_kind) {
    140   return reloc_kind_is_tls_le(c, k) ||
    141          (reloc_kind_is_secrel(c, k) && tgt_kind == SK_TLS);
    142 }
    143 
    144 /* Find the segment that contains image-relative `vaddr` and return its
    145  * runtime address (the runtime alias, not the write alias). Up to 3
    146  * segments after layout, so a linear scan is fine.
    147  *
    148  * The two-pass shape lets a vaddr that lands exactly on a segment's
    149  * one-past-end boundary (e.g. `__fini_array_end` when .fini_array is
    150  * the last section in its segment) still resolve, while preferring an
    151  * exact start-of-next-segment match when segments happen to abut. */
    152 static uintptr_t vaddr_to_runtime(const LinkImage* img,
    153                                   const KitExecMemRegion* segs, u64 vaddr) {
    154   u32 i;
    155   for (i = 0; i < img->nsegments; ++i) {
    156     const LinkSegment* s = &img->segments[i];
    157     u64 lo = s->vaddr;
    158     u64 hi = lo + s->mem_size;
    159     if (vaddr >= lo && vaddr < hi)
    160       return (uintptr_t)segs[i].runtime + (uintptr_t)(vaddr - lo);
    161   }
    162   for (i = 0; i < img->nsegments; ++i) {
    163     const LinkSegment* s = &img->segments[i];
    164     u64 hi = s->vaddr + s->mem_size;
    165     if (vaddr == hi) return (uintptr_t)segs[i].runtime + (uintptr_t)s->mem_size;
    166   }
    167   return 0;
    168 }
    169 
    170 /* Same as above but returns the WRITE-alias address — used to determine
    171  * the byte position at which to apply a relocation. The PC-relative
    172  * arithmetic in link_reloc_apply uses the runtime address; the bytes
    173  * themselves get patched at the matching offset of the write alias. */
    174 static uintptr_t vaddr_to_write(const LinkImage* img,
    175                                 const KitExecMemRegion* segs, u64 vaddr) {
    176   u32 i;
    177   for (i = 0; i < img->nsegments; ++i) {
    178     const LinkSegment* s = &img->segments[i];
    179     u64 lo = s->vaddr;
    180     u64 hi = lo + s->mem_size;
    181     if (vaddr >= lo && vaddr < hi)
    182       return (uintptr_t)segs[i].write + (uintptr_t)(vaddr - lo);
    183   }
    184   for (i = 0; i < img->nsegments; ++i) {
    185     const LinkSegment* s = &img->segments[i];
    186     u64 hi = s->vaddr + s->mem_size;
    187     if (vaddr == hi) return (uintptr_t)segs[i].write + (uintptr_t)s->mem_size;
    188   }
    189   return 0;
    190 }
    191 
    192 /* See forward decl above.  Find the paired AUIPC PCREL_HI20/GOT_HI20
    193  * reloc whose write_vaddr matches the anchor target, recompute the
    194  * displacement using runtime addresses, and return it so the
    195  * link_reloc_apply LO12_I/S encoder produces matching low-12 bits.
    196  *
    197  * Linear scan; reloc counts are small even for full JIT images. */
    198 static i64 jit_rv_pcrel_lo12_disp(LinkImage* img, KitExecMemRegion* segs,
    199                                   u64 auipc_image_vaddr) {
    200   u32 n = LinkRelocs_count(&img->relocs);
    201   u32 i;
    202   for (i = 0; i < n; ++i) {
    203     const LinkRelocApply* hi = LinkRelocs_at(&img->relocs, i);
    204     const LinkSymbol* hi_tgt;
    205     u64 hi_S, hi_P;
    206     if (!reloc_kind_is_pcrel_anchor(img->c, hi->kind)) continue;
    207     if (hi->write_vaddr != auipc_image_vaddr) continue;
    208     hi_tgt = LinkSyms_at(&img->syms, hi->target - 1);
    209     if (!hi_tgt) continue;
    210     if (hi_tgt->kind == SK_ABS)
    211       hi_S = hi_tgt->vaddr;
    212     else
    213       hi_S = (u64)vaddr_to_runtime(img, segs, hi_tgt->vaddr);
    214     hi_P = (u64)vaddr_to_runtime(img, segs, hi->write_vaddr);
    215     return (i64)hi_S + hi->addend - (i64)hi_P;
    216   }
    217   compiler_panic(img->c, SRCLOC_NONE,
    218                  "kit_jit: RV PCREL_LO12 at 0x%llx has no paired PCREL_HI20",
    219                  (unsigned long long)auipc_image_vaddr);
    220   return 0;
    221 }
    222 
    223 static void jit_copy_input_section_bytes(LinkImage* img,
    224                                          const KitExecMemRegion* segs) {
    225   Compiler* c = img->c;
    226   Linker* l = img->linker;
    227   u32 i;
    228   for (i = 0; i < img->nsections; ++i) {
    229     const LinkSection* ls = &img->sections[i];
    230     const LinkSegment* seg;
    231     ObjBuilder* ob;
    232     const Section* s;
    233     u32 input_idx;
    234     u8* dst;
    235     if (ls->input_id == LINK_INPUT_NONE) continue;
    236     input_idx = ls->input_id - 1u;
    237     if (ls->segment_id == LINK_SEG_NONE || ls->segment_id > img->nsegments)
    238       compiler_panic(c, SRCLOC_NONE,
    239                      "kit_jit_from_image: section has invalid segment");
    240     seg = &img->segments[ls->segment_id - 1u];
    241     ob = (input_idx < img->dbg_objs_n) ? img->dbg_objs[input_idx] : NULL;
    242     if (!ob && l && input_idx < LinkInputs_count(&l->inputs))
    243       ob = LinkInputs_at(&l->inputs, input_idx)->obj;
    244     if (!ob)
    245       compiler_panic(c, SRCLOC_NONE,
    246                      "kit_jit_from_image: input section bytes unavailable");
    247     s = obj_section_get(ob, ls->obj_section_id);
    248     if (!s || s->sem == SSEM_NOBITS || s->kind == SEC_BSS) continue;
    249     if (ls->size == 0) continue;
    250     dst = (u8*)segs[seg->id - 1u].write + (size_t)(ls->vaddr - seg->vaddr);
    251     metrics_count(c, "jit.input_section_bytes", ls->size);
    252     buf_read(&s->bytes, (u32)ls->obj_offset, dst, (size_t)ls->size);
    253   }
    254 }
    255 
    256 /* Opaque user data for the JitRelaxCtx.resolve_pair callback: the arch hook
    257  * (RISC-V PCREL_LO12) gets the paired AUIPC's runtime displacement without
    258  * reaching into LinkImage itself. */
    259 typedef struct {
    260   LinkImage* img;
    261   KitExecMemRegion* segs;
    262 } JitPairResolve;
    263 
    264 static i64 jit_resolve_pcrel_pair(void* user, u64 anchor_vaddr) {
    265   JitPairResolve* p = (JitPairResolve*)user;
    266   return jit_rv_pcrel_lo12_disp(p->img, p->segs, anchor_vaddr);
    267 }
    268 
    269 /* Apply one relocation against the JIT runtime base.  Shared by the bulk
    270  * from-image pass and the incremental-append pass so their reloc handling can
    271  * never drift.  Patch-site bytes go through the write alias; PC-relative
    272  * arithmetic uses the runtime alias.
    273  *
    274  * Order mirrors the resolution chain: drop Windows `_tls_index` refs, relax
    275  * Local-Exec TLS via the arch idiom hook, then resolve S and offer the reloc
    276  * to the arch's JIT-relaxation hook (TLV / GOT / weak-undef / RISC-V pcrel
    277  * pairing) before the ordinary apply.  `got_relaxed` is 1 on the append path
    278  * (no real GOT was built) and 0 for the full link (where .got slots exist). */
    279 static void jit_apply_reloc(Compiler* c, LinkImage* img, KitExecMemRegion* segs,
    280                             const LinkRelocApply* r, int got_relaxed) {
    281   const LinkSymbol* tgt = LinkSyms_at(&img->syms, r->target - 1);
    282   const LinkArchDesc* d;
    283   JitPairResolve pair;
    284   JitRelaxCtx ctx;
    285   u64 S, P;
    286   u8* P_bytes;
    287 
    288   /* Windows TLS idiom `_tls_index` references are dropped: the access is
    289    * relaxed to in-image addressing, so the index load is rewritten away. */
    290   if (jit_tls_is_index_ref(c, tgt)) return;
    291 
    292   /* Local-Exec TLS -> in-image addressing (single-threaded JIT).  The per-arch
    293    * idiom rewrite lives behind LinkArchDesc.jit_tls_le_relax; this stays
    294    * arch-neutral, classifying ELF via RELOC_IS_TLS_LE and the COFF Windows
    295    * idiom via its terminal SECREL reloc(s). */
    296   if (jit_reloc_is_tls_le(c, r->kind, tgt->kind)) {
    297     u64 storage =
    298         (tgt->kind == SK_ABS)
    299             ? tgt->vaddr + (u64)r->addend
    300             : (u64)vaddr_to_runtime(img, segs, tgt->vaddr) + (u64)r->addend;
    301     u8* bytes = (u8*)vaddr_to_write(img, segs, r->write_vaddr);
    302     u64 site_pc = (u64)vaddr_to_runtime(img, segs, r->write_vaddr);
    303     d = link_arch_desc_for(c);
    304     if (!d || !d->jit_tls_le_relax || !bytes)
    305       compiler_panic(c, SRCLOC_NONE,
    306                      "kit_jit: target has no TLS Local-Exec relaxation");
    307     d->jit_tls_le_relax(c, r->kind, bytes, storage, site_pc);
    308     return;
    309   }
    310 
    311   /* extern resolver result OR true absolute symbol — vaddr already holds the
    312    * runtime address; otherwise map the image vaddr to its runtime alias. */
    313   S = (tgt->kind == SK_ABS) ? tgt->vaddr
    314                             : (u64)vaddr_to_runtime(img, segs, tgt->vaddr);
    315   P = (u64)vaddr_to_runtime(img, segs, r->write_vaddr);
    316   P_bytes = (u8*)vaddr_to_write(img, segs, r->write_vaddr);
    317   if (!P_bytes)
    318     compiler_panic(c, SRCLOC_NONE, "kit_jit: relocation site is unmapped");
    319 
    320   /* Arch JIT relaxation: collapse the runtime indirection (Mach-O TLV
    321    * descriptor call, GOT load on the append path, weak-undef zeroing, RISC-V
    322    * PCREL_LO12 anchor recompute) to direct in-image addressing.  Returns 1 if
    323    * it owned the kind; otherwise fall through to the ordinary apply. */
    324   pair.img = img;
    325   pair.segs = segs;
    326   ctx.site = P_bytes;
    327   ctx.S = S;
    328   ctx.addend = r->addend;
    329   ctx.site_pc = P;
    330   ctx.weak_undef_zero =
    331       (tgt->bind == SB_WEAK && tgt->kind == SK_ABS && tgt->vaddr == 0);
    332   ctx.got_relaxed = got_relaxed;
    333   ctx.resolve_pair = jit_resolve_pcrel_pair;
    334   ctx.resolve_user = &pair;
    335   ctx.anchor_vaddr = tgt->vaddr;
    336   d = link_arch_desc_for(c);
    337   if (d && d->jit_reloc_relax && d->jit_reloc_relax(c, r->kind, &ctx)) return;
    338 
    339   link_reloc_apply(c, r->kind, P_bytes, S, r->addend, P);
    340 }
    341 
    342 KitJit* kit_jit_from_image(LinkImage* img) {
    343   Compiler* c;
    344   Heap* heap;
    345   const KitExecMem* mem;
    346   const KitJitHost* host;
    347   KitJit* jit;
    348   KitExecMemRegion* segs;
    349   KitExecMemRegion master;
    350   u64 page;
    351   u64 image_base = (u64)-1;
    352   u64 image_end = 0;
    353   u64 append_start;
    354   u64 append_total;
    355   u64 master_size;
    356   int needs_exec = 0;
    357   int needs_input_materialize = 0;
    358   u32 i;
    359 
    360   if (!img) return NULL;
    361   c = img->c;
    362   heap = img->heap;
    363   host = jit_host_from_linker(img->linker, c);
    364   mem = require_execmem_h(host, c);
    365   page = jit_page_size_h(host, c);
    366 
    367   /* Compute the span all segments must fit inside.  Layout guarantees
    368    * each segment's vaddr is page-aligned (layout_segments / layout_got
    369    * align via ALIGN_UP at the page size), so the offset within the
    370    * master mapping is (vaddr - image_base).  An empty JIT image has no
    371    * initial segments, but still owns append slack so the debugger can
    372    * start from an empty translation unit and append code later. */
    373   if (img->nsegments == 0) {
    374     image_base = page;
    375     image_end = page;
    376     needs_exec = 1;
    377   } else {
    378     for (i = 0; i < img->nsegments; ++i) {
    379       const LinkSegment* seg = &img->segments[i];
    380       u64 hi = ALIGN_UP(seg->vaddr + seg->mem_size, page);
    381       if (seg->vaddr < image_base) image_base = seg->vaddr;
    382       if (hi > image_end) image_end = hi;
    383       if (seg->flags & SF_EXEC) needs_exec = 1;
    384     }
    385   }
    386   if (image_base & (page - 1u))
    387     compiler_panic(c, SRCLOC_NONE,
    388                    "kit_jit_from_image: segment vaddr not page-aligned");
    389   append_start = image_end;
    390   append_total = JIT_APPEND_RX_SLACK + JIT_APPEND_R_SLACK +
    391                  JIT_APPEND_RW_SLACK + JIT_APPEND_TLS_SLACK;
    392   master_size = (image_end - image_base) + append_total;
    393   metrics_count(c, "jit.master_size", master_size);
    394   metrics_count(c, "jit.nsegments", img->nsegments);
    395 
    396   /* One reservation for the whole image.  Requesting EXEC if any segment
    397    * is exec triggers the dual-mapping path on Apple silicon; non-exec
    398    * regions just leave the alternate alias unused.  Per-segment final
    399    * perms are applied via mem->protect on sub-ranges below. */
    400   {
    401     int master_prot = KIT_PROT_READ | KIT_PROT_WRITE;
    402     if (needs_exec) master_prot |= KIT_PROT_EXEC;
    403     metrics_scope_begin(c, "jit.reserve");
    404     if (mem->reserve(mem->user, (size_t)master_size, master_prot, &master) !=
    405         0) {
    406       compiler_panic(c, SRCLOC_NONE,
    407                      "kit_jit_from_image: execmem.reserve failed");
    408     }
    409     metrics_scope_end(c, "jit.reserve");
    410   }
    411 
    412   segs = NULL;
    413   if (img->nsegments) {
    414     segs = (KitExecMemRegion*)heap->alloc(heap, sizeof(*segs) * img->nsegments,
    415                                           _Alignof(KitExecMemRegion));
    416     if (!segs) {
    417       mem->release(mem->user, &master);
    418       compiler_panic(c, SRCLOC_NONE,
    419                      "kit_jit_from_image: oom on segment table");
    420     }
    421     memset(segs, 0, sizeof(*segs) * img->nsegments);
    422   }
    423 
    424   /* Subdivide the master mapping.  segs[i].token stays NULL — the
    425    * master reservation owns the underlying mapping and is released in
    426    * kit_jit_free. */
    427   for (i = 0; i < img->nsegments; ++i) {
    428     const LinkSegment* seg = &img->segments[i];
    429     u64 off = seg->vaddr - image_base;
    430     size_t mlen = (size_t)ALIGN_UP(seg->mem_size, page);
    431     segs[i].write = (u8*)master.write + off;
    432     segs[i].runtime = (u8*)master.runtime + off;
    433     segs[i].size = mlen;
    434     segs[i].token = NULL;
    435   }
    436   /* Master reservation is zeroed; BSS is naturally zero. */
    437 
    438   /* Copy synthetic segment buffers to their write aliases.  In JIT mode,
    439    * ordinary input-section segments intentionally have NULL payload buffers
    440    * and are materialized directly from input Buf chunks below. */
    441   metrics_scope_begin(c, "jit.copy_segments");
    442   for (i = 0; i < img->nsegments; ++i) {
    443     const LinkSegment* seg = &img->segments[i];
    444     if (seg->file_size == 0) continue;
    445     if (!img->segment_bytes[i]) {
    446       needs_input_materialize = 1;
    447       continue;
    448     }
    449     metrics_count(c, "jit.segment_bytes", seg->file_size);
    450     memcpy(segs[i].write, img->segment_bytes[i], (size_t)seg->file_size);
    451   }
    452   if (needs_input_materialize) {
    453     if (!img->linker || !img->linker->jit_mode)
    454       compiler_panic(c, SRCLOC_NONE,
    455                      "kit_jit_from_image: segment bytes are not materialized");
    456     jit_copy_input_section_bytes(img, segs);
    457   }
    458   metrics_scope_end(c, "jit.copy_segments");
    459 
    460   /* Apply relocations. The patch site bytes go through the write
    461    * alias; PC-relative arithmetic uses the runtime alias address. */
    462   metrics_scope_begin(c, "jit.apply_relocs");
    463   for (i = 0; i < LinkRelocs_count(&img->relocs); ++i) {
    464     /* got_relaxed=0: the full from-image link ran GOT layout, so .got slots
    465      * exist and GOT loads apply normally (S points at the slot). */
    466     jit_apply_reloc(c, img, segs, LinkRelocs_at(&img->relocs, i), 0);
    467   }
    468   metrics_scope_end(c, "jit.apply_relocs");
    469 
    470   /* Flip the runtime alias of each segment to its final perms. The
    471    * write alias is unaffected (still RW for any segment we'd want to
    472    * write to from JITed code; for EXEC segments the write alias is
    473    * orphaned after this point — JITed code is not expected to write
    474    * to its own code).  Each segs[i] is a sub-range of master; protect
    475    * accepts arbitrary [addr,size) inside the reservation. */
    476   metrics_scope_begin(c, "jit.protect");
    477   for (i = 0; i < img->nsegments; ++i) {
    478     const LinkSegment* seg = &img->segments[i];
    479     if (mem->protect(mem->user, segs[i].runtime, segs[i].size,
    480                      perms_for(seg->flags)) != 0) {
    481       mem->release(mem->user, &master);
    482       if (segs) heap->free(heap, segs, sizeof(*segs) * img->nsegments);
    483       compiler_panic(c, SRCLOC_NONE,
    484                      "kit_jit_from_image: execmem.protect failed");
    485     }
    486   }
    487   if (append_total) {
    488     void* slack_rt =
    489         (u8*)master.runtime + (uintptr_t)(append_start - image_base);
    490     (void)mem->protect(mem->user, slack_rt, (size_t)append_total,
    491                        KIT_PROT_NONE);
    492   }
    493   metrics_scope_end(c, "jit.protect");
    494 
    495   /* Flush only the segments that will be executed, against the
    496    * runtime alias (the address from which the CPU will fetch). */
    497   if (mem->flush_icache) {
    498     metrics_scope_begin(c, "jit.flush_icache");
    499     for (i = 0; i < img->nsegments; ++i) {
    500       const LinkSegment* seg = &img->segments[i];
    501       if (seg->flags & SF_EXEC)
    502         mem->flush_icache(mem->user, segs[i].runtime, segs[i].size);
    503     }
    504     metrics_scope_end(c, "jit.flush_icache");
    505   }
    506 
    507   /* IFUNC pre-resolution: now that code is executable and slots are
    508    * writable, walk every (resolver_vaddr, slot_vaddr) pair, call the
    509    * resolver in-process, and store its return value into the slot's
    510    * runtime address.  The iplt stub then loads the slot and tail-
    511    * calls the chosen implementation on every invocation. */
    512   if (img->niplt) {
    513     typedef void* (*ResolverFn)(void);
    514     for (i = 0; i < img->niplt; ++i) {
    515       u64 resolver_vaddr = img->iplt_pairs[2u * i + 0];
    516       u64 slot_vaddr = img->iplt_pairs[2u * i + 1];
    517       uintptr_t resolver_rt = vaddr_to_runtime(img, segs, resolver_vaddr);
    518       uintptr_t slot_rt = vaddr_to_runtime(img, segs, slot_vaddr);
    519       void* impl;
    520       if (!resolver_rt || !slot_rt)
    521         compiler_panic(c, SRCLOC_NONE,
    522                        "kit_jit: iplt vaddr does not map to runtime");
    523       impl = ((ResolverFn)resolver_rt)();
    524       *(void**)(uintptr_t)slot_rt = impl;
    525     }
    526   }
    527 
    528   jit = (KitJit*)heap->alloc(heap, sizeof(*jit), _Alignof(KitJit));
    529   if (!jit) {
    530     mem->release(mem->user, &master);
    531     if (segs) heap->free(heap, segs, sizeof(*segs) * img->nsegments);
    532     compiler_panic(c, SRCLOC_NONE, "kit_jit_from_image: oom on jit handle");
    533   }
    534   jit->c = c;
    535   jit->image = img;
    536   jit->master = master;
    537   jit->image_base = image_base;
    538   jit->segs = segs;
    539   jit->nsegs = img->nsegments;
    540   jit->view = NULL;
    541   jit->linker = img->linker;
    542   jit->append_cursor[SEG_RX] = append_start;
    543   jit->append_limit[SEG_RX] = jit->append_cursor[SEG_RX] + JIT_APPEND_RX_SLACK;
    544   jit->append_cursor[SEG_R] = jit->append_limit[SEG_RX];
    545   jit->append_limit[SEG_R] = jit->append_cursor[SEG_R] + JIT_APPEND_R_SLACK;
    546   jit->append_cursor[SEG_RW] = jit->append_limit[SEG_R];
    547   jit->append_limit[SEG_RW] = jit->append_cursor[SEG_RW] + JIT_APPEND_RW_SLACK;
    548   jit->append_cursor[SEG_TLS] = jit->append_limit[SEG_RW];
    549   jit->append_limit[SEG_TLS] =
    550       jit->append_cursor[SEG_TLS] + JIT_APPEND_TLS_SLACK;
    551   jit->generation = 0;
    552   jit->view_built = 0u;
    553   jit->jit_host = host;
    554 
    555   /* Take ownership of the image: undefer it from the compiler so a
    556    * future panic doesn't reap something we still hold. */
    557   if (img->deferred) {
    558     compiler_undefer(c, img->deferred);
    559     img->deferred = NULL;
    560   }
    561   if (jit->linker && jit->linker->deferred) {
    562     compiler_undefer(c, jit->linker->deferred);
    563     jit->linker->deferred = NULL;
    564   }
    565 
    566   /* Run .init_array constructors in forward order. */
    567   {
    568     typedef void (*VoidFn)(void);
    569     void* p_start = kit_jit_lookup(jit, KIT_SLICE_LIT("__init_array_start"));
    570     void* p_end = kit_jit_lookup(jit, KIT_SLICE_LIT("__init_array_end"));
    571     if (p_start && p_end) {
    572       VoidFn* fn = (VoidFn*)p_start;
    573       VoidFn* end = (VoidFn*)p_end;
    574       metrics_scope_begin(c, "jit.ctors");
    575       for (; fn != end; ++fn)
    576         if (*fn) (*fn)();
    577       metrics_scope_end(c, "jit.ctors");
    578     }
    579   }
    580 
    581   return jit;
    582 }
    583 
    584 const KitExecMem* kit_jit_image_execmem(KitJit* jit) {
    585   if (!jit || !jit->jit_host) return NULL;
    586   return jit->jit_host->execmem;
    587 }
    588 
    589 void kit_jit_free(KitJit* jit) {
    590   Heap* heap;
    591   const KitExecMem* mem;
    592   if (!jit) return;
    593   heap = (Heap*)jit->c->ctx->heap;
    594   mem = jit->jit_host ? jit->jit_host->execmem : NULL;
    595   /* The debug view (if built) is closed first — it owns a private
    596    * Compiler whose pools must be released before the image's
    597    * referenced builders are freed in link_image_free. */
    598   if (jit->view) {
    599     jit_view_objfile_free(jit->view);
    600     jit->view = NULL;
    601   }
    602   /* segs[] are views into master — release master only. */
    603   if (mem && mem->release && jit->master.size) {
    604     mem->release(mem->user, &jit->master);
    605   }
    606   if (jit->segs) {
    607     heap->free(heap, jit->segs, sizeof(*jit->segs) * jit->nsegs);
    608   }
    609   if (jit->image) {
    610     /* link_image_free unfederes (no-op now) and releases storage. */
    611     link_image_free(jit->image);
    612   }
    613   if (jit->linker) {
    614     link_free(jit->linker);
    615     jit->linker = NULL;
    616   }
    617   heap->free(heap, jit, sizeof(*jit));
    618 }
    619 
    620 void* kit_jit_lookup(KitJit* jit, KitSlice name) {
    621   Sym sym;
    622   LinkSymId id;
    623   const LinkSymbol* s;
    624   if (!jit || !name.s) return NULL;
    625   /* C-symbol mangling lives in obj_format_c_mangle so JIT lookups by
    626    * source-level name find the symbol regardless of target format.
    627    * name.s is NUL-terminated (KIT_SLICE_LIT / kit_slice_cstr / interned).
    628    */
    629   sym = obj_format_c_mangle(jit->c, name.s);
    630   id = symhash_get(&jit->image->globals, sym);
    631   if (id == LINK_SYM_NONE) return NULL;
    632   s = LinkSyms_at(&jit->image->syms, id - 1);
    633   if (!s) return NULL;
    634   if (!s->defined) return NULL;
    635   if (s->kind == SK_ABS) return (void*)(uintptr_t)s->vaddr;
    636   return (void*)vaddr_to_runtime(jit->image, jit->segs, s->vaddr);
    637 }
    638 
    639 void* kit_jit_tls_addr(KitJit* jit, void* sym_addr) {
    640   /* Resolve a thread-local's in-image storage from the address its SYMBOL
    641    * resolves to (single-threaded JIT: the in-image .tdata/.tbss is the single
    642    * instance — see the TLVP relaxation in kit_jit_from_image).
    643    *
    644    *  - Mach-O: the symbol resolves to a 24-byte TLV descriptor; its +16 slot
    645    *    holds the variable's in-image storage address (the normal R_ABS64
    646    *    against the storage symbol, applied during the reloc pass).
    647    *  - ELF/COFF: the symbol already resolves to the in-image storage.
    648    *
    649    * Returns NULL for anything outside this image's reservation — e.g. a
    650    * foreign/extern thread-local resolved through the host (dlsym) — which we
    651    * must not dereference. The interpreter relies on this NULL to diagnose
    652    * cleanly rather than treat a foreign pointer as our storage. */
    653   u8* p = (u8*)sym_addr;
    654   u8* lo = (u8*)jit->master.runtime;
    655   u8* hi = lo + jit->master.size;
    656   if (!jit || !p) return NULL;
    657   if (!jit->master.runtime || p < lo || p >= hi) return NULL;
    658   if (obj_format_tls_via_descriptor(jit->c)) {
    659     void* storage;
    660     if (p + 24u > hi) return NULL;
    661     memcpy(&storage, p + 16u, sizeof storage);
    662     return storage;
    663   }
    664   return p;
    665 }
    666 
    667 uint64_t kit_jit_generation(KitJit* jit) { return jit ? jit->generation : 0; }
    668 
    669 typedef struct JitAppendSec {
    670   ObjSecId obj_sec;
    671   ObjAtomId obj_atom;
    672   LinkSectionId link_sec;
    673   LinkSegmentId link_seg;
    674   SegBucket bucket;
    675   u64 obj_offset;
    676   u64 vaddr;
    677   u64 size;
    678   u64 file_size;
    679   u32 align;
    680   u16 flags;
    681   u16 sem;
    682   Sym name;
    683 } JitAppendSec;
    684 
    685 SEGVEC_DEFINE(JitAppendSecs, JitAppendSec, 4);
    686 
    687 static InputMap jit_input_map_alloc(KitJit* jit, ObjBuilder* ob) {
    688   InputMap m;
    689   ObjSymIter* it;
    690   ObjSymEntry e;
    691   u32 nsyms = 0;
    692   it = obj_symiter_new(ob);
    693   while (obj_symiter_next(it, &e)) ++nsyms;
    694   obj_symiter_free(it);
    695   link_input_map_alloc(jit->image, &m, ob, nsyms + 1u);
    696   return m;
    697 }
    698 
    699 static void jit_invalidate_view(KitJit* jit) {
    700   if (jit->view) {
    701     jit_view_objfile_free(jit->view);
    702     jit->view = NULL;
    703   }
    704   jit->view_built = 0u;
    705 }
    706 
    707 /* Incremental-append reloc apply.  Delegates to the shared jit_apply_reloc
    708  * with got_relaxed=1: the append path does not re-run GOT layout, so GOT loads
    709  * in the appended object are relaxed to direct in-image addressing. */
    710 static void jit_apply_one_reloc(KitJit* jit, const LinkRelocApply* r) {
    711   jit_apply_reloc(jit->c, jit->image, jit->segs, r, 1);
    712 }
    713 
    714 static void jit_append_obj_inner(KitJit* jit, ObjBuilder* ob) {
    715   LinkImage* img = jit->image;
    716   Heap* h = img->heap;
    717   const KitExecMem* mem = require_execmem_h(jit->jit_host, jit->c);
    718   u64 page = jit_page_size_h(jit->jit_host, jit->c);
    719   u32 old_nsections = img->nsections;
    720   u32 old_nsegments = img->nsegments;
    721   u32 old_nsegs = jit->nsegs;
    722   u32 old_nmaps = img->ninput_maps;
    723   u32 old_dbg_n = img->dbg_objs_n;
    724   u32 old_nrelocs = LinkRelocs_count(&img->relocs);
    725   u64 old_cursor[SEG_NBUCKETS];
    726   InputMap m;
    727   JitAppendSecs secs;
    728   u32 nsecs = 0;
    729   u32 obj_sec_count;
    730   u64 cursor[SEG_NBUCKETS];
    731   LinkInputId new_input_id;
    732   u32 new_input_idx;
    733   ObjSymIter* it;
    734   ObjSymEntry e;
    735   u32 i;
    736 
    737   JitAppendSecs_init(&secs, h);
    738   for (i = 0; i < SEG_NBUCKETS; ++i) old_cursor[i] = jit->append_cursor[i];
    739 
    740   if (!jit || !ob || !jit->linker || obj_compiler(ob) != jit->c)
    741     compiler_panic(jit ? jit->c : NULL, SRCLOC_NONE,
    742                    "kit_jit_append_obj: object is not appendable");
    743 
    744   /* Preflight duplicate strong definitions and unresolved references before
    745    * mutating image-visible state. */
    746   it = obj_symiter_new(ob);
    747   while (obj_symiter_next(it, &e)) {
    748     const ObjSym* s = e.sym;
    749     if (link_sym_is_spurious_undef(s)) continue;
    750     if (link_sym_is_def(s) && s->name != 0 &&
    751         (s->bind == SB_GLOBAL || s->bind == SB_WEAK)) {
    752       LinkSymId existing = symhash_get(&img->globals, s->name);
    753       if (existing != LINK_SYM_NONE) {
    754         LinkSymbol* prev = LinkSyms_at(&img->syms, existing - 1);
    755         if (prev && prev->defined &&
    756             link_bind_strength((u8)s->bind) == link_bind_strength(SB_GLOBAL) &&
    757             link_bind_strength(prev->bind) == link_bind_strength(SB_GLOBAL)) {
    758           Slice nm_s = pool_slice(jit->c->global, s->name);
    759           const char* nm = nm_s.s;
    760           size_t namelen = nm_s.len;
    761           obj_format_demangle_c(jit->c, &nm, &namelen);
    762           obj_symiter_free(it);
    763           compiler_panic(jit->c, SRCLOC_NONE,
    764                          "kit_jit_append_obj: duplicate global '%.*s'",
    765                          (int)namelen, nm);
    766         }
    767       }
    768     } else if (!link_sym_is_def(s) && s->name != 0) {
    769       LinkSymId hit = symhash_get(&img->globals, s->name);
    770       int ok = 0;
    771       if (hit != LINK_SYM_NONE) {
    772         LinkSymbol* def = LinkSyms_at(&img->syms, hit - 1);
    773         if (def && def->defined) ok = 1;
    774       }
    775       if (!ok) {
    776         ObjSymIter* it2 = obj_symiter_new(ob);
    777         ObjSymEntry e2;
    778         while (obj_symiter_next(it2, &e2)) {
    779           const ObjSym* d = e2.sym;
    780           if (d && d->name == s->name && link_sym_is_def(d) &&
    781               (d->bind == SB_GLOBAL || d->bind == SB_WEAK)) {
    782             ok = 1;
    783             break;
    784           }
    785         }
    786         obj_symiter_free(it2);
    787       }
    788       if (!ok && jit->linker->resolver) {
    789         Slice nm_s = pool_slice(jit->c->global, s->name);
    790         if (jit->linker->resolver(jit->linker->resolver_user, nm_s)) ok = 1;
    791       }
    792       if (!ok && s->bind == SB_WEAK) ok = 1;
    793       /* Format pseudo-symbols the JIT image satisfies internally (Mach-O
    794        * __tlv_bootstrap, COFF _tls_index): the obj layer is the single
    795        * arbiter so src/link names no pseudo-symbol. */
    796       if (!ok && obj_format_jit_undef_internal(jit->c, s->name)) ok = 1;
    797       if (!ok) {
    798         Slice nm_s = pool_slice(jit->c->global, s->name);
    799         const char* nm = nm_s.s;
    800         size_t nlen = nm_s.len;
    801         obj_format_demangle_c(jit->c, &nm, &nlen);
    802         obj_symiter_free(it);
    803         compiler_panic(jit->c, SRCLOC_NONE,
    804                        "kit_jit_append_obj: undefined reference to '%.*s'",
    805                        (int)nlen, nm);
    806       }
    807     }
    808   }
    809   obj_symiter_free(it);
    810 
    811   m = jit_input_map_alloc(jit, ob);
    812   obj_sec_count = obj_section_count(ob);
    813   for (i = 0; i < SEG_NBUCKETS; ++i) cursor[i] = jit->append_cursor[i];
    814 
    815   for (i = 1; i < obj_sec_count; ++i) {
    816     const Section* s = obj_section_get(ob, (ObjSecId)i);
    817     u32 first = 0, count = 1, ai;
    818     int has_atoms;
    819     if (!s || !link_section_kept(s)) continue;
    820     has_atoms = link_input_section_has_atoms(&m, (ObjSecId)i);
    821     if (has_atoms) link_input_section_atoms(&m, (ObjSecId)i, &first, &count);
    822     for (ai = 0; ai < count; ++ai) {
    823       ObjAtomId aid =
    824           has_atoms ? m.section_atom_ids[first + ai] : OBJ_ATOM_NONE;
    825       const ObjAtom* atom = has_atoms ? obj_atom_get(ob, aid) : NULL;
    826       SegBucket b;
    827       u64 obj_offset;
    828       u64 size;
    829       u64 vaddr;
    830       JitAppendSec* ps;
    831       u32 sec_idx;
    832       if (has_atoms) {
    833         if (!atom || atom->removed) continue;
    834         obj_offset = atom->offset;
    835         size = atom->size;
    836       } else {
    837         obj_offset = 0u;
    838         size = link_section_size_for_link(s);
    839       }
    840       if (size == 0) continue;
    841       b = link_bucket_for(s->flags);
    842       vaddr = ALIGN_UP(cursor[b], (u64)(s->align ? s->align : 1u));
    843       if (vaddr + size > jit->append_limit[b])
    844         compiler_panic(jit->c, SRCLOC_NONE,
    845                        "kit_jit_append_obj: append slack exhausted");
    846       ps = JitAppendSecs_push(&secs, &sec_idx);
    847       if (!ps)
    848         compiler_panic(jit->c, SRCLOC_NONE,
    849                        "kit_jit_append_obj: oom on section plan");
    850       ps->obj_sec = (ObjSecId)i;
    851       ps->obj_atom = has_atoms ? aid : OBJ_ATOM_NONE;
    852       ps->link_sec = (LinkSectionId)(old_nsections + sec_idx + 1u);
    853       ps->link_seg = (LinkSegmentId)(old_nsegments + sec_idx + 1u);
    854       ps->bucket = b;
    855       ps->obj_offset = obj_offset;
    856       ps->vaddr = vaddr;
    857       ps->size = size;
    858       ps->file_size = (s->sem == SSEM_NOBITS || s->kind == SEC_BSS) ? 0 : size;
    859       ps->align = s->align ? s->align : 1u;
    860       ps->flags = s->flags;
    861       ps->sem = (s->kind == SEC_BSS) ? SSEM_NOBITS : s->sem;
    862       ps->name = s->name;
    863       if (has_atoms) {
    864         m.atom[aid] = ps->link_sec;
    865         if (m.section[i] == LINK_SEC_NONE) m.section[i] = ps->link_sec;
    866       } else {
    867         m.section[i] = ps->link_sec;
    868       }
    869       cursor[b] = vaddr + size;
    870     }
    871   }
    872   nsecs = JitAppendSecs_count(&secs);
    873   for (i = 0; i < SEG_NBUCKETS; ++i) jit->append_cursor[i] = cursor[i];
    874 
    875   it = obj_symiter_new(ob);
    876   while (obj_symiter_next(it, &e)) {
    877     const ObjSym* s = e.sym;
    878     int is_def;
    879     if (e.id >= m.nsym || link_sym_is_spurious_undef(s)) continue;
    880     is_def = link_sym_is_def(s);
    881     if (is_def && (s->bind == SB_GLOBAL || s->bind == SB_WEAK) &&
    882         s->name != 0) {
    883       LinkSymId existing = symhash_get(&img->globals, s->name);
    884       if (existing != LINK_SYM_NONE) {
    885         LinkSymbol* prev = LinkSyms_at(&img->syms, existing - 1);
    886         if (prev && prev->defined &&
    887             link_bind_strength((u8)s->bind) == link_bind_strength(SB_GLOBAL) &&
    888             link_bind_strength(prev->bind) == link_bind_strength(SB_GLOBAL)) {
    889           Slice nm_s = pool_slice(jit->c->global, s->name);
    890           const char* nm = nm_s.s;
    891           size_t namelen = nm_s.len;
    892           obj_format_demangle_c(jit->c, &nm, &namelen);
    893           obj_symiter_free(it);
    894           compiler_panic(jit->c, SRCLOC_NONE,
    895                          "kit_jit_append_obj: duplicate global '%.*s'",
    896                          (int)namelen, nm);
    897         }
    898         if (prev && prev->defined) {
    899           m.sym[e.id] = existing;
    900           continue;
    901         }
    902       }
    903     }
    904     m.sym[e.id] = (LinkSymId)(LinkSyms_count(&img->syms) + 1u);
    905     {
    906       LinkSymbol rec;
    907       memset(&rec, 0, sizeof(rec));
    908       rec.name = s->name;
    909       rec.input_id = (LinkInputId)(LinkInputs_count(&jit->linker->inputs) + 1u);
    910       rec.obj_sym = e.id;
    911       rec.section_id = LINK_SEC_NONE;
    912       rec.atom_id = is_def ? link_input_sym_atom(&m, e.id) : OBJ_ATOM_NONE;
    913       rec.value = s->value;
    914       rec.size = s->size;
    915       rec.common_align = (s->kind == SK_COMMON) ? (u32)s->common_align : 0u;
    916       rec.bind = (u8)s->bind;
    917       rec.kind = (u8)s->kind;
    918       rec.defined = (u8)is_def;
    919       if (is_def && s->section_id != OBJ_SEC_NONE &&
    920           s->section_id < m.nsection) {
    921         rec.section_id = link_input_symbol_section(&m, s, e.id);
    922         if (rec.section_id != LINK_SEC_NONE) {
    923           u32 sj;
    924           for (sj = 0; sj < nsecs; ++sj) {
    925             JitAppendSec* ps = JitAppendSecs_at(&secs, sj);
    926             if (ps && ps->link_sec == rec.section_id) {
    927               rec.vaddr = ps->vaddr + (s->value - ps->obj_offset);
    928               break;
    929             }
    930           }
    931         }
    932       } else if (s->kind == SK_ABS) {
    933         rec.vaddr = s->value;
    934       }
    935       m.sym[e.id] = link_append_symbol(img, &rec);
    936       if (is_def && (s->bind == SB_GLOBAL || s->bind == SB_WEAK) &&
    937           s->name != 0) {
    938         LinkSymId existing;
    939         if (symhash_insert(&img->globals, s->name, m.sym[e.id], &existing)) {
    940         } else {
    941           m.sym[e.id] = existing;
    942         }
    943       }
    944     }
    945   }
    946   obj_symiter_free(it);
    947 
    948   /* Resolve newly appended undefs before any bytes become executable. */
    949   for (i = 0; i < LinkSyms_count(&img->syms); ++i) {
    950     LinkSymbol* s = LinkSyms_at(&img->syms, i);
    951     if (s->input_id !=
    952         (LinkInputId)(LinkInputs_count(&jit->linker->inputs) + 1u))
    953       continue;
    954     if (s->defined) continue;
    955     if (s->name != 0) {
    956       LinkSymId hit = symhash_get(&img->globals, s->name);
    957       if (hit != LINK_SYM_NONE && hit != s->id) {
    958         LinkSymbol* def = LinkSyms_at(&img->syms, hit - 1);
    959         if (def->defined) {
    960           s->section_id = def->section_id;
    961           s->atom_id = def->atom_id;
    962           s->value = def->value;
    963           s->vaddr = def->vaddr;
    964           s->kind = def->kind;
    965           s->bind = def->bind;
    966           s->defined = 1;
    967           continue;
    968         }
    969       }
    970     }
    971     if (jit->linker->resolver && s->name != 0) {
    972       Slice nm_s = pool_slice(jit->c->global, s->name);
    973       void* p = jit->linker->resolver(jit->linker->resolver_user, nm_s);
    974       if (p) {
    975         s->kind = SK_ABS;
    976         s->vaddr = (u64)(uintptr_t)p;
    977         s->defined = 1;
    978         continue;
    979       }
    980     }
    981     if (s->bind == SB_WEAK) {
    982       s->kind = SK_ABS;
    983       s->vaddr = 0;
    984       s->defined = 1;
    985       continue;
    986     }
    987     /* Format pseudo-symbols the JIT image satisfies internally (Mach-O
    988      * __tlv_bootstrap, COFF _tls_index): define as 0 so resolution does not
    989      * reject them. Same obj-layer arbiter as the preflight pass above, so the
    990      * two no longer disagree (a non-weak undef _tls_index in an appended COFF
    991      * object used to pass preflight then panic here). */
    992     if (obj_format_jit_undef_internal(jit->c, s->name)) {
    993       s->kind = SK_ABS;
    994       s->vaddr = 0;
    995       s->defined = 1;
    996       continue;
    997     }
    998     if (s->name != 0) {
    999       Slice nm_s = pool_slice(jit->c->global, s->name);
   1000       const char* nm = nm_s.s;
   1001       size_t nlen = nm_s.len;
   1002       obj_format_demangle_c(jit->c, &nm, &nlen);
   1003       compiler_panic(jit->c, SRCLOC_NONE,
   1004                      "kit_jit_append_obj: undefined reference to '%.*s'",
   1005                      (int)nlen, nm);
   1006     }
   1007     compiler_panic(jit->c, SRCLOC_NONE,
   1008                    "kit_jit_append_obj: undefined anonymous symbol");
   1009   }
   1010 
   1011   new_input_id = link_add_obj(jit->linker, ob);
   1012   new_input_idx = new_input_id - 1u;
   1013 
   1014   {
   1015     InputMap* maps = (InputMap*)h->realloc(
   1016         h, img->input_maps, sizeof(*img->input_maps) * img->ninput_maps,
   1017         sizeof(*img->input_maps) * (new_input_idx + 1u), _Alignof(InputMap));
   1018     if (!maps)
   1019       compiler_panic(jit->c, SRCLOC_NONE,
   1020                      "kit_jit_append_obj: oom growing input maps");
   1021     img->input_maps = maps;
   1022     while (img->ninput_maps < new_input_idx) {
   1023       memset(&img->input_maps[img->ninput_maps], 0, sizeof(InputMap));
   1024       img->ninput_maps++;
   1025     }
   1026     img->input_maps[new_input_idx] = m;
   1027     img->ninput_maps = new_input_idx + 1u;
   1028   }
   1029 
   1030   if (nsecs) {
   1031     LinkSection* nsections = (LinkSection*)h->realloc(
   1032         h, img->sections, sizeof(*img->sections) * old_nsections,
   1033         sizeof(*img->sections) * (old_nsections + nsecs),
   1034         _Alignof(LinkSection));
   1035     LinkSegment* nsegments = (LinkSegment*)h->realloc(
   1036         h, img->segments, sizeof(*img->segments) * old_nsegments,
   1037         sizeof(*img->segments) * (old_nsegments + nsecs),
   1038         _Alignof(LinkSegment));
   1039     u8** nsegbytes = (u8**)h->realloc(
   1040         h, img->segment_bytes, sizeof(*img->segment_bytes) * old_nsegments,
   1041         sizeof(*img->segment_bytes) * (old_nsegments + nsecs), _Alignof(u8*));
   1042     size_t* nsegcaps = (size_t*)h->realloc(
   1043         h, img->segment_bytes_cap,
   1044         sizeof(*img->segment_bytes_cap) * old_nsegments,
   1045         sizeof(*img->segment_bytes_cap) * (old_nsegments + nsecs),
   1046         _Alignof(size_t));
   1047     KitExecMemRegion* njsegs = (KitExecMemRegion*)h->realloc(
   1048         h, jit->segs, sizeof(*jit->segs) * old_nsegs,
   1049         sizeof(*jit->segs) * (old_nsegs + nsecs), _Alignof(KitExecMemRegion));
   1050     if (!nsections || !nsegments || !nsegbytes || !nsegcaps || !njsegs)
   1051       compiler_panic(jit->c, SRCLOC_NONE,
   1052                      "kit_jit_append_obj: oom growing image tables");
   1053     img->sections = nsections;
   1054     img->segments = nsegments;
   1055     img->segment_bytes = nsegbytes;
   1056     img->segment_bytes_cap = nsegcaps;
   1057     jit->segs = njsegs;
   1058   }
   1059 
   1060   for (i = 0; i < nsecs; ++i) {
   1061     JitAppendSec* ps = JitAppendSecs_at(&secs, i);
   1062     LinkSection* ls = &img->sections[old_nsections + i];
   1063     LinkSegment* seg = &img->segments[old_nsegments + i];
   1064     KitExecMemRegion* js = &jit->segs[old_nsegs + i];
   1065     const Section* os = obj_section_get(ob, ps->obj_sec);
   1066     memset(ls, 0, sizeof(*ls));
   1067     ls->id = ps->link_sec;
   1068     ls->input_id = new_input_id;
   1069     ls->obj_section_id = ps->obj_sec;
   1070     ls->obj_atom_id = ps->obj_atom;
   1071     ls->segment_id = ps->link_seg;
   1072     ls->obj_offset = ps->obj_offset;
   1073     ls->input_offset = 0;
   1074     ls->file_offset = ps->vaddr;
   1075     ls->vaddr = ps->vaddr;
   1076     ls->size = ps->size;
   1077     ls->flags = ps->flags;
   1078     ls->align = ps->align;
   1079     ls->name = ps->name;
   1080     ls->sem = ps->sem;
   1081 
   1082     memset(seg, 0, sizeof(*seg));
   1083     seg->id = ps->link_seg;
   1084     seg->flags = SF_ALLOC;
   1085     if (ps->bucket == SEG_RX) seg->flags |= SF_EXEC;
   1086     if (ps->bucket == SEG_RW) seg->flags |= SF_WRITE;
   1087     if (ps->bucket == SEG_TLS) seg->flags |= SF_TLS;
   1088     seg->file_offset = ps->vaddr;
   1089     seg->vaddr = ps->vaddr;
   1090     seg->paddr = ps->vaddr;
   1091     seg->mem_size = ps->size;
   1092     seg->file_size = ps->file_size;
   1093     seg->align = (u32)page;
   1094     seg->nsections = 1;
   1095     img->segment_bytes[old_nsegments + i] = NULL;
   1096     img->segment_bytes_cap[old_nsegments + i] = 0;
   1097 
   1098     memset(js, 0, sizeof(*js));
   1099     js->write =
   1100         (u8*)jit->master.write + (uintptr_t)(ps->vaddr - jit->image_base);
   1101     js->runtime =
   1102         (u8*)jit->master.runtime + (uintptr_t)(ps->vaddr - jit->image_base);
   1103     js->size = (size_t)ps->size;
   1104     js->token = NULL;
   1105 
   1106     if (os && os->sem != SSEM_NOBITS && os->kind != SEC_BSS &&
   1107         os->bytes.total && ps->size)
   1108       buf_read(&os->bytes, (u32)ps->obj_offset, (u8*)js->write,
   1109                (size_t)ps->size);
   1110     img->nsections++;
   1111     img->nsegments++;
   1112     jit->nsegs++;
   1113   }
   1114 
   1115   {
   1116     u32 total = obj_reloc_total(ob);
   1117     for (i = 0; i < total; ++i) {
   1118       const Reloc* r = obj_reloc_at(ob, i);
   1119       LinkRelocApply rec;
   1120       LinkSectionId ls_id;
   1121       const LinkSection* ls;
   1122       if (!r || r->section_id == OBJ_SEC_NONE || r->section_id >= m.nsection)
   1123         continue;
   1124       ls_id = link_input_reloc_section(&m, r, i);
   1125       if (ls_id == LINK_SEC_NONE) continue;
   1126       if (r->sym == OBJ_SYM_NONE || r->sym >= m.nsym ||
   1127           m.sym[r->sym] == LINK_SYM_NONE)
   1128         continue;
   1129       ls = &img->sections[ls_id - 1];
   1130       memset(&rec, 0, sizeof(rec));
   1131       rec.input_id = new_input_id;
   1132       rec.section_id = r->section_id;
   1133       rec.link_section_id = ls_id;
   1134       rec.offset = r->offset - (u32)ls->obj_offset;
   1135       rec.width = reloc_kind_width(jit->c, (RelocKind)r->kind);
   1136       rec.write_vaddr = ls->vaddr + rec.offset;
   1137       rec.write_file_offset = rec.write_vaddr;
   1138       rec.kind = (RelocKind)r->kind;
   1139       rec.target = m.sym[r->sym];
   1140       rec.addend = r->addend;
   1141       *link_append_reloc_slot(img) = rec;
   1142     }
   1143   }
   1144 
   1145   for (i = old_nrelocs; i < LinkRelocs_count(&img->relocs); ++i) {
   1146     const LinkRelocApply* r = LinkRelocs_at(&img->relocs, i);
   1147     jit_apply_one_reloc(jit, r);
   1148   }
   1149 
   1150   for (i = old_nsegs; i < jit->nsegs; ++i) {
   1151     const LinkSegment* seg = &img->segments[i];
   1152     u64 page_lo = seg->vaddr & ~(page - 1u);
   1153     u64 page_hi = ALIGN_UP(seg->vaddr + seg->mem_size, page);
   1154     void* rt =
   1155         (u8*)jit->master.runtime + (uintptr_t)(page_lo - jit->image_base);
   1156     if (mem->protect(mem->user, rt, (size_t)(page_hi - page_lo),
   1157                      perms_for(seg->flags)) != 0) {
   1158       compiler_panic(jit->c, SRCLOC_NONE,
   1159                      "kit_jit_append_obj: execmem.protect failed");
   1160     }
   1161   }
   1162   if (mem->flush_icache) {
   1163     for (i = old_nsegs; i < jit->nsegs; ++i) {
   1164       const LinkSegment* seg = &img->segments[i];
   1165       if (seg->flags & SF_EXEC)
   1166         mem->flush_icache(mem->user, jit->segs[i].runtime, jit->segs[i].size);
   1167     }
   1168   }
   1169 
   1170   {
   1171     ObjBuilder** nd = (ObjBuilder**)h->realloc(
   1172         h, img->dbg_objs, sizeof(*img->dbg_objs) * old_dbg_n,
   1173         sizeof(*img->dbg_objs) * (new_input_idx + 1u), _Alignof(ObjBuilder*));
   1174     u8* no = (u8*)h->realloc(
   1175         h, img->dbg_objs_owned, sizeof(*img->dbg_objs_owned) * old_dbg_n,
   1176         sizeof(*img->dbg_objs_owned) * (new_input_idx + 1u), 1u);
   1177     if (!nd || !no)
   1178       compiler_panic(jit->c, SRCLOC_NONE,
   1179                      "kit_jit_append_obj: oom growing debug inputs");
   1180     img->dbg_objs = nd;
   1181     img->dbg_objs_owned = no;
   1182     while (img->dbg_objs_n < new_input_idx) {
   1183       img->dbg_objs[img->dbg_objs_n] = NULL;
   1184       img->dbg_objs_owned[img->dbg_objs_n] = 0u;
   1185       img->dbg_objs_n++;
   1186     }
   1187     img->dbg_objs[new_input_idx] = ob;
   1188     img->dbg_objs_owned[new_input_idx] = 0u;
   1189     img->dbg_objs_n = new_input_idx + 1u;
   1190   }
   1191 
   1192   jit_invalidate_view(jit);
   1193   jit->generation++;
   1194   JitAppendSecs_fini(&secs);
   1195   (void)old_cursor;
   1196   (void)old_nmaps;
   1197 }
   1198 
   1199 KitStatus kit_jit_publish(KitJit* jit, const KitJitPublishOptions* opts,
   1200                           KitJitPublishResult* result) {
   1201   PanicFrame panic;
   1202   Compiler* c;
   1203   KitLinkSession* link;
   1204   u32 i;
   1205 
   1206   if (result) memset(result, 0, sizeof(*result));
   1207   if (!jit || !opts || !opts->link) return KIT_INVALID;
   1208   if ((KitJitPublishKind)opts->kind != KIT_JIT_PUBLISH_APPEND_OBJECTS)
   1209     return KIT_UNSUPPORTED;
   1210   link = opts->link;
   1211   if (link->non_obj_inputs) return KIT_UNSUPPORTED;
   1212   c = jit->c;
   1213   compiler_panic_push(c, &panic);
   1214   if (setjmp(panic.env)) {
   1215     /* A failed append (e.g. duplicate global) panics from
   1216      * jit_append_obj_inner. compiler_run_cleanups fires the borrowed link
   1217      * session's deferred linker_cleanup, which frees its Linker; null the
   1218      * session's references so the caller's kit_link_session_free does not
   1219      * double-free. Mirrors link_session_guard's recovery. */
   1220     compiler_run_cleanups(c);
   1221     link->linker = NULL;
   1222     link->image = NULL;
   1223     compiler_panic_pop(c, &panic);
   1224     return KIT_ERR;
   1225   }
   1226   for (i = 0; i < link->npublish_objs; ++i)
   1227     jit_append_obj_inner(jit, link->publish_objs[i]);
   1228   compiler_panic_pop(c, &panic);
   1229   if (result) result->generation = jit->generation;
   1230   return KIT_OK;
   1231 }
   1232 
   1233 /* ---- inspector entries ---- */
   1234 
   1235 /* True if `name` (NUL-terminated) is a debug section the DWARF consumer
   1236  * (src/debug/dwarf_open.c) might read.  Everything else is skipped. */
   1237 static int jit_view_is_debug_name(const char* name) {
   1238   if (!name) return 0;
   1239   if (name[0] == '.' && name[1] == 'd' && name[2] == 'e' && name[3] == 'b' &&
   1240       name[4] == 'u' && name[5] == 'g' && name[6] == '_')
   1241     return 1; /* .debug_* */
   1242   /* .eh_frame is consulted by kit_dwarf for CFI unwinding when
   1243    * available; kit itself doesn't currently emit it, but inputs read
   1244    * from external .o files may carry it. */
   1245   if (name[0] == '.' && name[1] == 'e' && name[2] == 'h' && name[3] == '_' &&
   1246       name[4] == 'f' && name[5] == 'r' && name[6] == 'a' && name[7] == 'm' &&
   1247       name[8] == 'e' && name[9] == '\0')
   1248     return 1;
   1249   return 0;
   1250 }
   1251 
   1252 /* True if input `ii` carries any debug section that's worth surfacing.
   1253  * Cheap walk over the input's section table.  Sym values are pool-local,
   1254  * so name strings must be dereferenced through the input's *own*
   1255  * compiler pool — not the jit's pool. */
   1256 static int jit_view_input_has_debug(KitJit* jit, u32 ii) {
   1257   ObjBuilder* ob;
   1258   Pool* in_pool;
   1259   u32 nsec, k;
   1260   if (ii >= jit->image->dbg_objs_n) return 0;
   1261   ob = jit->image->dbg_objs[ii];
   1262   if (!ob) return 0;
   1263   in_pool = obj_compiler(ob)->global;
   1264   nsec = obj_section_count(ob);
   1265   for (k = 0; k < nsec; ++k) {
   1266     const Section* s = obj_section_get(ob, (ObjSecId)(k + 1));
   1267     const char* nm;
   1268     if (!s || !s->name) continue;
   1269     nm = pool_slice(in_pool, s->name).s;
   1270     if (jit_view_is_debug_name(nm)) return 1;
   1271   }
   1272   return 0;
   1273 }
   1274 
   1275 /* Resolve an input-local ObjSymId to the final image vaddr of the
   1276  * defining LinkSymbol, going through the per-input InputMap and the
   1277  * image's LinkSyms table.  Returns 0 if the symbol is undefined or the
   1278  * mapping is missing — debug relocations against unresolved symbols
   1279  * collapse to zero, which is the DWARF "absent" convention. */
   1280 static u64 jit_view_sym_vaddr(KitJit* jit, u32 ii, ObjSymId obj_sym) {
   1281   const InputMap* m;
   1282   LinkSymId lid;
   1283   const LinkSymbol* s;
   1284   if (obj_sym == OBJ_SYM_NONE) return 0;
   1285   if (ii >= jit->image->ninput_maps) return 0;
   1286   m = &jit->image->input_maps[ii];
   1287   if (!m->sym || obj_sym >= m->nsym) return 0;
   1288   lid = m->sym[obj_sym];
   1289   if (lid == LINK_SYM_NONE || lid > LinkSyms_count(&jit->image->syms)) return 0;
   1290   s = LinkSyms_at(&jit->image->syms, lid - 1);
   1291   if (!s || !s->defined) return 0;
   1292   return s->vaddr; /* image-relative — what DWARF was emitted in */
   1293 }
   1294 
   1295 /* Per-output-section state used while concatenating multi-input debug
   1296  * bytes.  Keyed by interned name in the view compiler's pool. */
   1297 typedef struct ViewSec {
   1298   Sym view_name; /* interned in view_ob's compiler pool */
   1299   ObjSecId view_id;
   1300   u32 cur_size; /* bytes currently in the view section */
   1301   /* Snapshot of cur_size taken at the start of processing the current
   1302    * input.  Reloc apply for SK_SECTION targets must use this value so
   1303    * that intra-input references resolve to *this* input's contribution,
   1304    * not bytes appended later from the same input. */
   1305   u32 snap;
   1306 } ViewSec;
   1307 
   1308 /* Find-or-create a ViewSec entry by debug-section name. */
   1309 static ViewSec* view_sec_for(KitJit* jit, ViewSec* tab, u32* ntab,
   1310                              u32* cap_inout, const char* name, u16 flags,
   1311                              u32 align, u32 entsize, ObjBuilder* view_ob,
   1312                              ViewSec** tab_out) {
   1313   Heap* h = (Heap*)jit->c->ctx->heap;
   1314   Pool* view_pool = obj_compiler(view_ob)->global;
   1315   Sym vn = pool_intern_cstr(view_pool, name);
   1316   u32 i;
   1317   for (i = 0; i < *ntab; ++i) {
   1318     if (tab[i].view_name == vn) {
   1319       if (tab_out) *tab_out = tab;
   1320       return &tab[i];
   1321     }
   1322   }
   1323   if (*ntab == *cap_inout) {
   1324     u32 ncap = *cap_inout ? *cap_inout * 2u : 4u;
   1325     ViewSec* na = (ViewSec*)h->realloc(h, tab, *cap_inout * sizeof(*tab),
   1326                                        ncap * sizeof(*tab), _Alignof(ViewSec));
   1327     if (!na) return NULL;
   1328     tab = na;
   1329     *cap_inout = ncap;
   1330     if (tab_out) *tab_out = tab;
   1331   } else if (tab_out) {
   1332     *tab_out = tab;
   1333   }
   1334   {
   1335     ViewSec* slot = &tab[(*ntab)++];
   1336     slot->view_name = vn;
   1337     slot->view_id = obj_section_ex(view_ob, vn, SEC_DEBUG, SSEM_PROGBITS, flags,
   1338                                    align ? align : 1u, entsize, 0, 0);
   1339     slot->cur_size = 0;
   1340     slot->snap = 0;
   1341     return slot;
   1342   }
   1343 }
   1344 
   1345 /* Find a ViewSec by view-pool name (no creation).  Returns NULL on miss. */
   1346 static ViewSec* view_sec_find(ViewSec* tab, u32 ntab, Sym view_name) {
   1347   u32 i;
   1348   for (i = 0; i < ntab; ++i)
   1349     if (tab[i].view_name == view_name) return &tab[i];
   1350   return NULL;
   1351 }
   1352 
   1353 /* Copy one debug section from input `ii` into the view, applying its
   1354  * relocations against either the view-relative section prefix (for
   1355  * SK_SECTION targets pointing at a debug section) or the final image
   1356  * vaddr (for code/data symbol targets like DW_AT_low_pc).
   1357  *
   1358  * `tab` is the find-or-create table of view sections, keyed by name;
   1359  * snapshots taken at the start of this input are read from it via
   1360  * view_sec_find. */
   1361 static void jit_view_copy_debug_section(KitJit* jit, u32 ii, ObjSecId in_sec_id,
   1362                                         ObjBuilder* view_ob, ViewSec* tab,
   1363                                         u32 ntab, ViewSec* out_vs) {
   1364   ObjBuilder* in_ob = jit->image->dbg_objs[ii];
   1365   const Section* in_sec = obj_section_get(in_ob, in_sec_id);
   1366   Pool* in_pool;
   1367   Pool* view_pool = obj_compiler(view_ob)->global;
   1368   Heap* h;
   1369   u32 nbytes, k, total_relocs;
   1370   u8* bytes;
   1371   if (!in_sec) return;
   1372   nbytes = in_sec->bytes.total;
   1373   if (nbytes == 0) return;
   1374   in_pool = obj_compiler(in_ob)->global;
   1375   h = (Heap*)jit->c->ctx->heap;
   1376   (void)in_pool;
   1377 
   1378   bytes = (u8*)h->alloc(h, nbytes, 1);
   1379   if (!bytes) return;
   1380   buf_flatten(&in_sec->bytes, bytes);
   1381 
   1382   /* Apply this section's relocations in place.  obj_reloc_at returns
   1383    * all relocations across the input; filter by section_id. */
   1384   total_relocs = obj_reloc_total(in_ob);
   1385   for (k = 0; k < total_relocs; ++k) {
   1386     const Reloc* r = obj_reloc_at(in_ob, k);
   1387     u64 S = 0;
   1388     int handled = 0;
   1389     if (!r || r->section_id != in_sec_id) continue;
   1390     if (r->offset >= nbytes) continue; /* malformed; skip */
   1391     /* SK_SECTION target → resolve against the per-input snapshot of the
   1392      * matching view section's prefix size.  This is what makes the
   1393      * concatenated multi-input view's cross-section offsets land in
   1394      * the right slot. */
   1395     if (r->sym != OBJ_SYM_NONE) {
   1396       const ObjSym* ts = obj_symbol_get(in_ob, r->sym);
   1397       if (ts && ts->kind == SK_SECTION && ts->section_id != OBJ_SEC_NONE) {
   1398         const Section* tsec = obj_section_get(in_ob, ts->section_id);
   1399         if (tsec && tsec->kind == SEC_DEBUG && tsec->name) {
   1400           Slice tnm_s = pool_slice(obj_compiler(in_ob)->global, tsec->name);
   1401           const char* tnm = tnm_s.s;
   1402           size_t tnlen = tnm_s.len;
   1403           if (tnm) {
   1404             Sym v_tn =
   1405                 pool_intern_slice(view_pool, (Slice){.s = tnm, .len = tnlen});
   1406             ViewSec* tgt = view_sec_find(tab, ntab, v_tn);
   1407             if (tgt) {
   1408               S = (u64)tgt->snap;
   1409               handled = 1;
   1410             }
   1411           }
   1412         }
   1413       }
   1414     }
   1415     if (!handled) S = jit_view_sym_vaddr(jit, ii, r->sym);
   1416     /* P is unused by ABS kinds; PC-relative debug-section relocs are
   1417      * not produced by kit's debug emitter, but if some external .o
   1418      * carried one against a debug section, P=0 would give a
   1419      * non-meaningful offset — acceptable for a viewer that only reads
   1420      * absolute address fields. */
   1421     link_reloc_apply(jit->c, (RelocKind)r->kind, bytes + r->offset, S,
   1422                      r->addend, 0);
   1423   }
   1424 
   1425   obj_write(view_ob, out_vs->view_id, bytes, nbytes);
   1426   out_vs->cur_size += nbytes;
   1427   h->free(h, bytes, nbytes);
   1428 }
   1429 
   1430 /* Build the view on first call.  Walks every input that carries a debug
   1431  * section and concatenates per-section bytes into the view's matching
   1432  * sections.  SK_SECTION relocations are resolved against the view-side
   1433  * prefix length snapshotted at the start of each input, so the merged
   1434  * CU2/CU3/... cross-section offsets land in the right slot. */
   1435 static KitObjFile* jit_view_build(KitJit* jit) {
   1436   KitObjFile* view;
   1437   ObjBuilder* view_ob;
   1438   ViewSec* tab = NULL;
   1439   u32 ntab = 0, cap = 0;
   1440   Heap* h;
   1441   u32 ii, k;
   1442   int any = 0;
   1443 
   1444   if (!jit->image || jit->image->dbg_objs_n == 0) return NULL;
   1445   for (ii = 0; ii < jit->image->dbg_objs_n; ++ii) {
   1446     if (jit_view_input_has_debug(jit, ii)) {
   1447       any = 1;
   1448       break;
   1449     }
   1450   }
   1451   if (!any) return NULL;
   1452 
   1453   view =
   1454       kit_objfile_internal_new(jit->c->ctx, jit->c->target, jit->c->target.obj);
   1455   if (!view) return NULL;
   1456   view_ob = kit_objfile_builder(view);
   1457   if (!view_ob) {
   1458     kit_objfile_internal_free(view);
   1459     return NULL;
   1460   }
   1461   h = (Heap*)jit->c->ctx->heap;
   1462 
   1463   for (ii = 0; ii < jit->image->dbg_objs_n; ++ii) {
   1464     ObjBuilder* in_ob;
   1465     u32 nsec;
   1466     if (!jit_view_input_has_debug(jit, ii)) continue;
   1467     in_ob = jit->image->dbg_objs[ii];
   1468 
   1469     /* Phase A: find-or-create every debug section this input contributes
   1470      * to, and snapshot cur_size as the per-input prefix. */
   1471     nsec = obj_section_count(in_ob);
   1472     for (k = 0; k < nsec; ++k) {
   1473       const Section* s = obj_section_get(in_ob, (ObjSecId)(k + 1));
   1474       const char* nm;
   1475       ViewSec* vs;
   1476       if (!s || !s->name) continue;
   1477       nm = pool_slice(obj_compiler(in_ob)->global, s->name).s;
   1478       if (!nm || !jit_view_is_debug_name(nm)) continue;
   1479       vs = view_sec_for(jit, tab, &ntab, &cap, nm, s->flags,
   1480                         s->align ? s->align : 1u, s->entsize, view_ob, &tab);
   1481       if (!vs) continue;
   1482     }
   1483     for (k = 0; k < ntab; ++k) tab[k].snap = tab[k].cur_size;
   1484 
   1485     /* Phase B: copy each debug section + apply relocs.  cur_size grows
   1486      * as bytes get appended; SK_SECTION resolution always reads `snap`
   1487      * (the start-of-input snapshot), so intra-input references inside
   1488      * any debug section still land in this input's slice. */
   1489     for (k = 0; k < nsec; ++k) {
   1490       const Section* s = obj_section_get(in_ob, (ObjSecId)(k + 1));
   1491       const char* nm;
   1492       size_t nlen = 0;
   1493       Sym v_nm;
   1494       ViewSec* vs;
   1495       if (!s || !s->name) continue;
   1496       {
   1497         Slice nm_s = pool_slice(obj_compiler(in_ob)->global, s->name);
   1498         nm = nm_s.s;
   1499         nlen = nm_s.len;
   1500       }
   1501       if (!nm || !jit_view_is_debug_name(nm)) continue;
   1502       v_nm = pool_intern_slice(obj_compiler(view_ob)->global,
   1503                                (Slice){.s = nm, .len = nlen});
   1504       vs = view_sec_find(tab, ntab, v_nm);
   1505       if (!vs) continue;
   1506       jit_view_copy_debug_section(jit, ii, (ObjSecId)(k + 1), view_ob, tab,
   1507                                   ntab, vs);
   1508     }
   1509   }
   1510 
   1511   if (tab) h->free(h, tab, sizeof(*tab) * cap);
   1512   obj_finalize(view_ob);
   1513   return view;
   1514 }
   1515 
   1516 const KitObjFile* kit_jit_view(KitJit* jit) {
   1517   if (!jit) return NULL;
   1518   if (jit->view_built) return jit->view;
   1519   jit->view = jit_view_build(jit);
   1520   jit->view_built = 1u;
   1521   return jit->view;
   1522 }
   1523 
   1524 /* True for symbol kinds the user-facing JIT inspector surfaces. Mapping
   1525  * symbols (SK_NOTYPE — aarch64 $x/$d), section/file/undef symbols are
   1526  * skipped. */
   1527 static int jit_sym_kind_visible(u8 k) {
   1528   return k == SK_FUNC || k == SK_OBJ || k == SK_COMMON || k == SK_TLS ||
   1529          k == SK_IFUNC || k == SK_ABS;
   1530 }
   1531 
   1532 /* Resolve a LinkSymbol's interned name to a stable C string, stripping
   1533  * the target format's C-mangling prefix (Mach-O's leading `_`) so the
   1534  * user sees the source-level name. */
   1535 static Slice jit_sym_name_slice(KitJit* jit, const LinkSymbol* s) {
   1536   Slice nm_s = pool_slice(jit->c->global, s->name);
   1537   const char* nm = nm_s.s;
   1538   size_t len = nm_s.len;
   1539   if (!nm) return KIT_SLICE_NULL;
   1540   obj_format_demangle_c(jit->c, &nm, &len);
   1541   nm_s.s = nm;
   1542   nm_s.len = len;
   1543   return nm_s;
   1544 }
   1545 
   1546 static uint64_t jit_sym_runtime_addr(KitJit* jit, const LinkSymbol* s) {
   1547   if (s->kind == SK_ABS) return s->vaddr;
   1548   return (uint64_t)vaddr_to_runtime(jit->image, jit->segs, s->vaddr);
   1549 }
   1550 
   1551 KitStatus kit_jit_addr_to_sym(KitJit* jit, uint64_t addr, KitSlice* name_out,
   1552                               uint64_t* off_out) {
   1553   u32 n;
   1554   u32 i;
   1555   const LinkSymbol* best = NULL;
   1556   uint64_t best_base = 0;
   1557   uint64_t best_off = (uint64_t)-1;
   1558   if (name_out) *name_out = KIT_SLICE_NULL;
   1559   if (off_out) *off_out = 0;
   1560   if (!jit) return KIT_INVALID;
   1561   n = LinkSyms_count(&jit->image->syms);
   1562   for (i = 0; i < n; ++i) {
   1563     LinkSymbol* s = LinkSyms_at(&jit->image->syms, i);
   1564     uint64_t base;
   1565     if (!s || !s->defined) continue;
   1566     if (!jit_sym_kind_visible(s->kind)) continue;
   1567     base = jit_sym_runtime_addr(jit, s);
   1568     if (!base) continue;
   1569     if (addr >= base && (s->size == 0 || addr < base + s->size)) {
   1570       uint64_t off = addr - base;
   1571       if (off < best_off ||
   1572           (off == best_off && best && best->size == 0 && s->size != 0)) {
   1573         best = s;
   1574         best_base = base;
   1575         best_off = off;
   1576       }
   1577     }
   1578   }
   1579   if (best) {
   1580     if (name_out) *name_out = jit_sym_name_slice(jit, best);
   1581     if (off_out) *off_out = addr - best_base;
   1582     return KIT_OK;
   1583   }
   1584   return KIT_NOT_FOUND;
   1585 }
   1586 
   1587 struct KitJitSymIter {
   1588   KitJit* jit;
   1589   u32 next;
   1590 };
   1591 
   1592 KitStatus kit_jit_sym_iter_new(KitJit* jit, KitJitSymIter** out) {
   1593   Heap* h;
   1594   KitJitSymIter* it;
   1595   if (!out) return KIT_INVALID;
   1596   *out = NULL;
   1597   if (!jit) return KIT_INVALID;
   1598   h = (Heap*)jit->c->ctx->heap;
   1599   it = (KitJitSymIter*)h->alloc(h, sizeof(*it), _Alignof(KitJitSymIter));
   1600   if (!it) return KIT_NOMEM;
   1601   it->jit = jit;
   1602   it->next = 0;
   1603   *out = it;
   1604   return KIT_OK;
   1605 }
   1606 
   1607 KitIterResult kit_jit_sym_iter_next(KitJitSymIter* it, KitJitSym* out) {
   1608   u32 n;
   1609   if (!it || !out) return KIT_ITER_ERROR;
   1610   n = LinkSyms_count(&it->jit->image->syms);
   1611   while (it->next < n) {
   1612     LinkSymbol* s = LinkSyms_at(&it->jit->image->syms, it->next++);
   1613     if (!s || !s->defined) continue;
   1614     if (!jit_sym_kind_visible(s->kind)) continue;
   1615     out->name = jit_sym_name_slice(it->jit, s);
   1616     out->addr = jit_sym_runtime_addr(it->jit, s);
   1617     out->size = s->size;
   1618     out->kind = (KitSymKind)s->kind;
   1619     return KIT_ITER_ITEM;
   1620   }
   1621   return KIT_ITER_END;
   1622 }
   1623 
   1624 void kit_jit_sym_iter_free(KitJitSymIter* it) {
   1625   Heap* h;
   1626   if (!it) return;
   1627   h = (Heap*)it->jit->c->ctx->heap;
   1628   h->free(h, it, sizeof(*it));
   1629 }
   1630 
   1631 /* ---- accessors for src/dbg/ ----
   1632  *
   1633  * The KitJit struct is private to this TU. The debugger session needs a
   1634  * way to validate that an address lies inside the JIT image (so it can
   1635  * reject breakpoint and read/write requests pointing outside the code
   1636  * region) and a way to read the image's target arch. These accessors give
   1637  * it just that, without exporting the segment table or the LinkImage. */
   1638 int kit_jit_image_contains(KitJit* jit, uint64_t runtime_addr) {
   1639   u32 i;
   1640   uintptr_t a;
   1641   if (!jit || !jit->segs) return 0;
   1642   a = (uintptr_t)runtime_addr;
   1643   for (i = 0; i < jit->nsegs; ++i) {
   1644     uintptr_t lo = (uintptr_t)jit->segs[i].runtime;
   1645     uintptr_t hi = lo + (uintptr_t)jit->segs[i].size;
   1646     if (a >= lo && a < hi) return 1;
   1647   }
   1648   return 0;
   1649 }
   1650 
   1651 /* runtime <-> image vaddr translation.  The pair is symmetric with
   1652  * vaddr_to_runtime above; exposed here so dwarf consumers and the
   1653  * driver can cross the boundary at every DWARF call.  Walks the
   1654  * segment table (at most a handful of entries) so callers can do this
   1655  * per stop without measurable cost. */
   1656 uint64_t kit_jit_runtime_to_image(KitJit* jit, uint64_t runtime_pc) {
   1657   u32 i;
   1658   uintptr_t a;
   1659   if (!jit || !jit->segs) return 0;
   1660   a = (uintptr_t)runtime_pc;
   1661   for (i = 0; i < jit->nsegs; ++i) {
   1662     uintptr_t lo = (uintptr_t)jit->segs[i].runtime;
   1663     uintptr_t hi = lo + (uintptr_t)jit->segs[i].size;
   1664     if (a >= lo && a < hi) {
   1665       const LinkSegment* s = &jit->image->segments[i];
   1666       return s->vaddr + (uint64_t)(a - lo);
   1667     }
   1668   }
   1669   /* One-past-end: lets a return-address that sits exactly at a
   1670    * segment's end-boundary still round-trip. */
   1671   for (i = 0; i < jit->nsegs; ++i) {
   1672     uintptr_t hi =
   1673         (uintptr_t)jit->segs[i].runtime + (uintptr_t)jit->segs[i].size;
   1674     if (a == hi) {
   1675       const LinkSegment* s = &jit->image->segments[i];
   1676       return s->vaddr + s->mem_size;
   1677     }
   1678   }
   1679   return 0;
   1680 }
   1681 
   1682 uint64_t kit_jit_image_to_runtime(KitJit* jit, uint64_t image_vaddr) {
   1683   uintptr_t rt;
   1684   if (!jit || !jit->segs) return 0;
   1685   rt = vaddr_to_runtime(jit->image, jit->segs, image_vaddr);
   1686   return (uint64_t)rt;
   1687 }
   1688 
   1689 KitArchKind kit_jit_image_arch(KitJit* jit) { return jit->c->target.arch; }
   1690 
   1691 Compiler* kit_jit_compiler(KitJit* jit) { return jit->c; }
   1692 
   1693 void kit_jit_run_dtors(KitJit* jit) {
   1694   typedef void (*VoidFn)(void);
   1695   void* p_start;
   1696   void* p_end;
   1697   if (!jit) return;
   1698   p_start = kit_jit_lookup(jit, KIT_SLICE_LIT("__fini_array_start"));
   1699   p_end = kit_jit_lookup(jit, KIT_SLICE_LIT("__fini_array_end"));
   1700   if (p_start && p_end) {
   1701     VoidFn* begin = (VoidFn*)p_start;
   1702     VoidFn* fn = (VoidFn*)p_end;
   1703     while (fn != begin) {
   1704       --fn;
   1705       if (*fn) (*fn)();
   1706     }
   1707   }
   1708 }