diff --git a/mlir/docs/Interfaces.md b/mlir/docs/Interfaces.md --- a/mlir/docs/Interfaces.md +++ b/mlir/docs/Interfaces.md @@ -379,6 +379,9 @@ * 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. @@ -415,6 +418,8 @@ - The structure of this code block corresponds 1-1 with the structure of a [`Trait::verifyTrait`](Traits.md) method. +##### 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 @@ -634,6 +639,71 @@ [DeclareOpInterfaceMethods]> { ... } ``` +##### 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 +`DeclareInterfaceMethods` helper classes. + +If our interface were to be specified as: + +```tablegen +def MyBaseInterface : OpInterface<"MyBaseInterface"> { + ... +} + +def MyOtherBaseInterface : OpInterface { + ... +} + +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--interface-decls` and `--gen--interface-defs` options with mlir-tblgen. Note that when diff --git a/mlir/include/mlir/IR/OpBase.td b/mlir/include/mlir/IR/OpBase.td --- a/mlir/include/mlir/IR/OpBase.td +++ b/mlir/include/mlir/IR/OpBase.td @@ -1947,7 +1947,7 @@ def NoRegionArguments : NativeOpTrait<"NoRegionArguments">, StructuralOpTrait; //===----------------------------------------------------------------------===// -// OpInterface definitions +// Interface definitions //===----------------------------------------------------------------------===// // Marker used to identify the argument list for an op or interface method. @@ -2025,7 +2025,7 @@ defaultImplementation>; // Interface represents a base interface. -class Interface { +class Interface baseInterfacesArg = []> { // A human-readable description of what this interface does. string description = ""; @@ -2055,28 +2055,36 @@ // `$_attr`/`$_op`/`$_type` may be used to refer to an instance of the // entity being checked. code extraClassOf = ""; + + // An optional set of base interfaces that this interface + // "derives" from. + list baseInterfaces = baseInterfacesArg; } // AttrInterface represents an interface registered to an attribute. -class AttrInterface : Interface, InterfaceTrait, - Attr()">, - name # " instance"> -{ +class AttrInterface baseInterfaces = []> + : Interface, InterfaceTrait, + Attr()">, + name # " instance" + > { let storageType = !if(!empty(cppNamespace), "", cppNamespace # "::") # name; let returnType = storageType; let convertFromStorage = "$_self"; } // OpInterface represents an interface registered to an operation. -class OpInterface : Interface, OpInterfaceTrait; +class OpInterface baseInterfaces = []> + : Interface, OpInterfaceTrait; // TypeInterface represents an interface registered to a type. -class TypeInterface : Interface, InterfaceTrait, - Type()">, +class TypeInterface baseInterfaces = []> + : Interface, InterfaceTrait, + Type()">, name # " instance", - !if(!empty(cppNamespace),"", cppNamespace # "::") # name>; + !if(!empty(cppNamespace),"", cppNamespace # "::") # name + >; // Whether to declare the interface methods in the user entity's header. This // class simply wraps an Interface but is used to indicate that the method @@ -2092,29 +2100,32 @@ class DeclareAttrInterfaceMethods overridenMethods = []> : DeclareInterfaceMethods, - AttrInterface { + AttrInterface { let description = interface.description; let cppInterfaceName = interface.cppInterfaceName; let cppNamespace = interface.cppNamespace; let methods = interface.methods; + let baseInterfaces = interface.baseInterfaces; } class DeclareOpInterfaceMethods overridenMethods = []> : DeclareInterfaceMethods, - OpInterface { + OpInterface { let description = interface.description; let cppInterfaceName = interface.cppInterfaceName; let cppNamespace = interface.cppNamespace; let methods = interface.methods; + let baseInterfaces = interface.baseInterfaces; } class DeclareTypeInterfaceMethods overridenMethods = []> : DeclareInterfaceMethods, - TypeInterface { + TypeInterface { let description = interface.description; let cppInterfaceName = interface.cppInterfaceName; let cppNamespace = interface.cppNamespace; let methods = interface.methods; + let baseInterfaces = interface.baseInterfaces; } //===----------------------------------------------------------------------===// diff --git a/mlir/include/mlir/IR/OperationSupport.h b/mlir/include/mlir/IR/OperationSupport.h --- a/mlir/include/mlir/IR/OperationSupport.h +++ b/mlir/include/mlir/IR/OperationSupport.h @@ -284,8 +284,9 @@ /// Attach the given models as implementations of the corresponding /// interfaces for the concrete operation. - template void attachInterface() { - getImpl()->getInterfaceMap().insert(); + template + void attachInterface() { + getImpl()->getInterfaceMap().insertModels(); } /// Returns true if this operation has the given interface registered to it. @@ -378,7 +379,8 @@ public: /// Implementation of the InterfaceConcept for operation APIs that forwarded /// to a concrete op implementation. - template struct Model : public Impl { + template + struct Model : public Impl { Model(Dialect *dialect) : Impl(ConcreteOp::getOperationName(), dialect, TypeID::get(), ConcreteOp::getInterfaceMap()) {} @@ -418,7 +420,8 @@ /// Register a new operation in a Dialect object. /// This constructor is used by Dialect objects when they register the list /// of operations they contain. - template static void insert(Dialect &dialect) { + template + static void insert(Dialect &dialect) { insert(std::make_unique>(&dialect), T::getAttributeNames()); } /// The use of this method is in general discouraged in favor of diff --git a/mlir/include/mlir/IR/StorageUniquerSupport.h b/mlir/include/mlir/IR/StorageUniquerSupport.h --- a/mlir/include/mlir/IR/StorageUniquerSupport.h +++ b/mlir/include/mlir/IR/StorageUniquerSupport.h @@ -140,7 +140,7 @@ "that is not itself registered."); (checkInterfaceTarget(), ...); - abstract->interfaceMap.template insert(); + abstract->interfaceMap.template insertModels(); } /// Get or create a new ConcreteT instance within the ctx. This diff --git a/mlir/include/mlir/Support/InterfaceSupport.h b/mlir/include/mlir/Support/InterfaceSupport.h --- a/mlir/include/mlir/Support/InterfaceSupport.h +++ b/mlir/include/mlir/Support/InterfaceSupport.h @@ -111,8 +111,8 @@ } /// Constructor for a known concept. - Interface(ValueT t, Concept *conceptImpl) - : BaseType(t), conceptImpl(conceptImpl) { + Interface(ValueT t, const Concept *conceptImpl) + : BaseType(t), conceptImpl(const_cast(conceptImpl)) { assert(!t || ConcreteType::getInterfaceFor(t) == conceptImpl); } @@ -152,25 +152,6 @@ template