Skip to content

[flang][OpenMP] Support tasks' implicit firstprivate DSA #85989

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion flang/include/flang/Semantics/symbol.h
Original file line number Diff line number Diff line change
Expand Up @@ -745,7 +745,8 @@ class Symbol {
OmpCommonBlock, OmpReduction, OmpAligned, OmpNontemporal, OmpAllocate,
OmpDeclarativeAllocateDirective, OmpExecutableAllocateDirective,
OmpDeclareSimd, OmpDeclareTarget, OmpThreadprivate, OmpDeclareReduction,
OmpFlushed, OmpCriticalLock, OmpIfSpecified, OmpNone, OmpPreDetermined);
OmpFlushed, OmpCriticalLock, OmpIfSpecified, OmpNone, OmpPreDetermined,
OmpImplicit);
using Flags = common::EnumSet<Flag, Flag_enumSize>;

const Scope &owner() const { return *owner_; }
Expand Down
117 changes: 104 additions & 13 deletions flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ void DataSharingProcessor::processStep1(
llvm::SmallVectorImpl<const Fortran::semantics::Symbol *> *privateSyms) {
collectSymbolsForPrivatization();
collectDefaultSymbols();
collectImplicitSymbols();
privatize(clauseOps, privateSyms);
defaultPrivatize(clauseOps, privateSyms);
implicitPrivatize(clauseOps, privateSyms);
insertBarrier();
}

Expand Down Expand Up @@ -302,6 +304,48 @@ void DataSharingProcessor::insertLastPrivateCompare(mlir::Operation *op) {
}
}

static const Fortran::parser::CharBlock *
getSource(const Fortran::semantics::SemanticsContext &semaCtx,
const Fortran::lower::pft::Evaluation &eval) {
const Fortran::parser::CharBlock *source = nullptr;

auto ompConsVisit = [&](const Fortran::parser::OpenMPConstruct &x) {
std::visit(Fortran::common::visitors{
[&](const Fortran::parser::OpenMPSectionsConstruct &x) {
source = &std::get<0>(x.t).source;
},
[&](const Fortran::parser::OpenMPLoopConstruct &x) {
source = &std::get<0>(x.t).source;
},
[&](const Fortran::parser::OpenMPBlockConstruct &x) {
source = &std::get<0>(x.t).source;
},
[&](const Fortran::parser::OpenMPCriticalConstruct &x) {
source = &std::get<0>(x.t).source;
},
[&](const Fortran::parser::OpenMPAtomicConstruct &x) {
std::visit([&](const auto &x) { source = &x.source; },
x.u);
},
[&](const auto &x) { source = &x.source; },
},
x.u);
};

eval.visit(Fortran::common::visitors{
[&](const Fortran::parser::OpenMPConstruct &x) { ompConsVisit(x); },
[&](const Fortran::parser::OpenMPDeclarativeConstruct &x) {
source = &x.source;
},
[&](const Fortran::parser::OmpEndLoopDirective &x) {
source = &x.source;
},
[&](const auto &x) {},
});

return source;
}

void DataSharingProcessor::collectSymbolsInNestedRegions(
Fortran::lower::pft::Evaluation &eval,
Fortran::semantics::Symbol::Flag flag,
Expand Down Expand Up @@ -329,11 +373,49 @@ void DataSharingProcessor::collectSymbolsInNestedRegions(
// Later, in current context, all symbols in the set
// `defaultSymbols` - `symbolsInNestedRegions` will be privatized.
void DataSharingProcessor::collectSymbols(
Fortran::semantics::Symbol::Flag flag) {
converter.collectSymbolSet(eval, defaultSymbols, flag,
Fortran::semantics::Symbol::Flag flag,
llvm::SetVector<const Fortran::semantics::Symbol *> &symbols) {
// Collect all scopes associated with 'eval'.
llvm::SetVector<const Fortran::semantics::Scope *> clauseScopes;
std::function<void(const Fortran::semantics::Scope *)> collectScopes =
[&](const Fortran::semantics::Scope *scope) {
clauseScopes.insert(scope);
for (const Fortran::semantics::Scope &child : scope->children())
collectScopes(&child);
};
const Fortran::parser::CharBlock *source =
clauses.empty() ? getSource(semaCtx, eval) : &clauses.front().source;
const Fortran::semantics::Scope *curScope = nullptr;
if (source && !source->empty()) {
curScope = &semaCtx.FindScope(*source);
collectScopes(curScope);
}
// Collect all symbols referenced in the evaluation being processed,
// that matches 'flag'.
llvm::SetVector<const Fortran::semantics::Symbol *> allSymbols;
converter.collectSymbolSet(eval, allSymbols, flag,
/*collectSymbols=*/true,
/*collectHostAssociatedSymbols=*/true);
llvm::SetVector<const Fortran::semantics::Symbol *> symbolsInNestedRegions;
collectSymbolsInNestedRegions(eval, flag, symbolsInNestedRegions);
// Filter-out symbols that must not be privatized.
bool collectImplicit = flag == Fortran::semantics::Symbol::Flag::OmpImplicit;
auto isPrivatizable = [](const Fortran::semantics::Symbol &sym) -> bool {
return !Fortran::semantics::IsProcedure(sym) &&
!sym.GetUltimate().has<Fortran::semantics::DerivedTypeDetails>() &&
!sym.GetUltimate().has<Fortran::semantics::NamelistDetails>() &&
!Fortran::semantics::IsImpliedDoIndex(sym.GetUltimate());
};
for (const auto *sym : allSymbols) {
assert(curScope && "couldn't find current scope");
if (isPrivatizable(*sym) && !symbolsInNestedRegions.contains(sym) &&
!privatizedSymbols.contains(sym) &&
!sym->test(Fortran::semantics::Symbol::Flag::OmpPreDetermined) &&
(collectImplicit ||
!sym->test(Fortran::semantics::Symbol::Flag::OmpImplicit)) &&
clauseScopes.contains(&sym->owner()))
symbols.insert(sym);
}
}

void DataSharingProcessor::collectDefaultSymbols() {
Expand All @@ -342,13 +424,22 @@ void DataSharingProcessor::collectDefaultSymbols() {
if (const auto *defaultClause =
std::get_if<omp::clause::Default>(&clause.u)) {
if (defaultClause->v == DataSharingAttribute::Private)
collectSymbols(Fortran::semantics::Symbol::Flag::OmpPrivate);
collectSymbols(Fortran::semantics::Symbol::Flag::OmpPrivate,
defaultSymbols);
else if (defaultClause->v == DataSharingAttribute::Firstprivate)
collectSymbols(Fortran::semantics::Symbol::Flag::OmpFirstPrivate);
collectSymbols(Fortran::semantics::Symbol::Flag::OmpFirstPrivate,
defaultSymbols);
}
}
}

void DataSharingProcessor::collectImplicitSymbols() {
// There will be no implicit symbols when a default clause is present.
if (defaultSymbols.empty())
collectSymbols(Fortran::semantics::Symbol::Flag::OmpImplicit,
implicitSymbols);
}

void DataSharingProcessor::privatize(
mlir::omp::PrivateClauseOps *clauseOps,
llvm::SmallVectorImpl<const Fortran::semantics::Symbol *> *privateSyms) {
Expand Down Expand Up @@ -378,15 +469,15 @@ void DataSharingProcessor::copyLastPrivatize(mlir::Operation *op) {
void DataSharingProcessor::defaultPrivatize(
mlir::omp::PrivateClauseOps *clauseOps,
llvm::SmallVectorImpl<const Fortran::semantics::Symbol *> *privateSyms) {
for (const Fortran::semantics::Symbol *sym : defaultSymbols) {
if (!Fortran::semantics::IsProcedure(*sym) &&
!sym->GetUltimate().has<Fortran::semantics::DerivedTypeDetails>() &&
!sym->GetUltimate().has<Fortran::semantics::NamelistDetails>() &&
!Fortran::semantics::IsImpliedDoIndex(sym->GetUltimate()) &&
!symbolsInNestedRegions.contains(sym) &&
!privatizedSymbols.contains(sym))
doPrivatize(sym, clauseOps, privateSyms);
}
for (const Fortran::semantics::Symbol *sym : defaultSymbols)
doPrivatize(sym, clauseOps, privateSyms);
}

void DataSharingProcessor::implicitPrivatize(
mlir::omp::PrivateClauseOps *clauseOps,
llvm::SmallVectorImpl<const Fortran::semantics::Symbol *> *privateSyms) {
for (const Fortran::semantics::Symbol *sym : implicitSymbols)
doPrivatize(sym, clauseOps, privateSyms);
}

void DataSharingProcessor::doPrivatize(
Expand Down
13 changes: 10 additions & 3 deletions flang/lib/Lower/OpenMP/DataSharingProcessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,21 @@ class DataSharingProcessor {
// Symbols in private, firstprivate, and/or lastprivate clauses.
llvm::SetVector<const Fortran::semantics::Symbol *> privatizedSymbols;
llvm::SetVector<const Fortran::semantics::Symbol *> defaultSymbols;
llvm::SetVector<const Fortran::semantics::Symbol *> symbolsInNestedRegions;
llvm::SetVector<const Fortran::semantics::Symbol *> implicitSymbols;
llvm::DenseMap<const Fortran::semantics::Symbol *, mlir::omp::PrivateClauseOp>
symToPrivatizer;
Fortran::lower::AbstractConverter &converter;
Fortran::semantics::SemanticsContext &semaCtx;
fir::FirOpBuilder &firOpBuilder;
omp::List<omp::Clause> clauses;
Fortran::lower::pft::Evaluation &eval;
bool useDelayedPrivatization;
Fortran::lower::SymMap *symTable;

bool needBarrier();
void collectSymbols(Fortran::semantics::Symbol::Flag flag);
void
collectSymbols(Fortran::semantics::Symbol::Flag flag,
llvm::SetVector<const Fortran::semantics::Symbol *> &symbols);
void collectSymbolsInNestedRegions(
Fortran::lower::pft::Evaluation &eval,
Fortran::semantics::Symbol::Flag flag,
Expand All @@ -62,12 +65,16 @@ class DataSharingProcessor {
void collectSymbolsForPrivatization();
void insertBarrier();
void collectDefaultSymbols();
void collectImplicitSymbols();
void privatize(
mlir::omp::PrivateClauseOps *clauseOps,
llvm::SmallVectorImpl<const Fortran::semantics::Symbol *> *privateSyms);
void defaultPrivatize(
mlir::omp::PrivateClauseOps *clauseOps,
llvm::SmallVectorImpl<const Fortran::semantics::Symbol *> *privateSyms);
void implicitPrivatize(
mlir::omp::PrivateClauseOps *clauseOps,
llvm::SmallVectorImpl<const Fortran::semantics::Symbol *> *privateSyms);
void doPrivatize(
const Fortran::semantics::Symbol *sym,
mlir::omp::PrivateClauseOps *clauseOps,
Expand All @@ -89,7 +96,7 @@ class DataSharingProcessor {
Fortran::lower::pft::Evaluation &eval,
bool useDelayedPrivatization = false,
Fortran::lower::SymMap *symTable = nullptr)
: hasLastPrivateOp(false), converter(converter),
: hasLastPrivateOp(false), converter(converter), semaCtx(semaCtx),
firOpBuilder(converter.getFirOpBuilder()), clauses(clauses), eval(eval),
useDelayedPrivatization(useDelayedPrivatization), symTable(symTable) {}

Expand Down
106 changes: 90 additions & 16 deletions flang/lib/Semantics/resolve-directives.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2028,34 +2028,108 @@ void OmpAttributeVisitor::Post(const parser::Name &name) {
if (found->test(semantics::Symbol::Flag::OmpThreadprivate))
return;
}
std::vector<Symbol *> defaultDSASymbols;

// 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];
bool hasDataSharingAttr{false};
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()) {
hasDataSharingAttr = true;
dsa = symMap.second;
break;
}
}
if (hasDataSharingAttr) {
if (defaultDSASymbols.size())
symbol = &MakeAssocSymbol(symbol->name(), *defaultDSASymbols.back(),

// When handling each implicit rule, either a new private symbol is
// declared or the last declared symbol is used.
// In the latter case, it's necessary to insert a new symbol in the scope
// being processed, associated with the last declared symbol.
// This captures the fact that, although we are using the last declared
// symbol, its DSA could be different in this scope.
// Also, because of how symbols are collected in lowering, not inserting
// a new symbol in this scope could lead to the conclusion that the
// symbol was declared in this 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 (note that symbols that don't have
// any private flag are considered as shared).
// 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 declNewSymbol = [&](Symbol::Flag flag) {
Symbol *hostSymbol =
lastDeclSymbol ? lastDeclSymbol : &symbol->GetUltimate();
lastDeclSymbol = DeclarePrivateAccessEntity(
*hostSymbol, flag, context_.FindScope(dirContext.directiveSource));
return lastDeclSymbol;
};
auto useLastDeclSymbol = [&]() {
if (lastDeclSymbol)
MakeAssocSymbol(symbol->name(), *lastDeclSymbol,
context_.FindScope(dirContext.directiveSource));
};

if (dsa.has_value()) {
useLastDeclSymbol();
prevDSA = dsa;
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));
bool taskGenDir = llvm::omp::taskGeneratingSet.test(dirContext.directive);
bool targetDir = llvm::omp::allTargetSet.test(dirContext.directive);
bool parallelDir = llvm::omp::allParallelSet.test(dirContext.directive);

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 ||
llvm::omp::allTeamsSet.test(dirContext.directive));
if (dirContext.defaultDSA != Symbol::Flag::OmpShared)
declNewSymbol(dirContext.defaultDSA);
else
useLastDeclSymbol();
dsa = dirContext.defaultDSA;
} else if (parallelDir) {
// 2) parallel -> shared
useLastDeclSymbol();
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
useLastDeclSymbol();
dsa = Symbol::Flag::OmpShared;
} else {
// 7) firstprivate
dsa = Symbol::Flag::OmpFirstPrivate;
declNewSymbol(*dsa)->set(Symbol::Flag::OmpImplicit);
}
}
prevDSA = dsa;
}
} // within OpenMP construct
}
Expand Down
4 changes: 1 addition & 3 deletions flang/test/Lower/OpenMP/default-clause-byref.f90
Original file line number Diff line number Diff line change
Expand Up @@ -240,14 +240,12 @@ subroutine nested_default_clause_tests
!CHECK: omp.terminator
!CHECK: }
!CHECK: omp.parallel {
!CHECK: %[[PRIVATE_INNER_Z:.*]] = fir.alloca i32 {bindc_name = "z", pinned, uniq_name = "_QFnested_default_clause_testsEz"}
!CHECK: %[[PRIVATE_INNER_Z_DECL:.*]]:2 = hlfir.declare %[[PRIVATE_INNER_Z]] {uniq_name = "_QFnested_default_clause_testsEz"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
!CHECK: %[[PRIVATE_INNER_W:.*]] = fir.alloca i32 {bindc_name = "w", pinned, uniq_name = "_QFnested_default_clause_testsEw"}
!CHECK: %[[PRIVATE_INNER_W_DECL:.*]]:2 = hlfir.declare %[[PRIVATE_INNER_W]] {uniq_name = "_QFnested_default_clause_testsEw"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
!CHECK: %[[PRIVATE_INNER_X:.*]] = fir.alloca i32 {bindc_name = "x", pinned, uniq_name = "_QFnested_default_clause_testsEx"}
!CHECK: %[[PRIVATE_INNER_X_DECL:.*]]:2 = hlfir.declare %[[PRIVATE_INNER_X]] {uniq_name = "_QFnested_default_clause_testsEx"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
!CHECK: %[[TEMP_1:.*]] = fir.load %[[PRIVATE_INNER_X_DECL]]#0 : !fir.ref<i32>
!CHECK: %[[TEMP_2:.*]] = fir.load %[[PRIVATE_INNER_Z_DECL]]#0 : !fir.ref<i32>
!CHECK: %[[TEMP_2:.*]] = fir.load %[[PRIVATE_Z_DECL]]#0 : !fir.ref<i32>
!CHECK: %[[RESULT:.*]] = arith.addi %{{.*}}, %{{.*}} : i32
!CHECK: hlfir.assign %[[RESULT]] to %[[PRIVATE_INNER_W_DECL]]#0 : i32, !fir.ref<i32>
!CHECK: omp.terminator
Expand Down
4 changes: 1 addition & 3 deletions flang/test/Lower/OpenMP/default-clause.f90
Original file line number Diff line number Diff line change
Expand Up @@ -250,14 +250,12 @@ subroutine nested_default_clause_test1
!CHECK: omp.terminator
!CHECK: }
!CHECK: omp.parallel {
!CHECK: %[[PRIVATE_INNER_Z:.*]] = fir.alloca i32 {bindc_name = "z", pinned, uniq_name = "_QFnested_default_clause_test2Ez"}
!CHECK: %[[PRIVATE_INNER_Z_DECL:.*]]:2 = hlfir.declare %[[PRIVATE_INNER_Z]] {uniq_name = "_QFnested_default_clause_test2Ez"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
!CHECK: %[[PRIVATE_INNER_W:.*]] = fir.alloca i32 {bindc_name = "w", pinned, uniq_name = "_QFnested_default_clause_test2Ew"}
!CHECK: %[[PRIVATE_INNER_W_DECL:.*]]:2 = hlfir.declare %[[PRIVATE_INNER_W]] {uniq_name = "_QFnested_default_clause_test2Ew"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
!CHECK: %[[PRIVATE_INNER_X:.*]] = fir.alloca i32 {bindc_name = "x", pinned, uniq_name = "_QFnested_default_clause_test2Ex"}
!CHECK: %[[PRIVATE_INNER_X_DECL:.*]]:2 = hlfir.declare %[[PRIVATE_INNER_X]] {uniq_name = "_QFnested_default_clause_test2Ex"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
!CHECK: %[[TEMP_1:.*]] = fir.load %[[PRIVATE_INNER_X_DECL]]#0 : !fir.ref<i32>
!CHECK: %[[TEMP_2:.*]] = fir.load %[[PRIVATE_INNER_Z_DECL]]#0 : !fir.ref<i32>
!CHECK: %[[TEMP_2:.*]] = fir.load %[[PRIVATE_Z_DECL]]#0 : !fir.ref<i32>
!CHECK: %[[RESULT:.*]] = arith.addi %{{.*}}, %{{.*}} : i32
!CHECK: hlfir.assign %[[RESULT]] to %[[PRIVATE_INNER_W_DECL]]#0 : i32, !fir.ref<i32>
!CHECK: omp.terminator
Expand Down
Loading
Loading