@thi.ng/wasm-api 2.4.32 → 2.5.0
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/README.md +31 -1
- package/api.d.ts +20 -0
- package/bridge.d.ts +9 -0
- package/bridge.js +62 -2
- package/package.json +11 -10
package/README.md
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
- [About](#about)
|
|
21
21
|
- [Polyglot bindings generator](#polyglot-bindings-generator)
|
|
22
22
|
- [Custom API modules](#custom-api-modules)
|
|
23
|
+
- [Async handling](#async-handling)
|
|
23
24
|
- [String handling](#string-handling)
|
|
24
25
|
- [Memory allocations](#memory-allocations)
|
|
25
26
|
- [API module auto-initialization](#api-module-auto-initialization)
|
|
@@ -123,6 +124,13 @@ export const CustomModule: WasmModuleSpec = {
|
|
|
123
124
|
// only happens at a later point via WasmBridge.instantiate() or
|
|
124
125
|
// WasmBridge.init() and each module's own init() method...
|
|
125
126
|
factory: () => new CustomAPI(),
|
|
127
|
+
opts: {
|
|
128
|
+
// List of exported symbols which should be auto-wrapped using
|
|
129
|
+
// `WebAssembly.promising`. The counterpart, async function WASM imports,
|
|
130
|
+
// are also auto-wrapped using `WebAssembly.Suspend`.
|
|
131
|
+
// Also see section "Async handling" in readme
|
|
132
|
+
asyncExports: [/* ... */]
|
|
133
|
+
}
|
|
126
134
|
};
|
|
127
135
|
|
|
128
136
|
// Optional declarations for JS-side functions which can be used from the WASM side.
|
|
@@ -172,6 +180,9 @@ export class CustomAPI implements IWasmAPI<CustomWasmExports> {
|
|
|
172
180
|
*
|
|
173
181
|
* Each module's imports will be grouped by its declared module ID, which
|
|
174
182
|
* also needs to be used to declare extern functions on the WASM side.
|
|
183
|
+
*
|
|
184
|
+
* Async functions given as WASM imports are will be auto-wrapped using
|
|
185
|
+
* `WebAssembly.Suspend`.
|
|
175
186
|
*/
|
|
176
187
|
getImports(): CustomImports {
|
|
177
188
|
return {
|
|
@@ -230,6 +241,25 @@ export fn test_randomVec4() void {
|
|
|
230
241
|
}
|
|
231
242
|
```
|
|
232
243
|
|
|
244
|
+
## Async handling
|
|
245
|
+
|
|
246
|
+
If the targeted WASM runtime supports
|
|
247
|
+
[`WebAssembly.promising`](https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/JavaScript_interface/promising_static)
|
|
248
|
+
and
|
|
249
|
+
[`WebAssembly.Suspending`](https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/JavaScript_interface/Suspending),
|
|
250
|
+
then async functions will be automatically wrapped as follows:
|
|
251
|
+
|
|
252
|
+
- Async functions declared in a module's WASM imports will be auto-wrapped using
|
|
253
|
+
`WebAssembly.Suspending`
|
|
254
|
+
- WASM exports declared via
|
|
255
|
+
[`WasmModuleOpts.asyncExports`](https://docs.thi.ng/umbrella/wasm-api/interfaces/WasmModuleOpts.html#asyncexports)
|
|
256
|
+
will be auto-wrapped using `WebAssembly.promising`
|
|
257
|
+
|
|
258
|
+
(Also see above code examples...)
|
|
259
|
+
|
|
260
|
+
If either of these are present, but the WASM runtime doesn't support these
|
|
261
|
+
features, an error will be thrown during instantiation/init.
|
|
262
|
+
|
|
233
263
|
## String handling
|
|
234
264
|
|
|
235
265
|
Most low-level languages deal with strings very differently and alas there's no
|
|
@@ -469,7 +499,7 @@ Browser ESM import:
|
|
|
469
499
|
|
|
470
500
|
[JSDelivr documentation](https://www.jsdelivr.com/)
|
|
471
501
|
|
|
472
|
-
Package sizes (brotli'd, pre-treeshake): ESM: 3.
|
|
502
|
+
Package sizes (brotli'd, pre-treeshake): ESM: 3.37 KB
|
|
473
503
|
|
|
474
504
|
## Dependencies
|
|
475
505
|
|
package/api.d.ts
CHANGED
|
@@ -96,6 +96,26 @@ export interface WasmModuleSpec<T extends WasmExports = WasmExports> {
|
|
|
96
96
|
* modules own {@link IWasmAPI.init} method.
|
|
97
97
|
*/
|
|
98
98
|
factory: Fn<WasmBridge<T>, IWasmAPI<T>>;
|
|
99
|
+
/**
|
|
100
|
+
* Module options to configure advanced WASM features
|
|
101
|
+
*/
|
|
102
|
+
opts?: WasmModuleOpts;
|
|
103
|
+
}
|
|
104
|
+
export interface WasmModuleOpts {
|
|
105
|
+
/**
|
|
106
|
+
* List of export symbol names which should be automatically wrapped
|
|
107
|
+
* using `WebAssembly.promising()`.
|
|
108
|
+
*
|
|
109
|
+
* @remarks
|
|
110
|
+
* Note: Async functions provided as module imports are automatically
|
|
111
|
+
* wrapped via `WebAssembly.Suspending()`, however for exports no
|
|
112
|
+
* auto-detection is possible and hence needs to be provided here.
|
|
113
|
+
*
|
|
114
|
+
* Reference:
|
|
115
|
+
* - https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/JavaScript_interface/promising_static
|
|
116
|
+
* - https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/JavaScript_interface/Suspending
|
|
117
|
+
*/
|
|
118
|
+
asyncExports?: string[];
|
|
99
119
|
}
|
|
100
120
|
/**
|
|
101
121
|
* Base interface of exports declared by the WASM module. At the very least, a
|
package/bridge.d.ts
CHANGED
|
@@ -59,6 +59,7 @@ export declare class WasmBridge<T extends WasmExports = WasmExports> implements
|
|
|
59
59
|
imports: WebAssembly.Imports;
|
|
60
60
|
exports: T;
|
|
61
61
|
api: CoreAPI;
|
|
62
|
+
moduleSpecs: IObjectOf<WasmModuleSpec<T>>;
|
|
62
63
|
modules: IObjectOf<IWasmAPI<T>>;
|
|
63
64
|
order: string[];
|
|
64
65
|
constructor(modules?: WasmModuleSpec<T>[], logger?: ILogger);
|
|
@@ -85,10 +86,17 @@ export declare class WasmBridge<T extends WasmExports = WasmExports> implements
|
|
|
85
86
|
* will be instantiated via `WebAssembly.instantiateStreaming()`, otherwise
|
|
86
87
|
* the non-streaming version will be used.
|
|
87
88
|
*
|
|
89
|
+
* Any async functions defined in given `imports` will be automatically
|
|
90
|
+
* wrapped using `WebAssembly.Suspend`. An error will be thrown if async
|
|
91
|
+
* function imports are provided, but if the suspend feature is not
|
|
92
|
+
* supported by the WASM runtime. See
|
|
93
|
+
* {@link WasmModuleSpec.opts} for more details and counterparts.
|
|
94
|
+
*
|
|
88
95
|
* @param src
|
|
89
96
|
* @param imports
|
|
90
97
|
*/
|
|
91
98
|
instantiate(src: Response | BufferSource | PromiseLike<Response | BufferSource>, imports?: WebAssembly.Imports): Promise<boolean>;
|
|
99
|
+
protected _prepareImports(imports: WebAssembly.Imports): WebAssembly.Imports;
|
|
92
100
|
/**
|
|
93
101
|
* Receives the WASM module's combined exports, stores them for future
|
|
94
102
|
* reference and then initializes all declared bridge child API modules in
|
|
@@ -102,6 +110,7 @@ export declare class WasmBridge<T extends WasmExports = WasmExports> implements
|
|
|
102
110
|
* @param exports
|
|
103
111
|
*/
|
|
104
112
|
init(exports: T): Promise<boolean>;
|
|
113
|
+
protected _prepareExports(exports: T): T;
|
|
105
114
|
/**
|
|
106
115
|
* Called automatically during initialization and from other memory
|
|
107
116
|
* accessors. Initializes and/or updates the various typed WASM memory views
|
package/bridge.js
CHANGED
|
@@ -10,8 +10,10 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
10
10
|
};
|
|
11
11
|
import { INotifyMixin } from "@thi.ng/api/mixins/inotify";
|
|
12
12
|
import { topoSort } from "@thi.ng/arrays/topo-sort";
|
|
13
|
+
import { isAsyncFunction } from "@thi.ng/checks/is-async-function";
|
|
13
14
|
import { assert } from "@thi.ng/errors/assert";
|
|
14
15
|
import { defError } from "@thi.ng/errors/deferror";
|
|
16
|
+
import { unsupportedFeature } from "@thi.ng/errors/unsupported";
|
|
15
17
|
import { U16, U32, U64BIG, U8, hexdumpLines } from "@thi.ng/hex";
|
|
16
18
|
import { ConsoleLogger } from "@thi.ng/logger/console";
|
|
17
19
|
import {
|
|
@@ -89,6 +91,7 @@ let WasmBridge = class {
|
|
|
89
91
|
imports;
|
|
90
92
|
exports;
|
|
91
93
|
api;
|
|
94
|
+
moduleSpecs;
|
|
92
95
|
modules;
|
|
93
96
|
order;
|
|
94
97
|
/**
|
|
@@ -126,6 +129,13 @@ let WasmBridge = class {
|
|
|
126
129
|
{}
|
|
127
130
|
);
|
|
128
131
|
this.order = topoSort(graph, (mod) => mod.deps?.map((x) => x.id));
|
|
132
|
+
this.moduleSpecs = this.order.reduce(
|
|
133
|
+
(acc, id) => {
|
|
134
|
+
acc[id] = graph[id];
|
|
135
|
+
return acc;
|
|
136
|
+
},
|
|
137
|
+
{}
|
|
138
|
+
);
|
|
129
139
|
this.modules = this.order.reduce(
|
|
130
140
|
(acc, id) => {
|
|
131
141
|
acc[id] = graph[id].factory(this);
|
|
@@ -144,15 +154,41 @@ let WasmBridge = class {
|
|
|
144
154
|
* will be instantiated via `WebAssembly.instantiateStreaming()`, otherwise
|
|
145
155
|
* the non-streaming version will be used.
|
|
146
156
|
*
|
|
157
|
+
* Any async functions defined in given `imports` will be automatically
|
|
158
|
+
* wrapped using `WebAssembly.Suspend`. An error will be thrown if async
|
|
159
|
+
* function imports are provided, but if the suspend feature is not
|
|
160
|
+
* supported by the WASM runtime. See
|
|
161
|
+
* {@link WasmModuleSpec.opts} for more details and counterparts.
|
|
162
|
+
*
|
|
147
163
|
* @param src
|
|
148
164
|
* @param imports
|
|
149
165
|
*/
|
|
150
166
|
async instantiate(src, imports) {
|
|
151
167
|
const $src = await src;
|
|
152
|
-
const $imports =
|
|
168
|
+
const $imports = this._prepareImports({
|
|
169
|
+
...this.getImports(),
|
|
170
|
+
...imports
|
|
171
|
+
});
|
|
153
172
|
const wasm = await ($src instanceof Response ? WebAssembly.instantiateStreaming($src, $imports) : WebAssembly.instantiate($src, $imports));
|
|
154
173
|
return this.init(wasm.instance.exports);
|
|
155
174
|
}
|
|
175
|
+
_prepareImports(imports) {
|
|
176
|
+
for (let modID in imports) {
|
|
177
|
+
const moduleImports = imports[modID];
|
|
178
|
+
for (let id in moduleImports) {
|
|
179
|
+
if (isAsyncFunction(moduleImports[id])) {
|
|
180
|
+
if (!("Suspending" in WebAssembly))
|
|
181
|
+
unsupportedFeature(
|
|
182
|
+
"async function in WASM module imports"
|
|
183
|
+
);
|
|
184
|
+
moduleImports[id] = new WebAssembly["Suspending"](
|
|
185
|
+
moduleImports[id]
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return imports;
|
|
191
|
+
}
|
|
156
192
|
/**
|
|
157
193
|
* Receives the WASM module's combined exports, stores them for future
|
|
158
194
|
* reference and then initializes all declared bridge child API modules in
|
|
@@ -166,7 +202,7 @@ let WasmBridge = class {
|
|
|
166
202
|
* @param exports
|
|
167
203
|
*/
|
|
168
204
|
async init(exports) {
|
|
169
|
-
this.exports = exports;
|
|
205
|
+
this.exports = this._prepareExports(exports);
|
|
170
206
|
this.ensureMemory(false);
|
|
171
207
|
for (const id of this.order) {
|
|
172
208
|
this.logger.debug(`initializing API module: ${id}`);
|
|
@@ -176,6 +212,30 @@ let WasmBridge = class {
|
|
|
176
212
|
this.notify({ id: EVENT_MEMORY_CHANGED, value: this.exports.memory });
|
|
177
213
|
return true;
|
|
178
214
|
}
|
|
215
|
+
_prepareExports(exports) {
|
|
216
|
+
for (let modID in this.modules) {
|
|
217
|
+
const opts = this.moduleSpecs[modID].opts;
|
|
218
|
+
if (!opts?.asyncExports) continue;
|
|
219
|
+
for (let id of opts.asyncExports) {
|
|
220
|
+
const item = exports[id];
|
|
221
|
+
if (item != null) {
|
|
222
|
+
if (!("promising" in WebAssembly))
|
|
223
|
+
unsupportedFeature(
|
|
224
|
+
"async function in WASM module exports"
|
|
225
|
+
);
|
|
226
|
+
exports = {
|
|
227
|
+
...exports,
|
|
228
|
+
[id]: WebAssembly.promising(item)
|
|
229
|
+
};
|
|
230
|
+
} else {
|
|
231
|
+
this.logger.warn(
|
|
232
|
+
`WASM export ${id} declared as async, but missing...`
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return exports;
|
|
238
|
+
}
|
|
179
239
|
/**
|
|
180
240
|
* Called automatically during initialization and from other memory
|
|
181
241
|
* accessors. Initializes and/or updates the various typed WASM memory views
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thi.ng/wasm-api",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "Generic, modular, extensible API bridge and infrastructure for hybrid JS & WebAssembly projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -43,22 +43,23 @@
|
|
|
43
43
|
"tool:tangle": "../../node_modules/.bin/tangle src/**/*.ts"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@thi.ng/api": "^8.12.
|
|
47
|
-
"@thi.ng/arrays": "^2.14.
|
|
48
|
-
"@thi.ng/checks": "^3.
|
|
49
|
-
"@thi.ng/errors": "^2.6.
|
|
50
|
-
"@thi.ng/hex": "^2.4.
|
|
51
|
-
"@thi.ng/idgen": "^2.2.
|
|
52
|
-
"@thi.ng/logger": "^3.3.
|
|
46
|
+
"@thi.ng/api": "^8.12.27",
|
|
47
|
+
"@thi.ng/arrays": "^2.14.25",
|
|
48
|
+
"@thi.ng/checks": "^3.11.0",
|
|
49
|
+
"@thi.ng/errors": "^2.6.16",
|
|
50
|
+
"@thi.ng/hex": "^2.4.19",
|
|
51
|
+
"@thi.ng/idgen": "^2.2.101",
|
|
52
|
+
"@thi.ng/logger": "^3.3.10"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"esbuild": "^0.28.
|
|
55
|
+
"esbuild": "^0.28.1",
|
|
56
56
|
"typedoc": "^0.28.19",
|
|
57
57
|
"typescript": "^6.0.3"
|
|
58
58
|
},
|
|
59
59
|
"keywords": [
|
|
60
60
|
"allocator",
|
|
61
61
|
"api",
|
|
62
|
+
"async",
|
|
62
63
|
"bigint",
|
|
63
64
|
"bindings",
|
|
64
65
|
"browser",
|
|
@@ -127,5 +128,5 @@
|
|
|
127
128
|
"tag": "wasm",
|
|
128
129
|
"year": 2022
|
|
129
130
|
},
|
|
130
|
-
"gitHead": "
|
|
131
|
+
"gitHead": "6a4e7c564ebc476770d213d98a2aa6257a41ac15"
|
|
131
132
|
}
|