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 <[email protected]> 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 <[email protected]> --- 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 <[email protected]> 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"( _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
