kit

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

commit 54c8a075d684c8901b0dd5e69625569d0a3f8137
parent 652fef0cc277f799a66c21f463aa6a56fd1e8d93
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu, 14 May 2026 17:33:36 -0700

link: make section grouping linear

Diffstat:
Msrc/link/link_layout.c | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
1 file changed, 66 insertions(+), 22 deletions(-)

diff --git a/src/link/link_layout.c b/src/link/link_layout.c @@ -95,6 +95,8 @@ typedef struct SecRef { LinkSectionId link_sec_id; } SecRef; +#define PLACE_NONE ((u32)~0u) + /* Within a bucket, input sections sharing a name are placed contiguously * — the standard "merge sections by name" rule. Without this the .init * prologue from crti.o and the matching epilogue from crtn.o (both in @@ -103,22 +105,38 @@ typedef struct SecRef { * * 1. Build a flat list of (input_idx, obj_sec_id) for kept+live * sections. - * 2. For each bucket, do a stable group-by-name pass over the list: - * take the first ungrouped section in input order, claim every - * later same-name+same-bucket section in input order, lay out the - * whole group adjacent. Then move to the next ungrouped section. - * 3. Different-name groups appear in first-occurrence order. - * - * O(N²) on section count, fine for our N. */ + * 2. While collecting, append each section to an O(1)-expected lookup + * keyed by (bucket, name). The group array is append-only, so group + * order is still first occurrence; each group's linked list preserves + * input order. + * 3. Lay out groups in first-occurrence order. + */ typedef struct PlaceEntry { u32 input_idx; ObjSecId obj_sec_id; Sym name; SegBucket bucket; - u8 placed; - u8 pad[3]; + u32 next; } PlaceEntry; +typedef struct PlaceGroup { + u32 head; + u32 tail; +} PlaceGroup; + +static inline u32 place_group_hash_(u64 key) { return hash_u64(key); } +HASHMAP_DEFINE(PlaceGroupHash, u64, u32, place_group_hash_); + +static u64 place_group_key(Sym name, SegBucket bucket) { + return (((u64)name + 1u) << 3) | ((u64)bucket + 1u); +} + +static u32 place_group_hash_cap(u32 n) { + u32 cap = CFREE_HASHMAP_INIT_CAP; + while (cap < 0x80000000u && (cap - cap / 4u) < n) cap <<= 1; + return cap; +} + static void link_layout_sections_scripted(Linker* l, LinkImage* img, const GcLive* g); @@ -152,21 +170,54 @@ void link_layout_sections(Linker* l, LinkImage* img, const GcLive* g) { total_kept ? (PlaceEntry*)h->alloc(h, sizeof(*entries) * total_kept, _Alignof(PlaceEntry)) : NULL; + PlaceGroup* groups = + total_kept ? (PlaceGroup*)h->alloc(h, sizeof(*groups) * total_kept, + _Alignof(PlaceGroup)) + : NULL; + PlaceGroupHash group_map; + u32 ngroups = 0; if (total_kept && !entries) compiler_panic(img->c, no_loc(), "link: oom on placement entries"); + if (total_kept && !groups) + compiler_panic(img->c, no_loc(), "link: oom on placement groups"); + if (total_kept) { + PlaceGroupHash_init_cap(&group_map, h, place_group_hash_cap(total_kept)); + if (!group_map.slots) + compiler_panic(img->c, no_loc(), "link: oom on placement group map"); + } { u32 e = 0; 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); + u64 key; + u32* hit; + u32 group_idx; if (!s || !link_section_kept(s) || !link_gc_live_get(g, ii, j)) continue; entries[e].input_idx = ii; entries[e].obj_sec_id = j; entries[e].name = s->name; entries[e].bucket = link_bucket_for(s->flags); - entries[e].placed = 0; + entries[e].next = PLACE_NONE; + + key = place_group_key(entries[e].name, entries[e].bucket); + hit = PlaceGroupHash_get(&group_map, key); + if (hit) { + group_idx = *hit - 1u; + } else { + group_idx = ngroups++; + groups[group_idx].head = PLACE_NONE; + groups[group_idx].tail = PLACE_NONE; + PlaceGroupHash_set(&group_map, key, group_idx + 1u); + } + if (groups[group_idx].tail == PLACE_NONE) { + groups[group_idx].head = e; + } else { + entries[groups[group_idx].tail].next = e; + } + groups[group_idx].tail = e; ++e; } } @@ -183,18 +234,10 @@ void link_layout_sections(Linker* l, LinkImage* img, const GcLive* g) { /* Pass 2: place sections, grouped by name within each bucket and * in first-occurrence order across groups. */ - for (u32 i = 0; i < total_kept; ++i) { - if (entries[i].placed) continue; - - Sym group_name = entries[i].name; - SegBucket bucket = entries[i].bucket; - - /* Walk the remaining list in input order; each match in the - * same bucket+name lays out adjacent. */ - for (u32 k = i; k < total_kept; ++k) { + for (u32 gi = 0; gi < ngroups; ++gi) { + for (u32 k = groups[gi].head; k != PLACE_NONE; k = entries[k].next) { PlaceEntry* pe = &entries[k]; - if (pe->placed) continue; - if (pe->bucket != bucket || pe->name != group_name) continue; + SegBucket bucket = pe->bucket; ObjBuilder* ob = LinkInputs_at(&l->inputs, pe->input_idx)->obj; InputMap* m = &img->input_maps[pe->input_idx]; @@ -236,10 +279,11 @@ void link_layout_sections(Linker* l, LinkImage* img, const GcLive* g) { ls->sem = s->sem; ls->segment_id = (LinkSegmentId)(bucket + 1u); /* 1..3 sentinel */ m->section[pe->obj_sec_id] = lsid; - pe->placed = 1; } } + if (total_kept) PlaceGroupHash_fini(&group_map); + if (groups) h->free(h, groups, sizeof(*groups) * total_kept); if (entries) h->free(h, entries, sizeof(*entries) * total_kept); /* Materialize one LinkSegment per non-empty bucket, then assign