Files
clang-p2996/clang/test/CodeGenCoroutines/coro-suspend-cleanups.cpp
Utkarsh Saxena 89ba7e183e [codegen] Emit missing cleanups for stmt-expr and coro suspensions [take-2] (#85398)
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.
2024-04-10 12:59:24 +02:00

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";
})};
}