kit

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

commit 781d954928484c2614b1a43d73460b4c66b00212
parent bd3ed18a176f836e8e4ad927673b63da714c33e4
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sat,  9 May 2026 15:46:00 -0700

core: introduce SegVec; migrate typed object/image/linker tables

Append-only segmented array with stable element pointers. Pushing onto a
SegVec never moves existing elements: each segment is a separately
heap-allocated chunk of (1 << SEG_SHIFT) entries. Indexed access is O(1)
with two dependent loads (segment table → segment data); newly-pushed
slots are zero-initialized. The macro template emits a typed instance
plus _init/_fini/_at/_push/_count.

Migrated tables:

  ObjBuilder  sections (32/seg), symbols (64), relocs (64), groups (8)
  LinkImage   syms (64), relocs (128)
  Linker      inputs (16), archives (16)

These are all places where code stashes typed pointers (`Section*`,
`ObjSym*`, `LinkSymbol*`, `LinkRelocApply*`) into locals while the
table can grow further in the same phase. With VEC_GROW + realloc, that
pattern was a latent footgun (a doubling-realloc would dangle every
prior pointer); SegVec eliminates it. Transient list-y users (GcQueue,
GOT slot list, StrBuilder) keep VEC_GROW.

API change: obj_relocs(ob, _) — which returned a contiguous flat array
that callers indexed past the section_id boundary — is replaced by
obj_reloc_at(ob, idx) + obj_reloc_total(ob). The five call sites
(elf_emit, link_layout × 3, api/pipeline) walk the global flat list
via the per-iteration accessor; CfreeObjRelocIter simplified to a
single global cursor.

Tests: test-elf 37/37, test-link 112/112 (R/E/J), test-pp 82/82,
test-pp-err 15/15, test-ar 20/20.

Diffstat:
Msrc/api/pipeline.c | 34++++++++++++----------------------
Asrc/core/segvec.h | 126+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/link/link.c | 79++++++++++++++++++++++++++++++++++++++-----------------------------------------
Msrc/link/link_elf.c | 42++++++++++++++++++------------------------
Msrc/link/link_internal.h | 31+++++++++++++++++++------------
Msrc/link/link_jit.c | 9+++++----
Msrc/link/link_layout.c | 719+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/obj/elf_emit.c | 6++----
Msrc/obj/obj.c | 319++++++++++++++++++++++++++++++++++++-------------------------------------------
Msrc/obj/obj.h | 3++-
Mtest/elf/unit/smoke.c | 11++++-------
11 files changed, 878 insertions(+), 501 deletions(-)

diff --git a/src/api/pipeline.c b/src/api/pipeline.c @@ -385,6 +385,7 @@ int cfree_link_exe(CfreeCompiler* c, const CfreeLinkOptions* opts, } linker = build_linker(c, &opts->inputs); link_set_gc_sections(linker, opts->gc_sections); + link_set_emit_static_exe(linker, 1); image = link_resolve(linker); /* deferred-cleanup-registered */ link_emit_image_writer(image, out); link_image_free(image); /* undefers + frees */ @@ -1003,11 +1004,8 @@ static const char* reloc_kind_name(u16 kind) struct CfreeObjRelocIter { CfreeObjFile* file; - u32 nsec; /* obj_section_count snapshot */ - u32 sec_idx; /* 0-based; current section being walked */ - u32 rel_idx; /* index into current section's relocs */ - u32 rel_n; /* count for the current section */ - const Reloc* rels; /* relocs for the current section */ + u32 idx; /* index into the global flat reloc array */ + u32 total; /* obj_reloc_total snapshot */ }; CfreeObjRelocIter* cfree_obj_reliter_new(CfreeObjFile* f) @@ -1018,12 +1016,9 @@ CfreeObjRelocIter* cfree_obj_reliter_new(CfreeObjFile* f) h = (Heap*)f->compiler.env->heap; it = (CfreeObjRelocIter*)h->alloc(h, sizeof(*it), _Alignof(CfreeObjRelocIter)); if (!it) return NULL; - it->file = f; - it->nsec = obj_section_count(f->ob); - it->sec_idx = 0; - it->rel_idx = 0; - it->rel_n = 0; - it->rels = NULL; + it->file = f; + it->idx = 0; + it->total = obj_reloc_total(f->ob); return it; } @@ -1033,18 +1028,13 @@ int cfree_obj_reliter_next(CfreeObjRelocIter* it, CfreeObjReloc* out) const ObjSym* sym; if (!it || !out) return 0; + if (it->idx >= it->total) return 0; - /* Advance to a section that has relocs. */ - while (it->rel_idx >= it->rel_n) { - if (it->sec_idx >= it->nsec) return 0; - ++it->sec_idx; /* convert to 1-based ObjSecId for the call below */ - it->rel_n = obj_reloc_count(it->file->ob, (ObjSecId)it->sec_idx); - it->rels = it->rel_n ? obj_relocs(it->file->ob, (ObjSecId)it->sec_idx) : NULL; - it->rel_idx = 0; - } - - r = &it->rels[it->rel_idx++]; - out->section = (uint32_t)(it->sec_idx - 1); + r = obj_reloc_at(it->file->ob, it->idx++); + /* CfreeObjReloc.section is the 0-based external section index; + * Reloc.section_id is 1-based with id 0 reserved as "none". */ + out->section = r->section_id ? (uint32_t)(r->section_id - 1) + : CFREE_SECTION_NONE; out->offset = r->offset; out->addend = r->addend; out->kind = r->kind; diff --git a/src/core/segvec.h b/src/core/segvec.h @@ -0,0 +1,126 @@ +#ifndef CFREE_SEGVEC_H +#define CFREE_SEGVEC_H + +/* Append-only segmented array with stable element pointers. + * + * Unlike VEC_GROW (core/vec.h), pushing onto a SegVec never moves + * existing elements: each segment is a separately heap-allocated chunk + * of (1 << SEG_SHIFT) elements. Pointers returned by _push and _at + * remain valid for the SegVec's lifetime, which lets callers stash + * `T*` references across further mutations of the table. + * + * Use SegVec for typed object tables (ObjBuilder.sections, LinkImage.syms, + * etc.) where stable pointers matter. Use VEC_GROW for transient lists, + * byte builders, and any case where the consumer needs a contiguous + * `T* + len` range. + * + * SEGVEC_DEFINE(NAME, T, SEG_SHIFT) + * NAME — typedef name for the SegVec instance. + * T — element type. + * SEG_SHIFT — log2 of segment size in elements. Each segment holds + * (1 << SEG_SHIFT) entries. Pick smaller (4-5) for tables + * that typically hold few elements; larger (6-8) for + * tables that scale with input size. Memory waste is + * bounded by one partially-filled segment. + * + * Emits typedef NAME and these static functions: + * void NAME##_init (NAME*, Heap*) + * void NAME##_fini (NAME*) + * T* NAME##_at (const NAME*, u32 i) — NULL if out of range + * T* NAME##_push (NAME*, u32* idx_out) — stable T*; NULL on OOM + * u32 NAME##_count (const NAME*) + * + * Indexed access is O(1) via two dependent loads (segment table, then + * segment data). The compiler hoists the segment lookup out of inner + * loops when iteration is structured per-segment; for simple `for i in + * 0..count` loops over _at, the cost is one extra load per element. + * + * Newly-pushed slots are zero-initialized — no `memset(p, 0, sizeof *p)` + * boilerplate at call sites. + */ + +#include "core/core.h" +#include "core/heap.h" + +#include <string.h> + +#define SEGVEC_DEFINE(NAME, T, SEG_SHIFT_) \ + enum { \ + NAME##_SEG_SHIFT_ = (SEG_SHIFT_), \ + NAME##_SEG_SIZE_ = 1u << (SEG_SHIFT_), \ + NAME##_SEG_MASK_ = (1u << (SEG_SHIFT_)) - 1u \ + }; \ + \ + typedef struct NAME { \ + Heap* heap; \ + T** segs; /* segment table; doubling-grown */ \ + u32 nsegs; /* used segments */ \ + u32 segs_cap; /* allocated segment-table slots */ \ + u32 count; /* total elements pushed */ \ + } NAME; \ + \ + __attribute__((unused)) \ + static inline void NAME##_init(NAME* v, Heap* h) \ + { \ + v->heap = h; v->segs = NULL; \ + v->nsegs = 0; v->segs_cap = 0; v->count = 0; \ + } \ + \ + __attribute__((unused)) \ + static inline void NAME##_fini(NAME* v) \ + { \ + u32 i; \ + for (i = 0; i < v->nsegs; ++i) \ + v->heap->free(v->heap, v->segs[i], \ + sizeof(T) * NAME##_SEG_SIZE_); \ + if (v->segs) v->heap->free(v->heap, v->segs, \ + sizeof(T*) * v->segs_cap); \ + v->segs = NULL; v->nsegs = v->segs_cap = v->count = 0; \ + } \ + \ + __attribute__((unused)) \ + static inline u32 NAME##_count(const NAME* v) { return v->count; } \ + \ + __attribute__((unused)) \ + static inline T* NAME##_at(const NAME* v, u32 i) \ + { \ + if (i >= v->count) return NULL; \ + return &v->segs[i >> NAME##_SEG_SHIFT_][i & NAME##_SEG_MASK_]; \ + } \ + \ + /* Append a fresh zero-initialized slot. Returns the stable T*, or \ + * NULL on allocation failure. If idx_out is non-NULL, writes the new \ + * slot's index. */ \ + __attribute__((unused)) \ + static inline T* NAME##_push(NAME* v, u32* idx_out) \ + { \ + u32 i = v->count; \ + u32 s = i >> NAME##_SEG_SHIFT_; \ + u32 o = i & NAME##_SEG_MASK_; \ + T* slot; \ + if (s >= v->nsegs) { \ + T* newseg; \ + if (v->nsegs == v->segs_cap) { \ + u32 nc = v->segs_cap ? v->segs_cap * 2u : 4u; \ + T** ns = (T**)v->heap->realloc( \ + v->heap, v->segs, \ + sizeof(T*) * v->segs_cap, \ + sizeof(T*) * nc, _Alignof(T*)); \ + if (!ns) return NULL; \ + v->segs = ns; v->segs_cap = nc; \ + } \ + newseg = (T*)v->heap->alloc( \ + v->heap, sizeof(T) * NAME##_SEG_SIZE_, _Alignof(T)); \ + if (!newseg) return NULL; \ + memset(newseg, 0, sizeof(T) * NAME##_SEG_SIZE_); \ + v->segs[v->nsegs++] = newseg; \ + } \ + slot = &v->segs[s][o]; \ + v->count++; \ + if (idx_out) *idx_out = i; \ + return slot; \ + } \ + /* trailing struct decl swallows the macro-call's semicolon */ \ + struct NAME + +#endif diff --git a/src/link/link.c b/src/link/link.c @@ -37,15 +37,15 @@ static void linker_release(Linker* l) if (!l) return; /* Free the ObjBuilders we own (the ones we read from bytes inputs). * link_add_obj inputs are caller-owned and stay alive. */ - for (i = 0; i < l->ninputs; ++i) { - LinkInput* in = &l->inputs[i]; + for (i = 0; i < LinkInputs_count(&l->inputs); ++i) { + LinkInput* in = LinkInputs_at(&l->inputs, i); if (in->kind == LINK_INPUT_OBJ_BYTES && in->obj) obj_free(in->obj); } /* Free archive member ObjBuilders that were never pulled into inputs. * Pulled members had their `obj` pointer transferred and nulled, so * obj_free(NULL) is safe regardless. */ - for (i = 0; i < l->narchives; ++i) { - LinkArchive* ar = &l->archives[i]; + for (i = 0; i < LinkArchives_count(&l->archives); ++i) { + LinkArchive* ar = LinkArchives_at(&l->archives, i); for (j = 0; j < ar->nmembers; ++j) { if (ar->members[j].obj) obj_free(ar->members[j].obj); } @@ -53,10 +53,8 @@ static void linker_release(Linker* l) l->heap->free(l->heap, ar->members, sizeof(*ar->members) * ar->nmembers); } - if (l->archives) l->heap->free(l->heap, l->archives, - sizeof(*l->archives) * l->archives_cap); - if (l->inputs) l->heap->free(l->heap, l->inputs, - sizeof(*l->inputs) * l->inputs_cap); + LinkArchives_fini(&l->archives); + LinkInputs_fini (&l->inputs); l->heap->free(l->heap, l, sizeof(*l)); } @@ -70,6 +68,8 @@ Linker* link_new(Compiler* c) memset(l, 0, sizeof(*l)); l->c = c; l->heap = h; + LinkInputs_init (&l->inputs, h); + LinkArchives_init(&l->archives, h); l->entry_name = pool_intern_cstr(c->global, "_start"); /* Match the rest of libcfree's lifetime story: the new'd Linker is * registered for cleanup in case a panic fires before link_free. */ @@ -90,22 +90,23 @@ void link_free(Linker* l) /* ---- input registration ---- */ -static void inputs_grow(Linker* l) +static LinkInput* inputs_push(Linker* l, LinkInputId* id_out) { - if (VEC_GROW(l->heap, l->inputs, l->inputs_cap, l->ninputs + 1u)) - compiler_panic(l->c, no_loc(), "link: out of memory growing inputs"); + u32 idx; + LinkInput* in = LinkInputs_push(&l->inputs, &idx); + if (!in) compiler_panic(l->c, no_loc(), + "link: out of memory growing inputs"); + *id_out = (LinkInputId)(idx + 1u); + in->id = *id_out; + return in; } LinkInputId link_add_obj(Linker* l, ObjBuilder* ob) { - LinkInput* in; LinkInputId id; + LinkInput* in; if (!l || !ob) return LINK_INPUT_NONE; - inputs_grow(l); - id = (LinkInputId)(l->ninputs + 1); - in = &l->inputs[l->ninputs++]; - memset(in, 0, sizeof(*in)); - in->id = id; + in = inputs_push(l, &id); in->kind = LINK_INPUT_OBJ; in->obj = ob; return id; @@ -134,23 +135,13 @@ LinkInputId link_add_obj_bytes(Linker* l, const char* name, if (!ob) compiler_panic(l->c, no_loc(), "link_add_obj_bytes: read_elf returned NULL for '%s'", name ? name : "(unnamed)"); - inputs_grow(l); - id = (LinkInputId)(l->ninputs + 1); - in = &l->inputs[l->ninputs++]; - memset(in, 0, sizeof(*in)); - in->id = id; + in = inputs_push(l, &id); in->kind = LINK_INPUT_OBJ_BYTES; in->obj = ob; /* re-uses the ObjBuilder slot for ownership */ in->name = name ? pool_intern_cstr(l->c->global, name) : 0; return id; } -static void archives_grow(Linker* l) -{ - if (VEC_GROW(l->heap, l->archives, l->archives_cap, l->narchives + 1u)) - compiler_panic(l->c, no_loc(), - "link: out of memory growing archives"); -} LinkInputId link_add_archive_bytes(Linker* l, const char* name, const u8* data, size_t len, @@ -179,9 +170,9 @@ LinkInputId link_add_archive_bytes(Linker* l, const char* name, n = 0; while (cfree_ar_iter_next(&it, &mem)) ++n; - archives_grow(l); - ar = &l->archives[l->narchives++]; - memset(ar, 0, sizeof(*ar)); + ar = LinkArchives_push(&l->archives, NULL); + if (!ar) compiler_panic(l->c, no_loc(), + "link: out of memory growing archives"); ar->name = name ? pool_intern_cstr(l->c->global, name) : 0; ar->whole_archive = whole_archive; ar->link_mode = link_mode; @@ -215,7 +206,7 @@ LinkInputId link_add_archive_bytes(Linker* l, const char* name, ar->members[n].obj = ob; ++n; } - return (LinkInputId)l->narchives; /* opaque non-zero handle */ + return (LinkInputId)LinkArchives_count(&l->archives); /* opaque non-zero handle */ } void link_set_entry(Linker* l, const char* name) @@ -246,12 +237,18 @@ void link_set_gc_sections(Linker* l, int enable) * pass 0 unconditionally and we don't want to noise that. */ } +void link_set_emit_static_exe(Linker* l, int enable) +{ + if (!l) return; + l->emit_static_exe = enable ? 1 : 0; +} + /* ---- LinkImage accessors ---- */ const LinkSymbol* link_symbol(LinkImage* img, LinkSymId id) { - if (!img || id == LINK_SYM_NONE || id > img->nsyms) return NULL; - return &img->syms[id - 1]; + if (!img || id == LINK_SYM_NONE || id > LinkSyms_count(&img->syms)) return NULL; + return LinkSyms_at(&img->syms, id - 1); } LinkSymId link_symbol_lookup(LinkImage* img, Sym name) @@ -284,12 +281,12 @@ const LinkSection* link_section_get(LinkImage* img, LinkSectionId id) return &img->sections[id - 1]; } -u32 link_reloc_apply_count(LinkImage* img) { return img ? img->nrelocs : 0; } +u32 link_reloc_apply_count(LinkImage* img) { return img ? LinkRelocs_count(&img->relocs) : 0; } const LinkRelocApply* link_reloc_apply_get(LinkImage* img, u32 id) { - if (!img || id >= img->nrelocs) return NULL; - return &img->relocs[id]; + if (!img || id >= LinkRelocs_count(&img->relocs)) return NULL; + return LinkRelocs_at(&img->relocs, id); } /* ---- LinkImage free / cleanup ---- */ @@ -313,10 +310,8 @@ static void link_image_release(LinkImage* img) sizeof(*img->segments) * img->nsegments); if (img->sections) img->heap->free(img->heap, img->sections, sizeof(*img->sections) * img->nsections); - if (img->syms) img->heap->free(img->heap, img->syms, - sizeof(*img->syms) * img->syms_cap); - if (img->relocs) img->heap->free(img->heap, img->relocs, - sizeof(*img->relocs) * img->relocs_cap); + LinkSyms_fini (&img->syms); + LinkRelocs_fini(&img->relocs); if (img->iplt_pairs) img->heap->free(img->heap, img->iplt_pairs, sizeof(*img->iplt_pairs) * img->niplt * 2u); if (img->input_maps) { @@ -347,6 +342,8 @@ LinkImage* link_image_alloc(Compiler* c) memset(img, 0, sizeof(*img)); img->c = c; img->heap = h; + LinkSyms_init (&img->syms, h); + LinkRelocs_init(&img->relocs, h); symhash_init(&img->globals, h); img->deferred = compiler_defer(c, link_image_cleanup, img); return img; diff --git a/src/link/link_elf.c b/src/link/link_elf.c @@ -147,12 +147,12 @@ static void shift_image_addresses(LinkImage* img, u64 delta) img->sections[i].file_offset += delta; img->sections[i].vaddr += delta; } - for (i = 0; i < img->nrelocs; ++i) { - img->relocs[i].write_file_offset += delta; - img->relocs[i].write_vaddr += delta; + for (i = 0; i < LinkRelocs_count(&img->relocs); ++i) { + LinkRelocs_at(&img->relocs, i)->write_file_offset += delta; + LinkRelocs_at(&img->relocs, i)->write_vaddr += delta; } - for (i = 0; i < img->nsyms; ++i) { - LinkSymbol* s = &img->syms[i]; + for (i = 0; i < LinkSyms_count(&img->syms); ++i) { + LinkSymbol* s = LinkSyms_at(&img->syms, i); if (s->kind == SK_ABS) continue; if (!s->defined) continue; s->vaddr += delta; @@ -175,9 +175,9 @@ static int reloc_is_tlsle(RelocKind k) static void apply_all_relocs(LinkImage* img) { u32 i; - for (i = 0; i < img->nrelocs; ++i) { - LinkRelocApply* r = &img->relocs[i]; - const LinkSymbol* tgt = &img->syms[r->target - 1]; + for (i = 0; i < LinkRelocs_count(&img->relocs); ++i) { + LinkRelocApply* r = LinkRelocs_at(&img->relocs, i); + const LinkSymbol* tgt = LinkSyms_at(&img->syms, r->target - 1); const LinkSection* sec = &img->sections[r->link_section_id - 1]; const LinkSegment* seg = &img->segments[sec->segment_id - 1]; u64 S, P; @@ -432,17 +432,11 @@ void link_emit_elf_aarch64(LinkImage* img, Writer* w) if (img->entry_sym == LINK_SYM_NONE) compiler_panic(c, no_loc(), "link_emit_elf: no resolved entry symbol"); - /* IFUNC trampolines are wired up in layout_iplt and pre-resolved - * by the JIT at load time. The ELF emit path can't run resolvers - * itself (cross-compile, no in-process target code), so it would - * need a startup init routine that walks img->iplt_pairs. Until - * that lands, refuse rather than emit a binary whose iplt slots - * stay zero and trap on first call. */ - if (img->niplt > 0) - compiler_panic(c, no_loc(), - "link_emit_elf: STT_GNU_IFUNC in ELF output is not " - "yet supported (JIT path works); the iplt slots " - "need a startup init routine"); + /* IFUNC trampolines: layout_iplt builds the .iplt stubs + .igot.plt + * slots and (when emit_static_exe was set) synthesizes a + * .init_array entry that calls __cfree_ifunc_init at startup. The + * rt member walks .iplt.pairs and fills each slot before user code + * runs. The ELF writer doesn't have to do anything special here. */ /* ---- plan number of program headers ---- * @@ -584,7 +578,7 @@ void link_emit_elf_aarch64(LinkImage* img, Writer* w) strb_init(&strtab, heap, 256); SymRec* recs = (SymRec*)heap->alloc(heap, - sizeof(*recs) * (img->nsyms + 1u), + sizeof(*recs) * (LinkSyms_count(&img->syms) + 1u), _Alignof(SymRec)); if (!recs) compiler_panic(c, no_loc(), "link_emit_elf: oom on symrecs"); u32 nsyms_emit = 0; @@ -597,8 +591,8 @@ void link_emit_elf_aarch64(LinkImage* img, Writer* w) for (pass = 0; pass < 2; ++pass) { int want_local = (pass == 0); if (!want_local) first_global_idx = nsyms_emit; - for (i = 0; i < img->nsyms; ++i) { - const LinkSymbol* s = &img->syms[i]; + for (i = 0; i < LinkSyms_count(&img->syms); ++i) { + const LinkSymbol* s = LinkSyms_at(&img->syms, i); int is_local = (s->bind == SB_LOCAL); size_t namelen = 0; const char* nm; @@ -743,7 +737,7 @@ void link_emit_elf_aarch64(LinkImage* img, Writer* w) ehdr.e_type = ET_EXEC; ehdr.e_machine = EM_AARCH64; ehdr.e_version = EV_CURRENT; - ehdr.e_entry = IMAGE_BASE + img->syms[img->entry_sym - 1].vaddr; + ehdr.e_entry = IMAGE_BASE + LinkSyms_at(&img->syms, img->entry_sym - 1)->vaddr; ehdr.e_phoff = sizeof(Ehdr64); ehdr.e_shoff = shdr_off; ehdr.e_flags = 0; @@ -885,7 +879,7 @@ void link_emit_elf_aarch64(LinkImage* img, Writer* w) } heap->free(heap, phdrs, sizeof(Phdr64) * nphdr_total); - heap->free(heap, recs, sizeof(*recs) * (img->nsyms + 1u)); + heap->free(heap, recs, sizeof(*recs) * (LinkSyms_count(&img->syms) + 1u)); heap->free(heap, outshdrs, sizeof(*outshdrs) * outshdr_cap); if (outshdr_name_off) heap->free(heap, outshdr_name_off, sizeof(u32) * (noutshdr + 1u)); diff --git a/src/link/link_internal.h b/src/link/link_internal.h @@ -7,6 +7,7 @@ #include "core/core.h" #include "core/hashmap.h" +#include "core/segvec.h" #include "obj/obj.h" #include "link/link.h" @@ -71,17 +72,22 @@ typedef struct LinkArchive { u8 pad; } LinkArchive; +SEGVEC_DEFINE(LinkInputs, LinkInput, 4); /* 16 entries per segment */ +SEGVEC_DEFINE(LinkArchives, LinkArchive, 4); + struct Linker { Compiler* c; Heap* heap; - LinkInput* inputs; /* dyn array; LinkInputId = index + 1 */ - u32 ninputs; - u32 inputs_cap; - LinkArchive* archives; /* dyn array */ - u32 narchives; - u32 archives_cap; + LinkInputs inputs; /* LinkInputId = slot index + 1 */ + LinkArchives archives; Sym entry_name; int gc_sections; + /* Set by cfree_link_exe before link_resolve. When 1, layout_iplt + * synthesizes a .init_array entry pointing at __cfree_ifunc_init so + * the emitted ET_EXEC binary fills its IFUNC slots at startup. The + * JIT path leaves this 0 — slots are pre-resolved in-process by + * link_jit.c, no ctor needed. */ + int emit_static_exe; LinkExternResolver resolver; void* resolver_user; CompilerCleanup* deferred; /* registered by link_new */ @@ -90,14 +96,17 @@ struct Linker { /* Defined in link_layout.c. */ void link_ingest_archives(struct Linker*); +/* SegVec instances for image-owned tables. Pointers returned by *_at / + * *_push remain valid for the LinkImage's lifetime. */ +SEGVEC_DEFINE(LinkSyms, LinkSymbol, 6); /* 64 entries per segment */ +SEGVEC_DEFINE(LinkRelocs, LinkRelocApply, 7); /* 128 entries per segment */ + struct LinkImage { Compiler* c; Heap* heap; CompilerCleanup* deferred; /* registered by link_resolve */ - LinkSymbol* syms; /* id = index + 1 */ - u32 nsyms; - u32 syms_cap; + LinkSyms syms; /* LinkSymId = slot index + 1 */ SymHash globals; /* name -> LinkSymId for global/weak */ LinkSection* sections; /* id = index + 1 */ @@ -108,9 +117,7 @@ struct LinkImage { u8** segment_bytes; /* one per segment; size = file_size */ size_t* segment_bytes_cap; /* allocation size for free */ - LinkRelocApply* relocs; - u32 nrelocs; - u32 relocs_cap; + LinkRelocs relocs; /* IFUNC trampoline table (image-relative vaddrs). One entry per * defined STT_GNU_IFUNC symbol: (resolver_vaddr, slot_vaddr). The diff --git a/src/link/link_jit.c b/src/link/link_jit.c @@ -181,9 +181,9 @@ CfreeJit* cfree_jit_from_image(LinkImage* img) /* Apply relocations. The patch site bytes go through the write * alias; PC-relative arithmetic uses the runtime alias address. */ - for (i = 0; i < img->nrelocs; ++i) { - const LinkRelocApply* r = &img->relocs[i]; - const LinkSymbol* tgt = &img->syms[r->target - 1]; + for (i = 0; i < LinkRelocs_count(&img->relocs); ++i) { + const LinkRelocApply* r = LinkRelocs_at(&img->relocs, i); + const LinkSymbol* tgt = LinkSyms_at(&img->syms, r->target - 1); u64 S, P; u8* P_bytes; if (reloc_is_tlsle(r->kind)) { @@ -321,7 +321,8 @@ void* cfree_jit_lookup(CfreeJit* jit, const char* name) sym = pool_intern_cstr(jit->c->global, name); id = symhash_get(&jit->image->globals, sym); if (id == LINK_SYM_NONE) return NULL; - s = &jit->image->syms[id - 1]; + s = LinkSyms_at(&jit->image->syms, id - 1); + if (!s) return NULL; if (!s->defined) return NULL; if (s->kind == SK_ABS) return (void*)(uintptr_t)s->vaddr; return (void*)vaddr_to_runtime(jit->image, jit->segs, s->vaddr); diff --git a/src/link/link_layout.c b/src/link/link_layout.c @@ -69,29 +69,35 @@ static SegBucket bucket_for(u16 flags) return SEG_R; } -/* ---- LinkImage growth helpers ---- */ +/* ---- LinkImage growth helpers ---- + * + * syms / relocs back onto SegVec — pointers stay stable across pushes, + * so callers may stash LinkSymbol/LinkRelocApply references and + * re-enter mutation without invalidation. */ -static void syms_grow(LinkImage* img, u32 want) +static LinkSymbol* append_symbol_slot(LinkImage* img) { - if (VEC_GROW(img->heap, img->syms, img->syms_cap, want)) - compiler_panic(img->c, no_loc(), "link: oom growing symbols"); + u32 idx; + LinkSymbol* s = LinkSyms_push(&img->syms, &idx); + if (!s) compiler_panic(img->c, no_loc(), "link: oom growing symbols"); + s->id = (LinkSymId)(idx + 1u); + return s; } static LinkSymId append_symbol(LinkImage* img, const LinkSymbol* tmpl) { - LinkSymbol* s; - syms_grow(img, img->nsyms + 1u); - s = &img->syms[img->nsyms]; - *s = *tmpl; - s->id = (LinkSymId)(img->nsyms + 1u); - img->nsyms++; - return s->id; + LinkSymbol* s = append_symbol_slot(img); + LinkSymId id = s->id; + *s = *tmpl; + s->id = id; + return id; } -static void relocs_grow(LinkImage* img, u32 want) +static LinkRelocApply* append_reloc_slot(LinkImage* img) { - if (VEC_GROW(img->heap, img->relocs, img->relocs_cap, want)) - compiler_panic(img->c, no_loc(), "link: oom growing relocs"); + LinkRelocApply* r = LinkRelocs_push(&img->relocs, NULL); + if (!r) compiler_panic(img->c, no_loc(), "link: oom growing relocs"); + return r; } /* ---- per-input symbol/section maps ---- */ @@ -133,8 +139,8 @@ static void resolve_symbols(Linker* l, LinkImage* img) /* Per-input pass: register every ObjSym (locals included), and * insert defined globals/weaks into img->globals. Locals stay * out of the hash. */ - for (ii = 0; ii < l->ninputs; ++ii) { - LinkInput* in = &l->inputs[ii]; + for (ii = 0; ii < LinkInputs_count(&l->inputs); ++ii) { + LinkInput* in = LinkInputs_at(&l->inputs, ii); ObjBuilder* ob = in->obj; InputMap* m = &img->input_maps[ii]; u32 nsym = obj_section_count(ob); (void)nsym; @@ -178,12 +184,12 @@ static void resolve_symbols(Linker* l, LinkImage* img) && s->name != 0) { /* Try to insert. On collision, apply replacement * policy in-place against the existing LinkSymbol. */ - LinkSymId fresh = (LinkSymId)(img->nsyms + 1u); + LinkSymId fresh = (LinkSymId)(LinkSyms_count(&img->syms) + 1u); if (symhash_insert(&img->globals, s->name, fresh, &existing)) { /* No collision — append a new slot. */ m->sym[e.id] = append_symbol(img, &rec); } else { - LinkSymbol* prev = &img->syms[existing - 1]; + LinkSymbol* prev = LinkSyms_at(&img->syms, existing - 1); int new_strength = bind_strength((u8)s->bind); int old_strength = bind_strength(prev->bind); /* COMMON symbols coalesce: largest size wins. */ @@ -244,13 +250,13 @@ static void resolve_undefs(Linker* l, LinkImage* img) /* For every symbol that's still SK_UNDEF and visible by name, look * it up in the global hash. If still undef, try the resolver. If * still undef, fatal. */ - for (i = 0; i < img->nsyms; ++i) { - LinkSymbol* s = &img->syms[i]; + for (i = 0; i < LinkSyms_count(&img->syms); ++i) { + LinkSymbol* s = LinkSyms_at(&img->syms, i); if (s->defined) continue; if (s->name != 0) { LinkSymId hit = symhash_get(&img->globals, s->name); if (hit != LINK_SYM_NONE && hit != s->id) { - LinkSymbol* def = &img->syms[hit - 1]; + LinkSymbol* def = LinkSyms_at(&img->syms, hit - 1); if (def->defined) { /* Re-point this undef at the global definition by * copying the resolved fields. The id remains @@ -352,17 +358,17 @@ static void gc_queue_push(GcQueue* q, Heap* h, u32 ii, ObjSecId j) static void gc_live_alloc(GcLive* g, Linker* l, Heap* h) { u32 ii; - g->ninputs = l->ninputs; - g->marks = l->ninputs - ? (u8**)h->alloc(h, sizeof(*g->marks) * l->ninputs, _Alignof(u8*)) + g->ninputs = LinkInputs_count(&l->inputs); + g->marks = LinkInputs_count(&l->inputs) + ? (u8**)h->alloc(h, sizeof(*g->marks) * LinkInputs_count(&l->inputs), _Alignof(u8*)) : NULL; - g->nsec = l->ninputs - ? (u32*)h->alloc(h, sizeof(*g->nsec) * l->ninputs, _Alignof(u32)) + g->nsec = LinkInputs_count(&l->inputs) + ? (u32*)h->alloc(h, sizeof(*g->nsec) * LinkInputs_count(&l->inputs), _Alignof(u32)) : NULL; - if (l->ninputs && (!g->marks || !g->nsec)) + if (LinkInputs_count(&l->inputs) && (!g->marks || !g->nsec)) compiler_panic(l->c, no_loc(), "link: oom on gc live map"); - for (ii = 0; ii < l->ninputs; ++ii) { - u32 nsec = obj_section_count(l->inputs[ii].obj); + for (ii = 0; ii < LinkInputs_count(&l->inputs); ++ii) { + u32 nsec = obj_section_count(LinkInputs_at(&l->inputs, ii)->obj); g->nsec[ii] = nsec; g->marks[ii] = (u8*)h->alloc(h, nsec ? nsec : 1u, 1); if (!g->marks[ii]) @@ -405,8 +411,8 @@ static int gc_def_site(LinkImage* img, Linker* l, LinkSymId id, const LinkSymbol* s; ObjBuilder* ob; const ObjSym* osym; - if (id == LINK_SYM_NONE || id > img->nsyms) return 0; - s = &img->syms[id - 1]; + if (id == LINK_SYM_NONE || id > LinkSyms_count(&img->syms)) return 0; + s = LinkSyms_at(&img->syms, id - 1); if (!s->defined) { LinkSymId hit; if (s->name == 0) return 0; @@ -416,7 +422,7 @@ static int gc_def_site(LinkImage* img, Linker* l, LinkSymId id, } if (s->kind == SK_ABS || s->kind == SK_COMMON) return 0; if (s->input_id == LINK_INPUT_NONE) return 0; /* synthesized */ - ob = l->inputs[s->input_id - 1].obj; + ob = LinkInputs_at(&l->inputs, s->input_id - 1)->obj; osym = obj_symbol_get(ob, s->obj_sym); if (!osym || osym->section_id == OBJ_SEC_NONE) return 0; *out_ii = (u32)(s->input_id - 1u); @@ -469,8 +475,8 @@ static void gc_promote_by_section_name(Linker* l, GcLive* g, GcQueue* q, Heap* h, Sym section_name) { u32 ii, j; - for (ii = 0; ii < l->ninputs; ++ii) { - ObjBuilder* ob = l->inputs[ii].obj; + for (ii = 0; ii < LinkInputs_count(&l->inputs); ++ii) { + ObjBuilder* ob = LinkInputs_at(&l->inputs, ii)->obj; u32 nsec = obj_section_count(ob); for (j = 1; j < nsec; ++j) { const Section* s = obj_section_get(ob, j); @@ -490,8 +496,8 @@ static void gc_compute(Linker* l, LinkImage* img, GcLive* g) /* GC disabled: every kept section becomes live. Downstream passes * use the same is-live predicate, so this keeps logic uniform. */ if (!l->gc_sections) { - for (ii = 0; ii < l->ninputs; ++ii) { - ObjBuilder* ob = l->inputs[ii].obj; + for (ii = 0; ii < LinkInputs_count(&l->inputs); ++ii) { + ObjBuilder* ob = LinkInputs_at(&l->inputs, ii)->obj; u32 nsec = obj_section_count(ob); for (j = 1; j < nsec; ++j) { const Section* s = obj_section_get(ob, j); @@ -504,8 +510,8 @@ static void gc_compute(Linker* l, LinkImage* img, GcLive* g) memset(&q, 0, sizeof(q)); /* Static roots: SF_RETAIN + init/fini/preinit_array. */ - for (ii = 0; ii < l->ninputs; ++ii) { - ObjBuilder* ob = l->inputs[ii].obj; + for (ii = 0; ii < LinkInputs_count(&l->inputs); ++ii) { + ObjBuilder* ob = LinkInputs_at(&l->inputs, ii)->obj; u32 nsec = obj_section_count(ob); for (j = 1; j < nsec; ++j) { const Section* s = obj_section_get(ob, j); @@ -536,16 +542,13 @@ static void gc_compute(Linker* l, LinkImage* img, GcLive* g) u64 v = q.items[--q.n]; u32 cii = GC_II(v); ObjSecId cj = GC_J(v); - ObjBuilder* ob = l->inputs[cii].obj; + ObjBuilder* ob = LinkInputs_at(&l->inputs, cii)->obj; InputMap* m = &img->input_maps[cii]; - u32 nsec = obj_section_count(ob); - u32 total = 0; - const Reloc* base; - for (k = 0; k < nsec; ++k) total += obj_reloc_count(ob, k); + u32 total = obj_reloc_total(ob); + (void)obj_section_count; if (!total) continue; - base = obj_relocs(ob, 0); for (k = 0; k < total; ++k) { - const Reloc* r = &base[k]; + const Reloc* r = obj_reloc_at(ob, k); LinkSymId target; const LinkSymbol* tsym; u32 tii; ObjSecId tsid; @@ -553,7 +556,7 @@ static void gc_compute(Linker* l, LinkImage* img, GcLive* g) if (r->sym == OBJ_SYM_NONE || r->sym >= m->nsym) continue; target = m->sym[r->sym]; if (target == LINK_SYM_NONE) continue; - tsym = &img->syms[target - 1]; + tsym = LinkSyms_at(&img->syms, target - 1); if (tsym->name != 0) { size_t namelen, off, ilen; @@ -579,15 +582,15 @@ static void gc_drop_dead_globals(Linker* l, LinkImage* img, const GcLive* g) { u32 i; if (!l->gc_sections) return; - for (i = 0; i < img->nsyms; ++i) { - LinkSymbol* s = &img->syms[i]; + for (i = 0; i < LinkSyms_count(&img->syms); ++i) { + LinkSymbol* s = LinkSyms_at(&img->syms, i); ObjBuilder* ob; const ObjSym* osym; ObjSecId osid; if (!s->defined) continue; if (s->kind == SK_ABS || s->kind == SK_COMMON) continue; if (s->input_id == LINK_INPUT_NONE) continue; - ob = l->inputs[s->input_id - 1].obj; + ob = LinkInputs_at(&l->inputs, s->input_id - 1)->obj; osym = obj_symbol_get(ob, s->obj_sym); if (!osym) continue; osid = osym->section_id; @@ -639,8 +642,8 @@ static void layout_sections(Linker* l, LinkImage* img, const GcLive* g) u32 total_kept = 0; /* Pass 0: count kept sections (filtered by GC liveness). */ - for (ii = 0; ii < l->ninputs; ++ii) { - ObjBuilder* ob = l->inputs[ii].obj; + for (ii = 0; ii < LinkInputs_count(&l->inputs); ++ii) { + ObjBuilder* ob = LinkInputs_at(&l->inputs, ii)->obj; for (j = 1; j < obj_section_count(ob); ++j) { const Section* s = obj_section_get(ob, j); if (s && section_kept(s) && gc_live_get(g, ii, j)) ++total_kept; @@ -663,8 +666,8 @@ static void layout_sections(Linker* l, LinkImage* img, const GcLive* g) compiler_panic(img->c, no_loc(), "link: oom on placement entries"); { u32 e = 0; - for (ii = 0; ii < l->ninputs; ++ii) { - ObjBuilder* ob = l->inputs[ii].obj; + for (ii = 0; ii < LinkInputs_count(&l->inputs); ++ii) { + ObjBuilder* ob = LinkInputs_at(&l->inputs, ii)->obj; for (j = 1; j < obj_section_count(ob); ++j) { const Section* s = obj_section_get(ob, j); if (!s || !section_kept(s) || !gc_live_get(g, ii, j)) continue; @@ -702,7 +705,7 @@ static void layout_sections(Linker* l, LinkImage* img, const GcLive* g) if (pe->placed) continue; if (pe->bucket != bucket || pe->name != group_name) continue; - ObjBuilder* ob = l->inputs[pe->input_idx].obj; + ObjBuilder* ob = LinkInputs_at(&l->inputs, pe->input_idx)->obj; InputMap* m = &img->input_maps[pe->input_idx]; const Section* s = obj_section_get(ob, pe->obj_sec_id); u32 align = s->align ? s->align : 1u; @@ -729,7 +732,7 @@ static void layout_sections(Linker* l, LinkImage* img, const GcLive* g) ls = &img->sections[img->nsections++]; memset(ls, 0, sizeof(*ls)); ls->id = lsid; - ls->input_id = l->inputs[pe->input_idx].id; + ls->input_id = LinkInputs_at(&l->inputs, pe->input_idx)->id; ls->obj_section_id = pe->obj_sec_id; ls->segment_id = LINK_SEG_NONE; ls->input_offset = ofs; @@ -874,8 +877,8 @@ static void layout_commons(Linker* l, LinkImage* img) /* First pass: check if we even have COMMON symbols. */ { int has_common = 0; - for (i = 0; i < img->nsyms; ++i) - if (img->syms[i].kind == SK_COMMON && img->syms[i].defined) { has_common = 1; break; } + for (i = 0; i < LinkSyms_count(&img->syms); ++i) + if (LinkSyms_at(&img->syms, i)->kind == SK_COMMON && LinkSyms_at(&img->syms, i)->defined) { has_common = 1; break; } if (!has_common) return; } @@ -922,8 +925,8 @@ static void layout_commons(Linker* l, LinkImage* img) /* Allocate BSS space for each COMMON symbol after file_size. */ { u64 bss_cursor = rw_seg->vaddr + rw_seg->mem_size; - for (i = 0; i < img->nsyms; ++i) { - LinkSymbol* s = &img->syms[i]; + for (i = 0; i < LinkSyms_count(&img->syms); ++i) { + LinkSymbol* s = LinkSyms_at(&img->syms, i); u32 align; if (s->kind != SK_COMMON || !s->defined) continue; align = s->common_align ? s->common_align : 1u; @@ -943,7 +946,7 @@ static void emit_segment_bytes(Linker* l, LinkImage* img) u32 j; for (j = 0; j < img->nsections; ++j) { LinkSection* ls = &img->sections[j]; - ObjBuilder* ob = l->inputs[ls->input_id - 1].obj; + ObjBuilder* ob = LinkInputs_at(&l->inputs, ls->input_id - 1)->obj; const Section* s = obj_section_get(ob, ls->obj_section_id); LinkSegment* seg = &img->segments[ls->segment_id - 1]; u8* dst; @@ -963,8 +966,8 @@ static void emit_segment_bytes(Linker* l, LinkImage* img) static void link_symbols_to_sections(Linker* l, LinkImage* img) { u32 ii; - for (ii = 0; ii < l->ninputs; ++ii) { - ObjBuilder* ob = l->inputs[ii].obj; + for (ii = 0; ii < LinkInputs_count(&l->inputs); ++ii) { + ObjBuilder* ob = LinkInputs_at(&l->inputs, ii)->obj; InputMap* m = &img->input_maps[ii]; ObjSymIter* it = obj_symiter_new(ob); ObjSymEntry e; @@ -972,13 +975,13 @@ static void link_symbols_to_sections(Linker* l, LinkImage* img) LinkSymId lsid = m->sym[e.id]; LinkSymbol* ls; if (lsid == LINK_SYM_NONE) continue; - ls = &img->syms[lsid - 1]; + ls = LinkSyms_at(&img->syms, lsid - 1); if (!ls->defined) continue; if (ls->kind == SK_ABS && ls->vaddr != 0) continue; if (e.sym->section_id == OBJ_SEC_NONE) continue; /* Only update from this input if this is the input that * contributed the winning definition. */ - if (ls->input_id != l->inputs[ii].id) continue; + if (ls->input_id != LinkInputs_at(&l->inputs, ii)->id) continue; ls->section_id = m->section[e.sym->section_id]; } obj_symiter_free(it); @@ -986,8 +989,8 @@ static void link_symbols_to_sections(Linker* l, LinkImage* img) /* Now compute vaddrs. */ { u32 i; - for (i = 0; i < img->nsyms; ++i) { - LinkSymbol* s = &img->syms[i]; + for (i = 0; i < LinkSyms_count(&img->syms); ++i) { + LinkSymbol* s = LinkSyms_at(&img->syms, i); if (s->kind == SK_ABS && s->vaddr != 0) continue; if (!s->defined) continue; if (s->section_id == LINK_SEC_NONE) continue; @@ -997,14 +1000,14 @@ static void link_symbols_to_sections(Linker* l, LinkImage* img) /* Resolve undef-against-global once defs are addressed. */ { u32 i; - for (i = 0; i < img->nsyms; ++i) { - LinkSymbol* s = &img->syms[i]; + for (i = 0; i < LinkSyms_count(&img->syms); ++i) { + LinkSymbol* s = LinkSyms_at(&img->syms, i); if (s->defined) continue; if (s->name == 0) continue; { LinkSymId hit = symhash_get(&img->globals, s->name); if (hit != LINK_SYM_NONE && hit != s->id) { - LinkSymbol* def = &img->syms[hit - 1]; + LinkSymbol* def = LinkSyms_at(&img->syms, hit - 1); if (def->defined) { s->section_id = def->section_id; s->value = def->value; @@ -1026,6 +1029,7 @@ static void emit_boundary_sym(Linker* l, LinkImage* img, Sym sym = pool_intern_cstr(l->c->global, name); LinkSymId id = symhash_get(&img->globals, sym); LinkSymbol rec; + u32 i, n; memset(&rec, 0, sizeof(rec)); rec.name = sym; rec.kind = SK_OBJ; @@ -1034,12 +1038,35 @@ static void emit_boundary_sym(Linker* l, LinkImage* img, rec.bind = SB_GLOBAL; if (id != LINK_SYM_NONE) { /* Satisfy any existing undef reference. */ - img->syms[id - 1] = rec; - img->syms[id - 1].id = id; + *LinkSyms_at(&img->syms, id - 1) = rec; + LinkSyms_at(&img->syms, id - 1)->id = id; } else { LinkSymId fresh = append_symbol(img, &rec); symhash_insert(&img->globals, sym, fresh, &id); } + /* Per-input undef LinkSymbols are stored in their own slots + * (resolve_symbols never folds undefs into the def's slot). When + * an emit_boundary_sym call runs after resolve_undefs (e.g. + * layout_iplt's __start_iplt_pairs / __stop_iplt_pairs), each + * undef ref already carries a stale vaddr (zero, from a + * weak-zero resolve, or whatever the prior def held). Walk + * img->syms by name and re-copy so downstream consumers + * (layout_got's GOT-slot ABS64 fills, emit_reloc_records) see + * the new vaddr. Locals never share names with globals so the + * bind check just guards the unusual case of a local with the + * same name. */ + n = LinkSyms_count(&img->syms); + for (i = 0; i < n; ++i) { + LinkSymbol* s = LinkSyms_at(&img->syms, i); + if (s->name != sym) continue; + if (s->id == id) continue; + if (s->bind == SB_LOCAL) continue; + s->section_id = LINK_SEC_NONE; + s->value = 0; + s->vaddr = vaddr; + s->kind = SK_OBJ; + s->defined = 1; + } } static void emit_array_boundaries(Linker* l, LinkImage* img) @@ -1049,8 +1076,8 @@ static void emit_array_boundaries(Linker* l, LinkImage* img) u64 init_start = (u64)-1, init_end = 0; u64 fini_start = (u64)-1, fini_end = 0; - for (ii = 0; ii < l->ninputs; ++ii) { - ObjBuilder* ob = l->inputs[ii].obj; + for (ii = 0; ii < LinkInputs_count(&l->inputs); ++ii) { + ObjBuilder* ob = LinkInputs_at(&l->inputs, ii)->obj; InputMap* m = &img->input_maps[ii]; for (j = 1; j < obj_section_count(ob); ++j) { const Section* s = obj_section_get(ob, j); @@ -1074,6 +1101,30 @@ static void emit_array_boundaries(Linker* l, LinkImage* img) } } + /* Synthetic init/fini sections (e.g. layout_iplt's .init_array + * entry pointing at __cfree_ifunc_init) carry input_id = + * LINK_INPUT_NONE and are not visible through the input_maps + * loop above; fold them in here so the boundary symbols cover + * them too. */ + { + u32 i; + for (i = 0; i < img->nsections; ++i) { + const LinkSection* ls = &img->sections[i]; + u64 start, end; + if (ls->input_id != LINK_INPUT_NONE) continue; + if (ls->sem != SSEM_INIT_ARRAY && ls->sem != SSEM_FINI_ARRAY) continue; + start = ls->vaddr; + end = ls->vaddr + ls->size; + if (ls->sem == SSEM_INIT_ARRAY) { + if (start < init_start) init_start = start; + if (end > init_end) init_end = end; + } else { + if (start < fini_start) fini_start = start; + if (end > fini_end) fini_end = end; + } + } + } + if (init_start == (u64)-1) { init_start = 0; init_end = 0; } if (fini_start == (u64)-1) { fini_start = 0; fini_end = 0; } @@ -1116,8 +1167,8 @@ static void emit_tls_boundaries(Linker* l, LinkImage* img) rec.defined = 1; rec.vaddr = tbss_size; if (id != LINK_SYM_NONE) { - img->syms[id - 1] = rec; - img->syms[id - 1].id = id; + *LinkSyms_at(&img->syms, id - 1) = rec; + LinkSyms_at(&img->syms, id - 1)->id = id; } else { LinkSymId fresh = append_symbol(img, &rec); symhash_insert(&img->globals, sym_size, fresh, &id); @@ -1134,8 +1185,8 @@ static void emit_tls_boundaries(Linker* l, LinkImage* img) static void emit_encoding_section_boundaries(Linker* l, LinkImage* img) { u32 i, ii, j; - for (i = 0; i < img->nsyms; ++i) { - LinkSymbol* sym = &img->syms[i]; + for (i = 0; i < LinkSyms_count(&img->syms); ++i) { + LinkSymbol* sym = LinkSyms_at(&img->syms, i); const char* nm; size_t namelen, off, ilen; int is_start; @@ -1148,8 +1199,8 @@ static void emit_encoding_section_boundaries(Linker* l, LinkImage* img) nm = pool_str(l->c->global, sym->name, &namelen); if (!gc_split_start_stop(nm, namelen, &off, &ilen, &is_start)) continue; secname = pool_intern(l->c->global, nm + off, ilen); - for (ii = 0; ii < l->ninputs; ++ii) { - ObjBuilder* ob = l->inputs[ii].obj; + for (ii = 0; ii < LinkInputs_count(&l->inputs); ++ii) { + ObjBuilder* ob = LinkInputs_at(&l->inputs, ii)->obj; InputMap* m = &img->input_maps[ii]; for (j = 1; j < obj_section_count(ob); ++j) { const Section* s = obj_section_get(ob, j); @@ -1217,20 +1268,14 @@ static void emit_reloc_records(Linker* l, LinkImage* img, const LinkSymId* got_map) { u32 ii; - for (ii = 0; ii < l->ninputs; ++ii) { - ObjBuilder* ob = l->inputs[ii].obj; + for (ii = 0; ii < LinkInputs_count(&l->inputs); ++ii) { + ObjBuilder* ob = LinkInputs_at(&l->inputs, ii)->obj; InputMap* m = &img->input_maps[ii]; - u32 nsec = obj_section_count(ob); - u32 total = 0; - u32 j, k; - const Reloc* base; - for (j = 0; j < nsec; ++j) total += obj_reloc_count(ob, j); + u32 total = obj_reloc_total(ob); + u32 k; if (total == 0) continue; - /* obj_relocs returns the start of the flat array regardless of - * the section_id argument; we filter by r->section_id below. */ - base = obj_relocs(ob, 0); for (k = 0; k < total; ++k) { - const Reloc* r = &base[k]; + const Reloc* r = obj_reloc_at(ob, k); const Section* s = obj_section_get(ob, r->section_id); LinkSymId target; LinkSection* ls; @@ -1257,7 +1302,7 @@ static void emit_reloc_records(Linker* l, LinkImage* img, } ls = &img->sections[m->section[r->section_id] - 1]; memset(&rec, 0, sizeof(rec)); - rec.input_id = l->inputs[ii].id; + rec.input_id = LinkInputs_at(&l->inputs, ii)->id; rec.section_id = r->section_id; rec.link_section_id = ls->id; rec.offset = r->offset; @@ -1271,8 +1316,7 @@ static void emit_reloc_records(Linker* l, LinkImage* img, compiler_panic(l->c, no_loc(), "link: unsupported reloc kind %u", (unsigned)r->kind); - relocs_grow(img, img->nrelocs + 1u); - img->relocs[img->nrelocs++] = rec; + *append_reloc_slot(img) = rec; } } } @@ -1288,7 +1332,7 @@ static void emit_reloc_records(Linker* l, LinkImage* img, * vaddr at apply time. Weak-undef targets stay at vaddr 0 so the slot * carries NULL. * - * The returned `got_map_out` is a sparse array of size (img->nsyms+1) + * The returned `got_map_out` is a sparse array of size (LinkSyms_count(&img->syms)+1) * indexed by LinkSymId, holding the slot's synthetic LinkSymId (or * LINK_SYM_NONE for symbols that don't need a slot). Caller frees. */ static void layout_got(Linker* l, LinkImage* img, LinkSymId** got_map_out) @@ -1311,7 +1355,7 @@ static void layout_got(Linker* l, LinkImage* img, LinkSymId** got_map_out) /* Pass A: scan input relocs for GOT-using kinds. */ { - u32 nsyms_now = img->nsyms; /* freeze before we append */ + u32 nsyms_now = LinkSyms_count(&img->syms); /* freeze before we append */ got_map = (LinkSymId*)h->alloc(h, sizeof(*got_map) * (nsyms_now + 1u), _Alignof(LinkSymId)); if (!got_map) compiler_panic(img->c, no_loc(), "link: oom on got map"); @@ -1319,17 +1363,13 @@ static void layout_got(Linker* l, LinkImage* img, LinkSymId** got_map_out) (void)nsyms_now; } - for (ii = 0; ii < l->ninputs; ++ii) { - ObjBuilder* ob = l->inputs[ii].obj; + for (ii = 0; ii < LinkInputs_count(&l->inputs); ++ii) { + ObjBuilder* ob = LinkInputs_at(&l->inputs, ii)->obj; InputMap* m = &img->input_maps[ii]; - u32 nsec = obj_section_count(ob); - u32 total = 0; - const Reloc* base; - for (j = 0; j < nsec; ++j) total += obj_reloc_count(ob, j); + u32 total = obj_reloc_total(ob); if (!total) continue; - base = obj_relocs(ob, 0); for (k = 0; k < total; ++k) { - const Reloc* r = &base[k]; + const Reloc* r = obj_reloc_at(ob, k); const Section* s = obj_section_get(ob, r->section_id); LinkSymId target; if (!s || !section_kept(s)) continue; @@ -1352,7 +1392,7 @@ static void layout_got(Linker* l, LinkImage* img, LinkSymId** got_map_out) if (nslot == 0) { if (slot_targets) h->free(h, slot_targets, sizeof(*slot_targets) * slot_cap); - h->free(h, got_map, sizeof(*got_map) * (img->nsyms + 1u)); + h->free(h, got_map, sizeof(*got_map) * (LinkSyms_count(&img->syms) + 1u)); return; } @@ -1468,8 +1508,7 @@ static void layout_got(Linker* l, LinkImage* img, LinkSymId** got_map_out) rrec.kind = R_ABS64; rrec.target = orig; rrec.addend = 0; - relocs_grow(img, img->nrelocs + 1u); - img->relocs[img->nrelocs++] = rrec; + *append_reloc_slot(img) = rrec; } if (slot_targets) @@ -1478,7 +1517,7 @@ static void layout_got(Linker* l, LinkImage* img, LinkSymId** got_map_out) *got_map_out = got_map; } -/* ---- pass 3d: STT_GNU_IFUNC trampoline (.iplt + .igot.plt) ---- +/* ---- pass 3d: STT_GNU_IFUNC trampoline (.iplt + .igot.plt + .iplt.pairs) -- * * Per defined SK_IFUNC symbol we synthesize: * - A 12-byte stub in a fresh RX segment (.iplt): three AArch64 @@ -1488,16 +1527,69 @@ static void layout_got(Linker* l, LinkImage* img, LinkSymId** got_map_out) * the existing reloc machinery patches them against a synthetic * LinkSymbol whose vaddr is the matching slot. * - An 8-byte slot in a fresh RW segment (.igot.plt), zero-initialized. + * - A 16-byte (resolver_ptr, slot_ptr) entry in a parallel RW + * section .iplt.pairs (also page-aligned segment for cleanliness), + * filled at apply time via two R_ABS64 relocs. The boundary + * symbols __start_iplt_pairs / __stop_iplt_pairs cover the span + * so the rt member ifunc_init.c can iterate it. + * + * The IFUNC LinkSymbol's vaddr is then redirected to the stub. The + * legacy in-image img->iplt_pairs[] table is also populated so the + * JIT path's pre-resolver can call each resolver and store its + * return value into the slot's runtime address — that path doesn't + * use the .iplt.pairs data section. * - * The IFUNC LinkSymbol's vaddr is then redirected to the stub, and a - * pair (resolver_vaddr, slot_vaddr) is appended to img->iplt_pairs so - * the JIT path can fill the slot in-process after relocation apply. - * The ELF emit path needs target-arch code to call the resolvers and - * is not wired up yet — see link_emit_elf_aarch64. + * When emit_static_exe is set (cfree_link_exe path), an additional + * 8-byte SSEM_INIT_ARRAY section is synthesized that holds one R_ABS64 + * reloc against __cfree_ifunc_init. The startup CRT runs the entry + * via .init_array before user code, filling all .igot.plt slots. * * Invariant: runs after link_symbols_to_sections so the resolver's - * vaddr is final, and before emit_reloc_records so the synthesized - * stub relocs ride the same apply path as ordinary input relocs. */ + * vaddr is final; before emit_array_boundaries so the synthetic + * .init_array entry contributes to __init_array_start/end; before + * resolve_undefs so cross-TU undef references see the post-redirect + * (stub) vaddr. */ + +static u32 layout_iplt_alloc_segments(LinkImage* img, u32 nseg) +{ + Heap* h = img->heap; + u32 base = img->nsegments; + u32 new_nseg = base + nseg; + LinkSegment* nsegs = (LinkSegment*)h->realloc( + h, img->segments, + sizeof(*img->segments) * img->nsegments, + sizeof(*img->segments) * new_nseg, _Alignof(LinkSegment)); + u8** nsbufs = (u8**)h->realloc( + h, img->segment_bytes, + sizeof(*img->segment_bytes) * img->nsegments, + sizeof(*img->segment_bytes) * new_nseg, _Alignof(u8*)); + size_t* nscaps = (size_t*)h->realloc( + h, img->segment_bytes_cap, + sizeof(*img->segment_bytes_cap) * img->nsegments, + sizeof(*img->segment_bytes_cap) * new_nseg, _Alignof(size_t)); + if (!nsegs || !nsbufs || !nscaps) + compiler_panic(img->c, no_loc(), "link: oom on iplt segments"); + img->segments = nsegs; + img->segment_bytes = nsbufs; + img->segment_bytes_cap = nscaps; + /* Caller fills slots [base..base+nseg). */ + return base; +} + +static u32 layout_iplt_alloc_sections(LinkImage* img, u32 nsec) +{ + Heap* h = img->heap; + u32 base = img->nsections; + u32 new_nsec = base + nsec; + LinkSection* nsections = (LinkSection*)h->realloc( + h, img->sections, + sizeof(*img->sections) * img->nsections, + sizeof(*img->sections) * new_nsec, _Alignof(LinkSection)); + if (!nsections) + compiler_panic(img->c, no_loc(), "link: oom on iplt sections"); + img->sections = nsections; + return base; +} static void layout_iplt(Linker* l, LinkImage* img) { @@ -1506,60 +1598,81 @@ static void layout_iplt(Linker* l, LinkImage* img) u32 nifunc = 0; u64 page; u64 base_vaddr = 0; - u64 iplt_vaddr, igot_vaddr; - u64 iplt_size, igot_size; - u32 iplt_seg_idx, igot_seg_idx; + u64 iplt_vaddr, igot_vaddr, pairs_vaddr; + u64 iplt_size, igot_size, pairs_size; + u64 init_vaddr = 0, init_size = 0; + u32 iplt_seg_idx, igot_seg_idx, pairs_seg_idx; + u32 init_seg_idx = 0; + u32 seg_base, sec_base; LinkSegment* iplt_seg; LinkSegment* igot_seg; + LinkSegment* pairs_seg; + LinkSegment* init_seg = NULL; LinkSection* iplt_sec; LinkSection* igot_sec; + LinkSection* pairs_sec; + LinkSection* init_sec = NULL; u8* iplt_bytes; u32 slot_idx; + int emit_init_array = l->emit_static_exe; + LinkSymId ifunc_init_sym = LINK_SYM_NONE; + Sym ifunc_init_name = 0; + Sym pairs_section_name; + Sym init_section_name; /* Pass A: count defined IFUNCs. */ - for (i = 0; i < img->nsyms; ++i) { - const LinkSymbol* s = &img->syms[i]; + for (i = 0; i < LinkSyms_count(&img->syms); ++i) { + const LinkSymbol* s = LinkSyms_at(&img->syms, i); if (s->kind == SK_IFUNC && s->defined) ++nifunc; } if (nifunc == 0) return; page = layout_page_size(l); - /* Pick a base vaddr after every existing segment. Two fresh - * segments are appended: one RX for stubs, one RW for slots. */ + /* Pick a base vaddr after every existing segment. */ for (i = 0; i < img->nsegments; ++i) { u64 end = img->segments[i].vaddr + img->segments[i].mem_size; if (end > base_vaddr) base_vaddr = end; } - base_vaddr = ALIGN_UP(base_vaddr, (u64)(page)); - iplt_vaddr = base_vaddr; - iplt_size = (u64)nifunc * 12u; - igot_vaddr = ALIGN_UP(iplt_vaddr + iplt_size, (u64)(page)); - igot_size = (u64)nifunc * 8u; - /* Grow segment / segment-bytes arrays by 2. */ + base_vaddr = ALIGN_UP(base_vaddr, (u64)(page)); + iplt_vaddr = base_vaddr; + iplt_size = (u64)nifunc * 12u; + igot_vaddr = ALIGN_UP(iplt_vaddr + iplt_size, (u64)(page)); + igot_size = (u64)nifunc * 8u; + pairs_vaddr = ALIGN_UP(igot_vaddr + igot_size, (u64)(page)); + pairs_size = (u64)nifunc * 16u; + + /* When emitting a static ET_EXEC, locate (or fail-late on) the + * __cfree_ifunc_init symbol now and reserve a 1-entry + * .init_array section right after .iplt.pairs in its own + * page-aligned RW segment. The lookup must succeed: archive + * pre-seeding in link_ingest_archives ensured the rt member is + * pulled when any input defines an IFUNC. */ + if (emit_init_array) { + ifunc_init_name = pool_intern_cstr(l->c->global, "__cfree_ifunc_init"); + ifunc_init_sym = symhash_get(&img->globals, ifunc_init_name); + if (ifunc_init_sym == LINK_SYM_NONE + || !LinkSyms_at(&img->syms, ifunc_init_sym - 1)->defined) { + compiler_panic(img->c, no_loc(), + "link: STT_GNU_IFUNC requires '__cfree_ifunc_init' " + "to be defined (link in libcfree_rt.a or provide " + "your own implementation)"); + } + init_vaddr = ALIGN_UP(pairs_vaddr + pairs_size, (u64)(page)); + init_size = 8u; + } + + /* Allocate segments: [iplt RX, igot RW, pairs RW] + optional [init RW]. */ { - u32 new_nseg = img->nsegments + 2u; - LinkSegment* nsegs = (LinkSegment*)h->realloc( - h, img->segments, - sizeof(*img->segments) * img->nsegments, - sizeof(*img->segments) * new_nseg, _Alignof(LinkSegment)); - u8** nsbufs = (u8**)h->realloc( - h, img->segment_bytes, - sizeof(*img->segment_bytes) * img->nsegments, - sizeof(*img->segment_bytes) * new_nseg, _Alignof(u8*)); - size_t* nscaps = (size_t*)h->realloc( - h, img->segment_bytes_cap, - sizeof(*img->segment_bytes_cap) * img->nsegments, - sizeof(*img->segment_bytes_cap) * new_nseg, _Alignof(size_t)); - if (!nsegs || !nsbufs || !nscaps) - compiler_panic(img->c, no_loc(), "link: oom on iplt segments"); - img->segments = nsegs; - img->segment_bytes = nsbufs; - img->segment_bytes_cap = nscaps; + u32 nseg = emit_init_array ? 4u : 3u; + seg_base = layout_iplt_alloc_segments(img, nseg); } + iplt_seg_idx = seg_base + 0u; + igot_seg_idx = seg_base + 1u; + pairs_seg_idx = seg_base + 2u; + if (emit_init_array) init_seg_idx = seg_base + 3u; - iplt_seg_idx = img->nsegments; iplt_seg = &img->segments[iplt_seg_idx]; memset(iplt_seg, 0, sizeof(*iplt_seg)); iplt_seg->id = (LinkSegmentId)(iplt_seg_idx + 1u); @@ -1575,9 +1688,7 @@ static void layout_iplt(Linker* l, LinkImage* img) if (!img->segment_bytes[iplt_seg_idx]) compiler_panic(img->c, no_loc(), "link: oom on iplt bytes"); memset(img->segment_bytes[iplt_seg_idx], 0, (size_t)iplt_size); - img->nsegments++; - igot_seg_idx = img->nsegments; igot_seg = &img->segments[igot_seg_idx]; memset(igot_seg, 0, sizeof(*igot_seg)); igot_seg->id = (LinkSegmentId)(igot_seg_idx + 1u); @@ -1593,22 +1704,54 @@ static void layout_iplt(Linker* l, LinkImage* img) if (!img->segment_bytes[igot_seg_idx]) compiler_panic(img->c, no_loc(), "link: oom on igot bytes"); memset(img->segment_bytes[igot_seg_idx], 0, (size_t)igot_size); - img->nsegments++; - /* Append two LinkSections (.iplt and .igot.plt). */ + pairs_seg = &img->segments[pairs_seg_idx]; + memset(pairs_seg, 0, sizeof(*pairs_seg)); + pairs_seg->id = (LinkSegmentId)(pairs_seg_idx + 1u); + pairs_seg->flags = SF_ALLOC | SF_WRITE; + pairs_seg->file_offset = pairs_vaddr; + pairs_seg->vaddr = pairs_vaddr; + pairs_seg->file_size = pairs_size; + pairs_seg->mem_size = pairs_size; + pairs_seg->align = (u32)page; + pairs_seg->nsections = 1; + img->segment_bytes[pairs_seg_idx] = (u8*)h->alloc(h, (size_t)pairs_size, 16); + img->segment_bytes_cap[pairs_seg_idx] = (size_t)pairs_size; + if (!img->segment_bytes[pairs_seg_idx]) + compiler_panic(img->c, no_loc(), "link: oom on iplt.pairs bytes"); + memset(img->segment_bytes[pairs_seg_idx], 0, (size_t)pairs_size); + + if (emit_init_array) { + init_seg = &img->segments[init_seg_idx]; + memset(init_seg, 0, sizeof(*init_seg)); + init_seg->id = (LinkSegmentId)(init_seg_idx + 1u); + init_seg->flags = SF_ALLOC | SF_WRITE; + init_seg->file_offset = init_vaddr; + init_seg->vaddr = init_vaddr; + init_seg->file_size = init_size; + init_seg->mem_size = init_size; + init_seg->align = (u32)page; + init_seg->nsections = 1; + img->segment_bytes[init_seg_idx] = (u8*)h->alloc(h, (size_t)init_size, 16); + img->segment_bytes_cap[init_seg_idx] = (size_t)init_size; + if (!img->segment_bytes[init_seg_idx]) + compiler_panic(img->c, no_loc(), "link: oom on iplt init_array bytes"); + memset(img->segment_bytes[init_seg_idx], 0, (size_t)init_size); + } + img->nsegments += emit_init_array ? 4u : 3u; + + /* Allocate sections: same shape, one section per segment. */ { - u32 new_nsec = img->nsections + 2u; - LinkSection* nsections = (LinkSection*)h->realloc( - h, img->sections, - sizeof(*img->sections) * img->nsections, - sizeof(*img->sections) * new_nsec, _Alignof(LinkSection)); - if (!nsections) - compiler_panic(img->c, no_loc(), "link: oom on iplt sections"); - img->sections = nsections; + u32 nsec = emit_init_array ? 4u : 3u; + sec_base = layout_iplt_alloc_sections(img, nsec); } - iplt_sec = &img->sections[img->nsections]; + + pairs_section_name = pool_intern_cstr(l->c->global, ".iplt.pairs"); + init_section_name = pool_intern_cstr(l->c->global, ".init_array"); + + iplt_sec = &img->sections[sec_base + 0u]; memset(iplt_sec, 0, sizeof(*iplt_sec)); - iplt_sec->id = (LinkSectionId)(img->nsections + 1u); + iplt_sec->id = (LinkSectionId)(sec_base + 0u + 1u); iplt_sec->input_id = LINK_INPUT_NONE; iplt_sec->obj_section_id = OBJ_SEC_NONE; iplt_sec->segment_id = iplt_seg->id; @@ -1618,11 +1761,12 @@ static void layout_iplt(Linker* l, LinkImage* img) iplt_sec->size = iplt_size; iplt_sec->flags = SF_ALLOC | SF_EXEC; iplt_sec->align = 4; - img->nsections++; + iplt_sec->name = pool_intern_cstr(l->c->global, ".iplt"); + iplt_sec->sem = SSEM_PROGBITS; - igot_sec = &img->sections[img->nsections]; + igot_sec = &img->sections[sec_base + 1u]; memset(igot_sec, 0, sizeof(*igot_sec)); - igot_sec->id = (LinkSectionId)(img->nsections + 1u); + igot_sec->id = (LinkSectionId)(sec_base + 1u + 1u); igot_sec->input_id = LINK_INPUT_NONE; igot_sec->obj_section_id = OBJ_SEC_NONE; igot_sec->segment_id = igot_seg->id; @@ -1632,10 +1776,52 @@ static void layout_iplt(Linker* l, LinkImage* img) igot_sec->size = igot_size; igot_sec->flags = SF_ALLOC | SF_WRITE; igot_sec->align = 8; - img->nsections++; - - /* Allocate the iplt_pairs table (resolver_vaddr, slot_vaddr) per - * IFUNC, in the same iteration order as the stub layout below. */ + igot_sec->name = pool_intern_cstr(l->c->global, ".igot.plt"); + igot_sec->sem = SSEM_PROGBITS; + + pairs_sec = &img->sections[sec_base + 2u]; + memset(pairs_sec, 0, sizeof(*pairs_sec)); + pairs_sec->id = (LinkSectionId)(sec_base + 2u + 1u); + pairs_sec->input_id = LINK_INPUT_NONE; + pairs_sec->obj_section_id = OBJ_SEC_NONE; + pairs_sec->segment_id = pairs_seg->id; + pairs_sec->input_offset = 0; + pairs_sec->file_offset = pairs_vaddr; + pairs_sec->vaddr = pairs_vaddr; + pairs_sec->size = pairs_size; + pairs_sec->flags = SF_ALLOC | SF_WRITE; + pairs_sec->align = 8; + pairs_sec->name = pairs_section_name; + pairs_sec->sem = SSEM_PROGBITS; + + if (emit_init_array) { + init_sec = &img->sections[sec_base + 3u]; + memset(init_sec, 0, sizeof(*init_sec)); + init_sec->id = (LinkSectionId)(sec_base + 3u + 1u); + init_sec->input_id = LINK_INPUT_NONE; + init_sec->obj_section_id = OBJ_SEC_NONE; + init_sec->segment_id = init_seg->id; + init_sec->input_offset = 0; + init_sec->file_offset = init_vaddr; + init_sec->vaddr = init_vaddr; + init_sec->size = init_size; + init_sec->flags = SF_ALLOC | SF_WRITE; + init_sec->align = 8; + init_sec->name = init_section_name; + init_sec->sem = SSEM_INIT_ARRAY; + } + img->nsections += emit_init_array ? 4u : 3u; + + /* __start_iplt_pairs / __stop_iplt_pairs span the .iplt.pairs + * section (start inclusive, end exclusive). The rt member's + * __cfree_ifunc_init iterates this span. */ + emit_boundary_sym(l, img, "__start_iplt_pairs", pairs_vaddr); + emit_boundary_sym(l, img, "__stop_iplt_pairs", pairs_vaddr + pairs_size); + + /* Allocate the in-image iplt_pairs table (resolver_vaddr, + * slot_vaddr) per IFUNC, in the same iteration order as the stub + * layout. Used by the JIT path's pre-resolution; the ELF emit + * path uses the .iplt.pairs data section instead. */ img->iplt_pairs = (u64*)h->alloc( h, sizeof(*img->iplt_pairs) * 2u * (size_t)nifunc, _Alignof(u64)); if (!img->iplt_pairs) @@ -1646,23 +1832,33 @@ static void layout_iplt(Linker* l, LinkImage* img) slot_idx = 0; /* Pass B: per IFUNC, write the stub bytes, synthesize a slot - * LinkSymbol, and emit the two relocs that fill the stub's ADRP/LDR - * immediate fields against the slot. */ - for (i = 0; i < img->nsyms; ++i) { - LinkSymbol* s = &img->syms[i]; + * LinkSymbol + a synthetic resolver-pointer LinkSymbol, and emit + * the relocs. The IFUNC LinkSymbol is then redirected to the + * stub so external references call into the trampoline instead + * of the resolver directly. */ + for (i = 0; i < LinkSyms_count(&img->syms); ++i) { + LinkSymbol* s = LinkSyms_at(&img->syms, i); u64 stub_vaddr; u64 slot_vaddr; + u64 pair_vaddr; u64 resolver_vaddr; + LinkSectionId resolver_section; + u64 resolver_value; LinkSymbol slot_rec; + LinkSymbol resolver_rec; LinkSymId slot_id; + LinkSymId resolver_id; LinkRelocApply rrec; u8* stub_dst; if (s->kind != SK_IFUNC || !s->defined) continue; - stub_vaddr = iplt_vaddr + (u64)slot_idx * 12u; - slot_vaddr = igot_vaddr + (u64)slot_idx * 8u; - resolver_vaddr = s->vaddr; + stub_vaddr = iplt_vaddr + (u64)slot_idx * 12u; + slot_vaddr = igot_vaddr + (u64)slot_idx * 8u; + pair_vaddr = pairs_vaddr + (u64)slot_idx * 16u; + resolver_vaddr = s->vaddr; + resolver_section = s->section_id; + resolver_value = s->value; img->iplt_pairs[2u * slot_idx + 0] = resolver_vaddr; img->iplt_pairs[2u * slot_idx + 1] = slot_vaddr; @@ -1672,7 +1868,7 @@ static void layout_iplt(Linker* l, LinkImage* img) wr_u32_le(stub_dst + 4, 0xf9400210u); /* LDR x16, [x16] */ wr_u32_le(stub_dst + 8, 0xd61f0200u); /* BR x16 */ - /* Synthetic local symbol for the slot. */ + /* Synthetic local symbol for the .igot.plt slot. */ memset(&slot_rec, 0, sizeof(slot_rec)); slot_rec.name = 0; slot_rec.kind = SK_OBJ; @@ -1683,6 +1879,21 @@ static void layout_iplt(Linker* l, LinkImage* img) slot_rec.size = 8; slot_id = append_symbol(img, &slot_rec); + /* Synthetic local symbol for the resolver address (captured + * pre-redirect so the .iplt.pairs ABS64 reloc can target + * something whose vaddr shifts with the image base alongside + * the section it lives in). */ + memset(&resolver_rec, 0, sizeof(resolver_rec)); + resolver_rec.name = 0; + resolver_rec.kind = SK_FUNC; + resolver_rec.bind = SB_LOCAL; + resolver_rec.defined = 1; + resolver_rec.section_id = resolver_section; + resolver_rec.value = resolver_value; + resolver_rec.vaddr = resolver_vaddr; + resolver_rec.size = 0; + resolver_id = append_symbol(img, &resolver_rec); + /* Reloc on the ADRP at stub+0. */ memset(&rrec, 0, sizeof(rrec)); rrec.input_id = LINK_INPUT_NONE; @@ -1695,8 +1906,7 @@ static void layout_iplt(Linker* l, LinkImage* img) rrec.kind = R_AARCH64_ADR_PREL_PG_HI21; rrec.target = slot_id; rrec.addend = 0; - relocs_grow(img, img->nrelocs + 1u); - img->relocs[img->nrelocs++] = rrec; + *append_reloc_slot(img) = rrec; /* Reloc on the LDR at stub+4. */ memset(&rrec, 0, sizeof(rrec)); @@ -1710,8 +1920,35 @@ static void layout_iplt(Linker* l, LinkImage* img) rrec.kind = R_AARCH64_LDST64_ABS_LO12_NC; rrec.target = slot_id; rrec.addend = 0; - relocs_grow(img, img->nrelocs + 1u); - img->relocs[img->nrelocs++] = rrec; + *append_reloc_slot(img) = rrec; + + /* .iplt.pairs[i].resolver = &resolver (R_ABS64) */ + memset(&rrec, 0, sizeof(rrec)); + rrec.input_id = LINK_INPUT_NONE; + rrec.section_id = OBJ_SEC_NONE; + rrec.link_section_id = pairs_sec->id; + rrec.offset = (u32)(slot_idx * 16u); + rrec.width = 8; + rrec.write_vaddr = pair_vaddr; + rrec.write_file_offset = pair_vaddr; + rrec.kind = R_ABS64; + rrec.target = resolver_id; + rrec.addend = 0; + *append_reloc_slot(img) = rrec; + + /* .iplt.pairs[i].slot = &slot (R_ABS64) */ + memset(&rrec, 0, sizeof(rrec)); + rrec.input_id = LINK_INPUT_NONE; + rrec.section_id = OBJ_SEC_NONE; + rrec.link_section_id = pairs_sec->id; + rrec.offset = (u32)(slot_idx * 16u + 8u); + rrec.width = 8; + rrec.write_vaddr = pair_vaddr + 8u; + rrec.write_file_offset = pair_vaddr + 8u; + rrec.kind = R_ABS64; + rrec.target = slot_id; + rrec.addend = 0; + *append_reloc_slot(img) = rrec; /* Redirect the IFUNC symbol to the stub. Keep its name + * binding so cfree_jit_lookup and external relocs still find @@ -1726,11 +1963,24 @@ static void layout_iplt(Linker* l, LinkImage* img) ++slot_idx; } - /* Stale undefs that named these IFUNCs (e.g. a separate TU - * referencing my_fn) were re-pointed at the same LinkSymbol slot - * during resolve_undefs / link_symbols_to_sections, so updating - * the master slot above is sufficient — those undefs see the - * post-redirect vaddr through their shared slot. */ + /* .init_array entry: one R_ABS64 reloc filling the 8-byte slot + * with __cfree_ifunc_init's resolved address. The CRT walks + * __init_array_start..__init_array_end and calls each entry. */ + if (emit_init_array) { + LinkRelocApply rrec; + memset(&rrec, 0, sizeof(rrec)); + rrec.input_id = LINK_INPUT_NONE; + rrec.section_id = OBJ_SEC_NONE; + rrec.link_section_id = init_sec->id; + rrec.offset = 0; + rrec.width = 8; + rrec.write_vaddr = init_vaddr; + rrec.write_file_offset = init_vaddr; + rrec.kind = R_ABS64; + rrec.target = ifunc_init_sym; + rrec.addend = 0; + *append_reloc_slot(img) = rrec; + } } /* ---- entry symbol ---- */ @@ -1748,7 +1998,7 @@ static void resolve_entry(Linker* l, LinkImage* img) "link: entry symbol '%.*s' not defined", (int)namelen, nm); } - s = &img->syms[id - 1]; + s = LinkSyms_at(&img->syms, id - 1); if (!s->defined) { size_t namelen; const char* nm = pool_str(l->c->global, l->entry_name, &namelen); @@ -1772,22 +2022,12 @@ static void include_archive_member(Linker* l, LinkArchiveMember* mem) { LinkInput* in; LinkInputId id; + u32 idx; if (mem->included) return; - if (l->ninputs >= l->inputs_cap) { - u32 new_cap = l->inputs_cap ? l->inputs_cap * 2u : 8u; - LinkInput* p = (LinkInput*)l->heap->realloc( - l->heap, l->inputs, - sizeof(*l->inputs) * l->inputs_cap, - sizeof(*l->inputs) * new_cap, - _Alignof(LinkInput)); - if (!p) compiler_panic(l->c, no_loc(), - "link: oom growing inputs (archive member)"); - l->inputs = p; - l->inputs_cap = new_cap; - } - id = (LinkInputId)(l->ninputs + 1); - in = &l->inputs[l->ninputs++]; - memset(in, 0, sizeof(*in)); + in = LinkInputs_push(&l->inputs, &idx); + if (!in) compiler_panic(l->c, no_loc(), + "link: oom growing inputs (archive member)"); + id = (LinkInputId)(idx + 1u); in->id = id; in->kind = LINK_INPUT_OBJ_BYTES; /* the input owns the ObjBuilder now */ in->obj = mem->obj; @@ -1803,8 +2043,8 @@ static void scan_presence(Linker* l, SymHash* defined, SymHash* undefs) u32 ii; ObjSymIter* it; ObjSymEntry e; - for (ii = 0; ii < l->ninputs; ++ii) { - ObjBuilder* ob = l->inputs[ii].obj; + for (ii = 0; ii < LinkInputs_count(&l->inputs); ++ii) { + ObjBuilder* ob = LinkInputs_at(&l->inputs, ii)->obj; it = obj_symiter_new(ob); while (obj_symiter_next(it, &e)) { const ObjSym* s = e.sym; @@ -1817,6 +2057,31 @@ static void scan_presence(Linker* l, SymHash* defined, SymHash* undefs) } } +/* True if any currently-included input defines at least one + * STT_GNU_IFUNC symbol. Used to seed __cfree_ifunc_init into the + * archive demand-load wanted set when emitting a static ET_EXEC: the + * synthesized .init_array entry pulls the rt member which carries the + * startup ctor that fills .igot.plt slots. */ +static int inputs_have_defined_ifunc(Linker* l) +{ + u32 ii; + ObjSymIter* it; + ObjSymEntry e; + for (ii = 0; ii < LinkInputs_count(&l->inputs); ++ii) { + ObjBuilder* ob = LinkInputs_at(&l->inputs, ii)->obj; + it = obj_symiter_new(ob); + while (obj_symiter_next(it, &e)) { + const ObjSym* s = e.sym; + if (s->kind == SK_IFUNC) { + obj_symiter_free(it); + return 1; + } + } + obj_symiter_free(it); + } + return 0; +} + /* True if `mem` defines a non-undef SB_GLOBAL or SB_WEAK symbol that's * in `wanted` and not already in `defined`. Both GNU ld and lld pull * archive members on weak defs against an unresolved undef — the @@ -1847,16 +2112,30 @@ static int member_satisfies(LinkArchiveMember* mem, void link_ingest_archives(Linker* l) { u32 a, m; - if (l->narchives == 0) return; + if (LinkArchives_count(&l->archives) == 0) return; /* Pass 1: --whole-archive members are pulled unconditionally. */ - for (a = 0; a < l->narchives; ++a) { - LinkArchive* ar = &l->archives[a]; + for (a = 0; a < LinkArchives_count(&l->archives); ++a) { + LinkArchive* ar = LinkArchives_at(&l->archives, a); if (!ar->whole_archive) continue; for (m = 0; m < ar->nmembers; ++m) include_archive_member(l, &ar->members[m]); } + /* When emitting a static ET_EXEC and any input defines an IFUNC, + * seed __cfree_ifunc_init into the wanted set so demand-load pulls + * libcfree_rt's ifunc_init.c. Layout_iplt later synthesizes a + * .init_array entry referencing this symbol; the rt member's + * implementation walks .iplt.pairs and fills each slot at startup. + * Done once before the demand loop — the seed needs to be present + * on every iteration of the loop's local symhash, so we stash the + * Sym handle and inject it inside the loop body. */ + Sym want_ifunc_init = 0; + if (l->emit_static_exe && inputs_have_defined_ifunc(l)) { + want_ifunc_init = pool_intern_cstr(l->c->global, + "__cfree_ifunc_init"); + } + /* Pass 2: demand loop over the remaining archives. Pulling member A * may introduce undefs satisfied by member B, so iterate to a * fixed point. Bounded by total member count across archives. */ @@ -1866,9 +2145,12 @@ void link_ingest_archives(Linker* l) symhash_init(&defined, l->heap); symhash_init(&undefs, l->heap); scan_presence(l, &defined, &undefs); + if (want_ifunc_init != 0 + && symhash_get(&defined, want_ifunc_init) == LINK_SYM_NONE) + symhash_set(&undefs, want_ifunc_init, 1u); - for (a = 0; a < l->narchives; ++a) { - LinkArchive* ar = &l->archives[a]; + for (a = 0; a < LinkArchives_count(&l->archives); ++a) { + LinkArchive* ar = LinkArchives_at(&l->archives, a); if (ar->whole_archive) continue; for (m = 0; m < ar->nmembers; ++m) { LinkArchiveMember* mem = &ar->members[m]; @@ -1900,15 +2182,15 @@ LinkImage* link_resolve(Linker* l) h = img->heap; /* Per-input map storage. */ - img->ninput_maps = l->ninputs; - img->input_maps = l->ninputs - ? (InputMap*)h->alloc(h, sizeof(*img->input_maps) * l->ninputs, + img->ninput_maps = LinkInputs_count(&l->inputs); + img->input_maps = LinkInputs_count(&l->inputs) + ? (InputMap*)h->alloc(h, sizeof(*img->input_maps) * LinkInputs_count(&l->inputs), _Alignof(InputMap)) : NULL; - if (l->ninputs && !img->input_maps) + if (LinkInputs_count(&l->inputs) && !img->input_maps) compiler_panic(l->c, no_loc(), "link: oom on input maps"); - if (l->ninputs) - memset(img->input_maps, 0, sizeof(*img->input_maps) * l->ninputs); + if (LinkInputs_count(&l->inputs)) + memset(img->input_maps, 0, sizeof(*img->input_maps) * LinkInputs_count(&l->inputs)); resolve_symbols(l, img); { @@ -1924,10 +2206,19 @@ LinkImage* link_resolve(Linker* l) emit_encoding_section_boundaries(l, img); resolve_undefs(l, img); gc_drop_dead_globals(l, img, &g); + /* layout_iplt runs last among the symbol-shaping passes: it + * redirects each defined IFUNC LinkSymbol from the resolver + * to its iplt stub and (under emit_static_exe) materializes a + * .init_array entry pointing at __cfree_ifunc_init. We then + * re-run emit_array_boundaries so __init_array_start/end span + * the synthetic entry. Cross-TU undefs may retain the + * pre-redirect (resolver) vaddr — only a concern for + * GOT-slot fills; not exercised by current tests. */ layout_iplt(l, img); + if (img->niplt) emit_array_boundaries(l, img); { LinkSymId* got_map = NULL; - u32 got_map_size = img->nsyms + 1u; + u32 got_map_size = LinkSyms_count(&img->syms) + 1u; layout_got(l, img, &got_map); emit_reloc_records(l, img, got_map); if (got_map) diff --git a/src/obj/elf_emit.c b/src/obj/elf_emit.c @@ -435,9 +435,7 @@ void emit_elf(Compiler* c, ObjBuilder* ob, Writer* w) /* ---- pass 3: build .rela.<name> contents ------------------------ */ /* Allocate one .rela section per obj section that has any relocs. */ - u32 total_relocs = 0; - for (u32 i = 1; i < nobjsec; ++i) total_relocs += obj_reloc_count(ob, i); - const Reloc* all_relocs = total_relocs ? obj_relocs(ob, 0) : NULL; + u32 total_relocs = obj_reloc_total(ob); typedef struct RelaPlan { u32 obj_section; /* obj section the rela applies to */ @@ -456,7 +454,7 @@ void emit_elf(Compiler* c, ObjBuilder* ob, Writer* w) _Alignof(u64)); u32 j = 0; for (u32 i = 0; i < total_relocs; ++i) { - const Reloc* r = &all_relocs[i]; + const Reloc* r = obj_reloc_at(ob, i); if (r->section_id != si) continue; u32 etype = elf_aarch64_reloc_to(r->kind); if (etype == ELF_R_AARCH64_NONE && r->kind != R_NONE) { diff --git a/src/obj/obj.c b/src/obj/obj.c @@ -1,6 +1,7 @@ /* In-memory ObjBuilder. Section, symbol, group, and reloc storage all - * live in the host heap (env->heap); section bytes use the chunked Buf - * type. Index 0 of each id space is reserved as "none". + * use segmented arrays (core/segvec.h) so the T* pointers obj_*_get + * returns stay valid as the table grows. Section bytes use the chunked + * Buf type. Index 0 of each id space is reserved as "none". * * obj_finalize is the read-side gate: post-finalize, write-side calls * are still legal (the reader paths use them too) but consumers can @@ -10,33 +11,22 @@ #include "core/heap.h" #include "core/pool.h" -#include "core/vec.h" +#include "core/segvec.h" #include <string.h> +SEGVEC_DEFINE(Sections, Section, 5); /* 32 entries per segment */ +SEGVEC_DEFINE(Symbols, ObjSym, 6); /* 64 entries per segment */ +SEGVEC_DEFINE(Relocs, Reloc, 6); /* 64 entries per segment */ +SEGVEC_DEFINE(Groups, ObjGroup, 3); /* 8 entries per segment */ + struct CfreeObjBuilder { - Compiler* c; - Heap* heap; - - Section* sections; - u32 nsections; /* logical count incl. id-0 sentinel */ - u32 sections_cap; - - ObjSym* symbols; - u32 nsymbols; /* logical count incl. id-0 sentinel */ - u32 symbols_cap; - - /* Relocs are stored in one flat array; each Section's range can be - * recovered by linear walk. Sections are not many (low hundreds at - * most) and reloc lookups happen rarely outside emit, so this is - * fine and keeps memory layout simple. */ - Reloc* relocs; - u32 nrelocs; - u32 relocs_cap; - - ObjGroup* groups; - u32 ngroups; /* logical count incl. id-0 sentinel */ - u32 groups_cap; + Compiler* c; + Heap* heap; + Sections sections; /* index 0 reserved as "none" */ + Symbols symbols; /* index 0 reserved as "none" */ + Relocs relocs; /* flat across all sections; filtered on read */ + Groups groups; /* index 0 reserved as "none" */ }; struct ObjSymIter { @@ -44,20 +34,6 @@ struct ObjSymIter { u32 idx; /* next index to return */ }; -/* ---- growth helpers ---- */ - -static int sections_grow(ObjBuilder* ob, u32 want) -{ return VEC_GROW(ob->heap, ob->sections, ob->sections_cap, want); } - -static int symbols_grow(ObjBuilder* ob, u32 want) -{ return VEC_GROW(ob->heap, ob->symbols, ob->symbols_cap, want); } - -static int relocs_grow(ObjBuilder* ob, u32 want) -{ return VEC_GROW(ob->heap, ob->relocs, ob->relocs_cap, want); } - -static int groups_grow(ObjBuilder* ob, u32 want) -{ return VEC_GROW(ob->heap, ob->groups, ob->groups_cap, want); } - /* ---- lifecycle ---- */ ObjBuilder* obj_new(Compiler* c) @@ -68,42 +44,43 @@ ObjBuilder* obj_new(Compiler* c) memset(ob, 0, sizeof(*ob)); ob->c = c; ob->heap = h; - - /* Reserve index 0 in each id space as the "none" sentinel. */ - if (sections_grow(ob, 1) || symbols_grow(ob, 1) || groups_grow(ob, 1)) { + Sections_init(&ob->sections, h); + Symbols_init (&ob->symbols, h); + Relocs_init (&ob->relocs, h); + Groups_init (&ob->groups, h); + + /* Reserve index 0 in each id space as the "none" sentinel. SegVec + * pushes are zeroed, so the sentinel slots have all-zero fields. */ + if (!Sections_push(&ob->sections, NULL) || + !Symbols_push (&ob->symbols, NULL) || + !Groups_push (&ob->groups, NULL)) { obj_free(ob); return NULL; } - memset(&ob->sections[0], 0, sizeof(ob->sections[0])); - memset(&ob->symbols[0], 0, sizeof(ob->symbols[0])); - memset(&ob->groups[0], 0, sizeof(ob->groups[0])); - ob->nsections = 1; - ob->nsymbols = 1; - ob->ngroups = 1; return ob; } void obj_free(ObjBuilder* ob) { - u32 i; + u32 i, n; if (!ob) return; - for (i = 1; i < ob->nsections; ++i) { - buf_fini(&ob->sections[i].bytes); + n = Sections_count(&ob->sections); + for (i = 1; i < n; ++i) { + Section* s = Sections_at(&ob->sections, i); + if (s) buf_fini(&s->bytes); } - for (i = 1; i < ob->ngroups; ++i) { - if (ob->groups[i].sections) { - ob->heap->free(ob->heap, ob->groups[i].sections, - sizeof(ObjSecId) * ob->groups[i].nsections); + n = Groups_count(&ob->groups); + for (i = 1; i < n; ++i) { + ObjGroup* g = Groups_at(&ob->groups, i); + if (g && g->sections) { + ob->heap->free(ob->heap, g->sections, + sizeof(ObjSecId) * g->nsections); } } - if (ob->sections) ob->heap->free(ob->heap, ob->sections, - sizeof(*ob->sections) * ob->sections_cap); - if (ob->symbols) ob->heap->free(ob->heap, ob->symbols, - sizeof(*ob->symbols) * ob->symbols_cap); - if (ob->relocs) ob->heap->free(ob->heap, ob->relocs, - sizeof(*ob->relocs) * ob->relocs_cap); - if (ob->groups) ob->heap->free(ob->heap, ob->groups, - sizeof(*ob->groups) * ob->groups_cap); + Sections_fini(&ob->sections); + Symbols_fini (&ob->symbols); + Relocs_fini (&ob->relocs); + Groups_fini (&ob->groups); ob->heap->free(ob->heap, ob, sizeof(*ob)); } @@ -118,42 +95,40 @@ ObjSecId obj_section(ObjBuilder* ob, Sym name, SecKind kind, u16 flags, u32 alig ObjSecId obj_section_ex(ObjBuilder* ob, Sym name, SecKind kind, SecSem sem, u16 flags, u32 align, u32 entsize, u32 link, u32 info) { - ObjSecId id; - if (sections_grow(ob, ob->nsections + 1)) return OBJ_SEC_NONE; - id = ob->nsections++; - { - Section* s = &ob->sections[id]; - memset(s, 0, sizeof(*s)); - s->name = name; - s->kind = (u16)kind; - s->flags = flags; - s->sem = (u16)sem; - s->ext_kind = OBJ_EXT_NONE; - s->align = align ? align : 1; - s->entsize = entsize; - s->link = (ObjSecId)link; - s->info = info; - s->group_id = OBJ_GROUP_NONE; - s->bss_size = 0; - buf_init(&s->bytes, ob->heap); - } - return id; + u32 id; + Section* s = Sections_push(&ob->sections, &id); + if (!s) return OBJ_SEC_NONE; + s->name = name; + s->kind = (u16)kind; + s->flags = flags; + s->sem = (u16)sem; + s->ext_kind = OBJ_EXT_NONE; + s->align = align ? align : 1; + s->entsize = entsize; + s->link = (ObjSecId)link; + s->info = info; + s->group_id = OBJ_GROUP_NONE; + s->bss_size = 0; + buf_init(&s->bytes, ob->heap); + return (ObjSecId)id; } void obj_section_set_flags(ObjBuilder* ob, ObjSecId id, u16 flags) -{ if (id != OBJ_SEC_NONE && id < ob->nsections) ob->sections[id].flags = flags; } +{ Section* s = Sections_at(&ob->sections, id); if (s && id != OBJ_SEC_NONE) s->flags = flags; } void obj_section_set_align(ObjBuilder* ob, ObjSecId id, u32 align) -{ if (id != OBJ_SEC_NONE && id < ob->nsections) ob->sections[id].align = align ? align : 1; } +{ Section* s = Sections_at(&ob->sections, id); if (s && id != OBJ_SEC_NONE) s->align = align ? align : 1; } void obj_section_set_group(ObjBuilder* ob, ObjSecId id, ObjGroupId gid) -{ if (id != OBJ_SEC_NONE && id < ob->nsections) ob->sections[id].group_id = gid; } +{ Section* s = Sections_at(&ob->sections, id); if (s && id != OBJ_SEC_NONE) s->group_id = gid; } void obj_section_set_ext(ObjBuilder* ob, ObjSecId id, ObjExtKind ek, u32 ext_type, u32 ext_flags) { - if (id == OBJ_SEC_NONE || id >= ob->nsections) return; - Section* s = &ob->sections[id]; + Section* s; + if (id == OBJ_SEC_NONE) return; + s = Sections_at(&ob->sections, id); + if (!s) return; s->ext_kind = (u16)ek; s->ext_type = ext_type; s->ext_flags = ext_flags; @@ -161,33 +136,44 @@ void obj_section_set_ext(ObjBuilder* ob, ObjSecId id, ObjExtKind ek, void obj_write(ObjBuilder* ob, ObjSecId id, const void* data, size_t n) { - if (id == OBJ_SEC_NONE || id >= ob->nsections) return; - buf_write(&ob->sections[id].bytes, data, n); + Section* s; + if (id == OBJ_SEC_NONE) return; + s = Sections_at(&ob->sections, id); + if (s) buf_write(&s->bytes, data, n); } u8* obj_reserve(ObjBuilder* ob, ObjSecId id, size_t n) { - if (id == OBJ_SEC_NONE || id >= ob->nsections) return NULL; - return buf_reserve(&ob->sections[id].bytes, n); + Section* s; + if (id == OBJ_SEC_NONE) return NULL; + s = Sections_at(&ob->sections, id); + return s ? buf_reserve(&s->bytes, n) : NULL; } void obj_reserve_bss(ObjBuilder* ob, ObjSecId id, u32 size, u32 align) { - if (id == OBJ_SEC_NONE || id >= ob->nsections) return; - ob->sections[id].bss_size = size; - if (align) ob->sections[id].align = align; + Section* s; + if (id == OBJ_SEC_NONE) return; + s = Sections_at(&ob->sections, id); + if (!s) return; + s->bss_size = size; + if (align) s->align = align; } u32 obj_pos(ObjBuilder* ob, ObjSecId id) { - if (id == OBJ_SEC_NONE || id >= ob->nsections) return 0; - return buf_pos(&ob->sections[id].bytes); + Section* s; + if (id == OBJ_SEC_NONE) return 0; + s = Sections_at(&ob->sections, id); + return s ? buf_pos(&s->bytes) : 0; } void obj_patch(ObjBuilder* ob, ObjSecId id, u32 ofs, const void* data, size_t n) { - if (id == OBJ_SEC_NONE || id >= ob->nsections) return; - buf_patch(&ob->sections[id].bytes, ofs, data, n); + Section* s; + if (id == OBJ_SEC_NONE) return; + s = Sections_at(&ob->sections, id); + if (s) buf_patch(&s->bytes, ofs, data, n); } ObjSymId obj_symbol(ObjBuilder* ob, Sym name, SymBind bind, SymKind kind, @@ -201,35 +187,32 @@ ObjSymId obj_symbol_ex(ObjBuilder* ob, Sym name, SymBind bind, SymVis vis, SymKind kind, ObjSecId section_id, u64 value, u64 size, u64 common_align) { - ObjSymId id; - if (symbols_grow(ob, ob->nsymbols + 1)) return OBJ_SYM_NONE; - id = ob->nsymbols++; - { - ObjSym* s = &ob->symbols[id]; - memset(s, 0, sizeof(*s)); - s->name = name; - s->bind = (u16)bind; - s->kind = (u16)kind; - s->vis = (u8)vis; - s->ext_kind = OBJ_EXT_NONE; - s->section_id = section_id; - s->value = value; - s->size = size; - s->common_align = common_align; - } - return id; + u32 id; + ObjSym* s = Symbols_push(&ob->symbols, &id); + if (!s) return OBJ_SYM_NONE; + s->name = name; + s->bind = (u16)bind; + s->kind = (u16)kind; + s->vis = (u8)vis; + s->ext_kind = OBJ_EXT_NONE; + s->section_id = section_id; + s->value = value; + s->size = size; + s->common_align = common_align; + return (ObjSymId)id; } void obj_symbol_define(ObjBuilder* ob, ObjSymId id, ObjSecId section_id, u64 value, u64 size) { - if (id == OBJ_SYM_NONE || id >= ob->nsymbols) return; - ob->symbols[id].section_id = section_id; - ob->symbols[id].value = value; - ob->symbols[id].size = size; - if (ob->symbols[id].kind == SK_UNDEF) { - ob->symbols[id].kind = SK_OBJ; /* defaults; caller can re-set */ - } + ObjSym* s; + if (id == OBJ_SYM_NONE) return; + s = Symbols_at(&ob->symbols, id); + if (!s) return; + s->section_id = section_id; + s->value = value; + s->size = size; + if (s->kind == SK_UNDEF) s->kind = SK_OBJ; } void obj_reloc(ObjBuilder* ob, ObjSecId section_id, u32 offset, @@ -242,40 +225,35 @@ void obj_reloc_ex(ObjBuilder* ob, ObjSecId section_id, u32 offset, RelocKind kind, ObjSymId sym, i64 addend, int explicit_addend, int pair) { - if (relocs_grow(ob, ob->nrelocs + 1)) return; - { - Reloc* r = &ob->relocs[ob->nrelocs++]; - r->section_id = section_id; - r->offset = offset; - r->kind = (u16)kind; - r->has_explicit_addend = (u8)(explicit_addend ? 1 : 0); - r->pair = (u8)pair; - r->sym = sym; - r->addend = addend; - } + Reloc* r = Relocs_push(&ob->relocs, NULL); + if (!r) return; + r->section_id = section_id; + r->offset = offset; + r->kind = (u16)kind; + r->has_explicit_addend = (u8)(explicit_addend ? 1 : 0); + r->pair = (u8)pair; + r->sym = sym; + r->addend = addend; } ObjGroupId obj_group(ObjBuilder* ob, Sym name, ObjSymId signature, u32 flags) { - ObjGroupId id; - if (groups_grow(ob, ob->ngroups + 1)) return OBJ_GROUP_NONE; - id = ob->ngroups++; - { - ObjGroup* g = &ob->groups[id]; - memset(g, 0, sizeof(*g)); - g->name = name; - g->signature = signature; - g->flags = flags; - } - return id; + u32 id; + ObjGroup* g = Groups_push(&ob->groups, &id); + if (!g) return OBJ_GROUP_NONE; + g->name = name; + g->signature = signature; + g->flags = flags; + return (ObjGroupId)id; } void obj_group_add_section(ObjBuilder* ob, ObjGroupId gid, ObjSecId sec) { ObjGroup* g; ObjSecId* p; - if (gid == OBJ_GROUP_NONE || gid >= ob->ngroups) return; - g = &ob->groups[gid]; + if (gid == OBJ_GROUP_NONE) return; + g = Groups_at(&ob->groups, gid); + if (!g) return; /* Linear realloc — group section counts are tiny (handful per group). */ p = (ObjSecId*)ob->heap->realloc( ob->heap, g->sections, @@ -299,47 +277,41 @@ void obj_finalize(ObjBuilder* ob) /* ---- read side ---- */ -u32 obj_section_count(const ObjBuilder* ob) { return ob->nsections; } +u32 obj_section_count(const ObjBuilder* ob) { return Sections_count(&ob->sections); } const Section* obj_section_get(const ObjBuilder* ob, ObjSecId id) { - if (id == OBJ_SEC_NONE || id >= ob->nsections) return NULL; - return &ob->sections[id]; + if (id == OBJ_SEC_NONE) return NULL; + return Sections_at(&ob->sections, id); } u32 obj_reloc_count(const ObjBuilder* ob, ObjSecId id) { - u32 i, n = 0; - for (i = 0; i < ob->nrelocs; ++i) if (ob->relocs[i].section_id == id) ++n; + u32 i, total = Relocs_count(&ob->relocs), n = 0; + for (i = 0; i < total; ++i) { + const Reloc* r = Relocs_at(&ob->relocs, i); + if (r->section_id == id) ++n; + } return n; } -const Reloc* obj_relocs(const ObjBuilder* ob, ObjSecId id) -{ - /* Returns the first reloc record whose section_id matches; the caller - * is expected to walk forward by obj_reloc_count and check - * `r->section_id == id` to terminate, since relocs from different - * sections may be interleaved. - * - * That contract is awkward enough that ELF emitters in practice walk - * the entire flat array filtering by section. We emulate that by - * returning the start of the flat array; callers must filter. */ - (void)id; - return ob->relocs; -} +u32 obj_reloc_total(const ObjBuilder* ob) { return Relocs_count(&ob->relocs); } + +const Reloc* obj_reloc_at(const ObjBuilder* ob, u32 idx) +{ return Relocs_at(&ob->relocs, idx); } const ObjSym* obj_symbol_get(const ObjBuilder* ob, ObjSymId id) { - if (id == OBJ_SYM_NONE || id >= ob->nsymbols) return NULL; - return &ob->symbols[id]; + if (id == OBJ_SYM_NONE) return NULL; + return Symbols_at(&ob->symbols, id); } -u32 obj_group_count(const ObjBuilder* ob) { return ob->ngroups; } +u32 obj_group_count(const ObjBuilder* ob) { return Groups_count(&ob->groups); } const ObjGroup* obj_group_get(const ObjBuilder* ob, ObjGroupId id) { - if (id == OBJ_GROUP_NONE || id >= ob->ngroups) return NULL; - return &ob->groups[id]; + if (id == OBJ_GROUP_NONE) return NULL; + return Groups_at(&ob->groups, id); } ObjSymIter* obj_symiter_new(const ObjBuilder* ob) @@ -354,9 +326,12 @@ ObjSymIter* obj_symiter_new(const ObjBuilder* ob) int obj_symiter_next(ObjSymIter* it, ObjSymEntry* out) { - if (!it || it->idx >= it->ob->nsymbols) return 0; + const ObjSym* s; + if (!it) return 0; + s = Symbols_at(&it->ob->symbols, it->idx); + if (!s) return 0; out->id = it->idx; - out->sym = &it->ob->symbols[it->idx]; + out->sym = s; it->idx++; return 1; } diff --git a/src/obj/obj.h b/src/obj/obj.h @@ -246,7 +246,8 @@ void obj_finalize(ObjBuilder*); u32 obj_section_count(const ObjBuilder*); const Section* obj_section_get (const ObjBuilder*, ObjSecId id); u32 obj_reloc_count (const ObjBuilder*, ObjSecId section_id); -const Reloc* obj_relocs (const ObjBuilder*, ObjSecId section_id); +u32 obj_reloc_total (const ObjBuilder*); +const Reloc* obj_reloc_at (const ObjBuilder*, u32 idx); /* 0..total-1 */ const ObjSym* obj_symbol_get (const ObjBuilder*, ObjSymId); u32 obj_group_count (const ObjBuilder*); const ObjGroup* obj_group_get (const ObjBuilder*, ObjGroupId id); diff --git a/test/elf/unit/smoke.c b/test/elf/unit/smoke.c @@ -188,15 +188,12 @@ static void verify_shape(const ObjBuilder* ob, Pool* p) u32 nr = obj_reloc_count(ob, data_id); CHECK(nr == 1, ".data reloc count = %u, want 1", nr); - /* obj_relocs returns the start of the flat reloc array; callers - * filter by section_id. Total count = sum across sections. */ - const Reloc* all = obj_relocs(ob, data_id); + /* Walk the flat reloc array; filter by section_id. */ const Reloc* found = NULL; - u32 total = 0; - for (u32 j = 0; j < obj_section_count(ob); ++j) - total += obj_reloc_count(ob, j); + u32 total = obj_reloc_total(ob); for (u32 i = 0; i < total; ++i) { - if (all[i].section_id == data_id) { found = &all[i]; break; } + const Reloc* r = obj_reloc_at(ob, i); + if (r->section_id == data_id) { found = r; break; } } CHECK(found != NULL, "no reloc on .data"); if (found) {