https://github.com/ergawy updated https://github.com/llvm/llvm-project/pull/225757
>From 4e323a69815056d56fb287b136ab008dee54f3db Mon Sep 17 00:00:00 2001 From: ergawy <[email protected]> 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 _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
