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 ®ion : 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 ®ion) { 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