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