Index: llvm/include/llvm/InitializePasses.h =================================================================== --- llvm/include/llvm/InitializePasses.h +++ llvm/include/llvm/InitializePasses.h @@ -124,6 +124,7 @@ void initializeDAEPass(PassRegistry&); void initializeDAHPass(PassRegistry&); void initializeDCELegacyPassPass(PassRegistry&); +void initializeDFAJumpThreadingLegacyPassPass(PassRegistry &); void initializeDSELegacyPassPass(PassRegistry&); void initializeDataFlowSanitizerLegacyPassPass(PassRegistry &); void initializeDeadMachineInstructionElimPass(PassRegistry&); Index: llvm/include/llvm/LinkAllPasses.h =================================================================== --- llvm/include/llvm/LinkAllPasses.h +++ llvm/include/llvm/LinkAllPasses.h @@ -174,6 +174,7 @@ (void) llvm::createStripDeadPrototypesPass(); (void) llvm::createTailCallEliminationPass(); (void) llvm::createJumpThreadingPass(); + (void) llvm::createDFAJumpThreadingPass(); (void) llvm::createUnifyFunctionExitNodesPass(); (void) llvm::createInstCountPass(); (void) llvm::createConstantHoistingPass(); Index: llvm/include/llvm/Transforms/Scalar.h =================================================================== --- llvm/include/llvm/Transforms/Scalar.h +++ llvm/include/llvm/Transforms/Scalar.h @@ -253,6 +253,14 @@ FunctionPass *createJumpThreadingPass(bool FreezeSelectCond = false, int Threshold = -1); +//===----------------------------------------------------------------------===// +// +// DFAJumpThreading - When a switch statement inside a loop is used to +// implement a deterministic finite automata we can jump thread the switch +// statement reducing number of conditional jumps. +// +FunctionPass *createDFAJumpThreadingPass(); + //===----------------------------------------------------------------------===// // // CFGSimplification - Merge basic blocks, eliminate unreachable blocks, Index: llvm/include/llvm/Transforms/Scalar/DFAJumpThreading.h =================================================================== --- /dev/null +++ llvm/include/llvm/Transforms/Scalar/DFAJumpThreading.h @@ -0,0 +1,27 @@ +//===- DFAJumpThreading.h - Threads a switch statement inside a loop ------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file provides the interface for the DFAJumpThreading pass. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_TRANSFORMS_SCALAR_DFAJUMPTHREADING_H +#define LLVM_TRANSFORMS_SCALAR_DFAJUMPTHREADING_H + +#include "llvm/IR/Function.h" +#include "llvm/IR/PassManager.h" + +namespace llvm { + +struct DFAJumpThreadingPass : PassInfoMixin { + PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM); +}; + +} // end namespace llvm + +#endif // LLVM_TRANSFORMS_SCALAR_DFAJUMPTHREADING_H Index: llvm/lib/Passes/PassBuilder.cpp =================================================================== --- llvm/lib/Passes/PassBuilder.cpp +++ llvm/lib/Passes/PassBuilder.cpp @@ -145,6 +145,7 @@ #include "llvm/Transforms/Scalar/ConstraintElimination.h" #include "llvm/Transforms/Scalar/CorrelatedValuePropagation.h" #include "llvm/Transforms/Scalar/DCE.h" +#include "llvm/Transforms/Scalar/DFAJumpThreading.h" #include "llvm/Transforms/Scalar/DeadStoreElimination.h" #include "llvm/Transforms/Scalar/DivRemPairs.h" #include "llvm/Transforms/Scalar/EarlyCSE.h" @@ -303,6 +304,7 @@ extern cl::opt EnableLoopInterchange; extern cl::opt EnableUnrollAndJam; extern cl::opt EnableLoopFlatten; +extern cl::opt EnableDFAJumpThreading; extern cl::opt RunNewGVN; extern cl::opt RunPartialInlining; extern cl::opt ExtraVectorizerPasses; @@ -827,6 +829,9 @@ // Re-consider control flow based optimizations after redundancy elimination, // redo DCE, etc. + if (EnableDFAJumpThreading && Level.getSizeLevel() == 0) + FPM.addPass(DFAJumpThreadingPass()); + FPM.addPass(JumpThreadingPass()); FPM.addPass(CorrelatedValuePropagationPass()); Index: llvm/lib/Passes/PassRegistry.def =================================================================== --- llvm/lib/Passes/PassRegistry.def +++ llvm/lib/Passes/PassRegistry.def @@ -211,6 +211,7 @@ FUNCTION_PASS("coro-cleanup", CoroCleanupPass()) FUNCTION_PASS("correlated-propagation", CorrelatedValuePropagationPass()) FUNCTION_PASS("dce", DCEPass()) +FUNCTION_PASS("dfa-jump-threading", DFAJumpThreadingPass()) FUNCTION_PASS("div-rem-pairs", DivRemPairsPass()) FUNCTION_PASS("dse", DSEPass()) FUNCTION_PASS("dot-cfg", CFGPrinterPass()) Index: llvm/lib/Transforms/IPO/PassManagerBuilder.cpp =================================================================== --- llvm/lib/Transforms/IPO/PassManagerBuilder.cpp +++ llvm/lib/Transforms/IPO/PassManagerBuilder.cpp @@ -97,6 +97,10 @@ cl::Hidden, cl::desc("Enable the LoopFlatten Pass")); +cl::opt EnableDFAJumpThreading("enable-dfa-jump-thread", + cl::desc("Enable DFA jump threading."), + cl::init(true), cl::Hidden); + static cl::opt EnablePrepareForThinLTO("prepare-for-thinlto", cl::init(false), cl::Hidden, cl::desc("Enable preparation for ThinLTO.")); @@ -494,6 +498,9 @@ MPM.add(createInstructionCombiningPass()); addExtensionsToPM(EP_Peephole, MPM); if (OptLevel > 1) { + if (EnableDFAJumpThreading && SizeLevel == 0) + MPM.add(createDFAJumpThreadingPass()); + MPM.add(createJumpThreadingPass()); // Thread jumps MPM.add(createCorrelatedValuePropagationPass()); } Index: llvm/lib/Transforms/Scalar/CMakeLists.txt =================================================================== --- llvm/lib/Transforms/Scalar/CMakeLists.txt +++ llvm/lib/Transforms/Scalar/CMakeLists.txt @@ -9,6 +9,7 @@ CorrelatedValuePropagation.cpp DCE.cpp DeadStoreElimination.cpp + DFAJumpThreading.cpp DivRemPairs.cpp EarlyCSE.cpp FlattenCFGPass.cpp Index: llvm/lib/Transforms/Scalar/DFAJumpThreading.cpp =================================================================== --- /dev/null +++ llvm/lib/Transforms/Scalar/DFAJumpThreading.cpp @@ -0,0 +1,1207 @@ +//===- DFAJumpThreading.cpp - Threads a switch statement inside a loop ----===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// When a switch statement inside a loop is used to implement a deterministic +// finite automaton, we can jump thread the switch statement reducing number of +// conditional jumps. Currently this does not happen in LLVM jump threading. +// For example the code pattern on the left could functionally be transformed +// to the right pattern if the switch condition is predictable. +// +// X = A goto A +// for (...) A: +// switch (X) ... +// case A goto B +// X = B B: +// case B ... +// X = C goto C +// +// The pass first checks that a switch variable is decided by the control flow +// path taken in the loop. It then enumerates through all paths in the loop and +// labels the basic blocks where the next state is decided. +// +// Using this information it creates new paths that unconditionally branch to +// the next case. This involves cloning code, so it only runs if the amount of +// code duplicated is below a threshold. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Transforms/Scalar/DFAJumpThreading.h" +#include "llvm/ADT/APInt.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DepthFirstIterator.h" +#include "llvm/ADT/SmallSet.h" +#include "llvm/ADT/Statistic.h" +#include "llvm/Analysis/AssumptionCache.h" +#include "llvm/Analysis/CodeMetrics.h" +#include "llvm/Analysis/LoopIterator.h" +#include "llvm/Analysis/TargetTransformInfo.h" +#include "llvm/CodeGen/Passes.h" +#include "llvm/IR/CFG.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/IntrinsicInst.h" +#include "llvm/IR/Verifier.h" +#include "llvm/InitializePasses.h" +#include "llvm/Pass.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Debug.h" +#include "llvm/Transforms/Scalar.h" +#include "llvm/Transforms/Utils/BasicBlockUtils.h" +#include "llvm/Transforms/Utils/Cloning.h" +#include "llvm/Transforms/Utils/SSAUpdaterBulk.h" +#include "llvm/Transforms/Utils/ValueMapper.h" +#include +#include +#include +#include + +using namespace llvm; + +#define DEBUG_TYPE "dfa-jump-threading" + +STATISTIC(NumTransforms, "Number of transformations done"); +STATISTIC(NumCloned, "Number of blocks cloned"); +STATISTIC(NumPaths, "Number of individual paths threaded"); + +static cl::opt + IsViewCFGBefore("dfa-jump-view-cfg-before", + cl::desc("View the CFG before DFA Jump Threading"), cl::Hidden, + cl::init(false)); + +static cl::opt MaxPathDepth( + "dfa-max-path-length", + cl::desc("Max number of blocks searched to find threadable path"), + cl::Hidden, cl::init(20)); + +static cl::opt + CostThreshold("dfa-cost-threshold", + cl::desc("Maximum cost accepted for the transformation"), + cl::Hidden, cl::init(50)); + +namespace { + +class SelectInstToUnfold { + SelectInst *SI; + PHINode *SIUse; + +public: + SelectInstToUnfold(SelectInst *SI, PHINode *SIUse) : SI(SI), SIUse(SIUse) {} + + SelectInst *getInst() { return SI; } + PHINode *getUse() { return SIUse; } + + explicit operator bool() const { return SI && SIUse; } +}; + +void unfold(DomTreeUpdater *DTU, SelectInstToUnfold SIToUnfold, + std::vector *NewSIsToUnfold, + std::vector *NewBBs); + +class DFAJumpThreading { +public: + DFAJumpThreading(AssumptionCache *AC, DominatorTree *DT, + TargetTransformInfo *TTI) + : AC(AC), DT(DT), TTI(TTI) {} + + bool run(Function &F); + +private: + void + unfoldSelectInstrs(DominatorTree *DT, + const SmallVector &SelectInsts) { + DomTreeUpdater DTU(DT, DomTreeUpdater::UpdateStrategy::Eager); + SmallVector Stack; + for (SelectInstToUnfold SIToUnfold : SelectInsts) { + Stack.push_back(SIToUnfold); + } + + while (!Stack.empty()) { + SelectInstToUnfold SIToUnfold = Stack.back(); + Stack.pop_back(); + + std::vector NewSIsToUnfold; + std::vector NewBBs; + unfold(&DTU, SIToUnfold, &NewSIsToUnfold, &NewBBs); + + // Put newly discovered select instructions into the work list. + for (const SelectInstToUnfold &NewSIToUnfold : NewSIsToUnfold) { + Stack.push_back(NewSIToUnfold); + } + } + } + + AssumptionCache *AC; + DominatorTree *DT; + TargetTransformInfo *TTI; +}; + +class DFAJumpThreadingLegacyPass : public FunctionPass { +public: + static char ID; // Pass identification + DFAJumpThreadingLegacyPass() : FunctionPass(ID) {} + + void getAnalysisUsage(AnalysisUsage &AU) const override { + AU.addRequired(); + AU.addRequired(); + AU.addRequired(); + } + + bool runOnFunction(Function &F) override { + if (skipFunction(F)) + return false; + + AssumptionCache *AC = + &getAnalysis().getAssumptionCache(F); + DominatorTree *DT = &getAnalysis().getDomTree(); + TargetTransformInfo *TTI = + &getAnalysis().getTTI(F); + + return DFAJumpThreading(AC, DT, TTI).run(F); + } +}; +} // end anonymous namespace + +char DFAJumpThreadingLegacyPass::ID = 0; +INITIALIZE_PASS_BEGIN(DFAJumpThreadingLegacyPass, "dfa-jump-threading", + "DFA Jump Threading", false, false) +INITIALIZE_PASS_DEPENDENCY(AssumptionCacheTracker) +INITIALIZE_PASS_DEPENDENCY(DominatorTreeWrapperPass) +INITIALIZE_PASS_DEPENDENCY(TargetTransformInfoWrapperPass) +INITIALIZE_PASS_END(DFAJumpThreadingLegacyPass, "dfa-jump-threading", + "DFA Jump Threading", false, false) + +// Public interface to the DFA Jump Threading pass +FunctionPass *llvm::createDFAJumpThreadingPass() { + return new DFAJumpThreadingLegacyPass(); +} + +namespace { + +/// Unfold the select instruction held in \p SIToUnfold. +/// +/// Put newly discovered select instructions into \p NewSIsToUnfold. Put newly +/// created basic blocks into \p NewBBs. +/// +/// This routine is inspired by CodeGenPrepare::optimizeSelectInst(). +void unfold(DomTreeUpdater *DTU, SelectInstToUnfold SIToUnfold, + std::vector *NewSIsToUnfold, + std::vector *NewBBs) { + SelectInst *SI = SIToUnfold.getInst(); + PHINode *SIUse = SIToUnfold.getUse(); + BasicBlock *StartBlock = SI->getParent(); + BasicBlock *EndBlock = SIUse->getParent(); + BranchInst *StartBlockTerm = + dyn_cast(StartBlock->getTerminator()); + + assert(StartBlockTerm && StartBlockTerm->isUnconditional()); + assert(SI->hasOneUse()); + + // These are the new basic blocks for the conditional branch. + // At least one will become an actual new basic block. + BasicBlock *TrueBlock = nullptr; + BasicBlock *FalseBlock = nullptr; + BranchInst *TrueBranch = nullptr; + BranchInst *FalseBranch = nullptr; + + // Sink select instructions to be able to unfold them later. + if (SelectInst *SIOp = dyn_cast(SI->getTrueValue())) { + assert(SIOp->hasOneUse()); + TrueBlock = BasicBlock::Create(SI->getContext(), "si.unfold.true", + EndBlock->getParent(), EndBlock); + NewBBs->push_back(TrueBlock); + TrueBranch = BranchInst::Create(EndBlock, TrueBlock); + SIOp->moveBefore(TrueBranch); + NewSIsToUnfold->push_back(SelectInstToUnfold(SIOp, SIUse)); + DTU->applyUpdates({{DominatorTree::Insert, TrueBlock, EndBlock}}); + } + if (SelectInst *SIOp = dyn_cast(SI->getFalseValue())) { + assert(SIOp->hasOneUse()); + FalseBlock = BasicBlock::Create(SI->getContext(), "si.unfold.false", + EndBlock->getParent(), EndBlock); + NewBBs->push_back(FalseBlock); + FalseBranch = BranchInst::Create(EndBlock, FalseBlock); + SIOp->moveBefore(FalseBranch); + NewSIsToUnfold->push_back(SelectInstToUnfold(SIOp, SIUse)); + DTU->applyUpdates({{DominatorTree::Insert, FalseBlock, EndBlock}}); + } + + // If there was nothing to sink, then arbitrarily choose the 'false' side + // for a new input value to the PHI. + if (!TrueBlock && !FalseBlock) { + FalseBlock = BasicBlock::Create(SI->getContext(), "si.unfold.false", + EndBlock->getParent(), EndBlock); + NewBBs->push_back(FalseBlock); + BranchInst::Create(EndBlock, FalseBlock); + DTU->applyUpdates({{DominatorTree::Insert, FalseBlock, EndBlock}}); + } + + // Insert the real conditional branch based on the original condition. + // If we did not create a new block for one of the 'true' or 'false' paths + // of the condition, it means that side of the branch goes to the end block + // directly and the path originates from the start block from the point of + // view of the new PHI. + BasicBlock *TT = EndBlock; + BasicBlock *FT = EndBlock; + if (TrueBlock && FalseBlock) { + // A diamond. + TT = TrueBlock; + FT = FalseBlock; + + // Update the phi node of SI. + SIUse->removeIncomingValue(StartBlock, /* DeletePHIIfEmpty = */ false); + SIUse->addIncoming(SI->getTrueValue(), TrueBlock); + SIUse->addIncoming(SI->getFalseValue(), FalseBlock); + + // Update any other PHI nodes in EndBlock. + for (auto II = EndBlock->begin(); PHINode *Phi = dyn_cast(II); + ++II) { + if (Phi != SIUse) { + Phi->addIncoming(Phi->getIncomingValueForBlock(StartBlock), TrueBlock); + Phi->addIncoming(Phi->getIncomingValueForBlock(StartBlock), FalseBlock); + } + } + } else { + BasicBlock *NewBlock = nullptr; + Value *SIOp1 = SI->getTrueValue(); + Value *SIOp2 = SI->getFalseValue(); + + // A triangle pointing right. + if (!TrueBlock) { + NewBlock = FalseBlock; + FT = FalseBlock; + } + // A triangle pointing left. + else { + NewBlock = TrueBlock; + TT = TrueBlock; + std::swap(SIOp1, SIOp2); + } + + // Update the phi node of SI. + for (unsigned Idx = 0; Idx < SIUse->getNumIncomingValues(); ++Idx) { + if (SIUse->getIncomingBlock(Idx) == StartBlock) { + SIUse->setIncomingValue(Idx, SIOp1); + } + } + SIUse->addIncoming(SIOp2, NewBlock); + + // Update any other PHI nodes in EndBlock. + for (auto II = EndBlock->begin(); PHINode *Phi = dyn_cast(II); + ++II) { + if (Phi != SIUse) { + Phi->addIncoming(Phi->getIncomingValueForBlock(StartBlock), NewBlock); + } + } + } + StartBlockTerm->eraseFromParent(); + BranchInst::Create(TT, FT, SI->getCondition(), StartBlock); + DTU->applyUpdates({{DominatorTree::Insert, StartBlock, TT}, + {DominatorTree::Insert, StartBlock, FT}}); + + // The select is now dead. + SI->eraseFromParent(); +} + +struct ClonedBlock { + BasicBlock *BB; + uint64_t State; +}; + +typedef std::deque PathType; +typedef std::vector PathsType; +typedef std::set VisitedBlocks; +typedef std::vector CloneList; + +// This data structure keeps track of all blocks that have been cloned in the +// optimization. If two different ThreadingPaths clone the same block for a +// certain state it should be reused, and it can be looked up in this map. +typedef DenseMap DuplicateBlockMap; + +// This map keeps track of all the new definitions for an instruction. This +// information is needed when restoring SSA form after cloning blocks. +typedef DenseMap> DefMap; + +inline raw_ostream &operator<<(raw_ostream &OS, const PathType &Path) { + OS << "< "; + for (const BasicBlock *BB : Path) { + std::string BBName; + if (BB->hasName()) + raw_string_ostream(BBName) << BB->getName(); + else + raw_string_ostream(BBName) << BB; + OS << BBName << " "; + } + OS << ">"; + return OS; +} + +struct ThreadingPath { + uint64_t getExitValue() const { return ExitVal; } + void setExitValue(const ConstantInt *V) { + ExitVal = V->getZExtValue(); + IsExitValSet = true; + } + bool isExitValueSet() const { return IsExitValSet; } + + const BasicBlock *getDeterminatorBB() const { return DBB; } + void setDeterminator(const BasicBlock *BB) { DBB = BB; } + + const PathType &getPath() const { return Path; } + void setPath(const PathType &NewPath) { Path = NewPath; } + + void print(raw_ostream &OS) const { + OS << Path << " [ " << ExitVal << ", " << DBB->getName() << " ]"; + } + +private: + PathType Path; + uint64_t ExitVal; + const BasicBlock *DBB = nullptr; + bool IsExitValSet = false; +}; + +#ifndef NDEBUG +inline raw_ostream &operator<<(raw_ostream &OS, const ThreadingPath &TPath) { + TPath.print(OS); + return OS; +} +#endif + +struct MainSwitch { + MainSwitch(SwitchInst *SI) { + if (isPredictable(SI)) + Instr = SI; + } + + virtual ~MainSwitch() = default; + + SwitchInst *getInstr() const { return Instr; } + const SmallVector getSelectInsts() { + return SelectInsts; + } + +private: + /// Do a use-def chain traversal. Make sure the value of the switch variable + /// is always a known constant. This means that all conditional jumps based on + /// switch variable can be converted to unconditional jump. + bool isPredictable(const SwitchInst *SI) { + std::deque Q; + SmallSet SeenValues; + SelectInsts.clear(); + + Value *FirstDef = SI->getOperand(0); + auto *Inst = dyn_cast(FirstDef); + + // If this is a function argument or another non-instruction, then give up. + // We are interested in loop local variables. + if (!Inst) + return false; + + // Require the first definition to be a PHINode + if (!isa(Inst)) + return false; + + LLVM_DEBUG(dbgs() << "\tisPredictable() FirstDef: " << *Inst << "\n"); + + Q.push_back(Inst); + SeenValues.insert(FirstDef); + + while (!Q.empty()) { + Instruction *Current = Q.front(); + Q.pop_front(); + + if (auto *Phi = dyn_cast(Current)) { + for (Value *Incoming : Phi->incoming_values()) { + if (!isValidValue(Incoming, SeenValues)) { + return false; + } + addInstToQueue(Incoming, Q, SeenValues); + } + LLVM_DEBUG(dbgs() << "\tisPredictable() phi: " << *Phi << "\n"); + } else if (SelectInst *SelI = dyn_cast(Current)) { + if (!isValidSelectInst(SelI)) { + return false; + } + if (!isValidValue(SelI->getTrueValue(), SeenValues) || + !isValidValue(SelI->getFalseValue(), SeenValues)) { + return false; + } + addInstToQueue(SelI->getTrueValue(), Q, SeenValues); + addInstToQueue(SelI->getFalseValue(), Q, SeenValues); + LLVM_DEBUG(dbgs() << "\tisPredictable() select: " << *SelI << "\n"); + if (auto *SelIUse = dyn_cast(SelI->user_back())) { + SelectInsts.push_back(SelectInstToUnfold(SelI, SelIUse)); + } + } else { + // If it is neither a phi nor a select, then we give up. + return false; + } + } + + return true; + } + + bool isValidValue(Value *InpVal, SmallSet &SeenValues) { + if (SeenValues.find(InpVal) != SeenValues.end()) + return true; + + if (isa(InpVal)) + return true; + + // If this is a function argument or another non-instruction, then give up. + if (!isa(InpVal)) + return false; + + return true; + } + + void addInstToQueue(Value *Val, std::deque &Q, + SmallSet &SeenValues) { + if (SeenValues.find(Val) != SeenValues.end()) + return; + if (Instruction *I = dyn_cast(Val)) { + Q.push_back(I); + } + SeenValues.insert(Val); + } + + bool isValidSelectInst(SelectInst *SI) { + if (!SI->hasOneUse()) { + return false; + } + + Instruction *SIUse = dyn_cast(SI->user_back()); + // The use of the select inst should be either a phi or another select. + if (!SIUse && !(isa(SIUse) || isa(SIUse))) { + return false; + } + + BasicBlock *SIBB = SI->getParent(); + + // Currently, we can only expand select instructions in basic blocks with + // one successor. + BranchInst *SITerm = dyn_cast(SIBB->getTerminator()); + if (!SITerm || !SITerm->isUnconditional()) { + return false; + } + + if (isa(SIUse) && SIBB->getSingleSuccessor() != + dyn_cast(SIUse)->getParent()) { + return false; + } + + // If select will not be sunk during unfolding, and it is in the same basic + // block as another state defining select, then cannot unfold both. + for (SelectInstToUnfold SIToUnfold : SelectInsts) { + SelectInst *PrevSI = SIToUnfold.getInst(); + if (PrevSI->getTrueValue() != SI && PrevSI->getFalseValue() != SI && + PrevSI->getParent() == SI->getParent()) + return false; + } + + return true; + } + + SwitchInst *Instr = nullptr; + SmallVector SelectInsts; +}; + +struct AllSwitchPaths { + AllSwitchPaths(const MainSwitch *MSwitch) + : Switch(MSwitch->getInstr()), SwitchBlock(Switch->getParent()) {} + + std::vector &getThreadingPaths() { return TPaths; } + unsigned getNumThreadingPaths() { return TPaths.size(); } + SwitchInst *getSwitchInst() { return Switch; } + BasicBlock *getSwitchBlock() { return SwitchBlock; } + + void run() { + VisitedBlocks Visited; + PathsType LoopPaths = paths(SwitchBlock, Visited, /* PathDepth = */ 1); + StateDefMap StateDef = getStateDefMap(); + + for (PathType Path : LoopPaths) { + ThreadingPath TPath; + + const BasicBlock *PrevBB = Path.back(); + for (const BasicBlock *BB : Path) { + if (StateDef.count(BB) != 0) { + const PHINode *Phi = dyn_cast(StateDef[BB]); + assert(Phi && "Expected a state-defining instr to be a phi node."); + + const Value *V = Phi->getIncomingValueForBlock(PrevBB); + if (const ConstantInt *C = dyn_cast(V)) { + TPath.setExitValue(C); + TPath.setDeterminator(BB); + TPath.setPath(Path); + } + } + + // Switch block is the determinator, this is the final exit value. + if (TPath.isExitValueSet() && BB == Path.front()) + break; + + PrevBB = BB; + } + + if (TPath.isExitValueSet()) + TPaths.push_back(TPath); + } + } + +private: + // Value: an instruction that defines a switch state; + // Key: the parent basic block of that instruction. + typedef DenseMap StateDefMap; + + PathsType paths(BasicBlock *BB, VisitedBlocks &Visited, + unsigned PathDepth) const { + PathsType Res; + + // Stop exploring paths after visiting MaxPathLength blocks + if (PathDepth > MaxPathDepth) + return Res; + + Visited.insert(BB); + + // Some blocks have multiple edges to the same successor, and this set + // is used to prevent a duplicate path from being generated + SmallSet Successors; + + for (succ_iterator SI = succ_begin(BB), E = succ_end(BB); SI != E; ++SI) { + BasicBlock *Succ = *SI; + + if (Successors.find(Succ) != Successors.end()) + continue; + Successors.insert(Succ); + + // Found a cycle through the SwitchBlock + if (Succ == SwitchBlock) { + Res.push_back({BB}); + continue; + } + + // We have encountered a cycle, do not get caught in it + if (Visited.find(Succ) != Visited.end()) + continue; + + PathsType SuccPaths = paths(Succ, Visited, PathDepth + 1); + for (PathType Path : SuccPaths) { + PathType NewPath(Path); + NewPath.push_front(BB); + Res.push_back(NewPath); + } + } + // This block could now be visited again from a different predecessor. Note + // that this will result in exponential runtime. Subpaths could possibly be + // cached but it takes a lot of memory to store them. + Visited.erase(BB); + return Res; + } + + /// Walk the use-def chain and collect all the state-defining instructions. + StateDefMap getStateDefMap() const { + StateDefMap Res; + + Value *FirstDef = Switch->getOperand(0); + + assert(isa(FirstDef) && "After select unfolding, all state " + "definitions are expected to be phi " + "nodes."); + + SmallVector Stack; + Stack.push_back(dyn_cast(FirstDef)); + SmallSet SeenValues; + + while (!Stack.empty()) { + PHINode *CurPhi = Stack.back(); + Stack.pop_back(); + + Res[CurPhi->getParent()] = CurPhi; + SeenValues.insert(CurPhi); + + for (Value *Incoming : CurPhi->incoming_values()) { + if (Incoming == FirstDef || isa(Incoming) || + SeenValues.find(Incoming) != SeenValues.end()) { + continue; + } + + assert(isa(Incoming) && "After select unfolding, all state " + "definitions are expected to be phi " + "nodes."); + + Stack.push_back(cast(Incoming)); + } + } + + return Res; + } + + SwitchInst *Switch; + BasicBlock *SwitchBlock; + std::vector TPaths; +}; + +struct TransformFSM { + TransformFSM(AllSwitchPaths *SwitchPaths, DominatorTree *DT, + AssumptionCache *AC, TargetTransformInfo *TTI, + SmallPtrSet EphValues) + : SwitchPaths(SwitchPaths), DT(DT), AC(AC), TTI(TTI), + EphValues(EphValues) {} + + void run() { + if (isLegalAndProfitableToTransform()) { + createAllExitPaths(); + } + } + +private: + /// This function performs both a legality check and profitability check at + /// the same time since it is convenient to do so. It iterates through all + /// blocks that will be cloned, and keeps track of the duplication cost. It + /// also returns false if it is illegal to clone some required block. + bool isLegalAndProfitableToTransform() { + CodeMetrics Metrics; + SwitchInst *Switch = SwitchPaths->getSwitchInst(); + + // Note that DuplicateBlockMap is not being used as intended here. It is + // just being used to ensure (BB, State) pairs are only counted once. + DuplicateBlockMap DuplicateMap; + + for (ThreadingPath &TPath : SwitchPaths->getThreadingPaths()) { + PathType PathBBs = TPath.getPath(); + uint64_t NextState = TPath.getExitValue(); + const BasicBlock *Determinator = TPath.getDeterminatorBB(); + + // Update Metrics for the Switch block, this is always cloned + BasicBlock *BB = SwitchPaths->getSwitchBlock(); + BasicBlock *VisitedBB = getClonedBB(BB, NextState, DuplicateMap); + if (!VisitedBB) { + Metrics.analyzeBasicBlock(BB, *TTI, EphValues); + DuplicateMap[BB].push_back({BB, NextState}); + } + + // If the Switch block is the Determinator, then we can continue since + // this is the only block that is cloned and we already counted for it. + if (PathBBs.front() == Determinator) { + continue; + } + + // Otherwise update Metrics for all blocks that will be cloned. If any + // block is already cloned and would be reused, don't double count it. + auto DetIt = std::find(PathBBs.begin(), PathBBs.end(), Determinator); + for (auto BBIt = DetIt; BBIt != PathBBs.end(); BBIt++) { + BB = *BBIt; + VisitedBB = getClonedBB(BB, NextState, DuplicateMap); + if (VisitedBB) + continue; + Metrics.analyzeBasicBlock(BB, *TTI, EphValues); + DuplicateMap[BB].push_back({BB, NextState}); + } + + if (Metrics.notDuplicatable) { + LLVM_DEBUG(dbgs() << "DFA Jump Threading: Not jump threading, contains " + << "non-duplicatable instructions.\n"); + return false; + } + + if (Metrics.convergent) { + LLVM_DEBUG(dbgs() << "DFA Jump Threading: Not jump threading, contains " + << "convergent instructions.\n"); + return false; + } + } + + // Factor in the number of conditional branches reduced from jump threading + unsigned CondBranches = + APInt(32, Switch->getNumSuccessors()).ceilLogBase2(); + unsigned NumInsts = Metrics.NumInsts; + unsigned DuplicationCost = NumInsts / CondBranches; + + LLVM_DEBUG(dbgs() << "\nDFA Jump Threading: Cost to jump thread block " + << SwitchPaths->getSwitchBlock()->getName() + << " is: " << DuplicationCost << "\n\n"); + + if (DuplicationCost > CostThreshold) { + LLVM_DEBUG(dbgs() << "Not jump threading, duplication cost exceeds the " + << "cost threshold.\n"); + return false; + } + + return true; + } + + /// Transform each threading path to effectively jump thread the FSM. For + /// example the CFG below could be transformed as follows, where the cloned + /// blocks unconditionally branch to the next correct case based on what is + /// identified in the analysis. + /// sw.bb sw.bb + /// / | \ / | \ + /// case1 case2 case3 case1 case2 case3 + /// \ | / | | | + /// determinator det.2 det.3 det.1 + /// br sw.bb / | \ + /// sw.bb.2 sw.bb.3 sw.bb.1 + /// br case2 br case3 br case1 + void createAllExitPaths() { + DomTreeUpdater DTU(*DT, DomTreeUpdater::UpdateStrategy::Eager); + + // Move the switch block to the end of the path, since it will be duplicated + BasicBlock *SwitchBlock = SwitchPaths->getSwitchBlock(); + for (ThreadingPath &TPath : SwitchPaths->getThreadingPaths()) { + LLVM_DEBUG(dbgs() << TPath << "\n"); + PathType NewPath(TPath.getPath()); + NewPath.push_back(SwitchBlock); + TPath.setPath(NewPath); + } + + // Transform the ThreadingPaths and keep track of the cloned values + DuplicateBlockMap DuplicateMap; + DefMap NewDefs; + + SmallSet BlocksToClean; + for (BasicBlock *BB : successors(SwitchBlock)) { + BlocksToClean.insert(BB); + } + + for (ThreadingPath &TPath : SwitchPaths->getThreadingPaths()) { + createExitPath(NewDefs, TPath, DuplicateMap, BlocksToClean, &DTU); + NumPaths++; + } + + // After all paths are cloned, now update the last successor of the cloned + // path so it skips over the switch statement + for (ThreadingPath &TPath : SwitchPaths->getThreadingPaths()) { + updateLastSuccessor(TPath, DuplicateMap, &DTU); + } + + // For each instruction that was cloned and used outside, update its uses + updateSSA(NewDefs); + + // Clean PHI Nodes for the newly created blocks + for (BasicBlock *BB : BlocksToClean) { + cleanPhiNodes(BB); + } + } + + /// For a specific ThreadingPath, create an exit path starting from the + /// determinator block. To remember the correct destination, we have to + /// duplicate blocks corresponding to each state. Also update the terminating + /// instruction of the predecessors, and phis in the successor blocks. + void createExitPath(DefMap &NewDefs, ThreadingPath &Path, + DuplicateBlockMap &DuplicateMap, + SmallSet &BlocksToClean, + DomTreeUpdater *DTU) { + uint64_t NextState = Path.getExitValue(); + const BasicBlock *Determinator = Path.getDeterminatorBB(); + PathType PathBBs = Path.getPath(); + + // Don't select the placeholder block in front + if (PathBBs.front() == Determinator) + PathBBs.pop_front(); + + auto DetIt = std::find(PathBBs.begin(), PathBBs.end(), Determinator); + auto Prev = std::prev(DetIt); + BasicBlock *PrevBB = *Prev; + for (auto BBIt = DetIt; BBIt != PathBBs.end(); BBIt++) { + BasicBlock *BB = *BBIt; + BlocksToClean.insert(BB); + + // We already cloned BB for this NextState, now just update the branch + // and continue. + BasicBlock *NextBB = getClonedBB(BB, NextState, DuplicateMap); + if (NextBB) { + updatePredecessor(PrevBB, BB, NextBB, DTU); + PrevBB = NextBB; + continue; + } + + // Clone the BB and update the successor of Prev to jump to the new block + BasicBlock *NewBB = cloneBlockAndUpdatePredecessor( + BB, PrevBB, NextState, DuplicateMap, NewDefs, DTU); + DuplicateMap[BB].push_back({NewBB, NextState}); + BlocksToClean.insert(NewBB); + PrevBB = NewBB; + } + } + + /// Restore SSA form after cloning blocks. Each cloned block creates new defs + /// for a variable, and the uses need to be updated to reflect this. The uses + /// may be replaced with a cloned value, or some derived phi instruction. Note + /// that all uses of a value defined in the same block were already remapped + /// when cloning the block. + void updateSSA(DefMap &NewDefs) { + SSAUpdaterBulk SSAUpdate; + SmallVector UsesToRename; + + for (auto KV : NewDefs) { + Instruction *I = KV.first; + BasicBlock *BB = I->getParent(); + std::vector Cloned = KV.second; + + // Scan all uses of this instruction to see if it is used outside of its + // block, and if so, record them in UsesToRename. + for (Use &U : I->uses()) { + Instruction *User = cast(U.getUser()); + if (PHINode *UserPN = dyn_cast(User)) { + if (UserPN->getIncomingBlock(U) == BB) + continue; + } else if (User->getParent() == BB) { + continue; + } + + UsesToRename.push_back(&U); + } + + // If there are no uses outside the block, we're done with this + // instruction. + if (UsesToRename.empty()) + continue; + LLVM_DEBUG(dbgs() << "DFA-JT: Renaming non-local uses of: " << *I + << "\n"); + + // We found a use of I outside of BB. Rename all uses of I that are + // outside its block to be uses of the appropriate PHI node etc. See + // ValuesInBlocks with the values we know. + unsigned VarNum = SSAUpdate.AddVariable(I->getName(), I->getType()); + SSAUpdate.AddAvailableValue(VarNum, BB, I); + for (Instruction *New : Cloned) + SSAUpdate.AddAvailableValue(VarNum, New->getParent(), New); + + while (!UsesToRename.empty()) + SSAUpdate.AddUse(VarNum, UsesToRename.pop_back_val()); + + LLVM_DEBUG(dbgs() << "\n"); + } + // SSAUpdater handles phi placement and renaming uses with the appropriate + // value. + SSAUpdate.RewriteAllUses(DT); + } + + /// Clones a basic block, and adds it to the CFG. This function also includes + /// updating phi nodes in the successors of the BB, and remapping uses that + /// were defined locally in the cloned BB. + BasicBlock *cloneBlockAndUpdatePredecessor(BasicBlock *BB, BasicBlock *PrevBB, + uint64_t NextState, + DuplicateBlockMap &DuplicateMap, + DefMap &NewDefs, + DomTreeUpdater *DTU) { + ValueToValueMapTy VMap; + BasicBlock *NewBB = CloneBasicBlock( + BB, VMap, ".jt" + std::to_string(NextState), BB->getParent()); + NewBB->moveAfter(BB); + NumCloned++; + + for (Instruction &I : *NewBB) { + // Do not remap operands of PHINode in case a definition in BB is an + // incoming value to a phi in the same block. This incoming value will + // be renamed later while restoring SSA. + if (isa(&I)) + continue; + RemapInstruction(&I, VMap, + RF_IgnoreMissingLocals | RF_NoModuleLevelChanges); + if (AssumeInst *II = dyn_cast(&I)) + AC->registerAssumption(II); + } + + updateSuccessorPhis(BB, NewBB, NextState, VMap, DuplicateMap); + updatePredecessor(PrevBB, BB, NewBB, DTU); + updateDefMap(NewDefs, VMap); + + // Add all successors to the DominatorTree + SmallPtrSet SuccSet; + for (auto *SuccBB : successors(NewBB)) { + if (SuccSet.insert(SuccBB).second) + DTU->applyUpdates({{DominatorTree::Insert, NewBB, SuccBB}}); + } + SuccSet.clear(); + return NewBB; + } + + /// Update the phi nodes in BB's successors. This means creating a new + /// incoming value from NewBB with the new instruction wherever there is + /// an incoming value from BB. + void updateSuccessorPhis(BasicBlock *BB, BasicBlock *ClonedBB, + uint64_t NextState, ValueToValueMapTy &VMap, + DuplicateBlockMap &DuplicateMap) { + std::vector BlocksToUpdate; + + // If BB is the last block in the path, we can simply update the one case + // successor that will be reached. + if (BB == SwitchPaths->getSwitchBlock()) { + SwitchInst *Switch = SwitchPaths->getSwitchInst(); + BasicBlock *NextCase = getNextCaseSuccessor(Switch, NextState); + BlocksToUpdate.push_back(NextCase); + BasicBlock *ClonedSucc = getClonedBB(NextCase, NextState, DuplicateMap); + if (ClonedSucc) { + BlocksToUpdate.push_back(ClonedSucc); + } + } + // Otherwise update phis in all successors. + else { + for (BasicBlock *Succ : successors(BB)) { + BlocksToUpdate.push_back(Succ); + + // Check if a successor has already been cloned for the particular exit + // value. In this case if a successor was already cloned, the phi nodes + // in the cloned block should be updated directly. + BasicBlock *ClonedSucc = getClonedBB(Succ, NextState, DuplicateMap); + if (ClonedSucc) { + BlocksToUpdate.push_back(ClonedSucc); + } + } + } + + // If there is a phi with an incoming value from BB, create a new incoming + // value for the new predecessor ClonedBB. The value will either be the same + // value from BB or a cloned value. + for (BasicBlock *Succ : BlocksToUpdate) { + for (auto II = Succ->begin(); PHINode *Phi = dyn_cast(II); + ++II) { + Value *Incoming = Phi->getIncomingValueForBlock(BB); + if (Incoming) { + if (isa(Incoming)) { + Phi->addIncoming(Incoming, ClonedBB); + continue; + } + Value *ClonedVal = VMap[Incoming]; + if (ClonedVal) { + Phi->addIncoming(ClonedVal, ClonedBB); + } else { + Phi->addIncoming(Incoming, ClonedBB); + } + } + } + } + } + + /// Sets the successor of PrevBB to be NewBB instead of OldBB. Note that all + /// other successors are kept as well. + void updatePredecessor(BasicBlock *PrevBB, BasicBlock *OldBB, + BasicBlock *NewBB, DomTreeUpdater *DTU) { + // When a path is reused, there is a chance that predecessors were already + // updated before. Check if the predecessor needs to be updated first. + if (!isPredecessor(OldBB, PrevBB)) + return; + + Instruction *PrevTerm = PrevBB->getTerminator(); + for (unsigned Idx = 0; Idx < PrevTerm->getNumSuccessors(); Idx++) { + if (PrevTerm->getSuccessor(Idx) == OldBB) { + OldBB->removePredecessor(PrevBB, /* KeepOneInputPHIs = */ true); + PrevTerm->setSuccessor(Idx, NewBB); + } + } + DTU->applyUpdates({{DominatorTree::Delete, PrevBB, OldBB}, + {DominatorTree::Insert, PrevBB, NewBB}}); + } + + /// Add new value mappings to the DefMap to keep track of all new definitions + /// for a particular instruction. These will be used while updating SSA form. + void updateDefMap(DefMap &NewDefs, ValueToValueMapTy &VMap) { + for (auto Entry : VMap) { + Instruction *Inst = + dyn_cast(const_cast(Entry.first)); + if (!Inst || !Entry.second || isa(Inst) || + isa(Inst)) { + continue; + } + + Instruction *Cloned = dyn_cast(Entry.second); + if (!Cloned) + continue; + + if (NewDefs.find(Inst) == NewDefs.end()) + NewDefs[Inst] = {Cloned}; + else + NewDefs[Inst].push_back(Cloned); + } + } + + /// Update the last branch of a particular cloned path to point to the correct + /// case successor. Note that this is an optional step and would have been + /// done in later optimizations, but it makes the CFG significantly easier to + /// work with. + void updateLastSuccessor(ThreadingPath &TPath, + DuplicateBlockMap &DuplicateMap, + DomTreeUpdater *DTU) { + uint64_t NextState = TPath.getExitValue(); + BasicBlock *BB = TPath.getPath().back(); + BasicBlock *LastBlock = getClonedBB(BB, NextState, DuplicateMap); + + // Note multiple paths can end at the same block so check that it is not + // updated yet + if (!isa(LastBlock->getTerminator())) + return; + SwitchInst *Switch = cast(LastBlock->getTerminator()); + BasicBlock *NextCase = getNextCaseSuccessor(Switch, NextState); + + std::vector DTUpdates; + SmallPtrSet SuccSet; + for (BasicBlock *Succ : successors(LastBlock)) { + if (Succ != NextCase && SuccSet.insert(Succ).second) + DTUpdates.push_back({DominatorTree::Delete, LastBlock, Succ}); + } + + Switch->eraseFromParent(); + BranchInst::Create(NextCase, LastBlock); + + DTU->applyUpdates(DTUpdates); + } + + /// After cloning blocks, some of the phi nodes have extra incoming values + /// that are no longer used. This function removes them. + void cleanPhiNodes(BasicBlock *BB) { + // If BB is no longer reachable, remove any remaining phi nodes + if (pred_empty(BB)) { + std::vector PhiToRemove; + for (auto II = BB->begin(); PHINode *Phi = dyn_cast(II); ++II) { + PhiToRemove.push_back(Phi); + } + for (PHINode *PN : PhiToRemove) { + PN->replaceAllUsesWith(UndefValue::get(PN->getType())); + PN->eraseFromParent(); + } + return; + } + + // Remove any incoming values that come from an invalid predecessor + for (auto II = BB->begin(); PHINode *Phi = dyn_cast(II); ++II) { + std::vector BlocksToRemove; + for (BasicBlock *IncomingBB : Phi->blocks()) { + if (!isPredecessor(BB, IncomingBB)) + BlocksToRemove.push_back(IncomingBB); + } + for (BasicBlock *BB : BlocksToRemove) { + Phi->removeIncomingValue(BB); + } + } + } + + /// Checks if BB was already cloned for a particular next state value. If it + /// was then it returns this cloned block, and otherwise null. + BasicBlock *getClonedBB(BasicBlock *BB, uint64_t NextState, + DuplicateBlockMap &DuplicateMap) { + CloneList ClonedBBs = DuplicateMap[BB]; + + // Find an entry in the CloneList with this NextState. If it exists then + // return the corresponding BB + auto It = llvm::find_if(ClonedBBs, [NextState](const ClonedBlock &C) { + return C.State == NextState; + }); + return It != ClonedBBs.end() ? (*It).BB : nullptr; + } + + /// Helper to get the successor corresponding to a particular case value for + /// a switch statement. + BasicBlock *getNextCaseSuccessor(SwitchInst *Switch, uint64_t NextState) { + BasicBlock *NextCase = nullptr; + for (auto Case : Switch->cases()) { + if (Case.getCaseValue()->getZExtValue() == NextState) { + NextCase = Case.getCaseSuccessor(); + break; + } + } + if (!NextCase) { + NextCase = Switch->getDefaultDest(); + } + return NextCase; + } + + /// Returns true if IncomingBB is a predecessor of BB. + bool isPredecessor(BasicBlock *BB, BasicBlock *IncomingBB) { + return llvm::find(predecessors(BB), IncomingBB) != pred_end(BB); + } + + AllSwitchPaths *SwitchPaths; + DominatorTree *DT; + AssumptionCache *AC; + TargetTransformInfo *TTI; + SmallPtrSet EphValues; + std::vector TPaths; +}; + +bool DFAJumpThreading::run(Function &F) { + LLVM_DEBUG(dbgs() << "\nDFA Jump threading: " << F.getName() << "\n"); + + if (IsViewCFGBefore) { + F.viewCFG(); + } + + SmallVector ThreadableLoops; + bool MadeChanges = false; + + for (Function::iterator B = F.begin(), BE = F.end(); B != BE; B++) { + if (auto *SI = dyn_cast(B->getTerminator())) { + + LLVM_DEBUG(dbgs() << "\nCheck if SwitchInst in BB " << B->getName() + << " is predictable\n"); + MainSwitch Switch(SI); + + if (!Switch.getInstr()) + continue; + + LLVM_DEBUG(dbgs() << "\nSwitchInst in BB " << B->getName() << " is a " + << "candidate for jump threading\n"); + LLVM_DEBUG(SI->dump()); + + unfoldSelectInstrs(DT, Switch.getSelectInsts()); + if (!Switch.getSelectInsts().empty()) + MadeChanges = true; + + AllSwitchPaths SwitchPaths(&Switch); + SwitchPaths.run(); + + if (SwitchPaths.getNumThreadingPaths() > 0) { + ThreadableLoops.push_back(SwitchPaths); + + // For the time being limit this optimization to occurring once in a + // function since it can change the CFG significantly. This is not a + // strict requirement but it can cause buggy behavior if there is an + // overlap of blocks in different opportunities. There is a lot of room + // to experiment with catching more opportunities here. + break; + } + } + } + + SmallPtrSet EphValues; + if (ThreadableLoops.size() > 0) { + CodeMetrics::collectEphemeralValues(&F, AC, EphValues); + } + + for (AllSwitchPaths SwitchPaths : ThreadableLoops) { + TransformFSM Transform(&SwitchPaths, DT, AC, TTI, EphValues); + Transform.run(); + MadeChanges = true; + NumTransforms++; + } + +#ifdef EXPENSIVE_CHECKS + assert(DT->verify(DominatorTree::VerificationLevel::Full)); + verifyFunction(F, &dbgs()); +#endif + + return MadeChanges; +} + +} // end anonymous namespace + +/// Integrate with the new Pass Manager +PreservedAnalyses DFAJumpThreadingPass::run(Function &F, + FunctionAnalysisManager &AM) { + AssumptionCache &AC = AM.getResult(F); + DominatorTree &DT = AM.getResult(F); + TargetTransformInfo &TTI = AM.getResult(F); + + if (!DFAJumpThreading(&AC, &DT, &TTI).run(F)) + return PreservedAnalyses::all(); + + PreservedAnalyses PA; + PA.preserve(); + return PA; +} Index: llvm/lib/Transforms/Scalar/Scalar.cpp =================================================================== --- llvm/lib/Transforms/Scalar/Scalar.cpp +++ llvm/lib/Transforms/Scalar/Scalar.cpp @@ -60,6 +60,7 @@ initializeInferAddressSpacesPass(Registry); initializeInstSimplifyLegacyPassPass(Registry); initializeJumpThreadingPass(Registry); + initializeDFAJumpThreadingLegacyPassPass(Registry); initializeLegacyLICMPassPass(Registry); initializeLegacyLoopSinkPassPass(Registry); initializeLoopFuseLegacyPass(Registry); Index: llvm/test/CodeGen/AMDGPU/opt-pipeline.ll =================================================================== --- llvm/test/CodeGen/AMDGPU/opt-pipeline.ll +++ llvm/test/CodeGen/AMDGPU/opt-pipeline.ll @@ -508,6 +508,10 @@ ; GCN-O2-NEXT: Lazy Block Frequency Analysis ; GCN-O2-NEXT: Optimization Remark Emitter ; GCN-O2-NEXT: Combine redundant instructions +; GCN-O2-NEXT: DFA Jump Threading +; GCN-O2-NEXT: Dominator Tree Construction +; GCN-O2-NEXT: Basic Alias Analysis (stateless AA impl) +; GCN-O2-NEXT: Function Alias Analysis Results ; GCN-O2-NEXT: Lazy Value Information Analysis ; GCN-O2-NEXT: Jump Threading ; GCN-O2-NEXT: Value Propagation @@ -868,6 +872,10 @@ ; GCN-O3-NEXT: Lazy Block Frequency Analysis ; GCN-O3-NEXT: Optimization Remark Emitter ; GCN-O3-NEXT: Combine redundant instructions +; GCN-O3-NEXT: DFA Jump Threading +; GCN-O3-NEXT: Dominator Tree Construction +; GCN-O3-NEXT: Basic Alias Analysis (stateless AA impl) +; GCN-O3-NEXT: Function Alias Analysis Results ; GCN-O3-NEXT: Lazy Value Information Analysis ; GCN-O3-NEXT: Jump Threading ; GCN-O3-NEXT: Value Propagation Index: llvm/test/Other/new-pm-defaults.ll =================================================================== --- llvm/test/Other/new-pm-defaults.ll +++ llvm/test/Other/new-pm-defaults.ll @@ -180,6 +180,8 @@ ; CHECK-O-NEXT: Running analysis: DemandedBitsAnalysis ; CHECK-O-NEXT: Running pass: InstCombinePass ; CHECK-EP-PEEPHOLE-NEXT: Running pass: NoOpFunctionPass +; CHECK-O2-NEXT: Running pass: DFAJumpThreadingPass +; CHECK-O3-NEXT: Running pass: DFAJumpThreadingPass ; CHECK-O23SZ-NEXT: Running pass: JumpThreadingPass ; CHECK-O23SZ-NEXT: Running analysis: LazyValueAnalysis ; CHECK-O23SZ-NEXT: Running pass: CorrelatedValuePropagationPass Index: llvm/test/Other/new-pm-thinlto-defaults.ll =================================================================== --- llvm/test/Other/new-pm-thinlto-defaults.ll +++ llvm/test/Other/new-pm-thinlto-defaults.ll @@ -167,6 +167,8 @@ ; CHECK-O-NEXT: Running pass: BDCEPass ; CHECK-O-NEXT: Running analysis: DemandedBitsAnalysis ; CHECK-O-NEXT: Running pass: InstCombinePass +; CHECK-O2-NEXT: Running pass: DFAJumpThreadingPass +; CHECK-O3-NEXT: Running pass: DFAJumpThreadingPass ; CHECK-O23SZ-NEXT: Running pass: JumpThreadingPass ; CHECK-O23SZ-NEXT: Running analysis: LazyValueAnalysis ; CHECK-O23SZ-NEXT: Running pass: CorrelatedValuePropagationPass Index: llvm/test/Other/new-pm-thinlto-postlink-pgo-defaults.ll =================================================================== --- llvm/test/Other/new-pm-thinlto-postlink-pgo-defaults.ll +++ llvm/test/Other/new-pm-thinlto-postlink-pgo-defaults.ll @@ -140,6 +140,8 @@ ; CHECK-O-NEXT: Running pass: BDCEPass ; CHECK-O-NEXT: Running analysis: DemandedBitsAnalysis ; CHECK-O-NEXT: Running pass: InstCombinePass +; CHECK-O2-NEXT: Running pass: DFAJumpThreadingPass +; CHECK-O3-NEXT: Running pass: DFAJumpThreadingPass ; CHECK-O23SZ-NEXT: Running pass: JumpThreadingPass ; CHECK-O23SZ-NEXT: Running analysis: LazyValueAnalysis ; CHECK-O23SZ-NEXT: Running pass: CorrelatedValuePropagationPass Index: llvm/test/Other/new-pm-thinlto-postlink-samplepgo-defaults.ll =================================================================== --- llvm/test/Other/new-pm-thinlto-postlink-samplepgo-defaults.ll +++ llvm/test/Other/new-pm-thinlto-postlink-samplepgo-defaults.ll @@ -149,6 +149,8 @@ ; CHECK-O-NEXT: Running pass: BDCEPass ; CHECK-O-NEXT: Running analysis: DemandedBitsAnalysis ; CHECK-O-NEXT: Running pass: InstCombinePass +; CHECK-O2-NEXT: Running pass: DFAJumpThreadingPass +; CHECK-O3-NEXT: Running pass: DFAJumpThreadingPass ; CHECK-O23SZ-NEXT: Running pass: JumpThreadingPass ; CHECK-O23SZ-NEXT: Running analysis: LazyValueAnalysis ; CHECK-O23SZ-NEXT: Running pass: CorrelatedValuePropagationPass Index: llvm/test/Other/new-pm-thinlto-prelink-pgo-defaults.ll =================================================================== --- llvm/test/Other/new-pm-thinlto-prelink-pgo-defaults.ll +++ llvm/test/Other/new-pm-thinlto-prelink-pgo-defaults.ll @@ -180,6 +180,8 @@ ; CHECK-O-NEXT: Running pass: BDCEPass ; CHECK-O-NEXT: Running analysis: DemandedBitsAnalysis ; CHECK-O-NEXT: Running pass: InstCombinePass +; CHECK-O2-NEXT: Running pass: DFAJumpThreadingPass +; CHECK-O3-NEXT: Running pass: DFAJumpThreadingPass ; CHECK-O23SZ-NEXT: Running pass: JumpThreadingPass ; CHECK-O23SZ-NEXT: Running analysis: LazyValueAnalysis ; CHECK-O23SZ-NEXT: Running pass: CorrelatedValuePropagationPass Index: llvm/test/Other/new-pm-thinlto-prelink-samplepgo-defaults.ll =================================================================== --- llvm/test/Other/new-pm-thinlto-prelink-samplepgo-defaults.ll +++ llvm/test/Other/new-pm-thinlto-prelink-samplepgo-defaults.ll @@ -143,6 +143,8 @@ ; CHECK-O-NEXT: Running pass: BDCEPass ; CHECK-O-NEXT: Running analysis: DemandedBitsAnalysis ; CHECK-O-NEXT: Running pass: InstCombinePass +; CHECK-O2-NEXT: Running pass: DFAJumpThreadingPass +; CHECK-O3-NEXT: Running pass: DFAJumpThreadingPass ; CHECK-O23SZ-NEXT: Running pass: JumpThreadingPass ; CHECK-O23SZ-NEXT: Running analysis: LazyValueAnalysis ; CHECK-O23SZ-NEXT: Running pass: CorrelatedValuePropagationPass Index: llvm/test/Other/opt-O2-pipeline.ll =================================================================== --- llvm/test/Other/opt-O2-pipeline.ll +++ llvm/test/Other/opt-O2-pipeline.ll @@ -153,6 +153,10 @@ ; CHECK-NEXT: Lazy Block Frequency Analysis ; CHECK-NEXT: Optimization Remark Emitter ; CHECK-NEXT: Combine redundant instructions +; CHECK-NEXT: DFA Jump Threading +; CHECK-NEXT: Dominator Tree Construction +; CHECK-NEXT: Basic Alias Analysis (stateless AA impl) +; CHECK-NEXT: Function Alias Analysis Results ; CHECK-NEXT: Lazy Value Information Analysis ; CHECK-NEXT: Jump Threading ; CHECK-NEXT: Value Propagation Index: llvm/test/Other/opt-O3-pipeline-enable-matrix.ll =================================================================== --- llvm/test/Other/opt-O3-pipeline-enable-matrix.ll +++ llvm/test/Other/opt-O3-pipeline-enable-matrix.ll @@ -158,6 +158,10 @@ ; CHECK-NEXT: Lazy Block Frequency Analysis ; CHECK-NEXT: Optimization Remark Emitter ; CHECK-NEXT: Combine redundant instructions +; CHECK-NEXT: DFA Jump Threading +; CHECK-NEXT: Dominator Tree Construction +; CHECK-NEXT: Basic Alias Analysis (stateless AA impl) +; CHECK-NEXT: Function Alias Analysis Results ; CHECK-NEXT: Lazy Value Information Analysis ; CHECK-NEXT: Jump Threading ; CHECK-NEXT: Value Propagation Index: llvm/test/Other/opt-O3-pipeline.ll =================================================================== --- llvm/test/Other/opt-O3-pipeline.ll +++ llvm/test/Other/opt-O3-pipeline.ll @@ -158,6 +158,10 @@ ; CHECK-NEXT: Lazy Block Frequency Analysis ; CHECK-NEXT: Optimization Remark Emitter ; CHECK-NEXT: Combine redundant instructions +; CHECK-NEXT: DFA Jump Threading +; CHECK-NEXT: Dominator Tree Construction +; CHECK-NEXT: Basic Alias Analysis (stateless AA impl) +; CHECK-NEXT: Function Alias Analysis Results ; CHECK-NEXT: Lazy Value Information Analysis ; CHECK-NEXT: Jump Threading ; CHECK-NEXT: Value Propagation Index: llvm/test/Transforms/DFAJumpThreading/dfa-constant-propagation.ll =================================================================== --- /dev/null +++ llvm/test/Transforms/DFAJumpThreading/dfa-constant-propagation.ll @@ -0,0 +1,31 @@ +; RUN: opt -S -dfa-jump-threading -sccp -simplifycfg %s | FileCheck %s + +; CHECK: define i32 @test +; CHECK-NEXT: entry: +; CHECK-NEXT: ret i32 3 + +; This test checks that a constant propagation is applied for a basic loop. +; Related to bug 44679. +define i32 @test(i32 %a) { +entry: + br label %while.cond + +while.cond: + %num = phi i32 [ 0, %entry ], [ %add, %case1 ] + %state = phi i32 [ 1, %entry ], [ %state.next, %case1 ] + switch i32 %state, label %end [ + i32 1, label %case1 + i32 2, label %case2 + ] + +case1: + %state.next = phi i32 [ 3, %case2 ], [ 2, %while.cond ] + %add = add nsw i32 %num, %state + br label %while.cond + +case2: + br label %case1 + +end: + ret i32 %num +} Index: llvm/test/Transforms/DFAJumpThreading/dfa-jump-threading-analysis.ll =================================================================== --- /dev/null +++ llvm/test/Transforms/DFAJumpThreading/dfa-jump-threading-analysis.ll @@ -0,0 +1,180 @@ +; REQUIRES: asserts +; RUN: opt -S -dfa-jump-threading -debug-only=dfa-jump-threading -disable-output %s 2>&1 | FileCheck %s + +; This test checks that the analysis identifies all threadable paths in a +; simple CFG. A threadable path includes a list of basic blocks, the exit +; state, and the block that determines the next state. +; < path of BBs that form a cycle > [ state, determinator ] +define i32 @test1(i32 %num) { +; CHECK: < for.body for.inc > [ 1, for.inc ] +; CHECK-NEXT: < for.body case1 for.inc > [ 2, for.inc ] +; CHECK-NEXT: < for.body case2 for.inc > [ 1, for.inc ] +; CHECK-NEXT: < for.body case2 si.unfold.false for.inc > [ 2, for.inc ] +entry: + br label %for.body + +for.body: + %count = phi i32 [ 0, %entry ], [ %inc, %for.inc ] + %state = phi i32 [ 1, %entry ], [ %state.next, %for.inc ] + switch i32 %state, label %for.inc [ + i32 1, label %case1 + i32 2, label %case2 + ] + +case1: + br label %for.inc + +case2: + %cmp = icmp eq i32 %count, 50 + %sel = select i1 %cmp, i32 1, i32 2 + br label %for.inc + +for.inc: + %state.next = phi i32 [ %sel, %case2 ], [ 1, %for.body ], [ 2, %case1 ] + %inc = add nsw i32 %count, 1 + %cmp.exit = icmp slt i32 %inc, %num + br i1 %cmp.exit, label %for.body, label %for.end + +for.end: + ret i32 0 +} + +; This test checks that the analysis finds threadable paths in a more +; complicated CFG. Here the FSM is represented as a nested loop, with +; fallthrough cases. +define i32 @test2(i32 %init) { +; CHECK: < loop.3 case2 > [ 3, loop.3 ] +; CHECK-NEXT: < loop.3 case2 loop.1.backedge loop.1 loop.2 > [ 1, loop.1 ] +; CHECK-NEXT: < loop.3 case2 loop.1.backedge si.unfold.false loop.1 loop.2 > [ 4, loop.1.backedge ] +; CHECK-NEXT: < loop.3 case3 loop.2.backedge loop.2 > [ 0, loop.2.backedge ] +; CHECK-NEXT: < loop.3 case3 case4 loop.2.backedge loop.2 > [ 3, loop.2.backedge ] +; CHECK-NEXT: < loop.3 case3 case4 loop.1.backedge loop.1 loop.2 > [ 1, loop.1 ] +; CHECK-NEXT: < loop.3 case3 case4 loop.1.backedge si.unfold.false loop.1 loop.2 > [ 2, loop.1.backedge ] +; CHECK-NEXT: < loop.3 case4 loop.2.backedge loop.2 > [ 3, loop.2.backedge ] +; CHECK-NEXT: < loop.3 case4 loop.1.backedge loop.1 loop.2 > [ 1, loop.1 ] +; CHECK-NEXT: < loop.3 case4 loop.1.backedge si.unfold.false loop.1 loop.2 > [ 2, loop.1.backedge ] +entry: + %cmp = icmp eq i32 %init, 0 + %sel = select i1 %cmp, i32 0, i32 2 + br label %loop.1 + +loop.1: + %state.1 = phi i32 [ %sel, %entry ], [ %state.1.be2, %loop.1.backedge ] + br label %loop.2 + +loop.2: + %state.2 = phi i32 [ %state.1, %loop.1 ], [ %state.2.be, %loop.2.backedge ] + br label %loop.3 + +loop.3: + %state = phi i32 [ %state.2, %loop.2 ], [ 3, %case2 ] + switch i32 %state, label %infloop.i [ + i32 2, label %case2 + i32 3, label %case3 + i32 4, label %case4 + i32 0, label %case0 + i32 1, label %case1 + ] + +case2: + br i1 %cmp, label %loop.3, label %loop.1.backedge + +case3: + br i1 %cmp, label %loop.2.backedge, label %case4 + +case4: + br i1 %cmp, label %loop.2.backedge, label %loop.1.backedge + +loop.1.backedge: + %state.1.be = phi i32 [ 2, %case4 ], [ 4, %case2 ] + %state.1.be2 = select i1 %cmp, i32 1, i32 %state.1.be + br label %loop.1 + +loop.2.backedge: + %state.2.be = phi i32 [ 3, %case4 ], [ 0, %case3 ] + br label %loop.2 + +case0: + br label %exit + +case1: + br label %exit + +infloop.i: + br label %infloop.i + +exit: + ret i32 0 +} + +declare void @baz() + +; Verify that having the switch block as a determinator is handled correctly. +define i32 @main() { +; CHECK: < bb43 bb59 bb3 bb31 bb41 > [ 77, bb43 ] +; CHECK-NEXT: < bb43 bb49 bb59 bb3 bb31 bb41 > [ 77, bb43 ] +bb: + %i = alloca [420 x i8], align 1 + %i2 = getelementptr inbounds [420 x i8], [420 x i8]* %i, i64 0, i64 390 + br label %bb3 + +bb3: ; preds = %bb59, %bb + %i4 = phi i8* [ %i2, %bb ], [ %i60, %bb59 ] + %i5 = phi i8 [ 77, %bb ], [ %i64, %bb59 ] + %i6 = phi i32 [ 2, %bb ], [ %i63, %bb59 ] + %i7 = phi i32 [ 26, %bb ], [ %i62, %bb59 ] + %i8 = phi i32 [ 25, %bb ], [ %i61, %bb59 ] + %i9 = icmp sgt i32 %i7, 2 + %i10 = select i1 %i9, i32 %i7, i32 2 + %i11 = add i32 %i8, 2 + %i12 = sub i32 %i11, %i10 + %i13 = mul nsw i32 %i12, 3 + %i14 = add nsw i32 %i13, %i6 + %i15 = sext i32 %i14 to i64 + %i16 = getelementptr inbounds i8, i8* %i4, i64 %i15 + %i17 = load i8, i8* %i16, align 1 + %i18 = icmp sgt i8 %i17, 0 + br i1 %i18, label %bb21, label %bb31 + +bb21: ; preds = %bb3 + br i1 true, label %bb59, label %bb43 + +bb59: ; preds = %bb49, %bb43, %bb31, %bb21 + %i60 = phi i8* [ %i44, %bb49 ], [ %i44, %bb43 ], [ %i34, %bb31 ], [ %i4, %bb21 ] + %i61 = phi i32 [ %i45, %bb49 ], [ %i45, %bb43 ], [ %i33, %bb31 ], [ %i8, %bb21 ] + %i62 = phi i32 [ %i47, %bb49 ], [ %i47, %bb43 ], [ %i32, %bb31 ], [ %i7, %bb21 ] + %i63 = phi i32 [ %i48, %bb49 ], [ %i48, %bb43 ], [ 2, %bb31 ], [ %i6, %bb21 ] + %i64 = phi i8 [ %i46, %bb49 ], [ %i46, %bb43 ], [ 77, %bb31 ], [ %i5, %bb21 ] + %i65 = icmp sgt i32 %i62, 0 + br i1 %i65, label %bb3, label %bb66 + +bb31: ; preds = %bb3 + %i32 = add nsw i32 %i7, -1 + %i33 = add nsw i32 %i8, -1 + %i34 = getelementptr inbounds i8, i8* %i4, i64 -15 + %i35 = icmp eq i8 %i5, 77 + br i1 %i35, label %bb59, label %bb41 + +bb41: ; preds = %bb31 + tail call void @baz() + br label %bb43 + +bb43: ; preds = %bb41, %bb21 + %i44 = phi i8* [ %i34, %bb41 ], [ %i4, %bb21 ] + %i45 = phi i32 [ %i33, %bb41 ], [ %i8, %bb21 ] + %i46 = phi i8 [ 77, %bb41 ], [ %i5, %bb21 ] + %i47 = phi i32 [ %i32, %bb41 ], [ %i7, %bb21 ] + %i48 = phi i32 [ 2, %bb41 ], [ %i6, %bb21 ] + tail call void @baz() + switch i8 %i5, label %bb59 [ + i8 68, label %bb49 + i8 73, label %bb49 + ] + +bb49: ; preds = %bb43, %bb43 + tail call void @baz() + br label %bb59 + +bb66: ; preds = %bb59 + ret i32 0 +} Index: llvm/test/Transforms/DFAJumpThreading/dfa-jump-threading-transform.ll =================================================================== --- /dev/null +++ llvm/test/Transforms/DFAJumpThreading/dfa-jump-threading-transform.ll @@ -0,0 +1,175 @@ +; RUN: opt -S -dfa-jump-threading %s | FileCheck %s + +; These tests check that the DFA jump threading transformation is applied +; properly to two CFGs. It checks that blocks are cloned, branches are updated, +; and SSA form is restored. +define i32 @test1(i32 %num) { +entry: + br label %for.body + +for.body: + %count = phi i32 [ 0, %entry ], [ %inc, %for.inc ] + %state = phi i32 [ 1, %entry ], [ %state.next, %for.inc ] + switch i32 %state, label %for.inc [ + i32 1, label %case1 + i32 2, label %case2 + ] + +; CHECK: for.body.jt2: +; CHECK-NEXT: %count.jt2 +; CHECK-NEXT: %state.jt2 +; CHECK-NEXT: br label %case2 + +; CHECK: for.body.jt1: +; CHECK-NEXT: %count.jt1 +; CHECK-NEXT: %state.jt1 +; CHECK-NEXT: br label %case1 + +case1: + br label %for.inc + +; CHECK: case1: +; CHECK-NEXT: %count2 = phi i32 [ %count.jt1, %for.body.jt1 ], [ %count, %for.body ] + +case2: + %cmp = icmp eq i32 %count, 50 + %sel = select i1 %cmp, i32 1, i32 2 + br label %for.inc + +; CHECK: case2: +; CHECK-NEXT: %count1 = phi i32 [ %count.jt2, %for.body.jt2 ], [ %count, %for.body ] +; CHECK: br i1 %cmp, label %for.inc.jt1, label %si.unfold.false + +; CHECK: si.unfold.false: +; CHECK-NEXT: br label %for.inc.jt2 + +for.inc: + %state.next = phi i32 [ %sel, %case2 ], [ 1, %for.body ], [ 2, %case1 ] + %inc = add nsw i32 %count, 1 + %cmp.exit = icmp slt i32 %inc, %num + br i1 %cmp.exit, label %for.body, label %for.end + +; CHECK: for.inc.jt2: +; CHECK-NEXT: %count4 = phi i32 [ %count1, %si.unfold.false ], [ %count2, %case1 ] +; CHECK: br i1 %cmp.exit.jt2, label %for.body.jt2, label %for.end + +; CHECK: for.inc.jt1: +; CHECK-NEXT: %count3 = phi i32 [ %count1, %case2 ], [ %count, %for.body ] +; CHECK: br i1 %cmp.exit.jt1, label %for.body.jt1, label %for.end + +for.end: + ret i32 0 +} + + +define i32 @test2(i32 %init) { +entry: + %cmp = icmp eq i32 %init, 0 + %sel = select i1 %cmp, i32 0, i32 2 + br label %loop.1 + +; CHECK: si.unfold.false: +; CHECK-NEXT: br label %loop.1 +; CHECK: si.unfold.false.jt2: +; CHECK-NEXT: br label %loop.1.jt2 +; CHECK: si.unfold.false.jt4: +; CHECK-NEXT: br label %loop.1.jt4 +; CHECK: si.unfold.false1: +; CHECK-NEXT: br label %loop.1 + +loop.1: + %state.1 = phi i32 [ %sel, %entry ], [ %state.1.be2, %loop.1.backedge ] + br label %loop.2 + +; CHECK: loop.1.jt2: +; CHECK :br label %loop.2.jt2 +; CHECK: loop.1.jt4: +; CHECK: br label %loop.2.jt4 +; CHECK: loop.1.jt1: +; CHECK: br label %loop.2.jt1 + +loop.2: + %state.2 = phi i32 [ %state.1, %loop.1 ], [ %state.2.be, %loop.2.backedge ] + br label %loop.3 + +; CHECK: loop.2.jt2: +; CHECK: br label %loop.3.jt2 +; CHECK: loop.2.jt3: +; CHECK: br label %loop.3.jt3 +; CHECK: loop.2.jt0: +; CHECK: br label %loop.3.jt0 +; CHECK: loop.2.jt4: +; CHECK: br label %loop.3.jt4 +; CHECK: loop.2.jt1: +; CHECK: br label %loop.3.jt1 + +loop.3: + %state = phi i32 [ %state.2, %loop.2 ], [ 3, %case2 ] + switch i32 %state, label %infloop.i [ + i32 2, label %case2 + i32 3, label %case3 + i32 4, label %case4 + i32 0, label %case0 + i32 1, label %case1 + ] + +; CHECK: loop.3.jt2: +; CHECK: br label %case2 +; CHECK: loop.3.jt0: +; CHECK: br label %case0 +; CHECK: loop.3.jt4: +; CHECK: br label %case4 +; CHECK: loop.3.jt1: +; CHECK: br label %case1 +; CHECK: loop.3.jt3: +; CHECK: br label %case3 + +case2: + br i1 %cmp, label %loop.3, label %loop.1.backedge + +; CHECK: case2: +; CHECK-NEXT: br i1 %cmp, label %loop.3.jt3, label %loop.1.backedge.jt4 + +case3: + br i1 %cmp, label %loop.2.backedge, label %case4 + +; CHECK: case3: +; CHECK-NEXT: br i1 %cmp, label %loop.2.backedge.jt0, label %case4 + +case4: + br i1 %cmp, label %loop.2.backedge, label %loop.1.backedge + +; CHECK: case4: +; CHECK-NEXT: br i1 %cmp, label %loop.2.backedge.jt3, label %loop.1.backedge.jt2 + +loop.1.backedge: + %state.1.be = phi i32 [ 2, %case4 ], [ 4, %case2 ] + %state.1.be2 = select i1 %cmp, i32 1, i32 %state.1.be + br label %loop.1 + +; CHECK: loop.1.backedge.jt2: +; CHECK: br i1 %cmp, label %loop.1.jt1, label %si.unfold.false.jt2 +; CHECK: loop.1.backedge.jt4: +; CHECK: br i1 %cmp, label %loop.1.jt1, label %si.unfold.false.jt4 + +loop.2.backedge: + %state.2.be = phi i32 [ 3, %case4 ], [ 0, %case3 ] + br label %loop.2 + +; CHECK: loop.2.backedge.jt3: +; CHECK: br label %loop.2.jt3 +; CHECK: loop.2.backedge.jt0: +; CHECK: br label %loop.2.jt0 + +case0: + br label %exit + +case1: + br label %exit + +infloop.i: + br label %infloop.i + +exit: + ret i32 0 +} Index: llvm/test/Transforms/DFAJumpThreading/dfa-unfold-select.ll =================================================================== --- /dev/null +++ llvm/test/Transforms/DFAJumpThreading/dfa-unfold-select.ll @@ -0,0 +1,164 @@ +; RUN: opt -S -dfa-jump-threading %s | FileCheck %s + +; These tests check if selects are unfolded properly for jump threading +; opportunities. There are three different patterns to consider: +; 1) Both operands are constant and the false branch is unfolded by default +; 2) One operand is constant and the other is another select to be unfolded. In +; this case a single select is sunk to a new block to unfold. +; 3) Both operands are a select, and both should be sunk to new blocks. +define i32 @test1(i32 %num) { +entry: + br label %for.body + +for.body: + %count = phi i32 [ 0, %entry ], [ %inc, %for.inc ] + %state = phi i32 [ 1, %entry ], [ %state.next, %for.inc ] + switch i32 %state, label %for.inc [ + i32 1, label %case1 + i32 2, label %case2 + ] + +case1: + br label %for.inc + +case2: +; CHECK: %cmp = icmp +; CHECK-NOT: %sel = select i1 %cmp, i32 1, i32 2 +; CHECK-NEXT: br i1 %cmp, label %for.inc +; CHECK: si.unfold.false + %cmp = icmp slt i32 %count, 50 + %sel = select i1 %cmp, i32 1, i32 2 + br label %for.inc + +; CHECK: si.unfold.false: +; CHECK-NEXT: br label %for.inc + +for.inc: +; CHECK: for.inc.jt2: +; CHECK: %state.next.jt2 = phi i32 +; CHECK: [ 2, %si.unfold.false ] +; CHECK: for.inc.jt1: +; CHECK: %state.next.jt1 = phi i32 +; CHECK: [ 1, %case2 ] + %state.next = phi i32 [ %sel, %case2 ], [ 1, %for.body ], [ 2, %case1 ] + %inc = add nsw i32 %count, 1 + %cmp.exit = icmp slt i32 %inc, %num + br i1 %cmp.exit, label %for.body, label %for.end + +for.end: + ret i32 0 +} + +define i32 @test2(i32 %num) { +entry: + br label %for.body + +for.body: + %count = phi i32 [ 0, %entry ], [ %inc, %for.inc ] + %state = phi i32 [ 1, %entry ], [ %state.next, %for.inc ] + switch i32 %state, label %for.inc [ + i32 1, label %case1 + i32 2, label %case2 + ] + +case1: +; CHECK: %cmp.c1 = icmp +; CHECK: %cmp2.c1 = icmp +; CHECK-NOT: select i1 +; CHECK-NEXT: br i1 %cmp2.c1 + %cmp.c1 = icmp slt i32 %count, 50 + %cmp2.c1 = icmp slt i32 %count, 100 + %state1.1 = select i1 %cmp.c1, i32 1, i32 2 + %state1.2 = select i1 %cmp2.c1, i32 %state1.1, i32 3 + br label %for.inc + +case2: +; CHECK: %cmp.c2 = icmp +; CHECK: %cmp2.c2 = icmp +; CHECK-NOT: select i1 +; CHECK-NEXT: br i1 %cmp2.c2 + %cmp.c2 = icmp slt i32 %count, 50 + %cmp2.c2 = icmp sgt i32 %count, 100 + %state2.1 = select i1 %cmp.c2, i32 1, i32 2 + %state2.2 = select i1 %cmp2.c2, i32 3, i32 %state2.1 + br label %for.inc + +; CHECK: si.unfold.false: +; CHECK-NEXT: br i1 %cmp.c2 +; CHECK: si.unfold.false1: +; CHECK-NEXT: br label %for.inc +; CHECK: si.unfold.true: +; CHECK-NEXT: br i1 %cmp.c1 +; CHECK: si.unfold.false2: +; CHECK-NEXT: br label %for.inc + +for.inc: +; CHECK: for.inc.jt3: +; CHECK: [ 3, %case1 ], [ 3, %case2 ] +; CHECK: for.inc.jt2: +; CHECK: [ 2, %si.unfold.false1 ], [ 2, %si.unfold.false2 ] +; CHECK: for.inc.jt1: +; CHECK: [ 1, %si.unfold.false ], [ 1, %si.unfold.true ] + %state.next = phi i32 [ %state1.2, %case1 ], [ %state2.2, %case2 ], [ 1, %for.body ] + %inc = add nsw i32 %count, 1 + %cmp.exit = icmp slt i32 %inc, %num + br i1 %cmp.exit, label %for.body, label %for.end + +for.end: + ret i32 0 +} + +define i32 @test3(i32 %num) { +entry: + br label %for.body + +for.body: + %count = phi i32 [ 0, %entry ], [ %inc, %for.inc ] + %state = phi i32 [ 1, %entry ], [ %state.next, %for.inc ] + switch i32 %state, label %for.inc [ + i32 1, label %case1 + i32 2, label %case2 + ] + +case1: + br label %for.inc + +case2: +; CHECK: %cmp.3 = icmp eq i32 %0, 0 +; CHECK-NOT: select i1 +; CHECK-NEXT: br i1 %cmp.3, label %si.unfold.true, label %si.unfold.false + %cmp.1 = icmp slt i32 %count, 50 + %cmp.2 = icmp slt i32 %count, 100 + %0 = and i32 %count, 1 + %cmp.3 = icmp eq i32 %0, 0 + %sel.1 = select i1 %cmp.1, i32 1, i32 2 + %sel.2 = select i1 %cmp.2, i32 3, i32 4 + %sel.3 = select i1 %cmp.3, i32 %sel.1, i32 %sel.2 + br label %for.inc + +; CHECK: si.unfold.true: +; CHECK-NEXT: br i1 %cmp.1 +; CHECK: si.unfold.false: +; CHECK-NEXT: br i1 %cmp.2 +; CHECK: si.unfold.false1: +; CHECK-NEXT: br label %for.inc +; CHECK: si.unfold.false2: +; CHECK-NEXT: br label %for.inc + +for.inc: +; CHECK: for.inc.jt4: +; CHECK: [ 4, %si.unfold.false1 ] +; CHECK: for.inc.jt3: +; CHECK: [ 3, %si.unfold.false ] +; CHECK: for.inc.jt2: +; CHECK: [ 2, %si.unfold.false2 ] +; CHECK: for.inc.jt1: +; CHECK: [ 1, %si.unfold.true ] + %state.next = phi i32 [ %sel.3, %case2 ], [ 1, %for.body ], [ 2, %case1 ] + %inc = add nsw i32 %count, 1 + %cmp.exit = icmp slt i32 %inc, %num + br i1 %cmp.exit, label %for.body, label %for.end + +for.end: + ret i32 0 +}