[llvm-branch-commits] [clang] [clang] Introduce scopes for arguments without destructors (PR #191019)
Paul Kirth via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Wed Apr 8 16:31:05 PDT 2026
https://github.com/ilovepi updated https://github.com/llvm/llvm-project/pull/191019
>From 1cccdf78f4602775bc6cc2f45ece6edb5f06db2d Mon Sep 17 00:00:00 2001
From: Paul Kirth <paulkirth at google.com>
Date: Thu, 2 Apr 2026 13:28:39 -0700
Subject: [PATCH 1/4] [clang] Introduce scopes for arguments without
destructors
The current way we mark argument lifetimes is overly conservative for
most programs. To improve the status quo, we can introduce a new scope
for the arguments when it is safe to do so. This applies to aggregate
types that do not have destructors, and Objective-C types that are not
managed by reference counting (ARC). These rules may be possible to
refine more, but this recaptures a significant amount of the cases where
temporary objects never have their storage reused in stack coloring.
Note: I used an LLM to help draft test changes and suggest the scoping
rules for Objective-C.
---
clang/lib/CodeGen/CGCall.cpp | 6 +-
clang/lib/CodeGen/CGExpr.cpp | 30 +++++++
clang/test/CodeGen/lifetime-bug-2.cpp | 10 ++-
clang/test/CodeGen/lifetime-bug.cpp | 2 +-
clang/test/CodeGen/lifetime-invoke-c.c | 14 +--
clang/test/CodeGen/stack-usage-lifetimes.c | 89 +++++++++++++++++++
.../CodeGenCXX/aggregate-lifetime-invoke.cpp | 32 +++----
clang/test/CodeGenCoroutines/pr59181.cpp | 2 +-
8 files changed, 154 insertions(+), 31 deletions(-)
create mode 100644 clang/test/CodeGen/stack-usage-lifetimes.c
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 419ad20a74465..fb241f37bdb1c 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -5072,15 +5072,13 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E,
// the value. If the argument's type has a destructor, that destructor
// will run at the end of the full-expression; emit matching lifetime
// markers.
- //
- // FIXME: For types which don't have a destructor, consider using a
- // narrower lifetime bound.
if (hasAggregateEvaluationKind(E->getType())) {
RawAddress ArgSlotAlloca = Address::invalid();
ArgSlot = CreateAggTemp(E->getType(), "agg.tmp", &ArgSlotAlloca);
// Emit a lifetime start/end for this temporary at the end of the full
- // expression.
+ // expression. Note that we emit new scopes for these arguments in EmitCall,
+ // so cleanups will happen correctly on any error path.
if (!CGM.getCodeGenOpts().NoLifetimeMarkersForTemporaries &&
EmitLifetimeStart(ArgSlotAlloca.getPointer()))
pushFullExprCleanup<CallLifetimeEnd>(CleanupKind::NormalEHLifetimeMarker,
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 23802cdeb4811..9c199b86e7115 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -6861,6 +6861,27 @@ LValue CodeGenFunction::EmitStmtExprLValue(const StmtExpr *E) {
AlignmentSource::Decl);
}
+// Check if it is safe to tighten the lifetime of temporary aggregates for this
+// call. We can only do this if the call does not involve destructors or
+// Objective-C retainable types, as those push cleanups that must outlive the
+// call.
+static bool isSafeToTightenLifetime(const CallExpr *E) {
+ QualType RetTy = E->getType().getCanonicalType();
+ if (RetTy->isObjCRetainableType())
+ return false;
+
+ for (const auto *Arg : E->arguments()) {
+ if (Arg->getType().isDestructedType())
+ return false;
+
+ QualType Ty = Arg->getType().getNonReferenceType().getCanonicalType();
+ if (Ty->isObjCRetainableType())
+ return false;
+ }
+
+ return true;
+}
+
RValue CodeGenFunction::EmitCall(QualType CalleeType,
const CGCallee &OrigCallee, const CallExpr *E,
ReturnValueSlot ReturnValue,
@@ -6872,6 +6893,15 @@ RValue CodeGenFunction::EmitCall(QualType CalleeType,
assert(CalleeType->isFunctionPointerType() &&
"Call must have function pointer type!");
+ // For calls with trivial aggregate arguments, we want to tighten the
+ // lifetime of those aggregates to end immediately after the call returns,
+ // rather than at the end of the full-expression. We create a nested cleanup
+ // scope to intercept these lifetime markers. However, we only do this if the
+ // call is "safe".
+ std::optional<RunCleanupsScope> Scope;
+ if (isSafeToTightenLifetime(E))
+ Scope.emplace(*this);
+
const Decl *TargetDecl =
OrigCallee.getAbstractInfo().getCalleeDecl().getDecl();
diff --git a/clang/test/CodeGen/lifetime-bug-2.cpp b/clang/test/CodeGen/lifetime-bug-2.cpp
index aa7f0802fbad3..d13e20877e989 100644
--- a/clang/test/CodeGen/lifetime-bug-2.cpp
+++ b/clang/test/CodeGen/lifetime-bug-2.cpp
@@ -23,8 +23,14 @@ void foo(){
// CHECK-NEXT: invoke void @f1
// CHECK-NEXT: to label %[[CONT:.*]] unwind label %[[LPAD1:.*]]
//
+// CHECK: [[CONT]]:
+// CHECK-NEXT: @llvm.lifetime.end.p0(ptr [[TMP2]])
+// CHECK-NEXT: invoke void @f2
+// CHECK-NEXT: to label %[[CONT2:.*]] unwind label %[[LPAD2:.*]]
+//
+// CHECK: [[CONT2]]:
+// CHECK-NEXT: lifetime.end.p0(ptr [[TMP1]])
+//
// CHECK: [[LPAD1]]:
// CHECK-NEXT: landingpad
-// CHECK: llvm.lifetime.end.p0(ptr [[TMP2]])
// CHECK: llvm.lifetime.end.p0(ptr [[TMP1]])
-
diff --git a/clang/test/CodeGen/lifetime-bug.cpp b/clang/test/CodeGen/lifetime-bug.cpp
index d9d5350fd4cbd..a48128448c3d9 100644
--- a/clang/test/CodeGen/lifetime-bug.cpp
+++ b/clang/test/CodeGen/lifetime-bug.cpp
@@ -19,7 +19,7 @@ struct e {
// CHECK-NEXT: [[EXN_SLOT:%.*]] = alloca ptr, align 8
// CHECK-NEXT: [[EHSELECTOR_SLOT:%.*]] = alloca i32, align 4
// CHECK-NEXT: [[CLEANUP_ISACTIVE:%.*]] = alloca i1, align 1
-// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr @d, align 4, !tbaa [[_ZTS1C_TBAA6:![0-9]+]]
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr @d, align 4, !tbaa [[_ZTS1C_TBAA5:![0-9]+]]
// CHECK-NEXT: switch i32 [[TMP0]], label %[[SW_DEFAULT:.*]] [
// CHECK-NEXT: i32 1, label %[[SW_BB:.*]]
// CHECK-NEXT: ]
diff --git a/clang/test/CodeGen/lifetime-invoke-c.c b/clang/test/CodeGen/lifetime-invoke-c.c
index 77514dc80e9e6..2c67f03b4f3f2 100644
--- a/clang/test/CodeGen/lifetime-invoke-c.c
+++ b/clang/test/CodeGen/lifetime-invoke-c.c
@@ -11,14 +11,14 @@ struct Trivial {
// CHECK-SAME: ptr noundef [[P:%.*]]) #[[ATTR0:[0-9]+]] {
// CHECK-NEXT: [[ENTRY:.*:]]
// CHECK-NEXT: [[P_ADDR:%.*]] = alloca ptr, align 8
-// CHECK-NEXT: store ptr [[P]], ptr [[P_ADDR]], align 8, !tbaa [[INTPTR_TBAA6:![0-9]+]]
+// CHECK-NEXT: store ptr [[P]], ptr [[P_ADDR]], align 8, !tbaa [[INTPTR_TBAA5:![0-9]+]]
// CHECK-NEXT: ret void
//
// EXCEPTIONS-LABEL: define dso_local void @cleanup(
// EXCEPTIONS-SAME: ptr noundef [[P:%.*]]) #[[ATTR0:[0-9]+]] {
// EXCEPTIONS-NEXT: [[ENTRY:.*:]]
// EXCEPTIONS-NEXT: [[P_ADDR:%.*]] = alloca ptr, align 8
-// EXCEPTIONS-NEXT: store ptr [[P]], ptr [[P_ADDR]], align 8, !tbaa [[INTPTR_TBAA6:![0-9]+]]
+// EXCEPTIONS-NEXT: store ptr [[P]], ptr [[P_ADDR]], align 8, !tbaa [[INTPTR_TBAA5:![0-9]+]]
// EXCEPTIONS-NEXT: ret void
//
void cleanup(int *p) {}
@@ -35,11 +35,11 @@ struct Trivial gen(void);
// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[AGG_TMP]]) #[[ATTR3]]
// CHECK-NEXT: call void @gen(ptr dead_on_unwind writable sret([[STRUCT_TRIVIAL]]) align 4 [[AGG_TMP]])
// CHECK-NEXT: call void @func(ptr noundef byval([[STRUCT_TRIVIAL]]) align 8 [[AGG_TMP]])
+// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[AGG_TMP]]) #[[ATTR3]]
// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[AGG_TMP1]]) #[[ATTR3]]
// CHECK-NEXT: call void @gen(ptr dead_on_unwind writable sret([[STRUCT_TRIVIAL]]) align 4 [[AGG_TMP1]])
// CHECK-NEXT: call void @func(ptr noundef byval([[STRUCT_TRIVIAL]]) align 8 [[AGG_TMP1]])
// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[AGG_TMP1]]) #[[ATTR3]]
-// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[AGG_TMP]]) #[[ATTR3]]
// CHECK-NEXT: call void @cleanup(ptr noundef [[X]])
// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[X]]) #[[ATTR3]]
// CHECK-NEXT: ret void
@@ -60,6 +60,7 @@ struct Trivial gen(void);
// EXCEPTIONS-NEXT: invoke void @func(ptr noundef byval([[STRUCT_TRIVIAL]]) align 8 [[AGG_TMP]])
// EXCEPTIONS-NEXT: to label %[[INVOKE_CONT1:.*]] unwind label %[[LPAD]]
// EXCEPTIONS: [[INVOKE_CONT1]]:
+// EXCEPTIONS-NEXT: call void @llvm.lifetime.end.p0(ptr [[AGG_TMP]]) #[[ATTR4]]
// EXCEPTIONS-NEXT: call void @llvm.lifetime.start.p0(ptr [[AGG_TMP2]]) #[[ATTR4]]
// EXCEPTIONS-NEXT: invoke void @gen(ptr dead_on_unwind writable sret([[STRUCT_TRIVIAL]]) align 4 [[AGG_TMP2]])
// EXCEPTIONS-NEXT: to label %[[INVOKE_CONT4:.*]] unwind label %[[LPAD3:.*]]
@@ -68,7 +69,6 @@ struct Trivial gen(void);
// EXCEPTIONS-NEXT: to label %[[INVOKE_CONT5:.*]] unwind label %[[LPAD3]]
// EXCEPTIONS: [[INVOKE_CONT5]]:
// EXCEPTIONS-NEXT: call void @llvm.lifetime.end.p0(ptr [[AGG_TMP2]]) #[[ATTR4]]
-// EXCEPTIONS-NEXT: call void @llvm.lifetime.end.p0(ptr [[AGG_TMP]]) #[[ATTR4]]
// EXCEPTIONS-NEXT: call void @cleanup(ptr noundef [[X]])
// EXCEPTIONS-NEXT: call void @llvm.lifetime.end.p0(ptr [[X]]) #[[ATTR4]]
// EXCEPTIONS-NEXT: ret void
@@ -79,6 +79,7 @@ struct Trivial gen(void);
// EXCEPTIONS-NEXT: store ptr [[TMP1]], ptr [[EXN_SLOT]], align 8
// EXCEPTIONS-NEXT: [[TMP2:%.*]] = extractvalue { ptr, i32 } [[TMP0]], 1
// EXCEPTIONS-NEXT: store i32 [[TMP2]], ptr [[EHSELECTOR_SLOT]], align 4
+// EXCEPTIONS-NEXT: call void @llvm.lifetime.end.p0(ptr [[AGG_TMP]]) #[[ATTR4]]
// EXCEPTIONS-NEXT: br label %[[EHCLEANUP:.*]]
// EXCEPTIONS: [[LPAD3]]:
// EXCEPTIONS-NEXT: [[TMP3:%.*]] = landingpad { ptr, i32 }
@@ -90,7 +91,6 @@ struct Trivial gen(void);
// EXCEPTIONS-NEXT: call void @llvm.lifetime.end.p0(ptr [[AGG_TMP2]]) #[[ATTR4]]
// EXCEPTIONS-NEXT: br label %[[EHCLEANUP]]
// EXCEPTIONS: [[EHCLEANUP]]:
-// EXCEPTIONS-NEXT: call void @llvm.lifetime.end.p0(ptr [[AGG_TMP]]) #[[ATTR4]]
// EXCEPTIONS-NEXT: call void @cleanup(ptr noundef [[X]])
// EXCEPTIONS-NEXT: call void @llvm.lifetime.end.p0(ptr [[X]]) #[[ATTR4]]
// EXCEPTIONS-NEXT: br label %[[EH_RESUME:.*]]
@@ -98,8 +98,8 @@ struct Trivial gen(void);
// EXCEPTIONS-NEXT: [[EXN:%.*]] = load ptr, ptr [[EXN_SLOT]], align 8
// EXCEPTIONS-NEXT: [[SEL:%.*]] = load i32, ptr [[EHSELECTOR_SLOT]], align 4
// EXCEPTIONS-NEXT: [[LPAD_VAL:%.*]] = insertvalue { ptr, i32 } poison, ptr [[EXN]], 0
-// EXCEPTIONS-NEXT: [[LPAD_VAL8:%.*]] = insertvalue { ptr, i32 } [[LPAD_VAL]], i32 [[SEL]], 1
-// EXCEPTIONS-NEXT: resume { ptr, i32 } [[LPAD_VAL8]]
+// EXCEPTIONS-NEXT: [[LPAD_VAL7:%.*]] = insertvalue { ptr, i32 } [[LPAD_VAL]], i32 [[SEL]], 1
+// EXCEPTIONS-NEXT: resume { ptr, i32 } [[LPAD_VAL7]]
//
void test() {
int x __attribute__((cleanup(cleanup)));
diff --git a/clang/test/CodeGen/stack-usage-lifetimes.c b/clang/test/CodeGen/stack-usage-lifetimes.c
new file mode 100644
index 0000000000000..189bc9c229ca4
--- /dev/null
+++ b/clang/test/CodeGen/stack-usage-lifetimes.c
@@ -0,0 +1,89 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 -emit-codegen-only -Rpass-analysis=prologepilog %s -verify=x86-precise
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 -emit-codegen-only -Rpass-analysis=prologepilog -sloppy-temporary-lifetimes %s -verify=x86-sloppy
+
+// RUN: %clang_cc1 -triple aarch64-unknown-linux-gnu -O1 -emit-codegen-only -Rpass-analysis=prologepilog %s -verify=aarch64-precise
+// RUN: %clang_cc1 -triple aarch64-unknown-linux-gnu -O1 -emit-codegen-only -Rpass-analysis=prologepilog -sloppy-temporary-lifetimes %s -verify=aarch64-sloppy
+
+// RUN: %clang_cc1 -triple riscv64-unknown-linux-gnu -O1 -emit-codegen-only -Rpass-analysis=prologepilog %s -verify=riscv-precise
+// RUN: %clang_cc1 -triple riscv64-unknown-linux-gnu -O1 -emit-codegen-only -Rpass-analysis=prologepilog -sloppy-temporary-lifetimes %s -verify=riscv-sloppy
+
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 -emit-codegen-only -Rpass-analysis=prologepilog %s -verify=x86-precise -xc++
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 -emit-codegen-only -Rpass-analysis=prologepilog -sloppy-temporary-lifetimes %s -verify=x86-sloppy -xc++
+
+// RUN: %clang_cc1 -triple aarch64-unknown-linux-gnu -O1 -emit-codegen-only -Rpass-analysis=prologepilog %s -verify=aarch64-precise -xc++
+// RUN: %clang_cc1 -triple aarch64-unknown-linux-gnu -O1 -emit-codegen-only -Rpass-analysis=prologepilog -sloppy-temporary-lifetimes %s -verify=aarch64-sloppy -xc++
+
+// RUN: %clang_cc1 -triple riscv64-unknown-linux-gnu -O1 -emit-codegen-only -Rpass-analysis=prologepilog %s -verify=riscv-precise -xc++
+// RUN: %clang_cc1 -triple riscv64-unknown-linux-gnu -O1 -emit-codegen-only -Rpass-analysis=prologepilog -sloppy-temporary-lifetimes %s -verify=riscv-sloppy -xc++
+
+
+typedef struct { char x[32]; } A;
+typedef struct { char *w, *x, *y, *z; } B;
+
+void useA(A);
+void useB(B);
+A genA(void);
+B genB(void);
+
+void t1(int c) {
+ // x86-precise-remark at -1 {{40 stack bytes}}
+ // x86-sloppy-remark at -2 {{72 stack bytes}}
+ // aarch64-precise-remark at -3 {{48 stack bytes}}
+ // aarch64-sloppy-remark at -4 {{80 stack bytes}}
+ // riscv-precise-remark at -5 {{48 stack bytes}}
+ // riscv-sloppy-remark at -6 {{80 stack bytes}}
+
+ if (c)
+ useA(genA());
+ else
+ useA(genA());
+}
+
+void t2(void) {
+ // x86-precise-remark at -1 {{40 stack bytes}}
+ // x86-sloppy-remark at -2 {{72 stack bytes}}
+ // aarch64-precise-remark at -3 {{48 stack bytes}}
+ // aarch64-sloppy-remark at -4 {{80 stack bytes}}
+ // riscv-precise-remark at -5 {{48 stack bytes}}
+ // riscv-sloppy-remark at -6 {{80 stack bytes}}
+
+ useA(genA());
+ useA(genA());
+}
+
+void t3(void) {
+ // x86-precise-remark at -1 {{40 stack bytes}}
+ // x86-sloppy-remark at -2 {{72 stack bytes}}
+ // aarch64-precise-remark at -3 {{48 stack bytes}}
+ // aarch64-sloppy-remark at -4 {{80 stack bytes}}
+ // riscv-precise-remark at -5 {{48 stack bytes}}
+ // riscv-sloppy-remark at -6 {{80 stack bytes}}
+
+ useB(genB());
+ useB(genB());
+}
+
+#ifdef __cplusplus
+struct C {
+ char x[24];
+ char *ptr;
+ ~C() {};
+};
+
+void useC(C);
+C genC(void);
+
+// This case works in C++, since its AST is structured slightly differently
+// than it is in C (CompundStmt/ExprWithCleanup/CallExpr vs CompundStmt/CallExpr).
+void t4() {
+ // x86-precise-remark at -1 {{40 stack bytes}}
+ // x86-sloppy-remark at -2 {{72 stack bytes}}
+ // aarch64-precise-remark at -3 {{48 stack bytes}}
+ // aarch64-sloppy-remark at -4 {{80 stack bytes}}
+ // riscv-precise-remark at -5 {{48 stack bytes}}
+ // riscv-sloppy-remark at -6 {{80 stack bytes}}
+
+ useC(genC());
+ useC(genC());
+}
+#endif
diff --git a/clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp b/clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp
index 2d1075bbcbbb0..4fe5104e350ba 100644
--- a/clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp
+++ b/clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp
@@ -18,33 +18,33 @@ void func_that_throws(Trivial t);
// CHECK-SAME: ) local_unnamed_addr #[[ATTR0:[0-9]+]] personality ptr @__gxx_personality_v0 {
// CHECK-NEXT: [[ENTRY:.*:]]
// CHECK-NEXT: [[AGG_TMP:%.*]] = alloca [[STRUCT_TRIVIAL:%.*]], align 8
-// CHECK-NEXT: [[AGG_TMP1:%.*]] = alloca [[STRUCT_TRIVIAL]], align 8
+// CHECK-NEXT: [[AGG_TMP2:%.*]] = alloca [[STRUCT_TRIVIAL]], align 8
// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[AGG_TMP]]) #[[ATTR4:[0-9]+]]
// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr noundef nonnull align 8 dereferenceable(400) [[AGG_TMP]], i8 0, i64 400, i1 false)
// CHECK-NEXT: invoke void @func_that_throws(ptr noundef nonnull byval([[STRUCT_TRIVIAL]]) align 8 [[AGG_TMP]])
-// CHECK-NEXT: to label %[[INVOKE_CONT:.*]] unwind label %[[LPAD:.*]]
+// CHECK-NEXT: to label %[[INVOKE_CONT:.*]] unwind label %[[LPAD1:.*]]
// CHECK: [[INVOKE_CONT]]:
-// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[AGG_TMP1]]) #[[ATTR4]]
-// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr noundef nonnull align 8 dereferenceable(400) [[AGG_TMP1]], i8 0, i64 400, i1 false)
-// CHECK-NEXT: invoke void @func_that_throws(ptr noundef nonnull byval([[STRUCT_TRIVIAL]]) align 8 [[AGG_TMP1]])
-// CHECK-NEXT: to label %[[INVOKE_CONT4:.*]] unwind label %[[LPAD3:.*]]
-// CHECK: [[INVOKE_CONT4]]:
-// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[AGG_TMP1]]) #[[ATTR4]]
// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[AGG_TMP]]) #[[ATTR4]]
+// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[AGG_TMP2]]) #[[ATTR4]]
+// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr noundef nonnull align 8 dereferenceable(400) [[AGG_TMP2]], i8 0, i64 400, i1 false)
+// CHECK-NEXT: invoke void @func_that_throws(ptr noundef nonnull byval([[STRUCT_TRIVIAL]]) align 8 [[AGG_TMP2]])
+// CHECK-NEXT: to label %[[INVOKE_CONT5:.*]] unwind label %[[LPAD4:.*]]
+// CHECK: [[INVOKE_CONT5]]:
+// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[AGG_TMP2]]) #[[ATTR4]]
// CHECK-NEXT: br label %[[TRY_CONT:.*]]
-// CHECK: [[LPAD]]:
+// CHECK: [[LPAD1]]:
// CHECK-NEXT: [[TMP0:%.*]] = landingpad { ptr, i32 }
// CHECK-NEXT: catch ptr null
-// CHECK-NEXT: br label %[[EHCLEANUP:.*]]
-// CHECK: [[LPAD3]]:
+// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[AGG_TMP]]) #[[ATTR4]]
+// CHECK-NEXT: br label %[[CATCH:.*]]
+// CHECK: [[LPAD4]]:
// CHECK-NEXT: [[TMP1:%.*]] = landingpad { ptr, i32 }
// CHECK-NEXT: catch ptr null
-// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[AGG_TMP1]]) #[[ATTR4]]
-// CHECK-NEXT: br label %[[EHCLEANUP]]
-// CHECK: [[EHCLEANUP]]:
-// CHECK-NEXT: [[DOTPN:%.*]] = phi { ptr, i32 } [ [[TMP1]], %[[LPAD3]] ], [ [[TMP0]], %[[LPAD]] ]
+// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[AGG_TMP2]]) #[[ATTR4]]
+// CHECK-NEXT: br label %[[CATCH]]
+// CHECK: [[CATCH]]:
+// CHECK-NEXT: [[DOTPN:%.*]] = phi { ptr, i32 } [ [[TMP1]], %[[LPAD4]] ], [ [[TMP0]], %[[LPAD1]] ]
// CHECK-NEXT: [[EXN_SLOT_0:%.*]] = extractvalue { ptr, i32 } [[DOTPN]], 0
-// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[AGG_TMP]]) #[[ATTR4]]
// CHECK-NEXT: [[TMP2:%.*]] = tail call ptr @__cxa_begin_catch(ptr [[EXN_SLOT_0]]) #[[ATTR4]]
// CHECK-NEXT: tail call void @__cxa_end_catch()
// CHECK-NEXT: br label %[[TRY_CONT]]
diff --git a/clang/test/CodeGenCoroutines/pr59181.cpp b/clang/test/CodeGenCoroutines/pr59181.cpp
index bb5eba1c3252d..7c0868d6b82bd 100644
--- a/clang/test/CodeGenCoroutines/pr59181.cpp
+++ b/clang/test/CodeGenCoroutines/pr59181.cpp
@@ -59,5 +59,5 @@ void foo() {
// CHECK: call void @llvm.coro.await.suspend.void(
// CHECK-NEXT: %{{[0-9]+}} = call i8 @llvm.coro.suspend(
-// CHECK-LABEL: cleanup.done20:
+// CHECK-LABEL: cond.end:
// CHECK: call void @llvm.lifetime.end.p0(ptr [[AGG]])
>From 343fdb080be1937f27bf5b5889beb8aa2b63f63a Mon Sep 17 00:00:00 2001
From: Paul Kirth <paulkirth at google.com>
Date: Wed, 8 Apr 2026 14:39:46 -0700
Subject: [PATCH 2/4] Save amdgpu test update
---
.../CodeGenCXX/conditional-temporaries.cpp | 46 ++++++++++++-------
1 file changed, 29 insertions(+), 17 deletions(-)
diff --git a/clang/test/CodeGenCXX/conditional-temporaries.cpp b/clang/test/CodeGenCXX/conditional-temporaries.cpp
index beac2d10291f0..160f9f148ff4f 100644
--- a/clang/test/CodeGenCXX/conditional-temporaries.cpp
+++ b/clang/test/CodeGenCXX/conditional-temporaries.cpp
@@ -67,8 +67,9 @@ int lifetime_nontriv(bool cond) {
// CHECK-NOOPT: store i1 false,
// CHECK-NOOPT: store i1 false,
// CHECK-NOOPT: store i1 false,
- // CHECK-NOOPT: br i1
+ // CHECK-NOOPT: br i1 {{.*}}, label %[[COND_TRUE:.*]], label %[[COND_FALSE:.*]]
//
+ // CHECK-NOOPT: [[COND_TRUE]]:
// CHECK-NOOPT: call void @llvm.lifetime.start
// CHECK-NOOPT: store i1 true,
// CHECK-NOOPT: store i1 true,
@@ -82,10 +83,6 @@ int lifetime_nontriv(bool cond) {
// CHECK-NOOPT: store i1 true,
// CHECK-NOOPT: call noundef i32 @_ZN1X1fEv(
// CHECK-NOOPT: call noundef i32 @_Z1giii(
- // CHECK-NOOPT: br label
- //
- // CHECK-NOOPT: call noundef i32 @_Z1giii(i32 noundef 1, i32 noundef 2, i32 noundef 3)
- // CHECK-NOOPT: br label
//
// CHECK-NOOPT: load i1,
// CHECK-NOOPT: br i1
@@ -117,6 +114,10 @@ int lifetime_nontriv(bool cond) {
// CHECK-NOOPT: call void @llvm.lifetime.end
// CHECK-NOOPT: br label
//
+ // CHECK-NOOPT: [[COND_FALSE]]:
+ // CHECK-NOOPT: call noundef i32 @_Z1giii(i32 noundef 1, i32 noundef 2, i32 noundef 3)
+ // CHECK-NOOPT: br label
+ //
// CHECK-NOOPT: ret
// CHECK-OPT: br i1
@@ -151,36 +152,39 @@ int lifetime_triv(bool cond) {
// CHECK-NOOPT: call noundef i32 @_ZN1Y1fEv(
// CHECK-NOOPT: call noundef i32 @_ZN1Y1fEv(
// CHECK-NOOPT: call noundef i32 @_Z1giii(
- // CHECK-NOOPT: br label
- //
- // CHECK-NOOPT: call noundef i32 @_Z1giii(i32 noundef 1, i32 noundef 2, i32 noundef 3)
- // CHECK-NOOPT: br label
- //
// CHECK-NOOPT: call void @llvm.lifetime.end
// CHECK-NOOPT-NOT: br
// CHECK-NOOPT: call void @llvm.lifetime.end
// CHECK-NOOPT-NOT: br
// CHECK-NOOPT: call void @llvm.lifetime.end
+ // CHECK-NOOPT: br label
+ //
+ // CHECK-NOOPT: cond.false:
+ // CHECK-NOOPT-NOT: call void @llvm.lifetime.end
+ // CHECK-NOOPT: call noundef i32 @_Z1giii(i32 noundef 1, i32 noundef 2, i32 noundef 3)
+ // CHECK-NOOPT: br label
//
// CHECK-NOOPT: ret
- // FIXME: LLVM isn't smart enough to remove the lifetime markers from the
- // g(1, 2, 3) path here.
-
+ // CHECK-OPT: entry:
// CHECK-OPT: call void @llvm.lifetime.start
// CHECK-OPT: call void @llvm.lifetime.start
// CHECK-OPT: call void @llvm.lifetime.start
// CHECK-OPT: br i1
//
+ // CHECK-OPT: [[COND_TRUE:.*]]:
// CHECK-OPT: call noundef i32 @_ZN1Y1fEv(
// CHECK-OPT: call noundef i32 @_ZN1Y1fEv(
// CHECK-OPT: call noundef i32 @_ZN1Y1fEv(
// CHECK-OPT: call noundef i32 @_Z1giii(
- // CHECK-OPT: br label
- //
// CHECK-OPT: call void @llvm.lifetime.end
// CHECK-OPT: call void @llvm.lifetime.end
// CHECK-OPT: call void @llvm.lifetime.end
+ // CHECK-OPT: br label %[[COND_END:.*]]
+ //
+ // CHECK-OPT: [[COND_END]]:
+ // CHECK-OPT: phi
+ // CHECK-OPT: ret
return cond ? g(Y().f(), Y().f(), Y().f()) : g(1, 2, 3);
}
@@ -188,8 +192,9 @@ struct Z { ~Z() {} int f(); };
int g(int, int, int);
// CHECK-LABEL: @_Z22lifetime_nontriv_empty
int lifetime_nontriv_empty(bool cond) {
- // CHECK-OPT: br i1
+ // CHECK-OPT: br i1 {{.*}}, label %[[COND_TRUE_2:.*]], label %[[COND_FALSE_2:.*]]
//
+ // CHECK-OPT: [[COND_TRUE_2]]:
// CHECK-OPT: call void @llvm.lifetime.start
// CHECK-OPT: call noundef i32 @_ZN1Z1fEv(
// CHECK-OPT: call void @llvm.lifetime.start
@@ -200,6 +205,13 @@ int lifetime_nontriv_empty(bool cond) {
// CHECK-OPT: call void @llvm.lifetime.end
// CHECK-OPT: call void @llvm.lifetime.end
// CHECK-OPT: call void @llvm.lifetime.end
- // CHECK-OPT: br label
+ // CHECK-OPT: br label %[[COND_END_2:.*]]
+ //
+ // CHECK-OPT: [[COND_FALSE_2]]:
+ // CHECK-OPT: call noundef i32 @_Z1giii(i32 noundef 1, i32 noundef 2, i32 noundef 3)
+ //
+ // CHECK-OPT: [[COND_END_2]]:
+ // CHECK-OPT: phi
+ // CHECK-OPT: ret
return cond ? g(Z().f(), Z().f(), Z().f()) : g(1, 2, 3);
}
>From 5480f19ae7c729f68b1b40646370d2df4c2e3245 Mon Sep 17 00:00:00 2001
From: Paul Kirth <paulkirth at google.com>
Date: Wed, 8 Apr 2026 15:45:28 -0700
Subject: [PATCH 3/4] Only shorten lifetime when enabled by flag
---
clang/lib/CodeGen/CGExpr.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 9c199b86e7115..094002c106576 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -6899,7 +6899,8 @@ RValue CodeGenFunction::EmitCall(QualType CalleeType,
// scope to intercept these lifetime markers. However, we only do this if the
// call is "safe".
std::optional<RunCleanupsScope> Scope;
- if (isSafeToTightenLifetime(E))
+ if (!CGM.getCodeGenOpts().NoLifetimeMarkersForTemporaries &&
+ isSafeToTightenLifetime(E))
Scope.emplace(*this);
const Decl *TargetDecl =
>From b0e06e5f2168dc9f85751dfb8e171c7c11d1080e Mon Sep 17 00:00:00 2001
From: Paul Kirth <paulkirth at google.com>
Date: Wed, 8 Apr 2026 16:30:14 -0700
Subject: [PATCH 4/4] Add checks for the lpad2 and ehcleanup blocks
---
clang/test/CodeGen/lifetime-bug-2.cpp | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/clang/test/CodeGen/lifetime-bug-2.cpp b/clang/test/CodeGen/lifetime-bug-2.cpp
index d13e20877e989..84b14220deefe 100644
--- a/clang/test/CodeGen/lifetime-bug-2.cpp
+++ b/clang/test/CodeGen/lifetime-bug-2.cpp
@@ -33,4 +33,12 @@ void foo(){
//
// CHECK: [[LPAD1]]:
// CHECK-NEXT: landingpad
+// CHECK: llvm.lifetime.end.p0(ptr [[TMP2]])
+// CHECK-NEXT: br label %[[EHCLEANUP:.*]]
+//
+// CHECK: [[LPAD2]]:
+// CHECK-NEXT: landingpad
+// CHECK-NOT: llvm.lifetime.end
+//
+// CHECK: [[EHCLEANUP]]:
// CHECK: llvm.lifetime.end.p0(ptr [[TMP1]])
More information about the llvm-branch-commits
mailing list