[llvm-branch-commits] [flang] [flang] Detect loops whose branching is confined to their body (PR #225757)

Kareem Ergawy via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Thu Sep 24 01:59:54 PDT 2026


https://github.com/ergawy updated https://github.com/llvm/llvm-project/pull/225757

>From 4e323a69815056d56fb287b136ab008dee54f3db Mon Sep 17 00:00:00 2001
From: ergawy <kareem.ergawy at gmail.com>
Date: Mon, 21 Sep 2026 06:16:09 -0700
Subject: [PATCH] [flang] Detect loops whose branching is confined to their
 body

A DO loop is classified as either structured or unstructured, and a single
raw branch anywhere in its body forces the loop -- and every construct
enclosing it -- onto the unstructured path.

That is stronger than necessary. A loop keeps its structured control flow
as long as its branching neither leaves its body nor enters it from
outside. Classify such a loop separately from a fully unstructured one.

This only classifies: lowering is unchanged. PFT dumps mark the new
classification with '~', which is what the tests key on.
---
 flang/include/flang/Lower/PFTBuilder.h        |  57 ++++-
 flang/lib/Lower/PFTBuilder.cpp                | 214 ++++++++++++++++--
 .../pre-fir-tree-unstructured-internals.f90   | 137 +++++++++++
 flang/test/Lower/trailing-cycle.f90           |  15 +-
 4 files changed, 388 insertions(+), 35 deletions(-)
 create mode 100644 flang/test/Lower/pre-fir-tree-unstructured-internals.f90

diff --git a/flang/include/flang/Lower/PFTBuilder.h b/flang/include/flang/Lower/PFTBuilder.h
index 7e748ae554a20..930214244c74c 100644
--- a/flang/include/flang/Lower/PFTBuilder.h
+++ b/flang/include/flang/Lower/PFTBuilder.h
@@ -28,6 +28,7 @@
 #include "flang/Semantics/symbol.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/raw_ostream.h"
+#include <algorithm>
 
 namespace Fortran::lower::pft {
 
@@ -322,6 +323,50 @@ struct Evaluation : EvaluationVariant {
   /// Return the FunctionLikeUnit containing this evaluation (or nullptr).
   FunctionLikeUnit *getOwningProcedure() const;
 
+  /// How this evaluation's control flow is lowered. Ordered by how much it
+  /// constrains lowering so that classification can only strengthen; see
+  /// markControlFlow.
+  enum class ControlFlow {
+    /// Lowered structurally
+    Structured,
+    /// Lowered structurally, but the body holds unstructured control flow
+    /// confined to it. Lowering folds that body -- not the construct, and not
+    /// the loop control -- into a parent region, so the structured op's
+    /// single-block region stays well formed.
+    ///
+    /// For now, this only applies to DO constructs.
+    StructuredWithUnstructuredInternals,
+    /// Lowered as raw CFG blocks.
+    Unstructured,
+  };
+
+  /// Strengthen the classification to \p kind; it never weakens. This is what
+  /// makes the analysis order-independent: a construct marked Unstructured by
+  /// any one child stays Unstructured whatever its siblings contribute.
+  void markControlFlow(ControlFlow kind) {
+    controlFlow = std::max(controlFlow, kind);
+  }
+
+  void markUnstructured() { markControlFlow(ControlFlow::Unstructured); }
+
+  /// Lower the classification to \p kind. Only for an analysis that has proven
+  /// a stronger classification unnecessary; every other caller wants
+  /// markControlFlow, which never weakens.
+  void weakenControlFlow(ControlFlow kind) {
+    controlFlow = std::min(controlFlow, kind);
+  }
+
+  /// True when control flow is not fully structured, category (c) included.
+  /// Existing consumers ask this to decide whether raw blocks are needed, and
+  /// a category (c) construct still needs them until its body is wrapped, so
+  /// it must answer true here. Use hasUnstructuredInternals() to single out
+  /// category (c) itself.
+  bool isUnstructured() const { return controlFlow != ControlFlow::Structured; }
+
+  bool hasUnstructuredInternals() const {
+    return controlFlow == ControlFlow::StructuredWithUnstructuredInternals;
+  }
+
   bool lowerAsStructured() const;
   bool lowerAsUnstructured() const;
   bool forceAsUnstructured() const;
@@ -339,11 +384,11 @@ struct Evaluation : EvaluationVariant {
   // from anywhere within the construct.
   //
   // An unstructured construct is one that contains some form of goto. This
-  // is indicated by the isUnstructured member flag, which may be set on a
-  // statement and propagated to enclosing constructs. This distinction allows
-  // a structured IF or DO statement to be materialized with custom structured
-  // FIR operations. An unstructured statement is materialized as mlir
-  // operation sequences that include explicit branches.
+  // is indicated by the controlFlow member, which may be set on a statement and
+  // propagated to enclosing constructs. This distinction allows a structured IF
+  // or DO statement to be materialized with custom structured FIR operations.
+  // An unstructured statement is materialized as mlir operation sequences that
+  // include explicit branches.
   //
   // The block member is set for statements that begin a new block. This
   // block is the target of any branch to the statement. Statements may have
@@ -374,7 +419,7 @@ struct Evaluation : EvaluationVariant {
   llvm::SmallVector<Evaluation *, 0> extraControlSuccessors;
   Evaluation *constructExit{nullptr};    // set for constructs
   bool isNewBlock{false};                // evaluation begins a new basic block
-  bool isUnstructured{false};  // evaluation has unstructured control flow
+  ControlFlow controlFlow{ControlFlow::Structured};
   bool negateCondition{false}; // If[Then]Stmt condition must be negated
   bool activeConstruct{false}; // temporarily set for some constructs
   // The enclosing evaluation-list traversal should skip this evaluation once
diff --git a/flang/lib/Lower/PFTBuilder.cpp b/flang/lib/Lower/PFTBuilder.cpp
index e6a4d11904679..948589a11b7d7 100644
--- a/flang/lib/Lower/PFTBuilder.cpp
+++ b/flang/lib/Lower/PFTBuilder.cpp
@@ -34,6 +34,9 @@ llvm::cl::opt<bool> wrapUnstructuredConstructsInExecuteRegion(
 
 using namespace Fortran;
 
+static void detectStructuredWithUnstructuredInternals(
+    Fortran::lower::pft::FunctionLikeUnit &unit);
+
 namespace {
 static llvm::cl::opt<bool> lowerDoWhileToSCFWhile(
     "lower-do-while-to-scf-while", llvm::cl::init(false),
@@ -525,11 +528,16 @@ class PFTBuilder {
   }
 
   void exitFunction() {
+    lower::pft::FunctionLikeUnit *exitingUnit = currentFunctionUnit;
     currentFunctionUnit = nullptr; // Clear when exiting function
     rewriteIfGotos();
     endFunctionBody();
     analyzeBranches(nullptr, *evaluationListStack.back()); // add branch links
 
+    // Branch analysis is complete, so the incoming-branch map is too.
+    if (exitingUnit)
+      detectStructuredWithUnstructuredInternals(*exitingUnit);
+
     processEntryPoints();
     containsStmtStack.pop_back();
     popEvaluationList();
@@ -862,7 +870,7 @@ class PFTBuilder {
       if (const auto *expr = std::get_if<parser::Expr>(&format.u)) {
         if (semantics::ExprHasTypeCategory(*semantics::GetExpr(*expr),
                                            common::TypeCategory::Integer))
-          eval.isUnstructured = true;
+          eval.markUnstructured();
       }
     };
     auto analyzeSpecs{[&](const auto &specList) {
@@ -916,7 +924,7 @@ class PFTBuilder {
   /// Mark the target of a branch as a new block.
   void markBranchTarget(lower::pft::Evaluation &sourceEvaluation,
                         lower::pft::Evaluation &targetEvaluation) {
-    sourceEvaluation.isUnstructured = true;
+    sourceEvaluation.markUnstructured();
     if (!sourceEvaluation.controlSuccessor)
       sourceEvaluation.controlSuccessor = &targetEvaluation;
     else if (sourceEvaluation.controlSuccessor != &targetEvaluation &&
@@ -942,11 +950,11 @@ class PFTBuilder {
       if (sourceConstruct != targetConstruct) // branch into a construct body
         for (lower::pft::Evaluation *eval = &targetEvaluation; eval;
              eval = eval->parentConstruct) {
-          eval->isUnstructured = true;
+          eval->markUnstructured();
           // If the branch is a backward branch into an already analyzed
           // DO or IF construct, mark the construct exit as a new block.
-          // For a forward branch, the isUnstructured flag will cause this
-          // to be done when the construct is analyzed.
+          // For a forward branch, the Unstructured classification will cause
+          // this to be done when the construct is analyzed.
           if (eval->constructExit && (eval->isA<parser::DoConstruct>() ||
                                       eval->isA<parser::IfConstruct>()))
             eval->constructExit->isNewBlock = true;
@@ -1051,7 +1059,7 @@ class PFTBuilder {
             markBranchTarget(eval, *construct->constructExit);
           },
           [&](const parser::FailImageStmt &) {
-            eval.isUnstructured = true;
+            eval.markUnstructured();
             if (eval.lexicalSuccessor->lexicalSuccessor)
               markSuccessorAsNewBlock(eval);
           },
@@ -1061,12 +1069,12 @@ class PFTBuilder {
             lastConstructStmtEvaluation = &eval;
           },
           [&](const parser::ReturnStmt &) {
-            eval.isUnstructured = true;
+            eval.markUnstructured();
             if (eval.lexicalSuccessor->lexicalSuccessor)
               markSuccessorAsNewBlock(eval);
           },
           [&](const parser::StopStmt &) {
-            eval.isUnstructured = true;
+            eval.markUnstructured();
             if (eval.lexicalSuccessor->lexicalSuccessor)
               markSuccessorAsNewBlock(eval);
           },
@@ -1094,7 +1102,7 @@ class PFTBuilder {
               target->isNewBlock = true;
               for (lower::pft::Evaluation *parent = target->parentConstruct;
                    parent; parent = parent->parentConstruct) {
-                parent->isUnstructured = true;
+                parent->markUnstructured();
                 // The exit of an enclosing DO or IF construct is a new block.
                 if (parent->constructExit &&
                     (parent->isA<parser::DoConstruct>() ||
@@ -1140,7 +1148,7 @@ class PFTBuilder {
                 for (auto label : iter->second)
                   markIfBranchTarget(label);
             }
-            eval.isUnstructured = true;
+            eval.markUnstructured();
             markSuccessorAsNewBlock(eval);
           },
 
@@ -1181,7 +1189,7 @@ class PFTBuilder {
             const auto &loopControl =
                 std::get<std::optional<parser::LoopControl>>(s.t);
             if (!loopControl.has_value()) {
-              eval.isUnstructured = true; // infinite loop
+              eval.markUnstructured(); // infinite loop
               return;
             }
             eval.nonNopSuccessor().isNewBlock = true;
@@ -1190,13 +1198,13 @@ class PFTBuilder {
                     std::get_if<parser::LoopControl::Bounds>(&loopControl->u)) {
               if (bounds->Name().thing.symbol->GetType()->IsNumeric(
                       common::TypeCategory::Real))
-                eval.isUnstructured = true; // real-valued loop control
+                eval.markUnstructured(); // real-valued loop control
             } else if (std::get_if<parser::ScalarLogicalExpr>(
                            &loopControl->u)) {
               // Leave DO WHILE structured when -lower-do-while-to-scf-while is
               // enabled; branch analysis will mark unstructured cases.
               if (!lowerDoWhileToSCFWhile)
-                eval.isUnstructured = true; // while loop
+                eval.markUnstructured(); // while loop
             }
           },
           [&](const parser::EndDoStmt &) {
@@ -1277,7 +1285,7 @@ class PFTBuilder {
           },
           [&](const parser::CaseConstruct &) {
             eval.constructExit = &eval.evaluationList->back();
-            eval.isUnstructured = true;
+            eval.markUnstructured();
           },
           [&](const parser::ChangeTeamConstruct &) {
             eval.constructExit = &eval.evaluationList->back();
@@ -1290,11 +1298,11 @@ class PFTBuilder {
           [&](const parser::IfConstruct &) { setConstructExit(eval); },
           [&](const parser::SelectRankConstruct &) {
             eval.constructExit = &eval.evaluationList->back();
-            eval.isUnstructured = true;
+            eval.markUnstructured();
           },
           [&](const parser::SelectTypeConstruct &) {
             eval.constructExit = &eval.evaluationList->back();
-            eval.isUnstructured = true;
+            eval.markUnstructured();
           },
           [&](const parser::WhereConstruct &) { setConstructExit(eval); },
 
@@ -1315,12 +1323,12 @@ class PFTBuilder {
       if (eval.evaluationList)
         analyzeBranches(&eval, *eval.evaluationList);
 
-      // Propagate isUnstructured flag to enclosing construct -- unless the
-      // wrap pass will fold this construct into a self-contained
+      // Propagate the Unstructured classification to the enclosing construct --
+      // unless the wrap pass will fold this construct into a self-contained
       // scf.execute_region, in which case the parent sees only a single op.
-      if (parentConstruct && eval.isUnstructured &&
+      if (parentConstruct && eval.isUnstructured() &&
           !lower::pft::isWrappableConstruct(eval, semanticsContext))
-        parentConstruct->isUnstructured = true;
+        parentConstruct->markUnstructured();
 
       // The successor of a branch starts a new block.
       if (eval.controlSuccessor && eval.isActionStmt() &&
@@ -1472,7 +1480,11 @@ class PFTDumper {
                       const std::string &indentString, int indent = 1) {
     llvm::StringRef name = evaluationName(eval);
     llvm::StringRef newBlock = eval.isNewBlock ? "^" : "";
-    llvm::StringRef bang = eval.isUnstructured ? "!" : "";
+    // "!" marks an unstructured evaluation. "~" marks one that is structured
+    // on the outside but has unstructured internals.
+    llvm::StringRef bang = eval.hasUnstructuredInternals()
+                               ? "~"
+                               : (eval.isUnstructured() ? "!" : "");
     outputStream << indentString;
     if (eval.printIndex)
       outputStream << eval.printIndex << ' ';
@@ -1720,7 +1732,7 @@ bool Fortran::lower::pft::Evaluation::lowerAsStructured() const {
 }
 
 bool Fortran::lower::pft::Evaluation::lowerAsUnstructured() const {
-  return isUnstructured || clDisableStructuredFir;
+  return isUnstructured() || clDisableStructuredFir;
 }
 
 bool Fortran::lower::pft::Evaluation::forceAsUnstructured() const {
@@ -2750,13 +2762,169 @@ static bool isOmpLoopBody(const Fortran::lower::pft::Evaluation &eval,
   return isAssociatedLoop(chain, loop->GetNestedLoop(), n);
 }
 
+/// The evaluations forming a loop's body: everything between the loop control
+/// statements, which bracket it and are lowered outside any body wrap.
+static llvm::iterator_range<Fortran::lower::pft::EvaluationList::const_iterator>
+loopBodyRange(const Fortran::lower::pft::Evaluation &loop) {
+  const auto &list = *loop.evaluationList;
+  return llvm::make_range(std::next(list.begin()), std::prev(list.end()));
+}
+
+/// True when \p eval lies in \p loop's body rather than in its loop control.
+static bool isInLoopBody(const Fortran::lower::pft::Evaluation *eval,
+                         const Fortran::lower::pft::Evaluation &loop) {
+  if (!eval || !loop.evaluationList || loop.evaluationList->empty())
+    return false;
+  const Fortran::lower::pft::Evaluation *first = &loop.evaluationList->front();
+  const Fortran::lower::pft::Evaluation *last = &loop.evaluationList->back();
+  for (const Fortran::lower::pft::Evaluation *p = eval; p;
+       p = p->parentConstruct)
+    if (p->parentConstruct == &loop)
+      return p != first && p != last;
+  return false;
+}
+
+/// A loop that can be lowered structurally even though its body holds
+/// unstructured control flow, because that control flow is confined to the body
+/// and can be folded into an SESE region.
+///
+/// The loop qualifies when:
+///   1. every branch leaving its body lands back inside that body, and
+///   2. every branch into its body comes from inside that body,
+/// and the body holds nothing a region cannot accommodate: an infinite DO
+/// never reaches the region's yield so RegionDCE would drop it, a ReturnStmt
+/// builds the function's final block in the current region, and a listless
+/// assigned GO TO has targets that cannot be enumerated -- so condition 1
+/// cannot be decided at all rather than merely failing.
+///
+/// A CYCLE is not an escape: its target is the EndDoStmt, which is where the
+/// wrap's yield sits, so it lands on the boundary. An EXIT targets the
+/// construct exit, beyond the loop entirely, and does escape.
+static bool isStructurableWithUnstructuredInternals(
+    const Fortran::lower::pft::Evaluation &loop,
+    const Fortran::lower::pft::FunctionLikeUnit &unit) {
+
+  if (!loop.isUnstructured() || !loop.evaluationList ||
+      loop.evaluationList->size() < 3)
+    return false;
+
+  // Only an increment loop keeps all of its control outside the body. A DO
+  // WHILE or an infinite DO lowers through a header block and a back edge, and
+  // a do concurrent has no plain bounds triple either, so in each case the
+  // loop's own control flow runs through the body a wrap would cover.
+  const auto *doConstruct = loop.getIf<parser::DoConstruct>();
+  if (!doConstruct)
+    return false;
+
+  const auto &loopControl = doConstruct->GetLoopControl();
+  if (!loopControl)
+    return false;
+
+  const auto *bounds =
+      std::get_if<parser::LoopControl::Bounds>(&loopControl->u);
+  if (!bounds)
+    return false;
+
+  // A REAL control variable does not lower to fir.do_loop, whose induction
+  // variable must be a signless integer or index, so such a loop is lowered as
+  // raw CFG whatever its body looks like.
+  const semantics::Symbol *ctrlVar = bounds->Name().thing.symbol;
+  if (!ctrlVar)
+    return false;
+
+  const semantics::DeclTypeSpec *ctrlType = ctrlVar->GetType();
+  if (!ctrlType || ctrlType->category() != semantics::DeclTypeSpec::Numeric ||
+      ctrlType->numericTypeSpec().category() != common::TypeCategory::Integer)
+    return false;
+
+  const Fortran::lower::pft::Evaluation *endDoStmt =
+      &loop.evaluationList->back();
+
+  auto isInfiniteDo = [](const parser::DoConstruct *d) {
+    return d && !d->GetLoopControl().has_value();
+  };
+
+  auto targetEscapes = [&](const Fortran::lower::pft::Evaluation *target) {
+    return target != endDoStmt && !isInLoopBody(target, loop);
+  };
+
+  std::function<bool(const Fortran::lower::pft::Evaluation &)> check =
+      [&](const Fortran::lower::pft::Evaluation &e) -> bool {
+    if (e.isA<parser::ReturnStmt>() ||
+        isInfiniteDo(e.getIf<parser::DoConstruct>()))
+      return false;
+
+    if (const auto *g = e.getIf<parser::AssignedGotoStmt>())
+      if (std::get<std::list<parser::Label>>(g->t).empty())
+        return false;
+
+    // Condition 1: nothing leaves the body, CYCLE excepted.
+    if (e.controlSuccessor && targetEscapes(e.controlSuccessor))
+      return false;
+
+    for (const Fortran::lower::pft::Evaluation *extra :
+         e.extraControlSuccessors)
+      if (targetEscapes(extra))
+        return false;
+
+    // Condition 2: nothing enters the body from outside it. This is the
+    // lookup the incoming-branch map exists for.
+    auto it = unit.incomingBranches.find(&e);
+    if (it != unit.incomingBranches.end())
+      for (const Fortran::lower::pft::Evaluation *src : it->second)
+        if (!isInLoopBody(src, loop))
+          return false;
+
+    if (e.evaluationList)
+      for (const Fortran::lower::pft::Evaluation &nested : *e.evaluationList)
+        if (!check(nested))
+          return false;
+
+    return true;
+  };
+
+  for (const Fortran::lower::pft::Evaluation &e : loopBodyRange(loop))
+    if (!check(e))
+      return false;
+
+  return true;
+}
+
+/// Reclassify every qualifying loop in \p unit.
+///
+/// Runs after branch analysis, when the incoming-branch map is complete;
+/// during analysis a branch later in the function would not yet be recorded
+/// and condition 2 would read a partial map.
+///
+/// Ancestors are deliberately left alone. A loop reclassified here lowers to a
+/// structured op, and a structured op is legal inside a parent being lowered
+/// as raw CFG, so leaving the parent Unstructured is conservative but correct.
+static void detectStructuredWithUnstructuredInternals(
+    Fortran::lower::pft::FunctionLikeUnit &unit) {
+  std::function<void(Fortran::lower::pft::EvaluationList &)> visit =
+      [&](Fortran::lower::pft::EvaluationList &list) {
+        for (Fortran::lower::pft::Evaluation &e : list) {
+          if (e.evaluationList)
+            visit(*e.evaluationList);
+
+          if (e.isA<parser::DoConstruct>() &&
+              isStructurableWithUnstructuredInternals(e, unit))
+            // The one place the classification weakens: detection has proven
+            // Unstructured unnecessary.
+            e.weakenControlFlow(Fortran::lower::pft::Evaluation::ControlFlow::
+                                    StructuredWithUnstructuredInternals);
+        }
+      };
+  visit(unit.evaluationList);
+}
+
 bool Fortran::lower::pft::isWrappableConstruct(
     const Fortran::lower::pft::Evaluation &eval,
     const Fortran::semantics::SemanticsContext &semaCtx) {
   if (!wrapUnstructuredConstructsInExecuteRegion)
     return false;
 
-  if (!eval.isUnstructured)
+  if (!eval.isUnstructured())
     return false;
 
   if (!(eval.isA<Fortran::parser::DoConstruct>() ||
diff --git a/flang/test/Lower/pre-fir-tree-unstructured-internals.f90 b/flang/test/Lower/pre-fir-tree-unstructured-internals.f90
new file mode 100644
index 0000000000000..30236e51bb40b
--- /dev/null
+++ b/flang/test/Lower/pre-fir-tree-unstructured-internals.f90
@@ -0,0 +1,137 @@
+! RUN: %flang_fc1 -fdebug-dump-pft %s 2>&1 | FileCheck %s
+
+! Detection of loops whose control flow is structured on the outside but has
+! raw branching confined to the body. Such a loop is marked '~' in the dump, as
+! opposed to '!' for a fully unstructured one and no marker for a structured
+! one.
+
+! Two forward GOTOs that both land inside the body. Nothing enters the body
+! from outside and nothing leaves it, so the loop qualifies even though its
+! internals are branch-based.
+subroutine internal_gotos(a, n)
+  real :: a(n)
+  ! CHECK:   <<DoConstruct~>> -> 10
+  ! CHECK:     6 GotoStmt! -> 8: goto 20
+  ! CHECK:     8 ^AssignmentStmt <- 6: 20 a(i) = a(i) + 1.0
+  ! CHECK:   <<End DoConstruct~>>
+  do i = 1, n
+    if (a(i) > 0.0) goto 10
+    a(i) = 1.0
+    goto 20
+10  a(i) = 2.0
+20  a(i) = a(i) + 1.0
+  end do
+end subroutine
+
+! A CYCLE targets the EndDoStmt, which is the boundary between the body and the
+! loop control, so it does not count as escaping the body.
+subroutine cycle_in_if_block(a, n)
+  real :: a(n)
+  ! CHECK:   <<DoConstruct~>> -> 8
+  ! CHECK:     [[CYC:[0-9]+]] CycleStmt! -> [[END:[0-9]+]]: cycle
+  ! CHECK:     [[END]] ^EndDoStmt -> 1 <- [[CYC]]: end do
+  ! CHECK:   <<End DoConstruct~>>
+  do i = 1, n
+    if (a(i) > 0.0) then
+      a(i) = 1.0
+      cycle
+    end if
+    a(i) = 2.0
+  end do
+end subroutine
+
+! The GOTO leaves the loop entirely, so the branch graph is not contained.
+subroutine escaping_goto(a, n)
+  real :: a(n)
+  ! CHECK:   <<DoConstruct!>> -> 7
+  ! CHECK:   <<End DoConstruct!>>
+  do i = 1, n
+    if (a(i) > 0.0) goto 20
+    a(i) = 2.0
+  end do
+20 continue
+end subroutine
+
+! A RETURN escapes the loop and the procedure both.
+subroutine body_has_return(a, n)
+  real :: a(n)
+  ! CHECK:   <<DoConstruct!>> -> 7
+  ! CHECK:   <<End DoConstruct!>>
+  do i = 1, n
+    if (a(i) > 0.0) return
+    a(i) = 3.0
+  end do
+end subroutine
+
+! The branch originates outside the loop and lands inside its body, so the body
+! has an external entry point and cannot be wrapped.
+subroutine incoming_from_outside(a, n)
+  real :: a(n)
+  ! CHECK:   <<DoConstruct!>> -> 8
+  ! CHECK:   <<End DoConstruct!>>
+  if (n < 0) goto 30
+  do i = 1, n
+    a(i) = 4.0
+30  continue
+  end do
+end subroutine
+
+! An inner infinite DO has no structured loop control to preserve.
+subroutine infinite_inner(a, n)
+  real :: a(n)
+  ! CHECK:   <<DoConstruct!>> -> 9
+  ! CHECK:     <<DoConstruct!>> -> 8
+  ! CHECK:     <<End DoConstruct!>>
+  ! CHECK:   <<End DoConstruct!>>
+  do i = 1, n
+    do
+      a(i) = 1.0
+      if (a(i) > 0.0) exit
+    end do
+  end do
+end subroutine
+
+! The shape that motivates this work: a forward GOTO raised inside a nested
+! IF, jumping over a whole inner DO construct and landing on the last statement
+! of the outer loop body. Both endpoints are inside the body, so the outer loop
+! is category (c) even though the branch crosses construct boundaries. Note the
+! two inner DO constructs stay structured -- only the outer loop carries the
+! branching.
+subroutine goto_over_inner_loop(qfx, hfx, a, its, ite, jts, jte, force, flux)
+  real :: qfx(ite,jte), hfx(ite,jte), a(ite,jte)
+  logical :: force
+  integer :: flux
+  ! CHECK:   <<DoConstruct~>> -> 18
+  ! CHECK:     <<DoConstruct>> -> 6
+  ! CHECK:     <<End DoConstruct>>
+  ! CHECK:       [[GOTO:[0-9]+]] ^GotoStmt! -> [[TGT:[0-9]+]]: goto 350
+  ! CHECK:     <<DoConstruct>> -> [[TGT]]
+  ! CHECK:     <<End DoConstruct>>
+  ! CHECK:     [[TGT]] ^ContinueStmt <- [[GOTO]]: 350 continue
+  ! CHECK:     17 EndDoStmt -> 1: enddo
+  ! CHECK:   <<End DoConstruct~>>
+  do j = jts, jte
+    do 330 i = its, ite
+      a(i,j) = a(i,j) + 1.0
+330 continue
+335 continue
+    if (force) then
+      if (flux .eq. 1) goto 350
+    endif
+    do i = its, ite
+      qfx(i,j) = 0.
+      hfx(i,j) = 0.
+    enddo
+350 continue
+  enddo
+end subroutine
+
+! No branching at all; detection must leave it unmarked.
+subroutine fully_structured(a, n)
+  real :: a(n)
+  ! CHECK:   <<DoConstruct>> -> 4
+  ! CHECK:   <<End DoConstruct>>
+  do i = 1, n
+    a(i) = 5.0
+  end do
+end subroutine
diff --git a/flang/test/Lower/trailing-cycle.f90 b/flang/test/Lower/trailing-cycle.f90
index 47f904cb46c99..4a69a3b4fc0ba 100644
--- a/flang/test/Lower/trailing-cycle.f90
+++ b/flang/test/Lower/trailing-cycle.f90
@@ -58,8 +58,10 @@ subroutine trailing_cycle(a, n)
     end do
   end do outer
 
-  ! A labeled CYCLE may be a branch target and is kept.
-  ! CHECK:   <<DoConstruct!>> -> 25
+  ! A labeled CYCLE may be a branch target and is kept. Its branches stay
+  ! inside the loop body, so the loop is category (c): structured control with
+  ! unstructured internals, marked '~' rather than '!'.
+  ! 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
@@ -68,20 +70,21 @@ subroutine trailing_cycle(a, n)
   ! CHECK:     <<End IfConstruct>>
   ! CHECK:     23 CycleStmt! -> 24: 10 cycle
   ! CHECK:     24 ^EndDoStmt -> 18 <- 23: end do
-  ! CHECK:   <<End DoConstruct!>>
+  ! 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
+  ! A CYCLE that is not last is a real branch and is kept. It targets the
+  ! EndDoStmt, which is the loop-body boundary, so this is category (c) too.
+  ! 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 <- 26: end do
-  ! CHECK:   <<End DoConstruct!>>
+  ! CHECK:   <<End DoConstruct~>>
   do i = 1, n
     cycle
     a(i) = 5.0



More information about the llvm-branch-commits mailing list