forked from OSchip/llvm-project
[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:
parent
5858c9d69f
commit
fafc6e4fdf
|
@ -3689,7 +3689,22 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E,
|
|||
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) {
|
||||
|
@ -4769,6 +4784,9 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
|
|||
for (CallLifetimeEnd &LifetimeEnd : CallLifetimeEndAfterCall)
|
||||
LifetimeEnd.Emit(*this, /*Flags=*/{});
|
||||
|
||||
for (auto < : CallArgs.getLifetimeCleanups())
|
||||
EmitLifetimeEnd(LT.Size, LT.Addr);
|
||||
|
||||
return Ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -283,6 +283,11 @@ public:
|
|||
llvm::Instruction *IsActiveIP;
|
||||
};
|
||||
|
||||
struct EndLifetimeInfo {
|
||||
llvm::Value *Addr;
|
||||
llvm::Value *Size;
|
||||
};
|
||||
|
||||
void add(RValue rvalue, QualType type) { push_back(CallArg(rvalue, type)); }
|
||||
|
||||
void addUncopiedAggregate(LValue LV, QualType type) {
|
||||
|
@ -299,6 +304,9 @@ public:
|
|||
CleanupsToDeactivate.insert(CleanupsToDeactivate.end(),
|
||||
other.CleanupsToDeactivate.begin(),
|
||||
other.CleanupsToDeactivate.end());
|
||||
LifetimeCleanups.insert(LifetimeCleanups.end(),
|
||||
other.LifetimeCleanups.begin(),
|
||||
other.LifetimeCleanups.end());
|
||||
assert(!(StackBase && other.StackBase) && "can't merge stackbases");
|
||||
if (!StackBase)
|
||||
StackBase = other.StackBase;
|
||||
|
@ -338,6 +346,14 @@ public:
|
|||
/// memory.
|
||||
bool isUsingInAlloca() const { return StackBase; }
|
||||
|
||||
void addLifetimeCleanup(EndLifetimeInfo Info) {
|
||||
LifetimeCleanups.push_back(Info);
|
||||
}
|
||||
|
||||
ArrayRef<EndLifetimeInfo> getLifetimeCleanups() const {
|
||||
return LifetimeCleanups;
|
||||
}
|
||||
|
||||
private:
|
||||
SmallVector<Writeback, 1> Writebacks;
|
||||
|
||||
|
@ -346,6 +362,10 @@ private:
|
|||
/// 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;
|
||||
};
|
||||
|
|
|
@ -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
|
|
@ -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: [[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: 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: [[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: [[T6:%.*]] = call i8* @_ZNK1T3strEv(%class.T* [[T1]])
|
||||
//
|
||||
|
|
Loading…
Reference in New Issue