blob: 3f49ba9213fccc433d7b2bff2ed29ee43e0cb8ce [file] [log] [blame]
//===--- ArrayPropertyOpt.cpp - Optimize Array Properties -----------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 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
//
//===----------------------------------------------------------------------===//
///
/// Optimize array property access by specializing loop bodies.
///
/// This optimization specializes loops with calls to
/// "array.props.isNative/needsElementTypeCheck".
///
/// The "array.props.isNative/needsElementTypeCheck" predicate has the property
/// that if it is true/false respectively for the array struct it is true/false
/// respectively until somebody writes a new array struct over the memory
/// location. Less abstractly, a fast native swift array does not transition to
/// a slow array (be it a cocoa array, or be it an array that needs type
/// checking) except if we store a new array to the variable that holds it.
///
/// Using this property we can hoist the predicate above a region where no such
/// store can take place.
///
/// func f(a : A[AClass]) {
/// for i in 0..a.count {
/// let b = a.props.isNative()
/// .. += _getElement(i, b)
/// }
/// }
///
/// ==>
///
/// func f(a : A[AClass]) {
/// let b = a.props.isNative
/// if (b) {
/// for i in 0..a.count {
/// .. += _getElement(i, false)
/// }
/// } else {
/// for i in 0..a.count {
/// let a = a.props.isNative
/// .. += _getElement(i, a)
/// }
/// }
/// }
///
//===----------------------------------------------------------------------===//
#define DEBUG_TYPE "array-property-opt"
#include "ArrayOpt.h"
#include "swift/SILOptimizer/Analysis/LoopAnalysis.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SILOptimizer/Utils/CFGOptUtils.h"
#include "swift/SILOptimizer/Utils/SILSSAUpdater.h"
#include "swift/SIL/CFG.h"
#include "swift/SIL/DebugUtils.h"
#include "swift/SIL/InstructionUtils.h"
#include "swift/SIL/Projection.h"
#include "swift/SIL/LoopInfo.h"
#include "swift/SIL/SILCloner.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Debug.h"
using namespace swift;
namespace {
/// Analysis whether it is safe to specialize this loop nest based on the
/// array.props function calls it contains. It is safe to hoist array.props
/// calls if the array does not escape such that the array container could be
/// overwritten in the hoisted region.
/// This analysis also checks if we can clone the instructions in the loop nest.
class ArrayPropertiesAnalysis {
using UserList = StructUseCollector::UserList;
using UserOperList = StructUseCollector::UserOperList;
SILFunction *Fun;
SILLoop *Loop;
SILBasicBlock *Preheader;
DominanceInfo *DomTree;
llvm::DenseMap<SILFunction *, uint32_t> InstCountCache;
llvm::SmallSet<SILValue, 16> HoistableArray;
SmallPtrSet<SILBasicBlock *, 16> ReachingBlocks;
SmallPtrSet<SILBasicBlock *, 16> CachedExitingBlocks;
// This controls the max instructions the analysis can scan before giving up
const uint32_t AnalysisThreshold = 5000;
// This controls the max threshold for instruction count in the loop
const uint32_t LoopInstCountThreshold = 500;
public:
ArrayPropertiesAnalysis(SILLoop *L, DominanceAnalysis *DA)
: Fun(L->getHeader()->getParent()), Loop(L), Preheader(nullptr),
DomTree(DA->get(Fun)) {}
/// Check if it is profitable to specialize a loop when you see an apply
/// instruction. We consider it is not profitable to specialize the loop when:
/// 1. The callee is not found in the module, or cannot be determined
/// 2. The number of instructions the analysis scans has exceeded the AnalysisThreshold
uint32_t checkProfitabilityRecursively(SILFunction *Callee) {
if (!Callee)
return AnalysisThreshold;
auto CacheEntry = InstCountCache.find(Callee);
if (CacheEntry != InstCountCache.end())
return CacheEntry->second;
InstCountCache.insert(std::make_pair(Callee, 0));
uint32_t InstCount = 0;
for (auto &BB : *Callee) {
for (auto &I : BB) {
if (InstCount++ >= AnalysisThreshold) {
LLVM_DEBUG(llvm::dbgs() << "ArrayPropertyOpt: Disabled Reason - Exceeded Analysis Threshold in "
<< BB.getParent()->getName() << "\n");
InstCountCache[Callee] = AnalysisThreshold;
return AnalysisThreshold;
}
if (auto Apply = FullApplySite::isa(&I)) {
auto Callee = Apply.getReferencedFunctionOrNull();
if (!Callee) {
LLVM_DEBUG(llvm::dbgs() << "ArrayPropertyOpt: Disabled Reason - Found opaque code in "
<< BB.getParent()->getName() << "\n");
LLVM_DEBUG(Apply.dump());
LLVM_DEBUG(I.getOperand(0)->dump());
}
const auto CalleeInstCount = checkProfitabilityRecursively(Callee);
InstCount += CalleeInstCount;
}
}
}
InstCountCache[Callee] = InstCount;
return InstCount;
}
bool run() {
Preheader = Loop->getLoopPreheader();
if (!Preheader) {
LLVM_DEBUG(llvm::dbgs() << "ArrayPropertiesAnalysis: "
"Missing preheader for "
<< *Loop);
return false;
}
// Check whether this is a 'array.props' instruction and whether we
// can hoist it. Heuristic: We only want to hoist array.props instructions
// if we can hoist all of them - only then can we get rid of all the
// control-flow if we specialize. Hoisting some but not others is not as
// beneficial. This heuristic also simplifies which regions we want to
// specialize on. We will specialize the outermost loopnest that has
// 'array.props' instructions in its preheader.
bool FoundHoistable = false;
uint32_t LoopInstCount = 0;
for (auto *BB : Loop->getBlocks()) {
for (auto &Inst : *BB) {
// Can't clone alloc_stack instructions whose dealloc_stack is outside
// the loop.
if (!Loop->canDuplicate(&Inst))
return false;
ArraySemanticsCall ArrayPropsInst(&Inst, "array.props", true);
if (!ArrayPropsInst)
continue;
if (!canHoistArrayPropsInst(ArrayPropsInst))
return false;
++LoopInstCount;
FoundHoistable = true;
}
}
if (!FoundHoistable)
return false;
// If the LoopInstCount exceeds the threshold, we will disable the optimization on this loop
// For loops of deeper nesting we increase the threshold by an additional 10%
if (LoopInstCount > LoopInstCountThreshold * (1 + (Loop->getLoopDepth() - 1) / 10)) {
LLVM_DEBUG(llvm::dbgs() << "Exceeded LoopInstCountThreshold\n");
return false;
}
// Additionally, we don't specialize the loop if we find opaque code or
// the analysis scans instructions greater than a threshold
// Since only few loops qualify as hoistable, and the profitability check
// can run long in cases of large thresholds, these checks are not folded
// along with the legality checks above.
for (auto *BB : Loop->getBlocks()) {
for (auto &Inst : *BB) {
if (auto Apply = FullApplySite::isa(&Inst)) {
const auto Callee = Apply.getReferencedFunctionOrNull();
auto CalleeInstCount = checkProfitabilityRecursively(Callee);
if (CalleeInstCount >= AnalysisThreshold)
return false;
}
}
}
LLVM_DEBUG(llvm::dbgs() << "Profitable ArrayPropertyOpt in "
<< Loop->getLoopPreheader()->getParent()->getName() << "\n");
LLVM_DEBUG(Loop->dump());
return true;
}
private:
/// Strip the struct load and the address projection to the location
/// holding the array struct.
SILValue stripArrayStructLoad(SILValue V) {
if (auto LI = dyn_cast<LoadInst>(V)) {
auto Val = LI->getOperand();
// We could have two arrays in a surrounding container so we can only
// strip off the 'array struct' project.
// struct Container {
// var a1 : [ClassA]
// var a2 : [ClassA]
// }
// 'a1' and 'a2' are different arrays.
if (auto SEAI = dyn_cast<StructElementAddrInst>(Val))
Val = SEAI->getOperand();
return Val;
}
return V;
}
SmallPtrSetImpl<SILBasicBlock *> &getReachingBlocks() {
if (ReachingBlocks.empty()) {
SmallVector<SILBasicBlock *, 8> Worklist;
ReachingBlocks.insert(Preheader);
Worklist.push_back(Preheader);
while (!Worklist.empty()) {
SILBasicBlock *BB = Worklist.pop_back_val();
for (auto PI = BB->pred_begin(), PE = BB->pred_end(); PI != PE; ++PI) {
if (ReachingBlocks.insert(*PI).second)
Worklist.push_back(*PI);
}
}
}
return ReachingBlocks;
}
/// Array address uses are safe if they don't store to the array struct. We
/// could for example store an NSArray array struct on top of the array. For
/// example, an opaque function that uses the array's address could store a
/// new array onto it.
bool checkSafeArrayAddressUses(UserList &AddressUsers) {
for (auto *UseInst : AddressUsers) {
if (UseInst->isDebugInstruction())
continue;
if (isa<DeallocStackInst>(UseInst)) {
// Handle destruction of a local array.
continue;
}
if (auto *AI = dyn_cast<ApplyInst>(UseInst)) {
if (ArraySemanticsCall(AI))
continue;
// Check if this escape can reach the current loop.
if (!Loop->contains(UseInst->getParent()) &&
!getReachingBlocks().count(UseInst->getParent())) {
continue;
}
LLVM_DEBUG(llvm::dbgs()
<< " Skipping Array: may escape through call!\n"
<< " " << *UseInst);
return false;
}
if (auto *StInst = dyn_cast<StoreInst>(UseInst)) {
// Allow a local array to be initialized outside the loop via a by-value
// argument or return value. The array value may be returned by its
// initializer or some other factory function.
if (Loop->contains(StInst->getParent())) {
LLVM_DEBUG(llvm::dbgs() << " Skipping Array: store inside loop!\n"
<< " " << *StInst);
return false;
}
SILValue InitArray = StInst->getSrc();
if (isa<SILArgument>(InitArray) || isa<ApplyInst>(InitArray))
continue;
return false;
}
LLVM_DEBUG(llvm::dbgs() << " Skipping Array: unknown Array use!\n"
<< " " << *UseInst);
// Found an unsafe or unknown user. The Array may escape here.
return false;
}
// Otherwise, all of our users are sane. The array does not escape.
return true;
}
/// Value uses are generally safe. We can't change the state of an array
/// through a value use.
bool checkSafeArrayValueUses(UserList &ValueUsers) {
return true;
}
bool checkSafeElementValueUses(UserOperList &ElementValueUsers) {
return true;
}
// We have a safe container if the array container is passed as a function
// argument by-value or by inout reference. In either case there can't be an
// alias of the container. Alternatively, we can have a local variable. We
// will check in checkSafeArrayAddressUses that all initialization stores to
// this variable are safe (i.e the store dominates the loop etc).
bool isSafeArrayContainer(SILValue V) {
if (auto *Arg = dyn_cast<SILArgument>(V)) {
// Check that the argument is passed as an inout or by value type. This
// means there are no aliases accessible within this function scope.
auto Params = Fun->getLoweredFunctionType()->getParameters();
ArrayRef<SILArgument *> FunctionArgs = Fun->begin()->getArguments();
for (unsigned ArgIdx = 0, ArgEnd = Params.size(); ArgIdx != ArgEnd;
++ArgIdx) {
if (FunctionArgs[ArgIdx] != Arg)
continue;
if (!Params[ArgIdx].isIndirectInOut()
&& Params[ArgIdx].isFormalIndirect()) {
LLVM_DEBUG(llvm::dbgs() << " Skipping Array: Not an inout or "
"by val argument!\n");
return false;
}
}
return true;
} else if (isa<AllocStackInst>(V))
return true;
LLVM_DEBUG(llvm::dbgs()
<< " Skipping Array: Not a know array container type!\n");
return false;
}
SmallPtrSetImpl<SILBasicBlock *> &getLoopExitingBlocks() {
if (!CachedExitingBlocks.empty())
return CachedExitingBlocks;
SmallVector<SILBasicBlock *, 16> ExitingBlocks;
Loop->getExitingBlocks(ExitingBlocks);
CachedExitingBlocks.insert(ExitingBlocks.begin(), ExitingBlocks.end());
return CachedExitingBlocks;
}
bool isConditionallyExecuted(ArraySemanticsCall Call) {
auto CallBB = (*Call).getParent();
for (auto *ExitingBlk : getLoopExitingBlocks())
if (!DomTree->dominates(CallBB, ExitingBlk))
return true;
return false;
}
bool isClassElementTypeArray(SILValue Arr) {
auto Ty = Arr->getType();
if (auto BGT = Ty.getAs<BoundGenericStructType>()) {
// Check the array element type parameter.
bool isClass = false;
for (auto EltTy : BGT->getGenericArgs()) {
if (!EltTy->hasReferenceSemantics())
return false;
isClass = true;
}
return isClass;
}
return false;
}
bool canHoistArrayPropsInst(ArraySemanticsCall Call) {
// TODO: This is way conservative. If there is an unconditionally
// executed call to the same array we can still hoist it.
if (isConditionallyExecuted(Call))
return false;
SILValue Arr = Call.getSelf();
// We don't attempt to hoist non-class element type arrays.
if (!isClassElementTypeArray(Arr))
return false;
// We can strip the load that might even occur in the loop because we make
// sure that no unsafe store to the array's address takes place.
Arr = stripArrayStructLoad(Arr);
// Have we already seen this array and deemed it safe?
if (HoistableArray.count(Arr))
return true;
// Do we know how to hoist the arguments of this call.
if (!Call.canHoist(Preheader->getTerminator(), DomTree))
return false;
SmallVector<int, 4> AccessPath;
SILValue ArrayContainer =
StructUseCollector::getAccessPath(Arr, AccessPath);
if (!isSafeArrayContainer(ArrayContainer))
return false;
StructUseCollector StructUses;
StructUses.collectUses(ArrayContainer, AccessPath);
if (!checkSafeArrayAddressUses(StructUses.AggregateAddressUsers) ||
!checkSafeArrayAddressUses(StructUses.StructAddressUsers) ||
!checkSafeArrayValueUses(StructUses.StructValueUsers) ||
!checkSafeElementValueUses(StructUses.ElementValueUsers) ||
!StructUses.ElementAddressUsers.empty())
return false;
HoistableArray.insert(Arr);
return true;
}
};
} // end anonymous namespace
namespace {
/// Clone a single exit multiple exit region starting at basic block and ending
/// in a set of basic blocks. Updates the dominator tree with the cloned blocks.
/// However, the client needs to update the dominator of the exit blocks.
///
/// FIXME: All functionality for generating valid SIL (including the DomTree)
/// should be handled by the common SILCloner. Currently, SILCloner only updates
/// the DomTree for original (non-cloned) blocks when splitting edges. The
/// cloned blocks won't be mapped to dominator nodes until fixDomTree()
/// runs. However, since SILCloner always handles single-entry regions,
/// fixDomTree() could be part of SILCloner itself.
class RegionCloner : public SILCloner<RegionCloner> {
SILBasicBlock *StartBB;
friend class SILInstructionVisitor<RegionCloner>;
friend class SILCloner<RegionCloner>;
public:
RegionCloner(SILBasicBlock *EntryBB, DominanceInfo &DT)
: SILCloner<RegionCloner>(*EntryBB->getParent(), &DT), StartBB(EntryBB) {}
SILBasicBlock *cloneRegion(ArrayRef<SILBasicBlock *> exitBBs) {
assert(DomTree->getNode(StartBB) != nullptr && "Can't cloned dead code");
cloneReachableBlocks(StartBB, exitBBs);
// Add dominator tree nodes for the new basic blocks.
fixDomTree();
// Update SSA form for values used outside of the copied region.
updateSSAForm();
return getOpBasicBlock(StartBB);
}
protected:
/// Clone the dominator tree from the original region to the cloned region.
void fixDomTree() {
for (auto *BB : originalPreorderBlocks()) {
auto *ClonedBB = getOpBasicBlock(BB);
auto *OrigDomBB = DomTree->getNode(BB)->getIDom()->getBlock();
if (BB == StartBB) {
// The cloned start node shares the same dominator as the original node.
auto *ClonedNode = DomTree->addNewBlock(ClonedBB, OrigDomBB);
(void)ClonedNode;
assert(ClonedNode);
continue;
}
// Otherwise, map the dominator structure using the mapped block.
DomTree->addNewBlock(ClonedBB, getOpBasicBlock(OrigDomBB));
}
}
SILValue getMappedValue(SILValue V) {
if (auto *BB = V->getParentBlock()) {
if (!DomTree->dominates(StartBB, BB)) {
// Must be a value that dominates the start basic block.
assert(DomTree->dominates(BB, StartBB)
&& "Must dominated the start of the cloned region");
return V;
}
}
return SILCloner<RegionCloner>::getMappedValue(V);
}
void postProcess(SILInstruction *Orig, SILInstruction *Cloned) {
SILCloner<RegionCloner>::postProcess(Orig, Cloned);
}
/// Update SSA form for values that are used outside the region.
void updateSSAForValue(SILBasicBlock *OrigBB, SILValue V,
SILSSAUpdater &SSAUp) {
// Collect outside uses.
SmallVector<UseWrapper, 16> UseList;
for (auto Use : V->getUses())
if (!isBlockCloned(Use->getUser()->getParent())) {
UseList.push_back(UseWrapper(Use));
}
if (UseList.empty())
return;
// Update SSA form.
SSAUp.initialize(V->getType());
SSAUp.addAvailableValue(OrigBB, V);
SILValue NewVal = getMappedValue(V);
SSAUp.addAvailableValue(getOpBasicBlock(OrigBB), NewVal);
for (auto U : UseList) {
Operand *Use = U;
SSAUp.rewriteUse(*Use);
}
}
void updateSSAForm() {
SILSSAUpdater SSAUp;
for (auto *origBB : originalPreorderBlocks()) {
// Update outside used phi values.
for (auto *arg : origBB->getArguments())
updateSSAForValue(origBB, arg, SSAUp);
// Update outside used instruction values.
for (auto &inst : *origBB) {
for (auto result : inst.getResults())
updateSSAForValue(origBB, result, SSAUp);
}
}
}
};
} // end anonymous namespace
namespace {
/// This class transforms a hoistable loop nest into a speculatively specialized
/// loop based on array.props calls.
class ArrayPropertiesSpecializer {
DominanceInfo *DomTree;
SILLoopAnalysis *LoopAnalysis;
SILBasicBlock *HoistableLoopPreheader;
public:
ArrayPropertiesSpecializer(DominanceInfo *DT, SILLoopAnalysis *LA,
SILBasicBlock *Hoistable)
: DomTree(DT), LoopAnalysis(LA), HoistableLoopPreheader(Hoistable) {}
void run() {
specializeLoopNest();
}
SILLoop *getLoop() {
auto *LoopInfo = LoopAnalysis->get(HoistableLoopPreheader->getParent());
return LoopInfo->getLoopFor(
HoistableLoopPreheader->getSingleSuccessorBlock());
}
protected:
void specializeLoopNest();
};
} // end anonymous namespace
static SILValue createStructExtract(SILBuilder &B, SILLocation Loc,
SILValue Opd, unsigned FieldNo) {
SILType Ty = Opd->getType();
auto SD = Ty.getStructOrBoundGenericStruct();
auto Properties = SD->getStoredProperties();
unsigned Counter = 0;
for (auto *D : Properties)
if (Counter++ == FieldNo)
return B.createStructExtract(Loc, Opd, D);
llvm_unreachable("Wrong field number");
}
static Identifier getBinaryFunction(StringRef Name, SILType IntSILTy,
ASTContext &C) {
auto IntTy = IntSILTy.castTo<BuiltinIntegerType>();
unsigned NumBits = IntTy->getWidth().getFixedWidth();
// Name is something like: add_Int64
std::string NameStr(Name);
NameStr += "_Int" + llvm::utostr(NumBits);
return C.getIdentifier(NameStr);
}
/// Create a binary and function.
static SILValue createAnd(SILBuilder &B, SILLocation Loc, SILValue Opd1,
SILValue Opd2) {
auto AndFn = getBinaryFunction("and", Opd1->getType(), B.getASTContext());
SILValue Args[] = {Opd1, Opd2};
return B.createBuiltin(Loc, AndFn, Opd1->getType(), {}, Args);
}
/// Create a check over all array.props calls that they have the 'fast native
/// swift' array value: isNative && !needsElementTypeCheck must be true.
static SILValue
createFastNativeArraysCheck(SmallVectorImpl<ArraySemanticsCall> &ArrayProps,
SILBuilder &B) {
assert(!ArrayProps.empty() && "Must have array.pros calls");
SILType IntBoolTy = SILType::getBuiltinIntegerType(1, B.getASTContext());
SILValue Result =
B.createIntegerLiteral((*ArrayProps[0]).getLoc(), IntBoolTy, 1);
for (auto Call : ArrayProps) {
auto Loc = (*Call).getLoc();
auto CallKind = Call.getKind();
if (CallKind == ArrayCallKind::kArrayPropsIsNativeTypeChecked) {
auto Val = createStructExtract(B, Loc, SILValue(Call), 0);
Result = createAnd(B, Loc, Result, Val);
}
}
return Result;
}
/// Collect all array.props calls in the cloned basic blocks stored in the map,
/// asserting that we found at least one.
static void collectArrayPropsCalls(RegionCloner &Cloner,
SmallVectorImpl<SILBasicBlock *> &ExitBlocks,
SmallVectorImpl<ArraySemanticsCall> &Calls) {
for (auto *origBB : Cloner.originalPreorderBlocks()) {
auto clonedBB = Cloner.getOpBasicBlock(origBB);
for (auto &Inst : *clonedBB) {
ArraySemanticsCall ArrayProps(&Inst, "array.props", true);
if (!ArrayProps)
continue;
Calls.push_back(ArrayProps);
}
}
assert(!Calls.empty() && "Should have a least one array.props call");
}
/// Replace an array.props call by the 'fast swift array' value.
///
/// This is true for array.props.isNative and false for
/// array.props.needsElementTypeCheck.
static void replaceArrayPropsCall(SILBuilder &B, ArraySemanticsCall C) {
assert(C.getKind() == ArrayCallKind::kArrayPropsIsNativeTypeChecked);
ApplyInst *AI = C;
SILType IntBoolTy = SILType::getBuiltinIntegerType(1, B.getASTContext());
auto BoolTy = AI->getType();
auto C0 = B.createIntegerLiteral(AI->getLoc(), IntBoolTy, 1);
auto BoolVal = B.createStruct(AI->getLoc(), BoolTy, {C0});
(*C).replaceAllUsesWith(BoolVal);
// Remove call to array.props.read/write.
C.removeCall();
}
/// Collects all loop dominated blocks outside the loop that are immediately
/// dominated by the loop.
static void
collectImmediateLoopDominatedBlocks(const SILLoop *Lp, DominanceInfoNode *Node,
SmallVectorImpl<SILBasicBlock *> &Blocks) {
SILBasicBlock *BB = Node->getBlock();
// Base case: First loop dominated block outside of loop.
if (!Lp->contains(BB)) {
Blocks.push_back(BB);
return;
}
// Loop contains the basic block. Look at immediately dominated nodes.
for (auto *Child : *Node)
collectImmediateLoopDominatedBlocks(Lp, Child, Blocks);
}
void ArrayPropertiesSpecializer::specializeLoopNest() {
auto *Lp = getLoop();
assert(Lp);
// Split of a new empty preheader. We don't want to duplicate the whole
// original preheader it might contain instructions that we can't clone.
// This will be block that will contain the check whether to execute the
// 'native swift array' loop or the original loop.
SILBuilder B(HoistableLoopPreheader);
auto *CheckBlock = splitBasicBlockAndBranch(B,
HoistableLoopPreheader->getTerminator(), DomTree, nullptr);
auto *Header = CheckBlock->getSingleSuccessorBlock();
assert(Header);
// Collect all loop dominated blocks (e.g exit blocks could be among them). We
// need to update their dominator.
SmallVector<SILBasicBlock *, 16> LoopDominatedBlocks;
collectImmediateLoopDominatedBlocks(Lp, DomTree->getNode(Header),
LoopDominatedBlocks);
// Collect all exit blocks.
SmallVector<SILBasicBlock *, 16> ExitBlocks;
Lp->getExitBlocks(ExitBlocks);
// Split the preheader before the first instruction.
SILBasicBlock *NewPreheader =
splitBasicBlockAndBranch(B, &*CheckBlock->begin(), DomTree, nullptr);
// Clone the region from the new preheader up to (not including) the exit
// blocks. This creates a second loop nest.
RegionCloner Cloner(NewPreheader, *DomTree);
auto *ClonedPreheader = Cloner.cloneRegion(ExitBlocks);
// Collect the array.props call that we will specialize on that we have
// cloned in the cloned loop.
SmallVector<ArraySemanticsCall, 16> ArrayPropCalls;
collectArrayPropsCalls(Cloner, ExitBlocks, ArrayPropCalls);
// Move them to the check block.
SmallVector<ArraySemanticsCall, 16> HoistedArrayPropCalls;
for (auto C: ArrayPropCalls)
HoistedArrayPropCalls.push_back(
ArraySemanticsCall(C.copyTo(CheckBlock->getTerminator(), DomTree)));
// Create a conditional branch on the fast condition being true.
B.setInsertionPoint(CheckBlock->getTerminator());
auto IsFastNativeArray =
createFastNativeArraysCheck(HoistedArrayPropCalls, B);
B.createCondBranch(CheckBlock->getTerminator()->getLoc(),
IsFastNativeArray, ClonedPreheader, NewPreheader);
CheckBlock->getTerminator()->eraseFromParent();
// Fixup the loop dominated blocks. They are now dominated by the check block.
for (auto *BB : LoopDominatedBlocks)
DomTree->changeImmediateDominator(DomTree->getNode(BB),
DomTree->getNode(CheckBlock));
// Replace the array.props calls uses in the cloned loop by their 'fast'
// value.
SILBuilder B2(ClonedPreheader->getTerminator());
for (auto C : ArrayPropCalls)
replaceArrayPropsCall(B2, C);
// We have potentially cloned a loop - invalidate loop info.
LoopAnalysis->invalidate(Header->getParent(),
SILAnalysis::InvalidationKind::FunctionBody);
}
namespace {
class SwiftArrayPropertyOptPass : public SILFunctionTransform {
void run() override {
auto *Fn = getFunction();
// FIXME: Add support for ownership.
if (Fn->hasOwnership())
return;
// Don't hoist array property calls at Osize.
if (Fn->optimizeForSize())
return;
DominanceAnalysis *DA = PM->getAnalysis<DominanceAnalysis>();
SILLoopAnalysis *LA = PM->getAnalysis<SILLoopAnalysis>();
SILLoopInfo *LI = LA->get(Fn);
bool HasChanged = false;
// Check whether we can hoist 'array.props' calls out of loops, collecting
// the preheader we can hoist to. We only hoist out of loops if 'all'
// array.props call can be hoisted for a given loop nest.
// We process the loop tree preorder (top-down) to hoist over the biggest
// possible loop-nest.
SmallVector<SILBasicBlock *, 16> HoistableLoopNests;
std::function<void(SILLoop *)> processChildren = [&](SILLoop *L) {
ArrayPropertiesAnalysis Analysis(L, DA);
if (Analysis.run()) {
// Hoist in the current loop nest.
HasChanged = true;
HoistableLoopNests.push_back(L->getLoopPreheader());
} else {
// Otherwise, try hoisting sub-loops.
for (auto *SubLoop : *L)
processChildren(SubLoop);
}
};
for (auto *L : *LI)
processChildren(L);
// Specialize the identified loop nest based on the 'array.props' calls.
if (HasChanged) {
LLVM_DEBUG(getFunction()->viewCFG());
DominanceInfo *DT = DA->get(getFunction());
// Process specialized loop-nests in loop-tree post-order (bottom-up).
std::reverse(HoistableLoopNests.begin(), HoistableLoopNests.end());
// Hoist the loop nests.
for (auto &HoistableLoopNest : HoistableLoopNests)
ArrayPropertiesSpecializer(DT, LA, HoistableLoopNest).run();
// Verify that no illegal critical edges were created.
getFunction()->verifyCriticalEdges();
LLVM_DEBUG(getFunction()->viewCFG());
// We preserve the dominator tree. Let's invalidate everything
// else.
DA->lockInvalidation();
invalidateAnalysis(SILAnalysis::InvalidationKind::FunctionBody);
DA->unlockInvalidation();
}
}
};
} // end anonymous namespace
SILTransform *swift::createSwiftArrayPropertyOpt() {
return new SwiftArrayPropertyOptPass();
}