-
Notifications
You must be signed in to change notification settings - Fork 12k
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
[libc++] [libc++abi] Initialize exception directly in make_exception_ptr if __cxa_init_primary_exception is available in ABI-library #65534
[libc++] [libc++abi] Initialize exception directly in make_exception_ptr if __cxa_init_primary_exception is available in ABI-library #65534
Conversation
969531a
to
786f1f4
Compare
✅ With the latest revision this PR passed the C/C++ code formatter. |
3373241
to
ec0229c
Compare
I had to split The build is failing mostly due to missing changes in abi-lists for various architectures, i'm planning to fix that once/if the general idea is approved. One question left is the way to determine whether the new @philnik777 @EricWF Could you please have a look? |
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.
I haven't looked at the actual changes closely yet, but the amount of extra code seems reasonable for a significant speedup.
@@ -51,11 +69,24 @@ class _LIBCPP_EXPORTED_FROM_ABI exception_ptr { | |||
template <class _Ep> | |||
_LIBCPP_HIDE_FROM_ABI exception_ptr make_exception_ptr(_Ep __e) _NOEXCEPT { | |||
# ifndef _LIBCPP_HAS_NO_EXCEPTIONS | |||
# if defined(LIBCXX_BUILDING_LIBCXXABI) |
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.
This should be guarded by an availability macro instead.
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.
Should it be a macro or a runtime availability check instead?
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.
I've added support for libcxxrt
as well and made it a runtime check
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.
The ifdef
became a bit too big to copy-paste it, so i moved it into __config
Benchmarks to back the performance claims up:
It can be seen that with the patch not only the performance is x30(!) in single-threaded case, but it also scales linearly with the number of threads (CPU / Time == number of threads), when on current trunk it's not the case: i'm using not the most recent gcc's unwinder, so contention on the lock in The effect is the same with I've addressed review comments and the build is green, so re-requesting the review. |
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.
Overall, seems like a good idea; I haven't reviewed the whole thing, but here's some comments.
// and the caller knows how to handle this (it fallbacks to throw + catch). | ||
using CxaInitPrimaryExceptionPrototype = void *(*)(void *, type_info *, void(*)(void *)); | ||
static CxaInitPrimaryExceptionPrototype cxa_init_primary_exception_fn = reinterpret_cast<CxaInitPrimaryExceptionPrototype>( | ||
dlsym(RTLD_DEFAULT, "__cxa_init_primary_exception")); |
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.
It would be better to use a weak reference to the __cxa_init_primary_exception instead of dlsym.
That way, the loader takes care of the lookup, and if you are building libc++ and libc++abi together (which is the common case), there's no extra overhead.
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.
Apparently that doesn't seem to work with Apple.
I'm fine with that, since i'm only interested in linux platform, and for now i ifdefed-out Apple
# if defined(LIBCXXRT) || defined(LIBCXX_BUILDING_LIBCXXABI) | ||
using _Ep2 = __decay_t<_Ep>; | ||
void* __ex = exception_ptr::__init_native_exception( | ||
sizeof(_Ep), const_cast<std::type_info*>(&typeid(_Ep)), exception_ptr::__dest_thunk<_Ep2>); |
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.
Can't use typeid
with -fno-rtti, but exceptions can be used with -fno-rtti so the previous impl would work. So this code should all be in a !_LIBCPP_HAS_NO_RTTI ifdef.
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.
Can't use
typeid
with -fno-rtti, but exceptions can be used with -fno-rtti so the previous impl would work. So this code should all be in a !_LIBCPP_HAS_NO_RTTI ifdef.
Hmm... (at least on Itanium ABI?) when exceptions need to be thrown, type_info
objects are generated and passed on throwing even if -fno-rtti is used.
Is there any intrinsic to obtain RTTI for EH when -fno-rtti is used?
2c6c34d
to
c74746d
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.
Hi! I've addressed/answered the inline comments and manually checked that libcxx builds + works with libcxxabi/libsupc++/libcxxrt (should there be a CI-build with such combinations?), so re-requesting the review.
exception_ptr exception_ptr::__from_native_exception_pointer(void* __e) noexcept | ||
{ | ||
exception_ptr ptr{}; | ||
new (reinterpret_cast<void*>(&ptr)) __exception_ptr::exception_ptr(__e); |
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.
This has got to be an aliasing violation that the compiler can optimize on?
The types have two different sizes.
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.
Do they?
llvm-project/libcxx/src/support/runtime/exception_pointer_glibcxx.ipp
Lines 42 to 47 in ea1909f
exception_ptr::exception_ptr(const exception_ptr& other) noexcept | |
: __ptr_(other.__ptr_) | |
{ | |
new (reinterpret_cast<void*>(this)) __exception_ptr::exception_ptr( | |
reinterpret_cast<const __exception_ptr::exception_ptr&>(other)); | |
} |
My understanding is that this is a hackery of course, but the hackery that should work:
llvm-project/libcxx/src/support/runtime/exception_pointer_glibcxx.ipp
Lines 13 to 16 in ea1909f
// we have little choice but to hijack std::__exception_ptr::exception_ptr's | |
// (which fortunately has the same layout as our std::exception_ptr) copy | |
// constructor, assignment operator and destructor (which are part of its | |
// stable ABI), and its rethrow_exception(std::__exception_ptr::exception_ptr) |
@@ -25,6 +36,9 @@ struct exception_ptr | |||
{ | |||
void* __ptr_; | |||
|
|||
# if defined(_LIBCPP_EXCEPTION_PTR_DIRECT_INIT) | |||
exception_ptr(void*) noexcept; |
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.
Explicit please.
} catch (...) { | ||
return current_exception(); | ||
} | ||
} |
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.
We should have an unreachable annotation at the bottom of this block.
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.
Added it, but could you please explain to me why is it needed?
} catch (...) { | ||
exception_ptr::__free_native_exception(__ex); |
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.
How do we know that we don't also have to destroy the constructed exception?
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.
We only get here if the exception constructor threw, so there's no object to destroy. Am i missing something?
# if defined(_LIBCPP_EXCEPTION_PTR_DIRECT_INIT) | ||
using _Ep2 = __decay_t<_Ep>; | ||
void* __ex = exception_ptr::__init_native_exception( | ||
sizeof(_Ep), const_cast<std::type_info*>(&typeid(_Ep)), exception_ptr::__dest_thunk<_Ep2>); |
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.
This code allocates for _Ep
here but then constructs _Ep2
. Similarly, it uses the typeinfo for _Ep
.
We're sure this is correct?
libcxx/include/__config
Outdated
@@ -1048,6 +1048,10 @@ __sanitizer_verify_double_ended_contiguous_container(const void*, const void*, c | |||
# define _LIBCPP_HAS_NO_RTTI | |||
# endif | |||
|
|||
# if !defined(_LIBCPP_HAS_NO_EXCEPTIONS) && !defined(_LIBCPP_HAS_NO_RTTI) && !defined(__APPLE__) && !defined(_WIN32) |
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.
Why is this disabled on Apple and on Windows? It seems to me like this should be an availability macro instead so that all platforms can take advantage of it as long as the deployment target is recent enough to support this new addition. I think this is what @philnik777 was referring to in another comment.
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.
The "availability macro": is that https://clang.llvm.org/docs/AttributeReference.html#availability?
Anyway, i'm failing to understand how that could eliminate the need to have a runtime check for the __cxa_init_primary_exception
presence -- say, if i build against 18.0 libcxxabi and then LD_PRELOAD 17.0 version, which should be a supported case, right?
To answer your question directly:
Windows: disabled due to mingw requiring this
#define _LIBCXXABI_DTOR_FUNC __thiscall |
__cxa_init_primary_exception
is present and the exception destructor is defined). I didn't want to copy-paste this macro in libc++, but definitely doable, should i implement it?
Apple: back-deployment-macosx tests fail (https://buildkite.com/llvm-project/libcxx-ci/builds/30969#018b50e0-c82b-42e0-bceb-b74d5808ffad, for example) with dyld[47810]: missing symbol called
error. I have very limited knowledge about how linking is performed on Apple, but my quick googling tells that it's much different from what ld does, and weak attributes don't work without some compiler flags specified. I have no clue how this should be fixed and it also takes forever for CI to reach this check, so any help would be appreciated.
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.
I've added a mingw support
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.
If you look at how the availability macros in __availability
work, basically we use the --target <arch>-<vendor>-<platform><version>
to drive this. The idea is that you tell the compiler what's the oldest version of the platform you are targeting, and based on that we make the function available or not. If you then decide to run your program on an older platform, you effectively lied to the compiler and in practice you'll get a load-time error.
…O_EXCEPTIONS or _LIBCPP_HAS_NO_RTTI
I had to spend some time bisecting very cryptic Building with libcxxrt also requires Also, addressing the last outstanding comment about |
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.
Thanks, LGTM!
The tests failing are ARMv7-picolib tests, and they fail with
which i don't think is related to this PR changes |
Indeed, those CI failures are unrelated and should be fixed on |
Thank you for all the time you've spent helping me with this PR. |
Hello, my (unsupported, non-standard, likely-not-correctly-set-up) bot started failing to build after this: http://45.33.8.238/linux/128697/step_4.txt Looking through the code a bit, I think libcxx/src/exception.cpp includes (For my bot, it's probably incorrect that none of these macros are defined! But exception.cpp kind of looks like that case is supposed to work, so I thought I'd raise this here.) |
See here for context: #65534 (comment)
Hi @nico! No, that is absolutely not intentional, i've overlooked that, sorry. |
FWIW this caused an incompatibility with recent libcxxrt (after libcxxrt/libcxxrt@03c83f5), since its I submitted libcxxrt/libcxxrt#25, since I am not sure about the official signature of these functions, if there is any. |
@DimitryAndric Thanks, please keep us in the loop. If we need to make a fix on our side, @itrofimow we'll want to cherry-pick it to LLVM 18. |
@ldionne it looks like the consensus is that GCC and LLVM are correct and that we're fixing libcxxrt. The relevant pull request is libcxxrt/libcxxrt#27. |
Thanks for the heads up! |
llvm/llvm-project#65534 introduces `__cxa_init_primary_exception` and uses this in libcxx. Like several other methods like the below in `cxa_exception.cpp`, https://github.com/emscripten-core/emscripten/blob/fbdd9249e939660cb0d20f00d6bc1897c2f3905e/system/lib/libcxxabi/src/cxa_exception.cpp#L264-L269 this new function takes a pointer to a destructor, and in Wasm destructor returns a `this` pointer, which requires `ifdef __USING_WASM_EXCEPTION__` on its signature. And that it is also used in libcxx means we need to guard some code in libcxx with `ifdef __USING_WASM_EXCEPTION__` for the signature difference, and we need to build libcxx with `-D__USING_WASM_EXCEPTIONS__` when Wasm EH is used.
llvm/llvm-project#65534 added `__cxa_init_primary_exception` function and use it in both in libcxxabi's `__cxa_throw` and also directly in libcxx. Because we create exceptions in JS, we need a counterpart in JS exception library too.
This updates libcxx and libcxxabi to LLVM 18.1.2. Due to llvm/llvm-project#65534 we need to update both at the same time. Things to note: - Disable hardening mode: LLVM 18.1.2 adds support for "Hardening Modes" to libcxx (https://libcxx.llvm.org/Hardening.html). There are four modes: none, fast, extensive and debug. Different hardening modes make different trade-offs between the amount of checking and runtime performance. We for now disable it (i.e., set it to 'none') so that we don't have any effects on the performance. We can consider enabling it when we ever get to enable the debug version of libcxx. - Disable C++20 time zone support: LLVM 18.1.2 adds C++20 time zone support (https://libcxx.llvm.org/DesignDocs/TimeZone.html) to libcxx, which requires access IANA Time Zone Database. Currently it seems it only supports Linux: https://github.com/llvm/llvm-project/blob/26a1d6601d727a96f4301d0d8647b5a42760ae0c/libcxx/src/tz.cpp#L45-L49 So this excludes the two source files from build (which is done via `CMakeLists.txt` in the upstream LLVM) and sets `_LIBCPP_HAS_NO_TIME_ZONE_DATABASE` macro in `__config_site`. In future maybe we can consider implementing this in JS. - `__cxa_init_primary_exception` support: llvm/llvm-project#65534 introduces `__cxa_init_primary_exception` and uses this in libcxx. Like several other methods like the below in `cxa_exception.cpp`, https://github.com/emscripten-core/emscripten/blob/fbdd9249e939660cb0d20f00d6bc1897c2f3905e/system/lib/libcxxabi/src/cxa_exception.cpp#L264-L269 this new function takes a pointer to a destructor, and in Wasm destructor returns a `this` pointer, which requires `ifdef __USING_WASM_EXCEPTION__` on its signature. And that it is also used in libcxx means we need to guard some code in libcxx with `ifdef __USING_WASM_EXCEPTION__` for the signature difference, and we need to build libcxx with `-D__USING_WASM_EXCEPTIONS__` when Wasm EH is used. Also we add Emscripte EH's counterpart in `cxa_exception_emscripten.cpp` too. - This version fixes long-running misbehaviors of `operator new` llvm/llvm-project#69498 seems to fix some long-running misbhaviors of `operator new`, which in emscripten we tried to fix in #11079 and #20154. So while resolving the conflicts, I effectively reverted #11079 and #20154 in `libcxx/src/stdlib_new_delete.cpp` and `libcxx/src/new.cpp`. - Add default `__assertion_handler` to `libcxx/include`: llvm/llvm-project#77883 adds a way for vendors to override how we handle assertions. If a vendor doesn't want to override it, CMake will copy the provided [default template assertion handler file](https://github.com/llvm/llvm-project/blob/26a1d6601d727a96f4301d0d8647b5a42760ae0c/libcxx/vendor/llvm/default_assertion_handler.in) to libcxx's generated include dir, which defaults to `SYSROOT/include/c++/v1`: https://github.com/llvm/llvm-project/blob/26a1d6601d727a96f4301d0d8647b5a42760ae0c/libcxx/CMakeLists.txt#L72-L73 https://github.com/llvm/llvm-project/blob/26a1d6601d727a96f4301d0d8647b5a42760ae0c/libcxx/CMakeLists.txt#L439 https://github.com/llvm/llvm-project/blob/26a1d6601d727a96f4301d0d8647b5a42760ae0c/libcxx/include/CMakeLists.txt#L1024 We don't use CMake, so this renames the provided `vendor/llvm/default_assertion_handler.in` to `__assert_handler` in our `libcxx/include` directory, which will be copied to `SYSROOT/include/c++/v1`. - Disable `__libcpp_verbose_abort directly` in code: In #20707 we decided to disable `__libcpp_verbose_abort` not to incur the code size increase that brings (it brings in `vfprintf` and its family). We disabled it in adding `#define _LIBCPP_AVAILABILITY_HAS_NO_VERBOSE_ABORT` in `__config_site`. But that `_NO_` macros are gone in LLVM 18.1.2, and changing it to `#define _LIBCPP_AVAILABILITY_HAS_VERBOSE_ABORT 0` does not work because it is overridden by this line, unless we provide our own vendor annotations: https://github.com/llvm/llvm-project/blob/38f5596feda3276a8aa64fc14e074334017088ca/libcxx/include/__availability#L138 I asked about this in llvm/llvm-project#87012 and llvm/llvm-project#71002 (comment) (and more comments below) but didn't get an actionable answer yet. So this disables `__libcpp_verbose_abort` in the code directly for now, hopefully temporarily. Each fix is described in its own commit, except for the fixes required to resolve conflicts when merging our changes, which I wasn't able to commit separately. Fixes #21603.
This updates libcxx and libcxxabi to LLVM 18.1.2. Due to llvm/llvm-project#65534 we need to update both at the same time. Things to note: - Disable hardening mode: LLVM 18.1.2 adds support for "Hardening Modes" to libcxx (https://libcxx.llvm.org/Hardening.html). There are four modes: none, fast, extensive and debug. Different hardening modes make different trade-offs between the amount of checking and runtime performance. We for now disable it (i.e., set it to 'none') so that we don't have any effects on the performance. We can consider enabling it when we ever get to enable the debug version of libcxx. - Disable C++20 time zone support: LLVM 18.1.2 adds C++20 time zone support (https://libcxx.llvm.org/DesignDocs/TimeZone.html) to libcxx, which requires access IANA Time Zone Database. Currently it seems it only supports Linux: https://github.com/llvm/llvm-project/blob/26a1d6601d727a96f4301d0d8647b5a42760ae0c/libcxx/src/tz.cpp#L45-L49 So this excludes the two source files from build (which is done via `CMakeLists.txt` in the upstream LLVM) and sets `_LIBCPP_HAS_NO_TIME_ZONE_DATABASE` macro in `__config_site`. In future maybe we can consider implementing this in JS. - `__cxa_init_primary_exception` support: llvm/llvm-project#65534 introduces `__cxa_init_primary_exception` and uses this in libcxx. Like several other methods like the below in `cxa_exception.cpp`, https://github.com/emscripten-core/emscripten/blob/fbdd9249e939660cb0d20f00d6bc1897c2f3905e/system/lib/libcxxabi/src/cxa_exception.cpp#L264-L269 this new function takes a pointer to a destructor, and in Wasm destructor returns a `this` pointer, which requires `ifdef __USING_WASM_EXCEPTION__` on its signature. And that it is also used in libcxx means we need to guard some code in libcxx with `ifdef __USING_WASM_EXCEPTION__` for the signature difference, and we need to build libcxx with `-D__USING_WASM_EXCEPTIONS__` when Wasm EH is used. Also we add Emscripte EH's counterpart in `cxa_exception_emscripten.cpp` too. - This version fixes long-running misbehaviors of `operator new` llvm/llvm-project#69498 seems to fix some long-running misbhaviors of `operator new`, which in emscripten we tried to fix in emscripten-core#11079 and emscripten-core#20154. So while resolving the conflicts, I effectively reverted emscripten-core#11079 and emscripten-core#20154 in `libcxx/src/stdlib_new_delete.cpp` and `libcxx/src/new.cpp`. - Add default `__assertion_handler` to `libcxx/include`: llvm/llvm-project#77883 adds a way for vendors to override how we handle assertions. If a vendor doesn't want to override it, CMake will copy the provided [default template assertion handler file](https://github.com/llvm/llvm-project/blob/26a1d6601d727a96f4301d0d8647b5a42760ae0c/libcxx/vendor/llvm/default_assertion_handler.in) to libcxx's generated include dir, which defaults to `SYSROOT/include/c++/v1`: https://github.com/llvm/llvm-project/blob/26a1d6601d727a96f4301d0d8647b5a42760ae0c/libcxx/CMakeLists.txt#L72-L73 https://github.com/llvm/llvm-project/blob/26a1d6601d727a96f4301d0d8647b5a42760ae0c/libcxx/CMakeLists.txt#L439 https://github.com/llvm/llvm-project/blob/26a1d6601d727a96f4301d0d8647b5a42760ae0c/libcxx/include/CMakeLists.txt#L1024 We don't use CMake, so this renames the provided `vendor/llvm/default_assertion_handler.in` to `__assert_handler` in our `libcxx/include` directory, which will be copied to `SYSROOT/include/c++/v1`. - Disable `__libcpp_verbose_abort directly` in code: In emscripten-core#20707 we decided to disable `__libcpp_verbose_abort` not to incur the code size increase that brings (it brings in `vfprintf` and its family). We disabled it in adding `#define _LIBCPP_AVAILABILITY_HAS_NO_VERBOSE_ABORT` in `__config_site`. But that `_NO_` macros are gone in LLVM 18.1.2, and changing it to `#define _LIBCPP_AVAILABILITY_HAS_VERBOSE_ABORT 0` does not work because it is overridden by this line, unless we provide our own vendor annotations: https://github.com/llvm/llvm-project/blob/38f5596feda3276a8aa64fc14e074334017088ca/libcxx/include/__availability#L138 I asked about this in llvm/llvm-project#87012 and llvm/llvm-project#71002 (comment) (and more comments below) but didn't get an actionable answer yet. So this disables `__libcpp_verbose_abort` in the code directly for now, hopefully temporarily. Each fix is described in its own commit, except for the fixes required to resolve conflicts when merging our changes, which I wasn't able to commit separately. Fixes emscripten-core#21603.
This patch implements an ABI-extension function
__cxa_init_primary_exception
in libcxxabi, which is already present as an extension in both libsupc++ and libcxxrt, and makes use of this function instd::make_exception_ptr
: now instead of going through full throw + catch cycle we are able to initialize an exception directly, thus makingstd::make_exception_ptr
around x30 times faster.