[flang-commits] [flang] b0c2dc0 - [flang][PFT-to-MLIR] allocate missing body blocks in `createEmptyRegionBlocks` (#210950)

via flang-commits flang-commits at lists.llvm.org
Wed Jul 22 00:55:15 PDT 2026


Author: Kareem Ergawy
Date: 2026-07-22T09:55:10+02:00
New Revision: b0c2dc0cf8f8de2fd2f0306deea0c5fcd07282da

URL: https://github.com/llvm/llvm-project/commit/b0c2dc0cf8f8de2fd2f0306deea0c5fcd07282da
DIFF: https://github.com/llvm/llvm-project/commit/b0c2dc0cf8f8de2fd2f0306deea0c5fcd07282da.diff

LOG: [flang][PFT-to-MLIR] allocate missing body blocks in `createEmptyRegionBlocks` (#210950)

Problem
-------
Under -mmlir --wrap-unstructured-constructs-in-execute-region, branch
lowering hit a null Evaluation::block for body statements inside an
OpenMP loop. genMultiwayBranch tripped

Bridge.cpp: Assertion `block && "missing multiway branch block"' failed

and genFIR(SelectCaseStmt) tripped

    Bridge.cpp: Assertion `e->block && "missing CaseStmt block"' failed

The wrap machinery decides an unstructured DO/IF is wrappable and stops
propagating its isUnstructured flag to the enclosing OpenMPConstruct.
The OMP construct's isUnstructured stays false, so the top-level
createEmptyBlocks treats it as a structured directive with nested
evaluations — it allocates only the OMP construct's first-nested block
and does not recurse into the DO body. Then OMP loop lowering takes
over the body via createEmptyRegionBlocks, which was written to
*re-parent* pre-existing blocks into its region and silently skipped
evals whose .block was still null. Body targets (e.g. label 17's
ContinueStmt, or a SelectCase's CaseStmts) therefore had no block when
the consumer read evalOfLabel(...).block, tripping the asserts.

Solution
--------
Restore the invariant that consumers of eval.block can trust it at
lowering time. In createEmptyRegionBlocks (DirectivesCommon.h), when an
eval has isNewBlock but no .block, create one in the current region
alongside the existing re-parenting path. The OMP lowering path then
finds pre-allocated blocks for the whole body — matching the state that
existed before the wrap feature changed isUnstructured propagation —
without any lazy-allocation logic at consumer sites.

Co-authored-by: Claude Opus 4.7 <noreply at anthropic.com>

Added: 
    flang/test/Lower/OpenMP/wsloop-computed-goto.f90
    flang/test/Lower/OpenMP/wsloop-select-case.f90

Modified: 
    flang/include/flang/Lower/DirectivesCommon.h

Removed: 
    


################################################################################
diff  --git a/flang/include/flang/Lower/DirectivesCommon.h b/flang/include/flang/Lower/DirectivesCommon.h
index cead8dbbeeddd..b50bf0f1ce3af 100644
--- a/flang/include/flang/Lower/DirectivesCommon.h
+++ b/flang/include/flang/Lower/DirectivesCommon.h
@@ -41,7 +41,12 @@ namespace Fortran {
 namespace lower {
 
 /// Create empty blocks for the current region.
-/// These blocks replace blocks parented to an enclosing region.
+/// These blocks replace blocks parented to an enclosing region, or are
+/// created fresh when the enclosing region-level createEmptyBlocks skipped
+/// them (this happens for the body of a wrappable DO/IF nested inside a
+/// directive whose isUnstructured no longer propagates from the child; the
+/// top-level pass then treats the directive as structured and never
+/// recurses into the body).
 template <typename... TerminatorOps>
 void createEmptyRegionBlocks(
     fir::FirOpBuilder &builder,
@@ -57,6 +62,8 @@ void createEmptyRegionBlocks(
         assert(mlir::isa<TerminatorOps...>(terminatorOp) &&
                "expected terminator op");
       }
+    } else if (eval.isNewBlock) {
+      eval.block = builder.createBlock(region);
     }
     if (!eval.isDirective() && eval.hasNestedEvaluations())
       createEmptyRegionBlocks<TerminatorOps...>(builder,

diff  --git a/flang/test/Lower/OpenMP/wsloop-computed-goto.f90 b/flang/test/Lower/OpenMP/wsloop-computed-goto.f90
new file mode 100644
index 0000000000000..64b221ce672dc
--- /dev/null
+++ b/flang/test/Lower/OpenMP/wsloop-computed-goto.f90
@@ -0,0 +1,39 @@
+! RUN: bbc -emit-hlfir -fopenmp --wrap-unstructured-constructs-in-execute-region %s -o - | FileCheck %s
+
+! An unstructured DO whose body has a computed GO TO targeting a label
+! inside the loop.  With the wrap flag on, the inner DO is wrappable, so
+! its isUnstructured no longer propagates to the enclosing
+! OpenMPConstruct.  The top-level createEmptyBlocks therefore treats the
+! OMP construct as structured and does not recurse into the DO body — the
+! label 17 ContinueStmt block would be left unallocated.  OpenMP's own
+! createEmptyRegionBlocks now creates the missing block in the loop-nest
+! region so genMultiwayBranch's fir.select finds a target.
+!
+! The outer IF is wrappable, so the whole subroutine body sits inside an
+! scf.execute_region — this exercises the interaction between the wrap
+! machinery (which does allocate its own nested blocks) and the OMP loop
+! lowering (which now allocates missing body blocks itself).
+subroutine s(ii1, a)
+  integer ii1, a
+  if (a > 0) then
+!$omp do
+    do ii1 = 0, 1
+      go to (17), ii1
+17    continue
+    end do
+    stop
+  end if
+end subroutine
+
+! CHECK-LABEL: func.func @_QPs
+! CHECK:         scf.execute_region no_inline {
+! CHECK:           cf.cond_br
+! CHECK:           omp.wsloop
+! CHECK:             omp.loop_nest
+! CHECK:               fir.load
+! CHECK:               fir.select %{{[0-9]+}} : i32 [1, ^[[TARGET:bb[0-9]+]], unit, ^[[TARGET]]]
+! CHECK:             ^[[TARGET]]:
+! CHECK:               omp.yield
+! CHECK:           fir.call @_FortranAStopStatement
+! CHECK:           scf.yield
+! CHECK:         }

diff  --git a/flang/test/Lower/OpenMP/wsloop-select-case.f90 b/flang/test/Lower/OpenMP/wsloop-select-case.f90
new file mode 100644
index 0000000000000..cd14ddbf6294e
--- /dev/null
+++ b/flang/test/Lower/OpenMP/wsloop-select-case.f90
@@ -0,0 +1,49 @@
+! RUN: bbc -emit-hlfir -fopenmp --wrap-unstructured-constructs-in-execute-region %s -o - | FileCheck %s
+
+! An unstructured SELECT CASE inside an OpenMP DO body.  With the wrap
+! flag on, the enclosing DoConstruct is wrappable, so its isUnstructured
+! no longer propagates to the OpenMPConstruct — the top-level
+! createEmptyBlocks does not recurse into the DO body, and the CaseStmt /
+! constructExit blocks the SelectCase lowering wants would be left
+! unallocated.  OpenMP's own createEmptyRegionBlocks now creates the
+! missing blocks in the loop-nest region so genFIR(SelectCaseStmt)
+! finds targets for its fir.select_case.
+!
+! The outer IF is wrappable, so the whole subroutine body sits inside an
+! scf.execute_region — this exercises the interaction between the wrap
+! machinery (which does allocate its own nested blocks) and the OMP loop
+! lowering (which now allocates missing body blocks itself).
+subroutine s(a, k)
+  integer a, k
+  if (a > 0) then
+!$omp do
+    do k = 1, 2
+      select case (k)
+        case (1)
+          a = 10
+        case (2)
+          a = 20
+        case default
+          a = 30
+      end select
+    end do
+    stop
+  end if
+end subroutine
+
+! CHECK-LABEL: func.func @_QPs
+! CHECK:         scf.execute_region no_inline {
+! CHECK:           cf.cond_br
+! CHECK:           omp.wsloop
+! CHECK:             omp.loop_nest
+! CHECK:               fir.load
+! CHECK:               fir.select_case %{{[0-9]+}} : i32 [#fir.point, %{{.*}}, ^[[C1:bb[0-9]+]], #fir.point, %{{.*}}, ^[[C2:bb[0-9]+]], unit, ^[[CDFLT:bb[0-9]+]]]
+! CHECK:             ^[[C1]]:
+! CHECK:               hlfir.assign
+! CHECK:             ^[[C2]]:
+! CHECK:               hlfir.assign
+! CHECK:             ^[[CDFLT]]:
+! CHECK:               hlfir.assign
+! CHECK:           fir.call @_FortranAStopStatement
+! CHECK:           scf.yield
+! CHECK:         }


        


More information about the flang-commits mailing list