1 //===-- lib/Semantics/check-omp-structure.cpp -----------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "check-omp-structure.h"
10 #include "flang/Parser/parse-tree.h"
11 #include "flang/Semantics/tools.h"
12 #include <unordered_map>
13 
14 namespace Fortran::semantics {
15 
16 std::string OmpStructureChecker::ContextDirectiveAsFortran() {
17   auto dir{EnumToString(GetContext().directive)};
18   std::replace(dir.begin(), dir.end(), '_', ' ');
19   return dir;
20 }
21 
22 void OmpStructureChecker::SayNotMatching(
23     const parser::CharBlock &beginSource, const parser::CharBlock &endSource) {
24   context_
25       .Say(endSource, "Unmatched %s directive"_err_en_US,
26           parser::ToUpperCaseLetters(endSource.ToString()))
27       .Attach(beginSource, "Does not match directive"_en_US);
28 }
29 
30 bool OmpStructureChecker::HasInvalidWorksharingNesting(
31     const parser::CharBlock &source, const OmpDirectiveSet &set) {
32   // set contains all the invalid closely nested directives
33   // for the given directive (`source` here)
34   if (CurrentDirectiveIsNested() && set.test(GetContext().directive)) {
35     context_.Say(source,
36         "A worksharing region may not be closely nested inside a "
37         "worksharing, explicit task, taskloop, critical, ordered, atomic, or "
38         "master region"_err_en_US);
39     return true;
40   }
41   return false;
42 }
43 
44 void OmpStructureChecker::CheckAllowed(OmpClause type) {
45   if (!GetContext().allowedClauses.test(type) &&
46       !GetContext().allowedOnceClauses.test(type) &&
47       !GetContext().allowedExclusiveClauses.test(type)) {
48     context_.Say(GetContext().clauseSource,
49         "%s clause is not allowed on the %s directive"_err_en_US,
50         EnumToString(type),
51         parser::ToUpperCaseLetters(GetContext().directiveSource.ToString()));
52     return;
53   }
54   if ((GetContext().allowedOnceClauses.test(type) ||
55           GetContext().allowedExclusiveClauses.test(type)) &&
56       FindClause(type)) {
57     context_.Say(GetContext().clauseSource,
58         "At most one %s clause can appear on the %s directive"_err_en_US,
59         EnumToString(type),
60         parser::ToUpperCaseLetters(GetContext().directiveSource.ToString()));
61     return;
62   }
63   if (GetContext().allowedExclusiveClauses.test(type)) {
64     std::vector<OmpClause> others;
65     GetContext().allowedExclusiveClauses.IterateOverMembers([&](OmpClause o) {
66       if (FindClause(o)) {
67         others.emplace_back(o);
68       }
69     });
70     for (const auto &e : others) {
71       context_.Say(GetContext().clauseSource,
72           "%s and %s are mutually exclusive and may not appear on the "
73           "same %s directive"_err_en_US,
74           EnumToString(type), EnumToString(e),
75           parser::ToUpperCaseLetters(GetContext().directiveSource.ToString()));
76     }
77     if (!others.empty()) {
78       return;
79     }
80   }
81   SetContextClauseInfo(type);
82 }
83 
84 void OmpStructureChecker::CheckRequired(OmpClause c) {
85   if (!FindClause(c)) {
86     context_.Say(GetContext().directiveSource,
87         "At least one %s clause must appear on the %s directive"_err_en_US,
88         EnumToString(c), ContextDirectiveAsFortran());
89   }
90 }
91 
92 void OmpStructureChecker::RequiresConstantPositiveParameter(
93     const OmpClause &clause, const parser::ScalarIntConstantExpr &i) {
94   if (const auto v{GetIntValue(i)}) {
95     if (*v <= 0) {
96       context_.Say(GetContext().clauseSource,
97           "The parameter of the %s clause must be "
98           "a constant positive integer expression"_err_en_US,
99           EnumToString(clause));
100     }
101   }
102 }
103 
104 void OmpStructureChecker::RequiresPositiveParameter(
105     const OmpClause &clause, const parser::ScalarIntExpr &i) {
106   if (const auto v{GetIntValue(i)}) {
107     if (*v <= 0) {
108       context_.Say(GetContext().clauseSource,
109           "The parameter of the %s clause must be "
110           "a positive integer expression"_err_en_US,
111           EnumToString(clause));
112     }
113   }
114 }
115 
116 void OmpStructureChecker::Enter(const parser::OpenMPConstruct &) {
117   // 2.8.1 TODO: Simd Construct with Ordered Construct Nesting check
118 }
119 
120 void OmpStructureChecker::Enter(const parser::OpenMPLoopConstruct &x) {
121   const auto &beginLoopDir{std::get<parser::OmpBeginLoopDirective>(x.t)};
122   const auto &beginDir{std::get<parser::OmpLoopDirective>(beginLoopDir.t)};
123 
124   // check matching, End directive is optional
125   if (const auto &endLoopDir{
126           std::get<std::optional<parser::OmpEndLoopDirective>>(x.t)}) {
127     CheckMatching<parser::OmpLoopDirective>(beginLoopDir, *endLoopDir);
128   }
129 
130   switch (beginDir.v) {
131   // 2.7.1 do-clause -> private-clause |
132   //                    firstprivate-clause |
133   //                    lastprivate-clause |
134   //                    linear-clause |
135   //                    reduction-clause |
136   //                    schedule-clause |
137   //                    collapse-clause |
138   //                    ordered-clause
139   case parser::OmpLoopDirective::Directive::Do: {
140     // nesting check
141     HasInvalidWorksharingNesting(beginDir.source,
142         {OmpDirective::DO, OmpDirective::SECTIONS, OmpDirective::SINGLE,
143             OmpDirective::WORKSHARE, OmpDirective::TASK, OmpDirective::TASKLOOP,
144             OmpDirective::CRITICAL, OmpDirective::ORDERED, OmpDirective::ATOMIC,
145             OmpDirective::MASTER});
146 
147     PushContext(beginDir.source, OmpDirective::DO);
148     OmpClauseSet allowed{OmpClause::PRIVATE, OmpClause::FIRSTPRIVATE,
149         OmpClause::LASTPRIVATE, OmpClause::LINEAR, OmpClause::REDUCTION};
150     SetContextAllowed(allowed);
151     OmpClauseSet allowedOnce{
152         OmpClause::SCHEDULE, OmpClause::COLLAPSE, OmpClause::ORDERED};
153     SetContextAllowedOnce(allowedOnce);
154   } break;
155 
156   // 2.11.1 parallel-do-clause -> parallel-clause |
157   //                              do-clause
158   case parser::OmpLoopDirective::Directive::ParallelDo: {
159     PushContext(beginDir.source, OmpDirective::PARALLEL_DO);
160     OmpClauseSet allowed{OmpClause::DEFAULT, OmpClause::PRIVATE,
161         OmpClause::FIRSTPRIVATE, OmpClause::SHARED, OmpClause::COPYIN,
162         OmpClause::REDUCTION, OmpClause::LASTPRIVATE, OmpClause::LINEAR};
163     SetContextAllowed(allowed);
164     OmpClauseSet allowedOnce{OmpClause::IF, OmpClause::NUM_THREADS,
165         OmpClause::PROC_BIND, OmpClause::SCHEDULE, OmpClause::COLLAPSE,
166         OmpClause::ORDERED};
167     SetContextAllowedOnce(allowedOnce);
168   } break;
169 
170   // 2.8.1 simd-clause -> safelen-clause |
171   //                      simdlen-clause |
172   //                      linear-clause |
173   //                      aligned-clause |
174   //                      private-clause |
175   //                      lastprivate-clause |
176   //                      reduction-clause |
177   //                      collapse-clause
178   case parser::OmpLoopDirective::Directive::Simd: {
179     PushContext(beginDir.source, OmpDirective::SIMD);
180     OmpClauseSet allowed{OmpClause::LINEAR, OmpClause::ALIGNED,
181         OmpClause::PRIVATE, OmpClause::LASTPRIVATE, OmpClause::REDUCTION};
182     SetContextAllowed(allowed);
183     OmpClauseSet allowedOnce{
184         OmpClause::COLLAPSE, OmpClause::SAFELEN, OmpClause::SIMDLEN};
185     SetContextAllowedOnce(allowedOnce);
186   } break;
187 
188   // 2.8.3 do-simd-clause -> do-clause |
189   //                         simd-clause
190   case parser::OmpLoopDirective::Directive::DoSimd: {
191     PushContext(beginDir.source, OmpDirective::DO_SIMD);
192     OmpClauseSet allowed{OmpClause::PRIVATE, OmpClause::FIRSTPRIVATE,
193         OmpClause::LASTPRIVATE, OmpClause::LINEAR, OmpClause::REDUCTION,
194         OmpClause::ALIGNED};
195     SetContextAllowed(allowed);
196     OmpClauseSet allowedOnce{OmpClause::SCHEDULE, OmpClause::COLLAPSE,
197         OmpClause::ORDERED, OmpClause::SAFELEN, OmpClause::SIMDLEN};
198     SetContextAllowedOnce(allowedOnce);
199   } break;
200 
201   // 2.11.4 parallel-do-simd-clause -> parallel-clause |
202   //                                   do-simd-clause
203   case parser::OmpLoopDirective::Directive::ParallelDoSimd: {
204     PushContext(beginDir.source, OmpDirective::PARALLEL_DO_SIMD);
205     OmpClauseSet allowed{OmpClause::DEFAULT, OmpClause::PRIVATE,
206         OmpClause::FIRSTPRIVATE, OmpClause::SHARED, OmpClause::COPYIN,
207         OmpClause::REDUCTION, OmpClause::LASTPRIVATE, OmpClause::LINEAR,
208         OmpClause::ALIGNED};
209     SetContextAllowed(allowed);
210     OmpClauseSet allowedOnce{OmpClause::IF, OmpClause::NUM_THREADS,
211         OmpClause::PROC_BIND, OmpClause::SCHEDULE, OmpClause::COLLAPSE,
212         OmpClause::ORDERED, OmpClause::SAFELEN, OmpClause::SIMDLEN};
213     SetContextAllowedOnce(allowedOnce);
214   } break;
215 
216   // 2.9.2 taskloop-clause -> if-clause |
217   //                          shared-clause |
218   //                          private-clause |
219   //                          firstprivate-clause |
220   //                          lastprivate-clause |
221   //                          default-clause |
222   //                          grainsize-clause |
223   //                          num-tasks-clause |
224   //                          collapse-clause |
225   //                          final-clause |
226   //                          priority-clause |
227   //                          untied-clause |
228   //                          mergeable-clause |
229   //                          nogroup-clause
230   case parser::OmpLoopDirective::Directive::Taskloop: {
231     PushContext(beginDir.source, OmpDirective::TASKLOOP);
232     OmpClauseSet allowed{OmpClause::SHARED, OmpClause::PRIVATE,
233         OmpClause::FIRSTPRIVATE, OmpClause::LASTPRIVATE, OmpClause::DEFAULT,
234         OmpClause::UNTIED, OmpClause::MERGEABLE, OmpClause::NOGROUP};
235     SetContextAllowed(allowed);
236     OmpClauseSet allowedOnce{OmpClause::COLLAPSE, OmpClause::IF,
237         OmpClause::FINAL, OmpClause::PRIORITY};
238     SetContextAllowedOnce(allowedOnce);
239     OmpClauseSet allowedExclusive{OmpClause::GRAINSIZE, OmpClause::NUM_TASKS};
240     SetContextAllowedExclusive(allowedExclusive);
241   } break;
242 
243   // 2.9.3 taskloop-simd-clause -> taskloop-clause |
244   //                               simd-clause
245   case parser::OmpLoopDirective::Directive::TaskloopSimd: {
246     PushContext(beginDir.source, OmpDirective::TASKLOOP_SIMD);
247     OmpClauseSet allowed{OmpClause::LINEAR, OmpClause::ALIGNED,
248         OmpClause::SHARED, OmpClause::PRIVATE, OmpClause::FIRSTPRIVATE,
249         OmpClause::LASTPRIVATE, OmpClause::DEFAULT, OmpClause::UNTIED,
250         OmpClause::MERGEABLE, OmpClause::NOGROUP};
251     SetContextAllowed(allowed);
252     OmpClauseSet allowedOnce{OmpClause::COLLAPSE, OmpClause::SAFELEN,
253         OmpClause::SIMDLEN, OmpClause::IF, OmpClause::FINAL,
254         OmpClause::PRIORITY};
255     SetContextAllowedOnce(allowedOnce);
256     OmpClauseSet allowedExclusive{OmpClause::GRAINSIZE, OmpClause::NUM_TASKS};
257     SetContextAllowedExclusive(allowedExclusive);
258   } break;
259 
260   // 2.10.8 distribute-clause -> private-clause |
261   //                             firstprivate-clause |
262   //                             lastprivate-clause |
263   //                             collapse-clause |
264   //                             dist-schedule-clause
265   case parser::OmpLoopDirective::Directive::Distribute: {
266     PushContext(beginDir.source, OmpDirective::DISTRIBUTE);
267     OmpClauseSet allowed{
268         OmpClause::PRIVATE, OmpClause::FIRSTPRIVATE, OmpClause::LASTPRIVATE};
269     SetContextAllowed(allowed);
270     OmpClauseSet allowedOnce{OmpClause::COLLAPSE, OmpClause::DIST_SCHEDULE};
271     SetContextAllowedOnce(allowedOnce);
272   } break;
273 
274   default:
275     // TODO others
276     break;
277   }
278 }
279 
280 void OmpStructureChecker::Leave(const parser::OpenMPLoopConstruct &) {
281   ompContext_.pop_back();
282 }
283 
284 void OmpStructureChecker::Enter(const parser::OmpEndLoopDirective &x) {
285   const auto &dir{std::get<parser::OmpLoopDirective>(x.t)};
286   ResetPartialContext(dir.source);
287   switch (dir.v) {
288   // 2.7.1 end-do -> END DO [nowait-clause]
289   // 2.8.3 end-do-simd -> END DO SIMD [nowait-clause]
290   case parser::OmpLoopDirective::Directive::Do:
291     SetContextDirectiveEnum(OmpDirective::END_DO);
292     SetContextAllowed(OmpClauseSet{OmpClause::NOWAIT});
293     break;
294   case parser::OmpLoopDirective::Directive::DoSimd:
295     SetContextDirectiveEnum(OmpDirective::END_DO_SIMD);
296     SetContextAllowed(OmpClauseSet{OmpClause::NOWAIT});
297     break;
298   default:
299     // no clauses are allowed
300     break;
301   }
302 }
303 
304 void OmpStructureChecker::Enter(const parser::OpenMPBlockConstruct &x) {
305   const auto &beginBlockDir{std::get<parser::OmpBeginBlockDirective>(x.t)};
306   const auto &endBlockDir{std::get<parser::OmpEndBlockDirective>(x.t)};
307   const auto &beginDir{
308       CheckMatching<parser::OmpBlockDirective>(beginBlockDir, endBlockDir)};
309 
310   switch (beginDir.v) {
311   // 2.5 parallel-clause -> if-clause |
312   //                        num-threads-clause |
313   //                        default-clause |
314   //                        private-clause |
315   //                        firstprivate-clause |
316   //                        shared-clause |
317   //                        copyin-clause |
318   //                        reduction-clause |
319   //                        proc-bind-clause
320   case parser::OmpBlockDirective::Directive::Parallel: {
321     // reserve for nesting check
322     PushContext(beginDir.source, OmpDirective::PARALLEL);
323     OmpClauseSet allowed{OmpClause::DEFAULT, OmpClause::PRIVATE,
324         OmpClause::FIRSTPRIVATE, OmpClause::SHARED, OmpClause::COPYIN,
325         OmpClause::REDUCTION};
326     SetContextAllowed(allowed);
327     OmpClauseSet allowedOnce{
328         OmpClause::IF, OmpClause::NUM_THREADS, OmpClause::PROC_BIND};
329     SetContextAllowedOnce(allowedOnce);
330   } break;
331   // 2.7.3 single-clause -> private-clause |
332   //                        firstprivate-clause
333   case parser::OmpBlockDirective::Directive::Single:
334     PushContext(beginDir.source, OmpDirective::SINGLE);
335     SetContextAllowed({OmpClause::PRIVATE, OmpClause::FIRSTPRIVATE});
336     break;
337   // 2.7.4 workshare (no clauses are allowed)
338   case parser::OmpBlockDirective::Directive::Workshare:
339     PushContext(beginDir.source, OmpDirective::WORKSHARE);
340     break;
341     // 2.9.1 task-clause -> if-clause |
342     //                      final-clause |
343     //                      untied-clause |
344     //                      default-clause |
345     //                      mergeable-clause |
346     //                      private-clause |
347     //                      firstprivate-clause |
348     //                      shared-clause |
349     //                      depend-clause |
350     //                      priority-clause
351   case parser::OmpBlockDirective::Directive::Task: {
352     PushContext(beginDir.source, OmpDirective::TASK);
353     OmpClauseSet allowed{OmpClause::UNTIED, OmpClause::DEFAULT,
354         OmpClause::MERGEABLE, OmpClause::PRIVATE, OmpClause::FIRSTPRIVATE,
355         OmpClause::SHARED, OmpClause::DEPEND};
356     SetContextAllowed(allowed);
357     OmpClauseSet allowedOnce{
358         OmpClause::IF, OmpClause::FINAL, OmpClause::PRIORITY};
359     SetContextAllowedOnce(allowedOnce);
360   } break;
361   // 2.10.4 target-clause -> if-clause |
362   //                         device-clause |
363   //                         private-clause |
364   //                         firstprivate-clause |
365   //                         map-clause |
366   //                         is-device-ptr-clause |
367   //                         defaultmap-clause |
368   //                         nowait-clause |
369   //                         depend-clause
370   case parser::OmpBlockDirective::Directive::Target: {
371     PushContext(beginDir.source, OmpDirective::TARGET);
372     OmpClauseSet allowed{OmpClause::IF, OmpClause::PRIVATE,
373         OmpClause::FIRSTPRIVATE, OmpClause::MAP, OmpClause::IS_DEVICE_PTR,
374         OmpClause::DEPEND};
375     SetContextAllowed(allowed);
376     OmpClauseSet allowedOnce{
377         OmpClause::DEVICE, OmpClause::DEFAULTMAP, OmpClause::NOWAIT};
378     SetContextAllowedOnce(allowedOnce);
379   } break;
380   // 2.10.7 teams-clause -> num-teams-clause |
381   //                        thread-limit-clause |
382   //                        default-clause |
383   //                        private-clause |
384   //                        firstprivate-clause |
385   //                        shared-clause |
386   //                        reduction-clause
387   case parser::OmpBlockDirective::Directive::Teams: {
388     PushContext(beginDir.source, OmpDirective::TEAMS);
389     OmpClauseSet allowed{OmpClause::PRIVATE, OmpClause::FIRSTPRIVATE,
390         OmpClause::SHARED, OmpClause::REDUCTION};
391     SetContextAllowed(allowed);
392     OmpClauseSet allowedOnce{
393         OmpClause::NUM_TEAMS, OmpClause::THREAD_LIMIT, OmpClause::DEFAULT};
394     SetContextAllowedOnce(allowedOnce);
395   } break;
396     // 2.10.1 target-data-clause -> if-clause |
397     //                              device-clause |
398     //                              map-clause |
399     //                              use-device-ptr-clause
400   case parser::OmpBlockDirective::Directive::TargetData: {
401     PushContext(beginDir.source, OmpDirective::TARGET_DATA);
402     OmpClauseSet allowed{
403         OmpClause::IF, OmpClause::MAP, OmpClause::USE_DEVICE_PTR};
404     SetContextAllowed(allowed);
405     SetContextAllowedOnce({OmpClause::DEVICE});
406     SetContextRequired({OmpClause::MAP});
407   } break;
408     // 2.13.1 master (no clauses are allowed)
409   case parser::OmpBlockDirective::Directive::Master:
410     PushContext(beginDir.source, OmpDirective::MASTER);
411     break;
412   default:
413     // TODO others
414     break;
415   }
416 }
417 
418 void OmpStructureChecker::Leave(const parser::OpenMPBlockConstruct &) {
419   ompContext_.pop_back();
420 }
421 
422 void OmpStructureChecker::Enter(const parser::OpenMPSectionsConstruct &x) {
423   const auto &beginSectionsDir{
424       std::get<parser::OmpBeginSectionsDirective>(x.t)};
425   const auto &endSectionsDir{std::get<parser::OmpEndSectionsDirective>(x.t)};
426   const auto &beginDir{CheckMatching<parser::OmpSectionsDirective>(
427       beginSectionsDir, endSectionsDir)};
428 
429   switch (beginDir.v) {
430   // 2.7.2 sections-clause -> private-clause |
431   //                          firstprivate-clause |
432   //                          lastprivate-clause |
433   //                          reduction-clause
434   case parser::OmpSectionsDirective::Directive::Sections: {
435     PushContext(beginDir.source, OmpDirective::SECTIONS);
436     OmpClauseSet allowed{OmpClause::PRIVATE, OmpClause::FIRSTPRIVATE,
437         OmpClause::LASTPRIVATE, OmpClause::REDUCTION};
438     SetContextAllowed(allowed);
439   } break;
440   case parser::OmpSectionsDirective::Directive::ParallelSections: {
441     PushContext(beginDir.source, OmpDirective::PARALLEL_SECTIONS);
442     OmpClauseSet allowed{OmpClause::DEFAULT, OmpClause::PRIVATE,
443         OmpClause::FIRSTPRIVATE, OmpClause::LASTPRIVATE, OmpClause::SHARED,
444         OmpClause::COPYIN, OmpClause::REDUCTION};
445     SetContextAllowed(allowed);
446     OmpClauseSet allowedOnce{
447         OmpClause::IF, OmpClause::NUM_THREADS, OmpClause::PROC_BIND};
448     SetContextAllowedOnce(allowedOnce);
449   } break;
450   }
451 }
452 
453 void OmpStructureChecker::Leave(const parser::OpenMPSectionsConstruct &) {
454   ompContext_.pop_back();
455 }
456 
457 void OmpStructureChecker::Enter(const parser::OmpEndSectionsDirective &x) {
458   const auto &dir{std::get<parser::OmpSectionsDirective>(x.t)};
459   ResetPartialContext(dir.source);
460   switch (dir.v) {
461     // 2.7.2 end-sections -> END SECTIONS [nowait-clause]
462   case parser::OmpSectionsDirective::Directive::Sections:
463     SetContextDirectiveEnum(OmpDirective::END_SECTIONS);
464     SetContextAllowed(OmpClauseSet{OmpClause::NOWAIT});
465     break;
466   default:
467     // no clauses are allowed
468     break;
469   }
470 }
471 
472 void OmpStructureChecker::Enter(const parser::OpenMPDeclareSimdConstruct &x) {
473   const auto &dir{std::get<parser::Verbatim>(x.t)};
474   PushContext(dir.source, OmpDirective::DECLARE_SIMD);
475   // 2.8.2 declare-simd-clause -> simdlen-clause |
476   //                              linear-clause |
477   //                              aligned-clause |
478   //                              uniform-clause |
479   //                              inbranch-clause |
480   //                              notinbranch-clause
481   OmpClauseSet allowed{
482       OmpClause::LINEAR, OmpClause::ALIGNED, OmpClause::UNIFORM};
483   SetContextAllowed(allowed);
484   SetContextAllowedOnce({OmpClause::SIMDLEN});
485   SetContextAllowedExclusive({OmpClause::INBRANCH, OmpClause::NOTINBRANCH});
486 }
487 
488 void OmpStructureChecker::Leave(const parser::OpenMPDeclareSimdConstruct &) {
489   ompContext_.pop_back();
490 }
491 
492 void OmpStructureChecker::Enter(const parser::OpenMPDeclareTargetConstruct &x) {
493   const auto &dir{std::get<parser::Verbatim>(x.t)};
494   PushContext(dir.source, OmpDirective::DECLARE_TARGET);
495   const auto &spec{std::get<parser::OmpDeclareTargetSpecifier>(x.t)};
496   if (std::holds_alternative<parser::OmpDeclareTargetWithClause>(spec.u)) {
497     SetContextAllowed(OmpClauseSet{OmpClause::TO, OmpClause::LINK});
498   }
499 }
500 
501 void OmpStructureChecker::Leave(const parser::OpenMPDeclareTargetConstruct &) {
502   ompContext_.pop_back();
503 }
504 
505 void OmpStructureChecker::Enter(
506     const parser::OpenMPSimpleStandaloneConstruct &x) {
507   const auto &dir{std::get<parser::OmpSimpleStandaloneDirective>(x.t)};
508   switch (dir.v) {
509   case parser::OmpSimpleStandaloneDirective::Directive::Barrier: {
510     // 2.13.3 barrier
511     PushContext(dir.source, OmpDirective::BARRIER);
512   } break;
513   case parser::OmpSimpleStandaloneDirective::Directive::Taskwait: {
514     // 2.13.4 taskwait
515     PushContext(dir.source, OmpDirective::TASKWAIT);
516   } break;
517   case parser::OmpSimpleStandaloneDirective::Directive::Taskyield: {
518     // 2.9.4 taskyield
519     PushContext(dir.source, OmpDirective::TASKYIELD);
520   } break;
521   case parser::OmpSimpleStandaloneDirective::Directive::TargetEnterData: {
522     // 2.10.2 target-enter-data-clause -> if-clause |
523     //                                    device-clause |
524     //                                    map-clause |
525     //                                    depend-clause |
526     //                                    nowait-clause
527     PushContext(dir.source, OmpDirective::TARGET_ENTER_DATA);
528     OmpClauseSet allowed{OmpClause::MAP, OmpClause::DEPEND, OmpClause::NOWAIT};
529     SetContextAllowed(allowed);
530     OmpClauseSet allowedOnce{OmpClause::DEVICE, OmpClause::IF};
531     SetContextAllowedOnce(allowedOnce);
532     SetContextRequired({OmpClause::MAP});
533   } break;
534   case parser::OmpSimpleStandaloneDirective::Directive::TargetExitData: {
535     // 2.10.3  target-enter-data-clause -> if-clause |
536     //                                     device-clause |
537     //                                     map-clause |
538     //                                     depend-clause |
539     //                                     nowait-clause
540     PushContext(dir.source, OmpDirective::TARGET_EXIT_DATA);
541     OmpClauseSet allowed{OmpClause::MAP, OmpClause::DEPEND, OmpClause::NOWAIT};
542     SetContextAllowed(allowed);
543     OmpClauseSet allowedOnce{OmpClause::DEVICE, OmpClause::IF};
544     SetContextAllowedOnce(allowedOnce);
545     SetContextRequired({OmpClause::MAP});
546   } break;
547   case parser::OmpSimpleStandaloneDirective::Directive::TargetUpdate: {
548     // 2.10.5 target-update
549     PushContext(dir.source, OmpDirective::TARGET_UPDATE);
550   } break;
551   case parser::OmpSimpleStandaloneDirective::Directive::Ordered: {
552     // 2.13.8 ordered-construct-clause -> depend-clause
553     PushContext(dir.source, OmpDirective::ORDERED);
554     OmpClauseSet allowed{OmpClause::DEPEND};
555     SetContextAllowed(allowed);
556   } break;
557   }
558 }
559 
560 void OmpStructureChecker::Leave(
561     const parser::OpenMPSimpleStandaloneConstruct &) {
562   ompContext_.pop_back();
563 }
564 
565 void OmpStructureChecker::Enter(const parser::OpenMPFlushConstruct &x) {
566   const auto &dir{std::get<parser::Verbatim>(x.t)};
567   PushContext(dir.source, OmpDirective::FLUSH);
568 }
569 
570 void OmpStructureChecker::Leave(const parser::OpenMPFlushConstruct &) {
571   ompContext_.pop_back();
572 }
573 
574 void OmpStructureChecker::Enter(const parser::OpenMPCancelConstruct &x) {
575   const auto &dir{std::get<parser::Verbatim>(x.t)};
576   PushContext(dir.source, OmpDirective::CANCEL);
577 }
578 
579 void OmpStructureChecker::Leave(const parser::OpenMPCancelConstruct &) {
580   ompContext_.pop_back();
581 }
582 
583 void OmpStructureChecker::Enter(
584     const parser::OpenMPCancellationPointConstruct &x) {
585   const auto &dir{std::get<parser::Verbatim>(x.t)};
586   PushContext(dir.source, OmpDirective::CANCELLATION_POINT);
587 }
588 
589 void OmpStructureChecker::Leave(
590     const parser::OpenMPCancellationPointConstruct &) {
591   ompContext_.pop_back();
592 }
593 
594 void OmpStructureChecker::Enter(const parser::OmpEndBlockDirective &x) {
595   const auto &dir{std::get<parser::OmpBlockDirective>(x.t)};
596   ResetPartialContext(dir.source);
597   switch (dir.v) {
598   // 2.7.3 end-single-clause -> copyprivate-clause |
599   //                            nowait-clause
600   case parser::OmpBlockDirective::Directive::Single: {
601     SetContextDirectiveEnum(OmpDirective::END_SINGLE);
602     OmpClauseSet allowed{OmpClause::COPYPRIVATE};
603     SetContextAllowed(allowed);
604     OmpClauseSet allowedOnce{OmpClause::NOWAIT};
605     SetContextAllowedOnce(allowedOnce);
606   } break;
607   // 2.7.4 end-workshare -> END WORKSHARE [nowait-clause]
608   case parser::OmpBlockDirective::Directive::Workshare:
609     SetContextDirectiveEnum(OmpDirective::END_WORKSHARE);
610     SetContextAllowed(OmpClauseSet{OmpClause::NOWAIT});
611     break;
612   default:
613     // no clauses are allowed
614     break;
615   }
616 }
617 
618 void OmpStructureChecker::Leave(const parser::OmpClauseList &) {
619   // 2.7 Loop Construct Restriction
620   if (doSet.test(GetContext().directive)) {
621     if (auto *clause{FindClause(OmpClause::SCHEDULE)}) {
622       // only one schedule clause is allowed
623       const auto &schedClause{std::get<parser::OmpScheduleClause>(clause->u)};
624       if (ScheduleModifierHasType(schedClause,
625               parser::OmpScheduleModifierType::ModType::Nonmonotonic)) {
626         if (FindClause(OmpClause::ORDERED)) {
627           context_.Say(clause->source,
628               "The NONMONOTONIC modifier cannot be specified "
629               "if an ORDERED clause is specified"_err_en_US);
630         }
631         if (ScheduleModifierHasType(schedClause,
632                 parser::OmpScheduleModifierType::ModType::Monotonic)) {
633           context_.Say(clause->source,
634               "The MONOTONIC and NONMONOTONIC modifiers "
635               "cannot be both specified"_err_en_US);
636         }
637       }
638     }
639 
640     if (auto *clause{FindClause(OmpClause::ORDERED)}) {
641       // only one ordered clause is allowed
642       const auto &orderedClause{
643           std::get<parser::OmpClause::Ordered>(clause->u)};
644 
645       if (orderedClause.v) {
646         if (FindClause(OmpClause::LINEAR)) {
647           context_.Say(clause->source,
648               "A loop directive may not have both a LINEAR clause and "
649               "an ORDERED clause with a parameter"_err_en_US);
650         }
651 
652         if (auto *clause2{FindClause(OmpClause::COLLAPSE)}) {
653           const auto &collapseClause{
654               std::get<parser::OmpClause::Collapse>(clause2->u)};
655           // ordered and collapse both have parameters
656           if (const auto orderedValue{GetIntValue(orderedClause.v)}) {
657             if (const auto collapseValue{GetIntValue(collapseClause.v)}) {
658               if (*orderedValue > 0 && *orderedValue < *collapseValue) {
659                 context_.Say(clause->source,
660                     "The parameter of the ORDERED clause must be "
661                     "greater than or equal to "
662                     "the parameter of the COLLAPSE clause"_err_en_US);
663               }
664             }
665           }
666         }
667       }
668 
669       // TODO: ordered region binding check (requires nesting implementation)
670     }
671   } // doSet
672 
673   // 2.8.1 Simd Construct Restriction
674   if (simdSet.test(GetContext().directive)) {
675     if (auto *clause{FindClause(OmpClause::SIMDLEN)}) {
676       if (auto *clause2{FindClause(OmpClause::SAFELEN)}) {
677         const auto &simdlenClause{
678             std::get<parser::OmpClause::Simdlen>(clause->u)};
679         const auto &safelenClause{
680             std::get<parser::OmpClause::Safelen>(clause2->u)};
681         // simdlen and safelen both have parameters
682         if (const auto simdlenValue{GetIntValue(simdlenClause.v)}) {
683           if (const auto safelenValue{GetIntValue(safelenClause.v)}) {
684             if (*safelenValue > 0 && *simdlenValue > *safelenValue) {
685               context_.Say(clause->source,
686                   "The parameter of the SIMDLEN clause must be less than or "
687                   "equal to the parameter of the SAFELEN clause"_err_en_US);
688             }
689           }
690         }
691       }
692     }
693 
694     // TODO: A list-item cannot appear in more than one aligned clause
695   } // SIMD
696 
697   // 2.7.3 Single Construct Restriction
698   if (GetContext().directive == OmpDirective::END_SINGLE) {
699     if (auto *clause{FindClause(OmpClause::COPYPRIVATE)}) {
700       if (FindClause(OmpClause::NOWAIT)) {
701         context_.Say(clause->source,
702             "The COPYPRIVATE clause must not be used with "
703             "the NOWAIT clause"_err_en_US);
704       }
705     }
706   }
707 
708   GetContext().requiredClauses.IterateOverMembers(
709       [this](OmpClause c) { CheckRequired(c); });
710 }
711 
712 void OmpStructureChecker::Enter(const parser::OmpClause &x) {
713   SetContextClause(x);
714 }
715 
716 void OmpStructureChecker::Enter(const parser::OmpNowait &) {
717   CheckAllowed(OmpClause::NOWAIT);
718 }
719 void OmpStructureChecker::Enter(const parser::OmpClause::Inbranch &) {
720   CheckAllowed(OmpClause::INBRANCH);
721 }
722 void OmpStructureChecker::Enter(const parser::OmpClause::Mergeable &) {
723   CheckAllowed(OmpClause::MERGEABLE);
724 }
725 void OmpStructureChecker::Enter(const parser::OmpClause::Nogroup &) {
726   CheckAllowed(OmpClause::NOGROUP);
727 }
728 void OmpStructureChecker::Enter(const parser::OmpClause::Notinbranch &) {
729   CheckAllowed(OmpClause::NOTINBRANCH);
730 }
731 void OmpStructureChecker::Enter(const parser::OmpClause::Untied &) {
732   CheckAllowed(OmpClause::UNTIED);
733 }
734 
735 void OmpStructureChecker::Enter(const parser::OmpClause::Collapse &x) {
736   CheckAllowed(OmpClause::COLLAPSE);
737   // collapse clause must have a parameter
738   RequiresConstantPositiveParameter(OmpClause::COLLAPSE, x.v);
739 }
740 
741 void OmpStructureChecker::Enter(const parser::OmpClause::Copyin &) {
742   CheckAllowed(OmpClause::COPYIN);
743 }
744 void OmpStructureChecker::Enter(const parser::OmpClause::Copyprivate &) {
745   CheckAllowed(OmpClause::COPYPRIVATE);
746 }
747 void OmpStructureChecker::Enter(const parser::OmpClause::Device &) {
748   CheckAllowed(OmpClause::DEVICE);
749 }
750 void OmpStructureChecker::Enter(const parser::OmpClause::DistSchedule &) {
751   CheckAllowed(OmpClause::DIST_SCHEDULE);
752 }
753 void OmpStructureChecker::Enter(const parser::OmpClause::Final &) {
754   CheckAllowed(OmpClause::FINAL);
755 }
756 void OmpStructureChecker::Enter(const parser::OmpClause::Firstprivate &) {
757   CheckAllowed(OmpClause::FIRSTPRIVATE);
758 }
759 void OmpStructureChecker::Enter(const parser::OmpClause::From &) {
760   CheckAllowed(OmpClause::FROM);
761 }
762 void OmpStructureChecker::Enter(const parser::OmpClause::Grainsize &x) {
763   CheckAllowed(OmpClause::GRAINSIZE);
764   RequiresPositiveParameter(OmpClause::GRAINSIZE, x.v);
765 }
766 void OmpStructureChecker::Enter(const parser::OmpClause::Lastprivate &) {
767   CheckAllowed(OmpClause::LASTPRIVATE);
768 }
769 void OmpStructureChecker::Enter(const parser::OmpClause::NumTasks &x) {
770   CheckAllowed(OmpClause::NUM_TASKS);
771   RequiresPositiveParameter(OmpClause::NUM_TASKS, x.v);
772 }
773 void OmpStructureChecker::Enter(const parser::OmpClause::NumTeams &x) {
774   CheckAllowed(OmpClause::NUM_TEAMS);
775   RequiresPositiveParameter(OmpClause::NUM_TEAMS, x.v);
776 }
777 void OmpStructureChecker::Enter(const parser::OmpClause::NumThreads &x) {
778   CheckAllowed(OmpClause::NUM_THREADS);
779   RequiresPositiveParameter(OmpClause::NUM_THREADS, x.v);
780   // if parameter is variable, defer to Expression Analysis
781 }
782 
783 void OmpStructureChecker::Enter(const parser::OmpClause::Ordered &x) {
784   CheckAllowed(OmpClause::ORDERED);
785   // the parameter of ordered clause is optional
786   if (const auto &expr{x.v}) {
787     RequiresConstantPositiveParameter(OmpClause::ORDERED, *expr);
788 
789     // 2.8.3 Loop SIMD Construct Restriction
790     if (doSimdSet.test(GetContext().directive)) {
791       context_.Say(GetContext().clauseSource,
792           "No ORDERED clause with a parameter can be specified "
793           "on the %s directive"_err_en_US,
794           ContextDirectiveAsFortran());
795     }
796   }
797 }
798 void OmpStructureChecker::Enter(const parser::OmpClause::Priority &x) {
799   CheckAllowed(OmpClause::PRIORITY);
800   RequiresPositiveParameter(OmpClause::PRIORITY, x.v);
801 }
802 void OmpStructureChecker::Enter(const parser::OmpClause::Private &) {
803   CheckAllowed(OmpClause::PRIVATE);
804 }
805 void OmpStructureChecker::Enter(const parser::OmpClause::Safelen &x) {
806   CheckAllowed(OmpClause::SAFELEN);
807   RequiresConstantPositiveParameter(OmpClause::SAFELEN, x.v);
808 }
809 void OmpStructureChecker::Enter(const parser::OmpClause::Shared &) {
810   CheckAllowed(OmpClause::SHARED);
811 }
812 void OmpStructureChecker::Enter(const parser::OmpClause::Simdlen &x) {
813   CheckAllowed(OmpClause::SIMDLEN);
814   RequiresConstantPositiveParameter(OmpClause::SIMDLEN, x.v);
815 }
816 void OmpStructureChecker::Enter(const parser::OmpClause::ThreadLimit &x) {
817   CheckAllowed(OmpClause::THREAD_LIMIT);
818   RequiresPositiveParameter(OmpClause::THREAD_LIMIT, x.v);
819 }
820 void OmpStructureChecker::Enter(const parser::OmpClause::To &) {
821   CheckAllowed(OmpClause::TO);
822 }
823 void OmpStructureChecker::Enter(const parser::OmpClause::Link &) {
824   CheckAllowed(OmpClause::LINK);
825 }
826 void OmpStructureChecker::Enter(const parser::OmpClause::Uniform &) {
827   CheckAllowed(OmpClause::UNIFORM);
828 }
829 void OmpStructureChecker::Enter(const parser::OmpClause::UseDevicePtr &) {
830   CheckAllowed(OmpClause::USE_DEVICE_PTR);
831 }
832 void OmpStructureChecker::Enter(const parser::OmpClause::IsDevicePtr &) {
833   CheckAllowed(OmpClause::IS_DEVICE_PTR);
834 }
835 
836 void OmpStructureChecker::Enter(const parser::OmpAlignedClause &x) {
837   CheckAllowed(OmpClause::ALIGNED);
838 
839   if (const auto &expr{
840           std::get<std::optional<parser::ScalarIntConstantExpr>>(x.t)}) {
841     if (const auto v{GetIntValue(*expr)}) {
842       if (*v <= 0) {
843         context_.Say(GetContext().clauseSource,
844             "The ALIGNMENT parameter of the ALIGNED clause must be "
845             "a constant positive integer expression"_err_en_US);
846       }
847     }
848   }
849   // 2.8.1 TODO: list-item attribute check
850 }
851 void OmpStructureChecker::Enter(const parser::OmpDefaultClause &) {
852   CheckAllowed(OmpClause::DEFAULT);
853 }
854 void OmpStructureChecker::Enter(const parser::OmpDefaultmapClause &x) {
855   CheckAllowed(OmpClause::DEFAULTMAP);
856   using VariableCategory = parser::OmpDefaultmapClause::VariableCategory;
857   if (!std::get<std::optional<VariableCategory>>(x.t)) {
858     context_.Say(GetContext().clauseSource,
859         "The argument TOFROM:SCALAR must be specified on the DEFAULTMAP "
860         "clause"_err_en_US);
861   }
862 }
863 void OmpStructureChecker::Enter(const parser::OmpDependClause &) {
864   CheckAllowed(OmpClause::DEPEND);
865 }
866 
867 void OmpStructureChecker::Enter(const parser::OmpIfClause &x) {
868   CheckAllowed(OmpClause::IF);
869 
870   using dirNameModifier = parser::OmpIfClause::DirectiveNameModifier;
871   static std::unordered_map<dirNameModifier, OmpDirectiveSet>
872       dirNameModifierMap{{dirNameModifier::Parallel, parallelSet},
873           {dirNameModifier::Target, targetSet},
874           {dirNameModifier::TargetEnterData, {OmpDirective::TARGET_ENTER_DATA}},
875           {dirNameModifier::TargetExitData, {OmpDirective::TARGET_EXIT_DATA}},
876           {dirNameModifier::TargetData, {OmpDirective::TARGET_DATA}},
877           {dirNameModifier::TargetUpdate, {OmpDirective::TARGET_UPDATE}},
878           {dirNameModifier::Task, {OmpDirective::TASK}},
879           {dirNameModifier::Taskloop, taskloopSet}};
880   if (const auto &directiveName{
881           std::get<std::optional<dirNameModifier>>(x.t)}) {
882     auto search{dirNameModifierMap.find(*directiveName)};
883     if (search == dirNameModifierMap.end() ||
884         !search->second.test(GetContext().directive)) {
885       context_
886           .Say(GetContext().clauseSource,
887               "Unmatched directive name modifier %s on the IF clause"_err_en_US,
888               parser::ToUpperCaseLetters(
889                   parser::OmpIfClause::EnumToString(*directiveName)))
890           .Attach(
891               GetContext().directiveSource, "Cannot apply to directive"_en_US);
892     }
893   }
894 }
895 
896 void OmpStructureChecker::Enter(const parser::OmpLinearClause &x) {
897   CheckAllowed(OmpClause::LINEAR);
898 
899   // 2.7 Loop Construct Restriction
900   if ((doSet | simdSet).test(GetContext().directive)) {
901     if (std::holds_alternative<parser::OmpLinearClause::WithModifier>(x.u)) {
902       context_.Say(GetContext().clauseSource,
903           "A modifier may not be specified in a LINEAR clause "
904           "on the %s directive"_err_en_US,
905           ContextDirectiveAsFortran());
906     }
907   }
908 }
909 void OmpStructureChecker::Enter(const parser::OmpMapClause &x) {
910   CheckAllowed(OmpClause::MAP);
911   if (const auto &maptype{std::get<std::optional<parser::OmpMapType>>(x.t)}) {
912     using Type = parser::OmpMapType::Type;
913     const Type &type{std::get<Type>(maptype->t)};
914     switch (GetContext().directive) {
915     case OmpDirective::TARGET:
916     case OmpDirective::TARGET_DATA: {
917       if (type != Type::To && type != Type::From && type != Type::Tofrom &&
918           type != Type::Alloc) {
919         context_.Say(GetContext().clauseSource,
920             "Only the TO, FROM, TOFROM, or ALLOC map types are permitted "
921             "for MAP clauses on the %s directive"_err_en_US,
922             ContextDirectiveAsFortran());
923       }
924     } break;
925     case OmpDirective::TARGET_ENTER_DATA: {
926       if (type != Type::To && type != Type::Alloc) {
927         context_.Say(GetContext().clauseSource,
928             "Only the TO or ALLOC map types are permitted "
929             "for MAP clauses on the %s directive"_err_en_US,
930             ContextDirectiveAsFortran());
931       }
932     } break;
933     case OmpDirective::TARGET_EXIT_DATA: {
934       if (type != Type::Delete && type != Type::Release && type != Type::From) {
935         context_.Say(GetContext().clauseSource,
936             "Only the FROM, RELEASE, or DELETE map types are permitted "
937             "for MAP clauses on the %s directive"_err_en_US,
938             ContextDirectiveAsFortran());
939       }
940     } break;
941     default:
942       break;
943     }
944   }
945 }
946 void OmpStructureChecker::Enter(const parser::OmpProcBindClause &) {
947   CheckAllowed(OmpClause::PROC_BIND);
948 }
949 void OmpStructureChecker::Enter(const parser::OmpReductionClause &) {
950   CheckAllowed(OmpClause::REDUCTION);
951 }
952 
953 bool OmpStructureChecker::ScheduleModifierHasType(
954     const parser::OmpScheduleClause &x,
955     const parser::OmpScheduleModifierType::ModType &type) {
956   const auto &modifier{
957       std::get<std::optional<parser::OmpScheduleModifier>>(x.t)};
958   if (modifier) {
959     const auto &modType1{
960         std::get<parser::OmpScheduleModifier::Modifier1>(modifier->t)};
961     const auto &modType2{
962         std::get<std::optional<parser::OmpScheduleModifier::Modifier2>>(
963             modifier->t)};
964     if (modType1.v.v == type || (modType2 && modType2->v.v == type)) {
965       return true;
966     }
967   }
968   return false;
969 }
970 void OmpStructureChecker::Enter(const parser::OmpScheduleClause &x) {
971   CheckAllowed(OmpClause::SCHEDULE);
972 
973   // 2.7 Loop Construct Restriction
974   if (doSet.test(GetContext().directive)) {
975     const auto &kind{std::get<1>(x.t)};
976     const auto &chunk{std::get<2>(x.t)};
977     if (chunk) {
978       if (kind == parser::OmpScheduleClause::ScheduleType::Runtime ||
979           kind == parser::OmpScheduleClause::ScheduleType::Auto) {
980         context_.Say(GetContext().clauseSource,
981             "When SCHEDULE clause has %s specified, "
982             "it must not have chunk size specified"_err_en_US,
983             parser::ToUpperCaseLetters(
984                 parser::OmpScheduleClause::EnumToString(kind)));
985       }
986     }
987 
988     if (ScheduleModifierHasType(
989             x, parser::OmpScheduleModifierType::ModType::Nonmonotonic)) {
990       if (kind != parser::OmpScheduleClause::ScheduleType::Dynamic &&
991           kind != parser::OmpScheduleClause::ScheduleType::Guided) {
992         context_.Say(GetContext().clauseSource,
993             "The NONMONOTONIC modifier can only be specified with "
994             "SCHEDULE(DYNAMIC) or SCHEDULE(GUIDED)"_err_en_US);
995       }
996     }
997   }
998 }
999 } // namespace Fortran::semantics
1000