From 83a635c0d4759bd77bbbb21ff8d202cb8c3ea57b Mon Sep 17 00:00:00 2001 From: River Riddle Date: Thu, 12 Jan 2023 13:35:15 -0800 Subject: [PATCH] [mlir] Add support for interface inheritance This allows for interfaces to define a set of "base classes", which are interfaces whose methods/extra class decls/etc. should be inherited by the derived interface. This more easily enables combining interfaces and their dependencies, without lots of awkard casting. Additional implicit conversion operators also greatly simplify the conversion process. One other aspect of this "inheritance" is that we also implicitly add the base interfaces to the attr/op/type. The user can still add them manually if desired, but this should help remove some of the boiler plate when an interface has dependencies. See https://discourse.llvm.org/t/interface-inheritance-and-dependencies-interface-method-visibility-interface-composition Differential Revision: https://reviews.llvm.org/D140198 --- mlir/docs/Interfaces.md | 70 +++++++++++++++ mlir/include/mlir/IR/OpBase.td | 41 +++++---- mlir/include/mlir/IR/OperationSupport.h | 11 ++- mlir/include/mlir/IR/StorageUniquerSupport.h | 2 +- mlir/include/mlir/Support/InterfaceSupport.h | 96 +++++++++----------- mlir/include/mlir/TableGen/Interfaces.h | 16 ++++ mlir/lib/IR/ExtensibleDialect.cpp | 2 +- mlir/lib/IR/MLIRContext.cpp | 2 +- mlir/lib/Support/InterfaceSupport.cpp | 31 +++---- mlir/lib/TableGen/AttrOrTypeDef.cpp | 21 ++++- mlir/lib/TableGen/Interfaces.cpp | 26 ++++++ mlir/lib/TableGen/Operator.cpp | 14 ++- mlir/test/lib/Dialect/Test/TestInterfaces.td | 22 ++++- mlir/test/mlir-tblgen/op-interface.td | 66 ++++++++++++++ mlir/tools/mlir-tblgen/OpInterfacesGen.cpp | 130 ++++++++++++++++++++------- mlir/tools/mlir-tblgen/SPIRVUtilsGen.cpp | 3 + 16 files changed, 413 insertions(+), 140 deletions(-) diff --git a/mlir/docs/Interfaces.md b/mlir/docs/Interfaces.md index 9482c5a..6bb5070 100644 --- a/mlir/docs/Interfaces.md +++ b/mlir/docs/Interfaces.md @@ -379,6 +379,9 @@ 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. @@ -415,6 +418,8 @@ comprised of the following components: - 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 @@ def OpWithOverrideInferTypeInterfaceOp : Op<... [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 index ea9976c..e859e40 100644 --- a/mlir/include/mlir/IR/OpBase.td +++ b/mlir/include/mlir/IR/OpBase.td @@ -1947,7 +1947,7 @@ def AttrSizedResultSegments : 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 @@ class StaticInterfaceMethod; // 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 @@ class Interface { // `$_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 DeclareInterfaceMethods overridenMethods = []> { 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 index fc7281f..5d526f9 100644 --- a/mlir/include/mlir/IR/OperationSupport.h +++ b/mlir/include/mlir/IR/OperationSupport.h @@ -284,8 +284,9 @@ public: /// 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 @@ class RegisteredOperationName : public OperationName { 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 @@ public: /// 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 index ff5a063..128ad81 100644 --- a/mlir/include/mlir/IR/StorageUniquerSupport.h +++ b/mlir/include/mlir/IR/StorageUniquerSupport.h @@ -140,7 +140,7 @@ public: "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 index 6ba7b33..71c4003 100644 --- a/mlir/include/mlir/Support/InterfaceSupport.h +++ b/mlir/include/mlir/Support/InterfaceSupport.h @@ -111,8 +111,8 @@ public: } /// 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 @@ struct count_if_t_impl template