kit

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

commit 4027978edd85e8d34153b0677c111c7cb0b89781
parent f44d51f84ea58f6dfeb441dc11b94927f80ea128
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu, 11 Jun 2026 22:12:51 -0700

refactor(api): promote driver build/target/object orchestration into libkit

Lift the driver's pure-public-API orchestration into the public libkit
surface so the toolchain core matches the altitude the cas/package
subsystems already set. The driver stays the first (and only) consumer of
the public API; nothing reaches into src/.

New / extended public API:
- kit/build.h (new): kit_build_compile[_one] / kit_build_link[_with_lto] +
  LTO batch. Was driver/lib/{compile,link}_engine.c; cc and build cut over,
  the driver engines are deleted.
- kit/target.h (new): kit_target_from_triple/to_triple, kit_arch_from_name,
  and the target-default helpers. driver/lib/target.c keeps the -march/-mattr
  CLI parsing and is otherwise reduced to thin wrappers.
- kit/archive.h: kit_ar_write_objs, kit_ar_reindex, kit_obj_global_syms.
  ranlib is now a single kit_ar_reindex call; ar/strip/archive_engine drop
  their duplicated symbol-index collection.
- kit/object.h: kit_obj_builder_strip (+ KitObjStripLevel),
  kit_obj_reloc_live_symbols, kit_obj_section_size_class, kit_obj_size_totals.
- kit/dwarf.h: kit_dwarf_resolve (+ KitDwarfResolve), kit_dwarf_{tag,attr,
  form}_name. objdump's ~170-line local DWARF name tables are removed.
- kit/compile.h: kit_input_kind_for_path (source/object/archive/dso).
- kit/link.h: kit_lib_resolve - owns the GNU-ld -l suffix-search order only;
  the host injects the filesystem probe, libkit does no I/O and no allocation.

Driver side: cc/build consume kit_build_*; ar/ranlib/strip/objcopy/size/
objdump/addr2line/symbolize and the shared inputs/lib_resolve/dwarfsym helpers
route through the new APIs. Behavior is preserved (archive bytes, strip
output, size numbers, DWARF rendering, -l ordering, search dirs).

test-driver, test-{link,cg-api,ar,dwarf,debug}, and the x64 smoke suite pass.

Diffstat:
Mdriver/cmd/addr2line.c | 4++--
Mdriver/cmd/ar.c | 34++++++++++------------------------
Mdriver/cmd/build.c | 59+++++++++++++++++++++++++++++------------------------------
Mdriver/cmd/cc.c | 33+++++++++++++++++----------------
Mdriver/cmd/objcopy.c | 48+++++++++---------------------------------------
Mdriver/cmd/objdump.c | 186++++---------------------------------------------------------------------------
Mdriver/cmd/ranlib.c | 144++++++-------------------------------------------------------------------------
Mdriver/cmd/size.c | 84+++++++++++++------------------------------------------------------------------
Mdriver/cmd/strip.c | 116+++++++++++++++++++++++++++----------------------------------------------------
Mdriver/cmd/symbolize.c | 7++++---
Mdriver/lib/archive_engine.c | 94++++++-------------------------------------------------------------------------
Ddriver/lib/compile_engine.c | 242-------------------------------------------------------------------------------
Ddriver/lib/compile_engine.h | 92-------------------------------------------------------------------------------
Mdriver/lib/dwarfsym.c | 25-------------------------
Mdriver/lib/dwarfsym.h | 29+++++------------------------
Mdriver/lib/inputs.c | 131+++++++++++++++++++++----------------------------------------------------------
Mdriver/lib/lib_resolve.c | 148++++++++++++++++++++++---------------------------------------------------------
Ddriver/lib/link_engine.c | 103-------------------------------------------------------------------------------
Ddriver/lib/link_engine.h | 53-----------------------------------------------------
Mdriver/lib/link_inputs.c | 4++--
Mdriver/lib/objedit.c | 52+++++++---------------------------------------------
Mdriver/lib/objedit.h | 9+++++----
Mdriver/lib/target.c | 279+++++++------------------------------------------------------------------------
Minclude/kit/archive.h | 39+++++++++++++++++++++++++++++++++++++++
Ainclude/kit/build.h | 150+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minclude/kit/compile.h | 18++++++++++++++++++
Minclude/kit/dwarf.h | 32++++++++++++++++++++++++++++++++
Minclude/kit/link.h | 42++++++++++++++++++++++++++++++++++++++++++
Minclude/kit/object.h | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ainclude/kit/target.h | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmk/driver_srcs.mk | 8+++-----
Mmk/lib_srcs.mk | 10+++++++---
Msrc/api/archive.c | 306+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/api/build.c | 347+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/api/compile.c | 24++++++++++++++++++++++++
Msrc/api/link.c | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/api/object_builder.c | 119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/api/object_file.c | 138+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/api/target.c | 297+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/debug/dwarf_dump.c | 177+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/debug/dwarf_query.c | 25+++++++++++++++++++++++++
Mtest/driver/run.sh | 9+++++----
42 files changed, 2216 insertions(+), 1718 deletions(-)

diff --git a/driver/cmd/addr2line.c b/driver/cmd/addr2line.c @@ -30,11 +30,11 @@ static void a2l_strip_basename(const char** path_ptr) { static void a2l_translate(DriverDwarfSym* sym, uint64_t addr, const A2lOpts* opts) { - DriverSymLoc loc; + KitDwarfResolve loc; /* Share the DWARF open + func/line queries with `kit symbolize`; the two * tools differ only in how they format the result. */ - driver_dwarfsym_lookup(sym, addr, opts->functions, &loc); + kit_dwarf_resolve(sym->dwarf, addr, opts->functions, &loc); if (opts->show_addr) driver_printf("0x%llx", (unsigned long long)addr); diff --git a/driver/cmd/ar.c b/driver/cmd/ar.c @@ -3,7 +3,6 @@ #include <kit/object.h> #include "driver.h" -#include "inputs.h" /* `kit ar` — POSIX ar archive front-end. * @@ -323,12 +322,11 @@ static int ar_do_write(DriverEnv* env, const char* archive_path, int nmembers, char* old_name_storage = NULL; size_t old_name_bytes = 0; /* Per-member symbol storage (only used when has_s). msyms is the array - * passed to kit_ar_write; sym_allocs holds the single per-member - * blob backing each msyms[i].names + name bytes (one allocation per - * member, sized in sym_alloc_szs for the matching driver_free). */ + * passed to kit_ar_write; sym_allocs holds the single per-member blob + * (from kit_obj_global_syms) backing each msyms[i].names + name bytes, freed + * via kit_obj_global_syms_free which recovers the size from the block. */ KitArMemberSymbols* msyms = NULL; void** sym_allocs = NULL; - size_t* sym_alloc_szs = NULL; opts.epoch = driver_epoch_from_env(); opts.long_names = 1; @@ -475,31 +473,20 @@ static int ar_do_write(DriverEnv* env, const char* archive_path, int nmembers, env, (size_t)nm * sizeof(*msyms)); sym_allocs = (void**)driver_alloc_zeroed(env, (size_t)nm * sizeof(*sym_allocs)); - sym_alloc_szs = - (size_t*)driver_alloc_zeroed(env, (size_t)nm * sizeof(*sym_alloc_szs)); - if (!msyms || !sym_allocs || !sym_alloc_szs) { + if (!msyms || !sym_allocs) { driver_errf(AR_TOOL, "out of memory"); rc = 1; goto done; } for (i = 0; i < nm; ++i) { - KitSlice in = members[i].bytes; - void* blob = NULL; - size_t blob_size = 0; - const KitSlice* names = NULL; - uint32_t count = 0; - - if (driver_collect_obj_global_syms(env, &ctx, AR_TOOL, &in, &blob, - &blob_size, &names, &count) != 0) { + /* libkit owns the "which symbols a linker indexes" policy + packing; the + * returned block carries its own free size (kit_obj_global_syms_free). */ + if (kit_obj_global_syms(&ctx, &members[i].bytes, &msyms[i], + &sym_allocs[i]) != KIT_OK) { + driver_errf(AR_TOOL, "out of memory"); rc = 1; goto done; } - if (count == 0) continue; - - sym_allocs[i] = blob; - sym_alloc_szs[i] = blob_size; - msyms[i].names = names; - msyms[i].count = count; } opts.symbol_index = 1; opts.member_symbols = msyms; @@ -519,10 +506,9 @@ static int ar_do_write(DriverEnv* env, const char* archive_path, int nmembers, done: if (sym_allocs) { for (i = 0; i < nm; ++i) { - if (sym_allocs[i]) driver_free(env, sym_allocs[i], sym_alloc_szs[i]); + if (sym_allocs[i]) kit_obj_global_syms_free(&ctx, sym_allocs[i]); } driver_free(env, sym_allocs, (size_t)nm * sizeof(*sym_allocs)); - driver_free(env, sym_alloc_szs, (size_t)nm * sizeof(*sym_alloc_szs)); } if (msyms) driver_free(env, msyms, (size_t)nm * sizeof(*msyms)); if (new_fds) { diff --git a/driver/cmd/build.c b/driver/cmd/build.c @@ -1,3 +1,4 @@ +#include <kit/build.h> #include <kit/compile.h> #include <kit/core.h> #include <kit/link.h> @@ -7,11 +8,9 @@ #include "archive_engine.h" #include "cflags.h" -#include "compile_engine.h" #include "driver.h" #include "hosted.h" #include "lib_resolve.h" -#include "link_engine.h" #include "link_flags.h" #include "link_inputs.h" #include "runtime.h" @@ -963,9 +962,9 @@ static int build_compile_source(BuildOptions* o, KitCompiler* compiler, } } - st = driver_compile_run(compiler, lang, code, diag, &o->groups[gi].pp, - lang_extra, kit_slice_cstr(path), &bytes, emit_out, - obj_out); + st = kit_build_compile_one(compiler, lang, code, diag, &o->groups[gi].pp, + lang_extra, kit_slice_cstr(path), &bytes, emit_out, + obj_out); rc = (st == KIT_OK) ? 0 : 1; out: @@ -1066,13 +1065,13 @@ static int build_compile_all(BuildOptions* o, KitCompiler* compiler, const KitDiagnosticOptions* diag, KitObjBuilder** objs, uint32_t* source_obj_index, uint8_t* source_order_keep, - const DriverCompileBatchOptions* batch, - DriverCompilePendingLto* pending_lto, + const KitBuildBatchOptions* batch, + KitBuildPendingLto* pending_lto, uint32_t* nobjs_out) { DriverLoad* loads = NULL; - DriverCompileSource* sources = NULL; + KitBuildSource* sources = NULL; void** lang_extras = NULL; - DriverCompileObjects out; + KitBuildObjects out; uint32_t i; KitStatus st; int rc = 1; @@ -1131,8 +1130,8 @@ static int build_compile_all(BuildOptions* o, KitCompiler* compiler, out.source_obj_index = source_obj_index; out.source_order_keep = source_order_keep; out.pending_lto = pending_lto; - st = driver_compile_sources_run(compiler, code, diag, sources, o->nsources, - batch, &out); + st = kit_build_compile(compiler, code, diag, sources, o->nsources, batch, + &out); if (nobjs_out) *nobjs_out = out.nobjs; if (st != KIT_OK) goto out; rc = 0; @@ -1172,12 +1171,12 @@ static int build_run_link(BuildOptions* o, KitCompiler* compiler, KitSlice* dso_names = NULL; KitLinkInputOrder* order = NULL; KitObjBuilder** objs = NULL; - DriverCompilePendingLto pending_lto = {0}; + KitBuildPendingLto pending_lto = {0}; uint32_t* source_obj_index = NULL; uint8_t* source_order_keep = NULL; KitLinkScript* script = NULL; KitSlice* rpath_slices = NULL; - DriverCompileBatchOptions lto_batch; + KitBuildBatchOptions lto_batch; uint32_t nobjs = 0; uint32_t i; uint32_t norder = 0; @@ -1277,7 +1276,7 @@ static int build_run_link(BuildOptions* o, KitCompiler* compiler, { KitLinkSessionOptions lopts; - DriverLinkInputs li; + KitLinkInputs li; KitStatus st; if (driver_link_flags_fill_options( &o->link, o->target, o->pie, o->shared, @@ -1297,8 +1296,8 @@ static int build_run_link(BuildOptions* o, KitCompiler* compiler, li.ndsos = o->inputs.ndsos; li.order = order; li.norder = norder; - st = driver_link_engine_emit_with_lto(compiler, &lopts, &li, &pending_lto, - &lto_batch, out_w); + st = kit_build_link_with_lto(compiler, &lopts, &li, &pending_lto, + &lto_batch, out_w); rc = (st == KIT_OK) ? 0 : 1; } @@ -1313,7 +1312,7 @@ out: } } if (script) kit_link_script_free(ctx, script); - driver_compile_pending_lto_abort(&pending_lto); + kit_build_lto_abort(&pending_lto); driver_link_flags_free_rpath_slices(&o->link, rpath_slices); driver_release_bytes(io, &script_lf); if (arch_lf) @@ -1367,9 +1366,9 @@ static int build_run_relocatable(BuildOptions* o, KitCompiler* compiler, uint32_t* source_obj_index = NULL; uint8_t* source_order_keep = NULL; KitLinkInputOrder* order = NULL; - DriverCompilePendingLto pending_lto = {0}; + KitBuildPendingLto pending_lto = {0}; KitWriter* out_w = NULL; - DriverCompileBatchOptions lto_batch; + KitBuildBatchOptions lto_batch; uint32_t nobjs = 0; uint32_t norder = 0; uint32_t i; @@ -1405,7 +1404,7 @@ static int build_run_relocatable(BuildOptions* o, KitCompiler* compiler, goto out; { KitLinkSessionOptions lopts; - DriverLinkInputs li; + KitLinkInputs li; KitStatus st; memset(&lopts, 0, sizeof(lopts)); lopts.output_kind = KIT_LINK_OUTPUT_RELOCATABLE; @@ -1416,14 +1415,14 @@ static int build_run_relocatable(BuildOptions* o, KitCompiler* compiler, li.nobjs = nobjs; li.order = order; li.norder = norder; - st = driver_link_engine_emit_with_lto(compiler, &lopts, &li, &pending_lto, - &lto_batch, out_w); + st = kit_build_link_with_lto(compiler, &lopts, &li, &pending_lto, + &lto_batch, out_w); rc = (st == KIT_OK) ? 0 : 1; } out: if (out_w) kit_writer_close(out_w); - driver_compile_pending_lto_abort(&pending_lto); + kit_build_lto_abort(&pending_lto); if (order) driver_free(env, order, o->nsources * sizeof(*order)); /* The link session borrows the builders; release them here (see * build_run_link). */ @@ -1466,7 +1465,7 @@ static int build_run_wasm_module(BuildOptions* o, KitCompiler* compiler, goto out; } { - DriverCompileBatchOptions batch; + KitBuildBatchOptions batch; memset(&batch, 0, sizeof batch); batch.output_kind = KIT_CG_OUTPUT_RELOCATABLE; batch.interposition_policy = KIT_CG_INTERPOSITION_DEFAULT; @@ -1506,7 +1505,7 @@ static int build_run_wasm_exe(BuildOptions* o, KitCompiler* compiler, KitObjBuilder** objs = NULL; uint32_t* source_obj_index = NULL; uint8_t* source_order_keep = NULL; - DriverCompilePendingLto pending_lto = {0}; + KitBuildPendingLto pending_lto = {0}; KitWriter* out_w = NULL; KitCodeOptions code2 = *code; uint32_t nobjs = 0; @@ -1525,7 +1524,7 @@ static int build_run_wasm_exe(BuildOptions* o, KitCompiler* compiler, goto out; } { - DriverCompileBatchOptions batch; + KitBuildBatchOptions batch; memset(&batch, 0, sizeof batch); batch.output_kind = KIT_CG_OUTPUT_EXECUTABLE; batch.interposition_policy = KIT_CG_INTERPOSITION_NONE; @@ -1544,7 +1543,7 @@ static int build_run_wasm_exe(BuildOptions* o, KitCompiler* compiler, KitSym entry_interned = kit_sym_intern(compiler, kit_slice_cstr(entry_name)); KitObjSymbol entry_sym = KIT_OBJ_SYMBOL_NONE; - DriverCompileBatchOptions batch2; + KitBuildBatchOptions batch2; KitCgSym csym; if (!entry_interned || @@ -1558,7 +1557,7 @@ static int build_run_wasm_exe(BuildOptions* o, KitCompiler* compiler, memset(&batch2, 0, sizeof batch2); batch2.output_kind = KIT_CG_OUTPUT_EXECUTABLE; batch2.interposition_policy = KIT_CG_INTERPOSITION_NONE; - if (driver_compile_pending_lto_finish(&pending_lto, &batch2, &csym, 1) != + if (kit_build_lto_finish(&pending_lto, &batch2, &csym, 1) != KIT_OK) { driver_errf(o->tool, "build-exe: LTO finish failed"); goto out; @@ -1572,7 +1571,7 @@ static int build_run_wasm_exe(BuildOptions* o, KitCompiler* compiler, out: if (out_w) kit_writer_close(out_w); - if (pending_lto.active) driver_compile_pending_lto_abort(&pending_lto); + if (pending_lto.active) kit_build_lto_abort(&pending_lto); if (objs) { for (i = 0; i < nobjs; ++i) kit_obj_builder_free(objs[i]); driver_free(env, objs, o->nsources * sizeof(*objs)); @@ -1616,7 +1615,7 @@ static int build_run_archive(BuildOptions* o, KitCompiler* compiler, goto out; } { - DriverCompileBatchOptions batch; + KitBuildBatchOptions batch; memset(&batch, 0, sizeof batch); batch.output_kind = KIT_CG_OUTPUT_ARCHIVE_MEMBER; batch.interposition_policy = KIT_CG_INTERPOSITION_DEFAULT; diff --git a/driver/cmd/cc.c b/driver/cmd/cc.c @@ -1,6 +1,7 @@ #include "c/c.h" #include <kit/asm_emit.h> +#include <kit/build.h> #include <kit/compile.h> #include <kit/core.h> #include <kit/link.h> @@ -9,11 +10,9 @@ #include <string.h> #include "cflags.h" -#include "compile_engine.h" #include "driver.h" #include "hosted.h" #include "lib_resolve.h" -#include "link_engine.h" #include "link_flags.h" #include "link_inputs.h" #include "runtime.h" @@ -1872,8 +1871,9 @@ static KitStatus cc_compile_source_obj(KitCompiler* compiler, KitLanguage lang, const KitPreprocessOptions* pp, KitSlice name, const KitSlice* input, KitObjBuilder** out) { - return driver_compile_run(compiler, lang, &copts->code, &copts->diagnostics, - pp, NULL, name, input, NULL, out); + return kit_build_compile_one(compiler, lang, &copts->code, + &copts->diagnostics, pp, NULL, name, input, NULL, + out); } static KitStatus cc_compile_source_emit(KitCompiler* compiler, KitLanguage lang, @@ -1881,8 +1881,9 @@ static KitStatus cc_compile_source_emit(KitCompiler* compiler, KitLanguage lang, const KitPreprocessOptions* pp, KitSlice name, const KitSlice* input, KitWriter* out) { - return driver_compile_run(compiler, lang, &copts->code, &copts->diagnostics, - pp, NULL, name, input, out, NULL); + return kit_build_compile_one(compiler, lang, &copts->code, + &copts->diagnostics, pp, NULL, name, input, out, + NULL); } static int cc_run_compile_one(DriverEnv* env, const CcOptions* o, @@ -2088,14 +2089,14 @@ static int cc_run_link_exe(DriverEnv* env, const CcOptions* o, KitSlice* dso_names = NULL; KitLinkInputOrder* order = NULL; KitObjBuilder** objs = NULL; - DriverCompileSource* sources = NULL; - DriverCompilePendingLto pending_lto = {0}; + KitBuildSource* sources = NULL; + KitBuildPendingLto pending_lto = {0}; uint32_t* source_obj_index = NULL; uint8_t* source_order_keep = NULL; KitLinkScript* script = NULL; KitSlice* rpath_slices = NULL; KitCCompileOptions copts; - DriverCompileBatchOptions lto_batch; + KitBuildBatchOptions lto_batch; uint32_t nsrc = o->nsource_files + o->nsource_memory; uint32_t i; uint32_t nobjs = 0; @@ -2245,15 +2246,15 @@ static int cc_run_link_exe(DriverEnv* env, const CcOptions* o, sources[si].pp = pp; } if (nsrc) { - DriverCompileObjects cout; + KitBuildObjects cout; KitStatus st; memset(&cout, 0, sizeof cout); cout.objs = objs; cout.source_obj_index = source_obj_index; cout.source_order_keep = source_order_keep; cout.pending_lto = &pending_lto; - st = driver_compile_sources_run(compiler, &copts.code, &copts.diagnostics, - sources, nsrc, &lto_batch, &cout); + st = kit_build_compile(compiler, &copts.code, &copts.diagnostics, sources, + nsrc, &lto_batch, &cout); nobjs = cout.nobjs; if (st != KIT_OK) goto out; } @@ -2278,7 +2279,7 @@ static int cc_run_link_exe(DriverEnv* env, const CcOptions* o, * o->inputs.nlink_items == 0 never fires (a link action always has at least * one input), so every add flows through the ordered path. */ { - DriverLinkInputs li; + KitLinkInputs li; norder = driver_link_inputs_build_order(&o->inputs, source_obj_index, source_order_keep, o->nsource_files, order); @@ -2295,8 +2296,8 @@ static int cc_run_link_exe(DriverEnv* env, const CcOptions* o, li.ndsos = o->inputs.ndsos; li.order = order; li.norder = norder; - st = driver_link_engine_emit_with_lto(compiler, &lopts, &li, &pending_lto, - &lto_batch, out_w); + st = kit_build_link_with_lto(compiler, &lopts, &li, &pending_lto, + &lto_batch, out_w); } rc = st == KIT_OK ? 0 : 1; } @@ -2311,7 +2312,7 @@ out: } } if (script) kit_link_script_free(&ctx, script); - driver_compile_pending_lto_abort(&pending_lto); + kit_build_lto_abort(&pending_lto); driver_link_flags_free_rpath_slices(&o->link, rpath_slices); if (compiler) driver_compiler_free(compiler); kit_target_free(target); diff --git a/driver/cmd/objcopy.c b/driver/cmd/objcopy.c @@ -5,7 +5,6 @@ #include <string.h> #include "driver.h" -#include "inputs.h" #include "objedit.h" /* `kit objcopy` — copy + transform an object file. v1 scope is the @@ -242,49 +241,20 @@ static KitObjSection find_sec_id(KitObjFile* of, const char* name) { static int apply_strip_pass(DriverEnv* env, KitObjFile* of, KitObjBuilder* b, const CopyOpts* opts) { - uint32_t i, nsec; - KitObjSymbol* needed = NULL; - uint32_t nneeded = 0, cap_needed = 0; - KitObjSymIter* sit = NULL; - int filter_syms = - (opts->op == COPY_OP_STRIP_UNNEEDED || opts->op == COPY_OP_STRIP_ALL); - int rc = 1; + int level; + (void)env; + (void)of; if (opts->op == COPY_OP_NONE) return 0; - /* Always drop debug sections for any strip op. */ - nsec = kit_obj_nsections(of); - for (i = 0; i < nsec; ++i) { - KitObjSecInfo si; - if (kit_obj_section(of, i, &si) != KIT_OK) continue; - if (si.kind == KIT_SEC_DEBUG) kit_obj_builder_remove_section(b, i); - } - if (!filter_syms) return 0; - - /* Collect reloc-targeted sym ids, skipping relocs in debug sections. */ - if (driver_obj_collect_reloc_target_syms(env, OBJCOPY_TOOL, of, &needed, - &nneeded, &cap_needed) != 0) { + level = opts->op == COPY_OP_STRIP_DEBUG ? KIT_STRIP_DEBUG + : opts->op == COPY_OP_STRIP_UNNEEDED ? KIT_STRIP_UNNEEDED + : KIT_STRIP_ALL; + if (kit_obj_builder_strip(b, level, NULL, 0) != KIT_OK) { + driver_errf(OBJCOPY_TOOL, "strip failed"); return 1; } - - /* Walk syms and drop unneeded ones. */ - if (kit_obj_symiter_new(of, &sit) != KIT_OK) { - driver_errf(OBJCOPY_TOOL, "out of memory"); - goto done; - } - for (;;) { - KitObjSymInfo si; - KitIterResult ir = kit_obj_symiter_next(sit, &si); - if (ir != KIT_ITER_ITEM) break; - if (si.kind == KIT_SK_UNDEF) continue; - if (!driver_obj_id_in_set(si.id, needed, nneeded)) - kit_obj_builder_remove_symbol(b, si.id); - } - kit_obj_symiter_free(sit); - rc = 0; -done: - if (needed) driver_free(env, needed, (size_t)cap_needed * sizeof(*needed)); - return rc; + return 0; } /* Apply --only-section: every section whose name isn't on the list is diff --git a/driver/cmd/objdump.c b/driver/cmd/objdump.c @@ -849,177 +849,9 @@ static void dump_file_header(KitObjFile* f, const char* label) { * * Pulls the raw .debug_info / .debug_abbrev / .debug_line / .debug_str * structure out via the kit_dwarf_*_iter API and formats it. The library - * hands back numeric DWARF codes; the symbolic-name tables below live here - * (display logic stays in the driver). Unknown codes fall back to hex. */ - -static const char* dw_tag_name(uint32_t tag) { - switch (tag) { - case 0x01: - return "DW_TAG_array_type"; - case 0x04: - return "DW_TAG_enumeration_type"; - case 0x05: - return "DW_TAG_formal_parameter"; - case 0x0b: - return "DW_TAG_lexical_block"; - case 0x0d: - return "DW_TAG_member"; - case 0x0f: - return "DW_TAG_pointer_type"; - case 0x11: - return "DW_TAG_compile_unit"; - case 0x13: - return "DW_TAG_structure_type"; - case 0x15: - return "DW_TAG_subroutine_type"; - case 0x16: - return "DW_TAG_typedef"; - case 0x17: - return "DW_TAG_union_type"; - case 0x18: - return "DW_TAG_unspecified_parameters"; - case 0x1d: - return "DW_TAG_inlined_subroutine"; - case 0x21: - return "DW_TAG_subrange_type"; - case 0x24: - return "DW_TAG_base_type"; - case 0x26: - return "DW_TAG_const_type"; - case 0x28: - return "DW_TAG_enumerator"; - case 0x2e: - return "DW_TAG_subprogram"; - case 0x34: - return "DW_TAG_variable"; - case 0x35: - return "DW_TAG_volatile_type"; - case 0x37: - return "DW_TAG_restrict_type"; - case 0x3b: - return "DW_TAG_unspecified_type"; - default: - return NULL; - } -} - -static const char* dw_at_name(uint32_t at) { - switch (at) { - case 0x01: - return "DW_AT_sibling"; - case 0x02: - return "DW_AT_location"; - case 0x03: - return "DW_AT_name"; - case 0x0b: - return "DW_AT_byte_size"; - case 0x0d: - return "DW_AT_bit_size"; - case 0x10: - return "DW_AT_stmt_list"; - case 0x11: - return "DW_AT_low_pc"; - case 0x12: - return "DW_AT_high_pc"; - case 0x13: - return "DW_AT_language"; - case 0x1b: - return "DW_AT_comp_dir"; - case 0x1c: - return "DW_AT_const_value"; - case 0x25: - return "DW_AT_producer"; - case 0x27: - return "DW_AT_prototyped"; - case 0x2f: - return "DW_AT_upper_bound"; - case 0x34: - return "DW_AT_artificial"; - case 0x37: - return "DW_AT_count"; - case 0x38: - return "DW_AT_data_member_location"; - case 0x39: - return "DW_AT_decl_column"; - case 0x3a: - return "DW_AT_decl_file"; - case 0x3b: - return "DW_AT_decl_line"; - case 0x3c: - return "DW_AT_declaration"; - case 0x3e: - return "DW_AT_encoding"; - case 0x3f: - return "DW_AT_external"; - case 0x40: - return "DW_AT_frame_base"; - case 0x49: - return "DW_AT_type"; - case 0x6e: - return "DW_AT_linkage_name"; - case 0x88: - return "DW_AT_alignment"; - default: - return NULL; - } -} - -static const char* dw_form_name(uint32_t form) { - switch (form) { - case 0x01: - return "DW_FORM_addr"; - case 0x05: - return "DW_FORM_data2"; - case 0x06: - return "DW_FORM_data4"; - case 0x07: - return "DW_FORM_data8"; - case 0x08: - return "DW_FORM_string"; - case 0x09: - return "DW_FORM_block"; - case 0x0b: - return "DW_FORM_data1"; - case 0x0c: - return "DW_FORM_flag"; - case 0x0d: - return "DW_FORM_sdata"; - case 0x0e: - return "DW_FORM_strp"; - case 0x0f: - return "DW_FORM_udata"; - case 0x10: - return "DW_FORM_ref_addr"; - case 0x11: - return "DW_FORM_ref1"; - case 0x12: - return "DW_FORM_ref2"; - case 0x13: - return "DW_FORM_ref4"; - case 0x14: - return "DW_FORM_ref8"; - case 0x15: - return "DW_FORM_ref_udata"; - case 0x17: - return "DW_FORM_sec_offset"; - case 0x18: - return "DW_FORM_exprloc"; - case 0x19: - return "DW_FORM_flag_present"; - case 0x1a: - return "DW_FORM_strx"; - case 0x1b: - return "DW_FORM_addrx"; - case 0x1f: - return "DW_FORM_line_strp"; - case 0x21: - return "DW_FORM_implicit_const"; - case 0x25: - return "DW_FORM_strx1"; - default: - return NULL; - } -} + * hands back numeric DWARF codes; kit_dwarf_{tag,attr,form}_name turn them + * into symbolic spellings (the canonical table all dumpers share), and + * dw_emit_code applies objdump's hex fallback for codes those don't name. */ /* Print a symbolic DWARF code or, when unknown, its hex value. */ static void dw_emit_code(const char* name, uint32_t val) { @@ -1063,9 +895,9 @@ static void dump_dwarf_die_attrs(KitDebugInfo* dbg, uint32_t die_offset, if (kit_dwarf_attr_iter_new(dbg, die_offset, &ai) != KIT_OK) return; while (kit_dwarf_attr_iter_next(ai, &a) == KIT_ITER_ITEM) { for (k = 0; k <= depth + 1; ++k) driver_printf(" "); - dw_emit_code(dw_at_name(a.attr), a.attr); + dw_emit_code(kit_dwarf_attr_name(a.attr), a.attr); driver_printf(" ("); - dw_emit_code(dw_form_name(a.form), a.form); + dw_emit_code(kit_dwarf_form_name(a.form), a.form); driver_printf(") = "); dw_emit_attr_value(&a); driver_printf("\n"); @@ -1094,7 +926,7 @@ static void dump_dwarf_info(KitDebugInfo* dbg) { uint32_t k; for (k = 0; k <= die.depth; ++k) driver_printf(" "); driver_printf("<0x%x> ", die.offset); - dw_emit_code(dw_tag_name(die.tag), die.tag); + dw_emit_code(kit_dwarf_tag_name(die.tag), die.tag); driver_printf("\n"); dump_dwarf_die_attrs(dbg, die.offset, die.depth); } @@ -1116,7 +948,7 @@ static void dump_dwarf_abbrev(KitDebugInfo* dbg) { driver_printf(" Abbrev table @ offset 0x%x:\n", cur_table); } driver_printf(" [%llu] ", (unsigned long long)ab.code); - dw_emit_code(dw_tag_name(ab.tag), ab.tag); + dw_emit_code(kit_dwarf_tag_name(ab.tag), ab.tag); driver_printf(" %s\n", ab.has_children ? "[has children]" : "[no children]"); if (kit_dwarf_abbrev_attr_iter_new(dbg, ab.table_offset, ab.code, &ait) != @@ -1124,9 +956,9 @@ static void dump_dwarf_abbrev(KitDebugInfo* dbg) { continue; while (kit_dwarf_abbrev_attr_iter_next(ait, &aa) == KIT_ITER_ITEM) { driver_printf(" "); - dw_emit_code(dw_at_name(aa.attr), aa.attr); + dw_emit_code(kit_dwarf_attr_name(aa.attr), aa.attr); driver_printf(" "); - dw_emit_code(dw_form_name(aa.form), aa.form); + dw_emit_code(kit_dwarf_form_name(aa.form), aa.form); driver_printf("\n"); } kit_dwarf_abbrev_attr_iter_free(ait); diff --git a/driver/cmd/ranlib.c b/driver/cmd/ranlib.c @@ -1,10 +1,8 @@ #include <kit/archive.h> #include <kit/core.h> #include <kit/object.h> -#include <stdint.h> #include "driver.h" -#include "inputs.h" /* `kit ranlib` — refresh / add a System-V `/` symbol-index member at the * head of an existing POSIX `ar` archive. Equivalent to `kit ar s ARCHIVE`, @@ -16,8 +14,8 @@ * member names are preserved via the `//` extended-name table. Reproducible * output via SOURCE_DATE_EPOCH (same epoch handling as `kit ar`). * - * Per-member symbol collection lives in driver/inputs.c - * (driver_collect_obj_global_syms) — shared with ar / strip. */ + * The read-members / recompute-index / rewrite pipeline is kit_ar_reindex in + * libkit (shared with ar / strip via the same symbol-index automation). */ #define RANLIB_TOOL "ranlib" @@ -54,18 +52,7 @@ int driver_ranlib(int argc, char** argv) { KitContext ctx; KitFileData old_fd = {0}; KitSlice input; - KitArIter* it = NULL; - KitArMember m; - KitArInput* members = NULL; - char* name_storage = NULL; - size_t name_bytes_total = 0; - uint32_t nmembers = 0; - uint32_t i; - KitArMemberSymbols* msyms = NULL; - void** sym_allocs = NULL; - size_t* sym_alloc_szs = NULL; KitWriter* out = NULL; - KitArWriteOptions opts = {0}; const char* archive_path; int have_old = 0; int rc = 1; @@ -93,134 +80,23 @@ int driver_ranlib(int argc, char** argv) { input.data = old_fd.data; input.len = old_fd.size; - /* Pass 1: count members and total name bytes (member names returned by - * the iterator alias an internal buffer overwritten on each next(), so - * we stash a stable copy). */ - if (kit_ar_iter_new(&ctx, &input, &it) != KIT_OK) { - driver_errf(RANLIB_TOOL, "not an archive: %.*s", - KIT_SLICE_ARG(kit_slice_cstr(archive_path))); - goto out; - } - for (;;) { - KitIterResult r = kit_ar_iter_next(it, &m); - if (r != KIT_ITER_ITEM) break; - nmembers++; - name_bytes_total += m.name.len + 1; - } - kit_ar_iter_free(it); - it = NULL; - - if (nmembers == 0) { - /* Empty archive — still rewrite with an empty symbol index (matches - * GNU ranlib's behaviour). */ - if (ctx.file_io->open_writer(ctx.file_io->user, archive_path, &out) != - KIT_OK) { - driver_errf(RANLIB_TOOL, "failed to open: %.*s", - KIT_SLICE_ARG(kit_slice_cstr(archive_path))); - goto out; - } - opts.epoch = driver_epoch_from_env(); - opts.long_names = 1; - opts.symbol_index = 1; - rc = kit_ar_write(out, NULL, 0, &opts) == KIT_OK ? 0 : 1; - goto out; - } - - members = (KitArInput*)driver_alloc_zeroed( - &env, (size_t)nmembers * sizeof(*members)); - if (!members) { - driver_errf(RANLIB_TOOL, "out of memory"); - goto out; - } - if (name_bytes_total > 0) { - name_storage = (char*)driver_alloc_zeroed(&env, name_bytes_total); - if (!name_storage) { - driver_errf(RANLIB_TOOL, "out of memory"); - goto out; - } - } - - /* Pass 2: copy names and member byte-spans into our parallel arrays. */ - if (kit_ar_iter_new(&ctx, &input, &it) != KIT_OK) { - driver_errf(RANLIB_TOOL, "iter re-open failed"); - goto out; - } - { - size_t cursor = 0; - uint32_t k = 0; - while (k < nmembers) { - KitIterResult r = kit_ar_iter_next(it, &m); - char* dst; - size_t j; - if (r != KIT_ITER_ITEM) break; - dst = name_storage + cursor; - for (j = 0; j < m.name.len; ++j) *dst++ = m.name.s[j]; - *dst++ = '\0'; - members[k].name.s = name_storage + cursor; - members[k].name.len = m.name.len; - members[k].bytes.data = m.data; - members[k].bytes.len = m.size; - cursor = (size_t)(dst - name_storage); - k++; - } - } - kit_ar_iter_free(it); - it = NULL; - - /* Pass 3: collect per-member global symbols. */ - msyms = (KitArMemberSymbols*)driver_alloc_zeroed( - &env, (size_t)nmembers * sizeof(*msyms)); - sym_allocs = - (void**)driver_alloc_zeroed(&env, (size_t)nmembers * sizeof(*sym_allocs)); - sym_alloc_szs = (size_t*)driver_alloc_zeroed( - &env, (size_t)nmembers * sizeof(*sym_alloc_szs)); - if (!msyms || !sym_allocs || !sym_alloc_szs) { - driver_errf(RANLIB_TOOL, "out of memory"); - goto out; - } - for (i = 0; i < nmembers; ++i) { - void* blob = NULL; - size_t blob_size = 0; - const KitSlice* names = NULL; - uint32_t count = 0; - if (driver_collect_obj_global_syms(&env, &ctx, RANLIB_TOOL, - &members[i].bytes, &blob, &blob_size, - &names, &count) != 0) { - goto out; - } - sym_allocs[i] = blob; - sym_alloc_szs[i] = blob_size; - msyms[i].names = names; - msyms[i].count = count; - } - + /* Read every member and rewrite the archive with a freshly-computed symbol + * index — the whole of ranlib is now kit_ar_reindex. */ if (ctx.file_io->open_writer(ctx.file_io->user, archive_path, &out) != KIT_OK) { driver_errf(RANLIB_TOOL, "failed to open: %.*s", KIT_SLICE_ARG(kit_slice_cstr(archive_path))); goto out; } - opts.epoch = driver_epoch_from_env(); - opts.long_names = 1; - opts.symbol_index = 1; - opts.member_symbols = msyms; - rc = kit_ar_write(out, members, nmembers, &opts) == KIT_OK ? 0 : 1; - if (rc == 0 && kit_writer_status(out) != KIT_OK) rc = 1; + if (kit_ar_reindex(&ctx, &input, driver_epoch_from_env(), out) != KIT_OK) { + driver_errf(RANLIB_TOOL, "not an archive: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(archive_path))); + goto out; + } + rc = kit_writer_status(out) == KIT_OK ? 0 : 1; out: if (out) kit_writer_close(out); - if (it) kit_ar_iter_free(it); - if (sym_allocs) { - for (i = 0; i < nmembers; ++i) { - if (sym_allocs[i]) driver_free(&env, sym_allocs[i], sym_alloc_szs[i]); - } - driver_free(&env, sym_allocs, (size_t)nmembers * sizeof(*sym_allocs)); - } - if (sym_alloc_szs) - driver_free(&env, sym_alloc_szs, (size_t)nmembers * sizeof(*sym_alloc_szs)); - if (msyms) driver_free(&env, msyms, (size_t)nmembers * sizeof(*msyms)); - if (name_storage) driver_free(&env, name_storage, name_bytes_total); - if (members) driver_free(&env, members, (size_t)nmembers * sizeof(*members)); if (have_old) ctx.file_io->release(ctx.file_io->user, &old_fd); driver_env_fini(&env); return rc; diff --git a/driver/cmd/size.c b/driver/cmd/size.c @@ -28,71 +28,13 @@ typedef struct SizeOpts { int totals; /* -t / --totals (berkeley only) */ } SizeOpts; -typedef struct SizeAgg { - uint64_t text; - uint64_t data; - uint64_t bss; - uint64_t total; -} SizeAgg; - -static void size_classify_section(const KitObjSecInfo* sec, int* is_text, - int* is_data, int* is_bss) { - int alloc = (sec->flags & KIT_SF_ALLOC) != 0; - int exec = (sec->flags & KIT_SF_EXEC) != 0; - int write = (sec->flags & KIT_SF_WRITE) != 0; - - *is_text = 0; - *is_data = 0; - *is_bss = 0; - - if (!alloc) return; - if (sec->kind == KIT_SEC_DEBUG) return; - - if (sec->kind == KIT_SEC_TEXT || exec) { - *is_text = 1; - return; - } - if (sec->kind == KIT_SEC_BSS) { - *is_bss = 1; - return; - } - if (sec->kind == KIT_SEC_RODATA) { - *is_data = 1; - return; - } - if (sec->kind == KIT_SEC_DATA || write) { - *is_data = 1; - return; - } - *is_data = 1; -} - -static SizeAgg size_compute_obj(KitObjFile* of, const SizeOpts* opts) { - SizeAgg a; - uint32_t ns, i; - memset(&a, 0, sizeof a); - ns = kit_obj_nsections(of); - for (i = 0; i < ns; ++i) { - KitObjSecInfo sec; - int is_text, is_data, is_bss; - if (kit_obj_section(of, i, &sec) != KIT_OK) continue; - size_classify_section(&sec, &is_text, &is_data, &is_bss); - if (is_text) a.text += sec.size; - if (is_data) a.data += sec.size; - if (is_bss) a.bss += sec.size; - } - if (opts->common) { - KitObjSymIter* it = NULL; - if (kit_obj_symiter_new(of, &it) == KIT_OK) { - for (;;) { - KitObjSymInfo si; - if (kit_obj_symiter_next(it, &si) != KIT_ITER_ITEM) break; - if (si.kind == KIT_SK_COMMON) a.bss += si.size; - } - kit_obj_symiter_free(it); - } - } - a.total = a.text + a.data + a.bss; +/* Section size classification + aggregation now live in libkit + * (kit_obj_section_size_class / kit_obj_size_totals). size_compute_obj is a + * thin shim that maps the tool's --common flag onto the public totals call. */ +static KitObjSizeTotals size_compute_obj(KitObjFile* of, const SizeOpts* opts) { + KitObjSizeTotals a; + if (kit_obj_size_totals(of, opts->common != 0, &a) != KIT_OK) + memset(&a, 0, sizeof a); return a; } @@ -121,7 +63,7 @@ static void size_print_berkeley_header(const SizeOpts* opts) { "data", w, "bss", "dec", "hex"); } -static void size_print_berkeley_sums(const SizeAgg* a, const char* name, +static void size_print_berkeley_sums(const KitObjSizeTotals* a, const char* name, const SizeOpts* opts) { int w = size_width(opts->radix); char text_buf[32], data_buf[32], bss_buf[32], dec_buf[32], hex_buf[32]; @@ -151,14 +93,14 @@ static void size_print_sysv(KitObjFile* of, const char* name, (unsigned long long)sec.size, (unsigned long long)sec.addr); } { - SizeAgg a = size_compute_obj(of, opts); + KitObjSizeTotals a = size_compute_obj(of, opts); driver_printf("Total %08llx\n", (unsigned long long)a.total); } } static int size_process_file(const KitContext* ctx, const KitSlice* input, const char* path, const SizeOpts* opts, - SizeAgg* total_out, int* any_out) { + KitObjSizeTotals* total_out, int* any_out) { KitBinFmt fmt = kit_detect_fmt(input->data, input->len); if (fmt == KIT_BIN_AR) { KitArIter* it = NULL; @@ -174,7 +116,7 @@ static int size_process_file(const KitContext* ctx, const KitSlice* input, mb.data = m.data; mb.len = m.size; if (kit_obj_open(ctx, KIT_SLICE_NULL, &mb, &of) == KIT_OK) { - SizeAgg a = size_compute_obj(of, opts); + KitObjSizeTotals a = size_compute_obj(of, opts); if (opts->fmt == SIZE_FMT_BERKELEY) { char nmbuf[512]; snprintf(nmbuf, sizeof nmbuf, "%.*s(%.*s)", @@ -203,7 +145,7 @@ static int size_process_file(const KitContext* ctx, const KitSlice* input, return 1; } { - SizeAgg a = size_compute_obj(of, opts); + KitObjSizeTotals a = size_compute_obj(of, opts); if (opts->fmt == SIZE_FMT_BERKELEY) { size_print_berkeley_sums(&a, path, opts); } else { @@ -244,7 +186,7 @@ int driver_size(int argc, char** argv) { DriverEnv env; KitContext ctx; SizeOpts opts; - SizeAgg totals; + KitObjSizeTotals totals; int i, rc = 1, any_input = 0, any_output = 0; if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) { diff --git a/driver/cmd/strip.c b/driver/cmd/strip.c @@ -5,7 +5,6 @@ #include <string.h> #include "driver.h" -#include "inputs.h" #include "objedit.h" /* `kit strip` — drop debug sections and / or unwanted symbols from a @@ -126,77 +125,57 @@ static int parse_name_arg(int* i, int argc, char** argv, const char* flag, return 0; } -/* The core strip pass: drop debug sections, then walk symbols and apply - * keep/strip lists and the operation policy. Mutations are issued - * against the builder; emit-time sweep cleans up cascades (orphan - * relocs against removed sections, dropped group memberships, etc.). */ +/* The core strip pass: drop debug sections + prune symbols per the level and + * keep-list (kit_obj_builder_strip in libkit), then layer the driver-only + * --strip-symbol explicit drops on top. Running the explicit drops last is + * what makes --strip-symbol win over --keep-symbol when a name is on both + * lists. Mutations are issued against the builder; the emit-time sweep cleans + * up cascades (orphan relocs against removed sections, etc.). */ static int strip_one_builder(DriverEnv* env, KitObjFile* of, KitObjBuilder* b, const StripOpts* opts) { - uint32_t i, nsec; - int filter_syms = (opts->op == STRIP_OP_UNNEEDED || opts->op == STRIP_OP_ALL); - KitObjSymbol* needed = NULL; - uint32_t nneeded = 0, cap_needed = 0; - KitObjSymIter* sit = NULL; + KitSlice* keep = NULL; + int level = opts->op == STRIP_OP_DEBUG ? KIT_STRIP_DEBUG + : opts->op == STRIP_OP_UNNEEDED ? KIT_STRIP_UNNEEDED + : KIT_STRIP_ALL; + uint32_t i; int rc = 1; - /* Step 1: drop debug sections (every supported op does this). */ - nsec = kit_obj_nsections(of); - for (i = 0; i < nsec; ++i) { - KitObjSecInfo si; - if (kit_obj_section(of, i, &si) != KIT_OK) continue; - if (si.kind == KIT_SEC_DEBUG) { - kit_obj_builder_remove_section(b, i); - } - } - - /* Step 2: compute the needed-sym set. */ - if (filter_syms) { - if (driver_obj_collect_reloc_target_syms(env, STRIP_TOOL, of, &needed, - &nneeded, &cap_needed) != 0) { + /* Adapt the --keep-symbol C-string list to the KitSlice keep_names shape. */ + if (opts->nkeep) { + keep = + (KitSlice*)driver_alloc_zeroed(env, (size_t)opts->nkeep * sizeof(*keep)); + if (!keep) { + driver_errf(STRIP_TOOL, "out of memory"); return 1; } + for (i = 0; i < opts->nkeep; ++i) keep[i] = kit_slice_cstr(opts->keep[i]); } - /* Step 3: walk symbols and apply filters. */ - if (kit_obj_symiter_new(of, &sit) != KIT_OK) { - driver_errf(STRIP_TOOL, "out of memory"); + if (kit_obj_builder_strip(b, level, keep, opts->nkeep) != KIT_OK) { + driver_errf(STRIP_TOOL, "strip failed"); goto done; } - for (;;) { - KitObjSymInfo si; - KitIterResult ir = kit_obj_symiter_next(sit, &si); - int drop = 0; - if (ir != KIT_ITER_ITEM) break; - /* --strip-symbol wins over --keep-symbol if both list the same name. */ - if (opts->nstrip && - driver_name_in_list(si.name, opts->strip, opts->nstrip)) { - drop = 1; - } else if (opts->nkeep && - driver_name_in_list(si.name, opts->keep, opts->nkeep)) { - drop = 0; - } else if (filter_syms) { - /* Keep undefined externals so the .o stays linkable; keep symbols - * targeted by a surviving reloc; drop everything else. Note that - * section symbols defined in removed debug sections are already - * tombstoned by the emit-time sweep cascade — no explicit handling - * needed here. */ - if (si.kind == KIT_SK_UNDEF) { - drop = 0; - } else if (driver_obj_id_in_set(si.id, needed, nneeded)) { - drop = 0; - } else { - drop = 1; - } + + /* --strip-symbol: always drop the named symbols (overrides keep + policy). */ + if (opts->nstrip) { + KitObjSymIter* sit = NULL; + if (kit_obj_symiter_new(of, &sit) != KIT_OK) { + driver_errf(STRIP_TOOL, "out of memory"); + goto done; } - if (drop) { - kit_obj_builder_remove_symbol(b, si.id); + for (;;) { + KitObjSymInfo si; + KitIterResult ir = kit_obj_symiter_next(sit, &si); + if (ir != KIT_ITER_ITEM) break; + if (driver_name_in_list(si.name, opts->strip, opts->nstrip)) + kit_obj_builder_remove_symbol(b, si.id); } + kit_obj_symiter_free(sit); } - kit_obj_symiter_free(sit); rc = 0; done: - if (needed) driver_free(env, needed, (size_t)cap_needed * sizeof(*needed)); + if (keep) driver_free(env, keep, (size_t)opts->nkeep * sizeof(*keep)); return rc; } @@ -275,7 +254,6 @@ static int strip_archive(DriverEnv* env, const KitContext* ctx, uint32_t nmembers = 0, k; KitArMemberSymbols* msyms = NULL; void** sym_allocs = NULL; - size_t* sym_alloc_szs = NULL; KitWriter* out = NULL; KitArWriteOptions opts_ar = {0}; int rc = 1; @@ -368,26 +346,16 @@ static int strip_archive(DriverEnv* env, const KitContext* ctx, env, (size_t)nmembers * sizeof(*msyms)); sym_allocs = (void**)driver_alloc_zeroed( env, (size_t)nmembers * sizeof(*sym_allocs)); - sym_alloc_szs = (size_t*)driver_alloc_zeroed( - env, (size_t)nmembers * sizeof(*sym_alloc_szs)); - if (!msyms || !sym_allocs || !sym_alloc_szs) { + if (!msyms || !sym_allocs) { driver_errf(STRIP_TOOL, "out of memory"); goto done; } for (k = 0; k < nmembers; ++k) { - void* blob = NULL; - size_t blob_size = 0; - const KitSlice* names = NULL; - uint32_t count = 0; - if (driver_collect_obj_global_syms(env, ctx, STRIP_TOOL, - &members[k].bytes, &blob, &blob_size, - &names, &count) != 0) { + if (kit_obj_global_syms(ctx, &members[k].bytes, &msyms[k], + &sym_allocs[k]) != KIT_OK) { + driver_errf(STRIP_TOOL, "out of memory"); goto done; } - sym_allocs[k] = blob; - sym_alloc_szs[k] = blob_size; - msyms[k].names = names; - msyms[k].count = count; } } @@ -409,14 +377,10 @@ done: if (it) kit_ar_iter_free(it); if (sym_allocs) { for (k = 0; k < nmembers; ++k) { - if (sym_allocs[k]) - driver_collect_obj_global_syms_free(env, sym_allocs[k], - sym_alloc_szs[k]); + if (sym_allocs[k]) kit_obj_global_syms_free(ctx, sym_allocs[k]); } driver_free(env, sym_allocs, (size_t)nmembers * sizeof(*sym_allocs)); } - if (sym_alloc_szs) - driver_free(env, sym_alloc_szs, (size_t)nmembers * sizeof(*sym_alloc_szs)); if (msyms) driver_free(env, msyms, (size_t)nmembers * sizeof(*msyms)); if (owned_data) { for (k = 0; k < nmembers; ++k) { diff --git a/driver/cmd/symbolize.c b/driver/cmd/symbolize.c @@ -66,7 +66,8 @@ static int sym_line_addr(const char* s, size_t len, uint64_t* out) { * "??" / "??:0" placeholders addr2line's pretty mode uses for the missing * halves. Mirrors a2l_translate's pretty path so the two tools render an * unresolved frame identically. */ -static void sym_emit_annotation(const DriverSymLoc* loc, const SymOpts* opts) { +static void sym_emit_annotation(const KitDwarfResolve* loc, + const SymOpts* opts) { driver_printf(" "); if (loc->have_func) driver_printf("%.*s at ", (int)loc->func.len, loc->func.s); @@ -183,8 +184,8 @@ int driver_symbolize(int argc, char** argv) { driver_printf("%.*s", (int)(end - start), (const char*)(data + start)); if (sym_line_addr((const char*)(data + start), end - start, &addr)) { - DriverSymLoc loc; - driver_dwarfsym_lookup(&sym, addr, 1, &loc); + KitDwarfResolve loc; + kit_dwarf_resolve(sym.dwarf, addr, 1, &loc); sym_emit_annotation(&loc, &opts); } if (end < size) { diff --git a/driver/lib/archive_engine.c b/driver/lib/archive_engine.c @@ -4,100 +4,20 @@ #include "inputs.h" +/* Thin wrapper over kit_ar_write_objs (libkit owns the serialize-each-builder, + * compute-the-symbol-index, write-the-archive pipeline). The driver layer adds + * the no-objects guard and the driver_errf diagnostics callers expect. */ int driver_archive_emit(DriverEnv* env, const KitContext* ctx, const char* tool, KitObjBuilder* const* objs, const KitSlice* names, uint32_t n, uint64_t epoch, KitWriter* out) { - KitArInput* members = NULL; - KitWriter** memw = NULL; - KitArMemberSymbols* msyms = NULL; - void** sym_blobs = NULL; - size_t* sym_blob_szs = NULL; - KitArWriteOptions opts = {0}; - uint32_t i; - int rc = 1; - + (void)env; if (n == 0) { driver_errf(tool, "no objects to archive"); return 1; } - - members = driver_alloc_zeroed(env, (size_t)n * sizeof(*members)); - memw = driver_alloc_zeroed(env, (size_t)n * sizeof(*memw)); - msyms = driver_alloc_zeroed(env, (size_t)n * sizeof(*msyms)); - sym_blobs = driver_alloc_zeroed(env, (size_t)n * sizeof(*sym_blobs)); - sym_blob_szs = driver_alloc_zeroed(env, (size_t)n * sizeof(*sym_blob_szs)); - if (!members || !memw || !msyms || !sym_blobs || !sym_blob_szs) { - driver_errf(tool, "out of memory"); - goto out; - } - - /* Serialize each builder into its own in-memory writer; the member bytes - * alias that writer's buffer, so the writers stay open until kit_ar_write - * has consumed them. */ - for (i = 0; i < n; ++i) { - const uint8_t* bytes; - size_t len = 0; - if (kit_writer_mem(ctx->heap, &memw[i]) != KIT_OK) { - driver_errf(tool, "out of memory"); - goto out; - } - if (kit_obj_builder_emit(objs[i], memw[i]) != KIT_OK || - kit_writer_status(memw[i]) != KIT_OK) { - driver_errf(tool, "failed to serialize object: %.*s", - KIT_SLICE_ARG(names[i])); - goto out; - } - bytes = kit_writer_mem_bytes(memw[i], &len); - members[i].name = names[i]; - members[i].bytes.s = (const char*)bytes; - members[i].bytes.len = len; - } - - /* Build the archive symbol index so the result links like a normal static - * library: collect each member's globally-defined symbols. */ - for (i = 0; i < n; ++i) { - void* blob = NULL; - size_t blob_size = 0; - const KitSlice* syms = NULL; - uint32_t count = 0; - if (driver_collect_obj_global_syms(env, ctx, tool, &members[i].bytes, &blob, - &blob_size, &syms, &count) != 0) - goto out; - if (count == 0) continue; - sym_blobs[i] = blob; - sym_blob_szs[i] = blob_size; - msyms[i].names = syms; - msyms[i].count = count; - } - - opts.epoch = epoch; - opts.long_names = 1; - opts.symbol_index = 1; - opts.member_symbols = msyms; - - if (kit_ar_write(out, members, n, &opts) != KIT_OK || - kit_writer_status(out) != KIT_OK) { + if (kit_ar_write_objs(ctx, objs, names, n, epoch, out) != KIT_OK) { driver_errf(tool, "failed to write archive"); - goto out; - } - rc = 0; - -out: - if (sym_blobs && sym_blob_szs) { - for (i = 0; i < n; ++i) { - if (sym_blobs[i]) - driver_collect_obj_global_syms_free(env, sym_blobs[i], sym_blob_szs[i]); - } - } - if (memw) { - for (i = 0; i < n; ++i) - if (memw[i]) kit_writer_close(memw[i]); + return 1; } - if (members) driver_free(env, members, (size_t)n * sizeof(*members)); - if (memw) driver_free(env, memw, (size_t)n * sizeof(*memw)); - if (msyms) driver_free(env, msyms, (size_t)n * sizeof(*msyms)); - if (sym_blobs) driver_free(env, sym_blobs, (size_t)n * sizeof(*sym_blobs)); - if (sym_blob_szs) - driver_free(env, sym_blob_szs, (size_t)n * sizeof(*sym_blob_szs)); - return rc; + return 0; } diff --git a/driver/lib/compile_engine.c b/driver/lib/compile_engine.c @@ -1,242 +0,0 @@ -#include "compile_engine.h" - -#include <kit/asm_emit.h> -#include <kit/cg.h> -#include <string.h> - -static KitStatus driver_compile_cg_run(KitCompiler* compiler, - const KitCodeOptions* code, - const KitDiagnosticOptions* diagnostics, - const DriverCompileSource* src, - KitCg* cg) { - KitCompileSessionOptions sopts; - KitCompileSession* session = NULL; - KitSourceInput sin; - KitStatus st; - - if (!compiler || !code || !diagnostics || !src || !cg) return KIT_INVALID; - memset(&sopts, 0, sizeof(sopts)); - sopts.lang = src->lang; - sopts.compile.code = *code; - sopts.compile.diagnostics = *diagnostics; - if (src->pp) sopts.compile.preprocess = *src->pp; - sopts.compile.language_options = src->lang_extra; - - memset(&sin, 0, sizeof(sin)); - sin.name = src->name; - sin.bytes = src->bytes; - sin.lang = src->lang; - - st = kit_compile_session_new(compiler, &sopts, &session); - if (st == KIT_OK) st = kit_compile_session_compile_cg(session, &sin, cg); - kit_compile_session_free(session); - return st; -} - -KitStatus driver_compile_run(KitCompiler* compiler, KitLanguage lang, - const KitCodeOptions* code, - const KitDiagnosticOptions* diagnostics, - const KitPreprocessOptions* pp, - const void* lang_extra, KitSlice name, - const KitSlice* bytes, KitWriter* emit_out, - KitObjBuilder** obj_out) { - KitCompileSessionOptions sopts; - KitCompileSession* session = NULL; - KitSourceInput sin; - KitObjBuilder* ob = NULL; - KitCodeOptions code_copy = *code; - KitStatus st; - - if (obj_out) *obj_out = NULL; - - /* For the in-CG emit modes the output writer is consumed during codegen, so - * wire it onto the code options before the session runs. */ - if (emit_out && code_copy.emit_c_source) code_copy.c_source_writer = emit_out; - if (emit_out && code_copy.emit_ir) code_copy.ir_dump_writer = emit_out; - - memset(&sopts, 0, sizeof(sopts)); - sopts.lang = lang; - sopts.compile.code = code_copy; - sopts.compile.diagnostics = *diagnostics; - if (pp) sopts.compile.preprocess = *pp; - sopts.compile.language_options = lang_extra; - - memset(&sin, 0, sizeof(sin)); - sin.name = name; - sin.bytes = *bytes; - sin.lang = lang; - - st = kit_compile_session_new(compiler, &sopts, &session); - if (st == KIT_OK) st = kit_compile_session_compile(session, &sin, &ob); - kit_compile_session_free(session); - if (st != KIT_OK) return st; - - if (obj_out) { - *obj_out = ob; - return KIT_OK; - } - - /* emit_out path: serialize by output mode. The in-CG modes already wrote - * through the wired writer above. */ - if (code_copy.emit_c_source || code_copy.emit_ir) { - /* nothing to serialize here */ - } else if (code_copy.emit_asm_source) { - st = kit_obj_builder_emit_asm(ob, emit_out); - } else { - st = kit_obj_builder_emit(ob, emit_out); - } - kit_obj_builder_free(ob); - return st; -} - -static int driver_compile_lto_enabled(const KitCodeOptions* code) { - return code && code->lto && !code->check_only && !code->emit_c_source && - !code->emit_ir && !code->emit_asm_source; -} - -static KitStatus driver_compile_start_lto(KitCompiler* compiler, - const KitCodeOptions* code, - KitObjBuilder** ob_out, - KitCg** cg_out) { - KitObjBuilder* ob = NULL; - KitCg* cg = NULL; - KitStatus st; - - if (ob_out) *ob_out = NULL; - if (cg_out) *cg_out = NULL; - if (!compiler || !code || !ob_out || !cg_out) return KIT_INVALID; - st = kit_obj_builder_new(compiler, &ob); - if (st == KIT_OK) st = kit_cg_new(compiler, &cg); - if (st == KIT_OK) st = kit_cg_begin(cg, ob, code); - if (st != KIT_OK) { - kit_cg_free(cg); - kit_obj_builder_free(ob); - return st; - } - *ob_out = ob; - *cg_out = cg; - return KIT_OK; -} - -KitStatus driver_compile_pending_lto_finish( - DriverCompilePendingLto* pending, const DriverCompileBatchOptions* batch, - const KitCgSym* preserved_symbols, uint32_t npreserved_symbols) { - KitCgFinishOptions finish; - KitStatus st; - - if (!pending || !pending->active) return KIT_OK; - if (!pending->obj || !pending->cg) { - driver_compile_pending_lto_abort(pending); - return KIT_INVALID; - } - - memset(&finish, 0, sizeof finish); - finish.output_kind = batch ? batch->output_kind : KIT_CG_OUTPUT_RELOCATABLE; - finish.interposition_policy = - batch ? batch->interposition_policy : KIT_CG_INTERPOSITION_DEFAULT; - finish.preserved_symbols = preserved_symbols; - finish.npreserved_symbols = npreserved_symbols; - - st = kit_cg_finish(pending->cg, &finish); - if (st == KIT_OK) st = kit_cg_detach(pending->cg); - if (st == KIT_OK) st = kit_obj_builder_finalize(pending->obj); - - kit_cg_free(pending->cg); - pending->cg = NULL; - pending->active = 0; - return st; -} - -void driver_compile_pending_lto_abort(DriverCompilePendingLto* pending) { - if (!pending || !pending->active) return; - kit_cg_free(pending->cg); - pending->cg = NULL; - pending->active = 0; -} - -KitStatus driver_compile_sources_run(KitCompiler* compiler, - const KitCodeOptions* code, - const KitDiagnosticOptions* diagnostics, - const DriverCompileSource* sources, - uint32_t nsources, - const DriverCompileBatchOptions* batch, - DriverCompileObjects* out) { - DriverCompilePendingLto pending_lto; - int lto_order_emitted = 0; - int lto_enabled = driver_compile_lto_enabled(code); - KitStatus st = KIT_OK; - - if (!compiler || !code || !diagnostics || (!sources && nsources) || !out || - !out->objs || !out->source_obj_index || !out->source_order_keep) { - return KIT_INVALID; - } - memset(&pending_lto, 0, sizeof pending_lto); - out->nobjs = 0; - if (out->pending_lto) memset(out->pending_lto, 0, sizeof(*out->pending_lto)); - for (uint32_t i = 0; i < nsources; ++i) { - out->source_obj_index[i] = (uint32_t)-1; - out->source_order_keep[i] = 0; - } - - for (uint32_t i = 0; i < nsources; ++i) { - const DriverCompileSource* src = &sources[i]; - KitFrontendCaps caps; - int stage_cg = 0; - - memset(&caps, 0, sizeof caps); - if (lto_enabled) { - st = kit_frontend_caps(compiler, src->lang, &caps); - if (st != KIT_OK) goto out; - stage_cg = caps.lto_mode == KIT_FRONTEND_LTO_CG; - } - - if (stage_cg) { - if (!pending_lto.active) { - st = driver_compile_start_lto(compiler, code, &pending_lto.obj, - &pending_lto.cg); - if (st != KIT_OK) goto out; - pending_lto.obj_index = out->nobjs; - pending_lto.active = 1; - out->objs[out->nobjs++] = pending_lto.obj; - } - out->source_obj_index[i] = pending_lto.obj_index; - if (!lto_order_emitted) { - out->source_order_keep[i] = 1; - lto_order_emitted = 1; - } - st = driver_compile_cg_run(compiler, code, diagnostics, src, - pending_lto.cg); - if (st != KIT_OK) goto out; - continue; - } - - { - KitObjBuilder* ob = NULL; - st = driver_compile_run(compiler, src->lang, code, diagnostics, src->pp, - src->lang_extra, src->name, &src->bytes, NULL, - &ob); - if (st != KIT_OK) goto out; - out->source_obj_index[i] = out->nobjs; - out->source_order_keep[i] = 1; - out->objs[out->nobjs++] = ob; - } - } - - if (pending_lto.active) { - if (batch && batch->defer_lto_finish) { - if (!out->pending_lto) { - st = KIT_INVALID; - goto out; - } - *out->pending_lto = pending_lto; - memset(&pending_lto, 0, sizeof pending_lto); - } else { - st = driver_compile_pending_lto_finish(&pending_lto, batch, NULL, 0); - if (st != KIT_OK) goto out; - } - } - -out: - if (pending_lto.active) driver_compile_pending_lto_abort(&pending_lto); - return st; -} diff --git a/driver/lib/compile_engine.h b/driver/lib/compile_engine.h @@ -1,92 +0,0 @@ -#ifndef KIT_DRIVER_COMPILE_ENGINE_H -#define KIT_DRIVER_COMPILE_ENGINE_H - -#include <kit/cg.h> -#include <kit/compile.h> -#include <kit/object.h> -#include <kit/preprocess.h> -#include <stdint.h> - -/* Language-neutral "compile one source" step shared by `cc` and `compile`. - * - * Builds a KitCompileSession for `lang`, compiles `bytes` (labelled `name`), - * and then either: - * - returns the object builder via `obj_out` (caller owns it; used by the - * link and check paths), or - * - emits the result to `emit_out`, routed by code->emit_*: object bytes by - * default, `.s` assembly for emit_asm_source, or — for emit_c_source / - * emit_ir — the writer is wired onto the CG and the builder is dropped. - * Exactly one of `emit_out` / `obj_out` must be non-NULL. - * - * code, diagnostics : common per-compile settings (required). - * pp : preprocessor settings, applied to preprocessor-enabled - * frontends; NULL for none. - * lang_extra : opaque per-frontend options for language_options - * (e.g. KitWasmCompileOptions*); NULL when the frontend - * has none. - * - * Returns the compile/emit KitStatus. */ -KitStatus driver_compile_run(KitCompiler* compiler, KitLanguage lang, - const KitCodeOptions* code, - const KitDiagnosticOptions* diagnostics, - const KitPreprocessOptions* pp, - const void* lang_extra, KitSlice name, - const KitSlice* bytes, KitWriter* emit_out, - KitObjBuilder** obj_out); - -typedef struct DriverCompileSource { - KitLanguage lang; - KitSlice name; - KitSlice bytes; - const KitPreprocessOptions* pp; - const void* lang_extra; -} DriverCompileSource; - -typedef struct DriverCompileObjects { - /* Caller-allocated capacity for at least nsources objects. Filled compactly. - */ - KitObjBuilder** objs; - uint32_t nobjs; - /* Caller-allocated nsources-entry maps. source_obj_index[i] is the compact - * object index for source i. source_order_keep[i] is true only for the source - * position that should contribute an order/archive member; later semantic LTO - * sources map to the same object and have keep=false. */ - uint32_t* source_obj_index; - uint8_t* source_order_keep; - struct DriverCompilePendingLto* pending_lto; -} DriverCompileObjects; - -typedef struct DriverCompileBatchOptions { - uint8_t output_kind; /* KitCgOutputKind */ - uint8_t interposition_policy; /* KitCgInterpositionPolicy */ - uint8_t defer_lto_finish; - uint8_t pad[1]; -} DriverCompileBatchOptions; - -typedef struct DriverCompilePendingLto { - KitObjBuilder* obj; - KitCg* cg; - uint32_t obj_index; - uint8_t active; - uint8_t pad[3]; -} DriverCompilePendingLto; - -/* Compile a batch of sources for a link/archive/relocatable output. When - * code->lto is set, KIT_FRONTEND_LTO_CG frontends emit into one shared KitCg - * unit and KIT_FRONTEND_LTO_OPAQUE/NONE frontends still compile as ordinary - * per-source objects. */ -KitStatus driver_compile_sources_run(KitCompiler* compiler, - const KitCodeOptions* code, - const KitDiagnosticOptions* diagnostics, - const DriverCompileSource* sources, - uint32_t nsources, - const DriverCompileBatchOptions* batch, - DriverCompileObjects* out); - -KitStatus driver_compile_pending_lto_finish( - DriverCompilePendingLto* pending, const DriverCompileBatchOptions* batch, - const KitCgSym* preserved_symbols, uint32_t npreserved_symbols); - -void driver_compile_pending_lto_abort(DriverCompilePendingLto* pending); - -#endif diff --git a/driver/lib/dwarfsym.c b/driver/lib/dwarfsym.c @@ -43,28 +43,3 @@ void driver_dwarfsym_close(DriverDwarfSym* s) { } if (s->env && s->ld.loaded) driver_release_bytes(&s->env->file_io, &s->ld); } - -void driver_dwarfsym_lookup(DriverDwarfSym* s, uint64_t addr, int want_func, - DriverSymLoc* out) { - KitSlice file; - uint32_t line = 0, col = 0; - - memset(out, 0, sizeof *out); - - if (kit_dwarf_addr_to_line(s->dwarf, addr, &file, &line, &col) == KIT_OK) { - out->have_line = 1; - out->file = file; - out->line = line; - out->col = col; - } - - if (want_func) { - KitSlice func; - uint64_t func_lo = 0, func_hi = 0; - if (kit_dwarf_func_at(s->dwarf, addr, &func, &func_lo, &func_hi) == - KIT_OK) { - out->have_func = 1; - out->func = func; - } - } -} diff --git a/driver/lib/dwarfsym.h b/driver/lib/dwarfsym.h @@ -8,14 +8,13 @@ #include "env.h" -/* Shared address -> symbol core for the `addr2line` and `symbolize` tools. +/* Shared image-open core for the `addr2line` and `symbolize` tools. * * A DriverDwarfSym holds one image's loaded bytes plus its opened DWARF - * reader, so a tool opens the object once and translates many addresses. - * driver_dwarfsym_lookup runs the same kit_dwarf_func_at / - * kit_dwarf_addr_to_line queries both tools share and reports the result in a - * DriverSymLoc; each tool then formats that however it likes (addr2line one - * line per address; symbolize annotating a backtrace stream in place). + * reader, so a tool opens the object once and translates many addresses. The + * per-address lookup itself is the public libkit kit_dwarf_resolve (over + * `s->dwarf`); each tool then formats that result however it likes (addr2line + * one line per address; symbolize annotating a backtrace stream in place). * * Addresses are image-relative, matching the kit_dwarf_* contract. The of / * dwarf borrow from the loaded bytes, so driver_dwarfsym_close frees in the @@ -40,22 +39,4 @@ int driver_dwarfsym_open(DriverDwarfSym* s, DriverEnv* env, const char* tool, * itself is owned by the caller and is left untouched. */ void driver_dwarfsym_close(DriverDwarfSym* s); -/* The resolved location for one address. `have_func` / `have_line` say which - * fields are valid; an unresolved query returns with both clear (and the - * slices empty). `func`, `file`, `line`, and `col` mirror kit_dwarf_func_at / - * kit_dwarf_addr_to_line outputs. */ -typedef struct DriverSymLoc { - int have_func; - KitSlice func; - int have_line; - KitSlice file; - uint32_t line; - uint32_t col; -} DriverSymLoc; - -/* Translate one image-relative address. `want_func` gates the (separate) - * DW function-name query so callers that never print a name skip the work. */ -void driver_dwarfsym_lookup(DriverDwarfSym* s, uint64_t addr, int want_func, - DriverSymLoc* out); - #endif /* KIT_DRIVER_DWARFSYM_H */ diff --git a/driver/lib/inputs.c b/driver/lib/inputs.c @@ -1,5 +1,6 @@ #include "inputs.h" +#include <kit/archive.h> #include <kit/frontend.h> #include <stddef.h> #include <stdint.h> @@ -75,21 +76,23 @@ int driver_path_is_source(const char* path) { int driver_inputs_classify(DriverInputs* in, const char* arg) { if (driver_streq(arg, "-")) return inputs_record_stdin(in); - /* Source classification flows through the shared driver_path_is_source helper - * (canonical extension registry, headers excluded). This also fixes a latent - * bug: the former explicit list checked only ".s" case-sensitively, so ".S" - * assembly was misclassified; the registry matches case-insensitively. */ - if (driver_path_is_source(arg)) { - in->sources[in->nsources++] = arg; - return 1; - } - if (driver_has_suffix(arg, ".o") || driver_has_suffix(arg, ".obj")) { - in->object_files[in->nobject_files++] = arg; - return 1; - } - if (driver_has_suffix(arg, ".a")) { - in->archives[in->narchives++] = arg; - return 1; + /* Classification flows through the public kit_input_kind_for_path: source + * via the canonical case-insensitive extension registry (headers excluded), + * then the object/archive suffix tests. A .so/.dylib is KIT_INPUT_DSO, which + * run/dbg do not accept, so it falls through to "unrecognized" like before. */ + switch (kit_input_kind_for_path(NULL, arg)) { + case KIT_INPUT_SOURCE: + in->sources[in->nsources++] = arg; + return 1; + case KIT_INPUT_OBJECT: + in->object_files[in->nobject_files++] = arg; + return 1; + case KIT_INPUT_ARCHIVE: + in->archives[in->narchives++] = arg; + return 1; + case KIT_INPUT_DSO: + case KIT_INPUT_UNKNOWN: + break; } return 0; } @@ -284,6 +287,13 @@ out: /* ---------------------------------------------------------------------- * Per-object global-symbol collection (shared by ar / ranlib / strip). + * + * The "which symbols a linker indexes" policy and the collect-and-pack work + * now live in libkit (kit_obj_global_syms); this is a thin driver-side wrapper + * that adapts the old (blob, blob_size, names, count) shape and emits a + * driver_errf on allocation failure. The libkit block carries its own free + * size, so blob_size is no longer load-bearing — it is reported as 0 and the + * matching _free routes through kit_obj_global_syms_free. * ---------------------------------------------------------------------- */ int driver_collect_obj_global_syms(DriverEnv* env, const KitContext* ctx, @@ -291,99 +301,28 @@ int driver_collect_obj_global_syms(DriverEnv* env, const KitContext* ctx, void** blob_out, size_t* blob_size_out, const KitSlice** names_out, uint32_t* count_out) { - KitObjFile* of = NULL; - KitObjSymIter* it = NULL; - KitObjSymInfo si; - uint32_t count = 0; - size_t name_bytes = 0; - size_t alloc_sz; - char* blob; - KitSlice* name_arr; - char* name_storage; - size_t cursor = 0; + KitArMemberSymbols syms = {NULL, 0}; + void* owned = NULL; + (void)env; *blob_out = NULL; *blob_size_out = 0; *names_out = NULL; *count_out = 0; - if (kit_obj_open(ctx, KIT_SLICE_NULL, member, &of) != KIT_OK) { - /* Not a recognized object — caller treats as "no symbols". */ - return 0; - } - - /* Pass A: count + measure name bytes. */ - if (kit_obj_symiter_new(of, &it) != KIT_OK) { - kit_obj_free(of); + if (kit_obj_global_syms(ctx, member, &syms, &owned) != KIT_OK) { driver_errf(tool, "out of memory"); return 1; } - for (;;) { - KitIterResult r = kit_obj_symiter_next(it, &si); - if (r != KIT_ITER_ITEM) break; - if (si.bind != KIT_SB_GLOBAL) continue; - if (si.section == KIT_SECTION_NONE) continue; - if (!si.name.len) continue; - count += 1; - name_bytes += si.name.len + 1; /* +NUL */ - } - kit_obj_symiter_free(it); - - if (count == 0) { - kit_obj_free(of); - return 0; - } - - alloc_sz = (size_t)count * sizeof(KitSlice) + name_bytes; - blob = (char*)driver_alloc_zeroed(env, alloc_sz); - if (!blob) { - kit_obj_free(of); - driver_errf(tool, "out of memory"); - return 1; - } - name_arr = (KitSlice*)blob; - name_storage = blob + (size_t)count * sizeof(KitSlice); - - /* Pass B: copy names. */ - if (kit_obj_symiter_new(of, &it) != KIT_OK) { - driver_free(env, blob, alloc_sz); - kit_obj_free(of); - driver_errf(tool, "out of memory"); - return 1; - } - { - uint32_t k = 0; - for (;;) { - KitIterResult r; - char* dst; - size_t j; - if (k >= count) break; - r = kit_obj_symiter_next(it, &si); - if (r != KIT_ITER_ITEM) break; - if (si.bind != KIT_SB_GLOBAL) continue; - if (si.section == KIT_SECTION_NONE) continue; - if (!si.name.len) continue; - dst = name_storage + cursor; - name_arr[k].s = dst; - name_arr[k].len = si.name.len; - for (j = 0; j < si.name.len; ++j) *dst++ = si.name.s[j]; - *dst++ = '\0'; - cursor = (size_t)(dst - name_storage); - k++; - } - count = k; - } - kit_obj_symiter_free(it); - kit_obj_free(of); - - *blob_out = blob; - *blob_size_out = alloc_sz; - *names_out = name_arr; - *count_out = count; + *blob_out = owned; + *names_out = syms.names; + *count_out = syms.count; return 0; } void driver_collect_obj_global_syms_free(DriverEnv* env, void* blob, size_t blob_size) { - if (blob) driver_free(env, blob, blob_size); + KitContext ctx = driver_env_to_context(env); + (void)blob_size; + if (blob) kit_obj_global_syms_free(&ctx, blob); } diff --git a/driver/lib/lib_resolve.c b/driver/lib/lib_resolve.c @@ -1,5 +1,7 @@ #include "lib_resolve.h" +#include <kit/link.h> +#include <stdbool.h> #include <stddef.h> #include <stdint.h> @@ -39,111 +41,35 @@ static char* compose_path(DriverEnv* env, const char* dir, const char* prefix, return buf; } -/* Try one (prefix, suffix) pair across every search dir; return 0 on - * the first hit. Allocations for non-matching candidates are freed - * before the next attempt. */ -static int try_variant(DriverEnv* env, const char* prefix, const char* name, - const char* suffix, const char* const* search_dirs, - uint32_t nsearch_dirs, char** out_path, - size_t* out_size) { - uint32_t i; - for (i = 0; i < nsearch_dirs; ++i) { - size_t bytes; - char* cand = - compose_path(env, search_dirs[i], prefix, name, suffix, &bytes); - if (!cand) return 1; - if (driver_path_exists(cand)) { - *out_path = cand; - *out_size = bytes; - return 0; - } - driver_free(env, cand, bytes); - } - return 1; -} - -/* POSIX-suffix `lib<name><suffix>` convenience wrapper. */ -static int try_suffix(DriverEnv* env, const char* name, const char* suffix, - const char* const* search_dirs, uint32_t nsearch_dirs, - char** out_path, size_t* out_size) { - return try_variant(env, "lib", name, suffix, search_dirs, nsearch_dirs, - out_path, out_size); -} - -static int resolve_posix(DriverEnv* env, const char* name, LibResolveMode mode, - const char* const* search_dirs, uint32_t nsearch_dirs, - char** out_path, size_t* out_size, - LibResolveKind* out_kind) { - /* GNU-ld order: under dynamic mode prefer .so over .a within the - * same search dir. In practice that means we still iterate dirs in - * order, but for each dir try .so first when applicable. To keep - * the implementation simple and match `clang -l` behaviour, we - * iterate suffix-first instead — `.so` is searched across every - * -L dir before falling back to `.a`. The musl/Alpine layout we - * target keeps both side-by-side, so the difference is invisible - * for the cases the harness exercises. */ - if (mode != LIB_RESOLVE_STATIC_ONLY) { - /* Apple .tbd / .dylib first — the macOS SDK ships .tbd stubs in - * place of full .dylib bytes for system libraries. */ - if (try_suffix(env, name, ".tbd", search_dirs, nsearch_dirs, out_path, - out_size) == 0) { - if (out_kind) *out_kind = LIB_RESOLVE_KIND_TBD; - return 0; - } - if (try_suffix(env, name, ".dylib", search_dirs, nsearch_dirs, out_path, - out_size) == 0) { - if (out_kind) *out_kind = LIB_RESOLVE_KIND_SHARED; - return 0; - } - if (try_suffix(env, name, ".so", search_dirs, nsearch_dirs, out_path, - out_size) == 0) { - if (out_kind) *out_kind = LIB_RESOLVE_KIND_SHARED; - return 0; - } - if (mode == LIB_RESOLVE_DYNAMIC_ONLY) return 1; - } - if (try_suffix(env, name, ".a", search_dirs, nsearch_dirs, out_path, - out_size) == 0) { - if (out_kind) *out_kind = LIB_RESOLVE_KIND_ARCHIVE; - return 0; - } - return 1; -} +/* Probe one candidate `<dir>/<prefix><name><suffix>` for kit_lib_resolve: + * compose the path, test existence, and on a hit hand the heap path + size + + * kind back to the resolver and stop the search. Allocation failure also stops + * (flagged via `oom`); a miss frees the candidate and continues. */ +typedef struct LibProbeCtx { + DriverEnv* env; + char** out_path; + size_t* out_size; + LibResolveKind* out_kind; + int oom; +} LibProbeCtx; -static int resolve_windows(DriverEnv* env, const char* name, - LibResolveMode mode, const char* const* search_dirs, - uint32_t nsearch_dirs, char** out_path, - size_t* out_size, LibResolveKind* out_kind) { - /* Windows / mingw layout. Try the mingw-canonical names first - * (lib<n>.dll.a, lib<n>.a) then the MSVC `<n>.lib` / `<n>.dll.a` - * variants. We feed every match to the linker as a static archive - * input — short-form import libraries (lib<n>.dll.a) are AR - * archives whose members are COFF .obj files plus IDATA stubs, so - * the existing archive ingestion path handles them. Long-form - * import libraries are tracked separately (a parallel Windows - * task; not yet wired here). */ - (void)mode; - if (try_variant(env, "lib", name, ".dll.a", search_dirs, nsearch_dirs, - out_path, out_size) == 0) { - if (out_kind) *out_kind = LIB_RESOLVE_KIND_ARCHIVE; - return 0; - } - if (try_variant(env, "lib", name, ".a", search_dirs, nsearch_dirs, out_path, - out_size) == 0) { - if (out_kind) *out_kind = LIB_RESOLVE_KIND_ARCHIVE; - return 0; - } - if (try_variant(env, "", name, ".lib", search_dirs, nsearch_dirs, out_path, - out_size) == 0) { - if (out_kind) *out_kind = LIB_RESOLVE_KIND_ARCHIVE; - return 0; +static bool lib_probe(void* user, const char* dir, const char* prefix, + const char* name, const char* suffix, uint8_t kind) { + LibProbeCtx* c = (LibProbeCtx*)user; + size_t bytes; + char* cand = compose_path(c->env, dir, prefix, name, suffix, &bytes); + if (!cand) { + c->oom = 1; + return true; } - if (try_variant(env, "", name, ".dll.a", search_dirs, nsearch_dirs, out_path, - out_size) == 0) { - if (out_kind) *out_kind = LIB_RESOLVE_KIND_ARCHIVE; - return 0; + if (driver_path_exists(cand)) { + *c->out_path = cand; + *c->out_size = bytes; + if (c->out_kind) *c->out_kind = (LibResolveKind)kind; + return true; } - return 1; + driver_free(c->env, cand, bytes); + return false; } int driver_lib_resolve_for_os(DriverEnv* env, const char* name, @@ -151,13 +77,21 @@ int driver_lib_resolve_for_os(DriverEnv* env, const char* name, const char* const* search_dirs, uint32_t nsearch_dirs, char** out_path, size_t* out_size, LibResolveKind* out_kind) { + LibProbeCtx ctx; if (!env || !name) return 1; - if (os == LIB_RESOLVE_OS_WINDOWS) { - return resolve_windows(env, name, mode, search_dirs, nsearch_dirs, out_path, - out_size, out_kind); + ctx.env = env; + ctx.out_path = out_path; + ctx.out_size = out_size; + ctx.out_kind = out_kind; + ctx.oom = 0; + /* libkit owns the GNU-ld suffix order; lib_probe does the composition and + * filesystem I/O. The LibResolve* enum values match KitLibResolve* exactly, + * so the OS/mode/kind casts are value-preserving. */ + if (kit_lib_resolve((uint8_t)os, (uint8_t)mode, name, search_dirs, + nsearch_dirs, lib_probe, &ctx)) { + return ctx.oom ? 1 : 0; /* stopped on a recorded hit, or on OOM */ } - return resolve_posix(env, name, mode, search_dirs, nsearch_dirs, out_path, - out_size, out_kind); + return 1; /* no candidate matched in any search dir */ } int driver_lib_resolve(DriverEnv* env, const char* name, LibResolveMode mode, diff --git a/driver/lib/link_engine.c b/driver/lib/link_engine.c @@ -1,103 +0,0 @@ -#include "link_engine.h" - -#include <string.h> - -typedef struct DriverPreservedVec { - KitHeap* heap; - KitCgSym* syms; - uint32_t nsyms; - uint32_t cap; - int oom; -} DriverPreservedVec; - -static void driver_preserved_vec_add(void* user, KitCgSym sym) { - DriverPreservedVec* v = (DriverPreservedVec*)user; - KitCgSym* ns; - uint32_t ncap; - if (!v || v->oom) return; - if (v->nsyms == v->cap) { - ncap = v->cap ? v->cap * 2u : 32u; - ns = (KitCgSym*)v->heap->realloc( - v->heap, v->syms, sizeof(*v->syms) * v->cap, sizeof(*v->syms) * ncap, - _Alignof(KitCgSym)); - if (!ns) { - v->oom = 1; - return; - } - v->syms = ns; - v->cap = ncap; - } - v->syms[v->nsyms++] = sym; -} - -static KitStatus driver_link_engine_add_inputs(KitLinkSession* link, - const DriverLinkInputs* in) { - KitStatus st = KIT_OK; - uint32_t i; - if (!link || !in) return KIT_INVALID; - - for (i = 0; i < in->norder && st == KIT_OK; ++i) { - const KitLinkInputOrder* ord = &in->order[i]; - switch ((KitLinkInputOrderKind)ord->kind) { - case KIT_LINK_INPUT_OBJ: - st = kit_link_session_add_obj(link, in->objs[ord->index]); - break; - case KIT_LINK_INPUT_OBJ_BYTES: - st = kit_link_session_add_obj_bytes(link, in->obj_names[ord->index], - &in->obj_bytes[ord->index]); - break; - case KIT_LINK_INPUT_ARCHIVE: - st = - kit_link_session_add_archive_bytes(link, &in->archives[ord->index]); - break; - case KIT_LINK_INPUT_DSO: - st = kit_link_session_add_dso_bytes(link, in->dso_names[ord->index], - &in->dso_bytes[ord->index]); - break; - } - } - return st; -} - -KitStatus driver_link_engine_emit_with_lto( - KitCompiler* compiler, const KitLinkSessionOptions* lopts, - const DriverLinkInputs* in, DriverCompilePendingLto* pending_lto, - const DriverCompileBatchOptions* batch, KitWriter* out) { - KitLinkSession* link = NULL; - DriverPreservedVec preserved; - KitStatus st; - - if (!compiler || !lopts || !in || !out) { - if (pending_lto && pending_lto->active) - driver_compile_pending_lto_abort(pending_lto); - return KIT_INVALID; - } - memset(&preserved, 0, sizeof preserved); - preserved.heap = kit_compiler_context(compiler)->heap; - st = kit_link_session_new(compiler, lopts, &link); - if (st == KIT_OK) st = driver_link_engine_add_inputs(link, in); - if (st == KIT_OK && pending_lto && pending_lto->active) { - st = kit_link_session_visit_lto_preserved( - link, pending_lto->obj, pending_lto->cg, driver_preserved_vec_add, - &preserved); - if (st == KIT_OK && preserved.oom) st = KIT_NOMEM; - if (st == KIT_OK) { - st = driver_compile_pending_lto_finish(pending_lto, batch, preserved.syms, - preserved.nsyms); - } - } - if (st == KIT_OK) st = kit_link_session_emit(link, out); - kit_link_session_free(link); - if (preserved.syms) - preserved.heap->free(preserved.heap, preserved.syms, - sizeof(*preserved.syms) * preserved.cap); - if (st != KIT_OK && pending_lto && pending_lto->active) - driver_compile_pending_lto_abort(pending_lto); - return st; -} - -KitStatus driver_link_engine_emit(KitCompiler* compiler, - const KitLinkSessionOptions* lopts, - const DriverLinkInputs* in, KitWriter* out) { - return driver_link_engine_emit_with_lto(compiler, lopts, in, NULL, NULL, out); -} diff --git a/driver/lib/link_engine.h b/driver/lib/link_engine.h @@ -1,53 +0,0 @@ -#ifndef KIT_DRIVER_LINK_ENGINE_H -#define KIT_DRIVER_LINK_ENGINE_H - -#include <kit/compile.h> -#include <kit/link.h> -#include <kit/object.h> - -#include "compile_engine.h" - -/* Reusable "build a link session, add inputs in command-line order, and emit" - * step shared by `cc` and the `build-*` commands. Every input is already - * loaded/compiled by the caller; path lookup, option parsing, hosted/runtime - * wiring, and the output writer's lifetime stay caller responsibilities. - * - * `order` drives the add sequence: each entry's (kind, index) selects one - * element of the parallel arrays below, so link order matches the command - * line exactly. - * KIT_LINK_INPUT_OBJ -> objs[index] (in-memory builder; borrowed) - * KIT_LINK_INPUT_OBJ_BYTES -> obj_bytes[index], labelled obj_names[index] - * KIT_LINK_INPUT_ARCHIVE -> archives[index] - * KIT_LINK_INPUT_DSO -> dso_bytes[index], labelled dso_names[index] - * - * Arrays whose count is zero may be NULL. */ -typedef struct DriverLinkInputs { - KitObjBuilder* const* objs; - uint32_t nobjs; - const KitSlice* obj_names; - const KitSlice* obj_bytes; - uint32_t nobj_bytes; - const KitLinkArchiveInput* archives; - uint32_t narchives; - const KitSlice* dso_names; - const KitSlice* dso_bytes; - uint32_t ndsos; - const KitLinkInputOrder* order; - uint32_t norder; -} DriverLinkInputs; - -/* Open a session with `lopts`, add every input named by `in->order`, and emit - * to `out`. Returns the resulting KitStatus (KIT_OK on success). The session is - * always freed before return; `out` is neither opened nor closed here. - * In-memory builders are borrowed for the duration of the session; callers - * retain ownership and must free them after this returns. */ -KitStatus driver_link_engine_emit(KitCompiler* compiler, - const KitLinkSessionOptions* lopts, - const DriverLinkInputs* in, KitWriter* out); - -KitStatus driver_link_engine_emit_with_lto( - KitCompiler* compiler, const KitLinkSessionOptions* lopts, - const DriverLinkInputs* in, DriverCompilePendingLto* pending_lto, - const DriverCompileBatchOptions* batch, KitWriter* out); - -#endif diff --git a/driver/lib/link_inputs.c b/driver/lib/link_inputs.c @@ -1,5 +1,6 @@ #include "link_inputs.h" +#include <kit/compile.h> #include <kit/core.h> #include "lib_resolve.h" @@ -56,8 +57,7 @@ void driver_link_inputs_fini(DriverLinkInputSet* set) { } int driver_is_dso_path(const char* s) { - return driver_has_suffix(s, ".so") || driver_has_suffix(s, ".dylib") || - driver_has_suffix(s, ".tbd"); + return kit_input_kind_for_path(NULL, s) == KIT_INPUT_DSO; } void driver_link_inputs_push(DriverLinkInputSet* set, uint8_t kind, diff --git a/driver/lib/objedit.c b/driver/lib/objedit.c @@ -1,7 +1,5 @@ #include "objedit.h" -#include <string.h> - int driver_name_in_list(KitSlice name, const char* const* list, uint32_t n) { uint32_t i; if (!name.len) return 0; @@ -19,54 +17,18 @@ int driver_obj_id_in_set(KitObjSymbol id, const KitObjSymbol* arr, uint32_t n) { return 0; } +/* Thin wrapper over kit_obj_reloc_live_symbols (the debug-aware reloc-target + * scan now lives in libkit). libkit returns an exactly-sized array from the + * same heap driver_free uses, so *cap_out is reported as *n_out to keep the + * caller's driver_free(arr, cap * sizeof) contract valid. */ int driver_obj_collect_reloc_target_syms(DriverEnv* env, const char* tool, KitObjFile* of, KitObjSymbol** out, uint32_t* n_out, uint32_t* cap_out) { - KitObjRelocIter* rit = NULL; - KitObjSymbol* arr = NULL; - uint32_t n = 0, cap = 0; - - if (kit_obj_reliter_new(of, &rit) != KIT_OK) { + KitContext ctx = driver_env_to_context(env); + if (kit_obj_reloc_live_symbols(&ctx, of, out, n_out) != KIT_OK) { driver_errf(tool, "out of memory"); return 1; } - for (;;) { - KitObjReloc r; - KitIterResult ir = kit_obj_reliter_next(rit, &r); - if (ir != KIT_ITER_ITEM) break; - if (r.sym == KIT_OBJ_SYMBOL_NONE) continue; - /* Skip relocs hosted in a debug section -- that section is being - * dropped, so its relocs don't actually "need" their targets. */ - if (r.section != KIT_SECTION_NONE) { - KitObjSecInfo hi; - if (kit_obj_section(of, r.section, &hi) == KIT_OK && - hi.kind == KIT_SEC_DEBUG) { - continue; - } - } - if (driver_obj_id_in_set(r.sym, arr, n)) continue; - if (n >= cap) { - uint32_t newcap = cap ? cap * 2u : 32u; - KitObjSymbol* nb = - (KitObjSymbol*)driver_alloc_zeroed(env, (size_t)newcap * sizeof(*nb)); - if (!nb) { - kit_obj_reliter_free(rit); - if (arr) driver_free(env, arr, (size_t)cap * sizeof(*arr)); - driver_errf(tool, "out of memory"); - return 1; - } - if (arr) { - memcpy(nb, arr, (size_t)n * sizeof(*arr)); - driver_free(env, arr, (size_t)cap * sizeof(*arr)); - } - arr = nb; - cap = newcap; - } - arr[n++] = r.sym; - } - kit_obj_reliter_free(rit); - *out = arr; - *n_out = n; - *cap_out = cap; + *cap_out = *n_out; return 0; } diff --git a/driver/lib/objedit.h b/driver/lib/objedit.h @@ -22,10 +22,11 @@ int driver_obj_id_in_set(KitObjSymbol id, const KitObjSymbol* arr, uint32_t n); /* Collect (deduplicated) the symbol ids targeted by any relocation whose * containing section will survive emit -- relocs hosted in KIT_SEC_DEBUG * sections are skipped, since those sections are about to be dropped and so do - * not actually keep their targets alive. On success *out owns a freshly - * allocated array of *n ids with capacity *cap (free with driver_free over - * *cap * sizeof(KitObjSymbol)); on failure emits an error tagged `tool` and - * returns nonzero. Returns 0 on success. */ + * not actually keep their targets alive. Thin wrapper over the libkit + * kit_obj_reloc_live_symbols scan: *out owns an exactly-sized array of *n ids, + * and *cap is reported equal to *n so the caller's driver_free(*out, + * *cap * sizeof(KitObjSymbol)) frees the exact allocation. On failure emits an + * error tagged `tool` and returns nonzero. Returns 0 on success. */ int driver_obj_collect_reloc_target_syms(DriverEnv* env, const char* tool, KitObjFile* of, KitObjSymbol** out, uint32_t* n, uint32_t* cap); diff --git a/driver/lib/target.c b/driver/lib/target.c @@ -3,122 +3,47 @@ #include <stdio.h> #include <string.h> -#include "driver.h" - -/* Pure target-triple parsing. No host I/O — just string walking — so this - * lives outside driver/env.c (which is the syscall/host-state abstraction - * layer). */ +#include <kit/target.h> -static int triple_tok_eq(const char* s, size_t n, const char* lit) { - size_t l = kit_slice_cstr(lit).len; - return n == l && memcmp(s, lit, n) == 0; -} - -/* Prefix match for OS tokens that carry a trailing version, e.g. clang emits - * "freebsd15.0" / "freebsd14" rather than a bare "freebsd". */ -static int triple_tok_prefix(const char* s, size_t n, const char* lit) { - size_t l = kit_slice_cstr(lit).len; - return n >= l && memcmp(s, lit, l) == 0; -} +#include "driver.h" -/* Recognize an architecture token, the single authority for the arch-name - * spellings the driver accepts. Writes arch + natural pointer size on a hit. - * Returns 0 on success, nonzero for an unrecognized token. Shared by the - * triple parser and the public driver_arch_from_name. */ -static int arch_from_tok(const char* s, size_t n, KitArchKind* arch_out, - uint8_t* ptr_size_out) { - KitArchKind arch; - uint8_t ptr_size; - if (triple_tok_eq(s, n, "x86_64") || triple_tok_eq(s, n, "amd64") || - triple_tok_eq(s, n, "x64")) { - arch = KIT_ARCH_X86_64; - ptr_size = 8; - } else if (triple_tok_eq(s, n, "i386") || triple_tok_eq(s, n, "i486") || - triple_tok_eq(s, n, "i586") || triple_tok_eq(s, n, "i686")) { - arch = KIT_ARCH_X86_32; - ptr_size = 4; - } else if (triple_tok_eq(s, n, "aarch64") || triple_tok_eq(s, n, "arm64") || - triple_tok_eq(s, n, "aa64")) { - arch = KIT_ARCH_ARM_64; - ptr_size = 8; - } else if (triple_tok_eq(s, n, "arm") || triple_tok_eq(s, n, "armv7")) { - arch = KIT_ARCH_ARM_32; - ptr_size = 4; - } else if (triple_tok_eq(s, n, "riscv64") || triple_tok_eq(s, n, "rv64")) { - arch = KIT_ARCH_RV64; - ptr_size = 8; - } else if (triple_tok_eq(s, n, "riscv32") || triple_tok_eq(s, n, "rv32")) { - arch = KIT_ARCH_RV32; - ptr_size = 4; - } else if (triple_tok_eq(s, n, "wasm32")) { - arch = KIT_ARCH_WASM; - ptr_size = 4; - } else if (triple_tok_eq(s, n, "wasm64")) { - arch = KIT_ARCH_WASM; - ptr_size = 8; - } else { - return 1; - } - if (arch_out) *arch_out = arch; - if (ptr_size_out) *ptr_size_out = ptr_size; - return 0; -} +/* Target-triple parsing and the spec-derived target defaults now live in the + * public libkit API (<kit/target.h>, src/api/target.c). The functions below are + * thin driver-side wrappers that preserve the historical driver_* signatures + * (and the driver's int-status / diagnostic conventions) so every CLI caller is + * unaffected. The CLI feature-parsing policy (-march / -mattr / -mcpu / -m<feat> + * → DriverTargetFeatures) stays here, since it consumes argv and emits + * diagnostics. */ int driver_arch_from_name(const char* name, KitArchKind* arch_out, uint8_t* ptr_size_out) { - if (!name) return 1; - return arch_from_tok(name, kit_slice_cstr(name).len, arch_out, ptr_size_out); + return kit_arch_from_name(name, arch_out, ptr_size_out) ? 0 : 1; } KitPic driver_default_pic(KitObjFmt obj, KitOSKind os) { - /* WASM has no PIC/PIE concept; freestanding targets have no dynamic - * loader to apply load-time relocations. Everything else is hosted and - * defaults to PIE. */ - if (obj == KIT_OBJ_WASM) return KIT_PIC_NONE; - if (os == KIT_OS_FREESTANDING) return KIT_PIC_NONE; - return KIT_PIC_PIE; + return kit_target_default_pic(obj, os); } int driver_link_pie(KitTargetSpec target, int explicit_pie, int shared, int relocatable) { - if (explicit_pie) return 1; - if (shared || relocatable) return 0; - return target.pic == KIT_PIC_PIE; + return kit_target_link_pie(target, explicit_pie, shared, relocatable); } const char* driver_default_exe_name(KitTargetSpec target) { - /* PE/COFF executables conventionally carry a `.exe` suffix; ELF/Mach-O - * default link output is the historical `a.out`. */ - return target.os == KIT_OS_WINDOWS ? "a.exe" : "a.out"; + return kit_target_default_exe_name(target); } void driver_default_obj_ext(KitTargetSpec target, const char** ext_out, size_t* ext_len_out) { - /* Windows targets default to a `.obj` suffix; everyone else `.o`. Drivers - * accept both spellings as inputs, but tooling that scrapes default outputs - * expects the canonical platform extension. */ - if (target.os == KIT_OS_WINDOWS) { - if (ext_out) *ext_out = ".obj"; - if (ext_len_out) *ext_len_out = 4u; - } else { - if (ext_out) *ext_out = ".o"; - if (ext_len_out) *ext_len_out = 2u; - } + kit_target_default_obj_ext(target, ext_out, ext_len_out); } int driver_target_needs_sysroot_libdir(KitTargetSpec target) { - /* Windows targets fold `<sysroot>/lib` into the library search path (the - * mingw import-library tree). The POSIX hosted profiles enumerate their - * libdirs through the hosted resolver instead. */ - return target.os == KIT_OS_WINDOWS ? 1 : 0; + return kit_target_needs_sysroot_libdir(target); } int driver_target_default_hosted_profile(KitTargetSpec target) { - /* Windows-COFF is the one target whose hosted libc profile is engaged by - * default (given a sysroot and no -nostdlib): the mingw/ucrt import - * libraries are mandatory to produce a runnable PE. Other targets stay - * freestanding unless the user opts in (-lc / explicit sysroot wiring). */ - return target.os == KIT_OS_WINDOWS && target.obj == KIT_OBJ_COFF ? 1 : 0; + return kit_target_default_hosted_profile(target); } static int target_features_grow(DriverTargetFeatures* tf) { @@ -332,175 +257,21 @@ KitStatus driver_target_new(const KitContext* ctx, KitTargetSpec target, } int driver_target_from_triple(const char* triple, KitTargetSpec* out) { - const char* parts[4]; - size_t plen[4]; - int np = 0; - const char* p; - KitTargetSpec t; - int os_set; - int i; - - if (!triple || !out) return 1; - memset(&t, 0, sizeof(t)); - - p = triple; - while (np < 4) { - const char* dash = driver_strchr(p, '-'); - parts[np] = p; - plen[np] = dash ? (size_t)(dash - p) : kit_slice_cstr(p).len; - if (plen[np] == 0) return 1; - np++; - if (!dash) break; - p = dash + 1; - } - - if (arch_from_tok(parts[0], plen[0], &t.arch, &t.ptr_size) != 0) return 1; - - os_set = 0; - for (i = 1; i < np; ++i) { - if (triple_tok_eq(parts[i], plen[i], "linux")) { - t.os = KIT_OS_LINUX; - t.obj = KIT_OBJ_ELF; - os_set = 1; - break; - } - if (triple_tok_eq(parts[i], plen[i], "darwin") || - triple_tok_eq(parts[i], plen[i], "macos")) { - t.os = KIT_OS_MACOS; - t.obj = KIT_OBJ_MACHO; - os_set = 1; - break; - } - if (triple_tok_eq(parts[i], plen[i], "windows") || - triple_tok_eq(parts[i], plen[i], "win32")) { - t.os = KIT_OS_WINDOWS; - t.obj = KIT_OBJ_COFF; - os_set = 1; - break; - } - if (triple_tok_prefix(parts[i], plen[i], "freebsd")) { - const char* ver = parts[i] + 7; /* skip "freebsd" */ - size_t rem = plen[i] - 7; - unsigned v = 0; - size_t j; - for (j = 0; j < rem && ver[j] >= '0' && ver[j] <= '9'; ++j) - v = v * 10 + (unsigned)(ver[j] - '0'); - t.os = KIT_OS_FREEBSD; - t.obj = KIT_OBJ_ELF; - t.os_version_major = (uint8_t)(v > 255 ? 0 : v); - os_set = 1; - break; - } - if (triple_tok_eq(parts[i], plen[i], "wasi")) { - t.os = KIT_OS_WASI; - t.obj = KIT_OBJ_WASM; - os_set = 1; - break; - } - if (triple_tok_eq(parts[i], plen[i], "none") || - triple_tok_eq(parts[i], plen[i], "freestanding")) { - t.os = KIT_OS_FREESTANDING; - t.obj = (t.arch == KIT_ARCH_WASM) ? KIT_OBJ_WASM : KIT_OBJ_ELF; - os_set = 1; - break; - } - } - if (!os_set) { - /* No recognized OS token. This deliberately also covers bare-metal and - * vendor-only triples that carry no OS at all -- e.g. "x86_64", - * "aarch64-unknown", "riscv64-unknown-elf" -- which must keep resolving to - * a freestanding ELF/WASM target. We cannot tell an unrecognized OS token - * (e.g. "x86_64-pc-frobnicate") apart from those without a vendor/env token - * table, so erroring here would regress valid bare-metal triples. Left as a - * silent freestanding default by design; see findings note for B15. */ - t.os = KIT_OS_FREESTANDING; - t.obj = (t.arch == KIT_ARCH_WASM) ? KIT_OBJ_WASM : KIT_OBJ_ELF; - } - - t.ptr_align = t.ptr_size; - t.big_endian = 0; - t.pic = driver_default_pic(t.obj, t.os); - t.code_model = KIT_CM_DEFAULT; - - *out = t; - return 0; + return kit_target_from_triple(triple, out) ? 0 : 1; } int driver_target_to_triple(KitTargetSpec target, char* buf, size_t cap) { - const char* arch; - const char* os; - int n; - if (!buf || cap == 0) return 1; - - switch (target.arch) { - case KIT_ARCH_X86_64: - arch = "x86_64"; - break; - case KIT_ARCH_X86_32: - arch = "i386"; - break; - case KIT_ARCH_ARM_64: - arch = "aarch64"; - break; - case KIT_ARCH_ARM_32: - arch = "arm"; - break; - case KIT_ARCH_RV64: - arch = "riscv64"; - break; - case KIT_ARCH_RV32: - arch = "riscv32"; - break; - case KIT_ARCH_WASM: - arch = target.ptr_size == 8 ? "wasm64" : "wasm32"; - break; - default: - arch = "unknown"; - break; - } - - switch (target.os) { - case KIT_OS_LINUX: - os = "linux"; - break; - case KIT_OS_MACOS: - os = "apple-darwin"; - break; - case KIT_OS_WINDOWS: - os = "windows"; - break; - case KIT_OS_FREEBSD: - os = "freebsd"; - break; - case KIT_OS_WASI: - os = "wasi"; - break; - case KIT_OS_FREESTANDING: - default: - os = "elf"; - break; - } - - n = snprintf(buf, cap, "%.*s-%.*s", KIT_SLICE_ARG(kit_slice_cstr(arch)), - KIT_SLICE_ARG(kit_slice_cstr(os))); - return n < 0 || (size_t)n >= cap; + return kit_target_to_triple(target, buf, cap) ? 0 : 1; } int driver_record_mcmodel(KitTargetSpec* target, const char* tool, const char* val) { - if (driver_streq(val, "small") || driver_streq(val, "medlow")) { - target->code_model = KIT_CM_SMALL; - return 0; - } - if (driver_streq(val, "medium") || driver_streq(val, "medany")) { - target->code_model = KIT_CM_MEDIUM; - return 0; - } - if (driver_streq(val, "large")) { - target->code_model = KIT_CM_LARGE; - return 0; + KitCodeModel cm; + if (!kit_target_code_model_from_name(val, &cm)) { + driver_errf(tool, "unknown -mcmodel value: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(val))); + return 1; } - driver_errf(tool, "unknown -mcmodel value: %.*s", - KIT_SLICE_ARG(kit_slice_cstr(val))); - return 1; + if (target) target->code_model = cm; + return 0; } diff --git a/include/kit/archive.h b/include/kit/archive.h @@ -47,4 +47,43 @@ KIT_API KitStatus kit_ar_iter_new(const KitContext*, const KitSlice* archive, KIT_API KitIterResult kit_ar_iter_next(KitArIter*, KitArMember* out); KIT_API void kit_ar_iter_free(KitArIter*); +/* ============================================================ + * Symbol-index automation + * ============================================================ + * + * The archive symbol index records, for each member, the linker-visible + * global symbols that member defines so a static library links without a + * separate scan. The helpers below own the "which symbols a linker indexes" + * policy (bind == GLOBAL and a defining section) and the "serialize builders, + * compute the index, write the archive" pipeline, so the ar / ranlib / strip + * tools no longer hand-roll the collect-and-pack dance. */ + +/* Collect one object's linker-visible global symbols (KitSymBind == GLOBAL + * with a defining section, non-empty name) into *out_syms. On success + * *out_owned receives a single heap block backing both the KitSlice array and + * the NUL-separated name bytes; free it with kit_obj_global_syms_free. When + * `obj_bytes` is not a recognized object, *out_syms is the empty set + * (count 0, *out_owned NULL) and KIT_OK is returned — non-object members + * contribute no symbols. */ +KIT_API KitStatus kit_obj_global_syms(const KitContext*, + const KitSlice* obj_bytes, + KitArMemberSymbols* out_syms, + void** out_owned); +KIT_API void kit_obj_global_syms_free(const KitContext*, void* owned); + +/* Serialize each builder to bytes, auto-compute the per-member symbol index, + * and write a long-names + symbol-index archive. objs[i] is written under + * member name names[i]; `epoch` seeds reproducible member timestamps. The + * builders are emitted, not freed; `out` is neither opened nor closed. */ +KIT_API KitStatus kit_ar_write_objs(const KitContext*, KitObjBuilder* const* objs, + const KitSlice* names, uint32_t n, + uint64_t epoch, KitWriter* out); + +/* Read an archive's members and rewrite it with a freshly-computed symbol + * index, preserving member names, contents, and order (this is `ranlib`). + * `epoch` seeds reproducible member timestamps; `out` is neither opened nor + * closed. */ +KIT_API KitStatus kit_ar_reindex(const KitContext*, const KitSlice* archive_bytes, + uint64_t epoch, KitWriter* out); + #endif diff --git a/include/kit/build.h b/include/kit/build.h @@ -0,0 +1,150 @@ +#ifndef KIT_BUILD_H +#define KIT_BUILD_H + +#include <kit/cg.h> +#include <kit/compile.h> +#include <kit/link.h> +#include <kit/object.h> +#include <kit/preprocess.h> +#include <stdint.h> + +/* + * Build orchestration API. + * + * A higher-level "build" tier layered over kit/compile.h + kit/link.h: the + * batch-compile and ordered-link steps a cc/build-like driver repeats verbatim. + * Each entry is written purely against the public compile/link/cg/object + * surface; path lookup, option parsing, hosted/runtime wiring, and writer + * lifetimes stay caller responsibilities. + */ + +/* Language-neutral "compile one source" step. + * + * Builds a KitCompileSession for `lang`, compiles `bytes` (labelled `name`), + * and then either: + * - returns the object builder via `obj_out` (caller owns it; used by the + * link and check paths), or + * - emits the result to `emit_out`, routed by code->emit_*: object bytes by + * default, `.s` assembly for emit_asm_source, or — for emit_c_source / + * emit_ir — the writer is wired onto the CG and the builder is dropped. + * Exactly one of `emit_out` / `obj_out` must be non-NULL. + * + * code, diagnostics : common per-compile settings (required). + * pp : preprocessor settings, applied to preprocessor-enabled + * frontends; NULL for none. + * lang_extra : opaque per-frontend options for language_options + * (e.g. KitWasmCompileOptions*); NULL when the frontend + * has none. + * + * Returns the compile/emit KitStatus. */ +KIT_API KitStatus kit_build_compile_one(KitCompiler* compiler, KitLanguage lang, + const KitCodeOptions* code, + const KitDiagnosticOptions* diagnostics, + const KitPreprocessOptions* pp, + const void* lang_extra, KitSlice name, + const KitSlice* bytes, + KitWriter* emit_out, + KitObjBuilder** obj_out); + +typedef struct KitBuildSource { + KitLanguage lang; + KitSlice name; + KitSlice bytes; + const KitPreprocessOptions* pp; + const void* lang_extra; +} KitBuildSource; + +typedef struct KitBuildObjects { + /* Caller-allocated capacity for at least nsources objects. Filled compactly. + */ + KitObjBuilder** objs; + uint32_t nobjs; + /* Caller-allocated nsources-entry maps. source_obj_index[i] is the compact + * object index for source i. source_order_keep[i] is true only for the source + * position that should contribute an order/archive member; later semantic LTO + * sources map to the same object and have keep=false. */ + uint32_t* source_obj_index; + uint8_t* source_order_keep; + struct KitBuildPendingLto* pending_lto; +} KitBuildObjects; + +typedef struct KitBuildBatchOptions { + uint8_t output_kind; /* KitCgOutputKind */ + uint8_t interposition_policy; /* KitCgInterpositionPolicy */ + uint8_t defer_lto_finish; + uint8_t pad[1]; +} KitBuildBatchOptions; + +typedef struct KitBuildPendingLto { + KitObjBuilder* obj; + KitCg* cg; + uint32_t obj_index; + uint8_t active; + uint8_t pad[3]; +} KitBuildPendingLto; + +/* Compile a batch of sources for a link/archive/relocatable output. When + * code->lto is set, KIT_FRONTEND_LTO_CG frontends emit into one shared KitCg + * unit and KIT_FRONTEND_LTO_OPAQUE/NONE frontends still compile as ordinary + * per-source objects. */ +KIT_API KitStatus kit_build_compile(KitCompiler* compiler, + const KitCodeOptions* code, + const KitDiagnosticOptions* diagnostics, + const KitBuildSource* sources, + uint32_t nsources, + const KitBuildBatchOptions* batch, + KitBuildObjects* out); + +KIT_API KitStatus kit_build_lto_finish(KitBuildPendingLto* pending, + const KitBuildBatchOptions* batch, + const KitCgSym* preserved_symbols, + uint32_t npreserved_symbols); + +KIT_API void kit_build_lto_abort(KitBuildPendingLto* pending); + +/* Reusable "build a link session, add inputs in command-line order, and emit" + * step. Every input is already loaded/compiled by the caller; path lookup, + * option parsing, hosted/runtime wiring, and the output writer's lifetime stay + * caller responsibilities. + * + * `order` drives the add sequence: each entry's (kind, index) selects one + * element of the parallel arrays below, so link order matches the command + * line exactly. + * KIT_LINK_INPUT_OBJ -> objs[index] (in-memory builder; borrowed) + * KIT_LINK_INPUT_OBJ_BYTES -> obj_bytes[index], labelled obj_names[index] + * KIT_LINK_INPUT_ARCHIVE -> archives[index] + * KIT_LINK_INPUT_DSO -> dso_bytes[index], labelled dso_names[index] + * + * Arrays whose count is zero may be NULL. */ +typedef struct KitLinkInputs { + KitObjBuilder* const* objs; + uint32_t nobjs; + const KitSlice* obj_names; + const KitSlice* obj_bytes; + uint32_t nobj_bytes; + const KitLinkArchiveInput* archives; + uint32_t narchives; + const KitSlice* dso_names; + const KitSlice* dso_bytes; + uint32_t ndsos; + const KitLinkInputOrder* order; + uint32_t norder; +} KitLinkInputs; + +/* Open a session with `lopts`, add every input named by `in->order`, and emit + * to `out`. Returns the resulting KitStatus (KIT_OK on success). The session is + * always freed before return; `out` is neither opened nor closed here. + * In-memory builders are borrowed for the duration of the session; callers + * retain ownership and must free them after this returns. */ +KIT_API KitStatus kit_build_link(KitCompiler* compiler, + const KitLinkSessionOptions* lopts, + const KitLinkInputs* in, KitWriter* out); + +KIT_API KitStatus kit_build_link_with_lto(KitCompiler* compiler, + const KitLinkSessionOptions* lopts, + const KitLinkInputs* in, + KitBuildPendingLto* pending_lto, + const KitBuildBatchOptions* batch, + KitWriter* out); + +#endif diff --git a/include/kit/compile.h b/include/kit/compile.h @@ -233,6 +233,24 @@ KIT_API const char* kit_language_name(KitCompiler*, KitLanguage); * registry with a NULL compiler). Accepts a NULL compiler (see note above). */ KIT_API const char* kit_language_default_extension(KitCompiler*, KitLanguage); +/* Coarse classification of a driver input path by extension: a source (some + * registered frontend claims its extension, per kit_language_for_path), a + * relocatable object (.o/.obj), a static archive (.a), a shared/dynamic + * library (.so/.dylib/.tbd), or unrecognized. A cc/ld-like driver uses this to + * route a positional argument to the right input slot. The object/archive/dso + * suffix tests are case-sensitive; source detection follows the + * case-insensitive extension registry. Accepts a NULL compiler (see the + * language resolvers above). */ +typedef enum KitInputKind { + KIT_INPUT_UNKNOWN = 0, + KIT_INPUT_SOURCE, + KIT_INPUT_OBJECT, + KIT_INPUT_ARCHIVE, + KIT_INPUT_DSO, +} KitInputKind; + +KIT_API KitInputKind kit_input_kind_for_path(KitCompiler*, const char* path); + KIT_API KitStatus kit_register_frontend(KitCompiler*, KitLanguage, const KitFrontendVTable*); diff --git a/include/kit/dwarf.h b/include/kit/dwarf.h @@ -37,6 +37,29 @@ KIT_API KitStatus kit_dwarf_func_at(KitDebugInfo*, uint64_t pc, KitSlice* name_out, uint64_t* low_pc_out, uint64_t* high_pc_out); +/* One address resolved to its enclosing function plus file:line:col, the + * combined query a symbolizer (addr2line / backtrace annotation) wants per + * frame. `have_func` / `have_line` say which halves are valid; an unresolved + * query leaves both clear (and the slices empty). `func`, `file`, `line`, and + * `col` mirror the kit_dwarf_func_at / kit_dwarf_addr_to_line outputs. */ +typedef struct KitDwarfResolve { + int have_func; + KitSlice func; + int have_line; + KitSlice file; + uint32_t line; + uint32_t col; +} KitDwarfResolve; + +/* Resolve one image-relative address by running kit_dwarf_addr_to_line and + * (when `want_func`) kit_dwarf_func_at, packing both into `out`. `want_func` + * gates the separate function-name query so callers that never print a name + * skip the work. Always returns KIT_OK for a usable reader (a miss is reported + * via the cleared have_* flags, not an error); KIT_INVALID if `d`/`out` is + * null. Addresses are image-relative, matching the kit_dwarf_* contract. */ +KIT_API KitStatus kit_dwarf_resolve(KitDebugInfo*, uint64_t addr, int want_func, + KitDwarfResolve* out); + typedef struct KitDwarfSubprogram { KitSlice name; uint64_t low_pc; @@ -186,6 +209,15 @@ KIT_API void kit_dwarf_param_iter_free(KitDwarfParamIter*); * caller's job. All iterators borrow from the KitDebugInfo and are * invalidated when it is freed. */ +/* Canonical symbolic names for the numeric DWARF codes the iterators above + * return, so every DWARF dumper shares one table. Return the spelled-out + * constant (e.g. "DW_TAG_subprogram") for a known code, or NULL for an + * unrecognized one — leaving the unknown-code fallback (hex, "unknown 0x..", + * etc.) to the caller's formatting. */ +KIT_API const char* kit_dwarf_tag_name(uint32_t tag); +KIT_API const char* kit_dwarf_attr_name(uint32_t attr); +KIT_API const char* kit_dwarf_form_name(uint32_t form); + /* .debug_info compilation-unit headers, in section order. */ typedef struct KitDwarfCuIter KitDwarfCuIter; typedef struct KitDwarfCu { diff --git a/include/kit/link.h b/include/kit/link.h @@ -133,6 +133,48 @@ typedef struct KitLinkArchiveInput { uint8_t pad; } KitLinkArchiveInput; +/* Library (-l<name>) resolution policy. + * + * kit_lib_resolve owns only the GNU-ld suffix search ORDER for a target OS and + * link mode; it performs no filesystem access and no allocation. The host + * supplies a probe that composes <dir>/<prefix><name><suffix> however its path + * rules require (separator choice, etc.), tests for existence, and on a hit + * records the result and returns true to stop the search. This keeps the + * ordering policy in libkit while path I/O stays a driver responsibility. */ +typedef enum KitLibResolveMode { + KIT_LIB_RESOLVE_STATIC_ONLY, + KIT_LIB_RESOLVE_DYNAMIC_PREFER, /* shared (.so/.dylib/.tbd) first, then .a */ + KIT_LIB_RESOLVE_DYNAMIC_ONLY, /* shared only, never .a */ +} KitLibResolveMode; + +typedef enum KitLibResolveKind { + KIT_LIB_RESOLVE_KIND_ARCHIVE = 0, + KIT_LIB_RESOLVE_KIND_SHARED = 1, + KIT_LIB_RESOLVE_KIND_TBD = 2, /* Apple .tbd text stub; routes like SHARED */ +} KitLibResolveKind; + +typedef enum KitLibResolveOS { + KIT_LIB_RESOLVE_OS_POSIX = 0, /* lib<name>.{tbd,dylib,so,a} */ + KIT_LIB_RESOLVE_OS_WINDOWS = 1, /* lib<name>.{dll.a,a}, <name>.{lib,dll.a} */ +} KitLibResolveOS; + +/* Probe one candidate. `dir` is one search directory (empty means the current + * directory); the leaf is <prefix><name><suffix>. `kind` is the + * KitLibResolveKind the matched suffix maps to. Return true to stop the search + * (a hit the host recorded, or a fatal error the host wants to surface); + * return false to keep searching. */ +typedef bool (*KitLibResolveProbe)(void* user, const char* dir, + const char* prefix, const char* name, + const char* suffix, uint8_t kind); + +/* Walk the GNU-ld suffix order for (os, mode), invoking `probe` for every + * (suffix-variant, search-dir) pair, suffix-major: each suffix is tried across + * all dirs before the next suffix. Returns true if a probe returned true. */ +KIT_API bool kit_lib_resolve(uint8_t os, uint8_t mode, const char* name, + const char* const* search_dirs, + uint32_t nsearch_dirs, KitLibResolveProbe probe, + void* user); + typedef enum KitLinkInputOrderKind { KIT_LINK_INPUT_OBJ, KIT_LINK_INPUT_OBJ_BYTES, diff --git a/include/kit/object.h b/include/kit/object.h @@ -223,6 +223,30 @@ KIT_API KitStatus kit_obj_builder_section_replace_bytes(KitObjBuilder*, const void* data, size_t n); +/* Debug-aware strip pass shared by `strip` and `objcopy --strip-*`. + * + * KIT_STRIP_DEBUG drop sections whose kind is KIT_SEC_DEBUG. + * KIT_STRIP_UNNEEDED drop debug sections, then prune symbols not in the + * needed set (see below). + * KIT_STRIP_ALL same symbol policy as UNNEEDED (the needed set already + * retains everything a relocatable .o must keep linkable). + * + * The needed set is: undefined externals (so the object stays linkable) plus + * every symbol still targeted by a relocation hosted in a surviving (non-debug) + * section. `keep_names` force-keeps the listed symbols even when the policy + * would drop them; names not present are ignored. Mutations are issued against + * the builder and the emit-time sweep handles cascades (orphan relocs against + * removed sections, dropped group memberships, ...). */ +typedef enum KitObjStripLevel { + KIT_STRIP_DEBUG, + KIT_STRIP_UNNEEDED, + KIT_STRIP_ALL, +} KitObjStripLevel; + +KIT_API KitStatus kit_obj_builder_strip(KitObjBuilder*, int level, + const KitSlice* keep_names, + uint32_t nkeep); + /* ============================================================ * Reader / inspection * ============================================================ */ @@ -334,6 +358,33 @@ KIT_API KitStatus kit_obj_section_data(const KitObjFile*, KitObjSection idx, KIT_API KitStatus kit_obj_section_by_name(const KitObjFile*, KitSlice name, KitObjSection* out); +/* `size`-style section bucketing: classify an allocatable section into the + * text / data / bss column it contributes to. Non-allocatable and debug + * sections are KIT_SEC_SIZE_NONE. Executable (or KIT_SEC_TEXT) sections are + * TEXT; KIT_SEC_BSS is BSS; everything else allocatable (rodata, data, + * writable) is DATA. */ +typedef enum KitObjSecSizeClass { + KIT_SEC_SIZE_NONE, + KIT_SEC_SIZE_TEXT, + KIT_SEC_SIZE_DATA, + KIT_SEC_SIZE_BSS, +} KitObjSecSizeClass; + +KIT_API int kit_obj_section_size_class(const KitObjSecInfo*); + +/* Aggregate section sizes by class, the figures `size` reports. When + * `include_common` is set, COMMON symbols' sizes are added to the bss total. + * `total` is text + data + bss. */ +typedef struct KitObjSizeTotals { + uint64_t text; + uint64_t data; + uint64_t bss; + uint64_t total; +} KitObjSizeTotals; + +KIT_API KitStatus kit_obj_size_totals(KitObjFile*, bool include_common, + KitObjSizeTotals* out); + /* Format-specific raw section attributes preserved by the reader. * * COFF : *raw_type_out = IMAGE_SECTION_HEADER.Characteristics @@ -363,6 +414,18 @@ KIT_API KitStatus kit_obj_reliter_new(KitObjFile*, KitObjRelocIter** out); KIT_API KitIterResult kit_obj_reliter_next(KitObjRelocIter*, KitObjReloc* out); KIT_API void kit_obj_reliter_free(KitObjRelocIter*); +/* Collect (deduplicated) the symbol ids targeted by any relocation whose + * containing section survives a debug strip — relocations hosted in a + * KIT_SEC_DEBUG section are skipped, since that section is about to be dropped + * and so does not keep its targets alive. This is the reloc-liveness scan that + * underpins the strip / objcopy needed-symbol set. On success *out owns an + * exactly-sized array of *n_out ids (free via the context heap with + * n_out * sizeof(KitObjSymbol)); *out is NULL and *n_out 0 when nothing is + * targeted. */ +KIT_API KitStatus kit_obj_reloc_live_symbols(const KitContext*, KitObjFile*, + KitObjSymbol** out, + uint32_t* n_out); + /* Section-group iteration (ELF SHT_GROUP / COMDAT and friends). Empty * for formats / objects that carry no groups. */ KIT_API KitStatus kit_obj_groupiter_new(KitObjFile*, KitObjGroupIter** out); diff --git a/include/kit/target.h b/include/kit/target.h @@ -0,0 +1,96 @@ +#ifndef KIT_TARGET_H +#define KIT_TARGET_H + +#include <kit/core.h> +#include <stddef.h> +#include <stdint.h> + +/* + * Target-triple <-> KitTargetSpec, plus the per-target defaults derived from a + * spec. This is the string interface every toolchain speaks: a triple like + * "aarch64-linux" or "x86_64-pc-windows" goes in, a resolved KitTargetSpec + * comes out (and back). <kit/core.h> hands you a KitTargetSpec and + * kit_target_new(), but no way to *construct* a spec from a human/triple + * string — this header is that missing layer, hoisted out of the CLI driver so + * any embedder gets the same parse and the same defaults. + * + * Everything here is pure: no host I/O, no DriverEnv, no diagnostics. The + * functions return a status/bool and leave any user-facing error message to the + * caller. The arch-name spellings accepted by kit_arch_from_name() and + * kit_target_from_triple() are the single authority for what an arch component + * may be written as. + */ + +/* Map an architecture-name literal (the arch component of a triple) to its + * KitArchKind and natural pointer size. Accepted spellings: + * x86_64 / amd64 / x64 -> KIT_ARCH_X86_64 (8) + * i386 / i486 / i586 / i686 -> KIT_ARCH_X86_32 (4) + * aarch64 / arm64 / aa64 -> KIT_ARCH_ARM_64 (8) + * arm / armv7 -> KIT_ARCH_ARM_32 (4) + * riscv64 / rv64 -> KIT_ARCH_RV64 (8) + * riscv32 / rv32 -> KIT_ARCH_RV32 (4) + * wasm32 -> KIT_ARCH_WASM (4) + * wasm64 -> KIT_ARCH_WASM (8) + * The single authority for arch-name spellings; kit_target_from_triple() uses + * the same table. Returns true on a hit (writing through non-NULL out + * pointers), false on an unrecognized name or a NULL `name`. Out pointers may + * each be NULL. */ +KIT_API bool kit_arch_from_name(const char* name, KitArchKind* arch_out, + uint8_t* ptr_size_out); + +/* Parse a target triple string (`<arch>[-<vendor>]-<os>[-<env>]`) into a + * KitTargetSpec. Recognized arches: see kit_arch_from_name. Recognized OSes are + * scanned across the remaining components (so vendor tokens like + * pc/apple/unknown are skipped): linux, darwin/macos, windows/win32, + * freebsd[<ver>], wasi, none/freestanding. An unrecognized or absent OS token + * resolves to a freestanding ELF/WASM target by design (covers bare-metal / + * vendor-only triples like "riscv64-unknown-elf"). + * + * Sets arch/os/obj/ptr_size/ptr_align/big_endian/os_version_major, and derives + * pic via kit_target_default_pic; code_model is left at KIT_CM_DEFAULT. The + * resolved data-model fields (long_size, wchar_size, ...) are NOT filled in + * here — they come from kit_target_new(). Returns true on success, false on an + * unrecognized arch or a NULL `triple`/`out`. */ +KIT_API bool kit_target_from_triple(const char* triple, KitTargetSpec* out); + +/* Render a canonical target triple for `spec` into `buf` (e.g. + * "x86_64-linux", "aarch64-apple-darwin"). Returns true on success, false when + * `buf` is NULL/zero-length or too small to hold the triple plus its NUL. */ +KIT_API bool kit_target_to_triple(KitTargetSpec spec, char* buf, size_t cap); + +/* Map a -mcmodel= value to a KitCodeModel. Accepts the x86 spellings + * (small/medium/large) and the RISC-V aliases (medlow->small, medany->medium). + * Returns true and writes *out on a known value, false on an unknown one (no + * diagnostic; *out is untouched). `out` may be NULL to query validity. */ +KIT_API bool kit_target_code_model_from_name(const char* val, + KitCodeModel* out); + +/* Default PIC/PIE model for an (obj, os) pair. Hosted targets default to PIE + * (ELF -> ET_DYN, Mach-O -> MH_PIE, PE/COFF -> .reloc + DYNAMIC_BASE). + * Freestanding targets (no dynamic loader) and WASM stay non-PIE. */ +KIT_API KitPic kit_target_default_pic(KitObjFmt obj, KitOSKind os); + +/* Whether the link step should emit a position-independent executable for + * `spec`. An explicit -pie always wins; -shared and -r (relocatable) always + * suppress it; otherwise it follows the target's PIC model. Returns 1/0. */ +KIT_API int kit_target_link_pie(KitTargetSpec spec, int explicit_pie, + int shared, int relocatable); + +/* Default executable name when no output is given: "a.exe" on Windows + * (PE/COFF), "a.out" elsewhere. */ +KIT_API const char* kit_target_default_exe_name(KitTargetSpec spec); + +/* Default relocatable-object extension for `spec`: ".obj" (len 4) on Windows, + * ".o" (len 2) elsewhere. NULL out-pointers are ignored. */ +KIT_API void kit_target_default_obj_ext(KitTargetSpec spec, const char** ext_out, + size_t* ext_len_out); + +/* Whether `<sysroot>/lib` should be folded into the library search path for + * `spec` (the Windows mingw import-library tree). Returns 1/0. */ +KIT_API int kit_target_needs_sysroot_libdir(KitTargetSpec spec); + +/* Whether the hosted libc profile is engaged by default for `spec` + * (Windows-COFF, given a sysroot and no -nostdlib). Returns 1/0. */ +KIT_API int kit_target_default_hosted_profile(KitTargetSpec spec); + +#endif diff --git a/mk/driver_srcs.mk b/mk/driver_srcs.mk @@ -54,9 +54,9 @@ DRIVER_SRCS += $(sort $(DRIVER_TOOL_SRCS)) # by KIT_CAS_ENABLED / KIT_PKG_ENABLED, asserted by config_assert.c); the dist # implementation and vendored primitives live in the library now. # build.c is one shared TU compiled whenever ANY of BUILD_EXE/LIB/OBJ is on, and -# it references driver_link_engine_emit, driver_archive_emit, and -# driver_lib_resolve_for_os unconditionally (the call sites are gated at runtime, -# not by #if). So link_engine/archive_engine/lib_resolve — and inputs, which +# it references kit_build_link (the public build tier in libkit), driver_archive_emit, +# and driver_lib_resolve_for_os unconditionally (the call sites are gated at +# runtime, not by #if). So archive_engine/lib_resolve — and inputs, which # archive_engine pulls in — must be present for every BUILD_* subset, not just # the tools that exercise each path. Hence the full BUILD_EXE BUILD_LIB BUILD_OBJ # union below. @@ -64,13 +64,11 @@ DRIVER_SRCS += $(call need-any,CC CHECK BUILD_EXE BUILD_LIB BUILD_OBJ CPP AS DBG DRIVER_SRCS += $(call need-any,CC CHECK BUILD_EXE BUILD_LIB BUILD_OBJ LD RUN,driver/lib/lib_resolve.c) DRIVER_SRCS += $(call need-any,CC CHECK BUILD_EXE BUILD_LIB BUILD_OBJ RUN,driver/lib/hosted.c) DRIVER_SRCS += $(call need-any,CC CHECK BUILD_EXE BUILD_LIB BUILD_OBJ LD,driver/lib/runtime.c) -DRIVER_SRCS += $(call need-any,CC CHECK BUILD_EXE BUILD_LIB BUILD_OBJ,driver/lib/link_engine.c) DRIVER_SRCS += $(call need-any,CC CHECK BUILD_EXE BUILD_LIB BUILD_OBJ,driver/lib/link_flags.c) DRIVER_SRCS += $(call need-any,BUILD_EXE BUILD_LIB BUILD_OBJ,driver/lib/archive_engine.c) DRIVER_SRCS += $(call need-any,AR RANLIB STRIP DBG RUN BUILD_EXE BUILD_LIB BUILD_OBJ,driver/lib/inputs.c) DRIVER_SRCS += $(call need-any,STRIP OBJCOPY,driver/lib/objedit.c) DRIVER_SRCS += $(call need-any,RUN,driver/lib/wasm_run.c) -DRIVER_SRCS += $(call need-any,CC CHECK BUILD_EXE BUILD_LIB BUILD_OBJ,driver/lib/compile_engine.c) DRIVER_SRCS += $(call need-any,CC CHECK BUILD_EXE BUILD_LIB BUILD_OBJ,driver/lib/link_inputs.c) DRIVER_SRCS += $(call need-any,CAS PKG,driver/lib/dist_host.c) DRIVER_SRCS += $(call need-any,ADDR2LINE SYMBOLIZE,driver/lib/dwarfsym.c) diff --git a/mk/lib_srcs.mk b/mk/lib_srcs.mk @@ -31,8 +31,9 @@ endef # directories, and ABI implementations are added below from their own groups. LIB_SRCS_ABI_CORE = src/abi/abi.c src/abi/registry.c LIB_SRCS_API_CORE = $(filter-out src/api/archive.c src/api/disasm.c \ - src/api/link.c src/api/cas.c src/api/package.c \ - src/api/compress.c src/api/stubs.c,$(wildcard src/api/*.c)) + src/api/link.c src/api/build.c src/api/cas.c \ + src/api/package.c src/api/compress.c src/api/stubs.c, \ + $(wildcard src/api/*.c)) LIB_SRCS_ARCH_CORE = $(filter-out src/arch/%_stubs.c,$(wildcard src/arch/*.c)) LIB_SRCS_ASM_CORE = $(wildcard src/asm/*.c) LIB_SRCS_CG_CORE = $(wildcard src/cg/*.c) @@ -100,6 +101,9 @@ LIB_SRCS_WASM_CORE := $(shell find src/wasm -name '*.c' 2>/dev/null) LIB_SRCS_API_AR = src/api/archive.c LIB_SRCS_API_DISASM = src/api/disasm.c LIB_SRCS_API_LINK = src/api/link.c +# Build orchestration tier (batch compile + ordered link). It uses the linker +# (kit_link_session_*), so it compiles only when KIT_LINK_ENABLED. +LIB_SRCS_API_BUILD = src/api/build.c # Distribution subsystem (content store + signed packages). The cas layer # needs blake2b (-> monocypher); the pkg layer adds the crypto/container shims # and the second monocypher TU. The compression codecs (deflate + lz4 block) @@ -154,7 +158,7 @@ ifeq ($(KIT_DWARF_ENABLED),1) LIB_SRCS += $(LIB_SRCS_DEBUG) endif ifeq ($(KIT_LINK_ENABLED),1) -LIB_SRCS += $(LIB_SRCS_API_LINK) $(LIB_SRCS_LINK) +LIB_SRCS += $(LIB_SRCS_API_LINK) $(LIB_SRCS_API_BUILD) $(LIB_SRCS_LINK) endif ifeq ($(KIT_DBG_ENABLED),1) LIB_SRCS += $(LIB_SRCS_DBG) diff --git a/src/api/archive.c b/src/api/archive.c @@ -390,6 +390,312 @@ void kit_ar_iter_free(KitArIter* it) { h->free(h, it, sizeof(*it)); } +/* ============================================================ + * Symbol-index automation + * ============================================================ */ + +/* The owned block is laid out as + * [size_t alloc_sz][KitSlice names[count]][NUL-separated name bytes] + * so the exact size for the size-tracking heap free travels with the block. + * *out_owned points at the header; out_syms->names points past it. */ +#define AR_SYMBLOB_HDR_BYTES \ + ((sizeof(size_t) + _Alignof(KitSlice) - 1u) & ~(_Alignof(KitSlice) - 1u)) + +KitStatus kit_obj_global_syms(const KitContext* ctx, const KitSlice* obj_bytes, + KitArMemberSymbols* out_syms, void** out_owned) { + Heap* h; + KitObjFile* of = NULL; + KitObjSymIter* it = NULL; + KitObjSymInfo si; + u32 count = 0; + size_t name_bytes = 0; + size_t alloc_sz; + char* blob; + KitSlice* name_arr; + char* name_storage; + size_t cursor = 0; + + if (!out_syms || !out_owned) return KIT_INVALID; + out_syms->names = NULL; + out_syms->count = 0; + *out_owned = NULL; + if (!ctx || !ctx->heap || !obj_bytes) return KIT_INVALID; + h = ctx->heap; + + if (kit_obj_open(ctx, KIT_SLICE_NULL, obj_bytes, &of) != KIT_OK) { + /* Not a recognized object — the empty set, not an error. */ + return KIT_OK; + } + + /* Pass A: count + measure name bytes. */ + if (kit_obj_symiter_new(of, &it) != KIT_OK) { + kit_obj_free(of); + return KIT_NOMEM; + } + for (;;) { + KitIterResult r = kit_obj_symiter_next(it, &si); + if (r != KIT_ITER_ITEM) break; + if (si.bind != KIT_SB_GLOBAL) continue; + if (si.section == KIT_SECTION_NONE) continue; + if (!si.name.len) continue; + count += 1; + name_bytes += si.name.len + 1; /* +NUL */ + } + kit_obj_symiter_free(it); + + if (count == 0) { + kit_obj_free(of); + return KIT_OK; + } + + alloc_sz = AR_SYMBLOB_HDR_BYTES + (size_t)count * sizeof(KitSlice) + name_bytes; + blob = (char*)h->alloc(h, alloc_sz, _Alignof(KitSlice)); + if (!blob) { + kit_obj_free(of); + return KIT_NOMEM; + } + *(size_t*)(void*)blob = alloc_sz; + name_arr = (KitSlice*)(void*)(blob + AR_SYMBLOB_HDR_BYTES); + name_storage = (char*)name_arr + (size_t)count * sizeof(KitSlice); + + /* Pass B: copy names. */ + if (kit_obj_symiter_new(of, &it) != KIT_OK) { + h->free(h, blob, alloc_sz); + kit_obj_free(of); + return KIT_NOMEM; + } + { + u32 k = 0; + for (;;) { + KitIterResult r; + char* dst; + size_t j; + if (k >= count) break; + r = kit_obj_symiter_next(it, &si); + if (r != KIT_ITER_ITEM) break; + if (si.bind != KIT_SB_GLOBAL) continue; + if (si.section == KIT_SECTION_NONE) continue; + if (!si.name.len) continue; + dst = name_storage + cursor; + name_arr[k].s = dst; + name_arr[k].len = si.name.len; + for (j = 0; j < si.name.len; ++j) *dst++ = si.name.s[j]; + *dst++ = '\0'; + cursor = (size_t)(dst - name_storage); + k++; + } + count = k; + } + kit_obj_symiter_free(it); + kit_obj_free(of); + + out_syms->names = name_arr; + out_syms->count = count; + *out_owned = blob; + return KIT_OK; +} + +void kit_obj_global_syms_free(const KitContext* ctx, void* owned) { + Heap* h; + size_t alloc_sz; + if (!ctx || !ctx->heap || !owned) return; + h = ctx->heap; + alloc_sz = *(const size_t*)owned; + h->free(h, owned, alloc_sz); +} + +/* Serialize one builder into a fresh in-memory writer; the returned bytes + * alias that writer's buffer, so the writer must stay open until the bytes + * have been consumed. */ +static KitStatus ar_emit_member(const KitContext* ctx, KitObjBuilder* b, + KitSlice name, KitWriter** w_out, + KitSlice* bytes_out) { + const uint8_t* bytes; + size_t len = 0; + (void)name; + if (kit_writer_mem(ctx->heap, w_out) != KIT_OK) return KIT_NOMEM; + if (kit_obj_builder_emit(b, *w_out) != KIT_OK || + kit_writer_status(*w_out) != KIT_OK) + return KIT_ERR; + bytes = kit_writer_mem_bytes(*w_out, &len); + bytes_out->s = (const char*)bytes; + bytes_out->len = len; + return KIT_OK; +} + +/* Shared tail of kit_ar_write_objs / kit_ar_reindex: given member name/byte + * slices already laid out in `members`, compute each member's symbol index and + * write the long-names + symbol-index archive. */ +static KitStatus ar_write_with_index(const KitContext* ctx, KitArInput* members, + u32 n, u64 epoch, KitWriter* out) { + Heap* h = ctx->heap; + KitArMemberSymbols* msyms = NULL; + void** owned = NULL; + KitArWriteOptions opts = {0}; + KitStatus st = KIT_OK; + u32 i; + + if (n) { + msyms = (KitArMemberSymbols*)h->alloc(h, (size_t)n * sizeof(*msyms), + _Alignof(KitArMemberSymbols)); + owned = (void**)h->alloc(h, (size_t)n * sizeof(*owned), _Alignof(void*)); + if (!msyms || !owned) { + st = KIT_NOMEM; + goto out; + } + for (i = 0; i < n; ++i) { + msyms[i].names = NULL; + msyms[i].count = 0; + owned[i] = NULL; + } + for (i = 0; i < n; ++i) { + st = kit_obj_global_syms(ctx, &members[i].bytes, &msyms[i], &owned[i]); + if (st != KIT_OK) goto out; + } + } + + opts.epoch = epoch; + opts.long_names = 1; + opts.symbol_index = 1; + opts.member_symbols = msyms; + st = kit_ar_write(out, members, n, &opts); + if (st == KIT_OK && kit_writer_status(out) != KIT_OK) st = KIT_ERR; + +out: + if (owned) { + for (i = 0; i < n; ++i) + if (owned[i]) kit_obj_global_syms_free(ctx, owned[i]); + h->free(h, owned, (size_t)n * sizeof(*owned)); + } + if (msyms) h->free(h, msyms, (size_t)n * sizeof(*msyms)); + return st; +} + +KitStatus kit_ar_write_objs(const KitContext* ctx, KitObjBuilder* const* objs, + const KitSlice* names, uint32_t n, uint64_t epoch, + KitWriter* out) { + Heap* h; + KitArInput* members = NULL; + KitWriter** memw = NULL; + KitStatus st = KIT_OK; + u32 i; + + if (!ctx || !ctx->heap || !out) return KIT_INVALID; + if (n && (!objs || !names)) return KIT_INVALID; + h = ctx->heap; + + if (n) { + members = + (KitArInput*)h->alloc(h, (size_t)n * sizeof(*members), _Alignof(KitArInput)); + memw = (KitWriter**)h->alloc(h, (size_t)n * sizeof(*memw), _Alignof(KitWriter*)); + if (!members || !memw) { + st = KIT_NOMEM; + goto out; + } + for (i = 0; i < n; ++i) { + members[i].name = names[i]; + members[i].bytes = KIT_SLICE_NULL; + memw[i] = NULL; + } + /* Serialize every builder; the member bytes alias each writer's buffer, so + * the writers stay open until ar_write_with_index has consumed them. */ + for (i = 0; i < n; ++i) { + st = ar_emit_member(ctx, objs[i], names[i], &memw[i], &members[i].bytes); + if (st != KIT_OK) goto out; + } + } + + st = ar_write_with_index(ctx, members, n, epoch, out); + +out: + if (memw) { + for (i = 0; i < n; ++i) + if (memw[i]) kit_writer_close(memw[i]); + h->free(h, memw, (size_t)n * sizeof(*memw)); + } + if (members) h->free(h, members, (size_t)n * sizeof(*members)); + return st; +} + +KitStatus kit_ar_reindex(const KitContext* ctx, const KitSlice* archive_bytes, + uint64_t epoch, KitWriter* out) { + Heap* h; + KitArIter* it = NULL; + KitArMember m; + KitArInput* members = NULL; + char* name_storage = NULL; + size_t name_bytes_total = 0; + u32 nmembers = 0; + KitStatus st = KIT_OK; + + if (!ctx || !ctx->heap || !archive_bytes || !out) return KIT_INVALID; + h = ctx->heap; + + /* Pass 1: count members + total name bytes (member names alias an internal + * buffer overwritten on each next(), so a stable copy is stashed). */ + if (kit_ar_iter_new(ctx, archive_bytes, &it) != KIT_OK) return KIT_MALFORMED; + for (;;) { + KitIterResult r = kit_ar_iter_next(it, &m); + if (r != KIT_ITER_ITEM) break; + nmembers++; + name_bytes_total += m.name.len + 1; + } + kit_ar_iter_free(it); + it = NULL; + + if (nmembers == 0) { + /* Empty archive: still emit an empty symbol index (matches GNU ranlib). */ + return ar_write_with_index(ctx, NULL, 0, epoch, out); + } + + members = + (KitArInput*)h->alloc(h, (size_t)nmembers * sizeof(*members), + _Alignof(KitArInput)); + if (!members) return KIT_NOMEM; + if (name_bytes_total) { + name_storage = (char*)h->alloc(h, name_bytes_total, 1); + if (!name_storage) { + h->free(h, members, (size_t)nmembers * sizeof(*members)); + return KIT_NOMEM; + } + } + + /* Pass 2: copy names and member byte-spans into the parallel arrays. */ + if (kit_ar_iter_new(ctx, archive_bytes, &it) != KIT_OK) { + st = KIT_MALFORMED; + goto out; + } + { + size_t cursor = 0; + u32 k = 0; + while (k < nmembers) { + KitIterResult r = kit_ar_iter_next(it, &m); + char* dst; + size_t j; + if (r != KIT_ITER_ITEM) break; + dst = name_storage + cursor; + for (j = 0; j < m.name.len; ++j) *dst++ = m.name.s[j]; + *dst++ = '\0'; + members[k].name.s = name_storage + cursor; + members[k].name.len = m.name.len; + members[k].bytes.data = m.data; + members[k].bytes.len = m.size; + cursor = (size_t)(dst - name_storage); + k++; + } + } + kit_ar_iter_free(it); + it = NULL; + + st = ar_write_with_index(ctx, members, nmembers, epoch, out); + +out: + if (it) kit_ar_iter_free(it); + if (name_storage) h->free(h, name_storage, name_bytes_total); + if (members) h->free(h, members, (size_t)nmembers * sizeof(*members)); + return st; +} + KitStatus kit_ar_list(const KitSlice* archive, KitWriter* out) { /* Iter API requires a context; emulate locally without heap allocation. */ struct KitArIter local; diff --git a/src/api/build.c b/src/api/build.c @@ -0,0 +1,347 @@ +/* Build orchestration API entries. + * + * The batch-compile and ordered-link tier over the public compile/link/cg + * surface: kit_build_compile_one/kit_build_compile drive the per-source and + * batched compile paths, and kit_build_link/kit_build_link_with_lto build a + * link session, add inputs in command-line order, and emit. Every entry is + * written purely against public kit_* calls. */ + +#include <kit/build.h> + +#include <kit/asm_emit.h> +#include <kit/cg.h> +#include <string.h> + +static KitStatus build_compile_cg_run(KitCompiler* compiler, + const KitCodeOptions* code, + const KitDiagnosticOptions* diagnostics, + const KitBuildSource* src, KitCg* cg) { + KitCompileSessionOptions sopts; + KitCompileSession* session = NULL; + KitSourceInput sin; + KitStatus st; + + if (!compiler || !code || !diagnostics || !src || !cg) return KIT_INVALID; + memset(&sopts, 0, sizeof(sopts)); + sopts.lang = src->lang; + sopts.compile.code = *code; + sopts.compile.diagnostics = *diagnostics; + if (src->pp) sopts.compile.preprocess = *src->pp; + sopts.compile.language_options = src->lang_extra; + + memset(&sin, 0, sizeof(sin)); + sin.name = src->name; + sin.bytes = src->bytes; + sin.lang = src->lang; + + st = kit_compile_session_new(compiler, &sopts, &session); + if (st == KIT_OK) st = kit_compile_session_compile_cg(session, &sin, cg); + kit_compile_session_free(session); + return st; +} + +KitStatus kit_build_compile_one(KitCompiler* compiler, KitLanguage lang, + const KitCodeOptions* code, + const KitDiagnosticOptions* diagnostics, + const KitPreprocessOptions* pp, + const void* lang_extra, KitSlice name, + const KitSlice* bytes, KitWriter* emit_out, + KitObjBuilder** obj_out) { + KitCompileSessionOptions sopts; + KitCompileSession* session = NULL; + KitSourceInput sin; + KitObjBuilder* ob = NULL; + KitCodeOptions code_copy = *code; + KitStatus st; + + if (obj_out) *obj_out = NULL; + + /* For the in-CG emit modes the output writer is consumed during codegen, so + * wire it onto the code options before the session runs. */ + if (emit_out && code_copy.emit_c_source) code_copy.c_source_writer = emit_out; + if (emit_out && code_copy.emit_ir) code_copy.ir_dump_writer = emit_out; + + memset(&sopts, 0, sizeof(sopts)); + sopts.lang = lang; + sopts.compile.code = code_copy; + sopts.compile.diagnostics = *diagnostics; + if (pp) sopts.compile.preprocess = *pp; + sopts.compile.language_options = lang_extra; + + memset(&sin, 0, sizeof(sin)); + sin.name = name; + sin.bytes = *bytes; + sin.lang = lang; + + st = kit_compile_session_new(compiler, &sopts, &session); + if (st == KIT_OK) st = kit_compile_session_compile(session, &sin, &ob); + kit_compile_session_free(session); + if (st != KIT_OK) return st; + + if (obj_out) { + *obj_out = ob; + return KIT_OK; + } + + /* emit_out path: serialize by output mode. The in-CG modes already wrote + * through the wired writer above. */ + if (code_copy.emit_c_source || code_copy.emit_ir) { + /* nothing to serialize here */ + } else if (code_copy.emit_asm_source) { + st = kit_obj_builder_emit_asm(ob, emit_out); + } else { + st = kit_obj_builder_emit(ob, emit_out); + } + kit_obj_builder_free(ob); + return st; +} + +static int build_compile_lto_enabled(const KitCodeOptions* code) { + return code && code->lto && !code->check_only && !code->emit_c_source && + !code->emit_ir && !code->emit_asm_source; +} + +static KitStatus build_start_lto(KitCompiler* compiler, + const KitCodeOptions* code, + KitObjBuilder** ob_out, KitCg** cg_out) { + KitObjBuilder* ob = NULL; + KitCg* cg = NULL; + KitStatus st; + + if (ob_out) *ob_out = NULL; + if (cg_out) *cg_out = NULL; + if (!compiler || !code || !ob_out || !cg_out) return KIT_INVALID; + st = kit_obj_builder_new(compiler, &ob); + if (st == KIT_OK) st = kit_cg_new(compiler, &cg); + if (st == KIT_OK) st = kit_cg_begin(cg, ob, code); + if (st != KIT_OK) { + kit_cg_free(cg); + kit_obj_builder_free(ob); + return st; + } + *ob_out = ob; + *cg_out = cg; + return KIT_OK; +} + +KitStatus kit_build_lto_finish(KitBuildPendingLto* pending, + const KitBuildBatchOptions* batch, + const KitCgSym* preserved_symbols, + uint32_t npreserved_symbols) { + KitCgFinishOptions finish; + KitStatus st; + + if (!pending || !pending->active) return KIT_OK; + if (!pending->obj || !pending->cg) { + kit_build_lto_abort(pending); + return KIT_INVALID; + } + + memset(&finish, 0, sizeof finish); + finish.output_kind = batch ? batch->output_kind : KIT_CG_OUTPUT_RELOCATABLE; + finish.interposition_policy = + batch ? batch->interposition_policy : KIT_CG_INTERPOSITION_DEFAULT; + finish.preserved_symbols = preserved_symbols; + finish.npreserved_symbols = npreserved_symbols; + + st = kit_cg_finish(pending->cg, &finish); + if (st == KIT_OK) st = kit_cg_detach(pending->cg); + if (st == KIT_OK) st = kit_obj_builder_finalize(pending->obj); + + kit_cg_free(pending->cg); + pending->cg = NULL; + pending->active = 0; + return st; +} + +void kit_build_lto_abort(KitBuildPendingLto* pending) { + if (!pending || !pending->active) return; + kit_cg_free(pending->cg); + pending->cg = NULL; + pending->active = 0; +} + +KitStatus kit_build_compile(KitCompiler* compiler, const KitCodeOptions* code, + const KitDiagnosticOptions* diagnostics, + const KitBuildSource* sources, uint32_t nsources, + const KitBuildBatchOptions* batch, + KitBuildObjects* out) { + KitBuildPendingLto pending_lto; + int lto_order_emitted = 0; + int lto_enabled = build_compile_lto_enabled(code); + KitStatus st = KIT_OK; + + if (!compiler || !code || !diagnostics || (!sources && nsources) || !out || + !out->objs || !out->source_obj_index || !out->source_order_keep) { + return KIT_INVALID; + } + memset(&pending_lto, 0, sizeof pending_lto); + out->nobjs = 0; + if (out->pending_lto) memset(out->pending_lto, 0, sizeof(*out->pending_lto)); + for (uint32_t i = 0; i < nsources; ++i) { + out->source_obj_index[i] = (uint32_t)-1; + out->source_order_keep[i] = 0; + } + + for (uint32_t i = 0; i < nsources; ++i) { + const KitBuildSource* src = &sources[i]; + KitFrontendCaps caps; + int stage_cg = 0; + + memset(&caps, 0, sizeof caps); + if (lto_enabled) { + st = kit_frontend_caps(compiler, src->lang, &caps); + if (st != KIT_OK) goto out; + stage_cg = caps.lto_mode == KIT_FRONTEND_LTO_CG; + } + + if (stage_cg) { + if (!pending_lto.active) { + st = build_start_lto(compiler, code, &pending_lto.obj, &pending_lto.cg); + if (st != KIT_OK) goto out; + pending_lto.obj_index = out->nobjs; + pending_lto.active = 1; + out->objs[out->nobjs++] = pending_lto.obj; + } + out->source_obj_index[i] = pending_lto.obj_index; + if (!lto_order_emitted) { + out->source_order_keep[i] = 1; + lto_order_emitted = 1; + } + st = build_compile_cg_run(compiler, code, diagnostics, src, + pending_lto.cg); + if (st != KIT_OK) goto out; + continue; + } + + { + KitObjBuilder* ob = NULL; + st = kit_build_compile_one(compiler, src->lang, code, diagnostics, + src->pp, src->lang_extra, src->name, + &src->bytes, NULL, &ob); + if (st != KIT_OK) goto out; + out->source_obj_index[i] = out->nobjs; + out->source_order_keep[i] = 1; + out->objs[out->nobjs++] = ob; + } + } + + if (pending_lto.active) { + if (batch && batch->defer_lto_finish) { + if (!out->pending_lto) { + st = KIT_INVALID; + goto out; + } + *out->pending_lto = pending_lto; + memset(&pending_lto, 0, sizeof pending_lto); + } else { + st = kit_build_lto_finish(&pending_lto, batch, NULL, 0); + if (st != KIT_OK) goto out; + } + } + +out: + if (pending_lto.active) kit_build_lto_abort(&pending_lto); + return st; +} + +typedef struct BuildPreservedVec { + KitHeap* heap; + KitCgSym* syms; + uint32_t nsyms; + uint32_t cap; + int oom; +} BuildPreservedVec; + +static void build_preserved_vec_add(void* user, KitCgSym sym) { + BuildPreservedVec* v = (BuildPreservedVec*)user; + KitCgSym* ns; + uint32_t ncap; + if (!v || v->oom) return; + if (v->nsyms == v->cap) { + ncap = v->cap ? v->cap * 2u : 32u; + ns = (KitCgSym*)v->heap->realloc( + v->heap, v->syms, sizeof(*v->syms) * v->cap, sizeof(*v->syms) * ncap, + _Alignof(KitCgSym)); + if (!ns) { + v->oom = 1; + return; + } + v->syms = ns; + v->cap = ncap; + } + v->syms[v->nsyms++] = sym; +} + +static KitStatus build_link_add_inputs(KitLinkSession* link, + const KitLinkInputs* in) { + KitStatus st = KIT_OK; + uint32_t i; + if (!link || !in) return KIT_INVALID; + + for (i = 0; i < in->norder && st == KIT_OK; ++i) { + const KitLinkInputOrder* ord = &in->order[i]; + switch ((KitLinkInputOrderKind)ord->kind) { + case KIT_LINK_INPUT_OBJ: + st = kit_link_session_add_obj(link, in->objs[ord->index]); + break; + case KIT_LINK_INPUT_OBJ_BYTES: + st = kit_link_session_add_obj_bytes(link, in->obj_names[ord->index], + &in->obj_bytes[ord->index]); + break; + case KIT_LINK_INPUT_ARCHIVE: + st = + kit_link_session_add_archive_bytes(link, &in->archives[ord->index]); + break; + case KIT_LINK_INPUT_DSO: + st = kit_link_session_add_dso_bytes(link, in->dso_names[ord->index], + &in->dso_bytes[ord->index]); + break; + } + } + return st; +} + +KitStatus kit_build_link_with_lto(KitCompiler* compiler, + const KitLinkSessionOptions* lopts, + const KitLinkInputs* in, + KitBuildPendingLto* pending_lto, + const KitBuildBatchOptions* batch, + KitWriter* out) { + KitLinkSession* link = NULL; + BuildPreservedVec preserved; + KitStatus st; + + if (!compiler || !lopts || !in || !out) { + if (pending_lto && pending_lto->active) kit_build_lto_abort(pending_lto); + return KIT_INVALID; + } + memset(&preserved, 0, sizeof preserved); + preserved.heap = kit_compiler_context(compiler)->heap; + st = kit_link_session_new(compiler, lopts, &link); + if (st == KIT_OK) st = build_link_add_inputs(link, in); + if (st == KIT_OK && pending_lto && pending_lto->active) { + st = kit_link_session_visit_lto_preserved( + link, pending_lto->obj, pending_lto->cg, build_preserved_vec_add, + &preserved); + if (st == KIT_OK && preserved.oom) st = KIT_NOMEM; + if (st == KIT_OK) { + st = kit_build_lto_finish(pending_lto, batch, preserved.syms, + preserved.nsyms); + } + } + if (st == KIT_OK) st = kit_link_session_emit(link, out); + kit_link_session_free(link); + if (preserved.syms) + preserved.heap->free(preserved.heap, preserved.syms, + sizeof(*preserved.syms) * preserved.cap); + if (st != KIT_OK && pending_lto && pending_lto->active) + kit_build_lto_abort(pending_lto); + return st; +} + +KitStatus kit_build_link(KitCompiler* compiler, + const KitLinkSessionOptions* lopts, + const KitLinkInputs* in, KitWriter* out) { + return kit_build_link_with_lto(compiler, lopts, in, NULL, NULL, out); +} diff --git a/src/api/compile.c b/src/api/compile.c @@ -132,6 +132,30 @@ KitLanguage kit_language_for_path(KitCompiler* c, const char* path) { return KIT_LANG_UNKNOWN; } +/* Case-sensitive trailing-substring match (mirrors the driver's exact suffix + * test for object/archive/dso paths). */ +static int path_has_suffix(const char* s, const char* suffix) { + size_t ls = 0, lf = 0; + while (s[ls]) ++ls; + while (suffix[lf]) ++lf; + return ls >= lf && memcmp(s + ls - lf, suffix, lf) == 0; +} + +KitInputKind kit_input_kind_for_path(KitCompiler* c, const char* path) { + if (!path) return KIT_INPUT_UNKNOWN; + /* A registered frontend's extension wins first (case-insensitive registry, + * headers excluded — they claim no language). */ + if (kit_language_for_path(c, path) != KIT_LANG_UNKNOWN) + return KIT_INPUT_SOURCE; + if (path_has_suffix(path, ".o") || path_has_suffix(path, ".obj")) + return KIT_INPUT_OBJECT; + if (path_has_suffix(path, ".a")) return KIT_INPUT_ARCHIVE; + if (path_has_suffix(path, ".so") || path_has_suffix(path, ".dylib") || + path_has_suffix(path, ".tbd")) + return KIT_INPUT_DSO; + return KIT_INPUT_UNKNOWN; +} + /* Compare a NUL-terminated name to a frontend's name slice, byte-for-byte * (case-sensitive, mirroring the driver's exact `-x` spellings). Returns * nonzero on a full match. */ diff --git a/src/api/link.c b/src/api/link.c @@ -80,6 +80,64 @@ static void link_warn_ignored_opt(Compiler* c, const char* name) { "link: option '%s' is not yet supported and is ignored", name); } +/* -l<name> suffix search order. libkit owns only this policy table + walk + * order; the host probe does all path composition and filesystem access. */ +typedef struct LibVariant { + const char* prefix; + const char* suffix; + uint8_t kind; /* KitLibResolveKind */ +} LibVariant; + +bool kit_lib_resolve(uint8_t os, uint8_t mode, const char* name, + const char* const* search_dirs, uint32_t nsearch_dirs, + KitLibResolveProbe probe, void* user) { + /* POSIX dynamic: Apple .tbd/.dylib first (the macOS SDK ships .tbd stubs), + * then .so, then the .a fallback. DYNAMIC_ONLY drops the trailing .a. */ + static const LibVariant posix_dyn[] = { + {"lib", ".tbd", KIT_LIB_RESOLVE_KIND_TBD}, + {"lib", ".dylib", KIT_LIB_RESOLVE_KIND_SHARED}, + {"lib", ".so", KIT_LIB_RESOLVE_KIND_SHARED}, + {"lib", ".a", KIT_LIB_RESOLVE_KIND_ARCHIVE}, + }; + static const LibVariant posix_static[] = { + {"lib", ".a", KIT_LIB_RESOLVE_KIND_ARCHIVE}, + }; + /* Windows / mingw: mingw-canonical names first (lib<n>.dll.a, lib<n>.a), + * then the MSVC <n>.lib / <n>.dll.a variants. Every match feeds the linker + * as an archive input (short-form import libs are AR archives). */ + static const LibVariant win_variants[] = { + {"lib", ".dll.a", KIT_LIB_RESOLVE_KIND_ARCHIVE}, + {"lib", ".a", KIT_LIB_RESOLVE_KIND_ARCHIVE}, + {"", ".lib", KIT_LIB_RESOLVE_KIND_ARCHIVE}, + {"", ".dll.a", KIT_LIB_RESOLVE_KIND_ARCHIVE}, + }; + const LibVariant* variants; + uint32_t nvariants, vi, di; + + if (!name || !probe) return false; + + if (os == KIT_LIB_RESOLVE_OS_WINDOWS) { + variants = win_variants; + nvariants = (uint32_t)(sizeof win_variants / sizeof win_variants[0]); + } else if (mode == KIT_LIB_RESOLVE_STATIC_ONLY) { + variants = posix_static; + nvariants = (uint32_t)(sizeof posix_static / sizeof posix_static[0]); + } else { + variants = posix_dyn; + nvariants = (uint32_t)(sizeof posix_dyn / sizeof posix_dyn[0]); + if (mode == KIT_LIB_RESOLVE_DYNAMIC_ONLY) nvariants -= 1u; /* drop .a */ + } + + for (vi = 0; vi < nvariants; ++vi) { + for (di = 0; di < nsearch_dirs; ++di) { + if (probe(user, search_dirs[di], variants[vi].prefix, name, + variants[vi].suffix, variants[vi].kind)) + return true; + } + } + return false; +} + KitStatus kit_link_session_new(KitCompiler* c, const KitLinkSessionOptions* opts, KitLinkSession** out) { diff --git a/src/api/object_builder.c b/src/api/object_builder.c @@ -4,6 +4,9 @@ #include <string.h> #include "core/core.h" +#include "core/heap.h" +#include "core/pool.h" +#include "core/slice.h" #include "obj/format.h" #include "obj/obj.h" @@ -253,6 +256,122 @@ KitStatus kit_obj_builder_section_replace_bytes(KitObjBuilder* b, return KIT_OK; } +/* ---- strip pass (shared by `strip` and `objcopy --strip-*`) ---- */ + +static int strip_id_in_set(ObjSymId id, const ObjSymId* arr, u32 n) { + u32 i; + for (i = 0; i < n; ++i) + if (arr[i] == id) return 1; + return 0; +} + +static int strip_name_in_keep(Slice name, const KitSlice* keep, u32 nkeep) { + u32 i; + if (!name.len) return 0; + for (i = 0; i < nkeep; ++i) + if (slice_eq(name, keep[i])) return 1; + return 0; +} + +/* The needed set: every symbol still targeted by a relocation whose host + * section is not being dropped (debug sections are about to go). Mirrors + * kit_obj_reloc_live_symbols but reads the builder directly, so the strip pass + * needs only the builder handle. */ +static KitStatus strip_collect_needed(ObjBuilder* ob, Heap* h, ObjSymId** out, + u32* n_out, u32* cap_out) { + ObjSymId* arr = NULL; + u32 n = 0, cap = 0; + u32 total = obj_reloc_total(ob); + u32 i; + for (i = 0; i < total; ++i) { + const Reloc* r = obj_reloc_at(ob, i); + if (!r || r->sym == OBJ_SYM_NONE) continue; + if (r->section_id != OBJ_SEC_NONE) { + const Section* hs = obj_section_get(ob, r->section_id); + if (hs && hs->kind == SEC_DEBUG) continue; + } + if (strip_id_in_set(r->sym, arr, n)) continue; + if (n >= cap) { + u32 newcap = cap ? cap * 2u : 32u; + ObjSymId* nb = + (ObjSymId*)h->alloc(h, (size_t)newcap * sizeof(*nb), _Alignof(ObjSymId)); + if (!nb) { + if (arr) h->free(h, arr, (size_t)cap * sizeof(*arr)); + return KIT_NOMEM; + } + if (arr) { + memcpy(nb, arr, (size_t)n * sizeof(*arr)); + h->free(h, arr, (size_t)cap * sizeof(*arr)); + } + arr = nb; + cap = newcap; + } + arr[n++] = r->sym; + } + *out = arr; + *n_out = n; + *cap_out = cap; + return KIT_OK; +} + +KitStatus kit_obj_builder_strip(KitObjBuilder* b, int level, + const KitSlice* keep_names, uint32_t nkeep) { + ObjBuilder* ob = (ObjBuilder*)b; + Compiler* c; + Heap* h; + u32 nsec, i; + int filter_syms; + ObjSymId* needed = NULL; + u32 nneeded = 0, cap_needed = 0; + ObjSymIter* sit; + ObjSymEntry e; + KitStatus st; + + if (!b) return KIT_INVALID; + if (nkeep && !keep_names) return KIT_INVALID; + c = obj_compiler(ob); + if (!c) return KIT_INVALID; + h = c->ctx->heap; + filter_syms = + (level == KIT_STRIP_UNNEEDED || level == KIT_STRIP_ALL); + + /* Step 1: drop debug sections (every level does this). */ + nsec = obj_section_count(ob); + for (i = 0; i < nsec; ++i) { + const Section* sec = obj_section_get(ob, (ObjSecId)(i + 1)); + if (sec && sec->kind == SEC_DEBUG) obj_section_remove(ob, (ObjSecId)(i + 1)); + } + + if (!filter_syms) return KIT_OK; + + /* Step 2: compute the needed (reloc-reachable, non-debug) sym set. */ + st = strip_collect_needed(ob, h, &needed, &nneeded, &cap_needed); + if (st != KIT_OK) return st; + + /* Step 3: walk symbols and apply keep-list + needed-set policy. + * Keep undefined externals so the .o stays linkable; keep names on the + * keep-list; keep symbols a surviving reloc targets; drop the rest. */ + sit = obj_symiter_new(ob); + if (!sit) { + if (needed) h->free(h, needed, (size_t)cap_needed * sizeof(*needed)); + return KIT_NOMEM; + } + while (obj_symiter_next(sit, &e)) { + const ObjSym* s = e.sym; + Slice name; + if (!s) continue; + name = s->name ? pool_slice(c->global, s->name) : SLICE_LIT(""); + if (nkeep && strip_name_in_keep(name, keep_names, nkeep)) continue; + if (s->kind == SK_UNDEF) continue; + if (strip_id_in_set(e.id, needed, nneeded)) continue; + obj_symbol_remove(ob, e.id); + } + obj_symiter_free(sit); + + if (needed) h->free(h, needed, (size_t)cap_needed * sizeof(*needed)); + return KIT_OK; +} + KitStatus kit_obj_builder_find_symbol(const KitObjBuilder* b, KitSym name, KitObjSymbol* out) { ObjSymId id; diff --git a/src/api/object_file.c b/src/api/object_file.c @@ -233,6 +233,60 @@ KitStatus kit_obj_section_by_name(const KitObjFile* f, KitSlice name, return KIT_NOT_FOUND; } +int kit_obj_section_size_class(const KitObjSecInfo* sec) { + int exec, write; + if (!sec) return KIT_SEC_SIZE_NONE; + exec = (sec->flags & KIT_SF_EXEC) != 0; + write = (sec->flags & KIT_SF_WRITE) != 0; + if (!(sec->flags & KIT_SF_ALLOC)) return KIT_SEC_SIZE_NONE; + if (sec->kind == KIT_SEC_DEBUG) return KIT_SEC_SIZE_NONE; + if (sec->kind == KIT_SEC_TEXT || exec) return KIT_SEC_SIZE_TEXT; + if (sec->kind == KIT_SEC_BSS) return KIT_SEC_SIZE_BSS; + if (sec->kind == KIT_SEC_RODATA) return KIT_SEC_SIZE_DATA; + if (sec->kind == KIT_SEC_DATA || write) return KIT_SEC_SIZE_DATA; + return KIT_SEC_SIZE_DATA; +} + +KitStatus kit_obj_size_totals(KitObjFile* f, bool include_common, + KitObjSizeTotals* out) { + u32 ns, i; + if (!f || !out) return KIT_INVALID; + out->text = 0; + out->data = 0; + out->bss = 0; + out->total = 0; + ns = obj_section_count(f->ob); + for (i = 0; i < ns; ++i) { + KitObjSecInfo sec; + if (kit_obj_section(f, i, &sec) != KIT_OK) continue; + switch (kit_obj_section_size_class(&sec)) { + case KIT_SEC_SIZE_TEXT: + out->text += sec.size; + break; + case KIT_SEC_SIZE_DATA: + out->data += sec.size; + break; + case KIT_SEC_SIZE_BSS: + out->bss += sec.size; + break; + default: + break; + } + } + if (include_common) { + KitObjSymIter* it = NULL; + if (kit_obj_symiter_new(f, &it) == KIT_OK) { + KitObjSymInfo si; + while (kit_obj_symiter_next(it, &si) == KIT_ITER_ITEM) { + if (si.kind == KIT_SK_COMMON) out->bss += si.size; + } + kit_obj_symiter_free(it); + } + } + out->total = out->text + out->data + out->bss; + return KIT_OK; +} + static void fill_syminfo(const KitObjFile* f, ObjSymId id, const ObjSym* sym, KitObjSymInfo* out) { out->name = @@ -460,6 +514,90 @@ void kit_obj_reliter_free(KitObjRelocIter* it) { h->free(h, it, sizeof(*it)); } +static int reloc_live_id_in_set(KitObjSymbol id, const KitObjSymbol* arr, + u32 n) { + u32 i; + for (i = 0; i < n; ++i) + if (arr[i] == id) return 1; + return 0; +} + +KitStatus kit_obj_reloc_live_symbols(const KitContext* ctx, KitObjFile* f, + KitObjSymbol** out, uint32_t* n_out) { + Heap* h; + KitObjRelocIter* rit = NULL; + KitObjSymbol* arr = NULL; + u32 n = 0, cap = 0; + KitStatus st; + + if (!out || !n_out) return KIT_INVALID; + *out = NULL; + *n_out = 0; + if (!ctx || !ctx->heap || !f) return KIT_INVALID; + h = ctx->heap; + + st = kit_obj_reliter_new(f, &rit); + if (st != KIT_OK) return st; + for (;;) { + KitObjReloc r; + KitIterResult ir = kit_obj_reliter_next(rit, &r); + if (ir != KIT_ITER_ITEM) break; + if (r.sym == KIT_OBJ_SYMBOL_NONE) continue; + /* Skip relocs hosted in a debug section -- that section is being dropped, + * so its relocs don't actually keep their targets alive. */ + if (r.section != KIT_SECTION_NONE) { + KitObjSecInfo hi; + if (kit_obj_section(f, r.section, &hi) == KIT_OK && + hi.kind == KIT_SEC_DEBUG) + continue; + } + if (reloc_live_id_in_set(r.sym, arr, n)) continue; + if (n >= cap) { + u32 newcap = cap ? cap * 2u : 32u; + KitObjSymbol* nb = + (KitObjSymbol*)h->alloc(h, (size_t)newcap * sizeof(*nb), + _Alignof(KitObjSymbol)); + if (!nb) { + kit_obj_reliter_free(rit); + if (arr) h->free(h, arr, (size_t)cap * sizeof(*arr)); + return KIT_NOMEM; + } + if (arr) { + memcpy(nb, arr, (size_t)n * sizeof(*arr)); + h->free(h, arr, (size_t)cap * sizeof(*arr)); + } + arr = nb; + cap = newcap; + } + arr[n++] = r.sym; + } + kit_obj_reliter_free(rit); + + /* Hand back an exactly-sized array so the caller frees with n_out, not a + * private capacity. */ + if (n == 0) { + if (arr) h->free(h, arr, (size_t)cap * sizeof(*arr)); + return KIT_OK; + } + if (n != cap) { + KitObjSymbol* shrunk = + (KitObjSymbol*)h->alloc(h, (size_t)n * sizeof(*shrunk), + _Alignof(KitObjSymbol)); + if (!shrunk) { + /* Keep the over-sized buffer rather than fail; but the caller frees with + * n, which would mismatch. Safer to report NOMEM and release. */ + h->free(h, arr, (size_t)cap * sizeof(*arr)); + return KIT_NOMEM; + } + memcpy(shrunk, arr, (size_t)n * sizeof(*shrunk)); + h->free(h, arr, (size_t)cap * sizeof(*arr)); + arr = shrunk; + } + *out = arr; + *n_out = n; + return KIT_OK; +} + struct KitObjGroupIter { KitObjFile* file; ObjGroupIter* inner; diff --git a/src/api/target.c b/src/api/target.c @@ -0,0 +1,297 @@ +/* Public target-triple parsing + per-target defaults. + * + * The triple<->KitTargetSpec string layer and the spec-derived defaults + * (PIC/PIE, default output names/extensions, hosted-profile gating). Pure + * computation: no host I/O, no diagnostics — callers turn a false/0 return into + * whatever message they like. Hoisted out of the CLI driver so the spelling + * tables here are the single authority every embedder shares. + */ + +#include <kit/target.h> + +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +static bool triple_tok_eq(const char* s, size_t n, const char* lit) { + size_t l = strlen(lit); + return n == l && memcmp(s, lit, n) == 0; +} + +/* Prefix match for OS tokens that carry a trailing version, e.g. clang emits + * "freebsd15.0" / "freebsd14" rather than a bare "freebsd". */ +static bool triple_tok_prefix(const char* s, size_t n, const char* lit) { + size_t l = strlen(lit); + return n >= l && memcmp(s, lit, l) == 0; +} + +/* Recognize an architecture token, the single authority for the arch-name + * spellings kit accepts. Writes arch + natural pointer size on a hit. Returns + * true on success, false for an unrecognized token. Shared by the triple parser + * and the public kit_arch_from_name. */ +static bool arch_from_tok(const char* s, size_t n, KitArchKind* arch_out, + uint8_t* ptr_size_out) { + KitArchKind arch; + uint8_t ptr_size; + if (triple_tok_eq(s, n, "x86_64") || triple_tok_eq(s, n, "amd64") || + triple_tok_eq(s, n, "x64")) { + arch = KIT_ARCH_X86_64; + ptr_size = 8; + } else if (triple_tok_eq(s, n, "i386") || triple_tok_eq(s, n, "i486") || + triple_tok_eq(s, n, "i586") || triple_tok_eq(s, n, "i686")) { + arch = KIT_ARCH_X86_32; + ptr_size = 4; + } else if (triple_tok_eq(s, n, "aarch64") || triple_tok_eq(s, n, "arm64") || + triple_tok_eq(s, n, "aa64")) { + arch = KIT_ARCH_ARM_64; + ptr_size = 8; + } else if (triple_tok_eq(s, n, "arm") || triple_tok_eq(s, n, "armv7")) { + arch = KIT_ARCH_ARM_32; + ptr_size = 4; + } else if (triple_tok_eq(s, n, "riscv64") || triple_tok_eq(s, n, "rv64")) { + arch = KIT_ARCH_RV64; + ptr_size = 8; + } else if (triple_tok_eq(s, n, "riscv32") || triple_tok_eq(s, n, "rv32")) { + arch = KIT_ARCH_RV32; + ptr_size = 4; + } else if (triple_tok_eq(s, n, "wasm32")) { + arch = KIT_ARCH_WASM; + ptr_size = 4; + } else if (triple_tok_eq(s, n, "wasm64")) { + arch = KIT_ARCH_WASM; + ptr_size = 8; + } else { + return false; + } + if (arch_out) *arch_out = arch; + if (ptr_size_out) *ptr_size_out = ptr_size; + return true; +} + +bool kit_arch_from_name(const char* name, KitArchKind* arch_out, + uint8_t* ptr_size_out) { + if (!name) return false; + return arch_from_tok(name, strlen(name), arch_out, ptr_size_out); +} + +KitPic kit_target_default_pic(KitObjFmt obj, KitOSKind os) { + /* WASM has no PIC/PIE concept; freestanding targets have no dynamic + * loader to apply load-time relocations. Everything else is hosted and + * defaults to PIE. */ + if (obj == KIT_OBJ_WASM) return KIT_PIC_NONE; + if (os == KIT_OS_FREESTANDING) return KIT_PIC_NONE; + return KIT_PIC_PIE; +} + +int kit_target_link_pie(KitTargetSpec target, int explicit_pie, int shared, + int relocatable) { + if (explicit_pie) return 1; + if (shared || relocatable) return 0; + return target.pic == KIT_PIC_PIE; +} + +const char* kit_target_default_exe_name(KitTargetSpec target) { + /* PE/COFF executables conventionally carry a `.exe` suffix; ELF/Mach-O + * default link output is the historical `a.out`. */ + return target.os == KIT_OS_WINDOWS ? "a.exe" : "a.out"; +} + +void kit_target_default_obj_ext(KitTargetSpec target, const char** ext_out, + size_t* ext_len_out) { + /* Windows targets default to a `.obj` suffix; everyone else `.o`. Drivers + * accept both spellings as inputs, but tooling that scrapes default outputs + * expects the canonical platform extension. */ + if (target.os == KIT_OS_WINDOWS) { + if (ext_out) *ext_out = ".obj"; + if (ext_len_out) *ext_len_out = 4u; + } else { + if (ext_out) *ext_out = ".o"; + if (ext_len_out) *ext_len_out = 2u; + } +} + +int kit_target_needs_sysroot_libdir(KitTargetSpec target) { + /* Windows targets fold `<sysroot>/lib` into the library search path (the + * mingw import-library tree). The POSIX hosted profiles enumerate their + * libdirs through the hosted resolver instead. */ + return target.os == KIT_OS_WINDOWS ? 1 : 0; +} + +int kit_target_default_hosted_profile(KitTargetSpec target) { + /* Windows-COFF is the one target whose hosted libc profile is engaged by + * default (given a sysroot and no -nostdlib): the mingw/ucrt import + * libraries are mandatory to produce a runnable PE. Other targets stay + * freestanding unless the user opts in (-lc / explicit sysroot wiring). */ + return target.os == KIT_OS_WINDOWS && target.obj == KIT_OBJ_COFF ? 1 : 0; +} + +bool kit_target_from_triple(const char* triple, KitTargetSpec* out) { + const char* parts[4]; + size_t plen[4]; + int np = 0; + const char* p; + KitTargetSpec t; + int os_set; + int i; + + if (!triple || !out) return false; + memset(&t, 0, sizeof(t)); + + p = triple; + while (np < 4) { + const char* dash = strchr(p, '-'); + parts[np] = p; + plen[np] = dash ? (size_t)(dash - p) : strlen(p); + if (plen[np] == 0) return false; + np++; + if (!dash) break; + p = dash + 1; + } + + if (!arch_from_tok(parts[0], plen[0], &t.arch, &t.ptr_size)) return false; + + os_set = 0; + for (i = 1; i < np; ++i) { + if (triple_tok_eq(parts[i], plen[i], "linux")) { + t.os = KIT_OS_LINUX; + t.obj = KIT_OBJ_ELF; + os_set = 1; + break; + } + if (triple_tok_eq(parts[i], plen[i], "darwin") || + triple_tok_eq(parts[i], plen[i], "macos")) { + t.os = KIT_OS_MACOS; + t.obj = KIT_OBJ_MACHO; + os_set = 1; + break; + } + if (triple_tok_eq(parts[i], plen[i], "windows") || + triple_tok_eq(parts[i], plen[i], "win32")) { + t.os = KIT_OS_WINDOWS; + t.obj = KIT_OBJ_COFF; + os_set = 1; + break; + } + if (triple_tok_prefix(parts[i], plen[i], "freebsd")) { + const char* ver = parts[i] + 7; /* skip "freebsd" */ + size_t rem = plen[i] - 7; + unsigned v = 0; + size_t j; + for (j = 0; j < rem && ver[j] >= '0' && ver[j] <= '9'; ++j) + v = v * 10 + (unsigned)(ver[j] - '0'); + t.os = KIT_OS_FREEBSD; + t.obj = KIT_OBJ_ELF; + t.os_version_major = (uint8_t)(v > 255 ? 0 : v); + os_set = 1; + break; + } + if (triple_tok_eq(parts[i], plen[i], "wasi")) { + t.os = KIT_OS_WASI; + t.obj = KIT_OBJ_WASM; + os_set = 1; + break; + } + if (triple_tok_eq(parts[i], plen[i], "none") || + triple_tok_eq(parts[i], plen[i], "freestanding")) { + t.os = KIT_OS_FREESTANDING; + t.obj = (t.arch == KIT_ARCH_WASM) ? KIT_OBJ_WASM : KIT_OBJ_ELF; + os_set = 1; + break; + } + } + if (!os_set) { + /* No recognized OS token. This deliberately also covers bare-metal and + * vendor-only triples that carry no OS at all -- e.g. "x86_64", + * "aarch64-unknown", "riscv64-unknown-elf" -- which must keep resolving to + * a freestanding ELF/WASM target. We cannot tell an unrecognized OS token + * (e.g. "x86_64-pc-frobnicate") apart from those without a vendor/env token + * table, so erroring here would regress valid bare-metal triples. Left as a + * silent freestanding default by design; see findings note for B15. */ + t.os = KIT_OS_FREESTANDING; + t.obj = (t.arch == KIT_ARCH_WASM) ? KIT_OBJ_WASM : KIT_OBJ_ELF; + } + + t.ptr_align = t.ptr_size; + t.big_endian = 0; + t.pic = kit_target_default_pic(t.obj, t.os); + t.code_model = KIT_CM_DEFAULT; + + *out = t; + return true; +} + +bool kit_target_to_triple(KitTargetSpec target, char* buf, size_t cap) { + const char* arch; + const char* os; + int n; + if (!buf || cap == 0) return false; + + switch (target.arch) { + case KIT_ARCH_X86_64: + arch = "x86_64"; + break; + case KIT_ARCH_X86_32: + arch = "i386"; + break; + case KIT_ARCH_ARM_64: + arch = "aarch64"; + break; + case KIT_ARCH_ARM_32: + arch = "arm"; + break; + case KIT_ARCH_RV64: + arch = "riscv64"; + break; + case KIT_ARCH_RV32: + arch = "riscv32"; + break; + case KIT_ARCH_WASM: + arch = target.ptr_size == 8 ? "wasm64" : "wasm32"; + break; + default: + arch = "unknown"; + break; + } + + switch (target.os) { + case KIT_OS_LINUX: + os = "linux"; + break; + case KIT_OS_MACOS: + os = "apple-darwin"; + break; + case KIT_OS_WINDOWS: + os = "windows"; + break; + case KIT_OS_FREEBSD: + os = "freebsd"; + break; + case KIT_OS_WASI: + os = "wasi"; + break; + case KIT_OS_FREESTANDING: + default: + os = "elf"; + break; + } + + n = snprintf(buf, cap, "%s-%s", arch, os); + return !(n < 0 || (size_t)n >= cap); +} + +bool kit_target_code_model_from_name(const char* val, KitCodeModel* out) { + KitCodeModel cm; + if (!val) return false; + if (strcmp(val, "small") == 0 || strcmp(val, "medlow") == 0) { + cm = KIT_CM_SMALL; + } else if (strcmp(val, "medium") == 0 || strcmp(val, "medany") == 0) { + cm = KIT_CM_MEDIUM; + } else if (strcmp(val, "large") == 0) { + cm = KIT_CM_LARGE; + } else { + return false; + } + if (out) *out = cm; + return true; +} diff --git a/src/debug/dwarf_dump.c b/src/debug/dwarf_dump.c @@ -471,3 +471,180 @@ KitIterResult kit_dwarf_str_iter_next(KitDwarfStrIter* it, KitDwarfStr* out) { void kit_dwarf_str_iter_free(KitDwarfStrIter* it) { if (it) dw_iter_free(it->d, it, sizeof(*it)); } + +/* ---- symbolic name tables -------------------------------------------- + * + * Canonical spellings for the numeric codes the iterators above hand back, + * so every DWARF dumper shares one table instead of copying its own. An + * unrecognized code returns NULL — the caller decides how to render the + * miss (objdump falls back to hex). The covered set matches what the + * dumpers actually emit; extend here when a new code needs a name. */ + +const char* kit_dwarf_tag_name(uint32_t tag) { + switch (tag) { + case 0x01: + return "DW_TAG_array_type"; + case 0x04: + return "DW_TAG_enumeration_type"; + case 0x05: + return "DW_TAG_formal_parameter"; + case 0x0b: + return "DW_TAG_lexical_block"; + case 0x0d: + return "DW_TAG_member"; + case 0x0f: + return "DW_TAG_pointer_type"; + case 0x11: + return "DW_TAG_compile_unit"; + case 0x13: + return "DW_TAG_structure_type"; + case 0x15: + return "DW_TAG_subroutine_type"; + case 0x16: + return "DW_TAG_typedef"; + case 0x17: + return "DW_TAG_union_type"; + case 0x18: + return "DW_TAG_unspecified_parameters"; + case 0x1d: + return "DW_TAG_inlined_subroutine"; + case 0x21: + return "DW_TAG_subrange_type"; + case 0x24: + return "DW_TAG_base_type"; + case 0x26: + return "DW_TAG_const_type"; + case 0x28: + return "DW_TAG_enumerator"; + case 0x2e: + return "DW_TAG_subprogram"; + case 0x34: + return "DW_TAG_variable"; + case 0x35: + return "DW_TAG_volatile_type"; + case 0x37: + return "DW_TAG_restrict_type"; + case 0x3b: + return "DW_TAG_unspecified_type"; + default: + return NULL; + } +} + +const char* kit_dwarf_attr_name(uint32_t attr) { + switch (attr) { + case 0x01: + return "DW_AT_sibling"; + case 0x02: + return "DW_AT_location"; + case 0x03: + return "DW_AT_name"; + case 0x0b: + return "DW_AT_byte_size"; + case 0x0d: + return "DW_AT_bit_size"; + case 0x10: + return "DW_AT_stmt_list"; + case 0x11: + return "DW_AT_low_pc"; + case 0x12: + return "DW_AT_high_pc"; + case 0x13: + return "DW_AT_language"; + case 0x1b: + return "DW_AT_comp_dir"; + case 0x1c: + return "DW_AT_const_value"; + case 0x25: + return "DW_AT_producer"; + case 0x27: + return "DW_AT_prototyped"; + case 0x2f: + return "DW_AT_upper_bound"; + case 0x34: + return "DW_AT_artificial"; + case 0x37: + return "DW_AT_count"; + case 0x38: + return "DW_AT_data_member_location"; + case 0x39: + return "DW_AT_decl_column"; + case 0x3a: + return "DW_AT_decl_file"; + case 0x3b: + return "DW_AT_decl_line"; + case 0x3c: + return "DW_AT_declaration"; + case 0x3e: + return "DW_AT_encoding"; + case 0x3f: + return "DW_AT_external"; + case 0x40: + return "DW_AT_frame_base"; + case 0x49: + return "DW_AT_type"; + case 0x6e: + return "DW_AT_linkage_name"; + case 0x88: + return "DW_AT_alignment"; + default: + return NULL; + } +} + +const char* kit_dwarf_form_name(uint32_t form) { + switch (form) { + case 0x01: + return "DW_FORM_addr"; + case 0x05: + return "DW_FORM_data2"; + case 0x06: + return "DW_FORM_data4"; + case 0x07: + return "DW_FORM_data8"; + case 0x08: + return "DW_FORM_string"; + case 0x09: + return "DW_FORM_block"; + case 0x0b: + return "DW_FORM_data1"; + case 0x0c: + return "DW_FORM_flag"; + case 0x0d: + return "DW_FORM_sdata"; + case 0x0e: + return "DW_FORM_strp"; + case 0x0f: + return "DW_FORM_udata"; + case 0x10: + return "DW_FORM_ref_addr"; + case 0x11: + return "DW_FORM_ref1"; + case 0x12: + return "DW_FORM_ref2"; + case 0x13: + return "DW_FORM_ref4"; + case 0x14: + return "DW_FORM_ref8"; + case 0x15: + return "DW_FORM_ref_udata"; + case 0x17: + return "DW_FORM_sec_offset"; + case 0x18: + return "DW_FORM_exprloc"; + case 0x19: + return "DW_FORM_flag_present"; + case 0x1a: + return "DW_FORM_strx"; + case 0x1b: + return "DW_FORM_addrx"; + case 0x1f: + return "DW_FORM_line_strp"; + case 0x21: + return "DW_FORM_implicit_const"; + case 0x25: + return "DW_FORM_strx1"; + default: + return NULL; + } +} diff --git a/src/debug/dwarf_query.c b/src/debug/dwarf_query.c @@ -78,6 +78,31 @@ KitStatus kit_dwarf_func_at(KitDebugInfo* d, uint64_t pc, KitSlice* name_out, return KIT_OK; } +KitStatus kit_dwarf_resolve(KitDebugInfo* d, uint64_t addr, int want_func, + KitDwarfResolve* out) { + KitSlice file; + uint32_t line = 0, col = 0; + if (!d || !out) return KIT_INVALID; + memset(out, 0, sizeof(*out)); + + if (kit_dwarf_addr_to_line(d, addr, &file, &line, &col) == KIT_OK) { + out->have_line = 1; + out->file = file; + out->line = line; + out->col = col; + } + + if (want_func) { + KitSlice func; + uint64_t func_lo = 0, func_hi = 0; + if (kit_dwarf_func_at(d, addr, &func, &func_lo, &func_hi) == KIT_OK) { + out->have_func = 1; + out->func = func; + } + } + return KIT_OK; +} + /* ---- variable resolution -------------------------------------------- */ static void fill_varloc(KitDebugInfo* d, u32 cu_idx, const DwLocal* v, u64 pc, diff --git a/test/driver/run.sh b/test/driver/run.sh @@ -202,12 +202,13 @@ fi # ---- cc -print-search-dirs surfaces the hosted sysroot dirs ---- # KIT_SYSROOT + a cross target makes the output deterministic on any host -# (independent of a native SDK). The Linux expansion must surface <sysroot>/lib, -# <sysroot>/include, and the arch multiarch include subdir. +# (independent of a native SDK). The Linux expansion uses the standard FHS +# sysroot layout: libc/kernel headers under <sysroot>/usr/include (plus the +# glibc multiarch subdir) and libraries under <sysroot>/usr/lib. if KIT_SYSROOT="$work/sr" "$KIT" cc -print-search-dirs -lc -target x86_64-linux \ > "$work/cc-searchdirs.out" 2> "$work/cc-searchdirs.err" && - grep -q "$work/sr/lib" "$work/cc-searchdirs.out" && - grep -q "$work/sr/include" "$work/cc-searchdirs.out" && + grep -q "$work/sr/usr/lib" "$work/cc-searchdirs.out" && + grep -q "$work/sr/usr/include" "$work/cc-searchdirs.out" && grep -q "x86_64-linux-gnu" "$work/cc-searchdirs.out"; then ok "cc-print-search-dirs-hosted" else