kit

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

commit 8812d246da197618158da26b55ec717632e8bcb7
parent f8a6d1101582182af525f54ad681e7467a83014b
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue,  9 Jun 2026 15:17:45 -0700

build: wasm multi-source via CG-merge in build-obj

build-obj -target wasm32-none a.c b.c -o out.wasm now merges all inputs
into one .wasm via the LTO CG-merge path, bypassing the linker (which has
no wasm support). The linker's relocatable writer dropped the OBJ_EXT_WASM
payload, producing an empty 8-byte stub; the new build_run_wasm_module
function finishes the merged ObjBuilder inline and emits directly.

Cross-TU calls and cross-TU data references work at all opt levels.
Same-name statics stay local per TU (LOCAL binding → distinct ObjSymIds).
Duplicate strong defs are rejected at -O1 via opt_resolve_duplicate_funcs.

Tests: structural (bo-wasm-multi, bo-wasm-polyglot, bo-wasm-dup) in
test-driver-build; execution (multi-tu/N interp, multi-tu/D JIT) in
test-wasm-front.

Diffstat:
Mdriver/cmd/build.c | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/buildcmds/run.sh | 32++++++++++++++++++++++++++++++++
Mtest/wasm/run.sh | 32++++++++++++++++++++++++++++++++
3 files changed, 125 insertions(+), 0 deletions(-)

diff --git a/driver/cmd/build.c b/driver/cmd/build.c @@ -1755,6 +1755,64 @@ out: return rc; } +/* build-obj wasm multi-source: merge all inputs into one .wasm via CG merge. */ +static int build_run_wasm_module(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; + 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_RELOCATABLE; + batch.interposition_policy = KIT_CG_INTERPOSITION_DEFAULT; + if (build_compile_all(o, compiler, ctx, &code2, diag, objs, source_obj_index, + source_order_keep, &batch, NULL, &nobjs) != 0) + goto out; + } + if (nobjs != 1) { + driver_errf(o->tool, + "build-obj: wasm multi-input expects a single merged module"); + 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 (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, @@ -2116,6 +2174,9 @@ static int build_main(int argc, char** argv, int kind, const char* tool) { : build_run_archive(&o, compiler, &ctx, &code, &diag); } else if (o.syntax_only) { rc = build_run_per_source(&o, compiler, &ctx, &code, &diag); + } else if (o.emit == BUILD_EMIT_OBJ && o.nsources > 1 && + o.target.obj == KIT_OBJ_WASM) { + rc = build_run_wasm_module(&o, compiler, &ctx, &code, &diag); } else if (o.emit == BUILD_EMIT_OBJ && o.nsources > 1) { rc = build_run_relocatable(&o, compiler, &ctx, &code, &diag); } else { diff --git a/test/buildcmds/run.sh b/test/buildcmds/run.sh @@ -40,6 +40,8 @@ printf 'int helper(void){return 0;}\n' > "$work/helper.c" printf '#include <stdint.h>\nint32_t f(void){return 1;}\n' > "$work/std.c" 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" cd "$work" @@ -110,6 +112,36 @@ same_file bo-reloc-ld-r-parity ldr.sorted combine.sorted # Polyglot combine: C + toy in one relocatable object. run_ok bo-polyglot-combine "$KIT" build-obj -Iinc helper.c prog.toy -o poly.o +# ---- wasm multi-source combine (CG-merge path) ----------------------------- +# Two C files merged into one .wasm module (magic \0asm + non-empty content). +run_ok bo-wasm-multi "$KIT" build-obj -target wasm32-none \ + wasm_main.c wasm_helper.c -o multi.wasm +assert_file_exists bo-wasm-multi-file multi.wasm +# Wasm magic (\0asm = 00 61 73 6d): check first 4 bytes via od (avoids null-byte +# issues with cmp on BSD). +_wasm_hdr=$(od -A n -t x1 -N 4 multi.wasm | tr -d ' \t\n') +[ "$_wasm_hdr" = "0061736d" ] \ + && ok bo-wasm-multi-magic || not_ok bo-wasm-multi-magic +# Non-trivial module: size must exceed the 8-byte magic+version stub. +_wasm_size=$(wc -c < multi.wasm | tr -d ' ') +[ "$_wasm_size" -gt 8 ] \ + && ok bo-wasm-multi-nontrivial || not_ok bo-wasm-multi-nontrivial + +# Polyglot wasm: C + toy in one .wasm module. +run_ok bo-wasm-polyglot "$KIT" build-obj -target wasm32-none \ + wasm_helper.c prog.toy -o poly.wasm +assert_file_exists bo-wasm-polyglot-file poly.wasm +_poly_hdr=$(od -A n -t x1 -N 4 poly.wasm | tr -d ' \t\n') +[ "$_poly_hdr" = "0061736d" ] \ + && ok bo-wasm-polyglot-magic || not_ok bo-wasm-polyglot-magic + +# Negative: duplicate strong symbol definition must be rejected (at -O1 the +# optimizer runs opt_resolve_duplicate_funcs which fires the ODR check). +printf 'int wasm_add(int a, int b){return a-b;}\n' > wasm_dup.c +run_fail bo-wasm-dup "$KIT" build-obj -O1 -target wasm32-none \ + wasm_helper.c wasm_dup.c -o dup.wasm +contains bo-wasm-dup-diag "$work/bo-wasm-dup.err" "duplicate definition" + # ---- negative paths -------------------------------------------------------- # A frontend with an unknown -X flag is rejected by that frontend's parser. run_fail bo-neg-bad-Xflag "$KIT" build-obj -c -Xtoy --bogus prog.toy -o x.o diff --git a/test/wasm/run.sh b/test/wasm/run.sh @@ -348,5 +348,37 @@ if [ "$RUN_DIAG" -eq 1 ]; then wf_fail "err/malformed-leb/wasm" "$KIT_BIN" cc -target "$target_triple" -c "$KIT_WORK/malformed-leb.wasm" -o "$KIT_WORK/bad-leb.o" fi +# ---- multi-source wasm (cross-TU call + cross-TU data reference) ----------- +if [ "$RUN_D" -eq 1 ] || [ "$RUN_N" -eq 1 ]; then + KIT_WORK="$BUILD_DIR/multi-tu"; rm -rf "$KIT_WORK"; mkdir -p "$KIT_WORK" + cat > "$KIT_WORK/mtu_helper.c" <<'EOF' +int g_count = 0; +int add_and_count(int a, int b) { g_count++; return a + b; } +EOF + cat > "$KIT_WORK/mtu_main.c" <<'EOF' +extern int g_count; +int add_and_count(int a, int b); +int test_main(void) { + int r = add_and_count(3, 4); + return (r == 7 && g_count == 1) ? 0 : 1; +} +EOF + _mtu_wasm="$KIT_WORK/multi_tu.wasm" + if "$KIT_BIN" build-obj -target wasm32-none \ + "$KIT_WORK/mtu_main.c" "$KIT_WORK/mtu_helper.c" \ + -o "$_mtu_wasm" >"$KIT_WORK/build.out" 2>"$KIT_WORK/build.err"; then + if [ "$RUN_N" -eq 1 ]; then + wf_rc_interp "multi-tu/N" 0 \ + "$KIT_BIN" run --no-jit -e test_main "$_mtu_wasm" + fi + if [ "$RUN_D" -eq 1 ]; then + wf_rc "multi-tu/D" 0 \ + "$KIT_BIN" run -e test_main "$_mtu_wasm" + fi + else + kit_fail "multi-tu/build" "build-obj failed" + fi +fi + kit_summary test-wasm-front kit_exit