#include "mlir/Transforms/ViewOpGraph.h"
#include "mlir/IR/Block.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/Operation.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Support/IndentedOstream.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/GraphWriter.h"
#include <map>
#include <optional>
#include <utility>
namespace mlir {
#define GEN_PASS_DEF_VIEWOPGRAPH
#include "mlir/Transforms/Passes.h.inc"
}
using namespace mlir;
static const StringRef kLineStyleControlFlow = "dashed";
static const StringRef kLineStyleDataFlow = "solid";
static const StringRef kShapeNode = "Mrecord";
static const StringRef kShapeNone = "plain";
static int64_t getLargeAttributeSizeLimit() {
if (std::optional<int64_t> limit =
OpPrintingFlags().getLargeElementsAttrLimit())
return *limit;
return 16;
}
static std::string strFromOs(function_ref<void(raw_ostream &)> func) {
std::string buf;
llvm::raw_string_ostream os(buf);
func(os);
return buf;
}
static std::string quoteString(const std::string &str) {
return "\"" + str + "\"";
}
std::string escapeLabelString(const std::string &str) {
std::string buf;
llvm::raw_string_ostream os(buf);
for (char c : str) {
if (llvm::is_contained({'{', '|', '<', '}', '>', '\n', '"'}, c))
os << '\\';
os << c;
}
return buf;
}
using AttributeMap = std::map<std::string, std::string>;
namespace {
struct Node {
public:
Node(int id = 0, std::optional<int> clusterId = std::nullopt)
: id(id), clusterId(clusterId) {}
int id;
std::optional<int> clusterId;
};
struct DataFlowEdge {
Value value;
Node node;
std::string port;
};
class PrintOpPass : public impl::ViewOpGraphBase<PrintOpPass> {
public:
PrintOpPass(raw_ostream &os) : os(os) {}
PrintOpPass(const PrintOpPass &o) : PrintOpPass(o.os.getOStream()) {}
void runOnOperation() override {
initColorMapping(*getOperation());
emitGraph([&]() {
processOperation(getOperation());
emitAllEdgeStmts();
});
markAllAnalysesPreserved();
}
void emitRegionCFG(Region ®ion) {
printControlFlowEdges = true;
printDataFlowEdges = false;
initColorMapping(region);
emitGraph([&]() { processRegion(region); });
}
private:
template <typename T>
void initColorMapping(T &irEntity) {
backgroundColors.clear();
SmallVector<Operation *> ops;
irEntity.walk([&](Operation *op) {
auto &entry = backgroundColors[op->getName()];
if (entry.first == 0)
ops.push_back(op);
++entry.first;
});
for (auto indexedOps : llvm::enumerate(ops)) {
double hue = ((double)indexedOps.index()) / ops.size();
backgroundColors[indexedOps.value()->getName()].second =
std::to_string(hue) + " 0.3 0.95";
}
}
void emitAllEdgeStmts() {
if (printDataFlowEdges) {
for (const auto &e : dataFlowEdges) {
emitEdgeStmt(valueToNode[e.value], e.node, e.port, kLineStyleDataFlow);
}
}
for (const std::string &edge : edges)
os << edge << ";\n";
edges.clear();
}
Node emitClusterStmt(function_ref<void()> builder,
const std::string &label = "") {
int clusterId = ++counter;
os << "subgraph cluster_" << clusterId << " {\n";
os.indent();
Node anchorNode = emitNodeStmt(" ", kShapeNone);
os << attrStmt("label", quoteString(label)) << ";\n";
builder();
os.unindent();
os << "}\n";
return Node(anchorNode.id, clusterId);
}
std::string attrStmt(const Twine &key, const Twine &value) {
return (key + " = " + value).str();
}
void emitAttrList(raw_ostream &os, const AttributeMap &map) {
os << "[";
interleaveComma(map, os, [&](const auto &it) {
os << this->attrStmt(it.first, it.second);
});
os << "]";
}
void emitMlirAttr(raw_ostream &os, Attribute attr) {
int64_t largeAttrLimit = getLargeAttributeSizeLimit();
if (isa<SplatElementsAttr>(attr)) {
os << escapeLabelString(
strFromOs([&](raw_ostream &os) { attr.print(os); }));
return;
}
auto elements = dyn_cast<ElementsAttr>(attr);
if (elements && elements.getNumElements() > largeAttrLimit) {
os << std::string(elements.getShapedType().getRank(), '[') << "..."
<< std::string(elements.getShapedType().getRank(), ']') << " : ";
emitMlirType(os, elements.getType());
return;
}
auto array = dyn_cast<ArrayAttr>(attr);
if (array && static_cast<int64_t>(array.size()) > largeAttrLimit) {
os << "[...]";
return;
}
std::string buf;
llvm::raw_string_ostream ss(buf);
attr.print(ss);
os << escapeLabelString(truncateString(buf));
}
void emitMlirType(raw_ostream &os, Type type) {
std::string buf;
llvm::raw_string_ostream ss(buf);
type.print(ss);
os << escapeLabelString(truncateString(buf));
}
void emitMlirOperand(raw_ostream &os, Value operand) {
operand.printAsOperand(os, OpPrintingFlags());
}
void emitEdgeStmt(Node n1, Node n2, std::string port, StringRef style) {
AttributeMap attrs;
attrs["style"] = style.str();
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 << "v" << n1.id;
if (!port.empty() && !n1.clusterId)
os << ":res" << port << ":s";
os << " -> ";
os << "v" << n2.id;
if (!port.empty() && !n2.clusterId)
os << ":arg" << port << ":n";
emitAttrList(os, attrs);
}));
}
void emitGraph(function_ref<void()> builder) {
os << "digraph G {\n";
os.indent();
os << attrStmt("compound", "true") << ";\n";
builder();
os.unindent();
os << "}\n";
}
Node emitNodeStmt(const std::string &label, StringRef shape = kShapeNode,
StringRef background = "") {
int nodeId = ++counter;
AttributeMap attrs;
attrs["label"] = quoteString(label);
attrs["shape"] = shape.str();
if (!background.empty()) {
attrs["style"] = "filled";
attrs["fillcolor"] = quoteString(background.str());
}
os << llvm::format("v%i ", nodeId);
emitAttrList(os, attrs);
os << ";\n";
return Node(nodeId);
}
std::string getValuePortName(Value operand) {
auto str = strFromOs([&](raw_ostream &os) {
operand.printAsOperand(os, OpPrintingFlags());
});
llvm::replace(str, '%', '_');
llvm::replace(str, '#', '_');
return str;
}
std::string getClusterLabel(Operation *op) {
return strFromOs([&](raw_ostream &os) {
os << op->getName();
if (printResultTypes) {
os << " : (";
std::string buf;
llvm::raw_string_ostream ss(buf);
interleaveComma(op->getResultTypes(), ss);
os << truncateString(buf) << ")";
}
if (printAttrs) {
os << "\\l";
for (const NamedAttribute &attr : op->getAttrs()) {
os << escapeLabelString(attr.getName().getValue().str()) << ": ";
emitMlirAttr(os, attr.getValue());
os << "\\l";
}
}
});
}
std::string getRecordLabel(Operation *op) {
return strFromOs([&](raw_ostream &os) {
os << "{";
if (op->getNumOperands() > 0) {
os << "{";
auto operandToPort = [&](Value operand) {
os << "<arg" << getValuePortName(operand) << "> ";
emitMlirOperand(os, operand);
};
interleave(op->getOperands(), os, operandToPort, "|");
os << "}|";
}
os << op->getName() << "\\l";
if (printAttrs && !op->getAttrs().empty()) {
os << "\\l";
for (const NamedAttribute &attr : op->getAttrs()) {
os << attr.getName().getValue() << ": ";
emitMlirAttr(os, attr.getValue());
os << "\\l";
}
}
if (op->getNumResults() > 0) {
os << "|{";
auto resultToPort = [&](Value result) {
os << "<res" << getValuePortName(result) << "> ";
emitMlirOperand(os, result);
if (printResultTypes) {
os << " ";
emitMlirType(os, result.getType());
}
};
interleave(op->getResults(), os, resultToPort, "|");
os << "}";
}
os << "}";
});
}
std::string getLabel(BlockArgument arg) {
return strFromOs([&](raw_ostream &os) {
os << "<res" << getValuePortName(arg) << "> ";
arg.printAsOperand(os, OpPrintingFlags());
if (printResultTypes) {
os << " ";
emitMlirType(os, arg.getType());
}
});
}
void processBlock(Block &block) {
emitClusterStmt([&]() {
for (BlockArgument &blockArg : block.getArguments())
valueToNode[blockArg] = emitNodeStmt(getLabel(blockArg));
std::optional<Node> prevNode;
for (Operation &op : block) {
Node nextNode = processOperation(&op);
if (printControlFlowEdges && prevNode)
emitEdgeStmt(*prevNode, nextNode, "", kLineStyleControlFlow);
prevNode = nextNode;
}
});
}
Node processOperation(Operation *op) {
Node node;
if (op->getNumRegions() > 0) {
node = emitClusterStmt(
[&]() {
for (Region ®ion : op->getRegions())
processRegion(region);
},
getClusterLabel(op));
} else {
node = emitNodeStmt(getRecordLabel(op), kShapeNode,
backgroundColors[op->getName()].second);
}
if (printDataFlowEdges) {
unsigned numOperands = op->getNumOperands();
for (unsigned i = 0; i < numOperands; i++) {
auto operand = op->getOperand(i);
dataFlowEdges.push_back({operand, node, getValuePortName(operand)});
}
}
for (Value result : op->getResults())
valueToNode[result] = node;
return node;
}
void processRegion(Region ®ion) {
for (Block &block : region.getBlocks())
processBlock(block);
}
std::string truncateString(std::string str) {
if (str.length() <= maxLabelLen)
return str;
return str.substr(0, maxLabelLen) + "...";
}
raw_indented_ostream os;
std::vector<std::string> edges;
DenseMap<Value, Node> valueToNode;
std::vector<DataFlowEdge> dataFlowEdges;
int counter = 0;
DenseMap<OperationName, std::pair<int, std::string>> backgroundColors;
};
}
std::unique_ptr<Pass> mlir::createPrintOpGraphPass(raw_ostream &os) {
return std::make_unique<PrintOpPass>(os);
}
static void llvmViewGraph(Region ®ion, const Twine &name) {
int fd;
std::string filename = llvm::createGraphFilename(name.str(), fd);
{
llvm::raw_fd_ostream os(fd, true);
if (fd == -1) {
llvm::errs() << "error opening file '" << filename << "' for writing\n";
return;
}
PrintOpPass pass(os);
pass.emitRegionCFG(region);
}
llvm::DisplayGraph(filename, false, llvm::GraphProgram::DOT);
}
void mlir::Region::viewGraph(const Twine ®ionName) {
llvmViewGraph(*this, regionName);
}
void mlir::Region::viewGraph() { viewGraph("region"); }