CIRCT 23.0.0git
Loading...
Searching...
No Matches
UnrollLoops.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
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"
22
23#define DEBUG_TYPE "llhd-unroll-loops"
24
25namespace circt {
26namespace llhd {
27#define GEN_PASS_DEF_UNROLLLOOPSPASS
28#include "circt/Dialect/LLHD/LLHDPasses.h.inc"
29} // namespace llhd
30} // namespace circt
31
32using namespace mlir;
33using namespace circt;
34using namespace llhd;
35using llvm::SmallDenseSet;
37
38//===----------------------------------------------------------------------===//
39// Utilities
40//===----------------------------------------------------------------------===//
41
42/// Clone a list of blocks into a region before the given block.
43///
44/// See `Region::cloneInto` for the original code that clones an entire region.
45static void cloneBlocks(ArrayRef<Block *> blocks, Region &region,
46 Region::iterator before, IRMapping &mapper) {
47 // If the list is empty there is nothing to clone.
48 if (blocks.empty())
49 return;
50
51 // First clone all the blocks and block arguments and map them, but don't yet
52 // clone the operations, as they may otherwise add a use to a block that has
53 // not yet been mapped
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);
63 }
64
65 // Now follow up with creating the operations, but don't yet clone their
66 // regions, nor set their operands. Setting the successors is safe as all have
67 // already been mapped. We are essentially just creating the operation results
68 // to be able to map them. Cloning the operands and region as well would lead
69 // to uses of operations not yet mapped.
70 auto cloneOptions =
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));
75
76 // Finally now that all operation results have been mapped, set the operands
77 // and clone the regions.
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());
82 llvm::transform(
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);
89 }
90 }
91}
92
93//===----------------------------------------------------------------------===//
94// Loop Unroller
95//===----------------------------------------------------------------------===//
96
97namespace {
98/// A data structure tracking information on a single loop.
99struct Loop {
100 Loop(unsigned loopId, CFGLoop &cfgLoop) : loopId(loopId), cfgLoop(cfgLoop) {}
101 bool failMatch(const Twine &msg) const;
102 bool match();
103 void unroll(CFGLoopInfo &cfgLoopInfo);
104
105 /// A numeric identifier for debugging purposes.
106 unsigned loopId;
107 /// Loop analysis information about this specific loop.
108 CFGLoop &cfgLoop;
109 /// The CFG edge exiting the loop.
110 BlockOperand *exitEdge = nullptr;
111 /// The SSA value holding the exit condition.
112 Value exitCondition;
113 /// Whether the exit condition is inverted, i.e. the contination condition.
114 bool exitInverted;
115 /// The induction variable.
116 Value indVar;
117 /// The updated induction variable passed into the next loop iteration.
118 Value indVarNext;
119 /// The continuation predicate. The loop continues until the induction
120 /// variable compared against the end bound no longer matches this predicate.
121 comb::ICmpPredicate predicate;
122 /// The induction variable increment.
123 APInt indVarIncrement;
124 /// The initial value for the induction variable.
125 APInt beginBound;
126 /// The final value for the induction variable.
127 APInt endBound;
128 /// The number of iterations of the loop.
129 unsigned tripCount = 0;
130};
131} // namespace
132
133static llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const Loop &loop) {
134 os << "#" << loop.loopId << " from ";
135 loop.cfgLoop.getHeader()->printAsOperand(os);
136 os << " to ";
137 loop.cfgLoop.getLoopLatch()->printAsOperand(os);
138 return os;
139}
140
141/// Helper to print a debug message on match failure and return false.
142bool Loop::failMatch(const Twine &msg) const {
143 LLVM_DEBUG(llvm::dbgs() << "- Ignoring loop " << *this << ": " << msg
144 << "\n");
145 return false;
146}
147
148/// Check that the loop matches the specific pattern we understand, and extract
149/// the loop condition and induction variable.
150bool Loop::match() {
151 // Ensure that there is a unique exit point and condition for the loop.
152 SmallVector<BlockOperand *> exits;
153 for (auto *block : cfgLoop.getBlocks())
154 for (auto &edge : block->getTerminator()->getBlockOperands())
155 if (!cfgLoop.contains(edge.get()))
156 exits.push_back(&edge);
157 if (exits.size() != 1)
158 return failMatch("multiple exits");
159 exitEdge = exits.back();
160
161 // The terminator doing the exit must be a conditional branch.
162 auto exitBranch = dyn_cast<cf::CondBranchOp>(exitEdge->getOwner());
163 if (!exitBranch)
164 return failMatch("unsupported exit branch");
165 exitCondition = exitBranch.getCondition();
166 exitInverted = exitEdge->getOperandNumber() == 1;
167
168 // Determine one of the loop bounds and the induction variable based on the
169 // exit condition.
170 if (auto icmpOp = exitCondition.getDefiningOp<comb::ICmpOp>()) {
171 IntegerAttr boundAttr;
172 if (!matchPattern(icmpOp.getRhs(), m_Constant(&boundAttr)))
173 return failMatch("non-constant loop bound");
174 indVar = icmpOp.getLhs();
175 predicate = icmpOp.getPredicate();
176 endBound = boundAttr.getValue();
177 } else {
178 return failMatch("unsupported exit condition");
179 }
180
181 // If the exit condition is not inverted, the predicate is the exit predicate.
182 // Negate it such that we have a continuation predicate.
183 if (!exitInverted)
184 predicate = comb::ICmpOp::getNegatedPredicate(predicate);
185
186 // Determine the initial and next value of the induction variable.
187 auto *header = cfgLoop.getHeader();
188 auto *latch = cfgLoop.getLoopLatch();
189 auto indVarArg = dyn_cast<BlockArgument>(indVar);
190 if (!indVarArg || indVarArg.getOwner() != header)
191 return failMatch("induction variable is not a header block argument");
192 IntegerAttr beginBoundAttr;
193 for (auto &pred : header->getUses()) {
194 auto branchOp = dyn_cast<BranchOpInterface>(pred.getOwner());
195 if (!branchOp)
196 return failMatch("header predecessor terminator is not a branch op");
197 auto indVarValue = branchOp.getSuccessorOperands(
198 pred.getOperandNumber())[indVarArg.getArgNumber()];
199 IntegerAttr boundAttr;
200 if (pred.getOwner()->getBlock() == latch) {
201 indVarNext = indVarValue;
202 } else if (matchPattern(indVarValue, m_Constant(&boundAttr))) {
203 if (!beginBoundAttr)
204 beginBoundAttr = boundAttr;
205 else if (boundAttr != beginBoundAttr)
206 return failMatch("multiple initial bounds");
207 } else {
208 return failMatch("unsupported induction variable value");
209 }
210 }
211 if (!beginBoundAttr)
212 return failMatch("no initial bound");
213 beginBound = beginBoundAttr.getValue();
214
215 // Pattern match the increment operation on the induction variable.
216 if (auto addOp = indVarNext.getDefiningOp<comb::AddOp>();
217 addOp && addOp.getNumOperands() == 2) {
218 if (addOp.getOperand(0) != indVarArg)
219 return failMatch("increment LHS not the induction variable");
220 IntegerAttr incAttr;
221 if (!matchPattern(addOp.getOperand(1), m_Constant(&incAttr)))
222 return failMatch("increment RHS non-constant");
223 indVarIncrement = incAttr.getValue();
224 } else {
225 return failMatch("unsupported increment");
226 }
227
228 std::optional<unsigned> range;
229 // Determine the trip count and loop behavior.
230 // for (unsigned i = N; i < M; i += S) with N <= M and S > 0
231 if (predicate == comb::ICmpPredicate::ult && beginBound.ule(endBound) &&
232 indVarIncrement.sgt(0)) {
233 range = endBound.getZExtValue() - beginBound.getZExtValue();
234 }
235 // for (signed i = N; i < M; i += S) with M > 0, N <= M and S > 0
236 if (predicate == comb::ICmpPredicate::slt && !endBound.isNegative() &&
237 beginBound.sle(endBound) && indVarIncrement.sgt(0)) {
238 range = endBound.getZExtValue() - beginBound.getZExtValue();
239 }
240 // for (signed i = N; i >= M; i += S) for N > 0, M >= 0, S < 0
241 if (predicate == comb::ICmpPredicate::sgt && !beginBound.isNegative() &&
242 endBound.sle(beginBound) && indVarIncrement.isNegative()) {
243 if (!endBound.isNegative())
244 range = beginBound.getZExtValue() - endBound.getZExtValue();
245 // Expressions like >= 0 are converted into > -1, so we handle this case.
246 else if (endBound.isAllOnes())
247 range = beginBound.getZExtValue() + 1;
248 }
249 // for (signless i = N; i == N; i += S) with S != 0
250 if (predicate == comb::ICmpPredicate::eq && indVarIncrement != 0 &&
251 beginBound == endBound) {
252 tripCount = 1;
253 return true;
254 }
255
256 if (!range.has_value())
257 return failMatch("unsupported loop bounds");
258
259 // Calculate the trip count as ceil(range/stride)
260 unsigned stride = indVarIncrement.abs().getZExtValue();
261 tripCount = (*range + stride - 1) / stride;
262 // For now don't expand more than 1k iterations.
263 if (tripCount >= 1024)
264 return failMatch("unsupported loop bounds");
265
266 return true;
267}
268
269/// Unroll the loop by cloning its body blocks and replacing the induction
270/// variable with constant iteration indices.
271void Loop::unroll(CFGLoopInfo &cfgLoopInfo) {
272 LLVM_DEBUG(llvm::dbgs() << "- Unrolling loop " << *this << "\n");
273 UnusedOpPruner pruner;
274
275 // Sort the blocks in the body. This is not strictly necessary, but makes the
276 // pass a lot easier to reason about in tests.
277 auto *header = cfgLoop.getHeader();
278 SmallVector<Block *> orderedBody;
279 for (auto &block : *header->getParent())
280 if (cfgLoop.contains(&block))
281 orderedBody.push_back(&block);
282
283 // Copy the loop body for every iteration of the loop.
284 auto *latch = cfgLoop.getLoopLatch();
285 OpBuilder builder(indVar.getContext());
286 auto indValue = beginBound;
287 for (unsigned trip = 0; trip < tripCount; ++trip) {
288 // Clone the loop body.
289 IRMapping mapper;
290 cloneBlocks(orderedBody, *header->getParent(), header->getIterator(),
291 mapper);
292 auto *clonedHeader = mapper.lookup(header);
293 auto *clonedTail = mapper.lookup(latch);
294
295 // Replace the induction variable with the concrete value.
296 auto iterIndVar = mapper.lookup(indVar);
297 pruner.eraseLaterIfUnused(iterIndVar);
298 builder.setInsertionPointAfterValue(iterIndVar);
299 iterIndVar.replaceAllUsesWith(
300 hw::ConstantOp::create(builder, iterIndVar.getLoc(), indValue));
301
302 // Update all edges to the original loop header to point to the cloned loop
303 // header. Leave the original back-edge untouched.
304 for (auto &blockOperand : llvm::make_early_inc_range(header->getUses()))
305 if (blockOperand.getOwner()->getBlock() != latch)
306 blockOperand.set(clonedHeader);
307
308 // Update the back-edge in the cloned latch to point to the original loop
309 // header, i.e. the next iteration, instead of the cloned loop header.
310 for (auto &blockOperand : clonedTail->getTerminator()->getBlockOperands())
311 if (blockOperand.get() == clonedHeader)
312 blockOperand.set(header);
313
314 // Remove the exit edge in the cloned body, since we statically know that
315 // the loop will continue.
316 auto exitBranchOp =
317 cast<cf::CondBranchOp>(mapper.lookup(exitEdge->getOwner()));
318 Block *continueDest = exitBranchOp.getTrueDest();
319 ValueRange continueDestOperands = exitBranchOp.getTrueDestOperands();
320 if (exitEdge->getOperandNumber() == 0) {
321 continueDest = exitBranchOp.getFalseDest();
322 continueDestOperands = exitBranchOp.getFalseDestOperands();
323 }
324 builder.setInsertionPoint(exitBranchOp);
325 cf::BranchOp::create(builder, exitBranchOp.getLoc(), continueDest,
326 continueDestOperands);
327 pruner.eraseLaterIfUnused(exitBranchOp.getOperands());
328 exitBranchOp.erase();
329
330 // Add the new blocks to the loop body.
331 for (auto *block : orderedBody) {
332 auto *newBlock = mapper.lookup(block);
333 cfgLoop.addBasicBlockToLoop(newBlock, cfgLoopInfo);
334 }
335
336 // Increment the induction variable value.
337 indValue += indVarIncrement;
338 }
339
340 // Now that the loop body has been cloned once for each trip throughout the
341 // loop, we can clean up the final iteration by always breaking out of the
342 // loop. Start by replacing the induction variable with the final value.
343 pruner.eraseLaterIfUnused(indVar);
344 builder.setInsertionPointAfterValue(indVar);
345 indVar.replaceAllUsesWith(
346 hw::ConstantOp::create(builder, indVar.getLoc(), indValue));
347 indVar = {};
348
349 // Remove the continue edge of the exit branch in the loop body, since we
350 // statically know that the loop will exit.
351 auto exitBranchOp = cast<cf::CondBranchOp>(exitEdge->getOwner());
352 Block *exitDest = exitBranchOp.getTrueDest();
353 ValueRange exitDestOperands = exitBranchOp.getTrueDestOperands();
354 if (exitEdge->getOperandNumber() == 1) {
355 exitDest = exitBranchOp.getFalseDest();
356 exitDestOperands = exitBranchOp.getFalseDestOperands();
357 }
358 builder.setInsertionPoint(exitBranchOp);
359 cf::BranchOp::create(builder, exitBranchOp.getLoc(), exitDest,
360 exitDestOperands);
361 pruner.eraseLaterIfUnused(exitBranchOp.getOperands());
362 exitBranchOp.erase();
363 exitEdge = nullptr;
364
365 // Prune any body blocks that have become unreachable.
366 SmallPtrSet<Block *, 8> blocksToPrune;
367 for (auto *block : cfgLoop.getBlocks())
368 if (block->use_empty())
369 blocksToPrune.insert(block);
370 while (!blocksToPrune.empty()) {
371 auto *block = *blocksToPrune.begin();
372 blocksToPrune.erase(block);
373 if (!block->use_empty())
374 continue;
375 for (auto *succ : block->getSuccessors())
376 if (cfgLoop.contains(succ))
377 blocksToPrune.insert(succ);
378 block->dropAllDefinedValueUses();
379 cfgLoopInfo.removeBlock(block);
380 block->erase();
381 }
382
383 // Remove any unused operations and block arguments.
384 pruner.eraseNow();
385
386 // Collapse trivial branches to avoid carrying a lot of useless blocks around
387 // especially when unrolling nested loops.
388 for (auto &block : *header->getParent()) {
389 if (!cfgLoop.contains(&block))
390 continue;
391 while (true) {
392 auto branchOp = dyn_cast<cf::BranchOp>(block.getTerminator());
393 if (!branchOp)
394 break;
395 auto *otherBlock = branchOp.getDest();
396 if (!cfgLoop.contains(otherBlock) || !otherBlock->getSinglePredecessor())
397 break;
398 for (auto [blockArg, branchArg] :
399 llvm::zip(otherBlock->getArguments(), branchOp.getDestOperands()))
400 blockArg.replaceAllUsesWith(branchArg);
401 block.getOperations().splice(branchOp->getIterator(),
402 otherBlock->getOperations());
403 branchOp.erase();
404 cfgLoopInfo.removeBlock(otherBlock);
405 otherBlock->erase();
406 }
407 }
408}
409
410//===----------------------------------------------------------------------===//
411// Pass Infrastructure
412//===----------------------------------------------------------------------===//
413
414namespace {
415struct UnrollLoopsPass
416 : public llhd::impl::UnrollLoopsPassBase<UnrollLoopsPass> {
417 void runOnOperation() override;
418 void runOnOperation(CombinationalOp op);
419};
420} // namespace
421
422void UnrollLoopsPass::runOnOperation() {
423 for (auto op : getOperation().getOps<CombinationalOp>())
424 runOnOperation(op);
425}
426
427void UnrollLoopsPass::runOnOperation(CombinationalOp op) {
428 // There's nothing to do if we only have a single block. MLIR even refuses to
429 // compute a dominator tree in that case.
430 if (op.getBody().hasOneBlock())
431 return;
432
433 // Find the loops.
434 LLVM_DEBUG(llvm::dbgs() << "Unrolling loops in " << op.getLoc() << "\n");
435 DominanceInfo domInfo(op);
436 CFGLoopInfo cfgLoopInfo(domInfo.getDomTree(&op.getBody()));
437
438 // We only support simple loops where there is a single back-edge to the
439 // header, and the latch block has a back-edge to a single header. Create a
440 // data structure for each loop we can potentially unroll. The loops are in
441 // preorder, with outer loops appearing before their child loops.
442 SmallVector<Loop> loops;
443 for (auto *cfgLoop : cfgLoopInfo.getLoopsInPreorder()) {
444 // To simplify unrolling we need a unique latch block branching back to the
445 // header.
446 auto *header = cfgLoop->getHeader();
447 auto *latch = cfgLoop->getLoopLatch();
448 if (!latch)
449 continue;
450
451 LLVM_DEBUG({
452 llvm::dbgs() << "- ";
453 cfgLoop->print(llvm::dbgs(), false, false);
454 llvm::dbgs() << "\n";
455 });
456 Loop loop(loops.size(), *cfgLoop);
457
458 // Ensure that the header block is only a header for the current loop. This
459 // simplifies unrolling.
460 auto *parent = cfgLoop->getParentLoop();
461 while (parent && parent->getHeader() != header)
462 parent = parent->getParentLoop();
463 if (parent) {
464 loop.failMatch("header block shared across multiple loops");
465 continue;
466 }
467
468 // Ensure that the latch block is only a latch for the current loop. This
469 // simplifies unrolling.
470 parent = cfgLoop->getParentLoop();
471 while (parent && !parent->isLoopLatch(latch))
472 parent = parent->getParentLoop();
473 if (parent) {
474 loop.failMatch("latch block shared across multiple loops");
475 continue;
476 }
477
478 // Check if the loop body matches the pattern we can unroll.
479 if (loop.match())
480 loops.push_back(std::move(loop));
481 }
482
483 if (loops.empty())
484 return;
485
486 // Dump some debugging information about the loops we've found.
487 LLVM_DEBUG({
488 auto &os = llvm::dbgs();
489 for (auto &loop : loops) {
490 os << "- Loop " << loop << ":\n";
491 os << " - ";
492 loop.cfgLoop.print(os, false, false);
493 os << "\n";
494 os << " - Exit: ";
495 loop.exitEdge->get()->printAsOperand(os);
496 os << " if ";
497 if (loop.exitInverted)
498 os << "not ";
499 os << loop.exitCondition;
500 os << "\n";
501 os << " - Induction variable: ";
502 loop.indVar.printAsOperand(os, OpPrintingFlags().useLocalScope());
503 os << ", from " << loop.beginBound << ", while " << loop.predicate << " "
504 << loop.endBound << ", increment " << loop.indVarIncrement << "\n";
505 os << " - Trip count: " << loop.tripCount << "\n";
506 }
507 });
508
509 // Unroll the loops. Handling the loops in reverse unrolls inner loops before
510 // their parent loops.
511 for (auto &loop : llvm::reverse(loops))
512 loop.unroll(cfgLoopInfo);
513}
static void cloneBlocks(ArrayRef< Block * > blocks, Region &region, Region::iterator before, IRMapping &mapper)
Clone a list of blocks into a region before the given block.
create(data_type, value)
Definition hw.py:433
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition CalyxOps.cpp:56
OS & operator<<(OS &os, const InnerSymTarget &target)
Printing InnerSymTarget's.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Utility that tracks operations that have potentially become unused and allows them to be cleaned up a...
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.