-
Notifications
You must be signed in to change notification settings - Fork 5
Description
I did a bit of research into how other WASM setups handled Promises and ended up finding Emscripten for C. They have two main approaches: asyncify and JSPI (JS-Promise-Integration). Asyncify relies on Emscripten compiling and modifying the code to something that sounds like a state machine in order to allow it to pause/resume for promises. That's probably beyond the scope of this library (especially since this isn't a compiler), but their second method, JS-Promise-Integration, seems much more promising.
JS-Promise-Integration (or JSPI) is an experimental feature that is only partially implemented in Chrome. Because of that, we probably shouldn't support it quite yet, however, I wanted to create this issue for if/when it's fully implemented in the future since it seems like a way to simplify the current async module implementation.
The JSPI proposal overview talks about adding two main wrapper objects. new WebAssembly.Suspending(jsFunc) and WebAssembly.promising(wasmFunc). When a Suspending object is passed as a WASM import, it allows any calls made to it from WASM to suspend the WASM VM (virtual machine) until the wrapped function is finished. Then, the promising wrapper allows wasm functions called from JS to return Promises. This way Suspending allows WASM to call async functions, and promising allows WASM functions to be async.
In addition to suspending the VM, it seems to also have some way of offloading or saving the stack such that calls can still be made to the WASM VM while another function is suspended. I believe that this allows it to run in a similar method to normal async calls in JavaScript where everything is handled by the background event loop.
As for implementing it twr-wasm, I think the main issue would be marking WASM exports. Imports could be marked similarly to how they are done now, however, I'm not sure how exports could be annotated. Though, the proposal says that if a WASM function wrapped with promising never makes a call to a Suspending function it just returns a fulfilled promise. With that in mind, an async module could just wrap every function with promising to ensure that every function that makes async calls can do so.
Example:
#include <stdio.h>
__attribute__((import_name("sleep"))
void sleep(int timeout_ms);
__attribute__((export_name("printLoop"))
void print_loop() {
for (int i = 0; i < 10; i++) {
printf("Hello World!\n");
sleep(1000);
}
}function sleep(timeoutMS) {
return new Promise((resolve, reject) => {
settimeout(
() => resolve(),
timeoutMS
);
});
}
var sampleModule = new WebAssembly.Instance(
demoBuffer,
{
js: {
sleep: new WebAssembly.Suspending(sleep)
}
);
const printLoop = WebAssembly.promising(sampleModule.exports.printLoop);
//should print "Hello World" 10 times with a second between each print
await printLoop();