#define DEBUG_TYPE "sil-simplify-cfg" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallVector.h" #include "swift/SIL/SILInstruction.h" #include "swift/SIL/InstructionUtils.h" #include "swift/SILOptimizer/Analysis/DominanceAnalysis.h" #include "swift/SILOptimizer/Utils/CFG.h" #include "swift/SILOptimizer/Utils/Local.h" #include "swift/SILOptimizer/Utils/SILInliner.h" using namespace swift; namespace { /// This is a class implementing a dominator-based jump-threading /// for checked_cast_br [exact]. class CheckedCastBrJumpThreading { // The checked_cast_br instruction, which // we try to jump-thread CheckedCastBranchInst *CCBI; // Basic block of the current checked_cast_br instruction. SILBasicBlock *BB; // Condition used by the current checked_cast_br instruction. SILValue Condition; // Success branch of the current checked_cast_br instruction. SILBasicBlock *SuccessBB; // Failure branch of the current checked_cast_br instruction. SILBasicBlock *FailureBB; // Current dominating checked_cast_br instruction. CheckedCastBranchInst *DomCCBI; // Basic block of the dominating checked_cast_br instruction. SILBasicBlock *DomBB; // Condition used by the dominating checked_cast_br instruction. SILValue DomCondition; // Success branch of the dominating checked_cast_br instruction. SILBasicBlock *DomSuccessBB; // Failure branch of the dominating checked_cast_br instruction. SILBasicBlock *DomFailureBB; // Current dominator tree node where we look for a dominating // checked_cast_br instruction. llvm::DomTreeNodeBase *Node; SILBasicBlock *ArgBB; // Dominator information to be used. DominanceInfo *DT; // Basic block created as a landing BB for all failure predecessors. SILBasicBlock *TargetFailureBB; // Basic block created as a landing BB for all success predecessors. SILBasicBlock *TargetSuccessBB; // Cloner used to clone the BB to FailureSuccessBB. Optional FailureBBCloner; // Cloner used to clone the BB to TargetSuccessBB. Optional SuccessBBCloner; // Predecessors reached only via a path along the // success branch of the dominating checked_cast_br. SmallVector SuccessPreds; // Predecessors reached only via a path along the // failure branch of the dominating checked_cast_br. SmallVector FailurePreds; // All other predecessors, where the outcome of the // checked_cast_br along the path is not known. SmallVector UnknownPreds; // Basic blocks to be added to for reprocessing // after jump-threading is done. SmallVectorImpl &BlocksForWorklist; bool areEquivalentConditionsAlongPaths(); bool areEquivalentConditionsAlongSomePaths(); bool handleArgBBIsEntryBlock(SILBasicBlock *ArgBB); bool checkCloningConstraints(); void modifyCFGForUnknownPreds(); void modifyCFGForFailurePreds(); void modifyCFGForSuccessPreds(); void updateDominatorTree(); void updateSSA(); void addBlockToSimplifyCFGWorklist(SILBasicBlock *BB); void addBlocksToWorklist(); void classifyPredecessor( SILBasicBlock *Pred, SmallVectorImpl &SuccessPreds, SmallVectorImpl &FailurePreds, SmallVectorImpl &UnknownPreds, bool SuccessDominates, bool FailureDominates); SILValue isArgValueEquivalentToCondition(SILValue Value, SILBasicBlock *DomBB, SILValue DomValue, DominanceInfo *DT); public: CheckedCastBrJumpThreading(DominanceInfo *DT, SmallVectorImpl &BBs) : DT(DT), BlocksForWorklist(BBs) { } bool trySimplify(TermInst *Term); ArrayRef getBlocksForWorklist() { return BlocksForWorklist; } }; } // end anonymous namespace /// Find a nearest common dominator for a given set of basic blocks. static DominanceInfoNode *findCommonDominator(ArrayRef BBs, DominanceInfo *DT) { DominanceInfoNode *CommonDom = nullptr; for (auto *BB : BBs) { if (!CommonDom) { CommonDom = DT->getNode(BB); } else { CommonDom = DT->getNode( DT->findNearestCommonDominator(CommonDom->getBlock(), BB)); } } return CommonDom; } /// Find a nearest common dominator for all predecessors of /// a given basic block. static DominanceInfoNode *findCommonDominator(SILBasicBlock *BB, DominanceInfo *DT) { SmallVector Preds; for (auto *Pred: BB->getPreds()) Preds.push_back(Pred); return findCommonDominator(Preds, DT); } /// Estimate the cost of inlining a given basic block. static unsigned basicBlockInlineCost(SILBasicBlock *BB, unsigned Cutoff) { unsigned Cost = 0; for (auto &I : *BB) { auto ICost = instructionInlineCost(I); Cost += unsigned(ICost); if (Cost > Cutoff) return Cost; } return Cost; } /// We cannot duplicate blocks with AllocStack instructions (they need to be /// FIFO). Other instructions can be duplicated. static bool canDuplicateBlock(SILBasicBlock *BB) { for (auto &I : *BB) { if (!I.isTriviallyDuplicatable()) return false; } return true; } void CheckedCastBrJumpThreading::addBlockToSimplifyCFGWorklist(SILBasicBlock *BB) { BlocksForWorklist.push_back(BB); } /// Add affected blocks for re-processing by simplifyCFG void CheckedCastBrJumpThreading::addBlocksToWorklist() { if (TargetFailureBB) { if (!TargetFailureBB->pred_empty()) addBlockToSimplifyCFGWorklist(TargetFailureBB); } if (TargetSuccessBB) { if (!TargetSuccessBB->pred_empty()) addBlockToSimplifyCFGWorklist(TargetSuccessBB); } if (!BB->pred_empty()) addBlockToSimplifyCFGWorklist(BB); } /// Classify a predecessor of a BB containing checked_cast_br as being /// reachable via success or failure branches of a dominating checked_cast_br /// or as unknown if it can be reached via success or failure branches /// at the same time. void CheckedCastBrJumpThreading::classifyPredecessor( SILBasicBlock *Pred, SmallVectorImpl &SuccessPreds, SmallVectorImpl &FailurePreds, SmallVectorImpl &UnknownPreds, bool SuccessDominates, bool FailureDominates) { if (SuccessDominates && FailureDominates) { UnknownPreds.push_back(Pred); return; } if (SuccessDominates) { SuccessPreds.push_back(Pred); return; } if (FailureDominates) { FailurePreds.push_back(Pred); return; } UnknownPreds.push_back(Pred); } /// Check if the root value for Value that comes /// along the path from DomBB is equivalent to the /// DomCondition. SILValue CheckedCastBrJumpThreading::isArgValueEquivalentToCondition( SILValue Value, SILBasicBlock *DomBB, SILValue DomValue, DominanceInfo *DT) { SmallPtrSet SeenValues; DomValue = stripClassCasts(DomValue); while (true) { Value = stripClassCasts(Value); if (Value == DomValue) return Value; // We know how to propagate through BBArgs only. auto *V = dyn_cast(Value); if (!V) return SILValue(); // Have we visited this BB already? if (!SeenValues.insert(Value).second) return SILValue(); if (SeenValues.size() > 10) return SILValue(); SmallVector IncomingValues; if (!V->getIncomingValues(IncomingValues) || IncomingValues.empty()) return SILValue(); ValueBase *Def = nullptr; for (auto IncomingValue : IncomingValues) { // Each incoming value should be either from a block // dominated by DomBB or it should be the value used in // condition in DomBB Value = stripClassCasts(IncomingValue); if (Value == DomValue) continue; // Values should be the same if (!Def) Def = Value; if (Def != Value) return SILValue(); if (!DT->dominates(DomBB, Value->getParentBB())) return SILValue(); // OK, this value is a potential candidate } Value = IncomingValues[0]; } } /// Update the SSA form after all changes. void CheckedCastBrJumpThreading::updateSSA() { assert(!(SuccessBBCloner.hasValue() && FailureBBCloner.hasValue()) && "Both cloners cannot be used at the same time yet"); // Now update the SSA form. if (!FailurePreds.empty() && FailureBBCloner.hasValue() && !SuccessBBCloner.hasValue()) updateSSAAfterCloning(*FailureBBCloner.getPointer(), TargetFailureBB, BB); if (SuccessBBCloner.hasValue() && !FailureBBCloner.hasValue()) { updateSSAAfterCloning(*SuccessBBCloner.getPointer(), TargetSuccessBB, BB); } } /// Update the SSA form after all changes. void CheckedCastBrJumpThreading::updateDominatorTree() { // Update the dominator tree. // If BB was IDom of something, then PredCBBI becomes the IDOM // of this after jump-threading. auto *BBDomNode = DT->getNode(BB); auto &Children = BBDomNode->getChildren(); if (Children.size() > 1) { SmallVector ChildrenCopy; std::copy(Children.begin(), Children.end(), std::back_inserter(ChildrenCopy)); for (auto *Child : ChildrenCopy) { DT->changeImmediateDominator(Child, Node); } } DominanceInfoNode *CommonDom; // Find a common dominator for all unknown preds. if (!UnknownPreds.empty()) { // Find a new IDom for FailureBB CommonDom = findCommonDominator(FailureBB, DT); if (CommonDom) DT->changeImmediateDominator(FailureBB, CommonDom->getBlock()); CommonDom = findCommonDominator(UnknownPreds, DT); // This common dominator dominates the BB now. if (CommonDom) { DT->changeImmediateDominator(BB, CommonDom->getBlock()); } } // Find a common dominator for all failure preds. CommonDom = findCommonDominator(FailurePreds, DT); // This common dominator dominates the TargetFailureBB now. if (CommonDom) { DT->addNewBlock(TargetFailureBB, CommonDom->getBlock()); // Find a new IDom for FailureBB CommonDom = findCommonDominator(FailureBB, DT); if (CommonDom) DT->changeImmediateDominator(FailureBB, CommonDom->getBlock()); } // Find a common dominator for all success preds. CommonDom = findCommonDominator(SuccessPreds, DT); // This common dominator of all success preds dominates the BB now. if (CommonDom) { if (TargetSuccessBB) { DT->addNewBlock(TargetSuccessBB, CommonDom->getBlock()); } else { DT->changeImmediateDominator(BB, CommonDom->getBlock()); } CommonDom = findCommonDominator(SuccessBB, DT); if (CommonDom) DT->changeImmediateDominator(SuccessBB, CommonDom->getBlock()); } // End of dominator tree update. } void CheckedCastBrJumpThreading::modifyCFGForUnknownPreds() { if (UnknownPreds.empty()) return; // Check the FailureBB if it is a BB that contains a class_method // referring to the same value as a condition. This pattern is typical // for method chaining code like obj.method1().method2().etc() SILInstruction *Inst = &*FailureBB->begin(); if (ClassMethodInst *CMI = dyn_cast(Inst)) { if (CMI->getOperand() == Condition) { // Replace checked_cast_br by branch to FailureBB. SILBuilder(BB).createBranch(CCBI->getLoc(), FailureBB); CCBI->eraseFromParent(); } } } /// Create a copy of the BB as a landing BB /// for all FailurePreds. void CheckedCastBrJumpThreading::modifyCFGForFailurePreds() { if (FailurePreds.empty()) return; FailureBBCloner.emplace(BasicBlockCloner(BB)); FailureBBCloner->clone(); TargetFailureBB = FailureBBCloner->getDestBB(); auto *TI = TargetFailureBB->getTerminator(); SILBuilderWithScope Builder(TI); // This BB copy branches to a FailureBB. Builder.createBranch(TI->getLoc(), FailureBB); TI->eraseFromParent(); // Redirect all FailurePreds to the copy of BB. for (auto *Pred : FailurePreds) { TermInst *TI = Pred->getTerminator(); // Replace branch to BB by branch to TargetFailureBB. replaceBranchTarget(TI, BB, TargetFailureBB, /*PreserveArgs=*/true); Pred = nullptr; } } /// Create a copy of the BB or reuse BB as /// a landing basic block for all FailurePreds. void CheckedCastBrJumpThreading::modifyCFGForSuccessPreds() { if (!UnknownPreds.empty()) { if (!SuccessPreds.empty()) { // Create a copy of the BB as a landing BB. // for all SuccessPreds. SuccessBBCloner.emplace(BasicBlockCloner(BB)); SuccessBBCloner->clone(); TargetSuccessBB = SuccessBBCloner->getDestBB(); auto *TI = TargetSuccessBB->getTerminator(); SILBuilderWithScope Builder(TI); SmallVector SuccessBBArgs; // Take argument value from the dominating BB. SuccessBBArgs.push_back(DomSuccessBB->getBBArg(0)); // This BB copy branches to SuccessBB. Builder.createBranch(TI->getLoc(), SuccessBB, SuccessBBArgs); TI->eraseFromParent(); // Redirect all SuccessPreds to the copy of BB. for (auto *Pred : SuccessPreds) { TermInst *TI = Pred->getTerminator(); // Replace branch to BB by branch to TargetSuccessBB. replaceBranchTarget(TI, BB, TargetSuccessBB, /*PreserveArgs=*/true); SuccessBBArgs.push_back(DomSuccessBB->getBBArg(0)); Pred = nullptr; } } } else { // There are no predecessors where it is not clear // if they are dominated by a success or failure branch // of DomBB. Therefore, there is no need to clone // the BB for SuccessPreds. Current BB can be re-used // instead as their target. // Add an unconditional jump at the end of the block. SmallVector SuccessBBArgs; // Take argument value from the dominating BB SuccessBBArgs.push_back(DomSuccessBB->getBBArg(0)); SILBuilder(BB).createBranch(CCBI->getLoc(), SuccessBB, SuccessBBArgs); CCBI->eraseFromParent(); } } /// Handle a special case, where ArgBB is the entry block. bool CheckedCastBrJumpThreading::handleArgBBIsEntryBlock(SILBasicBlock *ArgBB) { if (ArgBB->getPreds().begin() == ArgBB->getPreds().end()) { // It must be the entry block // See if it is reached over Success or Failure path. bool SuccessDominates = DomSuccessBB == BB; bool FailureDominates = DomFailureBB == BB; classifyPredecessor(ArgBB, SuccessPreds, FailurePreds, UnknownPreds, SuccessDominates, FailureDominates); return true; } return false; } // Returns false if cloning required by jump threading cannot // be performed, because some of the constraints are violated. bool CheckedCastBrJumpThreading::checkCloningConstraints() { // Check some cloning related constraints. // If this argument from a different BB, then jump-threading // may require too much code duplication. if (ArgBB && ArgBB != BB) return false; // Bail out if current BB cannot be duplicated. if (!canDuplicateBlock(BB)) return false; // Check if code-bloat would be too big when this BB // is jump-threaded. // TODO: Make InlineCostCutoff parameter configurable? // Dec 1, 2014: // We looked at the inline costs of BBs from our benchmark suite // and found that currently the highest inline cost for the // whole benchmark suite is 12. In 95% of all cases it is <=3. const unsigned InlineCostCutoff = 20; if (basicBlockInlineCost(BB, InlineCostCutoff) >= InlineCostCutoff) return false; return true; } /// If conditions are not equivalent along all paths, try harder /// to check if they are actually equivalent along a subset of paths. /// To do it, try to back-propagate the Condition /// backwards and see if it is actually equivalent to DomCondition. /// along some of the paths. bool CheckedCastBrJumpThreading::areEquivalentConditionsAlongSomePaths() { auto *Arg = dyn_cast(Condition); if (!Arg) return false; ArgBB = Arg->getParent(); if (!DT->dominates(DomBB, ArgBB)) return false; // Incoming values for the BBArg. SmallVector IncomingValues; if (ArgBB != ArgBB->getParent()->begin() && (!Arg->getIncomingValues(IncomingValues) || IncomingValues.empty())) return false; // Check for each predecessor, if the incoming value coming from it // is equivalent to the DomCondition. If this is the case, it is // possible to try jump-threading along this path. if (!handleArgBBIsEntryBlock(ArgBB)) { // ArgBB is not the entry block and has predecessors. unsigned idx = 0; for (auto *PredBB : ArgBB->getPreds()) { auto IncomingValue = IncomingValues[idx]; SILValue ReachingValue = isArgValueEquivalentToCondition( IncomingValue, DomBB, DomCondition, DT); if (ReachingValue == SILValue()) { UnknownPreds.push_back(PredBB); idx++; continue; } // Condition is the same if BB is reached over a pass through Pred. DEBUG(llvm::dbgs() << "Condition is the same if reached over "); DEBUG(PredBB->print(llvm::dbgs())); // See if it is reached over Success or Failure path. bool SuccessDominates = DT->dominates(DomSuccessBB, PredBB) || DT->dominates(DomSuccessBB, BB) || DomSuccessBB == BB; bool FailureDominates = DT->dominates(DomFailureBB, PredBB) || DT->dominates(DomFailureBB, BB) || DomFailureBB == BB; classifyPredecessor( PredBB, SuccessPreds, FailurePreds, UnknownPreds, SuccessDominates, FailureDominates); idx++; } } else { // ArgBB is the entry block. Check that conditions are the equivalent in this // case as well. if (!isArgValueEquivalentToCondition(Condition, DomBB, DomCondition, DT)) return false; } // At this point we know for each predecessor of ArgBB if its reached // over the success, failure or unknown path from DomBB. // Now we can generate a new BB for preds reaching BB over the success // path and a new BB for preds reaching BB over the failure path. // Then we redirect those preds to those new basic blocks. return true; } /// Check if conditions of CCBI and DomCCBI are equivalent along /// all or at least some paths. bool CheckedCastBrJumpThreading::areEquivalentConditionsAlongPaths() { // Are conditions equivalent along all paths? if (DomCondition == Condition) { // Conditions are exactly the same, without any restrictions. // They are equivalent along all paths. // Figure out for each predecessor which branch of // the dominating checked_cast_br is used to reach it. for (auto *PredBB : BB->getPreds()) { // All predecessors should either unconditionally branch // to the current BB or be another checked_cast_br instruction. if (!dyn_cast(PredBB->getTerminator()) && !dyn_cast(PredBB->getTerminator())) return false; bool SuccessDominates = DT->dominates(DomSuccessBB, PredBB) || DomSuccessBB == BB; bool FailureDominates = DT->dominates(DomFailureBB, PredBB) || DomFailureBB == BB; classifyPredecessor(PredBB, SuccessPreds, FailurePreds, UnknownPreds, SuccessDominates, FailureDominates); } return true; } // Check if conditions are equivalent along a subset of reaching paths. return areEquivalentConditionsAlongSomePaths(); } /// Try performing a dominator-based jump-threading for /// checked_cast_br instructions. bool CheckedCastBrJumpThreading::trySimplify(TermInst *Term) { CCBI = cast(Term); if (!CCBI) return false; // Init information about the checked_cast_br we try to // jump-thread. BB = Term->getParent(); Condition = stripClassCasts(Term->getOperand(0)); SuccessBB = CCBI->getSuccessBB(); FailureBB = CCBI->getFailureBB(); // Find a dominating checked_cast_br, which performs the same check. for (Node = DT->getNode(BB)->getIDom(); Node; Node = Node->getIDom()) { // Get current dominating block. DomBB = Node->getBlock(); auto *DomTerm = DomBB->getTerminator(); if (!DomTerm->getNumOperands()) continue; // Check that it is a dominating checked_cast_br. DomCCBI = dyn_cast(DomTerm); if (!DomCCBI) continue; // We need to verify that the result type is the same in the // dominating checked_cast_br, but only for non-exact casts. // For exact casts, we are interested only in the // fact that the source operand is the same for // both instructions. if (!CCBI->isExact() && !DomCCBI->isExact()) { if (DomCCBI->getCastType() != CCBI->getCastType()) continue; } // Conservatively check that both checked_cast_br instructions // are either exact or non-exact. This is very conservative, // but safe. // // TODO: // If the dominating checked_cast_br is non-exact, then // it is in general not safe to assume that current exact cast // would have the same outcome. But if the dominating non-exact // checked_cast_br fails, then the current exact cast would // always fail as well. // // If the dominating checked_cast_br is exact then then // it is in general not safe to assume that the current non-exact // cast would have the same outcome. But if the dominating exact // checked_cast_br succeeds, then the current non-exact cast // would always succeed as well. // // TODO: In some specific cases, it is possible to prove that // success or failure of the dominating cast is equivalent to // the success or failure of the current cast, even if one // of them is exact and the other not. This is the case // e.g. if the class has no subclasses. if (DomCCBI->isExact() != CCBI->isExact()) continue; // Initialize state variables for the current round of checks // based on the found dominating checked_cast_br. DomSuccessBB = DomCCBI->getSuccessBB(); DomFailureBB = DomCCBI->getFailureBB(); DomCondition = stripClassCasts(DomTerm->getOperand(0)); // Init state variables for paths analysis SuccessPreds.clear(); FailurePreds.clear(); UnknownPreds.clear(); ArgBB = nullptr; // Init state variables for jump-threading transformation. TargetFailureBB = nullptr; TargetSuccessBB = nullptr; // Are conditions of CCBI and DomCCBI equivalent along (some) paths? // If this is the case, classify all incoming paths into SuccessPreds, // FailurePreds or UnknownPreds depending on how they reach CCBI. if (!areEquivalentConditionsAlongPaths()) continue; // Check if any jump-threading is required and possible. if (SuccessPreds.empty() && FailurePreds.empty()) return false; // If this check is reachable via success, failure and unknown // at the same time, then we don't know the outcome of the // dominating check. No jump-threading is possible in this case. if (!SuccessPreds.empty() && !FailurePreds.empty() && !UnknownPreds.empty()) { return false; } unsigned TotalPreds = SuccessPreds.size() + FailurePreds.size() + UnknownPreds.size(); // We only need to clone the BB if not all of its // predecessors are in the same group. if (TotalPreds != SuccessPreds.size() && TotalPreds != UnknownPreds.size()) { // Check some cloning related constraints. if (!checkCloningConstraints()) return false; } bool InvertSuccess = false; if (DomCCBI->isExact() && CCBI->isExact() && DomCCBI->getCastType() != CCBI->getCastType()) { if (TotalPreds == SuccessPreds.size()) { // The dominating exact cast was successful, but it casted to a // different type. Therefore, the current cast fails for sure. // Since we are going to change the BB, // add its successors and predecessors // for re-processing. InvertSuccess = true; } else { // Otherwise, we don't know if the current cast will succeed or // fail. return false; } } // If we have predecessors, where it is not known if they are reached over // success or failure path, we cannot eliminate a checked_cast_br. // We have to generate new dedicated BBs as landing BBs for all // FailurePreds and all SuccessPreds. // Since we are going to change the BB, // add its successors and predecessors // for re-processing. for (auto *B : BB->getPreds()) { addBlockToSimplifyCFGWorklist(B); } for (auto *B : BB->getSuccessorBlocks()) { addBlockToSimplifyCFGWorklist(B); } // Create a copy of the BB as a landing BB // for all FailurePreds. modifyCFGForFailurePreds(); if (InvertSuccess) { SILBuilder(BB).createBranch(CCBI->getLoc(), FailureBB); CCBI->eraseFromParent(); SuccessPreds.clear(); } else { // Create a copy of the BB or reuse BB as // a landing basic block for all SuccessPreds. modifyCFGForSuccessPreds(); } // Handle unknown preds. modifyCFGForUnknownPreds(); // Update the dominator tree after all changes. updateDominatorTree(); // Update the SSA form after all changes. updateSSA(); // Since a few BBs were changed now, add them for re-processing. addBlocksToWorklist(); return true; } // Jump-threading was not possible. return false; } namespace swift { bool tryCheckedCastBrJumpThreading(TermInst *Term, DominanceInfo *DT, SmallVectorImpl &BBs) { CheckedCastBrJumpThreading CCBJumpThreading(DT, BBs); return CCBJumpThreading.trySimplify(Term); } } // end namespace swift