Skip to content

Commit 767e88c

Browse files
guybedfordtargos
authored andcommitted
esm: unwrap WebAssembly.Global on Wasm Namespaces
PR-URL: #57525 Reviewed-By: Jacob Smith <jacob@frende.me> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent 8bc0452 commit 767e88c

8 files changed

Lines changed: 499 additions & 15 deletions

File tree

‎lib/internal/modules/esm/create_dynamic_module.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import.meta.exports[${nameStringLit}] = {
4444
* @param {string} [url=''] - The URL of the module.
4545
* @param {(reflect: DynamicModuleReflect) => void} evaluate - The function to evaluate the module.
4646
* @typedef {object} DynamicModuleReflect
47-
* @property {string[]} imports - The imports of the module.
47+
* @property {Record<string, Record<string, any>>} imports - The imports of the module.
4848
* @property {string[]} exports - The exports of the module.
4949
* @property {(cb: (reflect: DynamicModuleReflect) => void) => void} onReady - Callback to evaluate the module.
5050
*/

‎lib/internal/modules/esm/translators.js‎

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
'use strict';
22

33
const{
4-
ArrayPrototypeMap,
54
ArrayPrototypePush,
65
FunctionPrototypeCall,
76
JSONParse,
8-
ObjectKeys,
7+
ObjectAssign,
98
ObjectPrototypeHasOwnProperty,
109
ReflectApply,
1110
SafeArrayIterator,
1211
SafeMap,
1312
SafeSet,
13+
SafeWeakMap,
1414
StringPrototypeIncludes,
1515
StringPrototypeReplaceAll,
1616
StringPrototypeSlice,
@@ -484,6 +484,14 @@ translators.set('json', function jsonStrategy(url, source) {
484484
});
485485

486486
// Strategy for loading a wasm module
487+
// This logic should collapse into WebAssembly Module Record in future.
488+
/**
489+
* @type {WeakMap<
490+
* import('internal/modules/esm/utils').ModuleNamespaceObject,
491+
* WebAssembly.Instance
492+
* >} [[Instance]] slot proxy for WebAssembly Module Record
493+
*/
494+
constwasmInstances=newSafeWeakMap();
487495
translators.set('wasm',asyncfunction(url,source){
488496
emitExperimentalWarning('Importing WebAssembly modules');
489497

@@ -502,19 +510,61 @@ translators.set('wasm', async function(url, source) {
502510
throwerr;
503511
}
504512

505-
constimports=
506-
ArrayPrototypeMap(WebAssembly.Module.imports(compiled),
507-
({ module })=>module);
508-
constexports=
509-
ArrayPrototypeMap(WebAssembly.Module.exports(compiled),
510-
({ name })=>name);
513+
constimportsList=newSafeSet();
514+
constwasmGlobalImports=[];
515+
for(constimptofWebAssembly.Module.imports(compiled)){
516+
if(impt.kind==='global'){
517+
ArrayPrototypePush(wasmGlobalImports,impt);
518+
}
519+
importsList.add(impt.module);
520+
}
521+
522+
constexportsList=newSafeSet();
523+
constwasmGlobalExports=newSafeSet();
524+
for(constexptofWebAssembly.Module.exports(compiled)){
525+
if(expt.kind==='global'){
526+
wasmGlobalExports.add(expt.name);
527+
}
528+
exportsList.add(expt.name);
529+
}
511530

512-
constcreateDynamicModule=require(
513-
'internal/modules/esm/create_dynamic_module');
514-
const{ module }=createDynamicModule(imports,exports,url,(reflect)=>{
531+
constcreateDynamicModule=require('internal/modules/esm/create_dynamic_module');
532+
533+
const{ module }=createDynamicModule([...importsList],[...exportsList],url,(reflect)=>{
534+
for(constimptofimportsList){
535+
constimportNs=reflect.imports[impt];
536+
constwasmInstance=wasmInstances.get(importNs);
537+
if(wasmInstance){
538+
constwrappedModule=ObjectAssign({__proto__: null},reflect.imports[impt]);
539+
for(const{ module, name }ofwasmGlobalImports){
540+
if(module!==impt){
541+
continue;
542+
}
543+
// Import of Wasm module global -> get direct WebAssembly.Global wrapped value.
544+
// JS API validations otherwise remain the same.
545+
wrappedModule[name]=wasmInstance[name];
546+
}
547+
reflect.imports[impt]=wrappedModule;
548+
}
549+
}
550+
// In cycles importing unexecuted Wasm, wasmInstance will be undefined, which will fail during
551+
// instantiation, since all bindings will be in the Temporal Deadzone (TDZ).
515552
const{ exports }=newWebAssembly.Instance(compiled,reflect.imports);
516-
for(constexptofObjectKeys(exports)){
517-
reflect.exports[expt].set(exports[expt]);
553+
wasmInstances.set(module.getNamespace(),exports);
554+
for(constexptofexportsList){
555+
letval=exports[expt];
556+
// Unwrap WebAssembly.Global for JS bindings
557+
if(wasmGlobalExports.has(expt)){
558+
try{
559+
// v128 will throw in GetGlobalValue, see:
560+
// https://webassembly.github.io/esm-integration/js-api/index.html#getglobalvalue
561+
val=val.value;
562+
}catch{
563+
// v128 doesn't support ToJsValue() -> use undefined (ideally should stay in TDZ)
564+
continue;
565+
}
566+
}
567+
reflect.exports[expt].set(val);
518568
}
519569
});
520570
// WebAssembly modules support source phase imports, to import the compiled module

‎test/es-module/test-esm-wasm.mjs‎

Lines changed: 148 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,154 @@ describe('ESM: WASM modules', { concurrency: !process.env.TEST_PARALLEL }, () =>
5252
[
5353
'import { strictEqual } from "node:assert";',
5454
`import * as wasmExports from ${JSON.stringify(fixtures.fileURL('es-modules/export-name-syntax-error.wasm'))};`,
55-
'assert.strictEqual(wasmExports["?f!o:o<b>a[r]"]?.value, 12682);',
55+
'assert.strictEqual(wasmExports["?f!o:o<b>a[r]"], 12682);',
56+
].join('\n'),
57+
]);
58+
59+
strictEqual(stderr,'');
60+
strictEqual(stdout,'');
61+
strictEqual(code,0);
62+
});
63+
64+
it('should properly handle all WebAssembly global types',async()=>{
65+
const{ code, stderr, stdout }=awaitspawnPromisified(execPath,[
66+
'--no-warnings',
67+
'--experimental-wasm-modules',
68+
'--input-type=module',
69+
'--eval',
70+
[
71+
'import { strictEqual, deepStrictEqual, ok } from "node:assert";',
72+
73+
// SIMD is not supported on rhel8-ppc64le
74+
'let wasmExports;',
75+
'try {',
76+
` wasmExports = await import(${JSON.stringify(fixtures.fileURL('es-modules/globals.wasm'))});`,
77+
'} catch (e) {',
78+
' ok(e instanceof WebAssembly.CompileError);',
79+
' ok(e.message.includes("SIMD unsupported"));',
80+
'}',
81+
82+
'if (wasmExports) {',
83+
84+
// Test imported globals using direct access
85+
' strictEqual(wasmExports.importedI32, 42);',
86+
' strictEqual(wasmExports.importedMutI32, 100);',
87+
' strictEqual(wasmExports.importedI64, 9223372036854775807n);',
88+
' strictEqual(wasmExports.importedMutI64, 200n);',
89+
' strictEqual(Math.round(wasmExports.importedF32 * 100000) / 100000, 3.14159);',
90+
' strictEqual(Math.round(wasmExports.importedMutF32 * 100000) / 100000, 2.71828);',
91+
' strictEqual(wasmExports.importedF64, 3.141592653589793);',
92+
' strictEqual(wasmExports.importedMutF64, 2.718281828459045);',
93+
' strictEqual(wasmExports.importedExternref !== null, true);',
94+
' strictEqual(wasmExports.importedMutExternref !== null, true);',
95+
' strictEqual(wasmExports.importedNullExternref, null);',
96+
97+
// Test local globals exported directly
98+
' strictEqual(wasmExports[\'🚀localI32\'], 42);',
99+
' strictEqual(wasmExports.localMutI32, 100);',
100+
' strictEqual(wasmExports.localI64, 9223372036854775807n);',
101+
' strictEqual(wasmExports.localMutI64, 200n);',
102+
' strictEqual(Math.round(wasmExports.localF32 * 100000) / 100000, 3.14159);',
103+
' strictEqual(Math.round(wasmExports.localMutF32 * 100000) / 100000, 2.71828);',
104+
' strictEqual(wasmExports.localF64, 2.718281828459045);',
105+
' strictEqual(wasmExports.localMutF64, 3.141592653589793);',
106+
107+
// Test imported globals using getter functions
108+
' strictEqual(wasmExports.getImportedMutI32(), 100);',
109+
' strictEqual(wasmExports.getImportedMutI64(), 200n);',
110+
' strictEqual(Math.round(wasmExports.getImportedMutF32() * 100000) / 100000, 2.71828);',
111+
' strictEqual(wasmExports.getImportedMutF64(), 2.718281828459045);',
112+
' strictEqual(wasmExports.getImportedMutExternref() !== null, true);',
113+
114+
// Test local globals using getter functions
115+
' strictEqual(wasmExports.getLocalMutI32(), 100);',
116+
' strictEqual(wasmExports.getLocalMutI64(), 200n);',
117+
' strictEqual(Math.round(wasmExports.getLocalMutF32() * 100000) / 100000, 2.71828);',
118+
' strictEqual(wasmExports.getLocalMutF64(), 3.141592653589793);',
119+
' strictEqual(wasmExports.getLocalMutExternref(), null);',
120+
121+
' assert.throws(wasmExports.getLocalMutV128);',
122+
123+
// Pending TDZ support
124+
' strictEqual(wasmExports.depV128, undefined);',
125+
126+
// Test modifying mutable globals and reading the new values
127+
' wasmExports.setImportedMutI32(999);',
128+
' strictEqual(wasmExports.getImportedMutI32(), 999);',
129+
130+
' wasmExports.setImportedMutI64(888n);',
131+
' strictEqual(wasmExports.getImportedMutI64(), 888n);',
132+
133+
' wasmExports.setImportedMutF32(7.77);',
134+
' strictEqual(Math.round(wasmExports.getImportedMutF32() * 100) / 100, 7.77);',
135+
136+
' wasmExports.setImportedMutF64(6.66);',
137+
' strictEqual(wasmExports.getImportedMutF64(), 6.66);',
138+
139+
// Test modifying mutable externref
140+
' const testObj = { test: "object" };',
141+
' wasmExports.setImportedMutExternref(testObj);',
142+
' strictEqual(wasmExports.getImportedMutExternref(), testObj);',
143+
144+
// Test modifying local mutable globals
145+
' wasmExports.setLocalMutI32(555);',
146+
' strictEqual(wasmExports.getLocalMutI32(), 555);',
147+
148+
' wasmExports.setLocalMutI64(444n);',
149+
' strictEqual(wasmExports.getLocalMutI64(), 444n);',
150+
151+
' wasmExports.setLocalMutF32(3.33);',
152+
' strictEqual(Math.round(wasmExports.getLocalMutF32() * 100) / 100, 3.33);',
153+
154+
' wasmExports.setLocalMutF64(2.22);',
155+
' strictEqual(wasmExports.getLocalMutF64(), 2.22);',
156+
157+
// These mutating pending live bindings support
158+
' strictEqual(wasmExports.localMutI32, 100);',
159+
' strictEqual(wasmExports.localMutI64, 200n);',
160+
' strictEqual(Math.round(wasmExports.localMutF32 * 100) / 100, 2.72);',
161+
' strictEqual(wasmExports.localMutF64, 3.141592653589793);',
162+
163+
// Test modifying local mutable externref
164+
' const anotherTestObj = { another: "test object" };',
165+
' wasmExports.setLocalMutExternref(anotherTestObj);',
166+
' strictEqual(wasmExports.getLocalMutExternref(), anotherTestObj);',
167+
' strictEqual(wasmExports.localMutExternref, null);',
168+
169+
// Test dep.wasm imports
170+
' strictEqual(wasmExports.depI32, 1001);',
171+
' strictEqual(wasmExports.depMutI32, 2001);',
172+
' strictEqual(wasmExports.getDepMutI32(), 2001);',
173+
' strictEqual(wasmExports.depI64, 10000000001n);',
174+
' strictEqual(wasmExports.depMutI64, 20000000001n);',
175+
' strictEqual(wasmExports.getDepMutI64(), 20000000001n);',
176+
' strictEqual(Math.round(wasmExports.depF32 * 100) / 100, 10.01);',
177+
' strictEqual(Math.round(wasmExports.depMutF32 * 100) / 100, 20.01);',
178+
' strictEqual(Math.round(wasmExports.getDepMutF32() * 100) / 100, 20.01);',
179+
' strictEqual(wasmExports.depF64, 100.0001);',
180+
' strictEqual(wasmExports.depMutF64, 200.0001);',
181+
' strictEqual(wasmExports.getDepMutF64(), 200.0001);',
182+
183+
// Test modifying dep.wasm mutable globals
184+
' wasmExports.setDepMutI32(3001);',
185+
' strictEqual(wasmExports.getDepMutI32(), 3001);',
186+
187+
' wasmExports.setDepMutI64(30000000001n);',
188+
' strictEqual(wasmExports.getDepMutI64(), 30000000001n);',
189+
190+
' wasmExports.setDepMutF32(30.01);',
191+
' strictEqual(Math.round(wasmExports.getDepMutF32() * 100) / 100, 30.01);',
192+
193+
' wasmExports.setDepMutF64(300.0001);',
194+
' strictEqual(wasmExports.getDepMutF64(), 300.0001);',
195+
196+
// These pending live bindings support
197+
' strictEqual(wasmExports.depMutI32, 2001);',
198+
' strictEqual(wasmExports.depMutI64, 20000000001n);',
199+
' strictEqual(Math.round(wasmExports.depMutF32 * 100) / 100, 20.01);',
200+
' strictEqual(wasmExports.depMutF64, 200.0001);',
201+
202+
'}',
56203
].join('\n'),
57204
]);
58205

‎test/fixtures/es-modules/dep.wasm‎

529 Bytes
Binary file not shown.

‎test/fixtures/es-modules/dep.wat‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
(module
2+
(type(;0;) (func (resulti32)))
3+
(type(;1;) (func (resulti64)))
4+
(type(;2;) (func (resultf32)))
5+
(type(;3;) (func (resultf64)))
6+
(type(;4;) (func (resultexternref)))
7+
(type(;5;) (func (resultv128)))
8+
(global$i32_value(;0;)i32i32.const1001)
9+
(global$i32_mut_value(;1;) (muti32) i32.const2001)
10+
(global$i64_value(;2;)i64i64.const10000000001)
11+
(global$i64_mut_value(;3;) (muti64) i64.const20000000001)
12+
(global$f32_value(;4;)f32f32.const0x1.4051ecp+3(;=10.01;))
13+
(global$f32_mut_value(;5;) (mutf32) f32.const0x1.4028f6p+4(;=20.01;))
14+
(global$f64_value(;6;)f64f64.const0x1.90001a36e2eb2p+6(;=100.0001;))
15+
(global$f64_mut_value(;7;) (mutf64) f64.const0x1.90000d1b71759p+7(;=200.0001;))
16+
(global$externref_value(;8;)externrefref.nullextern)
17+
(global$externref_mut_value(;9;) (mutexternref) ref.nullextern)
18+
(global$v128_value(;10;)v128v128.consti32x40x0000000a0x000000140x0000001e0x00000028)
19+
(global$v128_mut_value(;11;) (mutv128) v128.consti32x40x000000320x0000003c0x000000460x00000050)
20+
(export"i32_value" (global$i32_value))
21+
(export"i32_mut_value" (global$i32_mut_value))
22+
(export"i64_value" (global$i64_value))
23+
(export"i64_mut_value" (global$i64_mut_value))
24+
(export"f32_value" (global$f32_value))
25+
(export"f32_mut_value" (global$f32_mut_value))
26+
(export"f64_value" (global$f64_value))
27+
(export"f64_mut_value" (global$f64_mut_value))
28+
(export"externref_value" (global$externref_value))
29+
(export"externref_mut_value" (global$externref_mut_value))
30+
(export"v128_value" (global$v128_value))
31+
(export"v128_mut_value" (global$v128_mut_value))
32+
)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// globals.js - Direct global exports for WebAssembly imports
2+
3+
// Immutable globals (simple values)
4+
consti32_value=42;
5+
export{i32_valueas'🚀i32_value'}
6+
exportconsti64_value=9223372036854775807n;// Max i64 value
7+
exportconstf32_value=3.14159;
8+
exportconstf64_value=3.141592653589793;
9+
10+
// Mutable globals with WebAssembly.Global wrapper
11+
exportconsti32_mut_value=newWebAssembly.Global({value: 'i32',mutable: true},100);
12+
exportconsti64_mut_value=newWebAssembly.Global({value: 'i64',mutable: true},200n);
13+
exportconstf32_mut_value=newWebAssembly.Global({value: 'f32',mutable: true},2.71828);
14+
exportconstf64_mut_value=newWebAssembly.Global({value: 'f64',mutable: true},2.718281828459045);
15+
16+
exportconstexternref_value={hello: 'world'};
17+
exportconstexternref_mut_value=newWebAssembly.Global({value: 'externref',mutable: true},{mutable: 'global'});
18+
exportconstnull_externref_value=null;
3.26 KB
Binary file not shown.

0 commit comments

Comments
 (0)