forked from OSchip/llvm-project
Add pass generate per block in a function a GraphViz Dot graph with ops as nodes
* Add GraphTraits that treat a block as a graph, Operation* as node and use-relationship for edges; - Just basic graph output; * Add use iterator to iterate over all uses of an Operation; * Add testing pass to generate op graph; This does not support arbitrary operations other than function nor nested regions yet. PiperOrigin-RevId: 268121782
This commit is contained in:
parent
f4ae4762bf
commit
2660623a88
|
@ -686,6 +686,33 @@ public:
|
|||
: llvm::mapped_iterator<ResultIterator, Type (*)(Value *)>(it, &unwrap) {}
|
||||
};
|
||||
|
||||
/// This class implements use iterator for the Operation. This iterates over all
|
||||
/// uses of all results of an Operation.
|
||||
class UseIterator final
|
||||
: public llvm::iterator_facade_base<UseIterator, std::forward_iterator_tag,
|
||||
Operation *> {
|
||||
public:
|
||||
/// Initialize UseIterator for op, specify end to return iterator to last use.
|
||||
explicit UseIterator(Operation *op, bool end = false);
|
||||
|
||||
UseIterator &operator++();
|
||||
Operation *operator->() { return use->getOwner(); }
|
||||
Operation *operator*() { return use->getOwner(); }
|
||||
|
||||
bool operator==(const UseIterator &other) const;
|
||||
bool operator!=(const UseIterator &other) const;
|
||||
|
||||
private:
|
||||
void skipOverResultsWithNoUsers();
|
||||
|
||||
/// The operation whose uses are being iterated over.
|
||||
Operation *op;
|
||||
/// The result of op whoses uses are being iterated over.
|
||||
Operation::result_iterator res;
|
||||
/// The use of the result.
|
||||
Value::use_iterator use;
|
||||
};
|
||||
|
||||
// Implement the inline result iterator methods.
|
||||
inline auto Operation::result_begin() -> result_iterator {
|
||||
return result_iterator(this, 0);
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
//===- ViewOpGraph.h - View/write op graphviz graphs ------------*- C++ -*-===//
|
||||
//
|
||||
// Copyright 2019 The MLIR Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// =============================================================================
|
||||
//
|
||||
// Defines interface to produce Graphviz outputs of MLIR op within block.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef MLIR_TRANSFORMS_VIEWOPGRAPH_H_
|
||||
#define MLIR_TRANSFORMS_VIEWOPGRAPH_H_
|
||||
|
||||
#include "mlir/Support/LLVM.h"
|
||||
#include "llvm/Support/GraphWriter.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
|
||||
namespace mlir {
|
||||
class Block;
|
||||
class ModuleOp;
|
||||
template <typename T> class OpPassBase;
|
||||
using ModulePassBase = OpPassBase<ModuleOp>;
|
||||
|
||||
/// Displays the graph in a window. This is for use from the debugger and
|
||||
/// depends on Graphviz to generate the graph.
|
||||
void viewGraph(Block &block, const Twine &name, bool shortNames = false,
|
||||
const Twine &title = "",
|
||||
llvm::GraphProgram::Name program = llvm::GraphProgram::DOT);
|
||||
|
||||
llvm::raw_ostream &writeGraph(llvm::raw_ostream &os, Block &block,
|
||||
bool shortNames = false, const Twine &title = "");
|
||||
|
||||
/// Creates a pass to print op graphs.
|
||||
std::unique_ptr<ModulePassBase>
|
||||
createPrintOpGraphPass(llvm::raw_ostream &os = llvm::errs(),
|
||||
bool shortNames = false, const llvm::Twine &title = "");
|
||||
|
||||
} // end namespace mlir
|
||||
|
||||
#endif // MLIR_TRANSFORMS_VIEWOPGRAPH_H_
|
|
@ -1026,3 +1026,46 @@ void impl::ensureRegionTerminator(
|
|||
|
||||
block.push_back(buildTerminatorOp());
|
||||
}
|
||||
|
||||
UseIterator::UseIterator(Operation *op, bool end)
|
||||
: op(op), res(end ? op->result_end() : op->result_begin()) {
|
||||
// Only initialize current use if there are results/can be uses.
|
||||
if (op->getNumResults())
|
||||
skipOverResultsWithNoUsers();
|
||||
}
|
||||
|
||||
UseIterator &UseIterator::operator++() {
|
||||
// We increment over uses, if we reach the last use then move to next
|
||||
// result.
|
||||
if (use != (*res)->use_end())
|
||||
++use;
|
||||
if (use == (*res)->use_end()) {
|
||||
++res;
|
||||
skipOverResultsWithNoUsers();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool UseIterator::operator==(const UseIterator &other) const {
|
||||
if (op != other.op)
|
||||
return false;
|
||||
if (op->getNumResults() == 0)
|
||||
return true;
|
||||
return res == other.res && use == other.use;
|
||||
}
|
||||
|
||||
bool UseIterator::operator!=(const UseIterator &other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
void UseIterator::skipOverResultsWithNoUsers() {
|
||||
while (res != op->result_end() && (*res)->use_empty())
|
||||
++res;
|
||||
|
||||
// If we are at the last result, then set use to first use of
|
||||
// first result (sentinel value used for end).
|
||||
if (res == op->result_end())
|
||||
use = {};
|
||||
else
|
||||
use = (*res)->use_begin();
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ add_llvm_library(MLIRTransforms
|
|||
SimplifyAffineStructures.cpp
|
||||
StripDebugInfo.cpp
|
||||
Vectorize.cpp
|
||||
ViewOpGraph.cpp
|
||||
ViewRegionGraph.cpp
|
||||
|
||||
ADDITIONAL_HEADER_DIRS
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
//===- ViewOpGraph.cpp - View/write op graphviz graphs --------------------===//
|
||||
//
|
||||
// Copyright 2019 The MLIR Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// =============================================================================
|
||||
|
||||
#include "mlir/Transforms/ViewOpGraph.h"
|
||||
#include "mlir/IR/Block.h"
|
||||
#include "mlir/IR/Operation.h"
|
||||
#include "mlir/Pass/Pass.h"
|
||||
#include "llvm/Support/CommandLine.h"
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
static llvm::cl::opt<int> elideIfLarger(
|
||||
"print-op-graph-elide-if-larger",
|
||||
llvm::cl::desc("Upper limit to emit elements attribute rather than elide"),
|
||||
llvm::cl::init(16));
|
||||
|
||||
namespace llvm {
|
||||
|
||||
// Specialize GraphTraits to treat Block as a graph of Operations as nodes and
|
||||
// uses as edges.
|
||||
template <> struct llvm::GraphTraits<mlir::Block *> {
|
||||
using GraphType = mlir::Block *;
|
||||
using NodeRef = mlir::Operation *;
|
||||
|
||||
using ChildIteratorType = mlir::UseIterator;
|
||||
static ChildIteratorType child_begin(NodeRef n) {
|
||||
return ChildIteratorType(n);
|
||||
}
|
||||
static ChildIteratorType child_end(NodeRef n) {
|
||||
return ChildIteratorType(n, /*end=*/true);
|
||||
}
|
||||
|
||||
// Operation's destructor is private so use Operation* instead and use
|
||||
// mapped iterator.
|
||||
static mlir::Operation *AddressOf(mlir::Operation &op) { return &op; }
|
||||
using nodes_iterator =
|
||||
mapped_iterator<mlir::Block::iterator, decltype(&AddressOf)>;
|
||||
static nodes_iterator nodes_begin(mlir::Block *b) {
|
||||
return nodes_iterator(b->begin(), &AddressOf);
|
||||
}
|
||||
static nodes_iterator nodes_end(mlir::Block *b) {
|
||||
return nodes_iterator(b->end(), &AddressOf);
|
||||
}
|
||||
};
|
||||
|
||||
// Specialize DOTGraphTraits to produce more readable output.
|
||||
template <>
|
||||
struct DOTGraphTraits<mlir::Block *> : public DefaultDOTGraphTraits {
|
||||
using DefaultDOTGraphTraits::DefaultDOTGraphTraits;
|
||||
static std::string getNodeLabel(mlir::Operation *op, mlir::Block *);
|
||||
};
|
||||
|
||||
std::string DOTGraphTraits<mlir::Block *>::getNodeLabel(mlir::Operation *op,
|
||||
mlir::Block *b) {
|
||||
// Reuse the print output for the node labels.
|
||||
std::string ostr;
|
||||
raw_string_ostream os(ostr);
|
||||
os << op->getName() << "\n";
|
||||
for (auto attr : op->getAttrs()) {
|
||||
os << '\n' << attr.first << ": ";
|
||||
// Always emit splat attributes.
|
||||
if (attr.second.isa<mlir::SplatElementsAttr>()) {
|
||||
attr.second.print(os);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Elide "big" elements attributes.
|
||||
auto elements = attr.second.dyn_cast<mlir::ElementsAttr>();
|
||||
if (elements && elements.getNumElements() > elideIfLarger) {
|
||||
os << "...";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Print all other attributes.
|
||||
attr.second.print(os);
|
||||
}
|
||||
return os.str();
|
||||
}
|
||||
|
||||
} // end namespace llvm
|
||||
|
||||
namespace {
|
||||
// PrintOpPass is simple pass to write graph per function.
|
||||
// Note: this is a module pass only to avoid interleaving on the same ostream
|
||||
// due to multi-threading over functions.
|
||||
struct PrintOpPass : public mlir::ModulePass<PrintOpPass> {
|
||||
explicit PrintOpPass(llvm::raw_ostream &os = llvm::errs(),
|
||||
bool short_names = false, const llvm::Twine &title = "")
|
||||
: os(os), title(title.str()), short_names(short_names) {}
|
||||
|
||||
std::string getOpName(mlir::Operation &op) {
|
||||
auto symbolAttr = op.getAttrOfType<mlir::StringAttr>(
|
||||
mlir::SymbolTable::getSymbolAttrName());
|
||||
if (symbolAttr)
|
||||
return symbolAttr.getValue();
|
||||
++unnamedOpCtr;
|
||||
return (op.getName().getStringRef() + llvm::utostr(unnamedOpCtr)).str();
|
||||
}
|
||||
|
||||
// Print all the ops in a module.
|
||||
void processModule(mlir::ModuleOp module) {
|
||||
for (mlir::Operation &op : module) {
|
||||
// Modules may actually be nested, recurse on nesting.
|
||||
if (auto nestedModule = llvm::dyn_cast<mlir::ModuleOp>(op)) {
|
||||
processModule(nestedModule);
|
||||
continue;
|
||||
}
|
||||
auto opName = getOpName(op);
|
||||
for (mlir::Region ®ion : op.getRegions()) {
|
||||
for (auto indexed_block : llvm::enumerate(region)) {
|
||||
// Suffix block number if there are more than 1 block.
|
||||
auto blockName = region.getBlocks().size() == 1
|
||||
? ""
|
||||
: ("__" + llvm::utostr(indexed_block.index()));
|
||||
llvm::WriteGraph(os, &indexed_block.value(), short_names,
|
||||
llvm::Twine(title) + opName + blockName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void runOnModule() override { processModule(getModule()); }
|
||||
|
||||
private:
|
||||
llvm::raw_ostream &os;
|
||||
std::string title;
|
||||
int unnamedOpCtr = 0;
|
||||
bool short_names;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
void mlir::viewGraph(mlir::Block &block, const llvm::Twine &name,
|
||||
bool shortNames, const llvm::Twine &title,
|
||||
llvm::GraphProgram::Name program) {
|
||||
llvm::ViewGraph(&block, name, shortNames, title, program);
|
||||
}
|
||||
|
||||
llvm::raw_ostream &mlir::writeGraph(llvm::raw_ostream &os, mlir::Block &block,
|
||||
bool shortNames, const llvm::Twine &title) {
|
||||
return llvm::WriteGraph(os, &block, shortNames, title);
|
||||
}
|
||||
|
||||
std::unique_ptr<mlir::ModulePassBase>
|
||||
mlir::createPrintOpGraphPass(llvm::raw_ostream &os, bool shortNames,
|
||||
const llvm::Twine &title) {
|
||||
return std::make_unique<PrintOpPass>(os, shortNames, title);
|
||||
}
|
||||
|
||||
static mlir::PassRegistration<PrintOpPass> pass("print-op-graph",
|
||||
"Print op graph per region");
|
Loading…
Reference in New Issue