From 23470680342255ae9bd02ece1bbadc87db971e98 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Thu, 10 Jul 2025 17:45:41 -0700 Subject: [PATCH 1/6] test: wpt for Wasm jsapi including new ESM Integration tests --- test/common/wpt.js | 2 +- test/common/wpt/worker.js | 16 ++- .../esm-integration/exports.tentative.any.js | 36 ++++++ ...bal-exports-live-bindings.tentative.any.js | 46 ++++++++ .../global-exports.tentative.any.js | 111 ++++++++++++++++++ .../js-wasm-cycle.tentative.any.js | 7 ++ .../mutable-global-sharing.tentative.any.js | 78 ++++++++++++ .../namespace-instance.tentative.any.js | 52 ++++++++ .../reserved-import-names.tentative.any.js | 41 +++++++ .../resolve-export.tentative.any.js | 9 ++ .../jsapi/esm-integration/resources/dep.wasm | Bin 0 -> 529 bytes .../esm-integration/resources/exports.wasm | Bin 0 -> 226 bytes .../esm-integration/resources/globals.js | 29 +++++ .../esm-integration/resources/globals.wasm | Bin 0 -> 2973 bytes .../invalid-export-name-wasm-js.wasm | Bin 0 -> 64 bytes .../resources/invalid-export-name.wasm | Bin 0 -> 61 bytes .../resources/invalid-import-module.wasm | Bin 0 -> 94 bytes .../invalid-import-name-wasm-js.wasm | Bin 0 -> 94 bytes .../resources/invalid-import-name.wasm | Bin 0 -> 91 bytes .../resources/js-string-builtins.wasm | Bin 0 -> 401 bytes .../resources/js-wasm-cycle.js | 17 +++ .../resources/js-wasm-cycle.wasm | Bin 0 -> 101 bytes .../jsapi/esm-integration/resources/log.js | 1 + .../resources/mutable-global-export.wasm | Bin 0 -> 378 bytes .../resources/mutable-global-reexport.wasm | Bin 0 -> 428 bytes .../resources/resolve-export.js | 1 + .../resources/resolve-export.wasm | Bin 0 -> 8 bytes .../resources/wasm-export-to-wasm.wasm | Bin 0 -> 45 bytes .../resources/wasm-import-from-wasm.wasm | Bin 0 -> 75 bytes ...rce-phase-string-builtins.tentative.any.js | 39 ++++++ .../source-phase.tentative.any.js | 42 +++++++ .../string-builtins.tentative.any.js | 12 ++ .../esm-integration/v128-tdz.tentative.any.js | 11 ++ .../wasm-import-wasm-export.tentative.any.js | 14 +++ test/fixtures/wpt/wasm/resources/load_wasm.js | 12 ++ test/wpt/status/wasm/jsapi.json | 56 +++++++++ test/wpt/test-wasm-jsapi.mjs | 21 ++++ 37 files changed, 649 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/exports.tentative.any.js create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/global-exports-live-bindings.tentative.any.js create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/global-exports.tentative.any.js create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/js-wasm-cycle.tentative.any.js create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/mutable-global-sharing.tentative.any.js create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/namespace-instance.tentative.any.js create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/reserved-import-names.tentative.any.js create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/resolve-export.tentative.any.js create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/resources/dep.wasm create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/resources/exports.wasm create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/resources/globals.js create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/resources/globals.wasm create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/resources/invalid-export-name-wasm-js.wasm create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/resources/invalid-export-name.wasm create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/resources/invalid-import-module.wasm create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/resources/invalid-import-name-wasm-js.wasm create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/resources/invalid-import-name.wasm create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/resources/js-string-builtins.wasm create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/resources/js-wasm-cycle.js create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/resources/js-wasm-cycle.wasm create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/resources/log.js create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/resources/mutable-global-export.wasm create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/resources/mutable-global-reexport.wasm create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/resources/resolve-export.js create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/resources/resolve-export.wasm create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/resources/wasm-export-to-wasm.wasm create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/resources/wasm-import-from-wasm.wasm create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/source-phase-string-builtins.tentative.any.js create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/source-phase.tentative.any.js create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/string-builtins.tentative.any.js create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/v128-tdz.tentative.any.js create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/wasm-import-wasm-export.tentative.any.js create mode 100644 test/fixtures/wpt/wasm/resources/load_wasm.js create mode 100644 test/wpt/status/wasm/jsapi.json create mode 100644 test/wpt/test-wasm-jsapi.mjs diff --git a/test/common/wpt.js b/test/common/wpt.js index 8a0e4bea2ec568..6f776351435f1a 100644 --- a/test/common/wpt.js +++ b/test/common/wpt.js @@ -922,7 +922,7 @@ class WPTRunner { console.log(test.stack); } const command = `${process.execPath} ${process.execArgv}` + - ` ${require.main.filename} '${spec.filename}${spec.variant}'`; + ` ${require.main?.filename} '${spec.filename}${spec.variant}'`; console.log(`Command: ${command}\n`); reportResult?.addSubtest(test.name, 'FAIL', test.message); diff --git a/test/common/wpt/worker.js b/test/common/wpt/worker.js index 855ec7e91c394b..30585ecbe57f73 100644 --- a/test/common/wpt/worker.js +++ b/test/common/wpt/worker.js @@ -1,6 +1,10 @@ 'use strict'; -const { runInNewContext, runInThisContext } = require('vm'); +const { + runInNewContext, + runInThisContext, + constants: { USE_MAIN_CONTEXT_DEFAULT_LOADER }, +} = require('vm'); const { setFlagsFromString } = require('v8'); const { parentPort, workerData } = require('worker_threads'); @@ -28,11 +32,14 @@ globalThis.fetch = function fetch(file) { }; if (workerData.initScript) { - runInThisContext(workerData.initScript); + runInThisContext(workerData.initScript, { + importModuleDynamically: USE_MAIN_CONTEXT_DEFAULT_LOADER, + }); } runInThisContext(workerData.harness.code, { filename: workerData.harness.filename, + importModuleDynamically: USE_MAIN_CONTEXT_DEFAULT_LOADER, }); // eslint-disable-next-line no-undef @@ -66,5 +73,8 @@ add_completion_callback((_, status) => { }); for (const scriptToRun of workerData.scriptsToRun) { - runInThisContext(scriptToRun.code, { filename: scriptToRun.filename }); + runInThisContext(scriptToRun.code, { + filename: scriptToRun.filename, + importModuleDynamically: USE_MAIN_CONTEXT_DEFAULT_LOADER, + }); } diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/exports.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/esm-integration/exports.tentative.any.js new file mode 100644 index 00000000000000..9feaa283aaed26 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/esm-integration/exports.tentative.any.js @@ -0,0 +1,36 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm + +"use strict"; + +promise_test(async () => { + const mod = await import("./resources/exports.wasm"); + + assert_array_equals(Object.getOwnPropertyNames(mod).sort(), [ + "a\u200Bb\u0300c", + "func", + "glob", + "mem", + "tab", + "value with spaces", + "🎯test-func!", + ]); + assert_true(mod.func instanceof Function); + assert_true(mod.mem instanceof WebAssembly.Memory); + assert_true(mod.tab instanceof WebAssembly.Table); + + assert_false(mod.glob instanceof WebAssembly.Global); + assert_equals(typeof mod.glob, "number"); + + assert_throws_js(TypeError, () => { + mod.func = 2; + }); + + assert_equals(typeof mod["value with spaces"], "number"); + assert_equals(mod["value with spaces"], 123); + + assert_true(mod["🎯test-func!"] instanceof Function); + assert_equals(mod["🎯test-func!"](), 456); + + assert_equals(typeof mod["a\u200Bb\u0300c"], "number"); + assert_equals(mod["a\u200Bb\u0300c"], 789); +}, "Exported names from a WebAssembly module"); diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/global-exports-live-bindings.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/esm-integration/global-exports-live-bindings.tentative.any.js new file mode 100644 index 00000000000000..d80c30943c04c7 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/esm-integration/global-exports-live-bindings.tentative.any.js @@ -0,0 +1,46 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm + +promise_test(async () => { + const wasmExports = await import("./resources/globals.wasm"); + + wasmExports.setLocalMutI32(555); + assert_equals(wasmExports.getLocalMutI32(), 555); + assert_equals(wasmExports.localMutI32, 555); + + wasmExports.setLocalMutI64(444n); + assert_equals(wasmExports.getLocalMutI64(), 444n); + assert_equals(wasmExports.localMutI64, 444n); + + wasmExports.setLocalMutF32(3.33); + assert_equals(Math.round(wasmExports.getLocalMutF32() * 100) / 100, 3.33); + assert_equals(Math.round(wasmExports.localMutF32 * 100) / 100, 3.33); + + wasmExports.setLocalMutF64(2.22); + assert_equals(wasmExports.getLocalMutF64(), 2.22); + assert_equals(wasmExports.localMutF64, 2.22); + + const anotherTestObj = { another: "test object" }; + wasmExports.setLocalMutExternref(anotherTestObj); + assert_equals(wasmExports.getLocalMutExternref(), anotherTestObj); + assert_equals(wasmExports.localMutExternref, anotherTestObj); +}, "Local mutable global exports should be live bindings"); + +promise_test(async () => { + const wasmExports = await import("./resources/globals.wasm"); + + wasmExports.setDepMutI32(3001); + assert_equals(wasmExports.getDepMutI32(), 3001); + assert_equals(wasmExports.depMutI32, 3001); + + wasmExports.setDepMutI64(30000000001n); + assert_equals(wasmExports.getDepMutI64(), 30000000001n); + assert_equals(wasmExports.depMutI64, 30000000001n); + + wasmExports.setDepMutF32(30.01); + assert_equals(Math.round(wasmExports.getDepMutF32() * 100) / 100, 30.01); + assert_equals(Math.round(wasmExports.depMutF32 * 100) / 100, 30.01); + + wasmExports.setDepMutF64(300.0001); + assert_equals(wasmExports.getDepMutF64(), 300.0001); + assert_equals(wasmExports.depMutF64, 300.0001); +}, "Dep module mutable global exports should be live bindings"); diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/global-exports.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/esm-integration/global-exports.tentative.any.js new file mode 100644 index 00000000000000..51ebcaf95a2ad9 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/esm-integration/global-exports.tentative.any.js @@ -0,0 +1,111 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm + +promise_test(async () => { + const wasmModule = await import("./resources/globals.wasm"); + + assert_equals(wasmModule.importedI32, 42); + assert_equals(wasmModule.importedI64, 9223372036854775807n); + assert_equals(Math.round(wasmModule.importedF32 * 100000) / 100000, 3.14159); + assert_equals(wasmModule.importedF64, 3.141592653589793); + assert_not_equals(wasmModule.importedExternref, null); + assert_equals(wasmModule.importedNullExternref, null); +}, "WebAssembly module global values should be unwrapped when importing in ESM integration"); + +promise_test(async () => { + const wasmModule = await import("./resources/globals.wasm"); + + assert_equals(wasmModule.importedMutI32, 100); + assert_equals(wasmModule.importedMutI64, 200n); + assert_equals( + Math.round(wasmModule.importedMutF32 * 100000) / 100000, + 2.71828 + ); + assert_equals(wasmModule.importedMutF64, 2.718281828459045); + assert_not_equals(wasmModule.importedMutExternref, null); + assert_equals(wasmModule.importedMutExternref.mutable, "global"); +}, "WebAssembly mutable global values should be unwrapped when importing in ESM integration"); + +promise_test(async () => { + const wasmModule = await import("./resources/globals.wasm"); + + assert_equals(wasmModule["🚀localI32"], 42); + assert_equals(wasmModule.localMutI32, 100); + assert_equals(wasmModule.localI64, 9223372036854775807n); + assert_equals(wasmModule.localMutI64, 200n); + assert_equals(Math.round(wasmModule.localF32 * 100000) / 100000, 3.14159); + assert_equals(Math.round(wasmModule.localMutF32 * 100000) / 100000, 2.71828); + assert_equals(wasmModule.localF64, 2.718281828459045); + assert_equals(wasmModule.localMutF64, 3.141592653589793); +}, "WebAssembly local global values should be unwrapped when exporting in ESM integration"); + +promise_test(async () => { + const wasmModule = await import("./resources/globals.wasm"); + + assert_equals(wasmModule.depI32, 1001); + assert_equals(wasmModule.depMutI32, 2001); + assert_equals(wasmModule.depI64, 10000000001n); + assert_equals(wasmModule.depMutI64, 20000000001n); + assert_equals(Math.round(wasmModule.depF32 * 100) / 100, 10.01); + assert_equals(Math.round(wasmModule.depMutF32 * 100) / 100, 20.01); + assert_equals(wasmModule.depF64, 100.0001); + assert_equals(wasmModule.depMutF64, 200.0001); +}, "WebAssembly module globals from imported WebAssembly modules should be unwrapped"); + +promise_test(async () => { + const wasmModule = await import("./resources/globals.wasm"); + + assert_equals(wasmModule.importedI32, 42); + assert_equals(wasmModule.importedMutI32, 100); + assert_equals(wasmModule.importedI64, 9223372036854775807n); + assert_equals(wasmModule.importedMutI64, 200n); + assert_equals(Math.round(wasmModule.importedF32 * 100000) / 100000, 3.14159); + assert_equals( + Math.round(wasmModule.importedMutF32 * 100000) / 100000, + 2.71828 + ); + assert_equals(wasmModule.importedF64, 3.141592653589793); + assert_equals(wasmModule.importedMutF64, 2.718281828459045); + assert_equals(wasmModule.importedExternref !== null, true); + assert_equals(wasmModule.importedMutExternref !== null, true); + assert_equals(wasmModule.importedNullExternref, null); + + assert_equals(wasmModule["🚀localI32"], 42); + assert_equals(wasmModule.localMutI32, 100); + assert_equals(wasmModule.localI64, 9223372036854775807n); + assert_equals(wasmModule.localMutI64, 200n); + assert_equals(Math.round(wasmModule.localF32 * 100000) / 100000, 3.14159); + assert_equals(Math.round(wasmModule.localMutF32 * 100000) / 100000, 2.71828); + assert_equals(wasmModule.localF64, 2.718281828459045); + assert_equals(wasmModule.localMutF64, 3.141592653589793); + + assert_equals(wasmModule.getImportedMutI32(), 100); + assert_equals(wasmModule.getImportedMutI64(), 200n); + assert_equals( + Math.round(wasmModule.getImportedMutF32() * 100000) / 100000, + 2.71828 + ); + assert_equals(wasmModule.getImportedMutF64(), 2.718281828459045); + assert_equals(wasmModule.getImportedMutExternref() !== null, true); + + assert_equals(wasmModule.getLocalMutI32(), 100); + assert_equals(wasmModule.getLocalMutI64(), 200n); + assert_equals( + Math.round(wasmModule.getLocalMutF32() * 100000) / 100000, + 2.71828 + ); + assert_equals(wasmModule.getLocalMutF64(), 3.141592653589793); + assert_equals(wasmModule.getLocalMutExternref(), null); + + assert_equals(wasmModule.depI32, 1001); + assert_equals(wasmModule.depMutI32, 2001); + assert_equals(wasmModule.getDepMutI32(), 2001); + assert_equals(wasmModule.depI64, 10000000001n); + assert_equals(wasmModule.depMutI64, 20000000001n); + assert_equals(wasmModule.getDepMutI64(), 20000000001n); + assert_equals(Math.round(wasmModule.depF32 * 100) / 100, 10.01); + assert_equals(Math.round(wasmModule.depMutF32 * 100) / 100, 20.01); + assert_equals(Math.round(wasmModule.getDepMutF32() * 100) / 100, 20.01); + assert_equals(wasmModule.depF64, 100.0001); + assert_equals(wasmModule.depMutF64, 200.0001); + assert_equals(wasmModule.getDepMutF64(), 200.0001); +}, "WebAssembly should properly handle all global types"); diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/js-wasm-cycle.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/esm-integration/js-wasm-cycle.tentative.any.js new file mode 100644 index 00000000000000..ce71a33a24b043 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/esm-integration/js-wasm-cycle.tentative.any.js @@ -0,0 +1,7 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm + +promise_test(async () => { + const { f } = await import("./resources/js-wasm-cycle.js"); + + assert_equals(f(), 24); +}, "Check bindings in JavaScript and WebAssembly cycle (JS higher)"); diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/mutable-global-sharing.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/esm-integration/mutable-global-sharing.tentative.any.js new file mode 100644 index 00000000000000..c636da929b3b2f --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/esm-integration/mutable-global-sharing.tentative.any.js @@ -0,0 +1,78 @@ +promise_test(async () => { + const exporterModule = await import("./resources/mutable-global-export.wasm"); + const reexporterModule = await import( + "./resources/mutable-global-reexport.wasm" + ); + + assert_equals(exporterModule.mutableValue, 100); + assert_equals(reexporterModule.reexportedMutableValue, 100); +}, "WebAssembly modules should export shared mutable globals with correct initial values"); + +promise_test(async () => { + const exporterModule = await import("./resources/mutable-global-export.wasm"); + const reexporterModule = await import( + "./resources/mutable-global-reexport.wasm" + ); + + exporterModule.setGlobal(500); + + assert_equals(exporterModule.getGlobal(), 500, "exporter should see 500"); + assert_equals(reexporterModule.getImportedGlobal(), 500); + + reexporterModule.setImportedGlobal(600); + + assert_equals(exporterModule.getGlobal(), 600); + assert_equals(reexporterModule.getImportedGlobal(), 600); + + exporterModule.setGlobal(700); + + assert_equals(exporterModule.getGlobal(), 700); + assert_equals(reexporterModule.getImportedGlobal(), 700); +}, "Wasm-to-Wasm mutable global sharing is live"); + +promise_test(async () => { + const module1 = await import("./resources/mutable-global-export.wasm"); + const module2 = await import("./resources/mutable-global-export.wasm"); + + assert_equals(module1, module2); + + module1.setGlobal(800); + assert_equals(module1.getGlobal(), 800, "module1 should see its own change"); + assert_equals(module2.getGlobal(), 800); +}, "Multiple JavaScript imports return the same WebAssembly module instance"); + +promise_test(async () => { + const exporterModule = await import("./resources/mutable-global-export.wasm"); + const reexporterModule = await import( + "./resources/mutable-global-reexport.wasm" + ); + + assert_equals(exporterModule.getV128Lane(0), 1); + assert_equals(exporterModule.getV128Lane(1), 2); + assert_equals(exporterModule.getV128Lane(2), 3); + assert_equals(exporterModule.getV128Lane(3), 4); + + assert_equals(reexporterModule.getImportedV128Lane(0), 1); + assert_equals(reexporterModule.getImportedV128Lane(1), 2); + assert_equals(reexporterModule.getImportedV128Lane(2), 3); + assert_equals(reexporterModule.getImportedV128Lane(3), 4); +}, "v128 globals should work correctly in WebAssembly-to-WebAssembly imports"); + +promise_test(async () => { + const exporterModule = await import("./resources/mutable-global-export.wasm"); + const reexporterModule = await import( + "./resources/mutable-global-reexport.wasm" + ); + + exporterModule.setV128Global(10, 20, 30, 40); + + assert_equals(exporterModule.getV128Lane(0), 10); + assert_equals(exporterModule.getV128Lane(1), 20); + assert_equals(exporterModule.getV128Lane(2), 30); + assert_equals(exporterModule.getV128Lane(3), 40); + + assert_equals(reexporterModule.getImportedV128Lane(0), 10); + assert_equals(reexporterModule.getImportedV128Lane(1), 20); + assert_equals(reexporterModule.getImportedV128Lane(2), 30); + assert_equals(reexporterModule.getImportedV128Lane(3), 40); +}, "v128 global mutations should work correctly between WebAssembly modules"); diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/namespace-instance.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/esm-integration/namespace-instance.tentative.any.js new file mode 100644 index 00000000000000..e5e4e0721034d8 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/esm-integration/namespace-instance.tentative.any.js @@ -0,0 +1,52 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm + +promise_test(async () => { + const wasmNamespace = await import("./resources/mutable-global-export.wasm"); + const instance = WebAssembly.namespaceInstance(wasmNamespace); + + assert_true(instance instanceof WebAssembly.Instance); + + wasmNamespace.setGlobal(999); + assert_equals(instance.exports.getGlobal(), 999); + + instance.exports.setGlobal(888); + assert_equals(wasmNamespace.getGlobal(), 888); +}, "WebAssembly.namespaceInstance() should return the underlying instance with shared state"); + +promise_test(async () => { + assert_throws_js(TypeError, () => WebAssembly.namespaceInstance({})); + assert_throws_js(TypeError, () => WebAssembly.namespaceInstance(null)); + assert_throws_js(TypeError, () => WebAssembly.namespaceInstance(undefined)); + assert_throws_js(TypeError, () => WebAssembly.namespaceInstance(42)); + assert_throws_js(TypeError, () => + WebAssembly.namespaceInstance("not a namespace") + ); + assert_throws_js(TypeError, () => WebAssembly.namespaceInstance([])); + assert_throws_js(TypeError, () => + WebAssembly.namespaceInstance(function () {}) + ); + + const jsModule = await import("./resources/globals.js"); + assert_throws_js(TypeError, () => WebAssembly.namespaceInstance(jsModule)); +}, "WebAssembly.namespaceInstance() should throw TypeError for non-WebAssembly namespaces"); + +promise_test(async () => { + const exportsModule = await import("./resources/exports.wasm"); + const globalsModule = await import("./resources/globals.wasm"); + + const exportsInstance = WebAssembly.namespaceInstance(exportsModule); + const globalsInstance = WebAssembly.namespaceInstance(globalsModule); + + assert_not_equals(exportsInstance, globalsInstance); + assert_true(exportsInstance.exports.func instanceof Function); + assert_true(globalsInstance.exports.getLocalMutI32 instanceof Function); + + globalsModule.setLocalMutI32(12345); + assert_equals(globalsInstance.exports.getLocalMutI32(), 12345); + + globalsInstance.exports.setLocalMutI32(54321); + assert_equals(globalsModule.getLocalMutI32(), 54321); + + const exportsInstance2 = WebAssembly.namespaceInstance(exportsModule); + assert_equals(exportsInstance, exportsInstance2); +}, "WebAssembly.namespaceInstance() should work correctly with multiple modules"); diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/reserved-import-names.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/esm-integration/reserved-import-names.tentative.any.js new file mode 100644 index 00000000000000..aa17735b0206ff --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/esm-integration/reserved-import-names.tentative.any.js @@ -0,0 +1,41 @@ +// Test that wasm: and wasm-js: reserved cases should cause WebAssembly.LinkError + +promise_test(async (t) => { + await promise_rejects_js( + t, + WebAssembly.LinkError, + import("./resources/invalid-import-name.wasm") + ); +}, "wasm: reserved import names should cause WebAssembly.LinkError"); + +promise_test(async (t) => { + await promise_rejects_js( + t, + WebAssembly.LinkError, + import("./resources/invalid-import-name-wasm-js.wasm") + ); +}, "wasm-js: reserved import names should cause WebAssembly.LinkError"); + +promise_test(async (t) => { + await promise_rejects_js( + t, + WebAssembly.LinkError, + import("./resources/invalid-export-name.wasm") + ); +}, "wasm: reserved export names should cause WebAssembly.LinkError"); + +promise_test(async (t) => { + await promise_rejects_js( + t, + WebAssembly.LinkError, + import("./resources/invalid-export-name-wasm-js.wasm") + ); +}, "wasm-js: reserved export names should cause WebAssembly.LinkError"); + +promise_test(async (t) => { + await promise_rejects_js( + t, + WebAssembly.LinkError, + import("./resources/invalid-import-module.wasm") + ); +}, "wasm-js: reserved module names should cause WebAssembly.LinkError"); diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/resolve-export.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/esm-integration/resolve-export.tentative.any.js new file mode 100644 index 00000000000000..86325d11b0bad5 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/esm-integration/resolve-export.tentative.any.js @@ -0,0 +1,9 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm + +promise_test(async (t) => { + await promise_rejects_js( + t, + SyntaxError, + import("./resources/resolve-export.js") + ); +}, "ResolveExport on invalid re-export from WebAssembly"); diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/dep.wasm b/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/dep.wasm new file mode 100644 index 0000000000000000000000000000000000000000..ad9abfaa66af53b33aca06422ad2850020731a9c GIT binary patch literal 529 zcmZQbEY4+QU|?XBWJ_RRtOw#cAg%@C8X(RG;%c@Uo_Yqym+aj2jE)!ix$7958c(cW zpvqmx=+rpj{((~r+_enO-!v2)xoa7nt3?(#a@R1pY|_hH%*YVwz+J=W5-Gm@A_GIF z19v{dg?#ROMj%nm@Rx@RXsrkk%K@lkBt|^M#!=Ar&g4t z7UdPCra?{S5Jrd~)Nyi^85&tYm2mNaIS5&9hRuvDd5O8H?5i1h7!Y1yMD+m^k_S-T g&w_A1E2`_+5RPX@bvp;T(>bxZoD0d}+-U9w0Ac`}+5i9m literal 0 HcmV?d00001 diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/exports.wasm b/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/exports.wasm new file mode 100644 index 0000000000000000000000000000000000000000..7382eff0356128f58ee446e15be51f662e406590 GIT binary patch literal 226 zcmXAjF>b;@5Jms&tZjmoNQMiTf(9u)Ep@7#z^u*MfV_a@H7TKwlrA+#;2J471>ykd zY1lQ*`_gFUzkzBD0ib~9paZi2q@-}vC&3=t;z|qs<$~q zmhGkhlD(>1oqnzCGHrIM&<)eu|LNy!bz|TC&Ir^~*W-77ITkGXuOh(ghS>3sV|>bu uGMa|sSx9|tw(C{q4}*|8W$4pA5RIMH%PH|xb>^*EXB984EvK1YRQdt#qBtG^ literal 0 HcmV?d00001 diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/globals.js b/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/globals.js new file mode 100644 index 00000000000000..fabf23faf38ae3 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/globals.js @@ -0,0 +1,29 @@ +const i32_value = 42; +export { i32_value as "🚀i32_value" }; +export const i64_value = 9223372036854775807n; +export const f32_value = 3.14159; +export const f64_value = 3.141592653589793; + +export const i32_mut_value = new WebAssembly.Global( + { value: "i32", mutable: true }, + 100 +); +export const i64_mut_value = new WebAssembly.Global( + { value: "i64", mutable: true }, + 200n +); +export const f32_mut_value = new WebAssembly.Global( + { value: "f32", mutable: true }, + 2.71828 +); +export const f64_mut_value = new WebAssembly.Global( + { value: "f64", mutable: true }, + 2.718281828459045 +); + +export const externref_value = { hello: "world" }; +export const externref_mut_value = new WebAssembly.Global( + { value: "externref", mutable: true }, + { mutable: "global" } +); +export const null_externref_value = null; diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/globals.wasm b/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/globals.wasm new file mode 100644 index 0000000000000000000000000000000000000000..c5e6dd9f1ded20ddec7f1a5fa84b27bd063d0827 GIT binary patch literal 2973 zcmZ`*JCEB&5Z)CjQ4}ANDC%)Kotz+P62NDGB-jNw+m`?b#4d~gDeq*PXDE=4#+@!n zp-1`@>C@y_q)wYE@GnS}!j<9DK#F8WOYTcL0Xw_%eLM55hO?r`+NIi; zWuqDOu*Bd50=By){1p00;Eft|5gydBVNo2~-GU(Cl_0Er1o2SK0>pJ|3j*6NL0AVa z>=Y86Cd(|4(8bF?%U?`pDBX3o%R;K0`?pV0=$vGO(len z+NF}f#_d))3IIETP|6`vcU*u)RSqN?q)|BH>ZvcRDu7C)5FB zpsAv<(AZ9md_+E_Y%g|amPrFV2-pi!+UmPjz2U*qbi=#m6#?Ibml&@qQNjDNQ z$`>+@`pWej?75wCBgc`{$B;{I<2V{AO?|Ydr>(z?d&DhY4(C&7h8Fkc@DB`-ip0au zs>Y{@Dfr7cs;WF7HHn9ZuJLJ754d7hzR2tz$M6&!Z}d5?{xfncc+KExT?ZG%(LIKy zi7(JJX>L(CS&O6Kv>BQvfk4xw<6%0kAGXWv9O~NJw+d?aJhm*<`z4bm1DQ#agMtCs ap~QfDSTbocl9@C)YO>^9c-#?c9Q+^eI*}y+ literal 0 HcmV?d00001 diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/invalid-export-name-wasm-js.wasm b/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/invalid-export-name-wasm-js.wasm new file mode 100644 index 0000000000000000000000000000000000000000..a6b9a7f7c5ad57d8347003d77940f6ec99dadf26 GIT binary patch literal 64 zcmZQbEY4+QU|?WmWlUgTtY>CoWMCI&t+>OW#*M7=47TYFmSOkvM@MmaWn9- SCoWMCIy{zCFW$NFfeejF|sf?YH>60vE(J@ PrZTcKGO(1S7MB13J$4H? literal 0 HcmV?d00001 diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/invalid-import-module.wasm b/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/invalid-import-module.wasm new file mode 100644 index 0000000000000000000000000000000000000000..ead151ac0c84ad5a2504c1f7752a08e14b989a0e GIT binary patch literal 94 zcmZQbEY4+QU|?WmWlUgTtY?y71GvMW#*M7=47U@l%y7yFfcGPF*2}oFhY2Y iTx^Ui3<3{zCFW$NFfcGPF*2}oKx7#h ix!4$47z7x&8Dv@V5_3}-#h4g)p-Me-3-XIfAPNDJ%oP;? literal 0 HcmV?d00001 diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/invalid-import-name.wasm b/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/invalid-import-name.wasm new file mode 100644 index 0000000000000000000000000000000000000000..3c631418294584fecbe13fc122c28dbab5670525 GIT binary patch literal 91 zcmZQbEY4+QU|?WmWlUgTtY;EsWGP84F5xK$id$vol_ln6rZ6xtGchu-b3mjR7`fOO fSr`NuxEW+w@)C1X8O4|wc%e!?a|`l|N+1dWJZlsM literal 0 HcmV?d00001 diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/js-string-builtins.wasm b/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/js-string-builtins.wasm new file mode 100644 index 0000000000000000000000000000000000000000..4c4ff4df7f62b145aaf258e6cfb4775a19a9b437 GIT binary patch literal 401 zcmZ8cK~BRk5S+C)PKni0i4U}A!~u@*1#UbbwSpC>niy(V9Qz)Ai5RFujaT6oXwiGGd+vdJkw{yfo)H+#aB<`6BGHNRU Pj^&Zk`=tJ%I!pcliXL*D literal 0 HcmV?d00001 diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/js-wasm-cycle.js b/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/js-wasm-cycle.js new file mode 100644 index 00000000000000..9f14dc04bde41f --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/js-wasm-cycle.js @@ -0,0 +1,17 @@ +function f() { + return 42; +} +export { f }; + +import { mem, tab, glob, func } from "./js-wasm-cycle.wasm"; +assert_false(glob instanceof WebAssembly.Global, "imported global should be unwrapped in ESM integration"); +assert_equals(glob, 1, "unwrapped global should have direct value"); +assert_true(mem instanceof WebAssembly.Memory); +assert_true(tab instanceof WebAssembly.Table); +assert_true(func instanceof Function); + +f = () => { + return 24; +}; + +assert_equals(func(), 42); diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/js-wasm-cycle.wasm b/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/js-wasm-cycle.wasm new file mode 100644 index 0000000000000000000000000000000000000000..77a3b86ab67528e404eef4aae4a7c76511b1f863 GIT binary patch literal 101 zcmWN{K@LSQ6a~r77$x)=z630v?nfBvd^j^*`v u>2JRb)f_njVhC04aETDkT0Ur)rG-iuf`J@e|BY$cdV!AT>Op6z$dSE}6%fk+ literal 0 HcmV?d00001 diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/log.js b/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/log.js new file mode 100644 index 00000000000000..0c4f5ed519b0fd --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/log.js @@ -0,0 +1 @@ +export function logExec() { log.push("executed"); } diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/mutable-global-export.wasm b/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/mutable-global-export.wasm new file mode 100644 index 0000000000000000000000000000000000000000..89c478b19151d0abdc0c6fdb24935df8a2d7d1e7 GIT binary patch literal 378 zcmY+Ay-veG5QJxTFNvH>3JNMLWGQ)pKtt;SXy}kqA;pel(bx$h&JR$ShvhX+Vl7Mp zD~)vD?G&?vd{Yqs&t1j_8Q5g5v4J5Q?Kx1iA4|jZ3;u&))Y`?QCTH@4$Ns3=*7>q5 z=6Sg-6vN%@>gM+FvVN_V(Wa>H%k?rZf#~`8=Ggw{=IeIiZP)TczAB)@C)Y=UDZIl3 zK1PagmLSAP7-A+8Vo5Z_I;EePF~yy0CJYzo8L$JkARS1r-K)33C*3Nq3SK*$^6?Z; t{-@Kw;bf{HqE)e9?Am+9Iy#VV!gu9f30K+)gG<4OpwR^I>s literal 0 HcmV?d00001 diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/mutable-global-reexport.wasm b/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/mutable-global-reexport.wasm new file mode 100644 index 0000000000000000000000000000000000000000..b06f94a75d3c48e753a3f5860643af3e8820a660 GIT binary patch literal 428 zcmaJ-!Ab)$6rA^xwb)g$D81-Gtk+iR$%7ysY!UQY@YJUCQH1QSwA)tjkS`ZZy1@!7 z=90`z=4CQ4%Zvcfk(!Xh1myfqj$iq_cb%tU0|2zZ2$TQ- literal 0 HcmV?d00001 diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/wasm-import-from-wasm.wasm b/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/wasm-import-from-wasm.wasm new file mode 100644 index 0000000000000000000000000000000000000000..652ff143100f832bf3e4e16c4427e17e2a651148 GIT binary patch literal 75 zcmZQbEY4+QU|?WmVN76PU{Yd~($g;ois`0S6yz6`=$7Q`g1CAhf;lHYoq>UwiIIVw Xn~@#Jaji&AW? { + const wasmModuleSource = await import.source("./resources/js-string-builtins.wasm"); + + assert_true(wasmModuleSource instanceof WebAssembly.Module); + + const instance = new WebAssembly.Instance(wasmModuleSource, {}); + + assert_equals(instance.exports.getLength("hello"), 5); + assert_equals( + instance.exports.concatStrings("hello", " world"), + "hello world" + ); + assert_equals(instance.exports.compareStrings("test", "test"), 1); + assert_equals(instance.exports.compareStrings("test", "different"), 0); + assert_equals(instance.exports.testString("hello"), 1); + assert_equals(instance.exports.testString(42), 0); +}, "String builtins should be supported in source phase imports"); + +promise_test(async () => { + const wasmModuleSource = await import.source("./resources/js-string-builtins.wasm"); + + const exports = WebAssembly.Module.exports(wasmModuleSource); + const exportNames = exports.map((exp) => exp.name); + + assert_true(exportNames.includes("getLength")); + assert_true(exportNames.includes("concatStrings")); + assert_true(exportNames.includes("compareStrings")); + assert_true(exportNames.includes("testString")); +}, "Source phase import should properly expose string builtin exports"); + +promise_test(async () => { + const wasmModuleSource = await import.source("./resources/js-string-builtins.wasm"); + + const imports = WebAssembly.Module.imports(wasmModuleSource); + + assert_equals(imports.length, 0); +}, "Source phase import should handle string builtin import reflection correctly"); diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/source-phase.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/esm-integration/source-phase.tentative.any.js new file mode 100644 index 00000000000000..59a71226530ac1 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/esm-integration/source-phase.tentative.any.js @@ -0,0 +1,42 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm + +promise_test(async () => { + const exportedNamesSource = await import.source("./resources/exports.wasm"); + + assert_true(exportedNamesSource instanceof WebAssembly.Module); + const AbstractModuleSource = Object.getPrototypeOf(WebAssembly.Module); + assert_equals(AbstractModuleSource.name, "AbstractModuleSource"); + assert_true(exportedNamesSource instanceof AbstractModuleSource); + + assert_array_equals( + WebAssembly.Module.exports(exportedNamesSource) + .map(({ name }) => name) + .sort(), + [ + "a\u200Bb\u0300c", + "func", + "glob", + "mem", + "tab", + "value with spaces", + "🎯test-func!", + ] + ); + + const wasmImportFromWasmSource = await import.source( + "./resources/wasm-import-from-wasm.wasm" + ); + + assert_true(wasmImportFromWasmSource instanceof WebAssembly.Module); + + let logged = false; + const instance = await WebAssembly.instantiate(wasmImportFromWasmSource, { + "./wasm-export-to-wasm.wasm": { + log() { + logged = true; + }, + }, + }); + instance.exports.logExec(); + assert_true(logged, "WebAssembly instance should execute imported function"); +}, "Source phase imports"); diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/string-builtins.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/esm-integration/string-builtins.tentative.any.js new file mode 100644 index 00000000000000..bac8fd92727437 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/esm-integration/string-builtins.tentative.any.js @@ -0,0 +1,12 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm + +promise_test(async () => { + const wasmModule = await import("./resources/js-string-builtins.wasm"); + + assert_equals(wasmModule.getLength("hello"), 5); + assert_equals(wasmModule.concatStrings("hello", " world"), "hello world"); + assert_equals(wasmModule.compareStrings("test", "test"), 1); + assert_equals(wasmModule.compareStrings("test", "different"), 0); + assert_equals(wasmModule.testString("hello"), 1); + assert_equals(wasmModule.testString(42), 0); +}, "String builtins should be supported in imports in ESM integration"); diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/v128-tdz.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/esm-integration/v128-tdz.tentative.any.js new file mode 100644 index 00000000000000..5d11e590148851 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/esm-integration/v128-tdz.tentative.any.js @@ -0,0 +1,11 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm + +promise_test(async () => { + const exporterModule = await import("./resources/mutable-global-export.wasm"); + const reexporterModule = await import( + "./resources/mutable-global-reexport.wasm" + ); + + assert_throws_js(ReferenceError, () => exporterModule.v128Export); + assert_throws_js(ReferenceError, () => reexporterModule.reexportedV128Export); +}, "v128 global exports should cause TDZ errors"); diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/wasm-import-wasm-export.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/esm-integration/wasm-import-wasm-export.tentative.any.js new file mode 100644 index 00000000000000..2c1a1446ee4987 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/esm-integration/wasm-import-wasm-export.tentative.any.js @@ -0,0 +1,14 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm + +promise_test(async () => { + globalThis.log = []; + + const { logExec } = await import("./resources/wasm-import-from-wasm.wasm"); + logExec(); + + assert_equals(globalThis.log.length, 1, "log should have one entry"); + assert_equals(globalThis.log[0], "executed"); + + // Clean up + delete globalThis.log; +}, "Check import and export between WebAssembly modules"); diff --git a/test/fixtures/wpt/wasm/resources/load_wasm.js b/test/fixtures/wpt/wasm/resources/load_wasm.js new file mode 100644 index 00000000000000..7f280ac032f40a --- /dev/null +++ b/test/fixtures/wpt/wasm/resources/load_wasm.js @@ -0,0 +1,12 @@ +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +function createWasmModule() { + return fetch('/wasm/incrementer.wasm') + .then(response => { + if (!response.ok) throw new Error(response.statusText); + return response.arrayBuffer(); + }) + .then(WebAssembly.compile); +} diff --git a/test/wpt/status/wasm/jsapi.json b/test/wpt/status/wasm/jsapi.json new file mode 100644 index 00000000000000..4d7f5c266dde5b --- /dev/null +++ b/test/wpt/status/wasm/jsapi.json @@ -0,0 +1,56 @@ +{ + "esm-integration/global-exports-live-bindings.tentative.any.js": { + "skip": "Live bindings unsupported pending V8 WebAssemblyModuleRecord" + }, + "esm-integration/v128-tdz.tentative.any.js": { + "skip": "v128 undefined Wasm bindings not yet supported in V8" + }, + "esm-integration/namespace-instance.tentative.any.js": { + "skip": "pending https://github.com/nodejs/node/pull/59024" + }, + "esm-integration/reserved-import-names.tentative.any.js": { + "skip": "pending https://github.com/nodejs/node/pull/59020" + }, + "esm-integration/source-phase-string-builtins.tentative.any.js": { + "skip": "pending https://github.com/nodejs/node/pull/59020" + }, + "esm-integration/string-builtins.tentative.any.js": { + "skip": "pending https://github.com/nodejs/node/pull/59020" + }, + "exception/getArg.tentative.any.js": { + "skip": "track - still tentative / unsupported" + }, + "function/call.tentative.any.js": { + "skip": "track - still tentative / unsupported" + }, + "function/constructor.tentative.any.js": { + "skip": "track - still tentative / unsupported" + }, + "function/table.tentative.any.js": { + "skip": "track - still tentative / unsupported" + }, + "function/type.tentative.any.js": { + "skip": "track - still tentative / unsupported" + }, + "global/type.tentative.any.js": { + "skip": "track - still tentative / unsupported" + }, + "idlharness.any.js": { + "skip": "track - still tentative / unsupported" + }, + "memory/constructor-types.tentative.any.js": { + "skip": "track - still tentative / unsupported" + }, + "memory/type.tentative.any.js": { + "skip": "track - still tentative / unsupported" + }, + "table/constructor-types.tentative.any.js": { + "skip": "track - still tentative / unsupported" + }, + "table/type.tentative.any.js": { + "skip": "track - still tentative / unsupported" + }, + "tag/type.tentative.any.js": { + "skip": "track - still tentative / unsupported" + } +} diff --git a/test/wpt/test-wasm-jsapi.mjs b/test/wpt/test-wasm-jsapi.mjs new file mode 100644 index 00000000000000..e4ebb9c53e7373 --- /dev/null +++ b/test/wpt/test-wasm-jsapi.mjs @@ -0,0 +1,21 @@ +// Flags: --experimental-wasm-modules +import * as fixtures from '../common/fixtures.mjs'; +import { ok } from 'node:assert'; +import { WPTRunner } from '../common/wpt.js'; + +// Verify we have Wasm SIMD support by importing a Wasm with SIMD +// since Wasm SIMD is not supported on older architectures such as IBM Power8. +let supportsSimd = false; +try { + await import(fixtures.fileURL('es-modules/globals.wasm')); + supportsSimd = true; +} catch (e) { + ok(e instanceof WebAssembly.CompileError); + ok(e.message.includes('SIMD unsupported')); +} + +if (supportsSimd) { + const runner = new WPTRunner('wasm/jsapi'); + runner.setFlags(['--experimental-wasm-modules']); + runner.runJsTests(); +} From ddb0855460c22dd0b9a7ab7660d88653f40314c4 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Mon, 14 Jul 2025 11:08:56 -0700 Subject: [PATCH 2/6] include WebAssembly/esm-integraiton#113 --- .../mutable-global-sharing.tentative.any.js | 2 ++ .../esm-integration/resources/source-phase-identity.js | 6 ++++++ .../jsapi/esm-integration/source-phase.tentative.any.js | 7 +++++++ 3 files changed, 15 insertions(+) create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/resources/source-phase-identity.js diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/mutable-global-sharing.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/esm-integration/mutable-global-sharing.tentative.any.js index c636da929b3b2f..d76c69ad5d2333 100644 --- a/test/fixtures/wpt/wasm/jsapi/esm-integration/mutable-global-sharing.tentative.any.js +++ b/test/fixtures/wpt/wasm/jsapi/esm-integration/mutable-global-sharing.tentative.any.js @@ -1,3 +1,5 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm + promise_test(async () => { const exporterModule = await import("./resources/mutable-global-export.wasm"); const reexporterModule = await import( diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/source-phase-identity.js b/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/source-phase-identity.js new file mode 100644 index 00000000000000..643d15b2f8912e --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/esm-integration/resources/source-phase-identity.js @@ -0,0 +1,6 @@ +import * as mod1 from './exports.wasm'; +import * as mod2 from './exports.wasm'; +import source mod3 from './exports.wasm'; +import source mod4 from './exports.wasm'; + +export { mod1, mod2, mod3, mod4 } diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/source-phase.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/esm-integration/source-phase.tentative.any.js index 59a71226530ac1..ad45391f7f9c9f 100644 --- a/test/fixtures/wpt/wasm/jsapi/esm-integration/source-phase.tentative.any.js +++ b/test/fixtures/wpt/wasm/jsapi/esm-integration/source-phase.tentative.any.js @@ -40,3 +40,10 @@ promise_test(async () => { instance.exports.logExec(); assert_true(logged, "WebAssembly instance should execute imported function"); }, "Source phase imports"); + +promise_test(async () => { + const { mod1, mod2, mod3, mod4 } = await import('./resources/source-phase-identity.js'); + + assert_equals(mod1, mod2); + assert_equals(mod3, mod4); +}, "Source phase identities"); From 7e36e0d67fb2a56ba88adb18a0a12f96bfa39a12 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Mon, 23 Mar 2026 12:52:27 -0700 Subject: [PATCH 3/6] latest tests --- test/fixtures/wpt/README.md | 2 +- test/fixtures/wpt/versions.json | 2 +- test/fixtures/wpt/wasm/jsapi/WEB_FEATURES.yml | 4 + test/fixtures/wpt/wasm/jsapi/assertions.js | 5 + test/fixtures/wpt/wasm/jsapi/bad-imports.js | 1 + .../wasm/jsapi/constructor/WEB_FEATURES.yml | 8 + .../wpt/wasm/jsapi/constructor/compile.any.js | 2 +- .../instantiate-bad-imports.any.js | 2 +- .../wasm/jsapi/constructor/instantiate.any.js | 2 +- .../wasm/jsapi/constructor/multi-value.any.js | 2 +- .../wasm/jsapi/constructor/toStringTag.any.js | 2 +- .../wasm/jsapi/constructor/validate.any.js | 2 +- .../jsapi/esm-integration/WEB_FEATURES.yml | 9 + .../namespace-instance.tentative.any.js | 4 + .../wpt/wasm/jsapi/exception/WEB_FEATURES.yml | 3 + .../jsapi/exception/basic.tentative.any.js | 16 +- .../exception/constructor.tentative.any.js | 2 +- .../jsapi/exception/getArg.tentative.any.js | 2 +- .../jsapi/exception/identity.tentative.any.js | 2 +- .../wasm/jsapi/exception/is.tentative.any.js | 2 +- .../jsapi/exception/toString.tentative.any.js | 2 +- .../wasm/jsapi/function/call.tentative.any.js | 2 +- .../function/constructor.tentative.any.js | 2 +- .../jsapi/function/table.tentative.any.js | 2 +- .../wasm/jsapi/function/type.tentative.any.js | 2 +- .../wpt/wasm/jsapi/functions/WEB_FEATURES.yml | 3 + .../wpt/wasm/jsapi/gc/casts.tentative.any.js | 332 +++++++++++++ .../jsapi/gc/exported-object.tentative.any.js | 190 +++++++ .../wpt/wasm/jsapi/gc/i31.tentative.any.js | 98 ++++ .../wpt/wasm/jsapi/global/WEB_FEATURES.yml | 3 + .../wpt/wasm/jsapi/global/constructor.any.js | 2 +- .../wpt/wasm/jsapi/global/toString.any.js | 2 +- .../wasm/jsapi/global/type.tentative.any.js | 2 +- .../wasm/jsapi/global/value-get-set.any.js | 11 +- .../wpt/wasm/jsapi/global/valueOf.any.js | 2 +- .../fixtures/wpt/wasm/jsapi/idlharness.any.js | 1 + .../wpt/wasm/jsapi/instance/WEB_FEATURES.yml | 3 + .../instance/constructor-bad-imports.any.js | 2 +- .../jsapi/instance/constructor-caching.any.js | 2 +- .../wasm/jsapi/instance/constructor.any.js | 2 +- .../wpt/wasm/jsapi/instance/exports.any.js | 2 +- .../wpt/wasm/jsapi/instance/toString.any.js | 2 +- .../wpt/wasm/jsapi/instanceTestFactory.js | 8 +- test/fixtures/wpt/wasm/jsapi/interface.any.js | 2 +- .../wpt/wasm/jsapi/js-string/WEB_FEATURES.yml | 3 + .../wpt/wasm/jsapi/js-string/basic.any.js | 383 ++++++++++++++ .../wpt/wasm/jsapi/js-string/constants.any.js | 61 +++ .../wpt/wasm/jsapi/js-string/imports.any.js | 26 + .../wpt/wasm/jsapi/js-string/polyfill.js | 170 +++++++ test/fixtures/wpt/wasm/jsapi/jspi/README.txt | 3 + .../jsapi/jspi/js-promise-integration.any.js | 370 ++++++++++++++ .../wpt/wasm/jsapi/jspi/notraps.any.js | 82 +++ .../wpt/wasm/jsapi/jspi/rejects.any.js | 150 ++++++ .../wasm/jsapi/jspi/testharness-additions.js | 26 + .../wpt/wasm/jsapi/memory/WEB_FEATURES.yml | 12 + .../wpt/wasm/jsapi/memory/assertions.js | 2 + .../constructor-shared.tentative.any.js | 2 +- .../memory/constructor-types.tentative.any.js | 4 +- .../wpt/wasm/jsapi/memory/constructor.any.js | 2 +- .../wpt/wasm/jsapi/memory/grow.any.js | 2 +- .../to-fixed-length-buffer-shared.any.js | 17 + .../memory/to-fixed-length-buffer.any.js | 42 ++ .../memory/to-resizable-buffer-shared.any.js | 36 ++ .../jsapi/memory/to-resizable-buffer.any.js | 72 +++ .../wasm/jsapi/memory/type.tentative.any.js | 4 +- .../wpt/wasm/jsapi/module/WEB_FEATURES.yml | 3 + .../wpt/wasm/jsapi/module/constructor.any.js | 2 +- .../wasm/jsapi/module/customSections.any.js | 2 +- .../wpt/wasm/jsapi/module/exports.any.js | 8 +- .../wpt/wasm/jsapi/module/imports.any.js | 2 +- .../module/moduleSource.tentative.any.js | 35 ++ .../wpt/wasm/jsapi/module/toString.any.js | 2 +- .../fixtures/wpt/wasm/jsapi/prototypes.any.js | 2 +- .../wpt/wasm/jsapi/table/WEB_FEATURES.yml | 3 + .../wpt/wasm/jsapi/table/assertions.js | 2 + .../table/constructor-types.tentative.any.js | 4 +- .../wpt/wasm/jsapi/table/constructor.any.js | 7 +- .../wpt/wasm/jsapi/table/get-set.any.js | 2 +- .../fixtures/wpt/wasm/jsapi/table/grow.any.js | 2 +- .../wpt/wasm/jsapi/table/length.any.js | 2 +- .../wpt/wasm/jsapi/table/toString.any.js | 2 +- .../wasm/jsapi/table/type.tentative.any.js | 2 +- .../wpt/wasm/jsapi/tag/WEB_FEATURES.yml | 3 + .../jsapi/tag/constructor.tentative.any.js | 2 +- .../wasm/jsapi/tag/toString.tentative.any.js | 2 +- .../wpt/wasm/jsapi/tag/type.tentative.any.js | 2 +- .../wpt/wasm/jsapi/wasm-module-builder.js | 466 +++++++++++++++--- 87 files changed, 2642 insertions(+), 137 deletions(-) create mode 100644 test/fixtures/wpt/wasm/jsapi/WEB_FEATURES.yml create mode 100644 test/fixtures/wpt/wasm/jsapi/constructor/WEB_FEATURES.yml create mode 100644 test/fixtures/wpt/wasm/jsapi/esm-integration/WEB_FEATURES.yml create mode 100644 test/fixtures/wpt/wasm/jsapi/exception/WEB_FEATURES.yml create mode 100644 test/fixtures/wpt/wasm/jsapi/functions/WEB_FEATURES.yml create mode 100644 test/fixtures/wpt/wasm/jsapi/gc/casts.tentative.any.js create mode 100644 test/fixtures/wpt/wasm/jsapi/gc/exported-object.tentative.any.js create mode 100644 test/fixtures/wpt/wasm/jsapi/gc/i31.tentative.any.js create mode 100644 test/fixtures/wpt/wasm/jsapi/global/WEB_FEATURES.yml create mode 100644 test/fixtures/wpt/wasm/jsapi/instance/WEB_FEATURES.yml create mode 100644 test/fixtures/wpt/wasm/jsapi/js-string/WEB_FEATURES.yml create mode 100644 test/fixtures/wpt/wasm/jsapi/js-string/basic.any.js create mode 100644 test/fixtures/wpt/wasm/jsapi/js-string/constants.any.js create mode 100644 test/fixtures/wpt/wasm/jsapi/js-string/imports.any.js create mode 100644 test/fixtures/wpt/wasm/jsapi/js-string/polyfill.js create mode 100644 test/fixtures/wpt/wasm/jsapi/jspi/README.txt create mode 100644 test/fixtures/wpt/wasm/jsapi/jspi/js-promise-integration.any.js create mode 100644 test/fixtures/wpt/wasm/jsapi/jspi/notraps.any.js create mode 100644 test/fixtures/wpt/wasm/jsapi/jspi/rejects.any.js create mode 100644 test/fixtures/wpt/wasm/jsapi/jspi/testharness-additions.js create mode 100644 test/fixtures/wpt/wasm/jsapi/memory/WEB_FEATURES.yml create mode 100644 test/fixtures/wpt/wasm/jsapi/memory/to-fixed-length-buffer-shared.any.js create mode 100644 test/fixtures/wpt/wasm/jsapi/memory/to-fixed-length-buffer.any.js create mode 100644 test/fixtures/wpt/wasm/jsapi/memory/to-resizable-buffer-shared.any.js create mode 100644 test/fixtures/wpt/wasm/jsapi/memory/to-resizable-buffer.any.js create mode 100644 test/fixtures/wpt/wasm/jsapi/module/WEB_FEATURES.yml create mode 100644 test/fixtures/wpt/wasm/jsapi/module/moduleSource.tentative.any.js create mode 100644 test/fixtures/wpt/wasm/jsapi/table/WEB_FEATURES.yml create mode 100644 test/fixtures/wpt/wasm/jsapi/tag/WEB_FEATURES.yml diff --git a/test/fixtures/wpt/README.md b/test/fixtures/wpt/README.md index 89f0cefdb6363e..edb213e976292c 100644 --- a/test/fixtures/wpt/README.md +++ b/test/fixtures/wpt/README.md @@ -31,7 +31,7 @@ Last update: - url: https://github.com/web-platform-tests/wpt/tree/c928b19ab0/url - urlpattern: https://github.com/web-platform-tests/wpt/tree/a2e15ad405/urlpattern - user-timing: https://github.com/web-platform-tests/wpt/tree/5ae85bf826/user-timing -- wasm/jsapi: https://github.com/web-platform-tests/wpt/tree/cde25e7e3c/wasm/jsapi +- wasm/jsapi: https://github.com/web-platform-tests/wpt/tree/65a2134d50/wasm/jsapi - wasm/webapi: https://github.com/web-platform-tests/wpt/tree/fd1b23eeaa/wasm/webapi - web-locks: https://github.com/web-platform-tests/wpt/tree/10a122a6bc/web-locks - WebCryptoAPI: https://github.com/web-platform-tests/wpt/tree/6a1c545d77/WebCryptoAPI diff --git a/test/fixtures/wpt/versions.json b/test/fixtures/wpt/versions.json index ba7488a89a20b9..9ff0df2189231e 100644 --- a/test/fixtures/wpt/versions.json +++ b/test/fixtures/wpt/versions.json @@ -84,7 +84,7 @@ "path": "user-timing" }, "wasm/jsapi": { - "commit": "cde25e7e3c3b9d2280eb088a3fb9da988793d255", + "commit": "65a2134d50", "path": "wasm/jsapi" }, "wasm/webapi": { diff --git a/test/fixtures/wpt/wasm/jsapi/WEB_FEATURES.yml b/test/fixtures/wpt/wasm/jsapi/WEB_FEATURES.yml new file mode 100644 index 00000000000000..e69294f1e41249 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/WEB_FEATURES.yml @@ -0,0 +1,4 @@ +features: +- name: wasm + files: + - "*" diff --git a/test/fixtures/wpt/wasm/jsapi/assertions.js b/test/fixtures/wpt/wasm/jsapi/assertions.js index 162f5a9a6b8dcc..3b370190682a3a 100644 --- a/test/fixtures/wpt/wasm/jsapi/assertions.js +++ b/test/fixtures/wpt/wasm/jsapi/assertions.js @@ -6,6 +6,7 @@ function assert_function_name(fn, name, description) { assert_true(propdesc.configurable, "configurable", `${description} name should be configurable`); assert_equals(propdesc.value, name, `${description} name should be ${name}`); } +globalThis.assert_function_name = assert_function_name; function assert_function_length(fn, length, description) { const propdesc = Object.getOwnPropertyDescriptor(fn, "length"); @@ -15,6 +16,7 @@ function assert_function_length(fn, length, description) { assert_true(propdesc.configurable, "configurable", `${description} length should be configurable`); assert_equals(propdesc.value, length, `${description} length should be ${length}`); } +globalThis.assert_function_length = assert_function_length; function assert_exported_function(fn, { name, length }, description) { if (WebAssembly.Function === undefined) { @@ -28,6 +30,7 @@ function assert_exported_function(fn, { name, length }, description) { assert_function_name(fn, name, description); assert_function_length(fn, length, description); } +globalThis.assert_exported_function = assert_exported_function; function assert_Instance(instance, expected_exports) { assert_equals(Object.getPrototypeOf(instance), WebAssembly.Instance.prototype, @@ -77,6 +80,7 @@ function assert_Instance(instance, expected_exports) { } } } +globalThis.assert_Instance = assert_Instance; function assert_WebAssemblyInstantiatedSource(actual, expected_exports={}) { assert_equals(Object.getPrototypeOf(actual), Object.prototype, @@ -98,3 +102,4 @@ function assert_WebAssemblyInstantiatedSource(actual, expected_exports={}) { assert_true(instance.configurable, "instance: configurable"); assert_Instance(instance.value, expected_exports); } +globalThis.assert_WebAssemblyInstantiatedSource = assert_WebAssemblyInstantiatedSource; diff --git a/test/fixtures/wpt/wasm/jsapi/bad-imports.js b/test/fixtures/wpt/wasm/jsapi/bad-imports.js index 786fc650e326b6..c7c9c6b6cdd135 100644 --- a/test/fixtures/wpt/wasm/jsapi/bad-imports.js +++ b/test/fixtures/wpt/wasm/jsapi/bad-imports.js @@ -183,3 +183,4 @@ function test_bad_imports(t) { }); } } +globalThis.test_bad_imports = test_bad_imports; diff --git a/test/fixtures/wpt/wasm/jsapi/constructor/WEB_FEATURES.yml b/test/fixtures/wpt/wasm/jsapi/constructor/WEB_FEATURES.yml new file mode 100644 index 00000000000000..65154744f2a27a --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/constructor/WEB_FEATURES.yml @@ -0,0 +1,8 @@ +features: +- name: wasm + files: + - "*" + - "!multi-value.any.js" +- name: wasm-multi-value + files: + - "multi-value.any.js" diff --git a/test/fixtures/wpt/wasm/jsapi/constructor/compile.any.js b/test/fixtures/wpt/wasm/jsapi/constructor/compile.any.js index e94ce11717369f..f822aa30e69eb9 100644 --- a/test/fixtures/wpt/wasm/jsapi/constructor/compile.any.js +++ b/test/fixtures/wpt/wasm/jsapi/constructor/compile.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/wasm-module-builder.js function assert_Module(module) { diff --git a/test/fixtures/wpt/wasm/jsapi/constructor/instantiate-bad-imports.any.js b/test/fixtures/wpt/wasm/jsapi/constructor/instantiate-bad-imports.any.js index 30252bd6eeb3ab..e4926c8d70a86e 100644 --- a/test/fixtures/wpt/wasm/jsapi/constructor/instantiate-bad-imports.any.js +++ b/test/fixtures/wpt/wasm/jsapi/constructor/instantiate-bad-imports.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/wasm-module-builder.js // META: script=/wasm/jsapi/bad-imports.js diff --git a/test/fixtures/wpt/wasm/jsapi/constructor/instantiate.any.js b/test/fixtures/wpt/wasm/jsapi/constructor/instantiate.any.js index 8152f3a56f3f43..34e005c4700932 100644 --- a/test/fixtures/wpt/wasm/jsapi/constructor/instantiate.any.js +++ b/test/fixtures/wpt/wasm/jsapi/constructor/instantiate.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/wasm-module-builder.js // META: script=/wasm/jsapi/assertions.js // META: script=/wasm/jsapi/instanceTestFactory.js diff --git a/test/fixtures/wpt/wasm/jsapi/constructor/multi-value.any.js b/test/fixtures/wpt/wasm/jsapi/constructor/multi-value.any.js index 4b06d1da3c49b9..8786f9b953ba18 100644 --- a/test/fixtures/wpt/wasm/jsapi/constructor/multi-value.any.js +++ b/test/fixtures/wpt/wasm/jsapi/constructor/multi-value.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/wasm-module-builder.js // META: script=/wasm/jsapi/assertions.js diff --git a/test/fixtures/wpt/wasm/jsapi/constructor/toStringTag.any.js b/test/fixtures/wpt/wasm/jsapi/constructor/toStringTag.any.js index c6d2cdaf662e8b..5fae8304f8c0f6 100644 --- a/test/fixtures/wpt/wasm/jsapi/constructor/toStringTag.any.js +++ b/test/fixtures/wpt/wasm/jsapi/constructor/toStringTag.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm "use strict"; // https://webidl.spec.whatwg.org/#es-namespaces diff --git a/test/fixtures/wpt/wasm/jsapi/constructor/validate.any.js b/test/fixtures/wpt/wasm/jsapi/constructor/validate.any.js index 8b4f4582ab2987..fce43d1e175f48 100644 --- a/test/fixtures/wpt/wasm/jsapi/constructor/validate.any.js +++ b/test/fixtures/wpt/wasm/jsapi/constructor/validate.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/wasm-module-builder.js let emptyModuleBinary; diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/WEB_FEATURES.yml b/test/fixtures/wpt/wasm/jsapi/esm-integration/WEB_FEATURES.yml new file mode 100644 index 00000000000000..1f5269eb29b01f --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/esm-integration/WEB_FEATURES.yml @@ -0,0 +1,9 @@ +features: +- name: wasm-mutable-globals + files: + - "global-exports-live-bindings.tentative.any.js" + - "mutable-global-sharing.tentative.any.js" +- name: wasm-string-builtins + files: + - "source-phase-string-builtins.tentative.any.js" + - "string-builtins.tentative.any.js" diff --git a/test/fixtures/wpt/wasm/jsapi/esm-integration/namespace-instance.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/esm-integration/namespace-instance.tentative.any.js index e5e4e0721034d8..d50b5c6cb80edf 100644 --- a/test/fixtures/wpt/wasm/jsapi/esm-integration/namespace-instance.tentative.any.js +++ b/test/fixtures/wpt/wasm/jsapi/esm-integration/namespace-instance.tentative.any.js @@ -4,6 +4,10 @@ promise_test(async () => { const wasmNamespace = await import("./resources/mutable-global-export.wasm"); const instance = WebAssembly.namespaceInstance(wasmNamespace); + const wasmNamespace2 = await import("./resources/mutable-global-export.wasm"); + const instance2 = WebAssembly.namespaceInstance(wasmNamespace2); + assert_equals(instance, instance2); + assert_true(instance instanceof WebAssembly.Instance); wasmNamespace.setGlobal(999); diff --git a/test/fixtures/wpt/wasm/jsapi/exception/WEB_FEATURES.yml b/test/fixtures/wpt/wasm/jsapi/exception/WEB_FEATURES.yml new file mode 100644 index 00000000000000..ffbe372e8d6523 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/exception/WEB_FEATURES.yml @@ -0,0 +1,3 @@ +features: +- name: wasm-exception-handling + files: "**" diff --git a/test/fixtures/wpt/wasm/jsapi/exception/basic.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/exception/basic.tentative.any.js index acf644f904f53d..1b690ec463b36a 100644 --- a/test/fixtures/wpt/wasm/jsapi/exception/basic.tentative.any.js +++ b/test/fixtures/wpt/wasm/jsapi/exception/basic.tentative.any.js @@ -1,4 +1,4 @@ -// META: global=window,worker,jsshell +// META: global=window,worker,jsshell,shadowrealm // META: script=/wasm/jsapi/wasm-module-builder.js function assert_throws_wasm(fn, message) { @@ -7,12 +7,16 @@ function assert_throws_wasm(fn, message) { assert_not_reached(`expected to throw with ${message}`); } catch (e) { assert_true(e instanceof WebAssembly.Exception, `Error should be a WebAssembly.Exception with ${message}`); + // According to the spec discussion, the current `WebAssembly.Exception` does not have `[[ErrorData]]` semantically. + // - https://github.com/WebAssembly/spec/issues/1914 + // - https://webassembly.github.io/spec/js-api/#exceptions + // - https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-properties-of-error-instances + assert_false(Error.isError(e), `Error.isError(WebAssembly.Exception) should be false due to lacking [[ErrorData]]`); } } promise_test(async () => { - const kWasmAnyRef = 0x6f; - const kSig_v_r = makeSig([kWasmAnyRef], []); + const kSig_v_r = makeSig([kWasmExternRef], []); const builder = new WasmModuleBuilder(); const tagIndex = builder.addTag(kSig_v_r); builder.addFunction("throw_param", kSig_v_r) @@ -48,7 +52,7 @@ promise_test(async () => { const tagIndex = builder.addTag(kSig_v_a); builder.addFunction("throw_null", kSig_v_v) .addBody([ - kExprRefNull, kWasmAnyFunc, + kExprRefNull, kAnyFuncCode, kExprThrow, tagIndex, ]) .exportFunc(); @@ -82,7 +86,7 @@ promise_test(async () => { kExprCatch, tagIndex, kExprReturn, kExprEnd, - kExprRefNull, kWasmAnyRef, + kExprRefNull, kExternRefCode, ]) .exportFunc(); @@ -106,7 +110,7 @@ promise_test(async () => { kExprCatchAll, kExprRethrow, 0x00, kExprEnd, - kExprRefNull, kWasmAnyRef, + kExprRefNull, kExternRefCode, ]) .exportFunc(); diff --git a/test/fixtures/wpt/wasm/jsapi/exception/constructor.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/exception/constructor.tentative.any.js index 7ad08e1883ba13..a46d1816c351d2 100644 --- a/test/fixtures/wpt/wasm/jsapi/exception/constructor.tentative.any.js +++ b/test/fixtures/wpt/wasm/jsapi/exception/constructor.tentative.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/assertions.js test(() => { diff --git a/test/fixtures/wpt/wasm/jsapi/exception/getArg.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/exception/getArg.tentative.any.js index f0a568a857f0e7..87719c7ebdfb85 100644 --- a/test/fixtures/wpt/wasm/jsapi/exception/getArg.tentative.any.js +++ b/test/fixtures/wpt/wasm/jsapi/exception/getArg.tentative.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/memory/assertions.js test(() => { diff --git a/test/fixtures/wpt/wasm/jsapi/exception/identity.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/exception/identity.tentative.any.js index 65787c107e3c29..2675668ec73a95 100644 --- a/test/fixtures/wpt/wasm/jsapi/exception/identity.tentative.any.js +++ b/test/fixtures/wpt/wasm/jsapi/exception/identity.tentative.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/assertions.js // META: script=/wasm/jsapi/wasm-module-builder.js diff --git a/test/fixtures/wpt/wasm/jsapi/exception/is.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/exception/is.tentative.any.js index e28a88a3c5fdcf..840d00bf0d47a9 100644 --- a/test/fixtures/wpt/wasm/jsapi/exception/is.tentative.any.js +++ b/test/fixtures/wpt/wasm/jsapi/exception/is.tentative.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/memory/assertions.js test(() => { diff --git a/test/fixtures/wpt/wasm/jsapi/exception/toString.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/exception/toString.tentative.any.js index 00e801a6fc1a4a..6885cf0deb6918 100644 --- a/test/fixtures/wpt/wasm/jsapi/exception/toString.tentative.any.js +++ b/test/fixtures/wpt/wasm/jsapi/exception/toString.tentative.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm test(() => { const argument = { parameters: [] }; diff --git a/test/fixtures/wpt/wasm/jsapi/function/call.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/function/call.tentative.any.js index 626cd13c9f0095..2e63d5fa103fd1 100644 --- a/test/fixtures/wpt/wasm/jsapi/function/call.tentative.any.js +++ b/test/fixtures/wpt/wasm/jsapi/function/call.tentative.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/assertions.js function addxy(x, y) { diff --git a/test/fixtures/wpt/wasm/jsapi/function/constructor.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/function/constructor.tentative.any.js index 636aeca4dc1fa0..fc92fcfaf0cf49 100644 --- a/test/fixtures/wpt/wasm/jsapi/function/constructor.tentative.any.js +++ b/test/fixtures/wpt/wasm/jsapi/function/constructor.tentative.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/assertions.js function addxy(x, y) { diff --git a/test/fixtures/wpt/wasm/jsapi/function/table.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/function/table.tentative.any.js index d7d0d86e3b6a88..f0dd6ea6f8589a 100644 --- a/test/fixtures/wpt/wasm/jsapi/function/table.tentative.any.js +++ b/test/fixtures/wpt/wasm/jsapi/function/table.tentative.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/assertions.js function testfunc(n) {} diff --git a/test/fixtures/wpt/wasm/jsapi/function/type.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/function/type.tentative.any.js index e01a23a9e4339e..72a7f1bfbe5e0a 100644 --- a/test/fixtures/wpt/wasm/jsapi/function/type.tentative.any.js +++ b/test/fixtures/wpt/wasm/jsapi/function/type.tentative.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/assertions.js function addNumbers(x, y, z) { diff --git a/test/fixtures/wpt/wasm/jsapi/functions/WEB_FEATURES.yml b/test/fixtures/wpt/wasm/jsapi/functions/WEB_FEATURES.yml new file mode 100644 index 00000000000000..d8e24fc0e8f503 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/functions/WEB_FEATURES.yml @@ -0,0 +1,3 @@ +features: +- name: wasm + files: "**" diff --git a/test/fixtures/wpt/wasm/jsapi/gc/casts.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/gc/casts.tentative.any.js new file mode 100644 index 00000000000000..cce06224fd40c7 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/gc/casts.tentative.any.js @@ -0,0 +1,332 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js + +let exports = {}; +setup(() => { + const builder = new WasmModuleBuilder(); + const structIndex = builder.addStruct([makeField(kWasmI32, true)]); + const arrayIndex = builder.addArray(kWasmI32, true); + const structIndex2 = builder.addStruct([makeField(kWasmF32, true)]); + const arrayIndex2 = builder.addArray(kWasmF32, true); + const funcIndex = builder.addType({ params: [], results: [] }); + const funcIndex2 = builder.addType({ params: [], results: [kWasmI32] }); + + const argFunctions = [ + { name: "any", code: kWasmAnyRef }, + { name: "eq", code: kWasmEqRef }, + { name: "struct", code: kWasmStructRef }, + { name: "array", code: kWasmArrayRef }, + { name: "i31", code: kWasmI31Ref }, + { name: "func", code: kWasmFuncRef }, + { name: "extern", code: kWasmExternRef }, + { name: "none", code: kWasmNullRef }, + { name: "nofunc", code: kWasmNullFuncRef }, + { name: "noextern", code: kWasmNullExternRef }, + { name: "concreteStruct", code: structIndex }, + { name: "concreteArray", code: arrayIndex }, + { name: "concreteFunc", code: funcIndex }, + ]; + + for (const desc of argFunctions) { + builder + .addFunction(desc.name + "Arg", makeSig_v_x(wasmRefType(desc.code))) + .addBody([]) + .exportFunc(); + + builder + .addFunction(desc.name + "NullableArg", makeSig_v_x(wasmRefNullType(desc.code))) + .addBody([]) + .exportFunc(); + } + + builder + .addFunction("makeStruct", makeSig_r_v(wasmRefType(structIndex))) + .addBody([...wasmI32Const(42), + ...GCInstr(kExprStructNew), structIndex]) + .exportFunc(); + + builder + .addFunction("makeArray", makeSig_r_v(wasmRefType(arrayIndex))) + .addBody([...wasmI32Const(5), ...wasmI32Const(42), + ...GCInstr(kExprArrayNew), arrayIndex]) + .exportFunc(); + + builder + .addFunction("makeStruct2", makeSig_r_v(wasmRefType(structIndex2))) + .addBody([...wasmF32Const(42), + ...GCInstr(kExprStructNew), structIndex2]) + .exportFunc(); + + builder + .addFunction("makeArray2", makeSig_r_v(wasmRefType(arrayIndex2))) + .addBody([...wasmF32Const(42), ...wasmI32Const(5), + ...GCInstr(kExprArrayNew), arrayIndex2]) + .exportFunc(); + + builder + .addFunction("testFunc", funcIndex) + .addBody([]) + .exportFunc(); + + builder + .addFunction("testFunc2", funcIndex2) + .addBody([...wasmI32Const(42)]) + .exportFunc(); + + const buffer = builder.toBuffer(); + const module = new WebAssembly.Module(buffer); + const instance = new WebAssembly.Instance(module, {}); + exports = instance.exports; +}); + +test(() => { + exports.anyArg(exports.makeStruct()); + exports.anyArg(exports.makeArray()); + exports.anyArg(42); + exports.anyArg(42n); + exports.anyArg("foo"); + exports.anyArg({}); + exports.anyArg(() => {}); + exports.anyArg(exports.testFunc); + assert_throws_js(TypeError, () => exports.anyArg(null)); + + exports.anyNullableArg(null); + exports.anyNullableArg(exports.makeStruct()); + exports.anyNullableArg(exports.makeArray()); + exports.anyNullableArg(42); + exports.anyNullableArg(42n); + exports.anyNullableArg("foo"); + exports.anyNullableArg({}); + exports.anyNullableArg(() => {}); + exports.anyNullableArg(exports.testFunc); +}, "anyref casts"); + +test(() => { + exports.eqArg(exports.makeStruct()); + exports.eqArg(exports.makeArray()); + exports.eqArg(42); + assert_throws_js(TypeError, () => exports.eqArg(42n)); + assert_throws_js(TypeError, () => exports.eqArg("foo")); + assert_throws_js(TypeError, () => exports.eqArg({})); + assert_throws_js(TypeError, () => exports.eqArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.eqArg(() => {})); + assert_throws_js(TypeError, () => exports.eqArg(null)); + + exports.eqNullableArg(null); + exports.eqNullableArg(exports.makeStruct()); + exports.eqNullableArg(exports.makeArray()); + exports.eqNullableArg(42); + assert_throws_js(TypeError, () => exports.eqNullableArg(42n)); + assert_throws_js(TypeError, () => exports.eqNullableArg("foo")); + assert_throws_js(TypeError, () => exports.eqNullableArg({})); + assert_throws_js(TypeError, () => exports.eqNullableArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.eqNullableArg(() => {})); +}, "eqref casts"); + +test(() => { + exports.structArg(exports.makeStruct()); + assert_throws_js(TypeError, () => exports.structArg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.structArg(42)); + assert_throws_js(TypeError, () => exports.structArg(42n)); + assert_throws_js(TypeError, () => exports.structArg("foo")); + assert_throws_js(TypeError, () => exports.structArg({})); + assert_throws_js(TypeError, () => exports.structArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.structArg(() => {})); + assert_throws_js(TypeError, () => exports.structArg(null)); + + exports.structNullableArg(null); + exports.structNullableArg(exports.makeStruct()); + assert_throws_js(TypeError, () => exports.structNullableArg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.structNullableArg(42)); + assert_throws_js(TypeError, () => exports.structNullableArg(42n)); + assert_throws_js(TypeError, () => exports.structNullableArg("foo")); + assert_throws_js(TypeError, () => exports.structNullableArg({})); + assert_throws_js(TypeError, () => exports.structNullableArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.structNullableArg(() => {})); +}, "structref casts"); + +test(() => { + exports.arrayArg(exports.makeArray()); + assert_throws_js(TypeError, () => exports.arrayArg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.arrayArg(42)); + assert_throws_js(TypeError, () => exports.arrayArg(42n)); + assert_throws_js(TypeError, () => exports.arrayArg("foo")); + assert_throws_js(TypeError, () => exports.arrayArg({})); + assert_throws_js(TypeError, () => exports.arrayArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.arrayArg(() => {})); + assert_throws_js(TypeError, () => exports.arrayArg(null)); + + exports.arrayNullableArg(null); + exports.arrayNullableArg(exports.makeArray()); + assert_throws_js(TypeError, () => exports.arrayNullableArg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.arrayNullableArg(42)); + assert_throws_js(TypeError, () => exports.arrayNullableArg(42n)); + assert_throws_js(TypeError, () => exports.arrayNullableArg("foo")); + assert_throws_js(TypeError, () => exports.arrayNullableArg({})); + assert_throws_js(TypeError, () => exports.arrayNullableArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.arrayNullableArg(() => {})); +}, "arrayref casts"); + +test(() => { + exports.i31Arg(42); + assert_throws_js(TypeError, () => exports.i31Arg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.i31Arg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.i31Arg(42n)); + assert_throws_js(TypeError, () => exports.i31Arg("foo")); + assert_throws_js(TypeError, () => exports.i31Arg({})); + assert_throws_js(TypeError, () => exports.i31Arg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.i31Arg(() => {})); + assert_throws_js(TypeError, () => exports.i31Arg(null)); + + exports.i31NullableArg(null); + exports.i31NullableArg(42); + assert_throws_js(TypeError, () => exports.i31NullableArg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.i31NullableArg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.i31NullableArg(42n)); + assert_throws_js(TypeError, () => exports.i31NullableArg("foo")); + assert_throws_js(TypeError, () => exports.i31NullableArg({})); + assert_throws_js(TypeError, () => exports.i31NullableArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.i31NullableArg(() => {})); +}, "i31ref casts"); + +test(() => { + exports.funcArg(exports.testFunc); + assert_throws_js(TypeError, () => exports.funcArg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.funcArg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.funcArg(42)); + assert_throws_js(TypeError, () => exports.funcArg(42n)); + assert_throws_js(TypeError, () => exports.funcArg("foo")); + assert_throws_js(TypeError, () => exports.funcArg({})); + assert_throws_js(TypeError, () => exports.funcArg(() => {})); + assert_throws_js(TypeError, () => exports.funcArg(null)); + + exports.funcNullableArg(null); + exports.funcNullableArg(exports.testFunc); + assert_throws_js(TypeError, () => exports.funcNullableArg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.funcNullableArg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.funcNullableArg(42)); + assert_throws_js(TypeError, () => exports.funcNullableArg(42n)); + assert_throws_js(TypeError, () => exports.funcNullableArg("foo")); + assert_throws_js(TypeError, () => exports.funcNullableArg({})); + assert_throws_js(TypeError, () => exports.funcNullableArg(() => {})); +}, "funcref casts"); + +test(() => { + exports.externArg(exports.makeArray()); + exports.externArg(exports.makeStruct()); + exports.externArg(42); + exports.externArg(42n); + exports.externArg("foo"); + exports.externArg({}); + exports.externArg(exports.testFunc); + exports.externArg(() => {}); + assert_throws_js(TypeError, () => exports.externArg(null)); + + exports.externNullableArg(null); + exports.externNullableArg(exports.makeArray()); + exports.externNullableArg(exports.makeStruct()); + exports.externNullableArg(42); + exports.externNullableArg(42n); + exports.externNullableArg("foo"); + exports.externNullableArg({}); + exports.externNullableArg(exports.testFunc); + exports.externNullableArg(() => {}); +}, "externref casts"); + +test(() => { + for (const nullfunc of [exports.noneArg, exports.nofuncArg, exports.noexternArg]) { + assert_throws_js(TypeError, () => nullfunc(exports.makeStruct())); + assert_throws_js(TypeError, () => nullfunc(exports.makeArray())); + assert_throws_js(TypeError, () => nullfunc(42)); + assert_throws_js(TypeError, () => nullfunc(42n)); + assert_throws_js(TypeError, () => nullfunc("foo")); + assert_throws_js(TypeError, () => nullfunc({})); + assert_throws_js(TypeError, () => nullfunc(exports.testFunc)); + assert_throws_js(TypeError, () => nullfunc(() => {})); + assert_throws_js(TypeError, () => nullfunc(null)); + } + + for (const nullfunc of [exports.noneNullableArg, exports.nofuncNullableArg, exports.noexternNullableArg]) { + nullfunc(null); + assert_throws_js(TypeError, () => nullfunc(exports.makeStruct())); + assert_throws_js(TypeError, () => nullfunc(exports.makeArray())); + assert_throws_js(TypeError, () => nullfunc(42)); + assert_throws_js(TypeError, () => nullfunc(42n)); + assert_throws_js(TypeError, () => nullfunc("foo")); + assert_throws_js(TypeError, () => nullfunc({})); + assert_throws_js(TypeError, () => nullfunc(exports.testFunc)); + assert_throws_js(TypeError, () => nullfunc(() => {})); + } +}, "null casts"); + +test(() => { + exports.concreteStructArg(exports.makeStruct()); + assert_throws_js(TypeError, () => exports.concreteStructArg(exports.makeStruct2())); + assert_throws_js(TypeError, () => exports.concreteStructArg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.concreteStructArg(42)); + assert_throws_js(TypeError, () => exports.concreteStructArg(42n)); + assert_throws_js(TypeError, () => exports.concreteStructArg("foo")); + assert_throws_js(TypeError, () => exports.concreteStructArg({})); + assert_throws_js(TypeError, () => exports.concreteStructArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.concreteStructArg(() => {})); + assert_throws_js(TypeError, () => exports.concreteStructArg(null)); + + exports.concreteStructNullableArg(null); + exports.concreteStructNullableArg(exports.makeStruct()); + assert_throws_js(TypeError, () => exports.concreteStructNullableArg(exports.makeStruct2())); + assert_throws_js(TypeError, () => exports.concreteStructNullableArg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.concreteStructNullableArg(42)); + assert_throws_js(TypeError, () => exports.concreteStructNullableArg(42n)); + assert_throws_js(TypeError, () => exports.concreteStructNullableArg("foo")); + assert_throws_js(TypeError, () => exports.concreteStructNullableArg({})); + assert_throws_js(TypeError, () => exports.concreteStructNullableArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.concreteStructNullableArg(() => {})); +}, "concrete struct casts"); + +test(() => { + exports.concreteArrayArg(exports.makeArray()); + assert_throws_js(TypeError, () => exports.concreteArrayArg(exports.makeArray2())); + assert_throws_js(TypeError, () => exports.concreteArrayArg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.concreteArrayArg(42)); + assert_throws_js(TypeError, () => exports.concreteArrayArg(42n)); + assert_throws_js(TypeError, () => exports.concreteArrayArg("foo")); + assert_throws_js(TypeError, () => exports.concreteArrayArg({})); + assert_throws_js(TypeError, () => exports.concreteArrayArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.concreteArrayArg(() => {})); + assert_throws_js(TypeError, () => exports.concreteArrayArg(null)); + + exports.concreteArrayNullableArg(null); + exports.concreteArrayNullableArg(exports.makeArray()); + assert_throws_js(TypeError, () => exports.concreteArrayNullableArg(exports.makeArray2())); + assert_throws_js(TypeError, () => exports.concreteArrayNullableArg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.concreteArrayNullableArg(42)); + assert_throws_js(TypeError, () => exports.concreteArrayNullableArg(42n)); + assert_throws_js(TypeError, () => exports.concreteArrayNullableArg("foo")); + assert_throws_js(TypeError, () => exports.concreteArrayNullableArg({})); + assert_throws_js(TypeError, () => exports.concreteArrayNullableArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.concreteArrayNullableArg(() => {})); +}, "concrete array casts"); + +test(() => { + exports.concreteFuncArg(exports.testFunc); + assert_throws_js(TypeError, () => exports.concreteFuncArg(exports.testFunc2)); + assert_throws_js(TypeError, () => exports.concreteFuncArg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.concreteFuncArg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.concreteFuncArg(42)); + assert_throws_js(TypeError, () => exports.concreteFuncArg(42n)); + assert_throws_js(TypeError, () => exports.concreteFuncArg("foo")); + assert_throws_js(TypeError, () => exports.concreteFuncArg({})); + assert_throws_js(TypeError, () => exports.concreteFuncArg(() => {})); + assert_throws_js(TypeError, () => exports.concreteFuncArg(null)); + + exports.concreteFuncNullableArg(null); + exports.concreteFuncNullableArg(exports.testFunc); + assert_throws_js(TypeError, () => exports.concreteFuncNullableArg(exports.testFunc2)); + assert_throws_js(TypeError, () => exports.concreteFuncNullableArg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.concreteFuncNullableArg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.concreteFuncNullableArg(42)); + assert_throws_js(TypeError, () => exports.concreteFuncNullableArg(42n)); + assert_throws_js(TypeError, () => exports.concreteFuncNullableArg("foo")); + assert_throws_js(TypeError, () => exports.concreteFuncNullableArg({})); + assert_throws_js(TypeError, () => exports.concreteFuncNullableArg(() => {})); +}, "concrete func casts"); diff --git a/test/fixtures/wpt/wasm/jsapi/gc/exported-object.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/gc/exported-object.tentative.any.js new file mode 100644 index 00000000000000..b572f140067fda --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/gc/exported-object.tentative.any.js @@ -0,0 +1,190 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js + +let functions = {}; +setup(() => { + const builder = new WasmModuleBuilder(); + + const structIndex = builder.addStruct([makeField(kWasmI32, true)]); + const arrayIndex = builder.addArray(kWasmI32, true); + const structRef = wasmRefType(structIndex); + const arrayRef = wasmRefType(arrayIndex); + + builder + .addFunction("makeStruct", makeSig_r_v(structRef)) + .addBody([...wasmI32Const(42), + ...GCInstr(kExprStructNew), structIndex]) + .exportFunc(); + + builder + .addFunction("makeArray", makeSig_r_v(arrayRef)) + .addBody([...wasmI32Const(5), ...wasmI32Const(42), + ...GCInstr(kExprArrayNew), arrayIndex]) + .exportFunc(); + + const buffer = builder.toBuffer(); + const module = new WebAssembly.Module(buffer); + const instance = new WebAssembly.Instance(module, {}); + functions = instance.exports; +}); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_equals(struct.foo, undefined); + assert_equals(struct[0], undefined); + assert_equals(array.foo, undefined); + assert_equals(array[0], undefined); +}, "property access"); + +test(() => { + "use strict"; + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => { struct.foo = 5; }); + assert_throws_js(TypeError, () => { array.foo = 5; }); + assert_throws_js(TypeError, () => { struct[0] = 5; }); + assert_throws_js(TypeError, () => { array[0] = 5; }); +}, "property assignment (strict mode)"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => { struct.foo = 5; }); + assert_throws_js(TypeError, () => { array.foo = 5; }); + assert_throws_js(TypeError, () => { struct[0] = 5; }); + assert_throws_js(TypeError, () => { array[0] = 5; }); +}, "property assignment (non-strict mode)"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_equals(Object.getOwnPropertyNames(struct).length, 0); + assert_equals(Object.getOwnPropertyNames(array).length, 0); +}, "ownPropertyNames"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => Object.defineProperty(struct, "foo", { value: 1 })); + assert_throws_js(TypeError, () => Object.defineProperty(array, "foo", { value: 1 })); +}, "defineProperty"); + +test(() => { + "use strict"; + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => delete struct.foo); + assert_throws_js(TypeError, () => delete struct[0]); + assert_throws_js(TypeError, () => delete array.foo); + assert_throws_js(TypeError, () => delete array[0]); +}, "delete (strict mode)"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => delete struct.foo); + assert_throws_js(TypeError, () => delete struct[0]); + assert_throws_js(TypeError, () => delete array.foo); + assert_throws_js(TypeError, () => delete array[0]); +}, "delete (non-strict mode)"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_equals(Object.getPrototypeOf(struct), null); + assert_equals(Object.getPrototypeOf(array), null); +}, "getPrototypeOf"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => Object.setPrototypeOf(struct, {})); + assert_throws_js(TypeError, () => Object.setPrototypeOf(array, {})); +}, "setPrototypeOf"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_false(Object.isExtensible(struct)); + assert_false(Object.isExtensible(array)); +}, "isExtensible"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => Object.preventExtensions(struct)); + assert_throws_js(TypeError, () => Object.preventExtensions(array)); +}, "preventExtensions"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => Object.seal(struct)); + assert_throws_js(TypeError, () => Object.seal(array)); +}, "sealing"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_equals(typeof struct, "object"); + assert_equals(typeof array, "object"); +}, "typeof"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => struct.toString()); + assert_equals(Object.prototype.toString.call(struct), "[object Object]"); + assert_throws_js(TypeError, () => array.toString()); + assert_equals(Object.prototype.toString.call(array), "[object Object]"); +}, "toString"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => struct.valueOf()); + assert_equals(Object.prototype.valueOf.call(struct), struct); + assert_throws_js(TypeError, () => array.valueOf()); + assert_equals(Object.prototype.valueOf.call(array), array); +}, "valueOf"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + const map = new Map(); + map.set(struct, "struct"); + map.set(array, "array"); + assert_equals(map.get(struct), "struct"); + assert_equals(map.get(array), "array"); +}, "GC objects as map keys"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + const set = new Set(); + set.add(struct); + set.add(array); + assert_true(set.has(struct)); + assert_true(set.has(array)); +}, "GC objects as set element"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + const map = new WeakMap(); + map.set(struct, "struct"); + map.set(array, "array"); + assert_equals(map.get(struct), "struct"); + assert_equals(map.get(array), "array"); +}, "GC objects as weak map keys"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + const set = new WeakSet(); + set.add(struct); + set.add(array); + assert_true(set.has(struct)); + assert_true(set.has(array)); +}, "GC objects as weak set element"); diff --git a/test/fixtures/wpt/wasm/jsapi/gc/i31.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/gc/i31.tentative.any.js new file mode 100644 index 00000000000000..17fd82440cc8b2 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/gc/i31.tentative.any.js @@ -0,0 +1,98 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js + +let exports = {}; +setup(() => { + const builder = new WasmModuleBuilder(); + const i31Ref = wasmRefType(kWasmI31Ref); + const i31NullableRef = wasmRefNullType(kWasmI31Ref); + const anyRef = wasmRefType(kWasmAnyRef); + + builder + .addFunction("makeI31", makeSig_r_x(i31Ref, kWasmI32)) + .addBody([kExprLocalGet, 0, + ...GCInstr(kExprI31New)]) + .exportFunc(); + + builder + .addFunction("castI31", makeSig_r_x(kWasmI32, anyRef)) + .addBody([kExprLocalGet, 0, + ...GCInstr(kExprRefCast), kI31RefCode, + ...GCInstr(kExprI31GetU)]) + .exportFunc(); + + builder + .addFunction("getI31", makeSig_r_x(kWasmI32, i31Ref)) + .addBody([kExprLocalGet, 0, + ...GCInstr(kExprI31GetS)]) + .exportFunc(); + + builder + .addFunction("argI31", makeSig_v_x(i31NullableRef)) + .addBody([]) + .exportFunc(); + + builder + .addGlobal(i31NullableRef, true, [...wasmI32Const(0), ...GCInstr(kExprI31New)]) + builder + .addExportOfKind("i31Global", kExternalGlobal, 0); + + builder + .addTable(i31NullableRef, 10) + builder + .addExportOfKind("i31Table", kExternalTable, 0); + + const buffer = builder.toBuffer(); + const module = new WebAssembly.Module(buffer); + const instance = new WebAssembly.Instance(module, {}); + exports = instance.exports; +}); + +test(() => { + assert_equals(exports.makeI31(42), 42); + assert_equals(exports.makeI31(2 ** 30 - 1), 2 ** 30 - 1); + assert_equals(exports.makeI31(2 ** 30), -(2 ** 30)); + assert_equals(exports.makeI31(-(2 ** 30)), -(2 ** 30)); + assert_equals(exports.makeI31(2 ** 31 - 1), -1); + assert_equals(exports.makeI31(2 ** 31), 0); +}, "i31ref conversion to Number"); + +test(() => { + assert_equals(exports.getI31(exports.makeI31(42)), 42); + assert_equals(exports.getI31(42), 42); + assert_equals(exports.getI31(2.0 ** 30 - 1), 2 ** 30 - 1); + assert_equals(exports.getI31(-(2 ** 30)), -(2 ** 30)); +}, "Number conversion to i31ref"); + +test(() => { + exports.argI31(null); + assert_throws_js(TypeError, () => exports.argI31(2 ** 30)); + assert_throws_js(TypeError, () => exports.argI31(-(2 ** 30) - 1)); + assert_throws_js(TypeError, () => exports.argI31(2n)); + assert_throws_js(TypeError, () => exports.argI31(() => 3)); + assert_throws_js(TypeError, () => exports.argI31(exports.getI31)); +}, "Check i31ref argument type"); + +test(() => { + assert_equals(exports.castI31(42), 42); + assert_equals(exports.castI31(2 ** 30 - 1), 2 ** 30 - 1); + assert_throws_js(WebAssembly.RuntimeError, () => { exports.castI31(2 ** 30); }); + assert_throws_js(WebAssembly.RuntimeError, () => { exports.castI31(-(2 ** 30) - 1); }); + assert_throws_js(WebAssembly.RuntimeError, () => { exports.castI31(2 ** 32); }); +}, "Numbers in i31 range are i31ref, not hostref"); + +test(() => { + assert_equals(exports.i31Global.value, 0); + exports.i31Global.value = 42; + assert_throws_js(TypeError, () => exports.i31Global.value = 2 ** 30); + assert_throws_js(TypeError, () => exports.i31Global.value = -(2 ** 30) - 1); + assert_equals(exports.i31Global.value, 42); +}, "i31ref global"); + +test(() => { + assert_equals(exports.i31Table.get(0), null); + exports.i31Table.set(0, 42); + assert_throws_js(TypeError, () => exports.i31Table.set(0, 2 ** 30)); + assert_throws_js(TypeError, () => exports.i31Table.set(0, -(2 ** 30) - 1)); + assert_equals(exports.i31Table.get(0), 42); +}, "i31ref table"); diff --git a/test/fixtures/wpt/wasm/jsapi/global/WEB_FEATURES.yml b/test/fixtures/wpt/wasm/jsapi/global/WEB_FEATURES.yml new file mode 100644 index 00000000000000..d8e24fc0e8f503 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/global/WEB_FEATURES.yml @@ -0,0 +1,3 @@ +features: +- name: wasm + files: "**" diff --git a/test/fixtures/wpt/wasm/jsapi/global/constructor.any.js b/test/fixtures/wpt/wasm/jsapi/global/constructor.any.js index dade7b1f55a433..f83f77a5c3ecf3 100644 --- a/test/fixtures/wpt/wasm/jsapi/global/constructor.any.js +++ b/test/fixtures/wpt/wasm/jsapi/global/constructor.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/assertions.js function assert_Global(actual, expected) { diff --git a/test/fixtures/wpt/wasm/jsapi/global/toString.any.js b/test/fixtures/wpt/wasm/jsapi/global/toString.any.js index 359c4273b5bd78..b308498982ee5d 100644 --- a/test/fixtures/wpt/wasm/jsapi/global/toString.any.js +++ b/test/fixtures/wpt/wasm/jsapi/global/toString.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm test(() => { const argument = { "value": "i32" }; diff --git a/test/fixtures/wpt/wasm/jsapi/global/type.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/global/type.tentative.any.js index 95adc2af0f6813..78d612529dd3f3 100644 --- a/test/fixtures/wpt/wasm/jsapi/global/type.tentative.any.js +++ b/test/fixtures/wpt/wasm/jsapi/global/type.tentative.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/assertions.js function assert_type(argument) { diff --git a/test/fixtures/wpt/wasm/jsapi/global/value-get-set.any.js b/test/fixtures/wpt/wasm/jsapi/global/value-get-set.any.js index f95b7ca9e3f0d5..ee593619b96e8f 100644 --- a/test/fixtures/wpt/wasm/jsapi/global/value-get-set.any.js +++ b/test/fixtures/wpt/wasm/jsapi/global/value-get-set.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm test(() => { const thisValues = [ @@ -131,7 +131,14 @@ test(() => { const setter = desc.set; assert_equals(typeof setter, "function"); - assert_throws_js(TypeError, () => setter.call(global)); + assert_equals(global.value, 0); + + assert_equals(setter.call(global, undefined), undefined); + assert_equals(global.value, 0); + + // Should behave as if 'undefined' was passed as the argument. + assert_equals(setter.call(global), undefined); + assert_equals(global.value, 0); }, "Calling setter without argument"); test(() => { diff --git a/test/fixtures/wpt/wasm/jsapi/global/valueOf.any.js b/test/fixtures/wpt/wasm/jsapi/global/valueOf.any.js index 0695a5a61fbc6e..5bcb1718258988 100644 --- a/test/fixtures/wpt/wasm/jsapi/global/valueOf.any.js +++ b/test/fixtures/wpt/wasm/jsapi/global/valueOf.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm test(() => { const argument = { "value": "i32" }; diff --git a/test/fixtures/wpt/wasm/jsapi/idlharness.any.js b/test/fixtures/wpt/wasm/jsapi/idlharness.any.js index 98713d4bf6e43a..6478f857c7ae11 100644 --- a/test/fixtures/wpt/wasm/jsapi/idlharness.any.js +++ b/test/fixtures/wpt/wasm/jsapi/idlharness.any.js @@ -1,6 +1,7 @@ // META: script=/resources/WebIDLParser.js // META: script=/resources/idlharness.js // META: script=../resources/load_wasm.js +// META: global=window,dedicatedworker,shadowrealm-in-window 'use strict'; diff --git a/test/fixtures/wpt/wasm/jsapi/instance/WEB_FEATURES.yml b/test/fixtures/wpt/wasm/jsapi/instance/WEB_FEATURES.yml new file mode 100644 index 00000000000000..d8e24fc0e8f503 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/instance/WEB_FEATURES.yml @@ -0,0 +1,3 @@ +features: +- name: wasm + files: "**" diff --git a/test/fixtures/wpt/wasm/jsapi/instance/constructor-bad-imports.any.js b/test/fixtures/wpt/wasm/jsapi/instance/constructor-bad-imports.any.js index e4a5abb8eb2169..1ef4f8423de857 100644 --- a/test/fixtures/wpt/wasm/jsapi/instance/constructor-bad-imports.any.js +++ b/test/fixtures/wpt/wasm/jsapi/instance/constructor-bad-imports.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/wasm-module-builder.js // META: script=/wasm/jsapi/bad-imports.js diff --git a/test/fixtures/wpt/wasm/jsapi/instance/constructor-caching.any.js b/test/fixtures/wpt/wasm/jsapi/instance/constructor-caching.any.js index 1aa4739b6294d0..f969364d93f462 100644 --- a/test/fixtures/wpt/wasm/jsapi/instance/constructor-caching.any.js +++ b/test/fixtures/wpt/wasm/jsapi/instance/constructor-caching.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/wasm-module-builder.js function getExports() { diff --git a/test/fixtures/wpt/wasm/jsapi/instance/constructor.any.js b/test/fixtures/wpt/wasm/jsapi/instance/constructor.any.js index 26390ebd2cdb2e..24bf97356c83a7 100644 --- a/test/fixtures/wpt/wasm/jsapi/instance/constructor.any.js +++ b/test/fixtures/wpt/wasm/jsapi/instance/constructor.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/wasm-module-builder.js // META: script=/wasm/jsapi/assertions.js // META: script=/wasm/jsapi/instanceTestFactory.js diff --git a/test/fixtures/wpt/wasm/jsapi/instance/exports.any.js b/test/fixtures/wpt/wasm/jsapi/instance/exports.any.js index 6dcfbcee950d87..f7244923d83c51 100644 --- a/test/fixtures/wpt/wasm/jsapi/instance/exports.any.js +++ b/test/fixtures/wpt/wasm/jsapi/instance/exports.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/wasm-module-builder.js let emptyModuleBinary; diff --git a/test/fixtures/wpt/wasm/jsapi/instance/toString.any.js b/test/fixtures/wpt/wasm/jsapi/instance/toString.any.js index 547a9ca8295f5b..d77037d65b9c25 100644 --- a/test/fixtures/wpt/wasm/jsapi/instance/toString.any.js +++ b/test/fixtures/wpt/wasm/jsapi/instance/toString.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/wasm-module-builder.js test(() => { diff --git a/test/fixtures/wpt/wasm/jsapi/instanceTestFactory.js b/test/fixtures/wpt/wasm/jsapi/instanceTestFactory.js index ac468947ec22e2..2e015af8198a61 100644 --- a/test/fixtures/wpt/wasm/jsapi/instanceTestFactory.js +++ b/test/fixtures/wpt/wasm/jsapi/instanceTestFactory.js @@ -237,7 +237,7 @@ const instanceTestFactory = [ builder.addGlobal(kWasmI32, true) .exportAs("") - .init = 7; + .init = wasmI32Const(7); const buffer = builder.toBuffer(); @@ -273,10 +273,10 @@ const instanceTestFactory = [ builder.addGlobal(kWasmI32, true) .exportAs("global") - .init = 7; + .init = wasmI32Const(7); builder.addGlobal(kWasmF64, true) .exportAs("global2") - .init = 1.2; + .init = wasmF64Const(1.2); builder.addMemory(4, 8, true); @@ -759,3 +759,5 @@ const instanceTestFactory = [ } ], ]; + +globalThis.instanceTestFactory = instanceTestFactory; diff --git a/test/fixtures/wpt/wasm/jsapi/interface.any.js b/test/fixtures/wpt/wasm/jsapi/interface.any.js index 19d29ead0a7264..8256fc209a7f65 100644 --- a/test/fixtures/wpt/wasm/jsapi/interface.any.js +++ b/test/fixtures/wpt/wasm/jsapi/interface.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/assertions.js function test_operations(object, object_name, operations) { diff --git a/test/fixtures/wpt/wasm/jsapi/js-string/WEB_FEATURES.yml b/test/fixtures/wpt/wasm/jsapi/js-string/WEB_FEATURES.yml new file mode 100644 index 00000000000000..6c1e9bb84c92f7 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/js-string/WEB_FEATURES.yml @@ -0,0 +1,3 @@ +features: +- name: wasm-string-builtins + files: "**" diff --git a/test/fixtures/wpt/wasm/jsapi/js-string/basic.any.js b/test/fixtures/wpt/wasm/jsapi/js-string/basic.any.js new file mode 100644 index 00000000000000..174bab32eb171b --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/js-string/basic.any.js @@ -0,0 +1,383 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/assertions.js +// META: script=/wasm/jsapi/wasm-module-builder.js +// META: script=/wasm/jsapi/js-string/polyfill.js + +// Generate two sets of exports, one from a polyfill implementation and another +// from the builtins provided by the host. +let polyfillExports; +let builtinExports; +setup(() => { + // Compile a module that exports a function for each builtin that will call + // it. We could just generate a module that re-exports the builtins, but that + // would not catch any special codegen that could happen when direct calling + // a known builtin function from wasm. + const builder = new WasmModuleBuilder(); + const arrayIndex = builder.addArray(kWasmI16, true, kNoSuperType, true); + const builtins = [ + { + name: "test", + params: [kWasmExternRef], + results: [kWasmI32], + }, + { + name: "cast", + params: [kWasmExternRef], + results: [wasmRefType(kWasmExternRef)], + }, + { + name: "fromCharCodeArray", + params: [wasmRefNullType(arrayIndex), kWasmI32, kWasmI32], + results: [wasmRefType(kWasmExternRef)], + }, + { + name: "intoCharCodeArray", + params: [kWasmExternRef, wasmRefNullType(arrayIndex), kWasmI32], + results: [kWasmI32], + }, + { + name: "fromCharCode", + params: [kWasmI32], + results: [wasmRefType(kWasmExternRef)], + }, + { + name: "fromCodePoint", + params: [kWasmI32], + results: [wasmRefType(kWasmExternRef)], + }, + { + name: "charCodeAt", + params: [kWasmExternRef, kWasmI32], + results: [kWasmI32], + }, + { + name: "codePointAt", + params: [kWasmExternRef, kWasmI32], + results: [kWasmI32], + }, + { + name: "length", + params: [kWasmExternRef], + results: [kWasmI32], + }, + { + name: "concat", + params: [kWasmExternRef, kWasmExternRef], + results: [wasmRefType(kWasmExternRef)], + }, + { + name: "substring", + params: [kWasmExternRef, kWasmI32, kWasmI32], + results: [wasmRefType(kWasmExternRef)], + }, + { + name: "equals", + params: [kWasmExternRef, kWasmExternRef], + results: [kWasmI32], + }, + { + name: "compare", + params: [kWasmExternRef, kWasmExternRef], + results: [kWasmI32], + }, + ]; + + // Add a function type for each builtin + for (let builtin of builtins) { + builtin.type = builder.addType({ + params: builtin.params, + results: builtin.results + }); + } + + // Add an import for each builtin + for (let builtin of builtins) { + builtin.importFuncIndex = builder.addImport( + "wasm:js-string", + builtin.name, + builtin.type); + } + + // Generate an exported function to call the builtin + for (let builtin of builtins) { + let func = builder.addFunction(builtin.name + "Imp", builtin.type); + func.addLocals(builtin.params.length); + let body = []; + for (let i = 0; i < builtin.params.length; i++) { + body.push(kExprLocalGet); + body.push(...wasmSignedLeb(i)); + } + body.push(kExprCallFunction); + body.push(...wasmSignedLeb(builtin.importFuncIndex)); + func.addBody(body); + func.exportAs(builtin.name); + } + + const buffer = builder.toBuffer(); + + // Instantiate this module using the builtins from the host + const builtinModule = new WebAssembly.Module(buffer, { + builtins: ["js-string"] + }); + const builtinInstance = new WebAssembly.Instance(builtinModule, {}); + builtinExports = builtinInstance.exports; + + // Instantiate this module using the polyfill module + const polyfillModule = new WebAssembly.Module(buffer); + const polyfillInstance = new WebAssembly.Instance(polyfillModule, { + "wasm:js-string": polyfillImports + }); + polyfillExports = polyfillInstance.exports; +}); + +// A helper function to assert that the behavior of two functions are the +// same. +function assert_same_behavior(funcA, funcB, ...params) { + let resultA; + let errA = null; + try { + resultA = funcA(...params); + } catch (err) { + errA = err; + } + + let resultB; + let errB = null; + try { + resultB = funcB(...params); + } catch (err) { + errB = err; + } + + if (errA || errB) { + assert_equals(errA === null, errB === null, errA ? errA.message : errB.message); + assert_equals(Object.getPrototypeOf(errA), Object.getPrototypeOf(errB)); + } + assert_equals(resultA, resultB); + + if (errA) { + throw errA; + } + return resultA; +} + +function assert_throws_if(func, shouldThrow, constructor) { + let error = null; + try { + func(); + } catch (e) { + error = e; + } + assert_equals(error !== null, shouldThrow, "shouldThrow mismatch"); + if (shouldThrow && error !== null) { + assert_true(error instanceof constructor); + } +} + +// Constant values used in the tests below +const testStrings = [ + "", + "a", + "1", + "ab", + "hello, world", + "\n", + "☺", + "☺☺", + String.fromCodePoint(0x10000, 0x10001) +]; +const testCharCodes = [1, 2, 3, 10, 0x7f, 0xff, 0xfffe, 0xffff]; +const testCodePoints = [1, 2, 3, 10, 0x7f, 0xff, 0xfffe, 0xffff, 0x10000, 0x10001]; +const testExternRefValues = [ + null, + undefined, + true, + false, + {x:1337}, + ["abracadabra"], + 13.37, + -0, + 0x7fffffff + 0.1, + -0x7fffffff - 0.1, + 0x80000000 + 0.1, + -0x80000000 - 0.1, + 0xffffffff + 0.1, + -0xffffffff - 0.1, + Number.EPSILON, + Number.MAX_SAFE_INTEGER, + Number.MIN_SAFE_INTEGER, + Number.MIN_VALUE, + Number.MAX_VALUE, + Number.NaN, + "hi", + 37n, + new Number(42), + new Boolean(true), + Symbol("status"), + () => 1337, +]; + +// Test that `test` and `cast` work on various JS values. Run all the +// other builtins and assert that they also perform equivalent type +// checks. +test(() => { + for (let a of testExternRefValues) { + let isString = assert_same_behavior( + builtinExports['test'], + polyfillExports['test'], + a + ); + + assert_throws_if(() => assert_same_behavior( + builtinExports['cast'], + polyfillExports['cast'], + a + ), !isString, WebAssembly.RuntimeError); + + let arrayMutI16 = helperExports.createArrayMutI16(10); + assert_throws_if(() => assert_same_behavior( + builtinExports['intoCharCodeArray'], + polyfillExports['intoCharCodeArray'], + a, arrayMutI16, 0 + ), !isString, WebAssembly.RuntimeError); + + assert_throws_if(() => assert_same_behavior( + builtinExports['charCodeAt'], + polyfillExports['charCodeAt'], + a, 0 + ), !isString, WebAssembly.RuntimeError); + + assert_throws_if(() => assert_same_behavior( + builtinExports['codePointAt'], + polyfillExports['codePointAt'], + a, 0 + ), !isString, WebAssembly.RuntimeError); + + assert_throws_if(() => assert_same_behavior( + builtinExports['length'], + polyfillExports['length'], + a + ), !isString, WebAssembly.RuntimeError); + + assert_throws_if(() => assert_same_behavior( + builtinExports['concat'], + polyfillExports['concat'], + a, a + ), !isString, WebAssembly.RuntimeError); + + assert_throws_if(() => assert_same_behavior( + builtinExports['substring'], + polyfillExports['substring'], + a, 0, 0 + ), !isString, WebAssembly.RuntimeError); + + assert_throws_if(() => assert_same_behavior( + builtinExports['equals'], + polyfillExports['equals'], + a, a + ), a !== null && !isString, WebAssembly.RuntimeError); + + assert_throws_if(() => assert_same_behavior( + builtinExports['compare'], + polyfillExports['compare'], + a, a + ), !isString, WebAssembly.RuntimeError); + } +}); + +// Test that `fromCharCode` works on various char codes +test(() => { + for (let a of testCharCodes) { + assert_same_behavior( + builtinExports['fromCharCode'], + polyfillExports['fromCharCode'], + a + ); + } +}); + +// Test that `fromCodePoint` works on various code points +test(() => { + for (let a of testCodePoints) { + assert_same_behavior( + builtinExports['fromCodePoint'], + polyfillExports['fromCodePoint'], + a + ); + } +}); + +// Perform tests on various strings +test(() => { + for (let a of testStrings) { + let length = assert_same_behavior( + builtinExports['length'], + polyfillExports['length'], + a + ); + + for (let i = 0; i < length; i++) { + let charCode = assert_same_behavior( + builtinExports['charCodeAt'], + polyfillExports['charCodeAt'], + a, i + ); + } + + for (let i = 0; i < length; i++) { + let charCode = assert_same_behavior( + builtinExports['codePointAt'], + polyfillExports['codePointAt'], + a, i + ); + } + + let arrayMutI16 = helperExports.createArrayMutI16(length); + assert_same_behavior( + builtinExports['intoCharCodeArray'], + polyfillExports['intoCharCodeArray'], + a, arrayMutI16, 0 + ); + + assert_same_behavior( + builtinExports['fromCharCodeArray'], + polyfillExports['fromCharCodeArray'], + arrayMutI16, 0, length + ); + + for (let i = 0; i < length; i++) { + for (let j = 0; j < length; j++) { + assert_same_behavior( + builtinExports['substring'], + polyfillExports['substring'], + a, i, j + ); + } + } + } +}); + +// Test various binary operations +test(() => { + for (let a of testStrings) { + for (let b of testStrings) { + assert_same_behavior( + builtinExports['concat'], + polyfillExports['concat'], + a, b + ); + + assert_same_behavior( + builtinExports['equals'], + polyfillExports['equals'], + a, b + ); + + assert_same_behavior( + builtinExports['compare'], + polyfillExports['compare'], + a, b + ); + } + } +}); diff --git a/test/fixtures/wpt/wasm/jsapi/js-string/constants.any.js b/test/fixtures/wpt/wasm/jsapi/js-string/constants.any.js new file mode 100644 index 00000000000000..90cf94df76efcb --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/js-string/constants.any.js @@ -0,0 +1,61 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js + +// Instantiate a module with an imported global and return the global. +function instantiateImportedGlobal(module, name, type, mutable, importedStringConstants) { + let builder = new WasmModuleBuilder(); + builder.addImportedGlobal(module, name, type, mutable); + builder.addExportOfKind("global", kExternalGlobal, 0); + let bytes = builder.toBuffer(); + let mod = new WebAssembly.Module(bytes, { importedStringConstants }); + let instance = new WebAssembly.Instance(mod, {}); + return instance.exports["global"]; +} + +const badGlobalTypes = [ + [kWasmAnyRef, false], + [kWasmAnyRef, true], + [wasmRefType(kWasmAnyRef), false], + [wasmRefType(kWasmAnyRef), true], + [kWasmFuncRef, false], + [kWasmFuncRef, true], + [wasmRefType(kWasmFuncRef), false], + [wasmRefType(kWasmFuncRef), true], + [kWasmExternRef, true], + [wasmRefType(kWasmExternRef), true], +]; +for ([type, mutable] of badGlobalTypes) { + test(() => { + assert_throws_js(WebAssembly.CompileError, + () => instantiateImportedGlobal("'", "constant", type, mutable, "'"), + "type mismatch"); + }); +} + +const goodGlobalTypes = [ + [kWasmExternRef, false], + [wasmRefType(kWasmExternRef), false], +]; +const constants = [ + '', + '\0', + '0', + '0'.repeat(100000), + '\uD83D\uDE00', +]; +const namespaces = [ + "", + "'", + "strings" +]; + +for (let namespace of namespaces) { + for (let constant of constants) { + for ([type, mutable] of goodGlobalTypes) { + test(() => { + let result = instantiateImportedGlobal(namespace, constant, type, mutable, namespace); + assert_equals(result.value, constant); + }); + } + } +} diff --git a/test/fixtures/wpt/wasm/jsapi/js-string/imports.any.js b/test/fixtures/wpt/wasm/jsapi/js-string/imports.any.js new file mode 100644 index 00000000000000..d4e16dc47d060b --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/js-string/imports.any.js @@ -0,0 +1,26 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js + +test(() => { + let builder = new WasmModuleBuilder(); + + // Import a string constant + builder.addImportedGlobal("constants", "constant", kWasmExternRef, false); + + // Import a builtin function + builder.addImport( + "wasm:js-string", + "test", + {params: [kWasmExternRef], results: [kWasmI32]}); + + let buffer = builder.toBuffer(); + let module = new WebAssembly.Module(buffer, { + builtins: ["js-string"], + importedStringConstants: "constants" + }); + let imports = WebAssembly.Module.imports(module); + + // All imports that refer to a builtin module are suppressed from import + // reflection. + assert_equals(imports.length, 0); +}); diff --git a/test/fixtures/wpt/wasm/jsapi/js-string/polyfill.js b/test/fixtures/wpt/wasm/jsapi/js-string/polyfill.js new file mode 100644 index 00000000000000..7a00d4285d7a26 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/js-string/polyfill.js @@ -0,0 +1,170 @@ +// Generate some helper functions for manipulating (array (mut i16)) from JS +let helperExports; +{ + const builder = new WasmModuleBuilder(); + const arrayIndex = builder.addArray(kWasmI16, true, kNoSuperType, true); + + builder + .addFunction("createArrayMutI16", { + params: [kWasmI32], + results: [kWasmAnyRef] + }) + .addBody([ + kExprLocalGet, + ...wasmSignedLeb(0), + ...GCInstr(kExprArrayNewDefault), + ...wasmSignedLeb(arrayIndex) + ]) + .exportFunc(); + + builder + .addFunction("arrayLength", { + params: [kWasmArrayRef], + results: [kWasmI32] + }) + .addBody([ + kExprLocalGet, + ...wasmSignedLeb(0), + ...GCInstr(kExprArrayLen) + ]) + .exportFunc(); + + builder + .addFunction("arraySet", { + params: [wasmRefNullType(arrayIndex), kWasmI32, kWasmI32], + results: [] + }) + .addBody([ + kExprLocalGet, + ...wasmSignedLeb(0), + kExprLocalGet, + ...wasmSignedLeb(1), + kExprLocalGet, + ...wasmSignedLeb(2), + ...GCInstr(kExprArraySet), + ...wasmSignedLeb(arrayIndex) + ]) + .exportFunc(); + + builder + .addFunction("arrayGet", { + params: [wasmRefNullType(arrayIndex), kWasmI32], + results: [kWasmI32] + }) + .addBody([ + kExprLocalGet, + ...wasmSignedLeb(0), + kExprLocalGet, + ...wasmSignedLeb(1), + ...GCInstr(kExprArrayGetU), + ...wasmSignedLeb(arrayIndex) + ]) + .exportFunc(); + + let bytes = builder.toBuffer(); + let module = new WebAssembly.Module(bytes); + let instance = new WebAssembly.Instance(module); + + helperExports = instance.exports; +} + +function throwIfNotString(a) { + if (typeof a !== "string") { + throw new WebAssembly.RuntimeError(); + } +} + +this.polyfillImports = { + test: (string) => { + if (string === null || + typeof string !== "string") { + return 0; + } + return 1; + }, + cast: (string) => { + throwIfNotString(string); + return string; + }, + fromCharCodeArray: (array, arrayStart, arrayCount) => { + arrayStart >>>= 0; + arrayCount >>>= 0; + let length = helperExports.arrayLength(array); + if (BigInt(arrayStart) + BigInt(arrayCount) > BigInt(length)) { + throw new WebAssembly.RuntimeError(); + } + let result = ''; + for (let i = arrayStart; i < arrayStart + arrayCount; i++) { + result += String.fromCharCode(helperExports.arrayGet(array, i)); + } + return result; + }, + intoCharCodeArray: (string, arr, arrayStart) => { + arrayStart >>>= 0; + throwIfNotString(string); + let arrLength = helperExports.arrayLength(arr); + let stringLength = string.length; + if (BigInt(arrayStart) + BigInt(stringLength) > BigInt(arrLength)) { + throw new WebAssembly.RuntimeError(); + } + for (let i = 0; i < stringLength; i++) { + helperExports.arraySet(arr, arrayStart + i, string[i].charCodeAt(0)); + } + return stringLength; + }, + fromCharCode: (charCode) => { + charCode >>>= 0; + return String.fromCharCode(charCode); + }, + fromCodePoint: (codePoint) => { + codePoint >>>= 0; + return String.fromCodePoint(codePoint); + }, + charCodeAt: (string, stringIndex) => { + stringIndex >>>= 0; + throwIfNotString(string); + if (stringIndex >= string.length) + throw new WebAssembly.RuntimeError(); + return string.charCodeAt(stringIndex); + }, + codePointAt: (string, stringIndex) => { + stringIndex >>>= 0; + throwIfNotString(string); + if (stringIndex >= string.length) + throw new WebAssembly.RuntimeError(); + return string.codePointAt(stringIndex); + }, + length: (string) => { + throwIfNotString(string); + return string.length; + }, + concat: (stringA, stringB) => { + throwIfNotString(stringA); + throwIfNotString(stringB); + return stringA + stringB; + }, + substring: (string, startIndex, endIndex) => { + startIndex >>>= 0; + endIndex >>>= 0; + throwIfNotString(string); + if (startIndex > string.length, + endIndex > string.length, + endIndex < startIndex) { + return ""; + } + return string.substring(startIndex, endIndex); + }, + equals: (stringA, stringB) => { + if (stringA !== null) throwIfNotString(stringA); + if (stringB !== null) throwIfNotString(stringB); + return stringA === stringB; + }, + compare: (stringA, stringB) => { + throwIfNotString(stringA); + throwIfNotString(stringB); + if (stringA < stringB) { + return -1; + } + return stringA === stringB ? 0 : 1; + }, +}; diff --git a/test/fixtures/wpt/wasm/jsapi/jspi/README.txt b/test/fixtures/wpt/wasm/jsapi/jspi/README.txt new file mode 100644 index 00000000000000..c65b9893c6fd30 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/jspi/README.txt @@ -0,0 +1,3 @@ +This suite tests JSPI. + +The tests are based on wasm spec tests. diff --git a/test/fixtures/wpt/wasm/jsapi/jspi/js-promise-integration.any.js b/test/fixtures/wpt/wasm/jsapi/jspi/js-promise-integration.any.js new file mode 100644 index 00000000000000..01dba0e3dadc70 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/jspi/js-promise-integration.any.js @@ -0,0 +1,370 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js + +// Test for invalid wrappers +test(() => { + assert_throws_js(TypeError, () => WebAssembly.promising({}), + "Argument 0 must be a function"); + assert_throws_js(TypeError, () => WebAssembly.promising(() => {}), + "Argument 0 must be a WebAssembly exported function"); + assert_throws_js(TypeError, () => WebAssembly.Suspending(() => {}), + "WebAssembly.Suspending must be invoked with 'new'"); + assert_throws_js(TypeError, () => new WebAssembly.Suspending({}), + "Argument 0 must be a function"); + + function asmModule() { + "use asm"; + + function x(v) { + v = v | 0; + } + return x; + } + assert_throws_js(TypeError, () => WebAssembly.promising(asmModule()), + "Argument 0 must be a WebAssembly exported function"); +},"Valid use of API"); + +test(() => { + let builder = new WasmModuleBuilder(); + builder.addGlobal(kWasmI32, true).exportAs('g'); + builder.addFunction("test", kSig_i_v) + .addBody([ + kExprI32Const, 42, + kExprGlobalSet, 0, + kExprI32Const, 0 + ]).exportFunc(); + let instance = builder.instantiate(); + let wrapper = WebAssembly.promising(instance.exports.test); + wrapper(); + assert_equals(42, instance.exports.g.value); +},"Promising function always entered"); + +promise_test(async () => { + let builder = new WasmModuleBuilder(); + let import_index = builder.addImport('m', 'import', kSig_i_v); + builder.addFunction("test", kSig_i_i) + .addBody([ + kExprCallFunction, import_index, // suspend + ]).exportFunc(); + let js_import = () => 42; + let instance = builder.instantiate({ + m: { + import: js_import + } + }); + let wrapped_export = WebAssembly.promising(instance.exports.test); + let export_promise = wrapped_export(); + assert_true(export_promise instanceof Promise); + assert_equals(await export_promise, 42); +}, "Always get a Promise"); + +promise_test(async () => { + let builder = new WasmModuleBuilder(); + let import_index = builder.addImport('m', 'import', kSig_i_i); + builder.addFunction("test", kSig_i_i) + .addBody([ + kExprLocalGet, 0, + kExprCallFunction, import_index, // suspend + ]).exportFunc(); + let js_import = new WebAssembly.Suspending(() => Promise.resolve(42)); + let instance = builder.instantiate({ + m: { + import: js_import + } + }); + let wrapped_export = WebAssembly.promising(instance.exports.test); + let export_promise = wrapped_export(); + assert_true(export_promise instanceof Promise); + assert_equals(await export_promise, 42); +}, "Suspend once"); + +promise_test(async () => { + let builder = new WasmModuleBuilder(); + builder.addGlobal(kWasmI32, true).exportAs('g'); + let import_index = builder.addImport('m', 'import', kSig_i_v); + // void test() { + // for (i = 0; i < 5; ++i) { + // g = g + await import(); + // } + // } + builder.addFunction("test", kSig_v_i) + .addLocals({ + i32_count: 1 + }) + .addBody([ + kExprI32Const, 5, + kExprLocalSet, 1, + kExprLoop, kWasmStmt, + kExprCallFunction, import_index, // suspend + kExprGlobalGet, 0, + kExprI32Add, + kExprGlobalSet, 0, + kExprLocalGet, 1, + kExprI32Const, 1, + kExprI32Sub, + kExprLocalTee, 1, + kExprBrIf, 0, + kExprEnd, + ]).exportFunc(); + let i = 0; + + function js_import() { + return Promise.resolve(++i); + }; + let wasm_js_import = new WebAssembly.Suspending(js_import); + let instance = builder.instantiate({ + m: { + import: wasm_js_import + } + }); + let wrapped_export = WebAssembly.promising(instance.exports.test); + let export_promise = wrapped_export(); + assert_equals(instance.exports.g.value, 0); + assert_true(export_promise instanceof Promise); + await export_promise; + assert_equals(instance.exports.g.value, 15); +}, "Suspend/resume in a loop"); + +promise_test(async () => { + let builder = new WasmModuleBuilder(); + let import_index = builder.addImport('m', 'import', kSig_i_v); + builder.addFunction("test", kSig_i_v) + .addBody([ + kExprCallFunction, import_index, // suspend + ]).exportFunc(); + let js_import = new WebAssembly.Suspending(() => Promise.resolve(42)); + let instance = builder.instantiate({ + m: { + import: js_import + } + }); + let wrapped_export = WebAssembly.promising(instance.exports.test); + assert_equals(await wrapped_export(), 42); + + // Also try with a JS function with a mismatching arity. + js_import = new WebAssembly.Suspending((unused) => Promise.resolve(42)); + instance = builder.instantiate({ + m: { + import: js_import + } + }); + wrapped_export = WebAssembly.promising(instance.exports.test); + assert_equals(await wrapped_export(), 42); + + // Also try with a proxy. + js_import = new WebAssembly.Suspending(new Proxy(() => Promise.resolve(42), {})); + instance = builder.instantiate({ + m: { + import: js_import + } + }); + wrapped_export = WebAssembly.promising(instance.exports.test); + assert_equals(await wrapped_export(), 42); +},"Suspending with mismatched args and via Proxy"); + +function recordAbeforeB() { + let AbeforeB = []; + let setA = () => { + AbeforeB.push("A") + } + let setB = () => { + AbeforeB.push("B") + } + let isAbeforeB = () => + AbeforeB[0] == "A" && AbeforeB[1] == "B"; + + let isBbeforeA = () => + AbeforeB[0] == "B" && AbeforeB[1] == "A"; + + return { + setA: setA, + setB: setB, + isAbeforeB: isAbeforeB, + isBbeforeA: isBbeforeA, + } +} + +promise_test(async () => { + let builder = new WasmModuleBuilder(); + let AbeforeB = recordAbeforeB(); + let import42_index = builder.addImport('m', 'import42', kSig_i_i); + let importSetA_index = builder.addImport('m', 'setA', kSig_v_v); + builder.addFunction("test", kSig_i_i) + .addBody([ + kExprLocalGet, 0, + kExprCallFunction, import42_index, // suspend? + kExprCallFunction, importSetA_index + ]).exportFunc(); + let import42 = new WebAssembly.Suspending(() => Promise.resolve(42)); + let instance = builder.instantiate({ + m: { + import42: import42, + setA: AbeforeB.setA + } + }); + + let wrapped_export = WebAssembly.promising(instance.exports.test); + + let exported_promise = wrapped_export(); + + AbeforeB.setB(); + + assert_equals(await exported_promise, 42); + + assert_true(AbeforeB.isBbeforeA()); +}, "Make sure we actually suspend"); + +promise_test(async () => { + let builder = new WasmModuleBuilder(); + let AbeforeB = recordAbeforeB(); + let import42_index = builder.addImport('m', 'import42', kSig_i_i); + let importSetA_index = builder.addImport('m', 'setA', kSig_v_v); + builder.addFunction("test", kSig_i_i) + .addBody([ + kExprLocalGet, 0, + kExprCallFunction, import42_index, // suspend? + kExprCallFunction, importSetA_index + ]).exportFunc(); + let import42 = new WebAssembly.Suspending(() => 42); + let instance = builder.instantiate({ + m: { + import42: import42, + setA: AbeforeB.setA + } + }); + + let wrapped_export = WebAssembly.promising(instance.exports.test); + + let exported_promise = wrapped_export(); + AbeforeB.setB(); + + assert_equals(await exported_promise, 42); + assert_true(AbeforeB.isBbeforeA()); +}, "Do suspend even if the import's return value is not a Promise by wrapping it with Promise.resolve"); + +promise_test(async (t) => { + let tag = new WebAssembly.Tag({ + parameters: ['i32'] + }); + let builder = new WasmModuleBuilder(); + let import_index = builder.addImport('m', 'import', kSig_i_i); + let tag_index = builder.addImportedTag('m', 'tag', kSig_v_i); + builder.addFunction("test", kSig_i_i) + .addBody([ + kExprTry, kWasmI32, + kExprLocalGet, 0, + kExprCallFunction, import_index, + kExprCatch, tag_index, + kExprEnd + ]).exportFunc(); + + function js_import(unused) { + return Promise.reject(new WebAssembly.Exception(tag, [42])); + }; + let wasm_js_import = new WebAssembly.Suspending(js_import); + + let instance = builder.instantiate({ + m: { + import: wasm_js_import, + tag: tag + } + }); + let wrapped_export = WebAssembly.promising(instance.exports.test); + let export_promise = wrapped_export(); + assert_true(export_promise instanceof Promise); + assert_equals(await export_promise, 42); +}, "Catch rejected promise"); + +async function TestSandwich(suspend) { + // Set up a 'sandwich' scenario. The call chain looks like: + // top (wasm) -> outer (js) -> bottom (wasm) -> inner (js) + // If 'suspend' is true, the inner JS function returns a Promise, which + // suspends the bottom wasm function, which returns a Promise, which suspends + // the top wasm function, which returns a Promise. The inner Promise + // resolves first, which resumes the bottom continuation. Then the outer + // promise resolves which resumes the top continuation. + // If 'suspend' is false, the bottom JS function returns a regular value and + // no computation is suspended. + let builder = new WasmModuleBuilder(); + let inner_index = builder.addImport('m', 'inner', kSig_i_i); + let outer_index = builder.addImport('m', 'outer', kSig_i_i); + builder.addFunction("top", kSig_i_i) + .addBody([ + kExprLocalGet, 0, + kExprCallFunction, outer_index + ]).exportFunc(); + builder.addFunction("bottom", kSig_i_i) + .addBody([ + kExprLocalGet, 0, + kExprCallFunction, inner_index + ]).exportFunc(); + + let inner = new WebAssembly.Suspending(() => suspend ? Promise.resolve(42) : 43); + + let export_inner; + let outer = new WebAssembly.Suspending(() => export_inner()); + + let instance = builder.instantiate({ + m: { + inner, + outer + } + }); + export_inner = WebAssembly.promising(instance.exports.bottom); + let export_top = WebAssembly.promising(instance.exports.top); + let result = export_top(); + assert_true(result instanceof Promise); + if (suspend) + assert_equals(await result, 42); + else + assert_equals(await result, 43); +} + +promise_test(async () => { + TestSandwich(true); +}, "Test sandwich with suspension"); + +promise_test(async () => { + TestSandwich(false); +}, "Test sandwich with no suspension"); + +test(() => { + // Check that a promising function with no return is allowed. + let builder = new WasmModuleBuilder(); + builder.addFunction("export", kSig_v_v).addBody([]).exportFunc(); + let instance = builder.instantiate(); + let export_wrapper = WebAssembly.promising(instance.exports.export); + assert_true(export_wrapper instanceof Function); +},"Promising with no return"); + +promise_test(async () => { + let builder1 = new WasmModuleBuilder(); + let import_index = builder1.addImport('m', 'import', kSig_i_v); + builder1.addFunction("f", kSig_i_v) + .addBody([ + kExprCallFunction, import_index, // suspend + kExprI32Const, 1, + kExprI32Add, + ]).exportFunc(); + let js_import = new WebAssembly.Suspending(() => Promise.resolve(1)); + let instance1 = builder1.instantiate({ + m: { + import: js_import + } + }); + let builder2 = new WasmModuleBuilder(); + import_index = builder2.addImport('m', 'import', kSig_i_v); + builder2.addFunction("main", kSig_i_v) + .addBody([ + kExprCallFunction, import_index, + kExprI32Const, 1, + kExprI32Add, + ]).exportFunc(); + let instance2 = builder2.instantiate({ + m: { + import: instance1.exports.f + } + }); + let wrapped_export = WebAssembly.promising(instance2.exports.main); + assert_equals(await wrapped_export(), 3); +},"Suspend two modules"); \ No newline at end of file diff --git a/test/fixtures/wpt/wasm/jsapi/jspi/notraps.any.js b/test/fixtures/wpt/wasm/jsapi/jspi/notraps.any.js new file mode 100644 index 00000000000000..4060885f36cb6a --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/jspi/notraps.any.js @@ -0,0 +1,82 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js +// META: script=/wasm/jsapi/jspi/testharness-additions.js + +test(() => { + let builder = new WasmModuleBuilder(); + let js_tag = builder.addImportedTag("", "tag", kSig_v_r); + let try_sig_index = builder.addType(kSig_i_v); + + let promise42 = new WebAssembly.Suspending(() => Promise.resolve(42)); + let kPromise42Ref = builder.addImport("", "promise42", kSig_i_v); + + builder.addFunction("test", kSig_i_v) + .addBody([ + kExprTry, try_sig_index, + kExprCallFunction, kPromise42Ref, + kExprReturn, // If there was no trap or exception, return + kExprCatch, js_tag, + kExprI32Const, 43, + kExprReturn, + kExprEnd, + ]) + .exportFunc(); + + let instance = builder.instantiate({"": { + promise42: promise42, + tag: WebAssembly.JSTag, + }}); + + assert_equals(43, instance.exports.test()); + },"catch SuspendError"); + +promise_test(async t=>{ + let builder = new WasmModuleBuilder(); + let js_tag = builder.addImportedTag("", "tag", kSig_v_r); + let try_sig_index = builder.addType(kSig_i_v); + + let promise42 = new WebAssembly.Suspending(() => Promise.resolve(42)); + let kPromise42Ref = builder.addImport("", "promise42", kSig_i_v); + let backChnnlRef = builder.addImport("","backChnnl",kSig_i_v); + + builder.addFunction("main", kSig_i_v) + .addBody([ + kExprTry, try_sig_index, + kExprCallFunction, backChnnlRef, + kExprReturn, // If there was no trap or exception, return + kExprCatch, js_tag, + kExprI32Const, 43, + kExprReturn, + kExprEnd, + ]) + .exportFunc(); + + builder.addFunction("inner", kSig_i_v) + .addBody([ + kExprCallFunction, kPromise42Ref, + ]) + .exportFunc(); + + let backChnnl = ()=>instance.exports.inner(); + let instance = builder.instantiate({"": { + promise42: promise42, + backChnnl: backChnnl, + tag: WebAssembly.JSTag, + }}); + + wrapped_export = WebAssembly.promising(instance.exports.main); + + assert_equals(await wrapped_export(), 43); + },"throw on reentrance"); + +test(() => { + let builder = new WasmModuleBuilder(); + import_index = builder.addImport('m', 'import', kSig_v_v); + builder.addFunction("test", kSig_v_v) + .addBody([ + kExprCallFunction, import_index, + ]).exportFunc(); + let js_import = new WebAssembly.Suspending(() => Promise.resolve()); + let instance = builder.instantiate({m: {import: js_import}}); + assert_throws_js(WebAssembly.SuspendError, instance.exports.test); +},"unwrapped export"); diff --git a/test/fixtures/wpt/wasm/jsapi/jspi/rejects.any.js b/test/fixtures/wpt/wasm/jsapi/jspi/rejects.any.js new file mode 100644 index 00000000000000..55c5abc9a8a6ca --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/jspi/rejects.any.js @@ -0,0 +1,150 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js +// META: script=/wasm/jsapi/jspi/testharness-additions.js + +promise_test(t => { + let tag = new WebAssembly.Tag({ + parameters: [] + }); + let builder = new WasmModuleBuilder(); + import_index = builder.addImport('m', 'import', kSig_i_i); + tag_index = builder.addImportedTag('m', 'tag', kSig_v_v); + builder.addFunction("test", kSig_i_i) + .addBody([ + kExprLocalGet, 0, + kExprCallFunction, import_index, + kExprThrow, tag_index + ]).exportFunc(); + + function js_import() { + return Promise.resolve(); + }; + let wasm_js_import = new WebAssembly.Suspending(js_import); + + let instance = builder.instantiate({ + m: { + import: wasm_js_import, + tag: tag + } + }); + let wrapped_export = WebAssembly.promising(instance.exports.test); + let export_promise = wrapped_export(); + assert_true(export_promise instanceof Promise); + return promise_rejects_jspi(t, new WebAssembly.Exception(tag, []), export_promise); +}, "Throw after the first suspension"); + +// Throw an exception before suspending. The export wrapper should return a +// promise rejected with the exception. +promise_test(async (t) => { + let tag = new WebAssembly.Tag({ + parameters: [] + }); + let builder = new WasmModuleBuilder(); + tag_index = builder.addImportedTag('m', 'tag', kSig_v_v); + builder.addFunction("test", kSig_i_v) + .addBody([ + kExprThrow, tag_index + ]).exportFunc(); + + let instance = builder.instantiate({ + m: { + tag: tag + } + }); + let wrapped_export = WebAssembly.promising(instance.exports.test); + let export_promise = wrapped_export(); + + promise_rejects_jspi(t, new WebAssembly.Exception(tag, []), export_promise); +}, "Throw before suspending"); + +// Throw an exception after the first resume event, which propagates to the +// promise wrapper. +promise_test(async (t) => { + let tag = new WebAssembly.Tag({ + parameters: [] + }); + let builder = new WasmModuleBuilder(); + import_index = builder.addImport('m', 'import', kSig_i_v); + tag_index = builder.addImportedTag('m', 'tag', kSig_v_v); + builder.addFunction("test", kSig_i_v) + .addBody([ + kExprCallFunction, import_index, + kExprThrow, tag_index + ]).exportFunc(); + + function js_import() { + return Promise.resolve(42); + }; + let wasm_js_import = new WebAssembly.Suspending(js_import); + + let instance = builder.instantiate({ + m: { + import: wasm_js_import, + tag: tag + } + }); + let wrapped_export = WebAssembly.promising(instance.exports.test); + let export_promise = wrapped_export(); + + promise_rejects_jspi(t, new WebAssembly.Exception(tag, []), export_promise); +}, "Throw and propagate via Promise"); + +promise_test(async (t) => { + let builder = new WasmModuleBuilder(); + builder.addFunction("test", kSig_i_v) + .addBody([ + kExprCallFunction, 0 + ]).exportFunc(); + let instance = builder.instantiate(); + let wrapper = WebAssembly.promising(instance.exports.test); + + // Stack overflow has undefined behavior -- check if an exception was thrown. + let thrown = false; + try { + await wrapper(); + } catch (e) { + thrown = true; + } + assert_true(thrown); +}, "Stack overflow"); + +promise_test(async (t) => { + // The call stack of this test looks like: + // export1 -> import1 -> export2 -> import2 + // Where export1 is "promising" and import2 is "suspending". Returning a + // promise from import2 should trap because of the JS import in the middle. + let builder = new WasmModuleBuilder(); + let import1_index = builder.addImport("m", "import1", kSig_i_v); + let import2_index = builder.addImport("m", "import2", kSig_i_v); + builder.addFunction("export1", kSig_i_v) + .addBody([ + // export1 -> import1 (unwrapped) + kExprCallFunction, import1_index, + ]).exportFunc(); + builder.addFunction("export2", kSig_i_v) + .addBody([ + // export2 -> import2 (suspending) + kExprCallFunction, import2_index, + ]).exportFunc(); + let instance; + + function import1() { + // import1 -> export2 (unwrapped) + instance.exports.export2(); + } + + function import2() { + return Promise.resolve(0); + } + import2 = new WebAssembly.Suspending(import2); + instance = builder.instantiate({ + 'm': { + 'import1': import1, + 'import2': import2 + } + }); + // export1 (promising) + let wrapper = WebAssembly.promising(instance.exports.export1); + promise_rejects_js(t, WebAssembly.SuspendError, wrapper(), + "trying to suspend JS frames"); +}, "Try to suspend JS"); \ No newline at end of file diff --git a/test/fixtures/wpt/wasm/jsapi/jspi/testharness-additions.js b/test/fixtures/wpt/wasm/jsapi/jspi/testharness-additions.js new file mode 100644 index 00000000000000..e146c52f96dfba --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/jspi/testharness-additions.js @@ -0,0 +1,26 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +function assert_throws_jspi(code, func, description) { + try { + func(); + } catch (e) { + assert_true( + e.name === code.name, + 'expected exception ' + code.name + ', got ' + e.name); + return; + } + assert_true( + false, 'expected exception ' + code.name + ', no exception thrown'); + } + + function promise_rejects_jspi(test, expected, promise, description) { + return promise + .then(() => assert_unreached('Should have rejected: ' + description)) + .catch(function(e) { + assert_throws_jspi(expected, function() { + throw e; + }, description); + }); + } diff --git a/test/fixtures/wpt/wasm/jsapi/memory/WEB_FEATURES.yml b/test/fixtures/wpt/wasm/jsapi/memory/WEB_FEATURES.yml new file mode 100644 index 00000000000000..50f9ae19b6707b --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/memory/WEB_FEATURES.yml @@ -0,0 +1,12 @@ +features: +- name: wasm + files: + - "*" + - "!constructor-shared.tentative.any.js" + - "!to-fixed-length-buffer-shared.any.js" + - "!to-resizable-buffer-shared.any.js" +- name: wasm-threads + files: + - "constructor-shared.tentative.any.js" + - "to-fixed-length-buffer-shared.any.js" + - "to-resizable-buffer-shared.any.js" diff --git a/test/fixtures/wpt/wasm/jsapi/memory/assertions.js b/test/fixtures/wpt/wasm/jsapi/memory/assertions.js index b539513adcab7d..1430c523882307 100644 --- a/test/fixtures/wpt/wasm/jsapi/memory/assertions.js +++ b/test/fixtures/wpt/wasm/jsapi/memory/assertions.js @@ -26,6 +26,7 @@ function assert_ArrayBuffer(actual, { size=0, shared=false, detached=false }, me assert_equals(Object.isFrozen(actual), shared, "buffer frozen"); assert_equals(Object.isExtensible(actual), !shared, "buffer extensibility"); } +globalThis.assert_ArrayBuffer = assert_ArrayBuffer; function assert_Memory(memory, { size=0, shared=false }) { assert_equals(Object.getPrototypeOf(memory), WebAssembly.Memory.prototype, @@ -36,3 +37,4 @@ function assert_Memory(memory, { size=0, shared=false }) { assert_equals(memory.buffer, memory.buffer, "buffer should be idempotent"); assert_ArrayBuffer(memory.buffer, { size, shared }); } +globalThis.assert_Memory = assert_Memory; diff --git a/test/fixtures/wpt/wasm/jsapi/memory/constructor-shared.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/memory/constructor-shared.tentative.any.js index 216fc4ca55591f..0134b307749ed7 100644 --- a/test/fixtures/wpt/wasm/jsapi/memory/constructor-shared.tentative.any.js +++ b/test/fixtures/wpt/wasm/jsapi/memory/constructor-shared.tentative.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/assertions.js // META: script=/wasm/jsapi/memory/assertions.js diff --git a/test/fixtures/wpt/wasm/jsapi/memory/constructor-types.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/memory/constructor-types.tentative.any.js index d5378dbe82b00b..4653c6686a78e4 100644 --- a/test/fixtures/wpt/wasm/jsapi/memory/constructor-types.tentative.any.js +++ b/test/fixtures/wpt/wasm/jsapi/memory/constructor-types.tentative.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/assertions.js // META: script=/wasm/jsapi/memory/assertions.js @@ -17,4 +17,4 @@ test(() => { const argument = { minimum: 4 }; const memory = new WebAssembly.Memory(argument); assert_Memory(memory, { "size": 4 }); - }, "Non-zero minimum"); \ No newline at end of file + }, "Non-zero minimum"); diff --git a/test/fixtures/wpt/wasm/jsapi/memory/constructor.any.js b/test/fixtures/wpt/wasm/jsapi/memory/constructor.any.js index 0a0be11e370877..8f413c9f48a86d 100644 --- a/test/fixtures/wpt/wasm/jsapi/memory/constructor.any.js +++ b/test/fixtures/wpt/wasm/jsapi/memory/constructor.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/assertions.js // META: script=/wasm/jsapi/memory/assertions.js diff --git a/test/fixtures/wpt/wasm/jsapi/memory/grow.any.js b/test/fixtures/wpt/wasm/jsapi/memory/grow.any.js index c511129491f4de..2eafe5761eaa6d 100644 --- a/test/fixtures/wpt/wasm/jsapi/memory/grow.any.js +++ b/test/fixtures/wpt/wasm/jsapi/memory/grow.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/memory/assertions.js test(() => { diff --git a/test/fixtures/wpt/wasm/jsapi/memory/to-fixed-length-buffer-shared.any.js b/test/fixtures/wpt/wasm/jsapi/memory/to-fixed-length-buffer-shared.any.js new file mode 100644 index 00000000000000..ddaab236f41d8d --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/memory/to-fixed-length-buffer-shared.any.js @@ -0,0 +1,17 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js + +test(() => { + const memory = new WebAssembly.Memory({ initial: 0, maximum: 4, shared: true }); + const buffer1 = memory.buffer; + + assert_false(buffer1.growable, "By default the SAB is initially not growable"); + + const buffer2 = memory.toFixedLengthBuffer(); + assert_equals(buffer1, buffer2, "Not changing resizability does not make a new object"); + + const buffer3 = memory.toResizableBuffer(); + assert_not_equals(buffer2, buffer3, "Changing resizability makes a new object"); + assert_true(buffer3.growable); + assert_equals(memory.buffer, buffer3); +}, "toFixedLengthBuffer caching behavior"); diff --git a/test/fixtures/wpt/wasm/jsapi/memory/to-fixed-length-buffer.any.js b/test/fixtures/wpt/wasm/jsapi/memory/to-fixed-length-buffer.any.js new file mode 100644 index 00000000000000..d5e6d65b74782f --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/memory/to-fixed-length-buffer.any.js @@ -0,0 +1,42 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js + +test(() => { + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Memory, + WebAssembly.Memory.prototype, + ]; + + const desc = Object.getOwnPropertyDescriptor(WebAssembly.Memory.prototype, "toFixedLengthBuffer"); + assert_equals(typeof desc, "object"); + + const fun = desc.value; + assert_equals(typeof desc.value, "function"); + + for (const thisValue of thisValues) { + assert_throws_js(TypeError, () => fun.call(thisValue), `this=${format_value(thisValue)}`); + } +}, "API surface"); + +test(() => { + const memory = new WebAssembly.Memory({ initial: 0, maximum: 1 }); + const buffer1 = memory.buffer; + + assert_false(buffer1.resizable, "By default the AB is initially not resizable"); + + const buffer2 = memory.toFixedLengthBuffer(); + assert_equals(buffer1, buffer2, "Not changing resizability does not make a new object"); + + const buffer3 = memory.toResizableBuffer(); + assert_not_equals(buffer2, buffer3, "Changing resizability makes a new object"); + assert_true(buffer3.resizable); + assert_true(buffer2.detached); + assert_equals(memory.buffer, buffer3); +}, "toFixedLengthBuffer caching behavior"); diff --git a/test/fixtures/wpt/wasm/jsapi/memory/to-resizable-buffer-shared.any.js b/test/fixtures/wpt/wasm/jsapi/memory/to-resizable-buffer-shared.any.js new file mode 100644 index 00000000000000..3ef0e80995751a --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/memory/to-resizable-buffer-shared.any.js @@ -0,0 +1,36 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js + +test(() => { + const memory = new WebAssembly.Memory({ initial: 0, maximum: 4, shared: true }); + const buffer1 = memory.buffer; + + assert_false(buffer1.growable, "By default the SAB is initially not growable"); + + const buffer2 = memory.toResizableBuffer(); + assert_true(buffer2.growable); + assert_not_equals(buffer1, buffer2, "Changing resizability makes a new object"); + assert_equals(memory.buffer, buffer2, "The buffer created by the most recent toFooBuffer call is cached"); + + const buffer3 = memory.toResizableBuffer(); + assert_equals(buffer2, buffer3, "toResizableBuffer does nothing if buffer is already resizable") + assert_equals(memory.buffer, buffer3); +}, "toResizableBuffer caching behavior"); + +test(() => { + const maxNumPages = 4; + const memory = new WebAssembly.Memory({ initial: 0, maximum: maxNumPages, shared: true }); + const buffer = memory.toResizableBuffer(); + assert_equals(buffer.maxByteLength, kPageSize * maxNumPages, "Memory maximum is same as maxByteLength"); +}, "toResizableBuffer max size"); + +test(() => { + const memory = new WebAssembly.Memory({ initial: 0, maximum: 4, shared: true }); + const buffer = memory.toResizableBuffer(); + + assert_equals(buffer.byteLength, 0); + buffer.grow(2 * kPageSize); + assert_equals(buffer.byteLength, 2 * kPageSize); + + assert_throws_js(RangeError, () => buffer.grow(3 * kPageSize - 1), "Can only grow by page multiples"); +}, "Resizing a Memory's resizable buffer"); diff --git a/test/fixtures/wpt/wasm/jsapi/memory/to-resizable-buffer.any.js b/test/fixtures/wpt/wasm/jsapi/memory/to-resizable-buffer.any.js new file mode 100644 index 00000000000000..1f46fa7f9a7e3b --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/memory/to-resizable-buffer.any.js @@ -0,0 +1,72 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js + +test(() => { + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Memory, + WebAssembly.Memory.prototype, + ]; + + const desc = Object.getOwnPropertyDescriptor(WebAssembly.Memory.prototype, "toResizableBuffer"); + assert_equals(typeof desc, "object"); + + const fun = desc.value; + assert_equals(typeof desc.value, "function"); + + for (const thisValue of thisValues) { + assert_throws_js(TypeError, () => fun.call(thisValue), `this=${format_value(thisValue)}`); + } +}, "API surface"); + +test(() => { + const memory = new WebAssembly.Memory({ initial: 0, maximum: 1 }); + const buffer1 = memory.buffer; + + assert_false(buffer1.resizable, "By default the AB is initially not resizable"); + + const buffer2 = memory.toResizableBuffer(); + assert_true(buffer2.resizable); + assert_not_equals(buffer1, buffer2, "Changing resizability makes a new object"); + assert_true(buffer1.detached); + assert_equals(memory.buffer, buffer2, "The buffer created by the most recent toFooBuffer call is cached"); + + const buffer3 = memory.toResizableBuffer(); + assert_equals(buffer2, buffer3, "toResizableBuffer does nothing if buffer is already resizable") + assert_equals(memory.buffer, buffer3); + +}, "toResizableBuffer caching behavior"); + +test(() => { + { + const maxNumPages = 4; + const memory = new WebAssembly.Memory({ initial: 0, maximum: maxNumPages }); + const buffer = memory.toResizableBuffer(); + assert_equals(buffer.maxByteLength, kPageSize * maxNumPages, "Memory maximum is same as maxByteLength"); + } +}, "toResizableBuffer max size"); + +test(() => { + const maxNumPages = 4; + const memory = new WebAssembly.Memory({ initial: 0, maximum: maxNumPages }); + const buffer = memory.toResizableBuffer(); + + assert_equals(buffer.byteLength, 0); + buffer.resize(2 * kPageSize); + assert_equals(buffer.byteLength, 2 * kPageSize); + + assert_throws_js(RangeError, () => buffer.resize(3 * kPageSize - 1), "Can only grow by page multiples"); + assert_throws_js(RangeError, () => buffer.resize(1 * kPageSize), "Cannot shrink"); +}, "Resizing a Memory's resizable buffer"); + +test(() => { + const memory = new WebAssembly.Memory({ initial: 0, maximum: 1 }); + const buffer = memory.toResizableBuffer(); + assert_throws_js(TypeError, () => buffer.transfer(), "Cannot be detached by JS"); +}, "Resizable buffers from Memory cannot be detached by JS"); diff --git a/test/fixtures/wpt/wasm/jsapi/memory/type.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/memory/type.tentative.any.js index a96a3227adca7f..3f6531f5967ed7 100644 --- a/test/fixtures/wpt/wasm/jsapi/memory/type.tentative.any.js +++ b/test/fixtures/wpt/wasm/jsapi/memory/type.tentative.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/assertions.js function assert_type(argument) { @@ -34,4 +34,4 @@ test(() => { test(() => { assert_type({ "minimum": 0, "maximum": 10, "shared": true}); -}, "shared memory"); \ No newline at end of file +}, "shared memory"); diff --git a/test/fixtures/wpt/wasm/jsapi/module/WEB_FEATURES.yml b/test/fixtures/wpt/wasm/jsapi/module/WEB_FEATURES.yml new file mode 100644 index 00000000000000..d8e24fc0e8f503 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/module/WEB_FEATURES.yml @@ -0,0 +1,3 @@ +features: +- name: wasm + files: "**" diff --git a/test/fixtures/wpt/wasm/jsapi/module/constructor.any.js b/test/fixtures/wpt/wasm/jsapi/module/constructor.any.js index 9978f7e6ac8f2b..95604aabe475a8 100644 --- a/test/fixtures/wpt/wasm/jsapi/module/constructor.any.js +++ b/test/fixtures/wpt/wasm/jsapi/module/constructor.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/wasm-module-builder.js // META: script=/wasm/jsapi/assertions.js diff --git a/test/fixtures/wpt/wasm/jsapi/module/customSections.any.js b/test/fixtures/wpt/wasm/jsapi/module/customSections.any.js index 4029877e92c7b8..96958316e061d7 100644 --- a/test/fixtures/wpt/wasm/jsapi/module/customSections.any.js +++ b/test/fixtures/wpt/wasm/jsapi/module/customSections.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/wasm-module-builder.js function assert_ArrayBuffer(buffer, expected) { diff --git a/test/fixtures/wpt/wasm/jsapi/module/exports.any.js b/test/fixtures/wpt/wasm/jsapi/module/exports.any.js index 40a3935a4a23ba..0c32e984a2cad1 100644 --- a/test/fixtures/wpt/wasm/jsapi/module/exports.any.js +++ b/test/fixtures/wpt/wasm/jsapi/module/exports.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/wasm-module-builder.js let emptyModuleBinary; @@ -109,10 +109,10 @@ test(() => { builder.addGlobal(kWasmI32, true) .exportAs("global") - .init = 7; + .init = wasmI32Const(7); builder.addGlobal(kWasmF64, true) .exportAs("global2") - .init = 1.2; + .init = wasmF64Const(1.2); builder.addMemory(0, 256, true); @@ -167,7 +167,7 @@ test(() => { builder.addGlobal(kWasmI32, true) .exportAs("") - .init = 7; + .init = wasmI32Const(7); const buffer = builder.toBuffer() const module = new WebAssembly.Module(buffer); diff --git a/test/fixtures/wpt/wasm/jsapi/module/imports.any.js b/test/fixtures/wpt/wasm/jsapi/module/imports.any.js index ec550ce6c41af1..2ab1725359f187 100644 --- a/test/fixtures/wpt/wasm/jsapi/module/imports.any.js +++ b/test/fixtures/wpt/wasm/jsapi/module/imports.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/wasm-module-builder.js function assert_ModuleImportDescriptor(import_, expected) { diff --git a/test/fixtures/wpt/wasm/jsapi/module/moduleSource.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/module/moduleSource.tentative.any.js new file mode 100644 index 00000000000000..8a94cdd48cd497 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/module/moduleSource.tentative.any.js @@ -0,0 +1,35 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/wasm-module-builder.js +// META: script=/wasm/jsapi/assertions.js + +let emptyModuleBinary; +setup(() => { + emptyModuleBinary = new WasmModuleBuilder().toBuffer(); +}); + +test(() => { + assert_equals(typeof AbstractModuleSource, "undefined"); +}, "AbstractModuleSource is not a global"); + +test(() => { + const AbstractModuleSource = Object.getPrototypeOf(WebAssembly.Module); + assert_equals(AbstractModuleSource.name, "AbstractModuleSource"); + assert_not_equals(AbstractModuleSource, Function); +}, "AbstractModuleSource intrinsic"); + +test(() => { + const AbstractModuleSourceProto = Object.getPrototypeOf(WebAssembly.Module.prototype); + assert_not_equals(AbstractModuleSourceProto, Object.prototype); + const AbstractModuleSource = Object.getPrototypeOf(WebAssembly.Module); + assert_equals(AbstractModuleSource.prototype, AbstractModuleSourceProto); +}, "AbstractModuleSourceProto intrinsic"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + + const AbstractModuleSource = Object.getPrototypeOf(WebAssembly.Module); + const toStringTag = Object.getOwnPropertyDescriptor(AbstractModuleSource.prototype, Symbol.toStringTag).get; + + assert_equals(toStringTag.call(module), "WebAssembly.Module"); + assert_throws_js(TypeError, () => toStringTag.call({})); +}, "AbstractModuleSourceProto toStringTag brand check"); \ No newline at end of file diff --git a/test/fixtures/wpt/wasm/jsapi/module/toString.any.js b/test/fixtures/wpt/wasm/jsapi/module/toString.any.js index 1c20cd6108c84e..10c707979d5bdc 100644 --- a/test/fixtures/wpt/wasm/jsapi/module/toString.any.js +++ b/test/fixtures/wpt/wasm/jsapi/module/toString.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/wasm-module-builder.js test(() => { diff --git a/test/fixtures/wpt/wasm/jsapi/prototypes.any.js b/test/fixtures/wpt/wasm/jsapi/prototypes.any.js index 714f4f8430e5eb..2316f7d9b4eeb5 100644 --- a/test/fixtures/wpt/wasm/jsapi/prototypes.any.js +++ b/test/fixtures/wpt/wasm/jsapi/prototypes.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/assertions.js // META: script=/wasm/jsapi/wasm-module-builder.js diff --git a/test/fixtures/wpt/wasm/jsapi/table/WEB_FEATURES.yml b/test/fixtures/wpt/wasm/jsapi/table/WEB_FEATURES.yml new file mode 100644 index 00000000000000..d8e24fc0e8f503 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/table/WEB_FEATURES.yml @@ -0,0 +1,3 @@ +features: +- name: wasm + files: "**" diff --git a/test/fixtures/wpt/wasm/jsapi/table/assertions.js b/test/fixtures/wpt/wasm/jsapi/table/assertions.js index 19cc5c3b92d6fa..4fcd3517a6a869 100644 --- a/test/fixtures/wpt/wasm/jsapi/table/assertions.js +++ b/test/fixtures/wpt/wasm/jsapi/table/assertions.js @@ -11,6 +11,7 @@ function assert_equal_to_array(table, expected, message) { assert_throws_js(RangeError, () => table.get(expected.length + 1), `${message}: table.get(${expected.length + 1} of ${expected.length})`); } +globalThis.assert_equal_to_array = assert_equal_to_array; function assert_Table(actual, expected) { assert_equals(Object.getPrototypeOf(actual), WebAssembly.Table.prototype, @@ -22,3 +23,4 @@ function assert_Table(actual, expected) { assert_equals(actual.get(i), null, `actual.get(${i})`); } } +globalThis.assert_Table = assert_Table; diff --git a/test/fixtures/wpt/wasm/jsapi/table/constructor-types.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/table/constructor-types.tentative.any.js index 99ca41b55a9152..b4015bdc6f7503 100644 --- a/test/fixtures/wpt/wasm/jsapi/table/constructor-types.tentative.any.js +++ b/test/fixtures/wpt/wasm/jsapi/table/constructor-types.tentative.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/assertions.js // META: script=/wasm/jsapi/table/assertions.js @@ -17,4 +17,4 @@ test(() => { const argument = { "element": "anyfunc", "minimum": 5 }; const table = new WebAssembly.Table(argument); assert_Table(table, { "length": 5 }); -}, "Non-zero minimum"); \ No newline at end of file +}, "Non-zero minimum"); diff --git a/test/fixtures/wpt/wasm/jsapi/table/constructor.any.js b/test/fixtures/wpt/wasm/jsapi/table/constructor.any.js index 6d38d04e4f5050..1143bab72a26ce 100644 --- a/test/fixtures/wpt/wasm/jsapi/table/constructor.any.js +++ b/test/fixtures/wpt/wasm/jsapi/table/constructor.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/wasm-module-builder.js // META: script=/wasm/jsapi/assertions.js // META: script=/wasm/jsapi/table/assertions.js @@ -206,3 +206,8 @@ test(() => { assert_throws_js(TypeError, () => new WebAssembly.Table(argument, "cannot be used as a wasm function")); assert_throws_js(TypeError, () => new WebAssembly.Table(argument, 37)); }, "initialize anyfunc table with a bad default value"); + +test(() => { + assert_throws_js(RangeError, () => + new WebAssembly.Table({ "element": "anyfunc", "initial": 3, "maximum": 2 }, 37)); +}, "initialize anyfunc table with a bad default value and a bad descriptor"); diff --git a/test/fixtures/wpt/wasm/jsapi/table/get-set.any.js b/test/fixtures/wpt/wasm/jsapi/table/get-set.any.js index 9301057a533ed4..abe6fecac9860c 100644 --- a/test/fixtures/wpt/wasm/jsapi/table/get-set.any.js +++ b/test/fixtures/wpt/wasm/jsapi/table/get-set.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/wasm-module-builder.js // META: script=assertions.js diff --git a/test/fixtures/wpt/wasm/jsapi/table/grow.any.js b/test/fixtures/wpt/wasm/jsapi/table/grow.any.js index 520d24bf4bafbb..4038f1e64934b0 100644 --- a/test/fixtures/wpt/wasm/jsapi/table/grow.any.js +++ b/test/fixtures/wpt/wasm/jsapi/table/grow.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/wasm-module-builder.js // META: script=assertions.js diff --git a/test/fixtures/wpt/wasm/jsapi/table/length.any.js b/test/fixtures/wpt/wasm/jsapi/table/length.any.js index a9ef095ded4458..0e6de3f83e1ecf 100644 --- a/test/fixtures/wpt/wasm/jsapi/table/length.any.js +++ b/test/fixtures/wpt/wasm/jsapi/table/length.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm test(() => { const thisValues = [ diff --git a/test/fixtures/wpt/wasm/jsapi/table/toString.any.js b/test/fixtures/wpt/wasm/jsapi/table/toString.any.js index 8a09f2832c1d64..b5fb25b9c1e954 100644 --- a/test/fixtures/wpt/wasm/jsapi/table/toString.any.js +++ b/test/fixtures/wpt/wasm/jsapi/table/toString.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm test(() => { const argument = { "element": "anyfunc", "initial": 0 }; diff --git a/test/fixtures/wpt/wasm/jsapi/table/type.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/table/type.tentative.any.js index ef1ceecb17d695..ef9bbc5aa4a228 100644 --- a/test/fixtures/wpt/wasm/jsapi/table/type.tentative.any.js +++ b/test/fixtures/wpt/wasm/jsapi/table/type.tentative.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/assertions.js function assert_type(argument) { diff --git a/test/fixtures/wpt/wasm/jsapi/tag/WEB_FEATURES.yml b/test/fixtures/wpt/wasm/jsapi/tag/WEB_FEATURES.yml new file mode 100644 index 00000000000000..ffbe372e8d6523 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/tag/WEB_FEATURES.yml @@ -0,0 +1,3 @@ +features: +- name: wasm-exception-handling + files: "**" diff --git a/test/fixtures/wpt/wasm/jsapi/tag/constructor.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/tag/constructor.tentative.any.js index de63e7bf46d1e8..54edf8c8f59258 100644 --- a/test/fixtures/wpt/wasm/jsapi/tag/constructor.tentative.any.js +++ b/test/fixtures/wpt/wasm/jsapi/tag/constructor.tentative.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/assertions.js test(() => { diff --git a/test/fixtures/wpt/wasm/jsapi/tag/toString.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/tag/toString.tentative.any.js index ad9a4ba152f4f8..76fff0feef063d 100644 --- a/test/fixtures/wpt/wasm/jsapi/tag/toString.tentative.any.js +++ b/test/fixtures/wpt/wasm/jsapi/tag/toString.tentative.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm test(() => { const argument = { parameters: [] }; diff --git a/test/fixtures/wpt/wasm/jsapi/tag/type.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/tag/type.tentative.any.js index 9d2f0de1a00f20..58c96078bfe39d 100644 --- a/test/fixtures/wpt/wasm/jsapi/tag/type.tentative.any.js +++ b/test/fixtures/wpt/wasm/jsapi/tag/type.tentative.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/assertions.js function assert_type(argument) { diff --git a/test/fixtures/wpt/wasm/jsapi/wasm-module-builder.js b/test/fixtures/wpt/wasm/jsapi/wasm-module-builder.js index 86d836a5a37245..e8c65ed8c12100 100644 --- a/test/fixtures/wpt/wasm/jsapi/wasm-module-builder.js +++ b/test/fixtures/wpt/wasm/jsapi/wasm-module-builder.js @@ -45,7 +45,7 @@ var kWasmV3 = 0; var kHeaderSize = 8; var kPageSize = 65536; -var kSpecMaxPages = 65535; +var kSpecMaxPages = 65536; var kMaxVarInt32Size = 5; var kMaxVarInt64Size = 10; @@ -74,6 +74,14 @@ let kLocalNamesCode = 2; let kWasmFunctionTypeForm = 0x60; let kWasmAnyFunctionTypeForm = 0x70; +let kWasmStructTypeForm = 0x5f; +let kWasmArrayTypeForm = 0x5e; +let kWasmSubtypeForm = 0x50; +let kWasmSubtypeFinalForm = 0x4f; +let kWasmRecursiveTypeGroupForm = 0x4e; + +let kNoSuperType = 0xFFFFFFFF; +globalThis.kNoSuperType = kNoSuperType; let kHasMaximumFlag = 1; let kSharedHasMaximumFlag = 3; @@ -97,8 +105,57 @@ let kWasmI64 = 0x7e; let kWasmF32 = 0x7d; let kWasmF64 = 0x7c; let kWasmS128 = 0x7b; -let kWasmAnyRef = 0x6f; -let kWasmAnyFunc = 0x70; + +// Packed storage types +let kWasmI8 = 0x78; +let kWasmI16 = 0x77; + +// These are defined as negative integers to distinguish them from positive type +// indices. +let kWasmNullFuncRef = -0x0d; +let kWasmNullExternRef = -0x0e; +let kWasmNullRef = -0x0f; +let kWasmFuncRef = -0x10; +let kWasmAnyFunc = kWasmFuncRef; // Alias named as in the JS API spec +let kWasmExternRef = -0x11; +let kWasmAnyRef = -0x12; +let kWasmEqRef = -0x13; +let kWasmI31Ref = -0x14; +let kWasmStructRef = -0x15; +let kWasmArrayRef = -0x16; + +// Use the positive-byte versions inside function bodies. +let kLeb128Mask = 0x7f; +let kFuncRefCode = kWasmFuncRef & kLeb128Mask; +let kAnyFuncCode = kFuncRefCode; // Alias named as in the JS API spec +let kExternRefCode = kWasmExternRef & kLeb128Mask; +let kAnyRefCode = kWasmAnyRef & kLeb128Mask; +let kEqRefCode = kWasmEqRef & kLeb128Mask; +let kI31RefCode = kWasmI31Ref & kLeb128Mask; +let kNullExternRefCode = kWasmNullExternRef & kLeb128Mask; +let kNullFuncRefCode = kWasmNullFuncRef & kLeb128Mask; +let kStructRefCode = kWasmStructRef & kLeb128Mask; +let kArrayRefCode = kWasmArrayRef & kLeb128Mask; +let kNullRefCode = kWasmNullRef & kLeb128Mask; + +let kWasmRefNull = 0x63; +let kWasmRef = 0x64; +function wasmRefNullType(heap_type) { + return {opcode: kWasmRefNull, heap_type: heap_type}; +} +function wasmRefType(heap_type) { + return {opcode: kWasmRef, heap_type: heap_type}; +} + +Object.assign(globalThis, { + kWasmStmt, kWasmI32, kWasmI64, kWasmF32, kWasmF64, kWasmS128, kWasmI8, + kWasmI16, kWasmNullFuncRef, kWasmNullExternRef, kWasmNullRef, kWasmFuncRef, + kWasmAnyFunc, kWasmExternRef, kWasmAnyRef, kWasmEqRef, kWasmI31Ref, + kWasmStructRef, kWasmArrayRef, kFuncRefCode, kAnyFuncCode, kExternRefCode, + kAnyRefCode, kEqRefCode, kI31RefCode, kNullExternRefCode, kNullFuncRefCode, + kStructRefCode, kArrayRefCode, kNullRefCode, kWasmRefNull, kWasmRef, + wasmRefNullType, wasmRefType +}); let kExternalFunction = 0; let kExternalTable = 1; @@ -106,6 +163,11 @@ let kExternalMemory = 2; let kExternalGlobal = 3; let kExternalTag = 4; +Object.assign(globalThis, { + kExternalFunction, kExternalTable, kExternalMemory, kExternalGlobal, + kExternalTag +}); + let kTableZero = 0; let kMemoryZero = 0; let kSegmentZero = 0; @@ -146,14 +208,14 @@ let kSig_v_f = makeSig([kWasmF32], []); let kSig_f_f = makeSig([kWasmF32], [kWasmF32]); let kSig_f_d = makeSig([kWasmF64], [kWasmF32]); let kSig_d_d = makeSig([kWasmF64], [kWasmF64]); -let kSig_r_r = makeSig([kWasmAnyRef], [kWasmAnyRef]); +let kSig_r_r = makeSig([kWasmExternRef], [kWasmExternRef]); let kSig_a_a = makeSig([kWasmAnyFunc], [kWasmAnyFunc]); -let kSig_i_r = makeSig([kWasmAnyRef], [kWasmI32]); -let kSig_v_r = makeSig([kWasmAnyRef], []); +let kSig_i_r = makeSig([kWasmExternRef], [kWasmI32]); +let kSig_v_r = makeSig([kWasmExternRef], []); let kSig_v_a = makeSig([kWasmAnyFunc], []); -let kSig_v_rr = makeSig([kWasmAnyRef, kWasmAnyRef], []); +let kSig_v_rr = makeSig([kWasmExternRef, kWasmExternRef], []); let kSig_v_aa = makeSig([kWasmAnyFunc, kWasmAnyFunc], []); -let kSig_r_v = makeSig([], [kWasmAnyRef]); +let kSig_r_v = makeSig([], [kWasmExternRef]); let kSig_a_v = makeSig([], [kWasmAnyFunc]); let kSig_a_i = makeSig([kWasmI32], [kWasmAnyFunc]); @@ -181,6 +243,17 @@ function makeSig_r_xx(r, x) { return makeSig([x, x], [r]); } +Object.assign(globalThis, { + kSig_i_i, kSig_l_l, kSig_i_l, kSig_i_ii, kSig_i_iii, kSig_v_iiii, kSig_f_ff, + kSig_d_dd, kSig_l_ll, kSig_i_dd, kSig_v_v, kSig_i_v, kSig_l_v, kSig_f_v, + kSig_d_v, kSig_v_i, kSig_v_ii, kSig_v_iii, kSig_v_l, kSig_v_d, kSig_v_dd, + kSig_v_ddi, kSig_ii_v, kSig_iii_v, kSig_ii_i, kSig_iii_i, kSig_ii_ii, + kSig_iii_ii, kSig_v_f, kSig_f_f, kSig_f_d, kSig_d_d, kSig_r_r, kSig_a_a, + kSig_i_r, kSig_v_r, kSig_v_a, kSig_v_rr, kSig_v_aa, kSig_r_v, kSig_a_v, + kSig_a_i, + makeSig, makeSig_v_x, makeSig_v_xx, makeSig_r_v, makeSig_r_x, makeSig_r_xx +}); + // Opcodes let kExprUnreachable = 0x00; let kExprNop = 0x01; @@ -374,10 +447,50 @@ let kExprRefIsNull = 0xd1; let kExprRefFunc = 0xd2; // Prefix opcodes +let kGCPrefix = 0xfb; let kNumericPrefix = 0xfc; let kSimdPrefix = 0xfd; let kAtomicPrefix = 0xfe; +// Use these for multi-byte instructions (opcode > 0x7F needing two LEB bytes): +function GCInstr(opcode) { + if (opcode <= 0x7F) return [kGCPrefix, opcode]; + return [kGCPrefix, 0x80 | (opcode & 0x7F), opcode >> 7]; +} + +// GC opcodes +let kExprStructNew = 0x00; +let kExprStructNewDefault = 0x01; +let kExprStructGet = 0x02; +let kExprStructGetS = 0x03; +let kExprStructGetU = 0x04; +let kExprStructSet = 0x05; +let kExprArrayNew = 0x06; +let kExprArrayNewDefault = 0x07; +let kExprArrayNewFixed = 0x08; +let kExprArrayNewData = 0x09; +let kExprArrayNewElem = 0x0a; +let kExprArrayGet = 0x0b; +let kExprArrayGetS = 0x0c; +let kExprArrayGetU = 0x0d; +let kExprArraySet = 0x0e; +let kExprArrayLen = 0x0f; +let kExprArrayFill = 0x10; +let kExprArrayCopy = 0x11; +let kExprArrayInitData = 0x12; +let kExprArrayInitElem = 0x13; +let kExprRefTest = 0x14; +let kExprRefTestNull = 0x15; +let kExprRefCast = 0x16; +let kExprRefCastNull = 0x17; +let kExprBrOnCast = 0x18; +let kExprBrOnCastFail = 0x19; +let kExprExternInternalize = 0x1a; +let kExprExternExternalize = 0x1b; +let kExprI31New = 0x1c; +let kExprI31GetS = 0x1d; +let kExprI31GetU = 0x1e; + // Numeric opcodes. let kExprMemoryInit = 0x08; let kExprDataDrop = 0x09; @@ -467,6 +580,89 @@ let kExprI32x4Eq = 0x2c; let kExprS1x4AllTrue = 0x75; let kExprF32x4Min = 0x9e; +Object.assign(globalThis, { + kExprUnreachable, kExprNop, kExprBlock, kExprLoop, kExprIf, kExprElse, + kExprTry, kExprCatch, kExprCatchAll, kExprThrow, kExprRethrow, kExprBrOnExn, + kExprEnd, kExprBr, kExprBrIf, kExprBrTable, kExprReturn, kExprCallFunction, + kExprCallIndirect, kExprReturnCall, kExprReturnCallIndirect, kExprDrop, + kExprSelect, kExprLocalGet, kExprLocalSet, kExprLocalTee, kExprGlobalGet, + kExprGlobalSet, kExprTableGet, kExprTableSet, kExprI32LoadMem, + kExprI64LoadMem, kExprF32LoadMem, kExprF64LoadMem, kExprI32LoadMem8S, + kExprI32LoadMem8U, kExprI32LoadMem16S, kExprI32LoadMem16U, kExprI64LoadMem8S, + kExprI64LoadMem8U, kExprI64LoadMem16S, kExprI64LoadMem16U, kExprI64LoadMem32S, + kExprI64LoadMem32U, kExprI32StoreMem, kExprI64StoreMem, kExprF32StoreMem, + kExprF64StoreMem, kExprI32StoreMem8, kExprI32StoreMem16, kExprI64StoreMem8, + kExprI64StoreMem16, kExprI64StoreMem32, kExprMemorySize, kExprMemoryGrow, + kExprI32Const, kExprI64Const, kExprF32Const, kExprF64Const, kExprI32Eqz, + kExprI32Eq, kExprI32Ne, kExprI32LtS, kExprI32LtU, kExprI32GtS, kExprI32GtU, + kExprI32LeS, kExprI32LeU, kExprI32GeS, kExprI32GeU, kExprI64Eqz, kExprI64Eq, + kExprI64Ne, kExprI64LtS, kExprI64LtU, kExprI64GtS, kExprI64GtU, kExprI64LeS, + kExprI64LeU, kExprI64GeS, kExprI64GeU, kExprF32Eq, kExprF32Ne, kExprF32Lt, + kExprF32Gt, kExprF32Le, kExprF32Ge, kExprF64Eq, kExprF64Ne, kExprF64Lt, + kExprF64Gt, kExprF64Le, kExprF64Ge, kExprI32Clz, kExprI32Ctz, kExprI32Popcnt, + kExprI32Add, kExprI32Sub, kExprI32Mul, kExprI32DivS, kExprI32DivU, + kExprI32RemS, kExprI32RemU, kExprI32And, kExprI32Ior, kExprI32Xor, + kExprI32Shl, kExprI32ShrS, kExprI32ShrU, kExprI32Rol, kExprI32Ror, + kExprI64Clz, kExprI64Ctz, kExprI64Popcnt, kExprI64Add, kExprI64Sub, + kExprI64Mul, kExprI64DivS, kExprI64DivU, kExprI64RemS, kExprI64RemU, + kExprI64And, kExprI64Ior, kExprI64Xor, kExprI64Shl, kExprI64ShrS, + kExprI64ShrU, kExprI64Rol, kExprI64Ror, kExprF32Abs, kExprF32Neg, + kExprF32Ceil, kExprF32Floor, kExprF32Trunc, kExprF32NearestInt, kExprF32Sqrt, + kExprF32Add, kExprF32Sub, kExprF32Mul, kExprF32Div, kExprF32Min, kExprF32Max, + kExprF32CopySign, kExprF64Abs, kExprF64Neg, kExprF64Ceil, kExprF64Floor, + kExprF64Trunc, kExprF64NearestInt, kExprF64Sqrt, kExprF64Add, kExprF64Sub, + kExprF64Mul, kExprF64Div, kExprF64Min, kExprF64Max, kExprF64CopySign, + kExprI32ConvertI64, kExprI32SConvertF32, kExprI32UConvertF32, + kExprI32SConvertF64, kExprI32UConvertF64, kExprI64SConvertI32, + kExprI64UConvertI32, kExprI64SConvertF32, kExprI64UConvertF32, + kExprI64SConvertF64, kExprI64UConvertF64, kExprF32SConvertI32, + kExprF32UConvertI32, kExprF32SConvertI64, kExprF32UConvertI64, + kExprF32ConvertF64, kExprF64SConvertI32, kExprF64UConvertI32, + kExprF64SConvertI64, kExprF64UConvertI64, kExprF64ConvertF32, + kExprI32ReinterpretF32, kExprI64ReinterpretF64, kExprF32ReinterpretI32, + kExprF64ReinterpretI64, kExprI32SExtendI8, kExprI32SExtendI16, + kExprI64SExtendI8, kExprI64SExtendI16, kExprI64SExtendI32, kExprRefNull, + kExprRefIsNull, kExprRefFunc, + GCInstr, + kExprStructNew, kExprStructNewDefault, kExprStructGet, kExprStructGetS, + kExprStructGetU, kExprStructSet, kExprArrayNew, kExprArrayNewDefault, + kExprArrayNewFixed, kExprArrayNewData, kExprArrayNewElem, kExprArrayGet, + kExprArrayGetS, kExprArrayGetU, kExprArraySet, kExprArrayLen, kExprArrayFill, + kExprArrayCopy, kExprArrayInitData, kExprArrayInitElem, kExprRefTest, + kExprRefTestNull, kExprRefCast, kExprRefCastNull, kExprBrOnCast, + kExprBrOnCastFail, kExprExternInternalize, kExprExternExternalize, + kExprI31New, kExprI31GetS, kExprI31GetU, + kExprMemoryInit, kExprDataDrop, kExprMemoryCopy, kExprMemoryFill, + kExprTableInit, kExprElemDrop, kExprTableCopy, kExprTableGrow, kExprTableSize, + kExprTableFill, + kExprAtomicNotify, kExprI32AtomicWait, kExprI64AtomicWait, kExprI32AtomicLoad, + kExprI32AtomicLoad8U, kExprI32AtomicLoad16U, kExprI32AtomicStore, + kExprI32AtomicStore8U, kExprI32AtomicStore16U, kExprI32AtomicAdd, + kExprI32AtomicAdd8U, kExprI32AtomicAdd16U, kExprI32AtomicSub, + kExprI32AtomicSub8U, kExprI32AtomicSub16U, kExprI32AtomicAnd, + kExprI32AtomicAnd8U, kExprI32AtomicAnd16U, kExprI32AtomicOr, + kExprI32AtomicOr8U, kExprI32AtomicOr16U, kExprI32AtomicXor, + kExprI32AtomicXor8U, kExprI32AtomicXor16U, kExprI32AtomicExchange, + kExprI32AtomicExchange8U, kExprI32AtomicExchange16U, + kExprI32AtomicCompareExchange, kExprI32AtomicCompareExchange8U, + kExprI32AtomicCompareExchange16U, kExprI64AtomicLoad, kExprI64AtomicLoad8U, + kExprI64AtomicLoad16U, kExprI64AtomicLoad32U, kExprI64AtomicStore, + kExprI64AtomicStore8U, kExprI64AtomicStore16U, kExprI64AtomicStore32U, + kExprI64AtomicAdd, kExprI64AtomicAdd8U, kExprI64AtomicAdd16U, + kExprI64AtomicAdd32U, kExprI64AtomicSub, kExprI64AtomicSub8U, + kExprI64AtomicSub16U, kExprI64AtomicSub32U, kExprI64AtomicAnd, + kExprI64AtomicAnd8U, kExprI64AtomicAnd16U, kExprI64AtomicAnd32U, + kExprI64AtomicOr, kExprI64AtomicOr8U, kExprI64AtomicOr16U, + kExprI64AtomicOr32U, kExprI64AtomicXor, kExprI64AtomicXor8U, + kExprI64AtomicXor16U, kExprI64AtomicXor32U, kExprI64AtomicExchange, + kExprI64AtomicExchange8U, kExprI64AtomicExchange16U, + kExprI64AtomicExchange32U, kExprI64AtomicCompareExchange, + kExprI64AtomicCompareExchange8U, kExprI64AtomicCompareExchange16U, + kExprI64AtomicCompareExchange32U, + kExprS128LoadMem, kExprS128StoreMem, kExprI32x4Splat, kExprI32x4Eq, + kExprS1x4AllTrue, kExprF32x4Min +}); + class Binary { constructor() { this.length = 0; @@ -554,6 +750,25 @@ class Binary { } } + emit_heap_type(heap_type) { + this.emit_bytes(wasmSignedLeb(heap_type, kMaxVarInt32Size)); + } + + emit_type(type) { + if ((typeof type) == 'number') { + this.emit_u8(type >= 0 ? type : type & kLeb128Mask); + } else { + this.emit_u8(type.opcode); + if ('depth' in type) this.emit_u8(type.depth); + this.emit_heap_type(type.heap_type); + } + } + + emit_init_expr(expr) { + this.emit_bytes(expr); + this.emit_u8(kExprEnd); + } + emit_header() { this.emit_bytes([ kWasmH0, kWasmH1, kWasmH2, kWasmH3, kWasmV0, kWasmV1, kWasmV2, kWasmV3 @@ -644,11 +859,11 @@ class WasmFunctionBuilder { } class WasmGlobalBuilder { - constructor(module, type, mutable) { + constructor(module, type, mutable, init) { this.module = module; this.type = type; this.mutable = mutable; - this.init = 0; + this.init = init; } exportAs(name) { @@ -658,13 +873,24 @@ class WasmGlobalBuilder { } } +function checkExpr(expr) { + for (let b of expr) { + if (typeof b !== 'number' || (b & (~0xFF)) !== 0) { + throw new Error( + 'invalid body (entries must be 8 bit numbers): ' + expr); + } + } +} + class WasmTableBuilder { - constructor(module, type, initial_size, max_size) { + constructor(module, type, initial_size, max_size, init_expr) { this.module = module; this.type = type; this.initial_size = initial_size; this.has_max = max_size != undefined; this.max_size = max_size; + this.init_expr = init_expr; + this.has_init = init_expr !== undefined; } exportAs(name) { @@ -674,6 +900,35 @@ class WasmTableBuilder { } } +function makeField(type, mutability) { + if ((typeof mutability) != 'boolean') { + throw new Error('field mutability must be boolean'); + } + return {type: type, mutability: mutability}; +} + +class WasmStruct { + constructor(fields, is_final, supertype_idx) { + if (!Array.isArray(fields)) { + throw new Error('struct fields must be an array'); + } + this.fields = fields; + this.type_form = kWasmStructTypeForm; + this.is_final = is_final; + this.supertype = supertype_idx; + } +} + +class WasmArray { + constructor(type, mutability, is_final, supertype_idx) { + this.type = type; + this.mutability = mutability; + this.type_form = kWasmArrayTypeForm; + this.is_final = is_final; + this.supertype = supertype_idx; + } +} + class WasmModuleBuilder { constructor() { this.types = []; @@ -686,6 +941,7 @@ class WasmModuleBuilder { this.element_segments = []; this.data_segments = []; this.explicit = []; + this.rec_groups = []; this.num_imported_funcs = 0; this.num_imported_globals = 0; this.num_imported_tables = 0; @@ -728,25 +984,65 @@ class WasmModuleBuilder { this.explicit.push(this.createCustomSection(name, bytes)); } - addType(type) { - this.types.push(type); - var pl = type.params.length; // should have params - var rl = type.results.length; // should have results + // We use {is_final = true} so that the MVP syntax is generated for + // signatures. + addType(type, supertype_idx = kNoSuperType, is_final = true) { + var pl = type.params.length; // should have params + var rl = type.results.length; // should have results + var type_copy = {params: type.params, results: type.results, + is_final: is_final, supertype: supertype_idx}; + this.types.push(type_copy); return this.types.length - 1; } - addGlobal(local_type, mutable) { - let glob = new WasmGlobalBuilder(this, local_type, mutable); + addStruct(fields, supertype_idx = kNoSuperType, is_final = false) { + this.types.push(new WasmStruct(fields, is_final, supertype_idx)); + return this.types.length - 1; + } + + addArray(type, mutability, supertype_idx = kNoSuperType, is_final = false) { + this.types.push(new WasmArray(type, mutability, is_final, supertype_idx)); + return this.types.length - 1; + } + + static defaultFor(type) { + switch (type) { + case kWasmI32: + return wasmI32Const(0); + case kWasmI64: + return wasmI64Const(0); + case kWasmF32: + return wasmF32Const(0.0); + case kWasmF64: + return wasmF64Const(0.0); + case kWasmS128: + return [kSimdPrefix, kExprS128Const, ...(new Array(16).fill(0))]; + default: + if ((typeof type) != 'number' && type.opcode != kWasmRefNull) { + throw new Error("Non-defaultable type"); + } + let heap_type = (typeof type) == 'number' ? type : type.heap_type; + return [kExprRefNull, ...wasmSignedLeb(heap_type, kMaxVarInt32Size)]; + } + } + + addGlobal(type, mutable, init) { + if (init === undefined) init = WasmModuleBuilder.defaultFor(type); + checkExpr(init); + let glob = new WasmGlobalBuilder(this, type, mutable, init); glob.index = this.globals.length + this.num_imported_globals; this.globals.push(glob); return glob; } - addTable(type, initial_size, max_size = undefined) { - if (type != kWasmAnyRef && type != kWasmAnyFunc) { - throw new Error('Tables must be of type kWasmAnyRef or kWasmAnyFunc'); + addTable(type, initial_size, max_size = undefined, init_expr = undefined) { + if (type == kWasmI32 || type == kWasmI64 || type == kWasmF32 || + type == kWasmF64 || type == kWasmS128 || type == kWasmStmt) { + throw new Error('Tables must be of a reference type'); } - let table = new WasmTableBuilder(this, type, initial_size, max_size); + if (init_expr != undefined) checkExpr(init_expr); + let table = new WasmTableBuilder( + this, type, initial_size, max_size, init_expr); table.index = this.tables.length + this.num_imported_tables; this.tables.push(table); return table; @@ -754,9 +1050,9 @@ class WasmModuleBuilder { addTag(type) { let type_index = (typeof type) == "number" ? type : this.addType(type); - let except_index = this.tags.length + this.num_imported_tags; + let tag_index = this.tags.length + this.num_imported_tags; this.tags.push(type_index); - return except_index; + return tag_index; } addFunction(name, type) { @@ -877,6 +1173,21 @@ class WasmModuleBuilder { return this; } + startRecGroup() { + this.rec_groups.push({start: this.types.length, size: 0}); + } + + endRecGroup() { + if (this.rec_groups.length == 0) { + throw new Error("Did not start a recursive group before ending one") + } + let last_element = this.rec_groups[this.rec_groups.length - 1] + if (last_element.size != 0) { + throw new Error("Did not start a recursive group before ending one") + } + last_element.size = this.types.length - last_element.start; + } + setName(name) { this.name = name; return this; @@ -891,18 +1202,55 @@ class WasmModuleBuilder { // Add type section if (wasm.types.length > 0) { - if (debug) print("emitting types @ " + binary.length); + if (debug) print('emitting types @ ' + binary.length); binary.emit_section(kTypeSectionCode, section => { - section.emit_u32v(wasm.types.length); - for (let type of wasm.types) { - section.emit_u8(kWasmFunctionTypeForm); - section.emit_u32v(type.params.length); - for (let param of type.params) { - section.emit_u8(param); + let length_with_groups = wasm.types.length; + for (let group of wasm.rec_groups) { + length_with_groups -= group.size - 1; + } + section.emit_u32v(length_with_groups); + + let rec_group_index = 0; + + for (let i = 0; i < wasm.types.length; i++) { + if (rec_group_index < wasm.rec_groups.length && + wasm.rec_groups[rec_group_index].start == i) { + section.emit_u8(kWasmRecursiveTypeGroupForm); + section.emit_u32v(wasm.rec_groups[rec_group_index].size); + rec_group_index++; + } + + let type = wasm.types[i]; + if (type.supertype != kNoSuperType) { + section.emit_u8(type.is_final ? kWasmSubtypeFinalForm + : kWasmSubtypeForm); + section.emit_u8(1); // supertype count + section.emit_u32v(type.supertype); + } else if (!type.is_final) { + section.emit_u8(kWasmSubtypeForm); + section.emit_u8(0); // no supertypes } - section.emit_u32v(type.results.length); - for (let result of type.results) { - section.emit_u8(result); + if (type instanceof WasmStruct) { + section.emit_u8(kWasmStructTypeForm); + section.emit_u32v(type.fields.length); + for (let field of type.fields) { + section.emit_type(field.type); + section.emit_u8(field.mutability ? 1 : 0); + } + } else if (type instanceof WasmArray) { + section.emit_u8(kWasmArrayTypeForm); + section.emit_type(type.type); + section.emit_u8(type.mutability ? 1 : 0); + } else { + section.emit_u8(kWasmFunctionTypeForm); + section.emit_u32v(type.params.length); + for (let param of type.params) { + section.emit_type(param); + } + section.emit_u32v(type.results.length); + for (let result of type.results) { + section.emit_type(result); + } } } }); @@ -920,7 +1268,7 @@ class WasmModuleBuilder { if (imp.kind == kExternalFunction) { section.emit_u32v(imp.type); } else if (imp.kind == kExternalGlobal) { - section.emit_u32v(imp.type); + section.emit_type(imp.type); section.emit_u8(imp.mutable); } else if (imp.kind == kExternalMemory) { var has_max = (typeof imp.maximum) != "undefined"; @@ -933,7 +1281,7 @@ class WasmModuleBuilder { section.emit_u32v(imp.initial); // initial if (has_max) section.emit_u32v(imp.maximum); // maximum } else if (imp.kind == kExternalTable) { - section.emit_u8(imp.type); + section.emit_type(imp.type); var has_max = (typeof imp.maximum) != "undefined"; section.emit_u8(has_max ? 1 : 0); // flags section.emit_u32v(imp.initial); // initial @@ -965,10 +1313,11 @@ class WasmModuleBuilder { binary.emit_section(kTableSectionCode, section => { section.emit_u32v(wasm.tables.length); for (let table of wasm.tables) { - section.emit_u8(table.type); + section.emit_type(table.type); section.emit_u8(table.has_max); section.emit_u32v(table.initial_size); if (table.has_max) section.emit_u32v(table.max_size); + if (table.has_init) section.emit_init_expr(table.init_expr); } }); } @@ -997,41 +1346,9 @@ class WasmModuleBuilder { binary.emit_section(kGlobalSectionCode, section => { section.emit_u32v(wasm.globals.length); for (let global of wasm.globals) { - section.emit_u8(global.type); + section.emit_type(global.type); section.emit_u8(global.mutable); - if ((typeof global.init_index) == "undefined") { - // Emit a constant initializer. - switch (global.type) { - case kWasmI32: - section.emit_u8(kExprI32Const); - section.emit_u32v(global.init); - break; - case kWasmI64: - section.emit_u8(kExprI64Const); - section.emit_u64v(global.init); - break; - case kWasmF32: - section.emit_bytes(wasmF32Const(global.init)); - break; - case kWasmF64: - section.emit_bytes(wasmF64Const(global.init)); - break; - case kWasmAnyFunc: - case kWasmAnyRef: - if (global.function_index !== undefined) { - section.emit_u8(kExprRefFunc); - section.emit_u32v(global.function_index); - } else { - section.emit_u8(kExprRefNull); - } - break; - } - } else { - // Emit a global-index initializer. - section.emit_u8(kExprGlobalGet); - section.emit_u32v(global.init_index); - } - section.emit_u8(kExprEnd); // end of init expression + section.emit_init_expr(global.init); } }); } @@ -1161,7 +1478,7 @@ class WasmModuleBuilder { local_decls.push({count: l.s128_count, type: kWasmS128}); } if (l.anyref_count > 0) { - local_decls.push({count: l.anyref_count, type: kWasmAnyRef}); + local_decls.push({count: l.anyref_count, type: kWasmExternRef}); } if (l.anyfunc_count > 0) { local_decls.push({count: l.anyfunc_count, type: kWasmAnyFunc}); @@ -1171,7 +1488,7 @@ class WasmModuleBuilder { header.emit_u32v(local_decls.length); for (let decl of local_decls) { header.emit_u32v(decl.count); - header.emit_u8(decl.type); + header.emit_type(decl.type); } section.emit_u32v(header.length + func.body.length); @@ -1284,6 +1601,7 @@ class WasmModuleBuilder { return new WebAssembly.Module(this.toBuffer(debug)); } } +globalThis.WasmModuleBuilder = WasmModuleBuilder; function wasmSignedLeb(val, max_len = 5) { let res = []; @@ -1300,10 +1618,12 @@ function wasmSignedLeb(val, max_len = 5) { throw new Error( 'Leb value <' + val + '> exceeds maximum length of ' + max_len); } +globalThis.wasmSignedLeb = wasmSignedLeb; function wasmI32Const(val) { return [kExprI32Const, ...wasmSignedLeb(val, 5)]; } +globalThis.wasmI32Const = wasmI32Const; function wasmF32Const(f) { // Write in little-endian order at offset 0. @@ -1312,6 +1632,7 @@ function wasmF32Const(f) { kExprF32Const, byte_view[0], byte_view[1], byte_view[2], byte_view[3] ]; } +globalThis.wasmI32Const = wasmI32Const; function wasmF64Const(f) { // Write in little-endian order at offset 0. @@ -1321,3 +1642,4 @@ function wasmF64Const(f) { byte_view[3], byte_view[4], byte_view[5], byte_view[6], byte_view[7] ]; } +globalThis.wasmF64Const = wasmF64Const; From 88c31378f27d351343411f909167bf0977baf97e Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Mon, 23 Mar 2026 13:22:20 -0700 Subject: [PATCH 4/6] update test status --- test/wpt/status/wasm/jsapi.json | 65 ++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/test/wpt/status/wasm/jsapi.json b/test/wpt/status/wasm/jsapi.json index 4d7f5c266dde5b..81083601d8d3a0 100644 --- a/test/wpt/status/wasm/jsapi.json +++ b/test/wpt/status/wasm/jsapi.json @@ -2,20 +2,67 @@ "esm-integration/global-exports-live-bindings.tentative.any.js": { "skip": "Live bindings unsupported pending V8 WebAssemblyModuleRecord" }, - "esm-integration/v128-tdz.tentative.any.js": { - "skip": "v128 undefined Wasm bindings not yet supported in V8" - }, "esm-integration/namespace-instance.tentative.any.js": { "skip": "pending https://github.com/nodejs/node/pull/59024" }, - "esm-integration/reserved-import-names.tentative.any.js": { - "skip": "pending https://github.com/nodejs/node/pull/59020" + "esm-integration/source-phase.tentative.any.js": { + "fail": { + "expected": [ + "Source phase identities" + ] + } }, - "esm-integration/source-phase-string-builtins.tentative.any.js": { - "skip": "pending https://github.com/nodejs/node/pull/59020" + "esm-integration/v128-tdz.tentative.any.js": { + "skip": "v128 undefined Wasm bindings not yet supported in V8" }, - "esm-integration/string-builtins.tentative.any.js": { - "skip": "pending https://github.com/nodejs/node/pull/59020" + "global/value-get-set.any.js": { + "fail": { + "expected": [ + "Calling setter without argument" + ] + } + }, + "memory/to-fixed-length-buffer-shared.any.js": { + "fail": { + "expected": [ + "toFixedLengthBuffer caching behavior" + ] + } + }, + "memory/to-fixed-length-buffer.any.js": { + "fail": { + "expected": [ + "API surface", + "toFixedLengthBuffer caching behavior" + ] + } + }, + "memory/to-resizable-buffer-shared.any.js": { + "fail": { + "expected": [ + "toResizableBuffer caching behavior", + "toResizableBuffer max size", + "Resizing a Memory's resizable buffer" + ] + } + }, + "memory/to-resizable-buffer.any.js": { + "fail": { + "expected": [ + "API surface", + "toResizableBuffer caching behavior", + "toResizableBuffer max size", + "Resizing a Memory's resizable buffer", + "Resizable buffers from Memory cannot be detached by JS" + ] + } + }, + "module/moduleSource.tentative.any.js": { + "fail": { + "expected": [ + "AbstractModuleSourceProto toStringTag brand check" + ] + } }, "exception/getArg.tentative.any.js": { "skip": "track - still tentative / unsupported" From b95725a0f2268c2b49c78d155811f1ddd97e9b52 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Wed, 25 Mar 2026 13:15:11 -0700 Subject: [PATCH 5/6] test: add v128 SIMD global round-trip test for big-endian platforms --- test/parallel/test-wasm-simd-global.js | 59 ++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 test/parallel/test-wasm-simd-global.js diff --git a/test/parallel/test-wasm-simd-global.js b/test/parallel/test-wasm-simd-global.js new file mode 100644 index 00000000000000..08a1eeffe158b1 --- /dev/null +++ b/test/parallel/test-wasm-simd-global.js @@ -0,0 +1,59 @@ +'use strict'; + +// Test that v128 SIMD globals work correctly end-to-end on all platforms, +// including big-endian (s390x). Specifically: +// 1. v128.const initializer -> global.get -> i32x4.extract_lane round-trip +// 2. i32x4.replace_lane -> global.set -> global.get -> i32x4.extract_lane round-trip +// +// This is a regression check for potential big-endian issues in V8's handling +// of v128 globals (e.g. s390x), independent of the ESM import path. + +const common = require('../common'); +const assert = require('assert'); + +// WAT source: +// (module +// (global $g (mut v128) (v128.const i32x4 1 2 3 4)) +// (func (export "get0") (result i32) global.get $g i32x4.extract_lane 0) +// (func (export "get1") (result i32) global.get $g i32x4.extract_lane 1) +// (func (export "get2") (result i32) global.get $g i32x4.extract_lane 2) +// (func (export "get3") (result i32) global.get $g i32x4.extract_lane 3) +// (func (export "set") (param i32 i32 i32 i32) +// i32.const 0 i32x4.splat +// local.get 0 i32x4.replace_lane 0 +// local.get 1 i32x4.replace_lane 1 +// local.get 2 i32x4.replace_lane 2 +// local.get 3 i32x4.replace_lane 3 +// global.set $g) +// ) +const wasmBytes = new Uint8Array([ + 0, 97, 115, 109, 1, 0, 0, 0, 1, 12, 2, 96, 0, 1, 127, 96, 4, 127, 127, + 127, 127, 0, 3, 6, 5, 0, 0, 0, 0, 1, 6, 22, 1, 123, 1, 253, 12, 1, 0, 0, + 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 11, 7, 35, 5, 4, 103, 101, 116, 48, + 0, 0, 4, 103, 101, 116, 49, 0, 1, 4, 103, 101, 116, 50, 0, 2, 4, 103, 101, + 116, 51, 0, 3, 3, 115, 101, 116, 0, 4, 10, 62, 5, 7, 0, 35, 0, 253, 27, 0, + 11, 7, 0, 35, 0, 253, 27, 1, 11, 7, 0, 35, 0, 253, 27, 2, 11, 7, 0, 35, 0, + 253, 27, 3, 11, 28, 0, 65, 0, 253, 17, 32, 0, 253, 28, 0, 32, 1, 253, 28, + 1, 32, 2, 253, 28, 2, 32, 3, 253, 28, 3, 36, 0, 11, 0, 11, 4, 110, 97, + 109, 101, 7, 4, 1, 0, 1, 103, +]); + +if (!WebAssembly.validate(wasmBytes)) { + common.skip('WebAssembly SIMD not supported'); +} + +const { exports: { get0, get1, get2, get3, set } } = + new WebAssembly.Instance(new WebAssembly.Module(wasmBytes)); + +// Test 1: v128.const initializer lanes are read back correctly. +assert.strictEqual(get0(), 1, 'v128.const lane 0 should be 1'); +assert.strictEqual(get1(), 2, 'v128.const lane 1 should be 2'); +assert.strictEqual(get2(), 3, 'v128.const lane 2 should be 3'); +assert.strictEqual(get3(), 4, 'v128.const lane 3 should be 4'); + +// Test 2: replace_lane -> global.set -> global.get -> extract_lane round-trip. +set(10, 20, 30, 40); +assert.strictEqual(get0(), 10, 'after set, lane 0 should be 10'); +assert.strictEqual(get1(), 20, 'after set, lane 1 should be 20'); +assert.strictEqual(get2(), 30, 'after set, lane 2 should be 30'); +assert.strictEqual(get3(), 40, 'after set, lane 3 should be 40'); From 5a34f353b3397c5b78c4aee80e4d5754c6a4e592 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Wed, 25 Mar 2026 13:25:35 -0700 Subject: [PATCH 6/6] fixup: remove string literals from assert.strictEqual calls --- test/parallel/test-wasm-simd-global.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/parallel/test-wasm-simd-global.js b/test/parallel/test-wasm-simd-global.js index 08a1eeffe158b1..3d268171b3b005 100644 --- a/test/parallel/test-wasm-simd-global.js +++ b/test/parallel/test-wasm-simd-global.js @@ -46,14 +46,14 @@ const { exports: { get0, get1, get2, get3, set } } = new WebAssembly.Instance(new WebAssembly.Module(wasmBytes)); // Test 1: v128.const initializer lanes are read back correctly. -assert.strictEqual(get0(), 1, 'v128.const lane 0 should be 1'); -assert.strictEqual(get1(), 2, 'v128.const lane 1 should be 2'); -assert.strictEqual(get2(), 3, 'v128.const lane 2 should be 3'); -assert.strictEqual(get3(), 4, 'v128.const lane 3 should be 4'); +assert.strictEqual(get0(), 1); +assert.strictEqual(get1(), 2); +assert.strictEqual(get2(), 3); +assert.strictEqual(get3(), 4); // Test 2: replace_lane -> global.set -> global.get -> extract_lane round-trip. set(10, 20, 30, 40); -assert.strictEqual(get0(), 10, 'after set, lane 0 should be 10'); -assert.strictEqual(get1(), 20, 'after set, lane 1 should be 20'); -assert.strictEqual(get2(), 30, 'after set, lane 2 should be 30'); -assert.strictEqual(get3(), 40, 'after set, lane 3 should be 40'); +assert.strictEqual(get0(), 10); +assert.strictEqual(get1(), 20); +assert.strictEqual(get2(), 30); +assert.strictEqual(get3(), 40);