Add an optimization to eliminate a partial_apply if all applied arguments are dead in the applied function.

This consists of 3 parts:
1) Extend CallerAnalysis to also provide information if a function is partially applied
2) A new DeadArgSignatureOpt pass, similar to FunctionSignatureOpts, which just specializes for dead arguments of partially applied functions.
3) Let CapturePropagation eliminate such partial_apply instructions and replace them with a thin_to_thick conversion of the specialized functions.

This optimzation improves benchmarks where static struct or class functions are passed as a closure (e.g. -20% for SortStrings).
Such functions have a additional metatype parameter. We used to create a partial_apply in this case, which allocates a context, etc.
But this is not necessary as the metatype parameter is not used in most cases.

rdar://problem/27513085
This commit is contained in:
Erik Eckstein
2016-08-18 22:25:42 -07:00
parent f86f0253d1
commit 959e19d7bc
12 changed files with 662 additions and 83 deletions

View File

@@ -97,9 +97,6 @@ class FunctionSignatureTransform {
/// The newly created function.
SILFunction *NewF;
/// The pass manager we are using.
SILPassManager *PM;
/// The alias analysis we are using.
AliasAnalysis *AA;
@@ -123,9 +120,6 @@ class FunctionSignatureTransform {
/// will use during our optimization.
llvm::SmallVector<ResultDescriptor, 4> &ResultDescList;
/// Does this function have a caller inside current module
bool hasCaller;
/// Return a function name based on ArgumentDescList and ResultDescList.
std::string createOptimizedSILFunctionName();
@@ -224,26 +218,32 @@ private:
public:
/// Constructor.
FunctionSignatureTransform(SILFunction *F, bool hasCaller, SILPassManager *PM,
FunctionSignatureTransform(SILFunction *F,
AliasAnalysis *AA, RCIdentityAnalysis *RCIA,
FunctionSignatureSpecializationMangler &FM,
ArgumentIndexMap &AIM,
llvm::SmallVector<ArgumentDescriptor, 4> &ADL,
llvm::SmallVector<ResultDescriptor, 4> &RDL)
: F(F), NewF(nullptr), PM(PM), AA(AA), RCIA(RCIA), FM(FM),
: F(F), NewF(nullptr), AA(AA), RCIA(RCIA), FM(FM),
AIM(AIM), shouldModifySelfArgument(false), ArgumentDescList(ADL),
ResultDescList(RDL),
hasCaller(hasCaller) {}
ResultDescList(RDL) {}
/// Return the optimized function.
SILFunction *getOptimizedFunction() { return NewF; }
/// Run the optimization.
bool run() {
bool run(bool hasCaller) {
bool Changed = false;
if (!hasCaller && canBeCalledIndirectly(F->getRepresentation())) {
DEBUG(llvm::dbgs() << " function has no caller -> abort\n");
return false;
}
// Run OwnedToGuaranteed optimization.
if (OwnedToGuaranteedAnalyze()) {
Changed = true;
DEBUG(llvm::dbgs() << " transform owned-to-guaranteed\n");
OwnedToGuaranteedTransform();
}
@@ -252,6 +252,7 @@ public:
// already created a thunk.
if ((hasCaller || Changed) && DeadArgumentAnalyzeParameters()) {
Changed = true;
DEBUG(llvm::dbgs() << " remove dead arguments\n");
DeadArgumentTransformFunction();
}
@@ -274,10 +275,46 @@ public:
// Create the specialized function and invalidate the old function.
if (Changed) {
createFunctionSignatureOptimizedFunction();
PM->invalidateAnalysis(F, SILAnalysis::InvalidationKind::Everything);
}
return Changed;
}
/// Run dead argument elimination of partially applied functions.
/// After this optimization CapturePropagation can replace the partial_apply
/// by a direct reference to the specialized function.
bool removeDeadArgs(int minPartialAppliedArgs) {
if (minPartialAppliedArgs < 1)
return false;
if (!DeadArgumentAnalyzeParameters())
return false;
// Check if at least the minimum number of partially applied arguments
// are dead. Otherwise no partial_apply can be removed anyway.
for (unsigned Idx = 0, Num = ArgumentDescList.size(); Idx < Num; ++Idx) {
if (Idx < Num - minPartialAppliedArgs) {
// Don't remove arguments other than the partial applied ones, even if
// they are dead.
ArgumentDescList[Idx].IsEntirelyDead = false;
} else {
// Is the partially applied argument dead?
if (!ArgumentDescList[Idx].IsEntirelyDead)
return false;
// Currently we require that all dead parameters have trivial types.
// The reason is that it's very hard to find places where we can release
// those parameters (as a replacement for the removed partial_apply).
// TODO: maybe we can skip this restrication when we have semantic ARC.
if (!ArgumentDescList[Idx].Arg->getType().isTrivial(F->getModule()))
return false;
}
}
DEBUG(llvm::dbgs() << " remove dead arguments for partial_apply\n");
DeadArgumentTransformFunction();
createFunctionSignatureOptimizedFunction();
return true;
}
};
std::string FunctionSignatureTransform::createOptimizedSILFunctionName() {
@@ -812,32 +849,39 @@ void FunctionSignatureTransform::ArgumentExplosionFinalizeOptimizedFunction() {
//===----------------------------------------------------------------------===//
namespace {
class FunctionSignatureOpts : public SILFunctionTransform {
/// If true, perform a special kind of dead argument elimination to enable
/// removal of partial_apply instructions where all partially applied
/// arguments are dead.
bool OptForPartialApply;
public:
FunctionSignatureOpts(bool OptForPartialApply) :
OptForPartialApply(OptForPartialApply) { }
void run() override {
auto *F = getFunction();
// This is the function to optimize.
DEBUG(llvm::dbgs() << "*** FSO on function: " << F->getName() << " ***\n");
// Don't optimize callees that should not be optimized.
if (!F->shouldOptimize())
return;
// Does this function have a caller inside this module.
bool hasCaller = PM->getAnalysis<CallerAnalysis>()->hasCaller(F);
// If this function does not have a direct caller in the current module
// and maybe called indirectly, e.g. from virtual table do not function
// signature specialize it, as this will introduce a thunk.
if (!hasCaller && canBeCalledIndirectly(F->getRepresentation()))
return;
// This is the function to optimize.
DEBUG(llvm::dbgs() << "*** FSO on function: " << F->getName() << " ***\n");
// Check the signature of F to make sure that it is a function that we
// can specialize. These are conditions independent of the call graph.
if (!canSpecializeFunction(F))
if (!canSpecializeFunction(F)) {
DEBUG(llvm::dbgs() << " cannot specialize function -> abort\n");
return;
}
auto *AA = PM->getAnalysis<AliasAnalysis>();
auto *RCIA = getAnalysis<RCIdentityAnalysis>();
CallerAnalysis *CA = PM->getAnalysis<CallerAnalysis>();
const CallerAnalysis::FunctionInfo &FuncInfo = CA->getCallerInfo(F);
// As we optimize the function more and more, the name of the function is
// going to change, make sure the mangler is aware of all the changes done
@@ -866,22 +910,34 @@ public:
}
// Owned to guaranteed optimization.
FunctionSignatureTransform FST(F, hasCaller, PM, AA, RCIA, FM, AIM,
FunctionSignatureTransform FST(F, AA, RCIA, FM, AIM,
ArgumentDescList, ResultDescList);
if (FST.run()) {
bool Changed = false;
if (OptForPartialApply) {
Changed = FST.removeDeadArgs(FuncInfo.getMinPartialAppliedArgs());
} else {
Changed = FST.run(FuncInfo.hasCaller());
}
if (Changed) {
++ NumFunctionSignaturesOptimized;
// The old function must be a thunk now.
assert(F->isThunk() && "Old function should have been turned into a thunk");
PM->invalidateAnalysis(F, SILAnalysis::InvalidationKind::Everything);
// Make sure the PM knows about this function. This will also help us
// with self-recursion.
notifyPassManagerOfFunction(FST.getOptimizedFunction(), F);
// We have to restart the pipeline for this thunk in order to run the
// inliner (and other opts) again. This is important if the new
// specialized function (which is called from this thunk) is
// function-signature-optimized again and also becomes an
// always-inline-thunk.
restartPassPipeline();
if (!OptForPartialApply) {
// We have to restart the pipeline for this thunk in order to run the
// inliner (and other opts) again. This is important if the new
// specialized function (which is called from this thunk) is
// function-signature-optimized again and also becomes an
// always-inline-thunk.
restartPassPipeline();
}
}
}
@@ -891,5 +947,9 @@ public:
} // end anonymous namespace
SILTransform *swift::createFunctionSignatureOpts() {
return new FunctionSignatureOpts();
return new FunctionSignatureOpts(/* OptForPartialApply */ false);
}
SILTransform *swift::createDeadArgSignatureOpt() {
return new FunctionSignatureOpts(/* OptForPartialApply */ true);
}