mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
Merge remote-tracking branch 'origin/master' into master-next
This commit is contained in:
@@ -377,6 +377,9 @@ public:
|
||||
/// NodeType::Return.
|
||||
CGNode *ReturnNode = nullptr;
|
||||
|
||||
/// The list of use points.
|
||||
llvm::SmallVector<ValueBase *, 16> UsePointTable;
|
||||
|
||||
/// Mapping of use points to bit indices in CGNode::UsePoints.
|
||||
llvm::DenseMap<ValueBase *, int> UsePoints;
|
||||
|
||||
@@ -490,6 +493,8 @@ public:
|
||||
int Idx = (int)UsePoints.size();
|
||||
assert(UsePoints.count(V) == 0 && "value is already a use-point");
|
||||
UsePoints[V] = Idx;
|
||||
UsePointTable.push_back(V);
|
||||
assert(UsePoints.size() == UsePointTable.size());
|
||||
Node->setUsePointBit(Idx);
|
||||
return Idx;
|
||||
}
|
||||
@@ -568,6 +573,10 @@ public:
|
||||
/// e.g. release or apply instructions.
|
||||
bool isUsePoint(ValueBase *V, CGNode *Node);
|
||||
|
||||
/// Returns all use points of \p Node in \p UsePoints.
|
||||
void getUsePoints(CGNode *Node,
|
||||
llvm::SmallVectorImpl<ValueBase *> &UsePoints);
|
||||
|
||||
/// Computes the use point information.
|
||||
void computeUsePoints();
|
||||
|
||||
|
||||
@@ -201,6 +201,32 @@ void releasePartialApplyCapturedArg(
|
||||
SILBuilder &Builder, SILLocation Loc, SILValue Arg, SILParameterInfo PInfo,
|
||||
InstModCallbacks Callbacks = InstModCallbacks());
|
||||
|
||||
/// A utility for finding dead-end blocks.
|
||||
///
|
||||
/// Dead-end blocks are blocks from which there is no path to the function exit
|
||||
/// (either return or throw). These are blocks which end with an unreachable
|
||||
/// instruction and blocks from which all paths end in "unreachable" blocks.
|
||||
class DeadEndBlocks {
|
||||
llvm::SetVector<SILBasicBlock *> ReachableBlocks;
|
||||
SILFunction *F;
|
||||
bool isComputed = false;
|
||||
|
||||
void compute();
|
||||
|
||||
public:
|
||||
DeadEndBlocks(SILFunction *F) : F(F) {}
|
||||
|
||||
/// Returns true if \p BB is a dead-end block.
|
||||
bool isDeadEnd(SILBasicBlock *BB) {
|
||||
if (!isComputed) {
|
||||
// Lazily compute the dataflow.
|
||||
compute();
|
||||
isComputed = true;
|
||||
}
|
||||
return ReachableBlocks.count(BB) == 0;
|
||||
}
|
||||
};
|
||||
|
||||
/// This computes the lifetime of a single SILValue.
|
||||
///
|
||||
/// This does not compute a set of jointly postdominating use points. Instead it
|
||||
@@ -218,13 +244,13 @@ public:
|
||||
|
||||
/// Constructor for the value \p Def with a specific set of users of Def's
|
||||
/// users.
|
||||
ValueLifetimeAnalysis(SILValue Def, ArrayRef<SILInstruction*> UserList) :
|
||||
ValueLifetimeAnalysis(SILInstruction *Def, ArrayRef<SILInstruction*> UserList) :
|
||||
DefValue(Def), UserSet(UserList.begin(), UserList.end()) {
|
||||
propagateLiveness();
|
||||
}
|
||||
|
||||
/// Constructor for the value \p Def considering all the value's uses.
|
||||
ValueLifetimeAnalysis(SILValue Def) : DefValue(Def) {
|
||||
ValueLifetimeAnalysis(SILInstruction *Def) : DefValue(Def) {
|
||||
for (Operand *Op : Def->getUses()) {
|
||||
UserSet.insert(Op->getUser());
|
||||
}
|
||||
@@ -240,30 +266,42 @@ public:
|
||||
/// a critical edges.
|
||||
AllowToModifyCFG,
|
||||
|
||||
/// Ignore exit edges from the lifetime region at all.
|
||||
IgnoreExitEdges
|
||||
/// Require that all users must commonly post-dominate the definition. In
|
||||
/// other words: All paths from the definition to the function exit must
|
||||
/// contain at least one use. Fail if this is not the case.
|
||||
UsersMustPostDomDef
|
||||
};
|
||||
|
||||
/// Computes and returns the lifetime frontier for the value in \p Fr.
|
||||
///
|
||||
/// Returns true if all instructions in the frontier could be found in
|
||||
/// non-critical edges.
|
||||
/// Returns false if some frontier instructions are located on critical edges.
|
||||
/// In this case, if \p mode is AllowToModifyCFG, those critical edges are
|
||||
/// split, otherwise nothing is done and the returned \p Fr is not valid.
|
||||
bool computeFrontier(Frontier &Fr, Mode mode);
|
||||
///
|
||||
/// If \p DEBlocks is provided, all dead-end blocks are ignored. This prevents
|
||||
/// unreachable-blocks to be included in the frontier.
|
||||
bool computeFrontier(Frontier &Fr, Mode mode,
|
||||
DeadEndBlocks *DEBlocks = nullptr);
|
||||
|
||||
/// Returns true if the instruction \p Inst is located within the value's
|
||||
/// lifetime.
|
||||
/// It is assumed that \p Inst is located after the value's definition.
|
||||
bool isWithinLifetime(SILInstruction *Inst);
|
||||
|
||||
/// Returns true if the value is alive at the begin of block \p BB.
|
||||
bool isAliveAtBeginOfBlock(SILBasicBlock *BB) {
|
||||
return LiveBlocks.count(BB) && BB != DefValue->getParentBlock();
|
||||
}
|
||||
|
||||
/// For debug dumping.
|
||||
void dump() const;
|
||||
|
||||
private:
|
||||
|
||||
/// The value.
|
||||
SILValue DefValue;
|
||||
SILInstruction *DefValue;
|
||||
|
||||
/// The set of blocks where the value is live.
|
||||
llvm::SmallSetVector<SILBasicBlock *, 16> LiveBlocks;
|
||||
@@ -277,11 +315,6 @@ private:
|
||||
|
||||
/// Returns the last use of the value in the live block \p BB.
|
||||
SILInstruction *findLastUserInBlock(SILBasicBlock *BB);
|
||||
|
||||
/// Returns true if the value is alive at the begin of block \p BB.
|
||||
bool isAliveAtBeginOfBlock(SILBasicBlock *BB) {
|
||||
return LiveBlocks.count(BB) && BB != DefValue->getParentBlock();
|
||||
}
|
||||
};
|
||||
|
||||
/// Base class for BB cloners.
|
||||
|
||||
@@ -87,6 +87,7 @@ void EscapeAnalysis::ConnectionGraph::clear() {
|
||||
Nodes.clear();
|
||||
ReturnNode = nullptr;
|
||||
UsePoints.clear();
|
||||
UsePointTable.clear();
|
||||
NodeAllocator.DestroyAll();
|
||||
assert(ToMerge.empty());
|
||||
}
|
||||
@@ -547,6 +548,16 @@ bool EscapeAnalysis::ConnectionGraph::isUsePoint(ValueBase *V, CGNode *Node) {
|
||||
return Node->UsePoints.test(Idx);
|
||||
}
|
||||
|
||||
void EscapeAnalysis::ConnectionGraph::
|
||||
getUsePoints(CGNode *Node, llvm::SmallVectorImpl<ValueBase *> &UsePoints) {
|
||||
assert(Node->getEscapeState() < EscapeState::Global &&
|
||||
"Use points are only valid for non-escaping nodes");
|
||||
for (int Idx = Node->UsePoints.find_first(); Idx >= 0;
|
||||
Idx = Node->UsePoints.find_next(Idx)) {
|
||||
UsePoints.push_back(UsePointTable[Idx]);
|
||||
}
|
||||
}
|
||||
|
||||
bool EscapeAnalysis::ConnectionGraph::isReachable(CGNode *From, CGNode *To) {
|
||||
// See if we can reach the From-node by transitively visiting the
|
||||
// predecessor nodes of the To-node.
|
||||
@@ -879,11 +890,6 @@ void EscapeAnalysis::ConnectionGraph::print(llvm::raw_ostream &OS) const {
|
||||
}
|
||||
sortNodes(SortedNodes);
|
||||
|
||||
llvm::DenseMap<int, ValueBase *> Idx2UsePoint;
|
||||
for (auto Iter : UsePoints) {
|
||||
Idx2UsePoint[Iter.second] = Iter.first;
|
||||
}
|
||||
|
||||
for (CGNode *Nd : SortedNodes) {
|
||||
OS << " " << Nd->getTypeStr() << ' ' << NodeStr(Nd) << " Esc: ";
|
||||
switch (Nd->getEscapeState()) {
|
||||
@@ -891,7 +897,7 @@ void EscapeAnalysis::ConnectionGraph::print(llvm::raw_ostream &OS) const {
|
||||
const char *Separator = "";
|
||||
for (unsigned VIdx = Nd->UsePoints.find_first(); VIdx != -1u;
|
||||
VIdx = Nd->UsePoints.find_next(VIdx)) {
|
||||
ValueBase *V = Idx2UsePoint[VIdx];
|
||||
ValueBase *V = UsePointTable[VIdx];
|
||||
OS << Separator << '%' << InstToIDMap[V];
|
||||
Separator = ",";
|
||||
}
|
||||
|
||||
@@ -531,7 +531,8 @@ static void insertReleases(ArrayRef<StoreInst*> Stores,
|
||||
// TODO: This relies on the lowest level array.uninitialized not being
|
||||
// inlined. To do better we could either run this pass before semantic inlining,
|
||||
// or we could also handle calls to array.init.
|
||||
static bool removeAndReleaseArray(SILValue NewArrayValue, bool &CFGChanged) {
|
||||
static bool removeAndReleaseArray(SILInstruction *NewArrayValue,
|
||||
DeadEndBlocks &DEBlocks) {
|
||||
TupleExtractInst *ArrayDef = nullptr;
|
||||
TupleExtractInst *StorageAddress = nullptr;
|
||||
for (auto *Op : NewArrayValue->getUses()) {
|
||||
@@ -586,12 +587,15 @@ static bool removeAndReleaseArray(SILValue NewArrayValue, bool &CFGChanged) {
|
||||
return false;
|
||||
}
|
||||
// For each store location, insert releases.
|
||||
// This makes a strong assumption that the allocated object is released on all
|
||||
// paths in which some object initialization occurs.
|
||||
SILSSAUpdater SSAUp;
|
||||
ValueLifetimeAnalysis::Frontier ArrayFrontier;
|
||||
CFGChanged |= !VLA.computeFrontier(ArrayFrontier,
|
||||
ValueLifetimeAnalysis::IgnoreExitEdges);
|
||||
if (!VLA.computeFrontier(ArrayFrontier,
|
||||
ValueLifetimeAnalysis::UsersMustPostDomDef,
|
||||
&DEBlocks)) {
|
||||
// In theory the allocated object must be released on all paths in which
|
||||
// some object initialization occurs. If not (for some reason) we bail.
|
||||
return false;
|
||||
}
|
||||
|
||||
DeadStorage.visitStoreLocations([&] (ArrayRef<StoreInst*> Stores) {
|
||||
insertReleases(Stores, ArrayFrontier, SSAUp);
|
||||
@@ -619,7 +623,6 @@ namespace {
|
||||
class DeadObjectElimination : public SILFunctionTransform {
|
||||
llvm::DenseMap<SILType, bool> DestructorAnalysisCache;
|
||||
llvm::SmallVector<SILInstruction*, 16> Allocations;
|
||||
bool CFGChanged = false;
|
||||
|
||||
void collectAllocations(SILFunction &Fn) {
|
||||
for (auto &BB : Fn)
|
||||
@@ -634,9 +637,10 @@ class DeadObjectElimination : public SILFunctionTransform {
|
||||
bool processAllocRef(AllocRefInst *ARI);
|
||||
bool processAllocStack(AllocStackInst *ASI);
|
||||
bool processAllocBox(AllocBoxInst *ABI){ return false;}
|
||||
bool processAllocApply(ApplyInst *AI);
|
||||
bool processAllocApply(ApplyInst *AI, DeadEndBlocks &DEBlocks);
|
||||
|
||||
bool processFunction(SILFunction &Fn) {
|
||||
DeadEndBlocks DEBlocks(&Fn);
|
||||
Allocations.clear();
|
||||
DestructorAnalysisCache.clear();
|
||||
bool Changed = false;
|
||||
@@ -649,17 +653,14 @@ class DeadObjectElimination : public SILFunctionTransform {
|
||||
else if (auto *A = dyn_cast<AllocBoxInst>(II))
|
||||
Changed |= processAllocBox(A);
|
||||
else if (auto *A = dyn_cast<ApplyInst>(II))
|
||||
Changed |= processAllocApply(A);
|
||||
Changed |= processAllocApply(A, DEBlocks);
|
||||
}
|
||||
return Changed;
|
||||
}
|
||||
|
||||
void run() override {
|
||||
CFGChanged = false;
|
||||
if (processFunction(*getFunction())) {
|
||||
invalidateAnalysis(CFGChanged ?
|
||||
SILAnalysis::InvalidationKind::FunctionBody :
|
||||
SILAnalysis::InvalidationKind::CallsAndInstructions);
|
||||
invalidateAnalysis(SILAnalysis::InvalidationKind::CallsAndInstructions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -730,7 +731,8 @@ bool DeadObjectElimination::processAllocStack(AllocStackInst *ASI) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeadObjectElimination::processAllocApply(ApplyInst *AI) {
|
||||
bool DeadObjectElimination::processAllocApply(ApplyInst *AI,
|
||||
DeadEndBlocks &DEBlocks) {
|
||||
// Currently only handle array.uninitialized
|
||||
if (ArraySemanticsCall(AI).getKind() != ArrayCallKind::kArrayUninitialized)
|
||||
return false;
|
||||
@@ -746,7 +748,7 @@ bool DeadObjectElimination::processAllocApply(ApplyInst *AI) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!removeAndReleaseArray(AI, CFGChanged))
|
||||
if (!removeAndReleaseArray(AI, DEBlocks))
|
||||
return false;
|
||||
|
||||
DEBUG(llvm::dbgs() << " Success! Eliminating apply allocate(...).\n");
|
||||
|
||||
@@ -13,12 +13,11 @@
|
||||
#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"
|
||||
|
||||
#define DEBUG_TYPE "stack-promotion"
|
||||
@@ -27,320 +26,82 @@ 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.
|
||||
PostDominatorTreeBase 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;
|
||||
class StackPromotion : public SILFunctionTransform {
|
||||
|
||||
public:
|
||||
bool empty() const { return ToHandle.empty(); }
|
||||
StackPromotion() {}
|
||||
|
||||
SILBasicBlock *pop_back_val() { return ToHandle.pop_back_val(); }
|
||||
private:
|
||||
/// The entry point to the transformation.
|
||||
void run() override;
|
||||
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
/// Promotes allocations in \p BB.
|
||||
bool promoteInBlock(SILBasicBlock *BB, EscapeAnalysis *EA,
|
||||
DeadEndBlocks &DEBlocks);
|
||||
|
||||
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 ARI.
|
||||
bool tryPromoteAlloc(AllocRefInst *ARI, EscapeAnalysis *EA,
|
||||
DeadEndBlocks &DEBlocks);
|
||||
};
|
||||
|
||||
/// 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(*F);
|
||||
PostDomTreeValid = true;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
StackPromoter(SILFunction *F, EscapeAnalysis::ConnectionGraph *ConGraph,
|
||||
DominanceInfo *DT, EscapeAnalysis *EA) :
|
||||
F(F), ConGraph(ConGraph), DT(DT), EA(EA),
|
||||
PostDomTreeValid(false) { }
|
||||
|
||||
SILFunction *getFunction() const { return F; }
|
||||
|
||||
/// The main entry point for the optimization.
|
||||
///
|
||||
/// Returns true if some changes were made.
|
||||
bool promote();
|
||||
};
|
||||
|
||||
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();) {
|
||||
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)) {
|
||||
Changed |= tryPromoteAlloc(ARI);
|
||||
}
|
||||
// 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;
|
||||
@@ -349,228 +110,54 @@ 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.
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
// 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 {
|
||||
// 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);
|
||||
}
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
|
||||
@@ -1023,6 +1023,30 @@ bool swift::tryDeleteDeadClosure(SILInstruction *Closure,
|
||||
return true;
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// DeadEndBlocks
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
void DeadEndBlocks::compute() {
|
||||
assert(ReachableBlocks.empty() && "Computed twice");
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Value Lifetime
|
||||
//===----------------------------------------------------------------------===//
|
||||
@@ -1032,6 +1056,7 @@ void ValueLifetimeAnalysis::propagateLiveness() {
|
||||
|
||||
auto DefBB = DefValue->getParentBlock();
|
||||
llvm::SmallVector<SILBasicBlock *, 64> Worklist;
|
||||
int NumUsersBeforeDef = 0;
|
||||
|
||||
// Find the initial set of blocks where the value is live, because
|
||||
// it is used in those blocks.
|
||||
@@ -1039,6 +1064,17 @@ void ValueLifetimeAnalysis::propagateLiveness() {
|
||||
SILBasicBlock *UserBlock = User->getParent();
|
||||
if (LiveBlocks.insert(UserBlock))
|
||||
Worklist.push_back(UserBlock);
|
||||
|
||||
// A user in the DefBB could potentially be located before the DefValue.
|
||||
if (UserBlock == DefBB)
|
||||
NumUsersBeforeDef++;
|
||||
}
|
||||
// Don't count any users in the DefBB which are actually located _after_
|
||||
// the DefValue.
|
||||
auto InstIter = DefValue->getIterator();
|
||||
while (NumUsersBeforeDef > 0 && ++InstIter != DefBB->end()) {
|
||||
if (UserSet.count(&*InstIter))
|
||||
NumUsersBeforeDef--;
|
||||
}
|
||||
|
||||
// Now propagate liveness backwards until we hit the block that defines the
|
||||
@@ -1047,7 +1083,7 @@ void ValueLifetimeAnalysis::propagateLiveness() {
|
||||
auto *BB = Worklist.pop_back_val();
|
||||
|
||||
// Don't go beyond the definition.
|
||||
if (BB == DefBB)
|
||||
if (BB == DefBB && NumUsersBeforeDef == 0)
|
||||
continue;
|
||||
|
||||
for (SILBasicBlock *Pred : BB->getPredecessorBlocks()) {
|
||||
@@ -1070,7 +1106,11 @@ SILInstruction *ValueLifetimeAnalysis:: findLastUserInBlock(SILBasicBlock *BB) {
|
||||
llvm_unreachable("Expected to find use of value in block!");
|
||||
}
|
||||
|
||||
bool ValueLifetimeAnalysis::computeFrontier(Frontier &Fr, Mode mode) {
|
||||
bool ValueLifetimeAnalysis::computeFrontier(Frontier &Fr, Mode mode,
|
||||
DeadEndBlocks *DEBlocks) {
|
||||
assert(!isAliveAtBeginOfBlock(DefValue->getFunction()->getEntryBlock()) &&
|
||||
"Can't compute frontier for def which does not dominate all uses");
|
||||
|
||||
bool NoCriticalEdges = true;
|
||||
|
||||
// Exit-blocks from the lifetime region. The value is live at the end of
|
||||
@@ -1083,12 +1123,15 @@ bool ValueLifetimeAnalysis::computeFrontier(Frontier &Fr, Mode mode) {
|
||||
|
||||
/// The lifetime ends if we have a live block and a not-live successor.
|
||||
for (SILBasicBlock *BB : LiveBlocks) {
|
||||
if (DEBlocks && DEBlocks->isDeadEnd(BB))
|
||||
continue;
|
||||
|
||||
bool LiveInSucc = false;
|
||||
bool DeadInSucc = false;
|
||||
for (const SILSuccessor &Succ : BB->getSuccessors()) {
|
||||
if (isAliveAtBeginOfBlock(Succ)) {
|
||||
LiveInSucc = true;
|
||||
} else {
|
||||
} else if (!DEBlocks || !DEBlocks->isDeadEnd(Succ)) {
|
||||
DeadInSucc = true;
|
||||
}
|
||||
}
|
||||
@@ -1105,7 +1148,10 @@ bool ValueLifetimeAnalysis::computeFrontier(Frontier &Fr, Mode mode) {
|
||||
// frontier (see below).
|
||||
assert(DeadInSucc && "The final using TermInst must have successors");
|
||||
}
|
||||
if (DeadInSucc && mode != IgnoreExitEdges) {
|
||||
if (DeadInSucc) {
|
||||
if (mode == UsersMustPostDomDef)
|
||||
return false;
|
||||
|
||||
// The value is not live in some of the successor blocks.
|
||||
LiveOutBlocks.insert(BB);
|
||||
for (const SILSuccessor &Succ : BB->getSuccessors()) {
|
||||
@@ -1119,6 +1165,7 @@ bool ValueLifetimeAnalysis::computeFrontier(Frontier &Fr, Mode mode) {
|
||||
// Handle "exit" edges from the lifetime region.
|
||||
llvm::SmallPtrSet<SILBasicBlock *, 16> UnhandledFrontierBlocks;
|
||||
for (SILBasicBlock *FrontierBB: FrontierBlocks) {
|
||||
assert(mode != UsersMustPostDomDef);
|
||||
bool needSplit = false;
|
||||
// If the value is live only in part of the predecessor blocks we have to
|
||||
// split those predecessor edges.
|
||||
@@ -1141,6 +1188,7 @@ bool ValueLifetimeAnalysis::computeFrontier(Frontier &Fr, Mode mode) {
|
||||
// Split critical edges from the lifetime region to not yet handled frontier
|
||||
// blocks.
|
||||
for (SILBasicBlock *FrontierPred : LiveOutBlocks) {
|
||||
assert(mode != UsersMustPostDomDef);
|
||||
auto *T = FrontierPred->getTerminator();
|
||||
// Cache the successor blocks because splitting critical edges invalidates
|
||||
// the successor list iterator of T.
|
||||
@@ -1150,6 +1198,7 @@ bool ValueLifetimeAnalysis::computeFrontier(Frontier &Fr, Mode mode) {
|
||||
|
||||
for (unsigned i = 0, e = SuccBlocks.size(); i != e; ++i) {
|
||||
if (UnhandledFrontierBlocks.count(SuccBlocks[i])) {
|
||||
assert(mode == AllowToModifyCFG);
|
||||
assert(isCriticalEdge(T, i) && "actually not a critical edge?");
|
||||
SILBasicBlock *NewBlock = splitEdge(T, i);
|
||||
// The single terminator instruction is part of the frontier.
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
// protocol conformances with covariant return types correctly. The verifier
|
||||
// should trip if we do not handle things correctly.
|
||||
//
|
||||
// TODO: this is not working right now: rdar://problem/33461095
|
||||
// As a side-test it also checks if all allocs can be promoted to the stack.
|
||||
|
||||
// CHECK-LABEL: sil hidden @_T023devirt_covariant_return6driveryyF : $@convention(thin) () -> () {
|
||||
// CHECK: bb0
|
||||
// CHECK: alloc_ref [stack]
|
||||
// CHECK: alloc_ref [stack]
|
||||
// CHECK: alloc_ref [stack]
|
||||
// CHECK: alloc_ref
|
||||
// CHECK: alloc_ref
|
||||
// CHECK: alloc_ref
|
||||
// CHECK: function_ref @unknown1a : $@convention(thin) () -> ()
|
||||
// CHECK: apply
|
||||
// CHECK: function_ref @defenestrate : $@convention(thin) () -> ()
|
||||
@@ -21,9 +22,6 @@
|
||||
// CHECK: function_ref @unknown3a : $@convention(thin) () -> ()
|
||||
// CHECK: apply
|
||||
// CHECK: apply
|
||||
// CHECK: dealloc_ref [stack]
|
||||
// CHECK: dealloc_ref [stack]
|
||||
// CHECK: dealloc_ref [stack]
|
||||
// CHECK: tuple
|
||||
// CHECK: return
|
||||
|
||||
|
||||
@@ -209,8 +209,8 @@ sil hidden_external @use : $@convention(thin) <τ_0_0> (@in τ_0_0) -> ()
|
||||
// It should contain alloc_ref and alloc_stack instructions using opened archetypes.
|
||||
// CHECK-LABEL: sil @bar
|
||||
// CHECK: open_existential{{.*}}C08045E0-2779-11E7-970E-A45E60E99281
|
||||
// CHECK: alloc_ref{{.*}}C08045E0-2779-11E7-970E-A45E60E99281
|
||||
// CHECK: alloc_stack{{.*}}C08045E0-2779-11E7-970E-A45E60E99281
|
||||
// CHECK: alloc_ref{{.*}}C08045E0-2779-11E7-970E-A45E60E99281
|
||||
// CHECK-NOT: function_ref @use
|
||||
// CHECK: function_ref @use
|
||||
// CHECK-NOT: function_ref
|
||||
|
||||
@@ -160,7 +160,11 @@ bb1:
|
||||
|
||||
// CHECK-LABEL: sil @promote_in_loop_with_if
|
||||
// CHECK: [[O:%[0-9]+]] = alloc_ref [stack] $XX
|
||||
// CHECK: {{^}}bb4({{.*}}):
|
||||
// CHECK: bb2:
|
||||
// CHECK: strong_release
|
||||
// CHECK-NEXT: dealloc_ref [stack] [[O]] : $XX
|
||||
// CHECK: bb3:
|
||||
// CHECK-NEXT: strong_release
|
||||
// CHECK-NEXT: dealloc_ref [stack] [[O]] : $XX
|
||||
// CHECK: return
|
||||
sil @promote_in_loop_with_if : $@convention(thin) () -> Int32 {
|
||||
@@ -180,6 +184,7 @@ bb2:
|
||||
br bb4(%l2 : $Int32)
|
||||
|
||||
bb3:
|
||||
strong_release %n1 : $XX
|
||||
%i1 = integer_literal $Builtin.Int32, 0
|
||||
%i2 = struct $Int32 (%i1 : $Builtin.Int32)
|
||||
br bb4(%i2 : $Int32)
|
||||
@@ -262,6 +267,7 @@ bb2:
|
||||
// CHECK-LABEL: sil @promote_with_use_in_loop
|
||||
// CHECK: [[O:%[0-9]+]] = alloc_ref [stack] $XX
|
||||
// CHECK: {{^}}bb2:
|
||||
// CHECK-NEXT: strong_release
|
||||
// CHECK-NEXT: dealloc_ref [stack] [[O]] : $XX
|
||||
// CHECK-NEXT: return
|
||||
sil @promote_with_use_in_loop : $@convention(thin) () -> Int32 {
|
||||
@@ -272,18 +278,21 @@ bb0:
|
||||
br bb1
|
||||
|
||||
bb1:
|
||||
strong_retain %n1 : $XX
|
||||
%l1 = ref_element_addr %n1 : $XX, #XX.x
|
||||
%l2 = load %l1 : $*Int32
|
||||
strong_release %n1 : $XX
|
||||
cond_br undef, bb1, bb2
|
||||
|
||||
bb2:
|
||||
strong_release %n1 : $XX
|
||||
return %l2 : $Int32
|
||||
}
|
||||
|
||||
// CHECK-LABEL: sil @promote_with_use_in_multi_block_backedge_loop
|
||||
// CHECK: [[O:%[0-9]+]] = alloc_ref [stack] $XX
|
||||
// CHECK: {{^}}bb4:
|
||||
// CHECK-NEXT: strong_release
|
||||
// CHECK-NEXT: dealloc_ref [stack] [[O]] : $XX
|
||||
// CHECK-NEXT: return
|
||||
sil @promote_with_use_in_multi_block_backedge_loop : $@convention(thin) () -> Int32 {
|
||||
@@ -294,6 +303,7 @@ bb0:
|
||||
br bb1
|
||||
|
||||
bb1:
|
||||
strong_retain %n1 : $XX
|
||||
%l1 = ref_element_addr %n1 : $XX, #XX.x
|
||||
%l2 = load %l1 : $*Int32
|
||||
strong_release %n1 : $XX
|
||||
@@ -306,6 +316,7 @@ bb3:
|
||||
br bb1
|
||||
|
||||
bb4:
|
||||
strong_release %n1 : $XX
|
||||
return %l2 : $Int32
|
||||
}
|
||||
|
||||
@@ -345,15 +356,15 @@ bb5:
|
||||
return %a1 : $Int32
|
||||
}
|
||||
|
||||
// CHECK-LABEL: sil @promote_and_move_alloc_before_alloc_stack1
|
||||
// CHECK: [[O:%[0-9]+]] = alloc_ref [stack] $XX
|
||||
// CHECK-LABEL: sil @promote_and_fix_stack_nesting1
|
||||
// CHECK: alloc_stack
|
||||
// CHECK: {{^}}bb2:
|
||||
// CHECK: dealloc_stack
|
||||
// CHECK: [[O:%[0-9]+]] = alloc_ref [stack] $XX
|
||||
// CHECK: strong_release
|
||||
// CHECK: dealloc_ref [stack] [[O]] : $XX
|
||||
// CHECK: dealloc_stack
|
||||
// CHECK: return
|
||||
sil @promote_and_move_alloc_before_alloc_stack1 : $@convention(thin) () -> Int32 {
|
||||
sil @promote_and_fix_stack_nesting1 : $@convention(thin) () -> Int32 {
|
||||
bb0:
|
||||
%s1 = alloc_stack $Int32
|
||||
cond_br undef, bb1, bb2
|
||||
@@ -372,14 +383,15 @@ bb2:
|
||||
return %l2 : $Int32
|
||||
}
|
||||
|
||||
// CHECK-LABEL: sil @promote_and_move_alloc_before_alloc_stack2
|
||||
// CHECK: alloc_ref [stack] $XX
|
||||
// CHECK-NEXT: alloc_stack
|
||||
// CHECK-LABEL: sil @promote_and_fix_stack_nesting2
|
||||
// CHECK: alloc_stack
|
||||
// CHECK-NEXT: alloc_ref [stack] $XX
|
||||
// CHECK: {{^}}bb3:
|
||||
// CHECK: strong_release
|
||||
// CHECK-NEXT: dealloc_ref [stack]
|
||||
// CHECK-NEXT: dealloc_stack
|
||||
// CHECK-NEXT: return
|
||||
sil @promote_and_move_alloc_before_alloc_stack2 : $@convention(thin) () -> Int32 {
|
||||
sil @promote_and_fix_stack_nesting2 : $@convention(thin) () -> Int32 {
|
||||
bb0:
|
||||
%s1 = alloc_stack $Int32
|
||||
%o1 = alloc_ref $XX
|
||||
@@ -402,11 +414,15 @@ bb3:
|
||||
return %l2 : $Int32
|
||||
}
|
||||
|
||||
// CHECK-LABEL: sil @dont_move_above_operand_1
|
||||
// CHECK: alloc_ref [tail_elems $Int32
|
||||
// CHECK-NOT: dealloc_ref
|
||||
// CHECK-LABEL: sil @promote_and_fix_stack_nesting3
|
||||
// CHECK: alloc_stack
|
||||
// CHECK: {{^}}bb2:
|
||||
// CHECK: [[O:%[0-9]+]] = alloc_ref [stack] [tail_elems{{.*}}] $XX
|
||||
// CHECK: strong_release
|
||||
// CHECK: dealloc_ref [stack]
|
||||
// CHECK: dealloc_stack
|
||||
// CHECK: return
|
||||
sil @dont_move_above_operand_1 : $@convention(thin) () -> Int32 {
|
||||
sil @promote_and_fix_stack_nesting3 : $@convention(thin) () -> Int32 {
|
||||
bb0:
|
||||
%s1 = alloc_stack $Int32
|
||||
%i = integer_literal $Builtin.Word, 1
|
||||
@@ -426,11 +442,15 @@ bb2:
|
||||
return %l2 : $Int32
|
||||
}
|
||||
|
||||
// CHECK-LABEL: sil @dont_move_above_operand_2
|
||||
// CHECK: alloc_ref [tail_elems $Int32
|
||||
// CHECK-NOT: dealloc_ref
|
||||
// CHECK-LABEL: sil @promote_and_fix_stack_nesting4
|
||||
// CHECK: alloc_stack
|
||||
// CHECK: {{^}}bb2:
|
||||
// CHECK: [[O:%[0-9]+]] = alloc_ref [stack] [tail_elems{{.*}}] $XX
|
||||
// CHECK: strong_release
|
||||
// CHECK: dealloc_ref [stack]
|
||||
// CHECK: dealloc_stack
|
||||
// CHECK: return
|
||||
sil @dont_move_above_operand_2 : $@convention(thin) () -> Int32 {
|
||||
sil @promote_and_fix_stack_nesting4 : $@convention(thin) () -> Int32 {
|
||||
bb0:
|
||||
%s1 = alloc_stack $Int32
|
||||
cond_br undef, bb1, bb2
|
||||
@@ -450,11 +470,15 @@ bb2:
|
||||
return %l2 : $Int32
|
||||
}
|
||||
|
||||
// CHECK-LABEL: sil @dont_move_above_operand_3
|
||||
// CHECK: alloc_ref [tail_elems $Int32
|
||||
// CHECK-NOT: dealloc_ref
|
||||
// CHECK-LABEL: sil @promote_and_fix_stack_nesting5
|
||||
// CHECK: alloc_stack
|
||||
// CHECK: {{^}}bb2({{.*}}):
|
||||
// CHECK: [[O:%[0-9]+]] = alloc_ref [stack] [tail_elems{{.*}}] $XX
|
||||
// CHECK: strong_release
|
||||
// CHECK: dealloc_ref [stack]
|
||||
// CHECK: dealloc_stack
|
||||
// CHECK: return
|
||||
sil @dont_move_above_operand_3 : $@convention(thin) (Builtin.Word) -> Int32 {
|
||||
sil @promote_and_fix_stack_nesting5 : $@convention(thin) (Builtin.Word) -> Int32 {
|
||||
bb0(%0 : $Builtin.Word):
|
||||
%s1 = alloc_stack $Int32
|
||||
cond_br undef, bb1, bb2(%0 : $Builtin.Word)
|
||||
|
||||
Reference in New Issue
Block a user