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"
26 using namespace circt;
27 using namespace handshake;
31 auto controlInterface = dyn_cast<handshake::ControlInterface>(op);
32 return controlInterface && controlInterface.isControl();
36 struct HandshakeDotPrintPass
37 :
public HandshakeDotPrintBase<HandshakeDotPrintPass> {
38 void runOnOperation()
override {
39 ModuleOp m = getOperation();
44 SmallVector<std::string> sortedFuncs;
50 handshake::FuncOp topLevelOp =
51 cast<handshake::FuncOp>(m.lookupSymbol(topLevel));
55 llvm::raw_fd_ostream outfile(topLevel +
".dot", ec);
56 mlir::raw_indented_ostream os(outfile);
58 os <<
"Digraph G {\n";
60 os <<
"splines=spline;\n";
61 os <<
"compound=true; // Allow edges between clusters\n";
62 dotPrint(os,
"TOP", topLevelOp,
true);
71 std::string dotPrint(mlir::raw_indented_ostream &os, StringRef parentName,
72 handshake::FuncOp f,
bool isTop);
77 std::map<std::string, unsigned> instanceIdMap;
80 DenseMap<Operation *, std::string> opNameMap;
83 DenseMap<Value, std::string> argNameMap;
85 void setUsedByMapping(Value v, Operation *op, StringRef node);
86 void setProducedByMapping(Value v, Operation *op, StringRef node);
89 std::string getUsedByNode(Value v, Operation *consumer);
91 std::string getProducedByNode(Value v, Operation *producer);
97 DenseMap<Value, std::map<Operation *, std::string>> usedByMapping;
99 DenseMap<Value, std::map<Operation *, std::string>> producedByMapping;
102 struct HandshakeOpCountPass
103 :
public HandshakeOpCountBase<HandshakeOpCountPass> {
104 void runOnOperation()
override {
105 ModuleOp m = getOperation();
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";
145 llvm::outs() << it.first <<
"\t" << it.second <<
"\n";
157 StringRef instanceName, Operation *op,
158 DenseMap<Operation *, unsigned> &opIDs) {
163 std::string opDialectName = op->getName().getStringRef().str();
164 std::replace(opDialectName.begin(), opDialectName.end(),
'.',
'_');
165 std::string opName = (instanceName +
"." + opDialectName).str();
168 auto idAttr = op->getAttrOfType<IntegerAttr>(
"handshake_id");
170 opName +=
"_id" + std::to_string(idAttr.getValue().getZExtValue());
172 opName += std::to_string(opIDs[op]);
174 outfile <<
"\"" << opName <<
"\""
178 outfile <<
"fillcolor = ";
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"; });
195 outfile <<
", shape=";
196 if (op->getDialect()->getNamespace() ==
"handshake")
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")
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());
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:
232 case arith::CmpIPredicate::ne:
234 case arith::CmpIPredicate::uge:
235 case arith::CmpIPredicate::sge:
237 case arith::CmpIPredicate::ugt:
238 case arith::CmpIPredicate::sgt:
240 case arith::CmpIPredicate::ule:
241 case arith::CmpIPredicate::sle:
243 case arith::CmpIPredicate::ult:
244 case arith::CmpIPredicate::slt:
247 llvm_unreachable(
"unhandled cmpi predicate");
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());
261 outfile <<
" [" << std::to_string(idAttr.getValue().getZExtValue()) <<
"]";
266 outfile <<
", style=\"filled";
268 outfile <<
", dashed";
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; });
287 static std::string
getLocalName(StringRef instanceName, StringRef suffix) {
288 return (instanceName +
"." + suffix).str();
291 static std::string
getArgName(handshake::FuncOp op,
unsigned index) {
292 return op.getArgName(index).getValue().str();
296 handshake::FuncOp op,
unsigned index) {
300 static std::string
getResName(handshake::FuncOp op,
unsigned index) {
301 return op.getResName(index).getValue().str();
305 handshake::FuncOp op,
unsigned index) {
309 void HandshakeDotPrintPass::setUsedByMapping(Value v, Operation *op,
311 usedByMapping[v][op] = node;
313 void HandshakeDotPrintPass::setProducedByMapping(Value v, Operation *op,
315 producedByMapping[v][op] = node;
318 std::string HandshakeDotPrintPass::getUsedByNode(Value v, Operation *consumer) {
320 auto it = usedByMapping.find(v);
321 if (it != usedByMapping.end()) {
322 auto it2 = it->second.find(consumer);
323 if (it2 != it->second.end())
328 auto opNameIt = opNameMap.find(consumer);
329 assert(opNameIt != opNameMap.end() &&
330 "No name registered for the operation!");
331 return opNameIt->second;
334 std::string HandshakeDotPrintPass::getProducedByNode(Value v,
335 Operation *producer) {
337 auto it = producedByMapping.find(v);
338 if (it != producedByMapping.end()) {
339 auto it2 = it->second.find(producer);
340 if (it2 != it->second.end())
345 auto opNameIt = opNameMap.find(producer);
346 assert(opNameIt != opNameMap.end() &&
347 "No name registered for the operation!");
348 return opNameIt->second;
355 Value result, Operation *to) {
360 auto results = from->getResults();
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 <<
"\"";
368 os <<
" output=\"out" << resIdx <<
"\"";
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 <<
"\"";
380 os <<
" input=\"in" << opIdx <<
"\"";
384 std::string HandshakeDotPrintPass::dotPrint(mlir::raw_indented_ostream &os,
385 StringRef parentName,
386 handshake::FuncOp f,
bool isTop) {
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();
396 instanceName += std::to_string(thisId);
401 std::optional<std::string> anyArg, anyBody, anyRes;
407 for (Block &block : f) {
408 blockIDs[&block] = i++;
409 for (Operation &op : block)
410 opIDs[&op] = opTypeCntrs[op.getName().getStringRef().str()]++;
414 os <<
"// Subgraph for instance of " << name <<
"\n";
415 os <<
"subgraph \"cluster_" << instanceName <<
"\" {\n";
417 os <<
"label = \"" << name <<
"\"\n";
418 os <<
"labeljust=\"l\"\n";
419 os <<
"color = \"darkgreen\"\n";
421 os <<
"node [shape=box style=filled fillcolor=\"white\"]\n";
423 Block *bodyBlock = &f.getBody().front();
426 os <<
"// Function argument nodes\n";
427 std::string argsCluster =
"cluster_" + instanceName +
"_args";
428 os <<
"subgraph \"" << argsCluster <<
"\" {\n";
432 os <<
"label=\"\"\n";
433 os <<
"peripheries=0\n";
434 for (
const auto &barg : enumerate(bodyBlock->getArguments())) {
436 auto localArgName =
getLocalName(instanceName, argName);
437 os <<
"\"" << localArgName <<
"\" [shape=diamond";
438 if (barg.index() == bodyBlock->getNumArguments() - 1)
439 os <<
", style=dashed";
440 os <<
" label=\"" << argName <<
"\"";
442 if (!anyArg.has_value())
443 anyArg = localArgName;
448 os <<
"// Function return nodes\n";
449 std::string resCluster =
"cluster_" + instanceName +
"_res";
450 os <<
"subgraph \"" << resCluster <<
"\" {\n";
454 os <<
"label=\"\"\n";
455 os <<
"peripheries=0\n";
458 auto returnOp = *f.getBody().getOps<handshake::ReturnOp>().begin();
459 for (
const auto &res : llvm::enumerate(returnOp.getOperands())) {
462 os <<
"\"" << uniqueResName <<
"\" [shape=diamond";
463 if (res.index() == bodyBlock->getNumArguments() - 1)
464 os <<
", style=dashed";
465 os <<
" label=\"" << resName <<
"\"";
470 setUsedByMapping(res.value(), returnOp, uniqueResName);
472 if (!anyRes.has_value())
473 anyRes = uniqueResName;
479 std::string opsCluster =
"cluster_" + instanceName +
"_ops";
480 os <<
"subgraph \"" << opsCluster <<
"\" {\n";
484 os <<
"label=\"\"\n";
485 os <<
"peripheries=0\n";
486 for (Operation &op : *bodyBlock) {
487 if (!isa<handshake::InstanceOp, handshake::ReturnOp>(op)) {
489 opNameMap[&op] =
dotPrintNode(os, instanceName, &op, opIDs);
492 auto instOp = dyn_cast<handshake::InstanceOp>(op);
496 instOp->getParentOfType<ModuleOp>().lookupSymbol<handshake::FuncOp>(
499 auto subInstanceName = dotPrint(os, instanceName, calledFuncOp,
false);
503 for (
const auto &arg : llvm::enumerate(instOp.getOperands())) {
510 for (
const auto &res : llvm::enumerate(instOp.getResults())) {
511 setProducedByMapping(
517 if (!opNameMap.empty())
518 anyBody = opNameMap.begin()->second;
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);
532 os << getUsedByNode(result, useOp) <<
"\"";
534 os <<
" [style=\"dashed\"]";
549 os <<
"// Function argument edges\n";
550 for (
const auto &barg : enumerate(bodyBlock->getArguments())) {
552 os <<
"\"" <<
getLocalName(instanceName, argName) <<
"\" [shape=diamond";
553 if (barg.index() == bodyBlock->getNumArguments() - 1)
554 os <<
", style=dashed";
556 for (
auto *useOp : barg.value().getUsers()) {
557 os <<
"\"" <<
getLocalName(instanceName, argName) <<
"\" -> \""
558 << getUsedByNode(barg.value(), useOp) <<
"\"";
560 os <<
" [style=\"dashed\"]";
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";
583 struct HandshakeAddIDsPass :
public HandshakeAddIDsBase<HandshakeAddIDsPass> {
584 void runOnOperation()
override {
585 handshake::FuncOp funcOp = getOperation();
586 auto *ctx = &getContext();
588 funcOp.walk([&](Operation *op) {
589 if (op->hasAttr(
"handshake_id"))
591 llvm::SmallVector<NamedAttribute> attrs;
592 llvm::copy(op->getAttrs(), std::back_inserter(attrs));
593 attrs.push_back(
builder.getNamedAttr(
596 opCounters[op->getName().getStringRef().str()]++)));
604 std::map<std::string, unsigned> opCounters;
608 std::unique_ptr<mlir::OperationPass<mlir::ModuleOp>>
610 return std::make_unique<HandshakeDotPrintPass>();
613 std::unique_ptr<mlir::OperationPass<mlir::ModuleOp>>
615 return std::make_unique<HandshakeOpCountPass>();
619 return std::make_unique<HandshakeAddIDsPass>();
static bool isControlOp(Operation *op)
static std::string getUniqueResName(StringRef instanceName, handshake::FuncOp op, unsigned index)
static std::string getLocalName(StringRef instanceName, StringRef suffix)
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.
static std::string getArgName(handshake::FuncOp op, unsigned index)
static bool isControlOperand(Operation *op, Value v)
Returns true if v is used as a control operand in op.
static std::string getUniqueArgName(StringRef instanceName, handshake::FuncOp op, unsigned index)
static std::string getResName(handshake::FuncOp op, unsigned index)
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.
assert(baseType &&"element must be base type")
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
std::unique_ptr< mlir::OperationPass< mlir::ModuleOp > > createHandshakeOpCountPass()
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()
std::unique_ptr< mlir::Pass > createHandshakeAddIDsPass()
This file defines an intermediate representation for circuits acting as an abstraction for constraint...
mlir::raw_indented_ostream & outs()