Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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.namespaceInstance` 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/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