Skip to content

Commit 366eade

Browse files
authored
[flang][OpenMP] Reland Fix copyprivate semantic checks (#95799) (#101009)
There are some cases in which variables used in OpenMP constructs are predetermined as private. The semantic checks for copyprivate were not handling those cases. Besides that, shared symbols were not being properly represented in some cases. When there was no previously declared private (implicit) symbol, no new association symbols, representing shared ones, were being created. These symbols must always be inserted in constructs that may privatize the original symbol: parallel, teams and task generating constructs. Fixes #87214 and #86907
1 parent f9827e6 commit 366eade

18 files changed

+331
-98
lines changed

flang/include/flang/Semantics/tools.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ bool IsIntrinsicConcat(
8686
bool IsGenericDefinedOp(const Symbol &);
8787
bool IsDefinedOperator(SourceName);
8888
std::string MakeOpName(SourceName);
89+
bool IsCommonBlockContaining(const Symbol &, const Symbol &);
8990

9091
// Returns true if maybeAncestor exists and is a proper ancestor of a
9192
// descendent scope (or symbol owner). Will be false, unlike Scope::Contains(),

flang/lib/Semantics/resolve-directives.cpp

Lines changed: 102 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "flang/Parser/parse-tree.h"
2020
#include "flang/Parser/tools.h"
2121
#include "flang/Semantics/expression.h"
22+
#include "flang/Semantics/tools.h"
2223
#include <list>
2324
#include <map>
2425
#include <sstream>
@@ -729,7 +730,6 @@ class OmpAttributeVisitor : DirectiveAttributeVisitor<llvm::omp::Directive> {
729730
void CheckNameInAllocateStmt(const parser::CharBlock &source,
730731
const parser::Name &ompObject, const parser::AllocateStmt &allocate);
731732

732-
bool HasSymbolInEnclosingScope(const Symbol &, Scope &);
733733
std::int64_t ordCollapseLevel{0};
734734

735735
void AddOmpRequiresToScope(Scope &, WithOmpDeclarative::RequiresFlags,
@@ -2035,15 +2035,22 @@ void OmpAttributeVisitor::Post(const parser::OpenMPAllocatorsConstruct &x) {
20352035
// and adjust the symbol for each Name if necessary
20362036
void OmpAttributeVisitor::Post(const parser::Name &name) {
20372037
auto *symbol{name.symbol};
2038+
auto IsPrivatizable = [](const Symbol *sym) {
2039+
return !IsProcedure(*sym) && !IsNamedConstant(*sym) &&
2040+
!sym->owner().IsDerivedType() &&
2041+
sym->owner().kind() != Scope::Kind::ImpliedDos &&
2042+
!sym->detailsIf<semantics::AssocEntityDetails>() &&
2043+
!sym->detailsIf<semantics::NamelistDetails>();
2044+
};
2045+
20382046
if (symbol && !dirContext_.empty() && GetContext().withinConstruct) {
20392047
// Exclude construct-names
20402048
if (auto *details{symbol->detailsIf<semantics::MiscDetails>()}) {
20412049
if (details->kind() == semantics::MiscDetails::Kind::ConstructName) {
20422050
return;
20432051
}
20442052
}
2045-
if (!symbol->owner().IsDerivedType() && !IsProcedure(*symbol) &&
2046-
!IsObjectWithDSA(*symbol) && !IsNamedConstant(*symbol)) {
2053+
if (IsPrivatizable(symbol) && !IsObjectWithDSA(*symbol)) {
20472054
// TODO: create a separate function to go through the rules for
20482055
// predetermined, explicitly determined, and implicitly
20492056
// determined data-sharing attributes (2.15.1.1).
@@ -2068,6 +2075,9 @@ void OmpAttributeVisitor::Post(const parser::Name &name) {
20682075
if (found->test(semantics::Symbol::Flag::OmpThreadprivate))
20692076
return;
20702077
}
2078+
if (!IsPrivatizable(symbol)) {
2079+
return;
2080+
}
20712081

20722082
// Implicitly determined DSAs
20732083
// OMP 5.2 5.1.1 - Variables Referenced in a Construct
@@ -2085,16 +2095,22 @@ void OmpAttributeVisitor::Post(const parser::Name &name) {
20852095
}
20862096
}
20872097

2088-
// When handling each implicit rule, either a new private symbol is
2089-
// declared or the last declared symbol is used.
2090-
// In the latter case, it's necessary to insert a new symbol in the scope
2091-
// being processed, associated with the last declared symbol.
2092-
// This captures the fact that, although we are using the last declared
2093-
// symbol, its DSA could be different in this scope.
2094-
// Also, because of how symbols are collected in lowering, not inserting
2095-
// a new symbol in this scope could lead to the conclusion that the
2096-
// symbol was declared in this construct, which would result in wrong
2097-
// privatization code being generated.
2098+
// When handling each implicit rule for a given symbol, one of the
2099+
// following 3 actions may be taken:
2100+
// 1. Declare a new private symbol.
2101+
// 2. Create a new association symbol with no flags, that will represent
2102+
// a shared symbol in the current scope. Note that symbols without
2103+
// any private flags are considered as shared.
2104+
// 3. Use the last declared private symbol, by inserting a new symbol
2105+
// in the scope being processed, associated with it.
2106+
// If no private symbol was declared previously, then no association
2107+
// is needed and the symbol from the enclosing scope will be
2108+
// inherited by the current one.
2109+
//
2110+
// Because of how symbols are collected in lowering, not inserting a new
2111+
// symbol in the last case could lead to the conclusion that a symbol
2112+
// from an enclosing construct was declared in the current construct,
2113+
// which would result in wrong privatization code being generated.
20982114
// Consider the following example:
20992115
//
21002116
// !$omp parallel default(private) ! p1
@@ -2107,48 +2123,56 @@ void OmpAttributeVisitor::Post(const parser::Name &name) {
21072123
// (p2), it would use the x symbol definition from the enclosing scope.
21082124
// Then, when p2's default symbols were collected in lowering, the x
21092125
// symbol from the outer parallel construct (p1) would be collected, as
2110-
// it would have the private flag set (note that symbols that don't have
2111-
// any private flag are considered as shared).
2126+
// it would have the private flag set.
21122127
// This would make x appear to be defined in p2, causing it to be
21132128
// privatized in p2 and its privatization in p1 to be skipped.
2114-
auto declNewSymbol = [&](Symbol::Flag flag) {
2129+
auto makePrivateSymbol = [&](Symbol::Flag flag) {
21152130
Symbol *hostSymbol =
21162131
lastDeclSymbol ? lastDeclSymbol : &symbol->GetUltimate();
21172132
lastDeclSymbol = DeclarePrivateAccessEntity(
21182133
*hostSymbol, flag, context_.FindScope(dirContext.directiveSource));
21192134
return lastDeclSymbol;
21202135
};
2136+
auto makeSharedSymbol = [&]() {
2137+
Symbol *hostSymbol =
2138+
lastDeclSymbol ? lastDeclSymbol : &symbol->GetUltimate();
2139+
MakeAssocSymbol(symbol->name(), *hostSymbol,
2140+
context_.FindScope(dirContext.directiveSource));
2141+
};
21212142
auto useLastDeclSymbol = [&]() {
21222143
if (lastDeclSymbol)
21232144
MakeAssocSymbol(symbol->name(), *lastDeclSymbol,
21242145
context_.FindScope(dirContext.directiveSource));
21252146
};
21262147

2148+
bool taskGenDir = llvm::omp::taskGeneratingSet.test(dirContext.directive);
2149+
bool targetDir = llvm::omp::allTargetSet.test(dirContext.directive);
2150+
bool parallelDir = llvm::omp::allParallelSet.test(dirContext.directive);
2151+
bool teamsDir = llvm::omp::allTeamsSet.test(dirContext.directive);
2152+
21272153
if (dsa.has_value()) {
2128-
useLastDeclSymbol();
2154+
if (dsa.value() == Symbol::Flag::OmpShared &&
2155+
(parallelDir || taskGenDir || teamsDir))
2156+
makeSharedSymbol();
2157+
// Private symbols will have been declared already.
21292158
prevDSA = dsa;
21302159
continue;
21312160
}
21322161

2133-
bool taskGenDir = llvm::omp::taskGeneratingSet.test(dirContext.directive);
2134-
bool targetDir = llvm::omp::allTargetSet.test(dirContext.directive);
2135-
bool parallelDir = llvm::omp::allParallelSet.test(dirContext.directive);
2136-
21372162
if (dirContext.defaultDSA == Symbol::Flag::OmpPrivate ||
21382163
dirContext.defaultDSA == Symbol::Flag::OmpFirstPrivate ||
21392164
dirContext.defaultDSA == Symbol::Flag::OmpShared) {
21402165
// 1) default
21412166
// Allowed only with parallel, teams and task generating constructs.
2142-
assert(parallelDir || taskGenDir ||
2143-
llvm::omp::allTeamsSet.test(dirContext.directive));
2167+
assert(parallelDir || taskGenDir || teamsDir);
21442168
if (dirContext.defaultDSA != Symbol::Flag::OmpShared)
2145-
declNewSymbol(dirContext.defaultDSA);
2169+
makePrivateSymbol(dirContext.defaultDSA);
21462170
else
2147-
useLastDeclSymbol();
2171+
makeSharedSymbol();
21482172
dsa = dirContext.defaultDSA;
21492173
} else if (parallelDir) {
21502174
// 2) parallel -> shared
2151-
useLastDeclSymbol();
2175+
makeSharedSymbol();
21522176
dsa = Symbol::Flag::OmpShared;
21532177
} else if (!taskGenDir && !targetDir) {
21542178
// 3) enclosing context
@@ -2161,12 +2185,12 @@ void OmpAttributeVisitor::Post(const parser::Name &name) {
21612185
// TODO 5) dummy arg in orphaned taskgen construct -> firstprivate
21622186
if (prevDSA == Symbol::Flag::OmpShared) {
21632187
// 6) shared in enclosing context -> shared
2164-
useLastDeclSymbol();
2188+
makeSharedSymbol();
21652189
dsa = Symbol::Flag::OmpShared;
21662190
} else {
21672191
// 7) firstprivate
21682192
dsa = Symbol::Flag::OmpFirstPrivate;
2169-
declNewSymbol(*dsa)->set(Symbol::Flag::OmpImplicit);
2193+
makePrivateSymbol(*dsa)->set(Symbol::Flag::OmpImplicit);
21702194
}
21712195
}
21722196
prevDSA = dsa;
@@ -2570,20 +2594,59 @@ void ResolveOmpTopLevelParts(
25702594
});
25712595
}
25722596

2573-
void OmpAttributeVisitor::CheckDataCopyingClause(
2574-
const parser::Name &name, const Symbol &symbol, Symbol::Flag ompFlag) {
2575-
const auto *checkSymbol{&symbol};
2597+
static bool IsSymbolInCommonBlock(const Symbol &symbol) {
2598+
// TODO Improve the performance of this predicate function.
2599+
// Going through all symbols sequentially, in all common blocks, can be
2600+
// slow when there are many symbols. A possible optimization is to add
2601+
// an OmpInCommonBlock flag to Symbol, to make it possible to quickly
2602+
// test if a given symbol is in a common block.
2603+
for (const auto &cb : symbol.owner().commonBlocks()) {
2604+
if (IsCommonBlockContaining(cb.second.get(), symbol)) {
2605+
return true;
2606+
}
2607+
}
2608+
return false;
2609+
}
2610+
2611+
static bool IsSymbolThreadprivate(const Symbol &symbol) {
25762612
if (const auto *details{symbol.detailsIf<HostAssocDetails>()}) {
2577-
checkSymbol = &details->symbol();
2613+
return details->symbol().test(Symbol::Flag::OmpThreadprivate);
25782614
}
2615+
return symbol.test(Symbol::Flag::OmpThreadprivate);
2616+
}
25792617

2618+
static bool IsSymbolPrivate(const Symbol &symbol) {
2619+
if (symbol.test(Symbol::Flag::OmpPrivate) ||
2620+
symbol.test(Symbol::Flag::OmpFirstPrivate)) {
2621+
return true;
2622+
}
2623+
// A symbol that has not gone through constructs that may privatize the
2624+
// original symbol may be predetermined as private.
2625+
// (OMP 5.2 5.1.1 - Variables Referenced in a Construct)
2626+
if (symbol == symbol.GetUltimate()) {
2627+
switch (symbol.owner().kind()) {
2628+
case Scope::Kind::MainProgram:
2629+
case Scope::Kind::Subprogram:
2630+
case Scope::Kind::BlockConstruct:
2631+
return !symbol.attrs().test(Attr::SAVE) &&
2632+
!symbol.attrs().test(Attr::PARAMETER) && !IsAssumedShape(symbol) &&
2633+
!IsSymbolInCommonBlock(symbol);
2634+
default:
2635+
return false;
2636+
}
2637+
}
2638+
return false;
2639+
}
2640+
2641+
void OmpAttributeVisitor::CheckDataCopyingClause(
2642+
const parser::Name &name, const Symbol &symbol, Symbol::Flag ompFlag) {
25802643
if (ompFlag == Symbol::Flag::OmpCopyIn) {
25812644
// List of items/objects that can appear in a 'copyin' clause must be
25822645
// 'threadprivate'
2583-
if (!checkSymbol->test(Symbol::Flag::OmpThreadprivate)) {
2646+
if (!IsSymbolThreadprivate(symbol)) {
25842647
context_.Say(name.source,
25852648
"Non-THREADPRIVATE object '%s' in COPYIN clause"_err_en_US,
2586-
checkSymbol->name());
2649+
symbol.name());
25872650
}
25882651
} else if (ompFlag == Symbol::Flag::OmpCopyPrivate &&
25892652
GetContext().directive == llvm::omp::Directive::OMPD_single) {
@@ -2596,18 +2659,13 @@ void OmpAttributeVisitor::CheckDataCopyingClause(
25962659
"COPYPRIVATE variable '%s' may not appear on a PRIVATE or "
25972660
"FIRSTPRIVATE clause on a SINGLE construct"_err_en_US,
25982661
symbol.name());
2599-
} else {
2662+
} else if (!IsSymbolThreadprivate(symbol) && !IsSymbolPrivate(symbol)) {
26002663
// List of items/objects that can appear in a 'copyprivate' clause must be
26012664
// either 'private' or 'threadprivate' in enclosing context.
2602-
if (!checkSymbol->test(Symbol::Flag::OmpThreadprivate) &&
2603-
!(HasSymbolInEnclosingScope(symbol, currScope()) &&
2604-
(symbol.test(Symbol::Flag::OmpPrivate) ||
2605-
symbol.test(Symbol::Flag::OmpFirstPrivate)))) {
2606-
context_.Say(name.source,
2607-
"COPYPRIVATE variable '%s' is not PRIVATE or THREADPRIVATE in "
2608-
"outer context"_err_en_US,
2609-
symbol.name());
2610-
}
2665+
context_.Say(name.source,
2666+
"COPYPRIVATE variable '%s' is not PRIVATE or THREADPRIVATE in "
2667+
"outer context"_err_en_US,
2668+
symbol.name());
26112669
}
26122670
}
26132671
}
@@ -2677,12 +2735,6 @@ void OmpAttributeVisitor::CheckLabelContext(const parser::CharBlock source,
26772735
}
26782736
}
26792737

2680-
bool OmpAttributeVisitor::HasSymbolInEnclosingScope(
2681-
const Symbol &symbol, Scope &scope) {
2682-
const auto symbols{scope.parent().GetSymbols()};
2683-
return llvm::is_contained(symbols, symbol);
2684-
}
2685-
26862738
// Goes through the names in an OmpObjectList and checks if each name appears
26872739
// in the given allocate statement
26882740
void OmpAttributeVisitor::CheckAllNamesInAllocateStmt(

flang/test/Lower/OpenMP/associate.f90

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
! Check that constructs with associate and variables that have implicitly
2+
! determined DSAs are lowered properly.
3+
! RUN: %flang_fc1 -emit-hlfir -fopenmp -o - %s 2>&1 | FileCheck %s
4+
5+
!CHECK-LABEL: func @_QPtest_parallel_assoc
6+
!CHECK: omp.parallel {
7+
!CHECK-NOT: hlfir.declare {{.*}} {uniq_name = "_QFtest_parallel_assocEa"}
8+
!CHECK-NOT: hlfir.declare {{.*}} {uniq_name = "_QFtest_parallel_assocEb"}
9+
!CHECK: omp.wsloop {
10+
!CHECK: }
11+
!CHECK: }
12+
!CHECK: omp.parallel {
13+
!CHECK-NOT: hlfir.declare {{.*}} {uniq_name = "_QFtest_parallel_assocEb"}
14+
!CHECK: omp.wsloop {
15+
!CHECK: }
16+
!CHECK: }
17+
subroutine test_parallel_assoc()
18+
integer, parameter :: l = 3
19+
integer :: a(l)
20+
integer :: i
21+
a = 1
22+
23+
!$omp parallel do
24+
do i = 1,l
25+
associate (b=>a)
26+
b(i) = b(i) * 2
27+
end associate
28+
enddo
29+
!$omp end parallel do
30+
31+
!$omp parallel do default(private)
32+
do i = 1,l
33+
associate (b=>a)
34+
b(i) = b(i) * 2
35+
end associate
36+
enddo
37+
!$omp end parallel do
38+
end subroutine
Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
!RUN: %flang_fc1 -emit-hlfir -fopenmp %s -o - | FileCheck %s
22

3-
!CHECK: @_QPsb
3+
!CHECK-LABEL: func @_QPsb
44
subroutine sb(a)
55
integer :: a(:)
66
!CHECK: omp.parallel
@@ -9,3 +9,16 @@ subroutine sb(a)
99
if (any(a/=(/(100,i=1,5)/))) print *, "OK"
1010
!$omp end parallel
1111
end subroutine
12+
13+
!CHECK-LABEL: func @_QPsb2
14+
subroutine sb2()
15+
integer, parameter :: SIZE=20
16+
integer :: i, a(SIZE)
17+
18+
! Just check that the construct below doesn't hit a TODO in lowering.
19+
!CHECK: omp.parallel
20+
!$omp parallel
21+
a = [ (i, i=1, SIZE) ]
22+
print *, i
23+
!$omp end parallel
24+
end subroutine

0 commit comments

Comments
 (0)