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;
|
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 < : CallArgs.getLifetimeCleanups())
|
||||||
|
EmitLifetimeEnd(LT.Size, LT.Addr);
|
||||||
|
|
||||||
return Ret;
|
return Ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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: [[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]])
|
||||||
//
|
//
|
||||||
|
|
Loading…
Reference in New Issue