Hi,
I’ve run into an inconsistency that is relatively prevalent in the whole MLIR code base and I’m looking for advices on how to fix that.
In a nutshell several operations use index
for their dynamic attributes, but i64
for their static attributes, leading to (mixed) operands with more than one type!
I have a work-in-progress patch to fix that, see NF use index instead of i64 where it makes sense · llvm/llvm-project@07dd1f7 · GitHub (it only fixes the tensor
and memref
dialects for now), but I have mainly two problems:
- I only discovered the places I missed to fix at runtime and a blind
i64
toindex
is not the right approach. So essentially we need to audit our code to decide what is a legiti64
and what should be anindex
and I don’t think I can do this alone or go with “typeless” attributes (more on that below) - I don’t see a good way to stage the changes: it seems that we have to fix everything in one go to be able to change one dialect (because of the dependencies in lowering and whatnot.)
How do you think we should approach this inconsistency?
What is the problem?
Consider memref.reinterpret_cast
:
def MemRef_ReinterpretCastOp
: MemRef_OpWithOffsetSizesAndStrides<"reinterpret_cast", [
[...]
let arguments = (ins Arg<AnyRankedOrUnrankedMemRef, "", []>:$source,
[...]
Variadic<Index>:$sizes, // <-- dynamic sizes
[...]
I64ArrayAttr:$static_sizes, // <-- static sizes
If you write something like memref.reinterpret_cast ... to ..., [%size0, 8, %size2]
and then print the types of getMixedSizes()
, you’ll get index, i64, index
.
No surprise here, it’s consistent with the spec.
Now, let’s say we can prove that %size0
is a compile time constant of say 2
.
What should we use for the type of 2
?
Chances are we’ll want to do a RAUW and we’ll go with the type of %size0
, i.e., index
.
At this point, we may have in the IR several instances of 2
with different types but that represent the same thing (e.g., if another static size was initialized directly from the textual IR). Hence, unless the optimizers unbox the attributes, they would miss that they are actually equal.
The unboxing problem is something we know about in MLIR, nothing new here. What IMO is problematic in this instance is that we have an inconsistency of types within what is conceptually just one operand.
Typeless attributes
For some attributes we actually don’t care about their types or more precisely the type is not extensible so we could save ourselves the need to have it around. I.e., no need to store [{index, 3}, {index, 2}, {index, 1}]
for a static size of [3, 2, 1]
, just the constants are enough.
@mehdi_amini actually started this clean-up by introducing DenseXXArrayAttr
for exactly this kind of use case.
We could consider doing this clean-up directly as I believe this is the right long term approach. It’ll probably be more disruptive (or less incremental) though as it’ll likely require more changes in the C++ files.
What do people think?
Cheers,
-Quentin
CC: @nicolasvasilache, @ftynse