[flang-commits] [flang] [flang][OpenMP] Provide reasons for calculated sequence length (PR #187866)

Krzysztof Parzyszek via flang-commits flang-commits at lists.llvm.org
Wed Mar 25 06:21:20 PDT 2026


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

>From ecee1cba7222a79a51a88c52d79240f4d65f4228 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Tue, 17 Mar 2026 13:08:46 -0500
Subject: [PATCH 1/3] [flang][OpenMP] Provide reasons for calculated depths

If the depth (either semantic or perfect) was limited by some factor,
include the reason for what caused the reduction.

Issue: https://github.com/llvm/llvm-project/issues/185287
---
 flang/include/flang/Semantics/openmp-utils.h  |  4 +-
 flang/lib/Semantics/check-omp-loop.cpp        | 12 ++-
 flang/lib/Semantics/openmp-utils.cpp          | 99 +++++++++++++------
 flang/test/Semantics/OpenMP/do-collapse.f90   |  1 +
 .../OpenMP/do-concurrent-collapse.f90         |  2 +
 flang/test/Semantics/OpenMP/do08.f90          | 24 +++--
 flang/test/Semantics/OpenMP/do13.f90          |  9 +-
 flang/test/Semantics/OpenMP/do15.f90          |  3 +
 flang/test/Semantics/OpenMP/do16.f90          |  2 +
 flang/test/Semantics/OpenMP/do22.f90          |  2 +
 flang/test/Semantics/OpenMP/tile07.f90        |  2 +
 flang/test/Semantics/OpenMP/tile09.f90        | 63 ++++++++++++
 12 files changed, 174 insertions(+), 49 deletions(-)
 create mode 100644 flang/test/Semantics/OpenMP/tile09.f90

diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index 555ab0cd2842a..de5f9cc9072a3 100644
--- a/flang/include/flang/Semantics/openmp-utils.h
+++ b/flang/include/flang/Semantics/openmp-utils.h
@@ -191,10 +191,10 @@ struct LoopSequence {
   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;
+    WithReason<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;
+    WithReason<int64_t> perfect;
   };
 
   bool isNest() const { return length_ && *length_ == 1; }
diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp
index 70d7040472a89..36b7b22de67ea 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -327,23 +327,25 @@ void OmpStructureChecker::CheckNestedConstruct(
   auto &[haveSema, havePerf]{sequence.depth()};
 
   if (dir != llvm::omp::Directive::OMPD_fuse) {
-    auto &haveDepth = needPerfect ? havePerf : haveSema;
+    auto haveDepth = needPerfect ? havePerf : haveSema;
     // If the present depth is 0, it's likely that the construct doesn't
     // have any loops in it, which would be diagnosed above.
-    if (needDepth && haveDepth > 0) {
-      if (*needDepth.value > *haveDepth) {
+    if (needDepth && haveDepth.value > 0) {
+      if (*needDepth.value > *haveDepth.value) {
         if (needPerfect) {
           auto &msg{context_.Say(beginSource,
               "This construct requires a perfect nest of depth %" PRId64
               ", but the associated nest is a perfect nest of depth %" PRId64
               ""_err_en_US,
-              *needDepth.value, *haveDepth)};
+              *needDepth.value, *haveDepth.value)};
+          haveDepth.reason.AttachTo(msg);
           needDepth.reason.AttachTo(msg);
         } else {
           auto &msg{context_.Say(beginSource,
               "This construct requires a nest of depth %" PRId64
               ", but the associated nest has a depth of %" PRId64 ""_err_en_US,
-              *needDepth.value, *haveDepth)};
+              *needDepth.value, *haveDepth.value)};
+          haveDepth.reason.AttachTo(msg);
           needDepth.reason.AttachTo(msg);
         }
       }
diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index 96a117b9055ec..185673444f0f0 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -1157,20 +1157,6 @@ std::optional<int64_t> LoopSequence::getNestedLength() const {
 }
 
 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 invalidIC_ and opaqueIC_
   // members do not count canonical loop nests, but there can only be one
   // for depth to make sense.
@@ -1178,24 +1164,61 @@ LoopSequence::Depth LoopSequence::calculateDepths() const {
   // 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 (invalidIC_ || length.value_or(0) != 1) {
-    semaDepth = perfDepth = 0;
-  } else if (opaqueIC_ || length.value_or(0) != 1) {
-    perfDepth = 0;
+  if (invalidIC_) {
+    parser::CharBlock source{*parser::GetSource(*invalidIC_)};
+    if (semaDepth.value > 9) {
+      semaDepth.value = 0;
+      semaDepth.reason.Say(
+          source, "This is not a valid intervening code"_because_en_US);
+    }
+    if (perfDepth.value > 0) {
+      perfDepth.value = 0;
+      perfDepth.reason.Say(
+          source, "This is not a valid intervening code"_because_en_US);
+    }
+  } else if (opaqueIC_) {
+    parser::CharBlock source{*parser::GetSource(*opaqueIC_)};
+    if (perfDepth.value > 0) {
+      perfDepth.value = 0;
+      perfDepth.reason.Say(
+          source, "This code prevents perfect nesting"_because_en_US);
+    }
+  }
+  if (length.value_or(0) != 1) {
+    // This may simply be the bottom of the loop nest. Only emit messages
+    // if the depths are reset back to 0.
+    if (entry_->owner) {
+      parser::CharBlock source{*parser::GetSource(*entry_->owner)};
+      if (semaDepth.value > 0) {
+        semaDepth.reason.Say(source,
+            "This construct does not contain a loop nest"_because_en_US);
+      }
+      if (perfDepth.value > 9) {
+        perfDepth.reason.Say(source,
+            "This construct does not contain a loop nest"_because_en_US);
+      }
+    }
+    semaDepth.value = perfDepth.value = 0;
   }
 
   if (!entry_->owner) {
     return Depth{semaDepth, perfDepth};
   }
   if (parser::Unwrap<parser::DoConstruct>(entry_->owner)) {
-    return Depth{plus(1, semaDepth), plus(1, perfDepth)};
+    return Depth{int64_t(1) + semaDepth, int64_t(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};
+  bool isFullUnroll{IsFullUnroll(omp)};
+
+  // Check full unroll separately.
+  if (!isFullUnroll && !IsTransformableLoop(omp)) {
+    Reason reason;
+    reason.Say(beginSpec.DirName().source,
+        "This construct is not a DO-loop or a loop-nest-generating construct"_because_en_US);
+    return Depth{{0, reason}, {0, reason}};
   }
 
   switch (dir) {
@@ -1215,13 +1238,17 @@ LoopSequence::Depth LoopSequence::calculateDepths() const {
           if (*required == -1 || *required == *nestedLength) {
             return Depth{value, value};
           }
-          return Depth{1, 1};
+          Reason reason(std::move(range.reason));
+          reason.Say(beginSpec.DirName().source,
+              "%s construct results in a proper loop-sequence"_because_en_US,
+              GetUpperName(llvm::omp::Directive::OMPD_fuse, version_));
+          return Depth{{1, reason}, {1, reason}};
         }
       }
-      return Depth{std::nullopt, std::nullopt};
+      return Depth{};
     }
     // FUSE cannot create a nest of depth > 1 without DEPTH clause.
-    return Depth{1, 1};
+    return Depth{WithReason<int64_t>(1), WithReason<int64_t>(1)};
   case llvm::omp::Directive::OMPD_interchange:
   case llvm::omp::Directive::OMPD_nothing:
   case llvm::omp::Directive::OMPD_reverse:
@@ -1234,13 +1261,18 @@ LoopSequence::Depth LoopSequence::calculateDepths() const {
       // 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)};
+      return Depth{//
+          static_cast<int64_t>(num) + semaDepth,
+          static_cast<int64_t>(num) + perfDepth};
     }
     // The SIZES clause is mandatory, if it's missing the result is unknown.
     return {};
   case llvm::omp::Directive::OMPD_unroll:
-    if (IsFullUnroll(omp)) {
-      return Depth{0, 0};
+    if (isFullUnroll) {
+      Reason reason;
+      reason.Say(beginSpec.DirName().source,
+          "Fully unrolled loop does not result in a loop nest"_because_en_US);
+      return Depth{{0, reason}, {0, reason}};
     }
     // If this is not a full unroll then look for a PARTIAL clause.
     if (auto *clause{parser::omp::FindClause(
@@ -1258,9 +1290,12 @@ LoopSequence::Depth LoopSequence::calculateDepths() const {
       // 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};
+      Reason reason;
+      reason.Say(beginSpec.DirName().source,
+          "Partially unrolled loop cannot form a nest of depth > 1"_because_en_US);
+      return {{1, reason}, {1, reason}};
     }
-    return Depth{std::nullopt, std::nullopt};
+    return Depth{};
   default:
     llvm_unreachable("Expecting loop-transforming construct");
   }
@@ -1268,13 +1303,15 @@ LoopSequence::Depth LoopSequence::calculateDepths() const {
 
 LoopSequence::Depth LoopSequence::getNestedDepths() const {
   if (!isNest()) {
-    return {std::nullopt, std::nullopt};
+    // If the current sequence is not a nest, it can still be a part of
+    // an enclosing nest.
+    return Depth{WithReason<int64_t>(0), WithReason<int64_t>(0)};
   } else if (children_.empty()) {
     // No children, but length == 1.
     assert(entry_->owner &&
         parser::Unwrap<parser::DoConstruct>(entry_->owner) &&
         "Expecting DO construct");
-    return Depth{0, 0};
+    return Depth{WithReason<int64_t>(0), WithReason<int64_t>(0)};
   }
   return children_.front().depth_;
 }
diff --git a/flang/test/Semantics/OpenMP/do-collapse.f90 b/flang/test/Semantics/OpenMP/do-collapse.f90
index 70a84c333236a..9cac2c7f50a4d 100644
--- a/flang/test/Semantics/OpenMP/do-collapse.f90
+++ b/flang/test/Semantics/OpenMP/do-collapse.f90
@@ -29,6 +29,7 @@ program omp_doCollapse
   !BECAUSE: COLLAPSE clause was specified with argument 2
   !$omp parallel do collapse(2)
     do i = 1, 3
+      !BECAUSE: This is not a valid intervening code
       !ERROR: Loop control is not present in the DO LOOP
       !ERROR: The associated loop of a loop-associated directive cannot be a DO without control.
       do
diff --git a/flang/test/Semantics/OpenMP/do-concurrent-collapse.f90 b/flang/test/Semantics/OpenMP/do-concurrent-collapse.f90
index 3e382eb3cc4d4..b84d8d54a6629 100644
--- a/flang/test/Semantics/OpenMP/do-concurrent-collapse.f90
+++ b/flang/test/Semantics/OpenMP/do-concurrent-collapse.f90
@@ -6,6 +6,7 @@
 ! ERROR: DO CONCURRENT loops cannot be used with the COLLAPSE clause.
 !$omp parallel do collapse(2)
 do i = 1, 1
+  ! BECAUSE: This is not a valid intervening code
   ! ERROR: DO CONCURRENT loops cannot form part of a loop nest.
   do concurrent (j = 1:2)
     print *, j
@@ -37,6 +38,7 @@
 ! ERROR: DO CONCURRENT loops cannot be used with the COLLAPSE clause.
 !$omp loop collapse(2)
 do i = 1, 1
+  ! BECAUSE: This is not a valid intervening code
   do concurrent (j = 1:2)
     print *, j
   end do
diff --git a/flang/test/Semantics/OpenMP/do08.f90 b/flang/test/Semantics/OpenMP/do08.f90
index 8ab02a0d9acbf..300485f067b1d 100644
--- a/flang/test/Semantics/OpenMP/do08.f90
+++ b/flang/test/Semantics/OpenMP/do08.f90
@@ -11,6 +11,7 @@ program omp
   !BECAUSE: COLLAPSE clause was specified with argument 3
   !$omp do  collapse(3)
   do i = 0, 10
+    !BECAUSE: This code prevents perfect nesting
     !ERROR: CYCLE statement to non-innermost associated loop of an OpenMP DO construct
     if (i .lt. 1) cycle
     do j = 0, 10
@@ -26,6 +27,7 @@ program omp
   !$omp do  collapse(3)
   do i = 0, 10
     do j = 0, 10
+      !BECAUSE: This code prevents perfect nesting
       !ERROR: CYCLE statement to non-innermost associated loop of an OpenMP DO construct
       if (i .lt. 1) cycle
       do k  = 0, 10
@@ -39,6 +41,7 @@ program omp
   !BECAUSE: COLLAPSE clause was specified with argument 2
   !$omp do  collapse(2)
   do i = 0, 10
+    !BECAUSE: This code prevents perfect nesting
     !ERROR: CYCLE statement to non-innermost associated loop of an OpenMP DO construct
     if (i .lt. 1) cycle
     do j = 0, 10
@@ -54,6 +57,7 @@ program omp
   !BECAUSE: COLLAPSE clause was specified with argument 2
   !$omp do  collapse(2)
   foo: do i = 0, 10
+    !BECAUSE: This code prevents perfect nesting
     !ERROR: CYCLE statement to non-innermost associated loop of an OpenMP DO construct
     if (i .lt. 1) cycle foo
     do j = 0, 10
@@ -70,10 +74,11 @@ program omp
   !$omp do collapse(3)
   do 60 i=2,200,2
     do j=1,10
+      !BECAUSE: This code prevents perfect nesting
       !ERROR: CYCLE statement to non-innermost associated loop of an OpenMP DO construct
-      if(i==100) cycle
-      do k=1,10
-        print *,i
+      if (i == 100) cycle
+      do k = 1, 10
+        print *, i
       end do
     end do
   60 continue
@@ -134,12 +139,13 @@ program omp
   !$omp do  collapse(2) ordered(3)
   foo: do i = 0, 10
     foo1: do j = 0, 10
-             !ERROR: CYCLE statement to non-innermost associated loop of an OpenMP DO construct
-             if (k .lt. 1) cycle foo
-         foo2:  do k  = 0, 10
-             print *, i, j, k
-           end do foo2
-         end do foo1
+      !ERROR: CYCLE statement to non-innermost associated loop of an OpenMP DO construct
+      !BECAUSE: This code prevents perfect nesting
+      if (k .lt. 1) cycle foo
+      foo2:  do k  = 0, 10
+        print *, i, j, k
+      end do foo2
+    end do foo1
   end do foo
   !$omp end do
 
diff --git a/flang/test/Semantics/OpenMP/do13.f90 b/flang/test/Semantics/OpenMP/do13.f90
index 895724e0a10d5..b4b07432e3800 100644
--- a/flang/test/Semantics/OpenMP/do13.f90
+++ b/flang/test/Semantics/OpenMP/do13.f90
@@ -9,6 +9,7 @@ program omp
   !BECAUSE: COLLAPSE clause was specified with argument 3
   !$omp do  collapse(3)
   do i = 0, 10
+    !BECAUSE: This is not a valid intervening code
     !ERROR: CYCLE statement to non-innermost associated loop of an OpenMP DO construct
     cycle
     do j = 0, 10
@@ -24,6 +25,7 @@ program omp
   !$omp do  collapse(3)
   do i = 0, 10
     do j = 0, 10
+      !BECAUSE: This is not a valid intervening code
       !ERROR: CYCLE statement to non-innermost associated loop of an OpenMP DO construct
       cycle
       do k  = 0, 10
@@ -37,6 +39,7 @@ program omp
   !BECAUSE: COLLAPSE clause was specified with argument 2
   !$omp do  collapse(2)
   do i = 0, 10
+    !BECAUSE: This is not a valid intervening code
     !ERROR: CYCLE statement to non-innermost associated loop of an OpenMP DO construct
     cycle
     do j = 0, 10
@@ -52,6 +55,7 @@ program omp
   !BECAUSE: COLLAPSE clause was specified with argument 2
   !$omp do  collapse(2)
   foo: do i = 0, 10
+    !BECAUSE: This is not a valid intervening code
     !ERROR: CYCLE statement to non-innermost associated loop of an OpenMP DO construct
     cycle foo
     do j = 0, 10
@@ -68,6 +72,7 @@ program omp
   !$omp do collapse(3)
   do 60 i=1,10
     do j=1,10
+      !BECAUSE: This is not a valid intervening code
       !ERROR: CYCLE statement to non-innermost associated loop of an OpenMP DO construct
       cycle
       do k=1,10
@@ -185,7 +190,7 @@ program omp
       !$omp end parallel
     end do foo1
   end do foo
-!$omp end parallel do
-!$omp end parallel
+  !$omp end parallel do
+  !$omp end parallel
 
 end program omp
diff --git a/flang/test/Semantics/OpenMP/do15.f90 b/flang/test/Semantics/OpenMP/do15.f90
index 939d7bfde303e..00baa0c431c5f 100644
--- a/flang/test/Semantics/OpenMP/do15.f90
+++ b/flang/test/Semantics/OpenMP/do15.f90
@@ -9,6 +9,7 @@ program omp
   !BECAUSE: COLLAPSE clause was specified with argument 3
   !$omp do  collapse(3)
   do i = 0, 10
+    !BECAUSE: This code prevents perfect nesting
     if (i .lt. 1) then
       !ERROR: CYCLE statement to non-innermost associated loop of an OpenMP DO construct
       cycle
@@ -26,6 +27,7 @@ program omp
   !$omp do  collapse(3)
   do i = 0, 10
     do j = 0, 10
+      !BECAUSE: This code prevents perfect nesting
       if (i .lt. 1) then
         !ERROR: CYCLE statement to non-innermost associated loop of an OpenMP DO construct
         cycle
@@ -59,6 +61,7 @@ program omp
   !$omp do  collapse(3)
   foo: do i = 0, 10
     foo1: do j = 0, 10
+      !BECAUSE: This code prevents perfect nesting
       if (i .lt. 1) then
         !ERROR: CYCLE statement to non-innermost associated loop of an OpenMP DO construct
         cycle foo
diff --git a/flang/test/Semantics/OpenMP/do16.f90 b/flang/test/Semantics/OpenMP/do16.f90
index e671c73783e1c..35d94b17f3c68 100644
--- a/flang/test/Semantics/OpenMP/do16.f90
+++ b/flang/test/Semantics/OpenMP/do16.f90
@@ -9,6 +9,7 @@ program omp
   !BECAUSE: COLLAPSE clause was specified with argument 3
   !$omp do  collapse(3)
   do i = 0, 10
+    !BECAUSE: This code prevents perfect nesting
     select case (i)
     case(1)
       !ERROR: CYCLE statement to non-innermost associated loop of an OpenMP DO construct
@@ -27,6 +28,7 @@ program omp
   !$omp do  collapse(3)
   do i = 0, 10
     do j = 0, 10
+      !BECAUSE: This code prevents perfect nesting
       select case (i)
       case(1)
         !ERROR: CYCLE statement to non-innermost associated loop of an OpenMP DO construct
diff --git a/flang/test/Semantics/OpenMP/do22.f90 b/flang/test/Semantics/OpenMP/do22.f90
index dc38bd5d23253..a0502aee748ae 100644
--- a/flang/test/Semantics/OpenMP/do22.f90
+++ b/flang/test/Semantics/OpenMP/do22.f90
@@ -8,6 +8,7 @@ subroutine do_imperfectly_nested_before
   !BECAUSE: COLLAPSE clause was specified with argument 2
   !$omp do collapse(2)
   do i = 1, 10
+    !BECAUSE: This code prevents perfect nesting
     print *, i
     do j = 1, 10
       print *, i, j
@@ -27,6 +28,7 @@ subroutine do_imperfectly_nested_behind
     do j = 1, 10
       print *, i, j
     end do
+    !BECAUSE: This code prevents perfect nesting
     print *, i
   end do
   !$omp end do
diff --git a/flang/test/Semantics/OpenMP/tile07.f90 b/flang/test/Semantics/OpenMP/tile07.f90
index 9642fe10013fd..0c98f6856ff15 100644
--- a/flang/test/Semantics/OpenMP/tile07.f90
+++ b/flang/test/Semantics/OpenMP/tile07.f90
@@ -13,6 +13,7 @@ subroutine non_perfectly_nested_loop_behind
     do j = 1, 42
       print *, j
     end do
+    !BECAUSE: This code prevents perfect nesting
     print *, i
   end do
 end subroutine
@@ -26,6 +27,7 @@ subroutine non_perfectly_nested_loop_before
   !BECAUSE: SIZES clause was specified with 2 arguments
   !$omp tile sizes(2,2)
   do i = 1, 5
+    !BECAUSE: This code prevents perfect nesting
     print *, i
     do j = 1, 42
       print *, j
diff --git a/flang/test/Semantics/OpenMP/tile09.f90 b/flang/test/Semantics/OpenMP/tile09.f90
new file mode 100644
index 0000000000000..fc81d22e49b3d
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/tile09.f90
@@ -0,0 +1,63 @@
+! Testing the Semantics of tile
+!RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=61
+
+subroutine f00
+  integer :: i, j
+  !ERROR: This construct requires a perfect nest of depth 2, but the associated nest is a perfect nest of depth 1
+  !BECAUSE: SIZES clause was specified with 2 arguments
+  !$omp tile sizes(2, 2)
+  do i = 1, 10
+    !BECAUSE: This construct is not a DO-loop or a loop-nest-generating construct
+    !$omp do
+    do j = 1, 10
+    end do
+  end do
+end
+
+subroutine f01
+  integer :: i, j
+  !ERROR: This construct requires a perfect nest of depth 2, but the associated nest is a perfect nest of depth 1
+  !BECAUSE: SIZES clause was specified with 2 arguments
+  !$omp tile sizes(2, 2)
+  do i = 1, 10
+    !BECAUSE: Fully unrolled loop does not result in a loop nest
+    !$omp unroll full
+    do j = 1, 10
+    end do
+  end do
+end
+
+subroutine f02
+  integer :: i, j
+  !ERROR: This construct requires a perfect nest of depth 2, but the associated nest is a perfect nest of depth 1
+  !BECAUSE: SIZES clause was specified with 2 arguments
+  !$omp tile sizes(2, 2)
+  !BECAUSE: Partially unrolled loop cannot form a nest of depth > 1
+  !$omp unroll partial
+  do i = 1, 10
+    do j = 1, 10
+    end do
+  end do
+end
+
+subroutine f03
+  integer :: i, j, k
+  !ERROR: This construct requires a perfect nest of depth 3, but the associated nest is a perfect nest of depth 1
+  !BECAUSE: SIZES clause was specified with 3 arguments
+  !$omp tile sizes(2, 2, 2)
+  do i = 1, 10
+    !BECAUSE: LOOPRANGE clause was specified with a count of 1 starting at loop 1
+    !BECAUSE: FUSE construct results in a proper loop-sequence
+    !$omp fuse depth(2) looprange(1, 1)
+    do j = 1, 10
+      do k = 1, 10
+      end do
+    end do
+    do j = 1, 10
+      do k = 1, 10
+      end do
+    end do
+    !$omp end fuse
+  end do
+end
+

>From efdb9812bf9ca06e58d3a49c07435b7cec10d457 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Fri, 20 Mar 2026 09:40:06 -0500
Subject: [PATCH 2/3] [flang][OpenMP] Provide reasons for calculated sequence
 length

If the length was limited by some factor, include the reason for what
caused the reduction.

Issue: https://github.com/llvm/llvm-project/issues/185287
---
 flang/include/flang/Semantics/openmp-utils.h  | 10 ++--
 flang/lib/Semantics/check-omp-loop.cpp        | 16 +++---
 flang/lib/Semantics/openmp-utils.cpp          | 55 +++++++++++--------
 flang/test/Semantics/OpenMP/fuse1.f90         |  1 +
 .../loop-transformation-construct02.f90       |  1 +
 .../loop-transformation-construct04.f90       |  2 +
 6 files changed, 51 insertions(+), 34 deletions(-)

diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index de5f9cc9072a3..7c95edf81ada2 100644
--- a/flang/include/flang/Semantics/openmp-utils.h
+++ b/flang/include/flang/Semantics/openmp-utils.h
@@ -197,8 +197,8 @@ struct LoopSequence {
     WithReason<int64_t> perfect;
   };
 
-  bool isNest() const { return length_ && *length_ == 1; }
-  std::optional<int64_t> length() const { return length_; }
+  bool isNest() const { return length_.value == 1; }
+  const WithReason<int64_t> &length() const { return length_; }
   const Depth &depth() const { return depth_; }
   const std::vector<LoopSequence> &children() const { return children_; }
 
@@ -223,8 +223,8 @@ struct LoopSequence {
   /// Precalculate length and depth.
   void precalculate();
 
-  std::optional<int64_t> calculateLength() const;
-  std::optional<int64_t> getNestedLength() const;
+  WithReason<int64_t> calculateLength() const;
+  WithReason<int64_t> getNestedLength() const;
   Depth calculateDepths() const;
   Depth getNestedDepths() const;
 
@@ -241,7 +241,7 @@ struct LoopSequence {
   /// 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_;
+  WithReason<int64_t> length_;
   /// Precalculated depths. Only meaningful if the sequence is a nest.
   Depth depth_;
 
diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp
index 36b7b22de67ea..df9797ac8e56a 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -294,26 +294,28 @@ void OmpStructureChecker::CheckNestedConstruct(
   // in it.
   auto needRange{GetAffectedLoopRangeWithReason(beginSpec, version)};
 
-  if (std::optional<int64_t> numLoops{sequence.length()}) {
-    if (*numLoops == 0) {
+  if (auto haveLength{sequence.length()}) {
+    if (*haveLength.value == 0) {
       context_.Say(beginSource,
           "This construct should contain a DO-loop or a loop-nest-generating OpenMP construct"_err_en_US);
     } else {
       auto assoc{llvm::omp::getDirectiveAssociation(dir)};
-      if (*numLoops > 1 && assoc == llvm::omp::Association::LoopNest) {
-        context_.Say(beginSource,
+      if (*haveLength.value > 1 && assoc == llvm::omp::Association::LoopNest) {
+        auto &msg{context_.Say(beginSource,
             "This construct applies to a loop nest, but has a loop sequence of "
             "length %" PRId64 ""_err_en_US,
-            *numLoops);
+            *haveLength.value)};
+        haveLength.reason.AttachTo(msg);
       }
       if (assoc == llvm::omp::Association::LoopSeq) {
         if (auto requiredCount{GetRequiredCount(needRange.value)}) {
-          if (*requiredCount > 0 && *numLoops < *requiredCount) {
+          if (*requiredCount > 0 && *haveLength.value < *requiredCount) {
             auto &msg{context_.Say(beginSource,
                 "This construct requires a sequence of %" PRId64
                 " loops, but the loop sequence has a length of %" PRId64
                 ""_err_en_US,
-                *requiredCount, *numLoops)};
+                *requiredCount, *haveLength.value)};
+            haveLength.reason.AttachTo(msg);
             needRange.reason.AttachTo(msg);
           }
         }
diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index 185673444f0f0..aa3a7d364c62a 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -1083,24 +1083,27 @@ void LoopSequence::precalculate() {
   depth_ = calculateDepths();
 }
 
-std::optional<int64_t> LoopSequence::calculateLength() const {
+WithReason<int64_t> LoopSequence::calculateLength() const {
   if (!entry_->owner) {
     return getNestedLength();
   }
   if (parser::Unwrap<parser::DoConstruct>(entry_->owner)) {
-    return 1;
+    return WithReason<int64_t>(1);
   }
 
   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;
+    Reason reason;
+    reason.Say(beginSpec.DirName().source,
+        "This construct does not result in a loop nest or a loop sequence"_because_en_US);
+    return {0, std::move(reason)};
   }
 
   // TODO: Handle split, apply.
   if (IsFullUnroll(omp)) {
-    return std::nullopt;
+    return {};
   }
 
   auto nestedLength{getNestedLength()};
@@ -1116,24 +1119,33 @@ 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 (!nestedLength || *nestedLength == 0) {
-      return std::nullopt;
+    if (!nestedLength.value || *nestedLength.value == 0) {
+      return {};
     }
     auto *clause{
         parser::omp::FindClause(beginSpec, llvm::omp::Clause::OMPC_looprange)};
     if (!clause) {
-      return 1;
+      Reason reason;
+      reason.Say(beginSpec.DirName().source,
+          "%s clause was not specified, all loops in the sequence are fused"_because_en_US,
+          GetUpperName(llvm::omp::Clause::OMPC_looprange, version_));
+      return {1, std::move(reason)};
     }
 
     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;
+      return {};
     }
-    if (*count <= *nestedLength) {
-      return 1 + *nestedLength - *count;
+    if (*count <= *nestedLength.value) {
+      int64_t result{1 + *nestedLength.value - *count};
+      Reason reason;
+      reason.Say(beginSpec.DirName().source,
+          "Out of %" PRId64 " loops, %" PRId64 " were fused"_because_en_US,
+          *nestedLength.value, *count);
+      return {result, std::move(reason)};
     }
-    return std::nullopt;
+    return {};
   }
 
   if (dir == llvm::omp::Directive::OMPD_nothing) {
@@ -1141,16 +1153,16 @@ std::optional<int64_t> LoopSequence::calculateLength() const {
   }
 
   // For every other loop construct return 1.
-  return 1;
+  return {1, Reason()};
 }
 
-std::optional<int64_t> LoopSequence::getNestedLength() const {
-  int64_t sum{0};
+WithReason<int64_t> LoopSequence::getNestedLength() const {
+  WithReason<int64_t> sum(0);
   for (auto &seq : children_) {
-    if (auto len{seq.length()}) {
-      sum += *len;
+    if (const auto &len{seq.length()}) {
+      sum = sum + len;
     } else {
-      return std::nullopt;
+      return {};
     }
   }
   return sum;
@@ -1160,7 +1172,7 @@ LoopSequence::Depth LoopSequence::calculateDepths() const {
   // Get the length of the nested sequence. The invalidIC_ and opaqueIC_
   // members do not count canonical loop nests, but there can only be one
   // for depth to make sense.
-  std::optional<int64_t> length{getNestedLength()};
+  WithReason<int64_t> nestedLength{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()};
@@ -1184,7 +1196,7 @@ LoopSequence::Depth LoopSequence::calculateDepths() const {
           source, "This code prevents perfect nesting"_because_en_US);
     }
   }
-  if (length.value_or(0) != 1) {
+  if (nestedLength.value.value_or(0) != 1) {
     // This may simply be the bottom of the loop nest. Only emit messages
     // if the depths are reset back to 0.
     if (entry_->owner) {
@@ -1229,13 +1241,12 @@ LoopSequence::Depth LoopSequence::calculateDepths() const {
             beginSpec, llvm::omp::Clause::OMPC_depth)}) {
       auto &expr{parser::UnwrapRef<parser::Expr>(clause->u)};
       auto value{GetIntValue(expr)};
-      auto nestedLength{getNestedLength()};
       // The result is a perfect nest only if all loop in the sequence
       // are fused.
-      if (value && nestedLength) {
+      if (value && nestedLength.value) {
         auto range{GetAffectedLoopRangeWithReason(beginSpec, version_)};
         if (auto required{GetRequiredCount(range.value)}) {
-          if (*required == -1 || *required == *nestedLength) {
+          if (*required == -1 || *required == *nestedLength.value) {
             return Depth{value, value};
           }
           Reason reason(std::move(range.reason));
diff --git a/flang/test/Semantics/OpenMP/fuse1.f90 b/flang/test/Semantics/OpenMP/fuse1.f90
index 4dab01ca3ec26..a7dd7e190c16a 100644
--- a/flang/test/Semantics/OpenMP/fuse1.f90
+++ b/flang/test/Semantics/OpenMP/fuse1.f90
@@ -10,6 +10,7 @@ subroutine f
   !ERROR: This construct requires a sequence of 2 loops, but the loop sequence has a length of 1
   !BECAUSE: LOOPRANGE clause was specified with a count of 2 starting at loop 1
   !$omp fuse looprange(1, 2)
+  !BECAUSE: LOOPRANGE clause was not specified, all loops in the sequence are fused
   !$omp fuse
   do i = 1, 10
   end do
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
index 25247c3896cae..d0169c4b721bf 100644
--- a/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
+++ b/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
@@ -82,6 +82,7 @@ subroutine loop_transformation_construct6
 
   !ERROR: This construct applies to a loop nest, but has a loop sequence of length 2
   !$omp do
+  !BECAUSE: Out of 2 loops, 1 were fused
   !$omp fuse looprange(1,1)
   !$omp unroll partial(2)
   do x = 1, i
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct04.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct04.f90
index 158b030906e07..c23acc2c4c266 100644
--- a/flang/test/Semantics/OpenMP/loop-transformation-construct04.f90
+++ b/flang/test/Semantics/OpenMP/loop-transformation-construct04.f90
@@ -10,6 +10,7 @@ subroutine loop_transformation_construct3
 
   !ERROR: This construct applies to a loop nest, but has a loop sequence of length 2
   !$omp do
+  !BECAUSE: Out of 3 loops, 2 were fused
   !$omp fuse looprange(1,2)
   do x = 1, i
     v(x) = x * 2
@@ -32,6 +33,7 @@ subroutine loop_transformation_construct4
 
   !ERROR: This construct applies to a loop nest, but has a loop sequence of length 2
   !$omp tile sizes(2)
+  !BECAUSE: Out of 3 loops, 2 were fused
   !$omp fuse looprange(1,2)
   do x = 1, i
     v(x) = x * 2

>From 49ebf382ef71c02f3ab57fd925aac4d6fdaffd28 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Sat, 21 Mar 2026 09:53:32 -0500
Subject: [PATCH 3/3] Fix message for absent LOOPRANGE

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

diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index aa3a7d364c62a..20a4de77a1d7f 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -926,7 +926,7 @@ WithReason<std::pair<int64_t, int64_t>> GetAffectedLoopRangeWithReason(
     // associated sequence".
     Reason reason;
     reason.Say(spec.source,
-        "%s clause was not specified, a value of 1 was assumed"_because_en_US,
+        "%s clause was not specified, the entire sequence is affected by"_because_en_US,
         name.c_str());
     return {std::make_pair(1, -1), std::move(reason)};
   }



More information about the flang-commits mailing list