[flang-commits] [flang] [flang][OpenACC] Skip non-loop evaluations when descending a collapsed/tiled DO nest (PR #223579)
via flang-commits
flang-commits at lists.llvm.org
Mon Sep 14 19:02:41 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-flang-fir-hlfir
Author: Ron Green [NVIDIA] (ronGreenNV)
<details>
<summary>Changes</summary>
## 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.
---
Full diff: https://github.com/llvm/llvm-project/pull/223579.diff
2 Files Affected:
- (modified) flang/lib/Lower/Bridge.cpp (+31-6)
- (added) flang/test/Lower/OpenACC/acc-loop-collapse-directive-between-loops.f90 (+55)
``````````diff
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])
``````````
</details>
https://github.com/llvm/llvm-project/pull/223579
More information about the flang-commits
mailing list