[flang-commits] [flang] [flang][OpenMP] Identify affected loops, provide reason (PR #185299)

Krzysztof Parzyszek via flang-commits flang-commits at lists.llvm.org
Thu Mar 12 09:28:10 PDT 2026


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

>From 2d7d763b4cb55c3112454d5ad0022048125bc713 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Thu, 19 Feb 2026 13:16:34 -0600
Subject: [PATCH 01/13] [flang][OpenMP] Import ExecutionPartIterator et al into
 semantics::omp, NFC

---
 flang/include/flang/Semantics/openmp-utils.h | 13 +++++++++++++
 flang/lib/Semantics/check-omp-loop.cpp       |  3 ---
 2 files changed, 13 insertions(+), 3 deletions(-)

diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index 90fd36708de0e..221e4cb23eada 100644
--- a/flang/include/flang/Semantics/openmp-utils.h
+++ b/flang/include/flang/Semantics/openmp-utils.h
@@ -27,6 +27,14 @@
 #include <type_traits>
 #include <utility>
 
+namespace Fortran::parser::omp {
+struct ExecutionPartIterator;
+struct LoopNestIterator;
+template <typename T> struct ExecutionPartRange;
+using BlockRange = ExecutionPartRange<ExecutionPartIterator>;
+using LoopRange = ExecutionPartRange<LoopNestIterator>;
+} // namespace Fortran::parser::omp
+
 namespace Fortran::semantics {
 class Scope;
 class SemanticsContext;
@@ -34,6 +42,11 @@ class Symbol;
 
 // Add this namespace to avoid potential conflicts
 namespace omp {
+using Fortran::parser::omp::ExecutionPartIterator;
+using Fortran::parser::omp::LoopNestIterator;
+using Fortran::parser::omp::BlockRange;
+using Fortran::parser::omp::LoopRange;
+
 template <typename T, typename U = std::remove_const_t<T>> U AsRvalue(T &t) {
   return U(t);
 }
diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp
index f81bde981594d..e13ea820c7ef6 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -334,7 +334,6 @@ static std::optional<size_t> CountGeneratedNests(const parser::Block &block) {
   // messages about a potentially incorrect loop count.
   // In such cases reset the count to nullopt. Once it becomes nullopt,
   // keep it that way.
-  using LoopRange = parser::omp::LoopRange;
   std::optional<size_t> numLoops{0};
   for (auto &epc : LoopRange(block, LoopRange::Step::Over)) {
     if (auto genCount{CountGeneratedNests(epc)}) {
@@ -371,7 +370,6 @@ void OmpStructureChecker::CheckNestedConstruct(
 
   // Check constructs contained in the body of the loop construct.
   auto &body{std::get<parser::Block>(x.t)};
-  using BlockRange = parser::omp::BlockRange;
   for (auto &stmt : BlockRange(body, BlockRange::Step::Over)) {
     if (auto *dir{parser::Unwrap<parser::CompilerDirective>(stmt)}) {
       context_.Say(dir->source,
@@ -495,7 +493,6 @@ void OmpStructureChecker::SetLoopInfo(const parser::OpenMPLoopConstruct &x) {
 
 void OmpStructureChecker::CheckIterationVariableType(
     const parser::OpenMPLoopConstruct &x) {
-  using LoopRange = parser::omp::LoopRange;
   auto &body{std::get<parser::Block>(x.t)};
   for (auto &construct : LoopRange(body, LoopRange::Step::Into)) {
     // 'construct' can also be OpenMPLoopConstruct

>From 77235406105e7e686f1ca19bbfb4956aeef9257e Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Wed, 18 Feb 2026 10:53:13 -0600
Subject: [PATCH 02/13] [flang][OpenMP] Remember original range in
 ExecutionPartIterator

Storing the original range (instead of just the "remaining part")
will allow the iterator component to be reused.
---
 flang/include/flang/Parser/openmp-utils.h | 25 ++++++++++++++++++-----
 flang/lib/Parser/openmp-utils.cpp         | 11 ++++------
 2 files changed, 24 insertions(+), 12 deletions(-)

diff --git a/flang/include/flang/Parser/openmp-utils.h b/flang/include/flang/Parser/openmp-utils.h
index f23e52585d567..20754ad28d26d 100644
--- a/flang/include/flang/Parser/openmp-utils.h
+++ b/flang/include/flang/Parser/openmp-utils.h
@@ -296,14 +296,28 @@ struct ExecutionPartIterator {
   using IteratorType = Block::const_iterator;
   using IteratorRange = llvm::iterator_range<IteratorType>;
 
+  // An iterator range with a third iterator indicating a position inside
+  // the range.
+  struct IteratorGauge : public IteratorRange {
+    IteratorGauge(IteratorType b, IteratorType e)
+        : IteratorRange(b, e), at(b) {}
+    IteratorGauge(IteratorRange r) : IteratorRange(r), at(r.begin()) {}
+
+    bool atEnd() const { return at == end(); }
+    IteratorType at;
+  };
+
   struct Construct {
     Construct(IteratorType b, IteratorType e, const ExecutionPartConstruct *c)
-        : range(b, e), owner(c) {}
+        : location(b, e), owner(c) {}
     template <typename R>
     Construct(const R &r, const ExecutionPartConstruct *c)
-        : range(r), owner(c) {}
+        : location(r), owner(c) {}
     Construct(const Construct &c) = default;
-    IteratorRange range;
+    // The original range of the construct with the current position in it.
+    // The location.at is the construct currently being pointed at, or
+    // stepped into.
+    IteratorGauge location;
     const ExecutionPartConstruct *owner;
   };
 
@@ -332,6 +346,7 @@ struct ExecutionPartIterator {
 
   bool valid() const { return !stack_.empty(); }
 
+  const std::vector<Construct> &stack() const { return stack_; }
   decltype(auto) operator*() const { return *at(); }
   bool operator==(const ExecutionPartIterator &other) const {
     if (valid() != other.valid()) {
@@ -339,7 +354,7 @@ struct ExecutionPartIterator {
     }
     // Invalid iterators are considered equal.
     return !valid() ||
-        stack_.back().range.begin() == other.stack_.back().range.begin();
+        stack_.back().location.at == other.stack_.back().location.at;
   }
   bool operator!=(const ExecutionPartIterator &other) const {
     return !(*this == other);
@@ -368,7 +383,7 @@ struct ExecutionPartIterator {
   using iterator_category = std::forward_iterator_tag;
 
 private:
-  IteratorType at() const { return stack_.back().range.begin(); };
+  IteratorType at() const { return stack_.back().location.at; };
 
   // If the iterator is not at a legal location, keep advancing it until
   // it lands at a legal location or becomes invalid.
diff --git a/flang/lib/Parser/openmp-utils.cpp b/flang/lib/Parser/openmp-utils.cpp
index 6d4326af78344..f83a658104396 100644
--- a/flang/lib/Parser/openmp-utils.cpp
+++ b/flang/lib/Parser/openmp-utils.cpp
@@ -243,8 +243,7 @@ void ExecutionPartIterator::step() {
     } else if (auto *loop{GetDoConstruct(*where)}) {
       stack_.emplace_back(std::get<Block>(loop->t), &*where);
     } else {
-      stack_.back().range =
-          IteratorRange(std::next(where), stack_.back().range.end());
+      ++stack_.back().location.at;
     }
     adjust();
   }
@@ -254,8 +253,7 @@ void ExecutionPartIterator::next() {
   // Advance the iterator to the next legal position. If the current
   // position is a DO-loop or a loop construct, step over it.
   if (valid()) {
-    stack_.back().range =
-        IteratorRange(std::next(at()), stack_.back().range.end());
+    ++stack_.back().location.at;
     adjust();
   }
 }
@@ -264,11 +262,10 @@ void ExecutionPartIterator::adjust() {
   // If the iterator is not at a legal location, keep advancing it until
   // it lands at a legal location or becomes invalid.
   while (valid()) {
-    if (stack_.back().range.empty()) {
+    if (stack_.back().location.atEnd()) {
       stack_.pop_back();
       if (valid()) {
-        stack_.back().range =
-            IteratorRange(std::next(at()), stack_.back().range.end());
+        ++stack_.back().location.at;
       }
     } else if (auto *block{GetFortranBlockConstruct(*at())}) {
       stack_.emplace_back(std::get<Block>(block->t), &*at());

>From dfa96281667b3523448d1fc3afacf798d6283438 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Thu, 26 Feb 2026 11:28:32 -0600
Subject: [PATCH 03/13] [flang][OpenMP] Move two functions to openmp-utils.cpp,
 NFC

Move `IsLoopTransforming` and `IsFullUnroll` from check-omp-loop.cpp
to openmp-utils.cpp.
---
 flang/include/flang/Semantics/openmp-utils.h |  3 ++
 flang/lib/Semantics/check-omp-loop.cpp       | 29 --------------------
 flang/lib/Semantics/openmp-utils.cpp         | 27 ++++++++++++++++++
 3 files changed, 30 insertions(+), 29 deletions(-)

diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index 221e4cb23eada..a10d826e4050c 100644
--- a/flang/include/flang/Semantics/openmp-utils.h
+++ b/flang/include/flang/Semantics/openmp-utils.h
@@ -114,6 +114,9 @@ bool IsAssignment(const parser::ActionStmt *x);
 bool IsPointerAssignment(const evaluate::Assignment &x);
 
 MaybeExpr MakeEvaluateExpr(const parser::OmpStylizedInstance &inp);
+
+bool IsLoopTransforming(llvm::omp::Directive dir);
+bool IsFullUnroll(const parser::OpenMPLoopConstruct &x);
 } // namespace omp
 } // namespace Fortran::semantics
 
diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp
index e13ea820c7ef6..d6e5a3f0aa7fb 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -38,8 +38,6 @@
 #include <variant>
 
 namespace Fortran::semantics {
-static bool IsLoopTransforming(llvm::omp::Directive dir);
-static bool IsFullUnroll(const parser::OpenMPLoopConstruct &x);
 static std::optional<size_t> CountGeneratedNests(
     const parser::ExecutionPartConstruct &epc);
 static std::optional<size_t> CountGeneratedNests(const parser::Block &block);
@@ -248,33 +246,6 @@ void OmpStructureChecker::CheckSIMDNest(const parser::OpenMPConstruct &c) {
   }
 }
 
-static bool IsLoopTransforming(llvm::omp::Directive dir) {
-  switch (dir) {
-  // TODO case llvm::omp::Directive::OMPD_flatten:
-  case llvm::omp::Directive::OMPD_fuse:
-  case llvm::omp::Directive::OMPD_interchange:
-  case llvm::omp::Directive::OMPD_nothing:
-  case llvm::omp::Directive::OMPD_reverse:
-  // TODO case llvm::omp::Directive::OMPD_split:
-  case llvm::omp::Directive::OMPD_stripe:
-  case llvm::omp::Directive::OMPD_tile:
-  case llvm::omp::Directive::OMPD_unroll:
-    return true;
-  default:
-    return false;
-  }
-}
-
-static bool IsFullUnroll(const parser::OpenMPLoopConstruct &x) {
-  const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()};
-
-  if (beginSpec.DirName().v == llvm::omp::Directive::OMPD_unroll) {
-    return parser::omp::FindClause(
-               beginSpec, llvm::omp::Clause::OMPC_partial) == nullptr;
-  }
-  return false;
-}
-
 // Count the number of loop nests generated by `epc`. This is just a helper
 // function for counting the number of loop nests in a parser::Block.
 static std::optional<size_t> CountGeneratedNests(
diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index 937938a0d10ce..dbc7e216c4788 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -523,4 +523,31 @@ MaybeExpr MakeEvaluateExpr(const parser::OmpStylizedInstance &inp) {
       },
       instance.u);
 }
+
+bool IsLoopTransforming(llvm::omp::Directive dir) {
+  switch (dir) {
+  // TODO case llvm::omp::Directive::OMPD_flatten:
+  case llvm::omp::Directive::OMPD_fuse:
+  case llvm::omp::Directive::OMPD_interchange:
+  case llvm::omp::Directive::OMPD_nothing:
+  case llvm::omp::Directive::OMPD_reverse:
+  // TODO case llvm::omp::Directive::OMPD_split:
+  case llvm::omp::Directive::OMPD_stripe:
+  case llvm::omp::Directive::OMPD_tile:
+  case llvm::omp::Directive::OMPD_unroll:
+    return true;
+  default:
+    return false;
+  }
+}
+
+bool IsFullUnroll(const parser::OpenMPLoopConstruct &x) {
+  const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()};
+
+  if (beginSpec.DirName().v == llvm::omp::Directive::OMPD_unroll) {
+    return parser::omp::FindClause(
+               beginSpec, llvm::omp::Clause::OMPC_partial) == nullptr;
+  }
+  return false;
+}
 } // namespace Fortran::semantics::omp

>From c6b0684b934357d1e4c93f3300f2e69fc7856880 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Thu, 26 Feb 2026 11:20:56 -0600
Subject: [PATCH 04/13] [flang][OpenMP] Refactor CountGeneratedNests, NFC

Extract handling of individual constructs into a helper function.
Change the base count type to `int64_t` to match the type used
in GetIntValue.
Rename the function to GetNumGeneratedNests.
---
 flang/include/flang/Semantics/openmp-utils.h |  4 ++
 flang/lib/Semantics/check-omp-loop.cpp       | 63 ++++++--------------
 flang/lib/Semantics/openmp-utils.cpp         | 58 ++++++++++++++++++
 3 files changed, 80 insertions(+), 45 deletions(-)

diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index a10d826e4050c..7f6c2824a986a 100644
--- a/flang/include/flang/Semantics/openmp-utils.h
+++ b/flang/include/flang/Semantics/openmp-utils.h
@@ -117,6 +117,10 @@ MaybeExpr MakeEvaluateExpr(const parser::OmpStylizedInstance &inp);
 
 bool IsLoopTransforming(llvm::omp::Directive dir);
 bool IsFullUnroll(const parser::OpenMPLoopConstruct &x);
+
+std::optional<int64_t> GetNumGeneratedNestsFrom(
+    const parser::ExecutionPartConstruct &epc,
+    std::optional<int64_t> nestedCount);
 } // namespace omp
 } // namespace Fortran::semantics
 
diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp
index d6e5a3f0aa7fb..45f4798d0c3c6 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -38,9 +38,9 @@
 #include <variant>
 
 namespace Fortran::semantics {
-static std::optional<size_t> CountGeneratedNests(
+static std::optional<int64_t> GetNumGeneratedNests(
     const parser::ExecutionPartConstruct &epc);
-static std::optional<size_t> CountGeneratedNests(const parser::Block &block);
+static std::optional<int64_t> GetNumGeneratedNests(const parser::Block &block);
 } // namespace Fortran::semantics
 
 namespace {
@@ -248,7 +248,7 @@ void OmpStructureChecker::CheckSIMDNest(const parser::OpenMPConstruct &c) {
 
 // Count the number of loop nests generated by `epc`. This is just a helper
 // function for counting the number of loop nests in a parser::Block.
-static std::optional<size_t> CountGeneratedNests(
+static std::optional<int64_t> GetNumGeneratedNests(
     const parser::ExecutionPartConstruct &epc) {
   if (parser::Unwrap<parser::DoConstruct>(epc)) {
     return 1;
@@ -258,56 +258,29 @@ static std::optional<size_t> CountGeneratedNests(
   const parser::OmpDirectiveSpecification &beginSpec{omp.BeginDir()};
   llvm::omp::Directive dir{beginSpec.DirName().v};
 
-  // TODO: Handle split, apply.
-  if (IsFullUnroll(omp)) {
-    return std::nullopt;
-  }
-  if (dir == llvm::omp::Directive::OMPD_fuse) {
-    auto nestedCount{CountGeneratedNests(std::get<parser::Block>(omp.t))};
-    // If there are no loops nested inside of FUSE, then the construct is
-    // invalid. This case will be diagnosed when analyzing the body of the FUSE
-    // construct itself, not when checking a construct in which the FUSE is
-    // nested.
-    // Returning std::nullopt prevents error messages caused by the same
-    // problem from being emitted for every enclosing loop construct, for
-    // example:
-    //   !$omp do         ! error: this should contain a loop (superfluous)
-    //   !$omp fuse       ! error: this should contain a loop
-    //   !$omp end fuse
-    if (!nestedCount || *nestedCount == 0) {
-      return std::nullopt;
-    }
-    auto *clause{
-        parser::omp::FindClause(beginSpec, llvm::omp::Clause::OMPC_looprange)};
-    if (!clause) {
-      return 1;
-    }
-
-    auto *loopRange{parser::Unwrap<parser::OmpLooprangeClause>(*clause)};
-    std::optional<int64_t> count{GetIntValue(std::get<1>(loopRange->t))};
-    if (!count || *count <= 0) {
-      return std::nullopt;
-    }
-    if (static_cast<size_t>(*count) <= *nestedCount) {
-      return 1 + *nestedCount - static_cast<size_t>(*count);
-    }
-    return std::nullopt;
+  switch (dir) {
+  case llvm::omp::Directive::OMPD_fuse:
+  case llvm::omp::Directive::OMPD_nothing:
+    return GetNumGeneratedNestsFrom(
+        epc, GetNumGeneratedNests(std::get<parser::Block>(omp.t)));
+  default:
+    break;
   }
 
   // For every other loop construct return 1.
   return 1;
 }
 
-static std::optional<size_t> CountGeneratedNests(const parser::Block &block) {
+static std::optional<int64_t> GetNumGeneratedNests(const parser::Block &block) {
   // Count the number of loops in the associated block. If there are any
   // malformed construct in there, getting the number may be meaningless.
   // These issues will be diagnosed elsewhere, and we should not emit any
   // messages about a potentially incorrect loop count.
   // In such cases reset the count to nullopt. Once it becomes nullopt,
   // keep it that way.
-  std::optional<size_t> numLoops{0};
+  std::optional<int64_t> numLoops{0};
   for (auto &epc : LoopRange(block, LoopRange::Step::Over)) {
-    if (auto genCount{CountGeneratedNests(epc)}) {
+    if (auto genCount{GetNumGeneratedNests(epc)}) {
       *numLoops += *genCount;
     } else {
       numLoops = std::nullopt;
@@ -363,7 +336,7 @@ void OmpStructureChecker::CheckNestedConstruct(
 
   // Check if a loop-nest-associated construct has only one top-level loop
   // in it.
-  if (std::optional<size_t> numLoops{CountGeneratedNests(body)}) {
+  if (std::optional<int64_t> numLoops{GetNumGeneratedNests(body)}) {
     if (*numLoops == 0) {
       context_.Say(beginSpec.DirName().source,
           "This construct should contain a DO-loop or a loop-nest-generating OpenMP construct"_err_en_US);
@@ -371,7 +344,7 @@ void OmpStructureChecker::CheckNestedConstruct(
       auto assoc{llvm::omp::getDirectiveAssociation(beginSpec.DirName().v)};
       if (*numLoops > 1 && assoc == llvm::omp::Association::LoopNest) {
         context_.Say(beginSpec.DirName().source,
-            "This construct applies to a loop nest, but has a loop sequence of length %zu"_err_en_US,
+            "This construct applies to a loop nest, but has a loop sequence of length %ld"_err_en_US,
             *numLoops);
       }
     }
@@ -585,11 +558,11 @@ void OmpStructureChecker::CheckLooprangeBounds(
     if (!first || !count || *first <= 0 || *count <= 0) {
       return;
     }
-    auto requiredCount{static_cast<size_t>(*first + *count - 1)};
-    if (auto loopCount{CountGeneratedNests(std::get<parser::Block>(x.t))}) {
+    int64_t requiredCount{*first + *count - 1};
+    if (auto loopCount{GetNumGeneratedNests(std::get<parser::Block>(x.t))}) {
       if (*loopCount < requiredCount) {
         context_.Say(clause->source,
-            "The specified loop range requires %zu loops, but the loop sequence has a length of %zu"_err_en_US,
+            "The specified loop range requires %ld loops, but the loop sequence has a length of %ld"_err_en_US,
             requiredCount, *loopCount);
       }
     }
diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index dbc7e216c4788..533242287a667 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -550,4 +550,62 @@ bool IsFullUnroll(const parser::OpenMPLoopConstruct &x) {
   }
   return false;
 }
+
+std::optional<int64_t> GetNumGeneratedNestsFrom(
+    const parser::ExecutionPartConstruct &epc,
+    std::optional<int64_t> nestedCount) {
+  if (parser::Unwrap<parser::DoConstruct>(epc)) {
+    return 1;
+  }
+
+  auto &omp{DEREF(parser::Unwrap<parser::OpenMPLoopConstruct>(epc))};
+  const parser::OmpDirectiveSpecification &beginSpec{omp.BeginDir()};
+  llvm::omp::Directive dir{beginSpec.DirId()};
+  if (!IsLoopTransforming(dir)) {
+    return 0;
+  }
+
+  // TODO: Handle split, apply.
+  if (IsFullUnroll(omp)) {
+    return std::nullopt;
+  }
+
+  if (dir == llvm::omp::Directive::OMPD_fuse) {
+    // If there are no loops nested inside of FUSE, then the construct is
+    // invalid. This case will be diagnosed when analyzing the body of the FUSE
+    // construct itself, not when checking a construct in which the FUSE is
+    // nested.
+    // Returning std::nullopt prevents error messages caused by the same
+    // problem from being emitted for every enclosing loop construct, for
+    // example:
+    //   !$omp do         ! error: this should contain a loop (superfluous)
+    //   !$omp fuse       ! error: this should contain a loop
+    //   !$omp end fuse
+    if (!nestedCount || *nestedCount == 0) {
+      return std::nullopt;
+    }
+    auto *clause{
+        parser::omp::FindClause(beginSpec, llvm::omp::Clause::OMPC_looprange)};
+    if (!clause) {
+      return 1;
+    }
+
+    auto *loopRange{parser::Unwrap<parser::OmpLooprangeClause>(*clause)};
+    std::optional<int64_t> count{GetIntValue(std::get<1>(loopRange->t))};
+    if (!count || *count <= 0) {
+      return std::nullopt;
+    }
+    if (*count <= *nestedCount) {
+      return 1 + *nestedCount - *count;
+    }
+    return std::nullopt;
+  }
+
+  if (dir == llvm::omp::Directive::OMPD_nothing) {
+    return nestedCount;
+  }
+
+  // For every other loop construct return 1.
+  return 1;
+}
 } // namespace Fortran::semantics::omp

>From 45b8c77bfcfa9a25590058853af1fb1974585c89 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Thu, 26 Feb 2026 15:06:12 -0600
Subject: [PATCH 05/13] [flang][OpenMP] Implement checks of intervening code

Invalid intervening code will cause the containing loop to be the final
loop in the loop nest. Transparent intervening code will not affect
perfect nesting if present. Currently compiler directives are considered
transparent to allow code mixing OpenMP and such directives to compile.
---
 flang/lib/Semantics/openmp-utils.cpp | 147 ++++++++++++++++++++++++++-
 1 file changed, 145 insertions(+), 2 deletions(-)

diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index 533242287a667..0b50160053012 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -19,6 +19,7 @@
 #include "flang/Common/visit.h"
 #include "flang/Evaluate/check-expression.h"
 #include "flang/Evaluate/expression.h"
+#include "flang/Evaluate/match.h"
 #include "flang/Evaluate/tools.h"
 #include "flang/Evaluate/traverse.h"
 #include "flang/Evaluate/type.h"
@@ -244,14 +245,17 @@ bool IsMapExitingType(parser::OmpMapType::Value type) {
   }
 }
 
-MaybeExpr GetEvaluateExpr(const parser::Expr &parserExpr) {
-  const parser::TypedExpr &typedExpr{parserExpr.typedExpr};
+static MaybeExpr GetEvaluateExprFromTyped(const parser::TypedExpr &typedExpr) {
   // ForwardOwningPointer           typedExpr
   // `- GenericExprWrapper          ^.get()
   //    `- std::optional<Expr>      ^->v
   return DEREF(typedExpr.get()).v;
 }
 
+MaybeExpr GetEvaluateExpr(const parser::Expr &parserExpr) {
+  return GetEvaluateExprFromTyped(parserExpr.typedExpr);
+}
+
 std::optional<evaluate::DynamicType> GetDynamicType(
     const parser::Expr &parserExpr) {
   if (auto maybeExpr{GetEvaluateExpr(parserExpr)}) {
@@ -551,6 +555,145 @@ bool IsFullUnroll(const parser::OpenMPLoopConstruct &x) {
   return false;
 }
 
+namespace {
+// Helper class to check if a given evaluate::Expr is an array expression.
+// This does not check any proper subexpressions of the expression (except
+// parentheses).
+struct ArrayExpressionRecognizer {
+  template <TypeCategory C>
+  static bool isArrayExpression(
+      const evaluate::Expr<evaluate::SomeKind<C>> &x) {
+    return common::visit([](auto &&s) { return isArrayExpression(s); }, x.u);
+  }
+
+  template <TypeCategory C, int K>
+  static bool isArrayExpression(const evaluate::Expr<evaluate::Type<C, K>> &x) {
+    return common::visit([](auto &&s) { return isArrayExpression(s); },
+        evaluate::match::deparen(x).u);
+  }
+
+  template <typename T>
+  static bool isArrayExpression(const evaluate::Designator<T> &x) {
+    if (auto *sym{std::get_if<SymbolRef>(&x.u)}) {
+      return (*sym)->Rank() != 0;
+    }
+    if (auto *array{std::get_if<evaluate::ArrayRef>(&x.u)}) {
+      return llvm::any_of(array->subscript(), [](const evaluate::Subscript &s) {
+        // A vector subscript will not be a Triplet, but will have rank > 0.
+        return std::holds_alternative<evaluate::Triplet>(s.u) || s.Rank() > 0;
+      });
+    }
+    return false;
+  }
+
+  template <typename T> static bool isArrayExpression(const T &x) {
+    return false;
+  }
+
+  static bool isArrayExpression(const evaluate::Expr<evaluate::SomeType> &x) {
+    return common::visit([](auto &&s) { return isArrayExpression(s); }, x.u);
+  }
+};
+
+// Helper class to check if a given evaluate::Expr contains a subexpression
+// (not necessarily proper) that is an array expression.
+struct ArrayExpressionFinder
+    : public evaluate::AnyTraverse<ArrayExpressionFinder> {
+  using Base = evaluate::AnyTraverse<ArrayExpressionFinder>;
+  using Base::operator();
+  ArrayExpressionFinder() : Base(*this) {}
+
+  template <typename T>
+  bool operator()(const evaluate::Designator<T> &x) const {
+    return ArrayExpressionRecognizer::isArrayExpression(x);
+  }
+};
+
+// Helper class to check if any array expressions contained in the given
+// evaluate::Expr satisfy the criteria for being in "intervening code".
+struct ArrayExpressionChecker {
+  template <typename T> bool Pre(const T &) { return true; }
+  template <typename T> void Post(const T &) {}
+
+  bool Pre(const parser::Expr &parserExpr) {
+    // If we have found a prohibited expression, skip the rest of the
+    // traversal.
+    if (!rejected) {
+      if (auto expr{GetEvaluateExpr(parserExpr)}) {
+        rejected = ArrayExpressionFinder{}(*expr);
+      }
+    }
+    return !rejected;
+  }
+
+  bool rejected{false};
+};
+} // namespace
+
+static bool ContainsInvalidArrayExpression(
+    const parser::ExecutionPartConstruct &x) {
+  ArrayExpressionChecker checker;
+  parser::Walk(x, checker);
+  return checker.rejected;
+}
+
+bool IsValidInterveningCode(const parser::ExecutionPartConstruct &x) {
+  static auto isScalar = [](const parser::Variable &variable) {
+    if (auto expr{GetEvaluateExprFromTyped(variable.typedExpr)}) {
+      return expr->Rank() == 0;
+    }
+    return false;
+  };
+
+  auto *exec{parser::Unwrap<parser::ExecutableConstruct>(x)};
+  if (!exec) {
+    // DATA, ENTRY, FORMAT, NAMELIST are not explicitly prohibited in a CLN
+    // although they are likely disallowed due to other requirements.
+    // Return true, they should be rejected elsewhere if necessary.
+    return true;
+  }
+
+  if (auto *action{parser::Unwrap<parser::ActionStmt>(exec->u)}) {
+    if (parser::Unwrap<parser::CycleStmt>(action->u) ||
+        parser::Unwrap<parser::ExitStmt>(action->u) ||
+        parser::Unwrap<parser::ForallStmt>(action->u) ||
+        parser::Unwrap<parser::WhereStmt>(action->u)) {
+      return false;
+    }
+    if (auto *assign{parser::Unwrap<parser::AssignmentStmt>(&action->u)}) {
+      if (!isScalar(std::get<parser::Variable>(assign->t))) {
+        return false;
+      }
+    }
+  } else { // Not ActionStmt
+    if (parser::Unwrap<parser::LabelDoStmt>(exec->u) ||
+        parser::Unwrap<parser::DoConstruct>(exec->u) ||
+        parser::Unwrap<parser::ForallConstruct>(exec->u) ||
+        parser::Unwrap<parser::WhereConstruct>(exec->u)) {
+      return false;
+    }
+    if (auto *omp{parser::Unwrap<parser::OpenMPConstruct>(exec->u)}) {
+      auto dirName{GetOmpDirectiveName(*omp)};
+      if (llvm::omp::getDirectiveCategory(dirName.v) ==
+          llvm::omp::Category::Executable) {
+        return false;
+      }
+    }
+  }
+
+  if (ContainsInvalidArrayExpression(x)) {
+    return false;
+  }
+
+  return true;
+}
+
+bool IsTransparentInterveningCode(const parser::ExecutionPartConstruct &x) {
+  // Tolerate compiler directives in perfect nests.
+  return parser::Unwrap<parser::CompilerDirective>(x) ||
+      parser::Unwrap<parser::ContinueStmt>(x);
+}
+
 std::optional<int64_t> GetNumGeneratedNestsFrom(
     const parser::ExecutionPartConstruct &epc,
     std::optional<int64_t> nestedCount) {

>From be0d9dafc46a5fc8221295b31552f3b9f34a213a Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Thu, 26 Feb 2026 11:21:07 -0600
Subject: [PATCH 06/13] [flang][OpenMP] Implement LoopSequence class, calculate
 sequence length

This is a tree-like structure that can represent nesting of loop sequences,
or, if the sequences are of length 1, a loop nest.
---
 flang/include/flang/Semantics/openmp-utils.h  | 57 +++++++++++--
 flang/lib/Semantics/check-omp-loop.cpp        | 38 +--------
 flang/lib/Semantics/openmp-utils.cpp          | 83 +++++++++++++++++++
 .../loop-transformation-construct01.f90       |  1 +
 4 files changed, 134 insertions(+), 45 deletions(-)

diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index 7f6c2824a986a..ac2743445a34e 100644
--- a/flang/include/flang/Semantics/openmp-utils.h
+++ b/flang/include/flang/Semantics/openmp-utils.h
@@ -16,6 +16,7 @@
 #include "flang/Common/indirection.h"
 #include "flang/Evaluate/type.h"
 #include "flang/Parser/char-block.h"
+#include "flang/Parser/openmp-utils.h"
 #include "flang/Parser/parse-tree.h"
 #include "flang/Parser/tools.h"
 #include "flang/Semantics/tools.h"
@@ -27,14 +28,6 @@
 #include <type_traits>
 #include <utility>
 
-namespace Fortran::parser::omp {
-struct ExecutionPartIterator;
-struct LoopNestIterator;
-template <typename T> struct ExecutionPartRange;
-using BlockRange = ExecutionPartRange<ExecutionPartIterator>;
-using LoopRange = ExecutionPartRange<LoopNestIterator>;
-} // namespace Fortran::parser::omp
-
 namespace Fortran::semantics {
 class Scope;
 class SemanticsContext;
@@ -46,6 +39,7 @@ using Fortran::parser::omp::ExecutionPartIterator;
 using Fortran::parser::omp::LoopNestIterator;
 using Fortran::parser::omp::BlockRange;
 using Fortran::parser::omp::LoopRange;
+using Fortran::parser::omp::is_range_v;
 
 template <typename T, typename U = std::remove_const_t<T>> U AsRvalue(T &t) {
   return U(t);
@@ -121,6 +115,53 @@ bool IsFullUnroll(const parser::OpenMPLoopConstruct &x);
 std::optional<int64_t> GetNumGeneratedNestsFrom(
     const parser::ExecutionPartConstruct &epc,
     std::optional<int64_t> nestedCount);
+
+struct LoopSequence {
+  LoopSequence(
+      const parser::ExecutionPartConstruct &root, bool allowAllLoops = false);
+
+  template <typename R, typename = std::enable_if_t<is_range_v<R>>>
+  LoopSequence(const R &range, bool allowAllLoops = false)
+      : allowAllLoops_(allowAllLoops) {
+    entry_ = std::make_unique<Construct>(range, nullptr);
+    createChildrenFromRange(entry_->location);
+    length_ = calculateLength();
+  }
+
+  bool isNest() const { return length_ && *length_ == 1; }
+  std::optional<int64_t> length() const { return length_; }
+  const std::vector<LoopSequence> &children() const { return children_; }
+
+private:
+  using Construct = ExecutionPartIterator::Construct;
+
+  LoopSequence(std::unique_ptr<Construct> entry, bool allowAllLoops);
+
+  template <typename R, typename = std::enable_if_t<is_range_v<R>>>
+  void createChildrenFromRange(const R &range) {
+    createChildrenFromRange(range.begin(), range.end());
+  }
+
+  std::unique_ptr<Construct> createConstructEntry(
+      const parser::ExecutionPartConstruct &code);
+
+  void createChildrenFromRange( //
+      ExecutionPartIterator::IteratorType begin,
+      ExecutionPartIterator::IteratorType end);
+
+  std::optional<int64_t> calculateLength() const;
+  std::optional<int64_t> sumOfChildrenLengths() const;
+
+  // Precalculated length of the sequence. Note that this is different from
+  // the number of children because a child may result in a sequence, for
+  // example a fuse with a reduced loop range. The length of that sequence
+  // adds to the length of the owning LoopSequence.
+  std::optional<int64_t> length_;
+  // The core structure of the class:
+  bool allowAllLoops_;
+  std::unique_ptr<Construct> entry_;
+  std::vector<LoopSequence> children_;
+};
 } // namespace omp
 } // namespace Fortran::semantics
 
diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp
index 45f4798d0c3c6..6c0d6afed8696 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -38,8 +38,6 @@
 #include <variant>
 
 namespace Fortran::semantics {
-static std::optional<int64_t> GetNumGeneratedNests(
-    const parser::ExecutionPartConstruct &epc);
 static std::optional<int64_t> GetNumGeneratedNests(const parser::Block &block);
 } // namespace Fortran::semantics
 
@@ -246,31 +244,6 @@ void OmpStructureChecker::CheckSIMDNest(const parser::OpenMPConstruct &c) {
   }
 }
 
-// Count the number of loop nests generated by `epc`. This is just a helper
-// function for counting the number of loop nests in a parser::Block.
-static std::optional<int64_t> GetNumGeneratedNests(
-    const parser::ExecutionPartConstruct &epc) {
-  if (parser::Unwrap<parser::DoConstruct>(epc)) {
-    return 1;
-  }
-
-  auto &omp{DEREF(parser::Unwrap<parser::OpenMPLoopConstruct>(epc))};
-  const parser::OmpDirectiveSpecification &beginSpec{omp.BeginDir()};
-  llvm::omp::Directive dir{beginSpec.DirName().v};
-
-  switch (dir) {
-  case llvm::omp::Directive::OMPD_fuse:
-  case llvm::omp::Directive::OMPD_nothing:
-    return GetNumGeneratedNestsFrom(
-        epc, GetNumGeneratedNests(std::get<parser::Block>(omp.t)));
-  default:
-    break;
-  }
-
-  // For every other loop construct return 1.
-  return 1;
-}
-
 static std::optional<int64_t> GetNumGeneratedNests(const parser::Block &block) {
   // Count the number of loops in the associated block. If there are any
   // malformed construct in there, getting the number may be meaningless.
@@ -278,16 +251,7 @@ static std::optional<int64_t> GetNumGeneratedNests(const parser::Block &block) {
   // messages about a potentially incorrect loop count.
   // In such cases reset the count to nullopt. Once it becomes nullopt,
   // keep it that way.
-  std::optional<int64_t> numLoops{0};
-  for (auto &epc : LoopRange(block, LoopRange::Step::Over)) {
-    if (auto genCount{GetNumGeneratedNests(epc)}) {
-      *numLoops += *genCount;
-    } else {
-      numLoops = std::nullopt;
-      break;
-    }
-  }
-  return numLoops;
+  return LoopSequence(block, true).length();
 }
 
 void OmpStructureChecker::CheckNestedConstruct(
diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index 0b50160053012..1757dafdd3b3d 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -694,6 +694,24 @@ bool IsTransparentInterveningCode(const parser::ExecutionPartConstruct &x) {
       parser::Unwrap<parser::ContinueStmt>(x);
 }
 
+bool IsTransformableLoop(const parser::DoConstruct &loop) {
+  return loop.IsDoNormal();
+}
+
+bool IsTransformableLoop(const parser::OpenMPLoopConstruct &omp) {
+  return IsLoopTransforming(omp.BeginDir().DirId());
+}
+
+bool IsTransformableLoop(const parser::ExecutionPartConstruct &epc) {
+  if (auto *loop{parser::Unwrap<parser::DoConstruct>(epc)}) {
+    return IsTransformableLoop(*loop);
+  }
+  if (auto *omp{parser::Unwrap<parser::OpenMPLoopConstruct>(epc)}) {
+    return IsTransformableLoop(*omp);
+  }
+  return false;
+}
+
 std::optional<int64_t> GetNumGeneratedNestsFrom(
     const parser::ExecutionPartConstruct &epc,
     std::optional<int64_t> nestedCount) {
@@ -751,4 +769,69 @@ std::optional<int64_t> GetNumGeneratedNestsFrom(
   // For every other loop construct return 1.
   return 1;
 }
+
+LoopSequence::LoopSequence(
+    const parser::ExecutionPartConstruct &root, bool allowAllLoops)
+    : allowAllLoops_(allowAllLoops) {
+  entry_ = createConstructEntry(root);
+  assert(entry_ && "Expecting loop like code");
+
+  createChildrenFromRange(entry_->location);
+  length_ = calculateLength();
+}
+
+LoopSequence::LoopSequence(std::unique_ptr<Construct> entry, bool allowAllLoops)
+    : allowAllLoops_(allowAllLoops), entry_(std::move(entry)) {
+  createChildrenFromRange(entry_->location);
+  length_ = calculateLength();
+}
+
+std::unique_ptr<LoopSequence::Construct> LoopSequence::createConstructEntry(
+    const parser::ExecutionPartConstruct &code) {
+  if (auto *loop{parser::Unwrap<parser::DoConstruct>(code)}) {
+    if (allowAllLoops_ || IsTransformableLoop(*loop)) {
+      auto &body{std::get<parser::Block>(loop->t)};
+      return std::make_unique<Construct>(body, &code);
+    }
+  } else if (auto *omp{parser::Unwrap<parser::OpenMPLoopConstruct>(code)}) {
+    if (IsTransformableLoop(*omp)) {
+      auto &body{std::get<parser::Block>(omp->t)};
+      return std::make_unique<Construct>(body, &code);
+    }
+  }
+
+  return nullptr;
+}
+
+void LoopSequence::createChildrenFromRange(
+    ExecutionPartIterator::IteratorType begin,
+    ExecutionPartIterator::IteratorType end) {
+  for (auto &code : BlockRange(begin, end, BlockRange::Step::Over)) {
+    if (auto entry{createConstructEntry(code)}) {
+      children_.push_back(LoopSequence(std::move(entry), allowAllLoops_));
+    }
+  }
+}
+
+std::optional<int64_t> LoopSequence::calculateLength() const {
+  if (!entry_->owner) {
+    return sumOfChildrenLengths();
+  }
+  if (parser::Unwrap<parser::DoConstruct>(entry_->owner)) {
+    return 1;
+  }
+  return GetNumGeneratedNestsFrom(*entry_->owner, sumOfChildrenLengths());
+}
+
+std::optional<int64_t> LoopSequence::sumOfChildrenLengths() const {
+  int64_t sum{0};
+  for (auto &seq : children_) {
+    if (auto len{seq.length()}) {
+      sum += *len;
+    } else {
+      return std::nullopt;
+    }
+  }
+  return sum;
+}
 } // namespace Fortran::semantics::omp
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90
index 4eeb7330ea589..2d27d9ab85c00 100644
--- a/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90
+++ b/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90
@@ -33,6 +33,7 @@ subroutine loop_transformation_construct3
   integer :: x
   integer :: v(i)
 
+  !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
   !$omp do
   !ERROR: Only loop-transforming OpenMP constructs are allowed inside OpenMP loop constructs
   !$omp parallel do

>From 0ac4d130136304468d51a26713956b7f31cb0ee0 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Mon, 2 Mar 2026 07:48:58 -0600
Subject: [PATCH 07/13] [flang][OpenMP] Fold GetNumGeneratedNestsFrom into
 calculateLength, NFC

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

diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index 1757dafdd3b3d..4a79a595eb7ac 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -820,7 +820,58 @@ std::optional<int64_t> LoopSequence::calculateLength() const {
   if (parser::Unwrap<parser::DoConstruct>(entry_->owner)) {
     return 1;
   }
-  return GetNumGeneratedNestsFrom(*entry_->owner, sumOfChildrenLengths());
+
+  auto &omp{DEREF(parser::Unwrap<parser::OpenMPLoopConstruct>(*entry_->owner))};
+  const parser::OmpDirectiveSpecification &beginSpec{omp.BeginDir()};
+  llvm::omp::Directive dir{beginSpec.DirId()};
+  if (!IsLoopTransforming(dir)) {
+    return 0;
+  }
+
+  // TODO: Handle split, apply.
+  if (IsFullUnroll(omp)) {
+    return std::nullopt;
+  }
+
+  auto nestedCount{sumOfChildrenLengths()};
+
+  if (dir == llvm::omp::Directive::OMPD_fuse) {
+    // If there are no loops nested inside of FUSE, then the construct is
+    // invalid. This case will be diagnosed when analyzing the body of the FUSE
+    // construct itself, not when checking a construct in which the FUSE is
+    // nested.
+    // Returning std::nullopt prevents error messages caused by the same
+    // problem from being emitted for every enclosing loop construct, for
+    // example:
+    //   !$omp do         ! error: this should contain a loop (superfluous)
+    //   !$omp fuse       ! error: this should contain a loop
+    //   !$omp end fuse
+    if (!nestedCount || *nestedCount == 0) {
+      return std::nullopt;
+    }
+    auto *clause{
+        parser::omp::FindClause(beginSpec, llvm::omp::Clause::OMPC_looprange)};
+    if (!clause) {
+      return 1;
+    }
+
+    auto *loopRange{parser::Unwrap<parser::OmpLooprangeClause>(*clause)};
+    std::optional<int64_t> count{GetIntValue(std::get<1>(loopRange->t))};
+    if (!count || *count <= 0) {
+      return std::nullopt;
+    }
+    if (*count <= *nestedCount) {
+      return 1 + *nestedCount - *count;
+    }
+    return std::nullopt;
+  }
+
+  if (dir == llvm::omp::Directive::OMPD_nothing) {
+    return nestedCount;
+  }
+
+  // For every other loop construct return 1.
+  return 1;
 }
 
 std::optional<int64_t> LoopSequence::sumOfChildrenLengths() const {

>From 49fd51cd2bc688e2d2f32cd309a6b01a4d5bee5e Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Sat, 28 Feb 2026 09:39:50 -0600
Subject: [PATCH 08/13] [flang][OpenMP] Implement nest depth calculation in
 LoopSequence

Calculate two depths, a semantic one and a perfect one. The former is the
depth of a loop nest taking into account any loop- or sequence-transforming
OpenMP constructs. The latter is the maximum level to which the semantic
nest is a perfect nest.
---
 flang/include/flang/Semantics/openmp-utils.h |  28 +++-
 flang/lib/Semantics/openmp-utils.cpp         | 144 +++++++++++++++++--
 2 files changed, 161 insertions(+), 11 deletions(-)

diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index ac2743445a34e..1f1262738a341 100644
--- a/flang/include/flang/Semantics/openmp-utils.h
+++ b/flang/include/flang/Semantics/openmp-utils.h
@@ -125,11 +125,21 @@ struct LoopSequence {
       : allowAllLoops_(allowAllLoops) {
     entry_ = std::make_unique<Construct>(range, nullptr);
     createChildrenFromRange(entry_->location);
-    length_ = calculateLength();
+    calculateEverything();
   }
 
+  struct Depth {
+    // If this sequence is a nest, the depth of the Canonical Loop Nest rooted
+    // at this sequence. Otherwise unspecified.
+    std::optional<int64_t> semantic;
+    // If this sequence is a nest, the depth of the perfect Canonical Loop Nest
+    // rooted at this sequence. Otherwise unspecified.
+    std::optional<int64_t> perfect;
+  };
+
   bool isNest() const { return length_ && *length_ == 1; }
   std::optional<int64_t> length() const { return length_; }
+  Depth depth() const { return depth_; }
   const std::vector<LoopSequence> &children() const { return children_; }
 
 private:
@@ -149,14 +159,28 @@ struct LoopSequence {
       ExecutionPartIterator::IteratorType begin,
       ExecutionPartIterator::IteratorType end);
 
+  void calculateEverything();
+
   std::optional<int64_t> calculateLength() const;
-  std::optional<int64_t> sumOfChildrenLengths() const;
+  std::optional<int64_t> getNestedLength() const;
+  Depth calculateDepths() const;
+  Depth getNestedDepths() const;
+
+  // True if the sequence contains any code (besides transformable loops)
+  // that is not a valid intervening code.
+  bool hasInvalidIC_{false};
+  // True if the sequence contains any code (besides transformable loops)
+  // that is not a valid transparent code.
+  bool hasOpaqueIC_{false};
 
   // Precalculated length of the sequence. Note that this is different from
   // the number of children because a child may result in a sequence, for
   // example a fuse with a reduced loop range. The length of that sequence
   // adds to the length of the owning LoopSequence.
   std::optional<int64_t> length_;
+  // Precalculated depths. Only meaningful if the sequence is a nest.
+  Depth depth_;
+
   // The core structure of the class:
   bool allowAllLoops_;
   std::unique_ptr<Construct> entry_;
diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index 4a79a595eb7ac..8800d21c59cc1 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -777,13 +777,13 @@ LoopSequence::LoopSequence(
   assert(entry_ && "Expecting loop like code");
 
   createChildrenFromRange(entry_->location);
-  length_ = calculateLength();
+  calculateEverything();
 }
 
 LoopSequence::LoopSequence(std::unique_ptr<Construct> entry, bool allowAllLoops)
     : allowAllLoops_(allowAllLoops), entry_(std::move(entry)) {
   createChildrenFromRange(entry_->location);
-  length_ = calculateLength();
+  calculateEverything();
 }
 
 std::unique_ptr<LoopSequence::Construct> LoopSequence::createConstructEntry(
@@ -806,16 +806,32 @@ std::unique_ptr<LoopSequence::Construct> LoopSequence::createConstructEntry(
 void LoopSequence::createChildrenFromRange(
     ExecutionPartIterator::IteratorType begin,
     ExecutionPartIterator::IteratorType end) {
+  // Create children. If there is zero or one, this LoopSequence could be
+  // a nest. If there are more, it could be a proper sequence. In the latter
+  // case any code between consecutive children must be "transparent".
   for (auto &code : BlockRange(begin, end, BlockRange::Step::Over)) {
     if (auto entry{createConstructEntry(code)}) {
       children_.push_back(LoopSequence(std::move(entry), allowAllLoops_));
+      if (!IsTransformableLoop(code)) {
+        hasInvalidIC_ = true;
+        hasOpaqueIC_ = true;
+      }
+    } else {
+      hasInvalidIC_ = hasInvalidIC_ || !IsValidInterveningCode(code);
+      hasOpaqueIC_ = hasOpaqueIC_ || !IsTransparentInterveningCode(code);
     }
   }
 }
 
+void LoopSequence::calculateEverything() {
+  // Calculate length before depths.
+  length_ = calculateLength();
+  depth_ = calculateDepths();
+}
+
 std::optional<int64_t> LoopSequence::calculateLength() const {
   if (!entry_->owner) {
-    return sumOfChildrenLengths();
+    return getNestedLength();
   }
   if (parser::Unwrap<parser::DoConstruct>(entry_->owner)) {
     return 1;
@@ -833,7 +849,7 @@ std::optional<int64_t> LoopSequence::calculateLength() const {
     return std::nullopt;
   }
 
-  auto nestedCount{sumOfChildrenLengths()};
+  auto nestedLength{getNestedLength()};
 
   if (dir == llvm::omp::Directive::OMPD_fuse) {
     // If there are no loops nested inside of FUSE, then the construct is
@@ -846,7 +862,7 @@ std::optional<int64_t> LoopSequence::calculateLength() const {
     //   !$omp do         ! error: this should contain a loop (superfluous)
     //   !$omp fuse       ! error: this should contain a loop
     //   !$omp end fuse
-    if (!nestedCount || *nestedCount == 0) {
+    if (!nestedLength || *nestedLength == 0) {
       return std::nullopt;
     }
     auto *clause{
@@ -860,21 +876,21 @@ std::optional<int64_t> LoopSequence::calculateLength() const {
     if (!count || *count <= 0) {
       return std::nullopt;
     }
-    if (*count <= *nestedCount) {
-      return 1 + *nestedCount - *count;
+    if (*count <= *nestedLength) {
+      return 1 + *nestedLength - *count;
     }
     return std::nullopt;
   }
 
   if (dir == llvm::omp::Directive::OMPD_nothing) {
-    return nestedCount;
+    return nestedLength;
   }
 
   // For every other loop construct return 1.
   return 1;
 }
 
-std::optional<int64_t> LoopSequence::sumOfChildrenLengths() const {
+std::optional<int64_t> LoopSequence::getNestedLength() const {
   int64_t sum{0};
   for (auto &seq : children_) {
     if (auto len{seq.length()}) {
@@ -885,4 +901,114 @@ std::optional<int64_t> LoopSequence::sumOfChildrenLengths() const {
   }
   return sum;
 }
+
+LoopSequence::Depth LoopSequence::calculateDepths() const {
+  auto plus{[](std::optional<int64_t> a,
+                std::optional<int64_t> b) -> std::optional<int64_t> {
+    if (a && b) {
+      return *a + *b;
+    }
+    return std::nullopt;
+  }};
+
+  // The sequence length is calculated first, so we already know if this
+  // sequence is a nest or not.
+  if (!isNest()) {
+    return Depth{0, 0};
+  }
+
+  // Get the length of the nested sequence. The hasInvalidIC_ and hasOpaqueIC_
+  // flags do not count canonical loop nests, but there can only be one for
+  // depth to make sense.
+  std::optional<int64_t> length{getNestedLength()};
+  // Get the depths of the code nested in this sequence (e.g. contained in
+  // entry_), and use it as the basis for the depths of entry_->owner.
+  auto [semaDepth, perfDepth]{getNestedDepths()};
+  if (hasInvalidIC_ || length.value_or(0) != 1) {
+    semaDepth = perfDepth = 0;
+  } else if (hasOpaqueIC_ || length.value_or(0) != 1) {
+    perfDepth = 0;
+  }
+
+  if (!entry_->owner) {
+    return Depth{semaDepth, perfDepth};
+  }
+  if (parser::Unwrap<parser::DoConstruct>(entry_->owner)) {
+    return Depth{plus(1, semaDepth), plus(1, perfDepth)};
+  }
+
+  auto &omp{DEREF(parser::Unwrap<parser::OpenMPLoopConstruct>(*entry_->owner))};
+  const parser::OmpDirectiveSpecification &beginSpec{omp.BeginDir()};
+  llvm::omp::Directive dir{beginSpec.DirId()};
+  if (!IsTransformableLoop(omp)) {
+    return Depth{0, 0};
+  }
+
+  switch (dir) {
+  // TODO: case llvm::omp::Directive::OMPD_split:
+  // TODO: case llvm::omp::Directive::OMPD_flatten:
+  case llvm::omp::Directive::OMPD_fuse:
+    if (auto *clause{parser::omp::FindClause(
+            beginSpec, llvm::omp::Clause::OMPC_depth)}) {
+      // FIXME: all loops must be fused for this
+      auto &expr{parser::UnwrapRef<parser::Expr>(clause->u)};
+      auto value{GetIntValue(expr)};
+      return Depth{value, value};
+    }
+    return Depth{1, 1};
+  case llvm::omp::Directive::OMPD_interchange:
+  case llvm::omp::Directive::OMPD_nothing:
+  case llvm::omp::Directive::OMPD_reverse:
+    return {semaDepth, perfDepth};
+  case llvm::omp::Directive::OMPD_stripe:
+  case llvm::omp::Directive::OMPD_tile:
+    // Look for SIZES clause.
+    if (auto *clause{parser::omp::FindClause(
+            beginSpec, llvm::omp::Clause::OMPC_sizes)}) {
+      // Return the number of arguments in the SIZES clause
+      size_t num{
+          parser::UnwrapRef<parser::OmpClause::Sizes>(clause->u).v.size()};
+      return Depth{plus(num, semaDepth), plus(num, perfDepth)};
+    }
+    // The SIZES clause is mandatory, if it's missing the result is unknown.
+    return {std::nullopt, std::nullopt};
+  case llvm::omp::Directive::OMPD_unroll:
+    if (IsFullUnroll(omp)) {
+      return Depth{0, 0};
+    }
+    // If this is not a full unroll then look for a PARTIAL clause.
+    if (auto *clause{parser::omp::FindClause(
+            beginSpec, llvm::omp::Clause::OMPC_partial)}) {
+      std::optional<int64_t> factor;
+      if (auto *expr{parser::Unwrap<parser::Expr>(clause->u)}) {
+        factor = GetIntValue(*expr);
+      }
+      // If it's a partial unroll, and the unroll count is 1, then this
+      // construct is a no-op.
+      if (factor && *factor == 1) {
+        return Depth{semaDepth, perfDepth};
+      }
+      // If it's a proper partial unroll, then the resulting loop cannot
+      // have either depth greater than 1: if it had a loop nested in it,
+      // then after unroll it will have at least two copies it it, making
+      // it a final loop.
+      return {1, 1};
+    }
+    return Depth{std::nullopt, std::nullopt};
+  default:
+    llvm_unreachable("Expecting loop-transforming construct");
+  }
+}
+
+LoopSequence::Depth LoopSequence::getNestedDepths() const {
+  if (length() != 1) {
+    return Depth{0, 0};
+  } else if (children_.empty()) {
+    assert(entry_->owner &&
+        parser::Unwrap<parser::DoConstruct>(entry_->owner) &&
+        "Expecting DO construct");
+    return Depth{0, 0};
+  }
+  return children_.front().depth_;
+}
 } // namespace Fortran::semantics::omp

>From 27e42ae4783021ac0e6ef5d7f697334c65e4330f Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Fri, 6 Mar 2026 14:31:22 -0600
Subject: [PATCH 09/13] [flang][OpenMP] Identify affected loops, provide reason

Implement utility functions to calculate the number of affected loops
in a sequence or in a nest. Provide a reason for the returned value
to be used in an explanatory message.
---
 flang/include/flang/Semantics/openmp-utils.h |  59 +++++-
 flang/lib/Semantics/check-omp-loop.cpp       |  37 ++--
 flang/lib/Semantics/openmp-utils.cpp         | 189 ++++++++++++++++++-
 3 files changed, 248 insertions(+), 37 deletions(-)

diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index 1f1262738a341..a9801cf5b5753 100644
--- a/flang/include/flang/Semantics/openmp-utils.h
+++ b/flang/include/flang/Semantics/openmp-utils.h
@@ -22,11 +22,14 @@
 #include "flang/Semantics/tools.h"
 
 #include "llvm/ADT/ArrayRef.h"
+#include "llvm/Support/Format.h"
 
 #include <optional>
 #include <string>
+#include <tuple>
 #include <type_traits>
 #include <utility>
+#include <vector>
 
 namespace Fortran::semantics {
 class Scope;
@@ -109,6 +112,34 @@ bool IsPointerAssignment(const evaluate::Assignment &x);
 
 MaybeExpr MakeEvaluateExpr(const parser::OmpStylizedInstance &inp);
 
+// A representation of a "because" message. The `text` member is a formatted
+// message (i.e. without any printf-like formatting characters like %d, etc).
+// `source` is the location to which the "because" message will refer.
+struct Reason {
+  std::string text;
+  parser::CharBlock source;
+
+  Reason() = default;
+  Reason(const std::string t, parser::CharBlock s) : text(t), source(s) {}
+  operator bool() const { return !source.empty(); }
+};
+
+// Helper that formats the inputs into a std::string.
+template <typename ...Ts>
+static std::string format(const char *fmt, Ts... values) {
+  std::string str;
+  llvm::raw_string_ostream os(str);
+  os << llvm::format(fmt, values...);
+  return os.str();
+}
+
+std::pair<std::optional<int64_t>, Reason> GetArgumentValueWithReason(
+    const parser::OmpDirectiveSpecification &spec, llvm::omp::Clause clauseId,
+    unsigned version);
+std::pair<std::optional<int64_t>, Reason> GetNumArgumentsWithReason(
+    const parser::OmpDirectiveSpecification &spec, llvm::omp::Clause clauseId,
+    unsigned version);
+
 bool IsLoopTransforming(llvm::omp::Directive dir);
 bool IsFullUnroll(const parser::OpenMPLoopConstruct &x);
 
@@ -116,13 +147,29 @@ std::optional<int64_t> GetNumGeneratedNestsFrom(
     const parser::ExecutionPartConstruct &epc,
     std::optional<int64_t> nestedCount);
 
+// Return the depth of the affected nests:
+//   {affected-depth, must-be-perfect-nest, reason}.
+std::tuple<std::optional<int64_t>, bool, Reason> GetAffectedNestDepthWithReason(
+    const parser::OpenMPLoopConstruct &x, unsigned version);
+// Return the range of the affected nests in the sequence:
+//   {first, count, reason}.
+// If the range is "the whole sequence", the return value will be {1, -1, ...}.
+std::tuple<std::optional<int64_t>, std::optional<int64_t>, Reason>
+GetAffectedLoopRangeWithReason(
+    const parser::OpenMPLoopConstruct &x, unsigned version);
+
+// Count the required loop count from range. If count == -1, return -1,
+// indicating all loops in the sequence.
+std::optional<int64_t> GetRequiredCount(
+    std::optional<int64_t> first, std::optional<int64_t> count);
+
 struct LoopSequence {
-  LoopSequence(
-      const parser::ExecutionPartConstruct &root, bool allowAllLoops = false);
+  LoopSequence(const parser::ExecutionPartConstruct &root, unsigned version,
+      bool allowAllLoops = false);
 
   template <typename R, typename = std::enable_if_t<is_range_v<R>>>
-  LoopSequence(const R &range, bool allowAllLoops = false)
-      : allowAllLoops_(allowAllLoops) {
+  LoopSequence(const R &range, unsigned version, bool allowAllLoops = false)
+      : version_(version), allowAllLoops_(allowAllLoops) {
     entry_ = std::make_unique<Construct>(range, nullptr);
     createChildrenFromRange(entry_->location);
     calculateEverything();
@@ -145,7 +192,8 @@ struct LoopSequence {
 private:
   using Construct = ExecutionPartIterator::Construct;
 
-  LoopSequence(std::unique_ptr<Construct> entry, bool allowAllLoops);
+  LoopSequence(
+      std::unique_ptr<Construct> entry, unsigned version, bool allowAllLoops);
 
   template <typename R, typename = std::enable_if_t<is_range_v<R>>>
   void createChildrenFromRange(const R &range) {
@@ -182,6 +230,7 @@ struct LoopSequence {
   Depth depth_;
 
   // The core structure of the class:
+  unsigned version_; // Needed for GetXyzWithReason
   bool allowAllLoops_;
   std::unique_ptr<Construct> entry_;
   std::vector<LoopSequence> children_;
diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp
index 6c0d6afed8696..b17ece42f475a 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -37,10 +37,6 @@
 #include <tuple>
 #include <variant>
 
-namespace Fortran::semantics {
-static std::optional<int64_t> GetNumGeneratedNests(const parser::Block &block);
-} // namespace Fortran::semantics
-
 namespace {
 using namespace Fortran;
 
@@ -244,19 +240,10 @@ void OmpStructureChecker::CheckSIMDNest(const parser::OpenMPConstruct &c) {
   }
 }
 
-static std::optional<int64_t> GetNumGeneratedNests(const parser::Block &block) {
-  // Count the number of loops in the associated block. If there are any
-  // malformed construct in there, getting the number may be meaningless.
-  // These issues will be diagnosed elsewhere, and we should not emit any
-  // messages about a potentially incorrect loop count.
-  // In such cases reset the count to nullopt. Once it becomes nullopt,
-  // keep it that way.
-  return LoopSequence(block, true).length();
-}
-
 void OmpStructureChecker::CheckNestedConstruct(
     const parser::OpenMPLoopConstruct &x) {
   const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()};
+  unsigned version{context_.langOptions().OpenMPVersion};
 
   // End-directive is not allowed in such cases:
   //   do 100 i = ...
@@ -298,9 +285,11 @@ void OmpStructureChecker::CheckNestedConstruct(
     }
   }
 
+  LoopSequence sequence(body, version, true);
+
   // Check if a loop-nest-associated construct has only one top-level loop
   // in it.
-  if (std::optional<int64_t> numLoops{GetNumGeneratedNests(body)}) {
+  if (std::optional<int64_t> numLoops{sequence.length()}) {
     if (*numLoops == 0) {
       context_.Say(beginSpec.DirName().source,
           "This construct should contain a DO-loop or a loop-nest-generating OpenMP construct"_err_en_US);
@@ -514,20 +503,20 @@ void OmpStructureChecker::CheckDistLinear(
 
 void OmpStructureChecker::CheckLooprangeBounds(
     const parser::OpenMPLoopConstruct &x) {
+  unsigned version{context_.langOptions().OpenMPVersion};
   if (auto *clause{parser::omp::FindClause(
           x.BeginDir(), llvm::omp::Clause::OMPC_looprange)}) {
     auto *lrClause{parser::Unwrap<parser::OmpLooprangeClause>(clause)};
     auto first{GetIntValue(std::get<0>(lrClause->t))};
     auto count{GetIntValue(std::get<1>(lrClause->t))};
-    if (!first || !count || *first <= 0 || *count <= 0) {
-      return;
-    }
-    int64_t requiredCount{*first + *count - 1};
-    if (auto loopCount{GetNumGeneratedNests(std::get<parser::Block>(x.t))}) {
-      if (*loopCount < requiredCount) {
-        context_.Say(clause->source,
-            "The specified loop range requires %ld loops, but the loop sequence has a length of %ld"_err_en_US,
-            requiredCount, *loopCount);
+    if (auto requiredCount{GetRequiredCount(first, count)}) {
+      LoopSequence sequence(std::get<parser::Block>(x.t), version, true);
+      if (auto loopCount{sequence.length()}) {
+        if (*loopCount < *requiredCount) {
+          context_.Say(clause->source,
+              "The specified loop range requires %ld loops, but the loop sequence has a length of %ld"_err_en_US,
+              *requiredCount, *loopCount);
+        }
       }
     }
   }
diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index 8800d21c59cc1..63cd9b42e651c 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -528,6 +528,42 @@ MaybeExpr MakeEvaluateExpr(const parser::OmpStylizedInstance &inp) {
       instance.u);
 }
 
+std::pair<std::optional<int64_t>, Reason> GetArgumentValueWithReason(
+    const parser::OmpDirectiveSpecification &spec, llvm::omp::Clause clauseId,
+    unsigned version) {
+  if (auto *clause{parser::omp::FindClause(spec, clauseId)}) {
+    if (auto *expr{parser::Unwrap<parser::Expr>(clause->u)}) {
+      if (auto value{GetIntValue(*expr)}) {
+        llvm::StringRef name{llvm::omp::getOpenMPClauseName(clauseId, version)};
+        Reason reason( //
+            format("%s clause was specified with argument %ld",
+                parser::ToUpperCaseLetters(name.str()).c_str(), *value),
+            clause->source);
+        return {*value, reason};
+      }
+    }
+  }
+  return {std::nullopt, Reason()};
+}
+
+std::pair<std::optional<int64_t>, Reason> GetNumArgumentsWithReason(
+    const parser::OmpDirectiveSpecification &spec, llvm::omp::Clause clauseId,
+    unsigned version) {
+  if (auto *clause{parser::omp::FindClause(spec, clauseId)}) {
+    using ArgumentList = std::list<parser::ScalarIntExpr>;
+    if (auto *args{parser::Unwrap<ArgumentList>(clause->u)}) {
+      llvm::StringRef name{llvm::omp::getOpenMPClauseName(clauseId, version)};
+      auto num{static_cast<int64_t>(args->size())};
+      Reason reason( //
+          format("%s clause was specified with %ld arguments",
+              parser::ToUpperCaseLetters(name.str()).c_str(), num),
+          clause->source);
+      return {num, reason};
+    }
+  }
+  return {std::nullopt, Reason()};
+}
+
 bool IsLoopTransforming(llvm::omp::Directive dir) {
   switch (dir) {
   // TODO case llvm::omp::Directive::OMPD_flatten:
@@ -770,9 +806,132 @@ std::optional<int64_t> GetNumGeneratedNestsFrom(
   return 1;
 }
 
-LoopSequence::LoopSequence(
-    const parser::ExecutionPartConstruct &root, bool allowAllLoops)
-    : allowAllLoops_(allowAllLoops) {
+// Return the depth of the affected nests:
+//   {affected-depth, must-be-perfect-nest}.
+std::tuple<std::optional<int64_t>, bool, Reason> GetAffectedNestDepthWithReason(
+    const parser::OpenMPLoopConstruct &x, unsigned version) {
+  const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()};
+  llvm::omp::Directive dir{beginSpec.DirId()};
+  bool allowsCollapse{llvm::omp::isAllowedClauseForDirective(
+      dir, llvm::omp::Clause::OMPC_collapse, version)};
+  bool allowsOrdered{llvm::omp::isAllowedClauseForDirective(
+      dir, llvm::omp::Clause::OMPC_ordered, version)};
+
+  if (allowsCollapse || allowsOrdered) {
+    auto [count, reason]{GetArgumentValueWithReason(
+        beginSpec, llvm::omp::Clause::OMPC_collapse, version)};
+    auto [vo, ro]{GetArgumentValueWithReason(
+        beginSpec, llvm::omp::Clause::OMPC_ordered, version)};
+    if (vo) {
+      if (!count || *count < *vo) {
+        count = vo;
+        reason = ro;
+      }
+    }
+    return {count, true, reason};
+  }
+
+  if (IsLoopTransforming(dir)) {
+    switch (dir) {
+    case llvm::omp::Directive::OMPD_interchange: {
+      // Get the length of the argument list to PERMUTATION.
+      auto [num, reason]{GetNumArgumentsWithReason(
+          beginSpec, llvm::omp::Clause::OMPC_permutation, version)};
+      return {num, true, reason};
+    }
+    case llvm::omp::Directive::OMPD_stripe:
+    case llvm::omp::Directive::OMPD_tile: {
+      // Get the length of the argument list to SIZES.
+      auto [num, reason]{GetNumArgumentsWithReason(
+          beginSpec, llvm::omp::Clause::OMPC_sizes, version)};
+      return {num, true, reason};
+      return {std::nullopt, true, Reason()};
+    }
+    case llvm::omp::Directive::OMPD_fuse: {
+      // Get the value from the argument to DEPTH.
+      if (parser::omp::FindClause(beginSpec, llvm::omp::Clause::OMPC_depth)) {
+        auto [count, reason]{GetArgumentValueWithReason(
+            beginSpec, llvm::omp::Clause::OMPC_depth, version)};
+        return {count, true, reason};
+      }
+      std::string name{
+          parser::omp::GetUpperName(llvm::omp::Clause::OMPC_depth, version)};
+      Reason reason(
+          format("%s clause was not specified, a value of 1 was assumed",
+              name.c_str()),
+          beginSpec.source);
+      return {1, true, reason};
+    }
+    case llvm::omp::Directive::OMPD_reverse:
+    case llvm::omp::Directive::OMPD_unroll:
+      return {1, false, Reason()};
+    // TODO: case llvm::omp::Directive::OMPD_flatten:
+    // TODO: case llvm::omp::Directive::OMPD_split:
+    default:
+      break;
+    }
+  }
+
+  return {std::nullopt, false, Reason()};
+}
+
+// Return the range of the affected nests in the sequence:
+//   {first, count, reason}.
+std::tuple<std::optional<int64_t>, std::optional<int64_t>, Reason>
+GetAffectedLoopRangeWithReason(
+    const parser::OpenMPLoopConstruct &x, unsigned version) {
+  const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()};
+  llvm::omp::Directive dir{beginSpec.DirId()};
+
+  if (dir == llvm::omp::Directive::OMPD_fuse) {
+    std::string name{parser::ToUpperCaseLetters(llvm::omp::getOpenMPClauseName(
+        llvm::omp::Clause::OMPC_looprange, version))};
+    if (auto *clause{parser::omp::FindClause(
+            beginSpec, llvm::omp::Clause::OMPC_looprange)}) {
+      auto &range{DEREF(parser::Unwrap<parser::OmpLooprangeClause>(clause->u))};
+      std::optional<int64_t> first{GetIntValue(std::get<0>(range.t))};
+      std::optional<int64_t> count{GetIntValue(std::get<1>(range.t))};
+      if (!first || !count || *first <= 0 || *count <= 0) {
+        return {std::nullopt, std::nullopt, Reason()};
+      }
+      std::string name{parser::omp::GetUpperName(
+          llvm::omp::Clause::OMPC_looprange, version)};
+      Reason reason(
+          format("%s clause was specified with a count of %ld starting at loop "
+                 "%ld",
+              name.c_str(), *count, *first),
+          clause->source);
+      return {*first, *count, reason};
+    }
+    // If LOOPRANGE was not found, return {1, -1}, where -1 means "the whole
+    // associated sequence".
+    Reason reason(
+        "%s clause was not specified, a value of 1 was assumed", name.c_str());
+    return {1, -1, reason};
+  }
+
+  assert(llvm::omp::getDirectiveAssociation(dir) ==
+          llvm::omp::Association::LoopNest &&
+      "Expecting loop-nest-associated construct");
+  // For loop-nest constructs, a single loop-nest is affected.
+  return {1, 1, Reason()};
+}
+
+std::optional<int64_t> GetRequiredCount(
+    std::optional<int64_t> first, std::optional<int64_t> count) {
+  if (first && count && *first > 0) {
+    if (*count > 0) {
+      return *first + *count - 1;
+    } else if (*count == -1) {
+      return -1;
+    }
+  }
+  return std::nullopt;
+}
+
+LoopSequence::LoopSequence(const parser::ExecutionPartConstruct &root,
+    unsigned version, bool allowAllLoops)
+    : version_(version), allowAllLoops_(allowAllLoops) {
   entry_ = createConstructEntry(root);
   assert(entry_ && "Expecting loop like code");
 
@@ -780,8 +939,10 @@ LoopSequence::LoopSequence(
   calculateEverything();
 }
 
-LoopSequence::LoopSequence(std::unique_ptr<Construct> entry, bool allowAllLoops)
-    : allowAllLoops_(allowAllLoops), entry_(std::move(entry)) {
+LoopSequence::LoopSequence(
+    std::unique_ptr<Construct> entry, unsigned version, bool allowAllLoops)
+    : version_(version), allowAllLoops_(allowAllLoops),
+      entry_(std::move(entry)) {
   createChildrenFromRange(entry_->location);
   calculateEverything();
 }
@@ -811,7 +972,8 @@ void LoopSequence::createChildrenFromRange(
   // case any code between consecutive children must be "transparent".
   for (auto &code : BlockRange(begin, end, BlockRange::Step::Over)) {
     if (auto entry{createConstructEntry(code)}) {
-      children_.push_back(LoopSequence(std::move(entry), allowAllLoops_));
+      children_.push_back(
+          LoopSequence(std::move(entry), version_, allowAllLoops_));
       if (!IsTransformableLoop(code)) {
         hasInvalidIC_ = true;
         hasOpaqueIC_ = true;
@@ -950,10 +1112,21 @@ LoopSequence::Depth LoopSequence::calculateDepths() const {
   case llvm::omp::Directive::OMPD_fuse:
     if (auto *clause{parser::omp::FindClause(
             beginSpec, llvm::omp::Clause::OMPC_depth)}) {
-      // FIXME: all loops must be fused for this
       auto &expr{parser::UnwrapRef<parser::Expr>(clause->u)};
       auto value{GetIntValue(expr)};
-      return Depth{value, value};
+      auto nestedLength{getNestedLength()};
+      // The result is a perfect nest only if all loop in the sequence
+      // are fused.
+      if (value && nestedLength) {
+        auto [first, count, _]{GetAffectedLoopRangeWithReason(omp, version_)};
+        if (auto required{GetRequiredCount(first, count)}) {
+          if (*required == -1 || *required == *nestedLength) {
+            return Depth{value, value};
+          }
+          return Depth{1, 1};
+        }
+      }
+      return Depth{std::nullopt, std::nullopt};
     }
     return Depth{1, 1};
   case llvm::omp::Directive::OMPD_interchange:

>From 2db52144fe6cee0eb83b8e8d0032323492a5454e Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Sun, 8 Mar 2026 12:12:35 -0500
Subject: [PATCH 10/13] format

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

diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index a9801cf5b5753..6b67b04d7a2b7 100644
--- a/flang/include/flang/Semantics/openmp-utils.h
+++ b/flang/include/flang/Semantics/openmp-utils.h
@@ -125,7 +125,7 @@ struct Reason {
 };
 
 // Helper that formats the inputs into a std::string.
-template <typename ...Ts>
+template <typename... Ts>
 static std::string format(const char *fmt, Ts... values) {
   std::string str;
   llvm::raw_string_ostream os(str);

>From 03fcb44ce5ca58c1b45f332379b0c6c3a789e21e Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Thu, 12 Mar 2026 09:35:59 -0500
Subject: [PATCH 11/13] Fix merge error

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

diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index f4bcc5378ec0e..c5dc3887a077e 100644
--- a/flang/include/flang/Semantics/openmp-utils.h
+++ b/flang/include/flang/Semantics/openmp-utils.h
@@ -231,6 +231,7 @@ struct LoopSequence {
   Depth depth_;
 
   // The core structure of the class:
+  unsigned version_; // Needed for GetXyzWithReason
   bool allowAllLoops_;
   std::unique_ptr<Construct> entry_;
   std::vector<LoopSequence> children_;

>From 0c36be0b2b8c3bb7f8d425b58b07099a0352f947 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Thu, 12 Mar 2026 09:59:51 -0500
Subject: [PATCH 12/13] Fix more merge issues, use /// for comment

---
 flang/include/flang/Semantics/openmp-utils.h | 8 ++++----
 flang/lib/Semantics/openmp-utils.cpp         | 2 +-
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index c5dc3887a077e..463524a365edc 100644
--- a/flang/include/flang/Semantics/openmp-utils.h
+++ b/flang/include/flang/Semantics/openmp-utils.h
@@ -112,9 +112,9 @@ bool IsPointerAssignment(const evaluate::Assignment &x);
 
 MaybeExpr MakeEvaluateExpr(const parser::OmpStylizedInstance &inp);
 
-// A representation of a "because" message. The `text` member is a formatted
-// message (i.e. without any printf-like formatting characters like %d, etc).
-// `source` is the location to which the "because" message will refer.
+/// A representation of a "because" message. The `text` member is a formatted
+/// message (i.e. without any printf-like formatting characters like %d, etc).
+/// `source` is the location to which the "because" message will refer.
 struct Reason {
   std::string text;
   parser::CharBlock source;
@@ -172,7 +172,7 @@ struct LoopSequence {
       : version_(version), allowAllLoops_(allowAllLoops) {
     entry_ = std::make_unique<Construct>(range, nullptr);
     createChildrenFromRange(entry_->location);
-    calculateEverything();
+    precalculate();
   }
 
   struct Depth {
diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index cd8a7bacfc2cf..3f174241f21eb 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -944,7 +944,7 @@ LoopSequence::LoopSequence(const parser::ExecutionPartConstruct &root,
   assert(entry_ && "Expecting loop like code");
 
   createChildrenFromRange(entry_->location);
-  calculateEverything();
+  precalculate();
 }
 
 LoopSequence::LoopSequence(

>From d9ce92d47838789873ed4de4212d7cf79a626129 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Thu, 12 Mar 2026 11:20:44 -0500
Subject: [PATCH 13/13] Use parser::Messages for Reason

---
 flang/include/flang/Semantics/openmp-utils.h | 26 +++-----
 flang/lib/Semantics/openmp-utils.cpp         | 69 ++++++++++----------
 2 files changed, 43 insertions(+), 52 deletions(-)

diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index 463524a365edc..23bfd6a8de088 100644
--- a/flang/include/flang/Semantics/openmp-utils.h
+++ b/flang/include/flang/Semantics/openmp-utils.h
@@ -16,13 +16,13 @@
 #include "flang/Common/indirection.h"
 #include "flang/Evaluate/type.h"
 #include "flang/Parser/char-block.h"
+#include "flang/Parser/message.h"
 #include "flang/Parser/openmp-utils.h"
 #include "flang/Parser/parse-tree.h"
 #include "flang/Parser/tools.h"
 #include "flang/Semantics/tools.h"
 
 #include "llvm/ADT/ArrayRef.h"
-#include "llvm/Support/Format.h"
 
 #include <optional>
 #include <string>
@@ -112,27 +112,17 @@ bool IsPointerAssignment(const evaluate::Assignment &x);
 
 MaybeExpr MakeEvaluateExpr(const parser::OmpStylizedInstance &inp);
 
-/// A representation of a "because" message. The `text` member is a formatted
-/// message (i.e. without any printf-like formatting characters like %d, etc).
-/// `source` is the location to which the "because" message will refer.
+/// A representation of a "because" message.
 struct Reason {
-  std::string text;
-  parser::CharBlock source;
+  parser::Messages msgs;
 
-  Reason() = default;
-  Reason(const std::string t, parser::CharBlock s) : text(t), source(s) {}
-  operator bool() const { return !source.empty(); }
+  template <typename... Ts> Reason &Say(Ts &&...args) {
+    msgs.Say(std::forward<Ts>(args)...);
+    return *this;
+  };
+  operator bool() const { return !msgs.empty(); }
 };
 
-// Helper that formats the inputs into a std::string.
-template <typename... Ts>
-static std::string format(const char *fmt, Ts... values) {
-  std::string str;
-  llvm::raw_string_ostream os(str);
-  os << llvm::format(fmt, values...);
-  return os.str();
-}
-
 std::pair<std::optional<int64_t>, Reason> GetArgumentValueWithReason(
     const parser::OmpDirectiveSpecification &spec, llvm::omp::Clause clauseId,
     unsigned version);
diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index 3f174241f21eb..1f18af57c2d92 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -534,12 +534,12 @@ std::pair<std::optional<int64_t>, Reason> GetArgumentValueWithReason(
   if (auto *clause{parser::omp::FindClause(spec, clauseId)}) {
     if (auto *expr{parser::Unwrap<parser::Expr>(clause->u)}) {
       if (auto value{GetIntValue(*expr)}) {
-        llvm::StringRef name{llvm::omp::getOpenMPClauseName(clauseId, version)};
-        Reason reason( //
-            format("%s clause was specified with argument %ld",
-                parser::ToUpperCaseLetters(name.str()).c_str(), *value),
-            clause->source);
-        return {*value, reason};
+        std::string name{GetUpperName(clauseId, version)};
+        Reason reason;
+        reason.Say(clause->source,
+            "%s clause was specified with argument %" PRId64 ""_because_en_US,
+            name.c_str(), *value);
+        return {*value, std::move(reason)};
       }
     }
   }
@@ -552,13 +552,13 @@ std::pair<std::optional<int64_t>, Reason> GetNumArgumentsWithReason(
   if (auto *clause{parser::omp::FindClause(spec, clauseId)}) {
     using ArgumentList = std::list<parser::ScalarIntExpr>;
     if (auto *args{parser::Unwrap<ArgumentList>(clause->u)}) {
-      llvm::StringRef name{llvm::omp::getOpenMPClauseName(clauseId, version)};
+      std::string name{GetUpperName(clauseId, version)};
       auto num{static_cast<int64_t>(args->size())};
-      Reason reason( //
-          format("%s clause was specified with %ld arguments",
-              parser::ToUpperCaseLetters(name.str()).c_str(), num),
-          clause->source);
-      return {num, reason};
+      Reason reason;
+      reason.Say(clause->source,
+          "%s clause was specified with %" PRId64 " arguments"_because_en_US,
+          name.c_str(), num);
+      return {num, std::move(reason)};
     }
   }
   return {std::nullopt, Reason()};
@@ -833,10 +833,10 @@ std::tuple<std::optional<int64_t>, bool, Reason> GetAffectedNestDepthWithReason(
     if (vo) {
       if (!count || *count < *vo) {
         count = vo;
-        reason = ro;
+        reason = std::move(ro);
       }
     }
-    return {count, true, reason};
+    return {count, true, std::move(reason)};
   }
 
   if (IsLoopTransforming(dir)) {
@@ -845,14 +845,14 @@ std::tuple<std::optional<int64_t>, bool, Reason> GetAffectedNestDepthWithReason(
       // Get the length of the argument list to PERMUTATION.
       auto [num, reason]{GetNumArgumentsWithReason(
           beginSpec, llvm::omp::Clause::OMPC_permutation, version)};
-      return {num, true, reason};
+      return {num, true, std::move(reason)};
     }
     case llvm::omp::Directive::OMPD_stripe:
     case llvm::omp::Directive::OMPD_tile: {
       // Get the length of the argument list to SIZES.
       auto [num, reason]{GetNumArgumentsWithReason(
           beginSpec, llvm::omp::Clause::OMPC_sizes, version)};
-      return {num, true, reason};
+      return {num, true, std::move(reason)};
       return {std::nullopt, true, Reason()};
     }
     case llvm::omp::Directive::OMPD_fuse: {
@@ -860,15 +860,15 @@ std::tuple<std::optional<int64_t>, bool, Reason> GetAffectedNestDepthWithReason(
       if (parser::omp::FindClause(beginSpec, llvm::omp::Clause::OMPC_depth)) {
         auto [count, reason]{GetArgumentValueWithReason(
             beginSpec, llvm::omp::Clause::OMPC_depth, version)};
-        return {count, true, reason};
+        return {count, true, std::move(reason)};
       }
       std::string name{
           parser::omp::GetUpperName(llvm::omp::Clause::OMPC_depth, version)};
-      Reason reason(
-          format("%s clause was not specified, a value of 1 was assumed",
-              name.c_str()),
-          beginSpec.source);
-      return {1, true, reason};
+      Reason reason;
+      reason.Say(beginSpec.source,
+          "%s clause was not specified, a value of 1 was assumed"_because_en_US,
+          name.c_str());
+      return {1, true, std::move(reason)};
     }
     case llvm::omp::Directive::OMPD_reverse:
     case llvm::omp::Directive::OMPD_unroll:
@@ -884,7 +884,7 @@ std::tuple<std::optional<int64_t>, bool, Reason> GetAffectedNestDepthWithReason(
 }
 
 // Return the range of the affected nests in the sequence:
-//   {first, count, reason}.
+//   {first, count, std::move(reason)}.
 std::tuple<std::optional<int64_t>, std::optional<int64_t>, Reason>
 GetAffectedLoopRangeWithReason(
     const parser::OpenMPLoopConstruct &x, unsigned version) {
@@ -892,8 +892,7 @@ GetAffectedLoopRangeWithReason(
   llvm::omp::Directive dir{beginSpec.DirId()};
 
   if (dir == llvm::omp::Directive::OMPD_fuse) {
-    std::string name{parser::ToUpperCaseLetters(llvm::omp::getOpenMPClauseName(
-        llvm::omp::Clause::OMPC_looprange, version))};
+    std::string name{GetUpperName(llvm::omp::Clause::OMPC_looprange, version)};
     if (auto *clause{parser::omp::FindClause(
             beginSpec, llvm::omp::Clause::OMPC_looprange)}) {
       auto &range{DEREF(parser::Unwrap<parser::OmpLooprangeClause>(clause->u))};
@@ -904,18 +903,20 @@ GetAffectedLoopRangeWithReason(
       }
       std::string name{parser::omp::GetUpperName(
           llvm::omp::Clause::OMPC_looprange, version)};
-      Reason reason(
-          format("%s clause was specified with a count of %ld starting at loop "
-                 "%ld",
-              name.c_str(), *count, *first),
-          clause->source);
-      return {*first, *count, reason};
+      Reason reason;
+      reason.Say(clause->source,
+          "%s clause was specified with a count of %" PRId64
+          " starting at loop %" PRId64 ""_because_en_US,
+          name.c_str(), *count, *first);
+      return {*first, *count, std::move(reason)};
     }
     // If LOOPRANGE was not found, return {1, -1}, where -1 means "the whole
     // associated sequence".
-    Reason reason(
-        "%s clause was not specified, a value of 1 was assumed", name.c_str());
-    return {1, -1, reason};
+    Reason reason;
+    reason.Say(x.source,
+        "%s clause was not specified, a value of 1 was assumed"_because_en_US,
+        name.c_str());
+    return {1, -1, std::move(reason)};
   }
 
   assert(llvm::omp::getDirectiveAssociation(dir) ==



More information about the flang-commits mailing list