MLIR 24.0.0git
ViewOpGraph.cpp
Go to the documentation of this file.
1//===- ViewOpGraph.cpp - View/write op graphviz graphs --------------------===//
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
10
11#include "mlir/IR/AsmState.h"
12#include "mlir/IR/Block.h"
14#include "mlir/IR/Operation.h"
16#include "mlir/Pass/Pass.h"
18#include "llvm/ADT/STLExtras.h"
19#include "llvm/Support/Format.h"
20#include "llvm/Support/GraphWriter.h"
21#include <map>
22#include <optional>
23#include <utility>
24
25namespace mlir {
26#define GEN_PASS_DEF_VIEWOPGRAPHPASS
27#include "mlir/Transforms/Passes.h.inc"
28} // namespace mlir
29
30using namespace mlir;
31
32static const StringRef kLineStyleControlFlow = "dashed";
33static const StringRef kLineStyleDataFlow = "solid";
34static const StringRef kShapeNode = "Mrecord";
35static const StringRef kShapeNone = "plain";
36
37/// Return the size limits for eliding large attributes.
39 // Use the default from the printer flags if possible.
40 if (std::optional<int64_t> limit =
41 OpPrintingFlags().getLargeElementsAttrLimit())
42 return *limit;
43 return 16;
44}
45
46/// Return all values printed onto a stream as a string.
47static std::string strFromOs(function_ref<void(raw_ostream &)> func) {
48 std::string buf;
49 llvm::raw_string_ostream os(buf);
50 func(os);
51 return buf;
52}
53
54/// Put quotation marks around a given string.
55static std::string quoteString(const std::string &str) {
56 return "\"" + str + "\"";
57}
58
59/// For Graphviz record nodes:
60/// " Braces, vertical bars and angle brackets must be escaped with a backslash
61/// character if you wish them to appear as a literal character "
62static std::string escapeLabelString(const std::string &str) {
63 std::string buf;
64 llvm::raw_string_ostream os(buf);
65 for (char c : str) {
66 if (llvm::is_contained({'{', '|', '<', '}', '>', '\n', '"'}, c))
67 os << '\\';
68 os << c;
69 }
70 return buf;
71}
72
73using AttributeMap = std::map<std::string, std::string>;
74
75namespace {
76/// This struct represents a node in the DOT language. Each node has an
77/// identifier and an optional identifier for the cluster (subgraph) that
78/// contains the node.
79/// Note: In the DOT language, edges can be drawn only from nodes to nodes, but
80/// not between clusters. However, edges can be clipped to the boundary of a
81/// cluster with `lhead` and `ltail` attributes. Therefore, when creating a new
82/// cluster, an invisible "anchor" node is created.
83struct Node {
84public:
85 Node(int id = 0, std::optional<int> clusterId = std::nullopt)
86 : id(id), clusterId(clusterId) {}
87
88 int id;
89 std::optional<int> clusterId;
90};
91
92struct DataFlowEdge {
93 Value value;
94 Node node;
95 std::string port;
96};
97
98/// Printer options for Graphviz visualization of an MLIR operation.
99struct OpGraphPrinterOptions {
100 /// Limit attribute/type length to number of chars.
101 unsigned maxLabelLen = 20;
102 /// Print attributes of operations.
103 bool printAttrs = true;
104 /// Print control flow edges.
105 bool printControlFlowEdges = false;
106 /// Print data flow edges.
107 bool printDataFlowEdges = true;
108 /// Print result types of operations.
109 bool printResultTypes = true;
110
111 /// Default options for printing a CFG region.
112 static OpGraphPrinterOptions cfgRegionDefaults() {
113 OpGraphPrinterOptions options;
114 options.printControlFlowEdges = true;
115 options.printDataFlowEdges = false;
116 return options;
117 }
118};
119
120/// Printer to emit a Graphviz dataflow visualization of an MLIR operation.
121/// Note: See https://www.graphviz.org/doc/info/lang.html for more information
122/// about the Graphviz DOT language.
123class OpGraphPrinter {
124public:
125 OpGraphPrinter(const OpGraphPrinterOptions &options, raw_ostream &os,
126 Operation *asmStateOp,
127 const OpPrintingFlags &opPrintingFlags = OpPrintingFlags())
128 : opts(options), os(os), asmState(asmStateOp, opPrintingFlags) {}
129
130 void emitOperationGraph(Operation &op) {
131 initColorMapping(op);
132 emitGraph([&]() {
133 processOperation(&op);
134 emitAllEdgeStmts();
135 });
136 }
137
138 void emitRegionCFGGraph(Region &region) {
139 initColorMapping(region);
140 emitGraph([&]() { processRegion(region); });
141 }
142
143private:
144 /// Generate a color mapping that will color every operation with the same
145 /// name the same way. It'll interpolate the hue in the HSV color-space,
146 /// using muted colors that provide good contrast for black text.
147 template <typename T>
148 void initColorMapping(T &irEntity) {
149 backgroundColors.clear();
151 irEntity.walk([&](Operation *op) {
152 auto &entry = backgroundColors[op->getName()];
153 if (entry.first == 0)
154 ops.push_back(op);
155 ++entry.first;
156 });
157 for (auto indexedOps : llvm::enumerate(ops)) {
158 double hue = ((double)indexedOps.index()) / ops.size();
159 // Use lower saturation (0.3) and higher value (0.95) for better
160 // readability
161 backgroundColors[indexedOps.value()->getName()].second =
162 std::to_string(hue) + " 0.3 0.95";
163 }
164 }
165
166 /// Emit all edges. This function should be called after all nodes have been
167 /// emitted.
168 void emitAllEdgeStmts() {
169 if (opts.printDataFlowEdges) {
170 for (const auto &e : dataFlowEdges) {
171 emitEdgeStmt(valueToNode[e.value], e.node, e.port, kLineStyleDataFlow);
172 }
173 }
174
175 for (const std::string &edge : edges)
176 os << edge << ";\n";
177 edges.clear();
178 }
179
180 /// Emit a cluster (subgraph). The specified builder generates the body of the
181 /// cluster. Return the anchor node of the cluster.
182 Node emitClusterStmt(function_ref<void()> builder,
183 const std::string &label = "") {
184 int clusterId = ++counter;
185 os << "subgraph cluster_" << clusterId << " {\n";
186 os.indent();
187 // Emit invisible anchor node from/to which arrows can be drawn.
188 Node anchorNode = emitNodeStmt(" ", kShapeNone);
189 os << attrStmt("label", quoteString(label)) << ";\n";
190 builder();
191 os.unindent();
192 os << "}\n";
193 return Node(anchorNode.id, clusterId);
194 }
195
196 /// Generate an attribute statement.
197 std::string attrStmt(const Twine &key, const Twine &value) {
198 return (key + " = " + value).str();
199 }
200
201 /// Emit an attribute list.
202 void emitAttrList(raw_ostream &os, const AttributeMap &map) {
203 os << "[";
204 interleaveComma(map, os, [&](const auto &it) {
205 os << this->attrStmt(it.first, it.second);
206 });
207 os << "]";
208 }
209
210 // Print an MLIR attribute to `os`. Large attributes are truncated.
211 void emitMlirAttr(raw_ostream &os, Attribute attr) {
212 // A value used to elide large container attribute.
213 int64_t largeAttrLimit = getLargeAttributeSizeLimit();
214
215 // Always emit splat attributes.
216 if (isa<SplatElementsAttr>(attr)) {
217 os << escapeLabelString(
218 strFromOs([&](raw_ostream &os) { attr.print(os); }));
219 return;
220 }
221
222 // Elide "big" elements attributes.
223 auto elements = dyn_cast<ElementsAttr>(attr);
224 if (elements && elements.getNumElements() > largeAttrLimit) {
225 os << std::string(elements.getShapedType().getRank(), '[') << "..."
226 << std::string(elements.getShapedType().getRank(), ']') << " : ";
227 emitMlirType(os, elements.getType());
228 return;
229 }
230
231 auto array = dyn_cast<ArrayAttr>(attr);
232 if (array && static_cast<int64_t>(array.size()) > largeAttrLimit) {
233 os << "[...]";
234 return;
235 }
236
237 // Print all other attributes.
238 std::string buf;
239 llvm::raw_string_ostream ss(buf);
240 attr.print(ss);
241 os << escapeLabelString(truncateString(buf));
242 }
243
244 // Print a truncated and escaped MLIR type to `os`.
245 void emitMlirType(raw_ostream &os, Type type) {
246 std::string buf;
247 llvm::raw_string_ostream ss(buf);
248 type.print(ss);
249 os << escapeLabelString(truncateString(buf));
250 }
251
252 // Print a truncated and escaped MLIR operand to `os`.
253 void emitMlirOperand(raw_ostream &os, Value operand) {
254 operand.printAsOperand(os, asmState);
255 }
256
257 /// Append an edge to the list of edges.
258 /// Note: Edges are written to the output stream via `emitAllEdgeStmts`.
259 void emitEdgeStmt(Node n1, Node n2, std::string port, StringRef style) {
260 AttributeMap attrs;
261 attrs["style"] = style.str();
262 // Use `ltail` and `lhead` to draw edges between clusters.
263 if (n1.clusterId)
264 attrs["ltail"] = "cluster_" + std::to_string(*n1.clusterId);
265 if (n2.clusterId)
266 attrs["lhead"] = "cluster_" + std::to_string(*n2.clusterId);
267
268 edges.push_back(strFromOs([&](raw_ostream &os) {
269 os << "v" << n1.id;
270 if (!port.empty() && !n1.clusterId)
271 // Attach edge to south compass point of the result
272 os << ":res" << port << ":s";
273 os << " -> ";
274 os << "v" << n2.id;
275 if (!port.empty() && !n2.clusterId)
276 // Attach edge to north compass point of the operand
277 os << ":arg" << port << ":n";
278 emitAttrList(os, attrs);
279 }));
280 }
281
282 /// Emit a graph. The specified builder generates the body of the graph.
283 void emitGraph(function_ref<void()> builder) {
284 os << "digraph G {\n";
285 os.indent();
286 // Edges between clusters are allowed only in compound mode.
287 os << attrStmt("compound", "true") << ";\n";
288 builder();
289 os.unindent();
290 os << "}\n";
291 }
292
293 /// Emit a node statement.
294 Node emitNodeStmt(const std::string &label, StringRef shape = kShapeNode,
295 StringRef background = "") {
296 int nodeId = ++counter;
297 AttributeMap attrs;
298 attrs["label"] = quoteString(label);
299 attrs["shape"] = shape.str();
300 if (!background.empty()) {
301 attrs["style"] = "filled";
302 attrs["fillcolor"] = quoteString(background.str());
303 }
304 os << llvm::format("v%i ", nodeId);
305 emitAttrList(os, attrs);
306 os << ";\n";
307 return Node(nodeId);
308 }
309
310 std::string getValuePortName(Value operand) {
311 // Print value as an operand and omit the leading '%' character.
312 auto str = strFromOs(
313 [&](raw_ostream &os) { operand.printAsOperand(os, asmState); });
314 // Replace % and # with _
315 llvm::replace(str, '%', '_');
316 llvm::replace(str, '#', '_');
317 return str;
318 }
319
320 std::string getClusterLabel(Operation *op) {
321 return strFromOs([&](raw_ostream &os) {
322 // Print operation name and type.
323 os << op->getName();
324 if (opts.printResultTypes) {
325 os << " : (";
326 std::string buf;
327 llvm::raw_string_ostream ss(buf);
328 interleaveComma(op->getResultTypes(), ss);
329 os << truncateString(buf) << ")";
330 }
331
332 // Print attributes.
333 if (opts.printAttrs) {
334 os << "\\l";
337 op,
338 [&](StringRef name, Attribute &attr) { attrs.append(name, attr); });
339 for (const NamedAttribute &attr : attrs) {
340 os << escapeLabelString(attr.getName().getValue().str()) << ": ";
341 emitMlirAttr(os, attr.getValue());
342 os << "\\l";
343 }
344 }
345 });
346 }
347
348 /// Generate a label for an operation.
349 std::string getRecordLabel(Operation *op) {
350 return strFromOs([&](raw_ostream &os) {
351 os << "{";
352
353 // Print operation inputs.
354 if (op->getNumOperands() > 0) {
355 os << "{";
356 auto operandToPort = [&](Value operand) {
357 os << "<arg" << getValuePortName(operand) << "> ";
358 emitMlirOperand(os, operand);
359 };
360 interleave(op->getOperands(), os, operandToPort, "|");
361 os << "}|";
362 }
363 // Print operation name and type.
364 os << op->getName() << "\\l";
365
366 // Print attributes.
367 if (opts.printAttrs) {
370 op,
371 [&](StringRef name, Attribute &attr) { attrs.append(name, attr); });
372 if (!attrs.empty()) {
373 // Extra line break to separate attributes from the operation name.
374 os << "\\l";
375 for (const NamedAttribute &attr : attrs) {
376 os << attr.getName().getValue() << ": ";
377 emitMlirAttr(os, attr.getValue());
378 os << "\\l";
379 }
380 }
381 }
382
383 if (op->getNumResults() > 0) {
384 os << "|{";
385 auto resultToPort = [&](Value result) {
386 os << "<res" << getValuePortName(result) << "> ";
387 emitMlirOperand(os, result);
388 if (opts.printResultTypes) {
389 os << " ";
390 emitMlirType(os, result.getType());
391 }
392 };
393 interleave(op->getResults(), os, resultToPort, "|");
394 os << "}";
395 }
396
397 os << "}";
398 });
399 }
400
401 /// Generate a label for a block argument.
402 std::string getLabel(BlockArgument arg) {
403 return strFromOs([&](raw_ostream &os) {
404 os << "<res" << getValuePortName(arg) << "> ";
405 arg.printAsOperand(os, asmState);
406 if (opts.printResultTypes) {
407 os << " ";
408 emitMlirType(os, arg.getType());
409 }
410 });
411 }
412
413 /// Process a block. Emit a cluster and one node per block argument and
414 /// operation inside the cluster.
415 void processBlock(Block &block) {
416 emitClusterStmt([&]() {
417 for (BlockArgument &blockArg : block.getArguments())
418 valueToNode[blockArg] = emitNodeStmt(getLabel(blockArg));
419 // Emit a node for each operation.
420 std::optional<Node> prevNode;
421 for (Operation &op : block) {
422 Node nextNode = processOperation(&op);
423 if (opts.printControlFlowEdges && prevNode)
424 emitEdgeStmt(*prevNode, nextNode, /*port=*/"", kLineStyleControlFlow);
425 prevNode = nextNode;
426 }
427 });
428 }
429
430 /// Process an operation. If the operation has regions, emit a cluster.
431 /// Otherwise, emit a node.
432 Node processOperation(Operation *op) {
433 Node node;
434 if (op->getNumRegions() > 0) {
435 // Emit cluster for op with regions.
436 node = emitClusterStmt(
437 [&]() {
438 for (Region &region : op->getRegions())
439 processRegion(region);
440 },
441 getClusterLabel(op));
442 } else {
443 node = emitNodeStmt(getRecordLabel(op), kShapeNode,
444 backgroundColors[op->getName()].second);
445 }
446
447 // Insert data flow edges originating from each operand.
448 if (opts.printDataFlowEdges) {
449 unsigned numOperands = op->getNumOperands();
450 for (unsigned i = 0; i < numOperands; i++) {
451 auto operand = op->getOperand(i);
452 dataFlowEdges.push_back({operand, node, getValuePortName(operand)});
453 }
454 }
455
456 for (Value result : op->getResults())
457 valueToNode[result] = node;
458
459 return node;
460 }
461
462 /// Process a region.
463 void processRegion(Region &region) {
464 for (Block &block : region.getBlocks())
465 processBlock(block);
466 }
467
468 /// Truncate long strings.
469 std::string truncateString(std::string str) {
470 if (str.length() <= opts.maxLabelLen)
471 return str;
472 return str.substr(0, opts.maxLabelLen) + "...";
473 }
474
475 /// Printing configuration.
476 OpGraphPrinterOptions opts;
477 /// Output stream to write DOT file to.
479 /// Re-usable assembly printer state for efficient printing.
480 AsmState asmState;
481 /// A list of edges. For simplicity, should be emitted after all nodes were
482 /// emitted.
483 std::vector<std::string> edges;
484 /// Mapping of SSA values to Graphviz nodes/clusters.
485 DenseMap<Value, Node> valueToNode;
486 /// Output for data flow edges is delayed until the end to handle cycles
487 std::vector<DataFlowEdge> dataFlowEdges;
488 /// Counter for generating unique node/subgraph identifiers.
489 int counter = 0;
490
492};
493
494/// This pass generates a Graphviz dataflow visualization of an MLIR operation.
495/// Note: See https://www.graphviz.org/doc/info/lang.html for more information
496/// about the Graphviz DOT language.
497class PrintOpPass : public impl::ViewOpGraphPassBase<PrintOpPass> {
498public:
499 PrintOpPass() : os(&llvm::errs()) {}
500 explicit PrintOpPass(ViewOpGraphPassOptions options)
502 os(&llvm::errs()) {}
503 PrintOpPass(raw_ostream &os) : os(&os) {}
504 PrintOpPass(const PrintOpPass &other)
505 : impl::ViewOpGraphPassBase<PrintOpPass>(other), os(other.os) {}
506
507 void runOnOperation() override {
508 OpGraphPrinterOptions opts;
509 opts.maxLabelLen = maxLabelLen;
510 opts.printAttrs = printAttrs;
511 opts.printControlFlowEdges = printControlFlowEdges;
512 opts.printDataFlowEdges = printDataFlowEdges;
513 opts.printResultTypes = printResultTypes;
514 OpGraphPrinter printer(opts, *os, getOperation(), OpPrintingFlags());
515 printer.emitOperationGraph(*getOperation());
516 markAllAnalysesPreserved();
517 }
518
519private:
520 raw_ostream *os;
521};
522} // namespace
523
524std::unique_ptr<Pass> mlir::createViewOpGraphPass(raw_ostream &os) {
525 return std::make_unique<PrintOpPass>(os);
526}
527
528/// Generate a CFG for a region and show it in a window.
529static void llvmViewGraph(Region &region, const Twine &name) {
530 int fd;
531 std::string filename = llvm::createGraphFilename(name.str(), fd);
532 {
533 llvm::raw_fd_ostream os(fd, /*shouldClose=*/true);
534 if (fd == -1) {
535 llvm::errs() << "error opening file '" << filename << "' for writing\n";
536 return;
537 }
538 OpGraphPrinter printer(OpGraphPrinterOptions::cfgRegionDefaults(), os,
539 region.getParentOp());
540 printer.emitRegionCFGGraph(region);
541 }
542 llvm::DisplayGraph(filename, /*wait=*/false, llvm::GraphProgram::DOT);
543}
544
545void mlir::Region::viewGraph(const Twine &regionName) {
546 llvmViewGraph(*this, regionName);
547}
548
MemRefDependenceGraph::Node Node
Definition Utils.cpp:39
static llvm::ManagedStatic< PassManagerOptions > options
static std::string quoteString(const std::string &str)
Put quotation marks around a given string.
static std::string escapeLabelString(const std::string &str)
For Graphviz record nodes: " Braces, vertical bars and angle brackets must be escaped with a backslas...
static const StringRef kLineStyleDataFlow
static const StringRef kLineStyleControlFlow
static int64_t getLargeAttributeSizeLimit()
Return the size limits for eliding large attributes.
std::map< std::string, std::string > AttributeMap
static const StringRef kShapeNone
static const StringRef kShapeNode
static void llvmViewGraph(Region &region, const Twine &name)
Generate a CFG for a region and show it in a window.
static std::string strFromOs(function_ref< void(raw_ostream &)> func)
Return all values printed onto a stream as a string.
This class provides management for the lifetime of the state used when printing the IR.
Definition AsmState.h:542
Attributes are known-constant values of operations.
Definition Attributes.h:25
void print(raw_ostream &os, bool elideType=false) const
Print the attribute.
This class represents an argument of a Block.
Definition Value.h:306
Block represents an ordered list of Operations.
Definition Block.h:34
BlockArgListType getArguments()
Definition Block.h:112
NamedAttrList is array of NamedAttributes that tracks whether it is sorted and does some basic work t...
void append(StringRef name, Attribute attr)
Add an attribute with the specified name.
NamedAttribute represents a combination of a name and an Attribute value.
Definition Attributes.h:164
Set of flags used to control the behavior of the various IR print methods (e.g.
void walkInherentAttrs(Operation *op, InherentAttrVisitor visitor) const
Visit the inherent attributes stored in the properties of op.
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Value getOperand(unsigned idx)
Definition Operation.h:375
unsigned getNumRegions()
Returns the number of regions held by this operation.
Definition Operation.h:726
unsigned getNumOperands()
Definition Operation.h:371
OperationName getName()
The name of an operation is the key identifier for it.
Definition Operation.h:115
DictionaryAttr getDiscardableAttrDictionary()
Return all of the discardable attributes on this operation as a DictionaryAttr.
Definition Operation.h:553
MutableArrayRef< Region > getRegions()
Returns the regions held by this operation.
Definition Operation.h:729
result_type_range getResultTypes()
Definition Operation.h:453
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:403
result_range getResults()
Definition Operation.h:440
unsigned getNumResults()
Return the number of results held by this operation.
Definition Operation.h:429
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
Operation * getParentOp()
Return the parent operation this region is attached to.
Definition Region.h:198
void viewGraph(const Twine &regionName)
Displays the CFG in a window.
BlockListType & getBlocks()
Definition Region.h:45
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
void print(raw_ostream &os) const
Print the current type.
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Type getType() const
Return the type of this value.
Definition Value.h:105
void printAsOperand(raw_ostream &os, AsmState &state) const
Print this value as if it were an operand.
raw_ostream subclass that simplifies indention a sequence of code.
Include the generated interface declarations.
std::unique_ptr<::mlir::Pass > createViewOpGraphPass()
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:123
llvm::function_ref< Fn > function_ref
Definition LLVM.h:155