-
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
Exceptions vs. non-local control constructs vs. unwinding #142
Comments
I draw the exact opposite conclusion: given the wealth of arcane control features and semantics across languages, it would be a losing game to try supporting them all natively. Such an additive approach would produce a monster of a language. Instead, we need to enable all their implementations by a combination of a general and composable base mechanism for control transfer and the ability to code up as much of the specifics as possible in user space. As for cross-language control, I had assumed that their is agreement that it falls under the same "no seamless interop" caveat that Wasm already applies for cross-language data. All an exception/control mechanism for Wasm can hope to achieve is enabling interop. It cannot magically provide it, as there is no universal solution. Multiple interacting languages will either have to agree on a common control ABI, or avoid cross-language control transfer. Maybe interface types can one day be enriched with a control dimension. |
This is a strawman argument. Leaving room to add more control constructs is not the same as adding all the control constructs.
This, too, is a misrepresentation of what I was advocating for. I was advocating for a solution that avoids unintentional interference of control constructs. I illustrated that C++ avoids such unintentional interference, as do many other languages. On that note, I pointed out that |
Whether we should compile I'm not sure why is C++'s |
The example above shows that
I'm using the fact that |
As I said, we can compile
Still don't understand what this has to do with |
Because you said in our meetings that the reason you wanted
That spec is poorly worded, as the second sentence there contradicts the first. The second sentence states that the behavior is undefined if replacing the given
No, because
What I was trying to point out is that in the MVP all non-local control flow will likely be encoded with |
It's not the same yet, but it is the end of the path that such an approach inevitably puts you on. (Either that, or you intend to carve language privilege into stone forever.)
You can program up this solution by using two different Wasm-level exception tags in the C++ runtime, as @aheejin said. I don't see how unwind helps here, unless you are suggesting that we should also make longjmp a Wasm primitive? For that, see above.
Catch-all/rethrow is what e.g. JS engines do at the lower level to actually implement something like finally/unwind. Both features are also present in popular languages like C++ or C#. |
Engines are free to implement these constructs however they want. JS engines always "rethrow" an unwinding exception in the same dynamic context it was intercepted in. So even these systems seem to be restricted to |
@aheejin Given your clarification that |
Engines do things like compiling I don't understand how the current proposal allows a compiler to implement |
It doesn't allow this. |
How did the previous proposal allow this? You mean the one with
This doesn't currently allow this. What do you suggest as an alternative? Re-add I'm not against to adding |
It would be good not to get too caught up in the problem of supporting In more detail, the JVM specification documents a compilation scheme for |
I don't understand why
It was originally added to give wasm a way to do custom tasks for all non-local control flows. For example, wasm wants to print some message for all exceptional (or non-local) control flows. |
|
|
Can you provide a concrete example of how |
I think |
I understand the intent, but intents do not always match up with actual usage. I am looking for an actual usage that someone plans to generate to support some aspect of their language. |
I already answered about the usage: wasm needs a way to handle unknown non-local control flows, such as printing a message. |
Yes. Here is the situation as I perceive it. Before, we had a fairly canonical proposal with one universal try-construct that could express everything we needed it to express right now. Now, we have an ad-hoc proposal that already has a zoo of 4 different try-constructs in order to accommodate some future use cases, but cannot even efficiently express everything we need right now. So we will likely end up with an even larger zoo. In a low-level VM, I could see the need for perhaps two variants of try. But with 4+, I think we have taken a wrong turn. And to be clear, I don't think that's anybody's fault, and I especially sympathise with you trying to accommodate all the competing requests and making progress -- navigating the incompatible world views that we see on the CG these days has become almost impossible. We simply have run into a serious case of design-by-committee with too many hypotheticals.
Yes, but that was when we still had a coherent design that allowed doing that. It would be great if it still did. That's the problem: this is no longer possible, at least not without unbounded code duplication.
I certainly would prefer to not add yet another try-construct. One suggestion I briefly made earlier was to generalise But ultimately, that would merely be patching around the corners. The deeper problem is that we lack a principled overall design. Unfortunately, I don't have a constructive suggestion at this point other than going back to the drawing board (which is super frustrating, and I don't wanna be that guy). @tlively, I'd argue that enforcing unbounded code duplication is poor design, the JVM notwithstanding -- which can hardly be seen as a pinnacle of good and forward-looking design. As a counter point, .NET, which had the luxury to learn from some of the JVM's mistakes, supports efficient finally (albeit by introducing its own additive approach). |
@RossTate, I think the need for a catch-all is self-evident in a low-level VM, even if it's just to provide a means to implement robustness and diagnostics against uncaught exceptions on some level of a software stack. |
When we were discussing #11, we didn't have I share your concerns on proliferation of different
If we remove As you said, being able to unifying I appreciate your sympathy for this tumultuous CG process. As you've probably seen, the discussions in the repo in recent months have not been easy. I wish you weighed in more and shared your concerns you are currently sharing in the discussions before we passed the new proposal though, so that we were able to take into account your concerns more. |
It is also worth noting that even the few other systems with something like a From what I can tell, the prevailing misunderstanding seems to be that unwinding is done if and only if one is searching for an exception catch-point. Neither of these directions are true. Neither direction of this if and only if holds. To counter the "if" direction, we already have that a trap ignores unwinders, and #101 gives an application for an exception that ignores unwinders. But that direction is less pressing, so I only mention it to illustrate the disconnect between these constructs. More importantly, to counter the "only if" direction, the unwinding phase of two-phase exception handling has no search for an exception catch-point as that catch-point was already identified in the first phase. Another example is Common Lisp's As for |
Ah, fair point. I probably did not notice this limitation at the time. (For completeness, there is a way to compile finally without code duplication in the current proposal, by introducing an auxiliary one-off exception:
where the type of $Aux matches the result type of the try block A. But obviously, this translation would be pretty expensive on the regular path, so isn't attractive.)
Thinking out loud, there are a couple of options, but they all have a price:
Maybe structured EH is just too high-level for Wasm. Not that I have a better suggestion...
FWIW, reference types as such should not be an issue, since they're at phase 4 and about to land Real Soon Now(tm). But in any case, catch vs catch-all is the thing I worry about least.
Yeah, I'm sorry, I think I tried. But simultaneously being stuck in multiple other CG discussions that are even more tumultuous and time-consuming doesn't help. :( |
In a scenario where it is used to gracefully handle uncaught exceptions in a given component, by construction, any exception that ever reaches it is otherwise uncaught, relative to that component.
That may be so, but has nothing to do with exceptions, uncaught or otherwise. |
In what you described, more accurately you want to intercept all non-local control transfers out of the component. |
How does it do that when it's equivalent to And in general I'm confused about terms being used by different people with different meanings, in this proposal's issue discussions. It's not always clear from the context which meaning is meant. |
@ioannad |
I don't think people, including me here, are confused about something. What I (and some of other people, I believe) was talking about was, Also, when you argue to remove |
Hi Michal
Please be aware that there are already two proposals on the table and that
the stacks sub group is part of an effort to synthesize a workable stack
switching effort.
At the very least, I suggest holding off until you understand a little
more of the requirements that we are trying to satisfy.
Having said that, I do welcome your ideas and definitely would like to
hear more of your contributions. (This is written in mgt speak, but is also
real)
The next stack subgroup meeting is dedicated to talking about requirements.
Cheers
Francis
…On Tue, Dec 1, 2020 at 2:28 PM phoe ***@***.***> wrote:
But given how the stack discussions are going, we don't think we know
enough yet about what potential future 2PEH or stack-inspection proposals
would look like to agree that the risk is high enough, that it should weigh
heavily on what we do in the short term.
FYI, tomorrow I am going to submit a pre-proposal for implementing
non-local control flow operators in WebAssembly, as soon as I have it in a
somewhat workable shape and pre-reviewed. This proposal aims to be a
superset of the current exception handling proposal, just like general
control flow is a superset of exception handling.
I will mention the future compatibility risk in there, in context of
handling stack unwinds that are not exceptional (i.e. do not have exception
semantics, unwind to predefined points on the stack, and do not need to be
specially handled); I hope that it provides enough input to help with
deciding with regard to the current unknowns.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#142 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAQAXUCESJRO6IFCJU4MD7TSSVUXVANCNFSM4SRLMZ6Q>
.
--
Francis McCabe
SWE
|
@dschuff Thanks for the update! The two instructions clearly overlap significantly in the current proposal, so if keeping both of them it would be useful to signal to generators how we expect them to differ over time (even though nothing should be set in stone at present) so that they know which instruction they should generate. My guess from the discussions above is that the expected distinction that would arise in the future but is unobservable now is the following:
This distinction would become observable by the introduction of any non-exceptional unwinding form of control flow, such as Common Lisp's @phoe and @fgmccabe, I believe you two are attributing different meanings to the term "non-local control flow" in the above comment. I believe @fgmccabe heard multiple-stack notions of non-local control, whereas I believe @phoe meant to refer to specifically single-stack notions of non-local control. |
Yeah I agree that it would be useful to somehow signal the expected use cases or potential future extensions somehow; maybe not in the official spec but perhaps in one of the kind of docs we currently have in the design repo. The terminology is a bit interesting/tricky (as your parenthetical hints at) because even in the current form wasm exceptions are still a much lower-level primitive than language-level exceptions (e.g. you could imagine implementations of language-level exceptions that only use unwind, as could eventually even be the case with C++). |
Sounds good. Thanks for the clarification! |
I would like to reinforce the sentiment above about signaling. Currently, the following equivalence holds:
Because of this equivalence, someone might be tempted to rewrite one of those operators in terms of the other which will not introduce any short-term issues. Still, long-term issues will happen because this equivalence is accidental: the only kind of unwinds that are permitted by the current specification is via exceptions. When non-exceptional jumps are introduced in the future, this equivalence will break: a Therefore, I would like to request clarification in the spec by explicitly stating that:
Rationale: Common Lisp makes heavy use of other forms of unwinding non-local control. With this proposal, I will need to encode those through exceptions, though at a substantial performance cost as the Clasp Common Lisp team has experienced (when implementing CL unwinds with C++ exceptions). When direct support for other non-local control is added, I would like to be able to switch my implementation without breaking compatibility with other members of the ecosystem due to the switch no longer running their unwinders, should those unwinders be encoded using Having |
The current spec does not define or mention the concept of non-local jumps other than exceptions. Then how do we state in the spec that they are different even though there is no perceived differences?
While it is not impossible that we might have more means to unwind the stack later in future proposals, I'm not sure why you think |
There exist jumps that are not exceptions and therefore should not trigger
Yes, they can, and it is possible to use semantics of throwing to implement other forms of non-local control. Still, it's essentialy using a higher-level operator (throwing/catching) to implement a lower-level one (stack unwinding), which is IMO doing it backwards. To keep this issue on-topic, I'll expand on this in a separate issue; please give me an hour or so to clean my sketch up and post it. |
I'm not very sure what you mean by jumps. Do you mean branches? Or other kinds possible future non-local jumps other than exceptions? If you mean branches, There were discussions on how to support
What I meant was, I'm not sure why you think the current 'throwing' is a different thing than your 'unwinding'. |
Yes, I mean these.
My current goal is future-proofing the current definition of
If I understand this proposal's throwing mechanism correctly, then The operator I am thinking of performs throwing to an established point on the stack, executing all "finally" blocks along the way before continuing from a predefined point in a function of some sort, without the need to construct any exception object, catch, or rethrow anything, because the point from which execution should continue is known ahead of time. |
@phoe Also the concept of exception object is implicit, and the spec does not mandate creating a big object that contains all auxiliary info. The actual implementation can be as simple as a few values, depending on the need for a specific VM. What I've been trying to convey is, it is hard to add hypothetical plans or guesses about the future proposals in the current proposal. I don't think it is necessary in the first place, and stating them requires defining what those future concepts are, which have not been defined well. Also I'm not sure what you mean by the spec all along; the explainer doc, which is written in plain English, only serves an introductory role for the formal spec. And the formal spec is mostly comprised of mathematical notations, within which we don't even have any means to include concepts on future plans. I also don't understand why not including future plans in the current spec will cause backward compatibility problems; those future concepts have not even been defined or used in the current spec, so not mentioning them cannot imply anything about their future usage. |
@aheejin, if I understand @phoe correctly, he is expressing reasons to worry about including Am I understanding you correctly, @phoe? @ all: As I have said before, I don't think we should include Please don't get me wrong, I would love to see all sorts of advanced control flow in WebAssembly, I just don't think that this MVP exception handling proposal is the right place to debate these features. I think this proposal is to add very simple, throw-catch functionality that people want to use now, and that people have been implementing and discussing since 2017. |
I thought @phoe wanted to include
But @phoe, please correct me if I'm wrong.
As you can see from my previous comments in this post, I agree and I spent a long time here arguing to remove |
@aheejin OK - I think I understand a little bit more, thank you for the explanation. If we encode the "unwind-to" behavior into the Currently, it seems to me that the specified behavior will be to
Yes, I mean the English explainer document.
Yes, this is correct.
@ioannad The way I understand it, I think that the point raised by @RossTate somewhere earlier in the thread is that the current EH proposal is not about exception handling, it is also about destructors. Most importantly, adding Once that is possible, then people will start doing it, because they're naturally impatient and want to get their C++ code to work in whatever way possible. This means that we'll soon have a de-facto implementation of Removing I don't think this is solvable in the general case unless these old programs are recompiled to use the new, "real" I think that this danger is realistic because people are impatient, and that's why I am proposing this semantic addition to the current specification. If we explicitly specify that I kinda wish that math and formalism alone was able to solve this problem, but at this moment this is a people issue, not a purely technical one. The purely technical specification will say what is there to be used, but it won't say how it should be used in a way that is compatible with future intents. |
I think I've digested the issue well enough to try and summarize it. By explicitly committing to a vocabulary that expresses exception handling within WebAssembly programs, we also implicitly commit to some vocabulary that expresses non-local control flow therewithin. That's because exception handling is a subset of non-local control flow in the general case, so we can't really decide on anything about EH without also deciding on something about non-local control flow. In this concrete case, by explicitly specifying (If we try to sidestep the problem by not specifying So the real issue that we've been discussing here, as I understand it, boils down to something that is not a problem of "just" exception handling, but a problem of which non-local control flow operators are going to be implementable in WebAssembly in the future. If this proposal goes forwards in its current form, it implies that it must be possible and feasible to use From my (relatively inexperienced) point of view, this is a somewhat big statement, especially since once it's made, it can't really be changed anymore. Is WebAssembly ready to assert it? (1): Note that all of this involves a single stack only; I'm not knowledgeable in the semantics of unwinding or exception handling across multiple stacks. (Thanks for pointing this out, @fgmccabe.) |
Thanks Michal, |
I don't think we can guide people to use a specific instruction for a specific intent other than their formally specified semantics. Also I don't think the future extension proposal (if it exists) should be, or can be binary compatible with the MVP proposal realistically. For example, if we implement 2PEH as a follow-on proposal, VMs have to implement it again and users most likely will have to recompile their source code to benefit from the new proposal's functionality. And I don't think there are things like a real
The MVP proposal does not assert anything on the instructions' usage. The spec only describes the instructions' behaviors. I personally don't know enough to determine whether all those different constructs from Common Lisp, Erlang, or Scheme you mentioned can be expressed with As @ioannad also suggested, I would like to keep the MVP proposal as simple as possible, and I don't think we should include guesswork or conjecture on hypothetical future plans, or guidance on how users should or should not use those instructions. When there is an extension proposal with more functionality available, VMs can switch to use those proposals instead if they think the new functionalities are worth it. |
Many language-design teams signal to their community about how new features are expected to be used and about how the language might change in the future. This signaling helps the community plan. To this end, the current WebAssembly core specification already has a few notes about how features are intended to be used and a number of notes about potential extensions. A note indicating that there might be extensions to WebAssembly that would cause the stack to be unwound for reasons besides exceptions and in so doing would trigger |
If I understand @phoe's concerns correctly, one of them is essentially that there will need to be a change in compilation scheme when producers start using a future 2PEH proposal, whether or not they use (Alternatively, a producer could extract all of its non-exceptional event definitions and their uses into separately upgradable runtime modules, but I can't imagine that strategy being very appealing given that all Another of @phoe's concerns is that it is not clear whether the current proposal provides everything a producer implementing non-local control flow would need. Semantically, it should be possible to implement any non-local control flow mechanism on top of the current EH proposal, assuming an execution environment in which all the WebAssembly producers can trust/coordinate with each other. Given the ability the initiate unwinding with On the one hand, as @aheejin and @ioannad mentioned, the current proposal has really only tried to support the limited use case of C++-style exceptional control flow so far, so it could be considered a non-goal to explicitly support other forms of non-local control flow right now. On the other hand, it does seem likely that some producers will be eager to use the MVP functionality to implement non-exceptional control flow, so it might be nice to make sure their most pressing needs are met. We should definitively decide whether we want to consider non-exceptional use cases in the MVP as soon as possible (and explicitly write that decision down in the explainer) so that we can either defer further discussion about them to future proposals or continue discussing them now with a common understanding of our goals. |
What I'm trying to convey is, it is not easy for the MVP spec to contain any recommendations or restrictions on usage without precisely defining them, but we don't have precise definition for them at this point, and I don't want to include any guesswork in the spec text. We cannot say But at the same time I can't say these primitives can support all non-local non-exceptional control flows for all existing languages out there, because I simply don't know and that's not this proposal's goal anyway. My guess is some of them can probably be supported with the current proposal and some of them can't. So it does not make sense to say So we can't even precisely define what kind of non-local non-exceptional control flow we talk about, and we neither encourage nor discourage uses of the current MVP with any non-local non-exceptional control flow. As I said, it depends. Some constructs can be supported and some not. I'm not sure what more we can include in the current MVP spec. |
I don't think anyone was suggesting having notes making recommendations about |
@tlively, I never said that:
In particular, I never mentioned or indeed thought about C++ at all. Throw and catch support for zero cost exceptions is an MVP in the sense that it is a subset of control flow constructs in many (most?) languages. We made the changes last September to ensure that this MVP is extendible with more advanced forms of control flow in the future. I don't think there was ever agreement or indeed intent to add these other forms of non-local control flow right now. Did I miss this discussion? |
@ioannad, I wrote that with this comment you made in mind:
I intended my "C++-style exceptional control flow" to be exactly the "very simple, throw-catch functionality" you mentioned. In particular, I wrote "C++-style" because that's the specific language we have been implementing and discussing most, but I certainly expect other languages to use these mechanisms as well.
No, we have never said we want to add any other forms of non-local control flow to this proposal, and from what I can tell from this discussion, everyone is on the same page about that. However, there are still concerns about the performance and usability of the proposed throw-catch mechanisms with respect to non-exceptional control flow. We can either decide to address those concerns in a future proposal or decide to address them in this proposal (without adding new control flow primitives). It's not clear to me that we have consensus on whether these concerns are in-scope for this proposal, so I would like to hear what folks think about that choice. |
(If my questions have stirred up the pot a bit too much, then sorry about that.) I have been thinking about this issue for a good part of the past few days and came to some more conclusions. This discussion seems so messy to me because we are right now discussing not two, but three interconnected issues:
The first two issues are fundamentally incompatible with one another, because one has blazing fast non-throw scenarios while the other has cheap unwinds. Expressing one in terms of the other is going to break these performance assumptions. These two issues are also mostly orthogonal to the third, which is essential for implementing C++ destructors/Java's I understand that @aheejin is attempting to get the MVP for zero-cost exceptions ready, so C++ becomes partially compilable to WebAssembly. I say "partially", because getting C++ exceptions to work still does not solve the question of getting C++ destructors to work. If these are expressed in terms of My main question right now is: does WebAssembly need to care at all about this distinction? The rationale for my question is that Technical Report on C++ Performance, chapter 5.4.1, lists two distinct approaches for handling exceptions in C++: the "code" approach (5.4.1.1), which is the SJLJ style, and the "table" approach (5.4.1.2), which is the ZCEH style. The important implication of this document is that the same C++ code, which is going to have some combination of This means that C++ code is actually exception-style-agnostic, meaning, it does not care which exception model is used in the compiled binary under the hood. (Such is also the case with e.g. Lisp code, except I don't know any Lisp implementation that offers the "table" approach to control flow in practice.) I think that it would be possible to utilize the same technique in WebAssembly. It should be enough to compile C++ (and other languages) into a WebAssembly form that preserves enough information about The choice of strategy could be static or dynamic, and e.g. C++ modules which depend on the fast optimistic path could be compiled with ZCEH whereas e.g. Lisp modules which depend on fast unwinds could be compiled with SJLJ. It would be up to the implementation to provide bridging between modules with different exception handling styles. This would have the benefit of keeping the WebAssembly control flow and exception handling representation as language-agnostic as possible and therefore make it possible to build implementations that leverage this fact by optimizing for fast no-unwinds or fast-unwinds respectively. Such an approach would effectively defer the decision of whether to use ZCEH or SJLJ to the WebAssembly implementation, and, therefore, it would solve this current discussion that we are having. The current specification may need to be adapted to match the above goals. Speaking generally: there should be some kind of way to record dynamic environments and some way to record continuation marks - where:
For C++, this means information destructors and a map of exception types to When that information is present in WebAssembly, an implementation will be allowed to retain this information at runtime for a more dynamic, SJLJ-style approach to unwinds, or to precompute the unwinding/exception tables and optimize the code for non-unwind scenarios, ZCEH-style. I have identified several ways in which the current proposal could be adapted to fit the above description; this list is by no means exhaustive and is only valid if there are no errors with my reasoning above.
Does all of the above make sense? I've tried to speak from the C++ perspective for a moment - I'm not a C++ programmer, so please forgive and correct me if I'm wrong somewhere here. I did consult that with Clasp Common Lisp programmers though, so this should be good for both C++ and Lisp perspectives. [1] I've asked some people behind Clasp Common Lisp to write this down; this document describes the nature and issues with performing control flow and unwinds in a C++-centric environment. |
As you noted, the current proposal was designed with the zero-cost style EH in mind, but it can support some Also while we are open to minor revisions, I would really like to refrain from undertaking a major overhaul or redesigning the whole proposal at this point; the proposal has been around for more than three years now, and we also have mostly-working toolchain and two VM implementations. Especially, removing existing functionalities can cause unintended consequences.
I'm still not sure what "de-facto standards" we have. Also I'm really not sure what you are proposing or what clash we are having. Do you think the current I skimmed the doc you linked, but I have a very limited understanding of Common Lisp, so I'm not sure if I understood the doc properly. A summary of the problem you are talking about would be appreciated.
Including all this info within the spec, especially this MVP, is really not a goal for the MVP proposal. Some information is already generated and supplied with the appropriate toolchain support. The current C++ EH needs some of these and the toolchain embeds necessary information (LSDA tables in this case) in the data section of the binary. And as I said, if you want a type of unwinding that unwinds to a specific marker, you would probably need a new proposal.
I'm not sure what you mean by explicitly supporting unwinders. As I asked above, does
These have uses unrelated to unwinding, so it is hard to remove them. We have many existing discussion threads for this such as #126 or #127, so please refer to them. I'm also not sure why existence of these instructions is a problem for your use case. You may want to add some functionalities for, such as Common Lisp support (but as I said we would like to make the MVP simple and defer adding a new functionality to a future proposal), but I wonder why existence of these instructions is a problem for your use cases. |
This is an attempt to formally describe @aheejin's 3rd proposal, which she presented to the Wasm CG, and which was voted to be the new EH proposal, on September 18, 2020. This is not formal spec that I have developed, but a formal description of this 3rd proposal. This is a reworked form of my [first attempt on this formal spec overview](WebAssembly#87 (comment)) edited with my new understanding of the spec based on the discussion below, and in other issues, and according to @aheejin 's [3rd proposal overview](https://github.com/WebAssembly/exception-handling/blob/f7a4f60d11fb6326fc13f84d3889b11d3873f08a/proposals/Exceptions.md) in PR WebAssembly#137. This is in the form of a document as @tlively [requested](WebAssembly#142 (comment)), to make discussion on specific points easier. I wrote this formal spec overview roughly in the style of the 2nd proposal's [formal spec overview](WebAssembly#87 (comment)) by @rossberg. Particular points of interest: - In this I assume `rethrow` does have an immediate, as it is now described in WebAssembly#137. - The instruction `unwind` now reduces to `catch_all ... rethrow` as specified in Heejin's overview. - Because unwind is much simpler in this MVP, there is no `throw_point` anymore and `caught_m` is much simpler. - The introduction of `caught_m` now also introduces a label around the catch instructions, which label a rethrow instruction can reference. - Added explanation of the peculiar side condition in try's execution rule - just trying to make the rules more compact. I would be happy if anyone could point out things that are wrong or that I misunderstood.
Fixes WebAssembly#142. A mismatched `DataCount` is malformed, not a validation error.
This is an attempt to formally describe @aheejin's 3rd proposal, which she presented to the Wasm CG, and which was voted to be the new EH proposal, on September 18, 2020. This is not formal spec that I have developed, but a formal description of this 3rd proposal. This is a reworked form of my [first attempt on this formal spec overview](WebAssembly#87 (comment)) edited with my new understanding of the spec based on the discussion below, and in other issues, and according to @aheejin 's [3rd proposal overview](https://github.com/WebAssembly/exception-handling/blob/f7a4f60d11fb6326fc13f84d3889b11d3873f08a/proposals/Exceptions.md) in PR WebAssembly#137. This is in the form of a document as @tlively [requested](WebAssembly#142 (comment)), to make discussion on specific points easier. I wrote this formal spec overview roughly in the style of the 2nd proposal's [formal spec overview](WebAssembly#87 (comment)) by @rossberg. Particular points of interest: - In this I assume `rethrow` does have an immediate, as it is now described in WebAssembly#137. - The instruction `unwind` now reduces to `catch_all ... rethrow` as specified in Heejin's overview. - Because unwind is much simpler in this MVP, there is no `throw_point` anymore and `caught_m` is much simpler. - The introduction of `caught_m` now also introduces a label around the catch instructions, which label a rethrow instruction can reference. - Added explanation of the peculiar side condition in try's execution rule - just trying to make the rules more compact. I would be happy if anyone could point out things that are wrong or that I misunderstood.
Exceptions, non-local control constructs, and unwinding are all related but different entities. It is important to understand the distinction between these concepts, and to see that distinction consider the following C++ program:
According to the C++ spec, this program returns 2, not 1. This is because although
longjmp
is a non-local control construct, it is not an exception. (And to clarify, the behavior of this program does not depend on howcatch (...)
is specified to interact with foreign exceptions becauselongjmp
is not considered a foreign exception either.) So exceptions are distinct (but closely related to) from non-local control constructs.I was careful to make sure this example involves no unwinding. How
longjmp
interacts with unwinding is not defined by the C++ spec, intentionally deferring it to the platform. (Similarly, the C++ spec does not specify how unwinding interacts with uncaught exceptions, intentionally deferring it to the platform because the behavior of single-phase vs. two-phase EH implementations differ here.) The GNU compilers do not havelongjmp
cause unwinding, whereas Visual Studio does by default (though you can turn it off). (Visual Studio also lets you configure whether foreign/system exceptions should cause unwinding and discusses why you would want unwinding for some circumstances and why you would not want unwinding for other circumstances.) So non-local control constructs are distinct (but closely related to) from unwinding. (To clarify, I am not advocating to add non-unwinding non-local control constructs in this proposal.)Okay, so why do these distinctions matter? Well, just as many languages compiling to C use
setjmp
/longjmp
to implement their own non-local control constructs (as it is the only non-local option), many languages compiling to WebAssembly will usethrow
/catch
to implement their own non-local control constructs (again, as it is the only non-local option). We should anticipate this. Similarly, WebAssembly eventually add other non-local control constructs. We should leave room for this.unwind
does both by providing a way to specifying unwinding code with no assumptions about why the stack is being unwound. It is closely related to unwinding clauses in other systems—fault
, (part of)finally
,unwind-protect
, and (part of)dynamic-wind
—all of which similarly specify/treat an unwinder as a block/function of type[] -> []
.Now, one particular non-local control construct that will need to be emulated with
throw
/catch
issetjmp
/longjmp
. Because the spec gives us the option to havelongjmp
cause unwinding, this is mostly straightforward to do using some$longjmp
exception event. But there's a problem if one translatescatch (...)
tocatch_all
: thecatch_all
will mistake the$longjmp
event for an exception. That would make our example C++ program above incorrectly return 1. And while yes, you could hack the compilation ofcatch (...)
to exclude the$longjmp
event, that only excludes your own long jumps, failing to exclude other C/C++-as-wasm program's long jumps as well as other languages' non-local control constructs, whichcatch (...)
seems to specifically not be intended to catch.Hopefully this illustrates part of the rationale behind
unwind
, and hopefully this more concrete example better illustrates the concern about compositionality ofcatch_all
that I had expressed more abstractly in #128.The text was updated successfully, but these errors were encountered: