CIRCT  19.0.0git
Analysis.cpp
Go to the documentation of this file.
1 //===- Analysis.cpp - Analysis Pass -----------------------------*- C++ -*-===//
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 //
9 // Contains the definitions of the Analysis pass.
10 //
11 //===----------------------------------------------------------------------===//
12 
16 #include "circt/Support/LLVM.h"
17 #include "mlir/Dialect/Arith/IR/Arith.h"
18 #include "mlir/IR/BuiltinTypes.h"
19 #include "mlir/IR/OperationSupport.h"
20 #include "mlir/IR/PatternMatch.h"
21 #include "mlir/Pass/Pass.h"
22 #include "mlir/Support/IndentedOstream.h"
23 #include "llvm/ADT/TypeSwitch.h"
24 
25 #include <optional>
26 
27 namespace circt {
28 namespace handshake {
29 #define GEN_PASS_DEF_HANDSHAKEDOTPRINT
30 #define GEN_PASS_DEF_HANDSHAKEOPCOUNT
31 #define GEN_PASS_DEF_HANDSHAKEADDIDS
32 #include "circt/Dialect/Handshake/HandshakePasses.h.inc"
33 } // namespace handshake
34 } // namespace circt
35 
36 using namespace circt;
37 using namespace handshake;
38 using namespace mlir;
39 
40 static bool isControlOp(Operation *op) {
41  auto controlInterface = dyn_cast<handshake::ControlInterface>(op);
42  return controlInterface && controlInterface.isControl();
43 }
44 
45 namespace {
46 struct HandshakeDotPrintPass
47  : public circt::handshake::impl::HandshakeDotPrintBase<
48  HandshakeDotPrintPass> {
49  void runOnOperation() override {
50  ModuleOp m = getOperation();
51 
52  // Resolve the instance graph to get a top-level module.
53  std::string topLevel;
55  SmallVector<std::string> sortedFuncs;
56  if (resolveInstanceGraph(m, uses, topLevel, sortedFuncs).failed()) {
57  signalPassFailure();
58  return;
59  }
60 
61  handshake::FuncOp topLevelOp =
62  cast<handshake::FuncOp>(m.lookupSymbol(topLevel));
63 
64  // Create top-level graph.
65  std::error_code ec;
66  llvm::raw_fd_ostream outfile(topLevel + ".dot", ec);
67  mlir::raw_indented_ostream os(outfile);
68 
69  os << "Digraph G {\n";
70  os.indent();
71  os << "splines=spline;\n";
72  os << "compound=true; // Allow edges between clusters\n";
73  dotPrint(os, "TOP", topLevelOp, /*isTop=*/true);
74  os.unindent();
75  os << "}\n";
76  outfile.close();
77  };
78 
79 private:
80  /// Prints an instance of a handshake.func to the graph. Returns the unique
81  /// name that was assigned to the instance.
82  std::string dotPrint(mlir::raw_indented_ostream &os, StringRef parentName,
83  handshake::FuncOp f, bool isTop);
84 
85  /// Maintain a mapping of module names and the number of times one of those
86  /// modules have been instantiated in the design. This is used to generate
87  /// unique names in the output graph.
88  std::map<std::string, unsigned> instanceIdMap;
89 
90  /// A mapping between operations and their unique name in the .dot file.
91  DenseMap<Operation *, std::string> opNameMap;
92 
93  /// A mapping between block arguments and their unique name in the .dot file.
94  DenseMap<Value, std::string> argNameMap;
95 
96  void setUsedByMapping(Value v, Operation *op, StringRef node);
97  void setProducedByMapping(Value v, Operation *op, StringRef node);
98 
99  /// Returns the name of the vertex using 'v' through 'consumer'.
100  std::string getUsedByNode(Value v, Operation *consumer);
101  /// Returns the name of the vertex producing 'v' through 'producer'.
102  std::string getProducedByNode(Value v, Operation *producer);
103 
104  /// Maintain mappings between a value, the operation which (uses/produces) it,
105  /// and the node name which the (tail/head) of an edge should refer to. This
106  /// is used to resolve edges across handshake.instance's.
107  // "'value' used by 'operation*' is used by the 'string' vertex"
108  DenseMap<Value, std::map<Operation *, std::string>> usedByMapping;
109  // "'value' produced by 'operation*' is produced from the 'string' vertex"
110  DenseMap<Value, std::map<Operation *, std::string>> producedByMapping;
111 };
112 
113 struct HandshakeOpCountPass
114  : public circt::handshake::impl::HandshakeOpCountBase<
115  HandshakeOpCountPass> {
116  void runOnOperation() override {
117  ModuleOp m = getOperation();
118 
119  for (auto func : m.getOps<handshake::FuncOp>()) {
120  std::map<std::string, int> cnts;
121  for (Operation &op : func.getOps()) {
122  llvm::TypeSwitch<Operation *, void>(&op)
123  .Case<handshake::ConstantOp>([&](auto) { cnts["Constant"]++; })
124  .Case<handshake::MuxOp>([&](auto) { cnts["Mux"]++; })
125  .Case<handshake::LoadOp>([&](auto) { cnts["Load"]++; })
126  .Case<handshake::StoreOp>([&](auto) { cnts["Store"]++; })
127  .Case<handshake::MergeOp>([&](auto) { cnts["Merge"]++; })
128  .Case<handshake::ForkOp>([&](auto) { cnts["Fork"]++; })
129  .Case<handshake::BranchOp>([&](auto) { cnts["Branch"]++; })
130  .Case<handshake::MemoryOp, handshake::ExternalMemoryOp>(
131  [&](auto) { cnts["Memory"]++; })
132  .Case<handshake::ControlMergeOp>(
133  [&](auto) { cnts["CntrlMerge"]++; })
134  .Case<handshake::SinkOp>([&](auto) { cnts["Sink"]++; })
135  .Case<handshake::SourceOp>([&](auto) { cnts["Source"]++; })
136  .Case<handshake::JoinOp>([&](auto) { cnts["Join"]++; })
137  .Case<handshake::BufferOp>([&](auto) { cnts["Buffer"]++; })
138  .Case<handshake::ConditionalBranchOp>(
139  [&](auto) { cnts["Branch"]++; })
140  .Case<arith::AddIOp>([&](auto) { cnts["Add"]++; })
141  .Case<arith::SubIOp>([&](auto) { cnts["Sub"]++; })
142  .Case<arith::AddIOp>([&](auto) { cnts["Add"]++; })
143  .Case<arith::MulIOp>([&](auto) { cnts["Mul"]++; })
144  .Case<arith::CmpIOp>([&](auto) { cnts["Cmp"]++; })
145  .Case<arith::IndexCastOp, arith::ShLIOp, arith::ShRSIOp,
146  arith::ShRUIOp>([&](auto) { cnts["Ext/Sh"]++; })
147  .Case<handshake::ReturnOp>([&](auto) {})
148  .Default([&](auto op) {
149  llvm::outs() << "Unhandled operation: " << *op << "\n";
150  assert(false);
151  });
152  }
153 
154  llvm::outs() << "// RESOURCES"
155  << "\n";
156  for (auto it : cnts)
157  llvm::outs() << it.first << "\t" << it.second << "\n";
158  llvm::outs() << "// END"
159  << "\n";
160  }
161  }
162 };
163 
164 } // namespace
165 
166 /// Prints an operation to the dot file and returns the unique name for the
167 /// operation within the graph.
168 static std::string dotPrintNode(mlir::raw_indented_ostream &outfile,
169  StringRef instanceName, Operation *op,
170  DenseMap<Operation *, unsigned> &opIDs) {
171 
172  // We use "." to distinguish hierarchy in the dot file, but an op by default
173  // prints using "." between the dialect name and the op name. Replace uses of
174  // "." with "_".
175  std::string opDialectName = op->getName().getStringRef().str();
176  std::replace(opDialectName.begin(), opDialectName.end(), '.', '_');
177  std::string opName = (instanceName + "." + opDialectName).str();
178 
179  // Follow the naming convention used in FIRRTL lowering.
180  auto idAttr = op->getAttrOfType<IntegerAttr>("handshake_id");
181  if (idAttr)
182  opName += "_id" + std::to_string(idAttr.getValue().getZExtValue());
183  else
184  opName += std::to_string(opIDs[op]);
185 
186  outfile << "\"" << opName << "\""
187  << " [";
188 
189  /// Fill color
190  outfile << "fillcolor = ";
191  outfile
192  << llvm::TypeSwitch<Operation *, std::string>(op)
193  .Case<handshake::ForkOp, handshake::LazyForkOp, handshake::MuxOp,
194  handshake::JoinOp>([&](auto) { return "lavender"; })
195  .Case<handshake::BufferOp>([&](auto) { return "lightgreen"; })
196  .Case<handshake::ReturnOp>([&](auto) { return "gold"; })
197  .Case<handshake::SinkOp, handshake::ConstantOp>(
198  [&](auto) { return "gainsboro"; })
199  .Case<handshake::MemoryOp, handshake::LoadOp, handshake::StoreOp>(
200  [&](auto) { return "coral"; })
201  .Case<handshake::MergeOp, handshake::ControlMergeOp,
202  handshake::BranchOp, handshake::ConditionalBranchOp>(
203  [&](auto) { return "lightblue"; })
204  .Default([&](auto) { return "moccasin"; });
205 
206  /// Shape
207  outfile << ", shape=";
208  if (op->getDialect()->getNamespace() == "handshake")
209  outfile << "box";
210  else
211  outfile << "oval";
212 
213  /// Label
214  outfile << ", label=\"";
215  outfile << llvm::TypeSwitch<Operation *, std::string>(op)
216  .Case<handshake::ConstantOp>([&](auto op) {
217  return std::to_string(
218  op->template getAttrOfType<mlir::IntegerAttr>("value")
219  .getValue()
220  .getSExtValue());
221  })
222  .Case<handshake::ControlMergeOp>(
223  [&](auto) { return "cmerge"; })
224  .Case<handshake::ConditionalBranchOp>(
225  [&](auto) { return "cbranch"; })
226  .Case<handshake::BufferOp>([&](auto op) {
227  std::string n = "buffer ";
228  n += stringifyEnum(op.getBufferType());
229  return n;
230  })
231  .Case<arith::AddIOp>([&](auto) { return "+"; })
232  .Case<arith::SubIOp>([&](auto) { return "-"; })
233  .Case<arith::AndIOp>([&](auto) { return "&"; })
234  .Case<arith::OrIOp>([&](auto) { return "|"; })
235  .Case<arith::XOrIOp>([&](auto) { return "^"; })
236  .Case<arith::MulIOp>([&](auto) { return "*"; })
237  .Case<arith::ShRSIOp, arith::ShRUIOp>(
238  [&](auto) { return ">>"; })
239  .Case<arith::ShLIOp>([&](auto) { return "<<"; })
240  .Case<arith::CmpIOp>([&](arith::CmpIOp op) {
241  switch (op.getPredicate()) {
242  case arith::CmpIPredicate::eq:
243  return "==";
244  case arith::CmpIPredicate::ne:
245  return "!=";
246  case arith::CmpIPredicate::uge:
247  case arith::CmpIPredicate::sge:
248  return ">=";
249  case arith::CmpIPredicate::ugt:
250  case arith::CmpIPredicate::sgt:
251  return ">";
252  case arith::CmpIPredicate::ule:
253  case arith::CmpIPredicate::sle:
254  return "<=";
255  case arith::CmpIPredicate::ult:
256  case arith::CmpIPredicate::slt:
257  return "<";
258  }
259  llvm_unreachable("unhandled cmpi predicate");
260  })
261  .Default([&](auto op) {
262  auto opDialect = op->getDialect()->getNamespace();
263  std::string label = op->getName().getStringRef().str();
264  if (opDialect == "handshake")
265  label.erase(0, StringLiteral("handshake.").size());
266 
267  return label;
268  });
269  /// If we have an ID attribute, we'll add the ID of the operation as well.
270  /// This helps crossprobing the diagram with the Handshake IR and waveform
271  /// diagrams.
272  if (idAttr)
273  outfile << " [" << std::to_string(idAttr.getValue().getZExtValue()) << "]";
274 
275  outfile << "\"";
276 
277  /// Style; add dashed border for control nodes
278  outfile << ", style=\"filled";
279  if (isControlOp(op))
280  outfile << ", dashed";
281  outfile << "\"";
282  outfile << "]\n";
283 
284  return opName;
285 }
286 
287 /// Returns true if v is used as a control operand in op
288 static bool isControlOperand(Operation *op, Value v) {
289  if (isControlOp(op))
290  return true;
291 
292  return llvm::TypeSwitch<Operation *, bool>(op)
293  .Case<handshake::MuxOp, handshake::ConditionalBranchOp>(
294  [&](auto op) { return v == op.getOperand(0); })
295  .Case<handshake::ControlMergeOp>([&](auto) { return true; })
296  .Default([](auto) { return false; });
297 }
298 
299 static std::string getLocalName(StringRef instanceName, StringRef suffix) {
300  return (instanceName + "." + suffix).str();
301 }
302 
303 static std::string getArgName(handshake::FuncOp op, unsigned index) {
304  return op.getArgName(index).getValue().str();
305 }
306 
307 static std::string getUniqueArgName(StringRef instanceName,
308  handshake::FuncOp op, unsigned index) {
309  return getLocalName(instanceName, getArgName(op, index));
310 }
311 
312 static std::string getResName(handshake::FuncOp op, unsigned index) {
313  return op.getResName(index).getValue().str();
314 }
315 
316 static std::string getUniqueResName(StringRef instanceName,
317  handshake::FuncOp op, unsigned index) {
318  return getLocalName(instanceName, getResName(op, index));
319 }
320 
321 void HandshakeDotPrintPass::setUsedByMapping(Value v, Operation *op,
322  StringRef node) {
323  usedByMapping[v][op] = node;
324 }
325 void HandshakeDotPrintPass::setProducedByMapping(Value v, Operation *op,
326  StringRef node) {
327  producedByMapping[v][op] = node;
328 }
329 
330 std::string HandshakeDotPrintPass::getUsedByNode(Value v, Operation *consumer) {
331  // Check if there is any mapping registerred for the value-use relation.
332  auto it = usedByMapping.find(v);
333  if (it != usedByMapping.end()) {
334  auto it2 = it->second.find(consumer);
335  if (it2 != it->second.end())
336  return it2->second;
337  }
338 
339  // fallback to the registerred name for the operation
340  auto opNameIt = opNameMap.find(consumer);
341  assert(opNameIt != opNameMap.end() &&
342  "No name registered for the operation!");
343  return opNameIt->second;
344 }
345 
346 std::string HandshakeDotPrintPass::getProducedByNode(Value v,
347  Operation *producer) {
348  // Check if there is any mapping registerred for the value-produce relation.
349  auto it = producedByMapping.find(v);
350  if (it != producedByMapping.end()) {
351  auto it2 = it->second.find(producer);
352  if (it2 != it->second.end())
353  return it2->second;
354  }
355 
356  // fallback to the registerred name for the operation
357  auto opNameIt = opNameMap.find(producer);
358  assert(opNameIt != opNameMap.end() &&
359  "No name registered for the operation!");
360  return opNameIt->second;
361 }
362 
363 /// Emits additional, non-graphviz information about the connection between
364 /// from- and to. This does not have any effect on the graph itself, but may be
365 /// used by other tools to reason about the connectivity between nodes.
366 static void tryAddExtraEdgeInfo(mlir::raw_indented_ostream &os, Operation *from,
367  Value result, Operation *to) {
368  os << " // ";
369 
370  if (from) {
371  // Output port information
372  auto results = from->getResults();
373  unsigned resIdx =
374  std::distance(results.begin(), llvm::find(results, result));
375  auto fromNamedOpInterface = dyn_cast<handshake::NamedIOInterface>(from);
376  if (fromNamedOpInterface) {
377  auto resName = fromNamedOpInterface.getResultName(resIdx);
378  os << " output=\"" << resName << "\"";
379  } else
380  os << " output=\"out" << resIdx << "\"";
381  }
382 
383  if (to) {
384  // Input port information
385  auto ops = to->getOperands();
386  unsigned opIdx = std::distance(ops.begin(), llvm::find(ops, result));
387  auto toNamedOpInterface = dyn_cast<handshake::NamedIOInterface>(to);
388  if (toNamedOpInterface) {
389  auto opName = toNamedOpInterface.getOperandName(opIdx);
390  os << " input=\"" << opName << "\"";
391  } else
392  os << " input=\"in" << opIdx << "\"";
393  }
394 }
395 
396 std::string HandshakeDotPrintPass::dotPrint(mlir::raw_indented_ostream &os,
397  StringRef parentName,
398  handshake::FuncOp f, bool isTop) {
399  // Prints DOT representation of the dataflow graph, used for debugging.
400  DenseMap<Block *, unsigned> blockIDs;
401  std::map<std::string, unsigned> opTypeCntrs;
402  DenseMap<Operation *, unsigned> opIDs;
403  auto name = f.getName();
404  unsigned thisId = instanceIdMap[name.str()]++;
405  std::string instanceName = parentName.str() + "." + name.str();
406  // Follow submodule naming convention from FIRRTL lowering:
407  if (!isTop)
408  instanceName += std::to_string(thisId);
409 
410  /// Maintain a reference to any node in the args, body and result. These are
411  /// used to generate cluster edges at the end of this function, to facilitate
412  /// a nice layout.
413  std::optional<std::string> anyArg, anyBody, anyRes;
414 
415  unsigned i = 0;
416 
417  // Sequentially scan across the operations in the function and assign instance
418  // IDs to each operation.
419  for (Block &block : f) {
420  blockIDs[&block] = i++;
421  for (Operation &op : block)
422  opIDs[&op] = opTypeCntrs[op.getName().getStringRef().str()]++;
423  }
424 
425  if (!isTop) {
426  os << "// Subgraph for instance of " << name << "\n";
427  os << "subgraph \"cluster_" << instanceName << "\" {\n";
428  os.indent();
429  os << "label = \"" << name << "\"\n";
430  os << "labeljust=\"l\"\n";
431  os << "color = \"darkgreen\"\n";
432  }
433  os << "node [shape=box style=filled fillcolor=\"white\"]\n";
434 
435  Block *bodyBlock = &f.getBody().front();
436 
437  /// Print function arg and res nodes.
438  os << "// Function argument nodes\n";
439  std::string argsCluster = "cluster_" + instanceName + "_args";
440  os << "subgraph \"" << argsCluster << "\" {\n";
441  os.indent();
442  // No label or border; the subgraph just forces args to stay together in the
443  // diagram.
444  os << "label=\"\"\n";
445  os << "peripheries=0\n";
446  for (const auto &barg : enumerate(bodyBlock->getArguments())) {
447  auto argName = getArgName(f, barg.index());
448  auto localArgName = getLocalName(instanceName, argName);
449  os << "\"" << localArgName << "\" [shape=diamond";
450  if (barg.index() == bodyBlock->getNumArguments() - 1) // ctrl
451  os << ", style=dashed";
452  os << " label=\"" << argName << "\"";
453  os << "]\n";
454  if (!anyArg.has_value())
455  anyArg = localArgName;
456  }
457  os.unindent();
458  os << "}\n";
459 
460  os << "// Function return nodes\n";
461  std::string resCluster = "cluster_" + instanceName + "_res";
462  os << "subgraph \"" << resCluster << "\" {\n";
463  os.indent();
464  // No label or border; the subgraph just forces args to stay together in the
465  // diagram.
466  os << "label=\"\"\n";
467  os << "peripheries=0\n";
468  // Get the return op; a handshake.func always has a terminator, making this
469  // safe.
470  auto returnOp = *f.getBody().getOps<handshake::ReturnOp>().begin();
471  for (const auto &res : llvm::enumerate(returnOp.getOperands())) {
472  auto resName = getResName(f, res.index());
473  auto uniqueResName = getUniqueResName(instanceName, f, res.index());
474  os << "\"" << uniqueResName << "\" [shape=diamond";
475  if (res.index() == bodyBlock->getNumArguments() - 1) // ctrl
476  os << ", style=dashed";
477  os << " label=\"" << resName << "\"";
478  os << "]\n";
479 
480  // Create a mapping between the return op argument uses and the return
481  // nodes.
482  setUsedByMapping(res.value(), returnOp, uniqueResName);
483 
484  if (!anyRes.has_value())
485  anyRes = uniqueResName;
486  }
487  os.unindent();
488  os << "}\n";
489 
490  /// Print operation nodes.
491  std::string opsCluster = "cluster_" + instanceName + "_ops";
492  os << "subgraph \"" << opsCluster << "\" {\n";
493  os.indent();
494  // No label or border; the subgraph just forces args to stay together in the
495  // diagram.
496  os << "label=\"\"\n";
497  os << "peripheries=0\n";
498  for (Operation &op : *bodyBlock) {
499  if (!isa<handshake::InstanceOp, handshake::ReturnOp>(op)) {
500  // Regular node in the diagram.
501  opNameMap[&op] = dotPrintNode(os, instanceName, &op, opIDs);
502  continue;
503  }
504  auto instOp = dyn_cast<handshake::InstanceOp>(op);
505  if (instOp) {
506  // Recurse into instantiated submodule.
507  auto calledFuncOp =
508  instOp->getParentOfType<ModuleOp>().lookupSymbol<handshake::FuncOp>(
509  instOp.getModule());
510  assert(calledFuncOp);
511  auto subInstanceName = dotPrint(os, instanceName, calledFuncOp, false);
512 
513  // Create a mapping between the instance arguments and the arguments to
514  // the module which it instantiated.
515  for (const auto &arg : llvm::enumerate(instOp.getOperands())) {
516  setUsedByMapping(
517  arg.value(), instOp,
518  getUniqueArgName(subInstanceName, calledFuncOp, arg.index()));
519  }
520  // Create a mapping between the instance results and the results from the
521  // module which it instantiated.
522  for (const auto &res : llvm::enumerate(instOp.getResults())) {
523  setProducedByMapping(
524  res.value(), instOp,
525  getUniqueResName(subInstanceName, calledFuncOp, res.index()));
526  }
527  }
528  }
529  if (!opNameMap.empty())
530  anyBody = opNameMap.begin()->second;
531 
532  os.unindent();
533  os << "}\n";
534 
535  /// Print operation result edges.
536  os << "// Operation result edges\n";
537  for (Operation &op : *bodyBlock) {
538  for (auto result : op.getResults()) {
539  for (auto &u : result.getUses()) {
540  Operation *useOp = u.getOwner();
541  if (useOp->getBlock() == bodyBlock) {
542  os << "\"" << getProducedByNode(result, &op);
543  os << "\" -> \"";
544  os << getUsedByNode(result, useOp) << "\"";
545  if (isControlOp(&op) || isControlOperand(useOp, result))
546  os << " [style=\"dashed\"]";
547 
548  // Add extra, non-graphviz info to the edge.
549  tryAddExtraEdgeInfo(os, &op, result, useOp);
550 
551  os << "\n";
552  }
553  }
554  }
555  }
556 
557  if (!isTop)
558  os << "}\n";
559 
560  /// Print edges for function argument uses.
561  os << "// Function argument edges\n";
562  for (const auto &barg : enumerate(bodyBlock->getArguments())) {
563  auto argName = getArgName(f, barg.index());
564  os << "\"" << getLocalName(instanceName, argName) << "\" [shape=diamond";
565  if (barg.index() == bodyBlock->getNumArguments() - 1)
566  os << ", style=dashed";
567  os << "]\n";
568  for (auto *useOp : barg.value().getUsers()) {
569  os << "\"" << getLocalName(instanceName, argName) << "\" -> \""
570  << getUsedByNode(barg.value(), useOp) << "\"";
571  if (isControlOperand(useOp, barg.value()))
572  os << " [style=\"dashed\"]";
573 
574  tryAddExtraEdgeInfo(os, nullptr, barg.value(), useOp);
575  os << "\n";
576  }
577  }
578 
579  /// Print edges from arguments cluster to ops cluster and ops cluster to
580  /// results cluser, to coerce a nice layout.
581  if (anyArg.has_value() && anyBody.has_value())
582  os << "\"" << anyArg.value() << "\" -> \"" << anyBody.value()
583  << "\" [lhead=\"" << opsCluster << "\" ltail=\"" << argsCluster
584  << "\" style=invis]\n";
585  if (anyBody.has_value() && anyRes.has_value())
586  os << "\"" << anyBody.value() << "\" -> \"" << anyRes.value()
587  << "\" [lhead=\"" << resCluster << "\" ltail=\"" << opsCluster
588  << "\" style=invis]\n";
589 
590  os.unindent();
591  return instanceName;
592 }
593 
594 namespace {
595 struct HandshakeAddIDsPass
596  : public circt::handshake::impl::HandshakeAddIDsBase<HandshakeAddIDsPass> {
597  void runOnOperation() override {
598  handshake::FuncOp funcOp = getOperation();
599  auto *ctx = &getContext();
600  OpBuilder builder(funcOp);
601  funcOp.walk([&](Operation *op) {
602  if (op->hasAttr("handshake_id"))
603  return;
604  llvm::SmallVector<NamedAttribute> attrs;
605  llvm::copy(op->getAttrs(), std::back_inserter(attrs));
606  attrs.push_back(builder.getNamedAttr(
607  "handshake_id",
609  opCounters[op->getName().getStringRef().str()]++)));
610 
611  op->setAttrs(DictionaryAttr::get(ctx, attrs));
612  });
613  };
614 
615 private:
616  /// Maintain a counter for each operation type in the function.
617  std::map<std::string, unsigned> opCounters;
618 };
619 } // namespace
620 
621 std::unique_ptr<mlir::OperationPass<mlir::ModuleOp>>
623  return std::make_unique<HandshakeDotPrintPass>();
624 }
625 
626 std::unique_ptr<mlir::OperationPass<mlir::ModuleOp>>
628  return std::make_unique<HandshakeOpCountPass>();
629 }
630 
631 std::unique_ptr<mlir::Pass> circt::handshake::createHandshakeAddIDsPass() {
632  return std::make_unique<HandshakeAddIDsPass>();
633 }
static bool isControlOp(Operation *op)
Definition: Analysis.cpp:40
static std::string getUniqueResName(StringRef instanceName, handshake::FuncOp op, unsigned index)
Definition: Analysis.cpp:316
static std::string getLocalName(StringRef instanceName, StringRef suffix)
Definition: Analysis.cpp:299
static std::string dotPrintNode(mlir::raw_indented_ostream &outfile, StringRef instanceName, Operation *op, DenseMap< Operation *, unsigned > &opIDs)
Prints an operation to the dot file and returns the unique name for the operation within the graph.
Definition: Analysis.cpp:168
static std::string getArgName(handshake::FuncOp op, unsigned index)
Definition: Analysis.cpp:303
static bool isControlOperand(Operation *op, Value v)
Returns true if v is used as a control operand in op.
Definition: Analysis.cpp:288
static std::string getUniqueArgName(StringRef instanceName, handshake::FuncOp op, unsigned index)
Definition: Analysis.cpp:307
static std::string getResName(handshake::FuncOp op, unsigned index)
Definition: Analysis.cpp:312
static void tryAddExtraEdgeInfo(mlir::raw_indented_ostream &os, Operation *from, Value result, Operation *to)
Emits additional, non-graphviz information about the connection between from- and to.
Definition: Analysis.cpp:366
assert(baseType &&"element must be base type")
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:54
std::unique_ptr< mlir::OperationPass< mlir::ModuleOp > > createHandshakeOpCountPass()
Definition: Analysis.cpp:627
std::map< std::string, std::set< std::string > > InstanceGraph
Iterates over the handshake::FuncOp's in the program to build an instance graph.
LogicalResult resolveInstanceGraph(ModuleOp moduleOp, InstanceGraph &instanceGraph, std::string &topLevel, SmallVectorImpl< std::string > &sortedFuncs)
Iterates over the handshake::FuncOp's in the program to build an instance graph.
std::unique_ptr< mlir::OperationPass< mlir::ModuleOp > > createHandshakeDotPrintPass()
Definition: Analysis.cpp:622
std::unique_ptr< mlir::Pass > createHandshakeAddIDsPass()
Definition: Analysis.cpp:631
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21