Execution Lanes
One module, four lanes — browser engine, fast interpreter, or ahead-of-time machine code. A platform decision, never a rewrite.
rasc emits engine-neutral WebAssembly. What executes it depends on where the content lands:
| Lane | Engine | Role |
|---|---|---|
| Web | Browser wasm engine (V8, JavaScriptCore, …) | Scripts run directly as browser wasm — no WAMR on web. The web editor previews on the same engine the web runtime ships on. |
| Interp | WAMR fast interpreter | The dev loop and the downloaded-content fallback (e.g. iOS downloaded content). Instant instantiation on every edit. |
| AOT | wamrc ahead-of-time compile | The ship tier for bundled native content. Native-parity machine code, artifacts signed into the app binary. |
| AOT, hardware bounds | wamrc with guard pages | Opt-in (.hw.aot) where the platform allows it — roughly 20–30%
faster than software bounds checks. iOS needs the extended-virtual-addressing
entitlement. |
Measured picture
From the benchmark suite — real workloads, each pair measured in the same session; numbers refresh as the suite evolves:
- rasc + AOT is the fastest lane in every cell it occupies — about 8.7× native Luau and about 12× the shipping Luau web lane on the Draco decode workload.
- Draco decode, 40k-vertex sphere: native Luau 149.4 ms · shipping Luau web lane 206.8 ms · rasc interp 41.5 ms · rasc AOT 3.79 ms (3.04 ms with hardware bounds). The module is 23.5 KB gzipped versus 286 KB plus JS glue for the reference decoder — and beats it on every mesh on V8.
- Box2D pyramid, 240 boxes: native Luau 108.5 ms · rasc interp 84.9 ms — the interpreter beats the native Luau VM — and rasc SIMD AOT lands within 1.38× of full native C running its own SIMD.
- Fields, a full app built on Rive scripting: boot 36 ms on rasc + AOT versus 111 ms on native Luau; mesh parse 6 ms versus 42 ms — the wasm lane beats the trusted native VM 7×.
- The interp tier is honest about its ceiling: WAMR fast-interp trails V8 by ~8.5× on compute, a gap an interpreter cannot close. But across a corpus of real Rive files it holds 1.02× native median — typical content is fine on interp; AOT exists for compute-heavy content.
- Luau on the same substrate reaches native parity:
Luau-compiled wasm under AOT sweeps at 1.00× median against the native
Luau VM. The ~2× Luau-versus-rasc gap is structural, not compiler quality:
a Luau value round-trips through the
lua_Stateregister file in linear memory, where a rasc value stays in a wasm local.
rasc hits the same C-class ceiling as upstream AssemblyScript (within 9% of
native C on the reference workload). Nothing sits between your f32
and the hardware: no tags, no boxing, no interpreter dispatch.
The editor loop
The desktop editor previews on WAMR; the web editor previews on the browser engine — the same engine that ships. Edits climb a ladder:
- fast-interp — instant, every edit. A typical edit is rasc (~100–300 ms) + instantiate (~1 ms): running in about 0.3 s.
- wamrc -O0 — 0.55–3.5 s, about twice interp speed.
- wamrc -O3 in the background — native parity, the exact bits that ship.
Debugging always runs on the fast interpreter. The debug tier — source maps and statement-boundary hooks, so a script debugs identically on the browser, on WAMR, and on device — is on the roadmap.
Trust model
The WAMR sandbox is the safety boundary: untrusted content is safe to interpret because the sandbox contains it. Signing is provenance — it is what makes AOT distribution and marketplace content viable, not what makes interp content safe.
AOT on macOS
The required recipe, verified before trusting any measurement:
wamrc --bounds-checks=1 --cpu=apple-m1 --cpu-features=+reserve-x18 \
-o module.aot module.wasm
Then confirm wasm aot: loaded in the output. A stale-hash artifact
falls back to interp silently — module timing (and size) is the tell.
rasc modules boot with about one page and grow on demand. Under AOT, growth
moves malloc-backed memory under live AOT frames — pregrow with
RIVE_WASM_PREGROW_PAGES for content that grows at runtime.