@thi.ng/wasm-api 0.4.0 → 0.7.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/CHANGELOG.md +60 -1
- package/README.md +403 -16
- package/api.d.ts +213 -3
- package/api.js +9 -1
- package/bin/wasm-api +12 -0
- package/bridge.d.ts +78 -6
- package/bridge.js +96 -14
- package/cli.d.ts +5 -0
- package/cli.js +142 -0
- package/codegen/typescript.d.ts +26 -0
- package/codegen/typescript.js +153 -0
- package/codegen/utils.d.ts +29 -0
- package/codegen/utils.js +29 -0
- package/codegen/zig.d.ts +22 -0
- package/codegen/zig.js +75 -0
- package/codegen.d.ts +16 -0
- package/codegen.js +116 -0
- package/include/wasmapi.h +60 -0
- package/{zig/core.zig → include/wasmapi.zig} +76 -26
- package/index.d.ts +4 -0
- package/index.js +4 -0
- package/package.json +32 -7
- package/dev/custom.zig +0 -12
- package/dev/fieldinfo.zig +0 -135
- package/dev/hello.zig +0 -9
- package/dev/zig-cache/o/0fd683610fe16c12563bf410950c8193/builtin.zig +0 -39
- package/test/custom.zig +0 -12
package/api.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { Fn, Fn2 } from "@thi.ng/api";
|
|
1
|
+
import type { BigType, FloatType, Fn, Fn2 } from "@thi.ng/api";
|
|
2
2
|
import type { WasmBridge } from "./bridge.js";
|
|
3
|
+
export declare const PKG_NAME = "@thi.ng/wasm-api";
|
|
3
4
|
export declare type BigIntArray = bigint[] | BigInt64Array | BigUint64Array;
|
|
4
5
|
/**
|
|
5
6
|
* Common interface for WASM/JS child APIs which will be used in combination
|
|
@@ -27,7 +28,8 @@ export interface IWasmAPI<T extends WasmExports = WasmExports> {
|
|
|
27
28
|
}
|
|
28
29
|
/**
|
|
29
30
|
* Base interface of exports declared by the WASM module. At the very least, the
|
|
30
|
-
* module needs to export its memory
|
|
31
|
+
* module needs to export its memory and the functions defined in this
|
|
32
|
+
* interface.
|
|
31
33
|
*
|
|
32
34
|
* @remarks
|
|
33
35
|
* This interface is supposed to be extended with the concrete exports defined
|
|
@@ -39,9 +41,42 @@ export interface IWasmAPI<T extends WasmExports = WasmExports> {
|
|
|
39
41
|
export interface WasmExports {
|
|
40
42
|
/**
|
|
41
43
|
* The WASM module's linear memory buffer. The `WasmBridge` automatically
|
|
42
|
-
* creates various typed views of that memory.
|
|
44
|
+
* creates various typed views of that memory (i.e. u8, u16, u32, f32 etc.)
|
|
43
45
|
*/
|
|
44
46
|
memory: WebAssembly.Memory;
|
|
47
|
+
/**
|
|
48
|
+
* Implementation specific memory allocation function (likely heap-based).
|
|
49
|
+
* If successful returns address of new memory block, or zero if
|
|
50
|
+
* unsuccessful.
|
|
51
|
+
*
|
|
52
|
+
* @remarks
|
|
53
|
+
* In the supplied Zig bindings (see `/zig/core.zig`), by default this is
|
|
54
|
+
* using the `std.heap.GeneralPurposeAllocator` (which also automatically
|
|
55
|
+
* handles growing the WASM memory), however as mentioned the underlying
|
|
56
|
+
* mechanism is purposefully left to the actual WASM-side implementation. In
|
|
57
|
+
* a C program, this would likely use `malloc()` or similar...
|
|
58
|
+
*/
|
|
59
|
+
_wasm_allocate(numBytes: number): number;
|
|
60
|
+
/**
|
|
61
|
+
* Implementation specific function to free a previously allocated chunk of
|
|
62
|
+
* of WASM memory (allocated via {@link WasmExports._wasm_allocate}).
|
|
63
|
+
*
|
|
64
|
+
* @param addr
|
|
65
|
+
* @param numBytes
|
|
66
|
+
*/
|
|
67
|
+
_wasm_free(addr: number, numBytes: number): void;
|
|
68
|
+
}
|
|
69
|
+
export interface WasmMemViews {
|
|
70
|
+
i8: Int8Array;
|
|
71
|
+
u8: Uint8Array;
|
|
72
|
+
i16: Int16Array;
|
|
73
|
+
u16: Uint16Array;
|
|
74
|
+
i32: Int32Array;
|
|
75
|
+
u32: Uint32Array;
|
|
76
|
+
i64: BigInt64Array;
|
|
77
|
+
u64: BigUint64Array;
|
|
78
|
+
f32: Float32Array;
|
|
79
|
+
f64: Float64Array;
|
|
45
80
|
}
|
|
46
81
|
/**
|
|
47
82
|
* Core API of WASM imports defined by the {@link WasmBridge}. The same
|
|
@@ -76,4 +111,179 @@ export interface CoreAPI extends WebAssembly.ModuleImports {
|
|
|
76
111
|
_printStr0: (addr: number) => void;
|
|
77
112
|
_printStr: (addr: number, len: number) => void;
|
|
78
113
|
}
|
|
114
|
+
export interface WasmTypeBase {
|
|
115
|
+
/**
|
|
116
|
+
* Base address in linear WASM memory.
|
|
117
|
+
*/
|
|
118
|
+
readonly __base: number;
|
|
119
|
+
/**
|
|
120
|
+
* Obtain as byte buffer
|
|
121
|
+
*/
|
|
122
|
+
readonly __bytes: Uint8Array;
|
|
123
|
+
}
|
|
124
|
+
export interface WasmType<T> {
|
|
125
|
+
readonly align: number;
|
|
126
|
+
readonly size: number;
|
|
127
|
+
instance: Fn<number, T>;
|
|
128
|
+
}
|
|
129
|
+
export declare type WasmTypeConstructor<T> = Fn<WasmMemViews, WasmType<T>>;
|
|
130
|
+
export declare type WasmInt = "i8" | "i16" | "i32" | "i64";
|
|
131
|
+
export declare type WasmUint = "u8" | "u16" | "u32" | "u64";
|
|
132
|
+
export declare type WasmFloat = FloatType;
|
|
133
|
+
export declare type WasmPrim = WasmInt | WasmUint | WasmFloat;
|
|
134
|
+
export declare type WasmPrim32 = Exclude<WasmPrim, BigType>;
|
|
135
|
+
export declare type TypeColl = Record<string, TopLevelType>;
|
|
136
|
+
export interface TypeInfo {
|
|
137
|
+
/**
|
|
138
|
+
* Auto-computed size (in bytes)
|
|
139
|
+
*
|
|
140
|
+
* @internal
|
|
141
|
+
*/
|
|
142
|
+
__size?: number;
|
|
143
|
+
/**
|
|
144
|
+
* Auto-computed offset (in bytes) in parent struct
|
|
145
|
+
*
|
|
146
|
+
* @internal
|
|
147
|
+
*/
|
|
148
|
+
__offset?: number;
|
|
149
|
+
/**
|
|
150
|
+
* Auto-computed alignment (in bytes)
|
|
151
|
+
*
|
|
152
|
+
* @internal
|
|
153
|
+
*/
|
|
154
|
+
__align?: number;
|
|
155
|
+
}
|
|
156
|
+
export interface TopLevelType extends TypeInfo {
|
|
157
|
+
/**
|
|
158
|
+
* Type name
|
|
159
|
+
*/
|
|
160
|
+
name: string;
|
|
161
|
+
/**
|
|
162
|
+
* Optional (multi-line) docstring for this type
|
|
163
|
+
*/
|
|
164
|
+
doc?: string;
|
|
165
|
+
/**
|
|
166
|
+
* Type / kind
|
|
167
|
+
*/
|
|
168
|
+
type: "struct" | "enum";
|
|
169
|
+
}
|
|
170
|
+
export interface Struct extends TopLevelType {
|
|
171
|
+
type: "struct";
|
|
172
|
+
/**
|
|
173
|
+
* List of struct fields (might be re-ordered if {@link Struct.auto} is
|
|
174
|
+
* enabled).
|
|
175
|
+
*/
|
|
176
|
+
fields: StructField[];
|
|
177
|
+
/**
|
|
178
|
+
* If true, struct fields will be re-ordered in descending order based on
|
|
179
|
+
* their {@link TypeInfo.__align} size. This might result in overall smaller
|
|
180
|
+
* structs due to minimizing inter-field padding.
|
|
181
|
+
*
|
|
182
|
+
* @defaultValue false
|
|
183
|
+
*/
|
|
184
|
+
auto?: boolean;
|
|
185
|
+
}
|
|
186
|
+
export interface StructField extends TypeInfo {
|
|
187
|
+
/**
|
|
188
|
+
* Field name (prefix: "__" is reserved)
|
|
189
|
+
*/
|
|
190
|
+
name: string;
|
|
191
|
+
/**
|
|
192
|
+
* Field docstring (can be multiline, will be formatted)
|
|
193
|
+
*/
|
|
194
|
+
doc?: string;
|
|
195
|
+
/**
|
|
196
|
+
* Field type tag/qualifier (note: `slice` & `vec` are only supported by Zig
|
|
197
|
+
* & TS).
|
|
198
|
+
*
|
|
199
|
+
* @remarks
|
|
200
|
+
* - Array & vector fields are statically sized (using
|
|
201
|
+
* {@link StructField.len})
|
|
202
|
+
* - Pointers are emitted as single-value pointers (where this distinction
|
|
203
|
+
* exist), i.e. even if they're pointing to multiple values, there's no
|
|
204
|
+
* explicit length encoded/available
|
|
205
|
+
* - Zig slices are essentially a pointer w/ associated length
|
|
206
|
+
* - Zig vectors will be processed using SIMD (if enabled in WASM target)
|
|
207
|
+
* and therefore will have stricter (larger) alignment requirements.
|
|
208
|
+
*
|
|
209
|
+
* @defaultValue "scalar"
|
|
210
|
+
*/
|
|
211
|
+
tag?: "scalar" | "array" | "ptr" | "slice" | "vec";
|
|
212
|
+
/**
|
|
213
|
+
* Field base type. If not a {@link WasmPrim} or `opaque`, the value is
|
|
214
|
+
* interpreted as another type name in the {@link TypeColl}.
|
|
215
|
+
*
|
|
216
|
+
* TODO `opaque` currently unsupported.
|
|
217
|
+
* TODO add string support (see {@link StructField.sentinel})
|
|
218
|
+
*/
|
|
219
|
+
type: WasmPrim | "opaque" | string;
|
|
220
|
+
/**
|
|
221
|
+
* TODO currently unsupported & ignored!
|
|
222
|
+
*/
|
|
223
|
+
sentinel?: number;
|
|
224
|
+
/**
|
|
225
|
+
* Array or vector length (see {@link StructField.tag})
|
|
226
|
+
*/
|
|
227
|
+
len?: number;
|
|
228
|
+
/**
|
|
229
|
+
* TODO currently unsupported & ignored!
|
|
230
|
+
*/
|
|
231
|
+
default?: any;
|
|
232
|
+
}
|
|
233
|
+
export interface Enum extends TopLevelType {
|
|
234
|
+
type: "enum";
|
|
235
|
+
/**
|
|
236
|
+
* No i64/u64 support, due to Typescript not supporting bigint enum values
|
|
237
|
+
*/
|
|
238
|
+
tag: Exclude<WasmPrim32, FloatType>;
|
|
239
|
+
/**
|
|
240
|
+
* List of possible values/IDs. Use {@link EnumValue}s for more detailed
|
|
241
|
+
* config.
|
|
242
|
+
*/
|
|
243
|
+
values: (string | EnumValue)[];
|
|
244
|
+
}
|
|
245
|
+
export interface EnumValue {
|
|
246
|
+
/**
|
|
247
|
+
* Enum value name/ID
|
|
248
|
+
*/
|
|
249
|
+
name: string;
|
|
250
|
+
/**
|
|
251
|
+
* Optional associated numeric value
|
|
252
|
+
*/
|
|
253
|
+
value?: number;
|
|
254
|
+
/**
|
|
255
|
+
* Optional docstring for this value
|
|
256
|
+
*/
|
|
257
|
+
doc?: string;
|
|
258
|
+
}
|
|
259
|
+
export interface ICodeGen {
|
|
260
|
+
/**
|
|
261
|
+
* Optional prelude source, to be prepended before any generated type defs.
|
|
262
|
+
*/
|
|
263
|
+
pre?: string;
|
|
264
|
+
/**
|
|
265
|
+
* Optional source code to be appended after any generated type defs.
|
|
266
|
+
*/
|
|
267
|
+
post?: string;
|
|
268
|
+
/**
|
|
269
|
+
* Docstring codegen
|
|
270
|
+
*/
|
|
271
|
+
doc: (doc: string, indent: string, acc: string[], topLevel?: boolean) => void;
|
|
272
|
+
/**
|
|
273
|
+
* Codegen for enum types.
|
|
274
|
+
*/
|
|
275
|
+
enum: (type: Enum, types: TypeColl, acc: string[]) => void;
|
|
276
|
+
/**
|
|
277
|
+
* Codegen for struct types.
|
|
278
|
+
*/
|
|
279
|
+
struct: (type: Struct, types: TypeColl, acc: string[]) => void;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* WASM usize type. Assuming wasm32 until wasm64 surfaces, then need an option.
|
|
283
|
+
*/
|
|
284
|
+
export declare const USIZE = "u32";
|
|
285
|
+
/**
|
|
286
|
+
* Byte size of {@link USIZE}.
|
|
287
|
+
*/
|
|
288
|
+
export declare const USIZE_SIZE = 4;
|
|
79
289
|
//# sourceMappingURL=api.d.ts.map
|
package/api.js
CHANGED
package/bin/wasm-api
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
# https://stackoverflow.com/a/246128/294515
|
|
4
|
+
SOURCE="${BASH_SOURCE[0]}"
|
|
5
|
+
while [ -h "$SOURCE" ]; do
|
|
6
|
+
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
|
|
7
|
+
SOURCE="$(readlink "$SOURCE")"
|
|
8
|
+
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
|
|
9
|
+
done
|
|
10
|
+
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
|
|
11
|
+
|
|
12
|
+
/usr/bin/env node "$DIR/../cli.js" "$DIR" "$@"
|
package/bridge.d.ts
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
1
2
|
import type { NumericArray } from "@thi.ng/api";
|
|
2
3
|
import type { ILogger } from "@thi.ng/logger";
|
|
3
|
-
import type { BigIntArray, CoreAPI, IWasmAPI, WasmExports } from "./api.js";
|
|
4
|
+
import type { BigIntArray, CoreAPI, IWasmAPI, WasmExports, WasmMemViews } from "./api.js";
|
|
5
|
+
export declare const OutOfMemoryError: {
|
|
6
|
+
new (msg?: string | undefined): {
|
|
7
|
+
name: string;
|
|
8
|
+
message: string;
|
|
9
|
+
stack?: string | undefined;
|
|
10
|
+
};
|
|
11
|
+
captureStackTrace(targetObject: object, constructorOpt?: Function | undefined): void;
|
|
12
|
+
prepareStackTrace?: ((err: Error, stackTraces: NodeJS.CallSite[]) => any) | undefined;
|
|
13
|
+
stackTraceLimit: number;
|
|
14
|
+
};
|
|
4
15
|
/**
|
|
5
16
|
* The main interop API bridge between the JS host environment and a WebAssembly
|
|
6
17
|
* module. This class provides a small core API with various typed accessors and
|
|
@@ -17,7 +28,7 @@ import type { BigIntArray, CoreAPI, IWasmAPI, WasmExports } from "./api.js";
|
|
|
17
28
|
* 64bit integers are handled via JS `BigInt` and hence require the host env to
|
|
18
29
|
* support it. No polyfill is provided.
|
|
19
30
|
*/
|
|
20
|
-
export declare class WasmBridge<T extends WasmExports = WasmExports> {
|
|
31
|
+
export declare class WasmBridge<T extends WasmExports = WasmExports> implements WasmMemViews {
|
|
21
32
|
modules: Record<string, IWasmAPI<T>>;
|
|
22
33
|
logger: ILogger;
|
|
23
34
|
i8: Int8Array;
|
|
@@ -34,7 +45,7 @@ export declare class WasmBridge<T extends WasmExports = WasmExports> {
|
|
|
34
45
|
utf8Encoder: TextEncoder;
|
|
35
46
|
imports: WebAssembly.Imports;
|
|
36
47
|
exports: T;
|
|
37
|
-
|
|
48
|
+
api: CoreAPI;
|
|
38
49
|
constructor(modules?: Record<string, IWasmAPI<T>>, logger?: ILogger);
|
|
39
50
|
/**
|
|
40
51
|
* Instantiates WASM module from given `src` (and optional provided extra
|
|
@@ -58,6 +69,11 @@ export declare class WasmBridge<T extends WasmExports = WasmExports> {
|
|
|
58
69
|
* @param exports
|
|
59
70
|
*/
|
|
60
71
|
init(exports: T): Promise<boolean>;
|
|
72
|
+
/**
|
|
73
|
+
* Called automatically. Initializes and/or updates the various typed WASM
|
|
74
|
+
* memory views (e.g. after growing the WASM memory).
|
|
75
|
+
*/
|
|
76
|
+
ensureMemory(): void;
|
|
61
77
|
/**
|
|
62
78
|
* Required use for WASM module instantiation to provide JS imports to the
|
|
63
79
|
* module. Returns an object of all WASM imports declared by the bridge core
|
|
@@ -65,8 +81,9 @@ export declare class WasmBridge<T extends WasmExports = WasmExports> {
|
|
|
65
81
|
*
|
|
66
82
|
* @remarks
|
|
67
83
|
* Since v0.4.0 each API module's imports will be in their own WASM import
|
|
68
|
-
*
|
|
69
|
-
* creating the WASM bridge.
|
|
84
|
+
* object, named using the same key which was assigned to the module when
|
|
85
|
+
* creating the WASM bridge. The bridge's core API will be named `core` and
|
|
86
|
+
* is reserved.
|
|
70
87
|
*
|
|
71
88
|
* @example
|
|
72
89
|
* The following creates a bridge with a fictional `custom` API module:
|
|
@@ -78,7 +95,7 @@ export declare class WasmBridge<T extends WasmExports = WasmExports> {
|
|
|
78
95
|
* bridge.getImports();
|
|
79
96
|
* {
|
|
80
97
|
* // imports defined by the core API of the bridge itself
|
|
81
|
-
*
|
|
98
|
+
* wasmapi: { ... },
|
|
82
99
|
* // imports defined by the CustomAPI module
|
|
83
100
|
* custom: { ... }
|
|
84
101
|
* }
|
|
@@ -92,6 +109,38 @@ export declare class WasmBridge<T extends WasmExports = WasmExports> {
|
|
|
92
109
|
* ```
|
|
93
110
|
*/
|
|
94
111
|
getImports(): WebAssembly.Imports;
|
|
112
|
+
/**
|
|
113
|
+
* Attempts to grow the WASM memory by an additional `numPages` (64KB/page)
|
|
114
|
+
* and if successful updates all typed memory views to use the new
|
|
115
|
+
* underlying buffer.
|
|
116
|
+
*
|
|
117
|
+
* @param numPages
|
|
118
|
+
*/
|
|
119
|
+
growMemory(numPages: number): void;
|
|
120
|
+
/**
|
|
121
|
+
* Attempts to allocate `numBytes` using the exported WASM core API function
|
|
122
|
+
* {@link WasmExports._wasm_allocate} (implementation specific) and returns
|
|
123
|
+
* start address of the new memory block. If unsuccessful, throws an
|
|
124
|
+
* {@link OutOfMemoryError}. If `clear` is true, the allocated region will
|
|
125
|
+
* be zero-filled.
|
|
126
|
+
*
|
|
127
|
+
* @remarks
|
|
128
|
+
* See {@link WasmExports._wasm_allocate} docs for further details.
|
|
129
|
+
*
|
|
130
|
+
* @param numBytes
|
|
131
|
+
* @param clear
|
|
132
|
+
*/
|
|
133
|
+
allocate(numBytes: number, clear?: boolean): number;
|
|
134
|
+
/**
|
|
135
|
+
* Frees a previous allocated memory region using the exported WASM core API
|
|
136
|
+
* function {@link WasmExports._wasm_free} (implementation specific). The
|
|
137
|
+
* `numBytes` value must be the same as previously given to
|
|
138
|
+
* {@link WasmBridge.allocate}.
|
|
139
|
+
*
|
|
140
|
+
* @param addr
|
|
141
|
+
* @param numBytes
|
|
142
|
+
*/
|
|
143
|
+
free(addr: number, numBytes: number): void;
|
|
95
144
|
getI8(addr: number): number;
|
|
96
145
|
getU8(addr: number): number;
|
|
97
146
|
getI16(addr: number): number;
|
|
@@ -132,7 +181,30 @@ export declare class WasmBridge<T extends WasmExports = WasmExports> {
|
|
|
132
181
|
setU64Array(addr: number, buf: BigIntArray): this;
|
|
133
182
|
setF32Array(addr: number, buf: NumericArray): this;
|
|
134
183
|
setF64Array(addr: number, buf: NumericArray): this;
|
|
184
|
+
/**
|
|
185
|
+
* Reads UTF-8 encoded string from given address and optional byte length.
|
|
186
|
+
* The default length is 0, which will be interpreted as a zero-terminated
|
|
187
|
+
* string. Returns string.
|
|
188
|
+
*
|
|
189
|
+
* @param addr
|
|
190
|
+
* @param len
|
|
191
|
+
*/
|
|
135
192
|
getString(addr: number, len?: number): string;
|
|
193
|
+
/**
|
|
194
|
+
* Encodes given string as UTF-8 and writes it to WASM memory starting at
|
|
195
|
+
* `addr`. By default the string will be zero-terminated and only `maxBytes`
|
|
196
|
+
* will be written. Returns the number of bytes written.
|
|
197
|
+
*
|
|
198
|
+
* @remarks
|
|
199
|
+
* An error will be thrown if the encoded string doesn't fully fit into the
|
|
200
|
+
* designated memory region (also note that there might need to be space for
|
|
201
|
+
* the additional sentinel/termination byte).
|
|
202
|
+
*
|
|
203
|
+
* @param str
|
|
204
|
+
* @param addr
|
|
205
|
+
* @param maxBytes
|
|
206
|
+
* @param terminate
|
|
207
|
+
*/
|
|
136
208
|
setString(str: string, addr: number, maxBytes: number, terminate?: boolean): number;
|
|
137
209
|
getElementById(addr: number, len?: number): HTMLElement;
|
|
138
210
|
}
|
package/bridge.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { defError } from "@thi.ng/errors/deferror";
|
|
1
2
|
import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
|
|
2
3
|
import { U16, U32, U64HL, U8 } from "@thi.ng/hex";
|
|
3
4
|
import { ConsoleLogger } from "@thi.ng/logger/console";
|
|
4
5
|
const B32 = BigInt(32);
|
|
6
|
+
export const OutOfMemoryError = defError(() => "Out of memory");
|
|
5
7
|
/**
|
|
6
8
|
* The main interop API bridge between the JS host environment and a WebAssembly
|
|
7
9
|
* module. This class provides a small core API with various typed accessors and
|
|
@@ -26,7 +28,7 @@ export class WasmBridge {
|
|
|
26
28
|
this.utf8Encoder = new TextEncoder();
|
|
27
29
|
const logN = (x) => this.logger.debug(x);
|
|
28
30
|
const logA = (method) => (addr, len) => this.logger.debug(method(addr, len).join(", "));
|
|
29
|
-
this.
|
|
31
|
+
this.api = {
|
|
30
32
|
printI8: logN,
|
|
31
33
|
printU8: logN,
|
|
32
34
|
printU8Hex: (x) => this.logger.debug(`0x${U8(x)}`),
|
|
@@ -85,7 +87,23 @@ export class WasmBridge {
|
|
|
85
87
|
*/
|
|
86
88
|
async init(exports) {
|
|
87
89
|
this.exports = exports;
|
|
88
|
-
|
|
90
|
+
this.ensureMemory();
|
|
91
|
+
for (let id in this.modules) {
|
|
92
|
+
this.logger.debug(`initializing API module: ${id}`);
|
|
93
|
+
const status = await this.modules[id].init(this);
|
|
94
|
+
if (!status)
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Called automatically. Initializes and/or updates the various typed WASM
|
|
101
|
+
* memory views (e.g. after growing the WASM memory).
|
|
102
|
+
*/
|
|
103
|
+
ensureMemory() {
|
|
104
|
+
const buf = this.exports.memory.buffer;
|
|
105
|
+
if (this.u8 && this.u8.buffer === buf)
|
|
106
|
+
return;
|
|
89
107
|
this.i8 = new Int8Array(buf);
|
|
90
108
|
this.u8 = new Uint8Array(buf);
|
|
91
109
|
this.i16 = new Int16Array(buf);
|
|
@@ -96,13 +114,6 @@ export class WasmBridge {
|
|
|
96
114
|
this.u64 = new BigUint64Array(buf);
|
|
97
115
|
this.f32 = new Float32Array(buf);
|
|
98
116
|
this.f64 = new Float64Array(buf);
|
|
99
|
-
for (let id in this.modules) {
|
|
100
|
-
this.logger.debug(`initializing API module: ${id}`);
|
|
101
|
-
const status = await this.modules[id].init(this);
|
|
102
|
-
if (!status)
|
|
103
|
-
return false;
|
|
104
|
-
}
|
|
105
|
-
return true;
|
|
106
117
|
}
|
|
107
118
|
/**
|
|
108
119
|
* Required use for WASM module instantiation to provide JS imports to the
|
|
@@ -111,8 +122,9 @@ export class WasmBridge {
|
|
|
111
122
|
*
|
|
112
123
|
* @remarks
|
|
113
124
|
* Since v0.4.0 each API module's imports will be in their own WASM import
|
|
114
|
-
*
|
|
115
|
-
* creating the WASM bridge.
|
|
125
|
+
* object, named using the same key which was assigned to the module when
|
|
126
|
+
* creating the WASM bridge. The bridge's core API will be named `core` and
|
|
127
|
+
* is reserved.
|
|
116
128
|
*
|
|
117
129
|
* @example
|
|
118
130
|
* The following creates a bridge with a fictional `custom` API module:
|
|
@@ -124,7 +136,7 @@ export class WasmBridge {
|
|
|
124
136
|
* bridge.getImports();
|
|
125
137
|
* {
|
|
126
138
|
* // imports defined by the core API of the bridge itself
|
|
127
|
-
*
|
|
139
|
+
* wasmapi: { ... },
|
|
128
140
|
* // imports defined by the CustomAPI module
|
|
129
141
|
* custom: { ... }
|
|
130
142
|
* }
|
|
@@ -139,7 +151,7 @@ export class WasmBridge {
|
|
|
139
151
|
*/
|
|
140
152
|
getImports() {
|
|
141
153
|
if (!this.imports) {
|
|
142
|
-
this.imports = {
|
|
154
|
+
this.imports = { wasmapi: this.api };
|
|
143
155
|
for (let id in this.modules) {
|
|
144
156
|
if (this.imports[id] !== undefined) {
|
|
145
157
|
illegalArgs(`attempt to redeclare API module ${id}`);
|
|
@@ -149,6 +161,52 @@ export class WasmBridge {
|
|
|
149
161
|
}
|
|
150
162
|
return this.imports;
|
|
151
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* Attempts to grow the WASM memory by an additional `numPages` (64KB/page)
|
|
166
|
+
* and if successful updates all typed memory views to use the new
|
|
167
|
+
* underlying buffer.
|
|
168
|
+
*
|
|
169
|
+
* @param numPages
|
|
170
|
+
*/
|
|
171
|
+
growMemory(numPages) {
|
|
172
|
+
this.exports.memory.grow(numPages);
|
|
173
|
+
this.ensureMemory();
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Attempts to allocate `numBytes` using the exported WASM core API function
|
|
177
|
+
* {@link WasmExports._wasm_allocate} (implementation specific) and returns
|
|
178
|
+
* start address of the new memory block. If unsuccessful, throws an
|
|
179
|
+
* {@link OutOfMemoryError}. If `clear` is true, the allocated region will
|
|
180
|
+
* be zero-filled.
|
|
181
|
+
*
|
|
182
|
+
* @remarks
|
|
183
|
+
* See {@link WasmExports._wasm_allocate} docs for further details.
|
|
184
|
+
*
|
|
185
|
+
* @param numBytes
|
|
186
|
+
* @param clear
|
|
187
|
+
*/
|
|
188
|
+
allocate(numBytes, clear = false) {
|
|
189
|
+
const addr = this.exports._wasm_allocate(numBytes);
|
|
190
|
+
if (!addr)
|
|
191
|
+
throw new OutOfMemoryError(`unable to allocate: ${numBytes}`);
|
|
192
|
+
this.logger.debug(`allocated ${numBytes} bytes @ 0x${U32(addr)}`);
|
|
193
|
+
this.ensureMemory();
|
|
194
|
+
clear && this.u8.fill(0, addr, addr + numBytes);
|
|
195
|
+
return addr;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Frees a previous allocated memory region using the exported WASM core API
|
|
199
|
+
* function {@link WasmExports._wasm_free} (implementation specific). The
|
|
200
|
+
* `numBytes` value must be the same as previously given to
|
|
201
|
+
* {@link WasmBridge.allocate}.
|
|
202
|
+
*
|
|
203
|
+
* @param addr
|
|
204
|
+
* @param numBytes
|
|
205
|
+
*/
|
|
206
|
+
free(addr, numBytes) {
|
|
207
|
+
this.logger.debug(`freeing memory @ 0x${U32(addr)} .. 0x${U32(addr + numBytes - 1)}`);
|
|
208
|
+
this.exports._wasm_free(addr, numBytes);
|
|
209
|
+
}
|
|
152
210
|
getI8(addr) {
|
|
153
211
|
return this.i8[addr];
|
|
154
212
|
}
|
|
@@ -297,13 +355,37 @@ export class WasmBridge {
|
|
|
297
355
|
this.f64.set(buf, addr >> 3);
|
|
298
356
|
return this;
|
|
299
357
|
}
|
|
358
|
+
/**
|
|
359
|
+
* Reads UTF-8 encoded string from given address and optional byte length.
|
|
360
|
+
* The default length is 0, which will be interpreted as a zero-terminated
|
|
361
|
+
* string. Returns string.
|
|
362
|
+
*
|
|
363
|
+
* @param addr
|
|
364
|
+
* @param len
|
|
365
|
+
*/
|
|
300
366
|
getString(addr, len = 0) {
|
|
367
|
+
this.ensureMemory();
|
|
301
368
|
return this.utf8Decoder.decode(this.u8.subarray(addr, len > 0 ? addr + len : this.u8.indexOf(0, addr)));
|
|
302
369
|
}
|
|
370
|
+
/**
|
|
371
|
+
* Encodes given string as UTF-8 and writes it to WASM memory starting at
|
|
372
|
+
* `addr`. By default the string will be zero-terminated and only `maxBytes`
|
|
373
|
+
* will be written. Returns the number of bytes written.
|
|
374
|
+
*
|
|
375
|
+
* @remarks
|
|
376
|
+
* An error will be thrown if the encoded string doesn't fully fit into the
|
|
377
|
+
* designated memory region (also note that there might need to be space for
|
|
378
|
+
* the additional sentinel/termination byte).
|
|
379
|
+
*
|
|
380
|
+
* @param str
|
|
381
|
+
* @param addr
|
|
382
|
+
* @param maxBytes
|
|
383
|
+
* @param terminate
|
|
384
|
+
*/
|
|
303
385
|
setString(str, addr, maxBytes, terminate = true) {
|
|
304
386
|
maxBytes = Math.min(maxBytes, this.u8.length - addr);
|
|
305
387
|
const len = this.utf8Encoder.encodeInto(str, this.u8.subarray(addr, addr + maxBytes)).written;
|
|
306
|
-
if (len
|
|
388
|
+
if (len == null || len >= maxBytes + (terminate ? 0 : 1)) {
|
|
307
389
|
illegalArgs(`error writing string to 0x${U32(addr)}`);
|
|
308
390
|
}
|
|
309
391
|
if (terminate) {
|