MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1import { readFileSync } from 'ant:fs';
2
3function assert(condition, message) {
4 if (!condition) throw new Error(message);
5}
6
7function fixture(path) {
8 return readFileSync(import.meta.dirname + '/../' + path);
9}
10
11const api = globalThis.WebAssembly;
12
13assert(api && typeof api === 'object', 'globalThis.WebAssembly should exist');
14
15for (const name of ['Global', 'Instance', 'Memory', 'Module', 'Table', 'Tag', 'Exception', 'CompileError', 'LinkError', 'RuntimeError']) {
16 assert(typeof api[name] === 'function', `WebAssembly.${name} should exist`);
17}
18
19const incrementer = fixture('wpt/wasm/webapi/resources/incrementer.wasm');
20const tableExportFixture = new Uint8Array([
21 0, 97, 115, 109, 1, 0, 0, 0, 1, 9, 2, 96, 0, 1, 127, 96, 0, 1, 127, 3, 3, 2, 0, 1, 4, 4, 1, 112, 0, 2, 7, 24, 3, 5, 115, 101, 118, 101, 110, 0, 0,
22 4, 110, 105, 110, 101, 0, 1, 5, 116, 97, 98, 108, 101, 1, 0, 9, 8, 1, 0, 65, 0, 11, 2, 0, 1, 10, 11, 2, 4, 0, 65, 7, 11, 4, 0, 65, 9, 11, 0, 21, 4,
23 110, 97, 109, 101, 1, 14, 2, 0, 5, 115, 101, 118, 101, 110, 1, 4, 110, 105, 110, 101
24]);
25const throwingImportFixture = new Uint8Array([
26 0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, 0, 2, 12, 1, 3, 101, 110, 118, 4, 102, 97, 105, 108, 0, 0, 3, 2, 1, 0, 7, 7, 1, 3, 114, 117, 110, 0, 1,
27 10, 6, 1, 4, 0, 16, 0, 11
28]);
29
30assert(WebAssembly.validate(incrementer) === true, 'incrementer.wasm should validate');
31
32const module = new WebAssembly.Module(incrementer);
33const descriptors = WebAssembly.Module.exports(module);
34assert(Array.isArray(descriptors), 'WebAssembly.Module.exports() should return an array');
35assert(
36 descriptors.some(entry => entry && entry.name === 'increment' && entry.kind === 'function'),
37 'incrementer.wasm should export increment()'
38);
39
40const instance = new WebAssembly.Instance(module);
41assert(typeof instance.exports.increment === 'function', 'instance.exports.increment should exist');
42assert(instance.exports.increment(41) === 42, 'increment(41) should return 42');
43
44const tableModule = new WebAssembly.Module(tableExportFixture);
45const tableInstance = new WebAssembly.Instance(tableModule);
46assert(tableInstance.exports.table.length === 2, 'exported table should expose its length');
47const firstTableEntry = tableInstance.exports.table.get(0);
48const secondTableEntry = tableInstance.exports.table.get(1);
49assert(typeof firstTableEntry === 'function', 'table.get(0) should return a callable function');
50assert(typeof secondTableEntry === 'function', 'table.get(1) should return a callable function');
51assert(firstTableEntry() === 7, 'table.get(0) should invoke the first table function');
52assert(secondTableEntry() === 9, 'table.get(1) should invoke the second table function');
53tableInstance.exports.table.set(0, tableInstance.exports.nine);
54assert(tableInstance.exports.table.get(0)() === 9, 'table.set() should accept wasm-exported functions');
55
56const throwingModule = new WebAssembly.Module(throwingImportFixture);
57
58let sawPrimitiveThrow = false;
59try {
60 new WebAssembly.Instance(throwingModule, {
61 env: {
62 fail() {
63 throw Infinity;
64 }
65 }
66 }).exports.run();
67} catch (error) {
68 sawPrimitiveThrow = true;
69 assert(error === Infinity, 'import throws should preserve primitive thrown values');
70}
71assert(sawPrimitiveThrow, 'primitive import throws should escape the wasm call');
72
73const thrownError = new Error('import boom');
74let sawErrorThrow = false;
75try {
76 new WebAssembly.Instance(throwingModule, {
77 env: {
78 fail() {
79 throw thrownError;
80 }
81 }
82 }).exports.run();
83} catch (error) {
84 sawErrorThrow = true;
85 assert(error === thrownError, 'import throws should preserve original Error objects');
86}
87assert(sawErrorThrow, 'Error import throws should escape the wasm call');
88
89const thrownStatus = {
90 name: 'ExitStatus',
91 message: 'Program terminated with exit(1)',
92 status: 1
93};
94let sawErrorlikeThrow = false;
95try {
96 new WebAssembly.Instance(throwingModule, {
97 env: {
98 fail() {
99 throw thrownStatus;
100 }
101 }
102 }).exports.run();
103} catch (error) {
104 sawErrorlikeThrow = true;
105 assert(error === thrownStatus, 'import throws should preserve original error-like objects');
106 assert(error.stack === undefined, 'error-like import throws should not grow a synthetic stack');
107}
108assert(sawErrorlikeThrow, 'error-like import throws should escape the wasm call');
109
110const compiled = await WebAssembly.compile(incrementer);
111assert(compiled instanceof WebAssembly.Module, 'WebAssembly.compile() should resolve a module');
112
113const instantiated = await WebAssembly.instantiate(incrementer);
114assert(instantiated.module instanceof WebAssembly.Module, 'instantiate(bytes) should return a module');
115assert(instantiated.instance instanceof WebAssembly.Instance, 'instantiate(bytes) should return an instance');
116assert(instantiated.instance.exports.increment(9) === 10, 'instantiate(bytes) should wire exports');
117
118const global = new WebAssembly.Global({ value: 'i32', mutable: true }, 7);
119assert(global.value === 7, 'WebAssembly.Global should expose its initial value');
120global.value = 11;
121assert(global.value === 11, 'WebAssembly.Global should accept writes');
122assert(global.valueOf() === 11, 'WebAssembly.Global.prototype.valueOf() should read the value');
123
124const invalid = new Uint8Array([0x00, 0x61, 0x62, 0x63]);
125assert(WebAssembly.validate(invalid) === false, 'invalid wasm bytes should fail validation');
126
127let sawCompileError = false;
128try {
129 new WebAssembly.Module(invalid);
130} catch (error) {
131 sawCompileError = true;
132 assert(error instanceof WebAssembly.CompileError || error?.name === 'CompileError', 'invalid module should throw CompileError');
133}
134assert(sawCompileError, 'invalid module construction should fail');
135
136console.log('wasm:webassembly-api:ok');