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

via flang-commits flang-commits at lists.llvm.org
Mon Mar 16 11:51:55 PDT 2026


Author: Krzysztof Parzyszek
Date: 2026-03-16T13:51:50-05:00
New Revision: fc5a2b92f8c595747eff8a378478c7a9f30e22f4

URL: https://github.com/llvm/llvm-project/commit/fc5a2b92f8c595747eff8a378478c7a9f30e22f4
DIFF: https://github.com/llvm/llvm-project/commit/fc5a2b92f8c595747eff8a378478c7a9f30e22f4.diff

LOG: [flang][OpenMP] Identify affected loops, provide reason (#185299)

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.

Issue: https://github.com/llvm/llvm-project/issues/185287

Added: 
    

Modified: 
    flang/include/flang/Semantics/openmp-utils.h
    flang/lib/Semantics/check-omp-loop.cpp
    flang/lib/Semantics/openmp-utils.cpp

Removed: 
    


################################################################################
diff  --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index 8351f357ff2c6..23bfd6a8de088 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/message.h"
 #include "flang/Parser/openmp-utils.h"
 #include "flang/Parser/parse-tree.h"
 #include "flang/Parser/tools.h"
@@ -25,8 +26,10 @@
 
 #include <optional>
 #include <string>
+#include <tuple>
 #include <type_traits>
 #include <utility>
+#include <vector>
 
 namespace Fortran::semantics {
 class Scope;
@@ -109,27 +112,65 @@ bool IsPointerAssignment(const evaluate::Assignment &x);
 
 MaybeExpr MakeEvaluateExpr(const parser::OmpStylizedInstance &inp);
 
+/// A representation of a "because" message.
+struct Reason {
+  parser::Messages msgs;
+
+  template <typename... Ts> Reason &Say(Ts &&...args) {
+    msgs.Say(std::forward<Ts>(args)...);
+    return *this;
+  };
+  operator bool() const { return !msgs.empty(); }
+};
+
+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);
 
+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);
     precalculate();
   }
 
   struct Depth {
-    /// If this sequence is a nest, the depth of the Canonical Loop Nest rooted
-    /// at this sequence. Otherwise unspecified.
+    // 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.
+    // 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;
   };
 
@@ -141,7 +182,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) {
@@ -179,6 +221,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 8cb07ed352a01..a882dfd00296b 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -38,10 +38,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;
 
@@ -245,19 +241,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 = ...
@@ -299,9 +286,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);
@@ -516,22 +505,22 @@ 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 %" PRId64
-            " loops, but the loop sequence has a length of %" PRId64
-            ""_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 %" PRId64
+              " loops, but the loop sequence has a length of %" PRId64
+              ""_err_en_US,
+              *requiredCount, *loopCount);
+        }
       }
     }
   }

diff  --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index 204aff195b5e6..4a600ff4bdf84 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -34,6 +34,7 @@
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/STLExtras.h"
 
+#include <cinttypes>
 #include <optional>
 #include <string>
 #include <tuple>
@@ -531,6 +532,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)}) {
+        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)};
+      }
+    }
+  }
+  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)}) {
+      std::string name{GetUpperName(clauseId, version)};
+      auto num{static_cast<int64_t>(args->size())};
+      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()};
+}
+
 bool IsLoopTransforming(llvm::omp::Directive dir) {
   switch (dir) {
   // TODO case llvm::omp::Directive::OMPD_flatten:
@@ -723,9 +760,133 @@ bool IsTransformableLoop(const parser::ExecutionPartConstruct &epc) {
   return false;
 }
 
-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 = std::move(ro);
+      }
+    }
+    return {count, true, std::move(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, 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, std::move(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, std::move(reason)};
+      }
+      std::string name{
+          parser::omp::GetUpperName(llvm::omp::Clause::OMPC_depth, version)};
+      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:
+      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, std::move(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{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))};
+      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;
+      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;
+    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) ==
+          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");
 
@@ -733,8 +894,10 @@ LoopSequence::LoopSequence(
   precalculate();
 }
 
-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);
   precalculate();
 }
@@ -764,7 +927,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;
@@ -903,10 +1067,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:


        


More information about the flang-commits mailing list