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:
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,
- <o_batch, out_w);
+ st = kit_build_link_with_lto(compiler, &lopts, &li, &pending_lto,
+ <o_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,
- <o_batch, out_w);
+ st = kit_build_link_with_lto(compiler, &lopts, &li, &pending_lto,
+ <o_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, <o_batch, &cout);
+ st = kit_build_compile(compiler, &copts.code, &copts.diagnostics, sources,
+ nsrc, <o_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,
- <o_batch, out_w);
+ st = kit_build_link_with_lto(compiler, &lopts, &li, &pending_lto,
+ <o_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