| //===-- lib/Semantics/check-omp-syntax.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-omp-structure.h" | |
| #include "flang/Common/visit.h" | |
| #include "flang/Parser/char-block.h" | |
| #include "flang/Parser/openmp-utils.h" | |
| #include "flang/Parser/parse-tree.h" | |
| #include "flang/Semantics/openmp-modifiers.h" | |
| #include "flang/Semantics/openmp-utils.h" | |
| #include "llvm/ADT/ArrayRef.h" | |
| #include "llvm/ADT/DenseMap.h" | |
| #include "llvm/ADT/STLExtras.h" | |
| #include "llvm/ADT/SmallVector.h" | |
| #include "llvm/Frontend/Directive/Spelling.h" | |
| #include "llvm/Frontend/OpenMP/OMP.h" | |
| #include "llvm/Frontend/OpenMP/OMPDescriptors.h" | |
| #include <algorithm> | |
| #include <list> | |
| #include <optional> | |
| #include <string> | |
| #include <tuple> | |
| #include <utility> | |
| #include <variant> | |
| namespace Fortran::semantics { | |
| using namespace Fortran::parser::omp; | |
| template <typename T> struct SetTypeFor { | |
| using type = llvm::omp::EnumSet<T, | |
| llvm::to_underlying(T::Last_) - llvm::to_underlying(T::First_) + 1>; | |
| }; | |
| static llvm::omp::Modifiers getElements( | |
| const llvm::omp::descriptor::Clause &cdesc, llvm::omp::Version version) { | |
| return cdesc.getModifiers(version); | |
| } | |
| static llvm::omp::Modifiers getElements( | |
| const llvm::omp::descriptor::ModifierSet &sdesc, | |
| llvm::omp::Version version) { | |
| return sdesc.getModifiers(version); | |
| } | |
| static llvm::omp::ModifierSets getSets( | |
| const llvm::omp::descriptor::Clause &cdesc, llvm::omp::Version version) { | |
| return cdesc.getModifierSets(version); | |
| } | |
| template < // | |
| typename ElemTy, typename SetsSetTy, typename OwnerTy, | |
| typename ResultTy = llvm::DenseMap<ElemTy, | |
| std::pair<parser::CharBlock, llvm::directive::VersionRange>>> | |
| static ResultTy VerifyVersions( | |
| const AppliedElementInfo<ElemTy, SetsSetTy> &info, OwnerTy ownerId, | |
| llvm::omp::Version version) { | |
| using AppliedElementTy = AppliedElement<ElemTy, SetsSetTy>; | |
| ResultTy result; | |
| auto &odesc{llvm::omp::getDescriptor(ownerId)}; | |
| auto elements{getElements(odesc, version)}; | |
| for (const AppliedElementTy &elem : info.elements) { | |
| if (elements.test(elem.id.value)) { | |
| continue; | |
| } | |
| llvm::omp::Version since{~0u}, until{0u}; | |
| for (llvm::omp::Version v : odesc.getVersions()) { | |
| if (getElements(odesc, v).test(elem.id.value)) { | |
| if (v < version) { | |
| until = std::max(until, v); | |
| } else if (v > version) { | |
| since = std::min(since, v); | |
| } | |
| } | |
| } | |
| int minVer = static_cast<unsigned>(since); | |
| int maxVer = static_cast<unsigned>(until); | |
| result.insert({elem.id.value, | |
| {elem.id.source, llvm::directive::VersionRange{minVer, maxVer}}}); | |
| } | |
| return result; | |
| } | |
| template < // | |
| typename ElemTy, typename SetsSetTy, typename OwnerTy, | |
| typename ElemSetTy = typename SetTypeFor<ElemTy>::type, | |
| typename ResultTy = std::pair<ElemSetTy, SetsSetTy>> | |
| static ResultTy VerifyRequired( | |
| const AppliedElementInfo<ElemTy, SetsSetTy> &info, OwnerTy ownerId, | |
| llvm::omp::Version version) { | |
| using AppliedElementTy = AppliedElement<ElemTy, SetsSetTy>; | |
| ResultTy required; | |
| auto &odesc{llvm::omp::getDescriptor(ownerId)}; | |
| for (auto e : getElements(odesc, version)) { | |
| auto &edesc{llvm::omp::getDescriptor(e)}; | |
| if (edesc.getProperties(version).test(llvm::omp::Property::Required)) { | |
| required.first.set(e); | |
| } | |
| } | |
| for (auto s : getSets(odesc, version)) { | |
| auto &sdesc{llvm::omp::getDescriptor(s)}; | |
| if (sdesc.getProperties(version).test(llvm::omp::Property::Required)) { | |
| required.second.set(s); | |
| } | |
| } | |
| for (const AppliedElementTy &elem : info.elements) { | |
| required.first.reset(elem.id.value); | |
| required.second &= ~elem.sets; | |
| } | |
| return required; | |
| } | |
| template < // | |
| typename ElemTy, typename SetsSetTy, typename OwnerTy, | |
| typename ResultTy = | |
| llvm::DenseMap<ElemTy, std::pair<parser::CharBlock, parser::CharBlock>>> | |
| static ResultTy VerifyUnique(const AppliedElementInfo<ElemTy, SetsSetTy> &info, | |
| OwnerTy ownerId, llvm::omp::Version version) { | |
| using AppliedElementTy = AppliedElement<ElemTy, SetsSetTy>; | |
| using ElemSetTy = typename SetTypeFor<ElemTy>::type; | |
| ElemSetTy unique; | |
| auto &odesc{llvm::omp::getDescriptor(ownerId)}; | |
| auto elements{getElements(odesc, version)}; | |
| for (auto e : elements) { | |
| auto &edesc{llvm::omp::getDescriptor(e)}; | |
| // Exclusive modifiers should have the "unique" property present as well. | |
| if (edesc.getProperties(version).test(llvm::omp::Property::Unique)) { | |
| unique.set(e); | |
| } | |
| } | |
| for (auto s : getSets(odesc, version)) { | |
| auto &sdesc{llvm::omp::getDescriptor(s)}; | |
| if (sdesc.getProperties(version).test(llvm::omp::Property::Unique)) { | |
| unique |= getElements(sdesc, version); | |
| } | |
| } | |
| ResultTy repeated; | |
| llvm::DenseMap<ElemTy, parser::CharBlock> present; | |
| for (const AppliedElementTy &elem : info.elements) { | |
| if (!elements.test(elem.id.value)) { | |
| // Skip invalid elements. | |
| continue; | |
| } | |
| if (unique.test(elem.id.value)) { | |
| auto [where, inserted]{present.insert({elem.id.value, elem.id.source})}; | |
| if (!inserted) { | |
| repeated.insert({elem.id.value, {where->second, elem.id.source}}); | |
| } | |
| } | |
| } | |
| return repeated; | |
| } | |
| template < // | |
| typename ElemTy, typename SetsSetTy, typename OwnerTy, | |
| typename ResultTy = llvm::DenseMap<ElemTy, | |
| std::tuple<ElemTy, parser::CharBlock, parser::CharBlock>>> | |
| static ResultTy VerifyExclusive( | |
| const AppliedElementInfo<ElemTy, SetsSetTy> &info, OwnerTy ownerId, | |
| llvm::omp::Version version) { | |
| using AppliedElementTy = AppliedElement<ElemTy, SetsSetTy>; | |
| ResultTy result; | |
| auto &odesc{llvm::omp::getDescriptor(ownerId)}; | |
| auto elements{getElements(odesc, version)}; | |
| llvm::DenseMap<ElemTy, parser::CharBlock> present; | |
| for (const AppliedElementTy &elem : info.elements) { | |
| if (!elements.test(elem.id.value)) { | |
| // Skip invalid elements. | |
| continue; | |
| } | |
| present.insert({elem.id.value, elem.id.source}); | |
| } | |
| for (auto [id, source] : present) { | |
| auto &edesc{llvm::omp::getDescriptor(id)}; | |
| if (!edesc.getProperties(version).test(llvm::omp::Property::Exclusive)) { | |
| continue; | |
| } | |
| // Element is exclusive, it cannot coexist with any other element. | |
| for (auto [otherId, otherSource] : present) { | |
| if (otherId != id) { | |
| result.insert({id, {otherId, source, otherSource}}); | |
| break; | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| template < // | |
| typename ElemTy, typename SetsSetTy, typename OwnerTy, | |
| typename ResultTy = llvm::DenseMap<ElemTy, | |
| std::tuple<ElemTy, parser::CharBlock, parser::CharBlock>>> | |
| static ResultTy VerifyMutuallyExclusive( | |
| const AppliedElementInfo<ElemTy, SetsSetTy> &info, OwnerTy ownerId, | |
| llvm::omp::Version version) { | |
| using AppliedElementTy = AppliedElement<ElemTy, SetsSetTy>; | |
| using SetTy = typename SetsSetTy::value_type; | |
| ResultTy result; | |
| auto &odesc{llvm::omp::getDescriptor(ownerId)}; | |
| auto elements{getElements(odesc, version)}; | |
| llvm::DenseMap<SetTy, const AppliedElementTy *> exclusive; | |
| for (const AppliedElementTy &elem : info.elements) { | |
| if (!elements.test(elem.id.value)) { | |
| // Skip invalid elements. | |
| continue; | |
| } | |
| for (auto s : elem.sets) { | |
| auto &sdesc{llvm::omp::getDescriptor(s)}; | |
| if (!sdesc.getProperties(version).test(llvm::omp::Property::Exclusive)) { | |
| continue; | |
| } | |
| auto [where, inserted]{exclusive.insert({s, &elem})}; | |
| if (!inserted) { | |
| const AppliedElementTy *prev{where->second}; | |
| if (prev->id.value != elem.id.value) { | |
| result.insert({elem.id.value, | |
| {prev->id.value, elem.id.source, prev->id.source}}); | |
| } | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| template < // | |
| typename ElemTy, typename SetsSetTy, typename OwnerTy, | |
| typename ResultTy = llvm::DenseMap<ElemTy, parser::CharBlock>> | |
| static ResultTy VerifyUltimate( | |
| const AppliedElementInfo<ElemTy, SetsSetTy> &info, OwnerTy ownerId, | |
| llvm::omp::Version version, bool last = true) { | |
| ResultTy result; | |
| if (info.elements.empty()) { | |
| return result; | |
| } | |
| using AppliedElementTy = AppliedElement<ElemTy, SetsSetTy>; | |
| using ElemSetTy = typename SetTypeFor<ElemTy>::type; | |
| ElemSetTy ultimate; | |
| auto &odesc{llvm::omp::getDescriptor(ownerId)}; | |
| auto elements{getElements(odesc, version)}; | |
| for (auto e : elements) { | |
| auto &edesc{llvm::omp::getDescriptor(e)}; | |
| if (edesc.getProperties(version).test(llvm::omp::Property::Ultimate)) { | |
| ultimate.set(e); | |
| } | |
| } | |
| for (auto s : getSets(odesc, version)) { | |
| auto &sdesc{llvm::omp::getDescriptor(s)}; | |
| if (sdesc.getProperties(version).test(llvm::omp::Property::Ultimate)) { | |
| ultimate |= getElements(sdesc, version); | |
| } | |
| } | |
| // Check if there is an ultimate modifier that is in a wrong position. | |
| auto rest{last | |
| ? llvm::ArrayRef<AppliedElementTy>(info.elements).drop_back(1) | |
| : llvm::ArrayRef<AppliedElementTy>(info.elements).drop_front(1)}; | |
| for (const AppliedElementTy &elem : rest) { | |
| if (!elements.test(elem.id.value)) { | |
| // Skip invalid elements. | |
| continue; | |
| } | |
| if (ultimate.test(elem.id.value)) { | |
| result.insert({elem.id.value, elem.id.source}); | |
| } | |
| } | |
| return result; | |
| } | |
| bool OmpStructureChecker::VerifyModifierVersion( | |
| WithSource<llvm::omp::Clause> clause, const AppliedModifierInfo &info) { | |
| // Verify that the specified modifiers are allowed in this version. | |
| llvm::omp::Version version{context_.langOptions().getOpenMPVersion()}; | |
| auto result = VerifyVersions(info, clause.value, version); | |
| for (auto &[m, svr] : result) { | |
| std::string modName{llvm::omp::getDescriptor(m).getName().str()}; | |
| std::string clauseName{GetUpperName(clause.value, version)}; | |
| llvm::omp::Version since(svr.second.Min); | |
| llvm::omp::Version until(svr.second.Max); | |
| if (since == ~0u && until == 0u) { | |
| // This shouldn't really happen, but have it just in case. | |
| context_.Say(svr.first, | |
| "'%s' modifier is not supported on %s clause"_err_en_US, modName, | |
| clauseName); | |
| } else if (since != ~0u && version < since) { | |
| context_.Say(svr.first, | |
| "'%s' modifier is not supported on %s clause in %s, %s"_warn_en_US, | |
| modName, clauseName, omp::ThisVersion(version), | |
| omp::TryVersion(since)); | |
| } else if (until != 0u && version > until) { | |
| context_.Say(svr.first, | |
| "'%s' modifier is no longer supported on %s clause in %s"_warn_en_US, | |
| modName, clauseName, omp::ThisVersion(version)); | |
| } | |
| } | |
| return result.empty(); | |
| } | |
| bool OmpStructureChecker::VerifyModifierRequired( | |
| WithSource<llvm::omp::Clause> clause, const AppliedModifierInfo &info) { | |
| llvm::omp::Version version{context_.langOptions().getOpenMPVersion()}; | |
| auto result = VerifyRequired(info, clause.value, version); | |
| for (llvm::omp::Modifier m : result.first) { | |
| auto &mdesc{llvm::omp::getDescriptor(m)}; | |
| context_.Say(clause.source, "'%s' modifier is required"_err_en_US, | |
| mdesc.getName().str()); | |
| } | |
| for (llvm::omp::ModifierSet s : result.second) { | |
| auto &sdesc{llvm::omp::getDescriptor(s)}; | |
| // If the group is required, at least one modifier from that group must | |
| // be present. | |
| if (llvm::omp::isModifierGroup(s)) { | |
| context_.Say(clause.source, | |
| "modifier from '%s' modifier group is required"_err_en_US, | |
| sdesc.getName().str()); | |
| } else { | |
| context_.Say(clause.source, | |
| "modifier from the modifier set on %s clause is required"_err_en_US, | |
| GetUpperName(clause.value, version)); | |
| } | |
| } | |
| return result.first.empty() && result.second.empty(); | |
| } | |
| bool OmpStructureChecker::VerifyModifierUnique( | |
| WithSource<llvm::omp::Clause> clause, const AppliedModifierInfo &info) { | |
| llvm::omp::Version version{context_.langOptions().getOpenMPVersion()}; | |
| auto result = VerifyUnique(info, clause.value, version); | |
| for (auto [id, where] : result) { | |
| auto &mdesc{llvm::omp::getDescriptor(id)}; | |
| context_ | |
| .Say(where.first, "'%s' modifier cannot occur multiple times"_err_en_US, | |
| mdesc.getName().str()) | |
| .Attach(where.second, "previous occurrence of this modifier"_en_US); | |
| } | |
| return result.empty(); | |
| } | |
| bool OmpStructureChecker::VerifyModifierExclusive( | |
| WithSource<llvm::omp::Clause> clause, const AppliedModifierInfo &info) { | |
| llvm::omp::Version version{context_.langOptions().getOpenMPVersion()}; | |
| auto resultExcl = VerifyExclusive(info, clause.value, version); | |
| for (auto [id, wrong] : resultExcl) { | |
| auto [otherId, source, otherSource] = wrong; | |
| context_ | |
| .Say(source, | |
| "An exclusive '%s' modifier cannot be specified together with a modifier of a different type"_err_en_US, | |
| llvm::omp::getDescriptor(id).getName().str()) | |
| .Attach(otherSource, "'%s' provided here"_en_US, | |
| llvm::omp::getDescriptor(otherId).getName().str()); | |
| } | |
| auto resultMut = VerifyMutuallyExclusive(info, clause.value, version); | |
| for (auto [id, wrong] : resultMut) { | |
| auto [otherId, source, otherSource] = wrong; | |
| auto thisName{llvm::omp::getDescriptor(id).getName().str()}; | |
| context_ | |
| .Say(otherSource, | |
| "The '%s' and '%s' modifiers are mutually exclusive"_err_en_US, | |
| llvm::omp::getDescriptor(otherId).getName().str(), thisName) | |
| .Attach(source, "'%s' modifier specified here"_en_US, thisName); | |
| } | |
| return resultExcl.empty() && resultMut.empty(); | |
| } | |
| bool OmpStructureChecker::VerifyModifierUltimate( | |
| WithSource<llvm::omp::Clause> clause, const AppliedModifierInfo &info) { | |
| llvm::omp::Version version{context_.langOptions().getOpenMPVersion()}; | |
| auto &cdesc{llvm::omp::getDescriptor(clause.value)}; | |
| bool last{ | |
| !cdesc.getProperties(version).test(llvm::omp::Property::PostModified)}; | |
| std::string expected{last ? "last" : "first"}; | |
| auto result = VerifyUltimate(info, clause.value, version, last); | |
| for (auto [id, where] : result) { | |
| context_.Say(where, "'%s' should be the %s modifier"_err_en_US, | |
| llvm::omp::getDescriptor(id).getName().str(), expected); | |
| } | |
| return result.empty(); | |
| } | |
| template <typename UnionTy> | |
| AppliedModifierInfo GetAppliedModifiers(llvm::omp::Clause clauseId, | |
| llvm::omp::Version version, | |
| const std::optional<std::list<UnionTy>> &modifiers) { | |
| AppliedModifierInfo info; | |
| if (modifiers) { | |
| auto cdesc{llvm::omp::getDescriptor(clauseId)}; | |
| for (auto &m : *modifiers) { | |
| common::visit( | |
| [&](auto &&t) { | |
| auto &am{info.elements.emplace_back(AppliedModifier{})}; | |
| am.id = WithSource{t.Id, m.source}; | |
| for (auto s : cdesc.getModifierSets(version)) { | |
| auto &sdesc{llvm::omp::getDescriptor(s)}; | |
| if (sdesc.getModifiers(version).test(am.id.value)) { | |
| am.sets.set(s); | |
| } | |
| } | |
| }, | |
| m.u); | |
| } | |
| } | |
| return info; | |
| } | |
| static AppliedModifierInfo GetAppliedModifiersFromWrapper( | |
| llvm::omp::Clause clauseId, llvm::omp::Version version, | |
| const parser::OmpDependClause &depend) { | |
| using TaskDep = parser::OmpDependClause::TaskDep; | |
| if (auto *task{std::get_if<TaskDep>(&depend.u)}) { | |
| using Modifiers = std::optional<std::list<TaskDep::Modifier>>; | |
| return GetAppliedModifiers( | |
| llvm::omp::Clause::OMPC_depend, version, std::get<Modifiers>(task->t)); | |
| } else if (auto *doa{std::get_if<parser::OmpDoacross>(&depend.u)}) { | |
| using Modifiers = std::optional<std::list<parser::OmpDoacross::Modifier>>; | |
| return GetAppliedModifiers( | |
| llvm::omp::Clause::OMPC_depend, version, std::get<Modifiers>(doa->t)); | |
| } | |
| llvm_unreachable("Unexpected alternative in depend"); | |
| } | |
| static AppliedModifierInfo GetAppliedModifiersFromWrapper( | |
| llvm::omp::Clause clauseId, llvm::omp::Version version, | |
| const parser::OmpDoacrossClause &doacross) { | |
| using Modifiers = std::optional<std::list<parser::OmpDoacross::Modifier>>; | |
| return GetAppliedModifiers(llvm::omp::Clause::OMPC_doacross, version, | |
| std::get<Modifiers>(doacross.v.t)); | |
| } | |
| template <typename T> | |
| static AppliedModifierInfo GetAppliedModifiersFromWrapper( | |
| llvm::omp::Clause clauseId, llvm::omp::Version version, const T &wrapper) { | |
| if constexpr (HasModifier<T>) { | |
| using Modifiers = std::optional<std::list<typename T::Modifier>>; | |
| return GetAppliedModifiers( | |
| clauseId, version, std::get<Modifiers>(wrapper.t)); | |
| } else { | |
| return AppliedModifierInfo{}; | |
| } | |
| } | |
| AppliedModifierInfo GetAppliedModifiers( | |
| const parser::OmpClause &clause, llvm::omp::Version version) { | |
| return common::visit( | |
| [&](auto &&s) { | |
| using TypeS = llvm::remove_cvref_t<decltype(s)>; | |
| if constexpr (WrapperTrait<TypeS>) { | |
| return GetAppliedModifiersFromWrapper(clause.Id(), version, s.v); | |
| } else { | |
| return AppliedModifierInfo{}; | |
| } | |
| }, | |
| clause.u); | |
| } | |
| bool OmpStructureChecker::VerifyModifiers( | |
| WithSource<llvm::omp::Clause> clause, const AppliedModifierInfo &info) { | |
| // Run all checks without short-circuiting, return 'true' if all succeed. | |
| bool valid[]{ | |
| VerifyModifierVersion(clause, info), | |
| VerifyModifierRequired(clause, info), | |
| VerifyModifierUnique(clause, info), | |
| VerifyModifierUltimate(clause, info), | |
| VerifyModifierExclusive(clause, info), | |
| }; | |
| return llvm::all_of(valid, [](bool x) { return x; }); | |
| } | |
| void OmpStructureChecker::VerifyModifiers(const parser::OmpClause &x) { | |
| llvm::omp::Version version{context_.langOptions().getOpenMPVersion()}; | |
| llvm::omp::Clause id{x.Id()}; | |
| auto clauseId{WithSource(id, x.source)}; | |
| switch (id) { | |
| case llvm::omp::Clause::OMPC_ompx_bare: | |
| case llvm::omp::Clause::OMPC_cancellation_construct_type: | |
| // Those are extensions/synthetic clauses and they don't have descriptors. | |
| break; | |
| case llvm::omp::Clause::OMPC_uses_allocators: { | |
| // The traits of the deprecated syntax are stored as a traits-array | |
| // modifier, but they are not the 5.2 modifier, so they must not be | |
| // version-checked. A modifier that postdates the OpenMP version in effect | |
| // is only warned about, so the specification is accepted as an extension | |
| // and must still be checked, otherwise a malformed one would reach lowering | |
| // unvalidated. | |
| auto &uac{parser::UnwrapRef<parser::OmpUsesAllocatorsClause>(x)}; | |
| for (auto &&as : uac.v) { | |
| bool legacy{std::get<bool>(as.t)}; | |
| if (!legacy) { | |
| VerifyModifiers( | |
| clauseId, GetAppliedModifiers(id, version, OmpGetModifiers(as))); | |
| } | |
| } | |
| break; | |
| } | |
| default: | |
| VerifyModifiers(clauseId, GetAppliedModifiers(x, version)); | |
| break; | |
| } | |
| } | |
| } // namespace Fortran::semantics |