12660623aSJacques Pienaar //===- ViewOpGraph.cpp - View/write op graphviz graphs --------------------===//
22660623aSJacques Pienaar //
330857107SMehdi Amini // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
456222a06SMehdi Amini // See https://llvm.org/LICENSE.txt for license information.
556222a06SMehdi Amini // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
62660623aSJacques Pienaar //
756222a06SMehdi Amini //===----------------------------------------------------------------------===//
82660623aSJacques Pienaar 
92660623aSJacques Pienaar #include "mlir/Transforms/ViewOpGraph.h"
101834ad4aSRiver Riddle #include "PassDetail.h"
112660623aSJacques Pienaar #include "mlir/IR/Block.h"
122660623aSJacques Pienaar #include "mlir/IR/Operation.h"
13*8d15b7dcSMatthias Springer #include "mlir/Support/IndentedOstream.h"
14*8d15b7dcSMatthias Springer #include "llvm/Support/Format.h"
152660623aSJacques Pienaar 
164562e389SRiver Riddle using namespace mlir;
174562e389SRiver Riddle 
18*8d15b7dcSMatthias Springer static const StringRef kLineStyleDataFlow = "solid";
19*8d15b7dcSMatthias Springer static const StringRef kShapeNode = "ellipse";
20*8d15b7dcSMatthias Springer static const StringRef kShapeNone = "plain";
21*8d15b7dcSMatthias Springer 
22400ad6f9SRiver Riddle /// Return the size limits for eliding large attributes.
23400ad6f9SRiver Riddle static int64_t getLargeAttributeSizeLimit() {
24400ad6f9SRiver Riddle   // Use the default from the printer flags if possible.
25400ad6f9SRiver Riddle   if (Optional<int64_t> limit = OpPrintingFlags().getLargeElementsAttrLimit())
26400ad6f9SRiver Riddle     return *limit;
27400ad6f9SRiver Riddle   return 16;
28400ad6f9SRiver Riddle }
29400ad6f9SRiver Riddle 
30*8d15b7dcSMatthias Springer /// Return all values printed onto a stream as a string.
31*8d15b7dcSMatthias Springer static std::string strFromOs(function_ref<void(raw_ostream &)> func) {
32*8d15b7dcSMatthias Springer   std::string buf;
33*8d15b7dcSMatthias Springer   llvm::raw_string_ostream os(buf);
34*8d15b7dcSMatthias Springer   func(os);
35*8d15b7dcSMatthias Springer   return os.str();
362660623aSJacques Pienaar }
37*8d15b7dcSMatthias Springer 
38*8d15b7dcSMatthias Springer /// Escape special characters such as '\n' and quotation marks.
39*8d15b7dcSMatthias Springer static std::string escapeString(std::string str) {
40*8d15b7dcSMatthias Springer   return strFromOs([&](raw_ostream &os) { os.write_escaped(str); });
412660623aSJacques Pienaar }
42*8d15b7dcSMatthias Springer 
43*8d15b7dcSMatthias Springer /// Put quotation marks around a given string.
44*8d15b7dcSMatthias Springer static std::string quoteString(std::string str) { return "\"" + str + "\""; }
45*8d15b7dcSMatthias Springer 
46*8d15b7dcSMatthias Springer using AttributeMap = llvm::StringMap<std::string>;
47*8d15b7dcSMatthias Springer 
48*8d15b7dcSMatthias Springer namespace {
49*8d15b7dcSMatthias Springer 
50*8d15b7dcSMatthias Springer /// This struct represents a node in the DOT language. Each node has an
51*8d15b7dcSMatthias Springer /// identifier and an optional identifier for the cluster (subgraph) that
52*8d15b7dcSMatthias Springer /// contains the node.
53*8d15b7dcSMatthias Springer /// Note: In the DOT language, edges can be drawn only from nodes to nodes, but
54*8d15b7dcSMatthias Springer /// not between clusters. However, edges can be clipped to the boundary of a
55*8d15b7dcSMatthias Springer /// cluster with `lhead` and `ltail` attributes. Therefore, when creating a new
56*8d15b7dcSMatthias Springer /// cluster, an invisible "anchor" node is created.
57*8d15b7dcSMatthias Springer struct Node {
58*8d15b7dcSMatthias Springer public:
59*8d15b7dcSMatthias Springer   Node(int id = 0, Optional<int> clusterId = llvm::None)
60*8d15b7dcSMatthias Springer       : id(id), clusterId(clusterId) {}
61*8d15b7dcSMatthias Springer 
62*8d15b7dcSMatthias Springer   int id;
63*8d15b7dcSMatthias Springer   Optional<int> clusterId;
642660623aSJacques Pienaar };
652660623aSJacques Pienaar 
66*8d15b7dcSMatthias Springer /// This pass generates a Graphviz dataflow visualization of an MLIR operation.
67*8d15b7dcSMatthias Springer /// Note: See https://www.graphviz.org/doc/info/lang.html for more information
68*8d15b7dcSMatthias Springer /// about the Graphviz DOT language.
69*8d15b7dcSMatthias Springer class PrintOpPass : public ViewOpGraphPassBase<PrintOpPass> {
70*8d15b7dcSMatthias Springer public:
71*8d15b7dcSMatthias Springer   PrintOpPass(raw_ostream &os) : os(os) {}
72*8d15b7dcSMatthias Springer   PrintOpPass(const PrintOpPass &o) : os(o.os.getOStream()) {}
732660623aSJacques Pienaar 
74*8d15b7dcSMatthias Springer   void runOnOperation() override {
75*8d15b7dcSMatthias Springer     emitGraph([&]() {
76*8d15b7dcSMatthias Springer       processOperation(getOperation());
77*8d15b7dcSMatthias Springer       emitAllEdgeStmts();
78*8d15b7dcSMatthias Springer     });
79736ad206SJing Pu   }
80736ad206SJing Pu 
81*8d15b7dcSMatthias Springer private:
82*8d15b7dcSMatthias Springer   /// Emit all edges. This function should be called after all nodes have been
83*8d15b7dcSMatthias Springer   /// emitted.
84*8d15b7dcSMatthias Springer   void emitAllEdgeStmts() {
85*8d15b7dcSMatthias Springer     for (const std::string &edge : edges)
86*8d15b7dcSMatthias Springer       os << edge << ";\n";
87*8d15b7dcSMatthias Springer     edges.clear();
88*8d15b7dcSMatthias Springer   }
8917606a10SJing Pu 
90*8d15b7dcSMatthias Springer   /// Emit a cluster (subgraph). The specified builder generates the body of the
91*8d15b7dcSMatthias Springer   /// cluster. Return the anchor node of the cluster.
92*8d15b7dcSMatthias Springer   Node emitClusterStmt(function_ref<void()> builder, std::string label = "") {
93*8d15b7dcSMatthias Springer     int clusterId = ++counter;
94*8d15b7dcSMatthias Springer     os << "subgraph cluster_" << clusterId << " {\n";
95*8d15b7dcSMatthias Springer     os.indent();
96*8d15b7dcSMatthias Springer     // Emit invisible anchor node from/to which arrows can be drawn.
97*8d15b7dcSMatthias Springer     Node anchorNode = emitNodeStmt(" ", kShapeNone);
98*8d15b7dcSMatthias Springer     os << attrStmt("label", quoteString(escapeString(label))) << ";\n";
99*8d15b7dcSMatthias Springer     builder();
100*8d15b7dcSMatthias Springer     os.unindent();
101*8d15b7dcSMatthias Springer     os << "}\n";
102*8d15b7dcSMatthias Springer     return Node(anchorNode.id, clusterId);
103*8d15b7dcSMatthias Springer   }
104*8d15b7dcSMatthias Springer 
105*8d15b7dcSMatthias Springer   /// Generate an attribute statement.
106*8d15b7dcSMatthias Springer   std::string attrStmt(const Twine &key, const Twine &value) {
107*8d15b7dcSMatthias Springer     return (key + " = " + value).str();
108*8d15b7dcSMatthias Springer   }
109*8d15b7dcSMatthias Springer 
110*8d15b7dcSMatthias Springer   /// Emit an attribute list.
111*8d15b7dcSMatthias Springer   void emitAttrList(raw_ostream &os, const AttributeMap &map) {
112*8d15b7dcSMatthias Springer     os << "[";
113*8d15b7dcSMatthias Springer     interleaveComma(map, os, [&](const auto &it) {
114*8d15b7dcSMatthias Springer       os << attrStmt(it.getKey(), it.getValue());
115*8d15b7dcSMatthias Springer     });
116*8d15b7dcSMatthias Springer     os << "]";
117*8d15b7dcSMatthias Springer   }
118*8d15b7dcSMatthias Springer 
119*8d15b7dcSMatthias Springer   // Print an MLIR attribute to `os`. Large attributes are truncated.
120*8d15b7dcSMatthias Springer   void emitMlirAttr(raw_ostream &os, Attribute attr) {
121400ad6f9SRiver Riddle     // A value used to elide large container attribute.
122400ad6f9SRiver Riddle     int64_t largeAttrLimit = getLargeAttributeSizeLimit();
123*8d15b7dcSMatthias Springer 
1242660623aSJacques Pienaar     // Always emit splat attributes.
125*8d15b7dcSMatthias Springer     if (attr.isa<SplatElementsAttr>()) {
126*8d15b7dcSMatthias Springer       attr.print(os);
127*8d15b7dcSMatthias Springer       return;
1282660623aSJacques Pienaar     }
1292660623aSJacques Pienaar 
1302660623aSJacques Pienaar     // Elide "big" elements attributes.
131*8d15b7dcSMatthias Springer     auto elements = attr.dyn_cast<ElementsAttr>();
132400ad6f9SRiver Riddle     if (elements && elements.getNumElements() > largeAttrLimit) {
1332b86e27dSJacques Pienaar       os << std::string(elements.getType().getRank(), '[') << "..."
1342b86e27dSJacques Pienaar          << std::string(elements.getType().getRank(), ']') << " : "
1352b86e27dSJacques Pienaar          << elements.getType();
136*8d15b7dcSMatthias Springer       return;
1372660623aSJacques Pienaar     }
1382660623aSJacques Pienaar 
139*8d15b7dcSMatthias Springer     auto array = attr.dyn_cast<ArrayAttr>();
140400ad6f9SRiver Riddle     if (array && static_cast<int64_t>(array.size()) > largeAttrLimit) {
141563b5910SJing Pu       os << "[...]";
142*8d15b7dcSMatthias Springer       return;
143563b5910SJing Pu     }
144563b5910SJing Pu 
1452660623aSJacques Pienaar     // Print all other attributes.
146*8d15b7dcSMatthias Springer     attr.print(os);
1472660623aSJacques Pienaar   }
1482660623aSJacques Pienaar 
149*8d15b7dcSMatthias Springer   /// Append an edge to the list of edges.
150*8d15b7dcSMatthias Springer   /// Note: Edges are written to the output stream via `emitAllEdgeStmts`.
151*8d15b7dcSMatthias Springer   void emitEdgeStmt(Node n1, Node n2, std::string label,
152*8d15b7dcSMatthias Springer                     StringRef style = kLineStyleDataFlow) {
153*8d15b7dcSMatthias Springer     AttributeMap attrs;
154*8d15b7dcSMatthias Springer     attrs["style"] = style.str();
155*8d15b7dcSMatthias Springer     // Do not label edges that start/end at a cluster boundary. Such edges are
156*8d15b7dcSMatthias Springer     // clipped at the boundary, but labels are not. This can lead to labels
157*8d15b7dcSMatthias Springer     // floating around without any edge next to them.
158*8d15b7dcSMatthias Springer     if (!n1.clusterId && !n2.clusterId)
159*8d15b7dcSMatthias Springer       attrs["label"] = quoteString(escapeString(label));
160*8d15b7dcSMatthias Springer     // Use `ltail` and `lhead` to draw edges between clusters.
161*8d15b7dcSMatthias Springer     if (n1.clusterId)
162*8d15b7dcSMatthias Springer       attrs["ltail"] = "cluster_" + std::to_string(*n1.clusterId);
163*8d15b7dcSMatthias Springer     if (n2.clusterId)
164*8d15b7dcSMatthias Springer       attrs["lhead"] = "cluster_" + std::to_string(*n2.clusterId);
1652660623aSJacques Pienaar 
166*8d15b7dcSMatthias Springer     edges.push_back(strFromOs([&](raw_ostream &os) {
167*8d15b7dcSMatthias Springer       os << llvm::format("v%i -> v%i ", n1.id, n2.id);
168*8d15b7dcSMatthias Springer       emitAttrList(os, attrs);
169*8d15b7dcSMatthias Springer     }));
17002d7b260SJacques Pienaar   }
1712660623aSJacques Pienaar 
172*8d15b7dcSMatthias Springer   /// Emit a graph. The specified builder generates the body of the graph.
173*8d15b7dcSMatthias Springer   void emitGraph(function_ref<void()> builder) {
174*8d15b7dcSMatthias Springer     os << "digraph G {\n";
175*8d15b7dcSMatthias Springer     os.indent();
176*8d15b7dcSMatthias Springer     // Edges between clusters are allowed only in compound mode.
177*8d15b7dcSMatthias Springer     os << attrStmt("compound", "true") << ";\n";
178*8d15b7dcSMatthias Springer     builder();
179*8d15b7dcSMatthias Springer     os.unindent();
180*8d15b7dcSMatthias Springer     os << "}\n";
1812660623aSJacques Pienaar   }
1822660623aSJacques Pienaar 
183*8d15b7dcSMatthias Springer   /// Emit a node statement.
184*8d15b7dcSMatthias Springer   Node emitNodeStmt(std::string label, StringRef shape = kShapeNode) {
185*8d15b7dcSMatthias Springer     int nodeId = ++counter;
186*8d15b7dcSMatthias Springer     AttributeMap attrs;
187*8d15b7dcSMatthias Springer     attrs["label"] = quoteString(escapeString(label));
188*8d15b7dcSMatthias Springer     attrs["shape"] = shape.str();
189*8d15b7dcSMatthias Springer     os << llvm::format("v%i ", nodeId);
190*8d15b7dcSMatthias Springer     emitAttrList(os, attrs);
191*8d15b7dcSMatthias Springer     os << ";\n";
192*8d15b7dcSMatthias Springer     return Node(nodeId);
1932660623aSJacques Pienaar   }
1942660623aSJacques Pienaar 
195*8d15b7dcSMatthias Springer   /// Generate a label for an operation.
196*8d15b7dcSMatthias Springer   std::string getLabel(Operation *op) {
197*8d15b7dcSMatthias Springer     return strFromOs([&](raw_ostream &os) {
198*8d15b7dcSMatthias Springer       // Print operation name and type.
199*8d15b7dcSMatthias Springer       os << op->getName() << " : (";
200*8d15b7dcSMatthias Springer       interleaveComma(op->getResultTypes(), os);
201*8d15b7dcSMatthias Springer       os << ")\n";
2022660623aSJacques Pienaar 
203*8d15b7dcSMatthias Springer       // Print attributes.
204*8d15b7dcSMatthias Springer       for (const NamedAttribute &attr : op->getAttrs()) {
205*8d15b7dcSMatthias Springer         os << '\n' << attr.first << ": ";
206*8d15b7dcSMatthias Springer         emitMlirAttr(os, attr.second);
207*8d15b7dcSMatthias Springer       }
208*8d15b7dcSMatthias Springer     });
209*8d15b7dcSMatthias Springer   }
210*8d15b7dcSMatthias Springer 
211*8d15b7dcSMatthias Springer   /// Generate a label for a block argument.
212*8d15b7dcSMatthias Springer   std::string getLabel(BlockArgument arg) {
213*8d15b7dcSMatthias Springer     return "arg" + std::to_string(arg.getArgNumber());
214*8d15b7dcSMatthias Springer   }
215*8d15b7dcSMatthias Springer 
216*8d15b7dcSMatthias Springer   /// Process a block. Emit a cluster and one node per block argument and
217*8d15b7dcSMatthias Springer   /// operation inside the cluster.
218*8d15b7dcSMatthias Springer   void processBlock(Block &block) {
219*8d15b7dcSMatthias Springer     emitClusterStmt([&]() {
220*8d15b7dcSMatthias Springer       for (BlockArgument &blockArg : block.getArguments())
221*8d15b7dcSMatthias Springer         valueToNode[blockArg] = emitNodeStmt(getLabel(blockArg));
222*8d15b7dcSMatthias Springer 
223*8d15b7dcSMatthias Springer       // Emit a node for each operation.
224*8d15b7dcSMatthias Springer       for (Operation &op : block)
225*8d15b7dcSMatthias Springer         processOperation(&op);
226*8d15b7dcSMatthias Springer     });
227*8d15b7dcSMatthias Springer   }
228*8d15b7dcSMatthias Springer 
229*8d15b7dcSMatthias Springer   /// Process an operation. If the operation has regions, emit a cluster.
230*8d15b7dcSMatthias Springer   /// Otherwise, emit a node.
231*8d15b7dcSMatthias Springer   void processOperation(Operation *op) {
232*8d15b7dcSMatthias Springer     Node node;
233*8d15b7dcSMatthias Springer     if (op->getNumRegions() > 0) {
234*8d15b7dcSMatthias Springer       // Emit cluster for op with regions.
235*8d15b7dcSMatthias Springer       node = emitClusterStmt(
236*8d15b7dcSMatthias Springer           [&]() {
237*8d15b7dcSMatthias Springer             for (Region &region : op->getRegions())
238*8d15b7dcSMatthias Springer               processRegion(region);
239*8d15b7dcSMatthias Springer           },
240*8d15b7dcSMatthias Springer           getLabel(op));
241*8d15b7dcSMatthias Springer     } else {
242*8d15b7dcSMatthias Springer       node = emitNodeStmt(getLabel(op));
243*8d15b7dcSMatthias Springer     }
244*8d15b7dcSMatthias Springer 
245*8d15b7dcSMatthias Springer     // Insert edges originating from each operand.
246*8d15b7dcSMatthias Springer     unsigned numOperands = op->getNumOperands();
247*8d15b7dcSMatthias Springer     for (unsigned i = 0; i < numOperands; i++)
248*8d15b7dcSMatthias Springer       emitEdgeStmt(valueToNode[op->getOperand(i)], node,
249*8d15b7dcSMatthias Springer                    /*label=*/numOperands == 1 ? "" : std::to_string(i));
250*8d15b7dcSMatthias Springer 
251*8d15b7dcSMatthias Springer     for (Value result : op->getResults())
252*8d15b7dcSMatthias Springer       valueToNode[result] = node;
253*8d15b7dcSMatthias Springer   }
254*8d15b7dcSMatthias Springer 
255*8d15b7dcSMatthias Springer   /// Process a region.
256*8d15b7dcSMatthias Springer   void processRegion(Region &region) {
257*8d15b7dcSMatthias Springer     for (Block &block : region.getBlocks())
258*8d15b7dcSMatthias Springer       processBlock(block);
259*8d15b7dcSMatthias Springer   }
260*8d15b7dcSMatthias Springer 
261*8d15b7dcSMatthias Springer   /// Output stream to write DOT file to.
262*8d15b7dcSMatthias Springer   raw_indented_ostream os;
263*8d15b7dcSMatthias Springer   /// A list of edges. For simplicity, should be emitted after all nodes were
264*8d15b7dcSMatthias Springer   /// emitted.
265*8d15b7dcSMatthias Springer   std::vector<std::string> edges;
266*8d15b7dcSMatthias Springer   /// Mapping of SSA values to Graphviz nodes/clusters.
267*8d15b7dcSMatthias Springer   DenseMap<Value, Node> valueToNode;
268*8d15b7dcSMatthias Springer   /// Counter for generating unique node/subgraph identifiers.
269*8d15b7dcSMatthias Springer   int counter = 0;
2702660623aSJacques Pienaar };
271*8d15b7dcSMatthias Springer 
2722660623aSJacques Pienaar } // namespace
2732660623aSJacques Pienaar 
274*8d15b7dcSMatthias Springer std::unique_ptr<Pass>
275*8d15b7dcSMatthias Springer mlir::createPrintOpGraphPass(raw_ostream &os) {
276*8d15b7dcSMatthias Springer   return std::make_unique<PrintOpPass>(os);
2772660623aSJacques Pienaar }
278