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
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
Ports 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(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.