While we already extended MLIR with API support for the dynamic definition of dialects, accessing this feature from within MLIR remains hard and requires additional C++ code. Hence, we propose to add proper tool support for dynamic dialects. Following the design of PDL, we define a corresponding IRDL dialect that represents IR properties, such as types, attributes, and operations, as MLIR programs. We then extend mlir-opt to instantiate dynamic dialects from IRDL dialect definitions provided at the command line. This RFC introduces the core concepts of IRDL, with the objective of building more features on top of it.
I created two patches on phabricator, the first one to introduce the IRDL dialect, and the second one to add the registration of IRDL using dynamic dialects. Reviews would be very welcomed!
Motivating example
Let’s take the following IRDL program:
module {
irdl.dialect @cmath {
irdl.type @complex {
%0 = irdl.is f32
%1 = irdl.is f64
%2 = irdl.any_of(%0, %1)
irdl.parameters(%2)
}
irdl.operation @norm {
%0 = irdl.any
%1 = irdl.parametric @complex<%0>
irdl.operands(%1)
irdl.results(%0)
}
}
This program defines the cmath
dialect, with a cmath.complex
type and an cmath.norm
operation. The cmath.complex
type has one parameter that can be either f32
or f64
, representing the scalar type backing the complex. The cmath.norm
operation can only take one operand that must be a complex number, and return a value of the exact underlying scalar type. Notice how the type matched by %0
must be the same across references: if the operand is of type cmath.complex<f32>
, the result must be of type f32
.
The cmath
dialect can directly be used by mlir-opt
with the command mlir-opt program.mlir –irdl-file=dialect.irdl.mlir
. It will for instance parse the following program:
func.func @conorm(%p: !cmath.complex<f32>, %q: !cmath.complex<f32>) -> f32 {
%norm_p = "cmath.norm"(%p) : (!cmath.complex<f32>) -> f32
%norm_q = "cmath.norm"(%q) : (!cmath.complex<f32>) -> f32
%pq = arith.mulf %norm_p, %norm_q : f32
return %pq : f32
}
In particular, this registration is done entirely at runtime and does not require any recompilation.
How this works
IRDL-defined dialects are registered through the dynamic dialect infrastructure. On invocation of mlir-opt, we parse an IRDL file to register the dialects in the context. Then, we proceed with other MLIR files that can reference dialects described in the IRDL file.
One can see the verification process as a matching algorithm. Each !irdl.attribute
SSA value correspond to a single attribute, and operations define the matching constraints. Thus, using a value twice also correspond to having an equality constraint between two attributes. In order to simplify the language and the implementation, types are wrapped in a TypeAttr
, so matching a type t
correspond to matching the outer TypeAttr
, and then the contained type.
Additionally, C++ attributes and types can be registered to be used within an IRDL definition. This is achieved through the AttributeWrapper and TypeWrapper infrastructure, specifying the expected structure of a C++ attribute or type. Those wrappers are registered before reading the IRDL file. For example, this allows to parse the constraint irdl.parametric "builtin.complex"<%0>
, which constraint a type to be a ComplexType
, with a single parameter constrained by %0
.
Feature list
Dialect definition:
- irdl.dialect : Defines a new dialect.
- irdl.type / irdl.attribute: Defines a new type/attribute.
- irdl.parameters: Defines constraints over the parameters of the type/attribute definition.
- irdl.operation: Defines a new op.
- irdl.operands: Defines constraints over its operands
- irdl.results: Defines constraints over its results
Available attribute constraints:
- irdl.is : Check that an attribute is exactly the one given
- irdl.any : Satisfies any attribute
- irdl.any_of : Check that an attribute satisfies one of the given constraints
- irdl.all_of : Check that an attribute satisfies all the given constraints
- irdl.parametric : Check that an attribute is of a given attribute definition, and check that its parameters satisfy the given constraints
Roadmap
The current patch contains the core data structures and concepts of IRDL. This is by far not a complete implementation, but it lays the necessary groundwork for it. In particular, here is a list of features that would be added next (in no particular order):
- Add support for attributes and regions.
- Add support for custom verifiers that can be registered from C++.
- Add support for variadics.
- Add support for traits and interfaces.
- Add a separate irdl-Interp dialect to compile IRDL specifications into a faster and complete verifier, similar to pdl-interp.
Remaining questions on the PR
Currently, this patch only defines a single TypeWrapper
, for builtin.complex
. However, users need to define their own wrappers to use the other types or attributes defined in MLIR. I feel that the current design is not scalable, because the wrappers are defined in the IRDL dialect, meaning that the IRDL dialect would depend to any dialect that defines a type or an attribute. I have currently no clear solution in mind how to solve that problem, and would be happy to hear what people would have in mind to fix this.
Best,
Mathieu and @Moxinilian