[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