-
Notifications
You must be signed in to change notification settings - Fork 36
Remove rethrow #127
Comments
@backes how willing would V8 be to not attach stack traces to WebAssembly exceptions in the name of improving performance? Are implicit stack traces an important part of any debugging story? Would V8 be willing to remove implicit stack traces in the future once languages can use two-phase exceptions and collect their own stack traces? Concrete implementer guidance on this would be very helpful.
There is broad agreement on neither this semantic equivalence assertion nor this minimal changes assertion, so it would be good to dive into those more. One iffy situation we came up with is a program that conditionally swallows a foreign (e.g. JS) exception. Since it is a foreign exception, there would be no way to extract its payload, put the payload back on the stack, and throw it again. With rethrow, this is not a problem because the program can have a conditional |
Great questions, @tlively!
You can simply (conditionally) branch out of an |
I said all these in #126 but I'll repeat.
Added later: |
Java, C#, and Python all need stack traces to be both in terms of surface-level code (not wasm code) and to be explicit in the payload and directly accessible. The
Debugging is generally supported in two parts: support from the execution environment, and support from the application. The latter involves compiling the application differently. The former generally either interacts with annotations in the differently-compiled application or employs guesswork when those hooks are not present. One way applications can be compiled differently to give better stack traces in debug build is to make the stack trace explicit in the payload. (You can even always have an As for debug support from engines, when running an application in debug mode an engine can either give less stack trace information (e.g. only the stack trace from the last handler/unwinder, which is what some systems do), or maintain a stack trace in a global rather than as part of the payload, with heuristics as to when to clear vs. update the stack trace (which can easily be done to support So
|
Not sure what you're talking about, but you are trying to remove existing instruction, not adding a new one. Also don't understand what you mean by old For others, I don't think I need to repeat myself again what I wrote here: #127 (comment) |
I don't think we can use |
|
Please excuse the length of this post. I mean it to be exhaustive, but I try to keep each point brief. Collecting what I believe to be @RossTate's main arguments against
On the other side, collecting what I believe to be the main arguments for keeping
Overall, the most compelling argument by far against keeping The most compelling argument for keeping |
@tlively Before I make comments on each of your argument, have you checked #127 (comment)? Without @RossTate said we can use Before we argue on everything else, I think this alone makes removing |
@aheejin, good point, I should have addressed that specifically. The program we are talking about wants to conditionally swallow a foreign (e.g. JS) exception. With rethrow, it could look like this: (try
(call $js_throws)
catch_all
(if (eqz (... condition ...))
(... run destructors ...)
(rethrow) ;; propagates foreign exception
)
;; swallows foreign exception
) Without rethrow, we've proposed that this program would do the same thing: (try
(call $js_throws)
unwind
(br_if 0 (... condition ...)) ;; swallows foreign exception
(... run destructors ...)
;; reaching end of unwind propagates foreign exception
) In the future when we add two-phase unwinding (with filter functions syntactically attached to (try
(call $js_throws)
catch_all $condition ;; same condition, now as a filter function
(nop) ;; swallows foreign exception
unwind
(... run destructors ...)
) There's no So I think this demonstrates that (try
(call $js_throws)
catch_all ;; no syntactic filter function yet
(if (call $condition) ;; but we can still call a filter function manually
(nop) ;; swallows foreign exception
else
(rethrow) ;; propagate foreign exception
)
unwind
(... run destructors ...)
) @RossTate, wdyt? The ability to adopt the code structure of two-phase exception handling now and simplify future upgrades from single-phase to two-phase unwinding seems to me to be well worth the redundancy of keeping |
@tlively I'm not sure if we can make code structure as you suggested; I commented inline.
Is this the new spec (as of this week's CG meeting)? In the new spec, we put destructors not in
|
Yes, this is with the new spec. I'm not actually sure how
I'm not arguing that it's possible to transform arbitrary single-phase programs into equivalent two-phase programs by extracting filter conditions. I'm arguing that if the toolchain knows what filter function it wants to run, then if we have
Right, my example programs were specific to the behavior of swallowing the exception without doing anything else, but they can be generalized by putting arbitrary work before the |
I'm not sure if I understand what your point is.. Also I'm not sure what your (and Ross's) definition for 'redundancy' is.
I'm not sure if I understand. I didn't think we were talking about translating arbitrary single-phase program into an equivalent two-phase program; I thought we're talking about if it's possible to translate arbitrary program with
If someone argues we can remove |
@aheejin, I think we should chat offline about some of these points (on Monday) to make sure we're on the same page, but I don't think we're really disagreeing with each other either, so I don't want to spend a whole lot of space on this thread going back and forth. It would still be helpful if you could share how the example programs would be written with the correct |
@aheejin and @dschuff bring up some good concerns regarding Given the new issue focused on (@dschuff, your item 3 here is addressed by the fact that surface-language's catch would translate to a wasm |
What's the benefit of removing |
With the new change, every program with
rethrow
is semantically equivalent to a program withoutrethrow
with minimal changes. In the meanwhile,rethrow
is a rather complicated instruction due to its context-sensitivity. It complicates the spec by requiringcatch
blocks to be tracked on the stack. It complicates implementations by requiring (streaming) engines to keep a copy of the exception's payload on the stack until the end of the catch is reached in case it gets rethrown. So removingrethrow
will not lose any functionality and will simplify the spec and improve implementations. It might also make transforming programs easier because there will be no need to preserve the location ofrethrow
instructions within matchingcatch
blocks (or to reindex them to match changes to nestingcatch
blocks).The primary argument for
rethrow
has involved stack traces. Stack tracing should not be done implicitly (at least in production mode) because it can be quite costly, especially for languages that use exceptions as dynamic control throw rather than just for errors. As for debug mode, there are other ways to trace stacks, including techniques that will work just as well withoutrethrow
.As an example of exceptions as non-error control flow, there are two ways to implement
for each
loops (using iterators, not generators, so I'm not going into algebraic effects here). One is to check if the iterator has more elements before each iteration of the loop. The other is to just keep getting more elements from the iterator until it throws a "no more elements" exception. Provided throwing an exception is cheap, the latter can outperform the former even in very small loops because it eliminates an interface-method call in each iteration.This latter implementation of
for each
illustrates how to effectively use exceptional control flow in library and language design. But its performance is very sensitive to the time it takes to throw exceptions. As an example, Section 9.2 of this paper found disabling stack tracing in the JVM made one of its exception-intensive libraries implementing regex parsing 6x faster (it unfortunately only measures one library).Hopefully this 6x difference illustrates that, debug-mode aside, if we want people targeting WebAssembly to be able to reasonably account for performance tradeoffs in devising their compilation strategy, then WebAssembly engines need to be consistent about whether/how they perform stack tracing. And if we want this proposal to be a good target for exception-intensive programs/languages, then engines should not be implicitly performing stack tracing. If they're not, then
rethrow
serves no utility.The text was updated successfully, but these errors were encountered: