MLIR-based IR for computation graph DSL

Hello, colleagues.

I’m working on MLIR-based IR for our DSL called HIL (High-level Intermediate Language). This DSL is aimed at computation graph describing. Every graph has a number of nodes that are interconnected by channels. Nodes can be of different types (switch, merge, operation, source, sink).

In our project we have our own IR for programs are written in HIL and we want to migrate from that IR to MLIR-based IR. There is a component that prints IR into *.mlir file called Dumper. The *.mlir file is then parsed by my MLIR-based components.

Today the problem is that I have errors when parsing *.mlir file. The current one is:

loc("-":5:7): error: expected non-function type

Here is the erroneous *.mlir file:

hil.model "M" {
  hil.nodetypes {
    hil.nodetype "source" [
    ] => [
      hil.port<"x" "X" 1.00000000 0 0 4294967295>, hil.port<"y" "Y" 1.00000000 0 0 4294967295>
    ]
    hil.nodetype "split" [
      hil.port<"x" "X" 1.00000000 0 0 4294967295>
    ] => [
      hil.port<"x1" "X" 0.50000000 1 0 4294967295>, hil.port<"x2" "X" 0.50000000 1 0 4294967295>
    ]
    hil.nodetype "kernel1" [
      hil.port<"x" "X" 1.00000000 0 0 4294967295>, hil.port<"y" "Y" 0.50000000 0 0 4294967295>
    ] => [
      hil.port<"z" "Z" 0.25000000 1 0 4294967295>, hil.port<"w" "W" 1.00000000 2 0 4294967295>
    ]
    hil.nodetype "kernel2" [
      hil.port<"x" "X" 0.50000000 0 0 4294967295>, hil.port<"w" "W" 0.50000000 0 0 4294967295>
    ] => [
      hil.port<"z" "Z" 0.25000000 1 0 4294967295>
    ]
    hil.nodetype "merge" [
      hil.port<"z1" "Z" 0.50000000 0 0 4294967295>, hil.port<"z2" "Z" 0.50000000 0 0 4294967295>
    ] => [
      hil.port<"z" "Z" 1.00000000 1 0 4294967295>
    ]
    hil.nodetype "sink" [
      hil.port<"z" "Z" 1.00000000 0 0 4294967295>
    ] => [
    ]
  }
  hil.graph "main"{
    hil.chans {
      hil.chan "X" "x1" #hil.bnd<"n2" hil.port<"x1" "X" 0.50000000 1 0 4294967295>> => #hil.bnd<"n3" hil.port<"x" "X" 1.00000000 0 0 4294967295>>
      hil.chan "X" "x2" #hil.bnd<"n2" hil.port<"x2" "X" 0.50000000 1 0 4294967295>> => #hil.bnd<"n4" hil.port<"x" "X" 0.50000000 0 0 4294967295>>
      hil.chan "X" "x" #hil.bnd<"n1" hil.port<"x" "X" 1.00000000 0 0 4294967295>> => #hil.bnd<"n2" hil.port<"x" "X" 1.00000000 0 0 4294967295>>
      hil.chan "Y" "y" #hil.bnd<"n1" hil.port<"y" "Y" 1.00000000 0 0 4294967295>> => #hil.bnd<"n3" hil.port<"y" "Y" 0.50000000 0 0 4294967295>>
      hil.chan "Z" "z1" #hil.bnd<"n3" hil.port<"z" "Z" 0.25000000 1 0 4294967295>> => #hil.bnd<"n5" hil.port<"z1" "Z" 0.50000000 0 0 4294967295>>
      hil.chan "Z" "z2" #hil.bnd<"n4" hil.port<"z" "Z" 0.25000000 1 0 4294967295>> => #hil.bnd<"n5" hil.port<"z2" "Z" 0.50000000 0 0 4294967295>>
      hil.chan "Z" "z" #hil.bnd<"n5" hil.port<"z" "Z" 1.00000000 1 0 4294967295>> => #hil.bnd<"n6" hil.port<"z" "Z" 1.00000000 0 0 4294967295>>
      hil.chan "W" "w" #hil.bnd<"n3" hil.port<"w" "W" 1.00000000 2 0 4294967295>> => #hil.bnd<"n4" hil.port<"w" "W" 0.50000000 0 0 4294967295>>
    }
    hil.nodes {
      hil.node "source" "n1" [
      ] => [
        "x", "y"
      ]
      hil.node "split" "n2" [
        "x"
      ] => [
        "x1", "x2"
      ]
      hil.node "kernel1" "n3" [
        "x1", "y"
      ] => [
        "z1", "w"
      ]
      hil.node "kernel2" "n4" [
        "x2", "w"
      ] => [
        "z2"
      ]
      hil.node "merge" "n5" [
        "z1", "z2"
      ] => [
        "z"
      ]
      hil.node "sink" "n6" [
        "z"
      ] => [
      ]
    }
  }
}

There is something bad in my representation of node types in this file, but I don’t understand what is it.

Here is my MLIR-based IR: mlir.zip - Google Drive

I would really appreciate any advices or guidance in fixing this error.

This is most likely a missing error check in one of your custom parsers. (I haven’t checked source, so perhaps you are using declarative form asm). For these, I’d recommend stepping through the parse. Either insert a couple of logging statements or add break points so you know where it fails (it seems to be in hil.port). Before that, try and reduce the test case as much as possible, try having just a source and sink and then build up.

1 Like

@jpienaar thank you for your response!

I’ve written a minimalistic test program and finally I realized that my NodeType operations (hil.nodetype in the example above) use hil.port attributes. MLIR attributes should begin with ‘#’ symbol. So I’ve added them to my dumper, this error has gone and I’ve received next error:

loc("-":14:38): error: expected ‘<’

My MLIR program is as follows:

hil.model "SourceSink" {
  hil.nodetypes {
    hil.nodetype "source" [
    ] => [
      #hil.port<"x" "X" <1.00000000> 0 0 4294967295>
    ]
    hil.nodetype "sink" [
      #hil.port<"x" "X" <1.00000000> 0 0 4294967295>
    ] => [
    ]
  }
  hil.graph "main"{
    hil.chans {
      hil.chan "Y" "y" #hil.bnd<"n1" #hil.port<"x" "X" <1.00000000> 0 0 4294967295>> == #hil.bnd<"n6" #hil.port<"x" "X" <1.00000000> 0 0 4294967295>>
    }
    hil.nodes {
      hil.node "source" "n1" [
      ] => [
        "y"
      ]
      hil.node "sink" "n6" [
        "y"
      ] => [
      ]
    }
  }
}

The erroneous place is in my composite attribute hil.bnd that contains hil.port attribute.
Here are my TableGen-based sources for the dialect:

Dialect.td:

#ifndef HIL_DIALECT
#define HIL_DIALECT

include "mlir/IR/AttrTypeBase.td"
include "mlir/IR/OpBase.td"

//===----------------------------------------------------------------------===//
// HIL dialect definition.
//===----------------------------------------------------------------------===//

def HIL_Dialect : Dialect {
  let name = "hil";
  let summary = "A hil out-of-tree MLIR dialect.";
  let description = [{
      This dialect is an example of an out-of-tree MLIR dialect designed to
      illustrate the basic setup required to develop MLIR-based tools without
      working inside of the LLVM source tree.
  }];
  let cppNamespace = "::mlir::hil";
}

//===----------------------------------------------------------------------===//
// Base HIL operation definition.
//===----------------------------------------------------------------------===//

class HIL_Op<string mnemonic, list<Trait> traits = []> :
        Op<HIL_Dialect, mnemonic, traits>;

class HIL_Type<string name> : TypeDef<HIL_Dialect, name> { }

class HIL_Attr<string name> : AttrDef<HIL_Dialect, name> { }

#endif // HIL_DIALECT

Attributes.td:

#ifndef HIL_ATTRIBUTES
#define HIL_ATTRIBUTES

include "Dialect.td"

def PortAttr : HIL_Attr<"Port"> {
  let mnemonic = "port";
  let summary = "Port attribute";

  let description = [{
    port arg type
  }];

  let parameters = (ins
    "std::string":$name,
    "std::string":$typeName,
    "double*":$flow,
    "unsigned*":$latency,
    "bool*":$isConst,
    "unsigned*":$value);

  let hasCustomAssemblyFormat = 1;
}

def BindingAttr : HIL_Attr<"Binding"> {

  let mnemonic = "bnd";
  let summary = "Binding attribute";

  let description = [{
    binding attr type
  }];

  let parameters = (ins
    "std::string":$nodeName,
    PortAttr:$port);

  let hasCustomAssemblyFormat = 1;
}

#endif // HIL_ATTRIBUTES

Ops.td:

#ifndef HIL_OPS
#define HIL_OPS

include "Attributes.td"

class HIL_Container<string mnemonic, list<Trait> traits = []> :
  HIL_Op<mnemonic, !listconcat(traits, [
    NoRegionArguments,
    NoTerminator,
    SingleBlock
  ])> {
  let assemblyFormat = "$body attr-dict";
  let regions = (region SizedRegion<1>: $body);
  let extraClassDeclaration = [{
  Block *getBody() {
    Region& region = getOperation()->getRegion(0);
    assert(region.hasOneBlock() && "The body should have one block.");
    return &region.front();
  }
}];
}

def HIL_Model : HIL_Container<"model"> {
  let summary = "Model container.";
  let description = [{
    The `hil.model` operation
  }];
  let arguments = (ins StrAttr:$name);
  let assemblyFormat = "$name $body attr-dict";
}

def HIL_Graph : HIL_Container<"graph", [HasParent<"Model">]> {
  let summary = "Graph container.";
  let description = [{
    The `hil.graph` operation
  }];

  let arguments = (ins StrAttr:$name);
  let assemblyFormat = "$name $body attr-dict";
}

def HIL_NodeTypes : HIL_Container<"nodetypes", [HasParent<"Model">]> {
  let summary = "Nodetypes container.";
  let description = [{
    Container of `hil.nodetype`
  }];
}

def HIL_Chans : HIL_Container<"chans", [HasParent<"Graph">]> {
  let summary = "Chans container.";
  let description = [{
    Container of `hil.chan`
  }];
}

def HIL_Nodes : HIL_Container<"nodes", [HasParent<"Graph">]> {
  let summary = "Nodes container.";
  let description = [{
    Container of `hil.node`
  }];
}

def HIL_NodeType : HIL_Op<"nodetype", [HasParent<"NodeTypes">]> {
  let summary = "Node type.";
  let description = [{
    The `hil.nodetype` operation for defining types of nodes
  }];

  let arguments = (ins
    StrAttr:$name,
    TypedArrayAttrBase<PortAttr, "input ports">:$commandArguments,
    TypedArrayAttrBase<PortAttr, "output ports">:$commandResults);
  let assemblyFormat = [{
    $name $commandArguments `=``>` $commandResults attr-dict
  }];
}

def HIL_Node : HIL_Op<"node", [HasParent<"Nodes">]> {
  let summary = "Node.";
  let description = [{
    The `hil.nodetype` operation for defining types of nodes
  }];

  let arguments = (ins
    StrAttr:$nodeTypeName,
    StrAttr:$name,
    StrArrayAttr:$commandArguments,
    StrArrayAttr:$commandResults);
  let assemblyFormat = [{
    $nodeTypeName $name $commandArguments `=``>` $commandResults attr-dict
  }];
}

def HIL_Chan : HIL_Op<"chan", [HasParent<"Chans">]> {
  let summary = "Channel.";
  let description = [{
    The `hil.nodetype` operation for defining types of nodes
  }];

  let arguments = (ins
    StrAttr:$typeName,
    StrAttr:$varName,
    BindingAttr:$nodeFrom,
    BindingAttr:$nodeTo);
  let assemblyFormat = [{
    $typeName $varName $nodeFrom `=``=` $nodeTo attr-dict
  }];
}

#endif // HIL_OPS