[flang-commits] [flang] [flang] Delete a trailing CYCLE that is the last statement of its DO (PR #223399)
Kareem Ergawy via flang-commits
flang-commits at lists.llvm.org
Tue Sep 15 02:16:11 PDT 2026
https://github.com/ergawy updated https://github.com/llvm/llvm-project/pull/223399
>From 149f92e7fb3e41af55dd55927cf612ed17b2f40a Mon Sep 17 00:00:00 2001
From: ergawy <kareem.ergawy at gmail.com>
Date: Fri, 11 Sep 2026 15:21:03 -0700
Subject: [PATCH 1/2] [flang] Delete a trailing CYCLE that is the last
statement of its DO
A CycleStmt at the end of the body of its own DoConstruct is a no-op:
branching to the EndDoStmt and falling through to it are the same thing.
Branch analysis nonetheless marks the DoConstruct unstructured, which
costs the structured form of the loop and the induction variable
semantics that later passes rely on.
Delete such a CycleStmt in the PFT. The statement
must be unlabeled, so that it is not itself a branch target, and it must
name either no construct or this one.
---
flang/lib/Lower/PFTBuilder.cpp | 53 ++++++++
flang/test/Lower/OpenMP/trailing-cycle.f90 | 32 +++++
flang/test/Lower/trailing-cycle.f90 | 141 +++++++++++++++++++++
3 files changed, 226 insertions(+)
create mode 100644 flang/test/Lower/OpenMP/trailing-cycle.f90
create mode 100644 flang/test/Lower/trailing-cycle.f90
diff --git a/flang/lib/Lower/PFTBuilder.cpp b/flang/lib/Lower/PFTBuilder.cpp
index c37a75e90fe8e..b3becae2f2e6d 100644
--- a/flang/lib/Lower/PFTBuilder.cpp
+++ b/flang/lib/Lower/PFTBuilder.cpp
@@ -559,6 +559,7 @@ class PFTBuilder {
};
rewriteIfGotos();
+ rewriteTrailingCycle();
auto *eval = constructAndDirectiveStack.back();
if (eval->isExecutableDirective() && !isOpenMPLoopConstruct(eval)) {
// A construct at the end of an (unstructured) OpenACC or OpenMP
@@ -677,6 +678,58 @@ class PFTBuilder {
evaluationListStack.pop_back();
}
+ /// Delete a CycleStmt that is the last statement of the body of its own
+ /// DoConstruct, where it is a no-op. The pre-branch-analysis code:
+ ///
+ /// <<DoConstruct>>
+ /// 1 NonLabelDoStmt: do n = 1, nb
+ /// 2 Statement: ...
+ /// 3 CycleStmt: cycle
+ /// 4 EndDoStmt
+ /// <<End DoConstruct>>
+ ///
+ /// becomes:
+ ///
+ /// <<DoConstruct>>
+ /// 1 NonLabelDoStmt: do n = 1, nb
+ /// 2 Statement: ...
+ /// 4 EndDoStmt
+ /// <<End DoConstruct>>
+ ///
+ /// Branching to the EndDoStmt and falling through to it are the same thing,
+ /// so the CycleStmt has no effect. Deleting it matters because branch
+ /// analysis otherwise marks the DoConstruct unstructured, which costs the
+ /// structured form of the loop -- and with it the induction variable
+ /// semantics that later passes rely on.
+ void rewriteTrailingCycle() {
+ auto &evaluationList = *evaluationListStack.back();
+ if (evaluationList.size() < 3)
+ return;
+ const auto *doStmt =
+ evaluationList.begin()->getIf<parser::NonLabelDoStmt>();
+ if (!doStmt)
+ return;
+ lower::pft::EvaluationList::iterator endDoStmtIt =
+ std::prev(evaluationList.end());
+ if (!endDoStmtIt->isA<parser::EndDoStmt>())
+ return;
+ lower::pft::EvaluationList::iterator cycleStmtIt = std::prev(endDoStmtIt);
+ const auto *cycleStmt = cycleStmtIt->getIf<parser::CycleStmt>();
+ if (!cycleStmt || cycleStmtIt->label)
+ return;
+ std::string cycleName = getConstructName(*cycleStmt);
+ if (!cycleName.empty() && cycleName != getConstructName(*doStmt))
+ return; // cycle for an outer construct
+ // Relink the lexical predecessor of the CycleStmt to the EndDoStmt. That
+ // predecessor is the last statement reachable from the preceding
+ // evaluation, so descend through nested evaluation lists to find it.
+ lower::pft::Evaluation *predecessor = &*std::prev(cycleStmtIt);
+ while (predecessor->evaluationList && !predecessor->evaluationList->empty())
+ predecessor = &predecessor->evaluationList->back();
+ predecessor->lexicalSuccessor = cycleStmtIt->lexicalSuccessor;
+ evaluationList.erase(cycleStmtIt);
+ }
+
/// Rewrite IfConstructs containing a GotoStmt or CycleStmt to eliminate an
/// unstructured branch and a trivial basic block. The pre-branch-analysis
/// code:
diff --git a/flang/test/Lower/OpenMP/trailing-cycle.f90 b/flang/test/Lower/OpenMP/trailing-cycle.f90
new file mode 100644
index 0000000000000..73e987316a982
--- /dev/null
+++ b/flang/test/Lower/OpenMP/trailing-cycle.f90
@@ -0,0 +1,32 @@
+! RUN: %flang_fc1 -fopenmp -fdebug-dump-pft %s 2>&1 | FileCheck %s
+
+! A CYCLE that is the last statement of the body of its own DO is deleted, and
+! the lexical predecessor it is unlinked from is the last *statement* reachable
+! from the preceding evaluation, found by descending through nested evaluation
+! lists.
+
+! CHECK: 1 Subroutine s
+subroutine s(a, n)
+ integer :: n, i, j
+ real :: a(n)
+
+ ! CHECK: <<DoConstruct>> -> 8
+ ! CHECK: 1 NonLabelDoStmt -> 7: do i = 1, n
+ ! CHECK: 2 <<^OpenMPConstruct>>
+ ! CHECK: <<DoConstruct>> -> 7
+ ! CHECK: 3 NonLabelDoStmt -> 5: do j = 1, n
+ ! CHECK: 4 ^AssignmentStmt: a(j) = 1.0
+ ! CHECK: 5 EndDoStmt -> 3: end do
+ ! CHECK: <<End DoConstruct>>
+ ! CHECK: <<End OpenMPConstruct>>
+ ! CHECK: 7 EndDoStmt -> 1: end do
+ ! CHECK: <<End DoConstruct>>
+ do i = 1, n
+ !$omp do
+ do j = 1, n
+ a(j) = 1.0
+ end do
+ cycle
+ end do
+ ! CHECK: 8 EndSubroutineStmt
+end subroutine s
diff --git a/flang/test/Lower/trailing-cycle.f90 b/flang/test/Lower/trailing-cycle.f90
new file mode 100644
index 0000000000000..ed4ca7f17f0d5
--- /dev/null
+++ b/flang/test/Lower/trailing-cycle.f90
@@ -0,0 +1,141 @@
+! RUN: %flang_fc1 -fdebug-dump-pft %s 2>&1 | FileCheck %s
+
+! A CYCLE that is the last statement of the body of its own DO is a no-op, and
+! is deleted so that the DO is not marked unstructured. A trailing `!' on a
+! construct name in the dump marks it unstructured.
+
+! CHECK: 1 Subroutine trailing_cycle
+subroutine trailing_cycle(a, n)
+ integer :: n, i, j
+ real :: a(n)
+
+ ! The CYCLE is deleted; the DoConstruct stays structured.
+ ! CHECK: <<DoConstruct>> -> 5
+ ! CHECK: 1 NonLabelDoStmt -> 4: do i = 1, n
+ ! CHECK: 2 ^AssignmentStmt: a(i) = 1.0
+ ! CHECK: 4 EndDoStmt -> 1: end do
+ ! CHECK: <<End DoConstruct>>
+ do i = 1, n
+ a(i) = 1.0
+ cycle
+ end do
+
+ ! Same, with the CYCLE naming its own construct.
+ ! CHECK: <<DoConstruct>> -> 9
+ ! CHECK: 5 NonLabelDoStmt -> 8: loop: do i = 1, n
+ ! CHECK: 6 ^AssignmentStmt: a(i) = 2.0
+ ! CHECK: 8 EndDoStmt -> 5: end do loop
+ ! CHECK: <<End DoConstruct>>
+ loop: do i = 1, n
+ a(i) = 2.0
+ cycle loop
+ end do loop
+
+ ! The CYCLE is the whole body.
+ ! CHECK: <<DoConstruct>> -> 12
+ ! CHECK: 9 NonLabelDoStmt -> 11: do i = 1, n
+ ! CHECK: 11 ^EndDoStmt -> 9: end do
+ ! CHECK: <<End DoConstruct>>
+ do i = 1, n
+ cycle
+ end do
+
+ ! A CYCLE for an outer construct is a real branch and is kept.
+ ! CHECK: <<DoConstruct>> -> 18
+ ! CHECK: 12 NonLabelDoStmt -> 17: outer: do i = 1, n
+ ! CHECK: <<DoConstruct!>> -> 17
+ ! CHECK: 13 ^NonLabelDoStmt -> 16: do j = 1, n
+ ! CHECK: 14 ^AssignmentStmt: a(j) = 3.0
+ ! CHECK: 15 CycleStmt! -> 17: cycle outer
+ ! CHECK: 16 ^EndDoStmt -> 13: end do
+ ! CHECK: <<End DoConstruct!>>
+ ! CHECK: 17 ^EndDoStmt -> 12: end do outer
+ ! CHECK: <<End DoConstruct>>
+ outer: do i = 1, n
+ do j = 1, n
+ a(j) = 3.0
+ cycle outer
+ end do
+ end do outer
+
+ ! A labeled CYCLE may be a branch target and is kept.
+ ! CHECK: <<DoConstruct!>> -> 25
+ ! CHECK: 18 NonLabelDoStmt -> 24: do i = 1, n
+ ! CHECK: <<IfConstruct>> -> 23
+ ! CHECK: 19 ^IfStmt [negate] -> 23: if(a(i) > 0.0) goto 10
+ ! CHECK: 22 ^AssignmentStmt: a(i) = 4.0
+ ! CHECK: 21 EndIfStmt
+ ! CHECK: <<End IfConstruct>>
+ ! CHECK: 23 CycleStmt! -> 24: 10 cycle
+ ! CHECK: 24 ^EndDoStmt -> 18: end do
+ ! CHECK: <<End DoConstruct!>>
+ do i = 1, n
+ if (a(i) > 0.0) goto 10
+ a(i) = 4.0
+10 cycle
+ end do
+
+ ! A CYCLE that is not last is a real branch and is kept.
+ ! CHECK: <<DoConstruct!>> -> 29
+ ! CHECK: 25 ^NonLabelDoStmt -> 28: do i = 1, n
+ ! CHECK: 26 ^CycleStmt! -> 28: cycle
+ ! CHECK: 27 ^AssignmentStmt: a(i) = 5.0
+ ! CHECK: 28 ^EndDoStmt -> 25: end do
+ ! CHECK: <<End DoConstruct!>>
+ do i = 1, n
+ cycle
+ a(i) = 5.0
+ end do
+
+ ! The lexical predecessor of the CYCLE is the last statement of the
+ ! preceding construct, not the construct itself. Getting that wrong leaves
+ ! the EndIfStmt pointing at the deleted CycleStmt, which shows up here as
+ ! `<<IfConstruct>> -> 0'.
+ ! CHECK: <<DoConstruct>> -> 35
+ ! CHECK: 29 ^NonLabelDoStmt -> 34: do i = 1, n
+ ! CHECK: <<IfConstruct>> -> 34
+ ! CHECK: 30 ^IfThenStmt -> 34: if(a(i) > 0.0) then
+ ! CHECK: 31 ^AssignmentStmt: a(i) = 7.0
+ ! CHECK: 32 EndIfStmt: end if
+ ! CHECK: <<End IfConstruct>>
+ ! CHECK: 34 EndDoStmt -> 29: end do
+ ! CHECK: <<End DoConstruct>>
+ do i = 1, n
+ if (a(i) > 0.0) then
+ a(i) = 7.0
+ end if
+ cycle
+ end do
+
+ ! Same, with a nested DoConstruct as the preceding construct. Here a missing
+ ! descent sends the inner loop's exit to 41 -- past the outer EndDoStmt.
+ ! CHECK: <<DoConstruct>> -> 41
+ ! CHECK: 35 NonLabelDoStmt -> 40: do i = 1, n
+ ! CHECK: <<DoConstruct>> -> 40
+ ! CHECK: 36 ^NonLabelDoStmt -> 38: do j = 1, n
+ ! CHECK: 37 ^AssignmentStmt: a(j) = 8.0
+ ! CHECK: 38 EndDoStmt -> 36: end do
+ ! CHECK: <<End DoConstruct>>
+ ! CHECK: 40 EndDoStmt -> 35: end do
+ ! CHECK: <<End DoConstruct>>
+ do i = 1, n
+ do j = 1, n
+ a(j) = 8.0
+ end do
+ cycle
+ end do
+
+ ! A trailing CYCLE preceded by an `if (cond) cycle'. The two rewrites have no
+ ! strict ordering requirement here, because either way the surrounding DO
+ ! comes out structured, which is the property that matters.
+ !
+ ! CHECK: <<DoConstruct>> -> {{[0-9]+}}
+ ! CHECK: {{[0-9]+}} {{\^?}}NonLabelDoStmt -> {{[0-9]+}}: do i = 1, n
+ ! CHECK: {{[0-9]+}} ^IfStmt [negate] -> {{[0-9]+}}: if(a(i) > 0.0) cycle
+ ! CHECK: <<End DoConstruct>>
+ do i = 1, n
+ if (a(i) > 0.0) cycle
+ a(i) = 6.0
+ cycle
+ end do
+end subroutine trailing_cycle
>From 8756ee115ca429c2d5dc209d1615f2daf30ea723 Mon Sep 17 00:00:00 2001
From: ergawy <kareem.ergawy at gmail.com>
Date: Tue, 15 Sep 2026 02:15:48 -0700
Subject: [PATCH 2/2] Add assertion
---
flang/lib/Lower/PFTBuilder.cpp | 1 +
1 file changed, 1 insertion(+)
diff --git a/flang/lib/Lower/PFTBuilder.cpp b/flang/lib/Lower/PFTBuilder.cpp
index b3becae2f2e6d..afaa14f59e826 100644
--- a/flang/lib/Lower/PFTBuilder.cpp
+++ b/flang/lib/Lower/PFTBuilder.cpp
@@ -726,6 +726,7 @@ class PFTBuilder {
lower::pft::Evaluation *predecessor = &*std::prev(cycleStmtIt);
while (predecessor->evaluationList && !predecessor->evaluationList->empty())
predecessor = &predecessor->evaluationList->back();
+ assert(predecessor->lexicalSuccessor == &*cycleStmtIt);
predecessor->lexicalSuccessor = cycleStmtIt->lexicalSuccessor;
evaluationList.erase(cycleStmtIt);
}
More information about the flang-commits
mailing list