//===-- lib/Semantics/check-omp-variant.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, DECLARE VARIANT, and related constructs. // //===----------------------------------------------------------------------===// #include "check-omp-structure.h" #include "flang/Common/idioms.h" #include "flang/Common/indirection.h" #include "flang/Common/visit.h" #include "flang/Evaluate/check-expression.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/openmp-utils.h" #include "flang/Semantics/symbol.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::CheckContextSelectorSpecification( const parser::OmpContextSelector &ctx) { 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::Enter(const parser::OmpContextSelector &ctx) { EnterDirectiveNest(ContextSelectorNest); CheckContextSelectorSpecification(ctx); } 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) { if (!config.allowsScore) context_.Say(maybeScore->source, "SCORE is not allowed for %s trait set"_err_en_US, usn); else 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::OmpDirectiveSpecification &x) { // OmpDirectiveSpecification exists on its own only in clauses on // METADIRECTIVE. // In other cases it's a part of other constructs that handle directive // context stack by themselves. if (!GetDirectiveNest(MetadirectiveNest)) { return; } llvm::omp::Directive dirId{x.DirId()}; if (const parser::OpenMPConstruct *meta{GetCurrentConstruct()}) { if (parser::Unwrap(meta->u)) { unsigned version{context_.langOptions().OpenMPVersion}; switch (llvm::omp::getDirectiveAssociation(dirId)) { case llvm::omp::Association::Block: case llvm::omp::Association::LoopNest: case llvm::omp::Association::LoopSeq: break; default: if (dirId != llvm::omp::Directive::OMPD_nothing) { context_.Say(x.DirName().source, "A directive in BEGIN %s should have a corresponding end-directive"_err_en_US, parser::omp::GetUpperName( llvm::omp::Directive::OMPD_metadirective, version)); } } } } PushContextAndClauseSets( std::get(x.t).source, dirId); } void OmpStructureChecker::Leave(const parser::OmpDirectiveSpecification &x) { if (GetDirectiveNest(MetadirectiveNest)) { dirContext_.pop_back(); } } void OmpStructureChecker::Enter(const parser::OmpMetadirectiveDirective &x) { EnterDirectiveNest(MetadirectiveNest); PushContextAndClauseSets( x.v.source, llvm::omp::Directive::OMPD_metadirective); } void OmpStructureChecker::Leave(const parser::OmpMetadirectiveDirective &) { ExitDirectiveNest(MetadirectiveNest); dirContext_.pop_back(); } void OmpStructureChecker::Enter( const parser::OmpDelimitedMetadirectiveDirective &x) { PushContextAndClauseSets(x.source, llvm::omp::Directive::OMPD_metadirective); } void OmpStructureChecker::Leave( const parser::OmpDelimitedMetadirectiveDirective &) { dirContext_.pop_back(); } static const parser::traits::OmpContextSelectorSpecification * getMatchClauseContextSelector(const parser::OmpDirectiveSpecification &spec) { for (const parser::OmpClause &clause : spec.Clauses().v) { if (clause.Id() == llvm::omp::Clause::OMPC_match) return &std::get(clause.u).v.v; } return nullptr; } void OmpStructureChecker::CheckDeclareVariantUserConditions( const parser::OmpContextSelector &ctx) { using SetName = parser::OmpTraitSetSelectorName; using TraitName = parser::OmpTraitSelectorName; for (const parser::OmpTraitSetSelector &traitSet : ctx.v) { if (std::get(traitSet.t).v != SetName::Value::User) { continue; } for (const parser::OmpTraitSelector &trait : std::get>(traitSet.t)) { const auto &traitName{std::get(trait.t)}; if (!std::holds_alternative(traitName.u) || std::get(traitName.u) != TraitName::Value::Condition) { continue; } const auto &maybeProps{ std::get>( trait.t)}; if (!maybeProps) { continue; } const auto &properties{ std::get>(maybeProps->t)}; if (properties.size() != 1) { continue; } const parser::OmpTraitProperty &property{properties.front()}; const parser::ScalarExpr &scalarExpr{ std::get(property.u)}; auto maybeType{GetDynamicType(scalarExpr.thing.value())}; if (!maybeType || maybeType->category() != TypeCategory::Logical) { continue; } if (const auto *expr{GetExpr(scalarExpr)}) { if (!IsConstantExpr(*expr, &context_.foldingContext())) { context_.Say(property.source, "Run-time USER condition in the MATCH clause is not yet implemented"_err_en_US); } } } } } void OmpStructureChecker::CheckOmpDeclareVariantDirective( const parser::OmpDeclareVariantDirective &x) { const parser::OmpDirectiveSpecification &spec{x.v}; const parser::OmpArgumentList &args{spec.Arguments()}; if (args.v.size() != 1) { context_.Say(args.source, "DECLARE_VARIANT directive should have a single argument"_err_en_US); return; } auto InvalidArgument{[&](parser::CharBlock source) { context_.Say(source, "The argument to the DECLARE_VARIANT directive should be [base-name:]variant-name"_err_en_US); }}; auto CheckProcedureSymbol{[&](const Symbol *sym, parser::CharBlock source) { if (sym) { if (!IsProcedure(*sym) && !IsFunction(*sym)) { auto &msg{context_.Say(source, "The name '%s' should refer to a procedure"_err_en_US, sym->name())}; if (sym->test(Symbol::Flag::Implicit)) { msg.Attach(source, "The name '%s' has been implicitly declared"_en_US, sym->name()); } } } else { InvalidArgument(source); } }}; const Symbol *base{nullptr}; const Symbol *variant{nullptr}; const parser::OmpArgument &arg{args.v.front()}; common::visit( // common::visitors{ [&](const parser::OmpBaseVariantNames &y) { base = GetObjectSymbol(std::get<0>(y.t)); variant = GetObjectSymbol(std::get<1>(y.t)); CheckProcedureSymbol(base, arg.source); CheckProcedureSymbol(variant, arg.source); }, [&](const parser::OmpLocator &y) { variant = GetArgumentSymbol(arg); CheckProcedureSymbol(variant, arg.source); const Scope &containingScope{context_.FindScope(x.source)}; if (const Symbol *host{ GetProgramUnitContaining(containingScope).symbol()}) { base = host; } }, [&](auto &&y) { InvalidArgument(arg.source); }, }, arg.u); if (base && variant) { base = &base->GetUltimate(); variant = &variant->GetUltimate(); if (base == variant) { context_.Say(arg.source, "The variant procedure must differ from the base procedure"_err_en_US); } else if (!declareVariantPairs_.emplace(base, variant).second) { context_.Say(arg.source, "Variant '%s' was already specified for '%s' in another DECLARE VARIANT directive"_err_en_US, variant->name(), base->name()); } } const parser::traits::OmpContextSelectorSpecification *matchSelector{ getMatchClauseContextSelector(spec)}; if (!matchSelector) { context_.Say(x.source, "DECLARE_VARIANT directive requires a MATCH clause"_err_en_US); return; } EnterDirectiveNest(ContextSelectorNest); CheckContextSelectorSpecification(*matchSelector); CheckDeclareVariantUserConditions(*matchSelector); ExitDirectiveNest(ContextSelectorNest); } void OmpStructureChecker::Enter(const parser::OmpDeclareVariantDirective &x) { const parser::OmpDirectiveName &dirName{x.v.DirName()}; PushContextAndClauseSets(dirName.source, dirName.v); CheckOmpDeclareVariantDirective(x); } void OmpStructureChecker::Leave(const parser::OmpDeclareVariantDirective &) { dirContext_.pop_back(); } } // namespace Fortran::semantics