GeezerVison is showing github.com — esm: unwrap WebAssembly.Global on Wasm Namespaces · nodejs/node@767e88c
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: lib/internal/modules/esm/create_dynamic_module.js +1-1Lines changed: 1 addition & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -44,7 +44,7 @@ import.meta.exports[${nameStringLit}] = { | 44 | 44 | * @param {string} [url=''] - The URL of the module. | 45 | 45 | * @param {(reflect: DynamicModuleReflect) => void} evaluate - The function to evaluate the module. | 46 | 46 | * @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. | 48 | 48 | * @property {string[]} exports - The exports of the module. | 49 | 49 | * @property {(cb: (reflect: DynamicModuleReflect) => void) => void} onReady - Callback to evaluate the module. | 50 | 50 | */ | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/modules/esm/translators.js +63-13Lines changed: 63 additions & 13 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 1 | 1 | 'use strict'; | 2 | 2 |
| 3 | 3 | const { | 4 | | - ArrayPrototypeMap, | 5 | 4 | ArrayPrototypePush, | 6 | 5 | FunctionPrototypeCall, | 7 | 6 | JSONParse, | 8 | | - ObjectKeys, | | 7 | + ObjectAssign, | 9 | 8 | ObjectPrototypeHasOwnProperty, | 10 | 9 | ReflectApply, | 11 | 10 | SafeArrayIterator, | 12 | 11 | SafeMap, | 13 | 12 | SafeSet, | | 13 | + SafeWeakMap, | 14 | 14 | StringPrototypeIncludes, | 15 | 15 | StringPrototypeReplaceAll, | 16 | 16 | StringPrototypeSlice, | @@ -484,6 +484,14 @@ translators.set('json', function jsonStrategy(url, source) { | 484 | 484 | }); | 485 | 485 |
| 486 | 486 | // 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 | +const wasmInstances = new SafeWeakMap(); | 487 | 495 | translators.set('wasm', async function(url, source) { | 488 | 496 | emitExperimentalWarning('Importing WebAssembly modules'); | 489 | 497 |
| @@ -502,19 +510,61 @@ translators.set('wasm', async function(url, source) { | 502 | 510 | throw err; | 503 | 511 | } | 504 | 512 |
| 505 | | - const imports = | 506 | | - ArrayPrototypeMap(WebAssembly.Module.imports(compiled), | 507 | | - ({ module }) => module); | 508 | | - const exports = | 509 | | - ArrayPrototypeMap(WebAssembly.Module.exports(compiled), | 510 | | - ({ name }) => name); | | 513 | + const importsList = new SafeSet(); | | 514 | + const wasmGlobalImports = []; | | 515 | + for (const impt of WebAssembly.Module.imports(compiled)) { | | 516 | + if (impt.kind === 'global') { | | 517 | + ArrayPrototypePush(wasmGlobalImports, impt); | | 518 | + } | | 519 | + importsList.add(impt.module); | | 520 | + } | | 521 | + | | 522 | + const exportsList = new SafeSet(); | | 523 | + const wasmGlobalExports = new SafeSet(); | | 524 | + for (const expt of WebAssembly.Module.exports(compiled)) { | | 525 | + if (expt.kind === 'global') { | | 526 | + wasmGlobalExports.add(expt.name); | | 527 | + } | | 528 | + exportsList.add(expt.name); | | 529 | + } | 511 | 530 |
| 512 | | - const createDynamicModule = require( | 513 | | - 'internal/modules/esm/create_dynamic_module'); | 514 | | - const { module } = createDynamicModule(imports, exports, url, (reflect) => { | | 531 | + const createDynamicModule = require('internal/modules/esm/create_dynamic_module'); | | 532 | + | | 533 | + const { module } = createDynamicModule([...importsList], [...exportsList], url, (reflect) => { | | 534 | + for (const impt of importsList) { | | 535 | + const importNs = reflect.imports[impt]; | | 536 | + const wasmInstance = wasmInstances.get(importNs); | | 537 | + if (wasmInstance) { | | 538 | + const wrappedModule = ObjectAssign({ __proto__: null }, reflect.imports[impt]); | | 539 | + for (const { module, name } of wasmGlobalImports) { | | 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). | 515 | 552 | const { exports } = new WebAssembly.Instance(compiled, reflect.imports); | 516 | | - for (const expt of ObjectKeys(exports)) { | 517 | | - reflect.exports[expt].set(exports[expt]); | | 553 | + wasmInstances.set(module.getNamespace(), exports); | | 554 | + for (const expt of exportsList) { | | 555 | + let val = 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); | 518 | 568 | } | 519 | 569 | }); | 520 | 570 | // WebAssembly modules support source phase imports, to import the compiled module | |
Collapse file Copy file name to clipboardExpand all lines: test/es-module/test-esm-wasm.mjs +148-1Lines changed: 148 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -52,7 +52,154 @@ describe('ESM: WASM modules', { concurrency: !process.env.TEST_PARALLEL }, () => | 52 | 52 | [ | 53 | 53 | 'import { strictEqual } from "node:assert";', | 54 | 54 | `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 } = await spawnPromisified(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 | + '}', | 56 | 203 | ].join('\n'), | 57 | 204 | ]); | 58 | 205 |
| |
Collapse file Copy file name to clipboard Collapse file Copy file name to clipboard +32Lines changed: 32 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +(module | | 2 | + (type (;0;) (func (result i32))) | | 3 | + (type (;1;) (func (result i64))) | | 4 | + (type (;2;) (func (result f32))) | | 5 | + (type (;3;) (func (result f64))) | | 6 | + (type (;4;) (func (result externref))) | | 7 | + (type (;5;) (func (result v128))) | | 8 | + (global $i32_value (;0;) i32 i32.const 1001) | | 9 | + (global $i32_mut_value (;1;) (mut i32) i32.const 2001) | | 10 | + (global $i64_value (;2;) i64 i64.const 10000000001) | | 11 | + (global $i64_mut_value (;3;) (mut i64) i64.const 20000000001) | | 12 | + (global $f32_value (;4;) f32 f32.const 0x1.4051ecp+3 (;=10.01;)) | | 13 | + (global $f32_mut_value (;5;) (mut f32) f32.const 0x1.4028f6p+4 (;=20.01;)) | | 14 | + (global $f64_value (;6;) f64 f64.const 0x1.90001a36e2eb2p+6 (;=100.0001;)) | | 15 | + (global $f64_mut_value (;7;) (mut f64) f64.const 0x1.90000d1b71759p+7 (;=200.0001;)) | | 16 | + (global $externref_value (;8;) externref ref.null extern) | | 17 | + (global $externref_mut_value (;9;) (mut externref) ref.null extern) | | 18 | + (global $v128_value (;10;) v128 v128.const i32x4 0x0000000a 0x00000014 0x0000001e 0x00000028) | | 19 | + (global $v128_mut_value (;11;) (mut v128) v128.const i32x4 0x00000032 0x0000003c 0x00000046 0x00000050) | | 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 | +) |
Collapse file Copy file name to clipboard +18Lines changed: 18 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +// globals.js - Direct global exports for WebAssembly imports | | 2 | + | | 3 | +// Immutable globals (simple values) | | 4 | +const i32_value = 42; | | 5 | +export { i32_value as '🚀i32_value' } | | 6 | +export const i64_value = 9223372036854775807n; // Max i64 value | | 7 | +export const f32_value = 3.14159; | | 8 | +export const f64_value = 3.141592653589793; | | 9 | + | | 10 | +// Mutable globals with WebAssembly.Global wrapper | | 11 | +export const i32_mut_value = new WebAssembly.Global({ value: 'i32', mutable: true }, 100); | | 12 | +export const i64_mut_value = new WebAssembly.Global({ value: 'i64', mutable: true }, 200n); | | 13 | +export const f32_mut_value = new WebAssembly.Global({ value: 'f32', mutable: true }, 2.71828); | | 14 | +export const f64_mut_value = new WebAssembly.Global({ value: 'f64', mutable: true }, 2.718281828459045); | | 15 | + | | 16 | +export const externref_value = { hello: 'world' }; | | 17 | +export const externref_mut_value = new WebAssembly.Global({ value: 'externref', mutable: true }, { mutable: 'global' }); | | 18 | +export const null_externref_value = null; |
Collapse file Copy file name to clipboard
|