-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WebAssembly Exceptions #13130
Comments
Thanks for the detailed report Guilherme! I agree that 2 sounds like the best option, but I fear it might not be worth the effort once the other platforms starts supporting it... But I think you're the best one to decide what to do —and to do it, if I'm honest with you... |
In the future we should support proper wasm exception handling without workarounds, and enable it by default as soon as the rest of the ecosystem does the same. In the mean time, having the workaround (the ast transformation with asyncify) seems the best option. That said, I have been trying to enable wasm exception handling on LLVM and I completely failed the task.
This almost looks like a big proof of concept at this point. The end goal is to build existing C++ applications with exceptions for the Web, and they are doing it successfully. Nothing else. I'll be looking at implementing the AST transformation, given that this is our only viable path for now. |
WebAssembly began by focusing on very low level languages (primarily C and C++) and is only now getting support for more "high level" features such as exceptions and a native GC. Crystal needs exceptions to properly function and we need to investigate a way to implement it.
Part 1: the "exception handling" proposal:
There is a ongoing proposal to implement native "throw" and "catch" instructions. Here every exception has a numeric id and we can catch by the type. It fits well into Crystal's model. The proposal itself is still evolving and hasn't been accepted yet. It is still receiving changes, but they are mostly clarifications.
The following tools and runtimes implement the current proposal:
The following doesn't implement it:
Those are pretty significant runtimes outside the browser. They are used mostly in the backend space with serverless offerings. Also, they doesn't see exception handling as something very important to implement, in general.
Part 2: how other languages do it?
C++:
It uses the Emscripten toolkit to build C++ into Wasm. It has two modes of operation: using JavaScript exceptions or the exception handling proposal. JS-based exceptions works by calling into JavaScript and having a try-catch block there, that in turn calls into Wasm. Throwing an exception works by invoking JavaScript again to
throw
. Finally, it can be built with exceptions disabled, and many C++ libraries work well without exceptions.Go:
Both Go and TinyGo panics when you try to use the
recover
builtin to catch a panic-ing goroutine. That's fine because most Go programs don't need that to function..NET Blazor:
Uses Emscripten and primarily targets the browser. It uses the exception handling proposal.
Ruby:
Implement
setjmp
/longjmp
on top of Asyncify, and then use those to implement exceptions in the interpreter. I believe Python does the same.Dart/Flutter:
Uses the exception handling proposal.
Part 3: the action plan
Given that simply adding two numbers can raise an exception in Crystal, I don't think we can go very far without some kind of exception support.
We can enable the experimental wasm exception emit on LLVM and have it do all the heavy work for us. I'm not sure how to do it yet, but this is clearly the future-proof path. Today it will mean we would be primarily targeting the browser/node.js and nothing else. The downside is that we need asyncify for Fibers/GC and these two things aren't supported together yet on Binaryen. We would need to wait for it. This is option 1.
An alternative is to implement exceptions as a AST-level syntax transformation with some Asyncify runtime. Here is what I have been thinking:
Given this:
Transforms into this:
For this to work
__crystal_raise
would be modified to store the exception in a global state and begin a asyncify unwind. Here__crystal_wasm_rescue
is a runtime method that executes the received block. If it detects an asyncify unwind, it will stop it and return the stored exception. So it returnsTuple(Exception?, ReturnType?)
.(please see this PR for some explanation about what asyncify is #13107)
We still need to handle
return
,break
andnext
. Those can be implemented with marker structs, like so:Transforms into this:
And those runtime helpers could be implemented as:
There are a few more details, but this can be expanded later.
This would be an AST transformation that doesn't requires type information (and won't change the final type of any variable). So it would be done early in the pipeline, only for wasm. The result is that we would have exceptions working everywhere, relying only on Asyncify. This is option 2.
What do you think?
I'm leaning towards option 2 because it works everywhere, although it's also the more complicated on our side.
The text was updated successfully, but these errors were encountered: