2020-04-12 05:49:03 +08:00
|
|
|
# Interfaces
|
2019-08-15 11:48:35 +08:00
|
|
|
|
2020-04-30 07:27:36 +08:00
|
|
|
MLIR is a generic and extensible framework, representing different
|
|
|
|
dialects with their own operations, attributes, types, and so on.
|
|
|
|
MLIR Dialects can express operations with a wide variety of semantics
|
|
|
|
and different levels of abstraction. The downside to this is that MLIR
|
|
|
|
transformations and analyses need to account for the semantics of
|
|
|
|
every operation, or handle operations conservatively. Without care,
|
|
|
|
this can result in code with special-cases for each supported
|
|
|
|
operation type. To combat this, MLIR provides the concept of
|
|
|
|
`interfaces`.
|
2019-08-15 11:48:35 +08:00
|
|
|
|
|
|
|
## Motivation
|
|
|
|
|
|
|
|
Interfaces provide a generic way of interacting with the IR. The goal is to be
|
|
|
|
able to express transformations/analyses in terms of these interfaces without
|
|
|
|
encoding specific knowledge about the exact operation or dialect involved. This
|
2019-11-16 01:48:54 +08:00
|
|
|
makes the compiler more extensible by allowing the addition of new dialects and
|
2019-08-15 11:48:35 +08:00
|
|
|
operations in a decoupled way with respect to the implementation of
|
|
|
|
transformations/analyses.
|
|
|
|
|
|
|
|
### Dialect Interfaces
|
|
|
|
|
2020-07-01 06:42:52 +08:00
|
|
|
Dialect interfaces are generally useful for transformation passes or analyses
|
|
|
|
that want to operate generically on a set of attributes/operations/types, which
|
|
|
|
might even be defined in different dialects. These interfaces generally involve
|
|
|
|
wide coverage over the entire dialect and are only used for a handful of
|
|
|
|
transformations/analyses. In these cases, registering the interface directly on
|
|
|
|
each operation is overly complex and cumbersome. The interface is not core to
|
|
|
|
the operation, just to the specific transformation. An example of where this
|
|
|
|
type of interface would be used is inlining. Inlining generally queries
|
|
|
|
high-level information about the operations within a dialect, like legality and
|
|
|
|
cost modeling, that often is not specific to one operation.
|
2019-08-15 11:48:35 +08:00
|
|
|
|
|
|
|
A dialect interface can be defined by inheriting from the CRTP base class
|
|
|
|
`DialectInterfaceBase::Base`. This class provides the necessary utilities for
|
|
|
|
registering an interface with the dialect so that it can be looked up later.
|
2019-11-16 01:48:54 +08:00
|
|
|
Once the interface has been defined, dialects can override it using
|
|
|
|
dialect-specific information. The interfaces defined by a dialect are registered
|
|
|
|
in a similar mechanism to Attributes, Operations, Types, etc.
|
2019-08-15 11:48:35 +08:00
|
|
|
|
|
|
|
```c++
|
|
|
|
/// Define an Inlining interface to allow for dialects to opt-in.
|
|
|
|
class DialectInlinerInterface :
|
|
|
|
public DialectInterface::Base<DialectInlinerInterface> {
|
|
|
|
public:
|
|
|
|
/// Returns true if the given region 'src' can be inlined into the region
|
|
|
|
/// 'dest' that is attached to an operation registered to the current dialect.
|
|
|
|
/// 'valueMapping' contains any remapped values from within the 'src' region.
|
|
|
|
/// This can be used to examine what values will replace entry arguments into
|
2019-11-16 01:48:54 +08:00
|
|
|
/// the 'src' region, for example.
|
2019-08-15 11:48:35 +08:00
|
|
|
virtual bool isLegalToInline(Region *dest, Region *src,
|
|
|
|
BlockAndValueMapping &valueMapping) const {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-09-06 16:22:24 +08:00
|
|
|
/// Override the inliner interface to add support for inlining affine
|
|
|
|
/// operations.
|
2019-08-15 11:48:35 +08:00
|
|
|
struct AffineInlinerInterface : public DialectInlinerInterface {
|
|
|
|
/// Affine structures have specific inlining constraints.
|
|
|
|
bool isLegalToInline(Region *dest, Region *src,
|
|
|
|
BlockAndValueMapping &valueMapping) const final {
|
|
|
|
...
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Register the interface with the dialect.
|
2020-03-21 05:18:47 +08:00
|
|
|
AffineDialect::AffineDialect(MLIRContext *context) ... {
|
2019-08-15 11:48:35 +08:00
|
|
|
addInterfaces<AffineInlinerInterface>();
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2020-04-30 07:27:36 +08:00
|
|
|
Once registered, these interfaces can be queried from the dialect by
|
|
|
|
the transformation/analysis that wants to use them, without
|
|
|
|
determining the particular dialect subclass:
|
2019-08-15 11:48:35 +08:00
|
|
|
|
|
|
|
```c++
|
|
|
|
Dialect *dialect = ...;
|
|
|
|
if (auto *interface = dialect->getInterface<DialectInlinerInterface>())
|
|
|
|
... // The dialect provides this interface.
|
|
|
|
```
|
|
|
|
|
|
|
|
#### DialectInterfaceCollections
|
|
|
|
|
|
|
|
An additional utility is provided via DialectInterfaceCollection. This CRTP
|
|
|
|
class allows for collecting all of the dialects that have registered a given
|
|
|
|
interface within the context.
|
|
|
|
|
|
|
|
```c++
|
|
|
|
class InlinerInterface : public
|
|
|
|
DialectInterfaceCollection<DialectInlinerInterface> {
|
|
|
|
/// The hooks for this class mirror the hooks for the DialectInlinerInterface,
|
|
|
|
/// with default implementations that call the hook on the interface for a
|
|
|
|
/// given dialect.
|
|
|
|
virtual bool isLegalToInline(Region *dest, Region *src,
|
|
|
|
BlockAndValueMapping &valueMapping) const {
|
|
|
|
auto *handler = getInterfaceFor(dest->getContainingOp());
|
|
|
|
return handler ? handler->isLegalToInline(dest, src, valueMapping) : false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
MLIRContext *ctx = ...;
|
|
|
|
InlinerInterface interface(ctx);
|
|
|
|
if(!interface.isLegalToInline(...))
|
|
|
|
...
|
|
|
|
```
|
2019-08-20 03:43:46 +08:00
|
|
|
|
2020-07-01 06:42:52 +08:00
|
|
|
### Attribute/Operation/Type Interfaces
|
|
|
|
|
|
|
|
Attribute/Operation/Type interfaces, as the names suggest, are those registered
|
|
|
|
at the level of a specific attribute/operation/type. These interfaces provide
|
|
|
|
access to derived objects by providing a virtual interface that must be
|
|
|
|
implemented. As an example, the `Linalg` dialect may implement an interface that
|
|
|
|
provides general queries about some of the dialects library operations. These
|
|
|
|
queries may provide things like: the number of parallel loops; the number of
|
|
|
|
inputs and outputs; etc.
|
|
|
|
|
|
|
|
These interfaces are defined by overriding the CRTP base class `AttrInterface`,
|
|
|
|
`OpInterface`, or `TypeInterface` respectively. These classes take, as a
|
|
|
|
template parameter, a `Traits` class that defines a `Concept` and a `Model`
|
|
|
|
class. These classes provide an implementation of concept-based polymorphism,
|
|
|
|
where the Concept defines a set of virtual methods that are overridden by the
|
|
|
|
Model that is templated on the concrete object type. It is important to note
|
|
|
|
that these classes should be pure in that they contain no non-static data
|
|
|
|
members. Objects that wish to override this interface should add the provided
|
|
|
|
trait `*Interface<..>::Trait` to the trait list upon registration.
|
2019-08-20 03:43:46 +08:00
|
|
|
|
|
|
|
```c++
|
|
|
|
struct ExampleOpInterfaceTraits {
|
2019-10-03 00:15:53 +08:00
|
|
|
/// Define a base concept class that defines the virtual interface that needs
|
|
|
|
/// to be overridden.
|
|
|
|
struct Concept {
|
|
|
|
virtual ~Concept();
|
2020-06-25 08:23:41 +08:00
|
|
|
virtual unsigned getNumInputs(Operation *op) const = 0;
|
2019-10-03 00:15:53 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
/// Define a model class that specializes a concept on a given operation type.
|
|
|
|
template <typename OpT>
|
|
|
|
struct Model : public Concept {
|
|
|
|
/// Override the method to dispatch on the concrete operation.
|
2020-06-25 08:23:41 +08:00
|
|
|
unsigned getNumInputs(Operation *op) const final {
|
2019-10-03 00:15:53 +08:00
|
|
|
return llvm::cast<OpT>(op).getNumInputs();
|
|
|
|
}
|
|
|
|
};
|
2019-08-20 03:43:46 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
class ExampleOpInterface : public OpInterface<ExampleOpInterface,
|
|
|
|
ExampleOpInterfaceTraits> {
|
|
|
|
public:
|
2019-10-03 00:15:53 +08:00
|
|
|
/// Use base class constructor to support LLVM-style casts.
|
|
|
|
using OpInterface<ExampleOpInterface, ExampleOpInterfaceTraits>::OpInterface;
|
|
|
|
|
2019-08-20 03:43:46 +08:00
|
|
|
/// The interface dispatches to 'getImpl()', an instance of the concept.
|
2020-06-25 08:23:41 +08:00
|
|
|
unsigned getNumInputs() const {
|
2019-08-20 03:43:46 +08:00
|
|
|
return getImpl()->getNumInputs(getOperation());
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
Once the interface has been defined, it is registered to an operation by adding
|
|
|
|
the provided trait `ExampleOpInterface::Trait`. Using this interface is just
|
|
|
|
like using any other derived operation type, i.e. casting:
|
|
|
|
|
|
|
|
```c++
|
|
|
|
/// When defining the operation, the interface is registered via the nested
|
|
|
|
/// 'Trait' class provided by the 'OpInterface<>' base class.
|
|
|
|
class MyOp : public Op<MyOp, ExampleOpInterface::Trait> {
|
|
|
|
public:
|
|
|
|
/// The definition of the interface method on the derived operation.
|
|
|
|
unsigned getNumInputs() { return ...; }
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Later, we can query if a specific operation(like 'MyOp') overrides the given
|
|
|
|
/// interface.
|
|
|
|
Operation *op = ...;
|
|
|
|
if (ExampleOpInterface example = dyn_cast<ExampleOpInterface>(op))
|
2019-10-03 00:15:53 +08:00
|
|
|
llvm::errs() << "num inputs = " << example.getNumInputs() << "\n";
|
2019-08-20 03:43:46 +08:00
|
|
|
```
|
Add support for generating operation interfaces from the ODS framework.
Operation interfaces generally require a bit of boilerplate code to connect all of the pieces together. This cl introduces mechanisms in the ODS to allow for generating operation interfaces via the 'OpInterface' class.
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. There are two types of methods that can be used with an interface, `InterfaceMethod` and `StaticInterfaceMethod`. They are both comprised of the same core components, with the distinction that `StaticInterfaceMethod` models a static method on the derived operation.
An `InterfaceMethod` is comprised of the following components:
* ReturnType
- A string corresponding to the c++ return type of the method.
* MethodName
- A string corresponding to the desired name of the method.
* Arguments
- A dag of strings that correspond to a c++ type and variable name
respectively.
* MethodBody (Optional)
- An optional explicit implementation of the interface method.
def MyInterface : OpInterface<"MyInterface"> {
let methods = [
// A simple non-static method with no inputs.
InterfaceMethod<"unsigned", "foo">,
// A new non-static method accepting an input argument.
InterfaceMethod<"Value *", "bar", (ins "unsigned":$i)>,
// Query a static property of the derived operation.
StaticInterfaceMethod<"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), [{
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();
}]>,
];
PiperOrigin-RevId: 264754898
2019-08-22 11:57:23 +08:00
|
|
|
|
|
|
|
#### Utilizing the ODS Framework
|
|
|
|
|
|
|
|
Operation interfaces require a bit of boiler plate to connect all of the pieces
|
|
|
|
together. The ODS(Operation Definition Specification) framework provides
|
2020-07-01 06:42:52 +08:00
|
|
|
simplified mechanisms for [defining interfaces](OpDefinitions.md#interfaces).
|
Add support for generating operation interfaces from the ODS framework.
Operation interfaces generally require a bit of boilerplate code to connect all of the pieces together. This cl introduces mechanisms in the ODS to allow for generating operation interfaces via the 'OpInterface' class.
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. There are two types of methods that can be used with an interface, `InterfaceMethod` and `StaticInterfaceMethod`. They are both comprised of the same core components, with the distinction that `StaticInterfaceMethod` models a static method on the derived operation.
An `InterfaceMethod` is comprised of the following components:
* ReturnType
- A string corresponding to the c++ return type of the method.
* MethodName
- A string corresponding to the desired name of the method.
* Arguments
- A dag of strings that correspond to a c++ type and variable name
respectively.
* MethodBody (Optional)
- An optional explicit implementation of the interface method.
def MyInterface : OpInterface<"MyInterface"> {
let methods = [
// A simple non-static method with no inputs.
InterfaceMethod<"unsigned", "foo">,
// A new non-static method accepting an input argument.
InterfaceMethod<"Value *", "bar", (ins "unsigned":$i)>,
// Query a static property of the derived operation.
StaticInterfaceMethod<"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), [{
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();
}]>,
];
PiperOrigin-RevId: 264754898
2019-08-22 11:57:23 +08:00
|
|
|
|
|
|
|
As an example, using the ODS framework would allow for defining the example
|
|
|
|
interface above as:
|
|
|
|
|
|
|
|
```tablegen
|
|
|
|
def ExampleOpInterface : OpInterface<"ExampleOpInterface"> {
|
2019-09-25 04:51:56 +08:00
|
|
|
let description = [{
|
|
|
|
This is an example interface definition.
|
|
|
|
}];
|
|
|
|
|
Add support for generating operation interfaces from the ODS framework.
Operation interfaces generally require a bit of boilerplate code to connect all of the pieces together. This cl introduces mechanisms in the ODS to allow for generating operation interfaces via the 'OpInterface' class.
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. There are two types of methods that can be used with an interface, `InterfaceMethod` and `StaticInterfaceMethod`. They are both comprised of the same core components, with the distinction that `StaticInterfaceMethod` models a static method on the derived operation.
An `InterfaceMethod` is comprised of the following components:
* ReturnType
- A string corresponding to the c++ return type of the method.
* MethodName
- A string corresponding to the desired name of the method.
* Arguments
- A dag of strings that correspond to a c++ type and variable name
respectively.
* MethodBody (Optional)
- An optional explicit implementation of the interface method.
def MyInterface : OpInterface<"MyInterface"> {
let methods = [
// A simple non-static method with no inputs.
InterfaceMethod<"unsigned", "foo">,
// A new non-static method accepting an input argument.
InterfaceMethod<"Value *", "bar", (ins "unsigned":$i)>,
// Query a static property of the derived operation.
StaticInterfaceMethod<"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), [{
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();
}]>,
];
PiperOrigin-RevId: 264754898
2019-08-22 11:57:23 +08:00
|
|
|
let methods = [
|
2019-09-25 04:51:56 +08:00
|
|
|
InterfaceMethod<
|
|
|
|
"Get the number of inputs for the current operation.",
|
|
|
|
"unsigned", "getNumInputs"
|
|
|
|
>,
|
Add support for generating operation interfaces from the ODS framework.
Operation interfaces generally require a bit of boilerplate code to connect all of the pieces together. This cl introduces mechanisms in the ODS to allow for generating operation interfaces via the 'OpInterface' class.
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. There are two types of methods that can be used with an interface, `InterfaceMethod` and `StaticInterfaceMethod`. They are both comprised of the same core components, with the distinction that `StaticInterfaceMethod` models a static method on the derived operation.
An `InterfaceMethod` is comprised of the following components:
* ReturnType
- A string corresponding to the c++ return type of the method.
* MethodName
- A string corresponding to the desired name of the method.
* Arguments
- A dag of strings that correspond to a c++ type and variable name
respectively.
* MethodBody (Optional)
- An optional explicit implementation of the interface method.
def MyInterface : OpInterface<"MyInterface"> {
let methods = [
// A simple non-static method with no inputs.
InterfaceMethod<"unsigned", "foo">,
// A new non-static method accepting an input argument.
InterfaceMethod<"Value *", "bar", (ins "unsigned":$i)>,
// Query a static property of the derived operation.
StaticInterfaceMethod<"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), [{
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();
}]>,
];
PiperOrigin-RevId: 264754898
2019-08-22 11:57:23 +08:00
|
|
|
];
|
|
|
|
}
|
|
|
|
```
|
[MLIR] Add RegionKindInterface
Some dialects have semantics which is not well represented by common
SSA structures with dominance constraints. This patch allows
operations to declare the 'kind' of their contained regions.
Currently, two kinds are allowed: "SSACFG" and "Graph". The only
difference between them at the moment is that SSACFG regions are
required to have dominance, while Graph regions are not required to
have dominance. The intention is that this Interface would be
generated by ODS for existing operations, although this has not yet
been implemented. Presumably, if someone were interested in code
generation, we might also have a "CFG" dialect, which defines control
flow, but does not require SSA.
The new behavior is mostly identical to the previous behavior, since
registered operations without a RegionKindInterface are assumed to
contain SSACFG regions. However, the behavior has changed for
unregistered operations. Previously, these were checked for
dominance, however the new behavior allows dominance violations, in
order to allow the processing of unregistered dialects with Graph
regions. One implication of this is that regions in unregistered
operations with more than one op are no longer CSE'd (since it
requires dominance info).
I've also reorganized the LangRef documentation to remove assertions
about "sequential execution", "SSA Values", and "Dominance". Instead,
the core IR is simply "ordered" (i.e. totally ordered) and consists of
"Values". I've also clarified some things about how control flow
passes between blocks in an SSACFG region. Control Flow must enter a
region at the entry block and follow terminator operation successors
or be returned to the containing op. Graph regions do not define a
notion of control flow.
see discussion here:
https://llvm.discourse.group/t/rfc-allowing-dialects-to-relax-the-ssa-dominance-condition/833/53
Differential Revision: https://reviews.llvm.org/D80358
2020-05-16 01:33:13 +08:00
|
|
|
|
|
|
|
#### Operation Interface List
|
|
|
|
|
|
|
|
MLIR includes standard interfaces providing functionality that is
|
|
|
|
likely to be common across many different operations. Below is a list
|
|
|
|
of some key interfaces that may be used directly by any dialect. The
|
|
|
|
format of the header for each interface section goes as follows:
|
|
|
|
|
|
|
|
* `Interface class name`
|
|
|
|
- (`C++ class` -- `ODS class`(if applicable))
|
|
|
|
|
|
|
|
##### CallInterfaces
|
|
|
|
|
|
|
|
* `CallOpInterface` - Used to represent operations like 'call'
|
|
|
|
- `CallInterfaceCallable getCallableForCallee()`
|
|
|
|
* `CallableOpInterface` - Used to represent the target callee of call.
|
|
|
|
- `Region * getCallableRegion()`
|
|
|
|
- `ArrayRef<Type> getCallableResults()`
|
|
|
|
|
|
|
|
##### RegionKindInterfaces
|
|
|
|
|
|
|
|
* `RegionKindInterface` - Used to describe the abstract semantics of regions.
|
|
|
|
- `RegionKind getRegionKind(unsigned index)` - Return the kind of the region with the given index inside this operation.
|
|
|
|
- RegionKind::Graph - represents a graph region without control flow semantics
|
|
|
|
- RegionKind::SSACFG - represents an [SSA-style control flow](LangRef.md#modeling-control-flow) region with basic blocks and reachability
|
|
|
|
- `hasSSADominance(unsigned index)` - Return true if the region with the given index inside this operation requires dominance.
|