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