14#include "mlir/Analysis/CFGLoopInfo.h"
15#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
16#include "mlir/IR/Dominance.h"
17#include "mlir/IR/IRMapping.h"
18#include "mlir/IR/Matchers.h"
19#include "mlir/Pass/Pass.h"
20#include "llvm/ADT/PostOrderIterator.h"
21#include "llvm/Support/Debug.h"
23#define DEBUG_TYPE "llhd-unroll-loops"
27#define GEN_PASS_DEF_UNROLLLOOPSPASS
28#include "circt/Dialect/LLHD/LLHDPasses.h.inc"
35using llvm::SmallDenseSet;
45static void cloneBlocks(ArrayRef<Block *> blocks, Region ®ion,
46 Region::iterator before, IRMapping &mapper) {
54 SmallVector<Block *> newBlocks;
55 newBlocks.reserve(blocks.size());
56 for (
auto *block : blocks) {
57 auto *newBlock =
new Block();
58 mapper.map(block, newBlock);
59 for (
auto arg : block->getArguments())
60 mapper.map(arg, newBlock->addArgument(arg.getType(), arg.getLoc()));
61 region.getBlocks().insert(before, newBlock);
62 newBlocks.push_back(newBlock);
71 Operation::CloneOptions::all().cloneRegions(
false).cloneOperands(
false);
72 for (
auto [oldBlock, newBlock] : llvm::zip(blocks, newBlocks))
73 for (
auto &op : *oldBlock)
74 newBlock->push_back(op.clone(mapper, cloneOptions));
78 SmallVector<Value> operands;
79 for (
auto [oldBlock, newBlock] : llvm::zip(blocks, newBlocks)) {
80 for (
auto [oldOp, newOp] : llvm::zip(*oldBlock, *newBlock)) {
81 operands.resize(oldOp.getNumOperands());
83 oldOp.getOperands(), operands.begin(),
84 [&](Value operand) { return mapper.lookupOrDefault(operand); });
85 newOp.setOperands(operands);
86 for (
auto [oldRegion, newRegion] :
87 llvm::zip(oldOp.getRegions(), newOp.getRegions()))
88 oldRegion.cloneInto(&newRegion, mapper);
100 Loop(
unsigned loopId, CFGLoop &cfgLoop) : loopId(loopId), cfgLoop(cfgLoop) {}
101 bool failMatch(
const Twine &msg)
const;
103 bool matchExit(BlockOperand *edge);
104 bool routeLiveOutsThroughArguments();
105 void unroll(CFGLoopInfo &cfgLoopInfo);
112 BlockOperand *exitEdge =
nullptr;
123 bool comparesNext =
false;
126 comb::ICmpPredicate predicate;
128 APInt indVarIncrement;
134 unsigned tripCount = 0;
138static llvm::raw_ostream &
operator<<(llvm::raw_ostream &os,
const Loop &loop) {
139 os <<
"#" << loop.loopId <<
" from ";
140 loop.cfgLoop.getHeader()->printAsOperand(os);
142 loop.cfgLoop.getLoopLatch()->printAsOperand(os);
147bool Loop::failMatch(
const Twine &msg)
const {
148 LLVM_DEBUG(llvm::dbgs() <<
"- Ignoring loop " << *
this <<
": " << msg
159 case comb::ICmpPredicate::eq:
161 case comb::ICmpPredicate::ne:
163 case comb::ICmpPredicate::slt:
165 case comb::ICmpPredicate::sle:
167 case comb::ICmpPredicate::sgt:
169 case comb::ICmpPredicate::sge:
171 case comb::ICmpPredicate::ult:
173 case comb::ICmpPredicate::ule:
175 case comb::ICmpPredicate::ugt:
177 case comb::ICmpPredicate::uge:
198 SmallVector<BlockOperand *> exits;
199 for (
auto *block : cfgLoop.getBlocks())
200 for (
auto &edge : block->getTerminator()->getBlockOperands())
201 if (!cfgLoop.contains(edge.get()))
202 exits.push_back(&edge);
204 return failMatch(
"no exit");
210 if (exits.size() == 1)
211 return matchExit(exits.front());
212 auto *header = cfgLoop.getHeader();
214 for (
auto *edge : exits)
215 if (edge->getOwner()->getBlock() == header && matchExit(edge)) {
220 return failMatch(
"no counting exit in the header of a loop with several "
222 if (!routeLiveOutsThroughArguments())
223 return failMatch(
"a value of the loop is used after it where it cannot be "
224 "passed as a block argument");
240bool Loop::routeLiveOutsThroughArguments() {
241 Region *region = cfgLoop.getHeader()->getParent();
247 auto *header = cfgLoop.getHeader();
248 for (
auto arg : header->getArguments()) {
250 bool invariant =
true;
251 for (
auto &pred : header->getUses()) {
252 auto branch = dyn_cast<BranchOpInterface>(pred.getOwner());
257 Value value = branch.getSuccessorOperands(
258 pred.getOperandNumber())[arg.getArgNumber()];
261 if (!value || (incoming && incoming != value)) {
267 if (invariant && incoming)
268 arg.replaceAllUsesWith(incoming);
271 SmallVector<Value> worklist;
272 for (
auto *block : cfgLoop.getBlocks()) {
273 for (
auto arg : block->getArguments())
274 worklist.push_back(arg);
275 block->walk([&](Operation *op) {
276 for (
auto result : op->getResults())
277 worklist.push_back(result);
280 unsigned budget = 100000;
281 while (!worklist.empty()) {
282 Value value = worklist.pop_back_val();
283 for (
auto &use : value.getUses()) {
285 region->findAncestorBlockInRegion(*use.getOwner()->getBlock());
288 if (cfgLoop.contains(block))
293 if (block->use_empty() || --budget == 0)
295 SmallVector<std::pair<BranchOpInterface, unsigned>> edges;
296 for (
auto &edge : block->getUses()) {
297 auto branch = dyn_cast<BranchOpInterface>(edge.getOwner());
298 auto *pred = edge.getOwner()->getBlock();
299 if (!branch || pred == block ||
300 (!block->hasOneUse() && !cfgLoop.contains(pred)))
302 edges.push_back({branch, edge.getOperandNumber()});
304 auto arg = block->addArgument(value.getType(), value.getLoc());
305 value.replaceUsesWithIf(arg, [&](OpOperand &other) {
306 return region->findAncestorBlockInRegion(
307 *other.getOwner()->getBlock()) == block;
309 for (
auto [branch, index] : edges)
310 branch.getSuccessorOperands(index).append(value);
313 worklist.push_back(value);
324bool Loop::matchExit(BlockOperand *edge) {
327 comparesNext =
false;
331 auto exitBranch = dyn_cast<cf::CondBranchOp>(exitEdge->getOwner());
333 return failMatch(
"unsupported exit branch");
334 if (!cfgLoop.contains(
335 exitBranch->getSuccessor(1 - exitEdge->getOperandNumber())))
336 return failMatch(
"both successors of the exit branch leave the loop");
337 exitCondition = exitBranch.getCondition();
338 exitInverted = exitEdge->getOperandNumber() == 1;
342 if (
auto icmpOp = exitCondition.getDefiningOp<comb::ICmpOp>()) {
343 IntegerAttr boundAttr;
344 if (!matchPattern(icmpOp.getRhs(), m_Constant(&boundAttr)))
345 return failMatch(
"non-constant loop bound");
346 indVar = icmpOp.getLhs();
347 predicate = icmpOp.getPredicate();
348 endBound = boundAttr.getValue();
350 return failMatch(
"unsupported exit condition");
356 predicate = comb::ICmpOp::getNegatedPredicate(predicate);
359 auto *header = cfgLoop.getHeader();
360 auto *latch = cfgLoop.getLoopLatch();
361 auto indVarArg = dyn_cast<BlockArgument>(indVar);
365 if (
auto addOp = indVar.getDefiningOp<comb::AddOp>();
366 addOp && addOp.getNumOperands() == 2) {
367 indVarArg = dyn_cast<BlockArgument>(addOp.getOperand(0));
371 if (!indVarArg || indVarArg.getOwner() != header)
372 return failMatch(
"induction variable is not a header block argument");
373 Value compared = indVar;
375 IntegerAttr beginBoundAttr;
376 for (
auto &pred : header->getUses()) {
377 auto branchOp = dyn_cast<BranchOpInterface>(pred.getOwner());
379 return failMatch(
"header predecessor terminator is not a branch op");
380 auto indVarValue = branchOp.getSuccessorOperands(
381 pred.getOperandNumber())[indVarArg.getArgNumber()];
382 IntegerAttr boundAttr;
383 if (pred.getOwner()->getBlock() == latch) {
384 indVarNext = indVarValue;
385 }
else if (matchPattern(indVarValue, m_Constant(&boundAttr))) {
387 beginBoundAttr = boundAttr;
388 else if (boundAttr != beginBoundAttr)
389 return failMatch(
"multiple initial bounds");
391 return failMatch(
"unsupported induction variable value");
395 return failMatch(
"no initial bound");
396 beginBound = beginBoundAttr.getValue();
401 return failMatch(
"no back-edge value for the induction variable");
403 if (
auto addOp = indVarNext.getDefiningOp<comb::AddOp>();
404 addOp && addOp.getNumOperands() == 2) {
405 if (addOp.getOperand(0) != indVarArg)
406 return failMatch(
"increment LHS not the induction variable");
407 if (!matchPattern(addOp.getOperand(1), m_Constant(&incAttr)))
408 return failMatch(
"increment RHS non-constant");
409 indVarIncrement = incAttr.getValue();
410 }
else if (
auto subOp = indVarNext.getDefiningOp<comb::SubOp>()) {
411 if (subOp.getLhs() != indVarArg)
412 return failMatch(
"decrement LHS not the induction variable");
413 if (!matchPattern(subOp.getRhs(), m_Constant(&incAttr)))
414 return failMatch(
"decrement RHS non-constant");
415 indVarIncrement = -incAttr.getValue();
417 return failMatch(
"unsupported increment");
419 if (indVarIncrement.isZero())
420 return failMatch(
"zero increment");
421 if (comparesNext && compared != indVarNext)
422 return failMatch(
"exit condition tests a value other than the next "
423 "induction variable");
431 if (beginBound.getBitWidth() != endBound.getBitWidth() ||
432 beginBound.getBitWidth() != indVarIncrement.getBitWidth())
433 return failMatch(
"mismatched induction variable widths");
434 APInt value = beginBound;
438 predicate, comparesNext ? value + indVarIncrement : value, endBound);
440 return failMatch(
"unsupported loop predicate");
444 return failMatch(
"unsupported loop bounds");
445 value += indVarIncrement;
453void Loop::unroll(CFGLoopInfo &cfgLoopInfo) {
454 LLVM_DEBUG(llvm::dbgs() <<
"- Unrolling loop " << *
this <<
"\n");
455 UnusedOpPruner pruner;
459 auto *header = cfgLoop.getHeader();
460 SmallVector<Block *> orderedBody;
461 for (
auto &block : *header->getParent())
462 if (cfgLoop.contains(&block))
463 orderedBody.push_back(&block);
466 auto *latch = cfgLoop.getLoopLatch();
467 OpBuilder builder(indVar.getContext());
468 auto indValue = beginBound;
469 for (
unsigned trip = 0; trip < tripCount; ++trip) {
472 cloneBlocks(orderedBody, *header->getParent(), header->getIterator(),
474 auto *clonedHeader = mapper.lookup(header);
475 auto *clonedTail = mapper.lookup(latch);
478 auto iterIndVar = mapper.lookup(indVar);
480 builder.setInsertionPointAfterValue(iterIndVar);
481 iterIndVar.replaceAllUsesWith(
486 for (
auto &blockOperand : llvm::make_early_inc_range(header->getUses()))
487 if (blockOperand.getOwner()->getBlock() != latch)
488 blockOperand.set(clonedHeader);
492 for (
auto &blockOperand : clonedTail->getTerminator()->getBlockOperands())
493 if (blockOperand.get() == clonedHeader)
494 blockOperand.set(header);
499 cast<cf::CondBranchOp>(mapper.lookup(exitEdge->getOwner()));
500 Block *continueDest = exitBranchOp.getTrueDest();
501 ValueRange continueDestOperands = exitBranchOp.getTrueDestOperands();
502 if (exitEdge->getOperandNumber() == 0) {
503 continueDest = exitBranchOp.getFalseDest();
504 continueDestOperands = exitBranchOp.getFalseDestOperands();
506 builder.setInsertionPoint(exitBranchOp);
507 cf::BranchOp::create(builder, exitBranchOp.getLoc(), continueDest,
508 continueDestOperands);
510 exitBranchOp.erase();
513 for (
auto *block : orderedBody) {
514 auto *newBlock = mapper.lookup(block);
515 cfgLoop.addBasicBlockToLoop(newBlock, cfgLoopInfo);
519 indValue += indVarIncrement;
526 builder.setInsertionPointAfterValue(indVar);
527 indVar.replaceAllUsesWith(
533 auto exitBranchOp = cast<cf::CondBranchOp>(exitEdge->getOwner());
534 Block *exitDest = exitBranchOp.getTrueDest();
535 ValueRange exitDestOperands = exitBranchOp.getTrueDestOperands();
536 if (exitEdge->getOperandNumber() == 1) {
537 exitDest = exitBranchOp.getFalseDest();
538 exitDestOperands = exitBranchOp.getFalseDestOperands();
540 builder.setInsertionPoint(exitBranchOp);
541 cf::BranchOp::create(builder, exitBranchOp.getLoc(), exitDest,
544 exitBranchOp.erase();
548 auto ®ion = *header->getParent();
549 SmallPtrSet<Block *, 8> reachable;
550 SmallVector<Block *> worklist;
551 reachable.insert(®ion.front());
552 worklist.push_back(®ion.front());
553 while (!worklist.empty())
554 for (
auto *succ : worklist.pop_back_val()->getSuccessors())
555 if (reachable.insert(succ).second)
556 worklist.push_back(succ);
558 SmallVector<Block *> deadBlocks;
559 for (
auto &block : region) {
560 if (reachable.contains(&block))
562 block.dropAllDefinedValueUses();
563 deadBlocks.push_back(&block);
566 for (
auto *block : deadBlocks) {
567 cfgLoopInfo.removeBlock(block);
576 for (
auto &block : *header->getParent()) {
577 if (!cfgLoop.contains(&block))
580 auto branchOp = dyn_cast<cf::BranchOp>(block.getTerminator());
583 auto *otherBlock = branchOp.getDest();
584 if (!cfgLoop.contains(otherBlock) || !otherBlock->getSinglePredecessor())
586 for (
auto [blockArg, branchArg] :
587 llvm::zip(otherBlock->getArguments(), branchOp.getDestOperands()))
588 blockArg.replaceAllUsesWith(branchArg);
589 block.getOperations().splice(branchOp->getIterator(),
590 otherBlock->getOperations());
592 cfgLoopInfo.removeBlock(otherBlock);
603struct UnrollLoopsPass
604 :
public llhd::impl::UnrollLoopsPassBase<UnrollLoopsPass> {
605 void runOnOperation()
override;
606 void runOnOperation(CombinationalOp op);
607 bool unrollLoops(CombinationalOp op);
611void UnrollLoopsPass::runOnOperation() {
612 for (
auto op : getOperation().getOps<CombinationalOp>())
616void UnrollLoopsPass::runOnOperation(CombinationalOp op) {
619 for (
unsigned i = 0; i < 16; ++i)
620 if (!unrollLoops(op))
624bool UnrollLoopsPass::unrollLoops(CombinationalOp op) {
627 if (op.getBody().hasOneBlock())
631 LLVM_DEBUG(llvm::dbgs() <<
"Unrolling loops in " << op.getLoc() <<
"\n");
632 DominanceInfo domInfo(op);
633 CFGLoopInfo cfgLoopInfo(domInfo.getDomTree(&op.getBody()));
639 SmallVector<Loop> loops;
641 for (
auto *cfgLoop : cfgLoopInfo.getLoopsInPreorder()) {
644 auto *header = cfgLoop->getHeader();
645 auto *latch = cfgLoop->getLoopLatch();
650 llvm::dbgs() <<
"- ";
651 cfgLoop->print(llvm::dbgs(),
false,
false);
652 llvm::dbgs() <<
"\n";
654 Loop loop(loops.size(), *cfgLoop);
658 auto *parent = cfgLoop->getParentLoop();
659 while (parent && parent->getHeader() != header)
660 parent = parent->getParentLoop();
662 loop.failMatch(
"header block shared across multiple loops");
668 parent = cfgLoop->getParentLoop();
669 while (parent && !parent->isLoopLatch(latch))
670 parent = parent->getParentLoop();
672 loop.failMatch(
"latch block shared across multiple loops");
678 loops.push_back(std::move(loop));
683 retry |= llvm::any_of(loops, [&](
const Loop &other) {
684 return other.cfgLoop.contains(cfgLoop);
693 auto &os = llvm::dbgs();
694 for (
auto &loop : loops) {
695 os <<
"- Loop " << loop <<
":\n";
697 loop.cfgLoop.print(os,
false,
false);
700 loop.exitEdge->get()->printAsOperand(os);
702 if (loop.exitInverted)
704 os << loop.exitCondition;
706 os <<
" - Induction variable: ";
707 loop.indVar.printAsOperand(os, OpPrintingFlags().useLocalScope());
708 os <<
", from " << loop.beginBound <<
", while " << loop.predicate <<
" "
709 << loop.endBound <<
", increment " << loop.indVarIncrement <<
"\n";
710 os <<
" - Trip count: " << loop.tripCount <<
"\n";
716 for (
auto &loop : llvm::reverse(loops))
717 loop.unroll(cfgLoopInfo);
static constexpr unsigned kMaxTripCount
The largest number of iterations the unroller copies a loop body for.
static void cloneBlocks(ArrayRef< Block * > blocks, Region ®ion, Region::iterator before, IRMapping &mapper)
Clone a list of blocks into a region before the given block.
static std::optional< bool > evaluatePredicate(comb::ICmpPredicate predicate, const APInt &lhs, const APInt &rhs)
Evaluate a comparison predicate on two constants.
OS & operator<<(OS &os, const InnerSymTarget &target)
Printing InnerSymTarget's.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
void eraseLaterIfUnused(Operation *op)
Mark an op the be erased later if it is unused at that point.
void eraseNow(Operation *op)
Erase an operation immediately, and remove it from the set of ops to be removed later.