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:
Jacques Pienaar 2019-09-09 18:12:12 -07:00 committed by A. Unique TensorFlower
parent f4ae4762bf
commit 2660623a88
5 changed files with 285 additions and 0 deletions

View File

@ -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);

View File

@ -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_

View File

@ -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();
}

View File

@ -20,6 +20,7 @@ add_llvm_library(MLIRTransforms
SimplifyAffineStructures.cpp
StripDebugInfo.cpp
Vectorize.cpp
ViewOpGraph.cpp
ViewRegionGraph.cpp
ADDITIONAL_HEADER_DIRS

View File

@ -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 &region : 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");