-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Move parts of emscripten exception handling to native code. NFC #16627
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
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These utilities seem to be duplicated here and in the new utils file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
This seems to have broken some LTO tests: https://logs.chromium.org/logs/emscripten-releases/buildbucket/cr-buildbucket/8784015253103176065/+/u/Emscripten_testsuite__LTO_/stdout e.g:
|
Ugh I was hoping Emscripten's own CI checks would be enough, and they passed. Can you revert please? |
I'm investigating now. Will revert if I can't find a quick fix. |
(sadly we don't have time to run all tests in all configurations during CI, so we run some only the emscripten-releases waterfall: https://ci.chromium.org/p/emscripten-releases/g/main/console) |
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. |
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.