[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
This commit is contained in:
Erik Pilkington 2020-02-07 09:37:15 -08:00
parent 5858c9d69f
commit fafc6e4fdf
4 changed files with 131 additions and 1 deletions

View File

@ -3689,7 +3689,22 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E,
return; return;
} }
args.add(EmitAnyExprToTemp(E), type); AggValueSlot ArgSlot = AggValueSlot::ignored();
if (hasAggregateEvaluationKind(E->getType())) {
ArgSlot = CreateAggTemp(E->getType(), "agg.tmp");
// Emit a lifetime start/end for this temporary. If the type has a
// destructor, then we need to keep it alive. FIXME: We should still be able
// to end the lifetime after the destructor returns.
if (!E->getType().isDestructedType()) {
uint64_t size =
CGM.getDataLayout().getTypeAllocSize(ConvertTypeForMem(E->getType()));
if (auto *lifetimeSize = EmitLifetimeStart(size, ArgSlot.getPointer()))
args.addLifetimeCleanup({ArgSlot.getPointer(), lifetimeSize});
}
}
args.add(EmitAnyExpr(E, ArgSlot), type);
} }
QualType CodeGenFunction::getVarArgType(const Expr *Arg) { QualType CodeGenFunction::getVarArgType(const Expr *Arg) {
@ -4769,6 +4784,9 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
for (CallLifetimeEnd &LifetimeEnd : CallLifetimeEndAfterCall) for (CallLifetimeEnd &LifetimeEnd : CallLifetimeEndAfterCall)
LifetimeEnd.Emit(*this, /*Flags=*/{}); LifetimeEnd.Emit(*this, /*Flags=*/{});
for (auto &LT : CallArgs.getLifetimeCleanups())
EmitLifetimeEnd(LT.Size, LT.Addr);
return Ret; return Ret;
} }

View File

@ -283,6 +283,11 @@ public:
llvm::Instruction *IsActiveIP; llvm::Instruction *IsActiveIP;
}; };
struct EndLifetimeInfo {
llvm::Value *Addr;
llvm::Value *Size;
};
void add(RValue rvalue, QualType type) { push_back(CallArg(rvalue, type)); } void add(RValue rvalue, QualType type) { push_back(CallArg(rvalue, type)); }
void addUncopiedAggregate(LValue LV, QualType type) { void addUncopiedAggregate(LValue LV, QualType type) {
@ -299,6 +304,9 @@ public:
CleanupsToDeactivate.insert(CleanupsToDeactivate.end(), CleanupsToDeactivate.insert(CleanupsToDeactivate.end(),
other.CleanupsToDeactivate.begin(), other.CleanupsToDeactivate.begin(),
other.CleanupsToDeactivate.end()); other.CleanupsToDeactivate.end());
LifetimeCleanups.insert(LifetimeCleanups.end(),
other.LifetimeCleanups.begin(),
other.LifetimeCleanups.end());
assert(!(StackBase && other.StackBase) && "can't merge stackbases"); assert(!(StackBase && other.StackBase) && "can't merge stackbases");
if (!StackBase) if (!StackBase)
StackBase = other.StackBase; StackBase = other.StackBase;
@ -338,6 +346,14 @@ public:
/// memory. /// memory.
bool isUsingInAlloca() const { return StackBase; } bool isUsingInAlloca() const { return StackBase; }
void addLifetimeCleanup(EndLifetimeInfo Info) {
LifetimeCleanups.push_back(Info);
}
ArrayRef<EndLifetimeInfo> getLifetimeCleanups() const {
return LifetimeCleanups;
}
private: private:
SmallVector<Writeback, 1> Writebacks; SmallVector<Writeback, 1> Writebacks;
@ -346,6 +362,10 @@ private:
/// occurs. /// occurs.
SmallVector<CallArgCleanup, 1> CleanupsToDeactivate; 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. /// The stacksave call. It dominates all of the argument evaluation.
llvm::CallInst *StackBase; llvm::CallInst *StackBase;
}; };

View File

@ -0,0 +1,83 @@
// RUN: %clang -cc1 -triple x86_64-apple-macos -O1 -disable-llvm-passes %s -S -emit-llvm -o - | FileCheck %s --implicit-check-not=llvm.lifetime
// 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
// 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
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: [[CAST:%.*]] = bitcast %struct.aggregate* [[AGGTMP]] to i8*
// CHECK: call void @llvm.lifetime.start.p0i8(i64 400, i8* [[CAST]])
// CHECK: call void{{.*}} @gives_aggregate(%struct.aggregate* sret [[AGGTMP]])
// CHECK: call void @takes_aggregate(%struct.aggregate* byval(%struct.aggregate) align 8 [[AGGTMP]])
// CHECK: [[CAST:%.*]] = bitcast %struct.aggregate* [[AGGTMP]] to i8*
// CHECK: call void @llvm.lifetime.end.p0i8(i64 400, i8* [[CAST]])
}
// 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.p0i8(i64 400, i8*
// CXX: call void @gives_aggregate(%struct.aggregate* sret [[AGG]])
// CXX: call void @_ZZ2t2EN1SC1E9aggregate(%struct.S* {{.*}}, %struct.aggregate* byval(%struct.aggregate) align 8 [[AGG]])
// CXX: call void @llvm.lifetime.end.p0i8(i64 400, i8*
}
struct Dtor {
~Dtor();
};
void takes_dtor(Dtor);
Dtor gives_dtor();
// CXX: define void @t3
void t3() {
takes_dtor(gives_dtor());
// CXX-NOT @llvm.lifetime
// 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.p0i8(i64 400, i8*
// OBJC: call void{{.*}} @gives_aggregate(%struct.aggregate* sret [[AGGTMP]])
// OBJC: call {{.*}}@objc_msgSend
// OBJC: call void @llvm.lifetime.end.p0i8(i64 400, i8*
}
#endif
#ifdef __cplusplus
}
#endif

View File

@ -26,6 +26,8 @@ const char * f(S s)
// CHECK: [[T2:%.*]] = alloca %class.T, align 4 // CHECK: [[T2:%.*]] = alloca %class.T, align 4
// CHECK: [[T3:%.*]] = 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 // FIXME: We could defer starting the lifetime of the return object of concat
// until the call. // until the call.
// CHECK: [[T1i8:%.*]] = bitcast %class.T* [[T1]] to i8* // CHECK: [[T1i8:%.*]] = bitcast %class.T* [[T1]] to i8*
@ -37,8 +39,15 @@ const char * f(S s)
// //
// CHECK: [[T3i8:%.*]] = bitcast %class.T* [[T3]] to i8* // CHECK: [[T3i8:%.*]] = bitcast %class.T* [[T3]] to i8*
// CHECK: call void @llvm.lifetime.start.p0i8(i64 16, i8* [[T3i8]]) // CHECK: call void @llvm.lifetime.start.p0i8(i64 16, i8* [[T3i8]])
//
// CHECK: [[AGGi8:%.*]] = bitcast %class.S* [[AGG]] to i8*
// CHECK: call void @llvm.lifetime.start.p0i8(i64 8, i8* [[AGGi8]])
//
// CHECK: [[T5:%.*]] = call %class.T* @_ZN1TC1E1S(%class.T* [[T3]], [2 x i32] %{{.*}}) // CHECK: [[T5:%.*]] = call %class.T* @_ZN1TC1E1S(%class.T* [[T3]], [2 x i32] %{{.*}})
// //
// CHECK: [[AGGi8:%.*]] = bitcast %class.S* {{.*}} to i8*
// CHECK: call void @llvm.lifetime.end.p0i8(i64 8, i8* [[AGGi8]])
//
// CHECK: call void @_ZNK1T6concatERKS_(%class.T* sret [[T1]], %class.T* [[T2]], %class.T* dereferenceable(16) [[T3]]) // CHECK: call void @_ZNK1T6concatERKS_(%class.T* sret [[T1]], %class.T* [[T2]], %class.T* dereferenceable(16) [[T3]])
// CHECK: [[T6:%.*]] = call i8* @_ZNK1T3strEv(%class.T* [[T1]]) // CHECK: [[T6:%.*]] = call i8* @_ZNK1T3strEv(%class.T* [[T1]])
// //