From c4c3f95dfacef280a4173f4b2c1f59fbd916fe16 Mon Sep 17 00:00:00 2001 From: Paul Kirth Date: Mon, 1 Dec 2025 16:28:42 -0800 Subject: [PATCH 01/10] [clang] Limit lifetimes of temporaries to the full expression We have several issues describing suboptimal stack usage related to the lifetimes of temporary objects, such as #68747, #43598, and #109204. Previously, https://reviews.llvm.org/D74094 tried to address this. In that review, a few issues were brought up, particularly a concern about the lifetimes of the temporaries needing to be extended to end of the full expression. While there are arguably more optimal lifetime bounds we could enforce, for now we can conservatively make them extend to the end of the full expression, and later refine the optimization to use tighter bounds (or perhaps a better mechanism in the middle end?). Fixes #68747 Co-authored-by: Nick Desaulniers Co-authored-by: Erik Pilkington --- clang/docs/ReleaseNotes.rst | 9 ++ clang/include/clang/Basic/CodeGenOptions.def | 4 + clang/include/clang/Options/Options.td | 5 + clang/lib/CodeGen/CGCall.cpp | 19 +++- clang/test/CodeGen/lifetime-call-temp.c | 98 +++++++++++++++++++ clang/test/CodeGen/stack-usage-lifetimes.c | 89 +++++++++++++++++ .../CodeGenCXX/amdgcn-call-with-aggarg.cc | 19 ++++ .../CodeGenCXX/stack-reuse-miscompile.cpp | 4 + clang/test/CodeGenCoroutines/pr59181.cpp | 4 + 9 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 clang/test/CodeGen/lifetime-call-temp.c create mode 100644 clang/test/CodeGen/stack-usage-lifetimes.c create mode 100644 clang/test/CodeGenCXX/amdgcn-call-with-aggarg.cc diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 654a8e48cd104..aef14248b9022 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -86,6 +86,15 @@ Potentially Breaking Changes options-related code has been moved out of the Driver into a separate library. - The ``clangFrontend`` library no longer depends on ``clangDriver``, which may break downstream projects that relied on this transitive dependency. +- Clang is now more precise with regards to the lifetime of temporary objects + such as when aggregates are passed by value to a function, resulting in + better sharing of stack slots and reduced stack usage. This change can lead + to use-after-scope related issues in code that unintentionally relied on the + previous behavior. If recompiling with ``-fsanitize=address`` shows a + use-after-scope warning, then this is likely the case, and the report printed + should be able to help users pinpoint where the use-after-scope is occurring. + Users can use ``-Xclang -sloppy-temporary-lifetimes`` to retain the old + behavior until they are able to find and resolve issues in their code. C/C++ Language Potentially Breaking Changes ------------------------------------------- diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def index 76a6463881c6f..e7f5b4c9a08a9 100644 --- a/clang/include/clang/Basic/CodeGenOptions.def +++ b/clang/include/clang/Basic/CodeGenOptions.def @@ -475,6 +475,10 @@ ENUM_CODEGENOPT(ZeroCallUsedRegs, ZeroCallUsedRegsKind, /// non-deleting destructors. (No effect on Microsoft ABI.) CODEGENOPT(CtorDtorReturnThis, 1, 0, Benign) +/// Set via -Xclang -sloppy-temporary-lifetimes to disable emission of lifetime +/// marker intrinsic calls. +CODEGENOPT(NoLifetimeMarkersForTemporaries, 1, 0, Benign) + /// Enables emitting Import Call sections on supported targets that can be used /// by the Windows kernel to enable import call optimization. CODEGENOPT(ImportCallOptimization, 1, 0, Benign) diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index c6841937c8d39..02396fb9b4d2d 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -8151,6 +8151,11 @@ def import_call_optimization : Flag<["-"], "import-call-optimization">, def replaceable_function: Joined<["-"], "loader-replaceable-function=">, MarshallingInfoStringVector>; +def sloppy_temporary_lifetimes + : Flag<["-"], "sloppy-temporary-lifetimes">, + HelpText<"Don't emit lifetime markers for temporary objects">, + MarshallingInfoFlag>; + } // let Visibility = [CC1Option] //===----------------------------------------------------------------------===// diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp index 4a9025b6e0b0f..56d1c5ad41e2c 100644 --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -43,6 +43,7 @@ #include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/Intrinsics.h" #include "llvm/IR/Type.h" +#include "llvm/Support/TypeSize.h" #include "llvm/Transforms/Utils/Local.h" #include using namespace clang; @@ -4960,7 +4961,23 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E, return; } - args.add(EmitAnyExprToTemp(E), type); + AggValueSlot ArgSlot = AggValueSlot::ignored(); + // If the callee returns a reference, skip this stack saving optimization; + // we don't want to prematurely end the lifetime of the temporary. It may be + // possible to still perform this optimization if the return type is a + // reference to a different type than the parameter. + 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. + if (!CGM.getCodeGenOpts().NoLifetimeMarkersForTemporaries && + EmitLifetimeStart(ArgSlotAlloca.getPointer())) + pushFullExprCleanup(NormalAndEHCleanup, ArgSlotAlloca); + } + + args.add(EmitAnyExpr(E, ArgSlot), type); } QualType CodeGenFunction::getVarArgType(const Expr *Arg) { diff --git a/clang/test/CodeGen/lifetime-call-temp.c b/clang/test/CodeGen/lifetime-call-temp.c new file mode 100644 index 0000000000000..3bc68b5e8024a --- /dev/null +++ b/clang/test/CodeGen/lifetime-call-temp.c @@ -0,0 +1,98 @@ +// RUN: %clang -cc1 -triple x86_64-apple-macos -O1 -disable-llvm-passes %s \ +// RUN: -emit-llvm -o - | FileCheck %s --implicit-check-not=llvm.lifetime +// RUN: %clang -cc1 -xc++ -std=c++17 -triple x86_64-apple-macos -O1 \ +// RUN: -disable-llvm-passes %s -emit-llvm -o - -Wno-return-type-c-linkage | \ +// RUN: FileCheck %s --implicit-check-not=llvm.lifetime --check-prefixes=CHECK,CXX +// RUN: %clang -cc1 -xobjective-c -triple x86_64-apple-macos -O1 \ +// RUN: -disable-llvm-passes %s -emit-llvm -o - | \ +// RUN: FileCheck %s --implicit-check-not=llvm.lifetime --check-prefixes=CHECK,OBJC +// RUN: %clang -cc1 -triple x86_64-apple-macos -O1 -disable-llvm-passes %s \ +// RUN: -emit-llvm -o - -sloppy-temporary-lifetimes | \ +// RUN: FileCheck %s --implicit-check-not=llvm.lifetime --check-prefixes=SLOPPY + +typedef struct { int x[100]; } aggregate; + +#ifdef __cplusplus +extern "C" { +#endif + +void takes_aggregate(aggregate); +aggregate gives_aggregate(); + +// CHECK-LABEL: define void @t1 +void t1() { + takes_aggregate(gives_aggregate()); + + // CHECK: [[AGGTMP:%.*]] = alloca %struct.aggregate, align 8 + // CHECK: call void @llvm.lifetime.start.p0(ptr [[AGGTMP]]) + // CHECK: call void{{.*}} @gives_aggregate(ptr{{.*}}sret(%struct.aggregate) align 4 [[AGGTMP]]) + // CHECK: call void @takes_aggregate(ptr noundef byval(%struct.aggregate) align 8 [[AGGTMP]]) + // CHECK: call void @llvm.lifetime.end.p0(ptr [[AGGTMP]]) + + // SLOPPY: [[AGGTMP:%.*]] = alloca %struct.aggregate, align 8 + // SLOPPY-NEXT: call void (ptr, ...) @gives_aggregate(ptr{{.*}}sret(%struct.aggregate) align 4 [[AGGTMP]]) + // SLOPPY-NEXT: call void @takes_aggregate(ptr noundef byval(%struct.aggregate) align 8 [[AGGTMP]]) +} + +// CHECK: declare {{.*}}llvm.lifetime.start +// CHECK: declare {{.*}}llvm.lifetime.end + +#ifdef __cplusplus +// CXX: define void @t2 +void t2() { + struct S { + S(aggregate) {} + }; + S{gives_aggregate()}; + + // CXX: [[AGG:%.*]] = alloca %struct.aggregate + // CXX: call void @llvm.lifetime.start.p0(ptr [[AGG]] + // CXX: call void @gives_aggregate(ptr{{.*}}sret(%struct.aggregate) align 4 [[AGG]]) + // CXX: call void @_ZZ2t2EN1SC1E9aggregate(ptr {{.*}}, ptr {{.*}} byval(%struct.aggregate) align 8 [[AGG]]) + // CXX: call void @llvm.lifetime.end.p0(ptr [[AGG]] +} + +struct Dtor { + ~Dtor(); +}; + +void takes_dtor(Dtor); +Dtor gives_dtor(); + +// CXX: define void @t3 +void t3() { + takes_dtor(gives_dtor()); + + // CXX: [[AGG:%.*]] = alloca %struct.Dtor + // CXX: call void @llvm.lifetime.start.p0(ptr [[AGG]]) + // CXX: call void @gives_dtor(ptr{{.*}}sret(%struct.Dtor) align 1 [[AGG]]) + // CXX: call void @takes_dtor(ptr noundef [[AGG]]) + // CXX: call void @_ZN4DtorD1Ev(ptr {{.*}} [[AGG]]) + // CXX: call void @llvm.lifetime.end.p0(ptr [[AGG]]) + // CXX: ret void +} + +#endif + +#ifdef __OBJC__ + +@interface X +-m:(aggregate)x; +@end + +// OBJC: define void @t4 +void t4(X *x) { + [x m: gives_aggregate()]; + + // OBJC: [[AGG:%.*]] = alloca %struct.aggregate + // OBJC: call void @llvm.lifetime.start.p0(ptr [[AGG]] + // OBJC: call void{{.*}} @gives_aggregate(ptr{{.*}}sret(%struct.aggregate) align 4 [[AGGTMP]]) + // OBJC: call {{.*}}@objc_msgSend + // OBJC: call void @llvm.lifetime.end.p0(ptr [[AGG]] +} + +#endif + +#ifdef __cplusplus +} +#endif diff --git a/clang/test/CodeGen/stack-usage-lifetimes.c b/clang/test/CodeGen/stack-usage-lifetimes.c new file mode 100644 index 0000000000000..3787a29e4ce7d --- /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@-1 {{40 stack bytes}} + // x86-sloppy-remark@-2 {{72 stack bytes}} + // aarch64-precise-remark@-3 {{48 stack bytes}} + // aarch64-sloppy-remark@-4 {{80 stack bytes}} + // riscv-precise-remark@-5 {{48 stack bytes}} + // riscv-sloppy-remark@-6 {{80 stack bytes}} + + if (c) + useA(genA()); + else + useA(genA()); +} + +void t2(void) { + // x86-precise-remark@-1 {{72 stack bytes}} + // x86-sloppy-remark@-2 {{72 stack bytes}} + // aarch64-precise-remark@-3 {{80 stack bytes}} + // aarch64-sloppy-remark@-4 {{80 stack bytes}} + // riscv-precise-remark@-5 {{80 stack bytes}} + // riscv-sloppy-remark@-6 {{80 stack bytes}} + + useA(genA()); + useA(genA()); +} + +void t3(void) { + // x86-precise-remark@-1 {{72 stack bytes}} + // x86-sloppy-remark@-2 {{72 stack bytes}} + // aarch64-precise-remark@-3 {{80 stack bytes}} + // aarch64-sloppy-remark@-4 {{80 stack bytes}} + // riscv-precise-remark@-5 {{80 stack bytes}} + // riscv-sloppy-remark@-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@-1 {{40 stack bytes}} + // x86-sloppy-remark@-2 {{72 stack bytes}} + // aarch64-precise-remark@-3 {{48 stack bytes}} + // aarch64-sloppy-remark@-4 {{80 stack bytes}} + // riscv-precise-remark@-5 {{48 stack bytes}} + // riscv-sloppy-remark@-6 {{80 stack bytes}} + + useC(genC()); + useC(genC()); +} +#endif diff --git a/clang/test/CodeGenCXX/amdgcn-call-with-aggarg.cc b/clang/test/CodeGenCXX/amdgcn-call-with-aggarg.cc new file mode 100644 index 0000000000000..9b598a48f6436 --- /dev/null +++ b/clang/test/CodeGenCXX/amdgcn-call-with-aggarg.cc @@ -0,0 +1,19 @@ +// RUN: %clang_cc1 -triple amdgcn-amd-amdhsa -emit-llvm -O3 -disable-llvm-passes -o - %s | FileCheck %s + +struct A { + float x, y, z, w; +}; + +void foo(A a); + +// CHECK-LABEL: @_Z4testv +// CHECK: [[A:%.*]] = alloca [[STRUCT_A:%.*]], align 4, addrspace(5) +// CHECK-NEXT: [[AGG_TMP:%.*]] = alloca [[STRUCT_A]], align 4, addrspace(5) +// CHECK-NEXT: [[A_ASCAST:%.*]] = addrspacecast ptr addrspace(5) [[A]] to ptr +// CHECK-NEXT: [[AGG_TMP_ASCAST:%.*]] = addrspacecast ptr addrspace(5) [[AGG_TMP]] to ptr +// CHECK-NEXT: call void @llvm.lifetime.start.p5(i64 16, ptr addrspace(5) [[A]]) #[[ATTR4:[0-9]+]] +// CHECK-NEXT: call void @llvm.lifetime.start.p5(i64 16, ptr addrspace(5) [[AGG_TMP]]) #[[ATTR4]] +void test() { + A a; + foo(a); +} diff --git a/clang/test/CodeGenCXX/stack-reuse-miscompile.cpp b/clang/test/CodeGenCXX/stack-reuse-miscompile.cpp index 67fa9f9c9cd98..50c374d2710f4 100644 --- a/clang/test/CodeGenCXX/stack-reuse-miscompile.cpp +++ b/clang/test/CodeGenCXX/stack-reuse-miscompile.cpp @@ -26,6 +26,8 @@ const char * f(S s) // CHECK: [[T2:%.*]] = alloca %class.T, align 4 // CHECK: [[T3:%.*]] = alloca %class.T, align 4 // +// CHECK: [[AGG:%.*]] = alloca %class.S, align 4 +// // FIXME: We could defer starting the lifetime of the return object of concat // until the call. // CHECK: call void @llvm.lifetime.start.p0(ptr [[T1]]) @@ -34,10 +36,12 @@ const char * f(S s) // CHECK: [[T4:%.*]] = call noundef ptr @_ZN1TC1EPKc(ptr {{[^,]*}} [[T2]], ptr noundef @.str) // // 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 @_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 21e784e0031de..a68a61984f981 100644 --- a/clang/test/CodeGenCoroutines/pr59181.cpp +++ b/clang/test/CodeGenCoroutines/pr59181.cpp @@ -49,6 +49,7 @@ void foo() { } // CHECK: cleanup.cont:{{.*}} +// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[AGG:%agg.tmp]]) // CHECK-NEXT: load i8 // CHECK-NEXT: trunc // CHECK-NEXT: store i1 false @@ -57,3 +58,6 @@ void foo() { // CHECK-NOT: call void @llvm.lifetime // CHECK: call void @llvm.coro.await.suspend.void( // CHECK-NEXT: %{{[0-9]+}} = call i8 @llvm.coro.suspend( + +// CHECK-LABEL: cond.end: +// check call @llvm.lifetime.end.p0(ptr [[AGG]]) From 36be3288847e3502a2e643c2261b49bbda520227 Mon Sep 17 00:00:00 2001 From: Paul Kirth Date: Wed, 3 Dec 2025 14:15:08 -0800 Subject: [PATCH 02/10] Use more accurate comment --- clang/lib/CodeGen/CGCall.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp index 56d1c5ad41e2c..80075dd8a4cca 100644 --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -4962,10 +4962,13 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E, } AggValueSlot ArgSlot = AggValueSlot::ignored(); - // If the callee returns a reference, skip this stack saving optimization; - // we don't want to prematurely end the lifetime of the temporary. It may be - // possible to still perform this optimization if the return type is a - // reference to a different type than the parameter. + // For arguments with aggregate type, create an alloca to store + // 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); From 42f08baff629fa28d274b40a0d6ce317f3ebe821 Mon Sep 17 00:00:00 2001 From: Paul Kirth Date: Wed, 3 Dec 2025 16:20:07 -0800 Subject: [PATCH 03/10] Add test for invoke instructions --- .../CodeGenCXX/aggregate-lifetime-invoke.cpp | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp diff --git a/clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp b/clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp new file mode 100644 index 0000000000000..de8097b39bb1b --- /dev/null +++ b/clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp @@ -0,0 +1,45 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s -O1 -fexceptions -fcxx-exceptions | FileCheck %s + +struct Trivial { + int x[100]; +}; + +void func_that_throws(Trivial t); + +// CHECK-LABEL: define dso_local void @_Z4testv(){{.*}} personality ptr @__gxx_personality_v0 +void test() { + // CHECK: %[[AGG1:.*]] = alloca %struct.Trivial, align + // CHECK: %[[AGG2:.*]] = alloca %struct.Trivial, align + + // CHECK: call void @llvm.lifetime.start.p0(ptr nonnull %[[AGG1]]) + // CHECK: invoke void @_Z16func_that_throws7Trivial(ptr noundef nonnull byval(%struct.Trivial) align 8 %[[AGG1]]) + // CHECK-NEXT: to label %[[CONT1:.*]] unwind label %[[LPAD1:.*]] + + // CHECK: [[CONT1]]: + // CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull %[[AGG2]]) + // CHECK: invoke void @_Z16func_that_throws7Trivial(ptr noundef nonnull byval(%struct.Trivial) align 8 %[[AGG2]]) + // CHECK-NEXT: to label %[[CONT2:.*]] unwind label %[[LPAD2:.*]] + + // CHECK: [[CONT2]]: + // CHECK-DAG: call void @llvm.lifetime.end.p0(ptr nonnull %[[AGG2]]) + // CHECK-DAG: call void @llvm.lifetime.end.p0(ptr nonnull %[[AGG1]]) + // CHECK: br label %[[TRY_CONT:.*]] + + // CHECK: [[LPAD1]]: + // CHECK: landingpad + // CHECK: br label %[[EHCLEANUP:.*]] + + // CHECK: [[LPAD2]]: + // CHECK: landingpad + // CHECK: call void @llvm.lifetime.end.p0(ptr nonnull %[[AGG2]]) + // CHECK: br label %[[EHCLEANUP]] + + // CHECK: [[EHCLEANUP]]: + // CHECK: call void @llvm.lifetime.end.p0(ptr nonnull %[[AGG1]]) + // CHECK: call ptr @__cxa_begin_catch + try { + func_that_throws(Trivial{0}); + func_that_throws(Trivial{0}); + } catch (...) { + } +} From 9bf31b691fbf6869ff74c18cf02e761cf49c6567 Mon Sep 17 00:00:00 2001 From: Paul Kirth Date: Thu, 4 Dec 2025 13:05:58 -0800 Subject: [PATCH 04/10] Add additional invoke test in C --- clang/test/CodeGen/lifetime-invoke-c.c | 50 +++++++++++++++++++ .../CodeGenCXX/aggregate-lifetime-invoke.cpp | 2 +- 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 clang/test/CodeGen/lifetime-invoke-c.c diff --git a/clang/test/CodeGen/lifetime-invoke-c.c b/clang/test/CodeGen/lifetime-invoke-c.c new file mode 100644 index 0000000000000..a2ab7978cf88d --- /dev/null +++ b/clang/test/CodeGen/lifetime-invoke-c.c @@ -0,0 +1,50 @@ +// RUN: %clang_cc1 -emit-llvm -o - %s -O1 -disable-llvm-passes -fexceptions | FileCheck %s + +struct Trivial { + int x[100]; +}; + +void cleanup(int *p) {} +void func(struct Trivial t); +struct Trivial gen(void); + +// CHECK-LABEL: define dso_local void @test() +void test() { + int x __attribute__((cleanup(cleanup))); + + // CHECK: %[[AGG1:.*]] = alloca %struct.Trivial + // CHECK: %[[AGG2:.*]] = alloca %struct.Trivial + + // CHECK: call void @llvm.lifetime.start.p0(ptr %[[AGG1]]) + // CHECK: invoke void @gen(ptr{{.*}} sret(%struct.Trivial){{.*}} %[[AGG1]]) + + // CHECK: invoke void @func(ptr{{.*}} byval(%struct.Trivial) align 8 %[[AGG1]]) + // CHECK-NEXT: to label %[[CONT1:.*]] unwind label %[[LPAD1:.*]] + + // CHECK: [[CONT1]]: + // CHECK-NOT: call void @llvm.lifetime.end.p0(ptr %[[AGG1]]) + + // CHECK: call void @llvm.lifetime.start.p0(ptr %[[AGG2]]) + // CHECK: invoke void @gen(ptr{{.*}} sret(%struct.Trivial) align 4 %[[AGG2]]) + // CHECK: invoke void @func(ptr{{.*}} byval(%struct.Trivial) align 8 %[[AGG2]]) + // CHECK-NEXT: to label %[[CONT2:.*]] unwind label %[[LPAD2:.*]] + + // CHECK: [[CONT2]]: + // CHECK-DAG: call void @llvm.lifetime.end.p0(ptr %[[AGG2]]) + // CHECK-DAG: call void @llvm.lifetime.end.p0(ptr %[[AGG1]]) + + // CHECK: [[LPAD1]]: + // CHECK: landingpad + // CHECK: br label %[[EHCLEANUP:.*]] + + // CHECK: [[LPAD2]]: + // CHECK: landingpad + // CHECK: call void @llvm.lifetime.end.p0(ptr %[[AGG2]]) + // CHECK: br label %[[EHCLEANUP]] + + // CHECK: [[EHCLEANUP]]: + // CHECK: call void @llvm.lifetime.end.p0(ptr %[[AGG1]]) + // CHECK: call void @cleanup + func(gen()); + func(gen()); +} diff --git a/clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp b/clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp index de8097b39bb1b..e97f9ecd5ad60 100644 --- a/clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp +++ b/clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp @@ -14,7 +14,7 @@ void test() { // CHECK: call void @llvm.lifetime.start.p0(ptr nonnull %[[AGG1]]) // CHECK: invoke void @_Z16func_that_throws7Trivial(ptr noundef nonnull byval(%struct.Trivial) align 8 %[[AGG1]]) // CHECK-NEXT: to label %[[CONT1:.*]] unwind label %[[LPAD1:.*]] - + // CHECK: [[CONT1]]: // CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull %[[AGG2]]) // CHECK: invoke void @_Z16func_that_throws7Trivial(ptr noundef nonnull byval(%struct.Trivial) align 8 %[[AGG2]]) From 888b1ff522b1798682c43f0d048539e7fc42f6b0 Mon Sep 17 00:00:00 2001 From: Paul Kirth Date: Thu, 4 Dec 2025 13:35:53 -0800 Subject: [PATCH 05/10] Ignore unnecessary parts of IR --- .../CodeGenCXX/aggregate-lifetime-invoke.cpp | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp b/clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp index e97f9ecd5ad60..337078cb6b455 100644 --- a/clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp +++ b/clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp @@ -1,4 +1,6 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s -O1 -fexceptions -fcxx-exceptions | FileCheck %s +// RUN: %clang_cc1 -emit-llvm -o - %s -O1 -fexceptions -fcxx-exceptions | FileCheck %s + +extern "C" { struct Trivial { int x[100]; @@ -6,23 +8,23 @@ struct Trivial { void func_that_throws(Trivial t); -// CHECK-LABEL: define dso_local void @_Z4testv(){{.*}} personality ptr @__gxx_personality_v0 +// CHECK-LABEL: define{{.*}} void @test() void test() { - // CHECK: %[[AGG1:.*]] = alloca %struct.Trivial, align - // CHECK: %[[AGG2:.*]] = alloca %struct.Trivial, align + // CHECK: %[[AGG1:.*]] = alloca %struct.Trivial + // CHECK: %[[AGG2:.*]] = alloca %struct.Trivial - // CHECK: call void @llvm.lifetime.start.p0(ptr nonnull %[[AGG1]]) - // CHECK: invoke void @_Z16func_that_throws7Trivial(ptr noundef nonnull byval(%struct.Trivial) align 8 %[[AGG1]]) + // CHECK: call void @llvm.lifetime.start.p0(ptr{{.*}} %[[AGG1]]) + // CHECK: invoke void @func_that_throws(ptr{{.*}} %[[AGG1]]) // CHECK-NEXT: to label %[[CONT1:.*]] unwind label %[[LPAD1:.*]] // CHECK: [[CONT1]]: - // CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull %[[AGG2]]) - // CHECK: invoke void @_Z16func_that_throws7Trivial(ptr noundef nonnull byval(%struct.Trivial) align 8 %[[AGG2]]) + // CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr{{.*}} %[[AGG2]]) + // CHECK: invoke void @func_that_throws(ptr{{.*}} %[[AGG2]]) // CHECK-NEXT: to label %[[CONT2:.*]] unwind label %[[LPAD2:.*]] // CHECK: [[CONT2]]: - // CHECK-DAG: call void @llvm.lifetime.end.p0(ptr nonnull %[[AGG2]]) - // CHECK-DAG: call void @llvm.lifetime.end.p0(ptr nonnull %[[AGG1]]) + // CHECK-DAG: call void @llvm.lifetime.end.p0(ptr{{.*}} %[[AGG2]]) + // CHECK-DAG: call void @llvm.lifetime.end.p0(ptr{{.*}} %[[AGG1]]) // CHECK: br label %[[TRY_CONT:.*]] // CHECK: [[LPAD1]]: @@ -31,15 +33,15 @@ void test() { // CHECK: [[LPAD2]]: // CHECK: landingpad - // CHECK: call void @llvm.lifetime.end.p0(ptr nonnull %[[AGG2]]) + // CHECK: call void @llvm.lifetime.end.p0(ptr{{.*}} %[[AGG2]]) // CHECK: br label %[[EHCLEANUP]] // CHECK: [[EHCLEANUP]]: - // CHECK: call void @llvm.lifetime.end.p0(ptr nonnull %[[AGG1]]) - // CHECK: call ptr @__cxa_begin_catch + // CHECK: call void @llvm.lifetime.end.p0(ptr{{.*}} %[[AGG1]]) try { func_that_throws(Trivial{0}); func_that_throws(Trivial{0}); } catch (...) { } } +} // end extern "C" From 88b92fc32d533d91f0d13e588de2f2f372d59c26 Mon Sep 17 00:00:00 2001 From: Paul Kirth Date: Thu, 4 Dec 2025 14:01:11 -0800 Subject: [PATCH 06/10] Remove unnecessary include --- clang/lib/CodeGen/CGCall.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp index 80075dd8a4cca..35e237c8eedbe 100644 --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -43,7 +43,6 @@ #include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/Intrinsics.h" #include "llvm/IR/Type.h" -#include "llvm/Support/TypeSize.h" #include "llvm/Transforms/Utils/Local.h" #include using namespace clang; From 9dd1333a399a84c3e24f621464ebdd90f6dd0c17 Mon Sep 17 00:00:00 2001 From: Paul Kirth Date: Thu, 4 Dec 2025 14:32:23 -0800 Subject: [PATCH 07/10] Fix over specific attribute checks in test --- clang/test/CodeGen/lifetime-invoke-c.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clang/test/CodeGen/lifetime-invoke-c.c b/clang/test/CodeGen/lifetime-invoke-c.c index a2ab7978cf88d..3a45a7bd65c56 100644 --- a/clang/test/CodeGen/lifetime-invoke-c.c +++ b/clang/test/CodeGen/lifetime-invoke-c.c @@ -18,15 +18,15 @@ void test() { // CHECK: call void @llvm.lifetime.start.p0(ptr %[[AGG1]]) // CHECK: invoke void @gen(ptr{{.*}} sret(%struct.Trivial){{.*}} %[[AGG1]]) - // CHECK: invoke void @func(ptr{{.*}} byval(%struct.Trivial) align 8 %[[AGG1]]) + // CHECK: invoke void @func(ptr{{.*}} %[[AGG1]]) // CHECK-NEXT: to label %[[CONT1:.*]] unwind label %[[LPAD1:.*]] // CHECK: [[CONT1]]: // CHECK-NOT: call void @llvm.lifetime.end.p0(ptr %[[AGG1]]) // CHECK: call void @llvm.lifetime.start.p0(ptr %[[AGG2]]) - // CHECK: invoke void @gen(ptr{{.*}} sret(%struct.Trivial) align 4 %[[AGG2]]) - // CHECK: invoke void @func(ptr{{.*}} byval(%struct.Trivial) align 8 %[[AGG2]]) + // CHECK: invoke void @gen(ptr{{.*}} sret(%struct.Trivial){{.*}} %[[AGG2]]) + // CHECK: invoke void @func(ptr{{.*}} %[[AGG2]]) // CHECK-NEXT: to label %[[CONT2:.*]] unwind label %[[LPAD2:.*]] // CHECK: [[CONT2]]: From c459e4cc9f662a855a436c0f4fa918447d77130e Mon Sep 17 00:00:00 2001 From: Paul Kirth Date: Thu, 4 Dec 2025 14:51:46 -0800 Subject: [PATCH 08/10] Use triple in RUN line --- clang/test/CodeGen/lifetime-invoke-c.c | 2 +- clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/test/CodeGen/lifetime-invoke-c.c b/clang/test/CodeGen/lifetime-invoke-c.c index 3a45a7bd65c56..f3573f4b2ec17 100644 --- a/clang/test/CodeGen/lifetime-invoke-c.c +++ b/clang/test/CodeGen/lifetime-invoke-c.c @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -emit-llvm -o - %s -O1 -disable-llvm-passes -fexceptions | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s -O1 -disable-llvm-passes -fexceptions | FileCheck %s struct Trivial { int x[100]; diff --git a/clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp b/clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp index 337078cb6b455..32af6b51fe9c8 100644 --- a/clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp +++ b/clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -emit-llvm -o - %s -O1 -fexceptions -fcxx-exceptions | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s -O1 -fexceptions -fcxx-exceptions | FileCheck %s extern "C" { From 15c1888da4f4acd9211f26f4828e26a3991824ad Mon Sep 17 00:00:00 2001 From: Paul Kirth Date: Fri, 5 Dec 2025 10:35:55 -0800 Subject: [PATCH 09/10] Use correct CleanupKind --- clang/lib/CodeGen/CGCall.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp index 35e237c8eedbe..84d3229863fe9 100644 --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -4976,7 +4976,8 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E, // expression. if (!CGM.getCodeGenOpts().NoLifetimeMarkersForTemporaries && EmitLifetimeStart(ArgSlotAlloca.getPointer())) - pushFullExprCleanup(NormalAndEHCleanup, ArgSlotAlloca); + pushFullExprCleanup(NormalEHLifetimeMarker, + ArgSlotAlloca); } args.add(EmitAnyExpr(E, ArgSlot), type); From 712682c3a978238f52e3e40c3fad0fbf9ca85e57 Mon Sep 17 00:00:00 2001 From: Paul Kirth Date: Fri, 5 Dec 2025 10:48:23 -0800 Subject: [PATCH 10/10] Remove informational test file, since it relies on optimization --- clang/test/CodeGen/stack-usage-lifetimes.c | 89 ---------------------- 1 file changed, 89 deletions(-) delete mode 100644 clang/test/CodeGen/stack-usage-lifetimes.c diff --git a/clang/test/CodeGen/stack-usage-lifetimes.c b/clang/test/CodeGen/stack-usage-lifetimes.c deleted file mode 100644 index 3787a29e4ce7d..0000000000000 --- a/clang/test/CodeGen/stack-usage-lifetimes.c +++ /dev/null @@ -1,89 +0,0 @@ -// 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@-1 {{40 stack bytes}} - // x86-sloppy-remark@-2 {{72 stack bytes}} - // aarch64-precise-remark@-3 {{48 stack bytes}} - // aarch64-sloppy-remark@-4 {{80 stack bytes}} - // riscv-precise-remark@-5 {{48 stack bytes}} - // riscv-sloppy-remark@-6 {{80 stack bytes}} - - if (c) - useA(genA()); - else - useA(genA()); -} - -void t2(void) { - // x86-precise-remark@-1 {{72 stack bytes}} - // x86-sloppy-remark@-2 {{72 stack bytes}} - // aarch64-precise-remark@-3 {{80 stack bytes}} - // aarch64-sloppy-remark@-4 {{80 stack bytes}} - // riscv-precise-remark@-5 {{80 stack bytes}} - // riscv-sloppy-remark@-6 {{80 stack bytes}} - - useA(genA()); - useA(genA()); -} - -void t3(void) { - // x86-precise-remark@-1 {{72 stack bytes}} - // x86-sloppy-remark@-2 {{72 stack bytes}} - // aarch64-precise-remark@-3 {{80 stack bytes}} - // aarch64-sloppy-remark@-4 {{80 stack bytes}} - // riscv-precise-remark@-5 {{80 stack bytes}} - // riscv-sloppy-remark@-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@-1 {{40 stack bytes}} - // x86-sloppy-remark@-2 {{72 stack bytes}} - // aarch64-precise-remark@-3 {{48 stack bytes}} - // aarch64-sloppy-remark@-4 {{80 stack bytes}} - // riscv-precise-remark@-5 {{48 stack bytes}} - // riscv-sloppy-remark@-6 {{80 stack bytes}} - - useC(genC()); - useC(genC()); -} -#endif