Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 30 additions & 11 deletions lib/loader/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ const loader = require("assemblyscript/lib/loader");
API
---

* **instantiate**<`T`>(module: `WebAssembly.Module`, imports?: `WasmImports`): `ASUtil & T`<br />
Instantiates an AssemblyScript module using the specified imports.
* **instantiate**<`T`>(moduleOrBuffer: `WebAssembly.Module | BufferSource | Response | PromiseLike<WebAssembly.Module | BufferSource | Response>`, imports?: `WasmImports`): `Promise<ASUtil & T>`<br />
Asynchronously instantiates an AssemblyScript module from anything that can be instantiated.

* **instantiateBuffer**<`T`>(buffer: `Uint8Array`, imports?: `WasmImports`): `ASUtil & T`<br />
Instantiates an AssemblyScript module from a buffer using the specified imports.
* **instantiateSync**<`T`>(moduleOrBuffer: `WebAssembly.Module | BufferSource`, imports?: `WasmImports`): `ASUtil & T`<br />
Synchronously instantiates an AssemblyScript module from a WebAssembly.Module or binary buffer.

* **instantiateStreaming**<`T`>(response: `Response`, imports?: `WasmImports`): `Promise<ASUtil & T>`<br />
Instantiates an AssemblyScript module from a response using the specified imports.
* **instantiateStreaming**<`T`>(response: `Response | PromiseLike<Response>`, imports?: `WasmImports`): `Promise<ASUtil & T>`<br />
Asynchronously instantiates an AssemblyScript module from a response, i.e. as obtained by `fetch`.

* **demangle**<`T`>(exports: `WasmExports`, baseModule?: `Object`): `T`<br />
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.
Expand Down Expand Up @@ -139,18 +139,37 @@ Examples

### Instantiating a module

```js
// From a module provided as a buffer, i.e. as returned by fs.readFileSync
const myModule = loader.instantiateBuffer(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:

```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 = loader.instatiateBuffer<typeof MyModule>(fs.readFileSync("myModule.wasm"), myImports);
const myModule = await loader.instatiate<typeof MyModule>(fs.promises.readFile("myModule.wasm"), myImports);
```
12 changes: 6 additions & 6 deletions lib/loader/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,14 @@ interface ASUtil {
__collect(): void;
}

/** Instantiates an AssemblyScript module using the specified imports. */
export declare function instantiate<T extends {}>(module: WebAssembly.Module, imports?: ImportsObject): ASUtil & T;
/** Asynchronously instantiates an AssemblyScript module from anything that can be instantiated. */
export declare function instantiate<T extends {}>(source: WebAssembly.Module | BufferSource | Response | PromiseLike<WebAssembly.Module | BufferSource | Response>, imports?: ImportsObject): Promise<ASUtil & T>;

/** Instantiates an AssemblyScript module from a buffer using the specified imports. */
export declare function instantiateBuffer<T extends {}>(buffer: BufferSource, imports?: ImportsObject): ASUtil & T;
/** Synchronously instantiates an AssemblyScript module from a WebAssembly.Module or binary buffer. */
export declare function instantiateSync<T extends {}>(source: WebAssembly.Module | BufferSource, imports?: ImportsObject): ASUtil & T;

/** Instantiates an AssemblyScript module from a response using the specified imports. */
export declare function instantiateStreaming<T extends {}>(result: Response | PromiseLike<Response>, imports?: ImportsObject): Promise<ASUtil & T>;
/** Asynchronously instantiates an AssemblyScript module from a response, i.e. as obtained by `fetch`. */
export declare function instantiateStreaming<T extends {}>(source: Response | PromiseLike<Response>, imports?: ImportsObject): Promise<ASUtil & T>;

/** Demangles an AssemblyScript module's exports to a friendly object structure. */
export declare function demangle<T extends {}>(exports: {}, baseModule?: {}): T;
48 changes: 37 additions & 11 deletions lib/loader/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -280,28 +280,54 @@ function wrapFunction(fn, setargc) {
return wrap;
}

/** Instantiates an AssemblyScript module using the specified imports. */
function instantiate(module, imports) {
function isResponse(o) {
return typeof Response !== "undefined" && o instanceof Response;
}

/** Asynchronously instantiates an AssemblyScript module from anything that can be instantiated. */
async function instantiate(source, imports) {
if (isResponse(source = await source)) return instantiateStreaming(source, imports);
return postInstantiate(
preInstantiate(imports || (imports = {})),
new WebAssembly.Instance(module, imports)
await WebAssembly.instantiate(
source instanceof WebAssembly.Module
? source
: await WebAssembly.compile(source),
imports
)
);
}

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 WebAssembly.Module or binary buffer. */
function instantiateSync(source, imports) {
return postInstantiate(
preInstantiate(imports || (imports = {})),
new WebAssembly.Instance(
source instanceof WebAssembly.Module
? source
: new WebAssembly.Module(source),
imports
)
)
}

exports.instantiateBuffer = instantiateBuffer;

/** Instantiates an AssemblyScript module from a response using the specified imports. */
async function instantiateStreaming(response, imports) {
exports.instantiateSync = instantiateSync;

/** Asynchronously instantiates an AssemblyScript module from a response, i.e. as obtained by `fetch`. */
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
);
}

Expand Down
43 changes: 41 additions & 2 deletions lib/loader/tests/index.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,47 @@
<script>var exports = {};</script>
<script src="../index.js"></script>
<script>
function assert(c) {
if (!c) throw Error("assertion failed");
}
(async () => {
var module = await exports.instantiateStreaming(fetch("./build/untouched.wasm"));
console.log(module);
var module;

module = await exports.instantiate(fetch("./build/untouched.wasm"));
assert(module.memory);

module = await exports.instantiate(await fetch("./build/untouched.wasm"));
assert(module.memory);

module = await exports.instantiate((await fetch("./build/untouched.wasm")).arrayBuffer());
assert(module.memory);

module = await exports.instantiate(await (await fetch("./build/untouched.wasm")).arrayBuffer());
assert(module.memory);

module = await exports.instantiateStreaming(fetch("./build/untouched.wasm"));
assert(module.memory);

module = await exports.instantiateStreaming(await fetch("./build/untouched.wasm"));
assert(module.memory);

var instantiateStreaming = WebAssembly.instantiateStreaming;
delete WebAssembly.instantiateStreaming;

module = await exports.instantiate(fetch("./build/untouched.wasm"));
assert(module.memory);

module = await exports.instantiate(await fetch("./build/untouched.wasm"));
assert(module.memory);

module = await exports.instantiateStreaming(fetch("./build/untouched.wasm"));
assert(module.memory);

module = await exports.instantiateStreaming(await fetch("./build/untouched.wasm"));
assert(module.memory);

WebAssembly.instantiateStreaming = instantiateStreaming;

console.log("ok");
})();
</script>
27 changes: 26 additions & 1 deletion lib/loader/tests/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand Down Expand Up @@ -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);
})();