| //===-- 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/ADT/BitVector.h" |
| #include "llvm/Frontend/OpenMP/OMP.h" |
| |
| #include <cinttypes> |
| #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_); |
| } |
| 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) { |
| const parser::OmpDirectiveName &beginName{x.BeginDir().DirName()}; |
| if (llvm::omp::topDistributeSet.test(beginName.v)) { |
| // `distribute` region has to be nested |
| if (CurrentDirectiveIsNested()) { |
| // `distribute` region has to be strictly nested inside `teams` |
| if (!llvm::omp::bottomTeamsSet.test(GetContextParent().directive)) { |
| context_.Say(beginName.source, |
| "`DISTRIBUTE` region has to be strictly nested inside `TEAMS` " |
| "region."_err_en_US); |
| } |
| } else { |
| // If not lexically nested (orphaned), issue a warning. |
| context_.Say(beginName.source, |
| "`DISTRIBUTE` must be dynamically enclosed in a `TEAMS` " |
| "region."_warn_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) { |
| if (auto *clause{ |
| parser::omp::FindClause(beginSpec, llvm::omp::Clause::OMPC_bind)}) { |
| auto &bind{std::get<parser::OmpClause::Bind>(clause->u).v}; |
| if (bind.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) { |
| if (parser::omp::FindClause( |
| beginSpec, llvm::omp::Clause::OMPC_simd)) { |
| eligibleSIMD = true; |
| } |
| } |
| }, |
| [&](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) { |
| if (parser::omp::FindClause( |
| ssc->v, llvm::omp::Clause::OMPC_simd)) { |
| eligibleSIMD = true; |
| } |
| } 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::CheckRectangularNest( |
| const parser::OmpDirectiveSpecification &spec, const LoopSequence &nest) { |
| unsigned version{context_.langOptions().OpenMPVersion}; |
| auto depth{GetRectangularNestDepthWithReason(spec, version)}; |
| if (!depth || *depth.value == 0) { |
| return; |
| } |
| |
| int64_t height{0}; |
| std::vector<const LoopSequence *> outer; |
| for (const LoopSequence *n{&nest}; n;) { |
| if (n->owner()) { |
| WithReason<bool> rect{n->isRectangular(outer)}; |
| if (!rect.value.value_or(true)) { |
| auto &msg{context_.Say(spec.DirName().source, |
| "This construct requires a rectangular loop nest, but the associated nest is not"_err_en_US)}; |
| depth.reason.AttachTo(msg); |
| rect.reason.AttachTo(msg); |
| } |
| outer.push_back(n); |
| } |
| height += n->height().value.value_or(1); |
| if (height >= *depth.value) { |
| break; |
| } |
| n = n->children().empty() ? nullptr : &n->children().front(); |
| } |
| } |
| |
| void OmpStructureChecker::CheckNestedConstruct( |
| const parser::OpenMPLoopConstruct &x) { |
| const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()}; |
| llvm::omp::Directive dir{beginSpec.DirId()}; |
| unsigned version{context_.langOptions().OpenMPVersion}; |
| parser::CharBlock beginSource{beginSpec.DirName().source}; |
| |
| // End-directive is not allowed in such cases: |
| // do 100 i = ... |
| // !$omp do |
| // do 100 j = ... |
| // 100 continue |
| // !$omp end do ! error |
| auto &flags{std::get<parser::OmpDirectiveSpecification::Flags>(beginSpec.t)}; |
| if (flags.test(parser::OmpDirectiveSpecification::Flag::CrossesLabelDo)) { |
| if (auto &endSpec{x.EndDir()}) { |
| context_ |
| .Say(endSpec->DirName().source, |
| "END %s directive is not allowed when the construct does not contain all loops that share a loop-terminating statement"_err_en_US, |
| parser::ToUpperCaseLetters(beginSource.ToString())) |
| .Attach(beginSource, "The construct starts here"_en_US); |
| } |
| } |
| |
| // Check constructs contained in the body of the loop construct. |
| auto &body{std::get<parser::Block>(x.t)}; |
| |
| for (auto &stmt : BlockRange(body, BlockRange::Step::Over)) { |
| if (auto *d{parser::Unwrap<parser::CompilerDirective>(stmt)}) { |
| context_.Say(d->source, |
| "Compiler directives are not allowed inside OpenMP loop constructs"_warn_en_US); |
| } |
| } |
| |
| // The loop sequence will correspond to the nest associated with the |
| // loop-associated construct being visited. |
| LoopSequence sequence(body, version, true); |
| auto assoc{llvm::omp::getDirectiveAssociation(dir)}; |
| auto needRange{GetAffectedLoopRangeWithReason(beginSpec, version)}; |
| auto haveLength{sequence.length()}; |
| |
| const auto MsgShouldContainDoOr{ |
| "This construct should contain a DO-loop or a loop-%s-generating construct"_err_en_US}; |
| const auto MsgRequiresCanonical{ |
| "This construct requires a canonical loop %s"_err_en_US}; |
| |
| if (assoc == llvm::omp::Association::LoopNest) { |
| if (sequence.children().size() == 0) { |
| context_.Say(beginSource, MsgShouldContainDoOr, "nest"); |
| } else if (haveLength.value > 1) { |
| auto &msg{context_.Say(beginSource, |
| "This construct applies to a loop nest, but has a loop sequence of " |
| "length %" PRId64 ""_err_en_US, |
| *haveLength.value)}; |
| haveLength.reason.AttachTo(msg); |
| } |
| auto [isWellFormed, whyNot]{sequence.isWellFormedNest()}; |
| if (isWellFormed && !*isWellFormed) { |
| auto &msg{context_.Say(beginSource, MsgRequiresCanonical, "nest")}; |
| whyNot.AttachTo(msg); |
| } |
| |
| // Check requirements on nest depth. |
| auto [needDepth, needPerfect]{ |
| GetAffectedNestDepthWithReason(beginSpec, version)}; |
| auto &[haveSema, havePerf]{sequence.depth()}; |
| |
| auto haveDepth{needPerfect ? havePerf : haveSema}; |
| std::string_view perfectTxt{needPerfect ? " perfect" : ""}; |
| |
| if (needDepth.value > 1 && IsDoConcurrentLegal(version)) { |
| if (auto *conc{sequence.getNestedDoConcurrent()}) { |
| auto &msg{context_.Say(*parser::GetSource(*conc->owner()), |
| "DO CONCURRENT must be the only affected loop in a loop nest"_err_en_US)}; |
| needDepth.reason.AttachTo(msg); |
| } |
| } |
| |
| // If the present depth is 0, it's likely that the construct doesn't |
| // have any loops in it, which would be diagnosed above. |
| if (needDepth && haveDepth.value > 0) { |
| if (*needDepth.value > *haveDepth.value) { |
| auto &msg{context_.Say(beginSource, |
| "This construct requires a%s nest of depth %" PRId64 |
| ", but the associated nest is a%s nest of depth %" PRId64 |
| ""_err_en_US, |
| perfectTxt, *needDepth.value, perfectTxt, *haveDepth.value)}; |
| haveDepth.reason.AttachTo(msg); |
| needDepth.reason.AttachTo(msg); |
| } else { |
| CheckRectangularNest(beginSpec, sequence); |
| } |
| } |
| |
| } else if (assoc == llvm::omp::Association::LoopSeq) { |
| if (haveLength.value == 0) { |
| context_.Say(beginSource, MsgShouldContainDoOr, "sequence"); |
| } else { |
| auto [isWellFormed, whyNot]{sequence.isWellFormedSequence()}; |
| if (isWellFormed && !*isWellFormed) { |
| auto &msg{context_.Say(beginSource, MsgRequiresCanonical, "sequence")}; |
| whyNot.AttachTo(msg); |
| } |
| if (auto requiredCount{GetMinimumSequenceCount(needRange.value)}) { |
| if (*requiredCount > 0 && haveLength.value < *requiredCount) { |
| auto &msg{context_.Say(beginSource, |
| "This construct requires a sequence of at least %" PRId64 |
| " loops, but the loop sequence has a length of %" PRId64 |
| ""_err_en_US, |
| *requiredCount, *haveLength.value)}; |
| haveLength.reason.AttachTo(msg); |
| needRange.reason.AttachTo(msg); |
| } |
| } |
| } |
| } |
| } |
| |
| void OmpStructureChecker::Enter(const parser::OpenMPLoopConstruct &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); |
| } |
| |
| if (CurrentDirectiveIsNested() && |
| llvm::omp::topTeamsSet.test(GetContext().directive) && |
| GetContextParent().directive == llvm::omp::Directive::OMPD_target && |
| !GetDirectiveNest(TargetBlockOnlyTeams)) { |
| context_.Say(GetContextParent().directiveSource, |
| "TARGET construct with nested TEAMS region contains statements or " |
| "directives outside of the TEAMS construct"_err_en_US); |
| } |
| |
| // 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) { |
| // nesting check |
| HasInvalidWorksharingNesting(beginName, llvm::omp::nestedWorkshareErrSet); |
| } |
| |
| for (auto &construct : std::get<parser::Block>(x.t)) { |
| if (const auto *doConstruct{parser::omp::GetDoConstruct(construct)}) { |
| const auto &doBlock{std::get<parser::Block>(doConstruct->t)}; |
| CheckNoBranching(doBlock, beginName.v, beginName.source); |
| } |
| } |
| CheckIterationVariables(x); |
| CheckNestedConstruct(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::CheckIterationVariables( |
| const parser::OpenMPLoopConstruct &x) { |
| unsigned version{context_.langOptions().OpenMPVersion}; |
| auto doLoops{CollectAffectedDoLoops(x, version, &context_)}; |
| if (!doLoops) { |
| return; |
| } |
| const parser::OmpDirectiveSpecification &spec{x.BeginDir()}; |
| llvm::omp::Directive dirId{spec.DirId()}; |
| |
| // Collect symbols from DSA clauses on the construct. These symbols |
| // are the "host" versions of symbols inside the construct. The flags |
| // of interest are on the associated symbols. |
| struct ClauseAppearance { |
| llvm::omp::Clause clauseId; |
| parser::CharBlock source; |
| }; |
| std::multimap<const Symbol *, ClauseAppearance> dsa; |
| for (const parser::OmpClause &clause : spec.Clauses().v) { |
| llvm::omp::Clause clauseId{clause.Id()}; |
| if (llvm::omp::isDataSharingAttributeClause(clauseId, version)) { |
| for (const parser::OmpObject &object : |
| parser::omp::GetOmpObjectList(clause)->v) { |
| if (const Symbol *symbol{GetObjectSymbol(object)}) { |
| auto maybeSource{parser::omp::GetObjectSource(object)}; |
| assert(maybeSource && "Expecting object source"); |
| dsa.insert( |
| std::make_pair(symbol, ClauseAppearance{clauseId, *maybeSource})); |
| } |
| } |
| } |
| } |
| |
| auto [depth, _]{GetAffectedNestDepthWithReason(spec, version)}; |
| bool isLinearAllowed{false}; |
| if (!depth || depth.value == 1) { |
| auto leafs{llvm::omp::getLeafConstructsOrSelf(dirId)}; |
| isLinearAllowed = leafs.back() == llvm::omp::Directive::OMPD_simd; |
| } |
| |
| std::vector<parser::Name> ivs; |
| for (const parser::DoConstruct *loop : *doLoops) { |
| // Skip DO CONCURRENT, since their iteration variables are local. |
| if (loop->IsDoConcurrent()) { |
| continue; |
| } |
| for (auto &control : GetLoopControls(*loop)) { |
| if (control.iv.symbol) { |
| ivs.push_back(control.iv); |
| } |
| } |
| } |
| |
| for (const parser::Name &iv : ivs) { |
| const auto *type{iv.symbol->GetType()}; |
| if (!type->IsNumeric(TypeCategory::Integer)) { |
| context_.Say(iv.source, |
| "The DO loop iteration variable must be of integer type"_err_en_US, |
| iv.ToString()); |
| } |
| if (iv.symbol->GetUltimate().test(Symbol::Flag::OmpThreadprivate)) { |
| context_.Say(iv.source, |
| "Loop iteration variable of an affected loop cannot be THREADPRIVATE"_err_en_US, |
| iv.ToString()); |
| } |
| // Get the symbol from the variable that was listed in a DSA clause. |
| const Symbol *host{iv.symbol}; |
| while (host && !dsa.count(host)) { |
| host = GetHostSymbol(*host); |
| } |
| if (!host) { |
| continue; |
| } |
| // Check conflict between a predetermined DSA and explicit DSA. |
| assert(iv.symbol->test(Symbol::Flag::OmpPreDetermined) && |
| "Expecting affected iteration variable to have predetermined DSA"); |
| if (iv.symbol->test(Symbol::Flag::OmpExplicit)) { |
| auto range{dsa.equal_range(host)}; |
| for (auto found{range.first}; found != range.second; ++found) { |
| llvm::omp::Clause id{found->second.clauseId}; |
| if (!llvm::omp::isAllowedClauseForDirective(dirId, id, version)) { |
| continue; |
| } |
| if (id == llvm::omp::Clause::OMPC_private || |
| id == llvm::omp::Clause::OMPC_lastprivate) { |
| continue; |
| } |
| if (id == llvm::omp::Clause::OMPC_linear && isLinearAllowed) { |
| continue; |
| } |
| context_ |
| .Say(found->second.source, |
| "Loop iteration variable with a predetermined data sharing attribute cannot appear in a %s clause"_err_en_US, |
| parser::omp::GetUpperName(id, version)) |
| .Attach(iv.source, |
| "'%s' is an iteration variable of an affected loop"_because_en_US, |
| iv.ToString()); |
| } |
| } |
| } |
| } |
| |
| 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 (std::get_if<parser::OmpClause::Linear>(&clause.u)) { |
| GetSymbolsInObjectList(*parser::omp::GetOmpObjectList(clause), 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. |
| for (auto &construct : std::get<parser::Block>(x.t)) { |
| std::int64_t curCollapseVal{collapseVal}; |
| for (const parser::DoConstruct *loop{ |
| parser::omp::GetDoConstruct(construct)}; |
| 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()); |
| } |
| curCollapseVal--; |
| if (curCollapseVal == 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::CheckScanModifier( |
| const parser::OmpClause::Reduction &x) { |
| using ReductionModifier = parser::OmpReductionModifier; |
| |
| 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()); |
| } |
| } |
| }}; |
| |
| auto &modifiers{OmpGetModifiers(x.v)}; |
| auto *maybeModifier{OmpGetUniqueModifier<ReductionModifier>(modifiers)}; |
| if (maybeModifier && maybeModifier->v == ReductionModifier::Value::Inscan) { |
| for (const auto &ompObj : parser::omp::GetOmpObjectList(x)->v) { |
| common::visit( |
| common::visitors{ |
| [&](const parser::Designator &desg) { |
| if (auto *name{parser::GetDesignatorNameIfDataRef(desg)}) { |
| checkReductionSymbolInScan(*name); |
| } |
| }, |
| [&](const parser::Name &name) { |
| checkReductionSymbolInScan(name); |
| }, |
| [&](const parser::OmpObject::Invalid &invalid) {}, |
| }, |
| ompObj.u); |
| } |
| } |
| } |
| |
| void OmpStructureChecker::Leave(const parser::OpenMPLoopConstruct &x) { |
| const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()}; |
| |
| // 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. |
| for (const auto &clause : beginSpec.Clauses().v) { |
| if (auto *reduction{std::get_if<parser::OmpClause::Reduction>(&clause.u)}) { |
| CheckScanModifier(*reduction); |
| } |
| } |
| if (llvm::omp::allSimdSet.test(beginSpec.DirName().v)) { |
| ExitDirectiveNest(SIMDNest); |
| } |
| dirContext_.pop_back(); |
| } |
| |
| void OmpStructureChecker::Enter(const parser::OmpClause::Depth &x) { |
| CheckAllowedClause(llvm::omp::Clause::OMPC_depth); |
| |
| RequiresConstantPositiveParameter(llvm::omp::Clause::OMPC_depth, x.v); |
| } |
| |
| void OmpStructureChecker::Enter(const parser::OmpClause::Ordered &x) { |
| CheckAllowedClause(llvm::omp::Clause::OMPC_ordered); |
| |
| // the parameter of ordered clause is optional |
| if (const auto &expr{x.v}) { |
| RequiresConstantPositiveParameter(llvm::omp::Clause::OMPC_ordered, *expr); |
| // 2.8.3 Loop SIMD Construct Restriction |
| if (llvm::omp::allDoSimdSet.test(GetContext().directive)) { |
| context_.Say(GetContext().clauseSource, |
| "No ORDERED clause with a parameter can be specified " |
| "on the %s directive"_err_en_US, |
| ContextDirectiveAsFortran()); |
| } |
| } |
| } |
| |
| 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); |
| CheckAssumedSizeArray(symbols, llvm::omp::Clause::OMPC_linear); |
| |
| 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) { |
| auto &desc{OmpGetDescriptor<parser::OmpLinearModifier>()}; |
| parser::CharBlock modSource{OmpGetModifierSource(modifiers, linearMod)}; |
| bool valid{true}; |
| |
| if (version < 52) { |
| // Modifiers on LINEAR are only allowed on DECLARE SIMD |
| if (dir != llvm::omp::Directive::OMPD_declare_simd) { |
| context_.Say(modSource, |
| "A modifier may not be specified in a LINEAR clause on the %s directive"_err_en_US, |
| parser::omp::GetUpperName(dir, version)); |
| valid = false; |
| } |
| } else { |
| if (linearMod->v == parser::OmpLinearModifier::Value::Ref || |
| linearMod->v == parser::OmpLinearModifier::Value::Uval) { |
| if (dir != llvm::omp::Directive::OMPD_declare_simd) { |
| context_.Say(modSource, |
| "A REF or UVAL '%s' may not be specified in a LINEAR clause on the %s directive"_err_en_US, |
| desc.name.str(), parser::omp::GetUpperName(dir, version)); |
| valid = false; |
| } |
| } |
| if (!std::get</*PostModified=*/bool>(x.v.t)) { |
| context_.Say(modSource, |
| "The 'modifier(<list>)' syntax is deprecated in %s, use '<list> : modifier' instead"_warn_en_US, |
| ThisVersion(version)); |
| } |
| } |
| |
| if (valid) { |
| 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) |
| } |
| } |
| } |
| |
| // Linear clause restrictions. |
| for (auto &[symbol, source] : symbols) { |
| // Check that the list item is a scalar variable (rank 0) |
| // For declare simd with REF modifier, arrays are allowed |
| bool isArrayAllowed{dir == llvm::omp::Directive::OMPD_declare_simd && |
| linearMod && linearMod->v == parser::OmpLinearModifier::Value::Ref}; |
| if (symbol->Rank() != 0 && !isArrayAllowed) { |
| context_.Say(source, |
| "List item '%s' in LINEAR clause must be a scalar variable"_err_en_US, |
| symbol->name()); |
| } |
| 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::OmpClause::Sizes &c) { |
| CheckAllowedClause(llvm::omp::Clause::OMPC_sizes); |
| for (const parser::Cosubscript &v : c.v) |
| RequiresPositiveParameter(llvm::omp::Clause::OMPC_sizes, v, |
| /*paramName=*/"parameter", /*allowZero=*/false); |
| } |
| |
| void OmpStructureChecker::Enter(const parser::OmpClause::Permutation &c) { |
| unsigned version{context_.langOptions().OpenMPVersion}; |
| llvm::omp::Clause clause = llvm::omp::Clause::OMPC_permutation; |
| CheckAllowedClause(clause); |
| if (c.v.size() < 2) |
| context_.Say(GetContext().clauseSource, |
| "The %s clause must have a length of at least two"_err_en_US, |
| parser::omp::GetUpperName(clause, version)); |
| |
| llvm::BitVector found(c.v.size(), false); |
| bool cont = true; |
| for (const auto &val : c.v) { |
| if (const auto v{GetIntValue(val)}) { |
| if (*v <= 0) { |
| cont = false; |
| context_.Say(GetContext().clauseSource, |
| "The parameter of the %s clause must be a constant positive integer expression"_err_en_US, |
| parser::omp::GetUpperName(clause, version)); |
| } else if ((unsigned)*v - 1 < c.v.size()) { |
| found.set(*v - 1); |
| } |
| } else |
| cont = false; |
| } |
| |
| if (!cont) |
| return; |
| if (!found.all()) { |
| context_.Say(GetContext().clauseSource, |
| "Every integer from 1 must appear in the %s clause"_err_en_US, |
| parser::omp::GetUpperName(clause, version)); |
| } |
| } |
| |
| void OmpStructureChecker::Enter(const parser::OmpClause::Looprange &x) { |
| CheckAllowedClause(llvm::omp::Clause::OMPC_looprange); |
| auto &[first, count]{x.v.t}; |
| RequiresConstantPositiveParameter(llvm::omp::Clause::OMPC_looprange, first); |
| RequiresConstantPositiveParameter(llvm::omp::Clause::OMPC_looprange, count); |
| } |
| |
| void OmpStructureChecker::Enter(const parser::DoConstruct &x) { |
| Base::Enter(x); |
| constructStack_.push_back(&x); |
| } |
| |
| void OmpStructureChecker::Leave(const parser::DoConstruct &x) { |
| assert(!constructStack_.empty() && "Expecting non-empty construct stack"); |
| #ifndef NDEBUG |
| const LoopOrConstruct &top = constructStack_.back(); |
| auto *doc{std::get_if<const parser::DoConstruct *>(&top)}; |
| assert(doc != nullptr && *doc == &x && "Mismatched constructs"); |
| #endif |
| constructStack_.pop_back(); |
| Base::Leave(x); |
| } |
| |
| } // namespace Fortran::semantics |