commit e461b84e8baeee4238c130f0cd23f09cd8b95a81
parent 9102a9951e9c68f81a05ac78565d71a43c3a337d
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 9 Jun 2026 15:48:08 -0700
wasm: kit-entry custom section for auto-detecting entry in kit run
build-exe for wasm now embeds a kit-entry custom section (section id=0,
name="kit-entry") containing the entry function name. kit run reads this
section from the .wasm file header via a lightweight binary scanner and
uses it as the default entry, so `kit run out.wasm` works without -e.
Diffstat:
6 files changed, 102 insertions(+), 0 deletions(-)
diff --git a/driver/cmd/build.c b/driver/cmd/build.c
@@ -1881,6 +1881,8 @@ static int build_run_wasm_exe(BuildOptions* o, KitCompiler* compiler,
driver_errf(o->tool, "build-exe: LTO finish failed");
goto out;
}
+ kit_obj_builder_wasm_add_custom(objs[0], KIT_SLICE_LIT("kit-entry"),
+ kit_slice_cstr(entry_name));
}
if (build_open_output(ctx, env, o->tool, o->output_path, &out_w) != 0)
goto out;
diff --git a/driver/cmd/run.c b/driver/cmd/run.c
@@ -461,6 +461,57 @@ static int run_path_is_wasm_source(const char* path) {
return driver_has_suffix(path, ".wat") || driver_has_suffix(path, ".wasm");
}
+/* Scan raw wasm bytes for a custom section named `want`.
+ * On success sets *len_out and returns a pointer into `p`; returns NULL on
+ * miss or malformed input. The returned pointer is valid only while `p` is. */
+static const char* wasm_scan_custom(const uint8_t* p, size_t total,
+ const char* want, uint32_t* len_out) {
+ const uint8_t* end = p + total;
+ uint8_t id;
+ uint32_t size, shift;
+ const uint8_t* sec_end;
+ if (total < 8) return NULL;
+ p += 8; /* skip magic + version */
+ while (p < end) {
+ uint8_t b;
+ id = *p++;
+ size = 0; shift = 0;
+ for (;;) {
+ if (p >= end) return NULL;
+ b = *p++;
+ size |= (uint32_t)(b & 0x7f) << shift;
+ if (!(b & 0x80)) break;
+ shift += 7;
+ if (shift > 28) return NULL;
+ }
+ if ((uint32_t)(end - p) < size) return NULL;
+ sec_end = p + size;
+ if (id == 0) {
+ uint32_t nlen = 0;
+ shift = 0;
+ for (;;) {
+ if (p >= sec_end) { p = sec_end; goto next_section; }
+ b = *p++;
+ nlen |= (uint32_t)(b & 0x7f) << shift;
+ if (!(b & 0x80)) break;
+ shift += 7;
+ if (shift > 28) { p = sec_end; goto next_section; }
+ }
+ if ((uint32_t)(sec_end - p) >= nlen) {
+ size_t wlen = driver_strlen(want);
+ if (nlen == (uint32_t)wlen && memcmp(p, want, wlen) == 0) {
+ p += nlen;
+ *len_out = (uint32_t)(sec_end - p);
+ return (const char*)p;
+ }
+ }
+ }
+ next_section:
+ p = sec_end;
+ }
+ return NULL;
+}
+
static int run_inputs_have_wasm_source(const DriverInputs* in) {
uint32_t i;
for (i = 0; i < in->nsources; ++i)
@@ -714,6 +765,28 @@ static int run_parse(int argc, char** argv, RunOptions* o) {
"sandboxed wasm run does not support mixed native inputs");
return 1;
}
+ if (!o->entry && run_inputs_have_wasm_source(&o->inputs) &&
+ !run_inputs_have_non_wasm(&o->inputs) &&
+ o->inputs.nsources == 1) {
+ DriverLoad lf = {0};
+ KitSlice bytes = {0};
+ if (driver_load_bytes(&o->env->file_io, RUN_TOOL,
+ o->inputs.sources[0], &lf, &bytes) == 0) {
+ uint32_t elen = 0;
+ const char* ep = wasm_scan_custom((const uint8_t*)bytes.s,
+ (size_t)bytes.len,
+ "kit-entry", &elen);
+ if (ep && elen > 0 && elen < 256) {
+ char* buf = (char*)o->env->heap->alloc(o->env->heap, elen + 1u, 1);
+ if (buf) {
+ memcpy(buf, ep, elen);
+ buf[elen] = '\0';
+ o->entry = buf;
+ }
+ }
+ driver_release_bytes(&o->env->file_io, &lf);
+ }
+ }
if (!o->entry) o->entry = "main";
if (run_apply_hosted_profile(o) != 0) return 1;
diff --git a/include/kit/object.h b/include/kit/object.h
@@ -216,6 +216,8 @@ 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_wasm_add_custom(KitObjBuilder*, KitSlice name,
+ KitSlice data);
KIT_API KitStatus kit_obj_builder_section_replace_bytes(KitObjBuilder*,
KitObjSection,
const void* data,
diff --git a/src/obj/wasm/emit.c b/src/obj/wasm/emit.c
@@ -11,6 +11,26 @@
#include "obj/wasm/wasm.h"
#include "wasm/wasm.h"
+KitStatus kit_obj_builder_wasm_add_custom(KitObjBuilder* b, KitSlice name,
+ KitSlice data) {
+ ObjBuilder* ob = (ObjBuilder*)b;
+ Compiler* c = obj_compiler(ob);
+ WasmModule* m = (WasmModule*)obj_ext_get(ob, OBJ_EXT_WASM);
+ WasmCustom* cs;
+ if (!m || !c) return KIT_NOT_FOUND;
+ cs = wasm_add_custom(c, m);
+ if (!cs) return KIT_NOMEM;
+ cs->name = wasm_strdup(m->heap, name.s, (size_t)name.len);
+ if (!cs->name) return KIT_NOMEM;
+ if (data.len) {
+ cs->data = (uint8_t*)m->heap->alloc(m->heap, (size_t)data.len, 1);
+ if (!cs->data) return KIT_NOMEM;
+ memcpy(cs->data, data.s, (size_t)data.len);
+ cs->len = (uint32_t)data.len;
+ }
+ return KIT_OK;
+}
+
void emit_wasm(Compiler* c, ObjBuilder* o, Writer* w) {
WasmModule* m = (WasmModule*)obj_ext_get(o, OBJ_EXT_WASM);
if (m) {
diff --git a/test/buildcmds/run.sh b/test/buildcmds/run.sh
@@ -248,6 +248,9 @@ 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
+# kit run without -e reads the kit-entry custom section from the module.
+run_ok be-wasm-exe-autoentry "$KIT" run wexe.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
diff --git a/test/wasm/run.sh b/test/wasm/run.sh
@@ -400,6 +400,8 @@ EOF
"$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"
+ [ "$RUN_D" -eq 1 ] && wf_rc "wasm-exe/D-autoentry" 0 \
+ "$KIT_BIN" run "$_wasm_exe"
else
kit_fail "wasm-exe/build" "build-exe failed"
fi