mirror of
https://github.com/capstone-engine/llvm-capstone.git
synced 2025-05-13 17:37:00 +00:00

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. Reviewed By: ChuanqiXu Differential Revision: https://reviews.llvm.org/D108905
103 lines
3.5 KiB
C++
103 lines
3.5 KiB
C++
// Verify that coroutine promise and allocated memory are freed up on exception.
|
|
// RUN: %clang_cc1 -std=c++20 -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -fexceptions -fcxx-exceptions -disable-llvm-passes | FileCheck %s --check-prefixes=CHECK,THROWEND
|
|
// RUN: %clang_cc1 -std=c++20 -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -fexceptions -fcxx-exceptions -fassume-nothrow-exception-dtor -disable-llvm-passes | FileCheck %s --check-prefixes=CHECK,NOTHROWEND
|
|
|
|
namespace std {
|
|
template <typename... T> struct coroutine_traits;
|
|
|
|
template <class Promise = void> struct coroutine_handle {
|
|
coroutine_handle() = default;
|
|
static coroutine_handle from_address(void *) noexcept;
|
|
};
|
|
template <> struct coroutine_handle<void> {
|
|
static coroutine_handle from_address(void *) noexcept;
|
|
coroutine_handle() = default;
|
|
template <class PromiseType>
|
|
coroutine_handle(coroutine_handle<PromiseType>) noexcept;
|
|
};
|
|
} // namespace std
|
|
|
|
struct suspend_always {
|
|
bool await_ready() noexcept;
|
|
void await_suspend(std::coroutine_handle<>) noexcept;
|
|
void await_resume() noexcept;
|
|
};
|
|
|
|
template <> struct std::coroutine_traits<void> {
|
|
struct promise_type {
|
|
void get_return_object() noexcept;
|
|
suspend_always initial_suspend() noexcept;
|
|
suspend_always final_suspend() noexcept;
|
|
void return_void() noexcept;
|
|
promise_type();
|
|
~promise_type();
|
|
void unhandled_exception() noexcept;
|
|
};
|
|
};
|
|
|
|
struct Cleanup { ~Cleanup(); };
|
|
void may_throw();
|
|
|
|
// CHECK-LABEL: define{{.*}} void @_Z1fv(
|
|
void f() {
|
|
// CHECK: call noalias noundef nonnull ptr @_Znwm(i64
|
|
|
|
// If promise constructor throws, check that we free the memory.
|
|
|
|
// CHECK: invoke void @_ZNSt16coroutine_traitsIJvEE12promise_typeC1Ev(
|
|
// CHECK-NEXT: to label %{{.+}} unwind label %[[DeallocPad:.+]]
|
|
|
|
// CHECK: [[DeallocPad]]:
|
|
// CHECK-NEXT: landingpad
|
|
// CHECK-NEXT: cleanup
|
|
// THROWEND: br label %[[Dealloc:.+]]
|
|
// NOTHROWEND: icmp ne ptr %[[#]], null
|
|
// NOTHROWEND-NEXT: br i1 %[[#]], label %[[Dealloc:.+]], label
|
|
|
|
Cleanup cleanup;
|
|
may_throw();
|
|
|
|
// if may_throw throws, check that we destroy the promise and free the memory.
|
|
|
|
// CHECK: invoke void @_Z9may_throwv(
|
|
// CHECK-NEXT: to label %{{.+}} unwind label %[[CatchPad:.+]]
|
|
|
|
// CHECK: [[CatchPad]]:
|
|
// CHECK-NEXT: landingpad
|
|
// CHECK-NEXT: catch ptr null
|
|
// CHECK: call void @_ZN7CleanupD1Ev(
|
|
// CHECK: br label %[[Catch:.+]]
|
|
|
|
// CHECK: [[Catch]]:
|
|
// CHECK: call ptr @__cxa_begin_catch(
|
|
// CHECK: call void @_ZNSt16coroutine_traitsIJvEE12promise_type19unhandled_exceptionEv(
|
|
// THROWEND: invoke void @__cxa_end_catch()
|
|
// THROWEND-NEXT: to label %[[Cont:.+]] unwind
|
|
// NOTHROWEND: call void @__cxa_end_catch()
|
|
// NOTHROWEND-NEXT: br label %[[Cont2:.+]]
|
|
|
|
// THROWEND: [[Cont]]:
|
|
// THROWEND-NEXT: br label %[[Cont2:.+]]
|
|
// CHECK: [[Cont2]]:
|
|
// CHECK-NEXT: br label %[[Cleanup:.+]]
|
|
|
|
// CHECK: [[Cleanup]]:
|
|
// CHECK: call void @_ZNSt16coroutine_traitsIJvEE12promise_typeD1Ev(
|
|
// CHECK: %[[Mem0:.+]] = call ptr @llvm.coro.free(
|
|
// CHECK: call void @_ZdlPv(ptr noundef %[[Mem0]]
|
|
|
|
// CHECK: [[Dealloc]]:
|
|
// THROWEND: %[[Mem:.+]] = call ptr @llvm.coro.free(
|
|
// THROWEND: call void @_ZdlPv(ptr noundef %[[Mem]])
|
|
|
|
co_return;
|
|
}
|
|
|
|
// CHECK-LABEL: define{{.*}} void @_Z1gv(
|
|
void g() {
|
|
for (;;)
|
|
co_await suspend_always{};
|
|
// Since this is the endless loop there should be no fallthrough handler (call to 'return_void').
|
|
// CHECK-NOT: call void @_ZNSt16coroutine_traitsIJvEE12promise_type11return_voidEv
|
|
}
|