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
