mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
StackPromotion: a big simplification of the algorithm
Instead of having the complicated logic of finding the end of an object's lifetime we now use the existing ValueLifetimeAnalysis and StackNesting tools. This simplifies the implementation a lot and does not use dominator and post-dominator trees anymore. It's not a NFC because the way how to ensure proper nesting of stack allocations is now different. To correct the stack nesting, the deallocations are moved down (instead of moving the allocations up). Also it's now required that there is a final release for the allocation on all paths (which was not the case in some hand-written SIL tests). Computation of dead-end blocks and escape analysis are now only done on demand. This saves compile time in case a function has no alloc_refs at all.
This commit is contained in:
@@ -14,332 +14,93 @@
|
||||
#include "swift/SILOptimizer/PassManager/Passes.h"
|
||||
#include "swift/SILOptimizer/PassManager/Transforms.h"
|
||||
#include "swift/SILOptimizer/Analysis/EscapeAnalysis.h"
|
||||
#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h"
|
||||
#include "swift/SILOptimizer/Utils/Local.h"
|
||||
#include "swift/SILOptimizer/Utils/StackNesting.h"
|
||||
#include "swift/SIL/SILArgument.h"
|
||||
#include "swift/SIL/SILBuilder.h"
|
||||
#include "swift/SIL/CFG.h"
|
||||
#include "llvm/Support/GenericDomTree.h"
|
||||
#include "llvm/Support/GenericDomTreeConstruction.h"
|
||||
#include "llvm/ADT/Statistic.h"
|
||||
|
||||
STATISTIC(NumStackPromoted, "Number of objects promoted to the stack");
|
||||
|
||||
using namespace swift;
|
||||
|
||||
namespace {
|
||||
|
||||
/// Promotes heap allocated objects to the stack.
|
||||
///
|
||||
/// It handles alloc_ref instructions of native swift classes: if promoted,
|
||||
/// the [stack] attribute is set in the alloc_ref and a dealloc_ref [stack] is
|
||||
/// inserted at the end of the object's lifetime.
|
||||
class StackPromoter {
|
||||
|
||||
// Some analysis we need.
|
||||
|
||||
SILFunction *F;
|
||||
EscapeAnalysis::ConnectionGraph *ConGraph;
|
||||
DominanceInfo *DT;
|
||||
EscapeAnalysis *EA;
|
||||
|
||||
// We use our own post-dominator tree instead of PostDominatorAnalysis,
|
||||
// because we ignore unreachable blocks (actually all unreachable sub-graphs).
|
||||
// Example:
|
||||
// |
|
||||
// bb1
|
||||
// / \
|
||||
// unreachable bb2
|
||||
// \
|
||||
//
|
||||
// We want to get bb2 as immediate post-dominator of bb1. This is not the case
|
||||
// with the regular post-dominator tree.
|
||||
llvm::DominatorTreeBase<SILBasicBlock> PostDomTree;
|
||||
|
||||
bool PostDomTreeValid;
|
||||
|
||||
/// Worklist for visiting all blocks.
|
||||
class WorkListType {
|
||||
/// The nesting depth of stack allocation instructions for each block.
|
||||
/// A value of -1 means: not known yet.
|
||||
/// A value of -2 means: not known and not visited yet.
|
||||
/// All blocks in this map with a value >= -1 are already visited.
|
||||
llvm::DenseMap<SILBasicBlock *, int> Block2StackDepth;
|
||||
|
||||
/// The work list of not yet handled blocks.
|
||||
llvm::SmallVector<SILBasicBlock *, 8> ToHandle;
|
||||
|
||||
public:
|
||||
bool empty() const { return ToHandle.empty(); }
|
||||
|
||||
SILBasicBlock *pop_back_val() { return ToHandle.pop_back_val(); }
|
||||
|
||||
/// Insert a block into the worklist and set its stack depth.
|
||||
void insert(SILBasicBlock *BB, int StackDepth) {
|
||||
auto Iter = Block2StackDepth.find(BB);
|
||||
if (Iter != Block2StackDepth.end() && Iter->second >= -1) {
|
||||
// We already handled the block.
|
||||
assert(StackDepth >= 0);
|
||||
if (Iter->second < 0) {
|
||||
// Update the stack depth if we didn't set it yet for the block.
|
||||
Iter->second = StackDepth;
|
||||
} else {
|
||||
assert(Iter->second == StackDepth &&
|
||||
"inconsistent stack depth at a CFG merge point");
|
||||
}
|
||||
} else {
|
||||
Block2StackDepth[BB] = StackDepth;
|
||||
ToHandle.push_back(BB);
|
||||
}
|
||||
}
|
||||
|
||||
bool insertAsUnhandled(SILBasicBlock *Pred) {
|
||||
return Block2StackDepth.insert({Pred, -2}).second;
|
||||
}
|
||||
|
||||
int getStackDepth(SILBasicBlock *BB) {
|
||||
assert(Block2StackDepth.find(BB) != Block2StackDepth.end());
|
||||
int Depth = Block2StackDepth.lookup(BB);
|
||||
assert(Depth >= 0 && "EndBlock not reachable from StartBlock");
|
||||
return Depth;
|
||||
}
|
||||
};
|
||||
|
||||
/// Tries to promote the allocation \p AI.
|
||||
bool tryPromoteAlloc(AllocRefInst *ARI);
|
||||
|
||||
/// Returns true if the allocation \p AI can be promoted.
|
||||
/// In this case it sets the \a DeallocInsertionPoint to the instruction
|
||||
/// where the deallocation must be inserted.
|
||||
/// It optionally also sets \a AllocInsertionPoint in case the allocation
|
||||
/// instruction must be moved to another place.
|
||||
bool canPromoteAlloc(AllocRefInst *ARI,
|
||||
SILInstruction *&AllocInsertionPoint,
|
||||
SILInstruction *&DeallocInsertionPoint);
|
||||
|
||||
/// Returns the place where to insert the deallocation.
|
||||
/// Returns null if this doesn't succeed or, in case \p RestartPoint is set,
|
||||
/// a new iteration should be triggered.
|
||||
SILInstruction *findDeallocPoint(SILInstruction *StartInst,
|
||||
SILInstruction *&RestartPoint,
|
||||
EscapeAnalysis::CGNode *Node,
|
||||
int NumUsePointsToFind);
|
||||
|
||||
/// If \p CurrentBB is in a loop update the \p EndBlock so that it post-
|
||||
/// dominates the loop.
|
||||
/// Returns the new EndBlock or null if no one could be found.
|
||||
SILBasicBlock *updateEndBlock(SILBasicBlock *CurrentBB,
|
||||
SILBasicBlock *EndBlock,
|
||||
WorkListType &WorkList);
|
||||
|
||||
bool strictlyDominates(SILBasicBlock *A, SILBasicBlock *B) {
|
||||
return A != B && DT->dominates(A, B);
|
||||
}
|
||||
|
||||
bool strictlyPostDominates(SILBasicBlock *A, SILBasicBlock *B) {
|
||||
calculatePostDomTree();
|
||||
return A != B && PostDomTree.dominates(A, B);
|
||||
}
|
||||
|
||||
bool postDominates(SILBasicBlock *A, SILBasicBlock *B) {
|
||||
calculatePostDomTree();
|
||||
return PostDomTree.dominates(A, B);
|
||||
}
|
||||
|
||||
SILBasicBlock *getImmediatePostDom(SILBasicBlock *BB) {
|
||||
calculatePostDomTree();
|
||||
auto *Node = PostDomTree.getNode(BB);
|
||||
if (!Node)
|
||||
return nullptr;
|
||||
auto *IDomNode = Node->getIDom();
|
||||
if (!IDomNode)
|
||||
return nullptr;
|
||||
return IDomNode->getBlock();
|
||||
}
|
||||
|
||||
void calculatePostDomTree() {
|
||||
if (!PostDomTreeValid) {
|
||||
// The StackPromoter acts as a "graph" for which the post-dominator-tree
|
||||
// is calculated.
|
||||
PostDomTree.recalculate(*this);
|
||||
PostDomTreeValid = true;
|
||||
}
|
||||
}
|
||||
class StackPromotion : public SILFunctionTransform {
|
||||
|
||||
public:
|
||||
StackPromotion() {}
|
||||
|
||||
StackPromoter(SILFunction *F, EscapeAnalysis::ConnectionGraph *ConGraph,
|
||||
DominanceInfo *DT, EscapeAnalysis *EA) :
|
||||
F(F), ConGraph(ConGraph), DT(DT), EA(EA), PostDomTree(true),
|
||||
PostDomTreeValid(false) { }
|
||||
private:
|
||||
/// The entry point to the transformation.
|
||||
void run() override;
|
||||
|
||||
SILFunction *getFunction() const { return F; }
|
||||
/// Promotes allocations in \p BB.
|
||||
bool promoteInBlock(SILBasicBlock *BB, EscapeAnalysis *EA,
|
||||
DeadEndBlocks &DEBlocks);
|
||||
|
||||
/// The main entry point for the optimization.
|
||||
///
|
||||
/// Returns true if some changes were made.
|
||||
bool promote();
|
||||
/// Tries to promote the allocation \p ARI.
|
||||
bool tryPromoteAlloc(AllocRefInst *ARI, EscapeAnalysis *EA,
|
||||
DeadEndBlocks &DEBlocks);
|
||||
};
|
||||
|
||||
bool StackPromoter::promote() {
|
||||
|
||||
llvm::SetVector<SILBasicBlock *> ReachableBlocks;
|
||||
|
||||
// First step: find blocks which end up in a no-return block (terminated by
|
||||
// an unreachable instruction).
|
||||
// Search for function-exiting blocks, i.e. return and throw.
|
||||
for (SILBasicBlock &BB : *F) {
|
||||
TermInst *TI = BB.getTerminator();
|
||||
if (TI->isFunctionExiting())
|
||||
ReachableBlocks.insert(&BB);
|
||||
}
|
||||
// Propagate the reachability up the control flow graph.
|
||||
unsigned Idx = 0;
|
||||
while (Idx < ReachableBlocks.size()) {
|
||||
SILBasicBlock *BB = ReachableBlocks[Idx++];
|
||||
for (SILBasicBlock *Pred : BB->getPredecessorBlocks())
|
||||
ReachableBlocks.insert(Pred);
|
||||
}
|
||||
void StackPromotion::run() {
|
||||
SILFunction *F = getFunction();
|
||||
DEBUG(llvm::dbgs() << "** StackPromotion in " << F->getName() << " **\n");
|
||||
|
||||
auto *EA = PM->getAnalysis<EscapeAnalysis>();
|
||||
DeadEndBlocks DEBlocks(F);
|
||||
bool Changed = false;
|
||||
|
||||
// Search the whole function for stack promotable allocations.
|
||||
for (SILBasicBlock &BB : *F) {
|
||||
Changed |= promoteInBlock(&BB, EA, DEBlocks);
|
||||
}
|
||||
if (!Changed)
|
||||
return;
|
||||
|
||||
// Don't stack promote any allocation inside a code region which ends up in
|
||||
// a no-return block. Such allocations may missing their final release.
|
||||
// We would insert the deallocation too early, which may result in a
|
||||
// use-after-free problem.
|
||||
if (ReachableBlocks.count(&BB) == 0)
|
||||
continue;
|
||||
// Make sure that all stack allocating instructions are nested correctly.
|
||||
StackNesting SN;
|
||||
if (SN.correctStackNesting(F) == StackNesting::Changes::CFG) {
|
||||
invalidateAnalysis(SILAnalysis::InvalidationKind::BranchesAndInstructions);
|
||||
} else {
|
||||
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto Iter = BB.begin(); Iter != BB.end();) {
|
||||
// The allocation instruction may be moved, so increment Iter prior to
|
||||
// doing the optimization.
|
||||
SILInstruction *I = &*Iter++;
|
||||
if (auto *ARI = dyn_cast<AllocRefInst>(I)) {
|
||||
Changed |= tryPromoteAlloc(ARI);
|
||||
}
|
||||
bool StackPromotion::promoteInBlock(SILBasicBlock *BB, EscapeAnalysis *EA,
|
||||
DeadEndBlocks &DEBlocks) {
|
||||
bool Changed = false;
|
||||
for (auto Iter = BB->begin(); Iter != BB->end();) {
|
||||
// The allocation instruction may be moved, so increment Iter prior to
|
||||
// doing the optimization.
|
||||
SILInstruction *I = &*Iter++;
|
||||
if (auto *ARI = dyn_cast<AllocRefInst>(I)) {
|
||||
// Don't stack promote any allocation inside a code region which ends up
|
||||
// in a no-return block. Such allocations may missing their final release.
|
||||
// We would insert the deallocation too early, which may result in a
|
||||
// use-after-free problem.
|
||||
if (DEBlocks.isDeadEnd(BB))
|
||||
return false;
|
||||
|
||||
Changed |= tryPromoteAlloc(ARI, EA, DEBlocks);
|
||||
}
|
||||
}
|
||||
return Changed;
|
||||
}
|
||||
|
||||
bool StackPromoter::tryPromoteAlloc(AllocRefInst *ARI) {
|
||||
|
||||
SILInstruction *AllocInsertionPoint = nullptr;
|
||||
SILInstruction *DeallocInsertionPoint = nullptr;
|
||||
if (!canPromoteAlloc(ARI, AllocInsertionPoint, DeallocInsertionPoint))
|
||||
return false;
|
||||
|
||||
if (AllocInsertionPoint) {
|
||||
// Check if any operands of the alloc_ref prevents us from moving the
|
||||
// instruction.
|
||||
for (const Operand &Op : ARI->getAllOperands()) {
|
||||
if (!DT->properlyDominates(Op.get(), AllocInsertionPoint))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG(llvm::dbgs() << "Promoted " << *ARI);
|
||||
DEBUG(llvm::dbgs() << " in " << ARI->getFunction()->getName() << '\n');
|
||||
NumStackPromoted++;
|
||||
|
||||
SILBuilder B(DeallocInsertionPoint);
|
||||
// It's an object allocation. We set the [stack] attribute in the alloc_ref.
|
||||
ARI->setStackAllocatable();
|
||||
if (AllocInsertionPoint)
|
||||
ARI->moveBefore(AllocInsertionPoint);
|
||||
|
||||
/// And create a dealloc_ref [stack] at the end of the object's lifetime.
|
||||
B.createDeallocRef(ARI->getLoc(), ARI, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
/// Iterator which iterates over all basic blocks of a function which are not
|
||||
/// terminated by an unreachable inst.
|
||||
class NonUnreachableBlockIter :
|
||||
public std::iterator<std::forward_iterator_tag, SILBasicBlock, ptrdiff_t> {
|
||||
|
||||
SILFunction::iterator BaseIterator;
|
||||
SILFunction::iterator End;
|
||||
|
||||
void skipUnreachables() {
|
||||
while (true) {
|
||||
if (BaseIterator == End)
|
||||
return;
|
||||
if (!isa<UnreachableInst>(BaseIterator->getTerminator()))
|
||||
return;
|
||||
BaseIterator++;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
NonUnreachableBlockIter(SILFunction::iterator BaseIterator,
|
||||
SILFunction::iterator End) :
|
||||
BaseIterator(BaseIterator), End(End) {
|
||||
skipUnreachables();
|
||||
}
|
||||
|
||||
NonUnreachableBlockIter() = default;
|
||||
|
||||
SILBasicBlock &operator*() const { return *BaseIterator; }
|
||||
SILBasicBlock &operator->() const { return *BaseIterator; }
|
||||
|
||||
NonUnreachableBlockIter &operator++() {
|
||||
BaseIterator++;
|
||||
skipUnreachables();
|
||||
return *this;
|
||||
}
|
||||
|
||||
NonUnreachableBlockIter operator++(int unused) {
|
||||
NonUnreachableBlockIter Copy = *this;
|
||||
++*this;
|
||||
return Copy;
|
||||
}
|
||||
|
||||
friend bool operator==(NonUnreachableBlockIter lhs,
|
||||
NonUnreachableBlockIter rhs) {
|
||||
return lhs.BaseIterator == rhs.BaseIterator;
|
||||
}
|
||||
};
|
||||
} // end anonymous namespace
|
||||
|
||||
namespace llvm {
|
||||
|
||||
/// Use the StackPromoter as a wrapper for the function. It holds the list of
|
||||
/// basic blocks excluding all unreachable blocks.
|
||||
template <> struct GraphTraits<StackPromoter *>
|
||||
: public GraphTraits<swift::SILBasicBlock*> {
|
||||
typedef StackPromoter *GraphType;
|
||||
typedef swift::SILBasicBlock *NodeRef;
|
||||
|
||||
static NodeRef getEntryNode(GraphType SP) {
|
||||
return &SP->getFunction()->front();
|
||||
}
|
||||
|
||||
typedef pointer_iterator<NonUnreachableBlockIter> nodes_iterator;
|
||||
static nodes_iterator nodes_begin(GraphType SP) {
|
||||
return nodes_iterator(NonUnreachableBlockIter(SP->getFunction()->begin(),
|
||||
SP->getFunction()->end()));
|
||||
}
|
||||
static nodes_iterator nodes_end(GraphType SP) {
|
||||
return nodes_iterator(NonUnreachableBlockIter(SP->getFunction()->end(),
|
||||
SP->getFunction()->end()));
|
||||
}
|
||||
static unsigned size(GraphType SP) {
|
||||
return std::distance(SP->getFunction()->begin(), SP->getFunction()->end());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace llvm
|
||||
|
||||
bool StackPromoter::canPromoteAlloc(AllocRefInst *ARI,
|
||||
SILInstruction *&AllocInsertionPoint,
|
||||
SILInstruction *&DeallocInsertionPoint) {
|
||||
bool StackPromotion::tryPromoteAlloc(AllocRefInst *ARI, EscapeAnalysis *EA,
|
||||
DeadEndBlocks &DEBlocks) {
|
||||
if (ARI->isObjC() || ARI->canAllocOnStack())
|
||||
return false;
|
||||
|
||||
AllocInsertionPoint = nullptr;
|
||||
DeallocInsertionPoint = nullptr;
|
||||
auto *ConGraph = EA->getConnectionGraph(ARI->getFunction());
|
||||
auto *Node = ConGraph->getNodeOrNull(ARI, EA);
|
||||
if (!Node)
|
||||
return false;
|
||||
@@ -348,229 +109,55 @@ bool StackPromoter::canPromoteAlloc(AllocRefInst *ARI,
|
||||
if (Node->escapes())
|
||||
return false;
|
||||
|
||||
// Now we have to determine the lifetime of the allocated object in its
|
||||
// function.
|
||||
DEBUG(llvm::dbgs() << "Promote " << *ARI);
|
||||
|
||||
// Get all interesting uses of the object (e.g. release instructions). This
|
||||
// includes uses of objects where the allocation is stored to.
|
||||
int NumUsePointsToFind = ConGraph->getNumUsePoints(Node);
|
||||
if (NumUsePointsToFind == 0) {
|
||||
// There should always be at least one release for an allocated object.
|
||||
// But in case all paths from this block end in unreachable then the
|
||||
// final release of the object may be optimized away. We bail out in this
|
||||
// case.
|
||||
// Collect all use-points of the allocation. These are refcount instructions
|
||||
// and apply instructions.
|
||||
llvm::SmallVector<ValueBase *, 8> BaseUsePoints;
|
||||
llvm::SmallVector<SILInstruction *, 8> UsePoints;
|
||||
ConGraph->getUsePoints(Node, BaseUsePoints);
|
||||
for (ValueBase *UsePoint : BaseUsePoints) {
|
||||
if (SILInstruction *I = dyn_cast<SILInstruction>(UsePoint)) {
|
||||
UsePoints.push_back(I);
|
||||
} else {
|
||||
// Also block arguments can be use points.
|
||||
SILBasicBlock *UseBB = cast<SILPHIArgument>(UsePoint)->getParent();
|
||||
// For simplicity we just add the first instruction of the block as use
|
||||
// point.
|
||||
UsePoints.push_back(&UseBB->front());
|
||||
}
|
||||
}
|
||||
|
||||
ValueLifetimeAnalysis VLA(ARI, UsePoints);
|
||||
// Check if there is a use point before the allocation (this can happen e.g.
|
||||
// if the allocated object is stored into another object, which is already
|
||||
// alive at the allocation point).
|
||||
// In such a case the value lifetime extends up to the function entry.
|
||||
if (VLA.isAliveAtBeginOfBlock(ARI->getFunction()->getEntryBlock())) {
|
||||
DEBUG(llvm::dbgs() << " use before allocation -> don't promote");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to find the point where to insert the deallocation.
|
||||
// This might need more than one try in case we need to move the allocation
|
||||
// out of a stack-alloc-dealloc pair. See findDeallocPoint().
|
||||
SILInstruction *StartInst = ARI;
|
||||
for (;;) {
|
||||
SILInstruction *RestartPoint = nullptr;
|
||||
DeallocInsertionPoint = findDeallocPoint(StartInst, RestartPoint, Node,
|
||||
NumUsePointsToFind);
|
||||
if (DeallocInsertionPoint)
|
||||
return true;
|
||||
|
||||
if (!RestartPoint)
|
||||
return false;
|
||||
|
||||
// Retry with moving the allocation up.
|
||||
AllocInsertionPoint = RestartPoint;
|
||||
StartInst = RestartPoint;
|
||||
// Compute the places where the lifetime of the object ends.
|
||||
ValueLifetimeAnalysis::Frontier Frontier;
|
||||
if (!VLA.computeFrontier(Frontier, ValueLifetimeAnalysis::UsersMustPostDomDef,
|
||||
&DEBlocks)) {
|
||||
DEBUG(llvm::dbgs() << " uses don't post-dom allocation -> don't promote");
|
||||
return false;
|
||||
}
|
||||
NumStackPromoted++;
|
||||
|
||||
// We set the [stack] attribute in the alloc_ref.
|
||||
ARI->setStackAllocatable();
|
||||
|
||||
/// And create dealloc_ref [stack] at the end of the object's lifetime.
|
||||
for (SILInstruction *FrontierInst : Frontier) {
|
||||
SILBuilder B(FrontierInst);
|
||||
B.createDeallocRef(ARI->getLoc(), ARI, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
SILInstruction *StackPromoter::findDeallocPoint(SILInstruction *StartInst,
|
||||
SILInstruction *&RestartPoint,
|
||||
EscapeAnalysis::CGNode *Node,
|
||||
int NumUsePointsToFind) {
|
||||
// In the following we check two requirements for stack promotion:
|
||||
// 1) Are all uses in the same control region as the alloc? E.g. if the
|
||||
// allocation is in a loop then there may not be any uses of the object
|
||||
// outside the loop.
|
||||
// 2) We need to find an insertion place for the deallocation so that it
|
||||
// preserves a properly nested stack allocation-deallocation structure.
|
||||
SILBasicBlock *StartBlock = StartInst->getParent();
|
||||
|
||||
// The block where we assume we can insert the deallocation.
|
||||
SILBasicBlock *EndBlock = StartBlock;
|
||||
|
||||
// We visit all instructions starting at the allocation instruction.
|
||||
WorkListType WorkList;
|
||||
// It's important that the EndBlock is at the head of the WorkList so that
|
||||
// we handle it after all other blocks.
|
||||
WorkList.insert(EndBlock, -1);
|
||||
WorkList.insert(StartBlock, 0);
|
||||
|
||||
for (;;) {
|
||||
SILBasicBlock *BB = WorkList.pop_back_val();
|
||||
int StackDepth = 0;
|
||||
SILBasicBlock::iterator Iter;
|
||||
if (BB == StartBlock) {
|
||||
// In the first block we start at the allocation instruction and not at
|
||||
// the begin of the block.
|
||||
Iter = StartInst->getIterator();
|
||||
} else {
|
||||
// Track all uses in the block arguments.
|
||||
for (SILArgument *BBArg : BB->getArguments()) {
|
||||
if (ConGraph->isUsePoint(BBArg, Node))
|
||||
NumUsePointsToFind--;
|
||||
}
|
||||
// Make sure that the EndBlock is not inside a loop (which does not
|
||||
// contain the StartBlock).
|
||||
// E.g.:
|
||||
// %obj = alloc_ref // the allocation
|
||||
// br loop
|
||||
// loop:
|
||||
// the_only_use_of_obj(%obj)
|
||||
// cond_br ..., loop, exit
|
||||
// exit:
|
||||
// ... // this is the new EndBlock
|
||||
EndBlock = updateEndBlock(BB, EndBlock, WorkList);
|
||||
if (!EndBlock)
|
||||
return nullptr;
|
||||
Iter = BB->begin();
|
||||
StackDepth = WorkList.getStackDepth(BB);
|
||||
}
|
||||
// Visit all instructions of the current block.
|
||||
while (Iter != BB->end()) {
|
||||
SILInstruction &I = *Iter++;
|
||||
if (BB == EndBlock && StackDepth == 0 && NumUsePointsToFind == 0) {
|
||||
// We found a place to insert the stack deallocation.
|
||||
return &I;
|
||||
}
|
||||
if (I.isAllocatingStack()) {
|
||||
StackDepth++;
|
||||
} else if (I.isDeallocatingStack()) {
|
||||
if (StackDepth == 0) {
|
||||
// The allocation is inside a stack alloc-dealloc region and we are
|
||||
// now leaving this region without having found a place for the
|
||||
// deallocation. E.g.
|
||||
// E.g.:
|
||||
// %1 = alloc_stack
|
||||
// %obj = alloc_ref // the allocation
|
||||
// dealloc_stack %1
|
||||
// use_of_obj(%obj)
|
||||
//
|
||||
// In this case we can move the alloc_ref before the alloc_stack
|
||||
// to fix the nesting.
|
||||
auto *Alloc = dyn_cast<SILInstruction>(I.getOperand(0));
|
||||
if (!Alloc)
|
||||
return nullptr;
|
||||
|
||||
// This should always be the case, but let's be on the safe side.
|
||||
if (!postDominates(StartBlock, Alloc->getParent()))
|
||||
return nullptr;
|
||||
|
||||
// Trigger another iteration with a new start point;
|
||||
RestartPoint = Alloc;
|
||||
return nullptr;
|
||||
}
|
||||
StackDepth--;
|
||||
}
|
||||
// Track a use.
|
||||
if (ConGraph->isUsePoint(&I, Node) != 0)
|
||||
NumUsePointsToFind--;
|
||||
}
|
||||
if (WorkList.empty()) {
|
||||
if (EndBlock == BB) {
|
||||
// We reached the EndBlock but didn't find a place for the deallocation
|
||||
// so far (because we didn't find all uses yet or we entered another
|
||||
// stack alloc-dealloc region). Let's extend our lifetime region.
|
||||
// E.g.:
|
||||
// %obj = alloc_ref // the allocation
|
||||
// %1 = alloc_stack
|
||||
// use_of_obj(%obj) // can't insert the deallocation in this block
|
||||
// cond_br ..., bb1, bb2
|
||||
// bb1:
|
||||
// ...
|
||||
// br bb2
|
||||
// bb2:
|
||||
// dealloc_stack %1 // this is the new EndBlock
|
||||
EndBlock = getImmediatePostDom(EndBlock);
|
||||
if (!EndBlock)
|
||||
return nullptr;
|
||||
}
|
||||
// Again, it's important that the EndBlock is the first in the WorkList.
|
||||
WorkList.insert(EndBlock, -1);
|
||||
}
|
||||
// Push the successor blocks to the WorkList.
|
||||
for (SILBasicBlock *Succ : BB->getSuccessors()) {
|
||||
if (!strictlyDominates(StartBlock, Succ)) {
|
||||
// The StartBlock is inside a loop but we couldn't find a deallocation
|
||||
// place in this loop, e.g. because there are uses outside the loop.
|
||||
// E.g.:
|
||||
// %container = alloc_ref
|
||||
// br loop
|
||||
// loop:
|
||||
// %obj = alloc_ref // the allocation
|
||||
// store %obj to %some_field_in_container
|
||||
// cond_br ..., loop, exit
|
||||
// exit:
|
||||
// use(%container)
|
||||
return nullptr;
|
||||
}
|
||||
WorkList.insert(Succ, StackDepth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SILBasicBlock *StackPromoter::updateEndBlock(SILBasicBlock *CurrentBB,
|
||||
SILBasicBlock *EndBlock,
|
||||
WorkListType &WorkList) {
|
||||
llvm::SmallVector<SILBasicBlock *, 8> PredsToHandle;
|
||||
PredsToHandle.push_back(CurrentBB);
|
||||
|
||||
// Starting from BB, go back the control flow graph until we reach already
|
||||
// handled blocks.
|
||||
while (!PredsToHandle.empty()) {
|
||||
SILBasicBlock *BB = PredsToHandle.pop_back_val();
|
||||
for (SILBasicBlock *Pred : BB->getPredecessorBlocks()) {
|
||||
// Make sure that the EndBlock post-dominates all blocks we are visiting.
|
||||
while (!strictlyPostDominates(EndBlock, Pred)) {
|
||||
EndBlock = getImmediatePostDom(EndBlock);
|
||||
if (!EndBlock)
|
||||
return nullptr;
|
||||
}
|
||||
if (WorkList.insertAsUnhandled(Pred))
|
||||
PredsToHandle.push_back(Pred);
|
||||
}
|
||||
}
|
||||
return EndBlock;
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Top Level Driver
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
namespace {
|
||||
|
||||
class StackPromotion : public SILFunctionTransform {
|
||||
|
||||
public:
|
||||
StackPromotion() {}
|
||||
|
||||
private:
|
||||
/// The entry point to the transformation.
|
||||
void run() override {
|
||||
DEBUG(llvm::dbgs() << "** StackPromotion **\n");
|
||||
|
||||
auto *EA = PM->getAnalysis<EscapeAnalysis>();
|
||||
auto *DA = PM->getAnalysis<DominanceAnalysis>();
|
||||
|
||||
SILFunction *F = getFunction();
|
||||
if (auto *ConGraph = EA->getConnectionGraph(F)) {
|
||||
StackPromoter promoter(F, ConGraph, DA->get(F), EA);
|
||||
if (promoter.promote()) {
|
||||
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
SILTransform *swift::createStackPromotion() {
|
||||
|
||||
Reference in New Issue
Block a user