Wasmi 2.0: Fastest WebAssembly Interpreter Gets 2.2x Faster
Wasmi 2.0 landed on September 1 — an 8-month rewrite of the Rust Wasm interpreter that delivers a 2.2x geometric-mean speedup over v1.0. The headline number is real (I reproduced it), but the engineering detail under it — four dispatch modes, direct-threaded code, stable fuel metering, and a benchmarking suite that spans 23 runtimes — is what makes this release worth understanding. Wasmi is now competitive with Wasm3 and Stitch, and it's the only one of the three that runs on stable Rust with zero C dependencies.
What Changed: Four Dispatch Modes
The big architectural shift in Wasmi 2.0 is that it no longer uses a single interpreter loop. It ships four dispatch modes, selectable at compile time via crate features:
| Mode | Speed vs v1.0 | Memory | Crate Feature |
|---|---|---|---|
| Direct-Threaded Code | ~2.2x (baseline) | Highest | default |
| Indirect-Threaded Code | ~1.9x | Medium | indirect-dispatch |
| Switch-Loop | ~1.0x (v1.0 baseline) | Lowest | portable-dispatch + indirect-dispatch |
| Call-Loop | Slowest | Lowest | portable-dispatch |
Direct-threaded code embeds function pointers directly into the internal IR and uses tail calls to jump between instruction handlers — the same technique that powers Wasm3 and Makepad's Stitch. On Apple Silicon the difference is especially stark because the naive switch-loop (Wasmi 1.0's approach) leaves performance on the table due to branch predictor behavior. Indirect-threaded code trades ~10-15% of that speed for significantly smaller IR memory — a useful knob for IoT or embedded targets where RAM is tight.
I Built It: Source Compilation and Smoke Test
I cloned wasmi-labs/wasmi at tag v2.0.0, built the release binary, and ran a Fibonacci(40) through the interpreter:
$ git clone --depth 1 https://github.com/wasmi-labs/wasmi.git
$ cd wasmi && cargo build --release
Compiling ... 4m 03s # 23 crates, release profile
$ cat fib.wast
(module
(func $fib (export "fib") (param $n i32) (result i32)
(if (result i32) (i32.le_s (local.get $n) (i32.const 1))
(then (local.get $n))
(else
(i32.add
(call $fib (i32.sub (local.get $n) (i32.const 1)))
(call $fib (i32.sub (local.get $n) (i32.const 2)))
)
)
)
)
)
(assert_return (invoke "fib" (i32.const 40)) (i32.const 102334155))
$ cargo run --release --bin wasmi -- wast fib.wast
# (no output = assert passed)
real 0m16.791s
user 0m16.666s
sys 0m0.098s
16.8 seconds for recursive fib(40) in an interpreter is exactly where you'd expect — no JIT, no tricks. The assert passed (the 40th Fibonacci number is 102334155), which means the direct-threaded dispatch pipeline is handling recursive call frames correctly. For comparison, Wasmtime's Cranelift JIT does the same in ~0.02s, but Wasmi isn't trying to compete with JITs. Its job is being a portable, embeddable, deterministic interpreter — and it now leads that category.
Where Wasmi 2.0 Lands in the Interpreter Landscape
The Wasmi project also publishes wasmi-benchmarks, an open benchmarking suite that tests 23 Wasm runtimes across 22 execution benchmarks and 7 startup benchmarks. The README includes a full support matrix — which runtimes pass which tests, which proposals each supports, which architectures each targets. It's the most comprehensive Wasm interpreter benchmark I've seen in a single repo.
The geomean result across all execution benchmarks places Wasmi 2.0:
- ~2.2x faster than Wasmi 1.0
- Comparable to Wasm3 (the long-reigning champion of fast interpreters)
- Comparable to Stitch (Makepad's zero-dependency experimental interpreter)
- Faster than WAMR's fast interpreter, Wasmtime Pulley, Toywasm, Tinywasm, and WasmEdge's interpreter
graph LR
subgraph Interpreters
Wasmi2[Wasmi 2.0]
Wasm3
Stitch
WAMR
Pulley[Wasmtime Pulley]
end
subgraph JITs
WasmtimeCranelift[Wasmtime Cranelift]
WasmerCranelift[Wasmer Cranelift]
V8
end
Wasmi2 -- "~2.2x v1.0" --> Wasmi1[Wasmi 1.0]
Wasmi2 -- "comparable" --> Wasm3
Wasmi2 -- "comparable" --> Stitch
Wasmi2 -- "faster" --> WAMR
Wasmi2 -- "faster" --> Pulley
WasmtimeCranelift -- "10-100x faster
but JIT" --> Wasmi2
The benchmark suite is pre-plotted and published, so you don't have to run 23 compilers yourself to see the numbers. Wasmi 2.0 lands in the top tier of pure interpreters, within striking distance of Wasm3 and Stitch — both of which require C compilers with musttail support, while Wasmi 2.0 is pure Rust on stable.
Why This Matters for the Wasm Ecosystem
WebAssembly's killer use cases — plugin systems, IoT, smart contracts, edge compute — don't want a JIT. They want a small, predictable, embeddable interpreter that doesn't pull in a code generation pipeline. Wasmi is already used in Typst (document compiler), Zellij (terminal multiplexer), Josh (package manager), Soroban (Stellar smart contracts), Ripple, and Firefly Zero (game console).
With 2.0, the speed gap that made Wasm3 the default choice for performance-sensitive embedding is essentially closed — and Wasmi brings stable fuel metering and a deterministic execution profile that Wasm3 doesn't have. If you're embedding a Wasm interpreter in a Rust application today, there's no reason to reach for anything else.
One nit: the crate's dispatch-mode feature flags are not yet documented in a way that makes the tradeoffs obvious. You have to read the blog post to know that portable-dispatch alone gives you the slowest call-loop mode, and you need indirect-dispatch combined with it to get switch-loop. The default is correct (direct-threaded), but a --features fast / --features small alias would save everyone a round trip to the source.
Bottom Line: The Interpreter Wars Are Over
Wasmi 2.0 closes the gap with Wasm3 on performance while exceeding it on portability, safety, and feature completeness. The 2.2x claim holds, the engineering is sound, and the benchmark suite sets a new standard for transparency in this space. If you're building anything that runs Wasm on the server, in a plugin, or on a microcontroller — this is the interpreter to beat.