[llvm] [JumpThreading] Thread a common icmp across PHI nodes (PR #221125)

Abhay Kanhere via llvm-commits llvm-commits at lists.llvm.org
Tue Sep 8 10:54:45 PDT 2026


https://github.com/AbhayKanhere updated https://github.com/llvm/llvm-project/pull/221125

>From 5aa655b070a1f4ced005ea502151e325c84f4f6e Mon Sep 17 00:00:00 2001
From: Abhay Kanhere <a_kanhere at apple.com>
Date: Thu, 3 Sep 2026 10:32:40 -0700
Subject: [PATCH 1/2] [JumpThreading] Thread a common icmp across PHI nodes

When both icmp operands are block PHIs, use isImpliedCondition of the
predecessor's branch to recover its per-edge value and thread known edges.
---
 llvm/lib/Transforms/Scalar/JumpThreading.cpp  |  28 ++++
 .../JumpThreading/thread-cmp-of-phis.ll       | 134 ++++++++++++++++++
 2 files changed, 162 insertions(+)
 create mode 100644 llvm/test/Transforms/JumpThreading/thread-cmp-of-phis.ll

diff --git a/llvm/lib/Transforms/Scalar/JumpThreading.cpp b/llvm/lib/Transforms/Scalar/JumpThreading.cpp
index 7a0542aac83fc..203165bb27672 100644
--- a/llvm/lib/Transforms/Scalar/JumpThreading.cpp
+++ b/llvm/lib/Transforms/Scalar/JumpThreading.cpp
@@ -767,6 +767,34 @@ bool JumpThreadingPass::computeValueKnownInPredecessorsImpl(
           RHS = PN->getIncomingValue(i);
         }
         Value *Res = simplifyCmpInst(Pred, LHS, RHS, {DL});
+
+        // Even without folding, the compare may be known on this edge if
+        // PredBB's branch tests a condition that implies it -- most usefully
+        // the same compare, when both operands are PHIs. PredBB's false arm
+        // reaches BB, so C is false there; BB re-tests C via the PHIs:
+        //
+        //   PredBB: br (C = icmp <pred> A, B), T, BB   ; BB is the false arm
+        //                                          \
+        //                                           v
+        //   BB: L = phi [A, PredBB], ...   ; L -> A on the PredBB edge
+        //       R = phi [B, PredBB], ...   ; R -> B on the PredBB edge
+        //       br (icmp <pred> L, R)      ; == C, false => thread past it
+        //
+        // Require both operands available on the edge (not defined in BB).
+        if (!Res && isa<ICmpInst>(Cmp) && !CmpType->isVectorTy()) {
+          auto DefinedInBB = [&](Value *V) {
+            auto *VI = dyn_cast<Instruction>(V);
+            return VI && VI->getParent() == BB;
+          };
+          auto *PredBI = dyn_cast<CondBrInst>(PredBB->getTerminator());
+          if (PredBI && !DefinedInBB(LHS) && !DefinedInBB(RHS) &&
+              PredBI->getSuccessor(0) != PredBI->getSuccessor(1))
+            if (std::optional<bool> Implied = isImpliedCondition(
+                    PredBI->getCondition(), Pred, LHS, RHS, DL,
+                    /*LHSIsTrue=*/PredBI->getSuccessor(0) == BB))
+              Res = ConstantInt::getBool(Cmp->getContext(), *Implied);
+        }
+
         if (!Res) {
           if (!isa<Constant>(RHS))
             continue;
diff --git a/llvm/test/Transforms/JumpThreading/thread-cmp-of-phis.ll b/llvm/test/Transforms/JumpThreading/thread-cmp-of-phis.ll
new file mode 100644
index 0000000000000..d56ae2ae6a4e9
--- /dev/null
+++ b/llvm/test/Transforms/JumpThreading/thread-cmp-of-phis.ll
@@ -0,0 +1,134 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt -passes=jump-threading -S < %s | FileCheck %s
+;
+; JumpThreading can thread a block whose condition is `icmp <pred> (phiA, phiB)`
+; (both operands PHIs defined in the block) on the predecessor edges where the
+; predecessor's controlling branch implies the comparison. Threading is partial:
+; only edges with a proven result are threaded; others keep the branch.
+
+declare i64 @opaque()
+
+; Positive: on the %entry edge the PHIs reconstruct to `icmp eq %a, %b` = %c
+; (false there), so thread %entry -> %if.ne; the opaque %grow edge keeps its branch.
+define i32 @thread_cmp_of_phis(i64 %a, i64 %b) {
+; CHECK-LABEL: @thread_cmp_of_phis(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[C:%.*]] = icmp eq i64 [[A:%.*]], [[B:%.*]]
+; CHECK-NEXT:    br i1 [[C]], label [[JOIN:%.*]], label [[IF_NE:%.*]]
+; CHECK:       join:
+; CHECK-NEXT:    [[A2:%.*]] = call i64 @opaque()
+; CHECK-NEXT:    [[B2:%.*]] = call i64 @opaque()
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i64 [[A2]], [[B2]]
+; CHECK-NEXT:    br i1 [[CMP]], label [[IF_EQ:%.*]], label [[IF_NE]]
+; CHECK:       if.eq:
+; CHECK-NEXT:    ret i32 1
+; CHECK:       if.ne:
+; CHECK-NEXT:    ret i32 0
+;
+entry:
+  %c = icmp eq i64 %a, %b
+  br i1 %c, label %grow, label %join
+
+grow:
+  %a2 = call i64 @opaque()
+  %b2 = call i64 @opaque()
+  br label %join
+
+join:
+  %p1 = phi i64 [ %a, %entry ], [ %a2, %grow ]
+  %p2 = phi i64 [ %b, %entry ], [ %b2, %grow ]
+  %cmp = icmp eq i64 %p1, %p2
+  br i1 %cmp, label %if.eq, label %if.ne
+
+if.eq:
+  ret i32 1
+
+if.ne:
+  ret i32 0
+}
+
+; Negative: %c compares %a/%b, but the join re-tests %a2/%b2 -- different
+; operands, so %c does not imply it; %entry is not threaded.
+define i32 @no_thread_operands_differ(i64 %a, i64 %b, i64 %a2, i64 %b2) {
+; CHECK-LABEL: @no_thread_operands_differ(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[C:%.*]] = icmp eq i64 [[A:%.*]], [[B:%.*]]
+; CHECK-NEXT:    br i1 [[C]], label [[MID:%.*]], label [[JOIN:%.*]]
+; CHECK:       mid:
+; CHECK-NEXT:    [[PA:%.*]] = call i64 @opaque()
+; CHECK-NEXT:    [[PB:%.*]] = call i64 @opaque()
+; CHECK-NEXT:    br label [[JOIN]]
+; CHECK:       join:
+; CHECK-NEXT:    [[P1:%.*]] = phi i64 [ [[A2:%.*]], [[ENTRY:%.*]] ], [ [[PA]], [[MID]] ]
+; CHECK-NEXT:    [[P2:%.*]] = phi i64 [ [[B2:%.*]], [[ENTRY]] ], [ [[PB]], [[MID]] ]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i64 [[P1]], [[P2]]
+; CHECK-NEXT:    br i1 [[CMP]], label [[IF_EQ:%.*]], label [[IF_NE:%.*]]
+; CHECK:       if.eq:
+; CHECK-NEXT:    ret i32 1
+; CHECK:       if.ne:
+; CHECK-NEXT:    ret i32 0
+;
+entry:
+  %c = icmp eq i64 %a, %b
+  br i1 %c, label %mid, label %join
+
+mid:
+  %pa = call i64 @opaque()
+  %pb = call i64 @opaque()
+  br label %join
+
+join:
+  %p1 = phi i64 [ %a2, %entry ], [ %pa, %mid ]
+  %p2 = phi i64 [ %b2, %entry ], [ %pb, %mid ]
+  %cmp = icmp eq i64 %p1, %p2
+  br i1 %cmp, label %if.eq, label %if.ne
+
+if.eq:
+  ret i32 1
+
+if.ne:
+  ret i32 0
+}
+
+; Negative: same operands, but %c is unsigned (ult) while the join re-tests
+; signed (slt) -- ult does not imply slt; %entry is not threaded.
+define i32 @no_thread_predicate_differs(i64 %a, i64 %b) {
+; CHECK-LABEL: @no_thread_predicate_differs(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[C:%.*]] = icmp ult i64 [[A:%.*]], [[B:%.*]]
+; CHECK-NEXT:    br i1 [[C]], label [[GROW:%.*]], label [[JOIN:%.*]]
+; CHECK:       grow:
+; CHECK-NEXT:    [[A2:%.*]] = call i64 @opaque()
+; CHECK-NEXT:    [[B2:%.*]] = call i64 @opaque()
+; CHECK-NEXT:    br label [[JOIN]]
+; CHECK:       join:
+; CHECK-NEXT:    [[P1:%.*]] = phi i64 [ [[A]], [[ENTRY:%.*]] ], [ [[A2]], [[GROW]] ]
+; CHECK-NEXT:    [[P2:%.*]] = phi i64 [ [[B]], [[ENTRY]] ], [ [[B2]], [[GROW]] ]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i64 [[P1]], [[P2]]
+; CHECK-NEXT:    br i1 [[CMP]], label [[IF_EQ:%.*]], label [[IF_NE:%.*]]
+; CHECK:       if.eq:
+; CHECK-NEXT:    ret i32 1
+; CHECK:       if.ne:
+; CHECK-NEXT:    ret i32 0
+;
+entry:
+  %c = icmp ult i64 %a, %b
+  br i1 %c, label %grow, label %join
+
+grow:
+  %a2 = call i64 @opaque()
+  %b2 = call i64 @opaque()
+  br label %join
+
+join:
+  %p1 = phi i64 [ %a, %entry ], [ %a2, %grow ]
+  %p2 = phi i64 [ %b, %entry ], [ %b2, %grow ]
+  %cmp = icmp slt i64 %p1, %p2
+  br i1 %cmp, label %if.eq, label %if.ne
+
+if.eq:
+  ret i32 1
+
+if.ne:
+  ret i32 0
+}

>From 0454212f84f7995936977a3e5a2a96c319dd3ccb Mon Sep 17 00:00:00 2001
From: Abhay Kanhere <a_kanhere at apple.com>
Date: Tue, 8 Sep 2026 10:40:07 -0700
Subject: [PATCH 2/2] [JumpThreading] Address review feedback; refactor for
 readability (NFC)

Extract the edge-implied-compare check into a static isImpliedByEdgeBranch
helper (keeping the CFG diagram). No functional change.
---
 llvm/lib/Transforms/Scalar/JumpThreading.cpp | 62 ++++++++++++--------
 1 file changed, 36 insertions(+), 26 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/JumpThreading.cpp b/llvm/lib/Transforms/Scalar/JumpThreading.cpp
index 203165bb27672..3e20249478306 100644
--- a/llvm/lib/Transforms/Scalar/JumpThreading.cpp
+++ b/llvm/lib/Transforms/Scalar/JumpThreading.cpp
@@ -549,6 +549,36 @@ static Constant *getKnownConstant(Value *Val, ConstantPreference Preference) {
   return dyn_cast<ConstantInt>(Val);
 }
 
+/// If the edge PredBB->BB is controlled by a conditional branch whose condition
+/// implies `icmp Pred LHS, RHS`, return that compare's value on the edge (else
+/// std::nullopt). LHS/RHS are the operands as seen on the edge and must be
+/// available there -- a compare operand defined in BB does not exist on the
+/// edge. Most useful when both compare operands are PHIs in BB and PredBB
+/// already branched on the same compare:
+///
+///   PredBB: br (C = icmp <pred> A, B), T, BB   ; here BB is the false arm
+///                                          \
+///                                           v
+///   BB: L = phi [A, PredBB], ...   ; L -> A on the PredBB edge
+///       R = phi [B, PredBB], ...   ; R -> B on the PredBB edge
+///       br (icmp <pred> L, R)      ; == C, false => thread past it
+static std::optional<bool> isImpliedByEdgeBranch(BasicBlock *PredBB,
+                                                 BasicBlock *BB,
+                                                 CmpInst::Predicate Pred,
+                                                 Value *LHS, Value *RHS,
+                                                 const DataLayout &DL) {
+  auto DefinedInBB = [&](Value *V) {
+    auto *I = dyn_cast<Instruction>(V);
+    return I && I->getParent() == BB;
+  };
+  auto *PredBI = dyn_cast<CondBrInst>(PredBB->getTerminator());
+  if (!PredBI || PredBI->getSuccessor(0) == PredBI->getSuccessor(1) ||
+      DefinedInBB(LHS) || DefinedInBB(RHS))
+    return std::nullopt;
+  return isImpliedCondition(PredBI->getCondition(), Pred, LHS, RHS, DL,
+                            /*LHSIsTrue=*/PredBI->getSuccessor(0) == BB);
+}
+
 /// computeValueKnownInPredecessors - Given a basic block BB and a value V, see
 /// if we can infer that the value is a known ConstantInt/BlockAddress or undef
 /// in any of our predecessors.  If so, return the known list of value and pred
@@ -768,32 +798,12 @@ bool JumpThreadingPass::computeValueKnownInPredecessorsImpl(
         }
         Value *Res = simplifyCmpInst(Pred, LHS, RHS, {DL});
 
-        // Even without folding, the compare may be known on this edge if
-        // PredBB's branch tests a condition that implies it -- most usefully
-        // the same compare, when both operands are PHIs. PredBB's false arm
-        // reaches BB, so C is false there; BB re-tests C via the PHIs:
-        //
-        //   PredBB: br (C = icmp <pred> A, B), T, BB   ; BB is the false arm
-        //                                          \
-        //                                           v
-        //   BB: L = phi [A, PredBB], ...   ; L -> A on the PredBB edge
-        //       R = phi [B, PredBB], ...   ; R -> B on the PredBB edge
-        //       br (icmp <pred> L, R)      ; == C, false => thread past it
-        //
-        // Require both operands available on the edge (not defined in BB).
-        if (!Res && isa<ICmpInst>(Cmp) && !CmpType->isVectorTy()) {
-          auto DefinedInBB = [&](Value *V) {
-            auto *VI = dyn_cast<Instruction>(V);
-            return VI && VI->getParent() == BB;
-          };
-          auto *PredBI = dyn_cast<CondBrInst>(PredBB->getTerminator());
-          if (PredBI && !DefinedInBB(LHS) && !DefinedInBB(RHS) &&
-              PredBI->getSuccessor(0) != PredBI->getSuccessor(1))
-            if (std::optional<bool> Implied = isImpliedCondition(
-                    PredBI->getCondition(), Pred, LHS, RHS, DL,
-                    /*LHSIsTrue=*/PredBI->getSuccessor(0) == BB))
-              Res = ConstantInt::getBool(Cmp->getContext(), *Implied);
-        }
+        // If it doesn't fold, the compare may still be known on this edge when
+        // PredBB's branch condition implies it (see isImpliedByEdgeBranch).
+        if (!Res && isa<ICmpInst>(Cmp) && !CmpType->isVectorTy())
+          if (std::optional<bool> Implied =
+                  isImpliedByEdgeBranch(PredBB, BB, Pred, LHS, RHS, DL))
+            Res = ConstantInt::getBool(Cmp->getContext(), *Implied);
 
         if (!Res) {
           if (!isa<Constant>(RHS))



More information about the llvm-commits mailing list