[flang-commits] [flang] [flang][OpenMP] Generalize checks of loop construct structure (PR #170735)

Krzysztof Parzyszek via flang-commits flang-commits at lists.llvm.org
Thu Dec 11 09:38:28 PST 2025


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

>From ca0b4a3ae509f29aaaaa9a67140533e3ceca544b Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Tue, 2 Dec 2025 14:34:45 -0600
Subject: [PATCH 1/6] [flang][OpenMP] Implement loop construct iterator range

Since we're trying to preserve compiler directives in loop constructs,
not every element of the associated parser::Block needs to be a loop
or an OpenMP loop construct. Implement a helper class `LoopRange` to
make it easy to iterate over elements of parser::Block that are loops
or loop constructs.
---
 flang/include/flang/Parser/openmp-utils.h | 74 +++++++++++++++++++++++
 flang/lib/Parser/openmp-utils.cpp         | 37 ++++++++++++
 2 files changed, 111 insertions(+)

diff --git a/flang/include/flang/Parser/openmp-utils.h b/flang/include/flang/Parser/openmp-utils.h
index e164f63aa189b..27e4aa6db0f0b 100644
--- a/flang/include/flang/Parser/openmp-utils.h
+++ b/flang/include/flang/Parser/openmp-utils.h
@@ -237,6 +237,80 @@ struct OmpAllocateInfo {
 
 OmpAllocateInfo SplitOmpAllocate(const OmpAllocateDirective &x);
 
+namespace detail {
+template <bool IsConst, typename T>
+struct ConstIf {
+  using type = std::conditional_t<IsConst, std::add_const_t<T>, T>;
+};
+
+template <bool IsConst, typename T>
+using ConstIfT = typename ConstIf<IsConst, T>::type;
+}
+
+template <bool IsConst> struct LoopRange {
+  using QualBlock = detail::ConstIfT<IsConst, Block>;
+  using QualReference = decltype(std::declval<QualBlock>().front());
+  using QualPointer = std::remove_reference_t<QualReference> *;
+
+  LoopRange(QualBlock &x) { Initialize(x); }
+  LoopRange(QualReference x);
+
+  LoopRange(detail::ConstIfT<IsConst, OpenMPLoopConstruct> &x)
+      : LoopRange(std::get<Block>(x.t)) {}
+  LoopRange(detail::ConstIfT<IsConst, DoConstruct> &x)
+      : LoopRange(std::get<Block>(x.t)) {}
+
+  size_t size() const { return items.size(); }
+
+  struct iterator;
+
+  iterator begin();
+  iterator end();
+
+private:
+  void Initialize(QualBlock &body);
+
+  std::vector<QualPointer> items;
+};
+
+template <typename T> LoopRange(T &x) -> LoopRange<std::is_const_v<T>>;
+
+template <bool IsConst> struct LoopRange<IsConst>::iterator {
+  QualReference operator*() { return **at; }
+
+  bool operator==(const iterator &other) const { return at == other.at; }
+  bool operator!=(const iterator &other) const { return at != other.at; }
+
+  iterator &operator++() {
+    ++at;
+    return *this;
+  }
+  iterator &operator--() {
+    --at;
+    return *this;
+  }
+  iterator &operator++(int);
+  iterator &operator--(int);
+
+private:
+  friend struct LoopRange;
+  typename decltype(LoopRange::items)::iterator at;
+};
+
+template <bool IsConst> inline auto LoopRange<IsConst>::begin() -> iterator {
+  iterator x;
+  x.at = items.begin();
+  return x;
+}
+
+template <bool IsConst> inline auto LoopRange<IsConst>::end() -> iterator {
+  iterator x;
+  x.at = items.end();
+  return x;
+}
+
+using ConstLoopRange = LoopRange<true>;
+
 } // namespace Fortran::parser::omp
 
 #endif // FORTRAN_PARSER_OPENMP_UTILS_H
diff --git a/flang/lib/Parser/openmp-utils.cpp b/flang/lib/Parser/openmp-utils.cpp
index 4c38917e87d29..099c7faf328c3 100644
--- a/flang/lib/Parser/openmp-utils.cpp
+++ b/flang/lib/Parser/openmp-utils.cpp
@@ -205,4 +205,41 @@ OmpAllocateInfo SplitOmpAllocate(const OmpAllocateDirective &x) {
   return info;
 }
 
+template <bool IsConst>
+LoopRange<IsConst>::LoopRange(QualReference x) {
+  if (auto *doLoop{Unwrap<DoConstruct>(x)}) {
+    Initialize(std::get<Block>(doLoop->t));
+  } else if (auto *omp{Unwrap<OpenMPLoopConstruct>(x)}) {
+    Initialize(std::get<Block>(omp->t));
+  }
+}
+
+template <bool IsConst>
+void LoopRange<IsConst>::Initialize(QualBlock &body) {
+  using QualIterator = decltype(std::declval<QualBlock>().begin());
+  auto makeRange{[](auto &container) {
+    return llvm::make_range(container.begin(), container.end());
+  }};
+
+  std::vector<llvm::iterator_range<QualIterator>> nest{makeRange(body)};
+  do {
+    auto at{nest.back().begin()};
+    auto end{nest.back().end()};
+    nest.pop_back();
+    while (at != end) {
+      if (auto *block{Unwrap<BlockConstruct>(*at)}) {
+        nest.push_back(llvm::make_range(std::next(at), end));
+        nest.push_back(makeRange(std::get<Block>(block->t)));
+        break;
+      } else if (Unwrap<DoConstruct>(*at) || Unwrap<OpenMPLoopConstruct>(*at)) {
+        items.push_back(&*at);
+      }
+      ++at;
+    }
+  } while (!nest.empty());
+}
+
+template struct LoopRange<false>;
+template struct LoopRange<true>;
+
 } // namespace Fortran::parser::omp

>From 9a2d3dca08ab237e7e949fd5642c96cf0fba89b8 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Tue, 2 Dec 2025 14:59:34 -0600
Subject: [PATCH 2/6] [flang][OpenMP] Generalize checks of loop construct
 structure

For an OpenMP loop construct, count how many loops will effectively be
contained in its associated block. For constructs that are loop-nest
associated this number should be 1. Report cases where this number is
different.

Take into account that the block associated with a loop construct can
contain compiler directives.
---
 flang/lib/Semantics/check-omp-loop.cpp        | 201 +++++++++++-------
 flang/lib/Semantics/check-omp-structure.h     |   3 +-
 flang/test/Parser/OpenMP/tile-fail.f90        |   8 +-
 flang/test/Semantics/OpenMP/do21.f90          |  10 +-
 .../Semantics/OpenMP/loop-association.f90     |   6 +-
 .../OpenMP/loop-transformation-clauses01.f90  |  16 +-
 .../loop-transformation-construct01.f90       |   4 +-
 .../loop-transformation-construct02.f90       |   8 +-
 .../loop-transformation-construct04.f90       |   4 +-
 9 files changed, 156 insertions(+), 104 deletions(-)

diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp
index fc4b9222d91b3..6414f0028e008 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -37,6 +37,14 @@
 #include <tuple>
 #include <variant>
 
+namespace Fortran::semantics {
+static bool IsLoopTransforming(llvm::omp::Directive dir);
+static bool IsFullUnroll(const parser::OpenMPLoopConstruct &x);
+static std::optional<size_t> CountGeneratedLoops(
+    const parser::ExecutionPartConstruct &epc);
+static std::optional<size_t> CountGeneratedLoops(const parser::Block &block);
+} // namespace Fortran::semantics
+
 namespace {
 using namespace Fortran;
 
@@ -263,22 +271,19 @@ static bool IsLoopTransforming(llvm::omp::Directive dir) {
 }
 
 void OmpStructureChecker::CheckNestedBlock(const parser::OpenMPLoopConstruct &x,
-    const parser::Block &body, size_t &nestedCount) {
+    const parser::Block &body) {
   for (auto &stmt : body) {
     if (auto *dir{parser::Unwrap<parser::CompilerDirective>(stmt)}) {
       context_.Say(dir->source,
           "Compiler directives are not allowed inside OpenMP loop constructs"_warn_en_US);
-    } else if (parser::Unwrap<parser::DoConstruct>(stmt)) {
-      ++nestedCount;
     } else if (auto *omp{parser::Unwrap<parser::OpenMPLoopConstruct>(stmt)}) {
       if (!IsLoopTransforming(omp->BeginDir().DirName().v)) {
         context_.Say(omp->source,
             "Only loop-transforming OpenMP constructs are allowed inside OpenMP loop constructs"_err_en_US);
       }
-      ++nestedCount;
     } else if (auto *block{parser::Unwrap<parser::BlockConstruct>(stmt)}) {
-      CheckNestedBlock(x, std::get<parser::Block>(block->t), nestedCount);
-    } else {
+      CheckNestedBlock(x, std::get<parser::Block>(block->t));
+    } else if (!parser::Unwrap<parser::DoConstruct>(stmt)) {
       parser::CharBlock source{parser::GetSource(stmt).value_or(x.source)};
       context_.Say(source,
           "OpenMP loop construct can only contain DO loops or loop-nest-generating OpenMP constructs"_err_en_US);
@@ -286,16 +291,96 @@ void OmpStructureChecker::CheckNestedBlock(const parser::OpenMPLoopConstruct &x,
   }
 }
 
+static bool IsFullUnroll(const parser::OpenMPLoopConstruct &x) {
+  const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()};
+
+  if (beginSpec.DirName().v == llvm::omp::Directive::OMPD_unroll) {
+    return llvm::none_of(beginSpec.Clauses().v, [](const parser::OmpClause &c) {
+      return c.Id() == llvm::omp::Clause::OMPC_partial;
+    });
+  }
+  return false;
+}
+
+static std::optional<size_t> CountGeneratedLoops(
+    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};
+
+  // TODO: Handle split, apply.
+  if (IsFullUnroll(omp)) {
+    return std::nullopt;
+  }
+  if (dir == llvm::omp::Directive::OMPD_fuse) {
+    auto rangeAt{
+        llvm::find_if(beginSpec.Clauses().v, [](const parser::OmpClause &c) {
+          return c.Id() == llvm::omp::Clause::OMPC_looprange;
+        })};
+    if (rangeAt == beginSpec.Clauses().v.end()) {
+      return std::nullopt;
+    }
+
+    auto *loopRange{parser::Unwrap<parser::OmpLooprangeClause>(*rangeAt)};
+    std::optional<int64_t> count{GetIntValue(std::get<1>(loopRange->t))};
+    if (!count || *count <= 0) {
+      return std::nullopt;
+    }
+    if (auto nestedCount{CountGeneratedLoops(std::get<parser::Block>(omp.t))}) {
+      return 1 + *nestedCount - static_cast<size_t>(*count);
+    } else {
+      return std::nullopt;
+    }
+  }
+
+  // For every other loop construct return 1.
+  return 1;
+}
+
+static std::optional<size_t> CountGeneratedLoops(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};
+  for (auto &epc : parser::omp::LoopRange(block)) {
+    if (auto genCount{CountGeneratedLoops(epc)}) {
+      *numLoops += *genCount;
+    } else {
+      numLoops = std::nullopt;
+      break;
+    }
+  }
+  return numLoops;
+}
+
 void OmpStructureChecker::CheckNestedConstruct(
     const parser::OpenMPLoopConstruct &x) {
-  size_t nestedCount{0};
-
+  const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()};
   auto &body{std::get<parser::Block>(x.t)};
-  if (body.empty()) {
-    context_.Say(x.source,
-        "OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct"_err_en_US);
-  } else {
-    CheckNestedBlock(x, body, nestedCount);
+
+  CheckNestedBlock(x, body);
+
+  // Check if a loop-nest-associated construct has only one top-level loop
+  // in it.
+  if (std::optional<size_t> numLoops{CountGeneratedLoops(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);
+    } else {
+      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,
+            *numLoops);
+      }
+    }
   }
 }
 
@@ -304,16 +389,9 @@ void OmpStructureChecker::CheckFullUnroll(
   // If the nested construct is a full unroll, then this construct is invalid
   // since it won't contain a loop.
   if (const parser::OpenMPLoopConstruct *nested{x.GetNestedConstruct()}) {
-    auto &nestedSpec{nested->BeginDir()};
-    if (nestedSpec.DirName().v == llvm::omp::Directive::OMPD_unroll) {
-      bool isPartial{
-          llvm::any_of(nestedSpec.Clauses().v, [](const parser::OmpClause &c) {
-            return c.Id() == llvm::omp::Clause::OMPC_partial;
-          })};
-      if (!isPartial) {
-        context_.Say(x.source,
-            "OpenMP loop construct cannot apply to a fully unrolled loop"_err_en_US);
-      }
+    if (IsFullUnroll(*nested)) {
+      context_.Say(x.source,
+          "OpenMP loop construct cannot apply to a fully unrolled loop"_err_en_US);
     }
   }
 }
@@ -387,11 +465,6 @@ void OmpStructureChecker::Enter(const parser::OpenMPLoopConstruct &x) {
       beginName.v == llvm::omp::Directive::OMPD_distribute_simd) {
     CheckDistLinear(x);
   }
-  if (beginName.v == llvm::omp::Directive::OMPD_fuse) {
-    CheckLooprangeBounds(x);
-  } else {
-    CheckNestedFuse(x);
-  }
 }
 
 const parser::Name OmpStructureChecker::GetLoopIndex(
@@ -531,57 +604,20 @@ void OmpStructureChecker::CheckDistLinear(
 
 void OmpStructureChecker::CheckLooprangeBounds(
     const parser::OpenMPLoopConstruct &x) {
-  const parser::OmpClauseList &clauseList{x.BeginDir().Clauses()};
-  if (clauseList.v.empty()) {
-    return;
-  }
-  for (auto &clause : clauseList.v) {
-    if (const auto *lrClause{
-            std::get_if<parser::OmpClause::Looprange>(&clause.u)}) {
-      auto first{GetIntValue(std::get<0>((lrClause->v).t))};
-      auto count{GetIntValue(std::get<1>((lrClause->v).t))};
-      if (!first || !count) {
-        return;
-      }
-      auto &loopConsList{std::get<parser::Block>(x.t)};
-      if (*first > 0 && *count > 0 &&
-          loopConsList.size() < (unsigned)(*first + *count - 1)) {
-        context_.Say(clause.source,
-            "The loop range indicated in the %s clause must not be out of the bounds of the Loop Sequence following the construct."_err_en_US,
-            parser::ToUpperCaseLetters(clause.source.ToString()));
-      }
-      return;
-    }
-  }
-}
-
-void OmpStructureChecker::CheckNestedFuse(
-    const parser::OpenMPLoopConstruct &x) {
-  auto &loopConsList{std::get<parser::Block>(x.t)};
-  if (loopConsList.empty()) {
-    return;
-  }
-  const auto *ompConstruct{parser::omp::GetOmpLoop(loopConsList.front())};
-  if (!ompConstruct) {
-    return;
-  }
-  const parser::OmpClauseList &clauseList{ompConstruct->BeginDir().Clauses()};
-  if (clauseList.v.empty()) {
-    return;
-  }
-  for (auto &clause : clauseList.v) {
-    if (const auto *lrClause{
-            std::get_if<parser::OmpClause::Looprange>(&clause.u)}) {
-      auto count{GetIntValue(std::get<1>((lrClause->v).t))};
-      if (!count) {
+  for (const parser::OmpClause &clause : x.BeginDir().Clauses().v) {
+    if (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;
       }
-      auto &nestedLoopConsList{std::get<parser::Block>(ompConstruct->t)};
-      if (nestedLoopConsList.size() > (unsigned)(*count)) {
-        context_.Say(x.BeginDir().DirName().source,
-            "The loop sequence following the %s construct must be fully fused first."_err_en_US,
-            parser::ToUpperCaseLetters(
-                x.BeginDir().DirName().source.ToString()));
+      auto requiredCount{static_cast<size_t>(*first + *count - 1)};
+      if (auto loopCount{CountGeneratedLoops(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,
+              requiredCount, *loopCount);
+        }
       }
       return;
     }
@@ -625,18 +661,21 @@ void OmpStructureChecker::CheckScanModifier(
 }
 
 void OmpStructureChecker::Leave(const parser::OpenMPLoopConstruct &x) {
-  const parser::OmpClauseList &clauseList{x.BeginDir().Clauses()};
+  const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()};
 
   // A few semantic checks for InScan reduction are performed below as SCAN
   // constructs inside LOOP may add the relevant information. Scan reduction is
   // supported only in loop constructs, so same checks are not applicable to
   // other directives.
-  for (const auto &clause : clauseList.v) {
+  for (const auto &clause : beginSpec.Clauses().v) {
     if (auto *reduction{std::get_if<parser::OmpClause::Reduction>(&clause.u)}) {
       CheckScanModifier(*reduction);
     }
   }
-  if (llvm::omp::allSimdSet.test(GetContext().directive)) {
+  if (beginSpec.DirName().v == llvm::omp::Directive::OMPD_fuse) {
+    CheckLooprangeBounds(x);
+  }
+  if (llvm::omp::allSimdSet.test(beginSpec.DirName().v)) {
     ExitDirectiveNest(SIMDNest);
   }
   dirContext_.pop_back();
@@ -782,8 +821,8 @@ void OmpStructureChecker::Enter(const parser::OmpClause::Sizes &c) {
 void OmpStructureChecker::Enter(const parser::OmpClause::Looprange &x) {
   CheckAllowedClause(llvm::omp::Clause::OMPC_looprange);
   auto &[first, count]{x.v.t};
-  RequiresConstantPositiveParameter(llvm::omp::Clause::OMPC_looprange, count);
   RequiresConstantPositiveParameter(llvm::omp::Clause::OMPC_looprange, first);
+  RequiresConstantPositiveParameter(llvm::omp::Clause::OMPC_looprange, count);
 }
 
 void OmpStructureChecker::Enter(const parser::DoConstruct &x) {
diff --git a/flang/lib/Semantics/check-omp-structure.h b/flang/lib/Semantics/check-omp-structure.h
index 5bd5ae050be64..267362b6325f1 100644
--- a/flang/lib/Semantics/check-omp-structure.h
+++ b/flang/lib/Semantics/check-omp-structure.h
@@ -323,11 +323,10 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
 
   void CheckScanModifier(const parser::OmpClause::Reduction &x);
   void CheckLooprangeBounds(const parser::OpenMPLoopConstruct &x);
-  void CheckNestedFuse(const parser::OpenMPLoopConstruct &x);
   void CheckDistLinear(const parser::OpenMPLoopConstruct &x);
   void CheckSIMDNest(const parser::OpenMPConstruct &x);
   void CheckNestedBlock(const parser::OpenMPLoopConstruct &x,
-      const parser::Block &body, size_t &nestedCount);
+      const parser::Block &body);
   void CheckNestedConstruct(const parser::OpenMPLoopConstruct &x);
   void CheckFullUnroll(const parser::OpenMPLoopConstruct &x);
   void CheckTargetNest(const parser::OpenMPConstruct &x);
diff --git a/flang/test/Parser/OpenMP/tile-fail.f90 b/flang/test/Parser/OpenMP/tile-fail.f90
index a69261a927961..d5ff39cd1037c 100644
--- a/flang/test/Parser/OpenMP/tile-fail.f90
+++ b/flang/test/Parser/OpenMP/tile-fail.f90
@@ -1,7 +1,7 @@
 ! RUN: split-file %s %t
-! RUN: not %flang_fc1 -fsyntax-only -fopenmp %t/stray_end1.f90 2>&1 | FileCheck %t/stray_end1.f90
-! RUN: not %flang_fc1 -fsyntax-only -fopenmp %t/stray_end2.f90 2>&1 | FileCheck %t/stray_end2.f90
-! RUN: not %flang_fc1 -fsyntax-only -fopenmp %t/stray_begin.f90 2>&1 | FileCheck %t/stray_begin.f90
+! RUN: not %flang_fc1 -fsyntax-only -fopenmp -fopenmp-version=60 %t/stray_end1.f90 2>&1 | FileCheck %t/stray_end1.f90
+! RUN: not %flang_fc1 -fsyntax-only -fopenmp -fopenmp-version=60 %t/stray_end2.f90 2>&1 | FileCheck %t/stray_end2.f90
+! RUN: not %flang_fc1 -fsyntax-only -fopenmp -fopenmp-version=60 %t/stray_begin.f90 2>&1 | FileCheck %t/stray_begin.f90
 
 
 !--- stray_end1.f90
@@ -25,7 +25,7 @@ subroutine stray_end2
 !--- stray_begin.f90
 
 subroutine stray_begin
-  !CHECK: error: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+  !CHECK: error: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
   !$omp tile sizes(2)
 end subroutine
 
diff --git a/flang/test/Semantics/OpenMP/do21.f90 b/flang/test/Semantics/OpenMP/do21.f90
index e6fe7dd39dd3e..683118a5b2182 100644
--- a/flang/test/Semantics/OpenMP/do21.f90
+++ b/flang/test/Semantics/OpenMP/do21.f90
@@ -2,26 +2,26 @@
 ! Check for existence of loop following a DO directive
 
 subroutine do1
-  !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+  !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
   !$omp do
 end subroutine
 
 subroutine do2
-  !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+  !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
   !$omp parallel do
 end subroutine
 
 subroutine do3
-  !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+  !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
   !$omp simd
 end subroutine
 
 subroutine do4
-  !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+  !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
   !$omp do simd
 end subroutine
 
 subroutine do5
-  !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+  !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
   !$omp loop
 end subroutine
diff --git a/flang/test/Semantics/OpenMP/loop-association.f90 b/flang/test/Semantics/OpenMP/loop-association.f90
index 4e63cafb3fda1..4e6cf4ce13486 100644
--- a/flang/test/Semantics/OpenMP/loop-association.f90
+++ b/flang/test/Semantics/OpenMP/loop-association.f90
@@ -103,7 +103,7 @@
   !$omp parallel do private(c)
   do i = 1, N
      do j = 1, N
-        !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+        !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
         !$omp parallel do shared(b)
         a = 3.14
      enddo
@@ -123,7 +123,7 @@
   !ERROR: Misplaced OpenMP end-directive
   !$omp end parallel do
 
-  !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+  !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
   !$omp parallel do private(c)
 5 FORMAT (1PE12.4, I10)
   do i=1, N
@@ -140,7 +140,7 @@
   !ERROR: Misplaced OpenMP end-directive
   !$omp end parallel do simd
 
-  !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+  !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
   !$omp simd
     a = i + 1
   !ERROR: Misplaced OpenMP end-directive
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90 b/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90
index 9ca0e8cfc9af1..5e3d32d7c6eff 100644
--- a/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90
+++ b/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90
@@ -20,7 +20,7 @@ subroutine loop_transformation_construct1
   end do
   !$omp end fuse
 
-  !ERROR: The loop range indicated in the LOOPRANGE(5,2) clause must not be out of the bounds of the Loop Sequence following the construct.
+  !ERROR: The specified loop range requires 6 loops, but the loop sequence has a length of 2
   !$omp fuse looprange(5,2)
   do x = 1, i
     v(x) = x * 2
@@ -63,4 +63,18 @@ subroutine loop_transformation_construct1
     v(x) = x * 2
   end do
   !$omp end fuse
+
+  ! This is ok aside from the warnings about compiler directives
+  !$omp fuse looprange(1,3)
+    do x = 1, 10; end do        ! 1 loop
+    !WARNING: Compiler directives are not allowed inside OpenMP loop constructs
+    !dir$ novector
+    !$omp fuse looprange(1,2)   ! 2 loops
+      do x = 1, 10; end do
+      !WARNING: Compiler directives are not allowed inside OpenMP loop constructs
+      !dir$ nounroll
+      do x = 1, 10; end do
+      do x = 1, 10; end do
+    !$omp end fuse
+  !$omp end fuse
 end subroutine
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90
index caa8f3f216fec..4eeb7330ea589 100644
--- a/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90
+++ b/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90
@@ -7,7 +7,7 @@ subroutine loop_transformation_construct1
 
   !ERROR: OpenMP loop construct cannot apply to a fully unrolled loop
   !$omp do
-  !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+  !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
   !$omp unroll
 end subroutine
 
@@ -51,7 +51,7 @@ subroutine loop_transformation_construct4
   do x = 1, i
     v(x) = v(x) * 2
   end do
-  !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+  !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
   !ERROR: At least one of SIZES clause must appear on the TILE directive
   !$omp tile
 end subroutine
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
index 1b15c938915cd..25247c3896cae 100644
--- a/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
+++ b/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
@@ -7,7 +7,7 @@ subroutine loop_transformation_construct1
   implicit none
 
   !$omp do
-  !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+  !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
   !$omp fuse 
 end subroutine
 
@@ -15,7 +15,7 @@ subroutine loop_transformation_construct2
   implicit none
 
   !$omp do
-  !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+  !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
   !$omp fuse 
   !$omp end fuse
 end subroutine
@@ -50,7 +50,7 @@ subroutine loop_transformation_construct4
   do x = 1, i
     v(x) = v(x) * 2
   end do
-  !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+  !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
   !$omp fuse
   !$omp end fuse
 end subroutine
@@ -80,7 +80,7 @@ subroutine loop_transformation_construct6
   integer :: x
   integer :: v(i)
 
-  !ERROR: The loop sequence following the DO construct must be fully fused first.
+  !ERROR: This construct applies to a loop nest, but has a loop sequence of length 2
   !$omp do
   !$omp fuse looprange(1,1)
   !$omp unroll partial(2)
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct04.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct04.f90
index 2856247329f3b..158b030906e07 100644
--- a/flang/test/Semantics/OpenMP/loop-transformation-construct04.f90
+++ b/flang/test/Semantics/OpenMP/loop-transformation-construct04.f90
@@ -8,7 +8,7 @@ subroutine loop_transformation_construct3
   integer :: x
   integer :: v(i)
 
-  !ERROR: The loop sequence following the DO construct must be fully fused first.
+  !ERROR: This construct applies to a loop nest, but has a loop sequence of length 2
   !$omp do
   !$omp fuse looprange(1,2)
   do x = 1, i
@@ -30,7 +30,7 @@ subroutine loop_transformation_construct4
   integer :: x
   integer :: v(i)
 
-  !ERROR: The loop sequence following the TILE construct must be fully fused first.
+  !ERROR: This construct applies to a loop nest, but has a loop sequence of length 2
   !$omp tile sizes(2)
   !$omp fuse looprange(1,2)
   do x = 1, i

>From 713e9446c145e323651a8908c617f83b7389a757 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Thu, 4 Dec 2025 14:20:46 -0600
Subject: [PATCH 3/6] format

---
 flang/include/flang/Parser/openmp-utils.h | 5 ++---
 flang/lib/Parser/openmp-utils.cpp         | 6 ++----
 2 files changed, 4 insertions(+), 7 deletions(-)

diff --git a/flang/include/flang/Parser/openmp-utils.h b/flang/include/flang/Parser/openmp-utils.h
index 27e4aa6db0f0b..c47cdb295da4a 100644
--- a/flang/include/flang/Parser/openmp-utils.h
+++ b/flang/include/flang/Parser/openmp-utils.h
@@ -238,14 +238,13 @@ struct OmpAllocateInfo {
 OmpAllocateInfo SplitOmpAllocate(const OmpAllocateDirective &x);
 
 namespace detail {
-template <bool IsConst, typename T>
-struct ConstIf {
+template <bool IsConst, typename T> struct ConstIf {
   using type = std::conditional_t<IsConst, std::add_const_t<T>, T>;
 };
 
 template <bool IsConst, typename T>
 using ConstIfT = typename ConstIf<IsConst, T>::type;
-}
+} // namespace detail
 
 template <bool IsConst> struct LoopRange {
   using QualBlock = detail::ConstIfT<IsConst, Block>;
diff --git a/flang/lib/Parser/openmp-utils.cpp b/flang/lib/Parser/openmp-utils.cpp
index 099c7faf328c3..1593b19d6b372 100644
--- a/flang/lib/Parser/openmp-utils.cpp
+++ b/flang/lib/Parser/openmp-utils.cpp
@@ -205,8 +205,7 @@ OmpAllocateInfo SplitOmpAllocate(const OmpAllocateDirective &x) {
   return info;
 }
 
-template <bool IsConst>
-LoopRange<IsConst>::LoopRange(QualReference x) {
+template <bool IsConst> LoopRange<IsConst>::LoopRange(QualReference x) {
   if (auto *doLoop{Unwrap<DoConstruct>(x)}) {
     Initialize(std::get<Block>(doLoop->t));
   } else if (auto *omp{Unwrap<OpenMPLoopConstruct>(x)}) {
@@ -214,8 +213,7 @@ LoopRange<IsConst>::LoopRange(QualReference x) {
   }
 }
 
-template <bool IsConst>
-void LoopRange<IsConst>::Initialize(QualBlock &body) {
+template <bool IsConst> void LoopRange<IsConst>::Initialize(QualBlock &body) {
   using QualIterator = decltype(std::declval<QualBlock>().begin());
   auto makeRange{[](auto &container) {
     return llvm::make_range(container.begin(), container.end());

>From 06c45b22341bb4792ce89f592c2f299de4030104 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Thu, 4 Dec 2025 14:23:11 -0600
Subject: [PATCH 4/6] format

---
 flang/lib/Semantics/check-omp-loop.cpp    | 4 ++--
 flang/lib/Semantics/check-omp-structure.h | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp
index 6414f0028e008..917b2c5015744 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -270,8 +270,8 @@ static bool IsLoopTransforming(llvm::omp::Directive dir) {
   }
 }
 
-void OmpStructureChecker::CheckNestedBlock(const parser::OpenMPLoopConstruct &x,
-    const parser::Block &body) {
+void OmpStructureChecker::CheckNestedBlock(
+    const parser::OpenMPLoopConstruct &x, const parser::Block &body) {
   for (auto &stmt : body) {
     if (auto *dir{parser::Unwrap<parser::CompilerDirective>(stmt)}) {
       context_.Say(dir->source,
diff --git a/flang/lib/Semantics/check-omp-structure.h b/flang/lib/Semantics/check-omp-structure.h
index 267362b6325f1..12f458b624791 100644
--- a/flang/lib/Semantics/check-omp-structure.h
+++ b/flang/lib/Semantics/check-omp-structure.h
@@ -325,8 +325,8 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
   void CheckLooprangeBounds(const parser::OpenMPLoopConstruct &x);
   void CheckDistLinear(const parser::OpenMPLoopConstruct &x);
   void CheckSIMDNest(const parser::OpenMPConstruct &x);
-  void CheckNestedBlock(const parser::OpenMPLoopConstruct &x,
-      const parser::Block &body);
+  void CheckNestedBlock(
+      const parser::OpenMPLoopConstruct &x, const parser::Block &body);
   void CheckNestedConstruct(const parser::OpenMPLoopConstruct &x);
   void CheckFullUnroll(const parser::OpenMPLoopConstruct &x);
   void CheckTargetNest(const parser::OpenMPConstruct &x);

>From c7022e36fe3506aaa18a4a9f8595080fd12f3c88 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Mon, 8 Dec 2025 10:52:02 -0600
Subject: [PATCH 5/6] Add empty

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

diff --git a/flang/include/flang/Parser/openmp-utils.h b/flang/include/flang/Parser/openmp-utils.h
index c47cdb295da4a..6dac64ecbaf08 100644
--- a/flang/include/flang/Parser/openmp-utils.h
+++ b/flang/include/flang/Parser/openmp-utils.h
@@ -260,6 +260,7 @@ template <bool IsConst> struct LoopRange {
       : LoopRange(std::get<Block>(x.t)) {}
 
   size_t size() const { return items.size(); }
+  bool empty() const { return items.size() == 0; }
 
   struct iterator;
 

>From 11e3f22e0b997a5d9d403b0fd376a0d82d2d5c20 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Mon, 8 Dec 2025 11:30:45 -0600
Subject: [PATCH 6/6] Fix merge error

---
 flang/lib/Semantics/check-omp-loop.cpp | 1 -
 1 file changed, 1 deletion(-)

diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp
index fc269e54abebc..77cc6c9e38c9c 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -370,7 +370,6 @@ void OmpStructureChecker::CheckNestedConstruct(
   //     do 100 j = ...
   //   100 continue
   //   !$omp end do    ! error
-  const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()};
   auto &flags{std::get<parser::OmpDirectiveSpecification::Flags>(beginSpec.t)};
   if (flags.test(parser::OmpDirectiveSpecification::Flag::CrossesLabelDo)) {
     if (auto &endSpec{x.EndDir()}) {



More information about the flang-commits mailing list