@dacely/toilscript-loader 0.0.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/README.md ADDED
@@ -0,0 +1,318 @@
1
+ # AssemblyScript Loader
2
+
3
+ A tiny module loader that makes working with AssemblyScript modules as convenient as it gets without sacrificing efficiency. It about mirrors the relevant parts of the WebAssembly API while also providing utility to allocate and read strings, arrays and classes.
4
+
5
+ **DEPRECATION NOTICE:** The loader has been deprecated in AssemblyScript 0.20. It will likely continue to work for a while, but it is recommended to switch to the new [static bindings](https://www.assemblyscript.org/compiler.html#host-bindings) generation.
6
+
7
+ ## Example
8
+
9
+ ```ts
10
+ import loader from "@dacely/toilscript-loader"; // or require
11
+ loader.instantiate(
12
+ // Binary to instantiate
13
+ fetch("optimized.wasm"), // or fs.readFileSync
14
+ // or fs.promises.readFile
15
+ // or just a buffer
16
+ // Additional imports
17
+ { ... }
18
+ ).then(({ exports }) => {
19
+ ...
20
+ })
21
+ ```
22
+
23
+ The loader basically instantiates the module using `WebAssembly` APIs, but also adds additional utility.
24
+
25
+ ## Installation
26
+
27
+ The loader can be installed from [npm](https://www.npmjs.com/package/@dacely/toilscript-loader):
28
+
29
+ ```sh
30
+ npm install --save @dacely/toilscript-loader
31
+ ```
32
+
33
+ On the web:
34
+
35
+ ```html
36
+ <!-- ESM -->
37
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@dacely/toilscript-loader/index.js"></script>
38
+ <!-- UMD -->
39
+ <script src="https://cdn.jsdelivr.net/npm/@dacely/toilscript-loader/umd/index.js"></script>
40
+ ```
41
+
42
+ ## Usage
43
+
44
+ One task the loader does not perform is to implicitly translate between WebAssembly pointers and JavaScript objects, and that's where the mixed in utility comes into play. For example, if one has
45
+
46
+ ```ts
47
+ // AssemblyScript
48
+ export function concat(a: string, b: string): string {
49
+ return a + b
50
+ }
51
+ ```
52
+
53
+ and then wants to call `concat` externally, the string arguments cannot just be JavaScript strings but must first be allocated in the module's memory with their lifetime tracked, like so:
54
+
55
+ ```js
56
+ // JavaScript
57
+ const { concat } = myModule.exports
58
+ const { __newString, __getString } = myModule.exports
59
+
60
+ function doConcat(aStr, bStr) {
61
+ let aPtr = __newString(aStr)
62
+ let bPtr = __newString(bStr)
63
+ let cPtr = concat(aPtr, bPtr)
64
+ let cStr = __getString(cPtr)
65
+ return cStr
66
+ }
67
+
68
+ console.log(doConcat("Hello ", "world!"))
69
+ ```
70
+
71
+ ### Creating arrays
72
+
73
+ Arrays (or more advanced classes for that matter) require a bit more cooperation because we need to know their value type in order to work with them properly. To achieve this, every class has a unique id internally, and a chunk of runtime type information (RTTI) is shipped with the module to evaluate class types. Here's an example of working with an `Int32Array`:
74
+
75
+ ```ts
76
+ // AssemblyScript
77
+ export function sum(arr: Int32Array): i32 {
78
+ let sum = 0
79
+ for (let i = 0, k = arr.length; i < k; ++i) {
80
+ sum += unchecked(arr[i])
81
+ }
82
+ return sum
83
+ }
84
+ export const Int32Array_ID = idof<Int32Array>()
85
+ ```
86
+
87
+ ```js
88
+ // JavaScript
89
+ const { sum, Int32Array_ID } = myModule.exports
90
+ const { __newArray } = myModule.exports
91
+
92
+ function doSum(values) {
93
+ const arrPtr = __newArray(Int32Array_ID, values)
94
+ return sum(arrPtr)
95
+ }
96
+
97
+ console.log(doSum([1, 2, 3]))
98
+ ```
99
+
100
+ This works with all kinds of arrays, except that ids are different and values are interpreted differently, of course.
101
+
102
+ ### Reading arrays
103
+
104
+ If one is instead interested in the values of an array being returned by the module, there are two approaches to this. Let's say we have the following module:
105
+
106
+ ```ts
107
+ // AssemblyScript
108
+ export function getRandomArray(len: i32): Int32Array {
109
+ const arr = new Int32Array(len)
110
+ // fill with random values
111
+ return arr
112
+ }
113
+ ```
114
+
115
+ The first is, obviously, to read the array's values from the module's memory by essentially copying them to a JS array
116
+
117
+ ```js
118
+ // JavaScript
119
+ const { getRandomArray } = myModule.exports
120
+ const { __getArray } = myModule.exports
121
+
122
+ function doGetRandomArray(len) {
123
+ const arrPtr = getRandomArray(len)
124
+ const values = __getArray(arrPtr)
125
+ return values
126
+ }
127
+
128
+ console.log(doGetRandomArray(10))
129
+ ```
130
+
131
+ which is always safe, while the second is to create a live view on the array, enabling two-way modification of its values:
132
+
133
+ ```js
134
+ // JavaScript
135
+ const { getRandomArray } = myModule.exports
136
+ const { __getArrayView, __pin, __unpin } = myModule.exports
137
+
138
+ function doGetRandomArrayView(len) {
139
+ const arrPtr = __pin(getRandomArray(len)) // pin if necessary
140
+ const view = __getArrayView(arrPtr)
141
+ return { ptr, view }
142
+ }
143
+
144
+ const randomArray = doGetRandomArrayView(10)
145
+ console.log(randomArray.view)
146
+ __unpin(randomArray.ptr) // unpin if necessary
147
+ ```
148
+
149
+ The latter variant can be more efficient (and useful) but is a little dangerous because the view may become detached from the module's memory when memory automatically grows. Also, the viewed array can grow automatically when pushed to, with the view then referencing random memory. Pushing to an array can be avoided quite easily, yet it is notoriously hard to predict when module memory grows - but one can try to set a sufficiently large size of `--initialMemory` or defensively trigger a sufficiently large dynamic allocation being freed immediately before dealing with potentially problematic views.
150
+
151
+ ### Custom classes
152
+
153
+ As mentioned earlier, the loader understands how to make a nice object structure of a module's exports, and it is possible to utilize it to work with classes in a more natural way. For example, when calling the following function externally
154
+
155
+ ```ts
156
+ // AssemblyScript
157
+ export class Foo {
158
+ constructor(public str: string) {}
159
+ getString(): string {
160
+ return this.str
161
+ }
162
+ }
163
+
164
+ export function getFoo(): Foo { // this one
165
+ return new Foo("Hello world!")
166
+ }
167
+ ```
168
+
169
+ one can wrap the received pointer in a `myModule.exports.Foo` instance:
170
+
171
+ ```js
172
+ // JavaScript
173
+ const { Foo, getFoo } = myModule.exports
174
+ const { __getString, __pin, __unpin } = myModule.exports
175
+
176
+ const fooPtr = __pin(getFoo()) // pin if necessary
177
+ const foo = Foo.wrap(fooPtr)
178
+ const strPtr = foo.getString()
179
+ console.log(__getString(strPtr))
180
+ __unpin(fooPtr) // unpin if necessary
181
+ ```
182
+
183
+ ## API
184
+
185
+ For reference, here comes the full API provided by the loader.
186
+
187
+ ::: tip
188
+ Copying from and extending the examples above is typically sufficient.
189
+ :::
190
+
191
+ ### Static members
192
+
193
+ * ```ts
194
+ function instantiate<T>(
195
+ moduleOrBuffer: WasmInstantiable,
196
+ imports?: WasmImports
197
+ ): Promise<ASUtil & T>
198
+ ```
199
+ Asynchronously instantiates an AssemblyScript module from anything that can be instantiated.
200
+
201
+ * ```ts
202
+ function instantiateSync<T>(
203
+ moduleOrBuffer: WasmInstantiable,
204
+ imports?: WasmImports
205
+ ): ASUtil & T
206
+ ```
207
+ Synchronously instantiates an AssemblyScript module from a WebAssembly.Module or binary buffer. Not recommended.
208
+
209
+ * ```ts
210
+ function instantiateStreaming<T>(
211
+ response: Response | PromiseLike<Response>,
212
+ imports?: WasmImports
213
+ ): Promise<ASUtil & T>
214
+ ```
215
+ Asynchronously instantiates an AssemblyScript module from a response, i.e. as obtained by fetch.
216
+
217
+ * ```ts
218
+ function demangle<T>(
219
+ exports: WasmExports,
220
+ baseModule?: Object
221
+ ): T
222
+ ```
223
+ Demangles an AssemblyScript module's exports to a friendly object structure. You usually don't have to call this manually as instantiation does this implicitly.
224
+
225
+ Note that `T` above can either be omitted if the shape of the module is unknown, or can reference a `.d.ts` (i.e. `typeof MyModule`) as produced by the compiler with the `-d` option.
226
+
227
+ ### Module instance utility
228
+
229
+ The following utility functions are mixed into the module's exports.
230
+
231
+ * ```ts
232
+ function __newString(str: string): number
233
+ ```
234
+ Allocates a new string in the module's memory and returns a pointer to it. Requires `--exportRuntime` for access to `__new`.
235
+
236
+ * ```ts
237
+ function __newArray(
238
+ id: number,
239
+ values: valuesOrCapacity?: number[] | ArrayBufferView | number
240
+ ): number
241
+ ```
242
+ Allocates a new array in the module's memory and returns a pointer to it. The `id` is the unique runtime id of the respective array class. If you are using `Int32Array` for example, the best way to know the id is an `export const Int32Array_ID = idof<Int32Array>()`. Requires `--exportRuntime` for access to `__new`. The `values` parameter сan also be used to pre-allocate an otherwise empty array of a certain capacity.
243
+
244
+ * ```ts
245
+ function __getString(ptr: number): string
246
+ ```
247
+ Copies a string's value from the module's memory to a JavaScript string. `ptr` must not be zero.
248
+
249
+ * ```ts
250
+ function __getFunction(ptr: number): ((...args: unknown[]) => unknown) | null
251
+ ```
252
+ Gets a callable function object from the module's memory containing its table index. `ptr` must not be zero.
253
+
254
+ * ```ts
255
+ function __getArrayBuffer(ptr: number): ArrayBuffer
256
+ ```
257
+ Copies an ArrayBuffer's value from the module's memory to a JavaScript buffer. `ptr` must not be zero.
258
+
259
+ * ```ts
260
+ function __getArray(ptr: number): number[]
261
+ ```
262
+ Copies an array's values from the module's memory to a JavaScript array. Infers the array type from RTTI. `ptr` must not be zero.
263
+
264
+ * ```ts
265
+ function __getArrayView(ptr: number): TypedArray
266
+ ```
267
+ Gets a live view on the values of an array in the module's memory. Infers the array type from RTTI. `ptr` must not be zero.
268
+
269
+ This differs from `__getArray` in that the data isn't copied but remains live in both directions. That's faster but also unsafe because if the array grows or becomes garbage collected, the view will no longer represent the correct memory region and modifying its values in this state will most likely corrupt memory or otherwise explode. Use, but use with care.
270
+
271
+ * ```ts
272
+ function __getInt8ArrayView(ptr: number): Int8Array
273
+ function __getUint8ArrayView(ptr: number): Uint8Array
274
+ function __getUint8ClampedArrayView(ptr: number): Uint8ClampedArray
275
+ function __getInt16ArrayView(ptr: number): Int16Array
276
+ function __getUint16ArrayView(ptr: number): Uint16Array
277
+ function __getInt32ArrayView(ptr: number): Int32Array
278
+ function __getUint32ArrayView(ptr: number): Uint32Array
279
+ function __getInt64ArrayView(ptr: number): BigInt64Array
280
+ function __getUint64ArrayView(ptr: number): BigUint64Array
281
+ function __getFloat32ArrayView(ptr: number): Float32Array
282
+ function __getFloat64ArrayView(ptr: number): Float64Array
283
+ ```
284
+ Slightly more efficient variants of `__getArrayView` where the type of the array is know beforehand. Doesn't try to infer the type.
285
+
286
+ ### Module instance runtime interface
287
+
288
+ When compiling with `--exportRuntime`, the loader will expose the runtime interface (`__new`, `__pin`, `__unpin`, `__collect`) as well.
289
+
290
+ ## Convenience vs. efficiency
291
+
292
+ Making the loader's API any more convenient has its tradeoffs. One would either have to include extended type information with the module itself or generate an additional JavaScript file of glue code that does (and hides) all the lifting. As such, one can consider the loader as a small and efficient building block that can do it all, yet does not sacrifice efficiency. If that's not exactly what you are looking for, take a look at more convenient tools below. Just remember that these have tradeoffs.
293
+
294
+ ### More convenient tools
295
+
296
+ * [as-bind](https://github.com/torch2424/as-bind) is a library, built on top of the loader, to make passing high-level data structures between AssemblyScript and JavaScript more convenient.
297
+
298
+ ## Advanced usage
299
+
300
+ ### Direct memory access
301
+
302
+ All of the above can be mixed with direct memory accesses on `myModule.exports.memory.buffer`, for instance by adhering to class layout.
303
+
304
+ ### TypeScript definitions
305
+
306
+ The compiler is able to emit definitions using the `-d` command line option that are compatible with modules demangled by the loader, and these can be used for proper typings in development:
307
+
308
+ ```ts
309
+ // TypeScript
310
+ import type * as MyModule from "myModule"; // pointing at the generated d.ts
311
+
312
+ loader.instantiate<typeof MyModule>(
313
+ fetch("myModule.wasm"),
314
+ { ... }
315
+ ).then(({ exports }) => {
316
+ ...
317
+ })
318
+ ```
package/index.d.ts ADDED
@@ -0,0 +1,124 @@
1
+ /// <reference lib="esnext.bigint" />
2
+
3
+ export interface ResultObject {
4
+ module: WebAssembly.Module;
5
+ instance: WebAssembly.Instance;
6
+ }
7
+
8
+
9
+ /** WebAssembly imports with an optional env object and two levels of nesting. */
10
+ export type Imports = {
11
+ [key: string]: Record<string,unknown> | undefined;
12
+ env?: {
13
+ memory?: WebAssembly.Memory;
14
+ table?: WebAssembly.Table;
15
+ seed?(): number;
16
+ abort?(msg: number, file: number, line: number, column: number): void;
17
+ trace?(msg: number, numArgs?: number, ...args: number[]): void;
18
+ mark?(): void;
19
+ };
20
+ };
21
+
22
+ /** Utility mixed in by the loader. */
23
+ export interface ASUtil {
24
+ memory?: WebAssembly.Memory;
25
+ table?: WebAssembly.Table;
26
+
27
+ /** Copies a string's value from the module's memory. */
28
+ __getString(ptr: number): string;
29
+ /** Copies an ArrayBuffer's value from the module's memory. */
30
+ __getArrayBuffer(ptr: number): ArrayBuffer;
31
+
32
+ /** Copies an array's values from the module's memory. Infers the array type from RTTI. */
33
+ __getArray(ptr: number): number[];
34
+ /** Copies an Int8Array's values from the module's memory. */
35
+ __getInt8Array(ptr: number): Int8Array;
36
+ /** Copies an Uint8Array's values from the module's memory. */
37
+ __getUint8Array(ptr: number): Uint8Array;
38
+ /** Copies an Uint8ClampedArray's values from the module's memory. */
39
+ __getUint8ClampedArray(ptr: number): Uint8ClampedArray;
40
+ /** Copies an Int16Array's values from the module's memory. */
41
+ __getInt16Array(ptr: number): Int16Array;
42
+ /** Copies an Uint16Array's values from the module's memory. */
43
+ __getUint16Array(ptr: number): Uint16Array;
44
+ /** Copies an Int32Array's values from the module's memory. */
45
+ __getInt32Array(ptr: number): Int32Array;
46
+ /** Copies an Uint32Array's values from the module's memory. */
47
+ __getUint32Array(ptr: number): Uint32Array;
48
+ /** Copies an Int32Array's values from the module's memory. */
49
+ __getInt64Array?(ptr: number): BigInt64Array;
50
+ /** Copies an Uint32Array's values from the module's memory. */
51
+ __getUint64Array?(ptr: number): BigUint64Array;
52
+ /** Copies a Float32Array's values from the module's memory. */
53
+ __getFloat32Array(ptr: number): Float32Array;
54
+ /** Copies a Float64Array's values from the module's memory. */
55
+ __getFloat64Array(ptr: number): Float64Array;
56
+
57
+ /** Gets a live view on an array's values in the module's memory. Infers the array type from RTTI. */
58
+ __getArrayView(ptr: number): ArrayBufferView;
59
+ /** Gets a live view on an Int8Array's values in the module's memory. */
60
+ __getInt8ArrayView(ptr: number): Int8Array;
61
+ /** Gets a live view on an Uint8Array's values in the module's memory. */
62
+ __getUint8ArrayView(ptr: number): Uint8Array;
63
+ /** Gets a live view on an Uint8ClampedArray's values in the module's memory. */
64
+ __getUint8ClampedArrayView(ptr: number): Uint8ClampedArray;
65
+ /** Gets a live view on an Int16Array's values in the module's memory. */
66
+ __getInt16ArrayView(ptr: number): Int16Array;
67
+ /** Gets a live view on an Uint16Array's values in the module's memory. */
68
+ __getUint16ArrayView(ptr: number): Uint16Array;
69
+ /** Gets a live view on an Int32Array's values in the module's memory. */
70
+ __getInt32ArrayView(ptr: number): Int32Array;
71
+ /** Gets a live view on an Uint32Array's values in the module's memory. */
72
+ __getUint32ArrayView(ptr: number): Uint32Array;
73
+ /** Gets a live view on an Int32Array's values in the module's memory. */
74
+ __getInt64ArrayView?(ptr: number): BigInt64Array;
75
+ /** Gets a live view on an Uint32Array's values in the module's memory. */
76
+ __getUint64ArrayView?(ptr: number): BigUint64Array;
77
+ /** Gets a live view on a Float32Array's values in the module's memory. */
78
+ __getFloat32ArrayView(ptr: number): Float32Array;
79
+ /** Gets a live view on a Float64Array's values in the module's memory. */
80
+ __getFloat64ArrayView(ptr: number): Float64Array;
81
+
82
+ /** Gets a function from poiner which contain table's index. */
83
+ __getFunction(ptr: number): ((...args: unknown[]) => unknown) | null;
84
+
85
+ /** Allocates a new string in the module's memory and returns a reference (pointer) to it. */
86
+ __newString(str: string): number;
87
+ /** Allocates a new ArrayBuffer in the module's memory and returns a reference (pointer) to it. */
88
+ __newArrayBuffer(buf: ArrayBuffer): number;
89
+ /** Allocates a new array in the module's memory and returns a reference (pointer) to it. */
90
+ __newArray(id: number, valuesOrCapacity?: Array<number> | ArrayBufferView | number): number;
91
+
92
+ /** Allocates an instance of the class represented by the specified id. */
93
+ __new(size: number, id: number): number;
94
+ /** Pins a managed object externally, preventing it from becoming garbage collected. */
95
+ __pin(ptr: number): number;
96
+ /** Unpins a managed object externally, allowing it to become garbage collected. */
97
+ __unpin(ptr: number): void;
98
+ /** Performs a full garbage collection cycle. */
99
+ __collect(incremental?: boolean): void;
100
+ }
101
+
102
+ /** Asynchronously instantiates an AssemblyScript module from anything that can be instantiated. */
103
+ export declare function instantiate<T extends Record<string,unknown>>(
104
+ source: WebAssembly.Module | BufferSource | Response | PromiseLike<WebAssembly.Module | BufferSource | Response>,
105
+ imports?: Imports
106
+ ): Promise<ResultObject & { exports: ASUtil & T }>;
107
+
108
+ /** Synchronously instantiates an AssemblyScript module from a WebAssembly.Module or binary buffer. */
109
+ export declare function instantiateSync<T extends Record<string,unknown>>(
110
+ source: WebAssembly.Module | BufferSource,
111
+ imports?: Imports
112
+ ): ResultObject & { exports: ASUtil & T };
113
+
114
+ /** Asynchronously instantiates an AssemblyScript module from a response, i.e. as obtained by `fetch`. */
115
+ export declare function instantiateStreaming<T extends Record<string,unknown>>(
116
+ source: Response | PromiseLike<Response>,
117
+ imports?: Imports
118
+ ): Promise<ResultObject & { exports: ASUtil & T }>;
119
+
120
+ /** Demangles an AssemblyScript module's exports to a friendly object structure. */
121
+ export declare function demangle<T extends Record<string,unknown>>(
122
+ exports: Record<string,unknown>,
123
+ extendedExports?: Record<string,unknown>
124
+ ): T;