[RFC] Compound constructs in OpenMP

Summary: Currently clang defines individual AST nodes for combined directives, e.g. OMPParallelForDirective for a combination target parallel for. At the moment, the OpenMP spec defines approx. 35 combinations (that apply to C/C++), but the upcoming OpenMP standard will add several hundred more. Having separate nodes for each one of them seems untenable.

Background
The OpenMP standard defines a certain set of directives (used most commonly through #pragma omp somedirective). Some of these directives commonly occur together, and so the spec allows certain combinations to be given in a single line[1]. For example

#pragma omp parallel
#pragma omp for
for (...)
  ...

can be shortened to

#pragma omp parallel for
for (...)
   ...

The current OpenMP spec defines about 50 of such combinations (some of then are Fortran-specific). For those applicable to C/C++, clang defines an individual class for the AST node representing the corresponding combination. There are also multiple places where there are functions dedicated to handling such combinations, e.g. git grep ParallelFor -- clang/include returns (just in SemaOpenMP.h):

StmtResult ActOnOpenMPParallelForDirective(
StmtResult ActOnOpenMPParallelForSimdDirective(
StmtResult ActOnOpenMPTargetParallelForDirective(
StmtResult ActOnOpenMPDistributeParallelForDirective(
StmtResult ActOnOpenMPDistributeParallelForSimdDirective(
StmtResult ActOnOpenMPTargetParallelForSimdDirective(
StmtResult ActOnOpenMPTeamsDistributeParallelForSimdDirective(
StmtResult ActOnOpenMPTeamsDistributeParallelForDirective(
StmtResult ActOnOpenMPTargetTeamsDistributeParallelForDirective(
StmtResult ActOnOpenMPTargetTeamsDistributeParallelForSimdDirective(

Problem
The upcoming OpenMP standard will add (almost?) all possible combinations of leaf constructs[2], resulting in over 600 (IIRC) combinations. Continuing with the current approach will result in a lot of pain, both in the initial development, and in the maintenance.

More details
In addition to having directives, OpenMP allows additional clauses to be specified on almost every directive, they can be thought of as additional parameters as to what effect the directive needs to have. In particular, some of them determine how specific objects need to be shared (or not shared) between threads. The standard defines the complete set of clauses, and each directive specifies the subset of the clauses it allows.

I’ve been working on moving away from referring to individual combinations in flang and clang to handling the combinations more generally. In flang were able to break up each combination into leaf constructs, and process the leaf constructs individually during lowering of the flang AST to MLIR. What makes it less than trivial is that when combining directives, the clauses that would be added to the individual directives are simply listed all together on the combined directive. In order to break the combination up, the clauses need to be reassigned back to where they would have come from in the imagined pre-combination form. The OpenMP spec does have a section on how that needs to be done in cases where they may be ambiguities.

I’m trying to implement this sort of a break-up in clang as well, but things are more complicated. In flang we did it during what would be ā€œcodegenā€ in clang, but flang’s AST does not define individual nodes for each combination of directives. Doing the split in clang’s codegen would still leave the question of the AST nodes. Additionally, prior to template instantiation, some required information is not yet present (e.g. is x same as this->x?).

Ideas

  1. Change ParseOpenMP/SemaOpenMP to create AST nodes for individual leaf directives, with proper clauses attached to each leaf.

Clang’s OpenMP parser does a lot of work (like creating capture regions for future outlining, keeping track of data sharing attributes, etc) while creating AST nodes for the clauses. Some of that work depends on what combined directive it’s dealing with (e.g. capture regions). This dependence would need to be changed in such a way as to avoid mentioning specific combinations, and instead do something that would still work once additional combinations are allowed.

The problem here is that this break-up cannot be done in a template, before the variable references are resolved to specific declarations.

  1. Parse whole combinations, but create a catch-all node: OpenMPCombinedConstruct, and store the specifics of the actual combination as a member. Append all clauses to it. Break it up as soon as all necessary information is present.

For non-template functions, this could be a short-lived temporary state, for templates it could be how they are represented until instantiation time.

Questions
I’d like to get some feedback from people more experienced with clang. The goal here is to change the current code in such a way that adding more combined directives will require minimal (ideally none) changes.

Footnotes:
[1] The OpenMP spec defines ā€œcombinedā€ and ā€œcompositeā€ directives. Composite directives are those that together have a different meaning than if they were specified separately. Combined directives are those where the meaning is the same. Directives that are not made of two or more other directives are ā€œleafā€ directives.
[2] This only applies to combined directives, i.e. each such combination would be equivalent to having these directives given in adjacent lines.

Vote for option 2

I agree. I don’t know much of OMP, but in implementing OpenACC/looking at OMP, the combined constructs are something that I could forsee getting out of hand. Having a CompundStmt-like AST node to represent combined constructs seems to be the easiest way forward.

I think this ends up being a pretty sizable refactor, and requires that we change some of the interfaces to be better aware of this being the case (that is, you cannot simply check ā€˜is this a parallel construct’ via isa/etc), but IMO, ends up being worth it in the long run.

I don’t see any other action on this RFC, but I presume work has started in this direction?

1 Like

The work has started, yes, and it’s still in progress. I haven’t posted any RFCs yet because I wanted to better understand what needs to be changed to make it work.

I have a prototype that does the leaf-wise[1] AST construction with a minimal[2] amount of correctness testing. I’m cleaning it up right now, and I was thinking about posting it (as a WIP) to show what things look like.

[1] It’s actually either leafs or composite constructs, where ā€œcompositeā€ in OpenMP means that two or more leaf constructs apply to a loop. These are generally considered units on their own (i.e. not replaceable with a series of leaf constructs).
[2] Think ε > 0.

1 Like