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).
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.
legacy path (UTF-8 marshalling)
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+)
Wasm receives the JS string as an externref. The builtin functions (length, charCodeAt, etc.) read it directly. Zero marshalling, no temp buffers.
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);