Allow attaching descriptions to OpInterfaces and InterfaceMethods.

This change adds support for documenting interfaces and their methods. A tablegen generator for the interface documentation is also added(gen-op-interface-doc).

Documentation is added to an OpInterface via the `description` field:
def MyOpInterface : OpInterface<"MyOpInterface"> {
  let description = [{
    My interface is very interesting.
  }];
}

Documentation is added to an InterfaceMethod via a new `description` field that comes right before the optional body:

InterfaceMethod<"void", "foo", (ins), [{
  This is the foo method.
}]>,

PiperOrigin-RevId: 270965485
This commit is contained in:
River Riddle 2019-09-24 12:45:11 -07:00 committed by A. Unique TensorFlower
parent 458ede8775
commit 635544fc12
7 changed files with 302 additions and 65 deletions

View File

@ -298,10 +298,11 @@ of the boilerplate necessary.
Providing a definition of the `OpInterface` class will auto-generate the C++
classes for the interface. An `OpInterface` includes a name, for the C++ class,
along with a list of interface methods.
a description, and a list of interface methods.
```tablegen
def MyInterface : OpInterface<"MyInterface"> {
let description = ...;
let methods = [...];
}
```
@ -313,6 +314,8 @@ static method on the derived operation.
An `InterfaceMethod` is comprised of the following components:
* Description
- A string description of what this method does and its invariants.
* ReturnType
- A string corresponding to the C++ return type of the method.
* MethodName
@ -331,27 +334,38 @@ Examples:
```tablegen
def MyInterface : OpInterface<"MyInterface"> {
let description = [{
My interface is very interesting. ...
}];
let methods = [
// A simple non-static method with no inputs.
InterfaceMethod<"unsigned", "foo">,
InterfaceMethod<"'foo' is a non-static method with no inputs.",
"unsigned", "foo"
>,
// A new non-static method accepting an input argument.
InterfaceMethod<"Value *", "bar", (ins "unsigned":$i)>,
InterfaceMethod<"/*insert doc here*/",
"Value *", "bar", (ins "unsigned":$i)
>,
// Query a static property of the derived operation.
StaticInterfaceMethod<"unsigned", "fooStatic">,
StaticInterfaceMethod<"'fooStatic' is a static method with no inputs.",
"unsigned", "fooStatic"
>,
// Provide the definition of a static interface method.
// Note: `ConcreteOp` corresponds to the derived operation typename.
StaticInterfaceMethod<"Operation *", "create",
(ins "OpBuilder &":$builder, "Location":$loc), [{
StaticInterfaceMethod<"/*insert doc here*/",
"Operation *", "create", (ins "OpBuilder &":$builder, "Location":$loc), [{
return builder.create<ConcreteOp>(loc);
}]>,
// Provide a definition of the non-static method.
// Note: `op` corresponds to the derived operation variable.
InterfaceMethod<"unsigned", "getNumInputsAndOutputs", (ins), [{
return op.getNumInputs() + op.getNumOutputs();
InterfaceMethod<"/*insert doc here*/",
"unsigned", "getNumInputsAndOutputs", (ins), [{
return op.getNumInputs() + op.getNumOutputs();
}]>,
];
}

View File

@ -34,35 +34,50 @@ include "mlir/IR/OpBase.td"
// region. A callable is either a symbol, or an SSA value, that is referenced by
// a call-like operation. This represents the destination of the call.
// A call-like operation is one who transfers control from one sub-routine to
// another. These operations may be traditional direct calls `call @foo`, or
// indirect calls to other operations `call_indirect %foo`.
/// Interface for call-like operations.
def CallOpInterface : OpInterface<"CallOpInterface"> {
let description = [{
A call-like operation is one that transfers control from one sub-routine to
another. These operations may be traditional direct calls `call @foo`, or
indirect calls to other operations `call_indirect %foo`.
}];
let methods = [
// Returns the callee of this call-like operation. A `callee` is either a
// reference to a symbol, via SymbolRefAttr, or a reference to a defined SSA
// value.
InterfaceMethod<"CallInterfaceCallable", "getCallableForCallee">,
InterfaceMethod<[{
Returns the callee of this call-like operation. A `callee` is either a
reference to a symbol, via SymbolRefAttr, or a reference to a defined
SSA value.
}],
"CallInterfaceCallable", "getCallableForCallee"
>,
];
}
// A callable operation is one who represents a potential sub-routine, and may
// be a target for a call-like operation (those providing the CallOpInterface
// above). These operations may be traditional functional operation
// `func @foo(...)`, as well as function producing operations
// `%foo = dialect.create_function(...)`. These operations may produce multiple
// callable regions, or subroutines.
/// Interface for callable operations.
def CallableOpInterface : OpInterface<"CallableOpInterface"> {
let methods = [
// Returns a region on the current operation that the given callable refers
// to. This may return null in the case of an external callable object, e.g.
// an external function.
InterfaceMethod<"Region *", "getCallableRegion",
(ins "CallInterfaceCallable":$callable)>,
let description = [{
A callable operation is one who represents a potential sub-routine, and may
be a target for a call-like operation (those providing the CallOpInterface
above). These operations may be traditional functional operation
`func @foo(...)`, as well as function producing operations
`%foo = dialect.create_function(...)`. These operations may produce multiple
callable regions, or subroutines.
}];
// Returns all of the callable regions of this operation.
InterfaceMethod<"void", "getCallableRegions",
(ins "SmallVectorImpl<Region *> &":$callables)>,
let methods = [
InterfaceMethod<[{
Returns a region on the current operation that the given callable refers
to. This may return null in the case of an external callable object,
e.g. an external function.
}],
"Region *", "getCallableRegion", (ins "CallInterfaceCallable":$callable)
>,
InterfaceMethod<[{
Returns all of the callable regions of this operation
}],
"void", "getCallableRegions",
(ins "SmallVectorImpl<Region *> &":$callables)
>,
];
}

View File

@ -73,48 +73,102 @@ def ViewTraits : NativeOpTrait<"linalg::ViewTraits">;
def LinalgLibraryInterface : OpInterface<"LinalgOp"> {
let methods = [
/// Query the number of inputs and outputs from the operation.
InterfaceMethod<"unsigned", "getNumInputs">,
InterfaceMethod<"unsigned", "getNumOutputs">,
InterfaceMethod<"unsigned", "getNumInputsAndOutputs">,
InterfaceMethod<"Operation::operand_range", "getInputs">,
InterfaceMethod<"Operation::operand_range", "getOutputs">,
InterfaceMethod<"Operation::operand_range", "getInputsAndOutputs">,
InterfaceMethod<
"Query the number of inputs from the current operation.",
"unsigned", "getNumInputs"
>,
InterfaceMethod<
"Query the number of outputs from the current operation.",
"unsigned", "getNumOutputs"
>,
InterfaceMethod<
"Query the number of inputs and outputs from the current operation.",
"unsigned", "getNumInputsAndOutputs"
>,
InterfaceMethod<
"Query the input operands from the current operation.",
"Operation::operand_range", "getInputs"
>,
InterfaceMethod<
"Query the output operands from the current operation.",
"Operation::operand_range", "getOutputs"
>,
InterfaceMethod<
"Query the input and output operands from the current operation.",
"Operation::operand_range", "getInputsAndOutputs"
>,
/// Query the number of each type of loop.
InterfaceMethod<"unsigned", "getNumParallelLoops">,
InterfaceMethod<"unsigned", "getNumReductionLoops">,
InterfaceMethod<"unsigned", "getNumWindowLoops">,
InterfaceMethod<"unsigned", "getNumLoops", (ins), [{
InterfaceMethod<
"Query the number of parallel loops within the current operation.",
"unsigned", "getNumParallelLoops"
>,
InterfaceMethod<
"Query the number of reduction loops within the current operation.",
"unsigned", "getNumReductionLoops"
>,
InterfaceMethod<
"Query the number of window loops within the current operation.",
"unsigned", "getNumWindowLoops"
>,
InterfaceMethod<
"Query the number of loops within the current operation.",
"unsigned", "getNumLoops", (ins), [{
return op.getNumParallelLoops() + op.getNumReductionLoops() +
op.getNumWindowLoops();
}]>,
/// Get a specific input/output at the given index.
InterfaceMethod<"Value *", "getInput", (ins "unsigned":$i)>,
InterfaceMethod<"Value *", "getOutput", (ins "unsigned":$i)>,
InterfaceMethod<"Query the input for the given index.",
"Value *", "getInput", (ins "unsigned":$i)
>,
InterfaceMethod<"Query the output for the given index.",
"Value *", "getOutput", (ins "unsigned":$i)
>,
/// Get the index of the given value, or None if the value is not an input.
InterfaceMethod<"llvm::Optional<unsigned>", "getIndexOfInput",
(ins "Value *":$view)>,
InterfaceMethod<"llvm::Optional<unsigned>", "getIndexOfOutput",
(ins "Value *":$view)>,
InterfaceMethod<[{
Query the index of the given input value, or `None` if the value is not
an input.
}],
"llvm::Optional<unsigned>", "getIndexOfInput", (ins "Value *":$view)
>,
InterfaceMethod<[{
Query the index of the given view value, or `None` if the value is not
an view.
}],
"llvm::Optional<unsigned>", "getIndexOfOutput", (ins "Value *":$view)
>,
/// Get the view type of the input/output at the given index.
InterfaceMethod<"ViewType", "getInputViewType", (ins "unsigned":$i)>,
InterfaceMethod<"ViewType", "getOutputViewType", (ins "unsigned":$i)>,
InterfaceMethod<"Query the view type for the given input.",
"ViewType", "getInputViewType", (ins "unsigned":$i)
>,
InterfaceMethod<"Query the view type for the given output.",
"ViewType", "getOutputViewType", (ins "unsigned":$i)
>,
/// Create an operation with the given location and operands.
StaticInterfaceMethod<"Operation *", "create",
StaticInterfaceMethod<[{
Create an operation of the current type with the given location,
operands, and attributes.
}],
"Operation *", "create",
(ins "OpBuilder &":$builder, "Location":$loc,
"ArrayRef<Value *>":$operands,
"ArrayRef<NamedAttribute>":$attributes), [{
return builder.create<ConcreteOp>(loc, ArrayRef<Type>{}, operands,
attributes);
}]>,
}]
>,
/// Clone an operation with the given location and operands. This is used to
/// abstract away the optional underlying region creation.
InterfaceMethod<"Operation *", "clone",
InterfaceMethod<[{
Clone the current operation with the given location and operands. This
is used to abstract away the optional underlying region creation.
}],
"Operation *", "clone",
(ins "OpBuilder &":$b, "Location":$loc, "ArrayRef<Value *>":$operands), [{
BlockAndValueMapping map;
unsigned numRegions = op.getOperation()->getNumRegions();
@ -124,7 +178,8 @@ def LinalgLibraryInterface : OpInterface<"LinalgOp"> {
op.getOperation()->getRegion(ridx).cloneInto(
&res->getRegion(ridx), map);
return res;
}]>
}]
>
];
}

View File

@ -1255,8 +1255,11 @@ class OpInterfaceTrait<string name> : NativeOpTrait<""> {
// This class represents a single, optionally static, interface method.
// Note: non-static interface methods have an implicit 'op' parameter
// corresponding to an instance of the derived operation.
class InterfaceMethod<string retTy, string methodName,
class InterfaceMethod<string desc, string retTy, string methodName,
dag args = (ins), code methodBody = [{}]> {
// A human-readable description of what this method does.
string description = desc;
/// The name of the interface method.
string name = methodName;
@ -1271,12 +1274,15 @@ class InterfaceMethod<string retTy, string methodName,
}
// This class represents a single static interface method.
class StaticInterfaceMethod<string retTy, string methodName,
class StaticInterfaceMethod<string desc, string retTy, string methodName,
dag args = (ins), code methodBody = [{}]>
: InterfaceMethod<retTy, methodName, args, methodBody>;
: InterfaceMethod<desc, retTy, methodName, args, methodBody>;
// OpInterface represents an interface regarding an op.
class OpInterface<string name> : OpInterfaceTrait<name> {
// A human-readable description of what this interface does.
string description = "";
// The name given to the c++ interface class.
string cppClassName = name;

View File

@ -0,0 +1,45 @@
//===- DocGenUtilities.h - MLIR doc gen utilities ---------------*- 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.
// =============================================================================
//
// This file defines common utilities for generating documents from tablgen
// structures.
//
//===----------------------------------------------------------------------===//
#ifndef MLIR_TOOLS_MLIRTBLGEN_DOCGENUTILITIES_H_
#define MLIR_TOOLS_MLIRTBLGEN_DOCGENUTILITIES_H_
namespace llvm {
class raw_ostream;
class StringRef;
} // namespace llvm
namespace mlir {
namespace tblgen {
// Emit the description by aligning the text to the left per line (e.g.
// removing the minimum indentation across the block).
//
// This expects that the description in the tablegen file is already formatted
// in a way the user wanted but has some additional indenting due to being
// nested.
void emitDescription(llvm::StringRef description, llvm::raw_ostream &os);
} // namespace tblgen
} // namespace mlir
#endif // MLIR_TOOLS_MLIRTBLGEN_DOCGENUTILITIES_H_

View File

@ -20,6 +20,7 @@
//
//===----------------------------------------------------------------------===//
#include "DocGenUtilities.h"
#include "mlir/TableGen/GenInfo.h"
#include "mlir/TableGen/Operator.h"
#include "llvm/ADT/StringExtras.h"
@ -40,7 +41,7 @@ using mlir::tblgen::Operator;
// This expects that the description in the tablegen file is already formatted
// in a way the user wanted but has some additional indenting due to being
// nested in the op definition.
static void emitDescription(StringRef description, raw_ostream &os) {
void mlir::tblgen::emitDescription(StringRef description, raw_ostream &os) {
// Determine the minimum number of spaces in a line.
size_t min_indent = -1;
StringRef remaining = description;
@ -94,7 +95,7 @@ static void emitOpDoc(const RecordKeeper &recordKeeper, raw_ostream &os) {
os << "\n" << op.getSummary() << "\n";
os << "\n### Description:\n";
if (op.hasDescription())
emitDescription(op.getDescription(), os);
mlir::tblgen::emitDescription(op.getDescription(), os);
// Emit operands & type of operand. All operands are numbered, some may be
// named too.

View File

@ -19,6 +19,7 @@
//
//===----------------------------------------------------------------------===//
#include "DocGenUtilities.h"
#include "mlir/Support/STLExtras.h"
#include "mlir/TableGen/GenInfo.h"
#include "llvm/ADT/SmallVector.h"
@ -33,6 +34,10 @@ using namespace llvm;
using namespace mlir;
namespace {
//===----------------------------------------------------------------------===//
// OpInterfaceMethod
//===----------------------------------------------------------------------===//
// This struct represents a single method argument.
struct MethodArgument {
StringRef type, name;
@ -67,6 +72,12 @@ public:
return value.empty() ? llvm::Optional<StringRef>() : value;
}
// Return the description of this method if it has one.
llvm::Optional<StringRef> getDescription() const {
auto value = def->getValueAsString("description");
return value.empty() ? llvm::Optional<StringRef>() : value;
}
// Arguments.
ArrayRef<MethodArgument> getArguments() const { return arguments; }
bool arg_empty() const { return arguments.empty(); }
@ -79,6 +90,10 @@ protected:
SmallVector<MethodArgument, 2> arguments;
};
//===----------------------------------------------------------------------===//
// OpInterface
//===----------------------------------------------------------------------===//
// Wrapper class with helper methods for accessing OpInterfaces defined in
// TableGen.
class OpInterface {
@ -95,6 +110,12 @@ public:
// Return the methods of this interface.
ArrayRef<OpInterfaceMethod> getMethods() const { return methods; }
// Return the description of this method if it has one.
llvm::Optional<StringRef> getDescription() const {
auto value = def->getValueAsString("description");
return value.empty() ? llvm::Optional<StringRef>() : value;
}
protected:
// The TableGen definition of this interface.
const llvm::Record *def;
@ -118,8 +139,11 @@ static void emitMethodNameAndArgs(const OpInterfaceMethod &method,
os << ')';
}
static void emitInterfaceDef(const Record &interfaceDef, raw_ostream &os) {
OpInterface interface(&interfaceDef);
//===----------------------------------------------------------------------===//
// GEN: Interface definitions
//===----------------------------------------------------------------------===//
static void emitInterfaceDef(OpInterface &interface, raw_ostream &os) {
StringRef interfaceName = interface.getName();
// Insert the method definitions.
@ -142,11 +166,17 @@ static bool emitInterfaceDefs(const RecordKeeper &recordKeeper,
llvm::emitSourceFileHeader("Operation Interface Definitions", os);
auto defs = recordKeeper.getAllDerivedDefinitions("OpInterface");
for (const auto *def : defs)
emitInterfaceDef(*def, os);
for (const auto *def : defs) {
OpInterface interface(def);
emitInterfaceDef(interface, os);
}
return false;
}
//===----------------------------------------------------------------------===//
// GEN: Interface declarations
//===----------------------------------------------------------------------===//
static void emitConceptDecl(OpInterface &interface, raw_ostream &os) {
os << " class Concept {\n"
<< " public:\n"
@ -195,8 +225,7 @@ static void emitModelDecl(OpInterface &interface, raw_ostream &os) {
os << " };\n";
}
static void emitInterfaceDecl(const Record &interfaceDef, raw_ostream &os) {
OpInterface interface(&interfaceDef);
static void emitInterfaceDecl(OpInterface &interface, raw_ostream &os) {
StringRef interfaceName = interface.getName();
auto interfaceTraitsName = (interfaceName + "InterfaceTraits").str();
@ -227,11 +256,75 @@ static bool emitInterfaceDecls(const RecordKeeper &recordKeeper,
llvm::emitSourceFileHeader("Operation Interface Declarations", os);
auto defs = recordKeeper.getAllDerivedDefinitions("OpInterface");
for (const auto *def : defs)
emitInterfaceDecl(*def, os);
for (const auto *def : defs) {
OpInterface interface(def);
emitInterfaceDecl(interface, os);
}
return false;
}
//===----------------------------------------------------------------------===//
// GEN: Interface documentation
//===----------------------------------------------------------------------===//
/// Emit a string corresponding to a C++ type, followed by a space if necessary.
static raw_ostream &emitCPPType(StringRef type, raw_ostream &os) {
type = type.trim();
os << type;
if (type.back() != '&' && type.back() != '*')
os << " ";
return os;
}
static void emitInterfaceDoc(const Record &interfaceDef, raw_ostream &os) {
OpInterface interface(&interfaceDef);
// Emit the interface name followed by the description.
os << "## " << interface.getName() << " (" << interfaceDef.getName() << ")";
if (auto description = interface.getDescription())
mlir::tblgen::emitDescription(*description, os);
// Emit the methods required by the interface.
os << "\n### Methods:\n";
for (const auto &method : interface.getMethods()) {
// Emit the method name.
os << "#### `" << method.getName() << "`\n\n```c++\n";
// Emit the method signature.
if (method.isStatic())
os << "static ";
emitCPPType(method.getReturnType(), os) << method.getName() << '(';
interleaveComma(method.getArguments(), os, [&](const MethodArgument &arg) {
emitCPPType(arg.type, os) << arg.name;
});
os << ");\n```\n";
// Emit the description.
if (auto description = method.getDescription())
mlir::tblgen::emitDescription(*description, os);
// If the body is not provided, this method must be provided by the
// operation.
if (!method.getBody())
os << "\nNOTE: This method *must* be implemented by the operation.\n\n";
}
}
static bool emitInterfaceDocs(const RecordKeeper &recordKeeper,
raw_ostream &os) {
os << "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n";
os << "# Operation Interface definition\n";
auto defs = recordKeeper.getAllDerivedDefinitions("OpInterface");
for (const auto *def : defs)
emitInterfaceDoc(*def, os);
return false;
}
//===----------------------------------------------------------------------===//
// GEN: Interface registration hooks
//===----------------------------------------------------------------------===//
// Registers the operation interface generator to mlir-tblgen.
static mlir::GenRegistration
genInterfaceDecls("gen-op-interface-decls",
@ -247,3 +340,11 @@ static mlir::GenRegistration
[](const RecordKeeper &records, raw_ostream &os) {
return emitInterfaceDefs(records, os);
});
// Registers the operation interface document generator to mlir-tblgen.
static mlir::GenRegistration
genInterfaceDocs("gen-op-interface-doc",
"Generate op interface documentation",
[](const RecordKeeper &records, raw_ostream &os) {
return emitInterfaceDocs(records, os);
});