//===----------------------------------------------------------------------===// // // 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/parse-tree-visitor.h" #include "flang/Parser/parse-tree.h" #include "flang/Parser/tools.h" #include "flang/Semantics/expression.h" #include "flang/Semantics/openmp-modifiers.h" #include "flang/Semantics/symbol.h" #include "flang/Semantics/tools.h" #include #include #include template static Fortran::semantics::Scope *GetScope( Fortran::semantics::SemanticsContext &context, const T &x) { std::optional source{GetLastSource(x)}; return source ? &context.FindScope(*source) : nullptr; } namespace Fortran::semantics { 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; bool withinConstruct{false}; std::int64_t associatedLoopLevel{0}; }; DirContext &GetContext() { CHECK(!dirContext_.empty()); return dirContext_.back(); } std::optional GetContextIf() { return dirContext_.empty() ? std::nullopt : std::make_optional(dirContext_.back()); } void PushContext(const parser::CharBlock &source, T dir, Scope &scope) { dirContext_.emplace_back(source, dir, scope); } void PushContext(const parser::CharBlock &source, T dir) { dirContext_.emplace_back(source, dir, context_.FindScope(source)); } void PopContext() { dirContext_.pop_back(); } void SetContextDirectiveSource(parser::CharBlock &dir) { GetContext().directiveSource = dir; } Scope &currScope() { return GetContext().scope; } 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) { auto it{GetContext().objectWithDSA.find(&symbol)}; return it != GetContext().objectWithDSA.end(); } 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 &); const parser::Name *GetLoopIndex(const parser::DoConstruct &); const parser::DoConstruct *GetDoConstructIf( const parser::ExecutionPartConstruct &); Symbol *DeclareNewPrivateAccessEntity(const Symbol &, Symbol::Flag, Scope &); Symbol *DeclarePrivateAccessEntity( const parser::Name &, Symbol::Flag, Scope &); Symbol *DeclarePrivateAccessEntity(Symbol &, Symbol::Flag, Scope &); Symbol *DeclareOrMarkOtherAccessEntity(const parser::Name &, Symbol::Flag); 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(); } 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; } bool Pre(const parser::OpenACCStandaloneConstruct &); void Post(const parser::OpenACCStandaloneConstruct &) { PopContext(); } void Post(const parser::AccStandaloneDirective &) { GetContext().withinConstruct = true; } 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; } void Post(const parser::Name &); private: std::int64_t GetAssociatedLoopLevelFromClauses(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 &); 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 &, bool parentScope = false); 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); 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 &); Scope *topScope_; }; // Data-sharing and Data-mapping attributes for data-refs in OpenMP construct class OmpAttributeVisitor : DirectiveAttributeVisitor { public: explicit OmpAttributeVisitor(SemanticsContext &context) : DirectiveAttributeVisitor(context) {} template void Walk(const A &x) { parser::Walk(x, *this); } template bool Pre(const A &) { return true; } template void Post(const A &) {} template bool Pre(const parser::Statement &statement) { currentStatementSource_ = statement.source; // Keep track of the labels in all the labelled statements if (statement.label) { auto label{statement.label.value()}; // Get the context to check if the labelled statement is in an // enclosing OpenMP construct std::optional thisContext{GetContextIf()}; targetLabels_.emplace( label, std::make_pair(currentStatementSource_, thisContext)); // Check if a statement that causes a jump to the 'label' // has already been encountered auto range{sourceLabels_.equal_range(label)}; for (auto it{range.first}; it != range.second; ++it) { // Check if both the statement with 'label' and the statement that // causes a jump to the 'label' are in the same scope CheckLabelContext(it->second.first, currentStatementSource_, it->second.second, thisContext); } } return true; } bool Pre(const parser::InternalSubprogram &) { // Clear the labels being tracked in the previous scope ClearLabels(); return true; } bool Pre(const parser::ModuleSubprogram &) { // Clear the labels being tracked in the previous scope ClearLabels(); return true; } 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::OmpDirectiveSpecification &x) { PushContext(x.source, std::get(x.t)); return true; } void Post(const parser::OmpDirectiveSpecification &) { PopContext(); } bool Pre(const parser::OmpMetadirectiveDirective &x) { PushContext(x.source, llvm::omp::Directive::OMPD_metadirective); return true; } void Post(const parser::OmpMetadirectiveDirective &) { PopContext(); } bool Pre(const parser::OpenMPBlockConstruct &); void Post(const parser::OpenMPBlockConstruct &); void Post(const parser::OmpBeginBlockDirective &) { GetContext().withinConstruct = true; } bool Pre(const parser::OpenMPSimpleStandaloneConstruct &); void Post(const parser::OpenMPSimpleStandaloneConstruct &) { PopContext(); } bool Pre(const parser::OpenMPLoopConstruct &); void Post(const parser::OpenMPLoopConstruct &) { PopContext(); } void Post(const parser::OmpBeginLoopDirective &) { GetContext().withinConstruct = true; } bool Pre(const parser::DoConstruct &); bool Pre(const parser::OpenMPSectionsConstruct &); void Post(const parser::OpenMPSectionsConstruct &) { PopContext(); } bool Pre(const parser::OpenMPCriticalConstruct &critical); void Post(const parser::OpenMPCriticalConstruct &) { PopContext(); } bool Pre(const parser::OpenMPDeclareSimdConstruct &x) { PushContext(x.source, llvm::omp::Directive::OMPD_declare_simd); const auto &name{std::get>(x.t)}; if (name) { ResolveOmpName(*name, Symbol::Flag::OmpDeclareSimd); } return true; } void Post(const parser::OpenMPDeclareSimdConstruct &) { PopContext(); } bool Pre(const parser::OpenMPDepobjConstruct &x) { PushContext(x.source, llvm::omp::Directive::OMPD_depobj); auto &object{std::get(x.t)}; ResolveOmpObject(object, Symbol::Flag::OmpDependObject); return true; } void Post(const parser::OpenMPDepobjConstruct &) { PopContext(); } bool Pre(const parser::OpenMPRequiresConstruct &x) { using Flags = WithOmpDeclarative::RequiresFlags; using Requires = WithOmpDeclarative::RequiresFlag; PushContext(x.source, llvm::omp::Directive::OMPD_requires); // Gather information from the clauses. Flags flags; std::optional memOrder; for (const auto &clause : std::get(x.t).v) { flags |= common::visit( common::visitors{ [&memOrder]( const parser::OmpClause::AtomicDefaultMemOrder &atomic) { memOrder = atomic.v.v; return Flags{}; }, [](const parser::OmpClause::ReverseOffload &) { return Flags{Requires::ReverseOffload}; }, [](const parser::OmpClause::UnifiedAddress &) { return Flags{Requires::UnifiedAddress}; }, [](const parser::OmpClause::UnifiedSharedMemory &) { return Flags{Requires::UnifiedSharedMemory}; }, [](const parser::OmpClause::DynamicAllocators &) { return Flags{Requires::DynamicAllocators}; }, [](const auto &) { return Flags{}; }}, clause.u); } // Merge clauses into parents' symbols details. AddOmpRequiresToScope(currScope(), flags, memOrder); return true; } void Post(const parser::OpenMPRequiresConstruct &) { PopContext(); } bool Pre(const parser::OpenMPDeclareTargetConstruct &); void Post(const parser::OpenMPDeclareTargetConstruct &) { PopContext(); } bool Pre(const parser::OpenMPDeclareMapperConstruct &); void Post(const parser::OpenMPDeclareMapperConstruct &) { PopContext(); } bool Pre(const parser::OpenMPThreadprivate &); void Post(const parser::OpenMPThreadprivate &) { PopContext(); } bool Pre(const parser::OpenMPDeclarativeAllocate &); void Post(const parser::OpenMPDeclarativeAllocate &) { PopContext(); } bool Pre(const parser::OpenMPDispatchConstruct &); void Post(const parser::OpenMPDispatchConstruct &) { PopContext(); } bool Pre(const parser::OpenMPExecutableAllocate &); void Post(const parser::OpenMPExecutableAllocate &); bool Pre(const parser::OpenMPAllocatorsConstruct &); void Post(const parser::OpenMPAllocatorsConstruct &); void Post(const parser::OmpObjectList &x) { // The objects from OMP clauses should have already been resolved, // except common blocks (the ResolveNamesVisitor does not visit // parser::Name, those are dealt with as members of other structures). // Iterate over elements of x, and resolve any common blocks that // are still unresolved. for (const parser::OmpObject &obj : x.v) { auto *name{std::get_if(&obj.u)}; if (name && !name->symbol) { Resolve(*name, currScope().MakeCommonBlock(name->source)); } } } // 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::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) { const auto &objectList{std::get(x.t)}; ResolveOmpObjectList(objectList, 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) { const auto &objList{std::get(x.v.t)}; ResolveOmpObjectList(objList, 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) { auto &objects{std::get(x.t)}; ResolveOmpObjectList(objects, Symbol::Flag::OmpLinear); return false; } bool Pre(const parser::OmpClause::Reduction &x) { const auto &objList{std::get(x.v.t)}; 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) { const auto &alignedNameList{std::get(x.t)}; ResolveOmpObjectList(alignedNameList, Symbol::Flag::OmpAligned); return false; } bool Pre(const parser::OmpClause::Nontemporal &x) { const auto &nontemporalNameList{x.v}; ResolveOmpNameList(nontemporalNameList, 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 &); // Keep track of labels in the statements that causes jumps to target labels void Post(const parser::GotoStmt &gotoStmt) { CheckSourceLabel(gotoStmt.v); } void Post(const parser::ComputedGotoStmt &computedGotoStmt) { for (auto &label : std::get>(computedGotoStmt.t)) { CheckSourceLabel(label); } } void Post(const parser::ArithmeticIfStmt &arithmeticIfStmt) { CheckSourceLabel(std::get<1>(arithmeticIfStmt.t)); CheckSourceLabel(std::get<2>(arithmeticIfStmt.t)); CheckSourceLabel(std::get<3>(arithmeticIfStmt.t)); } void Post(const parser::AssignedGotoStmt &assignedGotoStmt) { for (auto &label : std::get>(assignedGotoStmt.t)) { CheckSourceLabel(label); } } void Post(const parser::AltReturnSpec &altReturnSpec) { CheckSourceLabel(altReturnSpec.v); } void Post(const parser::ErrLabel &errLabel) { CheckSourceLabel(errLabel.v); } void Post(const parser::EndLabel &endLabel) { CheckSourceLabel(endLabel.v); } void Post(const parser::EorLabel &eorLabel) { CheckSourceLabel(eorLabel.v); } void Post(const parser::OmpMapClause &x) { Symbol::Flag ompFlag = Symbol::Flag::OmpMapToFrom; 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: ompFlag = Symbol::Flag::OmpMapAlloc; break; case parser::OmpMapType::Value::Release: ompFlag = Symbol::Flag::OmpMapRelease; break; case parser::OmpMapType::Value::Delete: ompFlag = Symbol::Flag::OmpMapDelete; break; } } const auto &ompObjList{std::get(x.t)}; for (const auto &ompObj : ompObjList.v) { common::visit( common::visitors{ [&](const parser::Designator &designator) { if (const auto *name{ semantics::getDesignatorNameIfDataRef(designator)}) { if (name->symbol) { name->symbol->set(ompFlag); AddToContextObjectWithDSA(*name->symbol, ompFlag); } if (name->symbol && semantics::IsAssumedSizeArray(*name->symbol)) { context_.Say(designator.source, "Assumed-size whole arrays may not appear on the %s " "clause"_err_en_US, "MAP"); } } }, [&](const auto &name) {}, }, ompObj.u); ResolveOmpObject(ompObj, ompFlag); } } const parser::OmpClause *associatedClause{nullptr}; void SetAssociatedClause(const parser::OmpClause &c) { associatedClause = &c; } const parser::OmpClause *GetAssociatedClause() { return associatedClause; } private: std::int64_t GetAssociatedLoopLevelFromClauses(const parser::OmpClauseList &); Symbol::Flags dataSharingAttributeFlags{Symbol::Flag::OmpShared, Symbol::Flag::OmpPrivate, Symbol::Flag::OmpFirstPrivate, Symbol::Flag::OmpLastPrivate, Symbol::Flag::OmpReduction, Symbol::Flag::OmpLinear}; 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::OmpReduction, Symbol::Flag::OmpCriticalLock, Symbol::Flag::OmpCopyIn, Symbol::Flag::OmpUseDevicePtr, Symbol::Flag::OmpUseDeviceAddr, Symbol::Flag::OmpIsDevicePtr, Symbol::Flag::OmpHasDeviceAddr}; Symbol::Flags ompFlagsRequireMark{Symbol::Flag::OmpThreadprivate, Symbol::Flag::OmpDeclareTarget, Symbol::Flag::OmpExclusiveScan, Symbol::Flag::OmpInclusiveScan, Symbol::Flag::OmpInScanReduction}; Symbol::Flags dataCopyingAttributeFlags{ Symbol::Flag::OmpCopyIn, Symbol::Flag::OmpCopyPrivate}; std::vector allocateNames_; // on one directive UnorderedSymbolSet privateDataSharingAttributeObjects_; // on one directive UnorderedSymbolSet stmtFunctionExprSymbols_; std::multimap>> sourceLabels_; std::map>> targetLabels_; parser::CharBlock currentStatementSource_; void AddAllocateName(const parser::Name *&object) { allocateNames_.push_back(object); } void ClearAllocateNames() { allocateNames_.clear(); } void AddPrivateDataSharingAttributeObjects(SymbolRef object) { privateDataSharingAttributeObjects_.insert(object); } void ClearPrivateDataSharingAttributeObjects() { privateDataSharingAttributeObjects_.clear(); } // Predetermined DSA rules void PrivatizeAssociatedLoopIndexAndCheckLoopLevel( const parser::OpenMPLoopConstruct &); void ResolveSeqLoopIndexInParallelOrTaskConstruct(const parser::Name &); bool IsNestedInDirective(llvm::omp::Directive directive); void ResolveOmpObjectList(const parser::OmpObjectList &, Symbol::Flag); 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); Symbol *ResolveName(const parser::Name *); Symbol *ResolveOmpObjectScope(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 CheckAssocLoopLevel(std::int64_t level, const parser::OmpClause *clause); void CheckObjectIsPrivatizable( const parser::Name &, const Symbol &, Symbol::Flag); void CheckSourceLabel(const parser::Label &); void CheckLabelContext(const parser::CharBlock, const parser::CharBlock, std::optional, std::optional); void ClearLabels() { sourceLabels_.clear(); targetLabels_.clear(); }; void CheckAllNamesInAllocateStmt(const parser::CharBlock &source, const parser::OmpObjectList &ompObjectList, const parser::AllocateStmt &allocate); void CheckNameInAllocateStmt(const parser::CharBlock &source, const parser::Name &ompObject, const parser::AllocateStmt &allocate); std::int64_t ordCollapseLevel{0}; void AddOmpRequiresToScope(Scope &, WithOmpDeclarative::RequiresFlags, std::optional); void IssueNonConformanceWarning( llvm::omp::Directive D, parser::CharBlock source); void CreateImplicitSymbols( const Symbol *symbol, std::optional setFlag = std::nullopt); }; template bool DirectiveAttributeVisitor::HasDataSharingAttributeObject( const Symbol &object) { auto it{dataSharingAttributeObjects_.find(object)}; return it != dataSharingAttributeObjects_.end(); } template const parser::Name *DirectiveAttributeVisitor::GetLoopIndex( const parser::DoConstruct &x) { using Bounds = parser::LoopControl::Bounds; if (x.GetLoopControl()) { if (const Bounds * b{std::get_if(&x.GetLoopControl()->u)}) { return &b->name.thing; } else { return 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; } } template const parser::DoConstruct *DirectiveAttributeVisitor::GetDoConstructIf( const parser::ExecutionPartConstruct &x) { return parser::Unwrap(x); } template Symbol *DirectiveAttributeVisitor::DeclareNewPrivateAccessEntity( 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::DeclarePrivateAccessEntity( const parser::Name &name, Symbol::Flag flag, Scope &scope) { if (!name.symbol) { return nullptr; // not resolved by Name Resolution step, do nothing } name.symbol = DeclarePrivateAccessEntity(*name.symbol, flag, scope); return name.symbol; } template Symbol *DirectiveAttributeVisitor::DeclarePrivateAccessEntity( Symbol &object, Symbol::Flag flag, Scope &scope) { if (object.owner() != currScope()) { return DeclareNewPrivateAccessEntity(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(); 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 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); 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, bool parentScope) { Symbol *prev{currScope().FindSymbol(name.source)}; // Check in parent scope if asked for. if (!prev && parentScope) { prev = currScope().parent().FindSymbol(name.source); } if (prev != name.symbol) { name.symbol = prev; } return prev; } Symbol *AccAttributeVisitor::ResolveFctName(const parser::Name &name) { Symbol *prev{currScope().FindSymbol(name.source)}; if (!prev || (prev && prev->IsFuncResult())) { prev = currScope().parent().FindSymbol(name.source); if (!prev) { prev = &context_.globalScope().MakeSymbol( name.source, Attrs{}, ProcEntityDetails{}); } } if (prev != name.symbol) { 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()) { Fortran::semantics::OpenACCRoutineInfo info; const auto &clauses = std::get(x.t); for (const Fortran::parser::AccClause &clause : clauses.v) { if (std::get_if(&clause.u)) { if (info.deviceTypeInfos().empty()) { info.set_isSeq(); } else { info.deviceTypeInfos().back().set_isSeq(); } } else if (const auto *gangClause = std::get_if(&clause.u)) { if (info.deviceTypeInfos().empty()) { info.set_isGang(); } else { info.deviceTypeInfos().back().set_isGang(); } if (gangClause->v) { const Fortran::parser::AccGangArgList &x = *gangClause->v; for (const Fortran::parser::AccGangArg &gangArg : x.v) { if (const auto *dim = std::get_if(&gangArg.u)) { if (const auto v{EvaluateInt64(context_, dim->v)}) { if (info.deviceTypeInfos().empty()) { info.set_gangDim(*v); } else { info.deviceTypeInfos().back().set_gangDim(*v); } } } } } } else if (std::get_if(&clause.u)) { if (info.deviceTypeInfos().empty()) { info.set_isVector(); } else { info.deviceTypeInfos().back().set_isVector(); } } else if (std::get_if(&clause.u)) { if (info.deviceTypeInfos().empty()) { info.set_isWorker(); } else { info.deviceTypeInfos().back().set_isWorker(); } } else if (std::get_if(&clause.u)) { info.set_isNohost(); } 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)) { if (info.deviceTypeInfos().empty()) { info.set_bindName(sym->name().ToString()); } else { info.deviceTypeInfos().back().set_bindName( sym->name().ToString()); } } 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)}; std::stringstream bindName; bindName << "\"" << str << "\""; if (info.deviceTypeInfos().empty()) { info.set_bindName(bindName.str()); } else { info.deviceTypeInfos().back().set_bindName(bindName.str()); } } } else if (const auto *dType = std::get_if( &clause.u)) { const parser::AccDeviceTypeExprList &deviceTypeExprList = dType->v; OpenACCRoutineDeviceTypeInfo dtypeInfo; dtypeInfo.set_dType(deviceTypeExprList.v.front().v); info.add_deviceTypeInfo(dtypeInfo); } } 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 &optName{std::get>(x.t)}; if (optName) { if (Symbol *sym = ResolveFctName(*optName)) { Symbol &ultimate{sym->GetUltimate()}; AddRoutineInfoToSymbol(ultimate, x); } else { context_.Say((*optName).source, "No function or subroutine declared for '%s'"_err_en_US, (*optName).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(combinedDir.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); 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::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; } 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) { std::int64_t level{GetContext().associatedLoopLevel}; if (level <= 0) { // collapse value was negative or 0 return; } const auto getNextDoConstruct = [this](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 { 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; using Bounds = parser::LoopControl::Bounds; for (const parser::DoConstruct *loop{&outerDoConstruct}; loop && level > 0;) { // Go through all nested loops to ensure index variable exists. if (const parser::Name * ivName{GetLoopIndex(*loop)}) { if (auto *symbol{ResolveAcc(*ivName, flag, currScope())}) { if (auto &control{loop->GetLoopControl()}) { if (const Bounds * b{std::get_if(&control->u)}) { if (auto lowerExpr{semantics::AnalyzeExpr(context_, b->lower)}) { semantics::UnorderedSymbolSet lowerSyms = evaluate::CollectSymbols(*lowerExpr); checkExprHasSymbols(ivs, lowerSyms); } if (auto upperExpr{semantics::AnalyzeExpr(context_, b->upper)}) { semantics::UnorderedSymbolSet upperSyms = evaluate::CollectSymbols(*upperExpr); checkExprHasSymbols(ivs, upperSyms); } } } ivs.push_back(symbol); } } const auto &block{std::get(loop->t)}; --level; loop = getNextDoConstruct(block, level); } CHECK(level == 0); } 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; } } } // For OpenACC constructs, check all the data-refs within the constructs // and adjust the symbol for each Name if necessary void AccAttributeVisitor::Post(const parser::Name &name) { auto *symbol{name.symbol}; if (symbol && !dirContext_.empty() && GetContext().withinConstruct) { if (!symbol->owner().IsDerivedType() && !symbol->has() && !symbol->has() && !IsObjectWithDSA(*symbol)) { if (Symbol * found{currScope().FindSymbol(name.source)}) { if (symbol != found) { name.symbol = found; // adjust the symbol within region } else if (GetContext().defaultDSA == Symbol::Flag::AccNone) { // 2.5.14. context_.Say(name.source, "The DEFAULT(NONE) clause requires that '%s' must be listed in " "a data-mapping clause"_err_en_US, symbol->name()); } } } } // within OpenACC construct } Symbol *AccAttributeVisitor::ResolveAccCommonBlockName( const parser::Name *name) { if (auto *prev{name ? GetContext().scope.parent().FindCommonBlock(name->source) : nullptr}) { name->symbol = prev; return prev; } // Check if the Common Block is declared in the current scope if (auto *commonBlockSymbol{ name ? GetContext().scope.FindCommonBlock(name->source) : nullptr}) { name->symbol = commonBlockSymbol; return commonBlockSymbol; } return nullptr; } 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{ semantics::getDesignatorNameIfDataRef(designator)}) { if (auto *symbol{ResolveAcc(*name, accFlag, currScope())}) { AddToContextObjectWithDSA(*symbol, accFlag); if (dataSharingAttributeFlags.test(accFlag)) { CheckMultipleAppearances(*name, *symbol, accFlag); } } } 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, "COMMON block must be declared in the same scoping unit " "in which the OpenACC directive or clause appears"_err_en_US); } }, }, 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) { Symbol *prev{currScope().FindSymbol(name.source)}; if (!name.symbol || !prev) { return nullptr; } else if (prev != name.symbol) { name.symbol = prev; } return DeclareOrMarkOtherAccessEntity(*prev, accFlag); } 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; } static bool WithMultipleAppearancesAccException( const Symbol &symbol, Symbol::Flag flag) { return false; // Place holder } void AccAttributeVisitor::CheckMultipleAppearances( const parser::Name &name, const Symbol &symbol, Symbol::Flag accFlag) { const auto *target{&symbol}; if (HasDataSharingAttributeObject(*target) && !WithMultipleAppearancesAccException(symbol, accFlag)) { 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); } } bool OmpAttributeVisitor::Pre(const parser::OpenMPBlockConstruct &x) { const auto &beginBlockDir{std::get(x.t)}; const auto &beginDir{std::get(beginBlockDir.t)}; switch (beginDir.v) { case llvm::omp::Directive::OMPD_masked: case llvm::omp::Directive::OMPD_parallel_masked: case llvm::omp::Directive::OMPD_master: case llvm::omp::Directive::OMPD_parallel_master: case llvm::omp::Directive::OMPD_ordered: case llvm::omp::Directive::OMPD_parallel: case llvm::omp::Directive::OMPD_scope: case llvm::omp::Directive::OMPD_single: case llvm::omp::Directive::OMPD_target: case llvm::omp::Directive::OMPD_target_data: case llvm::omp::Directive::OMPD_task: case llvm::omp::Directive::OMPD_taskgroup: case llvm::omp::Directive::OMPD_teams: case llvm::omp::Directive::OMPD_workshare: case llvm::omp::Directive::OMPD_parallel_workshare: case llvm::omp::Directive::OMPD_target_teams: case llvm::omp::Directive::OMPD_target_parallel: PushContext(beginDir.source, beginDir.v); break; default: // TODO others break; } if (beginDir.v == llvm::omp::Directive::OMPD_master || beginDir.v == llvm::omp::Directive::OMPD_parallel_master) IssueNonConformanceWarning(beginDir.v, beginDir.source); ClearDataSharingAttributeObjects(); ClearPrivateDataSharingAttributeObjects(); ClearAllocateNames(); return true; } void OmpAttributeVisitor::Post(const parser::OpenMPBlockConstruct &x) { const auto &beginBlockDir{std::get(x.t)}; const auto &beginDir{std::get(beginBlockDir.t)}; switch (beginDir.v) { case llvm::omp::Directive::OMPD_masked: case llvm::omp::Directive::OMPD_master: case llvm::omp::Directive::OMPD_parallel_masked: case llvm::omp::Directive::OMPD_parallel_master: case llvm::omp::Directive::OMPD_parallel: case llvm::omp::Directive::OMPD_scope: case llvm::omp::Directive::OMPD_single: case llvm::omp::Directive::OMPD_target: case llvm::omp::Directive::OMPD_task: case llvm::omp::Directive::OMPD_teams: case llvm::omp::Directive::OMPD_parallel_workshare: case llvm::omp::Directive::OMPD_target_teams: case llvm::omp::Directive::OMPD_target_parallel: { bool hasPrivate; for (const auto *allocName : allocateNames_) { hasPrivate = false; for (auto privateObj : privateDataSharingAttributeObjects_) { const Symbol &symbolPrivate{*privateObj}; if (allocName->source == symbolPrivate.name()) { hasPrivate = true; break; } } if (!hasPrivate) { context_.Say(allocName->source, "The ALLOCATE clause requires that '%s' must be listed in a " "private " "data-sharing attribute clause on the same directive"_err_en_US, allocName->ToString()); } } break; } default: break; } PopContext(); } bool OmpAttributeVisitor::Pre( const parser::OpenMPSimpleStandaloneConstruct &x) { const auto &standaloneDir{ std::get(x.t)}; switch (standaloneDir.v) { case llvm::omp::Directive::OMPD_barrier: case llvm::omp::Directive::OMPD_ordered: case llvm::omp::Directive::OMPD_scan: case llvm::omp::Directive::OMPD_target_enter_data: case llvm::omp::Directive::OMPD_target_exit_data: case llvm::omp::Directive::OMPD_target_update: case llvm::omp::Directive::OMPD_taskwait: case llvm::omp::Directive::OMPD_taskyield: PushContext(standaloneDir.source, standaloneDir.v); break; default: break; } ClearDataSharingAttributeObjects(); return true; } bool OmpAttributeVisitor::Pre(const parser::OpenMPLoopConstruct &x) { const auto &beginLoopDir{std::get(x.t)}; const auto &beginDir{std::get(beginLoopDir.t)}; const auto &clauseList{std::get(beginLoopDir.t)}; switch (beginDir.v) { case llvm::omp::Directive::OMPD_distribute: case llvm::omp::Directive::OMPD_distribute_parallel_do: case llvm::omp::Directive::OMPD_distribute_parallel_do_simd: case llvm::omp::Directive::OMPD_distribute_simd: case llvm::omp::Directive::OMPD_do: case llvm::omp::Directive::OMPD_do_simd: case llvm::omp::Directive::OMPD_loop: case llvm::omp::Directive::OMPD_masked_taskloop_simd: case llvm::omp::Directive::OMPD_masked_taskloop: case llvm::omp::Directive::OMPD_master_taskloop_simd: case llvm::omp::Directive::OMPD_master_taskloop: case llvm::omp::Directive::OMPD_parallel_do: case llvm::omp::Directive::OMPD_parallel_do_simd: case llvm::omp::Directive::OMPD_parallel_masked_taskloop_simd: case llvm::omp::Directive::OMPD_parallel_masked_taskloop: case llvm::omp::Directive::OMPD_parallel_master_taskloop_simd: case llvm::omp::Directive::OMPD_parallel_master_taskloop: case llvm::omp::Directive::OMPD_simd: case llvm::omp::Directive::OMPD_target_loop: case llvm::omp::Directive::OMPD_target_parallel_do: case llvm::omp::Directive::OMPD_target_parallel_do_simd: case llvm::omp::Directive::OMPD_target_parallel_loop: case llvm::omp::Directive::OMPD_target_teams_distribute: case llvm::omp::Directive::OMPD_target_teams_distribute_parallel_do: case llvm::omp::Directive::OMPD_target_teams_distribute_parallel_do_simd: case llvm::omp::Directive::OMPD_target_teams_distribute_simd: case llvm::omp::Directive::OMPD_target_teams_loop: case llvm::omp::Directive::OMPD_target_simd: case llvm::omp::Directive::OMPD_taskloop: case llvm::omp::Directive::OMPD_taskloop_simd: case llvm::omp::Directive::OMPD_teams_distribute: case llvm::omp::Directive::OMPD_teams_distribute_parallel_do: case llvm::omp::Directive::OMPD_teams_distribute_parallel_do_simd: case llvm::omp::Directive::OMPD_teams_distribute_simd: case llvm::omp::Directive::OMPD_teams_loop: case llvm::omp::Directive::OMPD_tile: case llvm::omp::Directive::OMPD_unroll: PushContext(beginDir.source, beginDir.v); break; default: break; } if (beginDir.v == llvm::omp::OMPD_master_taskloop || beginDir.v == llvm::omp::OMPD_master_taskloop_simd || beginDir.v == llvm::omp::OMPD_parallel_master_taskloop || beginDir.v == llvm::omp::OMPD_parallel_master_taskloop_simd || beginDir.v == llvm::omp::Directive::OMPD_target_loop) IssueNonConformanceWarning(beginDir.v, beginDir.source); ClearDataSharingAttributeObjects(); SetContextAssociatedLoopLevel(GetAssociatedLoopLevelFromClauses(clauseList)); if (beginDir.v == llvm::omp::Directive::OMPD_do) { if (const auto &doConstruct{ std::get>(x.t)}) { if (doConstruct.value().IsDoWhile()) { return true; } } } PrivatizeAssociatedLoopIndexAndCheckLoopLevel(x); ordCollapseLevel = GetAssociatedLoopLevelFromClauses(clauseList) + 1; return true; } void OmpAttributeVisitor::ResolveSeqLoopIndexInParallelOrTaskConstruct( const parser::Name &iv) { // Find the parallel 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 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 is already Private or Firstprivate 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::OmpPrivate) || symbol->test(Symbol::Flag::OmpFirstPrivate)) { 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++; symbol->set(Symbol::Flag::OmpPreDetermined); 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 (!dirContext_.empty() && GetContext().withinConstruct) { llvm::SmallVector ivs; if (x.IsDoNormal()) { const parser::Name *iv{GetLoopIndex(x)}; if (iv && iv->symbol) ivs.push_back(iv); } ordCollapseLevel--; for (auto iv : ivs) { if (!iv->symbol->test(Symbol::Flag::OmpPreDetermined)) { ResolveSeqLoopIndexInParallelOrTaskConstruct(*iv); } else { // TODO: conflict checks with explicitly determined DSA } if (ordCollapseLevel) { if (const auto *details{iv->symbol->detailsIf()}) { const Symbol *tpSymbol = &details->symbol(); if (tpSymbol->test(Symbol::Flag::OmpThreadprivate)) { context_.Say(iv->source, "Loop iteration variable %s is not allowed in THREADPRIVATE."_err_en_US, iv->ToString()); } } } } } return true; } std::int64_t OmpAttributeVisitor::GetAssociatedLoopLevelFromClauses( const parser::OmpClauseList &x) { std::int64_t orderedLevel{0}; std::int64_t collapseLevel{0}; const parser::OmpClause *ordClause{nullptr}; const parser::OmpClause *collClause{nullptr}; for (const auto &clause : x.v) { if (const auto *orderedClause{ std::get_if(&clause.u)}) { if (const auto v{EvaluateInt64(context_, orderedClause->v)}) { orderedLevel = *v; } ordClause = &clause; } if (const auto *collapseClause{ std::get_if(&clause.u)}) { if (const auto v{EvaluateInt64(context_, collapseClause->v)}) { collapseLevel = *v; } collClause = &clause; } } if (orderedLevel && (!collapseLevel || orderedLevel >= collapseLevel)) { SetAssociatedClause(*ordClause); return orderedLevel; } else if (!orderedLevel && collapseLevel) { SetAssociatedClause(*collClause); return collapseLevel; } // orderedLevel < collapseLevel is an error handled in structural checks return 1; // default is outermost loop } // 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. // - The loop iteration variables in the associated do-loops of a simd // construct with multiple associated do-loops are lastprivate. void OmpAttributeVisitor::PrivatizeAssociatedLoopIndexAndCheckLoopLevel( const parser::OpenMPLoopConstruct &x) { std::int64_t level{GetContext().associatedLoopLevel}; if (level <= 0) { return; } Symbol::Flag ivDSA; if (!llvm::omp::allSimdSet.test(GetContext().directive)) { ivDSA = Symbol::Flag::OmpPrivate; } else if (level == 1) { ivDSA = Symbol::Flag::OmpLinear; } else { ivDSA = Symbol::Flag::OmpLastPrivate; } const auto &outer{std::get>(x.t)}; if (outer.has_value()) { for (const parser::DoConstruct *loop{&*outer}; loop && level > 0; --level) { // go through all the nested do-loops and resolve index variables const parser::Name *iv{GetLoopIndex(*loop)}; if (iv) { if (auto *symbol{ResolveOmp(*iv, ivDSA, currScope())}) { symbol->set(Symbol::Flag::OmpPreDetermined); iv->symbol = symbol; // adjust the symbol within region AddToContextObjectWithDSA(*symbol, ivDSA); } const auto &block{std::get(loop->t)}; const auto it{block.begin()}; loop = it != block.end() ? GetDoConstructIf(*it) : nullptr; } } CheckAssocLoopLevel(level, GetAssociatedClause()); } else { context_.Say(GetContext().directiveSource, "A DO loop must follow the %s directive"_err_en_US, parser::ToUpperCaseLetters( llvm::omp::getOpenMPDirectiveName(GetContext().directive).str())); } } void OmpAttributeVisitor::CheckAssocLoopLevel( std::int64_t level, const parser::OmpClause *clause) { if (clause && level != 0) { context_.Say(clause->source, "The value of the parameter in the COLLAPSE or ORDERED clause must" " not be larger than the number of nested loops" " following the construct."_err_en_US); } } bool OmpAttributeVisitor::Pre(const parser::OpenMPSectionsConstruct &x) { const auto &beginSectionsDir{ std::get(x.t)}; const auto &beginDir{ std::get(beginSectionsDir.t)}; switch (beginDir.v) { case llvm::omp::Directive::OMPD_parallel_sections: case llvm::omp::Directive::OMPD_sections: PushContext(beginDir.source, beginDir.v); GetContext().withinConstruct = true; break; default: break; } ClearDataSharingAttributeObjects(); return true; } bool OmpAttributeVisitor::Pre(const parser::OpenMPCriticalConstruct &x) { const auto &beginCriticalDir{std::get(x.t)}; const auto &endCriticalDir{std::get(x.t)}; PushContext(beginCriticalDir.source, llvm::omp::Directive::OMPD_critical); GetContext().withinConstruct = true; if (const auto &criticalName{ std::get>(beginCriticalDir.t)}) { ResolveOmpName(*criticalName, Symbol::Flag::OmpCriticalLock); } if (const auto &endCriticalName{ std::get>(endCriticalDir.t)}) { ResolveOmpName(*endCriticalName, Symbol::Flag::OmpCriticalLock); } return true; } bool OmpAttributeVisitor::Pre(const parser::OpenMPDeclareTargetConstruct &x) { PushContext(x.source, llvm::omp::Directive::OMPD_declare_target); const auto &spec{std::get(x.t)}; if (const auto *objectList{parser::Unwrap(spec.u)}) { ResolveOmpObjectList(*objectList, Symbol::Flag::OmpDeclareTarget); } else if (const auto *clauseList{ parser::Unwrap(spec.u)}) { for (const auto &clause : clauseList->v) { if (const auto *toClause{std::get_if(&clause.u)}) { auto &objList{std::get(toClause->v.t)}; ResolveOmpObjectList(objList, Symbol::Flag::OmpDeclareTarget); } else if (const auto *linkClause{ std::get_if(&clause.u)}) { ResolveOmpObjectList(linkClause->v, Symbol::Flag::OmpDeclareTarget); } else if (const auto *enterClause{ std::get_if(&clause.u)}) { ResolveOmpObjectList(enterClause->v, Symbol::Flag::OmpDeclareTarget); } } } return true; } bool OmpAttributeVisitor::Pre(const parser::OpenMPDeclareMapperConstruct &x) { PushContext(x.source, llvm::omp::Directive::OMPD_declare_mapper); return true; } bool OmpAttributeVisitor::Pre(const parser::OpenMPThreadprivate &x) { PushContext(x.source, llvm::omp::Directive::OMPD_threadprivate); const auto &list{std::get(x.t)}; ResolveOmpObjectList(list, Symbol::Flag::OmpThreadprivate); return true; } bool OmpAttributeVisitor::Pre(const parser::OpenMPDeclarativeAllocate &x) { PushContext(x.source, llvm::omp::Directive::OMPD_allocate); const auto &list{std::get(x.t)}; ResolveOmpObjectList(list, Symbol::Flag::OmpDeclarativeAllocateDirective); return false; } bool OmpAttributeVisitor::Pre(const parser::OpenMPDispatchConstruct &x) { PushContext(x.source, llvm::omp::Directive::OMPD_dispatch); return true; } bool OmpAttributeVisitor::Pre(const parser::OpenMPExecutableAllocate &x) { PushContext(x.source, llvm::omp::Directive::OMPD_allocate); const auto &list{std::get>(x.t)}; if (list) { ResolveOmpObjectList(*list, Symbol::Flag::OmpExecutableAllocateDirective); } return true; } bool OmpAttributeVisitor::Pre(const parser::OpenMPAllocatorsConstruct &x) { PushContext(x.source, llvm::omp::Directive::OMPD_allocators); const auto &clauseList{std::get(x.t)}; for (const auto &clause : clauseList.v) { if (const auto *allocClause{ std::get_if(&clause.u)}) { ResolveOmpObjectList(std::get(allocClause->v.t), Symbol::Flag::OmpExecutableAllocateDirective); } } return true; } 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::OpenMPExecutableAllocate &x) { bool hasAllocator = false; // TODO: Investigate whether searching the clause list can be done with // parser::Unwrap instead of the following loop const auto &clauseList{std::get(x.t)}; for (const auto &clause : clauseList.v) { if (std::get_if(&clause.u)) { hasAllocator = true; } } if (IsNestedInDirective(llvm::omp::Directive::OMPD_target) && !hasAllocator) { // TODO: expand this check to exclude the case when a requires // directive with the dynamic_allocators clause is present // in the same compilation unit (OMP5.0 2.11.3). context_.Say(x.source, "ALLOCATE directives that appear in a TARGET region " "must specify an allocator clause"_err_en_US); } const auto &allocateStmt = std::get>(x.t).statement; if (const auto &list{std::get>(x.t)}) { CheckAllNamesInAllocateStmt( std::get(x.t).source, *list, allocateStmt); } if (const auto &subDirs{ std::get>>( x.t)}) { for (const auto &dalloc : *subDirs) { CheckAllNamesInAllocateStmt(std::get(dalloc.t).source, std::get(dalloc.t), allocateStmt); } } PopContext(); } void OmpAttributeVisitor::Post(const parser::OpenMPAllocatorsConstruct &x) { const auto &dir{std::get(x.t)}; const auto &clauseList{std::get(x.t)}; for (const auto &clause : clauseList.v) { if (const auto *alloc{ std::get_if(&clause.u)}) { CheckAllNamesInAllocateStmt(dir.source, std::get(alloc->v.t), std::get>(x.t).statement); auto &modifiers{OmpGetModifiers(alloc->v)}; bool hasAllocator{ OmpGetUniqueModifier(modifiers) || OmpGetUniqueModifier(modifiers)}; // TODO: As with allocate directive, exclude the case when a requires // directive with the dynamic_allocators clause is present in // the same compilation unit (OMP5.0 2.11.3). if (IsNestedInDirective(llvm::omp::Directive::OMPD_target) && !hasAllocator) { context_.Say(x.source, "ALLOCATORS directives that appear in a TARGET region " "must specify an allocator"_err_en_US); } } } PopContext(); } static bool IsPrivatizable(const Symbol *sym) { auto *misc{sym->detailsIf()}; return IsVariableName(*sym) && !IsProcedure(*sym) && !IsNamedConstant(*sym) && ( // OpenMP 5.2, 5.1.1: Assumed-size arrays are shared !semantics::IsAssumedSizeArray(*sym) || // If CrayPointer is among the DSA list then the // CrayPointee is Privatizable sym->test(Symbol::Flag::CrayPointee)) && !sym->owner().IsDerivedType() && sym->owner().kind() != Scope::Kind::ImpliedDos && sym->owner().kind() != Scope::Kind::Forall && !sym->detailsIf() && !sym->detailsIf() && (!misc || (misc->kind() != MiscDetails::Kind::ComplexPartRe && misc->kind() != MiscDetails::Kind::ComplexPartIm && misc->kind() != MiscDetails::Kind::KindParamInquiry && misc->kind() != MiscDetails::Kind::LenParamInquiry && misc->kind() != MiscDetails::Kind::ConstructName)); } void OmpAttributeVisitor::CreateImplicitSymbols( const Symbol *symbol, std::optional setFlag) { if (!IsPrivatizable(symbol)) { return; } // Implicitly determined DSAs // OMP 5.2 5.1.1 - Variables Referenced in a Construct Symbol *lastDeclSymbol = nullptr; std::optional prevDSA; for (int dirDepth{0}; dirDepth < (int)dirContext_.size(); ++dirDepth) { DirContext &dirContext = dirContext_[dirDepth]; std::optional dsa; for (auto symMap : dirContext.objectWithDSA) { // if the `symbol` already has a data-sharing attribute if (symMap.first->name() == symbol->name()) { dsa = symMap.second; break; } } // When handling each implicit rule for a given symbol, one of the // following 3 actions may be taken: // 1. Declare a new private symbol. // 2. Create a new association symbol with no flags, that will represent // a shared symbol in the current scope. Note that symbols without // any private flags are considered as shared. // 3. Use the last declared private symbol, by inserting a new symbol // in the scope being processed, associated with it. // If no private 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 last 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 makePrivateSymbol = [&](Symbol::Flag flag) { const Symbol *hostSymbol = lastDeclSymbol ? lastDeclSymbol : &symbol->GetUltimate(); lastDeclSymbol = DeclareNewPrivateAccessEntity( *hostSymbol, flag, context_.FindScope(dirContext.directiveSource)); if (setFlag) { lastDeclSymbol->set(*setFlag); } return lastDeclSymbol; }; auto makeSharedSymbol = [&](std::optional flag = {}) { const Symbol *hostSymbol = lastDeclSymbol ? lastDeclSymbol : &symbol->GetUltimate(); Symbol &assocSymbol = MakeAssocSymbol(symbol->name(), *hostSymbol, context_.FindScope(dirContext.directiveSource)); if (flag) { assocSymbol.set(*flag); } }; auto useLastDeclSymbol = [&]() { if (lastDeclSymbol) { makeSharedSymbol(); } }; bool taskGenDir = llvm::omp::taskGeneratingSet.test(dirContext.directive); bool targetDir = llvm::omp::allTargetSet.test(dirContext.directive); bool parallelDir = llvm::omp::allParallelSet.test(dirContext.directive); bool teamsDir = llvm::omp::allTeamsSet.test(dirContext.directive); if (dsa.has_value()) { if (dsa.value() == Symbol::Flag::OmpShared && (parallelDir || taskGenDir || teamsDir)) { makeSharedSymbol(Symbol::Flag::OmpShared); } // Private symbols will have been declared already. prevDSA = dsa; continue; } 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; } if (dirContext.defaultDSA != Symbol::Flag::OmpShared) { makePrivateSymbol(dirContext.defaultDSA); } else { makeSharedSymbol(); } dsa = dirContext.defaultDSA; } else if (parallelDir) { // 2) parallel -> shared makeSharedSymbol(); dsa = Symbol::Flag::OmpShared; } else if (!taskGenDir && !targetDir) { // 3) enclosing context useLastDeclSymbol(); dsa = prevDSA; } else if (targetDir) { // TODO 4) not mapped target variable -> firstprivate dsa = prevDSA; } else if (taskGenDir) { // TODO 5) dummy arg in orphaned taskgen construct -> firstprivate if (prevDSA == Symbol::Flag::OmpShared) { // 6) shared in enclosing context -> shared makeSharedSymbol(); dsa = Symbol::Flag::OmpShared; } else { // 7) firstprivate dsa = Symbol::Flag::OmpFirstPrivate; makePrivateSymbol(*dsa)->set(Symbol::Flag::OmpImplicit); } } prevDSA = dsa; } } // 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 && !dirContext_.empty() && GetContext().withinConstruct) { if (IsPrivatizable(symbol) && !IsObjectWithDSA(*symbol)) { // 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 } else if (GetContext().defaultDSA == Symbol::Flag::OmpNone && !symbol->test(Symbol::Flag::OmpThreadprivate) && // Exclude indices of sequential loops that are privatised in // the scope of the parallel region, and not in this scope. // TODO: check whether this should be caught in IsObjectWithDSA !symbol->test(Symbol::Flag::OmpPrivate)) { if (symbol->test(Symbol::Flag::CrayPointee)) { std::string crayPtrName{ semantics::GetCrayPointer(*symbol).name().ToString()}; if (!IsObjectWithDSA(*currScope().FindSymbol(crayPtrName))) context_.Say(name.source, "The DEFAULT(NONE) clause requires that the Cray Pointer '%s' must be listed in a data-sharing attribute clause"_err_en_US, crayPtrName); } else { context_.Say(name.source, "The DEFAULT(NONE) clause requires that '%s' must be listed in a data-sharing attribute clause"_err_en_US, symbol->name()); } } } } if (Symbol * found{currScope().FindSymbol(name.source)}) { if (found->test(semantics::Symbol::Flag::OmpThreadprivate)) return; } CreateImplicitSymbols(symbol); } // within OpenMP construct } Symbol *OmpAttributeVisitor::ResolveName(const parser::Name *name) { 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)) { AddToContextObjectWithDSA(*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; } // Use this function over ResolveOmpName when an omp object's scope needs // resolving, it's symbol flag isn't important and a simple check for resolution // failure is desired. Using ResolveOmpName means needing to work with the // context to check for failure, whereas here a pointer comparison is all that's // needed. Symbol *OmpAttributeVisitor::ResolveOmpObjectScope(const parser::Name *name) { // TODO: Investigate whether the following block can be replaced by, or // included in, the ResolveOmpName function if (auto *prev{name ? GetContext().scope.parent().FindSymbol(name->source) : nullptr}) { name->symbol = prev; return nullptr; } // TODO: Investigate whether the following block can be replaced by, or // included in, the ResolveOmpName function if (auto *ompSymbol{ name ? GetContext().scope.FindSymbol(name->source) : nullptr}) { name->symbol = ompSymbol; return ompSymbol; } return nullptr; } void OmpAttributeVisitor::ResolveOmpObjectList( const parser::OmpObjectList &ompObjectList, Symbol::Flag ompFlag) { for (const auto &ompObject : ompObjectList.v) { ResolveOmpObject(ompObject, ompFlag); } } void OmpAttributeVisitor::ResolveOmpObject( const parser::OmpObject &ompObject, Symbol::Flag ompFlag) { common::visit( common::visitors{ [&](const parser::Designator &designator) { if (const auto *name{ semantics::getDesignatorNameIfDataRef(designator)}) { 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::ToUpperCaseLetters( llvm::omp::getOpenMPDirectiveName( GetContext().directive) .str())); } }; if (dataCopyingAttributeFlags.test(ompFlag)) { CheckDataCopyingClause(*name, *symbol, ompFlag); } else { AddToContextObjectWithDSA(*symbol, ompFlag); if (dataSharingAttributeFlags.test(ompFlag)) { CheckMultipleAppearances(*name, *symbol, ompFlag); } if (privateDataSharingAttributeFlags.test(ompFlag)) { CheckObjectIsPrivatizable(*name, *symbol, ompFlag); } if (ompFlag == Symbol::Flag::OmpAllocate) { AddAllocateName(name); } } if (ompFlag == Symbol::Flag::OmpDeclarativeAllocateDirective && IsAllocatable(*symbol) && !IsNestedInDirective(llvm::omp::Directive::OMPD_allocate)) { context_.Say(designator.source, "List items specified in the ALLOCATE directive must not " "have the ALLOCATABLE attribute unless the directive is " "associated with an ALLOCATE statement"_err_en_US); } if ((ompFlag == Symbol::Flag::OmpDeclarativeAllocateDirective || ompFlag == Symbol::Flag::OmpExecutableAllocateDirective) && ResolveOmpObjectScope(name) == nullptr) { context_.Say(designator.source, // 2.15.3 "List items must be declared in the same scoping unit " "in which the %s directive appears"_err_en_US, parser::ToUpperCaseLetters( llvm::omp::getOpenMPDirectiveName( GetContext().directive) .str())); } if (ompFlag == Symbol::Flag::OmpReduction) { const Symbol &ultimateSymbol{symbol->GetUltimate()}; // 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 (ultimateSymbol.test(Symbol::Flag::InNamelist)) { 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 (GetContext().directive == llvm::omp::Directive::OMPD_target_data) { checkExclusivelists(symbol, Symbol::Flag::OmpUseDevicePtr, symbol, Symbol::Flag::OmpUseDeviceAddr); } if (llvm::omp::allDistributeSet.test(GetContext().directive)) { checkExclusivelists(symbol, Symbol::Flag::OmpFirstPrivate, symbol, Symbol::Flag::OmpLastPrivate); } if (llvm::omp::allTargetSet.test(GetContext().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(); } } Symbol::Flag dataMappingAttributeFlags[] = { Symbol::Flag::OmpMapTo, Symbol::Flag::OmpMapFrom, Symbol::Flag::OmpMapToFrom, Symbol::Flag::OmpMapAlloc, Symbol::Flag::OmpMapRelease, Symbol::Flag::OmpMapDelete, Symbol::Flag::OmpIsDevicePtr, Symbol::Flag::OmpHasDeviceAddr}; 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(GetContext().directive) || llvm::omp::allParallelSet.test( GetContext().directive))) { for (Symbol::Flag ompFlag1 : dataMappingAttributeFlags) { for (Symbol::Flag ompFlag2 : dataSharingAttributeFlags) { checkExclusivelists( hostAssocSym, ompFlag1, symbol, ompFlag2); } } } } } } 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 OpenMP " "directives or clauses"_err_en_US); } } // other checks, more TBD } }, [&](const parser::Name &name) { // common block 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()}; unsigned index{0}; for (auto &object : details.objects()) { if (auto *resolvedObject{ ResolveOmp(*object, ompFlag, currScope())}) { if (dataCopyingAttributeFlags.test(ompFlag)) { CheckDataCopyingClause(name, *resolvedObject, ompFlag); } else { AddToContextObjectWithDSA(*resolvedObject, ompFlag); } details.replace_object(*resolvedObject, index); } index++; } } 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); } }, }, ompObject.u); } Symbol *OmpAttributeVisitor::ResolveOmp( const parser::Name &name, Symbol::Flag ompFlag, Scope &scope) { if (ompFlagsRequireNewSymbol.test(ompFlag)) { return DeclarePrivateAccessEntity(name, ompFlag, scope); } else { return DeclareOrMarkOtherAccessEntity(name, ompFlag); } } Symbol *OmpAttributeVisitor::ResolveOmp( Symbol &symbol, Symbol::Flag ompFlag, Scope &scope) { if (ompFlagsRequireNewSymbol.test(ompFlag)) { return DeclarePrivateAccessEntity(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) { if (ompFlagsRequireMark.test(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()); if (privateDataSharingAttributeFlags.test(ompFlag)) { AddPrivateDataSharingAttributeObjects(*target); } } } 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); } } } void ResolveOmpTopLevelParts( SemanticsContext &context, const parser::Program &program) { if (!context.IsEnabled(common::LanguageFeature::OpenMP)) { return; } // Gather REQUIRES clauses from all non-module top-level program unit symbols, // combine them together ensuring compatibility and apply them to all these // program units. Modules are skipped because their REQUIRES clauses should be // propagated via USE statements instead. WithOmpDeclarative::RequiresFlags combinedFlags; std::optional combinedMemOrder; // Function to go through non-module top level program units and extract // REQUIRES information to be processed by a function-like argument. auto processProgramUnits{[&](auto processFn) { for (const parser::ProgramUnit &unit : program.v) { if (!std::holds_alternative>( unit.u) && !std::holds_alternative>( unit.u) && !std::holds_alternative< common::Indirection>(unit.u)) { Symbol *symbol{common::visit( [&context](auto &x) { Scope *scope = GetScope(context, x.value()); return scope ? scope->symbol() : nullptr; }, unit.u)}; // FIXME There is no symbol defined for MainProgram units in certain // circumstances, so REQUIRES information has no place to be stored in // these cases. if (!symbol) { continue; } common::visit( [&](auto &details) { if constexpr (std::is_convertible_v) { processFn(*symbol, details); } }, symbol->details()); } } }}; // Combine global REQUIRES information from all program units except modules // and submodules. processProgramUnits([&](Symbol &symbol, WithOmpDeclarative &details) { if (const WithOmpDeclarative::RequiresFlags * flags{details.ompRequires()}) { combinedFlags |= *flags; } if (const common::OmpAtomicDefaultMemOrderType * memOrder{details.ompAtomicDefaultMemOrder()}) { if (combinedMemOrder && *combinedMemOrder != *memOrder) { context.Say(symbol.scope()->sourceRange(), "Conflicting '%s' REQUIRES clauses found in compilation " "unit"_err_en_US, parser::ToUpperCaseLetters(llvm::omp::getOpenMPClauseName( llvm::omp::Clause::OMPC_atomic_default_mem_order) .str())); } combinedMemOrder = *memOrder; } }); // Update all program units except modules and submodules with the combined // global REQUIRES information. processProgramUnits([&](Symbol &, WithOmpDeclarative &details) { if (combinedFlags.any()) { details.set_ompRequires(combinedFlags); } if (combinedMemOrder) { details.set_ompAtomicDefaultMemOrder(*combinedMemOrder); } }); } static bool IsSymbolInCommonBlock(const Symbol &symbol) { // TODO Improve the performance of this predicate function. // Going through all symbols sequentially, in all common blocks, can be // slow when there are many symbols. A possible optimization is to add // an OmpInCommonBlock flag to Symbol, to make it possible to quickly // test if a given symbol is in a common block. for (const auto &cb : symbol.owner().commonBlocks()) { if (IsCommonBlockContaining(cb.second.get(), symbol)) { return true; } } return false; } static bool IsSymbolThreadprivate(const Symbol &symbol) { if (const auto *details{symbol.detailsIf()}) { return details->symbol().test(Symbol::Flag::OmpThreadprivate); } return symbol.test(Symbol::Flag::OmpThreadprivate); } static bool IsSymbolPrivate(const Symbol &symbol) { if (symbol.test(Symbol::Flag::OmpPrivate) || symbol.test(Symbol::Flag::OmpFirstPrivate)) { 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) && !IsSymbolInCommonBlock(symbol); 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 (ultimateSymbol.test(Symbol::Flag::InNamelist)) { 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::CheckSourceLabel(const parser::Label &label) { // Get the context to check if the statement causing a jump to the 'label' is // in an enclosing OpenMP construct std::optional thisContext{GetContextIf()}; sourceLabels_.emplace( label, std::make_pair(currentStatementSource_, thisContext)); // Check if the statement with 'label' to which a jump is being introduced // has already been encountered auto it{targetLabels_.find(label)}; if (it != targetLabels_.end()) { // Check if both the statement with 'label' and the statement that causes a // jump to the 'label' are in the same scope CheckLabelContext(currentStatementSource_, it->second.first, thisContext, it->second.second); } } // Check for invalid branch into or out of OpenMP structured blocks void OmpAttributeVisitor::CheckLabelContext(const parser::CharBlock source, const parser::CharBlock target, std::optional sourceContext, std::optional targetContext) { if (targetContext && (!sourceContext || (sourceContext->scope != targetContext->scope && !DoesScopeContain( &targetContext->scope, sourceContext->scope)))) { context_ .Say(source, "invalid branch into an OpenMP structured block"_err_en_US) .Attach(target, "In the enclosing %s directive branched into"_en_US, parser::ToUpperCaseLetters( llvm::omp::getOpenMPDirectiveName(targetContext->directive) .str())); } if (sourceContext && (!targetContext || (sourceContext->scope != targetContext->scope && !DoesScopeContain( &sourceContext->scope, targetContext->scope)))) { context_ .Say(source, "invalid branch leaving an OpenMP structured block"_err_en_US) .Attach(target, "Outside the enclosing %s directive"_en_US, parser::ToUpperCaseLetters( llvm::omp::getOpenMPDirectiveName(sourceContext->directive) .str())); } } // Goes through the names in an OmpObjectList and checks if each name appears // in the given allocate statement void OmpAttributeVisitor::CheckAllNamesInAllocateStmt( const parser::CharBlock &source, const parser::OmpObjectList &ompObjectList, const parser::AllocateStmt &allocate) { for (const auto &obj : ompObjectList.v) { if (const auto *d{std::get_if(&obj.u)}) { if (const auto *ref{std::get_if(&d->u)}) { if (const auto *n{std::get_if(&ref->u)}) { CheckNameInAllocateStmt(source, *n, allocate); } } } } } void OmpAttributeVisitor::CheckNameInAllocateStmt( const parser::CharBlock &source, const parser::Name &name, const parser::AllocateStmt &allocate) { for (const auto &allocation : std::get>(allocate.t)) { const auto &allocObj = std::get(allocation.t); if (const auto *n{std::get_if(&allocObj.u)}) { if (n->source == name.source) { return; } } } context_.Say(source, "Object '%s' in %s directive not " "found in corresponding ALLOCATE statement"_err_en_US, name.ToString(), parser::ToUpperCaseLetters( llvm::omp::getOpenMPDirectiveName(GetContext().directive).str())); } void OmpAttributeVisitor::AddOmpRequiresToScope(Scope &scope, WithOmpDeclarative::RequiresFlags flags, std::optional memOrder) { Scope *scopeIter = &scope; do { if (Symbol * symbol{scopeIter->symbol()}) { common::visit( [&](auto &details) { // Store clauses information into the symbol for the parent and // enclosing modules, programs, functions and subroutines. if constexpr (std::is_convertible_v) { if (flags.any()) { if (const WithOmpDeclarative::RequiresFlags * otherFlags{details.ompRequires()}) { flags |= *otherFlags; } details.set_ompRequires(flags); } if (memOrder) { if (details.has_ompAtomicDefaultMemOrder() && *details.ompAtomicDefaultMemOrder() != *memOrder) { context_.Say(scopeIter->sourceRange(), "Conflicting '%s' REQUIRES clauses found in compilation " "unit"_err_en_US, parser::ToUpperCaseLetters(llvm::omp::getOpenMPClauseName( llvm::omp::Clause::OMPC_atomic_default_mem_order) .str())); } details.set_ompAtomicDefaultMemOrder(*memOrder); } } }, symbol->details()); } scopeIter = &scopeIter->parent(); } while (!scopeIter->IsGlobal()); } void OmpAttributeVisitor::IssueNonConformanceWarning( llvm::omp::Directive D, parser::CharBlock source) { std::string warnStr; llvm::raw_string_ostream warnStrOS(warnStr); warnStrOS << "OpenMP directive " << parser::ToUpperCaseLetters( llvm::omp::getOpenMPDirectiveName(D).str()) << " has been deprecated"; auto setAlternativeStr = [&warnStrOS](llvm::StringRef alt) { warnStrOS << ", please use " << alt << " instead."; }; switch (D) { case llvm::omp::OMPD_master: setAlternativeStr("MASKED"); break; case llvm::omp::OMPD_master_taskloop: setAlternativeStr("MASKED TASKLOOP"); break; case llvm::omp::OMPD_master_taskloop_simd: setAlternativeStr("MASKED TASKLOOP SIMD"); break; case llvm::omp::OMPD_parallel_master: setAlternativeStr("PARALLEL MASKED"); break; case llvm::omp::OMPD_parallel_master_taskloop: setAlternativeStr("PARALLEL MASKED TASKLOOP"); break; case llvm::omp::OMPD_parallel_master_taskloop_simd: setAlternativeStr("PARALLEL_MASKED TASKLOOP SIMD"); break; case llvm::omp::OMPD_target_loop: default:; } context_.Warn(common::UsageWarning::OpenMPUsage, source, "%s"_warn_en_US, warnStrOS.str()); } } // namespace Fortran::semantics