Open serhiihuralniksc opened 2 years ago
Seems like there was a fix posted recently by @jyknight Does https://reviews.llvm.org/D113620 fix the issue for you?
@hiraditya thanks for a reply.
Not really. That change looks just like an optimization to speed up program termination by the cost of ignoring cleanups for noexcept
frame. It doesn't change overall try-catch-all-terminate approach for noexcept
frames.
I'm actively working on a proposal/implementation towards this. I expect to post a proposal to llvm-dev real-soon-now.
I'd note that GCC also doesn't reliably have the desired behavior, see https://gcc.gnu.org/PR55918
@jyknight having this fixed would be just amazing!
Do you mind to post a link here once you start discussion in llvm-dev?
@llvm/issue-subscribers-clang-codegen
I think this is the discussion: https://discourse.llvm.org/t/rfc-add-call-unwindabort-to-llvm-ir/62543
@jyknight Seems like the work on unwindabort
may take some time. I came to a sort of temporary workaround that may be interesting given that there seems to be a real demand to get better stacktraces on attempt to throw through noexcept
frames.
The idea here is that we use another channel to let runtime lib to know that throw must be failed. Now it is encoded not via LSDA but via personality routine, Namely by using a thin wrapper on top of normal C++ personality routine that alters returns code to such one that terminates unwinding if otherwise it would try to unwind through this frame:
_Unwind_Reason_Code __gxx_personality_v0(<args>);
_Unwind_Reason_Code __clang_noexcept_wrapper_for___gxx_personality_v0(<args>) {
auto urc = __gxx_personality_v0(<forwarded_args>);
if (urc == _URC_CONTINUE_UNWIND) // Maybe also need some `action` test to ensure it is phase1,
// at phase 2 this should never be called at all
return _URC_FATAL_PHASE1_ERROR; // Or _URC_END_OF_STACK?
return urc;
}
Personality wrapper is intended to be used instead of normal personality for all noexcept functions:
void bar() noexcept { // define void @_Z3barv() #0 personality ptr @__clang_noexcept_wrapper_for___gxx_personality_v0 {
// ...
}
Cons - this at least will prohibit inlining of other C++ functions in such noexcept
ones (if inlined one does have associated personality routine which is now different from what noexcept
uses). Likely there are other issues that I may miss, not sure.
Pros: binary size needed to implement this is minimal and is amortized immediately by using a single instance of personality wrapper, which in turn can be emitted by using the same model as __clang_call_terminate
, i.e. hidden linkonce with comdat. There is no dependency on LSDA format, plus noexcept behavior is implemented independently from normal EH structures.
Obviously it only works for Itanium ABI platforms but still may be interesting.
Intentionally posted it here instead of https://discourse.llvm.org/t/rfc-add-call-unwindabort-to-llvm-ir/62543 as it is irrelevant to unwindabort
work.
Any thoughts?
@ldionne, following our brief offline discussion earlier this year about my last comment (appreciate the opportunity) - we've figured out that an approach with a special personality routine that implements noexcept
semantics may often be non applicable for mach-o binaries.
The reason is that Apple' compact unwind info format (which appears to be a linker' default) allows to have no more that 4 different personality routines per dynamic object (unwind instructions use 2-bit field to index global personality array).
We've hit this limit in one of our iOS applications when adding a noexcept shim function with custom personality:
ld: too many personality routines for compact unwind to encode for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
And it is very likely that many more complex apps (that are written in multiple languages) would hit it as well. Currently we can not afford switching off compact unwind info and decided to use standard cxx personality for our noexcept shim function that now has manually crafted LSDA with an empty callsite table that causes desired phase I error.
So that my suggestion about a new noexcept personality is fully applicable to ELFs only where unwind info formats are way less restrictive to the number of personalities.
Actual behavior Clang/LLVM emits such code for
noexcept
-labeled functions that makes it impossible to locate athrow
statement that leads to program termination due to an attempt to propagate an exception throughnoexcept
frame.This issue is architecture-independent and related to platforms that follow Itanium EHABI (observed for Linux and Android).
At least GCC does not have this problem.
Details Below is a sample program:
This obviously will terminate once exception reaches
h()
.Here is a crash backtrace for clang-compiled Linux amd64 binary (I'm using lldb but this is unwinder-independent):
App has terminated but the backtrace is useless for any further analysis because the call stack was partially unwound and we were taken away by the cxx runtime from the place where the culprit
throw
was executed. It is easy to imagine how this affects debuggability of a post-mortem dumps/traces for some complex C++ code that exposes C interface withnoexcept
s at the boundary to conform to the base platform ABI.The reason for this lost backtrace is how Clang implements
noexcept
specifier. An implementation boils down to an implicit try-catch that encloses the whole function code:There is an interesting consequence for such implementation. Exception propagation goes in the next way:
noexcept
functionnoexcept
frame which now calls__clang_call_terminate
which is clearly shown in the snippet above.So all is done according to the spec:
But it appears that this is not the only possible correct implementation and even more - it looks suboptimal in all aspects (binary size, cpu time, debuggability). This is what GCC produces for the same sample:
Backtrace is preserved and program was terminated w/o partial unwind. They implement
noexcept
by emitting an empty LSDA for such functions. This leads runtime to terminate at the 1st phase. I believe this behavior is mandated by the ABI. At least libstdc++ and libc++ do so.Expected behavior Is it possible for Clang to emit such code that will cause runtime to terminate during the 1st phase? Either by leveraging the same approach that GCC does or by using any other corner case that by the ABI should fail handler search before unwind even starts.
Extra Below are assembly listings that demonstrate implementation details more clearly:
Clang output. Termination landing pad and associated LSDA entry are present.
GCC output. No pads, only LSDA with an empty call-site table.