diff --git a/flang/include/flang/Semantics/symbol.h b/flang/include/flang/Semantics/symbol.h index cf0350735b5b94..b4db6689a94271 100644 --- a/flang/include/flang/Semantics/symbol.h +++ b/flang/include/flang/Semantics/symbol.h @@ -755,7 +755,7 @@ class Symbol { OmpDeclarativeAllocateDirective, OmpExecutableAllocateDirective, OmpDeclareSimd, OmpDeclareTarget, OmpThreadprivate, OmpDeclareReduction, OmpFlushed, OmpCriticalLock, OmpIfSpecified, OmpNone, OmpPreDetermined, - OmpImplicit); + OmpImplicit, OmpFromStmtFunction); using Flags = common::EnumSet; const Scope &owner() const { return *owner_; } diff --git a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp index e1a193edc004a7..1b2f926e21bed8 100644 --- a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp +++ b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp @@ -402,6 +402,15 @@ void DataSharingProcessor::collectSymbols( /*collectSymbols=*/true, /*collectHostAssociatedSymbols=*/true); + // Add implicitly referenced symbols from statement functions. + if (curScope) { + for (const auto &sym : curScope->GetSymbols()) { + if (sym->test(semantics::Symbol::Flag::OmpFromStmtFunction) && + sym->test(flag)) + allSymbols.insert(&*sym); + } + } + llvm::SetVector symbolsInNestedRegions; collectSymbolsInNestedRegions(eval, flag, symbolsInNestedRegions); diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp index cc9f1cc7ed2691..4aecb8b8e7b479 100644 --- a/flang/lib/Semantics/resolve-directives.cpp +++ b/flang/lib/Semantics/resolve-directives.cpp @@ -91,11 +91,12 @@ template class DirectiveAttributeVisitor { void SetContextAssociatedLoopLevel(std::int64_t level) { GetContext().associatedLoopLevel = level; } - Symbol &MakeAssocSymbol(const SourceName &name, Symbol &prev, Scope &scope) { + 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, Symbol &prev) { + Symbol &MakeAssocSymbol(const SourceName &name, const Symbol &prev) { return MakeAssocSymbol(name, prev, currScope()); } void AddDataSharingAttributeObject(SymbolRef object) { @@ -108,6 +109,7 @@ template class DirectiveAttributeVisitor { 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 &); @@ -736,6 +738,9 @@ class OmpAttributeVisitor : DirectiveAttributeVisitor { std::optional); void IssueNonConformanceWarning( llvm::omp::Directive D, parser::CharBlock source); + + void CreateImplicitSymbols( + const Symbol *symbol, std::optional setFlag = std::nullopt); }; template @@ -771,6 +776,19 @@ const parser::DoConstruct *DirectiveAttributeVisitor::GetDoConstructIf( 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) { @@ -785,13 +803,7 @@ 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; + return DeclareNewPrivateAccessEntity(object, flag, scope); } else { object.set(flag); return &object; @@ -2031,24 +2043,152 @@ void OmpAttributeVisitor::Post(const parser::OpenMPAllocatorsConstruct &x) { PopContext(); } +static bool IsPrivatizable(const Symbol *sym) { + auto *misc{sym->detailsIf()}; + return !IsProcedure(*sym) && !IsNamedConstant(*sym) && + !sym->owner().IsDerivedType() && + sym->owner().kind() != Scope::Kind::ImpliedDos && + !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 = [&]() { + const Symbol *hostSymbol = + lastDeclSymbol ? lastDeclSymbol : &symbol->GetUltimate(); + MakeAssocSymbol(symbol->name(), *hostSymbol, + context_.FindScope(dirContext.directiveSource)); + }; + 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(); + // 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. + assert(parallelDir || taskGenDir || teamsDir); + 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}; - auto IsPrivatizable = [](const Symbol *sym) { - auto *misc{sym->detailsIf()}; - return !IsProcedure(*sym) && !IsNamedConstant(*sym) && - !sym->owner().IsDerivedType() && - sym->owner().kind() != Scope::Kind::ImpliedDos && - !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)); - }; if (symbol && !dirContext_.empty() && GetContext().withinConstruct) { if (IsPrivatizable(symbol) && !IsObjectWithDSA(*symbol)) { @@ -2076,125 +2216,20 @@ void OmpAttributeVisitor::Post(const parser::Name &name) { if (found->test(semantics::Symbol::Flag::OmpThreadprivate)) return; } - 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() == 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) { - Symbol *hostSymbol = - lastDeclSymbol ? lastDeclSymbol : &symbol->GetUltimate(); - lastDeclSymbol = DeclarePrivateAccessEntity( - *hostSymbol, flag, context_.FindScope(dirContext.directiveSource)); - return lastDeclSymbol; - }; - auto makeSharedSymbol = [&]() { - Symbol *hostSymbol = - lastDeclSymbol ? lastDeclSymbol : &symbol->GetUltimate(); - MakeAssocSymbol(symbol->name(), *hostSymbol, - context_.FindScope(dirContext.directiveSource)); - }; - auto useLastDeclSymbol = [&]() { - if (lastDeclSymbol) - MakeAssocSymbol(symbol->name(), *lastDeclSymbol, - context_.FindScope(dirContext.directiveSource)); - }; - - 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(); - // 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. - assert(parallelDir || taskGenDir || teamsDir); - 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); + if (auto *stmtFunction{symbol->detailsIf()}; + stmtFunction && stmtFunction->stmtFunction()) { + // Each non-dummy argument from a statement function must be handled too, + // as if it was explicitly referenced. + semantics::UnorderedSymbolSet symbols{ + CollectSymbols(stmtFunction->stmtFunction().value())}; + for (const auto &sym : symbols) { + if (!IsStmtFunctionDummy(sym) && !IsObjectWithDSA(*sym)) { + CreateImplicitSymbols(&*sym, Symbol::Flag::OmpFromStmtFunction); } } - prevDSA = dsa; + } else { + CreateImplicitSymbols(symbol); } } // within OpenMP construct } diff --git a/flang/test/Lower/OpenMP/statement-function.f90 b/flang/test/Lower/OpenMP/statement-function.f90 new file mode 100644 index 00000000000000..6cdbcb6e141c7e --- /dev/null +++ b/flang/test/Lower/OpenMP/statement-function.f90 @@ -0,0 +1,43 @@ +! Test privatization within OpenMP constructs containing statement functions. +! RUN: %flang_fc1 -emit-hlfir -fopenmp -o - %s 2>&1 | FileCheck %s + +!CHECK-LABEL: func @_QPtest_implicit_use +!CHECK: %[[IEXP:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFtest_implicit_useEiexp"} : (!fir.ref) -> (!fir.ref, !fir.ref) +!CHECK: %[[IIMP:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFtest_implicit_useEiimp"} : (!fir.ref) -> (!fir.ref, !fir.ref) +!CHECK: omp.parallel private({{.*firstprivate.*}} %[[IEXP]]#0 -> %[[PRIV_IEXP:.*]] : !fir.ref, +!CHECK-SAME: {{.*firstprivate.*}} %[[IIMP]]#0 -> %[[PRIV_IIMP:.*]] : !fir.ref) +!CHECK: %{{.*}}:2 = hlfir.declare %[[PRIV_IEXP]] {uniq_name = "_QFtest_implicit_useEiexp"} : (!fir.ref) -> (!fir.ref, !fir.ref) +!CHECK: %{{.*}}:2 = hlfir.declare %[[PRIV_IIMP]] {uniq_name = "_QFtest_implicit_useEiimp"} : (!fir.ref) -> (!fir.ref, !fir.ref) +subroutine test_implicit_use() + implicit none + integer :: iexp, iimp + integer, external :: ifun + integer :: sf + + sf(iexp)=ifun(iimp)+iexp + !$omp parallel default(firstprivate) + iexp = sf(iexp) + !$omp end parallel +end subroutine + +!CHECK-LABEL: func @_QPtest_implicit_use2 +!CHECK: %[[IEXP:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFtest_implicit_use2Eiexp"} : (!fir.ref) -> (!fir.ref, !fir.ref) +!CHECK: %[[IIMP:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFtest_implicit_use2Eiimp"} : (!fir.ref) -> (!fir.ref, !fir.ref) +!CHECK: omp.task +!CHECK: %[[PRIV_IEXP:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFtest_implicit_use2Eiexp"} : (!fir.ref) -> (!fir.ref, !fir.ref) +!CHECK: %[[TEMP0:.*]] = fir.load %[[IEXP]]#0 : !fir.ref +!CHECK: hlfir.assign %[[TEMP0]] to %[[PRIV_IEXP]]#0 temporary_lhs : i32, !fir.ref +!CHECK: %[[PRIV_IIMP:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFtest_implicit_use2Eiimp"} : (!fir.ref) -> (!fir.ref, !fir.ref) +!CHECK: %[[TEMP1:.*]] = fir.load %[[IIMP]]#0 : !fir.ref +!CHECK: hlfir.assign %[[TEMP1]] to %[[PRIV_IIMP]]#0 temporary_lhs : i32, !fir.ref +subroutine test_implicit_use2() + implicit none + integer :: iexp, iimp + integer, external :: ifun + integer :: sf + + sf(iexp)=ifun(iimp) + !$omp task + iexp = sf(iexp) + !$omp end task +end subroutine