MLIR CAPI: how to set function argument names %arg0

Hi all,

I’m experimenting with the MLIR C API. Getting traction finally:

module  {
  func @__match(%arg0: !llvm.ptr<i8>, %arg1: !llvm.ptr<i8>) -> i32 {
  }
}

I’d like to rename %arg0 and %arg1 to %str and %pattern:

module  {
  func @__match(%str: !llvm.ptr<i8>, %pattern: !llvm.ptr<i8>) -> i32 {
  }
}

MLIR Reference documentation and tests get you far:

    MlirNamedAttribute funcAttrs[] = {
        mlirNamedAttributeGet(
            mlirIdentifierGet(ctx, mlirStringRefCreateFromCString("type")),
            funcTypeAttr),
        mlirNamedAttributeGet(
            mlirIdentifierGet(ctx, mlirStringRefCreateFromCString("sym_name")),
            funcNameAttr)};

    MlirOperationState funcState =
        mlirOperationStateGet(mlirStringRefCreateFromCString("func"), location);

    mlirOperationStateAddOwnedRegions(&funcState, 1, &funcBodyRegion);
    mlirOperationStateAddAttributes(&funcState, 2, funcAttrs);
    MlirOperation func = mlirOperationCreate(&funcState);

Except there is no clear path to naming arguments.

Is there an API I’m missing? I’m behind on the nuanced design aspects of MLIR. Are dialects used to solve this problem?

regards,
Arun

It depends what is your motivation, do you need to keep argument name because it matters for your system?

In TensorFlow we have this case, function input are looked up by name (the semantics is an unordered dictionary when exporting from Python), and we use argument attributes for this.
There is no convenience in the C API at the moment I think (feel free to send a patch) but you can still do it since it is just a convention: there is a special attribute on the function arg_attrs that must be an ArrayAttr, the size must match the number of arguments, and each entry in the array is a Dictionary of attributes for the an argument.

So you can have something that will look like this in the printer:

  func @__match(%arg0: !llvm.ptr<i8> { name = "str" }, %arg1: !llvm.ptr<i8> { name = "pattern" }) -> i32 {
1 Like

Hi @mehdi_amini et al.

I’ve applied the arg_attrs attribute as suggested. I noticed arg_attrs is included suffixed with attributes:

  func @__match(%arg0: !llvm.ptr<i8>, %arg1: !llvm.ptr<i8>) -> i32 attributes 
      {arg_attrs = [{name = "str"}, {name = "pattern"}]}

unlike your original (and logical) exemplar:

func @__match(%arg0: !llvm.ptr<i8> { name = "str" }, %arg1: !llvm.ptr<i8> { name = "pattern" }) -> i32 {

Are both definitions equivalent?

I’ve extended the attributes to include an additional array attribute:

    MlirAttribute funcArgAttrs = mlirArrayAttrGet(ctx, arity, funcValueAttr);
    MlirNamedAttribute funcAttrs[] = {
        mlirNamedAttributeGet(
            mlirIdentifierGet(ctx, mlirStringRefCreateFromCString("type")),
            funcTypeAttr),
        mlirNamedAttributeGet(
            mlirIdentifierGet(ctx, mlirStringRefCreateFromCString("sym_name")),
            funcNameAttr),
        mlirNamedAttributeGet(
            mlirIdentifierGet(ctx, mlirStringRefCreateFromCString("arg_attrs")),
            funcArgAttrs)};

running the commands below lowers MLIR (thanks @ftynse):

mlir-opt -convert-std-to-llvm 1.mlir > 2.mlir
mlir-translate -mlir-to-llvmir 2.mlir

but the arg_attrs are not lowered:

define i32 @__match(i8* %0, i8* %1)  {

LLVM C API allows for argument naming:

define i32 @__match(i8* %"$str", i8* %"$pattern") {

Has optimisation taken out argument names here?

regards,
Arun

Can you provide a reproducer?

I have to tweak the “name” key because it won’t pass the verifier for me but otherwise it works as expected:

$ echo 'func private @__match(%arg0: !llvm.ptr<i8>, %arg1: !llvm.ptr<i8>) -> i32 attributes  {arg_attrs = [{llvm.name = "str"}, {llvm.name = "pattern"}]}' | bin/mlir-opt 
  func private @__match(!llvm.ptr<i8> {llvm.name = "str"}, !llvm.ptr<i8> {llvm.name = "pattern"}) -> i32

MLIR does not have name for SSA values (even in LLVM this is a debug feature that is optional: for example clang turns this entirely off in release mode for performance reasons), so while you can add attributes on function parameters, they is not standard way to map this to LLVM SSA value names.

1 Like

See the reproducer below:

module  {
  func private @__match(%arg0: !llvm.ptr<i8>, %arg1: !llvm.ptr<i8>) -> i32 attributes {arg_attrs = [{llvm.name = "str"}, {llvm.name = "pattern"}]} {
    %0 = "std.constant"() {value = 1 : i32} : () -> i32
    "std.return"(%0) : (i32) -> ()
  }
}

I’ve ran the reproducer with mlir-opt (without options) still see the same output:

module  {
  func private @__match(%arg0: !llvm.ptr<i8>, %arg1: !llvm.ptr<i8>) -> i32 attributes {arg_attrs = [{llvm.name = "str"}, {llvm.name = "pattern"}]} {
    %c1_i32 = constant 1 : i32
    return %c1_i32 : i32
  }
}

I suspect it’s my version of llvm now if you’re seeing the expected output:

$ mlir-opt --version
Homebrew LLVM version 12.0.1
Optimized build.
Default target: x86_64-apple-darwin20.6.0
Host CPU: skylake

I’ve learned a lot @mehdi_amini appreciate your diligence and support.

regards,
Arun

Oh right: the internal organization of operand attributes for functions changed not that long ago, that’s why.

You can likely see it by writing the expected output:

module  {
  func private @__match(%arg0: !llvm.ptr<i8> {llvm.name = "str"}, %arg1: !llvm.ptr<i8> {llvm.name = "pattern"}) -> i32 {
    %c1_i32 = constant 1 : i32
    return %c1_i32 : i32
  }
}

And give it to your mlir-opt with the --mlir-print-op-generic option, currently it’ll show:

"module"() ( {
  "func"() ( {
  ^bb0(%arg0: !llvm.ptr<i8>, %arg1: !llvm.ptr<i8>):  // no predecessors
    %0 = "std.constant"() {value = 1 : i32} : () -> i32
    "std.return"(%0) : (i32) -> ()
  }) {arg_attrs = [{llvm.name = "str"}, {llvm.name = "pattern"}], sym_name = "__match", sym_visibility = "private", type = (!llvm.ptr<i8>, !llvm.ptr<i8>) -> i32} : () -> ()
}) : () -> ()
1 Like