Hi!
I’m trying to get a detailed understanding of dialect conversions, in particular of how to handle type conversions and conversions of operations from unrelated dialects. What is unclear to me is:
- The relationship of source, target and argument materializations to legalization (e.g., via rewrite patterns)
- The conversion of polymorphic operations neither belonging to the source nor the target dialect
As an example for a conversion scenario, consider the following function using FooDialect
that should be converted to operations and types from TargetDialect
. The function below:
func.func @foo(%arg0: !FooDialect.foo_type, %arg1: !FooDialect.foo_type) -> !FooDialect.foo_type
{
%0 = FooDialect.some_op %arg0, %arg1 : (!FooDialect.foo_type, !FooDialect.foo_type) -> !FooDialect.foo_type
return %0 : !FooDialect.foo_type
}
should become:
func.func @foo(%arg0: !TargetDialect.target_type, %arg1: !TargetDialect.target_type) -> !TargetDialect.target_type
{
%0 = TargetDialect.another_op %arg0, %arg1 : (!TargetDialect.target_type, !TargetDialect.target_type) -> !TargetDialect.target_type
return %0 : !TargetDialect.target_type
}
As far as I understand, the following is needed to implement the conversion:
- A conversion target specification marking:
- the
FooDialect
as illegal -
func::ReturnOp
dynamically as illegal if the operand and result types belong toFooDialect
- the
- A type converter with:
- an argument materialization creating
builtin.unrealized_conversion_cast
s - a source materialization creating
builtin.unrealized_conversion_cast
s - a target materialization creating
builtin.unrealized_conversion_cast
s
- an argument materialization creating
- A
ConversionPatternSet
with- A pattern for each operation from
FooDialect
, replacing the op with an op fromTargetDialect
- An instance of the
FunctionOpInterfaceSignatureConversion
pattern that converts the function type and updates the function in place - A pattern for
func::ReturnOp
- A pattern for each operation from
Furthermore, I understand that the unrealized_conversion_cast
s are only there to temporarily reconcile the IR until all operations have been converted.
I assume that source, target and argument conversions are triggered whenever an illegal op is processed, whose operands or results have a type for which a respective materialization exists. I further assume that this is completely transparent to the legalization pattern itself (e.g., a rewrite pattern), as operands are rewritten during materialization and before the illegal op is finally processed. Is this correct?
Let’s go through the conversion step by step. The first thing that should happen is the signature conversion. Since there are still consumers of the arguments with the old type, signature conversion should kick in and insert builtin.unrealized_conversion_cast
s for each argument, leaving the IR as:
func.func @foo(%arg0: !TargetDialect.target_type, %arg1: !TargetDialect.target_type) -> !TargetDialect.target_type
{
%tmpArg0 = builtin.unrealized_conversion_cast %arg0 : !TargetDialect.target_type to !FooDialect.foo_type
%tmpArg1 = builtin.unrealized_conversion_cast %arg1 : !TargetDialect.target_type to !FooDialect.foo_type
%0 = FooDialect.some_op %tmpArg0, %tmpArg1 : (!FooDialect.foo_type, !FooDialect.foo_type) -> !FooDialect.foo_type
return %0 : !FooDialect.foo_type
}
I’m not sure about how the return operation is handled. Is the above state, the IR is invalid, since the operand type of the return operation does not match the return type of the function operation. Supposing that the IR remains just invalid and the conversion attempts to reconcile it, the pattern for the return op should be executed next, triggering source materialization and resulting in:
func.func @foo(%arg0: !TargetDialect.target_type, %arg1: !TargetDialect.target_type) -> !TargetDialect.target_type
{
%tmpArg0 = builtin.unrealized_conversion_cast %arg0 : !TargetDialect.target_type to !FooDialect.foo_type
%tmpArg1 = builtin.unrealized_conversion_cast %arg1 : !TargetDialect.target_type to !FooDialect.foo_type
%0 = FooDialect.some_op %tmpArg0, %tmpArg1 : (!FooDialect.foo_type, !FooDialect.foo_type) -> !FooDialect.foo_type
%0converted = builtin.unrealized_conversion_cast %0 : !FooDialect.foo_type to !TargetDialect.target_type
return %0converted : !TargetDialect.target_type
}
Finally, the pattern for FooDialect.some_op
would be executed, triggering source and target materialization:
func.func @foo(%arg0: !TargetDialect.target_type, %arg1: !TargetDialect.target_type) -> !TargetDialect.target_type
{
%tmpArg0 = builtin.unrealized_conversion_cast %arg0 : !TargetDialect.target_type to !FooDialect.foo_type
%tmpArg1 = builtin.unrealized_conversion_cast %arg1 : !TargetDialect.target_type to !FooDialect.foo_type
%tmpArgReconverted0 = builtin.unrealized_conversion_cast %tmpArg0 : !FooDialect.foo_type to !TargetDialect.target_type
%tmpArgReconverted1 = builtin.unrealized_conversion_cast %tmpArg1 : !FooDialect.foo_type to !TargetDialect.target_type
%0 = TargetDialect.target_op %tmpArgReconverted0, %tmpArgReconverted1 : (!TargetDialect.target_type, !TargetDialect.target_type) -> !TargetDialect.target_type
%tmp0 = builtin.unrealized_conversion_cast %arg0 : !TargetDialect.target_type to !FooDialect.foo_type
%0converted = builtin.unrealized_conversion_cast %tmp0 : !FooDialect.foo_type to !TargetDialect.target_type
return %0converted : !TargetDialect.target_type
}
And then finally, the unrealized_conversion_cast
s are folded away:
func.func @foo(%arg0: !TargetDialect.target_type, %arg1: !TargetDialect.target_type) -> !TargetDialect.target_type
{
%0 = TargetDialect.target_op %arg0, %arg1 : (!TargetDialect.target_type, !TargetDialect.target_type) -> !TargetDialect.target_type
return %0 : !TargetDialect.target_type
}
Besides the handling of the return op after the signature conversion, I also wonder if there is a generic pattern that can be applied to handle polymorphic operations from unrelated dialects in general. At least, it seems odd to me to have a pattern for func::ReturnOp
in a conversion of FooDialect
to TargetDialect
. Following the same logic, there would have to be a pattern for linalg.generic
, too if for some reason the IR contains linalg.generic
operations working on FooDialect.foo_type
s.
I’d be grateful if someone could shed some light on this.
Thanks,
Andi