//===--- DeadCodeElimination.cpp - Delete dead code ----------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// #define DEBUG_TYPE "sil-dce" #include "swift/Basic/Assertions.h" #include "swift/Basic/BlotSetVector.h" #include "swift/SIL/BasicBlockBits.h" #include "swift/SIL/DebugUtils.h" #include "swift/SIL/MemAccessUtils.h" #include "swift/SIL/NodeBits.h" #include "swift/SIL/OSSACompleteLifetime.h" #include "swift/SIL/OwnershipUtils.h" #include "swift/SIL/SILArgument.h" #include "swift/SIL/SILBasicBlock.h" #include "swift/SIL/SILBuilder.h" #include "swift/SIL/SILFunction.h" #include "swift/SIL/SILUndef.h" #include "swift/SILOptimizer/Analysis/DeadEndBlocksAnalysis.h" #include "swift/SILOptimizer/Analysis/DominanceAnalysis.h" #include "swift/SILOptimizer/PassManager/Passes.h" #include "swift/SILOptimizer/PassManager/Transforms.h" #include "swift/SILOptimizer/Utils/BasicBlockOptUtils.h" #include "swift/SILOptimizer/Utils/CFGOptUtils.h" #include "swift/SILOptimizer/Utils/InstOptUtils.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/Statistic.h" #include "llvm/Support/Debug.h" #include "llvm/Support/raw_ostream.h" using namespace swift; STATISTIC(NumBranchesPromoted, "Number of dead branches promoted to jumps"); STATISTIC(NumDeletedInsts, "Number of instructions deleted"); namespace { // Without any complex analysis, does this instruction seem like // something that we need to keep? // // FIXME: Reconcile the similarities between this and // isInstructionTriviallyDead. static bool seemsUseful(SILInstruction *I) { // Even though begin_access/destroy_value/copy_value/end_lifetime have // side-effects, they can be DCE'ed if they do not have useful // dependencies/reverse dependencies if (isa(I) || isa(I) || isa(I) || isa(I) || isa(I)) return false; if (isa(I)) { return false; } // A load [copy] is okay to be DCE'ed if there are no useful dependencies if (auto *load = dyn_cast(I)) { if (load->getOwnershipQualifier() == LoadOwnershipQualifier::Copy) return false; } if (I->mayHaveSideEffects()) return true; if (llvm::any_of(I->getResults(), [](auto result) { return result->isLexical(); })) { return true; } // Instructions which end the lifetimes of values which escape can only be // deleted if compensating lifetime ends are added. Compensating lifetime // ends are added by OSSACompleteLifetime when the def block of the value // is different from the parent block of the instruction. But // OSSACompleteLifetime requires that liveness be complete--that there are no // pointer escapes. So we can't delete instructions which end the lifetime // of values which escape to a pointer and whose parent blocks are different. if (llvm::any_of(I->getAllOperands(), [I](Operand &operand) { if (!operand.isLifetimeEnding()) return false; auto value = operand.get(); if (isa(value)) return false; auto *insertionPoint = value->getDefiningInsertionPoint(); ASSERT(insertionPoint); if (insertionPoint->getParent() == I->getParent()) return false; return findPointerEscape(value); })) { return true; } if (auto *BI = dyn_cast(I)) { // Although the onFastPath builtin has no side-effects we don't want to // remove it. return BI->getBuiltinInfo().ID == BuiltinValueKind::OnFastPath; } if (isa(I)) return true; if (auto TI = dyn_cast(I)) { if (TI->isFunctionExiting()) return true; } // Is useful if it's associating with a function argument // If undef, it is useful and it doesn't cost anything. if (isa(I)) return isa(I->getOperand(0)) || isa(I->getOperand(0)); // Don't delete allocation instructions in DCE. if (isa(I) || isa(I)) { return true; } return false; } // We map from post-dominator tree node to a ControllingInfo struct // which contains the post-dominator tree level of this node, along // with the direct predecessors that this node is control-dependent // on, and the minimum level number of any predecessor in the subtree // below this node in the post-dominator tree. struct ControllingInfo { typedef std::pair PredInfo; SILBasicBlock *Block; // The post-dominator tree level for this node. unsigned Level; llvm::SmallVector ControllingPreds; unsigned MinTreePredLevel; }; class DCE { typedef llvm::DomTreeNodeBase PostDomTreeNode; SILFunction *F; ValueSet LiveArguments; InstructionSet LiveInstructions; BasicBlockSet LiveBlocks; llvm::SmallVector Worklist; PostDominanceInfo *PDT; DominanceInfo *DT; DeadEndBlocks *deadEndBlocks; llvm::DenseMap ControllingInfoMap; SmallBlotSetVector valuesToComplete; // Maps instructions which produce a failing condition (like overflow // builtins) to the actual cond_fail instructions which handle the failure. // Dependencies which go in the reverse direction. Usually for a pair // %1 = inst_a // inst_b(%1) // the dependency goes from inst_b to inst_a: if inst_b is alive then // inst_a is also alive. // For some instructions the dependency is exactly the other way round, e.g. // %1 = inst_which_can_fail // cond_fail(%1) // In this case cond_fail is alive only if inst_which_can_fail is alive. // The key of this map is the source of the dependency (inst_a), the // value is the set of instructions dependent on it (inst_b). llvm::DenseMap> ReverseDependencies; // guaranteedPhiDependencies tracks the dependency of reborrows and // @guaranteed forwarding phis with its base value. // If the base value is also passed along as a phi operand with the reborrow // operand/GuaranteedForwardingPhi operand, we will have a new base value // for the reborrow phi/@guaranteed forwarding phi. using BaseValueSet = SmallPtrSet; llvm::DenseMap guaranteedPhiDependencies; /// Tracks if the pass changed branches. bool BranchesChanged = false; /// Tracks if the pass changed ApplyInsts. bool CallsChanged = false; bool precomputeControlInfo(); /// Populates borrow dependencies and disables DCE if needed. void processBorrow(BorrowedValue borrow); void markLive(); /// Record a reverse dependency from \p from to \p to meaning \p to is live /// if \p from is also live. void addReverseDependency(SILValue from, SILInstruction *to); /// Starting from \p borrow find all reborrow and guaranteed phi dependencies /// along with their base values. void findGuaranteedPhiDependencies(BorrowedValue borrow); bool removeDead(); void computeLevelNumbers(PostDomTreeNode *root); bool hasInfiniteLoops(); void computePredecessorDependence(); void computeMinPredecessorLevels(PostDomTreeNode *root); void insertControllingInfo(SILBasicBlock *Block, unsigned Level); void markValueLive(SILValue V); void markInstructionLive(SILInstruction *Inst); void markTerminatorArgsLive(SILBasicBlock *Pred, SILBasicBlock *Succ, size_t ArgIndex); void markControllingTerminatorsLive(SILBasicBlock *Block); void propagateLiveBlockArgument(SILArgument *Arg); void propagateLiveness(SILInstruction *I); void collectControllingBlocksInTree(ControllingInfo &QueryInfo, PostDomTreeNode *root, llvm::SmallPtrSetImpl &Controlling); void collectControllingBlocks(SILBasicBlock *Block, llvm::SmallPtrSetImpl &); SILBasicBlock *nearestUsefulPostDominator(SILBasicBlock *Block); void replaceBranchWithJump(SILInstruction *Inst, SILBasicBlock *Block); /// Insert lifetime ending instruction if defining value of \p op is live. void endLifetimeOfLiveValue(Operand *op, SILInstruction *insertPt); public: DCE(SILFunction *F, PostDominanceInfo *PDT, DominanceInfo *DT, DeadEndBlocks *deadEndBlocks) : F(F), LiveArguments(F), LiveInstructions(F), LiveBlocks(F), PDT(PDT), DT(DT), deadEndBlocks(deadEndBlocks) {} /// The entry point to the transformation. bool run() { if (!precomputeControlInfo()) return false; markLive(); return removeDead(); } bool mustInvalidateCalls() const { return CallsChanged; } bool mustInvalidateBranches() const { return BranchesChanged; } }; // Keep track of the fact that V is live and add it to our worklist // so that we can process the values it depends on. void DCE::markValueLive(SILValue V) { if (SILInstruction *inst = V->getDefiningInstruction()) return markInstructionLive(inst); if (isa(V)) return; LLVM_DEBUG(llvm::dbgs() << "Marking as live: " << *V); auto *Arg = cast(V); if (!LiveArguments.insert(Arg)) return; markControllingTerminatorsLive(Arg->getParent()); propagateLiveBlockArgument(Arg); } void DCE::markInstructionLive(SILInstruction *Inst) { if (!LiveInstructions.insert(Inst)) return; LLVM_DEBUG(llvm::dbgs() << "Marking as live: " << *Inst); Worklist.push_back(Inst); } /// Gets the producing instruction of a cond_fail condition. Currently these /// are overflow builtins but may be extended to other instructions in the /// future. static BuiltinInst *getProducer(CondFailInst *CFI) { // Check for the pattern: // %1 = builtin "some_operation_with_overflow" // %2 = tuple_extract %1 // %3 = cond_fail %2 SILValue FailCond = CFI->getOperand(); if (auto *TEI = dyn_cast(FailCond)) { if (auto *BI = dyn_cast(TEI->getOperand())) { return BI; } } return nullptr; } void DCE::processBorrow(BorrowedValue borrow) { // Populate guaranteedPhiDependencies for this borrow findGuaranteedPhiDependencies(borrow); if (!borrow.hasReborrow()) { return; } // If the borrow was not computed from another // borrow, return. SILValue baseValue; if (auto *beginBorrow = dyn_cast(*borrow)) { auto borrowOp = beginBorrow->getOperand(); if (borrowOp->getOwnershipKind() != OwnershipKind::Guaranteed) { return; } baseValue = borrowOp; } else { auto *loadBorrow = cast(*borrow); auto accessBase = AccessBase::compute(loadBorrow->getOperand()); if (!accessBase.isReference()) { return; } baseValue = accessBase.getReference(); } // If the borrow was computed from another // borrow, disable DCE of the outer borrow. // This is because, when a reborrow is dead, DCE has to insert // end_borrows in predecessor blocks and it cannot yet handle borrow // nesting. // TODO: Instead of disabling DCE of outer borrow, consider inserting // end_borrows inside-out. SmallVector roots; findGuaranteedReferenceRoots(baseValue, /*lookThroughNestedBorrows=*/false, roots); // Visit the end_borrows of all the borrow scopes that this // begin_borrow could be borrowing, and mark them live. for (auto root : roots) { visitTransitiveEndBorrows(root, [&](EndBorrowInst *endBorrow) { markInstructionLive(endBorrow); }); } } // Determine which instructions from this function we need to keep. void DCE::markLive() { // Find the initial set of instructions in this function that appear // to be live in the sense that they are not trivially something we // can delete by examining only that instruction. for (auto &BB : *F) { for (auto &I : BB) { switch (I.getKind()) { case SILInstructionKind::CondFailInst: { if (auto *Prod = getProducer(cast(&I))) { addReverseDependency(Prod, &I); } else { markInstructionLive(&I); } break; } // The side-effects of fix_lifetime effect all references to the same // object. It must be preserved to keep alive any potentially aliasing // references. fix_lifetime can only be removed by proving that its // operand is both a unique and a dead reference, but this makes more // sense in DeadObjectElimination. case SILInstructionKind::FixLifetimeInst: { // If the operand is a trivial scalar value, then it has no aliases or // side-effects. Consider handling this as an instruction // canonicalization instead. SILValue Op = I.getOperand(0); if (!Op->getType().isAddress() && Op->getType().isTrivial(*F)) { addReverseDependency(Op, &I); } else { markInstructionLive(&I); } break; } case SILInstructionKind::EndAccessInst: { // An end_access is live only if it's begin_access is also live. auto *beginAccess = cast(&I)->getBeginAccess(); addReverseDependency(beginAccess, &I); break; } case SILInstructionKind::DestroyValueInst: { auto phi = PhiValue(I.getOperand(0)); // Disable DCE of phis which are lexical or may have a pointer escape. if (phi && (phi->isLexical() || findPointerEscape(phi))) { markInstructionLive(&I); } // The instruction is live only if it's operand value is also live addReverseDependency(I.getOperand(0), &I); break; } case SILInstructionKind::EndBorrowInst: { auto phi = PhiValue(lookThroughBorrowedFromDef(I.getOperand(0))); // If there is a pointer escape or phi is lexical, disable DCE. if (phi && (findPointerEscape(phi) || phi->isLexical())) { markInstructionLive(&I); } // The instruction is live only if it's operand value is also live addReverseDependency(I.getOperand(0), &I); break; } case SILInstructionKind::BorrowedFromInst: { addReverseDependency(I.getOperand(0), &I); break; } case SILInstructionKind::EndLifetimeInst: { if (I.getOperand(0)->getType().isAddress()) { // DCE cannot reason about values in memory. markInstructionLive(&I); break; } // The instruction is live only if it's operand value is also live addReverseDependency(I.getOperand(0), &I); break; } case SILInstructionKind::BeginBorrowInst: { auto *borrowInst = cast(&I); processBorrow(BorrowedValue(borrowInst)); break; } case SILInstructionKind::LoadBorrowInst: { auto *loadBorrowInst = cast(&I); processBorrow(BorrowedValue(loadBorrowInst)); break; } default: if (seemsUseful(&I)) markInstructionLive(&I); } } } // Now propagate liveness backwards from each instruction in our // worklist, adding new instructions to the worklist as we discover // more that we need to keep. while (!Worklist.empty()) { auto *I = Worklist.pop_back_val(); propagateLiveness(I); } } // Records a reverse dependency if needed. See DCE::ReverseDependencies. void DCE::addReverseDependency(SILValue from, SILInstruction *to) { LLVM_DEBUG(llvm::dbgs() << "Adding reverse dependency from " << from << " to " << *to); ReverseDependencies[from].insert(to); } void DCE::findGuaranteedPhiDependencies(BorrowedValue borrow) { assert(borrow.kind == BorrowedValueKind::BeginBorrow || borrow.kind == BorrowedValueKind::LoadBorrow); LLVM_DEBUG(llvm::dbgs() << "Finding @guaranteed phi dependencies of " << borrow << "\n"); auto visitDependentPhiBaseValuePair = [&](SILPhiArgument *phiArg, SILValue baseValue) { guaranteedPhiDependencies[phiArg].insert(baseValue); }; // Find all dependencies starting from \p borrowInst and populate // them in guaranteedPhiDependencies if (borrow.kind == BorrowedValueKind::BeginBorrow) { visitExtendedReborrowPhiBaseValuePairs(cast(borrow.value), visitDependentPhiBaseValuePair); } visitExtendedGuaranteedForwardingPhiBaseValuePairs( borrow, visitDependentPhiBaseValuePair); } // Mark as live the terminator argument at index ArgIndex in Pred that // targets Succ. void DCE::markTerminatorArgsLive(SILBasicBlock *Pred, SILBasicBlock *Succ, size_t ArgIndex) { auto *Term = Pred->getTerminator(); // If the arguments are live, we need to keep the terminator that // delivers those arguments. markInstructionLive(Term); switch (Term->getTermKind()) { case TermKind::ReturnInst: case TermKind::ThrowInst: case TermKind::ThrowAddrInst: case TermKind::UnwindInst: case TermKind::YieldInst: case TermKind::UnreachableInst: case TermKind::SwitchValueInst: case TermKind::SwitchEnumAddrInst: case TermKind::CheckedCastAddrBranchInst: llvm_unreachable("Unexpected argument for terminator kind!"); break; case TermKind::DynamicMethodBranchInst: case TermKind::SwitchEnumInst: case TermKind::CheckedCastBranchInst: assert(ArgIndex == 0 && "Expected a single argument!"); // We do not need to do anything with these. If the resulting // argument is used at the destination these terminators will end // up live, and then our normal liveness propagation will mark the // single operand of these instructions as live. break; case TermKind::BranchInst: markValueLive(cast(Term)->getArg(ArgIndex)); break; case TermKind::CondBranchInst: { auto *CondBr = cast(Term); if (CondBr->getTrueBB() == Succ) { auto TrueArgs = CondBr->getTrueArgs(); markValueLive(TrueArgs[ArgIndex]); } if (CondBr->getFalseBB() == Succ) { auto FalseArgs = CondBr->getFalseArgs(); markValueLive(FalseArgs[ArgIndex]); } break; } case TermKind::AwaitAsyncContinuationInst: case TermKind::TryApplyInst: { assert(ArgIndex == 0 && "Expect a single argument!"); break; } } } // Propagate liveness back from Arg to the terminator arguments that // supply its value. void DCE::propagateLiveBlockArgument(SILArgument *Arg) { // Conceptually, the dependency from a debug instruction to its definition // is in reverse direction: Only if its definition (the Arg) is alive, also // the debug_value instruction is alive. for (Operand *DU : getDebugUses(Arg)) markInstructionLive(DU->getUser()); // Mark all reverse dependencies on the Arg live for (auto *depInst : ReverseDependencies.lookup(Arg)) { markInstructionLive(depInst); } if (auto *phi = dyn_cast(Arg)) { for (auto depVal : guaranteedPhiDependencies.lookup(phi)) { markValueLive(depVal); } } auto *Block = Arg->getParent(); auto ArgIndex = Arg->getIndex(); for (auto Pred : Block->getPredecessorBlocks()) markTerminatorArgsLive(Pred, Block, ArgIndex); } // Given an instruction which is considered live, propagate that liveness // back to the instructions that produce values it consumes. void DCE::propagateLiveness(SILInstruction *I) { markControllingTerminatorsLive(I->getParent()); if (!isa(I)) { for (auto &O : I->getAllOperands()) markValueLive(O.get()); // Conceptually, the dependency from a debug instruction to its definition // is in reverse direction: Only if its definition is alive, also the // debug_value instruction is alive. for (auto result : I->getResults()) for (Operand *DU : getDebugUses(result)) markInstructionLive(DU->getUser()); // Handle all other reverse-dependency instructions, like cond_fail, // fix_lifetime, destroy_value, etc. Only if the definition is alive, the // user itself is alive. for (auto res : I->getResults()) { for (auto *depInst : ReverseDependencies.lookup(res)) { markInstructionLive(depInst); } } return; } switch (cast(I)->getTermKind()) { case TermKind::BranchInst: case TermKind::UnreachableInst: case TermKind::UnwindInst: case TermKind::ThrowAddrInst: return; case TermKind::ReturnInst: case TermKind::ThrowInst: case TermKind::CondBranchInst: case TermKind::SwitchEnumInst: case TermKind::SwitchEnumAddrInst: case TermKind::DynamicMethodBranchInst: markValueLive(I->getOperand(0)); return; case TermKind::AwaitAsyncContinuationInst: case TermKind::CheckedCastBranchInst: case TermKind::CheckedCastAddrBranchInst: case TermKind::TryApplyInst: case TermKind::SwitchValueInst: case TermKind::YieldInst: for (auto &O : I->getAllOperands()) markValueLive(O.get()); return; } llvm_unreachable("corrupt instruction!"); } SILBasicBlock *DCE::nearestUsefulPostDominator(SILBasicBlock *Block) { // Find the nearest post-dominator that has useful instructions. auto *PostDomNode = PDT->getNode(Block)->getIDom(); while (PostDomNode && // In case the PostDomNode's block is null, it means it's not contained // in LiveBlocks. (!PostDomNode->getBlock() || !LiveBlocks.contains(PostDomNode->getBlock()))) PostDomNode = PostDomNode->getIDom(); if (PostDomNode) return PostDomNode->getBlock(); return nullptr; } // Replace the given conditional branching instruction with a plain // jump (aka unconditional branch) to the destination block. void DCE::replaceBranchWithJump(SILInstruction *Inst, SILBasicBlock *Block) { ++NumBranchesPromoted; assert(Block && "Expected a destination block!"); assert((isa(Inst) || isa(Inst) || isa(Inst) || isa(Inst) || isa(Inst) || isa(Inst)) && "Unexpected dead terminator kind!"); SILInstruction *Branch; if (!Block->args_empty()) { std::vector Args; auto E = Block->args_end(); for (auto A = Block->args_begin(); A != E; ++A) { assert(!LiveArguments.contains(*A) && "Unexpected live block argument!"); Args.push_back(SILUndef::get(*A)); } Branch = SILBuilderWithScope(Inst).createBranch(Inst->getLoc(), Block, Args); } else { Branch = SILBuilderWithScope(Inst).createBranch(Inst->getLoc(), Block); } LLVM_DEBUG(llvm::dbgs() << "Inserted unconditional branch:\n"); LLVM_DEBUG(Branch->dump()); (void)Branch; } void DCE::endLifetimeOfLiveValue(Operand *op, SILInstruction *insertPt) { auto value = op->get(); if (SILInstruction *inst = value->getDefiningInstruction()) { if (!LiveInstructions.contains(inst)) return; } else if (auto *arg = dyn_cast(value)) { if (!LiveArguments.contains(arg)) return; } assert(op->isLifetimeEnding()); // If DCE is going to delete the block in which we have to insert a // compensating lifetime end, let complete lifetimes utility handle it. if (!LiveBlocks.contains(insertPt->getParent())) { valuesToComplete.insert(lookThroughBorrowedFromDef(value)); return; } SILBuilderWithScope builder(insertPt); if (value->getOwnershipKind() == OwnershipKind::Owned) { auto *destroy = builder.createDestroyValue( RegularLocation::getAutoGeneratedLocation(), value); markInstructionLive(destroy); } if (value->getOwnershipKind() == OwnershipKind::Guaranteed) { auto *endBorrow = builder.createEndBorrow( RegularLocation::getAutoGeneratedLocation(), value); markInstructionLive(endBorrow); } } // Remove the instructions that are not potentially useful. bool DCE::removeDead() { bool Changed = false; for (auto &BB : *F) { for (unsigned i = 0; i < BB.getArguments().size();) { auto *arg = BB.getArgument(i); if (LiveArguments.contains(arg)) { i++; continue; } LLVM_DEBUG(llvm::dbgs() << "Removing dead argument:\n"); LLVM_DEBUG(arg->dump()); arg->replaceAllUsesWithUndef(); if (!F->hasOwnership() || arg->getOwnershipKind() == OwnershipKind::None) { i++; Changed = true; BranchesChanged = true; continue; } if (!arg->isPhi()) { // We cannot delete a non phi. If it was @owned, insert a // destroy_value, because its consuming user has already been marked // dead and will be deleted. // We do not have to end lifetime of a @guaranteed non phi arg. if (arg->getOwnershipKind() == OwnershipKind::Owned) { auto loc = RegularLocation::getAutoGeneratedLocation(); // insertPt is non-null because Undef is non-owned. auto insertPt = getInsertAfterPoint(arg).value(); SILBuilderWithScope builder(insertPt); auto *destroy = builder.createDestroyValue(loc, arg); LiveInstructions.insert(destroy); } i++; Changed = true; BranchesChanged = true; continue; } auto *phiArg = cast(arg); // In OSSA, we have to delete a dead phi argument and insert destroy or // end_borrow at its predecessors if the incoming values are live. // This is not necessary in non-OSSA, and will infact be incorrect. // Because, passing a value as a phi argument does not imply end of // lifetime in non-OSSA. for (auto *pred : BB.getPredecessorBlocks()) { auto *predTerm = pred->getTerminator(); SILInstruction *insertPt = predTerm; if (phiArg->isReborrow()) { // If the phiArg is dead and had reborrow dependencies, its baseValue // may also have been dead and a destroy_value of its baseValue may // have been inserted before the pred's terminator. Make sure to // adjust the insertPt before any destroy_value. // // FIXME: This code currently can reorder destroys, e.g., when the // block already contains a destroy_value just before the // terminator. Fix this by making note of the added // destroy_value insts and only moving the insertion point // before those that are newly added. for (SILInstruction &predInst : llvm::reverse(*pred)) { if (&predInst == predTerm) continue; if (!isa(&predInst)) { break; } insertPt = &predInst; } } auto *predOp = phiArg->getIncomingPhiOperand(pred); if (predOp->isLifetimeEnding()) { endLifetimeOfLiveValue(predOp, insertPt); } } erasePhiArgument( &BB, i, /*cleanupDeadPhiOps=*/true, InstModCallbacks() .onCreateNewInst([&](auto *inst) { markInstructionLive(inst); }) .onDelete([&](auto *inst) { for (auto result : inst->getResults()) { result = lookThroughBorrowedFromDef(result); if (valuesToComplete.count(result)) { valuesToComplete.erase(result); } } inst->replaceAllUsesOfAllResultsWithUndef(); if (isa(inst)) CallsChanged = true; ++NumDeletedInsts; inst->eraseFromParent(); })); Changed = true; BranchesChanged = true; } for (auto I = BB.begin(), E = BB.end(); I != E; ) { auto *Inst = &*I; ++I; if (LiveInstructions.contains(Inst) || isa(Inst)) continue; // We want to replace dead terminators with unconditional branches to // the nearest post-dominator that has useful instructions. if (auto *termInst = dyn_cast(Inst)) { SILBasicBlock *postDom = nearestUsefulPostDominator(termInst->getParent()); if (!postDom) continue; for (auto &op : termInst->getAllOperands()) { if (op.isLifetimeEnding()) { endLifetimeOfLiveValue(&op, termInst); } } LLVM_DEBUG(llvm::dbgs() << "Replacing branch: "); LLVM_DEBUG(termInst->dump()); LLVM_DEBUG(llvm::dbgs() << "with jump to: BB" << postDom->getDebugID() << "\n"); replaceBranchWithJump(termInst, postDom); ++NumDeletedInsts; termInst->eraseFromParent(); BranchesChanged = true; Changed = true; continue; } ++NumDeletedInsts; LLVM_DEBUG(llvm::dbgs() << "Removing dead instruction:\n"); LLVM_DEBUG(Inst->dump()); if (F->hasOwnership()) { for (auto &Op : Inst->getAllOperands()) { if (Op.isLifetimeEnding()) { endLifetimeOfLiveValue(&Op, Inst); } } } for (auto result : Inst->getResults()) { result = lookThroughBorrowedFromDef(result); if (valuesToComplete.count(result)) { valuesToComplete.erase(result); } } Inst->replaceAllUsesOfAllResultsWithUndef(); if (isa(Inst)) CallsChanged = true; Inst->eraseFromParent(); Changed = true; } } OSSACompleteLifetime completion(F, DT, *deadEndBlocks); for (auto value : valuesToComplete) { if (!value.has_value()) continue; completion.completeOSSALifetime(*value, OSSACompleteLifetime::Boundary::Liveness); } return Changed; } // Precompute some information from the post-dominator tree to aid us // in determining control dependence without generating a complete // control dependence graph. Inspired by: // Optimal control dependence and the Roman chariots problem // TOPLAS, v19, issue 3, 1997 // http://dx.doi.org/10.1145/256167.256217 // // For each node in the post-dominator tree we will compute: // -- A level number. // // -- The list of immediate predecessors that this block is // control-dependent on along with the level number in the // post-dominator tree of each of those predecessors. // // -- The lowest level number of any predecessor below the given node // in the post-dominator tree. This will be used to exit early in // later control-dependence queries. // // Returns true upon success, false if nodes that are not present in the // post-dominator tree are detected. bool DCE::precomputeControlInfo() { computeLevelNumbers(PDT->getRootNode()); if (hasInfiniteLoops()) return false; computePredecessorDependence(); computeMinPredecessorLevels(PDT->getRootNode()); return true; } void DCE::insertControllingInfo(SILBasicBlock *Block, unsigned Level) { assert(ControllingInfoMap.find(Block) == ControllingInfoMap.end() && "Unexpected map entry for node!"); ControllingInfo Info; Info.Block = Block; Info.Level = Level; Info.MinTreePredLevel = -1; ControllingInfoMap[Block] = Info; } // Assign a level number to each node in the post-dominator tree. void DCE::computeLevelNumbers(PostDomTreeNode *root) { llvm::SmallVector, 32> workList; workList.push_back({root, 0}); while (!workList.empty()) { auto entry = workList.pop_back_val(); PostDomTreeNode *node = entry.first; unsigned level = entry.second; insertControllingInfo(node->getBlock(), level); for (PostDomTreeNode *child : *node) { workList.push_back({child, level + 1}); } } } // Structurally infinite loops like: // bb1: // br bb1 // are not present in the post-dominator tree. Their presence // requires significant modifications to the way the rest of the // algorithm works. They should be rare, so for now we'll do the most // conservative thing and completely bail out, doing no dead code // elimination. Note this will also hit for unreachable code, but // presumably we'll run DCE at some point after removing unreachable // code. bool DCE::hasInfiniteLoops() { for (auto &BB : *F) if (ControllingInfoMap.find(&BB) == ControllingInfoMap.end()) return true; return false; } // For each block, create a list of the direct predecessors that the // block is control-dependent on. With each predecessor, also keep the // level number of the predecessor in the post-dominator tree. void DCE::computePredecessorDependence() { for (auto &BB : *F) { assert(ControllingInfoMap.find(&BB) != ControllingInfoMap.end() && "Expected to already have a map entry for block!"); for (auto Pred : BB.getPredecessorBlocks()) if (!PDT->properlyDominates(&BB, Pred)) { assert(ControllingInfoMap.find(Pred) != ControllingInfoMap.end() && "Expected to already have a map entry for block!"); auto PredLevel = ControllingInfoMap[Pred].Level; auto PredInfo = std::make_pair(Pred, PredLevel); auto &MapElement = ControllingInfoMap[&BB]; MapElement.ControllingPreds.push_back(PredInfo); } } } // Assign the minimum post-dominator tree level to each node in the tree. void DCE::computeMinPredecessorLevels(PostDomTreeNode *root) { llvm::SmallVector postDomOrder; postDomOrder.reserve(ControllingInfoMap.size()); postDomOrder.push_back(root); for (unsigned idx = 0; idx < postDomOrder.size(); ++idx) { PostDomTreeNode *node = postDomOrder[idx]; for (PostDomTreeNode *child : *node) { postDomOrder.push_back(child); } } for (PostDomTreeNode *node : llvm::reverse(postDomOrder)) { SILBasicBlock *block = node->getBlock(); assert(ControllingInfoMap.find(block) != ControllingInfoMap.end() && "Expected to have map entry for node!"); ControllingInfo &nodeInfo = ControllingInfoMap[block]; for (auto &pred : nodeInfo.ControllingPreds) { nodeInfo.MinTreePredLevel = std::min(nodeInfo.MinTreePredLevel, pred.second); } if (PostDomTreeNode *parentNode = node->getIDom()) { ControllingInfo &parentInfo = ControllingInfoMap[parentNode->getBlock()]; parentInfo.MinTreePredLevel = std::min(parentInfo.MinTreePredLevel, nodeInfo.MinTreePredLevel); } } } void DCE::collectControllingBlocksInTree(ControllingInfo &QueryInfo, PostDomTreeNode *root, llvm::SmallPtrSetImpl &Controlling) { llvm::SmallVector workList; workList.push_back(root); while (!workList.empty()) { PostDomTreeNode *node = workList.pop_back_val(); SILBasicBlock *block = node->getBlock(); assert(ControllingInfoMap.find(block) != ControllingInfoMap.end() && "Expected to have map entry for node!"); auto &nodeInfo = ControllingInfoMap[block]; if (nodeInfo.MinTreePredLevel > QueryInfo.Level) continue; for (auto &PredInfo : nodeInfo.ControllingPreds) { if (PredInfo.second <= QueryInfo.Level) { assert(PDT->properlyDominates( PDT->getNode(PredInfo.first)->getIDom()->getBlock(), QueryInfo.Block) && "Expected predecessor's post-dominator to post-dominate node."); Controlling.insert(PredInfo.first); } } for (PostDomTreeNode *child : *node) { workList.push_back(child); } } } // Walk the post-dominator tree from the query block down, building // the set of blocks that the given block is control-dependent on. To // determine control dependence we use some precomputed information // about the direct predecessors that control each block, along with // the level numbers in the post-dominator tree of those controlling // predecessors. We can use the latter to terminate the walk down the // dominator tree early. void DCE::collectControllingBlocks(SILBasicBlock *Block, llvm::SmallPtrSetImpl &Controlling) { // First add the blocks that QueryNode is directly control-dependent on. assert(ControllingInfoMap.find(Block) != ControllingInfoMap.end() && "Expected map entry for node!"); auto &MapEntry = ControllingInfoMap[Block]; // Now walk the children looking for nodes that have controlling // predecessors that have the same or lower level number in the // post-dominator tree. collectControllingBlocksInTree(MapEntry, PDT->getNode(Block), Controlling); } void DCE::markControllingTerminatorsLive(SILBasicBlock *Block) { if (LiveBlocks.contains(Block)) return; LiveBlocks.insert(Block); llvm::SmallPtrSet ControllingBlocks; collectControllingBlocks(Block, ControllingBlocks); for (auto BB : ControllingBlocks) markInstructionLive(BB->getTerminator()); } class DCEPass : public SILFunctionTransform { public: /// The entry point to the transformation. void run() override { SILFunction *F = getFunction(); LLVM_DEBUG(llvm::dbgs() << "*** DCE on function: " << F->getName() << " ***\n"); auto *PDA = PM->getAnalysis(); PostDominanceInfo *PDT = PDA->get(F); auto *DA = PM->getAnalysis(); auto *DEA = getAnalysis(); // If we have a function that consists of nothing but a // structurally infinite loop like: // while true {} // we'll have an empty post dominator tree. if (!PDT->getRootNode()) return; DCE dce(F, PDT, DA->get(F), DEA->get(F)); if (dce.run()) { using InvalidationKind = SILAnalysis::InvalidationKind; unsigned Inv = InvalidationKind::Instructions; if (dce.mustInvalidateCalls()) Inv |= (unsigned)InvalidationKind::Calls; if (dce.mustInvalidateBranches()) { removeUnreachableBlocks(*F); Inv |= (unsigned)InvalidationKind::Branches; } invalidateAnalysis(SILAnalysis::InvalidationKind(Inv)); } } }; } // end anonymous namespace SILTransform *swift::createDCE() { return new DCEPass(); }