-
Notifications
You must be signed in to change notification settings - Fork 36
Inconsistency in documentation for try-delegate behavior for function body #176
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
Comments
I strongly support (and I've been assuming that was the case) the 'caller' case for |
This clarifies that `delegate` can target the function-level block, which means the exception is thrown to the caller. Also added more examples and shuffled some paragraphs around to make them more sense. Fixes WebAssembly#176.
This clarifies that `delegate` can target the function-level block, which means the exception is thrown to the caller. Also added a few more examples. Fixes WebAssembly#176.
This clarifies that `delegate` can target the function-level block, which means the exception is thrown to the caller. Also added a few more examples. AFAIK this is consistent with the current implementations in the toolchain, V8, and Spidermonkey. Fixes WebAssembly#176.
This clarifies that `delegate` can target the function-level block, which means the exception is thrown to the caller. Also added a few more examples. AFAIK this is consistent with the current implementations in the toolchain, V8, and Spidermonkey. Fixes WebAssembly#176.
Hm, to be honest, I would highly prefer to not make that change. The function-level block scope was always intended to be just a shorthand for saving a block instruction. This change would make it into a very special case, something new that is neither a regular block nor a try, which is the kind of one-off semantics that makes me uncomfortable. For example, it is non-composable, and breaks the ability to inline functions without ad-hoc transformations. If we allow this then we should allow targeting any block with a delegate. Is there a concrete use case for making this block special? |
We already define entering a function as introducing a label that breaks to the end of the function body (we could have instead required the function body to be explicitly wrapped in an outer block for this functionality) - could we support the desired semantics in the OP by enriching this label to be a |
Indeed, this is exactly like a catchless try. So we could handle it in the semantics by reducing a function call to frame(label(catch(...))). But of course, that is still a complication that potentially affects tools as well. So I'd still be curious if there actually is a strong use case for it. |
A |
If targeting the function level block with I'm not sure if we will actually end up doing 2PEH, but given that one of the reason I think considering it as a catchless try is a good way to interpret it.
There are functions that we don't have complete info on if it ever throws in the toolchain backend. But they can be happened to be wrapped into a (func $test
try
...
call $foo ;; if throws, should go to `catch` below
...
call $bar ;; if throws, should NOT go to `catch` below
...
catch
...
end
end) In LLVM IR terms, |
The design of |
@RossTate You are right when we use The reason we removed But disallowing |
I see, makes sense. But of course, a producer could equally well just insert a catchless try explicitly in such cases. That would put the burden on the producer actually needing this functionality rather than everybody in the larger ecosystem? I wonder, is there appetite for resolving this tension by simply removing the restriction that delegate can only target try-blocks? In other words, treat every block like a catchless try? That would avoid making an odd special case here. Given that engines already have to handle that case, I presume it wouldn't add particular difficulties to allow it everywhere. |
Yes, I suppose the "special case" for tunneling to the function scope would be one additional catchless try, which sounds simple enough.
Yes, personally I like this idea. It changes the mental model for |
If we were to generalise Or put another way, could engines/tools get any benefit from knowing upfront (streaming?) that a block scope can't be delegated to (which IIUC is currently exactly the difference between |
Delegation is lexical, so whether a block is being targeted is a simple syntactic property either way. |
Accounting for the discussion in WebAssembly#176 adjust try_delegate so that it can target any block.
It seems like there have been multiple solutions proposed here and a consensus wasn't reached quite yet, so I wanted to summarize the options.
Any others I missed? Is there agreement on proceeding with one of these options? |
Sorry for the delayed reply. I don't think I have a specific preference here. Option 2 is the currently implemented one in the toolchain and the V8 & FF engines. But I admit that has some inconsistency to it. I think I am OK with any options here. Any more opinions on what you prefer or not prefer among these? If we allow And whichever one we choose, we should coordinate when we land the change in V8 and the toolchain not to break Adobe origin trial. |
Personally, I'd prefer option 3, since it is the most flexible and most regular. Thanks @takikawa for trying it in the PR, LGTM. |
I don't have a strong preference but I agree that option 3 seems the most regular.
I just tried implementing the change in SpiderMonkey's baseline compiler and it seems like it should be pretty straightforward, and with minimal overhead as far as I can tell (since it can be statically determined if the delegation to a non-try block skips to an outer try block or rethrows out of the function). I'm not sure if there any implications on the producer side. |
It seems like there are no opinions against moving forward with option 3 so far and some explicit support. @thibaudmichaud do you have any concerns with implementing option 3 in V8 or have any preferences for any of the others? |
I have a weak preference against option 3, and in favour of option 2, simply because it feels strange to me to remove the remaining distinction between EDIT: I also think that changing the function-level That being said, I won't hold to this argument if there would otherwise be consensus for option 3. |
Maybe we can do a little straw poll to see where we stand: 😄 Option 1 |
FWIW, echoing @conrad-watt and also giving a bump to @aheejin's question about whether allowing the targeting of any block will make code transformations or optimizations any harder, option 2 seems more conservative (MVP-y), sufficiently flexible, and forces there to be more static information. Clearly it's possible to detect whether a block is targeted by delegate, so it's a minor consideration, but one-pass compilers, interpreters, and suchlike may in principle benefit from the extra information. |
What @lars-t-hansen said was the reason I was cautious about Option 3. I was concerned about the possibility that the "bleeding" of exception through While I don't have a strong opinion on one option, Option 1 can be a middle ground; it is consistent in that there's no exception for the function-level block. It will increase the instruction count (and thus the code size) by two instruction ( |
I should have mentioned option 1 in my previous post. To explicitly give my opinion now, I think it would strictly inferior to option 2, since it would require an almost identical type system extension (to track whether a label target can be delegated to). Generalising the function-level block to be a catchless try (i.e. going from 1 to 2) really shouldn't be an invasive change. I would be fine with 2 or 3, with a slight preference for 2. |
So it seems like when factoring in @rossberg's preference for option 3, it's evenly tied between 2 and 3 in the straw poll above and there's not really interest in doing 1. Anyone else have any thoughts to tilt it either way? For completeness sake, implementing option 2 in the reference interpreter (and thus the formal semantics) should look like this: takikawa@7cebbb1 The special handling of the body block is eliminated and it's treated as having a |
Sorry for the late response, i was on long holidays. I don't mind either option. I also think option 2 seems simpler to implement¹, but for the spec it's just a matter of validation I think, and of course a matter of simplicity as @rossberg pointed out. Having said that, I don't really understand @conrad-watt's spec argument against option 3, because in my mind² But again, either option is fine by me. ¹. In fact option 2 is what RabaldrMonkey is doing already, and @lars-t-hansen's comment seems to suggest option 3 would have optimization implications there. In the in-progress BaldrMonkey implementation, option 3 could be done with throws from the target blocks, which was the plan anyway. But I don't know whether with option 3 there might be (future) optimizations that could become tricky there as well. ². This view is what is done in the spec formal-overview draft #143, which btw in fact still only implements option 1. |
Option 2 is the one I like least, since it (a) is the least regular, and (b) puts more burden on the wider ecosystem instead of just users/producers of the feature. Option 1 and 3 avoid both these disadvantages, and option 3 even avoids any burden on the user side. @conrad-watt, I also don't understand your argument. The fact that the empty form of one construct is equivalent to another construct is a common situation – for example, an empty block is just a nop, an empty br_table is just a br, and so on. Why should this be an argument for introducing irregularities? |
My reasoning is coming from a different direction, since I don't see option 2 as causing any irregularities compared to either of the other options. So to me, the only remaining tie-breakers are marginal:
I agree that if I believed there were irregularities introduced by option 2, this would trump any of the above. I'm also happy enough with option 3 that I don't want to turn this into a big debate. |
Option 2 is conflating functions with exception handling. That is, the irregularity is that it requires introducing a special case of block kind only for function bodies (as was done in @takikawa's original interpreter implementation), or type-checking a function body as if it was a handler (although it never is). Furthermore, as mentioned above, this conflation affects relevant program equivalences and puts a burden on everybody who wants to do certain program transformations. In fact, it breaks existing inlining transformations, because they become incorrect for certain functions. I think such an avoidable lack of conservatism and orthogonality isn't justified by the minor convenience it provides for the users of this feature (who can trivially insert a try on their end under option 1, or don't even have to under option 3). |
I agree that introducing a special block case just for function bodies would be undesirable. If this was the only way to spec this behaviour, I would be against it.
We get to decide in the spec whether the implicit outer label of a function is a handler, in the same way that we decided that there should be an implicit outer label in the first place. We could say that it is a handler, in the same way that a catchless I can't give an informed opinion wrt. the concerns for toolchains and producers. I just think that at a semantic level, solution 2 isn't irregular. |
A catchless try is merely a degenerate case of a more general construct. The function body would always be such a degenerate case, so would be rather special. I don't think these situations are comparable. |
Even though nobody has explicitly voted for option 1, it does seem the most conservative, and it's still extensible to option 2 or 3, if we later decide that we would like to add more flexibility. Does anyone have anything against option 1? |
I still don't have a strong preference, but @rossberg's inlining argument makes sense to me. If we choose Option 3, optimizations like inlining can be straightforward in tools such as Binaryen. (Currently we don't do much optimizations yet in Binaryen, and we disable inlining any function containing try-delegate for this reason.) If we don't result in a consensus, I could maybe switch to weak preference to Option 3. |
If there is no more opinions or strong objections, I think we can resolve the discussion to Option 3. The engine implementations for V8 and FF have to change, but the engine implementors here said the changes would be minor, so I hope that is not a big problem. Thank you everyone for the discussion. I'll update the Explainer soon. |
Sorry, I think it'd be better to keep this open until I update the Explainer after all 😅 |
Cool, thanks @aheejin! |
I don't have any concerns about implementing option 3 in V8. I would just like to propose that we also remove the validation error discussed in #146: try
catch
try
delegate 0 ;; invalid
end Compare it to: try
catch
block
try
delegate 0 ;; valid
end
end The second one is valid with option 3 IIUC. The first enclosing try block is the same, so it feels inconsistent that they have a different behavior. |
@thibaudmichaud, I agree both should be equally valid. I believe this corresponds exactly to this comment I just made as well. |
@thibaudmichaud I agree this is now valid too. |
Update the behavior of 'delegate' according to: WebAssembly/exception-handling#176 Summary: delegate can target any block, which just rethrows to the next outer try/catch. [email protected] Bug: v8:8091 Change-Id: I967db9ab1cbb1a15b2c5e0a1a20f64fa19a3f769 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3140603 Commit-Queue: Thibaud Michaud <[email protected]> Reviewed-by: Clemens Backes <[email protected]> Cr-Commit-Position: refs/heads/main@{#76677}
This adds the changes decided in WebAssembly#176 and adds a modified version from WebAssembly#146 (comment) for clarification. (The example is not the same by the way; the `catch` from the outermost `try` has been moved) Closes WebAssembly#176.
This adds the changes decided in WebAssembly#176 and adds a modified version from WebAssembly#146 (comment) for clarification. (The example is not the same by the way; the `catch` from the outermost `try` has been moved) Closes WebAssembly#176.
The semantics of the try-delegate Wasm exception handling instruction was recently changed in this spec discussion: WebAssembly/exception-handling#176 This patch adjusts compilation and validation to match the new semantics, which allows delegate to target any block. Differential Revision: https://phabricator.services.mozilla.com/D124424
This adds the changes decided in #176 and adds a modified version from #146 (comment) for clarification. (The example is not the same by the way; the `catch` from the outermost `try` has been moved)
The semantics of the try-delegate Wasm exception handling instruction was recently changed in this spec discussion: WebAssembly/exception-handling#176 This patch adjusts compilation and validation to match the new semantics, which allows delegate to target any block. Differential Revision: https://phabricator.services.mozilla.com/D124424
The semantics was changed recently to remove the restriction to only target try blocks or the function body block: WebAssembly/exception-handling#176
The semantics was changed recently to remove the restriction to only target try blocks or the function body block: WebAssembly/exception-handling#176
This PR makes wabt's validation behavior for `try-delegate` match the spec and spec interpreter. The spec now allows the delegate to target any block, rather than just `try` blocks and the function body block. The semantics was changed a bit ago as discussed in WebAssembly/exception-handling#176
In #175 there's some discussion of what the intended behavior of
try-delegate
with a label pointing to the function body is supposed to be (#175 (comment)).My understanding was it should be valid and rethrow the exception out of the function, as discussed in #146 (see example there, especially for
delegate 3
) and as covered in the core tests in this test case which I'll just inline below:There's an inconsistency though with the overview in https://github.com/WebAssembly/exception-handling/blob/master/proposals/exception-handling/Exceptions.md as it says:
Assuming we agree on the behavior, we could change the overview to say "does not correspond to a try instruction or function body" (and an example could be helpful) and possibly adjust some other prose.
The text was updated successfully, but these errors were encountered: