kit

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

commit 9102a9951e9c68f81a05ac78565d71a43c3a337d
parent 8812d246da197618158da26b55ec717632e8bcb7
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue,  9 Jun 2026 15:29:08 -0700

build: wasm build-exe via CG-merge with DCE from entry point

kit build-exe -target wasm32-none a.c b.c -e entry -o out.wasm now
produces a runnable .wasm. Uses KIT_CG_OUTPUT_EXECUTABLE so DCE and
symbol internalization fire (unreachable code stripped, non-exported
symbols go local), giving a leaner module than build-obj's RELOCATABLE
output.

The entry symbol (-e, defaulting to "main") is resolved after compilation
via a new kit_obj_builder_find_symbol API and passed as the preserved
symbol to driver_compile_pending_lto_finish, keeping the entry alive
through DCE. build_is_link_output returns 0 for wasm exe so the hosted
libc/sysroot/runtime-archive setup is skipped. Default output name is
a.wasm. Link inputs (.o/.a/-l) are rejected with a diagnostic.

Tests: structural (be-wasm-exe, default name, bad-entry negative) in
test-driver-build; execution (wasm-exe/D JIT, wasm-exe/N interp) in
test-wasm-front.

Diffstat:
Mdriver/cmd/build.c | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Minclude/kit/object.h | 2++
Msrc/api/object_builder.c | 9+++++++++
Mtest/buildcmds/run.sh | 25+++++++++++++++++++++++++
Mtest/wasm/run.sh | 25+++++++++++++++++++++++++
5 files changed, 168 insertions(+), 2 deletions(-)

diff --git a/driver/cmd/build.c b/driver/cmd/build.c @@ -953,6 +953,7 @@ static int build_parse(int argc, char** argv, BuildOptions* o) { /* ===================================================================== */ static int build_is_link_output(const BuildOptions* o) { + if (o->kind == BUILD_OUT_EXE && o->target.obj == KIT_OBJ_WASM) return 0; return o->kind == BUILD_OUT_EXE || (o->kind == BUILD_OUT_LIB && o->dynamic); } @@ -1813,6 +1814,93 @@ out: return rc; } +/* build-exe wasm: merge all sources into one .wasm with DCE/internalization. */ +static int build_run_wasm_exe(BuildOptions* o, KitCompiler* compiler, + const KitContext* ctx, + const KitCodeOptions* code, + const KitDiagnosticOptions* diag) { + DriverEnv* env = o->env; + KitObjBuilder** objs = NULL; + uint32_t* source_obj_index = NULL; + uint8_t* source_order_keep = NULL; + DriverCompilePendingLto pending_lto = {0}; + KitWriter* out_w = NULL; + KitCodeOptions code2 = *code; + uint32_t nobjs = 0; + uint32_t i; + int rc = 1; + + code2.lto = true; + + objs = driver_alloc_zeroed(env, o->nsources * sizeof(*objs)); + source_obj_index = + driver_alloc_zeroed(env, o->nsources * sizeof(*source_obj_index)); + source_order_keep = + driver_alloc_zeroed(env, o->nsources * sizeof(*source_order_keep)); + if (!objs || !source_obj_index || !source_order_keep) { + driver_errf(o->tool, "out of memory"); + goto out; + } + { + DriverCompileBatchOptions batch; + memset(&batch, 0, sizeof batch); + batch.output_kind = KIT_CG_OUTPUT_EXECUTABLE; + batch.interposition_policy = KIT_CG_INTERPOSITION_NONE; + batch.defer_lto_finish = 1; + if (build_compile_all(o, compiler, ctx, &code2, diag, objs, + source_obj_index, source_order_keep, + &batch, &pending_lto, &nobjs) != 0) + goto out; + } + if (nobjs != 1) { + driver_errf(o->tool, "build-exe: wasm expects a single merged module"); + goto out; + } + { + const char* entry_name = o->link.entry ? o->link.entry : "main"; + KitSym entry_interned = + kit_sym_intern(compiler, kit_slice_cstr(entry_name)); + KitObjSymbol entry_sym = KIT_OBJ_SYMBOL_NONE; + DriverCompileBatchOptions batch2; + KitCgSym csym; + + if (!entry_interned || + kit_obj_builder_find_symbol(pending_lto.obj, entry_interned, + &entry_sym) != KIT_OK) { + driver_errf(o->tool, + "build-exe: entry symbol '%s' not found in module", + entry_name); + goto out; + } + csym = (KitCgSym)entry_sym; + 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) != KIT_OK) { + driver_errf(o->tool, "build-exe: LTO finish failed"); + goto out; + } + } + if (build_open_output(ctx, env, o->tool, o->output_path, &out_w) != 0) + goto out; + rc = (kit_obj_builder_emit(objs[0], out_w) == KIT_OK) ? 0 : 1; + +out: + if (out_w) kit_writer_close(out_w); + if (pending_lto.active) driver_compile_pending_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)); + } + if (source_order_keep) + driver_free(env, source_order_keep, + o->nsources * sizeof(*source_order_keep)); + if (source_obj_index) + driver_free(env, source_obj_index, o->nsources * sizeof(*source_obj_index)); + return rc; +} + /* build-lib static: archive every compiled source object. */ static int build_run_archive(BuildOptions* o, KitCompiler* compiler, const KitContext* ctx, const KitCodeOptions* code, @@ -1968,7 +2056,21 @@ static int build_validate(BuildOptions* o) { driver_errf(o->tool, "--emit/-S/-fsyntax-only are build-obj options"); return 1; } - if (!o->output_path) o->output_path = driver_default_exe_name(o->target); + if (!o->output_path) { + o->output_path = (o->target.obj == KIT_OBJ_WASM) + ? "a.wasm" + : driver_default_exe_name(o->target); + } + if (o->target.obj == KIT_OBJ_WASM) { + uint32_t total_link = + o->nobject_files + o->narchives + o->ndsos + o->npending_libs; + if (total_link) { + driver_errf(o->tool, + "build-exe -target wasm32-none accepts only source files " + "(no .o / .a / -l inputs)"); + return 1; + } + } return 0; } @@ -2167,7 +2269,10 @@ static int build_main(int argc, char** argv, int kind, const char* tool) { diag.max_errors = o.max_errors; if (o.kind == BUILD_OUT_EXE) { - rc = build_run_link(&o, compiler, &ctx, &code, &diag, KIT_LINK_OUTPUT_EXE); + if (o.target.obj == KIT_OBJ_WASM) + rc = build_run_wasm_exe(&o, compiler, &ctx, &code, &diag); + else + rc = build_run_link(&o, compiler, &ctx, &code, &diag, KIT_LINK_OUTPUT_EXE); } else if (o.kind == BUILD_OUT_LIB) { rc = o.shared ? build_run_link(&o, compiler, &ctx, &code, &diag, KIT_LINK_OUTPUT_SHARED) diff --git a/include/kit/object.h b/include/kit/object.h @@ -214,6 +214,8 @@ KIT_API KitStatus kit_obj_builder_symbol_set_bind(KitObjBuilder*, KitObjSymbol, KitSymBind); KIT_API KitStatus kit_obj_builder_symbol_set_vis(KitObjBuilder*, KitObjSymbol, KitSymVis); +KIT_API KitStatus kit_obj_builder_find_symbol(const KitObjBuilder*, KitSym, + KitObjSymbol* out); KIT_API KitStatus kit_obj_builder_section_replace_bytes(KitObjBuilder*, KitObjSection, const void* data, diff --git a/src/api/object_builder.c b/src/api/object_builder.c @@ -264,6 +264,15 @@ KitStatus kit_obj_builder_section_replace_bytes(KitObjBuilder* b, return KIT_OK; } +KitStatus kit_obj_builder_find_symbol(const KitObjBuilder* b, KitSym name, + KitObjSymbol* out) { + ObjSymId id; + if (!b || !out) return KIT_INVALID; + id = obj_symbol_find((ObjBuilder*)b, (Sym)name); + *out = intern_to_pub_sym(id); + return id != OBJ_SYM_NONE ? KIT_OK : KIT_NOT_FOUND; +} + KitStatus kit_obj_builder_emit(KitObjBuilder* b, KitWriter* w) { Compiler* c; if (!b || !w) return KIT_INVALID; diff --git a/test/buildcmds/run.sh b/test/buildcmds/run.sh @@ -42,6 +42,8 @@ cp "$(ls "$repo_root"/test/toy/cases/*.toy | head -1)" "$work/prog.toy" cp "$repo_root/test/wasm/cases/if_return.wat" "$work/prog.wat" printf 'int wasm_add(int a, int b);\nint wasm_main(void){return wasm_add(1,2)==3?0:1;}\n' > "$work/wasm_main.c" printf 'int wasm_add(int a, int b){return a+b;}\n' > "$work/wasm_helper.c" +printf 'int wexe_add(int a, int b);\nint test_main(void){return wexe_add(2,3)==5?0:1;}\n' > "$work/wexe_main.c" +printf 'int wexe_add(int a, int b){return a+b;}\nint dead_fn(void){return 99;}\n' > "$work/wexe_add.c" cd "$work" @@ -229,5 +231,28 @@ printf 'int helper(void);\nint main(void){return helper();}\n' > usemix.c run_ok be-link-archive "$KIT" build-exe -lc usemix.c -L. -lmix -o app2 run_ok be-run-archive ./app2 +# =========================================================================== +# build-exe — wasm target +# =========================================================================== + +# Multi-source: produces a .wasm with DCE/internalization, runnable via kit run. +run_ok be-wasm-exe "$KIT" build-exe -target wasm32-none \ + wexe_main.c wexe_add.c -e test_main -o wexe.wasm +assert_file_exists be-wasm-exe-file wexe.wasm +_wexe_hdr=$(od -A n -t x1 -N 4 wexe.wasm | tr -d ' \t\n') +[ "$_wexe_hdr" = "0061736d" ] && ok be-wasm-exe-magic || not_ok be-wasm-exe-magic +run_ok be-wasm-exe-run "$KIT" run -e test_main wexe.wasm + +# Default output name a.wasm when -o is omitted. +run_ok be-wasm-exe-default-name "$KIT" build-exe -target wasm32-none \ + wexe_main.c wexe_add.c -e test_main +assert_file_exists be-wasm-exe-default-name-file a.wasm + +# Unknown entry symbol is rejected with a clear diagnostic. +run_fail be-wasm-exe-bad-entry "$KIT" build-exe -target wasm32-none \ + wexe_main.c wexe_add.c -e no_such_fn -o bad_entry.wasm +contains be-wasm-exe-bad-entry-diag "$work/be-wasm-exe-bad-entry.err" \ + "entry symbol" + kit_summary build-driver kit_exit diff --git a/test/wasm/run.sh b/test/wasm/run.sh @@ -380,5 +380,30 @@ EOF fi fi +# ---- wasm build-exe: DCE from entry point, runnable via kit run ------------- +if [ "$RUN_D" -eq 1 ] || [ "$RUN_N" -eq 1 ]; then + KIT_WORK="$BUILD_DIR/wasm-exe"; rm -rf "$KIT_WORK"; mkdir -p "$KIT_WORK" + cat > "$KIT_WORK/exe_add.c" <<'EOF' +int add(int a, int b) { return a + b; } +int dead_fn(void) { return 99; } +EOF + cat > "$KIT_WORK/exe_main.c" <<'EOF' +int add(int a, int b); +int test_main(void) { return add(2, 3) == 5 ? 0 : 1; } +EOF + _wasm_exe="$KIT_WORK/out.wasm" + if "$KIT_BIN" build-exe -target wasm32-none \ + "$KIT_WORK/exe_main.c" "$KIT_WORK/exe_add.c" \ + -e test_main -o "$_wasm_exe" \ + >"$KIT_WORK/build.out" 2>"$KIT_WORK/build.err"; then + [ "$RUN_D" -eq 1 ] && wf_rc "wasm-exe/D" 0 \ + "$KIT_BIN" run -e test_main "$_wasm_exe" + [ "$RUN_N" -eq 1 ] && wf_rc_interp "wasm-exe/N" 0 \ + "$KIT_BIN" run --no-jit -e test_main "$_wasm_exe" + else + kit_fail "wasm-exe/build" "build-exe failed" + fi +fi + kit_summary test-wasm-front kit_exit