[flang-commits] [flang] [flang][OpenACC] Don't emit unstructured-loop TODO for wrappable loops (PR #222260)
Kareem Ergawy via flang-commits
flang-commits at lists.llvm.org
Wed Sep 9 01:12:53 PDT 2026
https://github.com/ergawy updated https://github.com/llvm/llvm-project/pull/222260
>From c34bd4162044b4104312cc7b0c25d029c99d5049 Mon Sep 17 00:00:00 2001
From: ergawy <kareem.ergawy at gmail.com>
Date: Wed, 9 Sep 2026 01:11:47 -0700
Subject: [PATCH] [flang][OpenACC] Don't emit unstructured-loop TODO for
wrappable loops
`genOpenACCLoopFromDoConstruct` raised an NYI for any unstructured DO inside `acc kernels`. That check ran before the wrapping in `genFIR(DoConstruct)`, so a loop whose CFG is self-contained aborted lowering even though the fall-back path would have wrapped it in an `scf.execute_region`.
Skip the TODO when `isWrappableConstruct()` accepts the loop. Such a loop is not attached to a directive; it is only nested inside the kernels region.
Correspondingly, a DO loop sitting directly in an `acc kernels` body is no longer wrappable, since it is the loop the directive parallelizes and hiding its CFG would leave the kernels region with nothing to partition.
---
flang/lib/Lower/OpenACC.cpp | 8 +++-
flang/lib/Lower/PFTBuilder.cpp | 39 +++++++++++++++++--
.../Todo/do-loops-to-acc-loops-todo.f90 | 27 +------------
flang/test/Lower/OpenACC/acc-unstructured.f90 | 27 +++++++++++++
4 files changed, 72 insertions(+), 29 deletions(-)
diff --git a/flang/lib/Lower/OpenACC.cpp b/flang/lib/Lower/OpenACC.cpp
index d883478e49375..fb5a84ac6aaef 100644
--- a/flang/lib/Lower/OpenACC.cpp
+++ b/flang/lib/Lower/OpenACC.cpp
@@ -5544,10 +5544,16 @@ mlir::Operation *Fortran::lower::genOpenACCLoopFromDoConstruct(
// privatizing the induction variable, the loop may not execute correctly.
// Only do this for `acc kernels` because in `acc parallel`, scalars end
// up as implicitly firstprivate.
+ //
+ // Unstructured constructs that are safe to wrap should not emit the TODO. A
+ // wrappable loop that reaches this condition is a loop that is NOT attached
+ // to any OpenACC directives (e.g. `kernels` ops), it is just nested inside
+ // the kernels region.
if (eval.lowerAsUnstructured()) {
if (mlir::isa_and_present<mlir::acc::KernelsOp>(
mlir::acc::getEnclosingComputeOp(
- converter.getFirOpBuilder().getRegion())))
+ converter.getFirOpBuilder().getRegion())) &&
+ !Fortran::lower::pft::isWrappableConstruct(eval, semanticsContext))
TODO(converter.getCurrentLocation(),
"unstructured do loop in acc kernels");
return nullptr;
diff --git a/flang/lib/Lower/PFTBuilder.cpp b/flang/lib/Lower/PFTBuilder.cpp
index fad47e752d958..c37a75e90fe8e 100644
--- a/flang/lib/Lower/PFTBuilder.cpp
+++ b/flang/lib/Lower/PFTBuilder.cpp
@@ -2575,6 +2575,36 @@ static bool isAssociatedLoop(const DoConstructChain &chain,
return std::distance(chain.begin(), it) < n;
}
+/// True if \p eval is a DoConstruct sitting directly in the body of an
+/// `!$acc kernels` region, i.e. attached to the directive itself rather than
+/// nested inside another loop.
+///
+/// Such a loop is the one the directive parallelizes, so it must keep its
+/// Fortran loop structure: wrapping its CFG in an scf.execute_region would
+/// leave the kernels region with nothing to partition. Loops nested deeper in
+/// the region are unaffected — hiding an inner loop's CFG still leaves the
+/// enclosing loop available to the directive.
+static bool isAccKernelsBody(const Fortran::lower::pft::Evaluation &eval) {
+ if (!eval.isA<parser::DoConstruct>())
+ return false;
+
+ const Fortran::lower::pft::Evaluation *p = eval.parentConstruct;
+ if (!p)
+ return false;
+
+ const auto *acc = p->getIf<parser::OpenACCConstruct>();
+ if (!acc)
+ return false;
+
+ const auto *block = std::get_if<parser::OpenACCBlockConstruct>(&acc->u);
+ if (!block)
+ return false;
+
+ const auto &beginDir = std::get<parser::AccBeginBlockDirective>(block->t);
+ return std::get<parser::AccBlockDirective>(beginDir.t).v ==
+ llvm::acc::Directive::ACCD_kernels;
+}
+
/// True if \p eval is a DoConstruct attached to an enclosing OpenACC loop.
static bool isAccLoopBody(const Fortran::lower::pft::Evaluation &eval) {
DoConstructChain chain;
@@ -2654,8 +2684,11 @@ bool Fortran::lower::pft::isWrappableConstruct(
//
// Note: Loops attached to OpenACC/OpenMP constructs are not wrappable since
// the directive lowering (e.g. genOpenACCLoopFromDoConstruct) takes over
- // code-gen when a DoConstruct is attached to such a directive. We might
- // extend wrapping to such unstructured loops later on if needed.
+ // code-gen when a DoConstruct is attached to such a directive. The same
+ // applies to a loop directly in an `!$acc kernels` body: it is the loop the
+ // directive parallelizes. We might extend wrapping to such unstructured
+ // loops later on if needed.
return !hasUnwrappableInternals(eval) && !hasIncomingBranch(eval) &&
- !isAccLoopBody(eval) && !isOmpLoopBody(eval, semaCtx);
+ !isAccLoopBody(eval) && !isAccKernelsBody(eval) &&
+ !isOmpLoopBody(eval, semaCtx);
}
diff --git a/flang/test/Lower/OpenACC/Todo/do-loops-to-acc-loops-todo.f90 b/flang/test/Lower/OpenACC/Todo/do-loops-to-acc-loops-todo.f90
index d59091a975822..d206ca8d0f1e4 100644
--- a/flang/test/Lower/OpenACC/Todo/do-loops-to-acc-loops-todo.f90
+++ b/flang/test/Lower/OpenACC/Todo/do-loops-to-acc-loops-todo.f90
@@ -1,7 +1,6 @@
! RUN: split-file %s %t
! RUN: %not_todo_cmd bbc -fopenacc -emit-hlfir %t/do_loop_with_cycle_goto.f90 -o - 2>&1 | FileCheck %s --check-prefix=CHECK2
! RUN: %not_todo_cmd bbc -fopenacc -emit-hlfir %t/nested_goto_loop.f90 -o - 2>&1 | FileCheck %s --check-prefix=CHECK3
-! RUN: %not_todo_cmd bbc -fopenacc -emit-hlfir %t/nested_loop_with_inner_goto.f90 -o - 2>&1 | FileCheck %s --check-prefix=CHECK4
! RUN: %not_todo_cmd bbc -fopenacc -emit-hlfir %t/collapse_lt.f90 -o - 2>&1 | FileCheck %s --check-prefix=CHECK7
! RUN: %not_todo_cmd bbc -fopenacc -emit-hlfir %t/collapse_gt.f90 -o - 2>&1 | FileCheck %s --check-prefix=CHECK8
! RUN: %not_todo_cmd bbc -fopenacc -emit-hlfir %t/collapse_nested.f90 -o - 2>&1 | FileCheck %s --check-prefix=CHECK6
@@ -14,6 +13,8 @@ subroutine do_loop_with_cycle_goto()
real, dimension(n) :: a, b
! Do loop with cycle and goto - unstructured control flow is not converted.
+ ! The loop is directly attached to the `acc kernels` directive, so it is not
+ ! wrapped in an scf.execute_region either.
!$acc kernels
do i = 1, n
if (i == 3) cycle
@@ -50,30 +51,6 @@ subroutine nested_goto_loop()
end subroutine
-//--- nested_loop_with_inner_goto.f90
-
-subroutine nested_loop_with_inner_goto()
- integer :: ii = 0, jj = 0
- integer, parameter :: nn = 3
- real, dimension(nn, nn) :: aa
-
- aa = -1
-
- ! Nested loop with goto from inner loop - unstructured control flow is not converted.
- !$acc kernels
- do ii = 1, nn
- do jj = 1, nn
- if (jj > 1) goto 300
- aa(jj, ii) = 1337
- end do
- 300 continue
- end do
- !$acc end kernels
-
-! CHECK4: not yet implemented: unstructured do loop in acc kernels
-
-end subroutine
-
//--- collapse_lt.f90
! collapse(2) over a 3-control do concurrent: collapse < control count (N < C).
diff --git a/flang/test/Lower/OpenACC/acc-unstructured.f90 b/flang/test/Lower/OpenACC/acc-unstructured.f90
index e4fb5b671f931..cbb27d74cc96c 100644
--- a/flang/test/Lower/OpenACC/acc-unstructured.f90
+++ b/flang/test/Lower/OpenACC/acc-unstructured.f90
@@ -536,3 +536,30 @@ subroutine test_unstructured_parallel_loop_collapse3_stop(a)
! CHECK-LABEL: func.func @_QPtest_unstructured_parallel_loop_collapse3_stop
! CHECK: acc.parallel combined(loop)
! CHECK: acc.loop combined(parallel)
+
+! Nested DO loops inside `!$acc kernels` where the inner loop branches to its
+! own exit. Only the inner loop is unstructured, so the outer one still lowers
+! as a structured acc.loop and the inner one is wrapped.
+subroutine nested_loop_with_inner_goto()
+ integer :: ii = 0, jj = 0
+ integer, parameter :: nn = 3
+ real, dimension(nn, nn) :: aa
+
+ aa = -1
+
+ !$acc kernels
+ do ii = 1, nn
+ do jj = 1, nn
+ if (jj > 1) goto 300
+ aa(jj, ii) = 1337
+ end do
+ 300 continue
+ end do
+ !$acc end kernels
+end subroutine
+
+! CHECK-LABEL: func.func @_QPnested_loop_with_inner_goto
+! CHECK: acc.kernels
+! CHECK: acc.loop private({{.*}}) control({{.*}}) = ({{.*}}) to ({{.*}}) step ({{.*}}) {
+! CHECK: scf.execute_region
+! CHECK: scf.yield
More information about the flang-commits
mailing list