blob: 37038e08da81ed4900817a4e49474a29784602df [file] [log] [blame]
//===--- ArgumentExplosionTransform.cpp -----------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
///
/// \file
///
/// This file contains an implementation of the partial dead argument
/// elimination optimization. We do this to attempt to remove non-trivial
/// arguments of callees to eliminate lifetime constraints of a large argument
/// on values in the caller.
///
//===----------------------------------------------------------------------===//
#define DEBUG_TYPE "fso-argument-explosion-transform"
#include "FunctionSignatureOpts.h"
#include "llvm/Support/CommandLine.h"
using namespace swift;
static llvm::cl::opt<bool> FSODisableArgExplosion(
"sil-fso-disable-arg-explosion",
llvm::cl::desc("Do not perform argument explosion during FSO. Intended "
"only for testing purposes"));
//===----------------------------------------------------------------------===//
// Utility
//===----------------------------------------------------------------------===//
/// Whether the known-to-date upper bound on the live leaf count is high enough
/// so that argument explosion is possible.
static bool
mayExplodeGivenLiveLeafCountUpperBound(unsigned knownLiveLeafCountUpperBound) {
return knownLiveLeafCountUpperBound > 0;
}
static unsigned maxExplosionSizeWhenSpecializationWillIntroduceThunk(
bool willSpecializationIntroduceThunk) {
// 3 is the heuristic max explosion size for a single argument when the
// specializing the function will introduce a thunk. If specializing the
// function may not introduce a thunk, then we rely on the maximum size
// imposed by shouldExpand.
return willSpecializationIntroduceThunk ? 3 : UINT_MAX;
}
static bool shouldExplode(unsigned knownLiveLeafCountUpperBound,
bool hasKnownDeadLeaves,
bool hasKnownDeadNontrivialLeaves,
bool willSpecializationIntroduceThunk) {
unsigned maxExplosionSize =
maxExplosionSizeWhenSpecializationWillIntroduceThunk(
/*willSpecializationIntroduceThunk=*/
willSpecializationIntroduceThunk);
bool isLiveLeafCountInExplodableRange =
mayExplodeGivenLiveLeafCountUpperBound(knownLiveLeafCountUpperBound) &&
(knownLiveLeafCountUpperBound <= maxExplosionSize);
bool hasKnownDeadRelevantLeaves = willSpecializationIntroduceThunk
? hasKnownDeadNontrivialLeaves
: hasKnownDeadLeaves;
return isLiveLeafCountInExplodableRange && hasKnownDeadRelevantLeaves;
}
/// Return true if it's both legal and a good idea to explode this argument.
///
/// Our main interest here is to expose more opportunities for ARC. This means
/// that we are not interested in exploding (and partially DCEing) structs in
/// the following cases:
///
/// 1. Completely dead arguments. This is handled by dead argument elimination.
///
/// 2. Structs with many live leaf nodes. Our heuristic is to explode if there
/// are only 1-3 live leaf nodes for specializations and 1-6 live leaf nodes
/// (in fact, the number specified in shouldExpand). Otherwise again we run
/// into register pressure/spilling issues.
/// TODO: Improve the 1-3 heuristic by having FSO consider the total
/// resultant argument count. Currently, there is no consideration of
/// that, meaning we could end up with argument exploding even in the
/// case of long argument lists where it isn't beneficial.
///
/// Perform argument exploding if one of the following sets of conditions hold:
///
/// 1. a. The live leaf count is less than or equal to 3.
/// b. There is a dead non-trivial leaf.
/// 2. a. The live leaf count is less than or equal to 6.
/// b. There is a dead trivial leaf.
/// c. Specializing the function will not result in a thunk.
static bool
shouldExplode(FunctionSignatureTransformDescriptor &transformDesc,
ArgumentDescriptor &argDesc,
ConsumedArgToEpilogueReleaseMatcher &epilogueReleaseMatcher) {
// The method is structured as follows:
//
// First, do some basic checks and exit early.
// Then in three steps of increasing complexity, calculate data which could
// permit the heuristic to decide to explode the argument. These steps
// provide information of increasing expense and fidelity. Checking whether
// the heuristic allows explosion after each step unnecessary work to be
// avoided.
//
// In a bit more detail:
//
// 1) Do some basic checks and exit early, returning false.
// - that we can optimize the argument at all
// - that the argument has more than a single leaf node
// - that the module permits the type to be expanded
// 2) Gather some basic leaf counts.
// - calculate the unmodified (unmodified that is by the results of the
// owned-to-guaranteed transformation) live leaf count
// - calculate the total list of leaf types to obtain the total leaf count
// 3) Check whether the heuristic allows the argument to be exploded using
// only potentially-trivial leaf counts. At this point it is certainly not
// known that there are dead non-trivial leaves, so exiting early here
// is only possible if specializing the function will not result in a
// thunk.
// 4) Gather the counts of non-trivial leaves.
// - calculate the count of total non-trivial leaves by filtering the total
// list of leaf types from step 2) according to whether leaf is trivial
// - calculate an upper bound (upper bound because it doesn't consider the
// results of the owned-to-guaranteed transformation) on the count of
// live non-trivial leaves
// 5) Check whether the heuristic allows the argument to be exploded using the
// upper bound on live non-trivial leaves.
// 6) Dial in the upper bounds calculated in steps 2) and 4) by compensating
// for the effects of the owned-to-guaranteed transformation.
// 7) Check whether the heuristic allows the argument to be exploded using the
// actual count of live leaves, both trivial and non-trivial.
// No passes can optimize this argument, so just bail.
if (!argDesc.canOptimizeLiveArg()) {
LLVM_DEBUG(llvm::dbgs()
<< "The argument is of a type that cannot be exploded.");
return false;
}
// If the argument is a singleton, it will not be exploded.
//
// Explosion makes sense only if some but not all of the leaves are live.
//
// Note that ProjectionTree::isSingleton returns true for enums since they are
// sums and not products and so only have a single top-level node.
if (argDesc.ProjTree.isSingleton()) {
LLVM_DEBUG(llvm::dbgs() << "The argument's type is a singleton.");
return false;
}
auto *argument = argDesc.Arg;
auto &module = argument->getModule();
auto type = argument->getType().getObjectType();
// If the global type expansion heuristic does not allow the type to be
// expanded, it will not be exploded.
if (!shouldExpand(module, type)) {
LLVM_DEBUG(llvm::dbgs()
<< "The argument is of a type which should not be expanded.");
return false;
}
bool willSpecializationIntroduceThunk =
transformDesc.willSpecializationIntroduceThunk();
unsigned const liveLeafCountUpperBound = argDesc.ProjTree.getLiveLeafCount();
// If we know already that we may not explode given the upper bound we have
// established on the live leaf count, exit early.
//
// If the argument is completely dead, it will not be exploded.
//
// Explosion makes sense only if some but not all of the leaves are live. The
// dead argument transformation will try to eliminate the argument.
if (!mayExplodeGivenLiveLeafCountUpperBound(liveLeafCountUpperBound)) {
LLVM_DEBUG(llvm::dbgs() << "The argument has no live leaves.");
return false;
}
// To determine whether some but not all of the leaves are used, the total
// leaf count must be retrieved.
llvm::SmallVector<SILType, 32> allLeaves;
argDesc.ProjTree.getAllLeafTypes(allLeaves);
unsigned const leafCount = allLeaves.size();
assert(
liveLeafCountUpperBound <= leafCount &&
"There should be no more *live* leaves than there are *total* leaves.");
if (shouldExplode(
/*knownLifeLeafCount=*/liveLeafCountUpperBound,
/*hasKnownDeadLeaves=*/liveLeafCountUpperBound < leafCount,
/*hasKnownDeadNontrivialLeaves=*/false,
/*willSpecializationIntroduceThunk=*/
willSpecializationIntroduceThunk)) {
LLVM_DEBUG(
llvm::dbgs()
<< "Without considering the liveness of non-trivial leaves, it has "
"already been determined that there are already fewer ("
<< liveLeafCountUpperBound
<< ") live leaves of the relevant sort (trivial) than total leaves ("
<< leafCount << ") and no more total live leaves ("
<< liveLeafCountUpperBound << ") than the heuristic permits ("
<< maxExplosionSizeWhenSpecializationWillIntroduceThunk(
/*willSpecializationIntroduceThunk=*/
willSpecializationIntroduceThunk)
<< "). Exploding.");
return true;
}
auto *function = argument->getFunction();
unsigned const nontrivialLeafCount = llvm::count_if(
allLeaves, [&](SILType type) { return !type.isTrivial(*function); });
llvm::SmallVector<const ProjectionTreeNode *, 32> liveLeaves;
argDesc.ProjTree.getLiveLeafNodes(liveLeaves);
// NOTE: The value obtained here is an upper bound because the
// owned-to-guaranteed transformation may eliminate some live
// non-trivial leaves, leaving the count lower.
unsigned const liveNontrivialLeafCountUpperBound =
llvm::count_if(liveLeaves, [&](const ProjectionTreeNode *leaf) {
return !leaf->getType().isTrivial(*function);
});
assert(liveNontrivialLeafCountUpperBound <= nontrivialLeafCount &&
"There should be no more *live* non-trivial leaves than there are "
"*total* non-trivial leaves.");
assert(nontrivialLeafCount <= leafCount &&
"There should be no more *non-trivial* leaves than there are *total* "
"leaves.");
// If it is known without taking the owned-to-guaranteed transformation into
// account both that exploding will reduce ARC traffic (because an upper bound
// for the number of live non-trivial leaves is less than the non-trivial
// leaf count) and also that the explosion will fit within the heuristic upper
// bound (because an upper bound for the total live leaf count falls within
// the limit imposed by the heuristic), then explode now.
bool shouldExplodeGivenUpperBounds = shouldExplode(
/*knownLiveLeafCount=*/liveLeafCountUpperBound,
/*hasKnownDeadLeaves=*/liveLeafCountUpperBound < leafCount,
/*hasKnownDeadNontrivialLeaves=*/liveNontrivialLeafCountUpperBound <
nontrivialLeafCount,
/*willSpecializationIntroduceThunk=*/willSpecializationIntroduceThunk);
if (shouldExplodeGivenUpperBounds) {
LLVM_DEBUG(
llvm::dbgs()
<< "Without considering the expected results of the "
"owned-to-guaranteed transformation, there are already fewer ("
<< liveNontrivialLeafCountUpperBound
<< ") live non-trivial leaves than total leaves ("
<< nontrivialLeafCount << ") and no more total live leaves ("
<< liveLeafCountUpperBound << ") than the heuristic permits ("
<< maxExplosionSizeWhenSpecializationWillIntroduceThunk(
/*willSpecializationIntroduceThunk=*/
willSpecializationIntroduceThunk)
<< "). Exploding.");
return true;
}
unsigned liveLeafCount = liveLeafCountUpperBound;
unsigned liveNontrivialLeafCount = liveNontrivialLeafCountUpperBound;
// The upper bounds that have been established for the live leaf counts are
// too high to permit us to explode. That could be because it hasn't been
// established that any leaves are dead or alternatively that it hasn't been
// established that there are fewer total live leaves than the limit imposed
// by the heuristic. In either case, if some live leaves are eliminated, the
// number of live leaves may decrease such that exploding will be possible.
// The results of the owned-to-guaranteed transformation are predicated. If
// it is predicted that a leaf will be dead after the owned-to-guaranteed
// transformation, then the leaf count is decreased.
//
// The owned-to-guaranteed will only be applied to the argumehnt if its
// convention is Direct_Owned. Additionally, it only applies to non-trivial
// leaves, which it may kill, so if it is already known that there are no live
// non-trivial leaves, owned-to-guaranteed will not eliminate anything.
if (argDesc.hasConvention(SILArgumentConvention::Direct_Owned) &&
liveNontrivialLeafCountUpperBound > 0) {
if (auto maybeReleases =
epilogueReleaseMatcher.getPartiallyPostDomReleaseSet(argument)) {
auto releases = maybeReleases.getValue();
llvm::SmallPtrSet<SILInstruction *, 8> users;
users.insert(std::begin(releases), std::end(releases));
for (auto *leaf : liveLeaves) {
if (llvm::all_of(leaf->getNonProjUsers(), [&](Operand *operand) {
return users.count(operand->getUser());
})) {
// Every non-projection user of the leaf is an epilogue release. The
// owned-to-guaranteed transformation will eliminate this usage. With
// the expectation of that usage being eliminated, stop considering
// this leaf to be live for the purposes of deciding whether the
// argument should be exploded.
--liveLeafCount;
--liveNontrivialLeafCount;
}
}
}
}
return shouldExplode(
/*knownLifeLeafCount=*/liveLeafCount,
/*hasKnownDeadLeaves=*/liveLeafCount < leafCount,
/*hasKnownDeadNontrivialLeaves=*/liveNontrivialLeafCount <
nontrivialLeafCount,
/*willSpecializationIntroduceThunk=*/willSpecializationIntroduceThunk);
}
//===----------------------------------------------------------------------===//
// Top Level Implementation
//===----------------------------------------------------------------------===//
bool FunctionSignatureTransform::ArgumentExplosionAnalyzeParameters() {
// If we are not supposed to perform argument explosion, bail.
if (FSODisableArgExplosion)
return false;
SILFunction *F = TransformDescriptor.OriginalFunction;
// Did we decide we should optimize any parameter?
bool SignatureOptimize = false;
auto Args = F->begin()->getFunctionArguments();
ConsumedArgToEpilogueReleaseMatcher ArgToReturnReleaseMap(
RCIA->get(F), F, {SILArgumentConvention::Direct_Owned});
// Analyze the argument information.
for (unsigned i : indices(Args)) {
ArgumentDescriptor &A = TransformDescriptor.ArgumentDescList[i];
// If the argument is dead, there is no point in trying to explode it. The
// dead argument pass will get it.
if (A.IsEntirelyDead) {
continue;
}
// Do not optimize argument.
if (!A.canOptimizeLiveArg()) {
continue;
}
// Explosion of generic parameters is not supported yet.
if (A.Arg->getType().hasArchetype())
continue;
A.ProjTree.computeUsesAndLiveness(A.Arg);
A.Explode = shouldExplode(TransformDescriptor, A, ArgToReturnReleaseMap);
// Modified self argument.
if (A.Explode && Args[i]->isSelf()) {
TransformDescriptor.shouldModifySelfArgument = true;
}
SignatureOptimize |= A.Explode;
}
return SignatureOptimize;
}
void FunctionSignatureTransform::ArgumentExplosionFinalizeOptimizedFunction() {
SILFunction *NewF = TransformDescriptor.OptimizedFunction.get();
SILBasicBlock *BB = &*NewF->begin();
SILBuilder Builder(BB->begin());
Builder.setCurrentDebugScope(BB->getParent()->getDebugScope());
unsigned TotalArgIndex = 0;
for (ArgumentDescriptor &AD : TransformDescriptor.ArgumentDescList) {
// If this argument descriptor was dead and we removed it, just skip it. Do
// not increment the argument index.
if (AD.WasErased) {
continue;
}
// Simply continue if do not explode.
if (!AD.Explode) {
TransformDescriptor.AIM[TotalArgIndex] = AD.Index;
++TotalArgIndex;
continue;
}
assert(!AD.IsEntirelyDead &&
"Should never see completely dead values here");
// OK, we need to explode this argument.
unsigned ArgOffset = ++TotalArgIndex;
unsigned OldArgIndex = ArgOffset - 1;
llvm::SmallVector<SILValue, 8> LeafValues;
// We do this in the same order as leaf types since ProjTree expects that
// the order of leaf values matches the order of leaf types.
llvm::SmallVector<const ProjectionTreeNode *, 8> LeafNodes;
AD.ProjTree.getLiveLeafNodes(LeafNodes);
for (auto *Node : LeafNodes) {
auto OwnershipKind = *AD.getTransformedOwnershipKind(Node->getType());
LeafValues.push_back(
BB->insertFunctionArgument(ArgOffset, Node->getType(), OwnershipKind,
BB->getArgument(OldArgIndex)->getDecl()));
TransformDescriptor.AIM[TotalArgIndex - 1] = AD.Index;
++ArgOffset;
++TotalArgIndex;
}
// Then go through the projection tree constructing aggregates and replacing
// uses.
AD.ProjTree.replaceValueUsesWithLeafUses(
Builder, BB->getParent()->getLocation(), LeafValues);
// We ignored debugvalue uses when we constructed the new arguments, in
// order to preserve as much information as possible, we construct a new
// value for OrigArg from the leaf values and use that in place of the
// OrigArg.
SILValue NewOrigArgValue = AD.ProjTree.computeExplodedArgumentValue(
Builder, BB->getParent()->getLocation(), LeafValues);
// Replace all uses of the original arg with the new value.
SILArgument *OrigArg = BB->getArgument(OldArgIndex);
OrigArg->replaceAllUsesWith(NewOrigArgValue);
// Now erase the old argument since it does not have any uses. We also
// decrement ArgOffset since we have one less argument now.
BB->eraseArgument(OldArgIndex);
--TotalArgIndex;
}
}