Skip to content
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

Reintroducing exnref #280

Open
titzer opened this issue Jul 24, 2023 · 28 comments
Open

Reintroducing exnref #280

titzer opened this issue Jul 24, 2023 · 28 comments

Comments

@titzer
Copy link
Contributor

titzer commented Jul 24, 2023

In (https://github.com/WebAssembly/meetings/blob/main/main/2020/CG-09-15.md), the EH proposal was changed to remove exception packages as first class values. Since that time, this proposal was advanced to phase 3, implemented in the reference interpreter, in toolchains (LLVM and Binaryen), and implemented in all 3 web engines. Web browsers have subsequently enabled the feature by default. Today, there are web properties and applications that use the feature and thus there are binaries that use the feature for C++ exception handling in the wild.

Given those constraints, making any change to the proposal requires careful thought and consideration, as any change we do make could have a potential disruptive effect on producers, toolchains, and engines, and even deployed applications on the web. This proposal has also undergone a number of changes and long design discussions over the years that have taken a lot of time and energy to work through and resolve.

That said, offline discussions have led me to propose that we find a deft way to reintroduce exnref to this proposal, within a set of hard constraints. I am led to this because exnref solves a number of problems that weren’t fully anticipated when it was removed and were only encountered after-the-fact.

Problems

Several issues were identified that are either directly related to lack of exnref or related to the lexical rethrow construct introduced to avoid exnref, which adds a new form of storage to Wasm.

  1. Difficulty in handling the identity of thrown exceptions in the JS API spec.
  2. Complexity in the formal specification around lexical rethrow.
  3. Engine complexity around lexical rethrow, e.g. interpreters.
  4. Inability to CPS-transform (e.g. asyncify) Wasm functions with exceptions.
  5. Lack of exnref significantly restricts toolchains’ code transformations and sometimes leads to unnecessary duplication. (Specific feedback from J2CL authors).

Opportunities

Reintroducing exnref addresses the above problems and allows us to further improve the EH proposal.

  1. Simplification of formal specification.
  2. Simplification of JS specification.
  3. More opportunities for factoring exception handling code, e.g. it could be outlined into other functions.
  4. Simplification to engine decoding and validation.
  5. More leeway for code transformation and optimization in toolchains.

Constraints

We’re operating under a number of tight constraints and requirements.

  1. Don’t break the web. Binaries that work today must continue to work in the presence of adding exnref to the spec, up to some deprecation threshold (2).
  2. Should we decide to deprecate any part of the existing proposal, we must offer an (automated) migration path for binaries in the wild.
  3. Support asyncify/CPS transform.
  4. Avoid protracted design exploration. From offline discussions, there is no energy for any fundamental redesign among key stakeholders.
  5. Must document Web reality. The status quo of the proposal (i.e. Phase 3) has been “stable” and in browsers for more than a year. How it works should be documented with appropriate rigor, even if the final proposal were to deprecate any functionality.

Related issues/links

“Ambiguity/identity loss when (re)throwing external exceptions” #207

“Should wasm code be able to extract the externref from exceptions thrown from js?” #202

“Clarify how exception identity is tracked” #242

“Update JS API to better specify opaqueData exception identity” #250

“Issues discussed in J2CL” #158

@titzer
Copy link
Contributor Author

titzer commented Jul 24, 2023

Also note: there is an item on the August 1st meeting agenda to discuss EH.

https://github.com/WebAssembly/meetings/blob/main/main/2023/CG-08-01.md

@titzer
Copy link
Contributor Author

titzer commented Aug 1, 2023

Slides from today's meeting: https://docs.google.com/presentation/d/15LAJ7VfE_68mgolMgeg9AEJbrrEGoNkEOXhG1J6_5W0/edit#slide=id.p

@keithw
Copy link
Member

keithw commented Aug 1, 2023

Thank you for the presentation today! I hadn't been following this conversation. I implemented the exception-handling support in wasm2c. One of the things that made that easier is the (current) lexical scope for storing caught exceptions. Right now wasm2c doesn't even copy a caught exception onto the stack unless that particular caught exception is the target of a later rethrow, which is easy to identify once you get to the rethrow instructions.

Option A and Option B seem to make life a bit harder for consumers like wasm2c, in that I think in practice they'd require that all caught exceptions will have to be stored somewhere with indefinite lifetime and dynamically known size. So I think there would now need to be a malloc at the start of every sequence of catches, and the runtime would need some sort of GC or a system of refcounting + an API for host functions to honor the refcounts when copying or storing these types. Which all feels less-than-ideal.

How would you and others feel about an "Option C," which roughly speaking would look something like this?

  • Preserve the existing EH proposal, and add no new types, but
  • Perhaps in a new named feature/proposal ("enhanced EH"?), add three new instructions or standardized func imports:
    • make_exnref: [] → externref. Returns the current caught exception as an externref
    • free_exnref: externref → []. Frees the object created by make_exnref
    • rethrow_exnref: [t₁* externref] → [t₂*]: Rethrows a caught exception. Traps if the externref is invalid (e.g. has been freed, is not an exception).

I'm definitely not an expert in the formal semantics of exception handling, but if something like this could be made to work, it seems like the benefits would be things like:

  • It could layer nicely on top of the current EH proposal, without deprecating or duplicating anything that already exists
  • malloc would only become necessary when explicitly invoked by make_exnref (rather than every time there is a catch or sequence of catches)
  • The lifetimes of exnref objects would become explicit and the responsibility of calling code. Of course an engine is still welcome to GC or refcount exnref objects itself if it chooses. (With wasm2c, the runtime would probably stick them all in a list and provide an API call to let the embedder destruct the whole context.)

Curious if this something like this is feasible.

@conrad-watt
Copy link
Contributor

conrad-watt commented Aug 1, 2023

Option A and Option B seem to make life a bit harder for consumers like wasm2c, in that I think in practice they'd require that all caught exceptions will have to be stored somewhere with indefinite lifetime and dynamically known size.

I think this specific concern is something that could be addressed by the variant of Option A that @titzer mentioned, where the existing "exnref-less" catch is maintained alongside the new catch-with-exnref. Catch bodies without an associated rethrow could continue using exnref-less catch, so the materialisation and storage of an exnref wouldn't be required in these cases. I acknowledge that this wouldn't address the wider concern that any refcounts/GC would be a new implementation concern for wasm2c though.

I think an instruction-set with an explicit deallocation instruction for exnref would be somewhat out-of-step with the direction of other proposals (e.g. GC types and stack switching).

@keithw
Copy link
Member

keithw commented Aug 1, 2023

Thinking about how we'd implement exnref in wasm2c, honestly I think we'd probably just represent is as a fixed-size value type on the stack (probably a struct that contains the tag type, the length of the serialized tag values, and the serialized tag values themselves). We already enforce a maximum size of a serialized exception at 256 bytes, so I think we can just do this and avoid any mallocs, refcounting, or GC. (Edit: Although this strategy wouldn't work for us if a tag type is allowed to contain an exnref...)

It would be very easy to implement some added instructions like:

  • make_exnref: [] → exnref. Returns the current caught exception as an exnref. This could also take a labelidx immediate (similar to today's rethrow), which would let it capture any caught exception as an exnref (just like rethrow can rethrow any caught exception).
  • rethrow_exnref: [t₁* exnref] → [t₂*]: Rethrows a caught exception.

It seems like these could be layered nicely on the current design without needing to deprecate anything.


Re: last comment, I don't think I understand the value of catch-with-exnref specifically. In a catch clause, the code knows the tag type and has the tag values. If that was all there was, it seems like it already has all the information it could want to freeze/rethaw the exception. The challenge seems to happen when we're talking about capturing a long-lived reference to an exception that was caught in a catch_all clause. In that situation, you don't know the tag type (it may not even be in the module) and can't get at the values, so there needs to be some opaque way to refer to them.

@conrad-watt
Copy link
Contributor

conrad-watt commented Aug 1, 2023

The challenge seems to happen when we're talking about capturing a long-lived reference to an exception that was caught in a catch_all clause

Sometimes we'll want to capture a long-lived reference to an exception that was caught in a regular catch clause too (my understanding - for code de-duplication and more complex interaction with JS). An idea in the style of make_exnref would also facilitate this, but wouldn't address the underlying concerns that some participants have expressed about our current lexical rethrow (hence the expressed desire to deprecate it if we're adding exnref) - my understanding is that all many of the same concerns could be identically expressed about a lexical make_exnref.

@aheejin
Copy link
Member

aheejin commented Aug 3, 2023

@keithw

Thinking about how we'd implement exnref in wasm2c, honestly I think we'd probably just represent is as a fixed-size value type on the stack (probably a struct that contains the tag type, the length of the serialized tag values, and the serialized tag values themselves). We already enforce a maximum size of a serialized exception at 256 bytes, so I think we can just do this and avoid any mallocs, refcounting, or GC. (Edit: Although this strategy wouldn't work for us if a tag type is allowed to contain an exnref...)

I was thinking in a similar vein. Without GC objects, exceptions only can contain primitive types, and I don't think this could be very different from handling multivalue types, in case wasm2c already supports them. As you said, this is not gonna support the exnref within exnref, but I think it's fine to just error out in that edge case; I can't imagine why anyone would want to do that.

It would be very easy to implement some added instructions like:

  • make_exnref: [] → exnref. Returns the current caught exception as an exnref. This could also take a labelidx immediate (similar to today's rethrow), which would let it capture any caught exception as an exnref (just like rethrow can rethrow any caught exception).
  • rethrow_exnref: [t₁* exnref] → [t₂*]: Rethrows a caught exception.

It seems like these could be layered nicely on the current design without needing to deprecate anything.

As @conrad-watt said, make_exnref depends on the surroundings and also becomes lexical, so it would have the same problems with the rethrow.

By the way I'm wondering, is the number of exnrefs on the fly the problem? Semantically whether catch returns an exnref or make_exnref returns an exnref doesn't seem to make much difference. (But I also wonder, even in the case the number is too large, can this be solved if we limit the value types to primitive types, as I said above?)

@aheejin
Copy link
Member

aheejin commented Aug 3, 2023

@conrad-watt

I think this specific concern is something that could be addressed by the variant of Option A that @titzer mentioned, where the existing "exnref-less" catch is maintained alongside the new catch-with-exnref. Catch bodies without an associated rethrow could continue using exnref-less catch, so the materialisation and storage of an exnref wouldn't be required in these cases. I acknowledge that this wouldn't address the wider concern that any refcounts/GC would be a new implementation concern for wasm2c though.

The current C++ implementation needs rethrow in, I guess, at least more than half of the cases. One case is for destructors:

try
  ... running destructors ...
catch_all
  rethrow
end

The other case is in try~catch, and the exception is not the right type:

try {
  ...
} catch (int) {
}

becomes something like

try
  ...
catch $cpp_tag
  if the thrown value is of type `int`
    do stuff
  else
    rethrow
end

So even if we have two different catches, one with exnref and the other without, I think wasm2c needs to be able to handle catch with exnref anyway. Also for the sake of simplicity it is likely the toolchain will use the version with exnref for all cases. (catch (not catch_all) doesn't account for much in programs, so the code size increase will be not really noticeable)

@keithw
Copy link
Member

keithw commented Aug 4, 2023

I got to talk today with @aheejin, @conrad-watt, and @titzer, about the implementability of exnref in wasm2c, and probably in other "offline" VMs that also lack GC.

The conclusion seemed to be that we agree there's a workable outcome where (for purposes of the current proposal):

  • It would be malformed for a tag type to include an exnref param
  • There would be a new implementation-defined limit on the number of parameters in a tag type (distinct from the existing limit on the number of parameters in a function type), and
  • People think it's reasonable for an implementation to set that limit at, like, 32

If the spec ends up this way, then I think implementations would be able to treat exnref as an "exception sum type," i.e., as a POD type big enough to contain any exception contents that gets copied when needed, and can avoid a dependency on GC.

All of these things might then be lifted in a future proposal. I hope that summarizes accurately!

(My personal preference would probably still be to maintain the status quo, both because it's already implemented and because I think it will perform marginally faster, but I think as far as wasm2c and perhaps other offline VMs are concerned, the above wouldn't be a major hardship.)

@RossTate
Copy link
Contributor

Thanks for doing this! I would recommend taking Option B a bit further: have two instructions catch $tag $label instr* end and catch_all $label instr* end indicating that exceptions (of the designated label) thrown from within instr* should be caught with $label as their handler. Bringing the label up front means the type-checker and compiler can know all local control targets of every function call while type-checking/compiling the call. So, as an example of how this might be helpful in the future, if the type of the $label says that local $foo must be initialized, the type-checker can know to check at every call site within instr* that $foo has been initialized.

@tlively
Copy link
Member

tlively commented Aug 28, 2023

@eqrion previously had a very similar idea, where he spelled it out like this:

try <blocktype> (catch <tag> <label>)* (catch_all <label>)? <instr>* end

I do like keeping the try syntax at the beginning, and we do still need the <blocktype> in the header as well in case there are no exceptions thrown. I also think it makes sense to allow catches and catch_all to be specified on the same try block, but I do agree that it would be nice to bring the labels up to the header before the instruction sequence.

@RossTate
Copy link
Contributor

Works for me. Besides complicating parsing, my main concern with that was that the order of catch clauses is semantically significant (because two clauses can have the same tag), so I thought splitting the instruction into one handler at a time would make that clearer.

Does the exnref still have more content to it than just its tag and payload? If so, since it's a bit costly to use exnref on some engines, you might consider having two variants of the instruction(s): one that provides an exnref (on all paths), and one that does not (on any path).

Neither of these are big items for me; bringing the labels up front is the item I care more about.

@tlively
Copy link
Member

tlively commented Sep 1, 2023

@keithw,

I was thinking more about the restrictions meant to avoid the need for reference counting in wasm2c that you were mentioning in #280 (comment). Looking forward a bit, whatever solution we end up with for stack switching will need reference counting anyway, and I don't think there will be any workarounds to avoid it. One option would be to plan to lift the restrictions in the stack switching proposal, but since we know we will need reference counting in wasm2c at some point anyway, would it make sense to bite the bullet and implement it along with exception handling? That would avoid the need for the restrictions in the first place.

@conrad-watt
Copy link
Contributor

conrad-watt commented Sep 1, 2023

@tlively IIUC the floated restrictions to exnref would only apply on a runtime which doesn't support the GC types proposal, so stack switching would only be a separate concern if wasm2c tried to support stack switching without supporting GC types.

More general point, I think there's a feature threshold after which it just makes sense for a runtime to go ahead and implement a somewhat general GC, at which point there's no reason for that runtime to specially restrict exnref. But I think making exnref itself that tipping point into GC-land (where the runtime hasn't yet committed to any other "GC-forcing" features, but wants to support EH) might complicate any attempt to get exnref adopted quickly (which we have a special interest in doing given the advanced state of the EH proposal).

EDIT: I should add that on the face of things, it seems like exnref would inevitably be a "GC-forcing" feature, but if the restrictions @keithw floated can avoid this being strictly the case, I think that would make introducing exnref a lot more generally palatable.

@keithw
Copy link
Member

keithw commented Sep 1, 2023

@tlively Unfortunately I haven't thought of a great way to do reference-counting in wasm2c (and similar offline VMs) without a lot of overhead. Currently our trap handling is just "longjmp to the trap handler," and our throw handling is "goto or longjmp to the nearest enclosing try block landing pad," both of which are pretty easy. The lexical rethrow in the current proposal is great for us.

With refcounting, we'd need a way to unwind the stack and decrement the refcount of every stack variable, param, and local. I can't think of a zero-cost way to do that in C. It would be expensive to put a setjmp at the beginning of every scope. (And I think people would prefer to avoid switching to "wasm2c++" if possible even though that would give us destructors and zero-cost exceptions...)

I'm not super-familiar with what's been happening with the stack-switching proposal, but my guess is that wasm2c will probably implement the GC proposal first and take a dependency on Boehm GC for that. But I don't want users to have to run Boehm GC just to support exception-handling. (For our own use cases, we really want deterministic memory consumption and also exceptions...)

@titzer
Copy link
Contributor Author

titzer commented Sep 1, 2023

I think our best bet to get exnref into this proposal is to put reasonable restrictions on it so that it can be effectively flattened when compiled in AOT mode.

@keithw Just gaming out the "mini GC" idea (which I am not suggesting you do right now, just brainstorming): you could keep a separate stack of exnrefs and an arena for allocating exception packages, and not do reference counting, but tracing, basically a very simple mark-sweep collector. If you have the entire set of tags, you can represent all exception packages as the union of all their fields (factoring them by kind), and if you have no exnref inside exnref, you can also skip tracing, and just keep a bitmap for allocating them.

@RossTate
Copy link
Contributor

RossTate commented Sep 1, 2023

@keithw A longer-term solution that would also help with supporting exceptions more efficiently would be to embed a description of the stack in the stack itself. That is, when translating an "interesting" wasm function to C, have a struct local variable that describes the "interesting" aspects of the wasm function, e.g. the exceptions it catches (and the relevant jump_buf to use) and/or the references it uses. Each of these struct values would have a "parent" field pointing to the next such struct up the stack, and you'd use a global or thread-local variable to track the address (on the stack) of the "current" such struct value. That way, when you throw an exception, you can search this linked list for a matching handler to long jump to and, whenever you pass a node that doesn't match, decrement the count of any references belonging to the frames that are being dropped.

@titzer
Copy link
Contributor Author

titzer commented Sep 1, 2023

@RossTate Unfortunately we are in a constraint system that does not admit significant redesign of the exception handling mechanism, so we'll have to stay focused on a very minimal change to make exnref work here.

@RossTate
Copy link
Contributor

RossTate commented Sep 1, 2023

I'm unsure what you're responding to. My previous comment was offering @keithw a suggestion on how to support reference counting and exception handling, just as you did. Though I forgot to tag him, so maybe that caused confusion.

@keithw
Copy link
Member

keithw commented Sep 19, 2023

It might be helpful to have a broader discussion about the deprecation plan for "legacy" (v3) exceptions. My concern would be that the major browsers will never deprecate them (similar to what's happened with the legacy text instruction names, e.g. "get_local"), and implementations that conform to the spec alone will receive a lot of support requests/user complaints. Or that many consumers will continue supporting v3 exceptions from an outside-the-spec document/testsuite that's only semi-maintained.

One option would be to really treat this as a first-class exercise in explicit deprecation, and fork the v3 proposal into something called (e.g.) "legacy-exceptions" that remains a live Phase 3 proposal, even as "exception-handling" is merged into the spec. And then there can be a subsequent explicit process to deprecate "legacy-exceptions" once the major browsers are willing to break compatibility, and all the consumers can do so at roughly the same time.

At the minimum, it would be nice if tests for "v3" exceptions remained in the WebAssembly/testsuite repository and were subject to continuous improvements/contributions (e.g. adding tests for lexical rethrow and interactions with branches and exceptions) as long as consumers are expected to support them.

@dschuff
Copy link
Member

dschuff commented Sep 20, 2023

There is a very real possibility that we will never be able to remove phase 3 exceptions from the web, at least in the near future. Even if we went to phase 4 today and all browsers were to implement it tomorrow, browsers whose release schedule is coupled with the OS take quite a bit longer to get major updates, and existing users generally want to target all browsers if they can.
I agree that even if the CG wants to forget that the current version ever existed, we will certainly need to maintain a good document and testsuite, and it would be great if there could be some accomodation for that at the CG level. Having a perpetual phase 3 proposal could be good for the reason you mentioned; we'd want to make it clear that it wasn't on track for full standardization as most proposals are (and we'd have to be ok with the possibility that it never goes away from the web completely). The "as long as consumers are expected to support them" bit is interesting, because given that non-web implementations are much less far along in support for the current proposal, I would actually not expect to see them get that support if we can expedite the replacement.

@keithw
Copy link
Member

keithw commented Oct 4, 2023

Sigh. :-/ I think if the browsers are going to keep supporting "legacy-exceptions" forever (and if some producers keep generating them so that their output works on legacy browsers) then I suspect people are going to keep wanting WABT to be able to parse, write, and validate these non-normative instructions. Which doesn't totally spark joy if we have to support this zombie proposal forever. (Maybe we can drop support in the interpreter and wasm2c at least.)

I recognize that the train is heading in this direction, and I don't want to speak too loudly because we'll be fine no matter what (wasm2c can implement the "GC-less" profile of exnref as discussed above). But procedurally, it seems like it would be helpful if the participants who object to lexical rethrow could register those objections in a place they can be discussed. I don't think this has been captured publicly so far. From WABT's perspective, I was surprised that lexical rethrow is a sticking point; WABT implemented EH in the interpreter in 2021 (WebAssembly/wabt#1749), and in wasm2c in 2022 (WebAssembly/wabt#1930), and I think this proposal's reference interpreter has had an implementation for a while too, and obviously the browsers + Deno + nodejs did too. I hadn't thought any of these were considered to be a particularly heavy lift at the time.

I'm very sympathetic to the desire to be able to asyncify and do other transformations, but if several implementations are really keeping "legacy-exceptions" forever, then it does seem cleaner to layer things on top (e.g. a future proposal that adds get_exnref). This would seemingly get most or all the benefits of exnref without the pain of supporting a zombie proposal forever. :-/ Again, not expecting this to actually happen, but it would be nice to see the objections to lexical rethrow written down in time for the discussion at the CG meeting.

@titzer
Copy link
Contributor Author

titzer commented Oct 6, 2023

Hi Keith,

The original issue above had a list of problems that the lack of exnref and the lexical rethrow construct give rise to. After many discussions with toolchain implementers, I think the strongest argument against lexical rethrow is that it makes compiler transformations of code with exception handlers difficult and sometimes impossible. In particular, there are many transformations (not just asyncify, but factoring, outlining, etc) that even if done on a more general IR, cannot then be re-encoded back into lexical rethrow. So fundamentally Wasm needs to have exnref and the related instructions to make those transformations possible. It's fundamentally more expressive.

That said, I interpret (no pun intended :-)) that part of what you're asking above is "Other interpreters and engines implemented lexical rethrow. What is the issue?" And indeed, in-place interpreters like Wizard's can support lexical rethrow at some cost. I'll explain that here for posterity. Again, please weight the explanation by the fact that I think all the other problems that lexical rethrow gives rise to are far more important. But for completeness sake:

---> Lexical rethrow storage needs explicit modeling in in-place interpretation

Lexical rethrow introduces a new form of storage into Wasm's computational model of a function activation. In particular, in addition to the value stack and control stack, there is a new, implicit caught exception stack that stores the exception packages (implicit exnrefs if you will). This only becomes apparent when trying to execute Wasm bytecode directly (i.e. in-place). Compilers for Wasm and rewriting interpreters for Wasm virtualize this new stack by statically remapping it to locals, value-stack slots, registers, or even static scopes in a target language that has scoped storage.

An in-place interpreter for Wasm has to model the caught exception stack one way or another and thus maintain additional state. There are basically only two ways of doing this; a completely separate data structure, i.e. an explicit stack of caught exceptions, with a separate stack pointer, or in pre-allocated space computed by verification, similar to the sidetable used for control flow. The first option, a completely separate stack and stack pointer, actually impacts all regular control flow, because a branch (of any kind) that leaves a scope now can implicitly pop exceptions from the caught exception stack, so needs to adjust this additional stack pointer (another entry and adjustment in the side table if you will). But, also, quite unfortunately, the end bytecode now actually deallocates an exception, because it closes a scope. That's also true of a catch or a catch_all bytecode, since they can end any previous handler's scope. But they can also end a try (i.e. a fallthrough), in which case they should not pop the exception stack. All of that amounts to: there is now another dynamic stack to maintain.

---> Wizard does implement Phase 3 with a leaky trick

I implemented Phase 3 EH in Wizard, but I don't use the above strategy. Instead, I cheated a little and pre-allocate enough space between the locals and the operand stack to store what are effectively exnref objects. The amount of space needed is equivalent to the maximum nesting depth of catch handlers. Then, the lexical rethrow opcode's depth immediate (which references the control stack nesting depth) is actually ignored, and a side-table entry instead stores the catch-depth. This means that the catch logic in the runtime that selects a handler needs to set the right exception entry in the catch exception stack, and the rethrow opcode has this additional immediate. Thus, Wizard basically does a mini rewrite of lexical storage to be statically allocated. That means that end can still be a nop, and there don't need to be adjustments of an additional caught exception stack pointer upon other control flow. The major downside to that is that this little caught exception stack is not in fact a stack, but is a small storage area that is never reclaimed until the function exits. So basically, exnrefs leak a little.

Again, please don't take the above as the tail that wags the dog. In-place interpretation is the least of our problems here, but is very indicative of something weird going on. When I had discussed this with @rossberg some many months ago, he pointed out that basically the same kind of weirdness pops up in the formal spec; there's additional storage that gets introduced into the semantics (i.e. an additionally indexable environment).

I've phrased it differently elsewhere, but that above amounts to: exnref is much more like Wasm's existing constructs and cost model, as opposed to lexical rethrow, which is a new and somewhat tricky form of storage.

@trcrsired
Copy link

Why bother with wasm2c? Why bother with so-called "zero-cost" eh despite they are never truly zero-cost?

i can guarantee this EH proposal will finally die out if you keep following this path.

@conrad-watt
Copy link
Contributor

conrad-watt commented Jan 30, 2024

@trcrsired if you have questions or concerns about the EH proposal in its current form, can you rephrase them so that people can more easily respond in good faith? (see also this comment)

If you're venting about the repeated changes to the proposal, we're sorry that this has caused you disruption. We don't expect to make further semantic changes, and I hope the discussions above give some context for you to engage with. Again, if you're not happy with the current state of the proposal, you can discuss this here, so long as you follow the W3C's code of ethics and professional conduct.

@eqrion
Copy link
Contributor

eqrion commented Jan 31, 2024

Firefox has now implemented the current version of the spec behind the pref javascript.options.wasm_exnref. It's been fuzzed for a while and is passing the latest spec tests so we're enabling it in Nightly 124 and will let it go to Beta (but not Release).

@hamishwillee
Copy link

hamishwillee commented Sep 30, 2024

FYI Firefox 131 ships support for exnref in https://bugzilla.mozilla.org/show_bug.cgi?id=1908375. MDN docs work for that can be tracked in mdn/content#35696.

Is there an explainer/examples of how this feature is supposed to be used?

Based on what is in the Tables section it sounds like references (of any type) are stored in tables. Tables are visible in both WASM and the shared environment (i.e. JavaScript), and provide a mechanism for sharing access to functions, exceptions, other things efficiently? - i.e. in this case allows an exception to easily/safely propagate to JavaScriipt (say). Docs don't really make it clear why this is needed - Exception seems to propagate happily if tables aren't defined.

It is clear that the docs are out of date in that they way that there are only funcref in tables, and exnref (and perhaps externref) now appear to be supported.

Is there some expert who might be able to help me update/create developer readable documentation for the API on MDN?

@sjrd
Copy link

sjrd commented Sep 30, 2024

In a typical use case for exnref, you don't actually store it anywhere. It remains in a local variable to be re-thrown with throw_ref later in the method. This is typically used for:

  • a language-level try/catch where the catch determines that it should not, in fact, catch the exception (for example after a failed type test on the payload), and
  • a language-level try/finally.

See #158 (comment) for a discussion on the try/finally case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests