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:
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))