Assemblyscript: Support Async/Await

Created on 14 Dec 2018  路  10Comments  路  Source: AssemblyScript/assemblyscript

A lot of existing typescript code relies on async/await. And while WASM doesn't have a concept of the JS event loop or generators, it seems like this could be supported in some form by first lowering to a style using switch/case like async/await leveling to ES3-style (See https://blog.mariusschulz.com/2016/12/09/typescript-2-1-async-await-for-es3-es5) in combination with an implementation of Promise in the runtime. Perhaps this code transformation could be copied from the TypeScript compiler, lowering it in the AST before compilation to WASM.

The resolve/reject functions would need to be exported to JS so that the JS could drive the resolution (e.g. Promise((resolve, reject) => {.... setTimeout(() => resolve()) }) would need to be rewritten by the developer so that resolve/reject are exported being passed through some kind of "AssemblyScriptPromise" class that can record when it's invoked and step the generator along. setTimeout is an external function which invokes the resolve)

Since interop with the web is invariably async, this would go a long way to reducing the impedance mismatch.

enhancement question

Most helpful comment

Something that might help here: Binaryen now has support for pausing and resuming wasm in the new "Bysyncify" feature. Basically you get some special functions to unwind/rewind the call stack, and then Bysyncify does everything else for you - that is, it will automatically rewrite all the wasm that needs to be rewritten so that it can save/restore locals and the call stack. This adds overhead as you'd expect, but it's surprisingly small both in size and speed (thanks to integration with the Binaryen optimizer).

Attached is a wip blogpost (not public yet) with more details, including complete examples in pure wasm and in JS+wasm, and benchmarks.

If you're interested to use this, let me know if I can help!

Bysyncify.pdf

All 10 comments

Absolutely, async/await is something that we'd like to support eventually. One missing building block at this point seems to be closure support, because promises and timeouts usually involve functions that access variables from a parent scope, though it might be possible to work around this with globals. Might also be good to note that deferring an operation to let's say the network or the filesystem is something that only the host can do at this point, and this leads us to interoperability concerns with the host, where waiting for host-bindings, reference-types et al. might make sense.

Is there a bug to track lambdas? It seems that this could be split into two or three useful partials:

1) lambdas with no captures
2) lambdas that capture by value (read-only, equivalent to Java or C++ captures)
3) lambdas that capture by reference (JS lambdas, C++ [&] lambdas)

1 seems straight forward, #2 seems straight forward if translated like Java or C++ does it with synthetic functor classes. #3 is a little more difficult since you'd need to promote a local to heap storage, but I think #1 and #2 probably capture most of the functional style of programming I see in JS and TS, and cases like #3 could be temporarily worked around by the developer by boxing the local into a holder object, and relying on support for case #2

Something that might help here: Binaryen now has support for pausing and resuming wasm in the new "Bysyncify" feature. Basically you get some special functions to unwind/rewind the call stack, and then Bysyncify does everything else for you - that is, it will automatically rewrite all the wasm that needs to be rewritten so that it can save/restore locals and the call stack. This adds overhead as you'd expect, but it's surprisingly small both in size and speed (thanks to integration with the Binaryen optimizer).

Attached is a wip blogpost (not public yet) with more details, including complete examples in pure wasm and in JS+wasm, and benchmarks.

If you're interested to use this, let me know if I can help!

Bysyncify.pdf

Thanks for the hint (and for making it possible in the first place ofc)! There are multiple things that I think can make use of the bysyncify pass, like

  • async/await once we have an idea how to design a Promise implementation and/or binding
  • Preliminary exception handling?
  • Re-implementations of fs.readFile etc. in context of https://github.com/AssemblyScript/assemblyscript/pull/708 (Edit: Well, that's actually just a callback)
  • Maybe more?

The most important at this point seems to get something-exception-handling up, but I haven't thought about how feasible this would be with bysyncify yet. Would you say that makes sense? Also, there is the mention of "using the option bysyncify-imports to Bysyncify" which I think is not yet possible with the C-API, but that's certainly not a blocker and can be introduced with a PR, if it turns out we need it.

Edit: Just a crazy idea, but if it turns out that bysyncify can help with preliminary exception handling, would it be possible to "polyfill" it on that basis? Like, make actual try/catch blocks, and it would downlevel with a pass?

Yeah, we could add a pass to lower wasm exceptions into a polyfill basically. Bysyncify has some useful tools for that, like analyzing which calls would need to be instrumented, but the new pass wouldn't need to think about locals etc., so it should have even less overhead.

I believe @aheejin will work on exceptions in binaryen soon. After that's done we can add a lowering/polyfill pass, should be straightforward (I can do it, if no one else wants to).

Nice, that would be super useful! In the meantime we can start thinking about Promises over here I guess :)

Interesting article about stateless and stateful (fiber) coroutines:
http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2018/p1364r0.pdf

For async / await which import / export to/from host we could use binaryen's Asyncify, but for internal async / awayt just generate stateless coroutines or generators via FSM approach similar to Python, C# and facebook's regenerator

@MaxGraey are there examples somewhere showing the unwind/rewind being used in Assemblyscript?

For host side I saw only this wrapper. In general, you might be inspired this blog post probably

Was this page helpful?
0 / 5 - 0 ratings