Index: include/llvm/InitializePasses.h =================================================================== --- include/llvm/InitializePasses.h +++ include/llvm/InitializePasses.h @@ -63,6 +63,7 @@ void initializeTarget(PassRegistry&); void initializeAAEvalLegacyPassPass(PassRegistry&); +void initializeACDCELegacyPassPass(PassRegistry&); void initializeAddDiscriminatorsPass(PassRegistry&); void initializeADCELegacyPassPass(PassRegistry&); void initializeBDCELegacyPassPass(PassRegistry &); Index: include/llvm/LinkAllPasses.h =================================================================== --- include/llvm/LinkAllPasses.h +++ include/llvm/LinkAllPasses.h @@ -60,6 +60,7 @@ return; (void) llvm::createAAEvalPass(); + (void) llvm::createAggressiveControlDCEPass(); (void) llvm::createAggressiveDCEPass(); (void) llvm::createBitTrackingDCEPass(); (void) llvm::createArgumentPromotionPass(); Index: include/llvm/Transforms/Scalar.h =================================================================== --- include/llvm/Transforms/Scalar.h +++ include/llvm/Transforms/Scalar.h @@ -93,6 +93,15 @@ //===----------------------------------------------------------------------===// // +// AggressiveControlDCE - This pass uses the SSA based Aggressive DCE algorithm. +// This algorithm assumes instructions are dead until proven otherwise, which +// makes it more successful at removing non-obviously dead instructions. This +// variant does not treat branches as automatically live +// +FunctionPass *createAggressiveControlDCEPass(); + +//===----------------------------------------------------------------------===// +// // BitTrackingDCE - This pass uses a bit-tracking DCE algorithm in order to // remove computations of dead bits. // Index: include/llvm/Transforms/Scalar/ACDCE.h =================================================================== --- /dev/null +++ include/llvm/Transforms/Scalar/ACDCE.h @@ -0,0 +1,39 @@ +//===- ACDCE.h - Aggressive control dead code elimination -----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file provides the interface for the Aggressive Control Dead Code +// Elimination pass. This pass optimistically assumes that all instructions are +// dead until proven otherwise, allowing it to eliminate dead computations that +// other DCE passes do not catch, particularly involving loop computations. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_TRANSFORMS_SCALAR_ACDCE_H +#define LLVM_TRANSFORMS_SCALAR_ACDCE_H + +#include "llvm/IR/Function.h" +#include "llvm/IR/PassManager.h" + +namespace llvm { + +/// A DCE pass that assumes instructions including branches are dead until +/// proven otherwise. +/// +/// This pass eliminates dead code by optimistically assuming that all +/// instructions are dead until proven otherwise. This allows it to eliminate +/// dead computations that other DCE passes do not catch, particularly involving +/// loop computations. This pass augments the predecessor "Aggressive DCE" by +/// not treating all branch operations as automatically live and by altering the +/// control flow graph remove dead branching. +struct ACDCEPass : PassInfoMixin { + PreservedAnalyses run(Function &F, AnalysisManager &AM); +}; +} + +#endif // LLVM_TRANSFORMS_SCALAR_ACDCE_H Index: lib/Passes/PassBuilder.cpp =================================================================== --- lib/Passes/PassBuilder.cpp +++ lib/Passes/PassBuilder.cpp @@ -65,6 +65,7 @@ #include "llvm/Transforms/InstrProfiling.h" #include "llvm/Transforms/PGOInstrumentation.h" #include "llvm/Transforms/SampleProfile.h" +#include "llvm/Transforms/Scalar/ACDCE.h" #include "llvm/Transforms/Scalar/ADCE.h" #include "llvm/Transforms/Scalar/BDCE.h" #include "llvm/Transforms/Scalar/DCE.h" Index: lib/Passes/PassRegistry.def =================================================================== --- lib/Passes/PassRegistry.def +++ lib/Passes/PassRegistry.def @@ -113,6 +113,7 @@ #ifndef FUNCTION_PASS #define FUNCTION_PASS(NAME, CREATE_PASS) #endif +FUNCTION_PASS("acdce", ACDCEPass()) FUNCTION_PASS("aa-eval", AAEvaluator()) FUNCTION_PASS("adce", ADCEPass()) FUNCTION_PASS("bdce", BDCEPass()) Index: lib/Transforms/IPO/PassManagerBuilder.cpp =================================================================== --- lib/Transforms/IPO/PassManagerBuilder.cpp +++ lib/Transforms/IPO/PassManagerBuilder.cpp @@ -307,7 +307,11 @@ if (LoadCombine) MPM.add(createLoadCombinePass()); - MPM.add(createAggressiveDCEPass()); // Delete dead instructions + if (OptLevel > 2) + MPM.add(createAggressiveControlDCEPass()); // Delete dead instructions + else + MPM.add(createAggressiveDCEPass()); // Delete dead instructions + MPM.add(createCFGSimplificationPass()); // Merge & remove BBs // Clean up after everything. addInstructionCombiningPass(MPM); Index: lib/Transforms/Scalar/ACDCE.cpp =================================================================== --- /dev/null +++ lib/Transforms/Scalar/ACDCE.cpp @@ -0,0 +1,1084 @@ +//===- ACDCE.cpp - Code to perform dead code elimination ------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements the Aggressive Dead Code Elimination pass. This pass +// optimistically assumes that all instructions are dead until proven otherwise, +// allowing it to eliminate dead computations that other DCE passes do not +// catch by not assuming that control operations are live. Under option, it will +// also remove may-be-infinite loops (legal in C++, not generally in Java) +// +//===----------------------------------------------------------------------===// + +#include "llvm/Transforms/Scalar/ACDCE.h" + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/PostOrderIterator.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/Statistic.h" +#include "llvm/Analysis/CFGPrinter.h" +#include "llvm/Analysis/GlobalsModRef.h" +#include "llvm/Analysis/IteratedDominanceFrontier.h" +#include "llvm/Analysis/PostDominators.h" +#include "llvm/IR/BasicBlock.h" +#include "llvm/IR/CFG.h" +#include "llvm/IR/DebugInfoMetadata.h" +#include "llvm/IR/InstIterator.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/IntrinsicInst.h" +#include "llvm/Pass.h" +#include "llvm/ProfileData/InstrProf.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Transforms/Scalar.h" + +using namespace llvm; + +#define DEBUG_TYPE "acdce" +#define DEBUG_MSG(x) DEBUG(dbgs() << x << '\n';) + +static cl::opt RemoveLoops("acdce-remove-loops", cl::init(false), + cl::Hidden); + +STATISTIC(NumRemoved, "Number of non-branch instructions removed"); +STATISTIC(NumBranchesRemoved, "Number of branch instructions removed"); + +namespace llvm { + +// Specialize po_iterator_storage to identify loop bottoms +// which we define simply as back edges in a depth first +// traversal of the CFG starting at the entry. +class PO_WithLoops; + +template <> class po_iterator_storage { + PO_WithLoops &Storage; + +public: + po_iterator_storage(PO_WithLoops &Storage) : Storage(Storage) {} + // These functions are defined below. + bool insertEdge(BasicBlock *From, BasicBlock *To); + void finishPostorder(BasicBlock *BB); +}; + +class PO_WithLoops { + typedef SmallPtrSet LoopBottomsType; + typedef po_iterator POTIterator; + + Function &F; + // Visited[BB] is true while the basic block *BB is on + // the stack of being visited nodes and false after + // all children have been visited + DenseMap Visited; + LoopBottomsType LoopBottoms; + +public: + PO_WithLoops(Function &F) : F(F) { Visited.grow(2 * F.size()); } + POTIterator begin() { return po_ext_begin(&F.getEntryBlock(), *this); } + POTIterator end() { return po_ext_end(&F.getEntryBlock(), *this); } + bool insertEdge(BasicBlock *From, BasicBlock *To) { + auto P = Visited.insert(std::make_pair(To, true)); + if (P.second) { + return true; + } + if (P.first->second) { + // back edge to an ancestor in DFS + LoopBottoms.insert(From); + } + return false; + } + + void finishPostorder(BasicBlock *BB) { Visited[BB] = false; } + bool reachable(BasicBlock *BB) const { + return (Visited.find(BB) != Visited.end()); + } + const LoopBottomsType &getLoopBottoms() { return LoopBottoms; } +}; + +bool po_iterator_storage::insertEdge(BasicBlock *From, + BasicBlock *To) { + return Storage.insertEdge(From, To); +} +void po_iterator_storage::finishPostorder(BasicBlock *BB) { + Storage.finishPostorder(BB); +} +} + +namespace { + +bool isUnconditional(TerminatorInst *Term) { + auto BR = dyn_cast(Term); + return BR && BR->isUnconditional(); +} + +struct BlockInfoType; +/// A Dead Region is a set of dead basic blocks with a common +/// post-dominating live block +struct DeadRegionInfo { + /// true when all blocks have unconditional branches + bool Trivial = true; // dead blocks are just unconditional branches + /// the set of dead blocks in this region + SmallVector DeadBlocks; + /// sources of edges from live nodes into dead ones of this region + SmallPtrSet LivePreds; +}; + +/// Information about Instruction's +struct InstInfoType { + + bool Live = false; ///< true if this unstruction is live + + /// quick access to information for block containing associated Instruction + struct BlockInfoType *Block = nullptr; +}; + +/// Information about basic blocks relevant to dead code elimination +struct BlockInfoType { + /// true when this block contains a live instructions + bool Live = false; + /// this block ends in an unconditional branch + bool UnconditionalBranch = false; + + /// quick access to the LiveInfo for the terminator + InstInfoType *TerminatorLiveInfo = nullptr; // == & InstInfo[Terminator] + + bool terminatorIsLive() const { return TerminatorLiveInfo->Live; } + + /// corresponding BasicBlock + BasicBlock *BB = nullptr; + + /// points to this BB when this block is live, otherwise the most immediate + /// live post-dominator of BB + BasicBlock *LivePostDom = nullptr; + + TerminatorInst *Terminator = nullptr; ///< cache of BB->getTerminator() + + /// block found to not reach a function return point + bool isUnreachable() const { return Terminator == nullptr; } + + /// the collection of dead blocks which have this block as + /// the most-immediate live post-dominator + DeadRegionInfo DeadRegion; + + /// used to control exploration of dead connected components + unsigned RegionVisited = 0; +}; + +/// This class implements analysis to determine instructions including branching +/// which does not effect the output. Such operations are removed as are +/// unreachable operations for some scenarions. Dead loops are optionally +/// removed +/// or retained. +class AggressiveControlDCE { + Function &F; + + /// reverse partial order of blocks with a collection of sources of backward + /// edges referred to as loop bottoms + PO_WithLoops PO; + PostDominatorTree &PDT; + bool GraphWasModified = false; + + /// function-level statistics + unsigned FuncNumBranchesRemoved = 0; + unsigned FuncNumInstRemoved = 0; + + /// one per basic block in the function + std::vector BlockInfoVec; + + /// mapping of blocks to associated information, element in BlockInfoVec + DenseMap BlockInfo; + bool isLive(BasicBlock *BB) { return BlockInfo[BB]->Live; } + + /// mapping of instructions to associated information + DenseMap InstInfo; + bool isLive(Instruction *I) { return InstInfo[I].Live; } + + /// initialize maps and BlockInfoVec + void initialize(); + + /// live instructions to be processed or dead instructions to be deleted + SmallVector Worklist; + SmallPtrSet AliveScopes; + + /// Propagate "live-ness" to definitions which reach "live" instructions + void markLive(); + /// Mark I and its containing block as live. + void markLive(Instruction *I); + void collectLiveScopes(const DILocalScope &LS); + void collectLiveScopes(const DILocation &DL); + + /// true for any instructions which is always treated as live + bool alwaysLive(Instruction &I) const; + + /// vector of blocks with not known to be live terminators, + SmallPtrSet BlocksWithDeadTerminators; + + /// The set of blocks which we have determined are live in the + /// most recent pass + SmallPtrSet NewLiveBlocks; + + /// Analyze basic blocks to find to loop bottoms to keep live and + /// remove unreachable blocks + void analyzeBlocks(); + + /// blocks found to be unreachable when searching for back edges + SmallVector UnreachableBlocks; + void removeUnreachable(BasicBlock *); + + /// Analyze dead branches to find those whose branches are the sources + /// of control dependences impacting a live block. Those branches are + /// marked live. + void checkControlDependences(); + + /// look for phi nodes with complex value which force + /// source blocks of reaching values to be treated as live. + void checkPhiNodes(); + + typedef SmallPtrSet RegionSetType; + + /// Traverse the connected component associated with BBInfo where + /// nodes whose RegionVisited field is greater than FirstVisit are considered + /// already 'visited' and we stop at tops of live blocks. + /// Set RegionVisited for blocks newly visited to CurrentVisit. + /// Record live blocks that are successors of DeadBlocks on this + /// componente in LivePdoms + void visitRegion(BlockInfoType *, unsigned FirstVisit, unsigned CurrentVisit, + RegionSetType *LivePdoms); + + /// Incremented for each new traversal of connected components + unsigned LastCurrentVisit = 0; + + /// Verify that all edges from "dead" predecessors into PN + /// have the same associated value. To refine the precision, the + /// dead blocks are partitioned into connnected components and + /// all definitions associated with predecessor blocks in the same + /// connected component must be the same + void checkPhiNode(PHINode *PN, unsigned FirstVisit); + + /// find groups of dead blocks with common live post-dominator + /// and rewrite the control flow graph + void processDeadRegions(); + + /// Rewrite non-trivial dead regions by redirection incoming live + /// control flow to the post-dominator and updating phi-nnodes in the + /// post-dominator + void updateDeadRegion(BasicBlock *BB, DeadRegionInfo *Info); + + /// adjust BB so it has an unconditional branch to Target + /// mark that branch live. + void makeUnconditional(BasicBlock *BB, BasicBlock *Target); + + /// the collection of unconditional branch instructions created + /// by makeUnconditional + SmallVector NewBranches; + + /// for trivial regions, we just mark all the branches as live to be + /// cleaned up by other phases + void cleanTrivialRegion(const DeadRegionInfo &Info); + + void removeDeadCode(); + +#ifndef NDEBUG + /// the set of live blocks with dead predecessors + SmallPtrSet BlocksForPhiValidation; + + /// verify one value for each predecessor after dead blocks are removed + void validatePhiNodes(BasicBlock *); +#endif + +public: + AggressiveControlDCE(Function &F, PostDominatorTree &PDT) + : F(F), PO(F), PDT(PDT) {} + bool doAggressiveCDCE(); +}; +} + +bool AggressiveControlDCE::doAggressiveCDCE() { + + initialize(); + markLive(); + processDeadRegions(); + removeDeadCode(); + + return GraphWasModified; +} + +//===----------------------------------------------------------------------===// +// +// +// Initialize of data structures and identification of +// loops and unreachable code +// +//===----------------------------------------------------------------------===// +void AggressiveControlDCE::initialize() { + + DEBUG(PDT.print(dbgs())); + + auto NumBlocks = F.size(); + BlockInfoVec.resize(NumBlocks); + BlockInfo.grow(2 * NumBlocks); + auto InfoP = BlockInfoVec.begin(); + size_t NumInsts = 0; + for (auto &BB : F) { + NumInsts += BB.size(); + auto &Info = *InfoP; + BlockInfo[&BB] = &Info; + DEBUG_MSG("initializing block " << BB.getName()); + Info.BB = &BB; + Info.Terminator = BB.getTerminator(); + Info.UnconditionalBranch = isUnconditional(Info.Terminator); + ++InfoP; + } + if (!RemoveLoops) { + // remove unreachable code, identify loop bottoms + // to be forced live below. + analyzeBlocks(); + } + InstInfo.grow(2 * NumInsts); + for (auto &BBInfo : BlockInfoVec) { + if (BBInfo.isUnreachable()) { // removed unreachable node + continue; + } + for (Instruction &I : *BBInfo.BB) { + auto &IInfo = InstInfo[&I]; + IInfo.Block = &BBInfo; + if (alwaysLive(I)) { + markLive(&I); + } + } + } + + // For blocks which do not reach a function return, mark + // them live. Also branches where some reach the return + // and some done as live. + for (auto &BBInfo : BlockInfoVec) { + if (!BBInfo.isUnreachable() && !PDT.getNode(BBInfo.BB)) { + markLive(BBInfo.Terminator); + for (auto Pred : predecessors(BBInfo.BB)) { + DEBUG_MSG("no pdom pred live: " << Pred->getName()); + markLive(Pred->getTerminator()); + } + } + } + + // mark all loop bottom terminators as live so loops are not + // deleted (in case they are infinite) + for (auto BB : PO.getLoopBottoms()) { + DEBUG_MSG("loop bottom " << BB->getName()); + markLive(BB->getTerminator()); + } + + for (auto &BBInfo : BlockInfoVec) { + BBInfo.TerminatorLiveInfo = &InstInfo[BBInfo.Terminator]; + if (!BBInfo.TerminatorLiveInfo->Live) { + BlocksWithDeadTerminators.insert(BBInfo.BB); + } + } + + // Treat the entry block as always live + auto BB = &F.getEntryBlock(); + auto &EntryInfo = *BlockInfo[BB]; + EntryInfo.Live = true; + EntryInfo.LivePostDom = BB; + if (EntryInfo.UnconditionalBranch) { + EntryInfo.TerminatorLiveInfo->Live = true; + BlocksWithDeadTerminators.erase(BB); + } +} + +// Find and remove unreachable code and compute the bottoms of loops +// which will be forced live so loops are retained. +void AggressiveControlDCE::analyzeBlocks() { + + // This loop drives a Post Order traversal of the + // control flow graph which will determine the set + // of blocks reachable from the entry and also mark + // those which are the sources of back edges (loop bottoms) + for (auto BB : PO) { + (void)BB; + } + + for (auto &BB : F) { + if (PO.reachable(&BB)) { + continue; + } + DEBUG_MSG("unreachable " << BB.getName()); + removeUnreachable(&BB); + UnreachableBlocks.push_back(&BB); + } + if (Worklist.empty()) { + return; + } + GraphWasModified = true; + for (auto I : Worklist) { + I->eraseFromParent(); + } + Worklist.clear(); +} + +// since we go to the trouble of finding unreachable blocks +// might as well remove them. +void AggressiveControlDCE::removeUnreachable(BasicBlock *BB) { + auto &Info = *BlockInfo[BB]; + Info.Live = true; + Info.Terminator = nullptr; + + while (PHINode *PN = dyn_cast(BB->begin())) { + PN->replaceAllUsesWith(Constant::getNullValue(PN->getType())); + BB->getInstList().pop_front(); + } + for (auto Succ : successors(BB)) { + Succ->removePredecessor(BB); + } + BB->dropAllReferences(); + for (Instruction &I : *BB) { + Worklist.push_back(&I); + } +} + +//===----------------------------------------------------------------------===// +// +// Core algorithm to propagate liveness +// +//===----------------------------------------------------------------------===// +void AggressiveControlDCE::markLive() { + + DEBUG_MSG("propagate live\n"); + + // Propagate liveness backwards to operands. + do { + while (!Worklist.empty()) { + Instruction *Curr = Worklist.pop_back_val(); + DEBUG(dbgs() << "work live: "; Curr->dump();); + for (Use &OI : Curr->operands()) { + if (Instruction *Inst = dyn_cast(OI)) { + markLive(Inst); + } + } + } + checkControlDependences(); + if (Worklist.empty()) { + checkPhiNodes(); + } + } while (!Worklist.empty()); + + // Find blocks with dead terminators which are simply + // unconditional branches and mark them live to avoid + // trivial edits + SmallVector NowLiveBlocks; + for (auto BB : BlocksWithDeadTerminators) { + auto Info = BlockInfo[BB]; + if (!Info->Live && Info->UnconditionalBranch && + &Info->BB->front() == Info->Terminator) { + if (auto Pred = Info->BB->getSinglePredecessor()) { + if (isLive(Pred->getTerminator())) { + // this mostly avoids unnecessary work removing trival dead regions + DEBUG_MSG("unconditional from live pred " << Info->BB->getName()); + Info->Live = true; + Info->TerminatorLiveInfo->Live = true; + Info->LivePostDom = BB; + NowLiveBlocks.push_back(BB); + } + } + } + } + for (auto BB : NowLiveBlocks) { + BlocksWithDeadTerminators.erase(BB); + } +} + +// Check if this instruction is a runtime call for value profiling and +// if it's instrumenting a constant. +static bool isInstrumentsConstant(Instruction &I) { + if (CallInst *CI = dyn_cast(&I)) + if (Function *Callee = CI->getCalledFunction()) + if (Callee->getName().equals(getInstrProfValueProfFuncName())) + if (isa(CI->getArgOperand(0))) + return true; + return false; +} + +bool AggressiveControlDCE::alwaysLive(Instruction &I) const { + if (I.isEHPad() || I.mayHaveSideEffects()) { + if (isInstrumentsConstant(I)) { + return false; + } + return true; + } + if (!isa(I)) { + return false; + } + if (isa(I) || isa(I)) { + return false; + } + return true; +} + +void AggressiveControlDCE::markLive(Instruction *I) { + auto &Info = InstInfo[I]; + if (Info.Live) { + return; + } + DEBUG(dbgs() << "mark live: "; I->dump()); + Info.Live = true; + Worklist.push_back(I); + + // Collect the live debug info scopes attached to this instruction. + if (const DILocation *DL = I->getDebugLoc()) + collectLiveScopes(*DL); + + auto &BBInfo = *Info.Block; + if (BBInfo.Live) { + return; + } + DEBUG_MSG("mark block live: " << BBInfo.BB->getName()); + BBInfo.Live = true; + NewLiveBlocks.insert(BBInfo.BB); + + BBInfo.LivePostDom = BBInfo.BB; + if (!BBInfo.UnconditionalBranch) { + return; + } + BlocksWithDeadTerminators.erase(BBInfo.BB); + + if (!BBInfo.TerminatorLiveInfo) { // not set in first initialization pass + InstInfo[BBInfo.Terminator].Live = true; + } else { + BBInfo.TerminatorLiveInfo->Live = true; + } +} + +void AggressiveControlDCE::collectLiveScopes(const DILocalScope &LS) { + if (!AliveScopes.insert(&LS).second) + return; + + if (isa(LS)) + return; + + // Tail-recurse through the scope chain. + collectLiveScopes(cast(*LS.getScope())); +} + +void AggressiveControlDCE::collectLiveScopes(const DILocation &DL) { + // Even though DILocations are not scopes, shove them into AliveScopes so we + // don't revisit them. + if (!AliveScopes.insert(&DL).second) + return; + + // Collect live scopes from the scope chain. + collectLiveScopes(*DL.getScope()); + + // Tail-recurse through the inlined-at chain. + if (const DILocation *IA = DL.getInlinedAt()) + collectLiveScopes(*IA); +} + +// Use the IDFCalculator to find control dependences +// strting at newly discovered live blocks but stopping +// at blocks with live terminators. +void AggressiveControlDCE::checkControlDependences() { + + if (BlocksWithDeadTerminators.empty()) { + return; + } + + DEBUG({ + dbgs() << "new live blocks:\n"; + for (auto BB : NewLiveBlocks) { + dbgs() << "\t" << BB->getName() << '\n'; + } + dbgs() << "dead terminator blocks:\n"; + for (auto BB : BlocksWithDeadTerminators) { + dbgs() << "\t" << BB->getName() << '\n'; + } + }); + + SmallVector IDFBlocks; + ReverseIDFCalculator IDFs(PDT); + IDFs.setDefiningBlocks(NewLiveBlocks); + IDFs.setLiveInBlocks(BlocksWithDeadTerminators); + IDFs.calculate(IDFBlocks); + NewLiveBlocks.clear(); + + // Dead terminators which control live blocks become live + for (auto BB : IDFBlocks) { + DEBUG_MSG("live control in: " << BB->getName()); + markLive(BB->getTerminator()); + BlocksWithDeadTerminators.erase(BB); + } +} + +// There are scenarios in which a phi node is really short-hand +// for a copy operations on the edge and the semantics of the program +// rely on this. We look for situations where there are distinct values from +// blocks with dead terminators (which we assume are all replaced with +// a single edge) and if we find this case we mark some of those terminators +// as live. As a trivial example, the first branch should be marked as live +// to distinguish which constant value is selected by the phi. + +// br i1 %1, label %2, label %3 +// ;