forked from OSchip/llvm-project
[mlir] Support drawing control-flow graphs in ViewOpGraph.cpp
* Add new pass option `print-data-flow-edges`, default value `true`. * Add new pass option `print-control-flow-edges`, default value `false`. * Remove `PrintCFGPass`. Same functionality now provided by `PrintOpPass`. Differential Revision: https://reviews.llvm.org/D106342
This commit is contained in:
parent
636428c727
commit
9102a16bef
|
@ -17,7 +17,6 @@
|
|||
#include "mlir/Pass/Pass.h"
|
||||
#include "mlir/Transforms/LocationSnapshot.h"
|
||||
#include "mlir/Transforms/ViewOpGraph.h"
|
||||
#include "mlir/Transforms/ViewRegionGraph.h"
|
||||
#include <limits>
|
||||
|
||||
namespace mlir {
|
||||
|
|
|
@ -612,11 +612,6 @@ def ParallelLoopCollapsing : Pass<"parallel-loop-collapsing"> {
|
|||
];
|
||||
}
|
||||
|
||||
def PrintCFG : FunctionPass<"print-cfg-graph"> {
|
||||
let summary = "Print CFG graph per-Region";
|
||||
let constructor = "mlir::createPrintCFGGraphPass()";
|
||||
}
|
||||
|
||||
def PrintOpStats : Pass<"print-op-stats"> {
|
||||
let summary = "Print statistics of operations";
|
||||
let constructor = "mlir::createPrintOpStatsPass()";
|
||||
|
@ -689,14 +684,17 @@ def SymbolDCE : Pass<"symbol-dce"> {
|
|||
}
|
||||
|
||||
def ViewOpGraphPass : Pass<"view-op-graph"> {
|
||||
let summary = "Print Graphviz dataflow visualization of an operation";
|
||||
let summary = "Print Graphviz visualization of an operation";
|
||||
let description = [{
|
||||
This pass prints a Graphviz dataflow graph of a module.
|
||||
This pass prints a Graphviz graph of a module.
|
||||
|
||||
- Operations are represented as nodes;
|
||||
- Uses as edges;
|
||||
- Uses (data flow) as edges;
|
||||
- Control flow as dashed edges;
|
||||
- Regions/blocks as subgraphs.
|
||||
|
||||
By default, only data flow edges are printed.
|
||||
|
||||
Note: See https://www.graphviz.org/doc/info/lang.html for more information
|
||||
about the Graphviz DOT language.
|
||||
}];
|
||||
|
@ -705,6 +703,10 @@ def ViewOpGraphPass : Pass<"view-op-graph"> {
|
|||
/*default=*/"20", "Limit attribute/type length to number of chars">,
|
||||
Option<"printAttrs", "print-attrs", "bool",
|
||||
/*default=*/"true", "Print attributes of operations">,
|
||||
Option<"printControlFlowEdges", "print-control-flow-edges", "bool",
|
||||
/*default=*/"false", "Print control flow edges">,
|
||||
Option<"printDataFlowEdges", "print-data-flow-edges", "bool",
|
||||
/*default=*/"true", "Print data flow edges">,
|
||||
Option<"printResultTypes", "print-result-types", "bool",
|
||||
/*default=*/"true", "Print result types of operations">
|
||||
];
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
//===- ViewRegionGraph.h - View/write graphviz graphs -----------*- C++ -*-===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Defines interface to produce Graphviz outputs of MLIR Regions.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef MLIR_TRANSFORMS_VIEWFUNCTIONGRAPH_H_
|
||||
#define MLIR_TRANSFORMS_VIEWFUNCTIONGRAPH_H_
|
||||
|
||||
#include "mlir/Support/LLVM.h"
|
||||
#include "llvm/Support/GraphWriter.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
|
||||
namespace mlir {
|
||||
class FuncOp;
|
||||
template <typename T> class OperationPass;
|
||||
class Region;
|
||||
|
||||
/// Displays the CFG in a window. This is for use from the debugger and
|
||||
/// depends on Graphviz to generate the graph.
|
||||
void viewGraph(Region ®ion, const Twine &name, bool shortNames = false,
|
||||
const Twine &title = "",
|
||||
llvm::GraphProgram::Name program = llvm::GraphProgram::DOT);
|
||||
|
||||
raw_ostream &writeGraph(raw_ostream &os, Region ®ion,
|
||||
bool shortNames = false, const Twine &title = "");
|
||||
|
||||
/// Creates a pass to print CFG graphs.
|
||||
std::unique_ptr<mlir::OperationPass<mlir::FuncOp>>
|
||||
createPrintCFGGraphPass(raw_ostream &os = llvm::errs(), bool shortNames = false,
|
||||
const Twine &title = "");
|
||||
|
||||
} // end namespace mlir
|
||||
|
||||
#endif // MLIR_TRANSFORMS_VIEWFUNCTIONGRAPH_H_
|
|
@ -21,7 +21,6 @@ add_mlir_library(MLIRTransforms
|
|||
StripDebugInfo.cpp
|
||||
SymbolDCE.cpp
|
||||
ViewOpGraph.cpp
|
||||
ViewRegionGraph.cpp
|
||||
|
||||
ADDITIONAL_HEADER_DIRS
|
||||
${MLIR_MAIN_INCLUDE_DIR}/mlir/Transforms
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "mlir/Transforms/Passes.h"
|
||||
#include "mlir/Transforms/Utils.h"
|
||||
#include "llvm/ADT/SmallSet.h"
|
||||
#include "llvm/Support/Debug.h"
|
||||
|
||||
#define DEBUG_TYPE "normalize-memrefs"
|
||||
|
||||
|
|
|
@ -12,9 +12,11 @@
|
|||
#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";
|
||||
|
@ -78,6 +80,13 @@ public:
|
|||
});
|
||||
}
|
||||
|
||||
/// 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.
|
||||
|
@ -151,8 +160,7 @@ private:
|
|||
|
||||
/// 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 = kLineStyleDataFlow) {
|
||||
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
|
||||
|
@ -233,14 +241,20 @@ private:
|
|||
valueToNode[blockArg] = emitNodeStmt(getLabel(blockArg));
|
||||
|
||||
// Emit a node for each operation.
|
||||
for (Operation &op : block)
|
||||
processOperation(&op);
|
||||
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.
|
||||
void processOperation(Operation *op) {
|
||||
Node processOperation(Operation *op) {
|
||||
Node node;
|
||||
if (op->getNumRegions() > 0) {
|
||||
// Emit cluster for op with regions.
|
||||
|
@ -254,14 +268,19 @@ private:
|
|||
node = emitNodeStmt(getLabel(op));
|
||||
}
|
||||
|
||||
// Insert edges originating from each operand.
|
||||
unsigned numOperands = op->getNumOperands();
|
||||
for (unsigned i = 0; i < numOperands; i++)
|
||||
emitEdgeStmt(valueToNode[op->getOperand(i)], node,
|
||||
/*label=*/numOperands == 1 ? "" : std::to_string(i));
|
||||
// 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.
|
||||
|
@ -294,3 +313,25 @@ 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"); }
|
||||
|
|
|
@ -1,82 +0,0 @@
|
|||
//===- ViewRegionGraph.cpp - View/write 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/ViewRegionGraph.h"
|
||||
#include "PassDetail.h"
|
||||
#include "mlir/IR/RegionGraphTraits.h"
|
||||
|
||||
using namespace mlir;
|
||||
|
||||
namespace llvm {
|
||||
|
||||
// Specialize DOTGraphTraits to produce more readable output.
|
||||
template <> struct DOTGraphTraits<Region *> : public DefaultDOTGraphTraits {
|
||||
using DefaultDOTGraphTraits::DefaultDOTGraphTraits;
|
||||
|
||||
static std::string getNodeLabel(Block *Block, Region *);
|
||||
};
|
||||
|
||||
std::string DOTGraphTraits<Region *>::getNodeLabel(Block *Block, Region *) {
|
||||
// Reuse the print output for the node labels.
|
||||
std::string outStreamStr;
|
||||
raw_string_ostream os(outStreamStr);
|
||||
Block->print(os);
|
||||
std::string &outStr = os.str();
|
||||
|
||||
if (outStr[0] == '\n')
|
||||
outStr.erase(outStr.begin());
|
||||
|
||||
// Process string output to left justify the block.
|
||||
for (unsigned i = 0; i != outStr.length(); ++i) {
|
||||
if (outStr[i] == '\n') {
|
||||
outStr[i] = '\\';
|
||||
outStr.insert(outStr.begin() + i + 1, 'l');
|
||||
}
|
||||
}
|
||||
|
||||
return outStr;
|
||||
}
|
||||
|
||||
} // end namespace llvm
|
||||
|
||||
void mlir::viewGraph(Region ®ion, const Twine &name, bool shortNames,
|
||||
const Twine &title, llvm::GraphProgram::Name program) {
|
||||
llvm::ViewGraph(®ion, name, shortNames, title, program);
|
||||
}
|
||||
|
||||
raw_ostream &mlir::writeGraph(raw_ostream &os, Region ®ion, bool shortNames,
|
||||
const Twine &title) {
|
||||
return llvm::WriteGraph(os, ®ion, shortNames, title);
|
||||
}
|
||||
|
||||
void mlir::Region::viewGraph(const Twine ®ionName) {
|
||||
::mlir::viewGraph(*this, regionName);
|
||||
}
|
||||
void mlir::Region::viewGraph() { viewGraph("region"); }
|
||||
|
||||
namespace {
|
||||
struct PrintCFGPass : public PrintCFGBase<PrintCFGPass> {
|
||||
PrintCFGPass(raw_ostream &os = llvm::errs(), bool shortNames = false,
|
||||
const Twine &title = "")
|
||||
: os(os), shortNames(shortNames), title(title.str()) {}
|
||||
void runOnFunction() override {
|
||||
mlir::writeGraph(os, getFunction().getBody(), shortNames, title);
|
||||
}
|
||||
|
||||
private:
|
||||
raw_ostream &os;
|
||||
bool shortNames;
|
||||
std::string title;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<mlir::OperationPass<mlir::FuncOp>>
|
||||
mlir::createPrintCFGGraphPass(raw_ostream &os, bool shortNames,
|
||||
const Twine &title) {
|
||||
return std::make_unique<PrintCFGPass>(os, shortNames, title);
|
||||
}
|
|
@ -1,26 +1,55 @@
|
|||
// RUN: mlir-opt -allow-unregistered-dialect -mlir-elide-elementsattrs-if-larger=2 -view-op-graph %s -o %t 2>&1 | FileCheck %s
|
||||
// RUN: mlir-opt -allow-unregistered-dialect -mlir-elide-elementsattrs-if-larger=2 -view-op-graph %s -o %t 2>&1 | FileCheck -check-prefix=DFG %s
|
||||
// RUN: mlir-opt -allow-unregistered-dialect -mlir-elide-elementsattrs-if-larger=2 -view-op-graph='print-data-flow-edges=false print-control-flow-edges=true' %s -o %t 2>&1 | FileCheck -check-prefix=CFG %s
|
||||
|
||||
// DFG-LABEL: digraph G {
|
||||
// DFG: subgraph {{.*}} {
|
||||
// DFG: subgraph {{.*}}
|
||||
// DFG: label = "builtin.func{{.*}}merge_blocks
|
||||
// DFG: subgraph {{.*}} {
|
||||
// DFG: v[[ARG0:.*]] [label = "arg0"
|
||||
// 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: }
|
||||
// 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]]]
|
||||
// DFG: v[[ANCHOR]] -> v[[TEST_RET]] [{{.*}}, ltail = [[CLUSTER_MERGE_BLOCKS]]]
|
||||
|
||||
// CFG-LABEL: digraph G {
|
||||
// CFG: subgraph {{.*}} {
|
||||
// CFG: subgraph {{.*}}
|
||||
// CFG: label = "builtin.func{{.*}}merge_blocks
|
||||
// CFG: subgraph {{.*}} {
|
||||
// CFG: v[[C1:.*]] [label = "std.constant
|
||||
// CFG: v[[C2:.*]] [label = "std.constant
|
||||
// CFG: v[[C3:.*]] [label = "std.constant
|
||||
// CFG: v[[C4:.*]] [label = "std.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: }
|
||||
// CFG: subgraph {{.*}} {
|
||||
// CFG: }
|
||||
// CFG: }
|
||||
// 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]]]
|
||||
// CFG: v[[ANCHOR]] -> v[[TEST_RET]] [{{.*}}, ltail = [[CLUSTER_MERGE_BLOCKS]]]
|
||||
|
||||
// CHECK-LABEL: digraph G {
|
||||
// CHECK: subgraph {{.*}} {
|
||||
// CHECK: subgraph {{.*}}
|
||||
// CHECK: label = "builtin.func{{.*}}merge_blocks
|
||||
// CHECK: subgraph {{.*}} {
|
||||
// CHECK: v[[ARG0:.*]] [label = "arg0"
|
||||
// CHECK: v[[CONST10:.*]] [label ={{.*}}10 : i32
|
||||
// CHECK: subgraph [[CLUSTER_MERGE_BLOCKS:.*]] {
|
||||
// CHECK: v[[ANCHOR:.*]] [label = " ", shape = plain]
|
||||
// CHECK: label = "test.merge_blocks
|
||||
// CHECK: subgraph {{.*}} {
|
||||
// CHECK: v[[TEST_BR:.*]] [label = "test.br
|
||||
// CHECK: }
|
||||
// CHECK: subgraph {{.*}} {
|
||||
// CHECK: }
|
||||
// CHECK: }
|
||||
// CHECK: v[[TEST_RET:.*]] [label = "test.return
|
||||
// CHECK: v[[ARG0]] -> v[[TEST_BR]]
|
||||
// CHECK: v[[CONST10]] -> v[[TEST_BR]]
|
||||
// CHECK: v[[ANCHOR]] -> v[[TEST_RET]] [{{.*}}, ltail = [[CLUSTER_MERGE_BLOCKS]]]
|
||||
// CHECK: v[[ANCHOR]] -> v[[TEST_RET]] [{{.*}}, ltail = [[CLUSTER_MERGE_BLOCKS]]]
|
||||
func @merge_blocks(%arg0: i32, %arg1 : i32) -> () {
|
||||
%0 = constant dense<[[0, 1], [2, 3]]> : tensor<2x2xi32>
|
||||
%1 = constant dense<1> : tensor<5xi32>
|
||||
|
|
Loading…
Reference in New Issue