Fixes https://github.com/llvm/llvm-project/issues/63818 for control flow out of an expressions. #### Background A control flow could happen in the middle of an expression due to stmt-expr and coroutine suspensions. Due to branch-in-expr, we missed running cleanups for the temporaries constructed in the expression before the branch. Previously, these cleanups were only added as `EHCleanup` during the expression and as normal expression after the full expression. Examples of such deferred cleanups include: `ParenList/InitList`: Cleanups for fields are performed by the destructor of the object being constructed. `Array init`: Cleanup for elements of an array is included in the array cleanup. `Lifetime-extended temporaries`: reference-binding temporaries in braced-init are lifetime extended to the parent scope. `Lambda capture init`: init in the lambda capture list is destroyed by the lambda object. --- #### In this PR In this PR, we change some of the `EHCleanups` cleanups to `NormalAndEHCleanups` to make sure these are emitted when we see a branch inside an expression (through statement expressions or coroutine suspensions). These are supposed to be deactivated after full expression and destroyed later as part of the destructor of the aggregate or array being constructed. To simplify deactivating cleanups, we add two utilities as well: * `DeferredDeactivationCleanupStack`: A stack to remember cleanups with deferred deactivation. * `CleanupDeactivationScope`: RAII for deactivating cleanups added to the above stack. --- #### Deactivating normal cleanups These were previously `EHCleanups` and not `Normal` and **deactivation** of **required** `Normal` cleanups had some bugs. These specifically include deactivating `Normal` cleanups which are not the top of `EHStack` [source1](92b56011e6/clang/lib/CodeGen/CGCleanup.cpp (L1319)), [2](92b56011e6/clang/lib/CodeGen/CGCleanup.cpp (L722-L746)). This has not been part of our test suite (maybe it was never required before statement expressions). In this PR, we also fix the emission of required-deactivated-normal cleanups.
94 lines
4.2 KiB
C++
94 lines
4.2 KiB
C++
// RUN: %clang_cc1 --std=c++20 -triple x86_64-linux-gnu -emit-llvm %s -o - | FileCheck %s
|
|
|
|
#include "Inputs/coroutine.h"
|
|
|
|
struct Printy {
|
|
Printy(const char *name) : name(name) {}
|
|
~Printy() {}
|
|
const char *name;
|
|
};
|
|
|
|
struct coroutine {
|
|
struct promise_type;
|
|
std::coroutine_handle<promise_type> handle;
|
|
~coroutine() {
|
|
if (handle) handle.destroy();
|
|
}
|
|
};
|
|
|
|
struct coroutine::promise_type {
|
|
coroutine get_return_object() {
|
|
return {std::coroutine_handle<promise_type>::from_promise(*this)};
|
|
}
|
|
std::suspend_never initial_suspend() noexcept { return {}; }
|
|
std::suspend_always final_suspend() noexcept { return {}; }
|
|
void return_void() {}
|
|
void unhandled_exception() {}
|
|
};
|
|
|
|
struct Awaiter : std::suspend_always {
|
|
Printy await_resume() { return {"awaited"}; }
|
|
};
|
|
|
|
int foo() { return 2; }
|
|
|
|
coroutine ArrayInitCoro() {
|
|
// Verify that:
|
|
// - We do the necessary stores for array cleanups.
|
|
// - Array cleanups are called by await.cleanup.
|
|
// - We activate the cleanup after the first element and deactivate it in await.ready (see cleanup.isactive).
|
|
|
|
// CHECK-LABEL: define dso_local void @_Z13ArrayInitCorov
|
|
// CHECK: %arrayinit.endOfInit = alloca ptr, align 8
|
|
// CHECK: %cleanup.isactive = alloca i1, align 1
|
|
Printy arr[2] = {
|
|
Printy("a"),
|
|
// CHECK: %arrayinit.begin = getelementptr inbounds [2 x %struct.Printy], ptr %arr.reload.addr, i64 0, i64 0
|
|
// CHECK-NEXT: %arrayinit.begin.spill.addr = getelementptr inbounds %_Z13ArrayInitCorov.Frame, ptr %0, i32 0, i32 10
|
|
// CHECK-NEXT: store ptr %arrayinit.begin, ptr %arrayinit.begin.spill.addr, align 8
|
|
// CHECK-NEXT: store i1 true, ptr %cleanup.isactive.reload.addr, align 1
|
|
// CHECK-NEXT: store ptr %arrayinit.begin, ptr %arrayinit.endOfInit.reload.addr, align 8
|
|
// CHECK-NEXT: call void @_ZN6PrintyC1EPKc(ptr noundef nonnull align 8 dereferenceable(8) %arrayinit.begin, ptr noundef @.str)
|
|
// CHECK-NEXT: %arrayinit.element = getelementptr inbounds %struct.Printy, ptr %arrayinit.begin, i64 1
|
|
// CHECK-NEXT: %arrayinit.element.spill.addr = getelementptr inbounds %_Z13ArrayInitCorov.Frame, ptr %0, i32 0, i32 11
|
|
// CHECK-NEXT: store ptr %arrayinit.element, ptr %arrayinit.element.spill.addr, align 8
|
|
// CHECK-NEXT: store ptr %arrayinit.element, ptr %arrayinit.endOfInit.reload.addr, align 8
|
|
co_await Awaiter{}
|
|
// CHECK-NEXT: @_ZNSt14suspend_always11await_readyEv
|
|
// CHECK-NEXT: br i1 %{{.+}}, label %await.ready, label %CoroSave30
|
|
};
|
|
// CHECK: await.cleanup: ; preds = %AfterCoroSuspend{{.*}}
|
|
// CHECK-NEXT: br label %cleanup{{.*}}.from.await.cleanup
|
|
|
|
// CHECK: cleanup{{.*}}.from.await.cleanup: ; preds = %await.cleanup
|
|
// CHECK: br label %cleanup{{.*}}
|
|
|
|
// CHECK: await.ready:
|
|
// CHECK-NEXT: %arrayinit.element.reload.addr = getelementptr inbounds %_Z13ArrayInitCorov.Frame, ptr %0, i32 0, i32 11
|
|
// CHECK-NEXT: %arrayinit.element.reload = load ptr, ptr %arrayinit.element.reload.addr, align 8
|
|
// CHECK-NEXT: call void @_ZN7Awaiter12await_resumeEv
|
|
// CHECK-NEXT: store i1 false, ptr %cleanup.isactive.reload.addr, align 1
|
|
// CHECK-NEXT: br label %cleanup{{.*}}.from.await.ready
|
|
|
|
// CHECK: cleanup{{.*}}: ; preds = %cleanup{{.*}}.from.await.ready, %cleanup{{.*}}.from.await.cleanup
|
|
// CHECK: %cleanup.is_active = load i1, ptr %cleanup.isactive.reload.addr, align 1
|
|
// CHECK-NEXT: br i1 %cleanup.is_active, label %cleanup.action, label %cleanup.done
|
|
|
|
// CHECK: cleanup.action:
|
|
// CHECK: %arraydestroy.isempty = icmp eq ptr %arrayinit.begin.reload{{.*}}, %{{.*}}
|
|
// CHECK-NEXT: br i1 %arraydestroy.isempty, label %arraydestroy.done{{.*}}, label %arraydestroy.body.from.cleanup.action
|
|
// Ignore rest of the array cleanup.
|
|
}
|
|
|
|
coroutine ArrayInitWithCoReturn() {
|
|
// CHECK-LABEL: define dso_local void @_Z21ArrayInitWithCoReturnv
|
|
// Verify that we start to emit the array destructor.
|
|
// CHECK: %arrayinit.endOfInit = alloca ptr, align 8
|
|
Printy arr[2] = {"a", ({
|
|
if (foo()) {
|
|
co_return;
|
|
}
|
|
"b";
|
|
})};
|
|
}
|