kit

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

commit 6ccc62a9ce1e2feed3f6d085b011571cccfc8447
parent 9bd665b95d72f696db659675bf3c85367e1b8cf1
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Wed,  3 Jun 2026 20:28:36 -0700

Implement wasm sandbox host shim

Diffstat:
Mdriver/cmd/run.c | 413++++++++++++++++++++-----------------------------------------------------------
Mdriver/env.h | 8++++++++
Mdriver/env/posix.c | 20+++++++++++++++++---
Mdriver/env/windows.c | 18++++++++++++++++--
Adriver/lib/wasm_run.c | 962+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adriver/lib/wasm_run.h | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minclude/kit/wasm.h | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmk/driver_srcs.mk | 1+
Mmk/test.mk | 6+++++-
Msrc/abi/abi.h | 4++++
Msrc/abi/abi_apple_arm64.c | 1+
Asrc/api/wasm_host.c | 816+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/arch/aa64/native.c | 58++++++++++++++++++++++++++++++++++------------------------
Atest/driver-wasm/run.sh | 325+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/wasm/run.sh | 10+++++-----
15 files changed, 2457 insertions(+), 343 deletions(-)

diff --git a/driver/cmd/run.c b/driver/cmd/run.c @@ -3,7 +3,6 @@ #include <kit/interp.h> #include <kit/jit.h> #include <kit/link.h> -#include <kit/wasm.h> #include <stdint.h> #include <stdlib.h> #include <string.h> @@ -12,6 +11,7 @@ #include "driver.h" #include "hosted.h" #include "inputs.h" +#include "wasm_run.h" /* `kit run` — JIT-compile one or more inputs and invoke the entry symbol * (default `main`) in-process. Args after `--` are passed to the JITed @@ -19,10 +19,11 @@ * .o objects, .a archives) and target/diagnostic flag surface; libkit's * JIT path forces PIC regardless of `-fPIC`/`-fPIE`/`-mcmodel`. * - * Host-symbol fallback (so JITed code can call libc) goes through - * driver_dlsym_resolver in driver/env.c. The driver returns whatever the - * entry returns, or 1 on a compile/link/lookup error. The entry is invoked - * as `int(*)(int, char**)`. */ + * Native host-symbol fallback (so JITed C/Toy/object code can call libc) goes + * through driver_dlsym_resolver in driver/env.c. Wasm source inputs do not get + * that fallback; host access is controlled by the Wasm import policy. The + * driver returns whatever the entry returns, or 1 on a compile/link/lookup + * error. The native entry is invoked as `int(*)(int, char**)`. */ #define RUN_TOOL "run" @@ -46,6 +47,7 @@ typedef struct RunOptions { DriverCflags cf; DriverInputs inputs; + DriverWasmRunOptions wasm; char** prog_argv; /* args after `--` */ uint32_t prog_argc; @@ -260,9 +262,10 @@ void driver_help_run(void) { " .a static archive\n" " - read C source from stdin (single source only)\n" "\n" - " JITed code may call any host symbol resolvable via " + " Native JITed code may call any host symbol resolvable via " "dlsym(RTLD_DEFAULT)\n" - " — typically libc. The JIT path forces PIC; -fPIC / -fPIE / " + " — typically libc. Wasm source inputs do not use this fallback.\n" + " The JIT path forces PIC; -fPIC / -fPIE / " "-mcmodel\n" " are accepted but have no observable effect.\n" "\n" @@ -294,7 +297,45 @@ void driver_help_run(void) { " -isystem DIR Add system-include search path\n" " -D NAME[=BODY] Define a macro\n" " -U NAME Undefine a builtin/predefined macro\n" - "\n" + "\n"))); + driver_printf( + "%.*s", + KIT_SLICE_ARG(KIT_SLICE_LIT( + "WASM SANDBOX\n" + " Wasm inputs run with a deny-by-default host-import policy. " + "No host\n" + " symbol or test-import fallback is enabled unless an explicit " + "flag\n" + " below requests it.\n" + " --wasm-memory-max=SIZE\n" + " Maximum total linear-memory reservation " + "(default 1G)\n" + " --wasm-instance-max=SIZE\n" + " Maximum runtime instance size (default 64M)\n" + " --wasm-memories-max=N\n" + " Maximum declared linear-memory count\n" + " --wasm-imports=deny|wasi|test\n" + " Import resolver policy (default deny). `test` " + "binds\n" + " only the in-tree env.host_add test shim.\n" + " `wasi` binds the configured WASI Preview1 " + "shim.\n" + " --wasm-wasi Alias for --wasm-imports=wasi\n" + " --wasm-env=none|allowlist|inherit\n" + " --wasm-env-pass=NAME, --wasm-env-set=NAME=VALUE\n" + " Guest environment policy for WASI env imports\n" + " --wasm-fs=none Filesystem disabled (default)\n" + " --wasm-map-dir=HOST=GUEST[:ro|rw]\n" + " --wasm-map-file=HOST=GUEST[:ro|rw]\n" + " --wasm-cwd=GUEST Guest cwd exposed through the WASI config\n" + " --wasm-stdio=null|inherit\n" + " --wasm-clock=none|monotonic|realtime\n" + " --wasm-random=none|host|seed:HEX\n" + " WASI host-resource policy; default is none\n" + "\n"))); + driver_printf( + "%.*s", + KIT_SLICE_ARG(KIT_SLICE_LIT( "ARGV PASSTHROUGH\n" " -- End of `kit run` options. Tokens after `--` " "are\n" @@ -355,6 +396,7 @@ static int run_alloc_arrays(RunOptions* o, int argc) { driver_errf(RUN_TOOL, "out of memory"); return 1; } + if (driver_wasm_run_options_init(&o->wasm, o->env, bound) != 0) return 1; return 0; } @@ -406,6 +448,25 @@ static int run_classify_positional(RunOptions* o, const char* a) { return 0; } +static int run_path_is_wasm_source(const char* path) { + return driver_has_suffix(path, ".wat") || driver_has_suffix(path, ".wasm"); +} + +static int run_inputs_have_wasm_source(const DriverInputs* in) { + uint32_t i; + for (i = 0; i < in->nsources; ++i) + if (run_path_is_wasm_source(in->sources[i])) return 1; + return 0; +} + +static int run_inputs_have_non_wasm(const DriverInputs* in) { + uint32_t i; + if (in->nsource_memory || in->nobject_files || in->narchives) return 1; + for (i = 0; i < in->nsources; ++i) + if (!run_path_is_wasm_source(in->sources[i])) return 1; + return 0; +} + static int run_apply_hosted_profile(RunOptions* o) { DriverHostedRequest req; uint32_t i; @@ -476,6 +537,12 @@ static int run_parse(int argc, char** argv, RunOptions* o) { } { + int wr = driver_wasm_run_try_consume(&o->wasm, RUN_TOOL, argc, argv, &i); + if (wr < 0) return 1; + if (wr > 0) continue; + } + + { int r = driver_cflags_try_consume(&o->cf, o->env, RUN_TOOL, argc, argv, &i); if (r < 0) return 1; @@ -632,6 +699,12 @@ static int run_parse(int argc, char** argv, RunOptions* o) { run_usage(); return 1; } + if (run_inputs_have_wasm_source(&o->inputs) && + run_inputs_have_non_wasm(&o->inputs)) { + driver_errf(RUN_TOOL, + "sandboxed wasm run does not support mixed native inputs"); + return 1; + } if (!o->entry) o->entry = "main"; if (run_apply_hosted_profile(o) != 0) return 1; @@ -639,12 +712,15 @@ static int run_parse(int argc, char** argv, RunOptions* o) { * the program name; under `kit run` there is no executable path, so * use the first input's display name. */ o->prog_argv[0] = (char*)driver_inputs_first_name(&o->inputs); + o->wasm.args = (const char* const*)o->prog_argv; + o->wasm.nargs = o->prog_argc; return 0; } static void run_options_release(RunOptions* o) { size_t bound = o->argv_bound; driver_hosted_plan_fini(o->env, &o->hosted); + driver_wasm_run_options_fini(&o->wasm); driver_inputs_release(&o->inputs); driver_target_features_fini(&o->target_features, o->env); driver_cflags_fini(&o->cf, o->env); @@ -671,309 +747,17 @@ static int run_compile_and_jit(RunOptions* o, KitCompiler* compiler, const KitJitHost* host, KitJit** out_jit) { KitCCompileOptions copts; KitPreprocessOptions pp; + void* (*extern_resolver)(void*, KitSlice) = driver_dlsym_resolver; run_fill_compile_opts(o, &copts); driver_cflags_fill_pp(&o->cf, &pp); + if (run_inputs_have_wasm_source(&o->inputs) || + driver_wasm_run_options_used(&o->wasm)) + extern_resolver = NULL; return driver_inputs_compile_and_jit(&o->inputs, compiler, host, &copts, &pp, - o->entry, driver_dlsym_resolver, NULL, - out_jit); + o->entry, extern_resolver, NULL, out_jit); } typedef int (*MainFn)(int, char**); -typedef void (*WasmInitFn)(void*); -typedef int (*WasmMainFn)(void*); - -/* Canned host import for the wasm-front test suite. Active when the - * caller sets KIT_TEST_HOST_IMPORTS=1 — production runs ignore it. - * Defined non-static so it has an external symbol the binder can call. */ -int32_t run_test_host_add(KitWasmInstance* inst, int32_t a, int32_t b) { - (void)inst; - return a + b; -} - -typedef struct RunWasmMemoryPrefix { - uint8_t* data; - uint64_t pages; - uint64_t max_pages; - uint32_t flags; -} RunWasmMemoryPrefix; - -typedef struct RunWasmInstanceAlloc { - uint8_t* instance; - uint8_t** memories; - uint64_t* memory_bytes; - uint32_t nmemories; - uint64_t instance_bytes; -} RunWasmInstanceAlloc; - -#define RUN_WASM_MAX_INSTANCE_BYTES (64ull * 1024ull * 1024ull) -#define RUN_WASM_MAX_TOTAL_MEMORY_BYTES (1024ull * 1024ull * 1024ull) - -static int run_wasm_u64_to_size(RunOptions* ro, uint64_t n, size_t* out, - const char* what) { - (void)ro; - if (n > (uint64_t)SIZE_MAX) { - driver_errf(RUN_TOOL, "wasm %s exceeds host size_t", what); - return 1; - } - *out = (size_t)n; - return 0; -} - -static int run_wasm_page_bytes(RunOptions* ro, uint64_t pages, uint64_t* out) { - (void)ro; - if (pages > UINT64_MAX / (uint64_t)KIT_WASM_PAGE_SIZE) { - driver_errf(RUN_TOOL, "wasm memory size overflows"); - return 1; - } - *out = pages * (uint64_t)KIT_WASM_PAGE_SIZE; - return 0; -} - -static int run_wasm_host_add_type(const KitWasmImportType* type) { - return type && type->nparams == 2u && type->nresults == 1u && - type->params[0] == KIT_WASM_VAL_I32 && - type->params[1] == KIT_WASM_VAL_I32 && - type->results[0] == KIT_WASM_VAL_I32; -} - -static void* run_wasm_test_resolve(void* user, const char* module, - const char* field, - const KitWasmImportType* type) { - extern int32_t run_test_host_add(KitWasmInstance*, int32_t, int32_t); - (void)user; - if (!module || !field) return NULL; - if (driver_streq(module, "env") && driver_streq(field, "host_add") && - run_wasm_host_add_type(type)) - return (void*)(uintptr_t)run_test_host_add; - return NULL; -} - -static void run_wasm_free_instance(RunOptions* ro, - RunWasmInstanceAlloc* alloc); - -/* Allocate and wire a Wasm instance + linear memory and bind host imports. - * Returns 0 on success (caller owns the returned instance and memory), - * nonzero on error (already freed). Shared by the JIT and interpreter paths. */ -static int run_wasm_make_instance(RunOptions* ro, KitCompiler* compiler, - KitJit* jit, - RunWasmInstanceAlloc* alloc_out) { - KitWasmRuntimeLayout layout; - RunWasmInstanceAlloc alloc; - size_t instance_size; - const KitWasmHostImport* h_imports = NULL; - size_t h_nimports = 0; - KitWasmResolveFn h_resolve = NULL; - void* h_user = NULL; - uint64_t total_memory_bytes = 0; - KitStatus layout_st; - memset(&alloc, 0, sizeof alloc); - if (!alloc_out) return 1; - memset(alloc_out, 0, sizeof *alloc_out); - layout_st = kit_wasm_get_runtime_layout(jit, &layout); - if (layout_st != KIT_OK) { - driver_errf(RUN_TOOL, layout_st == KIT_NOT_FOUND - ? "wasm runtime layout metadata missing" - : "wasm runtime layout metadata malformed"); - return 1; - } - alloc.instance_bytes = layout.instance_size ? layout.instance_size : 1u; - if (alloc.instance_bytes > RUN_WASM_MAX_INSTANCE_BYTES) { - driver_errf(RUN_TOOL, "wasm instance too large: %llu bytes", - (unsigned long long)alloc.instance_bytes); - return 1; - } - if (run_wasm_u64_to_size(ro, alloc.instance_bytes, &instance_size, - "instance") != 0) - return 1; - alloc.instance = (uint8_t*)driver_alloc_zeroed(ro->env, instance_size); - alloc.nmemories = layout.nmemories; - if (!alloc.instance) { - driver_errf(RUN_TOOL, "out of memory"); - return 1; - } - if (layout.nmemories) { - size_t ptr_bytes; - size_t size_bytes; - if ((uint64_t)layout.nmemories > (uint64_t)SIZE_MAX / sizeof(uint8_t*) || - (uint64_t)layout.nmemories > (uint64_t)SIZE_MAX / sizeof(uint64_t)) { - driver_errf(RUN_TOOL, "wasm memory count too large"); - driver_free(ro->env, alloc.instance, instance_size); - return 1; - } - ptr_bytes = (size_t)layout.nmemories * sizeof(uint8_t*); - size_bytes = (size_t)layout.nmemories * sizeof(uint64_t); - alloc.memories = (uint8_t**)driver_alloc_zeroed(ro->env, ptr_bytes); - alloc.memory_bytes = (uint64_t*)driver_alloc_zeroed(ro->env, size_bytes); - if (!alloc.memories || !alloc.memory_bytes) { - driver_errf(RUN_TOOL, "out of memory"); - driver_free(ro->env, alloc.memory_bytes, size_bytes); - driver_free(ro->env, alloc.memories, ptr_bytes); - driver_free(ro->env, alloc.instance, instance_size); - return 1; - } - } - for (uint32_t i = 0; i < layout.nmemories; ++i) { - const KitWasmMemoryLayout* ml = &layout.memories[i]; - RunWasmMemoryPrefix* rec; - uint64_t mem_bytes; - size_t mem_size; - if (ml->max_pages < ml->min_pages) { - driver_errf(RUN_TOOL, "wasm memory maximum below minimum"); - run_wasm_free_instance(ro, &alloc); - return 1; - } - if (ml->offset > alloc.instance_bytes || - alloc.instance_bytes - ml->offset < sizeof(RunWasmMemoryPrefix)) { - driver_errf(RUN_TOOL, "wasm memory record outside instance"); - run_wasm_free_instance(ro, &alloc); - return 1; - } - if (run_wasm_page_bytes(ro, ml->max_pages, &mem_bytes) != 0) { - run_wasm_free_instance(ro, &alloc); - return 1; - } - if (mem_bytes > RUN_WASM_MAX_TOTAL_MEMORY_BYTES || - total_memory_bytes > RUN_WASM_MAX_TOTAL_MEMORY_BYTES - mem_bytes) { - driver_errf(RUN_TOOL, "wasm linear memory reservation exceeds %llu bytes", - (unsigned long long)RUN_WASM_MAX_TOTAL_MEMORY_BYTES); - run_wasm_free_instance(ro, &alloc); - return 1; - } - total_memory_bytes += mem_bytes; - if (run_wasm_u64_to_size(ro, mem_bytes, &mem_size, - "linear memory") != 0) { - run_wasm_free_instance(ro, &alloc); - return 1; - } - if (mem_size) { - alloc.memories[i] = (uint8_t*)driver_alloc_zeroed(ro->env, mem_size); - if (!alloc.memories[i]) { - driver_errf(RUN_TOOL, "out of memory"); - run_wasm_free_instance(ro, &alloc); - return 1; - } - } - alloc.memory_bytes[i] = mem_bytes; - rec = (RunWasmMemoryPrefix*)(alloc.instance + ml->offset); - rec->data = alloc.memories[i]; - } - kit_wasm_get_host_imports(compiler, &h_imports, &h_nimports, &h_resolve, - &h_user); - if (h_nimports == 0 && !h_resolve && driver_getenv("KIT_TEST_HOST_IMPORTS")) { - h_resolve = run_wasm_test_resolve; - h_user = NULL; - } - { - KitStatus bind_st = kit_wasm_bind_host_imports( - compiler, jit, (KitWasmInstance*)alloc.instance, h_imports, h_nimports, - h_resolve, h_user); - if (bind_st != KIT_OK) { - const char* msg = bind_st == KIT_NOT_FOUND - ? "wasm host import unresolved" - : (bind_st == KIT_UNSUPPORTED - ? "wasm host import kind unsupported" - : "wasm host import metadata malformed"); - driver_errf(RUN_TOOL, "%s", msg); - run_wasm_free_instance(ro, &alloc); - return 1; - } - } - *alloc_out = alloc; - return 0; -} - -static void run_wasm_free_instance(RunOptions* ro, - RunWasmInstanceAlloc* alloc) { - if (!alloc) return; - if (alloc->memories) { - for (uint32_t i = 0; i < alloc->nmemories; ++i) { - size_t mem_size = 0; - if (alloc->memories[i] && - run_wasm_u64_to_size(ro, alloc->memory_bytes[i], &mem_size, - "linear memory") == 0) - driver_free(ro->env, alloc->memories[i], mem_size); - } - driver_free(ro->env, alloc->memories, - (size_t)alloc->nmemories * sizeof(uint8_t*)); - } - if (alloc->memory_bytes) - driver_free(ro->env, alloc->memory_bytes, - (size_t)alloc->nmemories * sizeof(uint64_t)); - if (alloc->instance) { - size_t instance_size = 0; - if (run_wasm_u64_to_size(ro, alloc->instance_bytes, &instance_size, - "instance") == 0) - driver_free(ro->env, alloc->instance, instance_size); - } - memset(alloc, 0, sizeof *alloc); -} - -static int run_call_wasm_entry(RunOptions* ro, KitCompiler* compiler, - KitJit* jit, void* entry, int* rc_out) { - void* init_sym = kit_jit_lookup(jit, KIT_SLICE_LIT("__kit_wasm_init")); - RunWasmInstanceAlloc alloc; - union { - void* p; - WasmInitFn fn; - } init_u; - union { - void* p; - WasmMainFn fn; - } entry_u; - if (!init_sym) return 0; - if (run_wasm_make_instance(ro, compiler, jit, &alloc) != 0) { - *rc_out = 1; - return 1; - } - init_u.p = init_sym; - entry_u.p = entry; - init_u.fn((KitWasmInstance*)alloc.instance); - *rc_out = entry_u.fn(alloc.instance); - run_wasm_free_instance(ro, &alloc); - return 1; -} - -/* Wasm entry through the interpreter (--no-jit). Sets up the instance like the - * JIT path, then runs __kit_wasm_init and the entry as InterpFuncs, passing - * the instance pointer as the (single) argument. Returns 0 if the module is - * not a Wasm module (no interpretable __kit_wasm_init); otherwise 1 with - * *rc_out set. */ -static int run_call_wasm_entry_interp(RunOptions* ro, KitCompiler* compiler, - KitJit* jit, KitInterpProgram* interp, - int* rc_out) { - KitInterpFunc* init_fn = - kit_interp_lookup(interp, KIT_SLICE_LIT("__kit_wasm_init")); - KitInterpFunc* entry_fn = - kit_interp_lookup(interp, kit_slice_cstr(ro->entry)); - RunWasmInstanceAlloc alloc; - uint64_t args[1]; - int64_t ret = 0; - KitInterpStatus s; - if (!init_fn) return 0; /* not a Wasm module */ - if (!entry_fn) { - driver_errf(RUN_TOOL, "interp: wasm entry %.*s has no interpretable IR", - KIT_SLICE_ARG(kit_slice_cstr(ro->entry))); - *rc_out = 1; - return 1; - } - if (run_wasm_make_instance(ro, compiler, jit, &alloc) != 0) { - *rc_out = 1; - return 1; - } - args[0] = (uint64_t)(uintptr_t)alloc.instance; - s = kit_interp_call_args(interp, init_fn, args, 1u, &ret); - if (s == KIT_INTERP_DONE) - s = kit_interp_call_args(interp, entry_fn, args, 1u, &ret); - if (s == KIT_INTERP_DONE) { - *rc_out = (int)ret; - } else { - driver_errf(RUN_TOOL, "interp: could not execute wasm entry %.*s", - KIT_SLICE_ARG(kit_slice_cstr(ro->entry))); - *rc_out = 1; - } - run_wasm_free_instance(ro, &alloc); - return 1; -} /* Host-identity symbol resolver for the interpreter. * @@ -1179,8 +963,14 @@ int driver_run(int argc, char** argv) { kit_interp_program_set_host(interp, &host); /* Wasm modules need their instance/linear-memory set up and a 2-call * (init, entry) sequence with the instance pointer as the argument. */ - if (run_call_wasm_entry_interp(&ro, compiler, jit, interp, &rc)) + if (driver_wasm_run_call_entry_interp(&ro.wasm, RUN_TOOL, compiler, jit, + interp, ro.entry, &rc)) + goto after_entry; + if (driver_wasm_run_options_used(&ro.wasm)) { + driver_errf(RUN_TOOL, "wasm sandbox flags require a wasm input"); + rc = 1; goto after_entry; + } ifn = kit_interp_lookup(interp, kit_slice_cstr(ro.entry)); if (!ifn) { driver_errf(RUN_TOOL, @@ -1210,8 +1000,15 @@ int driver_run(int argc, char** argv) { run_metrics_begin(metrics, "run.entry_call"); if (ro.bench_time) bench_exec_start = driver_now_ns(); - if (!run_call_wasm_entry(&ro, compiler, jit, sym, &rc)) - rc = entry_fn((int)ro.prog_argc, ro.prog_argv); + if (!driver_wasm_run_call_entry(&ro.wasm, RUN_TOOL, compiler, jit, sym, + &rc)) { + if (driver_wasm_run_options_used(&ro.wasm)) { + driver_errf(RUN_TOOL, "wasm sandbox flags require a wasm input"); + rc = 1; + } else { + rc = entry_fn((int)ro.prog_argc, ro.prog_argv); + } + } if (ro.bench_time) bench_exec_end = driver_now_ns(); run_metrics_end(metrics, "run.entry_call"); after_entry: diff --git a/driver/env.h b/driver/env.h @@ -155,6 +155,10 @@ void driver_memcpy(void* dst, const void* src, size_t n); * not close stdout. */ KitWriter* driver_stdout_writer(DriverEnv*); +/* Opens a Writer that writes to stderr. close frees the struct but does + * not close stderr. */ +KitWriter* driver_stderr_writer(DriverEnv*); + /* Test whether `path` names an existing filesystem entry (any type). * Returns nonzero on existence, zero otherwise. Used by library-path * resolution; intentionally distinct from read_all so candidate-search @@ -233,6 +237,10 @@ int driver_random_bytes(uint8_t* out, size_t n); * putenv from any caller. */ const char* driver_getenv(const char* name); +/* Borrow the process environment as NAME=VALUE strings. The array and strings + * are libc-owned and remain valid until the process environment is mutated. */ +const char* const* driver_environ(void); + /* Read all of stdin into a freshly-allocated buffer. On success returns 1 * and stores the buffer/size in out_data/out_size; the caller frees via * driver_free(env, *out_data, *out_size). Returns 0 on read failure or diff --git a/driver/env/posix.c b/driver/env/posix.c @@ -22,6 +22,8 @@ #include "env_posix.h" +extern char** environ; + /* ---------------- exec memory: single-mapping core + registry ---------------- */ @@ -249,11 +251,11 @@ static KitStatus stdio_w_status(KitWriter* w) { } static void stdio_w_close(KitWriter* w) { DriverStdioWriter* sw = (DriverStdioWriter*)w; - fflush(sw->fp); /* flush but do not close stdout */ + fflush(sw->fp); /* flush but do not close the borrowed stdio stream */ sw->heap->free(sw->heap, sw, sizeof(*sw)); } -KitWriter* driver_stdout_writer(DriverEnv* e) { +static KitWriter* driver_stdio_writer(DriverEnv* e, FILE* fp) { DriverStdioWriter* sw = (DriverStdioWriter*)e->heap->alloc( e->heap, sizeof(*sw), _Alignof(DriverStdioWriter)); if (!sw) return NULL; @@ -263,11 +265,23 @@ KitWriter* driver_stdout_writer(DriverEnv* e) { sw->base.status = stdio_w_status; sw->base.close = stdio_w_close; sw->heap = e->heap; - sw->fp = stdout; + sw->fp = fp; sw->status = KIT_OK; return &sw->base; } +KitWriter* driver_stdout_writer(DriverEnv* e) { + return driver_stdio_writer(e, stdout); +} + +KitWriter* driver_stderr_writer(DriverEnv* e) { + return driver_stdio_writer(e, stderr); +} + +const char* const* driver_environ(void) { + return (const char* const*)environ; +} + /* ---------------- file_io (POSIX open/read/write/stat) ---------------- */ static KitStatus posix_read_all(void* user, const char* path, diff --git a/driver/env/windows.c b/driver/env/windows.c @@ -54,6 +54,8 @@ #include "env_internal.h" +extern char** _environ; + /* Win32 dbg interrupt code: a synthetic signo handed up to on_fault. The * value just needs to be distinct from real exception codes; we pick a * small positive int so it round-trips through the int field of @@ -392,7 +394,7 @@ static void stdio_w_close(KitWriter* w) { sw->heap->free(sw->heap, sw, sizeof(*sw)); } -KitWriter* driver_stdout_writer(DriverEnv* e) { +static KitWriter* driver_stdio_writer(DriverEnv* e, FILE* fp) { DriverStdioWriter* sw = (DriverStdioWriter*)e->heap->alloc( e->heap, sizeof(*sw), _Alignof(DriverStdioWriter)); if (!sw) return NULL; @@ -402,11 +404,23 @@ KitWriter* driver_stdout_writer(DriverEnv* e) { sw->base.status = stdio_w_status; sw->base.close = stdio_w_close; sw->heap = e->heap; - sw->fp = stdout; + sw->fp = fp; sw->status = KIT_OK; return &sw->base; } +KitWriter* driver_stdout_writer(DriverEnv* e) { + return driver_stdio_writer(e, stdout); +} + +KitWriter* driver_stderr_writer(DriverEnv* e) { + return driver_stdio_writer(e, stderr); +} + +const char* const* driver_environ(void) { + return (const char* const*)_environ; +} + /* ============================================================ * file_io (CreateFileW + ReadFile/WriteFile) * ============================================================ */ diff --git a/driver/lib/wasm_run.c b/driver/lib/wasm_run.c @@ -0,0 +1,962 @@ +#include "wasm_run.h" + +#include <kit/core.h> +#include <kit/wasm.h> +#include <stddef.h> +#include <stdint.h> +#include <string.h> + +#include "driver.h" + +#define DRIVER_WASM_DEFAULT_MAX_INSTANCE_BYTES (64ull * 1024ull * 1024ull) +#define DRIVER_WASM_DEFAULT_MAX_TOTAL_MEMORY_BYTES \ + (1024ull * 1024ull * 1024ull) + +typedef void (*DriverWasmInitFn)(KitWasmInstance*); +typedef int (*DriverWasmMainFn)(KitWasmInstance*); + +typedef struct DriverWasmOwnedString { + char* ptr; + size_t size; +} DriverWasmOwnedString; + +typedef struct DriverWasmHostBuild { + DriverEnv* env; + const DriverWasmRunOptions* opts; + const char* tool; + + KitWasmHost* host; + const char** env_entries; + uint32_t nenv_entries; + uint32_t env_cap; + KitWasmFsMount* mounts; + uint32_t nmounts; + uint32_t mounts_cap; + DriverWasmOwnedString* owned; + uint32_t nowned; + uint32_t owned_cap; + uint64_t random_state; +} DriverWasmHostBuild; + +static int wasm_list_push(const char* tool, const char** items, size_t cap, + uint32_t* count, const char* value) { + if (*count >= cap || *count == UINT32_MAX) { + driver_errf(tool, "too many wasm sandbox options"); + return 1; + } + items[*count] = value; + *count += 1u; + return 0; +} + +int driver_wasm_run_options_init(DriverWasmRunOptions* o, DriverEnv* env, + size_t argv_bound) { + size_t bytes = argv_bound * sizeof(const char*); + memset(o, 0, sizeof *o); + o->env = env; + o->argv_bound = argv_bound; + o->max_instance_bytes = DRIVER_WASM_DEFAULT_MAX_INSTANCE_BYTES; + o->max_total_memory_bytes = DRIVER_WASM_DEFAULT_MAX_TOTAL_MEMORY_BYTES; + o->max_memories = UINT32_MAX; + o->imports = DRIVER_WASM_IMPORT_DENY; + o->env_mode = DRIVER_WASM_ENV_NONE; + o->stdio_mode = DRIVER_WASM_STDIO_NULL; + o->clock_mode = DRIVER_WASM_CLOCK_NONE; + o->random_mode = DRIVER_WASM_RANDOM_NONE; + if (argv_bound == 0) return 0; + o->env_pass = (const char**)driver_alloc_zeroed(env, bytes); + o->env_set = (const char**)driver_alloc_zeroed(env, bytes); + o->map_dirs = (const char**)driver_alloc_zeroed(env, bytes); + o->map_files = (const char**)driver_alloc_zeroed(env, bytes); + if (!o->env_pass || !o->env_set || !o->map_dirs || !o->map_files) { + driver_errf("run", "out of memory"); + return 1; + } + return 0; +} + +void driver_wasm_run_options_fini(DriverWasmRunOptions* o) { + size_t bytes; + if (!o) return; + bytes = o->argv_bound * sizeof(const char*); + if (o->env_pass) driver_free(o->env, o->env_pass, bytes); + if (o->env_set) driver_free(o->env, o->env_set, bytes); + if (o->map_dirs) driver_free(o->env, o->map_dirs, bytes); + if (o->map_files) driver_free(o->env, o->map_files, bytes); + memset(o, 0, sizeof *o); +} + +static int wasm_parse_u64_dec(const char* s, uint64_t* out) { + uint64_t v = 0; + int any = 0; + if (!s) return 1; + while (*s >= '0' && *s <= '9') { + unsigned d = (unsigned)(*s - '0'); + if (v > (UINT64_MAX - d) / 10u) return 1; + v = v * 10u + d; + any = 1; + s++; + } + if (!any || *s) return 1; + *out = v; + return 0; +} + +static int wasm_parse_size(const char* s, uint64_t* out) { + uint64_t v = 0; + uint64_t mul = 1; + int any = 0; + if (!s) return 1; + while (*s >= '0' && *s <= '9') { + unsigned d = (unsigned)(*s - '0'); + if (v > (UINT64_MAX - d) / 10u) return 1; + v = v * 10u + d; + any = 1; + s++; + } + if (!any) return 1; + if (*s) { + char c = *s++; + if (c >= 'a' && c <= 'z') c = (char)(c - ('a' - 'A')); + if (c == 'K') + mul = 1024ull; + else if (c == 'M') + mul = 1024ull * 1024ull; + else if (c == 'G') + mul = 1024ull * 1024ull * 1024ull; + else + return 1; + if (*s == 'i' || *s == 'I') s++; + if (*s == 'b' || *s == 'B') s++; + if (*s) return 1; + } + if (v > UINT64_MAX / mul) return 1; + *out = v * mul; + return 0; +} + +static const char* wasm_take_value(const char* tool, int argc, char** argv, + int* index, const char* flag, + const char* inline_value) { + if (inline_value) return inline_value; + if (*index + 1 >= argc) { + driver_errf(tool, "%.*s requires an argument", + KIT_SLICE_ARG(kit_slice_cstr(flag))); + return NULL; + } + *index += 1; + return argv[*index]; +} + +static int wasm_set_import_mode(const char* tool, DriverWasmRunOptions* o, + const char* value) { + if (driver_streq(value, "deny") || driver_streq(value, "none")) { + o->imports = DRIVER_WASM_IMPORT_DENY; + return 0; + } + if (driver_streq(value, "wasi")) { + o->imports = DRIVER_WASM_IMPORT_WASI; + return 0; + } + if (driver_streq(value, "test")) { + o->imports = DRIVER_WASM_IMPORT_TEST; + return 0; + } + driver_errf(tool, "unknown --wasm-imports value: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(value))); + return 1; +} + +static int wasm_set_env_mode(const char* tool, DriverWasmRunOptions* o, + const char* value) { + if (driver_streq(value, "none")) { + o->env_mode = DRIVER_WASM_ENV_NONE; + return 0; + } + if (driver_streq(value, "allowlist")) { + o->env_mode = DRIVER_WASM_ENV_ALLOWLIST; + return 0; + } + if (driver_streq(value, "inherit")) { + o->env_mode = DRIVER_WASM_ENV_INHERIT; + return 0; + } + driver_errf(tool, "unknown --wasm-env value: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(value))); + return 1; +} + +static int wasm_set_stdio_mode(const char* tool, DriverWasmRunOptions* o, + const char* value) { + if (driver_streq(value, "null")) { + o->stdio_mode = DRIVER_WASM_STDIO_NULL; + return 0; + } + if (driver_streq(value, "inherit")) { + o->stdio_mode = DRIVER_WASM_STDIO_INHERIT; + return 0; + } + driver_errf(tool, "unknown --wasm-stdio value: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(value))); + return 1; +} + +static int wasm_set_clock_mode(const char* tool, DriverWasmRunOptions* o, + const char* value) { + if (driver_streq(value, "none")) { + o->clock_mode = DRIVER_WASM_CLOCK_NONE; + return 0; + } + if (driver_streq(value, "monotonic")) { + o->clock_mode = DRIVER_WASM_CLOCK_MONOTONIC; + return 0; + } + if (driver_streq(value, "realtime")) { + o->clock_mode = DRIVER_WASM_CLOCK_REALTIME; + return 0; + } + driver_errf(tool, "unknown --wasm-clock value: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(value))); + return 1; +} + +static int wasm_set_random_mode(const char* tool, DriverWasmRunOptions* o, + const char* value) { + if (driver_streq(value, "none")) { + o->random_mode = DRIVER_WASM_RANDOM_NONE; + o->random_seed = NULL; + return 0; + } + if (driver_streq(value, "host")) { + o->random_mode = DRIVER_WASM_RANDOM_HOST; + o->random_seed = NULL; + return 0; + } + if (driver_strneq(value, "seed:", 5)) { + o->random_mode = DRIVER_WASM_RANDOM_SEED; + o->random_seed = value + 5; + return 0; + } + driver_errf(tool, "unknown --wasm-random value: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(value))); + return 1; +} + +int driver_wasm_run_try_consume(DriverWasmRunOptions* o, const char* tool, + int argc, char** argv, int* index) { + const char* a = argv[*index]; + const char* v; + uint64_t u64; + if (!driver_strneq(a, "--wasm-", 7)) return 0; + o->used = 1; + + if (driver_streq(a, "--wasm-wasi")) { + o->imports = DRIVER_WASM_IMPORT_WASI; + return 1; + } + if (driver_streq(a, "--wasm-memory-max") || + driver_strneq(a, "--wasm-memory-max=", 18)) { + v = wasm_take_value(tool, argc, argv, index, "--wasm-memory-max", + driver_strneq(a, "--wasm-memory-max=", 18) ? a + 18 + : NULL); + if (!v) return -1; + if (wasm_parse_size(v, &u64) != 0) { + driver_errf(tool, "--wasm-memory-max requires a byte size"); + return -1; + } + o->max_total_memory_bytes = u64; + return 1; + } + if (driver_streq(a, "--wasm-instance-max") || + driver_strneq(a, "--wasm-instance-max=", 20)) { + v = wasm_take_value(tool, argc, argv, index, "--wasm-instance-max", + driver_strneq(a, "--wasm-instance-max=", 20) ? a + 20 + : NULL); + if (!v) return -1; + if (wasm_parse_size(v, &u64) != 0) { + driver_errf(tool, "--wasm-instance-max requires a byte size"); + return -1; + } + o->max_instance_bytes = u64; + return 1; + } + if (driver_streq(a, "--wasm-memories-max") || + driver_strneq(a, "--wasm-memories-max=", 20)) { + v = wasm_take_value(tool, argc, argv, index, "--wasm-memories-max", + driver_strneq(a, "--wasm-memories-max=", 20) ? a + 20 + : NULL); + if (!v) return -1; + if (wasm_parse_u64_dec(v, &u64) != 0 || u64 > UINT32_MAX) { + driver_errf(tool, "--wasm-memories-max requires a non-negative integer"); + return -1; + } + o->max_memories = (uint32_t)u64; + return 1; + } + if (driver_streq(a, "--wasm-imports") || + driver_strneq(a, "--wasm-imports=", 15)) { + v = wasm_take_value(tool, argc, argv, index, "--wasm-imports", + driver_strneq(a, "--wasm-imports=", 15) ? a + 15 + : NULL); + if (!v) return -1; + return wasm_set_import_mode(tool, o, v) == 0 ? 1 : -1; + } + if (driver_streq(a, "--wasm-env") || driver_strneq(a, "--wasm-env=", 11)) { + v = wasm_take_value(tool, argc, argv, index, "--wasm-env", + driver_strneq(a, "--wasm-env=", 11) ? a + 11 : NULL); + if (!v) return -1; + return wasm_set_env_mode(tool, o, v) == 0 ? 1 : -1; + } + if (driver_streq(a, "--wasm-env-pass") || + driver_strneq(a, "--wasm-env-pass=", 16)) { + v = wasm_take_value(tool, argc, argv, index, "--wasm-env-pass", + driver_strneq(a, "--wasm-env-pass=", 16) ? a + 16 + : NULL); + if (!v) return -1; + if (wasm_list_push(tool, o->env_pass, o->argv_bound, &o->nenv_pass, v) != + 0) + return -1; + return 1; + } + if (driver_streq(a, "--wasm-env-set") || + driver_strneq(a, "--wasm-env-set=", 15)) { + v = wasm_take_value(tool, argc, argv, index, "--wasm-env-set", + driver_strneq(a, "--wasm-env-set=", 15) ? a + 15 + : NULL); + if (!v) return -1; + if (wasm_list_push(tool, o->env_set, o->argv_bound, &o->nenv_set, v) != 0) + return -1; + return 1; + } + if (driver_streq(a, "--wasm-fs") || driver_strneq(a, "--wasm-fs=", 10)) { + v = wasm_take_value(tool, argc, argv, index, "--wasm-fs", + driver_strneq(a, "--wasm-fs=", 10) ? a + 10 : NULL); + if (!v) return -1; + if (!driver_streq(v, "none")) { + driver_errf(tool, "unknown --wasm-fs value: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(v))); + return -1; + } + return 1; + } + if (driver_streq(a, "--wasm-map-dir") || + driver_strneq(a, "--wasm-map-dir=", 15)) { + v = wasm_take_value(tool, argc, argv, index, "--wasm-map-dir", + driver_strneq(a, "--wasm-map-dir=", 15) ? a + 15 + : NULL); + if (!v) return -1; + if (wasm_list_push(tool, o->map_dirs, o->argv_bound, &o->nmap_dirs, v) != 0) + return -1; + return 1; + } + if (driver_streq(a, "--wasm-map-file") || + driver_strneq(a, "--wasm-map-file=", 16)) { + v = wasm_take_value(tool, argc, argv, index, "--wasm-map-file", + driver_strneq(a, "--wasm-map-file=", 16) ? a + 16 + : NULL); + if (!v) return -1; + if (wasm_list_push(tool, o->map_files, o->argv_bound, &o->nmap_files, v) != + 0) + return -1; + return 1; + } + if (driver_streq(a, "--wasm-cwd") || driver_strneq(a, "--wasm-cwd=", 11)) { + v = wasm_take_value(tool, argc, argv, index, "--wasm-cwd", + driver_strneq(a, "--wasm-cwd=", 11) ? a + 11 : NULL); + if (!v) return -1; + o->cwd = v; + return 1; + } + if (driver_streq(a, "--wasm-stdio") || + driver_strneq(a, "--wasm-stdio=", 13)) { + v = wasm_take_value(tool, argc, argv, index, "--wasm-stdio", + driver_strneq(a, "--wasm-stdio=", 13) ? a + 13 + : NULL); + if (!v) return -1; + return wasm_set_stdio_mode(tool, o, v) == 0 ? 1 : -1; + } + if (driver_streq(a, "--wasm-clock") || + driver_strneq(a, "--wasm-clock=", 13)) { + v = wasm_take_value(tool, argc, argv, index, "--wasm-clock", + driver_strneq(a, "--wasm-clock=", 13) ? a + 13 + : NULL); + if (!v) return -1; + return wasm_set_clock_mode(tool, o, v) == 0 ? 1 : -1; + } + if (driver_streq(a, "--wasm-random") || + driver_strneq(a, "--wasm-random=", 14)) { + v = wasm_take_value(tool, argc, argv, index, "--wasm-random", + driver_strneq(a, "--wasm-random=", 14) ? a + 14 + : NULL); + if (!v) return -1; + return wasm_set_random_mode(tool, o, v) == 0 ? 1 : -1; + } + + driver_errf(tool, "unknown wasm sandbox flag: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(a))); + return -1; +} + +int driver_wasm_run_options_used(const DriverWasmRunOptions* o) { + return o && o->used; +} + +static int wasm_page_bytes(const char* tool, uint64_t pages, uint64_t* out) { + if (pages > UINT64_MAX / (uint64_t)KIT_WASM_PAGE_SIZE) { + driver_errf(tool, "wasm memory size overflows"); + return 1; + } + *out = pages * (uint64_t)KIT_WASM_PAGE_SIZE; + return 0; +} + +static int wasm_preflight_instance(const DriverWasmRunOptions* opts, + const char* tool, KitJit* jit) { + KitWasmRuntimeLayout layout; + KitStatus st = kit_wasm_get_runtime_layout(jit, &layout); + uint64_t instance_bytes; + uint64_t total_memory_bytes = 0; + uint32_t i; + if (st != KIT_OK) { + driver_errf(tool, st == KIT_NOT_FOUND + ? "wasm runtime layout metadata missing" + : "wasm runtime layout metadata malformed"); + return 1; + } + if (layout.nmemories > opts->max_memories) { + driver_errf(tool, "wasm memory count exceeds %u", opts->max_memories); + return 1; + } + instance_bytes = layout.instance_size ? layout.instance_size : 1u; + if (instance_bytes > opts->max_instance_bytes) { + driver_errf(tool, "wasm instance too large: %llu bytes", + (unsigned long long)instance_bytes); + return 1; + } + for (i = 0; i < layout.nmemories; ++i) { + const KitWasmMemoryLayout* ml = &layout.memories[i]; + uint64_t mem_bytes; + if (ml->max_pages < ml->min_pages) { + driver_errf(tool, "wasm memory maximum below minimum"); + return 1; + } + if (wasm_page_bytes(tool, ml->max_pages, &mem_bytes) != 0) return 1; + if (mem_bytes > opts->max_total_memory_bytes || + total_memory_bytes > opts->max_total_memory_bytes - mem_bytes) { + driver_errf(tool, "wasm linear memory reservation exceeds %llu bytes", + (unsigned long long)opts->max_total_memory_bytes); + return 1; + } + total_memory_bytes += mem_bytes; + } + return 0; +} + +/* Canned host import for the wasm-front test suite. Active only when the + * caller explicitly selects --wasm-imports=test. */ +int32_t driver_wasm_test_host_add(KitWasmInstance* inst, int32_t a, int32_t b) { + (void)inst; + return a + b; +} + +static int wasm_host_add_type(const KitWasmImportType* type) { + return type && type->nparams == 2u && type->nresults == 1u && + type->params[0] == KIT_WASM_VAL_I32 && + type->params[1] == KIT_WASM_VAL_I32 && + type->results[0] == KIT_WASM_VAL_I32; +} + +static void* wasm_test_resolve(void* user, const char* module, + const char* field, + const KitWasmImportType* type) { + (void)user; + if (!module || !field) return NULL; + if (driver_streq(module, "env") && driver_streq(field, "host_add") && + wasm_host_add_type(type)) + return (void*)(uintptr_t)driver_wasm_test_host_add; + return NULL; +} + +static const char* wasm_status_name(KitStatus st) { + switch (st) { + case KIT_OK: + return "ok"; + case KIT_NOMEM: + return "out of memory"; + case KIT_INVALID: + return "invalid wasm host configuration"; + case KIT_UNSUPPORTED: + return "wasm host import kind unsupported"; + case KIT_MALFORMED: + return "wasm host import metadata malformed"; + case KIT_NOT_FOUND: + return "wasm host import unresolved"; + case KIT_IO: + return "wasm host I/O failed"; + case KIT_ERR: + case KIT_AMBIGUOUS: + default: + return "wasm host setup failed"; + } +} + +static int wasm_build_add_owned(DriverWasmHostBuild* b, const char* data, + size_t len, const char** out) { + char* p; + if (b->nowned >= b->owned_cap) return 1; + p = (char*)driver_alloc(b->env, len + 1u); + if (!p) return 1; + if (len) driver_memcpy(p, data, len); + p[len] = '\0'; + b->owned[b->nowned].ptr = p; + b->owned[b->nowned].size = len + 1u; + b->nowned++; + *out = p; + return 0; +} + +static int wasm_env_name_valid(const char* name) { + return name && name[0] && !driver_strchr(name, '='); +} + +static int wasm_env_set_valid(const char* entry) { + const char* eq = driver_strchr(entry, '='); + return entry && entry[0] && eq && eq != entry; +} + +static int wasm_build_env_pass(DriverWasmHostBuild* b, const char* name, + const char** out) { + const char* value; + size_t name_len; + size_t value_len; + char* p; + if (!wasm_env_name_valid(name)) { + driver_errf(b->tool, "--wasm-env-pass requires NAME"); + return 1; + } + value = driver_getenv(name); + if (!value) { + *out = NULL; + return 0; + } + if (b->nowned >= b->owned_cap) return 1; + name_len = driver_strlen(name); + value_len = driver_strlen(value); + if (name_len > SIZE_MAX - value_len - 2u) return 1; + p = (char*)driver_alloc(b->env, name_len + value_len + 2u); + if (!p) return 1; + driver_memcpy(p, name, name_len); + p[name_len] = '='; + driver_memcpy(p + name_len + 1u, value, value_len); + p[name_len + 1u + value_len] = '\0'; + b->owned[b->nowned].ptr = p; + b->owned[b->nowned].size = name_len + value_len + 2u; + b->nowned++; + *out = p; + return 0; +} + +static uint32_t wasm_process_env_count(void) { + const char* const* envp = driver_environ(); + uint32_t n = 0; + if (!envp) return 0; + while (envp[n]) { + if (n == UINT32_MAX) break; + ++n; + } + return n; +} + +static int wasm_build_env(DriverWasmHostBuild* b, KitWasmHostConfig* cfg) { + const DriverWasmRunOptions* o = b->opts; + uint32_t inherited = o->env_mode == DRIVER_WASM_ENV_INHERIT + ? wasm_process_env_count() + : 0u; + uint64_t cap64 = (uint64_t)inherited + o->nenv_pass + o->nenv_set; + uint32_t n = 0; + uint32_t i; + if (cap64 > UINT32_MAX) { + driver_errf(b->tool, "too many wasm environment entries"); + return 1; + } + b->env_cap = (uint32_t)cap64; + if (b->env_cap) { + b->env_entries = (const char**)driver_alloc_zeroed( + b->env, (size_t)b->env_cap * sizeof(*b->env_entries)); + if (!b->env_entries) { + driver_errf(b->tool, "out of memory"); + return 1; + } + } + if (inherited) { + const char* const* envp = driver_environ(); + for (i = 0; i < inherited; ++i) b->env_entries[n++] = envp[i]; + } + for (i = 0; i < o->nenv_pass; ++i) { + const char* entry = NULL; + if (wasm_build_env_pass(b, o->env_pass[i], &entry) != 0) { + driver_errf(b->tool, "out of memory"); + return 1; + } + if (entry) b->env_entries[n++] = entry; + } + for (i = 0; i < o->nenv_set; ++i) { + if (!wasm_env_set_valid(o->env_set[i])) { + driver_errf(b->tool, "--wasm-env-set requires NAME=VALUE"); + return 1; + } + b->env_entries[n++] = o->env_set[i]; + } + b->nenv_entries = n; + cfg->env = b->env_entries; + cfg->nenv = n; + return 0; +} + +static const char* wasm_find_eq(const char* s) { + while (s && *s) { + if (*s == '=') return s; + ++s; + } + return NULL; +} + +static int wasm_suffix_mode_len(const char* s, size_t len, uint32_t* flags, + size_t* new_len) { + if (len >= 3u && s[len - 3u] == ':' && s[len - 2u] == 'r' && + s[len - 1u] == 'o') { + *flags = KIT_WASM_FS_READ; + *new_len = len - 3u; + return 0; + } + if (len >= 3u && s[len - 3u] == ':' && s[len - 2u] == 'r' && + s[len - 1u] == 'w') { + *flags = KIT_WASM_FS_READ | KIT_WASM_FS_WRITE; + *new_len = len - 3u; + return 0; + } + *flags = KIT_WASM_FS_READ; + *new_len = len; + return 0; +} + +static int wasm_build_one_mount(DriverWasmHostBuild* b, const char* spec, + uint32_t extra_flags, + KitWasmFsMount* out) { + const char* eq = wasm_find_eq(spec); + const char* host; + const char* guest; + size_t host_len; + size_t guest_len; + uint32_t flags; + if (!eq || eq == spec || !eq[1]) { + driver_errf(b->tool, "wasm filesystem maps require HOST=GUEST[:ro|rw]"); + return 1; + } + host = spec; + host_len = (size_t)(eq - spec); + guest = eq + 1; + guest_len = driver_strlen(guest); + wasm_suffix_mode_len(guest, guest_len, &flags, &guest_len); + if (guest_len == 0 || guest[0] != '/') { + driver_errf(b->tool, "wasm guest paths must be absolute"); + return 1; + } + if (wasm_build_add_owned(b, host, host_len, &out->host_path) != 0 || + wasm_build_add_owned(b, guest, guest_len, &out->guest_path) != 0) { + driver_errf(b->tool, "out of memory"); + return 1; + } + out->flags = flags | extra_flags; + return 0; +} + +static int wasm_build_mounts(DriverWasmHostBuild* b, KitWasmHostConfig* cfg) { + const DriverWasmRunOptions* o = b->opts; + uint64_t total = (uint64_t)o->nmap_dirs + o->nmap_files; + uint32_t n = 0; + uint32_t i; + if (total > UINT32_MAX) { + driver_errf(b->tool, "too many wasm filesystem maps"); + return 1; + } + b->mounts_cap = (uint32_t)total; + if (b->mounts_cap) { + b->mounts = (KitWasmFsMount*)driver_alloc_zeroed( + b->env, (size_t)b->mounts_cap * sizeof(*b->mounts)); + if (!b->mounts) { + driver_errf(b->tool, "out of memory"); + return 1; + } + } + for (i = 0; i < o->nmap_dirs; ++i) { + if (wasm_build_one_mount(b, o->map_dirs[i], 0u, &b->mounts[n]) != 0) + return 1; + ++n; + } + for (i = 0; i < o->nmap_files; ++i) { + if (wasm_build_one_mount(b, o->map_files[i], KIT_WASM_FS_FILE, + &b->mounts[n]) != 0) + return 1; + ++n; + } + b->nmounts = n; + cfg->mounts = b->mounts; + cfg->nmounts = n; + return 0; +} + +static KitStatus wasm_driver_write(void* user, uint32_t fd, + const uint8_t* data, size_t n, + size_t* nwritten_out) { + DriverWasmHostBuild* b = (DriverWasmHostBuild*)user; + KitWriter* w = fd == 2u ? driver_stderr_writer(b->env) + : driver_stdout_writer(b->env); + KitStatus st; + if (!w) return KIT_NOMEM; + st = kit_writer_write(w, data, n); + if (st == KIT_OK) st = kit_writer_status(w); + kit_writer_close(w); + if (nwritten_out) *nwritten_out = st == KIT_OK ? n : 0u; + return st; +} + +static KitStatus wasm_driver_clock(void* user, uint32_t clock_id, + uint64_t* ns_out) { + DriverWasmHostBuild* b = (DriverWasmHostBuild*)user; + if (b->opts->clock_mode == DRIVER_WASM_CLOCK_MONOTONIC && clock_id == 1u) { + *ns_out = driver_now_ns(); + return KIT_OK; + } + if (b->opts->clock_mode == DRIVER_WASM_CLOCK_REALTIME && clock_id == 0u && + b->env->now >= 0) { + *ns_out = (uint64_t)b->env->now * 1000000000ull; + return KIT_OK; + } + return KIT_UNSUPPORTED; +} + +static uint64_t wasm_seed_hash(const char* s) { + uint64_t h = 1469598103934665603ull; + if (!s || !*s) return 0x9e3779b97f4a7c15ull; + while (*s) { + h ^= (uint8_t)*s++; + h *= 1099511628211ull; + } + return h ? h : 0x9e3779b97f4a7c15ull; +} + +static uint64_t wasm_seed_next(DriverWasmHostBuild* b) { + uint64_t x = b->random_state; + x ^= x >> 12; + x ^= x << 25; + x ^= x >> 27; + b->random_state = x; + return x * 2685821657736338717ull; +} + +static KitStatus wasm_driver_random(void* user, uint8_t* dst, size_t n) { + DriverWasmHostBuild* b = (DriverWasmHostBuild*)user; + size_t i; + if (b->opts->random_mode == DRIVER_WASM_RANDOM_HOST) + return driver_random_bytes(dst, n) == 0 ? KIT_OK : KIT_ERR; + if (b->opts->random_mode != DRIVER_WASM_RANDOM_SEED) return KIT_UNSUPPORTED; + for (i = 0; i < n; ++i) { + if ((i & 7u) == 0) { + uint64_t x = wasm_seed_next(b); + size_t j; + for (j = 0; j < 8u && i + j < n; ++j) + dst[i + j] = (uint8_t)(x >> (j * 8u)); + } + } + return KIT_OK; +} + +static void wasm_build_release(DriverWasmHostBuild* b) { + uint32_t i; + if (!b) return; + if (b->host) kit_wasm_host_free(b->host); + for (i = 0; i < b->nowned; ++i) { + if (b->owned[i].ptr) driver_free(b->env, b->owned[i].ptr, b->owned[i].size); + } + if (b->owned) + driver_free(b->env, b->owned, (size_t)b->owned_cap * sizeof(*b->owned)); + if (b->mounts) + driver_free(b->env, b->mounts, + (size_t)b->mounts_cap * sizeof(*b->mounts)); + if (b->env_entries) { + driver_free(b->env, b->env_entries, + (size_t)b->env_cap * sizeof(*b->env_entries)); + } + memset(b, 0, sizeof *b); +} + +static int wasm_build_host(const DriverWasmRunOptions* opts, const char* tool, + DriverWasmHostBuild* b) { + KitWasmHostConfig cfg; + uint64_t owned_cap64; + KitStatus st; + memset(b, 0, sizeof *b); + memset(&cfg, 0, sizeof cfg); + b->env = opts->env; + b->opts = opts; + b->tool = tool; + owned_cap64 = (uint64_t)opts->nenv_pass + + ((uint64_t)opts->nmap_dirs + opts->nmap_files) * 2u; + if (owned_cap64 > UINT32_MAX) { + driver_errf(tool, "too many wasm sandbox strings"); + return 1; + } + b->owned_cap = (uint32_t)owned_cap64; + if (b->owned_cap) { + b->owned = (DriverWasmOwnedString*)driver_alloc_zeroed( + opts->env, (size_t)b->owned_cap * sizeof(*b->owned)); + if (!b->owned) { + driver_errf(tool, "out of memory"); + return 1; + } + } + + cfg.heap = opts->env->heap; + cfg.flags = opts->imports == DRIVER_WASM_IMPORT_WASI + ? KIT_WASM_HOST_WASI_PREVIEW1 + : 0u; + cfg.max_instance_bytes = opts->max_instance_bytes; + cfg.max_total_memory_bytes = opts->max_total_memory_bytes; + cfg.max_memories = opts->max_memories; + cfg.args = opts->args; + cfg.nargs = opts->nargs; + cfg.cwd = opts->cwd ? opts->cwd : "/"; + cfg.file_io = &opts->env->file_io; + cfg.user = b; + if (opts->stdio_mode == DRIVER_WASM_STDIO_INHERIT) + cfg.write = wasm_driver_write; + if (opts->clock_mode != DRIVER_WASM_CLOCK_NONE) cfg.clock = wasm_driver_clock; + if (opts->random_mode != DRIVER_WASM_RANDOM_NONE) { + cfg.random = wasm_driver_random; + b->random_state = wasm_seed_hash(opts->random_seed); + } + if (wasm_build_env(b, &cfg) != 0 || wasm_build_mounts(b, &cfg) != 0) + return 1; + st = kit_wasm_host_new(&cfg, &b->host); + if (st != KIT_OK) { + driver_errf(tool, "%s", wasm_status_name(st)); + return 1; + } + return 0; +} + +static int wasm_bind_imports(const DriverWasmRunOptions* opts, + const char* tool, KitCompiler* compiler, + KitJit* jit, DriverWasmHostBuild* build, + KitWasmInstance* inst) { + KitStatus st; + if (opts->imports == DRIVER_WASM_IMPORT_TEST) { + st = kit_wasm_bind_host_imports(compiler, jit, inst, NULL, 0, + wasm_test_resolve, NULL); + } else if (opts->imports == DRIVER_WASM_IMPORT_WASI) { + st = kit_wasm_host_bind_imports(build->host, compiler, jit, inst); + } else { + st = kit_wasm_bind_host_imports(compiler, jit, inst, NULL, 0, NULL, NULL); + } + if (st != KIT_OK) { + driver_errf(tool, "%s", wasm_status_name(st)); + return 1; + } + return 0; +} + +static int wasm_prepare_instance(const DriverWasmRunOptions* opts, + const char* tool, KitCompiler* compiler, + KitJit* jit, DriverWasmHostBuild* build, + KitWasmInstance** out) { + KitStatus st; + *out = NULL; + if (wasm_preflight_instance(opts, tool, jit) != 0) return 1; + if (wasm_build_host(opts, tool, build) != 0) return 1; + st = kit_wasm_instance_new(build->host, jit, out); + if (st != KIT_OK) { + driver_errf(tool, "%s", wasm_status_name(st)); + return 1; + } + if (wasm_bind_imports(opts, tool, compiler, jit, build, *out) != 0) { + kit_wasm_instance_free(*out); + *out = NULL; + return 1; + } + return 0; +} + +int driver_wasm_run_call_entry(const DriverWasmRunOptions* opts, + const char* tool, KitCompiler* compiler, + KitJit* jit, void* entry, int* rc_out) { + void* init_sym = kit_jit_lookup(jit, KIT_SLICE_LIT("__kit_wasm_init")); + DriverWasmHostBuild build; + KitWasmInstance* inst = NULL; + union { + void* p; + DriverWasmInitFn fn; + } init_u; + union { + void* p; + DriverWasmMainFn fn; + } entry_u; + if (!init_sym) return 0; + memset(&build, 0, sizeof build); + if (wasm_prepare_instance(opts, tool, compiler, jit, &build, &inst) != 0) { + *rc_out = 1; + wasm_build_release(&build); + return 1; + } + init_u.p = init_sym; + entry_u.p = entry; + init_u.fn(inst); + *rc_out = entry_u.fn(inst); + (void)kit_wasm_instance_exit_code(inst, rc_out); + kit_wasm_instance_free(inst); + wasm_build_release(&build); + return 1; +} + +int driver_wasm_run_call_entry_interp(const DriverWasmRunOptions* opts, + const char* tool, KitCompiler* compiler, + KitJit* jit, KitInterpProgram* interp, + const char* entry_name, int* rc_out) { + KitInterpFunc* init_fn = + kit_interp_lookup(interp, KIT_SLICE_LIT("__kit_wasm_init")); + KitInterpFunc* entry_fn = + kit_interp_lookup(interp, kit_slice_cstr(entry_name)); + DriverWasmHostBuild build; + KitWasmInstance* inst = NULL; + uint64_t args[1]; + int64_t ret = 0; + KitInterpStatus s; + if (!init_fn) return 0; + if (!entry_fn) { + driver_errf(tool, "interp: wasm entry %.*s has no interpretable IR", + KIT_SLICE_ARG(kit_slice_cstr(entry_name))); + *rc_out = 1; + return 1; + } + memset(&build, 0, sizeof build); + if (wasm_prepare_instance(opts, tool, compiler, jit, &build, &inst) != 0) { + *rc_out = 1; + wasm_build_release(&build); + return 1; + } + args[0] = (uint64_t)(uintptr_t)inst; + s = kit_interp_call_args(interp, init_fn, args, 1u, &ret); + if (s == KIT_INTERP_DONE) + s = kit_interp_call_args(interp, entry_fn, args, 1u, &ret); + if (s == KIT_INTERP_DONE) { + *rc_out = (int)ret; + (void)kit_wasm_instance_exit_code(inst, rc_out); + } else { + driver_errf(tool, "interp: could not execute wasm entry %.*s", + KIT_SLICE_ARG(kit_slice_cstr(entry_name))); + *rc_out = 1; + } + kit_wasm_instance_free(inst); + wasm_build_release(&build); + return 1; +} diff --git a/driver/lib/wasm_run.h b/driver/lib/wasm_run.h @@ -0,0 +1,91 @@ +#ifndef KIT_DRIVER_WASM_RUN_H +#define KIT_DRIVER_WASM_RUN_H + +#include <kit/interp.h> +#include <kit/jit.h> +#include <stdint.h> + +#include "env.h" + +typedef enum DriverWasmImportMode { + DRIVER_WASM_IMPORT_DENY = 0, + DRIVER_WASM_IMPORT_WASI = 1, + DRIVER_WASM_IMPORT_TEST = 2, +} DriverWasmImportMode; + +typedef enum DriverWasmEnvMode { + DRIVER_WASM_ENV_NONE = 0, + DRIVER_WASM_ENV_ALLOWLIST = 1, + DRIVER_WASM_ENV_INHERIT = 2, +} DriverWasmEnvMode; + +typedef enum DriverWasmStdioMode { + DRIVER_WASM_STDIO_NULL = 0, + DRIVER_WASM_STDIO_INHERIT = 1, +} DriverWasmStdioMode; + +typedef enum DriverWasmClockMode { + DRIVER_WASM_CLOCK_NONE = 0, + DRIVER_WASM_CLOCK_MONOTONIC = 1, + DRIVER_WASM_CLOCK_REALTIME = 2, +} DriverWasmClockMode; + +typedef enum DriverWasmRandomMode { + DRIVER_WASM_RANDOM_NONE = 0, + DRIVER_WASM_RANDOM_HOST = 1, + DRIVER_WASM_RANDOM_SEED = 2, +} DriverWasmRandomMode; + +typedef struct DriverWasmRunOptions { + DriverEnv* env; + size_t argv_bound; + int used; + + uint64_t max_instance_bytes; + uint64_t max_total_memory_bytes; + uint32_t max_memories; + + uint8_t imports; /* DriverWasmImportMode */ + uint8_t env_mode; + uint8_t stdio_mode; + uint8_t clock_mode; + uint8_t random_mode; + uint8_t reserved[3]; + + const char** env_pass; + const char** env_set; + const char** map_dirs; + const char** map_files; + uint32_t nenv_pass; + uint32_t nenv_set; + uint32_t nmap_dirs; + uint32_t nmap_files; + const char* const* args; + uint32_t nargs; + const char* cwd; + const char* random_seed; +} DriverWasmRunOptions; + +int driver_wasm_run_options_init(DriverWasmRunOptions*, DriverEnv*, + size_t argv_bound); +void driver_wasm_run_options_fini(DriverWasmRunOptions*); + +/* Try to consume argv[*index] as a Wasm sandbox option. + * Returns 1 on consume, 0 when the flag is unrelated, and -1 on usage error. */ +int driver_wasm_run_try_consume(DriverWasmRunOptions*, const char* tool, + int argc, char** argv, int* index); + +int driver_wasm_run_options_used(const DriverWasmRunOptions*); + +/* Returns 0 when the image is not a kit-lowered Wasm module. Otherwise runs + * the Wasm init + entry sequence, stores the result in *rc_out, and returns 1. */ +int driver_wasm_run_call_entry(const DriverWasmRunOptions*, const char* tool, + KitCompiler*, KitJit*, void* entry, + int* rc_out); + +int driver_wasm_run_call_entry_interp(const DriverWasmRunOptions*, + const char* tool, KitCompiler*, KitJit*, + KitInterpProgram*, const char* entry_name, + int* rc_out); + +#endif diff --git a/include/kit/wasm.h b/include/kit/wasm.h @@ -38,6 +38,7 @@ extern "C" { * tables, and lowered global slots. Treat as a raw buffer at the public * API surface; the binder uses module-emitted metadata to locate slots. */ typedef struct KitWasmInstance KitWasmInstance; +typedef struct KitWasmHost KitWasmHost; typedef enum KitWasmValType { KIT_WASM_VAL_I32 = 0, @@ -113,6 +114,53 @@ typedef struct KitWasmRuntimeLayout { } KitWasmRuntimeLayout; #endif +typedef enum KitWasmHostFlags { + KIT_WASM_HOST_WASI_PREVIEW1 = 1u << 0, +} KitWasmHostFlags; + +typedef enum KitWasmFsFlags { + KIT_WASM_FS_READ = 1u << 0, + KIT_WASM_FS_WRITE = 1u << 1, + KIT_WASM_FS_FILE = 1u << 2, +} KitWasmFsFlags; + +typedef struct KitWasmFsMount { + const char* host_path; /* host path exposed to the guest */ + const char* guest_path; /* guest absolute path, e.g. "/work" */ + uint32_t flags; /* KIT_WASM_FS_* */ +} KitWasmFsMount; + +typedef KitStatus (*KitWasmWriteFn)(void* user, uint32_t fd, + const uint8_t* data, size_t n, + size_t* nwritten_out); +typedef KitStatus (*KitWasmClockFn)(void* user, uint32_t clock_id, + uint64_t* ns_out); +typedef KitStatus (*KitWasmRandomFn)(void* user, uint8_t* dst, size_t n); + +typedef struct KitWasmHostConfig { + KitHeap* heap; + uint32_t flags; /* KIT_WASM_HOST_* */ + + uint64_t max_instance_bytes; + uint64_t max_total_memory_bytes; + uint32_t max_memories; + + const char* const* args; + uint32_t nargs; + const char* const* env; + uint32_t nenv; + + const KitWasmFsMount* mounts; + uint32_t nmounts; + const char* cwd; + const KitFileIO* file_io; + + KitWasmWriteFn write; + KitWasmClockFn clock; + KitWasmRandomFn random; + void* user; +} KitWasmHostConfig; + /* Static host-import descriptor. * * `func` must match the declared wasm type when called with the kit- @@ -167,6 +215,25 @@ KIT_API void kit_wasm_get_host_imports(KitCompiler*, KIT_API KitStatus kit_wasm_get_runtime_layout(KitJit* jit, KitWasmRuntimeLayout* out); +/* Create a configurable host shim and allocate/free instances with sandbox + * limits enforced before any module code runs. The config is shallow-copied: + * string arrays, mount arrays, callbacks, and file_io are borrowed and must + * outlive host use. */ +KIT_API KitStatus kit_wasm_host_new(const KitWasmHostConfig* config, + KitWasmHost** out); +KIT_API void kit_wasm_host_free(KitWasmHost*); +KIT_API KitStatus kit_wasm_instance_new(KitWasmHost*, KitJit*, + KitWasmInstance** out); +KIT_API void kit_wasm_instance_free(KitWasmInstance*); + +/* Bind the host's configured imports, currently WASI Preview1 when + * KIT_WASM_HOST_WASI_PREVIEW1 is set. */ +KIT_API KitStatus kit_wasm_host_bind_imports(KitWasmHost*, KitCompiler*, + KitJit*, KitWasmInstance*); + +/* Nonzero when a WASI proc_exit import was called. */ +KIT_API int kit_wasm_instance_exit_code(KitWasmInstance*, int* code_out); + /* Resolve every declared import in the module loaded into `jit` against * the supplied static table and/or resolver, and write the resolved * function pointers into the per-import slots of `inst`. diff --git a/mk/driver_srcs.mk b/mk/driver_srcs.mk @@ -55,6 +55,7 @@ DRIVER_SRCS += $(call need-any,CC CHECK LD RUN,driver/lib/lib_resolve.c) DRIVER_SRCS += $(call need-any,CC CHECK RUN,driver/lib/hosted.c) DRIVER_SRCS += $(call need-any,CC CHECK LD,driver/lib/runtime.c) DRIVER_SRCS += $(call need-any,AR RANLIB STRIP DBG RUN,driver/lib/inputs.c) +DRIVER_SRCS += $(call need-any,RUN,driver/lib/wasm_run.c) DRIVER_SRCS += $(call need-any,CC CHECK COMPILE,driver/lib/compile_engine.c) DRIVER_SRCS += $(call need-any,CAS PKG,driver/lib/dist_host.c) diff --git a/mk/test.mk b/mk/test.mk @@ -69,6 +69,7 @@ TEST_TARGETS = \ test-driver-pkg \ test-driver-strings \ test-driver-tools \ + test-driver-wasm \ test-hash \ test-driver-strip \ test-dwarf \ @@ -186,7 +187,7 @@ test-images: test-cf-corpus-selftest: @bash test/lib/kit_corpus_selftest.sh -test-driver: test-driver-cc test-driver-compile test-driver-ar test-driver-cas test-driver-strip test-driver-objcopy test-driver-objdump test-driver-pkg test-driver-strings test-driver-tools +test-driver: test-driver-cc test-driver-compile test-driver-ar test-driver-cas test-driver-strip test-driver-objcopy test-driver-objdump test-driver-pkg test-driver-strings test-driver-tools test-driver-wasm test-driver-cc: bin @KIT=$(abspath $(BIN)) sh test/driver/run.sh @@ -272,6 +273,9 @@ test-driver-strings: bin test-driver-tools: bin @KIT=$(abspath $(BIN)) sh test/tools/run.sh +test-driver-wasm: bin + @KIT=$(abspath $(BIN)) sh test/driver-wasm/run.sh + # DWARF consumer unit test: builds a hand-crafted DWARF-bearing ELF in # memory and exercises every kit_dwarf_* entry. It reaches into the # internal object builder to synthesize the fixture, so link individual diff --git a/src/abi/abi.h b/src/abi/abi.h @@ -124,6 +124,10 @@ typedef struct ABIFuncInfo { * Apple ARM64 sets this; AAPCS64 / SysV-x64 leave it 0 (variadics * use the same register routing as fixed args). */ u8 vararg_on_stack; + /* Minimum stack slot size/alignment for scalar stack-passed arguments. + * Zero means the backend default. Apple ARM64 uses 4-byte compact slots for + * stack arguments such as int32; AAPCS64 uses 8-byte slots. */ + u8 stack_arg_min_align; u32 vararg_gp_offset; u32 vararg_fp_offset; u32 vararg_overflow_offset; diff --git a/src/abi/abi_apple_arm64.c b/src/abi/abi_apple_arm64.c @@ -39,6 +39,7 @@ static ABIFuncInfo* apple_arm64_compute_func_info(TargetABI* a, * target OS itself. */ ABIFuncInfo* info = aapcs64_compute_func_info(a, fn); info->vararg_on_stack = 1; + info->stack_arg_min_align = 4; return info; } diff --git a/src/api/wasm_host.c b/src/api/wasm_host.c @@ -0,0 +1,816 @@ +#include <kit/wasm.h> +#include <stddef.h> +#include <stdint.h> +#include <string.h> + +typedef struct KitWasmMemoryRecord { + uint8_t* data; + uint64_t pages; + uint64_t max_pages; + uint32_t flags; +} KitWasmMemoryRecord; + +#define KIT_WASM_HOST_MAX_FDS 64u + +typedef struct KitWasmOpenFd { + uint32_t used; + KitFileData data; + const KitFileIO* io; + uint64_t pos; +} KitWasmOpenFd; + +struct KitWasmHost { + KitWasmHostConfig config; +}; + +typedef struct KitWasmRuntimeInstance { + KitWasmHost* host; + const KitWasmMemoryLayout* memory_layouts; + uint64_t allocation_bytes; + uint64_t instance_bytes; + uint64_t total_memory_bytes; + uint8_t** memories; + uint64_t* memory_bytes; + uint32_t nmemories; + int exit_called; + int exit_code; + KitWasmOpenFd fds[KIT_WASM_HOST_MAX_FDS]; + uint8_t instance[]; +} KitWasmRuntimeInstance; + +enum { + WASI_ESUCCESS = 0, + WASI_EACCES = 2, + WASI_EBADF = 8, + WASI_EINVAL = 28, + WASI_EIO = 29, + WASI_ENOMEM = 34, + WASI_ENOENT = 44, + WASI_ENOSYS = 52, + WASI_ENOTCAPABLE = 76, +}; + +static KitWasmRuntimeInstance* wasm_wrap_from_instance(KitWasmInstance* inst) { + return (KitWasmRuntimeInstance*)((uint8_t*)inst - + offsetof(KitWasmRuntimeInstance, instance)); +} + +static KitWasmInstance* wasm_instance_from_wrap(KitWasmRuntimeInstance* w) { + return (KitWasmInstance*)w->instance; +} + +static size_t wasm_cstrlen(const char* s) { + size_t n = 0; + if (!s) return 0; + while (s[n]) ++n; + return n; +} + +static int wasm_streq(const char* a, const char* b) { + size_t i = 0; + if (!a || !b) return 0; + while (a[i] && b[i]) { + if (a[i] != b[i]) return 0; + ++i; + } + return a[i] == b[i]; +} + +static KitStatus wasm_u64_to_size(uint64_t n, size_t* out) { + if (n > (uint64_t)SIZE_MAX) return KIT_INVALID; + *out = (size_t)n; + return KIT_OK; +} + +static KitStatus wasm_page_bytes(uint64_t pages, uint64_t* out) { + if (pages > UINT64_MAX / (uint64_t)KIT_WASM_PAGE_SIZE) return KIT_INVALID; + *out = pages * (uint64_t)KIT_WASM_PAGE_SIZE; + return KIT_OK; +} + +static uint32_t wasm_preopen_count(const KitWasmHost* host) { + return host ? host->config.nmounts : 0u; +} + +static const KitWasmFsMount* wasm_preopen_for_fd(KitWasmRuntimeInstance* w, + uint32_t fd) { + uint32_t index; + if (!w || !w->host || fd < 3u) return NULL; + index = fd - 3u; + if (index >= w->host->config.nmounts) return NULL; + return &w->host->config.mounts[index]; +} + +static size_t wasm_guest_parent_len(const char* path) { + size_t i; + size_t last = 0; + if (!path || path[0] != '/') return 0; + for (i = 1; path[i]; ++i) { + if (path[i] == '/') last = i; + } + return last ? last : 1u; +} + +static const char* wasm_guest_basename(const char* path) { + size_t i; + size_t last = 0; + if (!path) return NULL; + for (i = 0; path[i]; ++i) { + if (path[i] == '/') last = i + 1u; + } + return path + last; +} + +static int wasm_bytes_eq_cstr(const uint8_t* a, uint32_t alen, + const char* b) { + uint32_t i; + if (!a || !b) return 0; + for (i = 0; i < alen; ++i) { + if (b[i] == '\0' || a[i] != (uint8_t)b[i]) return 0; + } + return b[alen] == '\0'; +} + +static KitWasmMemoryRecord* wasm_memory0(KitWasmRuntimeInstance* w) { + if (!w || w->nmemories == 0 || !w->memory_layouts) return NULL; + if (w->memory_layouts[0].offset > w->instance_bytes) return NULL; + if (w->instance_bytes - w->memory_layouts[0].offset < + sizeof(KitWasmMemoryRecord)) + return NULL; + return (KitWasmMemoryRecord*)(w->instance + w->memory_layouts[0].offset); +} + +static int wasm_mem_ptr(KitWasmInstance* inst, uint32_t addr, uint32_t n, + uint8_t** out) { + KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst); + KitWasmMemoryRecord* mem = wasm_memory0(w); + uint64_t end; + uint64_t size; + if (!out || !mem || !mem->data) return 0; + if (addr > UINT32_MAX - n) return 0; + end = (uint64_t)addr + (uint64_t)n; + if (wasm_page_bytes(mem->pages, &size) != KIT_OK) return 0; + if (end > size) return 0; + *out = mem->data + addr; + return 1; +} + +static int wasm_read_u32(KitWasmInstance* inst, uint32_t addr, uint32_t* out) { + uint8_t* p; + if (!wasm_mem_ptr(inst, addr, 4u, &p)) return 0; + *out = ((uint32_t)p[0]) | ((uint32_t)p[1] << 8) | + ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); + return 1; +} + +static int wasm_write_u32(KitWasmInstance* inst, uint32_t addr, uint32_t v) { + uint8_t* p; + if (!wasm_mem_ptr(inst, addr, 4u, &p)) return 0; + p[0] = (uint8_t)(v & 0xffu); + p[1] = (uint8_t)((v >> 8) & 0xffu); + p[2] = (uint8_t)((v >> 16) & 0xffu); + p[3] = (uint8_t)((v >> 24) & 0xffu); + return 1; +} + +static int wasm_write_u64(KitWasmInstance* inst, uint32_t addr, uint64_t v) { + uint8_t* p; + if (!wasm_mem_ptr(inst, addr, 8u, &p)) return 0; + p[0] = (uint8_t)(v & 0xffu); + p[1] = (uint8_t)((v >> 8) & 0xffu); + p[2] = (uint8_t)((v >> 16) & 0xffu); + p[3] = (uint8_t)((v >> 24) & 0xffu); + p[4] = (uint8_t)((v >> 32) & 0xffu); + p[5] = (uint8_t)((v >> 40) & 0xffu); + p[6] = (uint8_t)((v >> 48) & 0xffu); + p[7] = (uint8_t)((v >> 56) & 0xffu); + return 1; +} + +static int wasm_status_errno(KitStatus st) { + switch (st) { + case KIT_OK: + return WASI_ESUCCESS; + case KIT_NOMEM: + return WASI_ENOMEM; + case KIT_NOT_FOUND: + return WASI_ENOENT; + case KIT_IO: + return WASI_EIO; + case KIT_UNSUPPORTED: + return WASI_ENOSYS; + case KIT_INVALID: + case KIT_MALFORMED: + case KIT_AMBIGUOUS: + case KIT_ERR: + default: + return WASI_EINVAL; + } +} + +static int wasm_sig(const KitWasmImportType* t, const KitWasmValType* params, + uint32_t nparams, const KitWasmValType* results, + uint32_t nresults) { + uint32_t i; + if (!t || t->nparams != nparams || t->nresults != nresults) return 0; + for (i = 0; i < nparams; ++i) + if (!t->params || t->params[i] != params[i]) return 0; + for (i = 0; i < nresults; ++i) + if (!t->results || t->results[i] != results[i]) return 0; + return 1; +} + +static void wasi_proc_exit(KitWasmInstance* inst, int32_t code) { + KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst); + w->exit_called = 1; + w->exit_code = code; +} + +static int32_t wasi_fd_write(KitWasmInstance* inst, int32_t fd_i, + int32_t iovs_i, int32_t iovs_len_i, + int32_t nwritten_i) { + KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst); + uint32_t fd = (uint32_t)fd_i; + uint32_t iovs = (uint32_t)iovs_i; + uint32_t iovs_len = (uint32_t)iovs_len_i; + uint32_t nwritten = (uint32_t)nwritten_i; + uint32_t total = 0; + uint32_t i; + if (fd != 1u && fd != 2u) return WASI_EBADF; + for (i = 0; i < iovs_len; ++i) { + uint32_t ptr; + uint32_t len; + uint8_t* data; + uint32_t off; + uint32_t rec; + size_t wrote = 0; + KitStatus st; + if (i > UINT32_MAX / 8u) return WASI_EINVAL; + off = i * 8u; + if (iovs > UINT32_MAX - off) return WASI_EINVAL; + rec = iovs + off; + if (!wasm_read_u32(inst, rec, &ptr) || + !wasm_read_u32(inst, rec + 4u, &len) || + !wasm_mem_ptr(inst, ptr, len, &data)) + return WASI_EINVAL; + if (w->host->config.write) { + st = w->host->config.write(w->host->config.user, fd, data, len, &wrote); + if (st != KIT_OK) return wasm_status_errno(st); + if (wrote > len) wrote = len; + } else { + wrote = len; + } + if (total > UINT32_MAX - (uint32_t)wrote) return WASI_EINVAL; + total += (uint32_t)wrote; + if (wrote < len) break; + } + if (!wasm_write_u32(inst, nwritten, total)) return WASI_EINVAL; + return WASI_ESUCCESS; +} + +static int wasm_strings_size(const char* const* items, uint32_t n, + uint32_t* bytes_out) { + uint64_t total = 0; + uint32_t i; + for (i = 0; i < n; ++i) { + total += (uint64_t)wasm_cstrlen(items ? items[i] : NULL) + 1u; + if (total > UINT32_MAX) return 0; + } + *bytes_out = (uint32_t)total; + return 1; +} + +static int32_t wasi_environ_sizes_get(KitWasmInstance* inst, int32_t count_i, + int32_t size_i) { + KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst); + uint32_t bytes; + if (!wasm_strings_size(w->host->config.env, w->host->config.nenv, &bytes)) + return WASI_EINVAL; + if (!wasm_write_u32(inst, (uint32_t)count_i, w->host->config.nenv) || + !wasm_write_u32(inst, (uint32_t)size_i, bytes)) + return WASI_EINVAL; + return WASI_ESUCCESS; +} + +static int wasm_write_strings(KitWasmInstance* inst, const char* const* items, + uint32_t nitems, uint32_t ptrs_addr, + uint32_t buf_addr) { + uint32_t total; + uint8_t* ptrs; + uint8_t* buf; + uint32_t off = 0; + uint32_t i; + if (!wasm_strings_size(items, nitems, &total)) return 0; + if (nitems > UINT32_MAX / 4u) return 0; + if (!wasm_mem_ptr(inst, ptrs_addr, nitems * 4u, &ptrs) || + !wasm_mem_ptr(inst, buf_addr, total, &buf)) + return 0; + (void)ptrs; + for (i = 0; i < nitems; ++i) { + const char* s = items ? items[i] : NULL; + size_t len = wasm_cstrlen(s); + if (len > (size_t)(UINT32_MAX - off - 1u)) return 0; + if (!wasm_write_u32(inst, ptrs_addr + i * 4u, buf_addr + off)) return 0; + if (len) memcpy(buf + off, s, len); + buf[off + (uint32_t)len] = 0; + off += (uint32_t)len + 1u; + } + return 1; +} + +static int32_t wasi_environ_get(KitWasmInstance* inst, int32_t env_i, + int32_t buf_i) { + KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst); + return wasm_write_strings(inst, w->host->config.env, w->host->config.nenv, + (uint32_t)env_i, + (uint32_t)buf_i) + ? WASI_ESUCCESS + : WASI_EINVAL; +} + +static int32_t wasi_args_sizes_get(KitWasmInstance* inst, int32_t count_i, + int32_t size_i) { + KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst); + uint32_t bytes; + if (!wasm_strings_size(w->host->config.args, w->host->config.nargs, &bytes)) + return WASI_EINVAL; + if (!wasm_write_u32(inst, (uint32_t)count_i, w->host->config.nargs) || + !wasm_write_u32(inst, (uint32_t)size_i, bytes)) + return WASI_EINVAL; + return WASI_ESUCCESS; +} + +static int32_t wasi_args_get(KitWasmInstance* inst, int32_t args_i, + int32_t buf_i) { + KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst); + return wasm_write_strings(inst, w->host->config.args, w->host->config.nargs, + (uint32_t)args_i, + (uint32_t)buf_i) + ? WASI_ESUCCESS + : WASI_EINVAL; +} + +static int32_t wasi_random_get(KitWasmInstance* inst, int32_t ptr_i, + int32_t len_i) { + KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst); + uint8_t* dst; + KitStatus st; + if (!w->host->config.random) return WASI_ENOSYS; + if (!wasm_mem_ptr(inst, (uint32_t)ptr_i, (uint32_t)len_i, &dst)) + return WASI_EINVAL; + st = w->host->config.random(w->host->config.user, dst, (size_t)len_i); + return wasm_status_errno(st); +} + +static int32_t wasi_clock_time_get(KitWasmInstance* inst, int32_t clock_id_i, + int64_t precision_i, int32_t time_i) { + KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst); + uint64_t ns = 0; + KitStatus st; + (void)precision_i; + if (!w->host->config.clock) return WASI_ENOSYS; + st = w->host->config.clock(w->host->config.user, (uint32_t)clock_id_i, &ns); + if (st != KIT_OK) return wasm_status_errno(st); + return wasm_write_u64(inst, (uint32_t)time_i, ns) ? WASI_ESUCCESS + : WASI_EINVAL; +} + +static int32_t wasi_fd_prestat_get(KitWasmInstance* inst, int32_t fd_i, + int32_t prestat_i) { + KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst); + const KitWasmFsMount* m = wasm_preopen_for_fd(w, (uint32_t)fd_i); + uint8_t* p; + uint32_t len; + if (!m) return WASI_EBADF; + len = (m->flags & KIT_WASM_FS_FILE) + ? (uint32_t)wasm_guest_parent_len(m->guest_path) + : (uint32_t)wasm_cstrlen(m->guest_path); + if (!wasm_mem_ptr(inst, (uint32_t)prestat_i, 8u, &p)) return WASI_EINVAL; + p[0] = 0; + p[1] = 0; + p[2] = 0; + p[3] = 0; + p[4] = (uint8_t)(len & 0xffu); + p[5] = (uint8_t)((len >> 8) & 0xffu); + p[6] = (uint8_t)((len >> 16) & 0xffu); + p[7] = (uint8_t)((len >> 24) & 0xffu); + return WASI_ESUCCESS; +} + +static int32_t wasi_fd_prestat_dir_name(KitWasmInstance* inst, int32_t fd_i, + int32_t path_i, int32_t path_len_i) { + KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst); + const KitWasmFsMount* m = wasm_preopen_for_fd(w, (uint32_t)fd_i); + uint8_t* dst; + size_t len; + if (!m) return WASI_EBADF; + len = (m->flags & KIT_WASM_FS_FILE) ? wasm_guest_parent_len(m->guest_path) + : wasm_cstrlen(m->guest_path); + if ((uint32_t)path_len_i < len) return WASI_EINVAL; + if (!wasm_mem_ptr(inst, (uint32_t)path_i, (uint32_t)len, &dst)) + return WASI_EINVAL; + if (len) memcpy(dst, m->guest_path, len); + return WASI_ESUCCESS; +} + +static int wasm_guest_path_safe(const uint8_t* path, uint32_t len) { + uint32_t i = 0; + if (!path || len == 0 || path[0] == '/') return 0; + while (i < len) { + uint32_t start = i; + while (i < len && path[i] != '/') { + if (path[i] == '\\') return 0; + ++i; + } + if (i == start) return 0; + if (i - start == 2u && path[start] == '.' && path[start + 1u] == '.') + return 0; + if (i < len) ++i; + } + return 1; +} + +static KitStatus wasm_join_path(KitHeap* heap, const char* base, + const uint8_t* rel, uint32_t rel_len, + char** out, size_t* out_size) { + size_t base_len = wasm_cstrlen(base); + size_t need; + char* p; + int slash; + if (!base || !rel || !out || !out_size) return KIT_INVALID; + slash = base_len && base[base_len - 1u] != '/' && base[base_len - 1u] != '\\'; + if (base_len > SIZE_MAX - (size_t)rel_len - (slash ? 2u : 1u)) + return KIT_INVALID; + need = base_len + (slash ? 1u : 0u) + (size_t)rel_len + 1u; + p = (char*)heap->alloc(heap, need, 1u); + if (!p) return KIT_NOMEM; + if (base_len) memcpy(p, base, base_len); + if (slash) p[base_len++] = '/'; + if (rel_len) memcpy(p + base_len, rel, rel_len); + p[base_len + (size_t)rel_len] = 0; + *out = p; + *out_size = need; + return KIT_OK; +} + +static int wasm_find_free_fd(KitWasmRuntimeInstance* w, uint32_t* out) { + uint32_t start = 3u + wasm_preopen_count(w->host); + uint32_t fd; + if (start >= KIT_WASM_HOST_MAX_FDS) return 0; + for (fd = start; fd < KIT_WASM_HOST_MAX_FDS; ++fd) { + if (!w->fds[fd].used) { + *out = fd; + return 1; + } + } + return 0; +} + +static int32_t wasi_path_open(KitWasmInstance* inst, int32_t dirfd_i, + int32_t dirflags_i, int32_t path_i, + int32_t path_len_i, int32_t oflags_i, + int64_t rights_base_i, + int64_t rights_inheriting_i, + int32_t fdflags_i, int32_t opened_fd_i) { + KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst); + const KitWasmFsMount* m = wasm_preopen_for_fd(w, (uint32_t)dirfd_i); + uint8_t* guest_path; + char* host_path = NULL; + size_t host_path_size = 0; + KitFileData data; + uint32_t fd; + KitStatus st; + (void)dirflags_i; + (void)rights_base_i; + (void)rights_inheriting_i; + (void)fdflags_i; + if (!m) return WASI_EBADF; + if (!(m->flags & KIT_WASM_FS_READ)) return WASI_EACCES; + if (oflags_i != 0) return WASI_ENOSYS; + if (!w->host->config.file_io || !w->host->config.file_io->read_all) + return WASI_ENOSYS; + if (!wasm_mem_ptr(inst, (uint32_t)path_i, (uint32_t)path_len_i, + &guest_path)) + return WASI_EINVAL; + if (!wasm_guest_path_safe(guest_path, (uint32_t)path_len_i)) + return WASI_ENOTCAPABLE; + if (m->flags & KIT_WASM_FS_FILE) { + const char* base = wasm_guest_basename(m->guest_path); + if (!base || !*base || + !wasm_bytes_eq_cstr(guest_path, (uint32_t)path_len_i, base)) + return WASI_ENOENT; + host_path = (char*)m->host_path; + } else { + st = wasm_join_path(w->host->config.heap, m->host_path, guest_path, + (uint32_t)path_len_i, &host_path, &host_path_size); + if (st != KIT_OK) return wasm_status_errno(st); + } + memset(&data, 0, sizeof data); + st = w->host->config.file_io->read_all(w->host->config.file_io->user, + host_path, &data); + if (!(m->flags & KIT_WASM_FS_FILE)) + w->host->config.heap->free(w->host->config.heap, host_path, + host_path_size); + if (st != KIT_OK) return wasm_status_errno(st); + if (!wasm_find_free_fd(w, &fd)) { + if (w->host->config.file_io->release) + w->host->config.file_io->release(w->host->config.file_io->user, &data); + return WASI_ENOMEM; + } + w->fds[fd].used = 1; + w->fds[fd].data = data; + w->fds[fd].io = w->host->config.file_io; + w->fds[fd].pos = 0; + if (!wasm_write_u32(inst, (uint32_t)opened_fd_i, fd)) return WASI_EINVAL; + return WASI_ESUCCESS; +} + +static int32_t wasi_fd_read(KitWasmInstance* inst, int32_t fd_i, + int32_t iovs_i, int32_t iovs_len_i, + int32_t nread_i) { + KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst); + uint32_t fd = (uint32_t)fd_i; + uint32_t iovs = (uint32_t)iovs_i; + uint32_t iovs_len = (uint32_t)iovs_len_i; + uint32_t total = 0; + uint32_t i; + if (fd >= KIT_WASM_HOST_MAX_FDS || !w->fds[fd].used) return WASI_EBADF; + for (i = 0; i < iovs_len; ++i) { + uint32_t ptr; + uint32_t len; + uint8_t* dst; + uint32_t off; + uint32_t rec; + uint64_t remain64; + uint32_t ncopy; + if (i > UINT32_MAX / 8u) return WASI_EINVAL; + off = i * 8u; + if (iovs > UINT32_MAX - off) return WASI_EINVAL; + rec = iovs + off; + if (!wasm_read_u32(inst, rec, &ptr) || + !wasm_read_u32(inst, rec + 4u, &len) || + !wasm_mem_ptr(inst, ptr, len, &dst)) + return WASI_EINVAL; + if (w->fds[fd].pos >= (uint64_t)w->fds[fd].data.size) break; + remain64 = (uint64_t)w->fds[fd].data.size - w->fds[fd].pos; + ncopy = len; + if ((uint64_t)ncopy > remain64) ncopy = (uint32_t)remain64; + if (ncopy) + memcpy(dst, w->fds[fd].data.data + (size_t)w->fds[fd].pos, ncopy); + w->fds[fd].pos += ncopy; + if (total > UINT32_MAX - ncopy) return WASI_EINVAL; + total += ncopy; + if (ncopy < len) break; + } + if (!wasm_write_u32(inst, (uint32_t)nread_i, total)) return WASI_EINVAL; + return WASI_ESUCCESS; +} + +static int32_t wasi_fd_close(KitWasmInstance* inst, int32_t fd_i) { + KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst); + uint32_t fd = (uint32_t)fd_i; + if (fd >= KIT_WASM_HOST_MAX_FDS || !w->fds[fd].used) return WASI_EBADF; + if (w->fds[fd].io && w->fds[fd].io->release) + w->fds[fd].io->release(w->fds[fd].io->user, &w->fds[fd].data); + memset(&w->fds[fd], 0, sizeof w->fds[fd]); + return WASI_ESUCCESS; +} + +static void* wasm_wasi_resolve(void* user, const char* module, + const char* field, + const KitWasmImportType* type) { + KitWasmHost* host = (KitWasmHost*)user; + KitWasmValType r_i32[1] = {KIT_WASM_VAL_I32}; + KitWasmValType p_proc_exit[1] = {KIT_WASM_VAL_I32}; + KitWasmValType p2_i32[2] = {KIT_WASM_VAL_I32, KIT_WASM_VAL_I32}; + KitWasmValType p4_i32[4] = {KIT_WASM_VAL_I32, KIT_WASM_VAL_I32, + KIT_WASM_VAL_I32, KIT_WASM_VAL_I32}; + KitWasmValType p_clock[3] = {KIT_WASM_VAL_I32, KIT_WASM_VAL_I64, + KIT_WASM_VAL_I32}; + KitWasmValType p_path_open[9] = { + KIT_WASM_VAL_I32, KIT_WASM_VAL_I32, KIT_WASM_VAL_I32, + KIT_WASM_VAL_I32, KIT_WASM_VAL_I32, KIT_WASM_VAL_I64, + KIT_WASM_VAL_I64, KIT_WASM_VAL_I32, KIT_WASM_VAL_I32}; + if (!host || !(host->config.flags & KIT_WASM_HOST_WASI_PREVIEW1)) + return NULL; + if (!wasm_streq(module, "wasi_snapshot_preview1")) return NULL; + if (wasm_streq(field, "proc_exit") && + wasm_sig(type, p_proc_exit, 1u, NULL, 0u)) + return (void*)(uintptr_t)wasi_proc_exit; + if (wasm_streq(field, "fd_write") && + wasm_sig(type, p4_i32, 4u, r_i32, 1u)) + return (void*)(uintptr_t)wasi_fd_write; + if (wasm_streq(field, "environ_sizes_get") && + wasm_sig(type, p2_i32, 2u, r_i32, 1u)) + return (void*)(uintptr_t)wasi_environ_sizes_get; + if (wasm_streq(field, "environ_get") && + wasm_sig(type, p2_i32, 2u, r_i32, 1u)) + return (void*)(uintptr_t)wasi_environ_get; + if (wasm_streq(field, "args_sizes_get") && + wasm_sig(type, p2_i32, 2u, r_i32, 1u)) + return (void*)(uintptr_t)wasi_args_sizes_get; + if (wasm_streq(field, "args_get") && + wasm_sig(type, p2_i32, 2u, r_i32, 1u)) + return (void*)(uintptr_t)wasi_args_get; + if (wasm_streq(field, "random_get") && + wasm_sig(type, p2_i32, 2u, r_i32, 1u)) + return (void*)(uintptr_t)wasi_random_get; + if (wasm_streq(field, "clock_time_get") && + wasm_sig(type, p_clock, 3u, r_i32, 1u)) + return (void*)(uintptr_t)wasi_clock_time_get; + if (wasm_streq(field, "fd_prestat_get") && + wasm_sig(type, p2_i32, 2u, r_i32, 1u)) + return (void*)(uintptr_t)wasi_fd_prestat_get; + if (wasm_streq(field, "fd_prestat_dir_name") && + wasm_sig(type, p4_i32, 4u, r_i32, 1u)) + return (void*)(uintptr_t)wasi_fd_prestat_dir_name; + if (wasm_streq(field, "path_open") && + wasm_sig(type, p_path_open, 9u, r_i32, 1u)) + return (void*)(uintptr_t)wasi_path_open; + if (wasm_streq(field, "fd_read") && + wasm_sig(type, p4_i32, 4u, r_i32, 1u)) + return (void*)(uintptr_t)wasi_fd_read; + if (wasm_streq(field, "fd_close") && + wasm_sig(type, p_proc_exit, 1u, r_i32, 1u)) + return (void*)(uintptr_t)wasi_fd_close; + return NULL; +} + +KIT_API KitStatus kit_wasm_host_new(const KitWasmHostConfig* config, + KitWasmHost** out) { + KitWasmHost* host; + uint32_t i; + if (!out || !config || !config->heap) return KIT_INVALID; + *out = NULL; + if (config->nmounts && !config->mounts) return KIT_INVALID; + if (config->nmounts > KIT_WASM_HOST_MAX_FDS - 3u) return KIT_INVALID; + for (i = 0; i < config->nmounts; ++i) { + const KitWasmFsMount* m = &config->mounts[i]; + if (!m->host_path || !m->guest_path || m->guest_path[0] != '/') + return KIT_INVALID; + if ((m->flags & KIT_WASM_FS_FILE) && + (!*wasm_guest_basename(m->guest_path))) + return KIT_INVALID; + } + host = (KitWasmHost*)config->heap->alloc(config->heap, sizeof(*host), + _Alignof(KitWasmHost)); + if (!host) return KIT_NOMEM; + host->config = *config; + *out = host; + return KIT_OK; +} + +KIT_API void kit_wasm_host_free(KitWasmHost* host) { + KitHeap* heap; + if (!host) return; + heap = host->config.heap; + heap->free(heap, host, sizeof(*host)); +} + +KIT_API KitStatus kit_wasm_instance_new(KitWasmHost* host, KitJit* jit, + KitWasmInstance** out) { + KitWasmRuntimeLayout layout; + KitWasmRuntimeInstance* w; + KitHeap* heap; + uint64_t instance_bytes; + uint64_t allocation_bytes; + size_t allocation_size; + uint64_t total_memory = 0; + KitStatus st; + uint32_t i; + if (!out || !host || !host->config.heap || !jit) return KIT_INVALID; + *out = NULL; + heap = host->config.heap; + st = kit_wasm_get_runtime_layout(jit, &layout); + if (st != KIT_OK) return st; + if (layout.nmemories > host->config.max_memories) return KIT_INVALID; + instance_bytes = layout.instance_size ? layout.instance_size : 1u; + if (instance_bytes > host->config.max_instance_bytes) return KIT_INVALID; + if (instance_bytes > UINT64_MAX - offsetof(KitWasmRuntimeInstance, instance)) + return KIT_INVALID; + allocation_bytes = + (uint64_t)offsetof(KitWasmRuntimeInstance, instance) + instance_bytes; + st = wasm_u64_to_size(allocation_bytes, &allocation_size); + if (st != KIT_OK) return st; + w = (KitWasmRuntimeInstance*)heap->alloc(heap, allocation_size, + _Alignof(KitWasmRuntimeInstance)); + if (!w) return KIT_NOMEM; + memset(w, 0, allocation_size); + w->host = host; + w->memory_layouts = layout.memories; + w->allocation_bytes = allocation_bytes; + w->instance_bytes = instance_bytes; + w->nmemories = layout.nmemories; + if (layout.nmemories) { + size_t ptr_bytes; + size_t bytes_bytes; + if ((uint64_t)layout.nmemories > (uint64_t)SIZE_MAX / sizeof(uint8_t*) || + (uint64_t)layout.nmemories > (uint64_t)SIZE_MAX / sizeof(uint64_t)) { + heap->free(heap, w, allocation_size); + return KIT_INVALID; + } + ptr_bytes = (size_t)layout.nmemories * sizeof(uint8_t*); + bytes_bytes = (size_t)layout.nmemories * sizeof(uint64_t); + w->memories = (uint8_t**)heap->alloc(heap, ptr_bytes, _Alignof(uint8_t*)); + w->memory_bytes = + (uint64_t*)heap->alloc(heap, bytes_bytes, _Alignof(uint64_t)); + if (!w->memories || !w->memory_bytes) { + if (w->memory_bytes) heap->free(heap, w->memory_bytes, bytes_bytes); + if (w->memories) heap->free(heap, w->memories, ptr_bytes); + heap->free(heap, w, allocation_size); + return KIT_NOMEM; + } + memset(w->memories, 0, ptr_bytes); + memset(w->memory_bytes, 0, bytes_bytes); + } + for (i = 0; i < layout.nmemories; ++i) { + const KitWasmMemoryLayout* ml = &layout.memories[i]; + KitWasmMemoryRecord* rec; + uint64_t mem_bytes; + size_t mem_size; + if (ml->max_pages < ml->min_pages) { + kit_wasm_instance_free(wasm_instance_from_wrap(w)); + return KIT_MALFORMED; + } + if (ml->offset > instance_bytes || + instance_bytes - ml->offset < sizeof(KitWasmMemoryRecord)) { + kit_wasm_instance_free(wasm_instance_from_wrap(w)); + return KIT_MALFORMED; + } + st = wasm_page_bytes(ml->max_pages, &mem_bytes); + if (st != KIT_OK) { + kit_wasm_instance_free(wasm_instance_from_wrap(w)); + return st; + } + if (mem_bytes > host->config.max_total_memory_bytes || + total_memory > host->config.max_total_memory_bytes - mem_bytes) { + kit_wasm_instance_free(wasm_instance_from_wrap(w)); + return KIT_INVALID; + } + total_memory += mem_bytes; + st = wasm_u64_to_size(mem_bytes, &mem_size); + if (st != KIT_OK) { + kit_wasm_instance_free(wasm_instance_from_wrap(w)); + return st; + } + if (mem_size) { + w->memories[i] = (uint8_t*)heap->alloc(heap, mem_size, 1u); + if (!w->memories[i]) { + kit_wasm_instance_free(wasm_instance_from_wrap(w)); + return KIT_NOMEM; + } + memset(w->memories[i], 0, mem_size); + } + w->memory_bytes[i] = mem_bytes; + rec = (KitWasmMemoryRecord*)(w->instance + ml->offset); + rec->data = w->memories[i]; + } + w->total_memory_bytes = total_memory; + *out = wasm_instance_from_wrap(w); + return KIT_OK; +} + +KIT_API void kit_wasm_instance_free(KitWasmInstance* inst) { + KitWasmRuntimeInstance* w; + KitHeap* heap; + uint32_t i; + size_t allocation_size; + if (!inst) return; + w = wasm_wrap_from_instance(inst); + heap = w->host->config.heap; + for (i = 0; i < KIT_WASM_HOST_MAX_FDS; ++i) { + if (w->fds[i].used && w->fds[i].io && w->fds[i].io->release) + w->fds[i].io->release(w->fds[i].io->user, &w->fds[i].data); + } + for (i = 0; i < w->nmemories; ++i) { + size_t mem_size; + if (w->memories && w->memories[i] && + wasm_u64_to_size(w->memory_bytes[i], &mem_size) == KIT_OK) + heap->free(heap, w->memories[i], mem_size); + } + if (w->memories) + heap->free(heap, w->memories, (size_t)w->nmemories * sizeof(uint8_t*)); + if (w->memory_bytes) + heap->free(heap, w->memory_bytes, + (size_t)w->nmemories * sizeof(uint64_t)); + allocation_size = (size_t)w->allocation_bytes; + heap->free(heap, w, allocation_size); +} + +KIT_API KitStatus kit_wasm_host_bind_imports(KitWasmHost* host, + KitCompiler* compiler, + KitJit* jit, + KitWasmInstance* inst) { + if (!host || !compiler || !jit || !inst) return KIT_INVALID; + return kit_wasm_bind_host_imports(compiler, jit, inst, NULL, 0, + wasm_wasi_resolve, host); +} + +KIT_API int kit_wasm_instance_exit_code(KitWasmInstance* inst, int* code_out) { + KitWasmRuntimeInstance* w; + if (!inst) return 0; + w = wasm_wrap_from_instance(inst); + if (!w->exit_called) return 0; + if (code_out) *code_out = w->exit_code; + return 1; +} diff --git a/src/arch/aa64/native.c b/src/arch/aa64/native.c @@ -2525,20 +2525,28 @@ static const ABIArgInfo* aa_param_abi(NativeTarget* t, const ABIFuncInfo* abi, return scratch; } -/* Stack footprint of a single argument part: each occupies a multiple of 8 - * bytes (so a 16-byte FP part such as binary128 takes 16, not 8). */ -static u32 aa_part_stack_size(const ABIArgPart* part) { - return align_up_u32(part->size ? part->size : 8u, 8u); +/* Stack footprint of a single argument part. AAPCS64 uses 8-byte slots; + * Apple ARM64 uses compact 4-byte slots for stack-passed int32-sized values. */ +static u32 aa_stack_arg_min_align(const ABIFuncInfo* abi) { + return (abi && abi->stack_arg_min_align) ? abi->stack_arg_min_align : 8u; } -/* Natural stack alignment of a part: at least 8, capped at 16 (binary128). */ -static u32 aa_part_stack_align(const ABIArgPart* part) { +/* Natural stack alignment of a part, capped at 16 (binary128). */ +static u32 aa_part_stack_align(const ABIFuncInfo* abi, + const ABIArgPart* part) { + u32 min_align = aa_stack_arg_min_align(abi); u32 al = part->align ? part->align : 8u; - if (al < 8u) al = 8u; + if (al < min_align) al = min_align; if (al > 16u) al = 16u; return al; } +static u32 aa_part_stack_size(const ABIFuncInfo* abi, + const ABIArgPart* part) { + return align_up_u32(part->size ? part->size : 8u, + aa_part_stack_align(abi, part)); +} + /* The scalar type used to move one ABI part through a register. Aggregate * args/results are split into parts; each part must move at its own width, not * the (possibly >8-byte) aggregate width. */ @@ -2560,15 +2568,16 @@ static KitCgTypeId aa_part_scalar_type(const ABIArgPart* part) { } } -static u32 aa_class_stack_size(const ABIArgInfo* ai) { +static u32 aa_class_stack_size(const ABIFuncInfo* abi, const ABIArgInfo* ai) { u32 total = 0; if (!ai || ai->kind == ABI_ARG_IGNORE) return 0; if (ai->kind == ABI_ARG_INDIRECT) return 8u; for (u32 p = 0; p < ai->nparts; ++p) { - total = align_up_u32(total, aa_part_stack_align(&ai->parts[p])); - total += aa_part_stack_size(&ai->parts[p]); + total = align_up_u32(total, aa_part_stack_align(abi, &ai->parts[p])); + total += aa_part_stack_size(abi, &ai->parts[p]); } - return align_up_u32(total ? total : 8u, 8u); + return align_up_u32(total ? total : aa_stack_arg_min_align(abi), + aa_stack_arg_min_align(abi)); } static u32 aa_call_stack_size(NativeTarget* t, const NativeCallDesc* desc) { @@ -2581,7 +2590,7 @@ static u32 aa_call_stack_size(NativeTarget* t, const NativeCallDesc* desc) { abi && abi->variadic && abi->vararg_on_stack && i >= abi->nparams; if (ai->kind == ABI_ARG_IGNORE) continue; if (force_stack) { - stack += aa_class_stack_size(ai); + stack += aa_class_stack_size(abi, ai); continue; } if (ai->kind == ABI_ARG_INDIRECT) { @@ -2597,15 +2606,15 @@ static u32 aa_call_stack_size(NativeTarget* t, const NativeCallDesc* desc) { if (next_fp < 8u) next_fp++; else { - stack = align_up_u32(stack, aa_part_stack_align(part)); - stack += aa_part_stack_size(part); + stack = align_up_u32(stack, aa_part_stack_align(abi, part)); + stack += aa_part_stack_size(abi, part); } } else { if (next_int < 8u) next_int++; else { - stack = align_up_u32(stack, aa_part_stack_align(part)); - stack += aa_part_stack_size(part); + stack = align_up_u32(stack, aa_part_stack_align(abi, part)); + stack += aa_part_stack_size(abi, part); } } } @@ -2723,12 +2732,13 @@ static void aa_plan_call(NativeTarget* t, const NativeCallDesc* desc, if (force_stack) { NativeLoc tmpreg = native_loc_reg(desc->args[i].type, NATIVE_REG_INT, AA_TMP0); - u32 n = aa_class_stack_size(ai); + u32 n = aa_class_stack_size(abi, ai); u32 off = 0; while (off < n) { - aa_load_part(t, tmpreg, desc->args[i], off, 8); - aa_store_outgoing_part(t, tail_call, stack + off, tmpreg, 8); - off += 8; + u32 chunk = (n - off > 8u) ? 8u : (n - off); + aa_load_part(t, tmpreg, desc->args[i], off, chunk); + aa_store_outgoing_part(t, tail_call, stack + off, tmpreg, chunk); + off += chunk; } stack += n; continue; @@ -2769,9 +2779,9 @@ static void aa_plan_call(NativeTarget* t, const NativeCallDesc* desc, NativeLoc tmpreg = native_loc_reg( desc->args[i].type, cls, cls == NATIVE_REG_FP ? 16u : AA_TMP0); aa_load_part(t, tmpreg, desc->args[i], part->src_offset, part->size); - stack = align_up_u32(stack, aa_part_stack_align(part)); + stack = align_up_u32(stack, aa_part_stack_align(abi, part)); aa_store_outgoing_part(t, tail_call, stack, tmpreg, part->size); - stack += aa_part_stack_size(part); + stack += aa_part_stack_size(abi, part); } } } @@ -3867,14 +3877,14 @@ static void aa_bind_native_param(NativeTarget* t, const CGParamDesc* p, NativeAddr saddr; src = native_loc_reg(p->type, cls, tmp); a->next_param_stack = - align_up_u32(a->next_param_stack, aa_part_stack_align(part)); + align_up_u32(a->next_param_stack, aa_part_stack_align(abi, part)); memset(&saddr, 0, sizeof saddr); saddr.base_kind = NATIVE_ADDR_BASE_REG; saddr.base.reg = AA_FP; saddr.base_type = p->type; saddr.offset = aa_fp_off_in_arg(a, a->next_param_stack); aa_emit_mem(a, 1, src, saddr, aa_mem_for_type(t, p->type, part->size)); - a->next_param_stack += aa_part_stack_size(part); + a->next_param_stack += aa_part_stack_size(abi, part); } if (dst.kind == NATIVE_LOC_NONE) { /* Unused parameter: only the ABI cursor advances. */ diff --git a/test/driver-wasm/run.sh b/test/driver-wasm/run.sh @@ -0,0 +1,325 @@ +#!/bin/sh +# Driver-level checks for `kit run` Wasm sandbox CLI behavior. + +set -u + +script_dir=$(cd "$(dirname "$0")" && pwd) +repo_root=$(cd "$script_dir/../.." && pwd) + +KIT="${KIT:-$repo_root/build/kit}" + +if [ ! -x "$KIT" ]; then + echo "driver-wasm: kit binary not found at $KIT" >&2 + exit 2 +fi + +work=$(mktemp -d "${TMPDIR:-/tmp}/kit-driver-wasm-test.XXXXXX") +trap 'rm -rf "$work"' EXIT + +KIT_KIT_DIR="$repo_root/test/lib" +. "$repo_root/test/lib/kit_sh_kit.sh" +kit_report_init + +cat > "$work/return0.wat" <<'WAT' +(module + (func (export "test_main") (result i32) + i32.const 0)) +WAT + +cat > "$work/host_add.wat" <<'WAT' +(module + (import "env" "host_add" (func $host_add (param i32 i32) (result i32))) + (func (export "test_main") (result i32) + i32.const 21 + i32.const 21 + call $host_add + i32.const 42 + i32.sub)) +WAT + +cat > "$work/memory_2page_max.wat" <<'WAT' +(module + (memory 1 2) + (func (export "test_main") (result i32) + i32.const 0)) +WAT + +cat > "$work/memory_one.wat" <<'WAT' +(module + (memory 1) + (func (export "test_main") (result i32) + i32.const 0)) +WAT + +cat > "$work/wasi_exit.wat" <<'WAT' +(module + (import "wasi_snapshot_preview1" "proc_exit" (func $proc_exit (param i32))) + (func (export "test_main") (result i32) + i32.const 7 + call $proc_exit + i32.const 0)) +WAT + +cat > "$work/wasi_stdout.wat" <<'WAT' +(module + (import "wasi_snapshot_preview1" "fd_write" + (func $fd_write (param i32 i32 i32 i32) (result i32))) + (memory 1) + (data (i32.const 0) "\08\00\00\00\0b\00\00\00") + (data (i32.const 8) "hello wasi\0a") + (func (export "test_main") (result i32) + i32.const 1 + i32.const 0 + i32.const 1 + i32.const 20 + call $fd_write + i32.const 0 + i32.ne)) +WAT + +cat > "$work/wasi_env.wat" <<'WAT' +(module + (import "wasi_snapshot_preview1" "environ_sizes_get" + (func $sizes (param i32 i32) (result i32))) + (import "wasi_snapshot_preview1" "environ_get" + (func $get (param i32 i32) (result i32))) + (memory 1) + (func (export "test_main") (result i32) + (local $err i32) + (local $p i32) + i32.const 0 + i32.const 4 + call $sizes + local.set $err + (if (i32.ne (local.get $err) (i32.const 0)) + (then (return (local.get $err)))) + (if (i32.ne (i32.load (i32.const 0)) (i32.const 1)) + (then (return (i32.const 10)))) + i32.const 8 + i32.const 64 + call $get + local.set $err + (if (i32.ne (local.get $err) (i32.const 0)) + (then (return (local.get $err)))) + i32.const 8 + i32.load + local.set $p + (if (i32.ne (i32.load8_u (local.get $p)) (i32.const 75)) + (then (return (i32.const 11)))) + (if (i32.ne (i32.load8_u offset=13 (local.get $p)) (i32.const 61)) + (then (return (i32.const 12)))) + (if (i32.ne (i32.load8_u offset=14 (local.get $p)) (i32.const 111)) + (then (return (i32.const 13)))) + i32.const 0)) +WAT + +cat > "$work/wasi_args.wat" <<'WAT' +(module + (import "wasi_snapshot_preview1" "args_sizes_get" + (func $sizes (param i32 i32) (result i32))) + (import "wasi_snapshot_preview1" "args_get" + (func $get (param i32 i32) (result i32))) + (memory 1) + (func (export "test_main") (result i32) + (local $err i32) + (local $p i32) + i32.const 0 + i32.const 4 + call $sizes + local.set $err + (if (i32.ne (local.get $err) (i32.const 0)) + (then (return (local.get $err)))) + (if (i32.ne (i32.load (i32.const 0)) (i32.const 2)) + (then (return (i32.const 20)))) + i32.const 8 + i32.const 64 + call $get + local.set $err + (if (i32.ne (local.get $err) (i32.const 0)) + (then (return (local.get $err)))) + i32.const 12 + i32.load + local.set $p + (if (i32.ne (i32.load8_u (local.get $p)) (i32.const 97)) + (then (return (i32.const 21)))) + (if (i32.ne (i32.load8_u offset=4 (local.get $p)) (i32.const 97)) + (then (return (i32.const 22)))) + i32.const 0)) +WAT + +cat > "$work/wasi_fs_read.wat" <<'WAT' +(module + (import "wasi_snapshot_preview1" "path_open" + (func $path_open + (param i32 i32 i32 i32 i32 i64 i64 i32 i32) (result i32))) + (import "wasi_snapshot_preview1" "fd_read" + (func $fd_read (param i32 i32 i32 i32) (result i32))) + (import "wasi_snapshot_preview1" "fd_close" + (func $fd_close (param i32) (result i32))) + (memory 1) + (data (i32.const 0) "\80\00\00\00\05\00\00\00") + (data (i32.const 64) "hello.txt") + (func (export "test_main") (result i32) + (local $fd i32) + (local $err i32) + i32.const 3 + i32.const 0 + i32.const 64 + i32.const 9 + i32.const 0 + i64.const 0 + i64.const 0 + i32.const 0 + i32.const 16 + call $path_open + local.set $err + (if (i32.ne (local.get $err) (i32.const 0)) + (then (return (local.get $err)))) + i32.const 16 + i32.load + local.set $fd + local.get $fd + i32.const 0 + i32.const 1 + i32.const 20 + call $fd_read + local.set $err + (if (i32.ne (local.get $err) (i32.const 0)) + (then (return (local.get $err)))) + (if (i32.ne (i32.load (i32.const 20)) (i32.const 5)) + (then (return (i32.const 30)))) + (if (i32.ne (i32.load8_u (i32.const 128)) (i32.const 104)) + (then (return (i32.const 31)))) + (if (i32.ne (i32.load8_u offset=4 (i32.const 128)) (i32.const 111)) + (then (return (i32.const 32)))) + local.get $fd + call $fd_close + drop + i32.const 0)) +WAT + +cat > "$work/wasi_unknown.wat" <<'WAT' +(module + (import "wasi_snapshot_preview1" "path_unlink_file" + (func $unlink (param i32 i32 i32) (result i32))) + (memory 1) + (func (export "test_main") (result i32) + i32.const 0 + i32.const 0 + i32.const 0 + call $unlink)) +WAT + +cat > "$work/main.c" <<'SRC' +int main(void) { return 0; } +SRC + +printf 'hello' > "$work/hello.txt" + +run_ok "wasm-default-runs-no-imports" \ + "$KIT" run -e test_main "$work/return0.wat" + +KIT_TEST_HOST_IMPORTS=1 "$KIT" run -e test_main "$work/host_add.wat" \ + > "$work/default-deny.out" 2> "$work/default-deny.err" +if [ "$?" -ne 0 ]; then + ok "wasm-default-denies-test-env-fallback" +else + not_ok "wasm-default-denies-test-env-fallback" "$work/default-deny.out" +fi +contains "wasm-default-deny-diag" "$work/default-deny.err" \ + "wasm host import unresolved" + +run_ok "wasm-explicit-test-import" \ + "$KIT" run --wasm-imports=test -e test_main "$work/host_add.wat" + +run_fail "wasm-memory-cap-fails" \ + "$KIT" run --wasm-memory-max=64K -e test_main "$work/memory_2page_max.wat" +contains "wasm-memory-cap-diag" "$work/wasm-memory-cap-fails.err" \ + "wasm linear memory reservation exceeds 65536 bytes" + +run_ok "wasm-memory-cap-allows" \ + "$KIT" run --wasm-memory-max=128K -e test_main "$work/memory_2page_max.wat" + +run_fail "wasm-memories-cap-fails" \ + "$KIT" run --wasm-memories-max=0 -e test_main "$work/memory_one.wat" +contains "wasm-memories-cap-diag" "$work/wasm-memories-cap-fails.err" \ + "wasm memory count exceeds 0" + +run_ok "wasm-policy-flags-parse" \ + "$KIT" run --wasm-env=inherit --wasm-env-pass=PATH \ + --wasm-env-set=KIT_TEST=1 --wasm-map-dir="$work=/work:ro" \ + --wasm-map-file="$work/return0.wat=/return0.wat:ro" --wasm-cwd=/work \ + --wasm-stdio=inherit --wasm-clock=monotonic --wasm-random=seed:abcd \ + -e test_main "$work/return0.wat" + +"$KIT" run --wasm-wasi -e test_main "$work/wasi_exit.wat" \ + > "$work/wasm-wasi-proc-exit.out" 2> "$work/wasm-wasi-proc-exit.err" +if [ "$?" -eq 7 ]; then + ok "wasm-wasi-proc-exit" +else + not_ok "wasm-wasi-proc-exit" "$work/wasm-wasi-proc-exit.err" +fi + +"$KIT" run --wasm-wasi --wasm-stdio=inherit -e test_main \ + "$work/wasi_stdout.wat" > "$work/wasm-wasi-stdout.out" \ + 2> "$work/wasm-wasi-stdout.err" +if [ "$?" -eq 0 ]; then ok "wasm-wasi-stdout"; else + not_ok "wasm-wasi-stdout" "$work/wasm-wasi-stdout.err" +fi +contains "wasm-wasi-stdout-text" "$work/wasm-wasi-stdout.out" "hello wasi" + +"$KIT" run --wasm-wasi --wasm-stdio=null -e test_main \ + "$work/wasi_stdout.wat" > "$work/wasm-wasi-stdout-null.out" \ + 2> "$work/wasm-wasi-stdout-null.err" +if [ "$?" -eq 0 ] && [ ! -s "$work/wasm-wasi-stdout-null.out" ]; then + ok "wasm-wasi-stdout-null" +else + not_ok "wasm-wasi-stdout-null" "$work/wasm-wasi-stdout-null.err" +fi + +run_ok "wasm-wasi-env-set" \ + "$KIT" run --wasm-wasi --wasm-env-set=KIT_WASM_TEST=ok \ + -e test_main "$work/wasi_env.wat" + +run_ok "wasm-wasi-args" \ + "$KIT" run --wasm-wasi -e test_main "$work/wasi_args.wat" -- alpha + +run_ok "wasm-wasi-fs-read" \ + "$KIT" run --wasm-wasi --wasm-map-dir="$work=/sandbox:ro" \ + -e test_main "$work/wasi_fs_read.wat" + +run_ok "wasm-wasi-fs-map-file-read" \ + "$KIT" run --wasm-wasi \ + --wasm-map-file="$work/hello.txt=/sandbox/hello.txt:ro" \ + -e test_main "$work/wasi_fs_read.wat" + +run_fail "wasm-wasi-unknown-import-fails" \ + "$KIT" run --wasm-wasi -e test_main "$work/wasi_unknown.wat" +contains "wasm-wasi-unknown-import-diag" \ + "$work/wasm-wasi-unknown-import-fails.err" \ + "wasm host import unresolved" + +run_fail "wasm-flags-reject-native" \ + "$KIT" run --wasm-memory-max=1M "$work/main.c" +contains "wasm-flags-reject-native-diag" "$work/wasm-flags-reject-native.err" \ + "wasm sandbox flags require a wasm input" + +run_fail "wasm-rejects-mixed-native-inputs" \ + "$KIT" run -e test_main "$work/return0.wat" "$work/main.c" +contains "wasm-rejects-mixed-native-inputs-diag" \ + "$work/wasm-rejects-mixed-native-inputs.err" \ + "sandboxed wasm run does not support mixed native inputs" + +run_fail "wasm-import-mode-invalid" \ + "$KIT" run --wasm-imports=env -e test_main "$work/return0.wat" +contains "wasm-import-mode-invalid-diag" \ + "$work/wasm-import-mode-invalid.err" \ + "unknown --wasm-imports value" + +run_fail "wasm-fs-invalid" \ + "$KIT" run --wasm-fs=host -e test_main "$work/return0.wat" +contains "wasm-fs-invalid-diag" "$work/wasm-fs-invalid.err" \ + "unknown --wasm-fs value" + +kit_summary test-driver-wasm +kit_exit diff --git a/test/wasm/run.sh b/test/wasm/run.sh @@ -69,7 +69,7 @@ case "$TEST_OBJ" in esac EXEC_TAG="$EXEC_ARCH-$EXEC_OS" export KIT_TEST_ARCH="$TEST_ARCH" KIT_TEST_OBJ="$TEST_OBJ" -export KIT_TEST_HOST_IMPORTS=1 # enable the runner's canned host-import resolver +WASM_RUN_TEST_IMPORTS=(--wasm-imports=test) have_wasm_tool=0; [ -x "$WASM_TOOL" ] && have_wasm_tool=1 have_jit_runner=0; [ -x "$JIT_RUNNER" ] && [ "$host_matches" -eq 1 ] && have_jit_runner=1 @@ -221,13 +221,13 @@ kit_lane_W() { else kit_skip "$KIT_NAME/W" "no wasm-tool"; fi } kit_lane_D() { - wf_rc "$KIT_NAME/D-wat" "$KIT_EXPECTED" "$KIT_BIN" run -e test_main "$KIT_SRC" - if wasm_build; then wf_rc "$KIT_NAME/D-wasm" "$KIT_EXPECTED" "$KIT_BIN" run -e test_main "$WASM" + wf_rc "$KIT_NAME/D-wat" "$KIT_EXPECTED" "$KIT_BIN" run "${WASM_RUN_TEST_IMPORTS[@]}" -e test_main "$KIT_SRC" + if wasm_build; then wf_rc "$KIT_NAME/D-wasm" "$KIT_EXPECTED" "$KIT_BIN" run "${WASM_RUN_TEST_IMPORTS[@]}" -e test_main "$WASM" else kit_skip "$KIT_NAME/D-wasm" "no wasm-tool"; fi } kit_lane_N() { - wf_rc_interp "$KIT_NAME/N-wat" "$KIT_EXPECTED" "$KIT_BIN" run --no-jit -e test_main "$KIT_SRC" - if wasm_build; then wf_rc_interp "$KIT_NAME/N-wasm" "$KIT_EXPECTED" "$KIT_BIN" run --no-jit -e test_main "$WASM" + wf_rc_interp "$KIT_NAME/N-wat" "$KIT_EXPECTED" "$KIT_BIN" run --no-jit "${WASM_RUN_TEST_IMPORTS[@]}" -e test_main "$KIT_SRC" + if wasm_build; then wf_rc_interp "$KIT_NAME/N-wasm" "$KIT_EXPECTED" "$KIT_BIN" run --no-jit "${WASM_RUN_TEST_IMPORTS[@]}" -e test_main "$WASM" else kit_skip "$KIT_NAME/N-wasm" "no wasm-tool"; fi } kit_lane_O() {