[flang-commits] [flang] [flang] Lowering fortran structured do-while loops to `scf.while` (PR #177476)

Susan Tan ス-ザン タン via flang-commits flang-commits at lists.llvm.org
Fri Jan 23 10:43:43 PST 2026


https://github.com/SusanTan updated https://github.com/llvm/llvm-project/pull/177476

>From 4bc32d7a48f2dd7a1104677ae554d9b70c3a55db Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Thu, 22 Jan 2026 11:05:04 -0800
Subject: [PATCH 1/6] add scf while lowering

---
 flang/lib/Lower/Bridge.cpp                    | 86 +++++++++++++++++++
 .../Lower/OpenACC/do-while-to-scf-while.f90   | 49 +++++++++++
 flang/test/Lower/do-while-to-scf-while.f90    | 31 +++++++
 3 files changed, 166 insertions(+)
 create mode 100644 flang/test/Lower/OpenACC/do-while-to-scf-while.f90
 create mode 100644 flang/test/Lower/do-while-to-scf-while.f90

diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 251e8d9ca9e14..440e5c6e73c88 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -67,6 +67,7 @@
 #include "flang/Support/Flags.h"
 #include "flang/Support/Version.h"
 #include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
+#include "mlir/Dialect/SCF/IR/SCF.h"
 #include "mlir/IR/BuiltinAttributes.h"
 #include "mlir/IR/Matchers.h"
 #include "mlir/IR/PatternMatch.h"
@@ -90,6 +91,11 @@ static llvm::cl::opt<bool> forceLoopToExecuteOnce(
     "always-execute-loop-body", llvm::cl::init(false),
     llvm::cl::desc("force the body of a loop to execute at least once"));
 
+static llvm::cl::opt<bool> lowerDoWhileToSCFWhile(
+    "lower-do-while-to-scf-while", llvm::cl::init(false),
+    llvm::cl::desc("lower structured DO WHILE loops to scf.while"),
+    llvm::cl::Hidden);
+
 namespace {
 /// Information for generating a structured or unstructured increment loop.
 struct IncrementLoopInfo {
@@ -1644,6 +1650,65 @@ class FirConverter : public Fortran::lower::AbstractConverter {
     genConditionalBranch(cond, trueTarget->block, falseTarget->block);
   }
 
+  /// Returns true iff \p eval's nested subtree contains no unstructured
+  /// evaluations except \p allowedEval (typically the NonLabelDoStmt of an
+  /// outer DO WHILE).
+  bool
+  doWhileBodyIsStructuredExcept(Fortran::lower::pft::Evaluation &eval,
+                                Fortran::lower::pft::Evaluation *allowedEval) {
+    if (&eval != allowedEval && eval.isUnstructured)
+      return false;
+    if (!eval.evaluationList)
+      return true;
+    for (Fortran::lower::pft::Evaluation &nested : *eval.evaluationList)
+      if (!doWhileBodyIsStructuredExcept(nested, allowedEval))
+        return false;
+    return true;
+  }
+
+  void
+  genDoWhileAsSCFWhile(const Fortran::parser::ScalarLogicalExpr &whileCondition,
+                       Fortran::lower::pft::Evaluation &doConstructEval,
+                       Fortran::lower::pft::Evaluation &doStmtEval) {
+    mlir::Location loc = toLocation();
+
+    auto scfWhile =
+        mlir::scf::WhileOp::create(*builder, loc,
+                                   /*resultTypes=*/mlir::TypeRange{},
+                                   /*inits=*/mlir::ValueRange{});
+
+    // Fill the "before" region: compute condition.
+    mlir::Block *beforeBlock =
+        builder->createBlock(&scfWhile.getBefore(), scfWhile.getBefore().end());
+    builder->setInsertionPointToStart(beforeBlock);
+    Fortran::lower::StatementContext stmtCtx;
+    mlir::Value cond = createFIRExpr(
+        loc, Fortran::semantics::GetExpr(whileCondition), stmtCtx);
+    stmtCtx.finalizeAndReset();
+    cond = builder->createConvert(loc, builder->getI1Type(), cond);
+    mlir::scf::ConditionOp::create(*builder, loc, cond, mlir::ValueRange{});
+
+    // Fill the "after" region: loop body.
+    mlir::Block *afterBlock =
+        builder->createBlock(&scfWhile.getAfter(), scfWhile.getAfter().end());
+    builder->setInsertionPointToStart(afterBlock);
+
+    // Lower nested evaluations excluding the loop control statement (the
+    // NonLabelDoStmt) and the EndDoStmt.
+    auto iter = doConstructEval.getNestedEvaluations().begin();
+    auto end = doConstructEval.getNestedEvaluations().end();
+    assert(iter != end && "malformed DoConstruct evaluation list");
+    ++iter; // skip the NonLabelDoStmt
+    assert(iter != end && "malformed DoConstruct evaluation list");
+    auto endDoIter = std::prev(end);
+    for (; iter != endDoIter; ++iter)
+      genFIR(*iter, /*unstructuredContext=*/false);
+
+    mlir::scf::YieldOp::create(*builder, loc);
+
+    builder->setInsertionPointAfter(scfWhile);
+  }
+
   /// Return the nearest active ancestor construct of \p eval, or nullptr.
   Fortran::lower::pft::Evaluation *
   getActiveAncestor(const Fortran::lower::pft::Evaluation &eval) {
@@ -2482,6 +2547,27 @@ class FirConverter : public Fortran::lower::AbstractConverter {
     } else if ((whileCondition =
                     std::get_if<Fortran::parser::ScalarLogicalExpr>(
                         &loopControl->u))) {
+      // Optionally lower a restricted subset of DO WHILE loops directly to
+      // scf.while. This subset excludes early-exit constructs (EXIT/CYCLE/GOTO,
+      // etc.) by requiring that the loop body is structured (as decided by the
+      // PFT branch analysis), allowing the loop to exit only when the condition
+      // becomes false.
+      bool doWhileBodyStructured = true;
+      if (auto *nestedList = eval.evaluationList.get()) {
+        for (Fortran::lower::pft::Evaluation &nested : *nestedList) {
+          if (!doWhileBodyIsStructuredExcept(nested,
+                                             /*allowedEval=*/&doStmtEval)) {
+            doWhileBodyStructured = false;
+            break;
+          }
+        }
+      }
+      if (lowerDoWhileToSCFWhile && doWhileBodyStructured) {
+        maybeStartBlock(preheaderBlock); // no block or empty block
+        genDoWhileAsSCFWhile(*whileCondition, eval, doStmtEval);
+        return;
+      }
+
       assert(unstructuredContext && "while loop must be unstructured");
       maybeStartBlock(preheaderBlock); // no block or empty block
       startBlock(headerBlock);
diff --git a/flang/test/Lower/OpenACC/do-while-to-scf-while.f90 b/flang/test/Lower/OpenACC/do-while-to-scf-while.f90
new file mode 100644
index 0000000000000..024a4f2492d4e
--- /dev/null
+++ b/flang/test/Lower/OpenACC/do-while-to-scf-while.f90
@@ -0,0 +1,49 @@
+! RUN: bbc -fopenacc -emit-hlfir -lower-do-while-to-scf-while %s -o - | FileCheck %s
+
+! CHECK-LABEL: func.func @_QPacc_nested_loops()
+! CHECK: scf.while
+subroutine acc_nested_loops()
+  use, intrinsic :: iso_fortran_env, only : real64
+  implicit none
+
+  integer, parameter :: loopcount = 1000
+  real(real64), parameter :: precision = 1.0e-3_real64
+
+  real(real64), allocatable :: a(:,:)
+  real(real64) :: avg
+  integer :: x, y
+
+  allocate(a(10, loopcount))
+
+  ! Initialize input data
+  do x = 1, 10
+    do y = 1, loopcount
+      a(x,y) = 1.0_real64
+    end do
+  end do
+
+  !$acc data copy(a(1:10, 1:loopcount))
+  !$acc parallel
+  !$acc loop
+  do x = 1, 10
+    avg = 0.0_real64
+    do while (avg - 1000.0_real64 < precision * real(loopcount, real64))
+      avg = 0.0_real64
+
+      !$acc loop
+      do y = 1, loopcount
+        a(x, y) = a(x, y) * 1.5_real64
+      end do
+
+      !$acc loop reduction(+:avg)
+      do y = 1, loopcount
+        avg = avg + (a(x, y) / real(loopcount, real64))
+      end do
+    end do
+  end do
+  !$acc end parallel
+  !$acc end data
+
+  deallocate(a)
+end subroutine acc_nested_loops
+
diff --git a/flang/test/Lower/do-while-to-scf-while.f90 b/flang/test/Lower/do-while-to-scf-while.f90
new file mode 100644
index 0000000000000..b8878f7fdedcd
--- /dev/null
+++ b/flang/test/Lower/do-while-to-scf-while.f90
@@ -0,0 +1,31 @@
+! RUN: bbc -emit-fir -hlfir=false -lower-do-while-to-scf-while %s -o - | FileCheck %s
+
+! CHECK-LABEL: func.func @_QPsimple_do_while()
+! CHECK: scf.while
+! CHECK: scf.condition
+! CHECK: scf.yield
+subroutine simple_do_while()
+  implicit none
+  integer :: i
+
+  i = 1
+  do while (i <= 10)
+    print *, "i =", i
+    i = i + 1
+  end do
+end subroutine simple_do_while
+
+! CHECK-LABEL: func.func @_QPdo_while_with_exit()
+! CHECK-NOT: scf.while
+! CHECK: cf.cond_br
+subroutine do_while_with_exit()
+  implicit none
+  integer :: i
+
+  i = 1
+  do while (i <= 10)
+    if (i == 5) exit
+    i = i + 1
+  end do
+end subroutine do_while_with_exit
+

>From 4924b51c822390ad3710ba7cc97a86123a86eb97 Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Thu, 22 Jan 2026 13:02:37 -0800
Subject: [PATCH 2/6] do not rely on internal unstructured check but check
 early exits

---
 flang/lib/Lower/Bridge.cpp                 | 96 ++++++++++++++++------
 flang/test/Lower/do-while-to-scf-while.f90 | 17 ++++
 2 files changed, 88 insertions(+), 25 deletions(-)

diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 440e5c6e73c88..5f82bb56b3447 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -1650,20 +1650,74 @@ class FirConverter : public Fortran::lower::AbstractConverter {
     genConditionalBranch(cond, trueTarget->block, falseTarget->block);
   }
 
-  /// Returns true iff \p eval's nested subtree contains no unstructured
-  /// evaluations except \p allowedEval (typically the NonLabelDoStmt of an
-  /// outer DO WHILE).
-  bool
-  doWhileBodyIsStructuredExcept(Fortran::lower::pft::Evaluation &eval,
-                                Fortran::lower::pft::Evaluation *allowedEval) {
-    if (&eval != allowedEval && eval.isUnstructured)
-      return false;
-    if (!eval.evaluationList)
+  /// Return true iff there is any control-flow transfer within \p outerDoWhile
+  /// that can exit the outer loop early or jump to the outer loop end-do
+  /// (i.e. early "continue"). Nested loop exits that remain inside the outer
+  /// loop are allowed.
+  bool doWhileHasEarlyExit(const Fortran::lower::pft::Evaluation &outerDoWhile,
+                           const Fortran::lower::pft::Evaluation &outerDoStmt,
+                           const Fortran::lower::pft::Evaluation &outerEndDo) {
+    auto branchTargetIsEarlyExit =
+        [&](const Fortran::lower::pft::Evaluation &target) -> bool {
+      // Disallow early "continue" of the outer loop (e.g. CYCLE outer) under
+      // the "only exit is condition false" restriction.
+      if (&target == &outerEndDo)
+        return true;
+      // Any branch target outside the outer loop is an early exit.
+      for (auto *e = ⌖ e; e = e->parentConstruct)
+        if (e == &outerDoWhile)
+          return false;
       return true;
-    for (Fortran::lower::pft::Evaluation &nested : *eval.evaluationList)
-      if (!doWhileBodyIsStructuredExcept(nested, allowedEval))
+    };
+
+    std::function<bool(const Fortran::lower::pft::Evaluation &)>
+        hasEarlyExitInSubtree =
+            [&](const Fortran::lower::pft::Evaluation &e) -> bool {
+      // Skip structural nodes for the outer loop itself.
+      if (&e == &outerDoStmt || &e == &outerEndDo)
         return false;
-    return true;
+
+      // Catch branches represented through the PFT controlSuccessor link.
+      if (e.controlSuccessor && branchTargetIsEarlyExit(*e.controlSuccessor))
+        return true;
+
+      // Support branch statements inside the loop by checking that all their
+      // targets remain inside the outer DO WHILE.
+      if (const auto *s = e.getIf<Fortran::parser::GotoStmt>()) {
+        if (branchTargetIsEarlyExit(evalOfLabel(s->v)))
+          return true;
+      } else if (const auto *s = e.getIf<Fortran::parser::ComputedGotoStmt>()) {
+        for (const auto &lab :
+             std::get<std::list<Fortran::parser::Label>>(s->t))
+          if (branchTargetIsEarlyExit(evalOfLabel(lab)))
+            return true;
+      } else if (const auto *s = e.getIf<Fortran::parser::ArithmeticIfStmt>()) {
+        if (branchTargetIsEarlyExit(evalOfLabel(std::get<1>(s->t))) ||
+            branchTargetIsEarlyExit(evalOfLabel(std::get<2>(s->t))) ||
+            branchTargetIsEarlyExit(evalOfLabel(std::get<3>(s->t))))
+          return true;
+      }
+
+      // Statements that exit the procedure are early exits from the loop.
+      if (e.isA<Fortran::parser::ReturnStmt>() ||
+          e.isA<Fortran::parser::StopStmt>() ||
+          e.isA<Fortran::parser::FailImageStmt>())
+        return true;
+
+      if (!e.evaluationList)
+        return false;
+      for (const Fortran::lower::pft::Evaluation &nested : *e.evaluationList)
+        if (hasEarlyExitInSubtree(nested))
+          return true;
+      return false;
+    };
+
+    if (outerDoWhile.evaluationList)
+      for (const Fortran::lower::pft::Evaluation &nested :
+           *outerDoWhile.evaluationList)
+        if (hasEarlyExitInSubtree(nested))
+          return true;
+    return false;
   }
 
   void
@@ -1705,7 +1759,6 @@ class FirConverter : public Fortran::lower::AbstractConverter {
       genFIR(*iter, /*unstructuredContext=*/false);
 
     mlir::scf::YieldOp::create(*builder, loc);
-
     builder->setInsertionPointAfter(scfWhile);
   }
 
@@ -2552,17 +2605,10 @@ class FirConverter : public Fortran::lower::AbstractConverter {
       // etc.) by requiring that the loop body is structured (as decided by the
       // PFT branch analysis), allowing the loop to exit only when the condition
       // becomes false.
-      bool doWhileBodyStructured = true;
-      if (auto *nestedList = eval.evaluationList.get()) {
-        for (Fortran::lower::pft::Evaluation &nested : *nestedList) {
-          if (!doWhileBodyIsStructuredExcept(nested,
-                                             /*allowedEval=*/&doStmtEval)) {
-            doWhileBodyStructured = false;
-            break;
-          }
-        }
-      }
-      if (lowerDoWhileToSCFWhile && doWhileBodyStructured) {
+      Fortran::lower::pft::Evaluation &endDoEval =
+          eval.getLastNestedEvaluation();
+      if (lowerDoWhileToSCFWhile &&
+          !doWhileHasEarlyExit(eval, doStmtEval, endDoEval)) {
         maybeStartBlock(preheaderBlock); // no block or empty block
         genDoWhileAsSCFWhile(*whileCondition, eval, doStmtEval);
         return;
@@ -7444,4 +7490,4 @@ void Fortran::lower::genCleanUpInRegionIfAny(
   context.finalizeAndPop();
   hlfir::YieldOp::ensureTerminator(region, builder, loc);
   builder.restoreInsertionPoint(insertPt);
-}
+}
\ No newline at end of file
diff --git a/flang/test/Lower/do-while-to-scf-while.f90 b/flang/test/Lower/do-while-to-scf-while.f90
index b8878f7fdedcd..5d450999c8de5 100644
--- a/flang/test/Lower/do-while-to-scf-while.f90
+++ b/flang/test/Lower/do-while-to-scf-while.f90
@@ -29,3 +29,20 @@ subroutine do_while_with_exit()
   end do
 end subroutine do_while_with_exit
 
+! CHECK-LABEL: func.func @_QPnested_do_while()
+! CHECK: scf.while
+! CHECK: scf.while
+subroutine nested_do_while()
+  implicit none
+  integer :: i, j
+
+  i = 1
+  do while (i <= 3)
+    j = 1
+    do while (j <= 2)
+      j = j + 1
+    end do
+    i = i + 1
+  end do
+end subroutine nested_do_while
+

>From ae16d9c4579e288ef8988666e65f148474fbc343 Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Thu, 22 Jan 2026 13:25:22 -0800
Subject: [PATCH 3/6] add more tests

---
 flang/test/Lower/do-while-to-scf-while.f90 | 43 ++++++++++++++++++++++
 1 file changed, 43 insertions(+)

diff --git a/flang/test/Lower/do-while-to-scf-while.f90 b/flang/test/Lower/do-while-to-scf-while.f90
index 5d450999c8de5..53ebbb2e513bb 100644
--- a/flang/test/Lower/do-while-to-scf-while.f90
+++ b/flang/test/Lower/do-while-to-scf-while.f90
@@ -46,3 +46,46 @@ subroutine nested_do_while()
   end do
 end subroutine nested_do_while
 
+! CHECK-LABEL: func.func @_QPdo_while_goto_internal_forward()
+! CHECK: scf.while
+! CHECK: scf.condition
+! CHECK: fir.if
+! CHECK-NOT: cf.br
+! CHECK: scf.yield
+subroutine do_while_goto_internal_forward()
+  implicit none
+  integer :: i, sum
+
+  i = 0
+  sum = 0
+  do while (i < 10)
+    i = i + 1
+
+    if (mod(i, 2) == 0) goto 100
+    sum = sum + i
+
+100 continue
+  end do
+  print *, "sum=", sum
+end subroutine do_while_goto_internal_forward
+
+! CHECK-LABEL: func.func @_QPdo_while_goto_internal_backedge()
+! CHECK-NOT: scf.while
+! CHECK: cf.cond_br
+! CHECK: cf.br
+subroutine do_while_goto_internal_backedge()
+  implicit none
+  integer :: i, sum
+
+  i = 0
+  sum = 0
+  do while (i < 5)
+    i = i + 1
+
+10  continue
+    sum = sum + 1
+    if (sum < 3) goto 10
+  end do
+  print *, "sum=", sum
+end subroutine do_while_goto_internal_backedge
+

>From a20a32b6dfa90708b824b6b17e26454d50c65b9d Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Thu, 22 Jan 2026 13:35:12 -0800
Subject: [PATCH 4/6] rm the acc test

---
 .../Lower/OpenACC/do-while-to-scf-while.f90   | 49 -------------------
 1 file changed, 49 deletions(-)
 delete mode 100644 flang/test/Lower/OpenACC/do-while-to-scf-while.f90

diff --git a/flang/test/Lower/OpenACC/do-while-to-scf-while.f90 b/flang/test/Lower/OpenACC/do-while-to-scf-while.f90
deleted file mode 100644
index 024a4f2492d4e..0000000000000
--- a/flang/test/Lower/OpenACC/do-while-to-scf-while.f90
+++ /dev/null
@@ -1,49 +0,0 @@
-! RUN: bbc -fopenacc -emit-hlfir -lower-do-while-to-scf-while %s -o - | FileCheck %s
-
-! CHECK-LABEL: func.func @_QPacc_nested_loops()
-! CHECK: scf.while
-subroutine acc_nested_loops()
-  use, intrinsic :: iso_fortran_env, only : real64
-  implicit none
-
-  integer, parameter :: loopcount = 1000
-  real(real64), parameter :: precision = 1.0e-3_real64
-
-  real(real64), allocatable :: a(:,:)
-  real(real64) :: avg
-  integer :: x, y
-
-  allocate(a(10, loopcount))
-
-  ! Initialize input data
-  do x = 1, 10
-    do y = 1, loopcount
-      a(x,y) = 1.0_real64
-    end do
-  end do
-
-  !$acc data copy(a(1:10, 1:loopcount))
-  !$acc parallel
-  !$acc loop
-  do x = 1, 10
-    avg = 0.0_real64
-    do while (avg - 1000.0_real64 < precision * real(loopcount, real64))
-      avg = 0.0_real64
-
-      !$acc loop
-      do y = 1, loopcount
-        a(x, y) = a(x, y) * 1.5_real64
-      end do
-
-      !$acc loop reduction(+:avg)
-      do y = 1, loopcount
-        avg = avg + (a(x, y) / real(loopcount, real64))
-      end do
-    end do
-  end do
-  !$acc end parallel
-  !$acc end data
-
-  deallocate(a)
-end subroutine acc_nested_loops
-

>From 762b81638d266fc743f5c87dc1f3f8af782a6b9c Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Thu, 22 Jan 2026 14:47:40 -0800
Subject: [PATCH 5/6] disable GOTOs

---
 flang/lib/Lower/Bridge.cpp                 | 31 +++++++++-------------
 flang/test/Lower/do-while-to-scf-while.f90 |  7 ++---
 2 files changed, 14 insertions(+), 24 deletions(-)

diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 5f82bb56b3447..d28c1d977cb2e 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -1650,10 +1650,6 @@ class FirConverter : public Fortran::lower::AbstractConverter {
     genConditionalBranch(cond, trueTarget->block, falseTarget->block);
   }
 
-  /// Return true iff there is any control-flow transfer within \p outerDoWhile
-  /// that can exit the outer loop early or jump to the outer loop end-do
-  /// (i.e. early "continue"). Nested loop exits that remain inside the outer
-  /// loop are allowed.
   bool doWhileHasEarlyExit(const Fortran::lower::pft::Evaluation &outerDoWhile,
                            const Fortran::lower::pft::Evaluation &outerDoStmt,
                            const Fortran::lower::pft::Evaluation &outerEndDo) {
@@ -1677,24 +1673,21 @@ class FirConverter : public Fortran::lower::AbstractConverter {
       if (&e == &outerDoStmt || &e == &outerEndDo)
         return false;
 
-      // Catch branches represented through the PFT controlSuccessor link.
-      if (e.controlSuccessor && branchTargetIsEarlyExit(*e.controlSuccessor))
+      // Any explicit branch statement in the loop body forces CFG and cannot be
+      // emitted inside scf.while's single-block regions by this lowering.
+      // TODO: can be improved to accept certain GOTOs within the loop
+      if (e.isA<Fortran::parser::GotoStmt>() ||
+          e.isA<Fortran::parser::ComputedGotoStmt>() ||
+          e.isA<Fortran::parser::AssignedGotoStmt>() ||
+          e.isA<Fortran::parser::ArithmeticIfStmt>() ||
+          e.isA<Fortran::parser::ExitStmt>() ||
+          e.isA<Fortran::parser::CycleStmt>())
         return true;
 
-      // Support branch statements inside the loop by checking that all their
-      // targets remain inside the outer DO WHILE.
-      if (const auto *s = e.getIf<Fortran::parser::GotoStmt>()) {
-        if (branchTargetIsEarlyExit(evalOfLabel(s->v)))
+      if (e.controlSuccessor) {
+        if (!e.isConstruct() && !e.isConstructStmt())
           return true;
-      } else if (const auto *s = e.getIf<Fortran::parser::ComputedGotoStmt>()) {
-        for (const auto &lab :
-             std::get<std::list<Fortran::parser::Label>>(s->t))
-          if (branchTargetIsEarlyExit(evalOfLabel(lab)))
-            return true;
-      } else if (const auto *s = e.getIf<Fortran::parser::ArithmeticIfStmt>()) {
-        if (branchTargetIsEarlyExit(evalOfLabel(std::get<1>(s->t))) ||
-            branchTargetIsEarlyExit(evalOfLabel(std::get<2>(s->t))) ||
-            branchTargetIsEarlyExit(evalOfLabel(std::get<3>(s->t))))
+        if (branchTargetIsEarlyExit(*e.controlSuccessor))
           return true;
       }
 
diff --git a/flang/test/Lower/do-while-to-scf-while.f90 b/flang/test/Lower/do-while-to-scf-while.f90
index 53ebbb2e513bb..1d3dc44fce05a 100644
--- a/flang/test/Lower/do-while-to-scf-while.f90
+++ b/flang/test/Lower/do-while-to-scf-while.f90
@@ -47,11 +47,8 @@ subroutine nested_do_while()
 end subroutine nested_do_while
 
 ! CHECK-LABEL: func.func @_QPdo_while_goto_internal_forward()
-! CHECK: scf.while
-! CHECK: scf.condition
-! CHECK: fir.if
-! CHECK-NOT: cf.br
-! CHECK: scf.yield
+! CHECK-NOT: scf.while
+! CHECK: cf.cond_br
 subroutine do_while_goto_internal_forward()
   implicit none
   integer :: i, sum

>From 550552a61f25fd95e377d0eaecf12574b6ff3d7f Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Fri, 23 Jan 2026 10:43:29 -0800
Subject: [PATCH 6/6] move early exit detect to PFTBuilder

---
 flang/lib/Lower/Bridge.cpp     |  75 ++---------------------
 flang/lib/Lower/PFTBuilder.cpp | 109 ++++++++++++++++++++++++++++++++-
 2 files changed, 112 insertions(+), 72 deletions(-)

diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index d28c1d977cb2e..44121395eabc5 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -91,10 +91,9 @@ static llvm::cl::opt<bool> forceLoopToExecuteOnce(
     "always-execute-loop-body", llvm::cl::init(false),
     llvm::cl::desc("force the body of a loop to execute at least once"));
 
-static llvm::cl::opt<bool> lowerDoWhileToSCFWhile(
-    "lower-do-while-to-scf-while", llvm::cl::init(false),
-    llvm::cl::desc("lower structured DO WHILE loops to scf.while"),
-    llvm::cl::Hidden);
+namespace Fortran::lower {
+extern llvm::cl::opt<bool> lowerDoWhileToSCFWhile;
+} // namespace Fortran::lower
 
 namespace {
 /// Information for generating a structured or unstructured increment loop.
@@ -1650,69 +1649,6 @@ class FirConverter : public Fortran::lower::AbstractConverter {
     genConditionalBranch(cond, trueTarget->block, falseTarget->block);
   }
 
-  bool doWhileHasEarlyExit(const Fortran::lower::pft::Evaluation &outerDoWhile,
-                           const Fortran::lower::pft::Evaluation &outerDoStmt,
-                           const Fortran::lower::pft::Evaluation &outerEndDo) {
-    auto branchTargetIsEarlyExit =
-        [&](const Fortran::lower::pft::Evaluation &target) -> bool {
-      // Disallow early "continue" of the outer loop (e.g. CYCLE outer) under
-      // the "only exit is condition false" restriction.
-      if (&target == &outerEndDo)
-        return true;
-      // Any branch target outside the outer loop is an early exit.
-      for (auto *e = ⌖ e; e = e->parentConstruct)
-        if (e == &outerDoWhile)
-          return false;
-      return true;
-    };
-
-    std::function<bool(const Fortran::lower::pft::Evaluation &)>
-        hasEarlyExitInSubtree =
-            [&](const Fortran::lower::pft::Evaluation &e) -> bool {
-      // Skip structural nodes for the outer loop itself.
-      if (&e == &outerDoStmt || &e == &outerEndDo)
-        return false;
-
-      // Any explicit branch statement in the loop body forces CFG and cannot be
-      // emitted inside scf.while's single-block regions by this lowering.
-      // TODO: can be improved to accept certain GOTOs within the loop
-      if (e.isA<Fortran::parser::GotoStmt>() ||
-          e.isA<Fortran::parser::ComputedGotoStmt>() ||
-          e.isA<Fortran::parser::AssignedGotoStmt>() ||
-          e.isA<Fortran::parser::ArithmeticIfStmt>() ||
-          e.isA<Fortran::parser::ExitStmt>() ||
-          e.isA<Fortran::parser::CycleStmt>())
-        return true;
-
-      if (e.controlSuccessor) {
-        if (!e.isConstruct() && !e.isConstructStmt())
-          return true;
-        if (branchTargetIsEarlyExit(*e.controlSuccessor))
-          return true;
-      }
-
-      // Statements that exit the procedure are early exits from the loop.
-      if (e.isA<Fortran::parser::ReturnStmt>() ||
-          e.isA<Fortran::parser::StopStmt>() ||
-          e.isA<Fortran::parser::FailImageStmt>())
-        return true;
-
-      if (!e.evaluationList)
-        return false;
-      for (const Fortran::lower::pft::Evaluation &nested : *e.evaluationList)
-        if (hasEarlyExitInSubtree(nested))
-          return true;
-      return false;
-    };
-
-    if (outerDoWhile.evaluationList)
-      for (const Fortran::lower::pft::Evaluation &nested :
-           *outerDoWhile.evaluationList)
-        if (hasEarlyExitInSubtree(nested))
-          return true;
-    return false;
-  }
-
   void
   genDoWhileAsSCFWhile(const Fortran::parser::ScalarLogicalExpr &whileCondition,
                        Fortran::lower::pft::Evaluation &doConstructEval,
@@ -2598,10 +2534,7 @@ class FirConverter : public Fortran::lower::AbstractConverter {
       // etc.) by requiring that the loop body is structured (as decided by the
       // PFT branch analysis), allowing the loop to exit only when the condition
       // becomes false.
-      Fortran::lower::pft::Evaluation &endDoEval =
-          eval.getLastNestedEvaluation();
-      if (lowerDoWhileToSCFWhile &&
-          !doWhileHasEarlyExit(eval, doStmtEval, endDoEval)) {
+      if (Fortran::lower::lowerDoWhileToSCFWhile && !unstructuredContext) {
         maybeStartBlock(preheaderBlock); // no block or empty block
         genDoWhileAsSCFWhile(*whileCondition, eval, doStmtEval);
         return;
diff --git a/flang/lib/Lower/PFTBuilder.cpp b/flang/lib/Lower/PFTBuilder.cpp
index 308db726f073c..ab4835150b620 100644
--- a/flang/lib/Lower/PFTBuilder.cpp
+++ b/flang/lib/Lower/PFTBuilder.cpp
@@ -25,9 +25,108 @@ static llvm::cl::opt<bool> clDisableStructuredFir(
     "no-structured-fir", llvm::cl::desc("disable generation of structured FIR"),
     llvm::cl::init(false), llvm::cl::Hidden);
 
+namespace Fortran::lower {
+llvm::cl::opt<bool> lowerDoWhileToSCFWhile(
+    "lower-do-while-to-scf-while", llvm::cl::init(false),
+    llvm::cl::desc("lower structured DO WHILE loops to scf.while"),
+    llvm::cl::Hidden);
+} // namespace Fortran::lower
+
 using namespace Fortran;
 
 namespace {
+/// Return true iff \p eval is a DO WHILE construct.
+static bool isDoWhile(const lower::pft::Evaluation &eval) {
+  if (!eval.isA<parser::DoConstruct>() || !eval.evaluationList ||
+      eval.evaluationList->empty())
+    return false;
+  const auto *doStmt =
+      eval.evaluationList->front().getIf<parser::NonLabelDoStmt>();
+  if (!doStmt)
+    return false;
+  const auto &loopControl =
+      std::get<std::optional<parser::LoopControl>>(doStmt->t);
+  if (!loopControl.has_value())
+    return false;
+  return std::get_if<parser::ScalarLogicalExpr>(&loopControl->u) != nullptr;
+}
+
+/// Return true iff \p doWhile has early-exit/branchy control flow in its body.
+static bool doWhileHasEarlyExit(const lower::pft::Evaluation &doWhileEval) {
+  if (!doWhileEval.evaluationList || doWhileEval.evaluationList->empty())
+    return true;
+  const lower::pft::Evaluation &doStmtEval =
+      doWhileEval.evaluationList->front();
+  const lower::pft::Evaluation &endDoEval = doWhileEval.evaluationList->back();
+
+  std::function<bool(const lower::pft::Evaluation &)> walk =
+      [&](const lower::pft::Evaluation &e) -> bool {
+    // Skip structural nodes for the loop itself.
+    if (&e == &doStmtEval || &e == &endDoEval)
+      return false;
+
+    // Any explicit branch statement is considered an early-exit/unsupported
+    // construct for structured DO WHILE lowering.
+    if (e.isA<parser::GotoStmt>() || e.isA<parser::ComputedGotoStmt>() ||
+        e.isA<parser::AssignedGotoStmt>() ||
+        e.isA<parser::ArithmeticIfStmt>() || e.isA<parser::ExitStmt>() ||
+        e.isA<parser::CycleStmt>())
+      return true;
+
+    // Statements that exit the procedure are early exits from the loop.
+    if (e.isA<parser::ReturnStmt>() || e.isA<parser::StopStmt>() ||
+        e.isA<parser::FailImageStmt>())
+      return true;
+
+    // CFG-style branches represented through controlSuccessor (e.g. I/O
+    // ERR/END/EOR labels) are not supported in structured DO WHILE.
+    if (e.controlSuccessor && e.isActionStmt() && !e.isConstructStmt())
+      return true;
+
+    // Recurse.
+    if (!e.evaluationList)
+      return false;
+    for (const lower::pft::Evaluation &nested : *e.evaluationList)
+      if (walk(nested))
+        return true;
+    return false;
+  };
+
+  if (doWhileEval.evaluationList)
+    for (const lower::pft::Evaluation &nested : *doWhileEval.evaluationList)
+      if (walk(nested))
+        return true;
+  return false;
+}
+
+/// Finalize the structured/unstructured classification for DO WHILE constructs.
+/// This is done after branch analysis has populated control-flow links; the
+/// early-exit scan refines the final decision for scf.while eligibility.
+static void classifyDoWhiles(lower::pft::Evaluation &eval) {
+  if (eval.evaluationList)
+    for (lower::pft::Evaluation &nested : *eval.evaluationList)
+      classifyDoWhiles(nested);
+
+  if (!isDoWhile(eval))
+    return;
+
+  // When the scf.while lowering is enabled, mark DO WHILE as structured unless
+  // it contains early exits / branchy control flow.
+  if (Fortran::lower::lowerDoWhileToSCFWhile) {
+    const bool unstructured = eval.isUnstructured || doWhileHasEarlyExit(eval);
+    if (unstructured) {
+      eval.isUnstructured = true;
+      // If we decide this DO WHILE must be lowered as unstructured CFG after
+      // branch analysis has already run, ensure the construct exit starts a new
+      // block so that Bridge.cpp can form a valid loop CFG (needs an explicit
+      // exit target block for the conditional branch).
+      if (eval.constructExit)
+        eval.constructExit->isNewBlock = true;
+    } else {
+      eval.isUnstructured = false;
+    }
+  }
+}
 /// Helpers to unveil parser node inside Fortran::parser::Statement<>,
 /// Fortran::parser::UnlabeledStatement, and Fortran::common::Indirection<>
 template <typename A>
@@ -489,6 +588,10 @@ class PFTBuilder {
     rewriteIfGotos();
     endFunctionBody();
     analyzeBranches(nullptr, *evaluationListStack.back()); // add branch links
+    // Finalize structured/unstructured DO WHILE classification when requested.
+    if (Fortran::lower::lowerDoWhileToSCFWhile && !evaluationListStack.empty())
+      for (auto &e : *evaluationListStack.back())
+        classifyDoWhiles(e);
     processEntryPoints();
     containsStmtStack.pop_back();
     popEvaluationList();
@@ -1060,7 +1163,11 @@ class PFTBuilder {
                 eval.isUnstructured = true; // real-valued loop control
             } else if (std::get_if<parser::ScalarLogicalExpr>(
                            &loopControl->u)) {
-              eval.isUnstructured = true; // while loop
+              // DO WHILE structured/unstructured classification is handled by
+              // branch analysis plus the classifyDoWhiles() post-pass below
+              // when -lower-do-while-to-scf-while is enabled.
+              if (!Fortran::lower::lowerDoWhileToSCFWhile)
+                eval.isUnstructured = true; // while loop
             }
           },
           [&](const parser::EndDoStmt &) {



More information about the flang-commits mailing list