[llvm] [SimpleLoopUnswitch] Allow trivial unswitch when exit LCSSA is a header PHI (PR #222962)

Dark Steve via llvm-commits llvm-commits at lists.llvm.org
Thu Sep 17 00:03:53 PDT 2026


https://github.com/PrasoonMishra updated https://github.com/llvm/llvm-project/pull/222962

>From f39cbd4bffee5c7070d35c2223375d85f096f47f Mon Sep 17 00:00:00 2001
From: Dark Steve Jobs <Prasoon.Mishra at amd.com>
Date: Fri, 11 Sep 2026 14:48:35 +0530
Subject: [PATCH 1/9] [SimpleLoopUnswitch] Allow trivial unswitch when exit
 LCSSA is a header PHI

Trivial unswitch bails on a common loop shape: a loop-invariant branch in the
header that exits the loop returning a loop-carried value.

  int acc = 0;
  for (...) {
    if (c)
      return acc;
    acc++;
  }

After loop-rotate the `if` sits in the header, and the exit reads the header PHI
`%acc`. Trivial unswitch hoists the branch into the preheader, so the exit value
must be available there. It only accepts loop-invariant values, and `%acc` is
not one, so it gives up.

But `%acc` is safe here: the branch is invariant, so if the loop exits it exits
on the first iteration, before the latch runs. On that edge `%acc` still holds
its entry value, 0.

Allow the unswitch in this case. Because the exit edge never runs the header,
replace the header PHI in the exit with the value it takes from the preheader
(its entry value), which keeps SSA valid.
---
 .../Transforms/Scalar/SimpleLoopUnswitch.cpp  |  68 ++++++++-
 .../nontrivial-unswitch-trivial-select.ll     |  13 +-
 .../Transforms/SimpleLoopUnswitch/pr138509.ll |   2 +-
 .../SimpleLoopUnswitch/trivial-unswitch.ll    | 134 ++++++++++++++++++
 4 files changed, 205 insertions(+), 12 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp b/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp
index 045c259f0d63b..4a6258afa66e7 100644
--- a/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp
+++ b/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp
@@ -275,6 +275,32 @@ static bool areLoopExitPHIsLoopInvariant(const Loop &L,
   llvm_unreachable("Basic blocks should never be empty!");
 }
 
+/// Return true if every LCSSA PHI in \p ExitBB has, on the edge from the loop
+/// header, either a loop-invariant incoming value or a header PHI.
+///
+/// A header PHI is treated as acceptable because on a header-to-exit edge the
+/// loop body has not run, so the PHI still holds its unique preheader (entry)
+/// value.
+static bool areLoopExitPHIsTrivialFromHeader(const Loop &L,
+                                             const BasicBlock &ExitBB) {
+  const BasicBlock *Header = L.getHeader();
+  for (const Instruction &I : ExitBB) {
+    const auto *PN = dyn_cast<PHINode>(&I);
+    if (!PN)
+      // No more PHIs to check.
+      return true;
+
+    const Value *V = PN->getIncomingValueForBlock(Header);
+    if (L.isLoopInvariant(V))
+      continue;
+
+    const auto *HeaderPN = dyn_cast<PHINode>(V);
+    if (!HeaderPN || HeaderPN->getParent() != Header)
+      return false;
+  }
+  llvm_unreachable("Basic blocks should never be empty!");
+}
+
 /// Copy a set of loop invariant values \p Invariants and insert them at the
 /// end of \p BB and conditionally branch on the copied condition. We only
 /// branch on a single value.
@@ -647,10 +673,19 @@ static bool unswitchTrivialBranch(Loop &L, CondBrInst &BI, DominatorTree &DT,
   }
   auto *ContinueBB = BI.getSuccessor(1 - LoopExitSuccIdx);
   auto *ParentBB = BI.getParent();
+
+  // If the exit incomings aren't loop-invariant, the unswitch is still trivial
+  // when the branch is in the header and every non-invariant incoming is a
+  // header PHI. Those incomings are repaired after unswitching.
+  bool TrivialFromHeader = false;
   if (!ModifiedBranch &&
       !areLoopExitPHIsLoopInvariant(L, *ParentBB, *LoopExitBB)) {
-    LLVM_DEBUG(dbgs() << "   Loop exit PHI's aren't loop-invariant!\n");
-    return false;
+    TrivialFromHeader = ParentBB == L.getHeader() &&
+                        areLoopExitPHIsTrivialFromHeader(L, *LoopExitBB);
+    if (!TrivialFromHeader) {
+      LLVM_DEBUG(dbgs() << "   Loop exit PHI's aren't loop-invariant!\n");
+      return false;
+    }
   }
 
   // When unswitching only part of the branch's condition, we need the exit
@@ -698,6 +733,20 @@ static bool unswitchTrivialBranch(Loop &L, CondBrInst &BI, DominatorTree &DT,
   // the conditional branch. We will change the preheader to have a conditional
   // branch on LoopCond.
   BasicBlock *OldPH = L.getLoopPreheader();
+
+  // Remember each header PHI's value coming from the preheader. We use it later
+  // to fix up the exit, and SplitEdge below is about to change these PHIs.
+  SmallDenseMap<PHINode *, Value *, 4> HeaderEntryValues;
+  if (TrivialFromHeader) {
+    assert(ParentBB == L.getHeader() &&
+           "Header-PHI relaxation only applies to a header branch");
+    for (PHINode &PN : ParentBB->phis()) {
+      Value *EntryV = PN.getIncomingValueForBlock(OldPH);
+      assert(EntryV && "Header PHI must have a preheader incoming value");
+      HeaderEntryValues[&PN] = EntryV;
+    }
+  }
+
   BasicBlock *NewPH = SplitEdge(OldPH, L.getHeader(), &DT, &LI, MSSAU);
 
   // Now that we have a place to insert the conditional branch, create a place
@@ -790,6 +839,21 @@ static bool unswitchTrivialBranch(Loop &L, CondBrInst &BI, DominatorTree &DT,
     rewritePHINodesForExitAndUnswitchedBlocks(*LoopExitBB, *UnswitchedBB,
                                               *ParentBB, *OldPH, FullUnswitch);
 
+  // On the OldPH edge, replace header-PHI incomings with the snapshotted
+  // entry values.
+  if (!HeaderEntryValues.empty())
+    for (PHINode &PN : UnswitchedBB->phis())
+      for (unsigned I = 0, E = PN.getNumIncomingValues(); I != E; ++I) {
+        if (PN.getIncomingBlock(I) != OldPH)
+          continue;
+        auto *HeaderPN = dyn_cast<PHINode>(PN.getIncomingValue(I));
+        if (!HeaderPN)
+          continue;
+        auto It = HeaderEntryValues.find(HeaderPN);
+        if (It != HeaderEntryValues.end())
+          PN.setIncomingValue(I, It->second);
+      }
+
   // The constant we can replace all of our invariants with inside the loop
   // body. If any of the invariants have a value other than this the loop won't
   // be entered.
diff --git a/llvm/test/Transforms/SimpleLoopUnswitch/nontrivial-unswitch-trivial-select.ll b/llvm/test/Transforms/SimpleLoopUnswitch/nontrivial-unswitch-trivial-select.ll
index 5280aa7d3e284..16871d0379c87 100644
--- a/llvm/test/Transforms/SimpleLoopUnswitch/nontrivial-unswitch-trivial-select.ll
+++ b/llvm/test/Transforms/SimpleLoopUnswitch/nontrivial-unswitch-trivial-select.ll
@@ -89,19 +89,14 @@ define i32 @unswitch_trivial_select_cmp_outside(i32 %x) {
 ; CHECK-NEXT:  entry:
 ; CHECK-NEXT:    [[C:%.*]] = icmp ult i32 [[X:%.*]], 100
 ; CHECK-NEXT:    br i1 [[C]], label [[ENTRY_SPLIT_US:%.*]], label [[ENTRY_SPLIT:%.*]]
-; CHECK:       entry.split.us:
-; CHECK-NEXT:    br label [[LOOP_US:%.*]]
-; CHECK:       loop.us:
-; CHECK-NEXT:    [[P_US:%.*]] = phi i32 [ 0, [[ENTRY_SPLIT_US]] ], [ 35, [[LOOP_US]] ]
-; CHECK-NEXT:    br label [[LOOP_US]]
 ; CHECK:       entry.split:
 ; CHECK-NEXT:    br label [[LOOP:%.*]]
 ; CHECK:       loop:
-; CHECK-NEXT:    [[P:%.*]] = phi i32 [ 0, [[ENTRY_SPLIT]] ]
-; CHECK-NEXT:    [[SPEC_SELECT:%.*]] = select i1 false, i1 true, i1 false
-; CHECK-NEXT:    br label [[EXIT:%.*]]
+; CHECK-NEXT:    [[P:%.*]] = phi i32 [ 0, [[ENTRY_SPLIT_US]] ], [ 35, [[LOOP]] ]
+; CHECK-NEXT:    [[SPEC_SELECT:%.*]] = select i1 true, i1 true, i1 false
+; CHECK-NEXT:    br label [[LOOP]]
 ; CHECK:       exit:
-; CHECK-NEXT:    [[LCSSA:%.*]] = phi i32 [ [[P]], [[LOOP]] ]
+; CHECK-NEXT:    [[LCSSA:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ]
 ; CHECK-NEXT:    ret i32 [[LCSSA]]
 ;
 entry:
diff --git a/llvm/test/Transforms/SimpleLoopUnswitch/pr138509.ll b/llvm/test/Transforms/SimpleLoopUnswitch/pr138509.ll
index 2ce0cb5c3f299..a0c5bc7a6531f 100644
--- a/llvm/test/Transforms/SimpleLoopUnswitch/pr138509.ll
+++ b/llvm/test/Transforms/SimpleLoopUnswitch/pr138509.ll
@@ -8,7 +8,7 @@
 ; RUN:     -disable-output 2>&1 | sort -b -k 1 | FileCheck %s --check-prefixes=LOOP-DIV-2
 
 ; LOOP-DIV-1-COUNT-6: Loop at depth 1 containing:
-; LOOP-DIV-2-COUNT-12: Loop at depth 1 containing:
+; LOOP-DIV-2-COUNT-11: Loop at depth 1 containing:
 
 @a = global i32 0, align 4
 @b = global i32 0, align 4
diff --git a/llvm/test/Transforms/SimpleLoopUnswitch/trivial-unswitch.ll b/llvm/test/Transforms/SimpleLoopUnswitch/trivial-unswitch.ll
index 9201880324bff..db9a1c4c273a8 100644
--- a/llvm/test/Transforms/SimpleLoopUnswitch/trivial-unswitch.ll
+++ b/llvm/test/Transforms/SimpleLoopUnswitch/trivial-unswitch.ll
@@ -1478,3 +1478,137 @@ lbl1.loopexit:                                    ; preds = %for.cond1
 ; CHECK-NEXT:    i32 2, label %lbl1.loopexit
 ; CHECK-NEXT:  ]
 }
+
+; Trivially unswitchable header branch whose exit LCSSA phi uses a header phi.
+define i32 @test_unswitch_header_phi(i1 %c) {
+; CHECK-LABEL: @test_unswitch_header_phi(
+entry:
+  br label %header
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br i1 %c, label %entry.split, label %exit
+;
+; CHECK:       entry.split:
+; CHECK-NEXT:    br label %header
+
+header:
+  %acc = phi i32 [ 0, %entry ], [ %next, %latch ]
+  br i1 %c, label %latch, label %exit
+; CHECK:       header:
+; CHECK-NEXT:    %acc = phi i32 [ 0, %entry.split ], [ %next, %latch ]
+; CHECK-NEXT:    br label %latch
+
+latch:
+  %next = add i32 %acc, 1
+  br label %header
+; CHECK:       latch:
+; CHECK-NEXT:    %next = add i32 %acc, 1
+; CHECK-NEXT:    br label %header
+
+exit:
+  %r = phi i32 [ %acc, %header ]
+  ret i32 %r
+; CHECK:       exit:
+; CHECK-NEXT:    %r = phi i32 [ 0, %entry ]
+; CHECK-NEXT:    ret i32 %r
+}
+
+; Header branch whose exit LCSSA phi uses a non-phi header value: not trivially
+; unswitchable, so the loop is left unchanged.
+define i32 @test_no_unswitch_header_nonphi(i1 %c) {
+; CHECK-LABEL: @test_no_unswitch_header_nonphi(
+entry:
+  br label %header
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label %header
+
+header:
+  %acc = phi i32 [ 0, %entry ], [ %next, %latch ]
+  %sum = add i32 %acc, 5
+  br i1 %c, label %latch, label %exit
+; CHECK:       header:
+; CHECK-NEXT:    %acc = phi i32 [ 0, %entry ], [ %next, %latch ]
+; CHECK-NEXT:    %sum = add i32 %acc, 5
+; CHECK-NEXT:    br i1 %c, label %latch, label %exit
+
+latch:
+  %next = add i32 %acc, 1
+  br label %header
+; CHECK:       latch:
+; CHECK-NEXT:    %next = add i32 %acc, 1
+; CHECK-NEXT:    br label %header
+
+exit:
+  %r = phi i32 [ %sum, %header ]
+  ret i32 %r
+; CHECK:       exit:
+; CHECK-NEXT:    %r = phi i32 [ %sum, %header ]
+; CHECK-NEXT:    ret i32 %r
+}
+
+; Trivially unswitchable header branch whose exit has two LCSSA phis: one with a
+; loop-invariant incoming, one with a header phi incoming.
+define i32 @test_unswitch_header_phi_mixed(i1 %c, i32 %inv) {
+; CHECK-LABEL: @test_unswitch_header_phi_mixed(
+entry:
+  br label %header
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br i1 %c, label %entry.split, label %exit
+
+header:
+  %acc = phi i32 [ 0, %entry ], [ %next, %latch ]
+  br i1 %c, label %latch, label %exit
+; CHECK:       header:
+; CHECK-NEXT:    %acc = phi i32 [ 0, %entry.split ], [ %next, %latch ]
+; CHECK-NEXT:    br label %latch
+
+latch:
+  %next = add i32 %acc, 1
+  br label %header
+; CHECK:       latch:
+; CHECK-NEXT:    %next = add i32 %acc, 1
+; CHECK-NEXT:    br label %header
+
+exit:
+  %r = phi i32 [ %acc, %header ]
+  %s = phi i32 [ %inv, %header ]
+  %sum = add i32 %r, %s
+  ret i32 %sum
+; CHECK:       exit:
+; CHECK-NEXT:    %r = phi i32 [ 0, %entry ]
+; CHECK-NEXT:    %s = phi i32 [ %inv, %entry ]
+; CHECK-NEXT:    %sum = add i32 %r, %s
+; CHECK-NEXT:    ret i32 %sum
+}
+
+; Trivially unswitchable header branch whose exit LCSSA phi uses a header phi
+; whose entry value is defined in the preheader, not a constant.
+define i32 @test_unswitch_header_phi_nonconst(i1 %c, i32 %x) {
+; CHECK-LABEL: @test_unswitch_header_phi_nonconst(
+entry:
+  %init = add i32 %x, 3
+  br label %header
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    %init = add i32 %x, 3
+; CHECK-NEXT:    br i1 %c, label %entry.split, label %exit
+
+header:
+  %acc = phi i32 [ %init, %entry ], [ %next, %latch ]
+  br i1 %c, label %latch, label %exit
+; CHECK:       header:
+; CHECK-NEXT:    %acc = phi i32 [ %init, %entry.split ], [ %next, %latch ]
+; CHECK-NEXT:    br label %latch
+
+latch:
+  %next = add i32 %acc, 1
+  br label %header
+; CHECK:       latch:
+; CHECK-NEXT:    %next = add i32 %acc, 1
+; CHECK-NEXT:    br label %header
+
+exit:
+  %r = phi i32 [ %acc, %header ]
+  ret i32 %r
+; CHECK:       exit:
+; CHECK-NEXT:    %r = phi i32 [ %init, %entry ]
+; CHECK-NEXT:    ret i32 %r
+}

>From 977734d1303027ede7ef5bccb5d8c78e6eaef986 Mon Sep 17 00:00:00 2001
From: Dark Steve Jobs <Prasoon.Mishra at amd.com>
Date: Fri, 11 Sep 2026 23:25:09 +0530
Subject: [PATCH 2/9] Generalizing for any br dominating the latch and not just
 the header br.

---
 .../Transforms/Scalar/SimpleLoopUnswitch.cpp  | 23 +++++-----
 .../SimpleLoopUnswitch/trivial-unswitch.ll    | 44 +++++++++++++++++++
 2 files changed, 56 insertions(+), 11 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp b/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp
index 4a6258afa66e7..8f3aa9b87c89c 100644
--- a/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp
+++ b/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp
@@ -275,13 +275,15 @@ static bool areLoopExitPHIsLoopInvariant(const Loop &L,
   llvm_unreachable("Basic blocks should never be empty!");
 }
 
-/// Return true if every LCSSA PHI in \p ExitBB has, on the edge from the loop
-/// header, either a loop-invariant incoming value or a header PHI.
+/// Return true if every LCSSA PHI in \p ExitBB has, on the edge from the
+/// \p ExitingBB, either a loop-invariant incoming value or a header PHI.
 ///
-/// A header PHI is treated as acceptable because on a header-to-exit edge the
-/// loop body has not run, so the PHI still holds its unique preheader (entry)
+/// A header PHI is acceptable only when the caller has ensured \p ExitingBB
+/// dominates the latch: the invariant exit then fires on the first iteration,
+/// before the latch runs, so the header PHI still holds its preheader (entry)
 /// value.
 static bool areLoopExitPHIsTrivialFromHeader(const Loop &L,
+                                             const BasicBlock &ExitingBB,
                                              const BasicBlock &ExitBB) {
   const BasicBlock *Header = L.getHeader();
   for (const Instruction &I : ExitBB) {
@@ -290,7 +292,7 @@ static bool areLoopExitPHIsTrivialFromHeader(const Loop &L,
       // No more PHIs to check.
       return true;
 
-    const Value *V = PN->getIncomingValueForBlock(Header);
+    const Value *V = PN->getIncomingValueForBlock(&ExitingBB);
     if (L.isLoopInvariant(V))
       continue;
 
@@ -675,13 +677,14 @@ static bool unswitchTrivialBranch(Loop &L, CondBrInst &BI, DominatorTree &DT,
   auto *ParentBB = BI.getParent();
 
   // If the exit incomings aren't loop-invariant, the unswitch is still trivial
-  // when the branch is in the header and every non-invariant incoming is a
+  // when ParentBB dominates the latch and every non-invariant incoming is a
   // header PHI. Those incomings are repaired after unswitching.
   bool TrivialFromHeader = false;
   if (!ModifiedBranch &&
       !areLoopExitPHIsLoopInvariant(L, *ParentBB, *LoopExitBB)) {
-    TrivialFromHeader = ParentBB == L.getHeader() &&
-                        areLoopExitPHIsTrivialFromHeader(L, *LoopExitBB);
+    TrivialFromHeader =
+        DT.dominates(ParentBB, L.getLoopLatch()) &&
+        areLoopExitPHIsTrivialFromHeader(L, *ParentBB, *LoopExitBB);
     if (!TrivialFromHeader) {
       LLVM_DEBUG(dbgs() << "   Loop exit PHI's aren't loop-invariant!\n");
       return false;
@@ -738,9 +741,7 @@ static bool unswitchTrivialBranch(Loop &L, CondBrInst &BI, DominatorTree &DT,
   // to fix up the exit, and SplitEdge below is about to change these PHIs.
   SmallDenseMap<PHINode *, Value *, 4> HeaderEntryValues;
   if (TrivialFromHeader) {
-    assert(ParentBB == L.getHeader() &&
-           "Header-PHI relaxation only applies to a header branch");
-    for (PHINode &PN : ParentBB->phis()) {
+    for (PHINode &PN : L.getHeader()->phis()) {
       Value *EntryV = PN.getIncomingValueForBlock(OldPH);
       assert(EntryV && "Header PHI must have a preheader incoming value");
       HeaderEntryValues[&PN] = EntryV;
diff --git a/llvm/test/Transforms/SimpleLoopUnswitch/trivial-unswitch.ll b/llvm/test/Transforms/SimpleLoopUnswitch/trivial-unswitch.ll
index db9a1c4c273a8..ea2c74ab05ec8 100644
--- a/llvm/test/Transforms/SimpleLoopUnswitch/trivial-unswitch.ll
+++ b/llvm/test/Transforms/SimpleLoopUnswitch/trivial-unswitch.ll
@@ -1612,3 +1612,47 @@ exit:
 ; CHECK-NEXT:    %r = phi i32 [ %init, %entry ]
 ; CHECK-NEXT:    ret i32 %r
 }
+
+; Trivially unswitchable branch that is not in the header but dominates the
+; latch, whose exit LCSSA phi uses a header phi. The header branch unswitches
+; first, then %mid is reached and also unswitches.
+define i32 @test_unswitch_nonheader_phi(i1 %c1, i1 %c2) {
+; CHECK-LABEL: @test_unswitch_nonheader_phi(
+entry:
+  br label %header
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br i1 %c1, label %exit1, label %entry.split
+
+header:
+  %acc = phi i32 [ 0, %entry ], [ %next, %latch ]
+  br i1 %c1, label %exit1, label %mid
+; CHECK:       entry.split:
+; CHECK-NEXT:    br i1 %c2, label %exit2, label %entry.split.split
+; CHECK:       header:
+; CHECK-NEXT:    %acc = phi i32 [ 0, %entry.split.split ], [ %next, %latch ]
+; CHECK-NEXT:    br label %mid
+
+mid:
+  br i1 %c2, label %exit2, label %latch
+; CHECK:       mid:
+; CHECK-NEXT:    br label %latch
+
+latch:
+  %next = add i32 %acc, 1
+  br label %header
+; CHECK:       latch:
+; CHECK-NEXT:    %next = add i32 %acc, 1
+; CHECK-NEXT:    br label %header
+
+exit1:
+  ret i32 100
+; CHECK:       exit1:
+; CHECK-NEXT:    ret i32 100
+
+exit2:
+  %r = phi i32 [ %acc, %mid ]
+  ret i32 %r
+; CHECK:       exit2:
+; CHECK-NEXT:    %r = phi i32 [ 0, %entry.split ]
+; CHECK-NEXT:    ret i32 %r
+}

>From 6d18d426329a650165d9c041f09dc47efee51214 Mon Sep 17 00:00:00 2001
From: Dark Steve Jobs <Prasoon.Mishra at amd.com>
Date: Sat, 12 Sep 2026 01:22:25 +0530
Subject: [PATCH 3/9] - Extended areLoopExitPHIsLoopInvariant function. -
 Removed the domiantor check as the br always post-dominate the header. -
 Removed the storing table as we can drive the same value from newPh  
 provided we fix up before replaceLoopInvariantUses is ran.

---
 .../Transforms/Scalar/SimpleLoopUnswitch.cpp  | 78 +++++--------------
 1 file changed, 21 insertions(+), 57 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp b/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp
index 8f3aa9b87c89c..663077ce2dc4f 100644
--- a/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp
+++ b/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp
@@ -258,47 +258,30 @@ static void replaceLoopInvariantUses(const Loop &L, Value *Invariant,
 
 /// Check that all the LCSSA PHI nodes in the loop exit block have trivial
 /// incoming values along this edge.
+///
+/// If \p UsedHeaderPHI is non-null, a header PHI incoming is accepted too and
+/// *UsedHeaderPHI is set; the caller then substitutes the header PHI's
+/// preheader (entry) value after unswitching.
 static bool areLoopExitPHIsLoopInvariant(const Loop &L,
                                          const BasicBlock &ExitingBB,
-                                         const BasicBlock &ExitBB) {
+                                         const BasicBlock &ExitBB,
+                                         bool *UsedHeaderPHI = nullptr) {
   for (const Instruction &I : ExitBB) {
     auto *PN = dyn_cast<PHINode>(&I);
     if (!PN)
       // No more PHIs to check.
       return true;
 
-    // If the incoming value for this edge isn't loop invariant the unswitch
-    // won't be trivial.
-    if (!L.isLoopInvariant(PN->getIncomingValueForBlock(&ExitingBB)))
-      return false;
-  }
-  llvm_unreachable("Basic blocks should never be empty!");
-}
-
-/// Return true if every LCSSA PHI in \p ExitBB has, on the edge from the
-/// \p ExitingBB, either a loop-invariant incoming value or a header PHI.
-///
-/// A header PHI is acceptable only when the caller has ensured \p ExitingBB
-/// dominates the latch: the invariant exit then fires on the first iteration,
-/// before the latch runs, so the header PHI still holds its preheader (entry)
-/// value.
-static bool areLoopExitPHIsTrivialFromHeader(const Loop &L,
-                                             const BasicBlock &ExitingBB,
-                                             const BasicBlock &ExitBB) {
-  const BasicBlock *Header = L.getHeader();
-  for (const Instruction &I : ExitBB) {
-    const auto *PN = dyn_cast<PHINode>(&I);
-    if (!PN)
-      // No more PHIs to check.
-      return true;
-
     const Value *V = PN->getIncomingValueForBlock(&ExitingBB);
     if (L.isLoopInvariant(V))
       continue;
 
+    if (!UsedHeaderPHI)
+      return false;
     const auto *HeaderPN = dyn_cast<PHINode>(V);
-    if (!HeaderPN || HeaderPN->getParent() != Header)
+    if (!HeaderPN || HeaderPN->getParent() != L.getHeader())
       return false;
+    *UsedHeaderPHI = true;
   }
   llvm_unreachable("Basic blocks should never be empty!");
 }
@@ -677,18 +660,14 @@ static bool unswitchTrivialBranch(Loop &L, CondBrInst &BI, DominatorTree &DT,
   auto *ParentBB = BI.getParent();
 
   // If the exit incomings aren't loop-invariant, the unswitch is still trivial
-  // when ParentBB dominates the latch and every non-invariant incoming is a
+  // when branch dominates the latch and every non-invariant incoming is a
   // header PHI. Those incomings are repaired after unswitching.
+  // Branch always dominates the latch as guaranteed by the caller.
   bool TrivialFromHeader = false;
-  if (!ModifiedBranch &&
-      !areLoopExitPHIsLoopInvariant(L, *ParentBB, *LoopExitBB)) {
-    TrivialFromHeader =
-        DT.dominates(ParentBB, L.getLoopLatch()) &&
-        areLoopExitPHIsTrivialFromHeader(L, *ParentBB, *LoopExitBB);
-    if (!TrivialFromHeader) {
-      LLVM_DEBUG(dbgs() << "   Loop exit PHI's aren't loop-invariant!\n");
-      return false;
-    }
+  if (!ModifiedBranch && !areLoopExitPHIsLoopInvariant(
+                             L, *ParentBB, *LoopExitBB, &TrivialFromHeader)) {
+    LLVM_DEBUG(dbgs() << "   Loop exit PHI's aren't loop-invariant!\n");
+    return false;
   }
 
   // When unswitching only part of the branch's condition, we need the exit
@@ -736,18 +715,6 @@ static bool unswitchTrivialBranch(Loop &L, CondBrInst &BI, DominatorTree &DT,
   // the conditional branch. We will change the preheader to have a conditional
   // branch on LoopCond.
   BasicBlock *OldPH = L.getLoopPreheader();
-
-  // Remember each header PHI's value coming from the preheader. We use it later
-  // to fix up the exit, and SplitEdge below is about to change these PHIs.
-  SmallDenseMap<PHINode *, Value *, 4> HeaderEntryValues;
-  if (TrivialFromHeader) {
-    for (PHINode &PN : L.getHeader()->phis()) {
-      Value *EntryV = PN.getIncomingValueForBlock(OldPH);
-      assert(EntryV && "Header PHI must have a preheader incoming value");
-      HeaderEntryValues[&PN] = EntryV;
-    }
-  }
-
   BasicBlock *NewPH = SplitEdge(OldPH, L.getHeader(), &DT, &LI, MSSAU);
 
   // Now that we have a place to insert the conditional branch, create a place
@@ -840,19 +807,16 @@ static bool unswitchTrivialBranch(Loop &L, CondBrInst &BI, DominatorTree &DT,
     rewritePHINodesForExitAndUnswitchedBlocks(*LoopExitBB, *UnswitchedBB,
                                               *ParentBB, *OldPH, FullUnswitch);
 
-  // On the OldPH edge, replace header-PHI incomings with the snapshotted
-  // entry values.
-  if (!HeaderEntryValues.empty())
+  // OldPH branches here without entering the loop, so a header PHI is still
+  // its entry value. Read it from NewPH, the preheader created by the split.
+  if (TrivialFromHeader)
     for (PHINode &PN : UnswitchedBB->phis())
       for (unsigned I = 0, E = PN.getNumIncomingValues(); I != E; ++I) {
         if (PN.getIncomingBlock(I) != OldPH)
           continue;
         auto *HeaderPN = dyn_cast<PHINode>(PN.getIncomingValue(I));
-        if (!HeaderPN)
-          continue;
-        auto It = HeaderEntryValues.find(HeaderPN);
-        if (It != HeaderEntryValues.end())
-          PN.setIncomingValue(I, It->second);
+        if (HeaderPN && HeaderPN->getParent() == L.getHeader())
+          PN.setIncomingValue(I, HeaderPN->getIncomingValueForBlock(NewPH));
       }
 
   // The constant we can replace all of our invariants with inside the loop

>From dc83b9564ccfd94f576d71ba5bc9aa809d9ef561 Mon Sep 17 00:00:00 2001
From: Dark Steve Jobs <Prasoon.Mishra at amd.com>
Date: Sat, 12 Sep 2026 02:43:17 +0530
Subject: [PATCH 4/9] Fixing the comment.

---
 llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp b/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp
index 663077ce2dc4f..1e0da3cc0ac2d 100644
--- a/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp
+++ b/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp
@@ -259,9 +259,8 @@ static void replaceLoopInvariantUses(const Loop &L, Value *Invariant,
 /// Check that all the LCSSA PHI nodes in the loop exit block have trivial
 /// incoming values along this edge.
 ///
-/// If \p UsedHeaderPHI is non-null, a header PHI incoming is accepted too and
-/// *UsedHeaderPHI is set; the caller then substitutes the header PHI's
-/// preheader (entry) value after unswitching.
+/// If \p UsedHeaderPHI is non-null, an incoming value that is a PHI in the
+/// loop header is accepted too. *UsedHeaderPHI is set to true if that happens.
 static bool areLoopExitPHIsLoopInvariant(const Loop &L,
                                          const BasicBlock &ExitingBB,
                                          const BasicBlock &ExitBB,

>From 5cc7998a7a2456aa09f9531df6daaa463713e8ed Mon Sep 17 00:00:00 2001
From: Dark Steve Jobs <Prasoon.Mishra at amd.com>
Date: Sun, 13 Sep 2026 13:02:49 +0530
Subject: [PATCH 5/9] Changed the utility name.

---
 llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp b/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp
index 1e0da3cc0ac2d..594544f61d33f 100644
--- a/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp
+++ b/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp
@@ -261,7 +261,7 @@ static void replaceLoopInvariantUses(const Loop &L, Value *Invariant,
 ///
 /// If \p UsedHeaderPHI is non-null, an incoming value that is a PHI in the
 /// loop header is accepted too. *UsedHeaderPHI is set to true if that happens.
-static bool areLoopExitPHIsLoopInvariant(const Loop &L,
+static bool areLoopExitPHIsTrivial(const Loop &L,
                                          const BasicBlock &ExitingBB,
                                          const BasicBlock &ExitBB,
                                          bool *UsedHeaderPHI = nullptr) {
@@ -608,7 +608,7 @@ static bool unswitchTrivialBranch(Loop &L, CondBrInst &BI, DominatorTree &DT,
   // Redirecting the latch edge to the exit block will cause us to skip latch
   // instructions. This can only be done if the latch instructions don't have
   // side effects and don't have any convergent instructions.
-  if (LatchIdx && areLoopExitPHIsLoopInvariant(L, *LoopLatch, *ULExit) &&
+  if (LatchIdx && areLoopExitPHIsTrivial(L, *LoopLatch, *ULExit) &&
       !llvm::any_of(*LoopLatch, [](Instruction &I) {
         if (const auto *CB = dyn_cast<CallBase>(&I))
           if (CB->isConvergent())
@@ -663,7 +663,7 @@ static bool unswitchTrivialBranch(Loop &L, CondBrInst &BI, DominatorTree &DT,
   // header PHI. Those incomings are repaired after unswitching.
   // Branch always dominates the latch as guaranteed by the caller.
   bool TrivialFromHeader = false;
-  if (!ModifiedBranch && !areLoopExitPHIsLoopInvariant(
+  if (!ModifiedBranch && !areLoopExitPHIsTrivial(
                              L, *ParentBB, *LoopExitBB, &TrivialFromHeader)) {
     LLVM_DEBUG(dbgs() << "   Loop exit PHI's aren't loop-invariant!\n");
     return false;
@@ -892,7 +892,7 @@ static bool unswitchTrivialSwitch(Loop &L, SwitchInst &SI, DominatorTree &DT,
     if (L.contains(&BBToCheck))
       return false;
     // BBToCheck is not trivial to unswitch if its phis aren't loop invariant.
-    if (!areLoopExitPHIsLoopInvariant(L, *ParentBB, BBToCheck))
+    if (!areLoopExitPHIsTrivial(L, *ParentBB, BBToCheck))
       return false;
     // We do not unswitch a block that only has an unreachable statement, as
     // it's possible this is a previously unswitched block. Only unswitch if

>From 5697b0b5807675e59bbdbcddcae85b03988e6ba8 Mon Sep 17 00:00:00 2001
From: Dark Steve Jobs <Prasoon.Mishra at amd.com>
Date: Sun, 13 Sep 2026 13:09:15 +0530
Subject: [PATCH 6/9] git-clang-format apply.

---
 llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp b/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp
index 594544f61d33f..07e07dd71aea4 100644
--- a/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp
+++ b/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp
@@ -261,10 +261,9 @@ static void replaceLoopInvariantUses(const Loop &L, Value *Invariant,
 ///
 /// If \p UsedHeaderPHI is non-null, an incoming value that is a PHI in the
 /// loop header is accepted too. *UsedHeaderPHI is set to true if that happens.
-static bool areLoopExitPHIsTrivial(const Loop &L,
-                                         const BasicBlock &ExitingBB,
-                                         const BasicBlock &ExitBB,
-                                         bool *UsedHeaderPHI = nullptr) {
+static bool areLoopExitPHIsTrivial(const Loop &L, const BasicBlock &ExitingBB,
+                                   const BasicBlock &ExitBB,
+                                   bool *UsedHeaderPHI = nullptr) {
   for (const Instruction &I : ExitBB) {
     auto *PN = dyn_cast<PHINode>(&I);
     if (!PN)
@@ -663,8 +662,8 @@ static bool unswitchTrivialBranch(Loop &L, CondBrInst &BI, DominatorTree &DT,
   // header PHI. Those incomings are repaired after unswitching.
   // Branch always dominates the latch as guaranteed by the caller.
   bool TrivialFromHeader = false;
-  if (!ModifiedBranch && !areLoopExitPHIsTrivial(
-                             L, *ParentBB, *LoopExitBB, &TrivialFromHeader)) {
+  if (!ModifiedBranch &&
+      !areLoopExitPHIsTrivial(L, *ParentBB, *LoopExitBB, &TrivialFromHeader)) {
     LLVM_DEBUG(dbgs() << "   Loop exit PHI's aren't loop-invariant!\n");
     return false;
   }

>From db476ed2684a29490d68837bf4068340a042fbac Mon Sep 17 00:00:00 2001
From: Dark Steve Jobs <Prasoon.Mishra at amd.com>
Date: Sun, 13 Sep 2026 13:26:26 +0530
Subject: [PATCH 7/9] Updated the utility ticket to be more explanatory.

---
 llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp b/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp
index 07e07dd71aea4..d6deea7a80cde 100644
--- a/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp
+++ b/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp
@@ -256,11 +256,13 @@ static void replaceLoopInvariantUses(const Loop &L, Value *Invariant,
   }
 }
 
-/// Check that all the LCSSA PHI nodes in the loop exit block have trivial
-/// incoming values along this edge.
+/// Check that all the LCSSA PHI nodes in \p ExitBB have trivial incoming values
+/// along the edge from \p ExitingBB, i.e. values that are still correct if the
+/// loop is not entered.
 ///
-/// If \p UsedHeaderPHI is non-null, an incoming value that is a PHI in the
-/// loop header is accepted too. *UsedHeaderPHI is set to true if that happens.
+/// Only loop invariant values are trivial by default. If \p UsedHeaderPHI is
+/// non-null, a PHI in the loop header counts as trivial too, and
+/// *UsedHeaderPHI is set to true when one is found.
 static bool areLoopExitPHIsTrivial(const Loop &L, const BasicBlock &ExitingBB,
                                    const BasicBlock &ExitBB,
                                    bool *UsedHeaderPHI = nullptr) {

>From 44c3c3828d99b3a91729f215ed55b5202567337a Mon Sep 17 00:00:00 2001
From: Dark Steve Jobs <Prasoon.Mishra at amd.com>
Date: Thu, 17 Sep 2026 09:31:18 +0530
Subject: [PATCH 8/9] Nits.

---
 llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp b/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp
index d6deea7a80cde..0cec29c131ae8 100644
--- a/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp
+++ b/llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp
@@ -809,15 +809,16 @@ static bool unswitchTrivialBranch(Loop &L, CondBrInst &BI, DominatorTree &DT,
 
   // OldPH branches here without entering the loop, so a header PHI is still
   // its entry value. Read it from NewPH, the preheader created by the split.
-  if (TrivialFromHeader)
+  if (TrivialFromHeader) {
     for (PHINode &PN : UnswitchedBB->phis())
-      for (unsigned I = 0, E = PN.getNumIncomingValues(); I != E; ++I) {
-        if (PN.getIncomingBlock(I) != OldPH)
+      for (unsigned Idx = 0, E = PN.getNumIncomingValues(); Idx != E; ++Idx) {
+        if (PN.getIncomingBlock(Idx) != OldPH)
           continue;
-        auto *HeaderPN = dyn_cast<PHINode>(PN.getIncomingValue(I));
+        auto *HeaderPN = dyn_cast<PHINode>(PN.getIncomingValue(Idx));
         if (HeaderPN && HeaderPN->getParent() == L.getHeader())
-          PN.setIncomingValue(I, HeaderPN->getIncomingValueForBlock(NewPH));
+          PN.setIncomingValue(Idx, HeaderPN->getIncomingValueForBlock(NewPH));
       }
+  }
 
   // The constant we can replace all of our invariants with inside the loop
   // body. If any of the invariants have a value other than this the loop won't

>From c601463d1646b28e2894cd7dce8ed5c47bf77648 Mon Sep 17 00:00:00 2001
From: Dark Steve Jobs <Prasoon.Mishra at amd.com>
Date: Thu, 17 Sep 2026 11:56:03 +0530
Subject: [PATCH 9/9] Added new tests.

---
 .../SimpleLoopUnswitch/trivial-unswitch.ll    | 75 +++++++++++++++++++
 1 file changed, 75 insertions(+)

diff --git a/llvm/test/Transforms/SimpleLoopUnswitch/trivial-unswitch.ll b/llvm/test/Transforms/SimpleLoopUnswitch/trivial-unswitch.ll
index ea2c74ab05ec8..45a1b7da9b6da 100644
--- a/llvm/test/Transforms/SimpleLoopUnswitch/trivial-unswitch.ll
+++ b/llvm/test/Transforms/SimpleLoopUnswitch/trivial-unswitch.ll
@@ -1656,3 +1656,78 @@ exit2:
 ; CHECK-NEXT:    %r = phi i32 [ 0, %entry.split ]
 ; CHECK-NEXT:    ret i32 %r
 }
+
+; Trivially unswitchable header branch whose exit LCSSA reads header phi %acc.
+; The exit is also reached from the latch, so it is split on unswitch.
+define i32 @test_unswitch_header_phi_split_exit(i1 %c, i32 %n) {
+; CHECK-LABEL: @test_unswitch_header_phi_split_exit(
+entry:
+  br label %header
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br i1 %c, label %exit.split, label %entry.split
+
+header:
+  %acc = phi i32 [ 0, %entry ], [ %next, %latch ]
+  br i1 %c, label %exit, label %latch
+; CHECK:       header:
+; CHECK-NEXT:    %acc = phi i32 [ 0, %entry.split ], [ %next, %latch ]
+; CHECK-NEXT:    br label %latch
+
+latch:
+  %next = add i32 %acc, 1
+  %cmp = icmp slt i32 %next, %n
+  br i1 %cmp, label %header, label %exit
+; CHECK:       latch:
+; CHECK-NEXT:    %next = add i32 %acc, 1
+; CHECK-NEXT:    %cmp = icmp slt i32 %next, %n
+; CHECK-NEXT:    br i1 %cmp, label %header, label %exit
+
+exit:
+  %r = phi i32 [ %acc, %header ], [ %next, %latch ]
+  ret i32 %r
+; CHECK:       exit:
+; CHECK-NEXT:    %r = phi i32 [ %next, %latch ]
+; CHECK-NEXT:    br label %exit.split
+; CHECK:       exit.split:
+; CHECK-NEXT:    %r.split = phi i32 [ 0, %entry ], [ %r, %exit ]
+; CHECK-NEXT:    ret i32 %r.split
+}
+
+; Trivially unswitchable header branch whose exit LCSSA reads header phi %acc.
+; The condition is an OR of invariant %a and variant %b, so only %a is peeled
+define i32 @test_unswitch_header_phi_partial(i1 %a) {
+; CHECK-LABEL: @test_unswitch_header_phi_partial(
+entry:
+  br label %header
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    %a.fr = freeze i1 %a
+; CHECK-NEXT:    br i1 %a.fr, label %exit.split, label %entry.split
+
+header:
+  %acc = phi i32 [ 0, %entry ], [ %next, %latch ]
+  %b = icmp eq i32 %acc, 5
+  %or = or i1 %a, %b
+  br i1 %or, label %exit, label %latch
+; CHECK:       header:
+; CHECK-NEXT:    %acc = phi i32 [ 0, %entry.split ], [ %next, %latch ]
+; CHECK-NEXT:    %b = icmp eq i32 %acc, 5
+; CHECK-NEXT:    %or = or i1 false, %b
+; CHECK-NEXT:    br i1 %or, label %exit, label %latch
+
+latch:
+  %next = add i32 %acc, 1
+  br label %header
+; CHECK:       latch:
+; CHECK-NEXT:    %next = add i32 %acc, 1
+; CHECK-NEXT:    br label %header
+
+exit:
+  %r = phi i32 [ %acc, %header ]
+  ret i32 %r
+; CHECK:       exit:
+; CHECK-NEXT:    %r = phi i32 [ %acc, %header ]
+; CHECK-NEXT:    br label %exit.split
+; CHECK:       exit.split:
+; CHECK-NEXT:    %r.split = phi i32 [ 0, %entry ], [ %r, %exit ]
+; CHECK-NEXT:    ret i32 %r.split
+}



More information about the llvm-commits mailing list