https://github.com/E00N777 updated 
https://github.com/llvm/llvm-project/pull/224829

>From 51889b162709f3e1baacd575b33bb5cee68ce539 Mon Sep 17 00:00:00 2001
From: E00N777 <[email protected]>
Date: Sat, 19 Sep 2026 17:31:12 +0800
Subject: [PATCH] [CIR] Don't create EH paths for lifetime-marker-only cleanups

---
 .../lib/CIR/Dialect/Transforms/FlattenCFG.cpp |  59 +++++-
 clang/test/CIR/CodeGen/lifetime-marker.cpp    | 183 ++++++++++++++++--
 .../flatten-cleanup-scope-musttail.cir        |  14 +-
 3 files changed, 222 insertions(+), 34 deletions(-)

diff --git a/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp 
b/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
index 8506985f4203fe..4005124a054ac2 100644
--- a/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
@@ -844,6 +844,51 @@ static cir::AllocaOp 
getOrCreateCleanupDestSlot(cir::FuncOp funcOp,
 /// Shared EH flattening utilities used by both CIRCleanupScopeOpFlattening
 /// and CIRTryOpFlattening.
 
+// Lifetime markers participate in existing EH paths, but do not require an
+// unwind edge on their own. Conservatively treat other cleanup code as real
+// cleanup, including regions with control flow.
+static bool isLifetimeMarkerOnly(mlir::Region &region) {
+  return llvm::hasSingleElement(region) &&
+         llvm::any_of(
+             region.front(),
+             [](mlir::Operation &op) { return isa<cir::LifetimeEndOp>(op); }) 
&&
+         llvm::all_of(region.front(), [](mlir::Operation &op) {
+           return isa<cir::LifetimeEndOp, cir::YieldOp>(op);
+         });
+}
+
+// Look for an enclosing handler or non-marker EH cleanup that protects this
+// operation. Follow regions, rather than just parent ops: a try does not catch
+// exceptions from its own handlers, and a cleanup does not protect itself.
+static bool hasEnclosingEHRequirement(mlir::Operation *op) {
+  for (mlir::Region *region = op->getParentRegion(); region;
+       region = region->getParentRegion()) {
+    mlir::Operation *parent = region->getParentOp();
+    if (!parent || isa<cir::FuncOp>(parent))
+      break;
+    if (auto tryOp = dyn_cast<cir::TryOp>(parent)) {
+      auto handlers = tryOp.getHandlerTypesAttr();
+      if (region == &tryOp.getTryRegion() && handlers &&
+          llvm::any_of(handlers, [](mlir::Attribute handler) {
+            return !isa<cir::UnwindAttr>(handler);
+          }))
+        return true;
+    } else if (auto cleanupOp = dyn_cast<cir::CleanupScopeOp>(parent)) {
+      if (region == &cleanupOp.getBodyRegion() &&
+          cleanupOp.getCleanupKindAttr().isEH() &&
+          !isLifetimeMarkerOnly(cleanupOp.getCleanupRegion()))
+        return true;
+    } else if (auto loopOp = dyn_cast<cir::LoopOpInterface>(parent)) {
+      // The enclosing loop may not yet have been rewritten to a cleanup scope.
+      mlir::Region *cleanup = loopOp.maybeGetCleanup();
+      if (cleanup && region != cleanup && loopOp.maybeGetCleanupKind().isEH() 
&&
+          !isLifetimeMarkerOnly(*cleanup))
+        return true;
+    }
+  }
+  return false;
+}
+
 // Collect all function calls in a region that may throw exceptions and need
 // to be replaced with try_call operations. Skips calls marked nothrow.
 // Nested cleanup scopes and try ops are always flattened before their
@@ -1678,7 +1723,7 @@ class CIRCleanupScopeOpFlattening
     if (hasNestedOpsToFlatten(cleanupOp.getBodyRegion()))
       return mlir::failure();
 
-    cir::CleanupKind cleanupKind = cleanupOp.getCleanupKind();
+    bool hasEHCleanup = cleanupOp.getCleanupKindAttr().isEH();
 
     // Collect all exits from the body region.
     llvm::SmallVector<CleanupExit> exits;
@@ -1696,19 +1741,21 @@ class CIRCleanupScopeOpFlattening
 #endif
 
     // Collect non-nothrow calls and throws that need to be converted to
-    // try_call/try_throw. This is only needed for EH and All cleanup kinds,
-    // but the vectors will simply be empty for Normal cleanup.
+    // try_call/try_throw. A marker-only cleanup must not introduce an unwind
+    // edge unless an enclosing handler or real EH cleanup requires one.
     llvm::SmallVector<cir::CallOp> callsToRewrite;
     llvm::SmallVector<cir::ThrowOp> throwsToRewrite;
-    if (cleanupKind != cir::CleanupKind::Normal) {
+    if (hasEHCleanup && (!isLifetimeMarkerOnly(cleanupOp.getCleanupRegion()) ||
+                         hasEnclosingEHRequirement(cleanupOp))) {
       collectThrowingCalls(cleanupOp.getBodyRegion(), callsToRewrite);
       collectThrows(cleanupOp.getBodyRegion(), throwsToRewrite);
     }
 
     // Collect resume ops from already-flattened inner cleanup scopes that
-    // need to chain through this cleanup's EH handler.
+    // need to chain through this cleanup's EH handler, including lifetime
+    // markers even when they did not introduce any unwind edges themselves.
     llvm::SmallVector<cir::ResumeOp> resumeOpsToChain;
-    if (cleanupKind != cir::CleanupKind::Normal)
+    if (hasEHCleanup)
       collectResumeOps(cleanupOp.getBodyRegion(), resumeOpsToChain);
 
     return flattenCleanup(cleanupOp, exits, callsToRewrite, throwsToRewrite,
diff --git a/clang/test/CIR/CodeGen/lifetime-marker.cpp 
b/clang/test/CIR/CodeGen/lifetime-marker.cpp
index dde66fac13d963..12ac98b3d53437 100644
--- a/clang/test/CIR/CodeGen/lifetime-marker.cpp
+++ b/clang/test/CIR/CodeGen/lifetime-marker.cpp
@@ -134,10 +134,8 @@ 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.
+// Lifetime cleanups retain the "all" kind, but must not introduce an unwind
+// edge on their own. Only the normal-path marker is needed here.
 void may_throw();
 
 void eh_cleanup() {
@@ -156,14 +154,11 @@ void eh_cleanup() {
 // CIR-EH:         }
 
 // LLVM-EH-LABEL: define{{.*}} void @_Z10eh_cleanupv()
+// LLVM-EH-NOT:     personality
 // 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 @_Z3usei
 // LLVM-EH:         call void @llvm.lifetime.end.p0(ptr %[[X]])
 
 // A loop condition variable is destroyed and re-created on every iteration
@@ -196,11 +191,11 @@ void while_condvar() {
 // CIR-EH:         } cleanup all {
 // CIR-EH:           cir.lifetime.end %[[C]] : !cir.ptr<!s32i>
 
-// LLVM-EH-LABEL: define{{.*}} void @_Z13while_condvarv
+// LLVM-EH-LABEL: define{{.*}} void @_Z13while_condvarv()
+// LLVM-EH-NOT:     personality
 // 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{{.*}} i32 @_Z6sourcev()
+// LLVM-EH:         call void @_Z3usei
 // LLVM-EH:         call void @llvm.lifetime.end.p0(ptr %[[C]])
 
 void for_condvar() {
@@ -230,11 +225,11 @@ void for_condvar() {
 // CIR-EH:         } cleanup all {
 // CIR-EH:           cir.lifetime.end %[[C]] : !cir.ptr<!s32i>
 
-// LLVM-EH-LABEL: define{{.*}} void @_Z11for_condvarv
+// LLVM-EH-LABEL: define{{.*}} void @_Z11for_condvarv()
+// LLVM-EH-NOT:     personality
 // 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{{.*}} i32 @_Z6sourcev()
+// LLVM-EH:         call void @_Z3usei
 // LLVM-EH:         call void @llvm.lifetime.end.p0(ptr %[[C]])
 
 struct LoopCond {
@@ -341,4 +336,156 @@ void catch_by_value() {
 // LLVM-EH:         call void @__cxa_end_catch()
 // LLVM-EH:         call void @llvm.lifetime.end.p0(ptr %[[C]])
 
+// An enclosing catch requires an unwind edge and the marker must run before
+// entering the handler.
+void lifetime_in_try() {
+  try {
+    int x = 1;
+    may_throw();
+    use(x);
+  } catch (...) {
+  }
+}
+
+// CIR-EH-LABEL: cir.func{{.*}} @_Z15lifetime_in_tryv()
+// CIR-EH:         %[[X:.*]] = cir.alloca "x"
+// CIR-EH:         cir.try {
+// CIR-EH:           cir.lifetime.start %[[X]]
+// CIR-EH:           cir.cleanup.scope {
+// CIR-EH:             cir.call @_Z9may_throwv()
+// CIR-EH:             cir.call @_Z3usei
+// CIR-EH:           } cleanup all {
+// CIR-EH:             cir.lifetime.end %[[X]]
+// CIR-EH:         } catch all
+
+// LLVM-EH-LABEL: define{{.*}} void @_Z15lifetime_in_tryv()
+// LLVM-EH:         call void @llvm.lifetime.start.p0(ptr %[[X:.*]])
+// LLVM-EH:         invoke void @_Z9may_throwv()
+// LLVM-EH:         invoke void @_Z3usei
+// LLVM-EH:         call void @llvm.lifetime.end.p0(ptr %[[X]])
+// LLVM-EH:         landingpad { ptr, i32 }
+// LLVM-EH:         call void @llvm.lifetime.end.p0(ptr %[[X]])
+// LLVM-EH:         call ptr @__cxa_begin_catch
+
+// A real outer cleanup requires the inner lifetime marker on the EH path.
+void lifetime_in_dtor_scope() {
+  S s;
+  int x = 1;
+  use(x);
+}
+
+// CIR-EH-LABEL: cir.func{{.*}} @_Z22lifetime_in_dtor_scopev()
+// CIR-EH:         %[[S:.*]] = cir.alloca "s"
+// CIR-EH:         %[[X:.*]] = cir.alloca "x"
+// CIR-EH:         cir.lifetime.start %[[S]]
+// CIR-EH:         cir.lifetime.start %[[X]]
+// CIR-EH:         cir.call @_Z3usei
+// CIR-EH:         } cleanup all {
+// CIR-EH:           cir.lifetime.end %[[X]]
+// CIR-EH:         } cleanup all {
+// CIR-EH:           cir.call @_ZN1SD1Ev(%[[S]])
+// CIR-EH:         } cleanup all {
+// CIR-EH:           cir.lifetime.end %[[S]]
+
+// LLVM-EH-LABEL: define{{.*}} void @_Z22lifetime_in_dtor_scopev()
+// LLVM-EH:         call void @llvm.lifetime.start.p0(ptr %[[S:.*]])
+// LLVM-EH:         call void @llvm.lifetime.start.p0(ptr %[[X:.*]])
+// LLVM-EH:         invoke void @_Z3usei
+// LLVM-EH:         landingpad { ptr, i32 }
+// LLVM-EH:         call void @llvm.lifetime.end.p0(ptr %[[X]])
+// LLVM-EH:         call void @_ZN1SD1Ev(ptr {{.*}} %[[S]])
+// LLVM-EH:         call void @llvm.lifetime.end.p0(ptr %[[S]])
+
+// The first call needs no unwind edge. The inner destructor's existing EH
+// path must still pass through both outer lifetime markers.
+void lifetime_around_dtor_scope() {
+  int x = 1;
+  use(x);
+  S s;
+  use(x);
+}
+
+// CIR-EH-LABEL: cir.func{{.*}} @_Z26lifetime_around_dtor_scopev()
+// CIR-EH:         %[[X:.*]] = cir.alloca "x"
+// CIR-EH:         %[[S:.*]] = cir.alloca "s"
+// CIR-EH:         cir.lifetime.start %[[X]]
+// CIR-EH:         cir.call @_Z3usei
+// CIR-EH:         cir.lifetime.start %[[S]]
+// CIR-EH:         cir.call @_Z3usei
+// CIR-EH:         } cleanup all {
+// CIR-EH:           cir.call @_ZN1SD1Ev(%[[S]])
+// CIR-EH:         } cleanup all {
+// CIR-EH:           cir.lifetime.end %[[S]]
+// CIR-EH:         } cleanup all {
+// CIR-EH:           cir.lifetime.end %[[X]]
+
+// LLVM-EH-LABEL: define{{.*}} void @_Z26lifetime_around_dtor_scopev()
+// LLVM-EH:         call void @llvm.lifetime.start.p0(ptr %[[X:.*]])
+// LLVM-EH:         call void @_Z3usei
+// LLVM-EH:         call void @llvm.lifetime.start.p0(ptr %[[S:.*]])
+// LLVM-EH:         invoke void @_Z3usei
+// LLVM-EH:         landingpad { ptr, i32 }
+// LLVM-EH:         call void @_ZN1SD1Ev(ptr {{.*}} %[[S]])
+// LLVM-EH:         call void @llvm.lifetime.end.p0(ptr %[[S]])
+// LLVM-EH:         call void @llvm.lifetime.end.p0(ptr %[[X]])
+
+// The outer loop's destructor is still a loop cleanup region when the inner
+// lifetime scope is flattened.
+void lifetime_in_loop() {
+  while (LoopCond c{}) {
+    int x = 1;
+    use(x);
+  }
+}
+
+// CIR-EH-LABEL: cir.func{{.*}} @_Z16lifetime_in_loopv()
+// CIR-EH:         %[[C:.*]] = cir.alloca "c"
+// CIR-EH:         cir.while {
+// CIR-EH:         } do {
+// CIR-EH:           %[[X:.*]] = cir.alloca "x"
+// CIR-EH:           cir.lifetime.start %[[X]]
+// CIR-EH:           cir.call @_Z3usei
+// CIR-EH:           } cleanup all {
+// CIR-EH:             cir.lifetime.end %[[X]]
+// CIR-EH:         } cleanup all {
+// CIR-EH:           cir.call @_ZN8LoopCondD1Ev(%[[C]])
+// CIR-EH:           cir.lifetime.end %[[C]]
+
+// LLVM-EH-LABEL: define{{.*}} void @_Z16lifetime_in_loopv()
+// LLVM-EH:         call void @llvm.lifetime.start.p0(ptr %[[C:.*]])
+// LLVM-EH:         call void @llvm.lifetime.start.p0(ptr %[[X:.*]])
+// LLVM-EH:         invoke void @_Z3usei
+// LLVM-EH:         landingpad { ptr, i32 }
+// LLVM-EH:         call void @llvm.lifetime.end.p0(ptr %[[X]])
+// LLVM-EH:         call void @_ZN8LoopCondD1Ev(ptr {{.*}} %[[C]])
+
+// A marker-only loop cleanup also participates in an enclosing try's EH path.
+void lifetime_loop_in_try() {
+  try {
+    while (int c = source())
+      use(c);
+  } catch (...) {
+  }
+}
+
+// CIR-EH-LABEL: cir.func{{.*}} @_Z20lifetime_loop_in_tryv()
+// CIR-EH:         cir.try {
+// CIR-EH:           %[[C:.*]] = cir.alloca "c"
+// CIR-EH:           cir.while {
+// CIR-EH:             cir.call @_Z6sourcev()
+// CIR-EH:           } do {
+// CIR-EH:             cir.call @_Z3usei
+// CIR-EH:           } cleanup all {
+// CIR-EH:             cir.lifetime.end %[[C]]
+// CIR-EH:         } catch all
+
+// LLVM-EH-LABEL: define{{.*}} void @_Z20lifetime_loop_in_tryv()
+// LLVM-EH:         call void @llvm.lifetime.start.p0(ptr %[[C:.*]])
+// LLVM-EH:         invoke{{.*}} i32 @_Z6sourcev()
+// LLVM-EH:         invoke void @_Z3usei
+// LLVM-EH:         call void @llvm.lifetime.end.p0(ptr %[[C]])
+// LLVM-EH:         landingpad { ptr, i32 }
+// LLVM-EH:         call void @llvm.lifetime.end.p0(ptr %[[C]])
+// LLVM-EH:         call ptr @__cxa_begin_catch
+
 #endif // __EXCEPTIONS
diff --git a/clang/test/CIR/Transforms/flatten-cleanup-scope-musttail.cir 
b/clang/test/CIR/Transforms/flatten-cleanup-scope-musttail.cir
index 453336a6823aaf..0cd686759e59af 100644
--- a/clang/test/CIR/Transforms/flatten-cleanup-scope-musttail.cir
+++ b/clang/test/CIR/Transforms/flatten-cleanup-scope-musttail.cir
@@ -200,20 +200,14 @@ cir.func @test_musttail_in_eh_cleanup(%arg0: !s32i) -> 
!s32i {
   cir.unreachable
 }
 
+// A lifetime marker alone does not require an unwind edge for Other.
 // CHECK-LABEL: cir.func @test_musttail_in_eh_cleanup
 // CHECK:         %[[LOCAL:.*]] = cir.alloca "local"
 // CHECK:         cir.br ^[[BODY:bb[0-9]+]]
 // CHECK:       ^[[BODY]]:
-// CHECK:         cir.try_call @Other(%{{.*}}) ^[[NORMAL:bb[0-9]+]], 
^[[UNWIND:bb[0-9]+]]
-// CHECK:       ^[[NORMAL]]:
+// CHECK:         %[[OTHER:.*]] = cir.call @Other(%{{.*}})
+// CHECK:         cir.store %[[OTHER]], %[[LOCAL]]
 // CHECK:         %[[TAIL_ARG:.*]] = cir.load %[[LOCAL]]
 // CHECK-NEXT:    %[[TAIL_RET:.*]] = cir.call @Bar(%[[TAIL_ARG]]) musttail
 // CHECK-NEXT:    cir.return %[[TAIL_RET]] : !s32i
-// CHECK:       ^[[UNWIND]]:
-// CHECK:         %[[TOKEN:.*]] = cir.eh.initiate cleanup : !cir.eh_token
-// CHECK:         cir.br ^[[EH_CLEANUP:bb[0-9]+]](%[[TOKEN]] : !cir.eh_token)
-// CHECK:       ^[[EH_CLEANUP]](%[[EH_TOKEN:.*]]: !cir.eh_token):
-// CHECK:         cir.begin_cleanup %[[EH_TOKEN]]
-// CHECK:         cir.lifetime.end %[[LOCAL]]
-// CHECK:         cir.end_cleanup
-// CHECK:         cir.resume %[[EH_TOKEN]]
+// CHECK-NOT:     cir.eh.initiate

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

Reply via email to