WebAssembly / exception-handling

Proposal to add exception handling to WebAssembly
https://webassembly.github.io/exception-handling/
Other
163 stars 35 forks source link

spec question; should <dfn>ToWebAssemblyValue</dfn> be updated to include exceptions? #328

Open fgmccabe opened 1 month ago

fgmccabe commented 1 month ago

From a potentially inadequate reading of the spec, there seems to be a gap in ToWebAssemblyValue: it does not seem to allow for a JavaScript exception value to be translated into a WebAssembly exception.

Something like this occurs in JSPI when the reject handler is called if the underlying Promise is rejected: it must be mapped (eventually) to a WebAssembly throw.

dschuff commented 1 month ago

The way to translate JS exceptions into wasm exceptions is to import the JavaScript exception tag. Then you can create a wasm catch with that tag, and when a JS function throws/propagates an exception, the thrown object is passed to wasm as a payload (see step 3.10.3 of create a host function). The reverse happens in step 11.3 of call an exported function. What you can't do though is get an exnref that corresponds to an exception originally thrown from JS. Nor can exnrefs go to or from JS via ToJSValue or ToWebAssemblyValue. There was some brief discussion of that here and previous discussion linked there.

fgmccabe commented 1 month ago

This does not appear to address my concern. In particular, I believe that it should be possible to pass a JS exception value to wasm, and have the wasm throw it. I.e., we don't throw it into wasm directly.

dschuff commented 1 month ago

You can pass any JS value as an externref into wasm (either by passing/returning it from a function, or catching it in wasm), and then you can throw it from wasm as a JS exception (using a throw with the JS tag as its tag, and that externref value as its payload). Once it propagates out to JS, IIUC it will behave the same as any other thrown JS value (i.e it will not be wrapped in a WebAssembly.Exception object).

sjrd commented 1 month ago

What you can't do though is get an exnref that corresponds to an exception originally thrown from JS.

Hum, I'm confused by that statement. A catch_ref $JSTag or catch_all_ref clause can definitely catch an exception originally thrown in JavaScript. That gives you a perfectly valid exnref representing the exception thrown by JavaScript. If you rethrow it with throw_ref you'll rethrow the original JavaScript-emitted exception.

Granted, you cannot directly manipulate an exnref in JavaScript code. But that applies regardless of whether the underlying exception was a JavaScript exception of a Wasm exception.

In fact, a JavaScript exception is not special at all. It's just like any other Wasm exception that happens to have the tag WA.JSTag.

dschuff commented 1 month ago

Yes, all of that is true. I probably should have said that you can't get a unique exnref that corresponds to the original JS throw. Because the exnref is actually created not at the time of the original JS throw, but when the exception propagates into wasm (3.10.3.3 of "create a host function"). Also, in the "sandwich" scenario with interleaved JS and wasm frames, if that same exception propagates back and forth between JS and wasm multiple times in a single unwind, a fresh exnref is actually created each time it goes from JS to wasm (rather than reusing the original one). I don't know of any case where that distinction actually matters though (and it's not observable in JS, and I guess not even in wasm; exnrefs are not eqrefs and you can't really do anything with them other than storing and throwing).

When we discussed this before where I suggested that we could also allow passing exnrefs out of wasm by means other than throwing and catching; @rossberg noted the complications around throwing vs just allocating a WA.Exception, but if we need to do more interesting things witn exnrefs e.g. for JSPI, then we could easily relax that restriction as well.