[llvm-dev] [Exception Handling] Could we mark __cxa_end_catch as nounwind conditionally? (original) (raw)

John McCall via llvm-dev llvm-dev at lists.llvm.org
Mon Aug 30 12:43:22 PDT 2021


On 30 Aug 2021, at 2:59, Fāng-ruì Sòng wrote:

On 2021-08-30, chuanqi.xcq wrote:

Hi all,

Let me introduce about the background: I find that the compiler couldn't mark foo as nounwind in the following example: _ _void bar();_ _void foo() {_ _try {_ _bar();_ _} catch(...) {}_ _}_ _ From my perspective, it is clear that foo wouldn't throw any exception. So it is natural to me that the compiler could mark foo as nounwind as an optimization. But it didn't. This pattern occurs frequently in C++20 coroutine. So I tried to handle coroutine specially before in: https://reviews.llvm.org/D108277. But the all the reviewers strongly suggested that we should handle this case generally for all of functions instead of coroutines only. Then when I looked into the details in IR, I found the reason is _that cxaendcatch isn't nounwind. Here is the IR generated: _ _; Function Attrs: mustprogress uwtable_ _define dsolocal void @Z3foov() localunnamedaddr #0 personality_ _i8* bitcast (i32 (...)* @_gxxpersonalityv0 to i8*) {_ _invoke void @Z3barv()_ _to label %5 unwind label %1_ _1: ; preds = %0_ _%2 = landingpad { i8*, i32 }_ _catch i8* null_ _%3 = extractvalue { i8*, i32 } %2, 0_ _%4 = tail call i8* @_cxabegincatch(i8* %3) #2 ; nounwind_ _tail call void @_cxaendcatch()_ _br label %5_ _5: ; preds = %0, %1_ _ret void_ _}_ _ _I found that if I marked the call to cxaendcatch() as nounwind, the foo could be marked as nounwind. So I start to _survey why cxaendcatch() isn't 'nounwind'. _First is the comment on cxaendcatch() in libcxxabi: _ _Upon exit for any reason, a handler must call:_ _void _cxaendcatch ();_ _This routine can be called for either a native or foreign exception._ _For a native exception:_ _* Locates the most recently caught exception and decrements its_ _handler count._ _* Removes the exception from the caught exception stack, if the_ _handler count goes to zero._ _* If the handler count goes down to zero, and the exception was not_ _re-thrown_ _by throw, it locates the primary exception (which may be the same as_ _the one_ _it's handling) and decrements its reference count. If that reference_ _count_ _goes to zero, the function destroys the exception. In any case, if_ _the current_ _exception is a dependent exception, it destroys that._ _For a foreign exception:_ _* If it has been rethrown, there is nothing to do._ _* Otherwise delete the exception and pop the catch stack to empty._ _ I am not familiar with exception handling. But from the comment _above, it looks like that cxaendcatch wouldn't throw. Then in clang::ItaniumCXXABI, I found this: _ _A cleanup to call _cxaendcatch. In many cases, the caught_ _exception type lets us state definitively that the thrown exception_ _type does not have a destructor. In particular:_ _- Catch-alls tell us nothing, so we have to conservatively_ _assume that the thrown exception might have a destructor._ _- Catches by reference behave according to their base types._ _- Catches of non-record types will only trigger for exceptions_ _of non-record types, which never have destructors._ _- Catches of record types can trigger for arbitrary subclasses_ _of the caught type, so we have to assume the actual thrown_ _exception type might have a throwing destructor, even if the_ _caught type's destructor is trivial or nothrow._ _ _It looks like that cxaendcatch would throw only if the exception caught has an destructor which may throw. Yes... But I think the situation is rare. First as the comment says, an exception type doesn't have a destructor usually. Then if it has a destructor, it is also rare that it may throw. Finally, it is a bad practice to throw from destructor which occurs in catch block. So I want to provide an option to tell the compiler whether the exceptions in current project has a may-throw destructor. In this way, we could optimize the example in the beginning. From GCC produced .gccexcepttable, it seems that GCC unconditionally _assumes that _cxabegincatch/cxaendcatch do not throw. GCC does _not emit call site records for the region with cxaendcatch. So I think we should unconditionally assume that __cxabegincatch/cxaendcatch don't throw as well. Sent https://reviews.llvm.org/D108905

That is not how this works. Here’s the path we need to take.

First, as I mentioned in the review, we need to figure out what the language standard expects to happen when the destructor for an exception throws at the end of a catch. Currently we are essentially treating this as undefined behavior, which I think is highly unlikely to be the intent of the standard.

If it’s allowed to unwind, then __cxa_end_catch can throw, and so the second step is to: (1) fix the bugs in the language runtimes to handle this situation correctly and not leak the exception object, and then (2) fix GCC to stop unconditionally assuming that __cxa_end_catch does not throw. We could then add an opt-in flag to globally assume that exception destructors never throw as an optimization if that’s really important for getting good code.

If it’s supposed to call std::terminate, then we need to come to an agreement with the language runtimes about whose responsibility it is to ensure that the exception is caught and std::terminate is called.
That means opening an issue with the Itanium C++ ABI. If we decide it’s the compiler’s responsibility to pass a noexcept function as the exception destructor, then we need to fix Clang to wrap the destructor in a noexcept function when it’s not noexcept. If we decide it’s the runtime’s responsibility, we need to fix the runtimes to catch this exception and call std::terminate. In either of those casess, __cxa_end_catch is known not to throw. We could also decide it’s the compiler’s responsibility to call std::terminate if __cxa_end_catch throws, and if so then we have to do that; however, I think that would be a bad outcome.

John. -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20210830/3054dfaa/attachment.html>



More information about the llvm-dev mailing list