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
- 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.
- 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.