Author: Morris Hafner
Date: 2026-10-09T13:46:26+02:00
New Revision: 4362ba8c236098f1e3b8fa7f7beec1969d06208b

URL: 
https://github.com/llvm/llvm-project/commit/4362ba8c236098f1e3b8fa7f7beec1969d06208b
DIFF: 
https://github.com/llvm/llvm-project/commit/4362ba8c236098f1e3b8fa7f7beec1969d06208b.diff

LOG: [CIR] Find conditional cleanups in implicit code (#229829)

`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 <[email protected]>

Added: 
    

Modified: 
    clang/include/clang/CIR/MissingFeatures.h
    clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
    clang/test/CIR/CodeGen/cleanup-conditional.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/CIR/MissingFeatures.h 
b/clang/include/clang/CIR/MissingFeatures.h
index 5adabec92a6f8..c908633c4ed6c 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 2173b27a20d78..7a703b84bb04f 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 4dea02e4b4a24..67af679262f18 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:.*]]
@@ -1168,3 +1168,191 @@ 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:         %[[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 {
+  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:         %[[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 {
+  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:         %[[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(
+
+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:         %[[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(
+
+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:         %[[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"(


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to