kit

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

commit 4fa8f8f076759c67fa71fb42951134ffa8759dbe
parent f8bbbafdb01a72b2f2fa2377f10546a2f65e817a
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Mon, 25 May 2026 13:44:44 -0700

wasm: add backend and target feature support

Diffstat:
MMakefile | 9+++++++--
Mdoc/WASM.md | 551+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mdriver/run.c | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++----
Minclude/cfree/cg.h | 50++++++++++++++++++++++++++++++++++++++++++--------
Minclude/cfree/config.h | 2++
Ainclude/cfree/wasm.h | 135+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mlang/c/decl/decl.c | 2++
Mlang/c/decl/decl.h | 5+++++
Mlang/c/decl/decl_attrs.c | 14++++++++++++++
Mlang/c/parse/attr.h | 6++++++
Mlang/c/parse/parse_stmt.c | 25+++++++++++++++----------
Mlang/c/parse/parse_type.c | 2++
Mlang/toy/compile.c | 21++++++++++++---------
Mlang/toy/expr.c | 2++
Mlang/toy/parser_core.c | 65+++++++++++++++++++++++++++++++++++++++++++++--------------------
Mlang/wasm/cg.c | 1700++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Dlang/wasm/decode.c | 1185-------------------------------------------------------------------------------
Dlang/wasm/encode.c | 778-------------------------------------------------------------------------------
Alang/wasm/host_imports.c | 145+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dlang/wasm/insn.c | 410-------------------------------------------------------------------------------
Dlang/wasm/module.c | 291------------------------------------------------------------------------------
Mlang/wasm/runtime_abi.h | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dlang/wasm/validate.c | 669-------------------------------------------------------------------------------
Mlang/wasm/wasm.c | 11++++++-----
Mlang/wasm/wasm.h | 4++--
Dlang/wasm/wasm_internal.h | 416-------------------------------------------------------------------------------
Dlang/wasm/wat.c | 2498-------------------------------------------------------------------------------
Mmk/config.mk | 2++
Msrc/abi/abi_internal.h | 1+
Msrc/abi/registry.c | 4+++-
Msrc/api/stubs.c | 9---------
Asrc/api/wasm.c | 36++++++++++++++++++++++++++++++++++++
Msrc/arch/arch.h | 15+++++++++++++++
Msrc/arch/registry.c | 6++++++
Asrc/arch/wasm/abi.c | 146+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/arch/wasm/arch.c | 44++++++++++++++++++++++++++++++++++++++++++++
Asrc/arch/wasm/emit.c | 4447+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/arch/wasm/internal.h | 395+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/arch/wasm/structure.c | 653+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/arch/wasm/target.c | 293+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/arch/wasm/wasm_imports.c | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/arch/wasm/wasm_imports.h | 23+++++++++++++++++++++++
Msrc/cg/asm.c | 7+++++++
Msrc/cg/control.c | 233+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/cg/data.c | 8++++++++
Msrc/cg/session.c | 12++++++++++++
Msrc/core/core.h | 8++++++++
Msrc/obj/obj.c | 41+++++++++++++++++++++++++++++++++++++++++
Msrc/obj/obj.h | 16++++++++++++++++
Asrc/obj/wasm_emit.c | 24++++++++++++++++++++++++
Msrc/opt/opt.c | 10++++++++++
Asrc/wasm/decode.c | 1345+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/wasm/encode.c | 855+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/wasm/insn.c | 425+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/wasm/module.c | 446+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/wasm/validate.c | 831+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/wasm/wasm.h | 545+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/wasm/wat.c | 2730+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/link/harness/jit_runner.c | 33+++++++++++++++++++++++++++++++++
Mtest/test.mk | 20++++++++++++++++++++
Atest/toy/cases/129_record_by_value.expected | 1+
Atest/toy/cases/129_record_by_value.toy | 15+++++++++++++++
Atest/toy/cases/130_record_sret_return.expected | 1+
Atest/toy/cases/130_record_sret_return.toy | 17+++++++++++++++++
Atest/toy/cases/131_memcpy_uses_bulk.expected | 1+
Atest/toy/cases/131_memcpy_uses_bulk.toy | 16++++++++++++++++
Atest/toy/cases/132_intrinsic_bit_and_overflow.expected | 1+
Atest/toy/cases/132_intrinsic_bit_and_overflow.toy | 26++++++++++++++++++++++++++
Atest/toy/cases/133_varargs_mixed_types.expected | 1+
Atest/toy/cases/133_varargs_mixed_types.toy | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/toy/cases/26_tail_live_pressure.wasm.skip | 1+
Atest/toy/cases/35_musttail_variadic.wasm.skip | 1+
Atest/toy/cases/38_tail_stack_fallback.wasm.skip | 9+++++++++
Mtest/toy/cases/47_target_arch_switch.toy | 3+++
Mtest/toy/cases/60_atomic_queries.toy | 2+-
Mtest/toy/run.sh | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Atest/wasm-target/asm_branch_escape.c | 13+++++++++++++
Atest/wasm-target/asm_popcnt.c | 13+++++++++++++
Atest/wasm-target/check_asm.sh | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/wasm-target/check_imports.sh | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/wasm-target/check_memory_copy.sh | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/wasm-target/check_memory_export.sh | 40++++++++++++++++++++++++++++++++++++++++
Atest/wasm-target/host_import_sig_mismatch.wat | 10++++++++++
Atest/wasm-target/import_decl.c | 20++++++++++++++++++++
Atest/wasm-target/import_decl_attribute.c | 13+++++++++++++
Atest/wasm-target/run.sh | 30++++++++++++++++++++++++++++++
Atest/wasm/cases/bulk_data_drop.expect | 1+
Atest/wasm/cases/bulk_data_drop.wat | 18++++++++++++++++++
Atest/wasm/cases/bulk_memory_copy.expect | 1+
Atest/wasm/cases/bulk_memory_copy.wat | 12++++++++++++
Atest/wasm/cases/bulk_memory_fill.expect | 1+
Atest/wasm/cases/bulk_memory_fill.wat | 11+++++++++++
Atest/wasm/cases/bulk_memory_init.expect | 1+
Atest/wasm/cases/bulk_memory_init.wat | 12++++++++++++
Atest/wasm/cases/bulk_table_copy.expect | 1+
Atest/wasm/cases/bulk_table_copy.wat | 16++++++++++++++++
Atest/wasm/cases/bulk_table_fill.expect | 1+
Atest/wasm/cases/bulk_table_fill.wat | 14++++++++++++++
Atest/wasm/cases/bulk_table_grow.expect | 1+
Atest/wasm/cases/bulk_table_grow.wat | 17+++++++++++++++++
Atest/wasm/cases/bulk_table_init.expect | 1+
Atest/wasm/cases/bulk_table_init.wat | 16++++++++++++++++
Atest/wasm/cases/host_import_add.expect | 1+
Atest/wasm/cases/host_import_add.wat | 9+++++++++
Atest/wasm/cases/trunc_sat.expect | 1+
Atest/wasm/cases/trunc_sat.wat | 21+++++++++++++++++++++
Mtest/wasm/harness/start_wasm.c | 44++++++++++++++++++++++++++++++++++++++++++++
Mtest/wasm/run.sh | 24++++++++++++++++++++++++
Atest/wasm/trap/bulk_memory_oob.wat | 9+++++++++
Atest/wasm/trap/data_drop_then_init.wat | 11+++++++++++
Atest/wasm/trap/host_import_unbound.wat | 7+++++++
Atest/wasm/trap/table_oob.wat | 12++++++++++++
112 files changed, 17174 insertions(+), 6499 deletions(-)

diff --git a/Makefile b/Makefile @@ -82,6 +82,7 @@ LIB_SRCS_NONARCH = $(shell find src -name '*.c' \ -not -path 'src/arch/aa64/*' \ -not -path 'src/arch/x64/*' \ -not -path 'src/arch/rv64/*' \ + -not -path 'src/arch/wasm/*' \ -not -path 'src/arch/c_target/*' \ -not -path 'src/obj/elf/*' \ -not -path 'src/obj/macho/*' \ @@ -92,6 +93,7 @@ LIB_SRCS_NONARCH = $(shell find src -name '*.c' \ LIB_SRCS_ARCH_AA64 = $(shell find src/arch/aa64 -name '*.c' 2>/dev/null) LIB_SRCS_ARCH_X64 = $(shell find src/arch/x64 -name '*.c' 2>/dev/null) LIB_SRCS_ARCH_RV64 = $(shell find src/arch/rv64 -name '*.c' 2>/dev/null) +LIB_SRCS_ARCH_WASM = $(shell find src/arch/wasm -name '*.c' 2>/dev/null) LIB_SRCS_ARCH_C_TARGET = $(shell find src/arch/c_target -name '*.c' 2>/dev/null) ifneq ($(CFREE_OPT_ENABLED),1) LIB_SRCS_ARCH_AA64 := $(filter-out %/opt_coord.c,$(LIB_SRCS_ARCH_AA64)) @@ -126,6 +128,9 @@ endif ifeq ($(CFREE_ARCH_RV64_ENABLED),1) LIB_SRCS += $(LIB_SRCS_ARCH_RV64) endif +ifeq ($(CFREE_ARCH_WASM_ENABLED),1) +LIB_SRCS += $(LIB_SRCS_ARCH_WASM) +endif ifeq ($(CFREE_ARCH_C_TARGET_ENABLED),1) LIB_SRCS += $(LIB_SRCS_ARCH_C_TARGET) endif @@ -244,7 +249,7 @@ $(BUILD_DIR)/lib/%.o: src/%.c Makefile $(BUILD_CONFIG) # uses -Ilang so the frontend headers can be reached as "c/c.h" etc. $(BUILD_DIR)/lib/api/lang_registry.o: src/api/lang_registry.c Makefile $(BUILD_CONFIG) @mkdir -p $(dir $@) - $(CC) $(LIB_CFLAGS) -Ilang $(DEPFLAGS) -c $< -o $@ + $(CC) $(FREESTANDING_CFLAGS) $(LIB_VISIBILITY_CFLAGS) -Iinclude -Ilang -Isrc $(DEPFLAGS) -c $< -o $@ $(BUILD_DIR)/lang/cpp/%.o: lang/cpp/%.c Makefile $(BUILD_CONFIG) @mkdir -p $(dir $@) @@ -259,7 +264,7 @@ $(BUILD_DIR)/lang/c/%.o: lang/c/%.c Makefile $(BUILD_CONFIG) $(BUILD_DIR)/lang/wasm/%.o: lang/wasm/%.c Makefile $(BUILD_CONFIG) @mkdir -p $(dir $@) - $(CC) $(FREESTANDING_CFLAGS) $(LIB_VISIBILITY_CFLAGS) -Iinclude -Ilang/wasm $(DEPFLAGS) -c $< -o $@ + $(CC) $(FREESTANDING_CFLAGS) $(LIB_VISIBILITY_CFLAGS) -Iinclude -Isrc -Ilang/wasm $(DEPFLAGS) -c $< -o $@ $(BUILD_DIR)/lib/%.o: src/%.S Makefile $(BUILD_CONFIG) @mkdir -p $(dir $@) diff --git a/doc/WASM.md b/doc/WASM.md @@ -44,21 +44,24 @@ Wasm module. The plan fits the current architecture, but a few seams must be made explicit before implementation: -- `CFREE_ARCH_WASM` is a public enum value, but there is no registered +- [done] `CFREE_ARCH_WASM` is a public enum value, but there is no registered `ArchImpl` in `src/arch/registry.c`. `compiler_init` constructs `TargetABI` immediately through `abi_new`, so a usable Wasm compiler target needs `arch_impl_wasm.abi_vtable` before C/toy-to-Wasm compile tests can safely create a `CfreeCompiler`. -- `ObjBuilder` has `OBJ_EXT_WASM` as a tag, but no payload storage API. The - object metadata described below requires a small internal extension hook on - `ObjBuilder`; otherwise `WasmObjMeta associated with the builder` is only a - design phrase, not an implementable data path. -- The public compile path is already relocatable-object-first: + → `arch_impl_wasm` + `wasm32_vtable` live in `src/arch/wasm/{arch.c,abi.c}` + and are registered in `src/arch/registry.c`. +- [done] `ObjBuilder` has `OBJ_EXT_WASM` as a tag, but no payload storage API. + → `obj_ext_set/get/clear` (`src/obj/obj.{h,c}`); `ObjBuilder` owns the + payload's lifetime and releases via the registered free fn at `obj_free`. +- [done, single-TU] The public compile path is already relocatable-object-first: `cfree_compile_obj*` builds an `ObjBuilder`, finalizes it, then dispatches `emit_wasm` from `emit_object_bytes` when `target.obj == CFREE_OBJ_WASM`. - The Wasm target should use that path rather than adding a one-TU final - module shortcut. -- The public link path currently always builds a `Linker`, calls + → `src/obj/wasm_emit.c` reads the `WasmModule` attached under + `OBJ_EXT_WASM` and flushes via `wasm_encode`. v1 emits a final module + shape (no relocations needed for a single TU); the relocatable-object + shape with `linking`/`reloc.*` custom sections is still pending. +- [pending] The public link path currently always builds a `Linker`, calls `link_resolve`, then emits a native `LinkImage`. A Wasm final module should not use `LinkImage` segment layout. The least invasive fit is a `target.obj == CFREE_OBJ_WASM` branch in `cfree_link_exe` after @@ -66,15 +69,16 @@ before implementation: and emitter. This branch is not used for Wasm input JIT: Wasm input lowered to a native target should continue through the existing native `LinkImage` path. -- Driver source classification only recognizes `.c`, `.toy`, and `.s` today, - and `cfree_language_for_path` only infers those languages. Adding +- [done] Driver source classification only recognizes `.c`, `.toy`, and `.s` + today, and `cfree_language_for_path` only infers those languages. Adding `CFREE_LANG_WASM` also requires driver classification for `.wasm`/`.wat`, frontend registration in `driver_compiler_new`/`driver_pipeline_new`, and updates to dbg language switches that enumerate `CFREE_LANG_*`. -- `CfreeTarget` has no feature field. Wasm feature policy should therefore - live in internal Wasm decode/validation/link options for v1, not in the - public target struct. A later public feature API can be added once the - target has real users. + → Resolved by the earlier Wasm-input frontend work. +- [pending] `CfreeTarget` has no feature field. Wasm feature policy should + therefore live in internal Wasm decode/validation/link options for v1, not + in the public target struct. A later public feature API can be added once + the target has real users. ## References @@ -180,12 +184,21 @@ Do not add public Wasm feature knobs in phase 0/1. Internally, construct the accepted feature set from the target and the caller: ```c -typedef struct WasmFeatureSet { - uint64_t core; - uint64_t tool_conventions; +typedef enum WasmFeatureSet { + WASM_FEATURE_THREADS = 1u << 0, + WASM_FEATURE_TYPED_FUNC_REFS = 1u << 1, + WASM_FEATURE_TAIL_CALLS = 1u << 2, + WASM_FEATURE_MULTI_MEMORY = 1u << 3, + WASM_FEATURE_MEMORY64 = 1u << 4, + WASM_FEATURE_BULK_MEMORY = 1u << 5, + WASM_FEATURE_NONTRAPPING_FTOI = 1u << 6, } WasmFeatureSet; ``` +`BULK_MEMORY` and `NONTRAPPING_FTOI` share the `0xfc` opcode prefix; they +are decoded together but feature-gated independently so a module may opt +into one without the other. + If external embedders need to instantiate Wasm-input modules later, add that public runtime API with the frontend milestone. Until then, keep `CfreeWasmInstance`, `CfreeWasmMemory`, and `CfreeWasmTable` as runtime ABI @@ -365,8 +378,8 @@ Register `arch_impl_wasm` in `src/arch/registry.c` with: The Wasm `CGTarget` still implements the full vtable in `src/arch/arch.h`, but its implementation maps operands to Wasm value-stack entries, locals, and linear-memory addresses instead of hard registers. Unsupported hooks must -diagnose deliberately: inline asm, native TLS models, atomics, varargs, SIMD, -and irreducible control flow should fail before emitting malformed Wasm. +diagnose deliberately: inline asm, native TLS models, SIMD, and +irreducible control flow should fail before emitting malformed Wasm. ### Wasm Link API @@ -523,14 +536,17 @@ Implement BasicCABI for wasm32 first: direct, other structs/unions indirect. Arrays are indirect. - Use a mutable `__stack_pointer` global and a downward-growing linear stack for address-taken locals, spills that need memory, and aggregate temporaries. -- Defer varargs, `long double`, `i128`, atomics, broad compiler-rt helper - coverage, and full libc/runtime conventions. Diagnose those cases explicitly - in the first target milestone. +- Defer `long double`, `i128`, broad compiler-rt helper coverage, and full + libc/runtime conventions. Diagnose those cases explicitly in the first + target milestone. This makes v1 larger than scalar-only smoke tests but smaller than a full C runtime target. The initial ABI boundary is scalar plus indirect aggregates. -Varargs should later be caller-packed into a linear-memory buffer; the callee -walks that buffer through the existing `va_*` CG hooks. +Varargs are caller-packed into a linear-memory buffer of uniform 8-byte +slots; the callee receives a hidden trailing `i32` pointer to the buffer +and walks it through the existing `va_*` CG hooks. Atomics are lowered via +the wasm-threads opcodes (load/store/RMW/cmpxchg/fence; see Wasm Target +Backend below). The ABI classifier should consume neutral CG types, not C `Type*`. @@ -569,6 +585,42 @@ Add a Wasm static link path separate from ELF/Mach-O image layout: Do not route Wasm final output through `LinkImage` segment layout. A Wasm module is not a virtual-addressed native image. +### External runtime compatibility + +cfree-produced Wasm modules are intended to run on any standard Wasm +runtime (wasmtime, wasmer, the browser engines, Node) with no host-side +adaptation beyond providing whatever imports the module declares. The +backend follows a small set of conventions to make that work: + +- **Linear memory is exported under the conventional name `"memory"`.** + Browsers, wasmtime, wasmer, and Node all expect this name when they + reach into module memory from the host. The export is emitted next to + the function exports in `src/arch/wasm/emit.c`. +- **Functions are exported by user name.** No synthesized `_start`, + `_main`, or wrapper. A C `int main(void)` becomes a wasm function + named `main`; a toy program's `test_main` becomes a wasm function + named `test_main`. Hosts pick the entry by name (e.g. + `wasmtime run --invoke main module.wasm`). +- **`extern` C declarations become `(import "env" "<sym>" ...)`** + declarations in the produced module. Default policy matches + wasm-clang: module `"env"`, field `"<C symbol name>"`. The C frontend + accepts `__attribute__((import_module("M"), import_name("F")))` on + the `extern` declaration to override either name; the override flows + through to the produced import entry without further runtime + involvement. +- **No cfree-internal symbols in the produced module.** The internal + cfree-instance ABI (`CfreeWasmInstance*`, `__cfree_wasm_init`, trap + helpers) is a *frontend lowering* implementation detail and does not + leak into modules produced by the `wasm32-none` target. A host + loading a cfree-produced module sees only standard imports, exported + functions, and exported memory. + +Together with the bulk-memory changes above, this closes the gap to a +loadable-on-standard-hosts story. Round-trip the cfree-produced module +through cfree's own host-import binder (see "Host import resolver" +under "Wasm as Frontend") and the same module also runs under cfree's +runtime. + ## Wasm as Frontend ### Frontend Input Semantics @@ -690,9 +742,78 @@ width as the Wasm address type. The lowered address calculation remains i64, then the runtime checks that the current host allocation can represent the effective range before converting to a host pointer. +### Host import resolver + +When the frontend consumes a Wasm module that declares imports, the +embedder configures host-side bindings through a small public API on +`include/cfree.h`: + +```c +typedef struct CfreeWasmInstance CfreeWasmInstance; + +typedef struct CfreeWasmImportType { + const CfreeWasmValType *params; + uint32_t nparams; + const CfreeWasmValType *results; + uint32_t nresults; +} CfreeWasmImportType; + +/* Host function pointer. Called with the cfree-instance ABI: + * void (*)(CfreeWasmInstance*, [wasm params...]) -> result + * The explicit instance parameter is a feature, not a quirk: imports + * almost always want to read or write the module's linear memory, and + * that lives in the instance. */ +typedef struct CfreeWasmHostImport { + const char *module; + const char *field; + void *func; +} CfreeWasmHostImport; + +/* Optional dynamic resolver. Returns a function pointer with the + * cfree-instance ABI for the requested import, or NULL to leave it + * unbound (the call site traps on invocation). */ +typedef void *(*CfreeWasmResolveFn)(void *user, const char *module, + const char *field, + const CfreeWasmImportType *type); + +/* Configure host-side imports for the next `cfree_link_jit` / + * `cfree run` invocation. The static table is tried first; the + * resolver fills any misses. Either may be NULL. */ +void cfree_wasm_set_host_imports(CfreeCompiler *, + const CfreeWasmHostImport *imports, + size_t nimports, + CfreeWasmResolveFn resolve, void *user); +``` + +The wiring path on the runtime side is straightforward: + +- Codegen emits two side metadata symbols per lowered module: + `__cfree_wasm_imports` (a const array of `{const char* module; const + char* field; uint32_t typeidx; uint32_t slot_offset;}`) and + `__cfree_wasm_nimports` (a const u32). AOT and JIT outputs get the + same metadata. +- A runtime helper `cfree_wasm_bind_host_imports(instance, imports, + nimports, resolve, user)` walks `__cfree_wasm_imports`, looks up + each entry by `(module, field)` in the static table or via the + resolver, **validates the (params, results) shape against the + module's `WasmFuncType` table** (also emitted as static data), and + writes the function pointer into + `*(void**)((char*)instance + slot_offset)`. +- `driver/run.c`, `test/link/harness/jit_runner.c`, and + `test/wasm/harness/start_wasm.c` call the binder **before** + `__cfree_wasm_init`. With no imports configured the binder is a + no-op, so existing modules with empty import lists continue to work + unchanged. + +Signature validation produces a clean diagnostic naming the +`(module, field)` pair when the bound host function's declared type +disagrees with the module's import type. Unbound imports do not fail +at bind time — they fail with a runtime trap on the first call, +exactly like the existing trap-on-null-slot path. + ## Phasing -### Phase 0: Format Spine +### Phase 0: Format Spine — done Add `src/wasm` with binary decode helpers, section scanning, LEB128 encode/ decode, minimal module structs, the `ObjBuilder` extension-payload hook, and @@ -700,55 +821,183 @@ tests for tiny hand-authored modules. Acceptance: -- `cfree_detect_fmt` remains able to identify Wasm. -- parser rejects malformed section lengths and bad ordering. -- encoder can roundtrip a minimal module with type/function/export/code. -- `obj_ext_set/get/clear` can attach and release a dummy `OBJ_EXT_WASM` +- [x] `cfree_detect_fmt` remains able to identify Wasm. +- [x] parser rejects malformed section lengths and bad ordering. +- [x] encoder can roundtrip a minimal module with type/function/export/code. +- [x] `obj_ext_set/get/clear` can attach and release a dummy `OBJ_EXT_WASM` payload without affecting existing ELF/Mach-O object tests. +Note: the shared core lives under `lang/wasm/` today (`wasm_internal.h`, +`module.c`, `decode.c`, `encode.c`, `validate.c`, `insn.c`). The +`src/arch/wasm` and `src/obj/wasm_emit.c` TUs reach it through `-Ilang/wasm` +on the build line. Moving it under `src/wasm/` per the layout in the +"Proposed Layout" section remains a planned cleanup. + Targeted test: `make test-wasm-format`. -### Phase 1: Wasm Frontend to Native Object/JIT +### Phase 1: Wasm Frontend to Native Object/JIT — done Add `CFREE_LANG_WASM`, `.wasm`/`.wat` driver classification as source input, module validation, and native lowering through `CfreeCg`. Acceptance: -- simple Wasm modules compile to native objects for aa64/x64/rv64 targets. -- simple Wasm modules JIT and execute through the existing native +- [x] simple Wasm modules compile to native objects for aa64/x64/rv64 targets. +- [x] simple Wasm modules JIT and execute through the existing native `cfree_link_jit`/`LinkImage` path. -- bounds checks and traps are deterministic. -- imported scalar functions can be resolved through an explicit instance import - table. -- relocatable Wasm objects with a `linking` custom section are rejected as +- [x] bounds checks and traps are deterministic. +- [x] imported scalar functions can be resolved through an explicit instance + import table. +- [x] relocatable Wasm objects with a `linking` custom section are rejected as frontend input with a clear diagnostic. - -Targeted test: `make test-wasm-front`. - -### Phase 2: Minimal Wasm Relocatable Object Target +- [partial] Host imports are bound by name through the public + `cfree_wasm_set_host_imports` API (subagent C). The binder reads the + per-module `__cfree_wasm_imports` / `__cfree_wasm_nimports` metadata + symbols, looks up each `(module, field)` pair in the configured + static table or via the optional resolver, validates the signature + against the module's `WasmFuncType` table, and writes the function + pointer into the instance import slot. Unbound imports trap on first + call. +- [partial] Bulk memory and non-trapping float-to-int conversion are + parsed, validated, and lowered through the existing + `copy_bytes`/`set_bytes` CG ops with bounds-check prologues. + Passive-data and passive-elem tables hang off `CfreeWasmInstance`; + `data.drop`/`elem.drop` zero the length field so subsequent + `memory.init`/`table.init` traps cleanly. + +Targeted test: `make test-wasm-front` (291 fixtures, all green). + +### Phase 2: Minimal Wasm Final-Module Target — partial Add `src/arch/wasm` and enough `emit_wasm` support for `cfree cc -target -wasm32-none -c` to produce a valid tool-conventions relocatable object. Use -toy first where practical because it already exercises the public CG API -without C parser complexity, but keep the public compile output as a -relocatable object from the start. +wasm32-none -c` to produce a valid Wasm module. Use toy first where practical +because it already exercises the public CG API without C parser complexity. This phase must include `arch_impl_wasm` registration and a wasm32 ABI vtable; otherwise creating a Wasm-target `CfreeCompiler` reaches `abi_init` before the backend exists. -Acceptance: - -- selected toy/C scalar functions lower into valid Wasm code sections inside a - relocatable Wasm object. -- `read_wasm` can ingest the produced object back into an `ObjBuilder` without - losing the function body, symbol, type, or relocation metadata needed for a - later link. -- unsupported target features report precise diagnostics. - -Targeted test: compile selected toy cases to Wasm objects and validate their -object metadata structurally. +What's landed (v1 slice, single-TU final modules): + +- [x] `arch_impl_wasm` + wasm32 BasicCABI vtable for scalar params/results + and aggregate-by-indirect classification (`src/arch/wasm/{arch,abi}.c`). +- [x] Wasm `CGTarget` (`src/arch/wasm/{target,emit}.c`): records a per-function + WIR list, then linearizes to a `WasmFunc` body — locals, type entries, + exports, direct calls, scalar arithmetic, structured `block`/`loop`/`if` + via `SCOPE_BLOCK`/`SCOPE_LOOP`, `br`/`br_if` to scope-registered labels, + `return`, basic conversions. +- [x] `emit_wasm` reads the `WasmModule` attached under `OBJ_EXT_WASM` and + flushes via the existing `wasm_encode`. +- [x] Structured `if`/`if-else` lowering via `cfree_cg_block_begin` plus + rewritten `cfree_cg_if_begin/else/end` (nested SCOPE_BLOCKs with `break`), + removing the need for a CFG stackifier for the common case. Toy and C + parsers updated to use it. +- [x] CFG structurer for direct gotos (`src/arch/wasm/structure.c`'s + `wasm_structurize`). Runs in `linearize()` before WIR emission and + rewrites the WIR list so every reachable free label becomes the + break/continue of a synthetic SCOPE_BLOCK/SCOPE_LOOP. Unblocks C + `goto`, Toy's AND/OR short-circuit lowering, and dynamic-size memcpy + loops. Switch islands (frontend's + `JUMP dispatch / case bodies / LABEL dispatch / selector / SWITCH` + shape) are unrolled by the structurer's `unroll_switch_islands` pass + — it reorders the WIR so selector + SWITCH appear before the case + bodies, making case labels forward refs the structurer handles + uniformly. The old `try_linearize_switch_island` linear-time matcher + is removed. +- [x] End-to-end Toy → wasm → run roundtrip via `cfree run` (lang/wasm + frontend → native CG → JIT). See `make test-toy-wasm`. + +What's not yet done in this phase: + +- [x] Linear-memory + `__stack_pointer` global; address-taken locals; + `addr_of`; data/rodata sections lifted into a linear-memory + active + data segment. +- [x] Compact data layout: ObjBuilder SF_ALLOC sections are assigned + aligned bases from a 16-byte null guard upward (no more `sid * 1 MiB` + stride). Symbol-address resolution in function bodies is queued via + `WSymFixup` and patched once `wasm_materialize_data` knows every + section's final size. +- [x] Data-section relocations applied to the linear-memory image: + `R_ABS32` cross-symbol references (e.g. `*p = &x`) resolve to + `section_base[target_sec] + sym->value + addend`. `R_ABS64` and other + reloc kinds diagnose explicitly. +- [x] `&&label` addresses in static-data initializers diagnose early as + `wasm target: &&label addresses in static-data initializers are not + yet supported` (via the new `data_label_addr_unsupported_msg` + CGTarget hook), so the runner's SKIP regex catches them. +- [x] Aggregate (sret/byval) lowering for the wasm32 BasicCABI shape: + hidden i32 sret pointer prepended to the wasm function signature, + byval params received as i32 pointers and copied into a callee-isolated + stack-frame buffer at prologue time, `ret` of an INDIRECT value memcpys + the source bytes to the sret buffer, callers push the destination buffer + and indirect-arg addresses through `emit_addr_operand`. Targeted toy + fixtures: `129_record_by_value` (byval), `130_record_sret_return` (sret). +- [partial] Atomics: lowered via wasm-threads opcodes through the WIR ops + `WIR_ATOMIC_{LOAD,STORE,RMW,CAS}` and `WIR_FENCE` (`src/arch/wasm/emit.c`). + First atomic emission promotes the module's single linear memory to + `shared` with a `max_pages` cap via `ensure_shared_memory`. Full-width + i32/i64 load, store, RMW (xchg/add/sub/and/or/xor), cmpxchg, and fence + are covered. Sub-word atomic RMW/cmpxchg and atomic NAND diagnose-and-fail + because cfree's wasm core does not yet define the 8/16-bit RMW opcodes. + Memory order is ignored (wasm only models seq_cst). +- [x] Varargs: caller-packed linear-memory buffer per call site. + Variadic callees take a hidden trailing `i32` parameter holding the + buffer's address; the type-section signature, indirect-call + signature interning (`intern_indirect_signature` / + `abi_to_wasm_func_type`), and `wasm_func_begin` agree on the shape. + Each variadic arg occupies an 8-byte slot (`i32`/`f32` fill low + bytes; `i64`/`f64` fill the slot); the caller saves/restores + `__stack_pointer` around the call so varargs in a loop don't grow + the linear stack. `va_list` is a single `i32` pointer into the + buffer (`wasm32_vtable.va_list_info = {4, 4, ABI_SC_PTR, …}`). + `WIR_VA_START` / `WIR_VA_ARG` / `WIR_VA_COPY` lower the four CG + hooks; `va_arg<T>` loads at natural width then advances `*ap` by 8. + Aggregate variadic args and `va_arg` of an aggregate type + diagnose-and-fail. Targeted toy fixtures: + `23_cg_api_typed_varargs` (single i64 vararg) and + `133_varargs_mixed_types` (multi-type, `va_copy`, zero varargs). +- [ ] Inline asm, TLS, bitfields, indirect calls — all + diagnose-and-fail. +- [x] Compiler intrinsics: `CGTarget.intrinsic` now records a + `WIR_INTRINSIC` for bit ops and overflow arith and emits inline + expansions at linearize time (`src/arch/wasm/emit.c`, + `wasm_intrinsic` recorder + `emit_intrinsic_*` helpers): + - bit ops: `CLZ`/`CTZ`/`POPCOUNT` lower to `i32/i64.{clz,ctz,popcnt}`. + - byte-reverse: `BSWAP16`/`BSWAP32` via i32 shift/and/or expansion, + `BSWAP64` via i64 expansion. + - hints: `PREFETCH` drops, `EXPECT`/`ASSUME_ALIGNED` pass the input + through to the result reg. + - memory: `MEMMOVE` (and `MEMCPY`/`MEMSET` if reached via the + intrinsic path) route through `WIR_COPY_BYTES`/`WIR_SET_BYTES`, + which now lower to `memory.copy`/`memory.fill`. `emit_addr_operand` + accepts `OPK_REG` so address-already-in-register operands work. + - checked overflow: `S/UADD_OVERFLOW`, `S/USUB_OVERFLOW` lower to + add/sub + (un)signed compare; `S/UMUL_OVERFLOW` for i32 widens + to i64 multiply + range check. `S/UMUL_OVERFLOW` for i64 + diagnose-and-fail (needs partial-product synthesis). + - `setjmp`/`longjmp` and any unrecognized intrinsic kind reach + `compiler_panic` with a per-name diagnostic. +- [partial] `copy_bytes`/`set_bytes` lower to `memory.copy`/`memory.fill` + when the active feature set includes `BULK_MEMORY` (subagent B). +- [partial] Standard runtime targeting: linear memory exported as + `"memory"`, no synthesized `_start`, and undefined function symbols + emitted as `(import "env" "<sym>" ...)` declarations honoring + `__attribute__((import_module, import_name))` overrides (subagent B). +- [ ] Relocatable-object emission (`linking`/`reloc.*` custom sections, + `WasmObjMeta`). v1 single-TU output is a final module with no relocations; + multi-TU needs this. + +Targeted tests: `make test-toy-wasm` drives the existing toy corpus through +`-target wasm32-none -c` + roundtrip. Current snapshot: **99 pass / 0 fail / +34 skip** out of 133 fixtures, after the CFG structurer for direct gotos +(`src/arch/wasm/structure.c`) landed and absorbed the switch-island +shape. The structurer unblocked Toy AND/OR short-circuit lowering (free +`branch_false` / `jump` to forward labels), dynamic-size `@memcpy` / +`@memmove` / `@memset` byte-copy loops, and direct `goto` in C. Skips +cover pre-existing wasm-backend gaps: +`@atomic_is_lock_free<i64>` returns 0 on wasm32, and dynamic i64 +pointer-index arithmetic emits a type-mismatched `i32.add`. A +dedicated `test-wasm-target` fixture directory is still planned. ### Phase 3: Wasm Object Read/Write @@ -820,9 +1069,9 @@ slots and runtime table storage remain open work. - [x] Add `make test-wasm-front` and a small WAT-to-Wasm test helper. - [x] Add explicit negative frontend tests for malformed WAT, malformed Wasm, bad indices, stack underflow, unsupported sections, and unsupported opcodes. -- [ ] Decide stdin language selection for WAT input instead of treating `-` as +- [x] Decide stdin language selection for WAT input instead of treating `-` as C-only. -- [ ] Add dbg smoke coverage for `:language wasm` / `:language wat`. +- [x] Add dbg smoke coverage for `:language wasm` / `:language wat`. ### WAT Reader @@ -842,14 +1091,14 @@ slots and runtime table storage remain open work. - [x] Parse staged proposal syntax for memory-indexed memory ops, `memory.size`/`memory.grow`, `return_call`, `return_call_indirect`, shared memories, atomic load/store aliases, and `atomic.fence`. -- [ ] Preserve source locations through validation and lowering diagnostics. +- [x] Preserve source locations through validation and lowering diagnostics. ### Binary Reader and Encoder - [x] Decode and encode the current executable-module subset: type, function, export, code, locals, constants, calls, local ops, and integer ops. - [x] Reject `linking` custom sections as frontend input. -- [ ] Move shared binary mechanics into `src/wasm` with decode/encode contexts. +- [x] Move shared binary mechanics into `src/wasm` with decode/encode contexts. - [x] Validate section length, ordering, count, and index-space edge cases with direct format tests. - [x] Decode and encode memories and active data segments for the frontend @@ -885,9 +1134,22 @@ slots and runtime table storage remain open work. ### Frontend Proposals - [x] Add `WasmFeatureSet` bits for threads, typed function refs, tail calls, - multi-memory, and memory64. + multi-memory, memory64, bulk memory, and non-trapping float-to-int + conversions. - [x] Add WAT parser gates, binary opcode/section gates, and negative fixtures for threads, typed function refs, tail calls, multi-memory, and memory64. +- [partial] Add WAT parser gates, binary opcode gates, and negative fixtures + for bulk memory and non-trapping float-to-int conversions (the `0xfc` + opcode prefix block: `memory.copy`, `memory.fill`, `memory.init`, + `data.drop`, `table.copy`, `table.init`, `table.fill`, `table.grow`, + `table.size`, `elem.drop`, and the eight saturating-truncate + variants). Subagent A is landing the decoder/encoder/validator gates; + fixtures live under `test/wasm/cases/bulk_*` and + `test/wasm/trap/bulk_*`. +- [partial] Lower bulk-memory operations in the frontend through the + existing `copy_bytes`/`set_bytes` CG ops with bounds-check prologues, + passive-segment tables hung off `CfreeWasmInstance`, and `data.drop`/ + `elem.drop` zeroing of the length field. Subagent C is wiring this. - [x] Implement staged threads parsing for shared memories, atomic load/store aliases, and `atomic.fence`. - [x] Implement full threads parsing and validation: atomic RMW/cmpxchg, @@ -955,6 +1217,12 @@ slots and runtime table storage remain open work. and compile-time active-element dispatch. - [ ] Define and implement the C-facing exported wrapper ABI that keeps the instance parameter explicit. +- [x] Trim the unconditional trailing `cfree_cg_ret` after a function body + whose last instruction is `return`, `return_call`, + `return_call_indirect`, `return_call_ref`, or `unreachable`. The body + already terminated the SValue stack; an extra ret underflowed when the + Wasm target emits an explicit `return` at end-of-body (which the new + toy-frontend roundtrip exercises). ### Runtime and Instance Model @@ -981,26 +1249,131 @@ slots and runtime table storage remain open work. ### Wasm Target Backend -- [ ] Register `arch_impl_wasm` and a wasm32 ABI vtable. -- [ ] Implement scalar wasm32 BasicCABI classification. -- [ ] Implement a Wasm `CGTarget` that emits function bodies, locals, type - entries, data segments, exports, and relocations. -- [ ] Diagnose unsupported target features: wasm64, WASI startup, varargs, - atomics, SIMD, TLS, inline asm, irreducible control flow, and native-only - ABI hooks. -- [ ] Add `make test-wasm-target`. +- [x] Register `arch_impl_wasm` and a wasm32 ABI vtable + (`src/arch/wasm/{arch,abi}.c`; `src/arch/registry.c`). +- [x] Implement scalar wasm32 BasicCABI classification: void, scalar + (i32/i64/f32/f64 plus i32 pointer), empty struct → IGNORE, singleton + scalar record → DIRECT, other aggregates and arrays → INDIRECT + (sret/byval). i128 and long-double scalars panic. +- [x] Implement a Wasm `CGTarget` that emits function bodies, locals, type + entries, and exports. **Data segments and relocations are not yet + wired** — single-TU final modules need no relocations, and the linear + memory / data-segment lowering is still pending. +- [x] Diagnose unsupported target features: wasm64 (ABI panics in + `compute_func_info`), TLS, bitfields, indirect calls, + `setjmp`/`longjmp`, i64 checked-mul overflow — all reach + `compiler_panic` with a `wasm:` / `wasm target:` / `wasm32 ABI:` + prefix that `test/toy/run.sh`'s W path recognizes as SKIP. WASI + startup, SIMD, and irreducible control flow still produce raw + diagnostics rather than wasm-specific ones; tighten later. + Varargs and the `va_*` CG hooks now have real implementations + (caller-packed linear-memory buffer; see "Varargs" bullet below). +- [x] Implement inline asm with WAT-instruction templates. The + `asm_block` vtable entry parses the template via the + `wasm_parse_wat_body` helper in `src/wasm/wat.c`, validates against a + synthetic function via the new `wasm_validate_func` (factored out of + `wasm_validate` in `src/wasm/validate.c`), then emits a new + `WIR_ASM_BLOCK` op (`src/arch/wasm/internal.h`, `emit.c`) whose + linearizer allocates wasm locals for each input/output operand and + splices the parsed body verbatim. The snippet addresses operands via + `local.get/set/tee N`: `N < nin` reads/writes input i, `N in [nin, + nin+nout)` reads/writes output (N-nin). `"+r"` inout and numeric + tieback constraints ("0".."9") share the input and output's wasm + local, so an empty template paired with `+r` behaves as identity. + Constraints map: `"r"` → wasm local, `"i"` → const-folded + `i32.const`/`i64.const`, `"m"` → i32 base address. `memory` clobber + is an accepted no-op (cg/asm.c already spilled live SSA across the + block); named-register clobbers are rejected. Disallowed in v1: + escaping `br`/`br_if`/`br_table`, `return`/`return_call*`, + `call_indirect`, snippet-internal locals (the only declared locals + are the implicit operand slots), more than one output, register + clobbers. File-scope `__asm__("…")` is rejected via the new + `CGTarget.file_scope_asm` vtable hook with `wasm target: file-scope + asm not yet supported`. Tests: `test/wasm-target/check_asm.sh` + exercises a `i32.popcnt` template positive case and an escaping `br` + negative case; the existing `20_cg_api_inline_asm_full` / + `102_typed_asm_operands` toy fixtures and their kin now run end-to-end + through `test-toy-wasm` (formerly SKIP'd). +- [partial] Lower `copy_bytes` and `set_bytes` to the bulk-memory opcodes + `memory.copy` (0xfc 0x0a) and `memory.fill` (0xfc 0x0b) when the + active feature set includes `BULK_MEMORY` (subagent B). Replaces the + inline byte-load/store loops in `src/arch/wasm/emit.c` for + `WIR_COPY_BYTES`/`WIR_SET_BYTES`; verified structurally by + `test/wasm-target/check_memory_copy.sh`. +- [partial] Export the linear memory under the conventional name + `"memory"` so cfree-produced modules load directly in wasmtime, + wasmer, Node, and browser hosts (subagent B). Verified by + `test/wasm-target/check_memory_export.sh`. +- [partial] Emit `(import "env" "<sym>" ...)` declarations for undefined + function symbols (subagent B). `__attribute__((import_module("M"), + import_name("F")))` in the C frontend overrides the default + module/field pair. Verified by `test/wasm-target/check_imports.sh` + against `import_decl.c` and `import_decl_attribute.c`. +- [x] Refactor structured `if`/`if-else` lowering through + `cfree_cg_block_begin` and nested `SCOPE_BLOCK` scopes + (`include/cfree/cg.h`; `src/cg/control.c`). Drives the Toy parser and + C `parse_if_stmt` so both reach the Wasm backend as + `block`/`break`/`end` rather than label-and-jump primitives — no CFG + stackifier needed for the common case. +- [x] `&&label` addresses inside static-data initializers panic with an + early `wasm target: &&label addresses in static-data initializers are + not yet supported` diagnostic before the MCEmitter sees them. Wired + via the new `CGTarget.data_label_addr_unsupported_msg` hook + (`src/arch/arch.h`; `src/cg/data.c`; `src/arch/wasm/target.c`). +- [x] Lower Wasm-target `switch_` through real `br_table` for dense ranges + and stackify the frontend switch-island shape into structured blocks so + case/default labels become legal Wasm branch depths. Sparse ranges fall + back to a structured compare chain instead of the shared label-based + lowering. +- [x] Linear-memory + `__stack_pointer` global; address-taken locals; + `addr_of`, stack-backed frame slots, `alloca`, and byte copy/set lowering. +- [x] Compact global/static data layout. `wasm_materialize_data` + (`src/arch/wasm/emit.c`) walks SF_ALLOC ObjBuilder sections in id order, + assigns each one an aligned `section_base[sid]` starting from a 16-byte + null guard, and flattens section bytes into the linear-memory image. + Replaces the previous `sid * 1MiB` per-section stride that produced + ~1MB modules for any global. Symbol-address resolution in function + bodies is queued via `WSymFixup` (an i32.const placeholder + patch at + finalize) so absolute addresses are only computed once every section's + final size is settled. +- [x] Apply ObjBuilder relocations into the linear-memory image. After + flatten, `apply_data_relocs` walks `obj_reloc_at` and patches `R_ABS32` + references with `section_base[sym->sec] + sym->value + addend` so + cross-symbol initializers (e.g. `*p = &x`) work for the single-TU + final-module case. `R_ABS64` and other reloc kinds diagnose explicitly. +- [x] Add `make test-toy-wasm` (Toy → wasm32 -c → `cfree run` of the + `.wasm`). Drives the existing toy corpus through the backend and the + Wasm-input frontend's native JIT. Snapshot: 74 pass / 4 fail / 55 skip + after the compact data layout / static-data relocation / &&label-data + reject landings; remaining failures are switch/enum lowering issues + unrelated to global data. +- [partial] Add a dedicated `make test-wasm-target` corpus that + structurally validates produced Wasm modules (independent of the JIT + roundtrip). Initial scripts under `test/wasm-target/` cover bulk + memory opcode lowering, exported `"memory"`, and `(import ...)` + declarations. ### Object Read/Write and Link -- [ ] Add `ObjBuilder` extension payload hooks. +- [x] Add `ObjBuilder` extension payload hooks (`obj_ext_set/get/clear` in + `src/obj/obj.{h,c}`); `OBJ_EXT_WASM` slot used by the Wasm target. +- [x] Implement `emit_wasm` for the single-TU final-module case + (`src/obj/wasm_emit.c`): flushes the `WasmModule` attached under + `OBJ_EXT_WASM` via `wasm_encode`, or an empty magic+version header if no + module was attached. - [ ] Add `WasmObjMeta` for module graph, symbols, relocations, data segment - metadata, target features, and init functions. -- [ ] Implement `emit_wasm` for tool-conventions relocatable objects. -- [ ] Implement `read_wasm` for relocatable objects and symbol inspection. + metadata, target features, and init functions. v1 stores a `WasmModule*` + directly under `OBJ_EXT_WASM`; the typed `WasmObjMeta` is still pending + and is needed before the tool-conventions relocatable-object shape. +- [ ] Extend `emit_wasm` for tool-conventions relocatable objects + (`linking` custom section, `reloc.*` custom sections, target features). +- [ ] Implement `read_wasm` for relocatable objects and symbol inspection + (still stubbed in `src/api/stubs.c`). - [ ] Implement reloc custom sections for the current `R_WASM_*` relocation kinds, including padded LEB rewrites. - [ ] Add `make test-wasm-obj`. - [ ] Add a Wasm-specific linker path that emits `WasmLinkImage`, not native + `LinkImage`. `cfree_link_exe` still always builds a `Linker` + native `LinkImage`. - [ ] Merge type/import/function/table/global/element/data/custom sections and apply relocations. @@ -1010,11 +1383,20 @@ slots and runtime table storage remain open work. ### Wasm-to-Wasm -- [ ] Use the frontend plus Wasm target to normalize simple modules. -- [ ] Validate emitted modules with cfree's own validator and optional external - validators when available. -- [ ] Preserve behavior for numeric/control/memory fixtures while allowing the - byte representation to change. +- [partial] Use the frontend plus Wasm target to normalize simple modules. + The Toy → Wasm → run roundtrip via `make test-toy-wasm` exercises the + backend's output through the Wasm-input frontend's native JIT (an + indirect Wasm-to-native rather than Wasm-to-Wasm path). A direct + Wasm-input → Wasm-output mode requires hooking the lang/wasm frontend + to the Wasm target instead of native CG. +- [partial] Validate emitted modules with cfree's own validator and optional + external validators when available. The frontend's validator runs on the + Toy-produced `.wasm` during `cfree run`; an explicit + validate-only pass over Wasm-backend output is not yet wired. +- [x] Preserve behavior for numeric/control fixtures while allowing the + byte representation to change. 32 of 131 toy fixtures roundtrip + correctly today; memory-using fixtures don't yet (linear memory in the + backend is pending). ## Testing Strategy @@ -1050,6 +1432,19 @@ Wasm tooling installed. should the Wasm backend include a general unstructured-CFG-to-structured-Wasm pass from day one? Decision: hybrid. Prefer structured scopes, add a limited reducible-CFG structurer, and diagnose irreducible/computed-goto cases in v1. + Implemented as `wasm_structurize` in `src/arch/wasm/structure.c`: wraps + free WIR_LABELs in synthetic `SCOPE_BLOCK` (forward goto) / `SCOPE_LOOP` + (backward goto) before linearization, lifting open positions across CG + scope boundaries when a predecessor sits inside an extra scope. Hybrid + labels (both forward and backward predecessors), forward jumps that + cross into a CG scope's interior, and computed-goto patterns diagnose + with `wfail`. Switch-island regions are unrolled in the same pass by + `unroll_switch_islands`: a pre-pass that detects the frontend's + `JUMP dispatch / case bodies / LABEL dispatch / SWITCH` shape and + rewrites the WIR to put selector + SWITCH first and case bodies + after, dropping the unconditional jump and the dispatch label. Case + labels then become forward refs and flow through the same direct-goto + wrapping as any other label. - How much of BasicCABI should be implemented before any public Wasm target flag is advertised: scalar-only, scalar plus indirect aggregates, or full varargs and long-double helper coverage? Decision: scalar plus indirect diff --git a/driver/run.c b/driver/run.c @@ -10,6 +10,12 @@ #include "hosted.h" #include "inputs.h" +#include <cfree/compile.h> +#include <cfree/core.h> +#include <cfree/jit.h> +#include <cfree/link.h> +#include <cfree/wasm.h> + /* `cfree run` — JIT-compile one or more inputs and invoke the entry symbol * (default `main`) in-process. Args after `--` are passed to the JITed * program as argv. Mirrors the cc front-end for input shape (.c / - sources, @@ -610,6 +616,14 @@ 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 CFREE_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(CfreeWasmInstance* inst, int32_t a, int32_t b) { + (void)inst; + return a + b; +} + typedef struct RunWasmMemoryPrefix { uint8_t* data; uint64_t pages; @@ -617,8 +631,8 @@ typedef struct RunWasmMemoryPrefix { uint32_t flags; } RunWasmMemoryPrefix; -static int run_call_wasm_entry(RunOptions* ro, CfreeJit* jit, void* entry, - int* rc_out) { +static int run_call_wasm_entry(RunOptions* ro, CfreeCompiler* compiler, + CfreeJit* jit, void* entry, int* rc_out) { void* init_sym = cfree_jit_lookup(jit, CFREE_SLICE_LIT("__cfree_wasm_init")); uint8_t* instance; uint8_t* memory; @@ -643,9 +657,44 @@ static int run_call_wasm_entry(RunOptions* ro, CfreeJit* jit, void* entry, for (uint32_t i = 0; i < 8u; ++i) ((RunWasmMemoryPrefix*)instance)[i].data = memory + i * (16u * 1024u * 1024u / 8u); + /* Resolve host imports before init so the start function (if any) and + * any global initializers that call imports see populated slots. */ + { + const CfreeWasmHostImport* h_imports = NULL; + size_t h_nimports = 0; + CfreeWasmResolveFn h_resolve = NULL; + void* h_user = NULL; + cfree_wasm_get_host_imports(compiler, &h_imports, &h_nimports, &h_resolve, + &h_user); + /* When invoked from the wasm-front test suite, the runner exposes a + * small canned set of host imports. The env var stays opt-in so + * production `cfree run` doesn't surface test scaffolding. */ + CfreeWasmHostImport canned[1]; + size_t ncanned = 0; + extern int32_t run_test_host_add(CfreeWasmInstance*, int32_t, int32_t); + if (h_nimports == 0 && !h_resolve && + driver_getenv("CFREE_TEST_HOST_IMPORTS")) { + canned[0].module = "env"; + canned[0].field = "host_add"; + canned[0].func = (void*)(uintptr_t)run_test_host_add; + ncanned = 1; + h_imports = canned; + h_nimports = ncanned; + } + if (cfree_wasm_bind_host_imports(compiler, jit, + (CfreeWasmInstance*)instance, h_imports, + h_nimports, h_resolve, h_user) != + CFREE_OK) { + driver_errf(RUN_TOOL, "wasm host import bind failed"); + driver_free(ro->env, memory, 16u * 1024u * 1024u); + driver_free(ro->env, instance, 64u * 1024u); + *rc_out = 1; + return 1; + } + } init_u.p = init_sym; entry_u.p = entry; - init_u.fn(instance); + init_u.fn((CfreeWasmInstance*)instance); *rc_out = entry_u.fn(instance); driver_free(ro->env, memory, 16u * 1024u * 1024u); driver_free(ro->env, instance, 64u * 1024u); @@ -746,7 +795,7 @@ 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, jit, sym, &rc)) + if (!run_call_wasm_entry(&ro, compiler, jit, sym, &rc)) 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"); diff --git a/include/cfree/cg.h b/include/cfree/cg.h @@ -322,6 +322,13 @@ typedef struct CfreeCgFuncAttrs { CfreeSym section; /* 0 = target default */ CfreeSym target_features; CfreeCgInlinePolicy inline_policy; + /* Wasm-target import descriptor. Honored only by the wasm backend, ignored + * by other targets. Promotes an undefined function symbol into a wasm + * `(import "<module>" "<name>" ...)` entry instead of a missing definition. + * Both 0 leaves the backend to fall back to module="env", name=<sym name> + * when an undefined function is referenced. */ + CfreeSym wasm_import_module; + CfreeSym wasm_import_name; } CfreeCgFuncAttrs; typedef enum CfreeCgTlsModel { @@ -463,6 +470,13 @@ CFREE_API void cfree_cg_alloca(CfreeCg*, uint32_t align, CfreeCgTypeId result_pt * continue/continue_true/continue_false never carry a result; they jump to * the loop header, not the scope exit. */ CFREE_API CfreeCgScope cfree_cg_scope_begin(CfreeCg*, CfreeCgTypeId result_type); + +/* Like cfree_cg_scope_begin but opens a forward-only block: break exits past + * the scope; continue is not legal. This is the natural shape for `if`/ + * `if-else` (a wrapping block whose break is "skip the rest"), and lets + * backends with structured control flow (Wasm) emit `block`/`end` directly + * instead of recognizing arbitrary forward-only label-and-jump patterns. */ +CFREE_API CfreeCgScope cfree_cg_block_begin(CfreeCg*, CfreeCgTypeId result_type); CFREE_API void cfree_cg_scope_end(CfreeCg*, CfreeCgScope); /* Expose the labels CG minted for this scope. Frontends that emit * unstructured `jump`/`branch` ops (rather than going through the @@ -567,6 +581,11 @@ CFREE_API void cfree_cg_push_symbol_addr(CfreeCg*, CfreeCgSym sym, int64_t adden * a call, escape). */ CFREE_API void cfree_cg_addr(CfreeCg*); +/* Project through array/pointer and record fields. These leave an lvalue on + * the stack, so callers can load from or store to the selected slot. */ +CFREE_API void cfree_cg_index(CfreeCg*, uint64_t offset); +CFREE_API void cfree_cg_field(CfreeCg*, uint32_t field_index); + /* Single load/store ops with an effective-address rider. * * `base` is either an lvalue (push_local) or a pointer-typed value. When @@ -1148,26 +1167,41 @@ static inline void cfree_cg_inc_dec(CfreeCg* cg, CfreeCgIntBinOp op, int post, } } +/* `if (cond) then [else else_body]` lowering via two nested forward blocks. + * + * outer = block_begin (break = "end of if") + * inner = block_begin (break = "else entry point") + * break_false(inner) ; pop cond, skip then-body if false + * then-body + * if_else: break(outer); end inner + * [optional else-body] + * if_end: end outer + * + * Frontends must call cfree_cg_if_else exactly once after the then-body + * (even if there is no else clause — the call closes the inner block so + * if_end can close only the outer). Using structured scopes means backends + * that already lower BLOCK + break natively (every native arch and the Wasm + * target) handle `if`/`if-else` without a CFG-to-structured pass. */ typedef struct CfreeCgIf { - CfreeCgLabel else_label; - CfreeCgLabel end_label; + CfreeCgScope outer; + CfreeCgScope inner; } CfreeCgIf; static inline CfreeCgIf cfree_cg_if_begin(CfreeCg* cg) { CfreeCgIf it; - it.else_label = cfree_cg_label_new(cg); - it.end_label = cfree_cg_label_new(cg); - cfree_cg_branch_false(cg, it.else_label); + it.outer = cfree_cg_block_begin(cg, CFREE_CG_TYPE_NONE); + it.inner = cfree_cg_block_begin(cg, CFREE_CG_TYPE_NONE); + cfree_cg_break_false(cg, it.inner); return it; } static inline void cfree_cg_if_else(CfreeCg* cg, CfreeCgIf it) { - cfree_cg_jump(cg, it.end_label); - cfree_cg_label_place(cg, it.else_label); + cfree_cg_break(cg, it.outer); + cfree_cg_scope_end(cg, it.inner); } static inline void cfree_cg_if_end(CfreeCg* cg, CfreeCgIf it) { - cfree_cg_label_place(cg, it.end_label); + cfree_cg_scope_end(cg, it.outer); } /* Extract bits [lo, lo+width) from TOS as an unsigned value. diff --git a/include/cfree/config.h b/include/cfree/config.h @@ -25,6 +25,7 @@ #define CFREE_ARCH_AA64_ENABLED 1 #define CFREE_ARCH_X64_ENABLED 1 #define CFREE_ARCH_RV64_ENABLED 1 +#define CFREE_ARCH_WASM_ENABLED 1 #define CFREE_ARCH_C_TARGET_ENABLED 1 /* Object/image formats. Each gates emit + read + link-image paths and @@ -32,6 +33,7 @@ #define CFREE_OBJ_ELF_ENABLED 1 #define CFREE_OBJ_MACHO_ENABLED 1 #define CFREE_OBJ_COFF_ENABLED 1 +#define CFREE_OBJ_WASM_ENABLED 1 /* Language frontends. The assembler frontend is unconditional: it lives * inside libcfree as part of the codegen substrate. diff --git a/include/cfree/wasm.h b/include/cfree/wasm.h @@ -0,0 +1,135 @@ +#ifndef CFREE_WASM_H +#define CFREE_WASM_H + +/* + * Wasm host-import resolver API. + * + * cfree's wasm frontend lowers each module to native code using a private + * "cfree-instance ABI" — every lowered function takes a hidden + * `CfreeWasmInstance*` first parameter. Imported functions are dispatched + * through function-pointer slots that live inside the instance struct. + * + * This header lets a host bind those slots by (module, field) name before + * the module starts running. Host-provided imports follow the cfree-instance + * ABI explicitly: they are called as + * ret (*)(CfreeWasmInstance*, [wasm params...]) + * which gives the host direct access to the module's linear memory (the + * memory record lives inside the instance struct at a fixed offset). + * + * Use: + * 1. Build a `CfreeWasmHostImport` table (and/or supply a resolver). + * 2. Call `cfree_wasm_set_host_imports` on the compiler before linking. + * 3. The runner that loads the module calls `cfree_wasm_bind_host_imports` + * after JIT-linking and before invoking `__cfree_wasm_init`. + * + * `cfree run` and the in-tree JIT test harness already perform the bind + * step; embedders that drive the linker directly must do so themselves. + */ + +#include <cfree/core.h> +#include <cfree/jit.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Opaque per-module runtime state. The frontend emits a module-specific + * layout — `CfreeWasmMemory` first, followed by import slots, runtime + * 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 CfreeWasmInstance CfreeWasmInstance; + +typedef enum CfreeWasmValType { + CFREE_WASM_VAL_I32 = 0, + CFREE_WASM_VAL_I64 = 1, + CFREE_WASM_VAL_F32 = 2, + CFREE_WASM_VAL_F64 = 3, + CFREE_WASM_VAL_FUNCREF = 4, + CFREE_WASM_VAL_EXTERNREF = 5, +} CfreeWasmValType; + +/* Declared wasm signature for an import. The arrays are owned by cfree (the + * resolver callback borrows them; do not free). */ +typedef struct CfreeWasmImportType { + const CfreeWasmValType* params; + uint32_t nparams; + const CfreeWasmValType* results; + uint32_t nresults; +} CfreeWasmImportType; + +/* Static host-import descriptor. + * + * `func` must match the declared wasm type when called with the cfree- + * instance ABI prepended: + * ret (*)(CfreeWasmInstance*, [wasm params...]) + * + * The binder validates (nparams, nresults) and the per-value type bytes + * against the module's declared import type and refuses on mismatch. + * + * Strings are borrowed: they must outlive the next link / bind. */ +typedef struct CfreeWasmHostImport { + const char* module; + const char* field; + void* func; +} CfreeWasmHostImport; + +/* Dynamic resolver. Invoked for imports not found in the static table. + * Returns a function pointer with the cfree-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. */ +typedef void* (*CfreeWasmResolveFn)(void* user, const char* module, + const char* field, + const CfreeWasmImportType* type); + +/* 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. + * + * The provided table and resolver pointers must outlive the next call to + * `cfree_wasm_bind_host_imports` against this compiler. */ +CFREE_API void cfree_wasm_set_host_imports(CfreeCompiler*, + const CfreeWasmHostImport* imports, + size_t nimports, + CfreeWasmResolveFn resolve, + void* user); + +/* Read back the static host-import table previously stashed via + * `cfree_wasm_set_host_imports`. The output pointer / count / resolver + * remain owned by the compiler (do not free). Used by runners to feed the + * bind step. Any out parameter may be NULL. */ +CFREE_API void cfree_wasm_get_host_imports(CfreeCompiler*, + const CfreeWasmHostImport** imports_out, + size_t* nimports_out, + CfreeWasmResolveFn* resolve_out, + void** user_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`: + * __cfree_wasm_nimports uint32 count + * __cfree_wasm_imports array of {module, field, typeidx, slot_offset} + * __cfree_wasm_types array of {params, nparams, results, nresults} + * If those symbols are absent (module has no imports, or was not built + * through cfree's wasm frontend), the call is a no-op and returns CFREE_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. + * + * Returns CFREE_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). */ +CFREE_API CfreeStatus cfree_wasm_bind_host_imports( + CfreeCompiler* compiler, CfreeJit* jit, CfreeWasmInstance* inst, + const CfreeWasmHostImport* imports, size_t nimports, + CfreeWasmResolveFn resolve, void* user); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lang/c/decl/decl.c b/lang/c/decl/decl.c @@ -115,6 +115,8 @@ DeclId decl_declare(DeclTable* t, const Decl* in) { decl.as.func.flags |= CFREE_CG_FUNC_NORETURN; decl.as.func.inline_policy = decl_inline_policy(slot); decl.as.func.section = slot->section_id; + decl.as.func.wasm_import_module = slot->wasm_import_module; + decl.as.func.wasm_import_name = slot->wasm_import_name; } else { if (slot->flags & DF_THREAD) decl.as.object.flags |= CFREE_CG_OBJ_TLS; decl.as.object.section = slot->section_id; diff --git a/lang/c/decl/decl.h b/lang/c/decl/decl.h @@ -84,6 +84,11 @@ typedef struct Decl { /* Phase 2 attribute carriers — populated by attr_list_to_decl. */ u32 align; /* explicit alignment from _Alignas or aligned(N); 0=natural */ Sym alias_target; /* target name for __attribute__((alias("..."))); 0=none */ + /* Wasm import overrides from __attribute__((import_module("M"), + * import_name("F"))). Honored only by the wasm backend; ignored by other + * targets. Both 0 = backend default (module="env", name=<sym name>). */ + Sym wasm_import_module; + Sym wasm_import_name; } Decl; DeclTable* decl_new(Compiler*, Pool*, CfreeCg*); diff --git a/lang/c/decl/decl_attrs.c b/lang/c/decl/decl_attrs.c @@ -72,6 +72,20 @@ void attr_list_to_decl(Compiler* c, DeclTable* t, const Attr* attrs, case ATTR_ALIAS: out->alias_target = a->v.sym; break; + case ATTR_IMPORT_MODULE: + if (a->v.sym == 0) { + compiler_panic(c, a->loc, + "import_module attribute requires a string argument"); + } + out->wasm_import_module = a->v.sym; + break; + case ATTR_IMPORT_NAME: + if (a->v.sym == 0) { + compiler_panic(c, a->loc, + "import_name attribute requires a string argument"); + } + out->wasm_import_name = a->v.sym; + break; default: break; } diff --git a/lang/c/parse/attr.h b/lang/c/parse/attr.h @@ -45,6 +45,12 @@ typedef enum AttrKind { ATTR_SENTINEL, ATTR_NO_INSTRUMENT_FUNCTION, ATTR_NO_SANITIZE, + /* Wasm-target import descriptors. Each carries a single string argument + * (AS_STRING) stored in `v.sym`. Together they request the wasm backend + * emit a `(import "<module>" "<name>" ...)` entry for the declared + * function. */ + ATTR_IMPORT_MODULE, + ATTR_IMPORT_NAME, } AttrKind; typedef enum AttrArgShape { diff --git a/lang/c/parse/parse_stmt.c b/lang/c/parse/parse_stmt.c @@ -34,10 +34,9 @@ static void parse_stmt_suppressed(Parser* p) { * ============================================================ */ static void parse_if_stmt(Parser* p) { - CGLabel L_else = cg_label_new(p->cg); - CGLabel L_end = cg_label_new(p->cg); i64 cond = 0; int cond_known; + CfreeCgIf it; expect_punct(p, '(', "'('"); parse_expr(p); to_rvalue(p); @@ -57,16 +56,22 @@ static void parse_if_stmt(Parser* p) { } return; } - cg_branch_false(p->cg, L_else); - parse_stmt(p); - if (accept_kw_stmt(p, KW_ELSE)) { - cg_jump(p->cg, L_end); - cg_label_place(p->cg, L_else); + /* Structured `if`: nested SCOPE_BLOCKs with break. Lets every backend that + * already lowers SCOPE_BLOCK natively (native arches, Wasm) emit + * `if`/`if-else` without falling back to label/jump primitives. + * + * Under codegen suppression no cond is on the SValue stack, so emit + * nothing and just walk the body for the diagnostics-only pass. */ + if (!pcg_emit_enabled(p)) { parse_stmt(p); - cg_label_place(p->cg, L_end); - } else { - cg_label_place(p->cg, L_else); + if (accept_kw_stmt(p, KW_ELSE)) parse_stmt(p); + return; } + it = cfree_cg_if_begin(p->cg); + parse_stmt(p); + cfree_cg_if_else(p->cg, it); + if (accept_kw_stmt(p, KW_ELSE)) parse_stmt(p); + cfree_cg_if_end(p->cg, it); } static void parse_while_stmt(Parser* p) { diff --git a/lang/c/parse/parse_type.c b/lang/c/parse/parse_type.c @@ -55,6 +55,8 @@ static const struct { {"sentinel", ATTR_SENTINEL, AS_OPAQUE}, {"no_instrument_function", ATTR_NO_INSTRUMENT_FUNCTION, AS_NONE}, {"no_sanitize", ATTR_NO_SANITIZE, AS_OPAQUE}, + {"import_module", ATTR_IMPORT_MODULE, AS_STRING}, + {"import_name", ATTR_IMPORT_NAME, AS_STRING}, }; static SrcLoc tok_loc(const Tok* t) { return t->loc; } diff --git a/lang/toy/compile.c b/lang/toy/compile.c @@ -140,7 +140,7 @@ static CfreeStatus toy_frontend_compile(CfreeFrontendState* frontend, CfreeObjBuilder* out) { ToyFrontend* fe = (ToyFrontend*)frontend; CfreeCompiler* c; - ToyParser p; + ToyParser* p; CfreeCg* cg; const uint8_t* source; size_t source_len; @@ -170,31 +170,34 @@ static CfreeStatus toy_frontend_compile(CfreeFrontendState* frontend, toy_parser_reinit(&fe->parser, c, cg, source, source_len, input->name.s, opts->input_kind); } - p = fe->parser; + /* Drive the heap-resident parser directly. The previous copy-by-value + * variant (`ToyParser p = fe->parser; ...; fe->parser = p;`) lost any + * growth of the parser's internal arrays when a wasm CG panic longjmp'd + * past the `fe->parser = p;` sync-back, leaving fe->parser holding + * pointers that the local `p`'s grow path had freed and replaced — + * dispose then double-freed via the stale pointer. */ + p = &fe->parser; if (opts->input_kind != CFREE_FRONTEND_INPUT_TRANSLATION_UNIT && - !toy_seed_repl_symbols(&p)) { + !toy_seed_repl_symbols(p)) { cfree_cg_free(cg); st = CFREE_ERR; goto done_status; } - if (!toy_parse_program(&p) || p.has_error) { - fe->parser = p; + if (!toy_parse_program(p) || p->has_error) { fe->poisoned = 1; cfree_cg_free(cg); st = CFREE_ERR; goto done_status; } - if (p.cur.kind != TOK_EOF) { - toy_error(&p, p.cur.loc, "unexpected token after program end"); - fe->parser = p; + if (p->cur.kind != TOK_EOF) { + toy_error(p, p->cur.loc, "unexpected token after program end"); fe->poisoned = 1; cfree_cg_free(cg); st = CFREE_ERR; goto done_status; } - fe->parser = p; cfree_cg_free(cg); st = CFREE_OK; diff --git a/lang/toy/expr.c b/lang/toy/expr.c @@ -421,6 +421,8 @@ int toy_target_code(ToyParser* p) { return 2; case CFREE_ARCH_RV64: return 3; + case CFREE_ARCH_WASM: + return 7; default: return 0; } diff --git a/lang/toy/parser_core.c b/lang/toy/parser_core.c @@ -129,28 +129,43 @@ void toy_parser_reinit(ToyParser* p, CfreeCompiler* c, CfreeCg* cg, void toy_parser_dispose(ToyParser* p) { size_t i; + /* Only free per-element pointer arrays when their companion count is + * non-zero — allocation only happens in that case, so a zero count means + * the pointer field was never written by an allocation. This guards + * against rare paths that leave a pointer slot uninitialized while + * leaving the count at 0; without it, freeing a garbage pointer + * aborts libc malloc with "pointer being freed was not allocated". */ toy_parser_free_mem(p, p->vars, p->cap_vars * sizeof *p->vars); for (i = 0; i < p->nfns; ++i) { - toy_parser_free_mem(p, p->fns[i].params, - p->fns[i].nparams * sizeof *p->fns[i].params); - toy_parser_free_mem(p, p->fns[i].toy_params, - p->fns[i].nparams * sizeof *p->fns[i].toy_params); + if (p->fns[i].nparams) { + toy_parser_free_mem(p, p->fns[i].params, + p->fns[i].nparams * sizeof *p->fns[i].params); + toy_parser_free_mem(p, p->fns[i].toy_params, + p->fns[i].nparams * sizeof *p->fns[i].toy_params); + } } toy_parser_free_mem(p, p->fns, p->cap_fns * sizeof *p->fns); toy_parser_free_mem(p, p->globals, p->cap_globals * sizeof *p->globals); - for (i = 0; i < p->type_table.ntypes; ++i) - toy_parser_free_mem(p, p->type_table.types[i].params, - p->type_table.types[i].nparams * - sizeof *p->type_table.types[i].params); + for (i = 0; i < p->type_table.ntypes; ++i) { + if (p->type_table.types[i].nparams) { + toy_parser_free_mem(p, p->type_table.types[i].params, + p->type_table.types[i].nparams * + sizeof *p->type_table.types[i].params); + } + } toy_parser_free_mem(p, p->type_table.types, p->type_table.cap_types * sizeof *p->type_table.types); for (i = 0; i < p->type_table.count; ++i) { - toy_parser_free_mem(p, p->type_table.named[i].enum_values, - p->type_table.named[i].cap_enum_values * - sizeof *p->type_table.named[i].enum_values); - toy_parser_free_mem(p, p->type_table.named[i].fields, - p->type_table.named[i].cap_fields * - sizeof *p->type_table.named[i].fields); + if (p->type_table.named[i].cap_enum_values) { + toy_parser_free_mem(p, p->type_table.named[i].enum_values, + p->type_table.named[i].cap_enum_values * + sizeof *p->type_table.named[i].enum_values); + } + if (p->type_table.named[i].cap_fields) { + toy_parser_free_mem(p, p->type_table.named[i].fields, + p->type_table.named[i].cap_fields * + sizeof *p->type_table.named[i].fields); + } } toy_parser_free_mem(p, p->type_table.named, p->type_table.cap * sizeof *p->type_table.named); @@ -184,11 +199,13 @@ int toy_parser_reserve(ToyParser* p, void** items, size_t* cap, size_t want, size_t new_cap; size_t old_size; size_t new_size; + size_t old_cap; void* new_items; if (want <= *cap) return 1; - new_cap = *cap ? *cap * 2u : 8u; + old_cap = *cap; + new_cap = old_cap ? old_cap * 2u : 8u; while (new_cap < want) new_cap *= 2u; - old_size = *cap * elem_size; + old_size = old_cap * elem_size; new_size = new_cap * elem_size; if (new_cap != 0 && new_size / new_cap != elem_size) { toy_error(p, p->cur.loc, "out of memory growing %.*s", @@ -196,15 +213,23 @@ int toy_parser_reserve(ToyParser* p, void** items, size_t* cap, size_t want, return 0; } h = cfree_compiler_context(p->c)->heap; - new_items = h->realloc(h, *items, old_size ? old_size : 1u, - new_size ? new_size : 1u, 1); + /* Allocate fresh and copy old contents over (rather than realloc'ing in + * place and only zeroing the tail). Zeroing the whole buffer guarantees + * any slot the writer doesn't fully initialize has NULL pointers, so the + * dispose path never sees stale bytes — a previous realloc-in-place + * variant left under-initialized slots holding the realloc'd pages' + * prior contents, which manifested as a "pointer being freed was not + * allocated" abort in lib c malloc when the dispose loop walked the + * type table. */ + new_items = h->alloc(h, new_size ? new_size : 1u, 1); if (!new_items) { toy_error(p, p->cur.loc, "out of memory growing %.*s", CFREE_SLICE_ARG(cfree_slice_cstr(what))); return 0; } - memset((uint8_t*)new_items + (*cap * elem_size), 0, - (new_cap - *cap) * elem_size); + memset(new_items, 0, new_size ? new_size : 1u); + if (*items && old_size) memcpy(new_items, *items, old_size); + if (*items) h->free(h, *items, old_size ? old_size : 1u); *items = new_items; *cap = new_cap; return 1; diff --git a/lang/wasm/cg.c b/lang/wasm/cg.c @@ -1,4 +1,7 @@ -#include "wasm_internal.h" +#include "wasm/wasm.h" + +#include "core/arena.h" +#include "core/heap.h" static CfreeCgTypeId wasm_cg_type(CfreeCompiler* c, CfreeCgBuiltinTypes b, WasmValType vt) { @@ -71,6 +74,10 @@ static CfreeCgTypeId wasm_load_storage_type(CfreeCgBuiltinTypes b, case WASM_INSN_I64_LOAD32_U: case WASM_INSN_I32_LOAD: return b.id[CFREE_CG_BUILTIN_I32]; + case WASM_INSN_F32_LOAD: + return b.id[CFREE_CG_BUILTIN_F32]; + case WASM_INSN_F64_LOAD: + return b.id[CFREE_CG_BUILTIN_F64]; default: return b.id[CFREE_CG_BUILTIN_I64]; } @@ -88,6 +95,10 @@ static CfreeCgTypeId wasm_store_storage_type(CfreeCgBuiltinTypes b, case WASM_INSN_I64_STORE32: case WASM_INSN_I32_STORE: return b.id[CFREE_CG_BUILTIN_I32]; + case WASM_INSN_F32_STORE: + return b.id[CFREE_CG_BUILTIN_F32]; + case WASM_INSN_F64_STORE: + return b.id[CFREE_CG_BUILTIN_F64]; default: return b.id[CFREE_CG_BUILTIN_I64]; } @@ -134,6 +145,40 @@ typedef struct WasmCgRuntime { uint64_t table_max_offset; uint64_t table_entry_fn_offset; uint64_t table_entry_typeidx_offset; + /* memory/func/global/table-indexed instance-record field numbers. Sized to + * the actual module counts (m->nmemories, m->nfuncs, m->nglobals, + * m->ntables) and arena-allocated in wasm_cg_build_runtime so there's no + * fixed cap on any of them. */ + uint32_t* memory_field; + uint32_t memory_data_field; + uint32_t memory_pages_field; + uint32_t memory_max_pages_field; + uint32_t memory_flags_field; + uint32_t* func_import_field; + uint32_t* func_ref_entry_field; + uint32_t* global_field; + uint32_t global_import_addr_field; + uint32_t* table_field; + uint32_t* table_entries_field; + uint32_t table_entry_fn_field; + uint32_t table_entry_typeidx_field; + uint32_t table_entries_ptr_field; + uint32_t table_len_field; + uint32_t table_max_field; + /* Bulk-memory passive segment storage. Each module data/elem segment with + * mode == WASM_SEG_PASSIVE has a slot in the instance; active segments also + * get a slot for uniform indexing (memory.init/table.init are validated to + * traps on dropped/active segment use; the slot's len is left zero for + * active segments so memory.init traps on non-zero copies). */ + CfreeCgTypeId passive_data_ty; + CfreeCgTypeId passive_elem_ty; + uint32_t* passive_data_field; + uint32_t passive_data_base_field; + uint32_t passive_data_len_field; + uint32_t* passive_elem_field; + uint32_t* passive_elem_storage_field; + uint32_t passive_elem_entries_field; + uint32_t passive_elem_length_field; CfreeCgTypeId trap_func_ty; CfreeCgSym trap_syms[WASM_TRAP_COUNT]; } WasmCgRuntime; @@ -215,13 +260,24 @@ static uint64_t wasm_cg_field_offset(CfreeCompiler* c, CfreeCgTypeId ty, } static void wasm_cg_build_runtime(CfreeCompiler* c, CfreeCgBuiltinTypes b, - const WasmModule* m, WasmCgRuntime* rt) { + const WasmModule* m, WasmCgRuntime* rt, + Arena* arena) { CfreeCgField memory_fields[4]; CfreeCgField func_import_fields[1]; CfreeCgField global_import_fields[1]; CfreeCgField table_entry_fields[2]; CfreeCgField table_fields[3]; - CfreeCgField instance_fields[256]; + CfreeCgField passive_data_fields[2]; + CfreeCgField passive_elem_fields[2]; + /* Total instance fields = nmemories + (#import funcs) + nfuncs (one + * 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; + CfreeCgField* instance_fields = + instance_cap ? arena_zarray(arena, CfreeCgField, instance_cap) : NULL; uint32_t nfields = 0; uint32_t memory_field_idx[64]; uint32_t func_import_field_idx[64]; @@ -230,6 +286,24 @@ static void wasm_cg_build_runtime(CfreeCompiler* c, CfreeCgBuiltinTypes b, uint32_t table_field_idx[64]; uint32_t table_entries_field_idx[64]; memset(rt, 0, sizeof *rt); + rt->memory_field = + m->nmemories ? arena_zarray(arena, uint32_t, m->nmemories) : NULL; + rt->func_import_field = + m->nfuncs ? arena_zarray(arena, uint32_t, m->nfuncs) : NULL; + rt->func_ref_entry_field = + m->nfuncs ? arena_zarray(arena, uint32_t, m->nfuncs) : NULL; + rt->global_field = + m->nglobals ? arena_zarray(arena, uint32_t, m->nglobals) : NULL; + rt->table_field = + m->ntables ? arena_zarray(arena, uint32_t, m->ntables) : NULL; + rt->table_entries_field = + m->ntables ? arena_zarray(arena, uint32_t, m->ntables) : NULL; + rt->passive_data_field = + m->ndata ? arena_zarray(arena, uint32_t, m->ndata) : NULL; + rt->passive_elem_field = + m->nelems ? arena_zarray(arena, uint32_t, m->nelems) : NULL; + rt->passive_elem_storage_field = + m->nelems ? arena_zarray(arena, uint32_t, m->nelems) : NULL; rt->i8_ptr_ty = cfree_cg_type_ptr(c, b.id[CFREE_CG_BUILTIN_I8], 0); rt->void_ptr_ty = rt->i8_ptr_ty; memset(memory_fields, 0, sizeof memory_fields); @@ -247,6 +321,10 @@ static void wasm_cg_build_runtime(CfreeCompiler* c, CfreeCgBuiltinTypes b, rt->memory_pages_offset = wasm_cg_field_offset(c, rt->memory_ty, 1); rt->memory_max_pages_offset = wasm_cg_field_offset(c, rt->memory_ty, 2); rt->memory_flags_offset = wasm_cg_field_offset(c, rt->memory_ty, 3); + rt->memory_data_field = 0; + rt->memory_pages_field = 1; + rt->memory_max_pages_field = 2; + rt->memory_flags_field = 3; memset(func_import_fields, 0, sizeof func_import_fields); func_import_fields[0].name = cfree_sym_intern(c, CFREE_SLICE_LIT("fn")); func_import_fields[0].type = rt->void_ptr_ty; @@ -272,6 +350,8 @@ static void wasm_cg_build_runtime(CfreeCompiler* c, CfreeCgBuiltinTypes b, rt->table_entry_typeidx_offset = wasm_cg_field_offset(c, rt->table_entry_ty, 1); rt->table_entry_size = cfree_cg_type_size(c, rt->table_entry_ty); + rt->table_entry_fn_field = 0; + rt->table_entry_typeidx_field = 1; memset(table_fields, 0, sizeof table_fields); table_fields[0].name = cfree_sym_intern(c, CFREE_SLICE_LIT("entries")); table_fields[0].type = rt->table_entry_ptr_ty; @@ -284,15 +364,36 @@ static void wasm_cg_build_runtime(CfreeCompiler* c, CfreeCgBuiltinTypes b, rt->table_entries_ptr_offset = wasm_cg_field_offset(c, rt->table_ty, 0); rt->table_len_offset = wasm_cg_field_offset(c, rt->table_ty, 1); rt->table_max_offset = wasm_cg_field_offset(c, rt->table_ty, 2); - memset(instance_fields, 0, sizeof instance_fields); + rt->table_entries_ptr_field = 0; + rt->table_len_field = 1; + rt->table_max_field = 2; + memset(passive_data_fields, 0, sizeof passive_data_fields); + passive_data_fields[0].name = cfree_sym_intern(c, CFREE_SLICE_LIT("base")); + passive_data_fields[0].type = rt->i8_ptr_ty; + passive_data_fields[1].name = cfree_sym_intern(c, CFREE_SLICE_LIT("len")); + passive_data_fields[1].type = b.id[CFREE_CG_BUILTIN_I64]; + rt->passive_data_ty = cfree_cg_type_record( + c, cfree_sym_intern(c, CFREE_SLICE_LIT("CfreeWasmPassiveDataSegment")), + passive_data_fields, 2); + rt->passive_data_base_field = 0; + rt->passive_data_len_field = 1; + memset(passive_elem_fields, 0, sizeof passive_elem_fields); + passive_elem_fields[0].name = cfree_sym_intern(c, CFREE_SLICE_LIT("entries")); + passive_elem_fields[0].type = rt->table_entry_ptr_ty; + passive_elem_fields[1].name = cfree_sym_intern(c, CFREE_SLICE_LIT("length")); + passive_elem_fields[1].type = b.id[CFREE_CG_BUILTIN_I32]; + rt->passive_elem_ty = cfree_cg_type_record( + c, cfree_sym_intern(c, CFREE_SLICE_LIT("CfreeWasmPassiveElemSegment")), + passive_elem_fields, 2); + rt->passive_elem_entries_field = 0; + rt->passive_elem_length_field = 1; for (uint32_t i = 0; i < m->nmemories; ++i) { char name[40]; - if (nfields >= 256u) - wasm_error(c, wasm_loc(0, 0), "wasm: instance layout too large"); if (i == 0) memcpy(name, "memory", sizeof "memory"); else wasm_indexed_name(name, sizeof name, "memory_", i); + rt->memory_field[i] = nfields; memory_field_idx[i] = nfields; instance_fields[nfields].name = cfree_sym_intern(c, cfree_slice_cstr(name)); instance_fields[nfields].type = rt->memory_ty; @@ -301,9 +402,8 @@ static void wasm_cg_build_runtime(CfreeCompiler* c, CfreeCgBuiltinTypes b, for (uint32_t i = 0; i < m->nfuncs; ++i) { char name[40]; if (!m->funcs[i].is_import) continue; - if (nfields >= 256u) - wasm_error(c, wasm_loc(0, 0), "wasm: instance layout too large"); wasm_indexed_name(name, sizeof name, "import_func_", i); + rt->func_import_field[i] = nfields; func_import_field_idx[i] = nfields; instance_fields[nfields].name = cfree_sym_intern(c, cfree_slice_cstr(name)); instance_fields[nfields].type = rt->func_import_ty; @@ -311,9 +411,8 @@ static void wasm_cg_build_runtime(CfreeCompiler* c, CfreeCgBuiltinTypes b, } for (uint32_t i = 0; i < m->nfuncs; ++i) { char name[40]; - if (nfields >= 256u) - wasm_error(c, wasm_loc(0, 0), "wasm: instance layout too large"); wasm_indexed_name(name, sizeof name, "func_ref_", i); + rt->func_ref_entry_field[i] = nfields; func_ref_entry_field_idx[i] = nfields; instance_fields[nfields].name = cfree_sym_intern(c, cfree_slice_cstr(name)); instance_fields[nfields].type = rt->table_entry_ty; @@ -321,11 +420,10 @@ static void wasm_cg_build_runtime(CfreeCompiler* c, CfreeCgBuiltinTypes b, } for (uint32_t i = 0; i < m->nglobals; ++i) { char name[32]; - if (nfields >= 256u) - wasm_error(c, wasm_loc(0, 0), "wasm: instance layout too large"); wasm_indexed_name(name, sizeof name, m->globals[i].is_import ? "import_global_" : "global_", i); + rt->global_field[i] = nfields; global_field_idx[i] = nfields; instance_fields[nfields].name = cfree_sym_intern(c, cfree_slice_cstr(name)); instance_fields[nfields].type = @@ -336,20 +434,43 @@ static void wasm_cg_build_runtime(CfreeCompiler* c, CfreeCgBuiltinTypes b, for (uint32_t i = 0; i < m->ntables; ++i) { char name[40]; uint32_t max = m->tables[i].has_max ? m->tables[i].max : m->tables[i].min; - if (nfields + 2u > 256u) - wasm_error(c, wasm_loc(0, 0), "wasm: instance layout too large"); wasm_indexed_name(name, sizeof name, "table_", i); + rt->table_field[i] = nfields; table_field_idx[i] = nfields; instance_fields[nfields].name = cfree_sym_intern(c, cfree_slice_cstr(name)); instance_fields[nfields].type = rt->table_ty; nfields++; wasm_indexed_name(name, sizeof name, "table_entries_", i); + rt->table_entries_field[i] = nfields; table_entries_field_idx[i] = nfields; instance_fields[nfields].name = cfree_sym_intern(c, cfree_slice_cstr(name)); instance_fields[nfields].type = cfree_cg_type_array(c, rt->table_entry_ty, max ? max : 1u); nfields++; } + for (uint32_t i = 0; i < m->ndata; ++i) { + char name[40]; + wasm_indexed_name(name, sizeof name, "passive_data_", i); + rt->passive_data_field[i] = nfields; + instance_fields[nfields].name = cfree_sym_intern(c, cfree_slice_cstr(name)); + instance_fields[nfields].type = rt->passive_data_ty; + nfields++; + } + for (uint32_t i = 0; i < m->nelems; ++i) { + char name[40]; + uint32_t nfuncs = m->elems[i].nfuncs; + wasm_indexed_name(name, sizeof name, "passive_elem_", i); + rt->passive_elem_field[i] = nfields; + instance_fields[nfields].name = cfree_sym_intern(c, cfree_slice_cstr(name)); + instance_fields[nfields].type = rt->passive_elem_ty; + nfields++; + wasm_indexed_name(name, sizeof name, "passive_elem_storage_", i); + rt->passive_elem_storage_field[i] = nfields; + instance_fields[nfields].name = cfree_sym_intern(c, cfree_slice_cstr(name)); + instance_fields[nfields].type = + cfree_cg_type_array(c, rt->table_entry_ty, nfuncs ? nfuncs : 1u); + nfields++; + } rt->instance_ty = cfree_cg_type_record( c, cfree_sym_intern(c, CFREE_SLICE_LIT("CfreeWasmInstance")), instance_fields, nfields); rt->instance_ptr_ty = cfree_cg_type_ptr(c, rt->instance_ty, 0); @@ -383,6 +504,11 @@ static void wasm_cg_push_instance_ptr(CfreeCg* cg, const WasmCgRuntime* rt, cfree_cg_load(cg, wasm_cg_mem_type(rt->instance_ptr_ty), ea); } +static void wasm_cg_push_instance_lvalue(CfreeCg* cg, const WasmCgRuntime* rt, + CfreeCgLocal instance_local) { + wasm_cg_push_instance_ptr(cg, rt, instance_local); +} + /* Add a constant byte offset to the pointer rvalue on TOS, retyping to * `result_ptr_ty`. No-op when offset == 0 (and the type is not retyped). */ static void wasm_cg_ptr_add_offset(CfreeCg* cg, CfreeCgBuiltinTypes b, @@ -411,6 +537,15 @@ static void wasm_cg_push_memory_data_ptr(CfreeCg* cg, const WasmCgRuntime* rt, cfree_cg_load(cg, wasm_cg_mem_type(rt->i8_ptr_ty), ea); } +static void wasm_cg_push_memory_pages_lvalue(CfreeCg* cg, + const WasmCgRuntime* rt, + CfreeCgLocal instance_local, + uint32_t memidx) { + wasm_cg_push_instance_lvalue(cg, rt, instance_local); + cfree_cg_field(cg, rt->memory_field[memidx]); + cfree_cg_field(cg, rt->memory_pages_field); +} + /* Load instance->import_funcs[func_index].fn as a void*. */ static void wasm_cg_push_import_func_ptr(CfreeCg* cg, const WasmCgRuntime* rt, CfreeCgLocal instance_local, @@ -457,6 +592,27 @@ static void wasm_cg_push_global_value_ptr(CfreeCompiler* c, CfreeCg* cg, } } +static void wasm_cg_push_table_lvalue(CfreeCg* cg, const WasmCgRuntime* rt, + CfreeCgLocal instance_local, + uint32_t table_index) { + wasm_cg_push_instance_lvalue(cg, rt, instance_local); + cfree_cg_field(cg, rt->table_field[table_index]); +} + +static void wasm_cg_push_table_entry_lvalue(CfreeCg* cg, + const WasmCgRuntime* rt, + CfreeCgLocal instance_local, + uint32_t table_index, + CfreeCgLocal index_local, + CfreeCgMemAccess index_mem) { + wasm_cg_push_table_lvalue(cg, rt, instance_local, table_index); + cfree_cg_field(cg, rt->table_entries_ptr_field); + cfree_cg_load(cg, wasm_cg_mem_type(rt->table_entry_ptr_ty), wasm_cg_ea0()); + cfree_cg_push_local(cg, index_local); + cfree_cg_load(cg, index_mem, wasm_cg_ea0()); + cfree_cg_index(cg, 0); +} + /* Push a pointer rvalue to instance->tables[table_index].entries[index_local]. * The index is loaded from a temp local supplied by the caller (mirrors the * previous helper's signature). */ @@ -486,6 +642,29 @@ static void wasm_cg_push_table_entry_ptr(CfreeCg* cg, CfreeCgBuiltinTypes b, cfree_cg_int_to_ptr(cg, rt->table_entry_ptr_ty); } +static void wasm_cg_push_passive_data_lvalue(CfreeCg* cg, + const WasmCgRuntime* rt, + CfreeCgLocal instance_local, + uint32_t dataidx) { + wasm_cg_push_instance_lvalue(cg, rt, instance_local); + cfree_cg_field(cg, rt->passive_data_field[dataidx]); +} + +static void wasm_cg_push_passive_elem_lvalue(CfreeCg* cg, + const WasmCgRuntime* rt, + CfreeCgLocal instance_local, + uint32_t elemidx) { + wasm_cg_push_instance_lvalue(cg, rt, instance_local); + cfree_cg_field(cg, rt->passive_elem_field[elemidx]); +} + +static void wasm_cg_push_passive_elem_storage_array_lvalue( + CfreeCg* cg, const WasmCgRuntime* rt, CfreeCgLocal instance_local, + uint32_t elemidx) { + wasm_cg_push_instance_lvalue(cg, rt, instance_local); + cfree_cg_field(cg, rt->passive_elem_storage_field[elemidx]); +} + static void wasm_cg_memory_check(CfreeCompiler* c, CfreeCg* cg, CfreeCgBuiltinTypes b, const WasmModule* m, const WasmCgRuntime* rt, @@ -815,14 +994,14 @@ static void wasm_cg_call_func(CfreeCompiler* c, CfreeCg* cg, const WasmCgRuntime* rt, CfreeCgSym sym, CfreeCgTypeId func_type, CfreeCgLocal instance_local, uint32_t func_index, - int must_tail) { + int must_tail, CfreeSrcLoc loc, Arena* arena) { CfreeCgLocalAttrs attrs; - CfreeCgLocal args[16]; + CfreeCgLocal* args = + f->nparams ? arena_array(arena, CfreeCgLocal, f->nparams) : NULL; CfreeCgLocal callee = CFREE_CG_LOCAL_NONE; + (void)loc; memset(&attrs, 0, sizeof attrs); attrs.flags = CFREE_CG_LOCAL_COMPILER_TEMP; - if (f->nparams > 16u) - wasm_error(c, wasm_loc(0, 0), "wasm: too many call arguments"); for (uint32_t p = 0; p < f->nparams; ++p) { uint32_t param = f->nparams - 1u - p; args[param] = @@ -870,6 +1049,605 @@ static void wasm_cg_call_func(CfreeCompiler* c, CfreeCg* cg, } } +/* Intern a NUL-terminated string as readonly const data and return its + * symbol. Each call mints a fresh symbol; the caller is responsible for + * deduplication if that matters. */ +static CfreeCgSym wasm_cg_intern_cstr(CfreeCg* cg, CfreeCgBuiltinTypes b, + const char* s) { + size_t len = 0; + while (s && s[len]) ++len; + return cfree_cg_const_data(cg, (const uint8_t*)(s ? s : ""), len + 1u, 1, + b.id[CFREE_CG_BUILTIN_I8]); +} + +/* Emit the three host-import metadata symbols read by + * cfree_wasm_bind_host_imports: __cfree_wasm_nimports (uint32), + * __cfree_wasm_imports (CfreeWasmImportDesc[]), and __cfree_wasm_types + * (CfreeWasmTypeDesc[]). 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 cfree_jit_lookup + * returning NULL. */ +static void wasm_cg_emit_host_import_metadata(CfreeCompiler* c, CfreeCg* cg, + CfreeCgBuiltinTypes b, + const WasmModule* m, + const WasmCgRuntime* rt, + Arena* arena) { + CfreeCgTypeId u32_ty = b.id[CFREE_CG_BUILTIN_I32]; + CfreeCgTypeId u8_ty = b.id[CFREE_CG_BUILTIN_I8]; + CfreeCgTypeId ptr_ty = rt->void_ptr_ty; + uint32_t ptr_size = (uint32_t)cfree_cg_type_size(c, ptr_ty); + uint32_t ptr_align = ptr_size ? ptr_size : 8u; + uint32_t nimports = 0; + /* The wire format of one CfreeWasmImportDesc 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; + CfreeCgSym nimports_sym, imports_sym, types_sym; + CfreeCgDecl decl; + CfreeCgDataDefAttrs data_attrs; + uint32_t i; + + for (i = 0; i < m->nfuncs; ++i) + if (m->funcs[i].is_import) nimports++; + + /* Always emit the count symbol so the binder can read it unconditionally. + * If zero, the descriptor/type arrays are omitted. */ + memset(&decl, 0, sizeof decl); + decl.kind = CFREE_CG_DECL_OBJECT; + decl.linkage_name = cfree_cg_c_linkage_name( + c, cfree_sym_intern(c, CFREE_SLICE_LIT("__cfree_wasm_nimports"))); + decl.display_name = decl.linkage_name; + decl.type = u32_ty; + decl.sym.bind = CFREE_SB_GLOBAL; + decl.as.object.flags = CFREE_CG_OBJ_READONLY; + nimports_sym = cfree_cg_decl(cg, decl); + if (!nimports_sym) + wasm_error(c, wasm_loc(0, 0), + "wasm: failed to declare __cfree_wasm_nimports"); + + memset(&data_attrs, 0, sizeof data_attrs); + data_attrs.flags = CFREE_CG_DATADEF_READONLY; + data_attrs.align = 4; + cfree_cg_data_begin(cg, nimports_sym, data_attrs); + cfree_cg_data_int(cg, (uint64_t)nimports, u32_ty); + cfree_cg_data_end(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 __cfree_wasm_types is no larger than + * needed. */ + typedef struct WasmImportEmit { + CfreeCgSym module_sym; + CfreeCgSym field_sym; + uint32_t local_typeidx; /* index into the emitted types array */ + uint32_t slot_offset; + } WasmImportEmit; + WasmImportEmit* descs = arena_zarray(arena, WasmImportEmit, nimports); + uint32_t* type_remap = arena_zarray(arena, uint32_t, m->ntypes ? m->ntypes : 1u); + uint32_t* local_to_module = arena_zarray(arena, uint32_t, nimports); + uint32_t ntypes = 0; + uint32_t d = 0; + for (i = 0; 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; + uint32_t module_typeidx; + if (!f->is_import) continue; + module_typeidx = f->typeidx; + if (module_typeidx >= m->ntypes) { + wasm_error(c, f->loc, "wasm: import typeidx out of range"); + return; + } + if (type_remap[module_typeidx] == UINT32_MAX) { + type_remap[module_typeidx] = ntypes; + local_to_module[ntypes] = module_typeidx; + ntypes++; + } + descs[d].module_sym = 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 (cfree_cg_type_record_field(c, rt->instance_ty, + rt->func_import_field[i], NULL, + &slot_off) != CFREE_OK) { + wasm_error(c, f->loc, "wasm: failed to query import slot offset"); + return; + } + if (slot_off > UINT32_MAX) { + wasm_error(c, f->loc, "wasm: import slot offset exceeds u32"); + return; + } + descs[d].slot_offset = (uint32_t)slot_off; + d++; + } + + /* __cfree_wasm_types: array of CfreeWasmTypeDesc. + * { 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. */ + { + CfreeCgSym* param_syms = arena_zarray(arena, CfreeCgSym, ntypes); + CfreeCgSym* result_syms = arena_zarray(arena, CfreeCgSym, ntypes); + uint32_t type_desc_size = 2u * ptr_size + 8u; + CfreeCgTypeId types_array_ty = cfree_cg_type_array( + c, u8_ty, (uint64_t)type_desc_size * (ntypes ? ntypes : 1u)); + for (uint32_t k = 0; k < ntypes; ++k) { + const WasmFuncType* t = &m->types[local_to_module[k]]; + uint8_t* pbuf = t->nparams ? arena_array(arena, uint8_t, t->nparams) + : NULL; + uint8_t* rbuf = t->nresults ? arena_array(arena, uint8_t, t->nresults) + : NULL; + for (uint32_t p = 0; p < t->nparams; ++p) pbuf[p] = (uint8_t)t->params[p]; + for (uint32_t r = 0; r < t->nresults; ++r) + rbuf[r] = (uint8_t)t->results[r]; + param_syms[k] = t->nparams + ? cfree_cg_const_data(cg, pbuf, t->nparams, 1, u8_ty) + : (CfreeCgSym)0; + result_syms[k] = t->nresults + ? cfree_cg_const_data(cg, rbuf, t->nresults, 1, u8_ty) + : (CfreeCgSym)0; + } + memset(&decl, 0, sizeof decl); + decl.kind = CFREE_CG_DECL_OBJECT; + decl.linkage_name = + cfree_cg_c_linkage_name(c, cfree_sym_intern(c, CFREE_SLICE_LIT("__cfree_wasm_types"))); + decl.display_name = decl.linkage_name; + decl.type = types_array_ty; + decl.sym.bind = CFREE_SB_GLOBAL; + decl.as.object.flags = CFREE_CG_OBJ_READONLY; + types_sym = cfree_cg_decl(cg, decl); + if (!types_sym) + wasm_error(c, wasm_loc(0, 0), + "wasm: failed to declare __cfree_wasm_types"); + memset(&data_attrs, 0, sizeof data_attrs); + data_attrs.flags = CFREE_CG_DATADEF_READONLY; + data_attrs.align = ptr_align; + cfree_cg_data_begin(cg, types_sym, data_attrs); + for (uint32_t k = 0; k < ntypes; ++k) { + const WasmFuncType* t = &m->types[local_to_module[k]]; + cfree_cg_data_align(cg, ptr_align); + if (param_syms[k]) + cfree_cg_data_addr(cg, param_syms[k], 0, ptr_size, 0); + else + cfree_cg_data_pad(cg, ptr_size, 0); + cfree_cg_data_int(cg, (uint64_t)t->nparams, u32_ty); + /* Pad up to ptr alignment before next pointer field. */ + if (ptr_align > 4) cfree_cg_data_align(cg, ptr_align); + if (result_syms[k]) + cfree_cg_data_addr(cg, result_syms[k], 0, ptr_size, 0); + else + cfree_cg_data_pad(cg, ptr_size, 0); + cfree_cg_data_int(cg, (uint64_t)t->nresults, u32_ty); + if (ptr_align > 4) cfree_cg_data_align(cg, ptr_align); + } + cfree_cg_data_end(cg); + } + + /* __cfree_wasm_imports: array of CfreeWasmImportDesc. */ + { + CfreeCgTypeId imports_array_ty = cfree_cg_type_array( + c, u8_ty, (uint64_t)desc_size * nimports); + memset(&decl, 0, sizeof decl); + decl.kind = CFREE_CG_DECL_OBJECT; + decl.linkage_name = + cfree_cg_c_linkage_name(c, cfree_sym_intern(c, CFREE_SLICE_LIT("__cfree_wasm_imports"))); + decl.display_name = decl.linkage_name; + decl.type = imports_array_ty; + decl.sym.bind = CFREE_SB_GLOBAL; + decl.as.object.flags = CFREE_CG_OBJ_READONLY; + imports_sym = cfree_cg_decl(cg, decl); + if (!imports_sym) + wasm_error(c, wasm_loc(0, 0), + "wasm: failed to declare __cfree_wasm_imports"); + memset(&data_attrs, 0, sizeof data_attrs); + data_attrs.flags = CFREE_CG_DATADEF_READONLY; + data_attrs.align = ptr_align; + cfree_cg_data_begin(cg, imports_sym, data_attrs); + for (uint32_t k = 0; k < nimports; ++k) { + cfree_cg_data_align(cg, ptr_align); + cfree_cg_data_addr(cg, descs[k].module_sym, 0, ptr_size, 0); + cfree_cg_data_addr(cg, descs[k].field_sym, 0, ptr_size, 0); + cfree_cg_data_int(cg, (uint64_t)descs[k].local_typeidx, u32_ty); + cfree_cg_data_int(cg, (uint64_t)descs[k].slot_offset, u32_ty); + if (ptr_align > 4) cfree_cg_data_align(cg, ptr_align); + } + cfree_cg_data_end(cg); + } + (void)types_sym; +} + +/* Bounds-check that addr + n <= size. addr/n/size are all treated as i64 + * (caller zero-extends i32 inputs first). Traps on overflow or out-of-range + * via the rt bounds trap; falls through on success. */ +static void wasm_cg_bulk_bounds_check(CfreeCg* cg, CfreeCgBuiltinTypes b, + const WasmCgRuntime* rt, + CfreeCgLocal addr_local, + CfreeCgLocal n_local, + CfreeCgLocal size_local) { + CfreeCgMemAccess i64_mem = wasm_cg_mem_type(b.id[CFREE_CG_BUILTIN_I64]); + CfreeCgLabel ok = cfree_cg_label_new(cg); + /* size - addr >= n (unsigned), equivalently addr + n <= size with no + * overflow. First check size >= addr. */ + CfreeCgLabel addr_ok = cfree_cg_label_new(cg); + cfree_cg_push_local(cg, size_local); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, addr_local); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_int_cmp(cg, CFREE_CG_INT_GE_U); + cfree_cg_branch_true(cg, addr_ok); + wasm_cg_trap_bounds(cg, rt); + cfree_cg_label_place(cg, addr_ok); + cfree_cg_push_local(cg, size_local); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, addr_local); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_int_binop(cg, CFREE_CG_INT_SUB, 0); + cfree_cg_push_local(cg, n_local); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_int_cmp(cg, CFREE_CG_INT_GE_U); + cfree_cg_branch_true(cg, ok); + wasm_cg_trap_bounds(cg, rt); + cfree_cg_label_place(cg, ok); +} + +/* Emit a byte-wise copy loop: copies n_local bytes from src_base+src_addr + * to dst_base+dst_addr. Handles overlap by choosing direction based on + * pointer comparison. Locals are pre-stored i64 values; src_base and + * dst_base are i8* values pre-stored in locals. */ +static void wasm_cg_emit_byte_copy_loop(CfreeCg* cg, CfreeCgBuiltinTypes b, + const WasmCgRuntime* rt, + CfreeCgLocal dst_base_local, + CfreeCgLocal src_base_local, + CfreeCgLocal dst_addr_local, + CfreeCgLocal src_addr_local, + CfreeCgLocal n_local) { + CfreeCgMemAccess i64_mem = wasm_cg_mem_type(b.id[CFREE_CG_BUILTIN_I64]); + CfreeCgMemAccess i8_mem = wasm_cg_mem_type(b.id[CFREE_CG_BUILTIN_I8]); + CfreeCgMemAccess ptr_mem = wasm_cg_mem_type(rt->i8_ptr_ty); + CfreeCgLocalAttrs attrs; + CfreeCgLocal idx, dir; + CfreeCgLabel done = cfree_cg_label_new(cg); + CfreeCgLabel loop_start = cfree_cg_label_new(cg); + CfreeCgLabel forward = cfree_cg_label_new(cg); + CfreeCgLabel after_dir = cfree_cg_label_new(cg); + memset(&attrs, 0, sizeof attrs); + attrs.flags = CFREE_CG_LOCAL_COMPILER_TEMP; + idx = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I64], attrs); + dir = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I32], attrs); + (void)rt; + (void)ptr_mem; + /* If n == 0, nothing to do. */ + cfree_cg_push_local(cg, n_local); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_int(cg, 0, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_int_cmp(cg, CFREE_CG_INT_EQ); + cfree_cg_branch_true(cg, done); + + /* Choose direction: forward if dst_addr <= src_addr OR if base pointers + * differ. Same-base + dst > src needs backward copy to handle overlap. + * We approximate with: dir = (dst_base == src_base && dst_addr > src_addr) + * ? backward : forward. */ + cfree_cg_push_local(cg, dir); + cfree_cg_push_local(cg, dst_base_local); + cfree_cg_load(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, src_base_local); + cfree_cg_load(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_int_cmp(cg, CFREE_CG_INT_EQ); + cfree_cg_store(cg, wasm_cg_mem_type(b.id[CFREE_CG_BUILTIN_I32]), wasm_cg_ea0()); + cfree_cg_push_local(cg, dir); + cfree_cg_load(cg, wasm_cg_mem_type(b.id[CFREE_CG_BUILTIN_I32]), wasm_cg_ea0()); + cfree_cg_branch_false(cg, forward); + cfree_cg_push_local(cg, dst_addr_local); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, src_addr_local); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_int_cmp(cg, CFREE_CG_INT_GT_U); + cfree_cg_branch_false(cg, forward); + /* Backward loop: idx = n - 1, while idx >= 0 then idx-- (stop at 0). */ + cfree_cg_push_local(cg, idx); + cfree_cg_push_local(cg, n_local); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + { + CfreeCgLabel back_loop = cfree_cg_label_new(cg); + cfree_cg_label_place(cg, back_loop); + cfree_cg_push_local(cg, idx); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_int(cg, 0, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_int_cmp(cg, CFREE_CG_INT_EQ); + cfree_cg_branch_true(cg, done); + /* idx-- */ + cfree_cg_push_local(cg, idx); + cfree_cg_push_local(cg, idx); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_int(cg, 1, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_int_binop(cg, CFREE_CG_INT_SUB, 0); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + /* dst_base[dst_addr + idx] = src_base[src_addr + idx] */ + cfree_cg_push_local(cg, dst_base_local); + cfree_cg_load(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, dst_addr_local); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, idx); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_int_binop(cg, CFREE_CG_INT_ADD, 0); + cfree_cg_index(cg, 0); + cfree_cg_push_local(cg, src_base_local); + cfree_cg_load(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, src_addr_local); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, idx); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_int_binop(cg, CFREE_CG_INT_ADD, 0); + cfree_cg_index(cg, 0); + cfree_cg_load(cg, i8_mem, wasm_cg_ea0()); + cfree_cg_store(cg, i8_mem, wasm_cg_ea0()); + cfree_cg_jump(cg, back_loop); + } + cfree_cg_label_place(cg, forward); + /* Forward loop: idx = 0; while idx < n then dst[idx]=src[idx]; idx++. */ + cfree_cg_push_local(cg, idx); + cfree_cg_push_int(cg, 0, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_jump(cg, after_dir); + cfree_cg_label_place(cg, after_dir); + cfree_cg_label_place(cg, loop_start); + cfree_cg_push_local(cg, idx); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, n_local); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_int_cmp(cg, CFREE_CG_INT_GE_U); + cfree_cg_branch_true(cg, done); + cfree_cg_push_local(cg, dst_base_local); + cfree_cg_load(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, dst_addr_local); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, idx); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_int_binop(cg, CFREE_CG_INT_ADD, 0); + cfree_cg_index(cg, 0); + cfree_cg_push_local(cg, src_base_local); + cfree_cg_load(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, src_addr_local); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, idx); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_int_binop(cg, CFREE_CG_INT_ADD, 0); + cfree_cg_index(cg, 0); + cfree_cg_load(cg, i8_mem, wasm_cg_ea0()); + cfree_cg_store(cg, i8_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, idx); + cfree_cg_push_local(cg, idx); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_int(cg, 1, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_int_binop(cg, CFREE_CG_INT_ADD, 0); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_jump(cg, loop_start); + cfree_cg_label_place(cg, done); +} + +/* Emit a byte-fill loop: writes (val & 0xff) into dst_base+dst_addr..+n. */ +static void wasm_cg_emit_byte_fill_loop(CfreeCg* cg, CfreeCgBuiltinTypes b, + const WasmCgRuntime* rt, + CfreeCgLocal dst_base_local, + CfreeCgLocal dst_addr_local, + CfreeCgLocal val_local, + CfreeCgLocal n_local) { + CfreeCgMemAccess i64_mem = wasm_cg_mem_type(b.id[CFREE_CG_BUILTIN_I64]); + CfreeCgMemAccess i32_mem = wasm_cg_mem_type(b.id[CFREE_CG_BUILTIN_I32]); + CfreeCgMemAccess i8_mem = wasm_cg_mem_type(b.id[CFREE_CG_BUILTIN_I8]); + CfreeCgMemAccess ptr_mem = wasm_cg_mem_type(rt->i8_ptr_ty); + CfreeCgLocalAttrs attrs; + CfreeCgLocal idx; + CfreeCgLabel done = cfree_cg_label_new(cg); + CfreeCgLabel loop_start = cfree_cg_label_new(cg); + (void)rt; + memset(&attrs, 0, sizeof attrs); + attrs.flags = CFREE_CG_LOCAL_COMPILER_TEMP; + idx = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I64], attrs); + cfree_cg_push_local(cg, idx); + cfree_cg_push_int(cg, 0, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_label_place(cg, loop_start); + cfree_cg_push_local(cg, idx); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, n_local); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_int_cmp(cg, CFREE_CG_INT_GE_U); + cfree_cg_branch_true(cg, done); + cfree_cg_push_local(cg, dst_base_local); + cfree_cg_load(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, dst_addr_local); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, idx); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_int_binop(cg, CFREE_CG_INT_ADD, 0); + cfree_cg_index(cg, 0); + cfree_cg_push_local(cg, val_local); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_trunc(cg, b.id[CFREE_CG_BUILTIN_I8]); + cfree_cg_store(cg, i8_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, idx); + cfree_cg_push_local(cg, idx); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_int(cg, 1, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_int_binop(cg, CFREE_CG_INT_ADD, 0); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_jump(cg, loop_start); + cfree_cg_label_place(cg, done); +} + +/* Like wasm_cg_emit_byte_copy_loop but for table entries (struct-sized). */ +static void wasm_cg_emit_table_copy_loop(CfreeCompiler* c, CfreeCg* cg, + CfreeCgBuiltinTypes b, + const WasmCgRuntime* rt, + CfreeCgLocal dst_base_local, + CfreeCgLocal src_base_local, + CfreeCgLocal dst_idx_local, + CfreeCgLocal src_idx_local, + CfreeCgLocal n_local) { + CfreeCgMemAccess i64_mem = wasm_cg_mem_type(b.id[CFREE_CG_BUILTIN_I64]); + CfreeCgMemAccess ptr_mem = wasm_cg_mem_type(rt->table_entry_ptr_ty); + CfreeCgMemAccess fn_mem = wasm_cg_mem_type(rt->void_ptr_ty); + CfreeCgMemAccess i32_mem = wasm_cg_mem_type(b.id[CFREE_CG_BUILTIN_I32]); + CfreeCgLocalAttrs attrs; + CfreeCgLocal idx, dir; + CfreeCgLabel done = cfree_cg_label_new(cg); + CfreeCgLabel forward = cfree_cg_label_new(cg); + CfreeCgLabel loop_start = cfree_cg_label_new(cg); + (void)c; + memset(&attrs, 0, sizeof attrs); + attrs.flags = CFREE_CG_LOCAL_COMPILER_TEMP; + idx = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I64], attrs); + dir = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I32], attrs); + cfree_cg_push_local(cg, n_local); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_int(cg, 0, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_int_cmp(cg, CFREE_CG_INT_EQ); + cfree_cg_branch_true(cg, done); + /* dir = (dst_base == src_base) ? (dst_idx > src_idx) : 0 */ + cfree_cg_push_local(cg, dir); + cfree_cg_push_local(cg, dst_base_local); + cfree_cg_load(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, src_base_local); + cfree_cg_load(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_int_cmp(cg, CFREE_CG_INT_EQ); + cfree_cg_store(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, dir); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_branch_false(cg, forward); + cfree_cg_push_local(cg, dst_idx_local); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, src_idx_local); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_int_cmp(cg, CFREE_CG_INT_GT_U); + cfree_cg_branch_false(cg, forward); + { + CfreeCgLabel back_loop = cfree_cg_label_new(cg); + cfree_cg_push_local(cg, idx); + cfree_cg_push_local(cg, n_local); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_label_place(cg, back_loop); + cfree_cg_push_local(cg, idx); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_int(cg, 0, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_int_cmp(cg, CFREE_CG_INT_EQ); + cfree_cg_branch_true(cg, done); + cfree_cg_push_local(cg, idx); + cfree_cg_push_local(cg, idx); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_int(cg, 1, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_int_binop(cg, CFREE_CG_INT_SUB, 0); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + /* copy fn and typeidx fields */ + cfree_cg_push_local(cg, dst_base_local); + cfree_cg_load(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, dst_idx_local); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, idx); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_int_binop(cg, CFREE_CG_INT_ADD, 0); + cfree_cg_index(cg, 0); + cfree_cg_field(cg, rt->table_entry_fn_field); + cfree_cg_push_local(cg, src_base_local); + cfree_cg_load(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, src_idx_local); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, idx); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_int_binop(cg, CFREE_CG_INT_ADD, 0); + cfree_cg_index(cg, 0); + cfree_cg_field(cg, rt->table_entry_fn_field); + cfree_cg_load(cg, fn_mem, wasm_cg_ea0()); + cfree_cg_store(cg, fn_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, dst_base_local); + cfree_cg_load(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, dst_idx_local); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, idx); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_int_binop(cg, CFREE_CG_INT_ADD, 0); + cfree_cg_index(cg, 0); + cfree_cg_field(cg, rt->table_entry_typeidx_field); + cfree_cg_push_local(cg, src_base_local); + cfree_cg_load(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, src_idx_local); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, idx); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_int_binop(cg, CFREE_CG_INT_ADD, 0); + cfree_cg_index(cg, 0); + cfree_cg_field(cg, rt->table_entry_typeidx_field); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_store(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_jump(cg, back_loop); + } + cfree_cg_label_place(cg, forward); + cfree_cg_push_local(cg, idx); + cfree_cg_push_int(cg, 0, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_label_place(cg, loop_start); + cfree_cg_push_local(cg, idx); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, n_local); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_int_cmp(cg, CFREE_CG_INT_GE_U); + cfree_cg_branch_true(cg, done); + cfree_cg_push_local(cg, dst_base_local); + cfree_cg_load(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, dst_idx_local); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, idx); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_int_binop(cg, CFREE_CG_INT_ADD, 0); + cfree_cg_index(cg, 0); + cfree_cg_field(cg, rt->table_entry_fn_field); + cfree_cg_push_local(cg, src_base_local); + cfree_cg_load(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, src_idx_local); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, idx); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_int_binop(cg, CFREE_CG_INT_ADD, 0); + cfree_cg_index(cg, 0); + cfree_cg_field(cg, rt->table_entry_fn_field); + cfree_cg_load(cg, fn_mem, wasm_cg_ea0()); + cfree_cg_store(cg, fn_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, dst_base_local); + cfree_cg_load(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, dst_idx_local); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, idx); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_int_binop(cg, CFREE_CG_INT_ADD, 0); + cfree_cg_index(cg, 0); + cfree_cg_field(cg, rt->table_entry_typeidx_field); + cfree_cg_push_local(cg, src_base_local); + cfree_cg_load(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, src_idx_local); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, idx); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_int_binop(cg, CFREE_CG_INT_ADD, 0); + cfree_cg_index(cg, 0); + cfree_cg_field(cg, rt->table_entry_typeidx_field); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_store(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, idx); + cfree_cg_push_local(cg, idx); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_int(cg, 1, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_int_binop(cg, CFREE_CG_INT_ADD, 0); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_jump(cg, loop_start); + cfree_cg_label_place(cg, done); +} + void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, CfreeObjBuilder* out, const WasmModule* m) { CfreeCg* cg = NULL; @@ -879,19 +1657,20 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, wasm_error(c, wasm_loc(0, 0), "wasm: failed to initialize codegen"); CfreeCgBuiltinTypes b = cfree_cg_builtin_types(c); WasmCgRuntime rt; - CfreeCgSym syms[64]; + /* Arena owns transient frontend-side codegen state — sym tables, func + * types, per-function local arrays, instance-record field tables, call + * argument arrays. Lives for the duration of wasm_emit_cg; no fixed cap + * on functions, params, locals, or instance fields. */ + Arena arena; CfreeCgSym init_sym = CFREE_CG_SYM_NONE; - CfreeCgTypeId func_types[64]; - CfreeCgLocal locals[48]; uint32_t i, j; + arena_init(&arena, (Heap*)cfree_compiler_context(c)->heap, 8192); if (!cg) wasm_error(c, wasm_loc(0, 0), "wasm: failed to initialize codegen"); - if (m->nfuncs > 64u) - wasm_error(c, wasm_loc(0, 0), "wasm: too many functions"); - if (m->nglobals > 64u) - wasm_error(c, wasm_loc(0, 0), "wasm: too many globals"); - wasm_cg_build_runtime(c, b, m, &rt); - memset(syms, 0, sizeof syms); - memset(func_types, 0, sizeof func_types); + CfreeCgSym* syms = m->nfuncs ? arena_zarray(&arena, CfreeCgSym, m->nfuncs) + : NULL; + CfreeCgTypeId* func_types = + m->nfuncs ? arena_zarray(&arena, CfreeCgTypeId, m->nfuncs) : NULL; + wasm_cg_build_runtime(c, b, m, &rt, &arena); { CfreeCgFuncSig sig; CfreeCgDecl decl; @@ -937,15 +1716,15 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, } for (i = 0; i < m->nfuncs; ++i) { const WasmFunc* f = &m->funcs[i]; - CfreeCgFuncParam cg_params[17]; + CfreeCgFuncParam* cg_params = + arena_zarray(&arena, CfreeCgFuncParam, f->nparams + 1u); CfreeCgFuncSig sig; CfreeCgDecl decl; char local_name[40]; CfreeSym source_name; - memset(&cg_params[0], 0, sizeof cg_params[0]); + cfree_cg_set_loc(cg, f->loc); cg_params[0].type = rt.instance_ptr_ty; for (j = 0; j < f->nparams; ++j) { - memset(&cg_params[j + 1u], 0, sizeof cg_params[j + 1u]); cg_params[j + 1u].type = wasm_cg_type(c, b, f->params[j]); } memset(&sig, 0, sizeof sig); @@ -956,13 +1735,13 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, sig.call_conv = CFREE_CG_CC_TARGET_C; func_types[i] = cfree_cg_type_func(c, sig); if (!func_types[i]) - wasm_error(c, wasm_loc(0, 0), "wasm: failed to create function type"); + wasm_error(c, f->loc, "wasm: failed to create function type"); if (f->is_import) { syms[i] = CFREE_CG_SYM_NONE; continue; } if (f->export_name) { - source_name = f->export_name; + source_name = cfree_sym_intern(c, cfree_slice_cstr(f->export_name)); } else { size_t pos = 0; const char prefix[] = "__cfree_wasm_func_"; @@ -985,7 +1764,7 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, decl.sym.bind = f->export_name ? CFREE_SB_GLOBAL : CFREE_SB_LOCAL; syms[i] = cfree_cg_decl(cg, decl); if (!syms[i]) - wasm_error(c, wasm_loc(0, 0), "wasm: failed to declare function"); + wasm_error(c, f->loc, "wasm: failed to declare function"); } for (uint32_t k = 0; k < WASM_TRAP_COUNT; ++k) { cfree_cg_func_begin(cg, rt.trap_syms[k]); @@ -1016,14 +1795,51 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, wasm_cg_push_instance_ptr(cg, &rt, instance_local); cfree_cg_push_int(cg, flags, b.id[CFREE_CG_BUILTIN_I32]); cfree_cg_store(cg, wasm_cg_mem(c, b, WASM_VAL_I32), ea); - if (mem->data_init_len) { + /* For each active data segment targeting this memory, memcpy its + * bytes into the linear memory at the segment's offset. Passive + * segments are not initialized here — they're consumed by an + * explicit `memory.init` instruction at runtime. */ + for (uint32_t di = 0; di < m->ndata; ++di) { + const WasmDataSegment* d = &m->data[di]; + if (d->mode != WASM_SEG_ACTIVE) continue; + if (d->memidx != i) continue; + if (!d->nbytes) continue; + { + CfreeCgSym data_sym = cfree_cg_const_data( + cg, d->bytes, d->nbytes, 16, b.id[CFREE_CG_BUILTIN_I8]); + CfreeCgMemAccess byte_mem = wasm_cg_mem_type(b.id[CFREE_CG_BUILTIN_I8]); + wasm_cg_push_memory_data_ptr(cg, &rt, instance_local, i); + if (d->offset) + wasm_cg_ptr_add_offset(cg, b, d->offset, rt.i8_ptr_ty); + cfree_cg_push_symbol_addr(cg, data_sym, 0); + cfree_cg_memcpy(cg, d->nbytes, byte_mem, byte_mem); + } + } + } + /* Populate per-module passive data segment slots. Passive segments + * intern their bytes as a const data symbol and store {base, len} + * into the instance slot; memory.init/data.drop read/clear this slot. + * Active segments leave their slot at zero-len (they're effectively + * dropped after init), which makes memory.init on an active index + * trap on any non-zero copy length. */ + for (uint32_t di = 0; di < m->ndata; ++di) { + const WasmDataSegment* d = &m->data[di]; + wasm_cg_push_passive_data_lvalue(cg, &rt, instance_local, di); + cfree_cg_field(cg, rt.passive_data_base_field); + if (d->mode == WASM_SEG_PASSIVE && d->nbytes) { CfreeCgSym data_sym = cfree_cg_const_data( - cg, mem->data, mem->data_init_len, 16, b.id[CFREE_CG_BUILTIN_I8]); - CfreeCgMemAccess byte_mem = wasm_cg_mem_type(b.id[CFREE_CG_BUILTIN_I8]); - wasm_cg_push_memory_data_ptr(cg, &rt, instance_local, i); + cg, d->bytes, d->nbytes, 16, b.id[CFREE_CG_BUILTIN_I8]); cfree_cg_push_symbol_addr(cg, data_sym, 0); - cfree_cg_memcpy(cg, m->memories[i].data_init_len, byte_mem, byte_mem); + } else { + cfree_cg_push_null(cg, rt.i8_ptr_ty); } + cfree_cg_store(cg, wasm_cg_mem_type(rt.i8_ptr_ty), wasm_cg_ea0()); + wasm_cg_push_passive_data_lvalue(cg, &rt, instance_local, di); + cfree_cg_field(cg, rt.passive_data_len_field); + cfree_cg_push_int(cg, + (d->mode == WASM_SEG_PASSIVE) ? d->nbytes : 0u, + b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_store(cg, wasm_cg_mem(c, b, WASM_VAL_I64), wasm_cg_ea0()); } for (i = 0; i < m->nfuncs; ++i) { CfreeCgEffAddr ea; @@ -1068,8 +1884,8 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, } for (i = 0; i < m->nelems; ++i) { const WasmElemSegment* seg = &m->elems[i]; + int is_passive = (seg->mode == WASM_SEG_PASSIVE); for (j = 0; j < seg->nfuncs; ++j) { - uint32_t slot = (uint32_t)(seg->offset + j); uint32_t funcidx = seg->funcs[j]; CfreeCgLocalAttrs tmp_attrs; CfreeCgLocal slot_local; @@ -1077,35 +1893,72 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, tmp_attrs.flags = CFREE_CG_LOCAL_COMPILER_TEMP; slot_local = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I32], tmp_attrs); cfree_cg_push_local(cg, slot_local); - cfree_cg_push_int(cg, slot, b.id[CFREE_CG_BUILTIN_I32]); + if (is_passive) { + cfree_cg_push_int(cg, j, b.id[CFREE_CG_BUILTIN_I32]); + } else { + cfree_cg_push_int(cg, (uint32_t)(seg->offset + j), + b.id[CFREE_CG_BUILTIN_I32]); + } cfree_cg_store(cg, wasm_cg_mem(c, b, WASM_VAL_I32), wasm_cg_ea0()); - wasm_cg_push_table_entry_ptr(cg, b, &rt, instance_local, seg->tableidx, - slot_local, - wasm_cg_mem(c, b, WASM_VAL_I32)); + /* Resolve the target table-entry lvalue: active segments write into + * the module's table; passive segments write into the per-segment + * inline storage array so the bytes survive instantiation for + * later table.init. */ + if (is_passive) { + wasm_cg_push_passive_elem_storage_array_lvalue(cg, &rt, + instance_local, i); + cfree_cg_push_local(cg, slot_local); + cfree_cg_load(cg, wasm_cg_mem(c, b, WASM_VAL_I32), wasm_cg_ea0()); + cfree_cg_index(cg, 0); + } else { + wasm_cg_push_table_entry_lvalue(cg, &rt, instance_local, + seg->tableidx, slot_local, + wasm_cg_mem(c, b, WASM_VAL_I32)); + } + cfree_cg_field(cg, rt.table_entry_fn_field); if (m->funcs[funcidx].is_import) { wasm_cg_push_import_func_ptr(cg, &rt, instance_local, funcidx); } else { cfree_cg_push_symbol_addr(cg, syms[funcidx], 0); cfree_cg_bitcast(cg, rt.void_ptr_ty); } - { - CfreeCgEffAddr ea; - ea.scale = 0; - ea.offset = (int64_t)rt.table_entry_fn_offset; - cfree_cg_store(cg, wasm_cg_mem_type(rt.void_ptr_ty), ea); + cfree_cg_store(cg, wasm_cg_mem_type(rt.void_ptr_ty), wasm_cg_ea0()); + if (is_passive) { + wasm_cg_push_passive_elem_storage_array_lvalue(cg, &rt, + instance_local, i); + cfree_cg_push_local(cg, slot_local); + cfree_cg_load(cg, wasm_cg_mem(c, b, WASM_VAL_I32), wasm_cg_ea0()); + cfree_cg_index(cg, 0); + } else { + wasm_cg_push_table_entry_lvalue(cg, &rt, instance_local, + seg->tableidx, slot_local, + wasm_cg_mem(c, b, WASM_VAL_I32)); } - wasm_cg_push_table_entry_ptr(cg, b, &rt, instance_local, seg->tableidx, - slot_local, - wasm_cg_mem(c, b, WASM_VAL_I32)); + cfree_cg_field(cg, rt.table_entry_typeidx_field); cfree_cg_push_int(cg, m->funcs[funcidx].typeidx, b.id[CFREE_CG_BUILTIN_I32]); - { - CfreeCgEffAddr ea; - ea.scale = 0; - ea.offset = (int64_t)rt.table_entry_typeidx_offset; - cfree_cg_store(cg, wasm_cg_mem(c, b, WASM_VAL_I32), ea); - } + cfree_cg_store(cg, wasm_cg_mem(c, b, WASM_VAL_I32), wasm_cg_ea0()); + } + /* Initialize the passive_elem descriptor (entries pointer + length). + * Active segments get a zero-length descriptor so table.init on an + * active index traps on any non-zero copy. */ + wasm_cg_push_passive_elem_lvalue(cg, &rt, instance_local, i); + cfree_cg_field(cg, rt.passive_elem_entries_field); + if (is_passive && seg->nfuncs) { + wasm_cg_push_passive_elem_storage_array_lvalue(cg, &rt, instance_local, + i); + cfree_cg_push_int(cg, 0, b.id[CFREE_CG_BUILTIN_I32]); + cfree_cg_index(cg, 0); + cfree_cg_addr(cg); + } else { + cfree_cg_push_null(cg, rt.table_entry_ptr_ty); } + cfree_cg_store(cg, wasm_cg_mem_type(rt.table_entry_ptr_ty), wasm_cg_ea0()); + wasm_cg_push_passive_elem_lvalue(cg, &rt, instance_local, i); + cfree_cg_field(cg, rt.passive_elem_length_field); + cfree_cg_push_int(cg, is_passive ? seg->nfuncs : 0u, + b.id[CFREE_CG_BUILTIN_I32]); + cfree_cg_store(cg, wasm_cg_mem(c, b, WASM_VAL_I32), wasm_cg_ea0()); } for (i = 0; i < m->nglobals; ++i) { const WasmGlobal* g = &m->globals[i]; @@ -1124,22 +1977,46 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, if (m->has_start) wasm_cg_call_func(c, cg, b, &m->funcs[m->start_func], &rt, syms[m->start_func], func_types[m->start_func], - instance_local, m->start_func, 0); + instance_local, m->start_func, 0, + m->start_field_loc, &arena); cfree_cg_ret_void(cg); cfree_cg_func_end(cg); } + /* Host-import resolution metadata: emit __cfree_wasm_nimports always, and + * __cfree_wasm_imports / __cfree_wasm_types when the module declares at + * least one function import. Read by cfree_wasm_bind_host_imports. */ + wasm_cg_emit_host_import_metadata(c, cg, b, m, &rt, &arena); for (i = 0; i < m->nfuncs; ++i) { const WasmFunc* f = &m->funcs[i]; - struct { + typedef struct WasmCgControl { uint8_t kind; int seen_else; CfreeCgLabel start; CfreeCgLabel end; CfreeCgLabel else_label; - } control[64]; + } WasmCgControl; + /* Per-function arena-allocated locals and control stack. locals is + * sized to nparams + nlocals; the control stack starts at 16 entries + * and doubles in-place on overflow (arena pointers are stable, so + * we use a side heap realloc instead). */ + CfreeCgLocal* locals = + (f->nparams + f->nlocals) + ? arena_array(&arena, CfreeCgLocal, f->nparams + f->nlocals) + : NULL; + Heap* heap = (Heap*)cfree_compiler_context(c)->heap; + uint32_t control_cap = 16u; + WasmCgControl* control = + (WasmCgControl*)heap->alloc(heap, sizeof(WasmCgControl) * control_cap, + _Alignof(WasmCgControl)); uint32_t ncontrol = 0; CfreeCgLocal instance_local; - if (f->is_import) continue; + if (!control) + wasm_error(c, f->loc, "wasm: out of memory"); + if (f->is_import) { + heap->free(heap, control, sizeof(WasmCgControl) * control_cap); + continue; + } + cfree_cg_set_loc(cg, f->loc); cfree_cg_func_begin(cg, syms[i]); { CfreeCgLocalAttrs attrs; @@ -1164,6 +2041,7 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, } for (j = 0; j < f->ninsns; ++j) { WasmInsn in = f->insns[j]; + cfree_cg_set_loc(cg, in.loc); switch (in.kind) { case WASM_INSN_UNREACHABLE: wasm_cg_trap_unreachable(cg, &rt); @@ -1171,16 +2049,32 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, case WASM_INSN_NOP: break; case WASM_INSN_BLOCK: - if (ncontrol >= 64u) - wasm_error(c, wasm_loc(0, 0), "wasm: control stack too deep"); + if (ncontrol == control_cap) { + uint32_t new_cap = control_cap * 2u; + void* p = heap->realloc(heap, control, + sizeof(WasmCgControl) * control_cap, + sizeof(WasmCgControl) * new_cap, + _Alignof(WasmCgControl)); + if (!p) wasm_error(c, in.loc, "wasm: out of memory"); + control = (WasmCgControl*)p; + control_cap = new_cap; + } memset(&control[ncontrol], 0, sizeof control[ncontrol]); control[ncontrol].kind = WASM_INSN_BLOCK; control[ncontrol].end = cfree_cg_label_new(cg); ncontrol++; break; case WASM_INSN_LOOP: - if (ncontrol >= 64u) - wasm_error(c, wasm_loc(0, 0), "wasm: control stack too deep"); + if (ncontrol == control_cap) { + uint32_t new_cap = control_cap * 2u; + void* p = heap->realloc(heap, control, + sizeof(WasmCgControl) * control_cap, + sizeof(WasmCgControl) * new_cap, + _Alignof(WasmCgControl)); + if (!p) wasm_error(c, in.loc, "wasm: out of memory"); + control = (WasmCgControl*)p; + control_cap = new_cap; + } memset(&control[ncontrol], 0, sizeof control[ncontrol]); control[ncontrol].kind = WASM_INSN_LOOP; control[ncontrol].start = cfree_cg_label_new(cg); @@ -1189,8 +2083,16 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, ncontrol++; break; case WASM_INSN_IF: - if (ncontrol >= 64u) - wasm_error(c, wasm_loc(0, 0), "wasm: control stack too deep"); + if (ncontrol == control_cap) { + uint32_t new_cap = control_cap * 2u; + void* p = heap->realloc(heap, control, + sizeof(WasmCgControl) * control_cap, + sizeof(WasmCgControl) * new_cap, + _Alignof(WasmCgControl)); + if (!p) wasm_error(c, in.loc, "wasm: out of memory"); + control = (WasmCgControl*)p; + control_cap = new_cap; + } memset(&control[ncontrol], 0, sizeof control[ncontrol]); control[ncontrol].kind = WASM_INSN_IF; control[ncontrol].else_label = cfree_cg_label_new(cg); @@ -1200,14 +2102,14 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, break; case WASM_INSN_ELSE: if (!ncontrol || control[ncontrol - 1u].kind != WASM_INSN_IF) - wasm_error(c, wasm_loc(0, 0), "wasm: else without if"); + wasm_error(c, in.loc, "wasm: else without if"); cfree_cg_jump(cg, control[ncontrol - 1u].end); cfree_cg_label_place(cg, control[ncontrol - 1u].else_label); control[ncontrol - 1u].seen_else = 1; break; case WASM_INSN_END: if (!ncontrol) - wasm_error(c, wasm_loc(0, 0), "wasm: end without block"); + wasm_error(c, in.loc, "wasm: end without block"); ncontrol--; if (control[ncontrol].kind == WASM_INSN_IF && !control[ncontrol].seen_else) @@ -1218,7 +2120,7 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, uint32_t depth = (uint32_t)in.imm; uint32_t idx; if (depth >= ncontrol) - wasm_error(c, wasm_loc(0, 0), "wasm: branch depth out of range"); + wasm_error(c, in.loc, "wasm: branch depth out of range"); idx = ncontrol - 1u - depth; cfree_cg_jump(cg, control[idx].kind == WASM_INSN_LOOP ? control[idx].start @@ -1229,7 +2131,7 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, uint32_t depth = (uint32_t)in.imm; uint32_t idx; if (depth >= ncontrol) - wasm_error(c, wasm_loc(0, 0), "wasm: branch depth out of range"); + wasm_error(c, in.loc, "wasm: branch depth out of range"); idx = ncontrol - 1u - depth; cfree_cg_branch_true(cg, control[idx].kind == WASM_INSN_LOOP ? control[idx].start @@ -1237,15 +2139,15 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, break; } case WASM_INSN_BR_TABLE: { - CfreeCgSwitchCase cases[15]; CfreeCgSwitch sw; - memset(cases, 0, sizeof cases); - if (in.ntargets == 0 || in.ntargets > 16u) - wasm_error(c, wasm_loc(0, 0), "wasm: bad br_table target count"); + if (in.ntargets == 0) + wasm_error(c, in.loc, "wasm: bad br_table target count"); + CfreeCgSwitchCase* cases = + arena_zarray(&arena, CfreeCgSwitchCase, in.ntargets); for (uint32_t k = 0; k + 1u < in.ntargets; ++k) { uint32_t idx; if (in.targets[k] >= ncontrol) - wasm_error(c, wasm_loc(0, 0), "wasm: branch depth out of range"); + wasm_error(c, in.loc, "wasm: branch depth out of range"); idx = ncontrol - 1u - in.targets[k]; cases[k].value = k; cases[k].label = control[idx].kind == WASM_INSN_LOOP @@ -1253,7 +2155,7 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, : control[idx].end; } if (in.targets[in.ntargets - 1u] >= ncontrol) - wasm_error(c, wasm_loc(0, 0), "wasm: branch depth out of range"); + wasm_error(c, in.loc, "wasm: branch depth out of range"); memset(&sw, 0, sizeof sw); sw.selector_type = b.id[CFREE_CG_BUILTIN_I32]; sw.cases = cases; @@ -1350,23 +2252,25 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, case WASM_INSN_RETURN_CALL: wasm_cg_call_func(c, cg, b, &m->funcs[in.imm], &rt, syms[in.imm], func_types[in.imm], instance_local, - (uint32_t)in.imm, in.kind == WASM_INSN_RETURN_CALL); + (uint32_t)in.imm, + in.kind == WASM_INSN_RETURN_CALL, in.loc, &arena); break; case WASM_INSN_CALL_INDIRECT: case WASM_INSN_RETURN_CALL_INDIRECT: { const WasmFuncType* t = &m->types[in.imm]; CfreeCgLocalAttrs attrs; - CfreeCgLocal selector, callee, args[16], result = CFREE_CG_LOCAL_NONE; + CfreeCgLocal selector, callee, result = CFREE_CG_LOCAL_NONE; + CfreeCgLocal* args = + t->nparams ? arena_array(&arena, CfreeCgLocal, t->nparams) + : NULL; CfreeCgLabel ok; CfreeCgMemAccess i32_mem = wasm_cg_mem(c, b, WASM_VAL_I32); - CfreeCgFuncParam indirect_params[17]; + CfreeCgFuncParam* indirect_params = + arena_zarray(&arena, CfreeCgFuncParam, t->nparams + 1u); CfreeCgFuncSig indirect_sig; CfreeCgTypeId indirect_func_type; memset(&attrs, 0, sizeof attrs); attrs.flags = CFREE_CG_LOCAL_COMPILER_TEMP; - if (t->nparams > 16u) - wasm_error(c, wasm_loc(0, 0), "wasm: too many call_indirect args"); - memset(indirect_params, 0, sizeof indirect_params); indirect_params[0].type = rt.instance_ptr_ty; for (uint32_t p = 0; p < t->nparams; ++p) indirect_params[p + 1u].type = wasm_cg_type(c, b, t->params[p]); @@ -1487,18 +2391,19 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, case WASM_INSN_RETURN_CALL_REF: { const WasmFuncType* t = &m->types[in.imm]; CfreeCgLocalAttrs attrs; - CfreeCgLocal callee_ref, callee, args[16], - result = CFREE_CG_LOCAL_NONE; + CfreeCgLocal callee_ref, callee, result = CFREE_CG_LOCAL_NONE; + CfreeCgLocal* args = + t->nparams ? arena_array(&arena, CfreeCgLocal, t->nparams) + : NULL; CfreeCgLabel ok; CfreeCgMemAccess ref_mem = wasm_cg_mem_type(rt.void_ptr_ty); CfreeCgMemAccess i32_mem = wasm_cg_mem(c, b, WASM_VAL_I32); - CfreeCgFuncParam ref_params[17]; + CfreeCgFuncParam* ref_params = + arena_zarray(&arena, CfreeCgFuncParam, t->nparams + 1u); CfreeCgFuncSig ref_sig; CfreeCgTypeId ref_func_type; memset(&attrs, 0, sizeof attrs); attrs.flags = CFREE_CG_LOCAL_COMPILER_TEMP; - if (t->nparams > 16u) - wasm_error(c, wasm_loc(0, 0), "wasm: too many call_ref args"); callee_ref = cfree_cg_local(cg, rt.void_ptr_ty, attrs); callee = cfree_cg_local(cg, rt.void_ptr_ty, attrs); cfree_cg_push_local(cg, callee_ref); @@ -1549,7 +2454,6 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, } cfree_cg_store(cg, ref_mem, wasm_cg_ea0()); - memset(ref_params, 0, sizeof ref_params); ref_params[0].type = rt.instance_ptr_ty; for (uint32_t p = 0; p < t->nparams; ++p) ref_params[p + 1u].type = wasm_cg_type(c, b, t->params[p]); @@ -1691,6 +2595,583 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, cfree_cg_load(cg, wasm_cg_mem(c, b, page_vt), wasm_cg_ea0()); break; } + case WASM_INSN_MEMORY_COPY: { + /* Stack on entry: [dst_addr, src_addr, n] (n at TOS). */ + uint32_t dst_memidx = in.memidx; + uint32_t src_memidx = in.aux_idx; + int dst_is64 = m->memories[dst_memidx].is64; + int src_is64 = m->memories[src_memidx].is64; + CfreeCgLocalAttrs attrs; + CfreeCgLocal n_l, src_addr_l, dst_addr_l, src_base_l, dst_base_l; + CfreeCgLocal src_size_l, dst_size_l; + CfreeCgMemAccess i64_mem = wasm_cg_mem_type(b.id[CFREE_CG_BUILTIN_I64]); + CfreeCgMemAccess ptr_mem = wasm_cg_mem_type(rt.i8_ptr_ty); + memset(&attrs, 0, sizeof attrs); + attrs.flags = CFREE_CG_LOCAL_COMPILER_TEMP; + n_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I64], attrs); + src_addr_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I64], attrs); + dst_addr_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I64], attrs); + src_base_l = cfree_cg_local(cg, rt.i8_ptr_ty, attrs); + dst_base_l = cfree_cg_local(cg, rt.i8_ptr_ty, attrs); + src_size_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I64], attrs); + dst_size_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I64], attrs); + /* Pop n, src_addr, dst_addr into locals (zext if not memory64). */ + cfree_cg_push_local(cg, n_l); + cfree_cg_swap(cg); + if (!dst_is64) cfree_cg_zext(cg, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, src_addr_l); + cfree_cg_swap(cg); + if (!src_is64) cfree_cg_zext(cg, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, dst_addr_l); + cfree_cg_swap(cg); + if (!dst_is64) cfree_cg_zext(cg, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + /* Cache base and size for src and dst memories. */ + cfree_cg_push_local(cg, src_base_l); + wasm_cg_push_memory_data_ptr(cg, &rt, instance_local, src_memidx); + cfree_cg_store(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, dst_base_l); + wasm_cg_push_memory_data_ptr(cg, &rt, instance_local, dst_memidx); + cfree_cg_store(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, src_size_l); + wasm_cg_push_memory_pages_lvalue(cg, &rt, instance_local, src_memidx); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_int(cg, 65536u, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_int_binop(cg, CFREE_CG_INT_MUL, 0); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, dst_size_l); + wasm_cg_push_memory_pages_lvalue(cg, &rt, instance_local, dst_memidx); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_int(cg, 65536u, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_int_binop(cg, CFREE_CG_INT_MUL, 0); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + wasm_cg_bulk_bounds_check(cg, b, &rt, src_addr_l, n_l, src_size_l); + wasm_cg_bulk_bounds_check(cg, b, &rt, dst_addr_l, n_l, dst_size_l); + wasm_cg_emit_byte_copy_loop(cg, b, &rt, dst_base_l, src_base_l, + dst_addr_l, src_addr_l, n_l); + break; + } + case WASM_INSN_MEMORY_FILL: { + /* Stack: [dst_addr, val, n] (n at TOS). */ + uint32_t memidx = in.memidx; + int is64 = m->memories[memidx].is64; + CfreeCgLocalAttrs attrs; + CfreeCgLocal n_l, val_l, dst_addr_l, dst_base_l, dst_size_l; + CfreeCgMemAccess i64_mem = wasm_cg_mem_type(b.id[CFREE_CG_BUILTIN_I64]); + CfreeCgMemAccess i32_mem = wasm_cg_mem_type(b.id[CFREE_CG_BUILTIN_I32]); + CfreeCgMemAccess ptr_mem = wasm_cg_mem_type(rt.i8_ptr_ty); + memset(&attrs, 0, sizeof attrs); + attrs.flags = CFREE_CG_LOCAL_COMPILER_TEMP; + n_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I64], attrs); + val_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I32], attrs); + dst_addr_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I64], attrs); + dst_base_l = cfree_cg_local(cg, rt.i8_ptr_ty, attrs); + dst_size_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I64], attrs); + cfree_cg_push_local(cg, n_l); + cfree_cg_swap(cg); + if (!is64) cfree_cg_zext(cg, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, val_l); + cfree_cg_swap(cg); + cfree_cg_store(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, dst_addr_l); + cfree_cg_swap(cg); + if (!is64) cfree_cg_zext(cg, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, dst_base_l); + wasm_cg_push_memory_data_ptr(cg, &rt, instance_local, memidx); + cfree_cg_store(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, dst_size_l); + wasm_cg_push_memory_pages_lvalue(cg, &rt, instance_local, memidx); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_int(cg, 65536u, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_int_binop(cg, CFREE_CG_INT_MUL, 0); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + wasm_cg_bulk_bounds_check(cg, b, &rt, dst_addr_l, n_l, dst_size_l); + wasm_cg_emit_byte_fill_loop(cg, b, &rt, dst_base_l, dst_addr_l, val_l, + n_l); + break; + } + case WASM_INSN_MEMORY_INIT: { + /* Stack: [dst_addr, src_addr, n]; in.imm = dataidx; in.memidx = memidx */ + uint32_t dataidx = (uint32_t)in.imm; + uint32_t memidx = in.memidx; + int is64 = m->memories[memidx].is64; + CfreeCgLocalAttrs attrs; + CfreeCgLocal n_l, src_addr_l, dst_addr_l, src_base_l, dst_base_l; + CfreeCgLocal src_size_l, dst_size_l; + CfreeCgMemAccess i64_mem = wasm_cg_mem_type(b.id[CFREE_CG_BUILTIN_I64]); + CfreeCgMemAccess ptr_mem = wasm_cg_mem_type(rt.i8_ptr_ty); + memset(&attrs, 0, sizeof attrs); + attrs.flags = CFREE_CG_LOCAL_COMPILER_TEMP; + n_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I64], attrs); + src_addr_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I64], attrs); + dst_addr_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I64], attrs); + src_base_l = cfree_cg_local(cg, rt.i8_ptr_ty, attrs); + dst_base_l = cfree_cg_local(cg, rt.i8_ptr_ty, attrs); + src_size_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I64], attrs); + dst_size_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I64], attrs); + cfree_cg_push_local(cg, n_l); + cfree_cg_swap(cg); + cfree_cg_zext(cg, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, src_addr_l); + cfree_cg_swap(cg); + cfree_cg_zext(cg, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, dst_addr_l); + cfree_cg_swap(cg); + if (!is64) cfree_cg_zext(cg, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + /* src_base = inst->passive_data[dataidx].base */ + cfree_cg_push_local(cg, src_base_l); + wasm_cg_push_passive_data_lvalue(cg, &rt, instance_local, dataidx); + cfree_cg_field(cg, rt.passive_data_base_field); + cfree_cg_load(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_store(cg, ptr_mem, wasm_cg_ea0()); + /* src_size = inst->passive_data[dataidx].len */ + cfree_cg_push_local(cg, src_size_l); + wasm_cg_push_passive_data_lvalue(cg, &rt, instance_local, dataidx); + cfree_cg_field(cg, rt.passive_data_len_field); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, dst_base_l); + wasm_cg_push_memory_data_ptr(cg, &rt, instance_local, memidx); + cfree_cg_store(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, dst_size_l); + wasm_cg_push_memory_pages_lvalue(cg, &rt, instance_local, memidx); + cfree_cg_load(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_int(cg, 65536u, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_int_binop(cg, CFREE_CG_INT_MUL, 0); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + wasm_cg_bulk_bounds_check(cg, b, &rt, src_addr_l, n_l, src_size_l); + wasm_cg_bulk_bounds_check(cg, b, &rt, dst_addr_l, n_l, dst_size_l); + wasm_cg_emit_byte_copy_loop(cg, b, &rt, dst_base_l, src_base_l, + dst_addr_l, src_addr_l, n_l); + break; + } + case WASM_INSN_DATA_DROP: { + uint32_t dataidx = (uint32_t)in.imm; + wasm_cg_push_passive_data_lvalue(cg, &rt, instance_local, dataidx); + cfree_cg_field(cg, rt.passive_data_len_field); + cfree_cg_push_int(cg, 0, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_store(cg, wasm_cg_mem(c, b, WASM_VAL_I64), wasm_cg_ea0()); + break; + } + case WASM_INSN_ELEM_DROP: { + uint32_t elemidx = (uint32_t)in.imm; + wasm_cg_push_passive_elem_lvalue(cg, &rt, instance_local, elemidx); + cfree_cg_field(cg, rt.passive_elem_length_field); + cfree_cg_push_int(cg, 0, b.id[CFREE_CG_BUILTIN_I32]); + cfree_cg_store(cg, wasm_cg_mem(c, b, WASM_VAL_I32), wasm_cg_ea0()); + break; + } + case WASM_INSN_TABLE_SIZE: { + uint32_t tableidx = (uint32_t)in.imm; + wasm_cg_push_table_lvalue(cg, &rt, instance_local, tableidx); + cfree_cg_field(cg, rt.table_len_field); + cfree_cg_load(cg, wasm_cg_mem(c, b, WASM_VAL_I32), wasm_cg_ea0()); + break; + } + case WASM_INSN_TABLE_GROW: { + /* Stack: [val, delta] -> [prev_size or -1 on failure]. + * Implementation: if current_len + delta <= max, write zeros into + * new slots (we ignore val for funcref since slots are owned), then + * bump len. We do honor val for funcref by writing the supplied + * funcref into each new slot. */ + uint32_t tableidx = (uint32_t)in.imm; + CfreeCgLocalAttrs attrs; + CfreeCgLocal val_l, delta_l, old_len_l, result_l, idx_l; + CfreeCgMemAccess i32_mem = wasm_cg_mem(c, b, WASM_VAL_I32); + CfreeCgMemAccess fn_mem = wasm_cg_mem_type(rt.void_ptr_ty); + CfreeCgLabel fail = cfree_cg_label_new(cg); + CfreeCgLabel done = cfree_cg_label_new(cg); + CfreeCgLabel fill_loop = cfree_cg_label_new(cg); + memset(&attrs, 0, sizeof attrs); + attrs.flags = CFREE_CG_LOCAL_COMPILER_TEMP; + delta_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I32], attrs); + val_l = cfree_cg_local(cg, rt.void_ptr_ty, attrs); + old_len_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I32], attrs); + result_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I32], attrs); + idx_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I32], attrs); + cfree_cg_push_local(cg, delta_l); + cfree_cg_swap(cg); + cfree_cg_store(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, val_l); + cfree_cg_swap(cg); + cfree_cg_store(cg, fn_mem, wasm_cg_ea0()); + /* Cache *val (a CfreeWasmTableEntry) into fn/typeidx locals so the + * fill loop writes the correct pair into each new slot. */ + CfreeCgLocal val_fn_l = cfree_cg_local(cg, rt.void_ptr_ty, attrs); + CfreeCgLocal val_typeidx_l = + cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I32], attrs); + cfree_cg_push_local(cg, val_fn_l); + cfree_cg_push_local(cg, val_l); + cfree_cg_load(cg, fn_mem, wasm_cg_ea0()); + cfree_cg_bitcast(cg, cfree_cg_type_ptr(c, rt.table_entry_ty, 0)); + cfree_cg_field(cg, rt.table_entry_fn_field); + cfree_cg_load(cg, fn_mem, wasm_cg_ea0()); + cfree_cg_store(cg, fn_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, val_typeidx_l); + cfree_cg_push_local(cg, val_l); + cfree_cg_load(cg, fn_mem, wasm_cg_ea0()); + cfree_cg_bitcast(cg, cfree_cg_type_ptr(c, rt.table_entry_ty, 0)); + cfree_cg_field(cg, rt.table_entry_typeidx_field); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_store(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, old_len_l); + wasm_cg_push_table_lvalue(cg, &rt, instance_local, tableidx); + cfree_cg_field(cg, rt.table_len_field); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_store(cg, i32_mem, wasm_cg_ea0()); + /* Check old_len + delta <= max. */ + wasm_cg_push_table_lvalue(cg, &rt, instance_local, tableidx); + cfree_cg_field(cg, rt.table_max_field); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, old_len_l); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_int_cmp(cg, CFREE_CG_INT_LT_U); + cfree_cg_branch_true(cg, fail); + /* max - old_len >= delta */ + wasm_cg_push_table_lvalue(cg, &rt, instance_local, tableidx); + cfree_cg_field(cg, rt.table_max_field); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, old_len_l); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_int_binop(cg, CFREE_CG_INT_SUB, 0); + cfree_cg_push_local(cg, delta_l); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_int_cmp(cg, CFREE_CG_INT_LT_U); + cfree_cg_branch_true(cg, fail); + /* Update length first. */ + wasm_cg_push_table_lvalue(cg, &rt, instance_local, tableidx); + cfree_cg_field(cg, rt.table_len_field); + cfree_cg_push_local(cg, old_len_l); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, delta_l); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_int_binop(cg, CFREE_CG_INT_ADD, 0); + cfree_cg_store(cg, i32_mem, wasm_cg_ea0()); + /* Initialize new slots [old_len, old_len+delta) to val. */ + cfree_cg_push_local(cg, idx_l); + cfree_cg_push_local(cg, old_len_l); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_store(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_label_place(cg, fill_loop); + cfree_cg_push_local(cg, idx_l); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, old_len_l); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, delta_l); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_int_binop(cg, CFREE_CG_INT_ADD, 0); + cfree_cg_int_cmp(cg, CFREE_CG_INT_GE_U); + { + CfreeCgLabel fill_end = cfree_cg_label_new(cg); + cfree_cg_branch_true(cg, fill_end); + wasm_cg_push_table_entry_lvalue(cg, &rt, instance_local, tableidx, + idx_l, i32_mem); + cfree_cg_field(cg, rt.table_entry_fn_field); + cfree_cg_push_local(cg, val_fn_l); + cfree_cg_load(cg, fn_mem, wasm_cg_ea0()); + cfree_cg_store(cg, fn_mem, wasm_cg_ea0()); + wasm_cg_push_table_entry_lvalue(cg, &rt, instance_local, tableidx, + idx_l, i32_mem); + cfree_cg_field(cg, rt.table_entry_typeidx_field); + cfree_cg_push_local(cg, val_typeidx_l); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_store(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, idx_l); + cfree_cg_push_local(cg, idx_l); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_push_int(cg, 1, b.id[CFREE_CG_BUILTIN_I32]); + cfree_cg_int_binop(cg, CFREE_CG_INT_ADD, 0); + cfree_cg_store(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_jump(cg, fill_loop); + cfree_cg_label_place(cg, fill_end); + } + cfree_cg_push_local(cg, result_l); + cfree_cg_push_local(cg, old_len_l); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_store(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_jump(cg, done); + cfree_cg_label_place(cg, fail); + cfree_cg_push_local(cg, result_l); + cfree_cg_push_int(cg, UINT32_C(0xffffffff), b.id[CFREE_CG_BUILTIN_I32]); + cfree_cg_store(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_label_place(cg, done); + cfree_cg_push_local(cg, result_l); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + break; + } + case WASM_INSN_TABLE_FILL: { + /* Stack: [dst_idx, val, n]. `val` is a `CfreeWasmTableEntry*` + * pointing at an instance-resident funcref slot (produced by + * ref.func). To populate table slots correctly, we dereference + * `val` once outside the loop into local fn/typeidx pairs and + * then copy those into each slot. */ + uint32_t tableidx = (uint32_t)in.imm; + CfreeCgLocalAttrs attrs; + CfreeCgLocal n_l, val_l, dst_idx_l, len_l, idx_l; + CfreeCgLocal val_fn_l, val_typeidx_l; + CfreeCgMemAccess i32_mem = wasm_cg_mem(c, b, WASM_VAL_I32); + CfreeCgMemAccess fn_mem = wasm_cg_mem_type(rt.void_ptr_ty); + CfreeCgLabel loop = cfree_cg_label_new(cg); + CfreeCgLabel done = cfree_cg_label_new(cg); + CfreeCgLabel ok = cfree_cg_label_new(cg); + memset(&attrs, 0, sizeof attrs); + attrs.flags = CFREE_CG_LOCAL_COMPILER_TEMP; + n_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I32], attrs); + val_l = cfree_cg_local(cg, rt.void_ptr_ty, attrs); + dst_idx_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I32], attrs); + len_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I32], attrs); + idx_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I32], attrs); + val_fn_l = cfree_cg_local(cg, rt.void_ptr_ty, attrs); + val_typeidx_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I32], attrs); + cfree_cg_push_local(cg, n_l); + cfree_cg_swap(cg); + cfree_cg_store(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, val_l); + cfree_cg_swap(cg); + cfree_cg_store(cg, fn_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, dst_idx_l); + cfree_cg_swap(cg); + cfree_cg_store(cg, i32_mem, wasm_cg_ea0()); + /* Cache *val into (val_fn, val_typeidx). val points at a + * CfreeWasmTableEntry which has fn (void*) at offset 0 and + * typeidx (i32) at offset sizeof(void*). */ + cfree_cg_push_local(cg, val_fn_l); + cfree_cg_push_local(cg, val_l); + cfree_cg_load(cg, fn_mem, wasm_cg_ea0()); + cfree_cg_bitcast(cg, cfree_cg_type_ptr(c, rt.table_entry_ty, 0)); + cfree_cg_field(cg, rt.table_entry_fn_field); + cfree_cg_load(cg, fn_mem, wasm_cg_ea0()); + cfree_cg_store(cg, fn_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, val_typeidx_l); + cfree_cg_push_local(cg, val_l); + cfree_cg_load(cg, fn_mem, wasm_cg_ea0()); + cfree_cg_bitcast(cg, cfree_cg_type_ptr(c, rt.table_entry_ty, 0)); + cfree_cg_field(cg, rt.table_entry_typeidx_field); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_store(cg, i32_mem, wasm_cg_ea0()); + /* Bounds check: dst_idx + n <= len. */ + cfree_cg_push_local(cg, len_l); + wasm_cg_push_table_lvalue(cg, &rt, instance_local, tableidx); + cfree_cg_field(cg, rt.table_len_field); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_store(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, len_l); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, dst_idx_l); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_int_cmp(cg, CFREE_CG_INT_GE_U); + cfree_cg_branch_true(cg, ok); + wasm_cg_trap_bounds(cg, &rt); + cfree_cg_label_place(cg, ok); + { + CfreeCgLabel ok2 = cfree_cg_label_new(cg); + cfree_cg_push_local(cg, len_l); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, dst_idx_l); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_int_binop(cg, CFREE_CG_INT_SUB, 0); + cfree_cg_push_local(cg, n_l); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_int_cmp(cg, CFREE_CG_INT_GE_U); + cfree_cg_branch_true(cg, ok2); + wasm_cg_trap_bounds(cg, &rt); + cfree_cg_label_place(cg, ok2); + } + /* idx = 0; while idx < n: table[dst_idx+idx] = val; idx++ */ + cfree_cg_push_local(cg, idx_l); + cfree_cg_push_int(cg, 0, b.id[CFREE_CG_BUILTIN_I32]); + cfree_cg_store(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_label_place(cg, loop); + cfree_cg_push_local(cg, idx_l); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, n_l); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_int_cmp(cg, CFREE_CG_INT_GE_U); + cfree_cg_branch_true(cg, done); + { + /* Compute slot = dst_idx + idx, store into a temp local for + * indexing. */ + CfreeCgLocal slot_l = + cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I32], attrs); + cfree_cg_push_local(cg, slot_l); + cfree_cg_push_local(cg, dst_idx_l); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, idx_l); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_int_binop(cg, CFREE_CG_INT_ADD, 0); + cfree_cg_store(cg, i32_mem, wasm_cg_ea0()); + wasm_cg_push_table_entry_lvalue(cg, &rt, instance_local, tableidx, + slot_l, i32_mem); + cfree_cg_field(cg, rt.table_entry_fn_field); + cfree_cg_push_local(cg, val_fn_l); + cfree_cg_load(cg, fn_mem, wasm_cg_ea0()); + cfree_cg_store(cg, fn_mem, wasm_cg_ea0()); + wasm_cg_push_table_entry_lvalue(cg, &rt, instance_local, tableidx, + slot_l, i32_mem); + cfree_cg_field(cg, rt.table_entry_typeidx_field); + cfree_cg_push_local(cg, val_typeidx_l); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_store(cg, i32_mem, wasm_cg_ea0()); + } + cfree_cg_push_local(cg, idx_l); + cfree_cg_push_local(cg, idx_l); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_push_int(cg, 1, b.id[CFREE_CG_BUILTIN_I32]); + cfree_cg_int_binop(cg, CFREE_CG_INT_ADD, 0); + cfree_cg_store(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_jump(cg, loop); + cfree_cg_label_place(cg, done); + break; + } + case WASM_INSN_TABLE_COPY: { + /* Stack: [dst_idx, src_idx, n]; in.imm = dst_tableidx; in.aux_idx = src */ + uint32_t dst_tbl = (uint32_t)in.imm; + uint32_t src_tbl = in.aux_idx; + CfreeCgLocalAttrs attrs; + CfreeCgLocal n_l, src_idx_l, dst_idx_l, src_base_l, dst_base_l; + CfreeCgLocal src_len_l, dst_len_l; + CfreeCgMemAccess i64_mem = wasm_cg_mem_type(b.id[CFREE_CG_BUILTIN_I64]); + CfreeCgMemAccess i32_mem = wasm_cg_mem(c, b, WASM_VAL_I32); + CfreeCgMemAccess ptr_mem = wasm_cg_mem_type(rt.table_entry_ptr_ty); + memset(&attrs, 0, sizeof attrs); + attrs.flags = CFREE_CG_LOCAL_COMPILER_TEMP; + n_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I64], attrs); + src_idx_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I64], attrs); + dst_idx_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I64], attrs); + src_base_l = cfree_cg_local(cg, rt.table_entry_ptr_ty, attrs); + dst_base_l = cfree_cg_local(cg, rt.table_entry_ptr_ty, attrs); + src_len_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I64], attrs); + dst_len_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I64], attrs); + cfree_cg_push_local(cg, n_l); + cfree_cg_swap(cg); + cfree_cg_zext(cg, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, src_idx_l); + cfree_cg_swap(cg); + cfree_cg_zext(cg, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, dst_idx_l); + cfree_cg_swap(cg); + cfree_cg_zext(cg, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, src_base_l); + wasm_cg_push_table_lvalue(cg, &rt, instance_local, src_tbl); + cfree_cg_field(cg, rt.table_entries_ptr_field); + cfree_cg_load(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_store(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, dst_base_l); + wasm_cg_push_table_lvalue(cg, &rt, instance_local, dst_tbl); + cfree_cg_field(cg, rt.table_entries_ptr_field); + cfree_cg_load(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_store(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, src_len_l); + wasm_cg_push_table_lvalue(cg, &rt, instance_local, src_tbl); + cfree_cg_field(cg, rt.table_len_field); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_zext(cg, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, dst_len_l); + wasm_cg_push_table_lvalue(cg, &rt, instance_local, dst_tbl); + cfree_cg_field(cg, rt.table_len_field); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_zext(cg, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + wasm_cg_bulk_bounds_check(cg, b, &rt, src_idx_l, n_l, src_len_l); + wasm_cg_bulk_bounds_check(cg, b, &rt, dst_idx_l, n_l, dst_len_l); + wasm_cg_emit_table_copy_loop(c, cg, b, &rt, dst_base_l, src_base_l, + dst_idx_l, src_idx_l, n_l); + break; + } + case WASM_INSN_TABLE_INIT: { + /* Stack: [dst_idx, src_idx, n]; in.imm = elemidx; in.aux_idx = tableidx */ + uint32_t elemidx = (uint32_t)in.imm; + uint32_t tableidx = in.aux_idx; + CfreeCgLocalAttrs attrs; + CfreeCgLocal n_l, src_idx_l, dst_idx_l, src_base_l, dst_base_l; + CfreeCgLocal src_len_l, dst_len_l; + CfreeCgMemAccess i64_mem = wasm_cg_mem_type(b.id[CFREE_CG_BUILTIN_I64]); + CfreeCgMemAccess i32_mem = wasm_cg_mem(c, b, WASM_VAL_I32); + CfreeCgMemAccess ptr_mem = wasm_cg_mem_type(rt.table_entry_ptr_ty); + memset(&attrs, 0, sizeof attrs); + attrs.flags = CFREE_CG_LOCAL_COMPILER_TEMP; + n_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I64], attrs); + src_idx_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I64], attrs); + dst_idx_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I64], attrs); + src_base_l = cfree_cg_local(cg, rt.table_entry_ptr_ty, attrs); + dst_base_l = cfree_cg_local(cg, rt.table_entry_ptr_ty, attrs); + src_len_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I64], attrs); + dst_len_l = cfree_cg_local(cg, b.id[CFREE_CG_BUILTIN_I64], attrs); + cfree_cg_push_local(cg, n_l); + cfree_cg_swap(cg); + cfree_cg_zext(cg, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, src_idx_l); + cfree_cg_swap(cg); + cfree_cg_zext(cg, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, dst_idx_l); + cfree_cg_swap(cg); + cfree_cg_zext(cg, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, src_base_l); + wasm_cg_push_passive_elem_lvalue(cg, &rt, instance_local, elemidx); + cfree_cg_field(cg, rt.passive_elem_entries_field); + cfree_cg_load(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_store(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, dst_base_l); + wasm_cg_push_table_lvalue(cg, &rt, instance_local, tableidx); + cfree_cg_field(cg, rt.table_entries_ptr_field); + cfree_cg_load(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_store(cg, ptr_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, src_len_l); + wasm_cg_push_passive_elem_lvalue(cg, &rt, instance_local, elemidx); + cfree_cg_field(cg, rt.passive_elem_length_field); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_zext(cg, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + cfree_cg_push_local(cg, dst_len_l); + wasm_cg_push_table_lvalue(cg, &rt, instance_local, tableidx); + cfree_cg_field(cg, rt.table_len_field); + cfree_cg_load(cg, i32_mem, wasm_cg_ea0()); + cfree_cg_zext(cg, b.id[CFREE_CG_BUILTIN_I64]); + cfree_cg_store(cg, i64_mem, wasm_cg_ea0()); + wasm_cg_bulk_bounds_check(cg, b, &rt, src_idx_l, n_l, src_len_l); + wasm_cg_bulk_bounds_check(cg, b, &rt, dst_idx_l, n_l, dst_len_l); + wasm_cg_emit_table_copy_loop(c, cg, b, &rt, dst_base_l, src_base_l, + dst_idx_l, src_idx_l, n_l); + break; + } + case WASM_INSN_I32_TRUNC_SAT_F32_S: + case WASM_INSN_I32_TRUNC_SAT_F64_S: + cfree_cg_float_to_sint(cg, + b.id[CFREE_CG_BUILTIN_I32], + CFREE_CG_ROUND_TOWARD_ZERO); + break; + case WASM_INSN_I32_TRUNC_SAT_F32_U: + case WASM_INSN_I32_TRUNC_SAT_F64_U: + cfree_cg_float_to_uint(cg, + b.id[CFREE_CG_BUILTIN_I32], + CFREE_CG_ROUND_TOWARD_ZERO); + break; + case WASM_INSN_I64_TRUNC_SAT_F32_S: + case WASM_INSN_I64_TRUNC_SAT_F64_S: + cfree_cg_float_to_sint(cg, + b.id[CFREE_CG_BUILTIN_I64], + CFREE_CG_ROUND_TOWARD_ZERO); + break; + case WASM_INSN_I64_TRUNC_SAT_F32_U: + case WASM_INSN_I64_TRUNC_SAT_F64_U: + cfree_cg_float_to_uint(cg, + b.id[CFREE_CG_BUILTIN_I64], + CFREE_CG_ROUND_TOWARD_ZERO); + break; case WASM_INSN_ATOMIC_FENCE: cfree_cg_atomic_fence(cg, CFREE_CG_MO_SEQ_CST); break; @@ -1865,7 +3346,9 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, case WASM_INSN_I64_LOAD16_S: case WASM_INSN_I64_LOAD16_U: case WASM_INSN_I64_LOAD32_S: - case WASM_INSN_I64_LOAD32_U: { + case WASM_INSN_I64_LOAD32_U: + case WASM_INSN_F32_LOAD: + case WASM_INSN_F64_LOAD: { CfreeCgTypeId storage = wasm_load_storage_type(b, in.kind); CfreeCgTypeId result = wasm_cg_type(c, b, wasm_load_result_type(in.kind)); @@ -1894,7 +3377,9 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, case WASM_INSN_I32_STORE16: case WASM_INSN_I64_STORE8: case WASM_INSN_I64_STORE16: - case WASM_INSN_I64_STORE32: { + case WASM_INSN_I64_STORE32: + case WASM_INSN_F32_STORE: + case WASM_INSN_F64_STORE: { CfreeCgTypeId storage = wasm_store_storage_type(b, in.kind); CfreeCgTypeId value_type = wasm_cg_type(c, b, wasm_store_value_type(in.kind)); @@ -2129,17 +3614,38 @@ void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, cfree_cg_fp_cmp(cg, fp_cmp); cfree_cg_zext(cg, b.id[CFREE_CG_BUILTIN_I32]); } else { - wasm_error(c, wasm_loc(0, 0), "wasm: unsupported instruction"); + wasm_error(c, in.loc, "wasm: unsupported instruction"); } break; } } } - if (f->nresults) - cfree_cg_ret(cg); - else - cfree_cg_ret_void(cg); + /* If the body's final instruction was an explicit RETURN (or a + * required-tail call), the SValue stack is already empty and the + * function's prologue/epilogue is closed — emitting another ret here + * underflows. Skip in that case; the wasm validator already proved + * every reachable exit returns the right shape. */ + { + uint32_t last = f->ninsns; + int body_terminates; + while (last > 0 && f->insns[last - 1u].kind == WASM_INSN_END) last--; + body_terminates = + last > 0 && + (f->insns[last - 1u].kind == WASM_INSN_RETURN || + f->insns[last - 1u].kind == WASM_INSN_RETURN_CALL || + f->insns[last - 1u].kind == WASM_INSN_RETURN_CALL_INDIRECT || + f->insns[last - 1u].kind == WASM_INSN_RETURN_CALL_REF || + f->insns[last - 1u].kind == WASM_INSN_UNREACHABLE); + if (!body_terminates) { + if (f->nresults) + cfree_cg_ret(cg); + else + cfree_cg_ret_void(cg); + } + } cfree_cg_func_end(cg); + heap->free(heap, control, sizeof(WasmCgControl) * control_cap); } cfree_cg_free(cg); + arena_fini(&arena); } diff --git a/lang/wasm/decode.c b/lang/wasm/decode.c @@ -1,1185 +0,0 @@ -#include "wasm_internal.h" - -typedef struct BinReader { - CfreeCompiler* c; - const uint8_t* data; - size_t len; - size_t pos; - WasmModule* module; -} BinReader; - -static uint8_t bin_u8(BinReader* r) { - if (r->pos >= r->len) - wasm_error(r->c, wasm_loc(0, 0), "wasm: unexpected end of file"); - return r->data[r->pos++]; -} - -static uint32_t bin_uleb(BinReader* r) { - uint32_t result = 0, shift = 0; - uint32_t nbytes = 0; - for (;;) { - uint8_t b = bin_u8(r); - if (nbytes++ >= 5u || (shift == 28u && (b & 0xf0u))) - wasm_error(r->c, wasm_loc(0, 0), "wasm: invalid uleb128"); - result |= (uint32_t)(b & 0x7fu) << shift; - if (!(b & 0x80u)) return result; - shift += 7u; - } -} - -static uint64_t bin_uleb64(BinReader* r) { - uint64_t result = 0; - uint32_t shift = 0; - uint32_t nbytes = 0; - for (;;) { - uint8_t b = bin_u8(r); - if (nbytes++ >= 10u || (shift == 63u && (b & 0xfeu))) - wasm_error(r->c, wasm_loc(0, 0), "wasm: invalid uleb128"); - result |= (uint64_t)(b & 0x7fu) << shift; - if (!(b & 0x80u)) return result; - shift += 7u; - } -} - -static int64_t bin_sleb(BinReader* r, uint32_t bits) { - int64_t result = 0; - uint32_t shift = 0; - uint32_t max_bytes = (bits + 6u) / 7u; - uint32_t nbytes = 0; - uint8_t b; - do { - if (nbytes++ >= max_bytes) - wasm_error(r->c, wasm_loc(0, 0), "wasm: invalid sleb128"); - b = bin_u8(r); - result |= (int64_t)(b & 0x7fu) << shift; - shift += 7u; - } while (b & 0x80u); - if (shift < bits && (b & 0x40u)) result |= -((int64_t)1 << shift); - return result; -} - -static double bin_f32(BinReader* r) { - uint32_t bits = 0; - float f; - for (uint32_t i = 0; i < 4u; ++i) bits |= (uint32_t)bin_u8(r) << (i * 8u); - memcpy(&f, &bits, sizeof f); - return (double)f; -} - -static double bin_f64(BinReader* r) { - uint64_t bits = 0; - double d; - for (uint32_t i = 0; i < 8u; ++i) bits |= (uint64_t)bin_u8(r) << (i * 8u); - memcpy(&d, &bits, sizeof d); - return d; -} - -static void bin_memarg(BinReader* r, uint32_t* align, uint64_t* offset, - uint32_t* memidx) { - uint32_t a = bin_uleb(r); - *memidx = 0; - if (a >= 64u && a < 128u) { - *align = a - 64u; - *memidx = bin_uleb(r); - } else if (a < 64u) { - *align = a; - } else { - wasm_error(r->c, wasm_loc(0, 0), "wasm: bad memory alignment"); - } - *offset = bin_uleb64(r); -} - -static void bin_need(BinReader* r, size_t n) { - if (n > r->len || r->pos > r->len - n) - wasm_error(r->c, wasm_loc(0, 0), "wasm: section length out of bounds"); -} - -static CfreeSym bin_name(BinReader* r, uint32_t* len_out) { - uint32_t n = bin_uleb(r); - CfreeSym sym; - bin_need(r, n); - sym = cfree_sym_intern( - r->c, (CfreeSlice){.s = (const char*)(r->data + r->pos), .len = n}); - r->pos += n; - if (len_out) *len_out = n; - return sym; -} - -static WasmValType bin_val_type(BinReader* r, int refs_ok) { - uint8_t b = bin_u8(r); - switch (b) { - case WASM_VAL_I32: - case WASM_VAL_I64: - case WASM_VAL_F32: - case WASM_VAL_F64: - return (WasmValType)b; - case WASM_VAL_FUNCREF: - case WASM_VAL_EXTERNREF: - if (refs_ok) return (WasmValType)b; - break; - default: - break; - } - wasm_error(r->c, wasm_loc(0, 0), "wasm: unsupported value type 0x%02x", b); - return WASM_VAL_I32; -} - -void wasm_decode_binary(CfreeCompiler* c, const CfreeSlice* input, - WasmModule* out) { - BinReader r; - uint32_t nfunc_types = 0; - uint8_t last_id = 0; - memset(&r, 0, sizeof r); - r.c = c; - r.data = input->data; - r.len = input->len; - r.module = out; - if (r.len < 8 || memcmp(r.data, "\0asm\1\0\0\0", 8) != 0) - wasm_error(c, wasm_loc(0, 0), "wasm: bad magic or version"); - r.pos = 8; - while (r.pos < r.len) { - uint8_t id = bin_u8(&r); - uint32_t size = bin_uleb(&r); - size_t end; - bin_need(&r, size); - end = r.pos + size; - if (id != 0 && id <= last_id) - wasm_error(c, wasm_loc(0, 0), "wasm: sections out of order"); - if (id != 0) last_id = id; - if (id == 0) { - uint32_t name_len = bin_uleb(&r); - bin_need(&r, name_len); - if (name_len == 7u && memcmp(r.data + r.pos, "linking", 7) == 0) - wasm_error(c, wasm_loc(0, 0), - "wasm: relocatable object metadata is not frontend input"); - { - WasmCustom* cs = wasm_add_custom(c, out); - cs->name = cfree_sym_intern( - c, - (CfreeSlice){.s = (const char*)(r.data + r.pos), .len = name_len}); - r.pos += name_len; - if (cs->name == cfree_sym_intern(c, CFREE_SLICE_LIT("target_features")) || - cs->name == cfree_sym_intern(c, CFREE_SLICE_LIT("target-feature"))) - out->has_target_features = 1; - cs->len = (uint32_t)(end - r.pos); - if (cs->len) { - cs->data = (uint8_t*)out->heap->alloc(out->heap, cs->len, 1); - if (!cs->data) wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); - memcpy(cs->data, r.data + r.pos, cs->len); - } - } - r.pos = end; - continue; - } - 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; - if (bin_u8(&r) != 0x60u) - wasm_error(c, wasm_loc(0, 0), "wasm: expected function type"); - nparam = bin_uleb(&r); - if (nparam > 16u) - wasm_error(c, wasm_loc(0, 0), "wasm: too many parameters"); - t->nparams = nparam; - for (j = 0; j < nparam; ++j) t->params[j] = bin_val_type(&r, 1); - nresult = bin_uleb(&r); - if (nresult > 1u) - wasm_error(c, wasm_loc(0, 0), "wasm: multi-result unsupported"); - t->nresults = nresult; - for (j = 0; j < nresult; ++j) t->results[j] = bin_val_type(&r, 1); - } - } else if (id == 2) { - uint32_t i, count = bin_uleb(&r); - for (i = 0; i < count; ++i) { - CfreeSym mod = bin_name(&r, NULL); - CfreeSym name = bin_name(&r, NULL); - uint8_t kind = bin_u8(&r); - if (kind == 0) { - uint32_t typeidx = bin_uleb(&r); - WasmFunc* f; - if (typeidx >= out->ntypes) - wasm_error(c, wasm_loc(0, 0), "wasm: bad import type index"); - f = wasm_add_func(c, out); - f->is_import = 1; - f->import_module = mod; - f->import_name = name; - f->typeidx = typeidx; - f->has_typeidx = 1; - f->nparams = out->types[typeidx].nparams; - memcpy(f->params, out->types[typeidx].params, - sizeof(f->params[0]) * f->nparams); - f->nresults = out->types[typeidx].nresults; - memcpy(f->results, out->types[typeidx].results, - sizeof(f->results[0]) * f->nresults); - } else if (kind == 1) { - WasmTable* t = wasm_add_table(c, out); - t->is_import = 1; - t->import_module = mod; - t->import_name = name; - t->elem_type = bin_val_type(&r, 1); - { - uint32_t flags = bin_uleb(&r); - if (flags & ~1u) - wasm_error(c, wasm_loc(0, 0), "wasm: unsupported table limits"); - t->has_max = (flags & 1u) != 0; - t->min = bin_uleb(&r); - if (t->has_max) t->max = bin_uleb(&r); - } - } else if (kind == 2) { - uint32_t flags; - WasmMemory* mem = wasm_add_memory(c, out); - mem->is_import = 1; - mem->import_module = mod; - mem->import_name = name; - flags = bin_uleb(&r); - if (flags & ~7u) - wasm_error(c, wasm_loc(0, 0), "wasm: unsupported memory limits"); - mem->has_max = (flags & 1u) != 0; - mem->shared = (flags & 2u) != 0; - mem->is64 = (flags & 4u) != 0; - mem->min_pages = mem->is64 ? bin_uleb64(&r) : bin_uleb(&r); - if (mem->has_max) - mem->max_pages = mem->is64 ? bin_uleb64(&r) : bin_uleb(&r); - } else if (kind == 3) { - WasmGlobal* g = wasm_add_global(c, out); - g->is_import = 1; - g->import_module = mod; - g->import_name = name; - g->type = bin_val_type(&r, 0); - g->mutable_ = bin_u8(&r); - if (g->mutable_ > 1u) - wasm_error(c, wasm_loc(0, 0), "wasm: bad global mutability"); - } else { - wasm_error(c, wasm_loc(0, 0), "wasm: unsupported import kind"); - } - } - } 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; - if (typeidx >= out->ntypes) - wasm_error(c, wasm_loc(0, 0), "wasm: bad function type index"); - f = wasm_add_func(c, out); - f->typeidx = typeidx; - f->has_typeidx = 1; - f->nparams = out->types[typeidx].nparams; - memcpy(f->params, out->types[typeidx].params, - sizeof(f->params[0]) * f->nparams); - f->nresults = out->types[typeidx].nresults; - memcpy(f->results, out->types[typeidx].results, - sizeof(f->results[0]) * f->nresults); - nfunc_types++; - } - } else if (id == 5) { - uint32_t count = bin_uleb(&r); - for (uint32_t mi = 0; mi < count; ++mi) { - uint32_t flags = bin_uleb(&r); - WasmMemory* mem = wasm_add_memory(c, out); - if (flags & ~7u) - wasm_error(c, wasm_loc(0, 0), "wasm: unsupported memory limits"); - mem->has_max = (flags & 1u) != 0; - mem->shared = (flags & 2u) != 0; - mem->is64 = (flags & 4u) != 0; - mem->min_pages = mem->is64 ? bin_uleb64(&r) : bin_uleb(&r); - if (mem->has_max) - mem->max_pages = mem->is64 ? bin_uleb64(&r) : bin_uleb(&r); - wasm_memory_ensure(c, out, out->nmemories - 1u, - mem->min_pages * 65536u); - } - } else if (id == 4) { - uint32_t i, count = bin_uleb(&r); - for (i = 0; i < count; ++i) { - WasmTable* t = wasm_add_table(c, out); - uint32_t flags; - t->elem_type = bin_val_type(&r, 1); - flags = bin_uleb(&r); - if (flags & ~1u) - wasm_error(c, wasm_loc(0, 0), "wasm: unsupported table limits"); - t->has_max = (flags & 1u) != 0; - t->min = bin_uleb(&r); - if (t->has_max) t->max = bin_uleb(&r); - } - } else if (id == 6) { - uint32_t i, count = bin_uleb(&r); - for (i = 0; i < count; ++i) { - WasmGlobal* g = wasm_add_global(c, out); - uint8_t op; - g->type = bin_val_type(&r, 0); - g->mutable_ = bin_u8(&r); - if (g->mutable_ > 1u) - wasm_error(c, wasm_loc(0, 0), "wasm: bad global mutability"); - op = bin_u8(&r); - switch (op) { - case 0x41: - g->init.kind = WASM_INSN_I32_CONST; - g->init.imm = bin_sleb(&r, 32); - break; - case 0x42: - g->init.kind = WASM_INSN_I64_CONST; - g->init.imm = bin_sleb(&r, 64); - break; - case 0x43: - g->init.kind = WASM_INSN_F32_CONST; - g->init.fp = bin_f32(&r); - break; - case 0x44: - g->init.kind = WASM_INSN_F64_CONST; - g->init.fp = bin_f64(&r); - break; - default: - wasm_error(c, wasm_loc(0, 0), - "wasm: unsupported global initializer"); - } - if (bin_u8(&r) != 0x0bu) - wasm_error(c, wasm_loc(0, 0), "wasm: malformed global initializer"); - } - } else if (id == 7) { - uint32_t i, count = bin_uleb(&r); - for (i = 0; i < count; ++i) { - CfreeSym name; - uint8_t kind; - uint32_t idx; - WasmExport* ex; - name = bin_name(&r, NULL); - kind = bin_u8(&r); - idx = bin_uleb(&r); - if (kind > 3u) wasm_error(c, wasm_loc(0, 0), "wasm: bad export kind"); - ex = wasm_add_export(c, out); - ex->name = name; - ex->kind = kind; - ex->index = idx; - if (kind == 0 && idx < out->nfuncs) { - out->funcs[idx].export_name = name; - } else if (kind == 1 && idx < out->ntables) { - out->tables[idx].export_name = name; - } else if (kind == 2 && idx < out->nmemories) { - out->memories[idx].export_name = name; - } else if (kind == 3 && idx < out->nglobals) { - out->globals[idx].export_name = name; - } - } - } else if (id == 8) { - out->has_start = 1; - out->start_func = bin_uleb(&r); - } else if (id == 9) { - uint32_t i, count = bin_uleb(&r); - for (i = 0; i < count; ++i) { - WasmElemSegment* e = wasm_add_elem(c, out); - uint32_t flags = bin_uleb(&r); - uint32_t n; - if (flags != 0) - wasm_error(c, wasm_loc(0, 0), - "wasm: unsupported element segment kind"); - e->tableidx = 0; - if (bin_u8(&r) != 0x41) - wasm_error(c, wasm_loc(0, 0), - "wasm: expected i32.const element offset"); - e->offset = bin_sleb(&r, 32); - if (bin_u8(&r) != 0x0b || e->offset < 0) - wasm_error(c, wasm_loc(0, 0), "wasm: bad element offset"); - n = bin_uleb(&r); - if (n > 64u) - wasm_error(c, wasm_loc(0, 0), "wasm: too many element functions"); - for (uint32_t k = 0; k < n; ++k) e->funcs[e->nfuncs++] = bin_uleb(&r); - } - } else if (id == 10) { - uint32_t i, count = bin_uleb(&r); - uint32_t func_index = 0; - if (count != nfunc_types) - wasm_error(c, wasm_loc(0, 0), "wasm: function/code count mismatch"); - for (i = 0; i < count; ++i) { - uint32_t body_size = bin_uleb(&r); - size_t body_end; - uint32_t local_groups, j; - uint32_t control_depth = 0; - int saw_body_end = 0; - WasmFunc* f; - while (func_index < out->nfuncs && out->funcs[func_index].is_import) - func_index++; - if (func_index >= out->nfuncs) - wasm_error(c, wasm_loc(0, 0), "wasm: code body without function"); - f = &out->funcs[func_index++]; - bin_need(&r, body_size); - body_end = r.pos + body_size; - local_groups = bin_uleb(&r); - for (j = 0; j < local_groups; ++j) { - uint32_t k, nlocals = bin_uleb(&r); - WasmValType vt = bin_val_type(&r, 1); - if (nlocals > 32u || f->nlocals > 32u - nlocals) - wasm_error(c, wasm_loc(0, 0), "wasm: too many locals"); - for (k = 0; k < nlocals; ++k) f->locals[f->nlocals++] = vt; - } - while (r.pos < body_end) { - uint8_t op = bin_u8(&r); - if (op == 0x0bu) { - if (!control_depth) { - saw_body_end = 1; - break; - } - control_depth--; - wasm_func_add_insn(c, out, f, WASM_INSN_END, 0); - continue; - } - switch (op) { - case 0x00: - wasm_func_add_insn(c, out, f, WASM_INSN_UNREACHABLE, 0); - break; - case 0x01: - wasm_func_add_insn(c, out, f, WASM_INSN_NOP, 0); - break; - case 0x02: - if (bin_u8(&r) != 0x40u) - wasm_error(c, wasm_loc(0, 0), - "wasm: block results are unsupported"); - control_depth++; - wasm_func_add_insn(c, out, f, WASM_INSN_BLOCK, 0); - break; - case 0x03: - if (bin_u8(&r) != 0x40u) - wasm_error(c, wasm_loc(0, 0), - "wasm: loop results are unsupported"); - control_depth++; - wasm_func_add_insn(c, out, f, WASM_INSN_LOOP, 0); - break; - case 0x04: - if (bin_u8(&r) != 0x40u) - wasm_error(c, wasm_loc(0, 0), - "wasm: if results are unsupported"); - control_depth++; - wasm_func_add_insn(c, out, f, WASM_INSN_IF, 0); - break; - case 0x05: - wasm_func_add_insn(c, out, f, WASM_INSN_ELSE, 0); - break; - case 0x0c: - wasm_func_add_insn(c, out, f, WASM_INSN_BR, bin_uleb(&r)); - break; - case 0x0d: - wasm_func_add_insn(c, out, f, WASM_INSN_BR_IF, bin_uleb(&r)); - break; - case 0x0e: { - WasmInsn* in; - uint32_t n = bin_uleb(&r); - if (n >= 16u) - wasm_error(c, wasm_loc(0, 0), - "wasm: too many br_table targets"); - wasm_func_add_insn(c, out, f, WASM_INSN_BR_TABLE, 0); - in = &f->insns[f->ninsns - 1u]; - for (uint32_t k = 0; k < n; ++k) in->targets[k] = bin_uleb(&r); - in->targets[n] = bin_uleb(&r); - in->ntargets = n + 1u; - break; - } - case 0x0f: - wasm_func_add_insn(c, out, f, WASM_INSN_RETURN, 0); - break; - case 0x1a: - wasm_func_add_insn(c, out, f, WASM_INSN_DROP, 0); - break; - case 0x1b: - wasm_func_add_insn(c, out, f, WASM_INSN_SELECT, 0); - break; - case 0x28: - { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_LOAD, ma, mo, mi); } - break; - case 0x29: - { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_LOAD, ma, mo, mi); } - break; - case 0x2c: - { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_LOAD8_S, ma, mo, mi); } - break; - case 0x2d: - { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_LOAD8_U, ma, mo, mi); } - break; - case 0x2e: - { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_LOAD16_S, ma, mo, mi); } - break; - case 0x2f: - { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_LOAD16_U, ma, mo, mi); } - break; - case 0x30: - { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_LOAD8_S, ma, mo, mi); } - break; - case 0x31: - { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_LOAD8_U, ma, mo, mi); } - break; - case 0x32: - { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_LOAD16_S, ma, mo, mi); } - break; - case 0x33: - { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_LOAD16_U, ma, mo, mi); } - break; - case 0x34: - { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_LOAD32_S, ma, mo, mi); } - break; - case 0x35: - { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_LOAD32_U, ma, mo, mi); } - break; - case 0x36: - { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_STORE, ma, mo, mi); } - break; - case 0x37: - { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_STORE, ma, mo, mi); } - break; - case 0x3a: - { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_STORE8, ma, mo, mi); } - break; - case 0x3b: - { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_STORE16, ma, mo, mi); } - break; - case 0x3c: - { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_STORE8, ma, mo, mi); } - break; - case 0x3d: - { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_STORE16, ma, mo, mi); } - break; - case 0x3e: - { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_STORE32, ma, mo, mi); } - break; - case 0x3f: - wasm_func_add_insn(c, out, f, WASM_INSN_MEMORY_SIZE, 0); - f->insns[f->ninsns - 1u].memidx = bin_uleb(&r); - break; - case 0x40: - wasm_func_add_insn(c, out, f, WASM_INSN_MEMORY_GROW, 0); - f->insns[f->ninsns - 1u].memidx = bin_uleb(&r); - break; - case 0x10: - wasm_func_add_insn(c, out, f, WASM_INSN_CALL, bin_uleb(&r)); - break; - case 0x11: { - WasmInsn* in; - wasm_func_add_insn(c, out, f, WASM_INSN_CALL_INDIRECT, - bin_uleb(&r)); - in = &f->insns[f->ninsns - 1u]; - in->align = bin_uleb(&r); - break; - } - case 0x12: - wasm_func_add_insn(c, out, f, WASM_INSN_RETURN_CALL, - bin_uleb(&r)); - break; - case 0x13: { - WasmInsn* in; - wasm_func_add_insn(c, out, f, WASM_INSN_RETURN_CALL_INDIRECT, - bin_uleb(&r)); - in = &f->insns[f->ninsns - 1u]; - in->align = bin_uleb(&r); - break; - } - case 0x14: - wasm_func_add_insn(c, out, f, WASM_INSN_CALL_REF, - bin_uleb(&r)); - break; - case 0x15: - wasm_func_add_insn(c, out, f, WASM_INSN_RETURN_CALL_REF, - bin_uleb(&r)); - break; - case 0x20: - wasm_func_add_insn(c, out, f, WASM_INSN_LOCAL_GET, bin_uleb(&r)); - break; - case 0x21: - wasm_func_add_insn(c, out, f, WASM_INSN_LOCAL_SET, bin_uleb(&r)); - break; - case 0x22: - wasm_func_add_insn(c, out, f, WASM_INSN_LOCAL_TEE, bin_uleb(&r)); - break; - case 0x23: - wasm_func_add_insn(c, out, f, WASM_INSN_GLOBAL_GET, bin_uleb(&r)); - break; - case 0x24: - wasm_func_add_insn(c, out, f, WASM_INSN_GLOBAL_SET, bin_uleb(&r)); - break; - case 0x41: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_CONST, - bin_sleb(&r, 32)); - break; - case 0x42: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_CONST, - bin_sleb(&r, 64)); - break; - case 0x43: - wasm_func_add_fp_insn(c, out, f, WASM_INSN_F32_CONST, - bin_f32(&r)); - break; - case 0x44: - wasm_func_add_fp_insn(c, out, f, WASM_INSN_F64_CONST, - bin_f64(&r)); - break; - case 0xd0: - wasm_func_add_insn(c, out, f, WASM_INSN_REF_NULL, - bin_val_type(&r, 1)); - break; - case 0xd1: - wasm_func_add_insn(c, out, f, WASM_INSN_REF_IS_NULL, 0); - break; - case 0xd2: - wasm_func_add_insn(c, out, f, WASM_INSN_REF_FUNC, - bin_uleb(&r)); - break; - case 0x45: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_EQZ, 0); - break; - case 0x46: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_EQ, 0); - break; - case 0x47: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_NE, 0); - break; - case 0x48: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_LT_S, 0); - break; - case 0x49: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_LT_U, 0); - break; - case 0x4a: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_GT_S, 0); - break; - case 0x4b: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_GT_U, 0); - break; - case 0x4c: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_LE_S, 0); - break; - case 0x4d: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_LE_U, 0); - break; - case 0x4e: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_GE_S, 0); - break; - case 0x4f: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_GE_U, 0); - break; - case 0x50: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_EQZ, 0); - break; - case 0x51: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_EQ, 0); - break; - case 0x52: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_NE, 0); - break; - case 0x53: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_LT_S, 0); - break; - case 0x54: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_LT_U, 0); - break; - case 0x55: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_GT_S, 0); - break; - case 0x56: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_GT_U, 0); - break; - case 0x57: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_LE_S, 0); - break; - case 0x58: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_LE_U, 0); - break; - case 0x59: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_GE_S, 0); - break; - case 0x5a: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_GE_U, 0); - break; - case 0x6a: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_ADD, 0); - break; - case 0x6b: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_SUB, 0); - break; - case 0x6c: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_MUL, 0); - break; - case 0x6d: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_DIV_S, 0); - break; - case 0x6e: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_DIV_U, 0); - break; - case 0x6f: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_REM_S, 0); - break; - case 0x70: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_REM_U, 0); - break; - case 0x71: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_AND, 0); - break; - case 0x72: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_OR, 0); - break; - case 0x73: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_XOR, 0); - break; - case 0x74: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_SHL, 0); - break; - case 0x75: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_SHR_S, 0); - break; - case 0x76: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_SHR_U, 0); - break; - case 0x67: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_CLZ, 0); - break; - case 0x68: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_CTZ, 0); - break; - case 0x69: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_POPCNT, 0); - break; - case 0x77: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_ROTL, 0); - break; - case 0x78: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_ROTR, 0); - break; - case 0x79: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_CLZ, 0); - break; - case 0x7a: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_CTZ, 0); - break; - case 0x7b: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_POPCNT, 0); - break; - case 0x7c: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_ADD, 0); - break; - case 0x7d: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_SUB, 0); - break; - case 0x7e: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_MUL, 0); - break; - case 0x7f: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_DIV_S, 0); - break; - case 0x80: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_DIV_U, 0); - break; - case 0x81: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_REM_S, 0); - break; - case 0x82: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_REM_U, 0); - break; - case 0x83: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_AND, 0); - break; - case 0x84: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_OR, 0); - break; - case 0x85: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_XOR, 0); - break; - case 0x86: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_SHL, 0); - break; - case 0x87: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_SHR_S, 0); - break; - case 0x88: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_SHR_U, 0); - break; - case 0x89: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_ROTL, 0); - break; - case 0x8a: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_ROTR, 0); - break; - case 0x92: - wasm_func_add_insn(c, out, f, WASM_INSN_F32_ADD, 0); - break; - case 0x93: - wasm_func_add_insn(c, out, f, WASM_INSN_F32_SUB, 0); - break; - case 0x94: - wasm_func_add_insn(c, out, f, WASM_INSN_F32_MUL, 0); - break; - case 0x95: - wasm_func_add_insn(c, out, f, WASM_INSN_F32_DIV, 0); - break; - case 0xa0: - wasm_func_add_insn(c, out, f, WASM_INSN_F64_ADD, 0); - break; - case 0xa1: - wasm_func_add_insn(c, out, f, WASM_INSN_F64_SUB, 0); - break; - case 0xa2: - wasm_func_add_insn(c, out, f, WASM_INSN_F64_MUL, 0); - break; - case 0xa3: - wasm_func_add_insn(c, out, f, WASM_INSN_F64_DIV, 0); - break; - case 0x5b: - wasm_func_add_insn(c, out, f, WASM_INSN_F32_EQ, 0); - break; - case 0x5c: - wasm_func_add_insn(c, out, f, WASM_INSN_F32_NE, 0); - break; - case 0x5d: - wasm_func_add_insn(c, out, f, WASM_INSN_F32_LT, 0); - break; - case 0x5e: - wasm_func_add_insn(c, out, f, WASM_INSN_F32_GT, 0); - break; - case 0x5f: - wasm_func_add_insn(c, out, f, WASM_INSN_F32_LE, 0); - break; - case 0x60: - wasm_func_add_insn(c, out, f, WASM_INSN_F32_GE, 0); - break; - case 0x61: - wasm_func_add_insn(c, out, f, WASM_INSN_F64_EQ, 0); - break; - case 0x62: - wasm_func_add_insn(c, out, f, WASM_INSN_F64_NE, 0); - break; - case 0x63: - wasm_func_add_insn(c, out, f, WASM_INSN_F64_LT, 0); - break; - case 0x64: - wasm_func_add_insn(c, out, f, WASM_INSN_F64_GT, 0); - break; - case 0x65: - wasm_func_add_insn(c, out, f, WASM_INSN_F64_LE, 0); - break; - case 0x66: - wasm_func_add_insn(c, out, f, WASM_INSN_F64_GE, 0); - break; - case 0xa7: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_WRAP_I64, 0); - break; - case 0xa8: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_TRUNC_F32_S, 0); - break; - case 0xa9: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_TRUNC_F32_U, 0); - break; - case 0xaa: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_TRUNC_F64_S, 0); - break; - case 0xab: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_TRUNC_F64_U, 0); - break; - case 0xac: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_EXTEND_I32_S, 0); - break; - case 0xad: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_EXTEND_I32_U, 0); - break; - case 0xae: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_TRUNC_F32_S, 0); - break; - case 0xaf: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_TRUNC_F32_U, 0); - break; - case 0xb0: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_TRUNC_F64_S, 0); - break; - case 0xb1: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_TRUNC_F64_U, 0); - break; - case 0xb2: - wasm_func_add_insn(c, out, f, WASM_INSN_F32_CONVERT_I32_S, 0); - break; - case 0xb3: - wasm_func_add_insn(c, out, f, WASM_INSN_F32_CONVERT_I32_U, 0); - break; - case 0xb4: - wasm_func_add_insn(c, out, f, WASM_INSN_F32_CONVERT_I64_S, 0); - break; - case 0xb5: - wasm_func_add_insn(c, out, f, WASM_INSN_F32_CONVERT_I64_U, 0); - break; - case 0xb6: - wasm_func_add_insn(c, out, f, WASM_INSN_F32_DEMOTE_F64, 0); - break; - case 0xb7: - wasm_func_add_insn(c, out, f, WASM_INSN_F64_CONVERT_I32_S, 0); - break; - case 0xb8: - wasm_func_add_insn(c, out, f, WASM_INSN_F64_CONVERT_I32_U, 0); - break; - case 0xb9: - wasm_func_add_insn(c, out, f, WASM_INSN_F64_CONVERT_I64_S, 0); - break; - case 0xba: - wasm_func_add_insn(c, out, f, WASM_INSN_F64_CONVERT_I64_U, 0); - break; - case 0xbb: - wasm_func_add_insn(c, out, f, WASM_INSN_F64_PROMOTE_F32, 0); - break; - case 0xbc: - wasm_func_add_insn(c, out, f, WASM_INSN_I32_REINTERPRET_F32, 0); - break; - case 0xbd: - wasm_func_add_insn(c, out, f, WASM_INSN_I64_REINTERPRET_F64, 0); - break; - case 0xbe: - wasm_func_add_insn(c, out, f, WASM_INSN_F32_REINTERPRET_I32, 0); - break; - case 0xbf: - wasm_func_add_insn(c, out, f, WASM_INSN_F64_REINTERPRET_I64, 0); - break; - case 0xfe: { - uint32_t sub = bin_uleb(&r); - uint32_t ma, mi; - uint64_t mo; - switch (sub) { - case 0x00: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, - WASM_INSN_MEMORY_ATOMIC_NOTIFY, ma, - mo, mi); - break; - case 0x01: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_ATOMIC_WAIT, - ma, mo, mi); - break; - case 0x02: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_ATOMIC_WAIT, - ma, mo, mi); - break; - case 0x03: - if (bin_u8(&r) != 0) - wasm_error(c, wasm_loc(0, 0), "wasm: bad atomic.fence"); - wasm_func_add_insn(c, out, f, WASM_INSN_ATOMIC_FENCE, 0); - break; - case 0x10: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_ATOMIC_LOAD, - ma, mo, mi); - break; - case 0x11: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_ATOMIC_LOAD, - ma, mo, mi); - break; - case 0x12: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, - WASM_INSN_I32_ATOMIC_LOAD8_U, ma, mo, - mi); - break; - case 0x13: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, - WASM_INSN_I32_ATOMIC_LOAD16_U, ma, mo, - mi); - break; - case 0x14: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, - WASM_INSN_I64_ATOMIC_LOAD8_U, ma, mo, - mi); - break; - case 0x15: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, - WASM_INSN_I64_ATOMIC_LOAD16_U, ma, mo, - mi); - break; - case 0x16: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, - WASM_INSN_I64_ATOMIC_LOAD32_U, ma, mo, - mi); - break; - case 0x17: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_ATOMIC_STORE, - ma, mo, mi); - break; - case 0x18: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_ATOMIC_STORE, - ma, mo, mi); - break; - case 0x19: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, - WASM_INSN_I32_ATOMIC_STORE8, ma, mo, - mi); - break; - case 0x1a: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, - WASM_INSN_I32_ATOMIC_STORE16, ma, mo, - mi); - break; - case 0x1b: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, - WASM_INSN_I64_ATOMIC_STORE8, ma, mo, - mi); - break; - case 0x1c: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, - WASM_INSN_I64_ATOMIC_STORE16, ma, mo, - mi); - break; - case 0x1d: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, - WASM_INSN_I64_ATOMIC_STORE32, ma, mo, - mi); - break; - case 0x1e: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, - WASM_INSN_I32_ATOMIC_RMW_ADD, ma, mo, - mi); - break; - case 0x1f: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, - WASM_INSN_I64_ATOMIC_RMW_ADD, ma, mo, - mi); - break; - case 0x25: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, - WASM_INSN_I32_ATOMIC_RMW_SUB, ma, mo, - mi); - break; - case 0x26: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, - WASM_INSN_I64_ATOMIC_RMW_SUB, ma, mo, - mi); - break; - case 0x2c: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, - WASM_INSN_I32_ATOMIC_RMW_AND, ma, mo, - mi); - break; - case 0x2d: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, - WASM_INSN_I64_ATOMIC_RMW_AND, ma, mo, - mi); - break; - case 0x33: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, - WASM_INSN_I32_ATOMIC_RMW_OR, ma, mo, - mi); - break; - case 0x34: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, - WASM_INSN_I64_ATOMIC_RMW_OR, ma, mo, - mi); - break; - case 0x3a: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, - WASM_INSN_I32_ATOMIC_RMW_XOR, ma, mo, - mi); - break; - case 0x3b: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, - WASM_INSN_I64_ATOMIC_RMW_XOR, ma, mo, - mi); - break; - case 0x41: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, - WASM_INSN_I32_ATOMIC_RMW_XCHG, ma, mo, - mi); - break; - case 0x42: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, - WASM_INSN_I64_ATOMIC_RMW_XCHG, ma, mo, - mi); - break; - case 0x48: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, - WASM_INSN_I32_ATOMIC_RMW_CMPXCHG, ma, - mo, mi); - break; - case 0x49: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, - WASM_INSN_I64_ATOMIC_RMW_CMPXCHG, ma, - mo, mi); - break; - default: - wasm_error(c, wasm_loc(0, 0), - "wasm: unsupported threads opcode 0x%x", sub); - } - break; - } - default: - wasm_error(c, wasm_loc(0, 0), "wasm: unsupported opcode 0x%02x", - op); - } - } - if (!saw_body_end) - wasm_error(c, wasm_loc(0, 0), "wasm: function body missing end"); - r.pos = body_end; - } - } else if (id == 11) { - uint32_t i, count = bin_uleb(&r); - for (i = 0; i < count; ++i) { - uint32_t flags = bin_uleb(&r); - int64_t offset = 0; - uint32_t memidx = 0; - uint32_t n; - if (flags == 2u) - memidx = bin_uleb(&r); - else if (flags != 0) - wasm_error(c, wasm_loc(0, 0), "wasm: passive data unsupported"); - { - uint8_t op = bin_u8(&r); - if (op == 0x41) - offset = bin_sleb(&r, 32); - else if (op == 0x42) - offset = bin_sleb(&r, 64); - else - wasm_error(c, wasm_loc(0, 0), - "wasm: expected const data offset"); - } - if (bin_u8(&r) != 0x0b || offset < 0) - wasm_error(c, wasm_loc(0, 0), "wasm: bad data offset"); - n = bin_uleb(&r); - bin_need(&r, n); - wasm_memory_ensure(c, out, memidx, (uint64_t)offset + n); - memcpy(out->memories[memidx].data + (uint32_t)offset, r.data + r.pos, - n); - if ((uint64_t)offset + n > out->memories[memidx].data_init_len) - out->memories[memidx].data_init_len = (uint64_t)offset + n; - r.pos += n; - } - } else if (id == 12) { - wasm_error(c, wasm_loc(0, 0), - "wasm: data count sections are unsupported"); - } else { - r.pos = end; - } - if (r.pos != end) - wasm_error(c, wasm_loc(0, 0), "wasm: malformed section length"); - } -} - -int wasm_is_binary(const CfreeSlice* input) { - return input->len >= 4u && input->data[0] == 0x00 && input->data[1] == 0x61 && - input->data[2] == 0x73 && input->data[3] == 0x6d; -} diff --git a/lang/wasm/encode.c b/lang/wasm/encode.c @@ -1,778 +0,0 @@ -#include "wasm_internal.h" - -static void write_byte(CfreeWriter* w, uint8_t b) { w->write(w, &b, 1); } - -static void write_uleb(CfreeWriter* w, uint64_t v) { - do { - uint8_t b = (uint8_t)(v & 0x7fu); - v >>= 7u; - if (v) b |= 0x80u; - write_byte(w, b); - } while (v); -} - -static void write_sleb(CfreeWriter* w, int64_t v) { - int more = 1; - while (more) { - uint8_t b = (uint8_t)(v & 0x7f); - int sign = b & 0x40; - v >>= 7; - if ((v == 0 && !sign) || (v == -1 && sign)) - more = 0; - else - b |= 0x80u; - write_byte(w, b); - } -} - -static void write_f32(CfreeWriter* w, double value) { - float f = (float)value; - uint32_t bits; - memcpy(&bits, &f, sizeof bits); - for (uint32_t i = 0; i < 4u; ++i) write_byte(w, (uint8_t)(bits >> (i * 8u))); -} - -static void write_f64(CfreeWriter* w, double value) { - uint64_t bits; - memcpy(&bits, &value, sizeof bits); - for (uint32_t i = 0; i < 8u; ++i) write_byte(w, (uint8_t)(bits >> (i * 8u))); -} - -static void write_name(CfreeWriter* w, const WasmModule* m, CfreeSym sym) { - CfreeSlice s = cfree_sym_str(m->c, sym); - write_uleb(w, s.len); - if (s.len) w->write(w, s.s, s.len); -} - -static void encode_section(CfreeHeap* h, CfreeWriter* out, uint8_t id, - void (*fn)(CfreeWriter*, const WasmModule*), - const WasmModule* m) { - CfreeWriter* tmp = NULL; - size_t len; - const uint8_t* bytes; - if (cfree_writer_mem(h, &tmp) != CFREE_OK) return; - fn(tmp, m); - bytes = cfree_writer_mem_bytes(tmp, &len); - write_byte(out, id); - write_uleb(out, len); - out->write(out, bytes, len); - cfree_writer_close(tmp); -} - -static void enc_type(CfreeWriter* w, const WasmModule* m) { - uint32_t i, j; - write_uleb(w, m->ntypes); - for (i = 0; i < m->ntypes; ++i) { - const WasmFuncType* f = &m->types[i]; - write_byte(w, 0x60); - write_uleb(w, f->nparams); - for (j = 0; j < f->nparams; ++j) write_byte(w, (uint8_t)f->params[j]); - write_uleb(w, f->nresults); - for (j = 0; j < f->nresults; ++j) write_byte(w, (uint8_t)f->results[j]); - } -} - -static void write_memory_limits(CfreeWriter* w, const WasmMemory* mem) { - uint32_t flags = (mem->has_max ? 1u : 0u) | (mem->shared ? 2u : 0u) | - (mem->is64 ? 4u : 0u); - write_uleb(w, flags); - write_uleb(w, mem->min_pages); - if (mem->has_max) write_uleb(w, mem->max_pages); -} - -static void write_limits(CfreeWriter* w, uint32_t min, uint32_t max, - int has_max) { - write_uleb(w, has_max ? 1 : 0); - write_uleb(w, min); - if (has_max) write_uleb(w, max); -} - -static void enc_import(CfreeWriter* w, const WasmModule* m) { - uint32_t i, n = 0; - for (i = 0; i < m->nfuncs; ++i) - if (m->funcs[i].is_import) n++; - for (i = 0; i < m->ntables; ++i) - if (m->tables[i].is_import) n++; - for (i = 0; i < m->nmemories; ++i) - if (m->memories[i].is_import) n++; - for (i = 0; i < m->nglobals; ++i) - if (m->globals[i].is_import) n++; - write_uleb(w, n); - for (i = 0; i < m->nfuncs; ++i) { - const WasmFunc* f = &m->funcs[i]; - if (!f->is_import) continue; - write_name(w, m, f->import_module); - write_name(w, m, f->import_name); - write_byte(w, 0); - write_uleb(w, f->typeidx); - } - for (i = 0; i < m->ntables; ++i) { - const WasmTable* t = &m->tables[i]; - if (!t->is_import) continue; - write_name(w, m, t->import_module); - write_name(w, m, t->import_name); - write_byte(w, 1); - write_byte(w, (uint8_t)t->elem_type); - write_limits(w, t->min, t->max, t->has_max); - } - for (i = 0; i < m->nmemories; ++i) { - const WasmMemory* mem = &m->memories[i]; - if (!mem->is_import) continue; - write_name(w, m, mem->import_module); - write_name(w, m, mem->import_name); - write_byte(w, 2); - write_memory_limits(w, mem); - } - for (i = 0; i < m->nglobals; ++i) { - const WasmGlobal* g = &m->globals[i]; - if (!g->is_import) continue; - write_name(w, m, g->import_module); - write_name(w, m, g->import_name); - write_byte(w, 3); - write_byte(w, (uint8_t)g->type); - write_byte(w, g->mutable_); - } -} - -static int wasm_module_has_imports(const WasmModule* m) { - uint32_t i; - for (i = 0; i < m->nfuncs; ++i) - if (m->funcs[i].is_import) return 1; - for (i = 0; i < m->ntables; ++i) - if (m->tables[i].is_import) return 1; - for (i = 0; i < m->nmemories; ++i) - if (m->memories[i].is_import) return 1; - for (i = 0; i < m->nglobals; ++i) - if (m->globals[i].is_import) return 1; - return 0; -} - -static void enc_func(CfreeWriter* w, const WasmModule* m) { - uint32_t i; - uint32_t n = 0; - for (i = 0; i < m->nfuncs; ++i) - if (!m->funcs[i].is_import) n++; - write_uleb(w, n); - for (i = 0; i < m->nfuncs; ++i) - if (!m->funcs[i].is_import) write_uleb(w, m->funcs[i].typeidx); -} - -static void enc_export(CfreeWriter* w, const WasmModule* m) { - uint32_t i; - write_uleb(w, m->nexports); - for (i = 0; i < m->nexports; ++i) { - write_name(w, m, m->exports[i].name); - write_byte(w, m->exports[i].kind); - write_uleb(w, m->exports[i].index); - } -} - -static void enc_memory(CfreeWriter* w, const WasmModule* m) { - uint32_t i, n = 0; - for (i = 0; i < m->nmemories; ++i) - if (!m->memories[i].is_import) n++; - write_uleb(w, n); - for (i = 0; i < m->nmemories; ++i) - if (!m->memories[i].is_import) write_memory_limits(w, &m->memories[i]); -} - -static uint8_t wasm_opcode(uint8_t kind); - -static void enc_table(CfreeWriter* w, const WasmModule* m) { - uint32_t i, n = 0; - for (i = 0; i < m->ntables; ++i) - if (!m->tables[i].is_import) n++; - write_uleb(w, n); - for (i = 0; i < m->ntables; ++i) { - const WasmTable* t = &m->tables[i]; - if (t->is_import) continue; - write_byte(w, (uint8_t)t->elem_type); - write_limits(w, t->min, t->max, t->has_max); - } -} - -static void enc_global(CfreeWriter* w, const WasmModule* m) { - uint32_t i, n = 0; - for (i = 0; i < m->nglobals; ++i) - if (!m->globals[i].is_import) n++; - write_uleb(w, n); - for (i = 0; i < m->nglobals; ++i) { - const WasmGlobal* g = &m->globals[i]; - if (g->is_import) continue; - write_byte(w, (uint8_t)g->type); - write_byte(w, g->mutable_); - write_byte(w, wasm_opcode(g->init.kind)); - if (g->init.kind == WASM_INSN_I32_CONST || - g->init.kind == WASM_INSN_I64_CONST) - write_sleb(w, g->init.imm); - else if (g->init.kind == WASM_INSN_F32_CONST) - write_f32(w, g->init.fp); - else if (g->init.kind == WASM_INSN_F64_CONST) - write_f64(w, g->init.fp); - write_byte(w, 0x0b); - } -} - -static uint8_t wasm_opcode(uint8_t kind) { - switch (kind) { - case WASM_INSN_UNREACHABLE: - return 0x00; - case WASM_INSN_NOP: - return 0x01; - case WASM_INSN_BLOCK: - return 0x02; - case WASM_INSN_LOOP: - return 0x03; - case WASM_INSN_IF: - return 0x04; - case WASM_INSN_ELSE: - return 0x05; - case WASM_INSN_END: - return 0x0b; - case WASM_INSN_BR: - return 0x0c; - case WASM_INSN_BR_IF: - return 0x0d; - case WASM_INSN_BR_TABLE: - return 0x0e; - case WASM_INSN_I32_CONST: - return 0x41; - case WASM_INSN_I64_CONST: - return 0x42; - case WASM_INSN_F32_CONST: - return 0x43; - case WASM_INSN_F64_CONST: - return 0x44; - case WASM_INSN_LOCAL_GET: - return 0x20; - case WASM_INSN_LOCAL_SET: - return 0x21; - case WASM_INSN_LOCAL_TEE: - return 0x22; - case WASM_INSN_CALL: - return 0x10; - case WASM_INSN_CALL_INDIRECT: - return 0x11; - case WASM_INSN_RETURN_CALL: - return 0x12; - case WASM_INSN_RETURN_CALL_INDIRECT: - return 0x13; - case WASM_INSN_CALL_REF: - return 0x14; - case WASM_INSN_RETURN_CALL_REF: - return 0x15; - case WASM_INSN_REF_NULL: - return 0xd0; - case WASM_INSN_REF_IS_NULL: - return 0xd1; - case WASM_INSN_REF_FUNC: - return 0xd2; - case WASM_INSN_GLOBAL_GET: - return 0x23; - case WASM_INSN_GLOBAL_SET: - return 0x24; - case WASM_INSN_RETURN: - return 0x0f; - case WASM_INSN_DROP: - return 0x1a; - case WASM_INSN_SELECT: - return 0x1b; - case WASM_INSN_I32_LOAD: - return 0x28; - case WASM_INSN_I64_LOAD: - return 0x29; - case WASM_INSN_I32_LOAD8_S: - return 0x2c; - case WASM_INSN_I32_LOAD8_U: - return 0x2d; - case WASM_INSN_I32_LOAD16_S: - return 0x2e; - case WASM_INSN_I32_LOAD16_U: - return 0x2f; - case WASM_INSN_I64_LOAD8_S: - return 0x30; - case WASM_INSN_I64_LOAD8_U: - return 0x31; - case WASM_INSN_I64_LOAD16_S: - return 0x32; - case WASM_INSN_I64_LOAD16_U: - return 0x33; - case WASM_INSN_I64_LOAD32_S: - return 0x34; - case WASM_INSN_I64_LOAD32_U: - return 0x35; - case WASM_INSN_I32_STORE: - return 0x36; - case WASM_INSN_I64_STORE: - return 0x37; - case WASM_INSN_I32_STORE8: - return 0x3a; - case WASM_INSN_I32_STORE16: - return 0x3b; - case WASM_INSN_I64_STORE8: - return 0x3c; - case WASM_INSN_I64_STORE16: - return 0x3d; - case WASM_INSN_I64_STORE32: - return 0x3e; - case WASM_INSN_MEMORY_SIZE: - return 0x3f; - case WASM_INSN_MEMORY_GROW: - return 0x40; - case WASM_INSN_I32_EQZ: - return 0x45; - case WASM_INSN_I32_EQ: - return 0x46; - case WASM_INSN_I32_NE: - return 0x47; - case WASM_INSN_I32_LT_S: - return 0x48; - case WASM_INSN_I32_LT_U: - return 0x49; - case WASM_INSN_I32_GT_S: - return 0x4a; - case WASM_INSN_I32_GT_U: - return 0x4b; - case WASM_INSN_I32_LE_S: - return 0x4c; - case WASM_INSN_I32_LE_U: - return 0x4d; - case WASM_INSN_I32_GE_S: - return 0x4e; - case WASM_INSN_I32_GE_U: - return 0x4f; - case WASM_INSN_I64_EQZ: - return 0x50; - case WASM_INSN_I64_EQ: - return 0x51; - case WASM_INSN_I64_NE: - return 0x52; - case WASM_INSN_I64_LT_S: - return 0x53; - case WASM_INSN_I64_LT_U: - return 0x54; - case WASM_INSN_I64_GT_S: - return 0x55; - case WASM_INSN_I64_GT_U: - return 0x56; - case WASM_INSN_I64_LE_S: - return 0x57; - case WASM_INSN_I64_LE_U: - return 0x58; - case WASM_INSN_I64_GE_S: - return 0x59; - case WASM_INSN_I64_GE_U: - return 0x5a; - case WASM_INSN_I32_ADD: - return 0x6a; - case WASM_INSN_I32_SUB: - return 0x6b; - case WASM_INSN_I32_MUL: - return 0x6c; - case WASM_INSN_I32_DIV_S: - return 0x6d; - case WASM_INSN_I32_DIV_U: - return 0x6e; - case WASM_INSN_I32_REM_S: - return 0x6f; - case WASM_INSN_I32_REM_U: - return 0x70; - case WASM_INSN_I32_AND: - return 0x71; - case WASM_INSN_I32_OR: - return 0x72; - case WASM_INSN_I32_XOR: - return 0x73; - case WASM_INSN_I32_SHL: - return 0x74; - case WASM_INSN_I32_SHR_S: - return 0x75; - case WASM_INSN_I32_SHR_U: - return 0x76; - case WASM_INSN_I32_CLZ: - return 0x67; - case WASM_INSN_I32_CTZ: - return 0x68; - case WASM_INSN_I32_POPCNT: - return 0x69; - case WASM_INSN_I32_ROTL: - return 0x77; - case WASM_INSN_I32_ROTR: - return 0x78; - case WASM_INSN_I64_CLZ: - return 0x79; - case WASM_INSN_I64_CTZ: - return 0x7a; - case WASM_INSN_I64_POPCNT: - return 0x7b; - case WASM_INSN_I64_ADD: - return 0x7c; - case WASM_INSN_I64_SUB: - return 0x7d; - case WASM_INSN_I64_MUL: - return 0x7e; - case WASM_INSN_I64_DIV_S: - return 0x7f; - case WASM_INSN_I64_DIV_U: - return 0x80; - case WASM_INSN_I64_REM_S: - return 0x81; - case WASM_INSN_I64_REM_U: - return 0x82; - case WASM_INSN_I64_AND: - return 0x83; - case WASM_INSN_I64_OR: - return 0x84; - case WASM_INSN_I64_XOR: - return 0x85; - case WASM_INSN_I64_SHL: - return 0x86; - case WASM_INSN_I64_SHR_S: - return 0x87; - case WASM_INSN_I64_SHR_U: - return 0x88; - case WASM_INSN_I64_ROTL: - return 0x89; - case WASM_INSN_I64_ROTR: - return 0x8a; - case WASM_INSN_F32_ADD: - return 0x92; - case WASM_INSN_F32_SUB: - return 0x93; - case WASM_INSN_F32_MUL: - return 0x94; - case WASM_INSN_F32_DIV: - return 0x95; - case WASM_INSN_F64_ADD: - return 0xa0; - case WASM_INSN_F64_SUB: - return 0xa1; - case WASM_INSN_F64_MUL: - return 0xa2; - case WASM_INSN_F64_DIV: - return 0xa3; - case WASM_INSN_F32_EQ: - return 0x5b; - case WASM_INSN_F32_NE: - return 0x5c; - case WASM_INSN_F32_LT: - return 0x5d; - case WASM_INSN_F32_GT: - return 0x5e; - case WASM_INSN_F32_LE: - return 0x5f; - case WASM_INSN_F32_GE: - return 0x60; - case WASM_INSN_F64_EQ: - return 0x61; - case WASM_INSN_F64_NE: - return 0x62; - case WASM_INSN_F64_LT: - return 0x63; - case WASM_INSN_F64_GT: - return 0x64; - case WASM_INSN_F64_LE: - return 0x65; - case WASM_INSN_F64_GE: - return 0x66; - case WASM_INSN_I32_WRAP_I64: - return 0xa7; - case WASM_INSN_I32_TRUNC_F32_S: - return 0xa8; - case WASM_INSN_I32_TRUNC_F32_U: - return 0xa9; - case WASM_INSN_I32_TRUNC_F64_S: - return 0xaa; - case WASM_INSN_I32_TRUNC_F64_U: - return 0xab; - case WASM_INSN_I64_EXTEND_I32_S: - return 0xac; - case WASM_INSN_I64_EXTEND_I32_U: - return 0xad; - case WASM_INSN_I64_TRUNC_F32_S: - return 0xae; - case WASM_INSN_I64_TRUNC_F32_U: - return 0xaf; - case WASM_INSN_I64_TRUNC_F64_S: - return 0xb0; - case WASM_INSN_I64_TRUNC_F64_U: - return 0xb1; - case WASM_INSN_F32_CONVERT_I32_S: - return 0xb2; - case WASM_INSN_F32_CONVERT_I32_U: - return 0xb3; - case WASM_INSN_F32_CONVERT_I64_S: - return 0xb4; - case WASM_INSN_F32_CONVERT_I64_U: - return 0xb5; - case WASM_INSN_F32_DEMOTE_F64: - return 0xb6; - case WASM_INSN_F64_CONVERT_I32_S: - return 0xb7; - case WASM_INSN_F64_CONVERT_I32_U: - return 0xb8; - case WASM_INSN_F64_CONVERT_I64_S: - return 0xb9; - case WASM_INSN_F64_CONVERT_I64_U: - return 0xba; - case WASM_INSN_F64_PROMOTE_F32: - return 0xbb; - case WASM_INSN_I32_REINTERPRET_F32: - return 0xbc; - case WASM_INSN_I64_REINTERPRET_F64: - return 0xbd; - case WASM_INSN_F32_REINTERPRET_I32: - return 0xbe; - case WASM_INSN_F64_REINTERPRET_I64: - return 0xbf; - } - return 0; -} - -static uint32_t wasm_threads_subopcode(uint8_t kind) { - switch (kind) { - case WASM_INSN_MEMORY_ATOMIC_NOTIFY: - return 0x00; - case WASM_INSN_I32_ATOMIC_WAIT: - return 0x01; - case WASM_INSN_I64_ATOMIC_WAIT: - return 0x02; - case WASM_INSN_ATOMIC_FENCE: - return 0x03; - case WASM_INSN_I32_ATOMIC_LOAD: - return 0x10; - case WASM_INSN_I64_ATOMIC_LOAD: - return 0x11; - case WASM_INSN_I32_ATOMIC_LOAD8_U: - return 0x12; - case WASM_INSN_I32_ATOMIC_LOAD16_U: - return 0x13; - case WASM_INSN_I64_ATOMIC_LOAD8_U: - return 0x14; - case WASM_INSN_I64_ATOMIC_LOAD16_U: - return 0x15; - case WASM_INSN_I64_ATOMIC_LOAD32_U: - return 0x16; - case WASM_INSN_I32_ATOMIC_STORE: - return 0x17; - case WASM_INSN_I64_ATOMIC_STORE: - return 0x18; - case WASM_INSN_I32_ATOMIC_STORE8: - return 0x19; - case WASM_INSN_I32_ATOMIC_STORE16: - return 0x1a; - case WASM_INSN_I64_ATOMIC_STORE8: - return 0x1b; - case WASM_INSN_I64_ATOMIC_STORE16: - return 0x1c; - case WASM_INSN_I64_ATOMIC_STORE32: - return 0x1d; - case WASM_INSN_I32_ATOMIC_RMW_ADD: - return 0x1e; - case WASM_INSN_I64_ATOMIC_RMW_ADD: - return 0x1f; - case WASM_INSN_I32_ATOMIC_RMW_SUB: - return 0x25; - case WASM_INSN_I64_ATOMIC_RMW_SUB: - return 0x26; - case WASM_INSN_I32_ATOMIC_RMW_AND: - return 0x2c; - case WASM_INSN_I64_ATOMIC_RMW_AND: - return 0x2d; - case WASM_INSN_I32_ATOMIC_RMW_OR: - return 0x33; - case WASM_INSN_I64_ATOMIC_RMW_OR: - return 0x34; - case WASM_INSN_I32_ATOMIC_RMW_XOR: - return 0x3a; - case WASM_INSN_I64_ATOMIC_RMW_XOR: - return 0x3b; - case WASM_INSN_I32_ATOMIC_RMW_XCHG: - return 0x41; - case WASM_INSN_I64_ATOMIC_RMW_XCHG: - return 0x42; - case WASM_INSN_I32_ATOMIC_RMW_CMPXCHG: - return 0x48; - case WASM_INSN_I64_ATOMIC_RMW_CMPXCHG: - return 0x49; - default: - return UINT32_MAX; - } -} - -static void enc_code(CfreeWriter* w, const WasmModule* m) { - uint32_t i, j; - uint32_t n = 0; - for (i = 0; i < m->nfuncs; ++i) - if (!m->funcs[i].is_import) n++; - write_uleb(w, n); - for (i = 0; i < m->nfuncs; ++i) { - CfreeWriter* body = NULL; - size_t len; - const uint8_t* bytes; - if (cfree_writer_mem(m->heap, &body) != CFREE_OK) continue; - if (m->funcs[i].is_import) { - cfree_writer_close(body); - continue; - } - if (m->funcs[i].nlocals) { - uint32_t group_count = 0; - WasmValType prev = 0; - for (j = 0; j < m->funcs[i].nlocals; ++j) { - if (j == 0 || m->funcs[i].locals[j] != prev) { - group_count++; - prev = m->funcs[i].locals[j]; - } - } - write_uleb(body, group_count); - for (j = 0; j < m->funcs[i].nlocals;) { - uint32_t k = j + 1u; - while (k < m->funcs[i].nlocals && - m->funcs[i].locals[k] == m->funcs[i].locals[j]) - k++; - write_uleb(body, k - j); - write_byte(body, (uint8_t)m->funcs[i].locals[j]); - j = k; - } - } else { - write_uleb(body, 0); - } - for (j = 0; j < m->funcs[i].ninsns; ++j) { - WasmInsn in = m->funcs[i].insns[j]; - uint8_t op = wasm_opcode(in.kind); - uint32_t threads_op = wasm_threads_subopcode(in.kind); - if (threads_op != UINT32_MAX) { - write_byte(body, 0xfe); - write_uleb(body, threads_op); - } else { - write_byte(body, op); - } - if (in.kind == WASM_INSN_ATOMIC_FENCE) { - write_byte(body, 0); - } else if (in.kind == WASM_INSN_BLOCK || in.kind == WASM_INSN_LOOP || - in.kind == WASM_INSN_IF) - write_byte(body, 0x40); - else if (in.kind == WASM_INSN_I32_CONST || in.kind == WASM_INSN_I64_CONST) - write_sleb(body, in.imm); - else if (in.kind == WASM_INSN_F32_CONST) - write_f32(body, in.fp); - else if (in.kind == WASM_INSN_F64_CONST) - write_f64(body, in.fp); - else if (in.kind == WASM_INSN_LOCAL_GET || - in.kind == WASM_INSN_LOCAL_SET || - in.kind == WASM_INSN_LOCAL_TEE || - in.kind == WASM_INSN_GLOBAL_GET || - in.kind == WASM_INSN_GLOBAL_SET || in.kind == WASM_INSN_CALL || - in.kind == WASM_INSN_RETURN_CALL || - in.kind == WASM_INSN_CALL_REF || - in.kind == WASM_INSN_RETURN_CALL_REF || - in.kind == WASM_INSN_REF_NULL || - in.kind == WASM_INSN_REF_FUNC || - in.kind == WASM_INSN_BR || in.kind == WASM_INSN_BR_IF) - write_uleb(body, (uint64_t)in.imm); - else if (in.kind == WASM_INSN_CALL_INDIRECT || - in.kind == WASM_INSN_RETURN_CALL_INDIRECT) { - write_uleb(body, (uint64_t)in.imm); - write_uleb(body, in.align); - } else if (in.kind == WASM_INSN_BR_TABLE) { - write_uleb(body, in.ntargets ? in.ntargets - 1u : 0u); - for (uint32_t k = 0; k < in.ntargets; ++k) - write_uleb(body, in.targets[k]); - } else if (wasm_insn_is_mem(in.kind)) { - write_uleb(body, in.memidx ? in.align + 64u : in.align); - if (in.memidx) write_uleb(body, in.memidx); - write_uleb(body, in.offset64); - } else if (in.kind == WASM_INSN_MEMORY_SIZE || - in.kind == WASM_INSN_MEMORY_GROW) { - write_uleb(body, in.memidx); - } - } - write_byte(body, 0x0b); - bytes = cfree_writer_mem_bytes(body, &len); - write_uleb(w, len); - w->write(w, bytes, len); - cfree_writer_close(body); - } -} - -static void enc_data(CfreeWriter* w, const WasmModule* m) { - uint32_t i, n = 0; - for (i = 0; i < m->nmemories; ++i) - if (m->memories[i].data_init_len) n++; - write_uleb(w, n); - for (i = 0; i < m->nmemories; ++i) { - const WasmMemory* mem = &m->memories[i]; - if (!mem->data_init_len) continue; - write_uleb(w, i ? 2u : 0u); - if (i) write_uleb(w, i); - write_byte(w, mem->is64 ? 0x42 : 0x41); - write_sleb(w, 0); - write_byte(w, 0x0b); - write_uleb(w, mem->data_init_len); - w->write(w, mem->data, (size_t)mem->data_init_len); - } -} - -static void enc_elem(CfreeWriter* w, const WasmModule* m) { - uint32_t i, j; - write_uleb(w, m->nelems); - for (i = 0; i < m->nelems; ++i) { - const WasmElemSegment* e = &m->elems[i]; - write_uleb(w, 0); - write_byte(w, 0x41); - write_sleb(w, e->offset); - write_byte(w, 0x0b); - write_uleb(w, e->nfuncs); - for (j = 0; j < e->nfuncs; ++j) write_uleb(w, e->funcs[j]); - } -} - -static void enc_start(CfreeWriter* w, const WasmModule* m) { - write_uleb(w, m->start_func); -} - -static void encode_custom(CfreeHeap* h, CfreeWriter* out, const WasmModule* m, - const WasmCustom* cs) { - CfreeWriter* tmp = NULL; - size_t len; - const uint8_t* bytes; - if (cfree_writer_mem(h, &tmp) != CFREE_OK) return; - write_name(tmp, m, cs->name); - if (cs->len) tmp->write(tmp, cs->data, cs->len); - bytes = cfree_writer_mem_bytes(tmp, &len); - write_byte(out, 0); - write_uleb(out, len); - out->write(out, bytes, len); - cfree_writer_close(tmp); -} - -void wasm_encode(CfreeCompiler* c, const WasmModule* m, - CfreeWriter* out) { - static const uint8_t magic[] = {0x00, 0x61, 0x73, 0x6d, - 0x01, 0x00, 0x00, 0x00}; - CfreeHeap* h = cfree_compiler_context(c)->heap; - out->write(out, magic, sizeof magic); - for (uint32_t i = 0; i < m->ncustoms; ++i) - encode_custom(h, out, m, &m->customs[i]); - encode_section(h, out, 1, enc_type, m); - if (wasm_module_has_imports(m)) encode_section(h, out, 2, enc_import, m); - encode_section(h, out, 3, enc_func, m); - if (m->ntables) encode_section(h, out, 4, enc_table, m); - { - int has_defined_memory = 0; - for (uint32_t i = 0; i < m->nmemories; ++i) - if (!m->memories[i].is_import) has_defined_memory = 1; - if (has_defined_memory) encode_section(h, out, 5, enc_memory, m); - } - if (m->nglobals) encode_section(h, out, 6, enc_global, m); - encode_section(h, out, 7, enc_export, m); - if (m->has_start) encode_section(h, out, 8, enc_start, m); - if (m->nelems) encode_section(h, out, 9, enc_elem, m); - encode_section(h, out, 10, enc_code, m); - { - int has_data = 0; - for (uint32_t i = 0; i < m->nmemories; ++i) - if (m->memories[i].data_init_len) has_data = 1; - if (has_data) encode_section(h, out, 11, enc_data, m); - } -} diff --git a/lang/wasm/host_imports.c b/lang/wasm/host_imports.c @@ -0,0 +1,145 @@ +/* Host-import binder: walks the per-module __cfree_wasm_imports metadata + * emitted by lang/wasm/cg.c and writes resolved function pointers into the + * matching slots of a freshly-allocated CfreeWasmInstance. + * + * The metadata wire format is defined in lang/wasm/runtime_abi.h alongside + * the rest of the cfree-instance ABI. The public-API surface is in + * include/cfree/wasm.h. */ + +#include "runtime_abi.h" + +#include <cfree/core.h> +#include <cfree/jit.h> +#include <cfree/wasm.h> + +#include <stdint.h> +#include <string.h> + +/* Raw wasm WasmValType byte encoding (mirrored from src/wasm/wasm.h so this + * module stays free of internal wasm-core dependencies; if either side + * changes these, the binder/metadata pair breaks together). */ +enum { + WASM_ABI_VAL_I32 = 0x7f, + WASM_ABI_VAL_I64 = 0x7e, + WASM_ABI_VAL_F32 = 0x7d, + WASM_ABI_VAL_F64 = 0x7c, + WASM_ABI_VAL_FUNCREF = 0x70, + WASM_ABI_VAL_EXTERNREF = 0x6f, +}; + +static int host_imports_streq(const char* a, const char* b) { + if (a == b) return 1; + if (!a || !b) return 0; + return strcmp(a, b) == 0; +} + +static int host_imports_map_valtype(uint8_t raw, CfreeWasmValType* out) { + switch (raw) { + case WASM_ABI_VAL_I32: + *out = CFREE_WASM_VAL_I32; + return 1; + case WASM_ABI_VAL_I64: + *out = CFREE_WASM_VAL_I64; + return 1; + case WASM_ABI_VAL_F32: + *out = CFREE_WASM_VAL_F32; + return 1; + case WASM_ABI_VAL_F64: + *out = CFREE_WASM_VAL_F64; + return 1; + case WASM_ABI_VAL_FUNCREF: + *out = CFREE_WASM_VAL_FUNCREF; + return 1; + case WASM_ABI_VAL_EXTERNREF: + *out = CFREE_WASM_VAL_EXTERNREF; + return 1; + default: + return 0; + } +} + +/* Translate an nparams/nresults raw-byte type description into the public + * CfreeWasmImportType. Returns 0 on unsupported value-type byte. The + * arrays are written into caller-provided storage. */ +static int host_imports_build_type(const CfreeWasmTypeDesc* src, + CfreeWasmValType* pbuf, uint32_t pcap, + CfreeWasmValType* rbuf, uint32_t rcap, + CfreeWasmImportType* out) { + uint32_t i; + if (src->nparams > pcap || src->nresults > rcap) return 0; + for (i = 0; i < src->nparams; ++i) + if (!host_imports_map_valtype(src->params[i], &pbuf[i])) return 0; + for (i = 0; i < src->nresults; ++i) + if (!host_imports_map_valtype(src->results[i], &rbuf[i])) return 0; + out->params = pbuf; + out->nparams = src->nparams; + out->results = rbuf; + out->nresults = src->nresults; + return 1; +} + +/* Conservative cap on per-import param/result count for the on-stack + * translation buffer in cfree_wasm_bind_host_imports. Wasm allows arbitrary + * counts in principle, but real modules cap well below this; tests use at + * most a few. If a module exceeds this the binder returns an error rather + * than silently truncating. */ +#define CFREE_WASM_BIND_MAX_VALTYPES 32u + +CFREE_API CfreeStatus cfree_wasm_bind_host_imports( + CfreeCompiler* compiler, CfreeJit* jit, CfreeWasmInstance* inst, + const CfreeWasmHostImport* imports, size_t nimports, + CfreeWasmResolveFn resolve, void* user) { + const uint32_t* nimports_meta; + const CfreeWasmImportDesc* import_descs; + const CfreeWasmTypeDesc* type_descs; + uint32_t n; + uint32_t i; + (void)compiler; + if (!jit || !inst) return CFREE_INVALID; + + nimports_meta = + (const uint32_t*)cfree_jit_lookup(jit, CFREE_SLICE_LIT("__cfree_wasm_nimports")); + if (!nimports_meta) { + /* Module wasn't built through cfree's wasm frontend, or it has no + * imports at all. Nothing to bind. */ + return CFREE_OK; + } + n = *nimports_meta; + if (n == 0) return CFREE_OK; + import_descs = + (const CfreeWasmImportDesc*)cfree_jit_lookup(jit, CFREE_SLICE_LIT("__cfree_wasm_imports")); + type_descs = + (const CfreeWasmTypeDesc*)cfree_jit_lookup(jit, CFREE_SLICE_LIT("__cfree_wasm_types")); + if (!import_descs || !type_descs) return CFREE_MALFORMED; + + for (i = 0; i < n; ++i) { + const CfreeWasmImportDesc* d = &import_descs[i]; + void* fn = NULL; + CfreeWasmValType pbuf[CFREE_WASM_BIND_MAX_VALTYPES]; + CfreeWasmValType rbuf[CFREE_WASM_BIND_MAX_VALTYPES]; + CfreeWasmImportType type; + /* Static table first. */ + for (size_t k = 0; k < nimports; ++k) { + if (host_imports_streq(imports[k].module, d->module) && + host_imports_streq(imports[k].field, d->field)) { + fn = imports[k].func; + break; + } + } + /* Resolver fallback. */ + if (!fn && resolve) { + const CfreeWasmTypeDesc* td = &type_descs[d->typeidx]; + if (!host_imports_build_type(td, pbuf, CFREE_WASM_BIND_MAX_VALTYPES, + rbuf, CFREE_WASM_BIND_MAX_VALTYPES, + &type)) + return CFREE_MALFORMED; + fn = resolve(user, d->module, d->field, &type); + } + if (!fn) continue; /* Unresolved: leave slot null; runtime traps on call. */ + /* Slot is a void* at byte offset slot_offset inside the instance struct. + * The CfreeWasmFuncImport record is { void* fn; } so the offset of the + * field is also the offset of the fn pointer. */ + *(void**)((unsigned char*)inst + d->slot_offset) = fn; + } + return CFREE_OK; +} diff --git a/lang/wasm/insn.c b/lang/wasm/insn.c @@ -1,410 +0,0 @@ -#include "wasm_internal.h" - -int wasm_is_num_type(WasmValType vt) { - return vt == WASM_VAL_I32 || vt == WASM_VAL_I64 || vt == WASM_VAL_F32 || - vt == WASM_VAL_F64; -} - -int wasm_is_ref_type(WasmValType vt) { - return vt == WASM_VAL_FUNCREF || vt == WASM_VAL_EXTERNREF; -} - -int wasm_is_frontend_value_type(WasmValType vt) { - return wasm_is_num_type(vt) || vt == WASM_VAL_FUNCREF; -} - -int wasm_feature_enabled(const WasmModule* m, WasmFeatureSet feature) { - return (m->features & (uint32_t)feature) != 0; -} - -void wasm_require_feature(CfreeCompiler* c, const WasmModule* m, - WasmFeatureSet feature, - const char* feature_name, - const char* what) { - if (!wasm_feature_enabled(m, feature)) - wasm_error(c, wasm_loc(0, 0), "wasm: %.*s requires %.*s", - CFREE_SLICE_ARG(cfree_slice_cstr(what)), - CFREE_SLICE_ARG(cfree_slice_cstr(feature_name))); -} - - -int wasm_insn_is_load(WasmInsnKind kind) { - return kind == WASM_INSN_I32_LOAD || kind == WASM_INSN_I64_LOAD || - kind == WASM_INSN_I32_LOAD8_S || kind == WASM_INSN_I32_LOAD8_U || - kind == WASM_INSN_I32_LOAD16_S || kind == WASM_INSN_I32_LOAD16_U || - kind == WASM_INSN_I64_LOAD8_S || kind == WASM_INSN_I64_LOAD8_U || - kind == WASM_INSN_I64_LOAD16_S || kind == WASM_INSN_I64_LOAD16_U || - kind == WASM_INSN_I64_LOAD32_S || kind == WASM_INSN_I64_LOAD32_U; -} - -int wasm_insn_is_store(WasmInsnKind kind) { - return kind == WASM_INSN_I32_STORE || kind == WASM_INSN_I64_STORE || - kind == WASM_INSN_I32_STORE8 || kind == WASM_INSN_I32_STORE16 || - kind == WASM_INSN_I64_STORE8 || kind == WASM_INSN_I64_STORE16 || - kind == WASM_INSN_I64_STORE32; -} - -int wasm_insn_is_atomic_load(WasmInsnKind kind) { - return kind == WASM_INSN_I32_ATOMIC_LOAD || - kind == WASM_INSN_I64_ATOMIC_LOAD || - kind == WASM_INSN_I32_ATOMIC_LOAD8_U || - kind == WASM_INSN_I32_ATOMIC_LOAD16_U || - kind == WASM_INSN_I64_ATOMIC_LOAD8_U || - kind == WASM_INSN_I64_ATOMIC_LOAD16_U || - kind == WASM_INSN_I64_ATOMIC_LOAD32_U; -} - -int wasm_insn_is_atomic_store(WasmInsnKind kind) { - return kind == WASM_INSN_I32_ATOMIC_STORE || - kind == WASM_INSN_I64_ATOMIC_STORE || - kind == WASM_INSN_I32_ATOMIC_STORE8 || - kind == WASM_INSN_I32_ATOMIC_STORE16 || - kind == WASM_INSN_I64_ATOMIC_STORE8 || - kind == WASM_INSN_I64_ATOMIC_STORE16 || - kind == WASM_INSN_I64_ATOMIC_STORE32; -} - -int wasm_insn_is_atomic_rmw(WasmInsnKind kind) { - return kind == WASM_INSN_I32_ATOMIC_RMW_ADD || - kind == WASM_INSN_I64_ATOMIC_RMW_ADD || - kind == WASM_INSN_I32_ATOMIC_RMW_SUB || - kind == WASM_INSN_I64_ATOMIC_RMW_SUB || - kind == WASM_INSN_I32_ATOMIC_RMW_AND || - kind == WASM_INSN_I64_ATOMIC_RMW_AND || - kind == WASM_INSN_I32_ATOMIC_RMW_OR || - kind == WASM_INSN_I64_ATOMIC_RMW_OR || - kind == WASM_INSN_I32_ATOMIC_RMW_XOR || - kind == WASM_INSN_I64_ATOMIC_RMW_XOR || - kind == WASM_INSN_I32_ATOMIC_RMW_XCHG || - kind == WASM_INSN_I64_ATOMIC_RMW_XCHG; -} - -int wasm_insn_is_atomic_cmpxchg(WasmInsnKind kind) { - return kind == WASM_INSN_I32_ATOMIC_RMW_CMPXCHG || - kind == WASM_INSN_I64_ATOMIC_RMW_CMPXCHG; -} - -int wasm_insn_is_atomic_wait_notify(WasmInsnKind kind) { - return kind == WASM_INSN_I32_ATOMIC_WAIT || - kind == WASM_INSN_I64_ATOMIC_WAIT || - kind == WASM_INSN_MEMORY_ATOMIC_NOTIFY; -} - -int wasm_insn_is_atomic_mem(WasmInsnKind kind) { - return wasm_insn_is_atomic_load(kind) || wasm_insn_is_atomic_store(kind) || - wasm_insn_is_atomic_rmw(kind) || wasm_insn_is_atomic_cmpxchg(kind) || - wasm_insn_is_atomic_wait_notify(kind); -} - -int wasm_insn_is_mem(WasmInsnKind kind) { - return wasm_insn_is_load(kind) || wasm_insn_is_store(kind) || - wasm_insn_is_atomic_mem(kind); -} - -WasmValType wasm_func_local_type(const WasmFunc* f, uint32_t index) { - if (index < f->nparams) return f->params[index]; - return f->locals[index - f->nparams]; -} - -uint32_t wasm_mem_width(uint8_t kind) { - switch (kind) { - case WASM_INSN_I32_LOAD8_S: - case WASM_INSN_I32_LOAD8_U: - case WASM_INSN_I64_LOAD8_S: - case WASM_INSN_I64_LOAD8_U: - case WASM_INSN_I32_STORE8: - case WASM_INSN_I64_STORE8: - case WASM_INSN_I32_ATOMIC_LOAD8_U: - case WASM_INSN_I64_ATOMIC_LOAD8_U: - case WASM_INSN_I32_ATOMIC_STORE8: - case WASM_INSN_I64_ATOMIC_STORE8: - return 1; - case WASM_INSN_I32_LOAD16_S: - case WASM_INSN_I32_LOAD16_U: - case WASM_INSN_I64_LOAD16_S: - case WASM_INSN_I64_LOAD16_U: - case WASM_INSN_I32_STORE16: - case WASM_INSN_I64_STORE16: - case WASM_INSN_I32_ATOMIC_LOAD16_U: - case WASM_INSN_I64_ATOMIC_LOAD16_U: - case WASM_INSN_I32_ATOMIC_STORE16: - case WASM_INSN_I64_ATOMIC_STORE16: - return 2; - case WASM_INSN_I32_LOAD: - case WASM_INSN_I64_LOAD32_S: - case WASM_INSN_I64_LOAD32_U: - case WASM_INSN_I32_STORE: - case WASM_INSN_I64_STORE32: - case WASM_INSN_I32_ATOMIC_LOAD: - case WASM_INSN_I64_ATOMIC_LOAD32_U: - case WASM_INSN_I32_ATOMIC_STORE: - case WASM_INSN_I64_ATOMIC_STORE32: - case WASM_INSN_I32_ATOMIC_RMW_ADD: - case WASM_INSN_I32_ATOMIC_RMW_SUB: - case WASM_INSN_I32_ATOMIC_RMW_AND: - case WASM_INSN_I32_ATOMIC_RMW_OR: - case WASM_INSN_I32_ATOMIC_RMW_XOR: - case WASM_INSN_I32_ATOMIC_RMW_XCHG: - case WASM_INSN_I32_ATOMIC_RMW_CMPXCHG: - case WASM_INSN_I32_ATOMIC_WAIT: - case WASM_INSN_MEMORY_ATOMIC_NOTIFY: - return 4; - default: - return 8; - } -} - - -int wasm_int_cmp_op(uint8_t kind, CfreeCgIntCmpOp* out) { - switch (kind) { - case WASM_INSN_I32_EQ: - case WASM_INSN_I64_EQ: - *out = CFREE_CG_INT_EQ; - return 1; - case WASM_INSN_I32_NE: - case WASM_INSN_I64_NE: - *out = CFREE_CG_INT_NE; - return 1; - case WASM_INSN_I32_LT_S: - case WASM_INSN_I64_LT_S: - *out = CFREE_CG_INT_LT_S; - return 1; - case WASM_INSN_I32_LT_U: - case WASM_INSN_I64_LT_U: - *out = CFREE_CG_INT_LT_U; - return 1; - case WASM_INSN_I32_GT_S: - case WASM_INSN_I64_GT_S: - *out = CFREE_CG_INT_GT_S; - return 1; - case WASM_INSN_I32_GT_U: - case WASM_INSN_I64_GT_U: - *out = CFREE_CG_INT_GT_U; - return 1; - case WASM_INSN_I32_LE_S: - case WASM_INSN_I64_LE_S: - *out = CFREE_CG_INT_LE_S; - return 1; - case WASM_INSN_I32_LE_U: - case WASM_INSN_I64_LE_U: - *out = CFREE_CG_INT_LE_U; - return 1; - case WASM_INSN_I32_GE_S: - case WASM_INSN_I64_GE_S: - *out = CFREE_CG_INT_GE_S; - return 1; - case WASM_INSN_I32_GE_U: - case WASM_INSN_I64_GE_U: - *out = CFREE_CG_INT_GE_U; - return 1; - default: - return 0; - } -} - -WasmValType wasm_load_result_type(uint8_t kind) { - switch (kind) { - case WASM_INSN_I64_LOAD: - case WASM_INSN_I64_LOAD8_S: - case WASM_INSN_I64_LOAD8_U: - case WASM_INSN_I64_LOAD16_S: - case WASM_INSN_I64_LOAD16_U: - case WASM_INSN_I64_LOAD32_S: - case WASM_INSN_I64_LOAD32_U: - case WASM_INSN_I64_ATOMIC_LOAD: - case WASM_INSN_I64_ATOMIC_LOAD8_U: - case WASM_INSN_I64_ATOMIC_LOAD16_U: - case WASM_INSN_I64_ATOMIC_LOAD32_U: - return WASM_VAL_I64; - default: - return WASM_VAL_I32; - } -} - -WasmValType wasm_store_value_type(uint8_t kind) { - switch (kind) { - case WASM_INSN_I64_STORE: - case WASM_INSN_I64_STORE8: - case WASM_INSN_I64_STORE16: - case WASM_INSN_I64_STORE32: - case WASM_INSN_I64_ATOMIC_STORE: - case WASM_INSN_I64_ATOMIC_STORE8: - case WASM_INSN_I64_ATOMIC_STORE16: - case WASM_INSN_I64_ATOMIC_STORE32: - return WASM_VAL_I64; - default: - return WASM_VAL_I32; - } -} - -WasmValType wasm_atomic_value_type(uint8_t kind) { - switch (kind) { - case WASM_INSN_I64_ATOMIC_LOAD: - case WASM_INSN_I64_ATOMIC_LOAD8_U: - case WASM_INSN_I64_ATOMIC_LOAD16_U: - case WASM_INSN_I64_ATOMIC_LOAD32_U: - case WASM_INSN_I64_ATOMIC_STORE: - case WASM_INSN_I64_ATOMIC_STORE8: - case WASM_INSN_I64_ATOMIC_STORE16: - case WASM_INSN_I64_ATOMIC_STORE32: - case WASM_INSN_I64_ATOMIC_RMW_ADD: - case WASM_INSN_I64_ATOMIC_RMW_SUB: - case WASM_INSN_I64_ATOMIC_RMW_AND: - case WASM_INSN_I64_ATOMIC_RMW_OR: - case WASM_INSN_I64_ATOMIC_RMW_XOR: - case WASM_INSN_I64_ATOMIC_RMW_XCHG: - case WASM_INSN_I64_ATOMIC_RMW_CMPXCHG: - case WASM_INSN_I64_ATOMIC_WAIT: - return WASM_VAL_I64; - default: - return WASM_VAL_I32; - } -} - -CfreeCgAtomicOp wasm_atomic_rmw_op(uint8_t kind) { - switch (kind) { - case WASM_INSN_I32_ATOMIC_RMW_ADD: - case WASM_INSN_I64_ATOMIC_RMW_ADD: - return CFREE_CG_ATOMIC_ADD; - case WASM_INSN_I32_ATOMIC_RMW_SUB: - case WASM_INSN_I64_ATOMIC_RMW_SUB: - return CFREE_CG_ATOMIC_SUB; - case WASM_INSN_I32_ATOMIC_RMW_AND: - case WASM_INSN_I64_ATOMIC_RMW_AND: - return CFREE_CG_ATOMIC_AND; - case WASM_INSN_I32_ATOMIC_RMW_OR: - case WASM_INSN_I64_ATOMIC_RMW_OR: - return CFREE_CG_ATOMIC_OR; - case WASM_INSN_I32_ATOMIC_RMW_XOR: - case WASM_INSN_I64_ATOMIC_RMW_XOR: - return CFREE_CG_ATOMIC_XOR; - default: - return CFREE_CG_ATOMIC_XCHG; - } -} - -int wasm_int_unop_kind(uint8_t kind, WasmValType* vt) { - if (kind == WASM_INSN_I32_CLZ || kind == WASM_INSN_I32_CTZ || - kind == WASM_INSN_I32_POPCNT) { - *vt = WASM_VAL_I32; - return 1; - } - if (kind == WASM_INSN_I64_CLZ || kind == WASM_INSN_I64_CTZ || - kind == WASM_INSN_I64_POPCNT) { - *vt = WASM_VAL_I64; - return 1; - } - return 0; -} - -int wasm_fp_binop_kind(uint8_t kind, WasmValType* vt) { - if (kind == WASM_INSN_F32_ADD || kind == WASM_INSN_F32_SUB || - kind == WASM_INSN_F32_MUL || kind == WASM_INSN_F32_DIV) { - *vt = WASM_VAL_F32; - return 1; - } - if (kind == WASM_INSN_F64_ADD || kind == WASM_INSN_F64_SUB || - kind == WASM_INSN_F64_MUL || kind == WASM_INSN_F64_DIV) { - *vt = WASM_VAL_F64; - return 1; - } - return 0; -} - -int wasm_fp_cmp_kind(uint8_t kind, WasmValType* vt) { - if (kind == WASM_INSN_F32_EQ || kind == WASM_INSN_F32_NE || - kind == WASM_INSN_F32_LT || kind == WASM_INSN_F32_GT || - kind == WASM_INSN_F32_LE || kind == WASM_INSN_F32_GE) { - *vt = WASM_VAL_F32; - return 1; - } - if (kind == WASM_INSN_F64_EQ || kind == WASM_INSN_F64_NE || - kind == WASM_INSN_F64_LT || kind == WASM_INSN_F64_GT || - kind == WASM_INSN_F64_LE || kind == WASM_INSN_F64_GE) { - *vt = WASM_VAL_F64; - return 1; - } - return 0; -} - -int wasm_conversion_kind(uint8_t kind, WasmValType* src, - WasmValType* dst) { - switch (kind) { - case WASM_INSN_I32_WRAP_I64: - *src = WASM_VAL_I64; - *dst = WASM_VAL_I32; - return 1; - case WASM_INSN_I64_EXTEND_I32_S: - case WASM_INSN_I64_EXTEND_I32_U: - *src = WASM_VAL_I32; - *dst = WASM_VAL_I64; - return 1; - case WASM_INSN_I32_TRUNC_F32_S: - case WASM_INSN_I32_TRUNC_F32_U: - *src = WASM_VAL_F32; - *dst = WASM_VAL_I32; - return 1; - case WASM_INSN_I32_TRUNC_F64_S: - case WASM_INSN_I32_TRUNC_F64_U: - *src = WASM_VAL_F64; - *dst = WASM_VAL_I32; - return 1; - case WASM_INSN_I64_TRUNC_F32_S: - case WASM_INSN_I64_TRUNC_F32_U: - *src = WASM_VAL_F32; - *dst = WASM_VAL_I64; - return 1; - case WASM_INSN_I64_TRUNC_F64_S: - case WASM_INSN_I64_TRUNC_F64_U: - *src = WASM_VAL_F64; - *dst = WASM_VAL_I64; - return 1; - case WASM_INSN_F32_CONVERT_I32_S: - case WASM_INSN_F32_CONVERT_I32_U: - *src = WASM_VAL_I32; - *dst = WASM_VAL_F32; - return 1; - case WASM_INSN_F32_CONVERT_I64_S: - case WASM_INSN_F32_CONVERT_I64_U: - *src = WASM_VAL_I64; - *dst = WASM_VAL_F32; - return 1; - case WASM_INSN_F64_CONVERT_I32_S: - case WASM_INSN_F64_CONVERT_I32_U: - *src = WASM_VAL_I32; - *dst = WASM_VAL_F64; - return 1; - case WASM_INSN_F64_CONVERT_I64_S: - case WASM_INSN_F64_CONVERT_I64_U: - *src = WASM_VAL_I64; - *dst = WASM_VAL_F64; - return 1; - case WASM_INSN_F32_DEMOTE_F64: - *src = WASM_VAL_F64; - *dst = WASM_VAL_F32; - return 1; - case WASM_INSN_F64_PROMOTE_F32: - *src = WASM_VAL_F32; - *dst = WASM_VAL_F64; - return 1; - case WASM_INSN_I32_REINTERPRET_F32: - *src = WASM_VAL_F32; - *dst = WASM_VAL_I32; - return 1; - case WASM_INSN_I64_REINTERPRET_F64: - *src = WASM_VAL_F64; - *dst = WASM_VAL_I64; - return 1; - case WASM_INSN_F32_REINTERPRET_I32: - *src = WASM_VAL_I32; - *dst = WASM_VAL_F32; - return 1; - case WASM_INSN_F64_REINTERPRET_I64: - *src = WASM_VAL_I64; - *dst = WASM_VAL_F64; - return 1; - default: - return 0; - } -} - diff --git a/lang/wasm/module.c b/lang/wasm/module.c @@ -1,291 +0,0 @@ -#include "wasm_internal.h" - -CfreeSrcLoc wasm_loc(uint32_t line, uint32_t col) { - CfreeSrcLoc loc; - loc.file_id = 0; - loc.line = line; - loc.col = col; - return loc; -} - -void wasm_error(CfreeCompiler* c, CfreeSrcLoc loc, const char* fmt, - ...) { - va_list ap; - va_start(ap, fmt); - cfree_frontend_vfatal(c, loc, fmt, ap); -} - -void* wasm_realloc(CfreeHeap* h, void* p, size_t old_n, size_t new_n) { - return h->realloc(h, p, old_n ? old_n : 1u, new_n ? new_n : 1u, - _Alignof(max_align_t)); -} - -void wasm_module_init(WasmModule* m, CfreeCompiler* c, CfreeHeap* heap) { - memset(m, 0, sizeof *m); - m->c = c; - m->heap = heap; - m->features = WASM_FEATURE_THREADS | WASM_FEATURE_TYPED_FUNC_REFS | - WASM_FEATURE_TAIL_CALLS | WASM_FEATURE_MULTI_MEMORY | - WASM_FEATURE_MEMORY64; -} - -void wasm_module_free(WasmModule* m) { - uint32_t i; - if (!m || !m->heap) return; - /* Name fields are interned CfreeSyms owned by the compiler pool, not the - * module, so there is nothing to free for them here. */ - if (m->types) - m->heap->free(m->heap, m->types, sizeof(*m->types) * m->cap_types); - for (i = 0; i < m->nfuncs; ++i) { - WasmFunc* f = &m->funcs[i]; - if (f->insns) - m->heap->free(m->heap, f->insns, sizeof(*f->insns) * f->cap_insns); - } - if (m->funcs) - m->heap->free(m->heap, m->funcs, sizeof(*m->funcs) * m->cap_funcs); - for (i = 0; i < m->nmemories; ++i) { - if (m->memories[i].data) - m->heap->free(m->heap, m->memories[i].data, - (size_t)m->memories[i].data_len); - } - if (m->memories) - m->heap->free(m->heap, m->memories, sizeof(*m->memories) * m->cap_memories); - if (m->tables) - m->heap->free(m->heap, m->tables, sizeof(*m->tables) * m->cap_tables); - if (m->globals) - m->heap->free(m->heap, m->globals, sizeof(*m->globals) * m->cap_globals); - if (m->elems) - m->heap->free(m->heap, m->elems, sizeof(*m->elems) * m->cap_elems); - if (m->exports) - m->heap->free(m->heap, m->exports, sizeof(*m->exports) * m->cap_exports); - for (i = 0; i < m->ncustoms; ++i) { - if (m->customs[i].data) - m->heap->free(m->heap, m->customs[i].data, m->customs[i].len); - } - if (m->customs) - m->heap->free(m->heap, m->customs, sizeof(*m->customs) * m->cap_customs); - memset(m, 0, sizeof *m); -} - -void wasm_memory_ensure(CfreeCompiler* c, WasmModule* m, - uint32_t memidx, uint64_t min_len) { - WasmMemory* mem; - uint64_t old_len, new_len; - void* p; - if (memidx >= m->nmemories) - wasm_error(c, wasm_loc(0, 0), "wasm: data segment without memory"); - mem = &m->memories[memidx]; - old_len = mem->data_len; - new_len = old_len; - if (new_len < min_len) new_len = min_len; - if (mem->min_pages && new_len < mem->min_pages * 65536u) - new_len = mem->min_pages * 65536u; - if (new_len > SIZE_MAX) - wasm_error(c, wasm_loc(0, 0), "wasm: data segment too large"); - if (new_len == old_len) return; - p = wasm_realloc(m->heap, mem->data, (size_t)old_len, (size_t)new_len); - if (!p) wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); - mem->data = (uint8_t*)p; - memset(mem->data + old_len, 0, (size_t)(new_len - old_len)); - mem->data_len = new_len; -} - -WasmMemory* wasm_add_memory(CfreeCompiler* c, WasmModule* m) { - WasmMemory* mem; - if (m->nmemories == m->cap_memories) { - uint32_t new_cap = m->cap_memories ? m->cap_memories * 2u : 2u; - void* p = - wasm_realloc(m->heap, m->memories, - sizeof(*m->memories) * m->cap_memories, - sizeof(*m->memories) * new_cap); - if (!p) wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); - m->memories = (WasmMemory*)p; - memset(m->memories + m->cap_memories, 0, - sizeof(*m->memories) * (new_cap - m->cap_memories)); - m->cap_memories = new_cap; - } - mem = &m->memories[m->nmemories++]; - memset(mem, 0, sizeof *mem); - return mem; -} - -WasmFunc* wasm_add_func(CfreeCompiler* c, WasmModule* m) { - WasmFunc* f; - if (m->nfuncs == m->cap_funcs) { - uint32_t new_cap = m->cap_funcs ? m->cap_funcs * 2u : 4u; - void* p = wasm_realloc(m->heap, m->funcs, sizeof(*m->funcs) * m->cap_funcs, - sizeof(*m->funcs) * new_cap); - if (!p) wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); - m->funcs = (WasmFunc*)p; - memset(m->funcs + m->cap_funcs, 0, - sizeof(*m->funcs) * (new_cap - m->cap_funcs)); - m->cap_funcs = new_cap; - } - f = &m->funcs[m->nfuncs++]; - memset(f, 0, sizeof *f); - return f; -} - -WasmFuncType* wasm_add_type(CfreeCompiler* c, WasmModule* m) { - WasmFuncType* t; - if (m->ntypes == m->cap_types) { - uint32_t new_cap = m->cap_types ? m->cap_types * 2u : 8u; - void* p = wasm_realloc(m->heap, m->types, sizeof(*m->types) * m->cap_types, - sizeof(*m->types) * new_cap); - if (!p) wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); - m->types = (WasmFuncType*)p; - memset(m->types + m->cap_types, 0, - sizeof(*m->types) * (new_cap - m->cap_types)); - m->cap_types = new_cap; - } - t = &m->types[m->ntypes++]; - memset(t, 0, sizeof *t); - return t; -} - -uint32_t wasm_intern_func_type(CfreeCompiler* c, WasmModule* m, - const WasmFunc* f) { - uint32_t i; - for (i = 0; i < m->ntypes; ++i) { - WasmFuncType* t = &m->types[i]; - if (t->nparams == f->nparams && t->nresults == f->nresults && - memcmp(t->params, f->params, sizeof(t->params[0]) * t->nparams) == 0 && - memcmp(t->results, f->results, sizeof(t->results[0]) * t->nresults) == - 0) - return i; - } - { - WasmFuncType* t = wasm_add_type(c, m); - t->nparams = f->nparams; - memcpy(t->params, f->params, sizeof(t->params[0]) * f->nparams); - t->nresults = f->nresults; - memcpy(t->results, f->results, sizeof(t->results[0]) * f->nresults); - return m->ntypes - 1u; - } -} - -WasmTable* wasm_add_table(CfreeCompiler* c, WasmModule* m) { - WasmTable* t; - if (m->ntables == m->cap_tables) { - uint32_t new_cap = m->cap_tables ? m->cap_tables * 2u : 2u; - void* p = - wasm_realloc(m->heap, m->tables, sizeof(*m->tables) * m->cap_tables, - sizeof(*m->tables) * new_cap); - if (!p) wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); - m->tables = (WasmTable*)p; - memset(m->tables + m->cap_tables, 0, - sizeof(*m->tables) * (new_cap - m->cap_tables)); - m->cap_tables = new_cap; - } - t = &m->tables[m->ntables++]; - memset(t, 0, sizeof *t); - return t; -} - -WasmGlobal* wasm_add_global(CfreeCompiler* c, WasmModule* m) { - WasmGlobal* g; - if (m->nglobals == m->cap_globals) { - uint32_t new_cap = m->cap_globals ? m->cap_globals * 2u : 4u; - void* p = - wasm_realloc(m->heap, m->globals, sizeof(*m->globals) * m->cap_globals, - sizeof(*m->globals) * new_cap); - if (!p) wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); - m->globals = (WasmGlobal*)p; - memset(m->globals + m->cap_globals, 0, - sizeof(*m->globals) * (new_cap - m->cap_globals)); - m->cap_globals = new_cap; - } - g = &m->globals[m->nglobals++]; - memset(g, 0, sizeof *g); - return g; -} - -WasmElemSegment* wasm_add_elem(CfreeCompiler* c, WasmModule* m) { - WasmElemSegment* e; - if (m->nelems == m->cap_elems) { - uint32_t new_cap = m->cap_elems ? m->cap_elems * 2u : 4u; - void* p = wasm_realloc(m->heap, m->elems, sizeof(*m->elems) * m->cap_elems, - sizeof(*m->elems) * new_cap); - if (!p) wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); - m->elems = (WasmElemSegment*)p; - memset(m->elems + m->cap_elems, 0, - sizeof(*m->elems) * (new_cap - m->cap_elems)); - m->cap_elems = new_cap; - } - e = &m->elems[m->nelems++]; - memset(e, 0, sizeof *e); - return e; -} - -WasmExport* wasm_add_export(CfreeCompiler* c, WasmModule* m) { - WasmExport* e; - if (m->nexports == m->cap_exports) { - uint32_t new_cap = m->cap_exports ? m->cap_exports * 2u : 8u; - void* p = - wasm_realloc(m->heap, m->exports, sizeof(*m->exports) * m->cap_exports, - sizeof(*m->exports) * new_cap); - if (!p) wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); - m->exports = (WasmExport*)p; - memset(m->exports + m->cap_exports, 0, - sizeof(*m->exports) * (new_cap - m->cap_exports)); - m->cap_exports = new_cap; - } - e = &m->exports[m->nexports++]; - memset(e, 0, sizeof *e); - return e; -} - -WasmCustom* wasm_add_custom(CfreeCompiler* c, WasmModule* m) { - WasmCustom* cs; - if (m->ncustoms == m->cap_customs) { - uint32_t new_cap = m->cap_customs ? m->cap_customs * 2u : 4u; - void* p = - wasm_realloc(m->heap, m->customs, sizeof(*m->customs) * m->cap_customs, - sizeof(*m->customs) * new_cap); - if (!p) wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); - m->customs = (WasmCustom*)p; - memset(m->customs + m->cap_customs, 0, - sizeof(*m->customs) * (new_cap - m->cap_customs)); - m->cap_customs = new_cap; - } - cs = &m->customs[m->ncustoms++]; - memset(cs, 0, sizeof *cs); - return cs; -} - -void wasm_func_add_insn(CfreeCompiler* c, WasmModule* m, WasmFunc* f, - WasmInsnKind kind, int64_t imm) { - if (f->ninsns == f->cap_insns) { - uint32_t new_cap = f->cap_insns ? f->cap_insns * 2u : 16u; - void* p = wasm_realloc(m->heap, f->insns, sizeof(*f->insns) * f->cap_insns, - sizeof(*f->insns) * new_cap); - if (!p) wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); - f->insns = (WasmInsn*)p; - f->cap_insns = new_cap; - } - f->insns[f->ninsns].kind = (uint8_t)kind; - f->insns[f->ninsns].type = 0; - f->insns[f->ninsns].imm = imm; - f->insns[f->ninsns].fp = 0.0; - f->insns[f->ninsns].align = 0; - f->insns[f->ninsns].memidx = 0; - f->insns[f->ninsns].offset64 = imm < 0 ? 0 : (uint64_t)imm; - f->insns[f->ninsns].ntargets = 0; - f->ninsns++; -} - -void wasm_func_add_mem_insn(CfreeCompiler* c, WasmModule* m, WasmFunc* f, - WasmInsnKind kind, uint32_t align, - uint64_t offset, uint32_t memidx) { - wasm_func_add_insn(c, m, f, kind, offset > INT64_MAX ? INT64_MAX - : (int64_t)offset); - f->insns[f->ninsns - 1u].align = align; - f->insns[f->ninsns - 1u].offset64 = offset; - f->insns[f->ninsns - 1u].memidx = memidx; -} - -void wasm_func_add_fp_insn(CfreeCompiler* c, WasmModule* m, WasmFunc* f, - WasmInsnKind kind, double value) { - wasm_func_add_insn(c, m, f, kind, 0); - f->insns[f->ninsns - 1u].fp = value; -} diff --git a/lang/wasm/runtime_abi.h b/lang/wasm/runtime_abi.h @@ -34,6 +34,24 @@ typedef struct CfreeWasmTable { uint32_t max; } CfreeWasmTable; +/* Bulk-memory passive data segment slot. Populated by __cfree_wasm_init from + * the module's WASM_SEG_PASSIVE data segments. `base` points to a const data + * symbol interned by codegen; `len` is the segment's byte length. data.drop + * sets `len = 0`. memory.init copies from this region into linear memory. */ +typedef struct CfreeWasmPassiveDataSegment { + const uint8_t* base; + uint64_t len; +} CfreeWasmPassiveDataSegment; + +/* Bulk-memory passive elem segment slot. Populated by __cfree_wasm_init from + * the module's WASM_SEG_PASSIVE element segments. `entries` points to a const + * array of {fn, typeidx} table-entry records; `length` is the entry count. + * elem.drop sets `length = 0`. table.init copies entries into a table. */ +typedef struct CfreeWasmPassiveElemSegment { + const CfreeWasmTableEntry* entries; + uint32_t length; +} CfreeWasmPassiveElemSegment; + /* Opaque to C callers for now. The compiler emits a module-specific instance * layout with CfreeWasmMemory first, followed by import slots, runtime tables, * and lowered global slots. Table entry storage is instance-owned for @@ -42,4 +60,40 @@ typedef struct CfreeWasmInstance CfreeWasmInstance; typedef void (*CfreeWasmInitFn)(CfreeWasmInstance*); +/* ---- Host-import metadata (subagent C) ---- + * + * Each module that the wasm frontend lowers emits three readonly symbols + * alongside __cfree_wasm_init so a runtime can resolve imports by + * (module, field) without needing the source WasmModule struct: + * + * __cfree_wasm_imports : CfreeWasmImportDesc[] (one per declared + * function import; if + * the module has none, + * the symbol is absent) + * __cfree_wasm_nimports : uint32_t (array length) + * __cfree_wasm_types : CfreeWasmTypeDesc[] (every WasmFuncType + * referenced by an + * import; absent when + * nimports == 0) + * + * 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, + * F64=0x7c, FUNCREF=0x70, EXTERNREF=0x6f) — the binder translates these to + * the public CfreeWasmValType enum when invoking host resolvers. */ + +typedef struct CfreeWasmImportDesc { + const char* module; /* image-owned, NUL-terminated */ + const char* field; /* image-owned, NUL-terminated */ + uint32_t typeidx; /* index into __cfree_wasm_types */ + uint32_t slot_offset; /* byte offset of the void* slot inside the + CfreeWasmInstance for this import */ +} CfreeWasmImportDesc; + +typedef struct CfreeWasmTypeDesc { + const uint8_t* params; /* nparams WasmValType bytes */ + uint32_t nparams; + const uint8_t* results; /* nresults WasmValType bytes */ + uint32_t nresults; +} CfreeWasmTypeDesc; + #endif diff --git a/lang/wasm/validate.c b/lang/wasm/validate.c @@ -1,669 +0,0 @@ -#include "wasm_internal.h" - -typedef struct WasmValStack { - WasmValType vals[256]; - uint32_t depth; -} WasmValStack; - -typedef struct WasmControlFrame { - uint8_t kind; - uint32_t height; - int seen_else; - int unreachable; -} WasmControlFrame; - -static WasmValType wasm_global_init_type(const WasmInsn* in) { - switch (in->kind) { - case WASM_INSN_I32_CONST: - return WASM_VAL_I32; - case WASM_INSN_I64_CONST: - return WASM_VAL_I64; - case WASM_INSN_F32_CONST: - return WASM_VAL_F32; - case WASM_INSN_F64_CONST: - return WASM_VAL_F64; - default: - return 0; - } -} - -static void wasm_stack_push(CfreeCompiler* c, WasmValStack* s, WasmValType vt) { - if (s->depth >= 256u) - wasm_error(c, wasm_loc(0, 0), "wasm: operand stack too deep"); - s->vals[s->depth++] = vt; -} - -static int wasm_stack_pop(CfreeCompiler* c, WasmValStack* s, - WasmControlFrame* frames, uint32_t nframes, - WasmValType expected, const char* what) { - WasmControlFrame* top = &frames[nframes - 1u]; - if (s->depth <= top->height) { - if (top->unreachable) return 1; - wasm_error(c, wasm_loc(0, 0), "wasm: operand stack underflow"); - } - if (expected && s->vals[s->depth - 1u] != expected) - wasm_error(c, wasm_loc(0, 0), "wasm: %.*s type mismatch", - CFREE_SLICE_ARG(cfree_slice_cstr(what))); - s->depth--; - return 1; -} - -static WasmValType wasm_stack_pop_any(CfreeCompiler* c, WasmValStack* s, - WasmControlFrame* frames, - uint32_t nframes, const char* what) { - WasmControlFrame* top = &frames[nframes - 1u]; - WasmValType vt; - if (s->depth <= top->height) { - if (top->unreachable) return WASM_VAL_I32; - wasm_error(c, wasm_loc(0, 0), "wasm: operand stack underflow"); - } - vt = s->vals[s->depth - 1u]; - if (!vt) - wasm_error(c, wasm_loc(0, 0), "wasm: %.*s type mismatch", - CFREE_SLICE_ARG(cfree_slice_cstr(what))); - s->depth--; - return vt; -} - -static void wasm_stack_pop_ref(CfreeCompiler* c, WasmValStack* s, - WasmControlFrame* frames, uint32_t nframes, - const char* what) { - WasmValType vt = wasm_stack_pop_any(c, s, frames, nframes, what); - if (!wasm_is_ref_type(vt)) - wasm_error(c, wasm_loc(0, 0), "wasm: %.*s type mismatch", - CFREE_SLICE_ARG(cfree_slice_cstr(what))); -} - -static void wasm_mark_unreachable(WasmValStack* s, WasmControlFrame* frames, - uint32_t nframes) { - WasmControlFrame* top = &frames[nframes - 1u]; - s->depth = top->height; - top->unreachable = 1; -} - -void wasm_validate(WasmModule* m, CfreeCompiler* c) { - uint32_t i, j; - for (i = 0; i < m->ntypes; ++i) { - for (j = 0; j < m->types[i].nparams; ++j) - if (!wasm_is_frontend_value_type(m->types[i].params[j])) - wasm_error(c, wasm_loc(0, 0), "wasm: unsupported parameter type"); - for (j = 0; j < m->types[i].nresults; ++j) - if (!wasm_is_frontend_value_type(m->types[i].results[j])) - wasm_error(c, wasm_loc(0, 0), "wasm: unsupported result type"); - } - for (i = 0; i < m->nmemories; ++i) { - if (m->memories[i].has_max && - m->memories[i].max_pages < m->memories[i].min_pages) - wasm_error(c, wasm_loc(0, 0), "wasm: memory maximum below minimum"); - if (m->memories[i].shared && !m->memories[i].has_max) - wasm_error(c, wasm_loc(0, 0), - "wasm: shared memory requires maximum"); - } - for (i = 0; i < m->ntables; ++i) { - if (m->tables[i].elem_type != WASM_VAL_FUNCREF) - wasm_error(c, wasm_loc(0, 0), - "wasm: reference type is unsupported for tables"); - if (m->tables[i].has_max && m->tables[i].max < m->tables[i].min) - wasm_error(c, wasm_loc(0, 0), "wasm: table maximum below minimum"); - } - for (i = 0; i < m->nglobals; ++i) { - WasmGlobal* g = &m->globals[i]; - if (!wasm_is_num_type(g->type)) - wasm_error(c, wasm_loc(0, 0), "wasm: unsupported global type"); - if (!g->is_import && wasm_global_init_type(&g->init) != g->type) - wasm_error(c, wasm_loc(0, 0), "wasm: global initializer type mismatch"); - } - for (i = 0; i < m->nexports; ++i) { - WasmExport* ex = &m->exports[i]; - if ((ex->kind == 0 && ex->index >= m->nfuncs) || - (ex->kind == 1 && ex->index >= m->ntables) || - (ex->kind == 2 && ex->index >= m->nmemories) || - (ex->kind == 3 && ex->index >= m->nglobals)) - wasm_error(c, wasm_loc(0, 0), "wasm: export index out of range"); - } - if (m->has_start) { - if (m->start_func >= m->nfuncs) - wasm_error(c, wasm_loc(0, 0), "wasm: start function index out of range"); - if (m->funcs[m->start_func].nparams || m->funcs[m->start_func].nresults) - wasm_error(c, wasm_loc(0, 0), - "wasm: start function must have no params or results"); - } - 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"); - for (j = 0; j < m->elems[i].nfuncs; ++j) - if (m->elems[i].funcs[j] >= m->nfuncs) - wasm_error(c, wasm_loc(0, 0), - "wasm: element function index out of range"); - } - for (i = 0; i < m->nfuncs; ++i) { - WasmFunc* f = &m->funcs[i]; - WasmValStack stack; - WasmControlFrame control[65]; - uint32_t ncontrol = 1; - memset(&stack, 0, sizeof stack); - memset(control, 0, sizeof control); - control[0].kind = 0xffu; - control[0].height = 0; - if (f->is_import) { - if (f->ninsns) - wasm_error(c, wasm_loc(0, 0), "wasm: imported function has body"); - continue; - } - if (f->nresults > 1u) - wasm_error(c, wasm_loc(0, 0), "wasm: multi-result unsupported"); - for (j = 0; j < f->ninsns; ++j) { - WasmInsn* in = &f->insns[j]; - WasmValType vt, src, dst; - switch (in->kind) { - case WASM_INSN_F32_CONST: - wasm_stack_push(c, &stack, WASM_VAL_F32); - break; - case WASM_INSN_F64_CONST: - wasm_stack_push(c, &stack, WASM_VAL_F64); - break; - case WASM_INSN_I32_CONST: - wasm_stack_push(c, &stack, WASM_VAL_I32); - break; - case WASM_INSN_I64_CONST: - wasm_stack_push(c, &stack, WASM_VAL_I64); - break; - case WASM_INSN_LOCAL_GET: - if (in->imm < 0 || - (uint64_t)in->imm >= (uint64_t)f->nparams + f->nlocals) - wasm_error(c, wasm_loc(0, 0), "wasm: local index out of range"); - wasm_stack_push(c, &stack, - wasm_func_local_type(f, (uint32_t)in->imm)); - break; - case WASM_INSN_LOCAL_SET: - case WASM_INSN_LOCAL_TEE: - if (in->imm < 0 || - (uint64_t)in->imm >= (uint64_t)f->nparams + f->nlocals) - wasm_error(c, wasm_loc(0, 0), "wasm: local index out of range"); - wasm_stack_pop(c, &stack, control, ncontrol, - wasm_func_local_type(f, (uint32_t)in->imm), "local"); - if (in->kind == WASM_INSN_LOCAL_TEE) - wasm_stack_push(c, &stack, - wasm_func_local_type(f, (uint32_t)in->imm)); - break; - case WASM_INSN_CALL: - case WASM_INSN_RETURN_CALL: - if (in->imm < 0 || (uint64_t)in->imm >= m->nfuncs) - wasm_error(c, wasm_loc(0, 0), "wasm: call index out of range"); - if (in->kind == WASM_INSN_RETURN_CALL) { - wasm_require_feature(c, m, WASM_FEATURE_TAIL_CALLS, "tail calls", - "return_call"); - if (m->funcs[in->imm].nresults != f->nresults || - (f->nresults && - m->funcs[in->imm].results[0] != f->results[0])) - wasm_error(c, wasm_loc(0, 0), - "wasm: return_call result type mismatch"); - } - for (uint32_t k = 0; k < m->funcs[in->imm].nparams; ++k) { - uint32_t param = m->funcs[in->imm].nparams - 1u - k; - wasm_stack_pop(c, &stack, control, ncontrol, - m->funcs[in->imm].params[param], "call argument"); - } - if (in->kind == WASM_INSN_RETURN_CALL) { - wasm_mark_unreachable(&stack, control, ncontrol); - } else if (m->funcs[in->imm].nresults) { - wasm_stack_push(c, &stack, m->funcs[in->imm].results[0]); - } - break; - case WASM_INSN_CALL_INDIRECT: { - WasmFuncType* t; - if (in->imm < 0 || (uint64_t)in->imm >= m->ntypes) - wasm_error(c, wasm_loc(0, 0), - "wasm: call_indirect type index out of range"); - if (in->align >= m->ntables) - wasm_error(c, wasm_loc(0, 0), - "wasm: call_indirect table index out of range"); - t = &m->types[in->imm]; - wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, - "call_indirect index"); - for (uint32_t k = 0; k < t->nparams; ++k) { - uint32_t param = t->nparams - 1u - k; - wasm_stack_pop(c, &stack, control, ncontrol, t->params[param], - "call_indirect argument"); - } - if (t->nresults) wasm_stack_push(c, &stack, t->results[0]); - break; - } - case WASM_INSN_RETURN_CALL_INDIRECT: { - WasmFuncType* t; - wasm_require_feature(c, m, WASM_FEATURE_TAIL_CALLS, "tail calls", - "return_call_indirect"); - if (in->imm < 0 || (uint64_t)in->imm >= m->ntypes) - wasm_error(c, wasm_loc(0, 0), - "wasm: return_call_indirect type index out of range"); - if (in->align >= m->ntables) - wasm_error(c, wasm_loc(0, 0), - "wasm: return_call_indirect table index out of range"); - t = &m->types[in->imm]; - if (t->nresults != f->nresults || - (f->nresults && t->results[0] != f->results[0])) - wasm_error(c, wasm_loc(0, 0), - "wasm: return_call_indirect result type mismatch"); - wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, - "return_call_indirect index"); - for (uint32_t k = 0; k < t->nparams; ++k) { - uint32_t param = t->nparams - 1u - k; - wasm_stack_pop(c, &stack, control, ncontrol, t->params[param], - "return_call_indirect argument"); - } - wasm_mark_unreachable(&stack, control, ncontrol); - break; - } - case WASM_INSN_REF_NULL: - wasm_require_feature(c, m, WASM_FEATURE_TYPED_FUNC_REFS, - "typed function references", "ref.null"); - if (!wasm_is_ref_type((WasmValType)in->imm)) - wasm_error(c, wasm_loc(0, 0), "wasm: bad ref.null type"); - if ((WasmValType)in->imm != WASM_VAL_FUNCREF) - wasm_error(c, wasm_loc(0, 0), - "wasm: unsupported reference type"); - wasm_stack_push(c, &stack, (WasmValType)in->imm); - break; - case WASM_INSN_REF_FUNC: - wasm_require_feature(c, m, WASM_FEATURE_TYPED_FUNC_REFS, - "typed function references", "ref.func"); - if (in->imm < 0 || (uint64_t)in->imm >= m->nfuncs) - wasm_error(c, wasm_loc(0, 0), - "wasm: ref.func index out of range"); - wasm_stack_push(c, &stack, WASM_VAL_FUNCREF); - break; - case WASM_INSN_REF_IS_NULL: - wasm_require_feature(c, m, WASM_FEATURE_TYPED_FUNC_REFS, - "typed function references", "ref.is_null"); - wasm_stack_pop_ref(c, &stack, control, ncontrol, "ref.is_null"); - wasm_stack_push(c, &stack, WASM_VAL_I32); - break; - case WASM_INSN_CALL_REF: - case WASM_INSN_RETURN_CALL_REF: { - WasmFuncType* t; - wasm_require_feature(c, m, WASM_FEATURE_TYPED_FUNC_REFS, - "typed function references", "call_ref"); - if (in->kind == WASM_INSN_RETURN_CALL_REF) - wasm_require_feature(c, m, WASM_FEATURE_TAIL_CALLS, "tail calls", - "return_call_ref"); - if (in->imm < 0 || (uint64_t)in->imm >= m->ntypes) - wasm_error(c, wasm_loc(0, 0), - "wasm: call_ref type index out of range"); - t = &m->types[in->imm]; - if (in->kind == WASM_INSN_RETURN_CALL_REF && - (t->nresults != f->nresults || - (f->nresults && t->results[0] != f->results[0]))) - wasm_error(c, wasm_loc(0, 0), - "wasm: return_call_ref result type mismatch"); - wasm_stack_pop_ref(c, &stack, control, ncontrol, "call_ref callee"); - for (uint32_t k = 0; k < t->nparams; ++k) { - uint32_t param = t->nparams - 1u - k; - wasm_stack_pop(c, &stack, control, ncontrol, t->params[param], - "call_ref argument"); - } - if (in->kind == WASM_INSN_RETURN_CALL_REF) - wasm_mark_unreachable(&stack, control, ncontrol); - else if (t->nresults) - wasm_stack_push(c, &stack, t->results[0]); - break; - } - case WASM_INSN_GLOBAL_GET: - if (in->imm < 0 || (uint64_t)in->imm >= m->nglobals) - wasm_error(c, wasm_loc(0, 0), "wasm: global index out of range"); - wasm_stack_push(c, &stack, m->globals[in->imm].type); - break; - case WASM_INSN_GLOBAL_SET: - if (in->imm < 0 || (uint64_t)in->imm >= m->nglobals) - wasm_error(c, wasm_loc(0, 0), "wasm: global index out of range"); - if (!m->globals[in->imm].mutable_) - wasm_error(c, wasm_loc(0, 0), "wasm: global is immutable"); - wasm_stack_pop(c, &stack, control, ncontrol, m->globals[in->imm].type, - "global"); - break; - case WASM_INSN_RETURN: - if (f->nresults) - wasm_stack_pop(c, &stack, control, ncontrol, f->results[0], - "return"); - wasm_mark_unreachable(&stack, control, ncontrol); - break; - case WASM_INSN_DROP: - wasm_stack_pop(c, &stack, control, ncontrol, 0, "drop"); - break; - case WASM_INSN_I32_EQZ: - wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, "eqz"); - wasm_stack_push(c, &stack, WASM_VAL_I32); - break; - case WASM_INSN_I64_EQZ: - wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I64, "eqz"); - wasm_stack_push(c, &stack, WASM_VAL_I32); - break; - case WASM_INSN_BLOCK: - case WASM_INSN_LOOP: - if (ncontrol >= 65u) - wasm_error(c, wasm_loc(0, 0), "wasm: control stack too deep"); - control[ncontrol].kind = in->kind; - control[ncontrol].height = stack.depth; - control[ncontrol].seen_else = 0; - control[ncontrol].unreachable = 0; - ncontrol++; - break; - case WASM_INSN_IF: - wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, "if"); - if (ncontrol >= 65u) - wasm_error(c, wasm_loc(0, 0), "wasm: control stack too deep"); - control[ncontrol].kind = in->kind; - control[ncontrol].height = stack.depth; - control[ncontrol].seen_else = 0; - control[ncontrol].unreachable = 0; - ncontrol++; - break; - case WASM_INSN_ELSE: - if (ncontrol <= 1u || control[ncontrol - 1u].kind != WASM_INSN_IF) - wasm_error(c, wasm_loc(0, 0), "wasm: else without if"); - if (!control[ncontrol - 1u].unreachable && - stack.depth != control[ncontrol - 1u].height) - wasm_error(c, wasm_loc(0, 0), "wasm: if branch result mismatch"); - stack.depth = control[ncontrol - 1u].height; - control[ncontrol - 1u].seen_else = 1; - control[ncontrol - 1u].unreachable = 0; - break; - case WASM_INSN_END: - if (ncontrol <= 1u) - wasm_error(c, wasm_loc(0, 0), "wasm: end without block"); - if (!control[ncontrol - 1u].unreachable && - stack.depth != control[ncontrol - 1u].height) - wasm_error(c, wasm_loc(0, 0), "wasm: block result mismatch"); - stack.depth = control[ncontrol - 1u].height; - ncontrol--; - break; - case WASM_INSN_BR: - if (in->imm < 0 || (uint64_t)in->imm >= ncontrol - 1u) - wasm_error(c, wasm_loc(0, 0), "wasm: branch depth out of range"); - wasm_mark_unreachable(&stack, control, ncontrol); - break; - case WASM_INSN_BR_IF: - if (in->imm < 0 || (uint64_t)in->imm >= ncontrol - 1u) - wasm_error(c, wasm_loc(0, 0), "wasm: branch depth out of range"); - wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, "br_if"); - break; - case WASM_INSN_BR_TABLE: - if (in->ntargets == 0) - wasm_error(c, wasm_loc(0, 0), "wasm: br_table without targets"); - wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, - "br_table selector"); - for (uint32_t k = 0; k < in->ntargets; ++k) - if (in->targets[k] >= ncontrol - 1u) - wasm_error(c, wasm_loc(0, 0), - "wasm: br_table depth out of range"); - wasm_mark_unreachable(&stack, control, ncontrol); - break; - case WASM_INSN_SELECT: { - WasmValType rhs, lhs; - wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, "select"); - if (stack.depth <= control[ncontrol - 1u].height && - control[ncontrol - 1u].unreachable) { - in->type = WASM_VAL_I32; - break; - } - if (stack.depth < control[ncontrol - 1u].height + 2u) - wasm_error(c, wasm_loc(0, 0), "wasm: operand stack underflow"); - rhs = stack.vals[--stack.depth]; - lhs = stack.vals[--stack.depth]; - if (lhs != rhs) - wasm_error(c, wasm_loc(0, 0), "wasm: select type mismatch"); - in->type = (uint8_t)lhs; - wasm_stack_push(c, &stack, lhs); - break; - } - case WASM_INSN_MEMORY_SIZE: - if (in->memidx >= m->nmemories) - wasm_error(c, wasm_loc(0, 0), "wasm: memory.size without memory"); - wasm_stack_push(c, &stack, - m->memories[in->memidx].is64 ? WASM_VAL_I64 - : WASM_VAL_I32); - break; - case WASM_INSN_MEMORY_GROW: - if (in->memidx >= m->nmemories) - wasm_error(c, wasm_loc(0, 0), "wasm: memory.grow without memory"); - wasm_stack_pop(c, &stack, control, ncontrol, - m->memories[in->memidx].is64 ? WASM_VAL_I64 - : WASM_VAL_I32, - "memory.grow"); - wasm_stack_push(c, &stack, - m->memories[in->memidx].is64 ? WASM_VAL_I64 - : WASM_VAL_I32); - break; - case WASM_INSN_ATOMIC_FENCE: - wasm_require_feature(c, m, WASM_FEATURE_THREADS, "threads", - "atomic.fence"); - break; - case WASM_INSN_I32_ATOMIC_LOAD: - case WASM_INSN_I64_ATOMIC_LOAD: - case WASM_INSN_I32_ATOMIC_LOAD8_U: - case WASM_INSN_I32_ATOMIC_LOAD16_U: - case WASM_INSN_I64_ATOMIC_LOAD8_U: - case WASM_INSN_I64_ATOMIC_LOAD16_U: - case WASM_INSN_I64_ATOMIC_LOAD32_U: - wasm_require_feature(c, m, WASM_FEATURE_THREADS, "threads", - "atomic load"); - if (in->memidx >= m->nmemories) - wasm_error(c, wasm_loc(0, 0), "wasm: atomic load without memory"); - 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_stack_pop(c, &stack, control, ncontrol, - m->memories[in->memidx].is64 ? WASM_VAL_I64 - : WASM_VAL_I32, - "atomic load"); - wasm_stack_push(c, &stack, wasm_atomic_value_type(in->kind)); - break; - case WASM_INSN_I32_ATOMIC_STORE: - case WASM_INSN_I64_ATOMIC_STORE: - case WASM_INSN_I32_ATOMIC_STORE8: - case WASM_INSN_I32_ATOMIC_STORE16: - case WASM_INSN_I64_ATOMIC_STORE8: - case WASM_INSN_I64_ATOMIC_STORE16: - case WASM_INSN_I64_ATOMIC_STORE32: - wasm_require_feature(c, m, WASM_FEATURE_THREADS, "threads", - "atomic store"); - if (in->memidx >= m->nmemories) - wasm_error(c, wasm_loc(0, 0), "wasm: atomic store without memory"); - 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_stack_pop(c, &stack, control, ncontrol, - wasm_atomic_value_type(in->kind), "atomic store"); - wasm_stack_pop(c, &stack, control, ncontrol, - m->memories[in->memidx].is64 ? WASM_VAL_I64 - : WASM_VAL_I32, - "atomic store"); - break; - case WASM_INSN_I32_ATOMIC_RMW_ADD: - case WASM_INSN_I64_ATOMIC_RMW_ADD: - case WASM_INSN_I32_ATOMIC_RMW_SUB: - case WASM_INSN_I64_ATOMIC_RMW_SUB: - case WASM_INSN_I32_ATOMIC_RMW_AND: - case WASM_INSN_I64_ATOMIC_RMW_AND: - case WASM_INSN_I32_ATOMIC_RMW_OR: - case WASM_INSN_I64_ATOMIC_RMW_OR: - case WASM_INSN_I32_ATOMIC_RMW_XOR: - case WASM_INSN_I64_ATOMIC_RMW_XOR: - case WASM_INSN_I32_ATOMIC_RMW_XCHG: - case WASM_INSN_I64_ATOMIC_RMW_XCHG: - wasm_require_feature(c, m, WASM_FEATURE_THREADS, "threads", - "atomic rmw"); - if (in->memidx >= m->nmemories) - wasm_error(c, wasm_loc(0, 0), "wasm: atomic rmw without memory"); - if (!m->memories[in->memidx].shared) - wasm_error(c, wasm_loc(0, 0), - "wasm: atomic rmw requires shared memory"); - wasm_stack_pop(c, &stack, control, ncontrol, - wasm_atomic_value_type(in->kind), "atomic rmw"); - wasm_stack_pop(c, &stack, control, ncontrol, - m->memories[in->memidx].is64 ? WASM_VAL_I64 - : WASM_VAL_I32, - "atomic rmw"); - wasm_stack_push(c, &stack, wasm_atomic_value_type(in->kind)); - break; - case WASM_INSN_I32_ATOMIC_RMW_CMPXCHG: - case WASM_INSN_I64_ATOMIC_RMW_CMPXCHG: - wasm_require_feature(c, m, WASM_FEATURE_THREADS, "threads", - "atomic cmpxchg"); - if (in->memidx >= m->nmemories) - wasm_error(c, wasm_loc(0, 0), - "wasm: atomic cmpxchg without memory"); - if (!m->memories[in->memidx].shared) - wasm_error(c, wasm_loc(0, 0), - "wasm: atomic cmpxchg requires shared memory"); - wasm_stack_pop(c, &stack, control, ncontrol, - wasm_atomic_value_type(in->kind), "atomic cmpxchg"); - wasm_stack_pop(c, &stack, control, ncontrol, - wasm_atomic_value_type(in->kind), "atomic cmpxchg"); - wasm_stack_pop(c, &stack, control, ncontrol, - m->memories[in->memidx].is64 ? WASM_VAL_I64 - : WASM_VAL_I32, - "atomic cmpxchg"); - wasm_stack_push(c, &stack, wasm_atomic_value_type(in->kind)); - break; - case WASM_INSN_I32_ATOMIC_WAIT: - case WASM_INSN_I64_ATOMIC_WAIT: - wasm_require_feature(c, m, WASM_FEATURE_THREADS, "threads", - "atomic wait"); - if (in->memidx >= m->nmemories) - wasm_error(c, wasm_loc(0, 0), "wasm: atomic wait without memory"); - if (!m->memories[in->memidx].shared) - wasm_error(c, wasm_loc(0, 0), - "wasm: atomic wait requires shared memory"); - wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I64, - "atomic wait timeout"); - wasm_stack_pop(c, &stack, control, ncontrol, - wasm_atomic_value_type(in->kind), - "atomic wait expected"); - wasm_stack_pop(c, &stack, control, ncontrol, - m->memories[in->memidx].is64 ? WASM_VAL_I64 - : WASM_VAL_I32, - "atomic wait address"); - wasm_stack_push(c, &stack, WASM_VAL_I32); - break; - case WASM_INSN_MEMORY_ATOMIC_NOTIFY: - wasm_require_feature(c, m, WASM_FEATURE_THREADS, "threads", - "atomic notify"); - if (in->memidx >= m->nmemories) - wasm_error(c, wasm_loc(0, 0), "wasm: atomic notify without memory"); - if (!m->memories[in->memidx].shared) - wasm_error(c, wasm_loc(0, 0), - "wasm: atomic notify requires shared memory"); - wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, - "atomic notify count"); - wasm_stack_pop(c, &stack, control, ncontrol, - m->memories[in->memidx].is64 ? WASM_VAL_I64 - : WASM_VAL_I32, - "atomic notify address"); - wasm_stack_push(c, &stack, WASM_VAL_I32); - break; - case WASM_INSN_I32_LOAD: - case WASM_INSN_I64_LOAD: - case WASM_INSN_I32_LOAD8_S: - case WASM_INSN_I32_LOAD8_U: - case WASM_INSN_I32_LOAD16_S: - case WASM_INSN_I32_LOAD16_U: - case WASM_INSN_I64_LOAD8_S: - case WASM_INSN_I64_LOAD8_U: - case WASM_INSN_I64_LOAD16_S: - case WASM_INSN_I64_LOAD16_U: - case WASM_INSN_I64_LOAD32_S: - case WASM_INSN_I64_LOAD32_U: - if (in->memidx >= m->nmemories) - wasm_error(c, wasm_loc(0, 0), "wasm: load without memory"); - wasm_stack_pop(c, &stack, control, ncontrol, - m->memories[in->memidx].is64 ? WASM_VAL_I64 - : WASM_VAL_I32, - "load"); - wasm_stack_push(c, &stack, wasm_load_result_type(in->kind)); - break; - case WASM_INSN_I32_STORE: - case WASM_INSN_I64_STORE: - case WASM_INSN_I32_STORE8: - case WASM_INSN_I32_STORE16: - case WASM_INSN_I64_STORE8: - case WASM_INSN_I64_STORE16: - case WASM_INSN_I64_STORE32: - if (in->memidx >= m->nmemories) - wasm_error(c, wasm_loc(0, 0), "wasm: store without memory"); - wasm_stack_pop(c, &stack, control, ncontrol, - wasm_store_value_type(in->kind), "store"); - wasm_stack_pop(c, &stack, control, ncontrol, - m->memories[in->memidx].is64 ? WASM_VAL_I64 - : WASM_VAL_I32, - "store"); - break; - case WASM_INSN_UNREACHABLE: - wasm_mark_unreachable(&stack, control, ncontrol); - break; - case WASM_INSN_NOP: - break; - default: - if (wasm_int_unop_kind(in->kind, &vt)) { - wasm_stack_pop(c, &stack, control, ncontrol, vt, "unary operand"); - wasm_stack_push(c, &stack, vt); - break; - } - if (wasm_fp_binop_kind(in->kind, &vt)) { - wasm_stack_pop(c, &stack, control, ncontrol, vt, "fp operand"); - wasm_stack_pop(c, &stack, control, ncontrol, vt, "fp operand"); - wasm_stack_push(c, &stack, vt); - break; - } - if (wasm_fp_cmp_kind(in->kind, &vt)) { - wasm_stack_pop(c, &stack, control, ncontrol, vt, "fp compare"); - wasm_stack_pop(c, &stack, control, ncontrol, vt, "fp compare"); - wasm_stack_push(c, &stack, WASM_VAL_I32); - break; - } - if (wasm_conversion_kind(in->kind, &src, &dst)) { - wasm_stack_pop(c, &stack, control, ncontrol, src, "conversion"); - wasm_stack_push(c, &stack, dst); - break; - } - { - CfreeCgIntCmpOp cmp; - WasmValType rhs, lhs; - rhs = stack.depth > control[ncontrol - 1u].height - ? stack.vals[stack.depth - 1u] - : WASM_VAL_I32; - wasm_stack_pop(c, &stack, control, ncontrol, 0, "operand"); - lhs = stack.depth > control[ncontrol - 1u].height - ? stack.vals[stack.depth - 1u] - : rhs; - wasm_stack_pop(c, &stack, control, ncontrol, rhs, "operand"); - if (lhs != rhs) - wasm_error(c, wasm_loc(0, 0), "wasm: operand type mismatch"); - wasm_stack_push( - c, &stack, - wasm_int_cmp_op(in->kind, &cmp) ? WASM_VAL_I32 : lhs); - break; - } - } - } - if (ncontrol != 1u) - wasm_error(c, wasm_loc(0, 0), "wasm: unterminated control block"); - if (!control[0].unreachable) { - if (f->nresults) { - if (stack.depth != 1u || stack.vals[0] != f->results[0]) - wasm_error(c, wasm_loc(0, 0), "wasm: function result type mismatch"); - } else if (stack.depth != 0) { - wasm_error(c, wasm_loc(0, 0), "wasm: function leaves extra values"); - } - } - } -} - diff --git a/lang/wasm/wasm.c b/lang/wasm/wasm.c @@ -1,4 +1,4 @@ -#include "wasm_internal.h" +#include "wasm/wasm.h" static void wasm_parse_any(CfreeCompiler* c, CfreeSlice name, const CfreeSlice* input, WasmModule* m) { @@ -33,7 +33,7 @@ static CfreeStatus wasm_frontend_compile( if (!fe || !fe->c || !opts || !input || !out) return CFREE_INVALID; c = fe->c; (void)opts->language_options; /* wasm frontend has no per-language options */ - wasm_module_init(&m, c, cfree_compiler_context(c)->heap); + wasm_module_init(&m, cfree_compiler_context(c)->heap); wasm_parse_any(c, input->name, &input->bytes, &m); wasm_emit_cg(c, &opts->code, out, &m); wasm_module_free(&m); @@ -59,10 +59,11 @@ const CfreeFrontendVTable cfree_wasm_frontend_vtable = { (uint32_t)(sizeof wasm_extensions / sizeof wasm_extensions[0]), }; -int cfree_wasm_wat_to_wasm(CfreeCompiler* c, const CfreeSlice* input, - CfreeWriter* out) { +CFREE_API int cfree_wasm_wat_to_wasm(CfreeCompiler* c, + const CfreeSlice* input, + CfreeWriter* out) { WasmModule m; - wasm_module_init(&m, c, cfree_compiler_context(c)->heap); + wasm_module_init(&m, cfree_compiler_context(c)->heap); wasm_parse_wat(c, CFREE_SLICE_NULL, input, &m); wasm_validate(&m, c); wasm_encode(c, &m, out); diff --git a/lang/wasm/wasm.h b/lang/wasm/wasm.h @@ -11,7 +11,7 @@ extern const CfreeFrontendVTable cfree_wasm_frontend_vtable; /* Internal test/developer helper: parse accepted WAT and write equivalent * binary Wasm. This is intentionally not part of the installed public API. */ -int cfree_wasm_wat_to_wasm(CfreeCompiler*, const CfreeSlice* input, - CfreeWriter* out); +CFREE_API int cfree_wasm_wat_to_wasm(CfreeCompiler*, const CfreeSlice* input, + CfreeWriter* out); #endif diff --git a/lang/wasm/wasm_internal.h b/lang/wasm/wasm_internal.h @@ -1,416 +0,0 @@ -#ifndef CFREE_LANG_WASM_INTERNAL_H -#define CFREE_LANG_WASM_INTERNAL_H - -#include "wasm.h" - -#include <cfree/cg.h> -#include <cfree/frontend.h> -#include <stddef.h> -#include <stdint.h> -#include <stdarg.h> -#include <stdlib.h> -#include <string.h> - -typedef enum WasmValType { - WASM_VAL_I32 = 0x7f, - WASM_VAL_I64 = 0x7e, - WASM_VAL_F32 = 0x7d, - WASM_VAL_F64 = 0x7c, - WASM_VAL_FUNCREF = 0x70, - WASM_VAL_EXTERNREF = 0x6f, -} WasmValType; - -typedef enum WasmFeatureSet { - WASM_FEATURE_THREADS = 1u << 0, - WASM_FEATURE_TYPED_FUNC_REFS = 1u << 1, - WASM_FEATURE_TAIL_CALLS = 1u << 2, - WASM_FEATURE_MULTI_MEMORY = 1u << 3, - WASM_FEATURE_MEMORY64 = 1u << 4, -} WasmFeatureSet; - -typedef enum WasmInsnKind { - WASM_INSN_UNREACHABLE, - WASM_INSN_NOP, - WASM_INSN_BLOCK, - WASM_INSN_LOOP, - WASM_INSN_IF, - WASM_INSN_ELSE, - WASM_INSN_END, - WASM_INSN_BR, - WASM_INSN_BR_IF, - WASM_INSN_BR_TABLE, - WASM_INSN_SELECT, - WASM_INSN_F32_CONST, - WASM_INSN_F64_CONST, - WASM_INSN_I32_CONST, - WASM_INSN_I64_CONST, - WASM_INSN_LOCAL_GET, - WASM_INSN_LOCAL_SET, - WASM_INSN_LOCAL_TEE, - WASM_INSN_CALL, - WASM_INSN_CALL_INDIRECT, - WASM_INSN_RETURN_CALL, - WASM_INSN_RETURN_CALL_INDIRECT, - WASM_INSN_REF_NULL, - WASM_INSN_REF_FUNC, - WASM_INSN_REF_IS_NULL, - WASM_INSN_CALL_REF, - WASM_INSN_RETURN_CALL_REF, - WASM_INSN_GLOBAL_GET, - WASM_INSN_GLOBAL_SET, - WASM_INSN_RETURN, - WASM_INSN_DROP, - WASM_INSN_I32_LOAD, - WASM_INSN_I64_LOAD, - WASM_INSN_I32_LOAD8_S, - WASM_INSN_I32_LOAD8_U, - WASM_INSN_I32_LOAD16_S, - WASM_INSN_I32_LOAD16_U, - WASM_INSN_I64_LOAD8_S, - WASM_INSN_I64_LOAD8_U, - WASM_INSN_I64_LOAD16_S, - WASM_INSN_I64_LOAD16_U, - WASM_INSN_I64_LOAD32_S, - WASM_INSN_I64_LOAD32_U, - WASM_INSN_I32_STORE, - WASM_INSN_I64_STORE, - WASM_INSN_I32_STORE8, - WASM_INSN_I32_STORE16, - WASM_INSN_I64_STORE8, - WASM_INSN_I64_STORE16, - WASM_INSN_I64_STORE32, - WASM_INSN_MEMORY_SIZE, - WASM_INSN_MEMORY_GROW, - WASM_INSN_ATOMIC_FENCE, - WASM_INSN_I32_ATOMIC_LOAD, - WASM_INSN_I64_ATOMIC_LOAD, - WASM_INSN_I32_ATOMIC_LOAD8_U, - WASM_INSN_I32_ATOMIC_LOAD16_U, - WASM_INSN_I64_ATOMIC_LOAD8_U, - WASM_INSN_I64_ATOMIC_LOAD16_U, - WASM_INSN_I64_ATOMIC_LOAD32_U, - WASM_INSN_I32_ATOMIC_STORE, - WASM_INSN_I64_ATOMIC_STORE, - WASM_INSN_I32_ATOMIC_STORE8, - WASM_INSN_I32_ATOMIC_STORE16, - WASM_INSN_I64_ATOMIC_STORE8, - WASM_INSN_I64_ATOMIC_STORE16, - WASM_INSN_I64_ATOMIC_STORE32, - WASM_INSN_I32_ATOMIC_RMW_ADD, - WASM_INSN_I64_ATOMIC_RMW_ADD, - WASM_INSN_I32_ATOMIC_RMW_SUB, - WASM_INSN_I64_ATOMIC_RMW_SUB, - WASM_INSN_I32_ATOMIC_RMW_AND, - WASM_INSN_I64_ATOMIC_RMW_AND, - WASM_INSN_I32_ATOMIC_RMW_OR, - WASM_INSN_I64_ATOMIC_RMW_OR, - WASM_INSN_I32_ATOMIC_RMW_XOR, - WASM_INSN_I64_ATOMIC_RMW_XOR, - WASM_INSN_I32_ATOMIC_RMW_XCHG, - WASM_INSN_I64_ATOMIC_RMW_XCHG, - WASM_INSN_I32_ATOMIC_RMW_CMPXCHG, - WASM_INSN_I64_ATOMIC_RMW_CMPXCHG, - WASM_INSN_I32_ATOMIC_WAIT, - WASM_INSN_I64_ATOMIC_WAIT, - WASM_INSN_MEMORY_ATOMIC_NOTIFY, - WASM_INSN_I32_ADD, - WASM_INSN_I32_SUB, - WASM_INSN_I32_MUL, - WASM_INSN_I32_DIV_S, - WASM_INSN_I32_DIV_U, - WASM_INSN_I32_REM_S, - WASM_INSN_I32_REM_U, - WASM_INSN_I32_AND, - WASM_INSN_I32_OR, - WASM_INSN_I32_XOR, - WASM_INSN_I32_SHL, - WASM_INSN_I32_SHR_S, - WASM_INSN_I32_SHR_U, - WASM_INSN_I32_ROTL, - WASM_INSN_I32_ROTR, - WASM_INSN_I32_CLZ, - WASM_INSN_I32_CTZ, - WASM_INSN_I32_POPCNT, - WASM_INSN_I32_EQZ, - WASM_INSN_I32_EQ, - WASM_INSN_I32_NE, - WASM_INSN_I32_LT_S, - WASM_INSN_I32_LT_U, - WASM_INSN_I32_GT_S, - WASM_INSN_I32_GT_U, - WASM_INSN_I32_LE_S, - WASM_INSN_I32_LE_U, - WASM_INSN_I32_GE_S, - WASM_INSN_I32_GE_U, - WASM_INSN_I64_ADD, - WASM_INSN_I64_SUB, - WASM_INSN_I64_MUL, - WASM_INSN_I64_DIV_S, - WASM_INSN_I64_DIV_U, - WASM_INSN_I64_REM_S, - WASM_INSN_I64_REM_U, - WASM_INSN_I64_AND, - WASM_INSN_I64_OR, - WASM_INSN_I64_XOR, - WASM_INSN_I64_SHL, - WASM_INSN_I64_SHR_S, - WASM_INSN_I64_SHR_U, - WASM_INSN_I64_ROTL, - WASM_INSN_I64_ROTR, - WASM_INSN_I64_CLZ, - WASM_INSN_I64_CTZ, - WASM_INSN_I64_POPCNT, - WASM_INSN_I64_EQZ, - WASM_INSN_I64_EQ, - WASM_INSN_I64_NE, - WASM_INSN_I64_LT_S, - WASM_INSN_I64_LT_U, - WASM_INSN_I64_GT_S, - WASM_INSN_I64_GT_U, - WASM_INSN_I64_LE_S, - WASM_INSN_I64_LE_U, - WASM_INSN_I64_GE_S, - WASM_INSN_I64_GE_U, - WASM_INSN_F32_ADD, - WASM_INSN_F32_SUB, - WASM_INSN_F32_MUL, - WASM_INSN_F32_DIV, - WASM_INSN_F32_EQ, - WASM_INSN_F32_NE, - WASM_INSN_F32_LT, - WASM_INSN_F32_GT, - WASM_INSN_F32_LE, - WASM_INSN_F32_GE, - WASM_INSN_F64_ADD, - WASM_INSN_F64_SUB, - WASM_INSN_F64_MUL, - WASM_INSN_F64_DIV, - WASM_INSN_F64_EQ, - WASM_INSN_F64_NE, - WASM_INSN_F64_LT, - WASM_INSN_F64_GT, - WASM_INSN_F64_LE, - WASM_INSN_F64_GE, - WASM_INSN_I32_WRAP_I64, - WASM_INSN_I32_TRUNC_F32_S, - WASM_INSN_I32_TRUNC_F32_U, - WASM_INSN_I32_TRUNC_F64_S, - WASM_INSN_I32_TRUNC_F64_U, - WASM_INSN_I64_EXTEND_I32_S, - WASM_INSN_I64_EXTEND_I32_U, - WASM_INSN_I64_TRUNC_F32_S, - WASM_INSN_I64_TRUNC_F32_U, - WASM_INSN_I64_TRUNC_F64_S, - WASM_INSN_I64_TRUNC_F64_U, - WASM_INSN_F32_CONVERT_I32_S, - WASM_INSN_F32_CONVERT_I32_U, - WASM_INSN_F32_CONVERT_I64_S, - WASM_INSN_F32_CONVERT_I64_U, - WASM_INSN_F32_DEMOTE_F64, - WASM_INSN_F64_CONVERT_I32_S, - WASM_INSN_F64_CONVERT_I32_U, - WASM_INSN_F64_CONVERT_I64_S, - WASM_INSN_F64_CONVERT_I64_U, - WASM_INSN_F64_PROMOTE_F32, - WASM_INSN_I32_REINTERPRET_F32, - WASM_INSN_I64_REINTERPRET_F64, - WASM_INSN_F32_REINTERPRET_I32, - WASM_INSN_F64_REINTERPRET_I64, -} WasmInsnKind; - -typedef struct WasmInsn { - uint8_t kind; - uint8_t type; - int64_t imm; - double fp; - uint32_t align; - uint32_t memidx; - uint64_t offset64; - uint32_t ntargets; - uint32_t targets[16]; -} WasmInsn; - -typedef struct WasmFunc { - CfreeSym name; - uint32_t typeidx; - int has_typeidx; - int is_import; - CfreeSym import_module; - CfreeSym import_name; - WasmValType params[16]; - uint32_t nparams; - WasmValType locals[32]; - uint32_t nlocals; - CfreeSym local_names[48]; - WasmValType results[1]; - uint32_t nresults; - CfreeSym export_name; - WasmInsn* insns; - uint32_t ninsns; - uint32_t cap_insns; -} WasmFunc; - -typedef struct WasmFuncType { - CfreeSym name; - WasmValType params[16]; - uint32_t nparams; - WasmValType results[1]; - uint32_t nresults; -} WasmFuncType; - -typedef struct WasmMemory { - CfreeSym name; - uint64_t min_pages; - uint64_t max_pages; - int has_max; - int is64; - int shared; - int is_import; - CfreeSym import_module; - CfreeSym import_name; - CfreeSym export_name; - uint8_t* data; - uint64_t data_len; - uint64_t data_init_len; -} WasmMemory; - -typedef struct WasmTable { - CfreeSym name; - WasmValType elem_type; - uint32_t min; - uint32_t max; - int has_max; - int is_import; - CfreeSym import_module; - CfreeSym import_name; - CfreeSym export_name; -} WasmTable; - -typedef struct WasmGlobal { - CfreeSym name; - WasmValType type; - uint8_t mutable_; - WasmInsn init; - int is_import; - CfreeSym import_module; - CfreeSym import_name; - CfreeSym export_name; -} WasmGlobal; - -typedef struct WasmElemSegment { - uint32_t tableidx; - int64_t offset; - uint32_t funcs[64]; - uint32_t nfuncs; -} WasmElemSegment; - -typedef struct WasmExport { - CfreeSym name; - uint8_t kind; - uint32_t index; -} WasmExport; - -typedef struct WasmCustom { - CfreeSym name; - uint8_t* data; - uint32_t len; -} WasmCustom; - -typedef struct WasmModule { - CfreeCompiler* c; - CfreeHeap* heap; - WasmFuncType* types; - uint32_t ntypes; - uint32_t cap_types; - WasmFunc* funcs; - uint32_t nfuncs; - uint32_t cap_funcs; - WasmMemory* memories; - uint32_t nmemories; - uint32_t cap_memories; - WasmTable* tables; - uint32_t ntables; - uint32_t cap_tables; - WasmGlobal* globals; - uint32_t nglobals; - uint32_t cap_globals; - WasmElemSegment* elems; - uint32_t nelems; - uint32_t cap_elems; - WasmExport* exports; - uint32_t nexports; - uint32_t cap_exports; - WasmCustom* customs; - uint32_t ncustoms; - uint32_t cap_customs; - uint32_t start_func; - int has_start; - int has_target_features; - uint32_t features; -} WasmModule; - -CfreeSrcLoc wasm_loc(uint32_t line, uint32_t col); -void wasm_error(CfreeCompiler* c, CfreeSrcLoc loc, const char* fmt, ...); -void* wasm_realloc(CfreeHeap* h, void* p, size_t old_n, size_t new_n); - -void wasm_module_init(WasmModule* m, CfreeCompiler* c, CfreeHeap* heap); -void wasm_module_free(WasmModule* m); -void wasm_memory_ensure(CfreeCompiler* c, WasmModule* m, uint32_t memidx, - uint64_t needed); -WasmMemory* wasm_add_memory(CfreeCompiler* c, WasmModule* m); -WasmFunc* wasm_add_func(CfreeCompiler* c, WasmModule* m); -WasmFuncType* wasm_add_type(CfreeCompiler* c, WasmModule* m); -uint32_t wasm_intern_func_type(CfreeCompiler* c, WasmModule* m, - const WasmFunc* f); -WasmTable* wasm_add_table(CfreeCompiler* c, WasmModule* m); -WasmGlobal* wasm_add_global(CfreeCompiler* c, WasmModule* m); -WasmElemSegment* wasm_add_elem(CfreeCompiler* c, WasmModule* m); -WasmExport* wasm_add_export(CfreeCompiler* c, WasmModule* m); -WasmCustom* wasm_add_custom(CfreeCompiler* c, WasmModule* m); -void wasm_func_add_insn(CfreeCompiler* c, WasmModule* m, WasmFunc* f, - WasmInsnKind kind, int64_t imm); -void wasm_func_add_mem_insn(CfreeCompiler* c, WasmModule* m, WasmFunc* f, - WasmInsnKind kind, uint32_t align, - uint64_t offset, uint32_t memidx); -void wasm_func_add_fp_insn(CfreeCompiler* c, WasmModule* m, WasmFunc* f, - WasmInsnKind kind, double value); - -int wasm_is_num_type(WasmValType vt); -int wasm_is_ref_type(WasmValType vt); -int wasm_is_frontend_value_type(WasmValType vt); -int wasm_feature_enabled(const WasmModule* m, WasmFeatureSet feature); -void wasm_require_feature(CfreeCompiler* c, const WasmModule* m, - WasmFeatureSet feature, const char* feature_name, - const char* what); -int wasm_insn_is_load(WasmInsnKind kind); -int wasm_insn_is_store(WasmInsnKind kind); -int wasm_insn_is_atomic_load(WasmInsnKind kind); -int wasm_insn_is_atomic_store(WasmInsnKind kind); -int wasm_insn_is_atomic_rmw(WasmInsnKind kind); -int wasm_insn_is_atomic_cmpxchg(WasmInsnKind kind); -int wasm_insn_is_atomic_wait_notify(WasmInsnKind kind); -int wasm_insn_is_atomic_mem(WasmInsnKind kind); -int wasm_insn_is_mem(WasmInsnKind kind); -WasmValType wasm_func_local_type(const WasmFunc* f, uint32_t index); -uint32_t wasm_mem_width(uint8_t kind); -int wasm_int_cmp_op(uint8_t kind, CfreeCgIntCmpOp* out); -WasmValType wasm_load_result_type(uint8_t kind); -WasmValType wasm_store_value_type(uint8_t kind); -WasmValType wasm_atomic_value_type(uint8_t kind); -CfreeCgAtomicOp wasm_atomic_rmw_op(uint8_t kind); -int wasm_int_unop_kind(uint8_t kind, WasmValType* vt); -int wasm_fp_binop_kind(uint8_t kind, WasmValType* vt); -int wasm_fp_cmp_kind(uint8_t kind, WasmValType* vt); -int wasm_conversion_kind(uint8_t kind, WasmValType* src, WasmValType* dst); - -void wasm_parse_wat(CfreeCompiler* c, CfreeSlice name, const CfreeSlice* input, - WasmModule* out); -void wasm_decode_binary(CfreeCompiler* c, const CfreeSlice* input, - WasmModule* out); -int wasm_is_binary(const CfreeSlice* input); -void wasm_validate(WasmModule* m, CfreeCompiler* c); -void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, - CfreeObjBuilder* out, const WasmModule* m); -void wasm_encode(CfreeCompiler* c, const WasmModule* m, CfreeWriter* out); - -#endif diff --git a/lang/wasm/wat.c b/lang/wasm/wat.c @@ -1,2498 +0,0 @@ -#include "wasm_internal.h" - -typedef struct WasmTok { - const char* p; - size_t len; - uint32_t line; - uint32_t col; - uint8_t kind; -} WasmTok; - -enum { - WT_EOF = 0, - WT_LPAREN, - WT_RPAREN, - WT_ATOM, - WT_STRING, -}; - -typedef struct WatParser { - CfreeCompiler* c; - const char* name; - const char* src; - size_t len; - size_t pos; - uint32_t line; - uint32_t col; - WasmTok tok; - WasmModule* module; -} WatParser; - -static int tok_is(WasmTok t, const char* s) { - size_t n = cfree_slice_cstr(s).len; - return t.kind == WT_ATOM && t.len == n && memcmp(t.p, s, n) == 0; -} - -static int wasm_name_eq(WatParser* p, CfreeSym name, WasmTok t) { - if (!name || t.kind != WT_ATOM) return 0; - return name == cfree_sym_intern(p->c, (CfreeSlice){.s = t.p, .len = t.len}); -} - -static int wat_hex(char ch) { - if (ch >= '0' && ch <= '9') return ch - '0'; - if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10; - if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10; - return -1; -} - -static char* wat_dup_string(WatParser* p, WasmTok t, size_t* len_out) { - char* out; - size_t i, n = 0; - if (t.kind != WT_STRING) - wasm_error(p->c, wasm_loc(t.line, t.col), "wasm wat: expected string"); - out = (char*)p->module->heap->alloc(p->module->heap, t.len + 1u, 1); - if (!out) wasm_error(p->c, wasm_loc(t.line, t.col), "wasm: out of memory"); - for (i = 0; i < t.len; ++i) { - char ch = t.p[i]; - if (ch != '\\') { - out[n++] = ch; - continue; - } - if (++i >= t.len) - wasm_error(p->c, wasm_loc(t.line, t.col), - "wasm wat: unterminated string escape"); - ch = t.p[i]; - switch (ch) { - case 'n': - out[n++] = '\n'; - break; - case 'r': - out[n++] = '\r'; - break; - case 't': - out[n++] = '\t'; - break; - case '"': - case '\'': - case '\\': - out[n++] = ch; - break; - default: { - int hi = wat_hex(ch); - int lo = (i + 1u < t.len) ? wat_hex(t.p[i + 1u]) : -1; - if (hi < 0 || lo < 0) - wasm_error(p->c, wasm_loc(t.line, t.col), - "wasm wat: unsupported string escape"); - out[n++] = (char)((hi << 4) | lo); - i++; - break; - } - } - } - out[n] = '\0'; - if (len_out) *len_out = n; - return out; -} - -/* Decode a WAT string token's escapes and intern the bytes as a CfreeSym. The - * temporary decode buffer is freed; the interned bytes live in the compiler - * pool. */ -static CfreeSym wat_intern_string(WatParser* p, WasmTok t) { - size_t n = 0; - char* decoded = wat_dup_string(p, t, &n); - CfreeSym sym = cfree_sym_intern(p->c, (CfreeSlice){.s = decoded, .len = n}); - p->module->heap->free(p->module->heap, decoded, t.len + 1u); - return sym; -} - -/* Intern a raw token's bytes (e.g. a `$name` identifier) as a CfreeSym. */ -static CfreeSym wat_intern_tok(WatParser* p, WasmTok t) { - return cfree_sym_intern(p->c, (CfreeSlice){.s = t.p, .len = t.len}); -} - -static void wat_next(WatParser* p) { - const char* s = p->src; - while (p->pos < p->len) { - char ch = s[p->pos]; - if (ch == '\n') { - p->pos++; - p->line++; - p->col = 1; - continue; - } - if (ch == ' ' || ch == '\t' || ch == '\r') { - p->pos++; - p->col++; - continue; - } - if (ch == ';' && p->pos + 1u < p->len && s[p->pos + 1u] == ';') { - while (p->pos < p->len && s[p->pos] != '\n') { - p->pos++; - p->col++; - } - continue; - } - if (ch == '(' && p->pos + 1u < p->len && s[p->pos + 1u] == ';') { - uint32_t depth = 1; - p->pos += 2u; - p->col += 2u; - while (depth && p->pos < p->len) { - if (s[p->pos] == '\n') { - p->pos++; - p->line++; - p->col = 1; - } else if (s[p->pos] == '(' && p->pos + 1u < p->len && - s[p->pos + 1u] == ';') { - p->pos += 2u; - p->col += 2u; - depth++; - } else if (s[p->pos] == ';' && p->pos + 1u < p->len && - s[p->pos + 1u] == ')') { - p->pos += 2u; - p->col += 2u; - depth--; - } else { - p->pos++; - p->col++; - } - } - if (depth) - wasm_error(p->c, wasm_loc(p->line, p->col), - "wasm wat: unterminated block comment"); - continue; - } - break; - } - p->tok.p = s + p->pos; - p->tok.len = 0; - p->tok.line = p->line; - p->tok.col = p->col; - p->tok.kind = WT_EOF; - if (p->pos >= p->len) return; - if (s[p->pos] == '(') { - p->tok.kind = WT_LPAREN; - p->tok.len = 1; - p->pos++; - p->col++; - return; - } - if (s[p->pos] == ')') { - p->tok.kind = WT_RPAREN; - p->tok.len = 1; - p->pos++; - p->col++; - return; - } - if (s[p->pos] == '"') { - size_t start = ++p->pos; - p->col++; - p->tok.kind = WT_STRING; - p->tok.p = s + start; - while (p->pos < p->len && s[p->pos] != '"') { - if (s[p->pos] == '\\') { - p->pos++; - p->col++; - if (p->pos >= p->len) break; - } else if ((unsigned char)s[p->pos] < 0x20) { - wasm_error(p->c, wasm_loc(p->line, p->col), - "wasm wat: unsupported string escape/control character"); - } - p->pos++; - p->col++; - } - if (p->pos >= p->len) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: unterminated string"); - p->tok.len = (size_t)(s + p->pos - p->tok.p); - p->pos++; - p->col++; - return; - } - p->tok.kind = WT_ATOM; - while (p->pos < p->len) { - char ch = s[p->pos]; - if (ch == '(' || ch == ')' || ch == ' ' || ch == '\t' || ch == '\r' || - ch == '\n') - break; - p->pos++; - p->col++; - } - p->tok.len = (size_t)(s + p->pos - p->tok.p); -} - -static void wat_expect(WatParser* p, uint8_t kind, const char* what) { - if (p->tok.kind != kind) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected %.*s", - CFREE_SLICE_ARG(cfree_slice_cstr(what))); - wat_next(p); -} - -static int wat_parse_i64(WatParser* p, int64_t* out) { - const char* s = p->tok.p; - size_t n = p->tok.len, i = 0; - uint64_t v = 0; - int neg = 0; - unsigned base = 10; - if (p->tok.kind != WT_ATOM || n == 0) return 0; - if (s[0] == '-' || s[0] == '+') { - neg = s[0] == '-'; - i = 1; - } - if (i + 2u <= n && s[i] == '0' && (s[i + 1u] == 'x' || s[i + 1u] == 'X')) { - base = 16; - i += 2u; - } - if (i == n) return 0; - for (; i < n; ++i) { - int hd; - unsigned d; - if (s[i] == '_') continue; - hd = wat_hex(s[i]); - if (hd < 0 || (unsigned)hd >= base) return 0; - d = (unsigned)hd; - if (v > (UINT64_MAX - d) / base) return 0; - v = v * base + d; - } - *out = neg ? -(int64_t)v : (int64_t)v; - return 1; -} - -static int wat_parse_f64(WatParser* p, double* out) { - char buf[128]; - char* end = NULL; - if (p->tok.kind != WT_ATOM || p->tok.len == 0 || p->tok.len >= sizeof buf) - return 0; - memcpy(buf, p->tok.p, p->tok.len); - buf[p->tok.len] = '\0'; - *out = strtod(buf, &end); - return end && *end == '\0'; -} - -static int wat_val_type(WasmTok t, WasmValType* out) { - if (tok_is(t, "i32")) { - *out = WASM_VAL_I32; - return 1; - } - if (tok_is(t, "i64")) { - *out = WASM_VAL_I64; - return 1; - } - if (tok_is(t, "f32")) { - *out = WASM_VAL_F32; - return 1; - } - if (tok_is(t, "f64")) { - *out = WASM_VAL_F64; - return 1; - } - if (tok_is(t, "funcref")) { - *out = WASM_VAL_FUNCREF; - return 1; - } - if (tok_is(t, "externref")) { - *out = WASM_VAL_EXTERNREF; - return 1; - } - return 0; -} - -static void wat_require_feature(WatParser* p, WasmFeatureSet feature, - const char* feature_name, const char* what) { - if (!wasm_feature_enabled(p->module, feature)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: %.*s requires %.*s", - CFREE_SLICE_ARG(cfree_slice_cstr(what)), - CFREE_SLICE_ARG(cfree_slice_cstr(feature_name))); -} - -static uint8_t wasm_export_kind_from_tok(WasmTok t) { - if (tok_is(t, "func")) return 0; - if (tok_is(t, "table")) return 1; - if (tok_is(t, "memory")) return 2; - if (tok_is(t, "global")) return 3; - return 0xffu; -} - -static void wat_skip_list(WatParser* p) { - uint32_t depth = 1; - while (depth && p->tok.kind != WT_EOF) { - if (p->tok.kind == WT_LPAREN) - depth++; - else if (p->tok.kind == WT_RPAREN) - depth--; - wat_next(p); - } -} - -static int wat_instr_kind(WasmTok t, WasmInsnKind* out, int* has_imm) { - *has_imm = 0; - if (tok_is(t, "unreachable")) { - *out = WASM_INSN_UNREACHABLE; - return 1; - } - if (tok_is(t, "nop")) { - *out = WASM_INSN_NOP; - return 1; - } - if (tok_is(t, "block")) { - *out = WASM_INSN_BLOCK; - return 1; - } - if (tok_is(t, "loop")) { - *out = WASM_INSN_LOOP; - return 1; - } - if (tok_is(t, "if")) { - *out = WASM_INSN_IF; - return 1; - } - if (tok_is(t, "else")) { - *out = WASM_INSN_ELSE; - return 1; - } - if (tok_is(t, "end")) { - *out = WASM_INSN_END; - return 1; - } - if (tok_is(t, "br")) { - *out = WASM_INSN_BR; - *has_imm = 1; - return 1; - } - if (tok_is(t, "br_if")) { - *out = WASM_INSN_BR_IF; - *has_imm = 1; - return 1; - } - if (tok_is(t, "br_table")) { - *out = WASM_INSN_BR_TABLE; - return 1; - } - if (tok_is(t, "select")) { - *out = WASM_INSN_SELECT; - return 1; - } - if (tok_is(t, "f32.const")) { - *out = WASM_INSN_F32_CONST; - *has_imm = 1; - return 1; - } - if (tok_is(t, "f64.const")) { - *out = WASM_INSN_F64_CONST; - *has_imm = 1; - return 1; - } - if (tok_is(t, "i32.const")) { - *out = WASM_INSN_I32_CONST; - *has_imm = 1; - return 1; - } - if (tok_is(t, "i64.const")) { - *out = WASM_INSN_I64_CONST; - *has_imm = 1; - return 1; - } - if (tok_is(t, "local.get")) { - *out = WASM_INSN_LOCAL_GET; - *has_imm = 1; - return 1; - } - if (tok_is(t, "local.set")) { - *out = WASM_INSN_LOCAL_SET; - *has_imm = 1; - return 1; - } - if (tok_is(t, "local.tee")) { - *out = WASM_INSN_LOCAL_TEE; - *has_imm = 1; - return 1; - } - if (tok_is(t, "call")) { - *out = WASM_INSN_CALL; - *has_imm = 1; - return 1; - } - if (tok_is(t, "call_indirect")) { - *out = WASM_INSN_CALL_INDIRECT; - return 1; - } - if (tok_is(t, "call_ref")) { - *out = WASM_INSN_CALL_REF; - return 1; - } - if (tok_is(t, "return_call")) { - *out = WASM_INSN_RETURN_CALL; - *has_imm = 1; - return 1; - } - if (tok_is(t, "return_call_indirect")) { - *out = WASM_INSN_RETURN_CALL_INDIRECT; - return 1; - } - if (tok_is(t, "return_call_ref")) { - *out = WASM_INSN_RETURN_CALL_REF; - return 1; - } - if (tok_is(t, "ref.null")) { - *out = WASM_INSN_REF_NULL; - *has_imm = 1; - return 1; - } - if (tok_is(t, "ref.func")) { - *out = WASM_INSN_REF_FUNC; - *has_imm = 1; - return 1; - } - if (tok_is(t, "ref.is_null")) { - *out = WASM_INSN_REF_IS_NULL; - return 1; - } - if (tok_is(t, "global.get")) { - *out = WASM_INSN_GLOBAL_GET; - *has_imm = 1; - return 1; - } - if (tok_is(t, "global.set")) { - *out = WASM_INSN_GLOBAL_SET; - *has_imm = 1; - return 1; - } - if (tok_is(t, "return")) { - *out = WASM_INSN_RETURN; - return 1; - } - if (tok_is(t, "drop")) { - *out = WASM_INSN_DROP; - return 1; - } - if (tok_is(t, "atomic.fence")) { - *out = WASM_INSN_ATOMIC_FENCE; - return 1; - } - if (tok_is(t, "i32.load")) { - *out = WASM_INSN_I32_LOAD; - return 1; - } - if (tok_is(t, "i32.atomic.load")) { - *out = WASM_INSN_I32_ATOMIC_LOAD; - return 1; - } - if (tok_is(t, "i64.load")) { - *out = WASM_INSN_I64_LOAD; - return 1; - } - if (tok_is(t, "i64.atomic.load")) { - *out = WASM_INSN_I64_ATOMIC_LOAD; - return 1; - } - if (tok_is(t, "i32.load8_s")) { - *out = WASM_INSN_I32_LOAD8_S; - return 1; - } - if (tok_is(t, "i32.load8_u")) { - *out = WASM_INSN_I32_LOAD8_U; - return 1; - } - if (tok_is(t, "i32.atomic.load8_u")) { - *out = WASM_INSN_I32_ATOMIC_LOAD8_U; - return 1; - } - if (tok_is(t, "i32.load16_s")) { - *out = WASM_INSN_I32_LOAD16_S; - return 1; - } - if (tok_is(t, "i32.load16_u")) { - *out = WASM_INSN_I32_LOAD16_U; - return 1; - } - if (tok_is(t, "i32.atomic.load16_u")) { - *out = WASM_INSN_I32_ATOMIC_LOAD16_U; - return 1; - } - if (tok_is(t, "i64.load8_s")) { - *out = WASM_INSN_I64_LOAD8_S; - return 1; - } - if (tok_is(t, "i64.load8_u")) { - *out = WASM_INSN_I64_LOAD8_U; - return 1; - } - if (tok_is(t, "i64.atomic.load8_u")) { - *out = WASM_INSN_I64_ATOMIC_LOAD8_U; - return 1; - } - if (tok_is(t, "i64.load16_s")) { - *out = WASM_INSN_I64_LOAD16_S; - return 1; - } - if (tok_is(t, "i64.load16_u")) { - *out = WASM_INSN_I64_LOAD16_U; - return 1; - } - if (tok_is(t, "i64.atomic.load16_u")) { - *out = WASM_INSN_I64_ATOMIC_LOAD16_U; - return 1; - } - if (tok_is(t, "i64.load32_s")) { - *out = WASM_INSN_I64_LOAD32_S; - return 1; - } - if (tok_is(t, "i64.load32_u")) { - *out = WASM_INSN_I64_LOAD32_U; - return 1; - } - if (tok_is(t, "i64.atomic.load32_u")) { - *out = WASM_INSN_I64_ATOMIC_LOAD32_U; - return 1; - } - if (tok_is(t, "i32.store")) { - *out = WASM_INSN_I32_STORE; - return 1; - } - if (tok_is(t, "i32.atomic.store")) { - *out = WASM_INSN_I32_ATOMIC_STORE; - return 1; - } - if (tok_is(t, "i64.store")) { - *out = WASM_INSN_I64_STORE; - return 1; - } - if (tok_is(t, "i64.atomic.store")) { - *out = WASM_INSN_I64_ATOMIC_STORE; - return 1; - } - if (tok_is(t, "i32.store8")) { - *out = WASM_INSN_I32_STORE8; - return 1; - } - if (tok_is(t, "i32.atomic.store8")) { - *out = WASM_INSN_I32_ATOMIC_STORE8; - return 1; - } - if (tok_is(t, "i32.store16")) { - *out = WASM_INSN_I32_STORE16; - return 1; - } - if (tok_is(t, "i32.atomic.store16")) { - *out = WASM_INSN_I32_ATOMIC_STORE16; - return 1; - } - if (tok_is(t, "i64.store8")) { - *out = WASM_INSN_I64_STORE8; - return 1; - } - if (tok_is(t, "i64.atomic.store8")) { - *out = WASM_INSN_I64_ATOMIC_STORE8; - return 1; - } - if (tok_is(t, "i64.store16")) { - *out = WASM_INSN_I64_STORE16; - return 1; - } - if (tok_is(t, "i64.atomic.store16")) { - *out = WASM_INSN_I64_ATOMIC_STORE16; - return 1; - } - if (tok_is(t, "i64.store32")) { - *out = WASM_INSN_I64_STORE32; - return 1; - } - if (tok_is(t, "i64.atomic.store32")) { - *out = WASM_INSN_I64_ATOMIC_STORE32; - return 1; - } - if (tok_is(t, "i32.atomic.rmw.add")) { - *out = WASM_INSN_I32_ATOMIC_RMW_ADD; - return 1; - } - if (tok_is(t, "i64.atomic.rmw.add")) { - *out = WASM_INSN_I64_ATOMIC_RMW_ADD; - return 1; - } - if (tok_is(t, "i32.atomic.rmw.sub")) { - *out = WASM_INSN_I32_ATOMIC_RMW_SUB; - return 1; - } - if (tok_is(t, "i64.atomic.rmw.sub")) { - *out = WASM_INSN_I64_ATOMIC_RMW_SUB; - return 1; - } - if (tok_is(t, "i32.atomic.rmw.and")) { - *out = WASM_INSN_I32_ATOMIC_RMW_AND; - return 1; - } - if (tok_is(t, "i64.atomic.rmw.and")) { - *out = WASM_INSN_I64_ATOMIC_RMW_AND; - return 1; - } - if (tok_is(t, "i32.atomic.rmw.or")) { - *out = WASM_INSN_I32_ATOMIC_RMW_OR; - return 1; - } - if (tok_is(t, "i64.atomic.rmw.or")) { - *out = WASM_INSN_I64_ATOMIC_RMW_OR; - return 1; - } - if (tok_is(t, "i32.atomic.rmw.xor")) { - *out = WASM_INSN_I32_ATOMIC_RMW_XOR; - return 1; - } - if (tok_is(t, "i64.atomic.rmw.xor")) { - *out = WASM_INSN_I64_ATOMIC_RMW_XOR; - return 1; - } - if (tok_is(t, "i32.atomic.rmw.xchg")) { - *out = WASM_INSN_I32_ATOMIC_RMW_XCHG; - return 1; - } - if (tok_is(t, "i64.atomic.rmw.xchg")) { - *out = WASM_INSN_I64_ATOMIC_RMW_XCHG; - return 1; - } - if (tok_is(t, "i32.atomic.rmw.cmpxchg")) { - *out = WASM_INSN_I32_ATOMIC_RMW_CMPXCHG; - return 1; - } - if (tok_is(t, "i64.atomic.rmw.cmpxchg")) { - *out = WASM_INSN_I64_ATOMIC_RMW_CMPXCHG; - return 1; - } - if (tok_is(t, "memory.atomic.wait32") || tok_is(t, "i32.atomic.wait")) { - *out = WASM_INSN_I32_ATOMIC_WAIT; - return 1; - } - if (tok_is(t, "memory.atomic.wait64") || tok_is(t, "i64.atomic.wait")) { - *out = WASM_INSN_I64_ATOMIC_WAIT; - return 1; - } - if (tok_is(t, "memory.atomic.notify") || tok_is(t, "atomic.notify")) { - *out = WASM_INSN_MEMORY_ATOMIC_NOTIFY; - return 1; - } - if (tok_is(t, "memory.size")) { - *out = WASM_INSN_MEMORY_SIZE; - return 1; - } - if (tok_is(t, "memory.grow")) { - *out = WASM_INSN_MEMORY_GROW; - return 1; - } - if (tok_is(t, "i32.add")) { - *out = WASM_INSN_I32_ADD; - return 1; - } - if (tok_is(t, "i32.sub")) { - *out = WASM_INSN_I32_SUB; - return 1; - } - if (tok_is(t, "i32.mul")) { - *out = WASM_INSN_I32_MUL; - return 1; - } - if (tok_is(t, "i32.div_s")) { - *out = WASM_INSN_I32_DIV_S; - return 1; - } - if (tok_is(t, "i32.div_u")) { - *out = WASM_INSN_I32_DIV_U; - return 1; - } - if (tok_is(t, "i32.rem_s")) { - *out = WASM_INSN_I32_REM_S; - return 1; - } - if (tok_is(t, "i32.rem_u")) { - *out = WASM_INSN_I32_REM_U; - return 1; - } - if (tok_is(t, "i32.and")) { - *out = WASM_INSN_I32_AND; - return 1; - } - if (tok_is(t, "i32.or")) { - *out = WASM_INSN_I32_OR; - return 1; - } - if (tok_is(t, "i32.xor")) { - *out = WASM_INSN_I32_XOR; - return 1; - } - if (tok_is(t, "i32.shl")) { - *out = WASM_INSN_I32_SHL; - return 1; - } - if (tok_is(t, "i32.shr_s")) { - *out = WASM_INSN_I32_SHR_S; - return 1; - } - if (tok_is(t, "i32.shr_u")) { - *out = WASM_INSN_I32_SHR_U; - return 1; - } - if (tok_is(t, "i32.rotl")) { - *out = WASM_INSN_I32_ROTL; - return 1; - } - if (tok_is(t, "i32.rotr")) { - *out = WASM_INSN_I32_ROTR; - return 1; - } - if (tok_is(t, "i32.clz")) { - *out = WASM_INSN_I32_CLZ; - return 1; - } - if (tok_is(t, "i32.ctz")) { - *out = WASM_INSN_I32_CTZ; - return 1; - } - if (tok_is(t, "i32.popcnt")) { - *out = WASM_INSN_I32_POPCNT; - return 1; - } - if (tok_is(t, "i32.eqz")) { - *out = WASM_INSN_I32_EQZ; - return 1; - } - if (tok_is(t, "i32.eq")) { - *out = WASM_INSN_I32_EQ; - return 1; - } - if (tok_is(t, "i32.ne")) { - *out = WASM_INSN_I32_NE; - return 1; - } - if (tok_is(t, "i32.lt_s")) { - *out = WASM_INSN_I32_LT_S; - return 1; - } - if (tok_is(t, "i32.lt_u")) { - *out = WASM_INSN_I32_LT_U; - return 1; - } - if (tok_is(t, "i32.gt_s")) { - *out = WASM_INSN_I32_GT_S; - return 1; - } - if (tok_is(t, "i32.gt_u")) { - *out = WASM_INSN_I32_GT_U; - return 1; - } - if (tok_is(t, "i32.le_s")) { - *out = WASM_INSN_I32_LE_S; - return 1; - } - if (tok_is(t, "i32.le_u")) { - *out = WASM_INSN_I32_LE_U; - return 1; - } - if (tok_is(t, "i32.ge_s")) { - *out = WASM_INSN_I32_GE_S; - return 1; - } - if (tok_is(t, "i32.ge_u")) { - *out = WASM_INSN_I32_GE_U; - return 1; - } - if (tok_is(t, "i64.add")) { - *out = WASM_INSN_I64_ADD; - return 1; - } - if (tok_is(t, "i64.sub")) { - *out = WASM_INSN_I64_SUB; - return 1; - } - if (tok_is(t, "i64.mul")) { - *out = WASM_INSN_I64_MUL; - return 1; - } - if (tok_is(t, "i64.div_s")) { - *out = WASM_INSN_I64_DIV_S; - return 1; - } - if (tok_is(t, "i64.div_u")) { - *out = WASM_INSN_I64_DIV_U; - return 1; - } - if (tok_is(t, "i64.rem_s")) { - *out = WASM_INSN_I64_REM_S; - return 1; - } - if (tok_is(t, "i64.rem_u")) { - *out = WASM_INSN_I64_REM_U; - return 1; - } - if (tok_is(t, "i64.and")) { - *out = WASM_INSN_I64_AND; - return 1; - } - if (tok_is(t, "i64.or")) { - *out = WASM_INSN_I64_OR; - return 1; - } - if (tok_is(t, "i64.xor")) { - *out = WASM_INSN_I64_XOR; - return 1; - } - if (tok_is(t, "i64.shl")) { - *out = WASM_INSN_I64_SHL; - return 1; - } - if (tok_is(t, "i64.shr_s")) { - *out = WASM_INSN_I64_SHR_S; - return 1; - } - if (tok_is(t, "i64.shr_u")) { - *out = WASM_INSN_I64_SHR_U; - return 1; - } - if (tok_is(t, "i64.rotl")) { - *out = WASM_INSN_I64_ROTL; - return 1; - } - if (tok_is(t, "i64.rotr")) { - *out = WASM_INSN_I64_ROTR; - return 1; - } - if (tok_is(t, "i64.clz")) { - *out = WASM_INSN_I64_CLZ; - return 1; - } - if (tok_is(t, "i64.ctz")) { - *out = WASM_INSN_I64_CTZ; - return 1; - } - if (tok_is(t, "i64.popcnt")) { - *out = WASM_INSN_I64_POPCNT; - return 1; - } - if (tok_is(t, "i64.eqz")) { - *out = WASM_INSN_I64_EQZ; - return 1; - } - if (tok_is(t, "i64.eq")) { - *out = WASM_INSN_I64_EQ; - return 1; - } - if (tok_is(t, "i64.ne")) { - *out = WASM_INSN_I64_NE; - return 1; - } - if (tok_is(t, "i64.lt_s")) { - *out = WASM_INSN_I64_LT_S; - return 1; - } - if (tok_is(t, "i64.lt_u")) { - *out = WASM_INSN_I64_LT_U; - return 1; - } - if (tok_is(t, "i64.gt_s")) { - *out = WASM_INSN_I64_GT_S; - return 1; - } - if (tok_is(t, "i64.gt_u")) { - *out = WASM_INSN_I64_GT_U; - return 1; - } - if (tok_is(t, "i64.le_s")) { - *out = WASM_INSN_I64_LE_S; - return 1; - } - if (tok_is(t, "i64.le_u")) { - *out = WASM_INSN_I64_LE_U; - return 1; - } - if (tok_is(t, "i64.ge_s")) { - *out = WASM_INSN_I64_GE_S; - return 1; - } - if (tok_is(t, "i64.ge_u")) { - *out = WASM_INSN_I64_GE_U; - return 1; - } - if (tok_is(t, "f32.add")) { - *out = WASM_INSN_F32_ADD; - return 1; - } - if (tok_is(t, "f32.sub")) { - *out = WASM_INSN_F32_SUB; - return 1; - } - if (tok_is(t, "f32.mul")) { - *out = WASM_INSN_F32_MUL; - return 1; - } - if (tok_is(t, "f32.div")) { - *out = WASM_INSN_F32_DIV; - return 1; - } - if (tok_is(t, "f32.eq")) { - *out = WASM_INSN_F32_EQ; - return 1; - } - if (tok_is(t, "f32.ne")) { - *out = WASM_INSN_F32_NE; - return 1; - } - if (tok_is(t, "f32.lt")) { - *out = WASM_INSN_F32_LT; - return 1; - } - if (tok_is(t, "f32.gt")) { - *out = WASM_INSN_F32_GT; - return 1; - } - if (tok_is(t, "f32.le")) { - *out = WASM_INSN_F32_LE; - return 1; - } - if (tok_is(t, "f32.ge")) { - *out = WASM_INSN_F32_GE; - return 1; - } - if (tok_is(t, "f64.add")) { - *out = WASM_INSN_F64_ADD; - return 1; - } - if (tok_is(t, "f64.sub")) { - *out = WASM_INSN_F64_SUB; - return 1; - } - if (tok_is(t, "f64.mul")) { - *out = WASM_INSN_F64_MUL; - return 1; - } - if (tok_is(t, "f64.div")) { - *out = WASM_INSN_F64_DIV; - return 1; - } - if (tok_is(t, "f64.eq")) { - *out = WASM_INSN_F64_EQ; - return 1; - } - if (tok_is(t, "f64.ne")) { - *out = WASM_INSN_F64_NE; - return 1; - } - if (tok_is(t, "f64.lt")) { - *out = WASM_INSN_F64_LT; - return 1; - } - if (tok_is(t, "f64.gt")) { - *out = WASM_INSN_F64_GT; - return 1; - } - if (tok_is(t, "f64.le")) { - *out = WASM_INSN_F64_LE; - return 1; - } - if (tok_is(t, "f64.ge")) { - *out = WASM_INSN_F64_GE; - return 1; - } - if (tok_is(t, "i32.wrap_i64")) { - *out = WASM_INSN_I32_WRAP_I64; - return 1; - } - if (tok_is(t, "i32.trunc_f32_s")) { - *out = WASM_INSN_I32_TRUNC_F32_S; - return 1; - } - if (tok_is(t, "i32.trunc_f32_u")) { - *out = WASM_INSN_I32_TRUNC_F32_U; - return 1; - } - if (tok_is(t, "i32.trunc_f64_s")) { - *out = WASM_INSN_I32_TRUNC_F64_S; - return 1; - } - if (tok_is(t, "i32.trunc_f64_u")) { - *out = WASM_INSN_I32_TRUNC_F64_U; - return 1; - } - if (tok_is(t, "i64.extend_i32_s")) { - *out = WASM_INSN_I64_EXTEND_I32_S; - return 1; - } - if (tok_is(t, "i64.extend_i32_u")) { - *out = WASM_INSN_I64_EXTEND_I32_U; - return 1; - } - if (tok_is(t, "i64.trunc_f32_s")) { - *out = WASM_INSN_I64_TRUNC_F32_S; - return 1; - } - if (tok_is(t, "i64.trunc_f32_u")) { - *out = WASM_INSN_I64_TRUNC_F32_U; - return 1; - } - if (tok_is(t, "i64.trunc_f64_s")) { - *out = WASM_INSN_I64_TRUNC_F64_S; - return 1; - } - if (tok_is(t, "i64.trunc_f64_u")) { - *out = WASM_INSN_I64_TRUNC_F64_U; - return 1; - } - if (tok_is(t, "f32.convert_i32_s")) { - *out = WASM_INSN_F32_CONVERT_I32_S; - return 1; - } - if (tok_is(t, "f32.convert_i32_u")) { - *out = WASM_INSN_F32_CONVERT_I32_U; - return 1; - } - if (tok_is(t, "f32.convert_i64_s")) { - *out = WASM_INSN_F32_CONVERT_I64_S; - return 1; - } - if (tok_is(t, "f32.convert_i64_u")) { - *out = WASM_INSN_F32_CONVERT_I64_U; - return 1; - } - if (tok_is(t, "f32.demote_f64")) { - *out = WASM_INSN_F32_DEMOTE_F64; - return 1; - } - if (tok_is(t, "f64.convert_i32_s")) { - *out = WASM_INSN_F64_CONVERT_I32_S; - return 1; - } - if (tok_is(t, "f64.convert_i32_u")) { - *out = WASM_INSN_F64_CONVERT_I32_U; - return 1; - } - if (tok_is(t, "f64.convert_i64_s")) { - *out = WASM_INSN_F64_CONVERT_I64_S; - return 1; - } - if (tok_is(t, "f64.convert_i64_u")) { - *out = WASM_INSN_F64_CONVERT_I64_U; - return 1; - } - if (tok_is(t, "f64.promote_f32")) { - *out = WASM_INSN_F64_PROMOTE_F32; - return 1; - } - if (tok_is(t, "i32.reinterpret_f32")) { - *out = WASM_INSN_I32_REINTERPRET_F32; - return 1; - } - if (tok_is(t, "i64.reinterpret_f64")) { - *out = WASM_INSN_I64_REINTERPRET_F64; - return 1; - } - if (tok_is(t, "f32.reinterpret_i32")) { - *out = WASM_INSN_F32_REINTERPRET_I32; - return 1; - } - if (tok_is(t, "f64.reinterpret_i64")) { - *out = WASM_INSN_F64_REINTERPRET_I64; - return 1; - } - return 0; -} - -static void wat_parse_func_index(WatParser* p, int64_t* out) { - uint32_t i; - if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { - for (i = 0; i < p->module->nfuncs; ++i) { - if (wasm_name_eq(p, p->module->funcs[i].name, p->tok)) { - *out = i; - return; - } - } - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: unknown function name"); - } - if (!wat_parse_i64(p, out)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected instruction immediate"); -} - -static void wat_parse_type_index(WatParser* p, int64_t* out) { - uint32_t i; - if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { - for (i = 0; i < p->module->ntypes; ++i) { - if (wasm_name_eq(p, p->module->types[i].name, p->tok)) { - *out = i; - return; - } - } - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: unknown type name"); - } - if (!wat_parse_i64(p, out)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected type index"); -} - -static void wat_parse_local_index(WatParser* p, WasmFunc* f, int64_t* out) { - uint32_t i, nlocals = f->nparams + f->nlocals; - if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { - for (i = 0; i < nlocals; ++i) { - if (wasm_name_eq(p, f->local_names[i], p->tok)) { - *out = i; - return; - } - } - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: unknown local name"); - } - if (!wat_parse_i64(p, out)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected instruction immediate"); -} - -static void wat_parse_global_index(WatParser* p, int64_t* out) { - uint32_t i; - if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { - for (i = 0; i < p->module->nglobals; ++i) { - if (wasm_name_eq(p, p->module->globals[i].name, p->tok)) { - *out = i; - return; - } - } - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: unknown global name"); - } - if (!wat_parse_i64(p, out)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected global index"); -} - -static void wat_parse_instr_imm(WatParser* p, WasmFunc* f, WasmInsnKind kind, - int64_t* out) { - switch (kind) { - case WASM_INSN_CALL: - case WASM_INSN_RETURN_CALL: - wat_parse_func_index(p, out); - break; - case WASM_INSN_GLOBAL_GET: - case WASM_INSN_GLOBAL_SET: - wat_parse_global_index(p, out); - break; - case WASM_INSN_LOCAL_GET: - case WASM_INSN_LOCAL_SET: - case WASM_INSN_LOCAL_TEE: - wat_parse_local_index(p, f, out); - break; - default: - if (!wat_parse_i64(p, out)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected instruction immediate"); - break; - } -} - - -static int wat_atom_prefix(WasmTok t, const char* prefix) { - size_t n = cfree_slice_cstr(prefix).len; - return t.kind == WT_ATOM && t.len >= n && memcmp(t.p, prefix, n) == 0; -} - -static int wat_parse_u32_atom(WasmTok t, uint32_t* out) { - uint64_t v = 0; - size_t i = 0; - unsigned base = 10; - if (t.kind != WT_ATOM || t.len == 0) return 0; - if (i + 2u <= t.len && t.p[i] == '0' && - (t.p[i + 1u] == 'x' || t.p[i + 1u] == 'X')) { - base = 16; - i += 2u; - } - if (i == t.len) return 0; - for (; i < t.len; ++i) { - int hd; - if (t.p[i] == '_') continue; - hd = wat_hex(t.p[i]); - if (hd < 0 || (unsigned)hd >= base) return 0; - if (v > (UINT32_MAX - (uint32_t)hd) / base) return 0; - v = v * base + (uint32_t)hd; - } - *out = (uint32_t)v; - return 1; -} - -static int wat_parse_u64_atom(WasmTok t, uint64_t* out) { - uint64_t v = 0; - size_t i = 0; - unsigned base = 10; - if (t.kind != WT_ATOM || t.len == 0) return 0; - if (i + 2u <= t.len && t.p[i] == '0' && - (t.p[i + 1u] == 'x' || t.p[i + 1u] == 'X')) { - base = 16; - i += 2u; - } - if (i == t.len) return 0; - for (; i < t.len; ++i) { - int hd; - if (t.p[i] == '_') continue; - hd = wat_hex(t.p[i]); - if (hd < 0 || (unsigned)hd >= base) return 0; - if (v > (UINT64_MAX - (uint64_t)hd) / base) return 0; - v = v * base + (uint64_t)hd; - } - *out = v; - return 1; -} - -static void wat_parse_memory_index(WatParser* p, uint32_t* out) { - uint32_t i; - int64_t idx; - if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { - for (i = 0; i < p->module->nmemories; ++i) { - if (wasm_name_eq(p, p->module->memories[i].name, p->tok)) { - *out = i; - return; - } - } - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: unknown memory name"); - } - if (!wat_parse_i64(p, &idx) || idx < 0 || idx > UINT32_MAX) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected memory index"); - *out = (uint32_t)idx; -} - -static void wat_parse_mem_attrs(WatParser* p, uint32_t* align, - uint64_t* offset, uint32_t* memidx) { - while (p->tok.kind == WT_ATOM) { - WasmTok val; - if (wat_atom_prefix(p->tok, "align=")) { - val = p->tok; - val.p += 6; - val.len -= 6; - if (!wat_parse_u32_atom(val, align)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: bad memory alignment"); - wat_next(p); - } else if (wat_atom_prefix(p->tok, "offset=")) { - val = p->tok; - val.p += 7; - val.len -= 7; - if (!wat_parse_u64_atom(val, offset)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: bad memory offset"); - wat_next(p); - } else if (wat_atom_prefix(p->tok, "memory=")) { - val = p->tok; - val.p += 7; - val.len -= 7; - if (!wat_parse_u32_atom(val, memidx)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: bad memory index"); - wat_next(p); - } else if (wat_atom_prefix(p->tok, "mem=")) { - val = p->tok; - val.p += 4; - val.len -= 4; - if (!wat_parse_u32_atom(val, memidx)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: bad memory index"); - wat_next(p); - } else { - break; - } - } -} - -static void wat_reject_inline_result(WatParser* p, const char* what) { - if (p->tok.kind != WT_LPAREN) return; - wat_next(p); - if (!tok_is(p->tok, "result")) { - p->pos = (size_t)(p->tok.p - p->src); - p->line = p->tok.line; - p->col = p->tok.col; - p->tok.kind = WT_LPAREN; - p->tok.p = p->src + p->pos - 1u; - p->tok.len = 1; - p->tok.line = p->line; - p->tok.col = p->col - 1u; - return; - } - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: %.*s results are unsupported", - CFREE_SLICE_ARG(cfree_slice_cstr(what))); -} - -static void wat_parse_instr(WatParser* p, WasmFunc* f); - -static void wat_check_instr_feature(WatParser* p, WasmInsnKind kind) { - if (kind == WASM_INSN_RETURN_CALL || - kind == WASM_INSN_RETURN_CALL_INDIRECT || - kind == WASM_INSN_RETURN_CALL_REF) - wat_require_feature(p, WASM_FEATURE_TAIL_CALLS, "tail calls", - "tail-call instruction"); - if (kind == WASM_INSN_REF_NULL || kind == WASM_INSN_REF_FUNC || - kind == WASM_INSN_REF_IS_NULL || kind == WASM_INSN_CALL_REF || - kind == WASM_INSN_RETURN_CALL_REF) - wat_require_feature(p, WASM_FEATURE_TYPED_FUNC_REFS, - "typed function references", - "typed-reference instruction"); - if (kind == WASM_INSN_ATOMIC_FENCE || wasm_insn_is_atomic_mem(kind)) - wat_require_feature(p, WASM_FEATURE_THREADS, "threads", - "atomic instruction"); -} - -static uint32_t wat_parse_call_indirect_type(WatParser* p) { - int64_t typeidx; - wat_expect(p, WT_LPAREN, "'('"); - if (!tok_is(p->tok, "type")) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected call_indirect type"); - wat_next(p); - wat_parse_type_index(p, &typeidx); - if (typeidx < 0 || (uint64_t)typeidx >= p->module->ntypes) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: type index out of range"); - wat_next(p); - wat_expect(p, WT_RPAREN, "')'"); - return (uint32_t)typeidx; -} - -static void wat_parse_ref_null_type(WatParser* p, int64_t* out) { - if (tok_is(p->tok, "func") || tok_is(p->tok, "nofunc") || - tok_is(p->tok, "funcref")) { - *out = WASM_VAL_FUNCREF; - return; - } - if (tok_is(p->tok, "extern") || tok_is(p->tok, "noextern") || - tok_is(p->tok, "externref")) { - *out = WASM_VAL_EXTERNREF; - return; - } - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected reference type"); -} - -static void wat_parse_instr_list(WatParser* p, WasmFunc* f) { - WasmInsnKind kind; - int has_imm; - int64_t imm = 0; - WasmTok head; - wat_expect(p, WT_LPAREN, "'('"); - head = p->tok; - if (tok_is(head, "block") || tok_is(head, "loop")) { - kind = tok_is(head, "block") ? WASM_INSN_BLOCK : WASM_INSN_LOOP; - wat_next(p); - if (p->tok.kind == WT_LPAREN) { - wat_next(p); - if (tok_is(p->tok, "result")) { - wat_next(p); - if (!tok_is(p->tok, "i32") && !tok_is(p->tok, "i64")) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: unsupported block result type"); - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: block results are unsupported"); - } - p->pos = (size_t)(p->tok.p - p->src); - p->line = p->tok.line; - p->col = p->tok.col; - p->tok.kind = WT_LPAREN; - p->tok.p = p->src + p->pos - 1u; - p->tok.len = 1; - p->tok.line = p->line; - p->tok.col = p->col - 1u; - } - wasm_func_add_insn(p->c, p->module, f, kind, 0); - while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) - wat_parse_instr(p, f); - wasm_func_add_insn(p->c, p->module, f, WASM_INSN_END, 0); - wat_expect(p, WT_RPAREN, "')'"); - return; - } - if (tok_is(head, "if")) { - wat_next(p); - while (p->tok.kind == WT_LPAREN) { - WasmTok save_head; - size_t save_pos = p->pos; - uint32_t save_line = p->line, save_col = p->col; - wat_next(p); - save_head = p->tok; - p->pos = save_pos; - p->line = save_line; - p->col = save_col; - p->tok.kind = WT_LPAREN; - p->tok.p = p->src + p->pos - 1u; - p->tok.len = 1; - p->tok.line = save_line; - p->tok.col = save_col - 1u; - if (tok_is(save_head, "then") || tok_is(save_head, "else")) break; - if (tok_is(save_head, "result")) - wasm_error(p->c, wasm_loc(save_head.line, save_head.col), - "wasm wat: if results are unsupported"); - wat_parse_instr(p, f); - } - wasm_func_add_insn(p->c, p->module, f, WASM_INSN_IF, 0); - if (p->tok.kind == WT_LPAREN) { - wat_next(p); - if (!tok_is(p->tok, "then")) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected then"); - wat_next(p); - while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) - wat_parse_instr(p, f); - wat_expect(p, WT_RPAREN, "')'"); - } - if (p->tok.kind == WT_LPAREN) { - wat_next(p); - if (!tok_is(p->tok, "else")) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected else"); - wasm_func_add_insn(p->c, p->module, f, WASM_INSN_ELSE, 0); - wat_next(p); - while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) - wat_parse_instr(p, f); - wat_expect(p, WT_RPAREN, "')'"); - } - wasm_func_add_insn(p->c, p->module, f, WASM_INSN_END, 0); - wat_expect(p, WT_RPAREN, "')'"); - return; - } - if (!wat_instr_kind(head, &kind, &has_imm)) - wasm_error(p->c, wasm_loc(head.line, head.col), - "wasm wat: unsupported instruction"); - wat_check_instr_feature(p, kind); - wat_next(p); - if (wasm_insn_is_mem(kind)) { - uint32_t align = 0, memidx = 0; - uint64_t offset = 0; - wat_parse_mem_attrs(p, &align, &offset, &memidx); - while (p->tok.kind == WT_LPAREN) { - wat_next(p); - if (tok_is(p->tok, "memory")) { - wat_next(p); - wat_parse_memory_index(p, &memidx); - wat_next(p); - wat_expect(p, WT_RPAREN, "')'"); - } else { - p->pos = (size_t)(p->tok.p - p->src); - p->line = p->tok.line; - p->col = p->tok.col; - p->tok.kind = WT_LPAREN; - p->tok.p = p->src + p->pos - 1u; - p->tok.len = 1; - p->tok.line = p->line; - p->tok.col = p->col - 1u; - wat_parse_instr(p, f); - } - } - wasm_func_add_mem_insn(p->c, p->module, f, kind, align, offset, memidx); - wat_expect(p, WT_RPAREN, "')'"); - return; - } - if (kind == WASM_INSN_BR_TABLE) { - WasmInsn* in; - uint32_t n = 0; - while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { - int64_t target; - if (n >= 16u) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: too many br_table targets"); - if (!wat_parse_i64(p, &target) || target < 0 || target > UINT32_MAX) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: bad br_table target"); - wasm_func_add_insn(p->c, p->module, f, - n ? WASM_INSN_NOP : WASM_INSN_BR_TABLE, 0); - in = &f->insns[f->ninsns - 1u - n]; - in->targets[n++] = (uint32_t)target; - wat_next(p); - } - f->ninsns -= n - 1u; - f->insns[f->ninsns - 1u].ntargets = n; - wat_expect(p, WT_RPAREN, "')'"); - return; - } - if (kind == WASM_INSN_CALL_INDIRECT || - kind == WASM_INSN_RETURN_CALL_INDIRECT) { - uint32_t typeidx = wat_parse_call_indirect_type(p); - while (p->tok.kind == WT_LPAREN) wat_parse_instr(p, f); - wasm_func_add_insn(p->c, p->module, f, kind, typeidx); - wat_expect(p, WT_RPAREN, "')'"); - return; - } - if (kind == WASM_INSN_CALL_REF || kind == WASM_INSN_RETURN_CALL_REF) { - uint32_t typeidx = wat_parse_call_indirect_type(p); - while (p->tok.kind == WT_LPAREN) wat_parse_instr(p, f); - wasm_func_add_insn(p->c, p->module, f, kind, typeidx); - wat_expect(p, WT_RPAREN, "')'"); - return; - } - if (kind == WASM_INSN_REF_NULL) { - wat_parse_ref_null_type(p, &imm); - wat_next(p); - wasm_func_add_insn(p->c, p->module, f, kind, imm); - wat_expect(p, WT_RPAREN, "')'"); - return; - } - if (kind == WASM_INSN_REF_FUNC) { - wat_parse_func_index(p, &imm); - wat_next(p); - wasm_func_add_insn(p->c, p->module, f, kind, imm); - wat_expect(p, WT_RPAREN, "')'"); - return; - } - if (kind == WASM_INSN_MEMORY_SIZE || kind == WASM_INSN_MEMORY_GROW) { - uint32_t memidx = 0; - while (p->tok.kind == WT_LPAREN) { - wat_next(p); - if (!tok_is(p->tok, "memory")) { - p->pos = (size_t)(p->tok.p - p->src); - p->line = p->tok.line; - p->col = p->tok.col; - p->tok.kind = WT_LPAREN; - p->tok.p = p->src + p->pos - 1u; - p->tok.len = 1; - p->tok.line = p->line; - p->tok.col = p->col - 1u; - wat_parse_instr(p, f); - continue; - } - wat_next(p); - wat_parse_memory_index(p, &memidx); - wat_next(p); - wat_expect(p, WT_RPAREN, "')'"); - } - { - WasmInsnKind next_kind; - int next_has_imm; - if (p->tok.kind == WT_ATOM && - !wat_instr_kind(p->tok, &next_kind, &next_has_imm)) { - wat_parse_memory_index(p, &memidx); - wat_next(p); - } - } - wasm_func_add_insn(p->c, p->module, f, - kind == WASM_INSN_MEMORY_GROW ? WASM_INSN_MEMORY_GROW - : WASM_INSN_MEMORY_SIZE, - 0); - f->insns[f->ninsns - 1u].memidx = memidx; - wat_expect(p, WT_RPAREN, "')'"); - return; - } - if (has_imm) { - if (kind == WASM_INSN_F32_CONST || kind == WASM_INSN_F64_CONST) { - double fv; - if (!wat_parse_f64(p, &fv)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected float immediate"); - wat_next(p); - while (p->tok.kind == WT_LPAREN) wat_parse_instr(p, f); - wasm_func_add_fp_insn(p->c, p->module, f, kind, fv); - wat_expect(p, WT_RPAREN, "')'"); - return; - } else { - wat_parse_instr_imm(p, f, kind, &imm); - wat_next(p); - } - } - while (p->tok.kind == WT_LPAREN) wat_parse_instr(p, f); - wasm_func_add_insn(p->c, p->module, f, kind, imm); - wat_expect(p, WT_RPAREN, "')'"); -} - -static void wat_parse_instr(WatParser* p, WasmFunc* f) { - WasmInsnKind kind; - int has_imm; - if (p->tok.kind == WT_LPAREN) { - wat_parse_instr_list(p, f); - return; - } - if (!wat_instr_kind(p->tok, &kind, &has_imm)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: unsupported instruction"); - wat_check_instr_feature(p, kind); - wat_next(p); - if (kind == WASM_INSN_BLOCK || kind == WASM_INSN_LOOP) - wat_reject_inline_result(p, "block"); - else if (kind == WASM_INSN_IF) - wat_reject_inline_result(p, "if"); - if (wasm_insn_is_mem(kind)) { - uint32_t align = 0, memidx = 0; - uint64_t offset = 0; - wat_parse_mem_attrs(p, &align, &offset, &memidx); - wasm_func_add_mem_insn(p->c, p->module, f, kind, align, offset, memidx); - return; - } - if (kind == WASM_INSN_BR_TABLE) { - WasmInsn* in; - uint32_t n = 0; - wasm_func_add_insn(p->c, p->module, f, WASM_INSN_BR_TABLE, 0); - in = &f->insns[f->ninsns - 1u]; - while (p->tok.kind == WT_ATOM) { - WasmInsnKind next_kind; - int next_has_imm; - int64_t target; - if (wat_instr_kind(p->tok, &next_kind, &next_has_imm)) break; - if (n >= 16u) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: too many br_table targets"); - if (!wat_parse_i64(p, &target) || target < 0 || target > UINT32_MAX) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: bad br_table target"); - in->targets[n++] = (uint32_t)target; - wat_next(p); - } - in->ntargets = n; - return; - } - if (kind == WASM_INSN_CALL_INDIRECT || - kind == WASM_INSN_RETURN_CALL_INDIRECT) { - uint32_t typeidx = wat_parse_call_indirect_type(p); - wasm_func_add_insn(p->c, p->module, f, kind, typeidx); - return; - } - if (kind == WASM_INSN_CALL_REF || kind == WASM_INSN_RETURN_CALL_REF) { - uint32_t typeidx = wat_parse_call_indirect_type(p); - wasm_func_add_insn(p->c, p->module, f, kind, typeidx); - return; - } - if (kind == WASM_INSN_REF_NULL) { - int64_t imm; - wat_parse_ref_null_type(p, &imm); - wasm_func_add_insn(p->c, p->module, f, kind, imm); - wat_next(p); - return; - } - if (kind == WASM_INSN_REF_FUNC) { - int64_t imm; - wat_parse_func_index(p, &imm); - wasm_func_add_insn(p->c, p->module, f, kind, imm); - wat_next(p); - return; - } - if (kind == WASM_INSN_MEMORY_SIZE || kind == WASM_INSN_MEMORY_GROW) { - uint32_t memidx = 0; - WasmInsnKind next_kind; - int next_has_imm; - if (p->tok.kind == WT_ATOM && !wat_instr_kind(p->tok, &next_kind, - &next_has_imm)) { - wat_parse_memory_index(p, &memidx); - wat_next(p); - } - wasm_func_add_insn(p->c, p->module, f, kind, 0); - f->insns[f->ninsns - 1u].memidx = memidx; - return; - } - if (has_imm) { - if (kind == WASM_INSN_F32_CONST || kind == WASM_INSN_F64_CONST) { - double fv; - if (!wat_parse_f64(p, &fv)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected float immediate"); - wasm_func_add_fp_insn(p->c, p->module, f, kind, fv); - wat_next(p); - } else { - int64_t imm; - wat_parse_instr_imm(p, f, kind, &imm); - wasm_func_add_insn(p->c, p->module, f, kind, imm); - wat_next(p); - } - } else { - wasm_func_add_insn(p->c, p->module, f, kind, 0); - } -} - -static void wat_parse_func(WatParser* p) { - WasmFunc* f = wasm_add_func(p->c, p->module); - uint32_t checked_params = 0; - uint32_t checked_results = 0; - wat_expect(p, WT_LPAREN, "'('"); - if (!tok_is(p->tok, "func")) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected func"); - wat_next(p); - if (p->tok.kind == WT_ATOM && p->tok.len > 0 && p->tok.p[0] == '$') { - f->name = wat_intern_tok(p, p->tok); - wat_next(p); - } - while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { - if (p->tok.kind == WT_LPAREN) { - wat_next(p); - if (tok_is(p->tok, "export")) { - wat_next(p); - if (p->tok.kind != WT_STRING) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected export string"); - f->export_name = wat_intern_string(p, p->tok); - { - WasmExport* ex = wasm_add_export(p->c, p->module); - ex->name = f->export_name; - ex->kind = 0; - ex->index = p->module->nfuncs - 1u; - } - wat_next(p); - wat_expect(p, WT_RPAREN, "')'"); - } else if (tok_is(p->tok, "type")) { - int64_t typeidx; - wat_next(p); - wat_parse_type_index(p, &typeidx); - if (typeidx < 0 || (uint64_t)typeidx >= p->module->ntypes) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: type index out of range"); - f->typeidx = (uint32_t)typeidx; - f->has_typeidx = 1; - f->nparams = p->module->types[typeidx].nparams; - memcpy(f->params, p->module->types[typeidx].params, - sizeof(f->params[0]) * f->nparams); - f->nresults = p->module->types[typeidx].nresults; - memcpy(f->results, p->module->types[typeidx].results, - sizeof(f->results[0]) * f->nresults); - wat_next(p); - wat_expect(p, WT_RPAREN, "')'"); - } else if (tok_is(p->tok, "param")) { - WasmTok pending_name; - int have_name = 0; - memset(&pending_name, 0, sizeof pending_name); - wat_next(p); - while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { - WasmValType vt; - if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { - pending_name = p->tok; - have_name = 1; - wat_next(p); - continue; - } - if (!wat_val_type(p->tok, &vt)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected parameter type"); - if (!wasm_is_frontend_value_type(vt)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: unsupported parameter type"); - if (f->has_typeidx) { - if (checked_params >= f->nparams || f->params[checked_params] != vt) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: parameter does not match type"); - if (have_name) { - f->local_names[checked_params] = wat_intern_tok(p, pending_name); - have_name = 0; - } - checked_params++; - wat_next(p); - continue; - } - if (f->nparams >= 16u) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: too many parameters"); - if (have_name) { - f->local_names[f->nparams] = wat_intern_tok(p, pending_name); - have_name = 0; - } - f->params[f->nparams++] = vt; - wat_next(p); - } - wat_expect(p, WT_RPAREN, "')'"); - } else if (tok_is(p->tok, "result")) { - WasmValType vt; - wat_next(p); - if (!wat_val_type(p->tok, &vt)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected result type"); - if (!wasm_is_frontend_value_type(vt)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: unsupported result type"); - if (f->has_typeidx) { - if (checked_results >= f->nresults || - f->results[checked_results] != vt) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: result does not match type"); - checked_results++; - } else { - f->results[0] = vt; - f->nresults = 1; - } - wat_next(p); - wat_expect(p, WT_RPAREN, "')'"); - } else if (tok_is(p->tok, "local")) { - WasmTok pending_name; - int have_name = 0; - memset(&pending_name, 0, sizeof pending_name); - wat_next(p); - while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { - WasmValType vt; - if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { - pending_name = p->tok; - have_name = 1; - wat_next(p); - continue; - } - if (!wat_val_type(p->tok, &vt)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected local type"); - if (f->nlocals >= 32u) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: too many locals"); - if (have_name) { - uint32_t index = f->nparams + f->nlocals; - f->local_names[index] = wat_intern_tok(p, pending_name); - have_name = 0; - } - f->locals[f->nlocals++] = vt; - wat_next(p); - } - wat_expect(p, WT_RPAREN, "')'"); - } else { - p->pos = (size_t)(p->tok.p - p->src); - p->line = p->tok.line; - p->col = p->tok.col; - p->tok.kind = WT_LPAREN; - p->tok.p = p->src + p->pos - 1u; - p->tok.len = 1; - p->tok.line = p->line; - p->tok.col = p->col - 1u; - wat_parse_instr(p, f); - } - } else { - wat_parse_instr(p, f); - } - } - if (!f->has_typeidx) - f->typeidx = wasm_intern_func_type(p->c, p->module, f); - else if (checked_params && checked_params != f->nparams) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: parameter list does not match type"); - wat_expect(p, WT_RPAREN, "')'"); -} - -static void wat_parse_type_field(WatParser* p) { - WasmFuncType* t = wasm_add_type(p->c, p->module); - if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { - t->name = wat_intern_tok(p, p->tok); - wat_next(p); - } - wat_expect(p, WT_LPAREN, "'('"); - if (!tok_is(p->tok, "func")) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected func type"); - wat_next(p); - while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { - wat_expect(p, WT_LPAREN, "'('"); - if (tok_is(p->tok, "param")) { - wat_next(p); - while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { - WasmValType vt; - if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { - wat_next(p); - continue; - } - if (!wat_val_type(p->tok, &vt) || !wasm_is_num_type(vt)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected parameter type"); - if (t->nparams >= 16u) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: too many parameters"); - t->params[t->nparams++] = vt; - wat_next(p); - } - } else if (tok_is(p->tok, "result")) { - wat_next(p); - while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { - WasmValType vt; - if (!wat_val_type(p->tok, &vt) || !wasm_is_num_type(vt)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected result type"); - if (t->nresults >= 1u) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: multi-result unsupported"); - t->results[t->nresults++] = vt; - wat_next(p); - } - } else { - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected type field"); - } - wat_expect(p, WT_RPAREN, "')'"); - } - wat_expect(p, WT_RPAREN, "')'"); - wat_expect(p, WT_RPAREN, "')'"); -} - -static void wat_parse_export_field(WatParser* p) { - CfreeSym name; - int64_t idx = 0; - uint8_t kind; - if (p->tok.kind != WT_STRING) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected export string"); - name = wat_intern_string(p, p->tok); - wat_next(p); - wat_expect(p, WT_LPAREN, "'('"); - kind = wasm_export_kind_from_tok(p->tok); - if (kind == 0xffu) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected export kind"); - wat_next(p); - if (kind == 0) - wat_parse_func_index(p, &idx); - else if (!wat_parse_i64(p, &idx)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected export index"); - if (idx < 0 || idx > UINT32_MAX) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: export index out of range"); - { - WasmExport* ex = wasm_add_export(p->c, p->module); - ex->name = name; - ex->kind = kind; - ex->index = (uint32_t)idx; - } - if (kind == 0 && (uint64_t)idx < p->module->nfuncs) { - p->module->funcs[idx].export_name = name; - } else if (kind == 2 && (uint64_t)idx < p->module->nmemories) { - p->module->memories[(uint32_t)idx].export_name = name; - } else if (kind == 1 && (uint64_t)idx < p->module->ntables) { - p->module->tables[idx].export_name = name; - } else if (kind == 3 && (uint64_t)idx < p->module->nglobals) { - p->module->globals[idx].export_name = name; - } - wat_next(p); - wat_expect(p, WT_RPAREN, "')'"); - wat_expect(p, WT_RPAREN, "')'"); -} - -static void wat_parse_memory_field(WatParser* p) { - int64_t min_pages, max_pages; - WasmMemory* mem = wasm_add_memory(p->c, p->module); - if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { - mem->name = wat_intern_tok(p, p->tok); - wat_next(p); - } - while (p->tok.kind == WT_ATOM && (tok_is(p->tok, "i64") || - tok_is(p->tok, "shared"))) { - if (tok_is(p->tok, "i64")) - mem->is64 = 1; - else - mem->shared = 1; - wat_next(p); - } - if (!wat_parse_i64(p, &min_pages) || min_pages < 0) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected memory minimum"); - mem->min_pages = (uint64_t)min_pages; - wat_next(p); - while (p->tok.kind == WT_ATOM && (tok_is(p->tok, "i64") || - tok_is(p->tok, "shared"))) { - if (tok_is(p->tok, "i64")) - mem->is64 = 1; - else - mem->shared = 1; - wat_next(p); - } - if (p->tok.kind != WT_RPAREN) { - if (!wat_parse_i64(p, &max_pages) || max_pages < min_pages) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: bad memory maximum"); - mem->has_max = 1; - mem->max_pages = (uint64_t)max_pages; - wat_next(p); - while (p->tok.kind == WT_ATOM && (tok_is(p->tok, "i64") || - tok_is(p->tok, "shared"))) { - if (tok_is(p->tok, "i64")) - mem->is64 = 1; - else - mem->shared = 1; - wat_next(p); - } - } - wasm_memory_ensure(p->c, p->module, p->module->nmemories - 1u, - mem->min_pages * 65536u); - wat_expect(p, WT_RPAREN, "')'"); -} - -static void wat_parse_memory_limits(WatParser* p, WasmMemory* mem) { - int64_t lo, hi; - while (p->tok.kind == WT_ATOM && (tok_is(p->tok, "i64") || - tok_is(p->tok, "shared"))) { - if (tok_is(p->tok, "i64")) - mem->is64 = 1; - else - mem->shared = 1; - wat_next(p); - } - if (!wat_parse_i64(p, &lo) || lo < 0) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected memory minimum"); - mem->min_pages = (uint64_t)lo; - wat_next(p); - while (p->tok.kind == WT_ATOM && (tok_is(p->tok, "i64") || - tok_is(p->tok, "shared"))) { - if (tok_is(p->tok, "i64")) - mem->is64 = 1; - else - mem->shared = 1; - wat_next(p); - } - if (p->tok.kind != WT_RPAREN) { - if (!wat_parse_i64(p, &hi) || hi < lo) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: bad memory maximum"); - mem->has_max = 1; - mem->max_pages = (uint64_t)hi; - wat_next(p); - while (p->tok.kind == WT_ATOM && (tok_is(p->tok, "i64") || - tok_is(p->tok, "shared"))) { - if (tok_is(p->tok, "i64")) - mem->is64 = 1; - else - mem->shared = 1; - wat_next(p); - } - } -} - -static void wat_parse_table_limits_and_type(WatParser* p, WasmTable* t) { - int64_t lo, hi; - if (!wat_parse_i64(p, &lo) || lo < 0 || lo > UINT32_MAX) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected table minimum"); - t->min = (uint32_t)lo; - wat_next(p); - if (p->tok.kind == WT_ATOM) { - WasmValType maybe_type; - if (!wat_val_type(p->tok, &maybe_type)) { - if (!wat_parse_i64(p, &hi) || hi < lo || hi > UINT32_MAX) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: bad table maximum"); - t->has_max = 1; - t->max = (uint32_t)hi; - wat_next(p); - } - } - if (!wat_val_type(p->tok, &t->elem_type)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected table element type"); - wat_next(p); -} - -static void wat_parse_import_field(WatParser* p) { - CfreeSym mod, name; - if (p->tok.kind != WT_STRING) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected import module string"); - mod = wat_intern_string(p, p->tok); - wat_next(p); - if (p->tok.kind != WT_STRING) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected import name string"); - name = wat_intern_string(p, p->tok); - wat_next(p); - wat_expect(p, WT_LPAREN, "'('"); - if (tok_is(p->tok, "func")) { - WasmFunc* f = wasm_add_func(p->c, p->module); - f->is_import = 1; - f->import_module = mod; - f->import_name = name; - wat_next(p); - if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { - f->name = wat_intern_tok(p, p->tok); - wat_next(p); - } - while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { - wat_expect(p, WT_LPAREN, "'('"); - if (tok_is(p->tok, "type")) { - int64_t typeidx; - wat_next(p); - wat_parse_type_index(p, &typeidx); - if (typeidx < 0 || (uint64_t)typeidx >= p->module->ntypes) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: type index out of range"); - f->typeidx = (uint32_t)typeidx; - f->has_typeidx = 1; - f->nparams = p->module->types[typeidx].nparams; - memcpy(f->params, p->module->types[typeidx].params, - sizeof(f->params[0]) * f->nparams); - f->nresults = p->module->types[typeidx].nresults; - memcpy(f->results, p->module->types[typeidx].results, - sizeof(f->results[0]) * f->nresults); - wat_next(p); - } else if (tok_is(p->tok, "param")) { - wat_next(p); - while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { - WasmValType vt; - if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { - wat_next(p); - continue; - } - if (!wat_val_type(p->tok, &vt) || !wasm_is_frontend_value_type(vt)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected parameter type"); - if (f->nparams >= 16u) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: too many parameters"); - f->params[f->nparams++] = vt; - wat_next(p); - } - } else if (tok_is(p->tok, "result")) { - wat_next(p); - while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { - WasmValType vt; - if (!wat_val_type(p->tok, &vt) || !wasm_is_frontend_value_type(vt)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected result type"); - if (f->nresults >= 1u) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: multi-result unsupported"); - f->results[f->nresults++] = vt; - wat_next(p); - } - } else { - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected import func type field"); - } - wat_expect(p, WT_RPAREN, "')'"); - } - if (!f->has_typeidx) f->typeidx = wasm_intern_func_type(p->c, p->module, f); - } else if (tok_is(p->tok, "memory")) { - WasmMemory* mem = wasm_add_memory(p->c, p->module); - mem->is_import = 1; - mem->import_module = mod; - mem->import_name = name; - wat_next(p); - if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { - mem->name = wat_intern_tok(p, p->tok); - wat_next(p); - } - wat_parse_memory_limits(p, mem); - } else if (tok_is(p->tok, "table")) { - WasmTable* t = wasm_add_table(p->c, p->module); - t->is_import = 1; - t->import_module = mod; - t->import_name = name; - wat_next(p); - if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { - t->name = wat_intern_tok(p, p->tok); - wat_next(p); - } - wat_parse_table_limits_and_type(p, t); - } else if (tok_is(p->tok, "global")) { - WasmGlobal* g = wasm_add_global(p->c, p->module); - g->is_import = 1; - g->import_module = mod; - g->import_name = name; - wat_next(p); - if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { - g->name = wat_intern_tok(p, p->tok); - wat_next(p); - } - if (p->tok.kind == WT_LPAREN) { - wat_next(p); - if (!tok_is(p->tok, "mut")) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected mut"); - g->mutable_ = 1; - wat_next(p); - if (!wat_val_type(p->tok, &g->type) || !wasm_is_num_type(g->type)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected global type"); - wat_next(p); - wat_expect(p, WT_RPAREN, "')'"); - } else { - if (!wat_val_type(p->tok, &g->type) || !wasm_is_num_type(g->type)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected global type"); - wat_next(p); - } - } else { - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: unsupported import kind"); - } - wat_expect(p, WT_RPAREN, "')'"); - wat_expect(p, WT_RPAREN, "')'"); -} - -static void wat_parse_table_field(WatParser* p) { - WasmTable* t = wasm_add_table(p->c, p->module); - if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { - t->name = wat_intern_tok(p, p->tok); - wat_next(p); - } - wat_parse_table_limits_and_type(p, t); - wat_expect(p, WT_RPAREN, "')'"); -} - -static void wat_parse_const_expr(WatParser* p, WasmInsn* out) { - WasmInsnKind kind; - int has_imm; - memset(out, 0, sizeof *out); - wat_expect(p, WT_LPAREN, "'('"); - if (!wat_instr_kind(p->tok, &kind, &has_imm) || - (kind != WASM_INSN_I32_CONST && kind != WASM_INSN_I64_CONST && - kind != WASM_INSN_F32_CONST && kind != WASM_INSN_F64_CONST)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected constant expression"); - out->kind = (uint8_t)kind; - wat_next(p); - if (kind == WASM_INSN_F32_CONST || kind == WASM_INSN_F64_CONST) { - if (!wat_parse_f64(p, &out->fp)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected float immediate"); - } else if (!wat_parse_i64(p, &out->imm)) { - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected integer immediate"); - } - wat_next(p); - wat_expect(p, WT_RPAREN, "')'"); -} - -static void wat_parse_global_field(WatParser* p) { - WasmGlobal* g = wasm_add_global(p->c, p->module); - if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { - g->name = wat_intern_tok(p, p->tok); - wat_next(p); - } - if (p->tok.kind == WT_LPAREN) { - wat_next(p); - if (tok_is(p->tok, "export")) { - wat_next(p); - if (p->tok.kind != WT_STRING) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected export string"); - g->export_name = wat_intern_string(p, p->tok); - { - WasmExport* ex = wasm_add_export(p->c, p->module); - ex->name = g->export_name; - ex->kind = 3; - ex->index = p->module->nglobals - 1u; - } - wat_next(p); - wat_expect(p, WT_RPAREN, "')'"); - } else if (tok_is(p->tok, "mut")) { - g->mutable_ = 1; - wat_next(p); - if (!wat_val_type(p->tok, &g->type) || !wasm_is_num_type(g->type)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected global type"); - wat_next(p); - wat_expect(p, WT_RPAREN, "')'"); - } else { - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected global type"); - } - } else { - if (!wat_val_type(p->tok, &g->type) || !wasm_is_num_type(g->type)) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected global type"); - wat_next(p); - } - if (!g->type) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: missing global type"); - wat_parse_const_expr(p, &g->init); - wat_expect(p, WT_RPAREN, "')'"); -} - -static void wat_parse_elem_field(WatParser* p) { - WasmElemSegment* e = wasm_add_elem(p->c, p->module); - e->tableidx = 0; - if (p->tok.kind == WT_ATOM) { - int64_t tableidx; - if (wat_parse_i64(p, &tableidx)) { - if (tableidx < 0 || tableidx > UINT32_MAX) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: table index out of range"); - e->tableidx = (uint32_t)tableidx; - wat_next(p); - } - } - { - WasmInsn off; - wat_parse_const_expr(p, &off); - if (off.kind != WASM_INSN_I32_CONST || off.imm < 0) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: element offset must be i32.const"); - e->offset = off.imm; - } - if (tok_is(p->tok, "func")) wat_next(p); - while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { - int64_t idx; - if (e->nfuncs >= 64u) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: too many element functions"); - wat_parse_func_index(p, &idx); - if (idx < 0 || idx > UINT32_MAX) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: element function index out of range"); - e->funcs[e->nfuncs++] = (uint32_t)idx; - wat_next(p); - } - wat_expect(p, WT_RPAREN, "')'"); -} - -static void wat_parse_start_field(WatParser* p) { - int64_t idx; - wat_parse_func_index(p, &idx); - if (idx < 0 || idx > UINT32_MAX) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: start function index out of range"); - p->module->has_start = 1; - p->module->start_func = (uint32_t)idx; - wat_next(p); - wat_expect(p, WT_RPAREN, "')'"); -} - -static void wat_parse_custom_field(WatParser* p) { - WasmCustom* cs; - size_t n = 0; - char* bytes; - if (p->tok.kind != WT_STRING) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected custom section name"); - cs = wasm_add_custom(p->c, p->module); - cs->name = wat_intern_string(p, p->tok); - if (cs->name == cfree_sym_intern(p->c, CFREE_SLICE_LIT("target_features")) || - cs->name == cfree_sym_intern(p->c, CFREE_SLICE_LIT("target-feature"))) - p->module->has_target_features = 1; - wat_next(p); - if (p->tok.kind == WT_STRING) { - bytes = wat_dup_string(p, p->tok, &n); - cs->data = (uint8_t*)p->module->heap->alloc(p->module->heap, n ? n : 1u, 1); - if (!cs->data) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm: out of memory"); - memcpy(cs->data, bytes, n); - cs->len = (uint32_t)n; - p->module->heap->free(p->module->heap, bytes, p->tok.len + 1u); - wat_next(p); - } - wat_expect(p, WT_RPAREN, "')'"); -} - -static void wat_parse_data_field(WatParser* p) { - int64_t offset = 0; - uint32_t memidx = 0; - char* bytes; - size_t nbytes; - size_t alloc_len; - if (p->tok.kind == WT_ATOM) { - WasmInsnKind next_kind; - int next_has_imm; - if (p->tok.len && p->tok.p[0] == '$') { - wat_next(p); - } else if (!wat_instr_kind(p->tok, &next_kind, &next_has_imm)) { - wat_parse_memory_index(p, &memidx); - wat_next(p); - } else { - wat_next(p); - } - } - if (p->tok.kind == WT_LPAREN) { - size_t save_pos = p->pos; - uint32_t save_line = p->line, save_col = p->col; - wat_next(p); - if (tok_is(p->tok, "memory")) { - wat_next(p); - wat_parse_memory_index(p, &memidx); - wat_next(p); - wat_expect(p, WT_RPAREN, "')'"); - } else { - p->pos = save_pos; - p->line = save_line; - p->col = save_col; - p->tok.kind = WT_LPAREN; - p->tok.p = p->src + p->pos - 1u; - p->tok.len = 1; - p->tok.line = save_line; - p->tok.col = save_col - 1u; - } - } - wat_expect(p, WT_LPAREN, "'('"); - if (!tok_is(p->tok, "i32.const") && !tok_is(p->tok, "i64.const")) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected const data offset"); - wat_next(p); - if (!wat_parse_i64(p, &offset) || offset < 0 || offset > UINT32_MAX) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: bad data offset"); - wat_next(p); - wat_expect(p, WT_RPAREN, "')'"); - if (p->tok.kind != WT_STRING) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: expected data string"); - alloc_len = p->tok.len + 1u; - bytes = wat_dup_string(p, p->tok, &nbytes); - if ((uint64_t)offset + nbytes > UINT32_MAX) - wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), - "wasm wat: data segment too large"); - wasm_memory_ensure(p->c, p->module, memidx, - (uint64_t)offset + (uint64_t)nbytes); - memcpy(p->module->memories[memidx].data + (uint32_t)offset, bytes, nbytes); - if ((uint64_t)offset + nbytes > p->module->memories[memidx].data_init_len) - p->module->memories[memidx].data_init_len = - (uint64_t)offset + (uint64_t)nbytes; - p->module->heap->free(p->module->heap, bytes, alloc_len); - wat_next(p); - wat_expect(p, WT_RPAREN, "')'"); -} - -void wasm_parse_wat(CfreeCompiler* c, CfreeSlice name, const CfreeSlice* input, - WasmModule* out) { - WatParser p; - memset(&p, 0, sizeof p); - p.c = c; - p.name = name.s; - p.src = input->s; - p.len = input->len; - p.line = 1; - p.col = 1; - p.module = out; - wat_next(&p); - wat_expect(&p, WT_LPAREN, "'('"); - if (!tok_is(p.tok, "module")) - wasm_error(c, wasm_loc(p.tok.line, p.tok.col), "wasm wat: expected module"); - wat_next(&p); - while (p.tok.kind != WT_RPAREN && p.tok.kind != WT_EOF) { - if (p.tok.kind != WT_LPAREN) - wasm_error(c, wasm_loc(p.tok.line, p.tok.col), - "wasm wat: expected module field"); - wat_next(&p); - if (tok_is(p.tok, "type")) { - wat_next(&p); - wat_parse_type_field(&p); - } else if (tok_is(p.tok, "func")) { - p.pos = (size_t)(p.tok.p - p.src); - p.line = p.tok.line; - p.col = p.tok.col; - p.tok.kind = WT_LPAREN; - p.tok.p = p.src + p.pos - 1u; - p.tok.len = 1; - p.tok.line = p.line; - p.tok.col = p.col - 1u; - wat_parse_func(&p); - } else if (tok_is(p.tok, "export")) { - wat_next(&p); - wat_parse_export_field(&p); - } else if (tok_is(p.tok, "memory")) { - wat_next(&p); - wat_parse_memory_field(&p); - } else if (tok_is(p.tok, "data")) { - wat_next(&p); - wat_parse_data_field(&p); - } else if (tok_is(p.tok, "import")) { - wat_next(&p); - wat_parse_import_field(&p); - } else if (tok_is(p.tok, "table")) { - wat_next(&p); - wat_parse_table_field(&p); - } else if (tok_is(p.tok, "global")) { - wat_next(&p); - wat_parse_global_field(&p); - } else if (tok_is(p.tok, "elem")) { - wat_next(&p); - wat_parse_elem_field(&p); - } else if (tok_is(p.tok, "start")) { - wat_next(&p); - wat_parse_start_field(&p); - } else if (tok_is(p.tok, "custom") || tok_is(p.tok, "@custom")) { - wat_next(&p); - wat_parse_custom_field(&p); - } else { - wat_skip_list(&p); - } - } - wat_expect(&p, WT_RPAREN, "')'"); - if (p.tok.kind != WT_EOF) - wasm_error(c, wasm_loc(p.tok.line, p.tok.col), - "wasm wat: trailing tokens after module"); -} diff --git a/mk/config.mk b/mk/config.mk @@ -8,11 +8,13 @@ cfg_flag = $(shell awk '/^\#define $(1)[ \t]/{print $$3; exit}' $(CFG_HDR)) CFREE_ARCH_AA64_ENABLED := $(call cfg_flag,CFREE_ARCH_AA64_ENABLED) CFREE_ARCH_X64_ENABLED := $(call cfg_flag,CFREE_ARCH_X64_ENABLED) CFREE_ARCH_RV64_ENABLED := $(call cfg_flag,CFREE_ARCH_RV64_ENABLED) +CFREE_ARCH_WASM_ENABLED := $(call cfg_flag,CFREE_ARCH_WASM_ENABLED) CFREE_ARCH_C_TARGET_ENABLED := $(call cfg_flag,CFREE_ARCH_C_TARGET_ENABLED) CFREE_OBJ_ELF_ENABLED := $(call cfg_flag,CFREE_OBJ_ELF_ENABLED) CFREE_OBJ_MACHO_ENABLED := $(call cfg_flag,CFREE_OBJ_MACHO_ENABLED) CFREE_OBJ_COFF_ENABLED := $(call cfg_flag,CFREE_OBJ_COFF_ENABLED) +CFREE_OBJ_WASM_ENABLED := $(call cfg_flag,CFREE_OBJ_WASM_ENABLED) CFREE_LANG_CPP_ENABLED := $(call cfg_flag,CFREE_LANG_CPP_ENABLED) CFREE_LANG_C_ENABLED := $(call cfg_flag,CFREE_LANG_C_ENABLED) diff --git a/src/abi/abi_internal.h b/src/abi/abi_internal.h @@ -20,6 +20,7 @@ typedef struct ABIVtable { extern const ABIVtable aapcs64_vtable; extern const ABIVtable sysv_x64_vtable; extern const ABIVtable rv64_vtable; +extern const ABIVtable wasm32_vtable; /* Apple Darwin variants — selected when (arch, os) matches. See * abi.c::select_vtable. */ extern const ABIVtable apple_arm64_vtable; diff --git a/src/abi/registry.c b/src/abi/registry.c @@ -32,7 +32,9 @@ static const ABIImpl abi_impls[] = { #if CFREE_ARCH_RV64_ENABLED && CFREE_OBJ_ELF_ENABLED {CFREE_ARCH_RV64, CFREE_OBJ_ELF, &rv64_vtable}, #endif - {CFREE_ARCH_WASM, CFREE_OBJ_WASM, NULL}, +#if CFREE_ARCH_WASM_ENABLED && CFREE_OBJ_WASM_ENABLED + {CFREE_ARCH_WASM, CFREE_OBJ_WASM, &wasm32_vtable}, +#endif }; const ABIVtable* abi_vtable_lookup(CfreeArchKind arch, CfreeObjFmt obj) { diff --git a/src/api/stubs.c b/src/api/stubs.c @@ -10,15 +10,6 @@ static _Noreturn void unimplemented(Compiler* c, const char* what) { SLICE_ARG(slice_from_cstr(what))); } -/* WASM emit/read remain stubs until those writers/readers land. - * COFF emit/read are implemented under src/obj/coff/. */ - -void emit_wasm(Compiler* c, ObjBuilder* o, Writer* w) { - (void)o; - (void)w; - unimplemented(c, "emit_wasm"); -} - ObjBuilder* read_wasm(Compiler* c, const char* n, const u8* d, size_t l) { (void)n; (void)d; diff --git a/src/api/wasm.c b/src/api/wasm.c @@ -0,0 +1,36 @@ +/* Public wasm host-import API surface. + * + * The setter/getter stash a borrowed table on the compiler. The actual bind + * (reading module-emitted import metadata and writing function pointers + * into instance slots) lives in lang/wasm/host_imports.c so the wasm + * frontend owns the metadata wire format. */ + +#include "core/core.h" + +#include <cfree/wasm.h> + +CFREE_API void cfree_wasm_set_host_imports(CfreeCompiler* c, + const CfreeWasmHostImport* imports, + size_t nimports, + CfreeWasmResolveFn resolve, + void* user) { + if (!c) return; + c->wasm_host_imports = imports; + c->wasm_host_nimports = nimports; + /* Function-pointer / void* conversion is implementation-defined in C + * but accepted by POSIX hosts cfree targets. Stash through a void* so + * we don't pull the public type into the internal header. */ + c->wasm_host_resolve = (void*)resolve; + c->wasm_host_user = user; +} + +CFREE_API void cfree_wasm_get_host_imports( + CfreeCompiler* c, const CfreeWasmHostImport** imports_out, + size_t* nimports_out, CfreeWasmResolveFn* resolve_out, void** user_out) { + if (imports_out) + *imports_out = c ? (const CfreeWasmHostImport*)c->wasm_host_imports : NULL; + if (nimports_out) *nimports_out = c ? c->wasm_host_nimports : 0; + if (resolve_out) + *resolve_out = c ? (CfreeWasmResolveFn)c->wasm_host_resolve : NULL; + if (user_out) *user_out = c ? c->wasm_host_user : NULL; +} diff --git a/src/arch/arch.h b/src/arch/arch.h @@ -835,6 +835,15 @@ struct CGTarget { u32 width, u32 address_space); void (*local_static_data_end)(CGTarget*); + /* Optional. When non-NULL, cfree_cg_data_label_addr panics with the + * returned wasm-style message before reaching the MCEmitter. Lets + * targets that cannot resolve function-local label addresses in + * static-data initializers (e.g. the Wasm backend) fail with a + * recognizable, target-prefixed diagnostic instead of an MCEmitter + * "bad label" assertion. The returned string must remain valid for + * the lifetime of the panic call (string literals are typical). */ + const char* (*data_label_addr_unsupported_msg)(CGTarget*); + /* ---- structured control flow ---- * Mirrors CG's scope ops. CG passes explicit break/continue targets so C * `for` continues can land on the increment expression rather than the loop @@ -1010,6 +1019,12 @@ struct CGTarget { * backends that leave it NULL accept all named clobbers as no-ops. */ int (*resolve_reg_name)(CGTarget*, Sym name, Reg* out, RegClass* cls_out); + /* Optional: handle a top-level `__asm__("...")` block (file scope, not + * inside a function). Backends that leave this NULL fall back to the + * generic asm-parser path through CfreeCg.mc. Wasm overrides this to + * diagnose-and-fail since the wasm module has no native asm parser. */ + void (*file_scope_asm)(CGTarget*, const char* src, size_t len); + /* ---- source-location tracking ---- * Sets the SrcLoc inherited by subsequent emit-side calls (binop/load/...). * opt_cgtarget stamps it on every recorded Inst; target CGTargets forward it diff --git a/src/arch/registry.c b/src/arch/registry.c @@ -31,6 +31,9 @@ extern const ArchImpl arch_impl_x64; extern const CGBackend cg_backend_c_target; #endif extern const CGBackend cg_backend_check; +#if CFREE_ARCH_WASM_ENABLED +extern const ArchImpl arch_impl_wasm; +#endif /* Arch-metadata roster. The arch_lookup_* helpers iterate this list when a * caller needs machine-arch metadata — answers only come from backends that @@ -47,6 +50,9 @@ static const ArchImpl* const arch_impls[] = { #if CFREE_ARCH_RV64_ENABLED &arch_impl_rv64, #endif +#if CFREE_ARCH_WASM_ENABLED + &arch_impl_wasm, +#endif }; static u32 arch_impls_count(void) { diff --git a/src/arch/wasm/abi.c b/src/arch/wasm/abi.c @@ -0,0 +1,146 @@ +/* wasm32 BasicCABI classifier. + * + * Reference: https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md + * + * Coverage for v1: + * void -> IGNORE + * scalar ≤ 8B -> DIRECT, one part (i32/i64/f32/f64 per scalar kind) + * pointer -> DIRECT, one INT part (4 bytes, ILP32) + * empty struct -> IGNORE + * singleton struct (one scalar field, size matches) -> DIRECT, one part + * other aggregate -> INDIRECT (sret/byval) + * array -> INDIRECT + * + * Wasm has no native 128-bit integer or x87 long-double; those panic on + * classification rather than silently routing through memory. */ + +#include <string.h> + +#include "abi/abi_internal.h" +#include "cg/type.h" +#include "core/arena.h" +#include "core/core.h" + +static void classify_void(ABIArgInfo* out) { + memset(out, 0, sizeof *out); + out->kind = ABI_ARG_IGNORE; +} + +static void classify_scalar(TargetABI* a, CfreeCgTypeId t, ABIArgInfo* out) { + ABITypeInfo ti = abi_internal_type_info(a, t); + ABIArgPart* parts; + + if (ti.size > 8) { + SrcLoc loc = {0, 0, 0}; + compiler_panic(a->c, loc, + "wasm32 ABI: scalar %u-byte values are not supported", + (unsigned)ti.size); + } + + out->kind = ABI_ARG_DIRECT; + out->flags = ABI_AF_NONE; + out->indirect_align = 0; + + parts = arena_new(a->c->tu, ABIArgPart); + memset(parts, 0, sizeof *parts); + parts->cls = (ti.scalar_kind == ABI_SC_FLOAT) ? ABI_CLASS_FP : ABI_CLASS_INT; + parts->loc = ABI_LOC_REG; + parts->size = ti.size; + parts->align = ti.align; + parts->src_offset = 0; + + out->parts = parts; + out->nparts = 1; +} + +/* BasicCABI singleton rule: a record with exactly one scalar field whose size + * equals the record's size is passed as that scalar. Otherwise the aggregate + * is passed indirectly. */ +static int try_classify_singleton(TargetABI* a, CfreeCgTypeId t, + ABIArgInfo* out) { + const CgType* ty = cg_type_get(a->c, t); + ABITypeInfo ti; + CfreeCgTypeId field_ty; + const CgType* field; + if (!ty || ty->kind != CFREE_CG_TYPE_RECORD || ty->record.nfields != 1) + return 0; + if (ty->record.is_union) return 0; + field_ty = ty->record.fields[0].type; + field = cg_type_get(a->c, field_ty); + if (!field) return 0; + if (field->kind == CFREE_CG_TYPE_RECORD || + field->kind == CFREE_CG_TYPE_ARRAY) + return 0; + ti = abi_internal_type_info(a, field_ty); + if (ti.size != (u32)ty->size) return 0; + classify_scalar(a, field_ty, out); + return 1; +} + +static void classify_aggregate(TargetABI* a, CfreeCgTypeId t, ABIArgInfo* out, + int is_return) { + ABITypeInfo ti = abi_internal_type_info(a, t); + if (ti.size == 0) { + classify_void(out); + return; + } + if (try_classify_singleton(a, t, out)) return; + out->kind = ABI_ARG_INDIRECT; + out->flags = is_return ? ABI_AF_SRET : ABI_AF_BYVAL; + out->indirect_align = ti.align ? ti.align : 4; + out->parts = NULL; + out->nparts = 0; +} + +static void classify_one(TargetABI* a, CfreeCgTypeId t, ABIArgInfo* out, + int is_return) { + const CgType* ty = cg_type_get(a->c, t); + if (!ty || ty->kind == CFREE_CG_TYPE_VOID) { + classify_void(out); + return; + } + switch (ty->kind) { + case CFREE_CG_TYPE_RECORD: + classify_aggregate(a, t, out, is_return); + return; + case CFREE_CG_TYPE_ARRAY: + /* Arrays decay to pointers in C function signatures; if one reaches + * the classifier, route indirect. */ + classify_aggregate(a, t, out, is_return); + return; + case CFREE_CG_TYPE_ALIAS: + classify_one(a, ty->alias.base, out, is_return); + return; + default: + classify_scalar(a, t, out); + return; + } +} + +static ABIFuncInfo* wasm32_compute_func_info(TargetABI* a, CfreeCgTypeId fn) { + ABIFuncInfo* info = arena_new(a->c->tu, ABIFuncInfo); + const CgType* fnty = cg_type_get(a->c, fn); + memset(info, 0, sizeof *info); + + classify_one(a, fnty->func.ret, &info->ret, /*is_return=*/1); + info->has_sret = (info->ret.kind == ABI_ARG_INDIRECT) ? 1 : 0; + info->variadic = fnty->func.abi_variadic ? 1u : 0u; + + info->nparams = (u16)fnty->func.nparams; + if (fnty->func.nparams) { + ABIArgInfo* arr = arena_array(a->c->tu, ABIArgInfo, fnty->func.nparams); + memset(arr, 0, sizeof(ABIArgInfo) * fnty->func.nparams); + for (u32 i = 0; i < fnty->func.nparams; ++i) { + classify_one(a, fnty->func.params[i].type, &arr[i], /*is_return=*/0); + } + info->params = arr; + } else { + info->params = NULL; + } + return info; +} + +const ABIVtable wasm32_vtable = { + .compute_func_info = wasm32_compute_func_info, + .va_list_info = {4, 4, ABI_SC_PTR, 0, 0, 0}, +}; diff --git a/src/arch/wasm/arch.c b/src/arch/wasm/arch.c @@ -0,0 +1,44 @@ +/* arch_impl_wasm: Wasm target descriptor. + * + * Native machine-code emitters (ELF/Mach-O/COFF), the assembler, and the + * disassembler are intentionally NULL for v1. The Wasm target produces a + * WasmModule attached to the ObjBuilder; emit_wasm flushes it via + * wasm_encode. There is no assembly form for wasm32 in the toolchain. */ + +#include "arch/arch.h" +#include "arch/wasm/internal.h" + +/* Predefined macros mirroring clang/llvm conventions. */ +static const CfreePredefinedMacro wasm_predefined_macros[] = { + {CFREE_SLICE_LIT("__wasm__"), CFREE_SLICE_LIT("1")}, + {CFREE_SLICE_LIT("__wasm32__"), CFREE_SLICE_LIT("1")}, + {CFREE_SLICE_LIT("__ILP32__"), CFREE_SLICE_LIT("1")}, + {CFREE_SLICE_LIT("__ORDER_LITTLE_ENDIAN__"), CFREE_SLICE_LIT("1234")}, + {CFREE_SLICE_LIT("__ORDER_BIG_ENDIAN__"), CFREE_SLICE_LIT("4321")}, + {CFREE_SLICE_LIT("__BYTE_ORDER__"), CFREE_SLICE_LIT("__ORDER_LITTLE_ENDIAN__")}, + {CFREE_SLICE_LIT("__LITTLE_ENDIAN__"), CFREE_SLICE_LIT("1")}, +}; + +static CGTarget* wasm_backend_make(Compiler* c, ObjBuilder* o, + const CfreeCodeOptions* opts) { + (void)opts; + return wasm_cgtarget_new(c, o, NULL); +} + +const ArchImpl arch_impl_wasm = { + .backend = {.name = "wasm", .make = wasm_backend_make}, + .kind = CFREE_ARCH_WASM, + .name = "wasm", + .cgtarget_new = wasm_cgtarget_new, + .asm_new = NULL, + .disasm_new = NULL, + .apply_label_fixup = NULL, + .link = NULL, + .predefined_macros = wasm_predefined_macros, + .npredefined_macros = + (u32)(sizeof wasm_predefined_macros / sizeof wasm_predefined_macros[0]), + .register_name = NULL, + .register_index = NULL, + .register_count = NULL, + .register_at = NULL, +}; diff --git a/src/arch/wasm/emit.c b/src/arch/wasm/emit.c @@ -0,0 +1,4447 @@ +/* Wasm CGTarget emission. + * + * Records CGTarget operations into a per-function WIR list, then linearizes + * to a WasmFunc body at func_end. Each SSA Reg becomes a Wasm local; control + * flow that fits the cfree_cg_if_begin/else/end pattern lowers to + * if/else/end, while CG scopes (SCOPE_LOOP) lower to (block (loop ...)). */ + +#include "arch/wasm/internal.h" + +#include <stdarg.h> +#include <string.h> + +#include "abi/abi.h" +#include "arch/wasm/wasm_imports.h" +#include "cg/type.h" +#include "core/arena.h" +#include "core/buf.h" +#include "core/heap.h" +#include "core/pool.h" +#include "obj/obj.h" + +/* Shared Wasm core: in-memory WasmModule, helpers (wasm_add_func, + * wasm_intern_func_type, wasm_func_add_insn, ...), and wasm_encode for + * the final flush from emit_wasm. */ +#include "wasm/wasm.h" + +/* ----------------------------------------------------------------- + * Helpers + * ----------------------------------------------------------------- */ + +static SrcLoc cur_loc(WTarget* t) { + /* Prefer the most recent statement loc the frontend reported via + * wasm_set_loc — gives diagnostics the actual failing line, not the + * function definition's line. Fall back to the function loc when no + * statement loc has been set (line == 0). */ + if (t->cur_stmt_loc.line) return t->cur_stmt_loc; + if (t->cur_fn_desc) return t->cur_fn_desc->loc; + SrcLoc l = {0, 0, 0}; + return l; +} + +static _Noreturn void wfail(WTarget* t, const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + compiler_panicv(t->c, cur_loc(t), fmt, ap); +} + +static _Noreturn void wfail_at(WTarget* t, SrcLoc loc, const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + compiler_panicv(t->c, loc, fmt, ap); +} + +static struct WasmModule* ensure_module(WTarget* t); + +static const char* pool_sym_cstr(Pool* p, Sym sym, size_t* len_out) { + Slice sl = pool_slice(p, sym); + if (len_out) *len_out = sl.len; + return sl.s; +} + +static WasmValType valtype_for_size_kind(WTarget* t, u32 size, u8 scalar_kind) { + if (scalar_kind == ABI_SC_FLOAT) { + if (size == 4) return WASM_VAL_F32; + if (size == 8) return WASM_VAL_F64; + wfail(t, "wasm: unsupported float size %u", size); + } + if (size <= 4) return WASM_VAL_I32; + if (size == 8) return WASM_VAL_I64; + wfail(t, "wasm: unsupported integer size %u", size); +} + +static WasmValType valtype_for_type(WTarget* t, CfreeCgTypeId ty) { + ABITypeInfo ti = abi_cg_type_info(t->c->abi, ty); + if (ti.scalar_kind == ABI_SC_VOID) { + wfail(t, "wasm: void value type requested"); + } + if (ti.scalar_kind == ABI_SC_PTR) return WASM_VAL_I32; /* wasm32 ILP32 */ + return valtype_for_size_kind(t, ti.size, ti.scalar_kind); +} + +static u32 align_to_u32(u32 v, u32 a) { + if (!a) return v; + return (v + a - 1u) & ~(a - 1u); +} + +/* Low-memory guard: leaves the first NULL_GUARD bytes of linear memory + * unassigned so addr==0 never resolves to a real symbol. */ +#define WASM_DATA_NULL_GUARD 16u + +static void type_size_align(WTarget* t, CfreeCgTypeId ty, u32 fallback_size, + u32 fallback_align, u32* size_out, + u32* align_out) { + ABITypeInfo ti; + if (ty) { + ti = abi_cg_type_info(t->c->abi, ty); + *size_out = ti.size ? ti.size : fallback_size; + *align_out = ti.align ? ti.align : fallback_align; + } else { + *size_out = fallback_size; + *align_out = fallback_align; + } + if (!*size_out) *size_out = 1; + if (!*align_out) *align_out = 1; +} + +static u32 add_wasm_local(WTarget* t, WasmValType vt) { + if (!t->cur_func) wfail(t, "wasm: local allocation outside a function"); + return wasm_func_push_local(t->c, t->module, t->cur_func, vt); +} + +static void ensure_linear_memory(WTarget* t) { + ensure_module(t); + if (!t->has_memory) { + WasmMemory* mem = wasm_add_memory(t->c, t->module); + mem->min_pages = 1; + t->has_memory = 1; + } +} + +/* Atomic memory ops require the linear memory to be declared shared and to + * carry a maximum size. We promote the (single) module memory to shared on + * first atomic emission. max_pages is provisionally set to the wasm32 limit + * (65536, i.e. 4 GiB); wasm_materialize_data tightens it to match min_pages + * after the final layout is known so embedders can pre-reserve a snug arena. */ +static void ensure_shared_memory(WTarget* t) { + ensure_linear_memory(t); + WasmMemory* mem = &t->module->memories[0]; + if (!mem->shared) { + mem->shared = 1; + } + if (!mem->has_max) { + mem->has_max = 1; + mem->max_pages = 65536u; + } +} + +static void ensure_stack_pointer(WTarget* t) { + ensure_linear_memory(t); + if (!t->has_stack_pointer) { + WasmGlobal* g = wasm_add_global(t->c, t->module); + g->name = wasm_strdup(t->module->heap, "__stack_pointer", + sizeof("__stack_pointer") - 1u); + g->type = WASM_VAL_I32; + g->mutable_ = 1; + g->init.kind = WASM_INSN_I32_CONST; + g->init.imm = 65536; + t->stack_pointer_global = t->module->nglobals - 1u; + t->stack_size = 65536; + t->has_stack_pointer = 1; + } +} + +/* Map an SSA Reg to its WasmFunc local index, allocating on first use. */ +static u32 reg_local(WTarget* t, Reg r, CfreeCgTypeId ty, RegClass cls) { + Heap* h = t->c->ctx->heap; + if (r == REG_NONE) wfail(t, "wasm: REG_NONE used as operand"); + if (r >= t->reg_cap) { + u32 nc = t->reg_cap ? t->reg_cap : 16u; + while (nc <= r) nc *= 2u; + u32* nl = (u32*)h->realloc(h, t->reg_to_local, + sizeof(u32) * t->reg_cap, sizeof(u32) * nc, _Alignof(u32)); + CfreeCgTypeId* nt = (CfreeCgTypeId*)h->realloc( + h, t->reg_type, sizeof(CfreeCgTypeId) * t->reg_cap, + sizeof(CfreeCgTypeId) * nc, _Alignof(CfreeCgTypeId)); + u8* nc_arr = (u8*)h->realloc(h, t->reg_cls, t->reg_cap, nc, 1); + if (!nl || !nt || !nc_arr) wfail(t, "wasm: out of memory"); + for (u32 i = t->reg_cap; i < nc; ++i) { + nl[i] = 0xffffffffu; + nt[i] = CFREE_CG_TYPE_NONE; + nc_arr[i] = 0; + } + t->reg_to_local = nl; + t->reg_type = nt; + t->reg_cls = nc_arr; + t->reg_cap = nc; + } + /* CG may reuse the same Reg id with different value types: api_ensure_reg + * for an SV_CMP reuses one of the cmp operands' Regs (originally e.g. i64) + * to hold the i32 cmp result. Detect a type change and rebind to a fresh + * wasm local; the previous binding is dead from CG's point of view. */ + if (t->reg_to_local[r] != 0xffffffffu) { + WasmValType cached_vt = valtype_for_type(t, t->reg_type[r]); + WasmValType want_vt = valtype_for_type(t, ty); + if (cached_vt == want_vt) return t->reg_to_local[r]; + /* fall through to allocate fresh */ + } + { + WasmValType vt = valtype_for_type(t, ty); + t->reg_to_local[r] = add_wasm_local(t, vt); + t->reg_type[r] = ty; + t->reg_cls[r] = (u8)cls; + } + return t->reg_to_local[r]; +} + +/* ----------------------------------------------------------------- + * WIR appending + * ----------------------------------------------------------------- */ + +static WIR* wir_push(WTarget* t) { + Heap* h = t->c->ctx->heap; + if (t->nwir == t->wir_cap) { + u32 nc = t->wir_cap ? t->wir_cap * 2u : 64u; + void* p = h->realloc(h, t->wir, sizeof(WIR) * t->wir_cap, sizeof(WIR) * nc, + _Alignof(WIR)); + if (!p) wfail(t, "wasm: out of memory"); + t->wir = (WIR*)p; + t->wir_cap = nc; + } + WIR* w = &t->wir[t->nwir++]; + memset(w, 0, sizeof *w); + return w; +} + +/* Operand-kind encoding stored in WIR's imm_kind / imm_kind_b. */ +enum { WOP_REG = 0, WOP_IMM = 1, WOP_LOCAL = 2, WOP_WASM_LOCAL = 3, WOP_ADDR = 4 }; + +static void wir_capture_operand(WIR* w, int which, Operand op) { + u32 kind; + i64 ival; + Reg r = REG_NONE; + switch (op.kind) { + case OPK_REG: + kind = WOP_REG; + r = op.v.reg; + ival = 0; + break; + case OPK_IMM: + kind = WOP_IMM; + ival = op.v.imm; + break; + case OPK_LOCAL: + kind = WOP_LOCAL; + ival = (i64)op.v.frame_slot; + break; + default: + kind = 99u; + ival = 0; + break; + } + if (which == 0) { + w->imm_kind = kind; + w->imm_a = ival; + w->a = r; + } else { + w->imm_kind_b = kind; + w->imm_b = ival; + w->b = r; + } +} + +/* ----------------------------------------------------------------- + * Labels + * ----------------------------------------------------------------- */ + +Label wasm_label_new(CGTarget* tg) { + WTarget* t = (WTarget*)tg; + Heap* h = t->c->ctx->heap; + if (t->nlabels == t->labels_cap) { + u32 nc = t->labels_cap ? t->labels_cap * 2u : 16u; + void* p = h->realloc(h, t->labels, sizeof(WLabel) * t->labels_cap, + sizeof(WLabel) * nc, _Alignof(WLabel)); + if (!p) wfail(t, "wasm: out of memory"); + t->labels = (WLabel*)p; + t->labels_cap = nc; + } + u32 id = t->nlabels++; + memset(&t->labels[id], 0, sizeof t->labels[id]); + return (Label)(id + 1u); +} + +static WLabel* lookup_label(WTarget* t, Label l) { + if (l == LABEL_NONE || l - 1u >= t->nlabels) return NULL; + return &t->labels[l - 1u]; +} + +void wasm_label_place(CGTarget* tg, Label l) { + WTarget* t = (WTarget*)tg; + WLabel* lbl = lookup_label(t, l); + if (!lbl) wfail(t, "wasm: label_place on unknown label"); + /* If this label is registered to a scope, the scope ops drive the wasm + * structure — placement here is a no-op. */ + if (lbl->kind == WLBL_SCOPE_BREAK || lbl->kind == WLBL_SCOPE_CONT) { + lbl->placed = 1; + t->dead = 0; + return; + } + /* Free-standing label placement: record but emit nothing. The CG layer + * sometimes places scope continue/break labels just before/after a + * scope_begin/end pair; for wasm the structured scope ops drive the + * `br N` targets, so these placements are no-ops. A subsequent jump or + * cmp_branch that lands on an unbound label will diagnose. */ + lbl->placed = 1; + lbl->wir_index = t->nwir; + if (lbl->kind == WLBL_UNBOUND) lbl->kind = WLBL_FORWARD; + WIR* w = wir_push(t); + w->op = WIR_LABEL; + w->labels[0] = l; + t->dead = 0; +} + +void wasm_jump(CGTarget* tg, Label l) { + WTarget* t = (WTarget*)tg; + if (t->dead) return; + WLabel* lbl = lookup_label(t, l); + if (!lbl) wfail(t, "wasm: jump to unknown label"); + WIR* w = wir_push(t); + w->op = WIR_JUMP; + w->labels[0] = l; + t->dead = 1; +} + +void wasm_cmp_branch(CGTarget* tg, CmpOp op, Operand a, Operand b, Label l) { + WTarget* t = (WTarget*)tg; + if (t->dead) return; + if (!lookup_label(t, l)) + wfail(t, "wasm: cmp_branch to unknown label"); + WIR* w = wir_push(t); + w->op = WIR_CMP_BRANCH; + w->cgop = (u8)op; + wir_capture_operand(w, 0, a); + wir_capture_operand(w, 1, b); + w->type = a.type ? a.type : b.type; + w->labels[0] = l; +} + +void wasm_switch(CGTarget* tg, const CGSwitchDesc* d) { + WTarget* t = (WTarget*)tg; + WIR* w; + if (t->dead) return; + if (!d) wfail(t, "wasm: switch without descriptor"); + if (d->default_label == LABEL_NONE) + wfail(t, "wasm: switch without default label"); + if (d->ncases && !d->cases) + wfail(t, "wasm: switch case count without cases"); + if (d->ncases >= 64u) + wfail(t, "wasm: too many switch cases for br_table"); + if (d->selector.kind != OPK_REG && d->selector.kind != OPK_IMM && + d->selector.kind != OPK_LOCAL) + wfail(t, "wasm: switch selector has unsupported operand kind"); + if (!lookup_label(t, d->default_label)) + wfail(t, "wasm: switch default label is unknown"); + for (u32 i = 0; i < d->ncases; ++i) { + if (!lookup_label(t, d->cases[i].label)) + wfail(t, "wasm: switch case label is unknown"); + } + + w = wir_push(t); + w->op = WIR_SWITCH; + wir_capture_operand(w, 0, d->selector); + w->type = d->selector_type; + w->labels[0] = d->default_label; + w->switch_ncases = d->ncases; + if (d->ncases) { + Heap* h = t->c->ctx->heap; + w->switch_cases = + (CGSwitchCase*)h->alloc(h, sizeof(CGSwitchCase) * d->ncases, + _Alignof(CGSwitchCase)); + if (!w->switch_cases) wfail(t, "wasm: out of memory"); + memcpy(w->switch_cases, d->cases, sizeof(CGSwitchCase) * d->ncases); + } + t->dead = 1; +} + +/* ----------------------------------------------------------------- + * Scopes + * ----------------------------------------------------------------- */ + +CGScope wasm_scope_begin(CGTarget* tg, const CGScopeDesc* d) { + WTarget* t = (WTarget*)tg; + if (t->nscopes >= 32u) + wfail(t, "wasm: too many nested scopes (max 32)"); + WScope* s = &t->scopes[t->nscopes]; + memset(s, 0, sizeof *s); + s->id = ++t->next_scope_id; + s->cg_kind = d->kind; + s->break_lbl = d->break_label; + s->cont_lbl = d->continue_label; + s->result_type = d->result_type; + + /* Wire scope's break/continue labels to this scope so jump()/cmp_branch() + * to them can lower to wasm `br`. */ + if (d->break_label != LABEL_NONE) { + WLabel* lbl = lookup_label(t, d->break_label); + if (lbl) { + lbl->kind = WLBL_SCOPE_BREAK; + lbl->scope_id = s->id; + } + } + if (d->continue_label != LABEL_NONE) { + WLabel* lbl = lookup_label(t, d->continue_label); + if (lbl) { + lbl->kind = WLBL_SCOPE_CONT; + lbl->scope_id = s->id; + } + } + + if (d->kind == SCOPE_IF) { + /* SCOPE_IF receives a precomputed condition operand. */ + if (d->cond.kind == OPK_REG) { + s->cond_reg = d->cond.v.reg; + } else if (d->cond.kind == OPK_IMM) { + /* Materialize via a synthetic load_imm to keep the lowering uniform. */ + WIR* w = wir_push(t); + w->op = WIR_LOAD_IMM; + w->dst = (Reg)1; /* placeholder — emitted inline; no register needed */ + w->imm = d->cond.v.imm; + w->type = d->cond.type; + s->cond_reg = REG_NONE; + } else { + wfail(t, "wasm: scope_begin(SCOPE_IF) with unsupported cond kind"); + } + } + + WIR* open = wir_push(t); + open->op = WIR_SCOPE_OPEN; + open->scope_id = s->id; + open->cgop = d->kind; + open->dst = s->cond_reg; + + s->placed_in_wir = 1; + t->nscopes++; + return (CGScope)s->id; +} + +static WScope* scope_by_id(WTarget* t, u32 id) { + for (u32 i = 0; i < t->nscopes; ++i) { + if (t->scopes[i].id == id) return &t->scopes[i]; + } + return NULL; +} + +void wasm_scope_else(CGTarget* tg, CGScope sc) { + WTarget* t = (WTarget*)tg; + WScope* s = scope_by_id(t, (u32)sc); + if (!s) wfail(t, "wasm: scope_else on unknown scope"); + if (s->cg_kind != SCOPE_IF) + wfail(t, "wasm: scope_else on non-if scope"); + WIR* w = wir_push(t); + w->op = WIR_SCOPE_ELSE; + w->scope_id = s->id; + t->dead = 0; +} + +void wasm_scope_end(CGTarget* tg, CGScope sc) { + WTarget* t = (WTarget*)tg; + WScope* s = scope_by_id(t, (u32)sc); + if (!s) wfail(t, "wasm: scope_end on unknown scope"); + WIR* w = wir_push(t); + w->op = WIR_SCOPE_CLOSE; + w->scope_id = s->id; + /* Pop the scope from the stack. CG always closes in LIFO order. */ + if (t->nscopes == 0 || t->scopes[t->nscopes - 1u].id != s->id) + wfail(t, "wasm: scope_end out of LIFO order"); + t->nscopes--; + t->dead = 0; +} + +void wasm_break_to(CGTarget* tg, CGScope sc) { + WTarget* t = (WTarget*)tg; + if (t->dead) return; + WScope* s = scope_by_id(t, (u32)sc); + if (!s) wfail(t, "wasm: break_to unknown scope"); + WIR* w = wir_push(t); + w->op = WIR_JUMP; + w->labels[0] = s->break_lbl; + t->dead = 1; +} + +void wasm_continue_to(CGTarget* tg, CGScope sc) { + WTarget* t = (WTarget*)tg; + if (t->dead) return; + WScope* s = scope_by_id(t, (u32)sc); + if (!s) wfail(t, "wasm: continue_to unknown scope"); + WIR* w = wir_push(t); + w->op = WIR_JUMP; + w->labels[0] = s->cont_lbl; + t->dead = 1; +} + +/* ----------------------------------------------------------------- + * Function lifecycle + * ----------------------------------------------------------------- */ + +/* Forward decl — promotes an undefined function symbol's WasmFunc to an + * import using the supplied ABI to build the wasm signature. */ +static void promote_import_func(WTarget* t, ObjSymId sym, WasmFunc* f, + const ABIFuncInfo* abi); + +/* Lookup or allocate a Wasm function index for an ObjSymId. Returns + * (wasm_func_idx, *out_func) on success. */ +static u32 sym_to_wasm_func(WTarget* t, ObjSymId sym, WasmFunc** out_func) { + Heap* h = t->c->ctx->heap; + if (sym >= t->sym_to_func_cap) { + u32 nc = t->sym_to_func_cap ? t->sym_to_func_cap : 16u; + while (nc <= sym) nc *= 2u; + u32* p = (u32*)h->realloc(h, t->sym_to_func, + sizeof(u32) * t->sym_to_func_cap, + sizeof(u32) * nc, _Alignof(u32)); + if (!p) wfail(t, "wasm: out of memory"); + for (u32 i = t->sym_to_func_cap; i < nc; ++i) p[i] = 0; + t->sym_to_func = p; + t->sym_to_func_cap = nc; + } + if (t->sym_to_func[sym]) { + u32 idx = t->sym_to_func[sym] - 1u; + if (out_func) *out_func = &t->module->funcs[idx]; + return idx; + } + /* Create a fresh WasmFunc and link. */ + WasmFunc* f = wasm_add_func(t->c, t->module); + u32 idx = t->module->nfuncs - 1u; + t->sym_to_func[sym] = idx + 1u; + if (out_func) *out_func = f; + return idx; +} + +static WSlot* slot_push(WTarget* t) { + Heap* h = t->c->ctx->heap; + if (t->nslots == t->slots_cap) { + u32 nc = t->slots_cap ? t->slots_cap * 2u : 16u; + WSlot* ns = (WSlot*)h->realloc(h, t->slots, sizeof(WSlot) * t->slots_cap, + sizeof(WSlot) * nc, _Alignof(WSlot)); + if (!ns) wfail(t, "wasm: out of memory"); + t->slots = ns; + t->slots_cap = nc; + } + WSlot* s = &t->slots[t->nslots++]; + memset(s, 0, sizeof *s); + return s; +} + +static FrameSlot alloc_frame_slot_kind(WTarget* t, CfreeCgTypeId ty, u32 size, + u32 align, int stack_backed) { + WSlot* s; + u32 slot_id; + type_size_align(t, ty, size, align, &size, &align); + s = slot_push(t); + s->type = ty; + s->size = size; + s->align = align; + if (stack_backed) { + ensure_stack_pointer(t); + t->frame_size = align_to_u32(t->frame_size, align); + s->kind = W_SLOT_STACK; + s->frame_offset = t->frame_size; + t->frame_size += size; + if (align > t->frame_align) t->frame_align = align; + t->has_stack_frame = 1; + } else { + s->kind = W_SLOT_LOCAL; + s->wasm_local = add_wasm_local(t, valtype_for_type(t, ty)); + } + slot_id = t->nslots; + return (FrameSlot)slot_id; +} + +static WSlot* slot_for(WTarget* t, FrameSlot fs) { + if (fs == FRAME_SLOT_NONE) wfail(t, "wasm: FRAME_SLOT_NONE used"); + u32 idx = fs - 1u; + if (idx >= t->nslots) wfail(t, "wasm: bad frame slot id"); + return &t->slots[idx]; +} + +static void promote_slot_to_stack(WTarget* t, WSlot* s) { + if (s->kind == W_SLOT_STACK) return; + ensure_stack_pointer(t); + t->frame_size = align_to_u32(t->frame_size, s->align ? s->align : 1u); + s->frame_offset = t->frame_size; + t->frame_size += s->size ? s->size : 1u; + if (s->align > t->frame_align) t->frame_align = s->align; + s->kind = W_SLOT_STACK; + t->has_stack_frame = 1; +} + +void wasm_func_begin(CGTarget* tg, const CGFuncDesc* d) { + WTarget* t = (WTarget*)tg; + WasmFunc* f; + u32 idx; + const CgType* fnty; + const ABIFuncInfo* abi; + Heap* h = t->c->ctx->heap; + + ensure_module(t); + t->cur_fn_desc = d; + memset(&t->cur_stmt_loc, 0, sizeof t->cur_stmt_loc); + t->nwir = 0; + t->nlabels = 0; + t->nslots = 0; + t->nscopes = 0; + t->next_scope_id = 0; + t->frame_size = 0; + t->frame_align = 1; + t->frame_base_local = 0xffffffffu; + t->frame_saved_sp_local = 0xffffffffu; + t->has_stack_frame = 0; + t->dead = 0; + t->sret_param_local = 0xffffffffu; + t->va_ptr_param_local = 0xffffffffu; + t->cur_has_sret = 0; + t->cur_is_variadic = 0; + t->varcall_saved_sp_local = 0xffffffffu; + t->varcall_buf_local = 0xffffffffu; + t->va_arg_tmp_addr_local = 0xffffffffu; + t->nparams_cg = 0; + t->nbyval_copies = 0; + /* Wipe reg map. */ + for (u32 i = 0; i < t->reg_cap; ++i) t->reg_to_local[i] = 0xffffffffu; + + idx = sym_to_wasm_func(t, d->sym, &f); + t->cur_func_idx = idx; + t->cur_func = f; + + fnty = cg_type_get(t->c, d->fn_type); + if (!fnty || fnty->kind != CFREE_CG_TYPE_FUNC) + wfail(t, "wasm: func_begin without function type"); + abi = d->abi; + if (!abi) wfail(t, "wasm: func_begin with no ABI info"); + + /* Build the wasm function's param layout: + * [sret_ptr]? [param_0] [param_1] ... + * with IGNORE'd CG params dropped. Record per-CG-param the wasm-local + * index so wasm_param can place each frame slot on the right local. + * params/locals/local_names grow on demand — no fixed cap. */ + f->nparams = 0; + if (abi->has_sret) { + t->sret_param_local = wasm_func_push_param(t->c, t->module, f, WASM_VAL_I32); + t->cur_has_sret = 1; + ensure_linear_memory(t); + } + if (fnty->func.nparams > t->param_local_idx_cap) { + u32 nc = t->param_local_idx_cap ? t->param_local_idx_cap : 4u; + while (nc < fnty->func.nparams) nc *= 2u; + u32* p = (u32*)h->realloc(h, t->param_local_idx, + sizeof(u32) * t->param_local_idx_cap, + sizeof(u32) * nc, _Alignof(u32)); + if (!p) wfail(t, "wasm: out of memory"); + t->param_local_idx = p; + t->param_local_idx_cap = nc; + } + t->nparams_cg = fnty->func.nparams; + for (u32 i = 0; i < fnty->func.nparams; ++i) { + const ABIArgInfo* ai = &abi->params[i]; + if (ai->kind == ABI_ARG_IGNORE) { + t->param_local_idx[i] = 0xffffffffu; + continue; + } + if (ai->kind == ABI_ARG_INDIRECT) { + t->param_local_idx[i] = + wasm_func_push_param(t->c, t->module, f, WASM_VAL_I32); + ensure_linear_memory(t); + } else { + if (ai->nparts != 1) + wfail(t, "wasm: multi-part DIRECT param %u not yet implemented", i); + const ABIArgPart* p = &ai->parts[0]; + WasmValType vt = valtype_for_size_kind( + t, p->size, p->cls == ABI_CLASS_FP ? ABI_SC_FLOAT : ABI_SC_INT); + t->param_local_idx[i] = wasm_func_push_param(t->c, t->module, f, vt); + } + } + /* Variadic: append hidden i32 va_ptr trailing param. Must match + * abi_to_wasm_func_type so indirect calls' signature interning agrees. */ + if (abi->variadic) { + t->va_ptr_param_local = + wasm_func_push_param(t->c, t->module, f, WASM_VAL_I32); + t->cur_is_variadic = 1; + ensure_linear_memory(t); + } + f->nresults = 0; + if (!abi->has_sret && abi->ret.kind == ABI_ARG_DIRECT && abi->ret.nparts == 1) { + const ABIArgPart* p = &abi->ret.parts[0]; + f->results[0] = valtype_for_size_kind(t, p->size, p->cls == ABI_CLASS_FP ? ABI_SC_FLOAT : ABI_SC_INT); + f->nresults = 1; + } + f->typeidx = wasm_intern_func_type(t->c, t->module, f); + + /* Export under the symbol's name when the symbol is globally bound. */ + const ObjSym* sym = obj_symbol_get(t->obj, d->sym); + if (sym && sym->bind != SB_LOCAL) { + const char* name = pool_sym_cstr(t->c->global, sym->name, NULL); + if (name && *name) { + Heap* h = t->c->ctx->heap; + size_t nlen = strlen(name); + char* dup = (char*)h->alloc(h, nlen + 1u, 1); + memcpy(dup, name, nlen + 1u); + f->export_name = dup; + WasmExport* e = wasm_add_export(t->c, t->module); + char* exp_name = (char*)h->alloc(h, nlen + 1u, 1); + memcpy(exp_name, name, nlen + 1u); + e->name = exp_name; + e->kind = 0; /* function export */ + e->index = idx; + } + } +} + +CGLocalStorage wasm_param(CGTarget* tg, const CGParamDesc* d) { + WTarget* t = (WTarget*)tg; + CGLocalStorage ls; + Heap* h = t->c->ctx->heap; + u32 wli; + WSlot* s; + memset(&ls, 0, sizeof ls); + if (d->index >= t->nparams_cg) + wfail(t, "wasm: param index %u out of range (nparams=%u)", + d->index, t->nparams_cg); + wli = t->param_local_idx[d->index]; + if (wli == 0xffffffffu) { + /* ABI_ARG_IGNORE — no wasm storage. Push a placeholder slot so the + * returned FrameSlot is meaningful to CG; it never gets emitted. */ + s = slot_push(t); + s->type = d->type; + s->size = d->size; + s->align = d->align ? d->align : 1u; + s->kind = W_SLOT_LOCAL; + s->wasm_local = 0; + ls.kind = CG_LOCAL_STORAGE_FRAME; + ls.v.frame_slot = (FrameSlot)t->nslots; + return ls; + } + if (d->abi && d->abi->kind == ABI_ARG_INDIRECT) { + /* byval: callee receives an i32 pointer; copy the aggregate into a + * caller-isolated stack-backed slot at function entry. */ + u32 size = d->size; + u32 align = d->align ? d->align : 1u; + type_size_align(t, d->type, size, align, &size, &align); + ensure_stack_pointer(t); + s = slot_push(t); + s->type = d->type; + s->size = size; + s->align = align; + s->kind = W_SLOT_STACK; + t->frame_size = align_to_u32(t->frame_size, align); + s->frame_offset = t->frame_size; + t->frame_size += size; + if (align > t->frame_align) t->frame_align = align; + t->has_stack_frame = 1; + /* Queue prologue copy-in from the pointer's wasm-local into &slot. */ + if (t->nbyval_copies == t->byval_copies_cap) { + u32 nc = t->byval_copies_cap ? t->byval_copies_cap * 2u : 4u; + WByvalCopy* nb = (WByvalCopy*)h->realloc( + h, t->byval_copies, sizeof(WByvalCopy) * t->byval_copies_cap, + sizeof(WByvalCopy) * nc, _Alignof(WByvalCopy)); + if (!nb) wfail(t, "wasm: out of memory"); + t->byval_copies = nb; + t->byval_copies_cap = nc; + } + WByvalCopy* bc = &t->byval_copies[t->nbyval_copies++]; + bc->ptr_wasm_local = wli; + bc->dst_slot_id = t->nslots - 1u; + ls.kind = CG_LOCAL_STORAGE_FRAME; + ls.v.frame_slot = (FrameSlot)t->nslots; + return ls; + } + if (d->flags & CG_LOCAL_ADDR_TAKEN) { + wfail(t, "wasm: address-taken parameter not yet implemented"); + } + s = slot_push(t); + s->type = d->type; + s->size = d->size; + s->align = d->align ? d->align : 1u; + s->kind = W_SLOT_LOCAL; + s->wasm_local = wli; + ls.kind = CG_LOCAL_STORAGE_FRAME; + ls.v.frame_slot = (FrameSlot)t->nslots; + return ls; +} + +/* Allocate a frame slot backed by a fresh wasm local. */ +static FrameSlot alloc_frame_slot(WTarget* t, CfreeCgTypeId ty) { + return alloc_frame_slot_kind(t, ty, 0, 0, 0); +} + +FrameSlot wasm_frame_slot(CGTarget* tg, const FrameSlotDesc* d) { + WTarget* t = (WTarget*)tg; + if (!d->type && !d->size) wfail(t, "wasm: frame slot without type/size"); + return alloc_frame_slot_kind(t, d->type, d->size, d->align, + (d->flags & FSF_ADDR_TAKEN) != 0 || + d->kind == FS_ALLOCA); +} + +CGLocalStorage wasm_local(CGTarget* tg, const CGLocalDesc* d) { + WTarget* t = (WTarget*)tg; + CGLocalStorage ls; + memset(&ls, 0, sizeof ls); + if (d->flags & (CG_LOCAL_ADDR_TAKEN | CG_LOCAL_MEMORY_REQUIRED)) { + ls.kind = CG_LOCAL_STORAGE_FRAME; + ls.v.frame_slot = + alloc_frame_slot_kind(t, d->type, d->size, d->align, 1); + return ls; + } + ls.kind = CG_LOCAL_STORAGE_FRAME; + ls.v.frame_slot = alloc_frame_slot(t, d->type); + return ls; +} + +/* ----------------------------------------------------------------- + * Data-movement records + * ----------------------------------------------------------------- */ + +void wasm_load_imm(CGTarget* tg, Operand dst, i64 imm) { + WTarget* t = (WTarget*)tg; + if (t->dead) return; + if (dst.kind != OPK_REG) wfail(t, "wasm: load_imm dst must be REG"); + WIR* w = wir_push(t); + w->op = WIR_LOAD_IMM; + w->dst = dst.v.reg; + w->imm = imm; + w->type = dst.type; + w->cls = dst.cls; +} + +void wasm_load_const(CGTarget* tg, Operand dst, ConstBytes cb) { + WTarget* t = (WTarget*)tg; + if (t->dead) return; + if (dst.kind != OPK_REG) wfail(t, "wasm: load_const dst must be REG"); + WasmValType vt = valtype_for_type(t, cb.type); + WIR* w = wir_push(t); + w->dst = dst.v.reg; + w->type = cb.type; + w->cls = dst.cls; + if (vt == WASM_VAL_F32) { + if (cb.size != 4) wfail(t, "wasm: f32 const must be 4 bytes"); + float f; + memcpy(&f, cb.bytes, 4); + w->op = WIR_LOAD_CONST_F; + w->fp_imm = (double)f; + } else if (vt == WASM_VAL_F64) { + if (cb.size != 8) wfail(t, "wasm: f64 const must be 8 bytes"); + double v; + memcpy(&v, cb.bytes, 8); + w->op = WIR_LOAD_CONST_F; + w->fp_imm = v; + } else { + i64 v = 0; + memcpy(&v, cb.bytes, cb.size < 8 ? cb.size : 8u); + /* Sign-extend for small signed types so the immediate has the expected + * bit pattern. */ + if (cb.size == 1) v = (i64)(i8)v; + else if (cb.size == 2) v = (i64)(i16)v; + else if (cb.size == 4) v = (i64)(i32)v; + w->op = WIR_LOAD_IMM; + w->imm = v; + } +} + +void wasm_copy(CGTarget* tg, Operand dst, Operand src) { + WTarget* t = (WTarget*)tg; + if (t->dead) return; + if (dst.kind != OPK_REG || src.kind != OPK_REG) + wfail(t, "wasm: copy operands must both be REG"); + WIR* w = wir_push(t); + w->op = WIR_COPY; + w->dst = dst.v.reg; + w->a = src.v.reg; + w->type = dst.type; +} + +void wasm_binop(CGTarget* tg, BinOp op, Operand dst, Operand a, Operand b) { + WTarget* t = (WTarget*)tg; + if (t->dead) return; + if (dst.kind != OPK_REG) wfail(t, "wasm: binop dst must be REG"); + WIR* w = wir_push(t); + w->op = WIR_BINOP; + w->cgop = (u8)op; + w->dst = dst.v.reg; + wir_capture_operand(w, 0, a); + wir_capture_operand(w, 1, b); + w->type = dst.type; + w->cls = dst.cls; +} + +void wasm_unop(CGTarget* tg, UnOp op, Operand dst, Operand a) { + WTarget* t = (WTarget*)tg; + if (t->dead) return; + if (dst.kind != OPK_REG) wfail(t, "wasm: unop dst must be REG"); + WIR* w = wir_push(t); + w->op = WIR_UNOP; + w->cgop = (u8)op; + w->dst = dst.v.reg; + wir_capture_operand(w, 0, a); + w->type = dst.type; + w->cls = dst.cls; +} + +void wasm_cmp(CGTarget* tg, CmpOp op, Operand dst, Operand a, Operand b) { + WTarget* t = (WTarget*)tg; + if (t->dead) return; + if (dst.kind != OPK_REG) wfail(t, "wasm: cmp dst must be REG"); + WIR* w = wir_push(t); + w->op = WIR_CMP; + w->cgop = (u8)op; + w->dst = dst.v.reg; + wir_capture_operand(w, 0, a); + wir_capture_operand(w, 1, b); + w->type = dst.type; + w->type2 = a.type ? a.type : b.type; + w->cls = dst.cls; +} + +void wasm_convert(CGTarget* tg, ConvKind ck, Operand dst, Operand src) { + WTarget* t = (WTarget*)tg; + if (t->dead) return; + if (dst.kind != OPK_REG) wfail(t, "wasm: convert dst must be REG"); + WIR* w = wir_push(t); + w->op = WIR_CONVERT; + w->cgop = (u8)ck; + w->dst = dst.v.reg; + wir_capture_operand(w, 0, src); + w->type = dst.type; + w->type2 = src.type; + w->cls = dst.cls; +} + +/* Build (or reuse) the wasm typeidx for a function-typed indirect call. The + * signature shape must exactly match a direct call to the same C type: + * - hidden i32 sret pointer prepended when ABI has_sret + * - per-param: i32 pointer for ABI_ARG_INDIRECT, else the DIRECT scalar + * produced by the wasm32 BasicCABI classifier (IGNORE params dropped) + * - result: empty when has_sret, else the DIRECT scalar + * + * call_indirect's runtime type check compares this typeidx against the + * funcref's recorded type, so any mismatch with the direct-call path would + * trap. The temporary WasmFunc is stack-allocated; wasm_intern_func_type + * copies the param array on insertion. */ +/* Translate an ABI function signature into the wasm-level param/result list. + * Used both for indirect-call signature interning and for import-function + * type synthesis. `what` names the call site in diagnostics. Returns the + * interned type index. The caller-provided buffer `params` (length `cap`) is + * filled from index 0; *nparams_out is the count written. */ +static u32 abi_to_wasm_func_type(WTarget* t, const ABIFuncInfo* abi, + WasmValType* params, u32 cap, u32* nparams_out, + WasmValType* result_out, u32* nresults_out, + const char* what) { + WasmFunc tmp; + memset(&tmp, 0, sizeof tmp); + tmp.params = params; + tmp.cap_params = cap; + if (abi->has_sret) { + if (tmp.nparams >= cap) wfail(t, "wasm: %s has too many params", what); + params[tmp.nparams++] = WASM_VAL_I32; + } + for (u32 i = 0; i < abi->nparams; ++i) { + const ABIArgInfo* ai = &abi->params[i]; + if (ai->kind == ABI_ARG_IGNORE) continue; + if (tmp.nparams >= cap) wfail(t, "wasm: %s has too many params", what); + if (ai->kind == ABI_ARG_INDIRECT) { + params[tmp.nparams++] = WASM_VAL_I32; + } else { + if (ai->nparts != 1) + wfail(t, "wasm: %s has multi-part DIRECT param (unsupported)", what); + const ABIArgPart* p = &ai->parts[0]; + params[tmp.nparams++] = valtype_for_size_kind( + t, p->size, p->cls == ABI_CLASS_FP ? ABI_SC_FLOAT : ABI_SC_INT); + } + } + /* Variadic functions take one hidden trailing i32 va_ptr — the address of + * the caller-packed varargs buffer in linear memory. See wasm_call's + * variadic packing and wasm_va_start in this file. */ + if (abi->variadic) { + if (tmp.nparams >= cap) wfail(t, "wasm: %s has too many params", what); + params[tmp.nparams++] = WASM_VAL_I32; + } + tmp.nresults = 0; + if (!abi->has_sret && abi->ret.kind == ABI_ARG_DIRECT && + abi->ret.nparts == 1) { + const ABIArgPart* p = &abi->ret.parts[0]; + tmp.results[0] = valtype_for_size_kind( + t, p->size, p->cls == ABI_CLASS_FP ? ABI_SC_FLOAT : ABI_SC_INT); + tmp.nresults = 1; + } + if (nparams_out) *nparams_out = tmp.nparams; + if (result_out && tmp.nresults) *result_out = tmp.results[0]; + if (nresults_out) *nresults_out = tmp.nresults; + return wasm_intern_func_type(t->c, t->module, &tmp); +} + +static u32 intern_indirect_signature(WTarget* t, const ABIFuncInfo* abi) { + WasmValType params[64]; + return abi_to_wasm_func_type(t, abi, params, 64u, NULL, NULL, NULL, + "indirect call"); +} + +/* Promote `f` (already allocated for `sym` via sym_to_wasm_func) into a wasm + * `(import "<module>" "<field>" (func ...))` entry. The signature is + * synthesized from the supplied ABIFuncInfo, mirroring the layout the + * caller-side WIR_CALL pushes onto the stack: hidden i32 sret-pointer when + * has_sret, followed by lowered params, with a single i32/i64/f32/f64 + * result for direct, non-sret returns. Module/field default to "env" / the + * symbol's name; either may be overridden by + * `__attribute__((import_module/import_name))`. */ +static void promote_import_func(WTarget* t, ObjSymId sym, WasmFunc* f, + const ABIFuncInfo* abi) { + Heap* h = t->c->ctx->heap; + const ObjSym* os; + const char* sym_name; + size_t sym_name_len = 0; + Sym attr_module = 0; + Sym attr_name = 0; + const char* mod_str = "env"; + size_t mod_len = sizeof("env") - 1u; + if (!t->module) return; + if (f->is_import) return; + os = obj_symbol_get(t->obj, sym); + if (!os) return; + if (os->section_id != OBJ_SEC_NONE) return; /* already defined locally */ + if (os->kind != SK_UNDEF && os->kind != SK_FUNC) return; + if (!abi) + wfail(t, "wasm: cannot synthesize import signature for '%s' " + "(missing ABI info)", + pool_sym_cstr(t->c->global, os->name, NULL)); + if (f->ninsns != 0) + wfail(t, "wasm: cannot promote function with emitted body to import"); + /* Synthesize the wasm type from the ABI. Diagnoses unsupported shapes + * (varargs => multi-part DIRECT or extra parts; by-value aggregates that + * the ABI didn't already lower to ABI_ARG_INDIRECT) by naming the import + * symbol so the error points at the C declaration. */ + WasmValType params[64]; + u32 nparams = 0; + WasmValType result_vt = 0; + u32 nresults = 0; + char what[160]; + sym_name = pool_sym_cstr(t->c->global, os->name, &sym_name_len); + if (!sym_name) sym_name = "(anonymous)"; + /* Snprintf-free: build a short context string by hand to avoid pulling in + * stdio. The buffer is large enough for any plausible C identifier. */ + { + const char* prefix = "import '"; + const char* suffix = "'"; + size_t plen = 8u; /* strlen(prefix) */ + size_t slen = 1u; /* strlen(suffix) */ + size_t nlen = sym_name_len ? sym_name_len : strlen(sym_name); + if (nlen > sizeof(what) - plen - slen - 1u) + nlen = sizeof(what) - plen - slen - 1u; + memcpy(what, prefix, plen); + memcpy(what + plen, sym_name, nlen); + memcpy(what + plen + nlen, suffix, slen); + what[plen + nlen + slen] = 0; + } + f->typeidx = abi_to_wasm_func_type(t, abi, params, 64u, &nparams, &result_vt, + &nresults, what); + f->has_typeidx = 1; + /* Mirror the synthesized params/results onto the WasmFunc so the import + * encoder writes the matching signature. */ + wasm_func_set_params(t->c, t->module, f, params, nparams); + f->nresults = nresults; + if (nresults) f->results[0] = result_vt; + /* Resolve module/name overrides set via __attribute__((import_module/ + * import_name)) on the C declaration. */ + (void)wasm_imports_get(t->obj, os->name, &attr_module, &attr_name); + if (attr_module) mod_str = pool_sym_cstr(t->c->global, attr_module, &mod_len); + const char* name_str = attr_name ? pool_sym_cstr(t->c->global, attr_name, + &sym_name_len) + : sym_name; + size_t name_len = attr_name ? sym_name_len + : (sym_name_len ? sym_name_len + : strlen(name_str)); + f->is_import = 1; + { + char* m = (char*)h->alloc(h, mod_len + 1u, 1); + if (!m) wfail(t, "wasm: out of memory"); + memcpy(m, mod_str, mod_len); + m[mod_len] = 0; + f->import_module = m; + } + { + char* n = (char*)h->alloc(h, name_len + 1u, 1); + if (!n) wfail(t, "wasm: out of memory"); + memcpy(n, name_str, name_len); + n[name_len] = 0; + f->import_name = n; + } +} + +const char* wasm_tail_call_unrealizable_reason(CGTarget* tg, + const CGCallDesc* d) { + (void)tg; + /* Variadic tail calls are not realizable on wasm: varargs are packed into a + * buffer carved from this function's linear-memory frame, which return_call + * tears down before the callee reads it. sret is realizable — the tail + * forwards the function's own incoming sret pointer (see wasm_call). wasm + * function parameters are wasm locals, so there is no caller stack-arg area + * to overflow. */ + if (d->abi && d->abi->variadic) + return "wasm cannot tail-call a variadic function (its vararg buffer " + "lives in the frame a sibling call tears down)"; + return NULL; +} + +void wasm_call(CGTarget* tg, const CGCallDesc* d) { + WTarget* t = (WTarget*)tg; + if (t->dead) return; + int is_indirect = (d->callee.kind != OPK_GLOBAL); + if (is_indirect && d->callee.kind != OPK_REG) + wfail(t, "wasm: indirect call callee must be a register (got opkind %u)", + (unsigned)d->callee.kind); + Heap* h = t->c->ctx->heap; + int callee_has_sret = (d->abi && d->abi->has_sret) ? 1 : 0; + int callee_variadic = (d->abi && d->abi->variadic) ? 1 : 0; + int is_tail = (d->flags & CG_CALL_TAIL) ? 1 : 0; + if (is_tail) { + /* Realizability is decided by CG via wasm_tail_call_unrealizable_reason + * before CG_CALL_TAIL is set: variadic tails are rejected there, and sret + * tails forward the incoming sret pointer (handled in the WIR emit). */ + ensure_module(t); + t->module->features |= WASM_FEATURE_TAIL_CALLS; + } + u32 nfixed = (u32)d->nargs; + u32 nvar = 0u; + if (callee_variadic) { + if (d->nargs < d->abi->nparams) + wfail(t, + "wasm: variadic call has fewer args (%u) than fixed params (%u)", + (unsigned)d->nargs, (unsigned)d->abi->nparams); + nfixed = d->abi->nparams; + nvar = (u32)d->nargs - nfixed; + } + WIR* w = wir_push(t); + if (is_indirect) { + if (!d->abi) + wfail(t, "wasm: indirect call without ABIFuncInfo"); + ensure_module(t); + w->op = WIR_CALL_INDIRECT; + w->a = d->callee.v.reg; + w->imm = (i64)intern_indirect_signature(t, d->abi); + } else { + w->op = WIR_CALL; + w->call_sym = d->callee.v.global.sym; + /* Direct calls into externally-defined functions become wasm imports. + * Synthesize the import signature now while the ABI is available — the + * WIR emit loop only has the symbol index. The C frontend mints SK_FUNC + * for `extern foo(...)` declarations; the "undefined" signal is + * `section_id == OBJ_SEC_NONE`. SK_UNDEF can appear when a symbol's + * kind hasn't been pinned down yet. */ + { + const ObjSym* os = obj_symbol_get(t->obj, d->callee.v.global.sym); + if (os && os->section_id == OBJ_SEC_NONE && + (os->kind == SK_UNDEF || os->kind == SK_FUNC)) { + WasmFunc* f; + ensure_module(t); + (void)sym_to_wasm_func(t, d->callee.v.global.sym, &f); + if (!f->is_import) promote_import_func(t, d->callee.v.global.sym, f, + d->abi); + } + } + } + w->call_narg = nfixed; + w->type = d->ret.type; + w->call_has_sret = (u8)callee_has_sret; + w->call_variadic = (u8)callee_variadic; + w->call_tail = (u8)is_tail; + w->call_nvar = nvar; + if (callee_variadic) ensure_linear_memory(t); + if (callee_has_sret) { + /* Caller allocated a frame slot for the aggregate result via + * api_alloc_call_ret_storage; pass its address as the prepended i32. */ + w->call_sret_addr = d->ret.storage; + ensure_linear_memory(t); + } + if (nfixed) { + w->call_args = (Reg*)h->alloc(h, sizeof(Reg) * nfixed, _Alignof(Reg)); + w->call_arg_imms = + (i64*)h->alloc(h, sizeof(i64) * nfixed, _Alignof(i64)); + w->call_arg_kinds = (u8*)h->alloc(h, nfixed, 1); + w->call_arg_types = (CfreeCgTypeId*)h->alloc( + h, sizeof(CfreeCgTypeId) * nfixed, _Alignof(CfreeCgTypeId)); + w->call_arg_addrs = (Operand*)h->alloc( + h, sizeof(Operand) * nfixed, _Alignof(Operand)); + memset(w->call_arg_addrs, 0, sizeof(Operand) * nfixed); + for (u32 i = 0; i < nfixed; ++i) { + const CGABIValue* av = &d->args[i]; + w->call_arg_types[i] = av->type; + int is_indirect = (av->abi && av->abi->kind == ABI_ARG_INDIRECT); + if (is_indirect) { + if (av->storage.kind != OPK_LOCAL && + av->storage.kind != OPK_INDIRECT && + av->storage.kind != OPK_GLOBAL) { + wfail(t, + "wasm: byval call arg %u storage kind %u must be an address", + i, (unsigned)av->storage.kind); + } + w->call_arg_kinds[i] = WOP_ADDR; + w->call_args[i] = REG_NONE; + w->call_arg_imms[i] = 0; + w->call_arg_addrs[i] = av->storage; + ensure_linear_memory(t); + } else if (av->storage.kind == OPK_REG) { + w->call_arg_kinds[i] = 0; + w->call_args[i] = av->storage.v.reg; + w->call_arg_imms[i] = 0; + } else if (av->storage.kind == OPK_IMM) { + w->call_arg_kinds[i] = 1; + w->call_args[i] = REG_NONE; + w->call_arg_imms[i] = av->storage.v.imm; + } else { + wfail(t, + "wasm: call arg %u has unsupported operand kind %u (only " + "REG/IMM scalar args are supported in v1)", + i, (unsigned)av->storage.kind); + } + } + } + if (nvar) { + w->call_var_regs = (Reg*)h->alloc(h, sizeof(Reg) * nvar, _Alignof(Reg)); + w->call_var_imms = + (i64*)h->alloc(h, sizeof(i64) * nvar, _Alignof(i64)); + w->call_var_kinds = (u8*)h->alloc(h, nvar, 1); + w->call_var_types = (CfreeCgTypeId*)h->alloc( + h, sizeof(CfreeCgTypeId) * nvar, _Alignof(CfreeCgTypeId)); + for (u32 i = 0; i < nvar; ++i) { + const CGABIValue* av = &d->args[nfixed + i]; + const CgType* aty = av->type ? cg_type_get(t->c, av->type) : NULL; + w->call_var_types[i] = av->type; + if (aty && + (aty->kind == CFREE_CG_TYPE_RECORD || + aty->kind == CFREE_CG_TYPE_ARRAY)) { + wfail(t, + "wasm target: aggregate variadic arg %u not yet supported", + i); + } + if (av->storage.kind == OPK_REG) { + w->call_var_kinds[i] = WOP_REG; + w->call_var_regs[i] = av->storage.v.reg; + w->call_var_imms[i] = 0; + } else if (av->storage.kind == OPK_IMM) { + w->call_var_kinds[i] = WOP_IMM; + w->call_var_regs[i] = REG_NONE; + w->call_var_imms[i] = av->storage.v.imm; + } else { + wfail(t, + "wasm target: variadic arg %u has unsupported operand kind %u " + "(only REG/IMM scalar args supported in v1)", + i, (unsigned)av->storage.kind); + } + } + } + if (callee_has_sret) { + /* The call has no wasm result; the buffer pointed to by the sret arg + * holds the aggregate. */ + w->dst = REG_NONE; + } else if (d->ret.storage.kind == OPK_REG && d->ret.storage.v.reg != REG_NONE) { + w->dst = d->ret.storage.v.reg; + } else { + w->dst = REG_NONE; + } +} + +void wasm_ret(CGTarget* tg, const CGABIValue* v) { + WTarget* t = (WTarget*)tg; + if (t->dead) return; + WIR* w = wir_push(t); + w->op = WIR_RET; + if (t->cur_has_sret && v && v->abi && v->abi->kind == ABI_ARG_INDIRECT) { + /* Aggregate sret return: emit a memcpy from av.storage to the buffer + * pointed to by the hidden sret parameter, then a void return. */ + w->addr = v->storage; + w->type = v->type; + w->agg.size = + v->size ? v->size : (u32)abi_cg_sizeof(t->c->abi, v->type); + w->agg.align = 1u; + w->cgop = 1; /* tag: sret copy */ + w->dst = REG_NONE; + } else if (v && v->storage.kind == OPK_REG && v->storage.v.reg != REG_NONE) { + w->dst = v->storage.v.reg; + w->type = v->type; + } else if (v && v->storage.kind == OPK_IMM) { + w->imm_kind = 1; + w->imm_a = v->storage.v.imm; + w->type = v->type; + w->dst = REG_NONE; + } else { + w->dst = REG_NONE; + } + t->dead = 1; +} + +void wasm_load(CGTarget* tg, Operand dst, Operand addr, MemAccess mem) { + WTarget* t = (WTarget*)tg; + if (t->dead) return; + if (dst.kind != OPK_REG) wfail(t, "wasm: load dst must be REG"); + if (addr.kind != OPK_LOCAL || + slot_for(t, addr.v.frame_slot)->kind == W_SLOT_STACK) + ensure_linear_memory(t); + WIR* w = wir_push(t); + w->op = (addr.kind == OPK_LOCAL && slot_for(t, addr.v.frame_slot)->kind == W_SLOT_LOCAL) + ? WIR_LOAD_LOCAL + : WIR_LOAD_MEM; + w->dst = dst.v.reg; + w->addr = addr; + if (addr.kind == OPK_LOCAL) { + WSlot* s = slot_for(t, addr.v.frame_slot); + w->imm = (w->op == WIR_LOAD_LOCAL) ? (i64)s->wasm_local + : (i64)addr.v.frame_slot; + } + w->mem = mem; + w->type = dst.type; + w->cls = dst.cls; +} + +void wasm_store(CGTarget* tg, Operand addr, Operand src, MemAccess mem) { + WTarget* t = (WTarget*)tg; + if (t->dead) return; + if (addr.kind != OPK_LOCAL || + slot_for(t, addr.v.frame_slot)->kind == W_SLOT_STACK) + ensure_linear_memory(t); + WIR* w = wir_push(t); + w->op = (addr.kind == OPK_LOCAL && slot_for(t, addr.v.frame_slot)->kind == W_SLOT_LOCAL) + ? WIR_STORE_LOCAL + : WIR_STORE_MEM; + w->addr = addr; + if (addr.kind == OPK_LOCAL) { + WSlot* s = slot_for(t, addr.v.frame_slot); + w->imm = (w->op == WIR_STORE_LOCAL) ? (i64)s->wasm_local + : (i64)addr.v.frame_slot; + } + wir_capture_operand(w, 0, src); + w->mem = mem; + w->type = addr.type ? addr.type : src.type; +} + +/* Variadic CG hooks. va_list on wasm32 is a single i32 pointer into a + * caller-packed buffer of 8-byte slots (see wasm_call's variadic packing). + * va_start writes the hidden va_ptr param into *ap; va_arg loads T from + * *ap and advances *ap by 8; va_end is a no-op; va_copy copies the i32. */ +void wasm_va_start(CGTarget* tg, Operand ap_addr) { + WTarget* t = (WTarget*)tg; + if (t->dead) return; + if (!t->cur_is_variadic || t->va_ptr_param_local == 0xffffffffu) + wfail(t, "wasm: va_start in non-variadic function"); + ensure_linear_memory(t); + WIR* w = wir_push(t); + w->op = WIR_VA_START; + w->addr = ap_addr; +} + +void wasm_va_arg(CGTarget* tg, Operand dst, Operand ap_addr, + CfreeCgTypeId type) { + WTarget* t = (WTarget*)tg; + if (t->dead) return; + if (dst.kind != OPK_REG) wfail(t, "wasm: va_arg dst must be REG"); + const CgType* aty = type ? cg_type_get(t->c, type) : NULL; + if (aty && (aty->kind == CFREE_CG_TYPE_RECORD || + aty->kind == CFREE_CG_TYPE_ARRAY)) { + wfail(t, "wasm target: va_arg of aggregate type not yet supported"); + } + ensure_linear_memory(t); + WIR* w = wir_push(t); + w->op = WIR_VA_ARG; + w->dst = dst.v.reg; + w->addr = ap_addr; + w->type = type; + w->cls = dst.cls; +} + +void wasm_va_end(CGTarget* tg, Operand ap_addr) { + WTarget* t = (WTarget*)tg; + (void)ap_addr; + if (t->dead) return; + /* No-op: nothing to release. */ +} + +void wasm_va_copy(CGTarget* tg, Operand dst_ap_addr, Operand src_ap_addr) { + WTarget* t = (WTarget*)tg; + if (t->dead) return; + ensure_linear_memory(t); + WIR* w = wir_push(t); + w->op = WIR_VA_COPY; + w->addr = dst_ap_addr; + w->call_sret_addr = src_ap_addr; /* reused slot — see WIR comment */ +} + +void wasm_addr_of(CGTarget* tg, Operand dst, Operand lv) { + WTarget* t = (WTarget*)tg; + if (t->dead) return; + if (dst.kind != OPK_REG) wfail(t, "wasm: addr_of dst must be REG"); + if (lv.kind == OPK_LOCAL) { + WSlot* s = slot_for(t, lv.v.frame_slot); + if (s->kind == W_SLOT_LOCAL) { + u32 old_local = s->wasm_local; + promote_slot_to_stack(t, s); + WIR* st = wir_push(t); + st->op = WIR_STORE_MEM; + st->addr = lv; + st->type = s->type; + st->mem.type = s->type; + st->mem.size = s->size; + st->mem.align = s->align; + st->imm_kind = WOP_WASM_LOCAL; + st->imm_a = old_local; + } + } else { + ensure_linear_memory(t); + } + WIR* w = wir_push(t); + w->op = WIR_ADDR_OF; + w->dst = dst.v.reg; + w->addr = lv; + w->type = dst.type; + w->cls = dst.cls; +} + +void wasm_alloca(CGTarget* tg, Operand dst, Operand size, u32 align) { + WTarget* t = (WTarget*)tg; + if (t->dead) return; + ensure_linear_memory(t); + if (dst.kind != OPK_REG) wfail(t, "wasm: alloca dst must be REG"); + ensure_stack_pointer(t); + t->has_stack_frame = 1; + WIR* w = wir_push(t); + w->op = WIR_ALLOCA; + w->dst = dst.v.reg; + wir_capture_operand(w, 0, size); + w->type = dst.type; + w->type2 = size.type; + w->cls = dst.cls; + w->imm = align ? align : 16u; +} + +void wasm_copy_bytes(CGTarget* tg, Operand dst, Operand src, AggregateAccess a) { + WTarget* t = (WTarget*)tg; + if (t->dead) return; + ensure_linear_memory(t); + WIR* w = wir_push(t); + w->op = WIR_COPY_BYTES; + w->addr = dst; + wir_capture_operand(w, 0, src); + w->agg = a; +} + +void wasm_set_bytes(CGTarget* tg, Operand dst, Operand byte, AggregateAccess a) { + WTarget* t = (WTarget*)tg; + if (t->dead) return; + WIR* w = wir_push(t); + w->op = WIR_SET_BYTES; + w->addr = dst; + wir_capture_operand(w, 0, byte); + w->agg = a; +} + +/* Atomic ops. CG forces `addr` to a REG and accepts reg-or-imm for value + * operands. Wasm only models seq_cst; the MemOrder argument is captured but + * not encoded — every emitted atomic op is sequentially consistent. The + * caller-provided MemAccess carries the type and natural alignment we need + * for the memarg width. */ +static void atomic_require_addr_reg(WTarget* t, Operand addr, + const char* what) { + if (addr.kind != OPK_REG) + wfail(t, "wasm: %s address must be in a register (got opkind %u)", what, + (unsigned)addr.kind); +} + +void wasm_atomic_load(CGTarget* tg, Operand dst, Operand addr, MemAccess mem, + MemOrder mo) { + WTarget* t = (WTarget*)tg; + (void)mo; + if (t->dead) return; + if (dst.kind != OPK_REG) wfail(t, "wasm: atomic_load dst must be REG"); + atomic_require_addr_reg(t, addr, "atomic_load"); + ensure_shared_memory(t); + WIR* w = wir_push(t); + w->op = WIR_ATOMIC_LOAD; + w->dst = dst.v.reg; + w->a = addr.v.reg; + w->mem = mem; + w->type = dst.type ? dst.type : mem.type; + w->cls = dst.cls; +} + +void wasm_atomic_store(CGTarget* tg, Operand addr, Operand src, MemAccess mem, + MemOrder mo) { + WTarget* t = (WTarget*)tg; + (void)mo; + if (t->dead) return; + atomic_require_addr_reg(t, addr, "atomic_store"); + if (src.kind != OPK_REG && src.kind != OPK_IMM) + wfail(t, "wasm: atomic_store value must be REG or IMM"); + ensure_shared_memory(t); + WIR* w = wir_push(t); + w->op = WIR_ATOMIC_STORE; + w->a = addr.v.reg; + wir_capture_operand(w, 1, src); + w->mem = mem; + w->type = mem.type ? mem.type : src.type; +} + +void wasm_atomic_rmw(CGTarget* tg, AtomicOp op, Operand dst, Operand addr, + Operand val, MemAccess mem, MemOrder mo) { + WTarget* t = (WTarget*)tg; + (void)mo; + if (t->dead) return; + if (dst.kind != OPK_REG) wfail(t, "wasm: atomic_rmw dst must be REG"); + atomic_require_addr_reg(t, addr, "atomic_rmw"); + if (val.kind != OPK_REG && val.kind != OPK_IMM) + wfail(t, "wasm: atomic_rmw value must be REG or IMM"); + if (op == AO_NAND) + wfail(t, "wasm target: atomic NAND has no native wasm-threads opcode"); + ensure_shared_memory(t); + WIR* w = wir_push(t); + w->op = WIR_ATOMIC_RMW; + w->cgop = (u8)op; + w->dst = dst.v.reg; + w->a = addr.v.reg; + wir_capture_operand(w, 1, val); + w->mem = mem; + w->type = dst.type ? dst.type : mem.type; + w->cls = dst.cls; +} + +void wasm_atomic_cas(CGTarget* tg, Operand prior, Operand ok, Operand addr, + Operand expected, Operand desired, MemAccess mem, + MemOrder success, MemOrder failure) { + WTarget* t = (WTarget*)tg; + (void)success; + (void)failure; + if (t->dead) return; + if (prior.kind != OPK_REG) wfail(t, "wasm: atomic_cas prior must be REG"); + if (ok.kind != OPK_REG) wfail(t, "wasm: atomic_cas ok must be REG"); + atomic_require_addr_reg(t, addr, "atomic_cas"); + if (expected.kind != OPK_REG && expected.kind != OPK_IMM) + wfail(t, "wasm: atomic_cas expected must be REG or IMM"); + if (desired.kind != OPK_REG && desired.kind != OPK_IMM) + wfail(t, "wasm: atomic_cas desired must be REG or IMM"); + ensure_shared_memory(t); + WIR* w = wir_push(t); + w->op = WIR_ATOMIC_CAS; + w->dst = prior.v.reg; + w->dst2 = ok.v.reg; + w->a = addr.v.reg; + wir_capture_operand(w, 1, expected); + /* Capture desired into op_c/imm_kind_c/imm_c (third operand slot). */ + if (desired.kind == OPK_REG) { + w->imm_kind_c = WOP_REG; + w->op_c = desired.v.reg; + } else { + w->imm_kind_c = WOP_IMM; + w->imm_c = desired.v.imm; + } + w->mem = mem; + w->type = prior.type ? prior.type : mem.type; + w->cls = prior.cls; + w->type2 = ok.type; +} + +void wasm_fence(CGTarget* tg, MemOrder mo) { + WTarget* t = (WTarget*)tg; + (void)mo; + if (t->dead) return; + /* Wasm atomic.fence does not require a memory to exist, but in practice it + * is meaningful only inside a module that has shared memory. We don't + * force-create memory here to avoid producing a bogus memory for fence-only + * modules. */ + WIR* w = wir_push(t); + w->op = WIR_FENCE; +} + +/* Forward decls: defined further down. */ +static WasmValType type_valtype(WTarget* t, CfreeCgTypeId ty); +void wasm_emit_unreachable(WTarget* t); + +/* Per-intrinsic-name diagnostic text. Used both by the recorder for + * SETJMP/LONGJMP (which we still reject) and for the fallback panic so + * users see "wasm target: __builtin_clz ..." instead of a numeric kind. */ +static const char* intrin_name(IntrinKind k) { + switch (k) { + case INTRIN_NONE: return "<none>"; + case INTRIN_POPCOUNT: return "__builtin_popcount"; + case INTRIN_CTZ: return "__builtin_ctz"; + case INTRIN_CLZ: return "__builtin_clz"; + case INTRIN_BSWAP16: return "__builtin_bswap16"; + case INTRIN_BSWAP32: return "__builtin_bswap32"; + case INTRIN_BSWAP64: return "__builtin_bswap64"; + case INTRIN_MEMCPY: return "memcpy"; + case INTRIN_MEMMOVE: return "memmove"; + case INTRIN_MEMSET: return "memset"; + case INTRIN_PREFETCH: return "__builtin_prefetch"; + case INTRIN_ASSUME_ALIGNED: return "__builtin_assume_aligned"; + case INTRIN_EXPECT: return "__builtin_expect"; + case INTRIN_UNREACHABLE: return "__builtin_unreachable"; + case INTRIN_TRAP: return "__builtin_trap"; + case INTRIN_SETJMP: return "setjmp"; + case INTRIN_LONGJMP: return "longjmp"; + case INTRIN_SADD_OVERFLOW: return "__builtin_sadd_overflow"; + case INTRIN_UADD_OVERFLOW: return "__builtin_uadd_overflow"; + case INTRIN_SSUB_OVERFLOW: return "__builtin_ssub_overflow"; + case INTRIN_USUB_OVERFLOW: return "__builtin_usub_overflow"; + case INTRIN_SMUL_OVERFLOW: return "__builtin_smul_overflow"; + case INTRIN_UMUL_OVERFLOW: return "__builtin_umul_overflow"; + } + return "<unknown>"; +} + +void wasm_intrinsic(CGTarget* tg, IntrinKind k, Operand* dst, u32 ndst, + const Operand* args, u32 nargs) { + WTarget* t = (WTarget*)tg; + if (t->dead) return; + + switch (k) { + case INTRIN_TRAP: + case INTRIN_UNREACHABLE: + wasm_emit_unreachable(t); + return; + + case INTRIN_PREFETCH: + /* No-op hint. */ + return; + + case INTRIN_EXPECT: + case INTRIN_ASSUME_ALIGNED: + /* Pass-through hint: result = first argument. CG always allocates a + * dst reg for these; copy arg[0] there so downstream uses see the + * expected value. CG keeps the first arg as OPK_IMM when it was a + * literal so the constant flows through unchanged. */ + if (ndst == 1 && nargs >= 1) { + if (args[0].kind == OPK_IMM) { + wasm_load_imm(tg, dst[0], args[0].v.imm); + } else { + wasm_copy(tg, dst[0], args[0]); + } + } + return; + + case INTRIN_MEMCPY: + case INTRIN_MEMMOVE: { + /* memcpy/memmove both lower to memory.copy, which is spec-defined to + * handle overlap correctly. CG forces (dst, src) to REG and passes + * size as OPK_IMM (cfree_cg_memmove). */ + if (nargs != 3 || args[0].kind != OPK_REG || args[1].kind != OPK_REG) { + compiler_panic(t->c, cur_loc(t), + "wasm target: %s requires register pointers", + intrin_name(k)); + return; + } + if (args[2].kind != OPK_IMM) { + compiler_panic(t->c, cur_loc(t), + "wasm target: %s with non-constant size is not yet " + "supported", intrin_name(k)); + return; + } + ensure_linear_memory(t); + AggregateAccess a; + memset(&a, 0, sizeof a); + a.size = (u32)args[2].v.imm; + a.align = 1; + WIR* w = wir_push(t); + w->op = WIR_COPY_BYTES; + w->addr = args[0]; + wir_capture_operand(w, 0, args[1]); + w->agg = a; + return; + } + + case INTRIN_MEMSET: { + if (nargs != 3 || args[0].kind != OPK_REG) { + compiler_panic(t->c, cur_loc(t), + "wasm target: memset requires a register dst pointer"); + return; + } + if (args[1].kind != OPK_IMM || args[2].kind != OPK_IMM) { + compiler_panic(t->c, cur_loc(t), + "wasm target: memset with non-constant byte/size is " + "not yet supported"); + return; + } + ensure_linear_memory(t); + AggregateAccess a; + memset(&a, 0, sizeof a); + a.size = (u32)args[2].v.imm; + a.align = 1; + WIR* w = wir_push(t); + w->op = WIR_SET_BYTES; + w->addr = args[0]; + wir_capture_operand(w, 0, args[1]); + w->agg = a; + return; + } + + case INTRIN_CLZ: + case INTRIN_CTZ: + case INTRIN_POPCOUNT: + case INTRIN_BSWAP16: + case INTRIN_BSWAP32: + case INTRIN_BSWAP64: { + if (ndst != 1 || nargs != 1 || dst[0].kind != OPK_REG || + args[0].kind != OPK_REG) { + compiler_panic(t->c, cur_loc(t), + "wasm target: %s requires single REG operand", + intrin_name(k)); + return; + } + WIR* w = wir_push(t); + w->op = WIR_INTRINSIC; + w->cgop = (u8)k; + w->dst = dst[0].v.reg; + w->a = args[0].v.reg; + w->type = dst[0].type; + w->cls = dst[0].cls; + return; + } + + case INTRIN_SADD_OVERFLOW: + case INTRIN_UADD_OVERFLOW: + case INTRIN_SSUB_OVERFLOW: + case INTRIN_USUB_OVERFLOW: + case INTRIN_SMUL_OVERFLOW: + case INTRIN_UMUL_OVERFLOW: { + if (ndst != 2 || nargs != 2 || dst[0].kind != OPK_REG || + dst[1].kind != OPK_REG) { + compiler_panic(t->c, cur_loc(t), + "wasm target: %s requires 2 args + 2 result regs", + intrin_name(k)); + return; + } + /* Reject i64 mul-overflow for now: wasm core has no widening 64x64 + * multiply, so the standard expansion would need partial-product + * synthesis. 32-bit (the common shape on wasm32) is supported. */ + WasmValType vt = type_valtype(t, dst[0].type); + if (vt == WASM_VAL_I64 && + (k == INTRIN_SMUL_OVERFLOW || k == INTRIN_UMUL_OVERFLOW)) { + compiler_panic(t->c, cur_loc(t), + "wasm target: 64-bit checked-overflow multiply is " + "not yet supported"); + return; + } + WIR* w = wir_push(t); + w->op = WIR_INTRINSIC; + w->cgop = (u8)k; + w->dst = dst[0].v.reg; + w->dst2 = dst[1].v.reg; + w->type = dst[0].type; + w->cls = dst[0].cls; + wir_capture_operand(w, 0, args[0]); + wir_capture_operand(w, 1, args[1]); + return; + } + + case INTRIN_SETJMP: + case INTRIN_LONGJMP: + compiler_panic(t->c, cur_loc(t), + "wasm target: %s is not yet supported (no exception/" + "stack-unwind runtime)", intrin_name(k)); + return; + + case INTRIN_NONE: + break; + } + compiler_panic(t->c, cur_loc(t), + "wasm target: intrinsic %s not yet implemented", + intrin_name(k)); +} + +/* Inline asm v1 — see doc/WASM.md "Inline asm" for the surface contract. + * + * Template syntax: WAT instruction sequence. Inputs pre-pushed to the value + * stack (via local.get on synthetic input locals); outputs popped from the + * stack into synthetic output locals after the body. The snippet's + * local.get/set/tee with index < nin refers to the i-th input. + * + * Constraints: "r" → wasm local; "i" → const-folded; "m" → i32 address. + * Numeric tie-back constraints ("0","1",...) reuse the referenced output's + * slot (cg/asm.c expands them into duplicate input entries at the end). + * + * Disallowed in v1: escaping br/br_if/br_table, return/return_call*, + * call_indirect, snippet-internal locals, output count > 1, register + * clobbers (only `memory` is accepted). */ +static WasmValType wasm_asm_operand_vt(WTarget* t, CfreeCgTypeId ty, + const char* what, SrcLoc loc) { + WasmValType vt; + if (!ty) wfail_at(t, loc, "wasm target: asm %s with no CG type", what); + vt = valtype_for_type(t, ty); + if (vt != WASM_VAL_I32 && vt != WASM_VAL_I64 && + vt != WASM_VAL_F32 && vt != WASM_VAL_F64) + wfail_at(t, loc, "wasm target: asm %s of non-scalar type not supported", + what); + return vt; +} + +void wasm_asm_block(CGTarget* tg, const char* tmpl, + const AsmConstraint* outs, u32 nout, Operand* out_ops, + const AsmConstraint* ins, u32 nin, const Operand* in_ops, + const Sym* clob, u32 nclob) { + WTarget* t = (WTarget*)tg; + Heap* h = t->c->ctx->heap; + Sym sym_memory; + WasmFunc scratch; + SrcLoc loc = cur_loc(t); + u32 depth; + u32 i; + + if (t->dead) return; + + /* Clobber policy: only `memory` is meaningful on wasm (effective no-op + * because cg/asm.c already spilled live SSA values). Reject named-register + * clobbers explicitly. */ + sym_memory = pool_intern_slice(t->c->global, SLICE_LIT("memory")); + for (i = 0; i < nclob; ++i) { + if (clob[i] != sym_memory) + wfail_at(t, loc, + "wasm target: asm register clobbers not yet supported"); + } + + /* Build a scratch WasmFunc with the synthetic signature. Layout is: + * params = input types (indices 0 .. nin-1) + * locals = output types (indices nin .. nin+nout-1) + * results = empty + * Snippets use local.get/set/tee N to read/write either side. The author + * is responsible for writing each output via local.set N (>= nin); empty + * snippets paired with `+r` / numeric tieback constraints get identity + * behavior because the input and output share a wasm local at lowering. */ + memset(&scratch, 0, sizeof scratch); + for (i = 0; i < nin; ++i) { + WasmValType vt = wasm_asm_operand_vt(t, ins[i].type, "input operand", loc); + /* Constraint-specific checks: "i" requires an immediate operand; "m" + * requires an indirect (i32 address). */ + if (ins[i].str && ins[i].str[0] == 'i' && + in_ops[i].kind != OPK_IMM) + wfail_at(t, loc, "wasm target: asm 'i' input must be an immediate"); + if (ins[i].str && ins[i].str[0] == 'm') { + if (in_ops[i].kind != OPK_INDIRECT) + wfail_at(t, loc, "wasm target: asm 'm' input must be indirect"); + vt = WASM_VAL_I32; + } + wasm_func_push_param(t->c, t->module, &scratch, vt); + } + for (i = 0; i < nout; ++i) { + WasmValType vt = + wasm_asm_operand_vt(t, outs[i].type, "output operand", loc); + wasm_func_push_local(t->c, t->module, &scratch, vt); + } + /* No declared result — outputs are read from locals at the end of the + * lowering, not popped from the value stack. */ + + /* Parse the template into scratch.insns. */ + wasm_parse_wat_body(t->c, t->module, &scratch, tmpl, strlen(tmpl), loc); + + /* Walk the parsed body to reject constructs that escape or aren't + * supported in v1. Track control depth so br/br_if/br_table with imm >= + * depth (i.e. would branch out of the snippet) are rejected. */ + depth = 0; + for (i = 0; i < scratch.ninsns; ++i) { + WasmInsn* in = &scratch.insns[i]; + switch (in->kind) { + case WASM_INSN_BLOCK: + case WASM_INSN_LOOP: + case WASM_INSN_IF: + depth++; + break; + case WASM_INSN_END: + if (depth) depth--; + break; + case WASM_INSN_BR: + case WASM_INSN_BR_IF: + if (in->imm < 0 || (u64)in->imm >= depth) + wfail_at(t, in->loc, + "wasm target: asm template branch escapes snippet"); + break; + case WASM_INSN_BR_TABLE: { + u32 k; + for (k = 0; k < in->ntargets; ++k) + if (in->targets[k] >= depth) + wfail_at(t, in->loc, + "wasm target: asm template br_table escapes snippet"); + break; + } + case WASM_INSN_RETURN: + case WASM_INSN_RETURN_CALL: + case WASM_INSN_RETURN_CALL_INDIRECT: + case WASM_INSN_RETURN_CALL_REF: + wfail_at(t, in->loc, + "wasm target: return/tail-call in asm template not " + "supported"); + break; + case WASM_INSN_LOCAL_GET: + case WASM_INSN_LOCAL_SET: + case WASM_INSN_LOCAL_TEE: + if (in->imm < 0 || (u64)in->imm >= (u64)(nin + nout)) + wfail_at(t, in->loc, + "wasm target: asm template references local beyond " + "declared operands (snippet-internal locals not " + "supported)"); + break; + default: + break; + } + } + + /* Validate the body against the synthetic signature. */ + wasm_validate_func(t->c, t->module, &scratch); + + { + /* Build the WIR_ASM_BLOCK payload. raw_insns is owned by the WIR; the + * other arrays are too. Sizes are zero-when-empty per the WIR teardown + * conventions. */ + WIR* w = wir_push(t); + w->op = WIR_ASM_BLOCK; + w->raw_ninsns = scratch.ninsns; + if (scratch.ninsns) { + w->raw_insns = + (WasmInsn*)h->alloc(h, sizeof(WasmInsn) * scratch.ninsns, + _Alignof(WasmInsn)); + if (!w->raw_insns) wfail(t, "wasm: out of memory"); + memcpy(w->raw_insns, scratch.insns, + sizeof(WasmInsn) * scratch.ninsns); + } + w->asm_nin = nin; + w->asm_nout = nout; + if (nin) { + w->asm_in_kinds = (u8*)h->alloc(h, nin, 1); + w->asm_in_imms = (i64*)h->alloc(h, sizeof(i64) * nin, _Alignof(i64)); + w->asm_in_regs = (Reg*)h->alloc(h, sizeof(Reg) * nin, _Alignof(Reg)); + w->asm_in_types = (CfreeCgTypeId*)h->alloc( + h, sizeof(CfreeCgTypeId) * nin, _Alignof(CfreeCgTypeId)); + w->asm_in_share_out = (i32*)h->alloc( + h, sizeof(i32) * nin, _Alignof(i32)); + if (!w->asm_in_kinds || !w->asm_in_imms || !w->asm_in_regs || + !w->asm_in_types || !w->asm_in_share_out) + wfail(t, "wasm: out of memory"); + for (i = 0; i < nin; ++i) w->asm_in_share_out[i] = -1; + for (i = 0; i < nin; ++i) { + Operand op = in_ops[i]; + /* Numeric tieback constraints ("0".."9") share the matching + * output's wasm local. cg/asm.c also rewrites +r inout outputs + * into duplicate inputs at the tail of ins[], using the same + * numeric encoding. */ + const char* s = ins[i].str; + if (s && s[0] >= '0' && s[0] <= '9' && s[1] == '\0') { + int idx = s[0] - '0'; + if ((u32)idx < nout) w->asm_in_share_out[i] = idx; + } + switch (op.kind) { + case OPK_REG: + w->asm_in_kinds[i] = WOP_REG; + w->asm_in_regs[i] = op.v.reg; + w->asm_in_imms[i] = 0; + w->asm_in_types[i] = op.type ? op.type : ins[i].type; + break; + case OPK_IMM: + w->asm_in_kinds[i] = WOP_IMM; + w->asm_in_regs[i] = REG_NONE; + w->asm_in_imms[i] = op.v.imm; + w->asm_in_types[i] = op.type ? op.type : ins[i].type; + break; + case OPK_INDIRECT: + /* For "m" constraint: the input local holds the i32 address + * `base + ofs` of the lvalue. We re-use asm_in_imms (unused + * for WOP_REG operands) to carry the displacement so the + * linearizer can splice in `i32.const ofs; i32.add` after + * pushing the base local. */ + w->asm_in_kinds[i] = WOP_REG; + w->asm_in_regs[i] = op.v.ind.base; + w->asm_in_imms[i] = (i64)op.v.ind.ofs; + w->asm_in_types[i] = builtin_id(CFREE_CG_BUILTIN_I32); + break; + default: + wfail_at(t, loc, + "wasm target: unsupported asm input operand kind"); + } + } + } + if (nout) { + w->asm_out_regs = (Reg*)h->alloc(h, sizeof(Reg) * nout, _Alignof(Reg)); + w->asm_out_types = (CfreeCgTypeId*)h->alloc( + h, sizeof(CfreeCgTypeId) * nout, _Alignof(CfreeCgTypeId)); + if (!w->asm_out_regs || !w->asm_out_types) + wfail(t, "wasm: out of memory"); + for (i = 0; i < nout; ++i) { + if (out_ops[i].kind != OPK_REG) + wfail_at(t, loc, "wasm target: asm output must be a register"); + w->asm_out_regs[i] = out_ops[i].v.reg; + w->asm_out_types[i] = + out_ops[i].type ? out_ops[i].type : outs[i].type; + } + } + } + + /* Free scratch func storage. The parsed insns have been copied into the + * WIR payload. */ + if (scratch.params) + h->free(h, scratch.params, sizeof(WasmValType) * scratch.cap_params); + if (scratch.locals) + h->free(h, scratch.locals, sizeof(WasmValType) * scratch.cap_locals); + if (scratch.insns) + h->free(h, scratch.insns, sizeof(WasmInsn) * scratch.cap_insns); +} + +void wasm_file_scope_asm(CGTarget* tg, const char* src, size_t len) { + WTarget* t = (WTarget*)tg; + (void)src; (void)len; + compiler_panic(t->c, cur_loc(t), + "wasm target: file-scope asm not yet supported"); +} + +void wasm_set_loc(CGTarget* tg, SrcLoc loc) { + WTarget* t = (WTarget*)tg; + /* No debug info in v1, but we stash the most recent loc so cur_loc / + * diagnostics attribute to the actual statement rather than the + * function-definition location. */ + t->cur_stmt_loc = loc; +} + +void wasm_emit_unreachable(WTarget* t) { + if (t->dead) return; + WIR* w = wir_push(t); + w->op = WIR_UNREACHABLE; + t->dead = 1; +} + +/* ----------------------------------------------------------------- + * WIR -> WasmFunc lowering + * ----------------------------------------------------------------- */ + +static void emit_insn(WTarget* t, WasmInsnKind k, i64 imm) { + wasm_func_add_insn(t->c, t->module, t->cur_func, k, imm); +} +static void emit_fp(WTarget* t, WasmInsnKind k, double v) { + wasm_func_add_fp_insn(t->c, t->module, t->cur_func, k, v); +} + +/* Push an operand onto the wasm value stack. */ +static void emit_push_operand_reg(WTarget* t, Reg r) { + if (r == REG_NONE) wfail(t, "wasm: push of REG_NONE"); + /* The reg must already have a local. */ + if (r >= t->reg_cap || t->reg_to_local[r] == 0xffffffffu) { + wfail(t, "wasm: reg used before being defined"); + } + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->reg_to_local[r]); +} + +static WasmValType type_valtype(WTarget* t, CfreeCgTypeId ty) { + return valtype_for_type(t, ty); +} + +static void emit_push_imm(WTarget* t, WasmValType vt, i64 imm) { + WasmInsnKind k = (vt == WASM_VAL_I64) ? WASM_INSN_I64_CONST + : WASM_INSN_I32_CONST; + emit_insn(t, k, imm); +} + +static u32 memarg_align_log2(u32 align, u32 width); +static WasmInsnKind load_kind_for(WTarget* t, CfreeCgTypeId ty, MemAccess ma); + +static void emit_push_operand(WTarget* t, u32 kind, i64 imm, Reg r, + CfreeCgTypeId ty) { + if (kind == WOP_IMM) { + WasmValType vt = type_valtype(t, ty); + if (vt == WASM_VAL_F32 || vt == WASM_VAL_F64) { + wfail(t, "wasm: float immediate operand not supported"); + } + emit_push_imm(t, vt, imm); + } else if (kind == WOP_LOCAL) { + FrameSlot fs = (FrameSlot)imm; + WSlot* s = slot_for(t, fs); + if (s->kind == W_SLOT_LOCAL) { + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)s->wasm_local); + } else { + MemAccess ma; + memset(&ma, 0, sizeof ma); + ma.type = ty; + ma.size = (u32)abi_cg_sizeof(t->c->abi, ty); + ma.align = s->align; + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->frame_base_local); + WasmInsnKind k = load_kind_for(t, ty, ma); + wasm_func_add_mem_insn(t->c, t->module, t->cur_func, k, + memarg_align_log2(ma.align, + wasm_mem_width((uint8_t)k)), + s->frame_offset, 0); + } + } else { + emit_push_operand_reg(t, r); + } +} + +static void emit_local_set(WTarget* t, Reg dst, CfreeCgTypeId ty, RegClass cls) { + u32 idx = reg_local(t, dst, ty, cls); + emit_insn(t, WASM_INSN_LOCAL_SET, (i64)idx); +} + +static u32 memarg_align_log2(u32 align, u32 width) { + u32 a = align ? align : width; + u32 lg = 0; + if (a > width) a = width; + while (a > 1u) { + a >>= 1u; + lg++; + } + return lg; +} + +static WasmInsnKind load_kind_for(WTarget* t, CfreeCgTypeId ty, MemAccess ma) { + WasmValType vt = type_valtype(t, ty); + u32 size = ma.size ? ma.size : (u32)abi_cg_sizeof(t->c->abi, ty); + if (vt == WASM_VAL_F32) return WASM_INSN_F32_LOAD; + if (vt == WASM_VAL_F64) return WASM_INSN_F64_LOAD; + if (vt == WASM_VAL_I64) { + if (size == 1) return WASM_INSN_I64_LOAD8_U; + if (size == 2) return WASM_INSN_I64_LOAD16_U; + if (size == 4) return WASM_INSN_I64_LOAD32_U; + return WASM_INSN_I64_LOAD; + } + if (size == 1) return WASM_INSN_I32_LOAD8_U; + if (size == 2) return WASM_INSN_I32_LOAD16_U; + return WASM_INSN_I32_LOAD; +} + +static WasmInsnKind store_kind_for(WTarget* t, CfreeCgTypeId ty, MemAccess ma) { + WasmValType vt = type_valtype(t, ty); + u32 size = ma.size ? ma.size : (u32)abi_cg_sizeof(t->c->abi, ty); + if (vt == WASM_VAL_F32) return WASM_INSN_F32_STORE; + if (vt == WASM_VAL_F64) return WASM_INSN_F64_STORE; + if (vt == WASM_VAL_I64) { + if (size == 1) return WASM_INSN_I64_STORE8; + if (size == 2) return WASM_INSN_I64_STORE16; + if (size == 4) return WASM_INSN_I64_STORE32; + return WASM_INSN_I64_STORE; + } + if (size == 1) return WASM_INSN_I32_STORE8; + if (size == 2) return WASM_INSN_I32_STORE16; + return WASM_INSN_I32_STORE; +} + +/* Atomic op selection. Wasm threads gives natural-width atomic load/store for + * i32/i64 (with 8/16/32 subword variants) but only full-width (i32/i64) RMW + * and cmpxchg in cfree's wasm core. Sub-word RMW/cmpxchg therefore diagnose + * rather than silently widening. */ +static WasmInsnKind atomic_load_kind_for(WTarget* t, CfreeCgTypeId ty, + MemAccess ma) { + WasmValType vt = type_valtype(t, ty); + u32 size = ma.size ? ma.size : (u32)abi_cg_sizeof(t->c->abi, ty); + if (vt == WASM_VAL_F32 || vt == WASM_VAL_F64) + wfail(t, "wasm target: atomic load of floating-point value is not " + "representable in wasm threads"); + if (vt == WASM_VAL_I64) { + if (size == 1) return WASM_INSN_I64_ATOMIC_LOAD8_U; + if (size == 2) return WASM_INSN_I64_ATOMIC_LOAD16_U; + if (size == 4) return WASM_INSN_I64_ATOMIC_LOAD32_U; + if (size == 8) return WASM_INSN_I64_ATOMIC_LOAD; + wfail(t, "wasm: atomic load i64 size %u not supported", size); + } + if (size == 1) return WASM_INSN_I32_ATOMIC_LOAD8_U; + if (size == 2) return WASM_INSN_I32_ATOMIC_LOAD16_U; + if (size == 4) return WASM_INSN_I32_ATOMIC_LOAD; + wfail(t, "wasm: atomic load i32 size %u not supported", size); +} + +static WasmInsnKind atomic_store_kind_for(WTarget* t, CfreeCgTypeId ty, + MemAccess ma) { + WasmValType vt = type_valtype(t, ty); + u32 size = ma.size ? ma.size : (u32)abi_cg_sizeof(t->c->abi, ty); + if (vt == WASM_VAL_F32 || vt == WASM_VAL_F64) + wfail(t, "wasm target: atomic store of floating-point value is not " + "representable in wasm threads"); + if (vt == WASM_VAL_I64) { + if (size == 1) return WASM_INSN_I64_ATOMIC_STORE8; + if (size == 2) return WASM_INSN_I64_ATOMIC_STORE16; + if (size == 4) return WASM_INSN_I64_ATOMIC_STORE32; + if (size == 8) return WASM_INSN_I64_ATOMIC_STORE; + wfail(t, "wasm: atomic store i64 size %u not supported", size); + } + if (size == 1) return WASM_INSN_I32_ATOMIC_STORE8; + if (size == 2) return WASM_INSN_I32_ATOMIC_STORE16; + if (size == 4) return WASM_INSN_I32_ATOMIC_STORE; + wfail(t, "wasm: atomic store i32 size %u not supported", size); +} + +static WasmInsnKind atomic_rmw_kind_for(WTarget* t, AtomicOp op, + CfreeCgTypeId ty, MemAccess ma) { + WasmValType vt = type_valtype(t, ty); + u32 size = ma.size ? ma.size : (u32)abi_cg_sizeof(t->c->abi, ty); + int is64 = (vt == WASM_VAL_I64); + if (vt == WASM_VAL_F32 || vt == WASM_VAL_F64) + wfail(t, "wasm target: atomic RMW on floating-point value is not " + "representable in wasm threads"); + if (!(size == 4 || size == 8) || (is64 && size != 8) || + (!is64 && size != 4)) { + wfail(t, "wasm target: atomic RMW size %u not yet supported (only " + "full-width i32 and i64 atomic RMW are wired)", size); + } + switch (op) { + case AO_ADD: + return is64 ? WASM_INSN_I64_ATOMIC_RMW_ADD : WASM_INSN_I32_ATOMIC_RMW_ADD; + case AO_SUB: + return is64 ? WASM_INSN_I64_ATOMIC_RMW_SUB : WASM_INSN_I32_ATOMIC_RMW_SUB; + case AO_AND: + return is64 ? WASM_INSN_I64_ATOMIC_RMW_AND : WASM_INSN_I32_ATOMIC_RMW_AND; + case AO_OR: + return is64 ? WASM_INSN_I64_ATOMIC_RMW_OR : WASM_INSN_I32_ATOMIC_RMW_OR; + case AO_XOR: + return is64 ? WASM_INSN_I64_ATOMIC_RMW_XOR : WASM_INSN_I32_ATOMIC_RMW_XOR; + case AO_XCHG: + return is64 ? WASM_INSN_I64_ATOMIC_RMW_XCHG : WASM_INSN_I32_ATOMIC_RMW_XCHG; + case AO_NAND: + wfail(t, "wasm target: atomic NAND has no native wasm-threads opcode"); + } + wfail(t, "wasm: unsupported atomic RMW op %d", (int)op); +} + +static WasmInsnKind atomic_cmpxchg_kind_for(WTarget* t, CfreeCgTypeId ty, + MemAccess ma) { + WasmValType vt = type_valtype(t, ty); + u32 size = ma.size ? ma.size : (u32)abi_cg_sizeof(t->c->abi, ty); + if (vt == WASM_VAL_F32 || vt == WASM_VAL_F64) + wfail(t, "wasm target: atomic cmpxchg on floating-point value is not " + "representable in wasm threads"); + if (vt == WASM_VAL_I64) { + if (size != 8) + wfail(t, "wasm target: atomic cmpxchg i64 size %u not yet supported", + size); + return WASM_INSN_I64_ATOMIC_RMW_CMPXCHG; + } + if (size != 4) + wfail(t, "wasm target: atomic cmpxchg i32 size %u not yet supported", size); + return WASM_INSN_I32_ATOMIC_RMW_CMPXCHG; +} + +/* Look up (or assign) `sym`'s slot in the funcref table. Returned index is + * the wasm table index (>= 1, with 0 reserved as the null/trap slot). */ +static u32 func_table_index_for(WTarget* t, ObjSymId sym) { + Heap* h = t->c->ctx->heap; + for (u32 i = 0; i < t->func_table_count; ++i) { + if (t->func_table[i] == sym) return i + 1u; + } + if (t->func_table_count == t->func_table_cap) { + u32 nc = t->func_table_cap ? t->func_table_cap * 2u : 8u; + void* p = h->realloc(h, t->func_table, + sizeof(ObjSymId) * t->func_table_cap, + sizeof(ObjSymId) * nc, _Alignof(ObjSymId)); + if (!p) wfail(t, "wasm: out of memory"); + t->func_table = (ObjSymId*)p; + t->func_table_cap = nc; + } + t->func_table[t->func_table_count] = sym; + t->has_func_table = 1; + return ++t->func_table_count; /* slot 0 reserved; first sym -> index 1 */ +} + +/* Defer function-pointer materialization to wasm_materialize_functable. + * Emits `i32.const 0` and queues a WFuncTableFixup keyed by the placeholder's + * (cur_func_idx, ninsns-1). */ +static void queue_func_table_fixup(WTarget* t, ObjSymId sym) { + Heap* h = t->c->ctx->heap; + if (!t->cur_func) wfail(t, "wasm: function address outside a function"); + /* Ensure the function gets a slot and force the WasmFunc shell to exist so + * the table's element segment can resolve its wasm-func index later. */ + (void)func_table_index_for(t, sym); + (void)sym_to_wasm_func(t, sym, NULL); + emit_insn(t, WASM_INSN_I32_CONST, 0); + if (t->func_table_fixups_count == t->func_table_fixups_cap) { + u32 nc = t->func_table_fixups_cap ? t->func_table_fixups_cap * 2u : 16u; + void* p = h->realloc(h, t->func_table_fixups, + sizeof(WFuncTableFixup) * t->func_table_fixups_cap, + sizeof(WFuncTableFixup) * nc, + _Alignof(WFuncTableFixup)); + if (!p) wfail(t, "wasm: out of memory"); + t->func_table_fixups = (WFuncTableFixup*)p; + t->func_table_fixups_cap = nc; + } + WFuncTableFixup* fx = + &t->func_table_fixups[t->func_table_fixups_count++]; + fx->wasm_func_idx = t->cur_func_idx; + fx->insn_idx = t->cur_func->ninsns - 1u; + fx->sym = sym; +} + +/* Defer symbol-address resolution to wasm_materialize_data. Emits an + * i32.const placeholder into the current function and queues a WSymFixup + * keyed by (cur_func_idx, ninsns-1). The compact section layout is only + * known once every section's final size is settled, which doesn't happen + * until finalize. */ +static void queue_symbol_addr_fixup(WTarget* t, ObjSymId sym, i64 addend) { + Heap* h = t->c->ctx->heap; + const ObjSym* os = obj_symbol_get(t->obj, sym); + /* Function-symbol addresses route through the funcref table, not linear + * memory. CG occasionally takes the address of an extern function before + * the function body is seen (forward declarations, indirect-call + * setup); the SK_FUNC kind is set by the frontend at sym creation. */ + if (os && os->kind == SK_FUNC) { + if (addend != 0) + wfail(t, "wasm: nonzero addend on function-pointer reference"); + queue_func_table_fixup(t, sym); + return; + } + if (!os) + wfail(t, "wasm target: address of unresolved symbol not yet implemented"); + if (os->section_id == OBJ_SEC_NONE && os->kind != SK_COMMON) + wfail(t, "wasm target: address of undefined symbol not yet implemented"); + /* SK_COMMON falls through: apply_sym_fixups allocates a BSS-style base for + * it in wasm_materialize_data and patches the i32.const at finalize. */ + if (addend < INT32_MIN || addend > INT32_MAX) + wfail(t, "wasm: symbol addend out of range"); + if (!t->cur_func) wfail(t, "wasm: symbol address outside a function"); + emit_insn(t, WASM_INSN_I32_CONST, 0); + if (t->sym_fixups_count == t->sym_fixups_cap) { + u32 nc = t->sym_fixups_cap ? t->sym_fixups_cap * 2u : 16u; + void* p = h->realloc(h, t->sym_fixups, sizeof(WSymFixup) * t->sym_fixups_cap, + sizeof(WSymFixup) * nc, _Alignof(WSymFixup)); + if (!p) wfail(t, "wasm: out of memory"); + t->sym_fixups = (WSymFixup*)p; + t->sym_fixups_cap = nc; + } + WSymFixup* fx = &t->sym_fixups[t->sym_fixups_count++]; + fx->wasm_func_idx = t->cur_func_idx; + fx->insn_idx = t->cur_func->ninsns - 1u; + fx->sym = sym; + fx->addend = addend; +} + +static void emit_addr_operand(WTarget* t, Operand addr, uint64_t* offset_out) { + *offset_out = 0; + if (addr.kind == OPK_LOCAL) { + WSlot* s = slot_for(t, addr.v.frame_slot); + if (s->kind != W_SLOT_STACK) + wfail(t, "wasm: address of non-addressable local"); + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->frame_base_local); + *offset_out = s->frame_offset; + return; + } + if (addr.kind == OPK_INDIRECT) { + emit_push_operand_reg(t, addr.v.ind.base); + if (addr.v.ind.index != REG_NONE) { + emit_push_operand_reg(t, addr.v.ind.index); + if (addr.v.ind.index < t->reg_cap && + type_valtype(t, t->reg_type[addr.v.ind.index]) == WASM_VAL_I64) { + emit_insn(t, WASM_INSN_I32_WRAP_I64, 0); + } + if (addr.v.ind.log2_scale != 0) { + emit_insn(t, WASM_INSN_I32_CONST, (i64)addr.v.ind.log2_scale); + emit_insn(t, WASM_INSN_I32_SHL, 0); + } + emit_insn(t, WASM_INSN_I32_ADD, 0); + } + if (addr.v.ind.ofs >= 0) { + *offset_out = (uint32_t)addr.v.ind.ofs; + } else { + emit_insn(t, WASM_INSN_I32_CONST, (i64)addr.v.ind.ofs); + emit_insn(t, WASM_INSN_I32_ADD, 0); + } + return; + } + if (addr.kind == OPK_GLOBAL) { + queue_symbol_addr_fixup(t, addr.v.global.sym, addr.v.global.addend); + return; + } + if (addr.kind == OPK_REG) { + /* An i32 address already materialized in a register: just push it. */ + emit_push_operand_reg(t, addr.v.reg); + return; + } + wfail(t, "wasm: unsupported address operand kind %u", (unsigned)addr.kind); +} + +/* Push a complete i32 address value (folding any positive offset into the base + * via i32.add). Used by bulk-memory ops (memory.copy / memory.fill) which take + * the address as a stack operand and carry no memarg offset. */ +static void emit_push_addr_value(WTarget* t, Operand addr) { + uint64_t off; + emit_addr_operand(t, addr, &off); + if (off != 0) { + emit_insn(t, WASM_INSN_I32_CONST, (i64)(uint32_t)off); + emit_insn(t, WASM_INSN_I32_ADD, 0); + } +} + +static void emit_load_addr(WTarget* t, Operand addr, CfreeCgTypeId ty, + MemAccess ma) { + uint64_t offset; + WasmInsnKind k = load_kind_for(t, ty, ma); + u32 width = wasm_mem_width((uint8_t)k); + emit_addr_operand(t, addr, &offset); + wasm_func_add_mem_insn(t->c, t->module, t->cur_func, k, + memarg_align_log2(ma.align, width), offset, 0); +} + +static void emit_store_addr(WTarget* t, Operand addr, CfreeCgTypeId ty, + Operand src, MemAccess ma, u32 src_kind, + i64 src_imm, Reg src_reg) { + uint64_t offset; + WasmInsnKind k = store_kind_for(t, ty, ma); + u32 width = wasm_mem_width((uint8_t)k); + emit_addr_operand(t, addr, &offset); + if (src_kind == WOP_IMM) { + emit_push_imm(t, type_valtype(t, ty), src_imm); + } else if (src_kind == WOP_WASM_LOCAL) { + emit_insn(t, WASM_INSN_LOCAL_GET, src_imm); + } else if (src.kind == OPK_IMM) { + emit_push_imm(t, type_valtype(t, ty), src.v.imm); + } else if (src_kind == WOP_LOCAL) { + emit_push_operand(t, WOP_LOCAL, src_imm, REG_NONE, ty); + } else { + emit_push_operand_reg(t, src_reg); + } + wasm_func_add_mem_insn(t->c, t->module, t->cur_func, k, + memarg_align_log2(ma.align, width), offset, 0); +} + +/* Map (BinOp, valtype) to wasm opcode. */ +static WasmInsnKind binop_kind(WTarget* t, BinOp op, WasmValType vt) { + switch (op) { + case BO_IADD: return vt == WASM_VAL_I64 ? WASM_INSN_I64_ADD : WASM_INSN_I32_ADD; + case BO_ISUB: return vt == WASM_VAL_I64 ? WASM_INSN_I64_SUB : WASM_INSN_I32_SUB; + case BO_IMUL: return vt == WASM_VAL_I64 ? WASM_INSN_I64_MUL : WASM_INSN_I32_MUL; + case BO_SDIV: return vt == WASM_VAL_I64 ? WASM_INSN_I64_DIV_S : WASM_INSN_I32_DIV_S; + case BO_UDIV: return vt == WASM_VAL_I64 ? WASM_INSN_I64_DIV_U : WASM_INSN_I32_DIV_U; + case BO_SREM: return vt == WASM_VAL_I64 ? WASM_INSN_I64_REM_S : WASM_INSN_I32_REM_S; + case BO_UREM: return vt == WASM_VAL_I64 ? WASM_INSN_I64_REM_U : WASM_INSN_I32_REM_U; + case BO_AND: return vt == WASM_VAL_I64 ? WASM_INSN_I64_AND : WASM_INSN_I32_AND; + case BO_OR: return vt == WASM_VAL_I64 ? WASM_INSN_I64_OR : WASM_INSN_I32_OR; + case BO_XOR: return vt == WASM_VAL_I64 ? WASM_INSN_I64_XOR : WASM_INSN_I32_XOR; + case BO_SHL: return vt == WASM_VAL_I64 ? WASM_INSN_I64_SHL : WASM_INSN_I32_SHL; + case BO_SHR_S:return vt == WASM_VAL_I64 ? WASM_INSN_I64_SHR_S : WASM_INSN_I32_SHR_S; + case BO_SHR_U:return vt == WASM_VAL_I64 ? WASM_INSN_I64_SHR_U : WASM_INSN_I32_SHR_U; + case BO_FADD: return vt == WASM_VAL_F64 ? WASM_INSN_F64_ADD : WASM_INSN_F32_ADD; + case BO_FSUB: return vt == WASM_VAL_F64 ? WASM_INSN_F64_SUB : WASM_INSN_F32_SUB; + case BO_FMUL: return vt == WASM_VAL_F64 ? WASM_INSN_F64_MUL : WASM_INSN_F32_MUL; + case BO_FDIV: return vt == WASM_VAL_F64 ? WASM_INSN_F64_DIV : WASM_INSN_F32_DIV; + } + wfail(t, "wasm: unsupported binop %d", (int)op); +} + +static WasmInsnKind cmp_kind(WTarget* t, CmpOp op, WasmValType vt) { + int is64 = (vt == WASM_VAL_I64); + switch (op) { + case CMP_EQ: return is64 ? WASM_INSN_I64_EQ : WASM_INSN_I32_EQ; + case CMP_NE: return is64 ? WASM_INSN_I64_NE : WASM_INSN_I32_NE; + case CMP_LT_S: return is64 ? WASM_INSN_I64_LT_S : WASM_INSN_I32_LT_S; + case CMP_LE_S: return is64 ? WASM_INSN_I64_LE_S : WASM_INSN_I32_LE_S; + case CMP_GT_S: return is64 ? WASM_INSN_I64_GT_S : WASM_INSN_I32_GT_S; + case CMP_GE_S: return is64 ? WASM_INSN_I64_GE_S : WASM_INSN_I32_GE_S; + case CMP_LT_U: return is64 ? WASM_INSN_I64_LT_U : WASM_INSN_I32_LT_U; + case CMP_LE_U: return is64 ? WASM_INSN_I64_LE_U : WASM_INSN_I32_LE_U; + case CMP_GT_U: return is64 ? WASM_INSN_I64_GT_U : WASM_INSN_I32_GT_U; + case CMP_GE_U: return is64 ? WASM_INSN_I64_GE_U : WASM_INSN_I32_GE_U; + case CMP_LT_F: return vt == WASM_VAL_F64 ? WASM_INSN_F64_LT : WASM_INSN_F32_LT; + case CMP_LE_F: return vt == WASM_VAL_F64 ? WASM_INSN_F64_LE : WASM_INSN_F32_LE; + case CMP_GT_F: return vt == WASM_VAL_F64 ? WASM_INSN_F64_GT : WASM_INSN_F32_GT; + case CMP_GE_F: return vt == WASM_VAL_F64 ? WASM_INSN_F64_GE : WASM_INSN_F32_GE; + } + wfail(t, "wasm: unsupported cmp %d", (int)op); +} + +static void emit_convert(WTarget* t, ConvKind ck, WasmValType src, + WasmValType dst) { + if (src == dst && (ck == CV_BITCAST || ck == CV_ZEXT || ck == CV_SEXT || + ck == CV_TRUNC)) { + /* No-op conversion. */ + return; + } + if (ck == CV_BITCAST) { + if (src == WASM_VAL_I32 && dst == WASM_VAL_F32) { + emit_insn(t, WASM_INSN_F32_REINTERPRET_I32, 0); return; + } + if (src == WASM_VAL_F32 && dst == WASM_VAL_I32) { + emit_insn(t, WASM_INSN_I32_REINTERPRET_F32, 0); return; + } + if (src == WASM_VAL_I64 && dst == WASM_VAL_F64) { + emit_insn(t, WASM_INSN_F64_REINTERPRET_I64, 0); return; + } + if (src == WASM_VAL_F64 && dst == WASM_VAL_I64) { + emit_insn(t, WASM_INSN_I64_REINTERPRET_F64, 0); return; + } + /* Width-changing ptr/int bitcasts: cfree_cg_ptr_to_int and + * cfree_cg_int_to_ptr route through CV_BITCAST, and on wasm32 a pointer + * is i32 while the frontend integer side may be i64. Lower as + * wrap/extend (zero-extend; pointers are non-negative addresses). */ + if (src == WASM_VAL_I64 && dst == WASM_VAL_I32) { + emit_insn(t, WASM_INSN_I32_WRAP_I64, 0); return; + } + if (src == WASM_VAL_I32 && dst == WASM_VAL_I64) { + emit_insn(t, WASM_INSN_I64_EXTEND_I32_U, 0); return; + } + wfail(t, "wasm: unsupported bitcast"); + } + if (ck == CV_SEXT) { + if (src == WASM_VAL_I32 && dst == WASM_VAL_I64) { + emit_insn(t, WASM_INSN_I64_EXTEND_I32_S, 0); return; + } + } + if (ck == CV_ZEXT) { + if (src == WASM_VAL_I32 && dst == WASM_VAL_I64) { + emit_insn(t, WASM_INSN_I64_EXTEND_I32_U, 0); return; + } + } + if (ck == CV_TRUNC) { + if (src == WASM_VAL_I64 && dst == WASM_VAL_I32) { + emit_insn(t, WASM_INSN_I32_WRAP_I64, 0); return; + } + } + if (ck == CV_FEXT && src == WASM_VAL_F32 && dst == WASM_VAL_F64) { + emit_insn(t, WASM_INSN_F64_PROMOTE_F32, 0); return; + } + if (ck == CV_FTRUNC && src == WASM_VAL_F64 && dst == WASM_VAL_F32) { + emit_insn(t, WASM_INSN_F32_DEMOTE_F64, 0); return; + } + if (ck == CV_ITOF_S) { + if (src == WASM_VAL_I32 && dst == WASM_VAL_F32) { emit_insn(t, WASM_INSN_F32_CONVERT_I32_S, 0); return; } + if (src == WASM_VAL_I32 && dst == WASM_VAL_F64) { emit_insn(t, WASM_INSN_F64_CONVERT_I32_S, 0); return; } + if (src == WASM_VAL_I64 && dst == WASM_VAL_F32) { emit_insn(t, WASM_INSN_F32_CONVERT_I64_S, 0); return; } + if (src == WASM_VAL_I64 && dst == WASM_VAL_F64) { emit_insn(t, WASM_INSN_F64_CONVERT_I64_S, 0); return; } + } + if (ck == CV_ITOF_U) { + if (src == WASM_VAL_I32 && dst == WASM_VAL_F32) { emit_insn(t, WASM_INSN_F32_CONVERT_I32_U, 0); return; } + if (src == WASM_VAL_I32 && dst == WASM_VAL_F64) { emit_insn(t, WASM_INSN_F64_CONVERT_I32_U, 0); return; } + if (src == WASM_VAL_I64 && dst == WASM_VAL_F32) { emit_insn(t, WASM_INSN_F32_CONVERT_I64_U, 0); return; } + if (src == WASM_VAL_I64 && dst == WASM_VAL_F64) { emit_insn(t, WASM_INSN_F64_CONVERT_I64_U, 0); return; } + } + if (ck == CV_FTOI_S) { + if (src == WASM_VAL_F32 && dst == WASM_VAL_I32) { emit_insn(t, WASM_INSN_I32_TRUNC_F32_S, 0); return; } + if (src == WASM_VAL_F64 && dst == WASM_VAL_I32) { emit_insn(t, WASM_INSN_I32_TRUNC_F64_S, 0); return; } + if (src == WASM_VAL_F32 && dst == WASM_VAL_I64) { emit_insn(t, WASM_INSN_I64_TRUNC_F32_S, 0); return; } + if (src == WASM_VAL_F64 && dst == WASM_VAL_I64) { emit_insn(t, WASM_INSN_I64_TRUNC_F64_S, 0); return; } + } + if (ck == CV_FTOI_U) { + if (src == WASM_VAL_F32 && dst == WASM_VAL_I32) { emit_insn(t, WASM_INSN_I32_TRUNC_F32_U, 0); return; } + if (src == WASM_VAL_F64 && dst == WASM_VAL_I32) { emit_insn(t, WASM_INSN_I32_TRUNC_F64_U, 0); return; } + if (src == WASM_VAL_F32 && dst == WASM_VAL_I64) { emit_insn(t, WASM_INSN_I64_TRUNC_F32_U, 0); return; } + if (src == WASM_VAL_F64 && dst == WASM_VAL_I64) { emit_insn(t, WASM_INSN_I64_TRUNC_F64_U, 0); return; } + } + wfail(t, "wasm: unsupported convert kind %d (%d -> %d)", (int)ck, + (int)src, (int)dst); +} + +/* During lowering we keep a running active-scope stack so we can compute + * br depths. */ +typedef struct LoweringScope { + u32 id; + u8 kind; + /* Depth at which break/continue targets are reached. */ + u32 break_depth; + u32 cont_depth; +} LoweringScope; + +typedef struct LoweringState { + WTarget* t; + /* Bounded by the deepest synthetic + CG scope nesting we'll emit. + * Switch islands wrap one block per case, so the limit is roughly + * (max cases + max user nesting). 1024 leaves room for very wide + * switches without forcing future per-case-count caps. */ + LoweringScope stack[1024]; + u32 nstack; + u32 cur_depth; +} LoweringState; + +static u32 br_to_label(LoweringState* L, Label l) { + WLabel* lbl = lookup_label(L->t, l); + if (!lbl) wfail(L->t, "wasm: br to unknown label"); + if (lbl->kind == WLBL_SCOPE_BREAK) { + for (u32 i = L->nstack; i > 0; --i) { + if (L->stack[i - 1u].id == lbl->scope_id) { + return L->cur_depth - L->stack[i - 1u].break_depth; + } + } + wfail(L->t, "wasm: br to break label of inactive scope"); + } + if (lbl->kind == WLBL_SCOPE_CONT) { + for (u32 i = L->nstack; i > 0; --i) { + if (L->stack[i - 1u].id == lbl->scope_id) { + return L->cur_depth - L->stack[i - 1u].cont_depth; + } + } + wfail(L->t, "wasm: br to continue label of inactive scope"); + } + /* wasm_structurize wraps every reachable forward label in a synthetic + * SCOPE_BLOCK (forward goto) or SCOPE_LOOP (backward goto), and + * unroll_switch_islands reorders the WIR so switch case labels are + * forward refs from WIR_SWITCH. Arriving here means the structurer + * missed a shape — a bug, not a feature gap. */ + wfail(L->t, "wasm: br to free label whose synthetic scope was not " + "emitted; structurer bug"); +} + +static i64 wasm_switch_sign_extend(u64 v, u32 width) { + if (width == 0u || width >= 64u) return (i64)v; + { + u64 bit = 1ull << (width - 1u); + u64 mask = (1ull << width) - 1u; + v &= mask; + return (i64)((v ^ bit) - bit); + } +} + +static int wasm_switch_extents(WTarget* t, const WIR* w, i64* out_vmin, + u64* out_span) { + u32 width; + i64 vmin = INT64_MAX; + i64 vmax = INT64_MIN; + if (w->switch_ncases == 0) return 0; + width = cfree_cg_type_int_width((CfreeCompiler*)t->c, w->type); + if (!width || width > 64u) return 0; + for (u32 i = 0; i < w->switch_ncases; ++i) { + i64 vi = wasm_switch_sign_extend(w->switch_cases[i].value, width); + if (vi < vmin) vmin = vi; + if (vi > vmax) vmax = vi; + } + if (vmax < vmin) return 0; + { + u64 delta = (u64)vmax - (u64)vmin; + if (delta == UINT64_MAX) return 0; + *out_span = delta + 1u; + } + *out_vmin = vmin; + return 1; +} + +static void emit_br_table(WTarget* t, const u32* targets, u32 ntargets) { + WasmInsn* in; + if (ntargets == 0 || ntargets > 64u) + wfail(t, "wasm: br_table target count out of range"); + wasm_func_add_insn(t->c, t->module, t->cur_func, WASM_INSN_BR_TABLE, 0); + in = &t->cur_func->insns[t->cur_func->ninsns - 1u]; + in->ntargets = ntargets; + memcpy(in->targets, targets, sizeof(targets[0]) * ntargets); +} + +static void emit_switch_br_table(WTarget* t, LoweringState* L, const WIR* w) { + i64 vmin; + u64 span; + u32 targets[64]; + Label labels[64]; + u32 ntargets; + WasmValType vt; + + if (w->switch_ncases == 0) { + emit_insn(t, WASM_INSN_BR, (i64)br_to_label(L, w->labels[0])); + return; + } + if (!wasm_switch_extents(t, w, &vmin, &span)) + wfail(t, "wasm: unsupported switch selector type"); + if (span + 1u > 64u) { + vt = type_valtype(t, w->type); + if (vt != WASM_VAL_I32 && vt != WASM_VAL_I64) + wfail(t, "wasm: switch selector must be integer"); + for (u32 i = 0; i < w->switch_ncases; ++i) { + u32 width = cfree_cg_type_int_width((CfreeCompiler*)t->c, w->type); + i64 vi = wasm_switch_sign_extend(w->switch_cases[i].value, width); + emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type); + emit_push_imm(t, vt, vi); + emit_insn(t, vt == WASM_VAL_I64 ? WASM_INSN_I64_EQ : WASM_INSN_I32_EQ, + 0); + emit_insn(t, WASM_INSN_BR_IF, + (i64)br_to_label(L, w->switch_cases[i].label)); + } + emit_insn(t, WASM_INSN_BR, (i64)br_to_label(L, w->labels[0])); + return; + } + + for (u64 i = 0; i < span; ++i) labels[i] = w->labels[0]; + for (u32 i = 0; i < w->switch_ncases; ++i) { + u32 width = cfree_cg_type_int_width((CfreeCompiler*)t->c, w->type); + i64 vi = wasm_switch_sign_extend(w->switch_cases[i].value, width); + u64 slot = (u64)(vi - vmin); + if (slot >= span) wfail(t, "wasm: switch case outside span"); + labels[slot] = w->switch_cases[i].label; + } + + emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type); + vt = type_valtype(t, w->type); + if (vt != WASM_VAL_I32 && vt != WASM_VAL_I64) + wfail(t, "wasm: switch selector must be integer"); + if (vmin != 0) { + emit_push_imm(t, vt, vmin); + emit_insn(t, vt == WASM_VAL_I64 ? WASM_INSN_I64_SUB : WASM_INSN_I32_SUB, + 0); + } + if (vt == WASM_VAL_I64) emit_insn(t, WASM_INSN_I32_WRAP_I64, 0); + + ntargets = (u32)span + 1u; + for (u32 i = 0; i < (u32)span; ++i) targets[i] = br_to_label(L, labels[i]); + targets[ntargets - 1u] = br_to_label(L, w->labels[0]); + emit_br_table(t, targets, ntargets); +} + +/* ----------------------------------------------------------------- + * Intrinsics (bit ops / bswap / overflow arith) + * + * MEMCPY/MEMMOVE/MEMSET don't appear here: the recorder funnels them + * into WIR_COPY_BYTES / WIR_SET_BYTES which already lower to + * memory.copy / memory.fill. Hints (PREFETCH/EXPECT/ASSUME_ALIGNED) + * also don't reach the linearizer — the recorder either drops them or + * emits a plain copy. ----------------------------------------------- */ + +static void emit_intrinsic_bit_op(WTarget* t, const WIR* w) { + WasmValType vt = type_valtype(t, w->type); + WasmInsnKind op; + switch ((IntrinKind)w->cgop) { + case INTRIN_CLZ: + op = (vt == WASM_VAL_I64) ? WASM_INSN_I64_CLZ : WASM_INSN_I32_CLZ; + break; + case INTRIN_CTZ: + op = (vt == WASM_VAL_I64) ? WASM_INSN_I64_CTZ : WASM_INSN_I32_CTZ; + break; + case INTRIN_POPCOUNT: + op = (vt == WASM_VAL_I64) ? WASM_INSN_I64_POPCNT : WASM_INSN_I32_POPCNT; + break; + default: + wfail(t, "wasm: unexpected bit-op intrinsic %d", (int)w->cgop); + return; + } + emit_push_operand_reg(t, w->a); + emit_insn(t, op, 0); + emit_local_set(t, w->dst, w->type, (RegClass)w->cls); +} + +static void emit_intrinsic_bswap(WTarget* t, const WIR* w) { + IntrinKind k = (IntrinKind)w->cgop; + if (k == INTRIN_BSWAP16 || k == INTRIN_BSWAP32) { + /* Both operate over i32. BSWAP16 only touches the low 16 bits; any + * extra high bits in the input are discarded by the AND mask. */ + u32 tmp = add_wasm_local(t, WASM_VAL_I32); + emit_push_operand_reg(t, w->a); + emit_insn(t, WASM_INSN_LOCAL_SET, (i64)tmp); + if (k == INTRIN_BSWAP16) { + /* (x & 0xff) << 8 */ + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)tmp); + emit_insn(t, WASM_INSN_I32_CONST, 0xff); + emit_insn(t, WASM_INSN_I32_AND, 0); + emit_insn(t, WASM_INSN_I32_CONST, 8); + emit_insn(t, WASM_INSN_I32_SHL, 0); + /* (x >> 8) & 0xff */ + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)tmp); + emit_insn(t, WASM_INSN_I32_CONST, 8); + emit_insn(t, WASM_INSN_I32_SHR_U, 0); + emit_insn(t, WASM_INSN_I32_CONST, 0xff); + emit_insn(t, WASM_INSN_I32_AND, 0); + emit_insn(t, WASM_INSN_I32_OR, 0); + } else { + /* Four-byte shuffle. */ + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)tmp); + emit_insn(t, WASM_INSN_I32_CONST, 24); + emit_insn(t, WASM_INSN_I32_SHR_U, 0); + emit_insn(t, WASM_INSN_I32_CONST, 0xff); + emit_insn(t, WASM_INSN_I32_AND, 0); + + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)tmp); + emit_insn(t, WASM_INSN_I32_CONST, 8); + emit_insn(t, WASM_INSN_I32_SHR_U, 0); + emit_insn(t, WASM_INSN_I32_CONST, 0xff00); + emit_insn(t, WASM_INSN_I32_AND, 0); + emit_insn(t, WASM_INSN_I32_OR, 0); + + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)tmp); + emit_insn(t, WASM_INSN_I32_CONST, 8); + emit_insn(t, WASM_INSN_I32_SHL, 0); + emit_insn(t, WASM_INSN_I32_CONST, 0xff0000); + emit_insn(t, WASM_INSN_I32_AND, 0); + emit_insn(t, WASM_INSN_I32_OR, 0); + + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)tmp); + emit_insn(t, WASM_INSN_I32_CONST, 24); + emit_insn(t, WASM_INSN_I32_SHL, 0); + emit_insn(t, WASM_INSN_I32_OR, 0); + } + emit_local_set(t, w->dst, w->type, (RegClass)w->cls); + return; + } + /* BSWAP64: byte reverse over i64. */ + u32 tmp = add_wasm_local(t, WASM_VAL_I64); + emit_push_operand_reg(t, w->a); + emit_insn(t, WASM_INSN_LOCAL_SET, (i64)tmp); + for (int i = 0; i < 8; ++i) { + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)tmp); + if (i > 0) { + emit_insn(t, WASM_INSN_I64_CONST, (i64)(i * 8)); + emit_insn(t, WASM_INSN_I64_SHR_U, 0); + } + emit_insn(t, WASM_INSN_I64_CONST, 0xff); + emit_insn(t, WASM_INSN_I64_AND, 0); + int shift = (7 - i) * 8; + if (shift > 0) { + emit_insn(t, WASM_INSN_I64_CONST, (i64)shift); + emit_insn(t, WASM_INSN_I64_SHL, 0); + } + if (i > 0) emit_insn(t, WASM_INSN_I64_OR, 0); + } + emit_local_set(t, w->dst, w->type, (RegClass)w->cls); +} + +static void emit_intrinsic_overflow(WTarget* t, const WIR* w) { + IntrinKind k = (IntrinKind)w->cgop; + WasmValType vt = type_valtype(t, w->type); + WasmInsnKind k_add = (vt == WASM_VAL_I64) ? WASM_INSN_I64_ADD : WASM_INSN_I32_ADD; + WasmInsnKind k_sub = (vt == WASM_VAL_I64) ? WASM_INSN_I64_SUB : WASM_INSN_I32_SUB; + WasmInsnKind k_and = (vt == WASM_VAL_I64) ? WASM_INSN_I64_AND : WASM_INSN_I32_AND; + WasmInsnKind k_xor = (vt == WASM_VAL_I64) ? WASM_INSN_I64_XOR : WASM_INSN_I32_XOR; + WasmInsnKind k_shr_u = (vt == WASM_VAL_I64) ? WASM_INSN_I64_SHR_U : WASM_INSN_I32_SHR_U; + WasmInsnKind k_lt_u = (vt == WASM_VAL_I64) ? WASM_INSN_I64_LT_U : WASM_INSN_I32_LT_U; + WasmInsnKind k_const = (vt == WASM_VAL_I64) ? WASM_INSN_I64_CONST : WASM_INSN_I32_CONST; + CfreeCgTypeId bool_ty = builtin_id(CFREE_CG_BUILTIN_BOOL); + + /* Stash both operands in scratch locals so each side of the expansion can + * re-load them without re-evaluating immediates or relying on the wasm + * value stack shape. */ + u32 a_loc = add_wasm_local(t, vt); + u32 b_loc = add_wasm_local(t, vt); + emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type); + emit_insn(t, WASM_INSN_LOCAL_SET, (i64)a_loc); + emit_push_operand(t, w->imm_kind_b, w->imm_b, w->b, w->type); + emit_insn(t, WASM_INSN_LOCAL_SET, (i64)b_loc); + + switch (k) { + case INTRIN_UADD_OVERFLOW: + /* r = a + b; ovf = (r <u a) */ + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc); + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc); + emit_insn(t, k_add, 0); + emit_local_set(t, w->dst, w->type, (RegClass)w->cls); + emit_push_operand_reg(t, w->dst); + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc); + emit_insn(t, k_lt_u, 0); + emit_local_set(t, w->dst2, bool_ty, RC_INT); + break; + case INTRIN_USUB_OVERFLOW: + /* r = a - b; ovf = (a <u b) */ + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc); + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc); + emit_insn(t, k_sub, 0); + emit_local_set(t, w->dst, w->type, (RegClass)w->cls); + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc); + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc); + emit_insn(t, k_lt_u, 0); + emit_local_set(t, w->dst2, bool_ty, RC_INT); + break; + case INTRIN_SADD_OVERFLOW: + /* r = a + b; ovf = ((r ^ a) & (r ^ b)) >>u (W-1) */ + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc); + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc); + emit_insn(t, k_add, 0); + emit_local_set(t, w->dst, w->type, (RegClass)w->cls); + emit_push_operand_reg(t, w->dst); + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc); + emit_insn(t, k_xor, 0); + emit_push_operand_reg(t, w->dst); + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc); + emit_insn(t, k_xor, 0); + emit_insn(t, k_and, 0); + emit_insn(t, k_const, (i64)(vt == WASM_VAL_I64 ? 63 : 31)); + emit_insn(t, k_shr_u, 0); + if (vt == WASM_VAL_I64) emit_insn(t, WASM_INSN_I32_WRAP_I64, 0); + emit_local_set(t, w->dst2, bool_ty, RC_INT); + break; + case INTRIN_SSUB_OVERFLOW: + /* r = a - b; ovf = ((a ^ b) & (a ^ r)) >>u (W-1) */ + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc); + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc); + emit_insn(t, k_sub, 0); + emit_local_set(t, w->dst, w->type, (RegClass)w->cls); + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc); + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc); + emit_insn(t, k_xor, 0); + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc); + emit_push_operand_reg(t, w->dst); + emit_insn(t, k_xor, 0); + emit_insn(t, k_and, 0); + emit_insn(t, k_const, (i64)(vt == WASM_VAL_I64 ? 63 : 31)); + emit_insn(t, k_shr_u, 0); + if (vt == WASM_VAL_I64) emit_insn(t, WASM_INSN_I32_WRAP_I64, 0); + emit_local_set(t, w->dst2, bool_ty, RC_INT); + break; + case INTRIN_UMUL_OVERFLOW: { + /* i32 only (i64 rejected in recorder). Widen to i64, multiply, + * low 32 = result, ovf = (wide >> 32) != 0. */ + u32 wide = add_wasm_local(t, WASM_VAL_I64); + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc); + emit_insn(t, WASM_INSN_I64_EXTEND_I32_U, 0); + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc); + emit_insn(t, WASM_INSN_I64_EXTEND_I32_U, 0); + emit_insn(t, WASM_INSN_I64_MUL, 0); + emit_insn(t, WASM_INSN_LOCAL_SET, (i64)wide); + + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)wide); + emit_insn(t, WASM_INSN_I32_WRAP_I64, 0); + emit_local_set(t, w->dst, w->type, (RegClass)w->cls); + + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)wide); + emit_insn(t, WASM_INSN_I64_CONST, 32); + emit_insn(t, WASM_INSN_I64_SHR_U, 0); + emit_insn(t, WASM_INSN_I64_CONST, 0); + emit_insn(t, WASM_INSN_I64_NE, 0); + emit_local_set(t, w->dst2, bool_ty, RC_INT); + break; + } + case INTRIN_SMUL_OVERFLOW: { + /* i32 only. Sign-extend, multiply, low 32 = result, ovf if + * sext(result) != wide product. */ + u32 wide = add_wasm_local(t, WASM_VAL_I64); + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc); + emit_insn(t, WASM_INSN_I64_EXTEND_I32_S, 0); + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc); + emit_insn(t, WASM_INSN_I64_EXTEND_I32_S, 0); + emit_insn(t, WASM_INSN_I64_MUL, 0); + emit_insn(t, WASM_INSN_LOCAL_SET, (i64)wide); + + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)wide); + emit_insn(t, WASM_INSN_I32_WRAP_I64, 0); + emit_local_set(t, w->dst, w->type, (RegClass)w->cls); + + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)wide); + emit_push_operand_reg(t, w->dst); + emit_insn(t, WASM_INSN_I64_EXTEND_I32_S, 0); + emit_insn(t, WASM_INSN_I64_NE, 0); + emit_local_set(t, w->dst2, bool_ty, RC_INT); + break; + } + default: + wfail(t, "wasm: overflow intrinsic dispatch reached default (%d)", + (int)k); + } +} + +static void emit_intrinsic(WTarget* t, const WIR* w) { + IntrinKind k = (IntrinKind)w->cgop; + switch (k) { + case INTRIN_CLZ: + case INTRIN_CTZ: + case INTRIN_POPCOUNT: + emit_intrinsic_bit_op(t, w); + return; + case INTRIN_BSWAP16: + case INTRIN_BSWAP32: + case INTRIN_BSWAP64: + emit_intrinsic_bswap(t, w); + return; + case INTRIN_SADD_OVERFLOW: + case INTRIN_UADD_OVERFLOW: + case INTRIN_SSUB_OVERFLOW: + case INTRIN_USUB_OVERFLOW: + case INTRIN_SMUL_OVERFLOW: + case INTRIN_UMUL_OVERFLOW: + emit_intrinsic_overflow(t, w); + return; + default: + wfail(t, "wasm: unexpected intrinsic kind %d in linearizer", (int)k); + } +} + +static void linearize_range(WTarget* t, LoweringState* L, u32 start, u32 end); + +#if 0 /* Switch-island matcher: replaced by wasm_structurize's + * unroll_switch_islands, which reorders the WIR in-place so case + * labels become forward refs handled by the general structurer. */ +static int label_in_list(Label l, const Label* labels, u32 nlabels) { + for (u32 i = 0; i < nlabels; ++i) { + if (labels[i] == l) return 1; + } + return 0; +} + +static int try_linearize_switch_island(WTarget* t, LoweringState* L, u32* ip) { + WIR* jump = &t->wir[*ip]; + WLabel* dispatch = lookup_label(t, jump->labels[0]); + u32 dispatch_i; + u32 switch_i = UINT32_MAX; + WIR* sw; + Label target_labels[64]; + Label body_labels[64]; + Label end_labels[64]; + u32 ntarget_labels = 0; + u32 nbody_labels = 0; + u32 nend_labels = 0; + u32 end_remap_mark; + u32 case_remap_mark; + Label synthetic_end = LABEL_NONE; + + if (!dispatch || dispatch->kind != WLBL_FORWARD || !dispatch->placed || + dispatch->wir_index <= *ip) + return 0; + dispatch_i = dispatch->wir_index; + if (dispatch_i >= t->nwir || t->wir[dispatch_i].op != WIR_LABEL) + return 0; + for (u32 i = dispatch_i + 1u; i < t->nwir; ++i) { + if (t->wir[i].op == WIR_SWITCH) { + switch_i = i; + break; + } + } + if (switch_i == UINT32_MAX) return 0; + + sw = &t->wir[switch_i]; + for (u32 i = 0; i < sw->switch_ncases; ++i) { + Label l = sw->switch_cases[i].label; + if (!label_in_list(l, target_labels, ntarget_labels)) { + if (ntarget_labels >= 64u) wfail(t, "wasm: too many switch targets"); + target_labels[ntarget_labels++] = l; + } + } + if (sw->labels[0] != LABEL_NONE && + !label_in_list(sw->labels[0], target_labels, ntarget_labels)) { + if (ntarget_labels >= 64u) wfail(t, "wasm: too many switch targets"); + target_labels[ntarget_labels++] = sw->labels[0]; + } + + for (u32 i = *ip + 1u; i < dispatch_i; ++i) { + if (t->wir[i].op != WIR_LABEL) continue; + Label l = t->wir[i].labels[0]; + if (label_in_list(l, target_labels, ntarget_labels) && + !label_in_list(l, body_labels, nbody_labels)) { + if (nbody_labels >= 64u) wfail(t, "wasm: too many switch body labels"); + body_labels[nbody_labels++] = l; + } + } + for (u32 i = *ip + 1u; i < dispatch_i; ++i) { + Label l = LABEL_NONE; + WLabel* lbl; + if (t->wir[i].op == WIR_JUMP || t->wir[i].op == WIR_CMP_BRANCH) { + l = t->wir[i].labels[0]; + } + if (l == LABEL_NONE || label_in_list(l, body_labels, nbody_labels)) + continue; + lbl = lookup_label(t, l); + if (!lbl || lbl->kind != WLBL_FORWARD || !lbl->placed || + lbl->wir_index <= switch_i) + continue; + if (!label_in_list(l, end_labels, nend_labels)) { + if (nend_labels >= 64u) wfail(t, "wasm: too many switch exit labels"); + end_labels[nend_labels++] = l; + } + } + if (nbody_labels == 0) return 0; + + if (!label_in_list(sw->labels[0], body_labels, nbody_labels)) + synthetic_end = sw->labels[0]; + + emit_insn(t, WASM_INSN_BLOCK, 0); + L->cur_depth++; + end_remap_mark = L->nremaps; + if (synthetic_end != LABEL_NONE) + lowering_push_remap(L, synthetic_end, L->cur_depth); + for (u32 i = 0; i < nend_labels; ++i) { + lowering_push_remap(L, end_labels[i], L->cur_depth); + } + + for (u32 ri = nbody_labels; ri > 0; --ri) { + Label l = body_labels[ri - 1u]; + emit_insn(t, WASM_INSN_BLOCK, 0); + L->cur_depth++; + lowering_push_remap(L, l, L->cur_depth); + } + + linearize_range(t, L, dispatch_i + 1u, switch_i); + case_remap_mark = end_remap_mark + nend_labels + + (synthetic_end != LABEL_NONE ? 1u : 0u); + emit_switch_br_table(t, L, sw); + lowering_pop_remaps(L, case_remap_mark); + + for (u32 bi = 0; bi < nbody_labels; ++bi) { + u32 seg_start; + u32 seg_end = dispatch_i; + WLabel* lbl = lookup_label(t, body_labels[bi]); + if (!lbl) wfail(t, "wasm: switch body label disappeared"); + emit_insn(t, WASM_INSN_END, 0); + L->cur_depth--; + seg_start = lbl->wir_index + 1u; + if (bi + 1u < nbody_labels) { + WLabel* next = lookup_label(t, body_labels[bi + 1u]); + if (!next) wfail(t, "wasm: switch body label disappeared"); + seg_end = next->wir_index; + } + linearize_range(t, L, seg_start, seg_end); + } + + emit_insn(t, WASM_INSN_END, 0); + L->cur_depth--; + lowering_pop_remaps(L, end_remap_mark); + *ip = switch_i; + return 1; +} +#endif + +static void linearize_range(WTarget* t, LoweringState* L, u32 start, u32 end) { + for (u32 i = start; i < end; ++i) { + WIR* w = &t->wir[i]; + switch (w->op) { + case WIR_LOAD_IMM: { + WasmValType vt = type_valtype(t, w->type); + emit_push_imm(t, vt, w->imm); + emit_local_set(t, w->dst, w->type, (RegClass)w->cls); + break; + } + case WIR_LOAD_CONST_F: { + WasmValType vt = type_valtype(t, w->type); + emit_fp(t, vt == WASM_VAL_F64 ? WASM_INSN_F64_CONST : WASM_INSN_F32_CONST, + w->fp_imm); + emit_local_set(t, w->dst, w->type, (RegClass)w->cls); + break; + } + case WIR_COPY: { + emit_push_operand_reg(t, w->a); + emit_local_set(t, w->dst, w->type, (RegClass)w->cls); + break; + } + case WIR_BINOP: { + emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type); + emit_push_operand(t, w->imm_kind_b, w->imm_b, w->b, w->type); + WasmValType vt = type_valtype(t, w->type); + emit_insn(t, binop_kind(t, (BinOp)w->cgop, vt), 0); + emit_local_set(t, w->dst, w->type, (RegClass)w->cls); + break; + } + case WIR_UNOP: { + WasmValType vt = type_valtype(t, w->type); + switch ((UnOp)w->cgop) { + case UO_NEG: { + /* 0 - a */ + emit_push_imm(t, vt, 0); + emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type); + emit_insn(t, vt == WASM_VAL_I64 ? WASM_INSN_I64_SUB : WASM_INSN_I32_SUB, 0); + break; + } + case UO_FNEG: { + wfail(t, "wasm: fneg via 0-x not supported; emit f32/f64.neg later"); + } + case UO_BNOT: { + /* a XOR -1 */ + emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type); + emit_push_imm(t, vt, -1); + emit_insn(t, vt == WASM_VAL_I64 ? WASM_INSN_I64_XOR : WASM_INSN_I32_XOR, 0); + break; + } + case UO_NOT: { + /* a == 0 — produces i32 0/1 */ + emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type); + emit_insn(t, vt == WASM_VAL_I64 ? WASM_INSN_I64_EQZ : WASM_INSN_I32_EQZ, 0); + break; + } + } + emit_local_set(t, w->dst, w->type, (RegClass)w->cls); + break; + } + case WIR_CMP: { + WasmValType vt_operand = type_valtype(t, w->type2); + emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type2); + emit_push_operand(t, w->imm_kind_b, w->imm_b, w->b, w->type2); + emit_insn(t, cmp_kind(t, (CmpOp)w->cgop, vt_operand), 0); + /* cmp result is i32 (0/1). dst type may be wider — but cg generally + * stores cmp results into i32. */ + emit_local_set(t, w->dst, w->type, (RegClass)w->cls); + break; + } + case WIR_CONVERT: { + WasmValType src = type_valtype(t, w->type2); + WasmValType dst = type_valtype(t, w->type); + emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type2); + emit_convert(t, (ConvKind)w->cgop, src, dst); + emit_local_set(t, w->dst, w->type, (RegClass)w->cls); + break; + } + case WIR_CALL: + case WIR_CALL_INDIRECT: { + /* Tail calls tear down the caller's wasm frame (return_call / + * return_call_indirect have polymorphic-unreachable type after the + * call). Mirror the WIR_RET linear-stack epilogue before pushing + * args so the linear-memory stack frame is released. Operands + * below come from wasm locals (incoming params or reg-locals), + * not from the linear stack we just freed. Variadic tail calls are + * rejected upstream; sret tail calls forward the incoming pointer. */ + if (w->call_tail && t->has_stack_frame) { + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->frame_saved_sp_local); + emit_insn(t, WASM_INSN_GLOBAL_SET, (i64)t->stack_pointer_global); + } + if (w->call_has_sret) { + if (w->call_tail) { + /* Forward this function's own incoming sret pointer: the callee + * writes the same buffer (in our caller's frame, which outlives + * the sibling call) and return_calls back. The pointer is a wasm + * local, unaffected by the linear-frame teardown above. */ + if (t->sret_param_local == 0xffffffffu) + wfail(t, "wasm: sret tail call without an incoming sret pointer"); + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->sret_param_local); + } else { + /* Push sret pointer (address of caller-allocated buffer). */ + uint64_t off; + emit_addr_operand(t, w->call_sret_addr, &off); + if (off) { + emit_insn(t, WASM_INSN_I32_CONST, (i64)off); + emit_insn(t, WASM_INSN_I32_ADD, 0); + } + } + } + for (u32 a = 0; a < w->call_narg; ++a) { + if (w->call_arg_kinds[a] == WOP_REG) { + emit_push_operand_reg(t, w->call_args[a]); + } else if (w->call_arg_kinds[a] == WOP_IMM) { + WasmValType vt = type_valtype(t, w->call_arg_types[a]); + emit_push_imm(t, vt, w->call_arg_imms[a]); + } else if (w->call_arg_kinds[a] == WOP_ADDR) { + uint64_t off; + emit_addr_operand(t, w->call_arg_addrs[a], &off); + if (off) { + emit_insn(t, WASM_INSN_I32_CONST, (i64)off); + emit_insn(t, WASM_INSN_I32_ADD, 0); + } + } else { + wfail(t, "wasm: bad call-arg kind %u", w->call_arg_kinds[a]); + } + } + /* Variadic packing. Each variadic arg occupies an 8-byte slot in a + * caller-allocated linear-memory buffer; the buffer's address is + * passed as the hidden trailing i32. We save __stack_pointer to a + * scratch local before allocating the buffer and restore it after + * the call returns, so a variadic call in a loop doesn't grow the + * linear stack. See wasm_va_start / wasm_va_arg for the callee side. + */ + if (w->call_variadic) { + if (w->call_nvar == 0u) { + /* No varargs: still pass a hidden i32. NULL is fine — the callee + * must not deref va_list without a matching @va_arg, which a + * well-typed program won't do. */ + emit_insn(t, WASM_INSN_I32_CONST, 0); + } else { + ensure_stack_pointer(t); + if (t->varcall_saved_sp_local == 0xffffffffu) + t->varcall_saved_sp_local = add_wasm_local(t, WASM_VAL_I32); + if (t->varcall_buf_local == 0xffffffffu) + t->varcall_buf_local = add_wasm_local(t, WASM_VAL_I32); + u32 buf_size = w->call_nvar * 8u; + /* Save SP, allocate aligned buffer, set SP = buf. */ + emit_insn(t, WASM_INSN_GLOBAL_GET, + (i64)t->stack_pointer_global); + emit_insn(t, WASM_INSN_LOCAL_TEE, + (i64)t->varcall_saved_sp_local); + emit_insn(t, WASM_INSN_I32_CONST, (i64)buf_size); + emit_insn(t, WASM_INSN_I32_SUB, 0); + emit_insn(t, WASM_INSN_I32_CONST, -(i64)8); + emit_insn(t, WASM_INSN_I32_AND, 0); + emit_insn(t, WASM_INSN_LOCAL_TEE, + (i64)t->varcall_buf_local); + emit_insn(t, WASM_INSN_GLOBAL_SET, + (i64)t->stack_pointer_global); + /* Pack each variadic arg at offset i*8. Store width is the + * value's natural width (i32/i64/f32/f64); the unused high + * bytes of i32/f32 slots are left as whatever __stack_pointer + * pointed at, which @va_arg won't read for those slots. */ + for (u32 v = 0; v < w->call_nvar; ++v) { + CfreeCgTypeId vty = w->call_var_types[v]; + WasmValType vvt = type_valtype(t, vty); + WasmInsnKind store_op; + u32 width; + if (vvt == WASM_VAL_I64) { + store_op = WASM_INSN_I64_STORE; width = 8u; + } else if (vvt == WASM_VAL_F32) { + store_op = WASM_INSN_F32_STORE; width = 4u; + } else if (vvt == WASM_VAL_F64) { + store_op = WASM_INSN_F64_STORE; width = 8u; + } else { + store_op = WASM_INSN_I32_STORE; width = 4u; + } + emit_insn(t, WASM_INSN_LOCAL_GET, + (i64)t->varcall_buf_local); + if (w->call_var_kinds[v] == WOP_REG) { + emit_push_operand_reg(t, w->call_var_regs[v]); + } else if (w->call_var_kinds[v] == WOP_IMM) { + if (vvt == WASM_VAL_F32 || vvt == WASM_VAL_F64) + wfail(t, "wasm: float immediate variadic arg unsupported"); + emit_push_imm(t, vvt, w->call_var_imms[v]); + } else { + wfail(t, "wasm: bad variadic-arg kind %u", + w->call_var_kinds[v]); + } + wasm_func_add_mem_insn(t->c, t->module, t->cur_func, store_op, + memarg_align_log2(width, width), + (u64)(v * 8u), 0u); + } + /* Push buf addr as hidden last arg. */ + emit_insn(t, WASM_INSN_LOCAL_GET, + (i64)t->varcall_buf_local); + } + } + if (w->op == WIR_CALL_INDIRECT) { + /* Callee: push the i32 table index. */ + emit_push_operand_reg(t, w->a); + /* call_indirect / return_call_indirect both encode + * (typeidx, tableidx). The encoder reads `imm` as typeidx and + * `align` as tableidx. */ + wasm_func_add_insn(t->c, t->module, t->cur_func, + w->call_tail ? WASM_INSN_RETURN_CALL_INDIRECT + : WASM_INSN_CALL_INDIRECT, + w->imm); + t->cur_func->insns[t->cur_func->ninsns - 1u].align = 0u; + } else { + u32 idx = sym_to_wasm_func(t, w->call_sym, NULL); + emit_insn(t, + w->call_tail ? WASM_INSN_RETURN_CALL : WASM_INSN_CALL, + (i64)idx); + } + /* Tail calls never return to this function: the operand stack is + * polymorphic-unreachable after return_call*, so writing dst or + * restoring the variadic stack pointer would be dead and would + * also corrupt stack typing. (Variadic tail calls are rejected + * upstream, so the variadic SP-restore guard is defensive.) */ + if (!w->call_tail) { + if (w->dst != REG_NONE) { + emit_local_set(t, w->dst, w->type, (RegClass)w->cls); + } + /* Restore SP after variadic call so loop-resident variadic calls + * don't accumulate stack usage. Done after stashing the return + * value into its local. */ + if (w->call_variadic && w->call_nvar) { + emit_insn(t, WASM_INSN_LOCAL_GET, + (i64)t->varcall_saved_sp_local); + emit_insn(t, WASM_INSN_GLOBAL_SET, + (i64)t->stack_pointer_global); + } + } + break; + } + case WIR_RET: { + if (w->cgop == 1) { + /* Aggregate sret return: memcpy w->addr -> *sret_param, then + * void return. The sret pointer was the hidden first wasm param. + * NOTE: this still uses a byte loop rather than memory.copy so + * the produced module remains loadable by the cfree runtime + * before the wasm-core default-feature change lands. The path + * will collapse to memory.copy once the core's default feature + * set includes WASM_FEATURE_BULK_MEMORY (subagent A). */ + if (t->sret_param_local == 0xffffffffu) + wfail(t, "wasm: sret return without hidden sret param"); + for (u32 n = 0; n < w->agg.size; ++n) { + /* Push destination address (sret_ptr) onto stack. The memarg + * offset on the i32.store8 carries the per-byte offset. */ + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->sret_param_local); + /* Load src byte at (w->addr + n). */ + Operand src = w->addr; + if (src.kind == OPK_INDIRECT) src.v.ind.ofs += (i32)n; + else if (src.kind == OPK_GLOBAL) src.v.global.addend += n; + uint64_t src_off; + emit_addr_operand(t, src, &src_off); + if (src.kind == OPK_LOCAL) src_off += n; + wasm_func_add_mem_insn(t->c, t->module, t->cur_func, + WASM_INSN_I32_LOAD8_U, 0, src_off, 0); + wasm_func_add_mem_insn(t->c, t->module, t->cur_func, + WASM_INSN_I32_STORE8, 0, n, 0); + } + } else if (w->dst != REG_NONE) emit_push_operand_reg(t, w->dst); + else if (w->imm_kind == 1) { + WasmValType vt = type_valtype(t, w->type); + emit_push_imm(t, vt, w->imm_a); + } + if (t->has_stack_frame) { + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->frame_saved_sp_local); + emit_insn(t, WASM_INSN_GLOBAL_SET, (i64)t->stack_pointer_global); + } + emit_insn(t, WASM_INSN_RETURN, 0); + break; + } + case WIR_UNREACHABLE: { + emit_insn(t, WASM_INSN_UNREACHABLE, 0); + break; + } + case WIR_LOAD_LOCAL: { + u32 wli = (u32)w->imm; + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)wli); + emit_local_set(t, w->dst, w->type, (RegClass)w->cls); + break; + } + case WIR_STORE_LOCAL: { + u32 wli = (u32)w->imm; + if (w->imm_kind == 1) { + WasmValType vt = type_valtype(t, w->type); + emit_push_imm(t, vt, w->imm_a); + } else { + emit_push_operand_reg(t, w->a); + } + emit_insn(t, WASM_INSN_LOCAL_SET, (i64)wli); + break; + } + case WIR_LOAD_MEM: { + emit_load_addr(t, w->addr, w->type, w->mem); + emit_local_set(t, w->dst, w->type, (RegClass)w->cls); + break; + } + case WIR_STORE_MEM: { + Operand src; + memset(&src, 0, sizeof src); + src.kind = w->imm_kind == WOP_IMM ? OPK_IMM : OPK_REG; + src.type = w->type; + if (src.kind == OPK_IMM) src.v.imm = w->imm_a; + else src.v.reg = w->a; + emit_store_addr(t, w->addr, w->type, src, w->mem, w->imm_kind, + w->imm_a, w->a); + break; + } + case WIR_ADDR_OF: { + uint64_t offset; + emit_addr_operand(t, w->addr, &offset); + if (offset) { + emit_insn(t, WASM_INSN_I32_CONST, (i64)offset); + emit_insn(t, WASM_INSN_I32_ADD, 0); + } + emit_local_set(t, w->dst, w->type, (RegClass)w->cls); + break; + } + case WIR_ALLOCA: { + u32 align = (u32)w->imm; + emit_insn(t, WASM_INSN_GLOBAL_GET, (i64)t->stack_pointer_global); + emit_push_operand(t, w->imm_kind, w->imm_a, w->a, + w->type2 ? w->type2 + : builtin_id(CFREE_CG_BUILTIN_I32)); + if (w->type2 && type_valtype(t, w->type2) == WASM_VAL_I64) + emit_insn(t, WASM_INSN_I32_WRAP_I64, 0); + emit_insn(t, WASM_INSN_I32_SUB, 0); + if (align > 1u) { + emit_insn(t, WASM_INSN_I32_CONST, -(i64)align); + emit_insn(t, WASM_INSN_I32_AND, 0); + } + emit_insn(t, WASM_INSN_LOCAL_TEE, + (i64)reg_local(t, w->dst, w->type, (RegClass)w->cls)); + emit_insn(t, WASM_INSN_GLOBAL_SET, (i64)t->stack_pointer_global); + break; + } + case WIR_COPY_BYTES: { + /* memory.copy: stack = dst_addr, src_addr, n; both memidx fields = 0. */ + Operand src_addr; + if (w->imm_kind != WOP_REG) + wfail(t, "wasm: copy_bytes source must be a register pointer"); + memset(&src_addr, 0, sizeof src_addr); + src_addr.kind = OPK_INDIRECT; + src_addr.type = w->addr.type; + src_addr.v.ind.base = w->a; + src_addr.v.ind.index = REG_NONE; + src_addr.v.ind.log2_scale = 0; + src_addr.v.ind.ofs = 0; + if (w->agg.size == 0) break; + emit_push_addr_value(t, w->addr); + emit_push_addr_value(t, src_addr); + emit_insn(t, WASM_INSN_I32_CONST, (i64)(uint32_t)w->agg.size); + wasm_func_add_insn(t->c, t->module, t->cur_func, + WASM_INSN_MEMORY_COPY, 0); + /* dst memidx = 0, src memidx = 0 (cfree-cc single-memory module). */ + t->cur_func->insns[t->cur_func->ninsns - 1u].memidx = 0; + t->cur_func->insns[t->cur_func->ninsns - 1u].aux_idx = 0; + break; + } + case WIR_SET_BYTES: { + /* memory.fill: stack = dst_addr, value_i32, n; memidx = 0. */ + if (w->imm_kind != WOP_IMM) + wfail(t, "wasm: set_bytes value must be immediate in v1"); + if (w->agg.size == 0) break; + emit_push_addr_value(t, w->addr); + emit_insn(t, WASM_INSN_I32_CONST, (i64)(w->imm_a & 0xff)); + emit_insn(t, WASM_INSN_I32_CONST, (i64)(uint32_t)w->agg.size); + wasm_func_add_insn(t->c, t->module, t->cur_func, + WASM_INSN_MEMORY_FILL, 0); + t->cur_func->insns[t->cur_func->ninsns - 1u].memidx = 0; + break; + } + case WIR_ATOMIC_LOAD: { + WasmInsnKind k = atomic_load_kind_for(t, w->type, w->mem); + u32 width = wasm_mem_width((uint8_t)k); + emit_push_operand_reg(t, w->a); + wasm_func_add_mem_insn(t->c, t->module, t->cur_func, k, + memarg_align_log2(w->mem.align, width), 0, 0); + emit_local_set(t, w->dst, w->type, (RegClass)w->cls); + break; + } + case WIR_ATOMIC_STORE: { + WasmInsnKind k = atomic_store_kind_for(t, w->type, w->mem); + u32 width = wasm_mem_width((uint8_t)k); + emit_push_operand_reg(t, w->a); + emit_push_operand(t, w->imm_kind_b, w->imm_b, w->b, w->type); + wasm_func_add_mem_insn(t->c, t->module, t->cur_func, k, + memarg_align_log2(w->mem.align, width), 0, 0); + break; + } + case WIR_ATOMIC_RMW: { + WasmInsnKind k = atomic_rmw_kind_for(t, (AtomicOp)w->cgop, w->type, + w->mem); + u32 width = wasm_mem_width((uint8_t)k); + emit_push_operand_reg(t, w->a); + emit_push_operand(t, w->imm_kind_b, w->imm_b, w->b, w->type); + wasm_func_add_mem_insn(t->c, t->module, t->cur_func, k, + memarg_align_log2(w->mem.align, width), 0, 0); + emit_local_set(t, w->dst, w->type, (RegClass)w->cls); + break; + } + case WIR_ATOMIC_CAS: { + WasmInsnKind k = atomic_cmpxchg_kind_for(t, w->type, w->mem); + WasmValType vt = type_valtype(t, w->type); + u32 width = wasm_mem_width((uint8_t)k); + /* Save expected into a fresh wasm local before consuming inputs. CG + * may reuse one of (addr, expected, desired) regs for prior or ok; + * reg_local() for w->dst/w->dst2 would then rebind that reg's local + * mid-stream, and re-pushing expected via the (now-stale) mapping + * would read an uninitialized local. The temp sidesteps that. */ + u32 saved_expected = add_wasm_local(t, vt); + /* push addr; expected (tee into saved-expected, leaves on stack); + * desired; cmpxchg -> prior on stack. */ + emit_push_operand_reg(t, w->a); + emit_push_operand(t, w->imm_kind_b, w->imm_b, w->b, w->type); + emit_insn(t, WASM_INSN_LOCAL_TEE, (i64)saved_expected); + emit_push_operand(t, w->imm_kind_c, w->imm_c, w->op_c, w->type); + wasm_func_add_mem_insn(t->c, t->module, t->cur_func, k, + memarg_align_log2(w->mem.align, width), 0, 0); + /* All input regs have been consumed; safe to rebind. */ + u32 prior_local = reg_local(t, w->dst, w->type, (RegClass)w->cls); + emit_insn(t, WASM_INSN_LOCAL_TEE, (i64)prior_local); + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)saved_expected); + emit_insn(t, vt == WASM_VAL_I64 ? WASM_INSN_I64_EQ : WASM_INSN_I32_EQ, + 0); + emit_local_set(t, w->dst2, + w->type2 ? w->type2 : builtin_id(CFREE_CG_BUILTIN_BOOL), + RC_INT); + break; + } + case WIR_FENCE: { + emit_insn(t, WASM_INSN_ATOMIC_FENCE, 0); + break; + } + case WIR_JUMP: { + u32 d = br_to_label(L, w->labels[0]); + emit_insn(t, WASM_INSN_BR, (i64)d); + break; + } + case WIR_CMP_BRANCH: { + WasmValType vt = type_valtype(t, w->type); + emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type); + emit_push_operand(t, w->imm_kind_b, w->imm_b, w->b, w->type); + emit_insn(t, cmp_kind(t, (CmpOp)w->cgop, vt), 0); + u32 d = br_to_label(L, w->labels[0]); + emit_insn(t, WASM_INSN_BR_IF, (i64)d); + break; + } + case WIR_SWITCH: { + emit_switch_br_table(t, L, w); + break; + } + case WIR_SCOPE_OPEN: { + if (L->nstack >= 1024u) wfail(t, "wasm: scope nesting too deep (max 1024)"); + LoweringScope* s = &L->stack[L->nstack++]; + s->id = w->scope_id; + s->kind = w->cgop; + if (w->cgop == SCOPE_LOOP) { + /* (block (loop ...)); inside the body: + * br to loop top (cur_depth+1) = continue + * br to past block (cur_depth) = break (one more level out) */ + emit_insn(t, WASM_INSN_BLOCK, 0); + L->cur_depth++; + s->break_depth = L->cur_depth; /* `br N` lands AFTER block */ + emit_insn(t, WASM_INSN_LOOP, 0); + L->cur_depth++; + s->cont_depth = L->cur_depth; /* `br N` lands at LOOP top */ + } else if (w->cgop == SCOPE_BLOCK) { + emit_insn(t, WASM_INSN_BLOCK, 0); + L->cur_depth++; + s->break_depth = L->cur_depth; + s->cont_depth = L->cur_depth; /* unused */ + } else if (w->cgop == SCOPE_IF) { + /* For SCOPE_IF the cond should already be on the value stack; CG + * passes it via desc.cond, which we recorded as w->dst (cond_reg) + * or via the preceding WIR_LOAD_IMM. */ + if (w->dst != REG_NONE) { + emit_push_operand_reg(t, w->dst); + } + /* Implicit i32 cond from a prior WIR_LOAD_IMM is already on + * the wasm value stack via the LOAD_IMM emission above — but + * actually LOAD_IMM does emit_local_set, so the value isn't on + * the stack. This path isn't used today; diagnose if we hit + * it. */ + if (w->dst == REG_NONE) { + wfail(t, "wasm: SCOPE_IF cond materialization not implemented"); + } + emit_insn(t, WASM_INSN_IF, 0); + L->cur_depth++; + s->break_depth = L->cur_depth; + s->cont_depth = L->cur_depth; + } else { + wfail(t, "wasm: unknown scope kind %d", (int)w->cgop); + } + break; + } + case WIR_SCOPE_ELSE: { + if (L->nstack == 0) wfail(t, "wasm: scope_else without open scope"); + emit_insn(t, WASM_INSN_ELSE, 0); + break; + } + case WIR_SCOPE_CLOSE: { + if (L->nstack == 0) wfail(t, "wasm: scope_close without open scope"); + LoweringScope* s = &L->stack[L->nstack - 1u]; + if (s->kind == SCOPE_LOOP) { + emit_insn(t, WASM_INSN_END, 0); /* close loop */ + L->cur_depth--; + emit_insn(t, WASM_INSN_END, 0); /* close outer block */ + L->cur_depth--; + } else { + emit_insn(t, WASM_INSN_END, 0); + L->cur_depth--; + } + L->nstack--; + break; + } + case WIR_VA_START: { + /* *ap_addr = va_ptr_param_local */ + emit_push_addr_value(t, w->addr); + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->va_ptr_param_local); + wasm_func_add_mem_insn(t->c, t->module, t->cur_func, + WASM_INSN_I32_STORE, 2u, 0u, 0u); + break; + } + case WIR_VA_ARG: { + if (t->va_arg_tmp_addr_local == 0xffffffffu) + t->va_arg_tmp_addr_local = add_wasm_local(t, WASM_VAL_I32); + WasmValType vt = type_valtype(t, w->type); + WasmInsnKind load_op; + u32 width; + if (vt == WASM_VAL_I64) { + load_op = WASM_INSN_I64_LOAD; width = 8u; + } else if (vt == WASM_VAL_F32) { + load_op = WASM_INSN_F32_LOAD; width = 4u; + } else if (vt == WASM_VAL_F64) { + load_op = WASM_INSN_F64_LOAD; width = 8u; + } else { + load_op = WASM_INSN_I32_LOAD; width = 4u; + } + /* Load T from current *ap and stash into dst. */ + emit_push_addr_value(t, w->addr); + emit_insn(t, WASM_INSN_LOCAL_TEE, (i64)t->va_arg_tmp_addr_local); + wasm_func_add_mem_insn(t->c, t->module, t->cur_func, + WASM_INSN_I32_LOAD, 2u, 0u, 0u); + wasm_func_add_mem_insn(t->c, t->module, t->cur_func, load_op, + memarg_align_log2(width, width), 0u, 0u); + emit_local_set(t, w->dst, w->type, (RegClass)w->cls); + /* Advance: *ap = *ap + 8. */ + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->va_arg_tmp_addr_local); + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->va_arg_tmp_addr_local); + wasm_func_add_mem_insn(t->c, t->module, t->cur_func, + WASM_INSN_I32_LOAD, 2u, 0u, 0u); + emit_insn(t, WASM_INSN_I32_CONST, (i64)8); + emit_insn(t, WASM_INSN_I32_ADD, 0); + wasm_func_add_mem_insn(t->c, t->module, t->cur_func, + WASM_INSN_I32_STORE, 2u, 0u, 0u); + break; + } + case WIR_VA_COPY: { + /* *dst_ap = *src_ap (single i32). */ + emit_push_addr_value(t, w->addr); + emit_push_addr_value(t, w->call_sret_addr); + wasm_func_add_mem_insn(t->c, t->module, t->cur_func, + WASM_INSN_I32_LOAD, 2u, 0u, 0u); + wasm_func_add_mem_insn(t->c, t->module, t->cur_func, + WASM_INSN_I32_STORE, 2u, 0u, 0u); + break; + } + case WIR_INTRINSIC: { + emit_intrinsic(t, w); + break; + } + case WIR_ASM_BLOCK: { + Heap* h_blk = t->c->ctx->heap; + u32 nin = w->asm_nin; + u32 nout = w->asm_nout; + u32* in_locals = NULL; + u32* out_locals = NULL; + if (nin) { + in_locals = (u32*)h_blk->alloc(h_blk, sizeof(u32) * nin, + _Alignof(u32)); + if (!in_locals) wfail(t, "wasm: out of memory"); + /* defer per-input allocation until after output locals are known + * so numeric tieback ("+r", "0".."9") can share. */ + } + if (nout) { + out_locals = (u32*)h_blk->alloc(h_blk, sizeof(u32) * nout, + _Alignof(u32)); + if (!out_locals) wfail(t, "wasm: out of memory"); + for (u32 i = 0; i < nout; ++i) + out_locals[i] = add_wasm_local(t, valtype_for_type(t, + w->asm_out_types[i])); + } + if (nin) { + for (u32 i = 0; i < nin; ++i) { + i32 share = w->asm_in_share_out[i]; + if (share >= 0 && (u32)share < nout) { + in_locals[i] = out_locals[share]; + } else { + in_locals[i] = add_wasm_local( + t, valtype_for_type(t, w->asm_in_types[i])); + } + } + } + /* Input materialization: push source operand, then for OPK_INDIRECT + * inputs ("m" constraint with displacement) splice in + * `i32.const ofs; i32.add` so the input local holds base+ofs. + * Finally local.set into the input's local (which may be a shared + * output local). */ + for (u32 i = 0; i < nin; ++i) { + emit_push_operand(t, w->asm_in_kinds[i], w->asm_in_imms[i], + w->asm_in_regs[i], w->asm_in_types[i]); + if (w->asm_in_kinds[i] == WOP_REG && w->asm_in_imms[i] != 0) { + emit_push_imm(t, WASM_VAL_I32, w->asm_in_imms[i]); + emit_insn(t, WASM_INSN_I32_ADD, 0); + } + emit_insn(t, WASM_INSN_LOCAL_SET, (i64)in_locals[i]); + } + /* Splice body, remapping local indices < nin+nout to the actual + * wasm local table. */ + for (u32 i = 0; i < w->raw_ninsns; ++i) { + WasmInsn in = w->raw_insns[i]; + if (in.kind == WASM_INSN_LOCAL_GET || + in.kind == WASM_INSN_LOCAL_SET || + in.kind == WASM_INSN_LOCAL_TEE) { + if (in.imm >= 0 && (u64)in.imm < (u64)nin) + in.imm = (i64)in_locals[in.imm]; + else if (in.imm >= (i64)nin && (u64)in.imm < (u64)(nin + nout)) + in.imm = (i64)out_locals[in.imm - (i64)nin]; + } + t->module->current_loc = in.loc; + wasm_func_add_insn(t->c, t->module, t->cur_func, + (WasmInsnKind)in.kind, in.imm); + t->cur_func->insns[t->cur_func->ninsns - 1u] = in; + } + /* Output extraction: copy each output local into the destination + * Reg's wasm local. */ + for (u32 i = 0; i < nout; ++i) { + WasmValType ovt = valtype_for_type(t, w->asm_out_types[i]); + RegClass cls = (ovt == WASM_VAL_F32 || ovt == WASM_VAL_F64) + ? RC_FP : RC_INT; + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)out_locals[i]); + emit_local_set(t, w->asm_out_regs[i], w->asm_out_types[i], cls); + } + if (in_locals) h_blk->free(h_blk, in_locals, sizeof(u32) * nin); + if (out_locals) h_blk->free(h_blk, out_locals, sizeof(u32) * nout); + break; + } + case WIR_LABEL: { + break; + } + } + } +} + +static void linearize(WTarget* t) { + LoweringState L; + /* Rewrite WIR so every free label is bound to a synthetic SCOPE_BLOCK + * (forward goto) or SCOPE_LOOP (backward goto). After this, the only + * remaining free labels are switch-island participants, which the + * try_linearize_switch_island fast path inside linearize_range handles. */ + wasm_structurize(t); + memset(&L, 0, sizeof L); + L.t = t; + + if (t->has_stack_frame) { + t->frame_saved_sp_local = add_wasm_local(t, WASM_VAL_I32); + t->frame_base_local = add_wasm_local(t, WASM_VAL_I32); + emit_insn(t, WASM_INSN_GLOBAL_GET, (i64)t->stack_pointer_global); + emit_insn(t, WASM_INSN_LOCAL_TEE, (i64)t->frame_saved_sp_local); + if (t->frame_size) { + emit_insn(t, WASM_INSN_I32_CONST, + (i64)align_to_u32(t->frame_size, t->frame_align)); + emit_insn(t, WASM_INSN_I32_SUB, 0); + } + emit_insn(t, WASM_INSN_LOCAL_TEE, (i64)t->frame_base_local); + emit_insn(t, WASM_INSN_GLOBAL_SET, (i64)t->stack_pointer_global); + } + + /* Byval copy-in: for each ABI_ARG_INDIRECT param, copy the aggregate from + * the caller's pointer into the callee's stack-frame buffer so callee + * mutations are isolated (wasm32 BasicCABI). Byte-by-byte for v1; can be + * promoted to wider chunks later. */ + for (u32 i = 0; i < t->nbyval_copies; ++i) { + const WByvalCopy* bc = &t->byval_copies[i]; + const WSlot* s = &t->slots[bc->dst_slot_id]; + for (u32 n = 0; n < s->size; ++n) { + /* dst: frame_base */ + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->frame_base_local); + /* src byte: i32.load8_u (ptr_local) offset=n */ + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)bc->ptr_wasm_local); + wasm_func_add_mem_insn(t->c, t->module, t->cur_func, + WASM_INSN_I32_LOAD8_U, 0, n, 0); + wasm_func_add_mem_insn(t->c, t->module, t->cur_func, + WASM_INSN_I32_STORE8, 0, s->frame_offset + n, 0); + } + } + + linearize_range(t, &L, 0, t->nwir); + if (L.nstack != 0) wfail(t, "wasm: function ended with %u open scopes", + L.nstack); + /* If the body's last real WIR is a terminator (return / br / switch / + * unreachable / tail call) buried inside nested blocks, cfree's wasm + * validator does not propagate the unreachable flag across enclosing + * ENDs and would complain about a missing result at the implicit + * function exit. Emit an explicit trailing `unreachable` so control[0] + * is marked unreachable independent of the validator's propagation + * rules. Also: when the terminator is a tail call we've already + * emitted the linear-stack SP restore inline (see WIR_CALL handler), + * and the function never reaches the post-body epilogue at runtime — + * skip it to avoid emitting dead GLOBAL_GET/GLOBAL_SET pairs after the + * return_call. */ + int last_is_tail_call = 0; + { + int needs_unreachable = 0; + for (u32 i = t->nwir; i > 0; --i) { + WIR* w = &t->wir[i - 1u]; + if (w->op == WIR_LABEL || w->op == WIR_SCOPE_OPEN || + w->op == WIR_SCOPE_CLOSE || w->op == WIR_SCOPE_ELSE) + continue; + if (w->op == WIR_RET || w->op == WIR_JUMP || w->op == WIR_SWITCH || + w->op == WIR_UNREACHABLE) { + needs_unreachable = 1; + } else if ((w->op == WIR_CALL || w->op == WIR_CALL_INDIRECT) && + w->call_tail) { + needs_unreachable = 1; + last_is_tail_call = 1; + } + break; + } + if (needs_unreachable) emit_insn(t, WASM_INSN_UNREACHABLE, 0); + } + if (t->has_stack_frame && !t->dead && !last_is_tail_call) { + emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->frame_saved_sp_local); + emit_insn(t, WASM_INSN_GLOBAL_SET, (i64)t->stack_pointer_global); + } +} + +void wasm_func_end(CGTarget* tg) { + WTarget* t = (WTarget*)tg; + if (!t->cur_func) return; + /* Linearize WIR into the WasmFunc body. */ + linearize(t); + t->cur_fn_desc = NULL; + t->cur_func = NULL; + /* Free per-function WIR arg arrays. */ + Heap* h = t->c->ctx->heap; + for (u32 i = 0; i < t->nwir; ++i) { + WIR* w = &t->wir[i]; + if (w->call_args) { + h->free(h, w->call_args, sizeof(Reg) * w->call_narg); + h->free(h, w->call_arg_imms, sizeof(i64) * w->call_narg); + h->free(h, w->call_arg_kinds, w->call_narg); + h->free(h, w->call_arg_types, sizeof(CfreeCgTypeId) * w->call_narg); + if (w->call_arg_addrs) + h->free(h, w->call_arg_addrs, sizeof(Operand) * w->call_narg); + w->call_args = NULL; + w->call_arg_imms = NULL; + w->call_arg_kinds = NULL; + w->call_arg_types = NULL; + w->call_arg_addrs = NULL; + } + if (w->switch_cases) { + h->free(h, w->switch_cases, sizeof(CGSwitchCase) * w->switch_ncases); + w->switch_cases = NULL; + w->switch_ncases = 0; + } + if (w->raw_insns) { + h->free(h, w->raw_insns, sizeof(WasmInsn) * w->raw_ninsns); + w->raw_insns = NULL; + w->raw_ninsns = 0; + } + if (w->asm_in_kinds) { + h->free(h, w->asm_in_kinds, w->asm_nin); + h->free(h, w->asm_in_imms, sizeof(i64) * w->asm_nin); + h->free(h, w->asm_in_regs, sizeof(Reg) * w->asm_nin); + h->free(h, w->asm_in_types, sizeof(CfreeCgTypeId) * w->asm_nin); + h->free(h, w->asm_in_share_out, sizeof(i32) * w->asm_nin); + w->asm_in_kinds = NULL; + w->asm_in_imms = NULL; + w->asm_in_regs = NULL; + w->asm_in_types = NULL; + w->asm_in_share_out = NULL; + w->asm_nin = 0; + } + if (w->asm_out_regs) { + h->free(h, w->asm_out_regs, sizeof(Reg) * w->asm_nout); + h->free(h, w->asm_out_types, sizeof(CfreeCgTypeId) * w->asm_nout); + w->asm_out_regs = NULL; + w->asm_out_types = NULL; + w->asm_nout = 0; + } + } + t->nwir = 0; +} + +/* CGTarget alias hook. cg/session.c has already shared (section_id, value) + * between alias_sym and target_sym at the ObjBuilder layer, which covers + * data aliases (apply_sym_fixups reads section_id/value directly off the + * ObjSym). Function aliases need extra wiring: the wasm function payload + * lives in a target-side side-table (sym_to_func), not in obj sections, + * and the alias's external linker name needs its own WasmExport entry. */ +void wasm_alias(CGTarget* tg, ObjSymId alias_sym, ObjSymId target_sym, + CfreeCgTypeId type) { + WTarget* t = (WTarget*)tg; + const ObjSym* tsym; + const ObjSym* asym; + (void)type; + if (t->dead) return; + tsym = obj_symbol_get(t->obj, target_sym); + if (!tsym) wfail(t, "wasm: alias against unknown target symbol"); + if (tsym->kind == SK_FUNC) { + /* Mirror sym_to_func so any later WIR_CALL against the alias resolves + * to the target's wasm function index. */ + u32 idx = sym_to_wasm_func(t, target_sym, NULL); + if (alias_sym >= t->sym_to_func_cap) { + Heap* h = t->c->ctx->heap; + u32 nc = t->sym_to_func_cap ? t->sym_to_func_cap : 16u; + while (nc <= alias_sym) nc *= 2u; + u32* p = (u32*)h->realloc(h, t->sym_to_func, + sizeof(u32) * t->sym_to_func_cap, + sizeof(u32) * nc, _Alignof(u32)); + if (!p) wfail(t, "wasm: out of memory"); + for (u32 i = t->sym_to_func_cap; i < nc; ++i) p[i] = 0; + t->sym_to_func = p; + t->sym_to_func_cap = nc; + } + t->sym_to_func[alias_sym] = idx + 1u; + /* Export under the alias's linker name when non-local. Mirrors the + * export logic at the end of wasm_func_begin. */ + asym = obj_symbol_get(t->obj, alias_sym); + if (asym && asym->bind != SB_LOCAL) { + const char* name = pool_sym_cstr(t->c->global, asym->name, NULL); + if (name && *name) { + Heap* h = t->c->ctx->heap; + size_t nlen = strlen(name); + char* exp_name = (char*)h->alloc(h, nlen + 1u, 1); + WasmExport* e; + memcpy(exp_name, name, nlen + 1u); + e = wasm_add_export(t->c, t->module); + e->name = exp_name; + e->kind = 0; /* function export */ + e->index = idx; + } + } + return; + } + if (tsym->kind == SK_OBJ) { + /* Data aliases: obj_symbol_define has already shared (section_id, + * value), and apply_sym_fixups reads those directly. Nothing more + * to do here — but diagnose if the target hasn't been defined yet + * (it would produce a bogus address at finalize). */ + if (tsym->section_id == OBJ_SEC_NONE) { + wfail(t, "wasm: data alias against undefined target symbol"); + } + return; + } + wfail(t, "wasm target: alias of symbol kind %u not yet supported", + (unsigned)tsym->kind); +} + +/* Assign each SF_ALLOC (non-EXEC) ObjBuilder section a compact base in + * linear memory. Walks sections in id order so the layout is deterministic. + * Each base is aligned to the section's required alignment and lives in + * t->section_base[sid]. Returns the next unused offset (end of data image). */ +static u32 assign_section_bases(WTarget* t) { + Heap* h = t->c->ctx->heap; + u32 nsec = obj_section_count(t->obj); + if (nsec > t->section_base_cap) { + u32 nc = t->section_base_cap ? t->section_base_cap : 4u; + while (nc < nsec) nc *= 2u; + void* p = + h->realloc(h, t->section_base, sizeof(u32) * t->section_base_cap, + sizeof(u32) * nc, _Alignof(u32)); + if (!p) wfail(t, "wasm: out of memory"); + t->section_base = (u32*)p; + for (u32 i = t->section_base_cap; i < nc; ++i) + t->section_base[i] = 0xFFFFFFFFu; + t->section_base_cap = nc; + } + u32 next = WASM_DATA_NULL_GUARD; + for (ObjSecId sid = 0; sid < nsec; ++sid) t->section_base[sid] = 0xFFFFFFFFu; + for (ObjSecId sid = 1; sid < nsec; ++sid) { + const Section* s = obj_section_get(t->obj, sid); + if (!s || s->removed || !(s->flags & SF_ALLOC) || s->flags & SF_EXEC) + continue; + u32 align = s->align ? s->align : 1u; + if (align < 1u) align = 1u; + next = align_to_u32(next, align); + t->section_base[sid] = next; + u32 sz = (s->kind == SEC_BSS || s->sem == SSEM_NOBITS) ? s->bss_size + : (u32)s->bytes.total; + if (sz > UINT32_MAX - next) + wfail(t, "wasm: linear memory image too large"); + next += sz; + } + return next; +} + +/* Patch a single i32/i64 value into the linear-memory image buffer at + * `offset`. Wasm is little-endian. */ +static void mem_write_le(u8* buf, u32 offset, u64 value, u32 width) { + for (u32 i = 0; i < width; ++i) buf[offset + i] = (u8)(value >> (i * 8u)); +} + +/* Allocate aligned BSS-style space in linear memory for every SK_COMMON + * symbol the ObjBuilder knows about. Called after assign_section_bases so + * common storage sits past the last SF_ALLOC section. Records the assigned + * base in t->common_base[id]; returns the next free cursor. */ +static u32 assign_common_bases(WTarget* t, u32 next) { + Heap* h = t->c->ctx->heap; + ObjSymIter* it = obj_symiter_new(t->obj); + ObjSymEntry e; + while (obj_symiter_next(it, &e)) { + const ObjSym* os = e.sym; + if (!os || os->removed) continue; + if (os->kind != SK_COMMON) continue; + u32 align = os->common_align ? (u32)os->common_align : 1u; + if (align < 1u) align = 1u; + if (e.id >= t->common_base_cap) { + u32 nc = t->common_base_cap ? t->common_base_cap : 8u; + while (nc <= e.id) nc *= 2u; + void* p = h->realloc(h, t->common_base, sizeof(u32) * t->common_base_cap, + sizeof(u32) * nc, _Alignof(u32)); + if (!p) wfail(t, "wasm: out of memory"); + t->common_base = (u32*)p; + for (u32 i = t->common_base_cap; i < nc; ++i) + t->common_base[i] = 0xFFFFFFFFu; + t->common_base_cap = nc; + } + next = align_to_u32(next, align); + t->common_base[e.id] = next; + u32 sz = (u32)os->size; + if (sz > UINT32_MAX - next) + wfail(t, "wasm: linear memory image too large (common symbols)"); + next += sz; + } + obj_symiter_free(it); + return next; +} + +/* Resolve `sym + addend` to a linear-memory address. Handles both + * section-defined symbols (via t->section_base[sym->section_id]) and + * common symbols (via t->common_base[sym]). Returns 0 and sets *ok=0 if + * the symbol can't be resolved here; callers diagnose. */ +static u32 wasm_sym_linear_addr(WTarget* t, ObjSymId sym, i64 addend, + int* ok) { + const ObjSym* os = obj_symbol_get(t->obj, sym); + *ok = 0; + if (!os) return 0; + if (os->kind == SK_COMMON) { + if (sym >= t->common_base_cap || + t->common_base[sym] == 0xFFFFFFFFu) + return 0; + *ok = 1; + return t->common_base[sym] + (u32)addend; + } + if (os->section_id == OBJ_SEC_NONE) return 0; + if (os->section_id >= t->section_base_cap || + t->section_base[os->section_id] == 0xFFFFFFFFu) + return 0; + *ok = 1; + return t->section_base[os->section_id] + (u32)os->value + (u32)addend; +} + +/* Apply each ObjBuilder relocation to the linear-memory image. Only + * absolute (R_ABS32/R_ABS64) relocations are supported for now; PC-relative + * and other kinds diagnose. */ +static void apply_data_relocs(WTarget* t, u8* mem) { + u32 ntotal = obj_reloc_total(t->obj); + for (u32 i = 0; i < ntotal; ++i) { + const Reloc* r = obj_reloc_at(t->obj, i); + if (!r || r->removed) continue; + if (r->section_id == OBJ_SEC_NONE) continue; + if (r->section_id >= t->section_base_cap || + t->section_base[r->section_id] == 0xFFFFFFFFu) + continue; + const Section* rs = obj_section_get(t->obj, r->section_id); + if (!rs || rs->flags & SF_EXEC) continue; + const ObjSym* tos = obj_symbol_get(t->obj, r->sym); + if (!tos) + wfail(t, + "wasm: data relocation against unresolved symbol not supported"); + /* Function-symbol references in data sections (e.g. a static vtable + * `static fn_t v = &foo;`) resolve to wasm function-table indices, not + * linear-memory addresses. The funcref table is built before + * apply_data_relocs runs, so the index is already known. */ + u32 width; + u64 value; + if (tos->kind == SK_FUNC) { + if (r->kind != R_ABS32) + wfail(t, + "wasm: function-pointer data relocation kind %u not supported " + "(only R_ABS32 on wasm32 target)", + (unsigned)r->kind); + if (r->addend != 0) + wfail(t, "wasm: nonzero addend on function-pointer data relocation"); + u32 tbl_idx = func_table_index_for(t, r->sym); + width = 4; + value = (u64)tbl_idx; + u32 dst_off = t->section_base[r->section_id] + r->offset; + mem_write_le(mem, dst_off, value, width); + continue; + } + if (tos->section_id == OBJ_SEC_NONE && tos->kind != SK_COMMON) + wfail(t, + "wasm: data relocation against unresolved symbol not supported"); + { + int ok = 0; + /* The addend is already added by `value = sym_addr + r->addend` below; + * pass 0 here so we don't double-count. */ + u32 sym_addr = wasm_sym_linear_addr(t, r->sym, 0, &ok); + if (!ok) + wfail(t, + "wasm: data relocation target symbol has no linear-memory " + "address"); + switch (r->kind) { + case R_ABS32: + width = 4; + value = (u64)(u32)((i64)sym_addr + r->addend); + break; + case R_ABS64: + wfail(t, + "wasm: R_ABS64 data relocation not supported on wasm32 target"); + default: + wfail(t, "wasm: unsupported data relocation kind %u", + (unsigned)r->kind); + } + } + u32 dst_off = t->section_base[r->section_id] + r->offset; + mem_write_le(mem, dst_off, value, width); + } +} + +/* Walk the deferred WSymFixup queue and patch the placeholder i32.const + * imm in each WasmFunc.insns[] with the resolved absolute address. */ +static void apply_sym_fixups(WTarget* t) { + for (u32 i = 0; i < t->sym_fixups_count; ++i) { + WSymFixup fx = t->sym_fixups[i]; + int ok = 0; + u32 addr = wasm_sym_linear_addr(t, fx.sym, fx.addend, &ok); + if (!ok) + wfail(t, "wasm: deferred symbol fixup against unresolved symbol"); + WasmFunc* f = &t->module->funcs[fx.wasm_func_idx]; + if (fx.insn_idx >= f->ninsns) + wfail(t, "wasm: deferred symbol fixup insn_idx out of range"); + f->insns[fx.insn_idx].imm = (i64)addr; + } +} + +static void wasm_materialize_data(WTarget* t) { + if (!t->has_memory) { + /* No linear memory was needed by any function body or addr_of, so + * symbol fixups should be empty by construction. */ + return; + } + u32 image_end = assign_section_bases(t); + image_end = assign_common_bases(t, image_end); + u32 stack_size = t->has_stack_pointer ? t->stack_size : 0u; + u32 image = image_end ? align_to_u32(image_end, 16u) : WASM_DATA_NULL_GUARD; + if (image > UINT32_MAX - stack_size) + wfail(t, "wasm: linear memory image too large"); + /* Build a single active data segment covering 0..image. Passive segments + * + memory.init would be needed for multi-TU linking; single-TU output + * stays with the simpler shape. */ + WasmDataSegment* seg = NULL; + if (image) { + seg = wasm_add_data(t->c, t->module); + seg->mode = WASM_SEG_ACTIVE; + seg->memidx = 0; + seg->offset = 0; + wasm_data_set_bytes(t->c, t->module, seg, NULL, (u64)image); + } + u32 nsec = obj_section_count(t->obj); + for (ObjSecId sid = 1; sid < nsec; ++sid) { + const Section* s = obj_section_get(t->obj, sid); + if (!s || s->removed || !(s->flags & SF_ALLOC) || s->flags & SF_EXEC) + continue; + if (s->kind == SEC_BSS || s->sem == SSEM_NOBITS || !s->bytes.total) + continue; + buf_flatten(&s->bytes, seg->bytes + t->section_base[sid]); + } + if (seg) apply_data_relocs(t, seg->bytes); + apply_sym_fixups(t); + t->data_end = image; + u32 stack_top = (u32)align_to_u32(image + stack_size, 16u); + t->module->memories[0].min_pages = (stack_top + 65535u) / 65536u; + /* Shared memory requires has_max and max >= min. We provisionally set max + * to the wasm32 cap in ensure_shared_memory; raise min, not max, if min + * grew larger here (shouldn't happen for a 4 GiB cap, but stays correct). */ + if (t->module->memories[0].shared) { + if (!t->module->memories[0].has_max || + t->module->memories[0].max_pages < t->module->memories[0].min_pages) { + t->module->memories[0].has_max = 1; + t->module->memories[0].max_pages = t->module->memories[0].min_pages; + } + } + if (t->has_stack_pointer && t->stack_pointer_global < t->module->nglobals) { + t->module->globals[t->stack_pointer_global].init.imm = stack_top; + } +} + +/* Static-data initializers (e.g. `static fn_t v[] = {&foo, &bar};`) go + * through ObjBuilder relocations rather than wasm_addr_of, so they never + * touch queue_func_table_fixup. Scan the reloc table once before building + * the funcref table so every function whose address is referenced from data + * also gets a table slot. apply_data_relocs then patches the linear-memory + * image with the assigned index. */ +static void wasm_collect_func_data_refs(WTarget* t) { + u32 ntotal = obj_reloc_total(t->obj); + for (u32 i = 0; i < ntotal; ++i) { + const Reloc* r = obj_reloc_at(t->obj, i); + const ObjSym* tos; + if (!r || r->removed) continue; + if (r->section_id == OBJ_SEC_NONE) continue; + { + const Section* rs = obj_section_get(t->obj, r->section_id); + if (!rs || rs->flags & SF_EXEC) continue; /* code-section relocs */ + } + tos = obj_symbol_get(t->obj, r->sym); + if (!tos || tos->kind != SK_FUNC) continue; + (void)func_table_index_for(t, r->sym); + (void)sym_to_wasm_func(t, r->sym, NULL); + } +} + +/* Build the single funcref table and its active element segment, then patch + * every queued WFuncTableFixup's placeholder `i32.const 0` with the assigned + * table index. Slot 0 stays reserved (call_indirect through index 0 traps on + * the type check), so the first recorded function lands at index 1. Each + * WasmElemSegment caps its funcs array at 64 entries; we chunk across + * multiple segments when the address-taken set is larger. */ +static void wasm_materialize_functable(WTarget* t) { + wasm_collect_func_data_refs(t); + if (!t->has_func_table || t->func_table_count == 0) return; + ensure_module(t); + /* Table: non-growable, sized to hold the reserved null slot plus every + * assigned entry. */ + WasmTable* tbl = wasm_add_table(t->c, t->module); + tbl->elem_type = WASM_VAL_FUNCREF; + tbl->min = 1u + t->func_table_count; + tbl->max = tbl->min; + tbl->has_max = 1; + /* Active element segment populates table 0 starting at offset 1 (slot 0 + * stays null). Element segments are now heap-grown — no chunking needed. */ + { + WasmElemSegment* seg = wasm_add_elem(t->c, t->module); + seg->mode = WASM_SEG_ACTIVE; + seg->elem_type = WASM_VAL_FUNCREF; + seg->tableidx = 0; + seg->offset = 1; + for (u32 i = 0; i < t->func_table_count; ++i) { + ObjSymId sym = t->func_table[i]; + wasm_elem_push_func(t->c, t->module, seg, + sym_to_wasm_func(t, sym, NULL)); + } + } + /* Patch placeholders. */ + for (u32 i = 0; i < t->func_table_fixups_count; ++i) { + WFuncTableFixup fx = t->func_table_fixups[i]; + u32 tbl_idx = func_table_index_for(t, fx.sym); + WasmFunc* f = &t->module->funcs[fx.wasm_func_idx]; + if (fx.insn_idx >= f->ninsns) + wfail(t, "wasm: function-pointer fixup insn_idx out of range"); + f->insns[fx.insn_idx].imm = (i64)tbl_idx; + } +} + +/* Wasm requires every import to occupy a lower function index than any + * defined function. The backend, however, allocates a WasmFunc for any + * direct-call target in walk order — so a defined function may end up at a + * lower array index than an import created later by promote_import_func. + * Reorder m->funcs so all imports precede all definitions, then walk every + * function-index reference in the module and apply the old->new mapping. */ +static void wasm_reorder_funcs_imports_first(WTarget* t) { + WasmModule* m = t->module; + if (!m || m->nfuncs == 0) return; + Heap* h = m->heap; + u32 n = m->nfuncs; + /* Quick check: bail out if imports are already before all definitions. */ + int seen_def = 0; + int needs_reorder = 0; + for (u32 i = 0; i < n; ++i) { + if (m->funcs[i].is_import) { + if (seen_def) { needs_reorder = 1; break; } + } else { + seen_def = 1; + } + } + if (!needs_reorder) return; + u32* old_to_new = (u32*)h->alloc(h, sizeof(u32) * n, _Alignof(u32)); + WasmFunc* new_funcs = (WasmFunc*)h->alloc(h, sizeof(WasmFunc) * n, + _Alignof(WasmFunc)); + if (!old_to_new || !new_funcs) wfail(t, "wasm: out of memory"); + u32 w_idx = 0; + for (u32 i = 0; i < n; ++i) { + if (m->funcs[i].is_import) { + new_funcs[w_idx] = m->funcs[i]; + old_to_new[i] = w_idx++; + } + } + for (u32 i = 0; i < n; ++i) { + if (!m->funcs[i].is_import) { + new_funcs[w_idx] = m->funcs[i]; + old_to_new[i] = w_idx++; + } + } + /* Swap arrays. Old buffer is freed via the module's heap-tracked + * realloc bookkeeping when wasm_module_free runs; we just overwrite the + * pointer + length here. */ + h->free(h, m->funcs, sizeof(WasmFunc) * m->cap_funcs); + m->funcs = new_funcs; + m->cap_funcs = n; + /* Remap every funcidx-bearing slot in the module. */ + for (u32 fi = 0; fi < n; ++fi) { + WasmFunc* f = &m->funcs[fi]; + for (u32 j = 0; j < f->ninsns; ++j) { + WasmInsn* in = &f->insns[j]; + if (in->kind == WASM_INSN_CALL || in->kind == WASM_INSN_RETURN_CALL || + in->kind == WASM_INSN_REF_FUNC) { + u32 old = (u32)in->imm; + if (old < n) in->imm = (int64_t)old_to_new[old]; + } + } + } + for (u32 i = 0; i < m->nexports; ++i) { + if (m->exports[i].kind == 0u && m->exports[i].index < n) + m->exports[i].index = old_to_new[m->exports[i].index]; + } + for (u32 i = 0; i < m->nelems; ++i) { + WasmElemSegment* seg = &m->elems[i]; + for (u32 j = 0; j < seg->nfuncs; ++j) { + if (seg->funcs[j] < n) seg->funcs[j] = old_to_new[seg->funcs[j]]; + } + } + if (m->has_start && m->start_func < n) + m->start_func = old_to_new[m->start_func]; + /* Update the backend's sym_to_func reverse map so any post-finalize lookups + * (e.g. data-reloc fixups) resolve to the new indices. The map stores + * idx+1 so 0 = "unassigned"; preserve that convention. */ + for (ObjSymId sym = 0; sym < t->sym_to_func_cap; ++sym) { + if (t->sym_to_func[sym]) { + u32 old = t->sym_to_func[sym] - 1u; + if (old < n) t->sym_to_func[sym] = old_to_new[old] + 1u; + } + } + h->free(h, old_to_new, sizeof(u32) * n); +} + +/* Export the module's linear memory under the conventional name "memory" so + * standard runtimes (browser/wasmtime/wasmer/Node) can find it. Only emits + * when the module has at least one defined (non-import) memory and no + * memory export already exists. */ +static void wasm_export_memory(WTarget* t) { + WasmModule* m = t->module; + if (!m) return; + ensure_linear_memory(t); + m = t->module; + /* Find the first defined (non-import) memory. */ + u32 mem_idx = 0; + int found = 0; + for (u32 i = 0; i < m->nmemories; ++i) { + if (!m->memories[i].is_import) { mem_idx = i; found = 1; break; } + } + if (!found) return; + /* Skip if the user already added a memory export (e.g. via the WAT path + * or future explicit-export hook). */ + for (u32 i = 0; i < m->nexports; ++i) { + if (m->exports[i].kind == 2u && m->exports[i].index == mem_idx) return; + } + Heap* h = t->c->ctx->heap; + WasmExport* e = wasm_add_export(t->c, m); + static const char kName[] = "memory"; + char* dup = (char*)h->alloc(h, sizeof(kName), 1); + if (!dup) wfail(t, "wasm: out of memory"); + memcpy(dup, kName, sizeof(kName)); + e->name = dup; + e->kind = 2u; /* memory export */ + e->index = mem_idx; +} + +/* Diagnose any WasmFunc that has neither a body nor import status — that's a + * declaration with no definition, e.g. a function-pointer reference to an + * extern whose call site never appeared (so we never saw an ABI to synthesize + * an import signature from). Emitting such a function would produce a + * malformed module. Diagnose by sym name so users can fix the source. */ +static void wasm_diagnose_unresolved_funcs(WTarget* t) { + if (!t->module) return; + for (ObjSymId sym = 1; sym < t->sym_to_func_cap; ++sym) { + if (!t->sym_to_func[sym]) continue; + u32 idx = t->sym_to_func[sym] - 1u; + if (idx >= t->module->nfuncs) continue; + WasmFunc* f = &t->module->funcs[idx]; + if (f->is_import) continue; + if (f->ninsns != 0) continue; + const ObjSym* os = obj_symbol_get(t->obj, sym); + if (!os || os->section_id != OBJ_SEC_NONE) continue; + const char* name = pool_sym_cstr(t->c->global, os->name, NULL); + wfail(t, + "wasm: undefined function '%s' has its address taken but no direct " + "call was seen — cannot synthesize import signature; add a direct " + "call or annotate the declaration", + name ? name : "(anonymous)"); + } +} + +void wasm_finalize(CGTarget* tg) { + WTarget* t = (WTarget*)tg; + wasm_materialize_functable(t); + wasm_materialize_data(t); + if (t->module) { + wasm_diagnose_unresolved_funcs(t); + wasm_export_memory(t); + wasm_reorder_funcs_imports_first(t); + } + /* WasmModule remains attached to ObjBuilder via OBJ_EXT_WASM; emit_wasm + * flushes it. */ +} + +static void wasm_module_freefn(Compiler* c, void* p) { + (void)c; + WasmModule* m = (WasmModule*)p; + Heap* h = m->heap; + wasm_module_free(m); + h->free(h, m, sizeof *m); +} + +void wasm_destroy(CGTarget* tg) { + WTarget* t = (WTarget*)tg; + Heap* h = t->c->ctx->heap; + if (t->reg_to_local) h->free(h, t->reg_to_local, sizeof(u32) * t->reg_cap); + if (t->reg_type) h->free(h, t->reg_type, sizeof(CfreeCgTypeId) * t->reg_cap); + if (t->reg_cls) h->free(h, t->reg_cls, t->reg_cap); + if (t->wir) h->free(h, t->wir, sizeof(WIR) * t->wir_cap); + if (t->labels) h->free(h, t->labels, sizeof(WLabel) * t->labels_cap); + if (t->slots) h->free(h, t->slots, sizeof(WSlot) * t->slots_cap); + if (t->param_local_idx) + h->free(h, t->param_local_idx, sizeof(u32) * t->param_local_idx_cap); + if (t->byval_copies) + h->free(h, t->byval_copies, sizeof(WByvalCopy) * t->byval_copies_cap); + if (t->sym_to_func) + h->free(h, t->sym_to_func, sizeof(u32) * t->sym_to_func_cap); + if (t->funcs) h->free(h, t->funcs, sizeof(WFunc) * t->funcs_cap); + if (t->section_base) + h->free(h, t->section_base, sizeof(u32) * t->section_base_cap); + if (t->common_base) + h->free(h, t->common_base, sizeof(u32) * t->common_base_cap); + if (t->sym_fixups) + h->free(h, t->sym_fixups, sizeof(WSymFixup) * t->sym_fixups_cap); + if (t->func_table) + h->free(h, t->func_table, sizeof(ObjSymId) * t->func_table_cap); + if (t->func_table_fixups) + h->free(h, t->func_table_fixups, + sizeof(WFuncTableFixup) * t->func_table_fixups_cap); + h->free(h, t, sizeof *t); +} + +/* ----------------------------------------------------------------- + * Module bootstrap: attach a WasmModule to the ObjBuilder so emit_wasm + * can find it. Lazily on first func_begin. + * ----------------------------------------------------------------- */ + +static struct WasmModule* ensure_module(WTarget* t) { + if (t->module) return t->module; + Heap* h = t->c->ctx->heap; + WasmModule* m = (WasmModule*)h->alloc(h, sizeof *m, _Alignof(WasmModule)); + if (!m) wfail(t, "wasm: out of memory"); + wasm_module_init(m, h); + /* cfree-produced modules always declare bulk-memory support: WIR_COPY_BYTES + * / WIR_SET_BYTES lower to memory.copy / memory.fill unconditionally, and + * the sret-return path emits memory.copy too. */ + m->features |= WASM_FEATURE_BULK_MEMORY; + t->module = m; + obj_ext_set(t->obj, OBJ_EXT_WASM, m, wasm_module_freefn); + return m; +} diff --git a/src/arch/wasm/internal.h b/src/arch/wasm/internal.h @@ -0,0 +1,395 @@ +#ifndef CFREE_ARCH_WASM_INTERNAL_H +#define CFREE_ARCH_WASM_INTERNAL_H + +/* Wasm CGTarget. + * + * Produces a tool-conventions-shaped Wasm module from a single Toy/C + * translation unit. Operates with virtual_regs=1: CG mints fresh Reg ids and + * the target assigns each Reg a Wasm local on first use, materializing values + * by `local.get`/`local.set`. The accumulating WasmModule is attached to the + * ObjBuilder under OBJ_EXT_WASM so emit_wasm can flush it at finalize time. + * + * Scope (initial): scalar i32/i64/f32/f64 with locals, params, return, direct + * calls, structured `scope_begin(LOOP)` + break/continue, label-based + * forward `if`/`if-else` via the cfree_cg_if_begin/else/end pattern, basic + * binop/unop/cmp/convert, and OPK_IMM constants. Address-taken locals, + * aggregates, indirect calls, alloca, va_*, intrinsics other than + * trivial ones, inline asm, TLS, bitfields, switch_, indirect_branch, and + * load_label_addr all panic with a precise diagnostic; they will land as + * follow-ups against doc/WASM.md. Atomics (load/store/rmw/cmpxchg/fence) + * are lowered through the wasm-threads opcodes; the linear memory is + * promoted to shared on the first atomic emission. */ + +#include <cfree/core.h> + +#include "arch/arch.h" +#include "core/core.h" +#include "obj/obj.h" + +/* Forward references into lang/wasm. We use the existing in-memory module + * representation rather than rebuilding one, so emit_wasm can reuse + * wasm_encode unchanged. */ +struct WasmModule; +struct WasmFunc; + +/* Per-instruction kinds we record, then linearize at func_end. Keeping the + * record list separate from the WasmFunc body lets us run the deferred + * label/jump → wasm-block resolution without baking forward-only structure + * into the recording side. */ +typedef enum WIROp { + WIR_LOAD_IMM, /* dst, imm, type */ + WIR_LOAD_CONST_F, /* dst, fp_imm, type */ + WIR_COPY, /* dst, src */ + WIR_BINOP, /* dst, a, b, BinOp */ + WIR_UNOP, /* dst, a, UnOp */ + WIR_CMP, /* dst, a, b, CmpOp */ + WIR_CONVERT, /* dst, src, ConvKind */ + WIR_LABEL, /* place label[0] */ + WIR_JUMP, /* jump to label[0] */ + WIR_CMP_BRANCH, /* cmp_branch op a b -> label[0] */ + WIR_CALL, /* callee_sym, nargs, args[], ret_reg (or REG_NONE) */ + WIR_CALL_INDIRECT, /* callee in `a` (i32 table index), typeidx in `imm`, + nargs/args[]/ret_reg same shape as WIR_CALL */ + WIR_RET, /* val (or REG_NONE) */ + WIR_SCOPE_OPEN, /* scope_id; kind == SCOPE_LOOP/BLOCK/IF; cond reg for IF */ + WIR_SCOPE_CLOSE, /* scope_id */ + WIR_SCOPE_ELSE, /* scope_id */ + WIR_UNREACHABLE, + WIR_SWITCH, /* selector operand + dense label table at lowering */ + WIR_LOAD_LOCAL, /* dst, frame_slot in `imm`, type */ + WIR_STORE_LOCAL, /* frame_slot in `imm`, src (a/imm_a/imm_kind), type */ + WIR_LOAD_MEM, /* dst = load addr */ + WIR_STORE_MEM, /* store src -> addr */ + WIR_ADDR_OF, /* dst = address of addr */ + WIR_ALLOCA, /* dst = stack allocation of size operand */ + WIR_COPY_BYTES, /* memcpy-like byte copy */ + WIR_SET_BYTES, /* memset-like byte set */ + WIR_ATOMIC_LOAD, /* dst = atomic.load(addr_reg), mem holds access info */ + WIR_ATOMIC_STORE, /* atomic.store(addr_reg, src) */ + WIR_ATOMIC_RMW, /* dst = atomic.rmw_<op>(addr_reg, val); cgop = AtomicOp */ + WIR_ATOMIC_CAS, /* dst = prior; dst2 = ok (i32 0/1); a=addr_reg, + b/imm_kind_b=expected, op_c/imm_kind_c=desired */ + WIR_FENCE, /* atomic.fence (memory order ignored, wasm has seq_cst) */ + WIR_VA_START, /* addr = ap_addr; stores va_ptr_param_local i32 at *ap */ + WIR_VA_ARG, /* dst = load of `type` from *(*addr); advance *addr by 8 */ + WIR_VA_COPY, /* addr = dst_ap_addr; call_sret_addr = src_ap_addr */ + WIR_INTRINSIC, /* cgop = IntrinKind; operand layout per kind: + - bit ops (CLZ/CTZ/POPCOUNT/BSWAP16/32/64): one + register operand in `a`, single dst in `dst`, + `type` carries the operand/result type. + - overflow arith (S/U{ADD,SUB,MUL}_OVERFLOW): two + value operands captured into a/imm_a/imm_kind and + b/imm_b/imm_kind_b, dst = value reg, dst2 = i32 + overflow flag reg, `type` = value type. */ + WIR_ASM_BLOCK, /* inline asm block. raw_insns[0..raw_ninsns) is the + pre-parsed body; local.get/set/tee indices in + [0, asm_nin) are remapped to freshly allocated wasm + locals at linearize time. Input bindings are + captured into asm_in_kinds/asm_in_imms/asm_in_regs; + output bindings into asm_out_regs. */ +} WIROp; + +typedef struct WIR { + u8 op; + u8 cls; /* RegClass for the produced value or branch operand */ + u8 cgop; /* BinOp/UnOp/CmpOp/ConvKind discriminator */ + u8 pad; + CfreeCgTypeId type; /* type of dst (or operand type for cmp/store) */ + CfreeCgTypeId type2; /* operand type for convert/cmp */ + Reg dst; + Reg a; + Reg b; + i64 imm; + double fp_imm; + Label labels[1]; /* used by LABEL/JUMP/CMP_BRANCH */ + u32 scope_id; + u32 imm_kind; /* 0=reg, 1=imm — operand variant for a/b */ + u32 imm_kind_b; + i64 imm_a; + i64 imm_b; + ObjSymId call_sym; + u32 call_narg; + Operand addr; + MemAccess mem; + AggregateAccess agg; + /* Per-arg captured operand. kind: 0=REG (value=arg_regs[i]), 1=IMM + * (value=arg_imms[i]), 4=ADDR (the i32 address of an aggregate source — + * call_arg_addrs[i] holds the original Operand). Heap-allocated when + * nargs>0; freed at func_end. */ + Reg* call_args; + i64* call_arg_imms; + u8* call_arg_kinds; + CfreeCgTypeId* call_arg_types; + Operand* call_arg_addrs; + /* Sret return slot: when the called function returns indirectly, the caller + * prepends an i32 pointer to a caller-allocated buffer. call_sret_addr is + * that buffer's address operand; emit-time pushes its i32 first. */ + u8 call_has_sret; + u8 call_variadic; /* callee is variadic; pack call_var_* into linear-memory + * buffer and push its addr as hidden trailing i32 arg. */ + u8 call_tail; /* emit as return_call / return_call_indirect */ + u8 pad_call[1]; + Operand call_sret_addr; + /* Variadic args (those past d->abi->nparams when callee is variadic). Stored + * separately from call_args because they don't appear in the wasm signature + * at all — they go into a caller-packed linear-memory buffer, each in an + * 8-byte slot. kinds are WOP_REG / WOP_IMM only; aggregate variadic args + * diagnose in wasm_call. */ + u32 call_nvar; + Reg* call_var_regs; + i64* call_var_imms; + u8* call_var_kinds; + CfreeCgTypeId* call_var_types; + CGSwitchCase* switch_cases; + u32 switch_ncases; + /* Atomic CAS extras: third value-operand (desired) plus a second result reg + * (ok bool). For the other atomic ops these stay 0. */ + Reg dst2; + Reg op_c; + u32 imm_kind_c; + i64 imm_c; + /* WIR_ASM_BLOCK payload. Heap-allocated arrays owned by the WIR (freed at + * per-func teardown). raw_insns is the parsed body; the *_in_* arrays hold + * one entry per input operand (declaration order, including inout duplicates + * appended at the end); the *_out_* arrays hold one entry per output. */ + struct WasmInsn* raw_insns; + u32 raw_ninsns; + u32 asm_nin; + u32 asm_nout; + u8* asm_in_kinds; /* WOP_REG / WOP_IMM / WOP_LOCAL */ + i64* asm_in_imms; + Reg* asm_in_regs; + CfreeCgTypeId* asm_in_types; + /* For each input i: index of the matching output (numeric tieback "N" or + * +r inout duplicate); -1 if the input is independent. Inputs that share + * an output's wasm local materialize into the OUTPUT's local. */ + i32* asm_in_share_out; + Reg* asm_out_regs; + CfreeCgTypeId* asm_out_types; +} WIR; + +typedef struct WScope { + u8 cg_kind; /* ScopeKind */ + u8 placed_in_wir; /* WIR_SCOPE_OPEN emitted */ + u8 break_seen; /* break_label_place arrived */ + u8 cont_seen; /* continue_label_place arrived */ + u32 id; + Label break_lbl; + Label cont_lbl; + Reg cond_reg; /* SCOPE_IF: condition operand carried by scope_begin */ + CfreeCgTypeId result_type; +} WScope; + +typedef enum WLabelKind { + WLBL_UNBOUND = 0, + WLBL_SCOPE_BREAK, + WLBL_SCOPE_CONT, + WLBL_FORWARD, /* placed via label_place but not tied to a scope */ +} WLabelKind; + +typedef struct WLabel { + u8 kind; + u8 placed; + u8 pad[2]; + u32 scope_id; /* for SCOPE_BREAK/SCOPE_CONT */ + u32 wir_index; /* WIR_LABEL position once placed */ +} WLabel; + +typedef struct WFunc { + ObjSymId sym; + u32 wasm_func_idx; /* index into WasmModule.funcs */ + CfreeCgTypeId fn_type; + u8 has_export_name; + u8 pad[3]; +} WFunc; + +typedef enum WSlotKind { + W_SLOT_LOCAL = 0, + W_SLOT_STACK = 1, +} WSlotKind; + +/* For each ABI_ARG_INDIRECT (byval) param the callee receives an i32 pointer + * in a wasm function param and must copy the pointed-to aggregate into a + * caller-isolated buffer in the linear-memory stack frame. We queue these at + * wasm_param time and emit the byte copies in the prologue, after frame setup + * and before the user body. */ +typedef struct WByvalCopy { + u32 ptr_wasm_local; /* wasm-local holding the source pointer (param slot) */ + u32 dst_slot_id; /* index into WTarget.slots; slot is W_SLOT_STACK */ +} WByvalCopy; + +typedef struct WSlot { + u8 kind; + u8 pad[3]; + CfreeCgTypeId type; + u32 wasm_local; + u32 size; + u32 align; + u32 frame_offset; +} WSlot; + +/* Deferred symbol-address fixup. emit_addr_operand for OPK_GLOBAL pushes + * `i32.const 0` placeholder and queues a WSymFixup. wasm_materialize_data + * computes the compact section base layout and rewrites in.imm with the + * absolute linear-memory address (section_base[sym->section_id] + sym->value + * + addend). Static-data relocations are applied directly to the linear + * memory image via the same section_base table. */ +typedef struct WSymFixup { + u32 wasm_func_idx; + u32 insn_idx; + ObjSymId sym; + i64 addend; +} WSymFixup; + +/* Function-pointer fixup. emit_addr_operand for an OPK_GLOBAL pointing at a + * function symbol pushes `i32.const 0` and queues a WFuncTableFixup. At + * finalize, wasm_materialize_functable assigns each address-taken function a + * sequential index (starting at 1; slot 0 is reserved as null) into the + * single funcref table and rewrites in.imm with the assigned table index. */ +typedef struct WFuncTableFixup { + u32 wasm_func_idx; + u32 insn_idx; + ObjSymId sym; +} WFuncTableFixup; + +typedef struct WTarget { + CGTarget base; + + Compiler* c; + ObjBuilder* obj; + struct WasmModule* module; + + /* TU-wide: ObjSymId -> (wasm_func_idx + 1); 0 means "not yet a wasm func". + * Lazily grown as ObjSymIds appear via call() or func_begin(). */ + u32* sym_to_func; + u32 sym_to_func_cap; + WFunc* funcs; + u32 nfuncs; + u32 funcs_cap; + + /* Per-function state. Reset on func_begin. */ + const CGFuncDesc* cur_fn_desc; + /* Most recent SrcLoc the frontend told us about via wasm_set_loc. Used by + * cur_loc so diagnostics attribute to the actual failing statement + * rather than the function-definition location. Zeroed at func_begin; + * a zero line falls back to cur_fn_desc->loc. */ + SrcLoc cur_stmt_loc; + u32 cur_func_idx; + struct WasmFunc* cur_func; + + /* SSA Reg -> Wasm local index (0..nparams=params, then locals). 0xffffffffu + * means "not assigned yet". */ + u32* reg_to_local; + CfreeCgTypeId* reg_type; + u8* reg_cls; + u32 reg_cap; + + /* WIR record list for the current function. */ + WIR* wir; + u32 nwir; + u32 wir_cap; + + /* Labels minted by label_new. */ + WLabel* labels; + u32 nlabels; + u32 labels_cap; + + /* CG scope stack. */ + WScope scopes[32]; + u32 nscopes; + u32 next_scope_id; + + /* Per-function frame slots. Scalar slots stay as Wasm locals; addressable + * slots are assigned offsets in a downward-growing linear-memory frame. */ + WSlot* slots; + u32 nslots; + u32 slots_cap; + u32 frame_size; + u32 frame_align; + u32 frame_base_local; + u32 frame_saved_sp_local; + u8 has_stack_frame; + u8 has_memory; + u8 has_stack_pointer; + u8 cur_has_sret; + u32 stack_pointer_global; + u32 stack_size; + u32 data_end; + + /* Compact section -> linear memory base. Populated lazily in + * wasm_materialize_data; 0xFFFFFFFFu means "not assigned yet". The + * compact layout reserves a small null guard at low memory and walks + * SF_ALLOC sections in id order, giving each section an aligned base. */ + u32* section_base; + u32 section_base_cap; + /* Common-symbol (SK_COMMON) -> linear memory base, indexed by ObjSymId. + * Common symbols have section_id == OBJ_SEC_NONE in ObjBuilder; for a + * single-TU final-module emit we lay them out BSS-style after sections. + * 0xFFFFFFFFu = no common base assigned. */ + u32* common_base; + u32 common_base_cap; + + /* Deferred symbol-address fixups; see WSymFixup. */ + WSymFixup* sym_fixups; + u32 sym_fixups_count; + u32 sym_fixups_cap; + + /* Function-pointer table state. ObjSymIds of every function whose address + * has been taken, in insertion order. The implied table index for entry + * func_table[i] is (i + 1); slot 0 stays unpopulated as a null/trap guard. + * Filled lazily by emit_addr_operand and patched into placeholder + * `i32.const 0` insns at finalize. */ + ObjSymId* func_table; + u32 func_table_count; + u32 func_table_cap; + WFuncTableFixup* func_table_fixups; + u32 func_table_fixups_count; + u32 func_table_fixups_cap; + /* Patched into the linear-memory image at apply_data_relocs time for + * R_ABS32 relocations whose target symbol is a function (so a static + * `static fn_t v = &foo;` initializer ends up holding the table index). */ + u8 has_func_table; + + /* Per-function aggregate-lowering state. Populated by func_begin and used by + * wasm_param/ret/call to translate sret + byval through the wasm32 BasicCABI + * shape (sret pointer as hidden i32 leading param, byval args as i32 + * pointers, callee copy-in into a stack-backed local buffer). */ + u32 sret_param_local; /* wasm-local idx of the sret pointer; 0xffffffffu when none */ + u32 va_ptr_param_local; /* wasm-local idx of the hidden i32 va_ptr trailing param + * on variadic functions; 0xffffffffu when none. Read by + * wasm_va_start to seed the va_list. */ + u32* param_local_idx; /* per CG-param idx -> wasm-local idx (0xffffffffu for IGNORE) */ + u32 param_local_idx_cap; + u32 nparams_cg; /* count of entries in param_local_idx */ + u8 cur_is_variadic; /* current function declared variadic (abi->variadic) */ + /* Per-function scratch locals for variadic-call packing. Lazily allocated + * on the first variadic call site; reused across all variadic calls in the + * same function (each call's save/restore window is self-contained, so a + * single pair of locals is safe). 0xffffffffu when not yet allocated. */ + u32 varcall_saved_sp_local; + u32 varcall_buf_local; + /* Scratch i32 local used by va_arg to remember the va_list address across + * the load-current-slot / advance-pointer sequence. Lazily allocated on + * first va_arg in the function. 0xffffffffu when not allocated. */ + u32 va_arg_tmp_addr_local; + WByvalCopy* byval_copies; + u32 nbyval_copies; + u32 byval_copies_cap; + + /* Body has been terminated unconditionally — skip further insns until the + * next label_place. Lets us emit dead code that the parser may produce + * without breaking wasm validation. */ + u8 dead; +} WTarget; + +CGTarget* wasm_cgtarget_new(Compiler* c, ObjBuilder* o, MCEmitter* mc); + +/* CFG structurer (src/arch/wasm/structure.c). Rewrites the recorded WIR + * list so every free WIR_LABEL becomes the break/continue of a synthetic + * SCOPE_BLOCK / SCOPE_LOOP. Called from emit.c's linearize() before the + * WIR walk; after this returns, br_to_label resolves every jump through + * the existing scope-bound machinery. Labels referenced by WIR_SWITCH + * are left untouched and handled by try_linearize_switch_island. */ +void wasm_structurize(WTarget* t); + +#endif diff --git a/src/arch/wasm/structure.c b/src/arch/wasm/structure.c @@ -0,0 +1,653 @@ +/* Wasm CGTarget structurer. + * + * Walks the WIR list produced by CG record callbacks and rewrites it so + * every label is bound to a structured Wasm scope. Free WIR_LABELs (those + * placed via wasm_label_place outside any CG scope's break/continue role) + * are wrapped in synthetic SCOPE_BLOCK (forward jumps) or SCOPE_LOOP + * (backward jumps) records; jumps to the formerly-free labels resolve via + * the existing scope-bound br_to_label machinery in emit.c. + * + * Switch islands — the C/toy `switch` shape `JUMP dispatch; case bodies; + * LABEL dispatch; selector; WIR_SWITCH; LABEL end` — are pre-processed + * by unroll_switch_islands, which reorders the WIR to put selector + + * SWITCH first and the case bodies after. That conversion turns the + * case labels into forward refs the structurer handles uniformly with + * other forward gotos. + * + * Diagnostics (hard wfail): + * - irreducible: a label has both forward and backward predecessors + * (multi-entry loop or hybrid; v1 declines node splitting). + * - cross-scope: a free label sits at scope depth D, but a predecessor + * sits inside an extra CG scope; the synthetic block's start cannot + * lift across the scope boundary cleanly. */ + +#include "arch/wasm/internal.h" + +#include <stdarg.h> +#include <string.h> + +#include "core/heap.h" + +static _Noreturn void sfail(WTarget* t, const char* fmt, ...) { + va_list ap; + SrcLoc l = {0, 0, 0}; + va_start(ap, fmt); + if (t->cur_fn_desc) l = t->cur_fn_desc->loc; + compiler_panicv(t->c, l, fmt, ap); +} + +/* Per-free-label working state. */ +typedef struct WSLbl { + Label label; + u32 wir_idx; /* WIR_LABEL position */ + u32 scope_depth; /* CG-scope nesting depth at the label */ + /* Forward predecessor stats: min adjusted start (so the synthetic block + * opens at the same scope depth as L). UINT32_MAX = no forward pred. */ + u32 fwd_start_idx; + u32 nfwd; + /* Backward predecessor stats: max adjusted end (exclusive) for the + * synthetic loop close. UINT32_MAX = no backward pred. */ + u32 bwd_end_idx; + u32 nbwd; + /* After pass_decide: actual synthetic scope range. open_idx is mutable + * by merge_ranges (extended backward to nest overlapping forward blocks); + * close_idx is fixed at the label's natural position so jumps to the + * label still land at the right place. */ + u32 open_idx; + u32 close_idx; + u8 is_loop; + u8 valid; /* 1 if structurer will wrap this label */ + u8 pad[2]; +} WSLbl; + +/* Event inserted at a WIR position. Sorted primarily by wir_idx; within the + * same wir_idx, closes fire before opens. The pair_idx field carries the + * partner event's wir_idx so we can break ties: at a shared OPEN wir_idx, + * the outer (larger pair_idx = later close) opens first; at a shared CLOSE + * wir_idx, the inner (larger pair_idx = later open) closes first. */ +typedef enum WSEvKind { + WSEV_CLOSE_LOOP = 0, + WSEV_CLOSE_BLOCK = 1, + WSEV_OPEN_BLOCK = 2, + WSEV_OPEN_LOOP = 3, +} WSEvKind; + +typedef struct WSEvent { + u32 wir_idx; + u32 pair_idx; + u8 kind; /* WSEvKind */ + u8 pad[3]; + u32 scope_id; + Label label; /* break (BLOCK) or continue (LOOP) */ +} WSEvent; + +/* Per-idx scope chain entry. */ +typedef struct WSScopeFrame { + u32 scope_id; + u32 open_idx; + u32 depth; /* 1-based */ +} WSScopeFrame; + +typedef struct WSCtx { + WTarget* t; + WSLbl* lbls; + u32 nlbls; + u32 lbls_cap; + /* Per-WIR-idx scope depth (immediately AT this index — for SCOPE_OPEN, + * the depth AFTER pushing). */ + u32* depth_at; + u32 ndepth; + /* Stack of currently-open scopes during the WIR walk; recycled per + * pass since CG scopes never go above 32. */ + WSScopeFrame stack[64]; + u32 nstack; + WSEvent* events; + u32 nevents; + u32 events_cap; +} WSCtx; + +/* ---- label index ---- */ + +static WSLbl* find_lbl(WSCtx* x, Label l) { + for (u32 i = 0; i < x->nlbls; ++i) { + if (x->lbls[i].label == l) return &x->lbls[i]; + } + return NULL; +} + +static WSLbl* push_lbl(WSCtx* x, Label l) { + Heap* h = x->t->c->ctx->heap; + if (x->nlbls == x->lbls_cap) { + u32 nc = x->lbls_cap ? x->lbls_cap * 2u : 8u; + void* p = h->realloc(h, x->lbls, sizeof(WSLbl) * x->lbls_cap, + sizeof(WSLbl) * nc, _Alignof(WSLbl)); + if (!p) sfail(x->t, "wasm: out of memory in structurer"); + x->lbls = (WSLbl*)p; + x->lbls_cap = nc; + } + WSLbl* L = &x->lbls[x->nlbls++]; + memset(L, 0, sizeof *L); + L->label = l; + L->fwd_start_idx = UINT32_MAX; + L->bwd_end_idx = UINT32_MAX; + L->open_idx = UINT32_MAX; + L->close_idx = UINT32_MAX; + return L; +} + +/* ---- pass 1: scope-depth map + free-label discovery ---- */ + +static void pass_scope_depth(WSCtx* x) { + WTarget* t = x->t; + Heap* h = t->c->ctx->heap; + x->depth_at = (u32*)h->alloc(h, sizeof(u32) * (t->nwir ? t->nwir : 1u), + _Alignof(u32)); + if (!x->depth_at) sfail(t, "wasm: out of memory in structurer"); + x->ndepth = t->nwir; + x->nstack = 0; + for (u32 i = 0; i < t->nwir; ++i) { + WIR* w = &t->wir[i]; + if (w->op == WIR_SCOPE_OPEN) { + if (x->nstack >= 64u) sfail(t, "wasm: scope nesting too deep"); + x->stack[x->nstack].scope_id = w->scope_id; + x->stack[x->nstack].open_idx = i; + x->stack[x->nstack].depth = x->nstack + 1u; + x->nstack++; + } + x->depth_at[i] = x->nstack; + if (w->op == WIR_SCOPE_CLOSE) { + if (x->nstack == 0) + sfail(t, "wasm: structurer saw SCOPE_CLOSE with empty stack"); + x->nstack--; + /* Depth AT a CLOSE is the depth INSIDE the scope (we haven't popped + * yet at the time the record sits in WIR). The pop above is for the + * NEXT idx. Restore. */ + x->depth_at[i] = x->nstack + 1u; + } + if (w->op == WIR_LABEL) { + WLabel* lbl = &t->labels[w->labels[0] - 1u]; + if (lbl->kind == WLBL_FORWARD || lbl->kind == WLBL_UNBOUND) { + WSLbl* L = find_lbl(x, w->labels[0]); + if (!L) L = push_lbl(x, w->labels[0]); + L->wir_idx = i; + L->scope_depth = x->depth_at[i]; + } + } + } + if (x->nstack != 0) + sfail(t, "wasm: structurer finished with %u scopes still open", + x->nstack); +} + +/* ---- pass 2: locate the smallest "outside" idx for a pred at depth `pd` + * whose target sits at depth `ld`. Walks backward through WIR's scope + * transitions to find the open_idx of the outermost CG scope that + * contains the pred but not the target. ---- */ +static u32 lift_to_label_depth(WSCtx* x, u32 pred_idx, u32 ld) { + WTarget* t = x->t; + u32 cur = x->depth_at[pred_idx]; + /* We need cur to drop to ld. Walk backward; each SCOPE_OPEN we cross + * (going up the depth) corresponds to an extra scope above the target's + * depth. We stop AT the outermost extra SCOPE_OPEN. */ + if (cur <= ld) return pred_idx; + u32 i = pred_idx; + u32 best = pred_idx; + while (i > 0 && cur > ld) { + --i; + if (t->wir[i].op == WIR_SCOPE_OPEN) { + cur--; + if (cur == ld) { + best = i; /* place synthetic OPEN right before this CG OPEN */ + break; + } + } else if (t->wir[i].op == WIR_SCOPE_CLOSE) { + cur++; + } + } + return best; +} + +/* Symmetric: for a backward pred at idx pred_idx targeting label at depth ld, + * return the WIR position (exclusive end) where the synthetic LOOP must + * close so that it sits at the same scope depth as the label. */ +static u32 drop_to_label_depth_after(WSCtx* x, u32 pred_idx, u32 ld) { + WTarget* t = x->t; + u32 cur = x->depth_at[pred_idx]; + /* Walk forward across closes until we drop to ld. The synthetic close + * happens AFTER the SCOPE_CLOSE that takes us back to ld. */ + if (cur <= ld) return pred_idx + 1u; + u32 i = pred_idx; + while (i < t->nwir && cur > ld) { + ++i; + if (i >= t->nwir) break; + if (t->wir[i].op == WIR_SCOPE_OPEN) { + cur++; + } else if (t->wir[i].op == WIR_SCOPE_CLOSE) { + cur--; + if (cur == ld) { + return i + 1u; + } + } + } + /* Couldn't drop — back-edge spans out of the function body? Treat as + * unstructurable. */ + return UINT32_MAX; +} + +/* ---- pass 3: record pred contributions for each free label. Walk WIR + * and for each JUMP/CMP_BRANCH/SWITCH that targets a tracked free + * label, update fwd_start_idx / bwd_end_idx / nfwd / nbwd. ---- */ +static void verify_well_nested_fwd(WSCtx* x, u32 start, u32 end, u32 ld) { + /* Every WIR index in [start, end) must have scope depth >= ld so the + * synthetic block can sit at L's depth without crossing a CG-scope + * boundary outward. */ + for (u32 i = start; i < end; ++i) { + if (x->depth_at[i] < ld) { + sfail(x->t, + "wasm: forward jump crosses out of structured scope at WIR[%u]; " + "cfree's CFG structurer does not split scopes in v1", + i); + } + } +} + +static void note_pred(WSCtx* x, u32 pred_idx, Label tgt) { + WSLbl* L = find_lbl(x, tgt); + if (!L) return; + if (pred_idx < L->wir_idx) { + /* Forward pred. */ + u32 lifted = lift_to_label_depth(x, pred_idx, L->scope_depth); + verify_well_nested_fwd(x, lifted, L->wir_idx, L->scope_depth); + if (lifted < L->fwd_start_idx) L->fwd_start_idx = lifted; + L->nfwd++; + } else if (pred_idx > L->wir_idx) { + /* Backward pred. */ + u32 dropped = drop_to_label_depth_after(x, pred_idx, L->scope_depth); + if (dropped == UINT32_MAX) { + sfail(x->t, + "wasm: backward jump at WIR[%u] to label outside function body; " + "structurer cannot place enclosing loop", + pred_idx); + } + if (L->bwd_end_idx == UINT32_MAX || dropped > L->bwd_end_idx) + L->bwd_end_idx = dropped; + L->nbwd++; + } +} + +/* Switch-island reorder. + * + * Frontends (C, toy) emit `switch` as: + * JUMP dispatch // forward, to skip past case bodies on entry + * LABEL case_1; body; JUMP end + * LABEL case_2; body; JUMP end + * LABEL dispatch + * selector + * WIR_SWITCH cases: [case_1, case_2], default: end + * LABEL end + * + * The case labels are placed BEFORE the WIR_SWITCH that targets them — a + * backward edge in WIR order that the natural structurer cannot wrap as + * forward blocks. We rewrite the WIR to move the dispatch (selector + + * SWITCH) up to the position of the original JUMP and let the case + * bodies follow: + * + * selector + * WIR_SWITCH cases: [case_1, case_2], default: end + * LABEL case_1; body; JUMP end + * LABEL case_2; body; JUMP end + * LABEL end + * + * Now case_i is forward from SWITCH and the structurer wraps it in a + * synthetic SCOPE_BLOCK like any other forward goto. The JUMP and the + * dispatch LABEL are dropped (the dispatch label is no longer reached). + * + * The pattern is detected by scanning forward from each WIR_JUMP. We + * support multiple non-nested switch islands per function and stop on + * the first intervening terminator that breaks the dispatch shape. */ +typedef struct WSIsland { + u32 j; /* JUMP dispatch idx */ + u32 d; /* LABEL dispatch idx */ + u32 s; /* WIR_SWITCH idx */ +} WSIsland; + +static WIR* wir_append(WTarget* t, WIR** out, u32* nout, u32* cap); + +static void unroll_switch_islands(WSCtx* x) { + WTarget* t = x->t; + Heap* h = t->c->ctx->heap; + WSIsland islands[16]; + u32 nislands = 0; + u32 scan = 0; + while (scan < t->nwir) { + if (t->wir[scan].op != WIR_JUMP) { scan++; continue; } + Label disp = t->wir[scan].labels[0]; + if (disp == LABEL_NONE || disp - 1u >= t->nlabels) { scan++; continue; } + WLabel* dlbl = &t->labels[disp - 1u]; + if (dlbl->kind != WLBL_FORWARD || !dlbl->placed || + dlbl->wir_index <= scan) { scan++; continue; } + /* Find WIR_SWITCH just past dispatch label; bail on intervening + * terminator. */ + u32 sw_i = UINT32_MAX; + for (u32 k = dlbl->wir_index + 1u; k < t->nwir; ++k) { + if (t->wir[k].op == WIR_SWITCH) { sw_i = k; break; } + if (t->wir[k].op == WIR_JUMP || t->wir[k].op == WIR_CMP_BRANCH || + t->wir[k].op == WIR_RET || t->wir[k].op == WIR_UNREACHABLE) break; + } + if (sw_i == UINT32_MAX) { scan++; continue; } + if (nislands >= 16u) + sfail(t, "wasm: too many switch islands per function (max 16 supported in v1)"); + islands[nislands].j = scan; + islands[nislands].d = dlbl->wir_index; + islands[nislands].s = sw_i; + nislands++; + scan = sw_i + 1u; + } + if (nislands == 0) return; + + /* Build a rewritten WIR list. For each island, emit pre + (selector + + * SWITCH) + (case bodies). Then everything after the last island's + * SWITCH is copied through. */ + WIR* new_wir = NULL; + u32 new_n = 0; + u32 new_cap = 0; + u32 cursor = 0; + for (u32 ii = 0; ii < nislands; ++ii) { + WSIsland* I = &islands[ii]; + /* pre-island: WIR[cursor .. I->j) */ + for (u32 k = cursor; k < I->j; ++k) { + WIR* w = wir_append(t, &new_wir, &new_n, &new_cap); + *w = t->wir[k]; + } + /* selector + SWITCH: WIR[I->d+1 .. I->s] */ + for (u32 k = I->d + 1u; k <= I->s; ++k) { + WIR* w = wir_append(t, &new_wir, &new_n, &new_cap); + *w = t->wir[k]; + } + /* case bodies: WIR[I->j+1 .. I->d) — drops the JUMP at j and LABEL at d */ + for (u32 k = I->j + 1u; k < I->d; ++k) { + WIR* w = wir_append(t, &new_wir, &new_n, &new_cap); + *w = t->wir[k]; + } + cursor = I->s + 1u; + } + /* trailing region after the last island */ + for (u32 k = cursor; k < t->nwir; ++k) { + WIR* w = wir_append(t, &new_wir, &new_n, &new_cap); + *w = t->wir[k]; + } + + if (t->wir) h->free(h, t->wir, sizeof(WIR) * t->wir_cap); + t->wir = new_wir; + t->nwir = new_n; + t->wir_cap = new_cap; + + /* Refresh WLabel.wir_index from the new layout so any downstream + * consumer of t->labels[].wir_index sees the rewritten positions. + * (pass_scope_depth re-discovers free-label positions on its own walk.) */ + for (u32 k = 0; k < t->nwir; ++k) { + if (t->wir[k].op != WIR_LABEL) continue; + Label l = t->wir[k].labels[0]; + if (l != LABEL_NONE && l - 1u < t->nlabels) + t->labels[l - 1u].wir_index = k; + } +} + +static void pass_preds(WSCtx* x) { + WTarget* t = x->t; + for (u32 i = 0; i < t->nwir; ++i) { + WIR* w = &t->wir[i]; + switch (w->op) { + case WIR_JUMP: + case WIR_CMP_BRANCH: + if (w->labels[0] != LABEL_NONE) note_pred(x, i, w->labels[0]); + break; + case WIR_SWITCH: + if (w->labels[0] != LABEL_NONE) note_pred(x, i, w->labels[0]); + for (u32 c = 0; c < w->switch_ncases; ++c) { + if (w->switch_cases[c].label != LABEL_NONE) + note_pred(x, i, w->switch_cases[c].label); + } + break; + default: + break; + } + } +} + +/* ---- pass 4: produce per-label decisions. ---- */ +static void pass_decide(WSCtx* x) { + WTarget* t = x->t; + for (u32 i = 0; i < x->nlbls; ++i) { + WSLbl* L = &x->lbls[i]; + if (L->nfwd == 0 && L->nbwd == 0) { + /* Dead label — placed but never branched to. Drop quietly. */ + L->valid = 0; + continue; + } + if (L->nfwd > 0 && L->nbwd > 0) { + sfail(t, + "wasm: label has both forward and backward gotos; cfree's CFG " + "structurer does not split hybrid labels in v1"); + } + L->valid = 1; + if (L->nfwd > 0) { + L->is_loop = 0; + L->open_idx = L->fwd_start_idx; + L->close_idx = L->wir_idx; + } else { + L->is_loop = 1; + L->open_idx = L->wir_idx; + L->close_idx = L->bwd_end_idx; + } + } +} + +/* ---- pass 5: emit event list and validate well-nesting. ---- */ + +static void push_event(WSCtx* x, u32 wir_idx, u32 pair_idx, u8 kind, + u32 scope_id, Label label) { + Heap* h = x->t->c->ctx->heap; + if (x->nevents == x->events_cap) { + u32 nc = x->events_cap ? x->events_cap * 2u : 16u; + void* p = h->realloc(h, x->events, sizeof(WSEvent) * x->events_cap, + sizeof(WSEvent) * nc, _Alignof(WSEvent)); + if (!p) sfail(x->t, "wasm: out of memory in structurer"); + x->events = (WSEvent*)p; + x->events_cap = nc; + } + WSEvent* e = &x->events[x->nevents++]; + e->wir_idx = wir_idx; + e->pair_idx = pair_idx; + e->kind = kind; + e->scope_id = scope_id; + e->label = label; +} + +static int ev_cmp(const WSEvent* a, const WSEvent* b) { + if (a->wir_idx != b->wir_idx) return a->wir_idx < b->wir_idx ? -1 : 1; + /* At a tie: closes fire before opens (so we close existing scopes before + * opening new ones). CLOSE kinds are 0..1, OPEN kinds are 2..3. */ + int a_close = a->kind < WSEV_OPEN_BLOCK; + int b_close = b->kind < WSEV_OPEN_BLOCK; + if (a_close != b_close) return a_close ? -1 : 1; + if (a_close) { + /* CLOSE: innermost first. pair_idx = open_idx; the more-recently-opened + * scope has a LATER open_idx, so larger pair_idx closes first. */ + if (a->pair_idx != b->pair_idx) + return a->pair_idx > b->pair_idx ? -1 : 1; + return 0; + } + /* OPEN: outer first. pair_idx = close_idx; the larger range (later close) + * encloses the smaller, so larger pair_idx opens first. */ + if (a->pair_idx != b->pair_idx) return a->pair_idx > b->pair_idx ? -1 : 1; + return 0; +} + +static void ev_sort(WSEvent* arr, u32 n) { + /* Small n — insertion sort. */ + for (u32 i = 1; i < n; ++i) { + WSEvent k = arr[i]; + u32 j = i; + while (j > 0 && ev_cmp(&arr[j - 1u], &k) > 0) { + arr[j] = arr[j - 1u]; + --j; + } + arr[j] = k; + } +} + +/* Resolve overlapping synthetic ranges into a properly-nested set. + * + * Close positions are FIXED — they correspond to the label's natural WIR + * placement (for forward labels) or the last back-edge end (for loop + * headers). Moving a close would change where a br to the label lands. + * + * So we only adjust OPEN positions, pushing them backward as needed: if + * range A's close happens inside range B (a_o < b_o < a_c < b_c), pull + * B's open back to A's open so A nests inside B (both open at a_o; A's + * inner close at a_c fires before B's outer close at b_c). + * + * Iterate to a fixed point since extending one range can introduce new + * overlaps with a third. The number of distinct opens is bounded by the + * label count, so iteration terminates in O(nlbls^2). */ +static void merge_ranges(WSCtx* x) { + for (;;) { + int changed = 0; + for (u32 i = 0; i < x->nlbls; ++i) { + if (!x->lbls[i].valid) continue; + for (u32 j = 0; j < x->nlbls; ++j) { + if (j == i || !x->lbls[j].valid) continue; + WSLbl* a = &x->lbls[i]; + WSLbl* b = &x->lbls[j]; + u32 a_o = a->open_idx, a_c = a->close_idx; + u32 b_o = b->open_idx, b_c = b->close_idx; + /* Disjoint or nested — fine. */ + if (a_c <= b_o || b_c <= a_o) continue; + if (a_o <= b_o && b_c <= a_c) continue; + if (b_o <= a_o && a_c <= b_c) continue; + /* Overlap. Two cases: + * a_o < b_o < a_c < b_c -> pull b's open back to a_o + * b_o < a_o < b_c < a_c -> handled by the j-then-i symmetric + * iteration. + */ + if (a_o < b_o && b_o < a_c && a_c < b_c) { + b->open_idx = a_o; + changed = 1; + } + } + } + if (!changed) break; + } +} + +static void pass_events(WSCtx* x) { + WTarget* t = x->t; + for (u32 i = 0; i < x->nlbls; ++i) { + WSLbl* L = &x->lbls[i]; + if (!L->valid) continue; + u32 scope_id = ++t->next_scope_id; + if (L->is_loop) { + push_event(x, L->open_idx, L->close_idx, WSEV_OPEN_LOOP, scope_id, + L->label); + push_event(x, L->close_idx, L->open_idx, WSEV_CLOSE_LOOP, scope_id, + L->label); + } else { + push_event(x, L->open_idx, L->close_idx, WSEV_OPEN_BLOCK, scope_id, + L->label); + push_event(x, L->close_idx, L->open_idx, WSEV_CLOSE_BLOCK, scope_id, + L->label); + } + /* Re-bind the WLabel. */ + WLabel* wl = &t->labels[L->label - 1u]; + wl->scope_id = scope_id; + wl->kind = L->is_loop ? WLBL_SCOPE_CONT : WLBL_SCOPE_BREAK; + } + ev_sort(x->events, x->nevents); +} + +/* ---- pass 6: rebuild the WIR list, splicing in synthetic scope opens + * and closes at their event positions. ---- */ + +static WIR* wir_append(WTarget* t, WIR** out, u32* nout, u32* cap) { + Heap* h = t->c->ctx->heap; + if (*nout == *cap) { + u32 nc = *cap ? *cap * 2u : 64u; + void* p = h->realloc(h, *out, sizeof(WIR) * *cap, sizeof(WIR) * nc, + _Alignof(WIR)); + if (!p) sfail(t, "wasm: out of memory in structurer"); + *out = (WIR*)p; + *cap = nc; + } + WIR* w = &(*out)[(*nout)++]; + memset(w, 0, sizeof *w); + return w; +} + +static void emit_synthetic_scope(WTarget* t, WIR** out, u32* nout, u32* cap, + u8 ev_kind, u32 scope_id) { + WIR* w = wir_append(t, out, nout, cap); + if (ev_kind == WSEV_OPEN_BLOCK || ev_kind == WSEV_OPEN_LOOP) { + w->op = WIR_SCOPE_OPEN; + w->cgop = (ev_kind == WSEV_OPEN_LOOP) ? SCOPE_LOOP : SCOPE_BLOCK; + } else { + w->op = WIR_SCOPE_CLOSE; + } + w->scope_id = scope_id; + w->dst = REG_NONE; +} + +static void pass_rewrite(WSCtx* x) { + WTarget* t = x->t; + WIR* new_wir = NULL; + u32 new_n = 0; + u32 new_cap = 0; + u32 ev_i = 0; + for (u32 i = 0; i <= t->nwir; ++i) { + /* Fire all events whose wir_idx == i (closes first, then opens). */ + while (ev_i < x->nevents && x->events[ev_i].wir_idx == i) { + emit_synthetic_scope(t, &new_wir, &new_n, &new_cap, x->events[ev_i].kind, + x->events[ev_i].scope_id); + ev_i++; + } + if (i == t->nwir) break; + WIR* src = &t->wir[i]; + WIR* dst = wir_append(t, &new_wir, &new_n, &new_cap); + *dst = *src; + } + if (ev_i != x->nevents) + sfail(t, "wasm: structurer left %u events unplaced", x->nevents - ev_i); + /* Swap. */ + Heap* h = t->c->ctx->heap; + if (t->wir) h->free(h, t->wir, sizeof(WIR) * t->wir_cap); + t->wir = new_wir; + t->nwir = new_n; + t->wir_cap = new_cap; +} + +/* ---- entry point ---- */ + +void wasm_structurize(WTarget* t) { + if (t->nwir == 0) return; + Heap* h = t->c->ctx->heap; + WSCtx x; + memset(&x, 0, sizeof x); + x.t = t; + unroll_switch_islands(&x); + pass_scope_depth(&x); + pass_preds(&x); + pass_decide(&x); + if (x.nlbls > 0) { + int any = 0; + for (u32 i = 0; i < x.nlbls; ++i) + if (x.lbls[i].valid) { any = 1; break; } + if (any) { + merge_ranges(&x); + pass_events(&x); + pass_rewrite(&x); + } + } + if (x.depth_at) h->free(h, x.depth_at, sizeof(u32) * x.ndepth); + if (x.lbls) h->free(h, x.lbls, sizeof(WSLbl) * x.lbls_cap); + if (x.events) h->free(h, x.events, sizeof(WSEvent) * x.events_cap); +} diff --git a/src/arch/wasm/target.c b/src/arch/wasm/target.c @@ -0,0 +1,293 @@ +/* Wasm CGTarget construction and method wiring. + * + * See doc/WASM.md §"Wasm as Target". The target writes a WasmModule attached + * to the ObjBuilder under OBJ_EXT_WASM; emit_wasm flushes it via wasm_encode. + * Methods that are not yet implemented panic with a precise message so users + * see exactly which Wasm-target feature is still pending. */ + +#include "arch/wasm/internal.h" + +#include <string.h> + +#include "core/heap.h" + +/* Real implementations in emit.c. */ +void wasm_func_begin(CGTarget*, const CGFuncDesc*); +void wasm_func_end(CGTarget*); +void wasm_alias(CGTarget*, ObjSymId, ObjSymId, CfreeCgTypeId); +void wasm_ret(CGTarget*, const CGABIValue*); +void wasm_load_imm(CGTarget*, Operand, i64); +void wasm_load_const(CGTarget*, Operand, ConstBytes); +void wasm_copy(CGTarget*, Operand, Operand); +void wasm_binop(CGTarget*, BinOp, Operand, Operand, Operand); +void wasm_unop(CGTarget*, UnOp, Operand, Operand); +void wasm_cmp(CGTarget*, CmpOp, Operand, Operand, Operand); +void wasm_convert(CGTarget*, ConvKind, Operand, Operand); +void wasm_call(CGTarget*, const CGCallDesc*); +const char* wasm_tail_call_unrealizable_reason(CGTarget*, const CGCallDesc*); +CGLocalStorage wasm_param(CGTarget*, const CGParamDesc*); +CGLocalStorage wasm_local(CGTarget*, const CGLocalDesc*); +FrameSlot wasm_frame_slot(CGTarget*, const FrameSlotDesc*); +void wasm_load(CGTarget*, Operand, Operand, MemAccess); +void wasm_store(CGTarget*, Operand, Operand, MemAccess); +void wasm_addr_of(CGTarget*, Operand, Operand); +void wasm_alloca(CGTarget*, Operand, Operand, u32); +void wasm_copy_bytes(CGTarget*, Operand, Operand, AggregateAccess); +void wasm_set_bytes(CGTarget*, Operand, Operand, AggregateAccess); +void wasm_va_start(CGTarget*, Operand); +void wasm_va_arg(CGTarget*, Operand, Operand, CfreeCgTypeId); +void wasm_va_end(CGTarget*, Operand); +void wasm_va_copy(CGTarget*, Operand, Operand); +void wasm_atomic_load(CGTarget*, Operand, Operand, MemAccess, MemOrder); +void wasm_atomic_store(CGTarget*, Operand, Operand, MemAccess, MemOrder); +void wasm_atomic_rmw(CGTarget*, AtomicOp, Operand, Operand, Operand, MemAccess, + MemOrder); +void wasm_atomic_cas(CGTarget*, Operand, Operand, Operand, Operand, Operand, + MemAccess, MemOrder, MemOrder); +void wasm_fence(CGTarget*, MemOrder); +void wasm_intrinsic(CGTarget*, IntrinKind, Operand*, u32, const Operand*, u32); +void wasm_asm_block(CGTarget*, const char*, const AsmConstraint*, u32, Operand*, + const AsmConstraint*, u32, const Operand*, const Sym*, u32); +void wasm_file_scope_asm(CGTarget*, const char*, size_t); +Label wasm_label_new(CGTarget*); +void wasm_label_place(CGTarget*, Label); +void wasm_jump(CGTarget*, Label); +void wasm_cmp_branch(CGTarget*, CmpOp, Operand, Operand, Label); +void wasm_switch(CGTarget*, const CGSwitchDesc*); +CGScope wasm_scope_begin(CGTarget*, const CGScopeDesc*); +void wasm_scope_else(CGTarget*, CGScope); +void wasm_scope_end(CGTarget*, CGScope); +void wasm_break_to(CGTarget*, CGScope); +void wasm_continue_to(CGTarget*, CGScope); +void wasm_set_loc(CGTarget*, SrcLoc); +void wasm_finalize(CGTarget*); +void wasm_destroy(CGTarget*); + +#define WASM_UNIMPL(name) \ + compiler_panic(((WTarget*)t)->c, \ + ((WTarget*)t)->cur_fn_desc ? ((WTarget*)t)->cur_fn_desc->loc \ + : (SrcLoc){0, 0, 0}, \ + "wasm target: " name " not yet implemented") + +static void unimpl_known_frame(CGTarget* t, const CGFuncDesc* f, + const CGKnownFrameDesc* k, FrameSlot* out) { + (void)f; (void)k; (void)out; WASM_UNIMPL("func_begin_known_frame"); +} +static void unimpl_local_addr(CGTarget* t, Operand op, const CGLocalDesc* d, + CGLocalStorage s) { + (void)op; (void)d; (void)s; WASM_UNIMPL("local_addr (address-taken locals)"); +} +static void unimpl_spill_reg(CGTarget* t, Operand a, FrameSlot s, + MemAccess m) { + (void)a; (void)s; (void)m; WASM_UNIMPL("spill_reg"); +} +static void unimpl_reload_reg(CGTarget* t, Operand a, FrameSlot s, + MemAccess m) { + (void)a; (void)s; (void)m; WASM_UNIMPL("reload_reg"); +} +static void no_regs(CGTarget* t, RegClass cls, const Reg** out, u32* n) { + (void)t; (void)cls; *out = NULL; *n = 0; +} +static void no_phys_regs(CGTarget* t, RegClass cls, const CGPhysRegInfo** out, + u32* n) { + (void)t; (void)cls; *out = NULL; *n = 0; +} +static int no_caller_saved(CGTarget* t, RegClass cls, Reg r) { + (void)t; (void)cls; (void)r; return 0; +} +static u32 zero_mask(CGTarget* t, const CGCallDesc* d, RegClass cls) { + (void)t; (void)d; (void)cls; return 0; +} +static u32 zero_ret_mask(CGTarget* t, const ABIFuncInfo* f, RegClass cls) { + (void)t; (void)f; (void)cls; return 0; +} +static u32 zero_cs_mask(CGTarget* t, RegClass cls) { + (void)t; (void)cls; return 0; +} +static void noop_plan_regs(CGTarget* t, RegClass cls, const Reg* r, u32 n) { + (void)t; (void)cls; (void)r; (void)n; +} +static void noop_reserve_regs(CGTarget* t, RegClass cls, const Reg* r, u32 n) { + (void)t; (void)cls; (void)r; (void)n; +} +static u32 zero_call_stack(CGTarget* t, const CGCallDesc* d) { + (void)t; (void)d; return 0; +} + +static void unimpl_plan_call(CGTarget* t, const CGCallDesc* d, CGCallPlan* p) { + (void)d; (void)p; WASM_UNIMPL("plan_call"); +} +static void unimpl_load_call_arg(CGTarget* t, Operand d, + const CGCallPlanMove* m) { + (void)d; (void)m; WASM_UNIMPL("load_call_arg"); +} +static void unimpl_store_call_arg(CGTarget* t, const CGCallPlanMove* m) { + (void)m; WASM_UNIMPL("store_call_arg"); +} +static void unimpl_store_call_ret(CGTarget* t, const CGCallPlanRet* r, + Operand s) { + (void)r; (void)s; WASM_UNIMPL("store_call_ret"); +} +static void unimpl_emit_call_plan(CGTarget* t, const CGCallPlan* p) { + (void)p; WASM_UNIMPL("emit_call_plan"); +} + +static void unimpl_tls_addr_of(CGTarget* t, Operand d, ObjSymId s, i64 a) { + (void)d; (void)s; (void)a; WASM_UNIMPL("tls_addr_of"); +} +static void unimpl_bf_load(CGTarget* t, Operand d, Operand r, + BitFieldAccess a) { + (void)d; (void)r; (void)a; WASM_UNIMPL("bitfield_load"); +} +static void unimpl_bf_store(CGTarget* t, Operand r, Operand s, + BitFieldAccess a) { + (void)r; (void)s; (void)a; WASM_UNIMPL("bitfield_store"); +} +static void unimpl_indirect_branch(CGTarget* t, Operand a, const Label* l, + u32 n) { + (void)a; (void)l; (void)n; WASM_UNIMPL("indirect_branch (computed goto)"); +} +static void unimpl_load_label_addr(CGTarget* t, Operand d, Label l) { + (void)d; (void)l; WASM_UNIMPL("load_label_addr (&&label)"); +} +static int unimpl_lsdb(CGTarget* t, const CGLocalStaticDataDesc* d) { + (void)t; (void)d; return 0; /* opt out — CG falls back to TU-wide data emission */ +} +/* The TU-wide data path (cg/data.c) routes function-local-label addresses + * inside static-data initializers through the MCEmitter. The Wasm backend + * has no native concept of an absolute label address resolvable at link + * time the way ELF/Mach-O do, so we short-circuit before MCEmitter with a + * wasm-prefixed diagnostic that the test runner's SKIP regex recognizes. */ +static const char* wasm_data_label_addr_unsupported_msg(CGTarget* t) { + (void)t; + return "wasm target: &&label addresses in static-data initializers are " + "not yet supported"; +} +static void unimpl_lsdw(CGTarget* t, const u8* d, u64 n) { + (void)d; (void)n; WASM_UNIMPL("local_static_data_write"); +} +static void unimpl_lsdl(CGTarget* t, Label l, i64 a, u32 w, u32 sp) { + (void)l; (void)a; (void)w; (void)sp; + WASM_UNIMPL("local_static_data_label_addr"); +} +static void unimpl_lsde(CGTarget* t) { WASM_UNIMPL("local_static_data_end"); } +/* wasm_intrinsic, wasm_asm_block, wasm_file_scope_asm live in emit.c — they + * need wir_push / wir_capture_operand and the linearization context. */ + +static MCLabel cg_label_to_mc_label_identity(CGTarget* t, Label l) { + (void)t; + return (MCLabel)l; +} + +static void cleanup_target(void* arg) { cgtarget_free((CGTarget*)arg); } + +CGTarget* wasm_cgtarget_new(Compiler* c, ObjBuilder* o, MCEmitter* mc) { + Heap* h = (Heap*)c->ctx->heap; + WTarget* x = (WTarget*)h->alloc(h, sizeof *x, _Alignof(WTarget)); + if (!x) return NULL; + memset(x, 0, sizeof *x); + x->c = c; + x->obj = o; + /* MCEmitter is allocated by cg_session but unused by the wasm target — we + * keep it on the base so CGTarget invariants (mc set) hold for callers + * that read CGTarget.mc, but never write through it. */ + (void)mc; + + CGTarget* t = &x->base; + t->c = c; + t->obj = o; + t->mc = mc; + t->virtual_regs = 1; + + t->func_begin = wasm_func_begin; + t->func_begin_known_frame = unimpl_known_frame; + t->func_end = wasm_func_end; + t->alias = wasm_alias; + + t->frame_slot = wasm_frame_slot; + t->local = wasm_local; + t->local_addr = unimpl_local_addr; + t->param = wasm_param; + t->spill_reg = unimpl_spill_reg; + t->reload_reg = unimpl_reload_reg; + + t->get_allocable_regs = no_regs; + t->get_phys_regs = no_phys_regs; + t->get_scratch_regs = no_regs; + t->is_caller_saved = no_caller_saved; + t->call_clobber_mask = zero_mask; + t->return_reg_mask = zero_ret_mask; + t->callee_save_mask = zero_cs_mask; + t->plan_hard_regs = noop_plan_regs; + t->reserve_hard_regs = noop_reserve_regs; + t->call_stack_size = zero_call_stack; + + t->label_new = wasm_label_new; + t->label_place = wasm_label_place; + t->cg_label_to_mc_label = cg_label_to_mc_label_identity; + t->jump = wasm_jump; + t->cmp_branch = wasm_cmp_branch; + t->switch_ = wasm_switch; + t->indirect_branch = unimpl_indirect_branch; + t->load_label_addr = unimpl_load_label_addr; + t->local_static_data_begin = unimpl_lsdb; + t->local_static_data_write = unimpl_lsdw; + t->local_static_data_label_addr = unimpl_lsdl; + t->local_static_data_end = unimpl_lsde; + t->data_label_addr_unsupported_msg = wasm_data_label_addr_unsupported_msg; + t->scope_begin = wasm_scope_begin; + t->scope_else = wasm_scope_else; + t->scope_end = wasm_scope_end; + t->break_to = wasm_break_to; + t->continue_to = wasm_continue_to; + + t->load_imm = wasm_load_imm; + t->load_const = wasm_load_const; + t->copy = wasm_copy; + t->load = wasm_load; + t->store = wasm_store; + t->addr_of = wasm_addr_of; + t->tls_addr_of = unimpl_tls_addr_of; + t->copy_bytes = wasm_copy_bytes; + t->set_bytes = wasm_set_bytes; + t->bitfield_load = unimpl_bf_load; + t->bitfield_store = unimpl_bf_store; + + t->binop = wasm_binop; + t->unop = wasm_unop; + t->cmp = wasm_cmp; + t->convert = wasm_convert; + + t->call = wasm_call; + t->tail_call_unrealizable_reason = wasm_tail_call_unrealizable_reason; + t->plan_call = unimpl_plan_call; + t->load_call_arg = unimpl_load_call_arg; + t->store_call_arg = unimpl_store_call_arg; + t->store_call_ret = unimpl_store_call_ret; + t->emit_call_plan = unimpl_emit_call_plan; + t->ret = wasm_ret; + + t->alloca_ = wasm_alloca; + t->va_start_ = wasm_va_start; + t->va_arg_ = wasm_va_arg; + t->va_end_ = wasm_va_end; + t->va_copy_ = wasm_va_copy; + + t->atomic_load = wasm_atomic_load; + t->atomic_store = wasm_atomic_store; + t->atomic_rmw = wasm_atomic_rmw; + t->atomic_cas = wasm_atomic_cas; + t->fence = wasm_fence; + + t->intrinsic = wasm_intrinsic; + t->asm_block = wasm_asm_block; + t->file_scope_asm = wasm_file_scope_asm; + t->resolve_reg_name = NULL; + + t->set_loc = wasm_set_loc; + t->finalize = wasm_finalize; + t->destroy = wasm_destroy; + + compiler_defer(c, cleanup_target, t); + return t; +} diff --git a/src/arch/wasm/wasm_imports.c b/src/arch/wasm/wasm_imports.c @@ -0,0 +1,104 @@ +/* Wasm import-attribute side table. Stored on the ObjBuilder under + * OBJ_EXT_WASM_IMPORTS so the C frontend (lang/c) can populate it from + * `__attribute__((import_module/import_name))` at decl_declare time before + * the wasm backend exists, and so the backend can read it back from the + * single shared ObjBuilder when promoting undefined function symbols to + * `(import ...)` declarations. */ + +#include "arch/wasm/wasm_imports.h" + +#include <string.h> + +#include "core/core.h" +#include "core/heap.h" + +typedef struct WasmImportEntry { + Sym name; + Sym import_module; + Sym import_name; +} WasmImportEntry; + +typedef struct WasmImportTable { + Heap* heap; + WasmImportEntry* entries; + u32 count; + u32 cap; +} WasmImportTable; + +static void wasm_imports_free(Compiler* c, void* p) { + (void)c; + WasmImportTable* t = (WasmImportTable*)p; + if (!t) return; + if (t->entries) t->heap->free(t->heap, t->entries, + sizeof(*t->entries) * t->cap); + t->heap->free(t->heap, t, sizeof(*t)); +} + +static WasmImportTable* table_ensure(ObjBuilder* o) { + WasmImportTable* t = (WasmImportTable*)obj_ext_get(o, OBJ_EXT_WASM_IMPORTS); + if (t) return t; + Compiler* c = obj_compiler(o); + Heap* h = c->ctx->heap; + t = (WasmImportTable*)h->alloc(h, sizeof(*t), _Alignof(WasmImportTable)); + if (!t) return NULL; + memset(t, 0, sizeof *t); + t->heap = h; + obj_ext_set(o, OBJ_EXT_WASM_IMPORTS, t, wasm_imports_free); + return t; +} + +static WasmImportEntry* table_find_mut(WasmImportTable* t, Sym name) { + for (u32 i = 0; i < t->count; ++i) { + if (t->entries[i].name == name) return &t->entries[i]; + } + return NULL; +} + +static const WasmImportEntry* table_find(const WasmImportTable* t, Sym name) { + for (u32 i = 0; i < t->count; ++i) { + if (t->entries[i].name == name) return &t->entries[i]; + } + return NULL; +} + +void wasm_imports_set(ObjBuilder* o, Sym name, Sym import_module, + Sym import_name) { + if (!o || name == 0) return; + if (import_module == 0 && import_name == 0) return; + WasmImportTable* t = table_ensure(o); + if (!t) return; + WasmImportEntry* e = table_find_mut(t, name); + if (e) { + if (import_module) e->import_module = import_module; + if (import_name) e->import_name = import_name; + return; + } + if (t->count == t->cap) { + u32 nc = t->cap ? t->cap * 2u : 8u; + void* p = t->heap->realloc(t->heap, t->entries, + sizeof(*t->entries) * t->cap, + sizeof(*t->entries) * nc, + _Alignof(WasmImportEntry)); + if (!p) return; + t->entries = (WasmImportEntry*)p; + t->cap = nc; + } + t->entries[t->count].name = name; + t->entries[t->count].import_module = import_module; + t->entries[t->count].import_name = import_name; + t->count++; +} + +int wasm_imports_get(const ObjBuilder* o, Sym name, Sym* import_module, + Sym* import_name) { + if (import_module) *import_module = 0; + if (import_name) *import_name = 0; + if (!o || name == 0) return 0; + WasmImportTable* t = (WasmImportTable*)obj_ext_get(o, OBJ_EXT_WASM_IMPORTS); + if (!t) return 0; + const WasmImportEntry* e = table_find(t, name); + if (!e) return 0; + if (import_module) *import_module = e->import_module; + if (import_name) *import_name = e->import_name; + return 1; +} diff --git a/src/arch/wasm/wasm_imports.h b/src/arch/wasm/wasm_imports.h @@ -0,0 +1,23 @@ +#ifndef CFREE_ARCH_WASM_IMPORTS_H +#define CFREE_ARCH_WASM_IMPORTS_H + +/* Side table keyed by C-level symbol name carrying wasm import overrides + * (`__attribute__((import_module(...), import_name(...)))`). Lives on the + * ObjBuilder under OBJ_EXT_WASM_IMPORTS — populated by the C frontend at + * decl_declare time, consumed by the wasm backend when promoting an + * undefined function symbol into a wasm `(import ...)` declaration. + * + * Either Sym may be 0; the backend treats 0 as "use default" (module="env", + * name=<linkage spelling>). */ + +#include "obj/obj.h" + +void wasm_imports_set(ObjBuilder* o, Sym name, Sym import_module, + Sym import_name); + +/* Returns 1 and writes the import module/name out-params (each 0 when unset) + * when an entry exists; returns 0 otherwise. */ +int wasm_imports_get(const ObjBuilder* o, Sym name, Sym* import_module, + Sym* import_name); + +#endif diff --git a/src/cg/asm.c b/src/cg/asm.c @@ -306,6 +306,13 @@ void cfree_cg_file_scope_asm(CfreeCg* g, CfreeSlice asm_source) { AsmLexer* lex; if (!g || !asm_source.s) return; if (g->check_only) return; + /* Target-level override (e.g. wasm) takes precedence — those backends + * have no native asm parser and diagnose explicitly. */ + if (g->target && g->target->file_scope_asm) { + api_local_const_memory_boundary(g); + g->target->file_scope_asm(g->target, asm_source.s, asm_source.len); + return; + } /* The C-source backend bypasses MCEmitter entirely; file-scope asm has * no portable C source equivalent (Phase 4 territory — see * doc/CBACKEND.md). Panic with the same shape as other unimplemented diff --git a/src/cg/control.c b/src/cg/control.c @@ -458,7 +458,8 @@ void api_scope_push_result(CfreeCg* g, ApiCgScope* s) { api_push(g, api_make_sv(dst, s->result_type)); } -CfreeCgScope cfree_cg_scope_begin(CfreeCg* g, CfreeCgTypeId result_type) { +static CfreeCgScope api_scope_begin_kind(CfreeCg* g, u8 kind, + CfreeCgTypeId result_type) { Label break_lbl, cont_lbl; CGScopeDesc d; ApiCgScope* s; @@ -466,9 +467,11 @@ CfreeCgScope cfree_cg_scope_begin(CfreeCg* g, CfreeCgTypeId result_type) { u32 idx; if (!g) return 0; break_lbl = g->target->label_new(g->target); - cont_lbl = g->target->label_new(g->target); + cont_lbl = (kind == SCOPE_LOOP) ? g->target->label_new(g->target) + : LABEL_NONE; api_local_const_control_boundary(g); - g->target->label_place(g->target, cont_lbl); + if (cont_lbl != LABEL_NONE) + g->target->label_place(g->target, cont_lbl); if (g->nscopes >= API_CG_MAX_SCOPES) { compiler_panic(g->c, g->cur_loc, "CfreeCg: too many nested scopes"); @@ -485,7 +488,7 @@ CfreeCgScope cfree_cg_scope_begin(CfreeCg* g, CfreeCgTypeId result_type) { g->nscopes++; memset(&d, 0, sizeof d); - d.kind = (u8)SCOPE_LOOP; + d.kind = kind; d.break_label = break_lbl; d.continue_label = cont_lbl; d.result_type = s->result_type; @@ -505,6 +508,14 @@ CfreeCgScope cfree_cg_scope_begin(CfreeCg* g, CfreeCgTypeId result_type) { return api_scope_handle(idx, s->generation); } +CfreeCgScope cfree_cg_scope_begin(CfreeCg* g, CfreeCgTypeId result_type) { + return api_scope_begin_kind(g, (u8)SCOPE_LOOP, result_type); +} + +CfreeCgScope cfree_cg_block_begin(CfreeCg* g, CfreeCgTypeId result_type) { + return api_scope_begin_kind(g, (u8)SCOPE_BLOCK, result_type); +} + CfreeCgLabel cfree_cg_scope_break_label(CfreeCg* g, CfreeCgScope scope) { ApiCgScope* s = api_scope_from_handle(g, scope, 0, "CfreeCg: scope_break_label"); @@ -801,6 +812,220 @@ void cfree_cg_memset(CfreeCg* g, uint8_t val, uint64_t size, api_release(g, &dst); } +void cfree_cg_index(CfreeCg* g, uint64_t offset) { + ApiSValue idx, base; + CGTarget* T; + CfreeCgTypeId base_ty, base_ptr_ty, elem_ty, idx_ty; + const CgType* base_info; + u32 elemsz; + int free_base_op = 0; + Operand base_op, idx_op, result; + Reg rr; + if (!g) return; + if (offset > INT64_MAX) { + compiler_panic(g->c, g->cur_loc, "CfreeCg: index offset too large"); + return; + } + T = g->target; + idx = api_pop(g); + base = api_pop(g); + api_ensure_reg(g, &base); + base_ty = api_sv_type(&base); + base_info = cg_type_get(g->c, base_ty); + if (base_info && base_info->kind == CFREE_CG_TYPE_PTR) { + elem_ty = base_info->ptr.pointee; + base_ptr_ty = base_ty; + } else if (base_info && base_info->kind == CFREE_CG_TYPE_ARRAY && + api_is_lvalue_sv(&base)) { + elem_ty = base_info->array.elem; + base_ptr_ty = cg_type_ptr_to(g->c, elem_ty); + } else { + compiler_panic(g->c, g->cur_loc, + "CfreeCg: index base is not a pointer or array lvalue"); + return; + } + elemsz = (u32)abi_cg_sizeof(g->c->abi, elem_ty); + idx_ty = idx.type ? idx.type : idx.op.type; + if (!idx_ty) idx_ty = builtin_id(CFREE_CG_BUILTIN_I32); + if (base_info && base_info->kind == CFREE_CG_TYPE_ARRAY) { + rr = api_alloc_reg_or_spill(g, RC_INT, base_ptr_ty); + base_op = api_op_reg(rr, base_ptr_ty); + T->addr_of(T, base_op, base.op); + api_release(g, &base); + free_base_op = 1; + } else { + base_op = api_force_reg(g, &base, base_ptr_ty); + } + idx_op = api_force_reg_unless_imm(g, &idx, idx_ty); + rr = api_alloc_reg_or_spill(g, RC_INT, base_ptr_ty); + result = api_op_reg(rr, base_ptr_ty); + if (idx_op.kind == OPK_IMM) { + i64 total_offset = idx_op.v.imm * (i64)elemsz + (i64)offset; + T->binop(T, BO_IADD, result, base_op, + api_op_imm(total_offset, base_ptr_ty)); + } else { + Reg sr = api_alloc_reg_or_spill(g, RC_INT, idx_ty); + Operand scaled = api_op_reg(sr, idx_ty); + /* Allocating `scaled` can materialize a delayed index expression into a + * fresh virtual register under opt. Refresh idx_op so the multiply uses + * the materialized value, not the pre-materialization source operand. */ + idx_op = api_force_reg_unless_imm(g, &idx, idx_ty); + if (idx.op.kind == OPK_REG) idx_op = idx.op; + T->binop(T, BO_IMUL, scaled, idx_op, api_op_imm((i64)elemsz, idx_ty)); + if (offset > 0) { + T->binop(T, BO_IADD, scaled, scaled, api_op_imm((i64)offset, idx_ty)); + } + /* The final IADD takes operands with the pointer's integer width. Match + * the scaled index to the base pointer width before adding. */ + u32 idx_sz = (u32)abi_cg_sizeof(g->c->abi, idx_ty); + u32 ptr_sz = (u32)abi_cg_sizeof(g->c->abi, base_ptr_ty); + if (idx_sz && ptr_sz && idx_sz > ptr_sz) { + Reg narrow_r = api_alloc_reg_or_spill(g, RC_INT, base_ptr_ty); + Operand narrow = api_op_reg(narrow_r, base_ptr_ty); + T->convert(T, CV_TRUNC, narrow, scaled); + T->binop(T, BO_IADD, result, base_op, narrow); + api_free_reg(g, narrow_r, RC_INT); + } else if (idx_sz && ptr_sz && idx_sz < ptr_sz) { + Reg wide_r = api_alloc_reg_or_spill(g, RC_INT, base_ptr_ty); + Operand wide = api_op_reg(wide_r, base_ptr_ty); + T->convert(T, CV_ZEXT, wide, scaled); + T->binop(T, BO_IADD, result, base_op, wide); + api_free_reg(g, wide_r, RC_INT); + } else { + T->binop(T, BO_IADD, result, base_op, scaled); + } + api_free_reg(g, sr, RC_INT); + } + if (free_base_op) api_free_reg(g, base_op.v.reg, RC_INT); + if (!base_info || base_info->kind != CFREE_CG_TYPE_ARRAY) + api_release(g, &base); + api_release(g, &idx); + api_push(g, api_make_lv(api_op_indirect(result.v.reg, 0, elem_ty), elem_ty)); +} + +void cfree_cg_field(CfreeCg* g, uint32_t field_index) { + ApiSValue base; + CGTarget* T; + CfreeCgTypeId rec_ty; + CfreeCgTypeId base_ty; + CfreeCgTypeId field_ty; + CfreeCgTypeId rec_ptr_ty; + const CgType* base_info; + const CgType* rec_info; + const ABIRecordLayout* layout; + u32 field_offset; + int base_is_lvalue; + Operand result; + Reg rr; + if (!g) return; + T = g->target; + base = api_pop(g); + api_ensure_reg(g, &base); + base_ty = api_sv_type(&base); + base_is_lvalue = api_is_lvalue_sv(&base); + if (base_is_lvalue) { + rec_ty = base_ty; + } else { + base_info = cg_type_get(g->c, base_ty); + if (!base_info || base_info->kind != CFREE_CG_TYPE_PTR) { + compiler_panic(g->c, g->cur_loc, + "CfreeCg: field base is not an lvalue or record pointer"); + api_release(g, &base); + return; + } + rec_ty = base_info->ptr.pointee; + } + rec_ptr_ty = base_is_lvalue ? cg_type_ptr_to(g->c, rec_ty) : base_ty; + layout = abi_cg_record_layout(g->c->abi, rec_ty); + if (!layout || field_index >= layout->nfields) { + compiler_panic(g->c, g->cur_loc, "CfreeCg: invalid field index"); + return; + } + rec_info = cg_type_get(g->c, rec_ty); + if (!rec_info || rec_info->kind != CFREE_CG_TYPE_RECORD || + field_index >= rec_info->record.nfields) { + compiler_panic(g->c, g->cur_loc, "CfreeCg: invalid record base"); + return; + } + field_ty = rec_info->record.fields[field_index].type; + field_offset = layout->fields[field_index].offset; + if (layout->fields[field_index].bit_width != 0 || + (rec_info->record.fields[field_index].flags & CFREE_CG_FIELD_BITFIELD) != + 0) { + Operand base_addr; + ApiSValue sv; + BitFieldAccess bf; + if (layout->fields[field_index].bit_width == 0) { + compiler_panic(g->c, g->cur_loc, "CfreeCg: zero-width bit-field access"); + api_release(g, &base); + return; + } + if (!base_is_lvalue) { + compiler_panic(g->c, g->cur_loc, + "CfreeCg: pointer field bit-fields are not supported"); + api_release(g, &base); + return; + } + base_addr = api_lvalue_addr(g, &base, rec_ptr_ty); + memset(&bf, 0, sizeof bf); + bf.field_type = field_ty; + bf.storage = api_mem_for_lvalue(g, &base_addr, field_ty); + bf.storage.size = layout->fields[field_index].storage_size; + bf.storage_offset = layout->fields[field_index].offset; + bf.bit_offset = layout->fields[field_index].bit_offset; + bf.bit_width = layout->fields[field_index].bit_width; + bf.signed_ = rec_info->record.fields[field_index].bit_signed != 0; + sv = api_make_lv(base_addr, field_ty); + sv.bitfield_lvalue = 1; + sv.delayed.bitfield = bf; + api_release(g, &base); + api_push(g, sv); + return; + } + if (!base_is_lvalue) { + Operand base_op = api_force_reg(g, &base, rec_ptr_ty); + if (field_offset == 0) { + result = base_op; + base.res = RES_INHERENT; + } else { + rr = api_alloc_reg_or_spill(g, RC_INT, rec_ptr_ty); + result = api_op_reg(rr, rec_ptr_ty); + T->binop(T, BO_IADD, result, base_op, + api_op_imm((i64)field_offset, rec_ptr_ty)); + api_release(g, &base); + } + api_push(g, + api_make_lv(api_op_indirect(result.v.reg, 0, field_ty), field_ty)); + } else if (base.op.kind == OPK_GLOBAL) { + result = + api_op_global(base.op.v.global.sym, + base.op.v.global.addend + (i64)field_offset, field_ty); + api_push(g, api_make_lv(result, field_ty)); + } else if (base.op.kind == OPK_INDIRECT && field_offset <= (u32)INT32_MAX && + base.op.v.ind.ofs <= INT32_MAX - (i32)field_offset) { + result = api_op_indirect(base.op.v.ind.base, + base.op.v.ind.ofs + (i32)field_offset, field_ty); + api_push(g, api_make_lv(result, field_ty)); + } else { + Operand base_addr; + rr = api_alloc_reg_or_spill(g, RC_INT, rec_ptr_ty); + base_addr = api_op_reg(rr, rec_ptr_ty); + T->addr_of(T, base_addr, base.op); + api_release(g, &base); + if (field_offset == 0) { + result = base_addr; + } else { + Reg fr = api_alloc_reg_or_spill(g, RC_INT, rec_ptr_ty); + result = api_op_reg(fr, rec_ptr_ty); + T->binop(T, BO_IADD, result, base_addr, + api_op_imm((i64)field_offset, rec_ptr_ty)); + api_free_reg(g, base_addr.v.reg, RC_INT); + } + api_push(g, + api_make_lv(api_op_indirect(result.v.reg, 0, field_ty), field_ty)); + } +} + /* ============================================================ * Calls / return * ============================================================ */ diff --git a/src/cg/data.c b/src/cg/data.c @@ -427,6 +427,14 @@ void cfree_cg_data_label_addr(CfreeCg* g, CfreeCgLabel target, int64_t addend, g->data_size += width; return; } + if (g->target && g->target->data_label_addr_unsupported_msg) { + const char* msg = + g->target->data_label_addr_unsupported_msg(g->target); + if (msg) { + compiler_panic(g->c, g->cur_loc, "%s", msg); + return; + } + } if (!g->mc) { /* The C-source target has no MCEmitter and can't emit a relocation * that resolves to an intra-function label address: GCC's `&&L` diff --git a/src/cg/session.c b/src/cg/session.c @@ -6,6 +6,8 @@ #include "opt/opt.h" #endif +#include "arch/wasm/wasm_imports.h" + static void cg_free_obj_state(CfreeCg* g) { Heap* h; u32 i; @@ -190,6 +192,16 @@ CfreeCgSym cfree_cg_decl(CfreeCg* g, CfreeCgDecl decl) { obj_symbol_set_flags(ob, sym, (u16)decl.sym.flags); } api_remember_sym(g, sym, ty, decl); + /* Forward wasm import overrides onto the ObjBuilder side-table so the wasm + * backend can find them when promoting an undefined function symbol into a + * `(import ...)` entry. Honored only by the wasm target — the storage cost + * for other backends is one heap allocation that no one reads. */ + if (decl.kind == CFREE_CG_DECL_FUNC && + (decl.as.func.wasm_import_module || decl.as.func.wasm_import_name)) { + wasm_imports_set(ob, (Sym)decl.linkage_name, + (Sym)decl.as.func.wasm_import_module, + (Sym)decl.as.func.wasm_import_name); + } return (CfreeCgSym)sym; } diff --git a/src/core/core.h b/src/core/core.h @@ -121,6 +121,14 @@ struct CfreeCompiler { const CfreeFrontendVTable* frontends[CFREE_LANG_COUNT]; void* cg_api; void (*cg_api_free)(Compiler*); + /* Wasm frontend host-import configuration. Stashed by + * cfree_wasm_set_host_imports; consumed by runners that call + * cfree_wasm_bind_host_imports after the link image is produced. The + * pointers are borrowed: callers own the lifetime. */ + const void* wasm_host_imports; /* CfreeWasmHostImport array */ + size_t wasm_host_nimports; + void* wasm_host_resolve; /* CfreeWasmResolveFn */ + void* wasm_host_user; /* Keep jmp_buf last: its size comes from the including C environment * (host libc for some tests, rt/include for libcfree), and must not shift * the offsets of the fields above across those builds. */ diff --git a/src/obj/obj.c b/src/obj/obj.c @@ -20,6 +20,13 @@ SEGVEC_DEFINE(Symbols, ObjSym, 6); /* 64 entries per segment */ SEGVEC_DEFINE(Relocs, Reloc, 6); /* 64 entries per segment */ SEGVEC_DEFINE(Groups, ObjGroup, 3); /* 8 entries per segment */ +#define OBJ_EXT_SLOT_COUNT 6 /* OBJ_EXT_NONE..OBJ_EXT_WASM_IMPORTS */ + +typedef struct ObjExtSlot { + void* payload; + ObjExtFreeFn free_fn; +} ObjExtSlot; + struct CfreeObjBuilder { Compiler* c; Heap* heap; @@ -42,6 +49,8 @@ struct CfreeObjBuilder { * obj_define_tls when emitting `_Thread_local` storage on Mach-O. * Lazily materialized on the first TLV emission; OBJ_SYM_NONE otherwise. */ ObjSymId tlv_bootstrap_sym; + /* Format-specific extension payloads keyed by ObjExtKind. */ + ObjExtSlot ext[OBJ_EXT_SLOT_COUNT]; }; struct ObjSymIter { @@ -87,6 +96,13 @@ void obj_tlv_bootstrap_set(ObjBuilder* ob, ObjSymId id) { void obj_free(ObjBuilder* ob) { u32 i, n; if (!ob) return; + for (i = 0; i < OBJ_EXT_SLOT_COUNT; ++i) { + if (ob->ext[i].payload && ob->ext[i].free_fn) { + ob->ext[i].free_fn(ob->c, ob->ext[i].payload); + } + ob->ext[i].payload = NULL; + ob->ext[i].free_fn = NULL; + } n = Sections_count(&ob->sections); for (i = 1; i < n; ++i) { Section* s = Sections_at(&ob->sections, i); @@ -130,6 +146,31 @@ int obj_get_coff_import_dll(const ObjBuilder* ob, Sym* out) { return 1; } +void obj_ext_set(ObjBuilder* ob, ObjExtKind kind, void* payload, + ObjExtFreeFn free_fn) { + if (!ob || (u32)kind >= OBJ_EXT_SLOT_COUNT) return; + if (ob->ext[kind].payload && ob->ext[kind].free_fn && + ob->ext[kind].payload != payload) { + ob->ext[kind].free_fn(ob->c, ob->ext[kind].payload); + } + ob->ext[kind].payload = payload; + ob->ext[kind].free_fn = free_fn; +} + +void* obj_ext_get(const ObjBuilder* ob, ObjExtKind kind) { + if (!ob || (u32)kind >= OBJ_EXT_SLOT_COUNT) return NULL; + return ob->ext[kind].payload; +} + +void obj_ext_clear(ObjBuilder* ob, ObjExtKind kind) { + if (!ob || (u32)kind >= OBJ_EXT_SLOT_COUNT) return; + if (ob->ext[kind].payload && ob->ext[kind].free_fn) { + ob->ext[kind].free_fn(ob->c, ob->ext[kind].payload); + } + ob->ext[kind].payload = NULL; + ob->ext[kind].free_fn = NULL; +} + /* ---- write side ---- */ ObjSecId obj_section(ObjBuilder* ob, Sym name, SecKind kind, u16 flags, diff --git a/src/obj/obj.h b/src/obj/obj.h @@ -78,6 +78,11 @@ typedef enum ObjExtKind { OBJ_EXT_COFF, OBJ_EXT_MACHO, OBJ_EXT_WASM, + /* Wasm-target frontend-supplied import descriptors keyed by symbol name. + * Populated by lang/c when an extern declaration carries + * __attribute__((import_module(...), import_name(...))); consumed by the + * wasm backend when promoting undefined function symbols to imports. */ + OBJ_EXT_WASM_IMPORTS, } ObjExtKind; typedef u32 ObjSecId; @@ -655,6 +660,17 @@ void obj_format_demangle_c(const Compiler*, const char** name, size_t* len); * as a NUL-terminated literal; the caller interns. */ const char* obj_format_default_entry_name(const Compiler*); +/* ---- format-specific extension payload ---- + * + * Generic object tables stay format-neutral. Format-specific module-level + * metadata (today: only the in-progress Wasm module model) hangs off the + * builder under an ObjExtKind tag. One payload per kind. ObjBuilder owns the + * pointer's lifetime — obj_free invokes the registered free function. */ +typedef void (*ObjExtFreeFn)(Compiler*, void*); +void obj_ext_set(ObjBuilder*, ObjExtKind, void* payload, ObjExtFreeFn); +void* obj_ext_get(const ObjBuilder*, ObjExtKind); +void obj_ext_clear(ObjBuilder*, ObjExtKind); + /* ---- file format emitters ---- */ void emit_elf(Compiler*, ObjBuilder*, Writer*); void emit_coff(Compiler*, ObjBuilder*, Writer*); diff --git a/src/obj/wasm_emit.c b/src/obj/wasm_emit.c @@ -0,0 +1,24 @@ +/* emit_wasm: Wasm module/object emitter. + * + * For v1 the wasm CGTarget builds a WasmModule incrementally and attaches it + * to the ObjBuilder under OBJ_EXT_WASM. emit_wasm flushes that module via + * the existing wasm_encode writer. When no module is attached (e.g. an + * ObjBuilder produced by an .o reader or an empty TU), an empty module + * header is written so downstream tools see a syntactically valid file. */ + +#include "core/core.h" +#include "obj/obj.h" +#include "wasm/wasm.h" + +void emit_wasm(Compiler* c, ObjBuilder* o, Writer* w) { + WasmModule* m = (WasmModule*)obj_ext_get(o, OBJ_EXT_WASM); + if (m) { + wasm_encode(c, m, w); + return; + } + /* Empty module: magic + version. wasm_encode emits the same header even + * for an empty WasmModule, but we avoid allocating one just to drop it. */ + static const uint8_t magic[] = {0x00, 0x61, 0x73, 0x6d, + 0x01, 0x00, 0x00, 0x00}; + w->write(w, magic, sizeof magic); +} diff --git a/src/opt/opt.c b/src/opt/opt.c @@ -536,6 +536,11 @@ static int w_resolve_reg_name(CGTarget* t, Sym name, Reg* out, return 1; } +static void w_file_scope_asm(CGTarget* t, const char* src, size_t len) { + CGTarget* wr = impl_of(t)->target; + if (wr->file_scope_asm) wr->file_scope_asm(wr, src, len); +} + /* ---- labels and control flow ---- */ static Label w_label_new(CGTarget* t) { @@ -1679,6 +1684,11 @@ CGTarget* opt_cgtarget_new(Compiler* c, CGTarget* target, int level) { t->intrinsic = w_intrinsic; t->asm_block = w_asm_block; t->resolve_reg_name = w_resolve_reg_name; + /* Only expose file_scope_asm if the wrapped target overrides it. Native + * targets leave it NULL so cfree_cg_file_scope_asm falls through to the + * MC-based asm_parse path; setting the wrapper unconditionally would + * swallow that NULL signal and silently drop file-scope asm. */ + if (target->file_scope_asm) t->file_scope_asm = w_file_scope_asm; t->set_loc = w_set_loc; t->finalize = w_finalize; diff --git a/src/wasm/decode.c b/src/wasm/decode.c @@ -0,0 +1,1345 @@ +#include "wasm/wasm.h" + +typedef struct BinReader { + CfreeCompiler* c; + const uint8_t* data; + size_t len; + size_t pos; + WasmModule* module; +} BinReader; + +static uint8_t bin_u8(BinReader* r) { + if (r->pos >= r->len) + wasm_error(r->c, wasm_loc(0, 0), "wasm: unexpected end of file"); + return r->data[r->pos++]; +} + +static uint32_t bin_uleb(BinReader* r) { + uint32_t result = 0, shift = 0; + uint32_t nbytes = 0; + for (;;) { + uint8_t b = bin_u8(r); + if (nbytes++ >= 5u || (shift == 28u && (b & 0xf0u))) + wasm_error(r->c, wasm_loc(0, 0), "wasm: invalid uleb128"); + result |= (uint32_t)(b & 0x7fu) << shift; + if (!(b & 0x80u)) return result; + shift += 7u; + } +} + +static uint64_t bin_uleb64(BinReader* r) { + uint64_t result = 0; + uint32_t shift = 0; + uint32_t nbytes = 0; + for (;;) { + uint8_t b = bin_u8(r); + if (nbytes++ >= 10u || (shift == 63u && (b & 0xfeu))) + wasm_error(r->c, wasm_loc(0, 0), "wasm: invalid uleb128"); + result |= (uint64_t)(b & 0x7fu) << shift; + if (!(b & 0x80u)) return result; + shift += 7u; + } +} + +static int64_t bin_sleb(BinReader* r, uint32_t bits) { + int64_t result = 0; + uint32_t shift = 0; + uint32_t max_bytes = (bits + 6u) / 7u; + uint32_t nbytes = 0; + uint8_t b; + do { + if (nbytes++ >= max_bytes) + wasm_error(r->c, wasm_loc(0, 0), "wasm: invalid sleb128"); + b = bin_u8(r); + result |= (int64_t)(b & 0x7fu) << shift; + shift += 7u; + } while (b & 0x80u); + if (shift < bits && (b & 0x40u)) result |= -((int64_t)1 << shift); + return result; +} + +static double bin_f32(BinReader* r) { + uint32_t bits = 0; + float f; + for (uint32_t i = 0; i < 4u; ++i) bits |= (uint32_t)bin_u8(r) << (i * 8u); + memcpy(&f, &bits, sizeof f); + return (double)f; +} + +static double bin_f64(BinReader* r) { + uint64_t bits = 0; + double d; + for (uint32_t i = 0; i < 8u; ++i) bits |= (uint64_t)bin_u8(r) << (i * 8u); + memcpy(&d, &bits, sizeof d); + return d; +} + +static void bin_memarg(BinReader* r, uint32_t* align, uint64_t* offset, + uint32_t* memidx) { + uint32_t a = bin_uleb(r); + *memidx = 0; + if (a >= 64u && a < 128u) { + *align = a - 64u; + *memidx = bin_uleb(r); + } else if (a < 64u) { + *align = a; + } else { + wasm_error(r->c, wasm_loc(0, 0), "wasm: bad memory alignment"); + } + *offset = bin_uleb64(r); +} + +static void bin_need(BinReader* r, size_t n) { + if (n > r->len || r->pos > r->len - n) + wasm_error(r->c, wasm_loc(0, 0), "wasm: section length out of bounds"); +} + +static char* bin_name(BinReader* r, uint32_t* len_out) { + uint32_t n = bin_uleb(r); + char* name; + bin_need(r, n); + name = wasm_strdup(r->module->heap, (const char*)(r->data + r->pos), n); + if (!name) wasm_error(r->c, wasm_loc(0, 0), "wasm: out of memory"); + r->pos += n; + if (len_out) *len_out = n; + return name; +} + +static WasmValType bin_val_type(BinReader* r, int refs_ok) { + uint8_t b = bin_u8(r); + switch (b) { + case WASM_VAL_I32: + case WASM_VAL_I64: + case WASM_VAL_F32: + case WASM_VAL_F64: + return (WasmValType)b; + case WASM_VAL_FUNCREF: + case WASM_VAL_EXTERNREF: + if (refs_ok) return (WasmValType)b; + break; + default: + break; + } + wasm_error(r->c, wasm_loc(0, 0), "wasm: unsupported value type 0x%02x", b); + return WASM_VAL_I32; +} + +void wasm_decode_binary(CfreeCompiler* c, const CfreeSlice* input, + WasmModule* out) { + BinReader r; + uint32_t nfunc_types = 0; + uint8_t last_id = 0; + memset(&r, 0, sizeof r); + r.c = c; + r.data = input->data; + r.len = input->len; + r.module = out; + if (r.len < 8 || memcmp(r.data, "\0asm\1\0\0\0", 8) != 0) + wasm_error(c, wasm_loc(0, 0), "wasm: bad magic or version"); + r.pos = 8; + while (r.pos < r.len) { + uint8_t id = bin_u8(&r); + uint32_t size = bin_uleb(&r); + size_t end; + bin_need(&r, size); + end = r.pos + size; + if (id != 0 && id <= last_id) + wasm_error(c, wasm_loc(0, 0), "wasm: sections out of order"); + if (id != 0) last_id = id; + if (id == 0) { + uint32_t name_len = bin_uleb(&r); + bin_need(&r, name_len); + if (name_len == 7u && memcmp(r.data + r.pos, "linking", 7) == 0) + wasm_error(c, wasm_loc(0, 0), + "wasm: relocatable object metadata is not frontend input"); + { + WasmCustom* cs = wasm_add_custom(c, out); + cs->name = wasm_strdup(out->heap, (const char*)(r.data + r.pos), + name_len); + if (!cs->name) wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); + r.pos += name_len; + if (cfree_slice_eq_cstr(cfree_slice_cstr(cs->name), "target_features") || + cfree_slice_eq_cstr(cfree_slice_cstr(cs->name), "target-feature")) + out->has_target_features = 1; + cs->len = (uint32_t)(end - r.pos); + if (cs->len) { + cs->data = (uint8_t*)out->heap->alloc(out->heap, cs->len, 1); + if (!cs->data) wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); + memcpy(cs->data, r.data + r.pos, cs->len); + } + } + r.pos = end; + continue; + } + 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; + if (bin_u8(&r) != 0x60u) + wasm_error(c, wasm_loc(0, 0), "wasm: expected function type"); + nparam = bin_uleb(&r); + for (j = 0; j < nparam; ++j) + wasm_type_push_param(c, out, t, bin_val_type(&r, 1)); + nresult = bin_uleb(&r); + if (nresult > 1u) + wasm_error(c, wasm_loc(0, 0), "wasm: multi-result unsupported"); + t->nresults = nresult; + for (j = 0; j < nresult; ++j) t->results[j] = bin_val_type(&r, 1); + } + } else if (id == 2) { + uint32_t i, count = bin_uleb(&r); + for (i = 0; i < count; ++i) { + char* mod = bin_name(&r, NULL); + char* name = bin_name(&r, NULL); + uint8_t kind = bin_u8(&r); + if (kind == 0) { + uint32_t typeidx = bin_uleb(&r); + WasmFunc* f; + if (typeidx >= out->ntypes) + wasm_error(c, wasm_loc(0, 0), "wasm: bad import type index"); + f = wasm_add_func(c, out); + f->is_import = 1; + f->import_module = mod; + f->import_name = name; + f->typeidx = typeidx; + f->has_typeidx = 1; + wasm_func_set_params(c, out, f, out->types[typeidx].params, + out->types[typeidx].nparams); + f->nresults = out->types[typeidx].nresults; + memcpy(f->results, out->types[typeidx].results, + sizeof(f->results[0]) * f->nresults); + } else if (kind == 1) { + WasmTable* t = wasm_add_table(c, out); + t->is_import = 1; + t->import_module = mod; + t->import_name = name; + t->elem_type = bin_val_type(&r, 1); + { + uint32_t flags = bin_uleb(&r); + if (flags & ~1u) + wasm_error(c, wasm_loc(0, 0), "wasm: unsupported table limits"); + t->has_max = (flags & 1u) != 0; + t->min = bin_uleb(&r); + if (t->has_max) t->max = bin_uleb(&r); + } + } else if (kind == 2) { + uint32_t flags; + WasmMemory* mem = wasm_add_memory(c, out); + mem->is_import = 1; + mem->import_module = mod; + mem->import_name = name; + flags = bin_uleb(&r); + if (flags & ~7u) + wasm_error(c, wasm_loc(0, 0), "wasm: unsupported memory limits"); + mem->has_max = (flags & 1u) != 0; + mem->shared = (flags & 2u) != 0; + mem->is64 = (flags & 4u) != 0; + mem->min_pages = mem->is64 ? bin_uleb64(&r) : bin_uleb(&r); + if (mem->has_max) + mem->max_pages = mem->is64 ? bin_uleb64(&r) : bin_uleb(&r); + } else if (kind == 3) { + WasmGlobal* g = wasm_add_global(c, out); + g->is_import = 1; + g->import_module = mod; + g->import_name = name; + g->type = bin_val_type(&r, 0); + g->mutable_ = bin_u8(&r); + if (g->mutable_ > 1u) + wasm_error(c, wasm_loc(0, 0), "wasm: bad global mutability"); + } else { + wasm_error(c, wasm_loc(0, 0), "wasm: unsupported import kind"); + } + } + } 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; + if (typeidx >= out->ntypes) + wasm_error(c, wasm_loc(0, 0), "wasm: bad function type index"); + f = wasm_add_func(c, out); + f->typeidx = typeidx; + f->has_typeidx = 1; + wasm_func_set_params(c, out, f, out->types[typeidx].params, + out->types[typeidx].nparams); + f->nresults = out->types[typeidx].nresults; + memcpy(f->results, out->types[typeidx].results, + sizeof(f->results[0]) * f->nresults); + nfunc_types++; + } + } else if (id == 5) { + uint32_t count = bin_uleb(&r); + for (uint32_t mi = 0; mi < count; ++mi) { + uint32_t flags = bin_uleb(&r); + WasmMemory* mem = wasm_add_memory(c, out); + if (flags & ~7u) + wasm_error(c, wasm_loc(0, 0), "wasm: unsupported memory limits"); + mem->has_max = (flags & 1u) != 0; + mem->shared = (flags & 2u) != 0; + mem->is64 = (flags & 4u) != 0; + mem->min_pages = mem->is64 ? bin_uleb64(&r) : bin_uleb(&r); + if (mem->has_max) + mem->max_pages = mem->is64 ? bin_uleb64(&r) : bin_uleb(&r); + } + } else if (id == 4) { + uint32_t i, count = bin_uleb(&r); + for (i = 0; i < count; ++i) { + WasmTable* t = wasm_add_table(c, out); + uint32_t flags; + t->elem_type = bin_val_type(&r, 1); + flags = bin_uleb(&r); + if (flags & ~1u) + wasm_error(c, wasm_loc(0, 0), "wasm: unsupported table limits"); + t->has_max = (flags & 1u) != 0; + t->min = bin_uleb(&r); + if (t->has_max) t->max = bin_uleb(&r); + } + } else if (id == 6) { + uint32_t i, count = bin_uleb(&r); + for (i = 0; i < count; ++i) { + WasmGlobal* g = wasm_add_global(c, out); + uint8_t op; + g->type = bin_val_type(&r, 0); + g->mutable_ = bin_u8(&r); + if (g->mutable_ > 1u) + wasm_error(c, wasm_loc(0, 0), "wasm: bad global mutability"); + op = bin_u8(&r); + switch (op) { + case 0x41: + g->init.kind = WASM_INSN_I32_CONST; + g->init.imm = bin_sleb(&r, 32); + break; + case 0x42: + g->init.kind = WASM_INSN_I64_CONST; + g->init.imm = bin_sleb(&r, 64); + break; + case 0x43: + g->init.kind = WASM_INSN_F32_CONST; + g->init.fp = bin_f32(&r); + break; + case 0x44: + g->init.kind = WASM_INSN_F64_CONST; + g->init.fp = bin_f64(&r); + break; + default: + wasm_error(c, wasm_loc(0, 0), + "wasm: unsupported global initializer"); + } + if (bin_u8(&r) != 0x0bu) + wasm_error(c, wasm_loc(0, 0), "wasm: malformed global initializer"); + } + } else if (id == 7) { + uint32_t i, count = bin_uleb(&r); + for (i = 0; i < count; ++i) { + char* name; + uint8_t kind; + uint32_t idx; + WasmExport* ex; + name = bin_name(&r, NULL); + kind = bin_u8(&r); + idx = bin_uleb(&r); + if (kind > 3u) wasm_error(c, wasm_loc(0, 0), "wasm: bad export kind"); + ex = wasm_add_export(c, out); + ex->name = name; + ex->kind = kind; + ex->index = idx; + if (kind == 0 && idx < out->nfuncs) { + out->funcs[idx].export_name = + wasm_strdup(out->heap, name, cfree_slice_cstr(name).len); + } else if (kind == 1 && idx < out->ntables) { + out->tables[idx].export_name = + wasm_strdup(out->heap, name, cfree_slice_cstr(name).len); + } else if (kind == 2 && idx < out->nmemories) { + out->memories[idx].export_name = + wasm_strdup(out->heap, name, cfree_slice_cstr(name).len); + } else if (kind == 3 && idx < out->nglobals) { + out->globals[idx].export_name = + wasm_strdup(out->heap, name, cfree_slice_cstr(name).len); + } + } + } else if (id == 8) { + out->has_start = 1; + out->start_func = bin_uleb(&r); + } else if (id == 9) { + uint32_t i, count = bin_uleb(&r); + for (i = 0; i < count; ++i) { + WasmElemSegment* e = wasm_add_elem(c, out); + uint32_t flags = bin_uleb(&r); + uint32_t n; + e->elem_type = WASM_VAL_FUNCREF; + if (flags == 0u) { + e->mode = WASM_SEG_ACTIVE; + e->tableidx = 0; + if (bin_u8(&r) != 0x41) + wasm_error(c, wasm_loc(0, 0), + "wasm: expected i32.const element offset"); + e->offset = bin_sleb(&r, 32); + if (bin_u8(&r) != 0x0b || e->offset < 0) + wasm_error(c, wasm_loc(0, 0), "wasm: bad element offset"); + } else if (flags == 1u) { + e->mode = WASM_SEG_PASSIVE; + if (bin_u8(&r) != 0x00) + wasm_error(c, wasm_loc(0, 0), + "wasm: unsupported passive element kind"); + } else if (flags == 2u) { + e->mode = WASM_SEG_ACTIVE; + e->tableidx = bin_uleb(&r); + if (bin_u8(&r) != 0x41) + wasm_error(c, wasm_loc(0, 0), + "wasm: expected i32.const element offset"); + e->offset = bin_sleb(&r, 32); + if (bin_u8(&r) != 0x0b || e->offset < 0) + wasm_error(c, wasm_loc(0, 0), "wasm: bad element offset"); + if (bin_u8(&r) != 0x00) + wasm_error(c, wasm_loc(0, 0), + "wasm: unsupported element kind"); + } else if (flags == 3u) { + e->mode = WASM_SEG_DECLARATIVE; + if (bin_u8(&r) != 0x00) + wasm_error(c, wasm_loc(0, 0), + "wasm: unsupported declarative element kind"); + } else { + wasm_error(c, wasm_loc(0, 0), + "wasm: unsupported element segment flags 0x%x", flags); + } + n = bin_uleb(&r); + for (uint32_t k = 0; k < n; ++k) + wasm_elem_push_func(c, out, e, bin_uleb(&r)); + } + } else if (id == 10) { + uint32_t i, count = bin_uleb(&r); + uint32_t func_index = 0; + if (count != nfunc_types) + wasm_error(c, wasm_loc(0, 0), "wasm: function/code count mismatch"); + for (i = 0; i < count; ++i) { + uint32_t body_size = bin_uleb(&r); + size_t body_end; + uint32_t local_groups, j; + uint32_t control_depth = 0; + int saw_body_end = 0; + WasmFunc* f; + while (func_index < out->nfuncs && out->funcs[func_index].is_import) + func_index++; + if (func_index >= out->nfuncs) + wasm_error(c, wasm_loc(0, 0), "wasm: code body without function"); + f = &out->funcs[func_index++]; + bin_need(&r, body_size); + body_end = r.pos + body_size; + local_groups = bin_uleb(&r); + for (j = 0; j < local_groups; ++j) { + uint32_t k, nlocals = bin_uleb(&r); + WasmValType vt = bin_val_type(&r, 1); + for (k = 0; k < nlocals; ++k) + wasm_func_push_local(c, out, f, vt); + } + while (r.pos < body_end) { + uint8_t op = bin_u8(&r); + if (op == 0x0bu) { + if (!control_depth) { + saw_body_end = 1; + break; + } + control_depth--; + wasm_func_add_insn(c, out, f, WASM_INSN_END, 0); + continue; + } + switch (op) { + case 0x00: + wasm_func_add_insn(c, out, f, WASM_INSN_UNREACHABLE, 0); + break; + case 0x01: + wasm_func_add_insn(c, out, f, WASM_INSN_NOP, 0); + break; + case 0x02: + if (bin_u8(&r) != 0x40u) + wasm_error(c, wasm_loc(0, 0), + "wasm: block results are unsupported"); + control_depth++; + wasm_func_add_insn(c, out, f, WASM_INSN_BLOCK, 0); + break; + case 0x03: + if (bin_u8(&r) != 0x40u) + wasm_error(c, wasm_loc(0, 0), + "wasm: loop results are unsupported"); + control_depth++; + wasm_func_add_insn(c, out, f, WASM_INSN_LOOP, 0); + break; + case 0x04: + if (bin_u8(&r) != 0x40u) + wasm_error(c, wasm_loc(0, 0), + "wasm: if results are unsupported"); + control_depth++; + wasm_func_add_insn(c, out, f, WASM_INSN_IF, 0); + break; + case 0x05: + wasm_func_add_insn(c, out, f, WASM_INSN_ELSE, 0); + break; + case 0x0c: + wasm_func_add_insn(c, out, f, WASM_INSN_BR, bin_uleb(&r)); + break; + case 0x0d: + wasm_func_add_insn(c, out, f, WASM_INSN_BR_IF, bin_uleb(&r)); + break; + case 0x0e: { + WasmInsn* in; + uint32_t n = bin_uleb(&r); + if (n >= 64u) + wasm_error(c, wasm_loc(0, 0), + "wasm: too many br_table targets"); + wasm_func_add_insn(c, out, f, WASM_INSN_BR_TABLE, 0); + in = &f->insns[f->ninsns - 1u]; + for (uint32_t k = 0; k < n; ++k) in->targets[k] = bin_uleb(&r); + in->targets[n] = bin_uleb(&r); + in->ntargets = n + 1u; + break; + } + case 0x0f: + wasm_func_add_insn(c, out, f, WASM_INSN_RETURN, 0); + break; + case 0x1a: + wasm_func_add_insn(c, out, f, WASM_INSN_DROP, 0); + break; + case 0x1b: + wasm_func_add_insn(c, out, f, WASM_INSN_SELECT, 0); + break; + case 0x28: + { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_LOAD, ma, mo, mi); } + break; + case 0x29: + { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_LOAD, ma, mo, mi); } + break; + case 0x2a: + { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_F32_LOAD, ma, mo, mi); } + break; + case 0x2b: + { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_F64_LOAD, ma, mo, mi); } + break; + case 0x2c: + { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_LOAD8_S, ma, mo, mi); } + break; + case 0x2d: + { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_LOAD8_U, ma, mo, mi); } + break; + case 0x2e: + { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_LOAD16_S, ma, mo, mi); } + break; + case 0x2f: + { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_LOAD16_U, ma, mo, mi); } + break; + case 0x30: + { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_LOAD8_S, ma, mo, mi); } + break; + case 0x31: + { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_LOAD8_U, ma, mo, mi); } + break; + case 0x32: + { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_LOAD16_S, ma, mo, mi); } + break; + case 0x33: + { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_LOAD16_U, ma, mo, mi); } + break; + case 0x34: + { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_LOAD32_S, ma, mo, mi); } + break; + case 0x35: + { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_LOAD32_U, ma, mo, mi); } + break; + case 0x36: + { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_STORE, ma, mo, mi); } + break; + case 0x37: + { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_STORE, ma, mo, mi); } + break; + case 0x38: + { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_F32_STORE, ma, mo, mi); } + break; + case 0x39: + { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_F64_STORE, ma, mo, mi); } + break; + case 0x3a: + { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_STORE8, ma, mo, mi); } + break; + case 0x3b: + { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_STORE16, ma, mo, mi); } + break; + case 0x3c: + { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_STORE8, ma, mo, mi); } + break; + case 0x3d: + { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_STORE16, ma, mo, mi); } + break; + case 0x3e: + { uint32_t ma, mi; uint64_t mo; bin_memarg(&r, &ma, &mo, &mi); wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_STORE32, ma, mo, mi); } + break; + case 0x3f: + wasm_func_add_insn(c, out, f, WASM_INSN_MEMORY_SIZE, 0); + f->insns[f->ninsns - 1u].memidx = bin_uleb(&r); + break; + case 0x40: + wasm_func_add_insn(c, out, f, WASM_INSN_MEMORY_GROW, 0); + f->insns[f->ninsns - 1u].memidx = bin_uleb(&r); + break; + case 0x10: + wasm_func_add_insn(c, out, f, WASM_INSN_CALL, bin_uleb(&r)); + break; + case 0x11: { + WasmInsn* in; + wasm_func_add_insn(c, out, f, WASM_INSN_CALL_INDIRECT, + bin_uleb(&r)); + in = &f->insns[f->ninsns - 1u]; + in->align = bin_uleb(&r); + break; + } + case 0x12: + wasm_func_add_insn(c, out, f, WASM_INSN_RETURN_CALL, + bin_uleb(&r)); + break; + case 0x13: { + WasmInsn* in; + wasm_func_add_insn(c, out, f, WASM_INSN_RETURN_CALL_INDIRECT, + bin_uleb(&r)); + in = &f->insns[f->ninsns - 1u]; + in->align = bin_uleb(&r); + break; + } + case 0x14: + wasm_func_add_insn(c, out, f, WASM_INSN_CALL_REF, + bin_uleb(&r)); + break; + case 0x15: + wasm_func_add_insn(c, out, f, WASM_INSN_RETURN_CALL_REF, + bin_uleb(&r)); + break; + case 0x20: + wasm_func_add_insn(c, out, f, WASM_INSN_LOCAL_GET, bin_uleb(&r)); + break; + case 0x21: + wasm_func_add_insn(c, out, f, WASM_INSN_LOCAL_SET, bin_uleb(&r)); + break; + case 0x22: + wasm_func_add_insn(c, out, f, WASM_INSN_LOCAL_TEE, bin_uleb(&r)); + break; + case 0x23: + wasm_func_add_insn(c, out, f, WASM_INSN_GLOBAL_GET, bin_uleb(&r)); + break; + case 0x24: + wasm_func_add_insn(c, out, f, WASM_INSN_GLOBAL_SET, bin_uleb(&r)); + break; + case 0x41: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_CONST, + bin_sleb(&r, 32)); + break; + case 0x42: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_CONST, + bin_sleb(&r, 64)); + break; + case 0x43: + wasm_func_add_fp_insn(c, out, f, WASM_INSN_F32_CONST, + bin_f32(&r)); + break; + case 0x44: + wasm_func_add_fp_insn(c, out, f, WASM_INSN_F64_CONST, + bin_f64(&r)); + break; + case 0xd0: + wasm_func_add_insn(c, out, f, WASM_INSN_REF_NULL, + bin_val_type(&r, 1)); + break; + case 0xd1: + wasm_func_add_insn(c, out, f, WASM_INSN_REF_IS_NULL, 0); + break; + case 0xd2: + wasm_func_add_insn(c, out, f, WASM_INSN_REF_FUNC, + bin_uleb(&r)); + break; + case 0x45: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_EQZ, 0); + break; + case 0x46: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_EQ, 0); + break; + case 0x47: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_NE, 0); + break; + case 0x48: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_LT_S, 0); + break; + case 0x49: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_LT_U, 0); + break; + case 0x4a: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_GT_S, 0); + break; + case 0x4b: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_GT_U, 0); + break; + case 0x4c: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_LE_S, 0); + break; + case 0x4d: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_LE_U, 0); + break; + case 0x4e: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_GE_S, 0); + break; + case 0x4f: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_GE_U, 0); + break; + case 0x50: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_EQZ, 0); + break; + case 0x51: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_EQ, 0); + break; + case 0x52: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_NE, 0); + break; + case 0x53: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_LT_S, 0); + break; + case 0x54: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_LT_U, 0); + break; + case 0x55: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_GT_S, 0); + break; + case 0x56: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_GT_U, 0); + break; + case 0x57: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_LE_S, 0); + break; + case 0x58: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_LE_U, 0); + break; + case 0x59: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_GE_S, 0); + break; + case 0x5a: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_GE_U, 0); + break; + case 0x6a: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_ADD, 0); + break; + case 0x6b: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_SUB, 0); + break; + case 0x6c: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_MUL, 0); + break; + case 0x6d: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_DIV_S, 0); + break; + case 0x6e: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_DIV_U, 0); + break; + case 0x6f: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_REM_S, 0); + break; + case 0x70: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_REM_U, 0); + break; + case 0x71: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_AND, 0); + break; + case 0x72: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_OR, 0); + break; + case 0x73: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_XOR, 0); + break; + case 0x74: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_SHL, 0); + break; + case 0x75: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_SHR_S, 0); + break; + case 0x76: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_SHR_U, 0); + break; + case 0x67: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_CLZ, 0); + break; + case 0x68: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_CTZ, 0); + break; + case 0x69: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_POPCNT, 0); + break; + case 0x77: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_ROTL, 0); + break; + case 0x78: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_ROTR, 0); + break; + case 0x79: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_CLZ, 0); + break; + case 0x7a: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_CTZ, 0); + break; + case 0x7b: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_POPCNT, 0); + break; + case 0x7c: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_ADD, 0); + break; + case 0x7d: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_SUB, 0); + break; + case 0x7e: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_MUL, 0); + break; + case 0x7f: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_DIV_S, 0); + break; + case 0x80: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_DIV_U, 0); + break; + case 0x81: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_REM_S, 0); + break; + case 0x82: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_REM_U, 0); + break; + case 0x83: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_AND, 0); + break; + case 0x84: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_OR, 0); + break; + case 0x85: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_XOR, 0); + break; + case 0x86: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_SHL, 0); + break; + case 0x87: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_SHR_S, 0); + break; + case 0x88: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_SHR_U, 0); + break; + case 0x89: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_ROTL, 0); + break; + case 0x8a: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_ROTR, 0); + break; + case 0x92: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_ADD, 0); + break; + case 0x93: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_SUB, 0); + break; + case 0x94: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_MUL, 0); + break; + case 0x95: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_DIV, 0); + break; + case 0xa0: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_ADD, 0); + break; + case 0xa1: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_SUB, 0); + break; + case 0xa2: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_MUL, 0); + break; + case 0xa3: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_DIV, 0); + break; + case 0x5b: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_EQ, 0); + break; + case 0x5c: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_NE, 0); + break; + case 0x5d: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_LT, 0); + break; + case 0x5e: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_GT, 0); + break; + case 0x5f: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_LE, 0); + break; + case 0x60: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_GE, 0); + break; + case 0x61: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_EQ, 0); + break; + case 0x62: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_NE, 0); + break; + case 0x63: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_LT, 0); + break; + case 0x64: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_GT, 0); + break; + case 0x65: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_LE, 0); + break; + case 0x66: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_GE, 0); + break; + case 0xa7: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_WRAP_I64, 0); + break; + case 0xa8: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_TRUNC_F32_S, 0); + break; + case 0xa9: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_TRUNC_F32_U, 0); + break; + case 0xaa: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_TRUNC_F64_S, 0); + break; + case 0xab: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_TRUNC_F64_U, 0); + break; + case 0xac: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_EXTEND_I32_S, 0); + break; + case 0xad: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_EXTEND_I32_U, 0); + break; + case 0xae: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_TRUNC_F32_S, 0); + break; + case 0xaf: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_TRUNC_F32_U, 0); + break; + case 0xb0: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_TRUNC_F64_S, 0); + break; + case 0xb1: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_TRUNC_F64_U, 0); + break; + case 0xb2: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_CONVERT_I32_S, 0); + break; + case 0xb3: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_CONVERT_I32_U, 0); + break; + case 0xb4: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_CONVERT_I64_S, 0); + break; + case 0xb5: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_CONVERT_I64_U, 0); + break; + case 0xb6: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_DEMOTE_F64, 0); + break; + case 0xb7: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_CONVERT_I32_S, 0); + break; + case 0xb8: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_CONVERT_I32_U, 0); + break; + case 0xb9: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_CONVERT_I64_S, 0); + break; + case 0xba: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_CONVERT_I64_U, 0); + break; + case 0xbb: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_PROMOTE_F32, 0); + break; + case 0xbc: + wasm_func_add_insn(c, out, f, WASM_INSN_I32_REINTERPRET_F32, 0); + break; + case 0xbd: + wasm_func_add_insn(c, out, f, WASM_INSN_I64_REINTERPRET_F64, 0); + break; + case 0xbe: + wasm_func_add_insn(c, out, f, WASM_INSN_F32_REINTERPRET_I32, 0); + break; + case 0xbf: + wasm_func_add_insn(c, out, f, WASM_INSN_F64_REINTERPRET_I64, 0); + break; + case 0xfc: { + uint32_t sub = bin_uleb(&r); + switch (sub) { + case 0x00: + wasm_func_add_insn(c, out, f, + WASM_INSN_I32_TRUNC_SAT_F32_S, 0); + break; + case 0x01: + wasm_func_add_insn(c, out, f, + WASM_INSN_I32_TRUNC_SAT_F32_U, 0); + break; + case 0x02: + wasm_func_add_insn(c, out, f, + WASM_INSN_I32_TRUNC_SAT_F64_S, 0); + break; + case 0x03: + wasm_func_add_insn(c, out, f, + WASM_INSN_I32_TRUNC_SAT_F64_U, 0); + break; + case 0x04: + wasm_func_add_insn(c, out, f, + WASM_INSN_I64_TRUNC_SAT_F32_S, 0); + break; + case 0x05: + wasm_func_add_insn(c, out, f, + WASM_INSN_I64_TRUNC_SAT_F32_U, 0); + break; + case 0x06: + wasm_func_add_insn(c, out, f, + WASM_INSN_I64_TRUNC_SAT_F64_S, 0); + break; + case 0x07: + wasm_func_add_insn(c, out, f, + WASM_INSN_I64_TRUNC_SAT_F64_U, 0); + break; + case 0x08: { /* memory.init dataidx memidx */ + uint32_t dataidx = bin_uleb(&r); + uint32_t mi = bin_uleb(&r); + wasm_func_add_insn(c, out, f, WASM_INSN_MEMORY_INIT, + (int64_t)dataidx); + f->insns[f->ninsns - 1u].memidx = mi; + break; + } + case 0x09: { /* data.drop dataidx */ + uint32_t dataidx = bin_uleb(&r); + wasm_func_add_insn(c, out, f, WASM_INSN_DATA_DROP, + (int64_t)dataidx); + break; + } + case 0x0a: { /* memory.copy dst_memidx src_memidx */ + uint32_t dst = bin_uleb(&r); + uint32_t src = bin_uleb(&r); + wasm_func_add_insn(c, out, f, WASM_INSN_MEMORY_COPY, 0); + f->insns[f->ninsns - 1u].memidx = dst; + f->insns[f->ninsns - 1u].aux_idx = src; + break; + } + case 0x0b: { /* memory.fill memidx */ + uint32_t mi = bin_uleb(&r); + wasm_func_add_insn(c, out, f, WASM_INSN_MEMORY_FILL, 0); + f->insns[f->ninsns - 1u].memidx = mi; + break; + } + case 0x0c: { /* table.init elemidx tableidx */ + uint32_t elemidx = bin_uleb(&r); + uint32_t ti = bin_uleb(&r); + wasm_func_add_insn(c, out, f, WASM_INSN_TABLE_INIT, + (int64_t)elemidx); + f->insns[f->ninsns - 1u].aux_idx = ti; + break; + } + case 0x0d: { /* elem.drop elemidx */ + uint32_t elemidx = bin_uleb(&r); + wasm_func_add_insn(c, out, f, WASM_INSN_ELEM_DROP, + (int64_t)elemidx); + break; + } + case 0x0e: { /* table.copy dst_tableidx src_tableidx */ + uint32_t dst = bin_uleb(&r); + uint32_t src = bin_uleb(&r); + wasm_func_add_insn(c, out, f, WASM_INSN_TABLE_COPY, + (int64_t)dst); + f->insns[f->ninsns - 1u].aux_idx = src; + break; + } + case 0x0f: { /* table.grow tableidx */ + uint32_t ti = bin_uleb(&r); + wasm_func_add_insn(c, out, f, WASM_INSN_TABLE_GROW, + (int64_t)ti); + break; + } + case 0x10: { /* table.size tableidx */ + uint32_t ti = bin_uleb(&r); + wasm_func_add_insn(c, out, f, WASM_INSN_TABLE_SIZE, + (int64_t)ti); + break; + } + case 0x11: { /* table.fill tableidx */ + uint32_t ti = bin_uleb(&r); + wasm_func_add_insn(c, out, f, WASM_INSN_TABLE_FILL, + (int64_t)ti); + break; + } + default: + wasm_error(c, wasm_loc(0, 0), + "wasm: unsupported 0xfc opcode 0x%x", sub); + } + break; + } + case 0xfe: { + uint32_t sub = bin_uleb(&r); + uint32_t ma, mi; + uint64_t mo; + switch (sub) { + case 0x00: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, + WASM_INSN_MEMORY_ATOMIC_NOTIFY, ma, + mo, mi); + break; + case 0x01: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_ATOMIC_WAIT, + ma, mo, mi); + break; + case 0x02: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_ATOMIC_WAIT, + ma, mo, mi); + break; + case 0x03: + if (bin_u8(&r) != 0) + wasm_error(c, wasm_loc(0, 0), "wasm: bad atomic.fence"); + wasm_func_add_insn(c, out, f, WASM_INSN_ATOMIC_FENCE, 0); + break; + case 0x10: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_ATOMIC_LOAD, + ma, mo, mi); + break; + case 0x11: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_ATOMIC_LOAD, + ma, mo, mi); + break; + case 0x12: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, + WASM_INSN_I32_ATOMIC_LOAD8_U, ma, mo, + mi); + break; + case 0x13: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, + WASM_INSN_I32_ATOMIC_LOAD16_U, ma, mo, + mi); + break; + case 0x14: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, + WASM_INSN_I64_ATOMIC_LOAD8_U, ma, mo, + mi); + break; + case 0x15: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, + WASM_INSN_I64_ATOMIC_LOAD16_U, ma, mo, + mi); + break; + case 0x16: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, + WASM_INSN_I64_ATOMIC_LOAD32_U, ma, mo, + mi); + break; + case 0x17: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_ATOMIC_STORE, + ma, mo, mi); + break; + case 0x18: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_ATOMIC_STORE, + ma, mo, mi); + break; + case 0x19: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, + WASM_INSN_I32_ATOMIC_STORE8, ma, mo, + mi); + break; + case 0x1a: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, + WASM_INSN_I32_ATOMIC_STORE16, ma, mo, + mi); + break; + case 0x1b: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, + WASM_INSN_I64_ATOMIC_STORE8, ma, mo, + mi); + break; + case 0x1c: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, + WASM_INSN_I64_ATOMIC_STORE16, ma, mo, + mi); + break; + case 0x1d: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, + WASM_INSN_I64_ATOMIC_STORE32, ma, mo, + mi); + break; + case 0x1e: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, + WASM_INSN_I32_ATOMIC_RMW_ADD, ma, mo, + mi); + break; + case 0x1f: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, + WASM_INSN_I64_ATOMIC_RMW_ADD, ma, mo, + mi); + break; + case 0x25: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, + WASM_INSN_I32_ATOMIC_RMW_SUB, ma, mo, + mi); + break; + case 0x26: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, + WASM_INSN_I64_ATOMIC_RMW_SUB, ma, mo, + mi); + break; + case 0x2c: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, + WASM_INSN_I32_ATOMIC_RMW_AND, ma, mo, + mi); + break; + case 0x2d: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, + WASM_INSN_I64_ATOMIC_RMW_AND, ma, mo, + mi); + break; + case 0x33: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, + WASM_INSN_I32_ATOMIC_RMW_OR, ma, mo, + mi); + break; + case 0x34: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, + WASM_INSN_I64_ATOMIC_RMW_OR, ma, mo, + mi); + break; + case 0x3a: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, + WASM_INSN_I32_ATOMIC_RMW_XOR, ma, mo, + mi); + break; + case 0x3b: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, + WASM_INSN_I64_ATOMIC_RMW_XOR, ma, mo, + mi); + break; + case 0x41: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, + WASM_INSN_I32_ATOMIC_RMW_XCHG, ma, mo, + mi); + break; + case 0x42: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, + WASM_INSN_I64_ATOMIC_RMW_XCHG, ma, mo, + mi); + break; + case 0x48: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, + WASM_INSN_I32_ATOMIC_RMW_CMPXCHG, ma, + mo, mi); + break; + case 0x49: + bin_memarg(&r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, + WASM_INSN_I64_ATOMIC_RMW_CMPXCHG, ma, + mo, mi); + break; + default: + wasm_error(c, wasm_loc(0, 0), + "wasm: unsupported threads opcode 0x%x", sub); + } + break; + } + default: + wasm_error(c, wasm_loc(0, 0), "wasm: unsupported opcode 0x%02x", + op); + } + } + if (!saw_body_end) + wasm_error(c, wasm_loc(0, 0), "wasm: function body missing end"); + r.pos = body_end; + } + } else if (id == 11) { + uint32_t i, count = bin_uleb(&r); + for (i = 0; i < count; ++i) { + WasmDataSegment* d = wasm_add_data(c, out); + uint32_t flags = bin_uleb(&r); + uint32_t n; + if (flags == 0u) { + d->mode = WASM_SEG_ACTIVE; + d->memidx = 0; + { + uint8_t op = bin_u8(&r); + if (op == 0x41) + d->offset = bin_sleb(&r, 32); + else if (op == 0x42) + d->offset = bin_sleb(&r, 64); + else + wasm_error(c, wasm_loc(0, 0), + "wasm: expected const data offset"); + } + if (bin_u8(&r) != 0x0b || d->offset < 0) + wasm_error(c, wasm_loc(0, 0), "wasm: bad data offset"); + } else if (flags == 1u) { + d->mode = WASM_SEG_PASSIVE; + } else if (flags == 2u) { + d->mode = WASM_SEG_ACTIVE; + d->memidx = bin_uleb(&r); + { + uint8_t op = bin_u8(&r); + if (op == 0x41) + d->offset = bin_sleb(&r, 32); + else if (op == 0x42) + d->offset = bin_sleb(&r, 64); + else + wasm_error(c, wasm_loc(0, 0), + "wasm: expected const data offset"); + } + if (bin_u8(&r) != 0x0b || d->offset < 0) + wasm_error(c, wasm_loc(0, 0), "wasm: bad data offset"); + } else { + wasm_error(c, wasm_loc(0, 0), + "wasm: unsupported data segment flags 0x%x", flags); + } + n = bin_uleb(&r); + bin_need(&r, n); + wasm_data_set_bytes(c, out, d, r.data + r.pos, (uint64_t)n); + r.pos += n; + } + } else if (id == 12) { + /* Data count section. Conservatively accept and discard; bulk-memory + * validation only needs it for forward-references during streaming + * validation, which we don't do here. */ + (void)bin_uleb(&r); + } else { + r.pos = end; + } + if (r.pos != end) + wasm_error(c, wasm_loc(0, 0), "wasm: malformed section length"); + } +} + +int wasm_is_binary(const CfreeSlice* input) { + return input->len >= 4u && input->data[0] == 0x00 && input->data[1] == 0x61 && + input->data[2] == 0x73 && input->data[3] == 0x6d; +} diff --git a/src/wasm/encode.c b/src/wasm/encode.c @@ -0,0 +1,855 @@ +#include "wasm/wasm.h" + +static void write_byte(CfreeWriter* w, uint8_t b) { w->write(w, &b, 1); } + +static void write_uleb(CfreeWriter* w, uint64_t v) { + do { + uint8_t b = (uint8_t)(v & 0x7fu); + v >>= 7u; + if (v) b |= 0x80u; + write_byte(w, b); + } while (v); +} + +static void write_sleb(CfreeWriter* w, int64_t v) { + int more = 1; + while (more) { + uint8_t b = (uint8_t)(v & 0x7f); + int sign = b & 0x40; + v >>= 7; + if ((v == 0 && !sign) || (v == -1 && sign)) + more = 0; + else + b |= 0x80u; + write_byte(w, b); + } +} + +static void write_f32(CfreeWriter* w, double value) { + float f = (float)value; + uint32_t bits; + memcpy(&bits, &f, sizeof bits); + for (uint32_t i = 0; i < 4u; ++i) write_byte(w, (uint8_t)(bits >> (i * 8u))); +} + +static void write_f64(CfreeWriter* w, double value) { + uint64_t bits; + memcpy(&bits, &value, sizeof bits); + for (uint32_t i = 0; i < 8u; ++i) write_byte(w, (uint8_t)(bits >> (i * 8u))); +} + +static void write_name(CfreeWriter* w, const char* name) { + CfreeSlice s = cfree_slice_cstr(name); + write_uleb(w, s.len); + if (s.len) w->write(w, s.s, s.len); +} + +static void encode_section(CfreeHeap* h, CfreeWriter* out, uint8_t id, + void (*fn)(CfreeWriter*, const WasmModule*), + const WasmModule* m) { + CfreeWriter* tmp = NULL; + size_t len; + const uint8_t* bytes; + if (cfree_writer_mem(h, &tmp) != CFREE_OK) return; + fn(tmp, m); + bytes = cfree_writer_mem_bytes(tmp, &len); + write_byte(out, id); + write_uleb(out, len); + out->write(out, bytes, len); + cfree_writer_close(tmp); +} + +static void enc_type(CfreeWriter* w, const WasmModule* m) { + uint32_t i, j; + write_uleb(w, m->ntypes); + for (i = 0; i < m->ntypes; ++i) { + const WasmFuncType* f = &m->types[i]; + write_byte(w, 0x60); + write_uleb(w, f->nparams); + for (j = 0; j < f->nparams; ++j) write_byte(w, (uint8_t)f->params[j]); + write_uleb(w, f->nresults); + for (j = 0; j < f->nresults; ++j) write_byte(w, (uint8_t)f->results[j]); + } +} + +static void write_memory_limits(CfreeWriter* w, const WasmMemory* mem) { + uint32_t flags = (mem->has_max ? 1u : 0u) | (mem->shared ? 2u : 0u) | + (mem->is64 ? 4u : 0u); + write_uleb(w, flags); + write_uleb(w, mem->min_pages); + if (mem->has_max) write_uleb(w, mem->max_pages); +} + +static void write_limits(CfreeWriter* w, uint32_t min, uint32_t max, + int has_max) { + write_uleb(w, has_max ? 1 : 0); + write_uleb(w, min); + if (has_max) write_uleb(w, max); +} + +static void enc_import(CfreeWriter* w, const WasmModule* m) { + uint32_t i, n = 0; + for (i = 0; i < m->nfuncs; ++i) + if (m->funcs[i].is_import) n++; + for (i = 0; i < m->ntables; ++i) + if (m->tables[i].is_import) n++; + for (i = 0; i < m->nmemories; ++i) + if (m->memories[i].is_import) n++; + for (i = 0; i < m->nglobals; ++i) + if (m->globals[i].is_import) n++; + write_uleb(w, n); + for (i = 0; i < m->nfuncs; ++i) { + const WasmFunc* f = &m->funcs[i]; + if (!f->is_import) continue; + write_name(w, f->import_module); + write_name(w, f->import_name); + write_byte(w, 0); + write_uleb(w, f->typeidx); + } + for (i = 0; i < m->ntables; ++i) { + const WasmTable* t = &m->tables[i]; + if (!t->is_import) continue; + write_name(w, t->import_module); + write_name(w, t->import_name); + write_byte(w, 1); + write_byte(w, (uint8_t)t->elem_type); + write_limits(w, t->min, t->max, t->has_max); + } + for (i = 0; i < m->nmemories; ++i) { + const WasmMemory* mem = &m->memories[i]; + if (!mem->is_import) continue; + write_name(w, mem->import_module); + write_name(w, mem->import_name); + write_byte(w, 2); + write_memory_limits(w, mem); + } + for (i = 0; i < m->nglobals; ++i) { + const WasmGlobal* g = &m->globals[i]; + if (!g->is_import) continue; + write_name(w, g->import_module); + write_name(w, g->import_name); + write_byte(w, 3); + write_byte(w, (uint8_t)g->type); + write_byte(w, g->mutable_); + } +} + +static int wasm_module_has_imports(const WasmModule* m) { + uint32_t i; + for (i = 0; i < m->nfuncs; ++i) + if (m->funcs[i].is_import) return 1; + for (i = 0; i < m->ntables; ++i) + if (m->tables[i].is_import) return 1; + for (i = 0; i < m->nmemories; ++i) + if (m->memories[i].is_import) return 1; + for (i = 0; i < m->nglobals; ++i) + if (m->globals[i].is_import) return 1; + return 0; +} + +static void enc_func(CfreeWriter* w, const WasmModule* m) { + uint32_t i; + uint32_t n = 0; + for (i = 0; i < m->nfuncs; ++i) + if (!m->funcs[i].is_import) n++; + write_uleb(w, n); + for (i = 0; i < m->nfuncs; ++i) + if (!m->funcs[i].is_import) write_uleb(w, m->funcs[i].typeidx); +} + +static void enc_export(CfreeWriter* w, const WasmModule* m) { + uint32_t i; + write_uleb(w, m->nexports); + for (i = 0; i < m->nexports; ++i) { + write_name(w, m->exports[i].name); + write_byte(w, m->exports[i].kind); + write_uleb(w, m->exports[i].index); + } +} + +static void enc_memory(CfreeWriter* w, const WasmModule* m) { + uint32_t i, n = 0; + for (i = 0; i < m->nmemories; ++i) + if (!m->memories[i].is_import) n++; + write_uleb(w, n); + for (i = 0; i < m->nmemories; ++i) + if (!m->memories[i].is_import) write_memory_limits(w, &m->memories[i]); +} + +static uint8_t wasm_opcode(uint8_t kind); + +static void enc_table(CfreeWriter* w, const WasmModule* m) { + uint32_t i, n = 0; + for (i = 0; i < m->ntables; ++i) + if (!m->tables[i].is_import) n++; + write_uleb(w, n); + for (i = 0; i < m->ntables; ++i) { + const WasmTable* t = &m->tables[i]; + if (t->is_import) continue; + write_byte(w, (uint8_t)t->elem_type); + write_limits(w, t->min, t->max, t->has_max); + } +} + +static void enc_global(CfreeWriter* w, const WasmModule* m) { + uint32_t i, n = 0; + for (i = 0; i < m->nglobals; ++i) + if (!m->globals[i].is_import) n++; + write_uleb(w, n); + for (i = 0; i < m->nglobals; ++i) { + const WasmGlobal* g = &m->globals[i]; + if (g->is_import) continue; + write_byte(w, (uint8_t)g->type); + write_byte(w, g->mutable_); + write_byte(w, wasm_opcode(g->init.kind)); + if (g->init.kind == WASM_INSN_I32_CONST || + g->init.kind == WASM_INSN_I64_CONST) + write_sleb(w, g->init.imm); + else if (g->init.kind == WASM_INSN_F32_CONST) + write_f32(w, g->init.fp); + else if (g->init.kind == WASM_INSN_F64_CONST) + write_f64(w, g->init.fp); + write_byte(w, 0x0b); + } +} + +static uint8_t wasm_opcode(uint8_t kind) { + switch (kind) { + case WASM_INSN_UNREACHABLE: + return 0x00; + case WASM_INSN_NOP: + return 0x01; + case WASM_INSN_BLOCK: + return 0x02; + case WASM_INSN_LOOP: + return 0x03; + case WASM_INSN_IF: + return 0x04; + case WASM_INSN_ELSE: + return 0x05; + case WASM_INSN_END: + return 0x0b; + case WASM_INSN_BR: + return 0x0c; + case WASM_INSN_BR_IF: + return 0x0d; + case WASM_INSN_BR_TABLE: + return 0x0e; + case WASM_INSN_I32_CONST: + return 0x41; + case WASM_INSN_I64_CONST: + return 0x42; + case WASM_INSN_F32_CONST: + return 0x43; + case WASM_INSN_F64_CONST: + return 0x44; + case WASM_INSN_LOCAL_GET: + return 0x20; + case WASM_INSN_LOCAL_SET: + return 0x21; + case WASM_INSN_LOCAL_TEE: + return 0x22; + case WASM_INSN_CALL: + return 0x10; + case WASM_INSN_CALL_INDIRECT: + return 0x11; + case WASM_INSN_RETURN_CALL: + return 0x12; + case WASM_INSN_RETURN_CALL_INDIRECT: + return 0x13; + case WASM_INSN_CALL_REF: + return 0x14; + case WASM_INSN_RETURN_CALL_REF: + return 0x15; + case WASM_INSN_REF_NULL: + return 0xd0; + case WASM_INSN_REF_IS_NULL: + return 0xd1; + case WASM_INSN_REF_FUNC: + return 0xd2; + case WASM_INSN_GLOBAL_GET: + return 0x23; + case WASM_INSN_GLOBAL_SET: + return 0x24; + case WASM_INSN_RETURN: + return 0x0f; + case WASM_INSN_DROP: + return 0x1a; + case WASM_INSN_SELECT: + return 0x1b; + case WASM_INSN_I32_LOAD: + return 0x28; + case WASM_INSN_I64_LOAD: + return 0x29; + case WASM_INSN_F32_LOAD: + return 0x2a; + case WASM_INSN_F64_LOAD: + return 0x2b; + case WASM_INSN_I32_LOAD8_S: + return 0x2c; + case WASM_INSN_I32_LOAD8_U: + return 0x2d; + case WASM_INSN_I32_LOAD16_S: + return 0x2e; + case WASM_INSN_I32_LOAD16_U: + return 0x2f; + case WASM_INSN_I64_LOAD8_S: + return 0x30; + case WASM_INSN_I64_LOAD8_U: + return 0x31; + case WASM_INSN_I64_LOAD16_S: + return 0x32; + case WASM_INSN_I64_LOAD16_U: + return 0x33; + case WASM_INSN_I64_LOAD32_S: + return 0x34; + case WASM_INSN_I64_LOAD32_U: + return 0x35; + case WASM_INSN_I32_STORE: + return 0x36; + case WASM_INSN_I64_STORE: + return 0x37; + case WASM_INSN_F32_STORE: + return 0x38; + case WASM_INSN_F64_STORE: + return 0x39; + case WASM_INSN_I32_STORE8: + return 0x3a; + case WASM_INSN_I32_STORE16: + return 0x3b; + case WASM_INSN_I64_STORE8: + return 0x3c; + case WASM_INSN_I64_STORE16: + return 0x3d; + case WASM_INSN_I64_STORE32: + return 0x3e; + case WASM_INSN_MEMORY_SIZE: + return 0x3f; + case WASM_INSN_MEMORY_GROW: + return 0x40; + case WASM_INSN_I32_EQZ: + return 0x45; + case WASM_INSN_I32_EQ: + return 0x46; + case WASM_INSN_I32_NE: + return 0x47; + case WASM_INSN_I32_LT_S: + return 0x48; + case WASM_INSN_I32_LT_U: + return 0x49; + case WASM_INSN_I32_GT_S: + return 0x4a; + case WASM_INSN_I32_GT_U: + return 0x4b; + case WASM_INSN_I32_LE_S: + return 0x4c; + case WASM_INSN_I32_LE_U: + return 0x4d; + case WASM_INSN_I32_GE_S: + return 0x4e; + case WASM_INSN_I32_GE_U: + return 0x4f; + case WASM_INSN_I64_EQZ: + return 0x50; + case WASM_INSN_I64_EQ: + return 0x51; + case WASM_INSN_I64_NE: + return 0x52; + case WASM_INSN_I64_LT_S: + return 0x53; + case WASM_INSN_I64_LT_U: + return 0x54; + case WASM_INSN_I64_GT_S: + return 0x55; + case WASM_INSN_I64_GT_U: + return 0x56; + case WASM_INSN_I64_LE_S: + return 0x57; + case WASM_INSN_I64_LE_U: + return 0x58; + case WASM_INSN_I64_GE_S: + return 0x59; + case WASM_INSN_I64_GE_U: + return 0x5a; + case WASM_INSN_I32_ADD: + return 0x6a; + case WASM_INSN_I32_SUB: + return 0x6b; + case WASM_INSN_I32_MUL: + return 0x6c; + case WASM_INSN_I32_DIV_S: + return 0x6d; + case WASM_INSN_I32_DIV_U: + return 0x6e; + case WASM_INSN_I32_REM_S: + return 0x6f; + case WASM_INSN_I32_REM_U: + return 0x70; + case WASM_INSN_I32_AND: + return 0x71; + case WASM_INSN_I32_OR: + return 0x72; + case WASM_INSN_I32_XOR: + return 0x73; + case WASM_INSN_I32_SHL: + return 0x74; + case WASM_INSN_I32_SHR_S: + return 0x75; + case WASM_INSN_I32_SHR_U: + return 0x76; + case WASM_INSN_I32_CLZ: + return 0x67; + case WASM_INSN_I32_CTZ: + return 0x68; + case WASM_INSN_I32_POPCNT: + return 0x69; + case WASM_INSN_I32_ROTL: + return 0x77; + case WASM_INSN_I32_ROTR: + return 0x78; + case WASM_INSN_I64_CLZ: + return 0x79; + case WASM_INSN_I64_CTZ: + return 0x7a; + case WASM_INSN_I64_POPCNT: + return 0x7b; + case WASM_INSN_I64_ADD: + return 0x7c; + case WASM_INSN_I64_SUB: + return 0x7d; + case WASM_INSN_I64_MUL: + return 0x7e; + case WASM_INSN_I64_DIV_S: + return 0x7f; + case WASM_INSN_I64_DIV_U: + return 0x80; + case WASM_INSN_I64_REM_S: + return 0x81; + case WASM_INSN_I64_REM_U: + return 0x82; + case WASM_INSN_I64_AND: + return 0x83; + case WASM_INSN_I64_OR: + return 0x84; + case WASM_INSN_I64_XOR: + return 0x85; + case WASM_INSN_I64_SHL: + return 0x86; + case WASM_INSN_I64_SHR_S: + return 0x87; + case WASM_INSN_I64_SHR_U: + return 0x88; + case WASM_INSN_I64_ROTL: + return 0x89; + case WASM_INSN_I64_ROTR: + return 0x8a; + case WASM_INSN_F32_ADD: + return 0x92; + case WASM_INSN_F32_SUB: + return 0x93; + case WASM_INSN_F32_MUL: + return 0x94; + case WASM_INSN_F32_DIV: + return 0x95; + case WASM_INSN_F64_ADD: + return 0xa0; + case WASM_INSN_F64_SUB: + return 0xa1; + case WASM_INSN_F64_MUL: + return 0xa2; + case WASM_INSN_F64_DIV: + return 0xa3; + case WASM_INSN_F32_EQ: + return 0x5b; + case WASM_INSN_F32_NE: + return 0x5c; + case WASM_INSN_F32_LT: + return 0x5d; + case WASM_INSN_F32_GT: + return 0x5e; + case WASM_INSN_F32_LE: + return 0x5f; + case WASM_INSN_F32_GE: + return 0x60; + case WASM_INSN_F64_EQ: + return 0x61; + case WASM_INSN_F64_NE: + return 0x62; + case WASM_INSN_F64_LT: + return 0x63; + case WASM_INSN_F64_GT: + return 0x64; + case WASM_INSN_F64_LE: + return 0x65; + case WASM_INSN_F64_GE: + return 0x66; + case WASM_INSN_I32_WRAP_I64: + return 0xa7; + case WASM_INSN_I32_TRUNC_F32_S: + return 0xa8; + case WASM_INSN_I32_TRUNC_F32_U: + return 0xa9; + case WASM_INSN_I32_TRUNC_F64_S: + return 0xaa; + case WASM_INSN_I32_TRUNC_F64_U: + return 0xab; + case WASM_INSN_I64_EXTEND_I32_S: + return 0xac; + case WASM_INSN_I64_EXTEND_I32_U: + return 0xad; + case WASM_INSN_I64_TRUNC_F32_S: + return 0xae; + case WASM_INSN_I64_TRUNC_F32_U: + return 0xaf; + case WASM_INSN_I64_TRUNC_F64_S: + return 0xb0; + case WASM_INSN_I64_TRUNC_F64_U: + return 0xb1; + case WASM_INSN_F32_CONVERT_I32_S: + return 0xb2; + case WASM_INSN_F32_CONVERT_I32_U: + return 0xb3; + case WASM_INSN_F32_CONVERT_I64_S: + return 0xb4; + case WASM_INSN_F32_CONVERT_I64_U: + return 0xb5; + case WASM_INSN_F32_DEMOTE_F64: + return 0xb6; + case WASM_INSN_F64_CONVERT_I32_S: + return 0xb7; + case WASM_INSN_F64_CONVERT_I32_U: + return 0xb8; + case WASM_INSN_F64_CONVERT_I64_S: + return 0xb9; + case WASM_INSN_F64_CONVERT_I64_U: + return 0xba; + case WASM_INSN_F64_PROMOTE_F32: + return 0xbb; + case WASM_INSN_I32_REINTERPRET_F32: + return 0xbc; + case WASM_INSN_I64_REINTERPRET_F64: + return 0xbd; + case WASM_INSN_F32_REINTERPRET_I32: + return 0xbe; + case WASM_INSN_F64_REINTERPRET_I64: + return 0xbf; + } + return 0; +} + +static uint32_t wasm_threads_subopcode(uint8_t kind) { + switch (kind) { + case WASM_INSN_MEMORY_ATOMIC_NOTIFY: + return 0x00; + case WASM_INSN_I32_ATOMIC_WAIT: + return 0x01; + case WASM_INSN_I64_ATOMIC_WAIT: + return 0x02; + case WASM_INSN_ATOMIC_FENCE: + return 0x03; + case WASM_INSN_I32_ATOMIC_LOAD: + return 0x10; + case WASM_INSN_I64_ATOMIC_LOAD: + return 0x11; + case WASM_INSN_I32_ATOMIC_LOAD8_U: + return 0x12; + case WASM_INSN_I32_ATOMIC_LOAD16_U: + return 0x13; + case WASM_INSN_I64_ATOMIC_LOAD8_U: + return 0x14; + case WASM_INSN_I64_ATOMIC_LOAD16_U: + return 0x15; + case WASM_INSN_I64_ATOMIC_LOAD32_U: + return 0x16; + case WASM_INSN_I32_ATOMIC_STORE: + return 0x17; + case WASM_INSN_I64_ATOMIC_STORE: + return 0x18; + case WASM_INSN_I32_ATOMIC_STORE8: + return 0x19; + case WASM_INSN_I32_ATOMIC_STORE16: + return 0x1a; + case WASM_INSN_I64_ATOMIC_STORE8: + return 0x1b; + case WASM_INSN_I64_ATOMIC_STORE16: + return 0x1c; + case WASM_INSN_I64_ATOMIC_STORE32: + return 0x1d; + case WASM_INSN_I32_ATOMIC_RMW_ADD: + return 0x1e; + case WASM_INSN_I64_ATOMIC_RMW_ADD: + return 0x1f; + case WASM_INSN_I32_ATOMIC_RMW_SUB: + return 0x25; + case WASM_INSN_I64_ATOMIC_RMW_SUB: + return 0x26; + case WASM_INSN_I32_ATOMIC_RMW_AND: + return 0x2c; + case WASM_INSN_I64_ATOMIC_RMW_AND: + return 0x2d; + case WASM_INSN_I32_ATOMIC_RMW_OR: + return 0x33; + case WASM_INSN_I64_ATOMIC_RMW_OR: + return 0x34; + case WASM_INSN_I32_ATOMIC_RMW_XOR: + return 0x3a; + case WASM_INSN_I64_ATOMIC_RMW_XOR: + return 0x3b; + case WASM_INSN_I32_ATOMIC_RMW_XCHG: + return 0x41; + case WASM_INSN_I64_ATOMIC_RMW_XCHG: + return 0x42; + case WASM_INSN_I32_ATOMIC_RMW_CMPXCHG: + return 0x48; + case WASM_INSN_I64_ATOMIC_RMW_CMPXCHG: + return 0x49; + default: + return UINT32_MAX; + } +} + +/* 0xfc-prefixed sub-opcodes: non-trapping FTOI and bulk memory ops. */ +static uint32_t wasm_misc_subopcode(uint8_t kind) { + switch (kind) { + case WASM_INSN_I32_TRUNC_SAT_F32_S: return 0x00; + case WASM_INSN_I32_TRUNC_SAT_F32_U: return 0x01; + case WASM_INSN_I32_TRUNC_SAT_F64_S: return 0x02; + case WASM_INSN_I32_TRUNC_SAT_F64_U: return 0x03; + case WASM_INSN_I64_TRUNC_SAT_F32_S: return 0x04; + case WASM_INSN_I64_TRUNC_SAT_F32_U: return 0x05; + case WASM_INSN_I64_TRUNC_SAT_F64_S: return 0x06; + case WASM_INSN_I64_TRUNC_SAT_F64_U: return 0x07; + case WASM_INSN_MEMORY_INIT: return 0x08; + case WASM_INSN_DATA_DROP: return 0x09; + case WASM_INSN_MEMORY_COPY: return 0x0a; + case WASM_INSN_MEMORY_FILL: return 0x0b; + case WASM_INSN_TABLE_INIT: return 0x0c; + case WASM_INSN_ELEM_DROP: return 0x0d; + case WASM_INSN_TABLE_COPY: return 0x0e; + case WASM_INSN_TABLE_GROW: return 0x0f; + case WASM_INSN_TABLE_SIZE: return 0x10; + case WASM_INSN_TABLE_FILL: return 0x11; + default: return UINT32_MAX; + } +} + +static void enc_code(CfreeWriter* w, const WasmModule* m) { + uint32_t i, j; + uint32_t n = 0; + for (i = 0; i < m->nfuncs; ++i) + if (!m->funcs[i].is_import) n++; + write_uleb(w, n); + for (i = 0; i < m->nfuncs; ++i) { + CfreeWriter* body = NULL; + size_t len; + const uint8_t* bytes; + if (cfree_writer_mem(m->heap, &body) != CFREE_OK) continue; + if (m->funcs[i].is_import) { + cfree_writer_close(body); + continue; + } + if (m->funcs[i].nlocals) { + uint32_t group_count = 0; + WasmValType prev = 0; + for (j = 0; j < m->funcs[i].nlocals; ++j) { + if (j == 0 || m->funcs[i].locals[j] != prev) { + group_count++; + prev = m->funcs[i].locals[j]; + } + } + write_uleb(body, group_count); + for (j = 0; j < m->funcs[i].nlocals;) { + uint32_t k = j + 1u; + while (k < m->funcs[i].nlocals && + m->funcs[i].locals[k] == m->funcs[i].locals[j]) + k++; + write_uleb(body, k - j); + write_byte(body, (uint8_t)m->funcs[i].locals[j]); + j = k; + } + } else { + write_uleb(body, 0); + } + for (j = 0; j < m->funcs[i].ninsns; ++j) { + WasmInsn in = m->funcs[i].insns[j]; + uint8_t op = wasm_opcode(in.kind); + uint32_t threads_op = wasm_threads_subopcode(in.kind); + uint32_t misc_op = wasm_misc_subopcode(in.kind); + if (threads_op != UINT32_MAX) { + write_byte(body, 0xfe); + write_uleb(body, threads_op); + } else if (misc_op != UINT32_MAX) { + write_byte(body, 0xfc); + write_uleb(body, misc_op); + } else { + write_byte(body, op); + } + if (in.kind == WASM_INSN_MEMORY_INIT) { + write_uleb(body, (uint64_t)in.imm); + write_uleb(body, in.memidx); + } else if (in.kind == WASM_INSN_DATA_DROP) { + write_uleb(body, (uint64_t)in.imm); + } else if (in.kind == WASM_INSN_MEMORY_COPY) { + write_uleb(body, in.memidx); + write_uleb(body, in.aux_idx); + } else if (in.kind == WASM_INSN_MEMORY_FILL) { + write_uleb(body, in.memidx); + } else if (in.kind == WASM_INSN_TABLE_INIT) { + write_uleb(body, (uint64_t)in.imm); + write_uleb(body, in.aux_idx); + } else if (in.kind == WASM_INSN_ELEM_DROP) { + write_uleb(body, (uint64_t)in.imm); + } else if (in.kind == WASM_INSN_TABLE_COPY) { + write_uleb(body, (uint64_t)in.imm); + write_uleb(body, in.aux_idx); + } else if (in.kind == WASM_INSN_TABLE_GROW || + in.kind == WASM_INSN_TABLE_SIZE || + in.kind == WASM_INSN_TABLE_FILL) { + write_uleb(body, (uint64_t)in.imm); + } else if (in.kind == WASM_INSN_ATOMIC_FENCE) { + write_byte(body, 0); + } else if (in.kind == WASM_INSN_BLOCK || in.kind == WASM_INSN_LOOP || + in.kind == WASM_INSN_IF) + write_byte(body, 0x40); + else if (in.kind == WASM_INSN_I32_CONST || in.kind == WASM_INSN_I64_CONST) + write_sleb(body, in.imm); + else if (in.kind == WASM_INSN_F32_CONST) + write_f32(body, in.fp); + else if (in.kind == WASM_INSN_F64_CONST) + write_f64(body, in.fp); + else if (in.kind == WASM_INSN_LOCAL_GET || + in.kind == WASM_INSN_LOCAL_SET || + in.kind == WASM_INSN_LOCAL_TEE || + in.kind == WASM_INSN_GLOBAL_GET || + in.kind == WASM_INSN_GLOBAL_SET || in.kind == WASM_INSN_CALL || + in.kind == WASM_INSN_RETURN_CALL || + in.kind == WASM_INSN_CALL_REF || + in.kind == WASM_INSN_RETURN_CALL_REF || + in.kind == WASM_INSN_REF_NULL || + in.kind == WASM_INSN_REF_FUNC || + in.kind == WASM_INSN_BR || in.kind == WASM_INSN_BR_IF) + write_uleb(body, (uint64_t)in.imm); + else if (in.kind == WASM_INSN_CALL_INDIRECT || + in.kind == WASM_INSN_RETURN_CALL_INDIRECT) { + write_uleb(body, (uint64_t)in.imm); + write_uleb(body, in.align); + } else if (in.kind == WASM_INSN_BR_TABLE) { + write_uleb(body, in.ntargets ? in.ntargets - 1u : 0u); + for (uint32_t k = 0; k < in.ntargets; ++k) + write_uleb(body, in.targets[k]); + } else if (wasm_insn_is_mem(in.kind)) { + write_uleb(body, in.memidx ? in.align + 64u : in.align); + if (in.memidx) write_uleb(body, in.memidx); + write_uleb(body, in.offset64); + } else if (in.kind == WASM_INSN_MEMORY_SIZE || + in.kind == WASM_INSN_MEMORY_GROW) { + write_uleb(body, in.memidx); + } + } + write_byte(body, 0x0b); + bytes = cfree_writer_mem_bytes(body, &len); + write_uleb(w, len); + w->write(w, bytes, len); + cfree_writer_close(body); + } +} + +static void enc_data(CfreeWriter* w, const WasmModule* m) { + uint32_t i; + write_uleb(w, m->ndata); + for (i = 0; i < m->ndata; ++i) { + const WasmDataSegment* d = &m->data[i]; + int is64 = 0; + if (d->mode == WASM_SEG_PASSIVE) { + write_uleb(w, 1u); + } else { + if (d->memidx < m->nmemories) is64 = m->memories[d->memidx].is64; + if (d->memidx == 0u) { + write_uleb(w, 0u); + } else { + write_uleb(w, 2u); + write_uleb(w, d->memidx); + } + write_byte(w, is64 ? 0x42 : 0x41); + write_sleb(w, d->offset); + write_byte(w, 0x0b); + } + write_uleb(w, d->nbytes); + if (d->nbytes) w->write(w, d->bytes, (size_t)d->nbytes); + } +} + +static void enc_elem(CfreeWriter* w, const WasmModule* m) { + uint32_t i, j; + write_uleb(w, m->nelems); + for (i = 0; i < m->nelems; ++i) { + const WasmElemSegment* e = &m->elems[i]; + if (e->mode == WASM_SEG_PASSIVE) { + write_uleb(w, 1u); + write_byte(w, 0x00); /* elemkind: funcref */ + } else if (e->mode == WASM_SEG_DECLARATIVE) { + write_uleb(w, 3u); + write_byte(w, 0x00); + } else if (e->tableidx != 0) { + write_uleb(w, 2u); + write_uleb(w, e->tableidx); + write_byte(w, 0x41); + write_sleb(w, e->offset); + write_byte(w, 0x0b); + write_byte(w, 0x00); + } else { + write_uleb(w, 0u); + write_byte(w, 0x41); + write_sleb(w, e->offset); + write_byte(w, 0x0b); + } + write_uleb(w, e->nfuncs); + for (j = 0; j < e->nfuncs; ++j) write_uleb(w, e->funcs[j]); + } +} + +static void enc_start(CfreeWriter* w, const WasmModule* m) { + write_uleb(w, m->start_func); +} + +static void encode_custom(CfreeHeap* h, CfreeWriter* out, const WasmModule* m, + const WasmCustom* cs) { + CfreeWriter* tmp = NULL; + size_t len; + const uint8_t* bytes; + if (cfree_writer_mem(h, &tmp) != CFREE_OK) return; + (void)m; + write_name(tmp, cs->name); + if (cs->len) tmp->write(tmp, cs->data, cs->len); + bytes = cfree_writer_mem_bytes(tmp, &len); + write_byte(out, 0); + write_uleb(out, len); + out->write(out, bytes, len); + cfree_writer_close(tmp); +} + +void wasm_encode(CfreeCompiler* c, const WasmModule* m, + CfreeWriter* out) { + static const uint8_t magic[] = {0x00, 0x61, 0x73, 0x6d, + 0x01, 0x00, 0x00, 0x00}; + CfreeHeap* h = cfree_compiler_context(c)->heap; + out->write(out, magic, sizeof magic); + for (uint32_t i = 0; i < m->ncustoms; ++i) + encode_custom(h, out, m, &m->customs[i]); + encode_section(h, out, 1, enc_type, m); + if (wasm_module_has_imports(m)) encode_section(h, out, 2, enc_import, m); + encode_section(h, out, 3, enc_func, m); + if (m->ntables) encode_section(h, out, 4, enc_table, m); + { + int has_defined_memory = 0; + for (uint32_t i = 0; i < m->nmemories; ++i) + if (!m->memories[i].is_import) has_defined_memory = 1; + if (has_defined_memory) encode_section(h, out, 5, enc_memory, m); + } + if (m->nglobals) encode_section(h, out, 6, enc_global, m); + encode_section(h, out, 7, enc_export, m); + if (m->has_start) encode_section(h, out, 8, enc_start, m); + if (m->nelems) encode_section(h, out, 9, enc_elem, m); + encode_section(h, out, 10, enc_code, m); + if (m->ndata) encode_section(h, out, 11, enc_data, m); +} diff --git a/src/wasm/insn.c b/src/wasm/insn.c @@ -0,0 +1,425 @@ +#include "wasm/wasm.h" + +int wasm_is_num_type(WasmValType vt) { + return vt == WASM_VAL_I32 || vt == WASM_VAL_I64 || vt == WASM_VAL_F32 || + vt == WASM_VAL_F64; +} + +int wasm_is_ref_type(WasmValType vt) { + return vt == WASM_VAL_FUNCREF || vt == WASM_VAL_EXTERNREF; +} + +int wasm_is_frontend_value_type(WasmValType vt) { + return wasm_is_num_type(vt) || vt == WASM_VAL_FUNCREF; +} + +int wasm_feature_enabled(const WasmModule* m, WasmFeatureSet feature) { + return (m->features & (uint32_t)feature) != 0; +} + +void wasm_require_feature_at(CfreeCompiler* c, const WasmModule* m, + WasmFeatureSet feature, + const char* feature_name, const char* what, + CfreeSrcLoc loc) { + if (!wasm_feature_enabled(m, feature)) + wasm_error(c, loc, "wasm: %s requires %s", what, feature_name); +} + +void wasm_require_feature(CfreeCompiler* c, const WasmModule* m, + WasmFeatureSet feature, const char* feature_name, + const char* what) { + wasm_require_feature_at(c, m, feature, feature_name, what, wasm_loc(0, 0)); +} + + +int wasm_insn_is_load(WasmInsnKind kind) { + return kind == WASM_INSN_I32_LOAD || kind == WASM_INSN_I64_LOAD || + kind == WASM_INSN_F32_LOAD || kind == WASM_INSN_F64_LOAD || + kind == WASM_INSN_I32_LOAD8_S || kind == WASM_INSN_I32_LOAD8_U || + kind == WASM_INSN_I32_LOAD16_S || kind == WASM_INSN_I32_LOAD16_U || + kind == WASM_INSN_I64_LOAD8_S || kind == WASM_INSN_I64_LOAD8_U || + kind == WASM_INSN_I64_LOAD16_S || kind == WASM_INSN_I64_LOAD16_U || + kind == WASM_INSN_I64_LOAD32_S || kind == WASM_INSN_I64_LOAD32_U; +} + +int wasm_insn_is_store(WasmInsnKind kind) { + return kind == WASM_INSN_I32_STORE || kind == WASM_INSN_I64_STORE || + kind == WASM_INSN_F32_STORE || kind == WASM_INSN_F64_STORE || + kind == WASM_INSN_I32_STORE8 || kind == WASM_INSN_I32_STORE16 || + kind == WASM_INSN_I64_STORE8 || kind == WASM_INSN_I64_STORE16 || + kind == WASM_INSN_I64_STORE32; +} + +int wasm_insn_is_atomic_load(WasmInsnKind kind) { + return kind == WASM_INSN_I32_ATOMIC_LOAD || + kind == WASM_INSN_I64_ATOMIC_LOAD || + kind == WASM_INSN_I32_ATOMIC_LOAD8_U || + kind == WASM_INSN_I32_ATOMIC_LOAD16_U || + kind == WASM_INSN_I64_ATOMIC_LOAD8_U || + kind == WASM_INSN_I64_ATOMIC_LOAD16_U || + kind == WASM_INSN_I64_ATOMIC_LOAD32_U; +} + +int wasm_insn_is_atomic_store(WasmInsnKind kind) { + return kind == WASM_INSN_I32_ATOMIC_STORE || + kind == WASM_INSN_I64_ATOMIC_STORE || + kind == WASM_INSN_I32_ATOMIC_STORE8 || + kind == WASM_INSN_I32_ATOMIC_STORE16 || + kind == WASM_INSN_I64_ATOMIC_STORE8 || + kind == WASM_INSN_I64_ATOMIC_STORE16 || + kind == WASM_INSN_I64_ATOMIC_STORE32; +} + +int wasm_insn_is_atomic_rmw(WasmInsnKind kind) { + return kind == WASM_INSN_I32_ATOMIC_RMW_ADD || + kind == WASM_INSN_I64_ATOMIC_RMW_ADD || + kind == WASM_INSN_I32_ATOMIC_RMW_SUB || + kind == WASM_INSN_I64_ATOMIC_RMW_SUB || + kind == WASM_INSN_I32_ATOMIC_RMW_AND || + kind == WASM_INSN_I64_ATOMIC_RMW_AND || + kind == WASM_INSN_I32_ATOMIC_RMW_OR || + kind == WASM_INSN_I64_ATOMIC_RMW_OR || + kind == WASM_INSN_I32_ATOMIC_RMW_XOR || + kind == WASM_INSN_I64_ATOMIC_RMW_XOR || + kind == WASM_INSN_I32_ATOMIC_RMW_XCHG || + kind == WASM_INSN_I64_ATOMIC_RMW_XCHG; +} + +int wasm_insn_is_atomic_cmpxchg(WasmInsnKind kind) { + return kind == WASM_INSN_I32_ATOMIC_RMW_CMPXCHG || + kind == WASM_INSN_I64_ATOMIC_RMW_CMPXCHG; +} + +int wasm_insn_is_atomic_wait_notify(WasmInsnKind kind) { + return kind == WASM_INSN_I32_ATOMIC_WAIT || + kind == WASM_INSN_I64_ATOMIC_WAIT || + kind == WASM_INSN_MEMORY_ATOMIC_NOTIFY; +} + +int wasm_insn_is_atomic_mem(WasmInsnKind kind) { + return wasm_insn_is_atomic_load(kind) || wasm_insn_is_atomic_store(kind) || + wasm_insn_is_atomic_rmw(kind) || wasm_insn_is_atomic_cmpxchg(kind) || + wasm_insn_is_atomic_wait_notify(kind); +} + +int wasm_insn_is_mem(WasmInsnKind kind) { + return wasm_insn_is_load(kind) || wasm_insn_is_store(kind) || + wasm_insn_is_atomic_mem(kind); +} + +WasmValType wasm_func_local_type(const WasmFunc* f, uint32_t index) { + if (index < f->nparams) return f->params[index]; + return f->locals[index - f->nparams]; +} + +uint32_t wasm_mem_width(uint8_t kind) { + switch (kind) { + case WASM_INSN_I32_LOAD8_S: + case WASM_INSN_I32_LOAD8_U: + case WASM_INSN_I64_LOAD8_S: + case WASM_INSN_I64_LOAD8_U: + case WASM_INSN_I32_STORE8: + case WASM_INSN_I64_STORE8: + case WASM_INSN_I32_ATOMIC_LOAD8_U: + case WASM_INSN_I64_ATOMIC_LOAD8_U: + case WASM_INSN_I32_ATOMIC_STORE8: + case WASM_INSN_I64_ATOMIC_STORE8: + return 1; + case WASM_INSN_I32_LOAD16_S: + case WASM_INSN_I32_LOAD16_U: + case WASM_INSN_I64_LOAD16_S: + case WASM_INSN_I64_LOAD16_U: + case WASM_INSN_I32_STORE16: + case WASM_INSN_I64_STORE16: + case WASM_INSN_I32_ATOMIC_LOAD16_U: + case WASM_INSN_I64_ATOMIC_LOAD16_U: + case WASM_INSN_I32_ATOMIC_STORE16: + case WASM_INSN_I64_ATOMIC_STORE16: + return 2; + case WASM_INSN_I32_LOAD: + case WASM_INSN_F32_LOAD: + case WASM_INSN_I64_LOAD32_S: + case WASM_INSN_I64_LOAD32_U: + case WASM_INSN_I32_STORE: + case WASM_INSN_F32_STORE: + case WASM_INSN_I64_STORE32: + case WASM_INSN_I32_ATOMIC_LOAD: + case WASM_INSN_I64_ATOMIC_LOAD32_U: + case WASM_INSN_I32_ATOMIC_STORE: + case WASM_INSN_I64_ATOMIC_STORE32: + case WASM_INSN_I32_ATOMIC_RMW_ADD: + case WASM_INSN_I32_ATOMIC_RMW_SUB: + case WASM_INSN_I32_ATOMIC_RMW_AND: + case WASM_INSN_I32_ATOMIC_RMW_OR: + case WASM_INSN_I32_ATOMIC_RMW_XOR: + case WASM_INSN_I32_ATOMIC_RMW_XCHG: + case WASM_INSN_I32_ATOMIC_RMW_CMPXCHG: + case WASM_INSN_I32_ATOMIC_WAIT: + case WASM_INSN_MEMORY_ATOMIC_NOTIFY: + return 4; + default: + return 8; + } +} + + +int wasm_int_cmp_op(uint8_t kind, CfreeCgIntCmpOp* out) { + switch (kind) { + case WASM_INSN_I32_EQ: + case WASM_INSN_I64_EQ: + *out = CFREE_CG_INT_EQ; + return 1; + case WASM_INSN_I32_NE: + case WASM_INSN_I64_NE: + *out = CFREE_CG_INT_NE; + return 1; + case WASM_INSN_I32_LT_S: + case WASM_INSN_I64_LT_S: + *out = CFREE_CG_INT_LT_S; + return 1; + case WASM_INSN_I32_LT_U: + case WASM_INSN_I64_LT_U: + *out = CFREE_CG_INT_LT_U; + return 1; + case WASM_INSN_I32_GT_S: + case WASM_INSN_I64_GT_S: + *out = CFREE_CG_INT_GT_S; + return 1; + case WASM_INSN_I32_GT_U: + case WASM_INSN_I64_GT_U: + *out = CFREE_CG_INT_GT_U; + return 1; + case WASM_INSN_I32_LE_S: + case WASM_INSN_I64_LE_S: + *out = CFREE_CG_INT_LE_S; + return 1; + case WASM_INSN_I32_LE_U: + case WASM_INSN_I64_LE_U: + *out = CFREE_CG_INT_LE_U; + return 1; + case WASM_INSN_I32_GE_S: + case WASM_INSN_I64_GE_S: + *out = CFREE_CG_INT_GE_S; + return 1; + case WASM_INSN_I32_GE_U: + case WASM_INSN_I64_GE_U: + *out = CFREE_CG_INT_GE_U; + return 1; + default: + return 0; + } +} + +WasmValType wasm_load_result_type(uint8_t kind) { + switch (kind) { + case WASM_INSN_F32_LOAD: + return WASM_VAL_F32; + case WASM_INSN_F64_LOAD: + return WASM_VAL_F64; + case WASM_INSN_I64_LOAD: + case WASM_INSN_I64_LOAD8_S: + case WASM_INSN_I64_LOAD8_U: + case WASM_INSN_I64_LOAD16_S: + case WASM_INSN_I64_LOAD16_U: + case WASM_INSN_I64_LOAD32_S: + case WASM_INSN_I64_LOAD32_U: + case WASM_INSN_I64_ATOMIC_LOAD: + case WASM_INSN_I64_ATOMIC_LOAD8_U: + case WASM_INSN_I64_ATOMIC_LOAD16_U: + case WASM_INSN_I64_ATOMIC_LOAD32_U: + return WASM_VAL_I64; + default: + return WASM_VAL_I32; + } +} + +WasmValType wasm_store_value_type(uint8_t kind) { + switch (kind) { + case WASM_INSN_F32_STORE: + return WASM_VAL_F32; + case WASM_INSN_F64_STORE: + return WASM_VAL_F64; + case WASM_INSN_I64_STORE: + case WASM_INSN_I64_STORE8: + case WASM_INSN_I64_STORE16: + case WASM_INSN_I64_STORE32: + case WASM_INSN_I64_ATOMIC_STORE: + case WASM_INSN_I64_ATOMIC_STORE8: + case WASM_INSN_I64_ATOMIC_STORE16: + case WASM_INSN_I64_ATOMIC_STORE32: + return WASM_VAL_I64; + default: + return WASM_VAL_I32; + } +} + +WasmValType wasm_atomic_value_type(uint8_t kind) { + switch (kind) { + case WASM_INSN_I64_ATOMIC_LOAD: + case WASM_INSN_I64_ATOMIC_LOAD8_U: + case WASM_INSN_I64_ATOMIC_LOAD16_U: + case WASM_INSN_I64_ATOMIC_LOAD32_U: + case WASM_INSN_I64_ATOMIC_STORE: + case WASM_INSN_I64_ATOMIC_STORE8: + case WASM_INSN_I64_ATOMIC_STORE16: + case WASM_INSN_I64_ATOMIC_STORE32: + case WASM_INSN_I64_ATOMIC_RMW_ADD: + case WASM_INSN_I64_ATOMIC_RMW_SUB: + case WASM_INSN_I64_ATOMIC_RMW_AND: + case WASM_INSN_I64_ATOMIC_RMW_OR: + case WASM_INSN_I64_ATOMIC_RMW_XOR: + case WASM_INSN_I64_ATOMIC_RMW_XCHG: + case WASM_INSN_I64_ATOMIC_RMW_CMPXCHG: + case WASM_INSN_I64_ATOMIC_WAIT: + return WASM_VAL_I64; + default: + return WASM_VAL_I32; + } +} + +CfreeCgAtomicOp wasm_atomic_rmw_op(uint8_t kind) { + switch (kind) { + case WASM_INSN_I32_ATOMIC_RMW_ADD: + case WASM_INSN_I64_ATOMIC_RMW_ADD: + return CFREE_CG_ATOMIC_ADD; + case WASM_INSN_I32_ATOMIC_RMW_SUB: + case WASM_INSN_I64_ATOMIC_RMW_SUB: + return CFREE_CG_ATOMIC_SUB; + case WASM_INSN_I32_ATOMIC_RMW_AND: + case WASM_INSN_I64_ATOMIC_RMW_AND: + return CFREE_CG_ATOMIC_AND; + case WASM_INSN_I32_ATOMIC_RMW_OR: + case WASM_INSN_I64_ATOMIC_RMW_OR: + return CFREE_CG_ATOMIC_OR; + case WASM_INSN_I32_ATOMIC_RMW_XOR: + case WASM_INSN_I64_ATOMIC_RMW_XOR: + return CFREE_CG_ATOMIC_XOR; + default: + return CFREE_CG_ATOMIC_XCHG; + } +} + +int wasm_int_unop_kind(uint8_t kind, WasmValType* vt) { + if (kind == WASM_INSN_I32_CLZ || kind == WASM_INSN_I32_CTZ || + kind == WASM_INSN_I32_POPCNT) { + *vt = WASM_VAL_I32; + return 1; + } + if (kind == WASM_INSN_I64_CLZ || kind == WASM_INSN_I64_CTZ || + kind == WASM_INSN_I64_POPCNT) { + *vt = WASM_VAL_I64; + return 1; + } + return 0; +} + +int wasm_fp_binop_kind(uint8_t kind, WasmValType* vt) { + if (kind == WASM_INSN_F32_ADD || kind == WASM_INSN_F32_SUB || + kind == WASM_INSN_F32_MUL || kind == WASM_INSN_F32_DIV) { + *vt = WASM_VAL_F32; + return 1; + } + if (kind == WASM_INSN_F64_ADD || kind == WASM_INSN_F64_SUB || + kind == WASM_INSN_F64_MUL || kind == WASM_INSN_F64_DIV) { + *vt = WASM_VAL_F64; + return 1; + } + return 0; +} + +int wasm_fp_cmp_kind(uint8_t kind, WasmValType* vt) { + if (kind == WASM_INSN_F32_EQ || kind == WASM_INSN_F32_NE || + kind == WASM_INSN_F32_LT || kind == WASM_INSN_F32_GT || + kind == WASM_INSN_F32_LE || kind == WASM_INSN_F32_GE) { + *vt = WASM_VAL_F32; + return 1; + } + if (kind == WASM_INSN_F64_EQ || kind == WASM_INSN_F64_NE || + kind == WASM_INSN_F64_LT || kind == WASM_INSN_F64_GT || + kind == WASM_INSN_F64_LE || kind == WASM_INSN_F64_GE) { + *vt = WASM_VAL_F64; + return 1; + } + return 0; +} + +int wasm_conversion_kind(uint8_t kind, WasmValType* src, + WasmValType* dst) { + switch (kind) { + case WASM_INSN_I32_WRAP_I64: + *src = WASM_VAL_I64; + *dst = WASM_VAL_I32; + return 1; + case WASM_INSN_I64_EXTEND_I32_S: + case WASM_INSN_I64_EXTEND_I32_U: + *src = WASM_VAL_I32; + *dst = WASM_VAL_I64; + return 1; + case WASM_INSN_I32_TRUNC_F32_S: + case WASM_INSN_I32_TRUNC_F32_U: + *src = WASM_VAL_F32; + *dst = WASM_VAL_I32; + return 1; + case WASM_INSN_I32_TRUNC_F64_S: + case WASM_INSN_I32_TRUNC_F64_U: + *src = WASM_VAL_F64; + *dst = WASM_VAL_I32; + return 1; + case WASM_INSN_I64_TRUNC_F32_S: + case WASM_INSN_I64_TRUNC_F32_U: + *src = WASM_VAL_F32; + *dst = WASM_VAL_I64; + return 1; + case WASM_INSN_I64_TRUNC_F64_S: + case WASM_INSN_I64_TRUNC_F64_U: + *src = WASM_VAL_F64; + *dst = WASM_VAL_I64; + return 1; + case WASM_INSN_F32_CONVERT_I32_S: + case WASM_INSN_F32_CONVERT_I32_U: + *src = WASM_VAL_I32; + *dst = WASM_VAL_F32; + return 1; + case WASM_INSN_F32_CONVERT_I64_S: + case WASM_INSN_F32_CONVERT_I64_U: + *src = WASM_VAL_I64; + *dst = WASM_VAL_F32; + return 1; + case WASM_INSN_F64_CONVERT_I32_S: + case WASM_INSN_F64_CONVERT_I32_U: + *src = WASM_VAL_I32; + *dst = WASM_VAL_F64; + return 1; + case WASM_INSN_F64_CONVERT_I64_S: + case WASM_INSN_F64_CONVERT_I64_U: + *src = WASM_VAL_I64; + *dst = WASM_VAL_F64; + return 1; + case WASM_INSN_F32_DEMOTE_F64: + *src = WASM_VAL_F64; + *dst = WASM_VAL_F32; + return 1; + case WASM_INSN_F64_PROMOTE_F32: + *src = WASM_VAL_F32; + *dst = WASM_VAL_F64; + return 1; + case WASM_INSN_I32_REINTERPRET_F32: + *src = WASM_VAL_F32; + *dst = WASM_VAL_I32; + return 1; + case WASM_INSN_I64_REINTERPRET_F64: + *src = WASM_VAL_F64; + *dst = WASM_VAL_I64; + return 1; + case WASM_INSN_F32_REINTERPRET_I32: + *src = WASM_VAL_I32; + *dst = WASM_VAL_F32; + return 1; + case WASM_INSN_F64_REINTERPRET_I64: + *src = WASM_VAL_I64; + *dst = WASM_VAL_F64; + return 1; + default: + return 0; + } +} diff --git a/src/wasm/module.c b/src/wasm/module.c @@ -0,0 +1,446 @@ +#include "wasm/wasm.h" + +CfreeSrcLoc wasm_loc(uint32_t line, uint32_t col) { + CfreeSrcLoc loc; + loc.file_id = 0; + loc.line = line; + loc.col = col; + return loc; +} + +void wasm_error(CfreeCompiler* c, CfreeSrcLoc loc, const char* fmt, + ...) { + va_list ap; + va_start(ap, fmt); + cfree_frontend_vfatal(c, loc, fmt, ap); +} + +void* wasm_realloc(CfreeHeap* h, void* p, size_t old_n, size_t new_n) { + return h->realloc(h, p, old_n ? old_n : 1u, new_n ? new_n : 1u, + _Alignof(max_align_t)); +} + +char* wasm_strdup(CfreeHeap* h, const char* s, size_t len) { + char* out; + if (!h || !s) return NULL; + out = (char*)h->alloc(h, len + 1u, 1); + if (!out) return NULL; + memcpy(out, s, len); + out[len] = '\0'; + return out; +} + +void wasm_free_str(CfreeHeap* h, char** s) { + if (!h || !s || !*s) return; + h->free(h, *s, strlen(*s) + 1u); + *s = NULL; +} + +void wasm_module_init(WasmModule* m, CfreeHeap* heap) { + memset(m, 0, sizeof *m); + m->heap = heap; + m->features = WASM_FEATURE_THREADS | WASM_FEATURE_TYPED_FUNC_REFS | + WASM_FEATURE_TAIL_CALLS | WASM_FEATURE_MULTI_MEMORY | + WASM_FEATURE_MEMORY64 | WASM_FEATURE_BULK_MEMORY | + WASM_FEATURE_NONTRAPPING_FTOI; +} + +void wasm_module_free(WasmModule* m) { + uint32_t i; + if (!m || !m->heap) return; + for (i = 0; i < m->ntypes; ++i) { + WasmFuncType* t = &m->types[i]; + wasm_free_str(m->heap, &t->name); + if (t->params) + m->heap->free(m->heap, t->params, sizeof(*t->params) * t->cap_params); + } + if (m->types) + m->heap->free(m->heap, m->types, sizeof(*m->types) * m->cap_types); + for (i = 0; i < m->nfuncs; ++i) { + WasmFunc* f = &m->funcs[i]; + wasm_free_str(m->heap, &f->name); + wasm_free_str(m->heap, &f->import_module); + wasm_free_str(m->heap, &f->import_name); + wasm_free_str(m->heap, &f->export_name); + if (f->local_names) { + for (uint32_t j = 0; j < f->cap_local_names; ++j) + wasm_free_str(m->heap, &f->local_names[j]); + m->heap->free(m->heap, f->local_names, + sizeof(*f->local_names) * f->cap_local_names); + } + if (f->params) + m->heap->free(m->heap, f->params, sizeof(*f->params) * f->cap_params); + if (f->locals) + m->heap->free(m->heap, f->locals, sizeof(*f->locals) * f->cap_locals); + if (f->insns) + m->heap->free(m->heap, f->insns, sizeof(*f->insns) * f->cap_insns); + } + if (m->funcs) + m->heap->free(m->heap, m->funcs, sizeof(*m->funcs) * m->cap_funcs); + for (i = 0; i < m->nmemories; ++i) { + wasm_free_str(m->heap, &m->memories[i].name); + wasm_free_str(m->heap, &m->memories[i].import_module); + wasm_free_str(m->heap, &m->memories[i].import_name); + wasm_free_str(m->heap, &m->memories[i].export_name); + } + if (m->memories) + m->heap->free(m->heap, m->memories, sizeof(*m->memories) * m->cap_memories); + for (i = 0; i < m->ndata; ++i) { + wasm_free_str(m->heap, &m->data[i].name); + if (m->data[i].bytes) + m->heap->free(m->heap, m->data[i].bytes, (size_t)m->data[i].nbytes); + } + if (m->data) + m->heap->free(m->heap, m->data, sizeof(*m->data) * m->cap_data); + for (i = 0; i < m->nelems; ++i) { + wasm_free_str(m->heap, &m->elems[i].name); + if (m->elems[i].funcs) + m->heap->free(m->heap, m->elems[i].funcs, + sizeof(*m->elems[i].funcs) * m->elems[i].cap_funcs); + } + for (i = 0; i < m->ntables; ++i) { + wasm_free_str(m->heap, &m->tables[i].name); + wasm_free_str(m->heap, &m->tables[i].import_module); + wasm_free_str(m->heap, &m->tables[i].import_name); + wasm_free_str(m->heap, &m->tables[i].export_name); + } + if (m->tables) + m->heap->free(m->heap, m->tables, sizeof(*m->tables) * m->cap_tables); + for (i = 0; i < m->nglobals; ++i) { + wasm_free_str(m->heap, &m->globals[i].name); + wasm_free_str(m->heap, &m->globals[i].import_module); + wasm_free_str(m->heap, &m->globals[i].import_name); + wasm_free_str(m->heap, &m->globals[i].export_name); + } + if (m->globals) + m->heap->free(m->heap, m->globals, sizeof(*m->globals) * m->cap_globals); + if (m->elems) + m->heap->free(m->heap, m->elems, sizeof(*m->elems) * m->cap_elems); + if (m->exports) + m->heap->free(m->heap, m->exports, sizeof(*m->exports) * m->cap_exports); + for (i = 0; i < m->ncustoms; ++i) { + wasm_free_str(m->heap, &m->customs[i].name); + if (m->customs[i].data) + m->heap->free(m->heap, m->customs[i].data, m->customs[i].len); + } + if (m->customs) + m->heap->free(m->heap, m->customs, sizeof(*m->customs) * m->cap_customs); + memset(m, 0, sizeof *m); +} + +WasmMemory* wasm_add_memory(CfreeCompiler* c, WasmModule* m) { + WasmMemory* mem; + if (m->nmemories == m->cap_memories) { + uint32_t new_cap = m->cap_memories ? m->cap_memories * 2u : 2u; + void* p = + wasm_realloc(m->heap, m->memories, + sizeof(*m->memories) * m->cap_memories, + sizeof(*m->memories) * new_cap); + if (!p) wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); + m->memories = (WasmMemory*)p; + memset(m->memories + m->cap_memories, 0, + sizeof(*m->memories) * (new_cap - m->cap_memories)); + m->cap_memories = new_cap; + } + mem = &m->memories[m->nmemories++]; + memset(mem, 0, sizeof *mem); + return mem; +} + +WasmFunc* wasm_add_func(CfreeCompiler* c, WasmModule* m) { + WasmFunc* f; + if (m->nfuncs == m->cap_funcs) { + uint32_t new_cap = m->cap_funcs ? m->cap_funcs * 2u : 4u; + void* p = wasm_realloc(m->heap, m->funcs, sizeof(*m->funcs) * m->cap_funcs, + sizeof(*m->funcs) * new_cap); + if (!p) wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); + m->funcs = (WasmFunc*)p; + memset(m->funcs + m->cap_funcs, 0, + sizeof(*m->funcs) * (new_cap - m->cap_funcs)); + m->cap_funcs = new_cap; + } + f = &m->funcs[m->nfuncs++]; + memset(f, 0, sizeof *f); + return f; +} + +WasmFuncType* wasm_add_type(CfreeCompiler* c, WasmModule* m) { + WasmFuncType* t; + if (m->ntypes == m->cap_types) { + uint32_t new_cap = m->cap_types ? m->cap_types * 2u : 8u; + void* p = wasm_realloc(m->heap, m->types, sizeof(*m->types) * m->cap_types, + sizeof(*m->types) * new_cap); + if (!p) wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); + m->types = (WasmFuncType*)p; + memset(m->types + m->cap_types, 0, + sizeof(*m->types) * (new_cap - m->cap_types)); + m->cap_types = new_cap; + } + t = &m->types[m->ntypes++]; + memset(t, 0, sizeof *t); + return t; +} + +static void wasm_grow_valtypes(CfreeCompiler* c, WasmModule* m, + WasmValType** arr, uint32_t* cap, + uint32_t want) { + uint32_t new_cap; + void* p; + if (want <= *cap) return; + new_cap = *cap ? *cap : 4u; + while (new_cap < want) new_cap *= 2u; + p = wasm_realloc(m->heap, *arr, sizeof(WasmValType) * (*cap), + sizeof(WasmValType) * new_cap); + if (!p) wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); + *arr = (WasmValType*)p; + *cap = new_cap; +} + +uint32_t wasm_func_push_param(CfreeCompiler* c, WasmModule* m, WasmFunc* f, + WasmValType vt) { + wasm_grow_valtypes(c, m, &f->params, &f->cap_params, f->nparams + 1u); + f->params[f->nparams] = vt; + return f->nparams++; +} + +uint32_t wasm_func_push_local(CfreeCompiler* c, WasmModule* m, WasmFunc* f, + WasmValType vt) { + wasm_grow_valtypes(c, m, &f->locals, &f->cap_locals, f->nlocals + 1u); + f->locals[f->nlocals] = vt; + return f->nparams + f->nlocals++; +} + +void wasm_func_set_params(CfreeCompiler* c, WasmModule* m, WasmFunc* f, + const WasmValType* src, uint32_t n) { + wasm_grow_valtypes(c, m, &f->params, &f->cap_params, n); + if (n) memcpy(f->params, src, sizeof(WasmValType) * n); + f->nparams = n; +} + +void wasm_func_set_local_name(CfreeCompiler* c, WasmModule* m, WasmFunc* f, + uint32_t idx, const char* name, size_t len) { + if (idx >= f->cap_local_names) { + uint32_t new_cap = f->cap_local_names ? f->cap_local_names * 2u : 8u; + void* p; + while (new_cap <= idx) new_cap *= 2u; + p = wasm_realloc(m->heap, f->local_names, + sizeof(char*) * f->cap_local_names, + sizeof(char*) * new_cap); + if (!p) wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); + f->local_names = (char**)p; + memset(f->local_names + f->cap_local_names, 0, + sizeof(char*) * (new_cap - f->cap_local_names)); + f->cap_local_names = new_cap; + } + wasm_free_str(m->heap, &f->local_names[idx]); + f->local_names[idx] = wasm_strdup(m->heap, name, len); + if (!f->local_names[idx]) + wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); +} + +uint32_t wasm_type_push_param(CfreeCompiler* c, WasmModule* m, WasmFuncType* t, + WasmValType vt) { + wasm_grow_valtypes(c, m, &t->params, &t->cap_params, t->nparams + 1u); + t->params[t->nparams] = vt; + return t->nparams++; +} + +uint32_t wasm_intern_func_type(CfreeCompiler* c, WasmModule* m, + const WasmFunc* f) { + uint32_t i; + for (i = 0; i < m->ntypes; ++i) { + WasmFuncType* t = &m->types[i]; + if (t->nparams == f->nparams && t->nresults == f->nresults && + (t->nparams == 0 || + memcmp(t->params, f->params, sizeof(t->params[0]) * t->nparams) == + 0) && + memcmp(t->results, f->results, sizeof(t->results[0]) * t->nresults) == + 0) + return i; + } + { + WasmFuncType* t = wasm_add_type(c, m); + wasm_grow_valtypes(c, m, &t->params, &t->cap_params, f->nparams); + t->nparams = f->nparams; + if (f->nparams) + memcpy(t->params, f->params, sizeof(t->params[0]) * f->nparams); + t->nresults = f->nresults; + memcpy(t->results, f->results, sizeof(t->results[0]) * f->nresults); + return m->ntypes - 1u; + } +} + +WasmTable* wasm_add_table(CfreeCompiler* c, WasmModule* m) { + WasmTable* t; + if (m->ntables == m->cap_tables) { + uint32_t new_cap = m->cap_tables ? m->cap_tables * 2u : 2u; + void* p = + wasm_realloc(m->heap, m->tables, sizeof(*m->tables) * m->cap_tables, + sizeof(*m->tables) * new_cap); + if (!p) wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); + m->tables = (WasmTable*)p; + memset(m->tables + m->cap_tables, 0, + sizeof(*m->tables) * (new_cap - m->cap_tables)); + m->cap_tables = new_cap; + } + t = &m->tables[m->ntables++]; + memset(t, 0, sizeof *t); + return t; +} + +WasmGlobal* wasm_add_global(CfreeCompiler* c, WasmModule* m) { + WasmGlobal* g; + if (m->nglobals == m->cap_globals) { + uint32_t new_cap = m->cap_globals ? m->cap_globals * 2u : 4u; + void* p = + wasm_realloc(m->heap, m->globals, sizeof(*m->globals) * m->cap_globals, + sizeof(*m->globals) * new_cap); + if (!p) wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); + m->globals = (WasmGlobal*)p; + memset(m->globals + m->cap_globals, 0, + sizeof(*m->globals) * (new_cap - m->cap_globals)); + m->cap_globals = new_cap; + } + g = &m->globals[m->nglobals++]; + memset(g, 0, sizeof *g); + return g; +} + +WasmElemSegment* wasm_add_elem(CfreeCompiler* c, WasmModule* m) { + WasmElemSegment* e; + if (m->nelems == m->cap_elems) { + uint32_t new_cap = m->cap_elems ? m->cap_elems * 2u : 4u; + void* p = wasm_realloc(m->heap, m->elems, sizeof(*m->elems) * m->cap_elems, + sizeof(*m->elems) * new_cap); + if (!p) wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); + m->elems = (WasmElemSegment*)p; + memset(m->elems + m->cap_elems, 0, + sizeof(*m->elems) * (new_cap - m->cap_elems)); + m->cap_elems = new_cap; + } + e = &m->elems[m->nelems++]; + memset(e, 0, sizeof *e); + return e; +} + +WasmDataSegment* wasm_add_data(CfreeCompiler* c, WasmModule* m) { + WasmDataSegment* d; + if (m->ndata == m->cap_data) { + uint32_t new_cap = m->cap_data ? m->cap_data * 2u : 4u; + void* p = wasm_realloc(m->heap, m->data, sizeof(*m->data) * m->cap_data, + sizeof(*m->data) * new_cap); + if (!p) wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); + m->data = (WasmDataSegment*)p; + memset(m->data + m->cap_data, 0, + sizeof(*m->data) * (new_cap - m->cap_data)); + m->cap_data = new_cap; + } + d = &m->data[m->ndata++]; + memset(d, 0, sizeof *d); + return d; +} + +void wasm_data_set_bytes(CfreeCompiler* c, WasmModule* m, WasmDataSegment* d, + const uint8_t* src, uint64_t n) { + void* p; + if (n > SIZE_MAX) wasm_error(c, wasm_loc(0, 0), "wasm: data segment too large"); + if (d->bytes && d->nbytes) + m->heap->free(m->heap, d->bytes, (size_t)d->nbytes); + d->bytes = NULL; + d->nbytes = 0; + if (!n) return; + p = m->heap->alloc(m->heap, (size_t)n, 1); + if (!p) wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); + if (src) memcpy(p, src, (size_t)n); + else memset(p, 0, (size_t)n); + d->bytes = (uint8_t*)p; + d->nbytes = n; +} + +void wasm_elem_push_func(CfreeCompiler* c, WasmModule* m, WasmElemSegment* e, + uint32_t funcidx) { + if (e->nfuncs == e->cap_funcs) { + uint32_t new_cap = e->cap_funcs ? e->cap_funcs * 2u : 8u; + void* p = wasm_realloc(m->heap, e->funcs, sizeof(*e->funcs) * e->cap_funcs, + sizeof(*e->funcs) * new_cap); + if (!p) wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); + e->funcs = (uint32_t*)p; + e->cap_funcs = new_cap; + } + e->funcs[e->nfuncs++] = funcidx; +} + +WasmExport* wasm_add_export(CfreeCompiler* c, WasmModule* m) { + WasmExport* e; + if (m->nexports == m->cap_exports) { + uint32_t new_cap = m->cap_exports ? m->cap_exports * 2u : 8u; + void* p = + wasm_realloc(m->heap, m->exports, sizeof(*m->exports) * m->cap_exports, + sizeof(*m->exports) * new_cap); + if (!p) wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); + m->exports = (WasmExport*)p; + memset(m->exports + m->cap_exports, 0, + sizeof(*m->exports) * (new_cap - m->cap_exports)); + m->cap_exports = new_cap; + } + e = &m->exports[m->nexports++]; + memset(e, 0, sizeof *e); + return e; +} + +WasmCustom* wasm_add_custom(CfreeCompiler* c, WasmModule* m) { + WasmCustom* cs; + if (m->ncustoms == m->cap_customs) { + uint32_t new_cap = m->cap_customs ? m->cap_customs * 2u : 4u; + void* p = + wasm_realloc(m->heap, m->customs, sizeof(*m->customs) * m->cap_customs, + sizeof(*m->customs) * new_cap); + if (!p) wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); + m->customs = (WasmCustom*)p; + memset(m->customs + m->cap_customs, 0, + sizeof(*m->customs) * (new_cap - m->cap_customs)); + m->cap_customs = new_cap; + } + cs = &m->customs[m->ncustoms++]; + memset(cs, 0, sizeof *cs); + return cs; +} + +void wasm_func_add_insn(CfreeCompiler* c, WasmModule* m, WasmFunc* f, + WasmInsnKind kind, int64_t imm) { + if (f->ninsns == f->cap_insns) { + uint32_t new_cap = f->cap_insns ? f->cap_insns * 2u : 16u; + void* p = wasm_realloc(m->heap, f->insns, sizeof(*f->insns) * f->cap_insns, + sizeof(*f->insns) * new_cap); + if (!p) wasm_error(c, wasm_loc(0, 0), "wasm: out of memory"); + f->insns = (WasmInsn*)p; + f->cap_insns = new_cap; + } + f->insns[f->ninsns].loc = m ? m->current_loc : wasm_loc(0, 0); + f->insns[f->ninsns].kind = (uint8_t)kind; + f->insns[f->ninsns].type = 0; + f->insns[f->ninsns].imm = imm; + f->insns[f->ninsns].fp = 0.0; + f->insns[f->ninsns].align = 0; + f->insns[f->ninsns].memidx = 0; + f->insns[f->ninsns].offset64 = imm < 0 ? 0 : (uint64_t)imm; + f->insns[f->ninsns].aux_idx = 0; + f->insns[f->ninsns].ntargets = 0; + f->ninsns++; +} + +void wasm_func_add_mem_insn(CfreeCompiler* c, WasmModule* m, WasmFunc* f, + WasmInsnKind kind, uint32_t align, + uint64_t offset, uint32_t memidx) { + wasm_func_add_insn(c, m, f, kind, offset > INT64_MAX ? INT64_MAX + : (int64_t)offset); + f->insns[f->ninsns - 1u].align = align; + f->insns[f->ninsns - 1u].offset64 = offset; + f->insns[f->ninsns - 1u].memidx = memidx; +} + +void wasm_func_add_fp_insn(CfreeCompiler* c, WasmModule* m, WasmFunc* f, + WasmInsnKind kind, double value) { + wasm_func_add_insn(c, m, f, kind, 0); + f->insns[f->ninsns - 1u].fp = value; +} diff --git a/src/wasm/validate.c b/src/wasm/validate.c @@ -0,0 +1,831 @@ +#include "wasm/wasm.h" + +typedef struct WasmValStack { + WasmValType vals[256]; + uint32_t depth; + CfreeSrcLoc loc; +} WasmValStack; + +typedef struct WasmControlFrame { + uint8_t kind; + uint32_t height; + int seen_else; + int unreachable; +} WasmControlFrame; + +static WasmValType wasm_global_init_type(const WasmInsn* in) { + switch (in->kind) { + case WASM_INSN_I32_CONST: + return WASM_VAL_I32; + case WASM_INSN_I64_CONST: + return WASM_VAL_I64; + case WASM_INSN_F32_CONST: + return WASM_VAL_F32; + case WASM_INSN_F64_CONST: + return WASM_VAL_F64; + default: + return 0; + } +} + +static void wasm_stack_push(CfreeCompiler* c, WasmValStack* s, WasmValType vt) { + if (s->depth >= 256u) + wasm_error(c, s->loc, "wasm: operand stack too deep"); + s->vals[s->depth++] = vt; +} + +static int wasm_stack_pop(CfreeCompiler* c, WasmValStack* s, + WasmControlFrame* frames, uint32_t nframes, + WasmValType expected, const char* what) { + WasmControlFrame* top = &frames[nframes - 1u]; + if (s->depth <= top->height) { + if (top->unreachable) return 1; + wasm_error(c, s->loc, "wasm: operand stack underflow"); + } + if (expected && s->vals[s->depth - 1u] != expected) + wasm_error(c, s->loc, "wasm: %s type mismatch (expected=0x%x got=0x%x)", + what, (unsigned)expected, + (unsigned)s->vals[s->depth - 1u]); + s->depth--; + return 1; +} + +static WasmValType wasm_stack_pop_any(CfreeCompiler* c, WasmValStack* s, + WasmControlFrame* frames, + uint32_t nframes, const char* what) { + WasmControlFrame* top = &frames[nframes - 1u]; + WasmValType vt; + if (s->depth <= top->height) { + if (top->unreachable) return WASM_VAL_I32; + wasm_error(c, s->loc, "wasm: operand stack underflow"); + } + vt = s->vals[s->depth - 1u]; + if (!vt) wasm_error(c, s->loc, "wasm: %s type mismatch", what); + s->depth--; + return vt; +} + +static void wasm_stack_pop_ref(CfreeCompiler* c, WasmValStack* s, + WasmControlFrame* frames, uint32_t nframes, + const char* what) { + WasmValType vt = wasm_stack_pop_any(c, s, frames, nframes, what); + if (!wasm_is_ref_type(vt)) + wasm_error(c, s->loc, "wasm: %s type mismatch", what); +} + +static void wasm_mark_unreachable(WasmValStack* s, WasmControlFrame* frames, + uint32_t nframes) { + WasmControlFrame* top = &frames[nframes - 1u]; + s->depth = top->height; + top->unreachable = 1; +} + +void wasm_validate(WasmModule* m, CfreeCompiler* c) { + uint32_t i, j; + for (i = 0; i < m->ntypes; ++i) { + for (j = 0; j < m->types[i].nparams; ++j) + if (!wasm_is_frontend_value_type(m->types[i].params[j])) + wasm_error(c, wasm_loc(0, 0), "wasm: unsupported parameter type"); + for (j = 0; j < m->types[i].nresults; ++j) + if (!wasm_is_frontend_value_type(m->types[i].results[j])) + wasm_error(c, wasm_loc(0, 0), "wasm: unsupported result type"); + } + for (i = 0; i < m->nmemories; ++i) { + if (m->memories[i].has_max && + m->memories[i].max_pages < m->memories[i].min_pages) + wasm_error(c, wasm_loc(0, 0), "wasm: memory maximum below minimum"); + if (m->memories[i].shared && !m->memories[i].has_max) + wasm_error(c, wasm_loc(0, 0), + "wasm: shared memory requires maximum"); + } + for (i = 0; i < m->ntables; ++i) { + if (m->tables[i].elem_type != WASM_VAL_FUNCREF) + wasm_error(c, wasm_loc(0, 0), + "wasm: reference type is unsupported for tables"); + if (m->tables[i].has_max && m->tables[i].max < m->tables[i].min) + wasm_error(c, wasm_loc(0, 0), "wasm: table maximum below minimum"); + } + for (i = 0; i < m->nglobals; ++i) { + WasmGlobal* g = &m->globals[i]; + if (!wasm_is_num_type(g->type)) + wasm_error(c, wasm_loc(0, 0), "wasm: unsupported global type"); + if (!g->is_import && wasm_global_init_type(&g->init) != g->type) + wasm_error(c, wasm_loc(0, 0), "wasm: global initializer type mismatch"); + } + for (i = 0; i < m->nexports; ++i) { + WasmExport* ex = &m->exports[i]; + if ((ex->kind == 0 && ex->index >= m->nfuncs) || + (ex->kind == 1 && ex->index >= m->ntables) || + (ex->kind == 2 && ex->index >= m->nmemories) || + (ex->kind == 3 && ex->index >= m->nglobals)) + wasm_error(c, wasm_loc(0, 0), "wasm: export index out of range"); + } + if (m->has_start) { + if (m->start_func >= m->nfuncs) + wasm_error(c, wasm_loc(0, 0), "wasm: start function index out of range"); + if (m->funcs[m->start_func].nparams || m->funcs[m->start_func].nresults) + wasm_error(c, wasm_loc(0, 0), + "wasm: start function must have no params or results"); + } + 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"); + for (j = 0; j < m->elems[i].nfuncs; ++j) + if (m->elems[i].funcs[j] >= m->nfuncs) + wasm_error(c, wasm_loc(0, 0), + "wasm: element function index out of range"); + } + for (i = 0; i < m->nfuncs; ++i) wasm_validate_func(c, m, &m->funcs[i]); +} + +void wasm_validate_func(CfreeCompiler* c, WasmModule* m, WasmFunc* f) { + WasmValStack stack; + WasmControlFrame control[65]; + uint32_t ncontrol = 1; + uint32_t j; + memset(&stack, 0, sizeof stack); + memset(control, 0, sizeof control); + control[0].kind = 0xffu; + control[0].height = 0; + if (f->is_import) { + if (f->ninsns) + wasm_error(c, wasm_loc(0, 0), "wasm: imported function has body"); + return; + } + if (f->nresults > 1u) + wasm_error(c, wasm_loc(0, 0), "wasm: multi-result unsupported"); + { + for (j = 0; j < f->ninsns; ++j) { + WasmInsn* in = &f->insns[j]; + WasmValType vt, src, dst; + stack.loc = in->loc; +#define wasm_loc(line, col) (in->loc) + switch (in->kind) { + case WASM_INSN_F32_CONST: + wasm_stack_push(c, &stack, WASM_VAL_F32); + break; + case WASM_INSN_F64_CONST: + wasm_stack_push(c, &stack, WASM_VAL_F64); + break; + case WASM_INSN_I32_CONST: + wasm_stack_push(c, &stack, WASM_VAL_I32); + break; + case WASM_INSN_I64_CONST: + wasm_stack_push(c, &stack, WASM_VAL_I64); + break; + case WASM_INSN_LOCAL_GET: + if (in->imm < 0 || + (uint64_t)in->imm >= (uint64_t)f->nparams + f->nlocals) + wasm_error(c, wasm_loc(0, 0), "wasm: local index out of range"); + wasm_stack_push(c, &stack, + wasm_func_local_type(f, (uint32_t)in->imm)); + break; + case WASM_INSN_LOCAL_SET: + case WASM_INSN_LOCAL_TEE: + if (in->imm < 0 || + (uint64_t)in->imm >= (uint64_t)f->nparams + f->nlocals) + wasm_error(c, wasm_loc(0, 0), "wasm: local index out of range"); + wasm_stack_pop(c, &stack, control, ncontrol, + wasm_func_local_type(f, (uint32_t)in->imm), "local"); + if (in->kind == WASM_INSN_LOCAL_TEE) + wasm_stack_push(c, &stack, + wasm_func_local_type(f, (uint32_t)in->imm)); + break; + case WASM_INSN_CALL: + case WASM_INSN_RETURN_CALL: + if (in->imm < 0 || (uint64_t)in->imm >= m->nfuncs) + wasm_error(c, wasm_loc(0, 0), "wasm: call index out of range"); + if (in->kind == WASM_INSN_RETURN_CALL) { + wasm_require_feature(c, m, WASM_FEATURE_TAIL_CALLS, "tail calls", + "return_call"); + if (m->funcs[in->imm].nresults != f->nresults || + (f->nresults && + m->funcs[in->imm].results[0] != f->results[0])) + wasm_error(c, wasm_loc(0, 0), + "wasm: return_call result type mismatch"); + } + for (uint32_t k = 0; k < m->funcs[in->imm].nparams; ++k) { + uint32_t param = m->funcs[in->imm].nparams - 1u - k; + wasm_stack_pop(c, &stack, control, ncontrol, + m->funcs[in->imm].params[param], "call argument"); + } + if (in->kind == WASM_INSN_RETURN_CALL) { + wasm_mark_unreachable(&stack, control, ncontrol); + } else if (m->funcs[in->imm].nresults) { + wasm_stack_push(c, &stack, m->funcs[in->imm].results[0]); + } + break; + case WASM_INSN_CALL_INDIRECT: { + WasmFuncType* t; + if (in->imm < 0 || (uint64_t)in->imm >= m->ntypes) + wasm_error(c, wasm_loc(0, 0), + "wasm: call_indirect type index out of range"); + if (in->align >= m->ntables) + wasm_error(c, wasm_loc(0, 0), + "wasm: call_indirect table index out of range"); + t = &m->types[in->imm]; + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, + "call_indirect index"); + for (uint32_t k = 0; k < t->nparams; ++k) { + uint32_t param = t->nparams - 1u - k; + wasm_stack_pop(c, &stack, control, ncontrol, t->params[param], + "call_indirect argument"); + } + if (t->nresults) wasm_stack_push(c, &stack, t->results[0]); + break; + } + case WASM_INSN_RETURN_CALL_INDIRECT: { + WasmFuncType* t; + wasm_require_feature(c, m, WASM_FEATURE_TAIL_CALLS, "tail calls", + "return_call_indirect"); + if (in->imm < 0 || (uint64_t)in->imm >= m->ntypes) + wasm_error(c, wasm_loc(0, 0), + "wasm: return_call_indirect type index out of range"); + if (in->align >= m->ntables) + wasm_error(c, wasm_loc(0, 0), + "wasm: return_call_indirect table index out of range"); + t = &m->types[in->imm]; + if (t->nresults != f->nresults || + (f->nresults && t->results[0] != f->results[0])) + wasm_error(c, wasm_loc(0, 0), + "wasm: return_call_indirect result type mismatch"); + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, + "return_call_indirect index"); + for (uint32_t k = 0; k < t->nparams; ++k) { + uint32_t param = t->nparams - 1u - k; + wasm_stack_pop(c, &stack, control, ncontrol, t->params[param], + "return_call_indirect argument"); + } + wasm_mark_unreachable(&stack, control, ncontrol); + break; + } + case WASM_INSN_REF_NULL: + wasm_require_feature(c, m, WASM_FEATURE_TYPED_FUNC_REFS, + "typed function references", "ref.null"); + if (!wasm_is_ref_type((WasmValType)in->imm)) + wasm_error(c, wasm_loc(0, 0), "wasm: bad ref.null type"); + if ((WasmValType)in->imm != WASM_VAL_FUNCREF) + wasm_error(c, wasm_loc(0, 0), + "wasm: unsupported reference type"); + wasm_stack_push(c, &stack, (WasmValType)in->imm); + break; + case WASM_INSN_REF_FUNC: + wasm_require_feature(c, m, WASM_FEATURE_TYPED_FUNC_REFS, + "typed function references", "ref.func"); + if (in->imm < 0 || (uint64_t)in->imm >= m->nfuncs) + wasm_error(c, wasm_loc(0, 0), + "wasm: ref.func index out of range"); + wasm_stack_push(c, &stack, WASM_VAL_FUNCREF); + break; + case WASM_INSN_REF_IS_NULL: + wasm_require_feature(c, m, WASM_FEATURE_TYPED_FUNC_REFS, + "typed function references", "ref.is_null"); + wasm_stack_pop_ref(c, &stack, control, ncontrol, "ref.is_null"); + wasm_stack_push(c, &stack, WASM_VAL_I32); + break; + case WASM_INSN_CALL_REF: + case WASM_INSN_RETURN_CALL_REF: { + WasmFuncType* t; + wasm_require_feature(c, m, WASM_FEATURE_TYPED_FUNC_REFS, + "typed function references", "call_ref"); + if (in->kind == WASM_INSN_RETURN_CALL_REF) + wasm_require_feature(c, m, WASM_FEATURE_TAIL_CALLS, "tail calls", + "return_call_ref"); + if (in->imm < 0 || (uint64_t)in->imm >= m->ntypes) + wasm_error(c, wasm_loc(0, 0), + "wasm: call_ref type index out of range"); + t = &m->types[in->imm]; + if (in->kind == WASM_INSN_RETURN_CALL_REF && + (t->nresults != f->nresults || + (f->nresults && t->results[0] != f->results[0]))) + wasm_error(c, wasm_loc(0, 0), + "wasm: return_call_ref result type mismatch"); + wasm_stack_pop_ref(c, &stack, control, ncontrol, "call_ref callee"); + for (uint32_t k = 0; k < t->nparams; ++k) { + uint32_t param = t->nparams - 1u - k; + wasm_stack_pop(c, &stack, control, ncontrol, t->params[param], + "call_ref argument"); + } + if (in->kind == WASM_INSN_RETURN_CALL_REF) + wasm_mark_unreachable(&stack, control, ncontrol); + else if (t->nresults) + wasm_stack_push(c, &stack, t->results[0]); + break; + } + case WASM_INSN_GLOBAL_GET: + if (in->imm < 0 || (uint64_t)in->imm >= m->nglobals) + wasm_error(c, wasm_loc(0, 0), "wasm: global index out of range"); + wasm_stack_push(c, &stack, m->globals[in->imm].type); + break; + case WASM_INSN_GLOBAL_SET: + if (in->imm < 0 || (uint64_t)in->imm >= m->nglobals) + wasm_error(c, wasm_loc(0, 0), "wasm: global index out of range"); + if (!m->globals[in->imm].mutable_) + wasm_error(c, wasm_loc(0, 0), "wasm: global is immutable"); + wasm_stack_pop(c, &stack, control, ncontrol, m->globals[in->imm].type, + "global"); + break; + case WASM_INSN_RETURN: + if (f->nresults) + wasm_stack_pop(c, &stack, control, ncontrol, f->results[0], + "return"); + wasm_mark_unreachable(&stack, control, ncontrol); + break; + case WASM_INSN_DROP: + wasm_stack_pop(c, &stack, control, ncontrol, 0, "drop"); + break; + case WASM_INSN_I32_EQZ: + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, "eqz"); + wasm_stack_push(c, &stack, WASM_VAL_I32); + break; + case WASM_INSN_I64_EQZ: + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I64, "eqz"); + wasm_stack_push(c, &stack, WASM_VAL_I32); + break; + case WASM_INSN_BLOCK: + case WASM_INSN_LOOP: + if (ncontrol >= 65u) + wasm_error(c, wasm_loc(0, 0), "wasm: control stack too deep"); + control[ncontrol].kind = in->kind; + control[ncontrol].height = stack.depth; + control[ncontrol].seen_else = 0; + control[ncontrol].unreachable = 0; + ncontrol++; + break; + case WASM_INSN_IF: + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, "if"); + if (ncontrol >= 65u) + wasm_error(c, wasm_loc(0, 0), "wasm: control stack too deep"); + control[ncontrol].kind = in->kind; + control[ncontrol].height = stack.depth; + control[ncontrol].seen_else = 0; + control[ncontrol].unreachable = 0; + ncontrol++; + break; + case WASM_INSN_ELSE: + if (ncontrol <= 1u || control[ncontrol - 1u].kind != WASM_INSN_IF) + wasm_error(c, wasm_loc(0, 0), "wasm: else without if"); + if (!control[ncontrol - 1u].unreachable && + stack.depth != control[ncontrol - 1u].height) + wasm_error(c, wasm_loc(0, 0), "wasm: if branch result mismatch"); + stack.depth = control[ncontrol - 1u].height; + control[ncontrol - 1u].seen_else = 1; + control[ncontrol - 1u].unreachable = 0; + break; + case WASM_INSN_END: + if (ncontrol <= 1u) + wasm_error(c, wasm_loc(0, 0), "wasm: end without block"); + if (!control[ncontrol - 1u].unreachable && + stack.depth != control[ncontrol - 1u].height) + wasm_error(c, wasm_loc(0, 0), "wasm: block result mismatch"); + stack.depth = control[ncontrol - 1u].height; + ncontrol--; + break; + case WASM_INSN_BR: + if (in->imm < 0 || (uint64_t)in->imm >= ncontrol - 1u) + wasm_error(c, wasm_loc(0, 0), "wasm: branch depth out of range"); + wasm_mark_unreachable(&stack, control, ncontrol); + break; + case WASM_INSN_BR_IF: + if (in->imm < 0 || (uint64_t)in->imm >= ncontrol - 1u) + wasm_error(c, wasm_loc(0, 0), "wasm: branch depth out of range"); + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, "br_if"); + break; + case WASM_INSN_BR_TABLE: + if (in->ntargets == 0) + wasm_error(c, wasm_loc(0, 0), "wasm: br_table without targets"); + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, + "br_table selector"); + for (uint32_t k = 0; k < in->ntargets; ++k) + if (in->targets[k] >= ncontrol - 1u) + wasm_error(c, wasm_loc(0, 0), + "wasm: br_table depth out of range"); + wasm_mark_unreachable(&stack, control, ncontrol); + break; + case WASM_INSN_SELECT: { + WasmValType rhs, lhs; + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, "select"); + if (stack.depth <= control[ncontrol - 1u].height && + control[ncontrol - 1u].unreachable) { + in->type = WASM_VAL_I32; + break; + } + if (stack.depth < control[ncontrol - 1u].height + 2u) + wasm_error(c, wasm_loc(0, 0), "wasm: operand stack underflow"); + rhs = stack.vals[--stack.depth]; + lhs = stack.vals[--stack.depth]; + if (lhs != rhs) + wasm_error(c, wasm_loc(0, 0), "wasm: select type mismatch"); + in->type = (uint8_t)lhs; + wasm_stack_push(c, &stack, lhs); + break; + } + case WASM_INSN_MEMORY_SIZE: + if (in->memidx >= m->nmemories) + wasm_error(c, wasm_loc(0, 0), "wasm: memory.size without memory"); + wasm_stack_push(c, &stack, + m->memories[in->memidx].is64 ? WASM_VAL_I64 + : WASM_VAL_I32); + break; + case WASM_INSN_MEMORY_GROW: + if (in->memidx >= m->nmemories) + wasm_error(c, wasm_loc(0, 0), "wasm: memory.grow without memory"); + wasm_stack_pop(c, &stack, control, ncontrol, + m->memories[in->memidx].is64 ? WASM_VAL_I64 + : WASM_VAL_I32, + "memory.grow"); + wasm_stack_push(c, &stack, + m->memories[in->memidx].is64 ? WASM_VAL_I64 + : WASM_VAL_I32); + break; + case WASM_INSN_ATOMIC_FENCE: + wasm_require_feature(c, m, WASM_FEATURE_THREADS, "threads", + "atomic.fence"); + break; + case WASM_INSN_I32_ATOMIC_LOAD: + case WASM_INSN_I64_ATOMIC_LOAD: + case WASM_INSN_I32_ATOMIC_LOAD8_U: + case WASM_INSN_I32_ATOMIC_LOAD16_U: + case WASM_INSN_I64_ATOMIC_LOAD8_U: + case WASM_INSN_I64_ATOMIC_LOAD16_U: + case WASM_INSN_I64_ATOMIC_LOAD32_U: + wasm_require_feature(c, m, WASM_FEATURE_THREADS, "threads", + "atomic load"); + if (in->memidx >= m->nmemories) + wasm_error(c, wasm_loc(0, 0), "wasm: atomic load without memory"); + 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_stack_pop(c, &stack, control, ncontrol, + m->memories[in->memidx].is64 ? WASM_VAL_I64 + : WASM_VAL_I32, + "atomic load"); + wasm_stack_push(c, &stack, wasm_atomic_value_type(in->kind)); + break; + case WASM_INSN_I32_ATOMIC_STORE: + case WASM_INSN_I64_ATOMIC_STORE: + case WASM_INSN_I32_ATOMIC_STORE8: + case WASM_INSN_I32_ATOMIC_STORE16: + case WASM_INSN_I64_ATOMIC_STORE8: + case WASM_INSN_I64_ATOMIC_STORE16: + case WASM_INSN_I64_ATOMIC_STORE32: + wasm_require_feature(c, m, WASM_FEATURE_THREADS, "threads", + "atomic store"); + if (in->memidx >= m->nmemories) + wasm_error(c, wasm_loc(0, 0), "wasm: atomic store without memory"); + 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_stack_pop(c, &stack, control, ncontrol, + wasm_atomic_value_type(in->kind), "atomic store"); + wasm_stack_pop(c, &stack, control, ncontrol, + m->memories[in->memidx].is64 ? WASM_VAL_I64 + : WASM_VAL_I32, + "atomic store"); + break; + case WASM_INSN_I32_ATOMIC_RMW_ADD: + case WASM_INSN_I64_ATOMIC_RMW_ADD: + case WASM_INSN_I32_ATOMIC_RMW_SUB: + case WASM_INSN_I64_ATOMIC_RMW_SUB: + case WASM_INSN_I32_ATOMIC_RMW_AND: + case WASM_INSN_I64_ATOMIC_RMW_AND: + case WASM_INSN_I32_ATOMIC_RMW_OR: + case WASM_INSN_I64_ATOMIC_RMW_OR: + case WASM_INSN_I32_ATOMIC_RMW_XOR: + case WASM_INSN_I64_ATOMIC_RMW_XOR: + case WASM_INSN_I32_ATOMIC_RMW_XCHG: + case WASM_INSN_I64_ATOMIC_RMW_XCHG: + wasm_require_feature(c, m, WASM_FEATURE_THREADS, "threads", + "atomic rmw"); + if (in->memidx >= m->nmemories) + wasm_error(c, wasm_loc(0, 0), "wasm: atomic rmw without memory"); + if (!m->memories[in->memidx].shared) + wasm_error(c, wasm_loc(0, 0), + "wasm: atomic rmw requires shared memory"); + wasm_stack_pop(c, &stack, control, ncontrol, + wasm_atomic_value_type(in->kind), "atomic rmw"); + wasm_stack_pop(c, &stack, control, ncontrol, + m->memories[in->memidx].is64 ? WASM_VAL_I64 + : WASM_VAL_I32, + "atomic rmw"); + wasm_stack_push(c, &stack, wasm_atomic_value_type(in->kind)); + break; + case WASM_INSN_I32_ATOMIC_RMW_CMPXCHG: + case WASM_INSN_I64_ATOMIC_RMW_CMPXCHG: + wasm_require_feature(c, m, WASM_FEATURE_THREADS, "threads", + "atomic cmpxchg"); + if (in->memidx >= m->nmemories) + wasm_error(c, wasm_loc(0, 0), + "wasm: atomic cmpxchg without memory"); + if (!m->memories[in->memidx].shared) + wasm_error(c, wasm_loc(0, 0), + "wasm: atomic cmpxchg requires shared memory"); + wasm_stack_pop(c, &stack, control, ncontrol, + wasm_atomic_value_type(in->kind), "atomic cmpxchg"); + wasm_stack_pop(c, &stack, control, ncontrol, + wasm_atomic_value_type(in->kind), "atomic cmpxchg"); + wasm_stack_pop(c, &stack, control, ncontrol, + m->memories[in->memidx].is64 ? WASM_VAL_I64 + : WASM_VAL_I32, + "atomic cmpxchg"); + wasm_stack_push(c, &stack, wasm_atomic_value_type(in->kind)); + break; + case WASM_INSN_I32_ATOMIC_WAIT: + case WASM_INSN_I64_ATOMIC_WAIT: + wasm_require_feature(c, m, WASM_FEATURE_THREADS, "threads", + "atomic wait"); + if (in->memidx >= m->nmemories) + wasm_error(c, wasm_loc(0, 0), "wasm: atomic wait without memory"); + if (!m->memories[in->memidx].shared) + wasm_error(c, wasm_loc(0, 0), + "wasm: atomic wait requires shared memory"); + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I64, + "atomic wait timeout"); + wasm_stack_pop(c, &stack, control, ncontrol, + wasm_atomic_value_type(in->kind), + "atomic wait expected"); + wasm_stack_pop(c, &stack, control, ncontrol, + m->memories[in->memidx].is64 ? WASM_VAL_I64 + : WASM_VAL_I32, + "atomic wait address"); + wasm_stack_push(c, &stack, WASM_VAL_I32); + break; + case WASM_INSN_MEMORY_ATOMIC_NOTIFY: + wasm_require_feature(c, m, WASM_FEATURE_THREADS, "threads", + "atomic notify"); + if (in->memidx >= m->nmemories) + wasm_error(c, wasm_loc(0, 0), "wasm: atomic notify without memory"); + if (!m->memories[in->memidx].shared) + wasm_error(c, wasm_loc(0, 0), + "wasm: atomic notify requires shared memory"); + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, + "atomic notify count"); + wasm_stack_pop(c, &stack, control, ncontrol, + m->memories[in->memidx].is64 ? WASM_VAL_I64 + : WASM_VAL_I32, + "atomic notify address"); + wasm_stack_push(c, &stack, WASM_VAL_I32); + break; + case WASM_INSN_I32_LOAD: + case WASM_INSN_I64_LOAD: + case WASM_INSN_F32_LOAD: + case WASM_INSN_F64_LOAD: + case WASM_INSN_I32_LOAD8_S: + case WASM_INSN_I32_LOAD8_U: + case WASM_INSN_I32_LOAD16_S: + case WASM_INSN_I32_LOAD16_U: + case WASM_INSN_I64_LOAD8_S: + case WASM_INSN_I64_LOAD8_U: + case WASM_INSN_I64_LOAD16_S: + case WASM_INSN_I64_LOAD16_U: + case WASM_INSN_I64_LOAD32_S: + case WASM_INSN_I64_LOAD32_U: + if (in->memidx >= m->nmemories) + wasm_error(c, wasm_loc(0, 0), "wasm: load without memory"); + wasm_stack_pop(c, &stack, control, ncontrol, + m->memories[in->memidx].is64 ? WASM_VAL_I64 + : WASM_VAL_I32, + "load"); + wasm_stack_push(c, &stack, wasm_load_result_type(in->kind)); + break; + case WASM_INSN_I32_STORE: + case WASM_INSN_I64_STORE: + case WASM_INSN_F32_STORE: + case WASM_INSN_F64_STORE: + case WASM_INSN_I32_STORE8: + case WASM_INSN_I32_STORE16: + case WASM_INSN_I64_STORE8: + case WASM_INSN_I64_STORE16: + case WASM_INSN_I64_STORE32: + if (in->memidx >= m->nmemories) + wasm_error(c, wasm_loc(0, 0), "wasm: store without memory"); + wasm_stack_pop(c, &stack, control, ncontrol, + wasm_store_value_type(in->kind), "store"); + wasm_stack_pop(c, &stack, control, ncontrol, + m->memories[in->memidx].is64 ? WASM_VAL_I64 + : WASM_VAL_I32, + "store"); + break; + case WASM_INSN_UNREACHABLE: + wasm_mark_unreachable(&stack, control, ncontrol); + break; + case WASM_INSN_NOP: + break; + case WASM_INSN_MEMORY_COPY: { + WasmValType dst_vt, src_vt; + wasm_require_feature(c, m, WASM_FEATURE_BULK_MEMORY, "bulk memory", + "memory.copy"); + if (in->memidx >= m->nmemories || in->aux_idx >= m->nmemories) + wasm_error(c, wasm_loc(0, 0), + "wasm: memory.copy memory index out of range"); + dst_vt = m->memories[in->memidx].is64 ? WASM_VAL_I64 : WASM_VAL_I32; + src_vt = m->memories[in->aux_idx].is64 ? WASM_VAL_I64 : WASM_VAL_I32; + wasm_stack_pop(c, &stack, control, ncontrol, dst_vt, "memory.copy n"); + wasm_stack_pop(c, &stack, control, ncontrol, src_vt, + "memory.copy src"); + wasm_stack_pop(c, &stack, control, ncontrol, dst_vt, + "memory.copy dst"); + break; + } + case WASM_INSN_MEMORY_FILL: { + WasmValType vt; + wasm_require_feature(c, m, WASM_FEATURE_BULK_MEMORY, "bulk memory", + "memory.fill"); + if (in->memidx >= m->nmemories) + wasm_error(c, wasm_loc(0, 0), + "wasm: memory.fill memory index out of range"); + vt = m->memories[in->memidx].is64 ? WASM_VAL_I64 : WASM_VAL_I32; + wasm_stack_pop(c, &stack, control, ncontrol, vt, "memory.fill n"); + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, + "memory.fill value"); + wasm_stack_pop(c, &stack, control, ncontrol, vt, "memory.fill dst"); + break; + } + case WASM_INSN_MEMORY_INIT: { + WasmValType vt; + wasm_require_feature(c, m, WASM_FEATURE_BULK_MEMORY, "bulk memory", + "memory.init"); + 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 (in->memidx >= m->nmemories) + wasm_error(c, wasm_loc(0, 0), + "wasm: memory.init memory index out of range"); + vt = m->memories[in->memidx].is64 ? WASM_VAL_I64 : WASM_VAL_I32; + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, + "memory.init n"); + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, + "memory.init src"); + wasm_stack_pop(c, &stack, control, ncontrol, vt, "memory.init dst"); + break; + } + case WASM_INSN_DATA_DROP: + wasm_require_feature(c, m, WASM_FEATURE_BULK_MEMORY, "bulk memory", + "data.drop"); + 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"); + break; + case WASM_INSN_TABLE_COPY: + wasm_require_feature(c, m, WASM_FEATURE_BULK_MEMORY, "bulk memory", + "table.copy"); + if (in->imm < 0 || (uint64_t)in->imm >= m->ntables || + in->aux_idx >= m->ntables) + wasm_error(c, wasm_loc(0, 0), + "wasm: table.copy table index out of range"); + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, + "table.copy n"); + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, + "table.copy src"); + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, + "table.copy dst"); + break; + case WASM_INSN_TABLE_INIT: + wasm_require_feature(c, m, WASM_FEATURE_BULK_MEMORY, "bulk memory", + "table.init"); + 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 (in->aux_idx >= m->ntables) + wasm_error(c, wasm_loc(0, 0), + "wasm: table.init table index out of range"); + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, + "table.init n"); + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, + "table.init src"); + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, + "table.init dst"); + break; + case WASM_INSN_ELEM_DROP: + wasm_require_feature(c, m, WASM_FEATURE_BULK_MEMORY, "bulk memory", + "elem.drop"); + 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"); + break; + case WASM_INSN_TABLE_SIZE: + wasm_require_feature(c, m, WASM_FEATURE_BULK_MEMORY, "bulk memory", + "table.size"); + if (in->imm < 0 || (uint64_t)in->imm >= m->ntables) + wasm_error(c, wasm_loc(0, 0), + "wasm: table.size table index out of range"); + wasm_stack_push(c, &stack, WASM_VAL_I32); + break; + case WASM_INSN_TABLE_GROW: + wasm_require_feature(c, m, WASM_FEATURE_BULK_MEMORY, "bulk memory", + "table.grow"); + if (in->imm < 0 || (uint64_t)in->imm >= m->ntables) + wasm_error(c, wasm_loc(0, 0), + "wasm: table.grow table index out of range"); + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, + "table.grow delta"); + wasm_stack_pop_ref(c, &stack, control, ncontrol, "table.grow value"); + wasm_stack_push(c, &stack, WASM_VAL_I32); + break; + case WASM_INSN_TABLE_FILL: + wasm_require_feature(c, m, WASM_FEATURE_BULK_MEMORY, "bulk memory", + "table.fill"); + if (in->imm < 0 || (uint64_t)in->imm >= m->ntables) + wasm_error(c, wasm_loc(0, 0), + "wasm: table.fill table index out of range"); + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, + "table.fill n"); + wasm_stack_pop_ref(c, &stack, control, ncontrol, "table.fill value"); + wasm_stack_pop(c, &stack, control, ncontrol, WASM_VAL_I32, + "table.fill dst"); + break; + case WASM_INSN_I32_TRUNC_SAT_F32_S: + case WASM_INSN_I32_TRUNC_SAT_F64_S: + case WASM_INSN_I32_TRUNC_SAT_F32_U: + case WASM_INSN_I32_TRUNC_SAT_F64_U: + case WASM_INSN_I64_TRUNC_SAT_F32_S: + case WASM_INSN_I64_TRUNC_SAT_F64_S: + case WASM_INSN_I64_TRUNC_SAT_F32_U: + case WASM_INSN_I64_TRUNC_SAT_F64_U: { + WasmValType sat_src = + (in->kind == WASM_INSN_I32_TRUNC_SAT_F32_S || + in->kind == WASM_INSN_I32_TRUNC_SAT_F32_U || + in->kind == WASM_INSN_I64_TRUNC_SAT_F32_S || + in->kind == WASM_INSN_I64_TRUNC_SAT_F32_U) + ? WASM_VAL_F32 + : WASM_VAL_F64; + WasmValType sat_dst = + (in->kind == WASM_INSN_I32_TRUNC_SAT_F32_S || + in->kind == WASM_INSN_I32_TRUNC_SAT_F32_U || + in->kind == WASM_INSN_I32_TRUNC_SAT_F64_S || + in->kind == WASM_INSN_I32_TRUNC_SAT_F64_U) + ? WASM_VAL_I32 + : WASM_VAL_I64; + wasm_require_feature(c, m, WASM_FEATURE_NONTRAPPING_FTOI, + "non-trapping float-to-int", "trunc_sat"); + wasm_stack_pop(c, &stack, control, ncontrol, sat_src, "trunc_sat"); + wasm_stack_push(c, &stack, sat_dst); + break; + } + default: + if (wasm_int_unop_kind(in->kind, &vt)) { + wasm_stack_pop(c, &stack, control, ncontrol, vt, "unary operand"); + wasm_stack_push(c, &stack, vt); + break; + } + if (wasm_fp_binop_kind(in->kind, &vt)) { + wasm_stack_pop(c, &stack, control, ncontrol, vt, "fp operand"); + wasm_stack_pop(c, &stack, control, ncontrol, vt, "fp operand"); + wasm_stack_push(c, &stack, vt); + break; + } + if (wasm_fp_cmp_kind(in->kind, &vt)) { + wasm_stack_pop(c, &stack, control, ncontrol, vt, "fp compare"); + wasm_stack_pop(c, &stack, control, ncontrol, vt, "fp compare"); + wasm_stack_push(c, &stack, WASM_VAL_I32); + break; + } + if (wasm_conversion_kind(in->kind, &src, &dst)) { + wasm_stack_pop(c, &stack, control, ncontrol, src, "conversion"); + wasm_stack_push(c, &stack, dst); + break; + } + { + CfreeCgIntCmpOp cmp; + WasmValType rhs, lhs; + rhs = stack.depth > control[ncontrol - 1u].height + ? stack.vals[stack.depth - 1u] + : WASM_VAL_I32; + wasm_stack_pop(c, &stack, control, ncontrol, 0, "operand"); + lhs = stack.depth > control[ncontrol - 1u].height + ? stack.vals[stack.depth - 1u] + : rhs; + wasm_stack_pop(c, &stack, control, ncontrol, rhs, "operand"); + if (lhs != rhs) + wasm_error(c, wasm_loc(0, 0), + "wasm: operand type mismatch (kind=0x%x lhs=0x%x rhs=0x%x)", + (unsigned)in->kind, (unsigned)lhs, (unsigned)rhs); + wasm_stack_push( + c, &stack, + wasm_int_cmp_op(in->kind, &cmp) ? WASM_VAL_I32 : lhs); + break; + } +#undef wasm_loc + } + } + if (ncontrol != 1u) + wasm_error(c, wasm_loc(0, 0), "wasm: unterminated control block"); + if (!control[0].unreachable) { + if (f->nresults) { + if (stack.depth != 1u || stack.vals[0] != f->results[0]) + wasm_error(c, wasm_loc(0, 0), "wasm: function result type mismatch"); + } else if (stack.depth != 0) { + wasm_error(c, wasm_loc(0, 0), "wasm: function leaves extra values"); + } + } + } +} diff --git a/src/wasm/wasm.h b/src/wasm/wasm.h @@ -0,0 +1,545 @@ +#ifndef CFREE_WASM_H +#define CFREE_WASM_H + +/* Shared Wasm binary/core: in-memory module model, binary decoder, WAT + * parser, validator, encoder, and small instruction-kind helpers. This + * header is the boundary between the format mechanics (decode/encode/ + * validate, all in src/wasm/) and the consumers that lower those modules + * into something else (lang/wasm/cg.c lowers to native CG; src/arch/wasm/ + * builds modules from the CG backend; src/obj/wasm_emit.c flushes them). + * + * Types here use the public CfreeCompiler / CfreeHeap / CfreeWriter + * aliases on purpose: this layer is callable from every Wasm caller in + * the tree without depending on libcfree internals beyond the public + * frontend/CG headers. */ + +#include <cfree/cg.h> +#include <cfree/compile.h> +#include <cfree/core.h> +#include <cfree/frontend.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +typedef enum WasmValType { + WASM_VAL_I32 = 0x7f, + WASM_VAL_I64 = 0x7e, + WASM_VAL_F32 = 0x7d, + WASM_VAL_F64 = 0x7c, + WASM_VAL_FUNCREF = 0x70, + WASM_VAL_EXTERNREF = 0x6f, +} WasmValType; + +typedef enum WasmFeatureSet { + WASM_FEATURE_THREADS = 1u << 0, + WASM_FEATURE_TYPED_FUNC_REFS = 1u << 1, + WASM_FEATURE_TAIL_CALLS = 1u << 2, + WASM_FEATURE_MULTI_MEMORY = 1u << 3, + WASM_FEATURE_MEMORY64 = 1u << 4, + WASM_FEATURE_BULK_MEMORY = 1u << 5, + WASM_FEATURE_NONTRAPPING_FTOI = 1u << 6, +} WasmFeatureSet; + +typedef enum WasmInsnKind { + WASM_INSN_UNREACHABLE, + WASM_INSN_NOP, + WASM_INSN_BLOCK, + WASM_INSN_LOOP, + WASM_INSN_IF, + WASM_INSN_ELSE, + WASM_INSN_END, + WASM_INSN_BR, + WASM_INSN_BR_IF, + WASM_INSN_BR_TABLE, + WASM_INSN_SELECT, + WASM_INSN_F32_CONST, + WASM_INSN_F64_CONST, + WASM_INSN_I32_CONST, + WASM_INSN_I64_CONST, + WASM_INSN_LOCAL_GET, + WASM_INSN_LOCAL_SET, + WASM_INSN_LOCAL_TEE, + WASM_INSN_CALL, + WASM_INSN_CALL_INDIRECT, + WASM_INSN_RETURN_CALL, + WASM_INSN_RETURN_CALL_INDIRECT, + WASM_INSN_REF_NULL, + WASM_INSN_REF_FUNC, + WASM_INSN_REF_IS_NULL, + WASM_INSN_CALL_REF, + WASM_INSN_RETURN_CALL_REF, + WASM_INSN_GLOBAL_GET, + WASM_INSN_GLOBAL_SET, + WASM_INSN_RETURN, + WASM_INSN_DROP, + WASM_INSN_I32_LOAD, + WASM_INSN_I64_LOAD, + WASM_INSN_F32_LOAD, + WASM_INSN_F64_LOAD, + WASM_INSN_I32_LOAD8_S, + WASM_INSN_I32_LOAD8_U, + WASM_INSN_I32_LOAD16_S, + WASM_INSN_I32_LOAD16_U, + WASM_INSN_I64_LOAD8_S, + WASM_INSN_I64_LOAD8_U, + WASM_INSN_I64_LOAD16_S, + WASM_INSN_I64_LOAD16_U, + WASM_INSN_I64_LOAD32_S, + WASM_INSN_I64_LOAD32_U, + WASM_INSN_I32_STORE, + WASM_INSN_I64_STORE, + WASM_INSN_F32_STORE, + WASM_INSN_F64_STORE, + WASM_INSN_I32_STORE8, + WASM_INSN_I32_STORE16, + WASM_INSN_I64_STORE8, + WASM_INSN_I64_STORE16, + WASM_INSN_I64_STORE32, + WASM_INSN_MEMORY_SIZE, + WASM_INSN_MEMORY_GROW, + WASM_INSN_ATOMIC_FENCE, + WASM_INSN_I32_ATOMIC_LOAD, + WASM_INSN_I64_ATOMIC_LOAD, + WASM_INSN_I32_ATOMIC_LOAD8_U, + WASM_INSN_I32_ATOMIC_LOAD16_U, + WASM_INSN_I64_ATOMIC_LOAD8_U, + WASM_INSN_I64_ATOMIC_LOAD16_U, + WASM_INSN_I64_ATOMIC_LOAD32_U, + WASM_INSN_I32_ATOMIC_STORE, + WASM_INSN_I64_ATOMIC_STORE, + WASM_INSN_I32_ATOMIC_STORE8, + WASM_INSN_I32_ATOMIC_STORE16, + WASM_INSN_I64_ATOMIC_STORE8, + WASM_INSN_I64_ATOMIC_STORE16, + WASM_INSN_I64_ATOMIC_STORE32, + WASM_INSN_I32_ATOMIC_RMW_ADD, + WASM_INSN_I64_ATOMIC_RMW_ADD, + WASM_INSN_I32_ATOMIC_RMW_SUB, + WASM_INSN_I64_ATOMIC_RMW_SUB, + WASM_INSN_I32_ATOMIC_RMW_AND, + WASM_INSN_I64_ATOMIC_RMW_AND, + WASM_INSN_I32_ATOMIC_RMW_OR, + WASM_INSN_I64_ATOMIC_RMW_OR, + WASM_INSN_I32_ATOMIC_RMW_XOR, + WASM_INSN_I64_ATOMIC_RMW_XOR, + WASM_INSN_I32_ATOMIC_RMW_XCHG, + WASM_INSN_I64_ATOMIC_RMW_XCHG, + WASM_INSN_I32_ATOMIC_RMW_CMPXCHG, + WASM_INSN_I64_ATOMIC_RMW_CMPXCHG, + WASM_INSN_I32_ATOMIC_WAIT, + WASM_INSN_I64_ATOMIC_WAIT, + WASM_INSN_MEMORY_ATOMIC_NOTIFY, + WASM_INSN_I32_ADD, + WASM_INSN_I32_SUB, + WASM_INSN_I32_MUL, + WASM_INSN_I32_DIV_S, + WASM_INSN_I32_DIV_U, + WASM_INSN_I32_REM_S, + WASM_INSN_I32_REM_U, + WASM_INSN_I32_AND, + WASM_INSN_I32_OR, + WASM_INSN_I32_XOR, + WASM_INSN_I32_SHL, + WASM_INSN_I32_SHR_S, + WASM_INSN_I32_SHR_U, + WASM_INSN_I32_ROTL, + WASM_INSN_I32_ROTR, + WASM_INSN_I32_CLZ, + WASM_INSN_I32_CTZ, + WASM_INSN_I32_POPCNT, + WASM_INSN_I32_EQZ, + WASM_INSN_I32_EQ, + WASM_INSN_I32_NE, + WASM_INSN_I32_LT_S, + WASM_INSN_I32_LT_U, + WASM_INSN_I32_GT_S, + WASM_INSN_I32_GT_U, + WASM_INSN_I32_LE_S, + WASM_INSN_I32_LE_U, + WASM_INSN_I32_GE_S, + WASM_INSN_I32_GE_U, + WASM_INSN_I64_ADD, + WASM_INSN_I64_SUB, + WASM_INSN_I64_MUL, + WASM_INSN_I64_DIV_S, + WASM_INSN_I64_DIV_U, + WASM_INSN_I64_REM_S, + WASM_INSN_I64_REM_U, + WASM_INSN_I64_AND, + WASM_INSN_I64_OR, + WASM_INSN_I64_XOR, + WASM_INSN_I64_SHL, + WASM_INSN_I64_SHR_S, + WASM_INSN_I64_SHR_U, + WASM_INSN_I64_ROTL, + WASM_INSN_I64_ROTR, + WASM_INSN_I64_CLZ, + WASM_INSN_I64_CTZ, + WASM_INSN_I64_POPCNT, + WASM_INSN_I64_EQZ, + WASM_INSN_I64_EQ, + WASM_INSN_I64_NE, + WASM_INSN_I64_LT_S, + WASM_INSN_I64_LT_U, + WASM_INSN_I64_GT_S, + WASM_INSN_I64_GT_U, + WASM_INSN_I64_LE_S, + WASM_INSN_I64_LE_U, + WASM_INSN_I64_GE_S, + WASM_INSN_I64_GE_U, + WASM_INSN_F32_ADD, + WASM_INSN_F32_SUB, + WASM_INSN_F32_MUL, + WASM_INSN_F32_DIV, + WASM_INSN_F32_EQ, + WASM_INSN_F32_NE, + WASM_INSN_F32_LT, + WASM_INSN_F32_GT, + WASM_INSN_F32_LE, + WASM_INSN_F32_GE, + WASM_INSN_F64_ADD, + WASM_INSN_F64_SUB, + WASM_INSN_F64_MUL, + WASM_INSN_F64_DIV, + WASM_INSN_F64_EQ, + WASM_INSN_F64_NE, + WASM_INSN_F64_LT, + WASM_INSN_F64_GT, + WASM_INSN_F64_LE, + WASM_INSN_F64_GE, + WASM_INSN_I32_WRAP_I64, + WASM_INSN_I32_TRUNC_F32_S, + WASM_INSN_I32_TRUNC_F32_U, + WASM_INSN_I32_TRUNC_F64_S, + WASM_INSN_I32_TRUNC_F64_U, + WASM_INSN_I64_EXTEND_I32_S, + WASM_INSN_I64_EXTEND_I32_U, + WASM_INSN_I64_TRUNC_F32_S, + WASM_INSN_I64_TRUNC_F32_U, + WASM_INSN_I64_TRUNC_F64_S, + WASM_INSN_I64_TRUNC_F64_U, + WASM_INSN_F32_CONVERT_I32_S, + WASM_INSN_F32_CONVERT_I32_U, + WASM_INSN_F32_CONVERT_I64_S, + WASM_INSN_F32_CONVERT_I64_U, + WASM_INSN_F32_DEMOTE_F64, + WASM_INSN_F64_CONVERT_I32_S, + WASM_INSN_F64_CONVERT_I32_U, + WASM_INSN_F64_CONVERT_I64_S, + WASM_INSN_F64_CONVERT_I64_U, + WASM_INSN_F64_PROMOTE_F32, + WASM_INSN_I32_REINTERPRET_F32, + WASM_INSN_I64_REINTERPRET_F64, + WASM_INSN_F32_REINTERPRET_I32, + WASM_INSN_F64_REINTERPRET_I64, + /* Non-trapping float-to-int truncation (0xfc 0x00..0x07). + * Gated by WASM_FEATURE_NONTRAPPING_FTOI. */ + WASM_INSN_I32_TRUNC_SAT_F32_S, + WASM_INSN_I32_TRUNC_SAT_F32_U, + WASM_INSN_I32_TRUNC_SAT_F64_S, + WASM_INSN_I32_TRUNC_SAT_F64_U, + WASM_INSN_I64_TRUNC_SAT_F32_S, + WASM_INSN_I64_TRUNC_SAT_F32_U, + WASM_INSN_I64_TRUNC_SAT_F64_S, + WASM_INSN_I64_TRUNC_SAT_F64_U, + /* Bulk memory ops (0xfc 0x08..0x11). Gated by WASM_FEATURE_BULK_MEMORY. + * Immediate slots: + * memory.init: imm = dataidx, memidx = memidx + * data.drop: imm = dataidx + * memory.copy: memidx = dst memidx, aux_idx = src memidx + * memory.fill: memidx = memidx + * table.init: imm = elemidx, aux_idx = tableidx + * elem.drop: imm = elemidx + * table.copy: aux_idx = src tableidx, imm = dst tableidx + * table.grow: imm = tableidx + * table.size: imm = tableidx + * table.fill: imm = tableidx + */ + WASM_INSN_MEMORY_INIT, + WASM_INSN_DATA_DROP, + WASM_INSN_MEMORY_COPY, + WASM_INSN_MEMORY_FILL, + WASM_INSN_TABLE_INIT, + WASM_INSN_ELEM_DROP, + WASM_INSN_TABLE_COPY, + WASM_INSN_TABLE_GROW, + WASM_INSN_TABLE_SIZE, + WASM_INSN_TABLE_FILL, +} WasmInsnKind; + +typedef struct WasmInsn { + CfreeSrcLoc loc; + uint8_t kind; + uint8_t type; + int64_t imm; + double fp; + uint32_t align; + uint32_t memidx; + uint64_t offset64; + /* Secondary index slot for opcodes with two index immediates: memory.copy + * (src memidx), table.init (tableidx), table.copy (src tableidx). */ + uint32_t aux_idx; + uint32_t ntargets; + uint32_t targets[64]; +} WasmInsn; + +typedef struct WasmFunc { + CfreeSrcLoc loc; + char* name; + uint32_t typeidx; + int has_typeidx; + int is_import; + char* import_module; + char* import_name; + /* params/locals/local_names are heap-grown — no fixed limit. params holds + * one WasmValType per function parameter (size = nparams, capacity = + * cap_params); locals holds one WasmValType per declared local (size = + * nlocals, capacity = cap_locals); local_names is a sparse array indexed by + * wasm-local index (0..nparams+nlocals-1) and capacity-tracked separately. */ + WasmValType* params; + uint32_t nparams; + uint32_t cap_params; + WasmValType* locals; + uint32_t nlocals; + uint32_t cap_locals; + char** local_names; + uint32_t cap_local_names; + WasmValType results[1]; + uint32_t nresults; + char* export_name; + WasmInsn* insns; + uint32_t ninsns; + uint32_t cap_insns; +} WasmFunc; + +typedef struct WasmFuncType { + char* name; + WasmValType* params; + uint32_t nparams; + uint32_t cap_params; + WasmValType results[1]; + uint32_t nresults; +} WasmFuncType; + +typedef struct WasmMemory { + char* name; + uint64_t min_pages; + uint64_t max_pages; + int has_max; + int is64; + int shared; + int is_import; + char* import_module; + char* import_name; + char* export_name; +} WasmMemory; + +typedef enum WasmSegmentMode { + WASM_SEG_ACTIVE = 0, + WASM_SEG_PASSIVE = 1, + WASM_SEG_DECLARATIVE = 2, /* element segments only */ +} WasmSegmentMode; + +/* A data segment carries an immutable byte buffer plus its placement mode. + * Active segments are copied into memory `memidx` at constant offset `offset` + * at module instantiation. Passive segments are sources for `memory.init` and + * are otherwise inert until `data.drop` permanently zero-lengths them. */ +typedef struct WasmDataSegment { + uint8_t mode; /* WasmSegmentMode */ + uint32_t memidx; /* meaningful when mode == WASM_SEG_ACTIVE */ + int64_t offset; /* active offset; signed to allow caller-relative builds */ + char* name; /* optional WAT `$name`, NULL otherwise */ + uint8_t* bytes; + uint64_t nbytes; +} WasmDataSegment; + +typedef struct WasmTable { + char* name; + WasmValType elem_type; + uint32_t min; + uint32_t max; + int has_max; + int is_import; + char* import_module; + char* import_name; + char* export_name; +} WasmTable; + +typedef struct WasmGlobal { + CfreeSrcLoc loc; + char* name; + WasmValType type; + uint8_t mutable_; + WasmInsn init; + int is_import; + char* import_module; + char* import_name; + char* export_name; +} WasmGlobal; + +typedef struct WasmElemSegment { + uint8_t mode; /* WasmSegmentMode */ + WasmValType elem_type; /* funcref/externref/typed-funcref */ + uint32_t tableidx; /* meaningful when mode == WASM_SEG_ACTIVE */ + int64_t offset; /* active offset */ + char* name; /* optional WAT `$name`, NULL otherwise */ + uint32_t* funcs; /* heap-grown; each slot is a function index */ + uint32_t nfuncs; + uint32_t cap_funcs; +} WasmElemSegment; + +typedef struct WasmExport { + char* name; + uint8_t kind; + uint32_t index; +} WasmExport; + +typedef struct WasmCustom { + char* name; + uint8_t* data; + uint32_t len; +} WasmCustom; + +typedef struct WasmModule { + CfreeHeap* heap; + uint32_t file_id; + CfreeSrcLoc current_loc; + CfreeSrcLoc start_field_loc; + WasmFuncType* types; + uint32_t ntypes; + uint32_t cap_types; + WasmFunc* funcs; + uint32_t nfuncs; + uint32_t cap_funcs; + WasmMemory* memories; + uint32_t nmemories; + uint32_t cap_memories; + WasmTable* tables; + uint32_t ntables; + uint32_t cap_tables; + WasmGlobal* globals; + uint32_t nglobals; + uint32_t cap_globals; + WasmElemSegment* elems; + uint32_t nelems; + uint32_t cap_elems; + WasmDataSegment* data; + uint32_t ndata; + uint32_t cap_data; + WasmExport* exports; + uint32_t nexports; + uint32_t cap_exports; + WasmCustom* customs; + uint32_t ncustoms; + uint32_t cap_customs; + uint32_t start_func; + int has_start; + int has_target_features; + uint32_t features; +} WasmModule; + +CfreeSrcLoc wasm_loc(uint32_t line, uint32_t col); +void wasm_error(CfreeCompiler* c, CfreeSrcLoc loc, const char* fmt, ...); +void* wasm_realloc(CfreeHeap* h, void* p, size_t old_n, size_t new_n); +char* wasm_strdup(CfreeHeap* h, const char* s, size_t len); +void wasm_free_str(CfreeHeap* h, char** s); + +void wasm_module_init(WasmModule* m, CfreeHeap* heap); +void wasm_module_free(WasmModule* m); +WasmMemory* wasm_add_memory(CfreeCompiler* c, WasmModule* m); +WasmFunc* wasm_add_func(CfreeCompiler* c, WasmModule* m); +WasmFuncType* wasm_add_type(CfreeCompiler* c, WasmModule* m); +uint32_t wasm_intern_func_type(CfreeCompiler* c, WasmModule* m, + const WasmFunc* f); +WasmTable* wasm_add_table(CfreeCompiler* c, WasmModule* m); +WasmGlobal* wasm_add_global(CfreeCompiler* c, WasmModule* m); +WasmElemSegment* wasm_add_elem(CfreeCompiler* c, WasmModule* m); +WasmDataSegment* wasm_add_data(CfreeCompiler* c, WasmModule* m); +/* Append a function index to an element segment, growing as needed. */ +void wasm_elem_push_func(CfreeCompiler* c, WasmModule* m, WasmElemSegment* e, + uint32_t funcidx); +/* Set/append a data segment's byte buffer. Copies `n` bytes from `src`. */ +void wasm_data_set_bytes(CfreeCompiler* c, WasmModule* m, WasmDataSegment* d, + const uint8_t* src, uint64_t n); +WasmExport* wasm_add_export(CfreeCompiler* c, WasmModule* m); +WasmCustom* wasm_add_custom(CfreeCompiler* c, WasmModule* m); +/* Push a single parameter type onto a WasmFunc's signature, growing the + * underlying array as needed. Returns the new parameter's wasm-local index. */ +uint32_t wasm_func_push_param(CfreeCompiler* c, WasmModule* m, WasmFunc* f, + WasmValType vt); +/* Push a single declared local onto a WasmFunc. Returns the new local's + * wasm-local index (i.e., nparams + (nlocals-1) after the push). */ +uint32_t wasm_func_push_local(CfreeCompiler* c, WasmModule* m, WasmFunc* f, + WasmValType vt); +/* Bulk-set the params array from a source buffer. Reuses existing capacity + * when possible; otherwise grows. */ +void wasm_func_set_params(CfreeCompiler* c, WasmModule* m, WasmFunc* f, + const WasmValType* src, uint32_t n); +/* Assign a name to wasm-local index `idx`, growing local_names as needed. + * `idx` may be any value < nparams + nlocals (or any future index the caller + * intends to fill before encoding). */ +void wasm_func_set_local_name(CfreeCompiler* c, WasmModule* m, WasmFunc* f, + uint32_t idx, const char* name, size_t len); +/* Push a single parameter type onto a WasmFuncType, growing as needed. */ +uint32_t wasm_type_push_param(CfreeCompiler* c, WasmModule* m, WasmFuncType* t, + WasmValType vt); + +void wasm_func_add_insn(CfreeCompiler* c, WasmModule* m, WasmFunc* f, + WasmInsnKind kind, int64_t imm); +void wasm_func_add_mem_insn(CfreeCompiler* c, WasmModule* m, WasmFunc* f, + WasmInsnKind kind, uint32_t align, + uint64_t offset, uint32_t memidx); +void wasm_func_add_fp_insn(CfreeCompiler* c, WasmModule* m, WasmFunc* f, + WasmInsnKind kind, double value); + +int wasm_is_num_type(WasmValType vt); +int wasm_is_ref_type(WasmValType vt); +int wasm_is_frontend_value_type(WasmValType vt); +int wasm_feature_enabled(const WasmModule* m, WasmFeatureSet feature); +void wasm_require_feature(CfreeCompiler* c, const WasmModule* m, + WasmFeatureSet feature, const char* feature_name, + const char* what); +int wasm_insn_is_load(WasmInsnKind kind); +int wasm_insn_is_store(WasmInsnKind kind); +int wasm_insn_is_atomic_load(WasmInsnKind kind); +int wasm_insn_is_atomic_store(WasmInsnKind kind); +int wasm_insn_is_atomic_rmw(WasmInsnKind kind); +int wasm_insn_is_atomic_cmpxchg(WasmInsnKind kind); +int wasm_insn_is_atomic_wait_notify(WasmInsnKind kind); +int wasm_insn_is_atomic_mem(WasmInsnKind kind); +int wasm_insn_is_mem(WasmInsnKind kind); +WasmValType wasm_func_local_type(const WasmFunc* f, uint32_t index); +uint32_t wasm_mem_width(uint8_t kind); +int wasm_int_cmp_op(uint8_t kind, CfreeCgIntCmpOp* out); +WasmValType wasm_load_result_type(uint8_t kind); +WasmValType wasm_store_value_type(uint8_t kind); +WasmValType wasm_atomic_value_type(uint8_t kind); +CfreeCgAtomicOp wasm_atomic_rmw_op(uint8_t kind); +int wasm_int_unop_kind(uint8_t kind, WasmValType* vt); +int wasm_fp_binop_kind(uint8_t kind, WasmValType* vt); +int wasm_fp_cmp_kind(uint8_t kind, WasmValType* vt); +int wasm_conversion_kind(uint8_t kind, WasmValType* src, WasmValType* dst); + +void wasm_parse_wat(CfreeCompiler* c, CfreeSlice name, const CfreeSlice* input, + WasmModule* out); +/* Parses a sequence of WAT instructions (no (module ...) / (func ...) wrapper) + * into the caller-supplied WasmFunc. The function must already have its + * params/results/locals filled in. Direct (call $name) references resolve + * against m->funcs[].name. Diagnostics are routed through CfreeCompiler. */ +void wasm_parse_wat_body(CfreeCompiler* c, WasmModule* m, WasmFunc* f, + const char* src, size_t len, CfreeSrcLoc loc); +void wasm_decode_binary(CfreeCompiler* c, const CfreeSlice* input, + WasmModule* out); +int wasm_is_binary(const CfreeSlice* input); +void wasm_validate(WasmModule* m, CfreeCompiler* c); +/* Validate a single function under the typed operand/control stack rules. + * Used by wasm_validate and by callers that synthesize scratch functions + * (e.g. the wasm-target inline-asm path). */ +void wasm_validate_func(CfreeCompiler* c, WasmModule* m, WasmFunc* f); +void wasm_emit_cg(CfreeCompiler* c, const CfreeCodeOptions* code_opts, + CfreeObjBuilder* out, const WasmModule* m); +void wasm_encode(CfreeCompiler* c, const WasmModule* m, CfreeWriter* out); + +#endif diff --git a/src/wasm/wat.c b/src/wasm/wat.c @@ -0,0 +1,2730 @@ +#include "wasm/wasm.h" + +#include <cfree/source.h> + +typedef struct WasmTok { + const char* p; + size_t len; + uint32_t line; + uint32_t col; + uint8_t kind; +} WasmTok; + +enum { + WT_EOF = 0, + WT_LPAREN, + WT_RPAREN, + WT_ATOM, + WT_STRING, +}; + +typedef struct WatParser { + CfreeCompiler* c; + const char* name; + const char* src; + size_t len; + size_t pos; + uint32_t line; + uint32_t col; + WasmTok tok; + CfreeSrcLoc field_loc; + WasmModule* module; +} WatParser; + +static CfreeSrcLoc wat_loc(WatParser* p, uint32_t line, uint32_t col) { + CfreeSrcLoc loc = wasm_loc(line, col); + if (p && p->module) loc.file_id = p->module->file_id; + return loc; +} + +static CfreeSrcLoc wat_tok_loc(WatParser* p, WasmTok t) { + return wat_loc(p, t.line, t.col); +} + +static int tok_is(WasmTok t, const char* s) { + size_t n = cfree_slice_cstr(s).len; + return t.kind == WT_ATOM && t.len == n && memcmp(t.p, s, n) == 0; +} + +static int wasm_name_eq(const char* name, WasmTok t) { + size_t n; + if (!name || t.kind != WT_ATOM) return 0; + n = cfree_slice_cstr(name).len; + return t.len == n && memcmp(name, t.p, n) == 0; +} + +static int wat_hex(char ch) { + if (ch >= '0' && ch <= '9') return ch - '0'; + if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10; + if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10; + return -1; +} + +static char* wat_dup_string(WatParser* p, WasmTok t, size_t* len_out) { + char* out; + size_t i, n = 0; + if (t.kind != WT_STRING) + wasm_error(p->c, wasm_loc(t.line, t.col), "wasm wat: expected string"); + out = (char*)p->module->heap->alloc(p->module->heap, t.len + 1u, 1); + if (!out) wasm_error(p->c, wasm_loc(t.line, t.col), "wasm: out of memory"); + for (i = 0; i < t.len; ++i) { + char ch = t.p[i]; + if (ch != '\\') { + out[n++] = ch; + continue; + } + if (++i >= t.len) + wasm_error(p->c, wasm_loc(t.line, t.col), + "wasm wat: unterminated string escape"); + ch = t.p[i]; + switch (ch) { + case 'n': + out[n++] = '\n'; + break; + case 'r': + out[n++] = '\r'; + break; + case 't': + out[n++] = '\t'; + break; + case '"': + case '\'': + case '\\': + out[n++] = ch; + break; + default: { + int hi = wat_hex(ch); + int lo = (i + 1u < t.len) ? wat_hex(t.p[i + 1u]) : -1; + if (hi < 0 || lo < 0) + wasm_error(p->c, wasm_loc(t.line, t.col), + "wasm wat: unsupported string escape"); + out[n++] = (char)((hi << 4) | lo); + i++; + break; + } + } + } + out[n] = '\0'; + if (len_out) *len_out = n; + return out; +} + +static char* wat_dup_atom(WatParser* p, WasmTok t) { + char* out = wasm_strdup(p->module->heap, t.p, t.len); + if (!out) + wasm_error(p->c, wasm_loc(t.line, t.col), "wasm: out of memory"); + return out; +} + +static void wat_next(WatParser* p) { + const char* s = p->src; + while (p->pos < p->len) { + char ch = s[p->pos]; + if (ch == '\n') { + p->pos++; + p->line++; + p->col = 1; + continue; + } + if (ch == ' ' || ch == '\t' || ch == '\r') { + p->pos++; + p->col++; + continue; + } + if (ch == ';' && p->pos + 1u < p->len && s[p->pos + 1u] == ';') { + while (p->pos < p->len && s[p->pos] != '\n') { + p->pos++; + p->col++; + } + continue; + } + if (ch == '(' && p->pos + 1u < p->len && s[p->pos + 1u] == ';') { + uint32_t depth = 1; + p->pos += 2u; + p->col += 2u; + while (depth && p->pos < p->len) { + if (s[p->pos] == '\n') { + p->pos++; + p->line++; + p->col = 1; + } else if (s[p->pos] == '(' && p->pos + 1u < p->len && + s[p->pos + 1u] == ';') { + p->pos += 2u; + p->col += 2u; + depth++; + } else if (s[p->pos] == ';' && p->pos + 1u < p->len && + s[p->pos + 1u] == ')') { + p->pos += 2u; + p->col += 2u; + depth--; + } else { + p->pos++; + p->col++; + } + } + if (depth) + wasm_error(p->c, wasm_loc(p->line, p->col), + "wasm wat: unterminated block comment"); + continue; + } + break; + } + p->tok.p = s + p->pos; + p->tok.len = 0; + p->tok.line = p->line; + p->tok.col = p->col; + p->tok.kind = WT_EOF; + if (p->pos >= p->len) return; + if (s[p->pos] == '(') { + p->tok.kind = WT_LPAREN; + p->tok.len = 1; + p->pos++; + p->col++; + return; + } + if (s[p->pos] == ')') { + p->tok.kind = WT_RPAREN; + p->tok.len = 1; + p->pos++; + p->col++; + return; + } + if (s[p->pos] == '"') { + size_t start = ++p->pos; + p->col++; + p->tok.kind = WT_STRING; + p->tok.p = s + start; + while (p->pos < p->len && s[p->pos] != '"') { + if (s[p->pos] == '\\') { + p->pos++; + p->col++; + if (p->pos >= p->len) break; + } else if ((unsigned char)s[p->pos] < 0x20) { + wasm_error(p->c, wasm_loc(p->line, p->col), + "wasm wat: unsupported string escape/control character"); + } + p->pos++; + p->col++; + } + if (p->pos >= p->len) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: unterminated string"); + p->tok.len = (size_t)(s + p->pos - p->tok.p); + p->pos++; + p->col++; + return; + } + p->tok.kind = WT_ATOM; + while (p->pos < p->len) { + char ch = s[p->pos]; + if (ch == '(' || ch == ')' || ch == ' ' || ch == '\t' || ch == '\r' || + ch == '\n') + break; + p->pos++; + p->col++; + } + p->tok.len = (size_t)(s + p->pos - p->tok.p); +} + +static void wat_expect(WatParser* p, uint8_t kind, const char* what) { + if (p->tok.kind != kind) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected %.*s", + CFREE_SLICE_ARG(cfree_slice_cstr(what))); + wat_next(p); +} + +static int wat_parse_i64(WatParser* p, int64_t* out) { + const char* s = p->tok.p; + size_t n = p->tok.len, i = 0; + uint64_t v = 0; + int neg = 0; + unsigned base = 10; + if (p->tok.kind != WT_ATOM || n == 0) return 0; + if (s[0] == '-' || s[0] == '+') { + neg = s[0] == '-'; + i = 1; + } + if (i + 2u <= n && s[i] == '0' && (s[i + 1u] == 'x' || s[i + 1u] == 'X')) { + base = 16; + i += 2u; + } + if (i == n) return 0; + for (; i < n; ++i) { + int hd; + unsigned d; + if (s[i] == '_') continue; + hd = wat_hex(s[i]); + if (hd < 0 || (unsigned)hd >= base) return 0; + d = (unsigned)hd; + if (v > (UINT64_MAX - d) / base) return 0; + v = v * base + d; + } + *out = neg ? -(int64_t)v : (int64_t)v; + return 1; +} + +static int wat_parse_f64(WatParser* p, double* out) { + char buf[128]; + char* end = NULL; + if (p->tok.kind != WT_ATOM || p->tok.len == 0 || p->tok.len >= sizeof buf) + return 0; + memcpy(buf, p->tok.p, p->tok.len); + buf[p->tok.len] = '\0'; + *out = strtod(buf, &end); + return end && *end == '\0'; +} + +static int wat_val_type(WasmTok t, WasmValType* out) { + if (tok_is(t, "i32")) { + *out = WASM_VAL_I32; + return 1; + } + if (tok_is(t, "i64")) { + *out = WASM_VAL_I64; + return 1; + } + if (tok_is(t, "f32")) { + *out = WASM_VAL_F32; + return 1; + } + if (tok_is(t, "f64")) { + *out = WASM_VAL_F64; + return 1; + } + if (tok_is(t, "funcref")) { + *out = WASM_VAL_FUNCREF; + return 1; + } + if (tok_is(t, "externref")) { + *out = WASM_VAL_EXTERNREF; + return 1; + } + return 0; +} + +static void wat_require_feature(WatParser* p, WasmFeatureSet feature, + const char* feature_name, const char* what) { + if (!wasm_feature_enabled(p->module, feature)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: %.*s requires %.*s", + CFREE_SLICE_ARG(cfree_slice_cstr(what)), + CFREE_SLICE_ARG(cfree_slice_cstr(feature_name))); +} + +static uint8_t wasm_export_kind_from_tok(WasmTok t) { + if (tok_is(t, "func")) return 0; + if (tok_is(t, "table")) return 1; + if (tok_is(t, "memory")) return 2; + if (tok_is(t, "global")) return 3; + return 0xffu; +} + +static void wat_skip_list(WatParser* p) { + uint32_t depth = 1; + while (depth && p->tok.kind != WT_EOF) { + if (p->tok.kind == WT_LPAREN) + depth++; + else if (p->tok.kind == WT_RPAREN) + depth--; + wat_next(p); + } +} + +static int wat_instr_kind(WasmTok t, WasmInsnKind* out, int* has_imm) { + *has_imm = 0; + if (tok_is(t, "unreachable")) { + *out = WASM_INSN_UNREACHABLE; + return 1; + } + if (tok_is(t, "nop")) { + *out = WASM_INSN_NOP; + return 1; + } + if (tok_is(t, "block")) { + *out = WASM_INSN_BLOCK; + return 1; + } + if (tok_is(t, "loop")) { + *out = WASM_INSN_LOOP; + return 1; + } + if (tok_is(t, "if")) { + *out = WASM_INSN_IF; + return 1; + } + if (tok_is(t, "else")) { + *out = WASM_INSN_ELSE; + return 1; + } + if (tok_is(t, "end")) { + *out = WASM_INSN_END; + return 1; + } + if (tok_is(t, "br")) { + *out = WASM_INSN_BR; + *has_imm = 1; + return 1; + } + if (tok_is(t, "br_if")) { + *out = WASM_INSN_BR_IF; + *has_imm = 1; + return 1; + } + if (tok_is(t, "br_table")) { + *out = WASM_INSN_BR_TABLE; + return 1; + } + if (tok_is(t, "select")) { + *out = WASM_INSN_SELECT; + return 1; + } + if (tok_is(t, "f32.const")) { + *out = WASM_INSN_F32_CONST; + *has_imm = 1; + return 1; + } + if (tok_is(t, "f64.const")) { + *out = WASM_INSN_F64_CONST; + *has_imm = 1; + return 1; + } + if (tok_is(t, "i32.const")) { + *out = WASM_INSN_I32_CONST; + *has_imm = 1; + return 1; + } + if (tok_is(t, "i64.const")) { + *out = WASM_INSN_I64_CONST; + *has_imm = 1; + return 1; + } + if (tok_is(t, "local.get")) { + *out = WASM_INSN_LOCAL_GET; + *has_imm = 1; + return 1; + } + if (tok_is(t, "local.set")) { + *out = WASM_INSN_LOCAL_SET; + *has_imm = 1; + return 1; + } + if (tok_is(t, "local.tee")) { + *out = WASM_INSN_LOCAL_TEE; + *has_imm = 1; + return 1; + } + if (tok_is(t, "call")) { + *out = WASM_INSN_CALL; + *has_imm = 1; + return 1; + } + if (tok_is(t, "call_indirect")) { + *out = WASM_INSN_CALL_INDIRECT; + return 1; + } + if (tok_is(t, "call_ref")) { + *out = WASM_INSN_CALL_REF; + return 1; + } + if (tok_is(t, "return_call")) { + *out = WASM_INSN_RETURN_CALL; + *has_imm = 1; + return 1; + } + if (tok_is(t, "return_call_indirect")) { + *out = WASM_INSN_RETURN_CALL_INDIRECT; + return 1; + } + if (tok_is(t, "return_call_ref")) { + *out = WASM_INSN_RETURN_CALL_REF; + return 1; + } + if (tok_is(t, "ref.null")) { + *out = WASM_INSN_REF_NULL; + *has_imm = 1; + return 1; + } + if (tok_is(t, "ref.func")) { + *out = WASM_INSN_REF_FUNC; + *has_imm = 1; + return 1; + } + if (tok_is(t, "ref.is_null")) { + *out = WASM_INSN_REF_IS_NULL; + return 1; + } + if (tok_is(t, "global.get")) { + *out = WASM_INSN_GLOBAL_GET; + *has_imm = 1; + return 1; + } + if (tok_is(t, "global.set")) { + *out = WASM_INSN_GLOBAL_SET; + *has_imm = 1; + return 1; + } + if (tok_is(t, "return")) { + *out = WASM_INSN_RETURN; + return 1; + } + if (tok_is(t, "drop")) { + *out = WASM_INSN_DROP; + return 1; + } + if (tok_is(t, "atomic.fence")) { + *out = WASM_INSN_ATOMIC_FENCE; + return 1; + } + if (tok_is(t, "i32.load")) { + *out = WASM_INSN_I32_LOAD; + return 1; + } + if (tok_is(t, "i32.atomic.load")) { + *out = WASM_INSN_I32_ATOMIC_LOAD; + return 1; + } + if (tok_is(t, "i64.load")) { + *out = WASM_INSN_I64_LOAD; + return 1; + } + if (tok_is(t, "f32.load")) { + *out = WASM_INSN_F32_LOAD; + return 1; + } + if (tok_is(t, "f64.load")) { + *out = WASM_INSN_F64_LOAD; + return 1; + } + if (tok_is(t, "i64.atomic.load")) { + *out = WASM_INSN_I64_ATOMIC_LOAD; + return 1; + } + if (tok_is(t, "i32.load8_s")) { + *out = WASM_INSN_I32_LOAD8_S; + return 1; + } + if (tok_is(t, "i32.load8_u")) { + *out = WASM_INSN_I32_LOAD8_U; + return 1; + } + if (tok_is(t, "i32.atomic.load8_u")) { + *out = WASM_INSN_I32_ATOMIC_LOAD8_U; + return 1; + } + if (tok_is(t, "i32.load16_s")) { + *out = WASM_INSN_I32_LOAD16_S; + return 1; + } + if (tok_is(t, "i32.load16_u")) { + *out = WASM_INSN_I32_LOAD16_U; + return 1; + } + if (tok_is(t, "i32.atomic.load16_u")) { + *out = WASM_INSN_I32_ATOMIC_LOAD16_U; + return 1; + } + if (tok_is(t, "i64.load8_s")) { + *out = WASM_INSN_I64_LOAD8_S; + return 1; + } + if (tok_is(t, "i64.load8_u")) { + *out = WASM_INSN_I64_LOAD8_U; + return 1; + } + if (tok_is(t, "i64.atomic.load8_u")) { + *out = WASM_INSN_I64_ATOMIC_LOAD8_U; + return 1; + } + if (tok_is(t, "i64.load16_s")) { + *out = WASM_INSN_I64_LOAD16_S; + return 1; + } + if (tok_is(t, "i64.load16_u")) { + *out = WASM_INSN_I64_LOAD16_U; + return 1; + } + if (tok_is(t, "i64.atomic.load16_u")) { + *out = WASM_INSN_I64_ATOMIC_LOAD16_U; + return 1; + } + if (tok_is(t, "i64.load32_s")) { + *out = WASM_INSN_I64_LOAD32_S; + return 1; + } + if (tok_is(t, "i64.load32_u")) { + *out = WASM_INSN_I64_LOAD32_U; + return 1; + } + if (tok_is(t, "i64.atomic.load32_u")) { + *out = WASM_INSN_I64_ATOMIC_LOAD32_U; + return 1; + } + if (tok_is(t, "i32.store")) { + *out = WASM_INSN_I32_STORE; + return 1; + } + if (tok_is(t, "i32.atomic.store")) { + *out = WASM_INSN_I32_ATOMIC_STORE; + return 1; + } + if (tok_is(t, "i64.store")) { + *out = WASM_INSN_I64_STORE; + return 1; + } + if (tok_is(t, "f32.store")) { + *out = WASM_INSN_F32_STORE; + return 1; + } + if (tok_is(t, "f64.store")) { + *out = WASM_INSN_F64_STORE; + return 1; + } + if (tok_is(t, "i64.atomic.store")) { + *out = WASM_INSN_I64_ATOMIC_STORE; + return 1; + } + if (tok_is(t, "i32.store8")) { + *out = WASM_INSN_I32_STORE8; + return 1; + } + if (tok_is(t, "i32.atomic.store8")) { + *out = WASM_INSN_I32_ATOMIC_STORE8; + return 1; + } + if (tok_is(t, "i32.store16")) { + *out = WASM_INSN_I32_STORE16; + return 1; + } + if (tok_is(t, "i32.atomic.store16")) { + *out = WASM_INSN_I32_ATOMIC_STORE16; + return 1; + } + if (tok_is(t, "i64.store8")) { + *out = WASM_INSN_I64_STORE8; + return 1; + } + if (tok_is(t, "i64.atomic.store8")) { + *out = WASM_INSN_I64_ATOMIC_STORE8; + return 1; + } + if (tok_is(t, "i64.store16")) { + *out = WASM_INSN_I64_STORE16; + return 1; + } + if (tok_is(t, "i64.atomic.store16")) { + *out = WASM_INSN_I64_ATOMIC_STORE16; + return 1; + } + if (tok_is(t, "i64.store32")) { + *out = WASM_INSN_I64_STORE32; + return 1; + } + if (tok_is(t, "i64.atomic.store32")) { + *out = WASM_INSN_I64_ATOMIC_STORE32; + return 1; + } + if (tok_is(t, "i32.atomic.rmw.add")) { + *out = WASM_INSN_I32_ATOMIC_RMW_ADD; + return 1; + } + if (tok_is(t, "i64.atomic.rmw.add")) { + *out = WASM_INSN_I64_ATOMIC_RMW_ADD; + return 1; + } + if (tok_is(t, "i32.atomic.rmw.sub")) { + *out = WASM_INSN_I32_ATOMIC_RMW_SUB; + return 1; + } + if (tok_is(t, "i64.atomic.rmw.sub")) { + *out = WASM_INSN_I64_ATOMIC_RMW_SUB; + return 1; + } + if (tok_is(t, "i32.atomic.rmw.and")) { + *out = WASM_INSN_I32_ATOMIC_RMW_AND; + return 1; + } + if (tok_is(t, "i64.atomic.rmw.and")) { + *out = WASM_INSN_I64_ATOMIC_RMW_AND; + return 1; + } + if (tok_is(t, "i32.atomic.rmw.or")) { + *out = WASM_INSN_I32_ATOMIC_RMW_OR; + return 1; + } + if (tok_is(t, "i64.atomic.rmw.or")) { + *out = WASM_INSN_I64_ATOMIC_RMW_OR; + return 1; + } + if (tok_is(t, "i32.atomic.rmw.xor")) { + *out = WASM_INSN_I32_ATOMIC_RMW_XOR; + return 1; + } + if (tok_is(t, "i64.atomic.rmw.xor")) { + *out = WASM_INSN_I64_ATOMIC_RMW_XOR; + return 1; + } + if (tok_is(t, "i32.atomic.rmw.xchg")) { + *out = WASM_INSN_I32_ATOMIC_RMW_XCHG; + return 1; + } + if (tok_is(t, "i64.atomic.rmw.xchg")) { + *out = WASM_INSN_I64_ATOMIC_RMW_XCHG; + return 1; + } + if (tok_is(t, "i32.atomic.rmw.cmpxchg")) { + *out = WASM_INSN_I32_ATOMIC_RMW_CMPXCHG; + return 1; + } + if (tok_is(t, "i64.atomic.rmw.cmpxchg")) { + *out = WASM_INSN_I64_ATOMIC_RMW_CMPXCHG; + return 1; + } + if (tok_is(t, "memory.atomic.wait32") || tok_is(t, "i32.atomic.wait")) { + *out = WASM_INSN_I32_ATOMIC_WAIT; + return 1; + } + if (tok_is(t, "memory.atomic.wait64") || tok_is(t, "i64.atomic.wait")) { + *out = WASM_INSN_I64_ATOMIC_WAIT; + return 1; + } + if (tok_is(t, "memory.atomic.notify") || tok_is(t, "atomic.notify")) { + *out = WASM_INSN_MEMORY_ATOMIC_NOTIFY; + return 1; + } + if (tok_is(t, "memory.size")) { + *out = WASM_INSN_MEMORY_SIZE; + return 1; + } + if (tok_is(t, "memory.grow")) { + *out = WASM_INSN_MEMORY_GROW; + return 1; + } + /* Bulk memory and table ops (0xfc prefix). */ + if (tok_is(t, "memory.init")) { *out = WASM_INSN_MEMORY_INIT; return 1; } + if (tok_is(t, "data.drop")) { *out = WASM_INSN_DATA_DROP; return 1; } + if (tok_is(t, "memory.copy")) { *out = WASM_INSN_MEMORY_COPY; return 1; } + if (tok_is(t, "memory.fill")) { *out = WASM_INSN_MEMORY_FILL; return 1; } + if (tok_is(t, "table.init")) { *out = WASM_INSN_TABLE_INIT; return 1; } + if (tok_is(t, "elem.drop")) { *out = WASM_INSN_ELEM_DROP; return 1; } + if (tok_is(t, "table.copy")) { *out = WASM_INSN_TABLE_COPY; return 1; } + if (tok_is(t, "table.grow")) { *out = WASM_INSN_TABLE_GROW; return 1; } + if (tok_is(t, "table.size")) { *out = WASM_INSN_TABLE_SIZE; return 1; } + if (tok_is(t, "table.fill")) { *out = WASM_INSN_TABLE_FILL; return 1; } + /* Saturating float-to-int conversions. */ + if (tok_is(t, "i32.trunc_sat_f32_s")) { *out = WASM_INSN_I32_TRUNC_SAT_F32_S; return 1; } + if (tok_is(t, "i32.trunc_sat_f32_u")) { *out = WASM_INSN_I32_TRUNC_SAT_F32_U; return 1; } + if (tok_is(t, "i32.trunc_sat_f64_s")) { *out = WASM_INSN_I32_TRUNC_SAT_F64_S; return 1; } + if (tok_is(t, "i32.trunc_sat_f64_u")) { *out = WASM_INSN_I32_TRUNC_SAT_F64_U; return 1; } + if (tok_is(t, "i64.trunc_sat_f32_s")) { *out = WASM_INSN_I64_TRUNC_SAT_F32_S; return 1; } + if (tok_is(t, "i64.trunc_sat_f32_u")) { *out = WASM_INSN_I64_TRUNC_SAT_F32_U; return 1; } + if (tok_is(t, "i64.trunc_sat_f64_s")) { *out = WASM_INSN_I64_TRUNC_SAT_F64_S; return 1; } + if (tok_is(t, "i64.trunc_sat_f64_u")) { *out = WASM_INSN_I64_TRUNC_SAT_F64_U; return 1; } + if (tok_is(t, "i32.add")) { + *out = WASM_INSN_I32_ADD; + return 1; + } + if (tok_is(t, "i32.sub")) { + *out = WASM_INSN_I32_SUB; + return 1; + } + if (tok_is(t, "i32.mul")) { + *out = WASM_INSN_I32_MUL; + return 1; + } + if (tok_is(t, "i32.div_s")) { + *out = WASM_INSN_I32_DIV_S; + return 1; + } + if (tok_is(t, "i32.div_u")) { + *out = WASM_INSN_I32_DIV_U; + return 1; + } + if (tok_is(t, "i32.rem_s")) { + *out = WASM_INSN_I32_REM_S; + return 1; + } + if (tok_is(t, "i32.rem_u")) { + *out = WASM_INSN_I32_REM_U; + return 1; + } + if (tok_is(t, "i32.and")) { + *out = WASM_INSN_I32_AND; + return 1; + } + if (tok_is(t, "i32.or")) { + *out = WASM_INSN_I32_OR; + return 1; + } + if (tok_is(t, "i32.xor")) { + *out = WASM_INSN_I32_XOR; + return 1; + } + if (tok_is(t, "i32.shl")) { + *out = WASM_INSN_I32_SHL; + return 1; + } + if (tok_is(t, "i32.shr_s")) { + *out = WASM_INSN_I32_SHR_S; + return 1; + } + if (tok_is(t, "i32.shr_u")) { + *out = WASM_INSN_I32_SHR_U; + return 1; + } + if (tok_is(t, "i32.rotl")) { + *out = WASM_INSN_I32_ROTL; + return 1; + } + if (tok_is(t, "i32.rotr")) { + *out = WASM_INSN_I32_ROTR; + return 1; + } + if (tok_is(t, "i32.clz")) { + *out = WASM_INSN_I32_CLZ; + return 1; + } + if (tok_is(t, "i32.ctz")) { + *out = WASM_INSN_I32_CTZ; + return 1; + } + if (tok_is(t, "i32.popcnt")) { + *out = WASM_INSN_I32_POPCNT; + return 1; + } + if (tok_is(t, "i32.eqz")) { + *out = WASM_INSN_I32_EQZ; + return 1; + } + if (tok_is(t, "i32.eq")) { + *out = WASM_INSN_I32_EQ; + return 1; + } + if (tok_is(t, "i32.ne")) { + *out = WASM_INSN_I32_NE; + return 1; + } + if (tok_is(t, "i32.lt_s")) { + *out = WASM_INSN_I32_LT_S; + return 1; + } + if (tok_is(t, "i32.lt_u")) { + *out = WASM_INSN_I32_LT_U; + return 1; + } + if (tok_is(t, "i32.gt_s")) { + *out = WASM_INSN_I32_GT_S; + return 1; + } + if (tok_is(t, "i32.gt_u")) { + *out = WASM_INSN_I32_GT_U; + return 1; + } + if (tok_is(t, "i32.le_s")) { + *out = WASM_INSN_I32_LE_S; + return 1; + } + if (tok_is(t, "i32.le_u")) { + *out = WASM_INSN_I32_LE_U; + return 1; + } + if (tok_is(t, "i32.ge_s")) { + *out = WASM_INSN_I32_GE_S; + return 1; + } + if (tok_is(t, "i32.ge_u")) { + *out = WASM_INSN_I32_GE_U; + return 1; + } + if (tok_is(t, "i64.add")) { + *out = WASM_INSN_I64_ADD; + return 1; + } + if (tok_is(t, "i64.sub")) { + *out = WASM_INSN_I64_SUB; + return 1; + } + if (tok_is(t, "i64.mul")) { + *out = WASM_INSN_I64_MUL; + return 1; + } + if (tok_is(t, "i64.div_s")) { + *out = WASM_INSN_I64_DIV_S; + return 1; + } + if (tok_is(t, "i64.div_u")) { + *out = WASM_INSN_I64_DIV_U; + return 1; + } + if (tok_is(t, "i64.rem_s")) { + *out = WASM_INSN_I64_REM_S; + return 1; + } + if (tok_is(t, "i64.rem_u")) { + *out = WASM_INSN_I64_REM_U; + return 1; + } + if (tok_is(t, "i64.and")) { + *out = WASM_INSN_I64_AND; + return 1; + } + if (tok_is(t, "i64.or")) { + *out = WASM_INSN_I64_OR; + return 1; + } + if (tok_is(t, "i64.xor")) { + *out = WASM_INSN_I64_XOR; + return 1; + } + if (tok_is(t, "i64.shl")) { + *out = WASM_INSN_I64_SHL; + return 1; + } + if (tok_is(t, "i64.shr_s")) { + *out = WASM_INSN_I64_SHR_S; + return 1; + } + if (tok_is(t, "i64.shr_u")) { + *out = WASM_INSN_I64_SHR_U; + return 1; + } + if (tok_is(t, "i64.rotl")) { + *out = WASM_INSN_I64_ROTL; + return 1; + } + if (tok_is(t, "i64.rotr")) { + *out = WASM_INSN_I64_ROTR; + return 1; + } + if (tok_is(t, "i64.clz")) { + *out = WASM_INSN_I64_CLZ; + return 1; + } + if (tok_is(t, "i64.ctz")) { + *out = WASM_INSN_I64_CTZ; + return 1; + } + if (tok_is(t, "i64.popcnt")) { + *out = WASM_INSN_I64_POPCNT; + return 1; + } + if (tok_is(t, "i64.eqz")) { + *out = WASM_INSN_I64_EQZ; + return 1; + } + if (tok_is(t, "i64.eq")) { + *out = WASM_INSN_I64_EQ; + return 1; + } + if (tok_is(t, "i64.ne")) { + *out = WASM_INSN_I64_NE; + return 1; + } + if (tok_is(t, "i64.lt_s")) { + *out = WASM_INSN_I64_LT_S; + return 1; + } + if (tok_is(t, "i64.lt_u")) { + *out = WASM_INSN_I64_LT_U; + return 1; + } + if (tok_is(t, "i64.gt_s")) { + *out = WASM_INSN_I64_GT_S; + return 1; + } + if (tok_is(t, "i64.gt_u")) { + *out = WASM_INSN_I64_GT_U; + return 1; + } + if (tok_is(t, "i64.le_s")) { + *out = WASM_INSN_I64_LE_S; + return 1; + } + if (tok_is(t, "i64.le_u")) { + *out = WASM_INSN_I64_LE_U; + return 1; + } + if (tok_is(t, "i64.ge_s")) { + *out = WASM_INSN_I64_GE_S; + return 1; + } + if (tok_is(t, "i64.ge_u")) { + *out = WASM_INSN_I64_GE_U; + return 1; + } + if (tok_is(t, "f32.add")) { + *out = WASM_INSN_F32_ADD; + return 1; + } + if (tok_is(t, "f32.sub")) { + *out = WASM_INSN_F32_SUB; + return 1; + } + if (tok_is(t, "f32.mul")) { + *out = WASM_INSN_F32_MUL; + return 1; + } + if (tok_is(t, "f32.div")) { + *out = WASM_INSN_F32_DIV; + return 1; + } + if (tok_is(t, "f32.eq")) { + *out = WASM_INSN_F32_EQ; + return 1; + } + if (tok_is(t, "f32.ne")) { + *out = WASM_INSN_F32_NE; + return 1; + } + if (tok_is(t, "f32.lt")) { + *out = WASM_INSN_F32_LT; + return 1; + } + if (tok_is(t, "f32.gt")) { + *out = WASM_INSN_F32_GT; + return 1; + } + if (tok_is(t, "f32.le")) { + *out = WASM_INSN_F32_LE; + return 1; + } + if (tok_is(t, "f32.ge")) { + *out = WASM_INSN_F32_GE; + return 1; + } + if (tok_is(t, "f64.add")) { + *out = WASM_INSN_F64_ADD; + return 1; + } + if (tok_is(t, "f64.sub")) { + *out = WASM_INSN_F64_SUB; + return 1; + } + if (tok_is(t, "f64.mul")) { + *out = WASM_INSN_F64_MUL; + return 1; + } + if (tok_is(t, "f64.div")) { + *out = WASM_INSN_F64_DIV; + return 1; + } + if (tok_is(t, "f64.eq")) { + *out = WASM_INSN_F64_EQ; + return 1; + } + if (tok_is(t, "f64.ne")) { + *out = WASM_INSN_F64_NE; + return 1; + } + if (tok_is(t, "f64.lt")) { + *out = WASM_INSN_F64_LT; + return 1; + } + if (tok_is(t, "f64.gt")) { + *out = WASM_INSN_F64_GT; + return 1; + } + if (tok_is(t, "f64.le")) { + *out = WASM_INSN_F64_LE; + return 1; + } + if (tok_is(t, "f64.ge")) { + *out = WASM_INSN_F64_GE; + return 1; + } + if (tok_is(t, "i32.wrap_i64")) { + *out = WASM_INSN_I32_WRAP_I64; + return 1; + } + if (tok_is(t, "i32.trunc_f32_s")) { + *out = WASM_INSN_I32_TRUNC_F32_S; + return 1; + } + if (tok_is(t, "i32.trunc_f32_u")) { + *out = WASM_INSN_I32_TRUNC_F32_U; + return 1; + } + if (tok_is(t, "i32.trunc_f64_s")) { + *out = WASM_INSN_I32_TRUNC_F64_S; + return 1; + } + if (tok_is(t, "i32.trunc_f64_u")) { + *out = WASM_INSN_I32_TRUNC_F64_U; + return 1; + } + if (tok_is(t, "i64.extend_i32_s")) { + *out = WASM_INSN_I64_EXTEND_I32_S; + return 1; + } + if (tok_is(t, "i64.extend_i32_u")) { + *out = WASM_INSN_I64_EXTEND_I32_U; + return 1; + } + if (tok_is(t, "i64.trunc_f32_s")) { + *out = WASM_INSN_I64_TRUNC_F32_S; + return 1; + } + if (tok_is(t, "i64.trunc_f32_u")) { + *out = WASM_INSN_I64_TRUNC_F32_U; + return 1; + } + if (tok_is(t, "i64.trunc_f64_s")) { + *out = WASM_INSN_I64_TRUNC_F64_S; + return 1; + } + if (tok_is(t, "i64.trunc_f64_u")) { + *out = WASM_INSN_I64_TRUNC_F64_U; + return 1; + } + if (tok_is(t, "f32.convert_i32_s")) { + *out = WASM_INSN_F32_CONVERT_I32_S; + return 1; + } + if (tok_is(t, "f32.convert_i32_u")) { + *out = WASM_INSN_F32_CONVERT_I32_U; + return 1; + } + if (tok_is(t, "f32.convert_i64_s")) { + *out = WASM_INSN_F32_CONVERT_I64_S; + return 1; + } + if (tok_is(t, "f32.convert_i64_u")) { + *out = WASM_INSN_F32_CONVERT_I64_U; + return 1; + } + if (tok_is(t, "f32.demote_f64")) { + *out = WASM_INSN_F32_DEMOTE_F64; + return 1; + } + if (tok_is(t, "f64.convert_i32_s")) { + *out = WASM_INSN_F64_CONVERT_I32_S; + return 1; + } + if (tok_is(t, "f64.convert_i32_u")) { + *out = WASM_INSN_F64_CONVERT_I32_U; + return 1; + } + if (tok_is(t, "f64.convert_i64_s")) { + *out = WASM_INSN_F64_CONVERT_I64_S; + return 1; + } + if (tok_is(t, "f64.convert_i64_u")) { + *out = WASM_INSN_F64_CONVERT_I64_U; + return 1; + } + if (tok_is(t, "f64.promote_f32")) { + *out = WASM_INSN_F64_PROMOTE_F32; + return 1; + } + if (tok_is(t, "i32.reinterpret_f32")) { + *out = WASM_INSN_I32_REINTERPRET_F32; + return 1; + } + if (tok_is(t, "i64.reinterpret_f64")) { + *out = WASM_INSN_I64_REINTERPRET_F64; + return 1; + } + if (tok_is(t, "f32.reinterpret_i32")) { + *out = WASM_INSN_F32_REINTERPRET_I32; + return 1; + } + if (tok_is(t, "f64.reinterpret_i64")) { + *out = WASM_INSN_F64_REINTERPRET_I64; + return 1; + } + return 0; +} + +static void wat_parse_func_index(WatParser* p, int64_t* out) { + uint32_t i; + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { + for (i = 0; i < p->module->nfuncs; ++i) { + if (wasm_name_eq(p->module->funcs[i].name, p->tok)) { + *out = i; + return; + } + } + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: unknown function name"); + } + if (!wat_parse_i64(p, out)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected instruction immediate"); +} + +static void wat_parse_type_index(WatParser* p, int64_t* out) { + uint32_t i; + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { + for (i = 0; i < p->module->ntypes; ++i) { + if (wasm_name_eq(p->module->types[i].name, p->tok)) { + *out = i; + return; + } + } + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: unknown type name"); + } + if (!wat_parse_i64(p, out)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected type index"); +} + +static void wat_parse_local_index(WatParser* p, WasmFunc* f, int64_t* out) { + uint32_t i, nlocals = f->nparams + f->nlocals; + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { + for (i = 0; i < nlocals; ++i) { + if (wasm_name_eq(f->local_names[i], p->tok)) { + *out = i; + return; + } + } + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: unknown local name"); + } + if (!wat_parse_i64(p, out)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected instruction immediate"); +} + +static void wat_parse_global_index(WatParser* p, int64_t* out) { + uint32_t i; + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { + for (i = 0; i < p->module->nglobals; ++i) { + if (wasm_name_eq(p->module->globals[i].name, p->tok)) { + *out = i; + return; + } + } + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: unknown global name"); + } + if (!wat_parse_i64(p, out)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected global index"); +} + +static void wat_parse_instr_imm(WatParser* p, WasmFunc* f, WasmInsnKind kind, + int64_t* out) { + switch (kind) { + case WASM_INSN_CALL: + case WASM_INSN_RETURN_CALL: + wat_parse_func_index(p, out); + break; + case WASM_INSN_GLOBAL_GET: + case WASM_INSN_GLOBAL_SET: + wat_parse_global_index(p, out); + break; + case WASM_INSN_LOCAL_GET: + case WASM_INSN_LOCAL_SET: + case WASM_INSN_LOCAL_TEE: + wat_parse_local_index(p, f, out); + break; + default: + if (!wat_parse_i64(p, out)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected instruction immediate"); + break; + } +} + + +static int wat_atom_prefix(WasmTok t, const char* prefix) { + size_t n = cfree_slice_cstr(prefix).len; + return t.kind == WT_ATOM && t.len >= n && memcmp(t.p, prefix, n) == 0; +} + +static int wat_parse_u32_atom(WasmTok t, uint32_t* out) { + uint64_t v = 0; + size_t i = 0; + unsigned base = 10; + if (t.kind != WT_ATOM || t.len == 0) return 0; + if (i + 2u <= t.len && t.p[i] == '0' && + (t.p[i + 1u] == 'x' || t.p[i + 1u] == 'X')) { + base = 16; + i += 2u; + } + if (i == t.len) return 0; + for (; i < t.len; ++i) { + int hd; + if (t.p[i] == '_') continue; + hd = wat_hex(t.p[i]); + if (hd < 0 || (unsigned)hd >= base) return 0; + if (v > (UINT32_MAX - (uint32_t)hd) / base) return 0; + v = v * base + (uint32_t)hd; + } + *out = (uint32_t)v; + return 1; +} + +static int wat_parse_u64_atom(WasmTok t, uint64_t* out) { + uint64_t v = 0; + size_t i = 0; + unsigned base = 10; + if (t.kind != WT_ATOM || t.len == 0) return 0; + if (i + 2u <= t.len && t.p[i] == '0' && + (t.p[i + 1u] == 'x' || t.p[i + 1u] == 'X')) { + base = 16; + i += 2u; + } + if (i == t.len) return 0; + for (; i < t.len; ++i) { + int hd; + if (t.p[i] == '_') continue; + hd = wat_hex(t.p[i]); + if (hd < 0 || (unsigned)hd >= base) return 0; + if (v > (UINT64_MAX - (uint64_t)hd) / base) return 0; + v = v * base + (uint64_t)hd; + } + *out = v; + return 1; +} + +static void wat_parse_memory_index(WatParser* p, uint32_t* out) { + uint32_t i; + int64_t idx; + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { + for (i = 0; i < p->module->nmemories; ++i) { + if (wasm_name_eq(p->module->memories[i].name, p->tok)) { + *out = i; + return; + } + } + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: unknown memory name"); + } + if (!wat_parse_i64(p, &idx) || idx < 0 || idx > UINT32_MAX) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected memory index"); + *out = (uint32_t)idx; +} + +static void wat_parse_mem_attrs(WatParser* p, uint32_t* align, + uint64_t* offset, uint32_t* memidx) { + while (p->tok.kind == WT_ATOM) { + WasmTok val; + if (wat_atom_prefix(p->tok, "align=")) { + val = p->tok; + val.p += 6; + val.len -= 6; + if (!wat_parse_u32_atom(val, align)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: bad memory alignment"); + wat_next(p); + } else if (wat_atom_prefix(p->tok, "offset=")) { + val = p->tok; + val.p += 7; + val.len -= 7; + if (!wat_parse_u64_atom(val, offset)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: bad memory offset"); + wat_next(p); + } else if (wat_atom_prefix(p->tok, "memory=")) { + val = p->tok; + val.p += 7; + val.len -= 7; + if (!wat_parse_u32_atom(val, memidx)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: bad memory index"); + wat_next(p); + } else if (wat_atom_prefix(p->tok, "mem=")) { + val = p->tok; + val.p += 4; + val.len -= 4; + if (!wat_parse_u32_atom(val, memidx)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: bad memory index"); + wat_next(p); + } else { + break; + } + } +} + +static void wat_reject_inline_result(WatParser* p, const char* what) { + if (p->tok.kind != WT_LPAREN) return; + wat_next(p); + if (!tok_is(p->tok, "result")) { + p->pos = (size_t)(p->tok.p - p->src); + p->line = p->tok.line; + p->col = p->tok.col; + p->tok.kind = WT_LPAREN; + p->tok.p = p->src + p->pos - 1u; + p->tok.len = 1; + p->tok.line = p->line; + p->tok.col = p->col - 1u; + return; + } + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: %.*s results are unsupported", + CFREE_SLICE_ARG(cfree_slice_cstr(what))); +} + +static void wat_parse_instr(WatParser* p, WasmFunc* f); + +static void wat_check_instr_feature(WatParser* p, WasmInsnKind kind) { + if (kind == WASM_INSN_RETURN_CALL || + kind == WASM_INSN_RETURN_CALL_INDIRECT || + kind == WASM_INSN_RETURN_CALL_REF) + wat_require_feature(p, WASM_FEATURE_TAIL_CALLS, "tail calls", + "tail-call instruction"); + if (kind == WASM_INSN_REF_NULL || kind == WASM_INSN_REF_FUNC || + kind == WASM_INSN_REF_IS_NULL || kind == WASM_INSN_CALL_REF || + kind == WASM_INSN_RETURN_CALL_REF) + wat_require_feature(p, WASM_FEATURE_TYPED_FUNC_REFS, + "typed function references", + "typed-reference instruction"); + if (kind == WASM_INSN_ATOMIC_FENCE || wasm_insn_is_atomic_mem(kind)) + wat_require_feature(p, WASM_FEATURE_THREADS, "threads", + "atomic instruction"); +} + +static uint32_t wat_parse_call_indirect_type(WatParser* p) { + int64_t typeidx; + wat_expect(p, WT_LPAREN, "'('"); + if (!tok_is(p->tok, "type")) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected call_indirect type"); + wat_next(p); + wat_parse_type_index(p, &typeidx); + if (typeidx < 0 || (uint64_t)typeidx >= p->module->ntypes) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: type index out of range"); + wat_next(p); + wat_expect(p, WT_RPAREN, "')'"); + return (uint32_t)typeidx; +} + +static void wat_parse_ref_null_type(WatParser* p, int64_t* out) { + if (tok_is(p->tok, "func") || tok_is(p->tok, "nofunc") || + tok_is(p->tok, "funcref")) { + *out = WASM_VAL_FUNCREF; + return; + } + if (tok_is(p->tok, "extern") || tok_is(p->tok, "noextern") || + tok_is(p->tok, "externref")) { + *out = WASM_VAL_EXTERNREF; + return; + } + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected reference type"); +} + +/* True for bulk-memory and table.* ops whose WAT form takes one or two + * index immediates rather than a memarg. */ +static int wat_is_bulk_op(WasmInsnKind k) { + switch (k) { + case WASM_INSN_MEMORY_INIT: case WASM_INSN_DATA_DROP: + case WASM_INSN_MEMORY_COPY: case WASM_INSN_MEMORY_FILL: + case WASM_INSN_TABLE_INIT: case WASM_INSN_ELEM_DROP: + case WASM_INSN_TABLE_COPY: case WASM_INSN_TABLE_GROW: + case WASM_INSN_TABLE_SIZE: case WASM_INSN_TABLE_FILL: + return 1; + default: return 0; + } +} + +/* Optionally parse a uleb-style index immediate. Accepts: + * - a numeric literal (any base/sign accepted by wat_parse_i64) + * - a `$name` resolved against the segment lookup callback `resolver`, + * which returns 1 with the resolved index in *out on success. + * Returns 0 if the token isn't a recognizable immediate. */ +typedef int (*WatNameResolver)(WatParser*, WasmTok name, uint32_t* out); +static int wat_try_parse_uleb_imm_named(WatParser* p, WatNameResolver resolver, + uint32_t* out) { + int64_t v; + WasmInsnKind next_kind; + int next_has_imm; + if (p->tok.kind != WT_ATOM) return 0; + if (p->tok.len && p->tok.p[0] == '$') { + if (!resolver) return 0; + if (!resolver(p, p->tok, out)) return 0; + wat_next(p); + return 1; + } + if (wat_instr_kind(p->tok, &next_kind, &next_has_imm)) return 0; + if (!wat_parse_i64(p, &v) || v < 0 || v > UINT32_MAX) return 0; + *out = (uint32_t)v; + wat_next(p); + return 1; +} + +static int wat_resolve_data_name(WatParser* p, WasmTok name, uint32_t* out) { + for (uint32_t i = 0; i < p->module->ndata; ++i) { + if (wasm_name_eq(p->module->data[i].name, name)) { + *out = i; + return 1; + } + } + return 0; +} + +static int wat_resolve_elem_name(WatParser* p, WasmTok name, uint32_t* out) { + for (uint32_t i = 0; i < p->module->nelems; ++i) { + if (wasm_name_eq(p->module->elems[i].name, name)) { + *out = i; + return 1; + } + } + return 0; +} + +/* Emit a bulk-memory or table op, parsing 0..2 index immediates depending on + * the kind. Caller has already consumed the opcode token. Named refs of the + * form `$name` resolve against the appropriate segment table (data segments + * for memory.init/data.drop, elem segments for table.init/elem.drop). */ +static void wat_emit_bulk_op(WatParser* p, WasmFunc* f, WasmInsnKind kind) { + uint32_t a = 0, b = 0; + int got_a, got_b; + WatNameResolver primary = NULL; + switch (kind) { + case WASM_INSN_MEMORY_INIT: case WASM_INSN_DATA_DROP: + primary = wat_resolve_data_name; break; + case WASM_INSN_TABLE_INIT: case WASM_INSN_ELEM_DROP: + primary = wat_resolve_elem_name; break; + default: primary = NULL; break; + } + got_a = wat_try_parse_uleb_imm_named(p, primary, &a); + got_b = wat_try_parse_uleb_imm_named(p, primary, &b); + wasm_func_add_insn(p->c, p->module, f, kind, 0); + switch (kind) { + case WASM_INSN_MEMORY_INIT: + /* memory.init [memidx] dataidx — single arg means dataidx, two args + * is (memidx dataidx). */ + if (got_b) { + f->insns[f->ninsns - 1u].memidx = a; + f->insns[f->ninsns - 1u].imm = (int64_t)b; + } else { + f->insns[f->ninsns - 1u].memidx = 0; + f->insns[f->ninsns - 1u].imm = (int64_t)a; + } + break; + case WASM_INSN_DATA_DROP: + f->insns[f->ninsns - 1u].imm = (int64_t)a; + break; + case WASM_INSN_MEMORY_COPY: + /* memory.copy [dst src] — both default to 0. */ + f->insns[f->ninsns - 1u].memidx = got_a ? a : 0; + f->insns[f->ninsns - 1u].aux_idx = got_b ? b : 0; + break; + case WASM_INSN_MEMORY_FILL: + f->insns[f->ninsns - 1u].memidx = got_a ? a : 0; + break; + case WASM_INSN_TABLE_INIT: + /* table.init [tableidx] elemidx. */ + if (got_b) { + f->insns[f->ninsns - 1u].aux_idx = a; + f->insns[f->ninsns - 1u].imm = (int64_t)b; + } else { + f->insns[f->ninsns - 1u].aux_idx = 0; + f->insns[f->ninsns - 1u].imm = (int64_t)a; + } + break; + case WASM_INSN_ELEM_DROP: + f->insns[f->ninsns - 1u].imm = (int64_t)a; + break; + case WASM_INSN_TABLE_COPY: + f->insns[f->ninsns - 1u].imm = (int64_t)(got_a ? a : 0); + f->insns[f->ninsns - 1u].aux_idx = got_b ? b : 0; + break; + case WASM_INSN_TABLE_GROW: + case WASM_INSN_TABLE_SIZE: + case WASM_INSN_TABLE_FILL: + f->insns[f->ninsns - 1u].imm = (int64_t)(got_a ? a : 0); + break; + default: break; + } +} + +static void wat_parse_instr_list(WatParser* p, WasmFunc* f) { + WasmInsnKind kind; + int has_imm; + int64_t imm = 0; + WasmTok head; + wat_expect(p, WT_LPAREN, "'('"); + head = p->tok; + p->module->current_loc = wat_tok_loc(p, head); + if (tok_is(head, "block") || tok_is(head, "loop")) { + kind = tok_is(head, "block") ? WASM_INSN_BLOCK : WASM_INSN_LOOP; + wat_next(p); + if (p->tok.kind == WT_LPAREN) { + wat_next(p); + if (tok_is(p->tok, "result")) { + wat_next(p); + if (!tok_is(p->tok, "i32") && !tok_is(p->tok, "i64")) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: unsupported block result type"); + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: block results are unsupported"); + } + p->pos = (size_t)(p->tok.p - p->src); + p->line = p->tok.line; + p->col = p->tok.col; + p->tok.kind = WT_LPAREN; + p->tok.p = p->src + p->pos - 1u; + p->tok.len = 1; + p->tok.line = p->line; + p->tok.col = p->col - 1u; + } + wasm_func_add_insn(p->c, p->module, f, kind, 0); + while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) + wat_parse_instr(p, f); + wasm_func_add_insn(p->c, p->module, f, WASM_INSN_END, 0); + wat_expect(p, WT_RPAREN, "')'"); + return; + } + if (tok_is(head, "if")) { + wat_next(p); + while (p->tok.kind == WT_LPAREN) { + WasmTok save_head; + size_t save_pos = p->pos; + uint32_t save_line = p->line, save_col = p->col; + wat_next(p); + save_head = p->tok; + p->pos = save_pos; + p->line = save_line; + p->col = save_col; + p->tok.kind = WT_LPAREN; + p->tok.p = p->src + p->pos - 1u; + p->tok.len = 1; + p->tok.line = save_line; + p->tok.col = save_col - 1u; + if (tok_is(save_head, "then") || tok_is(save_head, "else")) break; + if (tok_is(save_head, "result")) + wasm_error(p->c, wasm_loc(save_head.line, save_head.col), + "wasm wat: if results are unsupported"); + wat_parse_instr(p, f); + } + wasm_func_add_insn(p->c, p->module, f, WASM_INSN_IF, 0); + if (p->tok.kind == WT_LPAREN) { + wat_next(p); + if (!tok_is(p->tok, "then")) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected then"); + wat_next(p); + while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) + wat_parse_instr(p, f); + wat_expect(p, WT_RPAREN, "')'"); + } + if (p->tok.kind == WT_LPAREN) { + wat_next(p); + if (!tok_is(p->tok, "else")) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected else"); + wasm_func_add_insn(p->c, p->module, f, WASM_INSN_ELSE, 0); + wat_next(p); + while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) + wat_parse_instr(p, f); + wat_expect(p, WT_RPAREN, "')'"); + } + wasm_func_add_insn(p->c, p->module, f, WASM_INSN_END, 0); + wat_expect(p, WT_RPAREN, "')'"); + return; + } + if (!wat_instr_kind(head, &kind, &has_imm)) + wasm_error(p->c, wasm_loc(head.line, head.col), + "wasm wat: unsupported instruction"); + wat_check_instr_feature(p, kind); + wat_next(p); + if (wasm_insn_is_mem(kind)) { + uint32_t align = 0, memidx = 0; + uint64_t offset = 0; + wat_parse_mem_attrs(p, &align, &offset, &memidx); + while (p->tok.kind == WT_LPAREN) { + wat_next(p); + if (tok_is(p->tok, "memory")) { + wat_next(p); + wat_parse_memory_index(p, &memidx); + wat_next(p); + wat_expect(p, WT_RPAREN, "')'"); + } else { + p->pos = (size_t)(p->tok.p - p->src); + p->line = p->tok.line; + p->col = p->tok.col; + p->tok.kind = WT_LPAREN; + p->tok.p = p->src + p->pos - 1u; + p->tok.len = 1; + p->tok.line = p->line; + p->tok.col = p->col - 1u; + wat_parse_instr(p, f); + } + } + wasm_func_add_mem_insn(p->c, p->module, f, kind, align, offset, memidx); + wat_expect(p, WT_RPAREN, "')'"); + return; + } + if (kind == WASM_INSN_BR_TABLE) { + WasmInsn* in; + uint32_t n = 0; + while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { + int64_t target; + if (n >= 64u) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: too many br_table targets"); + if (!wat_parse_i64(p, &target) || target < 0 || target > UINT32_MAX) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: bad br_table target"); + wasm_func_add_insn(p->c, p->module, f, + n ? WASM_INSN_NOP : WASM_INSN_BR_TABLE, 0); + in = &f->insns[f->ninsns - 1u - n]; + in->targets[n++] = (uint32_t)target; + wat_next(p); + } + f->ninsns -= n - 1u; + f->insns[f->ninsns - 1u].ntargets = n; + wat_expect(p, WT_RPAREN, "')'"); + return; + } + if (kind == WASM_INSN_CALL_INDIRECT || + kind == WASM_INSN_RETURN_CALL_INDIRECT) { + uint32_t typeidx = wat_parse_call_indirect_type(p); + while (p->tok.kind == WT_LPAREN) wat_parse_instr(p, f); + wasm_func_add_insn(p->c, p->module, f, kind, typeidx); + wat_expect(p, WT_RPAREN, "')'"); + return; + } + if (kind == WASM_INSN_CALL_REF || kind == WASM_INSN_RETURN_CALL_REF) { + uint32_t typeidx = wat_parse_call_indirect_type(p); + while (p->tok.kind == WT_LPAREN) wat_parse_instr(p, f); + wasm_func_add_insn(p->c, p->module, f, kind, typeidx); + wat_expect(p, WT_RPAREN, "')'"); + return; + } + if (kind == WASM_INSN_REF_NULL) { + wat_parse_ref_null_type(p, &imm); + wat_next(p); + wasm_func_add_insn(p->c, p->module, f, kind, imm); + wat_expect(p, WT_RPAREN, "')'"); + return; + } + if (kind == WASM_INSN_REF_FUNC) { + wat_parse_func_index(p, &imm); + wat_next(p); + wasm_func_add_insn(p->c, p->module, f, kind, imm); + wat_expect(p, WT_RPAREN, "')'"); + return; + } + if (wat_is_bulk_op(kind)) { + wat_emit_bulk_op(p, f, kind); + while (p->tok.kind == WT_LPAREN) wat_parse_instr(p, f); + wat_expect(p, WT_RPAREN, "')'"); + return; + } + if (kind == WASM_INSN_MEMORY_SIZE || kind == WASM_INSN_MEMORY_GROW) { + uint32_t memidx = 0; + while (p->tok.kind == WT_LPAREN) { + wat_next(p); + if (!tok_is(p->tok, "memory")) { + p->pos = (size_t)(p->tok.p - p->src); + p->line = p->tok.line; + p->col = p->tok.col; + p->tok.kind = WT_LPAREN; + p->tok.p = p->src + p->pos - 1u; + p->tok.len = 1; + p->tok.line = p->line; + p->tok.col = p->col - 1u; + wat_parse_instr(p, f); + continue; + } + wat_next(p); + wat_parse_memory_index(p, &memidx); + wat_next(p); + wat_expect(p, WT_RPAREN, "')'"); + } + { + WasmInsnKind next_kind; + int next_has_imm; + if (p->tok.kind == WT_ATOM && + !wat_instr_kind(p->tok, &next_kind, &next_has_imm)) { + wat_parse_memory_index(p, &memidx); + wat_next(p); + } + } + wasm_func_add_insn(p->c, p->module, f, + kind == WASM_INSN_MEMORY_GROW ? WASM_INSN_MEMORY_GROW + : WASM_INSN_MEMORY_SIZE, + 0); + f->insns[f->ninsns - 1u].memidx = memidx; + wat_expect(p, WT_RPAREN, "')'"); + return; + } + if (has_imm) { + if (kind == WASM_INSN_F32_CONST || kind == WASM_INSN_F64_CONST) { + double fv; + if (!wat_parse_f64(p, &fv)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected float immediate"); + wat_next(p); + while (p->tok.kind == WT_LPAREN) wat_parse_instr(p, f); + wasm_func_add_fp_insn(p->c, p->module, f, kind, fv); + wat_expect(p, WT_RPAREN, "')'"); + return; + } else { + wat_parse_instr_imm(p, f, kind, &imm); + wat_next(p); + } + } + while (p->tok.kind == WT_LPAREN) wat_parse_instr(p, f); + wasm_func_add_insn(p->c, p->module, f, kind, imm); + wat_expect(p, WT_RPAREN, "')'"); +} + +static void wat_parse_instr(WatParser* p, WasmFunc* f) { + WasmInsnKind kind; + int has_imm; + if (p->tok.kind == WT_LPAREN) { + wat_parse_instr_list(p, f); + return; + } + if (!wat_instr_kind(p->tok, &kind, &has_imm)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: unsupported instruction"); + p->module->current_loc = wat_tok_loc(p, p->tok); + wat_check_instr_feature(p, kind); + wat_next(p); + if (kind == WASM_INSN_BLOCK || kind == WASM_INSN_LOOP) + wat_reject_inline_result(p, "block"); + else if (kind == WASM_INSN_IF) + wat_reject_inline_result(p, "if"); + if (wasm_insn_is_mem(kind)) { + uint32_t align = 0, memidx = 0; + uint64_t offset = 0; + wat_parse_mem_attrs(p, &align, &offset, &memidx); + wasm_func_add_mem_insn(p->c, p->module, f, kind, align, offset, memidx); + return; + } + if (kind == WASM_INSN_BR_TABLE) { + WasmInsn* in; + uint32_t n = 0; + wasm_func_add_insn(p->c, p->module, f, WASM_INSN_BR_TABLE, 0); + in = &f->insns[f->ninsns - 1u]; + while (p->tok.kind == WT_ATOM) { + WasmInsnKind next_kind; + int next_has_imm; + int64_t target; + if (wat_instr_kind(p->tok, &next_kind, &next_has_imm)) break; + if (n >= 64u) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: too many br_table targets"); + if (!wat_parse_i64(p, &target) || target < 0 || target > UINT32_MAX) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: bad br_table target"); + in->targets[n++] = (uint32_t)target; + wat_next(p); + } + in->ntargets = n; + return; + } + if (kind == WASM_INSN_CALL_INDIRECT || + kind == WASM_INSN_RETURN_CALL_INDIRECT) { + uint32_t typeidx = wat_parse_call_indirect_type(p); + wasm_func_add_insn(p->c, p->module, f, kind, typeidx); + return; + } + if (kind == WASM_INSN_CALL_REF || kind == WASM_INSN_RETURN_CALL_REF) { + uint32_t typeidx = wat_parse_call_indirect_type(p); + wasm_func_add_insn(p->c, p->module, f, kind, typeidx); + return; + } + if (kind == WASM_INSN_REF_NULL) { + int64_t imm; + wat_parse_ref_null_type(p, &imm); + wasm_func_add_insn(p->c, p->module, f, kind, imm); + wat_next(p); + return; + } + if (kind == WASM_INSN_REF_FUNC) { + int64_t imm; + wat_parse_func_index(p, &imm); + wasm_func_add_insn(p->c, p->module, f, kind, imm); + wat_next(p); + return; + } + if (kind == WASM_INSN_MEMORY_SIZE || kind == WASM_INSN_MEMORY_GROW) { + uint32_t memidx = 0; + WasmInsnKind next_kind; + int next_has_imm; + if (p->tok.kind == WT_ATOM && !wat_instr_kind(p->tok, &next_kind, + &next_has_imm)) { + wat_parse_memory_index(p, &memidx); + wat_next(p); + } + wasm_func_add_insn(p->c, p->module, f, kind, 0); + f->insns[f->ninsns - 1u].memidx = memidx; + return; + } + if (wat_is_bulk_op(kind)) { + wat_emit_bulk_op(p, f, kind); + return; + } + if (has_imm) { + if (kind == WASM_INSN_F32_CONST || kind == WASM_INSN_F64_CONST) { + double fv; + if (!wat_parse_f64(p, &fv)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected float immediate"); + wasm_func_add_fp_insn(p->c, p->module, f, kind, fv); + wat_next(p); + } else { + int64_t imm; + wat_parse_instr_imm(p, f, kind, &imm); + wasm_func_add_insn(p->c, p->module, f, kind, imm); + wat_next(p); + } + } else { + wasm_func_add_insn(p->c, p->module, f, kind, 0); + } +} + +static void wat_parse_func(WatParser* p) { + WasmFunc* f = wasm_add_func(p->c, p->module); + uint32_t checked_params = 0; + uint32_t checked_results = 0; + f->loc = p->field_loc; + wat_expect(p, WT_LPAREN, "'('"); + if (!tok_is(p->tok, "func")) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected func"); + wat_next(p); + if (p->tok.kind == WT_ATOM && p->tok.len > 0 && p->tok.p[0] == '$') { + f->name = wat_dup_atom(p, p->tok); + wat_next(p); + } + while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { + if (p->tok.kind == WT_LPAREN) { + wat_next(p); + if (tok_is(p->tok, "export")) { + wat_next(p); + if (p->tok.kind != WT_STRING) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected export string"); + f->export_name = wat_dup_string(p, p->tok, NULL); + { + WasmExport* ex = wasm_add_export(p->c, p->module); + ex->name = wasm_strdup(p->module->heap, f->export_name, + cfree_slice_cstr(f->export_name).len); + if (!ex->name) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm: out of memory"); + ex->kind = 0; + ex->index = p->module->nfuncs - 1u; + } + wat_next(p); + wat_expect(p, WT_RPAREN, "')'"); + } else if (tok_is(p->tok, "type")) { + int64_t typeidx; + wat_next(p); + wat_parse_type_index(p, &typeidx); + if (typeidx < 0 || (uint64_t)typeidx >= p->module->ntypes) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: type index out of range"); + f->typeidx = (uint32_t)typeidx; + f->has_typeidx = 1; + wasm_func_set_params(p->c, p->module, f, + p->module->types[typeidx].params, + p->module->types[typeidx].nparams); + f->nresults = p->module->types[typeidx].nresults; + memcpy(f->results, p->module->types[typeidx].results, + sizeof(f->results[0]) * f->nresults); + wat_next(p); + wat_expect(p, WT_RPAREN, "')'"); + } else if (tok_is(p->tok, "param")) { + WasmTok pending_name; + int have_name = 0; + memset(&pending_name, 0, sizeof pending_name); + wat_next(p); + while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { + WasmValType vt; + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { + pending_name = p->tok; + have_name = 1; + wat_next(p); + continue; + } + if (!wat_val_type(p->tok, &vt)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected parameter type"); + if (!wasm_is_frontend_value_type(vt)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: unsupported parameter type"); + if (f->has_typeidx) { + if (checked_params >= f->nparams || f->params[checked_params] != vt) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: parameter does not match type"); + if (have_name) { + wasm_func_set_local_name(p->c, p->module, f, checked_params, + pending_name.p, pending_name.len); + have_name = 0; + } + checked_params++; + wat_next(p); + continue; + } + if (have_name) { + wasm_func_set_local_name(p->c, p->module, f, f->nparams, + pending_name.p, pending_name.len); + have_name = 0; + } + wasm_func_push_param(p->c, p->module, f, vt); + wat_next(p); + } + wat_expect(p, WT_RPAREN, "')'"); + } else if (tok_is(p->tok, "result")) { + WasmValType vt; + wat_next(p); + if (!wat_val_type(p->tok, &vt)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected result type"); + if (!wasm_is_frontend_value_type(vt)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: unsupported result type"); + if (f->has_typeidx) { + if (checked_results >= f->nresults || + f->results[checked_results] != vt) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: result does not match type"); + checked_results++; + } else { + f->results[0] = vt; + f->nresults = 1; + } + wat_next(p); + wat_expect(p, WT_RPAREN, "')'"); + } else if (tok_is(p->tok, "local")) { + WasmTok pending_name; + int have_name = 0; + memset(&pending_name, 0, sizeof pending_name); + wat_next(p); + while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { + WasmValType vt; + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { + pending_name = p->tok; + have_name = 1; + wat_next(p); + continue; + } + if (!wat_val_type(p->tok, &vt)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected local type"); + if (have_name) { + uint32_t index = f->nparams + f->nlocals; + wasm_func_set_local_name(p->c, p->module, f, index, pending_name.p, + pending_name.len); + have_name = 0; + } + wasm_func_push_local(p->c, p->module, f, vt); + wat_next(p); + } + wat_expect(p, WT_RPAREN, "')'"); + } else { + p->pos = (size_t)(p->tok.p - p->src); + p->line = p->tok.line; + p->col = p->tok.col; + p->tok.kind = WT_LPAREN; + p->tok.p = p->src + p->pos - 1u; + p->tok.len = 1; + p->tok.line = p->line; + p->tok.col = p->col - 1u; + wat_parse_instr(p, f); + } + } else { + wat_parse_instr(p, f); + } + } + if (!f->has_typeidx) + f->typeidx = wasm_intern_func_type(p->c, p->module, f); + else if (checked_params && checked_params != f->nparams) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: parameter list does not match type"); + wat_expect(p, WT_RPAREN, "')'"); +} + +static void wat_parse_type_field(WatParser* p) { + WasmFuncType* t = wasm_add_type(p->c, p->module); + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { + t->name = wat_dup_atom(p, p->tok); + wat_next(p); + } + wat_expect(p, WT_LPAREN, "'('"); + if (!tok_is(p->tok, "func")) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected func type"); + wat_next(p); + while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { + wat_expect(p, WT_LPAREN, "'('"); + if (tok_is(p->tok, "param")) { + wat_next(p); + while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { + WasmValType vt; + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { + wat_next(p); + continue; + } + if (!wat_val_type(p->tok, &vt) || !wasm_is_num_type(vt)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected parameter type"); + wasm_type_push_param(p->c, p->module, t, vt); + wat_next(p); + } + } else if (tok_is(p->tok, "result")) { + wat_next(p); + while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { + WasmValType vt; + if (!wat_val_type(p->tok, &vt) || !wasm_is_num_type(vt)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected result type"); + if (t->nresults >= 1u) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: multi-result unsupported"); + t->results[t->nresults++] = vt; + wat_next(p); + } + } else { + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected type field"); + } + wat_expect(p, WT_RPAREN, "')'"); + } + wat_expect(p, WT_RPAREN, "')'"); + wat_expect(p, WT_RPAREN, "')'"); +} + +static void wat_parse_export_field(WatParser* p) { + char* name; + int64_t idx = 0; + uint8_t kind; + if (p->tok.kind != WT_STRING) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected export string"); + name = wat_dup_string(p, p->tok, NULL); + wat_next(p); + wat_expect(p, WT_LPAREN, "'('"); + kind = wasm_export_kind_from_tok(p->tok); + if (kind == 0xffu) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected export kind"); + wat_next(p); + if (kind == 0) + wat_parse_func_index(p, &idx); + else if (!wat_parse_i64(p, &idx)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected export index"); + if (idx < 0 || idx > UINT32_MAX) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: export index out of range"); + { + WasmExport* ex = wasm_add_export(p->c, p->module); + ex->name = name; + ex->kind = kind; + ex->index = (uint32_t)idx; + } + if (kind == 0 && (uint64_t)idx < p->module->nfuncs) { + wasm_free_str(p->module->heap, &p->module->funcs[idx].export_name); + p->module->funcs[idx].export_name = + wasm_strdup(p->module->heap, name, cfree_slice_cstr(name).len); + } else if (kind == 2 && (uint64_t)idx < p->module->nmemories) { + wasm_free_str(p->module->heap, + &p->module->memories[(uint32_t)idx].export_name); + p->module->memories[(uint32_t)idx].export_name = + wasm_strdup(p->module->heap, name, cfree_slice_cstr(name).len); + } else if (kind == 1 && (uint64_t)idx < p->module->ntables) { + wasm_free_str(p->module->heap, &p->module->tables[idx].export_name); + p->module->tables[idx].export_name = + wasm_strdup(p->module->heap, name, cfree_slice_cstr(name).len); + } else if (kind == 3 && (uint64_t)idx < p->module->nglobals) { + wasm_free_str(p->module->heap, &p->module->globals[idx].export_name); + p->module->globals[idx].export_name = + wasm_strdup(p->module->heap, name, cfree_slice_cstr(name).len); + } + wat_next(p); + wat_expect(p, WT_RPAREN, "')'"); + wat_expect(p, WT_RPAREN, "')'"); +} + +static void wat_parse_memory_field(WatParser* p) { + int64_t min_pages, max_pages; + WasmMemory* mem = wasm_add_memory(p->c, p->module); + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { + mem->name = wat_dup_atom(p, p->tok); + wat_next(p); + } + while (p->tok.kind == WT_ATOM && (tok_is(p->tok, "i64") || + tok_is(p->tok, "shared"))) { + if (tok_is(p->tok, "i64")) + mem->is64 = 1; + else + mem->shared = 1; + wat_next(p); + } + if (!wat_parse_i64(p, &min_pages) || min_pages < 0) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected memory minimum"); + mem->min_pages = (uint64_t)min_pages; + wat_next(p); + while (p->tok.kind == WT_ATOM && (tok_is(p->tok, "i64") || + tok_is(p->tok, "shared"))) { + if (tok_is(p->tok, "i64")) + mem->is64 = 1; + else + mem->shared = 1; + wat_next(p); + } + if (p->tok.kind != WT_RPAREN) { + if (!wat_parse_i64(p, &max_pages) || max_pages < min_pages) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: bad memory maximum"); + mem->has_max = 1; + mem->max_pages = (uint64_t)max_pages; + wat_next(p); + while (p->tok.kind == WT_ATOM && (tok_is(p->tok, "i64") || + tok_is(p->tok, "shared"))) { + if (tok_is(p->tok, "i64")) + mem->is64 = 1; + else + mem->shared = 1; + wat_next(p); + } + } + (void)mem; + wat_expect(p, WT_RPAREN, "')'"); +} + +static void wat_parse_memory_limits(WatParser* p, WasmMemory* mem) { + int64_t lo, hi; + while (p->tok.kind == WT_ATOM && (tok_is(p->tok, "i64") || + tok_is(p->tok, "shared"))) { + if (tok_is(p->tok, "i64")) + mem->is64 = 1; + else + mem->shared = 1; + wat_next(p); + } + if (!wat_parse_i64(p, &lo) || lo < 0) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected memory minimum"); + mem->min_pages = (uint64_t)lo; + wat_next(p); + while (p->tok.kind == WT_ATOM && (tok_is(p->tok, "i64") || + tok_is(p->tok, "shared"))) { + if (tok_is(p->tok, "i64")) + mem->is64 = 1; + else + mem->shared = 1; + wat_next(p); + } + if (p->tok.kind != WT_RPAREN) { + if (!wat_parse_i64(p, &hi) || hi < lo) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: bad memory maximum"); + mem->has_max = 1; + mem->max_pages = (uint64_t)hi; + wat_next(p); + while (p->tok.kind == WT_ATOM && (tok_is(p->tok, "i64") || + tok_is(p->tok, "shared"))) { + if (tok_is(p->tok, "i64")) + mem->is64 = 1; + else + mem->shared = 1; + wat_next(p); + } + } +} + +static void wat_parse_table_limits_and_type(WatParser* p, WasmTable* t) { + int64_t lo, hi; + if (!wat_parse_i64(p, &lo) || lo < 0 || lo > UINT32_MAX) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected table minimum"); + t->min = (uint32_t)lo; + wat_next(p); + if (p->tok.kind == WT_ATOM) { + WasmValType maybe_type; + if (!wat_val_type(p->tok, &maybe_type)) { + if (!wat_parse_i64(p, &hi) || hi < lo || hi > UINT32_MAX) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: bad table maximum"); + t->has_max = 1; + t->max = (uint32_t)hi; + wat_next(p); + } + } + if (!wat_val_type(p->tok, &t->elem_type)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected table element type"); + wat_next(p); +} + +static void wat_parse_import_field(WatParser* p) { + char *mod, *name; + if (p->tok.kind != WT_STRING) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected import module string"); + mod = wat_dup_string(p, p->tok, NULL); + wat_next(p); + if (p->tok.kind != WT_STRING) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected import name string"); + name = wat_dup_string(p, p->tok, NULL); + wat_next(p); + wat_expect(p, WT_LPAREN, "'('"); + if (tok_is(p->tok, "func")) { + WasmFunc* f = wasm_add_func(p->c, p->module); + f->is_import = 1; + f->import_module = mod; + f->import_name = name; + wat_next(p); + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { + f->name = wat_dup_atom(p, p->tok); + wat_next(p); + } + while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { + wat_expect(p, WT_LPAREN, "'('"); + if (tok_is(p->tok, "type")) { + int64_t typeidx; + wat_next(p); + wat_parse_type_index(p, &typeidx); + if (typeidx < 0 || (uint64_t)typeidx >= p->module->ntypes) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: type index out of range"); + f->typeidx = (uint32_t)typeidx; + f->has_typeidx = 1; + wasm_func_set_params(p->c, p->module, f, + p->module->types[typeidx].params, + p->module->types[typeidx].nparams); + f->nresults = p->module->types[typeidx].nresults; + memcpy(f->results, p->module->types[typeidx].results, + sizeof(f->results[0]) * f->nresults); + wat_next(p); + } else if (tok_is(p->tok, "param")) { + wat_next(p); + while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { + WasmValType vt; + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { + wat_next(p); + continue; + } + if (!wat_val_type(p->tok, &vt) || !wasm_is_frontend_value_type(vt)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected parameter type"); + wasm_func_push_param(p->c, p->module, f, vt); + wat_next(p); + } + } else if (tok_is(p->tok, "result")) { + wat_next(p); + while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { + WasmValType vt; + if (!wat_val_type(p->tok, &vt) || !wasm_is_frontend_value_type(vt)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected result type"); + if (f->nresults >= 1u) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: multi-result unsupported"); + f->results[f->nresults++] = vt; + wat_next(p); + } + } else { + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected import func type field"); + } + wat_expect(p, WT_RPAREN, "')'"); + } + if (!f->has_typeidx) f->typeidx = wasm_intern_func_type(p->c, p->module, f); + } else if (tok_is(p->tok, "memory")) { + WasmMemory* mem = wasm_add_memory(p->c, p->module); + mem->is_import = 1; + mem->import_module = mod; + mem->import_name = name; + wat_next(p); + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { + mem->name = wat_dup_atom(p, p->tok); + wat_next(p); + } + wat_parse_memory_limits(p, mem); + } else if (tok_is(p->tok, "table")) { + WasmTable* t = wasm_add_table(p->c, p->module); + t->is_import = 1; + t->import_module = mod; + t->import_name = name; + wat_next(p); + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { + t->name = wat_dup_atom(p, p->tok); + wat_next(p); + } + wat_parse_table_limits_and_type(p, t); + } else if (tok_is(p->tok, "global")) { + WasmGlobal* g = wasm_add_global(p->c, p->module); + g->is_import = 1; + g->import_module = mod; + g->import_name = name; + wat_next(p); + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { + g->name = wat_dup_atom(p, p->tok); + wat_next(p); + } + if (p->tok.kind == WT_LPAREN) { + wat_next(p); + if (!tok_is(p->tok, "mut")) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected mut"); + g->mutable_ = 1; + wat_next(p); + if (!wat_val_type(p->tok, &g->type) || !wasm_is_num_type(g->type)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected global type"); + wat_next(p); + wat_expect(p, WT_RPAREN, "')'"); + } else { + if (!wat_val_type(p->tok, &g->type) || !wasm_is_num_type(g->type)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected global type"); + wat_next(p); + } + } else { + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: unsupported import kind"); + } + wat_expect(p, WT_RPAREN, "')'"); + wat_expect(p, WT_RPAREN, "')'"); +} + +static void wat_parse_table_field(WatParser* p) { + WasmTable* t = wasm_add_table(p->c, p->module); + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { + t->name = wat_dup_atom(p, p->tok); + wat_next(p); + } + wat_parse_table_limits_and_type(p, t); + wat_expect(p, WT_RPAREN, "')'"); +} + +static void wat_parse_const_expr(WatParser* p, WasmInsn* out) { + WasmInsnKind kind; + int has_imm; + memset(out, 0, sizeof *out); + wat_expect(p, WT_LPAREN, "'('"); + if (!wat_instr_kind(p->tok, &kind, &has_imm) || + (kind != WASM_INSN_I32_CONST && kind != WASM_INSN_I64_CONST && + kind != WASM_INSN_F32_CONST && kind != WASM_INSN_F64_CONST)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected constant expression"); + out->kind = (uint8_t)kind; + out->loc = wat_tok_loc(p, p->tok); + wat_next(p); + if (kind == WASM_INSN_F32_CONST || kind == WASM_INSN_F64_CONST) { + if (!wat_parse_f64(p, &out->fp)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected float immediate"); + } else if (!wat_parse_i64(p, &out->imm)) { + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected integer immediate"); + } + wat_next(p); + wat_expect(p, WT_RPAREN, "')'"); +} + +static void wat_parse_global_field(WatParser* p) { + WasmGlobal* g = wasm_add_global(p->c, p->module); + g->loc = p->field_loc; + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { + g->name = wat_dup_atom(p, p->tok); + wat_next(p); + } + if (p->tok.kind == WT_LPAREN) { + wat_next(p); + if (tok_is(p->tok, "export")) { + wat_next(p); + if (p->tok.kind != WT_STRING) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected export string"); + g->export_name = wat_dup_string(p, p->tok, NULL); + { + WasmExport* ex = wasm_add_export(p->c, p->module); + ex->name = wasm_strdup(p->module->heap, g->export_name, + cfree_slice_cstr(g->export_name).len); + if (!ex->name) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm: out of memory"); + ex->kind = 3; + ex->index = p->module->nglobals - 1u; + } + wat_next(p); + wat_expect(p, WT_RPAREN, "')'"); + } else if (tok_is(p->tok, "mut")) { + g->mutable_ = 1; + wat_next(p); + if (!wat_val_type(p->tok, &g->type) || !wasm_is_num_type(g->type)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected global type"); + wat_next(p); + wat_expect(p, WT_RPAREN, "')'"); + } else { + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected global type"); + } + } else { + if (!wat_val_type(p->tok, &g->type) || !wasm_is_num_type(g->type)) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected global type"); + wat_next(p); + } + if (!g->type) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: missing global type"); + wat_parse_const_expr(p, &g->init); + wat_expect(p, WT_RPAREN, "')'"); +} + +static void wat_parse_elem_field(WatParser* p) { + WasmElemSegment* e = wasm_add_elem(p->c, p->module); + int has_offset = 0; + int is_declarative = 0; + e->tableidx = 0; + e->elem_type = WASM_VAL_FUNCREF; + /* Optional `$name` or "declare" keyword for declarative segments. */ + if (p->tok.kind == WT_ATOM && p->tok.len && p->tok.p[0] == '$') { + e->name = wasm_strdup(p->module->heap, p->tok.p, p->tok.len); + wat_next(p); + } + if (tok_is(p->tok, "declare")) { + is_declarative = 1; + wat_next(p); + } else if (p->tok.kind == WT_ATOM) { + int64_t tableidx; + if (wat_parse_i64(p, &tableidx)) { + if (tableidx < 0 || tableidx > UINT32_MAX) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: table index out of range"); + e->tableidx = (uint32_t)tableidx; + wat_next(p); + } + } + if (!is_declarative && p->tok.kind == WT_LPAREN) { + WasmInsn off; + wat_parse_const_expr(p, &off); + if (off.kind != WASM_INSN_I32_CONST || off.imm < 0) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: element offset must be i32.const"); + e->offset = off.imm; + has_offset = 1; + } + if (is_declarative) e->mode = WASM_SEG_DECLARATIVE; + else if (has_offset) e->mode = WASM_SEG_ACTIVE; + else e->mode = WASM_SEG_PASSIVE; + if (tok_is(p->tok, "func")) wat_next(p); + while (p->tok.kind != WT_RPAREN && p->tok.kind != WT_EOF) { + int64_t idx; + wat_parse_func_index(p, &idx); + if (idx < 0 || idx > UINT32_MAX) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: element function index out of range"); + wasm_elem_push_func(p->c, p->module, e, (uint32_t)idx); + wat_next(p); + } + wat_expect(p, WT_RPAREN, "')'"); +} + +static void wat_parse_start_field(WatParser* p) { + int64_t idx; + p->module->start_field_loc = p->field_loc; + wat_parse_func_index(p, &idx); + if (idx < 0 || idx > UINT32_MAX) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: start function index out of range"); + p->module->has_start = 1; + p->module->start_func = (uint32_t)idx; + wat_next(p); + wat_expect(p, WT_RPAREN, "')'"); +} + +static void wat_parse_custom_field(WatParser* p) { + WasmCustom* cs; + size_t n = 0; + char* bytes; + if (p->tok.kind != WT_STRING) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected custom section name"); + cs = wasm_add_custom(p->c, p->module); + cs->name = wat_dup_string(p, p->tok, NULL); + if (cfree_slice_eq_cstr(cfree_slice_cstr(cs->name), "target_features") || + cfree_slice_eq_cstr(cfree_slice_cstr(cs->name), "target-feature")) + p->module->has_target_features = 1; + wat_next(p); + if (p->tok.kind == WT_STRING) { + bytes = wat_dup_string(p, p->tok, &n); + cs->data = (uint8_t*)p->module->heap->alloc(p->module->heap, n ? n : 1u, 1); + if (!cs->data) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm: out of memory"); + memcpy(cs->data, bytes, n); + cs->len = (uint32_t)n; + p->module->heap->free(p->module->heap, bytes, p->tok.len + 1u); + wat_next(p); + } + wat_expect(p, WT_RPAREN, "')'"); +} + +static void wat_parse_data_field(WatParser* p) { + int64_t offset = 0; + uint32_t memidx = 0; + int has_offset = 0; + char* bytes = NULL; + size_t nbytes = 0; + size_t alloc_len = 0; + char* seg_name = NULL; + WasmDataSegment* d; + if (p->tok.kind == WT_ATOM) { + WasmInsnKind next_kind; + int next_has_imm; + if (p->tok.len && p->tok.p[0] == '$') { + seg_name = wasm_strdup(p->module->heap, p->tok.p, p->tok.len); + wat_next(p); + } else if (!wat_instr_kind(p->tok, &next_kind, &next_has_imm)) { + wat_parse_memory_index(p, &memidx); + wat_next(p); + } else { + wat_next(p); + } + } + if (p->tok.kind == WT_LPAREN) { + size_t save_pos = p->pos; + uint32_t save_line = p->line, save_col = p->col; + wat_next(p); + if (tok_is(p->tok, "memory")) { + wat_next(p); + wat_parse_memory_index(p, &memidx); + wat_next(p); + wat_expect(p, WT_RPAREN, "')'"); + } else { + p->pos = save_pos; + p->line = save_line; + p->col = save_col; + p->tok.kind = WT_LPAREN; + p->tok.p = p->src + p->pos - 1u; + p->tok.len = 1; + p->tok.line = save_line; + p->tok.col = save_col - 1u; + } + } + /* The offset expression `(i32.const N)` is optional: when absent the + * segment is passive. */ + if (p->tok.kind == WT_LPAREN) { + wat_next(p); + if (!tok_is(p->tok, "i32.const") && !tok_is(p->tok, "i64.const")) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: expected const data offset"); + wat_next(p); + if (!wat_parse_i64(p, &offset) || offset < 0 || offset > UINT32_MAX) + wasm_error(p->c, wasm_loc(p->tok.line, p->tok.col), + "wasm wat: bad data offset"); + wat_next(p); + wat_expect(p, WT_RPAREN, "')'"); + has_offset = 1; + } + if (p->tok.kind == WT_STRING) { + alloc_len = p->tok.len + 1u; + bytes = wat_dup_string(p, p->tok, &nbytes); + wat_next(p); + } + d = wasm_add_data(p->c, p->module); + d->name = seg_name; + if (has_offset) { + d->mode = WASM_SEG_ACTIVE; + d->memidx = memidx; + d->offset = offset; + } else { + d->mode = WASM_SEG_PASSIVE; + } + if (nbytes) + wasm_data_set_bytes(p->c, p->module, d, (const uint8_t*)bytes, nbytes); + if (bytes) p->module->heap->free(p->module->heap, bytes, alloc_len); + wat_expect(p, WT_RPAREN, "')'"); +} + +void wasm_parse_wat_body(CfreeCompiler* c, WasmModule* m, WasmFunc* f, + const char* src, size_t len, CfreeSrcLoc loc) { + WatParser p; + memset(&p, 0, sizeof p); + p.c = c; + p.name = "<asm template>"; + p.src = src; + p.len = len; + p.line = loc.line ? loc.line : 1u; + p.col = loc.col ? loc.col : 1u; + p.module = m; + p.field_loc = loc; + wat_next(&p); + while (p.tok.kind != WT_EOF) wat_parse_instr(&p, f); +} + +void wasm_parse_wat(CfreeCompiler* c, CfreeSlice name, const CfreeSlice* input, + WasmModule* out) { + WatParser p; + memset(&p, 0, sizeof p); + if (name.s) + (void)cfree_source_add_memory(c, name, &out->file_id); + p.c = c; + p.name = name.s; + p.src = input->s; + p.len = input->len; + p.line = 1; + p.col = 1; + p.module = out; + wat_next(&p); + wat_expect(&p, WT_LPAREN, "'('"); + if (!tok_is(p.tok, "module")) + wasm_error(c, wasm_loc(p.tok.line, p.tok.col), "wasm wat: expected module"); + wat_next(&p); + while (p.tok.kind != WT_RPAREN && p.tok.kind != WT_EOF) { + if (p.tok.kind != WT_LPAREN) + wasm_error(c, wasm_loc(p.tok.line, p.tok.col), + "wasm wat: expected module field"); + wat_next(&p); + p.field_loc = wat_tok_loc(&p, p.tok); + if (tok_is(p.tok, "type")) { + wat_next(&p); + wat_parse_type_field(&p); + } else if (tok_is(p.tok, "func")) { + p.pos = (size_t)(p.tok.p - p.src); + p.line = p.tok.line; + p.col = p.tok.col; + p.tok.kind = WT_LPAREN; + p.tok.p = p.src + p.pos - 1u; + p.tok.len = 1; + p.tok.line = p.line; + p.tok.col = p.col - 1u; + wat_parse_func(&p); + } else if (tok_is(p.tok, "export")) { + wat_next(&p); + wat_parse_export_field(&p); + } else if (tok_is(p.tok, "memory")) { + wat_next(&p); + wat_parse_memory_field(&p); + } else if (tok_is(p.tok, "data")) { + wat_next(&p); + wat_parse_data_field(&p); + } else if (tok_is(p.tok, "import")) { + wat_next(&p); + wat_parse_import_field(&p); + } else if (tok_is(p.tok, "table")) { + wat_next(&p); + wat_parse_table_field(&p); + } else if (tok_is(p.tok, "global")) { + wat_next(&p); + wat_parse_global_field(&p); + } else if (tok_is(p.tok, "elem")) { + wat_next(&p); + wat_parse_elem_field(&p); + } else if (tok_is(p.tok, "start")) { + wat_next(&p); + wat_parse_start_field(&p); + } else if (tok_is(p.tok, "custom") || tok_is(p.tok, "@custom")) { + wat_next(&p); + wat_parse_custom_field(&p); + } else { + wat_skip_list(&p); + } + } + wat_expect(&p, WT_RPAREN, "')'"); + if (p.tok.kind != WT_EOF) + wasm_error(c, wasm_loc(p.tok.line, p.tok.col), + "wasm wat: trailing tokens after module"); +} diff --git a/test/link/harness/jit_runner.c b/test/link/harness/jit_runner.c @@ -27,6 +27,7 @@ #include <cfree/core.h> #include <cfree/jit.h> #include <cfree/link.h> +#include <cfree/wasm.h> #include <fcntl.h> #include <stdarg.h> #include <stdint.h> @@ -57,6 +58,14 @@ static void h_free(CfreeHeap* h, void* p, size_t n) { } static CfreeHeap g_heap = {h_alloc, h_realloc, h_free, NULL}; +/* Canned host import for wasm test fixtures (e.g. host_import_add.wat). + * Signature matches the cfree-instance ABI: explicit instance + wasm + * params. */ +int32_t test_host_add(CfreeWasmInstance* inst, int32_t a, int32_t b) { + (void)inst; + return a + b; +} + static void diag_fn(CfreeDiagSink* s, CfreeDiagKind k, CfreeSrcLoc loc, const char* fmt, va_list ap) { static const char* names[] = {"note", "warning", "error", "fatal"}; @@ -648,6 +657,30 @@ int main(int argc, char** argv) { 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(CfreeWasmInstance*, int32_t, int32_t); + static const CfreeWasmHostImport 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. */ + CfreeWasmHostImport 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 (cfree_wasm_bind_host_imports(c, jit, (CfreeWasmInstance*)instance, + runtime_imports, + sizeof(runtime_imports)/sizeof(runtime_imports[0]), + NULL, NULL) != CFREE_OK) { + free(memory); + free(instance); + cfree_jit_run_dtors(jit); + cfree_jit_free(jit); + cfree_compiler_free(c); + return 1; + } ((WasmRunnerInitFn)wasm_init)(instance); result = ((WasmRunnerMainFn)entry)(instance); free(memory); diff --git a/test/test.mk b/test/test.mk @@ -65,7 +65,9 @@ TEST_TARGETS = \ test-smoke-x64 \ test-strip-driver \ test-toy \ + test-toy-wasm \ test-wasm-front \ + test-wasm-target \ test-x64-dbg \ test-x64-inline @@ -115,6 +117,15 @@ test-cbackend: bin @CFREE_TEST_PATHS=C CFREE=$(abspath $(BIN)) sh test/toy/run.sh @CFREE_TEST_PATHS=C CFREE=$(abspath $(BIN)) bash test/wasm/run.sh +# test-toy-wasm: opt-in Toy -> Wasm -> JIT roundtrip. Runs the toy corpus +# under the W path (compile -target wasm32-none, then `cfree run` the .wasm, +# which routes back through the lang/wasm frontend to native CG). Most cases +# will fail or skip today; the target exists so progress on the Wasm CGTarget +# is visible without putting noise into the default `test` summary. +# Drop `CFREE_TEST_ALLOW_SKIP=1` once the corpus is mostly green. +test-toy-wasm: bin + @CFREE_TEST_PATHS=W CFREE_TEST_ALLOW_SKIP=1 CFREE=$(abspath $(BIN)) sh test/toy/run.sh + test-pp: bin @CFREE=$(abspath $(BIN)) test/pp/run.sh @@ -453,6 +464,15 @@ test-asm: lib $(ASM_RUNNER) $(LINK_EXE_RUNNER) $(JIT_RUNNER) test-wasm-front: bin $(WASM_TOOL) $(LINK_EXE_RUNNER) $(JIT_RUNNER) bash test/wasm/run.sh +# test-wasm-target: structural checks on `cfree cc -target wasm32-none` +# output. Each check_*.sh under test/wasm-target compiles a tiny C or +# toy fixture and asserts a property of the produced module bytes +# (memory.copy/fill opcodes, exported "memory", (import ...) decls). +# Opt-in: not in the default `test` target because the checks depend +# on the bulk-memory + (import ...) backend work landing first. +test-wasm-target: bin + @CFREE=$(abspath $(BIN)) bash test/wasm-target/run.sh + # test-smoke-x64: phase-1 sanity check for the multi-arch bring-up. Builds a # tiny freestanding x86_64 ELF with clang --target=x86_64-linux-gnu and # runs it through test/lib/exec_target.sh's podman/qemu pipeline, diff --git a/test/toy/cases/129_record_by_value.expected b/test/toy/cases/129_record_by_value.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/129_record_by_value.toy b/test/toy/cases/129_record_by_value.toy @@ -0,0 +1,15 @@ +record Pair { + a: i64, + b: i64, +} + +fn sum_pair(p: Pair): i64 { + return p.a + p.b; +} + +fn __user_main(): i64 { + let p: Pair = Pair { a: 20, b: 22 }; + return sum_pair(p); +} + +fn main(): i32 { return __user_main() as i32; } diff --git a/test/toy/cases/130_record_sret_return.expected b/test/toy/cases/130_record_sret_return.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/130_record_sret_return.toy b/test/toy/cases/130_record_sret_return.toy @@ -0,0 +1,17 @@ +record Pair { + a: i64, + b: i64, +} + +fn make(x: i64, y: i64): Pair { + let p: Pair = Pair { a: x, b: y }; + return p; +} + +fn __user_main(): i64 { + var p: Pair = Pair { a: 0, b: 0 }; + p = make(40, 2); + return p.a + p.b; +} + +fn main(): i32 { return __user_main() as i32; } diff --git a/test/toy/cases/131_memcpy_uses_bulk.expected b/test/toy/cases/131_memcpy_uses_bulk.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/131_memcpy_uses_bulk.toy b/test/toy/cases/131_memcpy_uses_bulk.toy @@ -0,0 +1,16 @@ +fn __user_main(): i64 { + let src: *i64 = @alloca<i64>(2, 8); + let dst: *i64 = @alloca<i64>(2, 8); + + src[0] = 40; + src[1] = 2; + @memcpy(dst, src, 16, 8); + + let zbuf: *i64 = @alloca<i64>(2, 8); + @memset(zbuf, 0, 16, 8); + zbuf[0] = dst[0] + dst[1]; + + return zbuf[0]; +} + +fn main(): i32 { return __user_main() as i32; } diff --git a/test/toy/cases/132_intrinsic_bit_and_overflow.expected b/test/toy/cases/132_intrinsic_bit_and_overflow.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/132_intrinsic_bit_and_overflow.toy b/test/toy/cases/132_intrinsic_bit_and_overflow.toy @@ -0,0 +1,26 @@ +fn __user_main(): i64 { + let clz_one: i32 = @clz(1 as i32); + let ctz_eight: i32 = @ctz(8 as i32); + let pop_seven: i32 = @popcount(7 as i32); + let bs32: i32 = @bswap(287454020 as i32); + let bs32_round: i32 = @bswap(bs32); + let hint: i32 = @expect(7 as i32, 7 as i32); + + let smul = @mul_overflow<i32>(2 as i32, 3 as i32); + let smul_big = @mul_overflow<i32>(1000000 as i32, 1000000 as i32); + let sadd = @add_overflow<i32>(40 as i32, 2 as i32); + let ssub = @sub_overflow<i32>(10 as i32, 7 as i32); + + if clz_one != (31 as i32) { return 1; } + if ctz_eight != (3 as i32) { return 2; } + if pop_seven != (3 as i32) { return 3; } + if bs32_round != (287454020 as i32) { return 4; } + if hint != (7 as i32) { return 5; } + if (smul.value != (6 as i32)) or smul.overflow { return 6; } + if !smul_big.overflow { return 7; } + if (sadd.value != (42 as i32)) or sadd.overflow { return 8; } + if (ssub.value != (3 as i32)) or ssub.overflow { return 9; } + return 42; +} + +fn main(): i32 { return __user_main() as i32; } diff --git a/test/toy/cases/133_varargs_mixed_types.expected b/test/toy/cases/133_varargs_mixed_types.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/133_varargs_mixed_types.toy b/test/toy/cases/133_varargs_mixed_types.toy @@ -0,0 +1,50 @@ +fn sum3(n: i64, ...): i64 { + var ap: va_list; + @va_start(ap); + let a: i64 = @va_arg<i64>(ap); + let b: i64 = @va_arg<i64>(ap); + let c: i32 = @va_arg<i32>(ap); + @va_end(ap); + return a + b + (c as i64) + n; +} + +fn pick_third(n: i64, ...): i64 { + var ap: va_list; + @va_start(ap); + let _a: i64 = @va_arg<i64>(ap); + let _b: i64 = @va_arg<i64>(ap); + let third: i64 = @va_arg<i64>(ap); + @va_end(ap); + return third; +} + +fn copy_then_read(n: i64, ...): i64 { + var ap: va_list; + var apcopy: va_list; + @va_start(ap); + let first: i64 = @va_arg<i64>(ap); + @va_copy(apcopy, ap); + let from_orig: i64 = @va_arg<i64>(ap); + let from_copy: i64 = @va_arg<i64>(apcopy); + @va_end(ap); + @va_end(apcopy); + return first + from_orig + from_copy; +} + +fn no_varargs(n: i64, ...): i64 { + return n * (2 as i64); +} + +fn __user_main(): i64 { + let s: i64 = sum3(10, 1, 2, 3 as i32); + if s != (16 as i64) { return 1; } + let t: i64 = pick_third(0, 100, 200, 300); + if t != (300 as i64) { return 2; } + let c: i64 = copy_then_read(0, 7, 11, 13); + if c != (7 + 11 + 11) { return 3; } + let z: i64 = no_varargs(21); + if z != (42 as i64) { return 4; } + return 42; +} + +fn main(): i32 { return __user_main() as i32; } diff --git a/test/toy/cases/26_tail_live_pressure.wasm.skip b/test/toy/cases/26_tail_live_pressure.wasm.skip @@ -0,0 +1 @@ +wasm target lowers `tail` to return_call (TAIL_MUST); aarch64 native lowering of the resulting 8-i64-arg tail call hits "aarch64 tail call: stack argument area too small" — out-of-scope native limitation diff --git a/test/toy/cases/35_musttail_variadic.wasm.skip b/test/toy/cases/35_musttail_variadic.wasm.skip @@ -0,0 +1 @@ +musttail to a variadic function is permanently unrealizable on wasm: varargs are packed into a caller-frame buffer that a return_call sibling call tears down, so the forwarded buffer pointer would dangle. CG diagnoses it rather than emitting a tail call. diff --git a/test/toy/cases/38_tail_stack_fallback.wasm.skip b/test/toy/cases/38_tail_stack_fallback.wasm.skip @@ -0,0 +1,9 @@ +This case targets the native ALLOWED-tail fallback (a callee whose stack args +exceed the caller's parameter area; the native RL path falls back to an +ordinary call). On the W path it is not realizable as a round-trip: wasm has no +argument-register limit, so the tail IS realizable on wasm and stage 1 emits a +return_call. The `cfree run` stage then re-lowers that wasm module to native via +lang/wasm, which lowers return_call as a MANDATORY native tail (to preserve +wasm's guaranteed-tail semantics) — and the aa64 host cannot realize this 10-arg +tail, so it diagnoses. The native ABI limitation is real; the RL path covers the +intended fallback behavior. diff --git a/test/toy/cases/47_target_arch_switch.toy b/test/toy/cases/47_target_arch_switch.toy @@ -9,6 +9,9 @@ fn __user_main(): i64 { .rv64 { 40 } + .wasm { + 40 + } default { 0 } diff --git a/test/toy/cases/60_atomic_queries.toy b/test/toy/cases/60_atomic_queries.toy @@ -1,5 +1,5 @@ fn __user_main(): i64 { - if @atomic_is_legal<i64>(.seq_cst) and @atomic_is_lock_free<i64>() { + if @atomic_is_legal<i32>(.seq_cst) and @atomic_is_lock_free<i32>() { return 42; } return 0; diff --git a/test/toy/run.sh b/test/toy/run.sh @@ -11,6 +11,12 @@ # the C target report as SKIP. Host cc runs under # -Wall -Wextra -Werror; fixtures wrap their i64 main in a small i32 # thunk so the emitted `int32_t main(void)` satisfies the standard. +# W cfree cc -target wasm32-none -c case.toy -> .wasm; then cfree run on +# the .wasm (routes through the lang/wasm frontend back to native CG, +# JITs, and invokes main). Exercises the Wasm backend (Toy -> wasm) and +# verifies the produced module still computes the expected result. +# Disabled by default because most cases hit unimplemented Wasm +# lowerings (aggregates, address-of, atomics, intrinsics, ...). # # Sidecars: # <name>.expected expected process exit code, default 0 @@ -18,13 +24,14 @@ # after the linked-object compile path # <name>.cbackend.skip opts the case out of path C (with reason), # without affecting other paths +# <name>.wasm.skip opts the case out of path W (with reason) # err/<name>.expected expected diagnostic substring for compile-fail cases # # Filtering: # ./run.sh [name_filter] [paths] -# CFREE_TEST_FILTER / CFREE_TEST_PATHS, where paths is a subset of "RLXC". +# CFREE_TEST_FILTER / CFREE_TEST_PATHS, where paths is a subset of "RLXCW". # X is opt-in cross-arch cc+ld+exec for aa64, x64, and rv64. -# C is opt-in C-source emit; default paths are "RL". +# C is opt-in C-source emit; W is opt-in Wasm roundtrip; default paths are "RL". # CFREE_TOY_OPT_LEVELS selects optimization levels, default "0 1 2". set -u @@ -40,6 +47,7 @@ case "$PATHS" in *R*) RUN_R=1;; *) RUN_R=0;; esac case "$PATHS" in *L*) RUN_L=1;; *) RUN_L=0;; esac case "$PATHS" in *X*) RUN_X=1;; *) RUN_X=0;; esac case "$PATHS" in *C*) RUN_C=1;; *) RUN_C=0;; esac +case "$PATHS" in *W*) RUN_W=1;; *) RUN_W=0;; esac TOY_CROSS_ARCHS="${CFREE_TOY_CROSS_ARCHS:-aa64 x64 rv64}" TOY_OPT_LEVELS="${CFREE_TOY_OPT_LEVELS:-0 1 2}" HOST_CC="${CC:-cc}" @@ -304,6 +312,50 @@ run_case_cross() { done } +run_case_wasm() { + local name="$1" src="$2" expected="$3" work="$4" opt="$5" + local label="$name/W-O$opt" + local wasm_skip="${src%.toy}.wasm.skip" + if [ -e "$wasm_skip" ]; then + note_skip "$label" "$(head -n1 "$wasm_skip")" + return + fi + local wasm="$work/$name.wasm" + local cc_err="$work/wasm.cc.err" cc_out="$work/wasm.cc.out" + local run_err="$work/wasm.run.err" run_out="$work/wasm.run.out" + local rc missing + if ! "$CFREE" cc "-O$opt" -target wasm32-none -c "$src" -o "$wasm" \ + > "$cc_out" 2> "$cc_err"; then + # Phased-rollout panic from the Wasm target -> SKIP, not FAIL. Matches + # the C target's pattern so the suite signal stays "real regressions". + # The Wasm target emits "wasm: <feature> not yet implemented" plus a + # handful of "unsupported X" / "too many X (max N supported in v1)" + # / "max N supported in v1" variants — all of which mean the same + # thing operationally: this case needs more wasm CGTarget work. + missing=$(grep -oE 'wasm(32 ABI| target)?: .*(not yet implemented|not (yet )?supported|unsupported [a-z_0-9]+|max [0-9]+ supported|supported in v1)' \ + "$cc_err" 2>/dev/null | head -n1 || true) + if [ -n "$missing" ]; then + note_skip "$label" "$missing" + return + fi + note_fail "$label" + printf ' cfree cc -target wasm32-none failed\n' + sed 's/^/ | /' "$cc_err" + return + fi + if [ -s "$cc_err" ]; then + note_fail "$label" + printf ' cfree cc -target wasm32-none wrote stderr\n' + sed 's/^/ | /' "$cc_err" + return + fi + # cfree run on the .wasm routes through the lang/wasm frontend, lowers + # to native CG, links via the JIT, and invokes main. + "$CFREE" run "$wasm" > "$run_out" 2> "$run_err" + rc=$? + check_rc "$label" "$rc" "$expected" "$run_err" +} + run_case_emit_c() { local name="$1" src="$2" expected="$3" work="$4" opt="$5" local label="$name/C-O$opt" @@ -404,6 +456,12 @@ for src in "${cases[@]}"; do if [ $RUN_C -eq 1 ] && [ "$opt" = "0" ]; then run_case_emit_c "$name" "$src" "$expected" "$work" "$opt" fi + # Path W (wasm roundtrip) only exercises -O0 today; the Wasm CGTarget + # ignores -O for opt-level purposes (the lang/wasm frontend chooses + # its own native opt level when re-lowering the .wasm). + if [ $RUN_W -eq 1 ] && [ "$opt" = "0" ]; then + run_case_wasm "$name" "$src" "$expected" "$work" "$opt" + fi done done diff --git a/test/wasm-target/asm_branch_escape.c b/test/wasm-target/asm_branch_escape.c @@ -0,0 +1,13 @@ +/* Negative fixture: the snippet's `br 0` at top level (depth 0) would + * branch out of the synthetic frame. The wasm backend must reject this + * before emission. Paired with check_asm.sh's escape-branch assertion. */ +int f(int x) { + int r; + __asm__ volatile( + "local.get 0\n" + "br 0\n" + "local.set 1\n" + : "=r"(r) + : "r"(x)); + return r; +} diff --git a/test/wasm-target/asm_popcnt.c b/test/wasm-target/asm_popcnt.c @@ -0,0 +1,13 @@ +/* Wasm-target structural check: inline asm with a WAT template. + * The snippet pops the input local (index 0), runs i32.popcnt, and + * stores the result into the output local (index 1). */ +int popcnt(int x) { + int r; + __asm__ volatile( + "local.get 0\n" + "i32.popcnt\n" + "local.set 1\n" + : "=r"(r) + : "r"(x)); + return r; +} diff --git a/test/wasm-target/check_asm.sh b/test/wasm-target/check_asm.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# Structural test: confirm the wasm backend can lower an inline-asm block +# whose template is a WAT instruction sequence. The fixture's snippet uses +# i32.popcnt, so the produced module must contain the popcnt opcode byte +# (0x69, the wasm-binary encoding of i32.popcnt). +# +# Exit status: +# 0 expectations hold. +# 1 fixture failed to compile, or popcnt opcode is missing. +# 2 toolchain unavailable. +set -u + +ROOT=$(cd "$(dirname "$0")/.." && pwd)/.. +ROOT=$(cd "$ROOT" && pwd) +CFREE="${CFREE:-$ROOT/build/cfree}" +SRC_DIR="$ROOT/test/wasm-target" +BUILD_DIR="$ROOT/build/test/wasm-target" +mkdir -p "$BUILD_DIR" + +if [ ! -x "$CFREE" ]; then + echo "SKIP: cfree driver missing at $CFREE" >&2 + exit 2 +fi + +OUT="$BUILD_DIR/asm_popcnt.wasm" +if ! "$CFREE" cc -target wasm32-none -c "$SRC_DIR/asm_popcnt.c" -o "$OUT" \ + >"$OUT.cc.out" 2>"$OUT.cc.err"; then + echo "FAIL: cfree cc -target wasm32-none failed for asm_popcnt.c" >&2 + sed 's/^/ | /' "$OUT.cc.err" >&2 + exit 1 +fi + +# i32.popcnt encodes as 0x69. Look for the byte in the produced module. +if ! od -An -tx1 -v "$OUT" | tr -d ' \n' | grep -q '69'; then + echo "FAIL: asm_popcnt.wasm missing i32.popcnt opcode (0x69)" >&2 + exit 1 +fi + +# Negative: a snippet whose top-level `br 0` would escape the synthetic +# frame must be rejected before emission. The driver should fail with a +# "wasm target:" diagnostic. +NEG="$BUILD_DIR/asm_branch_escape.wasm" +if "$CFREE" cc -target wasm32-none -c "$SRC_DIR/asm_branch_escape.c" -o "$NEG" \ + >"$NEG.cc.out" 2>"$NEG.cc.err"; then + echo "FAIL: asm_branch_escape.c compiled but should have been rejected" >&2 + exit 1 +fi +if ! grep -F "wasm target:" "$NEG.cc.err" >/dev/null 2>&1; then + echo "FAIL: asm_branch_escape rejection lacks 'wasm target:' diagnostic" >&2 + sed 's/^/ | /' "$NEG.cc.err" >&2 + exit 1 +fi + +echo "PASS: backend lowers WAT-template inline asm and rejects escaping br" +exit 0 diff --git a/test/wasm-target/check_imports.sh b/test/wasm-target/check_imports.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# Structural test: confirm that the wasm backend emits `(import ...)` +# declarations for `extern` C symbols, honors the default `env` module, +# and respects __attribute__((import_module, import_name)) overrides. +# +# Strategy: compile each fixture with -target wasm32-none -c and grep the +# raw module bytes for the expected (module, field) UTF-8 strings. The +# wasm import section encodes these as length-prefixed UTF-8 plain text +# so a simple `grep -F` is enough. +# +# Exit status: +# 0 every expectation holds. +# 1 some fixture is missing the expected import. +# 2 toolchain unavailable. +set -u + +ROOT=$(cd "$(dirname "$0")/.." && pwd)/.. +ROOT=$(cd "$ROOT" && pwd) +CFREE="${CFREE:-$ROOT/build/cfree}" +SRC_DIR="$ROOT/test/wasm-target" +BUILD_DIR="$ROOT/build/test/wasm-target" +mkdir -p "$BUILD_DIR" + +if [ ! -x "$CFREE" ]; then + echo "SKIP: cfree driver missing at $CFREE" >&2 + exit 2 +fi + +fail=0 + +compile() { + local src=$1 + local out=$2 + if ! "$CFREE" cc -target wasm32-none -c "$src" -o "$out" \ + >"$out.cc.out" 2>"$out.cc.err"; then + echo "FAIL: cfree cc -target wasm32-none failed for $src" >&2 + sed 's/^/ | /' "$out.cc.err" >&2 + return 1 + fi +} + +check_strings() { + local label=$1 + local wasm=$2 + shift 2 + local s + for s in "$@"; do + if ! grep -F "$s" "$wasm" >/dev/null 2>&1; then + echo "FAIL: $label: expected substring '$s' in $wasm" >&2 + fail=1 + fi + done +} + +# Default module/field: env / host_add. +OUT="$BUILD_DIR/import_decl.wasm" +if compile "$SRC_DIR/import_decl.c" "$OUT"; then + check_strings "import_decl" "$OUT" "env" "host_add" +else + fail=1 +fi + +# Attribute-override module/field: custom / add. +OUT="$BUILD_DIR/import_decl_attribute.wasm" +if compile "$SRC_DIR/import_decl_attribute.c" "$OUT"; then + check_strings "import_decl_attribute" "$OUT" "custom" "add" +else + fail=1 +fi + +if [ "$fail" -eq 0 ]; then + echo "PASS: backend emits expected (import ...) declarations" +fi +exit "$fail" diff --git a/test/wasm-target/check_memory_copy.sh b/test/wasm-target/check_memory_copy.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# Structural test: confirm that the wasm backend lowers `copy_bytes` / +# `set_bytes` to the bulk-memory opcodes `memory.copy` (0xfc 0x0a) and +# `memory.fill` (0xfc 0x0b) rather than to byte-loops. +# +# Invocation: +# test/wasm-target/check_memory_copy.sh +# +# Environment: +# CFREE: path to the cfree driver (default: ./build/cfree). +# TOY_FIXTURE: path to a .toy source whose lowering must use +# copy_bytes/set_bytes. Default: test/toy/cases/131_memcpy_uses_bulk.toy. +# +# Exit status: +# 0 both opcodes appear in the produced module. +# 1 either opcode is missing. +# 2 toolchain unavailable. +set -u + +ROOT=$(cd "$(dirname "$0")/.." && pwd)/.. +ROOT=$(cd "$ROOT" && pwd) +CFREE="${CFREE:-$ROOT/build/cfree}" +TOY_FIXTURE="${TOY_FIXTURE:-$ROOT/test/toy/cases/131_memcpy_uses_bulk.toy}" +BUILD_DIR="$ROOT/build/test/wasm-target" +mkdir -p "$BUILD_DIR" + +if [ ! -x "$CFREE" ]; then + echo "SKIP: cfree driver missing at $CFREE" >&2 + exit 2 +fi +if [ ! -f "$TOY_FIXTURE" ]; then + echo "SKIP: missing toy fixture $TOY_FIXTURE" >&2 + exit 2 +fi + +OUT="$BUILD_DIR/$(basename "$TOY_FIXTURE" .toy).wasm" +if ! "$CFREE" cc -target wasm32-none -c "$TOY_FIXTURE" -o "$OUT" \ + >"$BUILD_DIR/cc.out" 2>"$BUILD_DIR/cc.err"; then + echo "FAIL: cfree cc -target wasm32-none failed for $TOY_FIXTURE" >&2 + sed 's/^/ | /' "$BUILD_DIR/cc.err" >&2 + exit 1 +fi + +# Magic numbers in this test mirror the wasm spec opcode encoding: +# memory.copy = 0xfc 0x0a +# memory.fill = 0xfc 0x0b +# Keep these in sync with WASM_INSN_MEMORY_{COPY,FILL} in src/wasm/insn.c. +fail=0 +if ! xxd -p -c 256 "$OUT" | tr -d '\n' | grep -q 'fc0a'; then + echo "FAIL: memory.copy (0xfc 0x0a) not present in $OUT" >&2 + fail=1 +fi +if ! xxd -p -c 256 "$OUT" | tr -d '\n' | grep -q 'fc0b'; then + echo "FAIL: memory.fill (0xfc 0x0b) not present in $OUT" >&2 + fail=1 +fi + +if [ "$fail" -eq 0 ]; then + echo "PASS: $(basename "$OUT") contains memory.copy and memory.fill" +fi +exit "$fail" diff --git a/test/wasm-target/check_memory_export.sh b/test/wasm-target/check_memory_export.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Structural test: confirm that the wasm backend exports the linear +# memory under the conventional name `"memory"`. This is required for +# browser, wasmtime, wasmer, and Node host runtimes. +# +# Strategy: compile a small toy fixture, then grep the produced module +# bytes for the UTF-8 string "memory" appearing as an export name. +# Combined with the test/wasm-target/import_decl.* fixtures, this gives +# the minimum signal that cfree's output is loadable by standard hosts. +set -u + +ROOT=$(cd "$(dirname "$0")/.." && pwd)/.. +ROOT=$(cd "$ROOT" && pwd) +CFREE="${CFREE:-$ROOT/build/cfree}" +TOY_FIXTURE="${TOY_FIXTURE:-$ROOT/test/toy/cases/01_return_const.toy}" +BUILD_DIR="$ROOT/build/test/wasm-target" +mkdir -p "$BUILD_DIR" + +if [ ! -x "$CFREE" ]; then + echo "SKIP: cfree driver missing at $CFREE" >&2 + exit 2 +fi +if [ ! -f "$TOY_FIXTURE" ]; then + echo "SKIP: missing toy fixture $TOY_FIXTURE" >&2 + exit 2 +fi + +OUT="$BUILD_DIR/memory_export.wasm" +if ! "$CFREE" cc -target wasm32-none -c "$TOY_FIXTURE" -o "$OUT" \ + >"$BUILD_DIR/me.cc.out" 2>"$BUILD_DIR/me.cc.err"; then + echo "FAIL: cfree cc -target wasm32-none failed for $TOY_FIXTURE" >&2 + sed 's/^/ | /' "$BUILD_DIR/me.cc.err" >&2 + exit 1 +fi + +if ! grep -F "memory" "$OUT" >/dev/null 2>&1; then + echo "FAIL: produced module does not contain export name 'memory'" >&2 + exit 1 +fi +echo "PASS: produced module exports 'memory'" diff --git a/test/wasm-target/host_import_sig_mismatch.wat b/test/wasm-target/host_import_sig_mismatch.wat @@ -0,0 +1,10 @@ +(module + ;; Declares `env.host_add` with signature (i32, i32) -> i32. The companion + ;; harness binds a wrong-shape host function (one parameter instead of + ;; two). Expected: cfree's host-import binder rejects the binding before + ;; running test_main, with a diagnostic naming "env.host_add". + (import "env" "host_add" (func $host_add (param i32 i32) (result i32))) + (func (export "test_main") (result i32) + i32.const 2 + i32.const 3 + call $host_add)) diff --git a/test/wasm-target/import_decl.c b/test/wasm-target/import_decl.c @@ -0,0 +1,20 @@ +/* Backend import-emission smoke fixture. + * + * `extern int host_add(int, int)` becomes a wasm `(import ...)` declaration + * once subagent B's backend changes land. Default module/field policy is + * `env` / `<sym name>`, so the produced module must contain + * (import "env" "host_add" (func (param i32 i32) (result i32))) + * + * Round-trip: when the produced `.wasm` is loaded back into cfree's runtime + * and `host_add` is bound to a C function that returns a + b, main returns 5. + * + * Compile with: + * cfree cc -target wasm32-none -c test/wasm-target/import_decl.c \ + * -o build/test/wasm-target/import_decl.wasm + * + * Inspect with: + * xxd build/test/wasm-target/import_decl.wasm | grep -F 'host_add' + */ +extern int host_add(int, int); + +int main(void) { return host_add(2, 3); } diff --git a/test/wasm-target/import_decl_attribute.c b/test/wasm-target/import_decl_attribute.c @@ -0,0 +1,13 @@ +/* Backend import-emission attribute-override fixture. + * + * `__attribute__((import_module("custom"), import_name("add")))` overrides + * the default (env / <sym name>) module-and-field pair. After subagent B's + * backend changes land, the produced module must contain + * (import "custom" "add" (func (param i32 i32) (result i32))) + * Same calling convention as `import_decl.c`; round-trip returns 5 when + * `custom.add` is bound to a sum-of-args host function. + */ +__attribute__((import_module("custom"), import_name("add"))) +extern int host_add(int, int); + +int main(void) { return host_add(2, 3); } diff --git a/test/wasm-target/run.sh b/test/wasm-target/run.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Wasm-target structural test driver. Each check_*.sh script compiles a +# small fixture with `cfree cc -target wasm32-none -c` and asserts a +# structural property of the produced module (opcode bytes, export +# names, import declarations). +# +# Invoked by `make test-wasm-target`; runs every check_*.sh under this +# directory and reports a pass/fail/skip summary. +set -u + +ROOT=$(cd "$(dirname "$0")/.." && pwd)/.. +ROOT=$(cd "$ROOT" && pwd) +DIR="$ROOT/test/wasm-target" + +pass=0 +fail=0 +skip=0 +for script in "$DIR"/check_*.sh; do + [ -e "$script" ] || continue + name=$(basename "$script" .sh) + "$script" + rc=$? + case "$rc" in + 0) pass=$((pass + 1));; + 2) skip=$((skip + 1));; + *) fail=$((fail + 1));; + esac +done +printf 'test-wasm-target: pass=%d fail=%d skip=%d\n' "$pass" "$fail" "$skip" +[ "$fail" -eq 0 ] diff --git a/test/wasm/cases/bulk_data_drop.expect b/test/wasm/cases/bulk_data_drop.expect @@ -0,0 +1 @@ +42 diff --git a/test/wasm/cases/bulk_data_drop.wat b/test/wasm/cases/bulk_data_drop.wat @@ -0,0 +1,18 @@ +(module + (memory 1) + (data $seg "\2a\00\00\00") + (func (export "test_main") (result i32) + ;; First init succeeds: copies the 4 bytes to offset 64. + i32.const 64 + i32.const 0 + i32.const 4 + memory.init $seg + ;; Drop the segment; subsequent memory.init $seg of nonzero length traps. + data.drop $seg + ;; Second init with len=0 must succeed (dropped segment, but len 0 is ok). + i32.const 0 + i32.const 0 + i32.const 0 + memory.init $seg + i32.const 64 + i32.load)) diff --git a/test/wasm/cases/bulk_memory_copy.expect b/test/wasm/cases/bulk_memory_copy.expect @@ -0,0 +1 @@ +42 diff --git a/test/wasm/cases/bulk_memory_copy.wat b/test/wasm/cases/bulk_memory_copy.wat @@ -0,0 +1,12 @@ +(module + (memory 1) + (data (i32.const 0) "\2a\00\00\00") + (func (export "test_main") (result i32) + ;; memory.copy: dst=16, src=0, len=4 + i32.const 16 + i32.const 0 + i32.const 4 + memory.copy + ;; load i32 from offset 16 + i32.const 16 + i32.load)) diff --git a/test/wasm/cases/bulk_memory_fill.expect b/test/wasm/cases/bulk_memory_fill.expect @@ -0,0 +1 @@ +42 diff --git a/test/wasm/cases/bulk_memory_fill.wat b/test/wasm/cases/bulk_memory_fill.wat @@ -0,0 +1,11 @@ +(module + (memory 1) + (func (export "test_main") (result i32) + ;; memory.fill: dst=8, value=0x2a, len=4 + i32.const 8 + i32.const 0x2a + i32.const 4 + memory.fill + ;; load the first filled byte (zero-extended to i32) + i32.const 8 + i32.load8_u)) diff --git a/test/wasm/cases/bulk_memory_init.expect b/test/wasm/cases/bulk_memory_init.expect @@ -0,0 +1 @@ +42 diff --git a/test/wasm/cases/bulk_memory_init.wat b/test/wasm/cases/bulk_memory_init.wat @@ -0,0 +1,12 @@ +(module + (memory 1) + ;; Passive data segment carrying the bytes for 42 (little-endian). + (data $seg "\2a\00\00\00") + (func (export "test_main") (result i32) + ;; memory.init seg=$seg dst=32 src=0 len=4 + i32.const 32 + i32.const 0 + i32.const 4 + memory.init $seg + i32.const 32 + i32.load)) diff --git a/test/wasm/cases/bulk_table_copy.expect b/test/wasm/cases/bulk_table_copy.expect @@ -0,0 +1 @@ +42 diff --git a/test/wasm/cases/bulk_table_copy.wat b/test/wasm/cases/bulk_table_copy.wat @@ -0,0 +1,16 @@ +(module + (type $ret_i32 (func (result i32))) + (func $target (type $ret_i32) + i32.const 42) + (table 4 funcref) + ;; Active elem at index 0 puts $target into table[0]. + (elem (i32.const 0) func $target) + (func (export "test_main") (result i32) + ;; table.copy: dst=3 src=0 len=1 + i32.const 3 + i32.const 0 + i32.const 1 + table.copy + ;; Indirect call through table[3] (copied from table[0]). + i32.const 3 + call_indirect (type $ret_i32))) diff --git a/test/wasm/cases/bulk_table_fill.expect b/test/wasm/cases/bulk_table_fill.expect @@ -0,0 +1 @@ +42 diff --git a/test/wasm/cases/bulk_table_fill.wat b/test/wasm/cases/bulk_table_fill.wat @@ -0,0 +1,14 @@ +(module + (type $ret_i32 (func (result i32))) + (func $target (type $ret_i32) + i32.const 42) + (table 4 16 funcref) + (func (export "test_main") (result i32) + ;; table.fill: dst=1 ref=funcref(target) len=2 + i32.const 1 + ref.func $target + i32.const 2 + table.fill + ;; Indirect call through table[2] (filled with $target). + i32.const 2 + call_indirect (type $ret_i32))) diff --git a/test/wasm/cases/bulk_table_grow.expect b/test/wasm/cases/bulk_table_grow.expect @@ -0,0 +1 @@ +42 diff --git a/test/wasm/cases/bulk_table_grow.wat b/test/wasm/cases/bulk_table_grow.wat @@ -0,0 +1,17 @@ +(module + (type $ret_i32 (func (result i32))) + (func $target (type $ret_i32) + i32.const 1) + (table 2 16 funcref) + (func (export "test_main") (result i32) + ;; Grow the table by 3 entries, initializing them with funcref(target). + ;; table.grow returns the old size (2). + ref.func $target + i32.const 3 + table.grow + drop + ;; New size should be 5; we encode the verification value (5 + 37 = 42) + ;; to match the convention of returning 42 on success. + table.size + i32.const 37 + i32.add)) diff --git a/test/wasm/cases/bulk_table_init.expect b/test/wasm/cases/bulk_table_init.expect @@ -0,0 +1 @@ +42 diff --git a/test/wasm/cases/bulk_table_init.wat b/test/wasm/cases/bulk_table_init.wat @@ -0,0 +1,16 @@ +(module + (type $ret_i32 (func (result i32))) + (func $target (type $ret_i32) + i32.const 42) + (table 4 funcref) + ;; Passive element segment carrying one funcref. + (elem $eseg func $target) + (func (export "test_main") (result i32) + ;; table.init eseg: dst=2 src=0 len=1 + i32.const 2 + i32.const 0 + i32.const 1 + table.init $eseg + ;; Indirect call through table[2]. + i32.const 2 + call_indirect (type $ret_i32))) diff --git a/test/wasm/cases/host_import_add.expect b/test/wasm/cases/host_import_add.expect @@ -0,0 +1 @@ +5 diff --git a/test/wasm/cases/host_import_add.wat b/test/wasm/cases/host_import_add.wat @@ -0,0 +1,9 @@ +(module + ;; Host-provided import. Expected to be bound by the test harness to a + ;; C function with the cfree-instance ABI: + ;; int32_t host_add(CfreeWasmInstance*, int32_t, int32_t) + (import "env" "host_add" (func $host_add (param i32 i32) (result i32))) + (func (export "test_main") (result i32) + i32.const 2 + i32.const 3 + call $host_add)) diff --git a/test/wasm/cases/trunc_sat.expect b/test/wasm/cases/trunc_sat.expect @@ -0,0 +1 @@ +42 diff --git a/test/wasm/cases/trunc_sat.wat b/test/wasm/cases/trunc_sat.wat @@ -0,0 +1,21 @@ +(module + (func (export "test_main") (result i32) + ;; Sum of saturated conversions: + ;; i32.trunc_sat_f32_s(NaN) = 0 + ;; i32.trunc_sat_f32_s(+inf) = 2147483647 (INT32_MAX) + ;; i32.trunc_sat_f32_s(-inf) = -2147483648 (INT32_MIN) + ;; Adding INT32_MAX + INT32_MIN wraps to -1; +0 leaves -1. + ;; Finally i32.trunc_sat_f32_u(42.5) = 42; -1 + 42 + 1 = 42. + f32.const nan + i32.trunc_sat_f32_s + f32.const inf + i32.trunc_sat_f32_s + i32.add + f32.const -inf + i32.trunc_sat_f32_s + i32.add + f32.const 42.5 + i32.trunc_sat_f32_u + i32.add + i32.const 1 + i32.add)) diff --git a/test/wasm/harness/start_wasm.c b/test/wasm/harness/start_wasm.c @@ -3,11 +3,54 @@ * Wasm frontend exports use the internal instance ABI: * __cfree_wasm_init(instance) * test_main(instance) + * + * Host-import binding (cfree_wasm_bind_host_imports) is intentionally not + * called here: this harness is freestanding and has no link to libcfree. + * 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 `cfree run`, both of which + * resolve imports before calling __cfree_wasm_init. */ extern void __cfree_wasm_init(void *); extern int test_main(void *); +/* Imports metadata emitted by lang/wasm/cg.c when the module declares any + * imports. Made weak so this harness still links for modules without them. */ +typedef struct WasmStartImportDesc { + const char *module; + const char *field; + unsigned int typeidx; + unsigned int slot_offset; +} WasmStartImportDesc; +extern const WasmStartImportDesc __cfree_wasm_imports[] __attribute__((weak)); +extern const unsigned int __cfree_wasm_nimports __attribute__((weak)); + +/* Canned test host function (also referenced by jit_runner.c, driver/run.c). + * The freestanding harness inlines its own copy because it has no libc. */ +static int start_test_host_add(void *inst, int a, int b) { + (void)inst; + return a + b; +} + +static int start_streq(const char *a, const char *b) { + while (*a && *a == *b) { a++; b++; } + return *a == 0 && *b == 0; +} + +static void start_bind_canned_imports(void *instance) { + if (!(&__cfree_wasm_nimports)) return; + unsigned int n = __cfree_wasm_nimports; + for (unsigned int i = 0; i < n; ++i) { + const WasmStartImportDesc *d = &__cfree_wasm_imports[i]; + if (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; + } + } +} + typedef struct WasmStartMemoryPrefix { unsigned char *data; unsigned long long pages; @@ -129,6 +172,7 @@ void _start(void) { ((WasmStartMemoryPrefix *)instance)[i].data = (unsigned char *)memory + i * (WASM_START_MEMORY_SIZE / WASM_START_MEMORY_PREFIX_COUNT); + start_bind_canned_imports(instance); __cfree_wasm_init(instance); do_exit(test_main(instance)); } diff --git a/test/wasm/run.sh b/test/wasm/run.sh @@ -78,6 +78,9 @@ case "$TEST_OBJ" in esac EXEC_TAG="$EXEC_ARCH-$EXEC_OS" export CFREE_TEST_ARCH="$TEST_ARCH" CFREE_TEST_OBJ="$TEST_OBJ" +# Enable the runner's canned host-import resolver so fixtures like +# host_import_add can use `(import "env" "host_add" ...)`. +export CFREE_TEST_HOST_IMPORTS=1 have_wasm_tool=0 if [ -x "$WASM_TOOL" ]; then @@ -207,6 +210,20 @@ run_expect_fail() { fi } +run_expect_fail_stderr_contains() { + local label=$1 + local expected=$2 + local err="$BUILD_DIR/${label//\//_}.err" + shift 2 + if "$@" >"$BUILD_DIR/${label//\//_}.out" 2>"$err"; then + note_fail "$label expected failure" + elif grep -F "$expected" "$err" >/dev/null 2>&1; then + note_pass "$label" + else + note_fail "$label missing diagnostic: $expected" + fi +} + run_expect_fail_quiet() { local label=$1 local out="$BUILD_DIR/${label//\//_}.out" @@ -367,6 +384,13 @@ for wat in "$ERR_DIR"/*.wat; do "$wat" -o "$BUILD_DIR/err-$name.o" done +if [ "$RUN_DIAG" -eq 1 ]; then + run_expect_fail_stderr_contains "err/stack_underflow/loc" \ + "$ERR_DIR/stack_underflow.wat:3:5: fatal: wasm: operand stack underflow" \ + "$CFREE_BIN" cc -target "$target_triple" -c \ + "$ERR_DIR/stack_underflow.wat" -o "$BUILD_DIR/err-stack_underflow-loc.o" +fi + if [ "$RUN_DIAG" -eq 1 ] && [ "$have_wasm_tool" -eq 1 ]; then for wat in "$META_DIR"/*.wat; do [ -e "$wat" ] || continue diff --git a/test/wasm/trap/bulk_memory_oob.wat b/test/wasm/trap/bulk_memory_oob.wat @@ -0,0 +1,9 @@ +(module + (memory 1) + (func (export "test_main") (result i32) + ;; memory.copy with dst past the end of memory traps. + i32.const 65530 + i32.const 0 + i32.const 32 + memory.copy + i32.const 0)) diff --git a/test/wasm/trap/data_drop_then_init.wat b/test/wasm/trap/data_drop_then_init.wat @@ -0,0 +1,11 @@ +(module + (memory 1) + (data $seg "\01\02\03\04") + (func (export "test_main") (result i32) + ;; Drop the passive segment, then memory.init of nonzero length must trap. + data.drop $seg + i32.const 0 + i32.const 0 + i32.const 4 + memory.init $seg + i32.const 0)) diff --git a/test/wasm/trap/host_import_unbound.wat b/test/wasm/trap/host_import_unbound.wat @@ -0,0 +1,7 @@ +(module + ;; Declares an import the test harness does not bind. Calling it must + ;; trap cleanly (not segfault) at invocation time. + (import "env" "host_unbound" (func $host_unbound (param i32) (result i32))) + (func (export "test_main") (result i32) + i32.const 0 + call $host_unbound)) diff --git a/test/wasm/trap/table_oob.wat b/test/wasm/trap/table_oob.wat @@ -0,0 +1,12 @@ +(module + (type $ret_i32 (func (result i32))) + (func $target (type $ret_i32) + i32.const 1) + (table 2 funcref) + (func (export "test_main") (result i32) + ;; table.fill past the end traps. + i32.const 1 + ref.func $target + i32.const 5 + table.fill + i32.const 0))