CIRCT 20.0.0git
Loading...
Searching...
No Matches
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
20namespace circt {
21namespace arc {
22#define GEN_PASS_DEF_SPLITLOOPS
23#include "circt/Dialect/Arc/ArcPasses.h.inc"
24} // namespace arc
25} // namespace circt
26
27using namespace circt;
28using namespace arc;
29using namespace hw;
30using mlir::CallOpInterface;
31
32using llvm::SmallSetVector;
33
34//===----------------------------------------------------------------------===//
35// Arc Splitter
36//===----------------------------------------------------------------------===//
37
38namespace {
39/// A value imported into a split.
40struct 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.
54struct 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.
99struct 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.
117void 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.
168Split &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
184namespace {
185struct 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
197void 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.
252void 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.
311void 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.
384LogicalResult 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
426std::unique_ptr<Pass> arc::createSplitLoopsPass() {
427 return std::make_unique<SplitLoopsPass>();
428}
assert(baseType &&"element must be base type")
static Block * getBodyBlock(FModuleLike mod)
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()
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition CalyxOps.cpp:55
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
int run(Type[Generator] generator=CppGenerator, cmdline_args=sys.argv)
Definition codegen.py:121
Definition hw.py:1