[flang-commits] [flang] [flang][OpenACC] Skip non-loop evaluations when descending a collapsed/tiled DO nest (PR #223579)
Ron Green [NVIDIA] via flang-commits
flang-commits at lists.llvm.org
Mon Sep 14 19:02:04 PDT 2026
https://github.com/ronGreenNV created https://github.com/llvm/llvm-project/pull/223579
## Summary
- The collapse/tile loop-nest descent in `Bridge.cpp` (used for OpenACC `collapse`/`tile` and CUDA Fortran `!$cuf kernel do`) assumed each loop level's nested evaluations have the fixed shape `{NonLabelDoStmt, DoConstruct, EndDoStmt}` and unconditionally advanced to the second nested evaluation to find the next inner `DO`.
- A compiler directive (e.g. `!DIR$ IVDEP`) placed between loop levels inserts an extra sibling evaluation there, so the descent landed on the directive instead of the inner `DoConstruct` and lowered the directive's (empty) nested evaluations as the loop body — silently dropping the real body with no diagnostic.
- Adds a helper that searches a level's nested evaluations for the actual `DoConstruct`, matching the approach already used by `visitLoopControl` in `OpenACC.cpp`, and uses it at all three affected descent sites (OpenACC collapse/tile, and the two CUDA Fortran kernel-do sites).
## Test plan
- New lit test `flang/test/Lower/OpenACC/acc-loop-collapse-directive-between-loops.f90` covering `collapse(2)` with `!DIR$ IVDEP` and `collapse(3)` with `!DIR$ NOVECTOR` between loop levels.
- Verified the new test fails without the fix (directive absorbed as loop body, no `hlfir.assign` for the real body) and passes with it.
- Existing `acc-loop-collapse-force-lowering.f90`, `acc-loop-collapse-force-non-tightly-nested.f90`, `do-concurrent-collapse.f90`, `cuda-kernel-loop-directive.cuf`, `cuda-kernel-do-reduction.cuf` all still pass.
>From 07b89605188b53533d96d5836dbe66344f6b33dc Mon Sep 17 00:00:00 2001
From: Ronald Green <rogreen at nvidia.com>
Date: Mon, 14 Sep 2026 18:45:22 -0700
Subject: [PATCH] [flang][OpenACC] Skip non-loop evaluations when descending a
collapsed/tiled DO nest
The collapse/tile loop-nest descent in Bridge.cpp (used for OpenACC
collapse/tile and CUDA Fortran `!$cuf kernel do`) assumed each loop
level's nested evaluations have the fixed shape
{NonLabelDoStmt, DoConstruct, EndDoStmt} and unconditionally advanced
to the second nested evaluation to find the next inner DO. A compiler
directive (e.g. !DIR$ IVDEP) placed between loop levels inserts an
extra sibling evaluation there, so the descent landed on the directive
instead of the inner DoConstruct and lowered the directive's (empty)
nested evaluations as the loop body -- silently dropping the real
body with no diagnostic.
Add a helper that searches a level's nested evaluations for the actual
DoConstruct, matching the approach already used by visitLoopControl in
OpenACC.cpp, and use it at all three affected descent sites.
---
flang/lib/Lower/Bridge.cpp | 37 +++++++++++--
...-loop-collapse-directive-between-loops.f90 | 55 +++++++++++++++++++
2 files changed, 86 insertions(+), 6 deletions(-)
create mode 100644 flang/test/Lower/OpenACC/acc-loop-collapse-directive-between-loops.f90
diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 4b70fbe9ea106..2b5bdeab6d118 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -502,6 +502,20 @@ class TypeInfoConverter {
using IncrementLoopNestInfo = llvm::SmallVector<IncrementLoopInfo, 8>;
} // namespace
+/// Find the first nested DoConstruct evaluation directly under \p eval,
+/// skipping over any other sibling evaluations (e.g. a CompilerDirective
+/// such as !DIR$ IVDEP) that may appear between loop levels of a collapsed
+/// or tiled loop nest. Returns nullptr if none is found.
+static Fortran::lower::pft::Evaluation *
+findNestedDoConstructEvaluation(Fortran::lower::pft::Evaluation &eval) {
+ if (!eval.hasNestedEvaluations())
+ return nullptr;
+ for (Fortran::lower::pft::Evaluation &child : eval.getNestedEvaluations())
+ if (child.getIf<Fortran::parser::DoConstruct>())
+ return &child;
+ return nullptr;
+}
+
//===----------------------------------------------------------------------===//
// FirConverter
//===----------------------------------------------------------------------===//
@@ -3638,9 +3652,11 @@ class FirConverter : public Fortran::lower::AbstractConverter {
const auto *outerDo = curEval->getIf<Fortran::parser::DoConstruct>();
if (!(outerDo && outerDo->IsDoConcurrent()))
for (uint64_t i = 1; i < loopCount; i++) {
- if (!curEval->hasNestedEvaluations())
+ Fortran::lower::pft::Evaluation *nextDo =
+ findNestedDoConstructEvaluation(*curEval);
+ if (!nextDo)
break;
- curEval = &*std::next(curEval->getNestedEvaluations().begin());
+ curEval = nextDo;
}
}
}
@@ -3983,8 +3999,12 @@ class FirConverter : public Fortran::lower::AbstractConverter {
ivTypes.push_back(idxTy);
ivLocs.push_back(crtLoc);
- if (i < nestedLoops - 1)
- loopEval = &*std::next(loopEval->getNestedEvaluations().begin());
+ if (i < nestedLoops - 1) {
+ Fortran::lower::pft::Evaluation *nextDo =
+ findNestedDoConstructEvaluation(*loopEval);
+ assert(nextDo && "expected a nested DO CONSTRUCT");
+ loopEval = nextDo;
+ }
}
}
@@ -4012,8 +4032,13 @@ class FirConverter : public Fortran::lower::AbstractConverter {
if (crtEval->lowerAsStructured()) {
crtEval = &crtEval->getFirstNestedEvaluation();
if (!outerDoConstruct->IsDoConcurrent())
- for (int64_t i = 1; i < nestedLoops; i++)
- crtEval = &*std::next(crtEval->getNestedEvaluations().begin());
+ for (int64_t i = 1; i < nestedLoops; i++) {
+ Fortran::lower::pft::Evaluation *nextDo =
+ findNestedDoConstructEvaluation(*crtEval);
+ if (!nextDo)
+ break;
+ crtEval = nextDo;
+ }
}
// Generate loop body
diff --git a/flang/test/Lower/OpenACC/acc-loop-collapse-directive-between-loops.f90 b/flang/test/Lower/OpenACC/acc-loop-collapse-directive-between-loops.f90
new file mode 100644
index 0000000000000..89f551ae2492f
--- /dev/null
+++ b/flang/test/Lower/OpenACC/acc-loop-collapse-directive-between-loops.f90
@@ -0,0 +1,55 @@
+! RUN: bbc -fopenacc -emit-hlfir %s -o - | FileCheck %s
+
+! Verify that a compiler directive (e.g. !DIR$ IVDEP) appearing between the
+! levels of a collapsed loop nest does not get mistaken for the next nested
+! DO CONSTRUCT. The directive is an extra sibling evaluation between the
+! NonLabelDoStmt and the inner DoConstruct; the collapse descent must skip
+! over it rather than absorbing it as the loop body.
+
+subroutine collapse2_directive_between_loops(n, a)
+ integer, intent(in) :: n
+ integer :: a(n,n)
+ integer :: i, j
+
+ !$acc parallel loop collapse(2) copy(a)
+ do i = 1, n
+!DIR$ IVDEP
+ do j = 1, n
+ a(j,i) = 1
+ end do
+ end do
+ !$acc end parallel loop
+end subroutine
+
+! CHECK-LABEL: func.func @_QPcollapse2_directive_between_loops(
+! CHECK: acc.parallel
+! CHECK: acc.loop combined(parallel)
+! CHECK: hlfir.designate
+! CHECK: hlfir.assign
+! CHECK: acc.yield
+! CHECK: collapse([2])
+
+subroutine collapse3_directive_between_loops(n, a)
+ integer, intent(in) :: n
+ integer :: a(n,n,n)
+ integer :: i, j, k
+
+ !$acc parallel loop collapse(3) copy(a)
+ do i = 1, n
+!DIR$ NOVECTOR
+ do j = 1, n
+ do k = 1, n
+ a(k,j,i) = 1
+ end do
+ end do
+ end do
+ !$acc end parallel loop
+end subroutine
+
+! CHECK-LABEL: func.func @_QPcollapse3_directive_between_loops(
+! CHECK: acc.parallel
+! CHECK: acc.loop combined(parallel)
+! CHECK: hlfir.designate
+! CHECK: hlfir.assign
+! CHECK: acc.yield
+! CHECK: collapse([3])
More information about the flang-commits
mailing list