[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