as-bind
as-bind copied to clipboard
Promise support
I’m publishing this Draft PR so folks can experiment with this. @torch2424 I’d love to hear what you think about this in general. I’m sure the code needs some cleaning up, but I’m pretty excited about having Web API Integration work almost out-of-the-box.
This PR gives as-bind the ability to handle async functions & promises in imported functions. This allows AssemblyScript modules to integrate with asynchronous web APIs like fetch()
, idb, createImageBitmap
, WebUSB, WebBluetooth, etc. You name it!
Example
// main. ts
declare function fetch(s: string): ArrayBuffer;
declare function log(s: string): void;
export function run(): void {
const buffer = fetch("/package.json"); // ⬅️ 🎉🎉🎉🎉
// Quick ad-hoc string decoding
const chars = Uint8Array.wrap(buffer);
let str = "";
for (let i = 0; i < chars.length; i++) {
str += String.fromCharCode(chars[i]);
}
log(str);
}
import { instantiate } from "as-bind";
import wasmUrl from "asc:./main.ts";
const instance = await instantiate(fetch(wasmUrl), {
main: {
log: (v) => (document.all.log.innerHTML += v + "\n"),
// This returns a Promise<ArrayBuffer>. as-bind utilizes asyncify
// to make the result appear synchronous to AS.
fetch: (v) => fetch(v).then((r) => r.arrayBuffer()),
},
});
instance.exports.run();
Screenshot
![Screenshot 2021-04-28 at 18 30 34](https://user-images.githubusercontent.com/234957/116447418-e272f700-a84f-11eb-8dee-4eea8b603ef9.png)
Here’s a gist with a working build system.
(In the future, you could even use externref
to hand Response, allowing you to import
fetch()` directly.)
How to use it
The heavy lifting is done under the hood by [Asyncify]. All you need to do is add --runPasses="asyncify"
to your asc
invocation, all the rest happens inside as-bind automatically.
If you want to play around with this, here are step-by-step instructions:
- Clone this repo and check out this branch
promise-support
- run
npm install && npm run build
- Clone the gist to somewhere else (or take any project that uses as-bind v0.7+)
- Run
npm link /path/to/as-bind/clone/from/step/1
- Add
--runPasses="asyncify"
to yourasc
invocation - ???
- Profit!
How does it work
WebAssembly is synchronous, and functions that are imported by your Wasm from the host environent (i.e. from JS) are expected to return their value synchronously. But the web is asynchronous. Making something synchronous look asynchronous is easy, but the other way is near impossible.
Asyncify is a tool (more specifically: a binaryen pass) that effectively allows you to “pause” WebAssembly. Through this, you can make make JS that’s asynchronous look synchronous to WebAssembly. The trick is to pause Wasm when an imported function returns a promise, wait until the promise has resolved and then unpause to continue with the resolved value. From the perspective of WebAssembly, it’s as if the pause never happened.
To facilitate the pausing and later resuming, Asyncify stores the current stack of the Wasm VM to memory. To avoid any corruption, as-bind allocates a block of memory via the runtime and prevents it from getting GC’d. How much memory is actually needed, depends on the number of function parameters and function calls you have on your stack. By default, as-bind reserves 8KiB per paused Wasm call. If you to change that number, add this just after instantiation: instance.asyncifyStorageSize = 8 * 1024;
.
What does it not do
This does not add support for async
/await
to AssemblyScript itself, nor is it a full integration of AS into JavaScript’s event loop.
Noteworthy limitations
Asyncify has a small performance and binary size impact. I have not seen it become a problem, but it’s something to be aware of. Because Asyncify rewrites your Wasm binary, it will probably break source maps. Also, Asyncify currently can’t handle externref
et al.
I'm not sure to understand. AsBind.instantiate(...)
already returns a promise
@StEvUgnIn Take a look at the code samples. The imported functions can return promises with this PR.
@surma This is perfect, just wondering if we can get this pulled into master?