We have two fastmath attribute kinds, one defined in Arith and another defined in the LLVM dialect. Furthermore, the one from Arith is also used in Math, FIR and Linalg. It may also end up being used in Func and many downstreams.
It sounds like carrying all of Arith just to use the attribute is too much. Should this attribute go to builtin (because it is fundamentally connected to the builtin FloatType)? To its own dialect that other dialects can depend on?
“float operations” is not specific to arith, for example we have the math dialect as well, and the complex dialect too. More importantly anyone building their own dialect may have “float operations” of their own. At minima moving it into a fastmath dialect would make this more reusable.
(I don’t know what’s special about the LLVM dialect fast math, but if there is a good reason for this dialect to has its own attribute, it isn’t incompatible with refactoring the attribute used for the rest of the ecosystem either).
Is it? If I’m using my own floating point type (or quantized type?), wouldn’t I be interested to reuse this with my own type? (this isn’t an argument against builtin: if I want to reuse it, builtin is always more convenient of course).
The argument against builtin is that we’re already suffering from tensor, memref, etc. not being in their own dialects (because we have types loaded in the context but we can’t use the supporting code to manipulate these types: for example tensor.dim requires to load a separate dialect, it caused all sort of issues with interfaces).
The way I see it, all “floating point” types have similar semantics, be them IEEE scalars, dense or sparse tensors, quantized, packed, custom, etc. “Fast math” really means a lot of things (well, specifically fastmath means all of them), but they’re basically similar to all floaty-pointy-thingys: ignore operation order, ignore precision, ignore denormals, ignore reciprocal and do what is “fastest”.
The implementation may be different in different hardware (various levels of support), but the meaning is largely the same.
If there was a table with all flags versus all shapes versus all implementations, we could see that they’re all the same (or not). Barring that, as an iterative implementation design, assuming they’re the same until proven different and add exceptions to those seems more maintainable than assuming they’re all different and duplicate code.
One reasonable explanation could be that they needed it to lower to LLVM IR and there wasn’t one elsewhere, then the arith one appeared and the LLVM one remained. IIUC, they both have the same meaning, so we could in theory just use one, outside of both dialects.
The though process behind placing the LLVM flags in the arith dialect was that, while it may be the same as LLVM now, it might not always be the same. For example, I could imagine something along the lines of flush-to-zero. Also, LLVM seemed to (at one point at least) be constrained by the number of bits previously allocated, whereas (hopefully) MLIR would not be constrained by that.
It is fundamentally connected to performing arithmetic on float types (as opposed to them just sitting there ). I don’t see this as broken as everyone else, but I will accept the blame for where it is.
Recapping the earlier discussions linked above: Are their users of the math dialect that (for example) want to raise a floating point number to a power, but don’t want to add them together (which would require a dependency on the arith dialect anyway)? Certainly it is possible.
So it seems like there are 3 options:
Create a fastmath dialect which, at the moment, will contain only a single attribute, and use it from arith, LLVM, and others
Move the fastmath attribute to builtin and use it from arith and LLVM, and others
Leave it in arith, forcing dialects that want to use it to depend on the arith dialect (if they aren’t already), and lower to the LLVM version (which is dedicated to represent what LLVM supports)
IMO for options 1 and 2 it is worth thinking through how adding an additional flag (that isn’t supported by LLVM) at some point would occur - especially in light of API stability and version compatibility. With option 3, lowering a non-LLVM flag to LLVM could simply fail, or perform some sort of custom lowering. With 1 & 2, seems such a change would be a bigger deal.
Given the relatively small amount of code being duplicated here, I thought that 3 was the best approach. (Just providing some more detail on the thought process.)
it isn’t clear to me why the question of the LLVM dialect keeping its own attribute should be mixed in any of the 1,2, or 3rd choice here. Each of these options is valid even if the LLVM dialect keeps its own attribute I believe!
We are not playing the blame game here. There was a consensus at the point, but we all may have missed something.
The reasoning for duplicating the attribute between LLVM and Arith is largely the same as for duplicating operations between these two dialects: the former must mirror LLVM IR, the latter may diverge. The operations actually do diverge, for example, they apply elementwise to nD vectors that don’t event exist at the LLVM level. That being said, I am not convinced fastmath flags will diverge and remain so for a long time, I’d rather expect new flags to migrate soon enough.
API stability is explicitly not guaranteed by LLVM or MLIR.
It wouldn’t really. We have the LegalizeForExport pass that runs before translation to LLVM IR, which could handle this mismatch or fail with a diagnostic similarly to the ArithToLLVM conversion pass.
This argument can be flipped: given the small amount of code being duplicated, we can always redo the duplication if necessary at some later point, and simplify the conceptual complexity and layering of the project until then.
This is a good argument. From the user perspective, it is quite likely that anything that uses fastmath attributes needs to depend on Arith anyway (though the dialect itself does not need to).
From the upstream MLIR perspective, the layering question remains for, e.g., func that may want to use the attribute for external function declarations.