@thi.ng/wasm-api 0.3.1 → 0.6.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 +55 -1
- package/README.md +179 -22
- package/api.d.ts +166 -4
- package/api.js +9 -1
- package/bridge.d.ts +102 -6
- package/bridge.js +129 -30
- package/codegen/typescript.d.ts +22 -0
- package/codegen/typescript.js +146 -0
- package/codegen/utils.d.ts +5 -0
- package/codegen/utils.js +7 -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 +28 -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/dev/zig-cache/o/85202d15b9c43c8c66de01ab98d6eb2c/builtin.zig +0 -39
- package/dev/zig-cache/o/9e1187e9310422c9c2330ebceeaecce7/builtin.zig +0 -39
- package/dev/zig-cache/o/f647d1186cb589536c92b809f6e25f75/builtin.zig +0 -39
- package/test/custom.zig +0 -12
- package/test/zig-cache/o/117d44467ded6ce3864cde3c180ef73c/builtin.zig +0 -39
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;
|
|
@@ -32,8 +43,9 @@ export declare class WasmBridge<T extends WasmExports = WasmExports> {
|
|
|
32
43
|
f64: Float64Array;
|
|
33
44
|
utf8Decoder: TextDecoder;
|
|
34
45
|
utf8Encoder: TextEncoder;
|
|
35
|
-
|
|
46
|
+
imports: WebAssembly.Imports;
|
|
36
47
|
exports: T;
|
|
48
|
+
api: CoreAPI;
|
|
37
49
|
constructor(modules?: Record<string, IWasmAPI<T>>, logger?: ILogger);
|
|
38
50
|
/**
|
|
39
51
|
* Instantiates WASM module from given `src` (and optional provided extra
|
|
@@ -57,17 +69,78 @@ export declare class WasmBridge<T extends WasmExports = WasmExports> {
|
|
|
57
69
|
* @param exports
|
|
58
70
|
*/
|
|
59
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;
|
|
60
77
|
/**
|
|
61
78
|
* Required use for WASM module instantiation to provide JS imports to the
|
|
62
79
|
* module. Returns an object of all WASM imports declared by the bridge core
|
|
63
80
|
* API and any provided bridge API modules.
|
|
64
81
|
*
|
|
65
82
|
* @remarks
|
|
66
|
-
* Since
|
|
67
|
-
*
|
|
68
|
-
*
|
|
83
|
+
* Since v0.4.0 each API module's imports will be in their own WASM import
|
|
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.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* The following creates a bridge with a fictional `custom` API module:
|
|
90
|
+
*
|
|
91
|
+
* ```ts
|
|
92
|
+
* const bridge = new WasmBridge({ custom: new CustomAPI() });
|
|
93
|
+
*
|
|
94
|
+
* // get combined imports object
|
|
95
|
+
* bridge.getImports();
|
|
96
|
+
* {
|
|
97
|
+
* // imports defined by the core API of the bridge itself
|
|
98
|
+
* wasmapi: { ... },
|
|
99
|
+
* // imports defined by the CustomAPI module
|
|
100
|
+
* custom: { ... }
|
|
101
|
+
* }
|
|
102
|
+
* ```
|
|
103
|
+
*
|
|
104
|
+
* Any related API bindings on the WASM (Zig) side then also need to refer
|
|
105
|
+
* to these custom import sections (also see `/zig/core.zig`):
|
|
106
|
+
*
|
|
107
|
+
* ```zig
|
|
108
|
+
* pub export "custom" fn foo(x: u32) void;
|
|
109
|
+
* ```
|
|
69
110
|
*/
|
|
70
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;
|
|
71
144
|
getI8(addr: number): number;
|
|
72
145
|
getU8(addr: number): number;
|
|
73
146
|
getI16(addr: number): number;
|
|
@@ -108,7 +181,30 @@ export declare class WasmBridge<T extends WasmExports = WasmExports> {
|
|
|
108
181
|
setU64Array(addr: number, buf: BigIntArray): this;
|
|
109
182
|
setF32Array(addr: number, buf: NumericArray): this;
|
|
110
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
|
+
*/
|
|
111
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
|
+
*/
|
|
112
208
|
setString(str: string, addr: number, maxBytes: number, terminate?: boolean): number;
|
|
113
209
|
getElementById(addr: number, len?: number): HTMLElement;
|
|
114
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)}`),
|
|
@@ -70,14 +72,10 @@ export class WasmBridge {
|
|
|
70
72
|
*/
|
|
71
73
|
async instantiate(src, imports) {
|
|
72
74
|
const $src = await src;
|
|
73
|
-
const $imports = { ...
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
wasm = await WebAssembly.instantiate($src, $imports);
|
|
80
|
-
}
|
|
75
|
+
const $imports = { ...this.getImports(), ...imports };
|
|
76
|
+
const wasm = await ($src instanceof Response
|
|
77
|
+
? WebAssembly.instantiateStreaming($src, $imports)
|
|
78
|
+
: WebAssembly.instantiate($src, $imports));
|
|
81
79
|
return this.init(wasm.instance.exports);
|
|
82
80
|
}
|
|
83
81
|
/**
|
|
@@ -89,7 +87,23 @@ export class WasmBridge {
|
|
|
89
87
|
*/
|
|
90
88
|
async init(exports) {
|
|
91
89
|
this.exports = exports;
|
|
92
|
-
|
|
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;
|
|
93
107
|
this.i8 = new Int8Array(buf);
|
|
94
108
|
this.u8 = new Uint8Array(buf);
|
|
95
109
|
this.i16 = new Int16Array(buf);
|
|
@@ -100,13 +114,6 @@ export class WasmBridge {
|
|
|
100
114
|
this.u64 = new BigUint64Array(buf);
|
|
101
115
|
this.f32 = new Float32Array(buf);
|
|
102
116
|
this.f64 = new Float64Array(buf);
|
|
103
|
-
for (let id in this.modules) {
|
|
104
|
-
this.logger.debug(`initializing API module: ${id}`);
|
|
105
|
-
const status = await this.modules[id].init(this);
|
|
106
|
-
if (!status)
|
|
107
|
-
return false;
|
|
108
|
-
}
|
|
109
|
-
return true;
|
|
110
117
|
}
|
|
111
118
|
/**
|
|
112
119
|
* Required use for WASM module instantiation to provide JS imports to the
|
|
@@ -114,23 +121,91 @@ export class WasmBridge {
|
|
|
114
121
|
* API and any provided bridge API modules.
|
|
115
122
|
*
|
|
116
123
|
* @remarks
|
|
117
|
-
* Since
|
|
118
|
-
*
|
|
119
|
-
*
|
|
124
|
+
* Since v0.4.0 each API module's imports will be in their own WASM import
|
|
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.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* The following creates a bridge with a fictional `custom` API module:
|
|
131
|
+
*
|
|
132
|
+
* ```ts
|
|
133
|
+
* const bridge = new WasmBridge({ custom: new CustomAPI() });
|
|
134
|
+
*
|
|
135
|
+
* // get combined imports object
|
|
136
|
+
* bridge.getImports();
|
|
137
|
+
* {
|
|
138
|
+
* // imports defined by the core API of the bridge itself
|
|
139
|
+
* wasmapi: { ... },
|
|
140
|
+
* // imports defined by the CustomAPI module
|
|
141
|
+
* custom: { ... }
|
|
142
|
+
* }
|
|
143
|
+
* ```
|
|
144
|
+
*
|
|
145
|
+
* Any related API bindings on the WASM (Zig) side then also need to refer
|
|
146
|
+
* to these custom import sections (also see `/zig/core.zig`):
|
|
147
|
+
*
|
|
148
|
+
* ```zig
|
|
149
|
+
* pub export "custom" fn foo(x: u32) void;
|
|
150
|
+
* ```
|
|
120
151
|
*/
|
|
121
152
|
getImports() {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (env[k] !== undefined) {
|
|
128
|
-
illegalArgs(`attempt to redeclare import: ${k} by API module ${id}`);
|
|
153
|
+
if (!this.imports) {
|
|
154
|
+
this.imports = { wasmapi: this.api };
|
|
155
|
+
for (let id in this.modules) {
|
|
156
|
+
if (this.imports[id] !== undefined) {
|
|
157
|
+
illegalArgs(`attempt to redeclare API module ${id}`);
|
|
129
158
|
}
|
|
159
|
+
this.imports[id] = this.modules[id].getImports();
|
|
130
160
|
}
|
|
131
|
-
Object.assign(env, imports);
|
|
132
161
|
}
|
|
133
|
-
return
|
|
162
|
+
return this.imports;
|
|
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);
|
|
134
209
|
}
|
|
135
210
|
getI8(addr) {
|
|
136
211
|
return this.i8[addr];
|
|
@@ -280,13 +355,37 @@ export class WasmBridge {
|
|
|
280
355
|
this.f64.set(buf, addr >> 3);
|
|
281
356
|
return this;
|
|
282
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
|
+
*/
|
|
283
366
|
getString(addr, len = 0) {
|
|
367
|
+
this.ensureMemory();
|
|
284
368
|
return this.utf8Decoder.decode(this.u8.subarray(addr, len > 0 ? addr + len : this.u8.indexOf(0, addr)));
|
|
285
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
|
+
*/
|
|
286
385
|
setString(str, addr, maxBytes, terminate = true) {
|
|
287
386
|
maxBytes = Math.min(maxBytes, this.u8.length - addr);
|
|
288
387
|
const len = this.utf8Encoder.encodeInto(str, this.u8.subarray(addr, addr + maxBytes)).written;
|
|
289
|
-
if (len
|
|
388
|
+
if (len == null || len >= maxBytes + (terminate ? 0 : 1)) {
|
|
290
389
|
illegalArgs(`error writing string to 0x${U32(addr)}`);
|
|
291
390
|
}
|
|
292
391
|
if (terminate) {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ICodeGen } from "../api.js";
|
|
2
|
+
export interface TSOpts {
|
|
3
|
+
/**
|
|
4
|
+
* Indentation string
|
|
5
|
+
*
|
|
6
|
+
* @defaultValue "\t"
|
|
7
|
+
*/
|
|
8
|
+
indent: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* TypeScript code generator. Call with options and then pass to
|
|
12
|
+
* {@link generateTypes} (see its docs for further usage).
|
|
13
|
+
*
|
|
14
|
+
* @remarks
|
|
15
|
+
* This codegen generates interface and enum definitions for a {@link TypeColl}
|
|
16
|
+
* given to {@link generateTypes}. For structs it will also generate memory
|
|
17
|
+
* mapped wrappers with fully typed accessors.
|
|
18
|
+
*
|
|
19
|
+
* @param opts
|
|
20
|
+
*/
|
|
21
|
+
export declare const TYPESCRIPT: (opts?: Partial<TSOpts>) => ICodeGen;
|
|
22
|
+
//# sourceMappingURL=typescript.d.ts.map
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { BIGINT_ARRAY_CTORS, BIT_SHIFTS, TYPEDARRAY_CTORS, } from "@thi.ng/api/typedarray";
|
|
2
|
+
import { isString } from "@thi.ng/checks/is-string";
|
|
3
|
+
import { PKG_NAME, USIZE, } from "../api.js";
|
|
4
|
+
import { isBigNumeric, isNumeric, isPrim, prefixLines } from "./utils.js";
|
|
5
|
+
/**
|
|
6
|
+
* TypeScript code generator. Call with options and then pass to
|
|
7
|
+
* {@link generateTypes} (see its docs for further usage).
|
|
8
|
+
*
|
|
9
|
+
* @remarks
|
|
10
|
+
* This codegen generates interface and enum definitions for a {@link TypeColl}
|
|
11
|
+
* given to {@link generateTypes}. For structs it will also generate memory
|
|
12
|
+
* mapped wrappers with fully typed accessors.
|
|
13
|
+
*
|
|
14
|
+
* @param opts
|
|
15
|
+
*/
|
|
16
|
+
export const TYPESCRIPT = (opts) => {
|
|
17
|
+
const { indent } = { indent: "\t", ...opts };
|
|
18
|
+
const I = indent;
|
|
19
|
+
const I2 = I + I;
|
|
20
|
+
const I3 = I2 + I;
|
|
21
|
+
const gen = {
|
|
22
|
+
pre: `import type { WasmTypeBase, WasmTypeConstructor } from "${PKG_NAME}";`,
|
|
23
|
+
doc: (doc, indent, acc) => {
|
|
24
|
+
if (doc.indexOf("\n") !== -1) {
|
|
25
|
+
acc.push(indent + "/**", prefixLines(indent + " * ", doc), indent + " */");
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
acc.push(`${indent}/** ${doc} */`);
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
enum: (type, _, acc) => {
|
|
32
|
+
const e = type;
|
|
33
|
+
acc.push(`export enum ${e.name} {`);
|
|
34
|
+
for (let v of e.values) {
|
|
35
|
+
var line = indent;
|
|
36
|
+
if (!isString(v)) {
|
|
37
|
+
v.doc && gen.doc(v.doc, indent, acc);
|
|
38
|
+
line += v.name;
|
|
39
|
+
if (v.value != null)
|
|
40
|
+
line += ` = ${v.value}`;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
line += v;
|
|
44
|
+
}
|
|
45
|
+
acc.push(line + ",");
|
|
46
|
+
}
|
|
47
|
+
acc.push("}\n");
|
|
48
|
+
return acc;
|
|
49
|
+
},
|
|
50
|
+
struct: (type, types, acc) => {
|
|
51
|
+
const struct = type;
|
|
52
|
+
const returnTypes = {};
|
|
53
|
+
// interface definition
|
|
54
|
+
acc.push(`export interface ${struct.name} extends WasmTypeBase {`);
|
|
55
|
+
for (let f of struct.fields) {
|
|
56
|
+
f.doc && gen.doc(f.doc, indent, acc);
|
|
57
|
+
let line = `${indent}${f.name}: `;
|
|
58
|
+
let rtype = "";
|
|
59
|
+
if (f.tag == "array" || f.tag == "slice" || f.tag === "vec") {
|
|
60
|
+
rtype = isNumeric(f.type)
|
|
61
|
+
? TYPEDARRAY_CTORS[f.type].name
|
|
62
|
+
: isBigNumeric(f.type)
|
|
63
|
+
? BIGINT_ARRAY_CTORS[f.type].name
|
|
64
|
+
: f.type + "[]";
|
|
65
|
+
}
|
|
66
|
+
else if (!f.tag || f.tag === "scalar" || f.tag === "ptr") {
|
|
67
|
+
rtype = isBigNumeric(f.type)
|
|
68
|
+
? "bigint"
|
|
69
|
+
: isNumeric(f.type)
|
|
70
|
+
? "number"
|
|
71
|
+
: f.type;
|
|
72
|
+
}
|
|
73
|
+
returnTypes[f.name] = rtype;
|
|
74
|
+
acc.push(line + rtype + ";");
|
|
75
|
+
}
|
|
76
|
+
acc.push("}\n");
|
|
77
|
+
// type implementation
|
|
78
|
+
acc.push(`export const $${struct.name}: WasmTypeConstructor<${struct.name}> = (mem) => ({`, `${I}get align() { return ${struct.__align}; },`, `${I}get size() { return ${struct.__size}; },`, `${I}instance: (base) => ({`, `${I2}get __base() { return base; },`, `${I2}get __bytes() { return mem.u8.subarray(base, base + ${struct.__size}); },`);
|
|
79
|
+
for (let f of struct.fields) {
|
|
80
|
+
const offset = f.__offset || 0;
|
|
81
|
+
acc.push(`${I2}get ${f.name}(): ${returnTypes[f.name]} {`);
|
|
82
|
+
const prim = isPrim(f.type);
|
|
83
|
+
if (f.tag === "ptr") {
|
|
84
|
+
acc.push(prim
|
|
85
|
+
? `${I3}return mem.${f.type}[${__ptrShift(offset, f.type)}];`
|
|
86
|
+
: `${I3}return $${f.type}.instance(${__ptr(offset)});`);
|
|
87
|
+
}
|
|
88
|
+
else if (f.tag === "slice") {
|
|
89
|
+
acc.push(`${I3}const len = ${__ptr(offset + 4)};`, prim
|
|
90
|
+
? `${I3}const addr = ${__ptrShift(offset, f.type)};
|
|
91
|
+
${I3}return mem.${f.type}.subarray(addr, addr + len);`
|
|
92
|
+
: `${I3}const addr = ${__ptr(offset)};\n${__mapArray(struct, f, I3)}`);
|
|
93
|
+
}
|
|
94
|
+
else if (f.tag === "array" || f.tag === "vec") {
|
|
95
|
+
acc.push(prim
|
|
96
|
+
? `${I3}const addr = ${__addrShift(offset, f.type)};
|
|
97
|
+
${I3}return mem.${f.type}.subarray(addr, addr + ${f.len});`
|
|
98
|
+
: `${I3}const addr = ${__addr(offset)};\n${__mapArray(struct, f, I3, f.len)}`);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
let setter;
|
|
102
|
+
if (prim) {
|
|
103
|
+
const addr = __mem(f.type, f.__offset);
|
|
104
|
+
acc.push(`${I3}return ${addr};`);
|
|
105
|
+
setter = `${addr} = x`;
|
|
106
|
+
}
|
|
107
|
+
else if (types[f.type].type === "enum") {
|
|
108
|
+
const tag = types[f.type].tag;
|
|
109
|
+
const addr = __mem(tag, f.__offset);
|
|
110
|
+
acc.push(`${I3}return ${addr};`);
|
|
111
|
+
setter = `${addr} = x`;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
acc.push(`${I3}return $${f.type}(mem).instance(${__addr(offset)});`);
|
|
115
|
+
setter = `mem.u8.set(x.__bytes, ${__addr(offset)})`;
|
|
116
|
+
}
|
|
117
|
+
// close getter
|
|
118
|
+
acc.push(`${I2}},`);
|
|
119
|
+
// setter
|
|
120
|
+
acc.push(`${I2}set ${f.name}(x: ${returnTypes[f.name]}) {`, `${I3}${setter};`);
|
|
121
|
+
}
|
|
122
|
+
// close field accessor
|
|
123
|
+
acc.push(`${I2}},`);
|
|
124
|
+
}
|
|
125
|
+
acc.push(`${I}})\n});\n`);
|
|
126
|
+
return acc;
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
return gen;
|
|
130
|
+
};
|
|
131
|
+
/** @internal */
|
|
132
|
+
const __shift = (type) => BIT_SHIFTS[type];
|
|
133
|
+
/** @internal */
|
|
134
|
+
const __addr = (offset) => (offset > 0 ? `(base + ${offset})` : "base");
|
|
135
|
+
/** @internal */
|
|
136
|
+
const __addrShift = (offset, shift) => __addr(offset) + " >>> " + __shift(shift);
|
|
137
|
+
/** @internal */
|
|
138
|
+
const __ptr = (offset) => `mem.${USIZE}[${__addrShift(offset, USIZE)}]`;
|
|
139
|
+
/** @internal */
|
|
140
|
+
const __ptrShift = (offset, shift) => __ptr(offset) + " >>> " + __shift(shift);
|
|
141
|
+
const __mem = (type, offset) => `mem.${type}[${__addrShift(offset, type)}]`;
|
|
142
|
+
/** @internal */
|
|
143
|
+
const __mapArray = (struct, f, indent, len = "len") => prefixLines(indent, `const inst = $${f.type}(mem);
|
|
144
|
+
const slice: ${f.type}[] = [];
|
|
145
|
+
for(let i = 0; i < ${len}; i++) slice.push(inst.instance(addr + i * ${struct.__size}));
|
|
146
|
+
return slice;`);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const isNumeric: (x: string) => boolean;
|
|
2
|
+
export declare const isBigNumeric: (x: string) => boolean;
|
|
3
|
+
export declare const isPrim: (x: string) => boolean;
|
|
4
|
+
export declare const prefixLines: (prefix: string, str: string) => string;
|
|
5
|
+
//# sourceMappingURL=utils.d.ts.map
|
package/codegen/utils.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const isNumeric = (x) => /^([iu](8|16|32))|(f(32|64))$/.test(x);
|
|
2
|
+
export const isBigNumeric = (x) => /^[iu]64$/.test(x);
|
|
3
|
+
export const isPrim = (x) => isNumeric(x) || isBigNumeric(x);
|
|
4
|
+
export const prefixLines = (prefix, str) => str
|
|
5
|
+
.split("\n")
|
|
6
|
+
.map((line) => prefix + line)
|
|
7
|
+
.join("\n");
|
package/codegen/zig.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ICodeGen } from "../api.js";
|
|
2
|
+
export interface ZigOpts {
|
|
3
|
+
/**
|
|
4
|
+
* If true, generates various struct & struct field analysis functions
|
|
5
|
+
* (sizes, alignment, offsets etc.).
|
|
6
|
+
*
|
|
7
|
+
* @defaultValue false
|
|
8
|
+
*/
|
|
9
|
+
debug: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Zig code generator. Call with options and then pass to {@link generateTypes}
|
|
13
|
+
* (see its docs for further usage).
|
|
14
|
+
*
|
|
15
|
+
* @remarks
|
|
16
|
+
* This codegen generates struct and enum definitions for a {@link TypeColl}
|
|
17
|
+
* given to {@link generateTypes}.
|
|
18
|
+
*
|
|
19
|
+
* @param opts
|
|
20
|
+
*/
|
|
21
|
+
export declare const ZIG: (opts?: Partial<ZigOpts>) => ICodeGen;
|
|
22
|
+
//# sourceMappingURL=zig.d.ts.map
|
package/codegen/zig.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { isString } from "@thi.ng/checks/is-string";
|
|
2
|
+
import { prefixLines } from "./utils.js";
|
|
3
|
+
/**
|
|
4
|
+
* Zig code generator. Call with options and then pass to {@link generateTypes}
|
|
5
|
+
* (see its docs for further usage).
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* This codegen generates struct and enum definitions for a {@link TypeColl}
|
|
9
|
+
* given to {@link generateTypes}.
|
|
10
|
+
*
|
|
11
|
+
* @param opts
|
|
12
|
+
*/
|
|
13
|
+
export const ZIG = (opts) => {
|
|
14
|
+
const { debug } = { debug: false, ...opts };
|
|
15
|
+
const gen = {
|
|
16
|
+
doc: (doc, indent, acc, topLevel = false) => {
|
|
17
|
+
acc.push(prefixLines(topLevel ? "//! " : indent + "/// ", doc));
|
|
18
|
+
},
|
|
19
|
+
enum: (e, _, acc) => {
|
|
20
|
+
acc.push(`pub const ${e.name} = enum(${e.tag}) {`);
|
|
21
|
+
for (let v of e.values) {
|
|
22
|
+
let line = ` `;
|
|
23
|
+
if (!isString(v)) {
|
|
24
|
+
v.doc && gen.doc(v.doc, " ", acc);
|
|
25
|
+
line += v.name;
|
|
26
|
+
if (v.value != null)
|
|
27
|
+
line += ` = ${v.value}`;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
line += v;
|
|
31
|
+
}
|
|
32
|
+
acc.push(line + ",");
|
|
33
|
+
}
|
|
34
|
+
acc.push("};\n");
|
|
35
|
+
},
|
|
36
|
+
struct: (struct, _, acc) => {
|
|
37
|
+
const name = struct.name;
|
|
38
|
+
acc.push(`pub const ${name} = struct {`);
|
|
39
|
+
const ftypes = {};
|
|
40
|
+
for (let f of struct.fields) {
|
|
41
|
+
f.doc && gen.doc(f.doc, " ", acc);
|
|
42
|
+
var ftype;
|
|
43
|
+
switch (f.tag) {
|
|
44
|
+
case "array":
|
|
45
|
+
ftype = `[${f.len}]${f.type}`;
|
|
46
|
+
break;
|
|
47
|
+
case "slice":
|
|
48
|
+
ftype = `[]${f.type}`;
|
|
49
|
+
break;
|
|
50
|
+
case "vec":
|
|
51
|
+
ftype = `@Vector(${f.len}, ${f.type})`;
|
|
52
|
+
break;
|
|
53
|
+
case "ptr":
|
|
54
|
+
ftype = `*${f.len ? `[${f.len}]` : ""}${f.type}`;
|
|
55
|
+
break;
|
|
56
|
+
case "scalar":
|
|
57
|
+
default:
|
|
58
|
+
ftype = f.type;
|
|
59
|
+
}
|
|
60
|
+
ftypes[f.name] = ftype;
|
|
61
|
+
acc.push(` ${f.name}: ${ftype},`);
|
|
62
|
+
}
|
|
63
|
+
acc.push("};\n");
|
|
64
|
+
if (!debug)
|
|
65
|
+
return;
|
|
66
|
+
const fn = (fname, body) => `export fn ${name}_${fname}() usize { return ${body}; }`;
|
|
67
|
+
acc.push(fn("align", `@alignOf(${name})`), fn("size", `@sizeOf(${name})`));
|
|
68
|
+
for (let f of struct.fields) {
|
|
69
|
+
acc.push(fn(f.name + "_align", `@alignOf(${ftypes[f.name]})`), fn(f.name + "_offset", `@offsetOf(${name}, "${f.name}")`), fn(f.name + "_size", `@sizeOf(${ftypes[f.name]})`));
|
|
70
|
+
}
|
|
71
|
+
acc.push("");
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
return gen;
|
|
75
|
+
};
|
package/codegen.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ICodeGen, TypeColl } from "./api.js";
|
|
2
|
+
export interface CodeGenOpts {
|
|
3
|
+
/**
|
|
4
|
+
* Optional string to be injected before generated type defs (but after
|
|
5
|
+
* codegen's own prelude, if any)
|
|
6
|
+
*/
|
|
7
|
+
pre: string;
|
|
8
|
+
/**
|
|
9
|
+
* Optional string to be injected after generated type defs (but before
|
|
10
|
+
* codegen's own epilogue, if any)
|
|
11
|
+
*/
|
|
12
|
+
post: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const prepareTypes: (types: TypeColl) => void;
|
|
15
|
+
export declare const generateTypes: (types: TypeColl, codegen: ICodeGen, opts?: Partial<CodeGenOpts>) => string;
|
|
16
|
+
//# sourceMappingURL=codegen.d.ts.map
|