@thi.ng/wasm-api 1.4.36 → 1.4.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1 -1
- package/README.md +1 -1
- package/api.js +6 -2
- package/bridge.js +436 -409
- package/object-index.js +74 -72
- package/package.json +14 -11
- package/pointer.js +34 -58
- package/string.js +201 -218
package/bridge.js
CHANGED
|
@@ -1,419 +1,446 @@
|
|
|
1
|
-
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
4
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
5
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
6
|
+
if (decorator = decorators[i])
|
|
7
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
8
|
+
if (kind && result)
|
|
9
|
+
__defProp(target, key, result);
|
|
10
|
+
return result;
|
|
11
|
+
};
|
|
2
12
|
import { INotifyMixin } from "@thi.ng/api/mixins/inotify";
|
|
3
13
|
import { topoSort } from "@thi.ng/arrays/topo-sort";
|
|
4
14
|
import { assert } from "@thi.ng/errors/assert";
|
|
5
15
|
import { defError } from "@thi.ng/errors/deferror";
|
|
6
16
|
import { U16, U32, U64BIG, U8, hexdumpLines } from "@thi.ng/hex";
|
|
7
17
|
import { ConsoleLogger } from "@thi.ng/logger/console";
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
u32;
|
|
36
|
-
i64;
|
|
37
|
-
u64;
|
|
38
|
-
f32;
|
|
39
|
-
f64;
|
|
40
|
-
utf8Decoder = new TextDecoder();
|
|
41
|
-
utf8Encoder = new TextEncoder();
|
|
42
|
-
imports;
|
|
43
|
-
exports;
|
|
44
|
-
api;
|
|
45
|
-
modules;
|
|
46
|
-
constructor(modules = [], logger = new ConsoleLogger("wasm")) {
|
|
47
|
-
this.logger = logger;
|
|
48
|
-
const logN = (x) => this.logger.debug(x);
|
|
49
|
-
const logA = (method) => (addr, len) => this.logger.debug(() => method(addr, len).join(", "));
|
|
50
|
-
this.api = {
|
|
51
|
-
printI8: logN,
|
|
52
|
-
printU8: logN,
|
|
53
|
-
printI16: logN,
|
|
54
|
-
printU16: logN,
|
|
55
|
-
printI32: logN,
|
|
56
|
-
printU32: (x) => this.logger.debug(x >>> 0),
|
|
57
|
-
printI64: (x) => this.logger.debug(x),
|
|
58
|
-
printU64: (x) => this.logger.debug(x),
|
|
59
|
-
printF32: logN,
|
|
60
|
-
printF64: logN,
|
|
61
|
-
printU8Hex: (x) => this.logger.debug(() => `0x${U8(x)}`),
|
|
62
|
-
printU16Hex: (x) => this.logger.debug(() => `0x${U16(x)}`),
|
|
63
|
-
printU32Hex: (x) => this.logger.debug(() => `0x${U32(x)}`),
|
|
64
|
-
printU64Hex: (x) => this.logger.debug(() => `0x${U64BIG(x)}`),
|
|
65
|
-
printHexdump: (addr, len) => {
|
|
66
|
-
this.ensureMemory();
|
|
67
|
-
for (let line of hexdumpLines(this.u8, addr, len)) {
|
|
68
|
-
this.logger.debug(line);
|
|
69
|
-
}
|
|
70
|
-
},
|
|
71
|
-
_printI8Array: logA(this.getI8Array.bind(this)),
|
|
72
|
-
_printU8Array: logA(this.getU8Array.bind(this)),
|
|
73
|
-
_printI16Array: logA(this.getI16Array.bind(this)),
|
|
74
|
-
_printU16Array: logA(this.getU16Array.bind(this)),
|
|
75
|
-
_printI32Array: logA(this.getI32Array.bind(this)),
|
|
76
|
-
_printU32Array: logA(this.getU32Array.bind(this)),
|
|
77
|
-
_printI64Array: logA(this.getI64Array.bind(this)),
|
|
78
|
-
_printU64Array: logA(this.getU64Array.bind(this)),
|
|
79
|
-
_printF32Array: logA(this.getF32Array.bind(this)),
|
|
80
|
-
_printF64Array: logA(this.getF64Array.bind(this)),
|
|
81
|
-
printStrZ: (addr) => this.logger.debug(() => this.getString(addr, 0)),
|
|
82
|
-
_printStr: (addr, len) => this.logger.debug(() => this.getString(addr, len)),
|
|
83
|
-
debug: () => {
|
|
84
|
-
debugger;
|
|
85
|
-
},
|
|
86
|
-
_panic: (addr, len) => {
|
|
87
|
-
const msg = this.getString(addr, len);
|
|
88
|
-
if (!this.notify({ id: EVENT_PANIC, value: msg })) {
|
|
89
|
-
throw new Panic(msg);
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
timer: () => performance.now(),
|
|
93
|
-
epoch: () => BigInt(Date.now()),
|
|
94
|
-
};
|
|
95
|
-
this.modules = modules.reduce((acc, x) => {
|
|
96
|
-
assert(acc[x.id] === undefined && x.id !== this.id, `duplicate API module ID: ${x.id}`);
|
|
97
|
-
acc[x.id] = x;
|
|
98
|
-
return acc;
|
|
99
|
-
}, {});
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Instantiates WASM module from given `src` (and optional provided extra
|
|
103
|
-
* imports), then automatically calls {@link WasmBridge.init} with the
|
|
104
|
-
* modules exports.
|
|
105
|
-
*
|
|
106
|
-
* @remarks
|
|
107
|
-
* If the given `src` is a `Response` or `Promise<Response>`, the module
|
|
108
|
-
* will be instantiated via `WebAssembly.instantiateStreaming()`, otherwise
|
|
109
|
-
* the non-streaming version will be used.
|
|
110
|
-
*
|
|
111
|
-
* @param src
|
|
112
|
-
* @param imports
|
|
113
|
-
*/
|
|
114
|
-
async instantiate(src, imports) {
|
|
115
|
-
const $src = await src;
|
|
116
|
-
const $imports = { ...this.getImports(), ...imports };
|
|
117
|
-
const wasm = await ($src instanceof Response
|
|
118
|
-
? WebAssembly.instantiateStreaming($src, $imports)
|
|
119
|
-
: WebAssembly.instantiate($src, $imports));
|
|
120
|
-
return this.init(wasm.instance.exports);
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Receives the WASM module's combined exports, stores them for future
|
|
124
|
-
* reference and then initializes all declared bridge child API modules in
|
|
125
|
-
* their stated dependency order. Returns false if any of the module
|
|
126
|
-
* initializations failed.
|
|
127
|
-
*
|
|
128
|
-
* @remarks
|
|
129
|
-
* Emits the {@link EVENT_MEMORY_CHANGED} event just before returning (and
|
|
130
|
-
* AFTER all child API modules have been initialized).
|
|
131
|
-
*
|
|
132
|
-
* @param exports
|
|
133
|
-
*/
|
|
134
|
-
async init(exports) {
|
|
135
|
-
this.exports = exports;
|
|
136
|
-
this.ensureMemory(false);
|
|
137
|
-
for (let id of topoSort(this.modules, (module) => module.dependencies)) {
|
|
138
|
-
assert(!!this.modules[id], `missing API module: ${id}`);
|
|
139
|
-
this.logger.debug(`initializing API module: ${id}`);
|
|
140
|
-
const status = await this.modules[id].init(this);
|
|
141
|
-
if (!status)
|
|
142
|
-
return false;
|
|
143
|
-
}
|
|
144
|
-
this.notify({ id: EVENT_MEMORY_CHANGED, value: this.exports.memory });
|
|
145
|
-
return true;
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Called automatically during initialization and from other memory
|
|
149
|
-
* accessors. Initializes and/or updates the various typed WASM memory views
|
|
150
|
-
* (e.g. after growing the WASM memory and the previous buffer becoming
|
|
151
|
-
* detached). Unless `notify` is false, the {@link EVENT_MEMORY_CHANGED}
|
|
152
|
-
* event will be emitted if the memory views had to be updated.
|
|
153
|
-
*
|
|
154
|
-
* @param notify
|
|
155
|
-
*/
|
|
156
|
-
ensureMemory(notify = true) {
|
|
157
|
-
const buf = this.exports.memory.buffer;
|
|
158
|
-
if (this.u8 && this.u8.buffer === buf)
|
|
159
|
-
return;
|
|
160
|
-
this.i8 = new Int8Array(buf);
|
|
161
|
-
this.u8 = new Uint8Array(buf);
|
|
162
|
-
this.i16 = new Int16Array(buf);
|
|
163
|
-
this.u16 = new Uint16Array(buf);
|
|
164
|
-
this.i32 = new Int32Array(buf);
|
|
165
|
-
this.u32 = new Uint32Array(buf);
|
|
166
|
-
this.i64 = new BigInt64Array(buf);
|
|
167
|
-
this.u64 = new BigUint64Array(buf);
|
|
168
|
-
this.f32 = new Float32Array(buf);
|
|
169
|
-
this.f64 = new Float64Array(buf);
|
|
170
|
-
notify &&
|
|
171
|
-
this.notify({
|
|
172
|
-
id: EVENT_MEMORY_CHANGED,
|
|
173
|
-
value: this.exports.memory,
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* Required use for WASM module instantiation to provide JS imports to the
|
|
178
|
-
* module. Returns an object of all WASM imports declared by the bridge core
|
|
179
|
-
* API and any provided bridge API modules.
|
|
180
|
-
*
|
|
181
|
-
* @remarks
|
|
182
|
-
* Each API module's imports will be in their own WASM import object/table,
|
|
183
|
-
* named using the same key which is defined by the JS side of the module
|
|
184
|
-
* via {@link IWasmAPI.id}. The bridge's core API is named `wasmapi` and is
|
|
185
|
-
* reserved.
|
|
186
|
-
*
|
|
187
|
-
* @example
|
|
188
|
-
* The following creates a bridge with a fictional `custom` API module:
|
|
189
|
-
*
|
|
190
|
-
* ```ts
|
|
191
|
-
* const bridge = new WasmBridge([new CustomAPI()]);
|
|
192
|
-
*
|
|
193
|
-
* // get combined imports object
|
|
194
|
-
* bridge.getImports();
|
|
195
|
-
* {
|
|
196
|
-
* // imports defined by the core API of the bridge itself
|
|
197
|
-
* wasmapi: { ... },
|
|
198
|
-
* // imports defined by the CustomAPI module
|
|
199
|
-
* custom: { ... }
|
|
200
|
-
* }
|
|
201
|
-
* ```
|
|
202
|
-
*
|
|
203
|
-
* Any related API bindings on the WASM (Zig) side then also need to refer
|
|
204
|
-
* to these custom import sections (also see `/zig/core.zig`):
|
|
205
|
-
*
|
|
206
|
-
* ```zig
|
|
207
|
-
* pub export "custom" fn foo(x: u32) void;
|
|
208
|
-
* ```
|
|
209
|
-
*/
|
|
210
|
-
getImports() {
|
|
211
|
-
if (!this.imports) {
|
|
212
|
-
this.imports = { [this.id]: this.api };
|
|
213
|
-
for (let id in this.modules) {
|
|
214
|
-
this.imports[id] = this.modules[id].getImports();
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
return this.imports;
|
|
218
|
-
}
|
|
219
|
-
growMemory(numPages) {
|
|
220
|
-
this.exports.memory.grow(numPages);
|
|
18
|
+
import {
|
|
19
|
+
EVENT_MEMORY_CHANGED,
|
|
20
|
+
EVENT_PANIC
|
|
21
|
+
} from "./api.js";
|
|
22
|
+
const Panic = defError(() => "Panic");
|
|
23
|
+
const OutOfMemoryError = defError(() => "Out of memory");
|
|
24
|
+
let WasmBridge = class {
|
|
25
|
+
constructor(modules = [], logger = new ConsoleLogger("wasm")) {
|
|
26
|
+
this.logger = logger;
|
|
27
|
+
const logN = (x) => this.logger.debug(x);
|
|
28
|
+
const logA = (method) => (addr, len) => this.logger.debug(() => method(addr, len).join(", "));
|
|
29
|
+
this.api = {
|
|
30
|
+
printI8: logN,
|
|
31
|
+
printU8: logN,
|
|
32
|
+
printI16: logN,
|
|
33
|
+
printU16: logN,
|
|
34
|
+
printI32: logN,
|
|
35
|
+
printU32: (x) => this.logger.debug(x >>> 0),
|
|
36
|
+
printI64: (x) => this.logger.debug(x),
|
|
37
|
+
printU64: (x) => this.logger.debug(x),
|
|
38
|
+
printF32: logN,
|
|
39
|
+
printF64: logN,
|
|
40
|
+
printU8Hex: (x) => this.logger.debug(() => `0x${U8(x)}`),
|
|
41
|
+
printU16Hex: (x) => this.logger.debug(() => `0x${U16(x)}`),
|
|
42
|
+
printU32Hex: (x) => this.logger.debug(() => `0x${U32(x)}`),
|
|
43
|
+
printU64Hex: (x) => this.logger.debug(() => `0x${U64BIG(x)}`),
|
|
44
|
+
printHexdump: (addr, len) => {
|
|
221
45
|
this.ensureMemory();
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const addr = this.exports._wasm_allocate(numBytes);
|
|
225
|
-
if (!addr)
|
|
226
|
-
throw new OutOfMemoryError(`unable to allocate: ${numBytes}`);
|
|
227
|
-
this.logger.fine(() => `allocated ${numBytes} bytes @ 0x${U32(addr)} .. 0x${U32(addr + numBytes - 1)}`);
|
|
228
|
-
this.ensureMemory();
|
|
229
|
-
clear && this.u8.fill(0, addr, addr + numBytes);
|
|
230
|
-
return [addr, numBytes];
|
|
231
|
-
}
|
|
232
|
-
free([addr, numBytes]) {
|
|
233
|
-
this.logger.fine(() => `freeing memory @ 0x${U32(addr)} .. 0x${U32(addr + numBytes - 1)}`);
|
|
234
|
-
this.exports._wasm_free(addr, numBytes);
|
|
235
|
-
}
|
|
236
|
-
getI8(addr) {
|
|
237
|
-
return this.i8[addr];
|
|
238
|
-
}
|
|
239
|
-
getU8(addr) {
|
|
240
|
-
return this.u8[addr];
|
|
241
|
-
}
|
|
242
|
-
getI16(addr) {
|
|
243
|
-
return this.i16[addr >> 1];
|
|
244
|
-
}
|
|
245
|
-
getU16(addr) {
|
|
246
|
-
return this.u16[addr >> 1];
|
|
247
|
-
}
|
|
248
|
-
getI32(addr) {
|
|
249
|
-
return this.i32[addr >> 2];
|
|
250
|
-
}
|
|
251
|
-
getU32(addr) {
|
|
252
|
-
return this.u32[addr >> 2];
|
|
253
|
-
}
|
|
254
|
-
getI64(addr) {
|
|
255
|
-
return this.i64[addr >> 3];
|
|
256
|
-
}
|
|
257
|
-
getU64(addr) {
|
|
258
|
-
return this.u64[addr >> 3];
|
|
259
|
-
}
|
|
260
|
-
getF32(addr) {
|
|
261
|
-
return this.f32[addr >> 2];
|
|
262
|
-
}
|
|
263
|
-
getF64(addr) {
|
|
264
|
-
return this.f64[addr >> 3];
|
|
265
|
-
}
|
|
266
|
-
setI8(addr, x) {
|
|
267
|
-
this.i8[addr] = x;
|
|
268
|
-
return this;
|
|
269
|
-
}
|
|
270
|
-
setU8(addr, x) {
|
|
271
|
-
this.u8[addr] = x;
|
|
272
|
-
return this;
|
|
273
|
-
}
|
|
274
|
-
setI16(addr, x) {
|
|
275
|
-
this.i16[addr >> 1] = x;
|
|
276
|
-
return this;
|
|
277
|
-
}
|
|
278
|
-
setU16(addr, x) {
|
|
279
|
-
this.u16[addr >> 1] = x;
|
|
280
|
-
return this;
|
|
281
|
-
}
|
|
282
|
-
setI32(addr, x) {
|
|
283
|
-
this.i32[addr >> 2] = x;
|
|
284
|
-
return this;
|
|
285
|
-
}
|
|
286
|
-
setU32(addr, x) {
|
|
287
|
-
this.u32[addr >> 2] = x;
|
|
288
|
-
return this;
|
|
289
|
-
}
|
|
290
|
-
setI64(addr, x) {
|
|
291
|
-
this.i64[addr >> 3] = x;
|
|
292
|
-
return this;
|
|
293
|
-
}
|
|
294
|
-
setU64(addr, x) {
|
|
295
|
-
this.u64[addr >> 3] = x;
|
|
296
|
-
return this;
|
|
297
|
-
}
|
|
298
|
-
setF32(addr, x) {
|
|
299
|
-
this.f32[addr >> 2] = x;
|
|
300
|
-
return this;
|
|
301
|
-
}
|
|
302
|
-
setF64(addr, x) {
|
|
303
|
-
this.f64[addr >> 3] = x;
|
|
304
|
-
return this;
|
|
305
|
-
}
|
|
306
|
-
getI8Array(addr, len) {
|
|
307
|
-
return this.i8.subarray(addr, addr + len);
|
|
308
|
-
}
|
|
309
|
-
getU8Array(addr, len) {
|
|
310
|
-
return this.u8.subarray(addr, addr + len);
|
|
311
|
-
}
|
|
312
|
-
getI16Array(addr, len) {
|
|
313
|
-
addr >>= 1;
|
|
314
|
-
return this.i16.subarray(addr, addr + len);
|
|
315
|
-
}
|
|
316
|
-
getU16Array(addr, len) {
|
|
317
|
-
addr >>= 1;
|
|
318
|
-
return this.u16.subarray(addr, addr + len);
|
|
319
|
-
}
|
|
320
|
-
getI32Array(addr, len) {
|
|
321
|
-
addr >>= 2;
|
|
322
|
-
return this.i32.subarray(addr, addr + len);
|
|
323
|
-
}
|
|
324
|
-
getU32Array(addr, len) {
|
|
325
|
-
addr >>= 2;
|
|
326
|
-
return this.u32.subarray(addr, addr + len);
|
|
327
|
-
}
|
|
328
|
-
getI64Array(addr, len) {
|
|
329
|
-
addr >>= 3;
|
|
330
|
-
return this.i64.subarray(addr, addr + len);
|
|
331
|
-
}
|
|
332
|
-
getU64Array(addr, len) {
|
|
333
|
-
addr >>= 3;
|
|
334
|
-
return this.u64.subarray(addr, addr + len);
|
|
335
|
-
}
|
|
336
|
-
getF32Array(addr, len) {
|
|
337
|
-
addr >>= 2;
|
|
338
|
-
return this.f32.subarray(addr, addr + len);
|
|
339
|
-
}
|
|
340
|
-
getF64Array(addr, len) {
|
|
341
|
-
addr >>= 3;
|
|
342
|
-
return this.f64.subarray(addr, addr + len);
|
|
343
|
-
}
|
|
344
|
-
setI8Array(addr, buf) {
|
|
345
|
-
this.i8.set(buf, addr);
|
|
346
|
-
return this;
|
|
347
|
-
}
|
|
348
|
-
setU8Array(addr, buf) {
|
|
349
|
-
this.u8.set(buf, addr);
|
|
350
|
-
return this;
|
|
351
|
-
}
|
|
352
|
-
setI16Array(addr, buf) {
|
|
353
|
-
this.i16.set(buf, addr >> 1);
|
|
354
|
-
return this;
|
|
355
|
-
}
|
|
356
|
-
setU16Array(addr, buf) {
|
|
357
|
-
this.u16.set(buf, addr >> 1);
|
|
358
|
-
return this;
|
|
359
|
-
}
|
|
360
|
-
setI32Array(addr, buf) {
|
|
361
|
-
this.i32.set(buf, addr >> 2);
|
|
362
|
-
return this;
|
|
363
|
-
}
|
|
364
|
-
setU32Array(addr, buf) {
|
|
365
|
-
this.u32.set(buf, addr >> 2);
|
|
366
|
-
return this;
|
|
367
|
-
}
|
|
368
|
-
setI64Array(addr, buf) {
|
|
369
|
-
this.i64.set(buf, addr >> 3);
|
|
370
|
-
return this;
|
|
371
|
-
}
|
|
372
|
-
setU64Array(addr, buf) {
|
|
373
|
-
this.u64.set(buf, addr >> 3);
|
|
374
|
-
return this;
|
|
375
|
-
}
|
|
376
|
-
setF32Array(addr, buf) {
|
|
377
|
-
this.f32.set(buf, addr >> 2);
|
|
378
|
-
return this;
|
|
379
|
-
}
|
|
380
|
-
setF64Array(addr, buf) {
|
|
381
|
-
this.f64.set(buf, addr >> 3);
|
|
382
|
-
return this;
|
|
383
|
-
}
|
|
384
|
-
getString(addr, len = 0) {
|
|
385
|
-
this.ensureMemory();
|
|
386
|
-
return this.utf8Decoder.decode(this.u8.subarray(addr, len > 0 ? addr + len : this.u8.indexOf(0, addr)));
|
|
387
|
-
}
|
|
388
|
-
setString(str, addr, maxBytes, terminate = true) {
|
|
389
|
-
this.ensureMemory();
|
|
390
|
-
maxBytes = Math.min(maxBytes, this.u8.length - addr);
|
|
391
|
-
const len = this.utf8Encoder.encodeInto(str, this.u8.subarray(addr, addr + maxBytes)).written;
|
|
392
|
-
assert(len != null && len < maxBytes + (terminate ? 0 : 1), `error writing string to 0x${U32(addr)} (max. ${maxBytes} bytes, got at least ${str.length})`);
|
|
393
|
-
if (terminate) {
|
|
394
|
-
this.u8[addr + len] = 0;
|
|
46
|
+
for (let line of hexdumpLines(this.u8, addr, len)) {
|
|
47
|
+
this.logger.debug(line);
|
|
395
48
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
49
|
+
},
|
|
50
|
+
_printI8Array: logA(this.getI8Array.bind(this)),
|
|
51
|
+
_printU8Array: logA(this.getU8Array.bind(this)),
|
|
52
|
+
_printI16Array: logA(this.getI16Array.bind(this)),
|
|
53
|
+
_printU16Array: logA(this.getU16Array.bind(this)),
|
|
54
|
+
_printI32Array: logA(this.getI32Array.bind(this)),
|
|
55
|
+
_printU32Array: logA(this.getU32Array.bind(this)),
|
|
56
|
+
_printI64Array: logA(this.getI64Array.bind(this)),
|
|
57
|
+
_printU64Array: logA(this.getU64Array.bind(this)),
|
|
58
|
+
_printF32Array: logA(this.getF32Array.bind(this)),
|
|
59
|
+
_printF64Array: logA(this.getF64Array.bind(this)),
|
|
60
|
+
printStrZ: (addr) => this.logger.debug(() => this.getString(addr, 0)),
|
|
61
|
+
_printStr: (addr, len) => this.logger.debug(() => this.getString(addr, len)),
|
|
62
|
+
debug: () => {
|
|
63
|
+
debugger;
|
|
64
|
+
},
|
|
65
|
+
_panic: (addr, len) => {
|
|
66
|
+
const msg = this.getString(addr, len);
|
|
67
|
+
if (!this.notify({ id: EVENT_PANIC, value: msg })) {
|
|
68
|
+
throw new Panic(msg);
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
timer: () => performance.now(),
|
|
72
|
+
epoch: () => BigInt(Date.now())
|
|
73
|
+
};
|
|
74
|
+
this.modules = modules.reduce((acc, x) => {
|
|
75
|
+
assert(
|
|
76
|
+
acc[x.id] === void 0 && x.id !== this.id,
|
|
77
|
+
`duplicate API module ID: ${x.id}`
|
|
78
|
+
);
|
|
79
|
+
acc[x.id] = x;
|
|
80
|
+
return acc;
|
|
81
|
+
}, {});
|
|
82
|
+
}
|
|
83
|
+
id = "wasmapi";
|
|
84
|
+
i8;
|
|
85
|
+
u8;
|
|
86
|
+
i16;
|
|
87
|
+
u16;
|
|
88
|
+
i32;
|
|
89
|
+
u32;
|
|
90
|
+
i64;
|
|
91
|
+
u64;
|
|
92
|
+
f32;
|
|
93
|
+
f64;
|
|
94
|
+
utf8Decoder = new TextDecoder();
|
|
95
|
+
utf8Encoder = new TextEncoder();
|
|
96
|
+
imports;
|
|
97
|
+
exports;
|
|
98
|
+
api;
|
|
99
|
+
modules;
|
|
100
|
+
/**
|
|
101
|
+
* Instantiates WASM module from given `src` (and optional provided extra
|
|
102
|
+
* imports), then automatically calls {@link WasmBridge.init} with the
|
|
103
|
+
* modules exports.
|
|
104
|
+
*
|
|
105
|
+
* @remarks
|
|
106
|
+
* If the given `src` is a `Response` or `Promise<Response>`, the module
|
|
107
|
+
* will be instantiated via `WebAssembly.instantiateStreaming()`, otherwise
|
|
108
|
+
* the non-streaming version will be used.
|
|
109
|
+
*
|
|
110
|
+
* @param src
|
|
111
|
+
* @param imports
|
|
112
|
+
*/
|
|
113
|
+
async instantiate(src, imports) {
|
|
114
|
+
const $src = await src;
|
|
115
|
+
const $imports = { ...this.getImports(), ...imports };
|
|
116
|
+
const wasm = await ($src instanceof Response ? WebAssembly.instantiateStreaming($src, $imports) : WebAssembly.instantiate($src, $imports));
|
|
117
|
+
return this.init(wasm.instance.exports);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Receives the WASM module's combined exports, stores them for future
|
|
121
|
+
* reference and then initializes all declared bridge child API modules in
|
|
122
|
+
* their stated dependency order. Returns false if any of the module
|
|
123
|
+
* initializations failed.
|
|
124
|
+
*
|
|
125
|
+
* @remarks
|
|
126
|
+
* Emits the {@link EVENT_MEMORY_CHANGED} event just before returning (and
|
|
127
|
+
* AFTER all child API modules have been initialized).
|
|
128
|
+
*
|
|
129
|
+
* @param exports
|
|
130
|
+
*/
|
|
131
|
+
async init(exports) {
|
|
132
|
+
this.exports = exports;
|
|
133
|
+
this.ensureMemory(false);
|
|
134
|
+
for (let id of topoSort(
|
|
135
|
+
this.modules,
|
|
136
|
+
(module) => module.dependencies
|
|
137
|
+
)) {
|
|
138
|
+
assert(!!this.modules[id], `missing API module: ${id}`);
|
|
139
|
+
this.logger.debug(`initializing API module: ${id}`);
|
|
140
|
+
const status = await this.modules[id].init(this);
|
|
141
|
+
if (!status)
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
this.notify({ id: EVENT_MEMORY_CHANGED, value: this.exports.memory });
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Called automatically during initialization and from other memory
|
|
149
|
+
* accessors. Initializes and/or updates the various typed WASM memory views
|
|
150
|
+
* (e.g. after growing the WASM memory and the previous buffer becoming
|
|
151
|
+
* detached). Unless `notify` is false, the {@link EVENT_MEMORY_CHANGED}
|
|
152
|
+
* event will be emitted if the memory views had to be updated.
|
|
153
|
+
*
|
|
154
|
+
* @param notify
|
|
155
|
+
*/
|
|
156
|
+
ensureMemory(notify = true) {
|
|
157
|
+
const buf = this.exports.memory.buffer;
|
|
158
|
+
if (this.u8 && this.u8.buffer === buf)
|
|
159
|
+
return;
|
|
160
|
+
this.i8 = new Int8Array(buf);
|
|
161
|
+
this.u8 = new Uint8Array(buf);
|
|
162
|
+
this.i16 = new Int16Array(buf);
|
|
163
|
+
this.u16 = new Uint16Array(buf);
|
|
164
|
+
this.i32 = new Int32Array(buf);
|
|
165
|
+
this.u32 = new Uint32Array(buf);
|
|
166
|
+
this.i64 = new BigInt64Array(buf);
|
|
167
|
+
this.u64 = new BigUint64Array(buf);
|
|
168
|
+
this.f32 = new Float32Array(buf);
|
|
169
|
+
this.f64 = new Float64Array(buf);
|
|
170
|
+
notify && this.notify({
|
|
171
|
+
id: EVENT_MEMORY_CHANGED,
|
|
172
|
+
value: this.exports.memory
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Required use for WASM module instantiation to provide JS imports to the
|
|
177
|
+
* module. Returns an object of all WASM imports declared by the bridge core
|
|
178
|
+
* API and any provided bridge API modules.
|
|
179
|
+
*
|
|
180
|
+
* @remarks
|
|
181
|
+
* Each API module's imports will be in their own WASM import object/table,
|
|
182
|
+
* named using the same key which is defined by the JS side of the module
|
|
183
|
+
* via {@link IWasmAPI.id}. The bridge's core API is named `wasmapi` and is
|
|
184
|
+
* reserved.
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* The following creates a bridge with a fictional `custom` API module:
|
|
188
|
+
*
|
|
189
|
+
* ```ts
|
|
190
|
+
* const bridge = new WasmBridge([new CustomAPI()]);
|
|
191
|
+
*
|
|
192
|
+
* // get combined imports object
|
|
193
|
+
* bridge.getImports();
|
|
194
|
+
* {
|
|
195
|
+
* // imports defined by the core API of the bridge itself
|
|
196
|
+
* wasmapi: { ... },
|
|
197
|
+
* // imports defined by the CustomAPI module
|
|
198
|
+
* custom: { ... }
|
|
199
|
+
* }
|
|
200
|
+
* ```
|
|
201
|
+
*
|
|
202
|
+
* Any related API bindings on the WASM (Zig) side then also need to refer
|
|
203
|
+
* to these custom import sections (also see `/zig/core.zig`):
|
|
204
|
+
*
|
|
205
|
+
* ```zig
|
|
206
|
+
* pub export "custom" fn foo(x: u32) void;
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
getImports() {
|
|
210
|
+
if (!this.imports) {
|
|
211
|
+
this.imports = { [this.id]: this.api };
|
|
212
|
+
for (let id in this.modules) {
|
|
213
|
+
this.imports[id] = this.modules[id].getImports();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return this.imports;
|
|
217
|
+
}
|
|
218
|
+
growMemory(numPages) {
|
|
219
|
+
this.exports.memory.grow(numPages);
|
|
220
|
+
this.ensureMemory();
|
|
221
|
+
}
|
|
222
|
+
allocate(numBytes, clear = false) {
|
|
223
|
+
const addr = this.exports._wasm_allocate(numBytes);
|
|
224
|
+
if (!addr)
|
|
225
|
+
throw new OutOfMemoryError(`unable to allocate: ${numBytes}`);
|
|
226
|
+
this.logger.fine(
|
|
227
|
+
() => `allocated ${numBytes} bytes @ 0x${U32(addr)} .. 0x${U32(
|
|
228
|
+
addr + numBytes - 1
|
|
229
|
+
)}`
|
|
230
|
+
);
|
|
231
|
+
this.ensureMemory();
|
|
232
|
+
clear && this.u8.fill(0, addr, addr + numBytes);
|
|
233
|
+
return [addr, numBytes];
|
|
234
|
+
}
|
|
235
|
+
free([addr, numBytes]) {
|
|
236
|
+
this.logger.fine(
|
|
237
|
+
() => `freeing memory @ 0x${U32(addr)} .. 0x${U32(
|
|
238
|
+
addr + numBytes - 1
|
|
239
|
+
)}`
|
|
240
|
+
);
|
|
241
|
+
this.exports._wasm_free(addr, numBytes);
|
|
242
|
+
}
|
|
243
|
+
getI8(addr) {
|
|
244
|
+
return this.i8[addr];
|
|
245
|
+
}
|
|
246
|
+
getU8(addr) {
|
|
247
|
+
return this.u8[addr];
|
|
248
|
+
}
|
|
249
|
+
getI16(addr) {
|
|
250
|
+
return this.i16[addr >> 1];
|
|
251
|
+
}
|
|
252
|
+
getU16(addr) {
|
|
253
|
+
return this.u16[addr >> 1];
|
|
254
|
+
}
|
|
255
|
+
getI32(addr) {
|
|
256
|
+
return this.i32[addr >> 2];
|
|
257
|
+
}
|
|
258
|
+
getU32(addr) {
|
|
259
|
+
return this.u32[addr >> 2];
|
|
260
|
+
}
|
|
261
|
+
getI64(addr) {
|
|
262
|
+
return this.i64[addr >> 3];
|
|
263
|
+
}
|
|
264
|
+
getU64(addr) {
|
|
265
|
+
return this.u64[addr >> 3];
|
|
266
|
+
}
|
|
267
|
+
getF32(addr) {
|
|
268
|
+
return this.f32[addr >> 2];
|
|
269
|
+
}
|
|
270
|
+
getF64(addr) {
|
|
271
|
+
return this.f64[addr >> 3];
|
|
272
|
+
}
|
|
273
|
+
setI8(addr, x) {
|
|
274
|
+
this.i8[addr] = x;
|
|
275
|
+
return this;
|
|
276
|
+
}
|
|
277
|
+
setU8(addr, x) {
|
|
278
|
+
this.u8[addr] = x;
|
|
279
|
+
return this;
|
|
280
|
+
}
|
|
281
|
+
setI16(addr, x) {
|
|
282
|
+
this.i16[addr >> 1] = x;
|
|
283
|
+
return this;
|
|
284
|
+
}
|
|
285
|
+
setU16(addr, x) {
|
|
286
|
+
this.u16[addr >> 1] = x;
|
|
287
|
+
return this;
|
|
288
|
+
}
|
|
289
|
+
setI32(addr, x) {
|
|
290
|
+
this.i32[addr >> 2] = x;
|
|
291
|
+
return this;
|
|
292
|
+
}
|
|
293
|
+
setU32(addr, x) {
|
|
294
|
+
this.u32[addr >> 2] = x;
|
|
295
|
+
return this;
|
|
296
|
+
}
|
|
297
|
+
setI64(addr, x) {
|
|
298
|
+
this.i64[addr >> 3] = x;
|
|
299
|
+
return this;
|
|
300
|
+
}
|
|
301
|
+
setU64(addr, x) {
|
|
302
|
+
this.u64[addr >> 3] = x;
|
|
303
|
+
return this;
|
|
304
|
+
}
|
|
305
|
+
setF32(addr, x) {
|
|
306
|
+
this.f32[addr >> 2] = x;
|
|
307
|
+
return this;
|
|
308
|
+
}
|
|
309
|
+
setF64(addr, x) {
|
|
310
|
+
this.f64[addr >> 3] = x;
|
|
311
|
+
return this;
|
|
312
|
+
}
|
|
313
|
+
getI8Array(addr, len) {
|
|
314
|
+
return this.i8.subarray(addr, addr + len);
|
|
315
|
+
}
|
|
316
|
+
getU8Array(addr, len) {
|
|
317
|
+
return this.u8.subarray(addr, addr + len);
|
|
318
|
+
}
|
|
319
|
+
getI16Array(addr, len) {
|
|
320
|
+
addr >>= 1;
|
|
321
|
+
return this.i16.subarray(addr, addr + len);
|
|
322
|
+
}
|
|
323
|
+
getU16Array(addr, len) {
|
|
324
|
+
addr >>= 1;
|
|
325
|
+
return this.u16.subarray(addr, addr + len);
|
|
326
|
+
}
|
|
327
|
+
getI32Array(addr, len) {
|
|
328
|
+
addr >>= 2;
|
|
329
|
+
return this.i32.subarray(addr, addr + len);
|
|
330
|
+
}
|
|
331
|
+
getU32Array(addr, len) {
|
|
332
|
+
addr >>= 2;
|
|
333
|
+
return this.u32.subarray(addr, addr + len);
|
|
334
|
+
}
|
|
335
|
+
getI64Array(addr, len) {
|
|
336
|
+
addr >>= 3;
|
|
337
|
+
return this.i64.subarray(addr, addr + len);
|
|
338
|
+
}
|
|
339
|
+
getU64Array(addr, len) {
|
|
340
|
+
addr >>= 3;
|
|
341
|
+
return this.u64.subarray(addr, addr + len);
|
|
342
|
+
}
|
|
343
|
+
getF32Array(addr, len) {
|
|
344
|
+
addr >>= 2;
|
|
345
|
+
return this.f32.subarray(addr, addr + len);
|
|
346
|
+
}
|
|
347
|
+
getF64Array(addr, len) {
|
|
348
|
+
addr >>= 3;
|
|
349
|
+
return this.f64.subarray(addr, addr + len);
|
|
350
|
+
}
|
|
351
|
+
setI8Array(addr, buf) {
|
|
352
|
+
this.i8.set(buf, addr);
|
|
353
|
+
return this;
|
|
354
|
+
}
|
|
355
|
+
setU8Array(addr, buf) {
|
|
356
|
+
this.u8.set(buf, addr);
|
|
357
|
+
return this;
|
|
358
|
+
}
|
|
359
|
+
setI16Array(addr, buf) {
|
|
360
|
+
this.i16.set(buf, addr >> 1);
|
|
361
|
+
return this;
|
|
362
|
+
}
|
|
363
|
+
setU16Array(addr, buf) {
|
|
364
|
+
this.u16.set(buf, addr >> 1);
|
|
365
|
+
return this;
|
|
366
|
+
}
|
|
367
|
+
setI32Array(addr, buf) {
|
|
368
|
+
this.i32.set(buf, addr >> 2);
|
|
369
|
+
return this;
|
|
370
|
+
}
|
|
371
|
+
setU32Array(addr, buf) {
|
|
372
|
+
this.u32.set(buf, addr >> 2);
|
|
373
|
+
return this;
|
|
374
|
+
}
|
|
375
|
+
setI64Array(addr, buf) {
|
|
376
|
+
this.i64.set(buf, addr >> 3);
|
|
377
|
+
return this;
|
|
378
|
+
}
|
|
379
|
+
setU64Array(addr, buf) {
|
|
380
|
+
this.u64.set(buf, addr >> 3);
|
|
381
|
+
return this;
|
|
382
|
+
}
|
|
383
|
+
setF32Array(addr, buf) {
|
|
384
|
+
this.f32.set(buf, addr >> 2);
|
|
385
|
+
return this;
|
|
386
|
+
}
|
|
387
|
+
setF64Array(addr, buf) {
|
|
388
|
+
this.f64.set(buf, addr >> 3);
|
|
389
|
+
return this;
|
|
390
|
+
}
|
|
391
|
+
getString(addr, len = 0) {
|
|
392
|
+
this.ensureMemory();
|
|
393
|
+
return this.utf8Decoder.decode(
|
|
394
|
+
this.u8.subarray(
|
|
395
|
+
addr,
|
|
396
|
+
len > 0 ? addr + len : this.u8.indexOf(0, addr)
|
|
397
|
+
)
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
setString(str, addr, maxBytes, terminate = true) {
|
|
401
|
+
this.ensureMemory();
|
|
402
|
+
maxBytes = Math.min(maxBytes, this.u8.length - addr);
|
|
403
|
+
const len = this.utf8Encoder.encodeInto(
|
|
404
|
+
str,
|
|
405
|
+
this.u8.subarray(addr, addr + maxBytes)
|
|
406
|
+
).written;
|
|
407
|
+
assert(
|
|
408
|
+
len != null && len < maxBytes + (terminate ? 0 : 1),
|
|
409
|
+
`error writing string to 0x${U32(
|
|
410
|
+
addr
|
|
411
|
+
)} (max. ${maxBytes} bytes, got at least ${str.length})`
|
|
412
|
+
);
|
|
413
|
+
if (terminate) {
|
|
414
|
+
this.u8[addr + len] = 0;
|
|
415
|
+
}
|
|
416
|
+
return len;
|
|
417
|
+
}
|
|
418
|
+
getElementById(addr, len = 0) {
|
|
419
|
+
const id = this.getString(addr, len);
|
|
420
|
+
const el = document.getElementById(id);
|
|
421
|
+
assert(!!el, `missing DOM element #${id}`);
|
|
422
|
+
return el;
|
|
423
|
+
}
|
|
424
|
+
/** {@inheritDoc @thi.ng/api#INotify.addListener} */
|
|
425
|
+
// @ts-ignore: mixin
|
|
426
|
+
// prettier-ignore
|
|
427
|
+
addListener(id, fn, scope) {
|
|
428
|
+
}
|
|
429
|
+
/** {@inheritDoc @thi.ng/api#INotify.removeListener} */
|
|
430
|
+
// @ts-ignore: mixin
|
|
431
|
+
// prettier-ignore
|
|
432
|
+
removeListener(id, fn, scope) {
|
|
433
|
+
}
|
|
434
|
+
/** {@inheritDoc @thi.ng/api#INotify.notify} */
|
|
435
|
+
// @ts-ignore: mixin
|
|
436
|
+
notify(event) {
|
|
437
|
+
}
|
|
415
438
|
};
|
|
416
|
-
WasmBridge =
|
|
417
|
-
|
|
439
|
+
WasmBridge = __decorateClass([
|
|
440
|
+
INotifyMixin
|
|
418
441
|
], WasmBridge);
|
|
419
|
-
export {
|
|
442
|
+
export {
|
|
443
|
+
OutOfMemoryError,
|
|
444
|
+
Panic,
|
|
445
|
+
WasmBridge
|
|
446
|
+
};
|