CIRCT  20.0.0git
SplitLoops.cpp
Go to the documentation of this file.
1 //===- SplitLoops.cpp -----------------------------------------------------===//
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 
12 #include "mlir/IR/IRMapping.h"
13 #include "mlir/IR/ImplicitLocOpBuilder.h"
14 #include "mlir/Pass/Pass.h"
15 #include "llvm/ADT/SetVector.h"
16 #include "llvm/Support/Debug.h"
17 
18 #define DEBUG_TYPE "arc-split-loops"
19 
20 namespace circt {
21 namespace arc {
22 #define GEN_PASS_DEF_SPLITLOOPS
23 #include "circt/Dialect/Arc/ArcPasses.h.inc"
24 } // namespace arc
25 } // namespace circt
26 
27 using namespace circt;
28 using namespace arc;
29 using namespace hw;
30 using mlir::CallOpInterface;
31 
32 using llvm::SmallSetVector;
33 
34 //===----------------------------------------------------------------------===//
35 // Arc Splitter
36 //===----------------------------------------------------------------------===//
37 
38 namespace {
39 /// A value imported into a split.
40 struct ImportedValue {
41  /// Indicates where this value originates from. If true, the value is an input
42  /// of the original arc. If false the value is exported from a different
43  /// split.
44  unsigned isInput : 1;
45  /// The original arc's input number, or the result number of the split that
46  /// exports this value.
47  unsigned index : 15;
48  /// If this value is exported from a different split, this is that split's
49  /// index.
50  unsigned split : 16;
51 };
52 
53 /// A single arc split out from another arc.
54 struct Split {
55  Split(MLIRContext *context, unsigned index, const APInt &color)
56  : index(index), color(color), block(std::make_unique<Block>()),
57  builder(context) {
58  builder.setInsertionPointToStart(block.get());
59  }
60 
61  /// Map an input of the original arc into this split.
62  void importInput(BlockArgument arg) {
63  importedValues.push_back({true, arg.getArgNumber(), 0});
64  mapping.map(arg, block->addArgument(arg.getType(), arg.getLoc()));
65  }
66 
67  /// Map a value in a different split into this split.
68  void importFromOtherSplit(Value value, Split &otherSplit) {
69  auto resultIdx = otherSplit.exportValue(value);
70  importedValues.push_back({false, resultIdx, otherSplit.index});
71  mapping.map(value, block->addArgument(value.getType(), value.getLoc()));
72  }
73 
74  /// Export a value in this split as an output. Returns result number this
75  /// value will have.
76  unsigned exportValue(Value value) {
77  value = mapping.lookup(value);
78  auto result = exportedValueIndices.insert({value, exportedValues.size()});
79  if (result.second)
80  exportedValues.push_back(value);
81  return result.first->second;
82  }
83 
84  unsigned index;
85  APInt color;
86 
87  std::unique_ptr<Block> block;
88  OpBuilder builder;
89  IRMapping mapping;
90 
91  /// Where each value mapped to a block argument is coming from.
92  SmallVector<ImportedValue> importedValues;
93  /// Which values of this split are exposed as outputs.
94  SmallVector<Value> exportedValues;
95  SmallDenseMap<Value, unsigned> exportedValueIndices;
96 };
97 
98 /// Helper structure to split one arc into multiple ones.
99 struct Splitter {
100  Splitter(MLIRContext *context, Location loc) : context(context), loc(loc) {}
101  void run(Block &block, DenseMap<Operation *, APInt> &coloring);
102  Split &getSplit(const APInt &color);
103 
104  MLIRContext *context;
105  Location loc;
106 
107  /// A split for each distinct operation coloring in the original arc.
108  SmallVector<Split *> splits;
110 
111  /// Where each of the original arc's outputs come from after splitting.
112  SmallVector<ImportedValue> outputs;
113 };
114 } // namespace
115 
116 /// Create a separate arc body block for every unique coloring of operations.
117 void Splitter::run(Block &block, DenseMap<Operation *, APInt> &coloring) {
118  for (auto &op : block.without_terminator()) {
119  auto color = coloring.lookup(&op);
120  auto &split = getSplit(color);
121 
122  // Collect the operands of the current operation.
123  SmallSetVector<Value, 4> operands;
124  op.walk([&](Operation *op) {
125  for (auto operand : op->getOperands())
126  if (operand.getParentBlock() == &block)
127  operands.insert(operand);
128  });
129 
130  // Each operand that is either an input of the original arc or that is
131  // defined by an operation that got moved to a different split, create an
132  // input to the current split for that value.
133  for (auto operand : operands) {
134  if (split.mapping.contains(operand))
135  continue;
136  if (auto blockArg = dyn_cast<BlockArgument>(operand)) {
137  split.importInput(blockArg);
138  continue;
139  }
140  auto *operandOp = operand.getDefiningOp();
141  auto operandColor = coloring.lookup(operandOp);
142  assert(operandOp && color != operandColor);
143  auto &operandSplit = getSplit(operandColor);
144  split.importFromOtherSplit(operand, operandSplit);
145  }
146 
147  // Move the operation into the split.
148  split.builder.clone(op, split.mapping);
149  }
150 
151  // Reconstruct where each of the original arc outputs got mapped to.
152  for (auto operand : block.getTerminator()->getOperands()) {
153  if (auto blockArg = dyn_cast<BlockArgument>(operand)) {
154  outputs.push_back({true, blockArg.getArgNumber(), 0});
155  continue;
156  }
157  auto &operandSplit = getSplit(coloring.lookup(operand.getDefiningOp()));
158  auto resultIdx = operandSplit.exportValue(operand);
159  outputs.push_back({false, resultIdx, operandSplit.index});
160  }
161 
162  // Create the final `arc.output` op for each of the splits.
163  for (auto &split : splits)
164  split->builder.create<arc::OutputOp>(loc, split->exportedValues);
165 }
166 
167 /// Get or create the split for a given operation color.
168 Split &Splitter::getSplit(const APInt &color) {
169  auto &split = splitsByColor[color];
170  if (!split) {
171  auto index = splits.size();
172  LLVM_DEBUG(llvm::dbgs()
173  << "- Creating split " << index << " for " << color << "\n");
174  split = std::make_unique<Split>(context, index, color);
175  splits.push_back(split.get());
176  }
177  return *split;
178 }
179 
180 //===----------------------------------------------------------------------===//
181 // Pass Implementation
182 //===----------------------------------------------------------------------===//
183 
184 namespace {
185 struct SplitLoopsPass : public arc::impl::SplitLoopsBase<SplitLoopsPass> {
186  void runOnOperation() override;
187  void splitArc(Namespace &arcNamespace, DefineOp defOp,
188  ArrayRef<CallOpInterface> arcUses);
189  void replaceArcUse(CallOpInterface arcUse, ArrayRef<DefineOp> splitDefs,
190  ArrayRef<Split *> splits, ArrayRef<ImportedValue> outputs);
191  LogicalResult ensureNoLoops();
192 
193  DenseSet<mlir::CallOpInterface> allArcUses;
194 };
195 } // namespace
196 
197 void SplitLoopsPass::runOnOperation() {
198  auto module = getOperation();
199  allArcUses.clear();
200 
201  // Collect all arc definitions.
202  Namespace arcNamespace;
203  DenseMap<StringAttr, DefineOp> arcDefs;
204  for (auto arcDef : module.getOps<DefineOp>()) {
205  arcNamespace.newName(arcDef.getSymName());
206  arcDefs[arcDef.getSymNameAttr()] = arcDef;
207  }
208 
209  // Collect all arc uses and determine which arcs we should split.
210  SetVector<DefineOp> arcsToSplit;
211  DenseMap<DefineOp, SmallVector<CallOpInterface>> arcUses;
212  SetVector<CallOpInterface> allArcUses;
213 
214  auto result = module.walk([&](CallOpInterface callOp) -> WalkResult {
215  auto refSym = dyn_cast<SymbolRefAttr>(callOp.getCallableForCallee());
216 
217  // If this call is not to an arc, skip it.
218  if (!refSym)
219  return WalkResult::advance();
220  StringAttr leafRef = refSym.getLeafReference();
221  if (!arcDefs.contains(leafRef))
222  return WalkResult::advance();
223 
224  auto defOp = arcDefs.lookup(leafRef);
225  arcUses[defOp].push_back(callOp);
226  allArcUses.insert(callOp);
227 
228  auto clockedOp = dyn_cast<ClockedOpInterface>(callOp.getOperation());
229  if ((!clockedOp || clockedOp.getLatency() == 0) &&
230  callOp->getNumResults() > 1)
231  arcsToSplit.insert(defOp);
232 
233  return WalkResult::advance();
234  });
235 
236  if (result.wasInterrupted())
237  return signalPassFailure();
238 
239  // Split all arcs with more than one result.
240  // TODO: This is ugly and we should only split arcs that are truly involved in
241  // a loop. But detecting the minimal split among the arcs is fairly
242  // non-trivial and needs a dedicated implementation effort.
243  for (auto defOp : arcsToSplit)
244  splitArc(arcNamespace, defOp, arcUses[defOp]);
245 
246  // Ensure that there are no loops through arcs remaining.
247  if (failed(ensureNoLoops()))
248  return signalPassFailure();
249 }
250 
251 /// Split a single arc into a separate arc for each result.
252 void SplitLoopsPass::splitArc(Namespace &arcNamespace, DefineOp defOp,
253  ArrayRef<CallOpInterface> arcUses) {
254  LLVM_DEBUG(llvm::dbgs() << "Splitting arc " << defOp.getSymNameAttr()
255  << "\n");
256 
257  // Mark the operations in the arc according to which result they contribute
258  // to.
259  auto numResults = defOp.getNumResults();
260  DenseMap<Value, APInt> valueColoring;
261  DenseMap<Operation *, APInt> opColoring;
262 
263  for (auto &operand : defOp.getBodyBlock().getTerminator()->getOpOperands())
264  valueColoring.insert(
265  {operand.get(),
266  APInt::getOneBitSet(numResults, operand.getOperandNumber())});
267 
268  for (auto &op : llvm::reverse(defOp.getBodyBlock().without_terminator())) {
269  auto coloring = APInt::getZero(numResults);
270  for (auto result : op.getResults())
271  if (auto it = valueColoring.find(result); it != valueColoring.end())
272  coloring |= it->second;
273  opColoring.insert({&op, coloring});
274  op.walk([&](Operation *op) {
275  for (auto &operand : op->getOpOperands())
276  valueColoring.try_emplace(operand.get(), numResults, 0).first->second |=
277  coloring;
278  });
279  }
280 
281  // Determine the splits for this arc.
282  Splitter splitter(&getContext(), defOp.getLoc());
283  splitter.run(defOp.getBodyBlock(), opColoring);
284 
285  // Materialize the split arc definitions.
286  ImplicitLocOpBuilder builder(defOp.getLoc(), defOp);
287  SmallVector<DefineOp> splitArcs;
288  splitArcs.reserve(splitter.splits.size());
289  for (auto &split : splitter.splits) {
290  auto splitName = defOp.getSymName();
291  if (splitter.splits.size() > 1)
292  splitName = arcNamespace.newName(defOp.getSymName() + "_split_" +
293  Twine(split->index));
294  auto splitArc = builder.create<DefineOp>(
295  splitName, builder.getFunctionType(
296  split->block->getArgumentTypes(),
297  split->block->getTerminator()->getOperandTypes()));
298  splitArc.getBody().push_back(split->block.release());
299  splitArcs.push_back(splitArc);
300  ++numArcsCreated;
301  }
302 
303  // Replace all uses with the new splits and remove the old definition.
304  for (auto arcUse : arcUses)
305  replaceArcUse(arcUse, splitArcs, splitter.splits, splitter.outputs);
306  defOp.erase();
307  ++numArcsRemoved;
308 }
309 
310 /// Replace a use of the original arc with new uses for the splits.
311 void SplitLoopsPass::replaceArcUse(CallOpInterface arcUse,
312  ArrayRef<DefineOp> splitDefs,
313  ArrayRef<Split *> splits,
314  ArrayRef<ImportedValue> outputs) {
315  ImplicitLocOpBuilder builder(arcUse.getLoc(), arcUse);
316  SmallVector<CallOp> newUses(splits.size());
317 
318  // Resolve an `ImportedValue` to either an operand of the original arc or the
319  // result of another split.
320  auto getMappedValue = [&](ImportedValue value) {
321  if (value.isInput)
322  return arcUse.getArgOperands()[value.index];
323  return newUses[value.split].getResult(value.index);
324  };
325 
326  // Collect the operands for each split and create a new use for each. These
327  // are either operands of the original arc, or values from other splits
328  // exported as results.
329  DenseMap<unsigned, unsigned> splitIdxMap;
330  for (auto [i, split] : llvm::enumerate(splits))
331  splitIdxMap[split->index] = i;
332 
333  DenseSet<unsigned> splitsDone;
334  SmallVector<std::pair<const DefineOp, const Split *>> worklist;
335 
336  auto getMappedValuesOrSchedule = [&](ArrayRef<ImportedValue> importedValues,
337  SmallVector<Value> &operands) {
338  for (auto importedValue : importedValues) {
339  if (!importedValue.isInput && !splitsDone.contains(importedValue.split)) {
340  unsigned idx = splitIdxMap[importedValue.split];
341  worklist.push_back({splitDefs[idx], splits[idx]});
342  return false;
343  }
344 
345  operands.push_back(getMappedValue(importedValue));
346  }
347 
348  return true;
349  };
350 
351  // Initialize worklist
352  for (auto [splitDef, split] : llvm::reverse(llvm::zip(splitDefs, splits)))
353  worklist.push_back({splitDef, split});
354 
355  // Process worklist
356  while (!worklist.empty()) {
357  auto [splitDef, split] = worklist.back();
358 
359  if (splitsDone.contains(split->index)) {
360  worklist.pop_back();
361  continue;
362  }
363 
364  SmallVector<Value> operands;
365  if (!getMappedValuesOrSchedule(split->importedValues, operands))
366  continue;
367 
368  auto newUse = builder.create<CallOp>(splitDef, operands);
369  allArcUses.insert(newUse);
370  newUses[split->index] = newUse;
371 
372  splitsDone.insert(split->index);
373  worklist.pop_back();
374  }
375 
376  // Update the users of the original arc results.
377  for (auto [result, importedValue] : llvm::zip(arcUse->getResults(), outputs))
378  result.replaceAllUsesWith(getMappedValue(importedValue));
379  allArcUses.erase(arcUse);
380  arcUse.erase();
381 }
382 
383 /// Check that there are no more zero-latency loops through arcs.
384 LogicalResult SplitLoopsPass::ensureNoLoops() {
385  SmallVector<std::pair<Operation *, unsigned>, 0> worklist;
386  DenseSet<Operation *> finished;
387  DenseSet<Operation *> seen;
388  for (auto op : allArcUses) {
389  if (finished.contains(op))
390  continue;
391  assert(seen.empty());
392  worklist.push_back({op, 0});
393  while (!worklist.empty()) {
394  auto [op, idx] = worklist.back();
395  ++worklist.back().second;
396  if (idx == op->getNumOperands()) {
397  seen.erase(op);
398  finished.insert(op);
399  worklist.pop_back();
400  continue;
401  }
402  auto operand = op->getOperand(idx);
403  auto *def = operand.getDefiningOp();
404  if (!def || finished.contains(def))
405  continue;
406  if (auto clockedOp = dyn_cast<ClockedOpInterface>(def);
407  clockedOp && clockedOp.getLatency() > 0)
408  continue;
409  if (!seen.insert(def).second) {
410  auto d = def->emitError(
411  "loop splitting did not eliminate all loops; loop detected");
412  for (auto [op, idx] : llvm::reverse(worklist)) {
413  d.attachNote(op->getLoc())
414  << "through operand " << (idx - 1) << " here:";
415  if (op == def)
416  break;
417  }
418  return failure();
419  }
420  worklist.push_back({def, 0});
421  }
422  }
423  return success();
424 }
425 
426 std::unique_ptr<Pass> arc::createSplitLoopsPass() {
427  return std::make_unique<SplitLoopsPass>();
428 }
assert(baseType &&"element must be base type")
A namespace that is used to store existing names and generate new names in some scope within the IR.
Definition: Namespace.h:30
StringRef newName(const Twine &name)
Return a unique name, derived from the input name, and add the new name to the internal namespace.
Definition: Namespace.h:85
std::unique_ptr< mlir::Pass > createSplitLoopsPass()
Definition: SplitLoops.cpp:426
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
int run(Type[Generator] generator=CppGenerator, cmdline_args=sys.argv)
Definition: codegen.py:121
Definition: hw.py:1