Previously, we discussed type inference here:
As a result, we added an optional hook operations can implement to enable inferring result types. I’m working on a DSL embedded in Swift, and have recently been trying to improve the ergonomics of literals, which are effectively context-free “constant” operations. I’m wondering whether we can use an approach similar to result type inference to make literal MLIR values simpler.
For a concrete example, let’s assume we are trying to generate MLIR for the statement foo & 42
, in which foo
is an MLIR value, &
represents the comb.and
operation (from circt), and 42
is an integer literal. comb.and
requires that all operands are of the same type, so we can infer the type of 42
to be whatever the type of foo
is. As an additional complication, if we were to create an rtl.constant
operation we would need to specify which block to add that operation to, which is also implied by foo
.
The way I handle this currently, is I have a sum type (called Port
for reasons) which can be either an MlirValue
or a literal. The Swift code that creates the comb.and
operation is then responsible for taking a sequence of Port
s and and promoting literals to their respective types. This works OK, but because I’m recreating some of the semantics of the comb.and
operation in Swift, this creates a considerable maintainability burden and other language bindings will have to write their own versions of this logic. For operations like comb.and
this could even be done automatically because I believe it has a trait saying all its operands must be the same type.
I’m not sure if there is a way to handle this in the MLIR infrastructure, but if we could make it work I think it would be valuable. Specifically, we could introduce a “value or literal” type (and support some blessed set of literals; integers and strings come to mind) and some mechanism to add an operation to a block with a specified list of value-or-literal operands. The operation would define a hook that takes an argument list of value-or-literal operands and creates a set of constant-like operations followed by an instance of itself (or fails, similar to how result type inference works currently).
Update: A simpler alternative might be to create a (operands: [ValueOrLiteral], results: [Optional<Type>]) -> (operandTypes: [Type], resultTypes: [Type])
hook which can be manually run before creating the operation. Then it would be the responsibility of the bindings to create constant operations for the literal operands, but they at least would not need to write the logic for inferring the type.
Aside: @clattner and @jdd were discussing rtl.array_index
(which indexes into a fixed-size array) and the tension between the fact that the bit width of the index type is clog2(<array size>)
, clog2(1) == 0
, and that the RTL integer type does not support bit widths of zero. One potential solution to this problem is changing the requirement to max(clog2(x), 1)
, but then all code that created array_index
operations would need to be updated. If we allowed operations to define a hook inferring their operand types, we could extend the printed form of MLIR to support literals and be able to write something like %foo = rtl.array_index %array, 1: rtl.array<i1x7>, ?
with the ?
implying that that argument should be treated as a literal.