demo · v149
Descriptor vs Wrapper Comparison
Step through the execution lifecycle — object creation, method call, instanceof check, prototype chain traversal — for both the legacy JS wrapper class pattern and the new WasmGC custom descriptor pattern. Each step is annotated with what the JS engine actually does, and a metrics panel summarises the key differences.
Step-by-step execution trace
Step 1 of 6
Legacy — JS wrapper class
New — Custom descriptor (Chrome 149)
Prototype chain
Output
Prototype chain
Output
Side-by-side code
Legacy — JS wrapper class
// 1. Wasm exports raw integer handles // wasmExports.counter_new() → id (i32) // wasmExports.counter_get(id) → i32 // wasmExports.counter_inc(id) → void // 2. Hand-write a JS wrapper class class Counter { constructor() { // Every instance pays allocation cost this._id = wasmExports.counter_new(); } get() { return wasmExports.counter_get(this._id); } inc() { wasmExports.counter_inc(this._id); } } // 3. Usage looks natural... const c = new Counter(); c.inc(); c.inc(); console.log(c.get()); // 2 // ...but two extra indirections: // JS → wrapper method → wasmExports // And _id stored redundantly on JS side
New — Custom descriptor (Chrome 149)
;; In the .wat / Wasm binary: ;; The struct declares its descriptor (type $Counter (struct (field $count i32) (descriptor $CounterDesc))) ;; Descriptor wires up the JS prototype (type $CounterDesc (descriptor $Counter (field $proto (ref $JSObject)))) ;; JS side: no wrapper class needed! // The module sets up the prototype once const proto = wasmModule.counterProto; proto.get = function() { ... }; proto.inc = function() { ... }; // Wasm struct IS the JS object const c = wasmExports.counter_new(); c.inc(); c.inc(); console.log(c.get()); // 2 // One indirection: JS method → Wasm field // No _id, no redundant JS allocation
Live simulation — create objects and call methods
Legacy wrapper result
Click "Run simulation" to start.
Custom descriptor (simulated) result
Click "Run simulation" to start.
Metrics comparison
Legacy wrapper class
Custom descriptor
/* Chrome 149 — Custom descriptors let Wasm types own their JS prototype */
/* Old way: every Wasm type needs a hand-written JS wrapper class.
Each wrapper instance duplicates the _id field on the JS heap. */
class Counter { constructor() { this._id = wasm.counter_new(); } ... }
/* New way: declare a descriptor in the Wasm binary.
The struct itself becomes the JS object — no _id, no wrapper class.
The prototype is wired up once at module instantiation time. */
const c = wasmExports.counter_new(); // c IS the Wasm struct
c instanceof Counter; // true — descriptor sets up the chain
see also
- Prototype Bridge — old vs new pattern overview
- Memory Footprint Demo — per-instance allocation comparison
- Method Dispatch Bench — timing three dispatch patterns
- JS Interop Explorer — live REPL with both patterns
- ChromeStatus: WebAssembly Custom Descriptors
implementation reference
Need the exact API surface, compatibility boundaries, errors, lifecycle, and source links? Read the matching gendn reference ↗