[flang-commits] [flang] [flang][PFT-to-MLIR] Wrap unstructured Fortran constructs in scf.execute_region (PR #208635)

Kareem Ergawy via flang-commits flang-commits at lists.llvm.org
Fri Jul 17 00:58:52 PDT 2026


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

>From 2abfb0ba265c8fff10d5d20e515367e8d7222e3c Mon Sep 17 00:00:00 2001
From: ergawy <kareem.ergawy at gmail.com>
Date: Tue, 16 Jun 2026 08:16:40 -0700
Subject: [PATCH 1/5] [flang][PFT-to-MLIR] Wrap unstructured Fortran constructs
 in scf.execute_region

Extend the PFT-to-MLIR (HLFIR/FIR) lowering so unstructured DO and IF
constructs are emitted inside scf.execute_region, hiding their multi-block
CFG behind a single op. OpenACC and OpenMP lowerings that reject
multi-block content (e.g. the "unstructured do loop in combined acc
construct" TODO in OpenACC.cpp) now see a structured op instead.

Flag: -mmlir --wrap-unstructured-constructs-in-execute-region (default on).

An evaluation is wrappable iff all of the following hold:

  * wrap flag on
  * eval is parser::DoConstruct or parser::IfConstruct
  * eval.isUnstructured
  * branchesAreInternal(eval) -- every controlSuccessor in the subtree
    targets a nested eval or the constructExit
  * !hasIncomingBranch(eval) -- no outside eval branches into the body
    (PFT's synthetic IfConstruct around `if(c) goto X` absorbs label
    targets between the IF and X; the incoming-branch check excludes
    such wrappers when an outer GOTO names one of those labels)
  * does not contain a ReturnStmt -- its lowering creates the function's
    final block in the current region, which would mis-parent func.return
  * not an infinite DO and does not contain one (no LoopControl in any
    nested DO): the wrap's yield is unreachable and the body has no
    write-shaped side effects, so RegionDCE treats the wrap as trivially
    dead and drops it. Excluding the whole enclosing construct keeps
    such infinite loops visible in the parent CFG.
  * not the body DO of an enclosing OpenACCLoopConstruct or
    OpenACCCombinedConstruct -- nor one of the N collapsed iterator DOs
    reached by walking down through `collapse(N)`. Such DOs are driven
    directly into acc.loop by the OpenACC lowering, so wrapping them
    would hide the iteration from the acc.loop op.

Diagnostics
-----------
Emit per-wrap and per-function diagnostics on stderr so the wrapping is
observable from a single compile invocation:

  [wrap-unstructured] wrapped DO at <loc>
  [wrap-unstructured] wrapped IF at <loc>
  [wrap-unstructured] summary: N execute_region(s) wrapping unstructured
                      constructs at <loc>

The counter is reset per function and the summary is suppressed when no
wraps fire.

Co-authored-by: Claude Opus 4.7 <noreply at anthropic.com>
---
 flang/include/flang/Lower/PFTBuilder.h        |  10 +
 flang/lib/Lower/Bridge.cpp                    | 103 +++++++-
 flang/lib/Lower/PFTBuilder.cpp                | 236 +++++++++++++++++-
 flang/lib/Lower/Runtime.cpp                   |   8 +-
 .../assumed_shape_with_value_keyword.f90      |  17 +-
 flang/test/HLFIR/optional_dummy.f90           |  23 +-
 .../parallel-private-reduction-worstcase.f90  |   3 +-
 .../Lower/HLFIR/intrinsic-subroutines.f90     |  41 +--
 flang/test/Lower/MIF/change_team2.f90         |  27 +-
 .../acc-unstructured-combined-construct.f90   |  64 +----
 .../Todo/do-loops-to-acc-loops-todo.f90       |  19 --
 flang/test/Lower/OpenACC/acc-terminator.f90   |   3 +-
 flang/test/Lower/OpenACC/acc-unstructured.f90 | 201 +++++++++++++--
 flang/test/Lower/OpenMP/loop-compound.f90     |  30 ++-
 .../test/Lower/OpenMP/parallel-reduction3.f90 |  29 ++-
 .../test/Lower/OpenMP/stop-stmt-in-region.f90 |  69 ++---
 flang/test/Lower/OpenMP/unstructured.f90      | 205 +++++++--------
 .../test/Lower/OpenMP/wsloop-unstructured.f90 |  35 ++-
 flang/test/Lower/branching-directive.f90      |  45 +++-
 .../Lower/do_loop_execute_region_wrap.f90     | 111 ++++++++
 flang/test/Lower/do_loop_unstructured.f90     | 125 +++-------
 flang/test/Lower/fail_image.f90               |  19 +-
 flang/test/Lower/ifconvert.f90                |   6 +-
 flang/test/Lower/mixed_loops.f90              | 108 ++++----
 flang/test/Lower/nsw.f90                      |  19 +-
 flang/test/Lower/pre-fir-tree02.f90           |   4 +-
 flang/test/Lower/while_loop.f90               |  15 +-
 27 files changed, 1058 insertions(+), 517 deletions(-)
 create mode 100644 flang/test/Lower/do_loop_execute_region_wrap.f90

diff --git a/flang/include/flang/Lower/PFTBuilder.h b/flang/include/flang/Lower/PFTBuilder.h
index 29b859ff9ace4..516ac7679c6a5 100644
--- a/flang/include/flang/Lower/PFTBuilder.h
+++ b/flang/include/flang/Lower/PFTBuilder.h
@@ -881,6 +881,16 @@ void visitAllSymbols(const FunctionLikeUnit &funit,
 void visitAllSymbols(const Evaluation &eval,
                      std::function<void(const semantics::Symbol &)> callBack);
 
+/// Return true if every control-flow successor nested under \p construct
+/// points to an evaluation that is itself nested under \p construct or is \p
+/// construct's own constructExit. A false return means at least one branch
+/// leaves the construct (e.g. a GOTO to an outer label).
+bool branchesAreInternal(const Evaluation &construct);
+
+/// Return true when \p eval is an unstructured DO or IF construct that can
+/// folded into a self-contained scf.execute_region.
+bool isWrappableConstruct(const Evaluation &eval);
+
 } // namespace Fortran::lower::pft
 
 namespace Fortran::lower {
diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 2cce44cdc8a70..bbf46b12f8b62 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -2538,6 +2538,50 @@ class FirConverter : public Fortran::lower::AbstractConverter {
     }
   }
 
+  /// Wrap an unstructured construct's CFG in a self-contained
+  /// scf.execute_region and set the builder insertion point inside it. Returns
+  /// the created op (null if the construct isn't wrappable).
+  mlir::scf::ExecuteRegionOp
+  wrapUnstructuredConstruct(Fortran::lower::pft::Evaluation &eval,
+                            mlir::Block *&savedExitBlock) {
+    if (!Fortran::lower::pft::isWrappableConstruct(eval))
+      return nullptr;
+
+    mlir::Location loc = toLocation();
+    auto wrapOp =
+        mlir::scf::ExecuteRegionOp::create(*builder, loc, mlir::TypeRange{},
+                                           /*noInline=*/builder->getUnitAttr());
+    ++wrapUnstructuredCount;
+    mlir::Block *entry = builder->createBlock(&wrapOp.getRegion());
+    builder->setInsertionPointToEnd(entry);
+    createEmptyBlocks(eval.getNestedEvaluations());
+    mlir::Block *yieldBlock = builder->createBlock(&wrapOp.getRegion());
+    builder->setInsertionPointToEnd(yieldBlock);
+    mlir::scf::YieldOp::create(*builder, loc);
+
+    if (eval.constructExit) {
+      savedExitBlock = eval.constructExit->block;
+      eval.constructExit->block = yieldBlock;
+    }
+
+    builder->setInsertionPointToEnd(entry);
+    return wrapOp;
+  }
+
+  /// Finalize a wrap created by wrapUnstructuredConstruct: restore the
+  /// original exit block and set the insertion point after the wrap op.
+  void closeUnstructuredWrap(mlir::scf::ExecuteRegionOp wrapOp,
+                             Fortran::lower::pft::Evaluation &eval,
+                             mlir::Block *savedExitBlock) {
+    if (!wrapOp)
+      return;
+
+    if (eval.constructExit)
+      eval.constructExit->block = savedExitBlock;
+
+    builder->setInsertionPointAfter(wrapOp);
+  }
+
   /// Generate FIR for a DO construct. There are six variants:
   ///  - unstructured infinite and while loops
   ///  - structured and unstructured increment loops
@@ -2547,6 +2591,19 @@ class FirConverter : public Fortran::lower::AbstractConverter {
     Fortran::lower::pft::Evaluation &eval = getEval();
     bool unstructuredContext = eval.lowerAsUnstructured();
 
+    mlir::Block *savedExitBlock = nullptr;
+    mlir::scf::ExecuteRegionOp wrapOp =
+        unstructuredContext ? wrapUnstructuredConstruct(eval, savedExitBlock)
+                            : nullptr;
+
+    if (wrapOp) {
+      LLVM_DEBUG({
+        llvm::dbgs() << "[wrap-unstructured] wrapped DO at ";
+        wrapOp.getLoc().print(llvm::dbgs());
+        llvm::dbgs() << "\n";
+      });
+    }
+
     // If this do-loop was absorbed by a collapse clause on a parent acc.loop,
     // skip generating any loop — just lower the body.  The IV value is
     // already available from the parent acc.loop's block argument.
@@ -2719,6 +2776,8 @@ class FirConverter : public Fortran::lower::AbstractConverter {
     for (IncrementLoopInfo &info : incrementLoopNestInfo)
       if (auto loopOp = mlir::dyn_cast_if_present<fir::DoLoopOp>(info.loopOp))
         attachAttributesToDoLoopOperations(loopOp, doStmtEval.dirs);
+
+    closeUnstructuredWrap(wrapOp, eval, savedExitBlock);
   }
 
   /// Generate FIR to evaluate loop control values (lower, upper and step).
@@ -3197,7 +3256,18 @@ class FirConverter : public Fortran::lower::AbstractConverter {
       return;
     }
 
-    // Unstructured branch sequence.
+    mlir::Block *savedExitBlock = nullptr;
+    mlir::scf::ExecuteRegionOp wrapOp =
+        wrapUnstructuredConstruct(eval, savedExitBlock);
+
+    if (wrapOp) {
+      LLVM_DEBUG({
+        llvm::dbgs() << "[wrap-unstructured] wrapped IF at ";
+        wrapOp.getLoc().print(llvm::dbgs());
+        llvm::dbgs() << "\n";
+      });
+    }
+
     llvm::SmallVector<Fortran::lower::pft::Evaluation *> exits, fallThroughs;
     collectFinalEvaluations(eval, exits, fallThroughs);
 
@@ -3229,6 +3299,8 @@ class FirConverter : public Fortran::lower::AbstractConverter {
         }
       }
     }
+
+    closeUnstructuredWrap(wrapOp, eval, savedExitBlock);
   }
 
   void genCaseOrRankConstruct() {
@@ -6416,7 +6488,16 @@ class FirConverter : public Fortran::lower::AbstractConverter {
       if (eval.isNewBlock)
         eval.block = builder->createBlock(region);
       if (eval.isConstruct() || eval.isDirective()) {
-        if (eval.lowerAsUnstructured()) {
+        if (Fortran::lower::pft::isWrappableConstruct(eval)) {
+          // The wrap owns internal blocks; only create the entry block here
+          // so the enclosing CFG can branch to it.
+          if (eval.hasNestedEvaluations()) {
+            Fortran::lower::pft::Evaluation &constructStmt =
+                eval.getFirstNestedEvaluation();
+            if (constructStmt.isNewBlock)
+              constructStmt.block = builder->createBlock(region);
+          }
+        } else if (eval.lowerAsUnstructured()) {
           createEmptyBlocks(eval.getNestedEvaluations());
         } else if (eval.hasNestedEvaluations()) {
           // A structured construct that is a target starts a new block.
@@ -6598,8 +6679,21 @@ class FirConverter : public Fortran::lower::AbstractConverter {
          entryIndex < last; ++entryIndex) {
       funit.setActiveEntry(entryIndex);
       startNewFunction(funit); // the entry point for lowering this procedure
+      wrapUnstructuredCount = 0;
+
       for (Fortran::lower::pft::Evaluation &eval : funit.evaluationList)
         genFIR(eval);
+
+      if (wrapUnstructuredCount > 0) {
+        LLVM_DEBUG({
+          llvm::dbgs()
+              << "[wrap-unstructured] summary: " << wrapUnstructuredCount
+              << " execute_region(s) wrapping unstructured constructs at ";
+          toLocation().print(llvm::dbgs());
+          llvm::dbgs() << "\n";
+        });
+      }
+
       endNewFunction(funit);
     }
     funit.setActiveEntry(0);
@@ -6997,6 +7091,11 @@ class FirConverter : public Fortran::lower::AbstractConverter {
   Fortran::parser::CharBlock currentPosition;
   TypeInfoConverter typeInfoConverter;
 
+  /// Counter of `scf.execute_region` ops created when
+  /// `--wrap-unstructured-constructs-in-execute-region` is enabled for the
+  /// currently-lowered function.
+  unsigned wrapUnstructuredCount = 0;
+
   // Stack to manage object deallocation and finalization at construct exits.
   llvm::SmallVector<ConstructContext> activeConstructStack;
 
diff --git a/flang/lib/Lower/PFTBuilder.cpp b/flang/lib/Lower/PFTBuilder.cpp
index ef86897a2bcc1..4e3bd4ba82715 100644
--- a/flang/lib/Lower/PFTBuilder.cpp
+++ b/flang/lib/Lower/PFTBuilder.cpp
@@ -17,6 +17,7 @@
 #include "llvm/ADT/IntervalMap.h"
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/Debug.h"
+#include <limits>
 
 #define DEBUG_TYPE "flang-pft"
 
@@ -24,6 +25,11 @@ static llvm::cl::opt<bool> clDisableStructuredFir(
     "no-structured-fir", llvm::cl::desc("disable generation of structured FIR"),
     llvm::cl::init(false), llvm::cl::Hidden);
 
+llvm::cl::opt<bool> wrapUnstructuredConstructsInExecuteRegion(
+    "wrap-unstructured-constructs-in-execute-region", llvm::cl::init(true),
+    llvm::cl::desc("try to wrap unstructured constructs' CFGs in "
+                   "self-contained MLIR regions"));
+
 using namespace Fortran;
 
 namespace {
@@ -1237,8 +1243,11 @@ class PFTBuilder {
       if (eval.evaluationList)
         analyzeBranches(&eval, *eval.evaluationList);
 
-      // Propagate isUnstructured flag to enclosing construct.
-      if (parentConstruct && eval.isUnstructured)
+      // Propagate isUnstructured flag to 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 &&
+          !lower::pft::isWrappableConstruct(eval))
         parentConstruct->isUnstructured = true;
 
       // The successor of a branch starts a new block.
@@ -2365,3 +2374,226 @@ void Fortran::lower::pft::visitAllSymbols(
     parser::Walk(functionParserNode, visitor);
   });
 }
+
+/// True if \p target lies within \p construct. In `strict` mode, a null
+/// target and the construct's own exit block count as *outside*; in
+/// non-strict mode they count as *inside* (a natural fall-through that
+/// doesn't leave the construct).
+static bool isInsideConstruct(const Fortran::lower::pft::Evaluation *target,
+                              const Fortran::lower::pft::Evaluation &construct,
+                              bool strict) {
+  if (!target)
+    return !strict;
+
+  if (target == construct.constructExit)
+    return !strict;
+
+  for (const Fortran::lower::pft::Evaluation *p = target; p;
+       p = p->parentConstruct)
+    if (p == &construct)
+      return true;
+
+  return false;
+}
+
+bool Fortran::lower::pft::branchesAreInternal(
+    const Fortran::lower::pft::Evaluation &construct) {
+  std::function<bool(const Fortran::lower::pft::EvaluationList &)> walk =
+      [&](const Fortran::lower::pft::EvaluationList &list) -> bool {
+    for (const Fortran::lower::pft::Evaluation &nested : list) {
+      if (nested.controlSuccessor &&
+          !isInsideConstruct(nested.controlSuccessor, construct,
+                             /*strict=*/false))
+        return false;
+
+      if (nested.evaluationList && !walk(*nested.evaluationList))
+        return false;
+    }
+
+    return true;
+  };
+
+  if (!construct.evaluationList)
+    return false;
+
+  return walk(*construct.evaluationList);
+}
+
+/// True if any eval outside \p construct branches strictly inside it.
+/// Such branches would cross the scf.execute_region boundary at lowering.
+static bool
+hasIncomingBranch(const Fortran::lower::pft::Evaluation &construct) {
+  const Fortran::lower::pft::FunctionLikeUnit *funit =
+      construct.getOwningProcedure();
+  if (!funit)
+    return false;
+
+  std::function<bool(const Fortran::lower::pft::EvaluationList &)> walk =
+      [&](const Fortran::lower::pft::EvaluationList &list) -> bool {
+    for (const Fortran::lower::pft::Evaluation &e : list) {
+      if (!isInsideConstruct(&e, construct, /*strict=*/true) &&
+          e.controlSuccessor &&
+          isInsideConstruct(e.controlSuccessor, construct, /*strict=*/true))
+        return true;
+
+      if (e.evaluationList && walk(*e.evaluationList))
+        return true;
+    }
+
+    return false;
+  };
+
+  // TODO: This is potentially expensive and we might need to prevent running
+  // this walk more than once somehow.
+  return walk(funit->evaluationList);
+}
+
+/// True for a `do; end do` (no bounds, no while-condition), or for any
+/// construct that contains one. Such bodies can't reach the wrap's yield
+/// block, and the wrap has only reads/branches inside — RegionDCE then drops
+/// the whole scf.execute_region. Excluding the entire enclosing construct
+/// keeps the infinite loop visible in the parent CFG.
+static bool
+containsInfiniteDoConstruct(const Fortran::lower::pft::Evaluation &eval) {
+  auto isInfinite = [](const parser::DoConstruct *d) {
+    return d && !d->GetLoopControl().has_value();
+  };
+
+  if (isInfinite(eval.getIf<parser::DoConstruct>()))
+    return true;
+
+  if (!eval.evaluationList)
+    return false;
+
+  std::function<bool(const Fortran::lower::pft::EvaluationList &)> walk =
+      [&](const Fortran::lower::pft::EvaluationList &list) -> bool {
+    for (const Fortran::lower::pft::Evaluation &e : list) {
+      if (isInfinite(e.getIf<parser::DoConstruct>()))
+        return true;
+
+      if (e.evaluationList && walk(*e.evaluationList))
+        return true;
+    }
+
+    return false;
+  };
+
+  return walk(*eval.evaluationList);
+}
+
+/// True if \p eval is a DoConstruct driven directly into an enclosing acc.loop
+/// by the OpenACCLoopConstruct / OpenACCCombinedConstruct lowering — the
+/// immediate body DO, or one of the N collapsed iterator DOs reached by
+/// walking down from the body DO under a `collapse(N)` clause.
+static bool isAccLoopBody(const Fortran::lower::pft::Evaluation &eval) {
+  const auto *doConstruct = eval.getIf<parser::DoConstruct>();
+  if (!doConstruct)
+    return false;
+  // N from `collapse(N)`, or 1 if no clause. eval at depth d from the body
+  // (d == 0 means eval IS the body) is a collapsed iterator iff d < N. If the
+  // Collapse value isn't a compile-time constant, be conservative and treat
+  // every DO in the chain as collapsed (INT64_MAX) — wrapping is opt-in and
+  // a false "is collapsed" is safer than a false "is not".
+  auto collapseN = [](const parser::AccClauseList &cl) -> int64_t {
+    for (const parser::AccClause &c : cl.v)
+      if (const auto *cc = std::get_if<parser::AccClause::Collapse>(&c.u)) {
+        const auto &intExpr = std::get<parser::ScalarIntConstantExpr>(cc->v.t);
+        if (const auto *expr = semantics::GetExpr(intExpr))
+          if (auto v = evaluate::ToInt64(*expr))
+            return *v;
+        return std::numeric_limits<int64_t>::max();
+      }
+    return 1;
+  };
+
+  // candidates[k] is the DoConstruct at depth k above (and including) eval.
+  llvm::SmallVector<const parser::DoConstruct *, 4> candidates{doConstruct};
+
+  for (const Fortran::lower::pft::Evaluation *p = eval.parentConstruct; p;
+       p = p->parentConstruct) {
+    if (const auto *d = p->getIf<parser::DoConstruct>()) {
+      candidates.push_back(d);
+      continue;
+    }
+
+    if (const auto *acc = p->getIf<parser::OpenACCConstruct>()) {
+      const parser::DoConstruct *body = nullptr;
+      int64_t n = 1;
+      if (const auto *loop =
+              std::get_if<parser::OpenACCLoopConstruct>(&acc->u)) {
+        if (const auto &b =
+                std::get<std::optional<parser::DoConstruct>>(loop->t))
+          body = &b.value();
+        n = collapseN(std::get<parser::AccClauseList>(std::get<0>(loop->t).t));
+      } else if (const auto *comb =
+                     std::get_if<parser::OpenACCCombinedConstruct>(&acc->u)) {
+        if (const auto &b =
+                std::get<std::optional<parser::DoConstruct>>(comb->t))
+          body = &b.value();
+        n = collapseN(std::get<parser::AccClauseList>(std::get<0>(comb->t).t));
+      }
+
+      if (body) {
+        // body is at index `candidates.size()-1` (the outermost candidate);
+        // eval at depth (candidates.size()-1) from body. Collapsed iff < N.
+        auto it = llvm::find(candidates, body);
+        if (it != candidates.end()) {
+          int64_t depth = std::distance(candidates.begin(), it);
+          if (depth < n)
+            return true;
+        }
+      }
+    }
+
+    break;
+  }
+
+  return false;
+}
+
+/// True if any nested eval is a ReturnStmt. Its lowering creates the
+/// function's final block in the current region, which would mis-parent
+/// the func.return if that region is the wrap's.
+static bool
+containsReturnStmt(const Fortran::lower::pft::Evaluation &construct) {
+  if (!construct.evaluationList)
+    return false;
+
+  std::function<bool(const Fortran::lower::pft::EvaluationList &)> walk =
+      [&](const Fortran::lower::pft::EvaluationList &list) -> bool {
+    for (const Fortran::lower::pft::Evaluation &e : list) {
+      if (e.isA<parser::ReturnStmt>())
+        return true;
+
+      if (e.evaluationList && walk(*e.evaluationList))
+        return true;
+    }
+
+    return false;
+  };
+
+  return walk(*construct.evaluationList);
+}
+
+bool Fortran::lower::pft::isWrappableConstruct(
+    const Fortran::lower::pft::Evaluation &eval) {
+  if (!wrapUnstructuredConstructsInExecuteRegion)
+    return false;
+
+  if (!eval.isUnstructured)
+    return false;
+
+  if (!(eval.isA<Fortran::parser::DoConstruct>() ||
+        eval.isA<Fortran::parser::IfConstruct>()))
+    return false;
+
+  // Wrapping requires self-contained CFG.
+  //
+  // Note: Loops attached to OpenACC constructs are not wrappable since
+  // genOpenACCLoopFromDoConstruct takes over code-gen when a DoConstruct is
+  // attached to an OpenACC directive. We might extend wrapping to such
+  // unstructured loops later on if needed.
+  return Fortran::lower::pft::branchesAreInternal(eval) &&
+         !hasIncomingBranch(eval) && !containsReturnStmt(eval) &&
+         !containsInfiniteDoConstruct(eval) && !isAccLoopBody(eval);
+}
diff --git a/flang/lib/Lower/Runtime.cpp b/flang/lib/Lower/Runtime.cpp
index ca1a1aca89606..ebbb9e3f74f03 100644
--- a/flang/lib/Lower/Runtime.cpp
+++ b/flang/lib/Lower/Runtime.cpp
@@ -19,6 +19,7 @@
 #include "flang/Runtime/stop.h"
 #include "flang/Semantics/tools.h"
 #include "mlir/Dialect/OpenMP/OpenMPDialect.h"
+#include "mlir/Dialect/SCF/IR/SCF.h"
 #include "llvm/Support/Debug.h"
 #include <optional>
 
@@ -31,8 +32,11 @@ using namespace Fortran::runtime;
 static void genUnreachable(fir::FirOpBuilder &builder, mlir::Location loc) {
   mlir::Block *curBlock = builder.getBlock();
   mlir::Operation *parentOp = curBlock->getParentOp();
-  if (parentOp->getDialect()->getNamespace() ==
-      mlir::omp::OpenMPDialect::getDialectNamespace())
+
+  if (mlir::isa<mlir::scf::ExecuteRegionOp>(parentOp))
+    fir::UnreachableOp::create(builder, loc);
+  else if (parentOp->getDialect()->getNamespace() ==
+           mlir::omp::OpenMPDialect::getDialectNamespace())
     Fortran::lower::genOpenMPTerminator(builder, parentOp, loc);
   else if (Fortran::lower::isInsideOpenACCComputeConstruct(builder))
     Fortran::lower::genOpenACCTerminator(builder, parentOp, loc);
diff --git a/flang/test/HLFIR/assumed_shape_with_value_keyword.f90 b/flang/test/HLFIR/assumed_shape_with_value_keyword.f90
index 89f83863b560c..9e6cd57da785a 100644
--- a/flang/test/HLFIR/assumed_shape_with_value_keyword.f90
+++ b/flang/test/HLFIR/assumed_shape_with_value_keyword.f90
@@ -134,14 +134,17 @@ subroutine test_optional3(x)
 ! CHECK-LABEL:  func.func @_QPtest_optional3(
 ! CHECK-SAME:     %[[ARG0:.*]]: !fir.box<!fir.array<?xf32>> {fir.bindc_name = "x", fir.optional}) {
 ! CHECK:          %[[VAL_0:.*]]:2 = hlfir.declare %[[ARG0]] dummy_scope %{{[0-9]+}} arg {{[0-9]+}} {fortran_attrs = #fir.var_attrs<optional, value>, uniq_name = "_QFtest_optional3Ex"} : (!fir.box<!fir.array<?xf32>>, !fir.dscope) -> (!fir.box<!fir.array<?xf32>>, !fir.box<!fir.array<?xf32>>)
-! CHECK:          %[[VAL_1:.*]] = fir.is_present %[[VAL_0]]#1 : (!fir.box<!fir.array<?xf32>>) -> i1
-! CHECK:          cf.cond_br %[[VAL_1]], ^bb1, ^bb2
+! CHECK:          scf.execute_region no_inline {
+! CHECK:            %[[VAL_1:.*]] = fir.is_present %[[VAL_0]]#1 : (!fir.box<!fir.array<?xf32>>) -> i1
+! CHECK:            cf.cond_br %[[VAL_1]], ^bb1, ^bb2
 ! CHECK:          b1:  // pred: ^bb0
-! CHECK:          %[[C0_I32:.*]] = arith.constant 0 : i32
-! CHECK:          %[[FALSE:.*]] = arith.constant false
-! CHECK:          %[[FALSE_0:.*]] = arith.constant false
-! CHECK:          fir.call @_FortranAStopStatement(%[[C0_I32]], %[[FALSE]], %[[FALSE]]_0) fastmath<contract> : (i32, i1, i1) -> ()
-! CHECK:          fir.unreachable
+! CHECK:            %[[C0_I32:.*]] = arith.constant 0 : i32
+! CHECK:            %[[FALSE:.*]] = arith.constant false
+! CHECK:            %[[FALSE_0:.*]] = arith.constant false
+! CHECK:            fir.call @_FortranAStopStatement(%[[C0_I32]], %[[FALSE]], %[[FALSE]]_0) fastmath<contract> : (i32, i1, i1) -> ()
+! CHECK:            fir.unreachable
 ! CHECK:          b2:  // pred: ^bb0
+! CHECK:            scf.yield
+! CHECK:          }
 ! CHECK:          return
 ! CHECK:        }
diff --git a/flang/test/HLFIR/optional_dummy.f90 b/flang/test/HLFIR/optional_dummy.f90
index 86ddeb9ba135a..04751cae9f3cf 100644
--- a/flang/test/HLFIR/optional_dummy.f90
+++ b/flang/test/HLFIR/optional_dummy.f90
@@ -6,17 +6,18 @@
 ! CHECK-LABEL:   func.func @_QPtest(
 ! CHECK-SAME:        %[[VAL_0:.*]]: !fir.box<!fir.array<?xi32>> {fir.bindc_name = "ext_buf", fir.contiguous, fir.optional}) {
 ! CHECK:           %[[VAL_1:.*]]:2 = hlfir.declare %[[VAL_0]] dummy_scope %{{[0-9]+}} arg {{[0-9]+}} {fortran_attrs = #fir.var_attrs<contiguous, optional>, uniq_name = "_QFtestEext_buf"} : (!fir.box<!fir.array<?xi32>>, !fir.dscope) -> (!fir.box<!fir.array<?xi32>>, !fir.box<!fir.array<?xi32>>)
-! CHECK:           %[[VAL_2:.*]] = fir.is_present %[[VAL_1]]#1 : (!fir.box<!fir.array<?xi32>>) -> i1
-! CHECK:           cf.cond_br %[[VAL_2]], ^bb1, ^bb2
-! CHECK:         ^bb1:
-! CHECK:           %[[VAL_3:.*]] = arith.constant 0 : i32
-! CHECK:           %[[VAL_4:.*]] = arith.constant false
-! CHECK:           %[[VAL_5:.*]] = arith.constant false
-! CHECK:           fir.call @_FortranAStopStatement(%[[VAL_3]], %[[VAL_4]], %[[VAL_5]]) fastmath<contract> : (i32, i1, i1) -> ()
-! CHECK:           fir.unreachable
-! CHECK:         ^bb2:
-! CHECK:           cf.br ^bb3
-! CHECK:         ^bb3:
+! CHECK:           scf.execute_region no_inline {
+! CHECK:             %[[VAL_2:.*]] = fir.is_present %[[VAL_1]]#1 : (!fir.box<!fir.array<?xi32>>) -> i1
+! CHECK:             cf.cond_br %[[VAL_2]], ^bb1, ^bb2
+! CHECK:           ^bb1:
+! CHECK:             %[[VAL_3:.*]] = arith.constant 0 : i32
+! CHECK:             %[[VAL_4:.*]] = arith.constant false
+! CHECK:             %[[VAL_5:.*]] = arith.constant false
+! CHECK:             fir.call @_FortranAStopStatement(%[[VAL_3]], %[[VAL_4]], %[[VAL_5]]) fastmath<contract> : (i32, i1, i1) -> ()
+! CHECK:             fir.unreachable
+! CHECK:           ^bb2:
+! CHECK:             scf.yield
+! CHECK:           }
 ! CHECK:           return
 ! CHECK:         }
 subroutine test(ext_buf)
diff --git a/flang/test/Integration/OpenMP/parallel-private-reduction-worstcase.f90 b/flang/test/Integration/OpenMP/parallel-private-reduction-worstcase.f90
index 94080d1d4f975..f9ea1a962d3c9 100644
--- a/flang/test/Integration/OpenMP/parallel-private-reduction-worstcase.f90
+++ b/flang/test/Integration/OpenMP/parallel-private-reduction-worstcase.f90
@@ -148,7 +148,7 @@ subroutine worst_case(a, b, c, d)
 ! CHECK:       omp.par.region31:                                 ; preds = %omp.par.region29
 ! CHECK-NEXT:    br label %omp.region.cont28
 
-! CHECK:       omp.region.cont28:                                ; preds = %omp.par.region30, %omp.par.region31
+! CHECK:       omp.region.cont28:                                ; preds = %omp.par.region31
 !                [omp parallel region done, call into the runtime to complete reduction]
 ! CHECK:         %[[VAL_233:.*]] = call i32 @__kmpc_reduce(
 ! CHECK:         switch i32 %[[VAL_233]], label %reduce.finalize [
@@ -197,6 +197,7 @@ subroutine worst_case(a, b, c, d)
 
 ! CHECK:       omp.par.region30:                                 ; preds = %omp.par.region29
 ! CHECK-NEXT:    call void @_FortranAStopStatement
+! CHECK-NEXT:    unreachable
 
 ! CHECK:       omp.reduction.neutral25:                          ; preds = %omp.reduction.neutral24
 !                [source length was zero: finish initializing array]
diff --git a/flang/test/Lower/HLFIR/intrinsic-subroutines.f90 b/flang/test/Lower/HLFIR/intrinsic-subroutines.f90
index cbc56ca1e395b..cbd0f65ff0e20 100644
--- a/flang/test/Lower/HLFIR/intrinsic-subroutines.f90
+++ b/flang/test/Lower/HLFIR/intrinsic-subroutines.f90
@@ -64,25 +64,28 @@ program main
 ! CHECK:             %[[VAL_34:.*]] = arith.select %[[VAL_33]], %[[VAL_20]], %[[VAL_32]] : i32
 ! CHECK:             fir.store %[[VAL_34]] to %[[VAL_19]] : !fir.ref<i32>
 ! CHECK:           }
-! CHECK:           %[[VAL_35:.*]] = arith.constant 5 : i32
-! CHECK:           %[[VAL_36:.*]] = hlfir.elemental %[[VAL_8]] unordered : (!fir.shape<1>) -> !hlfir.expr<3x!fir.logical<4>> {
-! CHECK:           ^bb0(%[[VAL_37:.*]]: index):
-! CHECK:             %[[VAL_38:.*]] = hlfir.designate %[[VAL_9]]#0 (%[[VAL_37]])  : (!fir.ref<!fir.array<3xi32>>, index) -> !fir.ref<i32>
-! CHECK:             %[[VAL_39:.*]] = fir.load %[[VAL_38]] : !fir.ref<i32>
-! CHECK:             %[[VAL_40:.*]] = arith.cmpi ne, %[[VAL_39]], %[[VAL_35]] : i32
-! CHECK:             %[[VAL_41:.*]] = fir.convert %[[VAL_40]] : (i1) -> !fir.logical<4>
-! CHECK:             hlfir.yield_element %[[VAL_41]] : !fir.logical<4>
+! CHECK:           scf.execute_region no_inline {
+! CHECK:             %[[VAL_35:.*]] = arith.constant 5 : i32
+! CHECK:             %[[VAL_36:.*]] = hlfir.elemental %[[VAL_8]] unordered : (!fir.shape<1>) -> !hlfir.expr<3x!fir.logical<4>> {
+! CHECK:             ^bb0(%[[VAL_37:.*]]: index):
+! CHECK:               %[[VAL_38:.*]] = hlfir.designate %[[VAL_9]]#0 (%[[VAL_37]])  : (!fir.ref<!fir.array<3xi32>>, index) -> !fir.ref<i32>
+! CHECK:               %[[VAL_39:.*]] = fir.load %[[VAL_38]] : !fir.ref<i32>
+! CHECK:               %[[VAL_40:.*]] = arith.cmpi ne, %[[VAL_39]], %[[VAL_35]] : i32
+! CHECK:               %[[VAL_41:.*]] = fir.convert %[[VAL_40]] : (i1) -> !fir.logical<4>
+! CHECK:               hlfir.yield_element %[[VAL_41]] : !fir.logical<4>
+! CHECK:             }
+! CHECK:             %[[VAL_42:.*]] = hlfir.any %[[VAL_36]] : (!hlfir.expr<3x!fir.logical<4>>) -> !fir.logical<4>
+! CHECK:             hlfir.destroy %[[VAL_36]] : !hlfir.expr<3x!fir.logical<4>>
+! CHECK:             %[[VAL_44:.*]] = fir.convert %[[VAL_42]] : (!fir.logical<4>) -> i1
+! CHECK:             cf.cond_br %[[VAL_44]], ^bb1, ^bb2
+! CHECK:           ^bb1:
+! CHECK:             %[[VAL_45:.*]] = arith.constant 1 : i32
+! CHECK:             %[[VAL_46:.*]] = arith.constant false
+! CHECK:             %[[VAL_47:.*]] = arith.constant false
+! CHECK:             fir.call @_FortranAStopStatement(%[[VAL_45]], %[[VAL_46]], %[[VAL_47]]) fastmath<contract> : (i32, i1, i1) -> ()
+! CHECK:             fir.unreachable
+! CHECK:           ^bb2:
+! CHECK:             scf.yield
 ! CHECK:           }
-! CHECK:           %[[VAL_42:.*]] = hlfir.any %[[VAL_43:.*]] : (!hlfir.expr<3x!fir.logical<4>>) -> !fir.logical<4>
-! CHECK:           hlfir.destroy %[[VAL_43]] : !hlfir.expr<3x!fir.logical<4>>
-! CHECK:           %[[VAL_44:.*]] = fir.convert %[[VAL_42]] : (!fir.logical<4>) -> i1
-! CHECK:           cf.cond_br %[[VAL_44]], ^bb1, ^bb2
-! CHECK:         ^bb1:
-! CHECK:           %[[VAL_45:.*]] = arith.constant 1 : i32
-! CHECK:           %[[VAL_46:.*]] = arith.constant false
-! CHECK:           %[[VAL_47:.*]] = arith.constant false
-! CHECK:           fir.call @_FortranAStopStatement(%[[VAL_45]], %[[VAL_46]], %[[VAL_47]]) fastmath<contract> : (i32, i1, i1) -> ()
-! CHECK:           fir.unreachable
-! CHECK:         ^bb2:
 ! CHECK:           return
 ! CHECK:         }
diff --git a/flang/test/Lower/MIF/change_team2.f90 b/flang/test/Lower/MIF/change_team2.f90
index 58131cd98c455..42d1f50cc10aa 100644
--- a/flang/test/Lower/MIF/change_team2.f90
+++ b/flang/test/Lower/MIF/change_team2.f90
@@ -13,18 +13,21 @@
     if (team_number() /= new_team) STOP 1
   end team
   ! COARRAY:  mif.change_team %[[TEAM:.*]]  : ({{.*}}) {
-  ! COARRAY:     %[[VAL_1:.*]] = mif.team_number : () -> i64
-  ! COARRAY:     %[[VAL_2:.*]] = fir.convert %[[VAL_1]] : (i64) -> i32
-  ! COARRAY:     %[[VAL_3:.*]] = fir.load %[[VAR_1:.*]]#0 : !fir.ref<i32>
-  ! COARRAY:     %[[VAL_4:.*]] = arith.cmpi ne, %[[VAL_2]], %[[VAL_3]] : i32
-  ! COARRAY:     cf.cond_br %[[VAL_4]], ^bb1, ^bb2
-  ! COARRAY:   ^bb1:  // pred: ^bb0
-  ! COARRAY:     %[[C1_I32:.*]] = arith.constant 1 : i32
-  ! COARRAY:     %[[FALSE_1:.*]] = arith.constant false
-  ! COARRAY:     %[[FALSE_2:.*]] = arith.constant false
-  ! COARRAY:     fir.call @_FortranAStopStatement(%[[C1_I32]], %[[FALSE_1]], %[[FALSE_2]]) fastmath<contract> : (i32, i1, i1) -> ()
-  ! COARRAY:     fir.unreachable
-  ! COARRAY:   ^bb2:  // pred: ^bb0
+  ! COARRAY:     scf.execute_region no_inline {
+  ! COARRAY:       %[[VAL_1:.*]] = mif.team_number : () -> i64
+  ! COARRAY:       %[[VAL_2:.*]] = fir.convert %[[VAL_1]] : (i64) -> i32
+  ! COARRAY:       %[[VAL_3:.*]] = fir.load %[[VAR_1:.*]]#0 : !fir.ref<i32>
+  ! COARRAY:       %[[VAL_4:.*]] = arith.cmpi ne, %[[VAL_2]], %[[VAL_3]] : i32
+  ! COARRAY:       cf.cond_br %[[VAL_4]], ^bb1, ^bb2
+  ! COARRAY:     ^bb1:  // pred: ^bb0
+  ! COARRAY:       %[[C1_I32:.*]] = arith.constant 1 : i32
+  ! COARRAY:       %[[FALSE_1:.*]] = arith.constant false
+  ! COARRAY:       %[[FALSE_2:.*]] = arith.constant false
+  ! COARRAY:       fir.call @_FortranAStopStatement(%[[C1_I32]], %[[FALSE_1]], %[[FALSE_2]]) fastmath<contract> : (i32, i1, i1) -> ()
+  ! COARRAY:       fir.unreachable
+  ! COARRAY:     ^bb2:  // pred: ^bb0
+  ! COARRAY:       scf.yield
+  ! COARRAY:     }
   ! COARRAY:     mif.end_team : () -> ()
   ! COARRAY:   }
 
diff --git a/flang/test/Lower/OpenACC/Todo/acc-unstructured-combined-construct.f90 b/flang/test/Lower/OpenACC/Todo/acc-unstructured-combined-construct.f90
index 74ba49f0cf5f3..110940d9e5069 100644
--- a/flang/test/Lower/OpenACC/Todo/acc-unstructured-combined-construct.f90
+++ b/flang/test/Lower/OpenACC/Todo/acc-unstructured-combined-construct.f90
@@ -2,46 +2,9 @@
 ! combined `acc parallel loop` construct (default parallelism is
 ! `independent`).
 
-! RUN: split-file %s %t
+! RUN: bbc -fopenacc -emit-hlfir %s -o - | FileCheck %s --check-prefix=CYCLE2-OK
+! RUN: %not_todo_cmd bbc -fopenacc -emit-hlfir --emit-independent-loops-as-unstructured=false %s -o - 2>&1 | FileCheck %s --check-prefix=CYCLE2
 
-! By default (--emit-independent-loops-as-unstructured=true), the loops are
-! lowered to combined `acc.parallel` + `acc.loop` operations.
-! RUN: bbc -fopenacc -emit-hlfir %t/stop_collapse1.f90 -o - | FileCheck %s --check-prefix=STOP1-OK
-! RUN: bbc -fopenacc -emit-hlfir %t/cycle_collapse2.f90 -o - | FileCheck %s --check-prefix=CYCLE2-OK
-! RUN: bbc -fopenacc -emit-hlfir %t/stop_collapse3.f90 -o - | FileCheck %s --check-prefix=STOP3-OK
-
-! With --emit-independent-loops-as-unstructured=false, the TODO is emitted.
-! RUN: %not_todo_cmd bbc -fopenacc -emit-hlfir --emit-independent-loops-as-unstructured=false %t/stop_collapse1.f90 -o - 2>&1 | FileCheck %s --check-prefix=STOP1
-! RUN: %not_todo_cmd bbc -fopenacc -emit-hlfir --emit-independent-loops-as-unstructured=false %t/cycle_collapse2.f90 -o - 2>&1 | FileCheck %s --check-prefix=CYCLE2
-! RUN: %not_todo_cmd bbc -fopenacc -emit-hlfir --emit-independent-loops-as-unstructured=false %t/stop_collapse3.f90 -o - 2>&1 | FileCheck %s --check-prefix=STOP3
-
-!--- stop_collapse1.f90
-
-! `acc parallel loop` with STOP in the body. Loop defaults to `independent`.
-subroutine test_unstructured2(a, b, c)
-  integer :: i, j, k
-  real :: a(:,:,:), b(:,:,:), c(:,:,:)
-
-  !$acc parallel loop
-  do i = 1, 10
-    do j = 1, 10
-      do k = 1, 10
-        if (a(1,2,3) > 10) stop 'just to be unstructured'
-      end do
-    end do
-  end do
-
-end subroutine
-
-! STOP1: not yet implemented: unstructured do loop in combined acc construct
-
-! STOP1-OK-LABEL: func.func @_QPtest_unstructured2
-! STOP1-OK: acc.parallel combined(loop)
-! STOP1-OK: acc.loop combined(parallel)
-
-!--- cycle_collapse2.f90
-
-! `acc parallel loop collapse(2)` with an early-exit (CYCLE).
 subroutine test_unstructured_collapse_cycle(a)
   integer :: i, j, jdiag
   real(8) :: a(:,:)
@@ -64,26 +27,3 @@ subroutine test_unstructured_collapse_cycle(a)
 ! CYCLE2-OK-LABEL: func.func @_QPtest_unstructured_collapse_cycle
 ! CYCLE2-OK: acc.parallel combined(loop)
 ! CYCLE2-OK: acc.loop combined(parallel)
-
-!--- stop_collapse3.f90
-
-! `acc parallel loop collapse(3)` with STOP - the collapse=3 form of the
-! STOP scenario above.
-subroutine test_unstructured_collapse_stop(a)
-  integer :: i, j, k
-  real :: a(:,:,:)
-  !$acc parallel loop collapse(3)
-  do i = 1, 10
-    do j = 1, 10
-      do k = 1, 10
-        if (a(1,2,3) > 10) stop 'just to be unstructured'
-      end do
-    end do
-  end do
-end subroutine
-
-! STOP3: not yet implemented: unstructured do loop in combined acc construct
-
-! STOP3-OK-LABEL: func.func @_QPtest_unstructured_collapse_stop
-! STOP3-OK: acc.parallel combined(loop)
-! STOP3-OK: acc.loop combined(parallel)
diff --git a/flang/test/Lower/OpenACC/Todo/do-loops-to-acc-loops-todo.f90 b/flang/test/Lower/OpenACC/Todo/do-loops-to-acc-loops-todo.f90
index 9cb62f0dc1a9a..d59091a975822 100644
--- a/flang/test/Lower/OpenACC/Todo/do-loops-to-acc-loops-todo.f90
+++ b/flang/test/Lower/OpenACC/Todo/do-loops-to-acc-loops-todo.f90
@@ -1,5 +1,4 @@
 ! RUN: split-file %s %t
-! RUN: %not_todo_cmd bbc -fopenacc -emit-hlfir %t/do_loop_with_stop.f90 -o - 2>&1 | FileCheck %s --check-prefix=CHECK1
 ! RUN: %not_todo_cmd bbc -fopenacc -emit-hlfir %t/do_loop_with_cycle_goto.f90 -o - 2>&1 | FileCheck %s --check-prefix=CHECK2
 ! RUN: %not_todo_cmd bbc -fopenacc -emit-hlfir %t/nested_goto_loop.f90 -o - 2>&1 | FileCheck %s --check-prefix=CHECK3
 ! RUN: %not_todo_cmd bbc -fopenacc -emit-hlfir %t/nested_loop_with_inner_goto.f90 -o - 2>&1 | FileCheck %s --check-prefix=CHECK4
@@ -7,24 +6,6 @@
 ! RUN: %not_todo_cmd bbc -fopenacc -emit-hlfir %t/collapse_gt.f90 -o - 2>&1 | FileCheck %s --check-prefix=CHECK8
 ! RUN: %not_todo_cmd bbc -fopenacc -emit-hlfir %t/collapse_nested.f90 -o - 2>&1 | FileCheck %s --check-prefix=CHECK6
 
-//--- do_loop_with_stop.f90
-
-subroutine do_loop_with_stop()
-  integer :: i
-  integer, parameter :: n = 10
-  real, dimension(n) :: a, b
-
-  !$acc kernels
-  do i = 1, n
-    a(i) = b(i) + 1.0
-    if (i == 5) stop
-  end do
-  !$acc end kernels
-
-! CHECK1: not yet implemented: unstructured do loop in acc kernels
-
-end subroutine
-
 //--- do_loop_with_cycle_goto.f90
 
 subroutine do_loop_with_cycle_goto()
diff --git a/flang/test/Lower/OpenACC/acc-terminator.f90 b/flang/test/Lower/OpenACC/acc-terminator.f90
index 65df424da5642..fae147cbcc04d 100644
--- a/flang/test/Lower/OpenACC/acc-terminator.f90
+++ b/flang/test/Lower/OpenACC/acc-terminator.f90
@@ -48,7 +48,8 @@ program main
 ! CHECK: acc.host_data
 ! CHECK: acc.terminator
 ! CHECK: fir.call @_FortranAStopStatement
-! CHECK: fir.unreachable
+! CHECK-NEXT: fir.unreachable
+! CHECK: scf.yield
 ! CHECK: acc.parallel
 ! CHECK-COUNT-3: acc.yield
 ! CHECK: acc.terminator
diff --git a/flang/test/Lower/OpenACC/acc-unstructured.f90 b/flang/test/Lower/OpenACC/acc-unstructured.f90
index 115626da9d160..0bb3ec2691b20 100644
--- a/flang/test/Lower/OpenACC/acc-unstructured.f90
+++ b/flang/test/Lower/OpenACC/acc-unstructured.f90
@@ -39,6 +39,11 @@ subroutine test_unstructured1(a, b, c)
 ! CHECK: fir.call @_FortranAStopStatementText
 
 
+! Body looks unstructured (if/stop) but the wrap-in-execute-region pass hides
+! the unstructured CFG inside scf.execute_region, so the DOs lower as
+! structured acc.loop control(...) = ... (no `unstructured` attribute). GOTO
+! exiting a combined OpenACC region is not yet implemented in lowering, so
+! there's no genuinely-unstructured counterpart for this combined form.
 subroutine test_unstructured2(a, b, c)
   integer :: i, j, k
   real :: a(:,:,:), b(:,:,:), c(:,:,:)
@@ -53,13 +58,10 @@ subroutine test_unstructured2(a, b, c)
   end do
 
 ! CHECK-LABEL: func.func @_QPtest_unstructured2
-! CHECK: acc.serial
-! CHECK: acc.loop combined(serial) private(%{{.*}} : !fir.ref<i32>) {
+! CHECK: acc.serial combined(loop) {
+! CHECK: acc.loop combined(serial) private({{.*}}) control({{.*}}) = ({{.*}}) to ({{.*}}) step ({{.*}}) {
+! CHECK: scf.execute_region
 ! CHECK: fir.call @_FortranAStopStatementText
-! CHECK: acc.yield
-! CHECK: acc.yield
-! CHECK: }
-! CHECK: acc.yield
 
 end subroutine
 
@@ -85,9 +87,10 @@ subroutine test_unstructured3(a, b, c)
 
 end subroutine
 
-! Test that acc.data is still created when there are no data clauses but the
-! construct contains unstructured control flow. Without this, the early return
-! in genACCDataOp skips acc.data creation, leaving orphaned blocks.
+! Body looks unstructured at the source level (if/stop), but the PFT-to-MLIR
+! wrap-in-execute-region pass hides the if/stop CFG inside scf.execute_region,
+! so genACCDataOp sees a structured body and (with only an if-clause and no
+! data clauses) hits the early-return that skips acc.data.
 subroutine test_unstructured4(a, n)
   integer :: n, i, j
   real :: a(:)
@@ -105,8 +108,30 @@ subroutine test_unstructured4(a, n)
 end subroutine
 
 ! CHECK-LABEL: func.func @_QPtest_unstructured4
-! CHECK: acc.data if(%{{.*}}) {
+! CHECK-NOT: acc.data
+! CHECK: fir.do_loop
+! CHECK: scf.execute_region
 ! CHECK: fir.call @_FortranAStopStatementText
+
+! Body is genuinely unstructured: GOTO exits the data region, so the
+! enclosing if-construct has an external branch and is not wrappable.
+! eval.lowerAsUnstructured() remains true and acc.data must be emitted.
+subroutine test_unstructured4_goto(a, n)
+  integer :: n, i
+  real :: a(:)
+  logical :: use_gpu
+
+  use_gpu = .true.
+  !$acc data if(use_gpu)
+  do i = 1, n
+    if (a(i) > 0.0) goto 100
+  end do
+  !$acc end data
+100 continue
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_unstructured4_goto
+! CHECK: acc.data if(%{{.*}}) {
 ! CHECK: acc.terminator
 ! CHECK: }
 
@@ -265,8 +290,9 @@ subroutine test_unstructured_collapse_cycle(a)
 ! CHECK: acc.yield
 ! CHECK: }
 
-! Test that `acc serial loop collapse(N)` lowers cleanly when the early-exit
-! is a STOP (the form already covered for collapse=1 by test_unstructured2).
+! `acc serial loop collapse(N)` with STOP in body: wrap-in-execute-region hides
+! the unstructured if/stop and the three collapsed iterators lower as a single
+! structured acc.loop control(...) (no `unstructured` attribute).
 subroutine test_unstructured_collapse_stop(a)
   integer :: i, j, k
   real :: a(:,:,:)
@@ -285,9 +311,11 @@ subroutine test_unstructured_collapse_stop(a)
 ! CHECK: acc.private varPtr(%{{.*}} : !fir.ref<i32>) recipe(@privatization_ref_i32) -> !fir.ref<i32> {implicit = true, name = "i"}
 ! CHECK: acc.private varPtr(%{{.*}} : !fir.ref<i32>) recipe(@privatization_ref_i32) -> !fir.ref<i32> {implicit = true, name = "j"}
 ! CHECK: acc.private varPtr(%{{.*}} : !fir.ref<i32>) recipe(@privatization_ref_i32) -> !fir.ref<i32> {implicit = true, name = "k"}
-! CHECK: acc.loop combined(serial) private(%{{.*}}, %{{.*}}, %{{.*}} : !fir.ref<i32>, !fir.ref<i32>, !fir.ref<i32>) {
+! CHECK: acc.loop combined(serial) private({{.*}}) control({{.*}}) = ({{.*}}) to ({{.*}}) step ({{.*}}) {
+! CHECK: scf.execute_region
 ! CHECK: fir.call @_FortranAStopStatementText
-! CHECK: }
+! CHECK-NOT: unstructured
+! CHECK: } attributes {collapse = [3]{{.*}}}
 
 ! Test orphaned `acc loop collapse(N)`
 subroutine test_unstructured_collapse_loop_only(a)
@@ -311,7 +339,9 @@ subroutine test_unstructured_collapse_loop_only(a)
 ! CHECK: acc.loop private(%{{.*}}, %{{.*}} : !fir.ref<i32>, !fir.ref<i32>) {
 ! CHECK: } attributes {collapse = [2], collapseDeviceType = [#acc.device_type<none>], independent = [#acc.device_type<none>], unstructured}
 
-! Standalone `acc loop seq` with STOP in body (explicit `seq` clause).
+! Standalone `acc loop seq` with STOP: wrap-in-execute-region hides the
+! if/stop and the DO lowers as structured acc.loop control(...) (no
+! `unstructured` attribute).
 subroutine test_unstructured_loop_seq_stop(a)
   integer :: i, j
   real :: a(:,:,:)
@@ -324,11 +354,33 @@ subroutine test_unstructured_loop_seq_stop(a)
 end subroutine
 
 ! CHECK-LABEL: func.func @_QPtest_unstructured_loop_seq_stop
-! CHECK: acc.loop private({{.*}})
+! CHECK: acc.loop private({{.*}}) control({{.*}}) = ({{.*}}) to ({{.*}}) step ({{.*}}) {
+! CHECK: scf.execute_region
 ! CHECK: fir.call @_FortranAStopStatementText
+! CHECK-NOT: unstructured
+! CHECK: } attributes {{{.*}}seq = [#acc.device_type<none>]{{.*}}}
+
+! Same loop but the if-construct has a GOTO exiting all loops, so the
+! if-construct is not wrappable, the DO remains unstructured, and acc.loop
+! emits the unstructured form with the `unstructured` attribute.
+subroutine test_unstructured_loop_seq_goto(a)
+  integer :: i, j
+  real :: a(:,:,:)
+  !$acc loop seq
+  do i = 1, 10
+    do j = 1, 10
+      if (a(1,2,3) > 10.0) goto 100
+    end do
+  end do
+100 continue
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_unstructured_loop_seq_goto
+! CHECK: acc.loop private({{.*}}) {
+! CHECK: cf.br
 ! CHECK: } attributes {{{.*}}seq = [#acc.device_type<none>], unstructured}
 
-! Standalone `acc loop auto` with STOP in body (explicit `auto` clause).
+! Standalone `acc loop auto` with STOP: same wrap-makes-structured behavior.
 subroutine test_unstructured_loop_auto_stop(a)
   integer :: i, j
   real :: a(:,:,:)
@@ -341,12 +393,32 @@ subroutine test_unstructured_loop_auto_stop(a)
 end subroutine
 
 ! CHECK-LABEL: func.func @_QPtest_unstructured_loop_auto_stop
-! CHECK: acc.loop private({{.*}})
+! CHECK: acc.loop private({{.*}}) control({{.*}}) = ({{.*}}) to ({{.*}}) step ({{.*}}) {
+! CHECK: scf.execute_region
 ! CHECK: fir.call @_FortranAStopStatementText
+! CHECK-NOT: unstructured
+! CHECK: } attributes {auto_ = [#acc.device_type<none>]{{.*}}}
+
+! Same loop with GOTO exit: genuinely unstructured, `unstructured` attribute
+! is emitted on acc.loop.
+subroutine test_unstructured_loop_auto_goto(a)
+  integer :: i, j
+  real :: a(:,:,:)
+  !$acc loop auto
+  do i = 1, 10
+    do j = 1, 10
+      if (a(1,2,3) > 10.0) goto 100
+    end do
+  end do
+100 continue
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_unstructured_loop_auto_goto
+! CHECK: acc.loop private({{.*}}) {
+! CHECK: cf.br
 ! CHECK: } attributes {auto_ = [#acc.device_type<none>], {{.*}}unstructured}
 
-! Standalone `acc loop` inside `acc serial` with STOP in body (loop is `seq`
-! by default because parent compute construct is serial).
+! Standalone `acc loop` inside `acc serial` with STOP: wrap-makes-structured.
 subroutine test_unstructured_loop_in_serial_stop(a)
   integer :: i, j
   real :: a(:,:,:)
@@ -362,11 +434,11 @@ subroutine test_unstructured_loop_in_serial_stop(a)
 
 ! CHECK-LABEL: func.func @_QPtest_unstructured_loop_in_serial_stop
 ! CHECK: acc.serial
-! CHECK: acc.loop private({{.*}})
+! CHECK: acc.loop private({{.*}}) control({{.*}}) = ({{.*}}) to ({{.*}}) step ({{.*}}) {
+! CHECK: scf.execute_region
 ! CHECK: fir.call @_FortranAStopStatementText
-! CHECK: } attributes {{{.*}}seq = [#acc.device_type<none>], unstructured}
 
-! Orphan `acc loop` inside a `seq` acc routine: loop is `seq` by default.
+! Orphan `acc loop` inside a `seq` acc routine with STOP: wrap-makes-structured.
 subroutine test_unstructured_orphan_loop_in_seq_routine(a)
   integer :: i, j
   real :: a(:,:,:)
@@ -380,7 +452,88 @@ subroutine test_unstructured_orphan_loop_in_seq_routine(a)
 end subroutine
 
 ! CHECK-LABEL: func.func @_QPtest_unstructured_orphan_loop_in_seq_routine
-! CHECK: acc.loop private({{.*}})
+! CHECK: acc.loop private({{.*}}) control({{.*}}) = ({{.*}}) to ({{.*}}) step ({{.*}}) {
+! CHECK: scf.execute_region
 ! CHECK: fir.call @_FortranAStopStatementText
+
+! Same orphan loop with GOTO exit: genuinely unstructured.
+subroutine test_unstructured_orphan_loop_in_seq_routine_goto(a)
+  integer :: i, j
+  real :: a(:,:,:)
+  !$acc routine seq
+  !$acc loop
+  do i = 1, 10
+    do j = 1, 10
+      if (a(1,2,3) > 10.0) goto 100
+    end do
+  end do
+100 continue
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_unstructured_orphan_loop_in_seq_routine_goto
+! CHECK: acc.loop private({{.*}}) {
+! CHECK: cf.br
 ! CHECK: } attributes {{{.*}}seq = [#acc.device_type<none>], unstructured}
 
+! DO loop with STOP inside `!$acc kernels`. Previously flagged as
+! "unstructured do loop in acc kernels" (TODO); wrap-in-execute-region now
+! hides the if/stop CFG inside scf.execute_region so the DO itself lowers as
+! a structured acc.loop with control bounds.
+subroutine test_unstructured_kernels_do_stop()
+  integer :: i
+  integer, parameter :: n = 10
+  real, dimension(n) :: a, b
+
+  !$acc kernels
+  do i = 1, n
+    a(i) = b(i) + 1.0
+    if (i == 5) stop
+  end do
+  !$acc end kernels
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_unstructured_kernels_do_stop
+! CHECK: acc.kernels
+! CHECK: acc.loop private({{.*}}) control({{.*}}) = ({{.*}}) to ({{.*}}) step ({{.*}}) {
+! CHECK: scf.execute_region
+! CHECK: fir.call @_FortranAStopStatement
+
+! `!$acc parallel loop` (combined construct) with STOP in the innermost
+! body. wrap-in-execute-region hides the if/stop CFG so the combined
+! construct still lowers as structured acc.parallel + acc.loop.
+subroutine test_unstructured_parallel_loop_stop(a, b, c)
+  integer :: i, j, k
+  real :: a(:,:,:), b(:,:,:), c(:,:,:)
+
+  !$acc parallel loop
+  do i = 1, 10
+    do j = 1, 10
+      do k = 1, 10
+        if (a(1,2,3) > 10) stop 'just to be unstructured'
+      end do
+    end do
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_unstructured_parallel_loop_stop
+! CHECK: acc.parallel combined(loop)
+! CHECK: acc.loop combined(parallel)
+
+! `!$acc parallel loop collapse(3)` with STOP in the innermost body. Same
+! wrap behavior as above with an added collapse clause.
+subroutine test_unstructured_parallel_loop_collapse3_stop(a)
+  integer :: i, j, k
+  real :: a(:,:,:)
+  !$acc parallel loop collapse(3)
+  do i = 1, 10
+    do j = 1, 10
+      do k = 1, 10
+        if (a(1,2,3) > 10) stop 'just to be unstructured'
+      end do
+    end do
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_unstructured_parallel_loop_collapse3_stop
+! CHECK: acc.parallel combined(loop)
+! CHECK: acc.loop combined(parallel)
diff --git a/flang/test/Lower/OpenMP/loop-compound.f90 b/flang/test/Lower/OpenMP/loop-compound.f90
index 8c025ab237920..b55235b3a4718 100644
--- a/flang/test/Lower/OpenMP/loop-compound.f90
+++ b/flang/test/Lower/OpenMP/loop-compound.f90
@@ -242,19 +242,23 @@ program main
   ! CHECK-NEXT: omp.wsloop
   ! CHECK-NEXT: omp.loop_nest
   !
-  ! Verify the conrol-flow of the unstructured inner loop.
-  ! CHECK:        cf.br ^[[BB1:.*]]
-  ! CHECK:      ^[[BB1]]:
-  ! CHECK:        cf.br ^[[BB2:.*]]
-  ! CHECK:      ^[[BB2]]:
-  ! CHECK:        cf.cond_br %{{.*}}, ^[[BB3:.*]], ^[[BB6:.*]]
-  ! CHECK:      ^[[BB3]]:
-  ! CHECK:        cf.cond_br %{{.*}}, ^[[BB4:.*]], ^[[BB5:.*]]
-  ! CHECK:      ^[[BB4]]:
-  ! CHECK:        cf.br ^[[BB6]]
-  ! CHECK:      ^[[BB5]]:
-  ! CHECK:        cf.br ^[[BB2]]
-  ! CHECK:      ^[[BB6]]:
+  ! Verify the control-flow of the unstructured inner loop, wrapped in an
+  ! scf.execute_region.
+  ! CHECK:        scf.execute_region no_inline {
+  ! CHECK:          cf.br ^[[BB1:.*]]
+  ! CHECK:        ^[[BB1]]:
+  ! CHECK:          cf.br ^[[BB2:.*]]
+  ! CHECK:        ^[[BB2]]:
+  ! CHECK:          cf.cond_br %{{.*}}, ^[[BB3:.*]], ^[[BB6:.*]]
+  ! CHECK:        ^[[BB3]]:
+  ! CHECK:          cf.cond_br %{{.*}}, ^[[BB4:.*]], ^[[BB5:.*]]
+  ! CHECK:        ^[[BB4]]:
+  ! CHECK:          cf.br ^[[BB6]]
+  ! CHECK:        ^[[BB5]]:
+  ! CHECK:          cf.br ^[[BB2]]
+  ! CHECK:        ^[[BB6]]:
+  ! CHECK-NEXT:     scf.yield
+  ! CHECK:        }
   ! CHECK-NEXT:   omp.yield
   !$omp target teams distribute parallel do
   do i = 1, 10
diff --git a/flang/test/Lower/OpenMP/parallel-reduction3.f90 b/flang/test/Lower/OpenMP/parallel-reduction3.f90
index 7437e1d35a624..ea6bf6cdf3ac9 100644
--- a/flang/test/Lower/OpenMP/parallel-reduction3.f90
+++ b/flang/test/Lower/OpenMP/parallel-reduction3.f90
@@ -104,19 +104,22 @@
 ! CHECK:             }
 ! CHECK:             omp.terminator
 ! CHECK:           }
-! CHECK:           %[[VAL_39:.*]] = arith.constant 1 : index
-! CHECK:           %[[VAL_40:.*]] = hlfir.designate %[[VAL_12]]#0 (%[[VAL_39]])  : (!fir.box<!fir.array<?xi32>>, index) -> !fir.ref<i32>
-! CHECK:           %[[VAL_41:.*]] = fir.load %[[VAL_40]] : !fir.ref<i32>
-! CHECK:           %[[VAL_42:.*]] = arith.constant 5050 : i32
-! CHECK:           %[[VAL_43:.*]] = arith.cmpi ne, %[[VAL_41]], %[[VAL_42]] : i32
-! CHECK:           cf.cond_br %[[VAL_43]], ^bb1, ^bb2
-! CHECK:         ^bb1:
-! CHECK:           %[[VAL_44:.*]] = arith.constant 1 : i32
-! CHECK:           %[[VAL_45:.*]] = arith.constant false
-! CHECK:           %[[VAL_46:.*]] = arith.constant false
-! CHECK:           fir.call @_FortranAStopStatement(%[[VAL_44]], %[[VAL_45]], %[[VAL_46]]) fastmath<contract> : (i32, i1, i1) -> ()
-! CHECK:           fir.unreachable
-! CHECK:         ^bb2:
+! CHECK:           scf.execute_region no_inline {
+! CHECK:             %[[VAL_39:.*]] = arith.constant 1 : index
+! CHECK:             %[[VAL_40:.*]] = hlfir.designate %[[VAL_12]]#0 (%[[VAL_39]])  : (!fir.box<!fir.array<?xi32>>, index) -> !fir.ref<i32>
+! CHECK:             %[[VAL_41:.*]] = fir.load %[[VAL_40]] : !fir.ref<i32>
+! CHECK:             %[[VAL_42:.*]] = arith.constant 5050 : i32
+! CHECK:             %[[VAL_43:.*]] = arith.cmpi ne, %[[VAL_41]], %[[VAL_42]] : i32
+! CHECK:             cf.cond_br %[[VAL_43]], ^bb1, ^bb2
+! CHECK:           ^bb1:
+! CHECK:             %[[VAL_44:.*]] = arith.constant 1 : i32
+! CHECK:             %[[VAL_45:.*]] = arith.constant false
+! CHECK:             %[[VAL_46:.*]] = arith.constant false
+! CHECK:             fir.call @_FortranAStopStatement(%[[VAL_44]], %[[VAL_45]], %[[VAL_46]]) fastmath<contract> : (i32, i1, i1) -> ()
+! CHECK:             fir.unreachable
+! CHECK:           ^bb2:
+! CHECK:             scf.yield
+! CHECK:           }
 ! CHECK:           return
 ! CHECK:         }
 ! CHECK:         func.func private @_FortranAStopStatement(i32, i1, i1) attributes {fir.runtime}
diff --git a/flang/test/Lower/OpenMP/stop-stmt-in-region.f90 b/flang/test/Lower/OpenMP/stop-stmt-in-region.f90
index 67cd8b17f484b..8acf1eaf387f8 100644
--- a/flang/test/Lower/OpenMP/stop-stmt-in-region.f90
+++ b/flang/test/Lower/OpenMP/stop-stmt-in-region.f90
@@ -48,17 +48,20 @@ subroutine test_stop_in_region2()
 ! CHECK:         omp.parallel   {
 ! CHECK:           %[[VAL_1:.*]] = arith.constant 3 : i32
 ! CHECK:           hlfir.assign %[[VAL_1]] to %[[VAL_0_DECL]]#0 : i32, !fir.ref<i32>
-! CHECK:           %[[VAL_2:.*]] = fir.load %[[VAL_0_DECL]]#0 : !fir.ref<i32>
-! CHECK:           %[[VAL_3:.*]] = arith.constant 1 : i32
-! CHECK:           %[[VAL_4:.*]] = arith.cmpi sgt, %[[VAL_2]], %[[VAL_3]] : i32
-! CHECK:           cf.cond_br %[[VAL_4]], ^bb1, ^bb2
-! CHECK:         ^bb1:
-! CHECK:           %[[VAL_5:.*]] = fir.load %[[VAL_0_DECL]]#0 : !fir.ref<i32>
-! CHECK:           %[[VAL_6:.*]] = arith.constant false
-! CHECK:           %[[VAL_7:.*]] = arith.constant false
-! CHECK:           fir.call @_FortranAStopStatement(%[[VAL_5]], %[[VAL_6]], %[[VAL_7]]) {{.*}} : (i32, i1, i1) -> ()
-! CHECK:           omp.terminator
-! CHECK:         ^bb2:
+! CHECK:           scf.execute_region no_inline {
+! CHECK:             %[[VAL_2:.*]] = fir.load %[[VAL_0_DECL]]#0 : !fir.ref<i32>
+! CHECK:             %[[VAL_3:.*]] = arith.constant 1 : i32
+! CHECK:             %[[VAL_4:.*]] = arith.cmpi sgt, %[[VAL_2]], %[[VAL_3]] : i32
+! CHECK:             cf.cond_br %[[VAL_4]], ^bb1, ^bb2
+! CHECK:           ^bb1:
+! CHECK:             %[[VAL_5:.*]] = fir.load %[[VAL_0_DECL]]#0 : !fir.ref<i32>
+! CHECK:             %[[VAL_6:.*]] = arith.constant false
+! CHECK:             %[[VAL_7:.*]] = arith.constant false
+! CHECK:             fir.call @_FortranAStopStatement(%[[VAL_5]], %[[VAL_6]], %[[VAL_7]]) {{.*}} : (i32, i1, i1) -> ()
+! CHECK:             fir.unreachable
+! CHECK:           ^bb2:
+! CHECK:             scf.yield
+! CHECK:           }
 ! CHECK:           omp.terminator
 ! CHECK:         }
 ! CHECK:         return
@@ -88,26 +91,25 @@ subroutine test_stop_in_region3()
 ! CHECK-NEXT:      omp.loop_nest (%[[VAL_6:.*]]) : i32 = (%[[VAL_3]]) to (%[[VAL_4]]) inclusive step (%[[VAL_5]]) {
 ! CHECK:             %[[VAL_0_DECL:.*]]:2 = hlfir.declare %[[VAL_0]] {uniq_name = "_QFtest_stop_in_region4Ei"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
 ! CHECK:             hlfir.assign %[[VAL_6]] to %[[VAL_0_DECL]]#0 : i32, !fir.ref<i32>
-! CHECK:             cf.br ^bb1
-! CHECK:           ^bb1:
 ! CHECK:             %[[VAL_7:.*]] = arith.constant 3 : i32
 ! CHECK:             hlfir.assign %[[VAL_7]] to %[[VAL_2_DECL]]#0 : i32, !fir.ref<i32>
-! CHECK:             %[[VAL_8:.*]] = fir.load %[[VAL_2_DECL]]#0 : !fir.ref<i32>
-! CHECK:             %[[VAL_9:.*]] = arith.constant 1 : i32
-! CHECK:             %[[VAL_10:.*]] = arith.cmpi sgt, %[[VAL_8]], %[[VAL_9]] : i32
-! CHECK:             cf.cond_br %[[VAL_10]], ^bb2, ^bb3
-! CHECK:           ^bb2:
-! CHECK:             %[[VAL_11:.*]] = fir.load %[[VAL_2_DECL]]#0 : !fir.ref<i32>
-! CHECK:             %[[VAL_12:.*]] = arith.constant false
-! CHECK:             %[[VAL_13:.*]] = arith.constant false
-! CHECK:             fir.call @_FortranAStopStatement(%[[VAL_11]], %[[VAL_12]], %[[VAL_13]]) {{.*}} : (i32, i1, i1) -> ()
-! CHECK:             omp.yield
-! CHECK:           ^bb3:
+! CHECK:             scf.execute_region no_inline {
+! CHECK:               %[[VAL_8:.*]] = fir.load %[[VAL_2_DECL]]#0 : !fir.ref<i32>
+! CHECK:               %[[VAL_9:.*]] = arith.constant 1 : i32
+! CHECK:               %[[VAL_10:.*]] = arith.cmpi sgt, %[[VAL_8]], %[[VAL_9]] : i32
+! CHECK:               cf.cond_br %[[VAL_10]], ^bb1, ^bb2
+! CHECK:             ^bb1:
+! CHECK:               %[[VAL_11:.*]] = fir.load %[[VAL_2_DECL]]#0 : !fir.ref<i32>
+! CHECK:               %[[VAL_12:.*]] = arith.constant false
+! CHECK:               %[[VAL_13:.*]] = arith.constant false
+! CHECK:               fir.call @_FortranAStopStatement(%[[VAL_11]], %[[VAL_12]], %[[VAL_13]]) {{.*}} : (i32, i1, i1) -> ()
+! CHECK:               fir.unreachable
+! CHECK:             ^bb2:
+! CHECK:               scf.yield
+! CHECK:             }
 ! CHECK:             omp.yield
 ! CHECK:           }
 ! CHECK:         }
-! CHECK:         cf.br ^bb1
-! CHECK:       ^bb1:
 ! CHECK:         return
 ! CHECK:       }
 
@@ -139,12 +141,15 @@ subroutine test_stop_in_region5()
 
 !CHECK-LABEL: func.func @_QPtest_stop_in_region6
 !CHECK:  omp.parallel   {
-!CHECK:    cf.cond_br %{{.*}}, ^[[BB1:.*]], ^[[BB2:.*]]
-!CHECK:  ^[[BB1]]:
-!CHECK:    {{.*}}fir.call @_FortranAStopStatement({{.*}}, {{.*}}, {{.*}}) fastmath<contract> : (i32, i1, i1) -> ()
-!CHECK:    omp.terminator
-!CHECK:  ^[[BB2]]:
-!CHECK:    {{.*}}fir.call @_FortranAStopStatement({{.*}}, {{.*}}, {{.*}}) fastmath<contract> : (i32, i1, i1) -> ()
+!CHECK:    scf.execute_region no_inline {
+!CHECK:      cf.cond_br %{{.*}}, ^[[BB1:.*]], ^[[BB2:.*]]
+!CHECK:    ^[[BB1]]:
+!CHECK:      {{.*}}fir.call @_FortranAStopStatement({{.*}}, {{.*}}, {{.*}}) fastmath<contract> : (i32, i1, i1) -> ()
+!CHECK:      fir.unreachable
+!CHECK:    ^[[BB2]]:
+!CHECK:      {{.*}}fir.call @_FortranAStopStatement({{.*}}, {{.*}}, {{.*}}) fastmath<contract> : (i32, i1, i1) -> ()
+!CHECK:      fir.unreachable
+!CHECK:    }
 !CHECK:    omp.terminator
 !CHECK:  }
 !CHECK:  return
diff --git a/flang/test/Lower/OpenMP/unstructured.f90 b/flang/test/Lower/OpenMP/unstructured.f90
index fa33abbb6cd4a..b60c1f5b7ef82 100644
--- a/flang/test/Lower/OpenMP/unstructured.f90
+++ b/flang/test/Lower/OpenMP/unstructured.f90
@@ -64,42 +64,45 @@ subroutine ss2(n) ! unstructured OpenMP construct; loop exit inside construct
 ! CHECK:     %[[ALLOCA_K:.*]] = fir.alloca i32 {bindc_name = "k", pinned}
 ! CHECK:     %[[K_DECL:.*]]:2 = hlfir.declare %[[ALLOCA_K]] {uniq_name = "_QFss3Ek"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
 
-! CHECK:     br ^bb1
-! CHECK:   ^bb1:  // 2 preds: ^bb0, ^bb3
-! CHECK:     cond_br %{{[0-9]*}}, ^bb2, ^bb4
-! CHECK:   ^bb2:  // pred: ^bb1
-
-! CHECK:     omp.wsloop private(@{{.*}} %{{.*}}#0 -> %[[ALLOCA_2:.*]] : !fir.ref<i32>) {
-! CHECK:       omp.loop_nest (%[[ARG1:.*]]) : {{.*}} {
-! CHECK:         %[[OMP_LOOP_K_DECL:.*]]:2 = hlfir.declare %[[ALLOCA_2]] {uniq_name = "_QFss3Ek"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
-! CHECK:         hlfir.assign %[[ARG1]] to %[[OMP_LOOP_K_DECL]]#0 : i32, !fir.ref<i32>
-! CHECK:         @_FortranAioBeginExternalListOutput
-! CHECK:         %[[LOAD_1:.*]] = fir.load %[[OMP_LOOP_K_DECL]]#0 : !fir.ref<i32>
-! CHECK:         @_FortranAioOutputInteger32(%{{.*}}, %[[LOAD_1]])
-! CHECK:         omp.yield
+! CHECK:     fir.do_loop
+! CHECK:       omp.wsloop private(@{{.*}} %{{.*}}#0 -> %[[ALLOCA_2:.*]] : !fir.ref<i32>) {
+! CHECK:         omp.loop_nest (%[[ARG1:.*]]) : {{.*}} {
+! CHECK:           %[[OMP_LOOP_K_DECL:.*]]:2 = hlfir.declare %[[ALLOCA_2]] {uniq_name = "_QFss3Ek"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
+! CHECK:           hlfir.assign %[[ARG1]] to %[[OMP_LOOP_K_DECL]]#0 : i32, !fir.ref<i32>
+! CHECK:           @_FortranAioBeginExternalListOutput
+! CHECK:           %[[LOAD_1:.*]] = fir.load %[[OMP_LOOP_K_DECL]]#0 : !fir.ref<i32>
+! CHECK:           @_FortranAioOutputInteger32(%{{.*}}, %[[LOAD_1]])
+! CHECK:           omp.yield
+! CHECK:         }
 ! CHECK:       }
-! CHECK:     }
 
-! CHECK:     omp.wsloop private(@{{.*}} %{{.*}}#0 -> %[[ALLOCA_1:.*]] : !fir.ref<i32>) {
-! CHECK:       omp.loop_nest (%[[ARG2:.*]]) : {{.*}} {
-! CHECK:         %[[OMP_LOOP_J_DECL:.*]]:2 = hlfir.declare %[[ALLOCA_1]] {uniq_name = "_QFss3Ej"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
-! CHECK:         hlfir.assign %[[ARG2]] to %[[OMP_LOOP_J_DECL]]#0 : i32, !fir.ref<i32>
-! CHECK:         br ^bb1
-! CHECK:       ^bb2:  // 2 preds: ^bb1, ^bb5
-! CHECK:         cond_br %{{[0-9]*}}, ^bb3, ^bb6
-! CHECK:       ^bb3:  // pred: ^bb2
-! CHECK:         cond_br %{{[0-9]*}}, ^bb4, ^bb5
-! CHECK:       ^bb4:  // pred: ^bb3
-! CHECK:         @_FortranAioBeginExternalListOutput
-! CHECK:         %[[LOAD_2:.*]] = fir.load %[[K_DECL]]#0 : !fir.ref<i32>
-! CHECK:         @_FortranAioOutputInteger32(%{{.*}}, %[[LOAD_2]])
-! CHECK:         br ^bb2
-! CHECK:       ^bb6:  // 2 preds: ^bb2, ^bb4
-! CHECK:         omp.yield
+! CHECK:       omp.wsloop private(@{{.*}} %{{.*}}#0 -> %[[ALLOCA_1:.*]] : !fir.ref<i32>) {
+! CHECK:         omp.loop_nest (%[[ARG2:.*]]) : {{.*}} {
+! CHECK:           %[[OMP_LOOP_J_DECL:.*]]:2 = hlfir.declare %[[ALLOCA_1]] {uniq_name = "_QFss3Ej"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
+! CHECK:           hlfir.assign %[[ARG2]] to %[[OMP_LOOP_J_DECL]]#0 : i32, !fir.ref<i32>
+! CHECK:           scf.execute_region no_inline {
+! CHECK:             cf.br ^bb1
+! CHECK:           ^bb1:
+! CHECK:             cf.br ^bb2
+! CHECK:           ^bb2:
+! CHECK:             cf.cond_br %{{[0-9]*}}, ^bb3, ^bb6
+! CHECK:           ^bb3:
+! CHECK:             cf.cond_br %{{[0-9]*}}, ^bb4, ^bb5
+! CHECK:           ^bb4:
+! CHECK:             cf.br ^bb6
+! CHECK:           ^bb5:
+! CHECK:             @_FortranAioBeginExternalListOutput
+! CHECK:             %[[LOAD_2:.*]] = fir.load %[[K_DECL]]#0 : !fir.ref<i32>
+! CHECK:             @_FortranAioOutputInteger32(%{{.*}}, %[[LOAD_2]])
+! CHECK:             cf.br ^bb2
+! CHECK:           ^bb6:
+! CHECK:             scf.yield
+! CHECK:           }
+! CHECK:           omp.yield
+! CHECK:         }
 ! CHECK:       }
+! CHECK:       fir.result
 ! CHECK:     }
-! CHECK:     br ^bb1
-! CHECK:   ^bb4:  // pred: ^bb1
 ! CHECK:     omp.terminator
 ! CHECK:   }
 ! CHECK: }
@@ -158,18 +161,19 @@ subroutine ss4(n) ! CYCLE in OpenMP wsloop constructs
 ! CHECK:  omp.parallel private(@{{.*}} %{{.*}}#0 -> %{{.*}} : {{.*}}) {
 ! CHECK:    omp.wsloop private({{.*}}) {
 ! CHECK:      omp.loop_nest {{.*}} {
-! CHECK:        br ^[[BB1:.*]]
-! CHECK:      ^[[BB1]]:
-! CHECK:        br ^[[BB2:.*]]
-! CHECK:      ^[[BB2]]:
-! CHECK:        cond_br %{{.*}}, ^[[BB3:.*]], ^[[BB6:.*]]
-! CHECK:      ^[[BB3]]:
-! CHECK:        cond_br %{{.*}}, ^[[BB4:.*]], ^[[BB3:.*]]
-! CHECK:      ^[[BB4]]:
-! CHECK:        br ^[[BB6]]
-! CHECK:      ^[[BB3]]:
-! CHECK:        br ^[[BB2]]
-! CHECK:      ^[[BB6]]:
+! CHECK:        scf.execute_region no_inline {
+! CHECK:          cf.br ^[[BB1:.*]]
+! CHECK:        ^[[BB1]]:
+! CHECK:          cf.cond_br %{{.*}}, ^[[BB2:.*]], ^[[BB5:.*]]
+! CHECK:        ^[[BB2]]:
+! CHECK:          cf.cond_br %{{.*}}, ^[[BB3:.*]], ^[[BB4:.*]]
+! CHECK:        ^[[BB3]]:
+! CHECK:          cf.br ^[[BB5]]
+! CHECK:        ^[[BB4]]:
+! CHECK:          cf.br ^[[BB1]]
+! CHECK:        ^[[BB5]]:
+! CHECK:          scf.yield
+! CHECK:        }
 ! CHECK:        omp.yield
 ! CHECK:      }
 ! CHECK:    }
@@ -193,30 +197,28 @@ subroutine ss5() ! EXIT inside OpenMP wsloop (inside parallel)
 end
 
 ! CHECK-LABEL: func @_QPss6() {
-! CHECK:  omp.parallel private(@{{.*}} %{{.*}}#0 -> %{{.*}} : {{.*}}) {
-! CHECK:    br ^[[BB1_OUTER:.*]]
-! CHECK:  ^[[BB1_OUTER]]:
-! CHECK:    cond_br %{{.*}}, ^[[BB2_OUTER:.*]], ^[[BB3_OUTER:.*]]
-! CHECK:  ^[[BB2_OUTER]]:
-! CHECK:    omp.wsloop private({{.*}}) {
-! CHECK:      omp.loop_nest {{.*}} {
-! CHECK:        br ^[[BB1:.*]]
-! CHECK:      ^[[BB1]]:
-! CHECK:        br ^[[BB2:.*]]
-! CHECK:      ^[[BB2]]:
-! CHECK:        cond_br %{{.*}}, ^[[BB3:.*]], ^[[BB6:.*]]
-! CHECK:      ^[[BB3]]:
-! CHECK:        cond_br %{{.*}}, ^[[BB4:.*]], ^[[BB5:.*]]
-! CHECK:      ^[[BB4]]:
-! CHECK:        br ^[[BB6]]
-! CHECK:      ^[[BB5]]
-! CHECK:        br ^[[BB2]]
-! CHECK:      ^[[BB6]]:
-! CHECK:        omp.yield
+! CHECK:  omp.parallel private(@{{.*}} %{{.*}}#0 -> %{{.*}}, @{{.*}} %{{.*}}#0 -> %{{.*}} : {{.*}}, {{.*}}) {
+! CHECK:    fir.do_loop
+! CHECK:      omp.wsloop private({{.*}}) {
+! CHECK:        omp.loop_nest {{.*}} {
+! CHECK:          scf.execute_region no_inline {
+! CHECK:            cf.br ^[[BB1:.*]]
+! CHECK:          ^[[BB1]]:
+! CHECK:            cf.cond_br %{{.*}}, ^[[BB2:.*]], ^[[BB5:.*]]
+! CHECK:          ^[[BB2]]:
+! CHECK:            cf.cond_br %{{.*}}, ^[[BB3:.*]], ^[[BB4:.*]]
+! CHECK:          ^[[BB3]]:
+! CHECK:            cf.br ^[[BB5]]
+! CHECK:          ^[[BB4]]:
+! CHECK:            cf.br ^[[BB1]]
+! CHECK:          ^[[BB5]]:
+! CHECK:            scf.yield
+! CHECK:          }
+! CHECK:          omp.yield
+! CHECK:        }
 ! CHECK:      }
+! CHECK:      fir.result
 ! CHECK:    }
-! CHECK:    br ^[[BB1_OUTER]]
-! CHECK:  ^[[BB3_OUTER]]:
 ! CHECK:    omp.terminator
 ! CHECK:  }
 subroutine ss6() ! EXIT inside OpenMP wsloop in a do loop (inside parallel)
@@ -239,33 +241,31 @@ subroutine ss6() ! EXIT inside OpenMP wsloop in a do loop (inside parallel)
 end
 
 ! CHECK-LABEL: func @_QPss7() {
-! CHECK: br ^[[BB1_OUTER:.*]]
-! CHECK: ^[[BB1_OUTER]]:
-! CHECK:   cond_br %{{.*}}, ^[[BB2_OUTER:.*]], ^[[BB3_OUTER:.*]]
-! CHECK-NEXT: ^[[BB2_OUTER:.*]]:
-! CHECK:   omp.parallel  {
-! CHECK:     omp.wsloop private({{.*}}) {
-! CHECK:       omp.loop_nest {{.*}} {
-! CHECK:         br ^[[BB1:.*]]
-! CHECK-NEXT:       ^[[BB1]]:
-! CHECK:         br ^[[BB2:.*]]
-! CHECK-NEXT:       ^[[BB2]]:
-! CHECK:         cond_br %{{.*}}, ^[[BB3:.*]], ^[[BB6:.*]]
-! CHECK-NEXT:       ^[[BB3]]:
-! CHECK:         cond_br %{{.*}}, ^[[BB4:.*]], ^[[BB5:.*]]
-! CHECK-NEXT:       ^[[BB4]]:
-! CHECK:         br ^[[BB6]]
-! CHECK-NEXT:       ^[[BB5]]:
-! CHECK:         br ^[[BB2]]
-! CHECK-NEXT:       ^[[BB6]]:
-! CHECK:         omp.yield
+! CHECK:   fir.do_loop
+! CHECK:     omp.parallel  {
+! CHECK:       omp.wsloop private({{.*}}) {
+! CHECK:         omp.loop_nest {{.*}} {
+! CHECK:           scf.execute_region no_inline {
+! CHECK:             cf.br ^[[BB1:.*]]
+! CHECK:           ^[[BB1]]:
+! CHECK:             cf.cond_br %{{.*}}, ^[[BB2:.*]], ^[[BB5:.*]]
+! CHECK:           ^[[BB2]]:
+! CHECK:             cf.cond_br %{{.*}}, ^[[BB3:.*]], ^[[BB4:.*]]
+! CHECK:           ^[[BB3]]:
+! CHECK:             cf.br ^[[BB5]]
+! CHECK:           ^[[BB4]]:
+! CHECK:             cf.br ^[[BB1]]
+! CHECK:           ^[[BB5]]:
+! CHECK:             scf.yield
+! CHECK:           }
+! CHECK:           omp.yield
+! CHECK:         }
 ! CHECK:       }
+! CHECK:       omp.terminator
 ! CHECK:     }
-! CHECK:     omp.terminator
+! CHECK:     fir.result
 ! CHECK:   }
-! CHECK:   br ^[[BB1_OUTER]]
-! CHECK-NEXT: ^[[BB3_OUTER]]:
-! CHECK-NEXT:   return
+! CHECK:   return
 subroutine ss7() ! EXIT inside OpenMP parallel do (inside do loop)
   integer :: x
     do i = 1, 3
@@ -286,18 +286,19 @@ subroutine ss7() ! EXIT inside OpenMP parallel do (inside do loop)
 ! CHECK:  omp.parallel  {
 ! CHECK:    omp.wsloop private({{.*}}) {
 ! CHECK:      omp.loop_nest {{.*}} {
-! CHECK:        br ^[[BB1:.*]]
-! CHECK-NEXT:      ^[[BB1]]:
-! CHECK:        br ^[[BB2:.*]]
-! CHECK:      ^[[BB2]]:
-! CHECK:        cond_br %{{.*}}, ^[[BB3:.*]], ^[[BB6:.*]]
-! CHECK:      ^[[BB3]]:
-! CHECK:        cond_br %{{.*}}, ^[[BB4:.*]], ^[[BB5:.*]]
-! CHECK:      ^[[BB4]]:
-! CHECK-NEXT:      br ^[[BB6]]
-! CHECK:      ^[[BB5]]:
-! CHECK:        br ^[[BB2]]
-! CHECK-NEXT:      ^[[BB6]]:
+! CHECK:        scf.execute_region no_inline {
+! CHECK:          cf.br ^[[BB1:.*]]
+! CHECK:        ^[[BB1]]:
+! CHECK:          cf.cond_br %{{.*}}, ^[[BB2:.*]], ^[[BB5:.*]]
+! CHECK:        ^[[BB2]]:
+! CHECK:          cf.cond_br %{{.*}}, ^[[BB3:.*]], ^[[BB4:.*]]
+! CHECK:        ^[[BB3]]:
+! CHECK:          cf.br ^[[BB5]]
+! CHECK:        ^[[BB4]]:
+! CHECK:          cf.br ^[[BB1]]
+! CHECK:        ^[[BB5]]:
+! CHECK:          scf.yield
+! CHECK:        }
 ! CHECK:        omp.yield
 ! CHECK:      }
 ! CHECK:    }
diff --git a/flang/test/Lower/OpenMP/wsloop-unstructured.f90 b/flang/test/Lower/OpenMP/wsloop-unstructured.f90
index fb79bd1c17d77..aa5729daf6a24 100644
--- a/flang/test/Lower/OpenMP/wsloop-unstructured.f90
+++ b/flang/test/Lower/OpenMP/wsloop-unstructured.f90
@@ -32,32 +32,31 @@ end subroutine sub
 ! CHECK:             omp.wsloop private({{.*}}) {
 ! CHECK-NEXT:          omp.loop_nest (%[[VAL_53:.*]], %[[VAL_54:.*]]) : i32 = ({{.*}}) to ({{.*}}) inclusive step ({{.*}}) {
 ! [...]
-! CHECK:                 cf.br ^bb1
-! CHECK:               ^bb1:
-! CHECK:                 cf.br ^bb2
-! CHECK:               ^bb2:
+! CHECK:                 scf.execute_region no_inline {
+! CHECK:                   cf.br ^bb1
+! CHECK:                 ^bb1:
 ! [...]
-! CHECK:                 cf.br ^bb3
-! CHECK:               ^bb3:
+! CHECK:                   cf.br ^bb2
+! CHECK:                 ^bb2:
 ! [...]
-! CHECK:                 %[[VAL_63:.*]] = arith.cmpi sgt, %{{.*}}, %{{.*}} : i32
-! CHECK:                 cf.cond_br %[[VAL_63]], ^bb4, ^bb7
-! CHECK:               ^bb4:
+! CHECK:                   %[[VAL_63:.*]] = arith.cmpi sgt, %{{.*}}, %{{.*}} : i32
+! CHECK:                   cf.cond_br %[[VAL_63]], ^bb3, ^bb6
+! CHECK:                 ^bb3:
 ! [...]
-! CHECK:                 %[[VAL_76:.*]] = arith.cmpf olt, %{{.*}}, %{{.*}} fastmath<contract> : f32
-! CHECK:                 cf.cond_br %[[VAL_76]], ^bb5, ^bb6
-! CHECK:               ^bb5:
-! CHECK:                 cf.br ^bb7
-! CHECK:               ^bb6:
+! CHECK:                   %[[VAL_76:.*]] = arith.cmpf olt, %{{.*}}, %{{.*}} fastmath<contract> : f32
+! CHECK:                   cf.cond_br %[[VAL_76]], ^bb4, ^bb5
+! CHECK:                 ^bb4:
+! CHECK:                   cf.br ^bb6
+! CHECK:                 ^bb5:
 ! [...]
-! CHECK:                 cf.br ^bb3
-! CHECK:               ^bb7:
+! CHECK:                   cf.br ^bb2
+! CHECK:                 ^bb6:
+! CHECK:                   scf.yield
+! CHECK:                 }
 ! CHECK:                 omp.yield
 ! CHECK:               }
 ! CHECK:             }
 ! CHECK:             omp.terminator
 ! CHECK:           }
-! CHECK:           cf.br ^bb1
-! CHECK:         ^bb1:
 ! CHECK:           return
 ! CHECK:         }
diff --git a/flang/test/Lower/branching-directive.f90 b/flang/test/Lower/branching-directive.f90
index 69270d7bcbe96..e4e511f33918c 100644
--- a/flang/test/Lower/branching-directive.f90
+++ b/flang/test/Lower/branching-directive.f90
@@ -2,15 +2,21 @@
 
 !https://github.com/llvm/llvm-project/issues/91526
 
+! The if-construct is unstructured (stop in else) but wrappable, so the
+! PFT-to-MLIR pass hides its CFG inside scf.execute_region. The THEN branch
+! correctly branches to the wrap's merge (yield) block rather than falling
+! into the ELSE block — the original bug fixed by issue 91526 stays fixed.
 !CHECK-LABEL: func.func @_QPsimple1
+!CHECK:   scf.execute_region
 !CHECK:   cf.cond_br %{{[0-9]+}}, ^bb[[THEN:[0-9]+]], ^bb[[ELSE:[0-9]+]]
 !CHECK: ^bb[[THEN]]:
 !CHECK:   omp.parallel
-!CHECK:   cf.br ^bb[[ENDIF:[0-9]+]]
+!CHECK:   cf.br ^bb[[MERGE:[0-9]+]]
 !CHECK: ^bb[[ELSE]]:
 !CHECK:   fir.call @_FortranAStopStatement
 !CHECK:   fir.unreachable
-!CHECK: ^bb[[ENDIF]]:
+!CHECK: ^bb[[MERGE]]:
+!CHECK:   scf.yield
 !CHECK:   return
 
 subroutine simple1(y)
@@ -26,7 +32,11 @@ subroutine simple1(y)
   end if
 end subroutine
 
-!CHECK-LABEL: func.func @_QPsimple2
+! Same scenario but the if-construct has a GOTO exiting the construct, so
+! branchesAreInternal is false and the wrap is skipped — the original
+! unstructured CFG with fir.unreachable is emitted.
+!CHECK-LABEL: func.func @_QPsimple1_goto
+!CHECK-NOT:   scf.execute_region
 !CHECK:   cf.cond_br %{{[0-9]+}}, ^bb[[THEN:[0-9]+]], ^bb[[ELSE:[0-9]+]]
 !CHECK: ^bb[[THEN]]:
 !CHECK:   omp.parallel
@@ -35,6 +45,35 @@ subroutine simple1(y)
 !CHECK:   fir.call @_FortranAStopStatement
 !CHECK:   fir.unreachable
 !CHECK: ^bb[[ENDIF]]:
+!CHECK:   return
+
+subroutine simple1_goto(y)
+  implicit none
+  logical, intent(in) :: y
+  integer :: i
+  if (y) then
+    !$omp parallel
+    i = 1
+    !$omp end parallel
+    goto 100
+  else
+    stop 1
+  end if
+  i = 2
+100 continue
+end subroutine
+
+!CHECK-LABEL: func.func @_QPsimple2
+!CHECK:   scf.execute_region
+!CHECK:   cf.cond_br %{{[0-9]+}}, ^bb[[THEN:[0-9]+]], ^bb[[ELSE:[0-9]+]]
+!CHECK: ^bb[[THEN]]:
+!CHECK:   omp.parallel
+!CHECK:   cf.br ^bb[[MERGE:[0-9]+]]
+!CHECK: ^bb[[ELSE]]:
+!CHECK:   fir.call @_FortranAStopStatement
+!CHECK:   fir.unreachable
+!CHECK: ^bb[[MERGE]]:
+!CHECK:   scf.yield
 !CHECK:   fir.call @_FortranAioOutputReal64
 !CHECK:   return
 subroutine simple2(x, yn)
diff --git a/flang/test/Lower/do_loop_execute_region_wrap.f90 b/flang/test/Lower/do_loop_execute_region_wrap.f90
new file mode 100644
index 0000000000000..74dd20cf88a12
--- /dev/null
+++ b/flang/test/Lower/do_loop_execute_region_wrap.f90
@@ -0,0 +1,111 @@
+! RUN: bbc -emit-hlfir --wrap-unstructured-constructs-in-execute-region %s -o - | FileCheck %s
+
+! An unstructured IF inside a DO is self-contained, so isUnstructured does
+! not propagate to the DO. The DO lowers as fir.do_loop and the IF's blocks
+! are wrapped in an scf.execute_region inside its body.
+subroutine wrapped_unstructured(n, a)
+  integer :: n, i
+  real :: a(n)
+  do i = 1, n
+    if (a(i) > 0.0) stop
+    a(i) = real(i)
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPwrapped_unstructured
+! CHECK:         fir.do_loop
+! CHECK:           scf.execute_region no_inline {
+! CHECK:             cf.cond_br
+! CHECK:             fir.call @_FortranAStopStatement
+! CHECK:             fir.unreachable
+! CHECK:             scf.yield
+! CHECK:           }
+! CHECK:           fir.result
+
+! Unstructured DO with a GOTO targeting a label outside the construct that
+! is reachable on a path distinct from the loop's natural exit: not wrapped.
+! The GOTO jumps past the post-loop store directly to label 99, so the
+! loop body has an outgoing edge that is neither another loop block nor the
+! construct exit. The wrap pass bails out and the CFG stays flat.
+subroutine not_wrapped_outer_label(n, a)
+  integer :: n, i
+  real :: a(n)
+  do i = 1, n
+    if (a(i) > 0.0) goto 99
+    a(i) = real(i)
+  end do
+  a(1) = -1.0
+99 continue
+end subroutine
+
+! CHECK-LABEL: func.func @_QPnot_wrapped_outer_label
+! CHECK-NOT:     scf.execute_region
+! CHECK:         cf.cond_br
+! CHECK:         return
+
+! A plain, structured DO with no early exits: lowered as fir.do_loop,
+! never reaches the wrap path (the loop is not unstructured at all).
+subroutine structured(n, a)
+  integer :: n, i
+  real :: a(n)
+  do i = 1, n
+    a(i) = real(i)
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPstructured
+! CHECK-NOT:     scf.execute_region
+! CHECK:         fir.do_loop
+! CHECK:         return
+
+! Nested DOs whose only unstructuredness comes from a self-contained IF.
+! Both DOs lower as fir.do_loop; only the IF is wrapped in scf.execute_region
+! at the innermost level.
+subroutine outer_structured_inner_wrapped(n, a)
+  integer :: n, i, j
+  real :: a(n)
+  do i = 1, n
+    do j = 1, n
+      if (a(j) > 0.0) stop
+      a(j) = real(i + j)
+    end do
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPouter_structured_inner_wrapped
+! CHECK:         fir.do_loop
+! CHECK:           fir.do_loop
+! CHECK:             scf.execute_region no_inline {
+! CHECK:               cf.cond_br
+! CHECK:               fir.call @_FortranAStopStatement
+! CHECK:               fir.unreachable
+! CHECK:               scf.yield
+! CHECK:             }
+! CHECK:             fir.result
+! CHECK:           fir.result
+
+! Structured outer DO containing an unstructured inner DO (the inner DO has
+! an EXIT that targets its own construct exit). isUnstructured does not
+! propagate from the inner DO to the outer DO, so the outer lowers as
+! fir.do_loop. The inner DO's blocks would otherwise have nowhere to live
+! inside the outer's single-block region, so the pre-wrap path creates an
+! scf.execute_region around the inner DO's CFG.
+subroutine outer_fir_do_loop_inner_unstructured_do(n, a)
+  integer :: n, i, j
+  real :: a(n)
+  do i = 1, n
+    do j = 1, n
+      if (a(j) > 0.0) exit
+    end do
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPouter_fir_do_loop_inner_unstructured_do
+! CHECK:         fir.do_loop
+! CHECK:           scf.execute_region no_inline {
+! CHECK:             cf.br
+! CHECK:             cf.cond_br
+! CHECK:             cf.cond_br
+! CHECK:             scf.yield
+! CHECK:           }
+! CHECK:           fir.result
diff --git a/flang/test/Lower/do_loop_unstructured.f90 b/flang/test/Lower/do_loop_unstructured.f90
index 4182ab0b6aefa..99b8cddd083fa 100644
--- a/flang/test/Lower/do_loop_unstructured.f90
+++ b/flang/test/Lower/do_loop_unstructured.f90
@@ -100,91 +100,41 @@ subroutine nested_unstructured()
     end do
   end do
 end subroutine
+! With the wrap-unstructured-constructs-in-execute-region pass, the innermost
+! k-loop is the only one classified unstructured (the `goto 404`/`404 continue`
+! pattern). It gets wrapped in scf.execute_region, and the outer i and j
+! loops fold back to fir.do_loop.
 ! CHECK-LABEL: nested_unstructured
 ! CHECK:   %[[TRIP_VAR_K_REF:.*]] = fir.alloca i32
-! CHECK:   %[[TRIP_VAR_J_REF:.*]] = fir.alloca i32
-! CHECK:   %[[TRIP_VAR_I_REF:.*]] = fir.alloca i32
 ! CHECK:   %[[LOOP_VAR_I_REF:.*]] = fir.alloca i32 {bindc_name = "i", uniq_name = "_QFnested_unstructuredEi"}
 ! CHECK:   %[[LOOP_VAR_I_DECL:.*]]:2 = hlfir.declare %[[LOOP_VAR_I_REF]]
 ! CHECK:   %[[LOOP_VAR_J_REF:.*]] = fir.alloca i32 {bindc_name = "j", uniq_name = "_QFnested_unstructuredEj"}
 ! CHECK:   %[[LOOP_VAR_J_DECL:.*]]:2 = hlfir.declare %[[LOOP_VAR_J_REF]]
 ! CHECK:   %[[LOOP_VAR_K_REF:.*]] = fir.alloca i32 {bindc_name = "k", uniq_name = "_QFnested_unstructuredEk"}
 ! CHECK:   %[[LOOP_VAR_K_DECL:.*]]:2 = hlfir.declare %[[LOOP_VAR_K_REF]]
-! CHECK:   %[[I_START:.*]] = arith.constant 1 : i32
-! CHECK:   %[[I_END:.*]] = arith.constant 100 : i32
-! CHECK:   %[[I_STEP:.*]] = arith.constant 1 : i32
-! CHECK:   %[[TMP1:.*]] = arith.subi %[[I_END]], %[[I_START]] : i32
-! CHECK:   %[[TMP2:.*]] = arith.addi %[[TMP1]], %[[I_STEP]] : i32
-! CHECK:   %[[TRIP_COUNT_I:.*]] = arith.divsi %[[TMP2]], %[[I_STEP]] : i32
-! CHECK:   fir.store %[[TRIP_COUNT_I]] to %[[TRIP_VAR_I_REF]] : !fir.ref<i32>
-! CHECK:   fir.store %[[I_START]] to %[[LOOP_VAR_I_DECL]]#0 : !fir.ref<i32>
-! CHECK:   cf.br ^[[HEADER_I:.*]]
-! CHECK: ^[[HEADER_I]]:
-! CHECK:   %[[TRIP_VAR_I:.*]] = fir.load %[[TRIP_VAR_I_REF]] : !fir.ref<i32>
-! CHECK:   %[[ZERO_1:.*]] = arith.constant 0 : i32
-! CHECK:   %[[COND_I:.*]] = arith.cmpi sgt, %[[TRIP_VAR_I]], %[[ZERO_1]] : i32
-! CHECK:   cf.cond_br %[[COND_I]], ^[[BODY_I:.*]], ^[[EXIT_I:.*]]
-! CHECK: ^[[BODY_I]]:
-! CHECK:   %[[J_START:.*]] = arith.constant 1 : i32
-! CHECK:   %[[J_END:.*]] = arith.constant 200 : i32
-! CHECK:   %[[J_STEP:.*]] = arith.constant 1 : i32
-! CHECK:   %[[TMP3:.*]] = arith.subi %[[J_END]], %[[J_START]] : i32
-! CHECK:   %[[TMP4:.*]] = arith.addi %[[TMP3]], %[[J_STEP]] : i32
-! CHECK:   %[[TRIP_COUNT_J:.*]] = arith.divsi %[[TMP4]], %[[J_STEP]] : i32
-! CHECK:   fir.store %[[TRIP_COUNT_J]] to %[[TRIP_VAR_J_REF]] : !fir.ref<i32>
-! CHECK:   fir.store %[[J_START]] to %[[LOOP_VAR_J_DECL]]#0 : !fir.ref<i32>
-! CHECK:   cf.br ^[[HEADER_J:.*]]
-! CHECK: ^[[HEADER_J]]:
-! CHECK:   %[[TRIP_VAR_J:.*]] = fir.load %[[TRIP_VAR_J_REF]] : !fir.ref<i32>
-! CHECK:   %[[ZERO_2:.*]] = arith.constant 0 : i32
-! CHECK:   %[[COND_J:.*]] = arith.cmpi sgt, %[[TRIP_VAR_J]], %[[ZERO_2]] : i32
-! CHECK:   cf.cond_br %[[COND_J]], ^[[BODY_J:.*]], ^[[EXIT_J:.*]]
-! CHECK: ^[[BODY_J]]:
-! CHECK:   %[[K_START:.*]] = arith.constant 1 : i32
-! CHECK:   %[[K_END:.*]] = arith.constant 300 : i32
-! CHECK:   %[[K_STEP:.*]] = arith.constant 1 : i32
-! CHECK:   %[[TMP3:.*]] = arith.subi %[[K_END]], %[[K_START]] : i32
-! CHECK:   %[[TMP4:.*]] = arith.addi %[[TMP3]], %[[K_STEP]] : i32
-! CHECK:   %[[TRIP_COUNT_K:.*]] = arith.divsi %[[TMP4]], %[[K_STEP]] : i32
-! CHECK:   fir.store %[[TRIP_COUNT_K]] to %[[TRIP_VAR_K_REF]] : !fir.ref<i32>
-! CHECK:   fir.store %[[K_START]] to %[[LOOP_VAR_K_DECL]]#0 : !fir.ref<i32>
-! CHECK:   cf.br ^[[HEADER_K:.*]]
-! CHECK: ^[[HEADER_K]]:
-! CHECK:   %[[TRIP_VAR_K:.*]] = fir.load %[[TRIP_VAR_K_REF]] : !fir.ref<i32>
-! CHECK:   %[[ZERO_2:.*]] = arith.constant 0 : i32
-! CHECK:   %[[COND_K:.*]] = arith.cmpi sgt, %[[TRIP_VAR_K]], %[[ZERO_2]] : i32
-! CHECK:   cf.cond_br %[[COND_K]], ^[[BODY_K:.*]], ^[[EXIT_K:.*]]
-! CHECK: ^[[BODY_K]]:
-! CHECK:   %[[TRIP_VAR_K:.*]] = fir.load %[[TRIP_VAR_K_REF]] : !fir.ref<i32>
-! CHECK:   %[[ONE_1:.*]] = arith.constant 1 : i32
-! CHECK:   %[[TRIP_VAR_K_NEXT:.*]] = arith.subi %[[TRIP_VAR_K]], %[[ONE_1]] : i32
-! CHECK:   fir.store %[[TRIP_VAR_K_NEXT]] to %[[TRIP_VAR_K_REF]] : !fir.ref<i32>
-! CHECK:   %[[LOOP_VAR_K:.*]] = fir.load %[[LOOP_VAR_K_DECL]]#0 : !fir.ref<i32>
-! CHECK:   %[[K_STEP_2:.*]] = arith.constant 1 : i32
-! CHECK:   %[[LOOP_VAR_K_NEXT:.*]] = arith.addi %[[LOOP_VAR_K]], %[[K_STEP_2]] overflow<nsw> : i32
-! CHECK:   fir.store %[[LOOP_VAR_K_NEXT]] to %[[LOOP_VAR_K_DECL]]#0 : !fir.ref<i32>
-! CHECK:   cf.br ^[[HEADER_K]]
-! CHECK: ^[[EXIT_K]]:
-! CHECK:   %[[TRIP_VAR_J:.*]] = fir.load %[[TRIP_VAR_J_REF]] : !fir.ref<i32>
-! CHECK:   %[[ONE_1:.*]] = arith.constant 1 : i32
-! CHECK:   %[[TRIP_VAR_J_NEXT:.*]] = arith.subi %[[TRIP_VAR_J]], %[[ONE_1]] : i32
-! CHECK:   fir.store %[[TRIP_VAR_J_NEXT]] to %[[TRIP_VAR_J_REF]] : !fir.ref<i32>
-! CHECK:   %[[LOOP_VAR_J:.*]] = fir.load %[[LOOP_VAR_J_DECL]]#0 : !fir.ref<i32>
-! CHECK:   %[[J_STEP_2:.*]] = arith.constant 1 : i32
-! CHECK:   %[[LOOP_VAR_J_NEXT:.*]] = arith.addi %[[LOOP_VAR_J]], %[[J_STEP_2]] overflow<nsw> : i32
-! CHECK:   fir.store %[[LOOP_VAR_J_NEXT]] to %[[LOOP_VAR_J_DECL]]#0 : !fir.ref<i32>
-! CHECK:   cf.br ^[[HEADER_J]]
-! CHECK: ^[[EXIT_J]]:
-! CHECK:   %[[TRIP_VAR_I:.*]] = fir.load %[[TRIP_VAR_I_REF]] : !fir.ref<i32>
-! CHECK:   %[[ONE_1:.*]] = arith.constant 1 : i32
-! CHECK:   %[[TRIP_VAR_I_NEXT:.*]] = arith.subi %[[TRIP_VAR_I]], %[[ONE_1]] : i32
-! CHECK:   fir.store %[[TRIP_VAR_I_NEXT]] to %[[TRIP_VAR_I_REF]] : !fir.ref<i32>
-! CHECK:   %[[LOOP_VAR_I:.*]] = fir.load %[[LOOP_VAR_I_DECL]]#0 : !fir.ref<i32>
-! CHECK:   %[[I_STEP_2:.*]] = arith.constant 1 : i32
-! CHECK:   %[[LOOP_VAR_I_NEXT:.*]] = arith.addi %[[LOOP_VAR_I]], %[[I_STEP_2]] overflow<nsw> : i32
-! CHECK:   fir.store %[[LOOP_VAR_I_NEXT]] to %[[LOOP_VAR_I_DECL]]#0 : !fir.ref<i32>
-! CHECK:   cf.br ^[[HEADER_I]]
-! CHECK: ^[[EXIT_I]]:
+! CHECK:   fir.do_loop %{{[^ ]+}} = %{{.*}} to %{{.*}} step %{{.*}} iter_args(%{{[^ ]+}} = %{{.*}}) -> (i32) {
+! CHECK:     fir.do_loop %{{[^ ]+}} = %{{.*}} to %{{.*}} step %{{.*}} iter_args(%{{[^ ]+}} = %{{.*}}) -> (i32) {
+! CHECK:       scf.execute_region no_inline {
+! CHECK:         cf.br ^[[HEADER_K:.*]]
+! CHECK:       ^[[HEADER_K]]:
+! CHECK:         %[[TRIP_COUNT_K:.*]] = arith.divsi %{{.*}}, %{{.*}} : i32
+! CHECK:         fir.store %[[TRIP_COUNT_K]] to %[[TRIP_VAR_K_REF]] : !fir.ref<i32>
+! CHECK:         fir.store %{{.*}} to %[[LOOP_VAR_K_DECL]]#0 : !fir.ref<i32>
+! CHECK:         cf.br ^[[HEADER_K_BODY:.*]]
+! CHECK:       ^[[HEADER_K_BODY]]:
+! CHECK:         %[[TRIP_VAR_K:.*]] = fir.load %[[TRIP_VAR_K_REF]] : !fir.ref<i32>
+! CHECK:         %[[COND_K:.*]] = arith.cmpi sgt, %[[TRIP_VAR_K]], %{{.*}} : i32
+! CHECK:         cf.cond_br %[[COND_K]], ^[[BODY_K:.*]], ^[[EXIT_K:.*]]
+! CHECK:       ^[[BODY_K]]:
+! CHECK:         cf.br ^{{.*}}
+! CHECK:         cf.br ^[[HEADER_K_BODY]]
+! CHECK:       ^[[EXIT_K]]:
+! CHECK:         scf.yield
+! CHECK:       }
+! CHECK:       fir.result %{{.*}} : i32
+! CHECK:     }
+! CHECK:     fir.result %{{.*}} : i32
+! CHECK:   }
 ! CHECK:   return
 
 ! Test the existence of a structured loop inside an unstructured loop.
@@ -247,13 +197,14 @@ subroutine unstructured_do_concurrent
 end
 ! CHECK-LABEL: func.func @_QPunstructured_do_concurrent
 ! CHECK:         %[[ITER_VAR:.*]] = fir.alloca i32
-
-! CHECK:       ^[[HEADER]]:
-! CHECK:         %{{.*}} = fir.load %[[ITER_VAR]] : !fir.ref<i32>
-! CHECK:         cf.cond_br %{{.*}}, ^[[BODY:.*]], ^[[EXIT:.*]]
-
-! CHECK:       ^[[BODY]]:
-! CHECK-NEXT:    %{{.*}} = fir.alloca !fir.logical<4> {bindc_name = "success", {{.*}}}
-
-! CHECK:       ^[[EXIT]]:
-! CHECK-NEXT:    return
+! CHECK:         scf.execute_region no_inline {
+! CHECK:           cf.br ^[[HEADER:.*]]
+! CHECK:         ^[[HEADER]]:
+! CHECK:           %{{.*}} = fir.load %[[ITER_VAR]] : !fir.ref<i32>
+! CHECK:           cf.cond_br %{{.*}}, ^[[BODY:.*]], ^[[EXIT:.*]]
+! CHECK:         ^[[BODY]]:
+! CHECK-NEXT:      %{{.*}} = fir.alloca !fir.logical<4> {bindc_name = "success", {{.*}}}
+! CHECK:         ^[[EXIT]]:
+! CHECK-NEXT:      scf.yield
+! CHECK:         }
+! CHECK:         return
diff --git a/flang/test/Lower/fail_image.f90 b/flang/test/Lower/fail_image.f90
index ae5152907c2ba..549f00e8d62cb 100644
--- a/flang/test/Lower/fail_image.f90
+++ b/flang/test/Lower/fail_image.f90
@@ -5,19 +5,20 @@
 subroutine fail_image_test(fail)
   logical :: fail
 ! CHECK:  %[[VAL_1:.*]]:2 = hlfir.declare %[[ARG0]]
-! CHECK:  %[[VAL_2:.*]] = fir.load %[[VAL_1]]#0 : !fir.ref<!fir.logical<4>>
-! CHECK:  %[[VAL_3:.*]] = fir.convert %[[VAL_2]] : (!fir.logical<4>) -> i1
-! CHECK:  cf.cond_br %[[VAL_3]], ^[[BB1:.*]], ^[[BB2:.*]]
-! CHECK: ^[[BB1]]:
+! CHECK:  scf.execute_region no_inline {
+! CHECK:    %[[VAL_2:.*]] = fir.load %[[VAL_1]]#0 : !fir.ref<!fir.logical<4>>
+! CHECK:    %[[VAL_3:.*]] = fir.convert %[[VAL_2]] : (!fir.logical<4>) -> i1
+! CHECK:    cf.cond_br %[[VAL_3]], ^[[BB1:.*]], ^[[BB2:.*]]
+! CHECK:  ^[[BB1]]:
   if (fail) then
-! CHECK: fir.call @_FortranAFailImageStatement() {{.*}}: () -> ()
+! CHECK:    fir.call @_FortranAFailImageStatement() {{.*}}: () -> ()
 ! CHECK-NEXT:  fir.unreachable
    FAIL IMAGE
   end if
-! CHECK: ^[[BB2]]:
-! CHECK-NEXT:  cf.br ^[[BB3:.*]]
-! CHECK-NEXT: ^[[BB3]]:
-! CHECK-NEXT:  return
+! CHECK:  ^[[BB2]]:
+! CHECK-NEXT:  scf.yield
+! CHECK:  }
+! CHECK:  return
   return
 end subroutine
 ! CHECK-LABEL: func.func private @_FortranAFailImageStatement() attributes {fir.runtime}
diff --git a/flang/test/Lower/ifconvert.f90 b/flang/test/Lower/ifconvert.f90
index cbd1e79bd0114..256bb37c4e19f 100644
--- a/flang/test/Lower/ifconvert.f90
+++ b/flang/test/Lower/ifconvert.f90
@@ -41,7 +41,7 @@
   ! CHECK:   15 PrintStmt: print*
   print*
 
-  ! CHECK:<<DoConstruct!>> -> 30
+  ! CHECK:<<DoConstruct>> -> 30
   ! CHECK:  16 NonLabelDoStmt -> 29: outer: do i = 1, 3
   ! CHECK:  <<DoConstruct!>> -> 29
   ! CHECK:    17 ^NonLabelDoStmt -> 28: inner: do j = 1, 5
@@ -62,7 +62,7 @@
   ! CHECK:    28 ^EndDoStmt -> 17: 3 end do inner
   ! CHECK:  <<End DoConstruct!>>
   ! CHECK:  29 ^EndDoStmt -> 16: end do outer
-  ! CHECK:<<End DoConstruct!>>
+  ! CHECK:<<End DoConstruct>>
   outer: do i = 1, 3
     inner: do j = 1, 5
              if (j <= 1 .or. j >= 5) cycle inner
@@ -72,7 +72,7 @@
   3        end do inner
          end do outer
 
-  ! CHECK:   30 ^PrintStmt: print*
+  ! CHECK:   30 PrintStmt: print*
   print*
 
   ! CHECK:<<DoConstruct>> -> 40
diff --git a/flang/test/Lower/mixed_loops.f90 b/flang/test/Lower/mixed_loops.f90
index 11534ae9490d0..2a5aa40ff22cd 100644
--- a/flang/test/Lower/mixed_loops.f90
+++ b/flang/test/Lower/mixed_loops.f90
@@ -1,63 +1,47 @@
 ! RUN: %flang_fc1 -emit-hlfir -o - %s | FileCheck %s
 
 ! Test while loop inside do loop.
+! With the wrap-unstructured-constructs-in-execute-region pass, the inner
+! `do while` is the only unstructured construct: it gets wrapped, and the
+! outer counted `do` folds back to fir.do_loop.
 ! CHECK-LABEL: while_inside_do_loop
 subroutine while_inside_do_loop
-  ! CHECK-DAG: %[[T_REF:.*]] = fir.alloca i32
   ! CHECK-DAG: %[[I_ADDR:.*]] = fir.alloca i32 {bindc_name = "i", uniq_name = "_QFwhile_inside_do_loopEi"}
   ! CHECK-DAG: %[[I:.*]]:2 = hlfir.declare %[[I_ADDR]]
   ! CHECK-DAG: %[[J_ADDR:.*]] = fir.alloca i32 {bindc_name = "j", uniq_name = "_QFwhile_inside_do_loopEj"}
   ! CHECK-DAG: %[[J:.*]]:2 = hlfir.declare %[[J_ADDR]]
   integer :: i, j
 
-  ! CHECK-DAG: %[[C8:.*]] = arith.constant 8 : i32
-  ! CHECK-DAG: %[[C13:.*]] = arith.constant 13 : i32
-  ! CHECK-DAG: %[[C1:.*]] = arith.constant 1 : i32
-  ! CHECK: %[[DIFF:.*]] = arith.subi %[[C13]], %[[C8]] : i32
-  ! CHECK: %[[RANGE:.*]] = arith.addi %[[DIFF]], %[[C1]] : i32
-  ! CHECK: %[[HIGH:.*]] = arith.divsi %[[RANGE]], %[[C1]] : i32
-  ! CHECK: fir.store %[[HIGH]] to %[[T_REF]] : !fir.ref<i32>
-  ! CHECK: fir.store %[[C8]] to %[[I]]#0 : !fir.ref<i32>
-
-  ! CHECK: cf.br ^[[HDR1:.*]]
-  ! CHECK: ^[[HDR1]]:  // 2 preds: ^{{.*}}, ^[[EXIT2:.*]]
-  ! CHECK: %[[T:.*]] = fir.load %[[T_REF]] : !fir.ref<i32>
-  ! CHECK: %[[C0:.*]] = arith.constant 0 : i32
-  ! CHECK: %[[COND:.*]] = arith.cmpi sgt, %[[T]], %[[C0]] : i32
-  ! CHECK: cf.cond_br %[[COND]], ^[[BODY1:.*]], ^[[EXIT1:.*]]
+  ! CHECK: fir.do_loop %{{[^ ]+}} = %{{.*}} to %{{.*}} step %{{.*}} iter_args(%[[I_IV:.*]] = %{{.*}}) -> (i32) {
+  ! CHECK:   fir.store %[[I_IV]] to %[[I]]#0 : !fir.ref<i32>
   do i=8,13
-    ! CHECK: ^[[BODY1]]:  // pred: ^[[HDR1]]
-    ! CHECK: %[[C3:.*]] = arith.constant 3 : i32
-    ! CHECK: hlfir.assign %[[C3]] to %[[J]]#0 : i32, !fir.ref<i32>
+    ! CHECK:   %[[C3:.*]] = arith.constant 3 : i32
+    ! CHECK:   hlfir.assign %[[C3]] to %[[J]]#0 : i32, !fir.ref<i32>
     j=3
 
-    ! CHECK: cf.br ^[[HDR2:.*]]
-    ! CHECK: ^[[HDR2]]:  // 2 preds: ^[[BODY1]], ^[[BODY2:.*]]
-    ! CHECK: %[[JVAL:.*]] = fir.load %[[J]]#0 : !fir.ref<i32>
-    ! CHECK: %[[IVAL:.*]] = fir.load %[[I]]#0 : !fir.ref<i32>
-    ! CHECK: %[[COND2:.*]] = arith.cmpi slt, %[[JVAL]], %[[IVAL]] : i32
-    ! CHECK: cf.cond_br %[[COND2]], ^[[BODY2:.*]], ^[[EXIT2]]
+    ! CHECK:   scf.execute_region no_inline {
+    ! CHECK:     cf.br ^[[HDR2:.*]]
+    ! CHECK:   ^[[HDR2]]:  // 2 preds: ^{{.*}}, ^[[BODY2:.*]]
+    ! CHECK:     %[[JVAL:.*]] = fir.load %[[J]]#0 : !fir.ref<i32>
+    ! CHECK:     %[[IVAL:.*]] = fir.load %[[I]]#0 : !fir.ref<i32>
+    ! CHECK:     %[[COND2:.*]] = arith.cmpi slt, %[[JVAL]], %[[IVAL]] : i32
+    ! CHECK:     cf.cond_br %[[COND2]], ^[[BODY2]], ^[[EXIT2:.*]]
     do while (j .lt. i)
-      ! CHECK: ^[[BODY2]]:  // pred: ^[[HDR2]]
-      ! CHECK: %[[C2:.*]] = arith.constant 2 : i32
-      ! CHECK: %[[JVAL2:.*]] = fir.load %[[J]]#0 : !fir.ref<i32>
-      ! CHECK: %[[INC2:.*]] = arith.muli %[[C2]], %[[JVAL2]] : i32
-      ! CHECK: hlfir.assign %[[INC2]] to %[[J]]#0 : i32, !fir.ref<i32>
+      ! CHECK:   ^[[BODY2]]:  // pred: ^[[HDR2]]
+      ! CHECK:     %[[C2:.*]] = arith.constant 2 : i32
+      ! CHECK:     %[[JVAL2:.*]] = fir.load %[[J]]#0 : !fir.ref<i32>
+      ! CHECK:     %[[INC2:.*]] = arith.muli %[[C2]], %[[JVAL2]] : i32
+      ! CHECK:     hlfir.assign %[[INC2]] to %[[J]]#0 : i32, !fir.ref<i32>
       j=j*2
-    ! CHECK: cf.br ^[[HDR2]]
+    ! CHECK:     cf.br ^[[HDR2]]
     end do
-
-  ! CHECK: ^[[EXIT2]]: // pred: ^[[HDR2]]
-  ! CHECK: %[[T2:.*]] = fir.load %[[T_REF]] : !fir.ref<i32>
-  ! CHECK: %[[TDEC:.*]] = arith.subi %[[T2]], {{.*}} : i32
-  ! CHECK: fir.store %[[TDEC]] to %[[T_REF]]
-  ! CHECK: %[[I3:.*]] = fir.load %[[I]]#0 : !fir.ref<i32>
-  ! CHECK: %[[IINC:.*]] = arith.addi %[[I3]], {{.*}} overflow<nsw> : i32
-  ! CHECK: fir.store %[[IINC]] to %[[I]]#0 : !fir.ref<i32>
-  ! CHECK: cf.br ^[[HDR1]]
+    ! CHECK:   ^[[EXIT2]]:
+    ! CHECK:     scf.yield
+    ! CHECK:   }
+    ! CHECK:   fir.result %{{.*}} : i32
   end do
+  ! CHECK: }
 
-  ! CHECK: ^[[EXIT1]]:  // pred: ^[[HDR1]]
   ! CHECK: %[[IPRINT:.*]] = fir.load %[[I]]#0 : !fir.ref<i32>
   ! CHECK: fir.call @_FortranAioOutputInteger32(%{{.*}}, %[[IPRINT]])
   ! CHECK: %[[JPRINT:.*]] = fir.load %[[J]]#0 : !fir.ref<i32>
@@ -78,37 +62,39 @@ subroutine do_inside_while_loop
     ! CHECK: hlfir.assign %[[C3]] to %[[J]]#0 : i32, !fir.ref<i32>
     j=3
 
-    ! CHECK: cf.br ^[[HDR1:.*]]
+    ! The outer `do while` is wrapped in scf.execute_region; the inner counted
+    ! `do` lowers as fir.do_loop inside the wrap.
+    ! CHECK: scf.execute_region no_inline {
+    ! CHECK:   cf.br ^[[HDR1:.*]]
     ! CHECK: ^[[HDR1]]:  // 2 preds: ^{{.*}}, ^[[BODY1:.*]]
-    ! CHECK: %[[JVAL:.*]] = fir.load %[[J]]#0 : !fir.ref<i32>
-    ! CHECK: %[[UL:.*]] = arith.constant 21 : i32
-    ! CHECK: %[[COND:.*]] = arith.cmpi slt, %[[JVAL]], %[[UL]] : i32
-    ! CHECK: cf.cond_br %[[COND]], ^[[BODY1]], ^[[EXIT1:.*]]
+    ! CHECK:   %[[JVAL:.*]] = fir.load %[[J]]#0 : !fir.ref<i32>
+    ! CHECK:   %[[UL:.*]] = arith.constant 21 : i32
+    ! CHECK:   %[[COND:.*]] = arith.cmpi slt, %[[JVAL]], %[[UL]] : i32
+    ! CHECK:   cf.cond_br %[[COND]], ^[[BODY1]], ^[[EXIT1:.*]]
     do while (j .lt. 21)
       ! CHECK: ^[[BODY1]]:  // pred: ^[[HDR1]]
 
-      ! CHECK-DAG: %[[C8:.*]] = arith.constant 8 : i32
-      ! CHECK-DAG: %[[C13:.*]] = arith.constant 13 : i32
-      ! CHECK-DAG: %[[C1:.*]] = arith.constant 1 : index
-      ! CHECK: %{{.*}} = fir.do_loop %{{.*}} = {{.*}} to {{.*}} step {{.*}} iter_args(%[[I_IV:.*]] = {{.*}}) -> (i32) {
-        ! CHECK: fir.store %[[I_IV]] to %[[I]]#0 : !fir.ref<i32>
-        ! CHECK: %[[C2:.*]] = arith.constant 2 : i32
-        ! CHECK: %[[J2VAL:.*]] = fir.load %[[J]]#0 : !fir.ref<i32>
-        ! CHECK: %[[JINC:.*]] = arith.muli %[[C2]], %[[J2VAL]] : i32
-        ! CHECK: hlfir.assign %[[JINC]] to %[[J]]#0 : i32, !fir.ref<i32>
-        ! CHECK: %[[I_IVLOAD:.*]] = fir.load %[[I]]#0 : !fir.ref<i32>
-        ! CHECK: %[[I_IVINC:.*]] = arith.addi %[[I_IVLOAD]], {{.*}} overflow<nsw> : i32
-        ! CHECK: fir.result %[[I_IVINC]] : i32
+      ! CHECK:   %{{.*}} = fir.do_loop %{{.*}} = {{.*}} to {{.*}} step {{.*}} iter_args(%[[I_IV:.*]] = {{.*}}) -> (i32) {
+      ! CHECK:     fir.store %[[I_IV]] to %[[I]]#0 : !fir.ref<i32>
+      ! CHECK:     %[[C2:.*]] = arith.constant 2 : i32
+      ! CHECK:     %[[J2VAL:.*]] = fir.load %[[J]]#0 : !fir.ref<i32>
+      ! CHECK:     %[[JINC:.*]] = arith.muli %[[C2]], %[[J2VAL]] : i32
+      ! CHECK:     hlfir.assign %[[JINC]] to %[[J]]#0 : i32, !fir.ref<i32>
+      ! CHECK:     %[[I_IVLOAD:.*]] = fir.load %[[I]]#0 : !fir.ref<i32>
+      ! CHECK:     %[[I_IVINC:.*]] = arith.addi %[[I_IVLOAD]], {{.*}} overflow<nsw> : i32
+      ! CHECK:     fir.result %[[I_IVINC]] : i32
       do i=8,13
         j=j*2
 
-      ! CHECK: fir.store %{{.*}} to %[[I]]#0 : !fir.ref<i32>
+      ! CHECK:   fir.store %{{.*}} to %[[I]]#0 : !fir.ref<i32>
       end do
 
-    ! CHECK: cf.br ^[[HDR1]]
+    ! CHECK:   cf.br ^[[HDR1]]
     end do
+    ! CHECK: ^[[EXIT1]]:
+    ! CHECK:   scf.yield
+    ! CHECK: }
 
-  ! CHECK: ^[[EXIT1]]:  // pred: ^[[HDR1]]
   ! CHECK: %[[IPRINT:.*]] = fir.load %[[I]]#0 : !fir.ref<i32>
   ! CHECK: fir.call @_FortranAioOutputInteger32(%{{.*}}, %[[IPRINT]])
   ! CHECK: %[[JPRINT:.*]] = fir.load %[[J]]#0 : !fir.ref<i32>
diff --git a/flang/test/Lower/nsw.f90 b/flang/test/Lower/nsw.f90
index e113c26a9dc80..4bc932ac38508 100644
--- a/flang/test/Lower/nsw.f90
+++ b/flang/test/Lower/nsw.f90
@@ -106,15 +106,16 @@ subroutine loop_params2(a,lb,ub,st)
 ! CHECK:           %[[VAL_14:.*]] = fir.declare %{{.*}}i"} : (!fir.ref<i32>) -> !fir.ref<i32>
 ! CHECK:           %[[VAL_16:.*]] = fir.declare %{{.*}}ii"} : (!fir.ref<f32>) -> !fir.ref<f32>
 ! CHECK:           %[[VAL_17:.*]] = fir.declare %{{.*}}st"} : (!fir.ref<i32>, !fir.dscope) -> !fir.ref<i32>
-! CHECK:           %[[VAL_18:.*]] = fir.load %[[VAL_11]] : !fir.ref<i32>
-! CHECK:           %[[VAL_20:.*]] = fir.load %[[VAL_12]] : !fir.ref<i32>
-! CHECK:           %[[VAL_29:.*]] = arith.addi %[[VAL_18]], %[[VAL_5]] overflow<nsw> : i32
-! CHECK:           %[[VAL_30:.*]] = fir.convert %[[VAL_29]] : (i32) -> f32
-! CHECK:           %[[VAL_31:.*]] = arith.subi %[[VAL_20]], %[[VAL_5]] overflow<nsw> : i32
-! CHECK:           %[[VAL_32:.*]] = fir.convert %[[VAL_31]] : (i32) -> f32
-! CHECK:           %[[VAL_33:.*]] = fir.load %[[VAL_17]] : !fir.ref<i32>
-! CHECK:           %[[VAL_34:.*]] = arith.muli %[[VAL_33]], %[[VAL_4]] overflow<nsw> : i32
-! CHECK:           %[[VAL_35:.*]] = fir.convert %[[VAL_34]] : (i32) -> f32
+! CHECK:           scf.execute_region no_inline {
+! CHECK:             %[[VAL_18:.*]] = fir.load %[[VAL_11]] : !fir.ref<i32>
+! CHECK:             %[[VAL_29:.*]] = arith.addi %[[VAL_18]], %[[VAL_5]] overflow<nsw> : i32
+! CHECK:             %[[VAL_30:.*]] = fir.convert %[[VAL_29]] : (i32) -> f32
+! CHECK:             %[[VAL_20:.*]] = fir.load %[[VAL_12]] : !fir.ref<i32>
+! CHECK:             %[[VAL_31:.*]] = arith.subi %[[VAL_20]], %[[VAL_5]] overflow<nsw> : i32
+! CHECK:             %[[VAL_32:.*]] = fir.convert %[[VAL_31]] : (i32) -> f32
+! CHECK:             %[[VAL_33:.*]] = fir.load %[[VAL_17]] : !fir.ref<i32>
+! CHECK:             %[[VAL_34:.*]] = arith.muli %[[VAL_33]], %[[VAL_4]] overflow<nsw> : i32
+! CHECK:             %[[VAL_35:.*]] = fir.convert %[[VAL_34]] : (i32) -> f32
 ! CHECK:           fir.store %[[VAL_35]] to %[[VAL_9]] : !fir.ref<f32>
 ! CHECK:           %[[VAL_36:.*]] = arith.subf %[[VAL_32]], %[[VAL_30]] fastmath<contract> : f32
 ! CHECK:           %[[VAL_37:.*]] = arith.addf %[[VAL_36]], %[[VAL_35]] fastmath<contract> : f32
diff --git a/flang/test/Lower/pre-fir-tree02.f90 b/flang/test/Lower/pre-fir-tree02.f90
index bef9abfde87bd..638dd94416229 100644
--- a/flang/test/Lower/pre-fir-tree02.f90
+++ b/flang/test/Lower/pre-fir-tree02.f90
@@ -306,7 +306,7 @@ subroutine sub2()
 5 j = j + 1
 6 i = i + j/2
 
-  ! CHECK: <<DoConstruct!>>
+  ! CHECK: <<DoConstruct>>
   do1: do k=1,10
     ! CHECK: <<DoConstruct!>>
     do2: do l=5,20
@@ -317,7 +317,7 @@ subroutine sub2()
     end do do2
     ! CHECK: <<End DoConstruct!>>
   end do do1
-  ! CHECK: <<End DoConstruct!>>
+  ! CHECK: <<End DoConstruct>>
 
   ! CHECK: PauseStmt
   pause 7
diff --git a/flang/test/Lower/while_loop.f90 b/flang/test/Lower/while_loop.f90
index 8114394a39919..8f2291f74a8ed 100644
--- a/flang/test/Lower/while_loop.f90
+++ b/flang/test/Lower/while_loop.f90
@@ -11,6 +11,7 @@ subroutine simple_loop
   ! CHECK: hlfir.assign %[[C5]] to %[[I]]#0
   i = 5
 
+  ! CHECK: scf.execute_region no_inline {
   ! CHECK: cf.br ^[[BB1:.*]]
   ! CHECK: ^[[BB1]]:  // 2 preds: ^{{.*}}, ^[[BB2:.*]]
   ! CHECK: %[[IVAL:.*]] = fir.load %[[I]]#0 : !fir.ref<i32>
@@ -23,11 +24,13 @@ subroutine simple_loop
   ! CHECK: %[[INC:.*]] = arith.subi %[[IVAL2]], %[[C2]] : i32
   ! CHECK: hlfir.assign %[[INC]] to %[[I]]#0 : i32, !fir.ref<i32>
   ! CHECK: cf.br ^[[BB1]]
+  ! CHECK: ^[[BB3]]:  // pred: ^[[BB1]]
+  ! CHECK: scf.yield
+  ! CHECK: }
   do while (i .gt. 1)
     i = i - 2
   end do
 
-  ! CHECK: ^[[BB3]]:  // pred: ^[[BB1]]
   ! CHECK: %[[IVAL3:.*]] = fir.load %[[I]]#0 : !fir.ref<i32>
   ! CHECK: fir.call @_FortranAioOutputInteger32(%{{.*}}, %[[IVAL3]])
   print *, i
@@ -46,8 +49,9 @@ subroutine while_inside_while_loop
   ! CHECK: hlfir.assign %[[C13]] to %[[I]]#0
   i = 13
 
+  ! CHECK: scf.execute_region no_inline {
   ! CHECK: cf.br ^[[HDR1:.*]]
-  ! CHECK: ^[[HDR1]]:  // 2 preds: ^{{.*}}, ^[[EXIT2:.*]]
+  ! CHECK: ^[[HDR1]]:  // 2 preds: ^{{.*}}, ^{{.*}}
   ! CHECK: %[[IVAL:.*]] = fir.load %[[I]]#0 : !fir.ref<i32>
   ! CHECK: %[[C8:.*]] = arith.constant 8 : i32
   ! CHECK: %[[COND:.*]] = arith.cmpi sgt, %[[IVAL]], %[[C8]] : i32
@@ -64,8 +68,9 @@ subroutine while_inside_while_loop
     ! CHECK: hlfir.assign %[[C3]] to %[[J]]#0
     j = 3
 
+    ! CHECK: scf.execute_region no_inline {
     ! CHECK: cf.br ^[[HDR2:.*]]
-    ! CHECK: ^[[HDR2]]:  // 2 preds: ^[[BODY1]], ^[[BODY2:.*]]
+    ! CHECK: ^[[HDR2]]:  // 2 preds: ^{{.*}}, ^[[BODY2:.*]]
     ! CHECK: %[[JVAL:.*]] = fir.load %[[J]]#0 : !fir.ref<i32>
     ! CHECK: %[[IVAL3:.*]] = fir.load %[[I]]#0 : !fir.ref<i32>
     ! CHECK: %[[COND2:.*]] = arith.cmpi slt, %[[JVAL]], %[[IVAL3]] : i32
@@ -81,10 +86,14 @@ subroutine while_inside_while_loop
     end do
 
     ! CHECK: ^[[EXIT2]]: // pred: ^[[HDR2]]
+    ! CHECK: scf.yield
+    ! CHECK: }
     ! CHECK: cf.br ^[[HDR1]]
   end do
 
   ! CHECK: ^[[EXIT1]]:  // pred: ^[[HDR1]]
+  ! CHECK: scf.yield
+  ! CHECK: }
   ! CHECK: %[[IPRINT:.*]] = fir.load %[[I]]#0 : !fir.ref<i32>
   ! CHECK: fir.call @_FortranAioOutputInteger32(%{{.*}}, %[[IPRINT]])
   ! CHECK: %[[JPRINT:.*]] = fir.load %[[J]]#0 : !fir.ref<i32>

>From 1fb2b8444ee48f123c35cdef77f37c459da21e6b Mon Sep 17 00:00:00 2001
From: ergawy <kareem.ergawy at gmail.com>
Date: Tue, 14 Jul 2026 07:45:04 -0700
Subject: [PATCH 2/5] [flang][PFT] check every branch target in wrappability
 analyses

Co-Authored-By: Claude Sonnet 4.6 <noreply at anthropic.com>
---
 flang/lib/Lower/PFTBuilder.cpp                | 31 +++++++++++----
 .../Lower/do_loop_execute_region_wrap.f90     | 38 +++++++++++++++++++
 2 files changed, 62 insertions(+), 7 deletions(-)

diff --git a/flang/lib/Lower/PFTBuilder.cpp b/flang/lib/Lower/PFTBuilder.cpp
index 4e3bd4ba82715..2fab67abf5ae8 100644
--- a/flang/lib/Lower/PFTBuilder.cpp
+++ b/flang/lib/Lower/PFTBuilder.cpp
@@ -2398,14 +2398,24 @@ static bool isInsideConstruct(const Fortran::lower::pft::Evaluation *target,
 
 bool Fortran::lower::pft::branchesAreInternal(
     const Fortran::lower::pft::Evaluation &construct) {
+  auto targetIsInternal = [&](const Fortran::lower::pft::Evaluation *target) {
+    return isInsideConstruct(target, construct, /*strict=*/false);
+  };
   std::function<bool(const Fortran::lower::pft::EvaluationList &)> walk =
       [&](const Fortran::lower::pft::EvaluationList &list) -> bool {
     for (const Fortran::lower::pft::Evaluation &nested : list) {
-      if (nested.controlSuccessor &&
-          !isInsideConstruct(nested.controlSuccessor, construct,
-                             /*strict=*/false))
+      if (nested.controlSuccessor && !targetIsInternal(nested.controlSuccessor))
         return false;
 
+      // Multiway branches (computed GO TO, arithmetic IF) record only the
+      // first target in controlSuccessor; the remaining targets are held in
+      // extraControlSuccessors and must be checked here as well or an
+      // escaping branch will silently slip through.
+      for (const Fortran::lower::pft::Evaluation *extra :
+           nested.extraControlSuccessors)
+        if (!targetIsInternal(extra))
+          return false;
+
       if (nested.evaluationList && !walk(*nested.evaluationList))
         return false;
     }
@@ -2428,13 +2438,20 @@ hasIncomingBranch(const Fortran::lower::pft::Evaluation &construct) {
   if (!funit)
     return false;
 
+  auto targetIsStrictlyInside = [&](const Fortran::lower::pft::Evaluation *t) {
+    return isInsideConstruct(t, construct, /*strict=*/true);
+  };
   std::function<bool(const Fortran::lower::pft::EvaluationList &)> walk =
       [&](const Fortran::lower::pft::EvaluationList &list) -> bool {
     for (const Fortran::lower::pft::Evaluation &e : list) {
-      if (!isInsideConstruct(&e, construct, /*strict=*/true) &&
-          e.controlSuccessor &&
-          isInsideConstruct(e.controlSuccessor, construct, /*strict=*/true))
-        return true;
+      if (!isInsideConstruct(&e, construct, /*strict=*/true)) {
+        if (e.controlSuccessor && targetIsStrictlyInside(e.controlSuccessor))
+          return true;
+        for (const Fortran::lower::pft::Evaluation *extra :
+             e.extraControlSuccessors)
+          if (targetIsStrictlyInside(extra))
+            return true;
+      }
 
       if (e.evaluationList && walk(*e.evaluationList))
         return true;
diff --git a/flang/test/Lower/do_loop_execute_region_wrap.f90 b/flang/test/Lower/do_loop_execute_region_wrap.f90
index 74dd20cf88a12..e9663e9e56fc0 100644
--- a/flang/test/Lower/do_loop_execute_region_wrap.f90
+++ b/flang/test/Lower/do_loop_execute_region_wrap.f90
@@ -43,6 +43,44 @@ subroutine not_wrapped_outer_label(n, a)
 ! CHECK:         cf.cond_br
 ! CHECK:         return
 
+! Multiway branch (computed GO TO) inside a DO with a target that escapes
+! the loop: not wrapped. Only the first target of a computed GO TO used to
+! be considered by the wrappability check, so an escaping non-first label
+! (99 below) was invisible and the DO was wrapped; the resulting
+! scf.execute_region contained a fir.select whose successor lived outside
+! the region, tripping MLIR's op verifier.
+subroutine not_wrapped_computed_goto_exit(sel, a)
+  integer :: sel, a, i
+  do i = 1, 10
+    go to (10, 99), sel
+10 end do
+  a = -1
+99 continue
+end subroutine
+
+! CHECK-LABEL: func.func @_QPnot_wrapped_computed_goto_exit
+! CHECK-NOT:     scf.execute_region
+! CHECK:         fir.select
+! CHECK:         return
+
+! Same shape via an arithmetic IF. Arithmetic IF has three label targets;
+! only the first was recorded on the source Evaluation, so a non-first
+! escape target (99) was again invisible to the wrappability check.
+subroutine not_wrapped_arithmetic_if_exit(x, a)
+  integer :: a, i
+  real :: x
+  do i = 1, 10
+    if (x) 10, 10, 99
+10 end do
+  a = -1
+99 continue
+end subroutine
+
+! CHECK-LABEL: func.func @_QPnot_wrapped_arithmetic_if_exit
+! CHECK-NOT:     scf.execute_region
+! CHECK:         cf.cond_br
+! CHECK:         return
+
 ! A plain, structured DO with no early exits: lowered as fir.do_loop,
 ! never reaches the wrap path (the loop is not unstructured at all).
 subroutine structured(n, a)

>From ccc371992b4053ce53eb102e38bdc5d9be2b1ccb Mon Sep 17 00:00:00 2001
From: ergawy <kareem.ergawy at gmail.com>
Date: Thu, 16 Jul 2026 03:41:34 -0700
Subject: [PATCH 3/5] [flang][PFT-to-MLIR] pull the scf.execute_region wrap
 inside the DO lowering

Co-authored-by: Claude Opus 4.7 <noreply at anthropic.com>
---
 flang/lib/Lower/Bridge.cpp | 59 +++++++++++++++++++++++++++++---------
 1 file changed, 46 insertions(+), 13 deletions(-)

diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index bbf46b12f8b62..0ab18131b745b 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -2592,17 +2592,7 @@ class FirConverter : public Fortran::lower::AbstractConverter {
     bool unstructuredContext = eval.lowerAsUnstructured();
 
     mlir::Block *savedExitBlock = nullptr;
-    mlir::scf::ExecuteRegionOp wrapOp =
-        unstructuredContext ? wrapUnstructuredConstruct(eval, savedExitBlock)
-                            : nullptr;
-
-    if (wrapOp) {
-      LLVM_DEBUG({
-        llvm::dbgs() << "[wrap-unstructured] wrapped DO at ";
-        wrapOp.getLoc().print(llvm::dbgs());
-        llvm::dbgs() << "\n";
-      });
-    }
+    mlir::scf::ExecuteRegionOp wrapOp = nullptr;
 
     // If this do-loop was absorbed by a collapse clause on a parent acc.loop,
     // skip generating any loop — just lower the body.  The IV value is
@@ -2660,15 +2650,52 @@ class FirConverter : public Fortran::lower::AbstractConverter {
       // blocks, created in outermost to innermost order.
       return beginBlock = beginBlock->splitBlock(beginBlock->end());
     };
-    mlir::Block *headerBlock =
-        unstructuredContext ? createNextBeginBlock() : nullptr;
+    // headerBlock is computed per-branch below, after wrapping, so that
+    // beginBlock can be corrected to the scf entry when wrapping occurs.
+    mlir::Block *headerBlock = nullptr;
     mlir::Block *bodyBlock = doStmtEval.lexicalSuccessor->block;
     mlir::Block *exitBlock = doStmtEval.parentConstruct->constructExit->block;
+
+    // Helper: called after maybeStartBlock(preheaderBlock) to create the
+    // scf.execute_region (if the construct is wrappable) and then refresh
+    // beginBlock/bodyBlock/exitBlock so that all loop structure lands inside
+    // the newly created region.
+    auto maybeWrapAndRecalc = [&]() {
+      wrapOp = unstructuredContext
+                   ? wrapUnstructuredConstruct(eval, savedExitBlock)
+                   : nullptr;
+      if (wrapOp) {
+        LLVM_DEBUG({
+          llvm::dbgs() << "[wrap-unstructured] wrapped DO at ";
+          wrapOp.getLoc().print(llvm::dbgs());
+          llvm::dbgs() << "\n";
+        });
+        // After wrapping, the builder is positioned at the scf entry block.
+        // createEmptyBlocks (called inside wrapUnstructuredConstruct) may have
+        // allocated a dedicated preheader block inside the scf region and
+        // stored it in doStmtEval.block.  If so, branch to it from scf_entry
+        // and use it as beginBlock; otherwise use scf_entry directly.
+        mlir::Block *newPreheader = doStmtEval.block;
+        if (newPreheader && newPreheader != preheaderBlock) {
+          beginBlock = newPreheader;
+          maybeStartBlock(beginBlock);
+        } else {
+          beginBlock = builder->getBlock();
+        }
+        bodyBlock = doStmtEval.lexicalSuccessor->block;
+        exitBlock = doStmtEval.parentConstruct->constructExit->block;
+      }
+    };
+
     IncrementLoopNestInfo incrementLoopNestInfo;
     const Fortran::parser::ScalarLogicalExpr *whileCondition = nullptr;
     bool infiniteLoop = !loopControl.has_value();
     if (infiniteLoop) {
       assert(unstructuredContext && "infinite loop must be unstructured");
+      // Infinite loops are never wrappable; maybeWrapAndRecalc is a no-op here,
+      // but call it for uniformity so wrapOp stays null.
+      maybeWrapAndRecalc();
+      headerBlock = createNextBeginBlock();
       startBlock(headerBlock);
     } else if ((whileCondition =
                     std::get_if<Fortran::parser::ScalarLogicalExpr>(
@@ -2685,6 +2712,8 @@ class FirConverter : public Fortran::lower::AbstractConverter {
 
       assert(unstructuredContext && "while loop must be unstructured");
       maybeStartBlock(preheaderBlock); // no block or empty block
+      maybeWrapAndRecalc();
+      headerBlock = createNextBeginBlock();
       startBlock(headerBlock);
       genConditionalBranch(*whileCondition, bodyBlock, exitBlock);
     } else if (const auto *bounds =
@@ -2696,6 +2725,8 @@ class FirConverter : public Fortran::lower::AbstractConverter {
           bounds->Step());
       if (unstructuredContext) {
         maybeStartBlock(preheaderBlock);
+        maybeWrapAndRecalc();
+        headerBlock = createNextBeginBlock();
         info.hasRealControl = info.loopVariableSym->GetType()->IsNumeric(
             Fortran::common::TypeCategory::Real);
         info.headerBlock = headerBlock;
@@ -2712,6 +2743,8 @@ class FirConverter : public Fortran::lower::AbstractConverter {
           std::get<std::list<Fortran::parser::LocalitySpec>>(concurrent->t));
       if (unstructuredContext) {
         maybeStartBlock(preheaderBlock);
+        maybeWrapAndRecalc();
+        headerBlock = createNextBeginBlock();
         for (IncrementLoopInfo &info : incrementLoopNestInfo) {
           // The original loop body provides the body and latch blocks of the
           // innermost dimension. The (first) body block of a non-innermost

>From 99824cd3db935dec1def4d245f58cb29a9a360a3 Mon Sep 17 00:00:00 2001
From: ergawy <kareem.ergawy at gmail.com>
Date: Thu, 16 Jul 2026 07:52:50 -0700
Subject: [PATCH 4/5] [flang][PFT-to-MLIR] start entry block for wrappable
 nested IfConstruct

Co-authored-by: Claude Opus 4.7 <noreply at anthropic.com>
---
 flang/lib/Lower/Bridge.cpp                    | 13 ++++++++
 .../if_construct_execute_region_wrap.f90      | 33 +++++++++++++++++++
 2 files changed, 46 insertions(+)
 create mode 100644 flang/test/Lower/if_construct_execute_region_wrap.f90

diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 0ab18131b745b..a796a5cbfb83b 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -3289,6 +3289,19 @@ class FirConverter : public Fortran::lower::AbstractConverter {
       return;
     }
 
+    // If our own first nested statement's block was pre-allocated by the
+    // enclosing construct (as a landing pad for its CFG to branch to; see
+    // the "wrap owns internal blocks" branch in createEmptyBlocks), start
+    // it here so our subsequent eager wrap lands inside it rather than
+    // after the enclosing cf.cond_br in a terminated block.
+    if (Fortran::lower::pft::isWrappableConstruct(eval) &&
+        eval.hasNestedEvaluations()) {
+      Fortran::lower::pft::Evaluation &firstStmt =
+          eval.getFirstNestedEvaluation();
+      if (firstStmt.block)
+        maybeStartBlock(firstStmt.block);
+    }
+
     mlir::Block *savedExitBlock = nullptr;
     mlir::scf::ExecuteRegionOp wrapOp =
         wrapUnstructuredConstruct(eval, savedExitBlock);
diff --git a/flang/test/Lower/if_construct_execute_region_wrap.f90 b/flang/test/Lower/if_construct_execute_region_wrap.f90
new file mode 100644
index 0000000000000..d1474a3aef70e
--- /dev/null
+++ b/flang/test/Lower/if_construct_execute_region_wrap.f90
@@ -0,0 +1,33 @@
+! RUN: bbc -emit-hlfir --wrap-unstructured-constructs-in-execute-region %s -o - | FileCheck %s
+
+! An IF/ELSE whose THEN branch nests another IF, and whose ELSE branch
+! ends in STOP.  Used to trip the MLIR verifier ("operation with block
+! successors must terminate its parent block") because the inner IF's
+! own scf.execute_region wrap was created at the outer IF's terminated
+! entry block (right after the outer's cf.cond_br).  Fixed by starting
+! the entry block that the outer's createEmptyBlocks allocated for the
+! inner (wrappable) IfConstruct before recursing.
+subroutine wrapped_if_nested_with_stop(a, b)
+  integer :: a, b
+  if (a == 1) then
+     if (b == 2) then
+        stop 1
+     end if
+  else
+     stop 1
+  end if
+end subroutine
+
+! CHECK-LABEL: func.func @_QPwrapped_if_nested_with_stop
+! CHECK:         scf.execute_region
+! CHECK:           cf.cond_br
+! CHECK:           scf.execute_region
+! CHECK:             cf.cond_br
+! CHECK:             fir.call @_FortranAStopStatement
+! CHECK:             fir.unreachable
+! CHECK:             scf.yield
+! CHECK:           }
+! CHECK:           fir.call @_FortranAStopStatement
+! CHECK:           fir.unreachable
+! CHECK:           scf.yield
+! CHECK:         }

>From e986d156f0b99379fe789a945fbf8f665e4c648b Mon Sep 17 00:00:00 2001
From: ergawy <kareem.ergawy at gmail.com>
Date: Fri, 17 Jul 2026 00:43:07 -0700
Subject: [PATCH 5/5] Self review

---
 flang/lib/Lower/Bridge.cpp | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index a796a5cbfb83b..55f410b8c1b62 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -2591,8 +2591,6 @@ class FirConverter : public Fortran::lower::AbstractConverter {
     Fortran::lower::pft::Evaluation &eval = getEval();
     bool unstructuredContext = eval.lowerAsUnstructured();
 
-    mlir::Block *savedExitBlock = nullptr;
-    mlir::scf::ExecuteRegionOp wrapOp = nullptr;
 
     // If this do-loop was absorbed by a collapse clause on a parent acc.loop,
     // skip generating any loop — just lower the body.  The IV value is
@@ -2656,8 +2654,11 @@ class FirConverter : public Fortran::lower::AbstractConverter {
     mlir::Block *bodyBlock = doStmtEval.lexicalSuccessor->block;
     mlir::Block *exitBlock = doStmtEval.parentConstruct->constructExit->block;
 
+    mlir::Block *savedExitBlock = nullptr;
+    mlir::scf::ExecuteRegionOp wrapOp = nullptr;
+
     // Helper: called after maybeStartBlock(preheaderBlock) to create the
-    // scf.execute_region (if the construct is wrappable) and then refresh
+    // scf.execute_region (if the construct is wrappable) and then update
     // beginBlock/bodyBlock/exitBlock so that all loop structure lands inside
     // the newly created region.
     auto maybeWrapAndRecalc = [&]() {
@@ -2695,6 +2696,7 @@ class FirConverter : public Fortran::lower::AbstractConverter {
       // Infinite loops are never wrappable; maybeWrapAndRecalc is a no-op here,
       // but call it for uniformity so wrapOp stays null.
       maybeWrapAndRecalc();
+      // TODO (Claude): Maybe we can absorb calls to createNextBeginBlock into mayWrapAndRecalc?
       headerBlock = createNextBeginBlock();
       startBlock(headerBlock);
     } else if ((whileCondition =
@@ -3289,11 +3291,9 @@ class FirConverter : public Fortran::lower::AbstractConverter {
       return;
     }
 
-    // If our own first nested statement's block was pre-allocated by the
-    // enclosing construct (as a landing pad for its CFG to branch to; see
-    // the "wrap owns internal blocks" branch in createEmptyBlocks), start
-    // it here so our subsequent eager wrap lands inside it rather than
-    // after the enclosing cf.cond_br in a terminated block.
+    // Start a new block before generating the scf.execute_region op if needed.
+    // This avoids generating the region directly after a terminator (e.g. a
+    // conditional branch from a parent construct).
     if (Fortran::lower::pft::isWrappableConstruct(eval) &&
         eval.hasNestedEvaluations()) {
       Fortran::lower::pft::Evaluation &firstStmt =



More information about the flang-commits mailing list