Skip to content

Commit fafc6e4

Browse files
committedFeb 7, 2020
[IRGen] Emit lifetime intrinsics around temporary aggregate argument allocas
These temporaries are only used in the callee, and their memory can be reused after the call is complete. rdar://58552124 Differential revision: https://reviews.llvm.org/D74094
1 parent 5858c9d commit fafc6e4

File tree

4 files changed

+131
-1
lines changed

4 files changed

+131
-1
lines changed
 

‎clang/lib/CodeGen/CGCall.cpp

+19-1
Original file line numberDiff line numberDiff line change
@@ -3689,7 +3689,22 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E,
36893689
return;
36903690
}
36913691

3692-
args.add(EmitAnyExprToTemp(E), type);
3692+
AggValueSlot ArgSlot = AggValueSlot::ignored();
3693+
if (hasAggregateEvaluationKind(E->getType())) {
3694+
ArgSlot = CreateAggTemp(E->getType(), "agg.tmp");
3695+
3696+
// Emit a lifetime start/end for this temporary. If the type has a
3697+
// destructor, then we need to keep it alive. FIXME: We should still be able
3698+
// to end the lifetime after the destructor returns.
3699+
if (!E->getType().isDestructedType()) {
3700+
uint64_t size =
3701+
CGM.getDataLayout().getTypeAllocSize(ConvertTypeForMem(E->getType()));
3702+
if (auto *lifetimeSize = EmitLifetimeStart(size, ArgSlot.getPointer()))
3703+
args.addLifetimeCleanup({ArgSlot.getPointer(), lifetimeSize});
3704+
}
3705+
}
3706+
3707+
args.add(EmitAnyExpr(E, ArgSlot), type);
36933708
}
36943709

36953710
QualType CodeGenFunction::getVarArgType(const Expr *Arg) {
@@ -4769,6 +4784,9 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
47694784
for (CallLifetimeEnd &LifetimeEnd : CallLifetimeEndAfterCall)
47704785
LifetimeEnd.Emit(*this, /*Flags=*/{});
47714786

4787+
for (auto &LT : CallArgs.getLifetimeCleanups())
4788+
EmitLifetimeEnd(LT.Size, LT.Addr);
4789+
47724790
return Ret;
47734791
}
47744792

‎clang/lib/CodeGen/CGCall.h

+20
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,11 @@ class CallArgList : public SmallVector<CallArg, 8> {
283283
llvm::Instruction *IsActiveIP;
284284
};
285285

286+
struct EndLifetimeInfo {
287+
llvm::Value *Addr;
288+
llvm::Value *Size;
289+
};
290+
286291
void add(RValue rvalue, QualType type) { push_back(CallArg(rvalue, type)); }
287292

288293
void addUncopiedAggregate(LValue LV, QualType type) {
@@ -299,6 +304,9 @@ class CallArgList : public SmallVector<CallArg, 8> {
299304
CleanupsToDeactivate.insert(CleanupsToDeactivate.end(),
300305
other.CleanupsToDeactivate.begin(),
301306
other.CleanupsToDeactivate.end());
307+
LifetimeCleanups.insert(LifetimeCleanups.end(),
308+
other.LifetimeCleanups.begin(),
309+
other.LifetimeCleanups.end());
302310
assert(!(StackBase && other.StackBase) && "can't merge stackbases");
303311
if (!StackBase)
304312
StackBase = other.StackBase;
@@ -338,6 +346,14 @@ class CallArgList : public SmallVector<CallArg, 8> {
338346
/// memory.
339347
bool isUsingInAlloca() const { return StackBase; }
340348

349+
void addLifetimeCleanup(EndLifetimeInfo Info) {
350+
LifetimeCleanups.push_back(Info);
351+
}
352+
353+
ArrayRef<EndLifetimeInfo> getLifetimeCleanups() const {
354+
return LifetimeCleanups;
355+
}
356+
341357
private:
342358
SmallVector<Writeback, 1> Writebacks;
343359

@@ -346,6 +362,10 @@ class CallArgList : public SmallVector<CallArg, 8> {
346362
/// occurs.
347363
SmallVector<CallArgCleanup, 1> CleanupsToDeactivate;
348364

365+
/// Lifetime information needed to call llvm.lifetime.end for any temporary
366+
/// argument allocas.
367+
SmallVector<EndLifetimeInfo, 2> LifetimeCleanups;
368+
349369
/// The stacksave call. It dominates all of the argument evaluation.
350370
llvm::CallInst *StackBase;
351371
};
+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// RUN: %clang -cc1 -triple x86_64-apple-macos -O1 -disable-llvm-passes %s -S -emit-llvm -o - | FileCheck %s --implicit-check-not=llvm.lifetime
2+
// RUN: %clang -cc1 -xc++ -std=c++17 -triple x86_64-apple-macos -O1 -disable-llvm-passes %s -S -emit-llvm -o - | FileCheck %s --implicit-check-not=llvm.lifetime --check-prefix=CHECK --check-prefix=CXX
3+
// RUN: %clang -cc1 -xobjective-c -triple x86_64-apple-macos -O1 -disable-llvm-passes %s -S -emit-llvm -o - | FileCheck %s --implicit-check-not=llvm.lifetime --check-prefix=CHECK --check-prefix=OBJC
4+
5+
typedef struct { int x[100]; } aggregate;
6+
7+
#ifdef __cplusplus
8+
extern "C" {
9+
#endif
10+
11+
void takes_aggregate(aggregate);
12+
aggregate gives_aggregate();
13+
14+
// CHECK-LABEL: define void @t1
15+
void t1() {
16+
takes_aggregate(gives_aggregate());
17+
18+
// CHECK: [[AGGTMP:%.*]] = alloca %struct.aggregate, align 8
19+
// CHECK: [[CAST:%.*]] = bitcast %struct.aggregate* [[AGGTMP]] to i8*
20+
// CHECK: call void @llvm.lifetime.start.p0i8(i64 400, i8* [[CAST]])
21+
// CHECK: call void{{.*}} @gives_aggregate(%struct.aggregate* sret [[AGGTMP]])
22+
// CHECK: call void @takes_aggregate(%struct.aggregate* byval(%struct.aggregate) align 8 [[AGGTMP]])
23+
// CHECK: [[CAST:%.*]] = bitcast %struct.aggregate* [[AGGTMP]] to i8*
24+
// CHECK: call void @llvm.lifetime.end.p0i8(i64 400, i8* [[CAST]])
25+
}
26+
27+
// CHECK: declare {{.*}}llvm.lifetime.start
28+
// CHECK: declare {{.*}}llvm.lifetime.end
29+
30+
#ifdef __cplusplus
31+
// CXX: define void @t2
32+
void t2() {
33+
struct S {
34+
S(aggregate) {}
35+
};
36+
S{gives_aggregate()};
37+
38+
// CXX: [[AGG:%.*]] = alloca %struct.aggregate
39+
// CXX: call void @llvm.lifetime.start.p0i8(i64 400, i8*
40+
// CXX: call void @gives_aggregate(%struct.aggregate* sret [[AGG]])
41+
// CXX: call void @_ZZ2t2EN1SC1E9aggregate(%struct.S* {{.*}}, %struct.aggregate* byval(%struct.aggregate) align 8 [[AGG]])
42+
// CXX: call void @llvm.lifetime.end.p0i8(i64 400, i8*
43+
}
44+
45+
struct Dtor {
46+
~Dtor();
47+
};
48+
49+
void takes_dtor(Dtor);
50+
Dtor gives_dtor();
51+
52+
// CXX: define void @t3
53+
void t3() {
54+
takes_dtor(gives_dtor());
55+
56+
// CXX-NOT @llvm.lifetime
57+
// CXX: ret void
58+
}
59+
60+
#endif
61+
62+
#ifdef __OBJC__
63+
64+
@interface X
65+
-m:(aggregate)x;
66+
@end
67+
68+
// OBJC: define void @t4
69+
void t4(X *x) {
70+
[x m: gives_aggregate()];
71+
72+
// OBJC: [[AGG:%.*]] = alloca %struct.aggregate
73+
// OBJC: call void @llvm.lifetime.start.p0i8(i64 400, i8*
74+
// OBJC: call void{{.*}} @gives_aggregate(%struct.aggregate* sret [[AGGTMP]])
75+
// OBJC: call {{.*}}@objc_msgSend
76+
// OBJC: call void @llvm.lifetime.end.p0i8(i64 400, i8*
77+
}
78+
79+
#endif
80+
81+
#ifdef __cplusplus
82+
}
83+
#endif

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

+9
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: [[T1i8:%.*]] = bitcast %class.T* [[T1]] to i8*
@@ -37,8 +39,15 @@ const char * f(S s)
3739
//
3840
// CHECK: [[T3i8:%.*]] = bitcast %class.T* [[T3]] to i8*
3941
// CHECK: call void @llvm.lifetime.start.p0i8(i64 16, i8* [[T3i8]])
42+
//
43+
// CHECK: [[AGGi8:%.*]] = bitcast %class.S* [[AGG]] to i8*
44+
// CHECK: call void @llvm.lifetime.start.p0i8(i64 8, i8* [[AGGi8]])
45+
//
4046
// CHECK: [[T5:%.*]] = call %class.T* @_ZN1TC1E1S(%class.T* [[T3]], [2 x i32] %{{.*}})
4147
//
48+
// CHECK: [[AGGi8:%.*]] = bitcast %class.S* {{.*}} to i8*
49+
// CHECK: call void @llvm.lifetime.end.p0i8(i64 8, i8* [[AGGi8]])
50+
//
4251
// CHECK: call void @_ZNK1T6concatERKS_(%class.T* sret [[T1]], %class.T* [[T2]], %class.T* dereferenceable(16) [[T3]])
4352
// CHECK: [[T6:%.*]] = call i8* @_ZNK1T3strEv(%class.T* [[T1]])
4453
//

0 commit comments

Comments
 (0)
Please sign in to comment.