forked from OSchip/llvm-project
534 lines
22 KiB
Markdown
534 lines
22 KiB
Markdown
# Interfaces
|
|
|
|
MLIR is a generic and extensible framework, representing different dialects with
|
|
their own attributes, operations, 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 be able
|
|
to account for the semantics of every operation, or be overly conservative.
|
|
Without care, this can result in code with special-cases for each supported
|
|
operation type. To combat this, MLIR provides a concept of `interfaces`.
|
|
|
|
## 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
|
|
makes the compiler more easily extensible by allowing the addition of new
|
|
dialects and operations in a decoupled way with respect to the implementation of
|
|
transformations/analyses.
|
|
|
|
### Dialect Interfaces
|
|
|
|
Dialect interfaces are generally useful for transformation passes or analyses
|
|
that want to operate generically on a set of attributes/operations/types, which
|
|
may be defined in different dialects. These interfaces generally involve wide
|
|
coverage over an entire dialect and are only used for a handful of analyses or
|
|
transformations. 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 cost modeling and
|
|
legality, that often is not specific to one operation.
|
|
|
|
A dialect interface can be defined by inheriting from the
|
|
[CRTP](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) base
|
|
class `DialectInterfaceBase::Base<>`. This class provides the necessary
|
|
utilities for registering an interface with a dialect so that it can be
|
|
referenced later. Once the interface has been defined, dialects can override it
|
|
using dialect-specific information. The interfaces defined by a dialect are
|
|
registered via `addInterfaces<>`, a similar mechanism to Attributes, Operations,
|
|
Types, etc
|
|
|
|
```c++
|
|
/// Define a base inlining interface class to allow for dialects to opt-in to
|
|
/// the inliner.
|
|
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
|
|
/// the 'src' region, for example.
|
|
virtual bool isLegalToInline(Region *dest, Region *src,
|
|
BlockAndValueMapping &valueMapping) const {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
/// Override the inliner interface to add support for the AffineDialect to
|
|
/// enable inlining affine operations.
|
|
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.
|
|
AffineDialect::AffineDialect(MLIRContext *context) ... {
|
|
addInterfaces<AffineInlinerInterface>();
|
|
}
|
|
```
|
|
|
|
Once registered, these interfaces can be queried from the dialect by an analysis
|
|
or transformation without the need to determine the specific dialect subclass:
|
|
|
|
```c++
|
|
Dialect *dialect = ...;
|
|
if (DialectInlinerInterface *interface
|
|
= dialect->getRegisteredInterface<DialectInlinerInterface>()) {
|
|
// The dialect has provided an implementation of this interface.
|
|
...
|
|
}
|
|
```
|
|
|
|
#### DialectInterfaceCollection
|
|
|
|
An additional utility is provided via `DialectInterfaceCollection`. This class
|
|
allows for collecting all of the dialects that have registered a given interface
|
|
within an instance of the `MLIRContext`. This can be useful to hide and optimize
|
|
the lookup of a registered dialect interface.
|
|
|
|
```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(...))
|
|
...
|
|
```
|
|
|
|
### 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, many analyses and transformations want to reason
|
|
about the side effects of an operation to improve performance and correctness.
|
|
The side effects of an operation are generally tied to the semantics of a
|
|
specific operation, for example an `affine.load` operation has a `write` effect
|
|
(as the name may suggest).
|
|
|
|
These interfaces are defined by overriding the
|
|
[CRTP](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) class
|
|
for the specific IR entity; `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
|
|
entity type. It is important to note that these classes should be pure, and
|
|
should not contain non-static data members or other mutable data. To attach an
|
|
interface to an object, the base interface classes provide a
|
|
[`Trait`](Traits.md) class that can be appended to the trait list of that
|
|
object.
|
|
|
|
```c++
|
|
struct ExampleOpInterfaceTraits {
|
|
/// Define a base concept class that specifies the virtual interface to be
|
|
/// implemented.
|
|
struct Concept {
|
|
virtual ~Concept();
|
|
|
|
/// This is an example of a non-static hook to an operation.
|
|
virtual unsigned exampleInterfaceHook(Operation *op) const = 0;
|
|
|
|
/// This is an example of a static hook to an operation. A static hook does
|
|
/// not require a concrete instance of the operation. The implementation is
|
|
/// a virtual hook, the same as the non-static case, because the
|
|
/// implementation of the hook itself still requires indirection.
|
|
virtual unsigned exampleStaticInterfaceHook() const = 0;
|
|
};
|
|
|
|
/// Define a model class that specializes a concept on a given operation type.
|
|
template <typename ConcreteOp>
|
|
struct Model : public Concept {
|
|
/// Override the method to dispatch on the concrete operation.
|
|
unsigned exampleInterfaceHook(Operation *op) const final {
|
|
return llvm::cast<ConcreteOp>(op).exampleInterfaceHook();
|
|
}
|
|
|
|
/// Override the static method to dispatch to the concrete operation type.
|
|
unsigned exampleStaticInterfaceHook() const final {
|
|
return ConcreteOp::exampleStaticInterfaceHook();
|
|
}
|
|
};
|
|
};
|
|
|
|
/// Define the main interface class that analyses and transformations will
|
|
/// interface with.
|
|
class ExampleOpInterface : public OpInterface<ExampleOpInterface,
|
|
ExampleOpInterfaceTraits> {
|
|
public:
|
|
/// Inherit the base class constructor to support LLVM-style casting.
|
|
using OpInterface<ExampleOpInterface, ExampleOpInterfaceTraits>::OpInterface;
|
|
|
|
/// The interface dispatches to 'getImpl()', a method provided by the base
|
|
/// `OpInterface` class that returns an instance of the concept.
|
|
unsigned exampleInterfaceHook() const {
|
|
return getImpl()->exampleInterfaceHook(getOperation());
|
|
}
|
|
unsigned exampleStaticInterfaceHook() const {
|
|
return getImpl()->exampleStaticInterfaceHook(getOperation()->getName());
|
|
}
|
|
};
|
|
|
|
```
|
|
|
|
Once the interface has been defined, it is registered to an operation by adding
|
|
the provided trait `ExampleOpInterface::Trait` as described earlier. 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 exampleInterfaceHook() { return ...; }
|
|
static unsigned exampleStaticInterfaceHook() { return ...; }
|
|
};
|
|
|
|
/// Later, we can query if a specific operation(like 'MyOp') overrides the given
|
|
/// interface.
|
|
Operation *op = ...;
|
|
if (ExampleOpInterface example = dyn_cast<ExampleOpInterface>(op))
|
|
llvm::errs() << "hook returned = " << example.exampleInterfaceHook() << "\n";
|
|
```
|
|
|
|
#### Utilizing the ODS Framework
|
|
|
|
Note: Before reading this section, the reader should have some familiarity with
|
|
the concepts described in the
|
|
[`Operation Definition Specification`](OpDefinitions.md) documentation.
|
|
|
|
As detailed above, [Interfaces](attribute-operation-type-interfaces) allow for
|
|
attributes, operations, and types to expose method calls without requiring that
|
|
the caller know the specific derived type. The downside to this infrastructure,
|
|
is that it requires a bit of boiler plate to connect all of the pieces together.
|
|
MLIR provides a mechanism with which to defines interfaces declaratively in ODS,
|
|
and have the C++ definitions auto-generated.
|
|
|
|
As an example, using the ODS framework would allow for defining the example
|
|
interface above as:
|
|
|
|
```tablegen
|
|
def ExampleOpInterface : OpInterface<"ExampleOpInterface"> {
|
|
let description = [{
|
|
This is an example interface definition.
|
|
}];
|
|
|
|
let methods = [
|
|
InterfaceMethod<
|
|
"This is an example of a non-static hook to an operation.",
|
|
"unsigned", "exampleInterfaceHook"
|
|
>,
|
|
StaticInterfaceMethod<
|
|
"This is an example of a static hook to an operation.",
|
|
"unsigned", "exampleStaticInterfaceHook"
|
|
>,
|
|
];
|
|
}
|
|
```
|
|
|
|
Providing a definition of the `AttrInterface`, `OpInterface`, or `TypeInterface`
|
|
class will auto-generate the C++ classes for the interface. Interfaces are
|
|
comprised of the following components:
|
|
|
|
* C++ Class Name (Provided via template parameter)
|
|
- The name of the C++ interface class.
|
|
* Description (`description`)
|
|
- A string description of the interface, its invariants, example usages,
|
|
etc.
|
|
* C++ Namespace (`cppNamespace`)
|
|
- The C++ namespace that the interface class should be generated in.
|
|
* Methods (`methods`)
|
|
- The list of interface hook methods that are defined by the IR object.
|
|
- The structure of these methods is defined below.
|
|
* Extra Class Declarations (Optional: `extraClassDeclaration`)
|
|
- Additional C++ code that is generated in the declaration of the
|
|
interface class. This allows for defining methods and more on the user
|
|
facing interface class, that do not need to hook into the IR entity.
|
|
|
|
`OpInterface` classes may additionally contain the following:
|
|
|
|
* Verifier (`verify`)
|
|
- A C++ code block containing additional verification applied to the
|
|
operation that the interface is attached to.
|
|
- The structure of this code block corresponds 1-1 with the structure of a
|
|
[`Trait::verifyTrait`](Traits.md) method.
|
|
|
|
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 IR object.
|
|
|
|
Interface methods are comprised of the following components:
|
|
|
|
* Description
|
|
- A string description of this method, its invariants, example usages,
|
|
etc.
|
|
* ReturnType
|
|
- A string corresponding to the C++ return type of the method.
|
|
* MethodName
|
|
- A string corresponding to the C++ name of the method.
|
|
* Arguments (Optional)
|
|
- A dag of strings that correspond to a C++ type and variable name
|
|
respectively.
|
|
* MethodBody (Optional)
|
|
- An optional explicit implementation of the interface method.
|
|
- This implementation is placed within the method defined on the `Model`
|
|
traits class, and is not defined by the `Trait` class that is attached
|
|
to the IR entity. More concretely, this body is only visible by the
|
|
interface class and does not affect the derived IR entity.
|
|
- `ConcreteAttr`/`ConcreteOp`/`ConcreteType` is an implicitly defined
|
|
`typename` that can be used to refer to the type of the derived IR
|
|
entity currently being operated on.
|
|
- In non-static methods, `$_op` and `$_self` may be used to refer to an
|
|
instance of the derived IR entity.
|
|
* DefaultImplementation (Optional)
|
|
- An optional explicit default implementation of the interface method.
|
|
- This implementation is placed within the `Trait` class that is attached
|
|
to the IR entity, and does not directly affect any of the interface
|
|
classes. As such, this method has the same characteristics as any other
|
|
[`Trait`](Traits.md) method.
|
|
- `ConcreteAttr`/`ConcreteOp`/`ConcreteType` is an implicitly defined
|
|
`typename` that can be used to refer to the type of the derived IR
|
|
entity currently being operated on.
|
|
|
|
ODS also allows for generating declarations for the `InterfaceMethod`s of an
|
|
operation if the operation specifies the interface with
|
|
`DeclareOpInterfaceMethods` (see an example below).
|
|
|
|
Examples:
|
|
|
|
~~~tablegen
|
|
def MyInterface : OpInterface<"MyInterface"> {
|
|
let description = [{
|
|
This is the description of the interface. It provides concrete information
|
|
on the semantics of the interface, and how it may be used by the compiler.
|
|
}];
|
|
|
|
let methods = [
|
|
InterfaceMethod<[{
|
|
This method represents a simple non-static interface method with no
|
|
inputs, and a void return type. This method is required to be implemented
|
|
by all operations implementing this interface. This method roughly
|
|
correlates to the following on an operation implementing this interface:
|
|
|
|
```c++
|
|
class ConcreteOp ... {
|
|
public:
|
|
void nonStaticMethod();
|
|
};
|
|
```
|
|
}], "void", "nonStaticMethod"
|
|
>,
|
|
|
|
InterfaceMethod<[{
|
|
This method represents a non-static interface method with a non-void
|
|
return value, as well as an `unsigned` input named `i`. This method is
|
|
required to be implemented by all operations implementing this interface.
|
|
This method roughly correlates to the following on an operation
|
|
implementing this interface:
|
|
|
|
```c++
|
|
class ConcreteOp ... {
|
|
public:
|
|
Value nonStaticMethod(unsigned i);
|
|
};
|
|
```
|
|
}], "Value", "nonStaticMethodWithParams", (ins "unsigned":$i)
|
|
>,
|
|
|
|
StaticInterfaceMethod<[{
|
|
This method represents a static interface method with no inputs, and a
|
|
void return type. This method is required to be implemented by all
|
|
operations implementing this interface. This method roughly correlates
|
|
to the following on an operation implementing this interface:
|
|
|
|
```c++
|
|
class ConcreteOp ... {
|
|
public:
|
|
static void staticMethod();
|
|
};
|
|
```
|
|
}], "void", "staticMethod"
|
|
>,
|
|
|
|
StaticInterfaceMethod<[{
|
|
This method corresponds to a static interface method that has an explicit
|
|
implementation of the method body. Given that the method body has been
|
|
explicitly implemented, this method should not be defined by the operation
|
|
implementing this method. This method merely takes advantage of properties
|
|
already available on the operation, in this case its `build` methods. This
|
|
method roughly correlates to the following on the interface `Model` class:
|
|
|
|
```c++
|
|
struct InterfaceTraits {
|
|
/// ... The `Concept` class is elided here ...
|
|
|
|
template <typename ConcreteOp>
|
|
struct Model : public Concept {
|
|
Operation *create(OpBuilder &builder, Location loc) const override {
|
|
return builder.create<ConcreteOp>(loc);
|
|
}
|
|
}
|
|
};
|
|
```
|
|
|
|
Note above how no modification is required for operations implementing an
|
|
interface with this method.
|
|
}],
|
|
"Operation *", "create", (ins "OpBuilder &":$builder, "Location":$loc),
|
|
/*methodBody=*/[{
|
|
return builder.create<ConcreteOp>(loc);
|
|
}]>,
|
|
|
|
InterfaceMethod<[{
|
|
This method represents a non-static method that has an explicit
|
|
implementation of the method body. Given that the method body has been
|
|
explicitly implemented, this method should not be defined by the operation
|
|
implementing this method. This method merely takes advantage of properties
|
|
already available on the operation, in this case its `build` methods. This
|
|
method roughly correlates to the following on the interface `Model` class:
|
|
|
|
```c++
|
|
struct InterfaceTraits {
|
|
/// ... The `Concept` class is elided here ...
|
|
|
|
template <typename ConcreteOp>
|
|
struct Model : public Concept {
|
|
Operation *create(Operation *opaqueOp, OpBuilder &builder,
|
|
Location loc) const override {
|
|
ConcreteOp op = cast<ConcreteOp>(opaqueOp);
|
|
return op.getNumInputs() + op.getNumOutputs();
|
|
}
|
|
}
|
|
};
|
|
```
|
|
|
|
Note above how no modification is required for operations implementing an
|
|
interface with this method.
|
|
}],
|
|
"unsigned", "getNumInputsAndOutputs", (ins), /*methodBody=*/[{
|
|
return $_op.getNumInputs() + $_op.getNumOutputs();
|
|
}]>,
|
|
|
|
InterfaceMethod<[{
|
|
This method represents a non-static method that has a default
|
|
implementation of the method body. This means that the implementation
|
|
defined here will be placed in the trait class that is attached to every
|
|
operation that implements this interface. This has no effect on the
|
|
generated `Concept` and `Model` class. This method roughly correlates to
|
|
the following on the interface `Trait` class:
|
|
|
|
```c++
|
|
template <typename ConcreteOp>
|
|
class MyTrait : public OpTrait::TraitBase<ConcreteType, MyTrait> {
|
|
public:
|
|
bool isSafeToTransform() {
|
|
ConcreteOp op = cast<ConcreteOp>(this->getOperation());
|
|
return op.getNumInputs() + op.getNumOutputs();
|
|
}
|
|
};
|
|
```
|
|
|
|
As detailed in [Traits](Traits.md), given that each operation implementing
|
|
this interface will also add the interface trait, the methods on this
|
|
interface are inherited by the derived operation. This allows for
|
|
injecting a default implementation of this method into each operation that
|
|
implements this interface, without changing the interface class itself. If
|
|
an operation wants to override this default implementation, it merely
|
|
needs to implement the method and the derived implementation will be
|
|
picked up transparently by the interface class.
|
|
|
|
```c++
|
|
class ConcreteOp ... {
|
|
public:
|
|
bool isSafeToTransform() {
|
|
// Here we can override the default implementation of the hook
|
|
// provided by the trait.
|
|
}
|
|
};
|
|
```
|
|
}],
|
|
"bool", "isSafeToTransform", (ins), /*methodBody=*/[{}],
|
|
/*defaultImplementation=*/[{
|
|
}]>,
|
|
];
|
|
}
|
|
|
|
// Operation interfaces can optionally be wrapped inside
|
|
// DeclareOpInterfaceMethods. This would result in autogenerating declarations
|
|
// for members `foo`, `bar` and `fooStatic`. Methods with bodies are not
|
|
// declared inside the op declaration but instead handled by the op interface
|
|
// trait directly.
|
|
def OpWithInferTypeInterfaceOp : Op<...
|
|
[DeclareOpInterfaceMethods<MyInterface>]> { ... }
|
|
|
|
// Methods that have a default implementation do not have declarations
|
|
// generated. If an operation wishes to override the default behavior, it can
|
|
// explicitly specify the method that it wishes to override. This will force
|
|
// the generation of a declaration for those methods.
|
|
def OpWithOverrideInferTypeInterfaceOp : Op<...
|
|
[DeclareOpInterfaceMethods<MyInterface, ["getNumWithDefault"]>]> { ... }
|
|
~~~
|
|
|
|
Note: Existing operation interfaces defined in C++ can be accessed in the ODS
|
|
framework via the `OpInterfaceTrait` class.
|
|
|
|
#### 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.
|
|
|
|
##### SymbolInterfaces
|
|
|
|
* `SymbolOpInterface` - Used to represent
|
|
[`Symbol`](SymbolsAndSymbolTables.md#symbol) operations which reside
|
|
immediately within a region that defines a
|
|
[`SymbolTable`](SymbolsAndSymbolTables.md#symbol-table).
|
|
|
|
* `SymbolUserOpInterface` - Used to represent operations that reference
|
|
[`Symbol`](SymbolsAndSymbolTables.md#symbol) operations. This provides the
|
|
ability to perform safe and efficient verification of symbol uses, as well
|
|
as additional functionality.
|