@fitzgen (he/him) thanks for the informative presentation on the status of core wasm proposals in Wasmtime yesterday. Regarding exception handling: you mentioned there were two paths to implementing it:
I'm curious if you've considered a third option: do a wasm->wasm transformation that converts use of exception handling to normal, Swift-style control flow (i.e. transform the wasm module into one that doesn't use wasm exception handling). The upshot being that Wasmtime wouldn't need to implement exception handling at all except as a preprocessing step prior to AOT compilation. What kind of performance penalty would that incur vs. doing a Swift-style implementation in Cranelift itself? And are there any other drawbacks you would anticipate with such an approach?
good question! the primary advantage of having cranelift do the "resultification" would be that we can tweak the ABI that functions use to make that work especially well. for example, IIRC swift will actually set a particular register when returning an error, and then propagating that error for callers ends up being a very simple test and branch, and doesn't have to do any kind of unpacking data from memory that was passed via return pointer or anything like that
that said, I suspect that we will probably want to do the unwind tables approach, because there are other things that want similar tables (eg stack maps and such) and the primary benefit of avoiding tables is to have things "obviously correct when looking at code emitted by the compiler" but as soon as we rely on any tables, we lose that property. and yeah there is something to be said about degrees, but I think going from 0 to 1 is a much bigger decision than 1 to 2 if that makes sense
I'm curious if you've considered a third option: do a wasm->wasm transformation that converts use of exception handling to normal, Swift-style control flow (i.e. transform the wasm module into one that doesn't use wasm exception handling).
I've considered implementing this (in binaryen, incidentally) since wasmtime doesn't support EH and the work required to add that support required time I do not have. I actually still have a WIP branch with the beginning of that implementation, here's a file I grabbed from it.
I've taken a slightly different approach (inspired by, but incompatible with Asyncify), which essentially has a global "unwinding" flag and all the associated exception data, and functions do branch after each invoke, but only on that flag rather than on the result. This has, I assume, several advantages:
I've never finished the work because the Wasm EH semantics is quite complex and has lots of edge cases that for the most part are not actually used by any frontends but still require some degree of care in a general Wasm->Wasm transformation.
For example, you can leave a catch_all
without rethrow
. This isn't something that a code generator lowering Itanium EHABI will ever produce, as far as I know, but it's permitted by the spec. (For those who aren't familiar: The way things usually work--and this is true for native code as well--is that a low-level exception type is allocated for the concept of a "C++ exception" as a whole, which is caught when any catch
block at all is present, even catch(...)
, and rethrown if the C++ type doesn't match. catch_all
is reserved for finally
blocks in presence of foreign exceptions, e.g. Ada exception propagating from C++ code. This is relevant basically never in native EHABI, though I think this might be how JS exceptions propagate from Wasm frames, in which case it'd be quite important to get this right for finally
to work.)
Another example is lexical rethrows (rethrow 1
for example), which I understand some people (the WAMR author I think?) consider a mistake. I don't quite recall all the details but it is true that it adds considerable implementation complexity. Fortunately that one doesn't seem to be relevant as clang will only ever emit rethrow 0
, right at the end of a catch
or catch_all
.
Hope this helps. I'm still interested in having this transformation be possible, just no time to work on it any time soon. It's a bit of a nightmare to test too.
@Catherine (whitequark) question for you, is your feedback here and the complexity of implementation all based on the "old" definition of the exception-handling proposal?
w.r.t. the past october the CG decided to take the exception handling approach in a different direction I think in response to some of this feedback
Oh yeah I just discovered that while I wasn't looking, the entire EH proposal was changed to something completely different. I'm really happy with this new proposal, as it can for the most part be trivially lowered to non-EH Wasm:
throw
sets the tag index to some non-zero value corresponding to the thrown tag, and copies the arguments over;try_table
checks the global tag index against the tag (or just if it's non-zero) in the catch
clause, and if it's a catch[_all]_ref
clause, packs all the globals into a tuple, replaces the exnref
with that tuple, and clears the global tag index;throw_ref
unpacks the tuple back into the globals and returns (or jumps to the outer try_table
).This approach has only one wrinkle: it's not strictly speaking fully general as it can't handle an open world system (e.g. dynamic linking) where any newcoming module may decide to allocate an exception with an ever-higher amount of arguments. This can be handled by storing the arguments in memory instead of in globals, but I'll argue this is unnecessary: in many cases a closed-world approach is fine, and whenever it's not, you could have a parameter saying how many arguments at most should be expected. People really do not create new EH lowerings very often and the value of 2 is sufficient to handle all code I've seen personally.
(If you go with an open-world, dynamic linking approach, the dynamic linker will have to allocate tag values too. But this lets you lower EH across Wasm module boundaries, which I think is pretty cool!)
Oh nice! Wanted to confirm one way or another, but that sounds like it speaks quite well in favor of the current trajectory
It's nice and simple enough that it's tempting to go and try my hand at implementing it, but I know better than that. Very happy to leave fun projects to other (newer?) contributors too :) and/or lend a hand implementing it
Last updated: Dec 23 2024 at 13:07 UTC