Skip to content

Commit 5daad5b

Browse files
ilovepinickdesaulniersepilk
authored
[clang] Limit lifetimes of temporaries to the full expression (#170517)
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 <[email protected]> Co-authored-by: Erik Pilkington <[email protected]> --------- Co-authored-by: Nick Desaulniers <[email protected]> Co-authored-by: Erik Pilkington <[email protected]>
1 parent bb17dfa commit 5daad5b

File tree

10 files changed

+261
-1
lines changed

10 files changed

+261
-1
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,15 @@ Potentially Breaking Changes
8686
options-related code has been moved out of the Driver into a separate library.
8787
- The ``clangFrontend`` library no longer depends on ``clangDriver``, which may
8888
break downstream projects that relied on this transitive dependency.
89+
- Clang is now more precise with regards to the lifetime of temporary objects
90+
such as when aggregates are passed by value to a function, resulting in
91+
better sharing of stack slots and reduced stack usage. This change can lead
92+
to use-after-scope related issues in code that unintentionally relied on the
93+
previous behavior. If recompiling with ``-fsanitize=address`` shows a
94+
use-after-scope warning, then this is likely the case, and the report printed
95+
should be able to help users pinpoint where the use-after-scope is occurring.
96+
Users can use ``-Xclang -sloppy-temporary-lifetimes`` to retain the old
97+
behavior until they are able to find and resolve issues in their code.
8998

9099
C/C++ Language Potentially Breaking Changes
91100
-------------------------------------------

clang/include/clang/Basic/CodeGenOptions.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,10 @@ ENUM_CODEGENOPT(ZeroCallUsedRegs, ZeroCallUsedRegsKind,
475475
/// non-deleting destructors. (No effect on Microsoft ABI.)
476476
CODEGENOPT(CtorDtorReturnThis, 1, 0, Benign)
477477

478+
/// Set via -Xclang -sloppy-temporary-lifetimes to disable emission of lifetime
479+
/// marker intrinsic calls.
480+
CODEGENOPT(NoLifetimeMarkersForTemporaries, 1, 0, Benign)
481+
478482
/// Enables emitting Import Call sections on supported targets that can be used
479483
/// by the Windows kernel to enable import call optimization.
480484
CODEGENOPT(ImportCallOptimization, 1, 0, Benign)

clang/include/clang/Options/Options.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8151,6 +8151,11 @@ def import_call_optimization : Flag<["-"], "import-call-optimization">,
81518151
def replaceable_function: Joined<["-"], "loader-replaceable-function=">,
81528152
MarshallingInfoStringVector<CodeGenOpts<"LoaderReplaceableFunctionNames">>;
81538153

8154+
def sloppy_temporary_lifetimes
8155+
: Flag<["-"], "sloppy-temporary-lifetimes">,
8156+
HelpText<"Don't emit lifetime markers for temporary objects">,
8157+
MarshallingInfoFlag<CodeGenOpts<"NoLifetimeMarkersForTemporaries">>;
8158+
81548159
} // let Visibility = [CC1Option]
81558160

81568161
//===----------------------------------------------------------------------===//

clang/lib/CodeGen/CGCall.cpp

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4960,7 +4960,27 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E,
49604960
return;
49614961
}
49624962

4963-
args.add(EmitAnyExprToTemp(E), type);
4963+
AggValueSlot ArgSlot = AggValueSlot::ignored();
4964+
// For arguments with aggregate type, create an alloca to store
4965+
// the value. If the argument's type has a destructor, that destructor
4966+
// will run at the end of the full-expression; emit matching lifetime
4967+
// markers.
4968+
//
4969+
// FIXME: For types which don't have a destructor, consider using a
4970+
// narrower lifetime bound.
4971+
if (hasAggregateEvaluationKind(E->getType())) {
4972+
RawAddress ArgSlotAlloca = Address::invalid();
4973+
ArgSlot = CreateAggTemp(E->getType(), "agg.tmp", &ArgSlotAlloca);
4974+
4975+
// Emit a lifetime start/end for this temporary at the end of the full
4976+
// expression.
4977+
if (!CGM.getCodeGenOpts().NoLifetimeMarkersForTemporaries &&
4978+
EmitLifetimeStart(ArgSlotAlloca.getPointer()))
4979+
pushFullExprCleanup<CallLifetimeEnd>(NormalEHLifetimeMarker,
4980+
ArgSlotAlloca);
4981+
}
4982+
4983+
args.add(EmitAnyExpr(E, ArgSlot), type);
49644984
}
49654985

49664986
QualType CodeGenFunction::getVarArgType(const Expr *Arg) {
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// RUN: %clang -cc1 -triple x86_64-apple-macos -O1 -disable-llvm-passes %s \
2+
// RUN: -emit-llvm -o - | FileCheck %s --implicit-check-not=llvm.lifetime
3+
// RUN: %clang -cc1 -xc++ -std=c++17 -triple x86_64-apple-macos -O1 \
4+
// RUN: -disable-llvm-passes %s -emit-llvm -o - -Wno-return-type-c-linkage | \
5+
// RUN: FileCheck %s --implicit-check-not=llvm.lifetime --check-prefixes=CHECK,CXX
6+
// RUN: %clang -cc1 -xobjective-c -triple x86_64-apple-macos -O1 \
7+
// RUN: -disable-llvm-passes %s -emit-llvm -o - | \
8+
// RUN: FileCheck %s --implicit-check-not=llvm.lifetime --check-prefixes=CHECK,OBJC
9+
// RUN: %clang -cc1 -triple x86_64-apple-macos -O1 -disable-llvm-passes %s \
10+
// RUN: -emit-llvm -o - -sloppy-temporary-lifetimes | \
11+
// RUN: FileCheck %s --implicit-check-not=llvm.lifetime --check-prefixes=SLOPPY
12+
13+
typedef struct { int x[100]; } aggregate;
14+
15+
#ifdef __cplusplus
16+
extern "C" {
17+
#endif
18+
19+
void takes_aggregate(aggregate);
20+
aggregate gives_aggregate();
21+
22+
// CHECK-LABEL: define void @t1
23+
void t1() {
24+
takes_aggregate(gives_aggregate());
25+
26+
// CHECK: [[AGGTMP:%.*]] = alloca %struct.aggregate, align 8
27+
// CHECK: call void @llvm.lifetime.start.p0(ptr [[AGGTMP]])
28+
// CHECK: call void{{.*}} @gives_aggregate(ptr{{.*}}sret(%struct.aggregate) align 4 [[AGGTMP]])
29+
// CHECK: call void @takes_aggregate(ptr noundef byval(%struct.aggregate) align 8 [[AGGTMP]])
30+
// CHECK: call void @llvm.lifetime.end.p0(ptr [[AGGTMP]])
31+
32+
// SLOPPY: [[AGGTMP:%.*]] = alloca %struct.aggregate, align 8
33+
// SLOPPY-NEXT: call void (ptr, ...) @gives_aggregate(ptr{{.*}}sret(%struct.aggregate) align 4 [[AGGTMP]])
34+
// SLOPPY-NEXT: call void @takes_aggregate(ptr noundef byval(%struct.aggregate) align 8 [[AGGTMP]])
35+
}
36+
37+
// CHECK: declare {{.*}}llvm.lifetime.start
38+
// CHECK: declare {{.*}}llvm.lifetime.end
39+
40+
#ifdef __cplusplus
41+
// CXX: define void @t2
42+
void t2() {
43+
struct S {
44+
S(aggregate) {}
45+
};
46+
S{gives_aggregate()};
47+
48+
// CXX: [[AGG:%.*]] = alloca %struct.aggregate
49+
// CXX: call void @llvm.lifetime.start.p0(ptr [[AGG]]
50+
// CXX: call void @gives_aggregate(ptr{{.*}}sret(%struct.aggregate) align 4 [[AGG]])
51+
// CXX: call void @_ZZ2t2EN1SC1E9aggregate(ptr {{.*}}, ptr {{.*}} byval(%struct.aggregate) align 8 [[AGG]])
52+
// CXX: call void @llvm.lifetime.end.p0(ptr [[AGG]]
53+
}
54+
55+
struct Dtor {
56+
~Dtor();
57+
};
58+
59+
void takes_dtor(Dtor);
60+
Dtor gives_dtor();
61+
62+
// CXX: define void @t3
63+
void t3() {
64+
takes_dtor(gives_dtor());
65+
66+
// CXX: [[AGG:%.*]] = alloca %struct.Dtor
67+
// CXX: call void @llvm.lifetime.start.p0(ptr [[AGG]])
68+
// CXX: call void @gives_dtor(ptr{{.*}}sret(%struct.Dtor) align 1 [[AGG]])
69+
// CXX: call void @takes_dtor(ptr noundef [[AGG]])
70+
// CXX: call void @_ZN4DtorD1Ev(ptr {{.*}} [[AGG]])
71+
// CXX: call void @llvm.lifetime.end.p0(ptr [[AGG]])
72+
// CXX: ret void
73+
}
74+
75+
#endif
76+
77+
#ifdef __OBJC__
78+
79+
@interface X
80+
-m:(aggregate)x;
81+
@end
82+
83+
// OBJC: define void @t4
84+
void t4(X *x) {
85+
[x m: gives_aggregate()];
86+
87+
// OBJC: [[AGG:%.*]] = alloca %struct.aggregate
88+
// OBJC: call void @llvm.lifetime.start.p0(ptr [[AGG]]
89+
// OBJC: call void{{.*}} @gives_aggregate(ptr{{.*}}sret(%struct.aggregate) align 4 [[AGGTMP]])
90+
// OBJC: call {{.*}}@objc_msgSend
91+
// OBJC: call void @llvm.lifetime.end.p0(ptr [[AGG]]
92+
}
93+
94+
#endif
95+
96+
#ifdef __cplusplus
97+
}
98+
#endif
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s -O1 -disable-llvm-passes -fexceptions | FileCheck %s
2+
3+
struct Trivial {
4+
int x[100];
5+
};
6+
7+
void cleanup(int *p) {}
8+
void func(struct Trivial t);
9+
struct Trivial gen(void);
10+
11+
// CHECK-LABEL: define dso_local void @test()
12+
void test() {
13+
int x __attribute__((cleanup(cleanup)));
14+
15+
// CHECK: %[[AGG1:.*]] = alloca %struct.Trivial
16+
// CHECK: %[[AGG2:.*]] = alloca %struct.Trivial
17+
18+
// CHECK: call void @llvm.lifetime.start.p0(ptr %[[AGG1]])
19+
// CHECK: invoke void @gen(ptr{{.*}} sret(%struct.Trivial){{.*}} %[[AGG1]])
20+
21+
// CHECK: invoke void @func(ptr{{.*}} %[[AGG1]])
22+
// CHECK-NEXT: to label %[[CONT1:.*]] unwind label %[[LPAD1:.*]]
23+
24+
// CHECK: [[CONT1]]:
25+
// CHECK-NOT: call void @llvm.lifetime.end.p0(ptr %[[AGG1]])
26+
27+
// CHECK: call void @llvm.lifetime.start.p0(ptr %[[AGG2]])
28+
// CHECK: invoke void @gen(ptr{{.*}} sret(%struct.Trivial){{.*}} %[[AGG2]])
29+
// CHECK: invoke void @func(ptr{{.*}} %[[AGG2]])
30+
// CHECK-NEXT: to label %[[CONT2:.*]] unwind label %[[LPAD2:.*]]
31+
32+
// CHECK: [[CONT2]]:
33+
// CHECK-DAG: call void @llvm.lifetime.end.p0(ptr %[[AGG2]])
34+
// CHECK-DAG: call void @llvm.lifetime.end.p0(ptr %[[AGG1]])
35+
36+
// CHECK: [[LPAD1]]:
37+
// CHECK: landingpad
38+
// CHECK: br label %[[EHCLEANUP:.*]]
39+
40+
// CHECK: [[LPAD2]]:
41+
// CHECK: landingpad
42+
// CHECK: call void @llvm.lifetime.end.p0(ptr %[[AGG2]])
43+
// CHECK: br label %[[EHCLEANUP]]
44+
45+
// CHECK: [[EHCLEANUP]]:
46+
// CHECK: call void @llvm.lifetime.end.p0(ptr %[[AGG1]])
47+
// CHECK: call void @cleanup
48+
func(gen());
49+
func(gen());
50+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s -O1 -fexceptions -fcxx-exceptions | FileCheck %s
2+
3+
extern "C" {
4+
5+
struct Trivial {
6+
int x[100];
7+
};
8+
9+
void func_that_throws(Trivial t);
10+
11+
// CHECK-LABEL: define{{.*}} void @test()
12+
void test() {
13+
// CHECK: %[[AGG1:.*]] = alloca %struct.Trivial
14+
// CHECK: %[[AGG2:.*]] = alloca %struct.Trivial
15+
16+
// CHECK: call void @llvm.lifetime.start.p0(ptr{{.*}} %[[AGG1]])
17+
// CHECK: invoke void @func_that_throws(ptr{{.*}} %[[AGG1]])
18+
// CHECK-NEXT: to label %[[CONT1:.*]] unwind label %[[LPAD1:.*]]
19+
20+
// CHECK: [[CONT1]]:
21+
// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr{{.*}} %[[AGG2]])
22+
// CHECK: invoke void @func_that_throws(ptr{{.*}} %[[AGG2]])
23+
// CHECK-NEXT: to label %[[CONT2:.*]] unwind label %[[LPAD2:.*]]
24+
25+
// CHECK: [[CONT2]]:
26+
// CHECK-DAG: call void @llvm.lifetime.end.p0(ptr{{.*}} %[[AGG2]])
27+
// CHECK-DAG: call void @llvm.lifetime.end.p0(ptr{{.*}} %[[AGG1]])
28+
// CHECK: br label %[[TRY_CONT:.*]]
29+
30+
// CHECK: [[LPAD1]]:
31+
// CHECK: landingpad
32+
// CHECK: br label %[[EHCLEANUP:.*]]
33+
34+
// CHECK: [[LPAD2]]:
35+
// CHECK: landingpad
36+
// CHECK: call void @llvm.lifetime.end.p0(ptr{{.*}} %[[AGG2]])
37+
// CHECK: br label %[[EHCLEANUP]]
38+
39+
// CHECK: [[EHCLEANUP]]:
40+
// CHECK: call void @llvm.lifetime.end.p0(ptr{{.*}} %[[AGG1]])
41+
try {
42+
func_that_throws(Trivial{0});
43+
func_that_throws(Trivial{0});
44+
} catch (...) {
45+
}
46+
}
47+
} // end extern "C"
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// RUN: %clang_cc1 -triple amdgcn-amd-amdhsa -emit-llvm -O3 -disable-llvm-passes -o - %s | FileCheck %s
2+
3+
struct A {
4+
float x, y, z, w;
5+
};
6+
7+
void foo(A a);
8+
9+
// CHECK-LABEL: @_Z4testv
10+
// CHECK: [[A:%.*]] = alloca [[STRUCT_A:%.*]], align 4, addrspace(5)
11+
// CHECK-NEXT: [[AGG_TMP:%.*]] = alloca [[STRUCT_A]], align 4, addrspace(5)
12+
// CHECK-NEXT: [[A_ASCAST:%.*]] = addrspacecast ptr addrspace(5) [[A]] to ptr
13+
// CHECK-NEXT: [[AGG_TMP_ASCAST:%.*]] = addrspacecast ptr addrspace(5) [[AGG_TMP]] to ptr
14+
// CHECK-NEXT: call void @llvm.lifetime.start.p5(i64 16, ptr addrspace(5) [[A]]) #[[ATTR4:[0-9]+]]
15+
// CHECK-NEXT: call void @llvm.lifetime.start.p5(i64 16, ptr addrspace(5) [[AGG_TMP]]) #[[ATTR4]]
16+
void test() {
17+
A a;
18+
foo(a);
19+
}

clang/test/CodeGenCXX/stack-reuse-miscompile.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ const char * f(S s)
2626
// CHECK: [[T2:%.*]] = alloca %class.T, align 4
2727
// CHECK: [[T3:%.*]] = alloca %class.T, align 4
2828
//
29+
// CHECK: [[AGG:%.*]] = alloca %class.S, align 4
30+
//
2931
// FIXME: We could defer starting the lifetime of the return object of concat
3032
// until the call.
3133
// CHECK: call void @llvm.lifetime.start.p0(ptr [[T1]])
@@ -34,10 +36,12 @@ const char * f(S s)
3436
// CHECK: [[T4:%.*]] = call noundef ptr @_ZN1TC1EPKc(ptr {{[^,]*}} [[T2]], ptr noundef @.str)
3537
//
3638
// CHECK: call void @llvm.lifetime.start.p0(ptr [[T3]])
39+
// CHECK: call void @llvm.lifetime.start.p0(ptr [[AGG]])
3740
// CHECK: [[T5:%.*]] = call noundef ptr @_ZN1TC1E1S(ptr {{[^,]*}} [[T3]], [2 x i32] %{{.*}})
3841
//
3942
// 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]])
4043
// CHECK: [[T6:%.*]] = call noundef ptr @_ZNK1T3strEv(ptr {{[^,]*}} [[T1]])
44+
// CHECK: call void @llvm.lifetime.end.p0(ptr [[AGG]])
4145
//
4246
// CHECK: call void @llvm.lifetime.end.p0(
4347
// CHECK: call void @llvm.lifetime.end.p0(

clang/test/CodeGenCoroutines/pr59181.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ void foo() {
4949
}
5050

5151
// CHECK: cleanup.cont:{{.*}}
52+
// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[AGG:%agg.tmp]])
5253
// CHECK-NEXT: load i8
5354
// CHECK-NEXT: trunc
5455
// CHECK-NEXT: store i1 false
@@ -57,3 +58,6 @@ void foo() {
5758
// CHECK-NOT: call void @llvm.lifetime
5859
// CHECK: call void @llvm.coro.await.suspend.void(
5960
// CHECK-NEXT: %{{[0-9]+}} = call i8 @llvm.coro.suspend(
61+
62+
// CHECK-LABEL: cond.end:
63+
// check call @llvm.lifetime.end.p0(ptr [[AGG]])

0 commit comments

Comments
 (0)