| <!--===- docs/OpenMP-semantics.md |
| |
| Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| See https://llvm.org/LICENSE.txt for license information. |
| SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| |
| --> |
| |
| # OpenMP Semantic Analysis |
| |
| ```eval_rst |
| .. contents:: |
| :local: |
| ``` |
| |
| ## OpenMP for F18 |
| |
| 1. Define and document the parse tree representation for |
| * Directives (listed below) |
| * Clauses (listed below) |
| * Documentation |
| 1. All the directives and clauses need source provenance for messages |
| 1. Define and document how an OpenMP directive in the parse tree |
| will be represented as the parent of the statement(s) |
| to which the directive applies. |
| The parser itself will not be able to construct this representation; |
| there will be subsequent passes that do so |
| just like for example _do-stmt_ and _do-construct_. |
| 1. Define and document the symbol table extensions |
| 1. Define and document the module file extensions |
| |
| |
| ### Directives |
| |
| OpenMP divides directives into three categories as follows. |
| The directives that are in the same categories share some characteristics. |
| |
| |
| |
| #### Declarative directives |
| |
| An OpenMP directive may only be placed in a declarative context. |
| A declarative directive results in one or more declarations only; |
| it is not associated with the immediate execution of any user code. |
| |
| List of existing ones: |
| * declare simd |
| * declare target |
| * threadprivate |
| * declare reduction |
| |
| There is a parser node for each of these directives and |
| the parser node saves information associated with the directive, |
| for example, |
| the name of the procedure-name in the `declare simd` directive. |
| |
| Each parse tree node keeps source provenance, |
| one for the directive name itself and |
| one for the entire directive starting from the directive name. |
| |
| A top-level class, `OpenMPDeclarativeConstruct`, |
| holds all four of the node types as discriminated unions |
| along with the source provenance for the entire directive |
| starting from `!$OMP`. |
| |
| In `parser-tree.h`, |
| `OpenMPDeclarativeConstruct` is part |
| of the `SpecificationConstruct` and `SpecificationPart` |
| in F18 because |
| a declarative directive can only be placed in the specification part |
| of a Fortran program. |
| |
| All the `Names` or `Designators` associated |
| with the declarative directive will be resolved in later phases. |
| |
| #### Executable directives |
| |
| An OpenMP directive that is **not** declarative. |
| That is, it may only be placed in an executable context. |
| It contains stand-alone directives and constructs |
| that are associated with code blocks. |
| The stand-alone directive is described in the next section. |
| |
| The constructs associated with code blocks listed below |
| share a similar structure: |
| _Begin Directive_, _Clause List_, _Code Block_, _End Directive_. |
| The _End Directive_ is optional for constructs |
| like Loop-associated constructs. |
| |
| * Block-associated constructs (`OpenMPBlockConstruct`) |
| * Loop-associated constructs (`OpenMPLoopConstruct`) |
| * Atomic construct (`OpenMPAtomicConstruct`) |
| * Sections Construct (`OpenMPSectionsConstruct`, |
| contains Sections/Parallel Sections constructs) |
| * Critical Construct (`OpenMPCriticalConstruct`) |
| |
| A top-level class, `OpenMPConstruct`, |
| includes stand-alone directive and constructs |
| listed above as discriminated unions. |
| |
| In the `parse-tree.h`, `OpenMPConstruct` is an element |
| of the `ExecutableConstruct`. |
| |
| All the `Names` or `Designators` associated |
| with the executable directive will be resolved in Semantic Analysis. |
| |
| When the backtracking parser can not identify the associated code blocks, |
| the parse tree will be rewritten later in the Semantics Analysis. |
| |
| #### Stand-alone Directives |
| |
| An OpenMP executable directive that has no associated user code |
| except for that which appears in clauses in the directive. |
| |
| List of existing ones: |
| * taskyield |
| * barrier |
| * taskwait |
| * target enter data |
| * target exit data |
| * target update |
| * ordered |
| * flush |
| * cancel |
| * cancellation point |
| |
| A higher-level class is created for each category |
| which contains directives listed above that share a similar structure: |
| * OpenMPSimpleStandaloneConstruct |
| (taskyield, barrier, taskwait, |
| target enter/exit data, target update, ordered) |
| * OpenMPFlushConstruct |
| * OpenMPCancelConstruct |
| * OpenMPCancellationPointConstruct |
| |
| A top-level class, `OpenMPStandaloneConstruct`, |
| holds all four of the node types as discriminated unions |
| along with the source provenance for the entire directive. |
| Also, each parser node for the stand-alone directive saves |
| the source provenance for the directive name itself. |
| |
| ### Clauses |
| |
| Each clause represented as a distinct class in `parse-tree.h`. |
| A top-level class, `OmpClause`, |
| includes all the clauses as discriminated unions. |
| The parser node for `OmpClause` saves the source provenance |
| for the entire clause. |
| |
| All the `Names` or `Designators` associated |
| with the clauses will be resolved in Semantic Analysis. |
| |
| Note that the backtracking parser will not validate |
| that the list of clauses associated |
| with a directive is valid other than to make sure they are well-formed. |
| In particular, |
| the parser does not check that |
| the association between directive and clauses is correct |
| nor check that the values in the directives or clauses are correct. |
| These checks are deferred to later phases of semantics to simplify the parser. |
| |
| ## Symbol Table Extensions for OpenMP |
| |
| Name resolution can be impacted by the OpenMP code. |
| In addition to the regular steps to do the name resolution, |
| new scopes and symbols may need to be created |
| when encountering certain OpenMP constructs. |
| This section describes the extensions |
| for OpenMP during Symbol Table construction. |
| |
| OpenMP uses the fork-join model of parallel execution and |
| all OpenMP threads have access to |
| a _shared_ memory place to store and retrieve variables |
| but each thread can also have access to |
| its _threadprivate_ memory that must not be accessed by other threads. |
| |
| For the directives and clauses that can control the data environments, |
| compiler needs to determine two kinds of _access_ |
| to variables used in the directive’s associated structured block: |
| **shared** and **private**. |
| Each variable referenced in the structured block |
| has an original variable immediately outside of the OpenMP constructs. |
| Reference to a shared variable in the structured block |
| becomes a reference to the original variable. |
| However, each private variable referenced in the structured block, |
| a new version of the original variable (of the same type and size) |
| will be created in the threadprivate memory. |
| |
| There are exceptions that directives/clauses |
| need to create a new `Symbol` without creating a new `Scope`, |
| but in general, |
| when encountering each of the data environment controlling directives |
| (discussed in the following sections), |
| a new `Scope` will be created. |
| For each private variable referenced in the structured block, |
| a new `Symbol` is created out of the original variable |
| and the new `Symbol` is associated |
| with original variable’s `Symbol` via `HostAssocDetails`. |
| A new set of OpenMP specific flags are added |
| into `Flag` class in `symbol.h` to indicate the types of |
| associations, |
| data-sharing attributes, |
| and data-mapping attributes |
| in the OpenMP data environments. |
| |
| ### New Symbol without new Scope |
| |
| OpenMP directives that require new `Symbol` to be created |
| but not new `Scope` are listed in the following table |
| in terms of the Symbol Table extensions for OpenMP: |
| |
| <table> |
| <tr> |
| <td rowspan="2" colspan="2" >Directives/Clauses |
| </td> |
| <td rowspan="2" >Create New |
| <p> |
| Symbol |
| <p> |
| w/ |
| </td> |
| <td colspan="2" >Add Flag |
| </td> |
| </tr> |
| <tr> |
| <td>on Symbol of |
| </td> |
| <td>Flag |
| </td> |
| </tr> |
| <tr> |
| <td rowspan="4" >Declarative Directives |
| </td> |
| <td>declare simd [(proc-name)] |
| </td> |
| <td>- |
| </td> |
| <td>The name of the enclosing function, subroutine, or interface body |
| to which it applies, or proc-name |
| </td> |
| <td>OmpDeclareSimd |
| </td> |
| </tr> |
| <tr> |
| <td>declare target |
| </td> |
| <td>- |
| </td> |
| <td>The name of the enclosing function, subroutine, or interface body |
| to which it applies |
| </td> |
| <td>OmpDeclareTarget |
| </td> |
| </tr> |
| <tr> |
| <td>threadprivate(list) |
| </td> |
| <td>- |
| </td> |
| <td>named variables and named common blocks |
| </td> |
| <td>OmpThreadPrivate |
| </td> |
| </tr> |
| <tr> |
| <td>declare reduction |
| </td> |
| <td>* |
| </td> |
| <td>reduction-identifier |
| </td> |
| <td>OmpDeclareReduction |
| </td> |
| </tr> |
| <tr> |
| <td>Stand-alone directives |
| </td> |
| <td>flush |
| </td> |
| <td>- |
| </td> |
| <td>variable, array section or common block name |
| </td> |
| <td>OmpFlushed |
| </td> |
| </tr> |
| <tr> |
| <td colspan="2" >critical [(name)] |
| </td> |
| <td>- |
| </td> |
| <td>name (user-defined identifier) |
| </td> |
| <td>OmpCriticalLock |
| </td> |
| </tr> |
| <tr> |
| <td colspan="2" >if ([ directive-name-modifier :] scalar-logical-expr) |
| </td> |
| <td>- |
| </td> |
| <td>directive-name-modifier |
| </td> |
| <td>OmpIfSpecified |
| </td> |
| </tr> |
| </table> |
| |
| |
| - No Action |
| |
| * Discussed in “Module File Extensions for OpenMP” section |
| |
| |
| ### New Symbol with new Scope |
| |
| For the following OpenMP regions: |
| |
| * `target` regions |
| * `teams` regions |
| * `parallel` regions |
| * `simd` regions |
| * task generating regions (created by `task` or `taskloop` constructs) |
| * worksharing regions |
| (created by `do`, `sections`, `single`, or `workshare` constructs) |
| |
| A new `Scope` will be created |
| when encountering the above OpenMP constructs |
| to ensure the correct data environment during the Code Generation. |
| To determine whether a variable referenced in these regions |
| needs the creation of a new `Symbol`, |
| all the data-sharing attribute rules |
| described in OpenMP Spec [2.15.1] apply during the Name Resolution. |
| The available data-sharing attributes are: |
| **_shared_**, |
| **_private_**, |
| **_linear_**, |
| **_firstprivate_**, |
| and **_lastprivate_**. |
| The attribute is represented as `Flag` in the `Symbol` object. |
| |
| More details are listed in the following table: |
| |
| <table> |
| <tr> |
| <td rowspan="2" >Attribute |
| </td> |
| <td rowspan="2" >Create New Symbol |
| </td> |
| <td colspan="2" >Add Flag |
| </td> |
| </tr> |
| <tr> |
| <td>on Symbol of |
| </td> |
| <td>Flag |
| </td> |
| </tr> |
| <tr> |
| <td>shared |
| </td> |
| <td>No |
| </td> |
| <td>Original variable |
| </td> |
| <td>OmpShared |
| </td> |
| </tr> |
| <tr> |
| <td>private |
| </td> |
| <td>Yes |
| </td> |
| <td>New Symbol |
| </td> |
| <td>OmpPrivate |
| </td> |
| </tr> |
| <tr> |
| <td>linear |
| </td> |
| <td>Yes |
| </td> |
| <td>New Symbol |
| </td> |
| <td>OmpLinear |
| </td> |
| </tr> |
| <tr> |
| <td>firstprivate |
| </td> |
| <td>Yes |
| </td> |
| <td>New Symbol |
| </td> |
| <td>OmpFirstPrivate |
| </td> |
| </tr> |
| <tr> |
| <td>lastprivate |
| </td> |
| <td>Yes |
| </td> |
| <td>New Symbol |
| </td> |
| <td>OmpLastPrivate |
| </td> |
| </tr> |
| </table> |
| |
| To determine the right data-sharing attribute, |
| OpenMP defines that the data-sharing attributes |
| of variables that are referenced in a construct can be |
| _predetermined_, _explicitly determined_, or _implicitly determined_. |
| |
| #### Predetermined data-sharing attributes |
| |
| * Assumed-size arrays are **shared** |
| * The loop iteration variable(s) |
| in the associated _do-loop(s)_ of a |
| _do_, |
| _parallel do_, |
| _taskloop_, |
| or _distributeconstruct_ |
| is (are) **private** |
| * A loop iteration variable |
| for a sequential loop in a _parallel_ or task generating construct |
| is **private** in the innermost such construct that encloses the loop |
| * Implied-do indices and _forall_ indices are **private** |
| * The loop iteration variable in the associated _do-loop_ |
| of a _simd_ construct with just one associated _do-loop_ |
| is **linear** with a linear-step |
| that is the increment of the associated _do-loop_ |
| * The loop iteration variables in the associated _do-loop(s)_ of a _simd_ |
| construct with multiple associated _do-loop(s)_ are **lastprivate** |
| |
| #### Explicitly determined data-sharing attributes |
| |
| Variables with _explicitly determined_ data-sharing attributes are: |
| |
| * Variables are referenced in a given construct |
| * Variables are listed in a data-sharing attribute clause on the construct. |
| |
| The data-sharing attribute clauses are: |
| * _default_ clause |
| (discussed in “Implicitly determined data-sharing attributes”) |
| * _shared_ clause |
| * _private_ clause |
| * _linear_ clause |
| * _firstprivate_ clause |
| * _lastprivate_ clause |
| * _reduction_ clause |
| (new `Symbol` created with the flag `OmpReduction` set) |
| |
| Note that variables with _predetermined_ data-sharing attributes |
| may not be listed (with exceptions) in data-sharing attribute clauses. |
| |
| #### Implicitly determined data-sharing attributes |
| |
| Variables with implicitly determined data-sharing attributes are: |
| |
| * Variables are referenced in a given construct |
| * Variables do not have _predetermined_ data-sharing attributes |
| * Variables are not listed in a data-sharing attribute clause |
| on the construct. |
| |
| Rules for variables with _implicitly determined_ data-sharing attributes: |
| |
| * In a _parallel_ construct, if no _default_ clause is present, |
| these variables are **shared** |
| * In a task generating construct, |
| if no _default_ clause is present, |
| a variable for which the data-sharing attribute |
| is not determined by the rules above |
| and that in the enclosing context is determined |
| to be shared by all implicit tasks |
| bound to the current team is **shared** |
| * In a _target_ construct, |
| variables that are not mapped after applying data-mapping attribute rules |
| (discussed later) are **firstprivate** |
| * In an orphaned task generating construct, |
| if no _default_ clause is present, dummy arguments are **firstprivate** |
| * In a task generating construct, if no _default_ clause is present, |
| a variable for which the data-sharing attribute is not determined |
| by the rules above is **firstprivate** |
| * For constructs other than task generating constructs or _target_ constructs, |
| if no _default_ clause is present, |
| these variables reference the variables with the same names |
| that exist in the enclosing context |
| * In a _parallel_, _teams_, or task generating construct, |
| the data-sharing attributes of these variables are determined |
| by the _default_ clause, if present: |
| * _default(shared)_ |
| clause causes all variables referenced in the construct |
| that have _implicitly determined_ data-sharing attributes |
| to be **shared** |
| * _default(private)_ |
| clause causes all variables referenced in the construct |
| that have _implicitly determined_ data-sharing attributes |
| to be **private** |
| * _default(firstprivate)_ |
| clause causes all variables referenced in the construct |
| that have _implicitly determined_ data-sharing attributes |
| to be **firstprivate** |
| * _default(none)_ |
| clause requires that each variable |
| that is referenced in the construct, |
| and that does not have a _predetermined_ data-sharing attribute, |
| must have its data-sharing attribute _explicitly determined_ |
| by being listed in a data-sharing attribute clause |
| |
| |
| ### Data-mapping Attribute |
| |
| When encountering the _target data_ and _target_ directives, |
| the data-mapping attributes of any variable referenced in a target region |
| will be determined and represented as `Flag` in the `Symbol` object |
| of the variable. |
| No `Symbol` or `Scope` will be created. |
| |
| The basic steps to determine the data-mapping attribute are: |
| |
| 1. If _map_ clause is present, |
| the data-mapping attribute is determined by the _map-type_ |
| on the clause and its corresponding `Flag` are listed below: |
| |
| <table> |
| <tr> |
| <td> |
| data-mapping attribute |
| </td> |
| <td>Flag |
| </td> |
| </tr> |
| <tr> |
| <td>to |
| </td> |
| <td>OmpMapTo |
| </td> |
| </tr> |
| <tr> |
| <td>from |
| </td> |
| <td>OmpMapFrom |
| </td> |
| </tr> |
| <tr> |
| <td>tofrom |
| (default if map-type is not present) |
| </td> |
| <td>OmpMapTo & OmpMapFrom |
| </td> |
| </tr> |
| <tr> |
| <td>alloc |
| </td> |
| <td>OmpMapAlloc |
| </td> |
| </tr> |
| <tr> |
| <td>release |
| </td> |
| <td>OmpMapRelease |
| </td> |
| </tr> |
| <tr> |
| <td>delete |
| </td> |
| <td>OmpMapDelete |
| </td> |
| </tr> |
| </table> |
| |
| 2. Otherwise, the following data-mapping rules apply |
| for variables referenced in a _target_ construct |
| that are _not_ declared in the construct and |
| do not appear in data-sharing attribute or map clauses: |
| * If a variable appears in a _to_ or _link_ clause |
| on a _declare target_ directive then it is treated |
| as if it had appeared in a _map_ clause with a _map-type_ of **tofrom** |
| 3. Otherwise, the following implicit data-mapping attribute rules apply: |
| * If a _defaultmap(tofrom:scalar)_ clause is _not_ present |
| then a scalar variable is not mapped, |
| but instead has an implicit data-sharing attribute of **firstprivate** |
| * If a _defaultmap(tofrom:scalar)_ clause is present |
| then a scalar variable is treated as if it had appeared |
| in a map clause with a map-type of **tofrom** |
| * If a variable is not a scalar |
| then it is treated as if it had appeared in a map clause |
| with a _map-type_ of **tofrom** |
| |
| After the completion of the Name Resolution phase, |
| all the data-sharing or data-mapping attributes marked for the `Symbols` |
| may be used later in the Semantics Analysis and in the Code Generation. |
| |
| ## Module File Extensions for OpenMP |
| |
| After the successful compilation of modules and submodules |
| that may contain the following Declarative Directives, |
| the entire directive starting from `!$OMP` needs to be written out |
| into `.mod` files in their corresponding Specification Part: |
| |
| * _declare simd_ or _declare target_ |
| |
| In the “New Symbol without new Scope” section, |
| we described that when encountering these two declarative directives, |
| new `Flag` will be applied to the Symbol of the name of |
| the enclosing function, subroutine, or interface body to |
| which it applies, or proc-name. |
| This `Flag` should be part of the API information |
| for the given subroutine or function |
| |
| * _declare reduction_ |
| |
| The _reduction-identifier_ in this directive |
| can be use-associated or host-associated. |
| However, it will not act like other Symbols |
| because user may have a reduction name |
| that is the same as a Fortran entity name in the same scope. |
| Therefore a specific data structure needs to be created |
| to save the _reduction-identifier_ information |
| in the Scope and this directive needs to be written into `.mod` files |
| |
| ## Phases of OpenMP Analysis |
| |
| 1. Create the parse tree for OpenMP |
| 1. Add types for directives and clauses |
| 1. Add type(s) that will be used for directives |
| 2. Add type(s) that will be used for clauses |
| 3. Add other types, e.g. wrappers or other containers |
| 4. Use std::variant to encapsulate meaningful types |
| 2. Implemented in the parser for OpenMP (openmp-grammar.h) |
| 2. Create canonical nesting |
| 1. Restructure parse tree to reflect the association |
| of directives and stmts |
| 1. Associate `OpenMPLoopConstruct` |
| with `DoConstruct` and `OpenMPEndLoopDirective` |
| 1. Investigate, and perhaps reuse, |
| the algorithm used to restructure do-loops |
| 2. Add a pass near the code that restructures do-loops; |
| but do not extend the code that handles do-loop for OpenMP; |
| keep this code separate. |
| 3. Report errors that prevent restructuring |
| (e.g. loop directive not followed by loop) |
| We should abort in case of errors |
| because there is no point to perform further checks |
| if it is not a legal OpenMP construct |
| 3. Validate the structured-block |
| 1. Structured-block is a block of executable statements |
| 1. Single entry and single exit |
| 1. Access to the structured block must not be the result of a branch |
| 1. The point of exit cannot be a branch out of the structured block |
| 4. Check that directive and clause combinations are legal |
| 1. Begin and End directive should match |
| 1. Simply check that the clauses are allowed by the directives |
| 1. Write as a separate pass for simplicity and correctness of the parse tree |
| 5. Write parse tree tests |
| 1. At this point, the parse tree should be perfectly formed |
| 1. Write tests that check for correct form and provenance information |
| 1. Write tests for errors that can occur during the restructuring |
| 6. Scope, symbol tables, and name resolution |
| 1. Update the existing code to handle names and scopes introduced by OpenMP |
| 1. Write tests to make sure names are properly implemented |
| 7. Check semantics that is specific to each directive |
| 1. Validate the directive and its clauses |
| 1. Some clause checks require the result of name resolution, |
| i.e. “A list item may appear in a _linear_ or _firstprivate_ clause |
| but not both.” |
| 1. TBD: |
| Validate the nested statement for legality in the scope of the directive |
| 1. Check the nesting of regions [OpenMP 4.5 spec 2.17] |
| 8. Module file utilities |
| 1. Write necessary OpenMP declarative directives to `.mod` files |
| 2. Update the existing code |
| to read available OpenMP directives from the `.mod` files |