Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -3266,6 +3266,13 @@ The WASI instance has already started.

The WASI instance has not been started.

<a id="ERR_WEBASSEMBLY_NOT_MODULE_RECORD_NAMESPACE"></a>

### `ERR_WEBASSEMBLY_NOT_MODULE_RECORD_NAMESPACE`

The object passed to `WebAssembly.moduleInstance` was not a valid WebAssembly
Module Record namespace object.

<a id="ERR_WEBASSEMBLY_RESPONSE"></a>

### `ERR_WEBASSEMBLY_RESPONSE`
Expand Down
1 change: 1 addition & 0 deletions lib/internal/bootstrap/web/exposed-window-or-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ internalBinding('wasm_web_api').setImplementation((streamState, source) => {
require('internal/wasm_web_api').wasmStreamingCallback(streamState, source);
});


// WebCryptoAPI
if (internalBinding('config').hasOpenSSL) {
defineReplaceableLazyAttribute(
Expand Down
1 change: 1 addition & 0 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1892,6 +1892,7 @@ E('ERR_VM_MODULE_NOT_MODULE',
'Provided module is not an instance of Module', Error);
E('ERR_VM_MODULE_STATUS', 'Module status %s', Error);
E('ERR_WASI_ALREADY_STARTED', 'WASI instance has already started', Error);
E('ERR_WEBASSEMBLY_NOT_MODULE_RECORD_NAMESPACE', 'Not a WebAssembly Module Record namespace object.', TypeError);
E('ERR_WEBASSEMBLY_RESPONSE', 'WebAssembly response %s', TypeError);
E('ERR_WORKER_INIT_FAILED', 'Worker initialization failure: %s', Error);
E('ERR_WORKER_INVALID_EXEC_ARGV', (errors, msg = 'invalid execArgv flags') =>
Expand Down
10 changes: 5 additions & 5 deletions lib/internal/modules/esm/translators.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ const {
SafeArrayIterator,
SafeMap,
SafeSet,
SafeWeakMap,
StringPrototypeIncludes,
StringPrototypeReplaceAll,
StringPrototypeSlice,
Expand Down Expand Up @@ -55,6 +54,7 @@ const {
ERR_UNKNOWN_BUILTIN_MODULE,
} = require('internal/errors').codes;
const { maybeCacheSourceMap } = require('internal/source_map/source_map_cache');
const { wasmInstances } = require('internal/wasm_web_api');
const moduleWrap = internalBinding('module_wrap');
const { ModuleWrap } = moduleWrap;

Expand Down Expand Up @@ -491,7 +491,6 @@ translators.set('json', function jsonStrategy(url, source) {
* WebAssembly.Instance
* >} [[Instance]] slot proxy for WebAssembly Module Record
*/
const wasmInstances = new SafeWeakMap();
translators.set('wasm', async function(url, source) {
emitExperimentalWarning('Importing WebAssembly modules');

Expand Down Expand Up @@ -533,7 +532,7 @@ translators.set('wasm', async function(url, source) {
const { module } = createDynamicModule([...importsList], [...exportsList], url, (reflect) => {
for (const impt of importsList) {
const importNs = reflect.imports[impt];
const wasmInstance = wasmInstances.get(importNs);
const wasmInstance = wasmInstances.get(importNs)?.exports;
if (wasmInstance) {
const wrappedModule = ObjectAssign({ __proto__: null }, reflect.imports[impt]);
for (const { module, name } of wasmGlobalImports) {
Expand All @@ -549,8 +548,9 @@ translators.set('wasm', async function(url, source) {
}
// In cycles importing unexecuted Wasm, wasmInstance will be undefined, which will fail during
// instantiation, since all bindings will be in the Temporal Deadzone (TDZ).
const { exports } = new WebAssembly.Instance(compiled, reflect.imports);
wasmInstances.set(module.getNamespace(), exports);
const instance = new WebAssembly.Instance(compiled, reflect.imports);
const { exports } = instance;
wasmInstances.set(module.getNamespace(), instance);
for (const expt of exportsList) {
let val = exports[expt];
// Unwrap WebAssembly.Global for JS bindings
Expand Down
18 changes: 18 additions & 0 deletions lib/internal/wasm_web_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

const {
PromiseResolve,
SafeWeakMap,
globalThis,
} = primordials;
const {
ERR_INVALID_ARG_TYPE,
ERR_WEBASSEMBLY_RESPONSE,
ERR_WEBASSEMBLY_NOT_MODULE_RECORD_NAMESPACE,
} = require('internal/errors').codes;

let undici;
Expand Down Expand Up @@ -61,6 +64,21 @@ function wasmStreamingCallback(streamState, source) {
});
}

// WebAssembly ESM Integration extension pending V8 support - namespaceInstance property:
// see https://webassembly.github.io/esm-integration/js-api/index.html#dom-webassembly-namespaceinstance.
const wasmInstances = new SafeWeakMap();
const { WebAssembly } = globalThis;
if (WebAssembly && !WebAssembly.namespaceInstance) {
WebAssembly.namespaceInstance = function namespaceInstance(ns) {
const instance = wasmInstances.get(ns);
if (!instance) {
throw new ERR_WEBASSEMBLY_NOT_MODULE_RECORD_NAMESPACE();
}
return instance;
};
}

module.exports = {
wasmStreamingCallback,
wasmInstances,
};
24 changes: 24 additions & 0 deletions test/es-module/test-esm-wasm.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -403,4 +403,28 @@ describe('ESM: WASM modules', { concurrency: !process.env.TEST_PARALLEL }, () =>
strictEqual(stdout, '');
notStrictEqual(code, 0);
});

it('should return the underlying instance with shared state', async () => {
const { code, stderr, stdout } = await spawnPromisified(execPath, [
'--no-warnings',
'--experimental-wasm-modules',
'--input-type=module',
'--eval',
[
'import { strictEqual, ok } from "node:assert";',
`const wasmNamespace = await import(${JSON.stringify(fixtures.fileURL('es-modules/globals.wasm'))});`,
'const instance = WebAssembly.namespaceInstance(wasmNamespace);',
'ok(instance instanceof WebAssembly.Instance);',
'// Verify shared state between namespace and instance',
'wasmNamespace.setLocalMutI32(999);',
'strictEqual(instance.exports.getLocalMutI32(), 999);',
'instance.exports.setLocalMutI32(888);',
'strictEqual(wasmNamespace.getLocalMutI32(), 888);',
].join('\n'),
]);

strictEqual(stderr, '');
strictEqual(stdout, '');
strictEqual(code, 0);
});
});
Loading