forked from OSchip/llvm-project
338 lines
11 KiB
C++
338 lines
11 KiB
C++
//===- ViewOpGraph.cpp - View/write op graphviz graphs --------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "mlir/Transforms/ViewOpGraph.h"
|
|
#include "PassDetail.h"
|
|
#include "mlir/IR/Block.h"
|
|
#include "mlir/IR/Operation.h"
|
|
#include "mlir/Support/IndentedOstream.h"
|
|
#include "llvm/Support/Format.h"
|
|
#include "llvm/Support/GraphWriter.h"
|
|
|
|
using namespace mlir;
|
|
|
|
static const StringRef kLineStyleControlFlow = "dashed";
|
|
static const StringRef kLineStyleDataFlow = "solid";
|
|
static const StringRef kShapeNode = "ellipse";
|
|
static const StringRef kShapeNone = "plain";
|
|
|
|
/// Return the size limits for eliding large attributes.
|
|
static int64_t getLargeAttributeSizeLimit() {
|
|
// Use the default from the printer flags if possible.
|
|
if (Optional<int64_t> limit = OpPrintingFlags().getLargeElementsAttrLimit())
|
|
return *limit;
|
|
return 16;
|
|
}
|
|
|
|
/// Return all values printed onto a stream as a string.
|
|
static std::string strFromOs(function_ref<void(raw_ostream &)> func) {
|
|
std::string buf;
|
|
llvm::raw_string_ostream os(buf);
|
|
func(os);
|
|
return os.str();
|
|
}
|
|
|
|
/// 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(std::string str) { return "\"" + str + "\""; }
|
|
|
|
using AttributeMap = llvm::StringMap<std::string>;
|
|
|
|
namespace {
|
|
|
|
/// This struct represents a node in the DOT language. Each node has an
|
|
/// identifier and an optional identifier for the cluster (subgraph) that
|
|
/// contains the node.
|
|
/// Note: In the DOT language, edges can be drawn only from nodes to nodes, but
|
|
/// not between clusters. However, edges can be clipped to the boundary of a
|
|
/// cluster with `lhead` and `ltail` attributes. Therefore, when creating a new
|
|
/// cluster, an invisible "anchor" node is created.
|
|
struct Node {
|
|
public:
|
|
Node(int id = 0, Optional<int> clusterId = llvm::None)
|
|
: id(id), clusterId(clusterId) {}
|
|
|
|
int id;
|
|
Optional<int> clusterId;
|
|
};
|
|
|
|
/// 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.
|
|
class PrintOpPass : public ViewOpGraphBase<PrintOpPass> {
|
|
public:
|
|
PrintOpPass(raw_ostream &os) : os(os) {}
|
|
PrintOpPass(const PrintOpPass &o) : PrintOpPass(o.os.getOStream()) {}
|
|
|
|
void runOnOperation() override {
|
|
emitGraph([&]() {
|
|
processOperation(getOperation());
|
|
emitAllEdgeStmts();
|
|
});
|
|
}
|
|
|
|
/// Create a CFG graph for a region. Used in `Region::viewGraph`.
|
|
void emitRegionCFG(Region ®ion) {
|
|
printControlFlowEdges = true;
|
|
printDataFlowEdges = false;
|
|
emitGraph([&]() { processRegion(region); });
|
|
}
|
|
|
|
private:
|
|
/// Emit all edges. This function should be called after all nodes have been
|
|
/// emitted.
|
|
void emitAllEdgeStmts() {
|
|
for (const std::string &edge : edges)
|
|
os << edge << ";\n";
|
|
edges.clear();
|
|
}
|
|
|
|
/// Emit a cluster (subgraph). The specified builder generates the body of the
|
|
/// cluster. Return the anchor node of the cluster.
|
|
Node emitClusterStmt(function_ref<void()> builder, std::string label = "") {
|
|
int clusterId = ++counter;
|
|
os << "subgraph cluster_" << clusterId << " {\n";
|
|
os.indent();
|
|
// Emit invisible anchor node from/to which arrows can be drawn.
|
|
Node anchorNode = emitNodeStmt(" ", kShapeNone);
|
|
os << attrStmt("label", quoteString(escapeString(label))) << ";\n";
|
|
builder();
|
|
os.unindent();
|
|
os << "}\n";
|
|
return Node(anchorNode.id, clusterId);
|
|
}
|
|
|
|
/// Generate an attribute statement.
|
|
std::string attrStmt(const Twine &key, const Twine &value) {
|
|
return (key + " = " + value).str();
|
|
}
|
|
|
|
/// Emit an attribute list.
|
|
void emitAttrList(raw_ostream &os, const AttributeMap &map) {
|
|
os << "[";
|
|
interleaveComma(map, os, [&](const auto &it) {
|
|
os << this->attrStmt(it.getKey(), it.getValue());
|
|
});
|
|
os << "]";
|
|
}
|
|
|
|
// Print an MLIR attribute to `os`. Large attributes are truncated.
|
|
void emitMlirAttr(raw_ostream &os, Attribute attr) {
|
|
// A value used to elide large container attribute.
|
|
int64_t largeAttrLimit = getLargeAttributeSizeLimit();
|
|
|
|
// Always emit splat attributes.
|
|
if (attr.isa<SplatElementsAttr>()) {
|
|
attr.print(os);
|
|
return;
|
|
}
|
|
|
|
// Elide "big" elements attributes.
|
|
auto elements = attr.dyn_cast<ElementsAttr>();
|
|
if (elements && elements.getNumElements() > largeAttrLimit) {
|
|
os << std::string(elements.getType().getRank(), '[') << "..."
|
|
<< std::string(elements.getType().getRank(), ']') << " : "
|
|
<< elements.getType();
|
|
return;
|
|
}
|
|
|
|
auto array = attr.dyn_cast<ArrayAttr>();
|
|
if (array && static_cast<int64_t>(array.size()) > largeAttrLimit) {
|
|
os << "[...]";
|
|
return;
|
|
}
|
|
|
|
// Print all other attributes.
|
|
std::string buf;
|
|
llvm::raw_string_ostream ss(buf);
|
|
attr.print(ss);
|
|
os << truncateString(ss.str());
|
|
}
|
|
|
|
/// 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) {
|
|
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(label));
|
|
// Use `ltail` and `lhead` to draw edges between clusters.
|
|
if (n1.clusterId)
|
|
attrs["ltail"] = "cluster_" + std::to_string(*n1.clusterId);
|
|
if (n2.clusterId)
|
|
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);
|
|
emitAttrList(os, attrs);
|
|
}));
|
|
}
|
|
|
|
/// Emit a graph. The specified builder generates the body of the graph.
|
|
void emitGraph(function_ref<void()> builder) {
|
|
os << "digraph G {\n";
|
|
os.indent();
|
|
// Edges between clusters are allowed only in compound mode.
|
|
os << attrStmt("compound", "true") << ";\n";
|
|
builder();
|
|
os.unindent();
|
|
os << "}\n";
|
|
}
|
|
|
|
/// Emit a node statement.
|
|
Node emitNodeStmt(std::string label, StringRef shape = kShapeNode) {
|
|
int nodeId = ++counter;
|
|
AttributeMap attrs;
|
|
attrs["label"] = quoteString(escapeString(label));
|
|
attrs["shape"] = shape.str();
|
|
os << llvm::format("v%i ", nodeId);
|
|
emitAttrList(os, attrs);
|
|
os << ";\n";
|
|
return Node(nodeId);
|
|
}
|
|
|
|
/// Generate a label for an operation.
|
|
std::string getLabel(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(ss.str()) << ")";
|
|
os << ")";
|
|
}
|
|
|
|
// Print attributes.
|
|
if (printAttrs) {
|
|
os << "\n";
|
|
for (const NamedAttribute &attr : op->getAttrs()) {
|
|
os << '\n' << attr.getName().getValue() << ": ";
|
|
emitMlirAttr(os, attr.getValue());
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/// Generate a label for a block argument.
|
|
std::string getLabel(BlockArgument arg) {
|
|
return "arg" + std::to_string(arg.getArgNumber());
|
|
}
|
|
|
|
/// Process a block. Emit a cluster and one node per block argument and
|
|
/// operation inside the cluster.
|
|
void processBlock(Block &block) {
|
|
emitClusterStmt([&]() {
|
|
for (BlockArgument &blockArg : block.getArguments())
|
|
valueToNode[blockArg] = emitNodeStmt(getLabel(blockArg));
|
|
|
|
// Emit a node for each operation.
|
|
Optional<Node> prevNode;
|
|
for (Operation &op : block) {
|
|
Node nextNode = processOperation(&op);
|
|
if (printControlFlowEdges && prevNode)
|
|
emitEdgeStmt(*prevNode, nextNode, /*label=*/"",
|
|
kLineStyleControlFlow);
|
|
prevNode = nextNode;
|
|
}
|
|
});
|
|
}
|
|
|
|
/// Process an operation. If the operation has regions, emit a cluster.
|
|
/// Otherwise, emit a node.
|
|
Node processOperation(Operation *op) {
|
|
Node node;
|
|
if (op->getNumRegions() > 0) {
|
|
// Emit cluster for op with regions.
|
|
node = emitClusterStmt(
|
|
[&]() {
|
|
for (Region ®ion : op->getRegions())
|
|
processRegion(region);
|
|
},
|
|
getLabel(op));
|
|
} else {
|
|
node = emitNodeStmt(getLabel(op));
|
|
}
|
|
|
|
// Insert data flow edges originating from each operand.
|
|
if (printDataFlowEdges) {
|
|
unsigned numOperands = op->getNumOperands();
|
|
for (unsigned i = 0; i < numOperands; i++)
|
|
emitEdgeStmt(valueToNode[op->getOperand(i)], node,
|
|
/*label=*/numOperands == 1 ? "" : std::to_string(i),
|
|
kLineStyleDataFlow);
|
|
}
|
|
|
|
for (Value result : op->getResults())
|
|
valueToNode[result] = node;
|
|
|
|
return node;
|
|
}
|
|
|
|
/// Process a region.
|
|
void processRegion(Region ®ion) {
|
|
for (Block &block : region.getBlocks())
|
|
processBlock(block);
|
|
}
|
|
|
|
/// Truncate long strings.
|
|
std::string truncateString(std::string str) {
|
|
if (str.length() <= maxLabelLen)
|
|
return str;
|
|
return str.substr(0, maxLabelLen) + "...";
|
|
}
|
|
|
|
/// Output stream to write DOT file to.
|
|
raw_indented_ostream os;
|
|
/// A list of edges. For simplicity, should be emitted after all nodes were
|
|
/// emitted.
|
|
std::vector<std::string> edges;
|
|
/// Mapping of SSA values to Graphviz nodes/clusters.
|
|
DenseMap<Value, Node> valueToNode;
|
|
/// Counter for generating unique node/subgraph identifiers.
|
|
int counter = 0;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
std::unique_ptr<Pass>
|
|
mlir::createPrintOpGraphPass(raw_ostream &os) {
|
|
return std::make_unique<PrintOpPass>(os);
|
|
}
|
|
|
|
/// Generate a CFG for a region and show it in a window.
|
|
static void llvmViewGraph(Region ®ion, const Twine &name) {
|
|
int fd;
|
|
std::string filename = llvm::createGraphFilename(name.str(), fd);
|
|
{
|
|
llvm::raw_fd_ostream os(fd, /*shouldClose=*/true);
|
|
if (fd == -1) {
|
|
llvm::errs() << "error opening file '" << filename << "' for writing\n";
|
|
return;
|
|
}
|
|
PrintOpPass pass(os);
|
|
pass.emitRegionCFG(region);
|
|
}
|
|
llvm::DisplayGraph(filename, /*wait=*/false, llvm::GraphProgram::DOT);
|
|
}
|
|
|
|
void mlir::Region::viewGraph(const Twine ®ionName) {
|
|
llvmViewGraph(*this, regionName);
|
|
}
|
|
|
|
void mlir::Region::viewGraph() { viewGraph("region"); }
|