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:
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) {