[flang-commits] [flang] [flang][OpenMP] Privatize loop IVs in the innermost parallel (PR #227486)
Leandro Lupori via flang-commits
flang-commits at lists.llvm.org
Tue Sep 29 14:52:25 PDT 2026
https://github.com/luporl created https://github.com/llvm/llvm-project/pull/227486
A sequential loop's iteration variable is predetermined private in the
innermost parallel, teams or task-generating construct that encloses
the loop. When the loop was nested in another construct, such as a
worksharing loop, lowering failed to privatize the variable in the
enclosing parallel region. Instead, it created a new local copy inside
the nested construct, so the parallel region's other references to the
variable used the shared host variable.
Fix this by deciding which construct privatizes a symbol based on the
scope that owns it. This also simplifies DataSharingProcessor: the
OMPConstructSymbolVisitor, which walked the parse tree to track where
symbols were defined, is no longer needed and has been removed.
I have noticed that metadirectives don't always own the symbols that
should be privatized in them, as semantics doesn't create a new scope.
I'm not very familiar with metadirectives, but it seems this causes some
privatization issues with non-explicitly specified DSAs, as
DataSharingProcessor relies on ownership information to determine which
symbols should be privatized. These issues already existed before this
change, and I haven't seen any test regressions after it. I've run the
gfortran and Fujitsu testsuites.
Fixes #207125
Assisted-By: Claude Code Opus 5.5 (tests and documentation only)
>From 427d5402bdbe307da4a0d10dc24ba2bb8edb84fe Mon Sep 17 00:00:00 2001
From: Leandro Lupori <leandro.lupori at linaro.org>
Date: Fri, 25 Sep 2026 18:20:22 -0300
Subject: [PATCH] [flang][OpenMP] Privatize loop IVs in the innermost parallel
A sequential loop's iteration variable is predetermined private in the
innermost parallel, teams or task-generating construct that encloses
the loop. When the loop was nested in another construct, such as a
worksharing loop, lowering failed to privatize the variable in the
enclosing parallel region. Instead, it created a new local copy inside
the nested construct, so the parallel region's other references to the
variable used the shared host variable.
Fix this by deciding which construct privatizes a symbol based on the
scope that owns it. This also simplifies DataSharingProcessor: the
OMPConstructSymbolVisitor, which walked the parse tree to track where
symbols were defined, is no longer needed and has been removed.
I have noticed that metadirectives don't always own the symbols that
should be privatized in them, as semantics doesn't create a new scope.
I'm not very familiar with metadirectives, but it seems this causes some
privatization issues with non-explicitly specified DSAs, as
DataSharingProcessor relies on ownership information to determine which
symbols should be privatized. These issues already existed before this
change, and I haven't seen any test regressions after it. I've run the
gfortran and Fujitsu testsuites.
Fixes #207125
Assisted-By: Claude Code Opus 5.5 (tests and documentation only)
---
flang/lib/Lower/Bridge.cpp | 9 +-
.../lib/Lower/OpenMP/DataSharingProcessor.cpp | 197 ++++++-----------
flang/lib/Lower/OpenMP/DataSharingProcessor.h | 73 ------
flang/test/Lower/OpenMP/lastprivate-simd.f90 | 5 +-
.../test/Lower/OpenMP/predetermined-do-iv.f90 | 208 ++++++++++++++++++
flang/test/Lower/OpenMP/shared-loop.f90 | 10 +-
flang/test/Lower/OpenMP/taskgroup02.f90 | 3 +-
flang/test/Lower/OpenMP/unstructured.f90 | 15 +-
8 files changed, 292 insertions(+), 228 deletions(-)
create mode 100644 flang/test/Lower/OpenMP/predetermined-do-iv.f90
diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 784a87cc61997..0dca0c0677012 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -1640,14 +1640,7 @@ class FirConverter : public Fortran::lower::AbstractConverter {
mlir::Value genLoopVariableAddress(mlir::Location loc,
const Fortran::semantics::Symbol &sym,
bool isUnordered) {
- if (!shallowLookupSymbol(sym) &&
- (isUnordered ||
- GetSymbolDSA(sym).test(Fortran::semantics::Symbol::Flag::OmpPrivate) ||
- GetSymbolDSA(sym).test(
- Fortran::semantics::Symbol::Flag::OmpFirstPrivate) ||
- GetSymbolDSA(sym).test(
- Fortran::semantics::Symbol::Flag::OmpLastPrivate) ||
- GetSymbolDSA(sym).test(Fortran::semantics::Symbol::Flag::OmpLinear))) {
+ if (!shallowLookupSymbol(sym) && isUnordered) {
// Do concurrent loop variables are not mapped yet since they are
// local to the Do concurrent scope (same for OpenMP loops).
mlir::OpBuilder::InsertPoint insPt = builder->saveInsertionPoint();
diff --git a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
index 8d0d191058cfb..142735d5ebe52 100644
--- a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
@@ -35,35 +35,6 @@
namespace Fortran {
namespace lower {
namespace omp {
-bool DataSharingProcessor::OMPConstructSymbolVisitor::isSymbolDefineBy(
- const semantics::Symbol *symbol, lower::pft::Evaluation &eval) const {
- auto definition = symDefMap.find(symbol);
- if (definition == symDefMap.end())
- return false;
-
- // The selected variant is not an enclosing parse-tree construct, so its
- // IVs map to a null construct. Admit only IVs owned by this variant because
- // enclosing IVs can map to null too.
- if (isMetadirectiveLoop)
- return metadirectiveLoopIVs.contains(symbol) &&
- definition->second ==
- ConstructPtr(
- static_cast<const parser::OpenMPConstruct *>(nullptr));
-
- return eval.visit(common::visitors{
- [&](const parser::OpenMPConstruct &ompConstruct) {
- return definition->second == ConstructPtr(&ompConstruct);
- },
- [](const auto &) { return false; }});
-}
-
-bool DataSharingProcessor::OMPConstructSymbolVisitor::
- isSymbolDefineByNestedDeclaration(const semantics::Symbol *symbol) const {
- auto definition = symDefMap.find(symbol);
- return definition != symDefMap.end() &&
- std::holds_alternative<const parser::DeclarationConstruct *>(
- definition->second);
-}
static bool isConstructWithTopLevelTarget(lower::pft::Evaluation &eval) {
const auto *ompEval = eval.getIf<parser::OpenMPConstruct>();
@@ -75,6 +46,23 @@ static bool isConstructWithTopLevelTarget(lower::pft::Evaluation &eval) {
return false;
}
+static parser::CharBlock getSource(const lower::pft::Evaluation &eval) {
+ return eval.visit(common::visitors{
+ [&](const parser::OpenMPConstruct &x) {
+ return parser::omp::GetOmpDirectiveName(x).source;
+ },
+ [&](const parser::OpenMPDeclarativeConstruct &x) { return x.source; },
+ [&](const auto &x) { return parser::CharBlock{}; },
+ });
+}
+
+static const semantics::Scope *
+getCurrentScope(const semantics::SemanticsContext &semaCtx,
+ const lower::pft::Evaluation &eval) {
+ parser::CharBlock source = getSource(eval);
+ return source.empty() ? nullptr : &semaCtx.FindScope(source);
+}
+
DataSharingProcessor::DataSharingProcessor(
lower::AbstractConverter &converter, semantics::SemanticsContext &semaCtx,
const List<Clause> &clauses, lower::pft::Evaluation &eval,
@@ -86,20 +74,7 @@ DataSharingProcessor::DataSharingProcessor(
shouldCollectPreDeterminedSymbols(shouldCollectPreDeterminedSymbols),
useDelayedPrivatization(useDelayedPrivatization), symTable(symTable),
isTargetPrivatization(isTargetPrivatization),
- isMetadirectiveLoop(!metadirectiveLoopIVs.empty()),
- visitor(semaCtx, metadirectiveLoopIVs) {
- eval.visit([&](const auto &functionParserNode) {
- parser::Walk(functionParserNode, visitor);
- });
- // For metadirective evaluations, the associated DO loop is spliced into the
- // evaluation tree but is not part of the metadirective's parse tree. Walk
- // nested evaluations' parse trees so the visitor can track their symbols
- // (e.g. loop iteration variables).
- if (isMetadirectiveLoop && eval.hasNestedEvaluations()) {
- for (auto &nestedEval : eval.getNestedEvaluations())
- nestedEval.visit([&](const auto &node) { parser::Walk(node, visitor); });
- }
-}
+ isMetadirectiveLoop(!metadirectiveLoopIVs.empty()) {}
DataSharingProcessor::DataSharingProcessor(lower::AbstractConverter &converter,
semantics::SemanticsContext &semaCtx,
@@ -116,10 +91,12 @@ void DataSharingProcessor::processStep1(
mlir::omp::PrivateClauseOps *clauseOps,
std::optional<llvm::omp::Directive> dir) {
collectSymbolsForPrivatization();
- collectDefaultSymbols();
- collectImplicitSymbols();
- collectPreDeterminedSymbols();
- collectIndirectReferences();
+ if (isOpenMPPrivatizingEvaluation(eval) || isMetadirectiveLoop) {
+ collectDefaultSymbols();
+ collectImplicitSymbols();
+ collectPreDeterminedSymbols();
+ collectIndirectReferences();
+ }
privatize(clauseOps, dir);
@@ -490,17 +467,6 @@ void DataSharingProcessor::insertLastPrivateCompare(mlir::Operation *op) {
}
}
-static parser::CharBlock getSource(const semantics::SemanticsContext &semaCtx,
- const lower::pft::Evaluation &eval) {
- return eval.visit(common::visitors{
- [&](const parser::OpenMPConstruct &x) {
- return parser::omp::GetOmpDirectiveName(x).source;
- },
- [&](const parser::OpenMPDeclarativeConstruct &x) { return x.source; },
- [&](const auto &x) { return parser::CharBlock{}; },
- });
-}
-
bool DataSharingProcessor::isOpenMPPrivatizingConstruct(
const parser::OpenMPConstruct &omp, llvm::omp::Version version) {
return llvm::omp::isPrivatizingConstruct(
@@ -525,43 +491,55 @@ void DataSharingProcessor::collectSymbolsInNestedRegions(
llvm::SetVector<const semantics::Symbol *> &symbolsInNestedRegions) {
if (!eval.hasNestedEvaluations())
return;
+ const semantics::Scope *curScope = getCurrentScope(semaCtx, eval);
+ if (!curScope)
+ return;
+ llvm::SetVector<const semantics::Symbol *> collectedSymbols;
for (pft::Evaluation &nestedEval : eval.getNestedEvaluations()) {
if (isOpenMPPrivatizingEvaluation(nestedEval)) {
- converter.collectSymbolSet(nestedEval, symbolsInNestedRegions, flag,
+ converter.collectSymbolSet(nestedEval, collectedSymbols, flag,
/*collectSymbols=*/true,
/*collectHostAssociatedSymbols=*/false);
} else {
// Recursively look for OpenMP constructs within `nestedEval`'s region
- collectSymbolsInNestedRegions(nestedEval, flag, symbolsInNestedRegions);
+ collectSymbolsInNestedRegions(nestedEval, flag, collectedSymbols);
}
}
+ for (const semantics::Symbol *sym : collectedSymbols)
+ if (sym->owner() != *curScope)
+ symbolsInNestedRegions.insert(sym);
}
-// Collect all scopes associated with `eval` and return the current scope.
-static const semantics::Scope *
-collectScopes(semantics::SemanticsContext &semaCtx,
- lower::pft::Evaluation &eval,
- llvm::SetVector<const semantics::Scope *> &clauseScopes) {
- std::function<void(const semantics::Scope *)> collect =
- [&](const semantics::Scope *scope) {
- clauseScopes.insert(scope);
- for (const semantics::Scope &child : scope->children())
- collect(&child);
- };
- parser::CharBlock source = getSource(semaCtx, eval);
- const semantics::Scope *curScope = nullptr;
- if (!source.empty()) {
- curScope = &semaCtx.FindScope(source);
- collect(curScope);
- }
- return curScope;
-}
-
+// Collect symbols that `eval` must privatize, but whose data-sharing attributes
+// (DSA) are not explicitly determined.
+//
+// `flag` selects the kind of symbols being collected:
+// - OmpPrivate / OmpFirstPrivate: symbols privatized by a DEFAULT clause.
+// - OmpImplicit: symbols with an implicitly determined DSA.
+// - OmpPreDetermined: symbols with a predetermined DSA.
+// - std::nullopt: indirect references (see collectIndirectReferences()).
+//
+// `allSymbols` contains the symbols referenced in `eval` (or the indirect
+// references, when `flag` is not set).
+// `symbolsInNestedRegions` contains the symbols referenced in privatizing
+// constructs nested in `eval` that are not owned by `eval`'s scope. When
+// `flag` is set, both sets are restricted to symbols that have it.
+//
+// Excluding some special cases, a symbol is privatized if it is in
+// `allSymbols` but not in `symbolsInNestedRegions`, and it is owned by
+// `eval`'s scope, meaning semantics assigned its DSA to this construct.
+//
+// The selected symbols are added to `allPrivatizedSymbols`, and also to
+// `*symbols` when `symbols` is not null.
void DataSharingProcessor::collectPrivatizedSymbols(
std::optional<semantics::Symbol::Flag> flag,
const llvm::SetVector<const semantics::Symbol *> &allSymbols,
const llvm::SetVector<const semantics::Symbol *> &symbolsInNestedRegions,
llvm::SetVector<const semantics::Symbol *> *symbols) {
+ const semantics::Scope *curScope = getCurrentScope(semaCtx, eval);
+ if (!curScope)
+ return;
+
// Filter-out symbols that must not be privatized.
bool collectImplicit = false;
bool collectPreDetermined = false;
@@ -593,24 +571,11 @@ void DataSharingProcessor::collectPrivatizedSymbols(
sym->test(semantics::Symbol::Flag::OmpFirstPrivate)) {
return false;
}
-
- // Collect implicit symbols only if they are not defined by a nested
- // `DeclarationConstruct`. If `sym` is not defined by the current OpenMP
- // evaluation then it is defined by a block nested within the OpenMP
- // construct. This, in turn, means that the private allocation for the
- // symbol will be emitted as part of the nested block and there is no need
- // to privatize it within the OpenMP construct.
- return !visitor.isSymbolDefineByNestedDeclaration(sym) &&
- sym->test(semantics::Symbol::Flag::OmpImplicit);
+ return sym->test(semantics::Symbol::Flag::OmpImplicit);
}
- if (collectPreDetermined) {
- // Similar to implicit symbols, collect pre-determined symbols only if
- // they are not defined by a nested `DeclarationConstruct`
- return visitor.isSymbolDefineBy(sym, eval) &&
- !visitor.isSymbolDefineByNestedDeclaration(sym) &&
- sym->test(semantics::Symbol::Flag::OmpPreDetermined);
- }
+ if (collectPreDetermined)
+ return sym->test(semantics::Symbol::Flag::OmpPreDetermined);
if (collectIndirectRefs)
return true;
@@ -619,27 +584,15 @@ void DataSharingProcessor::collectPrivatizedSymbols(
!sym->test(semantics::Symbol::Flag::OmpPreDetermined);
};
- llvm::SetVector<const semantics::Scope *> clauseScopes;
- const semantics::Scope *curScope = collectScopes(semaCtx, eval, clauseScopes);
-
- // For metadirective evaluations, the source range only covers the directive
- // clauses, not the spliced DO loop. The scope found from that narrow range
- // may not include parent scopes where the loop IV is declared (e.g. the
- // function scope when the metadirective is inside a target region). Walk up
- // the scope chain to include all ancestor scopes.
- if (isMetadirectiveLoop && curScope) {
- const semantics::Scope *scope = curScope;
- while (scope->kind() != semantics::Scope::Kind::Global) {
- clauseScopes.insert(scope);
- scope = &scope->parent();
- }
- }
-
for (const auto *sym : allSymbols) {
+ // Metadirective loops also have symbols in spliced nested evaluations,
+ // which means that not all symbols that must be privatized will be owned
+ // by the current scope.
if (semantics::omp::IsPrivatizable(*sym) &&
!symbolsInNestedRegions.contains(sym) &&
!explicitlyPrivatizedSymbols.contains(sym) &&
- shouldCollectSymbol(sym) && clauseScopes.contains(&sym->owner())) {
+ shouldCollectSymbol(sym) &&
+ (isMetadirectiveLoop || sym->owner() == *curScope)) {
allPrivatizedSymbols.insert(sym);
if (symbols)
symbols->insert(sym);
@@ -647,13 +600,6 @@ void DataSharingProcessor::collectPrivatizedSymbols(
}
}
-// Collect symbols to be default privatized in two steps.
-// In step 1, collect all symbols in `eval` that match `flag` into
-// `defaultSymbols`. In step 2, for nested constructs (if any), if and only if
-// the nested construct is an OpenMP construct, collect those nested
-// symbols skipping host associated symbols into `symbolsInNestedRegions`.
-// Later, in current context, all symbols in the set
-// `defaultSymbols` - `symbolsInNestedRegions` will be privatized.
void DataSharingProcessor::collectSymbols(
semantics::Symbol::Flag flag,
llvm::SetVector<const semantics::Symbol *> *symbols) {
@@ -675,10 +621,6 @@ void DataSharingProcessor::collectSymbols(
llvm::SetVector<const semantics::Symbol *> symbolsInNestedRegions;
collectSymbolsInNestedRegions(eval, flag, symbolsInNestedRegions);
- for (auto *symbol : allSymbols)
- if (visitor.isSymbolDefineBy(symbol, eval))
- symbolsInNestedRegions.remove(symbol);
-
collectPrivatizedSymbols(flag, allSymbols, symbolsInNestedRegions);
}
@@ -715,8 +657,7 @@ void DataSharingProcessor::collectIndirectReferences() {
if (!shouldCollectPreDeterminedSymbols)
return;
- llvm::SetVector<const semantics::Scope *> clauseScopes;
- const semantics::Scope *curScope = collectScopes(semaCtx, eval, clauseScopes);
+ const semantics::Scope *curScope = getCurrentScope(semaCtx, eval);
if (!curScope)
return;
@@ -733,10 +674,6 @@ void DataSharingProcessor::collectIndirectReferences() {
collect(semantics::Symbol::Flag::OmpLinear);
collect(semantics::Symbol::Flag::OmpLastPrivate);
- for (auto *symbol : allSymbols)
- if (visitor.isSymbolDefineBy(symbol, eval))
- symbolsInNestedRegions.remove(symbol);
-
auto isPrivate = [](const semantics::Symbol &sym) {
using Symbol = semantics::Symbol;
Symbol::Flags privateFlags{
diff --git a/flang/lib/Lower/OpenMP/DataSharingProcessor.h b/flang/lib/Lower/OpenMP/DataSharingProcessor.h
index 25d848c8baffc..73044c304800b 100644
--- a/flang/lib/Lower/OpenMP/DataSharingProcessor.h
+++ b/flang/lib/Lower/OpenMP/DataSharingProcessor.h
@@ -33,78 +33,6 @@ namespace omp {
class DataSharingProcessor {
private:
- /// A symbol visitor that keeps track of the currently active OpenMPConstruct
- /// at any point in time. This is used to track Symbol definition scopes in
- /// order to tell which OMP scope defined vs. references a certain Symbol.
- struct OMPConstructSymbolVisitor {
- OMPConstructSymbolVisitor(
- semantics::SemanticsContext &ctx,
- llvm::ArrayRef<const semantics::Symbol *> metadirectiveLoopIVs)
- : metadirectiveLoopIVs(metadirectiveLoopIVs.begin(),
- metadirectiveLoopIVs.end()),
- isMetadirectiveLoop(!metadirectiveLoopIVs.empty()),
- version(ctx.langOptions().getOpenMPVersion()) {}
- template <typename T>
- bool Pre(const T &) {
- return true;
- }
- template <typename T>
- void Post(const T &) {}
-
- bool Pre(const parser::LoopControl::Bounds &bounds) {
- if (isMetadirectiveLoop)
- if (const semantics::Symbol *symbol = bounds.Name().thing.symbol)
- metadirectiveLoopIVs.insert(symbol);
- return true;
- }
-
- bool Pre(const parser::OpenMPConstruct &omp) {
- // Skip constructs that may not have privatizations.
- if (isOpenMPPrivatizingConstruct(omp, version))
- constructs.push_back(&omp);
- return true;
- }
-
- void Post(const parser::OpenMPConstruct &omp) {
- if (isOpenMPPrivatizingConstruct(omp, version))
- constructs.pop_back();
- }
-
- void Post(const parser::Name &name) {
- auto current = !constructs.empty() ? constructs.back() : ConstructPtr();
- symDefMap.try_emplace(name.symbol, current);
- }
-
- bool Pre(const parser::DeclarationConstruct &decl) {
- constructs.push_back(&decl);
- return true;
- }
-
- void Post(const parser::DeclarationConstruct &decl) {
- constructs.pop_back();
- }
-
- /// Given a \p symbol and an \p eval, returns true if eval is the OMP
- /// construct that defines symbol.
- bool isSymbolDefineBy(const semantics::Symbol *symbol,
- lower::pft::Evaluation &eval) const;
-
- // Given a \p symbol, returns true if it is defined by a nested
- // `DeclarationConstruct`.
- bool
- isSymbolDefineByNestedDeclaration(const semantics::Symbol *symbol) const;
-
- private:
- using ConstructPtr = std::variant<const parser::OpenMPConstruct *,
- const parser::DeclarationConstruct *>;
- llvm::SmallVector<ConstructPtr> constructs;
- llvm::DenseMap<semantics::Symbol *, ConstructPtr> symDefMap;
- llvm::SmallPtrSet<const semantics::Symbol *, 4> metadirectiveLoopIVs;
- bool isMetadirectiveLoop;
-
- llvm::omp::Version version;
- };
-
mlir::OpBuilder::InsertPoint lastPrivIP;
llvm::SmallVector<mlir::Value> loopIVs;
// Symbols in private, firstprivate, and/or lastprivate clauses.
@@ -132,7 +60,6 @@ class DataSharingProcessor {
lower::SymMap &symTable;
bool isTargetPrivatization;
bool isMetadirectiveLoop;
- OMPConstructSymbolVisitor visitor;
bool needBarrier();
void collectPrivatizedSymbols(
diff --git a/flang/test/Lower/OpenMP/lastprivate-simd.f90 b/flang/test/Lower/OpenMP/lastprivate-simd.f90
index 356e5ad38d7e5..9342400a95ab0 100644
--- a/flang/test/Lower/OpenMP/lastprivate-simd.f90
+++ b/flang/test/Lower/OpenMP/lastprivate-simd.f90
@@ -25,8 +25,11 @@ subroutine simd_ivs
! CHECK: %[[IDO1_HOST_DECL:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "{{.*}}Eido1"}
! CHECK: %[[IDO2_HOST_DECL:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "{{.*}}Eido2"}
! CHECK: %[[IDO3_HOST_DECL:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "{{.*}}Eido3"}
+! CHECK: %[[IDO4_HOST_DECL:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "{{.*}}Eido4"}
-! CHECK: omp.parallel {
+! CHECK: omp.parallel private(@_QFsimd_ivsEido4_private_i32
+! CHECK-SAME: %[[IDO4_HOST_DECL:.*]]#0 -> %[[IDO4_PRIV_ARG:[^[:space:]]*]] {{.*}}) {
+! CHECK: %[[IDO4_PRIV_DECL:.*]]:2 = hlfir.declare %[[IDO4_PRIV_ARG]] {uniq_name = "{{.*}}Eido4"}
! CHECK: omp.simd private(
! CHECK-SAME: @{{.*}}do1_private{{.*}} %[[IDO1_HOST_DECL]]#0 -> %[[IDO1_PRIV_ARG:[^[:space:]]*]],
! CHECK-SAME: @{{.*}}do2_private{{.*}} %[[IDO2_HOST_DECL]]#0 -> %[[IDO2_PRIV_ARG:[^[:space:]]*]],
diff --git a/flang/test/Lower/OpenMP/predetermined-do-iv.f90 b/flang/test/Lower/OpenMP/predetermined-do-iv.f90
new file mode 100644
index 0000000000000..dd4c8fb8dff1a
--- /dev/null
+++ b/flang/test/Lower/OpenMP/predetermined-do-iv.f90
@@ -0,0 +1,208 @@
+! Test the privatization of predetermined DO loop iteration variables.
+
+! RUN: %flang_fc1 -emit-hlfir -fopenmp %s -o - | FileCheck %s
+
+! CHECK-LABEL: func @_QPparallel_do()
+! CHECK: %[[I3_HOST:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFparallel_doEi3"}
+! CHECK: omp.parallel private({{.*}}Ei3_private_i32 %[[I3_HOST]]#0 -> %[[I3_PRIV:[^ ]+]] : !fir.ref<i32>) {
+! CHECK-NOT: fir.alloca {{.*}}bindc_name = "i3"
+! CHECK: %[[I3_PRIV_DECL:.*]]:2 = hlfir.declare %[[I3_PRIV]] {uniq_name = "_QFparallel_doEi3"}
+! CHECK: omp.wsloop private({{.*}}Ei2_private_i32 %{{[^#]+}}#0 -> %[[I2_PRIV:[^ ]+]] : !fir.ref<i32>) {
+! CHECK: omp.loop_nest
+! CHECK: hlfir.declare %[[I2_PRIV]] {uniq_name = "_QFparallel_doEi2"}
+! CHECK: fir.do_loop
+! CHECK: fir.store %{{.*}} to %[[I3_PRIV_DECL]]#0
+! CHECK: omp.yield
+! CHECK: %[[LOAD:.*]] = fir.load %[[I3_PRIV_DECL]]#0
+! CHECK: %[[C3:.*]] = arith.constant 3 : i32
+! CHECK: arith.cmpi ne, %[[LOAD]], %[[C3]] : i32
+subroutine parallel_do()
+ integer :: i2 = 10, i3 = 99
+ integer :: a = 1, b = 1
+ !$omp parallel
+ !$omp do
+ do i2 = 1, 2
+ do i3 = 1, 2
+ b = b + a
+ end do
+ end do
+ !$omp end do
+ if (i3 /= 3) print *, 'Error i3', i3
+ !$omp end parallel
+end subroutine
+
+! CHECK-LABEL: func @_QPcollapsed_do()
+! CHECK: %[[K_HOST:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFcollapsed_doEk"}
+! CHECK: %[[L_HOST:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFcollapsed_doEl"}
+! CHECK: omp.parallel private(
+! CHECK: @_QFcollapsed_doEk_private_i32 %[[K_HOST]]#0 -> %[[K_PAR:[a-z0-9]+]]
+! CHECK: @_QFcollapsed_doEl_private_i32 %[[L_HOST]]#0 -> %[[L_PAR:[a-z0-9]+]]
+! CHECK-NOT: fir.alloca {{.*}}bindc_name = "k"
+! CHECK-NOT: fir.alloca {{.*}}bindc_name = "l"
+! CHECK: %[[K_PAR_DECL:.*]]:2 = hlfir.declare %[[K_PAR]] {uniq_name = "_QFcollapsed_doEk"}
+! CHECK: %[[L_PAR_DECL:.*]]:2 = hlfir.declare %[[L_PAR]] {uniq_name = "_QFcollapsed_doEl"}
+! CHECK: omp.wsloop private(@_QFcollapsed_doEi_private_i32 %{{[^,]+}}, @_QFcollapsed_doEj_private_i32 %{{[^:]+}}
+! CHECK-SAME: : !fir.ref<i32>, !fir.ref<i32>) {
+! CHECK: fir.do_loop
+! CHECK: fir.store %{{.*}} to %[[K_PAR_DECL]]#0
+! CHECK: fir.do_loop
+! CHECK: fir.store %{{.*}} to %[[L_PAR_DECL]]#0
+! CHECK: %[[C33:.*]] = arith.constant 33 : i32
+! CHECK: hlfir.assign %[[C33]] to %[[K_PAR_DECL]]#0
+! CHECK: %[[C44:.*]] = arith.constant 44 : i32
+! CHECK: hlfir.assign %[[C44]] to %[[L_PAR_DECL]]#0
+subroutine collapsed_do()
+ integer :: i, j, k, l
+ !$omp parallel
+ !$omp do collapse(2)
+ do i = 1, 4
+ do j = 1, 4
+ do k = 1, 4
+ do l = 1, 4
+ end do
+ end do
+ end do
+ end do
+ !$omp end do
+ k = 33
+ l = 44
+ !$omp end parallel
+end subroutine
+
+! CHECK-LABEL: func @_QPnested_parallel()
+! CHECK: %[[J_HOST:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFnested_parallelEj"}
+! CHECK: omp.parallel {
+! CHECK: omp.parallel private(@_QFnested_parallelEj_private_i32 %[[J_HOST]]#0 -> %[[J_PAR:[a-z0-9]+]] : !fir.ref<i32>) {
+! CHECK: %[[J_PAR_DECL:.*]]:2 = hlfir.declare %[[J_PAR]] {uniq_name = "_QFnested_parallelEj"}
+! CHECK: fir.do_loop
+! CHECK: fir.store %{{.*}} to %[[J_PAR_DECL]]#0
+subroutine nested_parallel()
+ integer :: j
+ !$omp parallel
+ !$omp parallel
+ do j = 1, 4
+ end do
+ !$omp end parallel
+ !$omp end parallel
+end subroutine
+
+! CHECK-LABEL: func @_QPparallel_task()
+! CHECK: %[[K_HOST:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFparallel_taskEk"}
+! CHECK: omp.parallel {
+! CHECK: omp.single
+! CHECK: omp.task private(@_QFparallel_taskEk_private_i32 %[[K_HOST]]#0 -> %[[K_TASK:[a-z0-9]+]] : !fir.ref<i32>) {
+! CHECK: %[[K_TASK_DECL:.*]]:2 = hlfir.declare %[[K_TASK]] {uniq_name = "_QFparallel_taskEk"}
+! CHECK: fir.do_loop
+! CHECK: fir.store %{{.*}} to %[[K_TASK_DECL]]#0
+! CHECK: %[[C11:.*]] = arith.constant 11 : i32
+! CHECK: hlfir.assign %[[C11]] to %[[K_TASK_DECL]]#0
+! CHECK: %[[C22:.*]] = arith.constant 22 : i32
+! CHECK: hlfir.assign %[[C22]] to %[[K_HOST]]#0
+subroutine parallel_task()
+ integer :: k
+ !$omp parallel
+ !$omp single
+ !$omp task
+ do k = 1, 4
+ end do
+ k = 11
+ !$omp end task
+ k = 22
+ !$omp end single
+ !$omp end parallel
+end subroutine
+
+! CHECK-LABEL: func @_QPparallel_single()
+! CHECK: %[[J_HOST:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFparallel_singleEj"}
+! CHECK: omp.parallel private(@_QFparallel_singleEj_private_i32 %[[J_HOST]]#0 -> %[[J_PAR:[a-z0-9]+]] : !fir.ref<i32>) {
+! CHECK: %[[J_PAR_DECL:.*]]:2 = hlfir.declare %[[J_PAR]] {uniq_name = "_QFparallel_singleEj"}
+! CHECK: omp.single {
+! CHECK-NOT: fir.alloca {{.*}}bindc_name = "j"
+! CHECK-NOT: hlfir.declare {{.*}}uniq_name = "_QFparallel_singleEj"
+! CHECK: fir.do_loop
+! CHECK: fir.store %{{.*}} to %[[J_PAR_DECL]]#0
+subroutine parallel_single()
+ integer :: j
+ !$omp parallel
+ !$omp single
+ do j = 1, 4
+ end do
+ !$omp end single
+ !$omp end parallel
+end subroutine
+
+! XXX STOPPED HERE
+
+! CHECK-LABEL: func @_QPblock_local_iv()
+! CHECK: omp.parallel {
+! CHECK: %[[I_ALLOCA:.*]] = fir.alloca i32 {{.*}}bindc_name = "i"
+! CHECK: %[[I_DECL:.*]]:2 = hlfir.declare %[[I_ALLOCA]] {uniq_name = "{{.*}}Ei"}
+! CHECK: fir.do_loop
+! CHECK: fir.store %{{.*}} to %[[I_DECL]]#0
+subroutine block_local_iv()
+ !$omp parallel
+ block
+ integer :: i
+ do i = 1, 4
+ end do
+ end block
+ !$omp end parallel
+end subroutine
+
+! CHECK-LABEL: func @_QPorphaned_do(
+! CHECK: %[[I_HOST:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QForphaned_doEi"}
+! CHECK: %[[J_HOST:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QForphaned_doEj"}
+! CHECK: omp.wsloop private(@_QForphaned_doEi_private_i32 %[[I_HOST]]#0 ->
+! CHECK-SAME: %[[I_PRIV:[a-z0-9]+]] : !fir.ref<i32>) {
+! CHECK: omp.loop_nest (%[[ARG:[a-z0-9]+]])
+! CHECK-NOT: hlfir.declare {{.*}}uniq_name = "_QForphaned_doEj"
+! CHECK: %[[I_PRIV_DECL:.*]]:2 = hlfir.declare %[[I_PRIV]] {uniq_name = "_QForphaned_doEi"
+! CHECK: hlfir.assign %[[ARG]] to %[[I_PRIV_DECL]]#0
+! CHECK: fir.do_loop
+! CHECK: fir.store %{{.*}} to %[[J_HOST]]#0
+subroutine orphaned_do()
+ integer :: i, j
+ !$omp do
+ do i = 1, 4
+ do j = 1, 4
+ end do
+ end do
+ !$omp end do
+end subroutine
+
+! CHECK-LABEL: func @_QPparallel_dos()
+! CHECK: %[[K_HOST:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFparallel_dosEk"}
+! CHECK: omp.parallel private(@_QFparallel_dosEi_private_i32 {{[^,]+}},
+! CHECK-SAME: @_QFparallel_dosEk_private_i32 %[[K_HOST]]#0 -> %[[K_PAR:[^ ]+]]
+! CHECK-SAME: : !fir.ref<i32>, !fir.ref<i32>) {
+! CHECK: %[[K_PAR_DECL:.*]]:2 = hlfir.declare %[[K_PAR]] {uniq_name = "_QFparallel_dosEk"}
+! CHECK: fir.do_loop
+! CHECK: omp.wsloop private(@_QFparallel_dosEk_private_i32 %[[K_PAR_DECL]]#0 ->
+! CHECK-SAME: %[[K_WSLOOP:[^ ]+]] : !fir.ref<i32>) {
+! CHECK: omp.loop_nest (%[[ARG1:[^)]*]])
+! CHECK: %[[K_WSLOOP_DECL:.*]]:2 = hlfir.declare %[[K_WSLOOP]] {uniq_name = "_QFparallel_dosEk"}
+! CHECK: hlfir.assign %[[ARG1]] to %[[K_WSLOOP_DECL]]#0
+! CHECK: omp.wsloop private(@_QFparallel_dosEj_private_i32 {{[^,:]*}} : !fir.ref<i32>) {
+! CHECK-NOT: fir.alloca {{.*}}bindc_name = "k"
+! CHECK-NOT: hlfir.declare {{.*}}uniq_name = "_QFparallel_dosEk"
+! CHECK: omp.loop_nest
+! CHECK: fir.do_loop
+! CHECK: fir.store %{{.*}} to %[[K_PAR_DECL]]#0
+! CHECK: omp.terminator
+subroutine parallel_dos()
+ integer :: i, j, k
+ !$omp parallel
+ do i = 1, 2
+ !$omp do
+ do k = 1, 3
+ enddo
+ !$omp end do
+ !$omp do
+ do j = 1, 2
+ do k = 1, 3
+ enddo
+ enddo
+ !$omp end do
+ enddo
+ !$omp end parallel
+end subroutine
diff --git a/flang/test/Lower/OpenMP/shared-loop.f90 b/flang/test/Lower/OpenMP/shared-loop.f90
index 5ea4bbb0b260d..3e5735a633048 100644
--- a/flang/test/Lower/OpenMP/shared-loop.f90
+++ b/flang/test/Lower/OpenMP/shared-loop.f90
@@ -51,9 +51,8 @@ subroutine omploop
! CHECK-LABEL: func.func @_QPomploop2()
! CHECK: %[[ALLOC_I:.*]] = fir.alloca i32 {bindc_name = "i", uniq_name = "_QFomploop2Ei"}
! CHECK: %[[DECL_I:.*]]:2 = hlfir.declare %[[ALLOC_I]] {uniq_name = "_QFomploop2Ei"} :
-! CHECK: omp.parallel {
-! CHECK: %[[ALLOC_PRIV_I:.*]] = fir.alloca i32 {bindc_name = "i", pinned}
-! CHECK: %[[DECL_PRIV_I:.*]]:2 = hlfir.declare %[[ALLOC_PRIV_I]]
+! CHECK: omp.parallel private(@_QFomploop2Ei_private_i32 %[[DECL_I]]#0 -> %[[PRIV_I:[^ ]*]] : !fir.ref<i32>) {
+! CHECK: %[[DECL_PRIV_I:.*]]:2 = hlfir.declare %[[PRIV_I]]
! CHECK: omp.sections {
! CHECK: omp.section {
! CHECK: fir.do_loop %[[ARG0:.*]] = %[[LB:.*]] to %[[UB:.*]] step %[[STEP:.*]] : i32 {
@@ -100,9 +99,8 @@ subroutine omploop2
! CHECK-LABEL: func.func @_QPomploop3()
! CHECK: %[[ALLOC_I:.*]] = fir.alloca i32 {bindc_name = "i", uniq_name = "_QFomploop3Ei"}
! CHECK: %[[DECL_I:.*]]:2 = hlfir.declare %[[ALLOC_I]] {uniq_name = "_QFomploop3Ei"} :
-! CHECK: omp.parallel {
-! CHECK: %[[ALLOC_PRIV_I:.*]] = fir.alloca i32 {bindc_name = "i", pinned}
-! CHECK: %[[DECL_PRIV_I:.*]]:2 = hlfir.declare %[[ALLOC_PRIV_I]]
+! CHECK: omp.parallel private(@_QFomploop3Ei_private_i32 %[[DECL_I]]#0 -> %[[PRIV_I:[^ ]*]] : !fir.ref<i32>) {
+! CHECK: %[[DECL_PRIV_I:.*]]:2 = hlfir.declare %[[PRIV_I]]
! CHECK: omp.sections {
! CHECK: omp.section {
! CHECK: fir.do_loop %[[ARG0:.*]] = %[[LB:.*]] to %[[UB:.*]] step %[[STEP:.*]] : i32 {
diff --git a/flang/test/Lower/OpenMP/taskgroup02.f90 b/flang/test/Lower/OpenMP/taskgroup02.f90
index 4c470b7aa82d1..53ed17a63acdc 100644
--- a/flang/test/Lower/OpenMP/taskgroup02.f90
+++ b/flang/test/Lower/OpenMP/taskgroup02.f90
@@ -4,8 +4,7 @@
!CHECK-LABEL: func.func @_QPsub() {
!CHECK: omp.parallel private(@_QFsubEi_private_i32 %[[SUB_I:.*]]#0 -> %[[ARG:.*]] : !fir.ref<i32>)
-!CHECK: %[[ALLOCA:.*]] = fir.alloca i32
-!CHECK: %[[PAR_I:.*]]:2 = hlfir.declare %[[ALLOCA]] {uniq_name = "_QFsubEi"}
+!CHECK: %[[PAR_I:.*]]:2 = hlfir.declare %[[ARG]] {uniq_name = "_QFsubEi"}
!CHECK: omp.master {
!CHECK: omp.taskgroup {
!CHECK-NEXT: omp.task private(@_QFsubEi_firstprivate_i32 %[[PAR_I]]#0 -> %[[TASK_I:.*]] : !fir.ref<i32>) {
diff --git a/flang/test/Lower/OpenMP/unstructured.f90 b/flang/test/Lower/OpenMP/unstructured.f90
index 32730b931abf7..7c9f4c9bcf871 100644
--- a/flang/test/Lower/OpenMP/unstructured.f90
+++ b/flang/test/Lower/OpenMP/unstructured.f90
@@ -60,14 +60,13 @@ subroutine ss2(n) ! unstructured OpenMP construct; loop exit inside construct
end
! CHECK-LABEL: func @_QPss3{{.*}} {
-! CHECK: omp.parallel private(@{{.*}} %{{.*}}#0 -> %{{.*}} : {{.*}}) {
-! CHECK: %[[ALLOCA_K:.*]] = fir.alloca i32 {bindc_name = "k", pinned}
-! CHECK: %[[K_DECL:.*]]:2 = hlfir.declare %[[ALLOCA_K]] {uniq_name = "_QFss3Ek"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
-
+! CHECK: omp.parallel private(@_QFss3Ei_private_i32 {{.*}} @_QFss3Ek_private_i32 {{.*}} -> %[[K_PRIV:[^ ]*]] {{.*}}) {
+! CHECK: %[[K_DECL:.*]]:2 = hlfir.declare %[[K_PRIV]] {uniq_name = "_QFss3Ek"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
! CHECK: fir.do_loop
-! CHECK: omp.wsloop private(@{{.*}} %{{.*}}#0 -> %[[ALLOCA_2:.*]] : !fir.ref<i32>) {
+
+! CHECK: omp.wsloop private(@_QFss3Ek_private_i32 %{{.*}}#0 -> %[[OMP_LOOP_K_PRIV:.*]] : !fir.ref<i32>) {
! CHECK: omp.loop_nest (%[[ARG1:.*]]) : {{.*}} {
-! CHECK: %[[OMP_LOOP_K_DECL:.*]]:2 = hlfir.declare %[[ALLOCA_2]] {uniq_name = "_QFss3Ek"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
+! CHECK: %[[OMP_LOOP_K_DECL:.*]]:2 = hlfir.declare %[[OMP_LOOP_K_PRIV]] {uniq_name = "_QFss3Ek"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
! CHECK: hlfir.assign %[[ARG1]] to %[[OMP_LOOP_K_DECL]]#0 : i32, !fir.ref<i32>
! CHECK: @_FortranAioBeginExternalListOutput
! CHECK: %[[LOAD_1:.*]] = fir.load %[[OMP_LOOP_K_DECL]]#0 : !fir.ref<i32>
@@ -76,9 +75,9 @@ subroutine ss2(n) ! unstructured OpenMP construct; loop exit inside construct
! CHECK: }
! CHECK: }
-! CHECK: omp.wsloop private(@{{.*}} %{{.*}}#0 -> %[[ALLOCA_1:.*]] : !fir.ref<i32>) {
+! CHECK: omp.wsloop private(@_QFss3Ej_private_i32 %{{.*}}#0 -> %[[J_PRIV:.*]] : !fir.ref<i32>) {
! CHECK: omp.loop_nest (%[[ARG2:.*]]) : {{.*}} {
-! CHECK: %[[OMP_LOOP_J_DECL:.*]]:2 = hlfir.declare %[[ALLOCA_1]] {uniq_name = "_QFss3Ej"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
+! CHECK: %[[OMP_LOOP_J_DECL:.*]]:2 = hlfir.declare %[[J_PRIV]] {uniq_name = "_QFss3Ej"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
! CHECK: hlfir.assign %[[ARG2]] to %[[OMP_LOOP_J_DECL]]#0 : i32, !fir.ref<i32>
! CHECK: scf.execute_region no_inline {
! CHECK: cf.br ^bb1
More information about the flang-commits
mailing list