//===- CFGToSCF.h - Control Flow Graph to Structured Control Flow *- C++ -*===// // // 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 code is an implementation of: // Helge Bahmann, Nico Reissmann, Magnus Jahre, and Jan Christian Meyer. 2015. // Perfect Reconstructability of Control Flow from Demand Dependence Graphs. ACM // Trans. Archit. Code Optim. 11, 4, Article 66 (January 2015), 25 pages. // https://doi.org/10.1145/2693261 // // It defines an algorithm to translate any control flow graph with a single // entry and single exit block into structured control flow operations // consisting of regions of do-while loops and operations conditionally // dispatching to one out of multiple regions before continuing after the // operation. This includes control flow graphs containing irreducible // control flow. // // The implementation here additionally supports the transformation on // regions with multiple exit blocks. This is implemented by first // transforming all occurrences of return-like operations to branch to a // single exit block containing an instance of that return-like operation. // If there are multiple kinds of return-like operations, multiple exit // blocks are created. In that case the transformation leaves behind a // conditional control flow graph operation that dispatches to the given regions // terminating with different kinds of return-like operations each. // // If the function only contains a single kind of return-like operations, // it is guaranteed that all control flow graph ops will be lifted to structured // control flow, and that no more control flow graph ops remain after the // operation. // // The algorithm to lift CFGs consists of two transformations applied after each // other on any single-entry, single-exit region: // 1) Lifting cycles to structured control flow loops // 2) Lifting conditional branches to structured control flow branches // These are then applied recursively on any new single-entry single-exit // regions created by the transformation until no more CFG operations remain. // // The first part of cycle lifting is to detect any cycles in the CFG. // This is done using an algorithm for iterating over SCCs. Every SCC // representing a cycle is then transformed into a structured loop with a single // entry block and a single latch containing the only back edge to the entry // block and the only edge to an exit block outside the loop. Rerouting control // flow to create single entry and exit blocks is achieved via a multiplexer // construct that can be visualized as follows: // +-----+ +-----+ +-----+ // | bb0 | | bb1 |...| bbN | // +--+--+ +--+--+ +-+---+ // | | | // | v | // | +------+ | // | ++ ++<----+ // | | Region | // +>| |<----+ // ++ ++ | // +------+------+ // // The above transforms to: // +-----+ +-----+ +-----+ // | bb0 | | bb1 |...| bbN | // +-----+ +--|--+ ++----+ // | v | // +->+-----+<---+ // | bbM |<-------+ // +---+-+ | // +---+ | +----+ | // | v | | // | +------+ | | // | ++ ++<-+ | // +->| Region | | // ++ ++ | // +------+-------+ // // bbM in the above is the multiplexer block, and any block previously branching // to an entry block of the region are redirected to it. This includes any // branches from within the region. Using a block argument, bbM then dispatches // to the correct entry block of the region dependent on the predecessor. // // A similar transformation is done to create the latch block with the single // back edge and loop exit edge. // // The above form has the advantage that bbM now acts as the loop header // of the loop body. After the transformation on the latch, this results in a // structured loop that can then be lifted to structured control flow. The // conditional branches created in bbM are later lifted to conditional // branches. // // Lifting conditional branches is done by analyzing the *first* conditional // branch encountered in the entry region. The algorithm then identifies // all blocks that are dominated by a specific control flow edge and // the region where control flow continues: // +-----+ // +-----+ bb0 +----+ // v +-----+ v // Region 1 +-+-+ ... +-+-+ Region n // +---+ +---+ // ... ... // | | // | +---+ | // +---->++ ++<---+ // | | // ++ ++ Region T // +---+ // Every region following bb0 consists of 0 or more blocks that eventually // branch to Region T. If there are multiple entry blocks into Region T, a // single entry block is created using a multiplexer block as shown above. // Region 1 to Region n are then lifted together with the conditional control // flow operation terminating bb0 into a structured conditional operation // followed by the operations of the entry block of Region T. //===----------------------------------------------------------------------===// #include "mlir/Transforms/CFGToSCF.h" #include "mlir/IR/RegionGraphTraits.h" #include "mlir/Interfaces/ControlFlowInterfaces.h" #include "mlir/Interfaces/SideEffectInterfaces.h" #include "llvm/ADT/DepthFirstIterator.h" #include "llvm/ADT/MapVector.h" #include "llvm/ADT/SCCIterator.h" #include "llvm/ADT/SetVector.h" #include "llvm/ADT/SmallPtrSet.h" using namespace mlir; /// Returns the mutable operand range used to transfer operands from `block` to /// its successor with the given index. The returned range being mutable allows /// us to modify the operands being transferred. static MutableOperandRange getMutableSuccessorOperands(Block *block, unsigned successorIndex) { auto branchOpInterface = cast(block->getTerminator()); SuccessorOperands succOps = branchOpInterface.getSuccessorOperands(successorIndex); return succOps.getMutableForwardedOperands(); } /// Return the operand range used to transfer operands from `block` to its /// successor with the given index. static OperandRange getSuccessorOperands(Block *block, unsigned successorIndex) { return getMutableSuccessorOperands(block, successorIndex); } /// Appends all the block arguments from `other` to the block arguments of /// `block`, copying their types and locations. static void addBlockArgumentsFromOther(Block *block, Block *other) { for (BlockArgument arg : other->getArguments()) block->addArgument(arg.getType(), arg.getLoc()); } namespace { /// Class representing an edge in the CFG. Consists of a from-block, a successor /// and corresponding successor operands passed to the block arguments of the /// successor. class Edge { Block *fromBlock; unsigned successorIndex; public: /// Constructs a new edge from `fromBlock` to the successor corresponding to /// `successorIndex`. Edge(Block *fromBlock, unsigned int successorIndex) : fromBlock(fromBlock), successorIndex(successorIndex) {} /// Returns the from-block. Block *getFromBlock() const { return fromBlock; } /// Returns the successor of the edge. Block *getSuccessor() const { return fromBlock->getSuccessor(successorIndex); } /// Sets the successor of the edge, adjusting the terminator in the /// from-block. void setSuccessor(Block *block) const { fromBlock->getTerminator()->setSuccessor(block, successorIndex); } /// Returns the arguments of this edge that are passed to the block arguments /// of the successor. MutableOperandRange getMutableSuccessorOperands() const { return ::getMutableSuccessorOperands(fromBlock, successorIndex); } /// Returns the arguments of this edge that are passed to the block arguments /// of the successor. OperandRange getSuccessorOperands() const { return ::getSuccessorOperands(fromBlock, successorIndex); } }; /// Structure containing the entry, exit and back edges of a cycle. A cycle is a /// generalization of a loop that may have multiple entry edges. See also /// https://llvm.org/docs/CycleTerminology.html. struct CycleEdges { /// All edges from a block outside the cycle to a block inside the cycle. /// The targets of these edges are entry blocks. SmallVector entryEdges; /// All edges from a block inside the cycle to a block outside the cycle. SmallVector exitEdges; /// All edges from a block inside the cycle to an entry block. SmallVector backEdges; }; /// Class used to orchestrate creation of so-called edge multiplexers. /// This class creates a new basic block and routes all inputs edges /// to this basic block before branching to their original target. /// The purpose of this transformation is to create single-entry, /// single-exit regions. class EdgeMultiplexer { public: /// Creates a new edge multiplexer capable of redirecting all edges to one of /// the `entryBlocks`. This creates the multiplexer basic block with /// appropriate block arguments after the first entry block. `extraArgs` /// contains the types of possible extra block arguments passed to the /// multiplexer block that are added to the successor operands of every /// outgoing edge. /// /// NOTE: This does not yet redirect edges to branch to the /// multiplexer block nor code dispatching from the multiplexer code /// to the original successors. /// See `redirectEdge` and `createSwitch`. static EdgeMultiplexer create(Location loc, ArrayRef entryBlocks, function_ref getSwitchValue, function_ref getUndefValue, TypeRange extraArgs = {}) { assert(!entryBlocks.empty() && "Require at least one entry block"); auto *multiplexerBlock = new Block; multiplexerBlock->insertAfter(entryBlocks.front()); // To implement the multiplexer block, we have to add the block arguments of // every distinct successor block to the multiplexer block. When redirecting // edges, block arguments designated for blocks that aren't branched to will // be assigned the `getUndefValue`. The amount of block arguments and their // offset is saved in the map for `redirectEdge` to transform the edges. llvm::SmallMapVector blockArgMapping; for (Block *entryBlock : entryBlocks) { auto [iter, inserted] = blockArgMapping.insert( {entryBlock, multiplexerBlock->getNumArguments()}); if (inserted) addBlockArgumentsFromOther(multiplexerBlock, entryBlock); } // If we have more than one successor, we have to additionally add a // discriminator value, denoting which successor to jump to. // When redirecting edges, an appropriate value will be passed using // `getSwitchValue`. Value discriminator; if (blockArgMapping.size() > 1) discriminator = multiplexerBlock->addArgument(getSwitchValue(0).getType(), loc); multiplexerBlock->addArguments( extraArgs, SmallVector(extraArgs.size(), loc)); return EdgeMultiplexer(multiplexerBlock, getSwitchValue, getUndefValue, std::move(blockArgMapping), discriminator); } /// Returns the created multiplexer block. Block *getMultiplexerBlock() const { return multiplexerBlock; } /// Redirects `edge` to branch to the multiplexer block before continuing to /// its original target. The edges successor must have originally been part /// of the entry blocks array passed to the `create` function. `extraArgs` /// must be used to pass along any additional values corresponding to /// `extraArgs` in `create`. void redirectEdge(Edge edge, ValueRange extraArgs = {}) const { const auto *result = blockArgMapping.find(edge.getSuccessor()); assert(result != blockArgMapping.end() && "Edge was not originally passed to `create` method."); MutableOperandRange successorOperands = edge.getMutableSuccessorOperands(); // Extra arguments are always appended at the end of the block arguments. unsigned extraArgsBeginIndex = multiplexerBlock->getNumArguments() - extraArgs.size(); // If a discriminator exists, it is right before the extra arguments. std::optional discriminatorIndex = discriminator ? extraArgsBeginIndex - 1 : std::optional{}; SmallVector newSuccOperands(multiplexerBlock->getNumArguments()); for (BlockArgument argument : multiplexerBlock->getArguments()) { unsigned index = argument.getArgNumber(); if (index >= result->second && index < result->second + edge.getSuccessor()->getNumArguments()) { // Original block arguments to the entry block. newSuccOperands[index] = successorOperands[index - result->second].get(); continue; } // Discriminator value if it exists. if (index == discriminatorIndex) { newSuccOperands[index] = getSwitchValue(result - blockArgMapping.begin()); continue; } // Followed by the extra arguments. if (index >= extraArgsBeginIndex) { newSuccOperands[index] = extraArgs[index - extraArgsBeginIndex]; continue; } // Otherwise undef values for any unused block arguments used by other // entry blocks. newSuccOperands[index] = getUndefValue(argument.getType()); } edge.setSuccessor(multiplexerBlock); successorOperands.assign(newSuccOperands); } /// Creates a switch op using `builder` which dispatches to the original /// successors of the edges passed to `create` minus the ones in `excluded`. /// The builder's insertion point has to be in a block dominated by the /// multiplexer block. All edges to the multiplexer block must have already /// been redirected using `redirectEdge`. void createSwitch( Location loc, OpBuilder &builder, CFGToSCFInterface &interface, const SmallPtrSetImpl &excluded = SmallPtrSet{}) { // We create the switch by creating a case for all entries and then // splitting of the last entry as a default case. SmallVector caseArguments; SmallVector caseValues; SmallVector caseDestinations; for (auto &&[index, pair] : llvm::enumerate(blockArgMapping)) { auto &&[succ, offset] = pair; if (excluded.contains(succ)) continue; caseValues.push_back(index); caseArguments.push_back(multiplexerBlock->getArguments().slice( offset, succ->getNumArguments())); caseDestinations.push_back(succ); } // If we don't have a discriminator due to only having one entry we have to // create a dummy flag for the switch. Value realDiscriminator = discriminator; if (!realDiscriminator || caseArguments.size() == 1) realDiscriminator = getSwitchValue(0); caseValues.pop_back(); Block *defaultDest = caseDestinations.pop_back_val(); ValueRange defaultArgs = caseArguments.pop_back_val(); assert(!builder.getInsertionBlock()->hasNoPredecessors() && "Edges need to be redirected prior to creating switch."); interface.createCFGSwitchOp(loc, builder, realDiscriminator, caseValues, caseDestinations, caseArguments, defaultDest, defaultArgs); } private: /// Newly created multiplexer block. Block *multiplexerBlock; /// Callback used to create a constant suitable as flag for /// the interfaces `createCFGSwitchOp`. function_ref getSwitchValue; /// Callback used to create undefined values of a given type. function_ref getUndefValue; /// Mapping of the block arguments of an entry block to the corresponding /// block arguments in the multiplexer block. Block arguments of an entry /// block are simply appended ot the multiplexer block. This map simply /// contains the offset to the range in the multiplexer block. llvm::SmallMapVector blockArgMapping; /// Discriminator value used in the multiplexer block to dispatch to the /// correct entry block. Null value if not required due to only having one /// entry block. Value discriminator; EdgeMultiplexer(Block *multiplexerBlock, function_ref getSwitchValue, function_ref getUndefValue, llvm::SmallMapVector &&entries, Value dispatchFlag) : multiplexerBlock(multiplexerBlock), getSwitchValue(getSwitchValue), getUndefValue(getUndefValue), blockArgMapping(std::move(entries)), discriminator(dispatchFlag) {} }; /// Alternative implementation of DenseMapInfo using the operation /// equivalence infrastructure to check whether two 'return-like' operations are /// equivalent in the context of this transformation. This means that both /// operations are of the same kind, have the same amount of operands and types /// and the same attributes and properties. The operands themselves don't have /// to be equivalent. struct ReturnLikeOpEquivalence : public llvm::DenseMapInfo { static unsigned getHashValue(const Operation *opC) { return OperationEquivalence::computeHash( const_cast(opC), /*hashOperands=*/OperationEquivalence::ignoreHashValue, /*hashResults=*/OperationEquivalence::ignoreHashValue, OperationEquivalence::IgnoreLocations); } static bool isEqual(const Operation *lhs, const Operation *rhs) { if (lhs == rhs) return true; if (lhs == getTombstoneKey() || lhs == getEmptyKey() || rhs == getTombstoneKey() || rhs == getEmptyKey()) return false; return OperationEquivalence::isEquivalentTo( const_cast(lhs), const_cast(rhs), OperationEquivalence::ignoreValueEquivalence, nullptr, OperationEquivalence::IgnoreLocations); } }; /// Utility-class for transforming a region to only have one single block for /// every return-like operation. class ReturnLikeExitCombiner { public: ReturnLikeExitCombiner(Region &topLevelRegion, CFGToSCFInterface &interface) : topLevelRegion(topLevelRegion), interface(interface) {} /// Transforms `returnLikeOp` to a branch to the only block in the /// region with an instance of `returnLikeOp`s kind. void combineExit(Operation *returnLikeOp, function_ref getSwitchValue) { auto [iter, inserted] = returnLikeToCombinedExit.insert({returnLikeOp, nullptr}); if (!inserted && iter->first == returnLikeOp) return; Block *exitBlock = iter->second; if (inserted) { exitBlock = new Block; iter->second = exitBlock; topLevelRegion.push_back(exitBlock); exitBlock->addArguments( returnLikeOp->getOperandTypes(), SmallVector(returnLikeOp->getNumOperands(), returnLikeOp->getLoc())); } auto builder = OpBuilder::atBlockTerminator(returnLikeOp->getBlock()); interface.createSingleDestinationBranch(returnLikeOp->getLoc(), builder, getSwitchValue(0), exitBlock, returnLikeOp->getOperands()); if (!inserted) { returnLikeOp->erase(); return; } returnLikeOp->moveBefore(exitBlock, exitBlock->end()); returnLikeOp->setOperands(exitBlock->getArguments()); } private: /// Mapping of return-like operation to block. All return-like operations /// of the same kind with the same attributes, properties and types are seen /// as equivalent. First occurrence seen is kept in the map. llvm::SmallDenseMap returnLikeToCombinedExit; Region &topLevelRegion; CFGToSCFInterface &interface; }; } // namespace /// Returns a range of all edges from `block` to each of its successors. static auto successorEdges(Block *block) { return llvm::map_range(llvm::seq(block->getNumSuccessors()), [=](unsigned index) { return Edge(block, index); }); } /// Calculates entry, exit and back edges of the given cycle. static CycleEdges calculateCycleEdges(const llvm::SmallSetVector &cycles) { CycleEdges result; SmallPtrSet entryBlocks; // First identify all exit and entry edges by checking whether any successors // or predecessors are from outside the cycles. for (Block *block : cycles) { for (auto pred = block->pred_begin(); pred != block->pred_end(); pred++) { if (cycles.contains(*pred)) continue; result.entryEdges.emplace_back(*pred, pred.getSuccessorIndex()); entryBlocks.insert(block); } for (auto &&[succIndex, succ] : llvm::enumerate(block->getSuccessors())) { if (cycles.contains(succ)) continue; result.exitEdges.emplace_back(block, succIndex); } } // With the entry blocks identified, find all the back edges. for (Block *block : cycles) { for (auto &&[succIndex, succ] : llvm::enumerate(block->getSuccessors())) { if (!entryBlocks.contains(succ)) continue; result.backEdges.emplace_back(block, succIndex); } } return result; } /// Creates a single entry block out of multiple entry edges using an edge /// multiplexer and returns it. static EdgeMultiplexer createSingleEntryBlock(Location loc, ArrayRef entryEdges, function_ref getSwitchValue, function_ref getUndefValue, CFGToSCFInterface &interface) { auto result = EdgeMultiplexer::create( loc, llvm::map_to_vector(entryEdges, std::mem_fn(&Edge::getSuccessor)), getSwitchValue, getUndefValue); // Redirect the edges prior to creating the switch op. // We guarantee that predecessors are up to date. for (Edge edge : entryEdges) result.redirectEdge(edge); auto builder = OpBuilder::atBlockBegin(result.getMultiplexerBlock()); result.createSwitch(loc, builder, interface); return result; } namespace { /// Special loop properties of a structured loop. /// A structured loop is a loop satisfying all of the following: /// * Has at most one entry, one exit and one back edge. /// * The back edge originates from the same block as the exit edge. struct StructuredLoopProperties { /// Block containing both the single exit edge and the single back edge. Block *latch; /// Loop condition of type equal to a value returned by `getSwitchValue`. Value condition; /// Exit block which is the only successor of the loop. Block *exitBlock; }; } // namespace /// Transforms a loop into a structured loop with only a single back edge and /// exiting edge, originating from the same block. static FailureOr createSingleExitingLatch( Location loc, ArrayRef backEdges, ArrayRef exitEdges, function_ref getSwitchValue, function_ref getUndefValue, CFGToSCFInterface &interface, ReturnLikeExitCombiner &exitCombiner) { assert(llvm::all_equal( llvm::map_range(backEdges, std::mem_fn(&Edge::getSuccessor))) && "All repetition edges must lead to the single loop header"); // First create the multiplexer block, which will be our latch, for all back // edges and exit edges. We pass an additional argument to the multiplexer // block which indicates whether the latch was reached from what was // originally a back edge or an exit block. // This is later used to branch using the new only back edge. SmallVector successors; llvm::append_range( successors, llvm::map_range(backEdges, std::mem_fn(&Edge::getSuccessor))); llvm::append_range( successors, llvm::map_range(exitEdges, std::mem_fn(&Edge::getSuccessor))); auto multiplexer = EdgeMultiplexer::create(loc, successors, getSwitchValue, getUndefValue, /*extraArgs=*/getSwitchValue(0).getType()); auto *latchBlock = multiplexer.getMultiplexerBlock(); // Create a separate exit block that comes right after the latch. auto *exitBlock = new Block; exitBlock->insertAfter(latchBlock); // Since this is a loop, all back edges point to the same loop header. Block *loopHeader = backEdges.front().getSuccessor(); // Redirect the edges prior to creating the switch op. // We guarantee that predecessors are up to date. // Redirecting back edges with `shouldRepeat` as 1. for (Edge backEdge : backEdges) multiplexer.redirectEdge(backEdge, /*extraArgs=*/getSwitchValue(1)); // Redirecting exits edges with `shouldRepeat` as 0. for (Edge exitEdge : exitEdges) multiplexer.redirectEdge(exitEdge, /*extraArgs=*/getSwitchValue(0)); // Create the new only back edge to the loop header. Branch to the // exit block otherwise. Value shouldRepeat = latchBlock->getArguments().back(); { auto builder = OpBuilder::atBlockBegin(latchBlock); interface.createConditionalBranch( loc, builder, shouldRepeat, loopHeader, latchBlock->getArguments().take_front(loopHeader->getNumArguments()), /*falseDest=*/exitBlock, /*falseArgs=*/{}); } { auto builder = OpBuilder::atBlockBegin(exitBlock); if (!exitEdges.empty()) { // Create the switch dispatching to what were originally the multiple exit // blocks. The loop header has to explicitly be excluded in the below // switch as we would otherwise be creating a new loop again. All back // edges leading to the loop header have already been handled in the // switch above. The remaining edges can only jump to blocks outside the // loop. SmallPtrSet excluded = {loopHeader}; multiplexer.createSwitch(loc, builder, interface, excluded); } else { // A loop without an exit edge is a statically known infinite loop. // Since structured control flow ops are not terminator ops, the caller // has to create a fitting return-like unreachable terminator operation. FailureOr terminator = interface.createUnreachableTerminator( loc, builder, *latchBlock->getParent()); if (failed(terminator)) return failure(); // Transform the just created transform operation in the case that an // occurrence of it existed in input IR. exitCombiner.combineExit(*terminator, getSwitchValue); } } return StructuredLoopProperties{latchBlock, /*condition=*/shouldRepeat, exitBlock}; } /// Transforms a structured loop into a loop in reduce form. /// /// Reduce form is defined as a structured loop where: /// (0) No values defined within the loop body are used outside the loop body. /// (1) The block arguments and successor operands of the exit block are equal /// to the block arguments of the loop header and the successor operands /// of the back edge. /// /// This is required for many structured control flow ops as they tend /// to not have separate "loop result arguments" and "loop iteration arguments" /// at the end of the block. Rather, the "loop iteration arguments" from the /// last iteration are the result of the loop. /// /// Note that the requirement of (0) is shared with LCSSA form in LLVM. However, /// due to this being a structured loop instead of a general loop, we do not /// require complicated dominance algorithms nor SSA updating making this /// implementation easier than creating a generic LCSSA transformation pass. static SmallVector transformToReduceLoop(Block *loopHeader, Block *exitBlock, const llvm::SmallSetVector &loopBlocks, function_ref getUndefValue, DominanceInfo &dominanceInfo) { Block *latch = exitBlock->getSinglePredecessor(); assert(latch && "Exit block must have only latch as predecessor at this point"); assert(exitBlock->getNumArguments() == 0 && "Exit block mustn't have any block arguments at this point"); unsigned loopHeaderIndex = 0; unsigned exitBlockIndex = 1; if (latch->getSuccessor(loopHeaderIndex) != loopHeader) std::swap(loopHeaderIndex, exitBlockIndex); assert(latch->getSuccessor(loopHeaderIndex) == loopHeader); assert(latch->getSuccessor(exitBlockIndex) == exitBlock); MutableOperandRange exitBlockSuccessorOperands = getMutableSuccessorOperands(latch, exitBlockIndex); // Save the values as a vector, not a `MutableOperandRange` as the latter gets // invalidated when mutating the operands through a different // `MutableOperandRange` of the same operation. SmallVector loopHeaderSuccessorOperands = llvm::to_vector(getSuccessorOperands(latch, loopHeaderIndex)); // Add all values used in the next iteration to the exit block. Replace // any uses that are outside the loop with the newly created exit block. for (Value arg : loopHeaderSuccessorOperands) { BlockArgument exitArg = exitBlock->addArgument(arg.getType(), arg.getLoc()); exitBlockSuccessorOperands.append(arg); arg.replaceUsesWithIf(exitArg, [&](OpOperand &use) { return !loopBlocks.contains(use.getOwner()->getBlock()); }); } // Loop below might add block arguments to the latch and loop header. // Save the block arguments prior to the loop to not process these. SmallVector latchBlockArgumentsPrior = llvm::to_vector(latch->getArguments()); SmallVector loopHeaderArgumentsPrior = llvm::to_vector(loopHeader->getArguments()); // Go over all values defined within the loop body. If any of them are used // outside the loop body, create a block argument on the exit block and loop // header and replace the outside uses with the exit block argument. // The loop header block argument is added to satisfy requirement (1) in the // reduce form condition. for (Block *loopBlock : loopBlocks) { // Cache dominance queries for loopBlock. // There are likely to be many duplicate queries as there can be many value // definitions within a block. llvm::SmallDenseMap dominanceCache; // Returns true if `loopBlock` dominates `block`. auto loopBlockDominates = [&](Block *block) { auto [iter, inserted] = dominanceCache.insert({block, false}); if (!inserted) return iter->second; iter->second = dominanceInfo.dominates(loopBlock, block); return iter->second; }; auto checkValue = [&](Value value) { Value blockArgument; for (OpOperand &use : llvm::make_early_inc_range(value.getUses())) { // Go through all the parent blocks and find the one part of the region // of the loop. If the block is part of the loop, then the value does // not escape the loop through this use. Block *currBlock = use.getOwner()->getBlock(); while (currBlock && currBlock->getParent() != loopHeader->getParent()) currBlock = currBlock->getParentOp()->getBlock(); if (loopBlocks.contains(currBlock)) continue; // Block argument is only created the first time it is required. if (!blockArgument) { blockArgument = exitBlock->addArgument(value.getType(), value.getLoc()); loopHeader->addArgument(value.getType(), value.getLoc()); // `value` might be defined in a block that does not dominate `latch` // but previously dominated an exit block with a use. // In this case, add a block argument to the latch and go through all // predecessors. If the value dominates the predecessor, pass the // value as a successor operand, otherwise pass undef. // The above is unnecessary if the value is a block argument of the // latch or if `value` dominates all predecessors. Value argument = value; if (value.getParentBlock() != latch && llvm::any_of(latch->getPredecessors(), [&](Block *pred) { return !loopBlockDominates(pred); })) { argument = latch->addArgument(value.getType(), value.getLoc()); for (auto iter = latch->pred_begin(); iter != latch->pred_end(); ++iter) { Value succOperand = value; if (!loopBlockDominates(*iter)) succOperand = getUndefValue(value.getType()); getMutableSuccessorOperands(*iter, iter.getSuccessorIndex()) .append(succOperand); } } loopHeaderSuccessorOperands.push_back(argument); for (Edge edge : successorEdges(latch)) edge.getMutableSuccessorOperands().append(argument); } use.set(blockArgument); } }; if (loopBlock == latch) llvm::for_each(latchBlockArgumentsPrior, checkValue); else if (loopBlock == loopHeader) llvm::for_each(loopHeaderArgumentsPrior, checkValue); else llvm::for_each(loopBlock->getArguments(), checkValue); for (Operation &op : *loopBlock) llvm::for_each(op.getResults(), checkValue); } // New block arguments may have been added to the loop header. // Adjust the entry edges to pass undef values to these. for (auto iter = loopHeader->pred_begin(); iter != loopHeader->pred_end(); ++iter) { // Latch successor arguments have already been handled. if (*iter == latch) continue; MutableOperandRange succOps = getMutableSuccessorOperands(*iter, iter.getSuccessorIndex()); succOps.append(llvm::map_to_vector( loopHeader->getArguments().drop_front(succOps.size()), [&](BlockArgument arg) { return getUndefValue(arg.getType()); })); } return loopHeaderSuccessorOperands; } /// Transforms all outer-most cycles in the region with the region entry /// `regionEntry` into structured loops. Returns the entry blocks of any newly /// created regions potentially requiring further transformations. static FailureOr> transformCyclesToSCFLoops( Block *regionEntry, function_ref getSwitchValue, function_ref getUndefValue, CFGToSCFInterface &interface, DominanceInfo &dominanceInfo, ReturnLikeExitCombiner &exitCombiner) { SmallVector newSubRegions; auto scc = llvm::scc_begin(regionEntry); while (!scc.isAtEnd()) { if (!scc.hasCycle()) { ++scc; continue; } // Save the set and increment the SCC iterator early to avoid our // modifications breaking the SCC iterator. llvm::SmallSetVector cycleBlockSet(scc->begin(), scc->end()); ++scc; CycleEdges edges = calculateCycleEdges(cycleBlockSet); Block *loopHeader = edges.entryEdges.front().getSuccessor(); // First turn the cycle into a loop by creating a single entry block if // needed. if (edges.entryEdges.size() > 1) { SmallVector edgesToEntryBlocks; llvm::append_range(edgesToEntryBlocks, edges.entryEdges); llvm::append_range(edgesToEntryBlocks, edges.backEdges); EdgeMultiplexer multiplexer = createSingleEntryBlock( loopHeader->getTerminator()->getLoc(), edgesToEntryBlocks, getSwitchValue, getUndefValue, interface); loopHeader = multiplexer.getMultiplexerBlock(); } cycleBlockSet.insert(loopHeader); // Then turn it into a structured loop by creating a single latch. FailureOr loopProperties = createSingleExitingLatch( edges.backEdges.front().getFromBlock()->getTerminator()->getLoc(), edges.backEdges, edges.exitEdges, getSwitchValue, getUndefValue, interface, exitCombiner); if (failed(loopProperties)) return failure(); Block *latchBlock = loopProperties->latch; Block *exitBlock = loopProperties->exitBlock; cycleBlockSet.insert(latchBlock); cycleBlockSet.insert(loopHeader); // Finally, turn it into reduce form. SmallVector iterationValues = transformToReduceLoop( loopHeader, exitBlock, cycleBlockSet, getUndefValue, dominanceInfo); // Create a block acting as replacement for the loop header and insert // the structured loop into it. auto *newLoopParentBlock = new Block; newLoopParentBlock->insertBefore(loopHeader); addBlockArgumentsFromOther(newLoopParentBlock, loopHeader); Region::BlockListType &blocks = regionEntry->getParent()->getBlocks(); Region loopBody; // Make sure the loop header is the entry block. loopBody.push_back(blocks.remove(loopHeader)); for (Block *block : cycleBlockSet) if (block != latchBlock && block != loopHeader) loopBody.push_back(blocks.remove(block)); // And the latch is the last block. loopBody.push_back(blocks.remove(latchBlock)); Operation *oldTerminator = latchBlock->getTerminator(); oldTerminator->remove(); auto builder = OpBuilder::atBlockBegin(newLoopParentBlock); FailureOr structuredLoopOp = interface.createStructuredDoWhileLoopOp( builder, oldTerminator, newLoopParentBlock->getArguments(), loopProperties->condition, iterationValues, std::move(loopBody)); if (failed(structuredLoopOp)) return failure(); oldTerminator->erase(); newSubRegions.push_back(loopHeader); for (auto &&[oldValue, newValue] : llvm::zip( exitBlock->getArguments(), (*structuredLoopOp)->getResults())) oldValue.replaceAllUsesWith(newValue); loopHeader->replaceAllUsesWith(newLoopParentBlock); // Merge the exit block right after the loop operation. newLoopParentBlock->getOperations().splice(newLoopParentBlock->end(), exitBlock->getOperations()); exitBlock->erase(); } return newSubRegions; } /// Makes sure the branch region only has a single exit. This is required by the /// recursive part of the algorithm, as it expects the CFG to be single-entry /// and single-exit. This is done by simply creating an empty block if there /// is more than one block with an edge to the continuation block. All blocks /// with edges to the continuation are then redirected to this block. A region /// terminator is later placed into the block. static void createSingleExitBranchRegion( ArrayRef branchRegion, Block *continuation, SmallVectorImpl>> &createdEmptyBlocks, Region &conditionalRegion) { Block *singleExitBlock = nullptr; std::optional previousEdgeToContinuation; Region::BlockListType &parentBlockList = branchRegion.front()->getParent()->getBlocks(); for (Block *block : branchRegion) { for (Edge edge : successorEdges(block)) { if (edge.getSuccessor() != continuation) continue; if (!previousEdgeToContinuation) { previousEdgeToContinuation = edge; continue; } // If this is not the first edge to the continuation we create the // single exit block and redirect the edges. if (!singleExitBlock) { singleExitBlock = new Block; addBlockArgumentsFromOther(singleExitBlock, continuation); previousEdgeToContinuation->setSuccessor(singleExitBlock); createdEmptyBlocks.emplace_back(singleExitBlock, singleExitBlock->getArguments()); } edge.setSuccessor(singleExitBlock); } conditionalRegion.push_back(parentBlockList.remove(block)); } if (singleExitBlock) conditionalRegion.push_back(singleExitBlock); } /// Returns true if this block is an exit block of the region. static bool isRegionExitBlock(Block *block) { return block->getNumSuccessors() == 0; } /// Transforms the first occurrence of conditional control flow in `regionEntry` /// into conditionally executed regions. Returns the entry block of the created /// regions and the region after the conditional control flow. static FailureOr> transformToStructuredCFBranches( Block *regionEntry, function_ref getSwitchValue, function_ref getUndefValue, CFGToSCFInterface &interface, DominanceInfo &dominanceInfo) { // Trivial region. if (regionEntry->getNumSuccessors() == 0) return SmallVector{}; if (regionEntry->getNumSuccessors() == 1) { // Single successor we can just splice together. Block *successor = regionEntry->getSuccessor(0); for (auto &&[oldValue, newValue] : llvm::zip( successor->getArguments(), getSuccessorOperands(regionEntry, 0))) oldValue.replaceAllUsesWith(newValue); regionEntry->getTerminator()->erase(); regionEntry->getOperations().splice(regionEntry->end(), successor->getOperations()); successor->erase(); return SmallVector{regionEntry}; } // Split the CFG into "#numSuccessor + 1" regions. // For every edge to a successor, the blocks it solely dominates are // determined and become the region following that edge. // The last region is the continuation that follows the branch regions. SmallPtrSet notContinuation; notContinuation.insert(regionEntry); SmallVector> successorBranchRegions( regionEntry->getNumSuccessors()); for (auto &&[blockList, succ] : llvm::zip(successorBranchRegions, regionEntry->getSuccessors())) { // If the region entry is not the only predecessor, then the edge does not // dominate the block it leads to. if (succ->getSinglePredecessor() != regionEntry) continue; // Otherwise get all blocks it dominates in DFS/pre-order. DominanceInfoNode *node = dominanceInfo.getNode(succ); for (DominanceInfoNode *curr : llvm::depth_first(node)) { blockList.push_back(curr->getBlock()); notContinuation.insert(curr->getBlock()); } } // Finds all relevant edges and checks the shape of the control flow graph at // this point. // Branch regions may either: // * Be post-dominated by the continuation // * Be post-dominated by a return-like op // * Dominate a return-like op and have an edge to the continuation. // // The control flow graph may then be one of three cases: // 1) All branch regions are post-dominated by the continuation. This is the // usual case. If there are multiple entry blocks into the continuation a // single entry block has to be created. A structured control flow op // can then be created from the branch regions. // // 2) No branch region has an edge to a continuation: // +-----+ // +-----+ bb0 +----+ // v +-----+ v // Region 1 +-+--+ ... +-+--+ Region n // |ret1| |ret2| // +----+ +----+ // // This can only occur if every region ends with a different kind of // return-like op. In that case the control flow operation must stay as we are // unable to create a single exit-block. We can nevertheless process all its // successors as they single-entry, single-exit regions. // // 3) Only some branch regions are post-dominated by the continuation. // The other branch regions may either be post-dominated by a return-like op // or lead to either the continuation or return-like op. // In this case we also create a single entry block like in 1) that also // includes all edges to the return-like op: // +-----+ // +-----+ bb0 +----+ // v +-----+ v // Region 1 +-+-+ ... +-+-+ Region n // +---+ +---+ // +---+ |... ... // |ret|<-+ | | // +---+ | +---+ | // +---->++ ++<---+ // | | // ++ ++ Region T // +---+ // This transforms to: // +-----+ // +-----+ bb0 +----+ // v +-----+ v // Region 1 +-+-+ ... +-+-+ Region n // +---+ +---+ // ... +-----+ ... // +---->+ bbM +<---+ // +-----+ // +-----+ | // | v // +---+ | +---+ // |ret+<---+ ++ ++ // +---+ | | // ++ ++ Region T // +---+ // // bb0 to bbM is now a single-entry, single-exit region that applies to case // 1). The control flow op at the end of bbM will trigger case 2. SmallVector continuationEdges; bool continuationPostDominatesAllRegions = true; bool noSuccessorHasContinuationEdge = true; for (auto &&[entryEdge, branchRegion] : llvm::zip(successorEdges(regionEntry), successorBranchRegions)) { // If the branch region is empty then the branch target itself is part of // the continuation. if (branchRegion.empty()) { continuationEdges.push_back(entryEdge); noSuccessorHasContinuationEdge = false; continue; } for (Block *block : branchRegion) { if (isRegionExitBlock(block)) { // If a return-like op is part of the branch region then the // continuation no longer post-dominates the branch region. // Add all its incoming edges to edge list to create the single-exit // block for all branch regions. continuationPostDominatesAllRegions = false; for (auto iter = block->pred_begin(); iter != block->pred_end(); ++iter) { continuationEdges.emplace_back(*iter, iter.getSuccessorIndex()); } continue; } for (Edge edge : successorEdges(block)) { if (notContinuation.contains(edge.getSuccessor())) continue; continuationEdges.push_back(edge); noSuccessorHasContinuationEdge = false; } } } // case 2) Keep the control flow op but process its successors further. if (noSuccessorHasContinuationEdge) return llvm::to_vector(regionEntry->getSuccessors()); Block *continuation = llvm::find_singleton( continuationEdges, [](Edge edge, bool) { return edge.getSuccessor(); }, /*AllowRepeats=*/true); // In case 3) or if not all continuation edges have the same entry block, // create a single entry block as continuation for all branch regions. if (!continuation || !continuationPostDominatesAllRegions) { EdgeMultiplexer multiplexer = createSingleEntryBlock( continuationEdges.front().getFromBlock()->getTerminator()->getLoc(), continuationEdges, getSwitchValue, getUndefValue, interface); continuation = multiplexer.getMultiplexerBlock(); } // Trigger reprocess of case 3) after creating the single entry block. if (!continuationPostDominatesAllRegions) { // Unlike in the general case, we are explicitly revisiting the same region // entry again after having changed its control flow edges and dominance. // We have to therefore explicitly invalidate the dominance tree. dominanceInfo.invalidate(regionEntry->getParent()); return SmallVector{regionEntry}; } SmallVector newSubRegions; // Empty blocks with the values they return to the parent op. SmallVector>> createdEmptyBlocks; // Create the branch regions. std::vector conditionalRegions(successorBranchRegions.size()); for (auto &&[branchRegion, entryEdge, conditionalRegion] : llvm::zip(successorBranchRegions, successorEdges(regionEntry), conditionalRegions)) { if (branchRegion.empty()) { // If no block is part of the branch region, we create a dummy block to // place the region terminator into. createdEmptyBlocks.emplace_back( new Block, llvm::to_vector(entryEdge.getSuccessorOperands())); conditionalRegion.push_back(createdEmptyBlocks.back().first); continue; } createSingleExitBranchRegion(branchRegion, continuation, createdEmptyBlocks, conditionalRegion); // The entries of the branch regions may only have redundant block arguments // since the edge to the branch region is always dominating. Block *subRegionEntryBlock = &conditionalRegion.front(); for (auto &&[oldValue, newValue] : llvm::zip(subRegionEntryBlock->getArguments(), entryEdge.getSuccessorOperands())) oldValue.replaceAllUsesWith(newValue); subRegionEntryBlock->eraseArguments(0, subRegionEntryBlock->getNumArguments()); newSubRegions.push_back(subRegionEntryBlock); } Operation *structuredCondOp; { auto opBuilder = OpBuilder::atBlockTerminator(regionEntry); FailureOr result = interface.createStructuredBranchRegionOp( opBuilder, regionEntry->getTerminator(), continuation->getArgumentTypes(), conditionalRegions); if (failed(result)) return failure(); structuredCondOp = *result; regionEntry->getTerminator()->erase(); } for (auto &&[block, valueRange] : createdEmptyBlocks) { auto builder = OpBuilder::atBlockEnd(block); LogicalResult result = interface.createStructuredBranchRegionTerminatorOp( structuredCondOp->getLoc(), builder, structuredCondOp, nullptr, valueRange); if (failed(result)) return failure(); } // Any leftover users of the continuation must be from unconditional branches // in a branch region. There can only be at most one per branch region as // all branch regions have been made single-entry single-exit above. // Replace them with the region terminator. for (Operation *user : llvm::make_early_inc_range(continuation->getUsers())) { assert(user->getNumSuccessors() == 1); auto builder = OpBuilder::atBlockTerminator(user->getBlock()); LogicalResult result = interface.createStructuredBranchRegionTerminatorOp( user->getLoc(), builder, structuredCondOp, user, static_cast( getMutableSuccessorOperands(user->getBlock(), 0))); if (failed(result)) return failure(); user->erase(); } for (auto &&[oldValue, newValue] : llvm::zip(continuation->getArguments(), structuredCondOp->getResults())) oldValue.replaceAllUsesWith(newValue); // Splice together the continuations operations with the region entry. regionEntry->getOperations().splice(regionEntry->end(), continuation->getOperations()); continuation->erase(); // After splicing the continuation, the region has to be reprocessed as it has // new successors. newSubRegions.push_back(regionEntry); return newSubRegions; } /// Transforms the region to only have a single block for every kind of /// return-like operation that all previous occurrences of the return-like op /// branch to. If the region only contains a single kind of return-like /// operation, it creates a single-entry and single-exit region. static ReturnLikeExitCombiner createSingleExitBlocksForReturnLike( Region ®ion, function_ref getSwitchValue, CFGToSCFInterface &interface) { ReturnLikeExitCombiner exitCombiner(region, interface); for (Block &block : region.getBlocks()) { if (block.getNumSuccessors() != 0) continue; exitCombiner.combineExit(block.getTerminator(), getSwitchValue); } return exitCombiner; } /// Checks all preconditions of the transformation prior to any transformations. /// Returns failure if any precondition is violated. static LogicalResult checkTransformationPreconditions(Region ®ion) { for (Block &block : region.getBlocks()) if (block.hasNoPredecessors() && !block.isEntryBlock()) return block.front().emitOpError( "transformation does not support unreachable blocks"); WalkResult result = region.walk([](Operation *operation) { if (operation->getNumSuccessors() == 0) return WalkResult::advance(); // This transformation requires all ops with successors to implement the // branch op interface. It is impossible to adjust their block arguments // otherwise. auto branchOpInterface = dyn_cast(operation); if (!branchOpInterface) { operation->emitOpError("transformation does not support terminators with " "successors not implementing BranchOpInterface"); return WalkResult::interrupt(); } // Branch operations must have no side effects. Replacing them would not be // valid otherwise. if (!isMemoryEffectFree(branchOpInterface)) { branchOpInterface->emitOpError( "transformation does not support terminators with side effects"); return WalkResult::interrupt(); } for (unsigned index : llvm::seq(operation->getNumSuccessors())) { SuccessorOperands succOps = branchOpInterface.getSuccessorOperands(index); // We cannot support operations with operation-produced successor operands // as it is currently not possible to pass them to any block arguments // other than the first. This breaks creating multiplexer blocks and would // likely need special handling elsewhere too. if (succOps.getProducedOperandCount() == 0) continue; branchOpInterface->emitOpError("transformation does not support " "operations with operation-produced " "successor operands"); return WalkResult::interrupt(); } return WalkResult::advance(); }); return failure(result.wasInterrupted()); } FailureOr mlir::transformCFGToSCF(Region ®ion, CFGToSCFInterface &interface, DominanceInfo &dominanceInfo) { if (region.empty() || region.hasOneBlock()) return false; if (failed(checkTransformationPreconditions(region))) return failure(); DenseMap typedUndefCache; auto getUndefValue = [&](Type type) { auto [iter, inserted] = typedUndefCache.insert({type, nullptr}); if (!inserted) return iter->second; auto constantBuilder = OpBuilder::atBlockBegin(®ion.front()); iter->second = interface.getUndefValue(region.getLoc(), constantBuilder, type); return iter->second; }; // The transformation only creates all values in the range of 0 to // max(#numSuccessors). Therefore using a vector instead of a map. SmallVector switchValueCache; auto getSwitchValue = [&](unsigned value) { if (value < switchValueCache.size()) if (switchValueCache[value]) return switchValueCache[value]; auto constantBuilder = OpBuilder::atBlockBegin(®ion.front()); switchValueCache.resize( std::max(switchValueCache.size(), value + 1)); switchValueCache[value] = interface.getCFGSwitchValue(region.getLoc(), constantBuilder, value); return switchValueCache[value]; }; ReturnLikeExitCombiner exitCombiner = createSingleExitBlocksForReturnLike(region, getSwitchValue, interface); // Invalidate any dominance tree on the region as the exit combiner has // added new blocks and edges. dominanceInfo.invalidate(®ion); SmallVector workList = {®ion.front()}; while (!workList.empty()) { Block *current = workList.pop_back_val(); // Turn all top-level cycles in the CFG to structured control flow first. // After this transformation, the remaining CFG ops form a DAG. FailureOr> newRegions = transformCyclesToSCFLoops(current, getSwitchValue, getUndefValue, interface, dominanceInfo, exitCombiner); if (failed(newRegions)) return failure(); // Add the newly created subregions to the worklist. These are the // bodies of the loops. llvm::append_range(workList, *newRegions); // Invalidate the dominance tree as blocks have been moved, created and // added during the cycle to structured loop transformation. if (!newRegions->empty()) dominanceInfo.invalidate(current->getParent()); newRegions = transformToStructuredCFBranches( current, getSwitchValue, getUndefValue, interface, dominanceInfo); if (failed(newRegions)) return failure(); // Invalidating the dominance tree is generally not required by the // transformation above as the new region entries correspond to unaffected // subtrees in the dominator tree. Only its parent nodes have changed but // won't be visited again. llvm::append_range(workList, *newRegions); } return true; }