diff --git a/mlir/docs/Interfaces.md b/mlir/docs/Interfaces.md --- a/mlir/docs/Interfaces.md +++ b/mlir/docs/Interfaces.md @@ -1,46 +1,47 @@ # Interfaces -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`. +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 extensible by allowing the addition of new dialects and -operations in a decoupled way with respect to the implementation of +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 -might even be defined in different dialects. These interfaces generally involve -wide coverage over the entire dialect and are only 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 generally queries -high-level information about the operations within a dialect, like legality and -cost modeling, that often is not specific to one operation. - -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. -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. +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 an Inlining interface to allow for dialects to opt-in. +/// Define a base inlining interface class to allow for dialects to opt-in to +/// the inliner. class DialectInlinerInterface : public DialectInterface::Base { public: @@ -55,8 +56,8 @@ } }; -/// Override the inliner interface to add support for inlining affine -/// operations. +/// 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, @@ -71,21 +72,24 @@ } ``` -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: +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 (auto *interface = dialect->getInterface()) - ... // The dialect provides this interface. +if (DialectInlinerInterface *interface + = dialect->getRegisteredInterface()) { + // The dialect has provided an implementation of this interface. + ... +} ``` -#### DialectInterfaceCollections +#### DialectInterfaceCollection -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. +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 @@ -111,57 +115,80 @@ 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, 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. - -These interfaces are defined by overriding the CRTP base class `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 object type. It is important to note -that these classes should be pure in that they contain no non-static data -members. Objects that wish to override this interface should add the provided -trait `*Interface<..>::Trait` to the trait list upon registration. +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 defines the virtual interface that needs - /// to be overridden. + /// Define a base concept class that specifies the virtual interface to be + /// implemented. struct Concept { virtual ~Concept(); - virtual unsigned getNumInputs(Operation *op) const = 0; + + /// 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 + template struct Model : public Concept { /// Override the method to dispatch on the concrete operation. - unsigned getNumInputs(Operation *op) const final { - return llvm::cast(op).getNumInputs(); + unsigned exampleInterfaceHook(Operation *op) const final { + return llvm::cast(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 { public: - /// Use base class constructor to support LLVM-style casts. + /// Inherit the base class constructor to support LLVM-style casting. using OpInterface::OpInterface; - /// The interface dispatches to 'getImpl()', an instance of the concept. - unsigned getNumInputs() const { - return getImpl()->getNumInputs(getOperation()); + /// 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`. Using this interface is just -like using any other derived operation type, i.e. casting: +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 @@ -169,21 +196,29 @@ class MyOp : public Op { public: /// The definition of the interface method on the derived operation. - unsigned getNumInputs() { return ...; } + 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(op)) - llvm::errs() << "num inputs = " << example.getNumInputs() << "\n"; + llvm::errs() << "hook returned = " << example.exampleInterfaceHook() << "\n"; ``` #### 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#interfaces). +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: @@ -196,19 +231,270 @@ let methods = [ InterfaceMethod< - "Get the number of inputs for the current operation.", - "unsigned", "getNumInputs" + "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 + struct Model : public Concept { + Operation *create(OpBuilder &builder, Location loc) const override { + return builder.create(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(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 + struct Model : public Concept { + Operation *create(Operation *opaqueOp, OpBuilder &builder, + Location loc) const override { + ConcreteOp op = cast(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 + class MyTrait : public OpTrait::TraitBase { + public: + bool isSafeToTransform() { + ConcreteOp op = cast(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]> { ... } + +// 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]> { ... } +~~~ + +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: +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)) @@ -224,10 +510,15 @@ ##### 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. + - `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 diff --git a/mlir/docs/OpDefinitions.md b/mlir/docs/OpDefinitions.md --- a/mlir/docs/OpDefinitions.md +++ b/mlir/docs/OpDefinitions.md @@ -340,134 +340,10 @@ Traits are operation properties that affect syntax or semantics. MLIR C++ models various traits in the `mlir::OpTrait` namespace. -Both operation traits, [interfaces](#operation-interfaces), and constraints -involving multiple operands/attributes/results are provided as the second -template parameter to the `Op` class. They should be deriving from the `OpTrait` -class. See [Constraints](#constraints) for more information. - -### Interfaces - -[Interfaces](Interfaces.md#attribute-operation-type-interfaces) allow for -attributes, operations, and types to expose method calls without the caller -needing to know the derived type. Operation interfaces defined in C++ can be -accessed in the ODS framework via the `OpInterfaceTrait` class. Aside from using -pre-existing interfaces in the C++ API, the ODS framework also provides a -simplified mechanism for defining such interfaces which removes much of the -boilerplate necessary. - -Providing a definition of the `AttrInterface`, `OpInterface`, or `TypeInterface` -class will auto-generate the C++ classes for the interface. An interface -includes a name, for the C++ class, a description, and a list of interface -methods. - -```tablegen -def MyInterface : OpInterface<"MyInterface"> { - let description = ...; - let 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: - -* Description - - A string description of what this method does and its invariants. -* ReturnType - - A string corresponding to the C++ return type of the method. -* MethodName - - A string corresponding to the desired 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. - - `ConcreteOp` is an implicitly defined typename that can be used to refer - to the type of the derived operation currently being operated on. - - In non-static methods, a variable 'ConcreteOp op' is defined and may be - used to refer to an instance of the derived operation. -* DefaultImplementation (Optional) - - An optional explicit default implementation of the interface method. - - This method is placed within the `Trait` class that is attached to the - operation. As such, this method has the same characteristics as any - other [`Trait`](Traits.md) method. - - `ConcreteOp` is an implicitly defined typename that can be used to refer - to the type of the derived operation currently being operated on. - -ODS also allows generating the declarations for the `InterfaceMethod` of the op -if one specifies the interface with `DeclareOpInterfaceMethods` (see example -below). - -Examples: - -```tablegen -def MyInterface : OpInterface<"MyInterface"> { - let description = [{ - My interface is very interesting. ... - }]; - - let methods = [ - // A simple non-static method with no inputs. - InterfaceMethod<"'foo' is a non-static method with no inputs.", - "unsigned", "foo" - >, - - // A new non-static method accepting an input argument. - InterfaceMethod<"/*insert doc here*/", - "Value ", "bar", (ins "unsigned":$i) - >, - - // Query a static property of the derived operation. - StaticInterfaceMethod<"'fooStatic' is a static method with no inputs.", - "unsigned", "fooStatic" - >, - - // Provide the definition of a static interface method. - // Note: `ConcreteOp` corresponds to the derived operation typename. - StaticInterfaceMethod<"/*insert doc here*/", - "Operation *", "create", (ins "OpBuilder &":$builder, "Location":$loc), [{ - return builder.create(loc); - }]>, - - // Provide a definition of the non-static method. - // Note: `op` corresponds to the derived operation variable. - InterfaceMethod<"/*insert doc here*/", - "unsigned", "getNumInputsAndOutputs", (ins), [{ - return op.getNumInputs() + op.getNumOutputs(); - }]>, - - // Provide only a default definition of the method. - // Note: `ConcreteOp` corresponds to the derived operation typename. - InterfaceMethod<"/*insert doc here*/", - "unsigned", "getNumWithDefault", (ins), /*methodBody=*/[{}], [{ - ConcreteOp op = cast(this->getOperation()); - return op.getNumInputs() + op.getNumOutputs(); - }]>, - ]; -} - -// 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]> { ... } - -// 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]> { ... } -``` - -Operation interfaces may also provide a verification method on `OpInterface` by -setting `verify`. Setting `verify` results in the generated trait having a -`verifyTrait` method that is applied to all operations implementing the trait. +Both operation traits, [interfaces](Interfaces.md#utilizing-the-ods-framework), +and constraints involving multiple operands/attributes/results are provided as +the second template parameter to the `Op` class. They should be deriving from +the `OpTrait` class. See [Constraints](#constraints) for more information. ### Builder methods