[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
Thu Apr 9 15:13:07 PDT 2026
https://github.com/khaki3 created https://github.com/llvm/llvm-project/pull/191310
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
>From a6cd7165663cec70bd13a5977fefc617f111cac8 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/3] [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 63867fab57678..4b144afaa9786 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -3641,8 +3641,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());
+ }
}
}
@@ -3669,9 +3672,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;
}
@@ -3692,16 +3694,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();
@@ -6273,29 +6278,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 2154f38dca568..5148b085d4b92 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());
@@ -1895,6 +1896,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 4658acc237e9b9d4bbc8928bc0ac90b0bac0971b 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/3] [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 80ad08f42b71ec34ee13c6512205102ec6cc8abf 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/3] 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 4b144afaa9786..1e096fd5a1ead 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -6278,29 +6278,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.
More information about the flang-commits
mailing list