kit

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

commit d49b364900f22f9bdc60b32ebc598c59efe64e53
parent b2c078511489aa871aed0d68e564019ee8c02760
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Mon, 25 May 2026 18:29:08 -0700

macho: lower split sections to atoms

Represent Mach-O function/data section splitting with explicit object atoms instead of one physical section per symbol. Teach object builders, Mach-O read/write, linker GC, layout, relocation emission, scripted layout, and JIT append to track atom offsets and map live atoms onto link sections.

Emit MH_SUBSECTIONS_VIA_SYMBOLS for atomized Mach-O objects, preserve retain semantics with NO_DEAD_STRIP, and add regressions for many -ffunction-sections functions, dead-atom relocations, and linker-script placement of atomized Mach-O input.

Diffstat:
MMakefile | 27++++++++++++++++++++-------
Mdoc/OPTv2.md | 4++--
Mdoc/WASM.md | 10+++++-----
Mdriver/cc.c | 4++++
Msrc/arch/aa64/emit.c | 4++++
Msrc/arch/arch.h | 2++
Msrc/arch/rv64/emit.c | 4++++
Msrc/arch/x64/emit.c | 4++++
Msrc/cg/data.c | 27+++++++++++++++++++++++----
Msrc/cg/internal.h | 4+++-
Msrc/cg/session.c | 9+++++++--
Msrc/link/link.c | 23+++++++++++++++++++++++
Msrc/link/link.h | 3+++
Msrc/link/link_internal.h | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/link/link_jit.c | 145++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Msrc/link/link_layout.c | 212+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/link/link_reloc_layout.c | 40++++++++++++++++++++++++++--------------
Msrc/link/link_resolve.c | 348+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Msrc/obj/format.h | 1+
Msrc/obj/macho/emit.c | 35+++++++++++++++++++++++++++++++----
Msrc/obj/macho/macho.h | 1+
Msrc/obj/macho/read.c | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/obj/obj.c | 98++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/obj/obj.h | 25+++++++++++++++++++++++++
Msrc/obj/obj_secnames.c | 8++++++++
Msrc/obj/registry.c | 1+
Msrc/opt/pass_lower.c | 9+++------
Mtest/driver/run.sh | 39+++++++++++++++++++++++++++++++++++++++
Mtest/libc/glibc/extract.sh | 2+-
Mtest/libc/musl/extract.sh | 2+-
Mtest/link/CORPUS.md | 7+++++++
Atest/link/cases/40_macho_gc_dead_atom_reloc/a.c | 9+++++++++
Atest/link/cases/40_macho_gc_dead_atom_reloc/cflags | 1+
Atest/link/cases/40_macho_gc_dead_atom_reloc/gc_absent | 2++
Atest/link/cases/40_macho_gc_dead_atom_reloc/linker_flags | 1+
Atest/link/cases/40_macho_gc_dead_atom_reloc/targets | 1+
Atest/link/cases/41_macho_many_atom_relocs/a.c | 28++++++++++++++++++++++++++++
Atest/link/cases/41_macho_many_atom_relocs/cflags | 1+
Atest/link/cases/41_macho_many_atom_relocs/expected | 1+
Atest/link/cases/41_macho_many_atom_relocs/gc_absent | 2++
Atest/link/cases/41_macho_many_atom_relocs/gc_present | 2++
Atest/link/cases/41_macho_many_atom_relocs/linker_flags | 1+
Atest/link/cases/41_macho_many_atom_relocs/targets | 1+
Atest/link/cases/41_macho_script_atoms/a.c | 6++++++
Atest/link/cases/41_macho_script_atoms/cflags | 1+
Atest/link/cases/41_macho_script_atoms/gc_absent | 1+
Atest/link/cases/41_macho_script_atoms/linker_flags | 1+
Atest/link/cases/41_macho_script_atoms/linker_script | 1+
Atest/link/cases/41_macho_script_atoms/script.lds | 21+++++++++++++++++++++
Atest/link/cases/41_macho_script_atoms/targets | 1+
Mtest/link/run.sh | 2+-
Mtest/parse/run.sh | 2+-
Mtest/parse/run_errors.sh | 2+-
Mtest/test.mk | 114++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
54 files changed, 1154 insertions(+), 278 deletions(-)

diff --git a/Makefile b/Makefile @@ -363,6 +363,9 @@ BIN = $(BUILD_DIR)/cfree format \ clean \ bootstrap \ + bootstrap-debug \ + bootstrap-release \ + _do-bootstrap \ bootstrap-test-toy \ bench-opt @@ -445,16 +448,26 @@ BOOTSTRAP_STAGE2_BIN = $(BOOTSTRAP_STAGE2_DIR)/cfree BOOTSTRAP_STAGE3_BIN = $(BOOTSTRAP_STAGE3_DIR)/cfree BOOTSTRAP_TOOLS = cc ld ar ranlib as BOOTSTRAP_HOST_MODE_CFLAGS = +BOOTSTRAP_HOST_MODE_LDFLAGS = BOOTSTRAP_STAMP = $(BOOTSTRAP_DIR)/.stamp BOOTSTRAP_RT_LIBS = $(addprefix $(RT_BUILD_DIR)/,$(addsuffix /libcfree_rt.a,$(RT_DEFAULT_VARIANTS))) BOOTSTRAP_MAKEFILES = Makefile mk/config.mk rt/Makefile ifeq ($(RELEASE),1) -bootstrap $(BOOTSTRAP_STAMP): HOST_OPTFLAGS = -O1 -bootstrap $(BOOTSTRAP_STAMP): BOOTSTRAP_HOST_MODE_CFLAGS = $(HOST_MODE_CFLAGS) +$(BOOTSTRAP_STAMP): HOST_OPTFLAGS = -O1 +$(BOOTSTRAP_STAMP): BOOTSTRAP_HOST_MODE_CFLAGS = $(HOST_MODE_CFLAGS) +$(BOOTSTRAP_STAMP): BOOTSTRAP_HOST_MODE_LDFLAGS = -Wl,--gc-sections endif -bootstrap: $(BOOTSTRAP_STAMP) +bootstrap: bootstrap-debug bootstrap-release + +bootstrap-debug: + $(MAKE) RELEASE=0 BUILD_DIR='$(BUILD_DIR)/debug' _do-bootstrap + +bootstrap-release: + $(MAKE) RELEASE=1 BUILD_DIR='$(BUILD_DIR)/release' _do-bootstrap + +_do-bootstrap: $(BOOTSTRAP_STAMP) $(BOOTSTRAP_STAMP): $(BIN) $(BOOTSTRAP_RT_LIBS) $(BOOTSTRAP_MAKEFILES) rm -rf $(BOOTSTRAP_DIR) @@ -466,7 +479,7 @@ $(BOOTSTRAP_STAMP): $(BIN) $(BOOTSTRAP_RT_LIBS) $(BOOTSTRAP_MAKEFILES) RELEASE='$(RELEASE)' \ HOST_OPTFLAGS='$(HOST_OPTFLAGS)' \ HOST_MODE_CFLAGS='$(BOOTSTRAP_HOST_MODE_CFLAGS)' \ - HOST_MODE_LDFLAGS= \ + HOST_MODE_LDFLAGS='$(BOOTSTRAP_HOST_MODE_LDFLAGS)' \ CC='$(abspath $(BOOTSTRAP_STAGE1_DIR))/cc' \ AR='$(abspath $(BOOTSTRAP_STAGE1_DIR))/ar' \ LD='$(abspath $(BOOTSTRAP_STAGE1_DIR))/ld' @@ -476,7 +489,7 @@ $(BOOTSTRAP_STAMP): $(BIN) $(BOOTSTRAP_RT_LIBS) $(BOOTSTRAP_MAKEFILES) RELEASE='$(RELEASE)' \ HOST_OPTFLAGS='$(HOST_OPTFLAGS)' \ HOST_MODE_CFLAGS='$(BOOTSTRAP_HOST_MODE_CFLAGS)' \ - HOST_MODE_LDFLAGS= \ + HOST_MODE_LDFLAGS='$(BOOTSTRAP_HOST_MODE_LDFLAGS)' \ CC='$(abspath $(BOOTSTRAP_STAGE2_DIR))/cc' \ AR='$(abspath $(BOOTSTRAP_STAGE2_DIR))/ar' \ LD='$(abspath $(BOOTSTRAP_STAGE2_DIR))/ld' @@ -484,8 +497,8 @@ $(BOOTSTRAP_STAMP): $(BIN) $(BOOTSTRAP_RT_LIBS) $(BOOTSTRAP_MAKEFILES) shasum -a 256 $(BOOTSTRAP_STAGE2_BIN) $(BOOTSTRAP_STAGE3_BIN) @touch $@ -bootstrap-test-toy: $(BOOTSTRAP_STAMP) - @CFREE='$(abspath $(BOOTSTRAP_STAGE3_BIN))' test/toy/run.sh +bootstrap-test-toy: bootstrap-debug + @CFREE='$(abspath $(BUILD_DIR)/debug/bootstrap/stage3/cfree)' test/toy/run.sh bench-opt: bin @bash scripts/opt_bench.sh diff --git a/doc/OPTv2.md b/doc/OPTv2.md @@ -45,11 +45,11 @@ Validation status for this slice: HOST_OPTFLAGS=-O0` - Passing (reconfirmed 2026-05-25, 698 checks): `make test-opt HOST_OPTFLAGS=-O1` -- Passing (reconfirmed 2026-05-25, 6/6): `make test-ar-driver +- Passing (reconfirmed 2026-05-25, 6/6): `make test-driver-ar HOST_OPTFLAGS=-O1` - Passing (reconfirmed 2026-05-25, 955/955): `make test-toy HOST_OPTFLAGS=-O1` -- Passing (reconfirmed 2026-05-25, 3712/3712): `make test-parse +- Passing (reconfirmed 2026-05-25, 3712/3712): `make test-parse-ok HOST_OPTFLAGS=-O1` - Passing (reconfirmed 2026-05-25, clean `build/bootstrap`, bootstrapped toy 955/955): `make bootstrap-test-toy HOST_OPTFLAGS=-O0` diff --git a/doc/WASM.md b/doc/WASM.md @@ -905,7 +905,7 @@ What's landed (v1 slice, single-TU final modules): uniformly. The old `try_linearize_switch_island` linear-time matcher is removed. - [x] End-to-end Toy → wasm → run roundtrip via `cfree run` (lang/wasm - frontend → native CG → JIT). See `make test-toy-wasm`. + frontend → native CG → JIT). See `make test-wasm-toy`. What's not yet done in this phase: @@ -987,7 +987,7 @@ What's not yet done in this phase: `WasmObjMeta`). v1 single-TU output is a final module with no relocations; multi-TU needs this. -Targeted tests: `make test-toy-wasm` drives the existing toy corpus through +Targeted tests: `make test-wasm-toy` drives the existing toy corpus through `-target wasm32-none -c` + roundtrip. Current snapshot: **99 pass / 0 fail / 34 skip** out of 133 fixtures, after the CFG structurer for direct gotos (`src/arch/wasm/structure.c`) landed and absorbed the switch-island @@ -1293,7 +1293,7 @@ slots and runtime table storage remain open work. exercises a `i32.popcnt` template positive case and an escaping `br` negative case; the existing `20_cg_api_inline_asm_full` / `102_typed_asm_operands` toy fixtures and their kin now run end-to-end - through `test-toy-wasm` (formerly SKIP'd). + through `test-wasm-toy` (formerly SKIP'd). - [partial] Lower `copy_bytes` and `set_bytes` to the bulk-memory opcodes `memory.copy` (0xfc 0x0a) and `memory.fill` (0xfc 0x0b) when the active feature set includes `BULK_MEMORY` (subagent B). Replaces the @@ -1341,7 +1341,7 @@ slots and runtime table storage remain open work. references with `section_base[sym->sec] + sym->value + addend` so cross-symbol initializers (e.g. `*p = &x`) work for the single-TU final-module case. `R_ABS64` and other reloc kinds diagnose explicitly. -- [x] Add `make test-toy-wasm` (Toy → wasm32 -c → `cfree run` of the +- [x] Add `make test-wasm-toy` (Toy → wasm32 -c → `cfree run` of the `.wasm`). Drives the existing toy corpus through the backend and the Wasm-input frontend's native JIT. Snapshot: 74 pass / 4 fail / 55 skip after the compact data layout / static-data relocation / &&label-data @@ -1384,7 +1384,7 @@ slots and runtime table storage remain open work. ### Wasm-to-Wasm - [partial] Use the frontend plus Wasm target to normalize simple modules. - The Toy → Wasm → run roundtrip via `make test-toy-wasm` exercises the + The Toy → Wasm → run roundtrip via `make test-wasm-toy` exercises the backend's output through the Wasm-input frontend's native JIT (an indirect Wasm-to-native rather than Wasm-to-Wasm path). A direct Wasm-input → Wasm-output mode requires hooking the lang/wasm frontend diff --git a/driver/cc.c b/driver/cc.c @@ -734,8 +734,12 @@ static int cc_record_mcmodel(CcOptions* o, const char* val) { } static void cc_log_ignored(const char* arg) { +#ifndef NDEBUG driver_errf(CC_TOOL, "ignoring accepted compatibility flag: %.*s", CFREE_SLICE_ARG(cfree_slice_cstr(arg))); +#else + (void)arg; +#endif } static int cc_set_probe(CcOptions* o, int kind, const char* arg) { diff --git a/src/arch/aa64/emit.c b/src/arch/aa64/emit.c @@ -529,6 +529,10 @@ finish: u32 end = mc->pos(mc); obj_symbol_define(obj, a->fd->sym, sec, (u64)a->func_start, (u64)(end - a->func_start)); + if (a->fd->atomize) { + obj_atom_define(obj, sec, a->func_start, end - a->func_start, a->fd->sym, + 0); + } if (t->debug) debug_func_pc_range(t->debug, sec, a->func_start, end); diff --git a/src/arch/arch.h b/src/arch/arch.h @@ -377,6 +377,8 @@ typedef struct CGFuncDesc { SrcLoc loc; u32 flags; /* CGFuncDescFlag */ CfreeCgInlinePolicy inline_policy; + u8 atomize; + u8 pad[3]; } CGFuncDesc; typedef struct CGKnownFrameDesc { diff --git a/src/arch/rv64/emit.c b/src/arch/rv64/emit.c @@ -630,6 +630,10 @@ finish: u32 end = mc->pos(mc); obj_symbol_define(obj, a->fd->sym, sec, (u64)a->func_start, (u64)(end - a->func_start)); + if (a->fd->atomize) { + obj_atom_define(obj, sec, a->func_start, end - a->func_start, a->fd->sym, + 0); + } if (t->debug) debug_func_pc_range(t->debug, sec, a->func_start, end); diff --git a/src/arch/x64/emit.c b/src/arch/x64/emit.c @@ -1102,6 +1102,10 @@ finish: u32 end = mc->pos(mc); obj_symbol_define(t->obj, a->fd->sym, a->fd->text_section_id, (u64)a->func_start, (u64)(end - a->func_start)); + if (a->fd->atomize) { + obj_atom_define(t->obj, a->fd->text_section_id, a->func_start, + end - a->func_start, a->fd->sym, 0); + } if (t->debug) debug_func_pc_range(t->debug, a->fd->text_section_id, a->func_start, end); diff --git a/src/cg/data.c b/src/cg/data.c @@ -69,6 +69,7 @@ void cfree_cg_data_begin(CfreeCg* g, CfreeCgSym cg_sym, Slice split_base; ObjSecId sec; CfreeCgDecl decl_attrs; + int atomize = 0; if (!g) return; c = g->c; ob = g->obj; @@ -162,14 +163,20 @@ void cfree_cg_data_begin(CfreeCg* g, CfreeCgSym cg_sym, sec_name_sym = pool_intern_slice(c->global, split_base); } if (!attrs.section && g->data_sections && split_base.len) { - Sym split_name = - api_cg_symbol_section_name(g, split_base, decl_attrs.linkage_name); - if (split_name) sec_name_sym = split_name; + atomize = obj_format_split_sections_as_atoms(c); + if (!atomize) { + Sym split_name = + api_cg_symbol_section_name(g, split_base, decl_attrs.linkage_name); + if (split_name) sec_name_sym = split_name; + } } if (attrs.flags & CFREE_CG_DATADEF_RETAIN) sec_flags |= SF_RETAIN; if (attrs.flags & CFREE_CG_DATADEF_MERGE) sec_flags |= SF_MERGE; if (attrs.flags & CFREE_CG_DATADEF_STRINGS) sec_flags |= SF_STRINGS; - if (attrs.flags & CFREE_CG_DATADEF_ZERO_FILL) { + if ((attrs.flags & CFREE_CG_DATADEF_ZERO_FILL) && atomize && + obj_format_split_sections_as_atoms(c)) { + sec = obj_section(ob, sec_name_sym, sec_kind, sec_flags, align); + } else if (attrs.flags & CFREE_CG_DATADEF_ZERO_FILL) { sec = obj_section_ex(ob, sec_name_sym, sec_kind, SSEM_NOBITS, sec_flags, align, 0, OBJ_SEC_NONE, 0); } else if (attrs.entsize) { @@ -182,6 +189,8 @@ void cfree_cg_data_begin(CfreeCg* g, CfreeCgSym cg_sym, g->data_sym = sym; g->data_base = obj_align_to(ob, sec, align); g->data_size = 0; + g->data_atomize = atomize ? 1u : 0u; + g->data_retain = (attrs.flags & CFREE_CG_DATADEF_RETAIN) ? 1u : 0u; if (sym != OBJ_SYM_NONE) { obj_symbol_define(ob, sym, sec, (u64)g->data_base, (u64)abi_cg_sizeof(c->abi, decl_attrs.type)); @@ -590,6 +599,8 @@ void cfree_cg_data_end(CfreeCg* g) { g->data_sym = OBJ_SYM_NONE; g->data_base = 0; g->data_size = 0; + g->data_atomize = 0; + g->data_retain = 0; g->data_local_static_target = 0; return; } @@ -621,16 +632,24 @@ void cfree_cg_data_end(CfreeCg* g) { g->data_sym = OBJ_SYM_NONE; g->data_base = 0; g->data_size = 0; + g->data_atomize = 0; + g->data_retain = 0; return; } if (g->data_sym != OBJ_SYM_NONE) { obj_symbol_define(g->obj, g->data_sym, g->data_sec, g->data_base, g->data_size); } + if (g->data_atomize) { + obj_atom_define(g->obj, g->data_sec, g->data_base, (u32)g->data_size, + g->data_sym, g->data_retain ? OBJ_ATOM_RETAIN : 0u); + } g->data_sec = OBJ_SEC_NONE; g->data_sym = OBJ_SYM_NONE; g->data_base = 0; g->data_size = 0; + g->data_atomize = 0; + g->data_retain = 0; } /* Emit a function-local jump-table of `n` label addresses into .rodata diff --git a/src/cg/internal.h b/src/cg/internal.h @@ -167,7 +167,9 @@ struct CfreeCg { u32 data_base; u64 data_size; u8 data_local_static_target; - u8 data_local_static_pad0[3]; + u8 data_atomize; + u8 data_retain; + u8 data_local_static_pad0[1]; u8 data_tls_collect; u8 data_tls_zero_fill; u8 data_tls_pad[2]; diff --git a/src/cg/session.c b/src/cg/session.c @@ -267,9 +267,13 @@ void cfree_cg_func_begin_attrs(CfreeCg* g, CfreeCgSym cg_sym, sec_name = begin_attrs.section ? (Sym)begin_attrs.section : (Sym)attrs.as.func.section; + int atomize = 0; if (!sec_name && g->function_sections) { - sec_name = - api_cg_symbol_section_name(g, SLICE_LIT(".text"), attrs.linkage_name); + atomize = obj_format_split_sections_as_atoms(c); + if (!atomize) { + sec_name = + api_cg_symbol_section_name(g, SLICE_LIT(".text"), attrs.linkage_name); + } } if (!sec_name) sec_name = pool_intern_slice(c->global, SLICE_LIT(".text")); text_sec = obj_section(ob, sec_name, SEC_TEXT, SF_EXEC | SF_ALLOC, 4); @@ -285,6 +289,7 @@ void cfree_cg_func_begin_attrs(CfreeCg* g, CfreeCgSym cg_sym, g->fn_desc.fn_type = fty; g->fn_desc.abi = abi; g->fn_desc.loc = g->cur_loc; + g->fn_desc.atomize = atomize ? 1u : 0u; if (attrs.as.func.flags & CFREE_CG_FUNC_NORETURN) { g->fn_desc.flags |= CGFD_NORETURN; } diff --git a/src/link/link.c b/src/link/link.c @@ -527,6 +527,29 @@ static void link_image_release(LinkImage* img) { if (m->section) img->heap->free(img->heap, m->section, sizeof(*m->section) * m->nsection); + if (m->atom) + img->heap->free(img->heap, m->atom, + sizeof(*m->atom) * (m->natom ? m->natom : 1u)); + if (m->sym_atom) + img->heap->free(img->heap, m->sym_atom, + sizeof(*m->sym_atom) * (m->nsym ? m->nsym : 1u)); + if (m->reloc_atom) + img->heap->free(img->heap, m->reloc_atom, + sizeof(*m->reloc_atom) * (m->nreloc ? m->nreloc : 1u)); + if (m->section_has_atoms) + img->heap->free(img->heap, m->section_has_atoms, + m->nsection ? m->nsection : 1u); + if (m->section_atom_first) + img->heap->free( + img->heap, m->section_atom_first, + sizeof(*m->section_atom_first) * (m->nsection ? m->nsection : 1u)); + if (m->section_atom_count) + img->heap->free( + img->heap, m->section_atom_count, + sizeof(*m->section_atom_count) * (m->nsection ? m->nsection : 1u)); + if (m->section_atom_ids) + img->heap->free(img->heap, m->section_atom_ids, + sizeof(*m->section_atom_ids) * m->nsection_atom_ids); if (m->comdat_discarded) img->heap->free(img->heap, m->comdat_discarded, m->nsection ? m->nsection : 1u); diff --git a/src/link/link.h b/src/link/link.h @@ -67,6 +67,7 @@ typedef struct LinkSymbol { LinkInputId input_id; ObjSymId obj_sym; ObjSecId section_id; + ObjAtomId atom_id; u64 value; u64 vaddr; /* final linked address, 0 for unresolved undef */ u64 size; @@ -105,7 +106,9 @@ typedef struct LinkSection { LinkSectionId id; LinkInputId input_id; ObjSecId obj_section_id; + ObjAtomId obj_atom_id; LinkSegmentId segment_id; + u64 obj_offset; u64 input_offset; u64 file_offset; u64 vaddr; diff --git a/src/link/link_internal.h b/src/link/link_internal.h @@ -23,6 +23,16 @@ typedef struct InputMap { u32 nsym; LinkSectionId* section; /* size = ObjBuilder.nsections */ u32 nsection; + LinkSectionId* atom; /* size = ObjBuilder.natoms */ + u32 natom; + ObjAtomId* sym_atom; /* size = nsym; ObjSymId -> ObjAtomId */ + ObjAtomId* reloc_atom; /* size = obj_reloc_total(input) */ + u32 nreloc; + u8* section_has_atoms; /* size = nsection */ + u32* section_atom_first; /* size = nsection; index into section_atom_ids */ + u32* section_atom_count; /* size = nsection */ + ObjAtomId* section_atom_ids; /* active atoms grouped by section */ + u32 nsection_atom_ids; /* COMDAT discard mask, size = nsection. Set by link_resolve_symbols * for COFF/PE SELECTANY: when an input's COMDAT section conflicts * with an earlier definition, the duplicate section is marked here @@ -30,6 +40,63 @@ typedef struct InputMap { u8* comdat_discarded; } InputMap; +void link_input_map_alloc(LinkImage*, InputMap*, ObjBuilder*, u32 nsym_slots); + +static inline ObjAtomId link_input_sym_atom(const InputMap* m, ObjSymId sym) { + return (m && m->sym_atom && sym < m->nsym) ? m->sym_atom[sym] : OBJ_ATOM_NONE; +} + +static inline ObjAtomId link_input_reloc_atom(const InputMap* m, + u32 reloc_index) { + return (m && m->reloc_atom && reloc_index < m->nreloc) + ? m->reloc_atom[reloc_index] + : OBJ_ATOM_NONE; +} + +static inline int link_input_section_has_atoms(const InputMap* m, + ObjSecId sid) { + return m && m->section_has_atoms && sid < m->nsection && + m->section_has_atoms[sid]; +} + +static inline void link_input_section_atoms(const InputMap* m, ObjSecId sid, + u32* first, u32* count) { + if (m && sid < m->nsection && m->section_atom_first && + m->section_atom_count) { + *first = m->section_atom_first[sid]; + *count = m->section_atom_count[sid]; + } else { + *first = 0; + *count = 0; + } +} + +static inline LinkSectionId link_input_reloc_section(const InputMap* m, + const Reloc* r, + u32 reloc_index) { + if (!m || !r || r->section_id == OBJ_SEC_NONE || r->section_id >= m->nsection) + return LINK_SEC_NONE; + if (link_input_section_has_atoms(m, r->section_id)) { + ObjAtomId aid = link_input_reloc_atom(m, reloc_index); + if (aid == OBJ_ATOM_NONE || aid >= m->natom) return LINK_SEC_NONE; + return m->atom[aid]; + } + return m->section[r->section_id]; +} + +static inline LinkSectionId link_input_symbol_section(const InputMap* m, + const ObjSym* s, + ObjSymId sym) { + if (!m || !s || s->section_id == OBJ_SEC_NONE || s->section_id >= m->nsection) + return LINK_SEC_NONE; + if (link_input_section_has_atoms(m, s->section_id)) { + ObjAtomId aid = link_input_sym_atom(m, sym); + if (aid == OBJ_ATOM_NONE || aid >= m->natom) return LINK_SEC_NONE; + return m->atom[aid]; + } + return m->section[s->section_id]; +} + /* Open-addressed name -> LinkSymId hash for global / weak definitions * and lookups (cfree_jit_lookup, entry-symbol resolution). Locals never * land in this table. Sym 0 is the empty-slot sentinel (it's also the @@ -134,14 +201,16 @@ struct Linker { /* ---- GC liveness (link_resolve.c) ---------------------------------------- */ typedef struct GcLive { - u8** marks; /* marks[input_idx][obj_sec_id]; 0/1, sized to nsec_per_input[ii] - */ - u32* nsec; /* obj_section_count per input */ + u8** + marks; /* marks[input_idx][obj_sec_id] for implicit whole-section units */ + u8** atom_marks; /* atom_marks[input_idx][obj_atom_id] for explicit atoms */ + u32* nsec; /* obj_section_count per input */ + u32* natom; /* obj_atom_count per input */ u32 ninputs; } GcLive; typedef struct GcQueue { - u64* items; /* (u64) packed: hi32 = input_idx, lo32 = obj_sec_id */ + u64* items; /* hi32=input_idx, low31=id, bit31=set for atom */ u32 n; u32 cap; } GcQueue; @@ -180,6 +249,7 @@ int link_gc_split_start_stop(const char* s, size_t n, size_t* out_off, /* GC liveness helpers (link_resolve.c). */ int link_gc_live_get(const GcLive* g, u32 ii, ObjSecId j); +int link_gc_atom_live_get(const GcLive* g, u32 ii, ObjAtomId j); /* Segment/section growth helpers for iplt (link_reloc_layout.c). */ u32 link_iplt_alloc_segments(LinkImage* img, u32 nseg); diff --git a/src/link/link_jit.c b/src/link/link_jit.c @@ -253,11 +253,11 @@ static void jit_copy_input_section_bytes(LinkImage* img, compiler_panic(c, no_loc(), "cfree_jit_from_image: input section bytes unavailable"); s = obj_section_get(ob, ls->obj_section_id); - if (!s || s->sem == SSEM_NOBITS) continue; - if (s->bytes.total == 0) continue; + if (!s || s->sem == SSEM_NOBITS || s->kind == SEC_BSS) continue; + if (ls->size == 0) continue; dst = (u8*)segs[seg->id - 1u].write + (size_t)(ls->vaddr - seg->vaddr); - metrics_count(c, "jit.input_section_bytes", s->bytes.total); - buf_flatten(&s->bytes, dst); + metrics_count(c, "jit.input_section_bytes", ls->size); + buf_read(&s->bytes, (u32)ls->obj_offset, dst, (size_t)ls->size); } } @@ -767,9 +767,11 @@ uint64_t cfree_jit_generation(CfreeJit* jit) { typedef struct JitAppendSec { ObjSecId obj_sec; + ObjAtomId obj_atom; LinkSectionId link_sec; LinkSegmentId link_seg; SegBucket bucket; + u64 obj_offset; u64 vaddr; u64 size; u64 file_size; @@ -779,6 +781,8 @@ typedef struct JitAppendSec { Sym name; } JitAppendSec; +SEGVEC_DEFINE(JitAppendSecs, JitAppendSec, 4); + static int jit_bind_strength(u8 bind) { switch (bind) { case SB_GLOBAL: @@ -827,25 +831,20 @@ static u8 jit_reloc_width_local(RelocKind k) { } } +static u32 jit_section_size_for_link(const Section* s) { + return (s->sem == SSEM_NOBITS || s->kind == SEC_BSS) ? s->bss_size + : s->bytes.total; +} + static InputMap jit_input_map_alloc(CfreeJit* jit, ObjBuilder* ob) { InputMap m; ObjSymIter* it; ObjSymEntry e; u32 nsyms = 0; - Heap* h = jit->image->heap; - memset(&m, 0, sizeof(m)); it = obj_symiter_new(ob); while (obj_symiter_next(it, &e)) ++nsyms; obj_symiter_free(it); - m.nsym = nsyms + 1u; - m.sym = (LinkSymId*)h->alloc(h, sizeof(*m.sym) * m.nsym, _Alignof(LinkSymId)); - m.nsection = obj_section_count(ob); - m.section = (LinkSectionId*)h->alloc(h, sizeof(*m.section) * m.nsection, - _Alignof(LinkSectionId)); - if (!m.sym || !m.section) - compiler_panic(jit->c, no_loc(), "cfree_jit_append_obj: oom on input map"); - memset(m.sym, 0, sizeof(*m.sym) * m.nsym); - memset(m.section, 0, sizeof(*m.section) * m.nsection); + link_input_map_alloc(jit->image, &m, ob, nsyms + 1u); return m; } @@ -930,8 +929,8 @@ static void jit_append_obj_inner(CfreeJit* jit, ObjBuilder* ob) { u32 old_nrelocs = LinkRelocs_count(&img->relocs); u64 old_cursor[SEG_NBUCKETS]; InputMap m; - JitAppendSec* secs = NULL; - u32 nsecs = 0, sec_cap = 0; + JitAppendSecs secs; + u32 nsecs = 0; u32 obj_sec_count; u64 cursor[SEG_NBUCKETS]; LinkInputId new_input_id; @@ -940,6 +939,7 @@ static void jit_append_obj_inner(CfreeJit* jit, ObjBuilder* ob) { ObjSymEntry e; u32 i; + JitAppendSecs_init(&secs, h); for (i = 0; i < SEG_NBUCKETS; ++i) old_cursor[i] = jit->append_cursor[i]; if (!jit || !ob || !jit->linker || obj_compiler(ob) != jit->c) @@ -1019,43 +1019,62 @@ static void jit_append_obj_inner(CfreeJit* jit, ObjBuilder* ob) { for (i = 1; i < obj_sec_count; ++i) { const Section* s = obj_section_get(ob, (ObjSecId)i); - SegBucket b; - u64 size; - u64 vaddr; + u32 first = 0, count = 1, ai; + int has_atoms; if (!s || !link_section_kept(s)) continue; - size = (s->sem == SSEM_NOBITS) ? (u64)s->bss_size : (u64)s->bytes.total; - if (size == 0) continue; - b = link_bucket_for(s->flags); - vaddr = ALIGN_UP(cursor[b], (u64)(s->align ? s->align : 1u)); - if (vaddr + size > jit->append_limit[b]) - compiler_panic(jit->c, no_loc(), - "cfree_jit_append_obj: append slack exhausted"); - if (nsecs == sec_cap) { - u32 ncap = sec_cap ? sec_cap * 2u : 8u; - JitAppendSec* ns = (JitAppendSec*)h->realloc( - h, secs, sizeof(*secs) * sec_cap, sizeof(*secs) * ncap, - _Alignof(JitAppendSec)); - if (!ns) + has_atoms = link_input_section_has_atoms(&m, (ObjSecId)i); + if (has_atoms) link_input_section_atoms(&m, (ObjSecId)i, &first, &count); + for (ai = 0; ai < count; ++ai) { + ObjAtomId aid = + has_atoms ? m.section_atom_ids[first + ai] : OBJ_ATOM_NONE; + const ObjAtom* atom = has_atoms ? obj_atom_get(ob, aid) : NULL; + SegBucket b; + u64 obj_offset; + u64 size; + u64 vaddr; + JitAppendSec* ps; + u32 sec_idx; + if (has_atoms) { + if (!atom || atom->removed) continue; + obj_offset = atom->offset; + size = atom->size; + } else { + obj_offset = 0u; + size = jit_section_size_for_link(s); + } + if (size == 0) continue; + b = link_bucket_for(s->flags); + vaddr = ALIGN_UP(cursor[b], (u64)(s->align ? s->align : 1u)); + if (vaddr + size > jit->append_limit[b]) + compiler_panic(jit->c, no_loc(), + "cfree_jit_append_obj: append slack exhausted"); + ps = JitAppendSecs_push(&secs, &sec_idx); + if (!ps) compiler_panic(jit->c, no_loc(), "cfree_jit_append_obj: oom on section plan"); - secs = ns; - sec_cap = ncap; + ps->obj_sec = (ObjSecId)i; + ps->obj_atom = has_atoms ? aid : OBJ_ATOM_NONE; + ps->link_sec = (LinkSectionId)(old_nsections + sec_idx + 1u); + ps->link_seg = (LinkSegmentId)(old_nsegments + sec_idx + 1u); + ps->bucket = b; + ps->obj_offset = obj_offset; + ps->vaddr = vaddr; + ps->size = size; + ps->file_size = (s->sem == SSEM_NOBITS || s->kind == SEC_BSS) ? 0 : size; + ps->align = s->align ? s->align : 1u; + ps->flags = s->flags; + ps->sem = (s->kind == SEC_BSS) ? SSEM_NOBITS : s->sem; + ps->name = s->name; + if (has_atoms) { + m.atom[aid] = ps->link_sec; + if (m.section[i] == LINK_SEC_NONE) m.section[i] = ps->link_sec; + } else { + m.section[i] = ps->link_sec; + } + cursor[b] = vaddr + size; } - secs[nsecs].obj_sec = (ObjSecId)i; - secs[nsecs].link_sec = (LinkSectionId)(old_nsections + nsecs + 1u); - secs[nsecs].link_seg = (LinkSegmentId)(old_nsegments + nsecs + 1u); - secs[nsecs].bucket = b; - secs[nsecs].vaddr = vaddr; - secs[nsecs].size = size; - secs[nsecs].file_size = (s->sem == SSEM_NOBITS) ? 0 : size; - secs[nsecs].align = s->align ? s->align : 1u; - secs[nsecs].flags = s->flags; - secs[nsecs].sem = s->sem; - secs[nsecs].name = s->name; - m.section[i] = secs[nsecs].link_sec; - cursor[b] = vaddr + size; - ++nsecs; } + nsecs = JitAppendSecs_count(&secs); for (i = 0; i < SEG_NBUCKETS; ++i) jit->append_cursor[i] = cursor[i]; it = obj_symiter_new(ob); @@ -1095,6 +1114,7 @@ static void jit_append_obj_inner(CfreeJit* jit, ObjBuilder* ob) { rec.input_id = (LinkInputId)(LinkInputs_count(&jit->linker->inputs) + 1u); rec.obj_sym = e.id; rec.section_id = LINK_SEC_NONE; + rec.atom_id = is_def ? link_input_sym_atom(&m, e.id) : OBJ_ATOM_NONE; rec.value = s->value; rec.size = s->size; rec.common_align = (s->kind == SK_COMMON) ? (u32)s->common_align : 0u; @@ -1103,12 +1123,13 @@ static void jit_append_obj_inner(CfreeJit* jit, ObjBuilder* ob) { rec.defined = (u8)is_def; if (is_def && s->section_id != OBJ_SEC_NONE && s->section_id < m.nsection) { - rec.section_id = m.section[s->section_id]; + rec.section_id = link_input_symbol_section(&m, s, e.id); if (rec.section_id != LINK_SEC_NONE) { u32 sj; for (sj = 0; sj < nsecs; ++sj) { - if (secs[sj].link_sec == rec.section_id) { - rec.vaddr = secs[sj].vaddr + s->value; + JitAppendSec* ps = JitAppendSecs_at(&secs, sj); + if (ps && ps->link_sec == rec.section_id) { + rec.vaddr = ps->vaddr + (s->value - ps->obj_offset); break; } } @@ -1142,6 +1163,7 @@ static void jit_append_obj_inner(CfreeJit* jit, ObjBuilder* ob) { LinkSymbol* def = LinkSyms_at(&img->syms, hit - 1); if (def->defined) { s->section_id = def->section_id; + s->atom_id = def->atom_id; s->value = def->value; s->vaddr = def->vaddr; s->kind = def->kind; @@ -1236,7 +1258,7 @@ static void jit_append_obj_inner(CfreeJit* jit, ObjBuilder* ob) { } for (i = 0; i < nsecs; ++i) { - JitAppendSec* ps = &secs[i]; + JitAppendSec* ps = JitAppendSecs_at(&secs, i); LinkSection* ls = &img->sections[old_nsections + i]; LinkSegment* seg = &img->segments[old_nsegments + i]; CfreeExecMemRegion* js = &jit->segs[old_nsegs + i]; @@ -1245,7 +1267,9 @@ static void jit_append_obj_inner(CfreeJit* jit, ObjBuilder* ob) { ls->id = ps->link_sec; ls->input_id = new_input_id; ls->obj_section_id = ps->obj_sec; + ls->obj_atom_id = ps->obj_atom; ls->segment_id = ps->link_seg; + ls->obj_offset = ps->obj_offset; ls->input_offset = 0; ls->file_offset = ps->vaddr; ls->vaddr = ps->vaddr; @@ -1278,9 +1302,10 @@ static void jit_append_obj_inner(CfreeJit* jit, ObjBuilder* ob) { js->size = (size_t)ps->size; js->token = NULL; - if (os && os->sem != SSEM_NOBITS && os->bytes.total) { - buf_flatten(&os->bytes, (u8*)js->write); - } + if (os && os->sem != SSEM_NOBITS && os->kind != SEC_BSS && + os->bytes.total && ps->size) + buf_read(&os->bytes, (u32)ps->obj_offset, (u8*)js->write, + (size_t)ps->size); img->nsections++; img->nsegments++; jit->nsegs++; @@ -1295,7 +1320,7 @@ static void jit_append_obj_inner(CfreeJit* jit, ObjBuilder* ob) { const LinkSection* ls; if (!r || r->section_id == OBJ_SEC_NONE || r->section_id >= m.nsection) continue; - ls_id = m.section[r->section_id]; + ls_id = link_input_reloc_section(&m, r, i); if (ls_id == LINK_SEC_NONE) continue; if (r->sym == OBJ_SYM_NONE || r->sym >= m.nsym || m.sym[r->sym] == LINK_SYM_NONE) @@ -1305,9 +1330,9 @@ static void jit_append_obj_inner(CfreeJit* jit, ObjBuilder* ob) { rec.input_id = new_input_id; rec.section_id = r->section_id; rec.link_section_id = ls_id; - rec.offset = r->offset; + rec.offset = r->offset - (u32)ls->obj_offset; rec.width = jit_reloc_width_local((RelocKind)r->kind); - rec.write_vaddr = ls->vaddr + r->offset; + rec.write_vaddr = ls->vaddr + rec.offset; rec.write_file_offset = rec.write_vaddr; rec.kind = (RelocKind)r->kind; rec.target = m.sym[r->sym]; @@ -1365,7 +1390,7 @@ static void jit_append_obj_inner(CfreeJit* jit, ObjBuilder* ob) { jit_invalidate_view(jit); jit->generation++; - if (secs) h->free(h, secs, sizeof(*secs) * sec_cap); + JitAppendSecs_fini(&secs); (void)old_cursor; (void)old_nmaps; } diff --git a/src/link/link_layout.c b/src/link/link_layout.c @@ -118,6 +118,9 @@ typedef struct SecRef { typedef struct PlaceEntry { u32 input_idx; ObjSecId obj_sec_id; + ObjAtomId obj_atom_id; + u32 obj_offset; + u32 size; Sym name; SegBucket bucket; u32 next; @@ -141,6 +144,37 @@ static u32 place_group_hash_cap(u32 n) { return cap; } +static u32 section_size_for_link(const Section* s) { + return (s->sem == SSEM_NOBITS || s->kind == SEC_BSS) ? s->bss_size + : s->bytes.total; +} + +static int live_section_units(const GcLive* g, const InputMap* m, u32 ii, + ObjBuilder* ob, ObjSecId sid) { + u32 n = 0, first, count, i; + if (link_input_section_has_atoms(m, sid)) { + link_input_section_atoms(m, sid, &first, &count); + for (i = 0; i < count; ++i) { + ObjAtomId aid = m->section_atom_ids[first + i]; + const ObjAtom* a = obj_atom_get(ob, aid); + if (!a || a->removed) continue; + if (link_gc_atom_live_get(g, ii, aid)) ++n; + } + return n; + } + return link_gc_live_get(g, ii, sid) ? 1 : 0; +} + +static void map_placed_unit(InputMap* m, ObjSecId sid, ObjAtomId aid, + LinkSectionId lsid) { + if (aid != OBJ_ATOM_NONE) { + m->atom[aid] = lsid; + if (m->section[sid] == LINK_SEC_NONE) m->section[sid] = lsid; + return; + } + m->section[sid] = lsid; +} + static void link_layout_sections_scripted(Linker* l, LinkImage* img, const GcLive* g); @@ -158,7 +192,9 @@ void link_layout_sections(Linker* l, LinkImage* img, const GcLive* g) { 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 && link_section_kept(s) && link_gc_live_get(g, ii, j)) ++total_kept; + InputMap* m = &img->input_maps[ii]; + if (s && link_section_kept(s) && !m->comdat_discarded[j]) + total_kept += live_section_units(g, m, ii, ob, j); } } @@ -193,36 +229,53 @@ void link_layout_sections(Linker* l, LinkImage* img, const GcLive* g) { u32 e = 0; 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); 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].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; + if (!s || !link_section_kept(s) || m->comdat_discarded[j]) continue; + u32 first = 0, count = 1, ai; + int has_atoms = link_input_section_has_atoms(m, j); + if (has_atoms) link_input_section_atoms(m, j, &first, &count); + for (ai = 0; ai < count; ++ai) { + ObjAtomId aid = + has_atoms ? m->section_atom_ids[first + ai] : OBJ_ATOM_NONE; + const ObjAtom* a = has_atoms ? obj_atom_get(ob, aid) : NULL; + if (has_atoms) { + if (!a || a->removed) continue; + if (!link_gc_atom_live_get(g, ii, aid)) continue; + } else if (!link_gc_live_get(g, ii, j)) { + continue; + } + entries[e].input_idx = ii; + entries[e].obj_sec_id = j; + entries[e].obj_atom_id = has_atoms ? aid : OBJ_ATOM_NONE; + entries[e].obj_offset = has_atoms ? a->offset : 0u; + entries[e].size = has_atoms ? a->size : section_size_for_link(s); + entries[e].name = s->name; + entries[e].bucket = link_bucket_for(s->flags); + 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; } - groups[group_idx].tail = e; - ++e; } } } @@ -251,16 +304,16 @@ void link_layout_sections(Linker* l, LinkImage* img, const GcLive* g) { LinkSection* ls; LinkSectionId lsid; - if (s->sem == SSEM_NOBITS) { + if (s->sem == SSEM_NOBITS || s->kind == SEC_BSS) { u64 cursor = seg_size[bucket] + seg_bss_extra[bucket]; cursor = ALIGN_UP(cursor, (u64)(align)); - seg_bss_extra[bucket] = cursor + (u64)s->bss_size - seg_size[bucket]; + seg_bss_extra[bucket] = cursor + (u64)pe->size - seg_size[bucket]; ofs = cursor; } else { seg_size[bucket] += seg_bss_extra[bucket]; seg_bss_extra[bucket] = 0; ofs = ALIGN_UP(seg_size[bucket], (u64)(align)); - seg_size[bucket] = ofs + (u64)s->bytes.total; + seg_size[bucket] = ofs + (u64)pe->size; } if (align > seg_align[bucket]) seg_align[bucket] = align; @@ -272,17 +325,19 @@ void link_layout_sections(Linker* l, LinkImage* img, const GcLive* g) { ls->id = lsid; ls->input_id = LinkInputs_at(&l->inputs, pe->input_idx)->id; ls->obj_section_id = pe->obj_sec_id; + ls->obj_atom_id = pe->obj_atom_id; ls->segment_id = LINK_SEG_NONE; + ls->obj_offset = pe->obj_offset; ls->input_offset = ofs; ls->file_offset = ofs; ls->vaddr = ofs; - ls->size = (s->sem == SSEM_NOBITS) ? s->bss_size : s->bytes.total; + ls->size = pe->size; ls->flags = s->flags; ls->align = align; ls->name = s->name; - ls->sem = s->sem; + ls->sem = (s->kind == SEC_BSS) ? SSEM_NOBITS : s->sem; ls->segment_id = (LinkSegmentId)(bucket + 1u); /* 1..3 sentinel */ - m->section[pe->obj_sec_id] = lsid; + map_placed_unit(m, pe->obj_sec_id, pe->obj_atom_id, lsid); } } @@ -600,9 +655,11 @@ static void link_layout_sections_scripted(Linker* l, LinkImage* img, 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); - if (s && link_section_kept(s) && link_gc_live_get(g, ii, j)) ++total_kept; + if (s && link_section_kept(s) && !m->comdat_discarded[j]) + total_kept += live_section_units(g, m, ii, ob, j); } } @@ -711,11 +768,10 @@ static void link_layout_sections_scripted(Linker* l, LinkImage* img, const Section* s; const char* nm; u32 align; - u64 ofs; - LinkSection* ls; - LinkSectionId lsid; + u32 first = 0, count = 1, ai; + int has_atoms; if (claimed[ii][j]) continue; - if (!link_gc_live_get(g, ii, j)) continue; + if (m->comdat_discarded[j]) continue; s = obj_section_get(ob, j); if (!s || !link_section_kept(s)) continue; { @@ -725,33 +781,58 @@ static void link_layout_sections_scripted(Linker* l, LinkImage* img, if (!input_match_section(im, nm)) continue; align = s->align ? s->align : 1u; - if (align > align_max) align_max = align; - dot = ALIGN_UP(dot, (u64)align); - ofs = dot; - - lsid = (LinkSectionId)(img->nsections + 1u); - ls = &img->sections[img->nsections++]; - memset(ls, 0, sizeof(*ls)); - ls->id = lsid; - ls->input_id = LinkInputs_at(&l->inputs, ii)->id; - ls->obj_section_id = j; - ls->segment_id = seg_id; - ls->vaddr = ofs; - ls->size = (s->sem == SSEM_NOBITS) ? s->bss_size : s->bytes.total; - ls->flags = s->flags; - ls->align = align; - ls->name = s->name; - ls->sem = s->sem; - ls->file_offset = ofs - sec_start_dot; - ls->input_offset = ls->file_offset; - m->section[j] = lsid; + has_atoms = link_input_section_has_atoms(m, j); + if (has_atoms) link_input_section_atoms(m, j, &first, &count); + for (ai = 0; ai < count; ++ai) { + ObjAtomId aid = + has_atoms ? m->section_atom_ids[first + ai] : OBJ_ATOM_NONE; + const ObjAtom* atom = has_atoms ? obj_atom_get(ob, aid) : NULL; + u64 ofs; + LinkSection* ls; + LinkSectionId lsid; + u64 obj_offset; + u64 size; + if (has_atoms) { + if (!atom || atom->removed) continue; + if (!link_gc_atom_live_get(g, ii, aid)) continue; + obj_offset = atom->offset; + size = atom->size; + } else if (!link_gc_live_get(g, ii, j)) { + continue; + } else { + obj_offset = 0u; + size = section_size_for_link(s); + } + if (align > align_max) align_max = align; + dot = ALIGN_UP(dot, (u64)align); + ofs = dot; + + lsid = (LinkSectionId)(img->nsections + 1u); + ls = &img->sections[img->nsections++]; + memset(ls, 0, sizeof(*ls)); + ls->id = lsid; + ls->input_id = LinkInputs_at(&l->inputs, ii)->id; + ls->obj_section_id = j; + ls->obj_atom_id = aid; + ls->segment_id = seg_id; + ls->obj_offset = obj_offset; + ls->vaddr = ofs; + ls->size = size; + ls->flags = s->flags; + ls->align = align; + ls->name = s->name; + ls->sem = (s->kind == SEC_BSS) ? SSEM_NOBITS : s->sem; + ls->file_offset = ofs - sec_start_dot; + ls->input_offset = ls->file_offset; + map_placed_unit(m, j, aid, lsid); + + dot += ls->size; + mem_size_accum = dot - sec_start_dot; + if (ls->sem != SSEM_NOBITS) file_size_accum = dot - sec_start_dot; + perms |= (s->flags & (SF_EXEC | SF_WRITE | SF_TLS)); + ++nsec_in_seg; + } claimed[ii][j] = 1; - - dot += ls->size; - mem_size_accum = dot - sec_start_dot; - if (s->sem != SSEM_NOBITS) file_size_accum = dot - sec_start_dot; - perms |= (s->flags & (SF_EXEC | SF_WRITE | SF_TLS)); - ++nsec_in_seg; } } } @@ -937,6 +1018,7 @@ void link_layout_commons(Linker* l, LinkImage* img) { /* Copy each input section's bytes into its segment buffer. */ void link_emit_segment_bytes(Linker* l, LinkImage* img) { u32 j; + (void)l; for (j = 0; j < img->nsections; ++j) { LinkSection* ls = &img->sections[j]; ObjBuilder* ob; @@ -945,11 +1027,11 @@ void link_emit_segment_bytes(Linker* l, LinkImage* img) { const Section* s = obj_section_get(ob, ls->obj_section_id); LinkSegment* seg = &img->segments[ls->segment_id - 1]; u8* dst; - if (!s || s->sem == SSEM_NOBITS) continue; - if (s->bytes.total == 0) continue; + if (!s || s->sem == SSEM_NOBITS || s->kind == SEC_BSS) continue; + if (ls->size == 0) continue; dst = img->segment_bytes[seg->id - 1] + (size_t)(ls->file_offset - seg->file_offset); - buf_flatten(&s->bytes, dst); + buf_read(&s->bytes, (u32)ls->obj_offset, dst, (size_t)ls->size); } } diff --git a/src/link/link_reloc_layout.c b/src/link/link_reloc_layout.c @@ -50,7 +50,7 @@ void link_assign_symbol_vaddrs(Linker* l, LinkImage* img) { if (ls->kind == SK_ABS && ls->vaddr != 0) continue; if (e.sym->section_id == OBJ_SEC_NONE) continue; if (ls->input_id != LinkInputs_at(&l->inputs, ii)->id) continue; - ls->section_id = m->section[e.sym->section_id]; + ls->section_id = link_input_symbol_section(m, e.sym, e.id); } obj_symiter_free(it); } @@ -61,7 +61,10 @@ void link_assign_symbol_vaddrs(Linker* l, LinkImage* img) { if (s->kind == SK_ABS && s->vaddr != 0) continue; if (!s->defined) continue; if (s->section_id == LINK_SEC_NONE) continue; - s->vaddr = img->sections[s->section_id - 1].vaddr + s->value; + { + const LinkSection* ls = &img->sections[s->section_id - 1]; + s->vaddr = ls->vaddr + (s->value - ls->obj_offset); + } } } { @@ -217,7 +220,8 @@ void link_emit_encoding_section_boundaries(Linker* l, LinkImage* img) { } if (!link_gc_split_start_stop(nm, namelen, &off, &ilen, &is_start)) continue; - secname = pool_intern_slice(l->c->global, (Slice){ .s = nm + off, .len = ilen }); + secname = + pool_intern_slice(l->c->global, (Slice){.s = nm + off, .len = ilen}); for (ii = 0; ii < LinkInputs_count(&l->inputs); ++ii) { ObjBuilder* ob = LinkInputs_at(&l->inputs, ii)->obj; InputMap* m = &img->input_maps[ii]; @@ -441,7 +445,7 @@ void link_layout_jit_stubs(Linker* l, LinkImage* img, u32 map_size, LinkSymId target; const LinkSymbol* tgt; if (!s || !link_section_kept(s)) continue; - if (m->section[r->section_id] == LINK_SEC_NONE) continue; + if (link_input_reloc_section(m, r, k) == LINK_SEC_NONE) continue; if (!arch->needs_jit_call_stub(r->kind)) continue; if (r->sym == OBJ_SYM_NONE || r->sym >= m->nsym) continue; target = m->sym[r->sym]; @@ -525,7 +529,8 @@ void link_layout_jit_stubs(Linker* l, LinkImage* img, u32 map_size, stubs_sec->size = stubs_size; stubs_sec->flags = SF_ALLOC | SF_EXEC; stubs_sec->align = 4; - stubs_sec->name = pool_intern_slice(l->c->global, SLICE_LIT(".cfree_jit_call_stubs")); + stubs_sec->name = + pool_intern_slice(l->c->global, SLICE_LIT(".cfree_jit_call_stubs")); stubs_sec->sem = SSEM_PROGBITS; slots_sec = &img->sections[sec_base + 1u]; @@ -540,7 +545,8 @@ void link_layout_jit_stubs(Linker* l, LinkImage* img, u32 map_size, slots_sec->size = slots_size; slots_sec->flags = SF_ALLOC | SF_WRITE; slots_sec->align = 8; - slots_sec->name = pool_intern_slice(l->c->global, SLICE_LIT(".cfree_jit_call_slots")); + slots_sec->name = + pool_intern_slice(l->c->global, SLICE_LIT(".cfree_jit_call_slots")); slots_sec->sem = SSEM_PROGBITS; img->nsections += 2u; @@ -656,7 +662,7 @@ void link_layout_got(Linker* l, LinkImage* img, u32 map_size, const Section* s = obj_section_get(ob, r->section_id); LinkSymId target; if (!s || !link_section_kept(s)) continue; - if (m->section[r->section_id] == LINK_SEC_NONE) continue; + if (link_input_reloc_section(m, r, k) == LINK_SEC_NONE) continue; if (!reloc_uses_got(r->kind)) continue; if (r->sym == OBJ_SYM_NONE || r->sym >= m->nsym) continue; target = m->sym[r->sym]; @@ -847,7 +853,8 @@ void link_layout_iplt(Linker* l, LinkImage* img) { pairs_size = (u64)nifunc * 16u; if (emit_init_array) { - ifunc_init_name = pool_intern_slice(l->c->global, SLICE_LIT("__cfree_ifunc_init")); + ifunc_init_name = + pool_intern_slice(l->c->global, SLICE_LIT("__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) { @@ -941,7 +948,8 @@ void link_layout_iplt(Linker* l, LinkImage* img) { sec_base = link_iplt_alloc_sections(img, nsec); } - pairs_section_name = pool_intern_slice(l->c->global, SLICE_LIT(".iplt.pairs")); + pairs_section_name = + pool_intern_slice(l->c->global, SLICE_LIT(".iplt.pairs")); init_section_name = obj_secname_preinit_array(l->c); iplt_sec = &img->sections[sec_base + 0u]; @@ -1211,7 +1219,7 @@ void link_emit_relocations(Linker* l, LinkImage* img, const LinkSymId* got_map, LinkSection* ls; LinkRelocApply rec; if (!s || !link_section_kept(s)) continue; - if (m->section[r->section_id] == LINK_SEC_NONE) continue; + if (link_input_reloc_section(m, r, k) == LINK_SEC_NONE) continue; if (r->kind == R_RV_RELAX || r->kind == R_RV_TPREL_ADD || r->kind == R_RV_ALIGN) continue; @@ -1232,15 +1240,19 @@ void link_emit_relocations(Linker* l, LinkImage* img, const LinkSymId* got_map, LinkSymId stub = stub_map[target]; if (stub != LINK_SYM_NONE) target = stub; } - ls = &img->sections[m->section[r->section_id] - 1]; + { + LinkSectionId src_lsid = link_input_reloc_section(m, r, k); + if (src_lsid == LINK_SEC_NONE) continue; + ls = &img->sections[src_lsid - 1]; + } memset(&rec, 0, sizeof(rec)); 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; + rec.offset = r->offset - (u32)ls->obj_offset; rec.width = reloc_width((RelocKind)r->kind); - rec.write_vaddr = ls->vaddr + r->offset; - rec.write_file_offset = ls->file_offset + r->offset; + rec.write_vaddr = ls->vaddr + rec.offset; + rec.write_file_offset = ls->file_offset + rec.offset; rec.kind = (RelocKind)r->kind; rec.target = target; rec.addend = r->addend; diff --git a/src/link/link_resolve.c b/src/link/link_resolve.c @@ -9,6 +9,7 @@ */ #include <cfree/core.h> +#include <stdlib.h> #include <string.h> #include "core/buf.h" @@ -29,8 +30,75 @@ static SrcLoc no_loc(void) { /* ---- per-input symbol/section maps ---- */ -static void map_alloc(LinkImage* img, InputMap* m, u32 nsym, u32 nsection) { +typedef struct AtomSortRec { + ObjAtomId id; + ObjSecId section_id; + u32 offset; +} AtomSortRec; + +static int atom_sort_rec_cmp(const void* av, const void* bv) { + const AtomSortRec* a = (const AtomSortRec*)av; + const AtomSortRec* b = (const AtomSortRec*)bv; + if (a->section_id < b->section_id) return -1; + if (a->section_id > b->section_id) return 1; + if (a->offset < b->offset) return -1; + if (a->offset > b->offset) return 1; + if (a->id < b->id) return -1; + if (a->id > b->id) return 1; + return 0; +} + +static ObjAtomId input_map_find_atom(const InputMap* m, ObjBuilder* ob, + ObjSecId sid, u32 offset) { + u32 first, count, i; + if (!link_input_section_has_atoms(m, sid)) return OBJ_ATOM_NONE; + link_input_section_atoms(m, sid, &first, &count); + for (i = 0; i < count; ++i) { + ObjAtomId aid = m->section_atom_ids[first + i]; + const ObjAtom* a = obj_atom_get(ob, aid); + u64 begin, end; + if (!a || a->removed) continue; + begin = a->offset; + end = begin + a->size; + if (a->size != 0 && (u64)offset >= begin && (u64)offset < end) return aid; + } + for (i = 0; i < count; ++i) { + ObjAtomId aid = m->section_atom_ids[first + i]; + const ObjAtom* a = obj_atom_get(ob, aid); + if (!a || a->removed) continue; + if (a->size == 0 && offset == a->offset) return aid; + } + return OBJ_ATOM_NONE; +} + +static ObjAtomId input_map_find_symbol_atom(const InputMap* m, ObjBuilder* ob, + ObjSymId sym) { + const ObjSym* s; + ObjAtomId aid; + u32 i; + if (!ob || sym == OBJ_SYM_NONE) return OBJ_ATOM_NONE; + s = obj_symbol_get(ob, sym); + if (!s || s->section_id == OBJ_SEC_NONE) return OBJ_ATOM_NONE; + aid = input_map_find_atom(m, ob, s->section_id, (u32)s->value); + if (aid != OBJ_ATOM_NONE) return aid; + for (i = 0; i < m->nsection_atom_ids; ++i) { + const ObjAtom* a = obj_atom_get(ob, m->section_atom_ids[i]); + if (a && !a->removed && a->signature == sym) return m->section_atom_ids[i]; + } + return OBJ_ATOM_NONE; +} + +void link_input_map_alloc(LinkImage* img, InputMap* m, ObjBuilder* ob, + u32 nsym) { Heap* h = img->heap; + u32 nsection = obj_section_count(ob); + u32 natom = obj_atom_count(ob); + u32 nreloc = obj_reloc_total(ob); + AtomSortRec* atoms = NULL; + u32 nactive = 0; + u32 i; + + memset(m, 0, sizeof(*m)); m->nsym = nsym; m->sym = (LinkSymId*)h->alloc(h, sizeof(*m->sym) * nsym, _Alignof(LinkSymId)); if (!m->sym) @@ -42,10 +110,87 @@ static void map_alloc(LinkImage* img, InputMap* m, u32 nsym, u32 nsection) { if (!m->section) compiler_panic(img->c, no_loc(), "link: oom on input section map"); memset(m->section, 0, sizeof(*m->section) * nsection); + m->natom = natom; + m->atom = (LinkSectionId*)h->alloc(h, sizeof(*m->atom) * (natom ? natom : 1u), + _Alignof(LinkSectionId)); + if (!m->atom) compiler_panic(img->c, no_loc(), "link: oom on input atom map"); + memset(m->atom, 0, sizeof(*m->atom) * (natom ? natom : 1u)); + m->sym_atom = (ObjAtomId*)h->alloc( + h, sizeof(*m->sym_atom) * (nsym ? nsym : 1u), _Alignof(ObjAtomId)); + if (!m->sym_atom) + compiler_panic(img->c, no_loc(), "link: oom on input symbol atom map"); + memset(m->sym_atom, 0, sizeof(*m->sym_atom) * (nsym ? nsym : 1u)); + m->nreloc = nreloc; + m->reloc_atom = (ObjAtomId*)h->alloc( + h, sizeof(*m->reloc_atom) * (nreloc ? nreloc : 1u), _Alignof(ObjAtomId)); + if (!m->reloc_atom) + compiler_panic(img->c, no_loc(), "link: oom on input reloc atom map"); + memset(m->reloc_atom, 0, sizeof(*m->reloc_atom) * (nreloc ? nreloc : 1u)); + m->section_has_atoms = (u8*)h->alloc(h, nsection ? nsection : 1u, 1); + if (!m->section_has_atoms) + compiler_panic(img->c, no_loc(), "link: oom on input section atom map"); + memset(m->section_has_atoms, 0, nsection ? nsection : 1u); + m->section_atom_first = (u32*)h->alloc( + h, sizeof(*m->section_atom_first) * (nsection ? nsection : 1u), + _Alignof(u32)); + m->section_atom_count = (u32*)h->alloc( + h, sizeof(*m->section_atom_count) * (nsection ? nsection : 1u), + _Alignof(u32)); + if (!m->section_atom_first || !m->section_atom_count) + compiler_panic(img->c, no_loc(), "link: oom on input section atom ranges"); + memset(m->section_atom_first, 0, + sizeof(*m->section_atom_first) * (nsection ? nsection : 1u)); + memset(m->section_atom_count, 0, + sizeof(*m->section_atom_count) * (nsection ? nsection : 1u)); m->comdat_discarded = (u8*)h->alloc(h, nsection ? nsection : 1u, 1); if (!m->comdat_discarded) compiler_panic(img->c, no_loc(), "link: oom on input comdat map"); memset(m->comdat_discarded, 0, nsection ? nsection : 1u); + + if (natom > 1u) { + atoms = (AtomSortRec*)h->alloc(h, sizeof(*atoms) * natom, + _Alignof(AtomSortRec)); + if (!atoms) compiler_panic(img->c, no_loc(), "link: oom on atom sort map"); + for (i = 1; i < natom; ++i) { + const ObjAtom* a = obj_atom_get(ob, (ObjAtomId)i); + if (!a || a->removed || a->section_id == OBJ_SEC_NONE || + a->section_id >= nsection) + continue; + atoms[nactive].id = (ObjAtomId)i; + atoms[nactive].section_id = a->section_id; + atoms[nactive].offset = a->offset; + ++nactive; + } + if (nactive > 1u) qsort(atoms, nactive, sizeof(*atoms), atom_sort_rec_cmp); + } + + m->nsection_atom_ids = nactive; + if (nactive) { + ObjSecId cur = OBJ_SEC_NONE; + m->section_atom_ids = (ObjAtomId*)h->alloc( + h, sizeof(*m->section_atom_ids) * nactive, _Alignof(ObjAtomId)); + if (!m->section_atom_ids) + compiler_panic(img->c, no_loc(), "link: oom on section atom ids"); + for (i = 0; i < nactive; ++i) { + ObjSecId sid = atoms[i].section_id; + m->section_atom_ids[i] = atoms[i].id; + if (sid != cur) { + m->section_has_atoms[sid] = 1; + m->section_atom_first[sid] = i; + cur = sid; + } + m->section_atom_count[sid]++; + } + } + if (atoms) h->free(h, atoms, sizeof(*atoms) * natom); + + for (i = 1; i < nsym; ++i) + m->sym_atom[i] = input_map_find_symbol_atom(m, ob, (ObjSymId)i); + for (i = 0; i < nreloc; ++i) { + const Reloc* r = obj_reloc_at(ob, i); + if (!r || r->section_id == OBJ_SEC_NONE) continue; + m->reloc_atom[i] = input_map_find_atom(m, ob, r->section_id, r->offset); + } } /* ---- pass 1: collect symbols ---- */ @@ -98,8 +243,8 @@ void link_resolve_symbols(Linker* l, LinkImage* img) { while (obj_symiter_next(it, &e)) ++nsyms_in_input; obj_symiter_free(it); - map_alloc(img, m, nsyms_in_input + 1u /* +1 for id-0 slot */, - obj_section_count(ob)); + link_input_map_alloc(img, m, ob, + nsyms_in_input + 1u /* +1 for id-0 slot */); it = obj_symiter_new(ob); while (obj_symiter_next(it, &e)) { @@ -115,14 +260,14 @@ void link_resolve_symbols(Linker* l, LinkImage* img) { } int is_def = (s->kind != SK_UNDEF) && (s->kind == SK_ABS || s->kind == SK_COMMON || - s->kind == SK_FILE || - s->section_id != OBJ_SEC_NONE); + s->kind == SK_FILE || s->section_id != OBJ_SEC_NONE); memset(&rec, 0, sizeof(rec)); rec.name = s->name; rec.input_id = in->id; rec.obj_sym = e.id; rec.section_id = LINK_SEC_NONE; + rec.atom_id = is_def ? link_input_sym_atom(m, e.id) : OBJ_ATOM_NONE; rec.value = s->value; rec.size = s->size; rec.common_align = (s->kind == SK_COMMON) ? (u32)s->common_align : 0u; @@ -164,13 +309,12 @@ void link_resolve_symbols(Linker* l, LinkImage* img) { new_strength == bind_strength(SB_GLOBAL)) { /* COFF SELECTANY: if both defs are in COMDAT sections, * keep the earlier one and discard the new section. */ - ObjBuilder* prev_ob = (prev->input_id != LINK_INPUT_NONE) - ? LinkInputs_at(&l->inputs, - prev->input_id - 1)->obj - : NULL; - const ObjSym* prev_os = prev_ob - ? obj_symbol_get(prev_ob, prev->obj_sym) - : NULL; + ObjBuilder* prev_ob = + (prev->input_id != LINK_INPUT_NONE) + ? LinkInputs_at(&l->inputs, prev->input_id - 1)->obj + : NULL; + const ObjSym* prev_os = + prev_ob ? obj_symbol_get(prev_ob, prev->obj_sym) : NULL; if (prev_ob && prev_os && obj_sym_defined_in_comdat(prev_ob, prev_os) && obj_sym_defined_in_comdat(ob, s)) { @@ -280,14 +424,15 @@ void link_resolve_undefs(Linker* l, LinkImage* img) { Sym candidates[2] = {0, 0}; u32 ncand = 0; if (nm && nlen >= 2 && nm[0] == '_') { - candidates[ncand++] = - pool_intern_slice(l->c->global, (Slice){ .s = nm + 1, .len = (u32)(nlen - 1u) }); + candidates[ncand++] = pool_intern_slice( + l->c->global, (Slice){.s = nm + 1, .len = (u32)(nlen - 1u)}); } if (nm && nlen > 0) { char* buf = (char*)arena_array(l->c->scratch, char, nlen + 1u); buf[0] = '_'; memcpy(buf + 1, nm, nlen); - candidates[ncand++] = pool_intern_slice(l->c->global, (Slice){ .s = buf, .len = (u32)(nlen + 1u) }); + candidates[ncand++] = pool_intern_slice( + l->c->global, (Slice){.s = buf, .len = (u32)(nlen + 1u)}); } int resolved = 0; for (u32 ci = 0; !resolved && ci < ncand; ++ci) { @@ -362,14 +507,16 @@ void link_resolve_undefs(Linker* l, LinkImage* img) { /* ---- pass 1b: --gc-sections liveness ---- */ -#define GC_PACK(ii, j) (((u64)(u32)(ii) << 32) | (u32)(j)) +#define GC_ATOM_BIT 0x80000000u +#define GC_PACK(ii, j, is_atom) \ + (((u64)(u32)(ii) << 32) | ((is_atom) ? GC_ATOM_BIT : 0u) | (u32)(j)) #define GC_II(p) ((u32)((p) >> 32)) -#define GC_J(p) ((ObjSecId)((p) & 0xffffffffu)) +#define GC_IS_ATOM(p) (((u32)(p) & GC_ATOM_BIT) != 0) +#define GC_J(p) ((u32)((p) & ~GC_ATOM_BIT)) -static void gc_queue_push(GcQueue* q, Heap* h, u32 ii, ObjSecId j) { - if (VEC_GROW(h, q->items, q->cap, q->n + 1u)) - return; - q->items[q->n++] = GC_PACK(ii, j); +static void gc_queue_push(GcQueue* q, Heap* h, u32 ii, u32 j, int is_atom) { + if (VEC_GROW(h, q->items, q->cap, q->n + 1u)) return; + q->items[q->n++] = GC_PACK(ii, j, is_atom); } void link_gc_live_alloc(GcLive* g, Linker* l, Heap* h) { @@ -380,19 +527,37 @@ void link_gc_live_alloc(GcLive* g, Linker* l, Heap* h) { ? (u8**)h->alloc(h, sizeof(*g->marks) * LinkInputs_count(&l->inputs), _Alignof(u8*)) : NULL; + g->atom_marks = + LinkInputs_count(&l->inputs) + ? (u8**)h->alloc( + h, sizeof(*g->atom_marks) * LinkInputs_count(&l->inputs), + _Alignof(u8*)) + : NULL; g->nsec = LinkInputs_count(&l->inputs) ? (u32*)h->alloc(h, sizeof(*g->nsec) * LinkInputs_count(&l->inputs), _Alignof(u32)) : NULL; - if (LinkInputs_count(&l->inputs) && (!g->marks || !g->nsec)) + g->natom = + LinkInputs_count(&l->inputs) + ? (u32*)h->alloc(h, sizeof(*g->natom) * LinkInputs_count(&l->inputs), + _Alignof(u32)) + : NULL; + if (LinkInputs_count(&l->inputs) && + (!g->marks || !g->atom_marks || !g->nsec || !g->natom)) compiler_panic(l->c, no_loc(), "link: oom on gc live map"); for (ii = 0; ii < LinkInputs_count(&l->inputs); ++ii) { u32 nsec = obj_section_count(LinkInputs_at(&l->inputs, ii)->obj); + u32 natom = obj_atom_count(LinkInputs_at(&l->inputs, ii)->obj); g->nsec[ii] = nsec; + g->natom[ii] = natom; g->marks[ii] = (u8*)h->alloc(h, nsec ? nsec : 1u, 1); if (!g->marks[ii]) compiler_panic(l->c, no_loc(), "link: oom on gc marks"); memset(g->marks[ii], 0, nsec); + g->atom_marks[ii] = (u8*)h->alloc(h, natom ? natom : 1u, 1); + if (!g->atom_marks[ii]) + compiler_panic(l->c, no_loc(), "link: oom on gc atom marks"); + memset(g->atom_marks[ii], 0, natom); } } @@ -404,7 +569,14 @@ void link_gc_live_free(GcLive* g, Heap* h) { h->free(h, g->marks[ii], g->nsec[ii] ? g->nsec[ii] : 1u); h->free(h, g->marks, sizeof(*g->marks) * g->ninputs); } + if (g->atom_marks) { + for (ii = 0; ii < g->ninputs; ++ii) + if (g->atom_marks[ii]) + h->free(h, g->atom_marks[ii], g->natom[ii] ? g->natom[ii] : 1u); + h->free(h, g->atom_marks, sizeof(*g->atom_marks) * g->ninputs); + } if (g->nsec) h->free(h, g->nsec, sizeof(*g->nsec) * g->ninputs); + if (g->natom) h->free(h, g->natom, sizeof(*g->natom) * g->ninputs); } int link_gc_live_get(const GcLive* g, u32 ii, ObjSecId j) { @@ -412,17 +584,49 @@ int link_gc_live_get(const GcLive* g, u32 ii, ObjSecId j) { return g->marks[ii][j]; } +int link_gc_atom_live_get(const GcLive* g, u32 ii, ObjAtomId j) { + if (ii >= g->ninputs || j == OBJ_ATOM_NONE || j >= g->natom[ii]) return 0; + return g->atom_marks[ii][j]; +} + static void gc_mark(GcLive* g, GcQueue* q, Heap* h, u32 ii, ObjSecId j) { if (ii >= g->ninputs || j == OBJ_SEC_NONE || j >= g->nsec[ii]) return; if (g->marks[ii][j]) return; g->marks[ii][j] = 1; - gc_queue_push(q, h, ii, j); + if (q) gc_queue_push(q, h, ii, j, 0); +} + +static void gc_mark_atom(GcLive* g, GcQueue* q, Heap* h, u32 ii, ObjAtomId j) { + if (ii >= g->ninputs || j == OBJ_ATOM_NONE || j >= g->natom[ii]) return; + if (g->atom_marks[ii][j]) return; + g->atom_marks[ii][j] = 1; + if (q) gc_queue_push(q, h, ii, j, 1); +} + +static void gc_mark_section_or_atoms(GcLive* g, GcQueue* q, Heap* h, + ObjBuilder* ob, const InputMap* m, u32 ii, + ObjSecId sid) { + u32 first, count, i; + int marked = 0; + if (!link_input_section_has_atoms(m, sid)) { + gc_mark(g, q, h, ii, sid); + return; + } + link_input_section_atoms(m, sid, &first, &count); + for (i = 0; i < count; ++i) { + ObjAtomId aid = m->section_atom_ids[first + i]; + const ObjAtom* a = obj_atom_get(ob, aid); + if (!a || a->removed) continue; + gc_mark_atom(g, q, h, ii, aid); + marked = 1; + } + if (!marked) gc_mark(g, q, h, ii, sid); } /* From a LinkSymId, find the (input_idx, obj_sec_id) of its defining section. * Returns 1 on hit. */ static int gc_def_site(LinkImage* img, Linker* l, LinkSymId id, u32* out_ii, - ObjSecId* out_sid) { + ObjSecId* out_sid, ObjAtomId* out_aid) { const LinkSymbol* s; ObjBuilder* ob; const ObjSym* osym; @@ -433,7 +637,7 @@ static int gc_def_site(LinkImage* img, Linker* l, LinkSymId id, u32* out_ii, if (s->name == 0) return 0; hit = symhash_get(&img->globals, s->name); if (hit == LINK_SYM_NONE || hit == s->id) return 0; - return gc_def_site(img, l, hit, out_ii, out_sid); + return gc_def_site(img, l, hit, out_ii, out_sid, out_aid); } if (s->kind == SK_ABS || s->kind == SK_COMMON) return 0; if (s->input_id == LINK_INPUT_NONE) return 0; @@ -442,12 +646,13 @@ static int gc_def_site(LinkImage* img, Linker* l, LinkSymId id, u32* out_ii, if (!osym || osym->section_id == OBJ_SEC_NONE) return 0; *out_ii = (u32)(s->input_id - 1u); *out_sid = osym->section_id; + *out_aid = link_input_sym_atom(&img->input_maps[*out_ii], s->obj_sym); return 1; } /* Detect __start_<X> / __stop_<X> with <X> a valid C identifier. */ int link_gc_split_start_stop(const char* s, size_t n, size_t* out_off, - size_t* out_len, int* out_is_start) { + size_t* out_len, int* out_is_start) { static const char START[] = "__start_"; static const char STOP[] = "__stop_"; size_t off, len, i; @@ -480,8 +685,8 @@ int link_gc_split_start_stop(const char* s, size_t n, size_t* out_off, return 1; } -static void gc_promote_by_section_name(Linker* l, GcLive* g, GcQueue* q, - Heap* h, Sym section_name) { +static void gc_promote_by_section_name(Linker* l, LinkImage* img, GcLive* g, + GcQueue* q, Heap* h, Sym section_name) { u32 ii, j; for (ii = 0; ii < LinkInputs_count(&l->inputs); ++ii) { ObjBuilder* ob = LinkInputs_at(&l->inputs, ii)->obj; @@ -490,7 +695,7 @@ static void gc_promote_by_section_name(Linker* l, GcLive* g, GcQueue* q, const Section* s = obj_section_get(ob, j); if (!s || !link_section_kept(s)) continue; if (s->name != section_name) continue; - gc_mark(g, q, h, ii, j); + gc_mark_section_or_atoms(g, q, h, ob, &img->input_maps[ii], ii, j); } } } @@ -508,7 +713,7 @@ void link_gc_compute(Linker* l, LinkImage* img, GcLive* g) { for (j = 1; j < nsec; ++j) { const Section* s = obj_section_get(ob, j); if (s && link_section_kept(s) && !m->comdat_discarded[j]) - g->marks[ii][j] = 1; + gc_mark_section_or_atoms(g, NULL, h, ob, m, ii, j); } } return; @@ -527,7 +732,17 @@ void link_gc_compute(Linker* l, LinkImage* img, GcLive* g) { if (m->comdat_discarded[j]) continue; root = (s->flags & SF_RETAIN) || s->sem == SSEM_INIT_ARRAY || s->sem == SSEM_FINI_ARRAY || s->sem == SSEM_PREINIT_ARRAY; - if (root) gc_mark(g, &q, h, ii, j); + if (root) gc_mark_section_or_atoms(g, &q, h, ob, m, ii, j); + if (link_input_section_has_atoms(m, j)) { + u32 first, count, ai; + link_input_section_atoms(m, j, &first, &count); + for (ai = 0; ai < count; ++ai) { + ObjAtomId aid = m->section_atom_ids[first + ai]; + const ObjAtom* a = obj_atom_get(ob, aid); + if (!a || a->removed) continue; + if (a->flags & OBJ_ATOM_RETAIN) gc_mark_atom(g, &q, h, ii, aid); + } + } } } @@ -535,15 +750,25 @@ void link_gc_compute(Linker* l, LinkImage* img, GcLive* g) { LinkSymId id = symhash_get(&img->globals, l->entry_name); u32 tii; ObjSecId tsid; - if (gc_def_site(img, l, id, &tii, &tsid)) gc_mark(g, &q, h, tii, tsid); + ObjAtomId taid; + if (gc_def_site(img, l, id, &tii, &tsid, &taid)) { + if (taid != OBJ_ATOM_NONE) + gc_mark_atom(g, &q, h, tii, taid); + else + gc_mark(g, &q, h, tii, tsid); + } } while (q.n > 0) { u64 v = q.items[--q.n]; u32 cii = GC_II(v); - ObjSecId cj = GC_J(v); + int c_is_atom = GC_IS_ATOM(v); + ObjSecId cj = (ObjSecId)GC_J(v); ObjBuilder* ob = LinkInputs_at(&l->inputs, cii)->obj; InputMap* m = &img->input_maps[cii]; + const ObjAtom* src_atom = + c_is_atom ? obj_atom_get(ob, (ObjAtomId)cj) : NULL; + ObjSecId src_sec = src_atom ? src_atom->section_id : cj; u32 total = obj_reloc_total(ob); (void)obj_section_count; if (!total) continue; @@ -553,7 +778,13 @@ void link_gc_compute(Linker* l, LinkImage* img, GcLive* g) { const LinkSymbol* tsym; u32 tii; ObjSecId tsid; - if (r->section_id != cj) continue; + ObjAtomId taid; + if (r->section_id != src_sec) continue; + if (src_atom) { + u64 begin = src_atom->offset; + u64 end = begin + src_atom->size; + if ((u64)r->offset < begin || (u64)r->offset >= end) continue; + } if (r->sym == OBJ_SYM_NONE || r->sym >= m->nsym) continue; target = m->sym[r->sym]; if (target == LINK_SYM_NONE) continue; @@ -565,13 +796,18 @@ void link_gc_compute(Linker* l, LinkImage* img, GcLive* g) { const char* nm = nm_s.s; size_t namelen = nm_s.len; if (link_gc_split_start_stop(nm, namelen, &off, &ilen, NULL)) { - Sym secname = pool_intern_slice(l->c->global, (Slice){ .s = nm + off, .len = ilen }); - gc_promote_by_section_name(l, g, &q, h, secname); + Sym secname = pool_intern_slice(l->c->global, + (Slice){.s = nm + off, .len = ilen}); + gc_promote_by_section_name(l, img, g, &q, h, secname); } } - if (gc_def_site(img, l, target, &tii, &tsid)) - gc_mark(g, &q, h, tii, tsid); + if (gc_def_site(img, l, target, &tii, &tsid, &taid)) { + if (taid != OBJ_ATOM_NONE) + gc_mark_atom(g, &q, h, tii, taid); + else + gc_mark(g, &q, h, tii, tsid); + } } } @@ -586,6 +822,7 @@ void link_gc_drop_dead_globals(Linker* l, LinkImage* img, const GcLive* g) { ObjBuilder* ob; const ObjSym* osym; ObjSecId osid; + ObjAtomId aid; if (!s->defined) continue; if (s->kind == SK_ABS || s->kind == SK_COMMON) continue; if (s->input_id == LINK_INPUT_NONE) continue; @@ -594,6 +831,14 @@ void link_gc_drop_dead_globals(Linker* l, LinkImage* img, const GcLive* g) { if (!osym) continue; osid = osym->section_id; if (osid == OBJ_SEC_NONE) continue; + aid = link_input_sym_atom(&img->input_maps[s->input_id - 1u], s->obj_sym); + if (aid != OBJ_ATOM_NONE) { + if (link_gc_atom_live_get(g, (u32)(s->input_id - 1u), aid)) continue; + s->defined = 0; + s->vaddr = 0; + s->section_id = LINK_SEC_NONE; + continue; + } if (link_gc_live_get(g, (u32)(s->input_id - 1u), osid)) continue; s->defined = 0; s->vaddr = 0; @@ -654,8 +899,7 @@ static void scan_presence_before(Linker* l, u32 max_order, SymHash* defined, * C frontend's per-extern undef synthesis (e.g. every prototype * in <math.h>) drags in matching archive members even when the * user's source never references them. */ - if (!s->referenced && - (s->bind == SB_GLOBAL || s->bind == SB_WEAK)) + if (!s->referenced && (s->bind == SB_GLOBAL || s->bind == SB_WEAK)) continue; symhash_set(undefs, s->name, 1u); } else { @@ -753,9 +997,9 @@ void link_synth_coff_ctor_dtor_list(Linker* l) { if (!l || l->c->target.obj != CFREE_OBJ_COFF) return; ob = obj_new(l->c); if (!ob) return; - sid = obj_section_ex(ob, pool_intern_slice(l->c->global, SLICE_LIT(".rdata$ctors")), - SEC_RODATA, SSEM_PROGBITS, SF_ALLOC | SF_RETAIN, 16, - 0u, 0u, 0u); + sid = obj_section_ex( + ob, pool_intern_slice(l->c->global, SLICE_LIT(".rdata$ctors")), + SEC_RODATA, SSEM_PROGBITS, SF_ALLOC | SF_RETAIN, 16, 0u, 0u, 0u); obj_section_replace_bytes(ob, sid, kZeros, sizeof(kZeros)); obj_symbol_ex(ob, pool_intern_slice(l->c->global, SLICE_LIT("__CTOR_LIST__")), SB_GLOBAL, SV_DEFAULT, SK_OBJ, sid, 0, 0, 0); @@ -769,13 +1013,13 @@ void link_synth_coff_ctor_dtor_list(Linker* l) { * already emits inline probes (or links libmingwex's __chkstk * which is a plain object, not an ARM64EC alias). */ if (l->c->target.arch == CFREE_ARCH_ARM_64) { - ObjSecId tsid = - obj_section_ex(ob, pool_intern_slice(l->c->global, SLICE_LIT(".text$chkstk")), - SEC_TEXT, SSEM_PROGBITS, - SF_ALLOC | SF_EXEC | SF_RETAIN, 4, 0u, 0u, 0u); + ObjSecId tsid = obj_section_ex( + ob, pool_intern_slice(l->c->global, SLICE_LIT(".text$chkstk")), + SEC_TEXT, SSEM_PROGBITS, SF_ALLOC | SF_EXEC | SF_RETAIN, 4, 0u, 0u, 0u); obj_section_replace_bytes(ob, tsid, kAa64Chkstk, sizeof(kAa64Chkstk)); - obj_symbol_ex(ob, pool_intern_slice(l->c->global, SLICE_LIT("__chkstk")), SB_GLOBAL, - SV_DEFAULT, SK_FUNC, tsid, 0, sizeof(kAa64Chkstk), 0); + obj_symbol_ex(ob, pool_intern_slice(l->c->global, SLICE_LIT("__chkstk")), + SB_GLOBAL, SV_DEFAULT, SK_FUNC, tsid, 0, sizeof(kAa64Chkstk), + 0); } obj_finalize(ob); in = LinkInputs_push(&l->inputs, &idx); @@ -784,7 +1028,8 @@ void link_synth_coff_ctor_dtor_list(Linker* l) { in->kind = LINK_INPUT_OBJ_BYTES; in->order = l->next_input_order++; in->obj = ob; - in->name = pool_intern_slice(l->c->global, SLICE_LIT("<cfree-synth-coff-runtime>")); + in->name = + pool_intern_slice(l->c->global, SLICE_LIT("<cfree-synth-coff-runtime>")); in->soname = 0; } @@ -808,7 +1053,8 @@ void link_ingest_archives(Linker* l) { Sym want_ifunc_init = 0; if (ar->whole_archive) continue; if (l->emit_static_exe && inputs_have_defined_ifunc_before(l, ar->order)) - want_ifunc_init = pool_intern_slice(l->c->global, SLICE_LIT("__cfree_ifunc_init")); + want_ifunc_init = + pool_intern_slice(l->c->global, SLICE_LIT("__cfree_ifunc_init")); for (;;) { SymHash defined, undefs; int changed = 0; diff --git a/src/obj/format.h b/src/obj/format.h @@ -124,6 +124,7 @@ typedef struct ObjFormatImpl { ObjFormatLayoutDynFn layout_dyn; ObjFormatFreeDynFn free_dyn; const ObjFormatEmuOps* emu; + u8 split_sections_as_atoms; const ObjElfArchOps* (*elf_arch)(CfreeArchKind); const ObjElfArchOps* (*elf_machine)(u32 e_machine); diff --git a/src/obj/macho/emit.c b/src/obj/macho/emit.c @@ -187,6 +187,9 @@ static u32 section_flags_for(u16 sec_kind, u16 sec_flags, const char* sectname, if (sec_flags & SF_STRINGS) { f = (f & ~SECTION_TYPE) | S_CSTRING_LITERALS; } + if (sec_flags & SF_RETAIN) { + f |= S_ATTR_NO_DEAD_STRIP; + } /* Default S_REGULAR (0) for all others. */ return f; } @@ -258,6 +261,7 @@ void emit_macho(Compiler* c, ObjBuilder* ob, Writer* w) { MSec* secs = arena_zarray(c->scratch, MSec, nobjsec ? nobjsec : 1); u32* obj_to_msec = arena_zarray(c->scratch, u32, nobjsec ? nobjsec : 1); u32 nsecs = 0; + int has_explicit_atoms = obj_atom_count(ob) > 1u; for (u32 i = 1; i < nobjsec; ++i) { const Section* s = obj_section_get(ob, i); @@ -306,6 +310,13 @@ void emit_macho(Compiler* c, ObjBuilder* ob, Writer* w) { obj_to_msec[i] = nsecs + 1; /* 1-based: matches Mach-O n_sect. */ nsecs++; } + if (nsecs > 255u) { + compiler_panic(c, no_loc(), + "emit_macho: too many physical sections for Mach-O " + "symbol n_sect ordinals (%u > 255); use atom splitting " + "instead of physical split sections", + nsecs); + } /* ---- pass 2: assign vmaddrs (segment-relative) and per-section * flat-layout addresses. MH_OBJECT keeps everything in @@ -424,9 +435,15 @@ void emit_macho(Compiler* c, ObjBuilder* ob, Writer* w) { } else { type |= N_SECT; u32 ms_idx = (s->section_id < nobjsec) ? obj_to_msec[s->section_id] : 0; + if (ms_idx > 255u) { + compiler_panic(c, no_loc(), + "emit_macho: symbol section ordinal %u exceeds " + "Mach-O n_sect range", + ms_idx); + } n_sect = (u8)ms_idx; - if (n_sect && n_sect <= nsecs) { - value = secs[n_sect - 1].addr + s->value; + if (n_sect && ms_idx <= nsecs) { + value = secs[ms_idx - 1].addr + s->value; } if (s->bind == SB_WEAK) n_desc |= N_WEAK_DEF; } @@ -437,6 +454,12 @@ void emit_macho(Compiler* c, ObjBuilder* ob, Writer* w) { * field) are already excluded by read_macho before stashing, * so a plain OR can't double-count. */ n_desc |= s->flags; + { + ObjAtomId aid = obj_atom_find_symbol(ob, e.id); + const ObjAtom* atom = obj_atom_get(ob, aid); + if (atom && atom->signature == e.id && (atom->flags & OBJ_ATOM_RETAIN)) + n_desc |= N_NO_DEAD_STRIP; + } ms->n_type = type; ms->n_sect = n_sect; @@ -591,8 +614,12 @@ void emit_macho(Compiler* c, ObjBuilder* ob, Writer* w) { if (r->addend != 0 && mtype != ARM64_RELOC_UNSIGNED) { u8* slot = buf + (size_t)j * MACHO_RELOC_SIZE; wr_u32_le(slot + 0, (u32)r->offset); + /* ARM64_RELOC_ADDEND stores a signed 24-bit immediate in + * r_symbolnum. It is not a symbol-table reference; setting + * r_extern would make readers interpret the addend as a symbol + * index. */ u32 packed = ((u32)(i64)r->addend & 0x00ffffffu) | (0u << 24) | - (length << 25) | (1u << 27) /*extern*/ | + (length << 25) | (ARM64_RELOC_ADDEND << 28); wr_u32_le(slot + 4, packed); ++j; @@ -666,7 +693,7 @@ void emit_macho(Compiler* c, ObjBuilder* ob, Writer* w) { wr_u32(w, MH_OBJECT); wr_u32(w, nload_cmds); wr_u32(w, sizeofcmds); - wr_u32(w, 0); /* flags — MH_OBJECT carries none in v1 */ + wr_u32(w, has_explicit_atoms ? MH_SUBSECTIONS_VIA_SYMBOLS : 0); wr_u32(w, 0); /* reserved */ /* LC_SEGMENT_64 (anonymous, contains everything) */ diff --git a/src/obj/macho/macho.h b/src/obj/macho/macho.h @@ -41,6 +41,7 @@ #define MH_NOUNDEFS 0x00000001u #define MH_DYLDLINK 0x00000004u #define MH_TWOLEVEL 0x00000080u +#define MH_SUBSECTIONS_VIA_SYMBOLS 0x00002000u #define MH_PIE 0x00200000u #define MH_HAS_TLV_DESCRIPTORS 0x00800000u diff --git a/src/obj/macho/read.c b/src/obj/macho/read.c @@ -9,6 +9,7 @@ * the linker has no consumer for it yet). Other archs / endianness * produce a compiler_panic with a diagnostic. */ +#include <stdlib.h> #include <string.h> #include "core/arena.h" @@ -43,6 +44,25 @@ typedef struct MSecRec { ObjSecId obj_sec; /* assigned in pass 1 */ } MSecRec; +typedef struct MAtomCand { + ObjSecId sec; + ObjSymId sym; + u32 offset; + u32 flags; +} MAtomCand; + +static int matom_cand_cmp(const void* av, const void* bv) { + const MAtomCand* a = (const MAtomCand*)av; + const MAtomCand* b = (const MAtomCand*)bv; + if (a->sec < b->sec) return -1; + if (a->sec > b->sec) return 1; + if (a->offset < b->offset) return -1; + if (a->offset > b->offset) return 1; + if (a->sym < b->sym) return -1; + if (a->sym > b->sym) return 1; + return 0; +} + static u32 fixed16_len(const char* s) { u32 n = 0; while (n < 16 && s[n] != 0) ++n; @@ -116,6 +136,7 @@ ObjBuilder* read_macho(Compiler* c, const char* name, const u8* data, u32 filetype = rd_u32_le(data + 12); u32 ncmds = rd_u32_le(data + 16); u32 sizeofcmds = rd_u32_le(data + 20); + u32 mh_flags = rd_u32_le(data + 24); if (!macho || !macho->reloc_from) compiler_panic(c, no_loc(), "read_macho: unsupported cputype 0x%x", @@ -228,6 +249,9 @@ ObjBuilder* read_macho(Compiler* c, const char* name, const u8* data, * mach_idx -> ObjSymId so reloc resolution works. */ ObjSymId* sym_macho_to_obj = arena_zarray(c->scratch, ObjSymId, nsyms ? nsyms : 1); + MAtomCand* atom_cands = + arena_zarray(c->scratch, MAtomCand, nsyms ? nsyms : 1); + u32 natom_cands = 0; const u8* sbase = data + symoff; for (u32 i = 0; i < nsyms; ++i) { @@ -304,6 +328,18 @@ ObjBuilder* read_macho(Compiler* c, const char* name, const u8* data, ObjSymId id = obj_symbol_ex(ob, sn, (SymBind)bind, (SymVis)vis, (SymKind)kind, sec_id, value, size, cmnalign); obj_sym_mark_referenced(ob, id); + if ((mh_flags & MH_SUBSECTIONS_VIA_SYMBOLS) && type_field == N_SECT && + sec_id != OBJ_SEC_NONE) { + MAtomCand* ac = &atom_cands[natom_cands++]; + ac->sec = sec_id; + ac->sym = id; + ac->offset = (u32)value; + if ((n_desc & N_NO_DEAD_STRIP) || + (n_sect != 0 && n_sect <= nmsecs && + (msecs[n_sect - 1].flags & S_ATTR_NO_DEAD_STRIP))) { + ac->flags |= OBJ_ATOM_RETAIN; + } + } /* n_desc carries Mach-O attribute bits beyond what bind/vis/kind * model — N_NO_DEAD_STRIP, N_REF_TO_WEAK, N_ARM_THUMB_DEF, etc. * Mask off the bits we already round-trip via bind (N_WEAK_DEF / @@ -317,6 +353,24 @@ ObjBuilder* read_macho(Compiler* c, const char* name, const u8* data, sym_macho_to_obj[i] = id; } + if (mh_flags & MH_SUBSECTIONS_VIA_SYMBOLS) { + if (natom_cands > 1u) + qsort(atom_cands, natom_cands, sizeof(*atom_cands), matom_cand_cmp); + for (u32 i = 0; i < natom_cands; ++i) { + MAtomCand* ac = &atom_cands[i]; + const Section* sec = obj_section_get(ob, ac->sec); + u32 end = sec ? ((sec->sem == SSEM_NOBITS || sec->kind == SEC_BSS) + ? sec->bss_size + : sec->bytes.total) + : ac->offset; + if (i + 1u < natom_cands && atom_cands[i + 1u].sec == ac->sec) + end = atom_cands[i + 1u].offset; + if (end >= ac->offset) + obj_atom_define(ob, ac->sec, ac->offset, end - ac->offset, ac->sym, + ac->flags); + } + } + /* ---- pass 4: parse per-section relocations into ObjBuilder relocs. * Mach-O encodes addends out-of-band as a leading * ARM64_RELOC_ADDEND followed by the real reloc; the diff --git a/src/obj/obj.c b/src/obj/obj.c @@ -19,6 +19,7 @@ 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 */ +SEGVEC_DEFINE(Atoms, ObjAtom, 5); /* 32 entries per segment */ #define OBJ_EXT_SLOT_COUNT 6 /* OBJ_EXT_NONE..OBJ_EXT_WASM_IMPORTS */ @@ -34,6 +35,7 @@ struct CfreeObjBuilder { Symbols symbols; /* index 0 reserved as "none" */ Relocs relocs; /* flat across all sections; filtered on read */ Groups groups; /* index 0 reserved as "none" */ + Atoms atoms; /* index 0 reserved as "none" */ /* Format-specific ELF e_flags. Set by read_elf to the input's * e_flags (e.g. on RISC-V, EF_RISCV_RVC | EF_RISCV_FLOAT_ABI_DOUBLE); * consumed by emit_elf to round-trip. Zero when not set — emit_elf @@ -71,11 +73,13 @@ ObjBuilder* obj_new(Compiler* c) { Symbols_init(&ob->symbols, h); Relocs_init(&ob->relocs, h); Groups_init(&ob->groups, h); + Atoms_init(&ob->atoms, 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)) { + !Symbols_push(&ob->symbols, NULL) || !Groups_push(&ob->groups, NULL) || + !Atoms_push(&ob->atoms, NULL)) { obj_free(ob); return NULL; } @@ -119,6 +123,7 @@ void obj_free(ObjBuilder* ob) { Symbols_fini(&ob->symbols); Relocs_fini(&ob->relocs); Groups_fini(&ob->groups); + Atoms_fini(&ob->atoms); ob->heap->free(ob->heap, ob, sizeof(*ob)); } @@ -417,6 +422,21 @@ void obj_sym_mark_referenced(ObjBuilder* ob, ObjSymId id) { if (s) s->referenced = 1; } +ObjAtomId obj_atom_define(ObjBuilder* ob, ObjSecId section_id, u32 offset, + u32 size, ObjSymId signature, u32 flags) { + u32 id; + ObjAtom* a; + if (!ob || section_id == OBJ_SEC_NONE) return OBJ_ATOM_NONE; + a = Atoms_push(&ob->atoms, &id); + if (!a) return OBJ_ATOM_NONE; + a->section_id = section_id; + a->offset = offset; + a->size = size; + a->signature = signature; + a->flags = flags; + return (ObjAtomId)id; +} + ObjGroupId obj_group(ObjBuilder* ob, Sym name, ObjSymId signature, u32 flags) { u32 id; ObjGroup* g = Groups_push(&ob->groups, &id); @@ -577,6 +597,25 @@ void obj_sweep_dead(ObjBuilder* ob) { } } + { + u32 natom = Atoms_count(&ob->atoms); + for (i = 1; i < natom; ++i) { + ObjAtom* a = Atoms_at(&ob->atoms, i); + const Section* sec; + const ObjSym* sig; + if (!a || a->removed) continue; + sec = Sections_at(&ob->sections, a->section_id); + if (!sec || sec->removed) { + a->removed = 1; + continue; + } + if (a->signature != OBJ_SYM_NONE) { + sig = Symbols_at(&ob->symbols, a->signature); + if (!sig || sig->removed) a->removed = 1; + } + } + } + /* Pass 3: compact each group's member list to drop removed sections; * tombstone the group if its list empties out or its signature symbol * is removed. Member list is rewritten in place — the storage stays @@ -648,6 +687,63 @@ const ObjSym* obj_symbol_get(const ObjBuilder* ob, ObjSymId id) { return Symbols_at(&ob->symbols, id); } +u32 obj_atom_count(const ObjBuilder* ob) { return Atoms_count(&ob->atoms); } + +const ObjAtom* obj_atom_get(const ObjBuilder* ob, ObjAtomId id) { + if (id == OBJ_ATOM_NONE) return NULL; + return Atoms_at(&ob->atoms, id); +} + +int obj_section_has_atoms(const ObjBuilder* ob, ObjSecId sid) { + u32 n; + if (!ob || sid == OBJ_SEC_NONE) return 0; + n = Atoms_count(&ob->atoms); + for (u32 i = 1; i < n; ++i) { + const ObjAtom* a = Atoms_at(&ob->atoms, i); + if (a && !a->removed && a->section_id == sid) return 1; + } + return 0; +} + +ObjAtomId obj_atom_find(const ObjBuilder* ob, ObjSecId sid, u32 offset) { + u32 n; + if (!ob || sid == OBJ_SEC_NONE) return OBJ_ATOM_NONE; + n = Atoms_count(&ob->atoms); + for (u32 i = 1; i < n; ++i) { + const ObjAtom* a = Atoms_at(&ob->atoms, i); + u64 begin, end; + if (!a || a->removed || a->section_id != sid) continue; + begin = a->offset; + end = begin + a->size; + if (a->size != 0 && (u64)offset >= begin && (u64)offset < end) + return (ObjAtomId)i; + } + for (u32 i = 1; i < n; ++i) { + const ObjAtom* a = Atoms_at(&ob->atoms, i); + if (!a || a->removed || a->section_id != sid) continue; + if (a->size == 0 && offset == a->offset) + return (ObjAtomId)i; + } + return OBJ_ATOM_NONE; +} + +ObjAtomId obj_atom_find_symbol(const ObjBuilder* ob, ObjSymId sym) { + const ObjSym* s; + ObjAtomId aid; + u32 n; + if (!ob || sym == OBJ_SYM_NONE) return OBJ_ATOM_NONE; + s = obj_symbol_get(ob, sym); + if (!s || s->section_id == OBJ_SEC_NONE) return OBJ_ATOM_NONE; + aid = obj_atom_find(ob, s->section_id, (u32)s->value); + if (aid != OBJ_ATOM_NONE) return aid; + n = Atoms_count(&ob->atoms); + for (u32 i = 1; i < n; ++i) { + const ObjAtom* a = Atoms_at(&ob->atoms, i); + if (a && !a->removed && a->signature == sym) return (ObjAtomId)i; + } + return OBJ_ATOM_NONE; +} + u32 obj_group_count(const ObjBuilder* ob) { return Groups_count(&ob->groups); } const ObjGroup* obj_group_get(const ObjBuilder* ob, ObjGroupId id) { diff --git a/src/obj/obj.h b/src/obj/obj.h @@ -98,6 +98,13 @@ typedef u32 ObjGroupId; typedef u32 ObjSymId; #define OBJ_SYM_NONE 0u +typedef u32 ObjAtomId; +#define OBJ_ATOM_NONE 0u + +typedef enum ObjAtomFlag { + OBJ_ATOM_RETAIN = 1u << 0, +} ObjAtomFlag; + typedef enum RelocKind { R_NONE = 0, R_ABS32, @@ -350,6 +357,15 @@ typedef struct ObjGroup { u8 removed; } ObjGroup; +typedef struct ObjAtom { + ObjSecId section_id; + u32 offset; + u32 size; + ObjSymId signature; + u32 flags; + u8 removed; +} ObjAtom; + /* The single concrete in-memory object representation. * Written by MCEmitter/CGTarget (during compile) or by an .o reader (during * link). Read by file emitters, the linker (file and JIT), and objdump. @@ -429,6 +445,9 @@ void obj_reloc_ex(ObjBuilder*, ObjSecId section_id, u32 offset, RelocKind, * emitted into the input. */ void obj_sym_mark_referenced(ObjBuilder*, ObjSymId); +ObjAtomId obj_atom_define(ObjBuilder*, ObjSecId section_id, u32 offset, + u32 size, ObjSymId signature, u32 flags); + ObjGroupId obj_group(ObjBuilder*, Sym name, ObjSymId signature, u32 flags); void obj_group_add_section(ObjBuilder*, ObjGroupId group_id, ObjSecId section_id); @@ -523,6 +542,11 @@ const Reloc* obj_reloc_at(const ObjBuilder*, u32 idx); /* 0..total-1 */ * never returned; unknown kinds collapse to "UNKNOWN". */ const char* reloc_kind_name(RelocKind); const ObjSym* obj_symbol_get(const ObjBuilder*, ObjSymId); +u32 obj_atom_count(const ObjBuilder*); +const ObjAtom* obj_atom_get(const ObjBuilder*, ObjAtomId); +int obj_section_has_atoms(const ObjBuilder*, ObjSecId); +ObjAtomId obj_atom_find(const ObjBuilder*, ObjSecId section_id, u32 offset); +ObjAtomId obj_atom_find_symbol(const ObjBuilder*, ObjSymId); u32 obj_group_count(const ObjBuilder*); const ObjGroup* obj_group_get(const ObjBuilder*, ObjGroupId id); @@ -637,6 +661,7 @@ int obj_format_extern_via_got(const Compiler*); * object (section_id == OBJ_SEC_NONE). Pure format/symbol policy with * no per-arch behavior — shared by every backend that emits GOT loads. */ int obj_symbol_extern_via_got(const Compiler*, ObjBuilder*, ObjSymId); +int obj_format_split_sections_as_atoms(const Compiler*); /* Apply the active object format's C-symbol mangling to `name` (a * NUL-terminated C string) and return the result interned in diff --git a/src/obj/obj_secnames.c b/src/obj/obj_secnames.c @@ -25,6 +25,7 @@ #include "core/heap.h" #include "core/pool.h" #include "core/slice.h" +#include "obj/format.h" static Sym secname_panic_unimpl(Compiler* c, const char* which) { SrcLoc l = {0, 0, 0}; @@ -141,6 +142,13 @@ int obj_symbol_extern_via_got(const Compiler* c, ObjBuilder* obj, return s && s->section_id == OBJ_SEC_NONE; } +int obj_format_split_sections_as_atoms(const Compiler* c) { + const ObjFormatImpl* fmt; + if (!c) return 0; + fmt = obj_format_lookup(c->target.obj); + return fmt && fmt->split_sections_as_atoms; +} + /* C-symbol mangling for the active object format. Mach-O prepends a * single `_` to every C source-level symbol on disk (matching Apple cc * and decl.c): "main" → `_main`, "_start" → `__start`, diff --git a/src/obj/registry.c b/src/obj/registry.c @@ -281,6 +281,7 @@ static const ObjFormatImpl obj_format_impl_macho = { .read = read_macho, .read_dso = read_macho_dso, .link_emit = OBJ_LINK_EMIT_MACHO, + .split_sections_as_atoms = 1, .macho_arch = obj_macho_arch, .macho_cputype = obj_macho_cputype, }; diff --git a/src/opt/pass_lower.c b/src/opt/pass_lower.c @@ -1625,18 +1625,15 @@ static int all_defs_dead(Func* f, Inst* in, u64* live) { void opt_dead_def_elim_with_live(Func* f, const OptLiveInfo* live_info) { u32 words = live_info ? live_info->words : f->opt_live_words; + if (!words) words = bit_words(opt_reg_count(f)); f->opt_dde_live_words_touched = 0; InstRefs refs; memset(&refs, 0, sizeof refs); for (u32 b = 0; b < f->nblocks; ++b) { Block* bl = &f->blocks[b]; - u64* live = arena_zarray(f->arena, u64, words); - const u64* live_out = - live_info ? live_info->blocks[b].live_out.words : NULL; + u64* live = arena_zarray(f->arena, u64, words ? words : 1u); f->opt_dde_live_words_touched += words; - if (live_out) { - for (u32 w = 0; w < words; ++w) live[w] = live_out[w]; - } + live_copy_block_out(f, live_info, b, live, words); Inst* new_insts = arena_array(f->arena, Inst, bl->ninsts); u32 w = 0; diff --git a/test/driver/run.sh b/test/driver/run.sh @@ -390,6 +390,45 @@ else fail=$((fail + 1)) fi +{ + printf 'int live(void) { return 0; }\n' + i=0 + while [ "$i" -lt 300 ]; do + printf 'int f%s(void) { return %s; }\n' "$i" "$i" + i=$((i + 1)) + done +} > "$work/macho-many.c" +if "$CFREE" cc -target arm64-apple-macos -O1 -ffunction-sections \ + -c "$work/macho-many.c" -o "$work/macho-many.o" \ + > "$work/macho-many-cc.out" 2> "$work/macho-many-cc.err" && + "$CFREE" objdump -h -t "$work/macho-many.o" \ + > "$work/macho-many-dump.out" 2> "$work/macho-many-dump.err"; then + macho_flags=$(od -An -tx4 -j 24 -N 4 "$work/macho-many.o" 2>/dev/null | + tr -d '[:space:]') + macho_sec_count=$(awk ' + /^SYMBOL TABLE:/ { in_sec = 0 } + in_sec && /^ *[0-9]+ / { n++ } + /^Sections:/ { in_sec = 1 } + END { print n + 0 } + ' "$work/macho-many-dump.out") + if [ "$macho_sec_count" -le 8 ] && + ! grep -q '\*UND\*' "$work/macho-many-dump.out" && + [ "$macho_flags" = "00002000" ]; then + printf 'PASS %s\n' "macho-function-sections-atoms" + pass=$((pass + 1)) + else + printf 'FAIL %s (sections=%s flags=%s; unexpected UND/flags/fanout)\n' \ + "macho-function-sections-atoms" "$macho_sec_count" "$macho_flags" + sed 's/^/ | /' "$work/macho-many-dump.out" + fail=$((fail + 1)) + fi +else + printf 'FAIL %s (compile or objdump failed)\n' \ + "macho-function-sections-atoms" + sed 's/^/ | /' "$work/macho-many-cc.err" "$work/macho-many-dump.err" + fail=$((fail + 1)) +fi + cat > "$work/run-main.c" <<'SRC' int add42(int); int main(void) { return add42(0); } diff --git a/test/libc/glibc/extract.sh b/test/libc/glibc/extract.sh @@ -14,7 +14,7 @@ # (Containerfile, Containerfile.x64, Containerfile.rv64). # # The aarch64 sysroot keeps the historical bare `build/glibc-sysroot/` -# path so test-glibc (the cfree-ld aarch64 harness) is unaffected. +# path so test-libc-glibc (the cfree-ld aarch64 harness) is unaffected. # test-libc on Linux uses the arch-suffixed paths uniformly and # re-targets `build/glibc-sysroot/` for aarch64. # diff --git a/test/libc/musl/extract.sh b/test/libc/musl/extract.sh @@ -13,7 +13,7 @@ # (Containerfile, Containerfile.x64, Containerfile.rv64). # # The aarch64 sysroot keeps the historical bare `build/musl-sysroot/` -# path so test-musl (the cfree-ld dynamic/static aarch64 harness) is +# path so test-libc-musl (the cfree-ld dynamic/static aarch64 harness) is # unaffected. test-libc on Linux uses the arch-suffixed paths uniformly # and re-targets `build/musl-sysroot/` for aarch64. # diff --git a/test/link/CORPUS.md b/test/link/CORPUS.md @@ -126,6 +126,13 @@ Cases 02–09 all pair ADR_PREL_PG_HI21 with their primary LDST reloc. |---|------|-----------| | 35 | `linker_script_kernel` | `ENTRY`, `SECTIONS { . = 0x40080000; .text/.rodata/.data/.bss with `ALIGN`; `__bss_start`, `_end`; `/DISCARD/` of `.note.*`, `.comment`, `.eh_frame`. Linked image boots under `qemu-system-aarch64 -kernel` and exits via ARM semihosting. | +### Group H — Mach-O atoms + +| # | Name | Exercises | +|---|------|-----------| +| 40 | `macho_gc_dead_atom_reloc` | GC skips relocations from dead Mach-O subsections-via-symbols atoms | +| 41 | `macho_many_atom_relocs` | Many Mach-O function atoms and call relocations under `--gc-sections` | + ### bad/ — negative tests | # | Name | Exercises | diff --git a/test/link/cases/40_macho_gc_dead_atom_reloc/a.c b/test/link/cases/40_macho_gc_dead_atom_reloc/a.c @@ -0,0 +1,9 @@ +/* Mach-O subsections-via-symbols GC regression: a dead atom with a relocation + * must not emit that relocation against the first live atom in the same input + * section. */ + +__attribute__((noinline)) int live_fn(void) { return 0; } +__attribute__((noinline)) int dead_target(void) { return 7; } +__attribute__((noinline)) int dead_reloc_source(void) { return dead_target(); } + +int test_main(void) { return live_fn(); } diff --git a/test/link/cases/40_macho_gc_dead_atom_reloc/cflags b/test/link/cases/40_macho_gc_dead_atom_reloc/cflags @@ -0,0 +1 @@ +-ffunction-sections diff --git a/test/link/cases/40_macho_gc_dead_atom_reloc/gc_absent b/test/link/cases/40_macho_gc_dead_atom_reloc/gc_absent @@ -0,0 +1,2 @@ +dead_reloc_source +dead_target diff --git a/test/link/cases/40_macho_gc_dead_atom_reloc/linker_flags b/test/link/cases/40_macho_gc_dead_atom_reloc/linker_flags @@ -0,0 +1 @@ +--gc-sections diff --git a/test/link/cases/40_macho_gc_dead_atom_reloc/targets b/test/link/cases/40_macho_gc_dead_atom_reloc/targets @@ -0,0 +1 @@ +aa64-macho diff --git a/test/link/cases/41_macho_many_atom_relocs/a.c b/test/link/cases/41_macho_many_atom_relocs/a.c @@ -0,0 +1,28 @@ +/* Mach-O atom stress: many subsections-via-symbols functions in one input + * section, with data relocations keeping the live atoms reachable. */ + +#define NOINLINE __attribute__((noinline)) + +#define EACH64(M) \ + M(0) M(1) M(2) M(3) M(4) M(5) M(6) M(7) M(8) M(9) M(10) M(11) M(12) M(13) \ + M(14) M(15) M(16) M(17) M(18) M(19) M(20) M(21) M(22) M(23) M(24) \ + M(25) M(26) M(27) M(28) M(29) M(30) M(31) M(32) M(33) M(34) M(35) \ + M(36) M(37) M(38) M(39) M(40) M(41) M(42) M(43) M(44) M(45) \ + M(46) M(47) M(48) M(49) M(50) M(51) M(52) M(53) M(54) \ + M(55) M(56) M(57) M(58) M(59) M(60) M(61) M(62) M(63) + +#define LIVE_DEF(n) NOINLINE int live_##n(void) { return n; } +#define DEAD_DEF(n) NOINLINE int dead_##n(void) { return n; } +#define LIVE_REF(n) live_##n, + +EACH64(LIVE_DEF) +EACH64(DEAD_DEF) + +typedef int (*Fn)(void); + +Fn live_table[] = {EACH64(LIVE_REF)}; + +int test_main(void) { + volatile Fn* p = live_table; + return p[63](); +} diff --git a/test/link/cases/41_macho_many_atom_relocs/cflags b/test/link/cases/41_macho_many_atom_relocs/cflags @@ -0,0 +1 @@ +-ffunction-sections diff --git a/test/link/cases/41_macho_many_atom_relocs/expected b/test/link/cases/41_macho_many_atom_relocs/expected @@ -0,0 +1 @@ +63 diff --git a/test/link/cases/41_macho_many_atom_relocs/gc_absent b/test/link/cases/41_macho_many_atom_relocs/gc_absent @@ -0,0 +1,2 @@ +dead_0 +dead_63 diff --git a/test/link/cases/41_macho_many_atom_relocs/gc_present b/test/link/cases/41_macho_many_atom_relocs/gc_present @@ -0,0 +1,2 @@ +live_0 +live_63 diff --git a/test/link/cases/41_macho_many_atom_relocs/linker_flags b/test/link/cases/41_macho_many_atom_relocs/linker_flags @@ -0,0 +1 @@ +--gc-sections diff --git a/test/link/cases/41_macho_many_atom_relocs/targets b/test/link/cases/41_macho_many_atom_relocs/targets @@ -0,0 +1 @@ +aa64-macho diff --git a/test/link/cases/41_macho_script_atoms/a.c b/test/link/cases/41_macho_script_atoms/a.c @@ -0,0 +1,6 @@ +/* Mach-O atomized sections must be visible to linker-script layout. */ + +__attribute__((noinline)) int live_fn(void) { return 0; } +__attribute__((noinline)) int dead_fn(void) { return 3; } + +int test_main(void) { return live_fn(); } diff --git a/test/link/cases/41_macho_script_atoms/cflags b/test/link/cases/41_macho_script_atoms/cflags @@ -0,0 +1 @@ +-ffunction-sections diff --git a/test/link/cases/41_macho_script_atoms/gc_absent b/test/link/cases/41_macho_script_atoms/gc_absent @@ -0,0 +1 @@ +dead_fn diff --git a/test/link/cases/41_macho_script_atoms/linker_flags b/test/link/cases/41_macho_script_atoms/linker_flags @@ -0,0 +1 @@ +--gc-sections diff --git a/test/link/cases/41_macho_script_atoms/linker_script b/test/link/cases/41_macho_script_atoms/linker_script @@ -0,0 +1 @@ +script.lds diff --git a/test/link/cases/41_macho_script_atoms/script.lds b/test/link/cases/41_macho_script_atoms/script.lds @@ -0,0 +1,21 @@ +ENTRY(_test_main) + +SECTIONS { + . = 0x100000000; + + .text : ALIGN(16) { + *(.text .text.* *text) + } + + .rodata : ALIGN(8) { + *(.rodata .rodata.* *const *cstring) + } + + .data : ALIGN(8) { + *(.data .data.* *data) + } + + .bss : ALIGN(8) { + *(.bss .bss.* *bss) + } +} diff --git a/test/link/cases/41_macho_script_atoms/targets b/test/link/cases/41_macho_script_atoms/targets @@ -0,0 +1 @@ +aa64-macho diff --git a/test/link/run.sh b/test/link/run.sh @@ -245,7 +245,7 @@ fi # Cached start.o — every case used to recompile this from the same source # (~40 ms × N cases). Build it once for the whole harness run. -START_OBJ="$BUILD_DIR/link_start.o" +START_OBJ="$BUILD_DIR/link_start.$TEST_ARCH.$CFREE_TEST_OBJ.o" have_start_obj=0 if [ $have_clang_cross -eq 1 ]; then if clang $CLANG_TARGET -O1 -ffreestanding -fno-stack-protector \ diff --git a/test/parse/run.sh b/test/parse/run.sh @@ -280,7 +280,7 @@ fi if [ -x "$PARSE_RUNNER" ]; then printf ' %s parse-runner\n' "$(color_grn found)" else - printf ' %s parse-runner missing — run "make test-parse"\n' \ + printf ' %s parse-runner missing — run "make test-parse-ok"\n' \ "$(color_red FATAL)" >&2 exit 1 fi diff --git a/test/parse/run_errors.sh b/test/parse/run_errors.sh @@ -25,7 +25,7 @@ FILTER="${1:-${CFREE_TEST_FILTER:-}}" PARSE_RUNNER="$BUILD_DIR/parse-runner" if [ ! -x "$PARSE_RUNNER" ]; then - echo "parse-err: parse-runner not built at $PARSE_RUNNER (run test-parse once first)" >&2 + echo "parse-err: parse-runner not built at $PARSE_RUNNER (run test-parse-ok once first)" >&2 exit 2 fi diff --git a/test/test.mk b/test/test.mk @@ -1,8 +1,10 @@ # Data-driven tests. Included from the top-level Makefile. # -# - test-driver: narrow CLI behavior checks that do not belong to a specific +# - test-driver: aggregate alias that runs all test-driver-* targets. +# - test-driver-cc: narrow CLI behavior checks that do not belong to a specific # frontend/linker corpus. Depends on the cfree driver binary. -# - test-pp: C preprocessor runner; depends on the cfree driver binary. +# - test-pp: aggregate alias that runs test-pp-ok and test-pp-err. +# - test-pp-ok: C preprocessor success cases; depends on the cfree driver binary. # - test-elf: ELF roundtrip harness in test/elf/; depends only on # libcfree.a and compiles its own test binaries against it. Skipped # layers are reported (set CFREE_TEST_ALLOW_SKIP=1 to allow skips). @@ -10,16 +12,18 @@ # libcfree.a. Set CFREE_AR_TEST_HOST=1 to also dump produced bytes # to /tmp and run the host's `ar t` / `nm --print-armap` as a # cross-check. -# - test-ar-driver: scenario-driven CLI harness for `cfree ar`. Each +# - test-driver-ar: scenario-driven CLI harness for `cfree ar`. Each # case under test/ar/cases/ runs a small script and diffs stdout. # Depends on the cfree driver binary. # - test-link: linker + JIT behavioral harness in test/link/; three paths # per case (roundtrip R, ELF exec E, JIT J). Depends only on libcfree.a. # Set CFREE_TEST_ALLOW_SKIP=1 to allow skipped layers. -# - test-parse / test-parse-err: file-driven C parser harness in -# test/parse/; each case is a .c source file. Built against the public -# cfree.h surface; reuses cfree-roundtrip, -# link-exe-runner, and jit-runner. +# - test-macho: Mach-O variant of test-link; defaults to roundtrip+JIT +# paths because hosted Mach-O executable execution is target/SDK-specific. +# - test-parse: aggregate alias that runs test-parse-ok and test-parse-err. +# - test-parse-ok: file-driven C parser success harness in test/parse/; each +# case is a .c source file. Built against the public cfree.h surface; +# reuses cfree-roundtrip, link-exe-runner, and jit-runner. # - test-asm: file-driven assembler/disassembler harness in test/asm/. # Three sub-corpora (encode/, decode/, listing/), one mode per # sub-dir. Phase 1: every smoke case carries a .skip sidecar because @@ -30,7 +34,6 @@ TEST_TARGETS = \ test-aa64-inline \ test-abi-classify \ test-ar \ - test-ar-driver \ test-asm \ test-bounce \ test-cbackend \ @@ -40,54 +43,55 @@ TEST_TARGETS = \ test-coff-windows-ucrt \ test-debug \ test-driver \ + test-driver-ar \ + test-driver-cc \ + test-driver-objcopy \ + test-driver-objdump \ + test-driver-strip \ test-dwarf \ test-elf \ test-emu \ - test-glibc \ - test-glibc-rv64 \ test-isa \ test-lib-deps \ + test-libc \ + test-libc-glibc \ + test-libc-glibc-rv64 \ + test-libc-musl \ + test-libc-musl-rv64 \ test-link \ - test-musl \ - test-musl-rv64 \ - test-objcopy-driver \ - test-objdump-driver \ + test-macho \ test-opt \ test-parse \ test-parse-err \ + test-parse-ok \ test-pp \ test-pp-err \ + test-pp-ok \ test-rt-headers \ test-rt-runtime \ test-rv64-inline \ test-rv64-jit \ test-smoke-rv64 \ test-smoke-x64 \ - test-strip-driver \ test-toy \ - test-toy-wasm \ + test-wasm \ test-wasm-front \ test-wasm-target \ + test-wasm-toy \ test-x64-dbg \ test-x64-inline DEFAULT_TEST_TARGETS = \ test-driver \ test-pp \ - test-pp-err \ test-elf \ test-coff \ test-ar \ - test-ar-driver \ - test-strip-driver \ - test-objcopy-driver \ - test-objdump-driver \ test-link \ test-toy \ test-dwarf \ test-debug \ test-parse \ - test-parse-err \ test-asm \ test-isa \ test-aa64-inline \ @@ -103,7 +107,9 @@ DEFAULT_TEST_TARGETS = \ test: $(DEFAULT_TEST_TARGETS) -test-driver: bin +test-driver: test-driver-cc test-driver-ar test-driver-strip test-driver-objcopy test-driver-objdump + +test-driver-cc: bin @CFREE=$(abspath $(BIN)) sh test/driver/run.sh # test-cbackend: --emit=c C-source backend, driven through three @@ -117,16 +123,18 @@ test-cbackend: bin @CFREE_TEST_PATHS=C CFREE=$(abspath $(BIN)) sh test/toy/run.sh @CFREE_TEST_PATHS=C CFREE=$(abspath $(BIN)) bash test/wasm/run.sh -# test-toy-wasm: opt-in Toy -> Wasm -> JIT roundtrip. Runs the toy corpus +# test-wasm-toy: opt-in Toy -> Wasm -> JIT roundtrip. Runs the toy corpus # under the W path (compile -target wasm32-none, then `cfree run` the .wasm, # which routes back through the lang/wasm frontend to native CG). Most cases # will fail or skip today; the target exists so progress on the Wasm CGTarget # is visible without putting noise into the default `test` summary. # Drop `CFREE_TEST_ALLOW_SKIP=1` once the corpus is mostly green. -test-toy-wasm: bin +test-wasm-toy: bin @CFREE_TEST_PATHS=W CFREE_TEST_ALLOW_SKIP=1 CFREE=$(abspath $(BIN)) sh test/toy/run.sh -test-pp: bin +test-pp: test-pp-ok test-pp-err + +test-pp-ok: bin @CFREE=$(abspath $(BIN)) test/pp/run.sh test-pp-err: bin @@ -149,16 +157,16 @@ $(AR_TEST_BIN): test/ar_test.c $(LIB_AR) @mkdir -p $(dir $@) $(CC) $(TEST_HOST_CFLAGS) test/ar_test.c $(LIB_AR) -o $@ -test-ar-driver: bin +test-driver-ar: bin @CFREE=$(abspath $(BIN)) test/ar/run.sh -test-strip-driver: bin +test-driver-strip: bin @CFREE=$(abspath $(BIN)) test/strip/run.sh -test-objcopy-driver: bin +test-driver-objcopy: bin @CFREE=$(abspath $(BIN)) test/objcopy/run.sh -test-objdump-driver: bin +test-driver-objdump: bin @CFREE=$(abspath $(BIN)) sh test/objdump/run.sh # DWARF consumer unit test: builds a hand-crafted DWARF-bearing ELF in @@ -440,6 +448,13 @@ test-coff-windows-ucrt: bin rt-aarch64-windows test-link: lib $(ROUNDTRIP_BIN) $(ROUNDTRIP_BIN_MACHO) $(LINK_EXE_RUNNER) $(JIT_RUNNER) bash test/link/run.sh +test-macho: lib $(ROUNDTRIP_BIN_MACHO) $(LINK_EXE_RUNNER) $(JIT_RUNNER) + CFREE_TEST_OBJ=macho \ + CFREE_TEST_ARCH=$${CFREE_TEST_ARCH:-aa64} \ + CFREE_TEST_PATHS=$${CFREE_TEST_PATHS:-RJ} \ + CFREE_TEST_ALLOW_SKIP=$${CFREE_TEST_ALLOW_SKIP:-1} \ + bash test/link/run.sh + OPT_TEST_BIN = build/test/opt_test test-opt: bin $(OPT_TEST_BIN) @@ -450,7 +465,9 @@ $(OPT_TEST_BIN): test/opt/opt_test.c $(LIB_OBJS) @mkdir -p $(dir $@) $(CC) $(TEST_HOST_CFLAGS) -Isrc test/opt/opt_test.c $(LIB_OBJS) -o $@ -test-parse: lib rt $(PARSE_RUNNER) $(ROUNDTRIP_BIN) $(LINK_EXE_RUNNER) $(JIT_RUNNER) +test-parse: test-parse-ok test-parse-err + +test-parse-ok: lib rt $(PARSE_RUNNER) $(ROUNDTRIP_BIN) $(LINK_EXE_RUNNER) $(JIT_RUNNER) bash test/parse/run.sh test-parse-err: lib $(PARSE_RUNNER) @@ -461,6 +478,8 @@ test-parse-err: lib $(PARSE_RUNNER) test-asm: lib $(ASM_RUNNER) $(LINK_EXE_RUNNER) $(JIT_RUNNER) bash test/asm/run.sh +test-wasm: test-wasm-front test-wasm-target test-wasm-toy + test-wasm-front: bin $(WASM_TOOL) $(LINK_EXE_RUNNER) $(JIT_RUNNER) bash test/wasm/run.sh @@ -505,18 +524,19 @@ test-smoke-rv64: test-bounce: $(BIN) bash test/bounce/bounce.sh -# test-musl / test-glibc: end-to-end static + dynamic libc link/run on -# aarch64. Each variant pulls its own pinned sysroot (podman, ~30s on +# test-libc: aggregate alias that runs test-libc-musl and test-libc-glibc. +# test-libc-musl / test-libc-glibc: end-to-end static + dynamic libc link/run +# on aarch64. Each variant pulls its own pinned sysroot (podman, ~30s on # first run) and shares the same case files under test/libc/cases/: # -# test-musl — Alpine 3.20 + musl 1.2.5 (test/libc/musl/) -# test-glibc — Debian bookworm + glibc 2.36 (test/libc/glibc/) +# test-libc-musl — Alpine 3.20 + musl 1.2.5 (test/libc/musl/) +# test-libc-glibc — Debian bookworm + glibc 2.36 (test/libc/glibc/) # # Both build build/rt/aarch64-linux/libcfree_rt.a for soft-float / TF # builtins, and run `cfree ld` against the real libc.a (static) and # libc.so / libc.so.6 (dynamic). Excluded from the # default `test` target because they need podman; opt-in via -# `make test-musl` / `make test-glibc`. +# `make test-libc-musl` / `make test-libc-glibc` (or `make test-libc` for both). # # Each sysroot is treated as a real prerequisite via its PROVENANCE # marker so subsequent runs skip extraction and re-extract only when @@ -546,13 +566,11 @@ $(GLIBC_SYSROOT_X64_MARKER): test/libc/glibc/extract.sh test/libc/glibc/Containe $(GLIBC_SYSROOT_RV64_MARKER): test/libc/glibc/extract.sh test/libc/glibc/Containerfile.rv64 @bash test/libc/glibc/extract.sh -a rv64 -# test-musl / test-glibc honor CFREE_LIBC_ARCHES (default "aa64"; +# test-libc-musl / test-libc-glibc honor CFREE_LIBC_ARCHES (default "aa64"; # values: aa64, x64, rv64). Each enabled arch contributes its sysroot # PROVENANCE marker and its rt archive to the prerequisite list, so -# `CFREE_LIBC_ARCHES="aa64 x64" make test-musl` builds both sysroots -# + both rt archives before the runner script picks them up. The -# default keeps the aa64-only prerequisite set so `make test-musl` is -# backwards-compatible. +# `CFREE_LIBC_ARCHES="aa64 x64" make test-libc-musl` builds both sysroots +# + both rt archives before the runner script picks them up. CFREE_LIBC_ARCHES ?= aa64 # Map an arch token to its musl/glibc sysroot marker and rt target. @@ -569,17 +587,19 @@ _LIBC_RT_rv64 = rt-riscv64-linux LIBC_MUSL_DEPS = $(foreach a,$(CFREE_LIBC_ARCHES),$(_LIBC_MUSL_SYSROOT_$(a)) $(_LIBC_RT_$(a))) LIBC_GLIBC_DEPS = $(foreach a,$(CFREE_LIBC_ARCHES),$(_LIBC_GLIBC_SYSROOT_$(a)) $(_LIBC_RT_$(a))) -test-musl: bin $(LIBC_MUSL_DEPS) +test-libc: test-libc-musl test-libc-glibc + +test-libc-musl: bin $(LIBC_MUSL_DEPS) @CFREE_LIBC_ARCHES="$(CFREE_LIBC_ARCHES)" bash test/libc/musl/run.sh -test-glibc: bin $(LIBC_GLIBC_DEPS) +test-libc-glibc: bin $(LIBC_GLIBC_DEPS) @CFREE_LIBC_ARCHES="$(CFREE_LIBC_ARCHES)" bash test/libc/glibc/run.sh -test-musl-rv64: - @$(MAKE) test-musl CFREE_LIBC_ARCHES=rv64 +test-libc-musl-rv64: + @$(MAKE) test-libc-musl CFREE_LIBC_ARCHES=rv64 -test-glibc-rv64: - @$(MAKE) test-glibc CFREE_LIBC_ARCHES=rv64 +test-libc-glibc-rv64: + @$(MAKE) test-libc-glibc CFREE_LIBC_ARCHES=rv64 # Fail if libcfree.a depends on any external symbol not in the allowlist, or # if a relocatable link exposes non-public global definitions.