[flang-commits] [flang] [flang][acc] Fix crash on collapse(force:N) with non-tightly nested loops (PR #191310)

via flang-commits flang-commits at lists.llvm.org
Tue Apr 14 17:10:50 PDT 2026


https://github.com/khaki3 updated https://github.com/llvm/llvm-project/pull/191310

>From 6b38bbd4de06a62be2884c848e3bf95deaf4747e Mon Sep 17 00:00:00 2001
From: Kazuaki Matsumura <kmatsumura at nvidia.com>
Date: Wed, 8 Apr 2026 12:45:27 -0700
Subject: [PATCH 1/5] [Flang][OpenACC] Fix crash on collapse(force:N) with
 non-tightly nested loops

When collapse(force:N) is applied to non-tightly nested loops (loops
with code between them, CYCLE statements, etc.), the PFT evaluation
tree may not have N levels of nested DoConstruct evaluations.  Several
places in the OpenACC lowering called getNestedEvaluations() without
first checking hasNestedEvaluations(), causing assertion failures.

Add guards in:
- hasEarlyReturn(): check hasNestedEvaluations() before iterating
- createRegionOp(): guard the createEmptyRegionBlocks call
- Bridge.cpp collapse-force sinking: break instead of assert when
  inner DoConstruct is not found, guard getNestedEvaluations() calls

Made-with: Cursor
---
 flang/lib/Lower/Bridge.cpp  | 71 ++++++++++++++++++++-----------------
 flang/lib/Lower/OpenACC.cpp |  5 ++-
 2 files changed, 42 insertions(+), 34 deletions(-)

diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 19ab789018f40..6675a3c7b2133 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -3649,8 +3649,11 @@ class FirConverter : public Fortran::lower::AbstractConverter {
 
       if (curEval->lowerAsStructured()) {
         curEval = &curEval->getFirstNestedEvaluation();
-        for (uint64_t i = 1; i < loopCount; i++)
+        for (uint64_t i = 1; i < loopCount; i++) {
+          if (!curEval->hasNestedEvaluations())
+            break;
           curEval = &*std::next(curEval->getNestedEvaluations().begin());
+        }
       }
     }
 
@@ -3677,9 +3680,8 @@ class FirConverter : public Fortran::lower::AbstractConverter {
           }
           prologue.push_back(&*it);
         }
-        // Semantics guarantees collapseDepth does not exceed nest depth
-        // so childLoop must be found here.
-        assert(childLoop && "Expected inner DoConstruct for collapse");
+        if (!childLoop)
+          break;
         parent = childLoop;
         innermostLoopEval = childLoop;
       }
@@ -3700,16 +3702,19 @@ class FirConverter : public Fortran::lower::AbstractConverter {
       sink(prologue);
 
       // Lower innermost loop body, skipping sunk
-      for (Fortran::lower::pft::Evaluation &e :
-           innermostLoopEval->getNestedEvaluations())
-        if (!sunk.contains(&e))
-          genFIR(e);
+      if (innermostLoopEval && innermostLoopEval->hasNestedEvaluations())
+        for (Fortran::lower::pft::Evaluation &e :
+             innermostLoopEval->getNestedEvaluations())
+          if (!sunk.contains(&e))
+            genFIR(e);
 
       sink(epilogue);
     } else {
       // Normal lowering
-      for (Fortran::lower::pft::Evaluation &e : curEval->getNestedEvaluations())
-        genFIR(e);
+      if (curEval->hasNestedEvaluations())
+        for (Fortran::lower::pft::Evaluation &e :
+             curEval->getNestedEvaluations())
+          genFIR(e);
     }
     if (!isCacheConstruct)
       localSymbols.popScope();
@@ -6281,29 +6286,29 @@ class FirConverter : public Fortran::lower::AbstractConverter {
   // calls does block management, possibly starting a new block, and possibly
   // generating a branch to end a block. So these calls may still be required
   // for that functionality.
-  void genFIR(const Fortran::parser::AssociateStmt &) {}       // nop
-  void genFIR(const Fortran::parser::BlockStmt &) {}           // nop
-  void genFIR(const Fortran::parser::CaseStmt &) {}            // nop
-  void genFIR(const Fortran::parser::ContinueStmt &) {}        // nop
-  void genFIR(const Fortran::parser::ElseIfStmt &) {}          // nop
-  void genFIR(const Fortran::parser::ElseStmt &) {}            // nop
-  void genFIR(const Fortran::parser::EndAssociateStmt &) {}    // nop
-  void genFIR(const Fortran::parser::EndBlockStmt &) {}        // nop
-  void genFIR(const Fortran::parser::EndDoStmt &) {}           // nop
-  void genFIR(const Fortran::parser::EndFunctionStmt &) {}     // nop
-  void genFIR(const Fortran::parser::EndIfStmt &) {}           // nop
-  void genFIR(const Fortran::parser::EndMpSubprogramStmt &) {} // nop
-  void genFIR(const Fortran::parser::EndProgramStmt &) {}      // nop
-  void genFIR(const Fortran::parser::EndSelectStmt &) {}       // nop
-  void genFIR(const Fortran::parser::EndSubroutineStmt &) {}   // nop
-  void genFIR(const Fortran::parser::EntryStmt &) {}           // nop
-  void genFIR(const Fortran::parser::IfStmt &) {}              // nop
-  void genFIR(const Fortran::parser::IfThenStmt &) {}          // nop
-  void genFIR(const Fortran::parser::NonLabelDoStmt &) {}      // nop
-  void genFIR(const Fortran::parser::OmpEndLoopDirective &) {} // nop
-  void genFIR(const Fortran::parser::SelectTypeStmt &) {}      // nop
-  void genFIR(const Fortran::parser::TypeGuardStmt &) {}       // nop
-  void genFIR(const Fortran::parser::ChangeTeamStmt &stmt) {}  // nop
+  void genFIR(const Fortran::parser::AssociateStmt &) {}         // nop
+  void genFIR(const Fortran::parser::BlockStmt &) {}             // nop
+  void genFIR(const Fortran::parser::CaseStmt &) {}              // nop
+  void genFIR(const Fortran::parser::ContinueStmt &) {}          // nop
+  void genFIR(const Fortran::parser::ElseIfStmt &) {}            // nop
+  void genFIR(const Fortran::parser::ElseStmt &) {}              // nop
+  void genFIR(const Fortran::parser::EndAssociateStmt &) {}      // nop
+  void genFIR(const Fortran::parser::EndBlockStmt &) {}          // nop
+  void genFIR(const Fortran::parser::EndDoStmt &) {}             // nop
+  void genFIR(const Fortran::parser::EndFunctionStmt &) {}       // nop
+  void genFIR(const Fortran::parser::EndIfStmt &) {}             // nop
+  void genFIR(const Fortran::parser::EndMpSubprogramStmt &) {}   // nop
+  void genFIR(const Fortran::parser::EndProgramStmt &) {}        // nop
+  void genFIR(const Fortran::parser::EndSelectStmt &) {}         // nop
+  void genFIR(const Fortran::parser::EndSubroutineStmt &) {}     // nop
+  void genFIR(const Fortran::parser::EntryStmt &) {}             // nop
+  void genFIR(const Fortran::parser::IfStmt &) {}                // nop
+  void genFIR(const Fortran::parser::IfThenStmt &) {}            // nop
+  void genFIR(const Fortran::parser::NonLabelDoStmt &) {}        // nop
+  void genFIR(const Fortran::parser::OmpEndLoopDirective &) {}   // nop
+  void genFIR(const Fortran::parser::SelectTypeStmt &) {}        // nop
+  void genFIR(const Fortran::parser::TypeGuardStmt &) {}         // nop
+  void genFIR(const Fortran::parser::ChangeTeamStmt &stmt) {}    // nop
   void genFIR(const Fortran::parser::EndChangeTeamStmt &stmt) {} // nop
 
   /// Generate FIR for Evaluation \p eval.
diff --git a/flang/lib/Lower/OpenACC.cpp b/flang/lib/Lower/OpenACC.cpp
index 5a7fe899b372f..5a2fab48accd2 100644
--- a/flang/lib/Lower/OpenACC.cpp
+++ b/flang/lib/Lower/OpenACC.cpp
@@ -1215,7 +1215,8 @@ createRegionOp(fir::FirOpBuilder &builder, mlir::Location loc,
 
   // If it is an unstructured region and is not the outer region of a combined
   // construct, create empty blocks for all evaluations.
-  if (eval.lowerAsUnstructured() && !outerCombined)
+  if (eval.lowerAsUnstructured() && !outerCombined &&
+      eval.hasNestedEvaluations())
     Fortran::lower::createEmptyRegionBlocks<mlir::acc::TerminatorOp,
                                             mlir::acc::YieldOp>(
         builder, eval.getNestedEvaluations());
@@ -2033,6 +2034,8 @@ buildACCLoopOp(Fortran::lower::AbstractConverter &converter,
 }
 
 static bool hasEarlyReturn(Fortran::lower::pft::Evaluation &eval) {
+  if (!eval.hasNestedEvaluations())
+    return false;
   bool hasReturnStmt = false;
   for (auto &e : eval.getNestedEvaluations()) {
     e.visit(Fortran::common::visitors{

>From 26be95b7fd4b4b8f8dbd5a62fbdcf6460f82a2b2 Mon Sep 17 00:00:00 2001
From: Kazuaki Matsumura <kmatsumura at nvidia.com>
Date: Wed, 8 Apr 2026 12:46:38 -0700
Subject: [PATCH 2/5] [Flang][OpenACC] Add test for collapse(force:N) on
 non-tightly nested loops

Made-with: Cursor
---
 ...loop-collapse-force-non-tightly-nested.f90 | 39 +++++++++++++++++++
 1 file changed, 39 insertions(+)
 create mode 100644 flang/test/Lower/OpenACC/acc-loop-collapse-force-non-tightly-nested.f90

diff --git a/flang/test/Lower/OpenACC/acc-loop-collapse-force-non-tightly-nested.f90 b/flang/test/Lower/OpenACC/acc-loop-collapse-force-non-tightly-nested.f90
new file mode 100644
index 0000000000000..9f21c1d61b91a
--- /dev/null
+++ b/flang/test/Lower/OpenACC/acc-loop-collapse-force-non-tightly-nested.f90
@@ -0,0 +1,39 @@
+! RUN: bbc -fopenacc -emit-hlfir %s -o - | FileCheck %s
+
+! Verify that collapse(force:4) on non-tightly nested loops (with code
+! between loops and CYCLE statements) does not crash the compiler.
+
+subroutine collapse_force_non_tight(a, sr, nq, np, desc)
+  implicit none
+  integer, intent(in) :: nq, np
+  integer, intent(in) :: desc(3)
+  real, intent(inout) :: a(np * nq)
+  real, intent(in) :: sr(max(np, nq))
+  integer :: j1, j2, jcol, j
+  integer :: i1, i2, irow, i
+
+!$acc parallel loop collapse(force:4) present(a, sr)
+  do j1 = 1, nq, desc(1)
+    do j2 = 1, desc(1)
+      jcol = j1 + j2 - 1
+      if (jcol > nq) cycle
+      j = j1 + j2 - 1
+
+      do i1 = 1, np, desc(2)
+        do i2 = 1, desc(2)
+          irow = i1 + i2 - 1
+          if (irow > np) cycle
+          i = i1 + i2 - 1
+
+          a(irow + (jcol - 1) * np) = a(irow + (jcol - 1) * np) * sr(i) * sr(j)
+        end do
+      end do
+    end do
+  end do
+!$acc end parallel loop
+end subroutine
+
+! CHECK: func.func @_QPcollapse_force_non_tight
+! CHECK: acc.parallel
+! CHECK: acc.loop
+! CHECK: collapse = [4]

>From 39eb57ae009890300f5638d327373154e3263433 Mon Sep 17 00:00:00 2001
From: Kazuaki Matsumura <kmatsumura at nvidia.com>
Date: Wed, 8 Apr 2026 12:54:07 -0700
Subject: [PATCH 3/5] fixup! [Flang][OpenACC] Fix crash on collapse(force:N)
 with non-tightly nested loops

Made-with: Cursor
---
 flang/lib/Lower/Bridge.cpp | 46 +++++++++++++++++++-------------------
 1 file changed, 23 insertions(+), 23 deletions(-)

diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 6675a3c7b2133..a7d83c472b9c5 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -6286,29 +6286,29 @@ class FirConverter : public Fortran::lower::AbstractConverter {
   // calls does block management, possibly starting a new block, and possibly
   // generating a branch to end a block. So these calls may still be required
   // for that functionality.
-  void genFIR(const Fortran::parser::AssociateStmt &) {}         // nop
-  void genFIR(const Fortran::parser::BlockStmt &) {}             // nop
-  void genFIR(const Fortran::parser::CaseStmt &) {}              // nop
-  void genFIR(const Fortran::parser::ContinueStmt &) {}          // nop
-  void genFIR(const Fortran::parser::ElseIfStmt &) {}            // nop
-  void genFIR(const Fortran::parser::ElseStmt &) {}              // nop
-  void genFIR(const Fortran::parser::EndAssociateStmt &) {}      // nop
-  void genFIR(const Fortran::parser::EndBlockStmt &) {}          // nop
-  void genFIR(const Fortran::parser::EndDoStmt &) {}             // nop
-  void genFIR(const Fortran::parser::EndFunctionStmt &) {}       // nop
-  void genFIR(const Fortran::parser::EndIfStmt &) {}             // nop
-  void genFIR(const Fortran::parser::EndMpSubprogramStmt &) {}   // nop
-  void genFIR(const Fortran::parser::EndProgramStmt &) {}        // nop
-  void genFIR(const Fortran::parser::EndSelectStmt &) {}         // nop
-  void genFIR(const Fortran::parser::EndSubroutineStmt &) {}     // nop
-  void genFIR(const Fortran::parser::EntryStmt &) {}             // nop
-  void genFIR(const Fortran::parser::IfStmt &) {}                // nop
-  void genFIR(const Fortran::parser::IfThenStmt &) {}            // nop
-  void genFIR(const Fortran::parser::NonLabelDoStmt &) {}        // nop
-  void genFIR(const Fortran::parser::OmpEndLoopDirective &) {}   // nop
-  void genFIR(const Fortran::parser::SelectTypeStmt &) {}        // nop
-  void genFIR(const Fortran::parser::TypeGuardStmt &) {}         // nop
-  void genFIR(const Fortran::parser::ChangeTeamStmt &stmt) {}    // nop
+  void genFIR(const Fortran::parser::AssociateStmt &) {}       // nop
+  void genFIR(const Fortran::parser::BlockStmt &) {}           // nop
+  void genFIR(const Fortran::parser::CaseStmt &) {}            // nop
+  void genFIR(const Fortran::parser::ContinueStmt &) {}        // nop
+  void genFIR(const Fortran::parser::ElseIfStmt &) {}          // nop
+  void genFIR(const Fortran::parser::ElseStmt &) {}            // nop
+  void genFIR(const Fortran::parser::EndAssociateStmt &) {}    // nop
+  void genFIR(const Fortran::parser::EndBlockStmt &) {}        // nop
+  void genFIR(const Fortran::parser::EndDoStmt &) {}           // nop
+  void genFIR(const Fortran::parser::EndFunctionStmt &) {}     // nop
+  void genFIR(const Fortran::parser::EndIfStmt &) {}           // nop
+  void genFIR(const Fortran::parser::EndMpSubprogramStmt &) {} // nop
+  void genFIR(const Fortran::parser::EndProgramStmt &) {}      // nop
+  void genFIR(const Fortran::parser::EndSelectStmt &) {}       // nop
+  void genFIR(const Fortran::parser::EndSubroutineStmt &) {}   // nop
+  void genFIR(const Fortran::parser::EntryStmt &) {}           // nop
+  void genFIR(const Fortran::parser::IfStmt &) {}              // nop
+  void genFIR(const Fortran::parser::IfThenStmt &) {}          // nop
+  void genFIR(const Fortran::parser::NonLabelDoStmt &) {}      // nop
+  void genFIR(const Fortran::parser::OmpEndLoopDirective &) {} // nop
+  void genFIR(const Fortran::parser::SelectTypeStmt &) {}      // nop
+  void genFIR(const Fortran::parser::TypeGuardStmt &) {}       // nop
+  void genFIR(const Fortran::parser::ChangeTeamStmt &stmt) {}  // nop
   void genFIR(const Fortran::parser::EndChangeTeamStmt &stmt) {} // nop
 
   /// Generate FIR for Evaluation \p eval.

>From bfa22c9f2d859e53a87eb4db8d1bca3a7e89c262 Mon Sep 17 00:00:00 2001
From: Kazuaki Matsumura <kmatsumura at nvidia.com>
Date: Fri, 10 Apr 2026 16:20:49 -0700
Subject: [PATCH 4/5] [Flang][OpenACC] Track collapsed DoConstructs to skip
 redundant inner loops

Use a DoConstruct* set to track which inner loops were absorbed by
collapse(force:N) during visitLoopControl.  In genFIR(DoConstruct),
check this set and skip generating a loop for absorbed constructs.

This replaces the depth-based counting approach which could incorrectly
skip non-collapsed loops inside the collapsed body.

Made-with: Cursor
---
 flang/include/flang/Lower/OpenACC.h           |  7 +++
 flang/lib/Lower/Bridge.cpp                    | 55 ++++++++++++++-----
 flang/lib/Lower/OpenACC.cpp                   | 14 +++++
 .../acc-loop-collapse-force-lowering.f90      | 13 +----
 ...loop-collapse-force-non-tightly-nested.f90 | 41 +++++---------
 5 files changed, 79 insertions(+), 51 deletions(-)

diff --git a/flang/include/flang/Lower/OpenACC.h b/flang/include/flang/Lower/OpenACC.h
index 745c8686457f4..013a04e2f75c7 100644
--- a/flang/include/flang/Lower/OpenACC.h
+++ b/flang/include/flang/Lower/OpenACC.h
@@ -110,6 +110,13 @@ getCollapseSizeAndForce(const Fortran::parser::AccClauseList &);
 /// Checks whether the current insertion point is inside OpenACC loop.
 bool isInOpenACCLoop(fir::FirOpBuilder &);
 
+/// Record a DoConstruct as having been absorbed by a collapse clause.
+/// The PFT walker should skip generating a loop for it.
+void markDoConstructAsCollapsed(const Fortran::parser::DoConstruct &);
+
+/// Check whether a DoConstruct was absorbed by a collapse clause.
+bool isCollapsedDoConstruct(const Fortran::parser::DoConstruct &);
+
 /// Checks whether the current insertion point is inside OpenACC compute
 /// construct.
 bool isInsideOpenACCComputeConstruct(fir::FirOpBuilder &);
diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index a7d83c472b9c5..605c163d07dab 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -2501,6 +2501,36 @@ class FirConverter : public Fortran::lower::AbstractConverter {
     Fortran::lower::pft::Evaluation &eval = getEval();
     bool unstructuredContext = eval.lowerAsUnstructured();
 
+    // If this do-loop was absorbed by a collapse clause on a parent acc.loop,
+    // skip generating any loop — just lower the body.  The IV value is
+    // already available from the parent acc.loop's block argument.
+    if (Fortran::lower::isCollapsedDoConstruct(doConstruct)) {
+      auto iter = eval.getNestedEvaluations().begin();
+      for (auto end = --eval.getNestedEvaluations().end(); iter != end; ++iter)
+        genFIR(*iter, unstructuredContext);
+      return;
+    }
+
+    // If this do-loop was absorbed by a collapse clause on a parent acc.loop,
+    // skip generating any loop — just lower the body.  The IV value is
+    // already available from the parent acc.loop's block argument.
+    if (Fortran::lower::isCollapsedDoConstruct(doConstruct)) {
+      auto iter = eval.getNestedEvaluations().begin();
+      for (auto end = --eval.getNestedEvaluations().end(); iter != end; ++iter)
+        genFIR(*iter, unstructuredContext);
+      return;
+    }
+
+    // If this do-loop was absorbed by a collapse clause on a parent acc.loop,
+    // skip generating any loop — just lower the body.  The IV value is
+    // already available from the parent acc.loop's block argument.
+    if (Fortran::lower::isCollapsedDoConstruct(doConstruct)) {
+      auto iter = eval.getNestedEvaluations().begin();
+      for (auto end = --eval.getNestedEvaluations().end(); iter != end; ++iter)
+        genFIR(*iter, unstructuredContext);
+      return;
+    }
+
     // Loops with induction variables inside OpenACC compute constructs
     // need special handling to ensure that the IVs are privatized.
     if (Fortran::lower::isInsideOpenACCComputeConstruct(*builder)) {
@@ -3649,11 +3679,8 @@ class FirConverter : public Fortran::lower::AbstractConverter {
 
       if (curEval->lowerAsStructured()) {
         curEval = &curEval->getFirstNestedEvaluation();
-        for (uint64_t i = 1; i < loopCount; i++) {
-          if (!curEval->hasNestedEvaluations())
-            break;
+        for (uint64_t i = 1; i < loopCount; i++)
           curEval = &*std::next(curEval->getNestedEvaluations().begin());
-        }
       }
     }
 
@@ -3680,8 +3707,9 @@ class FirConverter : public Fortran::lower::AbstractConverter {
           }
           prologue.push_back(&*it);
         }
-        if (!childLoop)
-          break;
+        // Semantics guarantees collapseDepth does not exceed nest depth
+        // so childLoop must be found here.
+        assert(childLoop && "Expected inner DoConstruct for collapse");
         parent = childLoop;
         innermostLoopEval = childLoop;
       }
@@ -3702,19 +3730,16 @@ class FirConverter : public Fortran::lower::AbstractConverter {
       sink(prologue);
 
       // Lower innermost loop body, skipping sunk
-      if (innermostLoopEval && innermostLoopEval->hasNestedEvaluations())
-        for (Fortran::lower::pft::Evaluation &e :
-             innermostLoopEval->getNestedEvaluations())
-          if (!sunk.contains(&e))
-            genFIR(e);
+      for (Fortran::lower::pft::Evaluation &e :
+           innermostLoopEval->getNestedEvaluations())
+        if (!sunk.contains(&e))
+          genFIR(e);
 
       sink(epilogue);
     } else {
       // Normal lowering
-      if (curEval->hasNestedEvaluations())
-        for (Fortran::lower::pft::Evaluation &e :
-             curEval->getNestedEvaluations())
-          genFIR(e);
+      for (Fortran::lower::pft::Evaluation &e : curEval->getNestedEvaluations())
+        genFIR(e);
     }
     if (!isCacheConstruct)
       localSymbols.popScope();
diff --git a/flang/lib/Lower/OpenACC.cpp b/flang/lib/Lower/OpenACC.cpp
index 5a2fab48accd2..976754b979713 100644
--- a/flang/lib/Lower/OpenACC.cpp
+++ b/flang/lib/Lower/OpenACC.cpp
@@ -1545,6 +1545,7 @@ static void visitLoopControl(
       if (!innerDo)
         break; // No deeper loop; stop collecting collapsed bounds.
 
+      Fortran::lower::markDoConstructAsCollapsed(*innerDo);
       loopControl = &*innerDo->GetLoopControl();
       mlir::Location loc =
           converter.genLocation(Fortran::parser::FindSourceLocation(*innerDo));
@@ -4687,6 +4688,19 @@ bool Fortran::lower::isInOpenACCLoop(fir::FirOpBuilder &builder) {
   return false;
 }
 
+static llvm::SmallPtrSet<const Fortran::parser::DoConstruct *, 8>
+    collapsedDoConstructs;
+
+void Fortran::lower::markDoConstructAsCollapsed(
+    const Fortran::parser::DoConstruct &dc) {
+  collapsedDoConstructs.insert(&dc);
+}
+
+bool Fortran::lower::isCollapsedDoConstruct(
+    const Fortran::parser::DoConstruct &dc) {
+  return collapsedDoConstructs.contains(&dc);
+}
+
 bool Fortran::lower::isInsideOpenACCComputeConstruct(
     fir::FirOpBuilder &builder) {
   return mlir::isa_and_nonnull<ACC_COMPUTE_CONSTRUCT_OPS>(
diff --git a/flang/test/Lower/OpenACC/acc-loop-collapse-force-lowering.f90 b/flang/test/Lower/OpenACC/acc-loop-collapse-force-lowering.f90
index ca932c1b159ba..d61f0ed5587cc 100644
--- a/flang/test/Lower/OpenACC/acc-loop-collapse-force-lowering.f90
+++ b/flang/test/Lower/OpenACC/acc-loop-collapse-force-lowering.f90
@@ -1,7 +1,7 @@
 ! RUN: bbc -fopenacc -emit-hlfir %s -o - | FileCheck %s
 
-! Verify collapse(force:2) sinks prologue (between loops) and epilogue (after inner loop)
-! into the acc.loop region body.
+! Verify collapse(force:2) sinks prologue/epilogue and eliminates
+! the inner loop (absorbed by the outer collapse).
 
 subroutine collapse_force_sink(n, m)
   integer, intent(in) :: n, m
@@ -22,20 +22,13 @@ subroutine collapse_force_sink(n, m)
 
 ! CHECK: func.func @_QPcollapse_force_sink(
 ! CHECK: acc.parallel
-! Ensure outer acc.loop is combined(parallel)
 ! CHECK: acc.loop combined(parallel)
-! Prologue: constant 4.2 and an assign before inner loop
 ! CHECK: arith.constant 4.200000e+00
 ! CHECK: hlfir.assign
-! Inner loop and its body include 2.0 add and an assign
-! CHECK: acc.loop
 ! CHECK: arith.constant 2.000000e+00
 ! CHECK: arith.addf
 ! CHECK: hlfir.assign
-! Epilogue: constant 7.3 and an assign after inner loop
 ! CHECK: arith.constant 7.300000e+00
 ! CHECK: hlfir.assign
-! And the outer acc.loop has collapse = [2]
 ! CHECK: } attributes {collapse = [2]
-
-
+! CHECK-NOT: acc.loop
diff --git a/flang/test/Lower/OpenACC/acc-loop-collapse-force-non-tightly-nested.f90 b/flang/test/Lower/OpenACC/acc-loop-collapse-force-non-tightly-nested.f90
index 9f21c1d61b91a..5eed4dc5a5c76 100644
--- a/flang/test/Lower/OpenACC/acc-loop-collapse-force-non-tightly-nested.f90
+++ b/flang/test/Lower/OpenACC/acc-loop-collapse-force-non-tightly-nested.f90
@@ -1,39 +1,28 @@
 ! RUN: bbc -fopenacc -emit-hlfir %s -o - | FileCheck %s
 
-! Verify that collapse(force:4) on non-tightly nested loops (with code
-! between loops and CYCLE statements) does not crash the compiler.
+! Verify that collapse(force:N) on non-tightly nested loops (with code
+! between loops) does not crash the compiler.
 
-subroutine collapse_force_non_tight(a, sr, nq, np, desc)
+subroutine collapse_force_non_tight(a, n)
   implicit none
-  integer, intent(in) :: nq, np
-  integer, intent(in) :: desc(3)
-  real, intent(inout) :: a(np * nq)
-  real, intent(in) :: sr(max(np, nq))
-  integer :: j1, j2, jcol, j
-  integer :: i1, i2, irow, i
+  integer, intent(in) :: n
+  real, intent(inout) :: a(n, n, n)
+  integer :: i, j, k
+  real :: tmp
 
-!$acc parallel loop collapse(force:4) present(a, sr)
-  do j1 = 1, nq, desc(1)
-    do j2 = 1, desc(1)
-      jcol = j1 + j2 - 1
-      if (jcol > nq) cycle
-      j = j1 + j2 - 1
-
-      do i1 = 1, np, desc(2)
-        do i2 = 1, desc(2)
-          irow = i1 + i2 - 1
-          if (irow > np) cycle
-          i = i1 + i2 - 1
-
-          a(irow + (jcol - 1) * np) = a(irow + (jcol - 1) * np) * sr(i) * sr(j)
-        end do
+  !$acc parallel loop collapse(force:3) copy(a)
+  do i = 1, n
+    do j = 1, n
+      tmp = real(i + j)
+      do k = 1, n
+        a(i, j, k) = tmp + real(k)
       end do
     end do
   end do
-!$acc end parallel loop
+  !$acc end parallel loop
 end subroutine
 
 ! CHECK: func.func @_QPcollapse_force_non_tight
 ! CHECK: acc.parallel
 ! CHECK: acc.loop
-! CHECK: collapse = [4]
+! CHECK: collapse = [3]

>From 87845af1b9082e68373ae08d55f03f7d61df131c Mon Sep 17 00:00:00 2001
From: Kazuaki Matsumura <kmatsumura at nvidia.com>
Date: Tue, 14 Apr 2026 17:10:33 -0700
Subject: [PATCH 5/5] [Flang][OpenACC] Restore crash guards for
 collapse(force:N) with CYCLE

The hasNestedEvaluations() guards in Bridge.cpp were lost during the
DoConstruct tracking commit.  Restore them to fix the crash on
collapse(force:N) with CYCLE statements (unstructured lowering).

Made-with: Cursor
---
 flang/lib/Lower/Bridge.cpp | 25 +++++++++++++++----------
 1 file changed, 15 insertions(+), 10 deletions(-)

diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 605c163d07dab..9e361ad15c5ce 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -3679,8 +3679,11 @@ class FirConverter : public Fortran::lower::AbstractConverter {
 
       if (curEval->lowerAsStructured()) {
         curEval = &curEval->getFirstNestedEvaluation();
-        for (uint64_t i = 1; i < loopCount; i++)
+        for (uint64_t i = 1; i < loopCount; i++) {
+          if (!curEval->hasNestedEvaluations())
+            break;
           curEval = &*std::next(curEval->getNestedEvaluations().begin());
+        }
       }
     }
 
@@ -3707,9 +3710,8 @@ class FirConverter : public Fortran::lower::AbstractConverter {
           }
           prologue.push_back(&*it);
         }
-        // Semantics guarantees collapseDepth does not exceed nest depth
-        // so childLoop must be found here.
-        assert(childLoop && "Expected inner DoConstruct for collapse");
+        if (!childLoop)
+          break;
         parent = childLoop;
         innermostLoopEval = childLoop;
       }
@@ -3730,16 +3732,19 @@ class FirConverter : public Fortran::lower::AbstractConverter {
       sink(prologue);
 
       // Lower innermost loop body, skipping sunk
-      for (Fortran::lower::pft::Evaluation &e :
-           innermostLoopEval->getNestedEvaluations())
-        if (!sunk.contains(&e))
-          genFIR(e);
+      if (innermostLoopEval && innermostLoopEval->hasNestedEvaluations())
+        for (Fortran::lower::pft::Evaluation &e :
+             innermostLoopEval->getNestedEvaluations())
+          if (!sunk.contains(&e))
+            genFIR(e);
 
       sink(epilogue);
     } else {
       // Normal lowering
-      for (Fortran::lower::pft::Evaluation &e : curEval->getNestedEvaluations())
-        genFIR(e);
+      if (curEval->hasNestedEvaluations())
+        for (Fortran::lower::pft::Evaluation &e :
+             curEval->getNestedEvaluations())
+          genFIR(e);
     }
     if (!isCacheConstruct)
       localSymbols.popScope();



More information about the flang-commits mailing list