summaryrefslogtreecommitdiff
path: root/flang/docs/OpenMP-semantics.md
blob: 579c40692ba4b4f4cfd72fbf594bd15e7ec30c05 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
<!--===- 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
* requires

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
* `target data` 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>
  <tr>
   <td>use_device_ptr
   </td>
   <td>Yes
   </td>
   <td>New Symbol
   </td>
   <td>OmpUseDevicePtr
   </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.

However, there are some exceptions for this, Pointers that appear in a
use_device_ptr clause are privatized and the device pointers to the
corresponding list items in the device data environment are assigned into the
private versions so it is best to follow the representation for privatised
variables i.e represent them with a new Symbol and `OmpUseDevicePtr` flag.

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