//===----------------------------------------------------------------------===// // // 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/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 #include 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) { 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, Symbol &prev, Scope &scope) { const auto pair{scope.try_emplace(name, Attrs{}, HostAssocDetails{prev})}; return *pair.first->second; } Symbol &MakeAssocSymbol(const SourceName &name, 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 *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) : DirectiveAttributeVisitor(context) {} 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)}; 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::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 &); static constexpr Symbol::Flags dataSharingAttributeFlags{ Symbol::Flag::AccShared, Symbol::Flag::AccPrivate, Symbol::Flag::AccPresent, Symbol::Flag::AccFirstPrivate, Symbol::Flag::AccReduction}; static constexpr Symbol::Flags dataMappingAttributeFlags{ Symbol::Flag::AccCreate, Symbol::Flag::AccCopyIn, Symbol::Flag::AccCopyOut, Symbol::Flag::AccDelete}; static constexpr Symbol::Flags accFlagsRequireNewSymbol{ Symbol::Flag::AccPrivate, Symbol::Flag::AccFirstPrivate, Symbol::Flag::AccReduction}; static constexpr Symbol::Flags accDataMvtFlags{ Symbol::Flag::AccDevice, Symbol::Flag::AccHost, Symbol::Flag::AccSelf}; static constexpr Symbol::Flags accFlagsRequireMark{}; void PrivatizeAssociatedLoopIndex(const parser::OpenACCLoopConstruct &); 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 *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 EnsureAllocatableOrPointer( const llvm::acc::Clause clause, const parser::AccObjectList &objectList); }; // 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::SpecificationPart &x) { Walk(std::get>(x.t)); 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::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::OpenMPRequiresConstruct &x) { PushContext(x.source, llvm::omp::Directive::OMPD_requires); return true; } void Post(const parser::OpenMPRequiresConstruct &) { PopContext(); } bool Pre(const parser::OpenMPDeclareTargetConstruct &); void Post(const parser::OpenMPDeclareTargetConstruct &) { 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::OpenMPExecutableAllocate &); void Post(const parser::OpenMPExecutableAllocate &); // 2.15.3 Data-Sharing Attribute Clauses 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) { ResolveOmpObjectList(x.v, 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) { common::visit(common::visitors{ [&](const parser::OmpLinearClause::WithoutModifier &linearWithoutModifier) { ResolveOmpNameList(linearWithoutModifier.names, Symbol::Flag::OmpLinear); }, [&](const parser::OmpLinearClause::WithModifier &linearWithModifier) { ResolveOmpNameList( linearWithModifier.names, Symbol::Flag::OmpLinear); }, }, x.u); return false; } bool Pre(const parser::OmpClause::Reduction &x) { const parser::OmpReductionOperator &opr{ std::get(x.v.t)}; if (const auto *procD{parser::Unwrap(opr.u)}) { if (const auto *name{parser::Unwrap(procD->u)}) { if (!name->symbol) { const auto namePair{currScope().try_emplace( name->source, Attrs{}, ProcEntityDetails{})}; auto &symbol{*namePair.first->second}; name->symbol = &symbol; name->symbol->set(Symbol::Flag::OmpReduction); AddToContextObjectWithDSA(*name->symbol, Symbol::Flag::OmpReduction); } } if (const auto *procRef{ parser::Unwrap(procD->u)}) { ResolveOmp(*procRef->v.thing.component.symbol, Symbol::Flag::OmpReduction, currScope()); } } const auto &objList{std::get(x.v.t)}; ResolveOmpObjectList(objList, Symbol::Flag::OmpReduction); 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; } bool Pre(const parser::OmpDependClause &x) { if (const auto *dependSink{ std::get_if(&x.u)}) { const auto &dependSinkVec{dependSink->v}; for (const auto &dependSinkElement : dependSinkVec) { const auto &name{std::get(dependSinkElement.t)}; ResolveName(&name); } } return false; } 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; } 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) { 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 && 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); } } 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 &); static constexpr Symbol::Flags dataSharingAttributeFlags{ Symbol::Flag::OmpShared, Symbol::Flag::OmpPrivate, Symbol::Flag::OmpFirstPrivate, Symbol::Flag::OmpLastPrivate, Symbol::Flag::OmpReduction, Symbol::Flag::OmpLinear}; static constexpr Symbol::Flags privateDataSharingAttributeFlags{ Symbol::Flag::OmpPrivate, Symbol::Flag::OmpFirstPrivate, Symbol::Flag::OmpLastPrivate}; static constexpr 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}; static constexpr Symbol::Flags ompFlagsRequireMark{ Symbol::Flag::OmpThreadprivate, Symbol::Flag::OmpDeclareTarget}; static constexpr 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 CheckObjectInNamelist( 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(); }; bool HasSymbolInEnclosingScope(const Symbol &, Scope &); std::int64_t ordCollapseLevel{0}; }; 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::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()) { 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; } 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); } else if (const auto *routineConstruct{ std::get_if(&x.u)}) { const auto &verbatim{std::get(routineConstruct->t)}; PushContext(verbatim.source, llvm::acc::Directive::ACCD_routine); } 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)); PrivatizeAssociatedLoopIndex(x); 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; } bool AccAttributeVisitor::Pre(const parser::OpenACCRoutineConstruct &x) { const auto &optName{std::get>(x.t)}; if (optName) { if (!ResolveName(*optName, true)) { context_.Say((*optName).source, "No function or subroutine declared for '%s'"_err_en_US, (*optName).source); } } return true; } bool AccAttributeVisitor::Pre(const parser::AccBindClause &x) { if (const auto *name{std::get_if(&x.u)}) { if (!ResolveName(*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; } 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; }, [](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); } } 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::PrivatizeAssociatedLoopIndex( const parser::OpenACCLoopConstruct &x) { std::int64_t level{GetContext().associatedLoopLevel}; if (level <= 0) { // collpase value was negative or 0 return; } Symbol::Flag ivDSA{Symbol::Flag::AccPrivate}; const auto getNextDoConstruct = [this](const parser::Block &block) -> 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 { break; } } return nullptr; }; const auto &outer{std::get>(x.t)}; 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{ResolveAcc(*iv, ivDSA, currScope())}) { symbol->set(Symbol::Flag::AccPreDetermined); iv->symbol = symbol; // adjust the symbol within region AddToContextObjectWithDSA(*symbol, ivDSA); } } const auto &block{std::get(loop->t)}; loop = getNextDoConstruct(block); } 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 (!IsAllocatableOrPointer(*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() && !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) { 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) { if (accFlagsRequireNewSymbol.test(accFlag)) { return DeclarePrivateAccessEntity(name, accFlag, scope); } else { return DeclareOrMarkOtherAccessEntity(name, accFlag); } } Symbol *AccAttributeVisitor::ResolveAcc( Symbol &symbol, Symbol::Flag accFlag, Scope &scope) { if (accFlagsRequireNewSymbol.test(accFlag)) { return DeclarePrivateAccessEntity(symbol, accFlag, scope); } else { 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)) { 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 (accFlagsRequireNewSymbol.test(accFlag)) { if (const auto *details{symbol.detailsIf()}) { target = &details->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_master: case llvm::omp::Directive::OMPD_ordered: case llvm::omp::Directive::OMPD_parallel: 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; } 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_parallel: 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_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_parallel_do: case llvm::omp::Directive::OMPD_parallel_do_simd: case llvm::omp::Directive::OMPD_simd: case llvm::omp::Directive::OMPD_target_parallel_do: case llvm::omp::Directive::OMPD_target_parallel_do_simd: 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_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_tile: case llvm::omp::Directive::OMPD_unroll: PushContext(beginDir.source, beginDir.v); break; default: break; } 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) { auto targetIt{dirContext_.rbegin()}; for (;; ++targetIt) { if (targetIt == dirContext_.rend()) { return; } if (llvm::omp::parallelSet.test(targetIt->directive) || llvm::omp::taskGeneratingSet.test(targetIt->directive)) { break; } } 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) { // TODO:[OpenMP 5.1] DO CONCURRENT indices are private if (x.IsDoNormal()) { if (!dirContext_.empty() && GetContext().withinConstruct) { const parser::Name *iv{GetLoopIndex(x)}; if (iv && iv->symbol) { if (!iv->symbol->test(Symbol::Flag::OmpPreDetermined)) { ResolveSeqLoopIndexInParallelOrTaskConstruct(*iv); } else { // TODO: conflict checks with explicitly determined DSA } ordCollapseLevel--; 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::simdSet.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)}; 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()); } 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); 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); 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)}) { ResolveOmpObjectList(toClause->v, Symbol::Flag::OmpDeclareTarget); } else if (const auto *linkClause{ std::get_if(&clause.u)}) { ResolveOmpObjectList(linkClause->v, Symbol::Flag::OmpDeclareTarget); } } } 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::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; } void OmpAttributeVisitor::Post(const parser::OmpDefaultClause &x) { if (!dirContext_.empty()) { switch (x.v) { case parser::OmpDefaultClause::Type::Private: SetContextDefaultDSA(Symbol::Flag::OmpPrivate); break; case parser::OmpDefaultClause::Type::Firstprivate: SetContextDefaultDSA(Symbol::Flag::OmpFirstPrivate); break; case parser::OmpDefaultClause::Type::Shared: SetContextDefaultDSA(Symbol::Flag::OmpShared); break; case parser::OmpDefaultClause::Type::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); } PopContext(); } // 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 (!symbol->owner().IsDerivedType() && !IsProcedure(*symbol) && !IsObjectWithDSA(*symbol) && !IsNamedConstant(*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) { 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()); } } } std::vector defaultDSASymbols; for (int dirDepth{0}; dirDepth < (int)dirContext_.size(); ++dirDepth) { DirContext &dirContext = dirContext_[dirDepth]; bool hasDataSharingAttr{false}; for (auto symMap : dirContext.objectWithDSA) { // if the `symbol` already has a data-sharing attribute if (symMap.first->name() == name.symbol->name()) { hasDataSharingAttr = true; break; } } if (hasDataSharingAttr) { if (defaultDSASymbols.size()) symbol = &MakeAssocSymbol(symbol->name(), *defaultDSASymbols.back(), context_.FindScope(dirContext.directiveSource)); continue; } if (dirContext.defaultDSA == semantics::Symbol::Flag::OmpPrivate || dirContext.defaultDSA == semantics::Symbol::Flag::OmpFirstPrivate) { Symbol *hostSymbol = defaultDSASymbols.size() ? defaultDSASymbols.back() : &symbol->GetUltimate(); defaultDSASymbols.push_back( DeclarePrivateAccessEntity(*hostSymbol, dirContext.defaultDSA, context_.FindScope(dirContext.directiveSource))); } else if (defaultDSASymbols.size()) symbol = &MakeAssocSymbol(symbol->name(), *defaultDSASymbols.back(), context_.FindScope(dirContext.directiveSource)); } } // 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 (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; } // 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())}) { if (dataCopyingAttributeFlags.test(ompFlag)) { CheckDataCopyingClause(*name, *symbol, ompFlag); } else { AddToContextObjectWithDSA(*symbol, ompFlag); if (dataSharingAttributeFlags.test(ompFlag)) { CheckMultipleAppearances(*name, *symbol, ompFlag); } if (privateDataSharingAttributeFlags.test(ompFlag)) { CheckObjectInNamelist(*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 ALLOCATE directive appears"_err_en_US); } } } 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) && !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); if (privateDataSharingAttributeFlags.test(ompFlag)) { AddPrivateDataSharingAttributeObjects(*target); } } } void ResolveAccParts( SemanticsContext &context, const parser::ProgramUnit &node) { if (context.IsEnabled(common::LanguageFeature::OpenACC)) { AccAttributeVisitor{context}.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 OmpAttributeVisitor::CheckDataCopyingClause( const parser::Name &name, const Symbol &symbol, Symbol::Flag ompFlag) { const auto *checkSymbol{&symbol}; if (const auto *details{symbol.detailsIf()}) { checkSymbol = &details->symbol(); } if (ompFlag == Symbol::Flag::OmpCopyIn) { // List of items/objects that can appear in a 'copyin' clause must be // 'threadprivate' if (!checkSymbol->test(Symbol::Flag::OmpThreadprivate)) { context_.Say(name.source, "Non-THREADPRIVATE object '%s' in COPYIN clause"_err_en_US, checkSymbol->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 { // List of items/objects that can appear in a 'copyprivate' clause must be // either 'private' or 'threadprivate' in enclosing context. if (!checkSymbol->test(Symbol::Flag::OmpThreadprivate) && !(HasSymbolInEnclosingScope(symbol, currScope()) && symbol.test(Symbol::Flag::OmpPrivate))) { context_.Say(name.source, "COPYPRIVATE variable '%s' is not PRIVATE or THREADPRIVATE in " "outer context"_err_en_US, symbol.name()); } } } } void OmpAttributeVisitor::CheckObjectInNamelist( 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()); } } 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())); } } bool OmpAttributeVisitor::HasSymbolInEnclosingScope( const Symbol &symbol, Scope &scope) { const auto symbols{scope.parent().GetSymbols()}; return llvm::is_contained(symbols, symbol); } } // namespace Fortran::semantics