| # 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`. | |
| [TOC] | |
| ## 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, | |
| IRMapping &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, | |
| IRMapping &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 = dyn_cast<DialectInlinerInterface>(dialect)) { | |
| // The dialect has provided an implementation of this interface. | |
| ... | |
| } | |
| ``` | |
| #### Utilizing the ODS framework | |
| Note: Before reading this section, the reader should have some familiarity with | |
| the concepts described in the | |
| [`Operation Definition Specification`](DefiningDialects/Operations.md) documentation. | |
| MLIR also supports defining dialect interfaces directly in **TableGen**. | |
| This reduces boilerplate and allows authors to specify high-level interface | |
| structure declaratively. | |
| For example, the above interface can be defined using ODS as follows: | |
| ```tablegen | |
| def DialectInlinerInterface : DialectInterface<"DialectInlinerInterface"> { | |
| let description = [{ | |
| Define a base inlining interface class to allow for dialects to opt-in to | |
| the inliner. | |
| }]; | |
| let methods = [ | |
| InterfaceMethod<[{ | |
| 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. | |
| }], | |
| "bool", "isLegalToInline", | |
| (ins "Region *":$dest, "Region *":$src, "IRMapping &":$valueMapping), | |
| [{ | |
| return false; | |
| }] | |
| > | |
| ]; | |
| } | |
| ``` | |
| `DialectInterfaces` class make use 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 [here](#dialect-interface-methods). | |
| The header file can be generated via the following command: | |
| ```bash | |
| mlir-tblgen --gen-dialect-interface-decls DialectInterface.td | |
| ``` | |
| To generate dialect interface declarations using the ODS framework in CMake, you would write: | |
| ```cmake | |
| set(LLVM_TARGET_DEFINITIONS DialectInlinerInterface.td) | |
| mlir_tablegen(DialectInlinerInterface.h.inc -gen-dialect-interface-decls) | |
| ``` | |
| An example of this can be found in the DialectInlinerInterface implementation | |
| and the related `CMakeLists.txt` under `mlir/include/mlir/Transforms`. | |
| ##### Dialect Interface Methods | |
| There are three types of methods that can be used with a dialect interface, | |
| `InterfaceMethod`, `InterfaceMethodDeclaration` and `PureVirtualInterfaceMethod`. | |
| They are all comprised of the same core components, with the distinction that | |
| `InterfaceMethod` also supports a default method body. | |
| 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, only in `InterfaceMethod`): an optional explicit implementation | |
| of the interface method. | |
| `InterfaceMethodDeclaration` will only declare the class method. On the other hand, | |
| `PureVirtualInterfaceMethod` marks the method as pure virtual, but also makes the | |
| constructor of the dialect calss protected. | |
| #### DialectInterfaceCollection | |
| An additional utility is provided via `DialectInterfaceCollection`. This class | |
| allows 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, | |
| IRMapping &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 `read` 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) 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"; | |
| ``` | |
| #### External Models for Attribute, Operation and Type Interfaces | |
| It may be desirable to provide an interface implementation for an IR object | |
| without modifying the definition of said object. Notably, this allows to | |
| implement interfaces for attributes, operations and types outside of the dialect | |
| that defines them, for example, to provide interfaces for built-in types. | |
| This is achieved by extending the concept-based polymorphism model with two more | |
| classes derived from `Concept` as follows. | |
| ```c++ | |
| struct ExampleTypeInterfaceTraits { | |
| struct Concept { | |
| virtual unsigned exampleInterfaceHook(Type type) const = 0; | |
| virtual unsigned exampleStaticInterfaceHook() const = 0; | |
| }; | |
| template <typename ConcreteType> | |
| struct Model : public Concept { /*...*/ }; | |
| /// Unlike `Model`, `FallbackModel` passes the type object through to the | |
| /// hook, making it accessible in the method body even if the method is not | |
| /// defined in the class itself and thus has no `this` access. ODS | |
| /// automatically generates this class for all interfaces. | |
| template <typename ConcreteType> | |
| struct FallbackModel : public Concept { | |
| unsigned exampleInterfaceHook(Type type) const override { | |
| getImpl()->exampleInterfaceHook(type); | |
| } | |
| unsigned exampleStaticInterfaceHook() const override { | |
| ConcreteType::exampleStaticInterfaceHook(); | |
| } | |
| }; | |
| /// `ExternalModel` provides a place for default implementations of interface | |
| /// methods by explicitly separating the model class, which implements the | |
| /// interface, from the type class, for which the interface is being | |
| /// implemented. Default implementations can be then defined generically | |
| /// making use of `cast<ConcreteType>`. If `ConcreteType` does not provide | |
| /// the APIs required by the default implementation, custom implementations | |
| /// may use `FallbackModel` directly to override the default implementation. | |
| /// Being located in a class template, it never gets instantiated and does not | |
| /// lead to compilation errors. ODS automatically generates this class and | |
| /// places default method implementations in it. | |
| template <typename ConcreteModel, typename ConcreteType> | |
| struct ExternalModel : public FallbackModel<ConcreteModel> { | |
| unsigned exampleInterfaceHook(Type type) const override { | |
| // Default implementation can be provided here. | |
| return cast<ConcreteType>(type).callSomeTypeSpecificMethod(); | |
| } | |
| }; | |
| }; | |
| ``` | |
| External models can be provided for attribute, operation and type interfaces by | |
| deriving either `FallbackModel` or `ExternalModel` and by registering the model | |
| class with the relevant class in a given context. Other contexts will not see | |
| the interface unless registered. | |
| ```c++ | |
| /// External interface implementation for a concrete class. This does not | |
| /// require modifying the definition of the type class itself. | |
| struct ExternalModelExample | |
| : public ExampleTypeInterface::ExternalModel<ExternalModelExample, | |
| IntegerType> { | |
| static unsigned exampleStaticInterfaceHook() { | |
| // Implementation is provided here. | |
| return IntegerType::someStaticMethod(); | |
| } | |
| // No need to define `exampleInterfaceHook` that has a default implementation | |
| // in `ExternalModel`. But it can be overridden if desired. | |
| } | |
| int main() { | |
| MLIRContext context; | |
| /* ... */; | |
| // Attach the interface model to the type in the given context before | |
| // using it. The dialect containing the type is expected to have been loaded | |
| // at this point. | |
| IntegerType::attachInterface<ExternalModelExample>(context); | |
| } | |
| ``` | |
| Note: It is strongly encouraged to only use this mechanism if you "own" the | |
| interface being externally applied. This prevents a situation where neither the | |
| owner of the dialect containing the object nor the owner of the interface are | |
| aware of an interface implementation, which can lead to duplicate or | |
| diverging implementations. | |
| Forgetting to register an external model can lead to bugs which are hard to | |
| track down. The `declarePromisedInterface` function can be used to declare that | |
| an external model implementation for an operation must eventually be provided. | |
| ``` | |
| void MyDialect::initialize() { | |
| declarePromisedInterface<SomeInterface, SomeOp>(); | |
| ... | |
| } | |
| ``` | |
| Now attempting to use the interface, e.g in a cast, without a prior registration | |
| of the external model will lead to a runtime error that will look similar to | |
| this: | |
| ``` | |
| LLVM ERROR: checking for an interface (`SomeInterface`) that was promised by dialect 'mydialect' but never implemented. This is generally an indication that the dialect extension implementing the interface was never registered. | |
| ``` | |
| If you encounter this error for a dialect and an interface provided by MLIR, you | |
| may look for a method that will be named like | |
| `register<Dialect><Interface>ExternalModels(DialectRegistry ®istry);` ; try | |
| to find it with `git grep 'register.*SomeInterface.*Model' mlir`. | |
| #### Dialect Fallback for OpInterface | |
| Some dialects have an open ecosystem and don't register all of the possible | |
| operations. In such cases it is still possible to provide support for | |
| implementing an `OpInterface` for these operation. When an operation isn't | |
| registered or does not provide an implementation for an interface, the query | |
| will fallback to the dialect itself. | |
| A second model is used for such cases and automatically generated when using ODS | |
| (see below) with the name `FallbackModel`. This model can be implemented for a | |
| particular dialect: | |
| ```c++ | |
| // This is the implementation of a dialect fallback for `ExampleOpInterface`. | |
| struct FallbackExampleOpInterface | |
| : public ExampleOpInterface::FallbackModel< | |
| FallbackExampleOpInterface> { | |
| static bool classof(Operation *op) { return true; } | |
| unsigned exampleInterfaceHook(Operation *op) const; | |
| unsigned exampleStaticInterfaceHook() const; | |
| }; | |
| ``` | |
| A dialect can then instantiate this implementation and returns it on specific | |
| operations by overriding the `getRegisteredInterfaceForOp` method : | |
| ```c++ | |
| void *TestDialect::getRegisteredInterfaceForOp(TypeID typeID, | |
| StringAttr opName) { | |
| if (typeID == TypeID::get<ExampleOpInterface>()) { | |
| if (isSupported(opName)) | |
| return fallbackExampleOpInterface; | |
| return nullptr; | |
| } | |
| return nullptr; | |
| } | |
| ``` | |
| #### Utilizing the ODS Framework | |
| As detailed above, [Interfaces](#attributeoperationtype-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. | |
| * Interface Base Classes | |
| - A set of interfaces that the interface class should derived from. See | |
| [Interface Inheritance](#interface-inheritance) below for more details. | |
| * 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. | |
| These declarations are _not_ implicitly visible in default | |
| implementations of interface methods, but static declarations may be | |
| accessed with full name qualification. | |
| * Extra Shared Class Declarations (Optional: `extraSharedClassDeclaration`) | |
| - Additional C++ code that is injected into the declarations of both the | |
| interface and the trait class. This allows for defining methods and more | |
| that are exposed on both the interface and the trait class, e.g. to inject | |
| utilities on both the interface and the derived entity implementing the | |
| interface (e.g. attribute, operation, etc.). | |
| - In non-static methods, `$_attr`/`$_op`/`$_type` | |
| (depending on the type of interface) may be used to refer to an | |
| instance of the IR entity. In the interface declaration, the type of | |
| the instance is the interface class. In the trait declaration, the | |
| type of the instance is the concrete entity class | |
| (e.g. `IntegerAttr`, `FuncOp`, etc.). | |
| * Extra Trait Class Declarations (Optional: `extraTraitClassDeclaration`) | |
| - Additional C++ code that is injected into the interface trait | |
| declaration. | |
| - Allows the same replacements as extra shared class declarations. | |
| `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) method. | |
| ##### Interface Methods | |
| `InterfaceMethod` and `StaticInterfaceMethod` both define methods on an | |
| attr/op/type interface. `InterfaceMethod` models an instance method, while | |
| `StaticInterfaceMethod` models a static method in the derived IR object class. | |
| For an operation interface method whose body only needs the raw `Operation *`, | |
| use `$_raw_op` in the `InterfaceMethod` body. The model callback is then | |
| generated once per interface rather than once per concrete operation type. | |
| Prefer this form when possible: it avoids generating a copy of the callback | |
| for every operation that implements the interface, reducing binary size. | |
| 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. | |
| - Normally, 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. | |
| - For a non-static operation interface method, a body that uses | |
| `$_raw_op` without `ConcreteOp`, `$_op`, or `$_self` is instead placed in | |
| a shared model callback. `$_raw_op` refers to the `Operation *` in both | |
| shared and per-operation model callbacks. Fallback and external models | |
| still use their own implementations. | |
| - `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) 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. | |
| - This may refer to static fields of the interface class using the | |
| qualified name, e.g., `TestOpInterface::staticMethod()`. | |
| 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). | |
| For example, an operation interface can share a callback across its concrete | |
| operation models while keeping a default implementation for the operation trait: | |
| ```tablegen | |
| InterfaceMethod< | |
| "Return the number of operands", "unsigned", "getNumOperands", (ins), | |
| /*methodBody=*/[{ return $_raw_op->getNumOperands(); }], | |
| /*defaultImplementation=*/[{ return this->getOperation()->getNumOperands(); }] | |
| > | |
| ``` | |
| 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 ConcreteOp::create(builder, 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 ConcreteOp::create(builder, 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 { | |
| unsigned getNumInputsAndOutputs(Operation *opaqueOp) 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.getProperties().hasFlag; | |
| } | |
| }; | |
| ``` | |
| As detailed in [Traits](Traits), 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=*/[{ | |
| return $_op.getProperties().hasFlag; | |
| }]>, | |
| ]; | |
| } | |
| // 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"]>]> { ... } | |
| ``` | |
| ##### Interface Inheritance | |
| Interfaces also support a limited form of inheritance, which allows for | |
| building upon pre-existing interfaces in a way similar to that of classes in | |
| programming languages like C++. This more easily allows for building modular | |
| interfaces, without suffering from the pain of lots of explicit casting. To | |
| enable inheritance, an interface simply needs to provide the desired set of | |
| base classes in its definition. For example: | |
| ```tablegen | |
| def MyBaseInterface : OpInterface<"MyBaseInterface"> { | |
| ... | |
| } | |
| def MyInterface : OpInterface<"MyInterface", [MyBaseInterface]> { | |
| ... | |
| } | |
| ``` | |
| This will result in `MyInterface` inheriting various components from | |
| `MyBaseInterface`, namely its interface methods and extra class declarations. | |
| Given that these inherited components are comprised of opaque C++ blobs, we | |
| cannot properly sandbox the names. As such, it's important to ensure that inherited | |
| components do not create name overlaps, as these will result in errors during | |
| interface generation. | |
| `MyInterface` will also implicitly inherit any base classes defined on | |
| `MyBaseInterface` as well. It's important to note, however, that there is only | |
| ever one instance of each interface for a given attribute, operation, or type. | |
| Inherited interface methods simplify forward to base interface implementation. | |
| This produces a simpler system overall, and also removes any potential problems | |
| surrounding "diamond inheritance". The interfaces on an attribute/op/type can be | |
| thought of as comprising a set, with each interface (including base interfaces) | |
| uniqued within this set and referenced elsewhere as necessary. | |
| When adding an interface with inheritance to an attribute, operation, or type, | |
| all of the base interfaces are also implicitly added as well. The user may still | |
| manually specify the base interfaces if they desire, such as for use with the | |
| `Declare<Attr|Op|Type>InterfaceMethods` helper classes. | |
| If our interface were to be specified as: | |
| ```tablegen | |
| def MyBaseInterface : OpInterface<"MyBaseInterface"> { | |
| ... | |
| } | |
| def MyOtherBaseInterface : OpInterface<MyOtherBaseInterface, [MyBaseInterface]> { | |
| ... | |
| } | |
| def MyInterface : OpInterface<"MyInterface", [MyBaseInterface, MyOtherBaseInterface]> { | |
| ... | |
| } | |
| ``` | |
| An operation with `MyInterface` attached, would have the following interfaces added: | |
| * MyBaseInterface, MyOtherBaseInterface, MyInterface | |
| The methods from `MyBaseInterface` in both `MyInterface` and `MyOtherBaseInterface` would | |
| forward to a single unique implementation for the operation. | |
| ##### Generation | |
| Once the interfaces have been defined, the C++ header and source files can be | |
| generated using the `--gen-<attr|op|type>-interface-decls` and | |
| `--gen-<attr|op|type>-interface-defs` options with mlir-tblgen. Note that when | |
| generating interfaces, mlir-tblgen will only generate interfaces defined in | |
| the top-level input `.td` file. This means that any interfaces that are | |
| defined within include files will not be considered for generation. | |
| 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()` | |
| - `void setCalleeFromCallable(CallInterfaceCallable)` | |
| - `Operation::operand_range getArgOperands()` | |
| - `MutableOperandRange getArgOperandsMutable()` | |
| - `Operation::result_range getForwardedResults()` | |
| - `Operation * resolveCallable()` | |
| - `Operation * resolveCallableInTable(SymbolTableCollection *)` | |
| - `ArrayAttr getArgAttrsAttr()` | |
| - `ArrayAttr getResAttrsAttr()` | |
| - `void setArgAttrsAttr(ArrayAttr)` | |
| - `void setResAttrsAttr(ArrayAttr)` | |
| - `Attribute removeArgAttrsAttr()` | |
| - `Attribute removeResAttrsAttr()` | |
| * `CallableOpInterface` - Used to represent the target callee of call. | |
| - `Region * getCallableRegion()` | |
| - `ArrayRef<Type> getArgumentTypes()` | |
| - `ArrayRef<Type> getResultTypes()` | |
| - `ArrayAttr getArgAttrsAttr()` | |
| - `ArrayAttr getResAttrsAttr()` | |
| - `void setArgAttrsAttr(ArrayAttr)` | |
| - `void setResAttrsAttr(ArrayAttr)` | |
| - `Attribute removeArgAttrsAttr()` | |
| - `Attribute removeResAttrsAttr()` | |
| A call operation may have operands and results that are not part of the call | |
| itself; such operands are said to be *consumed* by the operation and such | |
| results to be *produced* by it. The operands that are passed to the callee | |
| (`getArgOperands`) and the results that receive the values returned by the | |
| callee (`getForwardedResults`) are said to be *forwarded*, and they are in a 1:1 | |
| relationship with the arguments and results of the callee: the i-th forwarded | |
| operand is passed as the i-th argument of the callee and the i-th forwarded | |
| result receives the i-th value returned by the callee. Corresponding types are | |
| not required to be equal; it is up to the call operation to verify them as | |
| needed. | |
| Variadic arguments of a call to a variadic callee (if supported by the call | |
| op / callee op) are consumed operands, not forwarded ones: they do not | |
| correspond to any argument of the callee, which reads them through dedicated | |
| operations instead of receiving them as block arguments. | |
| ##### 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/#control-flow-and-ssacfg-regions) 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. |