kit

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

commit 1dae315948770c1020a5231df4d4a7698b48aa7c
parent c52ea923e42a779d36aba62e7d324f737e41cce6
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue, 16 Jun 2026 14:33:35 -0700

link: coalesce sections sharing a :phdr; honor multi-phdr sections

Each scripted output section made its own LinkSegment and only phdrs[0]
was consulted. Implement GNU ld phdr semantics:
 (a) a section whose primary :phdr matches the currently-open segment
     and is vaddr-contiguous coalesces into that one PT_LOAD (extending
     its byte buffer); and
 (b) each additional :phdr a section names becomes an alias program
     header covering just that section's vaddr/file range, with no byte
     buffer of its own.

The per-segment primary phdr name is tracked in a function-local array
(no LinkSegment field). Alias segments carry a NULL byte buffer; the ELF
byte writer now skips NULL-buffer segments (their file range is already
on disk via the primary). nseg_max is bumped to count additional phdrs.
Factored a script_apply_phdr helper out of the duplicated phdr-attr
setup.

Adds check_phdr_coalesce_and_multi (red->green). test-elf 41/0,
existing scripted layout checks still green.

Diffstat:
Msrc/link/link_layout.c | 235++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Msrc/obj/elf/link.c | 7++++++-
2 files changed, 186 insertions(+), 56 deletions(-)

diff --git a/src/link/link_layout.c b/src/link/link_layout.c @@ -831,6 +831,32 @@ static u64 eval_link_expr(Linker* l, LinkImage* img, u64 dot, } } +/* Set a segment's phdr attributes (type/filehdr/phdrs/flags) from the named + * PHDRS entry `name`. Panics if the name is unknown. `dot`/`outs`/`nouts` are + * forwarded to the FLAGS() expression evaluation. */ +static void script_apply_phdr(Linker* l, LinkImage* img, + const KitLinkScript* script, LinkSegment* seg, + KitSlice name, u64 dot, const ScriptOutInfo* outs, + u32 nouts) { + const KitLinkPhdr* ph = script_find_phdr(script, name); + if (!ph) + compiler_panic(l->c, SRCLOC_NONE, "linker script: unknown PHDR '%.*s'", + SLICE_ARG(name)); + seg->phdr_type = ph->type; + seg->phdr_filehdr = ph->filehdr ? 1u : 0u; + seg->phdr_phdrs = ph->phdrs ? 1u : 0u; + if (ph->flags) { + int err = 0; + seg->phdr_flags = (u32)eval_link_expr(l, img, dot, outs, nouts, ph->flags, + &err); + if (err) + compiler_panic(l->c, SRCLOC_NONE, + "linker script: invalid PHDR FLAGS for '%.*s'", + SLICE_ARG(ph->name)); + seg->phdr_flags_set = 1u; + } +} + /* Format-aware C-symbol mangling for linker-synthesized boundaries. */ static Sym boundary_name(Linker* l, const char* name) { return obj_format_c_mangle(l->c, name); @@ -1138,9 +1164,16 @@ static void link_layout_sections_scripted(Linker* l, LinkImage* img, * next unapplied top_asn. */ u32 dot_cursor = 0; + /* Upper bound on segments: each non-discard section contributes at least + * one, plus one per *additional* :phdr it names (a section listing several + * phdrs appears under each as its own program header). Coalescing sections + * that share a phdr only reduces the count, so this stays an upper bound. */ u32 nseg_max = 0; - for (si = 0; si < script->nsections; ++si) - if (!slice_eq_cstr(script->sections[si].name, "/DISCARD/")) ++nseg_max; + for (si = 0; si < script->nsections; ++si) { + const KitLinkOutputSection* os_ = &script->sections[si]; + if (slice_eq_cstr(os_->name, "/DISCARD/")) continue; + nseg_max += os_->nphdrs ? os_->nphdrs : 1u; + } img->segments = nseg_max ? (LinkSegment*)h->alloc(h, sizeof(*img->segments) * nseg_max, _Alignof(LinkSegment)) @@ -1163,6 +1196,22 @@ static void link_layout_sections_scripted(Linker* l, LinkImage* img, sizeof(*img->segment_bytes_cap) * nseg_max); } + /* Per-segment primary :phdr name (parallel to img->segments while we build + * it); a new section naming the same phdr as the currently-open segment and + * vaddr-contiguous with it coalesces into that one PT_LOAD. Kept local — no + * LinkSegment field needed. open_seg is the index of the coalescable open + * segment, or (u32)-1 when none can be extended. */ + KitSlice* seg_phdr_name = NULL; + u32 open_seg = (u32)-1; + if (nseg_max) { + seg_phdr_name = + (KitSlice*)h->alloc(h, sizeof(*seg_phdr_name) * nseg_max, + _Alignof(KitSlice)); + if (!seg_phdr_name) + compiler_panic(img->c, SRCLOC_NONE, "link: oom on segment phdr names"); + memset(seg_phdr_name, 0, sizeof(*seg_phdr_name) * nseg_max); + } + for (si = 0; si < script->nsections; ++si) { const KitLinkOutputSection* os = &script->sections[si]; int is_discard = slice_eq_cstr(os->name, "/DISCARD/"); @@ -1408,71 +1457,145 @@ static void link_layout_sections_scripted(Linker* l, LinkImage* img, continue; } - seg = &img->segments[img->nsegments]; - memset(seg, 0, sizeof(*seg)); - seg->id = seg_id; - seg->flags = SF_ALLOC | perms; - seg->vaddr = sec_start_dot; - seg->paddr = lma_start; - file_cursor = ALIGN_UP(file_cursor, (u64)PAGE_SIZE); - seg->file_offset = file_cursor; - seg->mem_size = mem_size_accum; - seg->file_size = file_size_accum; - seg->align = align_max; - seg->nsections = nsec_in_seg; - if (os->nphdrs) { - const KitLinkPhdr* ph = script_find_phdr(script, os->phdrs[0]); - if (!ph) - compiler_panic(l->c, SRCLOC_NONE, - "linker script: unknown PHDR '%.*s'", - SLICE_ARG(os->phdrs[0])); - seg->phdr_type = ph->type; - seg->phdr_filehdr = ph->filehdr ? 1u : 0u; - seg->phdr_phdrs = ph->phdrs ? 1u : 0u; - if (ph->flags) { + /* (a) Coalesce: if this section's PRIMARY :phdr matches the currently-open + * segment and the section is vaddr-contiguous with it, extend that segment + * into one PT_LOAD instead of opening a new one. A section listing several + * phdrs may still coalesce its primary here; its additional phdrs become + * alias program headers below covering just this section's range. FLAGS() + * / AT() seal a segment (they override the geometry) and never coalesce. */ + KitSlice primary_phdr = os->nphdrs ? os->phdrs[0] : KIT_SLICE_NULL; + int can_coalesce = + open_seg != (u32)-1 && primary_phdr.s && seg_phdr_name[open_seg].s && + slice_eq(seg_phdr_name[open_seg], primary_phdr) && + sec_start_dot == img->segments[open_seg].vaddr + + img->segments[open_seg].mem_size && + !os->lma && !os->flags; + + /* Per-section file geometry (used by both the primary placement and the + * alias program headers, which describe only this section's range). */ + u64 this_file_off; + u64 this_vaddr = sec_start_dot; + u64 this_paddr = lma_start; + + if (can_coalesce) { + LinkSegment* oseg = &img->segments[open_seg]; + u64 prev_file = oseg->file_size; + u64 new_file = prev_file + file_size_accum; + seg = oseg; + this_file_off = seg->file_offset + prev_file; + seg->mem_size += mem_size_accum; + /* This section's bytes follow the open segment's directly (no page + * break); rebase its LinkSections into the open segment. */ + { + u32 fi; + for (fi = first_section_idx; fi < img->nsections; ++fi) { + LinkSection* ls = &img->sections[fi]; + ls->segment_id = seg->id; + ls->file_offset = this_file_off + ls->file_offset; + } + } + file_cursor += file_size_accum; + if (file_size_accum && !l->jit_mode) { + u8* grown = (u8*)h->realloc(h, img->segment_bytes[open_seg], + img->segment_bytes_cap[open_seg], + (size_t)new_file, 16); + if (!grown) + compiler_panic(img->c, SRCLOC_NONE, + "link: oom growing coalesced segment bytes"); + img->segment_bytes[open_seg] = grown; + img->segment_bytes_cap[open_seg] = (size_t)new_file; + script_pattern_fill(grown + prev_file, (size_t)file_size_accum, + fill_value, fill_width); + } + seg->file_size = new_file; + seg->nsections += nsec_in_seg; + } else { + seg = &img->segments[img->nsegments]; + memset(seg, 0, sizeof(*seg)); + seg->id = seg_id; + seg->flags = SF_ALLOC | perms; + seg->vaddr = sec_start_dot; + seg->paddr = lma_start; + file_cursor = ALIGN_UP(file_cursor, (u64)PAGE_SIZE); + seg->file_offset = file_cursor; + this_file_off = seg->file_offset; + seg->mem_size = mem_size_accum; + seg->file_size = file_size_accum; + seg->align = align_max; + seg->nsections = nsec_in_seg; + /* Primary phdr names the segment's program-header attributes; the name + * is recorded so a following same-phdr contiguous section coalesces. */ + if (os->nphdrs) { + script_apply_phdr(l, img, script, seg, os->phdrs[0], dot, outs, + script->nsections); + seg_phdr_name[img->nsegments] = os->phdrs[0]; + } else { + seg_phdr_name[img->nsegments] = KIT_SLICE_NULL; + } + if (os->flags) { int err = 0; seg->phdr_flags = (u32)eval_link_expr(l, img, dot, outs, script->nsections, - ph->flags, &err); + os->flags, &err); if (err) compiler_panic(l->c, SRCLOC_NONE, - "linker script: invalid PHDR FLAGS for '%.*s'", - SLICE_ARG(ph->name)); + "linker script: invalid FLAGS for section '%.*s'", + SLICE_ARG(os->name)); seg->phdr_flags_set = 1u; } - } - if (os->flags) { - int err = 0; - seg->phdr_flags = - (u32)eval_link_expr(l, img, dot, outs, script->nsections, os->flags, - &err); - if (err) - compiler_panic(l->c, SRCLOC_NONE, - "linker script: invalid FLAGS for section '%.*s'", - SLICE_ARG(os->name)); - seg->phdr_flags_set = 1u; - } - file_cursor += file_size_accum; - if (file_size_accum && !l->jit_mode) { - img->segment_bytes[img->nsegments] = - (u8*)h->alloc(h, (size_t)file_size_accum, 16); - if (!img->segment_bytes[img->nsegments]) - compiler_panic(img->c, SRCLOC_NONE, - "link: oom on scripted segment bytes"); - img->segment_bytes_cap[img->nsegments] = (size_t)file_size_accum; - script_pattern_fill(img->segment_bytes[img->nsegments], - (size_t)file_size_accum, fill_value, fill_width); + file_cursor += file_size_accum; + if (file_size_accum && !l->jit_mode) { + img->segment_bytes[img->nsegments] = + (u8*)h->alloc(h, (size_t)file_size_accum, 16); + if (!img->segment_bytes[img->nsegments]) + compiler_panic(img->c, SRCLOC_NONE, + "link: oom on scripted segment bytes"); + img->segment_bytes_cap[img->nsegments] = (size_t)file_size_accum; + script_pattern_fill(img->segment_bytes[img->nsegments], + (size_t)file_size_accum, fill_value, fill_width); + } + { + u32 fi; + for (fi = first_section_idx; fi < img->nsections; ++fi) { + LinkSection* ls = &img->sections[fi]; + ls->file_offset = seg->file_offset + (ls->file_offset); + } + } + /* A primary segment (phdr-named, no FLAGS/AT) is coalescable by a + * following same-phdr contiguous section. No-phdr / FLAGS / AT segments + * are sealed (NULL name never matches a phdr-named follower, matching + * the pre-coalesce per-section behavior). */ + open_seg = (os->nphdrs && !os->flags && !os->lma) ? img->nsegments + : (u32)-1; + img->nsegments++; } + /* (b) Each ADDITIONAL :phdr the section names yields an alias program + * header covering only THIS section's vaddr/file range, with no byte + * buffer of its own (the bytes are on disk via the primary; the ELF byte + * writer skips NULL-buffer segments). */ { - u32 fi; - for (fi = first_section_idx; fi < img->nsections; ++fi) { - LinkSection* ls = &img->sections[fi]; - ls->file_offset = seg->file_offset + (ls->file_offset); + u32 pi; + for (pi = 1; pi < os->nphdrs; ++pi) { + LinkSegment* aseg = &img->segments[img->nsegments]; + memset(aseg, 0, sizeof(*aseg)); + aseg->id = (LinkSegmentId)(img->nsegments + 1u); + aseg->flags = SF_ALLOC | perms; + aseg->vaddr = this_vaddr; + aseg->paddr = this_paddr; + aseg->file_offset = this_file_off; + aseg->mem_size = mem_size_accum; + aseg->file_size = file_size_accum; + aseg->align = align_max; + aseg->nsections = 0; /* aliases own no sections */ + script_apply_phdr(l, img, script, aseg, os->phdrs[pi], dot, outs, + script->nsections); + seg_phdr_name[img->nsegments] = os->phdrs[pi]; + img->segment_bytes[img->nsegments] = NULL; + img->segment_bytes_cap[img->nsegments] = 0; + img->nsegments++; } } - - img->nsegments++; } /* Apply any trailing dot assignments after the last output section. */ @@ -1505,6 +1628,8 @@ static void link_layout_sections_scripted(Linker* l, LinkImage* img, h->free(h, claimed, sizeof(*claimed) * ni); } if (outs) h->free(h, outs, sizeof(*outs) * script->nsections); + if (seg_phdr_name) + h->free(h, seg_phdr_name, sizeof(*seg_phdr_name) * nseg_max); if (region_cursor) h->free(h, region_cursor, sizeof(*region_cursor) * script->nregions); if (load_region_cursor) diff --git a/src/obj/elf/link.c b/src/obj/elf/link.c @@ -1643,7 +1643,12 @@ void link_emit_elf(LinkImage* img, Writer* w) { u32 i; for (i = 0; i < img->nsegments; ++i) { const LinkSegment* seg = &img->segments[i]; - if (seg->file_size == 0) continue; + /* Alias program headers (a section listed under several :phdrs) have no + * byte buffer of their own — their file range already lives on disk via + * the primary segment, so skip them here (their phdr still describes the + * shared file range). */ + if (seg->file_size == 0 || img->segment_bytes[seg->id - 1] == NULL) + continue; if (cur_off < seg->file_offset) { write_zeroes(w, (size_t)(seg->file_offset - cur_off)); cur_off = seg->file_offset;