Background
OpenMP clauses can be attached to different constructs, but their representation in the spec is generally the same regardless of the construct to which they are attached. In the OpenMP MLIR dialect, the representation of a clause corresponds to a list of arguments and a declarative assembly format string to define their textual representation.
The current problem is that all operations for which a certain clause applies must explicitly define the relevant arguments and assembly format associated to that clause, which leads to redundancy in the dialect and makes it very easy to end up with inconsistent representations for the same clause on different operations. Also, any modifications made to a given clause result in having to search for all instances of it across all operations, which is error-prone.
Proposal
It would be better to have a single source of truth about OpenMP clauses rather than have this information spread and duplicated across multiple OpenMP operations, which should make the dialect simpler to understand and contribute to. To this end, the following stack of PRs has been created:
- [MLIR][OpenMP] Support clause-based representation of operations
- [MLIR][OpenMP] Add
OpenMP_Clause
tablegen definitions - [MLIR][OpenMP] Clause-based OpenMP operation definition
- [Flang][OpenMP] Update flang with changes to the OpenMP dialect
The idea of this patch stack is to make OpenMP clauses independent top level definitions which can then be passed in a template argument list to OpenMP operation definitions, just as it’s already done for traits. OpenMP_Clause
s are passed as a list template argument to OpenMP_Op
s and they can define certain properties, which are joined together in order to make a default initialization for the fields of the same name of the OpenMP operation:
traits
: Optional. They are added to the list of traits of the operation. This is a useful feature for clauses such asmap
, which must always come together with aMapClauseOwningOpInterface
.arguments
: Mandatory. It defines how the clause is represented, through adag(ins)
field.assemblyFormat
: Optional. It should almost always be defined, but there are clauses for which the string representation is always interlinked with the op they appear in, such ascollapse
. This is the declarative definition of the printer / parser for the arguments. How these are combined depends on whether the clause is marked as required or optional.description
: Optional. It’s used to populate aclausesDescription
field, so each operation definition must still define a description itself. That field is intended to be appended to the end of the OpenMP_Op’s description.extraClassDeclaration
: Optional. It can define some C++ code to be added to every OpenMP operation that includes that clause.
In order to give operation definitions fine-grained control over features of a certain clause might need to be inhibited, the OpenMP_Clause
class takes “skipTraits”, “skipArguments”, “skipAssemblyFormat”, “skipDescription” and “skipExtraClassDeclaration” bit template arguments. These are intended to be used very sparingly for cases where some of the clauses might collide in some way otherwise.
For operations where clause-populated fields must be overriden and augmented with further operation-specific information, the clausesArgs
, clausesAssemblyFormat
and clausesExtraClassDeclaration
fields contain what would otherwise be the default values for arguments
, assemblyFormat
and extraClassDeclaration
, respectively.
Example
To illustrate how this change would impact the definition of operations in the OpenMP dialect, below is a simplified version of the current definition of omp.teams
:
def TeamsOp : OpenMP_Op<"teams", [AttrSizedOperandSegments, ...]> {
let summary = "teams construct";
let description = [{
The teams construct defines a...
The optional `num_teams_upper` and `num_teams_lower` specify...
The `allocators_vars` and `allocate_vars` parameters are...
}];
let arguments = (ins Optional<AnyInteger>:$num_teams_lower,
Optional<AnyInteger>:$num_teams_upper,
Variadic<AnyType>:$allocate_vars,
Variadic<AnyType>:$allocators_vars);
let regions = (region AnyRegion:$region);
let builders = [
OpBuilder<(ins CArg<"const TeamsClauseOps &">:$clauses)>
];
let assemblyFormat = [{
oilist(
`num_teams` `(` ( $num_teams_lower^ `:` type($num_teams_lower) )? `to`
$num_teams_upper `:` type($num_teams_upper) `)`
| `allocate` `(`
custom<AllocateAndAllocator>(
$allocate_vars, type($allocate_vars),
$allocators_vars, type($allocators_vars)
) `)`
) $region attr-dict
}];
let hasVerifier = 1;
}
The equivalent representation (resulting in the same outputs from tablegen) following the proposed approach would instead define two OpenMP_Clause
instances, for the num_teams
and allocate
clauses, and an OpenMP_Op
for omp.teams
, as follows:
def OpenMP_NumTeamsClause : OpenMP_Clause<
/*isRequired=*/false, /*skipTraits=*/false, /*skipArguments=*/false,
/*skipAssemblyFormat=*/false, /*skipDescription=*/false,
/*skipExtraClassDeclaration=*/false> {
let arguments = (ins
Optional<AnyInteger>:$num_teams_lower,
Optional<AnyInteger>:$num_teams_upper
);
let assemblyFormat = [{
`num_teams` `(` ( $num_teams_lower^ `:` type($num_teams_lower) )? `to`
$num_teams_upper `:` type($num_teams_upper) `)`
}];
let description = [{
The optional `num_teams_upper` and `num_teams_lower` arguments specify...
}];
}
def OpenMP_AllocateClause : OpenMP_Clause<
/*isRequired=*/false, /*skipTraits=*/false, /*skipArguments=*/false,
/*skipAssemblyFormat=*/false, /*skipDescription=*/false,
/*skipExtraClassDeclaration=*/false> {
let arguments = (ins
Variadic<AnyType>:$allocate_vars,
Variadic<AnyType>:$allocators_vars
);
let assemblyFormat = [{
`allocate` `(`
custom<AllocateAndAllocator>($allocate_vars, type($allocate_vars),
$allocators_vars, type($allocators_vars)) `)`
}];
let description = [{
The `allocators_vars` and `allocate_vars` parameters are...
}];
}
def TeamsOp : OpenMP_Op<"teams", [AttrSizedOperandSegments...], [
OpenMP_NumTeamsClause, OpenMP_AllocateClause
], singleRegion = true> {
let summary = "teams construct";
let description = [{
The teams construct defines a...
}] # clausesDescription;
let builders = [
OpBuilder<(ins CArg<"const TeamsClauseOps &">:$clauses)>
];
let hasVerifier = 1;
}