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"
19#define DEBUG_TYPE "arc-split-loops"
23#define GEN_PASS_DEF_SPLITLOOPS
24#include "circt/Dialect/Arc/ArcPasses.h.inc"
31using mlir::CallOpInterface;
56 Split(MLIRContext *
context,
unsigned index,
const APInt &color)
57 : index(index), color(color), block(std::make_unique<
Block>()),
59 builder.setInsertionPointToStart(block.get());
63 void importInput(BlockArgument arg) {
64 importedValues.push_back({
true, arg.getArgNumber(), 0});
65 mapping.map(arg, block->addArgument(arg.getType(), arg.getLoc()));
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()));
77 unsigned exportValue(Value value) {
78 value = mapping.lookup(value);
79 auto result = exportedValueIndices.insert({value, exportedValues.size()});
81 exportedValues.push_back(value);
82 return result.first->second;
88 std::unique_ptr<Block> block;
93 SmallVector<ImportedValue> importedValues;
95 SmallVector<Value> exportedValues;
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);
105 MLIRContext *context;
109 SmallVector<Split *> splits;
113 SmallVector<ImportedValue> outputs;
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);
125 op.walk([&](Operation *op) {
126 for (
auto operand : op->getOperands())
127 if (operand.getParentBlock() == &block)
128 operands.insert(operand);
134 for (
auto operand : operands) {
135 if (split.mapping.contains(operand))
137 if (
auto blockArg = dyn_cast<BlockArgument>(operand)) {
138 split.importInput(blockArg);
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);
149 split.builder.clone(op, split.mapping);
153 for (
auto operand : block.getTerminator()->getOperands()) {
154 if (
auto blockArg = dyn_cast<BlockArgument>(operand)) {
155 outputs.push_back({
true, blockArg.getArgNumber(), 0});
158 auto &operandSplit = getSplit(coloring.lookup(operand.getDefiningOp()));
159 auto resultIdx = operandSplit.exportValue(operand);
160 outputs.push_back({
false, resultIdx, operandSplit.index});
164 for (
auto &split : splits)
165 arc::OutputOp::create(split->builder, loc, split->exportedValues);
169Split &Splitter::getSplit(
const APInt &color) {
170 auto &split = splitsByColor[color];
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());
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();
194 DenseSet<mlir::CallOpInterface> allArcUses;
198void SplitLoopsPass::runOnOperation() {
199 auto module = getOperation();
204 DenseMap<StringAttr, DefineOp> arcDefs;
205 for (
auto arcDef : module.getOps<DefineOp>()) {
206 arcNamespace.
newName(arcDef.getSymName());
207 arcDefs[arcDef.getSymNameAttr()] = arcDef;
211 SetVector<DefineOp> arcsToSplit;
212 DenseMap<DefineOp, SmallVector<CallOpInterface>> arcUses;
213 SetVector<CallOpInterface> allArcUses;
215 auto result =
module.walk([&](CallOpInterface callOp) -> WalkResult {
216 auto refSym = dyn_cast<SymbolRefAttr>(callOp.getCallableForCallee());
220 return WalkResult::advance();
221 StringAttr leafRef = refSym.getLeafReference();
222 if (!arcDefs.contains(leafRef))
223 return WalkResult::advance();
225 auto defOp = arcDefs.lookup(leafRef);
226 arcUses[defOp].push_back(callOp);
227 allArcUses.insert(callOp);
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);
235 return WalkResult::advance();
238 if (result.wasInterrupted())
239 return signalPassFailure();
245 for (
auto defOp : arcsToSplit)
246 splitArc(arcNamespace, defOp, arcUses[defOp]);
249 if (failed(ensureNoLoops()))
250 return signalPassFailure();
254void SplitLoopsPass::splitArc(
Namespace &arcNamespace, DefineOp defOp,
255 ArrayRef<CallOpInterface> arcUses) {
256 LLVM_DEBUG(llvm::dbgs() <<
"Splitting arc " << defOp.getSymNameAttr()
261 auto numResults = defOp.getNumResults();
262 DenseMap<Value, APInt> valueColoring;
263 DenseMap<Operation *, APInt> opColoring;
265 for (
auto &operand : defOp.
getBodyBlock().getTerminator()->getOpOperands())
266 valueColoring.insert(
268 APInt::getOneBitSet(numResults, operand.getOperandNumber())});
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 |=
284 Splitter splitter(&getContext(), defOp.getLoc());
285 splitter.run(defOp.getBodyBlock(), opColoring);
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));
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);
307 for (
auto arcUse : arcUses)
308 replaceArcUse(arcUse, splitArcs, splitter.splits, splitter.outputs);
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());
323 auto getMappedValue = [&](ImportedValue value) {
325 return arcUse.getArgOperands()[value.index];
326 return newUses[value.split].getResult(value.index);
332 DenseMap<unsigned, unsigned> splitIdxMap;
333 for (
auto [i, split] :
llvm::enumerate(splits))
334 splitIdxMap[split->index] = i;
336 DenseSet<unsigned> splitsDone;
337 SmallVector<std::pair<const DefineOp, const Split *>> worklist;
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]});
348 operands.push_back(getMappedValue(importedValue));
355 for (
auto [splitDef, split] :
llvm::reverse(
llvm::zip(splitDefs, splits)))
356 worklist.push_back({splitDef, split});
359 while (!worklist.empty()) {
360 auto [splitDef, split] = worklist.back();
362 if (splitsDone.contains(split->index)) {
367 SmallVector<Value> operands;
368 if (!getMappedValuesOrSchedule(split->importedValues, operands))
371 auto newUse = CallOp::create(builder, splitDef, operands);
372 allArcUses.insert(newUse);
373 newUses[split->index] = newUse;
375 splitsDone.insert(split->index);
380 for (
auto [result, importedValue] :
llvm::zip(arcUse->getResults(), outputs))
381 result.replaceAllUsesWith(getMappedValue(importedValue));
382 allArcUses.erase(arcUse);
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))
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()) {
405 auto operand = op->getOperand(idx);
406 auto *def = operand.getDefiningOp();
407 if (!def || finished.contains(def))
409 if (
auto clockedOp = dyn_cast<ClockedOpInterface>(def);
410 clockedOp && clockedOp.getLatency() > 0)
412 if (
auto dpiCallOp = dyn_cast<sim::DPICallOp>(def))
413 if (dpiCallOp.getClock())
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:";
426 worklist.push_back({def, 0});
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.
StringRef newName(const Twine &name)
Return a unique name, derived from the input name, and add the new name to the internal namespace.
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
int run(Type[Generator] generator=CppGenerator, cmdline_args=sys.argv)