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

Kareem Ergawy via flang-commits flang-commits at lists.llvm.org
Tue Sep 29 04:48:06 PDT 2026


================
@@ -2750,13 +2762,225 @@ 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.
+/// Follow the chain of unconditional GO TOs starting at \p start and return
+/// true if it closes on itself.
+///
+/// Such a cycle has no exit edge, which makes it a statically known infinite
+/// loop. Only unconditional transfers are followed, so the answer is a
+/// certainty rather than a guess -- the same bound the cf.br canonicalization
+/// applies when it declines to collapse cyclic branches.
+static bool
+startsExitFreeGotoCycle(const Fortran::lower::pft::Evaluation &start) {
+  auto gotoTarget = [](const Fortran::lower::pft::Evaluation &e) {
+    return e.getIf<parser::GotoStmt>() ? e.controlSuccessor : nullptr;
+  };
+
+  llvm::SmallPtrSet<const Fortran::lower::pft::Evaluation *, 4> visited;
+  for (const Fortran::lower::pft::Evaluation *e = &start; e; e = gotoTarget(*e))
+    if (!visited.insert(e).second)
+      return true;
+  return false;
+}
+
+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
+  // unstructured 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 {
+    // A body that cannot run to completion must stay unstructured. Its
+    // structured form puts the body in an scf.execute_region carrying no
+    // memory effects, and DCE deletes such a region outright -- discarding the
+    // non-termination and letting execution fall past the loop. Branches
+    // survive that, being terminators, so leave the loop unstructured.
+    //
+    // An infinite DO says so in its own syntax; a GO TO cycle has to be
+    // followed to be recognized.
+    if (e.isA<parser::ReturnStmt>() ||
+        isInfiniteDo(e.getIf<parser::DoConstruct>()) ||
+        startsExitFreeGotoCycle(e))
+      return false;
+
+    // An assigned GO TO reaches any label ASSIGNed to its variable, and a label
+    // list does not bound that: lowering deliberately allows a branch to any
+    // ASSIGNed label whether or not the list names it. The successors
+    // analyzeBranches recorded are therefore incomplete, since it only sees the
+    // ASSIGNs that precede the GO TO in program order.
+    //
+    // The symbol-to-labels map is complete once branch analysis has finished,
+    // which is when this runs, so ask it for the full target set instead of
+    // trusting the recorded successors.
+    if (const auto *g = e.getIf<parser::AssignedGotoStmt>()) {
+      const semantics::Symbol *sym = std::get<parser::Name>(g->t).symbol;
+      if (!sym)
+        return false;
+      auto assigned = unit.assignSymbolLabelMap.find(*sym);
+      if (assigned == unit.assignSymbolLabelMap.end())
+        return false;
+      for (parser::Label label : assigned->second) {
+        auto target = unit.labelEvaluationMap.find(label);
+        if (target == unit.labelEvaluationMap.end() ||
+            targetEscapes(target->second))
+          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;
----------------
ergawy wrote:

bdd687da6dca3adf9de3a5f5bf7449ec0b71319c adds a pre-pass across the function to populate `assignSymbolLabelMap`. This way we have a full view of the possible targets to an assigned `goto` before branch analysis.

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


More information about the flang-commits mailing list