A JavaScript engine written in Rust
Zipp is a from-scratch ECMAScript engine: a hand-written parser, a register bytecode compiler, an explicit-frame virtual machine, native JIT tiers and a generational garbage collector, all in Rust. The same engine runs as a native CLI, as an embeddable Rust library, and as a WebAssembly module in the browser.
What Zipp is
Zipp began on 29 May 2026 as a small typed language and became a JavaScript engine the next day. Since then it has grown into roughly 345,000 lines of first-party Rust in the zipp-vm crate, plus a CLI, a WebAssembly embedding, a hardened sandbox runner and an experimental Python frontend that compiles to the same bytecode.
It is not a binding to V8, SpiderMonkey or JavaScriptCore, and it does not wrap QuickJS. Every stage from source text to native machine code is Zipp's own, with two vendored exceptions: a fork of the regress regular-expression engine and the RustPython parser (parser only) used by the Python frontend.
- Test262 executions passing, corrected core profile
- 95,680 / 95,680
- Zipp/Node wall time, all 30 native benchmark rows
- 0.728×
- Median native process launch
- 7.4 ms
- WebAssembly module on the wire (Brotli)
- 1.24 MB
95,671 unmodified upstream. Corpus pinned to tc39/test262 4249661.
Geometric mean, 2 September 2026 capture. Lower is faster.
Node 30.4 ms, Bun 43.3 ms, Deno 82.6 ms on the same machine.
JavaScript-only build, 5.3 MB raw.
Language coverage
Zipp implements the ES2015 through ES2025 language and runtime, and passes the pinned Test262 corpus including the staging directory. The feature list is the boring kind of complete:
- Classes with private elements, static blocks, computed keys and all eight decorator kinds.
- Destructuring, spread and rest, generators, async generators, promises, iterator helpers, and
using/await usingexplicit resource management. Map,Set, weak collections, weak references and finalization registries.- Twelve TypedArray kinds including
Float16Array,DataView, resizable and transferable buffers, shared memory andAtomicswith real blocking waits. - Arbitrary-precision
BigInt(ani128fast path backed bynum-bigint), symbols, proxies, and all thirteenReflecttraps with invariant checks. - Modern
RegExp: named groups, lookbehind, match indices (/d) and Unicode sets (/v), executed directly over UTF-16 code units. - ES modules with top-level await, dynamic
import(), import attributes, and deferred and source-phase imports. eval,Function,ShadowRealm, andTemporalwith fifteen calendars and the IANA time-zone database generated from pinned upstream data.
Strings preserve lone UTF-16 surrogates (stored as WTF-8 internally), + on strings builds ropes in O(1) and flattens lazily, and deep recursion throws a catchable RangeError instead of overflowing the native stack, because JavaScript frames never live on the Rust stack.
How the engine runs code
Source goes through a hand-written lexer and recursive-descent parser that owns its AST, so parsed programs can be cached and shared. The compiler emits a flat, three-address register bytecode per function. The virtual machine executes that bytecode over a flat register file with an explicit frame stack, then hands hot code to a JIT.
- Parse
Hand-written lexer and recursive-descent parser, built primarily for correct early errors. The pinned Test262 corpus contains 8,755 negative executions and every one must fail with the right error type.
- Compile to register bytecode
Each function becomes a
Vec<Instr>over a fixed register file. Operands are register indices, small immediates or constant-pool entries. The instruction set is small and regular so the JIT can consume the same structured form. - Interpret
Values are NaN-boxed 64-bit words: doubles are themselves, and integers, booleans, null, undefined and heap references hide in the quiet-NaN space. Property access goes through shared shapes and per-site inline caches.
- Tier up
After eight calls or eight back-edges a function or loop region is offered to the x86-64 JIT, which keeps integers and doubles unboxed in machine registers. Guards bail to the exact bytecode instruction; nothing is ever truncated silently.
The architecture page goes deeper into the value model, the JIT tiers (SROA, INT, REGALLOC/DOUBLE and MEM), the non-moving generational nursery, and why the explicit frame stack is what makes deoptimization exact.
Three ways to run it
| Profile | Use it for | Boundary |
|---|---|---|
zipp js / zipp mjs native CLI | Trusted programs, scripts and benchmarks | Maximum throughput with the x86-64 or ARM64 JIT enabled |
zipp-wasm in a Web Worker | Arbitrary browser-hosted code | Interpreter-only safe-sandbox build with no unsafe code; the host terminates the Worker at its deadline |
zipp-sandbox native runner | Hardened native execution of untrusted scripts | No JIT, unsafe forbidden at compile time, instruction, heap, output, import and wall-time limits |
git clone https://github.com/f2i-com/zipp.org
cd zipp.org
cargo build --locked --release
./target/release/zipp js examples/hello.js
# ↳ hello, worldThe CLI also runs ES modules (zipp mjs), Python (zipp py), and picks a frontend from the file extension or shebang with zipp run. Bare source such as x = 1 never silently defaults to one language. Native builds are published for Windows and Linux x86-64 on the releases page.
Embedding Zipp in Rust
The zipp_vm::embed module keeps one compiled VM alive across host calls. A host compiles a script once, installs a host-call handler, runs the top level, and later evaluates expressions or calls functions in the same global context.
let mut st = zipp_vm::embed::compile_script(source)?;
st.set_host_call(Box::new(|kind, args| match kind {
"http.get" => Ok(blocking_get(&args[0])),
_ => Err(format!("TypeError: unknown host call {kind}")),
}));
st.run_init()?; // top-level execution
let html = st.eval_in_context("render()")?; // later, same globalsValues cross the boundary as owned HostValue trees, never as VM-internal handles. Arrays and plain objects marshal structurally; functions, classes, maps, dates, proxies and cycles cross as Opaque and are never overwritten by a structural write-back. The embedding guide covers slots, microtask draining, grammar goals and the browser Engine class.
JavaScript in WebAssembly
The zipp-wasm crate builds the engine for wasm32-unknown-unknown with wasm-bindgen, as a persistent VM a browser host can keep alive across re-entries. It is interpreter-only, grants no host capabilities by default, and exposes a small Engine class: initScript, evalInContext, callFunction, setInstructionBudget, takeOutput and friends.
The JavaScript-only module is 5,308,147 bytes raw and 1,239,957 bytes after Brotli; the build that also includes Python is 7,773,932 bytes raw and 1,777,382 on the wire. That is roughly three times the size of QuickJS-NG's WebAssembly reactor, a trade-off documented plainly on the WebAssembly page, which also explains the Worker-termination deadline model and the resource limits.
Conformance and performance
Zipp runs the pinned tc39/test262 corpus in both sloppy and strict mode: 95,680 executions, including staging, with zero skips. Against the unmodified corpus 95,671 pass; the nine remaining executions are contradictions in the pinned harness files and are corrected by five reviewable patches to five test files, giving 95,680 of 95,680. The Test262 page documents each correction, the CI gate, and how the number climbed from a mis-scored 100% to an honest 96.97% and back up.
On the native benchmark suites, the 2 September 2026 capture puts Zipp at a geometric mean of 0.728× Node's wall time across thirty rows, with 21 rows faster than Node and nine slower, including React-style reconciliation at 1.58× and allocation-survival at 1.56×. The benchmarks page has every row, the hardware, compiler flags, competitor versions, bootstrap intervals, links to the raw JSON, and the later capture that regressed after a correctness fix.
Frequently asked questions
Is Zipp production-ready?
It is a 0.0.x project that is 108 days old at the time of writing. The JavaScript engine is conformance-complete and benchmarked, the browser sandbox has been through two external correctness audits, and the Python frontend is explicitly experimental. Read the release notes and decide per use case.
Does Zipp have a JIT?
Yes, on native x86-64: a tiered template JIT with hot-loop on-stack replacement, plus a guarded ARM64 baseline tier. The WebAssembly build is interpreter-only by design, so that the browser sandbox contains no runtime code generation.
How is this different from Boa or QuickJS?
Boa is also a Rust engine but interpreter-only; QuickJS is a small C interpreter. Zipp has native JITs and a Python frontend, at the cost of a larger binary. The comparisons page lays out where each is the better choice.
Can I run Node packages?
Zipp is an ECMAScript engine, not a Node runtime. There is no require, fs or http built in. The zipp js compat goal permits top-level return for CommonJS-shaped scripts, and hosts expose capabilities explicitly through host calls.
What license is it under?
Apache-2.0 for Zipp itself. The vendored regex fork is MIT or Apache-2.0, the RustPython parser fork is MIT, and generated Unicode CLDR data carries the Unicode license.