Skip to content

Commit

Permalink
[flang][OpenMP] Privatize vars referenced in statement functions (llv…
Browse files Browse the repository at this point in the history
…m#103390)

Variables referenced in the body of statement functions need to be
handled as if they are explicitly referenced. Otherwise, they are
skipped during implicit privatization, because statement functions
are represented as procedures in the parse tree.

To avoid missing symbols referenced only in statement functions
during implicit privatization, new symbols, associated with them,
are created and inserted into the context of the directive that
privatizes them. They are later collected and processed in
lowering. To avoid confusing these new symbols with regular ones,
they are tagged with the new OmpFromStmtFunction flag.

Fixes llvm#74273
  • Loading branch information
luporl authored Aug 26, 2024
1 parent 54eb89f commit 216ba6b
Show file tree
Hide file tree
Showing 4 changed files with 227 additions and 140 deletions.
2 changes: 1 addition & 1 deletion flang/include/flang/Semantics/symbol.h
Original file line number Diff line number Diff line change
Expand Up @@ -755,7 +755,7 @@ class Symbol {
OmpDeclarativeAllocateDirective, OmpExecutableAllocateDirective,
OmpDeclareSimd, OmpDeclareTarget, OmpThreadprivate, OmpDeclareReduction,
OmpFlushed, OmpCriticalLock, OmpIfSpecified, OmpNone, OmpPreDetermined,
OmpImplicit);
OmpImplicit, OmpFromStmtFunction);
using Flags = common::EnumSet<Flag, Flag_enumSize>;

const Scope &owner() const { return *owner_; }
Expand Down
9 changes: 9 additions & 0 deletions flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<const semantics::Symbol *> symbolsInNestedRegions;
collectSymbolsInNestedRegions(eval, flag, symbolsInNestedRegions);

Expand Down
313 changes: 174 additions & 139 deletions flang/lib/Semantics/resolve-directives.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,12 @@ template <typename T> 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) {
Expand All @@ -108,6 +109,7 @@ template <typename T> 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 &);
Expand Down Expand Up @@ -736,6 +738,9 @@ class OmpAttributeVisitor : DirectiveAttributeVisitor<llvm::omp::Directive> {
std::optional<common::OmpAtomicDefaultMemOrderType>);
void IssueNonConformanceWarning(
llvm::omp::Directive D, parser::CharBlock source);

void CreateImplicitSymbols(
const Symbol *symbol, std::optional<Symbol::Flag> setFlag = std::nullopt);
};

template <typename T>
Expand Down Expand Up @@ -771,6 +776,19 @@ const parser::DoConstruct *DirectiveAttributeVisitor<T>::GetDoConstructIf(
return parser::Unwrap<parser::DoConstruct>(x);
}

template <typename T>
Symbol *DirectiveAttributeVisitor<T>::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 <typename T>
Symbol *DirectiveAttributeVisitor<T>::DeclarePrivateAccessEntity(
const parser::Name &name, Symbol::Flag flag, Scope &scope) {
Expand All @@ -785,13 +803,7 @@ template <typename T>
Symbol *DirectiveAttributeVisitor<T>::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;
Expand Down Expand Up @@ -2031,24 +2043,152 @@ void OmpAttributeVisitor::Post(const parser::OpenMPAllocatorsConstruct &x) {
PopContext();
}

static bool IsPrivatizable(const Symbol *sym) {
auto *misc{sym->detailsIf<MiscDetails>()};
return !IsProcedure(*sym) && !IsNamedConstant(*sym) &&
!sym->owner().IsDerivedType() &&
sym->owner().kind() != Scope::Kind::ImpliedDos &&
!sym->detailsIf<semantics::AssocEntityDetails>() &&
!sym->detailsIf<semantics::NamelistDetails>() &&
(!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<Symbol::Flag> setFlag) {
if (!IsPrivatizable(symbol)) {
return;
}

// Implicitly determined DSAs
// OMP 5.2 5.1.1 - Variables Referenced in a Construct
Symbol *lastDeclSymbol = nullptr;
std::optional<Symbol::Flag> prevDSA;
for (int dirDepth{0}; dirDepth < (int)dirContext_.size(); ++dirDepth) {
DirContext &dirContext = dirContext_[dirDepth];
std::optional<Symbol::Flag> 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<MiscDetails>()};
return !IsProcedure(*sym) && !IsNamedConstant(*sym) &&
!sym->owner().IsDerivedType() &&
sym->owner().kind() != Scope::Kind::ImpliedDos &&
!sym->detailsIf<semantics::AssocEntityDetails>() &&
!sym->detailsIf<semantics::NamelistDetails>() &&
(!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)) {
Expand Down Expand Up @@ -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<Symbol::Flag> prevDSA;
for (int dirDepth{0}; dirDepth < (int)dirContext_.size(); ++dirDepth) {
DirContext &dirContext = dirContext_[dirDepth];
std::optional<Symbol::Flag> 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<semantics::SubprogramDetails>()};
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
}
Expand Down
Loading

0 comments on commit 216ba6b

Please sign in to comment.