MLIR unexpectedly printing op in generic form

I am using the C API to create two modules: one simply parses the following MLIR:

module  {
  func @swap(%arg0: i1, %arg1: i1) -> (i1, i1) {
    return %arg1, %arg0 : i1, i1
  }
}

And the other tries to build the same MLIR explicitly (using mlirOperationCreate and friends). Both end up creating a module with the following generic form:

"module"() ( {
  "func"() ( {
  ^bb0(%arg0: i1, %arg1: i1):  // no predecessors
    "std.return"(%arg1, %arg0) : (i1, i1) -> ()
  }) {sym_name = "swap", type = (i1, i1) -> (i1, i1)} : () -> ()
  "module_terminator"() : () -> ()
}) : () -> ()

The parsed module prints correctly, but the constructed module prints "std.return" in generic form:

module  {
  func @swap(%arg0: i1, %arg1: i1) -> (i1, i1) {
    "std.return"(%arg1, %arg0) : (i1, i1) -> ()
  }
}

Anyone know why this would be the case, or how best to debug this issue?

This code is exercised in a test in the Swift bindings: MLIRSwift/Module Tests.swift at 79e0bda3d08782402f97c5e9a0e08eeb27ebef0b · GeorgeLyon/MLIRSwift · GitHub

The second looks to me like the standard dialect has not been loaded into the context in the right way. I’d have to dig up the details, but I think the parser does something special to load dialects when an op is encountered, whereas if you are building it yourself, this does not happen.

In any case, I would look at the code you are using to load/register dialects.

Stella is spot-on: you are likely allowing unregistered dialects in your context, the default would error out on the verifier here.

In general your frontend should explicitly load in the context dialects you will emit ahead of IR creations.

Thank you for the prompt response!

It looks like the issue on my end was that I was registering the dialect, but not loading it (I’m not sure what the distinction is here). The reason this wasn’t a problem before is because I happened to always parse before constructing and not switching to a new context in-between.

I believe that registering just creates a mapping between the namespace and a lambda that initializes the dialect. Loading actually performs the initialization. Some dialects have thousands of ops and other heavy weight initialization sequences, and there is a desire for tools like mlir-opt to be able to have both light weight registration of everything and still be able to do on demand initialize/load. For domain specific use when you know what you are building, just always load everything you care about.