Native GC primitives in a portable bytecode. Ratified in Wasm 3.0 (Sep 2025), shipped in all major browsers by Dec 2024. Dart, Kotlin, OCaml, Java/Scala/Scheme can now compile to Wasm without bundling a GC.
§1 Provenance
- WasmGC. Proposal repo: https://github.com/WebAssembly/gc. Champions: Andreas Rossberg (Mozilla, retd.), Francis McCabe (Google).
- Standardised as part of WebAssembly 3.0, W3C Recommendation, September 24, 2025.
- Earlier: Wasm 2.0 (Dec 2024, W3C REC); WasmGC was post-2.0 work that landed in 3.0.
- Browser support:
- Chrome 119 (Oct 2023)
- Firefox 120 (Nov 2023)
- Safari 18.2 (Dec 2024) — last to ship, unblocking GC-language deployment.
§2 Mechanism
WasmGC adds typed managed references to Wasm. Distinct from linear memory; references live in a separate “managed heap” that the host engine garbage-collects.
Core primitives:
- Struct types.
(type $point (struct (field f64) (field f64)))— fixed-shape product types. Allocated withstruct.new. Fields accessed by index. - Array types.
(type $bytes (array i8))— fixed-element arrays with bound.array.new,array.get,array.set,array.len. - Ref types.
(ref $point),(ref null $point),(ref any),(ref eq),(ref i31), etc. — typed references that the host knows are pointers (not integers), so it can trace them. i31ref. A tagged 31-bit integer that fits in a reference slot — the standard small-int unboxing trick from Smalltalk/V8.- Subtyping.
(sub final $base $derived)— single-inheritance type hierarchy withref.test,ref.cast,br_on_cast. Enables OO-style downcasts. - Function references.
(ref $functype)— typed function pointers; enables vtables.
The engine traces references precisely (it knows what’s a pointer and what’s a scalar from the type system). No conservative scanning, no fat pointers.
Languages compiling to WasmGC give up control of when GC runs but gain the engine’s industrial-strength collector for free, and don’t ship MB of nursery/old-gen plumbing inside their .wasm binary.
§3 Memory-safety property
The engine’s GC provides full temporal safety on the managed heap: a ref is either null or live. The Wasm type system provides spatial safety within each struct/array (field/element indices are checked against the declared type). The host engine is responsible for the actual collection algorithm.
This delivers the same memory-safety class as Java or .NET: managed-heap UAF and OOB are structurally impossible.
§4 Production status (May 2026)
- All major browsers ship WasmGC.
- Languages compiling to WasmGC in production:
- Kotlin/Wasm (JetBrains) — using WasmGC since Kotlin 1.9.20. Compose for Web hit Beta Sept 2025. JetBrains’ benchmarks show ~3× JS-build perf in UI-heavy workloads.
- Dart/Flutter —
--wasmflag; falls back to JS if WasmGC not detected. Production-supported by Google. - wasm_of_ocaml — emits WasmGC since 2024.
- J2CL (Google) — Java to WasmGC, used in Google Sheets and other internal apps.
- Scala.js — adds WasmGC backend (Issue #4928).
- Scheme, several research languages.
- Languages explicitly not using WasmGC: Rust, C++, Go, anything that wants direct memory control — they continue to use Wasm linear memory + ship their own GC if needed.
§5 Cost
- Throughput. The host’s GC is heavily optimised (V8 reuses Oilpan-class infrastructure). On Kotlin/Wasm benchmarks: comparable to JVM, generally faster than Kotlin/JS.
- Binary size. Huge win. A Kotlin/Wasm app drops the bundled-GC overhead (typical savings: hundreds of KB to MBs).
- Startup latency. Faster startup than JS-compiled equivalents (no need to JIT-warm the embedded GC).
- Cross-language interop. A Dart object and a Kotlin object live in the same heap; no copy across linear memories. Cross-language calls are zero-marshalling.
- Hidden cost. Languages with very specific GC requirements (low-latency real-time) cannot tune the engine GC; you take what V8/SpiderMonkey/JSC gives you.
§6 Mochi adaptation note
vm3’s handle ABI is already a WasmGC-style ref encoding. Compare:
| WasmGC | vm3 |
|---|---|
(ref $point) | Cell{arena_tag=kArenaStruct, gen, slot} → struct layout |
(ref null $arr) | Cell{arena_tag=kArenaList, ...} plus a null Cell value |
i31ref | We could pack small ints into Cells (currently use typed banks instead) |
array.get / bounds check | ListGet(h, i) with explicit bounds check |
struct.new | AllocStruct(...) |
| Engine GC | Go GC for backing slices + vm3 free-list/mark-sweep for slot liveness |
So if Mochi ever wanted to compile to WasmGC, the mapping is direct:
- Each typed arena (MEP-40 §6.2) maps to a WasmGC
(array …)type. - The Cell handle is a WasmGC
ref. - The 12-bit generation field is gone (the engine GC subsumes it; UAF is impossible by construction).
- compiler3 emits
array.get/array.setinstead ofListGet/ListSet.
This is the smallest patch to make Mochi browser-portable in the future. It’s a Phase-8-or-later prospect, but the architectural alignment is striking: MEP-40 chose the same abstractions WasmGC chose, independently. Worth flagging in MEP-41 as a validation point.
Adaptation note for now (Mochi-on-Go):
- Mochi.wasm target via WasmGC. A future MEP-50 could deliver Mochi-on-the-browser by emitting WasmGC instead of Go bytecode. The compiler3 IR is already type-driven (§7.2); the emit pass would target Wasm 3.0 GC instructions.
- i31ref-style packing in vm3 today. If we ever want to elide the typed register banks for small-int-heavy code, packing 31-bit ints into a “fake” Cell with arena_tag = kArenaSmallInt and slot = the int value is the same trick.
No design conflict. WasmGC validates Mochi’s typed-handle abstraction; the only divergence is that Mochi’s host is Go, not a browser.
§7 Open questions for MEP-41
- Should the MEP-40 cell layout be revised to make a future WasmGC port one-to-one? Specifically, should we adopt 31-bit unboxed-int representation now, instead of the typed register banks?
- WasmGC’s GC algorithm is engine-defined. If Mochi compiles to Wasm, do we lose the ability to tune for our workload? (Yes; the trade-off is portability.)
- The WasmGC type system is single-inheritance + interfaces (
(ref any)). Mochi has structural typing. How awkward is the mapping? - Could vm3 share its handle ABI with a WasmGC engine running embedded in the Go process, for FFI? Probably overkill, but worth thinking about for the long term.