MLIR documentation is confusing and not as helpful as it could be

Maybe it’s because I don’t understand it or maybe there’s an assumption that I’m going to be doing everything from C++, but it’s incredibly difficult to get a handle on what actual MLIR is supposed to look like. I’m trying to generate MLIR by hand, but the examples I come across in the docs don’t seem to translate to LLVM IR without an error. There’s also pieces of MLIR that I’m seeing that I can’t find documentation for what it is or why it’s there.

Take this section for example: LLVM IR Target - MLIR

The MLIR at the top is:

func @foo(%arg0: i32, %arg1: i64) -> (i32, i64) {
  return %arg0, %arg1 : i32, i64

func @bar() {
  %0 = arith.constant 42 : i32
  %1 = arith.constant 17 : i64
  %2:2 = call @foo(%0, %1) : (i32, i64) -> (i32, i64)
  "use_i32"(%2#0) : (i32) -> ()
  "use_i64"(%2#1) : (i64) -> ()

But trying to convert this to LLVM IR results in an error. Like I said before, I’m not using C++ and not using bindings or anything like that. My goal is to generate MLIR by hand so I’m using a combination of mlir-opt and mlir-translate (I don’t understand why I have to use two different tools for this) to convert MLIR to… the dialect version of MLIR? And then onto LLVM IR.

The commands I’m using are:

  • mlir-opt --convert-complex-to-llvm --convert-linalg-to-llvm --convert-math-to-llvm --convert-memref-to-llvm --convert-openacc-to-llvm --convert-openmp-to-llvm --convert-spirv-to-llvm --convert-std-to-llvm --convert-vector-to-llvm --gpu-to-llvm --llvm-legalize-for-export --std-expand mlir/test.mlir (if I add --convert-async-to-llvm it prints out a stack dump)

  • mlir-translate --mlir-to-llvmir test.mlir
    Using the first command the error I receive is:

mlir/test.mlir:6:8: error: custom op 'arith.constant' is unknown
  %0 = arith.constant 42 : i32

If I try the second command I get:

mlir/test.mlir:2:3: error: custom op 'return' is unknown
  return %arg0, %arg1 : i32, i64

The error from the first command seems to indicate it doesn’t know what arith is and the error from the second command appears to need to have the “dialect’d” form of MLIR converted by the first command.

My questions are as follows:

  1. Why do I need to use two different tools to convert MLIR to MLIR to LLVM IR?
  2. Why does MLIR Language Reference - MLIR not explain what something like "foo_div"() : () -> (f32, i32) does? Why are there quotes around the name? Is that supposed to call a function called foo_div specified elsewhere? If you try to convert that it complains about not knowing what the dialect is. I saw this tf.scramble in that doc too and it seems like it might call out to another dialect?
  3. How are you supposed to assign most types?
!foo = type i32

func @bar(%a: i32) -> !foo {
  return %a : i32

That converts just fine and so does:

!foo = type () -> (i32, i32)

func @bar(%a: !foo) -> !foo {
  return %a : !foo

But how am I supposed to assign to !foo?

!foo = type () -> (i32, i32)

func @bar() -> !foo {
  %a = ???
  return %a : !foo
  1. What does
error: expected operation name in quotes

actually mean and how I figure out what the problem is when I see that?

Any help or tips are much appreciated.

1 Like

I found this Mlir-opt error - #3 by mehdi_amini and maybe it’s because of my version? I’m using LLVM 13.0.1 and it specifically says not to use released versions. I’ll try the latest commit of LLVM and see what that gets me.

Hi rdeusser,

Let me try to answer your questions:

  1. mlir-translate is a command-line tool that allows translating an MLIR module to an external representation. For example, you can convert an MLIR module to LLVM IR such that you can use LLVM to obtain an executable. The everyday use case is to lower your MLIR module to the LLVM dialect ('llvm' Dialect - MLIR) using mlir-opt and then use mlir-translate to get LLVM IR.
  2. “foo_div” is a hypothetical operation in a hypothetical “foo” dialect; that’s the reason why it is not explained in the documentation. I don’t think the quote is necessary anymore but were needed for a user-defined operation. You cannot convert “foo_div” as you probably did not implement the “foo” dialect.
  3. Not sure what you mean by “assign most types”. “%a = i32” means that the Value “%a” is of Type “i32”. “!foo = type i32” is an alias for i32.

I hope this help. I also suggest building MLIR directly from the git repository as it moves fast. See: Getting Started - MLIR

Hi chelini,

Thanks for the response!

  1. That makes sense. I just wish the documentation made that more clear. If I can make a PR to the docs and add something like that just let me know and I’d be happy to do it.
  2. Gotcha. Maybe I just didn’t see it, but I would expect the docs to make that clear when it first appears.
  3. Yeah for basic types it seems pretty self-explanatory, but how do you handle something like this?
!foo = type () -> (i32, i32)

func @bar() -> !foo {
  %a = ???
  return %a : !foo

Also, if you could tell me what this means:

error: expected operation name in quotes

I’d be eternally grateful. :slight_smile:

And by “by hand” it sounds like you mean by generating strings directly rather than using op builder and creating the ops in memory. I’d recommend creating the ops using the OpBuilder methods directly instead of generating strings. If you do want to generate strings I’d suggest looking at the tests directory (full of .mlir files showing syntax).

Re: foo. foo is used in the traditional CS sense of a placeholder.

In the same way, you’d need a op that produces a result of that type. Now there may be no such op that returns that type upstream, so you’d need to define such an op (and given you want to codegen it, you’d need at legalization to LLVM dialect). The tutorial goes over this in chapter 7 but go through the entire tutorial. Starting of with ops returning function function types is going to be a bit involved without laying the foundations.

This is almost definitely as you are trying to use an unregistered operation. If an operation is not registered then it can only use the verbose syntax (see Chapter 2 of tutorial).

Sure, so the documentation is in one of two places, almost all are in main repo while some rare cases in mlir-www. For the former the review process follows the traditional LLVM dev process using Code Reviews with Phabricator — LLVM 16.0.0git documentation while the latter is GitHub PRs.

The quotes always had the same meaning: they distinguish between an operation that is printed/parsed in the generic format or using a custom parser. The MLIR parser will dispatch to a custom printer only when it sees the operation name without quotes.

I don’t think so: you should get something like “mlir/test.mlir:2:3: error: custom op ‘return’ is unknown” in this case.
This error is emitted instead when the parser is trying to parse an operation but it encounters a token that isn’t a valid identifier/keyword or a quoted string (so it can’t be parsing something unregistered I believe), here is the code:

if ( || nameTok.isKeyword())
    op = parseCustomOperation(resultIDs);
  else if (
    op = parseGenericOperation();
    return emitError("expected operation name in quotes");

There is no expectation that every valid MLIR file translates to LLVM IR. For example TensorFlow graphs can be represented in MLIR and don’t have a direct path to LLVM IR. Only the LLVM dialect (and extensions like OpenMP/OpenACC/NVPTX/…) can be exported to LLVM IR.
For most dialect (Linalg, Affine, Scf, Arith, …) they need to be converted to the LLVM dialect before export. Note that tools like mlir-opt and mlir-translate are intended for testing/development/debugging purpose, they exercise specific C++ component. mlir-opt will focus on executing a sequence of MLIR passes: these are by nature working with MLIR-in / MLIR-out.
mlir-translate is really for import/export into MLIR.
The tutorial Jacques referred to (and mentioned at the top of the getting started page) should walk you step by step through this: otherwise always happy to have a contribution :slight_smile:

1 Like