//===-- lib/Semantics/check-omp-metadirective.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 METADIRECTIVE and related constructs/clauses. // //===----------------------------------------------------------------------===// #include "check-omp-structure.h" #include "openmp-utils.h" #include "flang/Common/idioms.h" #include "flang/Common/indirection.h" #include "flang/Common/visit.h" #include "flang/Parser/characters.h" #include "flang/Parser/message.h" #include "flang/Parser/parse-tree.h" #include "flang/Semantics/openmp-modifiers.h" #include "flang/Semantics/tools.h" #include "llvm/Frontend/OpenMP/OMP.h" #include #include #include #include #include #include #include #include namespace Fortran::semantics { using namespace Fortran::semantics::omp; void OmpStructureChecker::Enter(const parser::OmpClause::When &x) { CheckAllowedClause(llvm::omp::Clause::OMPC_when); OmpVerifyModifiers( x.v, llvm::omp::OMPC_when, GetContext().clauseSource, context_); } void OmpStructureChecker::Enter(const parser::OmpContextSelector &ctx) { EnterDirectiveNest(ContextSelectorNest); using SetName = parser::OmpTraitSetSelectorName; std::map visited; for (const parser::OmpTraitSetSelector &traitSet : ctx.v) { auto &name{std::get(traitSet.t)}; auto [prev, unique]{visited.insert(std::make_pair(name.v, &name))}; if (!unique) { std::string showName{parser::ToUpperCaseLetters(name.ToString())}; parser::MessageFormattedText txt( "Repeated trait set name %s in a context specifier"_err_en_US, showName); parser::Message message(name.source, txt); message.Attach(prev->second->source, "Previous trait set %s provided here"_en_US, showName); context_.Say(std::move(message)); } CheckTraitSetSelector(traitSet); } } void OmpStructureChecker::Leave(const parser::OmpContextSelector &) { ExitDirectiveNest(ContextSelectorNest); } const std::list & OmpStructureChecker::GetTraitPropertyList( const parser::OmpTraitSelector &trait) { static const std::list empty{}; auto &[_, maybeProps]{trait.t}; if (maybeProps) { using PropertyList = std::list; return std::get(maybeProps->t); } else { return empty; } } std::optional OmpStructureChecker::GetClauseFromProperty( const parser::OmpTraitProperty &property) { using MaybeClause = std::optional; // The parser for OmpClause will only succeed if the clause was // given with all required arguments. // If this is a string or complex extension with a clause name, // treat it as a clause and let the trait checker deal with it. auto getClauseFromString{[&](const std::string &s) -> MaybeClause { auto id{llvm::omp::getOpenMPClauseKind(parser::ToLowerCaseLetters(s))}; if (id != llvm::omp::Clause::OMPC_unknown) { return id; } else { return std::nullopt; } }}; return common::visit( // common::visitors{ [&](const parser::OmpTraitPropertyName &x) -> MaybeClause { return getClauseFromString(x.v); }, [&](const common::Indirection &x) -> MaybeClause { return x.value().Id(); }, [&](const parser::ScalarExpr &x) -> MaybeClause { return std::nullopt; }, [&](const parser::OmpTraitPropertyExtension &x) -> MaybeClause { using ExtProperty = parser::OmpTraitPropertyExtension; if (auto *name{std::get_if(&x.u)}) { return getClauseFromString(name->v); } else if (auto *cpx{std::get_if(&x.u)}) { return getClauseFromString( std::get(cpx->t).v); } return std::nullopt; }, }, property.u); } void OmpStructureChecker::CheckTraitSelectorList( const std::list &traits) { // [6.0:322:20] // Each trait-selector-name may only be specified once in a trait selector // set. // Cannot store OmpTraitSelectorName directly, because it's not copyable. using TraitName = parser::OmpTraitSelectorName; using BareName = decltype(TraitName::u); std::map visited; for (const parser::OmpTraitSelector &trait : traits) { auto &name{std::get(trait.t)}; auto [prev, unique]{visited.insert(std::make_pair(name.u, &name))}; if (!unique) { std::string showName{parser::ToUpperCaseLetters(name.ToString())}; parser::MessageFormattedText txt( "Repeated trait name %s in a trait set"_err_en_US, showName); parser::Message message(name.source, txt); message.Attach(prev->second->source, "Previous trait %s provided here"_en_US, showName); context_.Say(std::move(message)); } } } void OmpStructureChecker::CheckTraitSetSelector( const parser::OmpTraitSetSelector &traitSet) { // Trait Set | Allowed traits | D-traits | X-traits | Score | // // Construct | Simd, directive-name | Yes | No | No | // Device | Arch, Isa, Kind | No | Yes | No | // Implementation | Atomic_Default_Mem_Order | No | Yes | Yes | // | Extension, Requires | | | | // | Vendor | | | | // Target_Device | Arch, Device_Num, Isa | No | Yes | No | // | Kind, Uid | | | | // User | Condition | No | No | Yes | struct TraitSetConfig { std::set allowed; bool allowsDirectiveTraits; bool allowsExtensionTraits; bool allowsScore; }; using SName = parser::OmpTraitSetSelectorName::Value; using TName = parser::OmpTraitSelectorName::Value; static const std::map configs{ {SName::Construct, // {{TName::Simd}, true, false, false}}, {SName::Device, // {{TName::Arch, TName::Isa, TName::Kind}, false, true, false}}, {SName::Implementation, // {{TName::Atomic_Default_Mem_Order, TName::Extension, TName::Requires, TName::Vendor}, false, true, true}}, {SName::Target_Device, // {{TName::Arch, TName::Device_Num, TName::Isa, TName::Kind, TName::Uid}, false, true, false}}, {SName::User, // {{TName::Condition}, false, false, true}}, }; auto checkTraitSet{[&](const TraitSetConfig &config) { auto &[setName, traits]{traitSet.t}; auto usn{parser::ToUpperCaseLetters(setName.ToString())}; // Check if there are any duplicate traits. CheckTraitSelectorList(traits); for (const parser::OmpTraitSelector &trait : traits) { // Don't use structured bindings here, because they cannot be captured // before C++20. auto &traitName = std::get(trait.t); auto &maybeProps = std::get>( trait.t); // Check allowed traits common::visit( // common::visitors{ [&](parser::OmpTraitSelectorName::Value v) { if (!config.allowed.count(v)) { context_.Say(traitName.source, "%s is not a valid trait for %s trait set"_err_en_US, parser::ToUpperCaseLetters(traitName.ToString()), usn); } }, [&](llvm::omp::Directive) { if (!config.allowsDirectiveTraits) { context_.Say(traitName.source, "Directive name is not a valid trait for %s trait set"_err_en_US, usn); } }, [&](const std::string &) { if (!config.allowsExtensionTraits) { context_.Say(traitName.source, "Extension traits are not valid for %s trait set"_err_en_US, usn); } }, }, traitName.u); // Check score if (maybeProps) { auto &[maybeScore, _]{maybeProps->t}; if (maybeScore) { CheckTraitScore(*maybeScore); } } // Check the properties of the individual traits CheckTraitSelector(traitSet, trait); } }}; checkTraitSet( configs.at(std::get(traitSet.t).v)); } void OmpStructureChecker::CheckTraitScore(const parser::OmpTraitScore &score) { // [6.0:322:23] // A score-expression must be a non-negative constant integer expression. if (auto value{GetIntValue(score)}; !value || value < 0) { context_.Say(score.source, "SCORE expression must be a non-negative constant integer expression"_err_en_US); } } bool OmpStructureChecker::VerifyTraitPropertyLists( const parser::OmpTraitSetSelector &traitSet, const parser::OmpTraitSelector &trait) { using TraitName = parser::OmpTraitSelectorName; using PropertyList = std::list; auto &[traitName, maybeProps]{trait.t}; auto checkPropertyList{[&](const PropertyList &properties, auto isValid, const std::string &message) { bool foundInvalid{false}; for (const parser::OmpTraitProperty &prop : properties) { if (!isValid(prop)) { if (foundInvalid) { context_.Say( prop.source, "More invalid properties are present"_err_en_US); break; } context_.Say(prop.source, "%s"_err_en_US, message); foundInvalid = true; } } return !foundInvalid; }}; bool invalid{false}; if (std::holds_alternative(traitName.u)) { // Directive-name traits don't have properties. if (maybeProps) { context_.Say(trait.source, "Directive-name traits cannot have properties"_err_en_US); invalid = true; } } // Ignore properties on extension traits. // See `TraitSelectorParser` in openmp-parser.cpp if (auto *v{std::get_if(&traitName.u)}) { switch (*v) { // name-list properties case parser::OmpTraitSelectorName::Value::Arch: case parser::OmpTraitSelectorName::Value::Extension: case parser::OmpTraitSelectorName::Value::Isa: case parser::OmpTraitSelectorName::Value::Kind: case parser::OmpTraitSelectorName::Value::Uid: case parser::OmpTraitSelectorName::Value::Vendor: if (maybeProps) { auto isName{[](const parser::OmpTraitProperty &prop) { return std::holds_alternative(prop.u); }}; invalid = !checkPropertyList(std::get(maybeProps->t), isName, "Trait property should be a name"); } break; // clause-list case parser::OmpTraitSelectorName::Value::Atomic_Default_Mem_Order: case parser::OmpTraitSelectorName::Value::Requires: case parser::OmpTraitSelectorName::Value::Simd: if (maybeProps) { auto isClause{[&](const parser::OmpTraitProperty &prop) { return GetClauseFromProperty(prop).has_value(); }}; invalid = !checkPropertyList(std::get(maybeProps->t), isClause, "Trait property should be a clause"); } break; // expr-list case parser::OmpTraitSelectorName::Value::Condition: case parser::OmpTraitSelectorName::Value::Device_Num: if (maybeProps) { auto isExpr{[](const parser::OmpTraitProperty &prop) { return std::holds_alternative(prop.u); }}; invalid = !checkPropertyList(std::get(maybeProps->t), isExpr, "Trait property should be a scalar expression"); } break; } // switch } return !invalid; } void OmpStructureChecker::CheckTraitSelector( const parser::OmpTraitSetSelector &traitSet, const parser::OmpTraitSelector &trait) { using TraitName = parser::OmpTraitSelectorName; auto &[traitName, maybeProps]{trait.t}; // Only do the detailed checks if the property lists are valid. if (VerifyTraitPropertyLists(traitSet, trait)) { if (std::holds_alternative(traitName.u) || std::holds_alternative(traitName.u)) { // No properties here: directives don't have properties, and // we don't implement any extension traits now. return; } // Specific traits we want to check. // Limitations: // (1) The properties for these traits are defined in "Additional // Definitions for the OpenMP API Specification". It's not clear how // to define them in a portable way, and how to verify their validity, // especially if they get replaced by their integer values (in case // they are defined as enums). // (2) These are entirely implementation-defined, and at the moment // there is no known schema to validate these values. auto v{std::get(traitName.u)}; switch (v) { case TraitName::Value::Arch: // Unchecked, TBD(1) break; case TraitName::Value::Atomic_Default_Mem_Order: CheckTraitADMO(traitSet, trait); break; case TraitName::Value::Condition: CheckTraitCondition(traitSet, trait); break; case TraitName::Value::Device_Num: CheckTraitDeviceNum(traitSet, trait); break; case TraitName::Value::Extension: // Ignore break; case TraitName::Value::Isa: // Unchecked, TBD(1) break; case TraitName::Value::Kind: // Unchecked, TBD(1) break; case TraitName::Value::Requires: CheckTraitRequires(traitSet, trait); break; case TraitName::Value::Simd: CheckTraitSimd(traitSet, trait); break; case TraitName::Value::Uid: // Unchecked, TBD(2) break; case TraitName::Value::Vendor: // Unchecked, TBD(1) break; } } } void OmpStructureChecker::CheckTraitADMO( const parser::OmpTraitSetSelector &traitSet, const parser::OmpTraitSelector &trait) { auto &traitName{std::get(trait.t)}; auto &properties{GetTraitPropertyList(trait)}; if (properties.size() != 1) { context_.Say(trait.source, "%s trait requires a single clause property"_err_en_US, parser::ToUpperCaseLetters(traitName.ToString())); } else { const parser::OmpTraitProperty &property{properties.front()}; auto clauseId{*GetClauseFromProperty(property)}; // Check that the clause belongs to the memory-order clause-set. // Clause sets will hopefully be autogenerated at some point. switch (clauseId) { case llvm::omp::Clause::OMPC_acq_rel: case llvm::omp::Clause::OMPC_acquire: case llvm::omp::Clause::OMPC_relaxed: case llvm::omp::Clause::OMPC_release: case llvm::omp::Clause::OMPC_seq_cst: break; default: context_.Say(property.source, "%s trait requires a clause from the memory-order clause set"_err_en_US, parser::ToUpperCaseLetters(traitName.ToString())); } using ClauseProperty = common::Indirection; if (!std::holds_alternative(property.u)) { context_.Say(property.source, "Invalid clause specification for %s"_err_en_US, parser::ToUpperCaseLetters(getClauseName(clauseId))); } } } void OmpStructureChecker::CheckTraitCondition( const parser::OmpTraitSetSelector &traitSet, const parser::OmpTraitSelector &trait) { auto &traitName{std::get(trait.t)}; auto &properties{GetTraitPropertyList(trait)}; if (properties.size() != 1) { context_.Say(trait.source, "%s trait requires a single expression property"_err_en_US, parser::ToUpperCaseLetters(traitName.ToString())); } else { const parser::OmpTraitProperty &property{properties.front()}; auto &scalarExpr{std::get(property.u)}; auto maybeType{GetDynamicType(scalarExpr.thing.value())}; if (!maybeType || maybeType->category() != TypeCategory::Logical) { context_.Say(property.source, "%s trait requires a single LOGICAL expression"_err_en_US, parser::ToUpperCaseLetters(traitName.ToString())); } } } void OmpStructureChecker::CheckTraitDeviceNum( const parser::OmpTraitSetSelector &traitSet, const parser::OmpTraitSelector &trait) { auto &traitName{std::get(trait.t)}; auto &properties{GetTraitPropertyList(trait)}; if (properties.size() != 1) { context_.Say(trait.source, "%s trait requires a single expression property"_err_en_US, parser::ToUpperCaseLetters(traitName.ToString())); } // No other checks at the moment. } void OmpStructureChecker::CheckTraitRequires( const parser::OmpTraitSetSelector &traitSet, const parser::OmpTraitSelector &trait) { unsigned version{context_.langOptions().OpenMPVersion}; auto &traitName{std::get(trait.t)}; auto &properties{GetTraitPropertyList(trait)}; for (const parser::OmpTraitProperty &property : properties) { auto clauseId{*GetClauseFromProperty(property)}; if (!llvm::omp::isAllowedClauseForDirective( llvm::omp::OMPD_requires, clauseId, version)) { context_.Say(property.source, "%s trait requires a clause from the requirement clause set"_err_en_US, parser::ToUpperCaseLetters(traitName.ToString())); } using ClauseProperty = common::Indirection; if (!std::holds_alternative(property.u)) { context_.Say(property.source, "Invalid clause specification for %s"_err_en_US, parser::ToUpperCaseLetters(getClauseName(clauseId))); } } } void OmpStructureChecker::CheckTraitSimd( const parser::OmpTraitSetSelector &traitSet, const parser::OmpTraitSelector &trait) { unsigned version{context_.langOptions().OpenMPVersion}; auto &traitName{std::get(trait.t)}; auto &properties{GetTraitPropertyList(trait)}; for (const parser::OmpTraitProperty &property : properties) { auto clauseId{*GetClauseFromProperty(property)}; if (!llvm::omp::isAllowedClauseForDirective( llvm::omp::OMPD_declare_simd, clauseId, version)) { context_.Say(property.source, "%s trait requires a clause that is allowed on the %s directive"_err_en_US, parser::ToUpperCaseLetters(traitName.ToString()), parser::ToUpperCaseLetters( getDirectiveName(llvm::omp::OMPD_declare_simd))); } using ClauseProperty = common::Indirection; if (!std::holds_alternative(property.u)) { context_.Say(property.source, "Invalid clause specification for %s"_err_en_US, parser::ToUpperCaseLetters(getClauseName(clauseId))); } } } void OmpStructureChecker::Enter(const parser::OmpMetadirectiveDirective &x) { EnterDirectiveNest(MetadirectiveNest); PushContextAndClauseSets(x.source, llvm::omp::Directive::OMPD_metadirective); } void OmpStructureChecker::Leave(const parser::OmpMetadirectiveDirective &) { ExitDirectiveNest(MetadirectiveNest); dirContext_.pop_back(); } } // namespace Fortran::semantics