v149 · webassembly · experimental
WebAssembly Custom Descriptors
A WasmGC extension that attaches JavaScript prototypes to WebAssembly struct types. JS code can call methods on Wasm objects using normal dot-notation; Wasm objects can participate in instanceof checks. Builds on the WasmGC proposal (structs, arrays, and precise GC).
at a glance
| Chrome | Origin trial in Chrome 149 |
|---|---|
| Status | WebAssembly CG proposal — phase 2 |
| Spec repo | WebAssembly / custom-descriptors |
| Overview | proposals/custom-descriptors/Overview.md |
| ChromeStatus | 6024844719947776 — WebAssembly Custom Descriptors |
| Prerequisite | WasmGC (shipped in Chrome 119) |
why it exists
WasmGC introduced first-class structs and arrays with precise garbage collection. However, to share type-level data — vtables, interface tables, static fields — across all instances of a struct type, WasmGC programs had to store a reference to that data inside each struct instance. This wastes memory (one pointer per object) and requires explicit field lookups in every vtable call.
Custom descriptors solve this with an engine-managed runtime type table: each struct type can have a descriptor type whose single instance is shared by all objects of that struct type. This eliminates the per-object vtable pointer. As a second-order effect, the first field of the descriptor can be a JavaScript prototype object, enabling seamless JS-Wasm interop: wasmObject.myMethod() dispatches through the prototype chain without any wrapping.
shape of the API
WAT: descriptor and describes clauses
Structs and their descriptors are paired in a rec group. The struct uses descriptor to name its descriptor type; the descriptor uses describes to name the struct it covers.
(rec
(type $counter (descriptor $counter.vtable)
(struct (field $val (mut i32))))
(type $counter.vtable (describes $counter)
(struct
(field $proto (ref extern)) ;; first field → JS prototype
(field $get (ref $get_fn))
(field $inc (ref $inc_fn))
)
)
)
WAT: allocating with a descriptor
struct.new_desc $T desc_ref field... |
Like struct.new but takes a reference to the descriptor instance as the final operand. The descriptor is shared — typically imported as a global. |
|---|---|
struct.new_default_desc $T desc_ref |
Same but initialises all fields to zero / null defaults. |
WAT: accessing the descriptor
ref.get_desc $T obj_ref |
Returns the descriptor for the given struct instance. If obj_ref has an exact type, the descriptor's exact type is known. |
|---|
JavaScript prototype integration
If a struct type has a descriptor whose first field is an immutable externref, the WebAssembly JS API uses that field's value as the prototype for all instances of the struct. No wrapping needed — the Wasm struct acts as a JS object.
// JavaScript
const counterProto = {};
counterProto.get = function() {
return wasmExports["counter.get"](this);
};
counterProto.inc = function() {
wasmExports["counter.inc"](this);
};
const { instance } = await WebAssembly.instantiateStreaming(
fetch("counter.wasm"),
{ env: { "counter.proto": counterProto } }
);
const c = instance.exports.counter.value;
console.log(c.get()); // normal method call on a Wasm object
c.inc();
console.log(c instanceof Object); // true — prototype chain works
Declarative configureAll (builtin)
For large modules, import configureAll from "wasm:js-prototypes" to set up all prototypes and constructors in one call from the start function. Requires { builtins: ["js-prototypes"] } in the compile options.
// WAT: import the builtin
(import "wasm:js-prototypes" "configureAll"
(func $configureAll (type $configureAll_t)))
// JS: compile with the builtin enabled
const { instance } = await WebAssembly.instantiate(bytes, imports, {
builtins: ["js-prototypes"]
});
const Counter = constructors.Counter; // installed by configureAll
const c = new Counter(0);
console.log(c.get());
console.log(c instanceof Counter); // true
Source: custom-descriptors/Overview.md §3–§5.
key instructions summary
struct.new_desc | Allocate a struct with an explicit descriptor reference. |
|---|---|
struct.new_default_desc | Allocate with zero-initialised fields + descriptor. |
ref.get_desc | Retrieve the descriptor instance for a struct object. |
ref.cast_desc_eq | Cast + check that two objects share the same descriptor instance. |
br_on_cast_desc_eq | Branch if descriptor of two objects is the same. |
br_on_cast_desc_eq_fail | Branch if descriptors differ. |
browser support
| Chrome | Origin trial in Chrome 149 (requires WasmGC, Chrome 119+) |
|---|---|
| Edge | Tracking Chromium |
| Firefox | No signal (as of Chrome 149) |
| Safari | No signal (as of Chrome 149) |
see also
- WebAssembly/custom-descriptors — spec repo and proposals
- MDN: WebAssembly.instantiateStreaming()
- ChromeStatus: WasmGC — the prerequisite proposal (shipped Chrome 119)
- chrome-platform-showcase: Custom Descriptors demos