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-04-30 07:27:36 +08:00
|
|
|
Dialect interfaces are generally useful for transformation passes or
|
|
|
|
analyses that want to operate generically on a set of operations,
|
|
|
|
which might even be defined in different dialects. These
|
2019-11-16 01:48:54 +08:00
|
|
|
interfaces generally involve wide coverage over the entire dialect and are only
|
2019-09-06 16:22:24 +08:00
|
|
|
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
|
2019-11-16 01:48:54 +08:00
|
|
|
generally queries high-level information about the operations within a dialect,
|
2019-09-06 16:22:24 +08:00
|
|
|
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
|
|
|
|
|
|
|
### Operation Interfaces
|
|
|
|
|
|
|
|
Operation interfaces, as the name suggests, are those registered at the
|
2020-04-30 07:27:36 +08:00
|
|
|
Operation level. These interfaces provide access to derived operations
|
2019-11-16 01:48:54 +08:00
|
|
|
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.
|
2019-08-20 03:43:46 +08:00
|
|
|
|
|
|
|
Operation interfaces are defined by overriding the CRTP base class
|
2019-11-16 01:48:54 +08:00
|
|
|
`OpInterface`. This class takes, as a template parameter, a `Traits` class that
|
2019-08-20 03:43:46 +08:00
|
|
|
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
|
|
|
|
operation type. It is important to note that these classes should be pure in
|
|
|
|
that they contain no non-static data members. Operations that wish to override
|
|
|
|
this interface should add the provided trait `OpInterface<..>::Trait` upon
|
|
|
|
registration.
|
|
|
|
|
|
|
|
```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();
|
|
|
|
virtual unsigned getNumInputs(Operation *op) = 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
/// 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.
|
|
|
|
unsigned getNumInputs(Operation *op) final {
|
|
|
|
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.
|
|
|
|
unsigned getNumInputs() {
|
|
|
|
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
|
|
|
|
simplified mechanisms for
|
|
|
|
[defining interfaces](OpDefinitions.md#operation-interfaces).
|
|
|
|
|
|
|
|
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
|
|
|
];
|
|
|
|
}
|
|
|
|
```
|