Skip to content

Commit

Permalink
[IRGen] Emit lifetime intrinsics around temporary aggregate argument …
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
epilk committed Feb 7, 2020
1 parent 5858c9d commit fafc6e4
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 1 deletion.
20 changes: 19 additions & 1 deletion clang/lib/CodeGen/CGCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -4769,6 +4784,9 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
for (CallLifetimeEnd &LifetimeEnd : CallLifetimeEndAfterCall)
LifetimeEnd.Emit(*this, /*Flags=*/{});

for (auto &LT : CallArgs.getLifetimeCleanups())
EmitLifetimeEnd(LT.Size, LT.Addr);

return Ret;
}

Expand Down
20 changes: 20 additions & 0 deletions clang/lib/CodeGen/CGCall.h
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,11 @@ class CallArgList : public SmallVector<CallArg, 8> {
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) {
Expand All @@ -299,6 +304,9 @@ class CallArgList : public SmallVector<CallArg, 8> {
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;
Expand Down Expand Up @@ -338,6 +346,14 @@ class CallArgList : public SmallVector<CallArg, 8> {
/// 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;

Expand All @@ -346,6 +362,10 @@ class CallArgList : public SmallVector<CallArg, 8> {
/// 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;
};
Expand Down
83 changes: 83 additions & 0 deletions clang/test/CodeGen/lifetime-call-temp.c
Original file line number Diff line number Diff line change
@@ -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
9 changes: 9 additions & 0 deletions clang/test/CodeGenCXX/stack-reuse-miscompile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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*
Expand All @@ -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]])
//
Expand Down

0 comments on commit fafc6e4

Please sign in to comment.