//===----------------------------------------------------------------------===// // // 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 "resolve-directives.h" #include "check-acc-structure.h" #include "check-omp-structure.h" #include "resolve-names-utils.h" #include "flang/Common/idioms.h" #include "flang/Evaluate/fold.h" #include "flang/Evaluate/tools.h" #include "flang/Evaluate/type.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/expression.h" #include "flang/Semantics/openmp-dsa.h" #include "flang/Semantics/openmp-modifiers.h" #include "flang/Semantics/openmp-utils.h" #include "flang/Semantics/symbol.h" #include "flang/Semantics/tools.h" #include "flang/Support/Flags.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/Frontend/OpenMP/OMP.h.inc" #include "llvm/Support/Debug.h" #include #include namespace Fortran::semantics { template static Scope *GetScope(SemanticsContext &context, const T &x) { if (auto source{GetLastSource(x)}) { return &context.FindScope(*source); } else { return nullptr; } } template class DirectiveAttributeVisitor { public: explicit DirectiveAttributeVisitor(SemanticsContext &context) : context_{context} {} template bool Pre(const A &) { return true; } template void Post(const A &) {} protected: struct DirContext { DirContext(const parser::CharBlock &source, T d, Scope &s) : directiveSource{source}, directive{d}, scope{s} {} parser::CharBlock directiveSource; T directive; Scope &scope; Symbol::Flag defaultDSA{Symbol::Flag::AccShared}; // TODOACC std::map objectWithDSA; std::map defaultMap; std::optional FindSymbolWithDSA(const Symbol &symbol) { if (auto it{objectWithDSA.find(&symbol)}; it != objectWithDSA.end()) { return it->second; } return std::nullopt; } bool withinConstruct{false}; std::int64_t associatedLoopLevel{0}; }; DirContext &GetContext() { CHECK(!dirContext_.empty()); return dirContext_.back(); } void PushContext(const parser::CharBlock &source, T dir, Scope &scope) { if constexpr (std::is_same_v) { dirContext_.emplace_back(source, dir, scope); if (std::size_t size{dirContext_.size()}; size > 1) { std::size_t lastIndex{size - 1}; dirContext_[lastIndex].defaultDSA = dirContext_[lastIndex - 1].defaultDSA; } } else { dirContext_.emplace_back(source, dir, scope); } } void PushContext(const parser::CharBlock &source, T dir) { PushContext(source, dir, context_.FindScope(source)); } void PopContext() { dirContext_.pop_back(); } void SetContextDirectiveSource(parser::CharBlock &dir) { GetContext().directiveSource = dir; } Scope &currScope() { return GetContext().scope; } void AddContextDefaultmapBehaviour(parser::OmpVariableCategory::Value VarCat, parser::OmpDefaultmapClause::ImplicitBehavior ImpBehav) { GetContext().defaultMap[VarCat] = ImpBehav; } void SetContextDefaultDSA(Symbol::Flag flag) { GetContext().defaultDSA = flag; } void AddToContextObjectWithDSA( const Symbol &symbol, Symbol::Flag flag, DirContext &context) { context.objectWithDSA.emplace(&symbol, flag); } void AddToContextObjectWithDSA(const Symbol &symbol, Symbol::Flag flag) { AddToContextObjectWithDSA(symbol, flag, GetContext()); } bool IsObjectWithDSA(const Symbol &symbol) { return GetContext().FindSymbolWithDSA(symbol).has_value(); } bool IsObjectWithVisibleDSA(const Symbol &symbol) { for (std::size_t i{dirContext_.size()}; i != 0; i--) { if (dirContext_[i - 1].FindSymbolWithDSA(symbol).has_value()) { return true; } } return false; } bool WithinConstruct() { return !dirContext_.empty() && GetContext().withinConstruct; } void SetContextAssociatedLoopLevel(std::int64_t level) { GetContext().associatedLoopLevel = level; } Symbol &MakeAssocSymbol( const SourceName &name, const Symbol &prev, Scope &scope) { const auto pair{scope.try_emplace(name, Attrs{}, HostAssocDetails{prev})}; return *pair.first->second; } Symbol &MakeAssocSymbol(const SourceName &name, const Symbol &prev) { return MakeAssocSymbol(name, prev, currScope()); } void AddDataSharingAttributeObject(SymbolRef object) { dataSharingAttributeObjects_.insert(object); } void ClearDataSharingAttributeObjects() { dataSharingAttributeObjects_.clear(); } bool HasDataSharingAttributeObject(const Symbol &); /// Extract the iv and bounds of a DO loop: /// 1. The loop index/induction variable /// 2. The lower bound /// 3. The upper bound /// 4. The step/increment (or nullptr if not present) /// /// Each returned tuple value can be nullptr if not present. Diagnoses an /// error if the the DO loop is a DO WHILE or DO CONCURRENT loop. std::tuple GetLoopBounds(const parser::DoConstruct &); /// Extract the loop index/induction variable from a DO loop. Diagnoses an /// error if the the DO loop is a DO WHILE or DO CONCURRENT loop and returns /// nullptr. const parser::Name *GetLoopIndex(const parser::DoConstruct &); const parser::DoConstruct *GetDoConstructIf( const parser::ExecutionPartConstruct &); Symbol *DeclareNewAccessEntity(const Symbol &, Symbol::Flag, Scope &); Symbol *DeclareAccessEntity(const parser::Name &, Symbol::Flag, Scope &); Symbol *DeclareAccessEntity(Symbol &, Symbol::Flag, Scope &); UnorderedSymbolSet dataSharingAttributeObjects_; // on one directive SemanticsContext &context_; std::vector dirContext_; // used as a stack }; class AccAttributeVisitor : DirectiveAttributeVisitor { public: explicit AccAttributeVisitor(SemanticsContext &context, Scope *topScope) : DirectiveAttributeVisitor(context), topScope_(topScope) {} template void Walk(const A &x) { parser::Walk(x, *this); } template bool Pre(const A &) { return true; } template void Post(const A &) {} bool Pre(const parser::OpenACCBlockConstruct &); void Post(const parser::OpenACCBlockConstruct &) { PopContext(); } bool Pre(const parser::OpenACCCombinedConstruct &); void Post(const parser::OpenACCCombinedConstruct &) { PopContext(); } void Post(const parser::AccBeginCombinedDirective &) { GetContext().withinConstruct = true; } bool Pre(const parser::OpenACCDeclarativeConstruct &); void Post(const parser::OpenACCDeclarativeConstruct &) { PopContext(); } void Post(const parser::AccDeclarativeDirective &) { GetContext().withinConstruct = true; } bool Pre(const parser::OpenACCRoutineConstruct &); bool Pre(const parser::AccBindClause &); void Post(const parser::OpenACCStandaloneDeclarativeConstruct &); void Post(const parser::AccBeginBlockDirective &) { GetContext().withinConstruct = true; } bool Pre(const parser::OpenACCLoopConstruct &); void Post(const parser::OpenACCLoopConstruct &) { PopContext(); } void Post(const parser::AccLoopDirective &) { GetContext().withinConstruct = true; } // TODO: We should probably also privatize ConcurrentBounds. template bool Pre(const parser::LoopBounds &x) { if (!dirContext_.empty() && GetContext().withinConstruct) { if (auto *symbol{ResolveAcc( x.Name().thing, Symbol::Flag::AccPrivate, currScope())}) { AddToContextObjectWithDSA(*symbol, Symbol::Flag::AccPrivate); } } return true; } bool Pre(const parser::OpenACCStandaloneConstruct &); void Post(const parser::OpenACCStandaloneConstruct &) { PopContext(); } void Post(const parser::AccStandaloneDirective &) { GetContext().withinConstruct = true; } bool Pre(const parser::OpenACCWaitConstruct &); void Post(const parser::OpenACCWaitConstruct &) { PopContext(); } bool Pre(const parser::OpenACCAtomicConstruct &); void Post(const parser::OpenACCAtomicConstruct &) { PopContext(); } bool Pre(const parser::OpenACCCacheConstruct &); void Post(const parser::OpenACCCacheConstruct &) { PopContext(); } void Post(const parser::AccDefaultClause &); bool Pre(const parser::AccClause::Attach &); bool Pre(const parser::AccClause::Detach &); bool Pre(const parser::AccClause::Copy &x) { ResolveAccObjectList(x.v, Symbol::Flag::AccCopy); return false; } bool Pre(const parser::AccClause::Create &x) { const auto &objectList{std::get(x.v.t)}; ResolveAccObjectList(objectList, Symbol::Flag::AccCreate); return false; } bool Pre(const parser::AccClause::Copyin &x) { const auto &objectList{std::get(x.v.t)}; const auto &modifier{ std::get>(x.v.t)}; if (modifier && (*modifier).v == parser::AccDataModifier::Modifier::ReadOnly) { ResolveAccObjectList(objectList, Symbol::Flag::AccCopyInReadOnly); } else { ResolveAccObjectList(objectList, Symbol::Flag::AccCopyIn); } return false; } bool Pre(const parser::AccClause::Copyout &x) { const auto &objectList{std::get(x.v.t)}; ResolveAccObjectList(objectList, Symbol::Flag::AccCopyOut); return false; } bool Pre(const parser::AccClause::Present &x) { ResolveAccObjectList(x.v, Symbol::Flag::AccPresent); return false; } bool Pre(const parser::AccClause::Private &x) { ResolveAccObjectList(x.v, Symbol::Flag::AccPrivate); return false; } bool Pre(const parser::AccClause::Firstprivate &x) { ResolveAccObjectList(x.v, Symbol::Flag::AccFirstPrivate); return false; } bool Pre(const parser::AccClause::Device &x) { ResolveAccObjectList(x.v, Symbol::Flag::AccDevice); return false; } bool Pre(const parser::AccClause::DeviceResident &x) { ResolveAccObjectList(x.v, Symbol::Flag::AccDeviceResident); return false; } bool Pre(const parser::AccClause::Deviceptr &x) { ResolveAccObjectList(x.v, Symbol::Flag::AccDevicePtr); return false; } bool Pre(const parser::AccClause::Link &x) { ResolveAccObjectList(x.v, Symbol::Flag::AccLink); return false; } bool Pre(const parser::AccClause::Host &x) { ResolveAccObjectList(x.v, Symbol::Flag::AccHost); return false; } bool Pre(const parser::AccClause::Self &x) { const std::optional &accSelfClause = x.v; if (accSelfClause && std::holds_alternative((*accSelfClause).u)) { const auto &accObjectList = std::get((*accSelfClause).u); ResolveAccObjectList(accObjectList, Symbol::Flag::AccSelf); } return false; } bool Pre(const parser::AccClause::Reduction &x) { const auto &objectList{std::get(x.v.t)}; ResolveAccObjectList(objectList, Symbol::Flag::AccReduction); return false; } bool Pre(const parser::AccClause::UseDevice &x) { ResolveAccObjectList(x.v, Symbol::Flag::AccUseDevice); // use_device is only valid on host_data directive assert(GetContext().directive == llvm::acc::Directive::ACCD_host_data && "use_device clause is only valid on host_data directive"); // Check for duplicate use_device variables for (const auto &accObject : x.v.v) { if (const auto *designator{ std::get_if(&accObject.u)}) { if (const auto *name{parser::GetDesignatorNameIfDataRef(*designator)}) { if (name->symbol) { AddUseDeviceObject(*name->symbol, *name); } } } } return false; } void Post(const parser::Name &); private: std::int64_t GetAssociatedLoopLevelFromClauses(const parser::AccClauseList &); bool HasForceCollapseModifier(const parser::AccClauseList &); Symbol::Flags dataSharingAttributeFlags{Symbol::Flag::AccShared, Symbol::Flag::AccPrivate, Symbol::Flag::AccFirstPrivate, Symbol::Flag::AccReduction}; Symbol::Flags dataMappingAttributeFlags{Symbol::Flag::AccCreate, Symbol::Flag::AccCopyIn, Symbol::Flag::AccCopyOut, Symbol::Flag::AccDelete, Symbol::Flag::AccPresent}; Symbol::Flags accDataMvtFlags{ Symbol::Flag::AccDevice, Symbol::Flag::AccHost, Symbol::Flag::AccSelf}; Symbol::Flags accFlagsRequireMark{Symbol::Flag::AccCreate, Symbol::Flag::AccCopyIn, Symbol::Flag::AccCopyInReadOnly, Symbol::Flag::AccCopy, Symbol::Flag::AccCopyOut, Symbol::Flag::AccDevicePtr, Symbol::Flag::AccDeviceResident, Symbol::Flag::AccLink, Symbol::Flag::AccPresent}; void CheckAssociatedLoop(const parser::DoConstruct &, bool forceCollapsed); void ResolveAccObjectList(const parser::AccObjectList &, Symbol::Flag); void ResolveAccObject(const parser::AccObject &, Symbol::Flag); Symbol *ResolveAcc(const parser::Name &, Symbol::Flag, Scope &); Symbol *ResolveAcc(Symbol &, Symbol::Flag, Scope &); Symbol *ResolveName(const parser::Name &); Symbol *ResolveFctName(const parser::Name &); Symbol *ResolveAccCommonBlockName(const parser::Name *); Symbol *DeclareOrMarkOtherAccessEntity(const parser::Name &, Symbol::Flag); Symbol *DeclareOrMarkOtherAccessEntity(Symbol &, Symbol::Flag); void CheckMultipleAppearances(const parser::Name &, const Symbol &, Symbol::Flag, const parser::AccObject *occurrence = nullptr); void AllowOnlyArrayAndSubArray(const parser::AccObjectList &objectList); void DoNotAllowAssumedSizedArray(const parser::AccObjectList &objectList); void AllowOnlyVariable(const parser::AccObject &object); void EnsureAllocatableOrPointer( const llvm::acc::Clause clause, const parser::AccObjectList &objectList); void AddRoutineInfoToSymbol( Symbol &, const parser::OpenACCRoutineConstruct &); // Track use_device variables and check for duplicates. // Emits an error if the object was already added. void AddUseDeviceObject(const Symbol &, const parser::Name &); void ClearUseDeviceObjects() { useDeviceObjects_.clear(); } UnorderedSymbolSet useDeviceObjects_; Scope *topScope_; }; // Data-sharing and Data-mapping attributes for data-refs in OpenMP construct class OmpAttributeVisitor : DirectiveAttributeVisitor { public: explicit OmpAttributeVisitor(SemanticsContext &context) : DirectiveAttributeVisitor(context) {} static bool HasStaticStorageDuration(const Symbol &symbol) { auto &ultSym = symbol.GetUltimate(); // Module-scope variable return ultSym.owner().kind() == Scope::Kind::Module || // Data statement variable ultSym.flags().test(Symbol::Flag::InDataStmt) || // Save attribute variable ultSym.attrs().test(Attr::SAVE) || // Referenced in a common block ultSym.flags().test(Symbol::Flag::InCommonBlock); } static const Symbol &GetStorageOwner(const Symbol &symbol) { static auto getParent = [](const Symbol *s) -> const Symbol * { if (auto *details{s->detailsIf()}) { return &details->symbol(); } else if (auto *details{s->detailsIf()}) { return &details->symbol(); } else { return nullptr; } }; static auto isPrivate = [](const Symbol &symbol) { static const Symbol::Flags privatizing{Symbol::Flag::OmpPrivate, Symbol::Flag::OmpFirstPrivate, Symbol::Flag::OmpLastPrivate, Symbol::Flag::OmpLinear}; return (symbol.flags() & privatizing).any(); }; const Symbol *sym = &symbol; while (true) { if (isPrivate(*sym)) { return *sym; } if (const Symbol *parent{getParent(sym)}) { sym = parent; } else { return *sym; } } llvm_unreachable("Error while looking for storage owning symbol"); } // Recognize symbols that are not created as a part of the OpenMP data- // sharing processing, and that are declared inside of the construct. // These symbols are predetermined private, but they shouldn't be marked // in any special way, because there is nothing to be done for them. // They are not symbols for which private copies need to be created, // they are already themselves private. static bool IsLocalInsideScope(const Symbol &symbol, const Scope &scope) { // A symbol that is marked with a DSA will be cloned in the construct // scope and marked as host-associated. This applies to privatized symbols // as well even though they will have their own storage. They should be // considered local regardless of the status of the original symbol. const Symbol &actual{GetStorageOwner(symbol)}; return actual.owner() != scope && scope.Contains(actual.owner()) && !HasStaticStorageDuration(actual); } template void Walk(const A &x) { parser::Walk(x, *this); } // Normally the catch-all Pre/Post functions are templates taking // "const T &". For a class D derived from B, and an explicit overload // of Pre(const B &), a call to Pre(D) will select the template instead // of the base clase overload. // Force user-defined conversion from any const-reference, to make sure // that the Pre(AbsorbAnyReference) and Post(AbsorbAnyReference) overloads // will be worse than derived-to-base conversions. This will, for example, // invoke Pre(const OmpBlockConstruct &) for directives derived from it. struct AbsorbAnyReference { template AbsorbAnyReference(const T &) {} }; bool Pre(AbsorbAnyReference) { return true; } void Post(AbsorbAnyReference) {} bool Pre(const parser::SpecificationPart &) { partStack_.push_back(PartKind::SpecificationPart); return true; } void Post(const parser::SpecificationPart &) { partStack_.pop_back(); } bool Pre(const parser::ExecutionPart &) { partStack_.push_back(PartKind::ExecutionPart); return true; } void Post(const parser::ExecutionPart &) { partStack_.pop_back(); } bool Pre(const parser::StmtFunctionStmt &x) { const auto &parsedExpr{std::get>(x.t)}; if (const auto *expr{GetExpr(context_, parsedExpr)}) { for (const Symbol &symbol : evaluate::CollectSymbols(*expr)) { if (!IsStmtFunctionDummy(symbol)) { stmtFunctionExprSymbols_.insert(symbol.GetUltimate()); } } } return true; } bool Pre(const parser::UseStmt &x) { if (x.moduleName.symbol) { Scope &thisScope{context_.FindScope(x.moduleName.source)}; common::visit( [&](auto &&details) { if constexpr (std::is_convertible_v) { AddOmpRequiresToScope(thisScope, details.ompRequires(), details.ompAtomicDefaultMemOrder()); } }, x.moduleName.symbol->details()); } return true; } bool Pre(const parser::OmpStylizedDeclaration &x) { static llvm::StringMap map{ {"omp_in", Symbol::Flag::OmpInVar}, {"omp_orig", Symbol::Flag::OmpOrigVar}, {"omp_out", Symbol::Flag::OmpOutVar}, {"omp_priv", Symbol::Flag::OmpPrivVar}, }; if (auto &name{std::get(x.var.t)}; name.symbol) { if (auto found{map.find(name.ToString())}; found != map.end()) { ResolveOmp(name, found->second, const_cast(DEREF(name.symbol).owner())); } } return false; } bool Pre(const parser::OmpMetadirectiveDirective &x) { PushContext(x.v.source, llvm::omp::Directive::OMPD_metadirective); return true; } void Post(const parser::OmpMetadirectiveDirective &) { PopContext(); } bool Pre(const parser::OmpBlockConstruct &); void Post(const parser::OmpBlockConstruct &); void Post(const parser::OmpBeginDirective &x) { GetContext().withinConstruct = true; } bool Pre(const parser::OmpGroupprivateDirective &); void Post(const parser::OmpGroupprivateDirective &) { PopContext(); } bool Pre(const parser::OpenMPStandaloneConstruct &x) { common::visit( [&](auto &&s) { using TypeS = llvm::remove_cvref_t; // These two cases are handled individually. if constexpr ( // !std::is_same_v && !std::is_same_v) { PushContext(x.source, s.v.DirId()); } }, x.u); return true; } void Post(const parser::OpenMPStandaloneConstruct &x) { // These two cases are handled individually. if (!std::holds_alternative(x.u) && !std::holds_alternative(x.u)) { PopContext(); } } bool Pre(const parser::OpenMPSimpleStandaloneConstruct &); void Post(const parser::OpenMPSimpleStandaloneConstruct &) { PopContext(); } bool Pre(const parser::OpenMPLoopConstruct &); void Post(const parser::OpenMPLoopConstruct &) { PopContext(); } bool Pre(const parser::OpenMPMisplacedEndDirective &x) { return false; } bool Pre(const parser::OpenMPInvalidDirective &x) { return false; } bool Pre(const parser::DoConstruct &); bool Pre(const parser::OpenMPSectionsConstruct &); void Post(const parser::OpenMPSectionsConstruct &) { PopContext(); } bool Pre(const parser::OmpSectionDirective &); void Post(const parser::OmpSectionDirective &) { PopContext(); } bool Pre(const parser::OpenMPCriticalConstruct &critical); void Post(const parser::OpenMPCriticalConstruct &) { PopContext(); } bool Pre(const parser::OmpDeclareSimdDirective &x) { PushContext(x.source, llvm::omp::Directive::OMPD_declare_simd); for (const parser::OmpArgument &arg : x.v.Arguments().v) { if (auto *object{parser::omp::GetArgumentObject(arg)}) { ResolveOmpObject(*object, Symbol::Flag::OmpDeclareSimd); } } return true; } void Post(const parser::OmpDeclareSimdDirective &) { PopContext(); } bool Pre(const parser::OpenMPDepobjConstruct &x) { PushContext(x.source, llvm::omp::Directive::OMPD_depobj); for (auto &arg : x.v.Arguments().v) { if (auto *object{parser::omp::GetArgumentObject(arg)}) { ResolveOmpObject(*object, Symbol::Flag::OmpDependObject); } } return true; } void Post(const parser::OpenMPDepobjConstruct &) { PopContext(); } bool Pre(const parser::OpenMPFlushConstruct &x) { PushContext(x.source, llvm::omp::Directive::OMPD_flush); for (auto &arg : x.v.Arguments().v) { if (auto *object{parser::omp::GetArgumentObject(arg)}) { if (auto *name{std::get_if(&object->u)}) { // ResolveOmpCommonBlockName resolves the symbol as a side effect if (!ResolveOmpCommonBlockName(name)) { context_.Say(name->source, // 2.15.3 "COMMON block must be declared in the same scoping unit " "in which the OpenMP directive or clause appears"_err_en_US); } } } } return true; } void Post(const parser::OpenMPFlushConstruct &) { PopContext(); } bool Pre(const parser::OmpRequiresDirective &x) { using OmpClauseSet = WithOmpDeclarative::OmpClauseSet; PushContext(x.source, llvm::omp::Directive::OMPD_requires); // Gather information from the clauses. OmpClauseSet reqs; std::optional memOrder; for (const parser::OmpClause &clause : x.v.Clauses().v) { using OmpClause = parser::OmpClause; reqs |= common::visit( common::visitors{ [&](const OmpClause::AtomicDefaultMemOrder &admo) { memOrder = admo.v.v; return OmpClauseSet{clause.Id()}; }, [&](auto &&s) { using TypeS = llvm::remove_cvref_t; if constexpr ( // std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v) { if (omp::GetLogicalArgument(s.v, context_).value_or(true)) { return OmpClauseSet{clause.Id()}; } } return OmpClauseSet{}; }, }, clause.u); } // Merge clauses into parents' symbols details. AddOmpRequiresToScope(currScope(), reqs, memOrder); return true; } void Post(const parser::OmpRequiresDirective &) { PopContext(); } bool Pre(const parser::OmpDeclareTargetDirective &); void Post(const parser::OmpDeclareTargetDirective &) { PopContext(); } bool Pre(const parser::OmpDeclareMapperDirective &); void Post(const parser::OmpDeclareMapperDirective &) { PopContext(); } bool Pre(const parser::OmpDeclareReductionDirective &); void Post(const parser::OmpDeclareReductionDirective &) { PopContext(); } bool Pre(const parser::OmpThreadprivateDirective &); void Post(const parser::OmpThreadprivateDirective &) { PopContext(); } bool Pre(const parser::OmpAllocateDirective &); bool Pre(const parser::OmpAssumeDirective &); void Post(const parser::OmpAssumeDirective &) { PopContext(); } bool Pre(const parser::OpenMPAtomicConstruct &); void Post(const parser::OpenMPAtomicConstruct &) { PopContext(); } bool Pre(const parser::OpenMPDispatchConstruct &); void Post(const parser::OpenMPDispatchConstruct &) { PopContext(); } bool Pre(const parser::OpenMPAllocatorsConstruct &); void Post(const parser::OpenMPAllocatorsConstruct &); bool Pre(const parser::OmpUtilityDirective &x) { PushContext(x.source, parser::omp::GetOmpDirectiveName(x).v); return true; } void Post(const parser::OmpUtilityDirective &) { PopContext(); } bool Pre(const parser::OmpDeclareVariantDirective &x) { PushContext(x.source, llvm::omp::Directive::OMPD_declare_variant); return true; } void Post(const parser::OmpDeclareVariantDirective &) { PopContext(); }; // 2.15.3 Data-Sharing Attribute Clauses bool Pre(const parser::OmpClause::Inclusive &x) { ResolveOmpObjectList(x.v, Symbol::Flag::OmpInclusiveScan); return false; } bool Pre(const parser::OmpClause::Exclusive &x) { ResolveOmpObjectList(x.v, Symbol::Flag::OmpExclusiveScan); return false; } void Post(const parser::OmpClause::Defaultmap &); void Post(const parser::OmpDefaultClause &); bool Pre(const parser::OmpClause::Shared &x) { ResolveOmpObjectList(x.v, Symbol::Flag::OmpShared); return false; } bool Pre(const parser::OmpClause::Private &x) { ResolveOmpObjectList(x.v, Symbol::Flag::OmpPrivate); return false; } bool Pre(const parser::OmpAllocateClause &x) { ResolveOmpObjectList( *parser::omp::GetOmpObjectList(x), Symbol::Flag::OmpAllocate); return false; } bool Pre(const parser::OmpClause::Firstprivate &x) { ResolveOmpObjectList(x.v, Symbol::Flag::OmpFirstPrivate); return false; } bool Pre(const parser::OmpClause::Lastprivate &x) { ResolveOmpObjectList( *parser::omp::GetOmpObjectList(x), Symbol::Flag::OmpLastPrivate); return false; } bool Pre(const parser::OmpClause::Copyin &x) { ResolveOmpObjectList(x.v, Symbol::Flag::OmpCopyIn); return false; } bool Pre(const parser::OmpClause::Copyprivate &x) { ResolveOmpObjectList(x.v, Symbol::Flag::OmpCopyPrivate); return false; } bool Pre(const parser::OmpLinearClause &x) { ResolveOmpObjectList( *parser::omp::GetOmpObjectList(x), Symbol::Flag::OmpLinear); return false; } bool Pre(const parser::OmpClause::Uniform &x) { ResolveOmpNameList(x.v, Symbol::Flag::OmpUniform); return false; } bool Pre(const parser::OmpInReductionClause &x) { ResolveOmpObjectList( *parser::omp::GetOmpObjectList(x), Symbol::Flag::OmpInReduction); return false; } bool Pre(const parser::OmpClause::Reduction &x) { const auto &objList{*parser::omp::GetOmpObjectList(x)}; ResolveOmpObjectList(objList, Symbol::Flag::OmpReduction); if (auto &modifiers{OmpGetModifiers(x.v)}) { auto createDummyProcSymbol = [&](const parser::Name *name) { // If name resolution failed, create a dummy symbol const auto namePair{currScope().try_emplace( name->source, Attrs{}, ProcEntityDetails{})}; auto &newSymbol{*namePair.first->second}; if (context_.intrinsics().IsIntrinsic(name->ToString())) { newSymbol.attrs().set(Attr::INTRINSIC); } name->symbol = &newSymbol; }; for (auto &mod : *modifiers) { if (!std::holds_alternative(mod.u)) { continue; } auto &opr{std::get(mod.u)}; if (auto *procD{parser::Unwrap(opr.u)}) { if (auto *name{parser::Unwrap(procD->u)}) { if (!name->symbol) { if (!ResolveName(name)) { createDummyProcSymbol(name); } } } if (auto *procRef{ parser::Unwrap(procD->u)}) { if (!procRef->v.thing.Component().symbol) { if (!ResolveName(&procRef->v.thing.Component())) { createDummyProcSymbol(&procRef->v.thing.Component()); } } } } } using ReductionModifier = parser::OmpReductionModifier; if (auto *maybeModifier{ OmpGetUniqueModifier(modifiers)}) { if (maybeModifier->v == ReductionModifier::Value::Inscan) { ResolveOmpObjectList(objList, Symbol::Flag::OmpInScanReduction); } } } return false; } bool Pre(const parser::OmpAlignedClause &x) { ResolveOmpObjectList( *parser::omp::GetOmpObjectList(x), Symbol::Flag::OmpAligned); return false; } bool Pre(const parser::OmpClause::Nontemporal &x) { ResolveOmpObjectList( *parser::omp::GetOmpObjectList(x), Symbol::Flag::OmpNontemporal); return false; } void Post(const parser::OmpIteration &x) { if (const auto &name{std::get(x.t)}; !name.symbol) { auto *symbol{currScope().FindSymbol(name.source)}; if (!symbol) { // OmpIteration must use an existing object. If there isn't one, // create a fake one and flag an error later. symbol = &currScope().MakeSymbol( name.source, Attrs{}, EntityDetails(/*isDummy=*/true)); } Resolve(name, symbol); } } bool Pre(const parser::OmpClause::UseDevicePtr &x) { ResolveOmpObjectList(x.v, Symbol::Flag::OmpUseDevicePtr); return false; } bool Pre(const parser::OmpClause::UseDeviceAddr &x) { ResolveOmpObjectList(x.v, Symbol::Flag::OmpUseDeviceAddr); return false; } bool Pre(const parser::OmpClause::IsDevicePtr &x) { ResolveOmpObjectList(x.v, Symbol::Flag::OmpIsDevicePtr); return false; } bool Pre(const parser::OmpClause::HasDeviceAddr &x) { ResolveOmpObjectList(x.v, Symbol::Flag::OmpHasDeviceAddr); return false; } void Post(const parser::Name &); void ResolveOmpObjectsForMapClause( Symbol::Flag mapFlag, const parser::OmpObjectList &objList) { for (const auto &ompObj : objList.v) { common::visit( common::visitors{ [&](const parser::Designator &designator) { if (const auto *name{ parser::GetDesignatorNameIfDataRef(designator)}) { if (name->symbol) { name->symbol->set(mapFlag); } } }, [&](const auto &name) {}, }, ompObj.u); ResolveOmpObject(ompObj, mapFlag); } } void Post(const parser::OmpFromClause &x) { const auto &ompObjList{*parser::omp::GetOmpObjectList(x)}; ResolveOmpObjectsForMapClause(Symbol::Flag::OmpMapFrom, ompObjList); } void Post(const parser::OmpToClause &x) { // This is different from the parser::OmpFromClause case, as this to applies // to both declare target to, and update to, so slightly different handling // is required in that we must exit early to avoid applying extra symbol // flags. This is reasonable for now, but if we wish to apply this // resolution to declare target to (and likely enter in another function // like this) we will have to extend the handling to act differently for // declare target rather than simply return. if (GetContext().directive == llvm::omp::Directive::OMPD_declare_target) return; const auto &ompObjList{*parser::omp::GetOmpObjectList(x)}; ResolveOmpObjectsForMapClause(Symbol::Flag::OmpMapTo, ompObjList); } void Post(const parser::OmpMapClause &x) { unsigned version{context_.langOptions().OpenMPVersion}; std::optional ompFlag; auto &mods{OmpGetModifiers(x)}; if (auto *mapType{OmpGetUniqueModifier(mods)}) { switch (mapType->v) { case parser::OmpMapType::Value::To: ompFlag = Symbol::Flag::OmpMapTo; break; case parser::OmpMapType::Value::From: ompFlag = Symbol::Flag::OmpMapFrom; break; case parser::OmpMapType::Value::Tofrom: ompFlag = Symbol::Flag::OmpMapToFrom; break; case parser::OmpMapType::Value::Alloc: case parser::OmpMapType::Value::Release: case parser::OmpMapType::Value::Storage: ompFlag = Symbol::Flag::OmpMapStorage; break; case parser::OmpMapType::Value::Delete: ompFlag = Symbol::Flag::OmpMapDelete; break; } } if (!ompFlag) { if (version >= 60) { // [6.0:275:12-15] // When a map-type is not specified for a clause on which it may be // specified, the map-type defaults to storage if the delete-modifier // is present on the clause or if the list item for which the map-type // is not specified is an assumed-size array. if (OmpGetUniqueModifier(mods)) { ompFlag = Symbol::Flag::OmpMapStorage; } // Otherwise, if delete-modifier is absent, leave ompFlag unset. } else { // [5.2:151:10] // If a map-type is not specified, the map-type defaults to tofrom. ompFlag = Symbol::Flag::OmpMapToFrom; } } const auto &ompObjList{*parser::omp::GetOmpObjectList(x)}; for (const auto &ompObj : ompObjList.v) { common::visit( common::visitors{ [&](const parser::Designator &designator) { if (const auto *name{ parser::GetDesignatorNameIfDataRef(designator)}) { if (name->symbol) { name->symbol->set( ompFlag.value_or(Symbol::Flag::OmpMapStorage)); AddToContextObjectWithDSA(*name->symbol, ompFlag.value_or(Symbol::Flag::OmpMapStorage)); } } }, [&](const auto &name) {}, }, ompObj.u); ResolveOmpObject(ompObj, ompFlag.value_or(Symbol::Flag::OmpMapStorage)); } } private: Symbol::Flags dataSharingAttributeFlags{Symbol::Flag::OmpShared, Symbol::Flag::OmpPrivate, Symbol::Flag::OmpFirstPrivate, Symbol::Flag::OmpLastPrivate, Symbol::Flag::OmpReduction, Symbol::Flag::OmpLinear}; Symbol::Flags dataMappingAttributeFlags{Symbol::Flag::OmpMapTo, Symbol::Flag::OmpMapFrom, Symbol::Flag::OmpMapToFrom, Symbol::Flag::OmpMapStorage, Symbol::Flag::OmpMapDelete, Symbol::Flag::OmpIsDevicePtr, Symbol::Flag::OmpHasDeviceAddr}; Symbol::Flags privateDataSharingAttributeFlags{Symbol::Flag::OmpPrivate, Symbol::Flag::OmpFirstPrivate, Symbol::Flag::OmpLastPrivate}; Symbol::Flags ompFlagsRequireNewSymbol{Symbol::Flag::OmpPrivate, Symbol::Flag::OmpLinear, Symbol::Flag::OmpFirstPrivate, Symbol::Flag::OmpLastPrivate, Symbol::Flag::OmpShared, Symbol::Flag::OmpReduction, Symbol::Flag::OmpCriticalLock, Symbol::Flag::OmpCopyIn, Symbol::Flag::OmpUseDevicePtr, Symbol::Flag::OmpUseDeviceAddr, Symbol::Flag::OmpIsDevicePtr, Symbol::Flag::OmpHasDeviceAddr, Symbol::Flag::OmpUniform}; Symbol::Flags dataCopyingAttributeFlags{ Symbol::Flag::OmpCopyIn, Symbol::Flag::OmpCopyPrivate}; UnorderedSymbolSet stmtFunctionExprSymbols_; enum class PartKind : int { // There are also other "parts", such as internal-subprogram-part, etc, // but we're keeping track of these two for now. SpecificationPart, ExecutionPart, }; std::vector partStack_; // Predetermined DSA rules void PrivatizeAssociatedLoopIndex(const parser::OpenMPLoopConstruct &); void ResolveSeqLoopIndexInParallelOrTaskConstruct(const parser::Name &); bool IsNestedInDirective(llvm::omp::Directive directive); void ResolveOmpObjectList(const parser::OmpObjectList &, Symbol::Flag); void ResolveOmpDesignator( const parser::Designator &designator, Symbol::Flag ompFlag); void ResolveOmpCommonBlock(const parser::Name &name, Symbol::Flag ompFlag); void ResolveOmpObject(const parser::OmpObject &, Symbol::Flag); Symbol *ResolveOmp(const parser::Name &, Symbol::Flag, Scope &); Symbol *ResolveOmp(Symbol &, Symbol::Flag, Scope &); Symbol *ResolveOmpCommonBlockName(const parser::Name *); void ResolveOmpNameList(const std::list &, Symbol::Flag); void ResolveOmpName(const parser::Name &, Symbol::Flag); void PropagateOmpFlagToEquivalenceSet(const Symbol &, Symbol::Flag); Symbol *ResolveName(const parser::Name *); Symbol *DeclareOrMarkOtherAccessEntity(const parser::Name &, Symbol::Flag); Symbol *DeclareOrMarkOtherAccessEntity(Symbol &, Symbol::Flag); void CheckMultipleAppearances( const parser::Name &, const Symbol &, Symbol::Flag); void CheckDataCopyingClause( const parser::Name &, const Symbol &, Symbol::Flag); void CheckObjectIsPrivatizable( const parser::Name &, const Symbol &, Symbol::Flag); void AddOmpRequiresToScope(Scope &, const WithOmpDeclarative::OmpClauseSet &, const std::optional &); void CreateImplicitSymbols(const parser::Name &, const Symbol *symbol); void AddToContextObjectWithExplicitDSA(Symbol &symbol, Symbol::Flag flag) { AddToContextObjectWithDSA(symbol, flag); if (dataSharingAttributeFlags.test(flag)) { symbol.set(Symbol::Flag::OmpExplicit); } } // Clear any previous data-sharing attribute flags and set the new ones. // Needed when setting PreDetermined DSAs, that take precedence over // Implicit ones. void SetSymbolDSA(Symbol &symbol, Symbol::Flags flags) { symbol.flags() &= ~(dataSharingAttributeFlags | Symbol::Flags{Symbol::Flag::OmpExplicit, Symbol::Flag::OmpImplicit, Symbol::Flag::OmpPreDetermined}); symbol.flags() |= flags; } }; void ResolveAccParts(SemanticsContext &context, const parser::ProgramUnit &node, Scope *topScope) { if (context.IsEnabled(common::LanguageFeature::OpenACC)) { AccAttributeVisitor{context, topScope}.Walk(node); } } void ResolveOmpParts( SemanticsContext &context, const parser::ProgramUnit &node) { if (context.IsEnabled(common::LanguageFeature::OpenMP)) { OmpAttributeVisitor{context}.Walk(node); if (!context.AnyFatalError()) { // The data-sharing attribute of the loop iteration variable for a // sequential loop (2.15.1.1) can only be determined when visiting // the corresponding DoConstruct, a second walk is to adjust the // symbols for all the data-refs of that loop iteration variable // prior to the DoConstruct. OmpAttributeVisitor{context}.Walk(node); } } } template bool DirectiveAttributeVisitor::HasDataSharingAttributeObject( const Symbol &object) { auto it{dataSharingAttributeObjects_.find(object)}; return it != dataSharingAttributeObjects_.end(); } template std::tuple DirectiveAttributeVisitor::GetLoopBounds(const parser::DoConstruct &x) { using Bounds = parser::LoopControl::Bounds; if (x.GetLoopControl()) { if (const Bounds *b{std::get_if(&x.GetLoopControl()->u)}) { const auto &step = b->Step(); return {&b->Name().thing, &b->Lower(), &b->Upper(), step.has_value() ? &step.value() : nullptr}; } } else { context_ .Say(std::get>(x.t).source, "Loop control is not present in the DO LOOP"_err_en_US) .Attach(GetContext().directiveSource, "associated with the enclosing LOOP construct"_en_US); } return {nullptr, nullptr, nullptr, nullptr}; } template const parser::Name *DirectiveAttributeVisitor::GetLoopIndex( const parser::DoConstruct &x) { return std::get(GetLoopBounds(x)); } template const parser::DoConstruct *DirectiveAttributeVisitor::GetDoConstructIf( const parser::ExecutionPartConstruct &x) { return parser::Unwrap(x); } template Symbol *DirectiveAttributeVisitor::DeclareNewAccessEntity( const Symbol &object, Symbol::Flag flag, Scope &scope) { assert(object.owner() != currScope()); auto &symbol{MakeAssocSymbol(object.name(), object, scope)}; symbol.set(flag); if (flag == Symbol::Flag::OmpCopyIn) { // The symbol in copyin clause must be threadprivate entity. symbol.set(Symbol::Flag::OmpThreadprivate); } return &symbol; } template Symbol *DirectiveAttributeVisitor::DeclareAccessEntity( const parser::Name &name, Symbol::Flag flag, Scope &scope) { if (!name.symbol) { return nullptr; // not resolved by Name Resolution step, do nothing } name.symbol = DeclareAccessEntity(*name.symbol, flag, scope); return name.symbol; } template Symbol *DirectiveAttributeVisitor::DeclareAccessEntity( Symbol &object, Symbol::Flag flag, Scope &scope) { if (object.owner() != currScope()) { return DeclareNewAccessEntity(object, flag, scope); } else { object.set(flag); return &object; } } bool AccAttributeVisitor::Pre(const parser::OpenACCBlockConstruct &x) { const auto &beginBlockDir{std::get(x.t)}; const auto &blockDir{std::get(beginBlockDir.t)}; switch (blockDir.v) { case llvm::acc::Directive::ACCD_data: case llvm::acc::Directive::ACCD_host_data: case llvm::acc::Directive::ACCD_kernels: case llvm::acc::Directive::ACCD_parallel: case llvm::acc::Directive::ACCD_serial: PushContext(blockDir.source, blockDir.v); break; default: break; } ClearDataSharingAttributeObjects(); ClearUseDeviceObjects(); return true; } bool AccAttributeVisitor::Pre(const parser::OpenACCDeclarativeConstruct &x) { if (const auto *declConstruct{ std::get_if(&x.u)}) { const auto &declDir{ std::get(declConstruct->t)}; PushContext(declDir.source, llvm::acc::Directive::ACCD_declare); } ClearDataSharingAttributeObjects(); return true; } static const parser::AccObjectList &GetAccObjectList( const parser::AccClause &clause) { if (const auto *copyClause = std::get_if(&clause.u)) { return copyClause->v; } else if (const auto *createClause = std::get_if(&clause.u)) { const Fortran::parser::AccObjectListWithModifier &listWithModifier = createClause->v; const Fortran::parser::AccObjectList &accObjectList = std::get(listWithModifier.t); return accObjectList; } else if (const auto *copyinClause = std::get_if(&clause.u)) { const Fortran::parser::AccObjectListWithModifier &listWithModifier = copyinClause->v; const Fortran::parser::AccObjectList &accObjectList = std::get(listWithModifier.t); return accObjectList; } else if (const auto *copyoutClause = std::get_if(&clause.u)) { const Fortran::parser::AccObjectListWithModifier &listWithModifier = copyoutClause->v; const Fortran::parser::AccObjectList &accObjectList = std::get(listWithModifier.t); return accObjectList; } else if (const auto *presentClause = std::get_if(&clause.u)) { return presentClause->v; } else if (const auto *deviceptrClause = std::get_if( &clause.u)) { return deviceptrClause->v; } else if (const auto *deviceResidentClause = std::get_if( &clause.u)) { return deviceResidentClause->v; } else if (const auto *linkClause = std::get_if(&clause.u)) { return linkClause->v; } else { llvm_unreachable("Clause without object list!"); } } void AccAttributeVisitor::Post( const parser::OpenACCStandaloneDeclarativeConstruct &x) { const auto &clauseList = std::get(x.t); for (const auto &clause : clauseList.v) { // Restriction - line 2414 // We assume the restriction is present because clauses that require // moving data would require the size of the data to be present, but // the deviceptr and present clauses do not require moving data and // thus we permit them. if (!std::holds_alternative(clause.u) && !std::holds_alternative(clause.u)) { DoNotAllowAssumedSizedArray(GetAccObjectList(clause)); } } } bool AccAttributeVisitor::Pre(const parser::OpenACCLoopConstruct &x) { const auto &beginDir{std::get(x.t)}; const auto &loopDir{std::get(beginDir.t)}; const auto &clauseList{std::get(beginDir.t)}; if (loopDir.v == llvm::acc::Directive::ACCD_loop) { PushContext(loopDir.source, loopDir.v); } ClearDataSharingAttributeObjects(); SetContextAssociatedLoopLevel(GetAssociatedLoopLevelFromClauses(clauseList)); const auto &outer{std::get>(x.t)}; CheckAssociatedLoop(*outer, HasForceCollapseModifier(clauseList)); return true; } bool AccAttributeVisitor::Pre(const parser::OpenACCStandaloneConstruct &x) { const auto &standaloneDir{std::get(x.t)}; switch (standaloneDir.v) { case llvm::acc::Directive::ACCD_enter_data: case llvm::acc::Directive::ACCD_exit_data: case llvm::acc::Directive::ACCD_init: case llvm::acc::Directive::ACCD_set: case llvm::acc::Directive::ACCD_shutdown: case llvm::acc::Directive::ACCD_update: PushContext(standaloneDir.source, standaloneDir.v); break; default: break; } ClearDataSharingAttributeObjects(); return true; } Symbol *AccAttributeVisitor::ResolveName(const parser::Name &name) { return name.symbol; } Symbol *AccAttributeVisitor::ResolveFctName(const parser::Name &name) { Symbol *prev{currScope().FindSymbol(name.source)}; if (prev && prev->IsFuncResult()) { prev = currScope().parent().FindSymbol(name.source); } if (!prev) { prev = &*context_.globalScope() .try_emplace(name.source, ProcEntityDetails{}) .first->second; } CHECK(!name.symbol || name.symbol == prev); name.symbol = prev; return prev; } template common::IfNoLvalue FoldExpr( evaluate::FoldingContext &foldingContext, T &&expr) { return evaluate::Fold(foldingContext, std::move(expr)); } template MaybeExpr EvaluateExpr( Fortran::semantics::SemanticsContext &semanticsContext, const T &expr) { return FoldExpr( semanticsContext.foldingContext(), AnalyzeExpr(semanticsContext, expr)); } void AccAttributeVisitor::AddRoutineInfoToSymbol( Symbol &symbol, const parser::OpenACCRoutineConstruct &x) { if (!symbol.has() && !symbol.has()) return; Fortran::semantics::OpenACCRoutineInfo info; std::vector currentDevices; currentDevices.push_back(&info); const auto &clauses{std::get(x.t)}; for (const Fortran::parser::AccClause &clause : clauses.v) { if (const auto *dTypeClause{ std::get_if(&clause.u)}) { currentDevices.clear(); for (const auto &deviceTypeExpr : dTypeClause->v.v) { currentDevices.push_back(&info.add_deviceTypeInfo(deviceTypeExpr.v)); } } else if (std::get_if(&clause.u)) { info.set_isNohost(); } else if (std::get_if(&clause.u)) { for (auto &device : currentDevices) { device->set_isSeq(); } } else if (std::get_if(&clause.u)) { for (auto &device : currentDevices) { device->set_isVector(); } } else if (std::get_if(&clause.u)) { for (auto &device : currentDevices) { device->set_isWorker(); } } else if (const auto *gangClause{ std::get_if(&clause.u)}) { for (auto &device : currentDevices) { device->set_isGang(); } if (gangClause->v) { const Fortran::parser::AccGangArgList &x = *gangClause->v; int numArgs{0}; for (const Fortran::parser::AccGangArg &gangArg : x.v) { CHECK(numArgs <= 1 && "expecting 0 or 1 gang dim args"); if (const auto *dim{ std::get_if(&gangArg.u)}) { if (const auto v{EvaluateInt64(context_, dim->v)}) { for (auto &device : currentDevices) { device->set_gangDim(*v); } } } numArgs++; } } } else if (const auto *bindClause{ std::get_if(&clause.u)}) { if (const auto *name{ std::get_if(&bindClause->v.u)}) { if (Symbol * sym{ResolveFctName(*name)}) { Symbol &ultimate{sym->GetUltimate()}; for (auto &device : currentDevices) { device->set_bindName(SymbolRef{ultimate}); } } else { context_.Say((*name).source, "No function or subroutine declared for '%s'"_err_en_US, (*name).source); } } else if (const auto charExpr{ std::get_if( &bindClause->v.u)}) { auto *charConst{ Fortran::parser::Unwrap( *charExpr)}; std::string str{std::get(charConst->t)}; for (auto &device : currentDevices) { device->set_bindName(std::string(str)); } } } } if (symbol.has()) symbol.get().add_openACCRoutineInfo(info); else symbol.get().add_openACCRoutineInfo(info); } bool AccAttributeVisitor::Pre(const parser::OpenACCRoutineConstruct &x) { const auto &verbatim{std::get(x.t)}; if (topScope_) { PushContext( verbatim.source, llvm::acc::Directive::ACCD_routine, *topScope_); } else { PushContext(verbatim.source, llvm::acc::Directive::ACCD_routine); } const auto &names{std::get>(x.t)}; if (!names.empty()) { if (names.size() > 1) { if (context_.IsEnabled( common::LanguageFeature::OpenACCMultipleNamesInRoutine)) { context_.Warn(common::LanguageFeature::OpenACCMultipleNamesInRoutine, verbatim.source, "OpenACC ROUTINE directive permits only a single name; multiple names accepted as an extension"_warn_en_US); if (llvm::any_of(std::get(x.t).v, [](const parser::AccClause &c) { return std::holds_alternative(c.u); })) { context_.Say(verbatim.source, "A BIND clause may only be specified when the ROUTINE directive refers to a single subroutine"_err_en_US); } } else { context_.Say(verbatim.source, "OpenACC ROUTINE directive does not permit multiple names"_err_en_US); } } for (const auto &name : names) { if (Symbol * sym{ResolveFctName(name)}) { Symbol &ultimate{sym->GetUltimate()}; AddRoutineInfoToSymbol(ultimate, x); } else { context_.Say(name.source, "No function or subroutine declared for '%s'"_err_en_US, name.source); } } } else { if (currScope().symbol()) { AddRoutineInfoToSymbol(*currScope().symbol(), x); } } return true; } bool AccAttributeVisitor::Pre(const parser::AccBindClause &x) { if (const auto *name{std::get_if(&x.u)}) { if (!ResolveFctName(*name)) { context_.Say(name->source, "No function or subroutine declared for '%s'"_err_en_US, name->source); } } return true; } bool AccAttributeVisitor::Pre(const parser::OpenACCCombinedConstruct &x) { const auto &beginBlockDir{std::get(x.t)}; const auto &combinedDir{ std::get(beginBlockDir.t)}; switch (combinedDir.v) { case llvm::acc::Directive::ACCD_kernels_loop: case llvm::acc::Directive::ACCD_parallel_loop: case llvm::acc::Directive::ACCD_serial_loop: PushContext(x.source, combinedDir.v); break; default: break; } const auto &clauseList{std::get(beginBlockDir.t)}; SetContextAssociatedLoopLevel(GetAssociatedLoopLevelFromClauses(clauseList)); const auto &outer{std::get>(x.t)}; CheckAssociatedLoop(*outer, HasForceCollapseModifier(clauseList)); ClearDataSharingAttributeObjects(); return true; } static bool IsLastNameArray(const parser::Designator &designator) { const auto &name{GetLastName(designator)}; const evaluate::DataRef dataRef{*(name.symbol)}; return common::visit( // common::visitors{ [](const evaluate::SymbolRef &ref) { return ref->Rank() > 0 || ref->GetType()->category() == DeclTypeSpec::Numeric; }, [](const evaluate::ArrayRef &aref) { return aref.base().IsSymbol() || aref.base().GetComponent().base().Rank() == 0; }, [](const auto &) { return false; }, }, dataRef.u); } void AccAttributeVisitor::AllowOnlyArrayAndSubArray( const parser::AccObjectList &objectList) { for (const auto &accObject : objectList.v) { common::visit( common::visitors{ [&](const parser::Designator &designator) { if (!IsLastNameArray(designator)) { context_.Say(designator.source, "Only array element or subarray are allowed in %s directive"_err_en_US, parser::ToUpperCaseLetters( llvm::acc::getOpenACCDirectiveName( GetContext().directive) .str())); } }, [&](const auto &name) { context_.Say(name.source, "Only array element or subarray are allowed in %s directive"_err_en_US, parser::ToUpperCaseLetters( llvm::acc::getOpenACCDirectiveName(GetContext().directive) .str())); }, }, accObject.u); } } void AccAttributeVisitor::DoNotAllowAssumedSizedArray( const parser::AccObjectList &objectList) { for (const auto &accObject : objectList.v) { common::visit( common::visitors{ [&](const parser::Designator &designator) { const auto &name{GetLastName(designator)}; if (name.symbol && semantics::IsAssumedSizeArray(*name.symbol)) { context_.Say(designator.source, "Assumed-size dummy arrays may not appear on the %s " "directive"_err_en_US, parser::ToUpperCaseLetters( llvm::acc::getOpenACCDirectiveName( GetContext().directive) .str())); } }, [&](const auto &name) { }, }, accObject.u); } } void AccAttributeVisitor::AllowOnlyVariable(const parser::AccObject &object) { common::visit( common::visitors{ [&](const parser::Designator &designator) { const auto &name{GetLastName(designator)}; if (name.symbol && !semantics::IsVariableName(*name.symbol) && !semantics::IsNamedConstant(*name.symbol)) { context_.Say(designator.source, "Only variables are allowed in data clauses on the %s " "directive"_err_en_US, parser::ToUpperCaseLetters( llvm::acc::getOpenACCDirectiveName(GetContext().directive) .str())); } }, [&](const auto &name) {}, }, object.u); } bool AccAttributeVisitor::Pre(const parser::OpenACCWaitConstruct &x) { const auto &verbatim{std::get(x.t)}; PushContext(verbatim.source, llvm::acc::Directive::ACCD_wait); ClearDataSharingAttributeObjects(); return true; } bool AccAttributeVisitor::Pre(const parser::OpenACCAtomicConstruct &x) { const auto &verbatimSource = common::visit( common::visitors{ [&](const parser::AccAtomicUpdate &atomic) { const auto &optVerbatim = std::get>(atomic.t); return optVerbatim ? optVerbatim->source : x.source; }, [&](const auto &atomic) { return std::get(atomic.t).source; }, }, x.u); PushContext(verbatimSource, llvm::acc::Directive::ACCD_atomic); ClearDataSharingAttributeObjects(); return true; } bool AccAttributeVisitor::Pre(const parser::OpenACCCacheConstruct &x) { const auto &verbatim{std::get(x.t)}; PushContext(verbatim.source, llvm::acc::Directive::ACCD_cache); ClearDataSharingAttributeObjects(); const auto &objectListWithModifier = std::get(x.t); const auto &objectList = std::get(objectListWithModifier.t); // 2.10 Cache directive restriction: A var in a cache directive must be a // single array element or a simple subarray. AllowOnlyArrayAndSubArray(objectList); return true; } bool AccAttributeVisitor::HasForceCollapseModifier( const parser::AccClauseList &x) { for (const auto &clause : x.v) { if (const auto *collapseClause{ std::get_if(&clause.u)}) { const parser::AccCollapseArg &arg = collapseClause->v; return std::get(arg.t); } } return false; } std::int64_t AccAttributeVisitor::GetAssociatedLoopLevelFromClauses( const parser::AccClauseList &x) { std::int64_t collapseLevel{0}; for (const auto &clause : x.v) { if (const auto *collapseClause{ std::get_if(&clause.u)}) { const parser::AccCollapseArg &arg = collapseClause->v; const auto &collapseValue{std::get(arg.t)}; if (const auto v{EvaluateInt64(context_, collapseValue)}) { collapseLevel = *v; } } } if (collapseLevel) { return collapseLevel; } return 1; // default is outermost loop } void AccAttributeVisitor::CheckAssociatedLoop( const parser::DoConstruct &outerDoConstruct, bool forceCollapsed) { std::int64_t level{GetContext().associatedLoopLevel}; if (level <= 0) { // collapse value was negative or 0 return; } const auto getNextDoConstruct = [this, forceCollapsed](const parser::Block &block, std::int64_t &level) -> const parser::DoConstruct * { for (const auto &entry : block) { if (const auto *doConstruct = GetDoConstructIf(entry)) { return doConstruct; } else if (parser::Unwrap(entry)) { // It is allowed to have a compiler directive associated with the loop. continue; } else if (const auto &accLoop{ parser::Unwrap(entry)}) { if (level == 0) break; const auto &beginDir{ std::get(accLoop->t)}; context_.Say(beginDir.source, "LOOP directive not expected in COLLAPSE loop nest"_err_en_US); level = 0; } else { if (!forceCollapsed) { break; } } } return nullptr; }; auto checkExprHasSymbols = [&](llvm::SmallVector &ivs, semantics::UnorderedSymbolSet &symbols) { for (auto iv : ivs) { if (symbols.count(*iv) != 0) { context_.Say(GetContext().directiveSource, "Trip count must be computable and invariant"_err_en_US); } } }; Symbol::Flag flag = Symbol::Flag::AccPrivate; llvm::SmallVector ivs; // Iterate the index variables of one DoConstruct, calling fn(name, lower, // upper) for each: once for a regular do loop, once per control variable for // a do concurrent loop. Null pointers signal a loop without valid bounds // (e.g. do while); the level must still be consumed. auto forEachIndex = [this](const parser::DoConstruct &loop, auto &&fn) { if (loop.IsDoConcurrent()) { const auto &loopControl{*loop.GetLoopControl()}; const auto &concurrent{ std::get(loopControl.u)}; const auto &header{std::get(concurrent.t)}; for (const auto &control : std::get>(header.t)) { fn(&std::get(control.t), &parser::UnwrapRef(std::get<1>(control.t)), &parser::UnwrapRef(std::get<2>(control.t))); } } else { auto bounds{GetLoopBounds(loop)}; const parser::ScalarExpr *lower{std::get<1>(bounds)}; const parser::ScalarExpr *upper{std::get<2>(bounds)}; fn(std::get<0>(bounds), lower ? &parser::UnwrapRef(*lower) : nullptr, upper ? &parser::UnwrapRef(*upper) : nullptr); } }; for (const parser::DoConstruct *loop{&outerDoConstruct}; loop && level > 0;) { forEachIndex(*loop, [&](const parser::Name *ivName, const parser::Expr *lower, const parser::Expr *upper) { if (level <= 0) return; if (ivName && lower && upper) { if (auto *symbol{ResolveAcc(*ivName, flag, currScope())}) { if (auto lowerExpr{semantics::AnalyzeExpr(context_, *lower)}) { semantics::UnorderedSymbolSet lowerSyms = evaluate::CollectSymbols(*lowerExpr); checkExprHasSymbols(ivs, lowerSyms); } if (auto upperExpr{semantics::AnalyzeExpr(context_, *upper)}) { semantics::UnorderedSymbolSet upperSyms = evaluate::CollectSymbols(*upperExpr); checkExprHasSymbols(ivs, upperSyms); } ivs.push_back(symbol); } } --level; }); const auto &block{std::get(loop->t)}; loop = getNextDoConstruct(block, level); } if (level != 0) { context_.Say(GetContext().directiveSource, "Not enough %s for COLLAPSE(%jd) clause, found %jd, expected %jd more"_err_en_US, forceCollapsed ? "nested loops" : "perfectly nested loops", GetContext().associatedLoopLevel, GetContext().associatedLoopLevel - level, level); } } void AccAttributeVisitor::EnsureAllocatableOrPointer( const llvm::acc::Clause clause, const parser::AccObjectList &objectList) { for (const auto &accObject : objectList.v) { common::visit( common::visitors{ [&](const parser::Designator &designator) { const auto &lastName{GetLastName(designator)}; if (!IsAllocatableOrObjectPointer(lastName.symbol)) { context_.Say(designator.source, "Argument `%s` on the %s clause must be a variable or " "array with the POINTER or ALLOCATABLE attribute"_err_en_US, lastName.symbol->name(), parser::ToUpperCaseLetters( llvm::acc::getOpenACCClauseName(clause).str())); } }, [&](const auto &name) { context_.Say(name.source, "Argument on the %s clause must be a variable or " "array with the POINTER or ALLOCATABLE attribute"_err_en_US, parser::ToUpperCaseLetters( llvm::acc::getOpenACCClauseName(clause).str())); }, }, accObject.u); } } bool AccAttributeVisitor::Pre(const parser::AccClause::Attach &x) { // Restriction - line 1708-1709 EnsureAllocatableOrPointer(llvm::acc::Clause::ACCC_attach, x.v); return true; } bool AccAttributeVisitor::Pre(const parser::AccClause::Detach &x) { // Restriction - line 1715-1717 EnsureAllocatableOrPointer(llvm::acc::Clause::ACCC_detach, x.v); return true; } void AccAttributeVisitor::Post(const parser::AccDefaultClause &x) { if (!dirContext_.empty()) { switch (x.v) { case llvm::acc::DefaultValue::ACC_Default_present: SetContextDefaultDSA(Symbol::Flag::AccPresent); break; case llvm::acc::DefaultValue::ACC_Default_none: SetContextDefaultDSA(Symbol::Flag::AccNone); break; } } } // Returns true iff symbol qualifies for the pre-OpenACC-3.2 DEFAULT(NONE) // scalar extension: an intrinsic numeric or logical non-array, non-pointer, // non-allocatable variable. Characters, derived types, allocatables, and // pointers are excluded because their data-sharing semantics under // DEFAULT(NONE) differ materially from plain scalar copy. static bool IsAccScalar(const Symbol &symbol) { if (IsAllocatable(symbol) || IsPointer(symbol)) return false; const auto *type{symbol.GetType()}; if (!type) return false; auto cat{type->category()}; if (cat != Fortran::semantics::DeclTypeSpec::Category::Numeric && cat != Fortran::semantics::DeclTypeSpec::Category::Logical) return false; const auto *det{symbol.detailsIf()}; return det && !det->IsArray(); } void AccAttributeVisitor::Post(const parser::Name &name) { if (name.symbol && WithinConstruct()) { const Symbol &symbol{name.symbol->GetUltimate()}; if (!symbol.owner().IsDerivedType() && !symbol.has() && !symbol.has() && !IsObjectWithVisibleDSA(symbol) && !symbol.has() && !symbol.has()) { if (Symbol * found{currScope().FindSymbol(name.source)}) { if (&symbol != found) { // adjust the symbol within the region // TODO: why didn't name resolution set the right name originally? name.symbol = found; } else if (GetContext().defaultDSA == Symbol::Flag::AccNone) { // 2.5.14. Pre-OpenACC-3.2 behavior: implicit scalars warn instead of // error unless strict mode is enabled. if (IsAccScalar(symbol) && !context_.IsEnabled( common::LanguageFeature::OpenAccDefaultNoneScalarsStrict)) { context_.Warn( common::LanguageFeature::OpenAccDefaultNoneScalarsStrict, name.source, "Implicit attribute inferred for DEFAULT(NONE) scalar '%s'"_warn_en_US, symbol.name()); } else { context_.Say(name.source, "The DEFAULT(NONE) clause requires that '%s' must be listed in a data-mapping clause"_err_en_US, symbol.name()); } } } else { // TODO: assertion here? or clear name.symbol? } } } } Symbol *AccAttributeVisitor::ResolveAccCommonBlockName( const parser::Name *name) { if (name) { if (Symbol * cb{GetContext().scope.FindCommonBlockInVisibleScopes(name->source)}) { name->symbol = cb; return cb; } } return nullptr; } void AccAttributeVisitor::AddUseDeviceObject( const Symbol &object, const parser::Name &name) { if (!useDeviceObjects_.insert(object).second) { context_.Say(name.source, "'%s' appears in more than one USE_DEVICE clause on the same HOST_DATA directive"_err_en_US, name.ToString()); } } void AccAttributeVisitor::ResolveAccObjectList( const parser::AccObjectList &accObjectList, Symbol::Flag accFlag) { for (const auto &accObject : accObjectList.v) { AllowOnlyVariable(accObject); ResolveAccObject(accObject, accFlag); } } void AccAttributeVisitor::ResolveAccObject( const parser::AccObject &accObject, Symbol::Flag accFlag) { common::visit( common::visitors{ [&](const parser::Designator &designator) { if (const auto *name{ parser::GetDesignatorNameIfDataRef(designator)}) { if (auto *symbol{ResolveAcc(*name, accFlag, currScope())}) { AddToContextObjectWithDSA(*symbol, accFlag); if (dataSharingAttributeFlags.test(accFlag)) { CheckMultipleAppearances(*name, *symbol, accFlag, &accObject); } } } else { // Array sections to be changed to substrings as needed if (AnalyzeExpr(context_, designator)) { if (std::holds_alternative(designator.u)) { context_.Say(designator.source, "Substrings are not allowed on OpenACC " "directives or clauses"_err_en_US); } } // other checks, more TBD } }, [&](const parser::Name &name) { // common block if (auto *symbol{ResolveAccCommonBlockName(&name)}) { CheckMultipleAppearances( name, *symbol, Symbol::Flag::AccCommonBlock); for (auto &object : symbol->get().objects()) { if (auto *resolvedObject{ ResolveAcc(*object, accFlag, currScope())}) { AddToContextObjectWithDSA(*resolvedObject, accFlag); } } } else { context_.Say(name.source, "Could not find COMMON block '%s' used in OpenACC directive"_err_en_US, name.ToString()); } }, }, accObject.u); } Symbol *AccAttributeVisitor::ResolveAcc( const parser::Name &name, Symbol::Flag accFlag, Scope &scope) { return DeclareOrMarkOtherAccessEntity(name, accFlag); } Symbol *AccAttributeVisitor::ResolveAcc( Symbol &symbol, Symbol::Flag accFlag, Scope &scope) { return DeclareOrMarkOtherAccessEntity(symbol, accFlag); } Symbol *AccAttributeVisitor::DeclareOrMarkOtherAccessEntity( const parser::Name &name, Symbol::Flag accFlag) { if (name.symbol) { return DeclareOrMarkOtherAccessEntity(*name.symbol, accFlag); } else { return nullptr; } } Symbol *AccAttributeVisitor::DeclareOrMarkOtherAccessEntity( Symbol &object, Symbol::Flag accFlag) { if (accFlagsRequireMark.test(accFlag)) { if (GetContext().directive == llvm::acc::ACCD_declare) { object.set(Symbol::Flag::AccDeclare); object.set(accFlag); } } return &object; } void AccAttributeVisitor::CheckMultipleAppearances(const parser::Name &name, const Symbol &symbol, Symbol::Flag accFlag, const parser::AccObject *occurrence) { const auto *target{&symbol}; if (HasDataSharingAttributeObject(*target)) { // A same-kind duplicate (e.g. private(x, x) or private(x) private(x)) // is benign: warn and tag this AccObject occurrence so rewrite-parse-tree // can drop it from the clause list. Cross-kind duplicates (e.g. // private(x) firstprivate(x)) remain hard errors. // // Reduction is excluded from the benign case: two reduction clauses // with the same Symbol::Flag may still differ in operator, which is a // real conflict that dedup would silently hide. auto firstFlag{GetContext().FindSymbolWithDSA(*target)}; if (occurrence && firstFlag && *firstFlag == accFlag && accFlag != Symbol::Flag::AccReduction) { context_.Warn(common::UsageWarning::OpenAccUsage, name.source, "'%s' appears more than once in the same kind of data-sharing clause on an OpenACC directive; duplicate ignored"_warn_en_US, name.ToString()); context_.MarkAccObjectDuplicate(occurrence); } else { context_.Say(name.source, "'%s' appears in more than one data-sharing clause on the same OpenACC directive"_err_en_US, name.ToString()); } } else { AddDataSharingAttributeObject(*target); } } #ifndef NDEBUG #define DEBUG_TYPE "omp" static llvm::raw_ostream &operator<<( llvm::raw_ostream &os, const Symbol::Flags &flags); namespace dbg { static void DumpAssocSymbols(llvm::raw_ostream &os, const Symbol &sym); static std::string ScopeSourcePos(const Fortran::semantics::Scope &scope); } // namespace dbg #endif bool OmpAttributeVisitor::Pre(const parser::OmpBlockConstruct &x) { const parser::OmpDirectiveSpecification &dirSpec{x.BeginDir()}; llvm::omp::Directive dirId{dirSpec.DirId()}; PushContext(dirSpec.source, dirId); ClearDataSharingAttributeObjects(); return true; } void OmpAttributeVisitor::Post(const parser::OmpBlockConstruct &x) { PopContext(); } bool OmpAttributeVisitor::Pre( const parser::OpenMPSimpleStandaloneConstruct &x) { const auto &standaloneDir{std::get(x.v.t)}; PushContext(standaloneDir.source, standaloneDir.v); ClearDataSharingAttributeObjects(); return true; } bool OmpAttributeVisitor::Pre(const parser::OpenMPLoopConstruct &x) { const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()}; const parser::OmpDirectiveName &beginName{beginSpec.DirName()}; PushContext(beginName.source, beginName.v); ClearDataSharingAttributeObjects(); if (beginName.v == llvm::omp::Directive::OMPD_do) { if (const parser::DoConstruct *doConstruct{x.GetNestedLoop()}) { if (doConstruct->IsDoWhile()) { return true; } } } PrivatizeAssociatedLoopIndex(x); return true; } void OmpAttributeVisitor::ResolveSeqLoopIndexInParallelOrTaskConstruct( const parser::Name &iv) { unsigned version{context_.langOptions().OpenMPVersion}; // Find the parallel, teams or task generating construct enclosing the // sequential loop. auto targetIt{dirContext_.rbegin()}; for (;; ++targetIt) { if (targetIt == dirContext_.rend()) { return; } if (llvm::omp::allParallelSet.test(targetIt->directive) || llvm::omp::taskGeneratingSet.test(targetIt->directive)) { break; } if (version >= 52) { if (llvm::omp::allTeamsSet.test(targetIt->directive)) { break; } } } if (IsLocalInsideScope(*iv.symbol, targetIt->scope)) { return; } // If this symbol already has a data-sharing attribute then there is nothing // to do here. if (const Symbol *symbol{iv.symbol}) { for (auto symMap : targetIt->objectWithDSA) { if (symMap.first->name() == symbol->name()) { return; } } } // If this symbol already has an explicit data-sharing attribute in the // enclosing OpenMP parallel or task then there is nothing to do here. if (auto *symbol{targetIt->scope.FindSymbol(iv.source)}) { if (symbol->owner() == targetIt->scope) { if (symbol->test(Symbol::Flag::OmpExplicit) && (symbol->flags() & dataSharingAttributeFlags).any()) { return; } } } // Otherwise find the symbol and make it Private for the entire enclosing // parallel or task if (auto *symbol{ResolveOmp(iv, Symbol::Flag::OmpPrivate, targetIt->scope)}) { targetIt++; SetSymbolDSA( *symbol, {Symbol::Flag::OmpPreDetermined, Symbol::Flag::OmpPrivate}); iv.symbol = symbol; // adjust the symbol within region for (auto it{dirContext_.rbegin()}; it != targetIt; ++it) { AddToContextObjectWithDSA(*symbol, Symbol::Flag::OmpPrivate, *it); } } } // [OMP-4.5]2.15.1.1 Data-sharing Attribute Rules - Predetermined // - A loop iteration variable for a sequential loop in a parallel // or task generating construct is private in the innermost such // construct that encloses the loop // Loop iteration variables are not well defined for DO WHILE loop. // Use of DO CONCURRENT inside OpenMP construct is unspecified behavior // till OpenMP-5.0 standard. // In above both cases we skip the privatization of iteration variables. bool OmpAttributeVisitor::Pre(const parser::DoConstruct &x) { if (WithinConstruct()) { llvm::SmallVector ivs; if (x.IsDoNormal()) { const parser::Name *iv{GetLoopIndex(x)}; if (iv && iv->symbol) ivs.push_back(iv); } for (auto iv : ivs) { if (!iv->symbol->test(Symbol::Flag::OmpPreDetermined)) { ResolveSeqLoopIndexInParallelOrTaskConstruct(*iv); } else { // TODO: conflict checks with explicitly determined DSA } } } return true; } // 2.15.1.1 Data-sharing Attribute Rules - Predetermined // - The loop iteration variable(s) in the associated do-loop(s) of a do, // parallel do, taskloop, or distribute construct is (are) private. // - The loop iteration variable in the associated do-loop of a simd construct // with just one associated do-loop is linear with a linear-step that is the // increment of the associated do-loop (only for OpenMP versions <= 4.5) // - The loop iteration variables in the associated do-loops of a simd // construct with multiple associated do-loops are lastprivate. void OmpAttributeVisitor::PrivatizeAssociatedLoopIndex( const parser::OpenMPLoopConstruct &x) { const parser::OmpDirectiveSpecification &spec{x.BeginDir()}; unsigned version{context_.langOptions().OpenMPVersion}; auto [depth, _]{ omp::GetAffectedNestDepthWithReason(spec, version, &context_)}; // If depth is absent, then there is some issue. Leave it alone here, // and let the semantic checks diagnose the problem. if (!depth || *depth.value <= 0) { return; } int64_t level{*depth.value}; Symbol::Flag ivDSA; if (!llvm::omp::allSimdSet.test(GetContext().directive)) { ivDSA = Symbol::Flag::OmpPrivate; } else if (level == 1 && version < 60) { ivDSA = Symbol::Flag::OmpLinear; } else { ivDSA = Symbol::Flag::OmpLastPrivate; } Scope &scope{currScope()}; if (auto doLoops{omp::CollectAffectedDoLoops(x, version, &context_)}) { for (const parser::DoConstruct *loop : *doLoops) { const parser::Name *iv{GetLoopIndex(*loop)}; if (!iv || (iv->symbol && IsLocalInsideScope(*iv->symbol, scope))) { continue; } if (auto *symbol{ResolveOmp(*iv, ivDSA, scope)}) { SetSymbolDSA(*symbol, {Symbol::Flag::OmpPreDetermined, ivDSA}); iv->symbol = symbol; // adjust the symbol within region AddToContextObjectWithDSA(*symbol, ivDSA); } } } } bool OmpAttributeVisitor::Pre(const parser::OmpGroupprivateDirective &x) { PushContext(x.source, llvm::omp::Directive::OMPD_groupprivate); for (const parser::OmpArgument &arg : x.v.Arguments().v) { if (auto *object{parser::omp::GetArgumentObject(arg)}) { ResolveOmpObject(*object, Symbol::Flag::OmpGroupPrivate); } } return true; } bool OmpAttributeVisitor::Pre(const parser::OpenMPSectionsConstruct &x) { const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()}; const parser::OmpDirectiveName &beginName{beginSpec.DirName()}; switch (beginName.v) { case llvm::omp::Directive::OMPD_parallel_sections: case llvm::omp::Directive::OMPD_sections: PushContext(beginName.source, beginName.v); GetContext().withinConstruct = true; break; default: break; } ClearDataSharingAttributeObjects(); return true; } bool OmpAttributeVisitor::Pre(const parser::OmpSectionDirective &x) { PushContext(x.source, llvm::omp::Directive::OMPD_section); GetContext().withinConstruct = true; return true; } bool OmpAttributeVisitor::Pre(const parser::OpenMPCriticalConstruct &x) { const parser::OmpBeginDirective &beginSpec{x.BeginDir()}; PushContext(beginSpec.DirName().source, beginSpec.DirId()); GetContext().withinConstruct = true; return true; } bool OmpAttributeVisitor::Pre(const parser::OmpDeclareTargetDirective &x) { PushContext(x.source, llvm::omp::Directive::OMPD_declare_target); unsigned version{context_.langOptions().OpenMPVersion}; using OmpClauseSet = WithOmpDeclarative::OmpClauseSet; std::map details; std::optional device; if (auto *devClause{ parser::omp::FindClause(x.v, llvm::omp::Clause::OMPC_device_type)}) { device = parser::UnwrapRef(*devClause); } auto addClause{[&](const parser::OmpObject &object, llvm::omp::Clause clauseId) { if (const Symbol *sym{omp::GetObjectSymbol(object)}) { auto &clauseSet{const_cast(details[sym].ompDeclTarget())}; clauseSet.set(clauseId); } }}; for (const parser::OmpArgument &arg : x.v.Arguments().v) { if (auto *object{parser::omp::GetArgumentObject(arg)}) { ResolveOmpObject(*object, Symbol::Flag::OmpDeclareTarget); // Always record this as "enter", even with older OpenMP versions // (as opposed to "to"). addClause(*object, llvm::omp::Clause::OMPC_enter); } } for (const parser::OmpClause &clause : x.v.Clauses().v) { if (auto *objects{parser::omp::GetOmpObjectList(clause)}) { for (const parser::OmpObject &object : objects->v) { ResolveOmpObject(object, Symbol::Flag::OmpDeclareTarget); addClause(object, clause.Id()); } } } if (x.v.Arguments().v.empty() && x.v.Clauses().v.empty()) { // "!$omp declare_target" alone applies to the associated procedure. const Scope &scope{omp::GetScopingUnit(context_.FindScope(x.source))}; if (auto *proc{const_cast(scope.symbol())}) { proc->flags().set(Symbol::Flag::OmpDeclareTarget); auto &clauseSet{ const_cast(details[proc].ompDeclTarget())}; clauseSet.set(llvm::omp::Clause::OMPC_enter); } } for (auto &pair : details) { const Symbol *sym{&pair.first->GetUltimate()}; const WithOmpDeclarative &decl{pair.second}; common::visit( [&](auto &d) { using TypeD = llvm::remove_cvref_t; if constexpr (std::is_base_of_v) { d.set_version(version); auto &clauseSet{const_cast(d.ompDeclTarget())}; clauseSet |= decl.ompDeclTarget(); if (device) { clauseSet.set(llvm::omp::Clause::OMPC_device_type); auto &deviceType{ const_cast(d.ompDeviceType())}; deviceType = device; } } else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { // e.g. KindParamInquiry // These cannot be specified on DECLARE_TARGET. The symbol will // receive the OmpDeclareTarget flag (for a diagnostic message), // but no details will be modified. } else { // Catch any unexpected cases. assert((std::is_base_of_v) && "Unexpected details type"); } }, const_cast(sym)->details()); } return true; } bool OmpAttributeVisitor::Pre(const parser::OmpDeclareMapperDirective &x) { const parser::OmpDirectiveName &dirName{x.v.DirName()}; PushContext(dirName.source, dirName.v); return true; } bool OmpAttributeVisitor::Pre(const parser::OmpDeclareReductionDirective &x) { PushContext(x.source, llvm::omp::Directive::OMPD_declare_reduction); return true; } bool OmpAttributeVisitor::Pre(const parser::OmpThreadprivateDirective &x) { const parser::OmpDirectiveName &dirName{x.v.DirName()}; PushContext(dirName.source, dirName.v); for (const parser::OmpArgument &arg : x.v.Arguments().v) { if (auto *object{parser::omp::GetArgumentObject(arg)}) { ResolveOmpObject(*object, Symbol::Flag::OmpThreadprivate); } } return true; } bool OmpAttributeVisitor::Pre(const parser::OmpAllocateDirective &x) { PushContext(x.source, llvm::omp::Directive::OMPD_allocate); assert(!partStack_.empty() && "Misplaced directive"); auto ompFlag{partStack_.back() == PartKind::SpecificationPart ? Symbol::Flag::OmpDeclarativeAllocateDirective : Symbol::Flag::OmpExecutableAllocateDirective}; parser::omp::OmpAllocateInfo info{parser::omp::SplitOmpAllocate(x)}; for (const parser::OmpAllocateDirective *ad : info.dirs) { for (const parser::OmpArgument &arg : ad->BeginDir().Arguments().v) { if (auto *object{parser::omp::GetArgumentObject(arg)}) { ResolveOmpObject(*object, ompFlag); } } } PopContext(); return false; } bool OmpAttributeVisitor::Pre(const parser::OmpAssumeDirective &x) { PushContext(x.source, llvm::omp::Directive::OMPD_assume); return true; } bool OmpAttributeVisitor::Pre(const parser::OpenMPAtomicConstruct &x) { PushContext(x.source, llvm::omp::Directive::OMPD_atomic); return true; } bool OmpAttributeVisitor::Pre(const parser::OpenMPDispatchConstruct &x) { PushContext(x.source, llvm::omp::Directive::OMPD_dispatch); return true; } bool OmpAttributeVisitor::Pre(const parser::OpenMPAllocatorsConstruct &x) { const parser::OmpDirectiveSpecification &dirSpec{x.BeginDir()}; PushContext(x.source, dirSpec.DirId()); for (const auto &clause : dirSpec.Clauses().v) { if (std::get_if(&clause.u)) { ResolveOmpObjectList(*parser::omp::GetOmpObjectList(clause), Symbol::Flag::OmpExecutableAllocateDirective); } } return true; } void OmpAttributeVisitor::Post(const parser::OmpClause::Defaultmap &x) { using ImplicitBehavior = parser::OmpDefaultmapClause::ImplicitBehavior; using VariableCategory = parser::OmpVariableCategory; VariableCategory::Value varCategory; ImplicitBehavior impBehavior; if (!dirContext_.empty()) { impBehavior = std::get(x.v.t); auto &modifiers{OmpGetModifiers(x.v)}; auto *maybeCategory{ OmpGetUniqueModifier(modifiers)}; if (maybeCategory) varCategory = maybeCategory->v; else varCategory = VariableCategory::Value::All; AddContextDefaultmapBehaviour(varCategory, impBehavior); } } void OmpAttributeVisitor::Post(const parser::OmpDefaultClause &x) { // The DEFAULT clause may also be used on METADIRECTIVE. In that case // there is nothing to do. using DataSharingAttribute = parser::OmpDefaultClause::DataSharingAttribute; if (auto *dsa{std::get_if(&x.u)}) { if (!dirContext_.empty()) { switch (*dsa) { case DataSharingAttribute::Private: SetContextDefaultDSA(Symbol::Flag::OmpPrivate); break; case DataSharingAttribute::Firstprivate: SetContextDefaultDSA(Symbol::Flag::OmpFirstPrivate); break; case DataSharingAttribute::Shared: SetContextDefaultDSA(Symbol::Flag::OmpShared); break; case DataSharingAttribute::None: SetContextDefaultDSA(Symbol::Flag::OmpNone); break; } } } } bool OmpAttributeVisitor::IsNestedInDirective(llvm::omp::Directive directive) { if (dirContext_.size() >= 1) { for (std::size_t i = dirContext_.size() - 1; i > 0; --i) { if (dirContext_[i - 1].directive == directive) { return true; } } } return false; } void OmpAttributeVisitor::Post(const parser::OpenMPAllocatorsConstruct &x) { PopContext(); } static bool IsTargetCaptureImplicitlyFirstprivatizeable(const Symbol &symbol, const Symbol::Flags &dsa, const Symbol::Flags &dataSharingAttributeFlags, const Symbol::Flags &dataMappingAttributeFlags, std::map defaultMap) { // If a Defaultmap clause is present for the current target scope, and it has // specified behaviour other than Firstprivate for scalars then we exit early, // as it overrides the implicit Firstprivatization of scalars OpenMP rule. if (!defaultMap.empty()) { if (llvm::is_contained( defaultMap, parser::OmpVariableCategory::Value::Scalar) && defaultMap[parser::OmpVariableCategory::Value::Scalar] != parser::OmpDefaultmapClause::ImplicitBehavior::Firstprivate) { return false; } if (llvm::is_contained( defaultMap, parser::OmpVariableCategory::Value::All) && defaultMap[parser::OmpVariableCategory::Value::All] != parser::OmpDefaultmapClause::ImplicitBehavior::Firstprivate) { return false; } } auto checkSymbol = [&](const Symbol &checkSym) { // if we're associated with any other flags we skip implicit privitization // for now. If we're an allocatable, pointer or declare target, we're not // implicitly firstprivitizeable under OpenMP restrictions. // TODO: Relax restriction as we progress privitization and further // investigate the flags we can intermix with. if (!(dsa & (dataSharingAttributeFlags | dataMappingAttributeFlags)) .none() || !checkSym.flags().none() || IsAssumedShape(checkSym) || semantics::IsAllocatableOrPointer(checkSym)) { return false; } // It is default firstprivatizeable as far as the OpenMP specification is // concerned if it is a non-array scalar type that has been implicitly // captured in a target region const auto *type{checkSym.GetType()}; if ((!checkSym.GetShape() || checkSym.GetShape()->empty()) && (type->category() == Fortran::semantics::DeclTypeSpec::Category::Numeric || type->category() == Fortran::semantics::DeclTypeSpec::Category::Logical || type->category() == Fortran::semantics::DeclTypeSpec::Category::Character)) { return true; } return false; }; return common::visit( common::visitors{ [&](const UseDetails &x) -> bool { return checkSymbol(x.symbol()); }, [&](const HostAssocDetails &x) -> bool { return checkSymbol(x.symbol()); }, [&](const auto &) -> bool { return checkSymbol(symbol); }, }, symbol.details()); } void OmpAttributeVisitor::CreateImplicitSymbols( const parser::Name &name, const Symbol *symbol) { if (!omp::IsPrivatizable(*symbol)) { return; } LLVM_DEBUG(llvm::dbgs() << "CreateImplicitSymbols: " << *symbol << '\n'); // Implicitly determined DSAs // OMP 5.2 5.1.1 - Variables Referenced in a Construct Symbol *lastDeclSymbol = nullptr; Symbol::Flags prevDSA; bool checkDefaultNone = false; for (int dirDepth{0}; dirDepth < (int)dirContext_.size(); ++dirDepth) { DirContext &dirContext = dirContext_[dirDepth]; Symbol::Flags dsa; Scope &scope{context_.FindScope(dirContext.directiveSource)}; auto initSymbolDSA = [&](const Symbol *sym, Symbol::Flags &dsa) { auto it{scope.find(sym->name())}; if (it != scope.end()) { // There is already a symbol in the current scope, use its DSA. dsa = GetSymbolDSA(*it->second); } else { for (auto symMap : dirContext.objectWithDSA) { if (symMap.first->name() == sym->name()) { // `sym` already has a data-sharing attribute in the current // context, use it. dsa.set(symMap.second); break; } } } }; initSymbolDSA(symbol, dsa); // When handling each implicit rule for a given symbol, one of the // following actions may be taken: // 1. Declare a new private or shared symbol. // 2. Use the last declared symbol, by inserting a new symbol in the // scope being processed, associated with it. // If no symbol was declared previously, then no association is needed // and the symbol from the enclosing scope will be inherited by the // current one. // // Because of how symbols are collected in lowering, not inserting a new // symbol in the second case could lead to the conclusion that a symbol // from an enclosing construct was declared in the current construct, // which would result in wrong privatization code being generated. // Consider the following example: // // !$omp parallel default(private) ! p1 // !$omp parallel default(private) shared(x) ! p2 // x = 10 // !$omp end parallel // !$omp end parallel // // If a new x symbol was not inserted in the inner parallel construct // (p2), it would use the x symbol definition from the enclosing scope. // Then, when p2's default symbols were collected in lowering, the x // symbol from the outer parallel construct (p1) would be collected, as // it would have the private flag set. // This would make x appear to be defined in p2, causing it to be // privatized in p2 and its privatization in p1 to be skipped. auto makeSymbol = [&](Symbol::Flags flags) { const Symbol *hostSymbol = lastDeclSymbol ? lastDeclSymbol : &symbol->GetUltimate(); assert(flags.LeastElement()); Symbol::Flag flag = *flags.LeastElement(); lastDeclSymbol = DeclareNewAccessEntity( *hostSymbol, flag, context_.FindScope(dirContext.directiveSource)); lastDeclSymbol->flags() |= flags; return lastDeclSymbol; }; auto useLastDeclSymbol = [&]() { if (lastDeclSymbol) { const Symbol *hostSymbol = lastDeclSymbol ? lastDeclSymbol : &symbol->GetUltimate(); MakeAssocSymbol(symbol->name(), *hostSymbol, context_.FindScope(dirContext.directiveSource)); } }; #ifndef NDEBUG auto printImplicitRule = [&](const char *id) { LLVM_DEBUG(llvm::dbgs() << "\t" << id << ": dsa: " << dsa << '\n'); LLVM_DEBUG( llvm::dbgs() << "\t\tScope: " << dbg::ScopeSourcePos(scope) << '\n'); }; #define PRINT_IMPLICIT_RULE(id) printImplicitRule(id) #else #define PRINT_IMPLICIT_RULE(id) #endif bool taskGenDir = llvm::omp::taskGeneratingSet.test(dirContext.directive); bool targetDir = llvm::omp::allTargetSet.test(dirContext.directive); bool parallelDir = llvm::omp::topParallelSet.test(dirContext.directive); bool teamsDir = llvm::omp::allTeamsSet.test(dirContext.directive); bool isStaticStorageDuration = HasStaticStorageDuration(*symbol); LLVM_DEBUG(llvm::dbgs() << "HasStaticStorageDuration(" << symbol->name() << "):\n"); const Symbol *crayPtr = nullptr; Symbol::Flags crayPtrDSA; if (symbol->GetUltimate().test(Symbol::Flag::CrayPointee)) { crayPtr = currScope().FindSymbol(semantics::GetCrayPointer(*symbol).name()); if (crayPtr) { initSymbolDSA(crayPtr, crayPtrDSA); } } if (dsa.none() && crayPtrDSA.none() && dirContext.defaultDSA == Symbol::Flag::OmpNone) { checkDefaultNone = true; } if (checkDefaultNone) { auto defaultNoneError = [&](parser::CharBlock loc, const Symbol *sym) { if (crayPtr) { context_.Say(loc, "The DEFAULT(NONE) clause requires that the Cray Pointer '%s' must be listed in a data-sharing attribute clause"_err_en_US, crayPtr->name()); } else { context_.Say(loc, "The DEFAULT(NONE) clause requires that '%s' must be listed in a data-sharing attribute clause"_err_en_US, sym->name()); } }; if (dsa.test(Symbol::Flag::OmpPrivate) || crayPtrDSA.test(Symbol::Flag::OmpPrivate)) { checkDefaultNone = false; } else if (dsa.any() || crayPtrDSA.any()) { defaultNoneError(dirContext.directiveSource, symbol); } else if (dirDepth == (int)dirContext_.size() - 1) { defaultNoneError(name.source, symbol); } } if (dsa.any()) { if (parallelDir || taskGenDir || teamsDir) { Symbol *prevDeclSymbol{lastDeclSymbol}; // NOTE As `dsa` will match that of the symbol in the current scope // (if any), we won't override the DSA of any existing symbol. if ((dsa & dataSharingAttributeFlags).any()) { makeSymbol(dsa); } // Fix host association of explicit symbols, as they can be created // before implicit ones in enclosing scope. if (prevDeclSymbol && prevDeclSymbol != lastDeclSymbol && lastDeclSymbol->test(Symbol::Flag::OmpExplicit)) { const auto *hostAssoc{lastDeclSymbol->detailsIf()}; if (hostAssoc && hostAssoc->symbol() != *prevDeclSymbol) { lastDeclSymbol->set_details(HostAssocDetails{*prevDeclSymbol}); } } } prevDSA = dsa; PRINT_IMPLICIT_RULE("0) already has DSA"); continue; } // NOTE Because of how lowering uses OmpImplicit flag, we can only set it // for symbols with private DSA. // Also, as the default clause is handled separately in lowering, // don't mark its symbols with OmpImplicit either. // Ideally, lowering should be changed and all implicit symbols // should be marked with OmpImplicit. if (dirContext.defaultDSA == Symbol::Flag::OmpPrivate || dirContext.defaultDSA == Symbol::Flag::OmpFirstPrivate || dirContext.defaultDSA == Symbol::Flag::OmpShared) { // 1) default // Allowed only with parallel, teams and task generating constructs. if (!parallelDir && !taskGenDir && !teamsDir) { return; } dsa = {dirContext.defaultDSA}; makeSymbol(dsa); PRINT_IMPLICIT_RULE("1) default"); } else if (!targetDir && parallelDir/*(!enableDelayedPrivatizationStaging && parallelDir) || (enableDelayedPrivatizationStaging && !targetDir && parallelDir)*/) { // 2) parallel -> shared dsa = {Symbol::Flag::OmpShared}; makeSymbol(dsa); PRINT_IMPLICIT_RULE("2) parallel"); } else if (!taskGenDir && !targetDir) { // 3) enclosing context dsa = prevDSA; useLastDeclSymbol(); PRINT_IMPLICIT_RULE("3) enclosing context"); } else if (targetDir) { // 4) not mapped target variable -> firstprivate // - i.e. implicit, but meets OpenMP specification rules for // firstprivate "promotion" if (/*enableDelayedPrivatizationStaging && */ IsTargetCaptureImplicitlyFirstprivatizeable(*symbol, prevDSA, dataSharingAttributeFlags, dataMappingAttributeFlags, dirContext.defaultMap)) { prevDSA.set(Symbol::Flag::OmpImplicit); prevDSA.set(Symbol::Flag::OmpFirstPrivate); makeSymbol(prevDSA); } dsa = prevDSA; PRINT_IMPLICIT_RULE("4) not mapped target variable -> firstprivate"); } else if (taskGenDir) { // TODO 5) dummy arg in orphaned taskgen construct -> firstprivate if (prevDSA.test(Symbol::Flag::OmpShared) || (isStaticStorageDuration && (prevDSA & dataSharingAttributeFlags).none())) { // 6) shared in enclosing context -> shared dsa = {Symbol::Flag::OmpShared}; makeSymbol(dsa); PRINT_IMPLICIT_RULE("6) taskgen: shared"); } else { // 7) firstprivate dsa = {Symbol::Flag::OmpFirstPrivate}; makeSymbol(dsa)->set(Symbol::Flag::OmpImplicit); PRINT_IMPLICIT_RULE("7) taskgen: firstprivate"); } } prevDSA = dsa; } } static bool IsOpenMPPointer(const Symbol &symbol) { if (IsPointer(symbol) || IsBuiltinCPtr(symbol)) return true; return false; } static bool IsOpenMPAggregate(const Symbol &symbol) { if (IsAllocatable(symbol) || IsOpenMPPointer(symbol)) return false; const auto *type{symbol.GetType()}; // OpenMP categorizes Fortran characters as aggregates. if (type->category() == Fortran::semantics::DeclTypeSpec::Category::Character) return true; if (const auto *det{symbol.GetUltimate() .detailsIf()}) if (det->IsArray()) return true; if (type->AsDerived()) return true; if (IsDeferredShape(symbol) || IsAssumedRank(symbol) || IsAssumedShape(symbol)) return true; return false; } static bool IsOpenMPScalar(const Symbol &symbol) { if (IsOpenMPAggregate(symbol) || IsOpenMPPointer(symbol) || IsAllocatable(symbol)) return false; const auto *type{symbol.GetType()}; if ((!symbol.GetShape() || symbol.GetShape()->empty()) && (type->category() == Fortran::semantics::DeclTypeSpec::Category::Numeric || type->category() == Fortran::semantics::DeclTypeSpec::Category::Logical)) return true; return false; } static bool DefaultMapCategoryMatchesSymbol( parser::OmpVariableCategory::Value category, const Symbol &symbol) { using VarCat = parser::OmpVariableCategory::Value; switch (category) { case VarCat::Scalar: return IsOpenMPScalar(symbol); case VarCat::Allocatable: return IsAllocatable(symbol); case VarCat::Aggregate: return IsOpenMPAggregate(symbol); case VarCat::Pointer: return IsOpenMPPointer(symbol); case VarCat::All: return true; } return false; } // For OpenMP constructs, check all the data-refs within the constructs // and adjust the symbol for each Name if necessary void OmpAttributeVisitor::Post(const parser::Name &name) { auto *symbol{name.symbol}; if (symbol && WithinConstruct()) { if (omp::IsPrivatizable(*symbol) && !IsObjectWithDSA(*symbol) && !IsLocalInsideScope(*symbol, currScope())) { // TODO: create a separate function to go through the rules for // predetermined, explicitly determined, and implicitly // determined data-sharing attributes (2.15.1.1). if (Symbol * found{currScope().FindSymbol(name.source)}) { if (symbol != found) { name.symbol = found; // adjust the symbol within region } } } // TODO: handle case where default and defaultmap are present on the same // construct and conflict, defaultmap should supersede default if they // conflict. if (!GetContext().defaultMap.empty()) { // Checked before implicit data sharing attributes as this rule ignores // them and expects explicit predetermined/specified attributes to be in // place for the types specified. if (Symbol * found{currScope().FindSymbol(name.source)}) { // If the variable has declare target applied to it (enter or link) it // is exempt from defaultmap(none) restrictions. // We also exempt procedures and named constants from defaultmap(none) // checking. if (!symbol->GetUltimate().test(Symbol::Flag::OmpDeclareTarget) && !(IsProcedure(*symbol) && !semantics::IsProcedurePointer(*symbol)) && !IsNamedConstant(*symbol)) { auto &dMap = GetContext().defaultMap; for (auto defaults : dMap) { if (defaults.second == parser::OmpDefaultmapClause::ImplicitBehavior::None) { if (DefaultMapCategoryMatchesSymbol(defaults.first, *found)) { if (!IsObjectWithDSA(*symbol)) { context_.Say(name.source, "The DEFAULTMAP(NONE) clause requires that '%s' must be " "listed in a " "data-sharing attribute, data-mapping attribute, or is_device_ptr clause"_err_en_US, symbol->name()); } } } } } } } if (Symbol * found{currScope().FindSymbol(name.source)}) { if (found->GetUltimate().test(semantics::Symbol::Flag::OmpThreadprivate)) return; } // We should only create any additional symbols, if the one mentioned // in the source code was declared outside of the construct. This was // always the case before Fortran 2008. F2008 introduced the BLOCK // construct, and allowed local variable declarations. // In OpenMP local (non-static) variables are always private in a given // construct, if they are declared inside the construct. In those cases // we don't need to do anything here (i.e. no flags are needed or // anything else). if (!IsLocalInsideScope(*symbol, currScope())) { CreateImplicitSymbols(name, symbol); } } // within OpenMP construct } Symbol *OmpAttributeVisitor::ResolveName(const parser::Name *name) { // TODO: why is the symbol not properly resolved by name resolution? if (auto *resolvedSymbol{ name ? GetContext().scope.FindSymbol(name->source) : nullptr}) { name->symbol = resolvedSymbol; return resolvedSymbol; } else { return nullptr; } } void OmpAttributeVisitor::ResolveOmpName( const parser::Name &name, Symbol::Flag ompFlag) { if (ResolveName(&name)) { if (auto *resolvedSymbol{ResolveOmp(name, ompFlag, currScope())}) { if (dataSharingAttributeFlags.test(ompFlag)) { AddToContextObjectWithExplicitDSA(*resolvedSymbol, ompFlag); } } } else if (ompFlag == Symbol::Flag::OmpCriticalLock) { const auto pair{ GetContext().scope.try_emplace(name.source, Attrs{}, UnknownDetails{})}; CHECK(pair.second); name.symbol = &pair.first->second.get(); } } void OmpAttributeVisitor::ResolveOmpNameList( const std::list &nameList, Symbol::Flag ompFlag) { for (const auto &name : nameList) { ResolveOmpName(name, ompFlag); } } Symbol *OmpAttributeVisitor::ResolveOmpCommonBlockName( const parser::Name *name) { if (!name) { return nullptr; } if (auto *cb{GetProgramUnitOrBlockConstructContaining(GetContext().scope) .FindCommonBlock(name->source)}) { name->symbol = cb; return cb; } return nullptr; } void OmpAttributeVisitor::ResolveOmpObjectList( const parser::OmpObjectList &ompObjectList, Symbol::Flag ompFlag) { for (const auto &ompObject : ompObjectList.v) { ResolveOmpObject(ompObject, ompFlag); } } /// True if either symbol is in a namelist or some other symbol in the same /// equivalence set as symbol is in a namelist. static bool SymbolOrEquivalentIsInNamelist(const Symbol &symbol) { auto isInNamelist{[](const Symbol &sym) { const Symbol &ultimate{sym.GetUltimate()}; return ultimate.test(Symbol::Flag::InNamelist); }}; const EquivalenceSet *eqv{FindEquivalenceSet(symbol)}; if (!eqv) { return isInNamelist(symbol); } return llvm::any_of(*eqv, [isInNamelist](const EquivalenceObject &obj) { return isInNamelist(obj.symbol); }); } void OmpAttributeVisitor::ResolveOmpDesignator( const parser::Designator &designator, Symbol::Flag ompFlag) { unsigned version{context_.langOptions().OpenMPVersion}; llvm::omp::Directive directive{GetContext().directive}; const auto *name{parser::GetDesignatorNameIfDataRef(designator)}; if (!name) { // Array sections to be changed to substrings as needed if (AnalyzeExpr(context_, designator)) { if (std::holds_alternative(designator.u)) { context_.Say(designator.source, "Substrings are not allowed on OpenMP directives or clauses"_err_en_US); } } // other checks, more TBD return; } if (auto *symbol{ResolveOmp(*name, ompFlag, currScope())}) { auto checkExclusivelists{// [&](const Symbol *symbol1, Symbol::Flag firstOmpFlag, const Symbol *symbol2, Symbol::Flag secondOmpFlag) { if ((symbol1->test(firstOmpFlag) && symbol2->test(secondOmpFlag)) || (symbol1->test(secondOmpFlag) && symbol2->test(firstOmpFlag))) { context_.Say(designator.source, "Variable '%s' may not appear on both %s and %s clauses on a %s construct"_err_en_US, symbol2->name(), Symbol::OmpFlagToClauseName(firstOmpFlag), Symbol::OmpFlagToClauseName(secondOmpFlag), parser::omp::GetUpperName(directive, version)); } }}; if (dataCopyingAttributeFlags.test(ompFlag)) { CheckDataCopyingClause(*name, *symbol, ompFlag); } else { AddToContextObjectWithExplicitDSA(*symbol, ompFlag); if (dataSharingAttributeFlags.test(ompFlag)) { CheckMultipleAppearances(*name, *symbol, ompFlag); } if (privateDataSharingAttributeFlags.test(ompFlag)) { CheckObjectIsPrivatizable(*name, *symbol, ompFlag); } } // Save the original symbol. For privatizing clauses, ensure enclosing // constructs properly capture the variable. const Symbol *origSymbol{name->symbol}; if (origSymbol && privateDataSharingAttributeFlags.test(ompFlag)) { CreateImplicitSymbols(*name, origSymbol); } if (ompFlag == Symbol::Flag::OmpReduction) { // Using variables inside of a namelist in OpenMP reductions // is allowed by the standard, but is not allowed for // privatisation. This looks like an oversight. If the // namelist is hoisted to a global, we cannot apply the // mapping for the reduction variable: resulting in incorrect // results. Disabling this hoisting could make some real // production code go slower. See discussion in #109303 if (SymbolOrEquivalentIsInNamelist(*symbol)) { context_.Say(name->source, "Variable '%s' in NAMELIST cannot be in a REDUCTION clause"_err_en_US, name->ToString()); } } if (ompFlag == Symbol::Flag::OmpInclusiveScan || ompFlag == Symbol::Flag::OmpExclusiveScan) { if (!symbol->test(Symbol::Flag::OmpInScanReduction)) { context_.Say(name->source, "List item %s must appear in REDUCTION clause with the INSCAN modifier of the parent directive"_err_en_US, name->ToString()); } } if (ompFlag == Symbol::Flag::OmpDeclareTarget) { if (symbol->IsFuncResult()) { if (Symbol * func{currScope().symbol()}) { CHECK(func->IsSubprogram()); func->set(ompFlag); name->symbol = func; } } } if (directive == llvm::omp::Directive::OMPD_target_data) { checkExclusivelists(symbol, Symbol::Flag::OmpUseDevicePtr, symbol, Symbol::Flag::OmpUseDeviceAddr); } if (llvm::omp::allDistributeSet.test(directive)) { checkExclusivelists(symbol, Symbol::Flag::OmpFirstPrivate, symbol, Symbol::Flag::OmpLastPrivate); } if (llvm::omp::allTargetSet.test(directive)) { checkExclusivelists(symbol, Symbol::Flag::OmpIsDevicePtr, symbol, Symbol::Flag::OmpHasDeviceAddr); const auto *hostAssocSym{symbol}; if (!symbol->test(Symbol::Flag::OmpIsDevicePtr) && !symbol->test(Symbol::Flag::OmpHasDeviceAddr)) { if (const auto *details{symbol->detailsIf()}) { hostAssocSym = &details->symbol(); } } static Symbol::Flag dataMappingAttributeFlags[] = {// Symbol::Flag::OmpMapTo, Symbol::Flag::OmpMapFrom, Symbol::Flag::OmpMapToFrom, Symbol::Flag::OmpMapStorage, Symbol::Flag::OmpMapDelete, Symbol::Flag::OmpIsDevicePtr, Symbol::Flag::OmpHasDeviceAddr}; static Symbol::Flag dataSharingAttributeFlags[] = {// Symbol::Flag::OmpPrivate, Symbol::Flag::OmpFirstPrivate, Symbol::Flag::OmpLastPrivate, Symbol::Flag::OmpShared, Symbol::Flag::OmpLinear}; // For OMP TARGET TEAMS directive some sharing attribute // flags and mapping attribute flags can co-exist. if (!llvm::omp::allTeamsSet.test(directive) && !llvm::omp::allParallelSet.test(directive)) { for (Symbol::Flag ompFlag1 : dataMappingAttributeFlags) { for (Symbol::Flag ompFlag2 : dataSharingAttributeFlags) { if ((hostAssocSym->test(ompFlag2) && hostAssocSym->test(Symbol::Flag::OmpExplicit)) || (symbol->test(ompFlag2) && symbol->test(Symbol::Flag::OmpExplicit))) { checkExclusivelists(hostAssocSym, ompFlag1, symbol, ompFlag2); } } } } } } } void OmpAttributeVisitor::PropagateOmpFlagToEquivalenceSet( const Symbol &symbol, Symbol::Flag ompFlag) { // Find the equivalence set containing this symbol if (const EquivalenceSet *eqSet{FindEquivalenceSet(symbol)}) { // Propagate the flag to all symbols in the equivalence set for (const EquivalenceObject &eqObj : *eqSet) { Symbol &eqSymbol{eqObj.symbol}; // Skip the symbol itself (already has the flag) if (&eqSymbol == &symbol) { continue; } // Set the OpenMP flag on the equivalenced symbol if (Symbol * resolvedSymbol{ResolveOmp(eqSymbol, ompFlag, currScope())}) { // Also add to the context if needed AddToContextObjectWithExplicitDSA(*resolvedSymbol, ompFlag); } } } } void OmpAttributeVisitor::ResolveOmpCommonBlock( const parser::Name &name, Symbol::Flag ompFlag) { if (auto *symbol{ResolveOmpCommonBlockName(&name)}) { if (!dataCopyingAttributeFlags.test(ompFlag)) { CheckMultipleAppearances(name, *symbol, Symbol::Flag::OmpCommonBlock); } // 2.15.3 When a named common block appears in a list, it has the // same meaning as if every explicit member of the common block // appeared in the list auto &details{symbol->get()}; for (auto [index, object] : llvm::enumerate(details.objects())) { if (auto *resolvedObject{ResolveOmp(*object, ompFlag, currScope())}) { if (dataCopyingAttributeFlags.test(ompFlag)) { CheckDataCopyingClause(name, *resolvedObject, ompFlag); } else { AddToContextObjectWithExplicitDSA(*resolvedObject, ompFlag); } details.replace_object(*resolvedObject, index); // Propagate the flag to symbols in the equivalence set if (ompFlag == Symbol::Flag::OmpThreadprivate) { PropagateOmpFlagToEquivalenceSet(*resolvedObject, ompFlag); } } } } else { context_.Say(name.source, // 2.15.3 "COMMON block must be declared in the same scoping unit in which the OpenMP directive or clause appears"_err_en_US); } } void OmpAttributeVisitor::ResolveOmpObject( const parser::OmpObject &ompObject, Symbol::Flag ompFlag) { common::visit( // common::visitors{ [&](const parser::Designator &designator) { ResolveOmpDesignator(designator, ompFlag); }, [&](const parser::Name &name) { // common block ResolveOmpCommonBlock(name, ompFlag); }, [&](const parser::OmpObject::Invalid &invalid) { switch (invalid.v) { SWITCH_COVERS_ALL_CASES case parser::OmpObject::Invalid::Kind::BlankCommonBlock: context_.Say(invalid.source, "Blank common blocks are not allowed as directive or clause arguments"_err_en_US); break; } }, }, ompObject.u); } Symbol *OmpAttributeVisitor::ResolveOmp( const parser::Name &name, Symbol::Flag ompFlag, Scope &scope) { if (ompFlagsRequireNewSymbol.test(ompFlag)) { return DeclareAccessEntity(name, ompFlag, scope); } else { return DeclareOrMarkOtherAccessEntity(name, ompFlag); } } Symbol *OmpAttributeVisitor::ResolveOmp( Symbol &symbol, Symbol::Flag ompFlag, Scope &scope) { if (ompFlagsRequireNewSymbol.test(ompFlag)) { return DeclareAccessEntity(symbol, ompFlag, scope); } else { return DeclareOrMarkOtherAccessEntity(symbol, ompFlag); } } Symbol *OmpAttributeVisitor::DeclareOrMarkOtherAccessEntity( const parser::Name &name, Symbol::Flag ompFlag) { Symbol *prev{currScope().FindSymbol(name.source)}; if (!name.symbol || !prev) { return nullptr; } else if (prev != name.symbol) { name.symbol = prev; } return DeclareOrMarkOtherAccessEntity(*prev, ompFlag); } Symbol *OmpAttributeVisitor::DeclareOrMarkOtherAccessEntity( Symbol &object, Symbol::Flag ompFlag) { object.set(ompFlag); return &object; } static bool WithMultipleAppearancesOmpException( const Symbol &symbol, Symbol::Flag flag) { return (flag == Symbol::Flag::OmpFirstPrivate && symbol.test(Symbol::Flag::OmpLastPrivate)) || (flag == Symbol::Flag::OmpLastPrivate && symbol.test(Symbol::Flag::OmpFirstPrivate)); } void OmpAttributeVisitor::CheckMultipleAppearances( const parser::Name &name, const Symbol &symbol, Symbol::Flag ompFlag) { const auto *target{&symbol}; if (ompFlagsRequireNewSymbol.test(ompFlag)) { if (const auto *details{symbol.detailsIf()}) { target = &details->symbol(); } } if (HasDataSharingAttributeObject(target->GetUltimate()) && !WithMultipleAppearancesOmpException(symbol, ompFlag)) { context_.Say(name.source, "'%s' appears in more than one data-sharing clause " "on the same OpenMP directive"_err_en_US, name.ToString()); } else { AddDataSharingAttributeObject(target->GetUltimate()); } } static bool IsSymbolThreadprivate(const Symbol &symbol) { const Symbol &ultimate{symbol.GetUltimate()}; return ultimate.test(Symbol::Flag::OmpThreadprivate); } static bool IsSymbolPrivate(const Symbol &symbol) { LLVM_DEBUG(llvm::dbgs() << "IsSymbolPrivate(" << symbol.name() << "):\n"); LLVM_DEBUG(dbg::DumpAssocSymbols(llvm::dbgs(), symbol)); if (Symbol::Flags dsa{GetSymbolDSA(symbol)}; dsa.any()) { if (dsa.test(Symbol::Flag::OmpShared)) { return false; } return true; } // A symbol that has not gone through constructs that may privatize the // original symbol may be predetermined as private. // (OMP 5.2 5.1.1 - Variables Referenced in a Construct) if (symbol == symbol.GetUltimate()) { switch (symbol.owner().kind()) { case Scope::Kind::MainProgram: case Scope::Kind::Subprogram: case Scope::Kind::BlockConstruct: return !symbol.attrs().test(Attr::SAVE) && !symbol.attrs().test(Attr::PARAMETER) && !IsAssumedShape(symbol) && !symbol.flags().test(Symbol::Flag::InCommonBlock); default: return false; } } return false; } void OmpAttributeVisitor::CheckDataCopyingClause( const parser::Name &name, const Symbol &symbol, Symbol::Flag ompFlag) { if (ompFlag == Symbol::Flag::OmpCopyIn) { // List of items/objects that can appear in a 'copyin' clause must be // 'threadprivate' if (!IsSymbolThreadprivate(symbol)) { context_.Say(name.source, "Non-THREADPRIVATE object '%s' in COPYIN clause"_err_en_US, symbol.name()); } } else if (ompFlag == Symbol::Flag::OmpCopyPrivate && GetContext().directive == llvm::omp::Directive::OMPD_single) { // A list item that appears in a 'copyprivate' clause may not appear on a // 'private' or 'firstprivate' clause on a single construct if (IsObjectWithDSA(symbol) && (symbol.test(Symbol::Flag::OmpPrivate) || symbol.test(Symbol::Flag::OmpFirstPrivate))) { context_.Say(name.source, "COPYPRIVATE variable '%s' may not appear on a PRIVATE or " "FIRSTPRIVATE clause on a SINGLE construct"_err_en_US, symbol.name()); } else if (!IsSymbolThreadprivate(symbol) && !IsSymbolPrivate(symbol)) { // List of items/objects that can appear in a 'copyprivate' clause must be // either 'private' or 'threadprivate' in enclosing context. context_.Say(name.source, "COPYPRIVATE variable '%s' is not PRIVATE or THREADPRIVATE in " "outer context"_err_en_US, symbol.name()); } } } void OmpAttributeVisitor::CheckObjectIsPrivatizable( const parser::Name &name, const Symbol &symbol, Symbol::Flag ompFlag) { const auto &ultimateSymbol{symbol.GetUltimate()}; llvm::StringRef clauseName{"PRIVATE"}; if (ompFlag == Symbol::Flag::OmpFirstPrivate) { clauseName = "FIRSTPRIVATE"; } else if (ompFlag == Symbol::Flag::OmpLastPrivate) { clauseName = "LASTPRIVATE"; } if (SymbolOrEquivalentIsInNamelist(symbol)) { context_.Say(name.source, "Variable '%s' in NAMELIST cannot be in a %s clause"_err_en_US, name.ToString(), clauseName.str()); } if (ultimateSymbol.has()) { context_.Say(name.source, "Variable '%s' in ASSOCIATE cannot be in a %s clause"_err_en_US, name.ToString(), clauseName.str()); } if (stmtFunctionExprSymbols_.find(ultimateSymbol) != stmtFunctionExprSymbols_.end()) { context_.Say(name.source, "Variable '%s' in statement function expression cannot be in a " "%s clause"_err_en_US, name.ToString(), clauseName.str()); } } void OmpAttributeVisitor::AddOmpRequiresToScope(Scope &scope, const WithOmpDeclarative::OmpClauseSet &reqs, const std::optional &memOrder) { unsigned version{context_.langOptions().OpenMPVersion}; const Scope &programUnit{omp::GetProgramUnit(scope)}; if (auto *symbol{const_cast(programUnit.symbol())}) { common::visit( [&](auto &details) { if constexpr (std::is_convertible_v) { if (reqs.any()) { details.set_ompRequires(reqs | details.ompRequires()); details.set_version(version); } if (memOrder) { if (auto &admo{details.ompAtomicDefaultMemOrder()}; admo && *admo != *memOrder) { context_.Say(programUnit.sourceRange(), "Conflicting '%s' REQUIRES clauses found in compilation unit"_err_en_US, parser::omp::GetUpperName( llvm::omp::Clause::OMPC_atomic_default_mem_order, version)); } details.set_ompAtomicDefaultMemOrder(*memOrder); } } }, symbol->details()); } } #ifndef NDEBUG static llvm::raw_ostream &operator<<( llvm::raw_ostream &os, const Symbol::Flags &flags) { flags.Dump(os, Symbol::EnumToString); return os; } namespace dbg { static llvm::raw_ostream &operator<<( llvm::raw_ostream &os, std::optional srcPos) { if (srcPos) { os << *srcPos.value().path << ":" << srcPos.value().line << ": "; } return os; } static std::optional GetSourcePosition( const Fortran::semantics::Scope &scope, const Fortran::parser::CharBlock &src) { parser::AllCookedSources &allCookedSources{ scope.context().allCookedSources()}; if (std::optional prange{ allCookedSources.GetProvenanceRange(src)}) { return allCookedSources.allSources().GetSourcePosition(prange->start()); } return std::nullopt; } // Returns a string containing the source location of `scope` followed by // its first source line. static std::string ScopeSourcePos(const Fortran::semantics::Scope &scope) { const parser::CharBlock &sourceRange{scope.sourceRange()}; std::string src{sourceRange.ToString()}; size_t nl{src.find('\n')}; std::string str; llvm::raw_string_ostream ss{str}; ss << GetSourcePosition(scope, sourceRange) << src.substr(0, nl); return str; } static void DumpAssocSymbols(llvm::raw_ostream &os, const Symbol &sym) { os << '\t' << sym << '\n'; os << "\t\tOwner: " << ScopeSourcePos(sym.owner()) << '\n'; if (const auto *details{sym.detailsIf()}) { DumpAssocSymbols(os, details->symbol()); } } } // namespace dbg #endif } // namespace Fortran::semantics