Type Inference in RTL

This may be a more general MLIR question, but I’m wondering if there is a way to do type inference on RTL operations as they are built.

A simple example is rtl.concat, whose result seems to be the sum of the bit-widths of its operands. Is there a way I can build an rtl.concat in with just its operands and have it infer its result type? I’m currently using MlirOperationState and mlirOperationCreate from the C bindings.

There are traits that can be used in ODS for type inference along the lines you are describing: https://mlir.llvm.org/docs/OpDefinitions/#type-inference. As far as I understand, these help ODS generate C++ builders that can do some type inference and ultimately construct the appropriate OperationState.

Since you’re talking about building ops through the C API with OperationState directly, I don’t know how much this is going to help. For example, if we put a trait on rtl.concat, maybe you could query for the trait and use it for type inference on your end similar to how the ODS generated C++ builder would?

So it sounds like one option is to recreate the semantics of various ODS traits like AllTypesMatch. This seems like it would work OK, though this would involve 1 implementation per host language which would be exacerbated as the number of these traits grows. This still won’t help with more complex relationships like the result of rtl.concat being the sum of the bit widths of the operands. I don’t know if it is possible, but maybe we can keep track of whether OperationState has explicitly specified operands, results, etc. and it if hasn’t we can run some sort of type inference to determine that value. The current state of the world is a little fragile: for instance, the rtl.concat example will assert that you have the wrong number of result types if you don’t specify one.

FWIW I agree it isn’t great to have to re-create functionality in each new language binding, but I don’t see a simple way to get the kind of type inference you want if you are building ops through the C API. The OperationState interface is generic, explicit, and low-level, so it makes sense to me that this is what the C API accepts.

Wait, I hadn’t seen this. I’m currently doing a lot of this in the FIRRTL dialect:

def PadPrimOp : PrimOp<"pad"> {
  let arguments = (ins IntType:$input, I32Attr:$amount);
  let results = (outs IntType:$result);

  let assemblyFormat = [{
    $input `,` $amount attr-dict `:` functional-type($input, $result)
  }];

and have a bespoke way to compute and verify that the result type correctly matches the type required by the input types and attributes. Are you saying there is a way to achieve:

  let assemblyFormat = [{
    $input `,` $amount attr-dict `:` type($input)
  }];

By implementing a “result type calculation” hook? Such a thing would be a HUGE improvement to the FIRRTL dialect. Are there any examples of this?

-Chris

I was thinking about using TypesMatchWith, which may help for some cases. It can look at an operand or an attribute and return the result type. Now that I’m looking more closely, I don’t think there is a way to provide a hook that can look at multiple operands at once. For example, the std.create_complex op uses TypesMatchWith on each operand separately: https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/Dialect/StandardOps/IR/Ops.td#L1181-L1189.

It looks like all you can use as the argument to the hook is a single named operand, result, or attribute. I don’t think there is a way to support something like concat or pad where the result type is a function of multiple operands and attributes.

I do think TypesMatchWith can help in some FIRRTL ops. Most binary ops can’t use SameOperandsAndResultType or TypesMatchWith because of how FIRRTL defines the result widths of primitive ops. But for things like unary ops, it should be possible to use TypesMatchWith and drop the result type from the assembly format.

Agreed. I think the right thing is to provide a new builtin trait that handles this. It would have a number of effects:

  1. It would mark the result type as being “handled” in the generated asmprinter/parser, so it wouldn’t be explicitly printed (unless explicitly asked for).

  2. It would be checked by the verification hook implicitly.

  3. It would suppress the result types in the implicitly generated build() methods.

such a feature would be a huge improvement to dialects like FIRRTL, as well as many other common cases.

I’ll add, that it would be very nice to be able to handle variadic arguments too.

FYI I filed an LLVM feature bug to track this.

I can’t comment on the bug yet, but it would be great to add to the list of requirements that we can call this hook from mlirOperationCreate in the C bindings (this may be trivial but still good to note). We can also create a new variant (i.e. mlirOperationCreateInferringResultTypes).