Generalize and fix SinkAddressProjections.

Fixes a potential real bug in the case that SinkAddressProjections moves
projections without notifying SimplifyCFG of the change. This could
fail to update Analyses (probably won't break anything in practice).

Introduce SILInstruction::isPure. Among other things, this can tell
you if it's safe to duplicate instructions at their
uses. SinkAddressProjections should check this before sinking uses. I
couldn't find a way to expose this as a real bug, but it is a
theoretical bug.

Add the SinkAddressProjections functionality to the BasicBlockCloner
utility. Enable address projection sinking for all BasicBlockCloner
clients (the four different kinds of jump-threading that use it). This
brings the compiler much closer to banning all address phis.

The "bugs" were originally introduced a week ago here:

commit f22371bf0b (fork/fix-address-phi, fix-address-phi)
Author: Andrew Trick <atrick@apple.com>
Date:   Tue Sep 17 16:45:51 2019

    Add SIL SinkAddressProjections utility to avoid address phis.

    Enable this utility during jump-threading in SimplifyCFG.

    Ultimately, the SIL verifier should prevent all address-phis and we'll
    need to use this utility in a few more places.

    Fixes <rdar://problem/55320867> SIL verification failed: Unknown
    formal access pattern: storage
This commit is contained in:
Andrew Trick
2019-09-30 13:56:17 -07:00
parent 1ca57e06d7
commit 38c29e231e
9 changed files with 373 additions and 141 deletions

View File

@@ -45,6 +45,7 @@
namespace swift { namespace swift {
class AllocationInst;
class DeclRefExpr; class DeclRefExpr;
class FloatLiteralExpr; class FloatLiteralExpr;
class FuncDecl; class FuncDecl;
@@ -618,6 +619,15 @@ public:
return getMemoryBehavior() != MemoryBehavior::None; return getMemoryBehavior() != MemoryBehavior::None;
} }
/// Return true if the instruction is "pure" in the sense that it may execute
/// multiple times without affecting behavior. This implies that it can be
/// trivially cloned at multiple use sites without preserving path
/// equivalence.
bool isPure() const {
return !mayReadOrWriteMemory() && !mayTrap() && !isa<AllocationInst>(this)
&& !isa<TermInst>(this);
}
/// Returns true if the result of this instruction is a pointer to stack /// Returns true if the result of this instruction is a pointer to stack
/// allocated memory. In this case there must be an adjacent deallocating /// allocated memory. In this case there must be an adjacent deallocating
/// instruction. /// instruction.

View File

@@ -22,9 +22,10 @@
#ifndef SWIFT_SILOPTIMIZER_UTILS_BASICBLOCKOPTUTILS_H #ifndef SWIFT_SILOPTIMIZER_UTILS_BASICBLOCKOPTUTILS_H
#define SWIFT_SILOPTIMIZER_UTILS_BASICBLOCKOPTUTILS_H #define SWIFT_SILOPTIMIZER_UTILS_BASICBLOCKOPTUTILS_H
#include "swift/SIL/SILInstruction.h"
#include "swift/SIL/SILBasicBlock.h" #include "swift/SIL/SILBasicBlock.h"
#include "swift/SIL/SILCloner.h" #include "swift/SIL/SILCloner.h"
#include "swift/SIL/SILInstruction.h"
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
namespace swift { namespace swift {
@@ -61,12 +62,65 @@ bool rotateLoop(SILLoop *loop, DominanceInfo *domInfo, SILLoopInfo *loopInfo,
bool rotateSingleBlockLoops, SILBasicBlock *upToBB, bool rotateSingleBlockLoops, SILBasicBlock *upToBB,
bool shouldVerify); bool shouldVerify);
/// Helper function to perform SSA updates in case of jump threading. /// Sink address projections to their out-of-block uses. This is
void updateSSAAfterCloning(BasicBlockCloner &cloner, SILBasicBlock *srcBB, /// required after cloning a block and before calling
SILBasicBlock *destBB); /// updateSSAAfterCloning to avoid address-type phis.
///
/// This clones address projections at their use points, but does not
/// mutate the block containing the projections.
///
/// BasicBlockCloner handles this internally.
class SinkAddressProjections {
// Projections ordered from last to first in the chain.
SmallVector<SingleValueInstruction *, 4> projections;
SmallSetVector<SILValue, 4> inBlockDefs;
// Transient per-projection data for use during cloning.
SmallVector<Operand *, 4> usesToReplace;
llvm::SmallDenseMap<SILBasicBlock *, Operand *, 4> firstBlockUse;
public:
/// Check for an address projection chain ending at \p inst. Return true if
/// the given instruction is successfully analyzed.
///
/// If \p inst does not produce an address, then return
/// true. getInBlockDefs() will contain \p inst if any of its
/// (non-address) values are used outside its block.
///
/// If \p inst does produce an address, return true only of the
/// chain of address projections within this block is clonable at
/// their use sites. getInBlockDefs will return all non-address
/// operands in the chain that are also defined in this block. These
/// may require phis after cloning the projections.
bool analyzeAddressProjections(SILInstruction *inst);
/// After analyzing projections, returns the list of (non-address) values
/// defined in the same block as the projections which will have uses outside
/// the block after cloning.
ArrayRef<SILValue> getInBlockDefs() const {
return inBlockDefs.getArrayRef();
}
/// Clone the chain of projections at their use sites.
///
/// Return true if anything was done.
///
/// getInBlockProjectionOperandValues() can be called before or after cloning.
bool cloneProjections();
};
/// Clone a single basic block and any required successor edges within the same /// Clone a single basic block and any required successor edges within the same
/// function. /// function.
///
/// Before cloning, call either canCloneBlock or call canCloneInstruction for
/// every instruction in the original block.
///
/// To clone just the block, call cloneBlock. To also update the original
/// block's branch to jump to the newly cloned block, call cloneBranchTarget
/// instead.
///
/// After cloning, call splitCriticalEdges, then updateSSAAfterCloning. This is
/// decoupled from cloning becaused some clients perform CFG edges updates after
/// cloning but before splitting CFG edges.
class BasicBlockCloner : public SILCloner<BasicBlockCloner> { class BasicBlockCloner : public SILCloner<BasicBlockCloner> {
using SuperTy = SILCloner<BasicBlockCloner>; using SuperTy = SILCloner<BasicBlockCloner>;
friend class SILCloner<BasicBlockCloner>; friend class SILCloner<BasicBlockCloner>;
@@ -75,18 +129,56 @@ protected:
/// The original block to be cloned. /// The original block to be cloned.
SILBasicBlock *origBB; SILBasicBlock *origBB;
/// Will cloning require an SSA update?
bool needsSSAUpdate = false;
/// Transient object for analyzing a single address projction chain. It's
/// state is reset each time analyzeAddressProjections is called.
SinkAddressProjections sinkProj;
public: public:
/// An ordered list of old to new available value pairs. /// An ordered list of old to new available value pairs.
/// ///
/// updateSSAAfterCloning() expects this public field to hold values that may /// updateSSAAfterCloning() expects this public field to hold values that may
/// be remapped in the cloned block and live out. /// be remapped in the cloned block and live out.
SmallVector<std::pair<SILValue, SILValue>, 16> AvailVals; SmallVector<std::pair<SILValue, SILValue>, 16> availVals;
// Clone blocks starting at `origBB`, within the same function. // Clone blocks starting at `origBB`, within the same function.
BasicBlockCloner(SILBasicBlock *origBB) BasicBlockCloner(SILBasicBlock *origBB)
: SILCloner(*origBB->getParent()), origBB(origBB) {} : SILCloner(*origBB->getParent()), origBB(origBB) {}
bool canCloneBlock() {
for (auto &inst : *origBB) {
if (!canCloneInstruction(&inst))
return false;
}
return true;
}
/// Returns true if \p inst can be cloned.
///
/// If canCloneBlock is not called, then this must be called for every
/// instruction in origBB, both to ensure clonability and to handle internal
/// book-keeping (needsSSAUpdate).
bool canCloneInstruction(SILInstruction *inst) {
assert(inst->getParent() == origBB);
if (!inst->isTriviallyDuplicatable())
return false;
if (!sinkProj.analyzeAddressProjections(inst))
return false;
// Check if any of the non-address defs in the cloned block (including the
// current instruction) will still have uses outside the block after sinking
// address projections.
needsSSAUpdate |= !sinkProj.getInBlockDefs().empty();
return true;
}
void cloneBlock(SILBasicBlock *insertAfterBB = nullptr) { void cloneBlock(SILBasicBlock *insertAfterBB = nullptr) {
sinkAddressProjections();
SmallVector<SILBasicBlock *, 4> successorBBs; SmallVector<SILBasicBlock *, 4> successorBBs;
successorBBs.reserve(origBB->getSuccessors().size()); successorBBs.reserve(origBB->getSuccessors().size());
llvm::copy(origBB->getSuccessors(), std::back_inserter(successorBBs)); llvm::copy(origBB->getSuccessors(), std::back_inserter(successorBBs));
@@ -95,6 +187,9 @@ public:
/// Clone the given branch instruction's destination block, splitting /// Clone the given branch instruction's destination block, splitting
/// its successors, and rewrite the branch instruction. /// its successors, and rewrite the branch instruction.
///
/// Return false if the branch's destination block cannot be cloned. When
/// false is returned, no changes have been made.
void cloneBranchTarget(BranchInst *bi) { void cloneBranchTarget(BranchInst *bi) {
assert(origBB == bi->getDestBB()); assert(origBB == bi->getDestBB());
@@ -110,10 +205,16 @@ public:
return remapBasicBlock(origBB); return remapBasicBlock(origBB);
} }
bool wasCloned() { return isBlockCloned(origBB); }
/// Call this after processing all instructions to fix the control flow /// Call this after processing all instructions to fix the control flow
/// graph. The branch cloner may have left critical edges. /// graph. The branch cloner may have left critical edges.
bool splitCriticalEdges(DominanceInfo *domInfo, SILLoopInfo *loopInfo); bool splitCriticalEdges(DominanceInfo *domInfo, SILLoopInfo *loopInfo);
/// Helper function to perform SSA updates after calling both
/// cloneBranchTarget and splitCriticalEdges.
void updateSSAAfterCloning();
protected: protected:
// MARK: CRTP overrides. // MARK: CRTP overrides.
@@ -137,8 +238,10 @@ protected:
void mapValue(SILValue origValue, SILValue mappedValue) { void mapValue(SILValue origValue, SILValue mappedValue) {
SuperTy::mapValue(origValue, mappedValue); SuperTy::mapValue(origValue, mappedValue);
AvailVals.emplace_back(origValue, mappedValue); availVals.emplace_back(origValue, mappedValue);
} }
void sinkAddressProjections();
}; };
// Helper class that provides a callback that can be used in // Helper class that provides a callback that can be used in
@@ -173,46 +276,6 @@ public:
} }
}; };
/// Sink address projections to their out-of-block uses. This is
/// required after cloning a block and before calling
/// updateSSAAfterCloning to avoid address-type phis.
///
/// This clones address projections at their use points, but does not
/// mutate the block containing the projections.
class SinkAddressProjections {
// Projections ordered from last to first in the chain.
SmallVector<SingleValueInstruction *, 4> projections;
SmallSetVector<SILValue, 4> inBlockDefs;
public:
/// Check for an address projection chain ending at \p inst. Return true if
/// the given instruction is successfully analyzed.
///
/// If \p inst does not produce an address, then return
/// true. getInBlockDefs() will contain \p inst if any of its
/// (non-address) values are used outside its block.
///
/// If \p inst does produce an address, return true only of the
/// chain of address projections within this block is clonable at
/// their use sites. getInBlockDefs will return all non-address
/// operands in the chain that are also defined in this block. These
/// may require phis after cloning the projections.
bool analyzeAddressProjections(SILInstruction *inst);
/// After analyzing projections, returns the list of (non-address) values
/// defined in the same block as the projections which will have uses outside
/// the block after cloning.
ArrayRef<SILValue> getInBlockDefs() const {
return inBlockDefs.getArrayRef();
}
/// Clone the chain of projections at their use sites.
///
/// Return true if anything was done.
///
/// getInBlockProjectionOperandValues() can be called before or after cloning.
bool cloneProjections();
};
/// Utility class for cloning init values into the static initializer of a /// Utility class for cloning init values into the static initializer of a
/// SILGlobalVariable. /// SILGlobalVariable.
class StaticInitCloner : public SILCloner<StaticInitCloner> { class StaticInitCloner : public SILCloner<StaticInitCloner> {

View File

@@ -133,8 +133,10 @@ public:
/// reconstruct the use. /// reconstruct the use.
UseWrapper(Operand *Use); UseWrapper(Operand *Use);
Operand *getOperand();
/// Return the operand we wrap. Reconstructing branch operands. /// Return the operand we wrap. Reconstructing branch operands.
operator Operand*(); operator Operand*() { return getOperand(); }
}; };
} // end namespace swift } // end namespace swift

View File

@@ -274,12 +274,15 @@ public:
ThreadInfo() = default; ThreadInfo() = default;
void threadEdge() { bool threadEdge() {
LLVM_DEBUG(llvm::dbgs() << "thread edge from bb" << Src->getDebugID() LLVM_DEBUG(llvm::dbgs() << "thread edge from bb" << Src->getDebugID()
<< " to bb" << Dest->getDebugID() << '\n'); << " to bb" << Dest->getDebugID() << '\n');
auto *SrcTerm = cast<BranchInst>(Src->getTerminator()); auto *SrcTerm = cast<BranchInst>(Src->getTerminator());
BasicBlockCloner Cloner(SrcTerm->getDestBB()); BasicBlockCloner Cloner(SrcTerm->getDestBB());
if (!Cloner.canCloneBlock())
return false;
Cloner.cloneBranchTarget(SrcTerm); Cloner.cloneBranchTarget(SrcTerm);
// We have copied the threaded block into the edge. // We have copied the threaded block into the edge.
@@ -329,7 +332,8 @@ public:
// After rewriting the cloned branch, split the critical edge. // After rewriting the cloned branch, split the critical edge.
// This does not currently update DominanceInfo. // This does not currently update DominanceInfo.
Cloner.splitCriticalEdges(nullptr, nullptr); Cloner.splitCriticalEdges(nullptr, nullptr);
updateSSAAfterCloning(Cloner, Src, Dest); Cloner.updateSSAAfterCloning();
return true;
} }
}; };
@@ -551,7 +555,7 @@ bool SimplifyCFG::dominatorBasedSimplifications(SILFunction &Fn,
return Changed; return Changed;
for (auto &ThreadInfo : JumpThreadableEdges) { for (auto &ThreadInfo : JumpThreadableEdges) {
ThreadInfo.threadEdge(); if (ThreadInfo.threadEdge())
Changed = true; Changed = true;
} }
@@ -922,16 +926,6 @@ bool SimplifyCFG::tryJumpThreading(BranchInst *BI) {
if (DestBB->getTerminator()->isFunctionExiting()) if (DestBB->getTerminator()->isFunctionExiting())
return false; return false;
// We need to update SSA if a value duplicated is used outside of the
// duplicated block.
bool NeedToUpdateSSA = false;
// Are the arguments to this block used outside of the block.
for (auto Arg : DestBB->getArguments())
if ((NeedToUpdateSSA |= isUsedOutsideOfBlock(Arg))) {
break;
}
// We don't have a great cost model at the SIL level, so we don't want to // We don't have a great cost model at the SIL level, so we don't want to
// blissly duplicate tons of code with a goal of improved performance (we'll // blissly duplicate tons of code with a goal of improved performance (we'll
// leave that to LLVM). However, doing limited code duplication can lead to // leave that to LLVM). However, doing limited code duplication can lead to
@@ -978,36 +972,6 @@ bool SimplifyCFG::tryJumpThreading(BranchInst *BI) {
if (ThreadingBudget <= 0) if (ThreadingBudget <= 0)
return false; return false;
// If it looks potentially interesting, decide whether we *can* do the
// operation and whether the block is small enough to be worth duplicating.
int copyCosts = 0;
SinkAddressProjections sinkProj;
for (auto ii = DestBB->begin(), ie = DestBB->end(); ii != ie;) {
copyCosts += getThreadingCost(&*ii);
if (ThreadingBudget <= copyCosts)
return false;
// If this is an address projection with outside uses, sink it before
// checking for SSA update.
if (!sinkProj.analyzeAddressProjections(&*ii))
return false;
sinkProj.cloneProjections();
// After cloning check if any of the non-address defs in the cloned block
// (including the current instruction) now have uses outside the
// block. Do this even if nothing was cloned.
if (!sinkProj.getInBlockDefs().empty())
NeedToUpdateSSA = true;
auto nextII = std::next(ii);
recursivelyDeleteTriviallyDeadInstructions(
&*ii, false, [&nextII](SILInstruction *deadInst) {
if (deadInst->getIterator() == nextII)
++nextII;
});
ii = nextII;
}
// Don't jump thread through a potential header - this can produce irreducible // Don't jump thread through a potential header - this can produce irreducible
// control flow. Still, we make an exception for switch_enum. // control flow. Still, we make an exception for switch_enum.
bool DestIsLoopHeader = (LoopHeaders.count(DestBB) != 0); bool DestIsLoopHeader = (LoopHeaders.count(DestBB) != 0);
@@ -1016,19 +980,31 @@ bool SimplifyCFG::tryJumpThreading(BranchInst *BI) {
return false; return false;
} }
// If it looks potentially interesting, decide whether we *can* do the
// operation and whether the block is small enough to be worth duplicating.
int copyCosts = 0;
BasicBlockCloner Cloner(DestBB);
for (auto &inst : *DestBB) {
copyCosts += getThreadingCost(&inst);
if (ThreadingBudget <= copyCosts)
return false;
// If this is an address projection with outside uses, sink it before
// checking for SSA update.
if (!Cloner.canCloneInstruction(&inst))
return false;
}
LLVM_DEBUG(llvm::dbgs() << "jump thread from bb" << SrcBB->getDebugID() LLVM_DEBUG(llvm::dbgs() << "jump thread from bb" << SrcBB->getDebugID()
<< " to bb" << DestBB->getDebugID() << '\n'); << " to bb" << DestBB->getDebugID() << '\n');
JumpThreadingCost[DestBB] += copyCosts; JumpThreadingCost[DestBB] += copyCosts;
// Okay, it looks like we want to do this and we can. Duplicate the // Duplicate the destination block into this one, rewriting uses of the BBArgs
// destination block into this one, rewriting uses of the BBArgs to use the // to use the branch arguments as we go.
// branch arguments as we go.
BasicBlockCloner Cloner(DestBB);
Cloner.cloneBranchTarget(BI); Cloner.cloneBranchTarget(BI);
// Does not currently update DominanceInfo. // Does not currently update DominanceInfo.
Cloner.splitCriticalEdges(nullptr, nullptr); Cloner.splitCriticalEdges(nullptr, nullptr);
Cloner.updateSSAAfterCloning();
// Once all the instructions are copied, we can nuke BI itself. We also add // Once all the instructions are copied, we can nuke BI itself. We also add
// the threaded and edge block to the worklist now that they (likely) can be // the threaded and edge block to the worklist now that they (likely) can be
@@ -1036,9 +1012,6 @@ bool SimplifyCFG::tryJumpThreading(BranchInst *BI) {
addToWorklist(SrcBB); addToWorklist(SrcBB);
addToWorklist(Cloner.getNewBB()); addToWorklist(Cloner.getNewBB());
if (NeedToUpdateSSA)
updateSSAAfterCloning(Cloner, Cloner.getNewBB(), DestBB);
// We may be able to simplify DestBB now that it has one fewer predecessor. // We may be able to simplify DestBB now that it has one fewer predecessor.
simplifyAfterDroppingPredecessor(DestBB); simplifyAfterDroppingPredecessor(DestBB);
@@ -2742,21 +2715,23 @@ bool SimplifyCFG::tailDuplicateObjCMethodCallSuccessorBlocks() {
for (auto *BB : ObjCBlocks) { for (auto *BB : ObjCBlocks) {
auto *Branch = cast<BranchInst>(BB->getTerminator()); auto *Branch = cast<BranchInst>(BB->getTerminator());
auto *DestBB = Branch->getDestBB(); auto *DestBB = Branch->getDestBB();
Changed = true;
// Okay, it looks like we want to do this and we can. Duplicate the // Okay, it looks like we want to do this and we can. Duplicate the
// destination block into this one, rewriting uses of the BBArgs to use the // destination block into this one, rewriting uses of the BBArgs to use the
// branch arguments as we go. // branch arguments as we go.
BasicBlockCloner Cloner(DestBB); BasicBlockCloner Cloner(DestBB);
if (!Cloner.canCloneBlock())
continue;
Cloner.cloneBranchTarget(Branch); Cloner.cloneBranchTarget(Branch);
// Does not currently update DominanceInfo. // Does not currently update DominanceInfo.
Cloner.splitCriticalEdges(nullptr, nullptr); Cloner.splitCriticalEdges(nullptr, nullptr);
Cloner.updateSSAAfterCloning();
updateSSAAfterCloning(Cloner, Cloner.getNewBB(), DestBB); Changed = true;
addToWorklist(Cloner.getNewBB()); addToWorklist(Cloner.getNewBB());
} }
return Changed; return Changed;
} }

View File

@@ -68,11 +68,19 @@ bool swift::removeUnreachableBlocks(SILFunction &f) {
return changed; return changed;
} }
/// Helper function to perform SSA updates in case of jump threading. void BasicBlockCloner::updateSSAAfterCloning() {
void swift::updateSSAAfterCloning(BasicBlockCloner &cloner, // All instructions should have been checked by canCloneInstruction. But we
SILBasicBlock *srcBB, SILBasicBlock *destBB) { // still need to check the arguments.
for (auto arg : origBB->getArguments()) {
if ((needsSSAUpdate |= isUsedOutsideOfBlock(arg))) {
break;
}
}
if (!needsSSAUpdate)
return;
SILSSAUpdater ssaUpdater; SILSSAUpdater ssaUpdater;
for (auto availValPair : cloner.AvailVals) { for (auto availValPair : availVals) {
ValueBase *inst = availValPair.first; ValueBase *inst = availValPair.first;
if (inst->use_empty()) if (inst->use_empty())
continue; continue;
@@ -85,20 +93,20 @@ void swift::updateSSAAfterCloning(BasicBlockCloner &cloner,
useList.push_back(UseWrapper(use)); useList.push_back(UseWrapper(use));
ssaUpdater.Initialize(inst->getType()); ssaUpdater.Initialize(inst->getType());
ssaUpdater.AddAvailableValue(destBB, inst); ssaUpdater.AddAvailableValue(origBB, inst);
ssaUpdater.AddAvailableValue(srcBB, newResult); ssaUpdater.AddAvailableValue(getNewBB(), newResult);
if (useList.empty()) if (useList.empty())
continue; continue;
// Update all the uses. // Update all the uses.
for (auto useWrapper : useList) { for (auto useWrapper : useList) {
Operand *use = useWrapper; Operand *use = useWrapper; // unwrap
SILInstruction *user = use->getUser(); SILInstruction *user = use->getUser();
assert(user && "Missing user"); assert(user && "Missing user");
// Ignore uses in the same basic block. // Ignore uses in the same basic block.
if (user->getParent() == destBB) if (user->getParent() == origBB)
continue; continue;
ssaUpdater.RewriteUse(*use); ssaUpdater.RewriteUse(*use);
@@ -128,6 +136,29 @@ bool BasicBlockCloner::splitCriticalEdges(DominanceInfo *domInfo,
return changed; return changed;
} }
void BasicBlockCloner::sinkAddressProjections() {
// Because the address projections chains will be disjoint (an instruction
// in one chain cannot use the result of an instruction in another chain),
// the order they are sunk does not matter.
for (auto ii = origBB->begin(), ie = origBB->end(); ii != ie;) {
bool canSink = sinkProj.analyzeAddressProjections(&*ii);
(void)canSink;
assert(canSink && "canCloneInstruction should catch this.");
sinkProj.cloneProjections();
assert((sinkProj.getInBlockDefs().empty() || needsSSAUpdate)
&& "canCloneInstruction should catch this.");
auto nextII = std::next(ii);
recursivelyDeleteTriviallyDeadInstructions(
&*ii, false, [&nextII](SILInstruction *deadInst) {
if (deadInst->getIterator() == nextII)
++nextII;
});
ii = nextII;
}
}
// Populate 'projections' with the chain of address projections leading // Populate 'projections' with the chain of address projections leading
// to and including 'inst'. // to and including 'inst'.
// //
@@ -149,7 +180,7 @@ bool SinkAddressProjections::analyzeAddressProjections(SILInstruction *inst) {
return true; return true;
} }
if (auto *addressProj = dyn_cast<SingleValueInstruction>(def)) { if (auto *addressProj = dyn_cast<SingleValueInstruction>(def)) {
if (addressProj->isTriviallyDuplicatable()) { if (addressProj->isPure()) {
projections.push_back(addressProj); projections.push_back(addressProj);
return true; return true;
} }
@@ -183,19 +214,40 @@ bool SinkAddressProjections::cloneProjections() {
return false; return false;
SILBasicBlock *bb = projections.front()->getParent(); SILBasicBlock *bb = projections.front()->getParent();
SmallVector<Operand *, 4> usesToReplace;
// Clone projections in last-to-first order. // Clone projections in last-to-first order.
for (unsigned idx = 0; idx < projections.size(); ++idx) { for (unsigned idx = 0; idx < projections.size(); ++idx) {
auto *oldProj = projections[idx]; auto *oldProj = projections[idx];
assert(oldProj->getParent() == bb); assert(oldProj->getParent() == bb);
// Reset transient per-projection sets.
usesToReplace.clear(); usesToReplace.clear();
firstBlockUse.clear();
// Gather uses.
for (Operand *use : oldProj->getUses()) { for (Operand *use : oldProj->getUses()) {
if (use->getUser()->getParent() != bb) auto *useBB = use->getUser()->getParent();
if (useBB != bb) {
firstBlockUse.try_emplace(useBB, use);
usesToReplace.push_back(use); usesToReplace.push_back(use);
} }
}
// Replace uses. Uses must be handled in the same order they were discovered
// above.
//
// Avoid cloning a projection multiple times per block. This avoids extra
// projections, but also prevents the removal of DebugValue. If a
// projection's only remaining is DebugValue, then it is deleted along with
// the DebugValue.
for (Operand *use : usesToReplace) { for (Operand *use : usesToReplace) {
auto *newProj = oldProj->clone(use->getUser()); auto *useBB = use->getUser()->getParent();
use->set(cast<SingleValueInstruction>(newProj)); auto *firstUse = firstBlockUse.lookup(useBB);
SingleValueInstruction *newProj;
if (use == firstUse)
newProj = cast<SingleValueInstruction>(oldProj->clone(use->getUser()));
else {
newProj = cast<SingleValueInstruction>(firstUse->get());
assert(newProj->getParent() == useBB);
newProj->moveFront(useBB);
}
use->set(newProj);
} }
} }
return true; return true;

View File

@@ -87,8 +87,8 @@ class CheckedCastBrJumpThreading {
hasUnknownPreds(hasUnknownPreds) { } hasUnknownPreds(hasUnknownPreds) { }
void modifyCFGForUnknownPreds(); void modifyCFGForUnknownPreds();
void modifyCFGForFailurePreds(Optional<BasicBlockCloner> &Cloner); void modifyCFGForFailurePreds(BasicBlockCloner &Cloner);
void modifyCFGForSuccessPreds(Optional<BasicBlockCloner> &Cloner); void modifyCFGForSuccessPreds(BasicBlockCloner &Cloner);
}; };
// Contains an entry for each checked_cast_br to be optimized. // Contains an entry for each checked_cast_br to be optimized.
@@ -243,15 +243,14 @@ void CheckedCastBrJumpThreading::Edit::modifyCFGForUnknownPreds() {
/// Create a copy of the BB as a landing BB /// Create a copy of the BB as a landing BB
/// for all FailurePreds. /// for all FailurePreds.
void CheckedCastBrJumpThreading::Edit:: void CheckedCastBrJumpThreading::Edit::modifyCFGForFailurePreds(
modifyCFGForFailurePreds(Optional<BasicBlockCloner> &Cloner) { BasicBlockCloner &Cloner) {
if (FailurePreds.empty()) if (FailurePreds.empty())
return; return;
assert(!Cloner.hasValue()); assert(!Cloner.wasCloned());
Cloner.emplace(CCBBlock); Cloner.cloneBlock();
Cloner->cloneBlock(); SILBasicBlock *TargetFailureBB = Cloner.getNewBB();
SILBasicBlock *TargetFailureBB = Cloner->getNewBB();
auto *TI = TargetFailureBB->getTerminator(); auto *TI = TargetFailureBB->getTerminator();
SILBuilderWithScope Builder(TI); SILBuilderWithScope Builder(TI);
// This BB copy branches to a FailureBB. // This BB copy branches to a FailureBB.
@@ -271,8 +270,8 @@ modifyCFGForFailurePreds(Optional<BasicBlockCloner> &Cloner) {
/// Create a copy of the BB or reuse BB as /// Create a copy of the BB or reuse BB as
/// a landing basic block for all FailurePreds. /// a landing basic block for all FailurePreds.
void CheckedCastBrJumpThreading::Edit:: void CheckedCastBrJumpThreading::Edit::modifyCFGForSuccessPreds(
modifyCFGForSuccessPreds(Optional<BasicBlockCloner> &Cloner) { BasicBlockCloner &Cloner) {
auto *CCBI = cast<CheckedCastBranchInst>(CCBBlock->getTerminator()); auto *CCBI = cast<CheckedCastBranchInst>(CCBBlock->getTerminator());
if (InvertSuccess) { if (InvertSuccess) {
@@ -285,10 +284,9 @@ modifyCFGForSuccessPreds(Optional<BasicBlockCloner> &Cloner) {
if (!SuccessPreds.empty()) { if (!SuccessPreds.empty()) {
// Create a copy of the BB as a landing BB. // Create a copy of the BB as a landing BB.
// for all SuccessPreds. // for all SuccessPreds.
assert(!Cloner.hasValue()); assert(!Cloner.wasCloned());
Cloner.emplace(CCBBlock); Cloner.cloneBlock();
Cloner->cloneBlock(); SILBasicBlock *TargetSuccessBB = Cloner.getNewBB();
SILBasicBlock *TargetSuccessBB = Cloner->getNewBB();
auto *TI = TargetSuccessBB->getTerminator(); auto *TI = TargetSuccessBB->getTerminator();
SILBuilderWithScope Builder(TI); SILBuilderWithScope Builder(TI);
// This BB copy branches to SuccessBB. // This BB copy branches to SuccessBB.
@@ -343,6 +341,11 @@ bool CheckedCastBrJumpThreading::handleArgBBIsEntryBlock(SILBasicBlock *ArgBB,
// Returns false if cloning required by jump threading cannot // Returns false if cloning required by jump threading cannot
// be performed, because some of the constraints are violated. // be performed, because some of the constraints are violated.
//
// This does not check the constraint on address projections with out-of-block
// uses. Those are rare enough that they don't need to be checked first for
// efficiency, but they need to be gathered later, just before cloning, anyway
// in order to sink the projections.
bool CheckedCastBrJumpThreading::checkCloningConstraints() { bool CheckedCastBrJumpThreading::checkCloningConstraints() {
// Check some cloning related constraints. // Check some cloning related constraints.
@@ -673,7 +676,9 @@ void CheckedCastBrJumpThreading::optimizeFunction() {
Fn->verifyCriticalEdges(); Fn->verifyCriticalEdges();
for (Edit *edit : Edits) { for (Edit *edit : Edits) {
Optional<BasicBlockCloner> Cloner; BasicBlockCloner Cloner(edit->CCBBlock);
if (!Cloner.canCloneBlock())
continue;
// Create a copy of the BB as a landing BB // Create a copy of the BB as a landing BB
// for all FailurePreds. // for all FailurePreds.
@@ -684,12 +689,11 @@ void CheckedCastBrJumpThreading::optimizeFunction() {
// Handle unknown preds. // Handle unknown preds.
edit->modifyCFGForUnknownPreds(); edit->modifyCFGForUnknownPreds();
if (Cloner.hasValue()) { if (Cloner.wasCloned()) {
updateSSAAfterCloning(*Cloner.getPointer(), Cloner->getNewBB(), Cloner.updateSSAAfterCloning();
edit->CCBBlock);
if (!Cloner->getNewBB()->pred_empty()) if (!Cloner.getNewBB()->pred_empty())
BlocksForWorklist.push_back(Cloner->getNewBB()); BlocksForWorklist.push_back(Cloner.getNewBB());
} }
if (!edit->CCBBlock->pred_empty()) if (!edit->CCBBlock->pred_empty())
BlocksForWorklist.push_back(edit->CCBBlock); BlocksForWorklist.push_back(edit->CCBBlock);

View File

@@ -438,7 +438,7 @@ UseWrapper::UseWrapper(Operand *Use) {
} }
/// Return the operand we wrap. Reconstructing branch operands. /// Return the operand we wrap. Reconstructing branch operands.
UseWrapper::operator Operand *() { Operand *UseWrapper::getOperand() {
switch (Type) { switch (Type) {
case kRegularUse: case kRegularUse:
return U; return U;

View File

@@ -43,8 +43,8 @@ public class C<R> {
let r : R let r : R
init(_ _r: R) { r = _r } init(_ _r: R) { r = _r }
// SIL: // C.f<A>(_:) // SIL-LABEL: // C.f<A>(_:)
// IR: define {{.*}} @"$s1A1CC1fyyqd__lF" // IR-LABEL: define {{.*}} @"$s1A1CC1fyyqd__lF"
#sourceLocation(file: "f.swift", line: 1) #sourceLocation(file: "f.swift", line: 1)
public func f<S>(_ s: S) { public func f<S>(_ s: S) {
// SIL: debug_value_addr %0 : $*S, let, name "s", argno 1,{{.*}} scope [[F]] // SIL: debug_value_addr %0 : $*S, let, name "s", argno 1,{{.*}} scope [[F]]
@@ -57,7 +57,13 @@ public class C<R> {
// IR: call {{.*}}3use // IR: call {{.*}}3use
#sourceLocation(file: "f.swift", line: 2) #sourceLocation(file: "f.swift", line: 2)
g(s) g(s)
// IR: dbg.value({{.*}}, metadata ![[GR_T:[0-9]+]] // Jump-threading removes the basic block containing debug_value
// "t" before the second call to `g(r)`. When this happens, the
// ref_element_addr in that removed block is left with a single
// debug_value use, so they are both deleted. This means we have
// no debug value for "t" in the call to `g(r)`.
// dbg.value({{.*}}, metadata ![[GR_T:[0-9]+]]
// IR: dbg.value({{.*}}, metadata ![[GR_U:[0-9]+]] // IR: dbg.value({{.*}}, metadata ![[GR_U:[0-9]+]]
// IR: call {{.*}}3use // IR: call {{.*}}3use
#sourceLocation(file: "f.swift", line: 3) #sourceLocation(file: "f.swift", line: 3)
@@ -81,6 +87,8 @@ public class C<R> {
g(false) g(false)
} }
} }
// SIL-LABEL: } // end sil function '$s1A1CC1fyyqd__lF'
// IR-LABEL: ret void
// IR: ![[BOOL:[0-9]+]] = !DICompositeType({{.*}}name: "Bool" // IR: ![[BOOL:[0-9]+]] = !DICompositeType({{.*}}name: "Bool"
// IR: ![[LET_BOOL:[0-9]+]] = !DIDerivedType(tag: DW_TAG_const_type, baseType: ![[BOOL]]) // IR: ![[LET_BOOL:[0-9]+]] = !DIDerivedType(tag: DW_TAG_const_type, baseType: ![[BOOL]])
@@ -96,9 +104,12 @@ public class C<R> {
// IR: ![[SP_GS_T]] = {{.*}}linkageName: "$s1A1gyyxlFqd___Ti5" // IR: ![[SP_GS_T]] = {{.*}}linkageName: "$s1A1gyyxlFqd___Ti5"
// IR: ![[GS_U]] = !DILocalVariable(name: "u", {{.*}} scope: ![[SP_GS_U:[0-9]+]], {{.*}} type: ![[LET_TAU_1_0]]) // IR: ![[GS_U]] = !DILocalVariable(name: "u", {{.*}} scope: ![[SP_GS_U:[0-9]+]], {{.*}} type: ![[LET_TAU_1_0]])
// IR: ![[SP_GS_U]] = {{.*}}linkageName: "$s1A1hyyxlFqd___Ti5" // IR: ![[SP_GS_U]] = {{.*}}linkageName: "$s1A1hyyxlFqd___Ti5"
// IR: ![[GR_T]] = !DILocalVariable(name: "t", {{.*}} scope: ![[SP_GR_T:[0-9]+]], {{.*}}type: ![[LET_TAU_0_0]])
// Debug info for this variable is removed. See the note above the call to g(r).
// ![[GR_T]] = !DILocalVariable(name: "t", {{.*}} scope: ![[SP_GR_T:[0-9]+]], {{.*}}type: ![[LET_TAU_0_0]])
// S has the same generic parameter numbering s T and U. // S has the same generic parameter numbering s T and U.
// IR: ![[SP_GR_T]] = {{.*}}linkageName: "$s1A1gyyxlF" // ![[SP_GR_T]] = {{.*}}linkageName: "$s1A1gyyxlF"
// IR: ![[GR_U]] = !DILocalVariable(name: "u", {{.*}} scope: ![[SP_GR_U:[0-9]+]], {{.*}}type: ![[LET_TAU_0_0]]) // IR: ![[GR_U]] = !DILocalVariable(name: "u", {{.*}} scope: ![[SP_GR_U:[0-9]+]], {{.*}}type: ![[LET_TAU_0_0]])
// IR: ![[SP_GR_U]] = {{.*}}linkageName: "$s1A1hyyxlF" // IR: ![[SP_GR_U]] = {{.*}}linkageName: "$s1A1hyyxlF"
// IR: ![[GRS_T]] = !DILocalVariable(name: "t", {{.*}} scope: ![[SP_GRS_T:[0-9]+]], {{.*}}type: ![[LET_TUPLE:[0-9]+]] // IR: ![[GRS_T]] = !DILocalVariable(name: "t", {{.*}} scope: ![[SP_GRS_T:[0-9]+]], {{.*}}type: ![[LET_TUPLE:[0-9]+]]

View File

@@ -134,3 +134,118 @@ bb4:
bb5: bb5:
return %val : $Builtin.Int32 return %val : $Builtin.Int32
} }
// Test that debug_value_addr is not unnecessarilly lost during address projection sinking.
public class CC<R> {
let r : R
init(_ _r: R) { r = _r }
}
sil @useAny : $@convention(thin) <V> (@in_guaranteed V) -> ()
// CHECK-LABEL: sil @testDebugValue : $@convention(method) <R><S> (@in_guaranteed S, @guaranteed CC<R>, Bool) -> () {
// CHECK: debug_value_addr %0 : $*S, let, name "u"
// CHECK: apply %{{.*}}<S>(%0) : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0) -> ()
// CHECK: [[FIELD:%.*]] = ref_element_addr %1 : $CC<R>, #CC.r
// CHECK: debug_value_addr [[FIELD]] : $*R, let, name "u"
// CHECK: apply %10<R>([[FIELD]]) : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0) -> ()
// CHECK-LABEL: } // end sil function 'testDebugValue'
sil @testDebugValue : $@convention(method) <R><S> (@in_guaranteed S, @guaranteed CC<R>, Bool) -> () {
bb0(%0 : $*S, %1 : $CC<R>, %2 : $Bool):
%bool = struct_extract %2 : $Bool, #Bool._value
cond_br %bool, bb1, bb2
bb1:
debug_value_addr %0 : $*S, let, name "u"
%f1 = function_ref @useAny : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0) -> ()
%call1 = apply %f1<S>(%0) : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0) -> ()
br bb2
bb2:
%field = ref_element_addr %1 : $CC<R>, #CC.r
debug_value_addr %field : $*R, let, name "t"
cond_br %bool, bb3, bb4
bb3:
debug_value_addr %field : $*R, let, name "u"
%f2 = function_ref @useAny : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0) -> ()
%call2 = apply %f2<R>(%field) : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0) -> ()
br bb4
bb4:
%z = tuple ()
return %z : $()
}
// Test multiple uses and cloned allocation.
//
// project_box and struct_extract_addr will be sunk into three
// different blocks, but only once per block.
struct S {
@_hasStorage @_hasInitialValue var x: Int { get set }
init(x: Int = 0)
init()
}
sil @doNothing : $@convention(thin) (@inout Int) -> ()
// CHECK-LABEL: sil @testMultiUse : $@convention(method) (Bool, @inout Int) -> () {
// CHECK: bb0(%0 : $Bool, %1 : $*Int):
// CHECK: cond_br %{{.*}}, bb2, bb1
// CHECK: bb1:
// CHECK: [[ALLOC1:%.*]] = alloc_box ${ var S }, var, name "s"
// CHECK: [[PROJ1:%.*]] = project_box [[ALLOC1]] : ${ var S }, 0
// CHECK: [[ADR1:%.*]] = struct_element_addr [[PROJ1]] : $*S, #S.x
// CHECK: store %{{.*}} to [[ADR1]] : $*Int
// CHECK: br bb3([[ALLOC1]] : ${ var S })
// CHECK: bb2:
// CHECK: apply %{{.*}}(%1) : $@convention(thin) (@inout Int) -> ()
// CHECK: [[ALLOC2:%.*]] = alloc_box ${ var S }, var, name "s"
// CHECK: [[PROJ2:%.*]] = project_box [[ALLOC2]] : ${ var S }, 0
// CHECK: [[ADR2:%.*]] = struct_element_addr [[PROJ2]] : $*S, #S.x
// CHECK: store %{{.*}} to [[ADR2]] : $*Int
// CHECK: apply %{{.*}}([[ADR2]]) : $@convention(thin) (@inout Int) -> ()
// CHECK: br bb3([[ALLOC2]] : ${ var S })
// CHECK: bb3([[BOXARG:%.*]] : ${ var S }):
// CHECK: [[PROJ3:%.*]] = project_box [[BOXARG]] : ${ var S }, 0
// CHECK: [[ADR3:%.*]] = struct_element_addr [[PROJ3]] : $*S, #S.x
// CHECK: apply %{{.*}}([[ADR3]]) : $@convention(thin) (@inout Int) -> ()
// CHECK: release_value [[BOXARG]] : ${ var S }
// CHECK-LABEL: } // end sil function 'testMultiUse'
sil @testMultiUse : $@convention(method) (Bool, @inout Int) -> () {
bb0(%0 : $Bool, %1 : $*Int):
%bool = struct_extract %0 : $Bool, #Bool._value
cond_br %bool, bb1, bb2
bb1:
%f1 = function_ref @doNothing : $@convention(thin) (@inout Int) -> ()
%call1 = apply %f1(%1) : $@convention(thin) (@inout Int) -> ()
br bb3
bb2:
br bb3
bb3:
%box3 = alloc_box ${ var S }, var, name "s"
%proj3 = project_box %box3 : ${ var S }, 0
%adr3 = struct_element_addr %proj3 : $*S, #S.x
cond_br %bool, bb4, bb5
bb4:
%i4 = load %1 : $*Int
store %i4 to %adr3 : $*Int
%f2 = function_ref @doNothing : $@convention(thin) (@inout Int) -> ()
%call2 = apply %f2(%adr3) : $@convention(thin) (@inout Int) -> ()
br bb6
bb5:
%i5 = load %1 : $*Int
store %i5 to %adr3 : $*Int
br bb6
bb6:
%f6 = function_ref @doNothing : $@convention(thin) (@inout Int) -> ()
%call6 = apply %f6(%adr3) : $@convention(thin) (@inout Int) -> ()
release_value %box3 : ${ var S }
%z = tuple ()
return %z : $()
}