[flang-commits] [flang] 11576ed - [flang] Delete a trailing CYCLE that is the last statement of its DO (#223399)
via flang-commits
flang-commits at lists.llvm.org
Tue Sep 15 03:03:41 PDT 2026
Author: Kareem Ergawy
Date: 2026-09-15T12:03:36+02:00
New Revision: 11576edbc8ab574ceacd9a624c08f730a5a7676b
URL: https://github.com/llvm/llvm-project/commit/11576edbc8ab574ceacd9a624c08f730a5a7676b
DIFF: https://github.com/llvm/llvm-project/commit/11576edbc8ab574ceacd9a624c08f730a5a7676b.diff
LOG: [flang] Delete a trailing CYCLE that is the last statement of its DO (#223399)
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.
Added:
flang/test/Lower/OpenMP/trailing-cycle.f90
flang/test/Lower/trailing-cycle.f90
Modified:
flang/lib/Lower/PFTBuilder.cpp
Removed:
################################################################################
diff --git a/flang/lib/Lower/PFTBuilder.cpp b/flang/lib/Lower/PFTBuilder.cpp
index c37a75e90fe8e..afaa14f59e826 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,59 @@ 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();
+ assert(predecessor->lexicalSuccessor == &*cycleStmtIt);
+ 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
More information about the flang-commits
mailing list