================
@@ -0,0 +1,430 @@
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir
-emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s -check-prefixes=CIR,CIR-NOEH
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu
-fcxx-exceptions -fexceptions -fclangir -emit-cir %s -o %t-eh.cir
+// RUN: FileCheck --input-file=%t-eh.cir %s -check-prefixes=CIR,CIR-EH
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -O1 -fclangir
-emit-cir %s -o %t-O1.cir
+// RUN: FileCheck --input-file=%t-O1.cir %s -check-prefix=CIR-O1
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -emit-llvm
-disable-llvm-passes %s -o %t.ll
+// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG
+
+// Ensure coroutine parameter copies, promises and the body are destroyed in
the correct order.
+
+#include "Inputs/coroutine.h"
+
+struct Dtor {
+ ~Dtor();
+ int x;
+};
+
+struct Task {
+ struct promise_type {
+ ~promise_type();
+ Task get_return_object() noexcept;
+ std::suspend_always initial_suspend() noexcept;
+ std::suspend_always final_suspend() noexcept;
+ void return_void() noexcept;
+ void unhandled_exception() noexcept;
+ };
+};
+
+struct TrivialTask {
+ struct promise_type {
+ TrivialTask get_return_object() noexcept;
+ std::suspend_always initial_suspend() noexcept;
+ std::suspend_always final_suspend() noexcept;
+ void return_void() noexcept;
+ void unhandled_exception() noexcept;
+ };
+};
+
+Task promise_dtor() { co_return; }
+
+// The promise's destructor is not scoped to initial_suspend, where the promise
+// is constructed, and runs only in destroy.
+
+// CIR-LABEL: cir.func coroutine {{.*}} @_Z12promise_dtorv(
+// CIR: %[[PROMISE:.*]] = cir.alloca "__promise"
+// CIR-NOT: cir.cleanup.scope
+// CIR: cir.coroutine initialSuspend : {
+// CIR-NOT: {{cir.cleanup.scope|cir.call @_ZN4Task12promise_typeD1Ev}}
+// CIR: cir.await(init,
+// CIR-NOT: cir.call @_ZN4Task12promise_typeD1Ev
+// CIR: }, body : {
+// CIR-NOT: cir.call @_ZN4Task12promise_typeD1Ev
+// CIR: }, destroy : {
+// CIR-NEXT: cir.call @_ZN4Task12promise_typeD1Ev(%[[PROMISE]])
+// CIR-NEXT: %[[MEM:.*]] = cir.coro.intrinsic.free
+// CIR-NOT: cir.call @_ZN4Task12promise_typeD1Ev
+// CIR: cir.call @_ZdlPvm(%[[MEM]],
+// CIR-NOT: cir.call @_ZN4Task12promise_typeD1Ev
+// CIR: }, exit : {
+// CIR-NOT: cir.call @_ZN4Task12promise_typeD1Ev
+
+// OGCG-LABEL: define {{.*}} @_Z12promise_dtorv(
+// OGCG: coro.cleanup:
+// OGCG-NEXT: call void @_ZN4Task12promise_typeD1Ev(ptr {{.*}} %__promise)
+// OGCG: call ptr @llvm.coro.free(
+
+TrivialTask param_dtor(Dtor p) {
+ co_await std::suspend_always{};
+}
+
+// The parameter copy's destructor is not scoped around the whole
+// cir.coroutine, where it would run on every return to the caller.
+
+// CIR-LABEL: cir.func coroutine {{.*}} @_Z10param_dtor4Dtor(
+// CIR: %[[P:.*]] = cir.alloca "p"
+// CIR-NOT: cir.cleanup.scope
+// CIR: cir.coroutine initialSuspend : {
+// CIR-NOT: cir.call @_ZN4DtorD1Ev
+// CIR: }, destroy : {
+// CIR-NEXT: cir.call @_ZN4DtorD1Ev(%[[P]])
+// CIR-NEXT: cir.coro.intrinsic.free
+// CIR-NOT: cir.call @_ZN4DtorD1Ev
+// CIR: }, exit : {
+// CIR-NOT: cir.call @_ZN4DtorD1Ev
+// CIR: cir.return
+// CIR-NEXT: }
+// CIR-NOT: cir.cleanup.scope
+// CIR-NOT: cir.call @_ZN4DtorD1Ev
+
+// OGCG-LABEL: define {{.*}} @_Z10param_dtor4Dtor(
+// OGCG: %[[P_COPY:.+]] = alloca %struct.Dtor
+// OGCG: coro.cleanup:
+// OGCG: call void @_ZN4DtorD1Ev(ptr {{[^,]*}} %[[P_COPY]])
+// OGCG: call ptr @llvm.coro.free(
+
+Task promise_and_params(Dtor a, int i, Dtor b) {
+ Dtor local;
+ co_await std::suspend_always{};
+}
+
+// The promise is destroyed first, then the parameter copies in reverse order
+// of construction. A local variable of the body is still destroyed when
+// control leaves the body, before the implicit co_return calls return_void().
+
+// CIR-LABEL: cir.func coroutine {{.*}} @_Z18promise_and_params4DtoriS_(
+// CIR-DAG: %[[A:.*]] = cir.alloca "a"
+// CIR-DAG: %[[B:.*]] = cir.alloca "b"
+// CIR-DAG: %[[PROMISE:.*]] = cir.alloca "__promise"
+// CIR-NOEH-DAG: %[[LOCAL:.*]] = cir.alloca "local"
+// CIR-NOT: cir.call {{@_ZN4DtorD1Ev|@_ZN4Task12promise_typeD1Ev}}
+// CIR: cir.coroutine initialSuspend : {
+// CIR-NOT: cir.call {{@_ZN4DtorD1Ev|@_ZN4Task12promise_typeD1Ev}}
+// CIR: }, body : {
+// CIR-EH: %[[LOCAL:.*]] = cir.alloca "local"
+// CIR: cir.await(user,
+// CIR: },)
+// CIR-NEXT: cir.yield
+// CIR-NOEH-NEXT: } cleanup normal {
+// CIR-EH-NEXT: } cleanup all {
+// CIR-NEXT: cir.call @_ZN4DtorD1Ev(%[[LOCAL]])
+// CIR-NEXT: cir.yield
+// CIR-NEXT: }
+// CIR-NEXT: cir.call @_ZN4Task12promise_type11return_voidEv(
+// CIR-NEXT: cir.co_return
+// CIR-NOT: cir.call {{@_ZN4DtorD1Ev|@_ZN4Task12promise_typeD1Ev}}
+// CIR: }, destroy : {
+// CIR-NEXT: cir.call @_ZN4Task12promise_typeD1Ev(%[[PROMISE]])
+// CIR-NEXT: cir.call @_ZN4DtorD1Ev(%[[B]])
+// CIR-NEXT: cir.call @_ZN4DtorD1Ev(%[[A]])
+// CIR-NEXT: cir.coro.intrinsic.free
+// CIR-NOT: cir.call {{@_ZN4DtorD1Ev|@_ZN4Task12promise_typeD1Ev}}
+// CIR: }, exit : {
+// CIR-NOT: cir.call {{@_ZN4DtorD1Ev|@_ZN4Task12promise_typeD1Ev}}
+
+// At -O1, the lifetime of the promise and of the parameter copies also ends in
+// destroy, after each object is destroyed.
+
+// CIR-O1-LABEL: cir.func coroutine {{.*}} @_Z18promise_and_params4DtoriS_(
+// CIR-O1-DAG: %[[A:.*]] = cir.alloca "a"
+// CIR-O1-DAG: %[[B:.*]] = cir.alloca "b"
+// CIR-O1-DAG: %[[PROMISE:.*]] = cir.alloca "__promise"
+// CIR-O1: cir.lifetime.start %[[A]]
+// CIR-O1: cir.lifetime.start %[[I:.*]] : !cir.ptr<!s32i>
+// CIR-O1: cir.lifetime.start %[[B]]
+// CIR-O1: cir.coroutine initialSuspend : {
+// CIR-O1-NEXT: cir.lifetime.start %[[PROMISE]]
+// CIR-O1-NOT: cir.lifetime.end %[[PROMISE]]
+// CIR-O1-NOT: cir.lifetime.end %[[B]]
+// CIR-O1-NOT: cir.lifetime.end %[[A]]
+// CIR-O1: }, destroy : {
+// CIR-O1-NEXT: cir.call @_ZN4Task12promise_typeD1Ev(%[[PROMISE]])
+// CIR-O1-NEXT: cir.lifetime.end %[[PROMISE]]
+// CIR-O1-NEXT: cir.call @_ZN4DtorD1Ev(%[[B]])
+// CIR-O1-NEXT: cir.lifetime.end %[[B]]
+// CIR-O1-NEXT: cir.lifetime.end %[[I]]
+// CIR-O1-NEXT: cir.call @_ZN4DtorD1Ev(%[[A]])
+// CIR-O1-NEXT: cir.lifetime.end %[[A]]
+// CIR-O1-NEXT: cir.coro.intrinsic.free
+
+// OGCG-LABEL: define {{.*}} @_Z18promise_and_params4DtoriS_(
+// OGCG: %[[A_COPY:.+]] = alloca %struct.Dtor
+// OGCG: %[[B_COPY:.+]] = alloca %struct.Dtor
+// OGCG: coro.cleanup:
+// OGCG-NEXT: call void @_ZN4Task12promise_typeD1Ev(ptr {{[^,]*}}
%__promise)
+// OGCG: call void @_ZN4DtorD1Ev(ptr {{[^,]*}} %[[B_COPY]])
+// OGCG: call void @_ZN4DtorD1Ev(ptr {{[^,]*}} %[[A_COPY]])
+// OGCG: call ptr @llvm.coro.free(
+
+// The promise constructor sees the parameter copies.
+struct PreviewTask {
+ struct promise_type {
+ promise_type(Dtor &);
+ ~promise_type();
+ PreviewTask get_return_object() noexcept;
+ std::suspend_always initial_suspend() noexcept;
+ std::suspend_always final_suspend() noexcept;
+ void return_void() noexcept;
+ void unhandled_exception() noexcept;
+ };
+};
+
+PreviewTask promise_preview(Dtor d) { co_return; }
+
+// CIR-LABEL: cir.func coroutine {{.*}} @_Z15promise_preview4Dtor(
+// CIR: %[[D:.*]] = cir.alloca "d"
+// CIR: %[[PROMISE:.*]] = cir.alloca "__promise"
+// CIR: cir.coroutine initialSuspend : {
+// CIR-NEXT: cir.call
@_ZN11PreviewTask12promise_typeC1ER4Dtor(%[[PROMISE]], %[[D]])
+// CIR: }, destroy : {
+// CIR-NEXT: cir.call @_ZN11PreviewTask12promise_typeD1Ev(%[[PROMISE]])
+// CIR-NEXT: cir.call @_ZN4DtorD1Ev(%[[D]])
+// CIR-NEXT: cir.coro.intrinsic.free
+
+// A temporary in a parameter copy's or the promise's initializer is still
+// destroyed at the end of its full-expression; it does not leak into destroy
+// and does not leave initial_suspend unterminated.
+struct Tmp {
+ Tmp();
+ ~Tmp();
+};
+
+struct TmpMove {
+ TmpMove(TmpMove &&, Tmp = Tmp());
+ ~TmpMove();
+};
+
+struct TmpTask {
+ struct promise_type {
+ promise_type(Tmp = Tmp());
+ ~promise_type();
+ TmpTask get_return_object() noexcept;
+ std::suspend_always initial_suspend() noexcept;
+ std::suspend_always final_suspend() noexcept;
+ void return_void() noexcept;
+ void unhandled_exception() noexcept;
+ };
+};
+
+TmpTask init_temporaries(TmpMove m) { co_return; }
+
+// CIR-LABEL: cir.func coroutine {{.*}} @_Z16init_temporaries7TmpMove(
+// CIR: %[[M:.*]] = cir.alloca "m"
+// CIR: %[[MTMP:.*]] = cir.alloca "agg.tmp0"
+// CIR: %[[PROMISE:.*]] = cir.alloca "__promise"
+// CIR: %[[PTMP:.*]] = cir.alloca "agg.tmp1"
+// CIR: cir.call @_ZN3TmpC1Ev(%[[MTMP]])
+// CIR-NEXT: cir.cleanup.scope {
+// CIR-NEXT: cir.call @_ZN7TmpMoveC1EOS_3Tmp(%[[M]], %arg0, %[[MTMP]])
+// CIR-NEXT: cir.yield
+// CIR-NOEH-NEXT: } cleanup normal {
+// CIR-EH-NEXT: } cleanup all {
+// CIR-NEXT: cir.call @_ZN3TmpD1Ev(%[[MTMP]])
+// CIR-NEXT: cir.yield
+// CIR-NEXT: }
+// CIR-NEXT: cir.coroutine initialSuspend : {
+// CIR-NEXT: cir.call @_ZN3TmpC1Ev(%[[PTMP]])
+// CIR-NEXT: cir.cleanup.scope {
+// CIR-NEXT: cir.call @_ZN7TmpTask12promise_typeC1E3Tmp(%[[PROMISE]],
%[[PTMP]])
+// CIR-NEXT: cir.yield
+// CIR-NOEH-NEXT: } cleanup normal {
+// CIR-EH-NEXT: } cleanup all {
+// CIR-NEXT: cir.call @_ZN3TmpD1Ev(%[[PTMP]])
+// CIR-NEXT: cir.yield
+// CIR-NEXT: }
+// CIR-NOT: cir.cleanup.scope
+// CIR: cir.await(init,
+// CIR: }, destroy : {
+// CIR-NEXT: cir.call @_ZN7TmpTask12promise_typeD1Ev(%[[PROMISE]])
+// CIR-NEXT: cir.call @_ZN7TmpMoveD1Ev(%[[M]])
+// CIR-NEXT: cir.coro.intrinsic.free
+// CIR-NOT: cir.call @_ZN3TmpD1Ev
+// CIR: }, exit : {
+
+// A temporary in the get_return_object() call is destroyed at the end of that
+// full-expression, inside initial_suspend ([class.temporary]p4). Classic
+// CodeGen does not end this full-expression: it leaves the temporary's cleanup
+// on the EH stack and destroys it in coro.cleanup, with the coroutine state.
+struct GROTask {
+ struct promise_type {
+ GROTask get_return_object(Tmp = Tmp()) noexcept;
+ std::suspend_always initial_suspend() noexcept;
+ std::suspend_always final_suspend() noexcept;
+ void return_void() noexcept;
+ void unhandled_exception() noexcept;
+ };
+};
+
+GROTask gro_temporary() { co_return; }
+
+// CIR-LABEL: cir.func coroutine {{.*}} @_Z13gro_temporaryv(
+// CIR: cir.coroutine initialSuspend : {
+// CIR: cir.cleanup.scope {
+// CIR-NEXT: cir.call
@_ZN7GROTask12promise_type17get_return_objectE3Tmp(
+// CIR-NOT: cir.await
+// CIR-NOEH: } cleanup normal {
+// CIR-EH: } cleanup all {
+// CIR-NEXT: cir.call @_ZN3TmpD1Ev
+// CIR-NEXT: cir.yield
+// CIR-NEXT: }
+// CIR-NEXT: cir.call @_ZN7GROTask12promise_type15initial_suspendEv(
+// CIR: cir.await(init,
+// CIR: }, destroy : {
+// CIR-NOT: cir.call @_ZN3TmpD1Ev
+// CIR: }, exit : {
+
+// OGCG-LABEL: define {{.*}} @_Z13gro_temporaryv(
+// OGCG: call void @_ZN7GROTask12promise_type17get_return_objectE3Tmp(
+// OGCG-NOT: call void @_ZN3TmpD1Ev
+// OGCG: coro.cleanup:
+// OGCG-NEXT: call void @_ZN3TmpD1Ev(
+
+// A temporary that such a default argument creates conditionally is destroyed
+// at the end of the full-expression too, guarded by its active flag. This also
+// holds for a parameter copy's constructor.
+bool cond();
+
+struct CondTmp {
+ CondTmp();
+ ~CondTmp();
+ bool ok();
+};
+
+struct CondGROTask {
+ struct promise_type {
+ CondGROTask get_return_object(bool = cond() && CondTmp().ok()) noexcept;
+ std::suspend_always initial_suspend() noexcept;
+ std::suspend_always final_suspend() noexcept;
+ void return_void() noexcept;
+ void unhandled_exception() noexcept;
+ };
+};
+
+CondGROTask cond_gro_temporary() { co_return; }
+
+// CIR-LABEL: cir.func coroutine {{.*}} @_Z18cond_gro_temporaryv(
+// CIR: %[[TMP:.*]] = cir.alloca "ref.tmp0" {{.*}} :
!cir.ptr<!rec_CondTmp>
+// CIR: %[[ACTIVE:.*]] = cir.alloca "cleanup.cond"
+// CIR: cir.coroutine initialSuspend : {
+// CIR: cir.cleanup.scope {
+// CIR: cir.call @_ZN7CondTmpC1Ev(%[[TMP]])
+// CIR: cir.call
@_ZN11CondGROTask12promise_type17get_return_objectEb(
+// CIR-NOEH: } cleanup normal {
+// CIR-EH: } cleanup all {
+// CIR-NEXT: %[[IS_ACTIVE:.*]] = cir.load {{.*}} %[[ACTIVE]]
----------------
mmha wrote:
Updated the PR. I added checks for the `cleanup.cond` store.
https://github.com/llvm/llvm-project/pull/230574
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits