Skip to content

WasmGC

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:

  1. Struct types. (type $point (struct (field f64) (field f64))) — fixed-shape product types. Allocated with struct.new. Fields accessed by index.
  2. Array types. (type $bytes (array i8)) — fixed-element arrays with bound. array.new, array.get, array.set, array.len.
  3. 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.
  4. i31ref. A tagged 31-bit integer that fits in a reference slot — the standard small-int unboxing trick from Smalltalk/V8.
  5. Subtyping. (sub final $base $derived) — single-inheritance type hierarchy with ref.test, ref.cast, br_on_cast. Enables OO-style downcasts.
  6. 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--wasm flag; 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:

WasmGCvm3
(ref $point)Cell{arena_tag=kArenaStruct, gen, slot} → struct layout
(ref null $arr)Cell{arena_tag=kArenaList, ...} plus a null Cell value
i31refWe could pack small ints into Cells (currently use typed banks instead)
array.get / bounds checkListGet(h, i) with explicit bounds check
struct.newAllocStruct(...)
Engine GCGo 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.set instead of ListGet/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):

  1. 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.
  2. 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.

Sources