| //===-- lib/Semantics/check-acc-structure.cpp -----------------------------===// | 
 | // | 
 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
 | // See https://llvm.org/LICENSE.txt for license information. | 
 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
 | // | 
 | //===----------------------------------------------------------------------===// | 
 | #include "check-acc-structure.h" | 
 | #include "flang/Common/enum-set.h" | 
 | #include "flang/Parser/parse-tree.h" | 
 | #include "flang/Semantics/tools.h" | 
 |  | 
 | #define CHECK_SIMPLE_CLAUSE(X, Y) \ | 
 |   void AccStructureChecker::Enter(const parser::AccClause::X &) { \ | 
 |     CheckAllowed(llvm::acc::Clause::Y); \ | 
 |   } | 
 |  | 
 | #define CHECK_REQ_SCALAR_INT_CONSTANT_CLAUSE(X, Y) \ | 
 |   void AccStructureChecker::Enter(const parser::AccClause::X &c) { \ | 
 |     CheckAllowed(llvm::acc::Clause::Y); \ | 
 |     RequiresConstantPositiveParameter(llvm::acc::Clause::Y, c.v); \ | 
 |   } | 
 |  | 
 | using ReductionOpsSet = | 
 |     Fortran::common::EnumSet<Fortran::parser::ReductionOperator::Operator, | 
 |         Fortran::parser::ReductionOperator::Operator_enumSize>; | 
 |  | 
 | static ReductionOpsSet reductionIntegerSet{ | 
 |     Fortran::parser::ReductionOperator::Operator::Plus, | 
 |     Fortran::parser::ReductionOperator::Operator::Multiply, | 
 |     Fortran::parser::ReductionOperator::Operator::Max, | 
 |     Fortran::parser::ReductionOperator::Operator::Min, | 
 |     Fortran::parser::ReductionOperator::Operator::Iand, | 
 |     Fortran::parser::ReductionOperator::Operator::Ior, | 
 |     Fortran::parser::ReductionOperator::Operator::Ieor}; | 
 |  | 
 | static ReductionOpsSet reductionRealSet{ | 
 |     Fortran::parser::ReductionOperator::Operator::Plus, | 
 |     Fortran::parser::ReductionOperator::Operator::Multiply, | 
 |     Fortran::parser::ReductionOperator::Operator::Max, | 
 |     Fortran::parser::ReductionOperator::Operator::Min}; | 
 |  | 
 | static ReductionOpsSet reductionComplexSet{ | 
 |     Fortran::parser::ReductionOperator::Operator::Plus, | 
 |     Fortran::parser::ReductionOperator::Operator::Multiply}; | 
 |  | 
 | static ReductionOpsSet reductionLogicalSet{ | 
 |     Fortran::parser::ReductionOperator::Operator::And, | 
 |     Fortran::parser::ReductionOperator::Operator::Or, | 
 |     Fortran::parser::ReductionOperator::Operator::Eqv, | 
 |     Fortran::parser::ReductionOperator::Operator::Neqv}; | 
 |  | 
 | namespace Fortran::semantics { | 
 |  | 
 | static constexpr inline AccClauseSet | 
 |     computeConstructOnlyAllowedAfterDeviceTypeClauses{ | 
 |         llvm::acc::Clause::ACCC_async, llvm::acc::Clause::ACCC_wait, | 
 |         llvm::acc::Clause::ACCC_num_gangs, llvm::acc::Clause::ACCC_num_workers, | 
 |         llvm::acc::Clause::ACCC_vector_length}; | 
 |  | 
 | static constexpr inline AccClauseSet loopOnlyAllowedAfterDeviceTypeClauses{ | 
 |     llvm::acc::Clause::ACCC_auto, llvm::acc::Clause::ACCC_collapse, | 
 |     llvm::acc::Clause::ACCC_independent, llvm::acc::Clause::ACCC_gang, | 
 |     llvm::acc::Clause::ACCC_seq, llvm::acc::Clause::ACCC_tile, | 
 |     llvm::acc::Clause::ACCC_vector, llvm::acc::Clause::ACCC_worker}; | 
 |  | 
 | static constexpr inline AccClauseSet updateOnlyAllowedAfterDeviceTypeClauses{ | 
 |     llvm::acc::Clause::ACCC_async, llvm::acc::Clause::ACCC_wait}; | 
 |  | 
 | static constexpr inline AccClauseSet routineOnlyAllowedAfterDeviceTypeClauses{ | 
 |     llvm::acc::Clause::ACCC_bind, llvm::acc::Clause::ACCC_gang, | 
 |     llvm::acc::Clause::ACCC_vector, llvm::acc::Clause::ACCC_worker, | 
 |     llvm::acc::Clause::ACCC_seq}; | 
 |  | 
 | static constexpr inline AccClauseSet routineMutuallyExclusiveClauses{ | 
 |     llvm::acc::Clause::ACCC_gang, llvm::acc::Clause::ACCC_worker, | 
 |     llvm::acc::Clause::ACCC_vector, llvm::acc::Clause::ACCC_seq}; | 
 |  | 
 | bool AccStructureChecker::CheckAllowedModifier(llvm::acc::Clause clause) { | 
 |   if (GetContext().directive == llvm::acc::ACCD_enter_data || | 
 |       GetContext().directive == llvm::acc::ACCD_exit_data) { | 
 |     context_.Say(GetContext().clauseSource, | 
 |         "Modifier is not allowed for the %s clause " | 
 |         "on the %s directive"_err_en_US, | 
 |         parser::ToUpperCaseLetters(getClauseName(clause).str()), | 
 |         ContextDirectiveAsFortran()); | 
 |     return true; | 
 |   } | 
 |   return false; | 
 | } | 
 |  | 
 | bool AccStructureChecker::IsComputeConstruct( | 
 |     llvm::acc::Directive directive) const { | 
 |   return directive == llvm::acc::ACCD_parallel || | 
 |       directive == llvm::acc::ACCD_parallel_loop || | 
 |       directive == llvm::acc::ACCD_serial || | 
 |       directive == llvm::acc::ACCD_serial_loop || | 
 |       directive == llvm::acc::ACCD_kernels || | 
 |       directive == llvm::acc::ACCD_kernels_loop; | 
 | } | 
 |  | 
 | bool AccStructureChecker::IsInsideComputeConstruct() const { | 
 |   if (dirContext_.size() <= 1) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   // Check all nested context skipping the first one. | 
 |   for (std::size_t i = dirContext_.size() - 1; i > 0; --i) { | 
 |     if (IsComputeConstruct(dirContext_[i - 1].directive)) { | 
 |       return true; | 
 |     } | 
 |   } | 
 |   return false; | 
 | } | 
 |  | 
 | void AccStructureChecker::CheckNotInComputeConstruct() { | 
 |   if (IsInsideComputeConstruct()) { | 
 |     context_.Say(GetContext().directiveSource, | 
 |         "Directive %s may not be called within a compute region"_err_en_US, | 
 |         ContextDirectiveAsFortran()); | 
 |   } | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::AccClause &x) { | 
 |   SetContextClause(x); | 
 | } | 
 |  | 
 | void AccStructureChecker::Leave(const parser::AccClauseList &) {} | 
 |  | 
 | void AccStructureChecker::Enter(const parser::OpenACCBlockConstruct &x) { | 
 |   const auto &beginBlockDir{std::get<parser::AccBeginBlockDirective>(x.t)}; | 
 |   const auto &endBlockDir{std::get<parser::AccEndBlockDirective>(x.t)}; | 
 |   const auto &beginAccBlockDir{ | 
 |       std::get<parser::AccBlockDirective>(beginBlockDir.t)}; | 
 |  | 
 |   CheckMatching(beginAccBlockDir, endBlockDir.v); | 
 |   PushContextAndClauseSets(beginAccBlockDir.source, beginAccBlockDir.v); | 
 | } | 
 |  | 
 | void AccStructureChecker::Leave(const parser::OpenACCBlockConstruct &x) { | 
 |   const auto &beginBlockDir{std::get<parser::AccBeginBlockDirective>(x.t)}; | 
 |   const auto &blockDir{std::get<parser::AccBlockDirective>(beginBlockDir.t)}; | 
 |   const parser::Block &block{std::get<parser::Block>(x.t)}; | 
 |   switch (blockDir.v) { | 
 |   case llvm::acc::Directive::ACCD_kernels: | 
 |   case llvm::acc::Directive::ACCD_parallel: | 
 |   case llvm::acc::Directive::ACCD_serial: | 
 |     // Restriction - line 1004-1005 | 
 |     CheckOnlyAllowedAfter(llvm::acc::Clause::ACCC_device_type, | 
 |         computeConstructOnlyAllowedAfterDeviceTypeClauses); | 
 |     // Restriction - line 1001 | 
 |     CheckNoBranching(block, GetContext().directive, blockDir.source); | 
 |     break; | 
 |   case llvm::acc::Directive::ACCD_data: | 
 |     // Restriction - 2.6.5 pt 1 | 
 |     // Only a warning is emitted here for portability reason. | 
 |     CheckRequireAtLeastOneOf(/*warnInsteadOfError=*/true); | 
 |     // Restriction is not formally in the specification but all compilers emit | 
 |     // an error and it is likely to be omitted from the spec. | 
 |     CheckNoBranching(block, GetContext().directive, blockDir.source); | 
 |     break; | 
 |   case llvm::acc::Directive::ACCD_host_data: | 
 |     // Restriction - line 1746 | 
 |     CheckRequireAtLeastOneOf(); | 
 |     break; | 
 |   default: | 
 |     break; | 
 |   } | 
 |   dirContext_.pop_back(); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter( | 
 |     const parser::OpenACCStandaloneDeclarativeConstruct &x) { | 
 |   const auto &declarativeDir{std::get<parser::AccDeclarativeDirective>(x.t)}; | 
 |   PushContextAndClauseSets(declarativeDir.source, declarativeDir.v); | 
 | } | 
 |  | 
 | void AccStructureChecker::Leave( | 
 |     const parser::OpenACCStandaloneDeclarativeConstruct &x) { | 
 |   // Restriction - line 2409 | 
 |   CheckAtLeastOneClause(); | 
 |  | 
 |   // Restriction - line 2417-2418 - In a Fortran module declaration section, | 
 |   // only create, copyin, device_resident, and link clauses are allowed. | 
 |   const auto &declarativeDir{std::get<parser::AccDeclarativeDirective>(x.t)}; | 
 |   const auto &scope{context_.FindScope(declarativeDir.source)}; | 
 |   const Scope &containingScope{GetProgramUnitContaining(scope)}; | 
 |   if (containingScope.kind() == Scope::Kind::Module) { | 
 |     for (auto cl : GetContext().actualClauses) { | 
 |       if (cl != llvm::acc::Clause::ACCC_create && | 
 |           cl != llvm::acc::Clause::ACCC_copyin && | 
 |           cl != llvm::acc::Clause::ACCC_device_resident && | 
 |           cl != llvm::acc::Clause::ACCC_link) { | 
 |         context_.Say(GetContext().directiveSource, | 
 |             "%s clause is not allowed on the %s directive in module " | 
 |             "declaration " | 
 |             "section"_err_en_US, | 
 |             parser::ToUpperCaseLetters( | 
 |                 llvm::acc::getOpenACCClauseName(cl).str()), | 
 |             ContextDirectiveAsFortran()); | 
 |       } | 
 |     } | 
 |   } | 
 |   dirContext_.pop_back(); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::OpenACCCombinedConstruct &x) { | 
 |   const auto &beginCombinedDir{ | 
 |       std::get<parser::AccBeginCombinedDirective>(x.t)}; | 
 |   const auto &combinedDir{ | 
 |       std::get<parser::AccCombinedDirective>(beginCombinedDir.t)}; | 
 |  | 
 |   // check matching, End directive is optional | 
 |   if (const auto &endCombinedDir{ | 
 |           std::get<std::optional<parser::AccEndCombinedDirective>>(x.t)}) { | 
 |     CheckMatching<parser::AccCombinedDirective>(combinedDir, endCombinedDir->v); | 
 |   } | 
 |  | 
 |   PushContextAndClauseSets(combinedDir.source, combinedDir.v); | 
 | } | 
 |  | 
 | void AccStructureChecker::Leave(const parser::OpenACCCombinedConstruct &x) { | 
 |   const auto &beginBlockDir{std::get<parser::AccBeginCombinedDirective>(x.t)}; | 
 |   const auto &combinedDir{ | 
 |       std::get<parser::AccCombinedDirective>(beginBlockDir.t)}; | 
 |   auto &doCons{std::get<std::optional<parser::DoConstruct>>(x.t)}; | 
 |   switch (combinedDir.v) { | 
 |   case llvm::acc::Directive::ACCD_kernels_loop: | 
 |   case llvm::acc::Directive::ACCD_parallel_loop: | 
 |   case llvm::acc::Directive::ACCD_serial_loop: | 
 |     // Restriction - line 1004-1005 | 
 |     CheckOnlyAllowedAfter(llvm::acc::Clause::ACCC_device_type, | 
 |         computeConstructOnlyAllowedAfterDeviceTypeClauses | | 
 |             loopOnlyAllowedAfterDeviceTypeClauses); | 
 |     if (doCons) { | 
 |       const parser::Block &block{std::get<parser::Block>(doCons->t)}; | 
 |       CheckNoBranching(block, GetContext().directive, beginBlockDir.source); | 
 |     } | 
 |     break; | 
 |   default: | 
 |     break; | 
 |   } | 
 |   dirContext_.pop_back(); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::OpenACCLoopConstruct &x) { | 
 |   const auto &beginDir{std::get<parser::AccBeginLoopDirective>(x.t)}; | 
 |   const auto &loopDir{std::get<parser::AccLoopDirective>(beginDir.t)}; | 
 |   PushContextAndClauseSets(loopDir.source, loopDir.v); | 
 | } | 
 |  | 
 | void AccStructureChecker::Leave(const parser::OpenACCLoopConstruct &x) { | 
 |   const auto &beginDir{std::get<parser::AccBeginLoopDirective>(x.t)}; | 
 |   const auto &loopDir{std::get<parser::AccLoopDirective>(beginDir.t)}; | 
 |   if (loopDir.v == llvm::acc::Directive::ACCD_loop) { | 
 |     // Restriction - line 1818-1819 | 
 |     CheckOnlyAllowedAfter(llvm::acc::Clause::ACCC_device_type, | 
 |         loopOnlyAllowedAfterDeviceTypeClauses); | 
 |     // Restriction - line 1834 | 
 |     CheckNotAllowedIfClause(llvm::acc::Clause::ACCC_seq, | 
 |         {llvm::acc::Clause::ACCC_gang, llvm::acc::Clause::ACCC_vector, | 
 |             llvm::acc::Clause::ACCC_worker}); | 
 |   } | 
 |   dirContext_.pop_back(); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::OpenACCStandaloneConstruct &x) { | 
 |   const auto &standaloneDir{std::get<parser::AccStandaloneDirective>(x.t)}; | 
 |   PushContextAndClauseSets(standaloneDir.source, standaloneDir.v); | 
 | } | 
 |  | 
 | void AccStructureChecker::Leave(const parser::OpenACCStandaloneConstruct &x) { | 
 |   const auto &standaloneDir{std::get<parser::AccStandaloneDirective>(x.t)}; | 
 |   switch (standaloneDir.v) { | 
 |   case llvm::acc::Directive::ACCD_enter_data: | 
 |   case llvm::acc::Directive::ACCD_exit_data: | 
 |     // Restriction - line 1310-1311 (ENTER DATA) | 
 |     // Restriction - line 1312-1313 (EXIT DATA) | 
 |     CheckRequireAtLeastOneOf(); | 
 |     break; | 
 |   case llvm::acc::Directive::ACCD_set: | 
 |     // Restriction - line 2610 | 
 |     CheckRequireAtLeastOneOf(); | 
 |     // Restriction - line 2602 | 
 |     CheckNotInComputeConstruct(); | 
 |     break; | 
 |   case llvm::acc::Directive::ACCD_update: | 
 |     // Restriction - line 2636 | 
 |     CheckRequireAtLeastOneOf(); | 
 |     // Restriction - line 2669 | 
 |     CheckOnlyAllowedAfter(llvm::acc::Clause::ACCC_device_type, | 
 |         updateOnlyAllowedAfterDeviceTypeClauses); | 
 |     break; | 
 |   case llvm::acc::Directive::ACCD_init: | 
 |   case llvm::acc::Directive::ACCD_shutdown: | 
 |     // Restriction - line 2525 (INIT) | 
 |     // Restriction - line 2561 (SHUTDOWN) | 
 |     CheckNotInComputeConstruct(); | 
 |     break; | 
 |   default: | 
 |     break; | 
 |   } | 
 |   dirContext_.pop_back(); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::OpenACCRoutineConstruct &x) { | 
 |   PushContextAndClauseSets(x.source, llvm::acc::Directive::ACCD_routine); | 
 |   const auto &optName{std::get<std::optional<parser::Name>>(x.t)}; | 
 |   if (!optName) { | 
 |     const auto &verbatim{std::get<parser::Verbatim>(x.t)}; | 
 |     const auto &scope{context_.FindScope(verbatim.source)}; | 
 |     const Scope &containingScope{GetProgramUnitContaining(scope)}; | 
 |     if (containingScope.kind() == Scope::Kind::Module) { | 
 |       context_.Say(GetContext().directiveSource, | 
 |           "ROUTINE directive without name must appear within the specification " | 
 |           "part of a subroutine or function definition, or within an interface " | 
 |           "body for a subroutine or function in an interface block"_err_en_US); | 
 |     } | 
 |   } | 
 | } | 
 | void AccStructureChecker::Leave(const parser::OpenACCRoutineConstruct &) { | 
 |   // Restriction - line 2790 | 
 |   CheckRequireAtLeastOneOf(); | 
 |   // Restriction - line 2788-2789 | 
 |   CheckOnlyAllowedAfter(llvm::acc::Clause::ACCC_device_type, | 
 |       routineOnlyAllowedAfterDeviceTypeClauses); | 
 |   dirContext_.pop_back(); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::OpenACCWaitConstruct &x) { | 
 |   const auto &verbatim{std::get<parser::Verbatim>(x.t)}; | 
 |   PushContextAndClauseSets(verbatim.source, llvm::acc::Directive::ACCD_wait); | 
 | } | 
 | void AccStructureChecker::Leave(const parser::OpenACCWaitConstruct &x) { | 
 |   dirContext_.pop_back(); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::OpenACCAtomicConstruct &x) { | 
 |   PushContextAndClauseSets(x.source, llvm::acc::Directive::ACCD_atomic); | 
 | } | 
 | void AccStructureChecker::Leave(const parser::OpenACCAtomicConstruct &x) { | 
 |   dirContext_.pop_back(); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::AccAtomicUpdate &x) { | 
 |   const parser::AssignmentStmt &assignment{ | 
 |       std::get<parser::Statement<parser::AssignmentStmt>>(x.t).statement}; | 
 |   const auto &var{std::get<parser::Variable>(assignment.t)}; | 
 |   const auto &expr{std::get<parser::Expr>(assignment.t)}; | 
 |   const auto *rhs{GetExpr(context_, expr)}; | 
 |   const auto *lhs{GetExpr(context_, var)}; | 
 |   if (lhs && rhs) { | 
 |     if (lhs->Rank() != 0) | 
 |       context_.Say(expr.source, | 
 |           "LHS of atomic update statement must be scalar"_err_en_US); | 
 |     if (rhs->Rank() != 0) | 
 |       context_.Say(var.GetSource(), | 
 |           "RHS of atomic update statement must be scalar"_err_en_US); | 
 |   } | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::OpenACCCacheConstruct &x) { | 
 |   const auto &verbatim = std::get<parser::Verbatim>(x.t); | 
 |   PushContextAndClauseSets(verbatim.source, llvm::acc::Directive::ACCD_cache); | 
 |   SetContextDirectiveSource(verbatim.source); | 
 |   if (loopNestLevel == 0) { | 
 |     context_.Say(verbatim.source, | 
 |           "The CACHE directive must be inside a loop"_err_en_US); | 
 |   } | 
 | } | 
 | void AccStructureChecker::Leave(const parser::OpenACCCacheConstruct &x) { | 
 |   dirContext_.pop_back(); | 
 | } | 
 |  | 
 | // Clause checkers | 
 | CHECK_SIMPLE_CLAUSE(Auto, ACCC_auto) | 
 | CHECK_SIMPLE_CLAUSE(Async, ACCC_async) | 
 | CHECK_SIMPLE_CLAUSE(Attach, ACCC_attach) | 
 | CHECK_SIMPLE_CLAUSE(Bind, ACCC_bind) | 
 | CHECK_SIMPLE_CLAUSE(Capture, ACCC_capture) | 
 | CHECK_SIMPLE_CLAUSE(Default, ACCC_default) | 
 | CHECK_SIMPLE_CLAUSE(DefaultAsync, ACCC_default_async) | 
 | CHECK_SIMPLE_CLAUSE(Delete, ACCC_delete) | 
 | CHECK_SIMPLE_CLAUSE(Detach, ACCC_detach) | 
 | CHECK_SIMPLE_CLAUSE(Device, ACCC_device) | 
 | CHECK_SIMPLE_CLAUSE(DeviceNum, ACCC_device_num) | 
 | CHECK_SIMPLE_CLAUSE(Finalize, ACCC_finalize) | 
 | CHECK_SIMPLE_CLAUSE(Firstprivate, ACCC_firstprivate) | 
 | CHECK_SIMPLE_CLAUSE(Host, ACCC_host) | 
 | CHECK_SIMPLE_CLAUSE(IfPresent, ACCC_if_present) | 
 | CHECK_SIMPLE_CLAUSE(Independent, ACCC_independent) | 
 | CHECK_SIMPLE_CLAUSE(NoCreate, ACCC_no_create) | 
 | CHECK_SIMPLE_CLAUSE(Nohost, ACCC_nohost) | 
 | CHECK_SIMPLE_CLAUSE(Private, ACCC_private) | 
 | CHECK_SIMPLE_CLAUSE(Read, ACCC_read) | 
 | CHECK_SIMPLE_CLAUSE(UseDevice, ACCC_use_device) | 
 | CHECK_SIMPLE_CLAUSE(Wait, ACCC_wait) | 
 | CHECK_SIMPLE_CLAUSE(Write, ACCC_write) | 
 | CHECK_SIMPLE_CLAUSE(Unknown, ACCC_unknown) | 
 |  | 
 | void AccStructureChecker::CheckMultipleOccurrenceInDeclare( | 
 |     const parser::AccObjectList &list, llvm::acc::Clause clause) { | 
 |   if (GetContext().directive != llvm::acc::Directive::ACCD_declare) | 
 |     return; | 
 |   for (const auto &object : list.v) { | 
 |     common::visit( | 
 |         common::visitors{ | 
 |             [&](const parser::Designator &designator) { | 
 |               if (const auto *name = getDesignatorNameIfDataRef(designator)) { | 
 |                 if (declareSymbols.contains(&name->symbol->GetUltimate())) { | 
 |                   if (declareSymbols[&name->symbol->GetUltimate()] == clause) { | 
 |                     context_.Warn(common::UsageWarning::OpenAccUsage, | 
 |                         GetContext().clauseSource, | 
 |                         "'%s' in the %s clause is already present in the same clause in this module"_warn_en_US, | 
 |                         name->symbol->name(), | 
 |                         parser::ToUpperCaseLetters( | 
 |                             llvm::acc::getOpenACCClauseName(clause).str())); | 
 |                   } else { | 
 |                     context_.Say(GetContext().clauseSource, | 
 |                         "'%s' in the %s clause is already present in another " | 
 |                         "%s clause in this module"_err_en_US, | 
 |                         name->symbol->name(), | 
 |                         parser::ToUpperCaseLetters( | 
 |                             llvm::acc::getOpenACCClauseName(clause).str()), | 
 |                         parser::ToUpperCaseLetters( | 
 |                             llvm::acc::getOpenACCClauseName( | 
 |                                 declareSymbols[&name->symbol->GetUltimate()]) | 
 |                                 .str())); | 
 |                   } | 
 |                 } | 
 |                 declareSymbols.insert({&name->symbol->GetUltimate(), clause}); | 
 |               } | 
 |             }, | 
 |             [&](const parser::Name &name) { | 
 |               // TODO: check common block | 
 |             }}, | 
 |         object.u); | 
 |   } | 
 | } | 
 |  | 
 | void AccStructureChecker::CheckMultipleOccurrenceInDeclare( | 
 |     const parser::AccObjectListWithModifier &list, llvm::acc::Clause clause) { | 
 |   const auto &objectList = std::get<Fortran::parser::AccObjectList>(list.t); | 
 |   CheckMultipleOccurrenceInDeclare(objectList, clause); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::AccClause::Create &c) { | 
 |   CheckAllowed(llvm::acc::Clause::ACCC_create); | 
 |   const auto &modifierClause{c.v}; | 
 |   if (const auto &modifier{ | 
 |           std::get<std::optional<parser::AccDataModifier>>(modifierClause.t)}) { | 
 |     if (modifier->v != parser::AccDataModifier::Modifier::Zero) { | 
 |       context_.Say(GetContext().clauseSource, | 
 |           "Only the ZERO modifier is allowed for the %s clause " | 
 |           "on the %s directive"_err_en_US, | 
 |           parser::ToUpperCaseLetters( | 
 |               llvm::acc::getOpenACCClauseName(llvm::acc::Clause::ACCC_create) | 
 |                   .str()), | 
 |           ContextDirectiveAsFortran()); | 
 |     } | 
 |     if (GetContext().directive == llvm::acc::Directive::ACCD_declare) { | 
 |       context_.Say(GetContext().clauseSource, | 
 |           "The ZERO modifier is not allowed for the %s clause " | 
 |           "on the %s directive"_err_en_US, | 
 |           parser::ToUpperCaseLetters( | 
 |               llvm::acc::getOpenACCClauseName(llvm::acc::Clause::ACCC_create) | 
 |                   .str()), | 
 |           ContextDirectiveAsFortran()); | 
 |     } | 
 |   } | 
 |   CheckMultipleOccurrenceInDeclare( | 
 |       modifierClause, llvm::acc::Clause::ACCC_create); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::AccClause::Copyin &c) { | 
 |   CheckAllowed(llvm::acc::Clause::ACCC_copyin); | 
 |   const auto &modifierClause{c.v}; | 
 |   if (const auto &modifier{ | 
 |           std::get<std::optional<parser::AccDataModifier>>(modifierClause.t)}) { | 
 |     if (CheckAllowedModifier(llvm::acc::Clause::ACCC_copyin)) { | 
 |       return; | 
 |     } | 
 |     if (modifier->v != parser::AccDataModifier::Modifier::ReadOnly) { | 
 |       context_.Say(GetContext().clauseSource, | 
 |           "Only the READONLY modifier is allowed for the %s clause " | 
 |           "on the %s directive"_err_en_US, | 
 |           parser::ToUpperCaseLetters( | 
 |               llvm::acc::getOpenACCClauseName(llvm::acc::Clause::ACCC_copyin) | 
 |                   .str()), | 
 |           ContextDirectiveAsFortran()); | 
 |     } | 
 |   } | 
 |   CheckMultipleOccurrenceInDeclare( | 
 |       modifierClause, llvm::acc::Clause::ACCC_copyin); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::AccClause::Copyout &c) { | 
 |   CheckAllowed(llvm::acc::Clause::ACCC_copyout); | 
 |   const auto &modifierClause{c.v}; | 
 |   if (const auto &modifier{ | 
 |           std::get<std::optional<parser::AccDataModifier>>(modifierClause.t)}) { | 
 |     if (CheckAllowedModifier(llvm::acc::Clause::ACCC_copyout)) { | 
 |       return; | 
 |     } | 
 |     if (modifier->v != parser::AccDataModifier::Modifier::Zero) { | 
 |       context_.Say(GetContext().clauseSource, | 
 |           "Only the ZERO modifier is allowed for the %s clause " | 
 |           "on the %s directive"_err_en_US, | 
 |           parser::ToUpperCaseLetters( | 
 |               llvm::acc::getOpenACCClauseName(llvm::acc::Clause::ACCC_copyout) | 
 |                   .str()), | 
 |           ContextDirectiveAsFortran()); | 
 |     } | 
 |     if (GetContext().directive == llvm::acc::Directive::ACCD_declare) { | 
 |       context_.Say(GetContext().clauseSource, | 
 |           "The ZERO modifier is not allowed for the %s clause " | 
 |           "on the %s directive"_err_en_US, | 
 |           parser::ToUpperCaseLetters( | 
 |               llvm::acc::getOpenACCClauseName(llvm::acc::Clause::ACCC_copyout) | 
 |                   .str()), | 
 |           ContextDirectiveAsFortran()); | 
 |     } | 
 |   } | 
 |   CheckMultipleOccurrenceInDeclare( | 
 |       modifierClause, llvm::acc::Clause::ACCC_copyout); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::AccClause::DeviceType &d) { | 
 |   CheckAllowed(llvm::acc::Clause::ACCC_device_type); | 
 |   if (GetContext().directive == llvm::acc::Directive::ACCD_set && | 
 |       d.v.v.size() > 1) { | 
 |     context_.Say(GetContext().clauseSource, | 
 |         "The %s clause on the %s directive accepts only one value"_err_en_US, | 
 |         parser::ToUpperCaseLetters( | 
 |             llvm::acc::getOpenACCClauseName(llvm::acc::Clause::ACCC_device_type) | 
 |                 .str()), | 
 |         ContextDirectiveAsFortran()); | 
 |   } | 
 |   ResetCrtGroup(); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::AccClause::Seq &g) { | 
 |   llvm::acc::Clause crtClause = llvm::acc::Clause::ACCC_seq; | 
 |   if (GetContext().directive == llvm::acc::Directive::ACCD_routine) { | 
 |     CheckMutuallyExclusivePerGroup(crtClause, | 
 |         llvm::acc::Clause::ACCC_device_type, routineMutuallyExclusiveClauses); | 
 |   } | 
 |   CheckAllowed(crtClause); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::AccClause::Vector &g) { | 
 |   llvm::acc::Clause crtClause = llvm::acc::Clause::ACCC_vector; | 
 |   if (GetContext().directive == llvm::acc::Directive::ACCD_routine) { | 
 |     CheckMutuallyExclusivePerGroup(crtClause, | 
 |         llvm::acc::Clause::ACCC_device_type, routineMutuallyExclusiveClauses); | 
 |   } | 
 |   CheckAllowed(crtClause); | 
 |   if (GetContext().directive != llvm::acc::Directive::ACCD_routine) { | 
 |     CheckAllowedOncePerGroup(crtClause, llvm::acc::Clause::ACCC_device_type); | 
 |   } | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::AccClause::Worker &g) { | 
 |   llvm::acc::Clause crtClause = llvm::acc::Clause::ACCC_worker; | 
 |   if (GetContext().directive == llvm::acc::Directive::ACCD_routine) { | 
 |     CheckMutuallyExclusivePerGroup(crtClause, | 
 |         llvm::acc::Clause::ACCC_device_type, routineMutuallyExclusiveClauses); | 
 |   } | 
 |   CheckAllowed(crtClause); | 
 |   if (GetContext().directive != llvm::acc::Directive::ACCD_routine) { | 
 |     CheckAllowedOncePerGroup(crtClause, llvm::acc::Clause::ACCC_device_type); | 
 |   } | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::AccClause::Tile &g) { | 
 |   CheckAllowed(llvm::acc::Clause::ACCC_tile); | 
 |   CheckAllowedOncePerGroup( | 
 |       llvm::acc::Clause::ACCC_tile, llvm::acc::Clause::ACCC_device_type); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::AccClause::Gang &g) { | 
 |   llvm::acc::Clause crtClause = llvm::acc::Clause::ACCC_gang; | 
 |   if (GetContext().directive == llvm::acc::Directive::ACCD_routine) { | 
 |     CheckMutuallyExclusivePerGroup(crtClause, | 
 |         llvm::acc::Clause::ACCC_device_type, routineMutuallyExclusiveClauses); | 
 |   } | 
 |   CheckAllowed(crtClause); | 
 |   if (GetContext().directive != llvm::acc::Directive::ACCD_routine) { | 
 |     CheckAllowedOncePerGroup(crtClause, llvm::acc::Clause::ACCC_device_type); | 
 |   } | 
 |  | 
 |   if (g.v) { | 
 |     bool hasNum = false; | 
 |     bool hasDim = false; | 
 |     bool hasStatic = false; | 
 |     const Fortran::parser::AccGangArgList &x = *g.v; | 
 |     for (const Fortran::parser::AccGangArg &gangArg : x.v) { | 
 |       if (std::get_if<Fortran::parser::AccGangArg::Num>(&gangArg.u)) { | 
 |         hasNum = true; | 
 |       } else if (std::get_if<Fortran::parser::AccGangArg::Dim>(&gangArg.u)) { | 
 |         hasDim = true; | 
 |       } else if (std::get_if<Fortran::parser::AccGangArg::Static>(&gangArg.u)) { | 
 |         hasStatic = true; | 
 |       } | 
 |     } | 
 |  | 
 |     if (GetContext().directive == llvm::acc::Directive::ACCD_routine && | 
 |         (hasStatic || hasNum)) { | 
 |       context_.Say(GetContext().clauseSource, | 
 |           "Only the dim argument is allowed on the %s clause on the %s directive"_err_en_US, | 
 |           parser::ToUpperCaseLetters( | 
 |               llvm::acc::getOpenACCClauseName(llvm::acc::Clause::ACCC_gang) | 
 |                   .str()), | 
 |           ContextDirectiveAsFortran()); | 
 |     } | 
 |  | 
 |     if (hasDim && hasNum) { | 
 |       context_.Say(GetContext().clauseSource, | 
 |           "The num argument is not allowed when dim is specified"_err_en_US); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::AccClause::NumGangs &n) { | 
 |   CheckAllowed(llvm::acc::Clause::ACCC_num_gangs, | 
 |       /*warnInsteadOfError=*/GetContext().directive == | 
 |               llvm::acc::Directive::ACCD_serial || | 
 |           GetContext().directive == llvm::acc::Directive::ACCD_serial_loop); | 
 |   CheckAllowedOncePerGroup( | 
 |       llvm::acc::Clause::ACCC_num_gangs, llvm::acc::Clause::ACCC_device_type); | 
 |  | 
 |   if (n.v.size() > 3) | 
 |     context_.Say(GetContext().clauseSource, | 
 |         "NUM_GANGS clause accepts a maximum of 3 arguments"_err_en_US); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::AccClause::NumWorkers &n) { | 
 |   CheckAllowed(llvm::acc::Clause::ACCC_num_workers, | 
 |       /*warnInsteadOfError=*/GetContext().directive == | 
 |               llvm::acc::Directive::ACCD_serial || | 
 |           GetContext().directive == llvm::acc::Directive::ACCD_serial_loop); | 
 |   CheckAllowedOncePerGroup( | 
 |       llvm::acc::Clause::ACCC_num_workers, llvm::acc::Clause::ACCC_device_type); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::AccClause::VectorLength &n) { | 
 |   CheckAllowed(llvm::acc::Clause::ACCC_vector_length, | 
 |       /*warnInsteadOfError=*/GetContext().directive == | 
 |               llvm::acc::Directive::ACCD_serial || | 
 |           GetContext().directive == llvm::acc::Directive::ACCD_serial_loop); | 
 |   CheckAllowedOncePerGroup(llvm::acc::Clause::ACCC_vector_length, | 
 |       llvm::acc::Clause::ACCC_device_type); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::AccClause::Reduction &reduction) { | 
 |   CheckAllowed(llvm::acc::Clause::ACCC_reduction); | 
 |  | 
 |   // From OpenACC 3.3 | 
 |   // At a minimum, the supported data types include Fortran logical as well as | 
 |   // the numerical data types (e.g. integer, real, double precision, complex). | 
 |   // However, for each reduction operator, the supported data types include only | 
 |   // the types permitted as operands to the corresponding operator in the base | 
 |   // language where (1) for max and min, the corresponding operator is less-than | 
 |   // and (2) for other operators, the operands and the result are the same type. | 
 |   // | 
 |   // The following check that the reduction operator is supported with the given | 
 |   // type. | 
 |   const parser::AccObjectListWithReduction &list{reduction.v}; | 
 |   const auto &op{std::get<parser::ReductionOperator>(list.t)}; | 
 |   const auto &objects{std::get<parser::AccObjectList>(list.t)}; | 
 |  | 
 |   for (const auto &object : objects.v) { | 
 |     common::visit( | 
 |         common::visitors{ | 
 |             [&](const parser::Designator &designator) { | 
 |               if (const auto *name = getDesignatorNameIfDataRef(designator)) { | 
 |                 if (name->symbol) { | 
 |                   const auto *type{name->symbol->GetType()}; | 
 |                   if (type->IsNumeric(TypeCategory::Integer) && | 
 |                       !reductionIntegerSet.test(op.v)) { | 
 |                     context_.Say(GetContext().clauseSource, | 
 |                         "reduction operator not supported for integer type"_err_en_US); | 
 |                   } else if (type->IsNumeric(TypeCategory::Real) && | 
 |                       !reductionRealSet.test(op.v)) { | 
 |                     context_.Say(GetContext().clauseSource, | 
 |                         "reduction operator not supported for real type"_err_en_US); | 
 |                   } else if (type->IsNumeric(TypeCategory::Complex) && | 
 |                       !reductionComplexSet.test(op.v)) { | 
 |                     context_.Say(GetContext().clauseSource, | 
 |                         "reduction operator not supported for complex type"_err_en_US); | 
 |                   } else if (type->category() == | 
 |                           Fortran::semantics::DeclTypeSpec::Category::Logical && | 
 |                       !reductionLogicalSet.test(op.v)) { | 
 |                     context_.Say(GetContext().clauseSource, | 
 |                         "reduction operator not supported for logical type"_err_en_US); | 
 |                   } | 
 |                   // TODO: check composite type. | 
 |                 } | 
 |               } | 
 |             }, | 
 |             [&](const Fortran::parser::Name &name) { | 
 |               // TODO: check common block | 
 |             }}, | 
 |         object.u); | 
 |   } | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::AccClause::Self &x) { | 
 |   CheckAllowed(llvm::acc::Clause::ACCC_self); | 
 |   const std::optional<parser::AccSelfClause> &accSelfClause = x.v; | 
 |   if (GetContext().directive == llvm::acc::Directive::ACCD_update && | 
 |       ((accSelfClause && | 
 |            std::holds_alternative<std::optional<parser::ScalarLogicalExpr>>( | 
 |                (*accSelfClause).u)) || | 
 |           !accSelfClause)) { | 
 |     context_.Say(GetContext().clauseSource, | 
 |         "SELF clause on the %s directive must have a var-list"_err_en_US, | 
 |         ContextDirectiveAsFortran()); | 
 |   } else if (GetContext().directive != llvm::acc::Directive::ACCD_update && | 
 |       accSelfClause && | 
 |       std::holds_alternative<parser::AccObjectList>((*accSelfClause).u)) { | 
 |     const auto &accObjectList = | 
 |         std::get<parser::AccObjectList>((*accSelfClause).u); | 
 |     if (accObjectList.v.size() != 1) { | 
 |       context_.Say(GetContext().clauseSource, | 
 |           "SELF clause on the %s directive only accepts optional scalar logical" | 
 |           " expression"_err_en_US, | 
 |           ContextDirectiveAsFortran()); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::AccClause::Collapse &x) { | 
 |   CheckAllowed(llvm::acc::Clause::ACCC_collapse); | 
 |   CheckAllowedOncePerGroup( | 
 |       llvm::acc::Clause::ACCC_collapse, llvm::acc::Clause::ACCC_device_type); | 
 |   const parser::AccCollapseArg &accCollapseArg = x.v; | 
 |   const auto &collapseValue{ | 
 |       std::get<parser::ScalarIntConstantExpr>(accCollapseArg.t)}; | 
 |   RequiresConstantPositiveParameter( | 
 |       llvm::acc::Clause::ACCC_collapse, collapseValue); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::AccClause::Present &x) { | 
 |   CheckAllowed(llvm::acc::Clause::ACCC_present); | 
 |   CheckMultipleOccurrenceInDeclare(x.v, llvm::acc::Clause::ACCC_present); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::AccClause::Copy &x) { | 
 |   CheckAllowed(llvm::acc::Clause::ACCC_copy); | 
 |   CheckMultipleOccurrenceInDeclare(x.v, llvm::acc::Clause::ACCC_copy); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::AccClause::Deviceptr &x) { | 
 |   CheckAllowed(llvm::acc::Clause::ACCC_deviceptr); | 
 |   CheckMultipleOccurrenceInDeclare(x.v, llvm::acc::Clause::ACCC_deviceptr); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::AccClause::DeviceResident &x) { | 
 |   CheckAllowed(llvm::acc::Clause::ACCC_device_resident); | 
 |   CheckMultipleOccurrenceInDeclare( | 
 |       x.v, llvm::acc::Clause::ACCC_device_resident); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::AccClause::Link &x) { | 
 |   CheckAllowed(llvm::acc::Clause::ACCC_link); | 
 |   CheckMultipleOccurrenceInDeclare(x.v, llvm::acc::Clause::ACCC_link); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::AccClause::Shortloop &x) { | 
 |   if (CheckAllowed(llvm::acc::Clause::ACCC_shortloop)) { | 
 |     context_.Warn(common::UsageWarning::OpenAccUsage, GetContext().clauseSource, | 
 |         "Non-standard shortloop clause ignored"_warn_en_US); | 
 |   } | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::AccClause::If &x) { | 
 |   CheckAllowed(llvm::acc::Clause::ACCC_if); | 
 |   if (const auto *expr{GetExpr(x.v)}) { | 
 |     if (auto type{expr->GetType()}) { | 
 |       if (type->category() == TypeCategory::Integer || | 
 |           type->category() == TypeCategory::Logical) { | 
 |         return; // LOGICAL and INTEGER type supported for the if clause. | 
 |       } | 
 |     } | 
 |   } | 
 |   context_.Say( | 
 |       GetContext().clauseSource, "Must have LOGICAL or INTEGER type"_err_en_US); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::OpenACCEndConstruct &x) { | 
 |   context_.Warn(common::UsageWarning::OpenAccUsage, x.source, | 
 |       "Misplaced OpenACC end directive"_warn_en_US); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::Module &) { | 
 |   declareSymbols.clear(); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::FunctionSubprogram &x) { | 
 |   declareSymbols.clear(); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::SubroutineSubprogram &) { | 
 |   declareSymbols.clear(); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::SeparateModuleSubprogram &) { | 
 |   declareSymbols.clear(); | 
 | } | 
 |  | 
 | void AccStructureChecker::Enter(const parser::DoConstruct &) { | 
 |   ++loopNestLevel; | 
 | } | 
 |  | 
 | void AccStructureChecker::Leave(const parser::DoConstruct &) { | 
 |   --loopNestLevel; | 
 | } | 
 |  | 
 | llvm::StringRef AccStructureChecker::getDirectiveName( | 
 |     llvm::acc::Directive directive) { | 
 |   return llvm::acc::getOpenACCDirectiveName(directive); | 
 | } | 
 |  | 
 | llvm::StringRef AccStructureChecker::getClauseName(llvm::acc::Clause clause) { | 
 |   return llvm::acc::getOpenACCClauseName(clause); | 
 | } | 
 |  | 
 | } // namespace Fortran::semantics |