RFC: Extending Declarative Assembly Format to support order-independent variadic segments

I propose an idea to extend the Declarative Assembly Format to support order-independent Variadic Segments for operands.

The Problem

This is regarding the ordering of clauses in the OpenMP and OpenACC dialects. Currently, the operations are defined using assembly format in OpenACC Dialect, which imposes a strict ordering on the clauses for an operation. For example, acc.data.

In the OpenMP Dialect, there are custom parsers for operations like omp.parallel and omp.wsloop allowing them to parse order-independent clauses. The problem with this approach is that the parsers for such constructs are huge. Moreover, a lot of such code is/will be duplicated in many directives as they have common clauses.

An interim solution to get rid of code duplication is to have a common function parseClauses which can be leveraged by other directives. This code is under review - D110903. However, the size of this function is huge, covering around 300 lines for 18 clauses.

A better approach would be to work on this using TableGen similar to what was done in D82982. These methods force the dialect design to fit with the higher abstracted language (Fortran, C/C++).

Proposed Solution

Extending the existing Declarative Assembly Format with an oilist element (Order Independent List) to support optional order-independent variadic segments for operations. This will allow OpenACC and OpenMP Dialects to have similar and relaxed requirements while encouraging the use of Declarative Assembly Format and avoiding code duplication. This will also be beneficial to frontends as they will not have to worry about the ordering and this will allow them to have similar design for both OpenMP and OpenACC.

Syntax for the oilist element:

// parses the following grammar
// op-list ::= op op-list | empty
// op ::= clause1 | clause2 | clause3
oilist( clause1 | clause2 | clause3 )

For example

// op ::= `opname` `keyword` clause-list
// clause-list ::= clause clause-list | empty
// clause ::= `private` op-and-type-list
//          | `firstprivate` op-and-type-list
//          | `reduction` reduction-op-list
assemblyFormat = [{
  `keyword` oilist(
    `private` custom<OpAndTypeList>(...) |
    `firstprivate` custom<OpAndTypeList>(...) |
    `reduction` custom<ReductionOpList>(...)
  )
}]

Any thoughts, feedback, ideas or suggestions are most welcome.

2 Likes

This seems like a dictionary attribute but with fixed set of keywords and no colon or comma seperator?

Hi Jacques, thanks for the reply. I think yes, except that I think attributes only allow constant values. I am not sure about this.

I tried working on this today and I could generate parser and printer for a grammar with minor modification. This is different from attribute dictionary in the way that it works for both attributes and operands. I was wondering if having this is something that the MLIR team would be interested in. The code is not review-ready because it doesn’t have proper tests (and verifier(?)), but I wanted the opinion of MLIR team before spending more time on this.

clause-list := clause clause-list | empty
clause := `keyword` clause1 | `otherKeyword` clause2

let assemblyFormat = [{
  oilist(`keyword` clause1 | `otherkeyword` clause2)
}];

If clause1 is empty, a UnitAttr is created and parsed for keyword. Same for clause2.

Parser

String keyword;
while(succeeded(parseOptionalKeyword(keyword)) {
  if(keyword == "keyword") {
    // parser for clause1
  }
  if(keyword == "otherkeyword") {
    // parser for clause2
  }
}

Printer

p << "keyword";
// printer for clause1
p << "otherkeyword";
// printer for clause2

+1 from me. I think given your use case, this kind of flexibility in the assembly format makes sense. Directives are (usually) straightforward to implement and I like the idea of having more powerful assembly formats.

You mean $clause1 and $clause2 right?

What is your opinion on going with something closer to the dictionary syntax?

private = %arg0, reduction = %arg1, firstprivate = %arg2

Actually I would look at the oilist primitive under this angle: can it be used to implement such syntax?
In particular what’s not clear to me is how to handle the separator here?

Right, I forgot these were variadic.

No, I actually meant any acceptable assembly format syntax. For example, this works too

oilist(`private` `(` $arg0 `:` type($arg0) `)` 
     | `nowait` 
     | `reduction` custom<ReductionClause>($arg1, type($arg1))
)

Not exactly, but it can be done without the commas. This should work (even if the args are variadic)

// parses
// private = %arg0 reduction = %arg1 firstprivate = %arg2
oilist( `private` `=` $arg0 `:` type($arg0)
      | `reduction` `=` $arg1 `:` type($arg1)
      | `firstprivate` `=` $arg3 `:` type($arg2)
)

Yes, that would be tricky. The grammar for comma-separated list will be different from the current one and so, the syntax has to be altered to accommodate the separator. Maybe something like

oilist( `separator`, clause1 | clause2 | clause3 )
clause-list = clause `,` clause-list | clause
clause = clause1 | clause2 | clause3