// The WebAssembly JavaScript API's constructors and reflection: new // WebAssembly.Memory, Table and Global (descriptors, defaults, conversions // and every error), using them as imports (limits, types and mutability // checked with Node's LinkError messages), the identity of exported // objects, WebAssembly.Module.imports, exports and customSections, and the // classes called without new. The modules are inline base64 literals // (their text is in tests/wasm-spec/corpus/jsapi.wat and jsapi-own.wat). const jsapi = Buffer.from("AGFzbQEAAAABKglgAX8Bf2ABfwBgAAF/YAF8AXxgAX4AYAABb2ABbwBgAn9/AX9gAn9/AAJbBwNlbnYDbG9nAAEDZW52Bm1lbW9yeQIBAQQDZW52BXRhYmxlAXABAggDZW52B2NvdW50ZXIDfwEDZW52BXNjYWxlA3wAA2VudgNiaWcDfgEDZW52A3JlZgNvAQMPDgAAAgMEBQYHCAACAAIBB5UBEQRidW1wAAMGc2NhbGVkAAQGYmlnQWRkAAUGZ2V0UmVmAAYGc2V0UmVmAAcFYXBwbHkACAVzdG9yZQAJBGxvYWQACgRzaXplAAsEZ3JvdwAMCXRhYmxlU2l6ZQANA3NheQAOBm1lbW9yeQIABXRhYmxlAQAHY291bnRlcgMAC2Fsc29Db3VudGVyAwAGZG91YmxlAAEJCAEAQQALAgECCnAOBwAgAEECbAsHACAAIABsCwsAIwBBAWokACMACwcAIAAjAaILCQAjAiAAfCQCCwQAIwMLBgAgACQDCwkAIAEgABEAAAsJACAAIAE6AAALBwAgAC0AAAsEAD8ACwYAIABAAAsFAPwQAAsGACAAEAALAAwIZHlsaW5rLjABAgMACgRtZXRhaGVsbG8ACwhkeWxpbmsuMAQFAAEA", "base64"); const own = Buffer.from("AGFzbQEAAAABBgFgAX8BfwMCAQAEBQFwAQIIBQQBAQEEBhICfwFBBwt8AEQAAAAAAAD4PwsHKgcDbWVtAgAEbWVtMgIAA3RhYgEAAWcDAAJnMgMABWZpeGVkAwECaWQAAAkHAQBBAQsBAAoGAQQAIAAL", "base64"); const show = (v) => { if (typeof v === "bigint") return `${v}n`; if (typeof v === "number") return Object.is(v, -0) ? "-0" : String(v); if (typeof v === "function") return "function"; if (Array.isArray(v)) return `[${v.map(show).join(", ")}]`; if (v instanceof WebAssembly.Memory) return "Memory"; if (v instanceof WebAssembly.Table) return "Table"; if (v instanceof WebAssembly.Global) return "Global"; if (v instanceof ArrayBuffer) return `ArrayBuffer(${v.byteLength})`; if (v !== null && typeof v === "object") return JSON.stringify(v); return String(v); }; const attempt = (label, f) => { try { console.log(label, show(f())); } catch (e) { console.log(label, e instanceof Error ? `${e.name}: ${e.message}` : `threw ${show(e)}`); } }; // ── WebAssembly.Global ──────────────────────────────────────────────────── console.log("Global"); attempt("i32 default", () => new WebAssembly.Global({ value: "i32" }).value); attempt("i64 default", () => new WebAssembly.Global({ value: "i64" }).value); attempt("f32", () => new WebAssembly.Global({ value: "f32" }, 1.1).value); attempt("f64", () => new WebAssembly.Global({ value: "f64" }, 1.1).value); attempt("f64 undefined", () => new WebAssembly.Global({ value: "f64" }, undefined).value); attempt("f64 null", () => new WebAssembly.Global({ value: "f64" }, null).value); attempt("i32 string", () => new WebAssembly.Global({ value: "i32" }, "7").value); attempt("i32 wraps", () => new WebAssembly.Global({ value: "i32" }, 2 ** 32 + 3).value); attempt("i32 object", () => new WebAssembly.Global({ value: "i32" }, { valueOf: () => 9 }).value); attempt("i64 bigint", () => new WebAssembly.Global({ value: "i64" }, 2n ** 63n).value); attempt("i64 string", () => new WebAssembly.Global({ value: "i64" }, "12").value); attempt("i64 number", () => new WebAssembly.Global({ value: "i64" }, 5).value); attempt("i64 null", () => new WebAssembly.Global({ value: "i64" }, null).value); attempt("i32 bigint", () => new WebAssembly.Global({ value: "i32" }, 5n).value); attempt("externref default", () => new WebAssembly.Global({ value: "externref" }).value); attempt("externref object", () => new WebAssembly.Global({ value: "externref" }, { a: 1 }).value); attempt("anyfunc default", () => new WebAssembly.Global({ value: "anyfunc" }).value); attempt("anyfunc arrow", () => new WebAssembly.Global({ value: "anyfunc" }, () => 1)); attempt("anyfunc number", () => new WebAssembly.Global({ value: "anyfunc" }, 1)); attempt("funcref name", () => new WebAssembly.Global({ value: "funcref" })); attempt("v128", () => new WebAssembly.Global({ value: "v128" })); attempt("bad type", () => new WebAssembly.Global({ value: "i16" })); attempt("no value", () => new WebAssembly.Global({})); attempt("no descriptor", () => new WebAssembly.Global()); attempt("number descriptor", () => new WebAssembly.Global(1)); attempt("toString type", () => new WebAssembly.Global({ value: { toString: () => "f64" } }, 2.5).value); const order = []; new WebAssembly.Global( { get value() { order.push("value"); return { toString: () => (order.push("value toString"), "f64") }; }, get mutable() { order.push("mutable"); return false; }, }, { valueOf: () => (order.push("valueOf"), 1) }, ); console.log("order", order.join(",")); const immutable = new WebAssembly.Global({ value: "i32" }, 4); attempt("immutable set", () => { immutable.value = 5; }); console.log("immutable after", immutable.value, immutable.valueOf()); const mutable = new WebAssembly.Global({ value: "i32", mutable: 1 }, 4); mutable.value = 2.9; console.log("mutable", mutable.value, mutable.valueOf()); mutable.value = "x"; console.log("mutable NaN", mutable.value); const mutable64 = new WebAssembly.Global({ value: "i64", mutable: true }); mutable64.value = true; console.log("mutable64", show(mutable64.value)); attempt("i64 set number", () => { mutable64.value = 3; }); const mutableF32 = new WebAssembly.Global({ value: "f32", mutable: true }, 1); mutableF32.value = undefined; console.log("f32 set undefined", mutableF32.value); const mutableRef = new WebAssembly.Global({ value: "externref", mutable: true }, "start"); mutableRef.value = undefined; console.log("externref set undefined", mutableRef.value); const mutableFunc = new WebAssembly.Global({ value: "anyfunc", mutable: true }); attempt("anyfunc set arrow", () => { mutableFunc.value = () => 2; }); console.log(String(immutable), Object.prototype.toString.call(immutable), immutable instanceof WebAssembly.Global); console.log(immutable); // @ts-ignore: called without new, as Node reports attempt("Global()", () => WebAssembly.Global({ value: "i32" })); // ── WebAssembly.Memory ──────────────────────────────────────────────────── console.log("Memory"); attempt("bytes", () => new WebAssembly.Memory({ initial: 1 }).buffer.byteLength); attempt("zero", () => new WebAssembly.Memory({ initial: 0 }).buffer.byteLength); attempt("string initial", () => new WebAssembly.Memory({ initial: "2" }).buffer.byteLength); attempt("fractional", () => new WebAssembly.Memory({ initial: 1.9 }).buffer.byteLength); attempt("null initial", () => new WebAssembly.Memory({ initial: null }).buffer.byteLength); attempt("missing", () => new WebAssembly.Memory({})); attempt("minimum only", () => new WebAssembly.Memory({ minimum: 1 })); attempt("undefined initial", () => new WebAssembly.Memory({ initial: undefined })); attempt("negative", () => new WebAssembly.Memory({ initial: -1 })); attempt("negative fraction", () => new WebAssembly.Memory({ initial: -0.5 })); attempt("NaN", () => new WebAssembly.Memory({ initial: NaN })); attempt("Infinity", () => new WebAssembly.Memory({ initial: Infinity })); attempt("2^32", () => new WebAssembly.Memory({ initial: 2 ** 32 })); attempt("too many pages", () => new WebAssembly.Memory({ initial: 65537 })); attempt("max below initial", () => new WebAssembly.Memory({ initial: 20, maximum: 1 })); attempt("max too large", () => new WebAssembly.Memory({ initial: 1, maximum: 65537 })); attempt("max 2^32", () => new WebAssembly.Memory({ initial: 1, maximum: 2 ** 32 })); attempt("max null", () => new WebAssembly.Memory({ initial: 1, maximum: null })); attempt("bigint initial", () => new WebAssembly.Memory({ initial: 1n })); attempt("throwing initial", () => new WebAssembly.Memory({ initial: { valueOf() { throw new Error("boom"); }, }, }), ); attempt("shared without maximum", () => new WebAssembly.Memory({ initial: 1, shared: true })); attempt("no descriptor", () => new WebAssembly.Memory()); attempt("string descriptor", () => new WebAssembly.Memory("x")); const memoryOrder = []; new WebAssembly.Memory({ get initial() { memoryOrder.push("initial"); return { valueOf: () => (memoryOrder.push("initial valueOf"), 1) }; }, get maximum() { memoryOrder.push("maximum"); return { valueOf: () => (memoryOrder.push("maximum valueOf"), 3) }; }, get shared() { memoryOrder.push("shared"); return false; }, }); console.log("order", memoryOrder.join(",")); const grown = new WebAssembly.Memory({ initial: 1, maximum: 2 }); const before = grown.buffer; new Uint8Array(before)[5] = 7; console.log("same buffer", grown.buffer === grown.buffer); attempt("grow", () => grown.grow(1)); console.log("after grow", grown.buffer.byteLength, new Uint8Array(grown.buffer)[5], before === grown.buffer); attempt("grow past maximum", () => grown.grow(1)); attempt("grow negative", () => grown.grow(-1)); attempt("grow missing", () => grown.grow()); attempt("grow NaN", () => grown.grow(NaN)); attempt("grow 2^32", () => grown.grow(2 ** 32)); attempt("grow zero", () => grown.grow(0)); attempt("unbounded grow", () => new WebAssembly.Memory({ initial: 1, maximum: undefined }).grow(3)); console.log(String(grown), grown instanceof WebAssembly.Memory); console.log(grown); // @ts-ignore: called without new, as Node reports attempt("Memory()", () => WebAssembly.Memory({ initial: 1 })); // ── WebAssembly.Table ───────────────────────────────────────────────────── console.log("Table"); attempt("anyfunc", () => new WebAssembly.Table({ initial: 2, element: "anyfunc" }).get(1)); attempt("externref", () => new WebAssembly.Table({ initial: 1, element: "externref" }).get(0)); attempt("externref init", () => new WebAssembly.Table({ initial: 2, element: "externref" }, "hi").get(1)); attempt("externref undefined", () => new WebAssembly.Table({ initial: 2, element: "externref" }, undefined).get(1)); attempt("anyfunc null", () => new WebAssembly.Table({ initial: 2, element: "anyfunc" }, null).get(1)); attempt("anyfunc arrow", () => new WebAssembly.Table({ initial: 2, element: "anyfunc" }, () => 1)); attempt("anyfunc number", () => new WebAssembly.Table({ initial: 2, element: "anyfunc" }, 5)); attempt("funcref name", () => new WebAssembly.Table({ initial: 1, element: "funcref" })); attempt("i32 element", () => new WebAssembly.Table({ initial: 1, element: "i32" })); attempt("no element", () => new WebAssembly.Table({ initial: 1 })); attempt("no initial", () => new WebAssembly.Table({ element: "anyfunc" })); // @ts-ignore: an incomplete descriptor attempt("empty", () => new WebAssembly.Table({})); attempt("no descriptor", () => new WebAssembly.Table()); attempt("max below initial", () => new WebAssembly.Table({ initial: 2, maximum: 1, element: "anyfunc" })); attempt("bad init and descriptor", () => new WebAssembly.Table({ initial: 3, maximum: 2, element: "anyfunc" }, 37)); attempt("too large", () => new WebAssembly.Table({ initial: 10000001, element: "anyfunc" })); attempt("large maximum", () => new WebAssembly.Table({ initial: 1, maximum: 10000001, element: "anyfunc" }).length); attempt("max 2^32", () => new WebAssembly.Table({ initial: 1, maximum: 2 ** 32, element: "anyfunc" })); attempt("toString element", () => new WebAssembly.Table({ element: { toString: () => "anyfunc" }, initial: 1 }).length); const tableOrder = []; new WebAssembly.Table({ get element() { tableOrder.push("element"); return { toString: () => (tableOrder.push("element toString"), "anyfunc") }; }, get initial() { tableOrder.push("initial"); return { valueOf: () => (tableOrder.push("initial valueOf"), 1) }; }, get maximum() { tableOrder.push("maximum"); return { valueOf: () => (tableOrder.push("maximum valueOf"), 3) }; }, }); console.log("order", tableOrder.join(",")); const refs = new WebAssembly.Table({ initial: 1, maximum: 4, element: "externref" }, 3); attempt("set default", () => { refs.set(0); return refs.get(0); }); attempt("grow default", () => [refs.grow(1), refs.get(1), refs.length]); attempt("grow value", () => [refs.grow(1, "z"), refs.get(2), refs.length]); attempt("grow past maximum", () => refs.grow(2)); attempt("get negative", () => refs.get(-1)); attempt("get missing", () => refs.get()); attempt("get out of range", () => refs.get(5)); attempt("set out of range", () => refs.set(9, 1)); const funcs = new WebAssembly.Table({ initial: 1, element: "anyfunc" }); attempt("set arrow", () => funcs.set(0, () => 1)); attempt("set number", () => funcs.set(0, 7)); attempt("grow arrow", () => funcs.grow(1, () => 1)); attempt("grow negative", () => funcs.grow(-1)); console.log(String(funcs), funcs instanceof WebAssembly.Table); console.log(funcs); // @ts-ignore: called without new, as Node reports attempt("Table()", () => WebAssembly.Table({ initial: 1, element: "anyfunc" })); // ── WebAssembly.Module's static functions ──────────────────────────────── console.log("Module"); const module = new WebAssembly.Module(jsapi); console.log(JSON.stringify(WebAssembly.Module.imports(module))); console.log(JSON.stringify(WebAssembly.Module.exports(module))); const firstImport = WebAssembly.Module.imports(module)[0]; console.log(Object.keys(firstImport).join(","), Array.isArray(WebAssembly.Module.imports(module))); firstImport.module = "changed"; console.log("fresh", WebAssembly.Module.imports(module)[0].module); const dylink = WebAssembly.Module.customSections(module, "dylink.0"); console.log( "dylink.0", dylink.length, dylink.map((b) => `${show(b)} ${[...new Uint8Array(b)].join(" ")}`).join(" | "), ); console.log("meta", new TextDecoder().decode(WebAssembly.Module.customSections(module, "meta")[0])); console.log("empty name", WebAssembly.Module.customSections(module, "").length); console.log("missing", WebAssembly.Module.customSections(module, "nope").length); console.log("toString", WebAssembly.Module.customSections(module, { toString: () => "meta" }).length); console.log("null", WebAssembly.Module.customSections(module, null).length); const copy = WebAssembly.Module.customSections(module, "meta")[0]; new Uint8Array(copy)[0] = 0; console.log( "copies", new Uint8Array(WebAssembly.Module.customSections(module, "meta")[0])[0], copy === WebAssembly.Module.customSections(module, "meta")[0], ); attempt("customSections missing name", () => WebAssembly.Module.customSections(module)); attempt("customSections symbol", () => WebAssembly.Module.customSections(module, Symbol("x"))); attempt("customSections number", () => WebAssembly.Module.customSections(1, "x")); attempt("imports object", () => WebAssembly.Module.imports({})); attempt("exports missing", () => WebAssembly.Module.exports()); attempt("imports stray", () => WebAssembly.Module.imports(module, 1).length); console.log("own", JSON.stringify(WebAssembly.Module.imports(new WebAssembly.Module(own)))); console.log("own", JSON.stringify(WebAssembly.Module.exports(new WebAssembly.Module(own)))); // @ts-ignore: called without new, as Node reports attempt("Module()", () => WebAssembly.Module(jsapi)); // @ts-ignore: called without new, as Node reports attempt("Instance()", () => WebAssembly.Instance(module, {})); // ── constructed objects as imports ──────────────────────────────────────── console.log("imports"); const logged = []; const memory = new WebAssembly.Memory({ initial: 1, maximum: 4 }); const table = new WebAssembly.Table({ initial: 2, maximum: 8, element: "anyfunc" }); const counter = new WebAssembly.Global({ value: "i32", mutable: true }, 10); const big = new WebAssembly.Global({ value: "i64", mutable: true }, 1n); const ref = new WebAssembly.Global({ value: "externref", mutable: true }, "first"); const env = { log: (v) => logged.push(v), memory, table, counter, scale: 2.5, big, ref }; const instance = new WebAssembly.Instance(module, { env }); const x = instance.exports; console.log( "identity", x.memory === memory, x.table === table, x.counter === counter, x.alsoCounter === counter, ); attempt("bump", () => [x.bump(), x.bump(), counter.value]); counter.value = 100; attempt("bump after set", () => x.bump()); attempt("scaled", () => x.scaled(4)); x.bigAdd(41n); console.log("big", show(big.value)); attempt("getRef", () => x.getRef()); x.setRef({ hello: "world" }); console.log("ref", JSON.stringify(ref.value)); ref.value = 42; attempt("getRef after set", () => x.getRef()); attempt("apply double", () => x.apply(0, 21)); attempt("apply square", () => x.apply(1, 12)); console.log("table get", table.get(0) === x.double); // A function the module does not export, reached through the table. const square = table.get(1); console.log("unexported", square(5), square === table.get(1), square.name, square.length, typeof square); x.store(3, 99); console.log("memory", new Uint8Array(memory.buffer)[3], x.load(3)); new Uint8Array(memory.buffer)[4] = 55; console.log("memory from js", x.load(4)); attempt("grow in wasm", () => [x.grow(1), x.size(), memory.buffer.byteLength]); attempt("grow from js", () => [memory.grow(1), x.size(), memory.buffer.byteLength]); attempt("table grow", () => [table.grow(2), x.tableSize(), table.length]); const global = new WebAssembly.Global({ value: "anyfunc", mutable: true }, x.double); console.log("funcref global", global.value === x.double); global.value = null; console.log("funcref null", global.value); table.set(1, x.double); attempt("apply after set", () => x.apply(1, 8)); x.say(5); console.log("logged", logged.join(",")); // Another instance's exports as imports, and the same objects again. const provider = new WebAssembly.Instance(new WebAssembly.Module(own)).exports; console.log("own identity", provider.mem === provider.mem2, provider.g === provider.g2, provider.mem instanceof WebAssembly.Memory); const second = new WebAssembly.Instance(module, { env: { ...env, memory: provider.mem, table: provider.tab, counter: provider.g }, }).exports; console.log("re-exported", second.memory === provider.mem, second.table === provider.tab, second.counter === provider.g); attempt("bump provider global", () => [second.bump(), provider.g.value]); attempt("apply provider table", () => second.apply(1, 3)); // ── link errors ─────────────────────────────────────────────────────────── console.log("link errors"); const link = (label, overrides) => attempt(label, () => new WebAssembly.Instance(module, { env: { ...env, ...overrides } }) && "linked"); link("memory too small", { memory: new WebAssembly.Memory({ initial: 0, maximum: 4 }) }); link("memory without maximum", { memory: new WebAssembly.Memory({ initial: 1 }) }); link("memory maximum too large", { memory: new WebAssembly.Memory({ initial: 1, maximum: 5 }) }); link("memory larger initial", { memory: new WebAssembly.Memory({ initial: 3, maximum: 4 }) }); link("memory number", { memory: 5 }); link("table too small", { table: new WebAssembly.Table({ initial: 1, maximum: 8, element: "anyfunc" }) }); link("table without maximum", { table: new WebAssembly.Table({ initial: 2, element: "anyfunc" }) }); link("table maximum too large", { table: new WebAssembly.Table({ initial: 2, maximum: 12, element: "anyfunc" }) }); link("table externref", { table: new WebAssembly.Table({ initial: 2, maximum: 8, element: "externref" }) }); link("table small externref", { table: new WebAssembly.Table({ initial: 1, maximum: 8, element: "externref" }) }); link("table number", { table: 3 }); link("global immutable", { counter: new WebAssembly.Global({ value: "i32" }, 1) }); link("global type", { counter: new WebAssembly.Global({ value: "i64", mutable: true }, 1n) }); link("global both", { counter: new WebAssembly.Global({ value: "f64" }, 1) }); link("global number", { counter: 1 }); link("immutable global object", { scale: new WebAssembly.Global({ value: "f64" }, 4) }); link("immutable global mutable", { scale: new WebAssembly.Global({ value: "f64", mutable: true }, 4) }); link("immutable global bigint", { scale: 4n }); link("i64 number", { big: 4 }); link("externref anything", { ref: new WebAssembly.Global({ value: "externref", mutable: true }, null) });