[llvm-branch-commits] [flang] [flang][OpenMP] Implement checks for rectangular loops (PR #190648)

Krzysztof Parzyszek via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Mon Apr 6 11:59:32 PDT 2026


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

>From 33e555beae86d04cb4ee268a0a419bb3e52242f3 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Tue, 24 Mar 2026 12:11:40 -0500
Subject: [PATCH 1/2] [flang][OpenMP] Implement checks for rectangular loops

Detect non-rectangular loops, emit diagnostic messages when the construct
requires that the affected loops are rectangular. Delete similar checks
from resolve-directive.cpp.

Issue: https://github.com/llvm/llvm-project/issues/185287
---
 flang/include/flang/Semantics/openmp-utils.h |  15 ++
 flang/lib/Semantics/check-omp-loop.cpp       |  31 +++
 flang/lib/Semantics/check-omp-structure.h    |   2 +
 flang/lib/Semantics/openmp-utils.cpp         | 191 +++++++++++++++++++
 flang/lib/Semantics/resolve-directives.cpp   |  84 --------
 flang/test/Semantics/OpenMP/do22.f90         |  18 +-
 flang/test/Semantics/OpenMP/tile06.f90       |  12 +-
 7 files changed, 260 insertions(+), 93 deletions(-)

diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index f6aebaf8ffdfc..487a0373b10f0 100644
--- a/flang/include/flang/Semantics/openmp-utils.h
+++ b/flang/include/flang/Semantics/openmp-utils.h
@@ -192,6 +192,8 @@ WithReason<int64_t> GetArgumentValueWithReason(
 WithReason<int64_t> GetNumArgumentsWithReason(
     const parser::OmpDirectiveSpecification &spec, llvm::omp::Clause clauseId,
     unsigned version);
+WithReason<int64_t> GetHeightWithReason(
+    const parser::OmpDirectiveSpecification &spec, unsigned version);
 
 // Return the depth of the affected nests:
 //   {affected-depth, reason, must-be-perfect-nest}.
@@ -202,6 +204,9 @@ std::pair<WithReason<int64_t>, bool> GetAffectedNestDepthWithReason(
 // If the range is "the whole sequence", the return value will be {1, -1, ...}.
 WithReason<std::pair<int64_t, int64_t>> GetAffectedLoopRangeWithReason(
     const parser::OmpDirectiveSpecification &spec, unsigned version);
+/// Return the depth in which all loops must be rectangular.
+WithReason<int64_t> GetRectangularNestDepthWithReason(
+    const parser::OmpDirectiveSpecification &spec, unsigned version);
 
 // Count the required loop count from range. If count == -1, return -1,
 // indicating all loops in the sequence.
@@ -233,8 +238,10 @@ struct LoopSequence {
 
   bool isNest() const { return length_.value == 1; }
   const WithReason<int64_t> &length() const { return length_; }
+  const WithReason<int64_t> &height() const { return height_; }
   const Depth &depth() const { return depth_; }
   const std::vector<LoopSequence> &children() const { return children_; }
+  const parser::ExecutionPartConstruct *owner() const { return entry_->owner; }
 
   WithReason<bool> isWellFormedSequence() const;
   WithReason<bool> isWellFormedNest() const;
@@ -270,6 +277,7 @@ struct LoopSequence {
   WithReason<int64_t> getNestedLength() const;
   Depth calculateDepths() const;
   Depth getNestedDepths() const;
+  WithReason<int64_t> calculateHeight() const;
 
   /// The construct that is not a loop or a loop-transforming construct,
   /// that is also not a valid intervening code. Unset if no such code is
@@ -287,6 +295,13 @@ struct LoopSequence {
   WithReason<int64_t> length_;
   /// Precalculated depths. Only meaningful if the sequence is a nest.
   Depth depth_;
+  /// Precalculated height of the sequence. The height is the difference
+  /// in the nesting level between "this" and any of the children (should
+  /// be the same for each child). Intuitively it is the number of nested
+  /// loops that are added by this construct. If this->depth_ included
+  /// child->depth_ for some child, then
+  ///   height_ = this->depth_ - child->depth_
+  WithReason<int64_t> height_;
 
   // The core structure of the class:
   unsigned version_; // Needed for GetXyzWithReason
diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp
index 48d0684320e1c..951751228f34b 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -232,6 +232,35 @@ void OmpStructureChecker::CheckSIMDNest(const parser::OpenMPConstruct &c) {
   }
 }
 
+void OmpStructureChecker::CheckRectangularNest(
+    const parser::OmpDirectiveSpecification &spec, const LoopSequence &nest) {
+  unsigned version{context_.langOptions().OpenMPVersion};
+  auto depth{GetRectangularNestDepthWithReason(spec, version)};
+  if (!depth || *depth.value == 0) {
+    return;
+  }
+
+  int64_t height{0};
+  std::vector<const LoopSequence *> outer;
+  for (const LoopSequence *n{&nest}; n;) {
+    if (n->owner()) {
+      WithReason<bool> rect{n->isRectangular(outer)};
+      if (!rect.value.value_or(true)) {
+        auto &msg{context_.Say(spec.DirName().source,
+            "This construct requires a rectangular loop nest, but the associated nest is not"_err_en_US)};
+        depth.reason.AttachTo(msg);
+        rect.reason.AttachTo(msg);
+      }
+      outer.push_back(n);
+    }
+    height += n->height().value.value_or(1);
+    if (height >= *depth.value) {
+      break;
+    }
+    n = n->children().empty() ? nullptr : &n->children().front();
+  }
+}
+
 void OmpStructureChecker::CheckNestedConstruct(
     const parser::OpenMPLoopConstruct &x) {
   const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()};
@@ -311,6 +340,8 @@ void OmpStructureChecker::CheckNestedConstruct(
             perfectTxt, *needDepth.value, perfectTxt, *haveDepth.value)};
         haveDepth.reason.AttachTo(msg);
         needDepth.reason.AttachTo(msg);
+      } else {
+        CheckRectangularNest(beginSpec, sequence);
       }
     }
 
diff --git a/flang/lib/Semantics/check-omp-structure.h b/flang/lib/Semantics/check-omp-structure.h
index a27b16561d3f7..7b4859e312c3f 100644
--- a/flang/lib/Semantics/check-omp-structure.h
+++ b/flang/lib/Semantics/check-omp-structure.h
@@ -332,6 +332,8 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
   void CheckScanModifier(const parser::OmpClause::Reduction &x);
   void CheckDistLinear(const parser::OpenMPLoopConstruct &x);
   void CheckSIMDNest(const parser::OpenMPConstruct &x);
+  void CheckRectangularNest(const parser::OmpDirectiveSpecification &spec,
+      const omp::LoopSequence &nest);
   void CheckNestedConstruct(const parser::OpenMPLoopConstruct &x);
   void CheckTargetNest(const parser::OpenMPConstruct &x);
   void CheckTargetUpdate();
diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index bdde253580c26..18a10be3276a5 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -665,6 +665,24 @@ parser::Message &Reason::AttachTo(parser::Message &msg) {
   return msg;
 }
 
+/// From `vars` select the subsequence of symbols that are used in `expr`
+/// either directly, or via some kind of association.
+static SymbolVector SelectUsedSymbols(
+    const SymbolVector &vars, const SomeExpr &expr) {
+  std::set<const Symbol *> uses;
+  for (SymbolRef s : evaluate::GetSymbolVector(expr)) {
+    uses.insert(&s->GetUltimate());
+  }
+
+  SymbolVector deps;
+  for (SymbolRef s : vars) {
+    if (uses.count(&s->GetUltimate())) {
+      deps.push_back(s);
+    }
+  }
+  return deps;
+}
+
 WithReason<int64_t> GetArgumentValueWithReason(
     const parser::OmpDirectiveSpecification &spec, llvm::omp::Clause clauseId,
     unsigned version) {
@@ -719,6 +737,53 @@ WithReason<int64_t> GetNumArgumentsWithReason(
   return {};
 }
 
+WithReason<int64_t> GetHeightWithReason(
+    const parser::OmpDirectiveSpecification &spec, unsigned version) {
+  bool isFullUnroll{IsFullUnroll(spec)};
+
+  if (!isFullUnroll && !IsTransformableLoop(spec)) {
+    Reason reason;
+    reason.Say(spec.DirName().source,
+        "This construct is not a DO-loop or a loop-transformation construct"_because_en_US);
+    return {0, reason};
+  }
+  switch (spec.DirName().v) {
+  case llvm::omp::Directive::OMPD_flatten:
+    if (auto &&value{GetArgumentValueWithReason(
+            spec, llvm::omp::Clause::OMPC_depth, version)}) {
+      // FLATTEN DEPTH(n) replaces n loops with 1.
+      return {int64_t(1) - *value.value, std::move(value.reason)};
+    } else {
+      Reason reason;
+      reason.Say(spec.DirName().source, MsgClauseAbsentAssume,
+          GetUpperName(llvm::omp::Clause::OMPC_depth, version),
+          "a depth of 2");
+      return {-1, std::move(reason)};
+    }
+  case llvm::omp::Directive::OMPD_fuse:
+  case llvm::omp::Directive::OMPD_split:
+    return {0, Reason()};
+  case llvm::omp::Directive::OMPD_interchange:
+  case llvm::omp::Directive::OMPD_nothing:
+  case llvm::omp::Directive::OMPD_reverse:
+    return {0, Reason()};
+  case llvm::omp::Directive::OMPD_stripe:
+  case llvm::omp::Directive::OMPD_tile:
+    return GetNumArgumentsWithReason(
+        spec, llvm::omp::Clause::OMPC_sizes, version);
+  case llvm::omp::Directive::OMPD_unroll:
+    if (isFullUnroll) {
+      Reason reason;
+      reason.Say(spec.DirName().source, MsgConstructDoesNotResult,
+          "Fully unrolled loop", "a loop nest");
+      return {-1, std::move(reason)};
+    }
+    return {0, Reason()};
+  default:
+    llvm_unreachable("Expecting loop-transforming construct");
+  }
+}
+
 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
@@ -1000,6 +1065,56 @@ WithReason<std::pair<int64_t, int64_t>> GetAffectedLoopRangeWithReason(
   return {std::make_pair(1, 1), Reason()};
 }
 
+WithReason<int64_t> GetRectangularNestDepthWithReason(
+    const parser::OmpDirectiveSpecification &spec, unsigned version) {
+  auto [depth, _]{GetAffectedNestDepthWithReason(spec, version)};
+  if (!depth) {
+    return {};
+  }
+
+  // Remove the reasons for the affected depth. Reasons for needing
+  // rectangular loops will be added instead.
+  depth.reason.msgs.clear();
+
+  static const std::array directives{
+      llvm::omp::Directive::OMPD_interchange,
+      llvm::omp::Directive::OMPD_stripe,
+      llvm::omp::Directive::OMPD_tile,
+  };
+
+  llvm::omp::Directive dirId{spec.DirId()};
+  if (llvm::is_contained(directives, dirId)) {
+    depth.reason.Say(spec.DirName().source,
+        "None of the loops affected by %s can be non-rectangular"_because_en_US,
+        GetUpperName(dirId, version));
+    return std::move(depth);
+  }
+
+  static const std::array clauses{
+      llvm::omp::Clause::OMPC_dist_schedule,
+      llvm::omp::Clause::OMPC_grainsize,
+      llvm::omp::Clause::OMPC_induction,
+      llvm::omp::Clause::OMPC_linear,
+      llvm::omp::Clause::OMPC_schedule,
+  };
+
+  auto clauseAt{
+      llvm::find_if(spec.Clauses().v, [&](const parser::OmpClause &c) {
+        llvm::omp::Clause clauseId{c.Id()};
+        return llvm::is_contained(clauses, clauseId) &&
+            llvm::omp::isAllowedClauseForDirective(dirId, clauseId, version);
+      })};
+  if (clauseAt != spec.Clauses().v.end()) {
+    depth.reason.Say(clauseAt->source,
+        "When %s clause is present, none of the loops affected by %s can be non-rectangular"_because_en_US,
+        GetUpperName(clauseAt->Id(), version), GetUpperName(dirId, version));
+    return std::move(depth);
+  }
+
+  // No restrictions.
+  return {0, Reason()};
+}
+
 std::optional<int64_t> GetRequiredCount(
     std::optional<int64_t> first, std::optional<int64_t> count) {
   if (first && count && *first > 0) {
@@ -1154,6 +1269,7 @@ void LoopSequence::precalculate() {
   // Calculate length before depths.
   length_ = calculateLength();
   depth_ = calculateDepths();
+  height_ = calculateHeight();
 }
 
 WithReason<int64_t> LoopSequence::calculateLength() const {
@@ -1405,6 +1521,23 @@ LoopSequence::Depth LoopSequence::getNestedDepths() const {
   return children_.front().depth_;
 }
 
+WithReason<int64_t> LoopSequence::calculateHeight() const {
+  if (!entry_->owner) {
+    return {0, Reason()};
+  }
+  if (parser::Unwrap<parser::DoConstruct>(*entry_->owner)) {
+    return {1, Reason()};
+  }
+  if (auto *omp{parser::Unwrap<parser::OpenMPLoopConstruct>(*entry_->owner)}) {
+    const parser::OmpDirectiveSpecification &beginSpec{omp->BeginDir()};
+    if (IsLoopTransforming(beginSpec.DirId())) {
+      return GetHeightWithReason(beginSpec, version_);
+    }
+    return {0, Reason()};
+  }
+  return {};
+}
+
 static bool IsDoConcurrent(const parser::ExecutionPartConstruct &x) {
   if (auto *loop{parser::Unwrap<parser::DoConstruct>(x)}) {
     return loop->IsDoConcurrent();
@@ -1465,4 +1598,62 @@ WithReason<bool> LoopSequence::isWellFormedNest() const {
   }
   return {true, Reason()};
 }
+
+static std::string JoinSymbolNames(const SymbolVector &syms) {
+  std::vector<std::string> names;
+  for (SymbolRef s : syms) {
+    names.push_back("'" + s->name().ToString() + "'");
+  }
+  return llvm::join(names, ", ");
+}
+
+static void CheckSymbolExprOverlap(WithReason<bool> &result,
+    const SymbolVector &syms, const SomeExpr &expr, std::string exprName,
+    parser::CharBlock exprSource) {
+  if (auto used{SelectUsedSymbols(syms, expr)}; !used.empty()) {
+    result.value = false;
+    result.reason.Say(exprSource,
+        "The %s of the affected loop uses iteration variables of enclosing loops: %s"_because_en_US,
+        exprName, JoinSymbolNames(used));
+  }
+}
+
+WithReason<bool> LoopSequence::isRectangular(
+    const std::vector<const LoopSequence *> &outer) const {
+  assert(entry_->owner && "Must have owner construct");
+  auto *loop{parser::Unwrap<parser::DoConstruct>(*entry_->owner)};
+  if (!loop) {
+    // Can "rectangular" property be computed for a loop-nest-generating
+    // construct? What if the loops in the nest are not rectangular with
+    // respect to each other?
+    return {};
+  }
+
+  SymbolVector outerIVs;
+  for (auto *sequence : llvm::reverse(outer)) {
+    for (auto &control : sequence->getLoopControls()) {
+      if (control.iv) {
+        outerIVs.emplace_back(*control.iv);
+      }
+    }
+  }
+
+  WithReason<bool> result(true);
+
+  for (auto &control : getLoopControls()) {
+    if (!control.iv || !control.lbound.value || !control.ubound.value) {
+      continue;
+    }
+    CheckSymbolExprOverlap(result, outerIVs, *control.lbound.value,
+        "lower bound", control.lbound.source);
+    CheckSymbolExprOverlap(result, outerIVs, *control.ubound.value,
+        "upper bound", control.ubound.source);
+    if (control.step.value) {
+      CheckSymbolExprOverlap(result, outerIVs, *control.step.value,
+          "iteration step", control.step.source);
+    }
+  }
+
+  return result;
+}
 } // namespace Fortran::semantics::omp
diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp
index fb241d2606b47..4553c90d88f7f 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -1097,13 +1097,6 @@ class OmpAttributeVisitor : DirectiveAttributeVisitor<llvm::omp::Directive> {
     privateDataSharingAttributeObjects_.clear();
   }
 
-  /// Check that loops in the loop nest are perfectly nested, as well that lower
-  /// bound, upper bound, and step expressions do not use the iv
-  /// of a surrounding loop of the associated loops nest.
-  /// We do not support non-perfectly nested loops not non-rectangular loops yet
-  /// (both introduced in OpenMP 5.0)
-  void CheckPerfectNestAndRectangularLoop(const parser::OpenMPLoopConstruct &x);
-
   // Predetermined DSA rules
   void PrivatizeAssociatedLoopIndexAndCheckLoopLevel(
       const parser::OpenMPLoopConstruct &);
@@ -2062,9 +2055,6 @@ bool OmpAttributeVisitor::Pre(const parser::OpenMPLoopConstruct &x) {
     }
   }
 
-  // Must be done before iv privatization
-  CheckPerfectNestAndRectangularLoop(x);
-
   PrivatizeAssociatedLoopIndexAndCheckLoopLevel(x);
   ordCollapseLevel = GetNumAffectedLoopsFromLoopConstruct(x) + 1;
   return true;
@@ -2275,80 +2265,6 @@ void OmpAttributeVisitor::CollectNumAffectedLoopsFromClauses(
   }
 }
 
-void OmpAttributeVisitor::CheckPerfectNestAndRectangularLoop(
-    const parser::OpenMPLoopConstruct &x) {
-  auto &dirContext{GetContext()};
-  std::int64_t dirDepth{dirContext.associatedLoopLevel};
-  if (dirDepth <= 0)
-    return;
-
-  auto checkExprHasSymbols = [&](llvm::SmallVector<Symbol *> &ivs,
-                                 const parser::ScalarExpr *bound) {
-    if (ivs.empty())
-      return;
-    auto boundExpr{semantics::AnalyzeExpr(context_, *bound)};
-    if (!boundExpr)
-      return;
-    semantics::UnorderedSymbolSet boundSyms{
-        evaluate::CollectSymbols(*boundExpr)};
-    if (boundSyms.empty())
-      return;
-    for (Symbol *iv : ivs) {
-      if (boundSyms.count(*iv) != 0) {
-        // TODO: Point to occurence of iv in boundExpr, directiveSource as a
-        //       note
-        context_.Say(dirContext.directiveSource,
-            "Trip count must be computable and invariant"_err_en_US);
-      }
-    }
-  };
-
-  // Find the associated region by skipping nested loop-associated constructs
-  // such as loop transformations
-  for (auto &construct : std::get<parser::Block>(x.t)) {
-    if (const auto *innermostConstruct{parser::omp::GetOmpLoop(construct)}) {
-      CheckPerfectNestAndRectangularLoop(*innermostConstruct);
-    } else if (const auto *doConstruct{
-                   parser::omp::GetDoConstruct(construct)}) {
-
-      llvm::SmallVector<Symbol *> ivs;
-      int curLevel{0};
-      const auto *loop{doConstruct};
-      while (true) {
-        auto [iv, lb, ub, step] = GetLoopBounds(*loop);
-
-        if (lb)
-          checkExprHasSymbols(ivs, lb);
-        if (ub)
-          checkExprHasSymbols(ivs, ub);
-        if (step)
-          checkExprHasSymbols(ivs, step);
-        if (iv) {
-          if (auto *symbol{currScope().FindSymbol(iv->source)})
-            ivs.push_back(symbol);
-        }
-
-        // Stop after processing all affected loops
-        if (curLevel + 1 >= dirDepth)
-          break;
-
-        // Recurse into nested loop
-        const auto &block{std::get<parser::Block>(loop->t)};
-        if (block.empty()) {
-          break;
-        }
-
-        loop = GetDoConstructIf(block.front());
-        if (!loop) {
-          break;
-        }
-
-        ++curLevel;
-      }
-    }
-  }
-}
-
 // 2.15.1.1 Data-sharing Attribute Rules - Predetermined
 //   - The loop iteration variable(s) in the associated do-loop(s) of a do,
 //     parallel do, taskloop, or distribute construct is (are) private.
diff --git a/flang/test/Semantics/OpenMP/do22.f90 b/flang/test/Semantics/OpenMP/do22.f90
index a0502aee748ae..2ced881a2af8b 100644
--- a/flang/test/Semantics/OpenMP/do22.f90
+++ b/flang/test/Semantics/OpenMP/do22.f90
@@ -38,9 +38,11 @@ subroutine do_imperfectly_nested_behind
 subroutine do_nonrectangular_lb
   integer i, j
 
-  !ERROR: Trip count must be computable and invariant
-  !$omp do collapse(2)
+  !ERROR: This construct requires a rectangular loop nest, but the associated nest is not
+  !BECAUSE: When SCHEDULE clause is present, none of the loops affected by DO can be non-rectangular
+  !$omp do collapse(2) schedule(auto)
   do i = 1, 10
+    !BECAUSE: The lower bound of the affected loop uses iteration variables of enclosing loops: 'i'
     do j = i, 10
       print *, i, j
     end do
@@ -52,9 +54,11 @@ subroutine do_nonrectangular_lb
 subroutine do_nonrectangular_ub
   integer i, j
 
-  !ERROR: Trip count must be computable and invariant
-  !$omp do collapse(2)
+  !ERROR: This construct requires a rectangular loop nest, but the associated nest is not
+  !BECAUSE: When SCHEDULE clause is present, none of the loops affected by DO can be non-rectangular
+  !$omp do collapse(2) schedule(auto)
   do i = 1, 10
+    !BECAUSE: The upper bound of the affected loop uses iteration variables of enclosing loops: 'i'
     do j = 0, i
       print *, i, j
     end do
@@ -66,9 +70,11 @@ subroutine do_nonrectangular_ub
 subroutine do_nonrectangular_step
   integer i, j
 
-  !ERROR: Trip count must be computable and invariant
-  !$omp do collapse(2)
+  !ERROR: This construct requires a rectangular loop nest, but the associated nest is not
+  !BECAUSE: When SCHEDULE clause is present, none of the loops affected by DO can be non-rectangular
+  !$omp do collapse(2) schedule(auto)
   do i = 1, 10
+    !BECAUSE: The iteration step of the affected loop uses iteration variables of enclosing loops: 'i'
     do j = 1, 10, i
       print *, i, j
     end do
diff --git a/flang/test/Semantics/OpenMP/tile06.f90 b/flang/test/Semantics/OpenMP/tile06.f90
index 52518d43f0554..dfe0594922fc6 100644
--- a/flang/test/Semantics/OpenMP/tile06.f90
+++ b/flang/test/Semantics/OpenMP/tile06.f90
@@ -6,9 +6,11 @@ subroutine nonrectangular_loop_lb
   implicit none
   integer i, j
 
-  !ERROR: Trip count must be computable and invariant
+  !ERROR: This construct requires a rectangular loop nest, but the associated nest is not
+  !BECAUSE: None of the loops affected by TILE can be non-rectangular
   !$omp tile sizes(2,2)
   do i = 1, 5
+    !BECAUSE: The upper bound of the affected loop uses iteration variables of enclosing loops: 'i'
     do j = 1, i
       print *, i, j
     end do
@@ -20,9 +22,11 @@ subroutine nonrectangular_loop_ub
   implicit none
   integer i, j
 
-  !ERROR: Trip count must be computable and invariant
+  !ERROR: This construct requires a rectangular loop nest, but the associated nest is not
+  !BECAUSE: None of the loops affected by TILE can be non-rectangular
   !$omp tile sizes(2,2)
   do i = 1, 5
+    !BECAUSE: The upper bound of the affected loop uses iteration variables of enclosing loops: 'i'
     do j = 1, i
       print *, i, j
     end do
@@ -34,9 +38,11 @@ subroutine nonrectangular_loop_step
   implicit none
   integer i, j
 
-  !ERROR: Trip count must be computable and invariant
+  !ERROR: This construct requires a rectangular loop nest, but the associated nest is not
+  !BECAUSE: None of the loops affected by TILE can be non-rectangular
   !$omp tile sizes(2,2)
   do i = 1, 5
+    !BECAUSE: The iteration step of the affected loop uses iteration variables of enclosing loops: 'i'
     do j = 1, 42, i
       print *, i, j
     end do

>From 2d3fc475416cf44298f29ce5fada8203d952a442 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Mon, 6 Apr 2026 13:59:18 -0500
Subject: [PATCH 2/2] format

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

diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index 18a10be3276a5..a8c2c11823ba7 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -756,8 +756,7 @@ WithReason<int64_t> GetHeightWithReason(
     } else {
       Reason reason;
       reason.Say(spec.DirName().source, MsgClauseAbsentAssume,
-          GetUpperName(llvm::omp::Clause::OMPC_depth, version),
-          "a depth of 2");
+          GetUpperName(llvm::omp::Clause::OMPC_depth, version), "a depth of 2");
       return {-1, std::move(reason)};
     }
   case llvm::omp::Directive::OMPD_fuse:



More information about the llvm-branch-commits mailing list