[llvm-branch-commits] [flang] [flang][OpenMP] Identify DO loops affected by loop-associated construct (PR #191719)

Krzysztof Parzyszek via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Sun Apr 12 08:16:09 PDT 2026


https://github.com/kparzysz created https://github.com/llvm/llvm-project/pull/191719

This is to identify iteration variables of DO loops affected by an OpenMP loop construct. These variables are privatized as per data-sharing rules.

>From bc23293b1f78161af44d0e4b04b72df6b30ac8b7 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Thu, 9 Apr 2026 14:24:16 -0500
Subject: [PATCH] [flang][OpenMP] Identify DO loops affected by loop-associated
 construct

This is to identify iteration variables of DO loops affected by an OpenMP
loop construct. These variables are privatized as per data-sharing rules.
---
 flang/include/flang/Semantics/openmp-utils.h  |  7 ++
 flang/lib/Semantics/openmp-utils.cpp          | 91 +++++++++++++++++++
 flang/lib/Semantics/resolve-directives.cpp    | 69 +++++++-------
 .../test/Semantics/OpenMP/affected-loops.f90  | 27 ++++++
 4 files changed, 158 insertions(+), 36 deletions(-)
 create mode 100644 flang/test/Semantics/OpenMP/affected-loops.f90

diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index 252c88c967750..1924fbe9b093d 100644
--- a/flang/include/flang/Semantics/openmp-utils.h
+++ b/flang/include/flang/Semantics/openmp-utils.h
@@ -242,6 +242,13 @@ std::optional<int64_t> GetMinimumSequenceCount(
 std::optional<int64_t> GetMinimumSequenceCount(
     std::optional<std::pair<int64_t, int64_t>> range);
 
+/// Collect the set of DO loops present in the source code that are directly
+/// affected by the given loop construct. This does not include any DO loops
+/// that are affected by any construct nested in `x`.
+std::optional<std::vector<const parser::DoConstruct *>> CollectAffectedDoLoops(
+    const parser::OpenMPLoopConstruct &x, unsigned version,
+    SemanticsContext *semaCtx = nullptr);
+
 struct LoopSequence {
   LoopSequence(const parser::ExecutionPartConstruct &root, unsigned version,
       bool allowAllLoops = false, SemanticsContext *semaCtx = nullptr);
diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index 7a5b31b1c805e..b09d4a2e207b7 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -1202,6 +1202,97 @@ std::optional<int64_t> GetMinimumSequenceCount(
   return GetMinimumSequenceCount(std::nullopt, std::nullopt);
 }
 
+/// Collect the DO loops that are affected directly by the given loop
+/// transformation. Not all DO loops nested in the associated nest are
+/// affected by the top-level loop transformation, e.g.
+///
+/// !$omp do collapse(5)                           | [2]
+/// !$omp tile sizes(2, 2)  | [1]                  | <- nest of 4 loops
+/// do i = 1, 10            | <- affected by TILE  |    generated by TILE
+///   do j = 1, 10          | <-                   |
+///     do k = 1, 10                               | <- affected by DO
+///     end do
+///   end do
+/// end do
+///
+/// The two DO loops (i and j) in [1] are affected by the TILE construct.
+/// The k DO loop is affected by the DO construct [2].
+/// For the top-level DO COLLAPSE(5) construct, the k loop is the only
+/// directly affected loop.
+std::optional<std::vector<const parser::DoConstruct *>> CollectAffectedDoLoops(
+    const parser::OpenMPLoopConstruct &x, unsigned version,
+    SemanticsContext *semaCtx) {
+  std::vector<const parser::DoConstruct *> result;
+  const parser::OmpDirectiveSpecification &spec{x.BeginDir()};
+
+  auto [depth, _]{GetAffectedNestDepthWithReason(spec, version, semaCtx)};
+
+  // If the depth is absent, then there is some issue. Leave it alone here,
+  // and let the semantic checks diagnose the problem.
+  if (!depth) {
+    return std::nullopt;
+  }
+  if (*depth.value <= 0) {
+    return result;
+  }
+
+  // The algorithm is to descend down the nest and keep track of intervening
+  // constructs and how many loops they consume and produce. This is similar
+  // to traversing an expression tree to identify the operands to the top-
+  // level operation:
+  //
+  //   ... + + x y z w ...
+  //       ^ ^     ^
+  //       | |     |
+  //       | |     +-- produces 1 value consumed by the first +
+  //       | +-- produces 1 value, but first consumes 2
+  //       +-- consumes 2 operands
+  //
+  // The analogous result here would be "z" as the operand to the first +.
+
+  int64_t produced{0};
+  int64_t consuming{0};
+  int64_t level{*depth.value};
+
+  auto visit{[&](const LoopSequence &nest, auto &&self) -> void {
+    const parser::ExecutionPartConstruct *owner{nest.owner()};
+
+    if (auto *doLoop{parser::Unwrap<parser::DoConstruct>(owner)}) {
+      if (consuming == 0) {
+        result.push_back(doLoop);
+        ++produced;
+      } else {
+        --consuming;
+      }
+    } else if (auto *omp{parser::Unwrap<parser::OpenMPLoopConstruct>(owner)}) {
+      const parser::OmpDirectiveSpecification &spec{omp->BeginDir()};
+      auto [cons, _1]{GetAffectedNestDepthWithReason(spec, version, semaCtx)};
+      auto [prod, _2]{GetGeneratedNestDepthWithReason(spec, version, semaCtx)};
+      if (!cons || !prod) {
+        return;
+      }
+      if (*prod.value <= consuming) {
+        consuming -= *prod.value;
+      } else {
+        produced += (*prod.value - consuming);
+        consuming = 0;
+      }
+      consuming += *cons.value;
+    }
+
+    if (produced < level) {
+      for (const LoopSequence &child : nest.children()) {
+        self(child, self);
+      }
+    }
+  }};
+
+  LoopSequence sequence(std::get<parser::Block>(x.t), version, true, semaCtx);
+  visit(sequence, visit);
+
+  return result;
+}
+
 #ifdef EXPENSIVE_CHECKS
 namespace {
 /// Check that for every value x of type T, there will be a "source" member
diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp
index c50c76647962c..6b42a7290e260 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -1114,8 +1114,7 @@ class OmpAttributeVisitor : DirectiveAttributeVisitor<llvm::omp::Directive> {
   }
 
   // Predetermined DSA rules
-  void PrivatizeAssociatedLoopIndexAndCheckLoopLevel(
-      const parser::OpenMPLoopConstruct &);
+  void PrivatizeAssociatedLoopIndex(const parser::OpenMPLoopConstruct &);
   void ResolveSeqLoopIndexInParallelOrTaskConstruct(const parser::Name &);
 
   bool IsNestedInDirective(llvm::omp::Directive directive);
@@ -2068,7 +2067,7 @@ bool OmpAttributeVisitor::Pre(const parser::OpenMPLoopConstruct &x) {
     }
   }
 
-  PrivatizeAssociatedLoopIndexAndCheckLoopLevel(x);
+  PrivatizeAssociatedLoopIndex(x);
   return true;
 }
 
@@ -2162,13 +2161,16 @@ bool OmpAttributeVisitor::Pre(const parser::DoConstruct &x) {
 //     increment of the associated do-loop (only for OpenMP versions <= 4.5)
 //   - The loop iteration variables in the associated do-loops of a simd
 //     construct with multiple associated do-loops are lastprivate.
-void OmpAttributeVisitor::PrivatizeAssociatedLoopIndexAndCheckLoopLevel(
+void OmpAttributeVisitor::PrivatizeAssociatedLoopIndex(
     const parser::OpenMPLoopConstruct &x) {
+  const parser::OmpDirectiveSpecification &spec{x.BeginDir()};
   unsigned version{context_.langOptions().OpenMPVersion};
+
   auto [depth, _]{
-      omp::GetAffectedNestDepthWithReason(x.BeginDir(), version, &context_)};
-  // If there was a problem obtaining the depth, it will be diagnosed in
-  // the semantic checks.
+      omp::GetAffectedNestDepthWithReason(spec, version, &context_)};
+
+  // If depth is absent, then there is some issue. Leave it alone here,
+  // and let the semantic checks diagnose the problem.
   if (!depth || *depth.value <= 0) {
     return;
   }
@@ -2183,35 +2185,30 @@ void OmpAttributeVisitor::PrivatizeAssociatedLoopIndexAndCheckLoopLevel(
     ivDSA = Symbol::Flag::OmpLastPrivate;
   }
 
-  for (auto &construct : std::get<parser::Block>(x.t)) {
-    if (const auto *innermostConstruct{parser::omp::GetOmpLoop(construct)}) {
-      PrivatizeAssociatedLoopIndexAndCheckLoopLevel(*innermostConstruct);
-    } else if (const auto *doConstruct{
-                   parser::omp::GetDoConstruct(construct)}) {
-      for (const parser::DoConstruct *loop{&*doConstruct}; loop && level > 0;
-          --level) {
-        // go through all the nested do-loops and resolve index variables
-        if (const parser::Name *iv{GetLoopIndex(*loop)}) {
-          if (!iv->symbol || !IsLocalInsideScope(*iv->symbol, currScope())) {
-            if (auto *symbol{ResolveOmp(*iv, ivDSA, currScope())}) {
-              if (const auto *details{
-                      iv->symbol->detailsIf<HostAssocDetails>()}) {
-                const Symbol *tpSymbol = &details->symbol();
-                if (tpSymbol->test(Symbol::Flag::OmpThreadprivate)) {
-                  context_.Say(iv->source,
-                      "Loop iteration variable %s is not allowed in THREADPRIVATE."_err_en_US,
-                      iv->ToString());
-                }
-              }
-              SetSymbolDSA(*symbol, {Symbol::Flag::OmpPreDetermined, ivDSA});
-              iv->symbol = symbol; // adjust the symbol within region
-              AddToContextObjectWithDSA(*symbol, ivDSA);
-            }
-          }
-          const auto &block{std::get<parser::Block>(loop->t)};
-          const auto it{block.begin()};
-          loop = it != block.end() ? GetDoConstructIf(*it) : nullptr;
-        }
+  auto checkThreadprivate{[&](const parser::Name &iv) {
+    if (const auto *details{iv.symbol->detailsIf<HostAssocDetails>()}) {
+      if (details->symbol().test(Symbol::Flag::OmpThreadprivate)) {
+        context_.Say(iv.source,
+            "Loop iteration variable %s is not allowed in THREADPRIVATE."_err_en_US,
+            iv.ToString());
+      }
+    }
+  }};
+
+  Scope &scope{currScope()};
+
+  if (auto doLoops{omp::CollectAffectedDoLoops(x, version, &context_)}) {
+    for (const parser::DoConstruct *loop : *doLoops) {
+      const parser::Name *iv{GetLoopIndex(*loop)};
+      if (!iv || (iv->symbol && IsLocalInsideScope(*iv->symbol, scope))) {
+        continue;
+      }
+
+      if (auto *symbol{ResolveOmp(*iv, ivDSA, scope)}) {
+        checkThreadprivate(*iv);
+        SetSymbolDSA(*symbol, {Symbol::Flag::OmpPreDetermined, ivDSA});
+        iv->symbol = symbol; // adjust the symbol within region
+        AddToContextObjectWithDSA(*symbol, ivDSA);
       }
     }
   }
diff --git a/flang/test/Semantics/OpenMP/affected-loops.f90 b/flang/test/Semantics/OpenMP/affected-loops.f90
new file mode 100644
index 0000000000000..01273ec4725ec
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/affected-loops.f90
@@ -0,0 +1,27 @@
+!RUN: %flang_fc1 -fdebug-dump-symbols -fopenmp -fopenmp-version=60 %s | FileCheck %s
+
+subroutine f
+  integer :: i, j, k
+  !$omp do collapse(5)
+  !$omp tile sizes(2, 2)
+  do i = 1, 10
+    do j = 1, 10
+      do k = 1, 10
+      end do
+    end do
+  end do
+end
+
+! Check that k is privatized in the scope of DO, and that i, j are privatized
+! in the scope of TILE.
+
+!CHECK: Subprogram scope: f size=12 alignment=4 sourceRange=139 bytes
+!CHECK:   f (Subroutine): HostAssoc => f (Subroutine): Subprogram ()
+!CHECK:   i size=4 offset=0: ObjectEntity type: INTEGER(4)
+!CHECK:   j size=4 offset=4: ObjectEntity type: INTEGER(4)
+!CHECK:   k size=4 offset=8: ObjectEntity type: INTEGER(4)
+!CHECK:   OtherConstruct scope: size=0 alignment=1 sourceRange=98 bytes
+!CHECK:     k (OmpPrivate, OmpPreDetermined): HostAssoc => k size=4 offset=8: ObjectEntity type: INTEGER(4)
+!CHECK:     OtherConstruct scope: size=0 alignment=1 sourceRange=77 bytes
+!CHECK:       i (OmpPrivate, OmpPreDetermined): HostAssoc => i size=4 offset=0: ObjectEntity type: INTEGER(4)
+!CHECK:       j (OmpPrivate, OmpPreDetermined): HostAssoc => j size=4 offset=4: ObjectEntity type: INTEGER(4)



More information about the llvm-branch-commits mailing list