@thi.ng/wasm-api 0.2.0 → 0.4.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 +27 -1
- package/README.md +34 -36
- package/api.d.ts +37 -4
- package/bridge.d.ts +120 -49
- package/bridge.js +247 -63
- package/dev/custom.zig +1 -0
- package/dev/fieldinfo.zig +135 -0
- package/package.json +6 -6
- package/test/custom.zig +12 -0
- package/zig/core.zig +58 -22
- package/dev/zig-cache/o/9e1187e9310422c9c2330ebceeaecce7/builtin.zig +0 -39
- package/dev/zig-cache/o/f647d1186cb589536c92b809f6e25f75/builtin.zig +0 -39
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
-
- **Last updated**: 2022-08-
|
|
3
|
+
- **Last updated**: 2022-08-07T15:28:01Z
|
|
4
4
|
- **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
|
|
5
5
|
|
|
6
6
|
All notable changes to this project will be documented in this file.
|
|
@@ -9,6 +9,32 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
|
|
|
9
9
|
**Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
|
|
10
10
|
and/or version bumps of transitive dependencies.
|
|
11
11
|
|
|
12
|
+
## [0.4.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/wasm-api@0.4.0) (2022-08-07)
|
|
13
|
+
|
|
14
|
+
#### 🚀 Features
|
|
15
|
+
|
|
16
|
+
- use named import objects ([4965f20](https://github.com/thi-ng/umbrella/commit/4965f20))
|
|
17
|
+
- switch to name import objects to avoid merging into flat namespace
|
|
18
|
+
- update externs in core.zig
|
|
19
|
+
- update docstrings
|
|
20
|
+
|
|
21
|
+
## [0.3.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/wasm-api@0.3.0) (2022-08-04)
|
|
22
|
+
|
|
23
|
+
#### 🚀 Features
|
|
24
|
+
|
|
25
|
+
- add i64/u64 support/accessors ([768c8bd](https://github.com/thi-ng/umbrella/commit/768c8bd))
|
|
26
|
+
- add WasmBridge.instantiate, add/update accessors ([0698bae](https://github.com/thi-ng/umbrella/commit/0698bae))
|
|
27
|
+
- add WasmBridge.instantiate() boilerplate
|
|
28
|
+
- add setters for typed scalars & arrays
|
|
29
|
+
- rename derefXX() => getXX() getters
|
|
30
|
+
- update tests
|
|
31
|
+
- major update WasmBridge, add types ([47aa222](https://github.com/thi-ng/umbrella/commit/47aa222))
|
|
32
|
+
- add WasmExports base interface
|
|
33
|
+
- add generics for WasmBridge & IWasmAPI
|
|
34
|
+
- update WasmBridge.init() arg (full WASM exports, not just mem)
|
|
35
|
+
- add WasmBridge.exports field to store WASM module exports
|
|
36
|
+
- add naming conflict check in WasmBridge.getImports()
|
|
37
|
+
|
|
12
38
|
## [0.2.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/wasm-api@0.2.0) (2022-08-01)
|
|
13
39
|
|
|
14
40
|
#### 🚀 Features
|
package/README.md
CHANGED
|
@@ -25,9 +25,10 @@ Modular, extensible API bridge and generic glue code between JS & WebAssembly.
|
|
|
25
25
|
|
|
26
26
|
This package provides a small, generic and modular
|
|
27
27
|
[`WasmBridge`](https://docs.thi.ng/umbrella/wasm-api/classes/WasmBridge.html)
|
|
28
|
-
class as interop basis for hybrid JS/WebAssembly
|
|
29
|
-
only a
|
|
30
|
-
|
|
28
|
+
class as interop basis and a much reduced boilerplate for hybrid JS/WebAssembly
|
|
29
|
+
applications. At the moment only a minimal core API is provided (i.e. for debug
|
|
30
|
+
output, string, pointer, typed array accessors [8/16/32/64 bit (u)ints, 32/64
|
|
31
|
+
bit floats]), but in the future we aim to also supply support modules for DOM
|
|
31
32
|
manipulation, WebGL, WebGPU, WebAudio etc.
|
|
32
33
|
|
|
33
34
|
In general, all languages with a WebAssembly target are supported, however
|
|
@@ -56,15 +57,15 @@ export class CustomAPI implements IWasmAPI {
|
|
|
56
57
|
|
|
57
58
|
/**
|
|
58
59
|
* Returns object of functions to import as externals into
|
|
59
|
-
* the WASM module. These imports are merged
|
|
60
|
-
*
|
|
60
|
+
* the WASM module. These imports are merged into a larger
|
|
61
|
+
* imports object alongside the bridge's core API...
|
|
61
62
|
*/
|
|
62
63
|
getImports(): WebAssembly.Imports {
|
|
63
64
|
return {
|
|
64
65
|
/**
|
|
65
66
|
* Writes 2 random float32 numbers to given address
|
|
66
67
|
*/
|
|
67
|
-
|
|
68
|
+
randomVec2: (addr: number) => {
|
|
68
69
|
this.parent.f32.set(
|
|
69
70
|
[Math.random(), Math.random()],
|
|
70
71
|
addr >> 2
|
|
@@ -82,25 +83,29 @@ export const bridge = new WasmBridge({ custom: new CustomAPI() });
|
|
|
82
83
|
```
|
|
83
84
|
|
|
84
85
|
In Zig (or any other language of your choice) we can then utilize this custom
|
|
85
|
-
API like so (also see example further below in this
|
|
86
|
+
API like so (Please also see /test/index.ts` & the example further below in this
|
|
87
|
+
readme):
|
|
86
88
|
|
|
87
89
|
```zig
|
|
90
|
+
// Import JS core API
|
|
88
91
|
const js = @import("wasmapi");
|
|
89
92
|
|
|
90
93
|
/// JS external to fill vec2 w/ random values
|
|
91
|
-
|
|
94
|
+
/// Note: Each API module uses a separate import object to avoid naming clashes
|
|
95
|
+
/// Here we declare an external binding belonging to the "custom" import group
|
|
96
|
+
extern "custom" fn randomVec2(addr: usize) void;
|
|
92
97
|
|
|
93
98
|
export fn test_randomVec2() void {
|
|
94
|
-
|
|
99
|
+
var foo = [2]f32{ 0, 0 };
|
|
95
100
|
|
|
96
101
|
// print original
|
|
97
|
-
|
|
102
|
+
js.printF32Array(foo[0..]);
|
|
98
103
|
|
|
99
104
|
// populate foo with random numbers
|
|
100
|
-
|
|
105
|
+
randomVec2(@ptrToInt(&foo));
|
|
101
106
|
|
|
102
107
|
// print result
|
|
103
|
-
|
|
108
|
+
js.printF32Array(foo[0..]);
|
|
104
109
|
}
|
|
105
110
|
```
|
|
106
111
|
|
|
@@ -180,7 +185,7 @@ node --experimental-repl-await
|
|
|
180
185
|
> const wasmApi = await import("@thi.ng/wasm-api");
|
|
181
186
|
```
|
|
182
187
|
|
|
183
|
-
Package sizes (gzipped, pre-treeshake): ESM: 1.
|
|
188
|
+
Package sizes (gzipped, pre-treeshake): ESM: 1.61 KB
|
|
184
189
|
|
|
185
190
|
## Dependencies
|
|
186
191
|
|
|
@@ -195,34 +200,26 @@ Package sizes (gzipped, pre-treeshake): ESM: 1.21 KB
|
|
|
195
200
|
[Generated API docs](https://docs.thi.ng/umbrella/wasm-api/)
|
|
196
201
|
|
|
197
202
|
```ts
|
|
198
|
-
import { WasmBridge } from "@thi.ng/wasm-api";
|
|
203
|
+
import { WasmBridge, WasmExports } from "@thi.ng/wasm-api";
|
|
199
204
|
import { readFileSync } from "fs";
|
|
200
205
|
|
|
201
206
|
// WASM exports from our dummy module (below)
|
|
202
|
-
interface App {
|
|
203
|
-
memory: WebAssembly.Memory;
|
|
207
|
+
interface App extends WasmExports {
|
|
204
208
|
start: () => void;
|
|
205
209
|
}
|
|
206
210
|
|
|
207
211
|
(async () => {
|
|
208
212
|
// new API bridge with defaults
|
|
209
213
|
// (i.e. no child API modules and using console logger)
|
|
210
|
-
const bridge = new WasmBridge();
|
|
214
|
+
const bridge = new WasmBridge<App>();
|
|
211
215
|
|
|
212
216
|
// instantiate WASM module using imports provided by the bridge
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
);
|
|
217
|
+
// this also initializes any bindings & bridge child APIs (if any)
|
|
218
|
+
// (also accepts a fetch() `Response` as input)
|
|
219
|
+
await bridge.instantiate(readFileSync("hello.wasm"));
|
|
217
220
|
|
|
218
|
-
//
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
// init bindings & child APIs (if any)
|
|
222
|
-
await bridge.init(app.memory);
|
|
223
|
-
|
|
224
|
-
// call a WASM function
|
|
225
|
-
app.start();
|
|
221
|
+
// call an exported WASM function
|
|
222
|
+
bridge.exports.start();
|
|
226
223
|
})();
|
|
227
224
|
```
|
|
228
225
|
|
|
@@ -238,16 +235,17 @@ export fn start() void {
|
|
|
238
235
|
}
|
|
239
236
|
```
|
|
240
237
|
|
|
241
|
-
The WASM binary can be built
|
|
242
|
-
.zig file(s) to your `build.zig` and/or source
|
|
238
|
+
The WASM binary can be built using the following command (or for more complex
|
|
239
|
+
scenarios add the supplied .zig file(s) to your `build.zig` and/or source
|
|
240
|
+
folder):
|
|
243
241
|
|
|
244
242
|
```bash
|
|
245
243
|
# compile WASM binary
|
|
246
244
|
zig build-lib \
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
245
|
+
--pkg-begin wasmapi node_modules/@thi.ng/wasm-api/zig/core.zig --pkg-end \
|
|
246
|
+
-target wasm32-freestanding \
|
|
247
|
+
-O ReleaseSmall -dynamic --strip \
|
|
248
|
+
hello.zig
|
|
251
249
|
|
|
252
250
|
# disassemble WASM
|
|
253
251
|
wasm-dis -o hello.wast hello.wasm
|
|
@@ -259,7 +257,7 @@ The resulting WASM:
|
|
|
259
257
|
(module
|
|
260
258
|
(type $i32_i32_=>_none (func (param i32 i32)))
|
|
261
259
|
(type $none_=>_none (func))
|
|
262
|
-
(import "
|
|
260
|
+
(import "core" "_printStr" (func $fimport$0 (param i32 i32)))
|
|
263
261
|
(global $global$0 (mut i32) (i32.const 65536))
|
|
264
262
|
(memory $0 2)
|
|
265
263
|
(data (i32.const 65536) "hello world!\00")
|
package/api.d.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import type { Fn } from "@thi.ng/api";
|
|
1
|
+
import type { Fn, Fn2 } from "@thi.ng/api";
|
|
2
2
|
import type { WasmBridge } from "./bridge.js";
|
|
3
|
+
export declare type BigIntArray = bigint[] | BigInt64Array | BigUint64Array;
|
|
3
4
|
/**
|
|
4
5
|
* Common interface for WASM/JS child APIs which will be used in combination
|
|
5
6
|
* with a parent {@link WasmBridge}.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* The generic type param is optional and only used if the API is requiring
|
|
10
|
+
* certain exports declared by WASM module.
|
|
6
11
|
*/
|
|
7
|
-
export interface IWasmAPI {
|
|
12
|
+
export interface IWasmAPI<T extends WasmExports = WasmExports> {
|
|
8
13
|
/**
|
|
9
14
|
* Called by {@link WasmBridge.init} to initialize all child APIs (async)
|
|
10
15
|
* after the WASM module has been instantiated. If the method returns false
|
|
@@ -12,7 +17,7 @@ export interface IWasmAPI {
|
|
|
12
17
|
*
|
|
13
18
|
* @param parent
|
|
14
19
|
*/
|
|
15
|
-
init(parent: WasmBridge): Promise<boolean>;
|
|
20
|
+
init(parent: WasmBridge<T>): Promise<boolean>;
|
|
16
21
|
/**
|
|
17
22
|
* Returns an object of this child API's declared WASM imports. Be aware
|
|
18
23
|
* imports from all child APIs will be merged into a single flat namespace,
|
|
@@ -20,7 +25,30 @@ export interface IWasmAPI {
|
|
|
20
25
|
*/
|
|
21
26
|
getImports(): WebAssembly.ModuleImports;
|
|
22
27
|
}
|
|
23
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Base interface of exports declared by the WASM module. At the very least, the
|
|
30
|
+
* module needs to export its memory.
|
|
31
|
+
*
|
|
32
|
+
* @remarks
|
|
33
|
+
* This interface is supposed to be extended with the concrete exports defined
|
|
34
|
+
* by your WASM module and is used as generic type param for {@link WasmBridge}
|
|
35
|
+
* and any {@link IWasmAPI} bridge modules. These exports can obtained via
|
|
36
|
+
* {@link WasmBridge.exports} where they will be stored during the execution of
|
|
37
|
+
* {@link WasmBridge.init}.
|
|
38
|
+
*/
|
|
39
|
+
export interface WasmExports {
|
|
40
|
+
/**
|
|
41
|
+
* The WASM module's linear memory buffer. The `WasmBridge` automatically
|
|
42
|
+
* creates various typed views of that memory.
|
|
43
|
+
*/
|
|
44
|
+
memory: WebAssembly.Memory;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Core API of WASM imports defined by the {@link WasmBridge}. The same
|
|
48
|
+
* functions are declared as bindings in `/zig/core.zig`. Also see this file for
|
|
49
|
+
* documentation of each function...
|
|
50
|
+
*/
|
|
51
|
+
export interface CoreAPI extends WebAssembly.ModuleImports {
|
|
24
52
|
printI8: Fn<number, void>;
|
|
25
53
|
printU8: Fn<number, void>;
|
|
26
54
|
printU8Hex: Fn<number, void>;
|
|
@@ -30,6 +58,9 @@ export interface CoreAPI {
|
|
|
30
58
|
printI32: Fn<number, void>;
|
|
31
59
|
printU32: Fn<number, void>;
|
|
32
60
|
printU32Hex: Fn<number, void>;
|
|
61
|
+
_printI64: Fn2<number, number, void>;
|
|
62
|
+
_printU64: Fn2<number, number, void>;
|
|
63
|
+
_printU64Hex: Fn2<number, number, void>;
|
|
33
64
|
printF32: Fn<number, void>;
|
|
34
65
|
printF64: Fn<number, void>;
|
|
35
66
|
_printI8Array: (addr: number, len: number) => void;
|
|
@@ -38,6 +69,8 @@ export interface CoreAPI {
|
|
|
38
69
|
_printU16Array: (addr: number, len: number) => void;
|
|
39
70
|
_printI32Array: (addr: number, len: number) => void;
|
|
40
71
|
_printU32Array: (addr: number, len: number) => void;
|
|
72
|
+
_printI64Array: (addr: number, len: number) => void;
|
|
73
|
+
_printU64Array: (addr: number, len: number) => void;
|
|
41
74
|
_printF32Array: (addr: number, len: number) => void;
|
|
42
75
|
_printF64Array: (addr: number, len: number) => void;
|
|
43
76
|
_printStr0: (addr: number) => void;
|
package/bridge.d.ts
CHANGED
|
@@ -1,7 +1,24 @@
|
|
|
1
|
+
import type { NumericArray } from "@thi.ng/api";
|
|
1
2
|
import type { ILogger } from "@thi.ng/logger";
|
|
2
|
-
import type { CoreAPI, IWasmAPI } from "./api.js";
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import type { BigIntArray, CoreAPI, IWasmAPI, WasmExports } from "./api.js";
|
|
4
|
+
/**
|
|
5
|
+
* The main interop API bridge between the JS host environment and a WebAssembly
|
|
6
|
+
* module. This class provides a small core API with various typed accessors and
|
|
7
|
+
* utils to exchange data (scalars, arrays, strings etc.) via the WASM module's
|
|
8
|
+
* memory.
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* All typed memory accessors are assuming the given lookup addresses are
|
|
12
|
+
* properly aligned to the corresponding primitive types (e.g. f32 values are
|
|
13
|
+
* aligned to 4 byte boundaries, f64 to 8 bytes etc.) Unaligned access is
|
|
14
|
+
* explicitly **not supported**! If you need such, please refer to other
|
|
15
|
+
* mechanisms like JS `DataView`...
|
|
16
|
+
*
|
|
17
|
+
* 64bit integers are handled via JS `BigInt` and hence require the host env to
|
|
18
|
+
* support it. No polyfill is provided.
|
|
19
|
+
*/
|
|
20
|
+
export declare class WasmBridge<T extends WasmExports = WasmExports> {
|
|
21
|
+
modules: Record<string, IWasmAPI<T>>;
|
|
5
22
|
logger: ILogger;
|
|
6
23
|
i8: Int8Array;
|
|
7
24
|
u8: Uint8Array;
|
|
@@ -9,60 +26,114 @@ export declare class WasmBridge {
|
|
|
9
26
|
u16: Uint16Array;
|
|
10
27
|
i32: Int32Array;
|
|
11
28
|
u32: Uint32Array;
|
|
29
|
+
i64: BigInt64Array;
|
|
30
|
+
u64: BigUint64Array;
|
|
12
31
|
f32: Float32Array;
|
|
13
32
|
f64: Float64Array;
|
|
14
33
|
utf8Decoder: TextDecoder;
|
|
15
34
|
utf8Encoder: TextEncoder;
|
|
35
|
+
imports: WebAssembly.Imports;
|
|
36
|
+
exports: T;
|
|
16
37
|
core: CoreAPI;
|
|
17
|
-
constructor(modules?: Record<string, IWasmAPI
|
|
18
|
-
init(mem: WebAssembly.Memory): Promise<boolean>;
|
|
38
|
+
constructor(modules?: Record<string, IWasmAPI<T>>, logger?: ILogger);
|
|
19
39
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
40
|
+
* Instantiates WASM module from given `src` (and optional provided extra
|
|
41
|
+
* imports), then automatically calls {@link WasmBridge.init} with the
|
|
42
|
+
* modules exports.
|
|
43
|
+
*
|
|
44
|
+
* @remarks
|
|
45
|
+
* If the given `src` is a `Response` or `Promise<Response>`, the module
|
|
46
|
+
* will be instantiated via `WebAssembly.instantiateStreaming()`, otherwise
|
|
47
|
+
* the non-streaming version will be used.
|
|
48
|
+
*
|
|
49
|
+
* @param src
|
|
50
|
+
* @param imports
|
|
22
51
|
*/
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
52
|
+
instantiate(src: Response | BufferSource | PromiseLike<Response | BufferSource>, imports?: WebAssembly.Imports): Promise<boolean>;
|
|
53
|
+
/**
|
|
54
|
+
* Receives the WASM module's exports, stores the for future reference and
|
|
55
|
+
* then initializes all declared bridge child API modules. Returns false if
|
|
56
|
+
* any of the module initializations failed.
|
|
57
|
+
*
|
|
58
|
+
* @param exports
|
|
59
|
+
*/
|
|
60
|
+
init(exports: T): Promise<boolean>;
|
|
61
|
+
/**
|
|
62
|
+
* Required use for WASM module instantiation to provide JS imports to the
|
|
63
|
+
* module. Returns an object of all WASM imports declared by the bridge core
|
|
64
|
+
* API and any provided bridge API modules.
|
|
65
|
+
*
|
|
66
|
+
* @remarks
|
|
67
|
+
* Since v0.4.0 each API module's imports will be in their own WASM import
|
|
68
|
+
* table, named using the same key which was assigned to the module when
|
|
69
|
+
* creating the WASM bridge.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* The following creates a bridge with a fictional `custom` API module:
|
|
73
|
+
*
|
|
74
|
+
* ```ts
|
|
75
|
+
* const bridge = new WasmBridge({ custom: new CustomAPI() });
|
|
76
|
+
*
|
|
77
|
+
* // get combined imports object
|
|
78
|
+
* bridge.getImports();
|
|
79
|
+
* {
|
|
80
|
+
* // imports defined by the core API of the bridge itself
|
|
81
|
+
* core: { ... },
|
|
82
|
+
* // imports defined by the CustomAPI module
|
|
83
|
+
* custom: { ... }
|
|
84
|
+
* }
|
|
85
|
+
* ```
|
|
86
|
+
*
|
|
87
|
+
* Any related API bindings on the WASM (Zig) side then also need to refer
|
|
88
|
+
* to these custom import sections (also see `/zig/core.zig`):
|
|
89
|
+
*
|
|
90
|
+
* ```zig
|
|
91
|
+
* pub export "custom" fn foo(x: u32) void;
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
getImports(): WebAssembly.Imports;
|
|
95
|
+
getI8(addr: number): number;
|
|
96
|
+
getU8(addr: number): number;
|
|
97
|
+
getI16(addr: number): number;
|
|
98
|
+
getU16(addr: number): number;
|
|
99
|
+
getI32(addr: number): number;
|
|
100
|
+
getU32(addr: number): number;
|
|
101
|
+
getI64(addr: number): bigint;
|
|
102
|
+
getU64(addr: number): bigint;
|
|
103
|
+
getF32(addr: number): number;
|
|
104
|
+
getF64(addr: number): number;
|
|
105
|
+
setI8(addr: number, x: number): this;
|
|
106
|
+
setU8(addr: number, x: number): this;
|
|
107
|
+
setI16(addr: number, x: number): this;
|
|
108
|
+
setU16(addr: number, x: number): this;
|
|
109
|
+
setI32(addr: number, x: number): this;
|
|
110
|
+
setU32(addr: number, x: number): this;
|
|
111
|
+
setI64(addr: number, x: bigint): this;
|
|
112
|
+
setU64(addr: number, x: bigint): this;
|
|
113
|
+
setF32(addr: number, x: number): this;
|
|
114
|
+
setF64(addr: number, x: number): this;
|
|
115
|
+
getI8Array(addr: number, len: number): Int8Array;
|
|
116
|
+
getU8Array(addr: number, len: number): Uint8Array;
|
|
117
|
+
getI16Array(addr: number, len: number): Int16Array;
|
|
118
|
+
getU16Array(addr: number, len: number): Uint16Array;
|
|
119
|
+
getI32Array(addr: number, len: number): Int32Array;
|
|
120
|
+
getU32Array(addr: number, len: number): Uint32Array;
|
|
121
|
+
getI64Array(addr: number, len: number): BigInt64Array;
|
|
122
|
+
getU64Array(addr: number, len: number): BigUint64Array;
|
|
123
|
+
getF32Array(addr: number, len: number): Float32Array;
|
|
124
|
+
getF64Array(addr: number, len: number): Float64Array;
|
|
125
|
+
setI8Array(addr: number, buf: NumericArray): this;
|
|
126
|
+
setU8Array(addr: number, buf: NumericArray): this;
|
|
127
|
+
setI16Array(addr: number, buf: NumericArray): this;
|
|
128
|
+
setU16Array(addr: number, buf: NumericArray): this;
|
|
129
|
+
setI32Array(addr: number, buf: NumericArray): this;
|
|
130
|
+
setU32Array(addr: number, buf: NumericArray): this;
|
|
131
|
+
setI64Array(addr: number, buf: BigIntArray): this;
|
|
132
|
+
setU64Array(addr: number, buf: BigIntArray): this;
|
|
133
|
+
setF32Array(addr: number, buf: NumericArray): this;
|
|
134
|
+
setF64Array(addr: number, buf: NumericArray): this;
|
|
64
135
|
getString(addr: number, len?: number): string;
|
|
65
|
-
getElementById(addr: number, len?: number): HTMLElement | null;
|
|
66
136
|
setString(str: string, addr: number, maxBytes: number, terminate?: boolean): number;
|
|
137
|
+
getElementById(addr: number, len?: number): HTMLElement;
|
|
67
138
|
}
|
|
68
139
|
//# sourceMappingURL=bridge.d.ts.map
|
package/bridge.js
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { U16, U32, U8 } from "@thi.ng/hex";
|
|
1
|
+
import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
|
|
2
|
+
import { U16, U32, U64HL, U8 } from "@thi.ng/hex";
|
|
3
3
|
import { ConsoleLogger } from "@thi.ng/logger/console";
|
|
4
|
+
const B32 = BigInt(32);
|
|
5
|
+
/**
|
|
6
|
+
* The main interop API bridge between the JS host environment and a WebAssembly
|
|
7
|
+
* module. This class provides a small core API with various typed accessors and
|
|
8
|
+
* utils to exchange data (scalars, arrays, strings etc.) via the WASM module's
|
|
9
|
+
* memory.
|
|
10
|
+
*
|
|
11
|
+
* @remarks
|
|
12
|
+
* All typed memory accessors are assuming the given lookup addresses are
|
|
13
|
+
* properly aligned to the corresponding primitive types (e.g. f32 values are
|
|
14
|
+
* aligned to 4 byte boundaries, f64 to 8 bytes etc.) Unaligned access is
|
|
15
|
+
* explicitly **not supported**! If you need such, please refer to other
|
|
16
|
+
* mechanisms like JS `DataView`...
|
|
17
|
+
*
|
|
18
|
+
* 64bit integers are handled via JS `BigInt` and hence require the host env to
|
|
19
|
+
* support it. No polyfill is provided.
|
|
20
|
+
*/
|
|
4
21
|
export class WasmBridge {
|
|
5
22
|
constructor(modules = {}, logger = new ConsoleLogger("wasm")) {
|
|
6
23
|
this.modules = modules;
|
|
@@ -17,8 +34,11 @@ export class WasmBridge {
|
|
|
17
34
|
printU16: logN,
|
|
18
35
|
printU16Hex: (x) => this.logger.debug(`0x${U16(x)}`),
|
|
19
36
|
printI32: logN,
|
|
20
|
-
printU32:
|
|
37
|
+
printU32: (x) => this.logger.debug(x >>> 0),
|
|
21
38
|
printU32Hex: (x) => this.logger.debug(`0x${U32(x)}`),
|
|
39
|
+
_printI64: (hi, lo) => this.logger.debug((BigInt(hi) << B32) | BigInt(lo)),
|
|
40
|
+
_printU64: (hi, lo) => this.logger.debug((BigInt(hi >>> 0) << B32) | BigInt(lo >>> 0)),
|
|
41
|
+
_printU64Hex: (hi, lo) => this.logger.debug(`0x${U64HL(hi, lo)}`),
|
|
22
42
|
printF32: logN,
|
|
23
43
|
printF64: logN,
|
|
24
44
|
_printI8Array: logA(this.getI8Array.bind(this)),
|
|
@@ -27,21 +47,55 @@ export class WasmBridge {
|
|
|
27
47
|
_printU16Array: logA(this.getU16Array.bind(this)),
|
|
28
48
|
_printI32Array: logA(this.getI32Array.bind(this)),
|
|
29
49
|
_printU32Array: logA(this.getU32Array.bind(this)),
|
|
50
|
+
_printI64Array: logA(this.getI64Array.bind(this)),
|
|
51
|
+
_printU64Array: logA(this.getU64Array.bind(this)),
|
|
30
52
|
_printF32Array: logA(this.getF32Array.bind(this)),
|
|
31
53
|
_printF64Array: logA(this.getF64Array.bind(this)),
|
|
32
54
|
_printStr0: (addr) => this.logger.debug(this.getString(addr, 0)),
|
|
33
55
|
_printStr: (addr, len) => this.logger.debug(this.getString(addr, len)),
|
|
34
56
|
};
|
|
35
57
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Instantiates WASM module from given `src` (and optional provided extra
|
|
60
|
+
* imports), then automatically calls {@link WasmBridge.init} with the
|
|
61
|
+
* modules exports.
|
|
62
|
+
*
|
|
63
|
+
* @remarks
|
|
64
|
+
* If the given `src` is a `Response` or `Promise<Response>`, the module
|
|
65
|
+
* will be instantiated via `WebAssembly.instantiateStreaming()`, otherwise
|
|
66
|
+
* the non-streaming version will be used.
|
|
67
|
+
*
|
|
68
|
+
* @param src
|
|
69
|
+
* @param imports
|
|
70
|
+
*/
|
|
71
|
+
async instantiate(src, imports) {
|
|
72
|
+
const $src = await src;
|
|
73
|
+
const $imports = { ...this.getImports(), ...imports };
|
|
74
|
+
const wasm = await ($src instanceof Response
|
|
75
|
+
? WebAssembly.instantiateStreaming($src, $imports)
|
|
76
|
+
: WebAssembly.instantiate($src, $imports));
|
|
77
|
+
return this.init(wasm.instance.exports);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Receives the WASM module's exports, stores the for future reference and
|
|
81
|
+
* then initializes all declared bridge child API modules. Returns false if
|
|
82
|
+
* any of the module initializations failed.
|
|
83
|
+
*
|
|
84
|
+
* @param exports
|
|
85
|
+
*/
|
|
86
|
+
async init(exports) {
|
|
87
|
+
this.exports = exports;
|
|
88
|
+
const buf = exports.memory.buffer;
|
|
89
|
+
this.i8 = new Int8Array(buf);
|
|
90
|
+
this.u8 = new Uint8Array(buf);
|
|
91
|
+
this.i16 = new Int16Array(buf);
|
|
92
|
+
this.u16 = new Uint16Array(buf);
|
|
93
|
+
this.i32 = new Int32Array(buf);
|
|
94
|
+
this.u32 = new Uint32Array(buf);
|
|
95
|
+
this.i64 = new BigInt64Array(buf);
|
|
96
|
+
this.u64 = new BigUint64Array(buf);
|
|
97
|
+
this.f32 = new Float32Array(buf);
|
|
98
|
+
this.f64 = new Float64Array(buf);
|
|
45
99
|
for (let id in this.modules) {
|
|
46
100
|
this.logger.debug(`initializing API module: ${id}`);
|
|
47
101
|
const status = await this.modules[id].init(this);
|
|
@@ -51,87 +105,217 @@ export class WasmBridge {
|
|
|
51
105
|
return true;
|
|
52
106
|
}
|
|
53
107
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
108
|
+
* Required use for WASM module instantiation to provide JS imports to the
|
|
109
|
+
* module. Returns an object of all WASM imports declared by the bridge core
|
|
110
|
+
* API and any provided bridge API modules.
|
|
111
|
+
*
|
|
112
|
+
* @remarks
|
|
113
|
+
* Since v0.4.0 each API module's imports will be in their own WASM import
|
|
114
|
+
* table, named using the same key which was assigned to the module when
|
|
115
|
+
* creating the WASM bridge.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* The following creates a bridge with a fictional `custom` API module:
|
|
119
|
+
*
|
|
120
|
+
* ```ts
|
|
121
|
+
* const bridge = new WasmBridge({ custom: new CustomAPI() });
|
|
122
|
+
*
|
|
123
|
+
* // get combined imports object
|
|
124
|
+
* bridge.getImports();
|
|
125
|
+
* {
|
|
126
|
+
* // imports defined by the core API of the bridge itself
|
|
127
|
+
* core: { ... },
|
|
128
|
+
* // imports defined by the CustomAPI module
|
|
129
|
+
* custom: { ... }
|
|
130
|
+
* }
|
|
131
|
+
* ```
|
|
132
|
+
*
|
|
133
|
+
* Any related API bindings on the WASM (Zig) side then also need to refer
|
|
134
|
+
* to these custom import sections (also see `/zig/core.zig`):
|
|
135
|
+
*
|
|
136
|
+
* ```zig
|
|
137
|
+
* pub export "custom" fn foo(x: u32) void;
|
|
138
|
+
* ```
|
|
56
139
|
*/
|
|
57
140
|
getImports() {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
141
|
+
if (!this.imports) {
|
|
142
|
+
this.imports = { core: this.core };
|
|
143
|
+
for (let id in this.modules) {
|
|
144
|
+
if (this.imports[id] !== undefined) {
|
|
145
|
+
illegalArgs(`attempt to redeclare API module ${id}`);
|
|
146
|
+
}
|
|
147
|
+
this.imports[id] = this.modules[id].getImports();
|
|
148
|
+
}
|
|
61
149
|
}
|
|
62
|
-
return
|
|
150
|
+
return this.imports;
|
|
151
|
+
}
|
|
152
|
+
getI8(addr) {
|
|
153
|
+
return this.i8[addr];
|
|
154
|
+
}
|
|
155
|
+
getU8(addr) {
|
|
156
|
+
return this.u8[addr];
|
|
157
|
+
}
|
|
158
|
+
getI16(addr) {
|
|
159
|
+
return this.i16[addr >> 1];
|
|
160
|
+
}
|
|
161
|
+
getU16(addr) {
|
|
162
|
+
return this.u16[addr >> 1];
|
|
163
|
+
}
|
|
164
|
+
getI32(addr) {
|
|
165
|
+
return this.i32[addr >> 2];
|
|
166
|
+
}
|
|
167
|
+
getU32(addr) {
|
|
168
|
+
return this.u32[addr >> 2];
|
|
169
|
+
}
|
|
170
|
+
getI64(addr) {
|
|
171
|
+
return this.i64[addr >> 3];
|
|
172
|
+
}
|
|
173
|
+
getU64(addr) {
|
|
174
|
+
return this.u64[addr >> 3];
|
|
175
|
+
}
|
|
176
|
+
getF32(addr) {
|
|
177
|
+
return this.f32[addr >> 2];
|
|
178
|
+
}
|
|
179
|
+
getF64(addr) {
|
|
180
|
+
return this.f64[addr >> 3];
|
|
181
|
+
}
|
|
182
|
+
setI8(addr, x) {
|
|
183
|
+
this.i8[addr] = x;
|
|
184
|
+
return this;
|
|
185
|
+
}
|
|
186
|
+
setU8(addr, x) {
|
|
187
|
+
this.u8[addr] = x;
|
|
188
|
+
return this;
|
|
189
|
+
}
|
|
190
|
+
setI16(addr, x) {
|
|
191
|
+
this.i16[addr >> 1] = x;
|
|
192
|
+
return this;
|
|
193
|
+
}
|
|
194
|
+
setU16(addr, x) {
|
|
195
|
+
this.u16[addr >> 1] = x;
|
|
196
|
+
return this;
|
|
197
|
+
}
|
|
198
|
+
setI32(addr, x) {
|
|
199
|
+
this.i32[addr >> 2] = x;
|
|
200
|
+
return this;
|
|
63
201
|
}
|
|
64
|
-
|
|
65
|
-
|
|
202
|
+
setU32(addr, x) {
|
|
203
|
+
this.u32[addr >> 2] = x;
|
|
204
|
+
return this;
|
|
66
205
|
}
|
|
67
|
-
|
|
68
|
-
|
|
206
|
+
setI64(addr, x) {
|
|
207
|
+
this.i64[addr >> 3] = x;
|
|
208
|
+
return this;
|
|
69
209
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return this
|
|
210
|
+
setU64(addr, x) {
|
|
211
|
+
this.u64[addr >> 3] = x;
|
|
212
|
+
return this;
|
|
73
213
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return this
|
|
214
|
+
setF32(addr, x) {
|
|
215
|
+
this.f32[addr >> 2] = x;
|
|
216
|
+
return this;
|
|
77
217
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return this
|
|
218
|
+
setF64(addr, x) {
|
|
219
|
+
this.f64[addr >> 3] = x;
|
|
220
|
+
return this;
|
|
81
221
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return this.u32.subarray(ptr, ptr + len);
|
|
222
|
+
getI8Array(addr, len) {
|
|
223
|
+
return this.i8.subarray(addr, addr + len);
|
|
85
224
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
return this.f32.subarray(ptr, ptr + len);
|
|
225
|
+
getU8Array(addr, len) {
|
|
226
|
+
return this.u8.subarray(addr, addr + len);
|
|
89
227
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
return this.
|
|
228
|
+
getI16Array(addr, len) {
|
|
229
|
+
addr >>= 1;
|
|
230
|
+
return this.i16.subarray(addr, addr + len);
|
|
93
231
|
}
|
|
94
|
-
|
|
95
|
-
|
|
232
|
+
getU16Array(addr, len) {
|
|
233
|
+
addr >>= 1;
|
|
234
|
+
return this.u16.subarray(addr, addr + len);
|
|
96
235
|
}
|
|
97
|
-
|
|
98
|
-
|
|
236
|
+
getI32Array(addr, len) {
|
|
237
|
+
addr >>= 2;
|
|
238
|
+
return this.i32.subarray(addr, addr + len);
|
|
99
239
|
}
|
|
100
|
-
|
|
101
|
-
|
|
240
|
+
getU32Array(addr, len) {
|
|
241
|
+
addr >>= 2;
|
|
242
|
+
return this.u32.subarray(addr, addr + len);
|
|
102
243
|
}
|
|
103
|
-
|
|
104
|
-
|
|
244
|
+
getI64Array(addr, len) {
|
|
245
|
+
addr >>= 3;
|
|
246
|
+
return this.i64.subarray(addr, addr + len);
|
|
105
247
|
}
|
|
106
|
-
|
|
107
|
-
|
|
248
|
+
getU64Array(addr, len) {
|
|
249
|
+
addr >>= 3;
|
|
250
|
+
return this.u64.subarray(addr, addr + len);
|
|
108
251
|
}
|
|
109
|
-
|
|
110
|
-
|
|
252
|
+
getF32Array(addr, len) {
|
|
253
|
+
addr >>= 2;
|
|
254
|
+
return this.f32.subarray(addr, addr + len);
|
|
111
255
|
}
|
|
112
|
-
|
|
113
|
-
|
|
256
|
+
getF64Array(addr, len) {
|
|
257
|
+
addr >>= 3;
|
|
258
|
+
return this.f64.subarray(addr, addr + len);
|
|
114
259
|
}
|
|
115
|
-
|
|
116
|
-
|
|
260
|
+
setI8Array(addr, buf) {
|
|
261
|
+
this.i8.set(buf, addr);
|
|
262
|
+
return this;
|
|
263
|
+
}
|
|
264
|
+
setU8Array(addr, buf) {
|
|
265
|
+
this.u8.set(buf, addr);
|
|
266
|
+
return this;
|
|
267
|
+
}
|
|
268
|
+
setI16Array(addr, buf) {
|
|
269
|
+
this.i16.set(buf, addr >> 1);
|
|
270
|
+
return this;
|
|
271
|
+
}
|
|
272
|
+
setU16Array(addr, buf) {
|
|
273
|
+
this.u16.set(buf, addr >> 1);
|
|
274
|
+
return this;
|
|
275
|
+
}
|
|
276
|
+
setI32Array(addr, buf) {
|
|
277
|
+
this.i32.set(buf, addr >> 2);
|
|
278
|
+
return this;
|
|
279
|
+
}
|
|
280
|
+
setU32Array(addr, buf) {
|
|
281
|
+
this.u32.set(buf, addr >> 2);
|
|
282
|
+
return this;
|
|
283
|
+
}
|
|
284
|
+
setI64Array(addr, buf) {
|
|
285
|
+
this.i64.set(buf, addr >> 3);
|
|
286
|
+
return this;
|
|
287
|
+
}
|
|
288
|
+
setU64Array(addr, buf) {
|
|
289
|
+
this.u64.set(buf, addr >> 3);
|
|
290
|
+
return this;
|
|
291
|
+
}
|
|
292
|
+
setF32Array(addr, buf) {
|
|
293
|
+
this.f32.set(buf, addr >> 2);
|
|
294
|
+
return this;
|
|
295
|
+
}
|
|
296
|
+
setF64Array(addr, buf) {
|
|
297
|
+
this.f64.set(buf, addr >> 3);
|
|
298
|
+
return this;
|
|
117
299
|
}
|
|
118
300
|
getString(addr, len = 0) {
|
|
119
301
|
return this.utf8Decoder.decode(this.u8.subarray(addr, len > 0 ? addr + len : this.u8.indexOf(0, addr)));
|
|
120
302
|
}
|
|
121
|
-
getElementById(addr, len = 0) {
|
|
122
|
-
const id = this.getString(addr, len);
|
|
123
|
-
const el = document.getElementById(id);
|
|
124
|
-
assert(!!el, `missing DOM element #${id}`);
|
|
125
|
-
return el;
|
|
126
|
-
}
|
|
127
303
|
setString(str, addr, maxBytes, terminate = true) {
|
|
128
304
|
maxBytes = Math.min(maxBytes, this.u8.length - addr);
|
|
129
305
|
const len = this.utf8Encoder.encodeInto(str, this.u8.subarray(addr, addr + maxBytes)).written;
|
|
130
|
-
|
|
306
|
+
if (len != null && len < maxBytes + (terminate ? 0 : 1)) {
|
|
307
|
+
illegalArgs(`error writing string to 0x${U32(addr)}`);
|
|
308
|
+
}
|
|
131
309
|
if (terminate) {
|
|
132
310
|
this.u8[addr + len] = 0;
|
|
133
311
|
return len + 1;
|
|
134
312
|
}
|
|
135
313
|
return len;
|
|
136
314
|
}
|
|
315
|
+
getElementById(addr, len = 0) {
|
|
316
|
+
const id = this.getString(addr, len);
|
|
317
|
+
const el = document.getElementById(id);
|
|
318
|
+
el == null && illegalArgs(`missing DOM element #${id}`);
|
|
319
|
+
return el;
|
|
320
|
+
}
|
|
137
321
|
}
|
package/dev/custom.zig
CHANGED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
const js = @import("wasmapi");
|
|
2
|
+
const std = @import("std");
|
|
3
|
+
|
|
4
|
+
const Foo = struct {
|
|
5
|
+
pos: [2]f32,
|
|
6
|
+
col: [3]f32,
|
|
7
|
+
speed: u8,
|
|
8
|
+
acc: u16,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
fn writeU32(buf: [*]u8, i: u32, x: u32) void {
|
|
12
|
+
buf[i] = @intCast(u8, x & 0xff);
|
|
13
|
+
buf[i + 1] = @intCast(u8, x >> 8 & 0xff);
|
|
14
|
+
buf[i + 2] = @intCast(u8, x >> 16 & 0xff);
|
|
15
|
+
buf[i + 3] = @intCast(u8, x >> 24);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const FieldType = enum(u8) {
|
|
19
|
+
I8,
|
|
20
|
+
U8,
|
|
21
|
+
I16,
|
|
22
|
+
U16,
|
|
23
|
+
I32,
|
|
24
|
+
U32,
|
|
25
|
+
F32,
|
|
26
|
+
F64,
|
|
27
|
+
|
|
28
|
+
pub fn fromTypeInfo(comptime info: std.builtin.TypeInfo) FieldType {
|
|
29
|
+
if (info == .Int) {
|
|
30
|
+
const bits = info.Int.bits;
|
|
31
|
+
if (!(bits == 8 or bits == 16 or bits == 32)) {
|
|
32
|
+
@compileError("unsupported int type (only 8, 16, 32)");
|
|
33
|
+
}
|
|
34
|
+
if (info.Int.signedness == .signed) {
|
|
35
|
+
return switch (bits) {
|
|
36
|
+
8 => .I8,
|
|
37
|
+
16 => .I16,
|
|
38
|
+
32 => .I32,
|
|
39
|
+
else => unreachable,
|
|
40
|
+
};
|
|
41
|
+
} else {
|
|
42
|
+
return switch (bits) {
|
|
43
|
+
8 => .U8,
|
|
44
|
+
16 => .U16,
|
|
45
|
+
32 => .U32,
|
|
46
|
+
else => unreachable,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
} else if (info == .Float) {
|
|
50
|
+
return switch (info.Float.bits) {
|
|
51
|
+
32 => .F32,
|
|
52
|
+
64 => .F64,
|
|
53
|
+
else => @compileError("unsupported float type (only f32, f64)"),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
@compileError("unsupported field type");
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// int
|
|
61
|
+
// float
|
|
62
|
+
// ptr
|
|
63
|
+
// array
|
|
64
|
+
// slice
|
|
65
|
+
|
|
66
|
+
// 8
|
|
67
|
+
// 16
|
|
68
|
+
// 32
|
|
69
|
+
// 64
|
|
70
|
+
|
|
71
|
+
const Field = packed struct {
|
|
72
|
+
name: [15]u8 = [_]u8{0} ** 15,
|
|
73
|
+
tag: FieldType = .U8,
|
|
74
|
+
offset: u32,
|
|
75
|
+
len: u32 = 0,
|
|
76
|
+
|
|
77
|
+
pub fn fromTypeInfo(comptime T: type, comptime field: std.builtin.TypeInfo.StructField) Field {
|
|
78
|
+
const finfo = @typeInfo(field.field_type);
|
|
79
|
+
var ftype: FieldType = .U8;
|
|
80
|
+
var flen = 0;
|
|
81
|
+
if (!(finfo == .Int or finfo == .Float or finfo == .Array)) {
|
|
82
|
+
@compileError("unsupported field type: " ++ @typeName(field.field_type));
|
|
83
|
+
}
|
|
84
|
+
if (finfo == .Array) {
|
|
85
|
+
const cinfo = @typeInfo(finfo.Array.child);
|
|
86
|
+
if (!(cinfo == .Int or cinfo == .Float)) {
|
|
87
|
+
@compileError("unsupported field array type: " ++ @typeName(field.field_type));
|
|
88
|
+
}
|
|
89
|
+
ftype = FieldType.fromTypeInfo(cinfo);
|
|
90
|
+
flen = finfo.Array.len;
|
|
91
|
+
} else {
|
|
92
|
+
ftype = FieldType.fromTypeInfo(finfo);
|
|
93
|
+
}
|
|
94
|
+
var res: Field = .{
|
|
95
|
+
.tag = ftype,
|
|
96
|
+
.offset = @offsetOf(T, field.name),
|
|
97
|
+
.len = flen,
|
|
98
|
+
};
|
|
99
|
+
const len = @minimum(14, field.name.len);
|
|
100
|
+
std.mem.copy(u8, res.name[0..len], field.name[0..len]);
|
|
101
|
+
return res;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
fn writeTypeInfo(comptime T: type) []u8 {
|
|
106
|
+
const fields = @typeInfo(T).Struct.fields;
|
|
107
|
+
const fsize = @sizeOf(Field);
|
|
108
|
+
var buf: [fields.len * fsize + 4]u8 = undefined;
|
|
109
|
+
var i = 4;
|
|
110
|
+
writeU32(&buf, 0, fields.len);
|
|
111
|
+
for (fields) |field| {
|
|
112
|
+
var f = Field.fromTypeInfo(T, field);
|
|
113
|
+
std.mem.copy(
|
|
114
|
+
u8,
|
|
115
|
+
buf[i .. i + fsize],
|
|
116
|
+
@ptrCast(*[fsize]u8, &f)[0..],
|
|
117
|
+
);
|
|
118
|
+
i += fsize;
|
|
119
|
+
}
|
|
120
|
+
return buf[0..];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export var Foo__info = writeTypeInfo(Foo);
|
|
124
|
+
export var Bar__info = writeTypeInfo(struct { x: i16, y: i16 });
|
|
125
|
+
|
|
126
|
+
export var U64: u64 = 0xdecafbadcafebabe;
|
|
127
|
+
export var I64: i64 = -0x8000000000000000;
|
|
128
|
+
|
|
129
|
+
export fn foo() void {
|
|
130
|
+
js.printI64(I64);
|
|
131
|
+
js.printU64(U64);
|
|
132
|
+
js.printI64Array(&[_]i64{ I64, I64 });
|
|
133
|
+
js.printU32(@truncate(u32, U64 >> 32));
|
|
134
|
+
js.printU32Hex(@truncate(u32, U64 >> 32));
|
|
135
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thi.ng/wasm-api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Modular, extensible API bridge and generic glue code between JS & WebAssembly",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -35,14 +35,14 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@thi.ng/api": "^8.3.9",
|
|
38
|
-
"@thi.ng/errors": "^2.1.
|
|
38
|
+
"@thi.ng/errors": "^2.1.10",
|
|
39
39
|
"@thi.ng/hex": "^2.1.9",
|
|
40
|
-
"@thi.ng/idgen": "^2.1.
|
|
41
|
-
"@thi.ng/logger": "^1.
|
|
40
|
+
"@thi.ng/idgen": "^2.1.10",
|
|
41
|
+
"@thi.ng/logger": "^1.2.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@microsoft/api-extractor": "^7.25.0",
|
|
45
|
-
"@thi.ng/testament": "^0.2.
|
|
45
|
+
"@thi.ng/testament": "^0.2.11",
|
|
46
46
|
"rimraf": "^3.0.2",
|
|
47
47
|
"tools": "^0.0.1",
|
|
48
48
|
"typedoc": "^0.22.17",
|
|
@@ -92,5 +92,5 @@
|
|
|
92
92
|
"status": "alpha",
|
|
93
93
|
"year": 2022
|
|
94
94
|
},
|
|
95
|
-
"gitHead": "
|
|
95
|
+
"gitHead": "0eeb5054111cea51f4714b013dda8700ade3cd54\n"
|
|
96
96
|
}
|
package/test/custom.zig
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Import JS core API
|
|
2
|
+
const js = @import("wasmapi");
|
|
3
|
+
|
|
4
|
+
/// Fill vec2 with random values
|
|
5
|
+
extern "custom" fn setVec2(addr: usize) void;
|
|
6
|
+
|
|
7
|
+
export fn test_setVec2() void {
|
|
8
|
+
var foo = [2]f32{ 0, 0 };
|
|
9
|
+
js.printF32Array(foo[0..]);
|
|
10
|
+
setVec2(@ptrToInt(&foo));
|
|
11
|
+
js.printF32Array(foo[0..]);
|
|
12
|
+
}
|
package/zig/core.zig
CHANGED
|
@@ -1,27 +1,51 @@
|
|
|
1
1
|
//! JavaScript externals for https://thi.ng/wasm-api
|
|
2
2
|
|
|
3
3
|
/// Prints number using configured JS logger
|
|
4
|
-
pub extern fn printI8(x: i8) void;
|
|
4
|
+
pub extern "core" fn printI8(x: i8) void;
|
|
5
5
|
/// Prints number using configured JS logger
|
|
6
|
-
pub extern fn printU8(x: u8) void;
|
|
6
|
+
pub extern "core" fn printU8(x: u8) void;
|
|
7
7
|
/// Prints hex number using configured JS logger
|
|
8
|
-
pub extern fn printU8Hex(x: u8) void;
|
|
8
|
+
pub extern "core" fn printU8Hex(x: u8) void;
|
|
9
|
+
|
|
9
10
|
/// Prints number using configured JS logger
|
|
10
|
-
pub extern fn printI16(x: i16) void;
|
|
11
|
+
pub extern "core" fn printI16(x: i16) void;
|
|
11
12
|
/// Prints number using configured JS logger
|
|
12
|
-
pub extern fn printU16(x: u16) void;
|
|
13
|
+
pub extern "core" fn printU16(x: u16) void;
|
|
13
14
|
/// Prints hex number using configured JS logger
|
|
14
|
-
pub extern fn printU16Hex(x: u16) void;
|
|
15
|
+
pub extern "core" fn printU16Hex(x: u16) void;
|
|
16
|
+
|
|
15
17
|
/// Prints number using configured JS logger
|
|
16
|
-
pub extern fn printI32(x: i32) void;
|
|
18
|
+
pub extern "core" fn printI32(x: i32) void;
|
|
17
19
|
/// Prints number using configured JS logger
|
|
18
|
-
pub extern fn printU32(x: u32) void;
|
|
20
|
+
pub extern "core" fn printU32(x: u32) void;
|
|
19
21
|
/// Prints hex number using configured JS logger
|
|
20
|
-
pub extern fn printU32Hex(x: u32) void;
|
|
22
|
+
pub extern "core" fn printU32Hex(x: u32) void;
|
|
23
|
+
|
|
24
|
+
/// Prints decomposed i64 number using configured JS logger
|
|
25
|
+
pub extern "core" fn _printI64(hi: i32, lo: i32) void;
|
|
26
|
+
/// Convenience wrapper for _printI64(), accepting an i64
|
|
27
|
+
pub fn printI64(x: i64) void {
|
|
28
|
+
_printI64(@truncate(i32, x >> 32), @truncate(i32, x));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// Prints decomposed u64 number using configured JS logger
|
|
32
|
+
pub extern "core" fn _printU64(hi: u32, lo: u32) void;
|
|
33
|
+
/// Convenience wrapper for _printU64(), accepting an u64
|
|
34
|
+
pub fn printU64(x: u64) void {
|
|
35
|
+
_printU64(@truncate(u32, x >> 32), @truncate(u32, x));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// Prints decomposed u64 hex number using configured JS logger
|
|
39
|
+
pub extern "core" fn _printU64Hex(hi: u32, lo: u32) void;
|
|
40
|
+
/// Convenience wrapper for _printU64Hex(), accepting an u64
|
|
41
|
+
pub fn printU64Hex(x: u64) void {
|
|
42
|
+
_printU64Hex(@truncate(u32, x >> 32), @truncate(u32, x));
|
|
43
|
+
}
|
|
44
|
+
|
|
21
45
|
/// Prints number using configured JS logger
|
|
22
|
-
pub extern fn printF32(x: f32) void;
|
|
46
|
+
pub extern "core" fn printF32(x: f32) void;
|
|
23
47
|
/// Prints number using configured JS logger
|
|
24
|
-
pub extern fn printF64(x: f64) void;
|
|
48
|
+
pub extern "core" fn printF64(x: f64) void;
|
|
25
49
|
|
|
26
50
|
/// Prints pointer as hex number using configured JS logger
|
|
27
51
|
pub fn printPtr(ptr: *const anyopaque) void {
|
|
@@ -29,21 +53,25 @@ pub fn printPtr(ptr: *const anyopaque) void {
|
|
|
29
53
|
}
|
|
30
54
|
|
|
31
55
|
/// Prints number array using configured JS logger
|
|
32
|
-
pub extern fn _printI8Array(addr: usize, len: usize) void;
|
|
56
|
+
pub extern "core" fn _printI8Array(addr: usize, len: usize) void;
|
|
57
|
+
/// Prints number array using configured JS logger
|
|
58
|
+
pub extern "core" fn _printU8Array(addr: usize, len: usize) void;
|
|
33
59
|
/// Prints number array using configured JS logger
|
|
34
|
-
pub extern fn
|
|
60
|
+
pub extern "core" fn _printI16Array(addr: usize, len: usize) void;
|
|
35
61
|
/// Prints number array using configured JS logger
|
|
36
|
-
pub extern fn
|
|
62
|
+
pub extern "core" fn _printU16Array(addr: usize, len: usize) void;
|
|
37
63
|
/// Prints number array using configured JS logger
|
|
38
|
-
pub extern fn
|
|
64
|
+
pub extern "core" fn _printI32Array(addr: usize, len: usize) void;
|
|
39
65
|
/// Prints number array using configured JS logger
|
|
40
|
-
pub extern fn
|
|
66
|
+
pub extern "core" fn _printU32Array(addr: usize, len: usize) void;
|
|
41
67
|
/// Prints number array using configured JS logger
|
|
42
|
-
pub extern fn
|
|
68
|
+
pub extern "core" fn _printI64Array(addr: usize, len: usize) void;
|
|
43
69
|
/// Prints number array using configured JS logger
|
|
44
|
-
pub extern fn
|
|
70
|
+
pub extern "core" fn _printU64Array(addr: usize, len: usize) void;
|
|
45
71
|
/// Prints number array using configured JS logger
|
|
46
|
-
pub extern fn
|
|
72
|
+
pub extern "core" fn _printF32Array(addr: usize, len: usize) void;
|
|
73
|
+
/// Prints number array using configured JS logger
|
|
74
|
+
pub extern "core" fn _printF64Array(addr: usize, len: usize) void;
|
|
47
75
|
|
|
48
76
|
/// Prints number array using configured JS logger
|
|
49
77
|
pub fn printI8Array(buf: []const i8) void {
|
|
@@ -70,6 +98,14 @@ pub fn printU32Array(buf: []const u32) void {
|
|
|
70
98
|
_printU32Array(@ptrToInt(buf.ptr), buf.len);
|
|
71
99
|
}
|
|
72
100
|
/// Prints number array using configured JS logger
|
|
101
|
+
pub fn printI64Array(buf: []const i64) void {
|
|
102
|
+
_printI64Array(@ptrToInt(buf.ptr), buf.len);
|
|
103
|
+
}
|
|
104
|
+
/// Prints number array using configured JS logger
|
|
105
|
+
pub fn printU64Array(buf: []const u64) void {
|
|
106
|
+
_printU64Array(@ptrToInt(buf.ptr), buf.len);
|
|
107
|
+
}
|
|
108
|
+
/// Prints number array using configured JS logger
|
|
73
109
|
pub fn printF32Array(buf: []const f32) void {
|
|
74
110
|
_printF32Array(@ptrToInt(buf.ptr), buf.len);
|
|
75
111
|
}
|
|
@@ -79,10 +115,10 @@ pub fn printF64Array(buf: []const f64) void {
|
|
|
79
115
|
}
|
|
80
116
|
|
|
81
117
|
/// Prints a zero-terminated string using configured JS logger
|
|
82
|
-
extern fn _printStr0(addr: usize) void;
|
|
118
|
+
pub extern "core" fn _printStr0(addr: usize) void;
|
|
83
119
|
/// Prints a string of given length using configured JS logger
|
|
84
|
-
extern fn _printStr(addr: usize, len: usize) void;
|
|
85
|
-
|
|
120
|
+
pub extern "core" fn _printStr(addr: usize, len: usize) void;
|
|
121
|
+
/// Convenience wrapper for _printStr, accepting a slice as arg
|
|
86
122
|
pub fn printStr(msg: []const u8) void {
|
|
87
123
|
_printStr(@ptrToInt(msg.ptr), msg.len);
|
|
88
124
|
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
const std = @import("std");
|
|
2
|
-
/// Zig version. When writing code that supports multiple versions of Zig, prefer
|
|
3
|
-
/// feature detection (i.e. with `@hasDecl` or `@hasField`) over version checks.
|
|
4
|
-
pub const zig_version = std.SemanticVersion.parse("0.10.0-dev.3034+6fab6c3e4") catch unreachable;
|
|
5
|
-
pub const zig_backend = std.builtin.CompilerBackend.stage1;
|
|
6
|
-
/// Temporary until self-hosted supports the `cpu.arch` value.
|
|
7
|
-
pub const stage2_arch: std.Target.Cpu.Arch = .wasm32;
|
|
8
|
-
|
|
9
|
-
pub const output_mode = std.builtin.OutputMode.Lib;
|
|
10
|
-
pub const link_mode = std.builtin.LinkMode.Dynamic;
|
|
11
|
-
pub const is_test = false;
|
|
12
|
-
pub const single_threaded = true;
|
|
13
|
-
pub const abi = std.Target.Abi.musl;
|
|
14
|
-
pub const cpu: std.Target.Cpu = .{
|
|
15
|
-
.arch = .wasm32,
|
|
16
|
-
.model = &std.Target.wasm.cpu.generic,
|
|
17
|
-
.features = std.Target.wasm.featureSet(&[_]std.Target.wasm.Feature{
|
|
18
|
-
}),
|
|
19
|
-
};
|
|
20
|
-
pub const os = std.Target.Os{
|
|
21
|
-
.tag = .freestanding,
|
|
22
|
-
.version_range = .{ .none = {} },
|
|
23
|
-
};
|
|
24
|
-
pub const target = std.Target{
|
|
25
|
-
.cpu = cpu,
|
|
26
|
-
.os = os,
|
|
27
|
-
.abi = abi,
|
|
28
|
-
};
|
|
29
|
-
pub const object_format = std.Target.ObjectFormat.wasm;
|
|
30
|
-
pub const mode = std.builtin.Mode.ReleaseSmall;
|
|
31
|
-
pub const link_libc = false;
|
|
32
|
-
pub const link_libcpp = false;
|
|
33
|
-
pub const have_error_return_tracing = false;
|
|
34
|
-
pub const valgrind_support = false;
|
|
35
|
-
pub const sanitize_thread = false;
|
|
36
|
-
pub const position_independent_code = true;
|
|
37
|
-
pub const position_independent_executable = false;
|
|
38
|
-
pub const strip_debug_info = true;
|
|
39
|
-
pub const code_model = std.builtin.CodeModel.default;
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
const std = @import("std");
|
|
2
|
-
/// Zig version. When writing code that supports multiple versions of Zig, prefer
|
|
3
|
-
/// feature detection (i.e. with `@hasDecl` or `@hasField`) over version checks.
|
|
4
|
-
pub const zig_version = std.SemanticVersion.parse("0.10.0-dev.3034+6fab6c3e4") catch unreachable;
|
|
5
|
-
pub const zig_backend = std.builtin.CompilerBackend.stage1;
|
|
6
|
-
/// Temporary until self-hosted supports the `cpu.arch` value.
|
|
7
|
-
pub const stage2_arch: std.Target.Cpu.Arch = .wasm32;
|
|
8
|
-
|
|
9
|
-
pub const output_mode = std.builtin.OutputMode.Lib;
|
|
10
|
-
pub const link_mode = std.builtin.LinkMode.Dynamic;
|
|
11
|
-
pub const is_test = false;
|
|
12
|
-
pub const single_threaded = true;
|
|
13
|
-
pub const abi = std.Target.Abi.musl;
|
|
14
|
-
pub const cpu: std.Target.Cpu = .{
|
|
15
|
-
.arch = .wasm32,
|
|
16
|
-
.model = &std.Target.wasm.cpu.generic,
|
|
17
|
-
.features = std.Target.wasm.featureSet(&[_]std.Target.wasm.Feature{
|
|
18
|
-
}),
|
|
19
|
-
};
|
|
20
|
-
pub const os = std.Target.Os{
|
|
21
|
-
.tag = .freestanding,
|
|
22
|
-
.version_range = .{ .none = {} },
|
|
23
|
-
};
|
|
24
|
-
pub const target = std.Target{
|
|
25
|
-
.cpu = cpu,
|
|
26
|
-
.os = os,
|
|
27
|
-
.abi = abi,
|
|
28
|
-
};
|
|
29
|
-
pub const object_format = std.Target.ObjectFormat.wasm;
|
|
30
|
-
pub const mode = std.builtin.Mode.ReleaseSmall;
|
|
31
|
-
pub const link_libc = false;
|
|
32
|
-
pub const link_libcpp = false;
|
|
33
|
-
pub const have_error_return_tracing = false;
|
|
34
|
-
pub const valgrind_support = false;
|
|
35
|
-
pub const sanitize_thread = false;
|
|
36
|
-
pub const position_independent_code = true;
|
|
37
|
-
pub const position_independent_executable = false;
|
|
38
|
-
pub const strip_debug_info = true;
|
|
39
|
-
pub const code_model = std.builtin.CodeModel.default;
|