1 //===- llvm/Support/GraphWriter.h - Write graph to a .dot file --*- 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 // This file defines a simple interface that can be used to print out generic 10 // LLVM graphs to ".dot" files. "dot" is a tool that is part of the AT&T 11 // graphviz package (http://www.research.att.com/sw/tools/graphviz/) which can 12 // be used to turn the files output by this interface into a variety of 13 // different graphics formats. 14 // 15 // Graphs do not need to implement any interface past what is already required 16 // by the GraphTraits template, but they can choose to implement specializations 17 // of the DOTGraphTraits template if they want to customize the graphs output in 18 // any way. 19 // 20 //===----------------------------------------------------------------------===// 21 22 #ifndef LLVM_SUPPORT_GRAPHWRITER_H 23 #define LLVM_SUPPORT_GRAPHWRITER_H 24 25 #include "llvm/ADT/GraphTraits.h" 26 #include "llvm/ADT/StringRef.h" 27 #include "llvm/ADT/Twine.h" 28 #include "llvm/Support/Compiler.h" 29 #include "llvm/Support/DOTGraphTraits.h" 30 #include "llvm/Support/FileSystem.h" 31 #include "llvm/Support/raw_ostream.h" 32 #include <iterator> 33 #include <string> 34 #include <type_traits> 35 #include <vector> 36 37 namespace llvm { 38 39 namespace DOT { // Private functions... 40 41 LLVM_ABI std::string EscapeString(const std::string &Label); 42 43 /// Get a color string for this node number. Simply round-robin selects 44 /// from a reasonable number of colors. 45 LLVM_ABI StringRef getColorString(unsigned NodeNumber); 46 47 } // end namespace DOT 48 49 namespace GraphProgram { 50 51 enum Name { 52 DOT, 53 FDP, 54 NEATO, 55 TWOPI, 56 CIRCO 57 }; 58 59 } // end namespace GraphProgram 60 61 LLVM_ABI bool DisplayGraph(StringRef Filename, bool wait = true, 62 GraphProgram::Name program = GraphProgram::DOT); 63 64 template<typename GraphType> 65 class GraphWriter { 66 raw_ostream &O; 67 const GraphType &G; 68 bool RenderUsingHTML = false; 69 70 using DOTTraits = DOTGraphTraits<GraphType>; 71 using GTraits = GraphTraits<GraphType>; 72 using NodeRef = typename GTraits::NodeRef; 73 using node_iterator = typename GTraits::nodes_iterator; 74 using child_iterator = typename GTraits::ChildIteratorType; 75 DOTTraits DTraits; 76 77 static_assert(std::is_pointer_v<NodeRef>, 78 "FIXME: Currently GraphWriter requires the NodeRef type to be " 79 "a pointer.\nThe pointer usage should be moved to " 80 "DOTGraphTraits, and removed from GraphWriter itself."); 81 82 // Writes the edge labels of the node to O and returns true if there are any 83 // edge labels not equal to the empty string "". getEdgeSourceLabels(raw_ostream & O,NodeRef Node)84 bool getEdgeSourceLabels(raw_ostream &O, NodeRef Node) { 85 child_iterator EI = GTraits::child_begin(Node); 86 child_iterator EE = GTraits::child_end(Node); 87 bool hasEdgeSourceLabels = false; 88 89 if (RenderUsingHTML) 90 O << "</tr><tr>"; 91 92 for (unsigned i = 0; EI != EE && i != 64; ++EI, ++i) { 93 std::string label = DTraits.getEdgeSourceLabel(Node, EI); 94 95 if (label.empty()) 96 continue; 97 98 hasEdgeSourceLabels = true; 99 100 if (RenderUsingHTML) 101 O << "<td colspan=\"1\" port=\"s" << i << "\">" << label << "</td>"; 102 else { 103 if (i) 104 O << "|"; 105 106 O << "<s" << i << ">" << DOT::EscapeString(label); 107 } 108 } 109 110 if (EI != EE && hasEdgeSourceLabels) { 111 if (RenderUsingHTML) 112 O << "<td colspan=\"1\" port=\"s64\">truncated...</td>"; 113 else 114 O << "|<s64>truncated..."; 115 } 116 117 return hasEdgeSourceLabels; 118 } 119 120 public: GraphWriter(raw_ostream & o,const GraphType & g,bool SN)121 GraphWriter(raw_ostream &o, const GraphType &g, bool SN) : O(o), G(g) { 122 DTraits = DOTTraits(SN); 123 RenderUsingHTML = DTraits.renderNodesUsingHTML(); 124 } 125 126 void writeGraph(const std::string &Title = "") { 127 // Output the header for the graph... 128 writeHeader(Title); 129 130 // Emit all of the nodes in the graph... 131 writeNodes(); 132 133 // Output any customizations on the graph 134 DOTGraphTraits<GraphType>::addCustomGraphFeatures(G, *this); 135 136 // Output the end of the graph 137 writeFooter(); 138 } 139 writeHeader(const std::string & Title)140 void writeHeader(const std::string &Title) { 141 std::string GraphName(DTraits.getGraphName(G)); 142 143 if (!Title.empty()) 144 O << "digraph \"" << DOT::EscapeString(Title) << "\" {\n"; 145 else if (!GraphName.empty()) 146 O << "digraph \"" << DOT::EscapeString(GraphName) << "\" {\n"; 147 else 148 O << "digraph unnamed {\n"; 149 150 if (DTraits.renderGraphFromBottomUp()) 151 O << "\trankdir=\"BT\";\n"; 152 153 if (!Title.empty()) 154 O << "\tlabel=\"" << DOT::EscapeString(Title) << "\";\n"; 155 else if (!GraphName.empty()) 156 O << "\tlabel=\"" << DOT::EscapeString(GraphName) << "\";\n"; 157 O << DTraits.getGraphProperties(G); 158 O << "\n"; 159 } 160 writeFooter()161 void writeFooter() { 162 // Finish off the graph 163 O << "}\n"; 164 } 165 writeNodes()166 void writeNodes() { 167 // Loop over the graph, printing it out... 168 for (const auto Node : nodes<GraphType>(G)) 169 if (!isNodeHidden(Node)) 170 writeNode(Node); 171 } 172 isNodeHidden(NodeRef Node)173 bool isNodeHidden(NodeRef Node) { return DTraits.isNodeHidden(Node, G); } 174 writeNode(NodeRef Node)175 void writeNode(NodeRef Node) { 176 std::string NodeAttributes = DTraits.getNodeAttributes(Node, G); 177 178 O << "\tNode" << static_cast<const void *>(Node) << " [shape="; 179 if (RenderUsingHTML) 180 O << "none,"; 181 else 182 O << "record,"; 183 184 if (!NodeAttributes.empty()) O << NodeAttributes << ","; 185 O << "label="; 186 187 if (RenderUsingHTML) { 188 // Count the numbewr of edges out of the node to determine how 189 // many columns to span (max 64) 190 unsigned ColSpan = 0; 191 child_iterator EI = GTraits::child_begin(Node); 192 child_iterator EE = GTraits::child_end(Node); 193 for (; EI != EE && ColSpan != 64; ++EI, ++ColSpan) 194 ; 195 if (ColSpan == 0) 196 ColSpan = 1; 197 // Include truncated messages when counting. 198 if (EI != EE) 199 ++ColSpan; 200 O << "<<table border=\"0\" cellborder=\"1\" cellspacing=\"0\"" 201 << " cellpadding=\"0\"><tr><td align=\"text\" colspan=\"" << ColSpan 202 << "\">"; 203 } else { 204 O << "\"{"; 205 } 206 207 if (!DTraits.renderGraphFromBottomUp()) { 208 if (RenderUsingHTML) 209 O << DTraits.getNodeLabel(Node, G) << "</td>"; 210 else 211 O << DOT::EscapeString(DTraits.getNodeLabel(Node, G)); 212 213 // If we should include the address of the node in the label, do so now. 214 std::string Id = DTraits.getNodeIdentifierLabel(Node, G); 215 if (!Id.empty()) 216 O << "|" << DOT::EscapeString(Id); 217 218 std::string NodeDesc = DTraits.getNodeDescription(Node, G); 219 if (!NodeDesc.empty()) 220 O << "|" << DOT::EscapeString(NodeDesc); 221 } 222 223 std::string edgeSourceLabels; 224 raw_string_ostream EdgeSourceLabels(edgeSourceLabels); 225 bool hasEdgeSourceLabels = getEdgeSourceLabels(EdgeSourceLabels, Node); 226 227 if (hasEdgeSourceLabels) { 228 if (!DTraits.renderGraphFromBottomUp()) 229 if (!RenderUsingHTML) 230 O << "|"; 231 232 if (RenderUsingHTML) 233 O << edgeSourceLabels; 234 else 235 O << "{" << edgeSourceLabels << "}"; 236 237 if (DTraits.renderGraphFromBottomUp()) 238 if (!RenderUsingHTML) 239 O << "|"; 240 } 241 242 if (DTraits.renderGraphFromBottomUp()) { 243 if (RenderUsingHTML) 244 O << DTraits.getNodeLabel(Node, G); 245 else 246 O << DOT::EscapeString(DTraits.getNodeLabel(Node, G)); 247 248 // If we should include the address of the node in the label, do so now. 249 std::string Id = DTraits.getNodeIdentifierLabel(Node, G); 250 if (!Id.empty()) 251 O << "|" << DOT::EscapeString(Id); 252 253 std::string NodeDesc = DTraits.getNodeDescription(Node, G); 254 if (!NodeDesc.empty()) 255 O << "|" << DOT::EscapeString(NodeDesc); 256 } 257 258 if (DTraits.hasEdgeDestLabels()) { 259 O << "|{"; 260 261 unsigned i = 0, e = DTraits.numEdgeDestLabels(Node); 262 for (; i != e && i != 64; ++i) { 263 if (i) O << "|"; 264 O << "<d" << i << ">" 265 << DOT::EscapeString(DTraits.getEdgeDestLabel(Node, i)); 266 } 267 268 if (i != e) 269 O << "|<d64>truncated..."; 270 O << "}"; 271 } 272 273 if (RenderUsingHTML) 274 O << "</tr></table>>"; 275 else 276 O << "}\""; 277 O << "];\n"; // Finish printing the "node" line 278 279 // Output all of the edges now 280 child_iterator EI = GTraits::child_begin(Node); 281 child_iterator EE = GTraits::child_end(Node); 282 for (unsigned i = 0; EI != EE && i != 64; ++EI, ++i) 283 if (!DTraits.isNodeHidden(*EI, G)) 284 writeEdge(Node, i, EI); 285 for (; EI != EE; ++EI) 286 if (!DTraits.isNodeHidden(*EI, G)) 287 writeEdge(Node, 64, EI); 288 } 289 writeEdge(NodeRef Node,unsigned edgeidx,child_iterator EI)290 void writeEdge(NodeRef Node, unsigned edgeidx, child_iterator EI) { 291 if (NodeRef TargetNode = *EI) { 292 int DestPort = -1; 293 if (DTraits.edgeTargetsEdgeSource(Node, EI)) { 294 child_iterator TargetIt = DTraits.getEdgeTarget(Node, EI); 295 296 // Figure out which edge this targets... 297 unsigned Offset = 298 (unsigned)std::distance(GTraits::child_begin(TargetNode), TargetIt); 299 DestPort = static_cast<int>(Offset); 300 } 301 302 if (DTraits.getEdgeSourceLabel(Node, EI).empty()) 303 edgeidx = -1; 304 305 emitEdge(static_cast<const void*>(Node), edgeidx, 306 static_cast<const void*>(TargetNode), DestPort, 307 DTraits.getEdgeAttributes(Node, EI, G)); 308 } 309 } 310 311 /// emitSimpleNode - Outputs a simple (non-record) node 312 void emitSimpleNode(const void *ID, const std::string &Attr, 313 const std::string &Label, unsigned NumEdgeSources = 0, 314 const std::vector<std::string> *EdgeSourceLabels = nullptr) { 315 O << "\tNode" << ID << "[ "; 316 if (!Attr.empty()) 317 O << Attr << ","; 318 O << " label =\""; 319 if (NumEdgeSources) O << "{"; 320 O << DOT::EscapeString(Label); 321 if (NumEdgeSources) { 322 O << "|{"; 323 324 for (unsigned i = 0; i != NumEdgeSources; ++i) { 325 if (i) O << "|"; 326 O << "<s" << i << ">"; 327 if (EdgeSourceLabels) O << DOT::EscapeString((*EdgeSourceLabels)[i]); 328 } 329 O << "}}"; 330 } 331 O << "\"];\n"; 332 } 333 334 /// emitEdge - Output an edge from a simple node into the graph... emitEdge(const void * SrcNodeID,int SrcNodePort,const void * DestNodeID,int DestNodePort,const std::string & Attrs)335 void emitEdge(const void *SrcNodeID, int SrcNodePort, 336 const void *DestNodeID, int DestNodePort, 337 const std::string &Attrs) { 338 if (SrcNodePort > 64) return; // Eminating from truncated part? 339 if (DestNodePort > 64) DestNodePort = 64; // Targeting the truncated part? 340 341 O << "\tNode" << SrcNodeID; 342 if (SrcNodePort >= 0) 343 O << ":s" << SrcNodePort; 344 O << " -> Node" << DestNodeID; 345 if (DestNodePort >= 0 && DTraits.hasEdgeDestLabels()) 346 O << ":d" << DestNodePort; 347 348 if (!Attrs.empty()) 349 O << "[" << Attrs << "]"; 350 O << ";\n"; 351 } 352 353 /// getOStream - Get the raw output stream into the graph file. Useful to 354 /// write fancy things using addCustomGraphFeatures(). getOStream()355 raw_ostream &getOStream() { 356 return O; 357 } 358 }; 359 360 template<typename GraphType> 361 raw_ostream &WriteGraph(raw_ostream &O, const GraphType &G, 362 bool ShortNames = false, 363 const Twine &Title = "") { 364 // Start the graph emission process... 365 GraphWriter<GraphType> W(O, G, ShortNames); 366 367 // Emit the graph. 368 W.writeGraph(Title.str()); 369 370 return O; 371 } 372 373 LLVM_ABI std::string createGraphFilename(const Twine &Name, int &FD); 374 375 /// Writes graph into a provided @c Filename. 376 /// If @c Filename is empty, generates a random one. 377 /// \return The resulting filename, or an empty string if writing 378 /// failed. 379 template <typename GraphType> 380 std::string WriteGraph(const GraphType &G, const Twine &Name, 381 bool ShortNames = false, 382 const Twine &Title = "", 383 std::string Filename = "") { 384 int FD; 385 if (Filename.empty()) { 386 Filename = createGraphFilename(Name.str(), FD); 387 } else { 388 std::error_code EC = sys::fs::openFileForWrite( 389 Filename, FD, sys::fs::CD_CreateAlways, sys::fs::OF_Text); 390 391 // Writing over an existing file is not considered an error. 392 if (EC == std::errc::file_exists) { 393 errs() << "file exists, overwriting" << "\n"; 394 } else if (EC) { 395 errs() << "error writing into file" << "\n"; 396 return ""; 397 } else { 398 errs() << "writing to the newly created file " << Filename << "\n"; 399 } 400 } 401 raw_fd_ostream O(FD, /*shouldClose=*/ true); 402 403 if (FD == -1) { 404 errs() << "error opening file '" << Filename << "' for writing!\n"; 405 return ""; 406 } 407 408 llvm::WriteGraph(O, G, ShortNames, Title); 409 errs() << " done. \n"; 410 411 return Filename; 412 } 413 414 /// DumpDotGraph - Just dump a dot graph to the user-provided file name. 415 #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) 416 template <typename GraphType> 417 LLVM_DUMP_METHOD void 418 dumpDotGraphToFile(const GraphType &G, const Twine &FileName, 419 const Twine &Title, bool ShortNames = false, 420 const Twine &Name = "") { 421 llvm::WriteGraph(G, Name, ShortNames, Title, FileName.str()); 422 } 423 #endif 424 425 /// ViewGraph - Emit a dot graph, run 'dot', run gv on the postscript file, 426 /// then cleanup. For use from the debugger. 427 /// 428 template<typename GraphType> 429 void ViewGraph(const GraphType &G, const Twine &Name, 430 bool ShortNames = false, const Twine &Title = "", 431 GraphProgram::Name Program = GraphProgram::DOT) { 432 std::string Filename = llvm::WriteGraph(G, Name, ShortNames, Title); 433 434 if (Filename.empty()) 435 return; 436 437 DisplayGraph(Filename, false, Program); 438 } 439 440 } // end namespace llvm 441 442 #endif // LLVM_SUPPORT_GRAPHWRITER_H 443