diff --git a/mlir/docs/OpDefinitions.md b/mlir/docs/OpDefinitions.md --- a/mlir/docs/OpDefinitions.md +++ b/mlir/docs/OpDefinitions.md @@ -943,6 +943,176 @@ power users; for not-yet-implemented widely-applicable cases, improving the infrastructure is preferable. +### Op availability + +Dialects are evolving op collections. Typically there are backward/forward +compatibility implications when introducing changes. This is especially true for +edge dialects, which model external programming models or hardware so must +mirroring the requirements. + +A typical mechanism to guarantee compatibility is _versioning_. But there are +other ways. For example, in SPIR-V, some ops are available only under certain +_capabilities_ or _extensions_. Generally, these are all mechanisms to control +the _availability_ of ops. + +MLIR uses a generic mechanism to support op availability. It is integrated into +ODS and tries to follow the same declarative mannaer and auto-generate as much +code as possible. Under the hood the generated code uses core mechanisms +including op interfaces, op traits, etc. So effectively it's a layer on top +of them. + +In a sense availability can be arbitrarily defined (e.g., we can define a v2 +version of an op with vastly different semantics and operands/attributes), +the op availability mechansim cannot support all cases. Instead, it's meant to +cover common cases (and this may grow). Right now the following places can have +availability specification attached: + +* The op itself. +* Enum attributes of an op. + +If an op has availability specification in any of the above places, the +following corresponding components can be generated from `mlir-tblgen`: + +* Op interface for the availability: via `-gen-avail-interface-decls` and + `-gen-avail-interface-defs`. +* Op interface method implementation: via `-gen-op-defs`. +* Enum availability query utility functions: via `-gen-enum-avail-decls` and + `-gen-enum-avail-defs`. + +Next we explain how to use op availability with examples. + +#### MinVersion and MaxVersion + +It's quite common to see minimal/maximal version requirements on ops. To help +with this common use case, `MinVersionBase` and `MaxVersionBase` are defined in +[`OpBase.td`][OpBase]. It allows us to plug in a dialect-specific versioning +scheme, defined as an `I32EnumAttr`. For example, + +```tablegen +def Example_V_1_0 : I32EnumAttrCase<"V_1_0", 0, "v1.0">; +def Example_V_1_1 : I32EnumAttrCase<"V_1_1", 1, "v1.1">; +def Example_V_1_2 : I32EnumAttrCase<"V_1_2", 2, "v1.2">; +def Example_V_1_3 : I32EnumAttrCase<"V_1_3", 3, "v1.3">; +def Example_V_1_4 : I32EnumAttrCase<"V_1_4", 3, "v1.4">; + +def Example_VersionAttr : I32EnumAttr<"Version", "supported versions", [ + Example_V_1_0, Example_V_1_1, Example_V_1_2, Example_V_1_3, Example_V_1_4]>; + +class Example_MinVersion : MinVersionBase< + "ExampleQueryMinVersionInterface", Example_VersionAttr, min> { + let interfaceDescription = [{ + Querying interface for minimal required version. + }]; + let interfaceNamespace = "::mlir::example"; +} + +class Example_MaxVersion : MaxVersionBase< + "ExampleQueryMaxVersionInterface", Example_VersionAttr, max> { + let interfaceDescription = [{ + Querying interface for maximal supported version. + }]; + let interfaceNamespace = "::mlir::example"; +} +``` + +The above defines min and max version requirements for some `example` dialect. +With `mlir-tblgen -gen-avail-interface-{decls|defs}`, two op interfaces are +generated from the above: `mlir::example::ExampleQueryMinVersionInterface` and +`mlir::example::ExampleQueryMaxVersionInterface`. They have a `getMinVersion()` +and `getMaxVersion()` method, respectively, for querying the minimal version +and maximal version requirements for all ops implementing them. + +Then we can use them in ops for the `example` dialect: + +```tablegen +def Example_MinMaxVersionOp : Example_Op<"min_max_version_op"> { + // ... + let availability = [ + Example_MinVersion, + Example_MaxVersion + ]; +} +``` + +The above defines an `example.min_max_version_op` that is introduced in v1.1 +and removed since v1.4. There is no need to manually include the +`mlir::example::EampleQuery{Min|Max}VersionInterface::Trait` in the op trait +list or implement the `get{Min|Max}Version()` method; they are both auto +inserted/generated when `mlir-tblgen -gen-op-defs`. `getMinVersion()` will +always return `mlir::example::Version::V_1_1`, and `getMaxVersion()` will always +return `mlir::example::Version::V_1_3`. They can be used, for example, together +with legality specification during dialect conversion. + +We can also attach version controls on enum attributes: + +```tablegen +def Example_VersionedEnumCaseA : I32EnumAttrCase<"A", 0> { + // No version requirements. +} +def Example_VersionedEnumCaseB : I32EnumAttrCase<"B", 1> { + // Removed since v1.3. + let availability = [Example_MaxVersion]; +} +def Example_VersionedEnumCaseC : I32EnumAttrCase<"C", 2> { + // Added since v1.1. + let availability = [Example_MinVersion]; +} + +def Example_VersionedEnumAttr : I32EnumAttr<"VersionedEnum", "valid enum cases", [ + VersionedEnumCaseA, VersionedEnumCaseB, VersionedEnumCaseC]> { + let cppNamespace = "::mlir::example"; +} + + +def Example_OneVersionedAttrOp : Example_Op<"one_versioned_attr_op"> { + let arguments = (ins Example_VersionedEnumAttr:$attr); +let results = (outs); +} + +def Example_MixVersionedAttrOp : Example_Op<"mix_versioned_attr_op"> { + let arguments = (ins + Example_VersionedEnumAttr:$attr1, + Example_VersionedEnumAttr:$attr2, + ... + ); + let results = (outs ...); + + let availability = [ + Example_MinVersion, + Example_MaxVersion + ]; +} +``` + +The above will need `mlir-tblgen -gen-enum-avail-{decls|defs}` to generate +the utility functions for generated C++ enums. + +`example.one_versioned_attr_op` does not have versioning itself; but because it +has an enum attribute that subjects to versioninig, it will still automatically +declare and implement the min/max version interface. But here the returned +version depends on the concrete `example.one_versioned_attr_op` instance. For +example, `example.one_versioned_attr_op attr=A` will have `getMinVersion()` and +`getMaxVersion()` to be both `llvm::None`. If `attr=B` then they are +`llvm::None` and `mlir::example::Version::V_1_2`. Similarly, if `attr=C` then +they are `mlir::example::Version::V_1_1` and `llvm::None`. + +`example.mix_versioned_attr_op` has version controls on both itself and two of +its attributes. The final min/max version takes consideration of all pieces. +`attr1=A, attr2=B` will have min/max version as v1.0/v1.2. `attr1=A, attr2=C` +will have min/max version as v1.1/v1.3. `attr1=B attr2=C` will have min/max +version as v1.1/v1.2. + +#### Other availability controls + +Versioninig is just one way to control availability. To support others, we can +subclass `Availability`. It requires specifying a few fields for the generated +interface and also logic for deducing final availability results from op +components. The definitions of `MinVersionBase`/`MaxVersionBase` can actually +serve as examples. Most importantly, we need to define `initializer` as the +initial availability value before considering any part of the op, and +`mergeAction` to specify how a new part's availability is merged into the +current deduced imtermediate availability value. + ### Generated C++ code [OpDefinitionsGen][OpDefinitionsGen] processes the op definition spec file and