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