Skip to content

Commit 891fa8c

Browse files
committed
wip
1 parent ad58c8d commit 891fa8c

36 files changed

+727
-17
lines changed

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module.exports = {
22
extends: ['rollup', 'plugin:import/typescript'],
3+
ignorePatterns: ['packages/wasm/test/fixtures/jsapi/**'],
34
parserOptions: {
45
project: ['./tsconfig.eslint.json', './packages/*/tsconfig.json'],
56
tsconfigRootDir: __dirname

packages/wasm/exports.wat

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
(module
2+
(type (;0;) (func (result i32)))
3+
(table $tab (;0;) 1 funcref)
4+
(memory $mem (;0;) 1)
5+
(global $glob (;0;) i32 i32.const 42)
6+
(global $global_with_space (;1;) i32 i32.const 123)
7+
(global $utf8_edge (;2;) i32 i32.const 789)
8+
(export "mem" (memory $mem))
9+
(export "tab" (table $tab))
10+
(export "glob" (global $glob))
11+
(export "func" (func $func))
12+
(export "valuewithspaces" (global $global_with_space))
13+
(export "\u{1f3af}test-func!" (func $emoji_func))
14+
(func $func (;0;) (type 0) (result i32)
15+
i32.const 100
16+
)
17+
(func $emoji_func (;1;) (type 0) (result i32)
18+
i32.const 456
19+
)
20+
)

packages/wasm/src/index.ts

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export function wasm(options: RollupWasmOptions = {}): Plugin {
8080
);
8181
},
8282

83-
transform(code, id) {
83+
async transform(code, id) {
8484
if (!filter(id)) {
8585
return null;
8686
}
@@ -90,8 +90,10 @@ export function wasm(options: RollupWasmOptions = {}): Plugin {
9090
const publicFilepath = copies[id] ? `'${copies[id].publicFilepath}'` : null;
9191
let src;
9292

93+
const buffer = Buffer.from(code, 'binary');
94+
9395
if (publicFilepath === null) {
94-
src = Buffer.from(code, 'binary').toString('base64');
96+
src = buffer.toString('base64');
9597
src = `'${src}'`;
9698
} else {
9799
if (isSync) {
@@ -100,16 +102,59 @@ export function wasm(options: RollupWasmOptions = {}): Plugin {
100102
src = null;
101103
}
102104

105+
const mod = await WebAssembly.compile(buffer);
106+
const imports = WebAssembly.Module.imports(mod);
107+
const exports = WebAssembly.Module.exports(mod);
108+
109+
// Generate import statements for WASM module imports
110+
let importStatements = '';
111+
let importObj = '';
112+
let i = 0;
113+
for (const { module, kind } of imports) {
114+
importStatements += `import * as i${i} from ${JSON.stringify(module)};\n`;
115+
// We use the special __wasmInstance export for Wasm - Wasm linking
116+
// to allow direct global bindings between Wasm modules.
117+
importObj += `${JSON.stringify(module)}: ${
118+
kind === 'global' ? `i${i}.__wasmInstance?.exports || i${i}` : `i${i}`
119+
},`;
120+
i += 1;
121+
}
122+
123+
// Always provide an imports object, even if empty
124+
importObj = importObj ? `{${importObj}}` : '{}';
125+
126+
// Generate export statements for WASM module exports
127+
let exportStatements = '';
128+
let exportList = '';
129+
i = 0;
130+
for (const { name, kind } of exports) {
131+
const idOrName = /^[$_a-z][$_a-z0-9]+$/i.test(name) ? name : JSON.stringify(name);
132+
exportStatements += `let e${i} = __wasmInstance.exports[${JSON.stringify(name)}];\n`;
133+
if (kind === 'global') {
134+
exportStatements += `try { e${i} = e${i}.value } catch { e${i} = undefined }\n`;
135+
}
136+
if (exportList) exportList += ', ';
137+
exportList += `e${i} as ${idOrName}`;
138+
i += 1;
139+
}
140+
103141
return {
104142
map: {
105143
mappings: ''
106144
},
107145
code: `import { _loadWasmModule } from ${JSON.stringify(HELPERS_ID)};
108-
export default function(imports){return _loadWasmModule(${+isSync}, ${publicFilepath}, ${src}, imports)}`
146+
${importStatements}
147+
export const __wasmInstance = ${
148+
isSync ? '' : 'await '
149+
}_loadWasmModule(${+isSync}, ${publicFilepath}, ${src}, ${importObj});
150+
${exportStatements}
151+
export { ${exportList} };
152+
`
109153
};
110154
}
111155
return null;
112156
},
157+
113158
generateBundle: async function write() {
114159
await Promise.all(
115160
Object.keys(copies).map(async (name) => {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// META: global=window,dedicatedworker,jsshell,shadowrealm
2+
3+
"use strict";
4+
5+
promise_test(async () => {
6+
const mod = await import("./resources/exports.wasm");
7+
8+
assert_array_equals(Object.getOwnPropertyNames(mod).sort(), [
9+
"func",
10+
"glob",
11+
"mem",
12+
"tab",
13+
"🎯test-func!",
14+
]);
15+
assert_true(mod.func instanceof Function);
16+
assert_true(mod.mem instanceof WebAssembly.Memory);
17+
assert_true(mod.tab instanceof WebAssembly.Table);
18+
19+
assert_false(mod.glob instanceof WebAssembly.Global);
20+
assert_equals(typeof mod.glob, "number");
21+
22+
assert_throws_js(TypeError, () => {
23+
mod.func = 2;
24+
});
25+
26+
assert_equals(typeof mod["value with spaces"], "number");
27+
assert_equals(mod["value with spaces"], 123);
28+
29+
assert_true(mod["🎯test-func!"] instanceof Function);
30+
assert_equals(mod["🎯test-func!"](), 456);
31+
32+
assert_equals(typeof mod["a\u200Bb\u0300c"], "number");
33+
assert_equals(mod["a\u200Bb\u0300c"], 789);
34+
}, "Exported names from a WebAssembly module");
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// META: global=window,dedicatedworker,jsshell,shadowrealm
2+
3+
promise_test(async () => {
4+
const wasmExports = await import("./resources/globals.wasm");
5+
6+
wasmExports.setLocalMutI32(555);
7+
assert_equals(wasmExports.getLocalMutI32(), 555);
8+
assert_equals(wasmExports.localMutI32, 555);
9+
10+
wasmExports.setLocalMutI64(444n);
11+
assert_equals(wasmExports.getLocalMutI64(), 444n);
12+
assert_equals(wasmExports.localMutI64, 444n);
13+
14+
wasmExports.setLocalMutF32(3.33);
15+
assert_equals(Math.round(wasmExports.getLocalMutF32() * 100) / 100, 3.33);
16+
assert_equals(Math.round(wasmExports.localMutF32 * 100) / 100, 3.33);
17+
18+
wasmExports.setLocalMutF64(2.22);
19+
assert_equals(wasmExports.getLocalMutF64(), 2.22);
20+
assert_equals(wasmExports.localMutF64, 2.22);
21+
22+
const anotherTestObj = { another: "test object" };
23+
wasmExports.setLocalMutExternref(anotherTestObj);
24+
assert_equals(wasmExports.getLocalMutExternref(), anotherTestObj);
25+
assert_equals(wasmExports.localMutExternref, anotherTestObj);
26+
}, "Local mutable global exports should be live bindings");
27+
28+
promise_test(async () => {
29+
const wasmExports = await import("./resources/globals.wasm");
30+
31+
wasmExports.setDepMutI32(3001);
32+
assert_equals(wasmExports.getDepMutI32(), 3001);
33+
assert_equals(wasmExports.depMutI32, 3001);
34+
35+
wasmExports.setDepMutI64(30000000001n);
36+
assert_equals(wasmExports.getDepMutI64(), 30000000001n);
37+
assert_equals(wasmExports.depMutI64, 30000000001n);
38+
39+
wasmExports.setDepMutF32(30.01);
40+
assert_equals(Math.round(wasmExports.getDepMutF32() * 100) / 100, 30.01);
41+
assert_equals(Math.round(wasmExports.depMutF32 * 100) / 100, 30.01);
42+
43+
wasmExports.setDepMutF64(300.0001);
44+
assert_equals(wasmExports.getDepMutF64(), 300.0001);
45+
assert_equals(wasmExports.depMutF64, 300.0001);
46+
}, "Dep module mutable global exports should be live bindings");
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// META: global=window,dedicatedworker,jsshell,shadowrealm
2+
3+
promise_test(async () => {
4+
const wasmModule = await import("./resources/globals.wasm");
5+
6+
assert_equals(wasmModule.importedI32, 42);
7+
assert_equals(wasmModule.importedI64, 9223372036854775807n);
8+
assert_equals(Math.round(wasmModule.importedF32 * 100000) / 100000, 3.14159);
9+
assert_equals(wasmModule.importedF64, 3.141592653589793);
10+
assert_not_equals(wasmModule.importedExternref, null);
11+
assert_equals(wasmModule.importedNullExternref, null);
12+
}, "WebAssembly module global values should be unwrapped when importing in ESM integration");
13+
14+
promise_test(async () => {
15+
const wasmModule = await import("./resources/globals.wasm");
16+
17+
assert_equals(wasmModule.importedMutI32, 100);
18+
assert_equals(wasmModule.importedMutI64, 200n);
19+
assert_equals(
20+
Math.round(wasmModule.importedMutF32 * 100000) / 100000,
21+
2.71828
22+
);
23+
assert_equals(wasmModule.importedMutF64, 2.718281828459045);
24+
assert_not_equals(wasmModule.importedMutExternref, null);
25+
assert_equals(wasmModule.importedMutExternref.mutable, "global");
26+
}, "WebAssembly mutable global values should be unwrapped when importing in ESM integration");
27+
28+
promise_test(async () => {
29+
const wasmModule = await import("./resources/globals.wasm");
30+
31+
assert_equals(wasmModule["🚀localI32"], 42);
32+
assert_equals(wasmModule.localMutI32, 100);
33+
assert_equals(wasmModule.localI64, 9223372036854775807n);
34+
assert_equals(wasmModule.localMutI64, 200n);
35+
assert_equals(Math.round(wasmModule.localF32 * 100000) / 100000, 3.14159);
36+
assert_equals(Math.round(wasmModule.localMutF32 * 100000) / 100000, 2.71828);
37+
assert_equals(wasmModule.localF64, 2.718281828459045);
38+
assert_equals(wasmModule.localMutF64, 3.141592653589793);
39+
}, "WebAssembly local global values should be unwrapped when exporting in ESM integration");
40+
41+
promise_test(async () => {
42+
const wasmModule = await import("./resources/globals.wasm");
43+
44+
assert_equals(wasmModule.depI32, 1001);
45+
assert_equals(wasmModule.depMutI32, 2001);
46+
assert_equals(wasmModule.depI64, 10000000001n);
47+
assert_equals(wasmModule.depMutI64, 20000000001n);
48+
assert_equals(Math.round(wasmModule.depF32 * 100) / 100, 10.01);
49+
assert_equals(Math.round(wasmModule.depMutF32 * 100) / 100, 20.01);
50+
assert_equals(wasmModule.depF64, 100.0001);
51+
assert_equals(wasmModule.depMutF64, 200.0001);
52+
}, "WebAssembly module globals from imported WebAssembly modules should be unwrapped");
53+
54+
promise_test(async () => {
55+
const wasmModule = await import("./resources/globals.wasm");
56+
57+
assert_equals(wasmModule.importedI32, 42);
58+
assert_equals(wasmModule.importedMutI32, 100);
59+
assert_equals(wasmModule.importedI64, 9223372036854775807n);
60+
assert_equals(wasmModule.importedMutI64, 200n);
61+
assert_equals(Math.round(wasmModule.importedF32 * 100000) / 100000, 3.14159);
62+
assert_equals(
63+
Math.round(wasmModule.importedMutF32 * 100000) / 100000,
64+
2.71828
65+
);
66+
assert_equals(wasmModule.importedF64, 3.141592653589793);
67+
assert_equals(wasmModule.importedMutF64, 2.718281828459045);
68+
assert_equals(wasmModule.importedExternref !== null, true);
69+
assert_equals(wasmModule.importedMutExternref !== null, true);
70+
assert_equals(wasmModule.importedNullExternref, null);
71+
72+
assert_equals(wasmModule["🚀localI32"], 42);
73+
assert_equals(wasmModule.localMutI32, 100);
74+
assert_equals(wasmModule.localI64, 9223372036854775807n);
75+
assert_equals(wasmModule.localMutI64, 200n);
76+
assert_equals(Math.round(wasmModule.localF32 * 100000) / 100000, 3.14159);
77+
assert_equals(Math.round(wasmModule.localMutF32 * 100000) / 100000, 2.71828);
78+
assert_equals(wasmModule.localF64, 2.718281828459045);
79+
assert_equals(wasmModule.localMutF64, 3.141592653589793);
80+
81+
assert_equals(wasmModule.getImportedMutI32(), 100);
82+
assert_equals(wasmModule.getImportedMutI64(), 200n);
83+
assert_equals(
84+
Math.round(wasmModule.getImportedMutF32() * 100000) / 100000,
85+
2.71828
86+
);
87+
assert_equals(wasmModule.getImportedMutF64(), 2.718281828459045);
88+
assert_equals(wasmModule.getImportedMutExternref() !== null, true);
89+
90+
assert_equals(wasmModule.getLocalMutI32(), 100);
91+
assert_equals(wasmModule.getLocalMutI64(), 200n);
92+
assert_equals(
93+
Math.round(wasmModule.getLocalMutF32() * 100000) / 100000,
94+
2.71828
95+
);
96+
assert_equals(wasmModule.getLocalMutF64(), 3.141592653589793);
97+
assert_equals(wasmModule.getLocalMutExternref(), null);
98+
99+
assert_equals(wasmModule.depI32, 1001);
100+
assert_equals(wasmModule.depMutI32, 2001);
101+
assert_equals(wasmModule.getDepMutI32(), 2001);
102+
assert_equals(wasmModule.depI64, 10000000001n);
103+
assert_equals(wasmModule.depMutI64, 20000000001n);
104+
assert_equals(wasmModule.getDepMutI64(), 20000000001n);
105+
assert_equals(Math.round(wasmModule.depF32 * 100) / 100, 10.01);
106+
assert_equals(Math.round(wasmModule.depMutF32 * 100) / 100, 20.01);
107+
assert_equals(Math.round(wasmModule.getDepMutF32() * 100) / 100, 20.01);
108+
assert_equals(wasmModule.depF64, 100.0001);
109+
assert_equals(wasmModule.depMutF64, 200.0001);
110+
assert_equals(wasmModule.getDepMutF64(), 200.0001);
111+
}, "WebAssembly should properly handle all global types");
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// META: global=window,dedicatedworker,jsshell,shadowrealm
2+
3+
promise_test(async () => {
4+
const { f } = await import("./resources/js-wasm-cycle.js");
5+
6+
assert_equals(f(), 24);
7+
}, "Check bindings in JavaScript and WebAssembly cycle (JS higher)");
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
promise_test(async () => {
2+
const exporterModule = await import("./resources/mutable-global-export.wasm");
3+
const reexporterModule = await import(
4+
"./resources/mutable-global-reexport.wasm"
5+
);
6+
7+
assert_equals(exporterModule.mutableValue, 100);
8+
assert_equals(reexporterModule.reexportedMutableValue, 100);
9+
}, "WebAssembly modules should export shared mutable globals with correct initial values");
10+
11+
promise_test(async () => {
12+
const exporterModule = await import("./resources/mutable-global-export.wasm");
13+
const reexporterModule = await import(
14+
"./resources/mutable-global-reexport.wasm"
15+
);
16+
17+
exporterModule.setGlobal(500);
18+
19+
assert_equals(exporterModule.getGlobal(), 500, "exporter should see 500");
20+
assert_equals(reexporterModule.getImportedGlobal(), 500);
21+
22+
reexporterModule.setImportedGlobal(600);
23+
24+
assert_equals(exporterModule.getGlobal(), 600);
25+
assert_equals(reexporterModule.getImportedGlobal(), 600);
26+
27+
exporterModule.setGlobal(700);
28+
29+
assert_equals(exporterModule.getGlobal(), 700);
30+
assert_equals(reexporterModule.getImportedGlobal(), 700);
31+
}, "Wasm-to-Wasm mutable global sharing is live");
32+
33+
promise_test(async () => {
34+
const module1 = await import("./resources/mutable-global-export.wasm");
35+
const module2 = await import("./resources/mutable-global-export.wasm");
36+
37+
assert_equals(module1, module2);
38+
39+
module1.setGlobal(800);
40+
assert_equals(module1.getGlobal(), 800, "module1 should see its own change");
41+
assert_equals(module2.getGlobal(), 800);
42+
}, "Multiple JavaScript imports return the same WebAssembly module instance");
43+
44+
promise_test(async () => {
45+
const exporterModule = await import("./resources/mutable-global-export.wasm");
46+
const reexporterModule = await import(
47+
"./resources/mutable-global-reexport.wasm"
48+
);
49+
50+
assert_equals(exporterModule.getV128Lane(0), 1);
51+
assert_equals(exporterModule.getV128Lane(1), 2);
52+
assert_equals(exporterModule.getV128Lane(2), 3);
53+
assert_equals(exporterModule.getV128Lane(3), 4);
54+
55+
assert_equals(reexporterModule.getImportedV128Lane(0), 1);
56+
assert_equals(reexporterModule.getImportedV128Lane(1), 2);
57+
assert_equals(reexporterModule.getImportedV128Lane(2), 3);
58+
assert_equals(reexporterModule.getImportedV128Lane(3), 4);
59+
}, "v128 globals should work correctly in WebAssembly-to-WebAssembly imports");
60+
61+
promise_test(async () => {
62+
const exporterModule = await import("./resources/mutable-global-export.wasm");
63+
const reexporterModule = await import(
64+
"./resources/mutable-global-reexport.wasm"
65+
);
66+
67+
exporterModule.setV128Global(10, 20, 30, 40);
68+
69+
assert_equals(exporterModule.getV128Lane(0), 10);
70+
assert_equals(exporterModule.getV128Lane(1), 20);
71+
assert_equals(exporterModule.getV128Lane(2), 30);
72+
assert_equals(exporterModule.getV128Lane(3), 40);
73+
74+
assert_equals(reexporterModule.getImportedV128Lane(0), 10);
75+
assert_equals(reexporterModule.getImportedV128Lane(1), 20);
76+
assert_equals(reexporterModule.getImportedV128Lane(2), 30);
77+
assert_equals(reexporterModule.getImportedV128Lane(3), 40);
78+
}, "v128 global mutations should work correctly between WebAssembly modules");

0 commit comments

Comments
 (0)