From 4a92f567dcaf2e235d80197a5025da0f9ae733d1 Mon Sep 17 00:00:00 2001 From: dcode Date: Thu, 17 Oct 2019 17:14:32 +0200 Subject: [PATCH 1/5] Align loader API with WebAssembly API --- lib/loader/README.md | 17 ++++++++++------- lib/loader/index.d.ts | 12 ++++++------ lib/loader/index.js | 24 ++++++++++++++++-------- lib/loader/tests/index.js | 2 +- 4 files changed, 33 insertions(+), 22 deletions(-) diff --git a/lib/loader/README.md b/lib/loader/README.md index 130c6b090a..7084202d04 100644 --- a/lib/loader/README.md +++ b/lib/loader/README.md @@ -14,14 +14,14 @@ const loader = require("assemblyscript/lib/loader"); API --- -* **instantiate**<`T`>(module: `WebAssembly.Module`, imports?: `WasmImports`): `ASUtil & T`
- Instantiates an AssemblyScript module using the specified imports. +* **instantiate**<`T`>(moduleOrBuffer: `WebAssembly.Module | BufferSource`, imports?: `WasmImports`): `Promise`
+ Asynchronously instantiates an AssemblyScript module from a module or buffer using the specified imports. -* **instantiateBuffer**<`T`>(buffer: `Uint8Array`, imports?: `WasmImports`): `ASUtil & T`
- Instantiates an AssemblyScript module from a buffer using the specified imports. +* **instantiateSync**<`T`>(moduleOrBuffer: `WebAssembly.Module | BufferSource`, imports?: `WasmImports`): `ASUtil & T`
+ Synchronously instantiates an AssemblyScript module from a module or buffer using the specified imports. * **instantiateStreaming**<`T`>(response: `Response`, imports?: `WasmImports`): `Promise`
- Instantiates an AssemblyScript module from a response using the specified imports. + Asynchronously instantiates an AssemblyScript module from a response object using the specified imports. * **demangle**<`T`>(exports: `WasmExports`, baseModule?: `Object`): `T`
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. @@ -141,10 +141,13 @@ Examples ```js // From a module provided as a buffer, i.e. as returned by fs.readFileSync -const myModule = loader.instantiateBuffer(fs.readFileSync("myModule.wasm"), myImports); +const myModule = await loader.instantiate(fs.readFileSync("myModule.wasm"), myImports); // From a response object, i.e. as returned by window.fetch const myModule = await loader.instantiateStreaming(fetch("myModule.wasm"), myImports); + +// Synchronously, i.e. if the goal is to immediately re-export as a node module +const myModule = loader.instantiateSync(fs.readFileSync("myModule.wasm"), myImports); ``` ### Usage with TypeScript definitions produced by the compiler @@ -152,5 +155,5 @@ const myModule = await loader.instantiateStreaming(fetch("myModule.wasm"), myImp ```ts import MyModule from "myModule"; // pointing at the d.ts -const myModule = loader.instatiateBuffer(fs.readFileSync("myModule.wasm"), myImports); +const myModule = await loader.instatiate(fs.readFileSync("myModule.wasm"), myImports); ``` diff --git a/lib/loader/index.d.ts b/lib/loader/index.d.ts index 2deb18e506..dec9554493 100644 --- a/lib/loader/index.d.ts +++ b/lib/loader/index.d.ts @@ -65,14 +65,14 @@ interface ASUtil { __collect(): void; } -/** Instantiates an AssemblyScript module using the specified imports. */ -export declare function instantiate(module: WebAssembly.Module, imports?: ImportsObject): ASUtil & T; +/** Asynchronously instantiates an AssemblyScript module from a module or buffer using the specified imports. */ +export declare function instantiate(moduleOrBuffer: WebAssembly.Module | BufferSource, imports?: ImportsObject): Promise; -/** Instantiates an AssemblyScript module from a buffer using the specified imports. */ -export declare function instantiateBuffer(buffer: BufferSource, imports?: ImportsObject): ASUtil & T; +/** Synchronously instantiates an AssemblyScript module from a module or buffer using the specified imports. */ +export declare function instantiateSync(moduleOrBuffer: WebAssembly.Module | BufferSource, imports?: ImportsObject): ASUtil & T; -/** Instantiates an AssemblyScript module from a response using the specified imports. */ -export declare function instantiateStreaming(result: Response | PromiseLike, imports?: ImportsObject): Promise; +/** Asynchronously instantiates an AssemblyScript module from a response object using the specified imports. */ +export declare function instantiateStreaming(response: Response | PromiseLike, imports?: ImportsObject): Promise; /** Demangles an AssemblyScript module's exports to a friendly object structure. */ export declare function demangle(exports: {}, baseModule?: {}): T; diff --git a/lib/loader/index.js b/lib/loader/index.js index a9b4bd5adc..31edc9c7d8 100644 --- a/lib/loader/index.js +++ b/lib/loader/index.js @@ -280,24 +280,32 @@ function wrapFunction(fn, setargc) { return wrap; } -/** Instantiates an AssemblyScript module using the specified imports. */ -function instantiate(module, imports) { +/** Asynchronously instantiates an AssemblyScript module from a module or buffer using the specified imports. */ +async function instantiate(moduleOrBuffer, imports) { return postInstantiate( preInstantiate(imports || (imports = {})), - new WebAssembly.Instance(module, imports) + (await WebAssembly.instantiate(moduleOrBuffer, imports)).instance ); } exports.instantiate = instantiate; -/** Instantiates an AssemblyScript module from a buffer using the specified imports. */ -function instantiateBuffer(buffer, imports) { - return instantiate(new WebAssembly.Module(buffer), imports); +/** Synchronously instantiates an AssemblyScript module from a module or buffer using the specified imports. */ +function instantiateSync(moduleOrBuffer, imports) { + return postInstantiate( + preInstantiate(imports || (imports = {})), + new WebAssembly.Instance( + moduleOrBuffer instanceof ArrayBuffer || ArrayBuffer.isView(moduleOrBuffer) + ? new WebAssembly.Module(moduleOrBuffer) + : moduleOrBuffer, + imports + ) + ) } -exports.instantiateBuffer = instantiateBuffer; +exports.instantiateSync = instantiateSync; -/** Instantiates an AssemblyScript module from a response using the specified imports. */ +/** Asynchronously instantiates an AssemblyScript module from a response object using the specified imports. */ async function instantiateStreaming(response, imports) { return postInstantiate( preInstantiate(imports || (imports = {})), diff --git a/lib/loader/tests/index.js b/lib/loader/tests/index.js index 9322238fe4..1f3320851c 100644 --- a/lib/loader/tests/index.js +++ b/lib/loader/tests/index.js @@ -4,7 +4,7 @@ var inspect = require("util").inspect; var loader = require(".."); var buffer = fs.readFileSync(__dirname + "/build/untouched.wasm"); -var module = loader.instantiateBuffer(buffer, {}); +var module = loader.instantiateSync(buffer, {}); console.log(inspect(module, true, 100, true)); From fd09c44db98c03ad3091bd27b6c60698f22df2fa Mon Sep 17 00:00:00 2001 From: dcode Date: Thu, 17 Oct 2019 18:41:31 +0200 Subject: [PATCH 2/5] Make instantiate universal --- lib/loader/README.md | 4 ++-- lib/loader/index.d.ts | 4 ++-- lib/loader/index.js | 10 ++++++---- lib/loader/tests/index.html | 4 ++++ 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/loader/README.md b/lib/loader/README.md index 7084202d04..ac1e1d3352 100644 --- a/lib/loader/README.md +++ b/lib/loader/README.md @@ -14,8 +14,8 @@ const loader = require("assemblyscript/lib/loader"); API --- -* **instantiate**<`T`>(moduleOrBuffer: `WebAssembly.Module | BufferSource`, imports?: `WasmImports`): `Promise`
- Asynchronously instantiates an AssemblyScript module from a module or buffer using the specified imports. +* **instantiate**<`T`>(moduleOrBuffer: `WebAssembly.Module | BufferSource | Response`, imports?: `WasmImports`): `Promise`
+ Asynchronously instantiates an AssemblyScript module from a module, buffer or response using the specified imports. * **instantiateSync**<`T`>(moduleOrBuffer: `WebAssembly.Module | BufferSource`, imports?: `WasmImports`): `ASUtil & T`
Synchronously instantiates an AssemblyScript module from a module or buffer using the specified imports. diff --git a/lib/loader/index.d.ts b/lib/loader/index.d.ts index dec9554493..c5d3978827 100644 --- a/lib/loader/index.d.ts +++ b/lib/loader/index.d.ts @@ -65,8 +65,8 @@ interface ASUtil { __collect(): void; } -/** Asynchronously instantiates an AssemblyScript module from a module or buffer using the specified imports. */ -export declare function instantiate(moduleOrBuffer: WebAssembly.Module | BufferSource, imports?: ImportsObject): Promise; +/** Asynchronously instantiates an AssemblyScript module from a module, buffer or response using the specified imports. */ +export declare function instantiate(moduleOrBuffer: WebAssembly.Module | BufferSource | Response | PromiseLike, imports?: ImportsObject): Promise; /** Synchronously instantiates an AssemblyScript module from a module or buffer using the specified imports. */ export declare function instantiateSync(moduleOrBuffer: WebAssembly.Module | BufferSource, imports?: ImportsObject): ASUtil & T; diff --git a/lib/loader/index.js b/lib/loader/index.js index 31edc9c7d8..5fa1606142 100644 --- a/lib/loader/index.js +++ b/lib/loader/index.js @@ -280,8 +280,10 @@ function wrapFunction(fn, setargc) { return wrap; } -/** Asynchronously instantiates an AssemblyScript module from a module or buffer using the specified imports. */ +/** Asynchronously instantiates an AssemblyScript module from a module, buffer or response using the specified imports. */ async function instantiate(moduleOrBuffer, imports) { + if (typeof moduleOrBuffer.then === "function") moduleOrBuffer = await moduleOrBuffer; + if (typeof Response !== "undefined" && moduleOrBuffer instanceof Response) return instantiateStreaming(moduleOrBuffer, imports); return postInstantiate( preInstantiate(imports || (imports = {})), (await WebAssembly.instantiate(moduleOrBuffer, imports)).instance @@ -295,9 +297,9 @@ function instantiateSync(moduleOrBuffer, imports) { return postInstantiate( preInstantiate(imports || (imports = {})), new WebAssembly.Instance( - moduleOrBuffer instanceof ArrayBuffer || ArrayBuffer.isView(moduleOrBuffer) - ? new WebAssembly.Module(moduleOrBuffer) - : moduleOrBuffer, + moduleOrBuffer instanceof WebAssembly.Module + ? moduleOrBuffer + : new WebAssembly.Module(moduleOrBuffer), imports ) ) diff --git a/lib/loader/tests/index.html b/lib/loader/tests/index.html index 323ccd2299..de79f32b87 100644 --- a/lib/loader/tests/index.html +++ b/lib/loader/tests/index.html @@ -1,6 +1,10 @@ From 35440be115f8235b57247815a8896e1c17187724 Mon Sep 17 00:00:00 2001 From: dcode Date: Thu, 17 Oct 2019 19:36:04 +0200 Subject: [PATCH 4/5] docs --- lib/loader/README.md | 10 +++++----- lib/loader/index.d.ts | 6 +++--- lib/loader/index.js | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/loader/README.md b/lib/loader/README.md index ac1e1d3352..c9730a13d6 100644 --- a/lib/loader/README.md +++ b/lib/loader/README.md @@ -14,14 +14,14 @@ const loader = require("assemblyscript/lib/loader"); API --- -* **instantiate**<`T`>(moduleOrBuffer: `WebAssembly.Module | BufferSource | Response`, imports?: `WasmImports`): `Promise`
- Asynchronously instantiates an AssemblyScript module from a module, buffer or response using the specified imports. +* **instantiate**<`T`>(moduleOrBuffer: `WebAssembly.Module | BufferSource | Response | PromiseLike`, imports?: `WasmImports`): `Promise`
+ Asynchronously instantiates an AssemblyScript module from anything that can be instantiated. * **instantiateSync**<`T`>(moduleOrBuffer: `WebAssembly.Module | BufferSource`, imports?: `WasmImports`): `ASUtil & T`
- Synchronously instantiates an AssemblyScript module from a module or buffer using the specified imports. + Synchronously instantiates an AssemblyScript module from a WebAssembly.Module or binary buffer. -* **instantiateStreaming**<`T`>(response: `Response`, imports?: `WasmImports`): `Promise`
- Asynchronously instantiates an AssemblyScript module from a response object using the specified imports. +* **instantiateStreaming**<`T`>(response: `Response | PromiseLike`, imports?: `WasmImports`): `Promise`
+ Asynchronously instantiates an AssemblyScript module from a response, i.e. as obtained by `fetch`. * **demangle**<`T`>(exports: `WasmExports`, baseModule?: `Object`): `T`
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. diff --git a/lib/loader/index.d.ts b/lib/loader/index.d.ts index c5d3978827..f58bef69c1 100644 --- a/lib/loader/index.d.ts +++ b/lib/loader/index.d.ts @@ -65,13 +65,13 @@ interface ASUtil { __collect(): void; } -/** Asynchronously instantiates an AssemblyScript module from a module, buffer or response using the specified imports. */ +/** Asynchronously instantiates an AssemblyScript module from anything that can be instantiated. */ export declare function instantiate(moduleOrBuffer: WebAssembly.Module | BufferSource | Response | PromiseLike, imports?: ImportsObject): Promise; -/** Synchronously instantiates an AssemblyScript module from a module or buffer using the specified imports. */ +/** Synchronously instantiates an AssemblyScript module from a WebAssembly.Module or binary buffer. */ export declare function instantiateSync(moduleOrBuffer: WebAssembly.Module | BufferSource, imports?: ImportsObject): ASUtil & T; -/** Asynchronously instantiates an AssemblyScript module from a response object using the specified imports. */ +/** Asynchronously instantiates an AssemblyScript module from a response, i.e. as obtained by `fetch`. */ export declare function instantiateStreaming(response: Response | PromiseLike, imports?: ImportsObject): Promise; /** Demangles an AssemblyScript module's exports to a friendly object structure. */ diff --git a/lib/loader/index.js b/lib/loader/index.js index e9fee977dc..09047a51e8 100644 --- a/lib/loader/index.js +++ b/lib/loader/index.js @@ -280,7 +280,7 @@ function wrapFunction(fn, setargc) { return wrap; } -/** Asynchronously instantiates an AssemblyScript module from a module, buffer or response using the specified imports. */ +/** Asynchronously instantiates an AssemblyScript module from anything that can be instantiated. */ async function instantiate(moduleOrBuffer, imports) { if (typeof moduleOrBuffer.then === "function") moduleOrBuffer = await moduleOrBuffer; if (typeof Response !== "undefined" && moduleOrBuffer instanceof Response) return instantiateStreaming(moduleOrBuffer, imports); @@ -292,7 +292,7 @@ async function instantiate(moduleOrBuffer, imports) { exports.instantiate = instantiate; -/** Synchronously instantiates an AssemblyScript module from a module or buffer using the specified imports. */ +/** Synchronously instantiates an AssemblyScript module from a WebAssembly.Module or binary buffer. */ function instantiateSync(moduleOrBuffer, imports) { return postInstantiate( preInstantiate(imports || (imports = {})), @@ -307,7 +307,7 @@ function instantiateSync(moduleOrBuffer, imports) { exports.instantiateSync = instantiateSync; -/** Asynchronously instantiates an AssemblyScript module from a response object using the specified imports. */ +/** Asynchronously instantiates an AssemblyScript module from a response, i.e. as obtained by `fetch`. */ async function instantiateStreaming(response, imports) { if (!WebAssembly.instantiateStreaming) return instantiate(await (await response).arrayBuffer(), imports); return postInstantiate( From b3c408f35314f393a44127f5b066d654e5e1f1bc Mon Sep 17 00:00:00 2001 From: dcode Date: Fri, 18 Oct 2019 12:26:59 +0200 Subject: [PATCH 5/5] more cases, more tests --- lib/loader/README.md | 30 +++++++++++++++++++++++------- lib/loader/index.d.ts | 6 +++--- lib/loader/index.js | 37 ++++++++++++++++++++++++++----------- lib/loader/tests/index.html | 25 +++++++++++++++---------- lib/loader/tests/index.js | 25 +++++++++++++++++++++++++ 5 files changed, 92 insertions(+), 31 deletions(-) diff --git a/lib/loader/README.md b/lib/loader/README.md index c9730a13d6..57bbb6dc91 100644 --- a/lib/loader/README.md +++ b/lib/loader/README.md @@ -139,21 +139,37 @@ Examples ### Instantiating a module -```js -// From a module provided as a buffer, i.e. as returned by fs.readFileSync -const myModule = await loader.instantiate(fs.readFileSync("myModule.wasm"), myImports); +The **asynchronous API** is analogous to [WebAssembly.instantiate](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiate) and [WebAssembly.instantiateStreaming](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiateStreaming) -// From a response object, i.e. as returned by window.fetch +```js +const myModule = await loader.instantiate(myModuleBuffer, myImports); const myModule = await loader.instantiateStreaming(fetch("myModule.wasm"), myImports); +``` + +with `loader.instantiate` actually accepting anything that can be instantiated for convenience: -// Synchronously, i.e. if the goal is to immediately re-export as a node module -const myModule = loader.instantiateSync(fs.readFileSync("myModule.wasm"), myImports); +```js +const myModule = await loader.instantiate(fs.promises.readFile("myModule.wasm"), myImports); +const myModule = await loader.instantiate(fetch("myModule.wasm"), myImports); +... ``` +If `WebAssembly.instantiateStreaming` is not supported by the environment a fallback is applied. + +The **synchronous API** utilizes [new WebAssembly.Instance](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Instance#Constructor_Syntax) and [new WebAssembly.Module](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Module#Constructor_Syntax), which is useful if the goal is to immediately re-export as a node module for example: + +```js +module.exports = loader.instantiateSync(fs.readFileSync("myModule.wasm"), myImports); +``` + +Note, though, that browsers have relatively tight limits for synchronous compilation and instantiation because these block the main thread, hence it is recommended to use the asynchronous API in browsers. + ### Usage with TypeScript definitions produced by the compiler +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: + ```ts import MyModule from "myModule"; // pointing at the d.ts -const myModule = await loader.instatiate(fs.readFileSync("myModule.wasm"), myImports); +const myModule = await loader.instatiate(fs.promises.readFile("myModule.wasm"), myImports); ``` diff --git a/lib/loader/index.d.ts b/lib/loader/index.d.ts index f58bef69c1..476ad1374f 100644 --- a/lib/loader/index.d.ts +++ b/lib/loader/index.d.ts @@ -66,13 +66,13 @@ interface ASUtil { } /** Asynchronously instantiates an AssemblyScript module from anything that can be instantiated. */ -export declare function instantiate(moduleOrBuffer: WebAssembly.Module | BufferSource | Response | PromiseLike, imports?: ImportsObject): Promise; +export declare function instantiate(source: WebAssembly.Module | BufferSource | Response | PromiseLike, imports?: ImportsObject): Promise; /** Synchronously instantiates an AssemblyScript module from a WebAssembly.Module or binary buffer. */ -export declare function instantiateSync(moduleOrBuffer: WebAssembly.Module | BufferSource, imports?: ImportsObject): ASUtil & T; +export declare function instantiateSync(source: WebAssembly.Module | BufferSource, imports?: ImportsObject): ASUtil & T; /** Asynchronously instantiates an AssemblyScript module from a response, i.e. as obtained by `fetch`. */ -export declare function instantiateStreaming(response: Response | PromiseLike, imports?: ImportsObject): Promise; +export declare function instantiateStreaming(source: Response | PromiseLike, imports?: ImportsObject): Promise; /** Demangles an AssemblyScript module's exports to a friendly object structure. */ export declare function demangle(exports: {}, baseModule?: {}): T; diff --git a/lib/loader/index.js b/lib/loader/index.js index 09047a51e8..8ed61bd0bb 100644 --- a/lib/loader/index.js +++ b/lib/loader/index.js @@ -280,26 +280,34 @@ function wrapFunction(fn, setargc) { return wrap; } +function isResponse(o) { + return typeof Response !== "undefined" && o instanceof Response; +} + /** Asynchronously instantiates an AssemblyScript module from anything that can be instantiated. */ -async function instantiate(moduleOrBuffer, imports) { - if (typeof moduleOrBuffer.then === "function") moduleOrBuffer = await moduleOrBuffer; - if (typeof Response !== "undefined" && moduleOrBuffer instanceof Response) return instantiateStreaming(moduleOrBuffer, imports); +async function instantiate(source, imports) { + if (isResponse(source = await source)) return instantiateStreaming(source, imports); return postInstantiate( preInstantiate(imports || (imports = {})), - (await WebAssembly.instantiate(moduleOrBuffer, imports)).instance + await WebAssembly.instantiate( + source instanceof WebAssembly.Module + ? source + : await WebAssembly.compile(source), + imports + ) ); } exports.instantiate = instantiate; /** Synchronously instantiates an AssemblyScript module from a WebAssembly.Module or binary buffer. */ -function instantiateSync(moduleOrBuffer, imports) { +function instantiateSync(source, imports) { return postInstantiate( preInstantiate(imports || (imports = {})), new WebAssembly.Instance( - moduleOrBuffer instanceof WebAssembly.Module - ? moduleOrBuffer - : new WebAssembly.Module(moduleOrBuffer), + source instanceof WebAssembly.Module + ? source + : new WebAssembly.Module(source), imports ) ) @@ -308,11 +316,18 @@ function instantiateSync(moduleOrBuffer, imports) { exports.instantiateSync = instantiateSync; /** Asynchronously instantiates an AssemblyScript module from a response, i.e. as obtained by `fetch`. */ -async function instantiateStreaming(response, imports) { - if (!WebAssembly.instantiateStreaming) return instantiate(await (await response).arrayBuffer(), imports); +async function instantiateStreaming(source, imports) { + if (!WebAssembly.instantiateStreaming) { + return instantiate( + isResponse(source = await source) + ? source.arrayBuffer() + : source, + imports + ); + } return postInstantiate( preInstantiate(imports || (imports = {})), - (await WebAssembly.instantiateStreaming(response, imports)).instance + (await WebAssembly.instantiateStreaming(source, imports)).instance ); } diff --git a/lib/loader/tests/index.html b/lib/loader/tests/index.html index 6a907b8f88..68a98e4c22 100644 --- a/lib/loader/tests/index.html +++ b/lib/loader/tests/index.html @@ -1,42 +1,47 @@ diff --git a/lib/loader/tests/index.js b/lib/loader/tests/index.js index 1f3320851c..4b760c53c7 100644 --- a/lib/loader/tests/index.js +++ b/lib/loader/tests/index.js @@ -173,3 +173,28 @@ module.dotrace(42); assert.deepEqual(view, new Float32Array([3, 2, 1])); module.__release(ptr); } + +// should be able to instantiate from a buffer +(async () => { + const module = await loader.instantiate(fs.readFileSync(__dirname + "/build/untouched.wasm"), {}); + assert(module.memory); +})(); + +// should be able to instantiate from a wasm module +(async () => { + const wasmModule = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/untouched.wasm")); + const module = await loader.instantiate(wasmModule, {}); + assert(module.memory); +})(); + +// should be able to instantiate from a promise yielding a buffer +(async () => { + const module = await loader.instantiate(fs.promises.readFile(__dirname + "/build/untouched.wasm"), {}); + assert(module.memory); +})(); + +// should be able to mimic instantiateStreaming under node (for now) +(async () => { + const module = await loader.instantiateStreaming(fs.promises.readFile(__dirname + "/build/untouched.wasm"), {}); + assert(module.memory); +})();