[flang-commits] [flang] [flang][OpenACC] Generalize cross-region GOTO exit handling for all ACC region ops (PR #187613)

via flang-commits flang-commits at lists.llvm.org
Mon Mar 23 18:04:23 PDT 2026


https://github.com/khaki3 updated https://github.com/llvm/llvm-project/pull/187613

>From e3a79700ddc9a0e64e0ef9d85569060e3e626986 Mon Sep 17 00:00:00 2001
From: Kazuaki Matsumura <kmatsumura at nvidia.com>
Date: Thu, 19 Mar 2026 17:14:13 -0700
Subject: [PATCH 1/8] [flang] Fix crash when GoTo exits acc.loop region

When a GoTo inside an $Acc Loop Seq targets a label outside the
acc.loop's MLIR region, the lowering generated an illegal cross-region
cf.br. This caused runRegionDCE's recursive propagateLiveness to enter
a cyclic traversal through the dangling reference, overflowing the
compiler stack.

Fix by generating acc.yield (proper region exit) instead of cf.br when
the GoTo target block is in a different region than the current one.
This reuses the existing early-exit mechanism already used for RETURN
statements inside acc.loop.

Made-with: Cursor
---
 flang/lib/Lower/Bridge.cpp                    | 11 ++++++++-
 .../test/Lower/OpenACC/acc-loop-goto-exit.f90 | 24 +++++++++++++++++++
 2 files changed, 34 insertions(+), 1 deletion(-)
 create mode 100644 flang/test/Lower/OpenACC/acc-loop-goto-exit.f90

diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 7459f814a0d4d..e14912017ae7d 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -6239,7 +6239,16 @@ class FirConverter : public Fortran::lower::AbstractConverter {
     genConstructExitBranch(*getEval().controlSuccessor);
   }
   void genFIR(const Fortran::parser::GotoStmt &) {
-    genConstructExitBranch(*getEval().controlSuccessor);
+    auto &targetEval = *getEval().controlSuccessor;
+    if (Fortran::lower::isInOpenACCLoop(*builder)) {
+      mlir::Block *targetBlock = targetEval.block;
+      mlir::Region *currentRegion = &builder->getRegion();
+      if (targetBlock && targetBlock->getParent() != currentRegion) {
+        mlir::acc::YieldOp::create(*builder, toLocation());
+        return;
+      }
+    }
+    genConstructExitBranch(targetEval);
   }
 
   // Nop statements - No code, or code is generated at the construct level.
diff --git a/flang/test/Lower/OpenACC/acc-loop-goto-exit.f90 b/flang/test/Lower/OpenACC/acc-loop-goto-exit.f90
new file mode 100644
index 0000000000000..88fc30d79c3d1
--- /dev/null
+++ b/flang/test/Lower/OpenACC/acc-loop-goto-exit.f90
@@ -0,0 +1,24 @@
+! Test that GoTo exiting an acc.loop seq region generates acc.yield
+! instead of an illegal cross-region cf.br that would crash the compiler.
+! RUN: bbc -fopenacc -emit-fir %s -o - | FileCheck %s
+
+! CHECK-LABEL: func.func @_QPfoo
+subroutine foo(N, A, B)
+  implicit real*8 (a-h, o-z)
+  !$acc routine gang
+  dimension A(*), B(*)
+  !$acc loop gang vector
+  do 100 i = 1, N
+  ! CHECK: acc.loop gang vector
+  ! CHECK: acc.loop
+  !$acc loop seq
+    do 10 j = 1, 1000
+      if (A(i) .gt. B(i)) goto 20
+10  continue
+  ! The GoTo crossing the acc.loop region boundary must generate
+  ! acc.yield (not cf.br) to properly exit the inner acc.loop.
+  ! CHECK: acc.yield
+  ! CHECK: acc.yield
+20  B(i) = A(i)
+100 continue
+end subroutine

>From cea5531c067db0da5dd94e51c913eb70aa339b02 Mon Sep 17 00:00:00 2001
From: Kazuaki Matsumura <kmatsumura at nvidia.com>
Date: Thu, 19 Mar 2026 22:03:36 -0700
Subject: [PATCH 2/8] [flang] Update lit test: use -emit-hlfir and fix CHECK
 lines

Made-with: Cursor
---
 flang/test/Lower/OpenACC/acc-loop-goto-exit.f90 | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/flang/test/Lower/OpenACC/acc-loop-goto-exit.f90 b/flang/test/Lower/OpenACC/acc-loop-goto-exit.f90
index 88fc30d79c3d1..fc7b036b0cef1 100644
--- a/flang/test/Lower/OpenACC/acc-loop-goto-exit.f90
+++ b/flang/test/Lower/OpenACC/acc-loop-goto-exit.f90
@@ -1,6 +1,6 @@
 ! Test that GoTo exiting an acc.loop seq region generates acc.yield
 ! instead of an illegal cross-region cf.br that would crash the compiler.
-! RUN: bbc -fopenacc -emit-fir %s -o - | FileCheck %s
+! RUN: bbc -fopenacc -emit-hlfir %s -o - | FileCheck %s
 
 ! CHECK-LABEL: func.func @_QPfoo
 subroutine foo(N, A, B)
@@ -10,15 +10,17 @@ subroutine foo(N, A, B)
   !$acc loop gang vector
   do 100 i = 1, N
   ! CHECK: acc.loop gang vector
-  ! CHECK: acc.loop
+  ! CHECK: acc.loop {{.*}} {
   !$acc loop seq
     do 10 j = 1, 1000
       if (A(i) .gt. B(i)) goto 20
 10  continue
   ! The GoTo crossing the acc.loop region boundary must generate
-  ! acc.yield (not cf.br) to properly exit the inner acc.loop.
+  ! acc.yield to properly exit the inner acc.loop, not an illegal
+  ! cross-region cf.br that would crash the compiler.
   ! CHECK: acc.yield
   ! CHECK: acc.yield
+  ! CHECK: }
 20  B(i) = A(i)
 100 continue
 end subroutine

>From 4947b54724437e8d730ccc4aed800bb5723cf312 Mon Sep 17 00:00:00 2001
From: Kazuaki Matsumura <kmatsumura at nvidia.com>
Date: Thu, 19 Mar 2026 22:10:45 -0700
Subject: [PATCH 3/8] [flang] Improve lit test: use -emit-hlfir, check
 seq+unstructured attrs

Made-with: Cursor
---
 flang/test/Lower/OpenACC/acc-loop-goto-exit.f90 | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/flang/test/Lower/OpenACC/acc-loop-goto-exit.f90 b/flang/test/Lower/OpenACC/acc-loop-goto-exit.f90
index fc7b036b0cef1..ff6d7ca4aef25 100644
--- a/flang/test/Lower/OpenACC/acc-loop-goto-exit.f90
+++ b/flang/test/Lower/OpenACC/acc-loop-goto-exit.f90
@@ -9,18 +9,18 @@ subroutine foo(N, A, B)
   dimension A(*), B(*)
   !$acc loop gang vector
   do 100 i = 1, N
-  ! CHECK: acc.loop gang vector
-  ! CHECK: acc.loop {{.*}} {
   !$acc loop seq
     do 10 j = 1, 1000
       if (A(i) .gt. B(i)) goto 20
 10  continue
-  ! The GoTo crossing the acc.loop region boundary must generate
-  ! acc.yield to properly exit the inner acc.loop, not an illegal
-  ! cross-region cf.br that would crash the compiler.
-  ! CHECK: acc.yield
-  ! CHECK: acc.yield
-  ! CHECK: }
 20  B(i) = A(i)
 100 continue
 end subroutine
+
+! Verify the inner acc.loop has seq and unstructured attributes,
+! and that it contains acc.yield (from the GoTo cross-region exit).
+! CHECK: acc.loop gang vector
+! CHECK: acc.loop
+! CHECK: acc.yield
+! CHECK: acc.yield
+! CHECK: } attributes {seq = [#acc.device_type<none>], unstructured}

>From 46296aa745922142caaa0a72e4455b3994b1f4f0 Mon Sep 17 00:00:00 2001
From: Kazuaki Matsumura <kmatsumura at nvidia.com>
Date: Thu, 19 Mar 2026 22:16:23 -0700
Subject: [PATCH 4/8] [flang] Use CHECK-NEXT in lit test for precise matching

Made-with: Cursor
---
 flang/test/Lower/OpenACC/acc-loop-goto-exit.f90 | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/flang/test/Lower/OpenACC/acc-loop-goto-exit.f90 b/flang/test/Lower/OpenACC/acc-loop-goto-exit.f90
index ff6d7ca4aef25..696051b9bb534 100644
--- a/flang/test/Lower/OpenACC/acc-loop-goto-exit.f90
+++ b/flang/test/Lower/OpenACC/acc-loop-goto-exit.f90
@@ -17,10 +17,17 @@ subroutine foo(N, A, B)
 100 continue
 end subroutine
 
-! Verify the inner acc.loop has seq and unstructured attributes,
-! and that it contains acc.yield (from the GoTo cross-region exit).
+! Verify the inner acc.loop (seq) contains acc.yield for the GoTo exit.
+! The GoTo target is outside the acc.loop region, so it must yield
+! instead of generating an illegal cross-region cf.br.
+
 ! CHECK: acc.loop gang vector
 ! CHECK: acc.loop
+! The GoTo comparison and branch:
+! CHECK: arith.cmpf ogt
+! CHECK-NEXT: cf.cond_br %{{.*}}, ^[[EXIT:bb[0-9]+]], ^
+! CHECK-NEXT: ^[[EXIT]]:
+! CHECK-NEXT: acc.yield
+! Normal loop end yield and closing:
 ! CHECK: acc.yield
-! CHECK: acc.yield
-! CHECK: } attributes {seq = [#acc.device_type<none>], unstructured}
+! CHECK-NEXT: } attributes {seq = [#acc.device_type<none>], unstructured}

>From 2015b9ad210c4c8b18d89f394e3d99dc2214abcc Mon Sep 17 00:00:00 2001
From: Kazuaki Matsumura <kmatsumura at nvidia.com>
Date: Fri, 20 Mar 2026 11:53:24 -0700
Subject: [PATCH 5/8] [flang][OpenACC] Generalize cross-region GOTO exit
 handling for all ACC ops

Replace the acc.loop-specific GOTO exit handling in genFIR(GotoStmt)
with a generalized genOpenACCRegionExitBranch helper called from
genBranch. This catches cross-region branches at the lowest level and
handles all ACC region ops (data, parallel, kernels, serial, loop,
host_data) by generating the appropriate terminator (acc.terminator or
acc.yield).

Also fix acc.data creation when the construct has no data clauses but
contains unstructured control flow: skip the early return so the
acc.data region is created and blocks are properly managed.

For GOTOs that exit multiple region levels, a TODO diagnostic is
emitted.

Made-with: Cursor
---
 flang/include/flang/Lower/OpenACC.h           |  8 ++
 flang/lib/Lower/Bridge.cpp                    | 80 +++++++++++++++++--
 flang/lib/Lower/OpenACC.cpp                   | 32 +++++++-
 .../Todo/acc-goto-multi-level-exit.f90        | 42 ++++++++++
 .../test/Lower/OpenACC/acc-loop-goto-exit.f90 | 33 --------
 5 files changed, 154 insertions(+), 41 deletions(-)
 create mode 100644 flang/test/Lower/OpenACC/Todo/acc-goto-multi-level-exit.f90
 delete mode 100644 flang/test/Lower/OpenACC/acc-loop-goto-exit.f90

diff --git a/flang/include/flang/Lower/OpenACC.h b/flang/include/flang/Lower/OpenACC.h
index c2a950f36c5a7..8e0f3da22ba9e 100644
--- a/flang/include/flang/Lower/OpenACC.h
+++ b/flang/include/flang/Lower/OpenACC.h
@@ -138,6 +138,14 @@ void setInsertionPointAfterOpenACCLoopIfInside(fir::FirOpBuilder &);
 
 void genEarlyReturnInOpenACCLoop(fir::FirOpBuilder &, mlir::Location);
 
+/// If \p targetBlock is outside the ACC region containing the current
+/// insertion point, generate the appropriate region terminator
+/// (acc.terminator or acc.yield) instead of a cross-region branch.
+/// Returns true if the exit was handled, false if no ACC region boundary
+/// is crossed.
+bool genOpenACCRegionExitBranch(fir::FirOpBuilder &, mlir::Location,
+                                mlir::Block *targetBlock);
+
 /// Generates an OpenACC loop from a do construct in order to
 /// properly capture the loop bounds, parallelism determination mode,
 /// and to privatize the loop variables.
diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index e14912017ae7d..1b0128c7b2449 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -1609,6 +1609,9 @@ class FirConverter : public Fortran::lower::AbstractConverter {
 
   void genBranch(mlir::Block *targetBlock) {
     assert(targetBlock && "missing unconditional target block");
+    if (Fortran::lower::genOpenACCRegionExitBranch(*builder, toLocation(),
+                                                   targetBlock))
+      return;
     mlir::cf::BranchOp::create(*builder, toLocation(), targetBlock);
   }
 
@@ -3575,6 +3578,19 @@ class FirConverter : public Fortran::lower::AbstractConverter {
 
     if (!isCacheConstruct)
       localSymbols.pushScope();
+    // Allocate exit selector for GOTO jump table if the construct is
+    // unstructured (may contain GOTOs that exit the ACC region).
+    bool needsExitSelector = getEval().lowerAsUnstructured();
+    if (needsExitSelector) {
+      AccRegionExitInfo exitInfo;
+      exitInfo.selector = builder->createTemporary(
+          toLocation(), builder->getI32Type());
+      mlir::Value zero = builder->createIntegerConstant(
+          toLocation(), builder->getI32Type(), 0);
+      fir::StoreOp::create(*builder, toLocation(), zero, exitInfo.selector);
+      accRegionExitStack.push_back(std::move(exitInfo));
+    }
+
     mlir::Value exitCond = genOpenACCConstruct(
         *this, bridge.getSemanticsContext(), getEval(), acc, localSymbols);
 
@@ -3677,6 +3693,42 @@ class FirConverter : public Fortran::lower::AbstractConverter {
       localSymbols.popScope();
     builder->restoreInsertionPoint(insertPt);
 
+    // Generate jump table for GOTO exits from the ACC region.
+    if (needsExitSelector) {
+      auto exitInfo = accRegionExitStack.pop_back_val();
+      if (!exitInfo.exits.empty()) {
+        mlir::Location loc = toLocation();
+        mlir::Value sel =
+            fir::LoadOp::create(*builder, loc, exitInfo.selector);
+        mlir::Block *continueBlock =
+            builder->getBlock()->splitBlock(builder->getBlock()->end());
+        if (exitInfo.exits.size() == 1) {
+          mlir::Value id = builder->createIntegerConstant(
+              loc, builder->getI32Type(), exitInfo.exits[0].first);
+          mlir::Value cmp = mlir::arith::CmpIOp::create(
+              *builder, loc, mlir::arith::CmpIPredicate::eq, sel, id);
+          mlir::cf::CondBranchOp::create(*builder, loc, cmp,
+                                         exitInfo.exits[0].second,
+                                         continueBlock);
+        } else {
+          // Multiple exit targets: chain of comparisons.
+          for (auto &[id, target] : exitInfo.exits) {
+            mlir::Value idVal = builder->createIntegerConstant(
+                loc, builder->getI32Type(), id);
+            mlir::Value cmp = mlir::arith::CmpIOp::create(
+                *builder, loc, mlir::arith::CmpIPredicate::eq, sel, idVal);
+            mlir::Block *nextCheck =
+                builder->getBlock()->splitBlock(builder->getBlock()->end());
+            mlir::cf::CondBranchOp::create(*builder, loc, cmp, target,
+                                           nextCheck);
+            builder->setInsertionPointToEnd(nextCheck);
+          }
+          mlir::cf::BranchOp::create(*builder, loc, continueBlock);
+        }
+        builder->setInsertionPointToEnd(continueBlock);
+      }
+    }
+
     if (accLoop && exitCond) {
       Fortran::lower::pft::FunctionLikeUnit *funit =
           getEval().getOwningProcedure();
@@ -6240,13 +6292,19 @@ class FirConverter : public Fortran::lower::AbstractConverter {
   }
   void genFIR(const Fortran::parser::GotoStmt &) {
     auto &targetEval = *getEval().controlSuccessor;
-    if (Fortran::lower::isInOpenACCLoop(*builder)) {
-      mlir::Block *targetBlock = targetEval.block;
-      mlir::Region *currentRegion = &builder->getRegion();
-      if (targetBlock && targetBlock->getParent() != currentRegion) {
-        mlir::acc::YieldOp::create(*builder, toLocation());
-        return;
-      }
+    mlir::Block *targetBlock = targetEval.block;
+    mlir::Region *currentRegion = &builder->getRegion();
+    if (targetBlock && targetBlock->getParent() != currentRegion &&
+        !accRegionExitStack.empty()) {
+      auto &exitInfo = accRegionExitStack.back();
+      int exitId = exitInfo.nextId++;
+      exitInfo.exits.push_back({exitId, targetBlock});
+      mlir::Value id = builder->createIntegerConstant(
+          toLocation(), builder->getI32Type(), exitId);
+      fir::StoreOp::create(*builder, toLocation(), id, exitInfo.selector);
+      Fortran::lower::genOpenACCRegionExitBranch(*builder, toLocation(),
+                                                 targetBlock);
+      return;
     }
     genConstructExitBranch(targetEval);
   }
@@ -7245,6 +7303,14 @@ class FirConverter : public Fortran::lower::AbstractConverter {
   // Stack to manage object deallocation and finalization at construct exits.
   llvm::SmallVector<ConstructContext> activeConstructStack;
 
+  /// Track GOTO exits from ACC regions for jump table generation.
+  struct AccRegionExitInfo {
+    mlir::Value selector{};       // alloca i32 for exit selector
+    llvm::SmallVector<std::pair<int, mlir::Block *>> exits; // {id, target}
+    int nextId = 1;
+  };
+  llvm::SmallVector<AccRegionExitInfo> accRegionExitStack;
+
   /// BLOCK name mangling component map
   int blockId = 0;
   Fortran::lower::mangle::ScopeBlockIdMap scopeBlockIdMap;
diff --git a/flang/lib/Lower/OpenACC.cpp b/flang/lib/Lower/OpenACC.cpp
index 2136f1d540a6a..1a8a688cfce84 100644
--- a/flang/lib/Lower/OpenACC.cpp
+++ b/flang/lib/Lower/OpenACC.cpp
@@ -2768,7 +2768,8 @@ static void genACCDataOp(Fortran::lower::AbstractConverter &converter,
   addOperands(operands, operandSegments, waitOperands);
   addOperands(operands, operandSegments, dataClauseOperands);
 
-  if (dataClauseOperands.empty() && !hasDefaultNone && !hasDefaultPresent)
+  if (dataClauseOperands.empty() && !hasDefaultNone && !hasDefaultPresent &&
+      !eval.lowerAsUnstructured())
     return;
 
   auto dataOp = createRegionOp<mlir::acc::DataOp, mlir::acc::TerminatorOp>(
@@ -4563,6 +4564,35 @@ void Fortran::lower::genEarlyReturnInOpenACCLoop(fir::FirOpBuilder &builder,
   mlir::acc::YieldOp::create(builder, loc, yieldValue);
 }
 
+bool Fortran::lower::genOpenACCRegionExitBranch(fir::FirOpBuilder &builder,
+                                                mlir::Location loc,
+                                                mlir::Block *targetBlock) {
+  mlir::Block *currentBlock = builder.getBlock();
+  if (!currentBlock || !targetBlock)
+    return false;
+  mlir::Region *curRegion = currentBlock->getParent();
+  mlir::Region *targetRegion = targetBlock->getParent();
+  if (curRegion == targetRegion)
+    return false;
+
+  // Check all regions between the current region and the target for ACC ops.
+  for (mlir::Region *r = curRegion; r && r != targetRegion;
+       r = r->getParentRegion()) {
+    mlir::Operation *op = r->getParentOp();
+    if (op &&
+        mlir::isa<ACC_COMPUTE_CONSTRUCT_OPS, ACC_DATA_CONSTRUCT_STRUCTURED_OPS,
+                  mlir::acc::LoopOp>(op)) {
+      if (targetRegion == r->getParentRegion()) {
+        genOpenACCTerminator(builder, op, loc);
+        return true;
+      }
+      TODO(loc, "GOTO exiting OpenACC region");
+    }
+  }
+
+  return false;
+}
+
 uint64_t Fortran::lower::getLoopCountForCollapseAndTile(
     const Fortran::parser::AccClauseList &clauseList) {
   uint64_t collapseLoopCount = getCollapseSizeAndForce(clauseList).first;
diff --git a/flang/test/Lower/OpenACC/Todo/acc-goto-multi-level-exit.f90 b/flang/test/Lower/OpenACC/Todo/acc-goto-multi-level-exit.f90
new file mode 100644
index 0000000000000..bacb8c4546f4d
--- /dev/null
+++ b/flang/test/Lower/OpenACC/Todo/acc-goto-multi-level-exit.f90
@@ -0,0 +1,42 @@
+! RUN: split-file %s %t
+! RUN: %not_todo_cmd bbc -fopenacc -emit-hlfir %t/acc_loop_multi_level.f90 -o - 2>&1 | FileCheck %s --check-prefix=CHECK1
+! RUN: %not_todo_cmd bbc -fopenacc -emit-hlfir %t/acc_data_multi_level.f90 -o - 2>&1 | FileCheck %s --check-prefix=CHECK2
+
+//--- acc_loop_multi_level.f90
+
+subroutine acc_loop_multi_level(a, n)
+  integer :: n, i, j
+  real :: a(*)
+
+  !$acc parallel
+  !$acc loop seq
+  do i = 1, n
+    do j = 1, n
+      if (a(j) > 0.0) goto 999
+    end do
+  end do
+  !$acc end parallel
+999 continue
+end subroutine
+
+! CHECK1: not yet implemented: GOTO exiting OpenACC region
+
+//--- acc_data_multi_level.f90
+
+subroutine acc_data_multi_level(a, n)
+  integer :: n, i, j
+  real :: a(*)
+
+  !$acc parallel
+  !$acc data
+  do i = 1, n
+    do j = 1, n
+      if (a(j) > 0.0) goto 999
+    end do
+  end do
+  !$acc end data
+  !$acc end parallel
+999 continue
+end subroutine
+
+! CHECK2: not yet implemented: GOTO exiting OpenACC region
diff --git a/flang/test/Lower/OpenACC/acc-loop-goto-exit.f90 b/flang/test/Lower/OpenACC/acc-loop-goto-exit.f90
deleted file mode 100644
index 696051b9bb534..0000000000000
--- a/flang/test/Lower/OpenACC/acc-loop-goto-exit.f90
+++ /dev/null
@@ -1,33 +0,0 @@
-! Test that GoTo exiting an acc.loop seq region generates acc.yield
-! instead of an illegal cross-region cf.br that would crash the compiler.
-! RUN: bbc -fopenacc -emit-hlfir %s -o - | FileCheck %s
-
-! CHECK-LABEL: func.func @_QPfoo
-subroutine foo(N, A, B)
-  implicit real*8 (a-h, o-z)
-  !$acc routine gang
-  dimension A(*), B(*)
-  !$acc loop gang vector
-  do 100 i = 1, N
-  !$acc loop seq
-    do 10 j = 1, 1000
-      if (A(i) .gt. B(i)) goto 20
-10  continue
-20  B(i) = A(i)
-100 continue
-end subroutine
-
-! Verify the inner acc.loop (seq) contains acc.yield for the GoTo exit.
-! The GoTo target is outside the acc.loop region, so it must yield
-! instead of generating an illegal cross-region cf.br.
-
-! CHECK: acc.loop gang vector
-! CHECK: acc.loop
-! The GoTo comparison and branch:
-! CHECK: arith.cmpf ogt
-! CHECK-NEXT: cf.cond_br %{{.*}}, ^[[EXIT:bb[0-9]+]], ^
-! CHECK-NEXT: ^[[EXIT]]:
-! CHECK-NEXT: acc.yield
-! Normal loop end yield and closing:
-! CHECK: acc.yield
-! CHECK-NEXT: } attributes {seq = [#acc.device_type<none>], unstructured}

>From e7d0f00443372ce8b119d8e88bd7b52086b34eca Mon Sep 17 00:00:00 2001
From: Kazuaki Matsumura <kmatsumura at nvidia.com>
Date: Fri, 20 Mar 2026 17:02:43 -0700
Subject: [PATCH 6/8] [flang][OpenACC] Add jump table tests for acc.loop and
 acc.data exits

Made-with: Cursor
---
 flang/test/Lower/OpenACC/acc-unstructured.f90 | 138 ++++++++++++++++++
 1 file changed, 138 insertions(+)

diff --git a/flang/test/Lower/OpenACC/acc-unstructured.f90 b/flang/test/Lower/OpenACC/acc-unstructured.f90
index bbbf8974a6c30..57b678c1200d9 100644
--- a/flang/test/Lower/OpenACC/acc-unstructured.f90
+++ b/flang/test/Lower/OpenACC/acc-unstructured.f90
@@ -84,3 +84,141 @@ subroutine test_unstructured3(a, b, c)
 ! CHECK: acc.yield
 
 end subroutine
+
+! Test that acc.data is still created when there are no data clauses but the
+! construct contains unstructured control flow. Without this, the early return
+! in genACCDataOp skips acc.data creation, leaving orphaned blocks.
+subroutine test_unstructured4(a, n)
+  integer :: n, i, j
+  real :: a(:)
+  logical :: use_gpu
+
+  use_gpu = .true.
+  !$acc data if(use_gpu)
+  do i = 1, n
+    do j = 1, n
+      if (a(j) > 0.0) stop 'unstructured'
+    end do
+  end do
+  !$acc end data
+
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_unstructured4
+! CHECK: acc.data if(%{{.*}}) {
+! CHECK: fir.call @_FortranAStopStatementText
+! CHECK: acc.terminator
+! CHECK: }
+
+! Test that GOTO exiting acc.data (one level) generates acc.terminator
+! instead of an invalid cross-region branch.
+subroutine test_unstructured5(a, n)
+  integer :: n, i, j
+  real :: a(:)
+  logical :: use_gpu
+
+  use_gpu = .true.
+  !$acc data if(use_gpu)
+  do i = 1, n
+    do j = 1, n
+      if (a(j) > 0.0) goto 999
+    end do
+  end do
+  !$acc end data
+999 continue
+
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_unstructured5
+! CHECK: acc.data if(%{{.*}}) {
+! CHECK: fir.store %{{.*}} to %{{.*}} : !fir.ref<i32>
+! CHECK: acc.terminator
+! CHECK: acc.terminator
+! CHECK: }
+! CHECK: arith.cmpi eq
+! CHECK: cf.cond_br
+
+! Test that GOTO exiting acc.loop (one level) generates acc.yield
+! instead of an invalid cross-region branch.
+subroutine test_unstructured6(N, A, B)
+  implicit real*8 (a-h, o-z)
+  !$acc routine gang
+  dimension A(*), B(*)
+  !$acc loop gang vector
+  do 100 i = 1, N
+  !$acc loop seq
+    do 10 j = 1, 1000
+      if (A(i) .gt. B(i)) goto 20
+10  continue
+20  B(i) = A(i)
+100 continue
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_unstructured6
+! CHECK: acc.loop gang vector
+! CHECK: acc.loop
+! CHECK: arith.cmpf ogt
+! CHECK: fir.store %{{.*}} to %{{.*}} : !fir.ref<i32>
+! CHECK: acc.yield
+! CHECK: } attributes {seq = [#acc.device_type<none>], unstructured}
+
+! Test GOTO exiting acc.loop with intermediate code between loop end and
+! target. A jump table (exit selector + dispatch) skips the intermediate code.
+subroutine test_unstructured7(A, B, C, N)
+  implicit real*8 (a-h, o-z)
+  !$acc routine gang
+  dimension A(*), B(*), C(*)
+  !$acc loop gang vector
+  do 100 i = 1, N
+  !$acc loop seq
+    do 10 j = 1, 1000
+      if (A(i) .gt. B(i)) goto 20
+10  continue
+    C(i) = 999.0
+20  B(i) = A(i)
+100 continue
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_unstructured7
+! CHECK: acc.loop gang vector
+! Inner loop stores exit selector and yields:
+! CHECK: acc.loop
+! CHECK: fir.store %{{.*}} to %{{.*}} : !fir.ref<i32>
+! CHECK: acc.yield
+! CHECK: } attributes {seq = [#acc.device_type<none>], unstructured}
+! Jump table after inner loop:
+! CHECK: fir.load %{{.*}} : !fir.ref<i32>
+! CHECK: arith.cmpi eq
+! CHECK: cf.cond_br
+! Intermediate code on fall-through path:
+! CHECK: arith.constant 9.990000e+02
+
+! Test GOTO exiting acc.data with intermediate code. Jump table dispatches
+! after the acc.data op.
+subroutine test_unstructured8(a, n)
+  integer :: n, i, j
+  real :: a(:)
+  logical :: use_gpu
+  use_gpu = .true.
+  !$acc data if(use_gpu)
+  do i = 1, n
+    do j = 1, n
+      if (a(j) > 0.0) goto 999
+    end do
+  end do
+  a(1) = -1.0
+  !$acc end data
+999 continue
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_unstructured8
+! Inside acc.data, GOTO stores exit selector and terminates:
+! CHECK: acc.data if(%{{.*}}) {
+! CHECK: fir.store %{{.*}} to %{{.*}} : !fir.ref<i32>
+! CHECK: acc.terminator
+! CHECK: acc.terminator
+! CHECK: }
+! Jump table after acc.data:
+! CHECK: fir.load %{{.*}} : !fir.ref<i32>
+! CHECK: arith.cmpi eq
+! CHECK: cf.cond_br

>From 0aa338138cd144b587c42ce7ab94e0582c7852a7 Mon Sep 17 00:00:00 2001
From: Kazuaki Matsumura <kmatsumura at nvidia.com>
Date: Fri, 20 Mar 2026 17:14:23 -0700
Subject: [PATCH 7/8] clang-format fix

Made-with: Cursor
---
 flang/lib/Lower/Bridge.cpp | 18 ++++++++----------
 1 file changed, 8 insertions(+), 10 deletions(-)

diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 1b0128c7b2449..bc8921d7392c3 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -3583,8 +3583,8 @@ class FirConverter : public Fortran::lower::AbstractConverter {
     bool needsExitSelector = getEval().lowerAsUnstructured();
     if (needsExitSelector) {
       AccRegionExitInfo exitInfo;
-      exitInfo.selector = builder->createTemporary(
-          toLocation(), builder->getI32Type());
+      exitInfo.selector =
+          builder->createTemporary(toLocation(), builder->getI32Type());
       mlir::Value zero = builder->createIntegerConstant(
           toLocation(), builder->getI32Type(), 0);
       fir::StoreOp::create(*builder, toLocation(), zero, exitInfo.selector);
@@ -3698,8 +3698,7 @@ class FirConverter : public Fortran::lower::AbstractConverter {
       auto exitInfo = accRegionExitStack.pop_back_val();
       if (!exitInfo.exits.empty()) {
         mlir::Location loc = toLocation();
-        mlir::Value sel =
-            fir::LoadOp::create(*builder, loc, exitInfo.selector);
+        mlir::Value sel = fir::LoadOp::create(*builder, loc, exitInfo.selector);
         mlir::Block *continueBlock =
             builder->getBlock()->splitBlock(builder->getBlock()->end());
         if (exitInfo.exits.size() == 1) {
@@ -3707,14 +3706,13 @@ class FirConverter : public Fortran::lower::AbstractConverter {
               loc, builder->getI32Type(), exitInfo.exits[0].first);
           mlir::Value cmp = mlir::arith::CmpIOp::create(
               *builder, loc, mlir::arith::CmpIPredicate::eq, sel, id);
-          mlir::cf::CondBranchOp::create(*builder, loc, cmp,
-                                         exitInfo.exits[0].second,
-                                         continueBlock);
+          mlir::cf::CondBranchOp::create(
+              *builder, loc, cmp, exitInfo.exits[0].second, continueBlock);
         } else {
           // Multiple exit targets: chain of comparisons.
           for (auto &[id, target] : exitInfo.exits) {
-            mlir::Value idVal = builder->createIntegerConstant(
-                loc, builder->getI32Type(), id);
+            mlir::Value idVal =
+                builder->createIntegerConstant(loc, builder->getI32Type(), id);
             mlir::Value cmp = mlir::arith::CmpIOp::create(
                 *builder, loc, mlir::arith::CmpIPredicate::eq, sel, idVal);
             mlir::Block *nextCheck =
@@ -7305,7 +7303,7 @@ class FirConverter : public Fortran::lower::AbstractConverter {
 
   /// Track GOTO exits from ACC regions for jump table generation.
   struct AccRegionExitInfo {
-    mlir::Value selector{};       // alloca i32 for exit selector
+    mlir::Value selector{}; // alloca i32 for exit selector
     llvm::SmallVector<std::pair<int, mlir::Block *>> exits; // {id, target}
     int nextId = 1;
   };

>From 72b9d3a15cc426adc6224949843d2acde90dac97 Mon Sep 17 00:00:00 2001
From: Kazuaki Matsumura <kmatsumura at nvidia.com>
Date: Mon, 23 Mar 2026 18:04:09 -0700
Subject: [PATCH 8/8] [flang][OpenACC] Address review: move exit handling to
 genBranch, simplify jump table

- Move selector store logic from genFIR(GotoStmt) into genBranch so
  genConstructExitBranch cleanup is not bypassed.
- Revert genFIR(GotoStmt) to simple genConstructExitBranch call.
- Remove 1-exit special case in jump table; use the loop for all cases.
- Fix TODO test: use nested acc.data (valid per spec) instead of GOTO
  out of acc.parallel (illegal per spec 2.5.4).

Made-with: Cursor
---
 flang/lib/Lower/Bridge.cpp                    | 64 ++++++++-----------
 .../Todo/acc-goto-multi-level-exit.f90        | 40 +++---------
 2 files changed, 35 insertions(+), 69 deletions(-)

diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index bc8921d7392c3..c94b649e9029f 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -1609,9 +1609,20 @@ class FirConverter : public Fortran::lower::AbstractConverter {
 
   void genBranch(mlir::Block *targetBlock) {
     assert(targetBlock && "missing unconditional target block");
-    if (Fortran::lower::genOpenACCRegionExitBranch(*builder, toLocation(),
-                                                   targetBlock))
-      return;
+    if (!accRegionExitStack.empty()) {
+      mlir::Region *curRegion = builder->getBlock()->getParent();
+      if (targetBlock->getParent() != curRegion) {
+        auto &exitInfo = accRegionExitStack.back();
+        int exitId = exitInfo.nextId++;
+        exitInfo.exits.push_back({exitId, targetBlock});
+        mlir::Value id = builder->createIntegerConstant(
+            toLocation(), builder->getI32Type(), exitId);
+        fir::StoreOp::create(*builder, toLocation(), id, exitInfo.selector);
+        Fortran::lower::genOpenACCRegionExitBranch(*builder, toLocation(),
+                                                   targetBlock);
+        return;
+      }
+    }
     mlir::cf::BranchOp::create(*builder, toLocation(), targetBlock);
   }
 
@@ -3701,28 +3712,18 @@ class FirConverter : public Fortran::lower::AbstractConverter {
         mlir::Value sel = fir::LoadOp::create(*builder, loc, exitInfo.selector);
         mlir::Block *continueBlock =
             builder->getBlock()->splitBlock(builder->getBlock()->end());
-        if (exitInfo.exits.size() == 1) {
-          mlir::Value id = builder->createIntegerConstant(
-              loc, builder->getI32Type(), exitInfo.exits[0].first);
+        for (auto &[id, target] : exitInfo.exits) {
+          mlir::Value idVal =
+              builder->createIntegerConstant(loc, builder->getI32Type(), id);
           mlir::Value cmp = mlir::arith::CmpIOp::create(
-              *builder, loc, mlir::arith::CmpIPredicate::eq, sel, id);
-          mlir::cf::CondBranchOp::create(
-              *builder, loc, cmp, exitInfo.exits[0].second, continueBlock);
-        } else {
-          // Multiple exit targets: chain of comparisons.
-          for (auto &[id, target] : exitInfo.exits) {
-            mlir::Value idVal =
-                builder->createIntegerConstant(loc, builder->getI32Type(), id);
-            mlir::Value cmp = mlir::arith::CmpIOp::create(
-                *builder, loc, mlir::arith::CmpIPredicate::eq, sel, idVal);
-            mlir::Block *nextCheck =
-                builder->getBlock()->splitBlock(builder->getBlock()->end());
-            mlir::cf::CondBranchOp::create(*builder, loc, cmp, target,
-                                           nextCheck);
-            builder->setInsertionPointToEnd(nextCheck);
-          }
-          mlir::cf::BranchOp::create(*builder, loc, continueBlock);
+              *builder, loc, mlir::arith::CmpIPredicate::eq, sel, idVal);
+          mlir::Block *nextCheck =
+              builder->getBlock()->splitBlock(builder->getBlock()->end());
+          mlir::cf::CondBranchOp::create(*builder, loc, cmp, target,
+                                         nextCheck);
+          builder->setInsertionPointToEnd(nextCheck);
         }
+        mlir::cf::BranchOp::create(*builder, loc, continueBlock);
         builder->setInsertionPointToEnd(continueBlock);
       }
     }
@@ -6289,22 +6290,7 @@ class FirConverter : public Fortran::lower::AbstractConverter {
     genConstructExitBranch(*getEval().controlSuccessor);
   }
   void genFIR(const Fortran::parser::GotoStmt &) {
-    auto &targetEval = *getEval().controlSuccessor;
-    mlir::Block *targetBlock = targetEval.block;
-    mlir::Region *currentRegion = &builder->getRegion();
-    if (targetBlock && targetBlock->getParent() != currentRegion &&
-        !accRegionExitStack.empty()) {
-      auto &exitInfo = accRegionExitStack.back();
-      int exitId = exitInfo.nextId++;
-      exitInfo.exits.push_back({exitId, targetBlock});
-      mlir::Value id = builder->createIntegerConstant(
-          toLocation(), builder->getI32Type(), exitId);
-      fir::StoreOp::create(*builder, toLocation(), id, exitInfo.selector);
-      Fortran::lower::genOpenACCRegionExitBranch(*builder, toLocation(),
-                                                 targetBlock);
-      return;
-    }
-    genConstructExitBranch(targetEval);
+    genConstructExitBranch(*getEval().controlSuccessor);
   }
 
   // Nop statements - No code, or code is generated at the construct level.
diff --git a/flang/test/Lower/OpenACC/Todo/acc-goto-multi-level-exit.f90 b/flang/test/Lower/OpenACC/Todo/acc-goto-multi-level-exit.f90
index bacb8c4546f4d..a73af3217dffe 100644
--- a/flang/test/Lower/OpenACC/Todo/acc-goto-multi-level-exit.f90
+++ b/flang/test/Lower/OpenACC/Todo/acc-goto-multi-level-exit.f90
@@ -1,42 +1,22 @@
-! RUN: split-file %s %t
-! RUN: %not_todo_cmd bbc -fopenacc -emit-hlfir %t/acc_loop_multi_level.f90 -o - 2>&1 | FileCheck %s --check-prefix=CHECK1
-! RUN: %not_todo_cmd bbc -fopenacc -emit-hlfir %t/acc_data_multi_level.f90 -o - 2>&1 | FileCheck %s --check-prefix=CHECK2
+! GOTO exits through two nested ACC data regions. The branch crosses
+! two ACC region boundaries, requiring multi-level exit handling.
 
-//--- acc_loop_multi_level.f90
+! RUN: %not_todo_cmd bbc -fopenacc -emit-hlfir %s -o - 2>&1 | FileCheck %s
 
-subroutine acc_loop_multi_level(a, n)
+subroutine nested_data_exit(a, n)
   integer :: n, i, j
   real :: a(*)
 
-  !$acc parallel
-  !$acc loop seq
+  !$acc data copy(a(1:n))
+  !$acc data copyout(a(1:n))
   do i = 1, n
     do j = 1, n
-      if (a(j) > 0.0) goto 999
-    end do
-  end do
-  !$acc end parallel
-999 continue
-end subroutine
-
-! CHECK1: not yet implemented: GOTO exiting OpenACC region
-
-//--- acc_data_multi_level.f90
-
-subroutine acc_data_multi_level(a, n)
-  integer :: n, i, j
-  real :: a(*)
-
-  !$acc parallel
-  !$acc data
-  do i = 1, n
-    do j = 1, n
-      if (a(j) > 0.0) goto 999
+      if (a(j) > 0.0) goto 888
     end do
   end do
   !$acc end data
-  !$acc end parallel
-999 continue
+  !$acc end data
+888 continue
 end subroutine
 
-! CHECK2: not yet implemented: GOTO exiting OpenACC region
+! CHECK: not yet implemented: GOTO exiting OpenACC region



More information about the flang-commits mailing list