Expand description
WebAssembly code generation from VM bytecode: the shared byte emitter (wasm::encode +
wasm::func, always built) plus the WS6 browser WASM-JIT tier (wasm::region_jit, behind
the wasm-jit feature). The direct AOT backend (largo build --emit wasm) consumes the
same emitter.
WebAssembly code generation from VM bytecode — one shared codegen, two consumers.
WASM has only structured control flow, so the register-machine bytecode (&[Op], with
absolute jumps) is lowered through a basic-block dispatch loop (a loop of blocks with a
br_table on a “next block” local). That lowering (cfg) and the hand-rolled binary
encoder (encode) are the hard, validated core; both of the following build on it:
region_jit— the WS6 browser JIT tier: emits one WebAssembly module per hot function and runs it onwasmi(native) or the host’s realWebAssembly(wasm32). Gated behind thewasm-jitfeature.- the direct AOT backend (
module) —assemble_program(and its source-level wrappercrate::compile::compile_to_wasm): emits one self-contained.wasmfor a whole program with NO rustc/cargo/wasm-bindgen/linker. Feature-independent (needs onlyencode/cfg/kind, notwasmi/js_sys); exposed today as a library API.
§The AOT backend
§Value model (kind)
The bytecode’s arithmetic and Show are runtime-polymorphic (one Op::Add adds Ints or
Floats), so a standalone module must know each register’s static kind. A dataflow fixpoint
infers a kind::Kind per register, each mapping to exactly one wasm value type:
- Scalars, by value:
Int/Bool/Moment→i64,Float→f64,Date→i32. - Heap values, an
i32handle into linear memory:Text,Struct,Map,Set,Enum,Closure,Tuple(heterogeneous), and the sequencesSeqInt/SeqFloat/SeqText/SeqStruct/SeqEnum/SeqSeqInt(plusSeqAny, a not-yet-refined empty sequence).
Int and Bool share i64, so a register reused across them coalesces. A register reused
across kinds of different value types in disjoint live ranges (e.g. an Enum loop variable
whose slot a later Int reuses) is Unsupported — a sound refusal (never a miscompile),
pending register-live-range splitting.
§Heap value model (self-contained, no linker)
When a program uses a heap op the module gets a linear memory + a bump-allocator pointer global
__heap_ptr (scalar-only modules stay memory-free). Every heap value is a stable i32
handle so a realloc-on-grow never invalidates the register holding it:
- A sequence or Set is a 16-byte header
[len:i32][cap:i32][data_ptr:i32]over a separate buffer of 8-byte slots — a scalar element by value, a handle element (Text / Struct / Enum / inner sequence) in the slot’s low word. Indexing is 1-based and bounds-checked (trap on OOB);Push/Addreallocate the buffer and rewrite the header in place. ASeq of Structflows the element’s field layout to an extracted element so(item N of xs)'s fieldresolves. - A Map is the same header over 16-byte
[key][value]entries — Int or Text keys, Int/Float/Bool values — looked up by linear scan, order-independent so byte-identical to the VM’s hashmap for get/insert/contains/length. - A Struct is a header over 8-byte field slots (field names are compile-time only; a
struct_layoutanalysis maps each field to its slot + kind). A Tuple is the same, by position. An Enum is[tag:i32][arg slots…](the tag is the constructor’s constant index, so ani32.eqon tags is the VM’s constructor-name compare; payload args follow). A Closure is[func_idx:i32][captured values…], invoked bycall_indirectthrough the module’s function table; captures (locals and snapshotted globals) pass as trailing parameters.
Show of a scalar, a Text, a sequence of scalars/Text, or a Set (all insertion-ordered, so
deterministic) is byte-identical to the VM; whole-Map/whole-Struct display (hashmap/field
order is non-reproducible against the AOT’s insertion order) is the deferred remainder.
§Host interface
Raw env.* imports (no wasm-bindgen — runs under wasmi/wasmtime/a browser shim): the
print_* sinks (the host formats to match the tree-walker’s to_display_string, reading
sequences / Sets / Text out of the exported memory), pow_ff/pow_fi (exact powf/powi),
today/now (honoring the test fixed clock), and fmt_*_into (interpolating scalar operands
into a module buffer). A runtime error — out-of-bounds index, undefined variable, an overflow
the oracle could not rule out — lowers to a wasm unreachable trap (the standalone module has
no VM to surface the message), a contract the lock proves as tw-error ⟺ wasm-trap.
§The lock (tests/wasm_aot_lock.rs)
WASM == VM == Tree-walker, mirroring the Futamura locks. An EXHAUSTIVE op_support(&Op) match
(compiler-enforced: a new VM op fails to build until classified Supported/Deferred) plus a
behavioural biconditional (a program compiles IFF every op is Supported, then runs
byte-identically) plus a full-language coverage proof (every Supported instruction is exercised
end-to-end over the curated corpus). Goal: the Deferred set shrinks to ∅.
Modules§
- encode
- WebAssembly binary-encoding primitives — the shared byte layer beneath both the browser
WASM-JIT tier (
super::region_jit) and the direct AOT backend ([super::module]). - func
- Per-function WebAssembly lowering: a region of VM bytecode (
&[Op], a register machine) → a WebAssembly function (a stack machine with i64 locals). - region_
jit - The WS6 browser WASM-JIT tier (region/function emit + host instantiation). Only the
running of emitted modules touches
wasmi/js_sys, so this — and only this — is gated behind thewasm-jitfeature;encode/func(pure byte emission) are always built. WS6 (FINISH_INTERPRETER Phase 13) — the browser WASM-JIT tier.
Enums§
- Wasm
Lower Error - Why the AOT WebAssembly backend declined to lower a program. The backend is total on its supported fragment and rejects everything else explicitly (never miscompiles): the corpus ratchet asserts each program is either lowered-and-correct or on the allowed-unsupported list, so a program silently leaving the fragment fails a test rather than emitting wrong code.
Functions§
- assemble_
program - Lower a whole program to a self-contained WebAssembly module (the bytes of a
.wasmfile). ReturnsWasmLowerError::Unsupportedfor any program outside the scalar fragment — a sound refusal, never a wrong module.