[llvm-branch-commits] [flang] [flang][OpenMP] Implement LoopSequence class, calculate sequence length (PR #185296)

via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Sun Mar 8 08:42:16 PDT 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-flang-openmp

Author: Krzysztof Parzyszek (kparzysz)

<details>
<summary>Changes</summary>

This is a tree-like structure that can represent nesting of loop sequences, or, if the sequences are of length 1, a loop nest.

---
Full diff: https://github.com/llvm/llvm-project/pull/185296.diff


4 Files Affected:

- (modified) flang/include/flang/Semantics/openmp-utils.h (+49-8) 
- (modified) flang/lib/Semantics/check-omp-loop.cpp (+1-37) 
- (modified) flang/lib/Semantics/openmp-utils.cpp (+83) 
- (modified) flang/test/Semantics/OpenMP/loop-transformation-construct01.f90 (+1) 


``````````diff
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

``````````

</details>


https://github.com/llvm/llvm-project/pull/185296


More information about the llvm-branch-commits mailing list