[llvm-branch-commits] [clang] [clang] Use tighter lifetime bounds for C temporary arguments (PR #170518)

Paul Kirth via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Tue Jan 13 11:25:36 PST 2026


https://github.com/ilovepi updated https://github.com/llvm/llvm-project/pull/170518

>From c4dd4adf2c87e332da8273e47dab354295f05287 Mon Sep 17 00:00:00 2001
From: Paul Kirth <paulkirth at google.com>
Date: Tue, 2 Dec 2025 15:14:32 -0800
Subject: [PATCH] [clang] Use tighter lifetime bounds for C temporary arguments

In C, consecutive statements in the same scope are under
CompoundStmt/CallExpr, while in C++ they typically fall under
CompoundStmt/ExprWithCleanup. This leads to different behavior with
respect to where pushFullExprCleanUp inserts the lifetime end markers
(e.g., at the end of scope).

For these cases, we can track and insert the lifetime end markers right
after the call completes. Allowing the stack space to be reused
immediately. This partially addresses #109204 and #43598 for improving
stack usage.
---
 clang/lib/CodeGen/CGCall.cpp                  | 32 ++++---
 clang/lib/CodeGen/CGCall.h                    | 19 ++++
 clang/test/CodeGen/lifetime-invoke-c.c        |  2 +-
 clang/test/CodeGen/stack-usage-lifetimes.c    | 89 +++++++++++++++++++
 .../CodeGenCXX/aggregate-lifetime-invoke.cpp  |  2 +-
 .../CodeGenCXX/stack-reuse-miscompile.cpp     |  2 +-
 clang/test/CodeGenCoroutines/pr59181.cpp      |  2 +-
 7 files changed, 134 insertions(+), 14 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 d4fe113d52ec8..212123f63ed68 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -4962,12 +4962,10 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E,
 
   AggValueSlot ArgSlot = AggValueSlot::ignored();
   // For arguments with aggregate type, create an alloca to store
-  // the value.  If the argument's type has a destructor, that destructor
+  // 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.
+  // markers. For types which don't have a destructor, we use a narrower
+  // lifetime bound.
   // FIXME: This should work fine w/ exceptions, but somehow breaks the
   // dominance relationship in the def-use chain.
   if (!CGM.getLangOpts().Exceptions &&
@@ -4975,12 +4973,20 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E,
     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.
+    // Emit a lifetime start/end for this temporary. If the type has a
+    // destructor, then we need to keep it alive for the full expression.
     if (!CGM.getCodeGenOpts().NoLifetimeMarkersForTemporaries &&
-        EmitLifetimeStart(ArgSlotAlloca.getPointer()))
-      pushFullExprCleanup<CallLifetimeEnd>(CleanupKind::NormalEHLifetimeMarker,
-                                           ArgSlotAlloca);
+        EmitLifetimeStart(ArgSlotAlloca.getPointer())) {
+      if (E->getType().isDestructedType()) {
+        pushFullExprCleanup<CallLifetimeEnd>(NormalEHLifetimeMarker,
+                                             ArgSlotAlloca);
+      } else {
+        args.addLifetimeCleanup({ArgSlotAlloca.getPointer()});
+        if (getInvokeDest())
+          pushFullExprCleanup<CallLifetimeEnd>(CleanupKind::EHCleanup,
+                                               ArgSlotAlloca);
+      }
+    }
   }
 
   args.add(EmitAnyExpr(E, ArgSlot), type);
@@ -6310,6 +6316,12 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
   for (CallLifetimeEnd &LifetimeEnd : CallLifetimeEndAfterCall)
     LifetimeEnd.Emit(*this, /*Flags=*/{});
 
+  // Add lifetime end markers for any temporary aggregates. Under
+  // NoLifetimeMarkersForTemporaries LifetimeCleanups will be empty, so this is
+  // still correct.
+  for (const CallArgList::EndLifetimeInfo &LT : CallArgs.getLifetimeCleanups())
+    EmitLifetimeEnd(LT.Addr);
+
   if (!ReturnValue.isExternallyDestructed() &&
       RetTy.isDestructedType() == QualType::DK_nontrivial_c_struct)
     pushDestroy(QualType::DK_nontrivial_c_struct, Ret.getAggregateAddress(),
diff --git a/clang/lib/CodeGen/CGCall.h b/clang/lib/CodeGen/CGCall.h
index 4a86d58895dd9..4c5201cad5d7f 100644
--- a/clang/lib/CodeGen/CGCall.h
+++ b/clang/lib/CodeGen/CGCall.h
@@ -299,6 +299,10 @@ class CallArgList : public SmallVector<CallArg, 8> {
     llvm::Instruction *IsActiveIP;
   };
 
+  struct EndLifetimeInfo {
+    llvm::Value *Addr;
+  };
+
   void add(RValue rvalue, QualType type) { push_back(CallArg(rvalue, type)); }
 
   void addUncopiedAggregate(LValue LV, QualType type) {
@@ -312,6 +316,9 @@ class CallArgList : public SmallVector<CallArg, 8> {
     llvm::append_range(*this, other);
     llvm::append_range(Writebacks, other.Writebacks);
     llvm::append_range(CleanupsToDeactivate, other.CleanupsToDeactivate);
+    LifetimeCleanups.insert(LifetimeCleanups.end(),
+                            other.LifetimeCleanups.begin(),
+                            other.LifetimeCleanups.end());
     assert(!(StackBase && other.StackBase) && "can't merge stackbases");
     if (!StackBase)
       StackBase = other.StackBase;
@@ -352,6 +359,14 @@ class CallArgList : public SmallVector<CallArg, 8> {
   /// memory.
   bool isUsingInAlloca() const { return StackBase; }
 
+  void addLifetimeCleanup(EndLifetimeInfo Info) {
+    LifetimeCleanups.push_back(Info);
+  }
+
+  ArrayRef<EndLifetimeInfo> getLifetimeCleanups() const {
+    return LifetimeCleanups;
+  }
+
   // Support reversing writebacks for MSVC ABI.
   void reverseWritebacks() {
     std::reverse(Writebacks.begin(), Writebacks.end());
@@ -365,6 +380,10 @@ class CallArgList : public SmallVector<CallArg, 8> {
   /// occurs.
   SmallVector<CallArgCleanup, 1> CleanupsToDeactivate;
 
+  /// Lifetime information needed to call llvm.lifetime.end for any temporary
+  /// argument allocas.
+  SmallVector<EndLifetimeInfo, 2> LifetimeCleanups;
+
   /// The stacksave call.  It dominates all of the argument evaluation.
   llvm::CallInst *StackBase = nullptr;
 };
diff --git a/clang/test/CodeGen/lifetime-invoke-c.c b/clang/test/CodeGen/lifetime-invoke-c.c
index 6a43b3562a2e8..c7181b40f685c 100644
--- a/clang/test/CodeGen/lifetime-invoke-c.c
+++ b/clang/test/CodeGen/lifetime-invoke-c.c
@@ -18,11 +18,11 @@ void test() {
   // CHECK: call void @llvm.lifetime.start.p0(ptr %[[AGG1]])
   // CHECK: call void @gen(ptr{{.*}} sret(%struct.Trivial){{.*}} %[[AGG1]])
   // CHECK: call void @func(ptr{{.*}} %[[AGG1]])
+  // CHECK: call void @llvm.lifetime.end.p0(ptr %[[AGG1]])
   // CHECK: call void @llvm.lifetime.start.p0(ptr %[[AGG2]])
   // CHECK: call void @gen(ptr{{.*}} sret(%struct.Trivial){{.*}} %[[AGG2]])
   // CHECK: call void @func(ptr{{.*}} %[[AGG2]])
   // CHECK: call void @llvm.lifetime.end.p0(ptr %[[AGG2]])
-  // CHECK: call void @llvm.lifetime.end.p0(ptr %[[AGG1]])
   // CHECK: call void @cleanup
 
   // EXCEPTIONS: %[[AGG1:.*]] = alloca %struct.Trivial
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 c3ce91aea73f3..0c7ef09a4734b 100644
--- a/clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp
+++ b/clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp
@@ -25,7 +25,7 @@ void test() {
   // CHECK: invoke void @func_that_throws(ptr{{.*}} %[[AGG2]])
   // CHECK-NEXT: to label %[[CONT:.*]] unwind label %[[LPAD:.*]]
 
-  // CHECK: [[LPAD]]:
+  // CHECK: [[LPAD1:lpad.*]]:
   // CHECK: landingpad
 
   // CHECK-NOT: llvm.lifetime.start
diff --git a/clang/test/CodeGenCXX/stack-reuse-miscompile.cpp b/clang/test/CodeGenCXX/stack-reuse-miscompile.cpp
index 50c374d2710f4..4aef39119c94a 100644
--- a/clang/test/CodeGenCXX/stack-reuse-miscompile.cpp
+++ b/clang/test/CodeGenCXX/stack-reuse-miscompile.cpp
@@ -38,10 +38,10 @@ const char * f(S s)
 // CHECK: call void @llvm.lifetime.start.p0(ptr [[T3]])
 // CHECK: call void @llvm.lifetime.start.p0(ptr [[AGG]])
 // CHECK: [[T5:%.*]] = call noundef ptr @_ZN1TC1E1S(ptr {{[^,]*}} [[T3]], [2 x i32] %{{.*}})
+// CHECK: call void @llvm.lifetime.end.p0(ptr [[AGG]])
 //
 // CHECK: call void @_ZNK1T6concatERKS_(ptr dead_on_unwind writable sret(%class.T) align 4 [[T1]], ptr {{[^,]*}} [[T2]], ptr noundef nonnull align 4 dereferenceable(16) [[T3]])
 // CHECK: [[T6:%.*]] = call noundef ptr @_ZNK1T3strEv(ptr {{[^,]*}} [[T1]])
-// CHECK: call void @llvm.lifetime.end.p0(ptr [[AGG]])
 //
 // CHECK: call void @llvm.lifetime.end.p0(
 // CHECK: call void @llvm.lifetime.end.p0(
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]])



More information about the llvm-branch-commits mailing list