demo · v130

string builtins performance benchmark

The chromestatus summary says the builtins let WebAssembly create and manipulate JS strings "without native support within WebAssembly while still allowing for a similar performance as native string references." Run the benchmark below to compare the legacy path (encode JS string to UTF-8 bytes, hand to wasm, decode result, return to JS) against the Chrome 130 builtin path (string handle passes directly).

requires Chrome 130+ The benchmark uses WebAssembly.compile with the builtins: ["js-string"] option, which is recognised in Chrome 130 and later. Older browsers will report "builtins unavailable" and skip the after column.
probing…

legacy path (UTF-8 marshalling)

– ms

Each call encodes the JS string to UTF-8 bytes, writes them into the wasm linear memory, the imported function reads them out, encodes the result, and JS decodes it back. Round-trip cost per call.

js-string builtins (Chrome 130+)

– ms

Wasm receives the JS string as an externref. The builtin functions (length, charCodeAt, etc.) read it directly. Zero marshalling, no temp buffers.

run the benchmark above to see the speed-up

the code

// Module imports js-string builtins by name.
const wasm = new Uint8Array([/* hand-crafted module that imports
   wasm:js-string/length and uses it from a function */]);

// Chrome 130: pass builtins option.
const m = await WebAssembly.compile(wasm, { builtins: ["js-string"] });
const inst = await WebAssembly.instantiate(m);

// Call with a JS string directly — no encode/decode step.
const len = inst.exports.stringLength("hello world");   // 11

// Pre-130 / no builtins: you would encode to UTF-8 first, hand the
// bytes + length to wasm, and let it iterate manually.
const enc = new TextEncoder().encode("hello world");
const ptr = inst.exports.alloc(enc.length);
new Uint8Array(inst.exports.memory.buffer).set(enc, ptr);
const len = inst.exports.utf8Length(ptr, enc.length);

see also