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

implementation reference

Need the exact API surface, compatibility boundaries, errors, lifecycle, and source links? Read the matching gendn reference ↗