Link: https://lists.llvm.org/pipermail/cfe-dev/2021-August/068740.html ("[Exception Handling] Could we mark __cxa_end_catch as nounwind conditionally?"
Link: https://github.com/llvm/llvm-project/issues/57375
A catch handler calls __cxa_begin_catch and __cxa_end_catch. For a catch-all
clause or a catch clause matching a record type, we:
- assume that the exception object may have a throwing destructor
- emit invoke void @__cxa_end_catch (as the call is not marked as the nounwind attribute).
- emit a landing pad to destroy local variables and call _Unwind_Resume
struct A { ~A(); }; struct B { int x; }; void opaque(); void foo() { A a; try { opaque(); } catch (...) { } // the exception object has an unknown type and may throw try { opaque(); } catch (B b) { } // B::~B is nothrow, but we do not utilize this }
Per C++ [dcl.fct.def.coroutine], a coroutine's function body implies a catch (...).
Our code generation pessimizes even simple code, like:
UserFacing foo() { A a; opaque(); co_return; // For `invoke void @__cxa_end_catch()`, the landing pad destroys the // promise_type and deletes the coro frame. }
Throwing destructors are typically discouraged. In many environments, the
destructors of exception objects are guaranteed to never throw, making our
conservative code generation approach seem wasteful.
Furthermore, throwing destructors tend not to work well in practice:
- GCC does not emit call site records for the region containing __cxa_end_catch. This has been a long time, since 2000.
- If a catch-all clause catches an exception object that throws, both GCC and Clang using libstdc++ leak the allocated exception object.
To avoid code generation pessimization, add an opt-in driver option
-fassume-nothrow-exception-dtor to assume that __cxa_end_catch calls have the
nounwind attribute. This implies that thrown exception objects' destructors
will never throw.
To detect misuses, diagnose throw expressions with a potentially-throwing
destructor. Technically, it is possible that a potentially-throwing destructor
never throws when called transitively by __cxa_end_catch, but these cases seem
rare enough to justify a relaxed mode.
I think it is better to treat the program as invalid if the compiler find the exception object's destructor is `noexcept(false)`. The earlier error message is better than debugging and understanding what happened actually.