[clang] [CIR] Find conditional cleanups in implicit code (PR #229829)
Morris Hafner via cfe-commits
cfe-commits at lists.llvm.org
Thu Oct 8 16:59:58 PDT 2026
https://github.com/mmha updated https://github.com/llvm/llvm-project/pull/229829
>From 2444b72bf72977adf5cb76a85c91c12567f2870c Mon Sep 17 00:00:00 2001
From: Morris Hafner <mhafner at nvidia.com>
Date: Wed, 7 Oct 2026 02:18:28 +0000
Subject: [PATCH 1/2] [CIR] Find conditional cleanups in implicit code
`ConditionalEvaluationFinder` in `CIRGenCleanup` skipped implicit code
because that's the default for `RecursiveASTVisitor`. This lead to default
arguments and default member initializers being skipped.
This patch enables the traversal of implicit code with the exception of
the implicit call to `await_resume()` in `co_await` and `co_yield` expressions.
This requires cleanup scopes for await full-expressions which don't exist yet.
Co-Authored-By: Claude Opus 5.5 <noreply at anthropic.com>
---
clang/include/clang/CIR/MissingFeatures.h | 1 +
clang/lib/CIR/CodeGen/CIRGenCleanup.cpp | 29 ++-
.../test/CIR/CodeGen/cleanup-conditional.cpp | 183 ++++++++++++++++++
3 files changed, 211 insertions(+), 2 deletions(-)
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 68e15b84cea165..6f4d59a63cd53b 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -124,6 +124,7 @@ struct MissingFeatures {
static bool noUniqueAddressLayout() { return false; }
// Coroutines
+ static bool coroAwaitFullExprCleanups() { return false; }
static bool coroOutsideFrameMD() { return false; }
// Various handling of deferred processing in CIRGenModule.
diff --git a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
index a84531fe908d8f..0933c967237b0c 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
@@ -33,6 +33,10 @@ class ConditionalEvaluationFinder
bool foundConditional = false;
public:
+ // Default arguments and default member initializers can contain conditional
+ // temporaries whose cleanups belong to the enclosing full-expression.
+ bool shouldVisitImplicitCode() const { return true; }
+
bool found() const { return foundConditional; }
bool VisitAbstractConditionalOperator(AbstractConditionalOperator *) {
@@ -59,10 +63,31 @@ class ConditionalEvaluationFinder
return true;
}
- // Don't cross evaluation-context boundaries.
- bool TraverseLambdaExpr(LambdaExpr *) { return true; }
+ // Don't cross evaluation-context boundaries. Only the initializers of a
+ // lambda's captures are part of the enclosing full-expression.
+ bool TraverseLambdaExpr(LambdaExpr *e) {
+ for (Expr *init : e->capture_inits())
+ if (init && !TraverseStmt(init))
+ return false;
+ return true;
+ }
bool TraverseBlockExpr(BlockExpr *) { return true; }
bool TraverseStmtExpr(StmtExpr *) { return true; }
+
+ // Skip over the implicit call to await_resume(). This requires cleanup
+ // scopes for await full-expressions which don't exist yet.
+ bool TraverseCoawaitExpr(CoawaitExpr *e) {
+ assert(!cir::MissingFeatures::coroAwaitFullExprCleanups());
+ return TraverseStmt(e->getOperand());
+ }
+ bool TraverseDependentCoawaitExpr(DependentCoawaitExpr *e) {
+ assert(!cir::MissingFeatures::coroAwaitFullExprCleanups());
+ return TraverseStmt(e->getOperand());
+ }
+ bool TraverseCoyieldExpr(CoyieldExpr *e) {
+ assert(!cir::MissingFeatures::coroAwaitFullExprCleanups());
+ return TraverseStmt(e->getOperand());
+ }
};
} // namespace
diff --git a/clang/test/CIR/CodeGen/cleanup-conditional.cpp b/clang/test/CIR/CodeGen/cleanup-conditional.cpp
index 4dea02e4b4a245..9e208eaa791812 100644
--- a/clang/test/CIR/CodeGen/cleanup-conditional.cpp
+++ b/clang/test/CIR/CodeGen/cleanup-conditional.cpp
@@ -1168,3 +1168,186 @@ void test_short_circuit_cond_temp(bool always, bool c, int n) {
// OGCG: [[COND_FALSE]]:
// OGCG: call void @_ZN1QC1Ev(ptr {{.*}} %[[REF_TMP1]])
// OGCG: store i1 true, ptr %[[ACTIVE1]]
+
+// A temporary that is created conditionally in a default argument, a default
+// member initializer or a lambda's capture initializer is destroyed at the end
+// of the enclosing full-expression, guarded by its active flag.
+
+bool cond();
+
+void f(bool = cond() && S().get());
+
+void default_argument() { f(); }
+
+// CIR-LABEL: cir.func {{.*}} @_Z16default_argumentv(
+// CIR: %[[TMP:.*]] = cir.alloca "ref.tmp0" {{.*}} : !cir.ptr<!rec_S>
+// CIR: %[[ACTIVE:.*]] = cir.alloca "cleanup.cond"
+// CIR: cir.cleanup.scope {
+// CIR: cir.call @_ZN1SC1Ev(%[[TMP]])
+// CIR: cir.call @_Z1fb(
+// CIR: } cleanup normal {
+// CIR-NEXT: %[[IS_ACTIVE:.*]] = cir.load {{.*}} %[[ACTIVE]]
+// CIR-NEXT: cir.if %[[IS_ACTIVE]] {
+// CIR-NEXT: cir.call @_ZN1SD1Ev(%[[TMP]])
+
+// LLVMCIR-LABEL: define {{.*}} void @_Z16default_argumentv(
+// LLVMCIR: %[[TMP:.*]] = alloca %struct.S
+// LLVMCIR: %[[ACTIVE:.*]] = alloca i8
+// LLVMCIR: call void @_ZN1SC1Ev(ptr {{.*}} %[[TMP]])
+// LLVMCIR-NEXT: store i8 1, ptr %[[ACTIVE]]
+// LLVMCIR: call void @_Z1fb(
+// LLVMCIR: %[[A:.*]] = load i8, ptr %[[ACTIVE]]
+// LLVMCIR-NEXT: %[[B:.*]] = trunc i8 %[[A]] to i1
+// LLVMCIR-NEXT: br i1 %[[B]], label %[[DTOR:.*]], label
+// LLVMCIR: [[DTOR]]:
+// LLVMCIR-NEXT: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]])
+
+// OGCG-LABEL: define {{.*}} void @_Z16default_argumentv(
+// OGCG: call void @_ZN1SC1Ev(ptr {{.*}} %[[TMP:.*]])
+// OGCG: call void @_Z1fb(
+// OGCG: br i1 %cleanup.is_active, label %cleanup.action
+// OGCG: cleanup.action:
+// OGCG-NEXT: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]])
+
+struct D {
+ D(bool = cond() && S().get());
+};
+
+void default_ctor_argument() { D d; }
+
+// CIR-LABEL: cir.func {{.*}} @_Z21default_ctor_argumentv(
+// CIR: %[[TMP:.*]] = cir.alloca "ref.tmp0" {{.*}} : !cir.ptr<!rec_S>
+// CIR: %[[ACTIVE:.*]] = cir.alloca "cleanup.cond"
+// CIR: cir.cleanup.scope {
+// CIR: cir.call @_ZN1SC1Ev(%[[TMP]])
+// CIR: cir.call @_ZN1DC1Eb(
+// CIR: } cleanup normal {
+// CIR-NEXT: %[[IS_ACTIVE:.*]] = cir.load {{.*}} %[[ACTIVE]]
+// CIR-NEXT: cir.if %[[IS_ACTIVE]] {
+// CIR-NEXT: cir.call @_ZN1SD1Ev(%[[TMP]])
+
+// LLVMCIR-LABEL: define {{.*}} void @_Z21default_ctor_argumentv(
+// LLVMCIR: %[[TMP:.*]] = alloca %struct.S
+// LLVMCIR: %[[ACTIVE:.*]] = alloca i8
+// LLVMCIR: call void @_ZN1SC1Ev(ptr {{.*}} %[[TMP]])
+// LLVMCIR-NEXT: store i8 1, ptr %[[ACTIVE]]
+// LLVMCIR: call void @_ZN1DC1Eb(
+// LLVMCIR: %[[A:.*]] = load i8, ptr %[[ACTIVE]]
+// LLVMCIR-NEXT: %[[B:.*]] = trunc i8 %[[A]] to i1
+// LLVMCIR-NEXT: br i1 %[[B]], label %[[DTOR:.*]], label
+// LLVMCIR: [[DTOR]]:
+// LLVMCIR-NEXT: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]])
+
+// OGCG-LABEL: define {{.*}} void @_Z21default_ctor_argumentv(
+// OGCG: call void @_ZN1SC1Ev(ptr {{.*}} %[[TMP:.*]])
+// OGCG: call void @_ZN1DC1Eb(
+// OGCG: br i1 %cleanup.is_active, label %cleanup.action
+// OGCG: cleanup.action:
+// OGCG-NEXT: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]])
+
+struct DefaultInit {
+ int i;
+ bool b = cond() && S().get();
+};
+
+void use(DefaultInit);
+
+void aggregate_default_member_init() {
+ DefaultInit s{1};
+ use(s);
+}
+
+// CIR-LABEL: cir.func {{.*}} @_Z29aggregate_default_member_initv(
+// CIR: %[[S:.*]] = cir.alloca "s"
+// CIR: %[[TMP:.*]] = cir.alloca "ref.tmp0" {{.*}} : !cir.ptr<!rec_S>
+// CIR: %[[ACTIVE:.*]] = cir.alloca "cleanup.cond"
+// CIR: %[[B:.*]] = cir.get_member %[[S]][1] {name = "b"}
+// CIR: cir.cleanup.scope {
+// CIR: cir.call @_ZN1SC1Ev(%[[TMP]])
+// CIR: cir.store {{.*}}, %[[B]]
+// CIR: } cleanup normal {
+// CIR-NEXT: %[[IS_ACTIVE:.*]] = cir.load {{.*}} %[[ACTIVE]]
+// CIR-NEXT: cir.if %[[IS_ACTIVE]] {
+// CIR-NEXT: cir.call @_ZN1SD1Ev(%[[TMP]])
+// CIR: cir.call @_Z3use11DefaultInit(
+
+// LLVMCIR-LABEL: define {{.*}} void @_Z29aggregate_default_member_initv(
+// LLVMCIR: %[[TMP:.*]] = alloca %struct.S
+// LLVMCIR: %[[ACTIVE:.*]] = alloca i8
+// LLVMCIR: call void @_ZN1SC1Ev(ptr {{.*}} %[[TMP]])
+// LLVMCIR-NEXT: store i8 1, ptr %[[ACTIVE]]
+// LLVMCIR: %[[A:.*]] = load i8, ptr %[[ACTIVE]]
+// LLVMCIR-NEXT: %[[B:.*]] = trunc i8 %[[A]] to i1
+// LLVMCIR-NEXT: br i1 %[[B]], label %[[DTOR:.*]], label
+// LLVMCIR: [[DTOR]]:
+// LLVMCIR-NEXT: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]])
+// LLVMCIR: call void @_Z3use11DefaultInit(
+
+// OGCG-LABEL: define {{.*}} void @_Z29aggregate_default_member_initv(
+// OGCG: call void @_ZN1SC1Ev(ptr {{.*}} %[[TMP:.*]])
+// OGCG: br i1 %cleanup.is_active, label %cleanup.action
+// OGCG: cleanup.action:
+// OGCG-NEXT: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]])
+// OGCG: call void @_Z3use11DefaultInit(
+
+void empty_aggregate_default_member_init() {
+ DefaultInit s{};
+ use(s);
+}
+
+// CIR-LABEL: cir.func {{.*}} @_Z35empty_aggregate_default_member_initv(
+// CIR: %[[TMP:.*]] = cir.alloca "ref.tmp0" {{.*}} : !cir.ptr<!rec_S>
+// CIR: %[[ACTIVE:.*]] = cir.alloca "cleanup.cond"
+// CIR: cir.cleanup.scope {
+// CIR: cir.call @_ZN1SC1Ev(%[[TMP]])
+// CIR: } cleanup normal {
+// CIR-NEXT: %[[IS_ACTIVE:.*]] = cir.load {{.*}} %[[ACTIVE]]
+// CIR-NEXT: cir.if %[[IS_ACTIVE]] {
+// CIR-NEXT: cir.call @_ZN1SD1Ev(%[[TMP]])
+// CIR: cir.call @_Z3use11DefaultInit(
+
+// LLVMCIR-LABEL: define {{.*}} void @_Z35empty_aggregate_default_member_initv(
+// LLVMCIR: call void @_ZN1SC1Ev(ptr {{.*}} %[[TMP:.*]])
+// LLVMCIR: br i1 %{{.*}}, label %[[DTOR:.*]], label
+// LLVMCIR: [[DTOR]]:
+// LLVMCIR-NEXT: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]])
+// LLVMCIR: call void @_Z3use11DefaultInit(
+
+// OGCG-LABEL: define {{.*}} void @_Z35empty_aggregate_default_member_initv(
+// OGCG: call void @_ZN1SC1Ev(ptr {{.*}} %[[TMP:.*]])
+// OGCG: br i1 %cleanup.is_active, label %cleanup.action
+// OGCG: cleanup.action:
+// OGCG-NEXT: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]])
+// OGCG: call void @_Z3use11DefaultInit(
+
+void use(bool);
+
+void lambda_init_capture() {
+ auto l = [b = cond() && S().get()] { use(b); };
+ l();
+}
+
+// CIR-LABEL: cir.func {{.*}} @_Z19lambda_init_capturev(
+// CIR: %[[TMP:.*]] = cir.alloca "ref.tmp0" {{.*}} : !cir.ptr<!rec_S>
+// CIR: %[[ACTIVE:.*]] = cir.alloca "cleanup.cond"
+// CIR: cir.cleanup.scope {
+// CIR: cir.call @_ZN1SC1Ev(%[[TMP]])
+// CIR: } cleanup normal {
+// CIR-NEXT: %[[IS_ACTIVE:.*]] = cir.load {{.*}} %[[ACTIVE]]
+// CIR-NEXT: cir.if %[[IS_ACTIVE]] {
+// CIR-NEXT: cir.call @_ZN1SD1Ev(%[[TMP]])
+// CIR: cir.call @_ZZ19lambda_init_capturevENK3$_0clEv(
+
+// LLVMCIR-LABEL: define {{.*}} void @_Z19lambda_init_capturev(
+// LLVMCIR: call void @_ZN1SC1Ev(ptr {{.*}} %[[TMP:.*]])
+// LLVMCIR: br i1 %{{.*}}, label %[[DTOR:.*]], label
+// LLVMCIR: [[DTOR]]:
+// LLVMCIR-NEXT: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]])
+// LLVMCIR: call void @"_ZZ19lambda_init_capturevENK3$_0clEv"(
+
+// OGCG-LABEL: define {{.*}} void @_Z19lambda_init_capturev(
+// OGCG: call void @_ZN1SC1Ev(ptr {{.*}} %[[TMP:.*]])
+// OGCG: br i1 %cleanup.is_active, label %cleanup.action
+// OGCG: cleanup.action:
+// OGCG-NEXT: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]])
+// OGCG: call void @"_ZZ19lambda_init_capturevENK3$_0clEv"(
>From 9d009a6609326488d663c659c7f5423cfffd2553 Mon Sep 17 00:00:00 2001
From: Morris Hafner <mhafner at nvidia.com>
Date: Thu, 8 Oct 2026 23:59:45 +0000
Subject: [PATCH 2/2] Remove SSA values
---
.../test/CIR/CodeGen/cleanup-conditional.cpp | 31 +++++++++++--------
1 file changed, 18 insertions(+), 13 deletions(-)
diff --git a/clang/test/CIR/CodeGen/cleanup-conditional.cpp b/clang/test/CIR/CodeGen/cleanup-conditional.cpp
index 9e208eaa791812..67af679262f18a 100644
--- a/clang/test/CIR/CodeGen/cleanup-conditional.cpp
+++ b/clang/test/CIR/CodeGen/cleanup-conditional.cpp
@@ -80,7 +80,7 @@ void test_ternary_temporary(bool c, int x) {
// LLVMCIR: store i32 %[[RESULT]], ptr %{{.*}}
// OGCG-LABEL: define dso_local void @_Z22test_ternary_temporarybi(
-// OGCG: entry:
+// OGCG: [[ENTRY:.*]]:
// OGCG: store i1 false, ptr %[[ACTIVE:.*]]
// OGCG: br i1 %[[COND_BOOL:.*]], label %[[TRUE_BR:.*]], label %[[FALSE_BR:.*]]
// OGCG: [[TRUE_BR]]:
@@ -203,7 +203,7 @@ void test_ternary_both_branches(bool c) {
// LLVMCIR: br label %{{.*}}
// OGCG-LABEL: define dso_local void @_Z26test_ternary_both_branchesb(
-// OGCG: entry:
+// OGCG: [[ENTRY:.*]]:
// OGCG: store i1 false, ptr %[[ACTA:.*]]
// OGCG: store i1 false, ptr %[[ACTB:.*]]
// OGCG: br i1 %[[COND_BOOL:.*]], label %[[TRUE_BR:.*]], label %[[FALSE_BR:.*]]
@@ -327,7 +327,7 @@ int test_return_ternary(bool c) {
// LLVMCIR: ret i32 %[[RET]]
// OGCG-LABEL: define dso_local noundef i32 @_Z19test_return_ternaryb(
-// OGCG: entry:
+// OGCG: [[ENTRY:.*]]:
// OGCG: store i1 false, ptr %[[ACTA:.*]]
// OGCG: store i1 false, ptr %[[ACTB:.*]]
// OGCG: br i1 %[[COND_BOOL:.*]], label %[[TRUE_BR:.*]], label %[[FALSE_BR:.*]]
@@ -1205,8 +1205,9 @@ void default_argument() { f(); }
// OGCG-LABEL: define {{.*}} void @_Z16default_argumentv(
// OGCG: call void @_ZN1SC1Ev(ptr {{.*}} %[[TMP:.*]])
// OGCG: call void @_Z1fb(
-// OGCG: br i1 %cleanup.is_active, label %cleanup.action
-// OGCG: cleanup.action:
+// OGCG: %[[IS_ACTIVE:.*]] = load i1, ptr %{{.*}}
+// OGCG-NEXT: br i1 %[[IS_ACTIVE]], label %[[DTOR:.*]], label
+// OGCG: [[DTOR]]:
// OGCG-NEXT: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]])
struct D {
@@ -1241,8 +1242,9 @@ void default_ctor_argument() { D d; }
// OGCG-LABEL: define {{.*}} void @_Z21default_ctor_argumentv(
// OGCG: call void @_ZN1SC1Ev(ptr {{.*}} %[[TMP:.*]])
// OGCG: call void @_ZN1DC1Eb(
-// OGCG: br i1 %cleanup.is_active, label %cleanup.action
-// OGCG: cleanup.action:
+// OGCG: %[[IS_ACTIVE:.*]] = load i1, ptr %{{.*}}
+// OGCG-NEXT: br i1 %[[IS_ACTIVE]], label %[[DTOR:.*]], label
+// OGCG: [[DTOR]]:
// OGCG-NEXT: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]])
struct DefaultInit {
@@ -1285,8 +1287,9 @@ void aggregate_default_member_init() {
// OGCG-LABEL: define {{.*}} void @_Z29aggregate_default_member_initv(
// OGCG: call void @_ZN1SC1Ev(ptr {{.*}} %[[TMP:.*]])
-// OGCG: br i1 %cleanup.is_active, label %cleanup.action
-// OGCG: cleanup.action:
+// OGCG: %[[IS_ACTIVE:.*]] = load i1, ptr %{{.*}}
+// OGCG-NEXT: br i1 %[[IS_ACTIVE]], label %[[DTOR:.*]], label
+// OGCG: [[DTOR]]:
// OGCG-NEXT: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]])
// OGCG: call void @_Z3use11DefaultInit(
@@ -1315,8 +1318,9 @@ void empty_aggregate_default_member_init() {
// OGCG-LABEL: define {{.*}} void @_Z35empty_aggregate_default_member_initv(
// OGCG: call void @_ZN1SC1Ev(ptr {{.*}} %[[TMP:.*]])
-// OGCG: br i1 %cleanup.is_active, label %cleanup.action
-// OGCG: cleanup.action:
+// OGCG: %[[IS_ACTIVE:.*]] = load i1, ptr %{{.*}}
+// OGCG-NEXT: br i1 %[[IS_ACTIVE]], label %[[DTOR:.*]], label
+// OGCG: [[DTOR]]:
// OGCG-NEXT: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]])
// OGCG: call void @_Z3use11DefaultInit(
@@ -1347,7 +1351,8 @@ void lambda_init_capture() {
// OGCG-LABEL: define {{.*}} void @_Z19lambda_init_capturev(
// OGCG: call void @_ZN1SC1Ev(ptr {{.*}} %[[TMP:.*]])
-// OGCG: br i1 %cleanup.is_active, label %cleanup.action
-// OGCG: cleanup.action:
+// OGCG: %[[IS_ACTIVE:.*]] = load i1, ptr %{{.*}}
+// OGCG-NEXT: br i1 %[[IS_ACTIVE]], label %[[DTOR:.*]], label
+// OGCG: [[DTOR]]:
// OGCG-NEXT: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]])
// OGCG: call void @"_ZZ19lambda_init_capturevENK3$_0clEv"(
More information about the cfe-commits
mailing list