@thi.ng/wasm-api 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -1
- package/api.d.ts +26 -2
- package/bridge.d.ts +40 -2
- package/bridge.js +57 -11
- package/dev/hello.zig +5 -0
- package/package.json +4 -3
- package/test/custom.zig +4 -0
- package/test/zig-cache/o/117d44467ded6ce3864cde3c180ef73c/builtin.zig +39 -0
- package/zig/core.zig +37 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
-
- **Last updated**: 2022-08-
|
|
3
|
+
- **Last updated**: 2022-08-08T22:36:17Z
|
|
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,17 @@ 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.5.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/wasm-api@0.5.0) (2022-08-08)
|
|
13
|
+
|
|
14
|
+
#### 🚀 Features
|
|
15
|
+
|
|
16
|
+
- add memory allocation ([980c1f2](https://github.com/thi-ng/umbrella/commit/980c1f2))
|
|
17
|
+
- add WasmBridge.allocate()/free()
|
|
18
|
+
- add WasmBridge.growMemory()
|
|
19
|
+
- extract WasmBridge.ensureMemory()
|
|
20
|
+
- update WasmExports
|
|
21
|
+
- update Zig bindings (configurable allocator, GPA as default)
|
|
22
|
+
|
|
12
23
|
## [0.4.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/wasm-api@0.4.0) (2022-08-07)
|
|
13
24
|
|
|
14
25
|
#### 🚀 Features
|
package/api.d.ts
CHANGED
|
@@ -27,7 +27,8 @@ export interface IWasmAPI<T extends WasmExports = WasmExports> {
|
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
29
29
|
* Base interface of exports declared by the WASM module. At the very least, the
|
|
30
|
-
* module needs to export its memory
|
|
30
|
+
* module needs to export its memory and the functions defined in this
|
|
31
|
+
* interface.
|
|
31
32
|
*
|
|
32
33
|
* @remarks
|
|
33
34
|
* This interface is supposed to be extended with the concrete exports defined
|
|
@@ -39,9 +40,32 @@ export interface IWasmAPI<T extends WasmExports = WasmExports> {
|
|
|
39
40
|
export interface WasmExports {
|
|
40
41
|
/**
|
|
41
42
|
* The WASM module's linear memory buffer. The `WasmBridge` automatically
|
|
42
|
-
* creates various typed views of that memory.
|
|
43
|
+
* creates various typed views of that memory (i.e. u8, u16, u32, f32 etc.)
|
|
43
44
|
*/
|
|
44
45
|
memory: WebAssembly.Memory;
|
|
46
|
+
/**
|
|
47
|
+
* Implementation specific memory allocation function (likely heap-based).
|
|
48
|
+
* If successful returns address of new memory block, or zero if
|
|
49
|
+
* unsuccessful.
|
|
50
|
+
*
|
|
51
|
+
* @remarks
|
|
52
|
+
* In the supplied Zig bindings (see `/zig/core.zig`), by default this is
|
|
53
|
+
* using the `std.heap.GeneralPurposeAllocator` (which also automatically
|
|
54
|
+
* handles growing the WASM memory), however as mentioned the underlying
|
|
55
|
+
* mechanism is purposefully left to the actual WASM-side implementation. In
|
|
56
|
+
* a C program, this would likely use `malloc()` or similar...
|
|
57
|
+
*/
|
|
58
|
+
_wasm_allocate(numBytes: number): number;
|
|
59
|
+
/**
|
|
60
|
+
* Implementation specific function to free a previously allocated chunk of
|
|
61
|
+
* of WASM memory (allocated via {@link WasmExports._wasm_allocate}).
|
|
62
|
+
*
|
|
63
|
+
* @remarks
|
|
64
|
+
* In the supplied Zig bindings (/zig/core.zig) this is a no-op (currently).
|
|
65
|
+
*
|
|
66
|
+
* @param addr
|
|
67
|
+
*/
|
|
68
|
+
_wasm_free(addr: number): void;
|
|
45
69
|
}
|
|
46
70
|
/**
|
|
47
71
|
* Core API of WASM imports defined by the {@link WasmBridge}. The same
|
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
4
|
import type { BigIntArray, CoreAPI, IWasmAPI, WasmExports } 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
|
|
@@ -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:
|
|
@@ -92,6 +109,27 @@ 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}.
|
|
125
|
+
*
|
|
126
|
+
* @remarks
|
|
127
|
+
* See {@link WasmExports._wasm_allocate} docs for further details.
|
|
128
|
+
*
|
|
129
|
+
* @param numBytes
|
|
130
|
+
*/
|
|
131
|
+
allocate(numBytes: number): number;
|
|
132
|
+
free(addr: number): void;
|
|
95
133
|
getI8(addr: number): number;
|
|
96
134
|
getU8(addr: number): number;
|
|
97
135
|
getI16(addr: number): number;
|
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
|
|
@@ -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:
|
|
@@ -149,6 +161,40 @@ 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}.
|
|
180
|
+
*
|
|
181
|
+
* @remarks
|
|
182
|
+
* See {@link WasmExports._wasm_allocate} docs for further details.
|
|
183
|
+
*
|
|
184
|
+
* @param numBytes
|
|
185
|
+
*/
|
|
186
|
+
allocate(numBytes) {
|
|
187
|
+
const addr = this.exports._wasm_allocate(numBytes);
|
|
188
|
+
if (!addr)
|
|
189
|
+
throw new OutOfMemoryError(`unable to allocate: ${numBytes}`);
|
|
190
|
+
this.logger.debug(`allocated ${numBytes} bytes @ 0x${U32(addr)}`);
|
|
191
|
+
this.ensureMemory();
|
|
192
|
+
return addr;
|
|
193
|
+
}
|
|
194
|
+
free(addr) {
|
|
195
|
+
this.logger.debug(`freeing memory @ 0x${U32(addr)}`);
|
|
196
|
+
this.exports._wasm_free(addr);
|
|
197
|
+
}
|
|
152
198
|
getI8(addr) {
|
|
153
199
|
return this.i8[addr];
|
|
154
200
|
}
|
|
@@ -303,7 +349,7 @@ export class WasmBridge {
|
|
|
303
349
|
setString(str, addr, maxBytes, terminate = true) {
|
|
304
350
|
maxBytes = Math.min(maxBytes, this.u8.length - addr);
|
|
305
351
|
const len = this.utf8Encoder.encodeInto(str, this.u8.subarray(addr, addr + maxBytes)).written;
|
|
306
|
-
if (len
|
|
352
|
+
if (len == null || len >= maxBytes + (terminate ? 0 : 1)) {
|
|
307
353
|
illegalArgs(`error writing string to 0x${U32(addr)}`);
|
|
308
354
|
}
|
|
309
355
|
if (terminate) {
|
package/dev/hello.zig
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
/// import externals
|
|
4
4
|
/// see build command for configuration
|
|
5
5
|
const js = @import("wasmapi");
|
|
6
|
+
const std = @import("std");
|
|
7
|
+
|
|
8
|
+
// var buf: [1024]u8 = undefined;
|
|
9
|
+
// var fba = std.heap.FixedBufferAllocator.init(&buf);
|
|
10
|
+
pub const WASM_ALLOCATOR: ?std.mem.Allocator = null; //fba.allocator();
|
|
6
11
|
|
|
7
12
|
export fn start() void {
|
|
8
13
|
js.printStr("hello world!");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thi.ng/wasm-api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Modular, extensible API bridge and generic glue code between JS & WebAssembly",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
"doc:readme": "yarn doc:stats && tools:readme",
|
|
32
32
|
"doc:stats": "tools:module-stats",
|
|
33
33
|
"pub": "yarn npm publish --access public",
|
|
34
|
-
"test": "testament test"
|
|
34
|
+
"test": "testament test",
|
|
35
|
+
"test:build-zig": "zig build-lib -O ReleaseSmall -target wasm32-freestanding -dynamic --strip --pkg-begin wasmapi zig/core.zig --pkg-end test/custom.zig && wasm-dis -o custom.wast custom.wasm && cp custom.wasm test"
|
|
35
36
|
},
|
|
36
37
|
"dependencies": {
|
|
37
38
|
"@thi.ng/api": "^8.3.9",
|
|
@@ -92,5 +93,5 @@
|
|
|
92
93
|
"status": "alpha",
|
|
93
94
|
"year": 2022
|
|
94
95
|
},
|
|
95
|
-
"gitHead": "
|
|
96
|
+
"gitHead": "e579cb171fc720cbf0b71d3a5f4adfacccdaf214\n"
|
|
96
97
|
}
|
package/test/custom.zig
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
// Import JS core API
|
|
2
2
|
const js = @import("wasmapi");
|
|
3
|
+
const std = @import("std");
|
|
4
|
+
|
|
5
|
+
pub const WASM_ALLOCATOR: ?std.mem.Allocator = null;
|
|
3
6
|
|
|
4
7
|
/// Fill vec2 with random values
|
|
8
|
+
/// Associate this function with the "custom" import section
|
|
5
9
|
extern "custom" fn setVec2(addr: usize) void;
|
|
6
10
|
|
|
7
11
|
export fn test_setVec2() void {
|
|
@@ -0,0 +1,39 @@
|
|
|
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;
|
package/zig/core.zig
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
//! JavaScript externals for https://thi.ng/wasm-api
|
|
2
2
|
|
|
3
|
+
const std = @import("std");
|
|
4
|
+
const root = @import("root");
|
|
5
|
+
|
|
6
|
+
/// Initialize the allocator to be exposed to the WASM host env
|
|
7
|
+
/// (via `_wasm_allocate()` and `_wasm_free()`).
|
|
8
|
+
/// If the user defines a public `WASM_ALLOCATOR` in their root file
|
|
9
|
+
/// then this allocator will be used, otherwise the implementation
|
|
10
|
+
/// falls back to using GPA.
|
|
11
|
+
/// Note: The type for this var is purposefully chosen as an optional,
|
|
12
|
+
/// effectively disabling allocations from the WASM host side if
|
|
13
|
+
/// `WASM_ALLOCATOR` is set to null.
|
|
14
|
+
pub const allocator: ?std.mem.Allocator = alloc: {
|
|
15
|
+
if (@hasDecl(root, "WASM_ALLOCATOR")) {
|
|
16
|
+
break :alloc root.WASM_ALLOCATOR;
|
|
17
|
+
} else {
|
|
18
|
+
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
19
|
+
break :alloc gpa.allocator();
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/// Attempts to allocate memory using configured `allocator` and if
|
|
24
|
+
/// successful returns address of new chunk or zero if failed
|
|
25
|
+
/// Note: For SIMD compatibility all allocations are aligned to 16 bytes
|
|
26
|
+
pub export fn _wasm_allocate(numBytes: usize) usize {
|
|
27
|
+
if (allocator) |a| {
|
|
28
|
+
var buf = a.alignedAlloc(u8, 16, numBytes) catch return 0;
|
|
29
|
+
return @ptrToInt(buf.ptr);
|
|
30
|
+
}
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// Frees chunk of heap memory (previously allocated using `_wasm_allocate()`)
|
|
35
|
+
/// starting at given address. Note: This is a no-op currently.
|
|
36
|
+
pub export fn _wasm_free(addr: usize) void {
|
|
37
|
+
_ = addr;
|
|
38
|
+
}
|
|
39
|
+
|
|
3
40
|
/// Prints number using configured JS logger
|
|
4
41
|
pub extern "core" fn printI8(x: i8) void;
|
|
5
42
|
/// Prints number using configured JS logger
|