kit

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

commit 9bd665b95d72f696db659675bf3c85367e1b8cf1
parent 5bd8de1ceeabdaef0abc018ea94891371381655d
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Wed,  3 Jun 2026 19:18:22 -0700

Harden wasm runtime import handling

Diffstat:
Mdriver/cmd/run.c | 244++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Minclude/kit/wasm.h | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mlang/wasm/cg.c | 579++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Mlang/wasm/host_imports.c | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mlang/wasm/runtime_abi.h | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Msrc/wasm/decode.c | 3---
Msrc/wasm/validate.c | 84++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Msrc/wasm/wat.c | 15++++++++++++++-
Mtest/link/harness/jit_runner.c | 205+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Atest/wasm/cases/host_import_dedup_sig.expect | 1+
Atest/wasm/cases/host_import_dedup_sig.wat | 12++++++++++++
Mtest/wasm/cases/import_slot_unused.wat | 2+-
Atest/wasm/cases/many_funcs_70.expect | 1+
Atest/wasm/cases/many_funcs_70.wat | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/wasm/cases/memory_grow_large.expect | 1+
Atest/wasm/cases/memory_grow_large.wat | 14++++++++++++++
Atest/wasm/cases/passive_elem_no_table.expect | 1+
Atest/wasm/cases/passive_elem_no_table.wat | 6++++++
Atest/wasm/cases/table_fill_null.expect | 1+
Atest/wasm/cases/table_fill_null.wat | 8++++++++
Atest/wasm/cases/table_grow_null.expect | 1+
Atest/wasm/cases/table_grow_null.wat | 10++++++++++
Atest/wasm/err/data_segment_oob.wat | 3+++
Atest/wasm/err/memory_bad_align.wat | 5+++++
Atest/wasm/err/memory_init_active.wat | 8++++++++
Atest/wasm/err/table_init_active.wat | 11+++++++++++
Mtest/wasm/harness/start_wasm.c | 102++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Mtest/wasm/run.sh | 16+++++++++++-----
Mtest/wasm/trap/host_import_unbound.wat | 4++--
Atest/wasm/trap/import_global_unbound.wat | 4++++
Atest/wasm/trap/import_memory_unbound.wat | 4++++
Atest/wasm/trap/import_table_unbound.wat | 4++++
Atest/wasm/trap/memory_offset_overflow.wat | 6++++++
33 files changed, 1467 insertions(+), 252 deletions(-)

diff --git a/driver/cmd/run.c b/driver/cmd/run.c @@ -6,6 +6,7 @@ #include <kit/wasm.h> #include <stdint.h> #include <stdlib.h> +#include <string.h> #include "cflags.h" #include "driver.h" @@ -696,67 +697,221 @@ typedef struct RunWasmMemoryPrefix { uint32_t flags; } RunWasmMemoryPrefix; -#define RUN_WASM_INSTANCE_BYTES (64u * 1024u) -#define RUN_WASM_MEMORY_BYTES (16u * 1024u * 1024u) +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, uint8_t** inst_out, - uint8_t** mem_out) { - uint8_t* instance = - (uint8_t*)driver_alloc_zeroed(ro->env, RUN_WASM_INSTANCE_BYTES); - uint8_t* memory = - (uint8_t*)driver_alloc_zeroed(ro->env, RUN_WASM_MEMORY_BYTES); + 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; - KitWasmHostImport canned[1]; - extern int32_t run_test_host_add(KitWasmInstance*, int32_t, int32_t); - if (!instance || !memory) { + 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"); - driver_free(ro->env, memory, RUN_WASM_MEMORY_BYTES); - driver_free(ro->env, instance, RUN_WASM_INSTANCE_BYTES); return 1; } - for (uint32_t i = 0; i < 8u; ++i) - ((RunWasmMemoryPrefix*)instance)[i].data = - memory + i * (RUN_WASM_MEMORY_BYTES / 8u); + 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")) { - canned[0].module = "env"; - canned[0].field = "host_add"; - canned[0].func = (void*)(uintptr_t)run_test_host_add; - h_imports = canned; - h_nimports = 1; + h_resolve = run_wasm_test_resolve; + h_user = NULL; } - if (kit_wasm_bind_host_imports(compiler, jit, (KitWasmInstance*)instance, - h_imports, h_nimports, h_resolve, - h_user) != KIT_OK) { - driver_errf(RUN_TOOL, "wasm host import bind failed"); - driver_free(ro->env, memory, RUN_WASM_MEMORY_BYTES); - driver_free(ro->env, instance, RUN_WASM_INSTANCE_BYTES); - return 1; + { + 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; + } } - *inst_out = instance; - *mem_out = memory; + *alloc_out = alloc; return 0; } -static void run_wasm_free_instance(RunOptions* ro, uint8_t* instance, - uint8_t* memory) { - driver_free(ro->env, memory, RUN_WASM_MEMORY_BYTES); - driver_free(ro->env, instance, RUN_WASM_INSTANCE_BYTES); +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")); - uint8_t* instance; - uint8_t* memory; + RunWasmInstanceAlloc alloc; union { void* p; WasmInitFn fn; @@ -766,15 +921,15 @@ static int run_call_wasm_entry(RunOptions* ro, KitCompiler* compiler, WasmMainFn fn; } entry_u; if (!init_sym) return 0; - if (run_wasm_make_instance(ro, compiler, jit, &instance, &memory) != 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*)instance); - *rc_out = entry_u.fn(instance); - run_wasm_free_instance(ro, instance, memory); + init_u.fn((KitWasmInstance*)alloc.instance); + *rc_out = entry_u.fn(alloc.instance); + run_wasm_free_instance(ro, &alloc); return 1; } @@ -790,8 +945,7 @@ static int run_call_wasm_entry_interp(RunOptions* ro, KitCompiler* compiler, kit_interp_lookup(interp, KIT_SLICE_LIT("__kit_wasm_init")); KitInterpFunc* entry_fn = kit_interp_lookup(interp, kit_slice_cstr(ro->entry)); - uint8_t* instance; - uint8_t* memory; + RunWasmInstanceAlloc alloc; uint64_t args[1]; int64_t ret = 0; KitInterpStatus s; @@ -802,11 +956,11 @@ static int run_call_wasm_entry_interp(RunOptions* ro, KitCompiler* compiler, *rc_out = 1; return 1; } - if (run_wasm_make_instance(ro, compiler, jit, &instance, &memory) != 0) { + if (run_wasm_make_instance(ro, compiler, jit, &alloc) != 0) { *rc_out = 1; return 1; } - args[0] = (uint64_t)(uintptr_t)instance; + 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); @@ -817,7 +971,7 @@ static int run_call_wasm_entry_interp(RunOptions* ro, KitCompiler* compiler, KIT_SLICE_ARG(kit_slice_cstr(ro->entry))); *rc_out = 1; } - run_wasm_free_instance(ro, instance, memory); + run_wasm_free_instance(ro, &alloc); return 1; } diff --git a/include/kit/wasm.h b/include/kit/wasm.h @@ -57,14 +57,72 @@ typedef struct KitWasmImportType { uint32_t nresults; } KitWasmImportType; +enum { KIT_WASM_PAGE_SIZE = 65536u }; + +#ifndef KIT_WASM_MEMORY_FLAG_TYPES +#define KIT_WASM_MEMORY_FLAG_TYPES +enum { + KIT_WASM_MEMORY_SHARED = 1u << 0, + KIT_WASM_MEMORY_64 = 1u << 1, +}; +#endif + +#ifndef KIT_WASM_IMPORT_KIND_TYPES +#define KIT_WASM_IMPORT_KIND_TYPES +typedef enum KitWasmImportKind { + KIT_WASM_IMPORT_FUNC = 0, + KIT_WASM_IMPORT_TABLE = 1, + KIT_WASM_IMPORT_MEMORY = 2, + KIT_WASM_IMPORT_GLOBAL = 3, +} KitWasmImportKind; +#endif + +typedef struct KitWasmMemoryImportType { + uint64_t min_pages; + uint64_t max_pages; + uint32_t flags; /* KIT_WASM_MEMORY_* */ + uint32_t has_max; /* nonzero when the module declared an explicit maximum */ +} KitWasmMemoryImportType; + +typedef struct KitWasmTableImportType { + KitWasmValType elem_type; + uint32_t min; + uint32_t max; + uint32_t has_max; /* nonzero when the module declared an explicit maximum */ +} KitWasmTableImportType; + +typedef struct KitWasmGlobalImportType { + KitWasmValType type; + uint32_t mutable_; +} KitWasmGlobalImportType; + +#ifndef KIT_WASM_RUNTIME_LAYOUT_TYPES +#define KIT_WASM_RUNTIME_LAYOUT_TYPES +typedef struct KitWasmMemoryLayout { + uint64_t offset; /* byte offset of this KitWasmMemory in the instance */ + uint64_t min_pages; /* initial page count */ + uint64_t max_pages; /* backing allocation must cover this page count */ + uint32_t flags; /* KIT_WASM_MEMORY_* */ + uint32_t reserved; +} KitWasmMemoryLayout; + +typedef struct KitWasmRuntimeLayout { + uint64_t instance_size; + const KitWasmMemoryLayout* memories; /* borrowed from the JIT image */ + uint32_t nmemories; +} KitWasmRuntimeLayout; +#endif + /* Static host-import descriptor. * * `func` must match the declared wasm type when called with the kit- * instance ABI prepended: * ret (*)(KitWasmInstance*, [wasm params...]) * - * The binder validates (nparams, nresults) and the per-value type bytes - * against the module's declared import type and refuses on mismatch. + * Static entries are raw C function pointers; the binder can match them by + * name but cannot prove the C signature. Use a KitWasmResolveFn when the + * host needs to validate imports against the declared KitWasmImportType + * before returning a function pointer. * * Strings are borrowed: they must outlive the next link / bind. */ typedef struct KitWasmHostImport { @@ -75,8 +133,8 @@ typedef struct KitWasmHostImport { /* Dynamic resolver. Invoked for imports not found in the static table. * Returns a function pointer with the kit-instance ABI for the requested - * import, or NULL to leave the slot unbound (the call site traps on - * invocation). `type` describes the declared wasm signature. */ + * import, or NULL to reject instantiation. `type` describes the declared wasm + * signature. */ typedef void* (*KitWasmResolveFn)(void* user, const char* module, const char* field, const KitWasmImportType* type); @@ -84,8 +142,8 @@ typedef void* (*KitWasmResolveFn)(void* user, const char* module, /* Configure host-import resolution for subsequent links on this compiler. * * `imports` is consulted first (linear scan by module+field). On miss - * `resolve` is invoked. Either may be NULL. Unresolved imports remain - * NULL — calling them traps via the existing import-null check. + * `resolve` is invoked. Either may be NULL. Unresolved imports make + * `kit_wasm_bind_host_imports` fail, matching Wasm instantiation semantics. * * The provided table and resolver pointers must outlive the next call to * `kit_wasm_bind_host_imports` against this compiler. */ @@ -104,24 +162,37 @@ KIT_API void kit_wasm_get_host_imports(KitCompiler*, KitWasmResolveFn* resolve_out, void** user_out); +/* Read the module-emitted runtime layout metadata from a JIT image. Returns + * KIT_NOT_FOUND when the image is not a kit-lowered Wasm module. */ +KIT_API KitStatus kit_wasm_get_runtime_layout(KitJit* jit, + KitWasmRuntimeLayout* 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`. * * The function reads three module-emitted symbols out of `jit`: * __kit_wasm_nimports uint32 count - * __kit_wasm_imports array of {module, field, typeidx, slot_offset} - * __kit_wasm_types array of {params, nparams, results, nresults} + * __kit_wasm_imports array of + * {module, field, kind, desc_index, slot_offset} + * __kit_wasm_nfunc_import_types + * unique function-import type-description count + * __kit_wasm_types unique function-import type descriptions * If those symbols are absent (module has no imports, or was not built * through kit's wasm frontend), the call is a no-op and returns KIT_OK. * - * Slots whose `(module, field)` resolves to NULL are left untouched — the - * caller chose not to bind them, and the in-module null check will trap - * if they are invoked. + * Function imports are resolved and written today. Memory, table, and global + * imports have emitted descriptors and reserved instance slots, but this + * binder returns KIT_UNSUPPORTED for those kinds until host-backed binding is + * implemented; runners must treat that as instantiation failure. + * + * Slots whose `(module, field)` resolves to NULL make binding fail. The + * in-module null checks are still present as a defensive guard against + * malformed or manually-built instances. * - * Returns KIT_MALFORMED on signature mismatch between the host import - * and the declared wasm type, naming the offending import via the diag - * sink configured on `compiler` (passed implicitly through the JIT). */ + * Returns KIT_NOT_FOUND when a function import is unresolved, KIT_UNSUPPORTED + * for currently-unhandled import kinds, or KIT_MALFORMED if the image's import + * metadata is inconsistent. */ KIT_API KitStatus kit_wasm_bind_host_imports(KitCompiler* compiler, KitJit* jit, KitWasmInstance* inst, const KitWasmHostImport* imports, diff --git a/lang/wasm/cg.c b/lang/wasm/cg.c @@ -1,5 +1,6 @@ #include <kit/support/arena.h> +#include "runtime_abi.h" #include "wasm/wasm.h" static KitCgTypeId wasm_cg_type(KitCompiler* c, KitCgBuiltinTypes b, @@ -115,12 +116,12 @@ typedef struct WasmCgRuntime { KitCgTypeId instance_ptr_ty; uint64_t table_entry_size; /* Byte offsets within the instance struct for each top-level slot. */ - uint64_t memory_offset[64]; - uint64_t func_import_offset[64]; - uint64_t func_ref_entry_offset[64]; - uint64_t global_offset[64]; - uint64_t table_offset[64]; - uint64_t table_entries_offset[64]; + uint64_t* memory_offset; + uint64_t* func_import_offset; + uint64_t* func_ref_entry_offset; + uint64_t* global_offset; + uint64_t* table_offset; + uint64_t* table_entries_offset; /* Byte offsets within their containing record. */ uint64_t memory_data_offset; uint64_t memory_pages_offset; @@ -247,6 +248,13 @@ static uint64_t wasm_cg_field_offset(KitCompiler* c, KitCgTypeId ty, return off; } +static uint32_t wasm_cg_checked_add_u32(KitCompiler* c, uint32_t a, + uint32_t b, KitSrcLoc loc) { + if (UINT32_MAX - a < b) + wasm_error(c, loc, "wasm: module layout is too large"); + return a + b; +} + static void wasm_cg_build_runtime(KitCompiler* c, KitCgBuiltinTypes b, const WasmModule* m, WasmCgRuntime* rt, KitArena* arena) { @@ -261,36 +269,81 @@ static void wasm_cg_build_runtime(KitCompiler* c, KitCgBuiltinTypes b, * func_ref entry per func) + nglobals + 2*ntables + ndata + nelems. * Allocate the upper bound from the arena so nfields can grow with the * module. */ - uint32_t instance_cap = m->nmemories + m->nfuncs + m->nfuncs + m->nglobals + - 2u * m->ntables + m->ndata + 2u * m->nelems; + uint32_t instance_cap = 0; KitCgField* instance_fields = instance_cap ? kit_arena_zarray(arena, KitCgField, instance_cap) : NULL; uint32_t nfields = 0; - uint32_t memory_field_idx[64]; - uint32_t func_import_field_idx[64]; - uint32_t func_ref_entry_field_idx[64]; - uint32_t global_field_idx[64]; - uint32_t table_field_idx[64]; - uint32_t table_entries_field_idx[64]; + uint32_t* memory_field_idx = NULL; + uint32_t* func_import_field_idx = NULL; + uint32_t* func_ref_entry_field_idx = NULL; + uint32_t* global_field_idx = NULL; + uint32_t* table_field_idx = NULL; + uint32_t* table_entries_field_idx = NULL; memset(rt, 0, sizeof *rt); + + instance_cap = + wasm_cg_checked_add_u32(c, instance_cap, m->nmemories, wasm_loc(0, 0)); + instance_cap = + wasm_cg_checked_add_u32(c, instance_cap, m->nfuncs, wasm_loc(0, 0)); + instance_cap = + wasm_cg_checked_add_u32(c, instance_cap, m->nfuncs, wasm_loc(0, 0)); + instance_cap = + wasm_cg_checked_add_u32(c, instance_cap, m->nglobals, wasm_loc(0, 0)); + if (m->ntables > UINT32_MAX / 2u) + wasm_error(c, wasm_loc(0, 0), "wasm: module layout is too large"); + instance_cap = wasm_cg_checked_add_u32(c, instance_cap, 2u * m->ntables, + wasm_loc(0, 0)); + instance_cap = + wasm_cg_checked_add_u32(c, instance_cap, m->ndata, wasm_loc(0, 0)); + if (m->nelems > UINT32_MAX / 2u) + wasm_error(c, wasm_loc(0, 0), "wasm: module layout is too large"); + instance_cap = wasm_cg_checked_add_u32(c, instance_cap, 2u * m->nelems, + wasm_loc(0, 0)); + instance_fields = + instance_cap ? kit_arena_zarray(arena, KitCgField, instance_cap) : NULL; + rt->memory_field = m->nmemories ? kit_arena_zarray(arena, uint32_t, m->nmemories) : NULL; + rt->memory_offset = + m->nmemories ? kit_arena_zarray(arena, uint64_t, m->nmemories) : NULL; rt->func_import_field = m->nfuncs ? kit_arena_zarray(arena, uint32_t, m->nfuncs) : NULL; + rt->func_import_offset = + m->nfuncs ? kit_arena_zarray(arena, uint64_t, m->nfuncs) : NULL; rt->func_ref_entry_field = m->nfuncs ? kit_arena_zarray(arena, uint32_t, m->nfuncs) : NULL; + rt->func_ref_entry_offset = + m->nfuncs ? kit_arena_zarray(arena, uint64_t, m->nfuncs) : NULL; rt->global_field = m->nglobals ? kit_arena_zarray(arena, uint32_t, m->nglobals) : NULL; + rt->global_offset = + m->nglobals ? kit_arena_zarray(arena, uint64_t, m->nglobals) : NULL; rt->table_field = m->ntables ? kit_arena_zarray(arena, uint32_t, m->ntables) : NULL; + rt->table_offset = + m->ntables ? kit_arena_zarray(arena, uint64_t, m->ntables) : NULL; rt->table_entries_field = m->ntables ? kit_arena_zarray(arena, uint32_t, m->ntables) : NULL; + rt->table_entries_offset = + m->ntables ? kit_arena_zarray(arena, uint64_t, m->ntables) : NULL; rt->passive_data_field = m->ndata ? kit_arena_zarray(arena, uint32_t, m->ndata) : NULL; rt->passive_elem_field = m->nelems ? kit_arena_zarray(arena, uint32_t, m->nelems) : NULL; rt->passive_elem_storage_field = m->nelems ? kit_arena_zarray(arena, uint32_t, m->nelems) : NULL; + memory_field_idx = + m->nmemories ? kit_arena_zarray(arena, uint32_t, m->nmemories) : NULL; + func_import_field_idx = + m->nfuncs ? kit_arena_zarray(arena, uint32_t, m->nfuncs) : NULL; + func_ref_entry_field_idx = + m->nfuncs ? kit_arena_zarray(arena, uint32_t, m->nfuncs) : NULL; + global_field_idx = + m->nglobals ? kit_arena_zarray(arena, uint32_t, m->nglobals) : NULL; + table_field_idx = + m->ntables ? kit_arena_zarray(arena, uint32_t, m->ntables) : NULL; + table_entries_field_idx = + m->ntables ? kit_arena_zarray(arena, uint32_t, m->ntables) : NULL; rt->i8_ptr_ty = kit_cg_type_ptr(c, b.id[KIT_CG_BUILTIN_I8], 0); rt->void_ptr_ty = rt->i8_ptr_ty; memset(memory_fields, 0, sizeof memory_fields); @@ -647,12 +700,17 @@ static void wasm_cg_memory_check(KitCompiler* c, KitCg* cg, KitCgBuiltinTypes b, KitCgLocal instance_local, const WasmInsn* in) { uint32_t width = wasm_mem_width(in->kind); - uint64_t end = in->offset64 + width; + uint64_t end; KitCgLabel ok = kit_cg_label_new(cg); - uint32_t max_pages = (uint32_t)(m->memories[in->memidx].has_max - ? m->memories[in->memidx].max_pages - : m->memories[in->memidx].min_pages); - if (end > (uint64_t)max_pages * 65536u) { + uint64_t max_pages = m->memories[in->memidx].has_max + ? m->memories[in->memidx].max_pages + : m->memories[in->memidx].min_pages; + if (in->offset64 > UINT64_MAX - (uint64_t)width) { + wasm_cg_trap_bounds(cg, rt); + return; + } + end = in->offset64 + (uint64_t)width; + if (max_pages > UINT64_MAX / 65536u || end > max_pages * 65536u) { wasm_cg_trap_bounds(cg, rt); return; } @@ -1030,13 +1088,10 @@ static KitCgSym wasm_cg_intern_cstr(KitCg* cg, KitCgBuiltinTypes b, b.id[KIT_CG_BUILTIN_I8]); } -/* Emit the three host-import metadata symbols read by - * kit_wasm_bind_host_imports: __kit_wasm_nimports (uint32), - * __kit_wasm_imports (KitWasmImportDesc[]), and __kit_wasm_types - * (KitWasmTypeDesc[]). Layout matches runtime_abi.h. When the module has - * no function imports, only the count (=0) is emitted; the other two - * symbols are omitted so the binder can no-op early via kit_jit_lookup - * returning NULL. */ +/* Emit the host-import metadata symbols read by kit_wasm_bind_host_imports. + * Layout matches runtime_abi.h. When the module has no imports, only the + * count (=0) is emitted; descriptor/type arrays are omitted so the binder can + * no-op early via kit_jit_lookup returning NULL. */ static void wasm_cg_emit_host_import_metadata(KitCompiler* c, KitCg* cg, KitCgBuiltinTypes b, const WasmModule* m, @@ -1048,18 +1103,39 @@ static void wasm_cg_emit_host_import_metadata(KitCompiler* c, KitCg* cg, uint32_t ptr_size = (uint32_t)kit_cg_type_size(c, ptr_ty); uint32_t ptr_align = ptr_size ? ptr_size : 8u; uint32_t nimports = 0; - /* The wire format of one KitWasmImportDesc on the runtime side is - * { const char* module; const char* field; uint32_t typeidx; - * uint32_t slot_offset; } - * with natural alignment. Total = 2*ptr + 8. */ - uint32_t desc_size = 2u * ptr_size + 8u; - KitCgSym nimports_sym, imports_sym, types_sym; + uint32_t nfunc_imports = 0; + uint32_t nmemory_imports = 0; + uint32_t ntable_imports = 0; + uint32_t nglobal_imports = 0; + uint32_t desc_size = 2u * ptr_size + 16u; + uint32_t type_desc_size = + 2u * ptr_size + 8u + + (ptr_align > 4u ? 2u * (ptr_align - 4u) : 0u); + KitCgSym nimports_sym, imports_sym; KitCgDecl decl; KitCgDataDefAttrs data_attrs; uint32_t i; - for (i = 0; i < m->nfuncs; ++i) - if (m->funcs[i].is_import) nimports++; + for (i = 0; i < m->nfuncs; ++i) { + if (!m->funcs[i].is_import) continue; + nimports++; + nfunc_imports++; + } + for (i = 0; i < m->nmemories; ++i) { + if (!m->memories[i].is_import) continue; + nimports++; + nmemory_imports++; + } + for (i = 0; i < m->ntables; ++i) { + if (!m->tables[i].is_import) continue; + nimports++; + ntable_imports++; + } + for (i = 0; i < m->nglobals; ++i) { + if (!m->globals[i].is_import) continue; + nimports++; + nglobal_imports++; + } /* Always emit the count symbol so the binder can read it unconditionally. * If zero, the descriptor/type arrays are omitted. */ @@ -1086,25 +1162,45 @@ static void wasm_cg_emit_host_import_metadata(KitCompiler* c, KitCg* cg, if (!nimports) return; /* Walk imports once, build sym handles for module/field strings, capture - * each import's typeidx and slot offset. Build a compact list of unique - * typeidxs used by imports so __kit_wasm_types is no larger than - * needed. */ + * each import's kind-specific descriptor index and slot offset. Function + * import signatures are deduplicated into __kit_wasm_types; the emitted + * __kit_wasm_nfunc_import_types count lets the binder validate the compact + * descriptor indexes before reading that table. */ typedef struct WasmImportEmit { KitCgSym module_sym; KitCgSym field_sym; - uint32_t local_typeidx; /* index into the emitted types array */ + uint32_t kind; + uint32_t desc_index; uint32_t slot_offset; } WasmImportEmit; WasmImportEmit* descs = kit_arena_zarray(arena, WasmImportEmit, nimports); - uint32_t* type_remap = - kit_arena_zarray(arena, uint32_t, m->ntypes ? m->ntypes : 1u); - uint32_t* local_to_module = kit_arena_zarray(arena, uint32_t, nimports); + KitWasmMemoryImportDesc* memory_descs = + nmemory_imports ? kit_arena_zarray(arena, KitWasmMemoryImportDesc, + nmemory_imports) + : NULL; + KitWasmTableImportDesc* table_descs = + ntable_imports + ? kit_arena_zarray(arena, KitWasmTableImportDesc, ntable_imports) + : NULL; + KitWasmGlobalImportDesc* global_descs = + nglobal_imports ? kit_arena_zarray(arena, KitWasmGlobalImportDesc, + nglobal_imports) + : NULL; + uint32_t* type_remap = nfunc_imports + ? kit_arena_zarray(arena, uint32_t, + m->ntypes ? m->ntypes : 1u) + : NULL; + uint32_t* local_to_module = + nfunc_imports ? kit_arena_zarray(arena, uint32_t, nfunc_imports) : NULL; uint32_t ntypes = 0; uint32_t d = 0; - for (i = 0; i < m->ntypes; ++i) type_remap[i] = UINT32_MAX; + uint32_t mem_d = 0; + uint32_t table_d = 0; + uint32_t global_d = 0; + for (i = 0; type_remap && i < m->ntypes; ++i) type_remap[i] = UINT32_MAX; for (i = 0; i < m->nfuncs; ++i) { const WasmFunc* f = &m->funcs[i]; - uint64_t slot_off = 0; + uint64_t slot_off; uint32_t module_typeidx; if (!f->is_import) continue; module_typeidx = f->typeidx; @@ -1121,12 +1217,9 @@ static void wasm_cg_emit_host_import_metadata(KitCompiler* c, KitCg* cg, wasm_cg_intern_cstr(cg, b, f->import_module ? f->import_module : ""); descs[d].field_sym = wasm_cg_intern_cstr(cg, b, f->import_name ? f->import_name : ""); - descs[d].local_typeidx = type_remap[module_typeidx]; - if (kit_cg_type_record_field(c, rt->instance_ty, rt->func_import_field[i], - NULL, &slot_off) != KIT_OK) { - wasm_error(c, f->loc, "wasm: failed to query import slot offset"); - return; - } + descs[d].kind = KIT_WASM_IMPORT_FUNC; + descs[d].desc_index = type_remap[module_typeidx]; + slot_off = rt->func_import_offset[i]; if (slot_off > UINT32_MAX) { wasm_error(c, f->loc, "wasm: import slot offset exceeds u32"); return; @@ -1134,17 +1227,88 @@ static void wasm_cg_emit_host_import_metadata(KitCompiler* c, KitCg* cg, descs[d].slot_offset = (uint32_t)slot_off; d++; } + for (i = 0; i < m->nmemories; ++i) { + const WasmMemory* mem = &m->memories[i]; + uint64_t max_pages; + uint32_t flags; + if (!mem->is_import) continue; + max_pages = mem->has_max ? mem->max_pages : mem->min_pages; + flags = (mem->shared ? KIT_WASM_MEMORY_SHARED : 0u) | + (mem->is64 ? KIT_WASM_MEMORY_64 : 0u); + descs[d].module_sym = + wasm_cg_intern_cstr(cg, b, + mem->import_module ? mem->import_module : ""); + descs[d].field_sym = + wasm_cg_intern_cstr(cg, b, mem->import_name ? mem->import_name : ""); + descs[d].kind = KIT_WASM_IMPORT_MEMORY; + descs[d].desc_index = mem_d; + if (rt->memory_offset[i] > UINT32_MAX) { + wasm_error(c, wasm_loc(0, 0), "wasm: memory import slot exceeds u32"); + return; + } + descs[d].slot_offset = (uint32_t)rt->memory_offset[i]; + memory_descs[mem_d].min_pages = mem->min_pages; + memory_descs[mem_d].max_pages = max_pages; + memory_descs[mem_d].flags = flags; + memory_descs[mem_d].has_max = mem->has_max ? 1u : 0u; + mem_d++; + d++; + } + for (i = 0; i < m->ntables; ++i) { + const WasmTable* t = &m->tables[i]; + uint32_t max; + if (!t->is_import) continue; + max = t->has_max ? t->max : t->min; + descs[d].module_sym = + wasm_cg_intern_cstr(cg, b, t->import_module ? t->import_module : ""); + descs[d].field_sym = + wasm_cg_intern_cstr(cg, b, t->import_name ? t->import_name : ""); + descs[d].kind = KIT_WASM_IMPORT_TABLE; + descs[d].desc_index = table_d; + if (rt->table_offset[i] > UINT32_MAX) { + wasm_error(c, wasm_loc(0, 0), "wasm: table import slot exceeds u32"); + return; + } + descs[d].slot_offset = (uint32_t)rt->table_offset[i]; + table_descs[table_d].elem_type = (uint32_t)t->elem_type; + table_descs[table_d].min = t->min; + table_descs[table_d].max = max; + table_descs[table_d].has_max = t->has_max ? 1u : 0u; + table_d++; + d++; + } + for (i = 0; i < m->nglobals; ++i) { + const WasmGlobal* g = &m->globals[i]; + if (!g->is_import) continue; + descs[d].module_sym = + wasm_cg_intern_cstr(cg, b, g->import_module ? g->import_module : ""); + descs[d].field_sym = + wasm_cg_intern_cstr(cg, b, g->import_name ? g->import_name : ""); + descs[d].kind = KIT_WASM_IMPORT_GLOBAL; + descs[d].desc_index = global_d; + if (rt->global_offset[i] > UINT32_MAX) { + wasm_error(c, g->loc, "wasm: global import slot exceeds u32"); + return; + } + descs[d].slot_offset = (uint32_t)rt->global_offset[i]; + global_descs[global_d].type = (uint32_t)g->type; + global_descs[global_d].mutable_ = g->mutable_ ? 1u : 0u; + global_d++; + d++; + } /* __kit_wasm_types: array of KitWasmTypeDesc. * { const u8* params; u32 nparams; const u8* results; u32 nresults; } - * Each typeidx referenced by an import gets one entry. Param/result bytes - * are emitted as interned const u8 arrays using raw WasmValType bytes. */ - { + * Each distinct imported function signature gets one entry. Param/result + * bytes are emitted as interned const u8 arrays using raw WasmValType + * bytes. */ + if (ntypes) { + KitCgSym nfunc_import_types_sym; + KitCgSym types_sym; KitCgSym* param_syms = kit_arena_zarray(arena, KitCgSym, ntypes); KitCgSym* result_syms = kit_arena_zarray(arena, KitCgSym, ntypes); - uint32_t type_desc_size = 2u * ptr_size + 8u; KitCgTypeId types_array_ty = kit_cg_type_array( - c, u8_ty, (uint64_t)type_desc_size * (ntypes ? ntypes : 1u)); + c, u8_ty, (uint64_t)type_desc_size * ntypes); for (uint32_t k = 0; k < ntypes; ++k) { const WasmFuncType* t = &m->types[local_to_module[k]]; uint8_t* pbuf = @@ -1164,6 +1328,25 @@ static void wasm_cg_emit_host_import_metadata(KitCompiler* c, KitCg* cg, memset(&decl, 0, sizeof decl); decl.kind = KIT_CG_DECL_OBJECT; decl.linkage_name = kit_cg_c_linkage_name( + c, kit_sym_intern(c, KIT_SLICE_LIT("__kit_wasm_nfunc_import_types"))); + decl.display_name = decl.linkage_name; + decl.type = u32_ty; + decl.sym.bind = KIT_SB_GLOBAL; + decl.as.object.flags = KIT_CG_OBJ_READONLY; + nfunc_import_types_sym = kit_cg_decl(cg, decl); + if (!nfunc_import_types_sym) + wasm_error(c, wasm_loc(0, 0), + "wasm: failed to declare __kit_wasm_nfunc_import_types"); + memset(&data_attrs, 0, sizeof data_attrs); + data_attrs.flags = KIT_CG_DATADEF_READONLY; + data_attrs.align = 4; + kit_cg_data_begin(cg, nfunc_import_types_sym, data_attrs); + kit_cg_data_int(cg, (uint64_t)ntypes, u32_ty); + kit_cg_data_end(cg); + + memset(&decl, 0, sizeof decl); + decl.kind = KIT_CG_DECL_OBJECT; + decl.linkage_name = kit_cg_c_linkage_name( c, kit_sym_intern(c, KIT_SLICE_LIT("__kit_wasm_types"))); decl.display_name = decl.linkage_name; decl.type = types_array_ty; @@ -1196,6 +1379,93 @@ static void wasm_cg_emit_host_import_metadata(KitCompiler* c, KitCg* cg, kit_cg_data_end(cg); } + if (nmemory_imports) { + KitCgSym memory_types_sym; + KitCgTypeId memory_array_ty = kit_cg_type_array( + c, u8_ty, (uint64_t)sizeof(KitWasmMemoryImportDesc) * nmemory_imports); + memset(&decl, 0, sizeof decl); + decl.kind = KIT_CG_DECL_OBJECT; + decl.linkage_name = kit_cg_c_linkage_name( + c, kit_sym_intern(c, + KIT_SLICE_LIT("__kit_wasm_memory_import_types"))); + decl.display_name = decl.linkage_name; + decl.type = memory_array_ty; + decl.sym.bind = KIT_SB_GLOBAL; + decl.as.object.flags = KIT_CG_OBJ_READONLY; + memory_types_sym = kit_cg_decl(cg, decl); + if (!memory_types_sym) + wasm_error(c, wasm_loc(0, 0), + "wasm: failed to declare __kit_wasm_memory_import_types"); + memset(&data_attrs, 0, sizeof data_attrs); + data_attrs.flags = KIT_CG_DATADEF_READONLY; + data_attrs.align = 8; + kit_cg_data_begin(cg, memory_types_sym, data_attrs); + for (uint32_t k = 0; k < nmemory_imports; ++k) { + kit_cg_data_align(cg, 8); + kit_cg_data_int(cg, memory_descs[k].min_pages, b.id[KIT_CG_BUILTIN_I64]); + kit_cg_data_int(cg, memory_descs[k].max_pages, b.id[KIT_CG_BUILTIN_I64]); + kit_cg_data_int(cg, memory_descs[k].flags, u32_ty); + kit_cg_data_int(cg, memory_descs[k].has_max, u32_ty); + } + kit_cg_data_end(cg); + } + + if (ntable_imports) { + KitCgSym table_types_sym; + KitCgTypeId table_array_ty = kit_cg_type_array( + c, u8_ty, (uint64_t)sizeof(KitWasmTableImportDesc) * ntable_imports); + memset(&decl, 0, sizeof decl); + decl.kind = KIT_CG_DECL_OBJECT; + decl.linkage_name = kit_cg_c_linkage_name( + c, kit_sym_intern(c, KIT_SLICE_LIT("__kit_wasm_table_import_types"))); + decl.display_name = decl.linkage_name; + decl.type = table_array_ty; + decl.sym.bind = KIT_SB_GLOBAL; + decl.as.object.flags = KIT_CG_OBJ_READONLY; + table_types_sym = kit_cg_decl(cg, decl); + if (!table_types_sym) + wasm_error(c, wasm_loc(0, 0), + "wasm: failed to declare __kit_wasm_table_import_types"); + memset(&data_attrs, 0, sizeof data_attrs); + data_attrs.flags = KIT_CG_DATADEF_READONLY; + data_attrs.align = 4; + kit_cg_data_begin(cg, table_types_sym, data_attrs); + for (uint32_t k = 0; k < ntable_imports; ++k) { + kit_cg_data_int(cg, table_descs[k].elem_type, u32_ty); + kit_cg_data_int(cg, table_descs[k].min, u32_ty); + kit_cg_data_int(cg, table_descs[k].max, u32_ty); + kit_cg_data_int(cg, table_descs[k].has_max, u32_ty); + } + kit_cg_data_end(cg); + } + + if (nglobal_imports) { + KitCgSym global_types_sym; + KitCgTypeId global_array_ty = kit_cg_type_array( + c, u8_ty, (uint64_t)sizeof(KitWasmGlobalImportDesc) * nglobal_imports); + memset(&decl, 0, sizeof decl); + decl.kind = KIT_CG_DECL_OBJECT; + decl.linkage_name = kit_cg_c_linkage_name( + c, kit_sym_intern(c, KIT_SLICE_LIT("__kit_wasm_global_import_types"))); + decl.display_name = decl.linkage_name; + decl.type = global_array_ty; + decl.sym.bind = KIT_SB_GLOBAL; + decl.as.object.flags = KIT_CG_OBJ_READONLY; + global_types_sym = kit_cg_decl(cg, decl); + if (!global_types_sym) + wasm_error(c, wasm_loc(0, 0), + "wasm: failed to declare __kit_wasm_global_import_types"); + memset(&data_attrs, 0, sizeof data_attrs); + data_attrs.flags = KIT_CG_DATADEF_READONLY; + data_attrs.align = 4; + kit_cg_data_begin(cg, global_types_sym, data_attrs); + for (uint32_t k = 0; k < nglobal_imports; ++k) { + kit_cg_data_int(cg, global_descs[k].type, u32_ty); + kit_cg_data_int(cg, global_descs[k].mutable_, u32_ty); + } + kit_cg_data_end(cg); + } + /* __kit_wasm_imports: array of KitWasmImportDesc. */ { KitCgTypeId imports_array_ty = @@ -1220,13 +1490,111 @@ static void wasm_cg_emit_host_import_metadata(KitCompiler* c, KitCg* cg, kit_cg_data_align(cg, ptr_align); kit_cg_data_addr(cg, descs[k].module_sym, 0, ptr_size, 0); kit_cg_data_addr(cg, descs[k].field_sym, 0, ptr_size, 0); - kit_cg_data_int(cg, (uint64_t)descs[k].local_typeidx, u32_ty); + kit_cg_data_int(cg, (uint64_t)descs[k].kind, u32_ty); + kit_cg_data_int(cg, (uint64_t)descs[k].desc_index, u32_ty); kit_cg_data_int(cg, (uint64_t)descs[k].slot_offset, u32_ty); + kit_cg_data_int(cg, 0, u32_ty); if (ptr_align > 4) kit_cg_data_align(cg, ptr_align); } kit_cg_data_end(cg); } - (void)types_sym; +} + +/* Emit the runtime layout symbols read by kit_wasm_get_runtime_layout: + * + * __kit_wasm_instance_size uint64 + * __kit_wasm_nmemories uint32 + * __kit_wasm_memory_layouts KitWasmMemoryLayout[] + * + * The memory-layout array intentionally contains only fixed-width integers, so + * the host reader can consume it without target pointer-size ambiguity. */ +static void wasm_cg_emit_runtime_layout_metadata(KitCompiler* c, KitCg* cg, + KitCgBuiltinTypes b, + const WasmModule* m, + const WasmCgRuntime* rt) { + KitCgTypeId u8_ty = b.id[KIT_CG_BUILTIN_I8]; + KitCgTypeId u32_ty = b.id[KIT_CG_BUILTIN_I32]; + KitCgTypeId u64_ty = b.id[KIT_CG_BUILTIN_I64]; + KitCgDecl decl; + KitCgDataDefAttrs data_attrs; + KitCgSym instance_size_sym; + KitCgSym nmemories_sym; + uint64_t instance_size = kit_cg_type_size(c, rt->instance_ty); + + memset(&decl, 0, sizeof decl); + decl.kind = KIT_CG_DECL_OBJECT; + decl.linkage_name = kit_cg_c_linkage_name( + c, kit_sym_intern(c, KIT_SLICE_LIT("__kit_wasm_instance_size"))); + decl.display_name = decl.linkage_name; + decl.type = u64_ty; + decl.sym.bind = KIT_SB_GLOBAL; + decl.as.object.flags = KIT_CG_OBJ_READONLY; + instance_size_sym = kit_cg_decl(cg, decl); + if (!instance_size_sym) + wasm_error(c, wasm_loc(0, 0), + "wasm: failed to declare __kit_wasm_instance_size"); + memset(&data_attrs, 0, sizeof data_attrs); + data_attrs.flags = KIT_CG_DATADEF_READONLY; + data_attrs.align = 8; + kit_cg_data_begin(cg, instance_size_sym, data_attrs); + kit_cg_data_int(cg, instance_size, u64_ty); + kit_cg_data_end(cg); + + memset(&decl, 0, sizeof decl); + decl.kind = KIT_CG_DECL_OBJECT; + decl.linkage_name = kit_cg_c_linkage_name( + c, kit_sym_intern(c, KIT_SLICE_LIT("__kit_wasm_nmemories"))); + decl.display_name = decl.linkage_name; + decl.type = u32_ty; + decl.sym.bind = KIT_SB_GLOBAL; + decl.as.object.flags = KIT_CG_OBJ_READONLY; + nmemories_sym = kit_cg_decl(cg, decl); + if (!nmemories_sym) + wasm_error(c, wasm_loc(0, 0), + "wasm: failed to declare __kit_wasm_nmemories"); + memset(&data_attrs, 0, sizeof data_attrs); + data_attrs.flags = KIT_CG_DATADEF_READONLY; + data_attrs.align = 4; + kit_cg_data_begin(cg, nmemories_sym, data_attrs); + kit_cg_data_int(cg, (uint64_t)m->nmemories, u32_ty); + kit_cg_data_end(cg); + + if (m->nmemories) { + const uint64_t layout_size = sizeof(KitWasmMemoryLayout); + KitCgTypeId layouts_array_ty = + kit_cg_type_array(c, u8_ty, layout_size * m->nmemories); + KitCgSym layouts_sym; + memset(&decl, 0, sizeof decl); + decl.kind = KIT_CG_DECL_OBJECT; + decl.linkage_name = kit_cg_c_linkage_name( + c, kit_sym_intern(c, KIT_SLICE_LIT("__kit_wasm_memory_layouts"))); + decl.display_name = decl.linkage_name; + decl.type = layouts_array_ty; + decl.sym.bind = KIT_SB_GLOBAL; + decl.as.object.flags = KIT_CG_OBJ_READONLY; + layouts_sym = kit_cg_decl(cg, decl); + if (!layouts_sym) + wasm_error(c, wasm_loc(0, 0), + "wasm: failed to declare __kit_wasm_memory_layouts"); + memset(&data_attrs, 0, sizeof data_attrs); + data_attrs.flags = KIT_CG_DATADEF_READONLY; + data_attrs.align = 8; + kit_cg_data_begin(cg, layouts_sym, data_attrs); + for (uint32_t i = 0; i < m->nmemories; ++i) { + const WasmMemory* mem = &m->memories[i]; + uint64_t max_pages = mem->has_max ? mem->max_pages : mem->min_pages; + uint32_t flags = + (mem->shared ? KIT_WASM_MEMORY_SHARED : 0u) | + (mem->is64 ? KIT_WASM_MEMORY_64 : 0u); + kit_cg_data_align(cg, 8); + kit_cg_data_int(cg, rt->memory_offset[i], u64_ty); + kit_cg_data_int(cg, mem->min_pages, u64_ty); + kit_cg_data_int(cg, max_pages, u64_ty); + kit_cg_data_int(cg, flags, u32_ty); + kit_cg_data_int(cg, 0, u32_ty); + } + kit_cg_data_end(cg); + } } /* Bounds-check that addr + n <= size. addr/n/size are all treated as i64 @@ -1609,6 +1977,51 @@ static void wasm_cg_emit_table_copy_loop( kit_cg_label_place(cg, done); } +static void wasm_cg_cache_funcref_entry(KitCompiler* c, KitCg* cg, + KitCgBuiltinTypes b, + const WasmCgRuntime* rt, + KitCgLocal ref_local, + KitCgLocal fn_local, + KitCgLocal typeidx_local, + KitCgMemAccess ref_mem, + KitCgMemAccess i32_mem) { + KitCgLabel is_null = kit_cg_label_new(cg); + KitCgLabel done = kit_cg_label_new(cg); + kit_cg_push_local(cg, ref_local); + kit_cg_load(cg, ref_mem); + kit_cg_push_null(cg, rt->void_ptr_ty); + kit_cg_int_cmp(cg, KIT_CG_INT_EQ); + kit_cg_branch_true(cg, is_null); + + kit_cg_push_local(cg, fn_local); + kit_cg_push_local(cg, ref_local); + kit_cg_load(cg, ref_mem); + kit_cg_bitcast(cg, kit_cg_type_ptr(c, rt->table_entry_ty, 0)); + kit_cg_deref(cg, 0); + kit_cg_field(cg, rt->table_entry_fn_field); + kit_cg_load(cg, ref_mem); + kit_cg_store(cg, ref_mem); + + kit_cg_push_local(cg, typeidx_local); + kit_cg_push_local(cg, ref_local); + kit_cg_load(cg, ref_mem); + kit_cg_bitcast(cg, kit_cg_type_ptr(c, rt->table_entry_ty, 0)); + kit_cg_deref(cg, 0); + kit_cg_field(cg, rt->table_entry_typeidx_field); + kit_cg_load(cg, i32_mem); + kit_cg_store(cg, i32_mem); + kit_cg_jump(cg, done); + + kit_cg_label_place(cg, is_null); + kit_cg_push_local(cg, fn_local); + kit_cg_push_null(cg, rt->void_ptr_ty); + kit_cg_store(cg, ref_mem); + kit_cg_push_local(cg, typeidx_local); + kit_cg_push_int(cg, 0, b.id[KIT_CG_BUILTIN_I32]); + kit_cg_store(cg, i32_mem); + kit_cg_label_place(cg, done); +} + void wasm_emit_cg(KitCompiler* c, const KitCodeOptions* code_opts, KitObjBuilder* out, const WasmModule* m) { KitCg* cg = NULL; @@ -1943,10 +2356,12 @@ void wasm_emit_cg(KitCompiler* c, const KitCodeOptions* code_opts, kit_cg_ret(cg); kit_cg_func_end(cg); } - /* Host-import resolution metadata: emit __kit_wasm_nimports always, and - * __kit_wasm_imports / __kit_wasm_types when the module declares at - * least one function import. Read by kit_wasm_bind_host_imports. */ + /* Host-import resolution metadata: emit __kit_wasm_nimports always, plus + * import descriptors and kind-specific type arrays when the module declares + * imports. Function import signatures are deduplicated in __kit_wasm_types. + * Read by kit_wasm_bind_host_imports. */ wasm_cg_emit_host_import_metadata(c, cg, b, m, &rt, arena); + wasm_cg_emit_runtime_layout_metadata(c, cg, b, m, &rt); for (i = 0; i < m->nfuncs; ++i) { const WasmFunc* f = &m->funcs[i]; typedef struct WasmCgControl { @@ -2750,27 +3165,14 @@ void wasm_emit_cg(KitCompiler* c, const KitCodeOptions* code_opts, kit_cg_push_local(cg, val_l); kit_cg_swap(cg); kit_cg_store(cg, fn_mem); - /* Cache *val (a KitWasmTableEntry) into fn/typeidx locals so the - * fill loop writes the correct pair into each new slot. */ + /* Cache val into fn/typeidx locals so the fill loop writes the + * correct pair into each new slot. A null ref grows with null slots. + */ KitCgLocal val_fn_l = kit_cg_local(cg, rt.void_ptr_ty, attrs); KitCgLocal val_typeidx_l = kit_cg_local(cg, b.id[KIT_CG_BUILTIN_I32], attrs); - kit_cg_push_local(cg, val_fn_l); - kit_cg_push_local(cg, val_l); - kit_cg_load(cg, fn_mem); - kit_cg_bitcast(cg, kit_cg_type_ptr(c, rt.table_entry_ty, 0)); - kit_cg_deref(cg, 0); - kit_cg_field(cg, rt.table_entry_fn_field); - kit_cg_load(cg, fn_mem); - kit_cg_store(cg, fn_mem); - kit_cg_push_local(cg, val_typeidx_l); - kit_cg_push_local(cg, val_l); - kit_cg_load(cg, fn_mem); - kit_cg_bitcast(cg, kit_cg_type_ptr(c, rt.table_entry_ty, 0)); - kit_cg_deref(cg, 0); - kit_cg_field(cg, rt.table_entry_typeidx_field); - kit_cg_load(cg, i32_mem); - kit_cg_store(cg, i32_mem); + wasm_cg_cache_funcref_entry(c, cg, b, &rt, val_l, val_fn_l, + val_typeidx_l, fn_mem, i32_mem); kit_cg_push_local(cg, old_len_l); wasm_cg_push_table_lvalue(cg, &rt, instance_local, tableidx); kit_cg_field(cg, rt.table_len_field); @@ -2889,25 +3291,10 @@ void wasm_emit_cg(KitCompiler* c, const KitCodeOptions* code_opts, kit_cg_push_local(cg, dst_idx_l); kit_cg_swap(cg); kit_cg_store(cg, i32_mem); - /* Cache *val into (val_fn, val_typeidx). val points at a - * KitWasmTableEntry which has fn (void*) at offset 0 and - * typeidx (i32) at offset sizeof(void*). */ - kit_cg_push_local(cg, val_fn_l); - kit_cg_push_local(cg, val_l); - kit_cg_load(cg, fn_mem); - kit_cg_bitcast(cg, kit_cg_type_ptr(c, rt.table_entry_ty, 0)); - kit_cg_deref(cg, 0); - kit_cg_field(cg, rt.table_entry_fn_field); - kit_cg_load(cg, fn_mem); - kit_cg_store(cg, fn_mem); - kit_cg_push_local(cg, val_typeidx_l); - kit_cg_push_local(cg, val_l); - kit_cg_load(cg, fn_mem); - kit_cg_bitcast(cg, kit_cg_type_ptr(c, rt.table_entry_ty, 0)); - kit_cg_deref(cg, 0); - kit_cg_field(cg, rt.table_entry_typeidx_field); - kit_cg_load(cg, i32_mem); - kit_cg_store(cg, i32_mem); + /* Cache val into (val_fn, val_typeidx). Null refs fill with null + * entries and are checked later by call_indirect/call_ref. */ + wasm_cg_cache_funcref_entry(c, cg, b, &rt, val_l, val_fn_l, + val_typeidx_l, fn_mem, i32_mem); /* Bounds check: dst_idx + n <= len. */ kit_cg_push_local(cg, len_l); wasm_cg_push_table_lvalue(cg, &rt, instance_local, tableidx); diff --git a/lang/wasm/host_imports.c b/lang/wasm/host_imports.c @@ -84,6 +84,31 @@ static int host_imports_build_type(const KitWasmTypeDesc* src, * than silently truncating. */ #define KIT_WASM_BIND_MAX_VALTYPES 32u +KIT_API KitStatus kit_wasm_get_runtime_layout(KitJit* jit, + KitWasmRuntimeLayout* out) { + const uint64_t* instance_size; + const uint32_t* nmemories; + const KitWasmMemoryLayout* memories = NULL; + KitWasmRuntimeLayout z = {0}; + if (!jit || !out) return KIT_INVALID; + instance_size = (const uint64_t*)kit_jit_lookup( + jit, KIT_SLICE_LIT("__kit_wasm_instance_size")); + nmemories = (const uint32_t*)kit_jit_lookup( + jit, KIT_SLICE_LIT("__kit_wasm_nmemories")); + if (!instance_size && !nmemories) return KIT_NOT_FOUND; + if (!instance_size || !nmemories) return KIT_MALFORMED; + if (*nmemories) { + memories = (const KitWasmMemoryLayout*)kit_jit_lookup( + jit, KIT_SLICE_LIT("__kit_wasm_memory_layouts")); + if (!memories) return KIT_MALFORMED; + } + z.instance_size = *instance_size; + z.memories = memories; + z.nmemories = *nmemories; + *out = z; + return KIT_OK; +} + KIT_API KitStatus kit_wasm_bind_host_imports(KitCompiler* compiler, KitJit* jit, KitWasmInstance* inst, const KitWasmHostImport* imports, @@ -91,10 +116,19 @@ KIT_API KitStatus kit_wasm_bind_host_imports(KitCompiler* compiler, KitJit* jit, KitWasmResolveFn resolve, void* user) { const uint32_t* nimports_meta; + const uint32_t* nfunc_import_types_meta = NULL; const KitWasmImportDesc* import_descs; - const KitWasmTypeDesc* type_descs; + const KitWasmTypeDesc* type_descs = NULL; + const KitWasmMemoryImportDesc* memory_descs = NULL; + const KitWasmTableImportDesc* table_descs = NULL; + const KitWasmGlobalImportDesc* global_descs = NULL; uint32_t n; uint32_t i; + uint32_t nfunc_descs = 0; + uint32_t nmemory_descs = 0; + uint32_t ntable_descs = 0; + uint32_t nglobal_descs = 0; + uint32_t nfunc_import_types = 0; (void)compiler; if (!jit || !inst) return KIT_INVALID; @@ -109,9 +143,36 @@ KIT_API KitStatus kit_wasm_bind_host_imports(KitCompiler* compiler, KitJit* jit, if (n == 0) return KIT_OK; import_descs = (const KitWasmImportDesc*)kit_jit_lookup( jit, KIT_SLICE_LIT("__kit_wasm_imports")); - type_descs = (const KitWasmTypeDesc*)kit_jit_lookup( - jit, KIT_SLICE_LIT("__kit_wasm_types")); - if (!import_descs || !type_descs) return KIT_MALFORMED; + if (!import_descs) return KIT_MALFORMED; + + for (i = 0; i < n; ++i) { + switch (import_descs[i].kind) { + case KIT_WASM_IMPORT_FUNC: + nfunc_descs++; + break; + case KIT_WASM_IMPORT_MEMORY: + nmemory_descs++; + break; + case KIT_WASM_IMPORT_TABLE: + ntable_descs++; + break; + case KIT_WASM_IMPORT_GLOBAL: + nglobal_descs++; + break; + default: + return KIT_MALFORMED; + } + } + if (nfunc_descs) { + nfunc_import_types_meta = (const uint32_t*)kit_jit_lookup( + jit, KIT_SLICE_LIT("__kit_wasm_nfunc_import_types")); + type_descs = (const KitWasmTypeDesc*)kit_jit_lookup( + jit, KIT_SLICE_LIT("__kit_wasm_types")); + if (!nfunc_import_types_meta || !type_descs) return KIT_MALFORMED; + nfunc_import_types = *nfunc_import_types_meta; + if (nfunc_import_types == 0 || nfunc_import_types > nfunc_descs) + return KIT_MALFORMED; + } for (i = 0; i < n; ++i) { const KitWasmImportDesc* d = &import_descs[i]; @@ -119,8 +180,36 @@ KIT_API KitStatus kit_wasm_bind_host_imports(KitCompiler* compiler, KitJit* jit, KitWasmValType pbuf[KIT_WASM_BIND_MAX_VALTYPES]; KitWasmValType rbuf[KIT_WASM_BIND_MAX_VALTYPES]; KitWasmImportType type; + switch (d->kind) { + case KIT_WASM_IMPORT_FUNC: + if (d->desc_index >= nfunc_import_types) return KIT_MALFORMED; + break; + case KIT_WASM_IMPORT_MEMORY: + if (d->desc_index >= nmemory_descs) return KIT_MALFORMED; + if (!memory_descs) + memory_descs = (const KitWasmMemoryImportDesc*)kit_jit_lookup( + jit, KIT_SLICE_LIT("__kit_wasm_memory_import_types")); + if (!memory_descs) return KIT_MALFORMED; + return KIT_UNSUPPORTED; + case KIT_WASM_IMPORT_TABLE: + if (d->desc_index >= ntable_descs) return KIT_MALFORMED; + if (!table_descs) + table_descs = (const KitWasmTableImportDesc*)kit_jit_lookup( + jit, KIT_SLICE_LIT("__kit_wasm_table_import_types")); + if (!table_descs) return KIT_MALFORMED; + return KIT_UNSUPPORTED; + case KIT_WASM_IMPORT_GLOBAL: + if (d->desc_index >= nglobal_descs) return KIT_MALFORMED; + if (!global_descs) + global_descs = (const KitWasmGlobalImportDesc*)kit_jit_lookup( + jit, KIT_SLICE_LIT("__kit_wasm_global_import_types")); + if (!global_descs) return KIT_MALFORMED; + return KIT_UNSUPPORTED; + default: + return KIT_MALFORMED; + } /* Static table first. */ - for (size_t k = 0; k < nimports; ++k) { + for (size_t k = 0; imports && k < nimports; ++k) { if (host_imports_streq(imports[k].module, d->module) && host_imports_streq(imports[k].field, d->field)) { fn = imports[k].func; @@ -129,13 +218,14 @@ KIT_API KitStatus kit_wasm_bind_host_imports(KitCompiler* compiler, KitJit* jit, } /* Resolver fallback. */ if (!fn && resolve) { - const KitWasmTypeDesc* td = &type_descs[d->typeidx]; + const KitWasmTypeDesc* td; + td = &type_descs[d->desc_index]; if (!host_imports_build_type(td, pbuf, KIT_WASM_BIND_MAX_VALTYPES, rbuf, KIT_WASM_BIND_MAX_VALTYPES, &type)) return KIT_MALFORMED; fn = resolve(user, d->module, d->field, &type); } - if (!fn) continue; /* Unresolved: leave slot null; runtime traps on call. */ + if (!fn) return KIT_NOT_FOUND; /* Slot is a void* at byte offset slot_offset inside the instance struct. * The KitWasmFuncImport record is { void* fn; } so the offset of the * field is also the offset of the fn pointer. */ diff --git a/lang/wasm/runtime_abi.h b/lang/wasm/runtime_abi.h @@ -10,10 +10,40 @@ typedef struct KitWasmMemory { uint32_t flags; } KitWasmMemory; +#ifndef KIT_WASM_MEMORY_FLAG_TYPES +#define KIT_WASM_MEMORY_FLAG_TYPES enum { KIT_WASM_MEMORY_SHARED = 1u << 0, KIT_WASM_MEMORY_64 = 1u << 1, }; +#endif + +#ifndef KIT_WASM_IMPORT_KIND_TYPES +#define KIT_WASM_IMPORT_KIND_TYPES +typedef enum KitWasmImportKind { + KIT_WASM_IMPORT_FUNC = 0, + KIT_WASM_IMPORT_TABLE = 1, + KIT_WASM_IMPORT_MEMORY = 2, + KIT_WASM_IMPORT_GLOBAL = 3, +} KitWasmImportKind; +#endif + +#ifndef KIT_WASM_RUNTIME_LAYOUT_TYPES +#define KIT_WASM_RUNTIME_LAYOUT_TYPES +typedef struct KitWasmMemoryLayout { + uint64_t offset; + uint64_t min_pages; + uint64_t max_pages; + uint32_t flags; + uint32_t reserved; +} KitWasmMemoryLayout; + +typedef struct KitWasmRuntimeLayout { + uint64_t instance_size; + const KitWasmMemoryLayout* memories; + uint32_t nmemories; +} KitWasmRuntimeLayout; +#endif typedef struct KitWasmFuncImport { void* fn; @@ -62,19 +92,25 @@ typedef void (*KitWasmInitFn)(KitWasmInstance*); /* ---- Host-import metadata (subagent C) ---- * - * Each module that the wasm frontend lowers emits three readonly symbols - * alongside __kit_wasm_init so a runtime can resolve imports by - * (module, field) without needing the source WasmModule struct: + * Each module that the wasm frontend lowers emits readonly symbols alongside + * __kit_wasm_init so a runtime can resolve imports by (module, field) without + * needing the source WasmModule struct: * * __kit_wasm_imports : KitWasmImportDesc[] (one per declared - * function import; if - * the module has none, - * the symbol is absent) + * import; if the module has + * none, the symbol is + * absent) * __kit_wasm_nimports : uint32_t (array length) - * __kit_wasm_types : KitWasmTypeDesc[] (every WasmFuncType - * referenced by an - * import; absent when - * nimports == 0) + * __kit_wasm_nfunc_import_types : uint32_t (unique function import + * signature count) + * __kit_wasm_types : KitWasmTypeDesc[] (unique WasmFuncType + * signatures referenced by + * imported functions; + * absent when no functions + * are imported) + * __kit_wasm_memory_import_types : KitWasmMemoryImportDesc[] + * __kit_wasm_table_import_types : KitWasmTableImportDesc[] + * __kit_wasm_global_import_types : KitWasmGlobalImportDesc[] * * The wire format below is the C ABI the JIT/AOT image exposes. Param/result * bytes use the raw wasm WasmValType encoding (I32=0x7f, I64=0x7e, F32=0x7d, @@ -82,11 +118,12 @@ typedef void (*KitWasmInitFn)(KitWasmInstance*); * the public KitWasmValType enum when invoking host resolvers. */ typedef struct KitWasmImportDesc { - const char* module; /* image-owned, NUL-terminated */ - const char* field; /* image-owned, NUL-terminated */ - uint32_t typeidx; /* index into __kit_wasm_types */ - uint32_t slot_offset; /* byte offset of the void* slot inside the - KitWasmInstance for this import */ + const char* module; /* image-owned, NUL-terminated */ + const char* field; /* image-owned, NUL-terminated */ + uint32_t kind; /* KitWasmImportKind */ + uint32_t desc_index; /* index into the kind-specific descriptor array */ + uint32_t slot_offset; /* byte offset of the instance slot for this import */ + uint32_t reserved; } KitWasmImportDesc; typedef struct KitWasmTypeDesc { @@ -96,4 +133,23 @@ typedef struct KitWasmTypeDesc { uint32_t nresults; } KitWasmTypeDesc; +typedef struct KitWasmMemoryImportDesc { + uint64_t min_pages; + uint64_t max_pages; + uint32_t flags; /* KIT_WASM_MEMORY_* */ + uint32_t has_max; /* nonzero when the import declared an explicit maximum */ +} KitWasmMemoryImportDesc; + +typedef struct KitWasmTableImportDesc { + uint32_t elem_type; /* raw WasmValType byte */ + uint32_t min; + uint32_t max; + uint32_t has_max; /* nonzero when the import declared an explicit maximum */ +} KitWasmTableImportDesc; + +typedef struct KitWasmGlobalImportDesc { + uint32_t type; /* raw WasmValType byte */ + uint32_t mutable_; +} KitWasmGlobalImportDesc; + #endif diff --git a/src/wasm/decode.c b/src/wasm/decode.c @@ -1054,7 +1054,6 @@ void wasm_decode_binary(KitCompiler* c, const KitSlice* input, } if (id == 1) { uint32_t i, count = bin_uleb(&r); - if (count > 64u) wasm_error(c, wasm_loc(0, 0), "wasm: too many types"); for (i = 0; i < count; ++i) { WasmFuncType* t = wasm_add_type(c, out); uint32_t j, nparam, nresult; @@ -1135,8 +1134,6 @@ void wasm_decode_binary(KitCompiler* c, const KitSlice* input, } } else if (id == 3) { uint32_t i, count = bin_uleb(&r); - if (count > 64u) - wasm_error(c, wasm_loc(0, 0), "wasm: too many functions"); for (i = 0; i < count; ++i) { uint32_t typeidx = bin_uleb(&r); WasmFunc* f; diff --git a/src/wasm/validate.c b/src/wasm/validate.c @@ -28,6 +28,29 @@ static WasmValType wasm_global_init_type(const WasmInsn* in) { } } +static uint64_t wasm_memory_initial_bytes(KitCompiler* c, + const WasmMemory* mem) { + if (mem->min_pages > UINT64_MAX / 65536u) + wasm_error(c, wasm_loc(0, 0), "wasm: memory minimum overflows"); + return mem->min_pages * 65536u; +} + +static uint32_t wasm_mem_align_log2(uint32_t width) { + uint32_t lg = 0; + while (width > 1u) { + width >>= 1u; + lg++; + } + return lg; +} + +static void wasm_validate_memarg(KitCompiler* c, const WasmInsn* in, + const char* what) { + uint32_t max_align = wasm_mem_align_log2(wasm_mem_width(in->kind)); + if (in->align > max_align) + wasm_error(c, in->loc, "wasm: bad %s alignment", what); +} + static void wasm_stack_push(KitCompiler* c, WasmValStack* s, WasmValType vt) { if (s->depth >= 256u) wasm_error(c, s->loc, "wasm: operand stack too deep"); s->vals[s->depth++] = vt; @@ -138,14 +161,37 @@ void wasm_validate(WasmModule* m, KitCompiler* c) { wasm_error(c, wasm_loc(0, 0), "wasm: start function must have no params or results"); } + for (i = 0; i < m->ndata; ++i) { + const WasmDataSegment* d = &m->data[i]; + uint64_t memory_bytes; + uint64_t offset; + if (d->mode != WASM_SEG_ACTIVE) continue; + if (d->memidx >= m->nmemories) + wasm_error(c, wasm_loc(0, 0), "wasm: data memory index out of range"); + if (d->offset < 0) + wasm_error(c, wasm_loc(0, 0), "wasm: bad data offset"); + memory_bytes = wasm_memory_initial_bytes(c, &m->memories[d->memidx]); + offset = (uint64_t)d->offset; + if (offset > memory_bytes || d->nbytes > memory_bytes - offset) + wasm_error(c, wasm_loc(0, 0), "wasm: data segment out of range"); + } for (i = 0; i < m->nelems; ++i) { - uint32_t table_min; - if (m->elems[i].tableidx >= m->ntables) - wasm_error(c, wasm_loc(0, 0), "wasm: element table index out of range"); - table_min = m->tables[m->elems[i].tableidx].min; - if (m->elems[i].offset < 0 || - (uint64_t)m->elems[i].offset + m->elems[i].nfuncs > table_min) - wasm_error(c, wasm_loc(0, 0), "wasm: element segment out of range"); + if (m->elems[i].elem_type != WASM_VAL_FUNCREF) + wasm_error(c, wasm_loc(0, 0), + "wasm: unsupported element segment type"); + if (m->elems[i].mode == WASM_SEG_ACTIVE) { + uint32_t table_min; + uint64_t offset; + if (m->elems[i].tableidx >= m->ntables) + wasm_error(c, wasm_loc(0, 0), + "wasm: element table index out of range"); + table_min = m->tables[m->elems[i].tableidx].min; + if (m->elems[i].offset < 0) + wasm_error(c, wasm_loc(0, 0), "wasm: element segment out of range"); + offset = (uint64_t)m->elems[i].offset; + if (offset > table_min || m->elems[i].nfuncs > table_min - offset) + wasm_error(c, wasm_loc(0, 0), "wasm: element segment out of range"); + } for (j = 0; j < m->elems[i].nfuncs; ++j) if (m->elems[i].funcs[j] >= m->nfuncs) wasm_error(c, wasm_loc(0, 0), @@ -479,8 +525,7 @@ void wasm_validate_func(KitCompiler* c, WasmModule* m, WasmFunc* f) { if (!m->memories[in->memidx].shared) wasm_error(c, wasm_loc(0, 0), "wasm: atomic load requires shared memory"); - if (in->align && in->align > wasm_mem_width(in->kind)) - wasm_error(c, wasm_loc(0, 0), "wasm: bad atomic alignment"); + wasm_validate_memarg(c, in, "atomic"); wasm_stack_pop( c, &stack, control, ncontrol, m->memories[in->memidx].is64 ? WASM_VAL_I64 : WASM_VAL_I32, @@ -501,8 +546,7 @@ void wasm_validate_func(KitCompiler* c, WasmModule* m, WasmFunc* f) { if (!m->memories[in->memidx].shared) wasm_error(c, wasm_loc(0, 0), "wasm: atomic store requires shared memory"); - if (in->align && in->align > wasm_mem_width(in->kind)) - wasm_error(c, wasm_loc(0, 0), "wasm: bad atomic alignment"); + wasm_validate_memarg(c, in, "atomic"); wasm_stack_pop(c, &stack, control, ncontrol, wasm_atomic_value_type(in->kind), "atomic store"); wasm_stack_pop( @@ -529,6 +573,7 @@ void wasm_validate_func(KitCompiler* c, WasmModule* m, WasmFunc* f) { if (!m->memories[in->memidx].shared) wasm_error(c, wasm_loc(0, 0), "wasm: atomic rmw requires shared memory"); + wasm_validate_memarg(c, in, "atomic"); wasm_stack_pop(c, &stack, control, ncontrol, wasm_atomic_value_type(in->kind), "atomic rmw"); wasm_stack_pop( @@ -547,6 +592,7 @@ void wasm_validate_func(KitCompiler* c, WasmModule* m, WasmFunc* f) { if (!m->memories[in->memidx].shared) wasm_error(c, wasm_loc(0, 0), "wasm: atomic cmpxchg requires shared memory"); + wasm_validate_memarg(c, in, "atomic"); wasm_stack_pop(c, &stack, control, ncontrol, wasm_atomic_value_type(in->kind), "atomic cmpxchg"); wasm_stack_pop(c, &stack, control, ncontrol, @@ -566,6 +612,7 @@ void wasm_validate_func(KitCompiler* c, WasmModule* m, WasmFunc* f) { if (!m->memories[in->memidx].shared) wasm_error(c, wasm_loc(0, 0), "wasm: atomic wait requires shared memory"); + wasm_validate_memarg(c, in, "atomic"); wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I64, "atomic wait timeout"); wasm_stack_pop(c, &stack, control, ncontrol, @@ -585,6 +632,7 @@ void wasm_validate_func(KitCompiler* c, WasmModule* m, WasmFunc* f) { if (!m->memories[in->memidx].shared) wasm_error(c, wasm_loc(0, 0), "wasm: atomic notify requires shared memory"); + wasm_validate_memarg(c, in, "atomic"); wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, "atomic notify count"); wasm_stack_pop( @@ -609,6 +657,7 @@ void wasm_validate_func(KitCompiler* c, WasmModule* m, WasmFunc* f) { case WASM_INSN_I64_LOAD32_U: if (in->memidx >= m->nmemories) wasm_error(c, wasm_loc(0, 0), "wasm: load without memory"); + wasm_validate_memarg(c, in, "load"); wasm_stack_pop( c, &stack, control, ncontrol, m->memories[in->memidx].is64 ? WASM_VAL_I64 : WASM_VAL_I32, @@ -626,6 +675,7 @@ void wasm_validate_func(KitCompiler* c, WasmModule* m, WasmFunc* f) { case WASM_INSN_I64_STORE32: if (in->memidx >= m->nmemories) wasm_error(c, wasm_loc(0, 0), "wasm: store without memory"); + wasm_validate_memarg(c, in, "store"); wasm_stack_pop(c, &stack, control, ncontrol, wasm_store_value_type(in->kind), "store"); wasm_stack_pop( @@ -675,6 +725,9 @@ void wasm_validate_func(KitCompiler* c, WasmModule* m, WasmFunc* f) { if (in->imm < 0 || (uint64_t)in->imm >= m->ndata) wasm_error(c, wasm_loc(0, 0), "wasm: memory.init data index out of range"); + if (m->data[in->imm].mode != WASM_SEG_PASSIVE) + wasm_error(c, wasm_loc(0, 0), + "wasm: memory.init requires passive data segment"); if (in->memidx >= m->nmemories) wasm_error(c, wasm_loc(0, 0), "wasm: memory.init memory index out of range"); @@ -692,6 +745,9 @@ void wasm_validate_func(KitCompiler* c, WasmModule* m, WasmFunc* f) { if (in->imm < 0 || (uint64_t)in->imm >= m->ndata) wasm_error(c, wasm_loc(0, 0), "wasm: data.drop data index out of range"); + if (m->data[in->imm].mode != WASM_SEG_PASSIVE) + wasm_error(c, wasm_loc(0, 0), + "wasm: data.drop requires passive data segment"); break; case WASM_INSN_TABLE_COPY: wasm_require_feature(c, m, WASM_FEATURE_BULK_MEMORY, "bulk memory", @@ -713,6 +769,9 @@ void wasm_validate_func(KitCompiler* c, WasmModule* m, WasmFunc* f) { if (in->imm < 0 || (uint64_t)in->imm >= m->nelems) wasm_error(c, wasm_loc(0, 0), "wasm: table.init elem index out of range"); + if (m->elems[in->imm].mode != WASM_SEG_PASSIVE) + wasm_error(c, wasm_loc(0, 0), + "wasm: table.init requires passive element segment"); if (in->aux_idx >= m->ntables) wasm_error(c, wasm_loc(0, 0), "wasm: table.init table index out of range"); @@ -729,6 +788,9 @@ void wasm_validate_func(KitCompiler* c, WasmModule* m, WasmFunc* f) { if (in->imm < 0 || (uint64_t)in->imm >= m->nelems) wasm_error(c, wasm_loc(0, 0), "wasm: elem.drop elem index out of range"); + if (m->elems[in->imm].mode != WASM_SEG_PASSIVE) + wasm_error(c, wasm_loc(0, 0), + "wasm: elem.drop requires passive element segment"); break; case WASM_INSN_TABLE_SIZE: wasm_require_feature(c, m, WASM_FEATURE_BULK_MEMORY, "bulk memory", diff --git a/src/wasm/wat.c b/src/wasm/wat.c @@ -492,6 +492,19 @@ static int wat_parse_u32_atom(WasmTok t, uint32_t* out) { return 1; } +static int wat_parse_mem_align_log2(WasmTok t, uint32_t* out) { + uint32_t bytes; + uint32_t lg = 0; + if (!wat_parse_u32_atom(t, &bytes) || bytes == 0u) return 0; + if (bytes & (bytes - 1u)) return 0; + while (bytes > 1u) { + bytes >>= 1u; + lg++; + } + *out = lg; + return 1; +} + static int wat_parse_u64_atom(WasmTok t, uint64_t* out) { uint64_t v = 0; size_t i = 0; @@ -542,7 +555,7 @@ static void wat_parse_mem_attrs(WatParser* p, uint32_t* align, uint64_t* offset, val = p->tok; val.p += 6; val.len -= 6; - if (!wat_parse_u32_atom(val, align)) + if (!wat_parse_mem_align_log2(val, align)) wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), "wasm wat: bad memory alignment"); wat_next(p); diff --git a/test/link/harness/jit_runner.c b/test/link/harness/jit_runner.c @@ -423,9 +423,157 @@ typedef struct WasmRunnerMemoryPrefix { uint32_t flags; } WasmRunnerMemoryPrefix; +typedef struct WasmRunnerInstanceAlloc { + uint8_t* instance; + uint8_t** memories; + uint32_t nmemories; +} WasmRunnerInstanceAlloc; + typedef void (*WasmRunnerInitFn)(void*); typedef int (*WasmRunnerMainFn)(void*); +#define WASM_RUNNER_MAX_INSTANCE_BYTES (64ull * 1024ull * 1024ull) +#define WASM_RUNNER_MAX_TOTAL_MEMORY_BYTES (1024ull * 1024ull * 1024ull) + +static int wasm_runner_u64_to_size(uint64_t n, size_t* out, + const char* what) { + if (n > (uint64_t)SIZE_MAX) { + fprintf(stderr, "jit-runner: wasm %s exceeds host size_t\n", what); + return 1; + } + *out = (size_t)n; + return 0; +} + +static int wasm_runner_page_bytes(uint64_t pages, uint64_t* out) { + if (pages > UINT64_MAX / (uint64_t)KIT_WASM_PAGE_SIZE) { + fprintf(stderr, "jit-runner: wasm memory size overflows\n"); + return 1; + } + *out = pages * (uint64_t)KIT_WASM_PAGE_SIZE; + return 0; +} + +static void wasm_runner_free_instance(WasmRunnerInstanceAlloc* alloc) { + if (!alloc) return; + if (alloc->memories) { + for (uint32_t i = 0; i < alloc->nmemories; ++i) free(alloc->memories[i]); + free(alloc->memories); + } + free(alloc->instance); + memset(alloc, 0, sizeof *alloc); +} + +static int wasm_runner_make_instance(KitJit* jit, + WasmRunnerInstanceAlloc* out) { + KitWasmRuntimeLayout layout; + WasmRunnerInstanceAlloc alloc; + uint64_t instance_bytes; + uint64_t total_memory_bytes = 0; + size_t instance_size; + KitStatus st; + memset(&alloc, 0, sizeof alloc); + if (!out) return 1; + memset(out, 0, sizeof *out); + st = kit_wasm_get_runtime_layout(jit, &layout); + if (st != KIT_OK) { + fprintf(stderr, "jit-runner: wasm runtime layout metadata %s\n", + st == KIT_NOT_FOUND ? "missing" : "malformed"); + return 1; + } + instance_bytes = layout.instance_size ? layout.instance_size : 1u; + if (instance_bytes > WASM_RUNNER_MAX_INSTANCE_BYTES) { + fprintf(stderr, "jit-runner: wasm instance too large: %llu bytes\n", + (unsigned long long)instance_bytes); + return 1; + } + if (wasm_runner_u64_to_size(instance_bytes, &instance_size, "instance") != 0) + return 1; + alloc.instance = (uint8_t*)calloc(1, instance_size); + alloc.nmemories = layout.nmemories; + if (!alloc.instance) { + fprintf(stderr, "jit-runner: out of memory\n"); + return 1; + } + if (layout.nmemories) { + if ((uint64_t)layout.nmemories > (uint64_t)SIZE_MAX / sizeof(uint8_t*)) { + fprintf(stderr, "jit-runner: wasm memory count too large\n"); + wasm_runner_free_instance(&alloc); + return 1; + } + alloc.memories = (uint8_t**)calloc(layout.nmemories, sizeof(uint8_t*)); + if (!alloc.memories) { + fprintf(stderr, "jit-runner: out of memory\n"); + wasm_runner_free_instance(&alloc); + return 1; + } + } + for (uint32_t i = 0; i < layout.nmemories; ++i) { + const KitWasmMemoryLayout* ml = &layout.memories[i]; + uint64_t mem_bytes; + size_t mem_size; + WasmRunnerMemoryPrefix* rec; + if (ml->max_pages < ml->min_pages) { + fprintf(stderr, "jit-runner: wasm memory maximum below minimum\n"); + wasm_runner_free_instance(&alloc); + return 1; + } + if (ml->offset > instance_bytes || + instance_bytes - ml->offset < sizeof(WasmRunnerMemoryPrefix)) { + fprintf(stderr, "jit-runner: wasm memory record outside instance\n"); + wasm_runner_free_instance(&alloc); + return 1; + } + if (wasm_runner_page_bytes(ml->max_pages, &mem_bytes) != 0) { + wasm_runner_free_instance(&alloc); + return 1; + } + if (mem_bytes > WASM_RUNNER_MAX_TOTAL_MEMORY_BYTES || + total_memory_bytes > WASM_RUNNER_MAX_TOTAL_MEMORY_BYTES - mem_bytes) { + fprintf(stderr, + "jit-runner: wasm linear memory reservation exceeds %llu bytes\n", + (unsigned long long)WASM_RUNNER_MAX_TOTAL_MEMORY_BYTES); + wasm_runner_free_instance(&alloc); + return 1; + } + total_memory_bytes += mem_bytes; + if (wasm_runner_u64_to_size(mem_bytes, &mem_size, "linear memory") != 0) { + wasm_runner_free_instance(&alloc); + return 1; + } + if (mem_size) { + alloc.memories[i] = (uint8_t*)calloc(1, mem_size); + if (!alloc.memories[i]) { + fprintf(stderr, "jit-runner: out of memory\n"); + wasm_runner_free_instance(&alloc); + return 1; + } + } + rec = (WasmRunnerMemoryPrefix*)(alloc.instance + ml->offset); + rec->data = alloc.memories[i]; + } + *out = alloc; + return 0; +} + +static int wasm_runner_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_runner_resolve_import(void* user, const char* module, + const char* field, + const KitWasmImportType* type) { + (void)user; + if (!module || !field) return NULL; + if (strcmp(module, "env") == 0 && strcmp(field, "host_add") == 0 && + wasm_runner_host_add_type(type)) + return (void*)(uintptr_t)test_host_add; + return NULL; +} + int main(int argc, char** argv) { { long ps = sysconf(_SC_PAGESIZE); @@ -657,48 +805,33 @@ int main(int argc, char** argv) { int result; if (wasm_init && entry) { - uint8_t* instance = calloc(1, 64u * 1024u); - uint8_t* memory = calloc(1, 16u * 1024u * 1024u); - if (!instance || !memory) { - free(memory); - free(instance); + WasmRunnerInstanceAlloc alloc; + if (wasm_runner_make_instance(jit, &alloc) != 0) { kit_jit_run_dtors(jit); kit_jit_free(jit); free_compiler_target(c, kt); return 1; } - for (uint32_t i = 0; i < 8u; ++i) - ((WasmRunnerMemoryPrefix*)instance)[i].data = - memory + i * (16u * 1024u * 1024u / 8u); - /* Provide a small set of canned host imports for test fixtures. The - * binder is a no-op if the module doesn't declare these names. */ - extern int32_t test_host_add(KitWasmInstance*, int32_t, int32_t); - static const KitWasmHostImport test_imports[] = { - {"env", "host_add", (void*)0}, - }; - /* Patch the function pointer at runtime so the static initializer - * stays simple — clang under -Werror dislikes function-to-void* casts - * in const initializers. */ - KitWasmHostImport - runtime_imports[sizeof(test_imports) / sizeof(test_imports[0])]; - for (size_t k = 0; k < sizeof(test_imports) / sizeof(test_imports[0]); ++k) - runtime_imports[k] = test_imports[k]; - runtime_imports[0].func = (void*)(uintptr_t)test_host_add; - if (kit_wasm_bind_host_imports( - c, jit, (KitWasmInstance*)instance, runtime_imports, - sizeof(runtime_imports) / sizeof(runtime_imports[0]), NULL, - NULL) != KIT_OK) { - free(memory); - free(instance); - kit_jit_run_dtors(jit); - kit_jit_free(jit); - free_compiler_target(c, kt); - return 1; + { + KitStatus bind_st = kit_wasm_bind_host_imports( + c, jit, (KitWasmInstance*)alloc.instance, NULL, 0, + wasm_runner_resolve_import, NULL); + if (bind_st != KIT_OK) { + fprintf(stderr, "jit-runner: wasm host import %s\n", + bind_st == KIT_NOT_FOUND + ? "unresolved" + : (bind_st == KIT_UNSUPPORTED ? "kind unsupported" + : "bind failed")); + wasm_runner_free_instance(&alloc); + kit_jit_run_dtors(jit); + kit_jit_free(jit); + free_compiler_target(c, kt); + return 1; + } } - ((WasmRunnerInitFn)wasm_init)(instance); - result = ((WasmRunnerMainFn)entry)(instance); - free(memory); - free(instance); + ((WasmRunnerInitFn)wasm_init)(alloc.instance); + result = ((WasmRunnerMainFn)entry)(alloc.instance); + wasm_runner_free_instance(&alloc); } else if (fn) { #if defined(__aarch64__) || defined(__arm64__) result = call_with_aarch64_tls(fn, tls_block); diff --git a/test/wasm/cases/host_import_dedup_sig.expect b/test/wasm/cases/host_import_dedup_sig.expect @@ -0,0 +1 @@ +14 diff --git a/test/wasm/cases/host_import_dedup_sig.wat b/test/wasm/cases/host_import_dedup_sig.wat @@ -0,0 +1,12 @@ +(module + (type $binop (func (param i32 i32) (result i32))) + (import "env" "host_add" (func $add_a (type $binop))) + (import "env" "host_add" (func $add_b (type $binop))) + (func (export "test_main") (result i32) + i32.const 2 + i32.const 3 + call $add_a + i32.const 4 + i32.const 5 + call $add_b + i32.add)) diff --git a/test/wasm/cases/import_slot_unused.wat b/test/wasm/cases/import_slot_unused.wat @@ -1,4 +1,4 @@ (module - (import "host" "unused" (func $unused (param i32) (result i32))) + (import "env" "host_add" (func $unused (param i32 i32) (result i32))) (func (export "test_main") (result i32) i32.const 42)) diff --git a/test/wasm/cases/many_funcs_70.expect b/test/wasm/cases/many_funcs_70.expect @@ -0,0 +1 @@ +42 diff --git a/test/wasm/cases/many_funcs_70.wat b/test/wasm/cases/many_funcs_70.wat @@ -0,0 +1,75 @@ +(module + ;; Exercises module-indexed codegen layout tables past the old fixed + ;; 64-entry offset arrays. + (func $f0 (result i32) i32.const 0) + (func $f1 (result i32) i32.const 1) + (func $f2 (result i32) i32.const 2) + (func $f3 (result i32) i32.const 3) + (func $f4 (result i32) i32.const 4) + (func $f5 (result i32) i32.const 5) + (func $f6 (result i32) i32.const 6) + (func $f7 (result i32) i32.const 7) + (func $f8 (result i32) i32.const 8) + (func $f9 (result i32) i32.const 9) + (func $f10 (result i32) i32.const 10) + (func $f11 (result i32) i32.const 11) + (func $f12 (result i32) i32.const 12) + (func $f13 (result i32) i32.const 13) + (func $f14 (result i32) i32.const 14) + (func $f15 (result i32) i32.const 15) + (func $f16 (result i32) i32.const 16) + (func $f17 (result i32) i32.const 17) + (func $f18 (result i32) i32.const 18) + (func $f19 (result i32) i32.const 19) + (func $f20 (result i32) i32.const 20) + (func $f21 (result i32) i32.const 21) + (func $f22 (result i32) i32.const 22) + (func $f23 (result i32) i32.const 23) + (func $f24 (result i32) i32.const 24) + (func $f25 (result i32) i32.const 25) + (func $f26 (result i32) i32.const 26) + (func $f27 (result i32) i32.const 27) + (func $f28 (result i32) i32.const 28) + (func $f29 (result i32) i32.const 29) + (func $f30 (result i32) i32.const 30) + (func $f31 (result i32) i32.const 31) + (func $f32 (result i32) i32.const 32) + (func $f33 (result i32) i32.const 33) + (func $f34 (result i32) i32.const 34) + (func $f35 (result i32) i32.const 35) + (func $f36 (result i32) i32.const 36) + (func $f37 (result i32) i32.const 37) + (func $f38 (result i32) i32.const 38) + (func $f39 (result i32) i32.const 39) + (func $f40 (result i32) i32.const 40) + (func $f41 (result i32) i32.const 41) + (func $f42 (result i32) i32.const 42) + (func $f43 (result i32) i32.const 43) + (func $f44 (result i32) i32.const 44) + (func $f45 (result i32) i32.const 45) + (func $f46 (result i32) i32.const 46) + (func $f47 (result i32) i32.const 47) + (func $f48 (result i32) i32.const 48) + (func $f49 (result i32) i32.const 49) + (func $f50 (result i32) i32.const 50) + (func $f51 (result i32) i32.const 51) + (func $f52 (result i32) i32.const 52) + (func $f53 (result i32) i32.const 53) + (func $f54 (result i32) i32.const 54) + (func $f55 (result i32) i32.const 55) + (func $f56 (result i32) i32.const 56) + (func $f57 (result i32) i32.const 57) + (func $f58 (result i32) i32.const 58) + (func $f59 (result i32) i32.const 59) + (func $f60 (result i32) i32.const 60) + (func $f61 (result i32) i32.const 61) + (func $f62 (result i32) i32.const 62) + (func $f63 (result i32) i32.const 63) + (func $f64 (result i32) i32.const 64) + (func $f65 (result i32) i32.const 65) + (func $f66 (result i32) i32.const 66) + (func $f67 (result i32) i32.const 67) + (func $f68 (result i32) i32.const 68) + (func $f69 (result i32) i32.const 42) + (func (export "test_main") (result i32) + call $f69)) diff --git a/test/wasm/cases/memory_grow_large.expect b/test/wasm/cases/memory_grow_large.expect @@ -0,0 +1 @@ +42 diff --git a/test/wasm/cases/memory_grow_large.wat b/test/wasm/cases/memory_grow_large.wat @@ -0,0 +1,14 @@ +(module + ;; Old kit run allocated a fixed 16 MiB memory slab, then let memory.grow + ;; increase the logical page count past that backing store. A valid access in + ;; the newly-grown range must remain in-bounds for the host allocation. + (memory 1 300) + (func (export "test_main") (result i32) + i32.const 299 + memory.grow + drop + i32.const 19595264 + i32.const 42 + i32.store + i32.const 19595264 + i32.load)) diff --git a/test/wasm/cases/passive_elem_no_table.expect b/test/wasm/cases/passive_elem_no_table.expect @@ -0,0 +1 @@ +42 diff --git a/test/wasm/cases/passive_elem_no_table.wat b/test/wasm/cases/passive_elem_no_table.wat @@ -0,0 +1,6 @@ +(module + (func $target (result i32) + i32.const 7) + (elem $unused func $target) + (func (export "test_main") (result i32) + i32.const 42)) diff --git a/test/wasm/cases/table_fill_null.expect b/test/wasm/cases/table_fill_null.expect @@ -0,0 +1 @@ +42 diff --git a/test/wasm/cases/table_fill_null.wat b/test/wasm/cases/table_fill_null.wat @@ -0,0 +1,8 @@ +(module + (table 1 4 funcref) + (func (export "test_main") (result i32) + i32.const 0 + ref.null func + i32.const 1 + table.fill + i32.const 42)) diff --git a/test/wasm/cases/table_grow_null.expect b/test/wasm/cases/table_grow_null.expect @@ -0,0 +1 @@ +42 diff --git a/test/wasm/cases/table_grow_null.wat b/test/wasm/cases/table_grow_null.wat @@ -0,0 +1,10 @@ +(module + (table 0 4 funcref) + (func (export "test_main") (result i32) + ref.null func + i32.const 2 + table.grow + drop + table.size + i32.const 40 + i32.add)) diff --git a/test/wasm/err/data_segment_oob.wat b/test/wasm/err/data_segment_oob.wat @@ -0,0 +1,3 @@ +(module + (memory 1) + (data (i32.const 65536) "\01")) diff --git a/test/wasm/err/memory_bad_align.wat b/test/wasm/err/memory_bad_align.wat @@ -0,0 +1,5 @@ +(module + (memory 1) + (func (export "test_main") (result i32) + i32.const 0 + i32.load align=8)) diff --git a/test/wasm/err/memory_init_active.wat b/test/wasm/err/memory_init_active.wat @@ -0,0 +1,8 @@ +(module + (memory 1) + (data $seg (i32.const 0) "\01") + (func (export "test_main") + i32.const 0 + i32.const 0 + i32.const 0 + memory.init $seg)) diff --git a/test/wasm/err/table_init_active.wat b/test/wasm/err/table_init_active.wat @@ -0,0 +1,11 @@ +(module + (type $ret_i32 (func (result i32))) + (func $target (type $ret_i32) + i32.const 42) + (table 1 funcref) + (elem $seg (i32.const 0) func $target) + (func (export "test_main") + i32.const 0 + i32.const 0 + i32.const 0 + table.init $seg)) diff --git a/test/wasm/harness/start_wasm.c b/test/wasm/harness/start_wasm.c @@ -6,10 +6,8 @@ * * Host-import binding (kit_wasm_bind_host_imports) is intentionally not * called here: this harness is freestanding and has no link to libkit. - * Modules executed through this path that declare imports leave the slots - * NULL; invoking such an import traps via the in-module null check. Real - * embedders should use the JIT runner or `kit run`, both of which - * resolve imports before calling __kit_wasm_init. + * It binds the small canned test import set below and rejects startup if a + * module declares anything else, matching Wasm instantiation semantics. */ extern void __kit_wasm_init(void*); @@ -20,8 +18,10 @@ extern int test_main(void*); typedef struct WasmStartImportDesc { const char* module; const char* field; - unsigned int typeidx; + unsigned int kind; + unsigned int desc_index; unsigned int slot_offset; + unsigned int reserved; } WasmStartImportDesc; extern const WasmStartImportDesc __kit_wasm_imports[] __attribute__((weak)); extern const unsigned int __kit_wasm_nimports __attribute__((weak)); @@ -41,16 +41,20 @@ static int start_streq(const char* a, const char* b) { return *a == 0 && *b == 0; } -static void start_bind_canned_imports(void* instance) { - if (!(&__kit_wasm_nimports)) return; +static int start_bind_canned_imports(void* instance) { + if (!(&__kit_wasm_nimports)) return 1; unsigned int n = __kit_wasm_nimports; for (unsigned int i = 0; i < n; ++i) { const WasmStartImportDesc* d = &__kit_wasm_imports[i]; - if (start_streq(d->module, "env") && start_streq(d->field, "host_add")) { + if (d->kind == 0 && start_streq(d->module, "env") && + start_streq(d->field, "host_add")) { *(void**)((unsigned char*)instance + d->slot_offset) = (void*)(unsigned long)start_test_host_add; + } else { + return 0; } } + return 1; } typedef struct WasmStartMemoryPrefix { @@ -60,9 +64,26 @@ typedef struct WasmStartMemoryPrefix { unsigned int flags; } WasmStartMemoryPrefix; +typedef struct WasmStartMemoryLayout { + unsigned long long offset; + unsigned long long min_pages; + unsigned long long max_pages; + unsigned int flags; + unsigned int reserved; +} WasmStartMemoryLayout; + +extern const unsigned long long __kit_wasm_instance_size + __attribute__((weak)); +extern const unsigned int __kit_wasm_nmemories __attribute__((weak)); +extern const WasmStartMemoryLayout __kit_wasm_memory_layouts[] + __attribute__((weak)); + #define WASM_START_MEMORY_PREFIX_COUNT 8u #define WASM_START_INSTANCE_SIZE (64u * 1024u) #define WASM_START_MEMORY_SIZE (16u * 1024u * 1024u) +#define WASM_START_PAGE_SIZE (64ull * 1024ull) +#define WASM_START_MAX_INSTANCE_SIZE (64ull * 1024ull * 1024ull) +#define WASM_START_MAX_TOTAL_MEMORY_SIZE (1024ull * 1024ull * 1024ull) #define WASM_START_PROT_READ 1 #define WASM_START_PROT_WRITE 2 #define WASM_START_MAP_PRIVATE 2 @@ -154,18 +175,67 @@ static void* start_mmap(unsigned long size) { #endif } +static int start_pages_to_bytes(unsigned long long pages, + unsigned long long* out) { + unsigned long long max = ~0ull; + if (pages > max / WASM_START_PAGE_SIZE) return 0; + *out = pages * WASM_START_PAGE_SIZE; + return 1; +} + +static int start_setup_dynamic_instance(void** instance_out) { + void* instance; + unsigned long long instance_size; + unsigned long long total_memory = 0; + if (!(&__kit_wasm_instance_size) || !(&__kit_wasm_nmemories)) + return 0; + instance_size = __kit_wasm_instance_size ? __kit_wasm_instance_size : 1ull; + if (instance_size > WASM_START_MAX_INSTANCE_SIZE) return -1; + if (__kit_wasm_nmemories && !(&__kit_wasm_memory_layouts)) return -1; + instance = start_mmap((unsigned long)instance_size); + if (!instance) return -1; + for (unsigned int i = 0; i < __kit_wasm_nmemories; ++i) { + const WasmStartMemoryLayout* ml = &__kit_wasm_memory_layouts[i]; + WasmStartMemoryPrefix* rec; + unsigned long long bytes; + void* memory = (void*)0; + if (ml->max_pages < ml->min_pages) return -1; + if (ml->offset > instance_size || + instance_size - ml->offset < sizeof(WasmStartMemoryPrefix)) + return -1; + if (!start_pages_to_bytes(ml->max_pages, &bytes)) return -1; + if (bytes > WASM_START_MAX_TOTAL_MEMORY_SIZE || + total_memory > WASM_START_MAX_TOTAL_MEMORY_SIZE - bytes) + return -1; + total_memory += bytes; + if (bytes) { + memory = start_mmap((unsigned long)bytes); + if (!memory) return -1; + } + rec = (WasmStartMemoryPrefix*)((unsigned char*)instance + ml->offset); + rec->data = (unsigned char*)memory; + } + *instance_out = instance; + return 1; +} + #if defined(__x86_64__) __attribute__((force_align_arg_pointer)) #endif void _start(void) { - void* instance = start_mmap(WASM_START_INSTANCE_SIZE); - void* memory = start_mmap(WASM_START_MEMORY_SIZE); - if (!instance || !memory) do_exit(1); - for (unsigned int i = 0; i < WASM_START_MEMORY_PREFIX_COUNT; ++i) - ((WasmStartMemoryPrefix*)instance)[i].data = - (unsigned char*)memory + - i * (WASM_START_MEMORY_SIZE / WASM_START_MEMORY_PREFIX_COUNT); - start_bind_canned_imports(instance); + void* instance = (void*)0; + int dyn = start_setup_dynamic_instance(&instance); + if (dyn < 0) do_exit(1); + if (dyn == 0) { + void* memory = start_mmap(WASM_START_MEMORY_SIZE); + instance = start_mmap(WASM_START_INSTANCE_SIZE); + if (!instance || !memory) do_exit(1); + for (unsigned int i = 0; i < WASM_START_MEMORY_PREFIX_COUNT; ++i) + ((WasmStartMemoryPrefix*)instance)[i].data = + (unsigned char*)memory + + i * (WASM_START_MEMORY_SIZE / WASM_START_MEMORY_PREFIX_COUNT); + } + if (!start_bind_canned_imports(instance)) do_exit(1); __kit_wasm_init(instance); do_exit(test_main(instance)); } diff --git a/test/wasm/run.sh b/test/wasm/run.sh @@ -130,19 +130,25 @@ static int32_t test_host_add(void *inst, int32_t a, int32_t b) { typedef struct { const char *module; const char *field; - unsigned int typeidx; + unsigned int kind; + unsigned int desc_index; unsigned int slot_offset; + unsigned int reserved; } WasmImportDesc; __attribute__((weak)) const unsigned int __kit_wasm_nimports = 0; -__attribute__((weak)) const WasmImportDesc __kit_wasm_imports[1] = {{0, 0, 0, 0}}; +__attribute__((weak)) const WasmImportDesc __kit_wasm_imports[1] = {{0, 0, 0, 0, 0, 0}}; -static void bind_canned_imports(void *instance) { +static int bind_canned_imports(void *instance) { for (unsigned int i = 0; i < __kit_wasm_nimports; ++i) { const WasmImportDesc *d = &__kit_wasm_imports[i]; - if (strcmp(d->module, "env") == 0 && strcmp(d->field, "host_add") == 0) + if (d->kind == 0 && strcmp(d->module, "env") == 0 && + strcmp(d->field, "host_add") == 0) *(void **)((unsigned char *)instance + d->slot_offset) = (void *)(uintptr_t)test_host_add; + else + return 0; } + return 1; } typedef struct { unsigned char *data; unsigned long long pages; @@ -159,7 +165,7 @@ int main(void) { memory + i * (WASM_START_MEMORY_SIZE / WASM_START_MEMORY_PREFIX_COUNT); /* After the memory prefix (which can overlap import slots for memory-less * modules), before init (which consumes the bound slots). */ - bind_canned_imports(instance); + if (!bind_canned_imports(instance)) return 1; __kit_wasm_init(instance); return (int)test_main(instance); } diff --git a/test/wasm/trap/host_import_unbound.wat b/test/wasm/trap/host_import_unbound.wat @@ -1,6 +1,6 @@ (module - ;; Declares an import the test harness does not bind. Calling it must - ;; trap cleanly (not segfault) at invocation time. + ;; Declares an import the test harness does not bind. Instantiation must + ;; fail cleanly (not run with a dangling or NULL import slot). (import "env" "host_unbound" (func $host_unbound (param i32) (result i32))) (func (export "test_main") (result i32) i32.const 0 diff --git a/test/wasm/trap/import_global_unbound.wat b/test/wasm/trap/import_global_unbound.wat @@ -0,0 +1,4 @@ +(module + (import "env" "global" (global i32)) + (func (export "test_main") (result i32) + global.get 0)) diff --git a/test/wasm/trap/import_memory_unbound.wat b/test/wasm/trap/import_memory_unbound.wat @@ -0,0 +1,4 @@ +(module + (import "env" "memory" (memory 1)) + (func (export "test_main") (result i32) + i32.const 0)) diff --git a/test/wasm/trap/import_table_unbound.wat b/test/wasm/trap/import_table_unbound.wat @@ -0,0 +1,4 @@ +(module + (import "env" "table" (table 1 funcref)) + (func (export "test_main") (result i32) + i32.const 0)) diff --git a/test/wasm/trap/memory_offset_overflow.wat b/test/wasm/trap/memory_offset_overflow.wat @@ -0,0 +1,6 @@ +(module + (memory 1) + (data (i32.const 0) "\2a\00\00\00") + (func (export "test_main") (result i32) + i32.const 1 + i32.load offset=18446744073709551615))