[flang-commits] [flang] [flang] Record the evaluations that branch to each evaluation (PR #225755)

Kareem Ergawy via flang-commits flang-commits at lists.llvm.org
Thu Sep 24 02:57:37 PDT 2026


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

>From b880df0dad5ecfa8eff46ae72ac70b8ee7b8bea3 Mon Sep 17 00:00:00 2001
From: ergawy <kareem.ergawy at gmail.com>
Date: Mon, 21 Sep 2026 05:43:14 -0700
Subject: [PATCH 1/2] [flang] Record the evaluations that branch to each
 evaluation

The PFT records where each branch goes, but not where it comes from, so
asking whether anything branches into a construct means walking the whole
procedure.

Record the reverse edges beside the forward ones, and print them in PFT
dumps so both directions of the branch graph are visible.
---
 flang/include/flang/Lower/PFTBuilder.h        |  3 +
 flang/include/flang/Lower/PFTDefs.h           |  3 +
 flang/lib/Lower/PFTBuilder.cpp                | 20 ++++++
 .../test/Lower/assigned-goto-labeled-end.f90  | 16 ++---
 flang/test/Lower/ifconvert.f90                |  2 +-
 .../Lower/pre-fir-tree-incoming-branches.f90  | 68 +++++++++++++++++++
 flang/test/Lower/trailing-cycle.f90           |  6 +-
 7 files changed, 106 insertions(+), 12 deletions(-)
 create mode 100644 flang/test/Lower/pre-fir-tree-incoming-branches.f90

diff --git a/flang/include/flang/Lower/PFTBuilder.h b/flang/include/flang/Lower/PFTBuilder.h
index f495046c66a32..ce390c511135b 100644
--- a/flang/include/flang/Lower/PFTBuilder.h
+++ b/flang/include/flang/Lower/PFTBuilder.h
@@ -733,6 +733,9 @@ struct FunctionLikeUnit : public ProgramUnit {
   const semantics::Scope *scope;
   LabelEvalMap labelEvaluationMap;
   SymbolLabelMap assignSymbolLabelMap;
+  /// Evaluations that branch to a given evaluation -- the inverse of the
+  /// controlSuccessor / extraControlSuccessors edges.
+  IncomingBranchMap incomingBranches;
   ContainedUnitList containedUnitList;
   EvaluationList evaluationList;
   /// <Symbol, Evaluation> pairs for each entry point. The pair at index 0
diff --git a/flang/include/flang/Lower/PFTDefs.h b/flang/include/flang/Lower/PFTDefs.h
index 194f1020da57c..c2d65320acc48 100644
--- a/flang/include/flang/Lower/PFTDefs.h
+++ b/flang/include/flang/Lower/PFTDefs.h
@@ -14,6 +14,7 @@
 #define FORTRAN_LOWER_PFTDEFS_H
 
 #include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/SetVector.h"
 #include "llvm/ADT/SmallSet.h"
 #include "llvm/ADT/StringRef.h"
 
@@ -55,6 +56,8 @@ using Label = std::uint64_t;
 using LabelSet = llvm::SmallSet<Label, 4>;
 using SymbolLabelMap = llvm::DenseMap<SymbolRef, LabelSet>;
 using LabelEvalMap = llvm::DenseMap<Label, Evaluation *>;
+using IncomingBranchMap =
+    llvm::DenseMap<const Evaluation *, llvm::SmallSetVector<Evaluation *, 2>>;
 
 } // namespace pft
 } // namespace lower
diff --git a/flang/lib/Lower/PFTBuilder.cpp b/flang/lib/Lower/PFTBuilder.cpp
index 82c16169b8e42..e6a4d11904679 100644
--- a/flang/lib/Lower/PFTBuilder.cpp
+++ b/flang/lib/Lower/PFTBuilder.cpp
@@ -513,6 +513,7 @@ class PFTBuilder {
         addContainedUnit(lower::pft::FunctionLikeUnit{
             func, pftParentStack.back(), semanticsContext});
     labelEvaluationMap = &unit.labelEvaluationMap;
+    incomingBranches = &unit.incomingBranches;
     assignSymbolLabelMap = &unit.assignSymbolLabelMap;
     containsStmtStack.push_back(false);
     containedUnitList = &unit.containedUnitList;
@@ -528,10 +529,12 @@ class PFTBuilder {
     rewriteIfGotos();
     endFunctionBody();
     analyzeBranches(nullptr, *evaluationListStack.back()); // add branch links
+
     processEntryPoints();
     containsStmtStack.pop_back();
     popEvaluationList();
     labelEvaluationMap = nullptr;
+    incomingBranches = nullptr;
     assignSymbolLabelMap = nullptr;
     pftParentStack.pop_back();
     resetFunctionState();
@@ -588,6 +591,7 @@ class PFTBuilder {
           [&](lower::pft::FunctionLikeUnit &p) {
             containedUnitList = &p.containedUnitList;
             labelEvaluationMap = &p.labelEvaluationMap;
+            incomingBranches = &p.incomingBranches;
             assignSymbolLabelMap = &p.assignSymbolLabelMap;
           },
           [&](auto &) { containedUnitList = nullptr; },
@@ -920,6 +924,8 @@ class PFTBuilder {
                         &targetEvaluation) ==
                  sourceEvaluation.extraControlSuccessors.end())
       sourceEvaluation.extraControlSuccessors.push_back(&targetEvaluation);
+    // Record the reverse edge beside the forward one.
+    (*incomingBranches)[&targetEvaluation].insert(&sourceEvaluation);
     targetEvaluation.isNewBlock = true;
     // If this is a branch into the body of a construct (usually illegal,
     // but allowed in some legacy cases), then the targetEvaluation and its
@@ -1371,6 +1377,7 @@ class PFTBuilder {
   std::vector<lower::pft::EvaluationList *> evaluationListStack{};
   llvm::DenseMap<parser::Label, lower::pft::Evaluation *> *labelEvaluationMap{};
   lower::pft::SymbolLabelMap *assignSymbolLabelMap{};
+  lower::pft::IncomingBranchMap *incomingBranches{};
   std::map<std::string, lower::pft::Evaluation *> constructNameMap{};
   int specificationPartLevel{};
   int interfaceBodyLevel{};
@@ -1488,6 +1495,19 @@ class PFTDumper {
     } else if (eval.isA<parser::EntryStmt>() && eval.lexicalSuccessor) {
       outputStream << " -> " << eval.lexicalSuccessor->printIndex;
     }
+
+    // Incoming branches, the inverse of the "-> N" edges above.
+    if (const lower::pft::FunctionLikeUnit *unit = eval.getOwningProcedure()) {
+      auto it = unit->incomingBranches.find(&eval);
+      if (it != unit->incomingBranches.end() && !it->second.empty()) {
+        outputStream << " <- ";
+        llvm::interleaveComma(it->second, outputStream,
+                              [&](const lower::pft::Evaluation *src) {
+                                outputStream << src->printIndex;
+                              });
+      }
+    }
+
     bool extraNewline = false;
     if (!eval.position.empty())
       outputStream << ": " << eval.position.ToString();
diff --git a/flang/test/Lower/assigned-goto-labeled-end.f90 b/flang/test/Lower/assigned-goto-labeled-end.f90
index 1642560ccea0c..a69cf79157874 100644
--- a/flang/test/Lower/assigned-goto-labeled-end.f90
+++ b/flang/test/Lower/assigned-goto-labeled-end.f90
@@ -12,8 +12,8 @@
 ! Two units in one file, each with a labeled END.  The second unit's END must
 ! not be recorded under the first unit's position.
 ! CHECK-LABEL: Subroutine two_first:
-! CHECK:         AssignedGotoStmt! -> [[E1:[0-9]+]]: go to j
-! CHECK:         [[E1]] ^EndSubroutineStmt: 41 end subroutine
+! CHECK:         [[G1:[0-9]+]] AssignedGotoStmt! -> [[E1:[0-9]+]]: go to j
+! CHECK:         [[E1]] ^EndSubroutineStmt <- [[G1]]: 41 end subroutine
 subroutine two_first(j)
   integer :: j
   assign 41 to j
@@ -21,8 +21,8 @@ subroutine two_first(j)
 41 end subroutine
 
 ! CHECK-LABEL: Subroutine two_second:
-! CHECK:         AssignedGotoStmt! -> [[E2:[0-9]+]]: go to j
-! CHECK:         [[E2]] ^EndSubroutineStmt: 42 end subroutine
+! CHECK:         [[G2:[0-9]+]] AssignedGotoStmt! -> [[E2:[0-9]+]]: go to j
+! CHECK:         [[E2]] ^EndSubroutineStmt <- [[G2]]: 42 end subroutine
 subroutine two_second(j)
   integer :: j
   assign 42 to j
@@ -33,8 +33,8 @@ subroutine two_second(j)
 ! marks the target except the GO TO itself, so this shape depends only on the
 ! recorded classification.
 ! CHECK-LABEL: Subroutine listed_end:
-! CHECK:         AssignedGotoStmt! -> [[E3:[0-9]+]]: go to j,(43)
-! CHECK:         [[E3]] ^EndSubroutineStmt: 43 end subroutine
+! CHECK:         [[G3:[0-9]+]] AssignedGotoStmt! -> [[E3:[0-9]+]]: go to j,(43)
+! CHECK:         [[E3]] ^EndSubroutineStmt <- [[G3]]: 43 end subroutine
 subroutine listed_end(j)
   integer :: j
   go to j, (43)
@@ -42,8 +42,8 @@ subroutine listed_end(j)
 
 ! A labeled END of an internal subprogram, reached through the host's CONTAINS.
 ! CHECK-LABEL: Subroutine inner:
-! CHECK:         AssignedGotoStmt! -> [[E4:[0-9]+]]: go to j
-! CHECK:         [[E4]] ^EndSubroutineStmt: 44 end subroutine
+! CHECK:         [[G4:[0-9]+]] AssignedGotoStmt! -> [[E4:[0-9]+]]: go to j
+! CHECK:         [[E4]] ^EndSubroutineStmt <- [[G4]]: 44 end subroutine
 program host
   call inner(1)
 contains
diff --git a/flang/test/Lower/ifconvert.f90 b/flang/test/Lower/ifconvert.f90
index 0fbe02c8bc903..0827cff080ee5 100644
--- a/flang/test/Lower/ifconvert.f90
+++ b/flang/test/Lower/ifconvert.f90
@@ -61,7 +61,7 @@
   ! CHECK:    <<End IfConstruct!>>
   ! CHECK:    28 ^EndDoStmt -> 17: 3 end do inner
   ! CHECK:  <<End DoConstruct!>>
-  ! CHECK:  29 ^EndDoStmt -> 16: end do outer
+  ! CHECK:  29 ^EndDoStmt -> 16 <- 25: end do outer
   ! CHECK:<<End DoConstruct>>
   outer: do i = 1, 3
     inner: do j = 1, 5
diff --git a/flang/test/Lower/pre-fir-tree-incoming-branches.f90 b/flang/test/Lower/pre-fir-tree-incoming-branches.f90
new file mode 100644
index 0000000000000..dbde4c18d25b1
--- /dev/null
+++ b/flang/test/Lower/pre-fir-tree-incoming-branches.f90
@@ -0,0 +1,68 @@
+! RUN: bbc -pft-test -o %t %s | FileCheck %s
+
+! A branch recorded as "-> target" on its source must appear as "<- source" on
+! its target. Each case captures the indices from the outgoing edge and
+! matches them on the incoming one, so the two directions are checked to agree
+! rather than merely both being present.
+
+! CHECK-LABEL: Subroutine cycle_after_assignment
+subroutine cycle_after_assignment(n, v)
+  integer :: n, i, v(n)
+  do i = 1, n
+    if (v(i) == 1) then
+      ! A statement ahead of the CYCLE keeps rewriteIfGotos from folding the
+      ! branch into a negated condition, so the CycleStmt survives.
+      v(i) = 0
+      ! CHECK: [[CYC:[0-9]+]] CycleStmt! -> [[END:[0-9]+]]
+      cycle
+    end if
+    v(i) = 2
+  ! CHECK: [[END]] ^EndDoStmt -> {{[0-9]+}} <- [[CYC]]
+  end do
+end subroutine
+
+! CHECK-LABEL: Subroutine two_gotos_one_target
+subroutine two_gotos_one_target(n, v)
+  integer :: n, i, v(n)
+  do i = 1, n
+    if (v(i) == 1) then
+      v(i) = 7
+      ! CHECK: [[G1:[0-9]+]] GotoStmt! -> [[TGT:[0-9]+]]
+      goto 60
+    end if
+    if (v(i) == 2) then
+      v(i) = 8
+      ! Two distinct sources converge on one target.
+      ! CHECK: [[G2:[0-9]+]] GotoStmt! -> [[TGT]]
+      goto 60
+    end if
+    v(i) = 3
+  ! CHECK: [[TGT]] ^ContinueStmt <- [[G1]], [[G2]]
+60  continue
+  end do
+end subroutine
+
+! CHECK-LABEL: Subroutine gotos_at_different_depths
+subroutine gotos_at_different_depths(n, v)
+  integer :: n, i, j, v(n)
+  do i = 1, n
+    if (v(i) == 1) then
+      v(i) = 7
+      ! A source in the loop body.
+      ! CHECK: [[D1:[0-9]+]] GotoStmt! -> [[LBL:[0-9]+]]
+      goto 70
+    end if
+    do j = 1, n
+      if (v(j) == 2) then
+        v(j) = 8
+        ! A source nested one loop deeper, branching out of the inner DO to
+        ! the same label. Sources at different depths must both be recorded.
+        ! CHECK: [[D2:[0-9]+]] GotoStmt! -> [[LBL]]
+        goto 70
+      end if
+    end do
+    v(i) = 3
+  ! CHECK: [[LBL]] ^ContinueStmt <- [[D1]], [[D2]]
+70  continue
+  end do
+end subroutine
diff --git a/flang/test/Lower/trailing-cycle.f90 b/flang/test/Lower/trailing-cycle.f90
index ed4ca7f17f0d5..47f904cb46c99 100644
--- a/flang/test/Lower/trailing-cycle.f90
+++ b/flang/test/Lower/trailing-cycle.f90
@@ -49,7 +49,7 @@ subroutine trailing_cycle(a, n)
   ! CHECK:       15 CycleStmt! -> 17: cycle outer
   ! CHECK:       16 ^EndDoStmt -> 13: end do
   ! CHECK:     <<End DoConstruct!>>
-  ! CHECK:     17 ^EndDoStmt -> 12: end do outer
+  ! CHECK:     17 ^EndDoStmt -> 12 <- 15: end do outer
   ! CHECK:   <<End DoConstruct>>
   outer: do i = 1, n
     do j = 1, n
@@ -67,7 +67,7 @@ subroutine trailing_cycle(a, n)
   ! CHECK:       21 EndIfStmt
   ! CHECK:     <<End IfConstruct>>
   ! CHECK:     23 CycleStmt! -> 24: 10 cycle
-  ! CHECK:     24 ^EndDoStmt -> 18: end do
+  ! CHECK:     24 ^EndDoStmt -> 18 <- 23: end do
   ! CHECK:   <<End DoConstruct!>>
   do i = 1, n
     if (a(i) > 0.0) goto 10
@@ -80,7 +80,7 @@ subroutine trailing_cycle(a, n)
   ! CHECK:     25 ^NonLabelDoStmt -> 28: do i = 1, n
   ! CHECK:     26 ^CycleStmt! -> 28: cycle
   ! CHECK:     27 ^AssignmentStmt: a(i) = 5.0
-  ! CHECK:     28 ^EndDoStmt -> 25: end do
+  ! CHECK:     28 ^EndDoStmt -> 25 <- 26: end do
   ! CHECK:   <<End DoConstruct!>>
   do i = 1, n
     cycle

>From 2424d261588a344d43f8032a7552707a35dfab97 Mon Sep 17 00:00:00 2001
From: ergawy <kareem.ergawy at gmail.com>
Date: Thu, 24 Sep 2026 01:56:16 -0700
Subject: [PATCH 2/2] [flang][NFC] Say which control successors are not
 incoming branches

The map records branches, not every control successor: a construct
transferring control between its own statements is not a branch
(F2023 11.2.1 p1), so the successors analyzeBranches sets directly are
absent from it. Say so, rather than calling the map a plain inverse.
---
 flang/include/flang/Lower/PFTBuilder.h | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/flang/include/flang/Lower/PFTBuilder.h b/flang/include/flang/Lower/PFTBuilder.h
index ce390c511135b..7e748ae554a20 100644
--- a/flang/include/flang/Lower/PFTBuilder.h
+++ b/flang/include/flang/Lower/PFTBuilder.h
@@ -733,8 +733,10 @@ struct FunctionLikeUnit : public ProgramUnit {
   const semantics::Scope *scope;
   LabelEvalMap labelEvaluationMap;
   SymbolLabelMap assignSymbolLabelMap;
-  /// Evaluations that branch to a given evaluation -- the inverse of the
-  /// controlSuccessor / extraControlSuccessors edges.
+  /// Evaluations that branch to a given evaluation. A construct transferring
+  /// control between its own statements is not a branch (F2023 11.2.1 p1), so
+  /// the control successors analyzeBranches sets for CASE, ELSE IF, ELSE,
+  /// SELECT RANK and a DO statement and its EndDoStmt are not recorded here.
   IncomingBranchMap incomingBranches;
   ContainedUnitList containedUnitList;
   EvaluationList evaluationList;



More information about the flang-commits mailing list