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 ®ion) { + 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
