@thi.ng/wasm-api 0.9.0 → 0.10.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2022-08-23T10:37:04Z
3
+ - **Last updated**: 2022-08-24T09:52:32Z
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,18 @@ 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.10.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/wasm-api@0.10.0) (2022-08-24)
13
+
14
+ #### 🚀 Features
15
+
16
+ - add events, update allocator handling ([89416b1](https://github.com/thi-ng/umbrella/commit/89416b1))
17
+ - add INotify impl for WasmBridge
18
+ - emit event when WASM memory has changed (e.g. to recreate user views)
19
+ - reverse logic so that NO allocator is used by default and instead must
20
+ be explicitly enabled (rather than disabled)
21
+ - update Zig & C bindings
22
+ - add/update docstrings
23
+
12
24
  ## [0.9.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/wasm-api@0.9.0) (2022-08-23)
13
25
 
14
26
  #### 🚀 Features
package/README.md CHANGED
@@ -418,12 +418,28 @@ TypeScript code generator explicitly.
418
418
 
419
419
  ### Memory allocations
420
420
 
421
- The `WasmBridge` includes support for malloc/free-style allocations (within the
422
- linear WASM memory), _unless_ these are explicitly disabled on the WASM side. Please see for further reference:
423
-
424
- - [`/include/wasmapi.zig`](https://github.com/thi-ng/umbrella/blob/develop/packages/wasm-api/include/wasmapi.zig#L6) - comments about WASM-side allocator handling
425
- - [`WasmBridge.allocate()`](https://docs.thi.ng/umbrella/wasm-api/classes/WasmBridge.html#allocate) - allocating memory from JS side
426
- - [`WasmBridge.free()`](https://docs.thi.ng/umbrella/wasm-api/classes/WasmBridge.html#free) - freeing previously allocated memory
421
+ If explicitly enabled on the WASM side, the `WasmBridge` includes support for
422
+ malloc/free-style allocations (within the linear WASM memory) from the JS side
423
+ (Note: This is a breaking change in v0.10.0, now using a more flexible approach
424
+ & reverse logic of earlier alpha versions).
425
+
426
+ The actual allocator is implementation specific and suitable generic mechanisms
427
+ are defined for both the included Zig & C bindings. Please see for further
428
+ reference:
429
+
430
+ - [`/include/wasmapi.zig`](https://github.com/thi-ng/umbrella/blob/develop/packages/wasm-api/include/wasmapi.zig#L6):
431
+ comments about WASM-side allocator handling in Zig
432
+ - [`/include/wasmapi.h`](https://github.com/thi-ng/umbrella/blob/develop/packages/wasm-api/include/wasmapi.h#L19):
433
+ comments about WASM-side allocator handling in C/C++
434
+ - [`WasmBridge.allocate()`](https://docs.thi.ng/umbrella/wasm-api/classes/WasmBridge.html#allocate):
435
+ allocating memory from JS side
436
+ - [`WasmBridge.free()`](https://docs.thi.ng/umbrella/wasm-api/classes/WasmBridge.html#free):
437
+ freeing previously allocated memory from JS side
438
+
439
+ Note: The provided Zig mechanism supports the idiomatic (Zig) pattern of working
440
+ with multiple allocators in different parts of the application and supports
441
+ dynamic assignments/swapping of the exposed allocator. See comments in source
442
+ file for more details...
427
443
 
428
444
  ```ts
429
445
  try {
@@ -431,7 +447,7 @@ try {
431
447
  const addr = bridge.allocate(256);
432
448
 
433
449
  // write string to reserved memory
434
- // max. 256 bytes written, zero terminated
450
+ // max. 256 bytes, zero terminated
435
451
  const num = bridge.setString("hello WASM world!", addr, 256, true);
436
452
 
437
453
  // call WASM function doing something w/ the string
@@ -440,7 +456,7 @@ try {
440
456
  // cleanup
441
457
  bridge.free(addr, 256);
442
458
  } catch(e) {
443
- // allocation error
459
+ // deal with allocation error
444
460
  // ...
445
461
  }
446
462
  ```
@@ -521,7 +537,7 @@ node --experimental-repl-await
521
537
  > const wasmApi = await import("@thi.ng/wasm-api");
522
538
  ```
523
539
 
524
- Package sizes (gzipped, pre-treeshake): ESM: 4.34 KB
540
+ Package sizes (gzipped, pre-treeshake): ESM: 4.48 KB
525
541
 
526
542
  **IMPORTANT:** The package includes various code generators and supporting
527
543
  functions which are NOT required during runtime. Hence the actual package size
package/api.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { BigType, FloatType, Fn, Fn2 } from "@thi.ng/api";
2
2
  import type { WasmBridge } from "./bridge.js";
3
3
  export declare const PKG_NAME = "@thi.ng/wasm-api";
4
+ export declare const EVENT_MEMORY_CHANGED = "memory-changed";
4
5
  export declare type BigIntArray = bigint[] | BigInt64Array | BigUint64Array;
5
6
  /**
6
7
  * Common interface for WASM/JS child APIs which will be used in combination
@@ -27,9 +28,9 @@ export interface IWasmAPI<T extends WasmExports = WasmExports> {
27
28
  getImports(): WebAssembly.ModuleImports;
28
29
  }
29
30
  /**
30
- * Base interface of exports declared by the WASM module. At the very least, the
31
- * module needs to export its memory and the functions defined in this
32
- * interface.
31
+ * Base interface of exports declared by the WASM module. At the very least, a
32
+ * compatible module needs to export its memory and the functions defined in
33
+ * this interface.
33
34
  *
34
35
  * @remarks
35
36
  * This interface is supposed to be extended with the concrete exports defined
@@ -45,21 +46,34 @@ export interface WasmExports {
45
46
  */
46
47
  memory: WebAssembly.Memory;
47
48
  /**
48
- * Implementation specific memory allocation function (likely heap-based).
49
- * If successful returns address of new memory block, or zero if
50
- * unsuccessful.
49
+ * Implementation specific WASM memory allocation function. If successful
50
+ * returns address of new memory block, or zero if unsuccessful.
51
51
  *
52
52
  * @remarks
53
- * In the supplied Zig bindings (see `/zig/core.zig`), by default this is
54
- * using the `std.heap.GeneralPurposeAllocator` (which also automatically
55
- * handles growing the WASM memory), however as mentioned the underlying
56
- * mechanism is purposefully left to the actual WASM-side implementation. In
57
- * a C program, this would likely use `malloc()` or similar...
53
+ * #### Zig
54
+ *
55
+ * Using the supplied Zig bindings (see `/include/wasmapi.zig`), it's the
56
+ * user's responsibility to define a public `WASM_ALLOCATOR` in the root
57
+ * source file to enable allocations, e.g. using the
58
+ * [`std.heap.GeneralPurposeAllocator`](https://ziglang.org/documentation/master/#Choosing-an-Allocator)
59
+ * (which also automatically handles growing the WASM memory). However, as
60
+ * mentioned, the underlying mechanism is purposefully left to the actual
61
+ * WASM-side implementation. If no allocator is defined this function
62
+ * returns zero, which in turn will cause {@link WasmBridge.allocate} to
63
+ * throw an error.
64
+ *
65
+ * #### C/C++
66
+ *
67
+ * Using the supplied C bindings (see `/include/wasmapi.h`), it's the user's
68
+ * responsibility to enable allocation support by defining the
69
+ * `WASMAPI_MALLOC` symbol (and compiling the WASM module with a malloc
70
+ * implementation).
58
71
  */
59
72
  _wasm_allocate(numBytes: number): number;
60
73
  /**
61
74
  * Implementation specific function to free a previously allocated chunk of
62
- * of WASM memory (allocated via {@link WasmExports._wasm_allocate}).
75
+ * of WASM memory (allocated via {@link WasmExports._wasm_allocate}, also
76
+ * see remarks for that function).
63
77
  *
64
78
  * @param addr
65
79
  * @param numBytes
package/api.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export const PKG_NAME = "@thi.ng/wasm-api";
2
+ export const EVENT_MEMORY_CHANGED = "memory-changed";
2
3
  /**
3
4
  * WASM usize type. Assuming wasm32 until wasm64 surfaces, then need an option.
4
5
  */
package/bridge.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /// <reference types="node" />
2
- import type { NumericArray } from "@thi.ng/api";
2
+ import type { Event, INotify, Listener, NumericArray } from "@thi.ng/api";
3
3
  import type { ILogger } from "@thi.ng/logger";
4
- import type { BigIntArray, CoreAPI, IWasmAPI, WasmExports, IWasmMemoryAccess } from "./api.js";
4
+ import { BigIntArray, CoreAPI, IWasmAPI, WasmExports, IWasmMemoryAccess } from "./api.js";
5
5
  export declare const OutOfMemoryError: {
6
6
  new (msg?: string | undefined): {
7
7
  name: string;
@@ -28,7 +28,7 @@ export declare const OutOfMemoryError: {
28
28
  * 64bit integers are handled via JS `BigInt` and hence require the host env to
29
29
  * support it. No polyfill is provided.
30
30
  */
31
- export declare class WasmBridge<T extends WasmExports = WasmExports> implements IWasmMemoryAccess {
31
+ export declare class WasmBridge<T extends WasmExports = WasmExports> implements IWasmMemoryAccess, INotify {
32
32
  modules: Record<string, IWasmAPI<T>>;
33
33
  logger: ILogger;
34
34
  i8: Int8Array;
@@ -66,14 +66,23 @@ export declare class WasmBridge<T extends WasmExports = WasmExports> implements
66
66
  * then initializes all declared bridge child API modules. Returns false if
67
67
  * any of the module initializations failed.
68
68
  *
69
+ * @remarks
70
+ * Emits the {@link EVENT_MEMORY_CHANGED} event just before returning (and
71
+ * AFTER all child API modules have been initialized).
72
+ *
69
73
  * @param exports
70
74
  */
71
75
  init(exports: T): Promise<boolean>;
72
76
  /**
73
- * Called automatically. Initializes and/or updates the various typed WASM
74
- * memory views (e.g. after growing the WASM memory).
77
+ * Called automatically during initialization. Initializes and/or updates
78
+ * the various typed WASM memory views (e.g. after growing the WASM memory
79
+ * and the previous buffer becoming detached). Unless `notify` is false,
80
+ * the {@link EVENT_MEMORY_CHANGED} event will be emitted if the memory
81
+ * views had to be updated.
82
+ *
83
+ * @param notify
75
84
  */
76
- ensureMemory(): void;
85
+ ensureMemory(notify?: boolean): void;
77
86
  /**
78
87
  * Required use for WASM module instantiation to provide JS imports to the
79
88
  * module. Returns an object of all WASM imports declared by the bridge core
@@ -137,6 +146,10 @@ export declare class WasmBridge<T extends WasmExports = WasmExports> implements
137
146
  * `numBytes` value must be the same as previously given to
138
147
  * {@link WasmBridge.allocate}.
139
148
  *
149
+ * @remarks
150
+ * This function always succeeds, regardless of presence of an active
151
+ * allocator on the WASM side or validity of given arguments.
152
+ *
140
153
  * @param addr
141
154
  * @param numBytes
142
155
  */
@@ -208,5 +221,11 @@ export declare class WasmBridge<T extends WasmExports = WasmExports> implements
208
221
  */
209
222
  setString(str: string, addr: number, maxBytes: number, terminate?: boolean): number;
210
223
  getElementById(addr: number, len?: number): HTMLElement;
224
+ /** {@inheritDoc @thi.ng/api#INotify.addListener} */
225
+ addListener(id: string, fn: Listener, scope?: any): boolean;
226
+ /** {@inheritDoc @thi.ng/api#INotify.removeListener} */
227
+ removeListener(id: string, fn: Listener, scope?: any): boolean;
228
+ /** {@inheritDoc @thi.ng/api#INotify.notify} */
229
+ notify(event: Event): void;
211
230
  }
212
231
  //# sourceMappingURL=bridge.d.ts.map
package/bridge.js CHANGED
@@ -1,7 +1,10 @@
1
+ import { __decorate } from "tslib";
2
+ import { INotifyMixin } from "@thi.ng/api/mixins/inotify";
1
3
  import { defError } from "@thi.ng/errors/deferror";
2
4
  import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
3
5
  import { U16, U32, U64HL, U8 } from "@thi.ng/hex";
4
6
  import { ConsoleLogger } from "@thi.ng/logger/console";
7
+ import { EVENT_MEMORY_CHANGED, } from "./api.js";
5
8
  const B32 = BigInt(32);
6
9
  export const OutOfMemoryError = defError(() => "Out of memory");
7
10
  /**
@@ -20,7 +23,7 @@ export const OutOfMemoryError = defError(() => "Out of memory");
20
23
  * 64bit integers are handled via JS `BigInt` and hence require the host env to
21
24
  * support it. No polyfill is provided.
22
25
  */
23
- export class WasmBridge {
26
+ let WasmBridge = class WasmBridge {
24
27
  constructor(modules = {}, logger = new ConsoleLogger("wasm")) {
25
28
  this.modules = modules;
26
29
  this.logger = logger;
@@ -86,24 +89,34 @@ export class WasmBridge {
86
89
  * then initializes all declared bridge child API modules. Returns false if
87
90
  * any of the module initializations failed.
88
91
  *
92
+ * @remarks
93
+ * Emits the {@link EVENT_MEMORY_CHANGED} event just before returning (and
94
+ * AFTER all child API modules have been initialized).
95
+ *
89
96
  * @param exports
90
97
  */
91
98
  async init(exports) {
92
99
  this.exports = exports;
93
- this.ensureMemory();
100
+ this.ensureMemory(false);
94
101
  for (let id in this.modules) {
95
102
  this.logger.debug(`initializing API module: ${id}`);
96
103
  const status = await this.modules[id].init(this);
97
104
  if (!status)
98
105
  return false;
99
106
  }
107
+ this.notify({ id: EVENT_MEMORY_CHANGED, value: this.exports.memory });
100
108
  return true;
101
109
  }
102
110
  /**
103
- * Called automatically. Initializes and/or updates the various typed WASM
104
- * memory views (e.g. after growing the WASM memory).
111
+ * Called automatically during initialization. Initializes and/or updates
112
+ * the various typed WASM memory views (e.g. after growing the WASM memory
113
+ * and the previous buffer becoming detached). Unless `notify` is false,
114
+ * the {@link EVENT_MEMORY_CHANGED} event will be emitted if the memory
115
+ * views had to be updated.
116
+ *
117
+ * @param notify
105
118
  */
106
- ensureMemory() {
119
+ ensureMemory(notify = true) {
107
120
  const buf = this.exports.memory.buffer;
108
121
  if (this.u8 && this.u8.buffer === buf)
109
122
  return;
@@ -117,6 +130,11 @@ export class WasmBridge {
117
130
  this.u64 = new BigUint64Array(buf);
118
131
  this.f32 = new Float32Array(buf);
119
132
  this.f64 = new Float64Array(buf);
133
+ notify &&
134
+ this.notify({
135
+ id: EVENT_MEMORY_CHANGED,
136
+ value: this.exports.memory,
137
+ });
120
138
  }
121
139
  /**
122
140
  * Required use for WASM module instantiation to provide JS imports to the
@@ -192,7 +210,7 @@ export class WasmBridge {
192
210
  const addr = this.exports._wasm_allocate(numBytes);
193
211
  if (!addr)
194
212
  throw new OutOfMemoryError(`unable to allocate: ${numBytes}`);
195
- this.logger.debug(`allocated ${numBytes} bytes @ 0x${U32(addr)}`);
213
+ this.logger.debug(`allocated ${numBytes} bytes @ 0x${U32(addr)} .. 0x${U32(addr + numBytes - 1)}`);
196
214
  this.ensureMemory();
197
215
  clear && this.u8.fill(0, addr, addr + numBytes);
198
216
  return addr;
@@ -203,6 +221,10 @@ export class WasmBridge {
203
221
  * `numBytes` value must be the same as previously given to
204
222
  * {@link WasmBridge.allocate}.
205
223
  *
224
+ * @remarks
225
+ * This function always succeeds, regardless of presence of an active
226
+ * allocator on the WASM side or validity of given arguments.
227
+ *
206
228
  * @param addr
207
229
  * @param numBytes
208
230
  */
@@ -403,4 +425,17 @@ export class WasmBridge {
403
425
  el == null && illegalArgs(`missing DOM element #${id}`);
404
426
  return el;
405
427
  }
406
- }
428
+ /** {@inheritDoc @thi.ng/api#INotify.addListener} */
429
+ // @ts-ignore: mixin
430
+ addListener(id, fn, scope) { }
431
+ /** {@inheritDoc @thi.ng/api#INotify.removeListener} */
432
+ // @ts-ignore: mixin
433
+ removeListener(id, fn, scope) { }
434
+ /** {@inheritDoc @thi.ng/api#INotify.notify} */
435
+ // @ts-ignore: mixin
436
+ notify(event) { }
437
+ };
438
+ WasmBridge = __decorate([
439
+ INotifyMixin
440
+ ], WasmBridge);
441
+ export { WasmBridge };
package/include/wasmapi.h CHANGED
@@ -16,16 +16,17 @@ extern "C" {
16
16
  // Same as EMSCRIPTEN_KEEP_ALIVE, ensures symbol will be exported
17
17
  #define WASM_KEEP __attribute__((used))
18
18
 
19
- // Generate stubs only if explicitly disabled by defining this symbol
20
- #ifdef WASMAPI_NO_MALLOC
21
- size_t WASM_KEEP _wasm_allocate(size_t num_bytes) { return 0; }
22
- void WASM_KEEP _wasm_free(size_t addr) {}
23
- #else
19
+ // Generate malloc/free wrappers only if explicitly enabled by defining this
20
+ // symbol. If undefined some function stubs are exported.
21
+ #ifdef WASMAPI_MALLOC
24
22
  #include <stdlib.h>
25
23
  size_t WASM_KEEP _wasm_allocate(size_t numBytes) {
26
24
  return (size_t)malloc(numBytes);
27
25
  }
28
26
  void WASM_KEEP _wasm_free(size_t addr) { free((void*)addr); }
27
+ #else
28
+ size_t WASM_KEEP _wasm_allocate(size_t num_bytes) { return 0; }
29
+ void WASM_KEEP _wasm_free(size_t addr) {}
29
30
  #endif
30
31
 
31
32
  WASM_IMPORT("wasmapi", void, printI8, wasm_)(int8_t x);
@@ -3,28 +3,26 @@
3
3
  const std = @import("std");
4
4
  const root = @import("root");
5
5
 
6
- /// Initialize the allocator to be exposed to the WASM host env
6
+ /// Obtains the allocator to be exposed to the WASM host env
7
7
  /// (via `_wasm_allocate()` and `_wasm_free()`).
8
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.
9
+ /// then this allocator will be used, otherwise the implementations
10
+ /// of the two mentioned functions are no-ops.
11
+ /// The `WASM_ALLOCATOR` can be changed and/or enabled/disabled dynamically
12
+ /// This helper function here is used to always lookup the current value/impl.
13
+ ///
11
14
  /// 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
- };
15
+ /// effectively disabling allocations from the WASM host side if no
16
+ /// `WASM_ALLOCATOR` is set (or set to null).
17
+ pub fn allocator() ?std.mem.Allocator {
18
+ return if (@hasDecl(root, "WASM_ALLOCATOR")) root.WASM_ALLOCATOR else null;
19
+ }
22
20
 
23
- /// Attempts to allocate memory using configured `allocator` and if
21
+ /// Attempts to allocate memory using configured `allocator()` and if
24
22
  /// successful returns address of new chunk or zero if failed
25
23
  /// Note: For SIMD compatibility all allocations are aligned to 16 bytes
26
24
  pub export fn _wasm_allocate(numBytes: usize) usize {
27
- if (allocator) |alloc| {
25
+ if (allocator()) |alloc| {
28
26
  var mem = alloc.alignedAlloc(u8, 16, numBytes) catch return 0;
29
27
  return @ptrToInt(mem.ptr);
30
28
  }
@@ -33,11 +31,10 @@ pub export fn _wasm_allocate(numBytes: usize) usize {
33
31
 
34
32
  /// Frees chunk of heap memory (previously allocated using `_wasm_allocate()`)
35
33
  /// starting at given address and of given byte length.
36
- /// Note: This is a no-op if the allocator is explicitly disabled (see `setAllocator()`),
34
+ /// Note: This is a no-op if no allocator is configured (see `allocator()`)
37
35
  pub export fn _wasm_free(addr: usize, numBytes: usize) void {
38
- if (allocator) |alloc| {
36
+ if (allocator()) |alloc| {
39
37
  var mem = [2]usize{ addr, numBytes };
40
- printFmt("{d}", .{@ptrCast(*[]u8, &mem).*});
41
38
  alloc.free(@ptrCast(*[]u8, &mem).*);
42
39
  }
43
40
  }
@@ -169,7 +166,7 @@ pub fn printStr(msg: []const u8) void {
169
166
  /// to output it via JS, then frees string's memory again
170
167
  /// (Only available if the allocator used by `_wasm_allocate()` hasn't been disabled.)
171
168
  pub fn printFmt(comptime fmt: []const u8, args: anytype) void {
172
- if (allocator) |alloc| {
169
+ if (allocator()) |alloc| {
173
170
  const res = std.fmt.allocPrint(alloc, fmt, args) catch return;
174
171
  defer alloc.free(res);
175
172
  printStr(res);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/wasm-api",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Generic, modular, extensible API bridge, glue code and bindings code generator for hybrid JS & WebAssembly projects",
5
5
  "type": "module",
6
6
  "module": "./index.js",
@@ -57,14 +57,20 @@
57
57
  "typescript": "^4.7.4"
58
58
  },
59
59
  "keywords": [
60
+ "allocator",
60
61
  "api",
61
62
  "bindings",
62
63
  "c",
63
64
  "codegen",
65
+ "event",
64
66
  "id",
65
67
  "logger",
66
68
  "memory",
69
+ "string",
70
+ "struct",
71
+ "typedarray",
67
72
  "typescript",
73
+ "utf8",
68
74
  "wasm",
69
75
  "webassembly",
70
76
  "wrapper",
@@ -117,5 +123,5 @@
117
123
  "status": "alpha",
118
124
  "year": 2022
119
125
  },
120
- "gitHead": "be8423e2019e95c14a096260a93b9762dde0c768\n"
126
+ "gitHead": "4264b91052b8c1f74db9fd51dc48c830dc0d5829\n"
121
127
  }