Move parts of emscripten exception handling to native code. NFC#16627
Move parts of emscripten exception handling to native code. NFC#16627
Conversation
8cb6dae to
225496d
Compare
6befd1b to
ed6e571
Compare
ed6e571 to
c7c5836
Compare
|
Diffing the cpp file to the file it is adapted from, I see stuff like this: // Allocate a __cxa_exception object, and zero-fill it.
// Reserve "thrown_size" bytes on the end for the user's exception
// object. Zero-fill the object. If memory can't be allocated, call
// std::terminate. Return a pointer to the memory to be used for the
// user's exception object.
void *__cxa_allocate_exception(size_t thrown_size) _NOEXCEPT {
size_t actual_size = cxa_exception_size_from_exception_thrown_size(thrown_size);
- // Allocate extra space before the __cxa_exception header to ensure the
- // start of the thrown object is sufficiently aligned.
- size_t header_offset = get_cxa_exception_offset();
char *raw_buffer =
- (char *)__aligned_malloc_with_fallback(header_offset + actual_size);
+ (char *)__aligned_malloc_with_fallback(actual_size);
if (NULL == raw_buffer)
std::terminate();
__cxa_exception *exception_header =
- static_cast<__cxa_exception *>((void *)(raw_buffer + header_offset));
+ static_cast<__cxa_exception *>((void *)(raw_buffer));
::memset(exception_header, 0, actual_size);
return thrown_object_from_cxa_exception(exception_header);
}Why do we not have the The other part of the diff that looks unclear to me is this: void __cxa_decrement_exception_refcount(void *thrown_object) _NOEXCEPT {
if (thrown_object != NULL )
{
__cxa_exception* exception_header = cxa_exception_from_thrown_object(thrown_object);
- if (std::__libcpp_atomic_add(&exception_header->referenceCount, size_t(-1)) == 0)
+ DEBUG("DEC: %p refcnt=%zu rethrown=%d\n", thrown_object, exception_header->referenceCount, exception_header->rethrown);
+ assert(exception_header->referenceCount > 0);
+ if (std::__libcpp_atomic_add(&exception_header->referenceCount, size_t(-1)) == 0 && !exception_header->rethrown)
{
+ DEBUG("DEL: %p\n", thrown_object);
if (NULL != exception_header->exceptionDestructor)
exception_header->exceptionDestructor(thrown_object);
__cxa_free_exception(thrown_object);
}
}
}We add |
|
Yes, those deltas are deliberate, and made to match the JS implementation. Honestly I'm not totally sure why they exist, but they match the existing JS code. |
|
Added more comments |
c7c5836 to
3efb144
Compare
kripken
left a comment
There was a problem hiding this comment.
Ok, sgtm to match the old JS behavior even if we don't fully understand it.
I think we need a separate file in libc++abi for 'our' EH, meaning both Emscripten EH and Wasm EH. (I think they can be in the same file, as long as we have `ifdef`s) This also adds a file header. I think this method can be in that file, rather than having a separate file for this. I named this `cxa_emscripten.cpp` because it's the name emscripten-core#16627 used and I hope we can put all Emscripten specific stuff there, but maybe @sbc100 had another thought; if so let me know.
I think we need a separate file in libc++abi for 'our' EH, meaning both Emscripten EH and Wasm EH. (I think they can be in the same file, as long as we have `ifdef`s) This also adds a file header. I think this method can be in that file, rather than having a separate file for this. I named this `cxa_emscripten.cpp` because it's the name emscripten-core#16627 used and I hope we can put all Emscripten specific stuff there, but maybe @sbc100 had another thought; if so let me know.
|
Oh I started working on something similar but then decided to search PRs just in case, and glad I did. There seems to be a lot of conflicts - mainly in tests - but otherwise seems like this was in mergeable state? |
|
I landed a smaller version of this patch in #18292. I'm not sure its worth revisiting this larger change. Would be good to have but not sure if its high enough priority given that it works, and we have native exception handling coming down the pipe soon. |
Unfortunately, one doesn't contradict the other. I still need to work on something like this for project I'm working on at the moment, because neither Wasmtime nor Wasmer support Wasm exceptions yet, so the only chance for STANDALONE_WASM is manually shimming out Emscripten exception support in the custom runtime we have. Moving parts of the Emscripten EH that just modify C++ exception structures from JS to C++ would make this much easier, as in the end I'd only need to shim out |
|
I guess, looking at the number of conflicts, I should just try & start anew after all... (unless you think this PR is still salvageable) |
|
I'll try to rebase it first after all. |
|
Sounds good. I would still love to see this land if you can make it happen. |
3efb144 to
a113c6b
Compare
| static | ||
| inline | ||
| __cxa_exception* | ||
| cxa_exception_from_thrown_object(void* thrown_object) |
There was a problem hiding this comment.
These utilities seem to be duplicated here and in the new utils file.
There was a problem hiding this comment.
Yeah, just like in cxa_exception they were originally borrowed from, they're static local helpers everywhere.
|
@sbc100 FWIW this is WIP, I just wanted to push as I got far enough in my conflict resolution & new changes that I didn't want to lose. |
|
I'll continue working on this tomorrow. For now I managed to rebase most of this stuff, although had to deal with new helpers exposed to JS that were added since this PR. Since they're shared between backends, I've moved them to a new Ideal end state I think would be to reuse |
| # case because that only works for user-level code, and __cxa_begin_catch | ||
| # can be used by the standard library. | ||
| '__cxa_increment_exception_refcount', | ||
| # Same for __cxa_end_catch |
There was a problem hiding this comment.
Also had to add __cxa_decrement_exception_refcount here to make some tests pass; I'm not sure why it wasn't necessary for the original PR but became now - maybe I'm missing a better place where it should be added, or maybe something changed in tests.
|
One more thing I noticed is that this part Lines 2785 to 2806 in b5c68f6 seems to overlap in terms of intent with this one Lines 2897 to 2911 in b5c68f6 Should they be merged somehow or are they responsible for different things? (one is under |
c563002 to
b5c68f6
Compare
|
From a quick look, the signature seems the same as before despite moving func from JS to C++. I'm curious why it complains... |
|
Its particularly interesting that it only happens for LTO.. |
|
I think I have a fix. |
|
Could it be related to _LIBCXXABI_NO_CFI? |
This test started failing after #16627. Prior to that change the exceptions destructors were called via the following code in JS: ``` // In Wasm, destructors return 'this' as in ARM {{{ makeDynCall('pp', 'destructor') }}}(info.excPtr); ``` I'm not sure why this test only started failing under LTO. Its seems like it should be failing under non-LTO too.
This test started failing after #16627. Prior to that change the exceptions destructors were called via the following code in JS: ``` // In Wasm, destructors return 'this' as in ARM {{{ makeDynCall('pp', 'destructor') }}}(info.excPtr); ``` I'm not sure why this test only started failing under LTO. Its seems like it should be failing under non-LTO too.
This test started failing after #16627. Prior to that change the exceptions destructors were called via the following code in JS: ``` // In Wasm, destructors return 'this' as in ARM {{{ makeDynCall('pp', 'destructor') }}}(info.excPtr); ``` I'm not sure why this test only started failing under LTO. Its seems like it should be failing under non-LTO too.
This test started failing after #16627. Prior to that change the exceptions destructors were called via the following code in JS: ``` // In Wasm, destructors return 'this' as in ARM {{{ makeDynCall('pp', 'destructor') }}}(info.excPtr); ``` For some reason the LTO build produces the following for for the call to `exception_header->exceptionDestructor(thrown_object)` in `__cxa_decrement_exception_refcount`: ``` call_indirect 0 (type 0) ``` Where as the normal non-LTO build produces: ``` call 13 <invoke_ii> ``` Because invoke_ii goes via JS it uses the sloppy type checking and doesn't trap, but `call_indirect` has strict type checking and so does trap.
This test started failing after #16627. Prior to that change the exceptions destructors were called via the following code in JS: ``` // In Wasm, destructors return 'this' as in ARM {{{ makeDynCall('pp', 'destructor') }}}(info.excPtr); ``` For some reason the LTO build produces the following for for the call to `exception_header->exceptionDestructor(thrown_object)` in `__cxa_decrement_exception_refcount`: ``` call_indirect 0 (type 0) ``` Where as the normal non-LTO build produces: ``` call 13 <invoke_ii> ``` Because invoke_ii goes via JS it uses the sloppy type checking and doesn't trap, but `call_indirect` has strict type checking and so does trap.
|
This is still causing a failure in the LLVM testsuite: https://logs.chromium.org/logs/emscripten-releases/buildbucket/cr-buildbucket/8783849429524138417/+/u/LLVM_testsuite/stdout |
|
I have it reproing locally too, let me know if you want more detail or the files. |
|
I'm guessing that test is building with the default settings? (i.e. |
|
I'm guessing the change to system/lib/libcxxabi/src/cxa_noexception.cpp that was part of this change needs to have __cxa_free_exception as well as the __cxa_allocate_exception |
|
Yup that looks like it the problem.. the sad thing is I have no idea why non of our core tests required that. Any idea what |
|
According to https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html#cxx-destroy it seems like |
|
Oh wait, I see what you're saying. If catching is disabled, then why are we still getting this dependency? Yeah I don't quite get it yet either. |
|
I disassembled gtest-all.cc.o and found that |
|
In emscripten we default to throwing being enabled but catching being disabled.. which is a little unusual. |
|
I don't see |
|
I looks like it must be generated here: https://github.com/llvm/llvm-project/blob/64ae7669a7cefa12ea1117680cf9bfb5c3f6084c/clang/lib/CodeGen/CGException.cpp#L380-L388 Maybe we can add a test that simply calls it directly? Or figure out how to trigger the above code indirectly in C++? |
|
Yeah, if a generated call is possible, then a test that calls it directly probably makes sense. |
When we enable (Wasm / Emscripten) EH and don't use it, we still pulled in a lot of JS library functions + libc++abi library functions that were not DCE'ed. The reason was we mandatorily exported many functions whenever some EH options were enabled, regardless of they were used or not. 1. We used to enable `EXPORT_EXCEPTION_HANDLING_HELPERS`, which exports `getExceptionMessage` and `in/decrementExceptionRefcount`, whenever `EXCEPTION_STACK_TRACES` was true. And `EXCEPTION_STACK_TRACES` is true whenever `ASSERTIONS` is true, which is the default at `-O0`. And those exported functions can pull in many libc++abi functions. As a result, at `-O0`, we pull in a lot of functions even when we are not using any exceptions. This PR removes that `EXCEPTION_STACK_TRACES` -> `EXPORT_EXCEPTION_HANDLING_HELPERS` link. Without this link, we can still use see stack traces with an exception message with no problem, because `__cxa_throw` -> `__throw_exception_with_stack_trace` -> `getExceptionMessage` dependencies: https://github.com/emscripten-core/emscripten/blob/6ad2f5e03021a39377428e9d476985fc967014d4/system/lib/libcxxabi/src/cxa_exception.cpp#L302-L304 https://github.com/emscripten-core/emscripten/blob/6ad2f5e03021a39377428e9d476985fc967014d4/src/lib/libexceptions.js#L311 2. If we do 1, Emscripten EH's `getExceptionMessage` does not work, because unlike Wasm EH, its `getExceptionMessage` dependency is within `runtime_exceptions.js`, where we can't attach `__deps`, so we can't track it: https://github.com/emscripten-core/emscripten/blob/6ad2f5e03021a39377428e9d476985fc967014d4/src/runtime_exceptions.js#L20 So, this adds `getExceptionMessage` as a dependency of `libexception.js`'s `__cxa_throw` directly, when `EXCEPTION_STACK_TRACES` is enabled. 3. This removes several functions from `REQUIRED_EXPORTS` when Emscripten EH is used. Previously, the comment said, in LTO mode, `_cxa_find_matching_catch_*` -> `__cxa_can_catch` dependency is was not tracked. But now, in Emscripten EH, we preemptively add several `__cxa_find_matching_catch_n`s, and it depends on `findMatchingCatch`, which depends on `__cxa_end_catch`: https://github.com/emscripten-core/emscripten/blob/78403050f355085104175499224ad0f6bccb5fb1/src/lib/libexceptions.js#L371-L405 https://github.com/emscripten-core/emscripten/blob/78403050f355085104175499224ad0f6bccb5fb1/src/lib/libexceptions.js#L217 So we don't need to require exporting `__cxa_end_catch` anymore. Also, other required exports (`__cxa_in/decrement_exception_count` and `__cxa_free_exceptions`) are dependencies that can be naturally referred within libc++abi. I think these were added here in emscripten-core#16627 when we were using `deps_info.py`, which we don't use anymore. After this commit, when you compile an empty `int main { return 0; }` with `-O0` and `-fexceptions`/`-fwasm-exceptions`, the size decreases to: - `-fexceptions`: 113212 -> 1168 - `-fwasm-exceptions`: 109177 -> 1106
When we enabled (Wasm / Emscripten) EH and don't use it, we still pulled in a lot of JS library functions + libc++abi library functions that were not DCE'ed. The reason was we mandatorily exported many functions whenever some EH options were enabled, regardless of whether they were used or not. 1. We used to enable `EXPORT_EXCEPTION_HANDLING_HELPERS`, which exports `getExceptionMessage` and `in/decrementExceptionRefcount`, whenever `EXCEPTION_STACK_TRACES` was true. And `EXCEPTION_STACK_TRACES` is true whenever `ASSERTIONS` is true, which is the default at `-O0`. And those exported functions can pull in many libc++abi functions. As a result, at `-O0`, we pulled in a lot of functions even when we were not using any exceptions. This PR removes that `EXCEPTION_STACK_TRACES` -> `EXPORT_EXCEPTION_HANDLING_HELPERS` link. Without this link, we can still see stack traces with exception messages with no problem, because `__cxa_throw` -> `__throw_exception_with_stack_trace` -> `getExceptionMessage` dependencies: https://github.com/emscripten-core/emscripten/blob/6ad2f5e03021a39377428e9d476985fc967014d4/system/lib/libcxxabi/src/cxa_exception.cpp#L302-L304 https://github.com/emscripten-core/emscripten/blob/6ad2f5e03021a39377428e9d476985fc967014d4/src/lib/libexceptions.js#L311 2. If we do 1, Emscripten EH's `getExceptionMessage` does not work, because unlike Wasm EH, its `getExceptionMessage` dependency is within `runtime_exceptions.js`, where we can't attach `__deps`, so we can't track it: https://github.com/emscripten-core/emscripten/blob/6ad2f5e03021a39377428e9d476985fc967014d4/src/runtime_exceptions.js#L20 So, this adds `getExceptionMessage` as a dependency of `libexception.js`'s `__cxa_throw` directly, when `EXCEPTION_STACK_TRACES` is enabled. 3. This removes several functions from `REQUIRED_EXPORTS` when Emscripten EH is used. Previously, the comment said, in LTO mode, `_cxa_find_matching_catch_*` -> `__cxa_can_catch` dependency was not tracked. But now, in Emscripten EH, we preemptively add several `__cxa_find_matching_catch_n`s, and it depends on `findMatchingCatch`, which depends on `__cxa_end_catch`: https://github.com/emscripten-core/emscripten/blob/78403050f355085104175499224ad0f6bccb5fb1/src/lib/libexceptions.js#L371-L405 https://github.com/emscripten-core/emscripten/blob/78403050f355085104175499224ad0f6bccb5fb1/src/lib/libexceptions.js#L217 So we don't need to require exporting `__cxa_end_catch` anymore. Also, other required exports (`__cxa_in/decrement_exception_count` and `__cxa_free_exceptions`) are dependencies that can be naturally referred within libc++abi. I think these were added here in emscripten-core#16627 when we were using `deps_info.py`, which we don't use anymore. After this commit, when you compile an empty `int main { return 0; }` with `-O0` and `-fexceptions`/`-fwasm-exceptions`, the size decreases to: - `-fexceptions`: 113212 -> 1168 - `-fwasm-exceptions`: 109177 -> 1106
When we enabled (Wasm / Emscripten) EH and didn't use it, we still pulled in a lot of JS library functions + libc++abi library functions that were not DCE'ed. The reason was we mandatorily exported many functions whenever some EH options were enabled, regardless of whether they were used or not. 1. We used to enable `EXPORT_EXCEPTION_HANDLING_HELPERS`, which exports `getExceptionMessage` and `in/decrementExceptionRefcount`, whenever `EXCEPTION_STACK_TRACES` was true. And `EXCEPTION_STACK_TRACES` is true whenever `ASSERTIONS` is true, which is the default at `-O0`. And those exported functions can pull in many libc++abi functions. As a result, at `-O0`, we pulled in a lot of functions even when we were not using any exceptions. This PR removes that `EXCEPTION_STACK_TRACES` -> `EXPORT_EXCEPTION_HANDLING_HELPERS` link. Without this link, we can still see stack traces with exception messages with no problem, because `__cxa_throw` -> `__throw_exception_with_stack_trace` -> `getExceptionMessage` dependencies: https://github.com/emscripten-core/emscripten/blob/6ad2f5e03021a39377428e9d476985fc967014d4/system/lib/libcxxabi/src/cxa_exception.cpp#L302-L304 https://github.com/emscripten-core/emscripten/blob/6ad2f5e03021a39377428e9d476985fc967014d4/src/lib/libexceptions.js#L311 2. If we do 1, Emscripten EH's `getExceptionMessage` does not work, because unlike Wasm EH, its `getExceptionMessage` dependency is within `runtime_exceptions.js`, where we can't attach `__deps`, so we can't track it: https://github.com/emscripten-core/emscripten/blob/6ad2f5e03021a39377428e9d476985fc967014d4/src/runtime_exceptions.js#L20 So, this adds `getExceptionMessage` as a dependency of `libexception.js`'s `__cxa_throw` directly, when `EXCEPTION_STACK_TRACES` is enabled. 3. This removes several functions from `REQUIRED_EXPORTS` when Emscripten EH is used. Previously, the comment said, in LTO mode, `_cxa_find_matching_catch_*` -> `__cxa_can_catch` dependency was not tracked. But now, in Emscripten EH, we preemptively add several `__cxa_find_matching_catch_n`s, and it depends on `findMatchingCatch`, which depends on `__cxa_end_catch`: https://github.com/emscripten-core/emscripten/blob/78403050f355085104175499224ad0f6bccb5fb1/src/lib/libexceptions.js#L371-L405 https://github.com/emscripten-core/emscripten/blob/78403050f355085104175499224ad0f6bccb5fb1/src/lib/libexceptions.js#L217 So we don't need to require exporting `__cxa_end_catch` anymore. Also, other required exports (`__cxa_in/decrement_exception_count` and `__cxa_free_exceptions`) are dependencies that can be naturally referred within libc++abi. I think these were added here in #16627 when we were using `deps_info.py`, which we don't use anymore. After this commit, when you compile an empty `int main { return 0; }` with `-O0` and `-fexceptions`/`-fwasm-exceptions`, the size decreases to: - `-fexceptions`: 113212 -> 1168 - `-fwasm-exceptions`: 109177 -> 1106
Specifically, this change moves the allocation and reference counting
functions.
This saves on code size but more importantly reduces the number and of
complexity of imports/exports, which in turn helps with the wasm64 work
I've been doing.