[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
Mon Jan 26 09:58: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/7] 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/7] 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/7] 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/7] 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/7] 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/7] 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 &) {
>From f5b6206fcba5b874335455a404a3db0eccafb4c6 Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Mon, 26 Jan 2026 09:58:27 -0800
Subject: [PATCH 7/7] remove cl options in bridge.cpp
---
flang/lib/Lower/Bridge.cpp | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 16f94ca4eabca..495b535076fa3 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -91,10 +91,6 @@ 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"));
-namespace Fortran::lower {
-extern llvm::cl::opt<bool> lowerDoWhileToSCFWhile;
-} // namespace Fortran::lower
-
namespace {
/// Information for generating a structured or unstructured increment loop.
struct IncrementLoopInfo {
@@ -2534,7 +2530,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.
- if (Fortran::lower::lowerDoWhileToSCFWhile && !unstructuredContext) {
+ if (!unstructuredContext) {
maybeStartBlock(preheaderBlock); // no block or empty block
genDoWhileAsSCFWhile(*whileCondition, eval, doStmtEval);
return;
More information about the flang-commits
mailing list