[Mlir-commits] [mlir] [WIP][ViewOpGraph] Improve GraphViz output (PR #125509)

Eric Hein llvmlistbot at llvm.org
Tue Feb 4 10:05:01 PST 2025


https://github.com/ehein6 updated https://github.com/llvm/llvm-project/pull/125509

>From 645f70e82350be4d995a07a50c734ba4ee76360c Mon Sep 17 00:00:00 2001
From: Eric Hein <ehein at modular.com>
Date: Fri, 31 Jan 2025 14:47:36 +0000
Subject: [PATCH 01/22] [WIP] Improve graphviz output

---
 mlir/lib/Transforms/ViewOpGraph.cpp | 20 ++++++++++++++++++--
 1 file changed, 18 insertions(+), 2 deletions(-)

diff --git a/mlir/lib/Transforms/ViewOpGraph.cpp b/mlir/lib/Transforms/ViewOpGraph.cpp
index fa0af7665ba4c4..920de8b22f1ac9 100644
--- a/mlir/lib/Transforms/ViewOpGraph.cpp
+++ b/mlir/lib/Transforms/ViewOpGraph.cpp
@@ -29,7 +29,7 @@ using namespace mlir;
 
 static const StringRef kLineStyleControlFlow = "dashed";
 static const StringRef kLineStyleDataFlow = "solid";
-static const StringRef kShapeNode = "ellipse";
+static const StringRef kShapeNode = "record";
 static const StringRef kShapeNone = "plain";
 
 /// Return the size limits for eliding large attributes.
@@ -59,6 +59,21 @@ static std::string quoteString(const std::string &str) {
   return "\"" + str + "\"";
 }
 
+/// For Graphviz record nodes:
+/// " Braces, vertical bars and angle brackets must be escaped with a backslash
+/// character if you wish them to appear as a literal character "
+static std::string escapeLabelString(const std::string &str) {
+  std::string buf;
+  llvm::raw_string_ostream os(buf);
+  for (char c : str) {
+    if (c == '{' || c == '|' || c == '<' || c == '}' || c == '>') {
+      os << "\\\\";
+    }
+    os << c;
+  }
+  return buf;
+}
+
 using AttributeMap = std::map<std::string, std::string>;
 
 namespace {
@@ -240,7 +255,8 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
                     StringRef background = "") {
     int nodeId = ++counter;
     AttributeMap attrs;
-    attrs["label"] = quoteString(escapeString(std::move(label)));
+    attrs["label"] =
+        quoteString(escapeString(escapeLabelString(std::move(label))));
     attrs["shape"] = shape.str();
     if (!background.empty()) {
       attrs["style"] = "filled";

>From efbff2b2b43fc9220faa12b8525af1e55cb17fe4 Mon Sep 17 00:00:00 2001
From: Eric Hein <ehein at modular.com>
Date: Fri, 31 Jan 2025 13:26:14 -0500
Subject: [PATCH 02/22] Moving to record nodes and port-based edges.

---
 mlir/lib/Transforms/ViewOpGraph.cpp | 43 ++++++++++++++++++-----------
 1 file changed, 27 insertions(+), 16 deletions(-)

diff --git a/mlir/lib/Transforms/ViewOpGraph.cpp b/mlir/lib/Transforms/ViewOpGraph.cpp
index 920de8b22f1ac9..53e4bcfaa57f6e 100644
--- a/mlir/lib/Transforms/ViewOpGraph.cpp
+++ b/mlir/lib/Transforms/ViewOpGraph.cpp
@@ -62,12 +62,12 @@ static std::string quoteString(const std::string &str) {
 /// For Graphviz record nodes:
 /// " Braces, vertical bars and angle brackets must be escaped with a backslash
 /// character if you wish them to appear as a literal character "
-static std::string escapeLabelString(const std::string &str) {
+std::string escapeLabelString(const std::string &str) {
   std::string buf;
   llvm::raw_string_ostream os(buf);
   for (char c : str) {
     if (c == '{' || c == '|' || c == '<' || c == '}' || c == '>') {
-      os << "\\\\";
+      os << '\\';
     }
     os << c;
   }
@@ -145,7 +145,7 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
   void emitAllEdgeStmts() {
     if (printDataFlowEdges) {
       for (const auto &[value, node, label] : dataFlowEdges) {
-        emitEdgeStmt(valueToNode[value], node, label, kLineStyleDataFlow);
+        emitEdgeStmt(valueToNode[value], "", node, "", kLineStyleDataFlow);
       }
     }
 
@@ -219,14 +219,10 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
 
   /// Append an edge to the list of edges.
   /// Note: Edges are written to the output stream via `emitAllEdgeStmts`.
-  void emitEdgeStmt(Node n1, Node n2, std::string label, StringRef style) {
+  void emitEdgeStmt(Node n1, std::string outPort, Node n2, std::string inPort,
+                    StringRef style) {
     AttributeMap attrs;
     attrs["style"] = style.str();
-    // Do not label edges that start/end at a cluster boundary. Such edges are
-    // clipped at the boundary, but labels are not. This can lead to labels
-    // floating around without any edge next to them.
-    if (!n1.clusterId && !n2.clusterId)
-      attrs["label"] = quoteString(escapeString(std::move(label)));
     // Use `ltail` and `lhead` to draw edges between clusters.
     if (n1.clusterId)
       attrs["ltail"] = "cluster_" + std::to_string(*n1.clusterId);
@@ -234,7 +230,13 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
       attrs["lhead"] = "cluster_" + std::to_string(*n2.clusterId);
 
     edges.push_back(strFromOs([&](raw_ostream &os) {
-      os << llvm::format("v%i -> v%i ", n1.id, n2.id);
+      os << "v" << n1.id;
+      if (!outPort.empty())
+        os << ":" << outPort;
+      os << " -> ";
+      os << "v" << n2.id;
+      if (!inPort.empty())
+        os << ":" << inPort;
       emitAttrList(os, attrs);
     }));
   }
@@ -255,8 +257,7 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
                     StringRef background = "") {
     int nodeId = ++counter;
     AttributeMap attrs;
-    attrs["label"] =
-        quoteString(escapeString(escapeLabelString(std::move(label))));
+    attrs["label"] = quoteString(escapeString(std::move(label)));
     attrs["shape"] = shape.str();
     if (!background.empty()) {
       attrs["style"] = "filled";
@@ -271,6 +272,16 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
   /// Generate a label for an operation.
   std::string getLabel(Operation *op) {
     return strFromOs([&](raw_ostream &os) {
+      os << "{{";
+      // Print operation inputs.
+      interleave(
+          op->getOperands(), os,
+          [&](Value operand) {
+            OpPrintingFlags flags;
+            operand.printAsOperand(os, flags);
+          },
+          "|");
+      os << "}|";
       // Print operation name and type.
       os << op->getName();
       if (printResultTypes) {
@@ -289,6 +300,7 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
           emitMlirAttr(os, attr.getValue());
         }
       }
+      os << "}";
     });
   }
 
@@ -301,16 +313,15 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
   /// operation inside the cluster.
   void processBlock(Block &block) {
     emitClusterStmt([&]() {
-      for (BlockArgument &blockArg : block.getArguments())
+      for (BlockArgument &blockArg : block.getArguments()) {
         valueToNode[blockArg] = emitNodeStmt(getLabel(blockArg));
-
+      }
       // Emit a node for each operation.
       std::optional<Node> prevNode;
       for (Operation &op : block) {
         Node nextNode = processOperation(&op);
         if (printControlFlowEdges && prevNode)
-          emitEdgeStmt(*prevNode, nextNode, /*label=*/"",
-                       kLineStyleControlFlow);
+          emitEdgeStmt(*prevNode, "", nextNode, "", kLineStyleControlFlow);
         prevNode = nextNode;
       }
     });

>From 8f229f862988866818adf5439d5cb2ff76f2b1c6 Mon Sep 17 00:00:00 2001
From: Eric Hein <ehein at modular.com>
Date: Fri, 31 Jan 2025 14:44:13 -0500
Subject: [PATCH 03/22] Fix input port linking

---
 mlir/lib/Transforms/ViewOpGraph.cpp | 54 +++++++++++++++++++++--------
 1 file changed, 39 insertions(+), 15 deletions(-)

diff --git a/mlir/lib/Transforms/ViewOpGraph.cpp b/mlir/lib/Transforms/ViewOpGraph.cpp
index 53e4bcfaa57f6e..88ec22d8810952 100644
--- a/mlir/lib/Transforms/ViewOpGraph.cpp
+++ b/mlir/lib/Transforms/ViewOpGraph.cpp
@@ -94,6 +94,13 @@ struct Node {
   std::optional<int> clusterId;
 };
 
+struct DataFlowEdge {
+  Value value;
+  std::string outPort;
+  Node node;
+  std::string inPort;
+};
+
 /// This pass generates a Graphviz dataflow visualization of an MLIR operation.
 /// Note: See https://www.graphviz.org/doc/info/lang.html for more information
 /// about the Graphviz DOT language.
@@ -144,8 +151,9 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
   /// emitted.
   void emitAllEdgeStmts() {
     if (printDataFlowEdges) {
-      for (const auto &[value, node, label] : dataFlowEdges) {
-        emitEdgeStmt(valueToNode[value], "", node, "", kLineStyleDataFlow);
+      for (const auto &e : dataFlowEdges) {
+        emitEdgeStmt(valueToNode[e.value], e.outPort, e.node, e.inPort,
+                     kLineStyleDataFlow);
       }
     }
 
@@ -269,19 +277,33 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
     return Node(nodeId);
   }
 
+  std::string getOperandPortName(Value operand) {
+    // Print value as an operand and omit the leading '%' character.
+    return strFromOs([&](raw_ostream &os) {
+             operand.printAsOperand(os, OpPrintingFlags());
+           })
+        .substr(1, std::string::npos);
+  }
+
   /// Generate a label for an operation.
   std::string getLabel(Operation *op) {
     return strFromOs([&](raw_ostream &os) {
-      os << "{{";
+      os << "{";
+
       // Print operation inputs.
-      interleave(
-          op->getOperands(), os,
-          [&](Value operand) {
-            OpPrintingFlags flags;
-            operand.printAsOperand(os, flags);
-          },
-          "|");
-      os << "}|";
+      if (op->getNumOperands() > 0) {
+        os << "{";
+        interleave(
+            op->getOperands(), os,
+            [&](Value operand) {
+              os << "<";
+              os << getOperandPortName(operand);
+              os << "> ";
+              operand.printAsOperand(os, OpPrintingFlags());
+            },
+            "|");
+        os << "}|";
+      }
       // Print operation name and type.
       os << op->getName();
       if (printResultTypes) {
@@ -347,9 +369,11 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
     // Insert data flow edges originating from each operand.
     if (printDataFlowEdges) {
       unsigned numOperands = op->getNumOperands();
-      for (unsigned i = 0; i < numOperands; i++)
-        dataFlowEdges.push_back({op->getOperand(i), node,
-                                 numOperands == 1 ? "" : std::to_string(i)});
+      for (unsigned i = 0; i < numOperands; i++) {
+        auto operand = op->getOperand(i);
+        auto inPort = getOperandPortName(operand);
+        dataFlowEdges.push_back({operand, "", node, inPort});
+      }
     }
 
     for (Value result : op->getResults())
@@ -379,7 +403,7 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
   /// Mapping of SSA values to Graphviz nodes/clusters.
   DenseMap<Value, Node> valueToNode;
   /// Output for data flow edges is delayed until the end to handle cycles
-  std::vector<std::tuple<Value, Node, std::string>> dataFlowEdges;
+  std::vector<DataFlowEdge> dataFlowEdges;
   /// Counter for generating unique node/subgraph identifiers.
   int counter = 0;
 

>From ca0c74057f67b5f71c73b0bc9cc5a4119ecc3bb1 Mon Sep 17 00:00:00 2001
From: Eric Hein <ehein at modular.com>
Date: Fri, 31 Jan 2025 15:04:46 -0500
Subject: [PATCH 04/22] Fix result type printing

---
 mlir/lib/Transforms/ViewOpGraph.cpp | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)

diff --git a/mlir/lib/Transforms/ViewOpGraph.cpp b/mlir/lib/Transforms/ViewOpGraph.cpp
index 88ec22d8810952..bf8e2a845751b8 100644
--- a/mlir/lib/Transforms/ViewOpGraph.cpp
+++ b/mlir/lib/Transforms/ViewOpGraph.cpp
@@ -265,7 +265,7 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
                     StringRef background = "") {
     int nodeId = ++counter;
     AttributeMap attrs;
-    attrs["label"] = quoteString(escapeString(std::move(label)));
+    attrs["label"] = quoteString(label);
     attrs["shape"] = shape.str();
     if (!background.empty()) {
       attrs["style"] = "filled";
@@ -306,12 +306,19 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
       }
       // Print operation name and type.
       os << op->getName();
-      if (printResultTypes) {
-        os << " : (";
+      if (printResultTypes && op->getNumResults() > 0) {
+        os << "|{";
         std::string buf;
         llvm::raw_string_ostream ss(buf);
-        interleaveComma(op->getResultTypes(), ss);
-        os << truncateString(buf) << ")";
+        interleave(
+            op->getResultTypes(), ss,
+            [&](Type type) {
+              ss << escapeLabelString(
+                  strFromOs([&](raw_ostream &os) { os << type; }));
+            },
+            "|");
+        // TODO: how to truncate string without breaking the layout?
+        os << buf << "}";
       }
 
       // Print attributes.

>From 87dd5c98b333da2e0c406452a42a8e024c6ae229 Mon Sep 17 00:00:00 2001
From: Eric Hein <ehein at modular.com>
Date: Fri, 31 Jan 2025 15:22:33 -0500
Subject: [PATCH 05/22] Fix attribute printing

---
 mlir/lib/Transforms/ViewOpGraph.cpp | 22 ++++++++++++----------
 1 file changed, 12 insertions(+), 10 deletions(-)

diff --git a/mlir/lib/Transforms/ViewOpGraph.cpp b/mlir/lib/Transforms/ViewOpGraph.cpp
index bf8e2a845751b8..2ddefe971f38a1 100644
--- a/mlir/lib/Transforms/ViewOpGraph.cpp
+++ b/mlir/lib/Transforms/ViewOpGraph.cpp
@@ -66,7 +66,7 @@ std::string escapeLabelString(const std::string &str) {
   std::string buf;
   llvm::raw_string_ostream os(buf);
   for (char c : str) {
-    if (c == '{' || c == '|' || c == '<' || c == '}' || c == '>') {
+    if (c == '{' || c == '|' || c == '<' || c == '}' || c == '>' || c == '\n') {
       os << '\\';
     }
     os << c;
@@ -222,7 +222,7 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
     std::string buf;
     llvm::raw_string_ostream ss(buf);
     attr.print(ss);
-    os << truncateString(buf);
+    os << escapeLabelString(truncateString(buf));
   }
 
   /// Append an edge to the list of edges.
@@ -306,6 +306,16 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
       }
       // Print operation name and type.
       os << op->getName();
+
+      // Print attributes.
+      if (printAttrs && !op->getAttrs().empty()) {
+        os << "\\n";
+        for (const NamedAttribute &attr : op->getAttrs()) {
+          os << "\\n" << attr.getName().getValue() << ": ";
+          emitMlirAttr(os, attr.getValue());
+        }
+      }
+
       if (printResultTypes && op->getNumResults() > 0) {
         os << "|{";
         std::string buf;
@@ -321,14 +331,6 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
         os << buf << "}";
       }
 
-      // Print attributes.
-      if (printAttrs) {
-        os << "\n";
-        for (const NamedAttribute &attr : op->getAttrs()) {
-          os << '\n' << attr.getName().getValue() << ": ";
-          emitMlirAttr(os, attr.getValue());
-        }
-      }
       os << "}";
     });
   }

>From 2ad44fca3ecdb0d9adab7ebeb0c25a4c258ac192 Mon Sep 17 00:00:00 2001
From: Eric Hein <ehein at modular.com>
Date: Fri, 31 Jan 2025 15:41:02 -0500
Subject: [PATCH 06/22] Switch to Mrecord shape (rounded rectangle)

---
 mlir/lib/Transforms/ViewOpGraph.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/mlir/lib/Transforms/ViewOpGraph.cpp b/mlir/lib/Transforms/ViewOpGraph.cpp
index 2ddefe971f38a1..7493eab592f8ec 100644
--- a/mlir/lib/Transforms/ViewOpGraph.cpp
+++ b/mlir/lib/Transforms/ViewOpGraph.cpp
@@ -29,7 +29,7 @@ using namespace mlir;
 
 static const StringRef kLineStyleControlFlow = "dashed";
 static const StringRef kLineStyleDataFlow = "solid";
-static const StringRef kShapeNode = "record";
+static const StringRef kShapeNode = "Mrecord";
 static const StringRef kShapeNone = "plain";
 
 /// Return the size limits for eliding large attributes.

>From 96b42e840a003954d3b21c34adacf6f9c27e1aec Mon Sep 17 00:00:00 2001
From: Eric Hein <ehein at modular.com>
Date: Fri, 31 Jan 2025 15:41:27 -0500
Subject: [PATCH 07/22] Handle more special characters in node labels

---
 mlir/lib/Transforms/ViewOpGraph.cpp | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

diff --git a/mlir/lib/Transforms/ViewOpGraph.cpp b/mlir/lib/Transforms/ViewOpGraph.cpp
index 7493eab592f8ec..246aa88a045b58 100644
--- a/mlir/lib/Transforms/ViewOpGraph.cpp
+++ b/mlir/lib/Transforms/ViewOpGraph.cpp
@@ -65,8 +65,9 @@ static std::string quoteString(const std::string &str) {
 std::string escapeLabelString(const std::string &str) {
   std::string buf;
   llvm::raw_string_ostream os(buf);
+  llvm::DenseSet<char> shouldEscape = {'{', '|', '<', '}', '>', '\n', '"'};
   for (char c : str) {
-    if (c == '{' || c == '|' || c == '<' || c == '}' || c == '>' || c == '\n') {
+    if (shouldEscape.contains(c)) {
       os << '\\';
     }
     os << c;
@@ -279,10 +280,13 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
 
   std::string getOperandPortName(Value operand) {
     // Print value as an operand and omit the leading '%' character.
-    return strFromOs([&](raw_ostream &os) {
-             operand.printAsOperand(os, OpPrintingFlags());
-           })
-        .substr(1, std::string::npos);
+    auto str = strFromOs([&](raw_ostream &os) {
+      operand.printAsOperand(os, OpPrintingFlags());
+    });
+    // Replace % and # with _
+    std::replace(str.begin(), str.end(), '%', '_');
+    std::replace(str.begin(), str.end(), '#', '_');
+    return str;
   }
 
   /// Generate a label for an operation.

>From 4ac25aa8483ffa6b32757b6956d951bdfd3879c3 Mon Sep 17 00:00:00 2001
From: Eric Hein <ehein at modular.com>
Date: Sat, 1 Feb 2025 14:30:55 -0500
Subject: [PATCH 08/22] Fix output port names

---
 mlir/lib/Transforms/ViewOpGraph.cpp | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/mlir/lib/Transforms/ViewOpGraph.cpp b/mlir/lib/Transforms/ViewOpGraph.cpp
index 246aa88a045b58..27d7131543c454 100644
--- a/mlir/lib/Transforms/ViewOpGraph.cpp
+++ b/mlir/lib/Transforms/ViewOpGraph.cpp
@@ -320,19 +320,19 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
         }
       }
 
-      if (printResultTypes && op->getNumResults() > 0) {
+      if (op->getNumResults() > 0) {
         os << "|{";
-        std::string buf;
-        llvm::raw_string_ostream ss(buf);
         interleave(
-            op->getResultTypes(), ss,
-            [&](Type type) {
-              ss << escapeLabelString(
-                  strFromOs([&](raw_ostream &os) { os << type; }));
+            op->getResults(), os,
+            [&](Value result) {
+              os << "<" << getOperandPortName(result) << "> ";
+              if (printResultTypes)
+                os << truncateString(escapeLabelString(strFromOs(
+                    [&](raw_ostream &os) { os << result.getType(); })));
             },
             "|");
         // TODO: how to truncate string without breaking the layout?
-        os << buf << "}";
+        os << "}";
       }
 
       os << "}";
@@ -385,7 +385,7 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
       for (unsigned i = 0; i < numOperands; i++) {
         auto operand = op->getOperand(i);
         auto inPort = getOperandPortName(operand);
-        dataFlowEdges.push_back({operand, "", node, inPort});
+        dataFlowEdges.push_back({operand, inPort, node, inPort});
       }
     }
 

>From 8a62b0582383b2fdcdc90388456ebfa7e1382148 Mon Sep 17 00:00:00 2001
From: Eric Hein <ehein at modular.com>
Date: Sat, 1 Feb 2025 15:16:34 -0500
Subject: [PATCH 09/22] Clean up port generation code

---
 mlir/lib/Transforms/ViewOpGraph.cpp | 37 +++++++++++++----------------
 1 file changed, 16 insertions(+), 21 deletions(-)

diff --git a/mlir/lib/Transforms/ViewOpGraph.cpp b/mlir/lib/Transforms/ViewOpGraph.cpp
index 27d7131543c454..af8095583de5a8 100644
--- a/mlir/lib/Transforms/ViewOpGraph.cpp
+++ b/mlir/lib/Transforms/ViewOpGraph.cpp
@@ -278,7 +278,7 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
     return Node(nodeId);
   }
 
-  std::string getOperandPortName(Value operand) {
+  std::string getValuePortName(Value operand) {
     // Print value as an operand and omit the leading '%' character.
     auto str = strFromOs([&](raw_ostream &os) {
       operand.printAsOperand(os, OpPrintingFlags());
@@ -297,15 +297,11 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
       // Print operation inputs.
       if (op->getNumOperands() > 0) {
         os << "{";
-        interleave(
-            op->getOperands(), os,
-            [&](Value operand) {
-              os << "<";
-              os << getOperandPortName(operand);
-              os << "> ";
-              operand.printAsOperand(os, OpPrintingFlags());
-            },
-            "|");
+        auto operandToPort = [&](Value operand) {
+          os << "<" << getValuePortName(operand) << "> ";
+          operand.printAsOperand(os, OpPrintingFlags());
+        };
+        interleave(op->getOperands(), os, operandToPort, "|");
         os << "}|";
       }
       // Print operation name and type.
@@ -322,16 +318,15 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
 
       if (op->getNumResults() > 0) {
         os << "|{";
-        interleave(
-            op->getResults(), os,
-            [&](Value result) {
-              os << "<" << getOperandPortName(result) << "> ";
-              if (printResultTypes)
-                os << truncateString(escapeLabelString(strFromOs(
-                    [&](raw_ostream &os) { os << result.getType(); })));
-            },
-            "|");
-        // TODO: how to truncate string without breaking the layout?
+        auto resultToPort = [&](Value result) {
+          os << "<" << getValuePortName(result) << "> ";
+          if (printResultTypes)
+            os << truncateString(escapeLabelString(
+                strFromOs([&](raw_ostream &os) { os << result.getType(); })));
+          else
+            result.printAsOperand(os, OpPrintingFlags());
+        };
+        interleave(op->getResults(), os, resultToPort, "|");
         os << "}";
       }
 
@@ -384,7 +379,7 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
       unsigned numOperands = op->getNumOperands();
       for (unsigned i = 0; i < numOperands; i++) {
         auto operand = op->getOperand(i);
-        auto inPort = getOperandPortName(operand);
+        auto inPort = getValuePortName(operand);
         dataFlowEdges.push_back({operand, inPort, node, inPort});
       }
     }

>From a51de257110715811d99bbb4d3d95e9d90732854 Mon Sep 17 00:00:00 2001
From: Eric Hein <ehein at modular.com>
Date: Sat, 1 Feb 2025 15:40:45 -0500
Subject: [PATCH 10/22] Left-justify the label text.

---
 mlir/lib/Transforms/ViewOpGraph.cpp | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/mlir/lib/Transforms/ViewOpGraph.cpp b/mlir/lib/Transforms/ViewOpGraph.cpp
index af8095583de5a8..3bc6a460d01ed7 100644
--- a/mlir/lib/Transforms/ViewOpGraph.cpp
+++ b/mlir/lib/Transforms/ViewOpGraph.cpp
@@ -305,14 +305,16 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
         os << "}|";
       }
       // Print operation name and type.
-      os << op->getName();
+      os << op->getName() << "\\l";
 
       // Print attributes.
       if (printAttrs && !op->getAttrs().empty()) {
-        os << "\\n";
+        // Extra line break to separate attributes from the operation name.
+        os << "\\l";
         for (const NamedAttribute &attr : op->getAttrs()) {
-          os << "\\n" << attr.getName().getValue() << ": ";
+          os << attr.getName().getValue() << ": ";
           emitMlirAttr(os, attr.getValue());
+          os << "\\l";
         }
       }
 

>From fc069edfa994187da0552f8060e50f525318bffa Mon Sep 17 00:00:00 2001
From: Eric Hein <ehein at modular.com>
Date: Sat, 1 Feb 2025 15:41:09 -0500
Subject: [PATCH 11/22] Always print result short name.

---
 mlir/lib/Transforms/ViewOpGraph.cpp | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/mlir/lib/Transforms/ViewOpGraph.cpp b/mlir/lib/Transforms/ViewOpGraph.cpp
index 3bc6a460d01ed7..3576ed60184370 100644
--- a/mlir/lib/Transforms/ViewOpGraph.cpp
+++ b/mlir/lib/Transforms/ViewOpGraph.cpp
@@ -322,11 +322,11 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
         os << "|{";
         auto resultToPort = [&](Value result) {
           os << "<" << getValuePortName(result) << "> ";
+          result.printAsOperand(os, OpPrintingFlags());
           if (printResultTypes)
-            os << truncateString(escapeLabelString(
-                strFromOs([&](raw_ostream &os) { os << result.getType(); })));
-          else
-            result.printAsOperand(os, OpPrintingFlags());
+            os << " "
+               << truncateString(escapeLabelString(strFromOs(
+                      [&](raw_ostream &os) { os << result.getType(); })));
         };
         interleave(op->getResults(), os, resultToPort, "|");
         os << "}";

>From 1398fe919c3e9c222a27a69aab4dc43a38b32604 Mon Sep 17 00:00:00 2001
From: Eric Hein <ehein at modular.com>
Date: Sat, 1 Feb 2025 15:58:31 -0500
Subject: [PATCH 12/22] Fix edge attachment points.

---
 mlir/lib/Transforms/ViewOpGraph.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/mlir/lib/Transforms/ViewOpGraph.cpp b/mlir/lib/Transforms/ViewOpGraph.cpp
index 3576ed60184370..ac9ce6b0c641cf 100644
--- a/mlir/lib/Transforms/ViewOpGraph.cpp
+++ b/mlir/lib/Transforms/ViewOpGraph.cpp
@@ -241,11 +241,11 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
     edges.push_back(strFromOs([&](raw_ostream &os) {
       os << "v" << n1.id;
       if (!outPort.empty())
-        os << ":" << outPort;
+        os << ":" << outPort << ":s";
       os << " -> ";
       os << "v" << n2.id;
       if (!inPort.empty())
-        os << ":" << inPort;
+        os << ":" << inPort << ":n";
       emitAttrList(os, attrs);
     }));
   }

>From c681a203068209ee9c4db7a4c012d02339764386 Mon Sep 17 00:00:00 2001
From: Eric Hein <ehein at modular.com>
Date: Sun, 2 Feb 2025 13:30:47 -0500
Subject: [PATCH 13/22] Merge in and out ports into a single port attribute.

---
 mlir/lib/Transforms/ViewOpGraph.cpp | 24 +++++++++++-------------
 1 file changed, 11 insertions(+), 13 deletions(-)

diff --git a/mlir/lib/Transforms/ViewOpGraph.cpp b/mlir/lib/Transforms/ViewOpGraph.cpp
index ac9ce6b0c641cf..43949ca289590b 100644
--- a/mlir/lib/Transforms/ViewOpGraph.cpp
+++ b/mlir/lib/Transforms/ViewOpGraph.cpp
@@ -97,9 +97,8 @@ struct Node {
 
 struct DataFlowEdge {
   Value value;
-  std::string outPort;
   Node node;
-  std::string inPort;
+  std::string port;
 };
 
 /// This pass generates a Graphviz dataflow visualization of an MLIR operation.
@@ -153,8 +152,7 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
   void emitAllEdgeStmts() {
     if (printDataFlowEdges) {
       for (const auto &e : dataFlowEdges) {
-        emitEdgeStmt(valueToNode[e.value], e.outPort, e.node, e.inPort,
-                     kLineStyleDataFlow);
+        emitEdgeStmt(valueToNode[e.value], e.node, e.port, kLineStyleDataFlow);
       }
     }
 
@@ -228,8 +226,7 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
 
   /// Append an edge to the list of edges.
   /// Note: Edges are written to the output stream via `emitAllEdgeStmts`.
-  void emitEdgeStmt(Node n1, std::string outPort, Node n2, std::string inPort,
-                    StringRef style) {
+  void emitEdgeStmt(Node n1, Node n2, std::string port, StringRef style) {
     AttributeMap attrs;
     attrs["style"] = style.str();
     // Use `ltail` and `lhead` to draw edges between clusters.
@@ -240,12 +237,14 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
 
     edges.push_back(strFromOs([&](raw_ostream &os) {
       os << "v" << n1.id;
-      if (!outPort.empty())
-        os << ":" << outPort << ":s";
+      if (!port.empty())
+        // Attach edge to south compass point of the result
+        os << ":" << port << ":s";
       os << " -> ";
       os << "v" << n2.id;
-      if (!inPort.empty())
-        os << ":" << inPort << ":n";
+      if (!port.empty())
+        // Attach edge to north compass point of the operand
+        os << ":" << port << ":n";
       emitAttrList(os, attrs);
     }));
   }
@@ -353,7 +352,7 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
       for (Operation &op : block) {
         Node nextNode = processOperation(&op);
         if (printControlFlowEdges && prevNode)
-          emitEdgeStmt(*prevNode, "", nextNode, "", kLineStyleControlFlow);
+          emitEdgeStmt(*prevNode, nextNode, /*port=*/"", kLineStyleControlFlow);
         prevNode = nextNode;
       }
     });
@@ -381,8 +380,7 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
       unsigned numOperands = op->getNumOperands();
       for (unsigned i = 0; i < numOperands; i++) {
         auto operand = op->getOperand(i);
-        auto inPort = getValuePortName(operand);
-        dataFlowEdges.push_back({operand, inPort, node, inPort});
+        dataFlowEdges.push_back({operand, node, getValuePortName(operand)});
       }
     }
 

>From da400cc7452b9b9f3c56c085198eb65480467e1b Mon Sep 17 00:00:00 2001
From: Eric Hein <ehein at modular.com>
Date: Sun, 2 Feb 2025 21:16:49 -0500
Subject: [PATCH 14/22] Fix formatting of cluster labels

---
 mlir/lib/Transforms/ViewOpGraph.cpp | 38 +++++++++++++++++++++--------
 1 file changed, 28 insertions(+), 10 deletions(-)

diff --git a/mlir/lib/Transforms/ViewOpGraph.cpp b/mlir/lib/Transforms/ViewOpGraph.cpp
index 43949ca289590b..2fef6aa4d9a082 100644
--- a/mlir/lib/Transforms/ViewOpGraph.cpp
+++ b/mlir/lib/Transforms/ViewOpGraph.cpp
@@ -49,11 +49,6 @@ static std::string strFromOs(function_ref<void(raw_ostream &)> func) {
   return buf;
 }
 
-/// Escape special characters such as '\n' and quotation marks.
-static std::string escapeString(std::string str) {
-  return strFromOs([&](raw_ostream &os) { os.write_escaped(str); });
-}
-
 /// Put quotation marks around a given string.
 static std::string quoteString(const std::string &str) {
   return "\"" + str + "\"";
@@ -169,8 +164,7 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
     os.indent();
     // Emit invisible anchor node from/to which arrows can be drawn.
     Node anchorNode = emitNodeStmt(" ", kShapeNone);
-    os << attrStmt("label", quoteString(escapeString(std::move(label))))
-       << ";\n";
+    os << attrStmt("label", quoteString(label)) << ";\n";
     builder();
     os.unindent();
     os << "}\n";
@@ -288,8 +282,32 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
     return str;
   }
 
+  std::string getClusterLabel(Operation *op) {
+    return strFromOs([&](raw_ostream &os) {
+      // Print operation name and type.
+      os << op->getName();
+      if (printResultTypes) {
+        os << " : (";
+        std::string buf;
+        llvm::raw_string_ostream ss(buf);
+        interleaveComma(op->getResultTypes(), ss);
+        os << truncateString(buf) << ")";
+      }
+
+      // Print attributes.
+      if (printAttrs) {
+        os << "\\l";
+        for (const NamedAttribute &attr : op->getAttrs()) {
+          os << attr.getName().getValue() << ": ";
+          emitMlirAttr(os, attr.getValue());
+          os << "\\l";
+        }
+      }
+    });
+  }
+
   /// Generate a label for an operation.
-  std::string getLabel(Operation *op) {
+  std::string getRecordLabel(Operation *op) {
     return strFromOs([&](raw_ostream &os) {
       os << "{";
 
@@ -369,9 +387,9 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
             for (Region &region : op->getRegions())
               processRegion(region);
           },
-          getLabel(op));
+          getClusterLabel(op));
     } else {
-      node = emitNodeStmt(getLabel(op), kShapeNode,
+      node = emitNodeStmt(getRecordLabel(op), kShapeNode,
                           backgroundColors[op->getName()].second);
     }
 

>From 464880325ecfae2fc7123b0b96d8c47499926f76 Mon Sep 17 00:00:00 2001
From: Eric Hein <ehein at modular.com>
Date: Sun, 2 Feb 2025 21:32:26 -0500
Subject: [PATCH 15/22] Print result types in block arguments

---
 mlir/lib/Transforms/ViewOpGraph.cpp | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/mlir/lib/Transforms/ViewOpGraph.cpp b/mlir/lib/Transforms/ViewOpGraph.cpp
index 2fef6aa4d9a082..abf0d8e36bc5ca 100644
--- a/mlir/lib/Transforms/ViewOpGraph.cpp
+++ b/mlir/lib/Transforms/ViewOpGraph.cpp
@@ -355,7 +355,14 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
 
   /// Generate a label for a block argument.
   std::string getLabel(BlockArgument arg) {
-    return "arg" + std::to_string(arg.getArgNumber());
+    return strFromOs([&](raw_ostream &os) {
+      os << "<" << getValuePortName(arg) << "> ";
+      arg.printAsOperand(os, OpPrintingFlags());
+      if (printResultTypes)
+        os << " "
+           << truncateString(escapeLabelString(
+                  strFromOs([&](raw_ostream &os) { os << arg.getType(); })));
+    });
   }
 
   /// Process a block. Emit a cluster and one node per block argument and

>From 566c8a6b1e3eb843ff944786c0f2208847cf03dc Mon Sep 17 00:00:00 2001
From: Eric Hein <ehein at modular.com>
Date: Mon, 3 Feb 2025 09:13:16 -0500
Subject: [PATCH 16/22] Improved node fill color choices

---
 mlir/lib/Transforms/ViewOpGraph.cpp | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/mlir/lib/Transforms/ViewOpGraph.cpp b/mlir/lib/Transforms/ViewOpGraph.cpp
index abf0d8e36bc5ca..065b4da568b588 100644
--- a/mlir/lib/Transforms/ViewOpGraph.cpp
+++ b/mlir/lib/Transforms/ViewOpGraph.cpp
@@ -124,7 +124,7 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
 private:
   /// Generate a color mapping that will color every operation with the same
   /// name the same way. It'll interpolate the hue in the HSV color-space,
-  /// attempting to keep the contrast suitable for black text.
+  /// using muted colors that provide good contrast for black text.
   template <typename T>
   void initColorMapping(T &irEntity) {
     backgroundColors.clear();
@@ -137,8 +137,10 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
     });
     for (auto indexedOps : llvm::enumerate(ops)) {
       double hue = ((double)indexedOps.index()) / ops.size();
+      // Use lower saturation (0.3) and higher value (0.95) for better
+      // readability
       backgroundColors[indexedOps.value()->getName()].second =
-          std::to_string(hue) + " 1.0 1.0";
+          std::to_string(hue) + " 0.3 0.95";
     }
   }
 
@@ -263,7 +265,7 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
     attrs["shape"] = shape.str();
     if (!background.empty()) {
       attrs["style"] = "filled";
-      attrs["fillcolor"] = ("\"" + background + "\"").str();
+      attrs["fillcolor"] = quoteString(background.str());
     }
     os << llvm::format("v%i ", nodeId);
     emitAttrList(os, attrs);

>From 9afe4113dccb20f0722774e6f1463d5c0265bf03 Mon Sep 17 00:00:00 2001
From: Eric Hein <ehein at modular.com>
Date: Mon, 3 Feb 2025 20:48:20 -0500
Subject: [PATCH 17/22] Add helpers for printing MLIR types and operands

---
 mlir/lib/Transforms/ViewOpGraph.cpp | 33 ++++++++++++++++++++---------
 1 file changed, 23 insertions(+), 10 deletions(-)

diff --git a/mlir/lib/Transforms/ViewOpGraph.cpp b/mlir/lib/Transforms/ViewOpGraph.cpp
index 065b4da568b588..c11255e22cc957 100644
--- a/mlir/lib/Transforms/ViewOpGraph.cpp
+++ b/mlir/lib/Transforms/ViewOpGraph.cpp
@@ -220,6 +220,19 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
     os << escapeLabelString(truncateString(buf));
   }
 
+  // Print a truncated and escaped MLIR type to `os`.
+  void emitMlirType(raw_ostream &os, Type type) {
+    std::string buf;
+    llvm::raw_string_ostream ss(buf);
+    type.print(ss);
+    os << escapeLabelString(truncateString(buf));
+  }
+
+  // Print a truncated and escaped MLIR operand to `os`.
+  void emitMlirOperand(raw_ostream &os, Value operand) {
+    operand.printAsOperand(os, OpPrintingFlags());
+  }
+
   /// Append an edge to the list of edges.
   /// Note: Edges are written to the output stream via `emitAllEdgeStmts`.
   void emitEdgeStmt(Node n1, Node n2, std::string port, StringRef style) {
@@ -318,7 +331,7 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
         os << "{";
         auto operandToPort = [&](Value operand) {
           os << "<" << getValuePortName(operand) << "> ";
-          operand.printAsOperand(os, OpPrintingFlags());
+          emitMlirOperand(os, operand);
         };
         interleave(op->getOperands(), os, operandToPort, "|");
         os << "}|";
@@ -341,11 +354,11 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
         os << "|{";
         auto resultToPort = [&](Value result) {
           os << "<" << getValuePortName(result) << "> ";
-          result.printAsOperand(os, OpPrintingFlags());
-          if (printResultTypes)
-            os << " "
-               << truncateString(escapeLabelString(strFromOs(
-                      [&](raw_ostream &os) { os << result.getType(); })));
+          emitMlirOperand(os, result);
+          if (printResultTypes) {
+            os << " ";
+            emitMlirType(os, result.getType());
+          }
         };
         interleave(op->getResults(), os, resultToPort, "|");
         os << "}";
@@ -360,10 +373,10 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
     return strFromOs([&](raw_ostream &os) {
       os << "<" << getValuePortName(arg) << "> ";
       arg.printAsOperand(os, OpPrintingFlags());
-      if (printResultTypes)
-        os << " "
-           << truncateString(escapeLabelString(
-                  strFromOs([&](raw_ostream &os) { os << arg.getType(); })));
+      if (printResultTypes) {
+        os << " ";
+        emitMlirType(os, arg.getType());
+      }
     });
   }
 

>From 76ab77fea852d6759c6bbbd65e4e934f7324976b Mon Sep 17 00:00:00 2001
From: Eric Hein <ehein at modular.com>
Date: Tue, 4 Feb 2025 10:10:48 -0500
Subject: [PATCH 18/22] Update print-op-graph unit tests

---
 .../Transforms/print-op-graph-back-edges.mlir | 12 +++----
 .../Transforms/print-op-graph-cycles.mlir     | 32 +++++++++----------
 mlir/test/Transforms/print-op-graph.mlir      | 32 +++++++++----------
 3 files changed, 38 insertions(+), 38 deletions(-)

diff --git a/mlir/test/Transforms/print-op-graph-back-edges.mlir b/mlir/test/Transforms/print-op-graph-back-edges.mlir
index ed922dd7cb13bd..71eda7f1b1be22 100644
--- a/mlir/test/Transforms/print-op-graph-back-edges.mlir
+++ b/mlir/test/Transforms/print-op-graph-back-edges.mlir
@@ -4,17 +4,17 @@
 //       DFG:   compound = true;
 //       DFG:   subgraph cluster_1 {
 //       DFG:     v2 [label = " ", shape = plain];
-//       DFG:     label = "builtin.module : ()\n";
+//       DFG:     label = "builtin.module : ()\l";
 //       DFG:     subgraph cluster_3 {
 //       DFG:       v4 [label = " ", shape = plain];
 //       DFG:       label = "";
-//       DFG:       v5 [fillcolor = "0.000000 1.0 1.0", label = "arith.addi : (index)\n\noverflowFlags: #arith.overflow<none...", shape = ellipse, style = filled];
-//       DFG:       v6 [fillcolor = "0.333333 1.0 1.0", label = "arith.constant : (index)\n\nvalue: 0 : index", shape = ellipse, style = filled];
-//       DFG:       v7 [fillcolor = "0.333333 1.0 1.0", label = "arith.constant : (index)\n\nvalue: 1 : index", shape = ellipse, style = filled];
+//       DFG:       v5 [fillcolor = "0.000000 0.3 0.95", label = "{{\{\{}}<arg_c0> %c0|<arg_c1> %c1}|arith.addi\l\loverflowFlags: #arith.overflow\<none...\l|{<res_0> %0 index}}", shape = Mrecord, style = filled];
+//       DFG:       v6 [fillcolor = "0.333333 0.3 0.95", label = "{arith.constant\l\lvalue: 0 : index\l|{<res_c0> %c0 index}}", shape = Mrecord, style = filled];
+//       DFG:       v7 [fillcolor = "0.333333 0.3 0.95", label = "{arith.constant\l\lvalue: 1 : index\l|{<res_c1> %c1 index}}", shape = Mrecord, style = filled];
 //       DFG:     }
 //       DFG:   }
-//       DFG:   v6 -> v5 [label = "0", style = solid];
-//       DFG:   v7 -> v5 [label = "1", style = solid];
+//       DFG:   v6:res_c0:s -> v5:arg_c0:n[style = solid];
+//       DFG:   v7:res_c1:s -> v5:arg_c1:n[style = solid];
 //       DFG: }
 
 module {
diff --git a/mlir/test/Transforms/print-op-graph-cycles.mlir b/mlir/test/Transforms/print-op-graph-cycles.mlir
index 7e4eb5616a28b3..760867802753b0 100644
--- a/mlir/test/Transforms/print-op-graph-cycles.mlir
+++ b/mlir/test/Transforms/print-op-graph-cycles.mlir
@@ -4,41 +4,41 @@
 //       DFG:   compound = true;
 //       DFG:   subgraph cluster_1 {
 //       DFG:     v2 [label = " ", shape = plain];
-//       DFG:     label = "builtin.module : ()\n";
+//       DFG:     label = "builtin.module : ()\l";
 //       DFG:     subgraph cluster_3 {
 //       DFG:       v4 [label = " ", shape = plain];
 //       DFG:       label = "";
 //       DFG:       subgraph cluster_5 {
 //       DFG:         v6 [label = " ", shape = plain];
-//       DFG:         label = "test.graph_region : ()\n";
+//       DFG:         label = "test.graph_region : ()\l";
 //       DFG:         subgraph cluster_7 {
 //       DFG:           v8 [label = " ", shape = plain];
 //       DFG:           label = "";
-//       DFG:           v9 [fillcolor = "0.000000 1.0 1.0", label = "op1 : (i32)\n", shape = ellipse, style = filled];
+//       DFG:           v9 [fillcolor = "0.000000 0.3 0.95", label = "{{\{\{}}<arg_0> %0|<arg_2> %2}|op1\l|{<res_0> %0 i32}}", shape = Mrecord, style = filled];
 //       DFG:           subgraph cluster_10 {
 //       DFG:             v11 [label = " ", shape = plain];
-//       DFG:             label = "test.ssacfg_region : (i32)\n";
+//       DFG:             label = "test.ssacfg_region : (i32)\l";
 //       DFG:             subgraph cluster_12 {
 //       DFG:               v13 [label = " ", shape = plain];
 //       DFG:               label = "";
-//       DFG:               v14 [fillcolor = "0.166667 1.0 1.0", label = "op2 : (i32)\n", shape = ellipse, style = filled];
+//       DFG:               v14 [fillcolor = "0.166667 0.3 0.95", label = "{{\{\{}}<arg_0> %0|<arg_1> %1|<arg_2> %2|<arg_3> %3}|op2\l|{<res_4> %4 i32}}", shape = Mrecord, style = filled];
 //       DFG:             }
 //       DFG:           }
-//       DFG:           v15 [fillcolor = "0.166667 1.0 1.0", label = "op2 : (i32)\n", shape = ellipse, style = filled];
-//       DFG:           v16 [fillcolor = "0.500000 1.0 1.0", label = "op3 : (i32)\n", shape = ellipse, style = filled];
+//       DFG:           v15 [fillcolor = "0.166667 0.3 0.95", label = "{{\{\{}}<arg_0> %0|<arg_3> %3}|op2\l|{<res_2> %2 i32}}", shape = Mrecord, style = filled];
+//       DFG:           v16 [fillcolor = "0.500000 0.3 0.95", label = "{{\{\{}}<arg_0> %0}|op3\l|{<res_3> %3 i32}}", shape = Mrecord, style = filled];
 //       DFG:         }
 //       DFG:       }
 //       DFG:     }
 //       DFG:   }
-//       DFG:   v9 -> v9 [label = "0", style = solid];
-//       DFG:   v15 -> v9 [label = "1", style = solid];
-//       DFG:   v9 -> v14 [label = "0", style = solid];
-//       DFG:   v11 -> v14 [ltail = cluster_10, style = solid];
-//       DFG:   v15 -> v14 [label = "2", style = solid];
-//       DFG:   v16 -> v14 [label = "3", style = solid];
-//       DFG:   v9 -> v15 [label = "0", style = solid];
-//       DFG:   v16 -> v15 [label = "1", style = solid];
-//       DFG:   v9 -> v16 [label = "", style = solid];
+//       DFG:   v9:res_0:s -> v9:arg_0:n[style = solid];
+//       DFG:   v15:res_2:s -> v9:arg_2:n[style = solid];
+//       DFG:   v9:res_0:s -> v14:arg_0:n[style = solid];
+//       DFG:   v11:res_1:s -> v14:arg_1:n[ltail = cluster_10, style = solid];
+//       DFG:   v15:res_2:s -> v14:arg_2:n[style = solid];
+//       DFG:   v16:res_3:s -> v14:arg_3:n[style = solid];
+//       DFG:   v9:res_0:s -> v15:arg_0:n[style = solid];
+//       DFG:   v16:res_3:s -> v15:arg_3:n[style = solid];
+//       DFG:   v9:res_0:s -> v16:arg_0:n[style = solid];
 //       DFG: }
 
 "test.graph_region"() ({ // A Graph region
diff --git a/mlir/test/Transforms/print-op-graph.mlir b/mlir/test/Transforms/print-op-graph.mlir
index df03194a663d95..430ed6299c89c3 100644
--- a/mlir/test/Transforms/print-op-graph.mlir
+++ b/mlir/test/Transforms/print-op-graph.mlir
@@ -6,49 +6,49 @@
 //       DFG:     subgraph {{.*}}
 //       DFG:       label = "func.func{{.*}}merge_blocks
 //       DFG:       subgraph {{.*}} {
-//       DFG:         v[[ARG0:.*]] [label = "arg0"
+//       DFG:         v[[ARG0:.*]] [label = "<res_arg0> %arg0 i32"
 //       DFG:         v[[CONST10:.*]] [{{.*}}label ={{.*}}10 : i32
 //       DFG:         subgraph [[CLUSTER_MERGE_BLOCKS:.*]] {
 //       DFG:           v[[ANCHOR:.*]] [label = " ", shape = plain]
 //       DFG:           label = "test.merge_blocks
 //       DFG:           subgraph {{.*}} {
-//       DFG:             v[[TEST_BR:.*]] [{{.*}}label = "test.br
+//       DFG:             v[[TEST_BR:.*]] [{{.*}}label = "{{.*}}test.br
 //       DFG:           }
 //       DFG:           subgraph {{.*}} {
 //       DFG:           }
 //       DFG:         }
-//       DFG:         v[[TEST_RET:.*]] [{{.*}}label = "test.return
-//       DFG:   v[[ARG0]] -> v[[TEST_BR]]
-//       DFG:   v[[CONST10]] -> v[[TEST_BR]]
-//       DFG:   v[[ANCHOR]] -> v[[TEST_RET]] [ltail = [[CLUSTER_MERGE_BLOCKS]], style = solid];
-//       DFG:   v[[ANCHOR]] -> v[[TEST_RET]] [ltail = [[CLUSTER_MERGE_BLOCKS]], style = solid];
+//       DFG:         v[[TEST_RET:.*]] [{{.*}}label = "{{.*}}test.return
+//       DFG:   v[[ARG0]]:res_arg0:s -> v[[TEST_BR]]:arg_arg0:n
+//       DFG:   v[[CONST10]]:res_c10_i32:s -> v[[TEST_BR]]
+//       DFG:   v[[ANCHOR]]:res_1_0:s -> v[[TEST_RET]]:arg_1_0:n[ltail = [[CLUSTER_MERGE_BLOCKS]], style = solid];
+//       DFG:   v[[ANCHOR]]:res_1_1:s -> v[[TEST_RET]]:arg_1_1:n[ltail = [[CLUSTER_MERGE_BLOCKS]], style = solid];
 
 // CFG-LABEL: digraph G {
 //       CFG:   subgraph {{.*}} {
 //       CFG:     subgraph {{.*}}
 //       CFG:       label = "func.func{{.*}}merge_blocks
 //       CFG:       subgraph {{.*}} {
-//       CFG:         v[[C1:.*]] [{{.*}}label = "arith.constant
-//       CFG:         v[[C2:.*]] [{{.*}}label = "arith.constant
-//       CFG:         v[[C3:.*]] [{{.*}}label = "arith.constant
-//       CFG:         v[[C4:.*]] [{{.*}}label = "arith.constant
-//       CFG:         v[[TEST_FUNC:.*]] [{{.*}}label = "test.func
+//       CFG:         v[[C1:.*]] [{{.*}}label = "{arith.constant
+//       CFG:         v[[C2:.*]] [{{.*}}label = "{arith.constant
+//       CFG:         v[[C3:.*]] [{{.*}}label = "{arith.constant
+//       CFG:         v[[C4:.*]] [{{.*}}label = "{arith.constant
+//       CFG:         v[[TEST_FUNC:.*]] [{{.*}}label = "{test.func
 //       CFG:         subgraph [[CLUSTER_MERGE_BLOCKS:.*]] {
 //       CFG:           v[[ANCHOR:.*]] [label = " ", shape = plain]
 //       CFG:           label = "test.merge_blocks
 //       CFG:           subgraph {{.*}} {
-//       CFG:             v[[TEST_BR:.*]] [{{.*}}label = "test.br
+//       CFG:             v[[TEST_BR:.*]] [{{.*}}label = "{{.*}}test.br
 //       CFG:           }
 //       CFG:           subgraph {{.*}} {
 //       CFG:           }
 //       CFG:         }
-//       CFG:         v[[TEST_RET:.*]] [{{.*}}label = "test.return
+//       CFG:         v[[TEST_RET:.*]] [{{.*}}label = "{{.*}}test.return
 //       CFG:   v[[C1]] -> v[[C2]]
 //       CFG:   v[[C2]] -> v[[C3]]
 //       CFG:   v[[C3]] -> v[[C4]]
 //       CFG:   v[[C4]] -> v[[TEST_FUNC]]
-//       CFG:   v[[TEST_FUNC]] -> v[[ANCHOR]] [lhead = [[CLUSTER_MERGE_BLOCKS]], style = dashed];
-//       CFG:   v[[ANCHOR]] -> v[[TEST_RET]] [ltail = [[CLUSTER_MERGE_BLOCKS]], style = dashed];
+//       CFG:   v[[TEST_FUNC]] -> v[[ANCHOR]][lhead = [[CLUSTER_MERGE_BLOCKS]], style = dashed];
+//       CFG:   v[[ANCHOR]] -> v[[TEST_RET]][ltail = [[CLUSTER_MERGE_BLOCKS]], style = dashed];
 
 func.func @merge_blocks(%arg0: i32, %arg1 : i32) -> () {
   %0 = arith.constant dense<[[0, 1], [2, 3]]> : tensor<2x2xi32>

>From 47b9ae60ca021866bfa209a1e147e1c5fef3eb20 Mon Sep 17 00:00:00 2001
From: Eric Hein <ehein at modular.com>
Date: Tue, 4 Feb 2025 10:26:12 -0500
Subject: [PATCH 19/22] Uniquely identify input and output ports.

Turns out we can use the same value in both input and result slots
of an MLIR operation.
---
 mlir/lib/Transforms/ViewOpGraph.cpp | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/mlir/lib/Transforms/ViewOpGraph.cpp b/mlir/lib/Transforms/ViewOpGraph.cpp
index c11255e22cc957..d6dfea1db546b9 100644
--- a/mlir/lib/Transforms/ViewOpGraph.cpp
+++ b/mlir/lib/Transforms/ViewOpGraph.cpp
@@ -248,12 +248,12 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
       os << "v" << n1.id;
       if (!port.empty())
         // Attach edge to south compass point of the result
-        os << ":" << port << ":s";
+        os << ":res" << port << ":s";
       os << " -> ";
       os << "v" << n2.id;
       if (!port.empty())
         // Attach edge to north compass point of the operand
-        os << ":" << port << ":n";
+        os << ":arg" << port << ":n";
       emitAttrList(os, attrs);
     }));
   }
@@ -330,7 +330,7 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
       if (op->getNumOperands() > 0) {
         os << "{";
         auto operandToPort = [&](Value operand) {
-          os << "<" << getValuePortName(operand) << "> ";
+          os << "<arg" << getValuePortName(operand) << "> ";
           emitMlirOperand(os, operand);
         };
         interleave(op->getOperands(), os, operandToPort, "|");
@@ -353,7 +353,7 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
       if (op->getNumResults() > 0) {
         os << "|{";
         auto resultToPort = [&](Value result) {
-          os << "<" << getValuePortName(result) << "> ";
+          os << "<res" << getValuePortName(result) << "> ";
           emitMlirOperand(os, result);
           if (printResultTypes) {
             os << " ";
@@ -371,7 +371,7 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
   /// Generate a label for a block argument.
   std::string getLabel(BlockArgument arg) {
     return strFromOs([&](raw_ostream &os) {
-      os << "<" << getValuePortName(arg) << "> ";
+      os << "<res" << getValuePortName(arg) << "> ";
       arg.printAsOperand(os, OpPrintingFlags());
       if (printResultTypes) {
         os << " ";

>From 768d032e1ceeee33c3fbf2c8e4359a31e41af204 Mon Sep 17 00:00:00 2001
From: Eric Hein <ehein at modular.com>
Date: Tue, 4 Feb 2025 11:39:33 -0500
Subject: [PATCH 20/22] Fix escaping of some attributes.

---
 mlir/lib/Transforms/ViewOpGraph.cpp | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/mlir/lib/Transforms/ViewOpGraph.cpp b/mlir/lib/Transforms/ViewOpGraph.cpp
index d6dfea1db546b9..e6d3fc3e414656 100644
--- a/mlir/lib/Transforms/ViewOpGraph.cpp
+++ b/mlir/lib/Transforms/ViewOpGraph.cpp
@@ -194,7 +194,8 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
 
     // Always emit splat attributes.
     if (isa<SplatElementsAttr>(attr)) {
-      attr.print(os);
+      os << escapeLabelString(
+          strFromOs([&](raw_ostream &os) { attr.print(os); }));
       return;
     }
 
@@ -202,8 +203,8 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
     auto elements = dyn_cast<ElementsAttr>(attr);
     if (elements && elements.getNumElements() > largeAttrLimit) {
       os << std::string(elements.getShapedType().getRank(), '[') << "..."
-         << std::string(elements.getShapedType().getRank(), ']') << " : "
-         << elements.getType();
+         << std::string(elements.getShapedType().getRank(), ']') << " : ";
+      emitMlirType(os, elements.getType());
       return;
     }
 
@@ -313,7 +314,7 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
       if (printAttrs) {
         os << "\\l";
         for (const NamedAttribute &attr : op->getAttrs()) {
-          os << attr.getName().getValue() << ": ";
+          os << escapeLabelString(attr.getName().getValue().str()) << ": ";
           emitMlirAttr(os, attr.getValue());
           os << "\\l";
         }

>From 35806adde44b96cd15e96cfacc40bbcde9a53d66 Mon Sep 17 00:00:00 2001
From: Eric Hein <ehein at modular.com>
Date: Tue, 4 Feb 2025 11:46:57 -0500
Subject: [PATCH 21/22] Don't use ports when edges attach to clusters.

---
 mlir/lib/Transforms/ViewOpGraph.cpp             | 4 ++--
 mlir/test/Transforms/print-op-graph-cycles.mlir | 2 +-
 mlir/test/Transforms/print-op-graph.mlir        | 4 ++--
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/mlir/lib/Transforms/ViewOpGraph.cpp b/mlir/lib/Transforms/ViewOpGraph.cpp
index e6d3fc3e414656..2d274a00d078ed 100644
--- a/mlir/lib/Transforms/ViewOpGraph.cpp
+++ b/mlir/lib/Transforms/ViewOpGraph.cpp
@@ -247,12 +247,12 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
 
     edges.push_back(strFromOs([&](raw_ostream &os) {
       os << "v" << n1.id;
-      if (!port.empty())
+      if (!port.empty() && !n1.clusterId)
         // Attach edge to south compass point of the result
         os << ":res" << port << ":s";
       os << " -> ";
       os << "v" << n2.id;
-      if (!port.empty())
+      if (!port.empty() && !n2.clusterId)
         // Attach edge to north compass point of the operand
         os << ":arg" << port << ":n";
       emitAttrList(os, attrs);
diff --git a/mlir/test/Transforms/print-op-graph-cycles.mlir b/mlir/test/Transforms/print-op-graph-cycles.mlir
index 760867802753b0..4058078fc8f026 100644
--- a/mlir/test/Transforms/print-op-graph-cycles.mlir
+++ b/mlir/test/Transforms/print-op-graph-cycles.mlir
@@ -33,7 +33,7 @@
 //       DFG:   v9:res_0:s -> v9:arg_0:n[style = solid];
 //       DFG:   v15:res_2:s -> v9:arg_2:n[style = solid];
 //       DFG:   v9:res_0:s -> v14:arg_0:n[style = solid];
-//       DFG:   v11:res_1:s -> v14:arg_1:n[ltail = cluster_10, style = solid];
+//       DFG:   v11 -> v14:arg_1:n[ltail = cluster_10, style = solid];
 //       DFG:   v15:res_2:s -> v14:arg_2:n[style = solid];
 //       DFG:   v16:res_3:s -> v14:arg_3:n[style = solid];
 //       DFG:   v9:res_0:s -> v15:arg_0:n[style = solid];
diff --git a/mlir/test/Transforms/print-op-graph.mlir b/mlir/test/Transforms/print-op-graph.mlir
index 430ed6299c89c3..440b037d780921 100644
--- a/mlir/test/Transforms/print-op-graph.mlir
+++ b/mlir/test/Transforms/print-op-graph.mlir
@@ -20,8 +20,8 @@
 //       DFG:         v[[TEST_RET:.*]] [{{.*}}label = "{{.*}}test.return
 //       DFG:   v[[ARG0]]:res_arg0:s -> v[[TEST_BR]]:arg_arg0:n
 //       DFG:   v[[CONST10]]:res_c10_i32:s -> v[[TEST_BR]]
-//       DFG:   v[[ANCHOR]]:res_1_0:s -> v[[TEST_RET]]:arg_1_0:n[ltail = [[CLUSTER_MERGE_BLOCKS]], style = solid];
-//       DFG:   v[[ANCHOR]]:res_1_1:s -> v[[TEST_RET]]:arg_1_1:n[ltail = [[CLUSTER_MERGE_BLOCKS]], style = solid];
+//       DFG:   v[[ANCHOR]] -> v[[TEST_RET]]:arg_1_0:n[ltail = [[CLUSTER_MERGE_BLOCKS]], style = solid];
+//       DFG:   v[[ANCHOR]] -> v[[TEST_RET]]:arg_1_1:n[ltail = [[CLUSTER_MERGE_BLOCKS]], style = solid];
 
 // CFG-LABEL: digraph G {
 //       CFG:   subgraph {{.*}} {

>From 5bc34e795db4a53c894702f4f9f9a8fa53649449 Mon Sep 17 00:00:00 2001
From: Eric Hein <ehein at modular.com>
Date: Tue, 4 Feb 2025 13:01:12 -0500
Subject: [PATCH 22/22] Drop one-line braces for style

---
 mlir/lib/Transforms/ViewOpGraph.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/mlir/lib/Transforms/ViewOpGraph.cpp b/mlir/lib/Transforms/ViewOpGraph.cpp
index 2d274a00d078ed..b53be32f2f6a3e 100644
--- a/mlir/lib/Transforms/ViewOpGraph.cpp
+++ b/mlir/lib/Transforms/ViewOpGraph.cpp
@@ -385,9 +385,8 @@ class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
   /// operation inside the cluster.
   void processBlock(Block &block) {
     emitClusterStmt([&]() {
-      for (BlockArgument &blockArg : block.getArguments()) {
+      for (BlockArgument &blockArg : block.getArguments())
         valueToNode[blockArg] = emitNodeStmt(getLabel(blockArg));
-      }
       // Emit a node for each operation.
       std::optional<Node> prevNode;
       for (Operation &op : block) {



More information about the Mlir-commits mailing list