-
Notifications
You must be signed in to change notification settings - Fork 36
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
What counts as an exception? #1
Comments
I think there is a meaningful difference between explicitly-raised exceptions (which I guess includes JS exceptions but ignoring that for now) and machine-type error traps (OOB, signature mismatch etc) and it's not clear that they should be catchable in the same way that user exceptions are. So obviously we can debate that in this issue (and we can design such a feature) but I think the first proposal should exclude them because the use cases are very different. |
Right, we've talked about having support for signals as well as exceptions. It would be interesting to define what we see in each bucket, and what other platforms have done. Specifically, I think the Windows SEH approach, its ups and downs, can inform our decision. |
One advantage the Windows SEH approach has over handling runtime traps with signals is that it avoids global state that determines how a trap will be handled. There isn't a good way with POSIX signals to handle a trap from just one thread. A Windows SEH filter is effectively a stack-scoped signal handler: when an exception occurs, the SEH filter functions are called "on top" of the stack, so the information is still available to resume execution, or get a stack trace, or unwind to the handler. A C++ catch maps to a SEH filter that checks the exception information and returns a value that indicates the associated handler should be executed. Thread-local signal handlers would provide the same functionality (could you throw from them?), but require some additional mechanism to compose (a stack of handlers, perhaps?). My most preferred design would be very close to Windows SEH. My least preferred design would be something that unifies traps and exceptions, but doesn't allow resuming or inspecting the stack at the trapping code. |
This proposal intentionally did not focus on what exceptions should be produced by WebAsembly. Rather, any such exception would need to be imported to be caught, since it is not one the module defines. Hence, the framework needed to handle such exceptions, should they be added, is already there. We would not need to add any new concepts. |
If runtime-generated traps can be caught as exceptions, then there should be a mechanism to retry the trapping code. There should also be a way to handle a runtime trap by capturing a stack trace and other state information at the point of the trap for automatic bug reporting. I think it's fine to start with exceptions as they are and add the ability to catch runtime traps later, but there's additional functionality required if the above cases are to be supported. Would that require changing any of base exception functionality? I can imagine adding a A way to capture a stack trace is not specified by WebAssembly; without considering whether it should be, what would be needed from this proposal for an implementation to expose the trapping call stack to a runtime trap handler? |
Good point. I hadn't thought of those issues. I agree that sometime in the future, we should consider runtime traps. |
Marked as (future) enhancement to add handling of runtime traps. |
I don't think we want to outright declare this as a future feature. It's worth exploring having it in the first version as well, and if that's not viable then move to future. |
@jfbastien, are you referring to reifying traps as exceptions, or supporting resumption? Both are separate features, and the latter is far more difficult and debatable than the former. |
Both, separately.
Agreed. |
At least in the future I've been imagining, the distinction between traps and exceptions is:
With this distinction, exception handling is single-pass unwind-as-you-go, unlike, e.g., itanium ABI's two-pass design. Also, it is an orthogonal question of how to define which trap handler to call (module-scoped, lexically-scoped, dynamically-scoped like SEH, etc). Also, with this split, trap handlers are definitely the more advanced feature with the more narrow (but important, in the limit, if we want to allow all the fancy VM tricks) use case, so I think we should keep them separate from the exception handling MVP knowing they can be added later in a complementary way. |
That's the bit I'm not convinced about. |
Is there are particular scenario you're thinking about? |
Maybe I haven't thought about it enough. I'm not convinced this ins't a plausible scenario:
I'm just not convinced this isn't the case. As well as the above scenario, it could unfold as:
|
Yeah, that is the risk. It seems like we could mitigate it, without increasing the size of the EH MVP, by doing some work to collect all the interesting use cases for traps we can think of and reasoning through how the final traps+EH design would address them. |
I think it’s time to revive this old issue. Should we catch wasm traps with wasm
I'm not arguing we should catch traps, but anyway all below are based on the assumption that “what if we catch traps”, just for discussion.
4-a.
4-b. It is not even easy to unwind to the right ‘catch’ clause. LLVM generates |
From my point of view as a Java to Wasm compiler writer, I would suggest the follow solution:
Then the compilers of the different languages can add a simple handler with |
Note that index spaces are unsigned. But there could be other ways of binding primitive exceptions. However, the harder questions are different ones, e.g., potential performance implications of making every trap catchable. |
I think the performance decrement will be larger if the trap can't catch. This required a compiler of a language that support such catching (e.g. Java) to check every trap self. For example the compiler must check for every division that there is not a zero. The result will be that for every instruction that produce a trap the compiler will call a function. The idea that runtime exception should result in a program termination seems me absurdly. Another thing is if there are traps that should never occur if the compiler has work correctly like unreachable. For an high level application developer there is no difference between an trap and a self fired exception or an exception from some library. |
I understand, but there are relevant languages that do not benefit from the check and would be penalised by any extra cost implicit in the semantics -- such as C, where e.g. division by zero is undefined behaviour. You would also penalise code where the producing compiler was already able to prove the absence of failure. |
This is valid for every feature in wasm.
An option for different behavior in the wasm can be a solution for it. If you can not decide on a feature, add an option! Like the command line switches for experimental feature currently this can be enabled in the wasm binary self. Of course would this increment the complexity of the runtime and I can understand if you does not accept this as a solution.
This can be possible for DivisionByZero, ArrayIndexOut, NullPointer but what is with OutOfMemory? I think it is not possible to check if any allocation would work. |
OOM is a very hard case anyway. IME, very few systems (if any) are able to recover from it reliably and allow general execution to proceed afterwards in a stable manner. |
How about IO and threading related exceptions? |
Well, there is no I/O in Wasm, nor any traps specific to threading. |
The only existing traps in Wasm are from undefined arithmetics, illegal memory/table accesses, and ill-typed indirect function calls. Of these, only the arithmetic ones would actually make sense to catch, since the others should never occur in a correctly compiled program. |
In the GC spec there are a large count of traps like wrong casting, null reference to complete the list of possible traps. |
True, but these all fall into the same category as ill-typed function calls. |
I would argue that it's very possible for an incorrect cast or null reference to occur during runtime. Not all programmers are savvy enough to write their code in a way that would prevent them from happening, and there's only so much that compilers can do to detect and eliminate the possibility. Not allowing those traps to be caught and handled could result in a situation where such trivial coding errors becomes hours of tedious debugging. It's 2019 for goodness sake. |
There was some discussion about stack traces with regards to traps, which I would expect to make debugging straightforward, so hours of tedious debugging as you put it would probably be overstating the problem. Without traces though, I agree, debugging would be a pain. In general though, my feeling is that traps should at least have some similarity to POSIX signals, in that some of them cannot be caught (resulting in a panic/abort), and some can (defaulting to a panic/abort if unhandled, but if a program chooses to handle them, allows it to catch the trap and recover), and there is some kind of standardized mechanism for doing so. To be clear though, I don't necessarily think the interface for handling traps needs to be the same as POSIX signals, though I think it has potential as a model; but signals do provide a good baseline for the kind of traps that can occur, and/or are useful to catch, and illustrate that not all of them can or need to be recoverable. I think I'd want to see something similar in Wasm, where you can opt in to handling certain traps that are considered recoverable, and for the rest, a program simply panics with a backtrace to the instruction which trapped. I'm not sure how I feel about the idea of traps being converted to exceptions, it should be the job of the language compiling to Wasm to deal with things like division by zero, null pointer dereferencing, stack overflow, etc. As Andreas said, some languages can either guarantee that certain trappable behaviors will never occur as part of their compilation process, or choose to skip checks which add overhead at runtime as an optimization opportunity, and I don't think we should be forcing them to deal with that overhead if it is not needed. If those kind of checks need to be part of Wasm, the best option for that in my mind would be adding checked/unchecked forms instructions which may trap, which would raise an exception rather than trap - this would address to some degree the potential for code size issues for languages which need to ensure that traps are surfaced as exceptions in the source language, since those checks would not need to be duplicated everywhere. |
@andrewackerman, there are implementation-level type errors and language-level (i.e., user) type errors. Traps signify the former, while the latter will typically have to be implemented by a compiler. I think it's best not to conflate the two. Even user-observable null checks should best be explicit in the code, to avoid as much overhead as possible around implementation-level checks. |
@rossberg If the compiler need to check to prevent runtime traps then it can be required to add some more instructions. For example in the complex range of the GC spec. I have not check this if this really required. The more I think about it the better I like it. Each language can precisely prevent the traps that are defined in its language. To finish this discussion this should be a topic in one of the next community/working group meetings. |
Added to the March 19 agenda. Does that work? |
I am OOO until 3/22, and in a different time zone, so I don't think I can attend it. Can we do it later? |
I've been OOO (and will be for some more days) so I haven't fully caught up with the comments so far, but one thing I'd like to make sure is even if we decide to catch traps with |
OK, no problem, let's postpone. |
We discussed the topic in the in-person CG meeting in June. It's been a while, but I'd like to reopen the discussions and make decisions on this issue to proceed to Stage 2 for the EH proposal. I'd like to propose that we don't catch traps with wasm As I mentioned above, for a practical reason, we need a way to abort, to implement functions like C I also think whether hardware traps should be handled like or converted to exceptions is the job of languages themselves. For example, Rust compiler inserts code that checks for divide by zero errors and throws exceptions. C++ treat them as UB and doesn't pay the performance cost. Java VM handles arithmetic errors using signal-handler-like routines and creates and throws Java exceptions. OTOH, C++, the important language wasm currently supports, is not gonna benefit from making Some people were concerned about debuggability if traps are not catchable; I don't think that will be a problem. If the embedder has stack trace support, users will be able to see stack traces when a program crashes after a trap just fine, provided that we have appropriate JS (or other embedder) API support. This scheme will require some changes to the JS API, because traps should not be catchable even after they hit a JS frame. In the current JS API, if a trap hits a JS frame, it becomes a |
cc @titzer, in case he has more insights on support for languages like Java. (We talked a little on that at the CG meeting, but the it was more like a passing conversation, so) |
I also think it makes sense not to have traps caught by wasm It's slightly unclear to me (as someone not accustomed to JS spec-ese) whether there could currently ever be a |
I opened #89 to discuss this. |
This adds overview on traps not being caught by the `catch` instruction and its relationship with the JS API. Closes WebAssembly#1 and closes WebAssembly#89.
This adds overview on traps not being caught by the `catch` instruction and its relationship with the JS API. Closes WebAssembly#1 and closes WebAssembly#89.
This adds overview on traps not being caught by the `catch` instruction and its relationship with the JS API. Closes WebAssembly#1 and closes WebAssembly#89.
Restoring lost issue:
@wibblymat
Should things like unreachable, out-of-bounds memory access, call_indirect with an invalid table index, etc., become catchable exceptions?
The text was updated successfully, but these errors were encountered: