Skip to content

Conversation

@aheejin
Copy link
Member

@aheejin aheejin commented Jun 6, 2022

In Emscripten EH, the thrown value by the JS library is the original
user-thrown value itself.

But in Wasm EH, what we throw is an WebAssembly.Exception object. You
can use getArg method to get the value thrown. But even this value is
not the actual user-thrown value; in libc++abi, which Wasm EH uses, what
we actually throw is a pointer to something called unwind header,
represented by _Unwind_Exception:

_Unwind_RaiseException(&exception_header->unwindHeader);

The current getCppExceptionThrownValue in our JS exception library
returns the _Unwind_Exception pointer, which is the value actually
thrown by libc++abi, and not the original user-thrown value. This is
confusing in the first place, and I got requests from users who want to
access the original user-thrown value.

Because of WebAssembly.Exception object, we still have some
discrepancy between Emscripten EH and Wasm EH, but I think this is more
intuitive, and it actually simplifies libc++abi code as well.

So the current state:

  • Emscripten EH: Throws original user-thrown value
  • Wasm EH: Throws WebAssembly.Exception object, which contains the
    pointer to the unwind header

After this PR:

  • Emscripten EH: Throws original user-thrown value
  • Wasm EH: Throws WebAssembly.Exception object, which contains the
    original user-thrown value

Addresses #16033.

In Emscripten EH, the thrown value by the JS library is the original
user-thrown value itself.

But in Wasm EH, what we throw is an `WebAssembly.Exception` object. You
can use getArg method to get the value thrown. But even this value is
not the actual user-thrown value; in libc++abi, which Wasm EH uses, what
we actually throw is a pointer to something called unwind header,
represented by `_Unwind_Exception`:
https://github.com/emscripten-core/emscripten/blob/1f6b136d75c9a6a89d07ed069b9cede80b39cebd/system/lib/libcxxabi/src/cxa_exception.cpp#L284

The current `getCppExceptionThrownValue` in our JS exception library
returns the `_Unwind_Exception` pointer, which is the value actually
thrown by libc++abi, and not the original user-thrown value. This is
confusing in the first place, and I got requests from users who want to
access the original user-thrown value.

Because of `WebAssembly.Exception` object, we still have some
discrepancy between Emscripten EH and Wasm EH, but I think this is more
intuitive, and it actually simplifies libc++abi code as well.

So the current state:
- Emscripten EH: Throws original user-thrown value
- Wasm EH: Throws `WebAssembly.Exception` object, which contains the
           pointer to the unwind header

After this PR:
- Emscripten EH: Throws original user-thrown value
 Wasm EH: Throws `WebAssembly.Exception` object, which contains the
          original user-thrown value
@aheejin aheejin requested a review from sbc100 June 6, 2022 10:23
@aheejin
Copy link
Member Author

aheejin commented Jun 6, 2022

cc @hoodmane @Krelborn @donalffons


$getExceptionMessage__deps: ['__get_exception_message', 'free', '$getCppExceptionThrownValue'],
$getExceptionMessage: function(ptr) {
$getExceptionMessage: function(obj) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drive-by fix: the variable names (obj and ptr) were swapped in the previous code.

// Relies on the structure layout, where the unwind pointer is right in
// front of the user's exception object
static inline __cxa_exception* cxa_exception_from_exception_unwind_exception(
static inline __cxa_exception* cxa_exception_from_unwind_exception(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was named cxa_exception_from_exception_unwind_exception because it was copied from cxa_exception.cpp. I'm not sure why we need to repeat exception, so I shortened the name. The same with the function below.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is more correct we should maybe propose this as an upstream change? I do think there is some value in matching these names with the upstream ones... but I don't feel strongly about it.

@aheejin
Copy link
Member Author

aheejin commented Jun 8, 2022

Gentle ping 😀


$getCppExceptionThrownValue__deps: ['$getCppExceptionTag'],
$getCppExceptionThrownValue__deps: ['$getCppExceptionTag', '__thrown_object_from_unwind_exception'],
$getCppExceptionThrownValue: function(ex) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we call this function getCppExceptionThrownObject? Or is thrown value a thing that is different?

Copy link
Member Author

@aheejin aheejin Jun 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah these are confusing. So far in library_exceptions.js, I tried to use 'object' for WebAssembly.Exception object thrown, and 'value' for the user-thrown value. So this function takes an object and returns the user-thrown value. This got more confusing by the param name for this function is ex while other functions' param name is obj, when they denote the same thing.

At the same time libc++abi uses 'object' to mean the user-thrown value, as you can see in the name of thrown_object_from_unwind_exception, which adds to the confusion. It's hard to rename it because that function is copied from cxa_exception.cpp, and changing the function name will make our libc++abi more confusing. I guess we should make these terms clearer.

How about this?

  • object: the user-thrown value. So we rename this function to getCppExceptionThrownObject as you suggested.
  • value: let's not use this term at all
  • ex/exception: WebAssembly.Exception. So we rename current obj params in other functions, which meant WebAssembly.Exceptions, to ex.

If you think this sound fine, I'll make this change as a followup PR.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe "JSObject" or "JSException"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the thing you want to represent with "JSObject" or "JSException"? WebAssembly.Exception or the user-thrown value(or object)? I guess the latter? In that case it's confusing in another way because this is a Wasm exception and that sounds like this is a JS exception thrown from JS, a foreign exception..

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WebAssembly.Exception is what I propose to refer to as JS object. The thrown object lives in wasm memory, as does the exception object, right? They are both allocated with malloc.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example a longer name for this function might be something like getCppThrownObjectFromJSCaughtObject?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure JSCaughtObject is clearer to someone who doesn't know this context. How about just getCppThrownObjectFromWebassemblyException, with comments clarifying what this means? This name is long but I guess at least not misleading..

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, sounds reasonable. I think if was a user I would be looking for something do with the thing that I caught.. so I guess JSCaughtObject and WebassemblyException both describe that thing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll explain in the comment that this is WebAssembly.Exception object that Wasm EH JS API provides.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyway, will do this in a followup. Thanks for the discussion!

Copy link
Collaborator

@sbc100 sbc100 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm % comments

@aheejin
Copy link
Member Author

aheejin commented Jun 8, 2022

Will land this for now, but I'm planning on changing some methods' names in library_exception.js, so please don't consider the current APIs as settled just yet!

@aheejin aheejin merged commit af7f969 into emscripten-core:main Jun 8, 2022
@aheejin aheejin deleted the cpp_ex_value branch June 8, 2022 23:58
aheejin added a commit to aheejin/emscripten that referenced this pull request Jun 9, 2022
Terms like 'object' and 'value' were used interchangeably and
inconsistently, causing confusion. This tries to make it more
consistent.

- Thrown object: A user-thrown value in C++, which is a C++ pointer.
  When you do `throw 3;`, libc++abi allocates a storage to to store an
  exception and store that value '3' within that storage. This is the
  pointer to that location. This is consistent with the term used by
  libc++abi; which also calls the pointer 'thrown object'. Emscripten EH
  throws this directly, so you get this value right away when you catch
  an exception with JS `catch`. In Wasm EH, which uses libc++abi, this
  is not the value libc++abi actually throws. See below.

- Unwind header: The pointer that actually gets thrown in libc++abi.
  This pointer preceeds the thrown object:
  https://github.com/emscripten-core/emscripten/blob/24f765dc3545e89f232c9f8503f6426055271628/system/lib/libcxxabi/src/cxa_exception.cpp#L26-L28
  This is an internal detail of libc++abi so we don't want to expose
  this to users. Emscripten EH doesn't use this concept.

- WebAssembly exception: A native Wasm exception, represented by
  `WebAssembly.Exception` JS object. This is what is actually gets
  thrown from Wasm `throw` instruction. libunwind receives the unwind
  header pointer (see above) from libc++abi, and create an
  `WebAssembly.Exception` object and package the received value with the
  C++ exception tag, and throws it. So this is what you get when you
  catch a Wasm exception with JS `catch`.

Relevant previous discussion: emscripten-core#17157 (review)
aheejin added a commit to aheejin/emscripten that referenced this pull request Jun 9, 2022
Terms like 'object' and 'value' were used interchangeably and
inconsistently, causing confusion. This tries to make it more
consistent.

- Thrown object: A user-thrown value in C++, which is a C++ pointer.
  When you do `throw 3;`, libc++abi allocates a storage to to store an
  exception and store that value '3' within that storage. This is the
  pointer to that location. This is consistent with the term used by
  libc++abi; which also calls the pointer 'thrown object'. Emscripten EH
  throws this directly, so you get this value right away when you catch
  an exception with JS `catch`. In Wasm EH, which uses libc++abi, this
  is not the value libc++abi actually throws. See below.

- Unwind header: The pointer that actually gets thrown in libc++abi.
  This pointer preceeds the thrown object:
  https://github.com/emscripten-core/emscripten/blob/24f765dc3545e89f232c9f8503f6426055271628/system/lib/libcxxabi/src/cxa_exception.cpp#L26-L28
  This is an internal detail of libc++abi so we don't want to expose
  this to users. Emscripten EH doesn't use this concept.

- WebAssembly exception: A native Wasm exception, represented by
  `WebAssembly.Exception` JS object. This is what is actually gets
  thrown from Wasm `throw` instruction. libunwind receives the unwind
  header pointer (see above) from libc++abi, and create an
  `WebAssembly.Exception` object and package the received value with the
  C++ exception tag, and throws it. So this is what you get when you
  catch a Wasm exception with JS `catch`.

Relevant previous discussion: emscripten-core#17157 (review)
aheejin added a commit that referenced this pull request Jun 10, 2022
Terms like 'object' and 'value' were used interchangeably and
inconsistently, causing confusion. This tries to make it more
consistent.

- Thrown object: A user-thrown value in C++, which is a C++ pointer.
  When you do `throw 3;`, libc++abi allocates a storage to store an
  exception and store that value '3' within that storage. This is the
  pointer to that location. This is consistent with the term used by
  libc++abi; which also calls the pointer 'thrown object'. Emscripten EH
  throws this directly, so you get this value right away when you catch
  an exception with JS `catch`. In Wasm EH, which uses libc++abi, this
  is not the value libc++abi actually throws. See below.

- Unwind header: The pointer that actually gets thrown in libc++abi.
  This pointer preceeds the thrown object:
  https://github.com/emscripten-core/emscripten/blob/24f765dc3545e89f232c9f8503f6426055271628/system/lib/libcxxabi/src/cxa_exception.cpp#L26-L28
  This is an internal detail of libc++abi so we don't want to expose
  this to users. Emscripten EH doesn't use this concept.

- WebAssembly exception: A native Wasm exception, represented by
  `WebAssembly.Exception` JS object. This is what is actually gets
  thrown from Wasm `throw` instruction. libunwind receives the unwind
  header pointer (see above) from libc++abi, and create an
  `WebAssembly.Exception` object and package the received value with the
  C++ exception tag, and throws it. So this is what you get when you
  catch a Wasm exception with JS `catch`.

Relevant previous discussion: #17157 (review)
aheejin added a commit to aheejin/emscripten that referenced this pull request Jun 14, 2022
After emscripten-core#17064 and emscripten-core#17157 exception message printing is working, but it
still requires adding a lot of functions to `EXPORTED_FUNCTIONS` and
`DEFAULT_LIBRARY_FUNCS_TO_INLCLUDE`, which is difficult to use and
requires users to know unnecessary library internals:
https://github.com/emscripten-core/emscripten/blob/8c0fe77d8946a7ec2f73dfa74779ac957dd24530/tests/test_core.py#L1639-L1643

This adds `EXCEPTION_PRINTING_SUPPORT` option, which adds necessary
symbols to those settings for easier use. This also exports functions
with which you can get the C++ tag and the thrown value from
`WebAssembly.Exception` object.

Fixes emscripten-core#17114.
aheejin added a commit that referenced this pull request Jun 15, 2022
After #17064 and #17157 exception message printing is working, but it
still requires adding a lot of functions to `EXPORTED_FUNCTIONS` and
`DEFAULT_LIBRARY_FUNCS_TO_INLCLUDE`, which is difficult to use and
requires users to know unnecessary library internals:
https://github.com/emscripten-core/emscripten/blob/8c0fe77d8946a7ec2f73dfa74779ac957dd24530/tests/test_core.py#L1639-L1643

This adds `EXPORT_EXCEPTION_HANDLING_HELPERS` option, which adds                             
necessary symbols to those settings for easier use. This also exports
functions with which you can get the C++ tag and the thrown value from
`WebAssembly.Exception` object.
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

Successfully merging this pull request may close these issues.

2 participants