| //===-- lib/Semantics/check-omp-loop.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 |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // Semantic checks for constructs and clauses related to loops. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "check-omp-structure.h" |
| |
| #include "check-directive-structure.h" |
| |
| #include "flang/Common/idioms.h" |
| #include "flang/Common/visit.h" |
| #include "flang/Parser/char-block.h" |
| #include "flang/Parser/openmp-utils.h" |
| #include "flang/Parser/parse-tree-visitor.h" |
| #include "flang/Parser/parse-tree.h" |
| #include "flang/Parser/tools.h" |
| #include "flang/Semantics/openmp-modifiers.h" |
| #include "flang/Semantics/openmp-utils.h" |
| #include "flang/Semantics/semantics.h" |
| #include "flang/Semantics/symbol.h" |
| #include "flang/Semantics/tools.h" |
| #include "flang/Semantics/type.h" |
| |
| #include "llvm/Frontend/OpenMP/OMP.h" |
| |
| #include <cstdint> |
| #include <map> |
| #include <optional> |
| #include <string> |
| #include <tuple> |
| #include <variant> |
| |
| namespace { |
| using namespace Fortran; |
| |
| class AssociatedLoopChecker { |
| public: |
| AssociatedLoopChecker( |
| semantics::SemanticsContext &context, std::int64_t level) |
| : context_{context}, level_{level} {} |
| |
| template <typename T> bool Pre(const T &) { return true; } |
| template <typename T> void Post(const T &) {} |
| |
| bool Pre(const parser::DoConstruct &dc) { |
| level_--; |
| const auto &doStmt{ |
| std::get<parser::Statement<parser::NonLabelDoStmt>>(dc.t)}; |
| const auto &constructName{ |
| std::get<std::optional<parser::Name>>(doStmt.statement.t)}; |
| if (constructName) { |
| constructNamesAndLevels_.emplace( |
| constructName.value().ToString(), level_); |
| } |
| if (level_ >= 0) { |
| if (dc.IsDoWhile()) { |
| context_.Say(doStmt.source, |
| "The associated loop of a loop-associated directive cannot be a DO WHILE."_err_en_US); |
| } |
| if (!dc.GetLoopControl()) { |
| context_.Say(doStmt.source, |
| "The associated loop of a loop-associated directive cannot be a DO without control."_err_en_US); |
| } |
| } |
| return true; |
| } |
| |
| void Post(const parser::DoConstruct &dc) { level_++; } |
| |
| bool Pre(const parser::CycleStmt &cyclestmt) { |
| std::map<std::string, std::int64_t>::iterator it; |
| bool err{false}; |
| if (cyclestmt.v) { |
| it = constructNamesAndLevels_.find(cyclestmt.v->source.ToString()); |
| err = (it != constructNamesAndLevels_.end() && it->second > 0); |
| } else { // If there is no label then use the level of the last enclosing DO |
| err = level_ > 0; |
| } |
| if (err) { |
| context_.Say(*source_, |
| "CYCLE statement to non-innermost associated loop of an OpenMP DO " |
| "construct"_err_en_US); |
| } |
| return true; |
| } |
| |
| bool Pre(const parser::ExitStmt &exitStmt) { |
| std::map<std::string, std::int64_t>::iterator it; |
| bool err{false}; |
| if (exitStmt.v) { |
| it = constructNamesAndLevels_.find(exitStmt.v->source.ToString()); |
| err = (it != constructNamesAndLevels_.end() && it->second >= 0); |
| } else { // If there is no label then use the level of the last enclosing DO |
| err = level_ >= 0; |
| } |
| if (err) { |
| context_.Say(*source_, |
| "EXIT statement terminates associated loop of an OpenMP DO " |
| "construct"_err_en_US); |
| } |
| return true; |
| } |
| |
| bool Pre(const parser::Statement<parser::ActionStmt> &actionstmt) { |
| source_ = &actionstmt.source; |
| return true; |
| } |
| |
| private: |
| semantics::SemanticsContext &context_; |
| const parser::CharBlock *source_; |
| std::int64_t level_; |
| std::map<std::string, std::int64_t> constructNamesAndLevels_; |
| }; |
| } // namespace |
| |
| namespace Fortran::semantics { |
| |
| using namespace Fortran::semantics::omp; |
| |
| void OmpStructureChecker::HasInvalidDistributeNesting( |
| const parser::OpenMPLoopConstruct &x) { |
| bool violation{false}; |
| const parser::OmpDirectiveName &beginName{x.BeginDir().DirName()}; |
| if (llvm::omp::topDistributeSet.test(beginName.v)) { |
| // `distribute` region has to be nested |
| if (!CurrentDirectiveIsNested()) { |
| violation = true; |
| } else { |
| // `distribute` region has to be strictly nested inside `teams` |
| if (!llvm::omp::bottomTeamsSet.test(GetContextParent().directive)) { |
| violation = true; |
| } |
| } |
| } |
| if (violation) { |
| context_.Say(beginName.source, |
| "`DISTRIBUTE` region has to be strictly nested inside `TEAMS` " |
| "region."_err_en_US); |
| } |
| } |
| void OmpStructureChecker::HasInvalidLoopBinding( |
| const parser::OpenMPLoopConstruct &x) { |
| const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()}; |
| const parser::OmpDirectiveName &beginName{beginSpec.DirName()}; |
| |
| auto teamsBindingChecker = [&](parser::MessageFixedText msg) { |
| for (const auto &clause : beginSpec.Clauses().v) { |
| if (const auto *bindClause{ |
| std::get_if<parser::OmpClause::Bind>(&clause.u)}) { |
| if (bindClause->v.v != parser::OmpBindClause::Binding::Teams) { |
| context_.Say(beginName.source, msg); |
| } |
| } |
| } |
| }; |
| |
| if (llvm::omp::Directive::OMPD_loop == beginName.v && |
| CurrentDirectiveIsNested() && |
| llvm::omp::bottomTeamsSet.test(GetContextParent().directive)) { |
| teamsBindingChecker( |
| "`BIND(TEAMS)` must be specified since the `LOOP` region is " |
| "strictly nested inside a `TEAMS` region."_err_en_US); |
| } |
| |
| if (OmpDirectiveSet{ |
| llvm::omp::OMPD_teams_loop, llvm::omp::OMPD_target_teams_loop} |
| .test(beginName.v)) { |
| teamsBindingChecker( |
| "`BIND(TEAMS)` must be specified since the `LOOP` directive is " |
| "combined with a `TEAMS` construct."_err_en_US); |
| } |
| } |
| |
| void OmpStructureChecker::CheckSIMDNest(const parser::OpenMPConstruct &c) { |
| // Check the following: |
| // The only OpenMP constructs that can be encountered during execution of |
| // a simd region are the `atomic` construct, the `loop` construct, the `simd` |
| // construct and the `ordered` construct with the `simd` clause. |
| |
| // Check if the parent context has the SIMD clause |
| // Please note that we use GetContext() instead of GetContextParent() |
| // because PushContextAndClauseSets() has not been called on the |
| // current context yet. |
| // TODO: Check for declare simd regions. |
| bool eligibleSIMD{false}; |
| common::visit( |
| common::visitors{ |
| // Allow `!$OMP ORDERED SIMD` |
| [&](const parser::OmpBlockConstruct &c) { |
| const parser::OmpDirectiveSpecification &beginSpec{c.BeginDir()}; |
| if (beginSpec.DirId() == llvm::omp::Directive::OMPD_ordered) { |
| for (const auto &clause : beginSpec.Clauses().v) { |
| if (std::get_if<parser::OmpClause::Simd>(&clause.u)) { |
| eligibleSIMD = true; |
| break; |
| } |
| } |
| } |
| }, |
| [&](const parser::OpenMPStandaloneConstruct &c) { |
| if (auto *ssc{std::get_if<parser::OpenMPSimpleStandaloneConstruct>( |
| &c.u)}) { |
| llvm::omp::Directive dirId{ssc->v.DirId()}; |
| if (dirId == llvm::omp::Directive::OMPD_ordered) { |
| for (const parser::OmpClause &x : ssc->v.Clauses().v) { |
| if (x.Id() == llvm::omp::Clause::OMPC_simd) { |
| eligibleSIMD = true; |
| break; |
| } |
| } |
| } else if (dirId == llvm::omp::Directive::OMPD_scan) { |
| eligibleSIMD = true; |
| } |
| } |
| }, |
| // Allowing SIMD and loop construct |
| [&](const parser::OpenMPLoopConstruct &c) { |
| const auto &beginName{c.BeginDir().DirName()}; |
| if (beginName.v == llvm::omp::Directive::OMPD_simd || |
| beginName.v == llvm::omp::Directive::OMPD_do_simd || |
| beginName.v == llvm::omp::Directive::OMPD_loop) { |
| eligibleSIMD = true; |
| } |
| }, |
| [&](const parser::OpenMPAtomicConstruct &c) { |
| // Allow `!$OMP ATOMIC` |
| eligibleSIMD = true; |
| }, |
| [&](const auto &c) {}, |
| }, |
| c.u); |
| if (!eligibleSIMD) { |
| context_.Say(parser::omp::GetOmpDirectiveName(c).source, |
| "The only OpenMP constructs that can be encountered during execution " |
| "of a 'SIMD' region are the `ATOMIC` construct, the `LOOP` construct, " |
| "the `SIMD` construct, the `SCAN` construct and the `ORDERED` " |
| "construct with the `SIMD` clause."_err_en_US); |
| } |
| } |
| |
| void OmpStructureChecker::Enter(const parser::OpenMPLoopConstruct &x) { |
| loopStack_.push_back(&x); |
| |
| const parser::OmpDirectiveName &beginName{x.BeginDir().DirName()}; |
| PushContextAndClauseSets(beginName.source, beginName.v); |
| |
| // Check matching, end directive is optional |
| if (auto &endSpec{x.EndDir()}) { |
| CheckMatching<parser::OmpDirectiveName>(beginName, endSpec->DirName()); |
| |
| AddEndDirectiveClauses(endSpec->Clauses()); |
| } |
| |
| if (llvm::omp::allSimdSet.test(GetContext().directive)) { |
| EnterDirectiveNest(SIMDNest); |
| } |
| |
| // Combined target loop constructs are target device constructs. Keep track of |
| // whether any such construct has been visited to later check that REQUIRES |
| // directives for target-related options don't appear after them. |
| if (llvm::omp::allTargetSet.test(beginName.v)) { |
| deviceConstructFound_ = true; |
| } |
| |
| if (beginName.v == llvm::omp::Directive::OMPD_do) { |
| // 2.7.1 do-clause -> private-clause | |
| // firstprivate-clause | |
| // lastprivate-clause | |
| // linear-clause | |
| // reduction-clause | |
| // schedule-clause | |
| // collapse-clause | |
| // ordered-clause |
| |
| // nesting check |
| HasInvalidWorksharingNesting( |
| beginName.source, llvm::omp::nestedWorkshareErrSet); |
| } |
| SetLoopInfo(x); |
| |
| auto &optLoopCons = std::get<std::optional<parser::NestedConstruct>>(x.t); |
| if (optLoopCons.has_value()) { |
| if (const auto &doConstruct{ |
| std::get_if<parser::DoConstruct>(&*optLoopCons)}) { |
| const auto &doBlock{std::get<parser::Block>(doConstruct->t)}; |
| CheckNoBranching(doBlock, beginName.v, beginName.source); |
| } |
| } |
| CheckLoopItrVariableIsInt(x); |
| CheckAssociatedLoopConstraints(x); |
| HasInvalidDistributeNesting(x); |
| HasInvalidLoopBinding(x); |
| if (CurrentDirectiveIsNested() && |
| llvm::omp::bottomTeamsSet.test(GetContextParent().directive)) { |
| HasInvalidTeamsNesting(beginName.v, beginName.source); |
| } |
| if (beginName.v == llvm::omp::Directive::OMPD_distribute_parallel_do_simd || |
| beginName.v == llvm::omp::Directive::OMPD_distribute_simd) { |
| CheckDistLinear(x); |
| } |
| } |
| |
| const parser::Name OmpStructureChecker::GetLoopIndex( |
| const parser::DoConstruct *x) { |
| using Bounds = parser::LoopControl::Bounds; |
| return std::get<Bounds>(x->GetLoopControl()->u).name.thing; |
| } |
| |
| void OmpStructureChecker::SetLoopInfo(const parser::OpenMPLoopConstruct &x) { |
| auto &optLoopCons = std::get<std::optional<parser::NestedConstruct>>(x.t); |
| if (optLoopCons.has_value()) { |
| if (const auto &loopConstruct{ |
| std::get_if<parser::DoConstruct>(&*optLoopCons)}) { |
| const parser::DoConstruct *loop{&*loopConstruct}; |
| if (loop && loop->IsDoNormal()) { |
| const parser::Name &itrVal{GetLoopIndex(loop)}; |
| SetLoopIv(itrVal.symbol); |
| } |
| } |
| } |
| } |
| |
| void OmpStructureChecker::CheckLoopItrVariableIsInt( |
| const parser::OpenMPLoopConstruct &x) { |
| auto &optLoopCons = std::get<std::optional<parser::NestedConstruct>>(x.t); |
| if (optLoopCons.has_value()) { |
| if (const auto &loopConstruct{ |
| std::get_if<parser::DoConstruct>(&*optLoopCons)}) { |
| |
| for (const parser::DoConstruct *loop{&*loopConstruct}; loop;) { |
| if (loop->IsDoNormal()) { |
| const parser::Name &itrVal{GetLoopIndex(loop)}; |
| if (itrVal.symbol) { |
| const auto *type{itrVal.symbol->GetType()}; |
| if (!type->IsNumeric(TypeCategory::Integer)) { |
| context_.Say(itrVal.source, |
| "The DO loop iteration" |
| " variable must be of the type integer."_err_en_US, |
| itrVal.ToString()); |
| } |
| } |
| } |
| // Get the next DoConstruct if block is not empty. |
| const auto &block{std::get<parser::Block>(loop->t)}; |
| const auto it{block.begin()}; |
| loop = it != block.end() ? parser::Unwrap<parser::DoConstruct>(*it) |
| : nullptr; |
| } |
| } |
| } |
| } |
| |
| std::int64_t OmpStructureChecker::GetOrdCollapseLevel( |
| const parser::OpenMPLoopConstruct &x) { |
| const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()}; |
| std::int64_t orderedCollapseLevel{1}; |
| std::int64_t orderedLevel{1}; |
| std::int64_t collapseLevel{1}; |
| |
| for (const auto &clause : beginSpec.Clauses().v) { |
| if (const auto *collapseClause{ |
| std::get_if<parser::OmpClause::Collapse>(&clause.u)}) { |
| if (const auto v{GetIntValue(collapseClause->v)}) { |
| collapseLevel = *v; |
| } |
| } |
| if (const auto *orderedClause{ |
| std::get_if<parser::OmpClause::Ordered>(&clause.u)}) { |
| if (const auto v{GetIntValue(orderedClause->v)}) { |
| orderedLevel = *v; |
| } |
| } |
| } |
| if (orderedLevel >= collapseLevel) { |
| orderedCollapseLevel = orderedLevel; |
| } else { |
| orderedCollapseLevel = collapseLevel; |
| } |
| return orderedCollapseLevel; |
| } |
| |
| void OmpStructureChecker::CheckAssociatedLoopConstraints( |
| const parser::OpenMPLoopConstruct &x) { |
| std::int64_t ordCollapseLevel{GetOrdCollapseLevel(x)}; |
| AssociatedLoopChecker checker{context_, ordCollapseLevel}; |
| parser::Walk(x, checker); |
| } |
| |
| void OmpStructureChecker::CheckDistLinear( |
| const parser::OpenMPLoopConstruct &x) { |
| const parser::OmpClauseList &clauses{x.BeginDir().Clauses()}; |
| |
| SymbolSourceMap indexVars; |
| |
| // Collect symbols of all the variables from linear clauses |
| for (auto &clause : clauses.v) { |
| if (auto *linearClause{std::get_if<parser::OmpClause::Linear>(&clause.u)}) { |
| auto &objects{std::get<parser::OmpObjectList>(linearClause->v.t)}; |
| GetSymbolsInObjectList(objects, indexVars); |
| } |
| } |
| |
| if (!indexVars.empty()) { |
| // Get collapse level, if given, to find which loops are "associated." |
| std::int64_t collapseVal{GetOrdCollapseLevel(x)}; |
| // Include the top loop if no collapse is specified |
| if (collapseVal == 0) { |
| collapseVal = 1; |
| } |
| |
| // Match the loop index variables with the collected symbols from linear |
| // clauses. |
| auto &optLoopCons = std::get<std::optional<parser::NestedConstruct>>(x.t); |
| if (optLoopCons.has_value()) { |
| if (const auto &loopConstruct{ |
| std::get_if<parser::DoConstruct>(&*optLoopCons)}) { |
| for (const parser::DoConstruct *loop{&*loopConstruct}; loop;) { |
| if (loop->IsDoNormal()) { |
| const parser::Name &itrVal{GetLoopIndex(loop)}; |
| if (itrVal.symbol) { |
| // Remove the symbol from the collected set |
| indexVars.erase(&itrVal.symbol->GetUltimate()); |
| } |
| collapseVal--; |
| if (collapseVal == 0) { |
| break; |
| } |
| } |
| // Get the next DoConstruct if block is not empty. |
| const auto &block{std::get<parser::Block>(loop->t)}; |
| const auto it{block.begin()}; |
| loop = it != block.end() ? parser::Unwrap<parser::DoConstruct>(*it) |
| : nullptr; |
| } |
| } |
| } |
| |
| // Show error for the remaining variables |
| for (auto &[symbol, source] : indexVars) { |
| const Symbol &root{GetAssociationRoot(*symbol)}; |
| context_.Say(source, |
| "Variable '%s' not allowed in LINEAR clause, only loop iterator can be specified in LINEAR clause of a construct combined with DISTRIBUTE"_err_en_US, |
| root.name()); |
| } |
| } |
| } |
| |
| void OmpStructureChecker::Leave(const parser::OpenMPLoopConstruct &x) { |
| const parser::OmpClauseList &clauseList{x.BeginDir().Clauses()}; |
| |
| // A few semantic checks for InScan reduction are performed below as SCAN |
| // constructs inside LOOP may add the relevant information. Scan reduction is |
| // supported only in loop constructs, so same checks are not applicable to |
| // other directives. |
| using ReductionModifier = parser::OmpReductionModifier; |
| for (const auto &clause : clauseList.v) { |
| if (const auto *reductionClause{ |
| std::get_if<parser::OmpClause::Reduction>(&clause.u)}) { |
| auto &modifiers{OmpGetModifiers(reductionClause->v)}; |
| auto *maybeModifier{OmpGetUniqueModifier<ReductionModifier>(modifiers)}; |
| if (maybeModifier && |
| maybeModifier->v == ReductionModifier::Value::Inscan) { |
| const auto &objectList{ |
| std::get<parser::OmpObjectList>(reductionClause->v.t)}; |
| auto checkReductionSymbolInScan = [&](const parser::Name *name) { |
| if (auto &symbol = name->symbol) { |
| if (!symbol->test(Symbol::Flag::OmpInclusiveScan) && |
| !symbol->test(Symbol::Flag::OmpExclusiveScan)) { |
| context_.Say(name->source, |
| "List item %s must appear in EXCLUSIVE or " |
| "INCLUSIVE clause of an " |
| "enclosed SCAN directive"_err_en_US, |
| name->ToString()); |
| } |
| } |
| }; |
| for (const auto &ompObj : objectList.v) { |
| common::visit( |
| common::visitors{ |
| [&](const parser::Designator &designator) { |
| if (const auto *name{semantics::getDesignatorNameIfDataRef( |
| designator)}) { |
| checkReductionSymbolInScan(name); |
| } |
| }, |
| [&](const parser::Name &name) { |
| checkReductionSymbolInScan(&name); |
| }, |
| [&](const parser::OmpObject::Invalid &invalid) {}, |
| }, |
| ompObj.u); |
| } |
| } |
| } |
| } |
| if (llvm::omp::allSimdSet.test(GetContext().directive)) { |
| ExitDirectiveNest(SIMDNest); |
| } |
| dirContext_.pop_back(); |
| |
| assert(!loopStack_.empty() && "Expecting non-empty loop stack"); |
| #ifndef NDEBUG |
| const LoopConstruct &top{loopStack_.back()}; |
| auto *loopc{std::get_if<const parser::OpenMPLoopConstruct *>(&top)}; |
| assert(loopc != nullptr && *loopc == &x && "Mismatched loop constructs"); |
| #endif |
| loopStack_.pop_back(); |
| } |
| |
| void OmpStructureChecker::Enter(const parser::OmpEndLoopDirective &x) { |
| const parser::OmpDirectiveName &dir{x.DirName()}; |
| ResetPartialContext(dir.source); |
| switch (dir.v) { |
| // 2.7.1 end-do -> END DO [nowait-clause] |
| // 2.8.3 end-do-simd -> END DO SIMD [nowait-clause] |
| case llvm::omp::Directive::OMPD_do: |
| PushContextAndClauseSets(dir.source, llvm::omp::Directive::OMPD_end_do); |
| break; |
| case llvm::omp::Directive::OMPD_do_simd: |
| PushContextAndClauseSets( |
| dir.source, llvm::omp::Directive::OMPD_end_do_simd); |
| break; |
| default: |
| // no clauses are allowed |
| break; |
| } |
| } |
| |
| void OmpStructureChecker::Leave(const parser::OmpEndLoopDirective &x) { |
| if ((GetContext().directive == llvm::omp::Directive::OMPD_end_do) || |
| (GetContext().directive == llvm::omp::Directive::OMPD_end_do_simd)) { |
| dirContext_.pop_back(); |
| } |
| } |
| |
| void OmpStructureChecker::Enter(const parser::OmpClause::Linear &x) { |
| CheckAllowedClause(llvm::omp::Clause::OMPC_linear); |
| unsigned version{context_.langOptions().OpenMPVersion}; |
| llvm::omp::Directive dir{GetContext().directive}; |
| parser::CharBlock clauseSource{GetContext().clauseSource}; |
| const parser::OmpLinearModifier *linearMod{nullptr}; |
| |
| SymbolSourceMap symbols; |
| auto &objects{std::get<parser::OmpObjectList>(x.v.t)}; |
| CheckCrayPointee(objects, "LINEAR", false); |
| GetSymbolsInObjectList(objects, symbols); |
| |
| auto CheckIntegerNoRef{[&](const Symbol *symbol, parser::CharBlock source) { |
| if (!symbol->GetType()->IsNumeric(TypeCategory::Integer)) { |
| auto &desc{OmpGetDescriptor<parser::OmpLinearModifier>()}; |
| context_.Say(source, |
| "The list item '%s' specified without the REF '%s' must be of INTEGER type"_err_en_US, |
| symbol->name(), desc.name.str()); |
| } |
| }}; |
| |
| if (OmpVerifyModifiers(x.v, llvm::omp::OMPC_linear, clauseSource, context_)) { |
| auto &modifiers{OmpGetModifiers(x.v)}; |
| linearMod = OmpGetUniqueModifier<parser::OmpLinearModifier>(modifiers); |
| if (linearMod) { |
| // 2.7 Loop Construct Restriction |
| if ((llvm::omp::allDoSet | llvm::omp::allSimdSet).test(dir)) { |
| context_.Say(clauseSource, |
| "A modifier may not be specified in a LINEAR clause on the %s directive"_err_en_US, |
| ContextDirectiveAsFortran()); |
| return; |
| } |
| |
| auto &desc{OmpGetDescriptor<parser::OmpLinearModifier>()}; |
| for (auto &[symbol, source] : symbols) { |
| if (linearMod->v != parser::OmpLinearModifier::Value::Ref) { |
| CheckIntegerNoRef(symbol, source); |
| } else { |
| if (!IsAllocatable(*symbol) && !IsAssumedShape(*symbol) && |
| !IsPolymorphic(*symbol)) { |
| context_.Say(source, |
| "The list item `%s` specified with the REF '%s' must be polymorphic variable, assumed-shape array, or a variable with the `ALLOCATABLE` attribute"_err_en_US, |
| symbol->name(), desc.name.str()); |
| } |
| } |
| if (linearMod->v == parser::OmpLinearModifier::Value::Ref || |
| linearMod->v == parser::OmpLinearModifier::Value::Uval) { |
| if (!IsDummy(*symbol) || IsValue(*symbol)) { |
| context_.Say(source, |
| "If the `%s` is REF or UVAL, the list item '%s' must be a dummy argument without the VALUE attribute"_err_en_US, |
| desc.name.str(), symbol->name()); |
| } |
| } |
| } // for (symbol, source) |
| |
| if (version >= 52 && !std::get</*PostModified=*/bool>(x.v.t)) { |
| context_.Say(OmpGetModifierSource(modifiers, linearMod), |
| "The 'modifier(<list>)' syntax is deprecated in %s, use '<list> : modifier' instead"_warn_en_US, |
| ThisVersion(version)); |
| } |
| } |
| } |
| |
| // OpenMP 5.2: Ordered clause restriction |
| if (const auto *clause{ |
| FindClause(GetContext(), llvm::omp::Clause::OMPC_ordered)}) { |
| const auto &orderedClause{std::get<parser::OmpClause::Ordered>(clause->u)}; |
| if (orderedClause.v) { |
| return; |
| } |
| } |
| |
| // OpenMP 5.2: Linear clause Restrictions |
| for (auto &[symbol, source] : symbols) { |
| if (!linearMod) { |
| // Already checked this with the modifier present. |
| CheckIntegerNoRef(symbol, source); |
| } |
| if (dir == llvm::omp::Directive::OMPD_declare_simd && !IsDummy(*symbol)) { |
| context_.Say(source, |
| "The list item `%s` must be a dummy argument"_err_en_US, |
| symbol->name()); |
| } |
| if (IsPointer(*symbol) || symbol->test(Symbol::Flag::CrayPointer)) { |
| context_.Say(source, |
| "The list item `%s` in a LINEAR clause must not be Cray Pointer or a variable with POINTER attribute"_err_en_US, |
| symbol->name()); |
| } |
| if (FindCommonBlockContaining(*symbol)) { |
| context_.Say(source, |
| "'%s' is a common block name and must not appear in an LINEAR clause"_err_en_US, |
| symbol->name()); |
| } |
| } |
| } |
| |
| void OmpStructureChecker::Enter(const parser::DoConstruct &x) { |
| Base::Enter(x); |
| loopStack_.push_back(&x); |
| } |
| |
| void OmpStructureChecker::Leave(const parser::DoConstruct &x) { |
| assert(!loopStack_.empty() && "Expecting non-empty loop stack"); |
| #ifndef NDEBUG |
| const LoopConstruct &top = loopStack_.back(); |
| auto *doc{std::get_if<const parser::DoConstruct *>(&top)}; |
| assert(doc != nullptr && *doc == &x && "Mismatched loop constructs"); |
| #endif |
| loopStack_.pop_back(); |
| Base::Leave(x); |
| } |
| |
| } // namespace Fortran::semantics |