Complex constants are already supported inside DenseElementsAttr, but DenseElementsAttr::AttributeElementIterator::operator*() runs into an llvm_unreachable(“unexpected type”). This operator is (indirectly) used by the getSplatValue() method which in turn is needed by the FoldSplatConstant pass in linalg. If we have a builtin ComplexAttr, we could implement the missing functionality in this operator and add support for folding complex splat constants.
Ideally we would use the same syntax for complex values inside DenseElementsAttr and for complex values for ComplexAttr. Complex values inside DenseElementsAttr are represented as “(-1.0, 1.0)”, i.e. the two float or int constants are inside parentheses and separated by a comma. However if we want to reuse that for ComplexAttr, the parser would have to somehow distinguish between reading a ComplexAttr or a TypeAttr that has a function type (e.g. “type = (tensor<2xf32>) → tensor<2xf32>” inside a attribute dictionary). Maybe this is possible if TypeAttr can only occur in certain contexts, but even then it would mean that ComplexAttr could never be used in the same context as a TypeAttr.
Therefore the suggestion is to change the syntax which is used for complex values inside DenseElementsAttr and use this new syntax also for ComplexAttr.
Here are some ideas how the syntax could look like:
complex(1.0, -1.0) : complex<f32>
complex(1.0, -1.0) : f32
pair(-1.0, -1.0) : complex<f32>
The idea with pair would be if we want to support a generalization of ComplexAttr, but without a need for a pair of values in a different context, this would not make much sense.
For complex values inside DenseElementsAttr we would still omit the type, because the type of elements can be inferred from the type of DenseElementsAttr.
Edit: I just realized that even with the keyword complex in front of the parenthesis, we would have difficulty with distinguishing it from a type attribute with type complex<f32>. We would need to look ahead whether the complex keyword is followed by a ‘(’ or a ‘<’. Suggestions for different syntax is welcome
Details on the specification for ComplexAttr:
ComplexAttr could be specified as a pair of attributes, with constraints on which attributes we allow as first and second element (custom Verifier). As StorageClass, it would be easiest to use std::pair<Attribute, Attribute>, because std::pair already has DenseMapInfo support. If we were to use std::complex<Attribute>, we would have to add DenseMapInfo support for it. With extra class definitions we could still add a real() and imag() method to ComplexAttr class.
Once we have ComplexAttr, we can support complex constants in the standard dialect and in the LLVMIR dialect. For the LLVMIR dialect, the ModuleTranslation would have to be changed to add limited support for struct type (i.e. only supporting struct type if the attribute is a ComplexAttr). I already have a prototype where this is working end-to-end with lowering tf.Reciprocal (i.e. 1 / x) for complex types.