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

Krzysztof Parzyszek via flang-commits flang-commits at lists.llvm.org
Wed Apr 15 07:02:23 PDT 2026


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

>From 124455574867b7c26c13c8c60abf5af62482a2b3 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Sun, 12 Apr 2026 09:02:28 -0500
Subject: [PATCH 1/8] [flang][OpenMP] Implement GetGeneratedNestDepthWithReason

For a loop-nest-generating construct this function returns the number of
loops in the generated loop nest.

A loop-nest-transformation construct can be thought as replacing N nested
loops with K nested loops, where
  N = GetAffectedNestDepthWithReason
  K = GetGeneratedNestDepthWithReason
---
 flang/include/flang/Semantics/openmp-utils.h |  5 ++
 flang/lib/Semantics/openmp-utils.cpp         | 91 ++++++++++++++------
 2 files changed, 72 insertions(+), 24 deletions(-)

diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index b449988abe9c8..252c88c967750 100644
--- a/flang/include/flang/Semantics/openmp-utils.h
+++ b/flang/include/flang/Semantics/openmp-utils.h
@@ -218,6 +218,11 @@ WithReason<int64_t> GetHeightWithReason(
 std::pair<WithReason<int64_t>, bool> GetAffectedNestDepthWithReason(
     const parser::OmpDirectiveSpecification &spec, unsigned version,
     SemanticsContext *semaCtx = nullptr);
+/// Return the depth of the generated nest(s):
+///   {generated-depth, is-perfect-nest}
+std::pair<WithReason<int64_t>, bool> GetGeneratedNestDepthWithReason(
+    const parser::OmpDirectiveSpecification &spec, unsigned version,
+    SemanticsContext *semaCtx = nullptr);
 /// Return the range of the affected nests in the sequence:
 ///   {first, count}.
 /// If the range is "the whole sequence", the return value will be {1, -1}.
diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index c901d00695093..7a5b31b1c805e 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -767,37 +767,27 @@ WithReason<int64_t> GetHeightWithReason(
         "This construct is not a DO-loop or a loop-transformation construct"_because_en_US);
     return {0, reason};
   }
-  switch (spec.DirName().v) {
-  case llvm::omp::Directive::OMPD_flatten:
-    if (auto &&value{GetArgumentValueWithReason(
-            spec, llvm::omp::Clause::OMPC_depth, version, semaCtx)}) {
-      // FLATTEN DEPTH(n) replaces n loops with 1.
-      return {int64_t(1) - *value.value, std::move(value.reason)};
-    } else {
-      Reason reason;
-      reason.Say(spec.DirName().source, MsgClauseAbsentAssume,
-          GetUpperName(llvm::omp::Clause::OMPC_depth, version), "a depth of 2");
-      return {-1, std::move(reason)};
-    }
+
+  switch (spec.DirId()) {
+  // These generate loop sequences.
   case llvm::omp::Directive::OMPD_fuse:
   case llvm::omp::Directive::OMPD_split:
     return {0, Reason()};
+  case llvm::omp::Directive::OMPD_flatten:
   case llvm::omp::Directive::OMPD_interchange:
   case llvm::omp::Directive::OMPD_nothing:
   case llvm::omp::Directive::OMPD_reverse:
-    return {0, Reason()};
   case llvm::omp::Directive::OMPD_stripe:
   case llvm::omp::Directive::OMPD_tile:
-    return GetNumArgumentsWithReason(
-        spec, llvm::omp::Clause::OMPC_sizes, version, semaCtx);
-  case llvm::omp::Directive::OMPD_unroll:
-    if (isFullUnroll) {
-      Reason reason;
-      reason.Say(spec.DirName().source, MsgConstructDoesNotResult,
-          "Fully unrolled loop", "a loop nest");
-      return {-1, std::move(reason)};
+  case llvm::omp::Directive::OMPD_unroll: {
+    auto [cons, _1]{GetAffectedNestDepthWithReason(spec, version, semaCtx)};
+    auto [prod, _2]{GetGeneratedNestDepthWithReason(spec, version, semaCtx)};
+    if (cons && prod) {
+      return WithReason<int64_t>{*prod.value - *cons.value,
+          Reason().Append(cons.reason).Append(prod.reason)};
     }
-    return {0, Reason()};
+    return {};
+  }
   default:
     llvm_unreachable("Expecting loop-transforming construct");
   }
@@ -1000,6 +990,18 @@ std::pair<WithReason<int64_t>, bool> GetAffectedNestDepthWithReason(
 
   if (IsLoopTransforming(dir)) {
     switch (dir) {
+    case llvm::omp::Directive::OMPD_flatten:
+      if (auto &&value{GetArgumentValueWithReason(
+              spec, llvm::omp::Clause::OMPC_depth, version, semaCtx)}) {
+        // FLATTEN DEPTH(n) replaces n loops with 1.
+        return {std::move(value), true};
+      } else {
+        Reason reason;
+        reason.Say(spec.DirName().source, MsgClauseAbsentAssume,
+            GetUpperName(llvm::omp::Clause::OMPC_depth, version), "a depth of 2");
+        return {{2, std::move(reason)}, true};
+      }
+      break;
     case llvm::omp::Directive::OMPD_interchange: {
       // Get the length of the argument list to PERMUTATION.
       if (parser::omp::FindClause(spec, llvm::omp::Clause::OMPC_permutation)) {
@@ -1015,6 +1017,8 @@ std::pair<WithReason<int64_t>, bool> GetAffectedNestDepthWithReason(
           spec.source, MsgClauseAbsentAssume, name, "a permutation (2, 1)");
       return {{2, std::move(reason)}, true};
     }
+    case llvm::omp::Directive::OMPD_nothing:
+      return {WithReason<int64_t>(0), false};
     case llvm::omp::Directive::OMPD_stripe:
     case llvm::omp::Directive::OMPD_tile: {
       // Get the length of the argument list to SIZES.
@@ -1035,10 +1039,9 @@ std::pair<WithReason<int64_t>, bool> GetAffectedNestDepthWithReason(
       return {{1, std::move(reason)}, true};
     }
     case llvm::omp::Directive::OMPD_reverse:
+    case llvm::omp::Directive::OMPD_split:
     case llvm::omp::Directive::OMPD_unroll:
       return {WithReason<int64_t>(1), false};
-    // TODO: case llvm::omp::Directive::OMPD_flatten:
-    // TODO: case llvm::omp::Directive::OMPD_split:
     default:
       break;
     }
@@ -1047,6 +1050,46 @@ std::pair<WithReason<int64_t>, bool> GetAffectedNestDepthWithReason(
   return {{}, false};
 }
 
+/// Return the depth of the generated nest(s)
+///   {generated-depth, is-perfect-nest}
+std::pair<WithReason<int64_t>, bool> GetGeneratedNestDepthWithReason(
+    const parser::OmpDirectiveSpecification &spec, unsigned version,
+    SemanticsContext *semaCtx) {
+  llvm::omp::Directive dir{spec.DirId()};
+  if (!IsLoopTransforming(dir)) {
+    return {{}, false};
+  }
+
+  auto [depth, _]{GetAffectedNestDepthWithReason(spec, version, semaCtx)};
+
+  switch (dir) {
+  case llvm::omp::Directive::OMPD_flatten:
+    return {WithReason<int64_t>(1), true};
+  case llvm::omp::Directive::OMPD_fuse:
+  case llvm::omp::Directive::OMPD_split:
+    // These result in loop sequences.
+    return {{}, false};
+  case llvm::omp::Directive::OMPD_interchange:
+  case llvm::omp::Directive::OMPD_nothing:
+  case llvm::omp::Directive::OMPD_reverse:
+    return {depth, true};
+  case llvm::omp::Directive::OMPD_stripe:
+  case llvm::omp::Directive::OMPD_tile:
+    if (depth) {
+      return {
+          WithReason<int64_t>(2 * *depth.value, std::move(depth.reason)), true};
+    }
+    return {{}, true};
+  case llvm::omp::Directive::OMPD_unroll:
+    if (IsFullUnroll(spec)) {
+      return {WithReason<int64_t>(0), false};
+    }
+    return {WithReason<int64_t>(1), true};
+  default:
+    return {{}, false};
+  }
+}
+
 /// Return the range of the affected nests in the sequence:
 ///   {first, count}
 WithReason<std::pair<int64_t, int64_t>> GetAffectedLoopRangeWithReason(

>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 2/8] [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)

>From 5c5b8ea7c1ef85da9c2d533b1531de959337935a Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Sun, 12 Apr 2026 10:17:22 -0500
Subject: [PATCH 3/8] format

---
 flang/lib/Semantics/openmp-utils.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index 7a5b31b1c805e..436ee35664f2c 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -998,7 +998,8 @@ std::pair<WithReason<int64_t>, bool> GetAffectedNestDepthWithReason(
       } else {
         Reason reason;
         reason.Say(spec.DirName().source, MsgClauseAbsentAssume,
-            GetUpperName(llvm::omp::Clause::OMPC_depth, version), "a depth of 2");
+            GetUpperName(llvm::omp::Clause::OMPC_depth, version),
+            "a depth of 2");
         return {{2, std::move(reason)}, true};
       }
       break;

>From 3cfc65a2112d00a9df1ff0df802bf129a5dd0766 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Mon, 13 Apr 2026 16:56:03 -0500
Subject: [PATCH 4/8] Rename local variable

---
 flang/lib/Semantics/openmp-utils.cpp | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index 10e825a3a136c..4ff29ed9e25c8 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -1266,9 +1266,9 @@ std::optional<std::vector<const parser::DoConstruct *>> CollectAffectedDoLoops(
         --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)};
+      const parser::OmpDirectiveSpecification &ods{omp->BeginDir()};
+      auto [cons, _1]{GetAffectedNestDepthWithReason(ods, version, semaCtx)};
+      auto [prod, _2]{GetGeneratedNestDepthWithReason(ods, version, semaCtx)};
       if (!cons || !prod) {
         return;
       }

>From 387ff9c4791b954ac534c7bd622e2998e7f5d902 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Wed, 15 Apr 2026 08:36:55 -0500
Subject: [PATCH 5/8] Detect issues getting cons/prod in the middle of nest

---
 flang/lib/Semantics/openmp-utils.cpp | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index 4ff29ed9e25c8..59d80474727f9 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -1255,7 +1255,7 @@ std::optional<std::vector<const parser::DoConstruct *>> CollectAffectedDoLoops(
   int64_t consuming{0};
   int64_t level{*depth.value};
 
-  auto visit{[&](const LoopSequence &nest, auto &&self) -> void {
+  auto visit{[&](const LoopSequence &nest, auto &&self) -> bool {
     const parser::ExecutionPartConstruct *owner{nest.owner()};
 
     if (auto *doLoop{parser::Unwrap<parser::DoConstruct>(owner)}) {
@@ -1270,7 +1270,7 @@ std::optional<std::vector<const parser::DoConstruct *>> CollectAffectedDoLoops(
       auto [cons, _1]{GetAffectedNestDepthWithReason(ods, version, semaCtx)};
       auto [prod, _2]{GetGeneratedNestDepthWithReason(ods, version, semaCtx)};
       if (!cons || !prod) {
-        return;
+        return false;
       }
       if (*prod.value <= consuming) {
         consuming -= *prod.value;
@@ -1281,17 +1281,21 @@ std::optional<std::vector<const parser::DoConstruct *>> CollectAffectedDoLoops(
       consuming += *cons.value;
     }
 
+    bool success{true};
     if (produced < level) {
       for (const LoopSequence &child : nest.children()) {
-        self(child, self);
+        success = success && self(child, self);
       }
     }
+
+    return success;
   }};
 
   LoopSequence sequence(std::get<parser::Block>(x.t), version, true, semaCtx);
-  visit(sequence, visit);
-
-  return result;
+  if (visit(sequence, visit)) {
+    return result;
+  }
+  return std::nullopt;
 }
 
 #ifdef EXPENSIVE_CHECKS

>From 2be9a7fd3a66fe6179968ac96af1e465c33857da Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Wed, 15 Apr 2026 08:42:11 -0500
Subject: [PATCH 6/8] Depth should not be negative

---
 flang/lib/Semantics/openmp-utils.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index 59d80474727f9..4fd61fffb4c01 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -1233,9 +1233,10 @@ std::optional<std::vector<const parser::DoConstruct *>> CollectAffectedDoLoops(
   if (!depth) {
     return std::nullopt;
   }
-  if (*depth.value <= 0) {
+  if (*depth.value == 0) {
     return result;
   }
+  assert(*depth.value > 0 && "Expecting positive depth");
 
   // 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

>From 25264b6f414cc48f2c9ec3f8657fac53e71d3432 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Wed, 15 Apr 2026 08:52:57 -0500
Subject: [PATCH 7/8] Check if enough loops were present

---
 flang/lib/Semantics/openmp-utils.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index 4fd61fffb4c01..58aefe2e1fc52 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -1289,7 +1289,7 @@ std::optional<std::vector<const parser::DoConstruct *>> CollectAffectedDoLoops(
       }
     }
 
-    return success;
+    return success && produced >= level;
   }};
 
   LoopSequence sequence(std::get<parser::Block>(x.t), version, true, semaCtx);

>From f22071a2ecd8516232e4709b377ec545606c1792 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Wed, 15 Apr 2026 08:57:58 -0500
Subject: [PATCH 8/8] Update comment about returning std::nullopt

---
 flang/include/flang/Semantics/openmp-utils.h | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index 1924fbe9b093d..665d5f221b43a 100644
--- a/flang/include/flang/Semantics/openmp-utils.h
+++ b/flang/include/flang/Semantics/openmp-utils.h
@@ -245,6 +245,8 @@ std::optional<int64_t> GetMinimumSequenceCount(
 /// 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`.
+/// Returns std::nullopt if `x` or code nested in `x` was malformed in a
+/// way that prevented the function from returning an accurate result.
 std::optional<std::vector<const parser::DoConstruct *>> CollectAffectedDoLoops(
     const parser::OpenMPLoopConstruct &x, unsigned version,
     SemanticsContext *semaCtx = nullptr);



More information about the flang-commits mailing list