[clang] [CIR] Don't create EH paths for lifetime-marker-only cleanups (PR #224829)
via cfe-commits
cfe-commits at lists.llvm.org
Sat Sep 19 02:42:20 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clangir
Author: Jiahao Guo (E00N777)
<details>
<summary>Changes</summary>
### summary
fix https://github.com/llvm/llvm-project/issues/224821
A lifetime marker is not a real cleanup, so it must never be the reason an unwind edge exists.
Assisted by : Claude Opus5
---
Full diff: https://github.com/llvm/llvm-project/pull/224829.diff
3 Files Affected:
- (modified) clang/lib/CIR/CodeGen/CIRGenCleanup.cpp (+3)
- (modified) clang/lib/CIR/CodeGen/CIRGenStmt.cpp (+22-12)
- (modified) clang/test/CIR/CodeGen/lifetime-marker.cpp (+24-25)
``````````diff
diff --git a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
index 8103ef37f9225c..53a0b78c6ec7c3 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
@@ -349,6 +349,9 @@ void *EHScopeStack::pushCleanup(CleanupKind kind, size_t size) {
bool isLifetimeMarker = kind & LifetimeMarker;
bool skipCleanupScope = false;
+ if (isLifetimeMarker)
+ isEHCleanup = false;
+
cir::CleanupKind cleanupKind = cir::CleanupKind::All;
if (isEHCleanup && cgf->getLangOpts().Exceptions) {
cleanupKind =
diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
index e9f5e466c63d24..044947b9ef1bca 100644
--- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
@@ -1026,10 +1026,10 @@ mlir::LogicalResult CIRGenFunction::emitForStmt(const ForStmt &s) {
// per-iteration cleanup region. This scope is constructed after the
// init-statement so the init-statement's cleanups are not captured.
const VarDecl *condVar = s.getConditionVariable();
+ bool condVarNeedsDtor =
+ condVar && condVar->needsDestruction(getContext()) != QualType::DK_none;
bool needsCondCleanup =
- condVar &&
- (condVar->needsDestruction(getContext()) != QualType::DK_none ||
- shouldEmitLifetimeMarkersForAutoVar());
+ condVar && (condVarNeedsDtor || shouldEmitLifetimeMarkersForAutoVar());
DeferredLoopConditionCleanup loopCondScope(*this, needsCondCleanup);
auto condBuilder = [&](mlir::OpBuilder &b, mlir::Location loc) {
@@ -1065,9 +1065,14 @@ mlir::LogicalResult CIRGenFunction::emitForStmt(const ForStmt &s) {
};
if (needsCondCleanup) {
- cir::CleanupKind cleanupKind = getLangOpts().Exceptions
- ? cir::CleanupKind::All
- : cir::CleanupKind::Normal;
+ // Without a destructor the cleanup region holds nothing but
+ // cir.lifetime.end. A lifetime marker must not be the reason an unwind
+ // edge exists, and an EH kind here would make every call in the
+ // condition, body and step unwind to it.
+ cir::CleanupKind cleanupKind =
+ getLangOpts().Exceptions && condVarNeedsDtor
+ ? cir::CleanupKind::All
+ : cir::CleanupKind::Normal;
forOp = builder.createFor(
getLoc(s.getSourceRange()), condBuilder, bodyBuilder, stepBuilder,
/*cleanupBuilder=*/
@@ -1158,10 +1163,10 @@ mlir::LogicalResult CIRGenFunction::emitWhileStmt(const WhileStmt &s) {
// destructor and lifetime-end cleanups and emit them into the loop's
// per-iteration cleanup region.
const VarDecl *condVar = s.getConditionVariable();
+ bool condVarNeedsDtor =
+ condVar && condVar->needsDestruction(getContext()) != QualType::DK_none;
bool needsCondCleanup =
- condVar &&
- (condVar->needsDestruction(getContext()) != QualType::DK_none ||
- shouldEmitLifetimeMarkersForAutoVar());
+ condVar && (condVarNeedsDtor || shouldEmitLifetimeMarkersForAutoVar());
DeferredLoopConditionCleanup loopCondScope(*this, needsCondCleanup);
auto condBuilder = [&](mlir::OpBuilder &b, mlir::Location loc) {
@@ -1185,9 +1190,14 @@ mlir::LogicalResult CIRGenFunction::emitWhileStmt(const WhileStmt &s) {
};
if (needsCondCleanup) {
- cir::CleanupKind cleanupKind = getLangOpts().Exceptions
- ? cir::CleanupKind::All
- : cir::CleanupKind::Normal;
+ // Without a destructor the cleanup region holds nothing but
+ // cir.lifetime.end. A lifetime marker must not be the reason an unwind
+ // edge exists, and an EH kind here would make every call in the
+ // condition and body unwind to it.
+ cir::CleanupKind cleanupKind =
+ getLangOpts().Exceptions && condVarNeedsDtor
+ ? cir::CleanupKind::All
+ : cir::CleanupKind::Normal;
whileOp = builder.createWhile(
getLoc(s.getSourceRange()), condBuilder, bodyBuilder,
/*cleanupBuilder=*/
diff --git a/clang/test/CIR/CodeGen/lifetime-marker.cpp b/clang/test/CIR/CodeGen/lifetime-marker.cpp
index dde66fac13d963..d1de8ef9185c93 100644
--- a/clang/test/CIR/CodeGen/lifetime-marker.cpp
+++ b/clang/test/CIR/CodeGen/lifetime-marker.cpp
@@ -134,10 +134,10 @@ void if_body(int n) {
// LLVM: [[IF_END]]:
// LLVM: call void @_Z3usei
-// With exceptions enabled the scope cleanup runs on both the normal and the
-// exceptional edge, so the cleanup kind is "all" and lifetime.end is emitted in
-// the EH cleanup handler (the landing pad) as well as on the normal path. The
-// may_throw() call is what forces an unwind edge.
+// A lifetime marker is not a real cleanup, so even with exceptions enabled it
+// must never be the reason an unwind path exists. The cleanup kind stays
+// "normal" and may_throw() is a plain call, mirroring classic CodeGen, where
+// EHScopeStack::requiresLandingPad skips lifetime-marker cleanups.
void may_throw();
void eh_cleanup() {
@@ -151,19 +151,16 @@ void eh_cleanup() {
// CIR-EH: cir.lifetime.start %[[X]] : !cir.ptr<!s32i>
// CIR-EH: cir.cleanup.scope {
// CIR-EH: cir.call @_Z9may_throwv()
-// CIR-EH: } cleanup all {
+// CIR-EH: } cleanup normal {
// CIR-EH: cir.lifetime.end %[[X]] : !cir.ptr<!s32i>
// CIR-EH: }
-// LLVM-EH-LABEL: define{{.*}} void @_Z10eh_cleanupv()
+// The '{' right after the attribute group pins the absence of a personality
+// clause: the function needs no exception handling at all.
+// LLVM-EH-LABEL: define{{.*}} void @_Z10eh_cleanupv() #{{[0-9]+}} {
// LLVM-EH: %[[X:.*]] = alloca i32
// LLVM-EH: call void @llvm.lifetime.start.p0(ptr %[[X]])
-// LLVM-EH: invoke void @_Z9may_throwv()
-// The normal-path end marker.
-// LLVM-EH: call void @llvm.lifetime.end.p0(ptr %[[X]])
-// The EH cleanup handler runs the same end marker on the unwind path.
-// LLVM-EH: landingpad { ptr, i32 }
-// LLVM-EH-NEXT: cleanup
+// LLVM-EH: call void @_Z9may_throwv()
// LLVM-EH: call void @llvm.lifetime.end.p0(ptr %[[X]])
// A loop condition variable is destroyed and re-created on every iteration
@@ -188,19 +185,21 @@ void while_condvar() {
// LLVM: call void @llvm.lifetime.start.p0(ptr %[[C:.*]])
// LLVM: call void @llvm.lifetime.end.p0(ptr %[[C]])
+// A loop's cleanup region spans the condition, the body and the step, so an EH
+// cleanup kind here would make every call in the loop unwind to it. A
+// marker-only region must therefore stay "normal".
// CIR-EH-LABEL: cir.func{{.*}} @_Z13while_condvarv
// CIR-EH: %[[C:.*]] = cir.alloca "c" {{.*}} : !cir.ptr<!s32i>
// CIR-EH: cir.while {
// CIR-EH: cir.lifetime.start %[[C]] : !cir.ptr<!s32i>
// CIR-EH: } do {
-// CIR-EH: } cleanup all {
+// CIR-EH: } cleanup normal {
// CIR-EH: cir.lifetime.end %[[C]] : !cir.ptr<!s32i>
-// LLVM-EH-LABEL: define{{.*}} void @_Z13while_condvarv
+// LLVM-EH-LABEL: define{{.*}} void @_Z13while_condvarv() #{{[0-9]+}} {
// LLVM-EH: call void @llvm.lifetime.start.p0(ptr %[[C:.*]])
-// LLVM-EH: call void @llvm.lifetime.end.p0(ptr %[[C]])
-// LLVM-EH: landingpad { ptr, i32 }
-// LLVM-EH-NEXT: cleanup
+// LLVM-EH: call noundef i32 @_Z6sourcev()
+// LLVM-EH: call void @_Z3usei
// LLVM-EH: call void @llvm.lifetime.end.p0(ptr %[[C]])
void for_condvar() {
@@ -227,14 +226,13 @@ void for_condvar() {
// CIR-EH: cir.lifetime.start %[[C]] : !cir.ptr<!s32i>
// CIR-EH: } body {
// CIR-EH: } step {
-// CIR-EH: } cleanup all {
+// CIR-EH: } cleanup normal {
// CIR-EH: cir.lifetime.end %[[C]] : !cir.ptr<!s32i>
-// LLVM-EH-LABEL: define{{.*}} void @_Z11for_condvarv
+// LLVM-EH-LABEL: define{{.*}} void @_Z11for_condvarv() #{{[0-9]+}} {
// LLVM-EH: call void @llvm.lifetime.start.p0(ptr %[[C:.*]])
-// LLVM-EH: call void @llvm.lifetime.end.p0(ptr %[[C]])
-// LLVM-EH: landingpad { ptr, i32 }
-// LLVM-EH-NEXT: cleanup
+// LLVM-EH: call noundef i32 @_Z6sourcev()
+// LLVM-EH: call void @_Z3usei
// LLVM-EH: call void @llvm.lifetime.end.p0(ptr %[[C]])
struct LoopCond {
@@ -243,7 +241,8 @@ struct LoopCond {
};
// A non-trivial condition variable runs its destructor before lifetime.end in
-// the loop cleanup region.
+// the loop cleanup region. That region is a real cleanup, so it keeps the "all"
+// kind and the marker rides along on the unwind path the destructor requires.
void while_record_condvar() {
while (LoopCond c{}) {}
}
@@ -300,7 +299,7 @@ void catch_by_ref() {
// CIR-EH: } cleanup all {
// CIR-EH: cir.end_catch %[[CATCH_TOK]]
// CIR-EH: }
-// CIR-EH: } cleanup all {
+// CIR-EH: } cleanup normal {
// CIR-EH-NEXT: cir.lifetime.end %[[E]] : !cir.ptr<!cir.ptr<!rec_Ex>>
// LLVM-EH-LABEL: define{{.*}} void @_Z12catch_by_refv()
@@ -330,7 +329,7 @@ void catch_by_value() {
// CIR-EH-NEXT: %[[CATCH_TOK:.*]], %{{.*}} = cir.begin_catch %[[TOK]]
// CIR-EH: cir.call @_ZN4CopyD1Ev(%[[C]])
// CIR-EH: cir.end_catch %[[CATCH_TOK]]
-// CIR-EH: } cleanup all {
+// CIR-EH: } cleanup normal {
// CIR-EH-NEXT: cir.lifetime.end %[[C]] : !cir.ptr<!rec_Copy>
// LLVM-EH-LABEL: define{{.*}} void @_Z14catch_by_valuev()
``````````
</details>
https://github.com/llvm/llvm-project/pull/224829
More information about the cfe-commits
mailing list