[llvm] [JumpThreading] Infer constants from correlated PHIs (PR #190184)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Apr 2 07:37:30 PDT 2026
https://github.com/Lane0218 created https://github.com/llvm/llvm-project/pull/190184
This fixes the missed optimization reported in #188452.
JumpThreading already queries `getConstantOnEdge()` for non-local values, but that is not enough when the value is a PHI whose incoming is correlated with another PHI used by the predecessor branch condition.
This patch adds a small correlated-PHI fallback in JumpThreading:
- first try the existing edge-constant query
- if that fails, inspect another PHI in the same block that controls the predecessor branch
- if the branch condition makes exactly one incoming block feasible, use the corresponding incoming value of the queried PHI
This is enough to fold the branch in the reduced reproducer from #188452 while keeping the change local and conservative.
Tests:
- a regression test for the reduced reproducer
- a negative test where the correlated incoming value is not constant
>From 653cda8b52d71e911281826182810cc48ccb8b62 Mon Sep 17 00:00:00 2001
From: Lane0218 <laneljc at qq.com>
Date: Thu, 2 Apr 2026 22:34:13 +0800
Subject: [PATCH] [JumpThreading] Infer constants from correlated PHIs
Teach JumpThreading to infer a PHI value on a predecessor edge from
another PHI in the same block that controls the predecessor branch.
If the branch condition makes exactly one incoming block feasible, use
the corresponding incoming value of the queried PHI. This fixes the
missed fold in the reduced reproducer from #188452.
Add a regression test and a negative test.
---
llvm/lib/Transforms/Scalar/JumpThreading.cpp | 117 ++++++++++++++++++
.../JumpThreading/phi-correlated-edge.ll | 72 +++++++++++
2 files changed, 189 insertions(+)
create mode 100644 llvm/test/Transforms/JumpThreading/phi-correlated-edge.ll
diff --git a/llvm/lib/Transforms/Scalar/JumpThreading.cpp b/llvm/lib/Transforms/Scalar/JumpThreading.cpp
index 415136b612ac2..9712fb71664de 100644
--- a/llvm/lib/Transforms/Scalar/JumpThreading.cpp
+++ b/llvm/lib/Transforms/Scalar/JumpThreading.cpp
@@ -549,6 +549,120 @@ static Constant *getKnownConstant(Value *Val, ConstantPreference Preference) {
return dyn_cast<ConstantInt>(Val);
}
+static bool getUniqueIncomingValueForBlock(PHINode *PN, BasicBlock *IncomingBB,
+ Value *&IncomingValue) {
+ IncomingValue = nullptr;
+ bool SeenIncoming = false;
+ for (unsigned I = 0, E = PN->getNumIncomingValues(); I != E; ++I) {
+ if (PN->getIncomingBlock(I) != IncomingBB)
+ continue;
+
+ Value *CurrentValue = PN->getIncomingValue(I);
+ if (!SeenIncoming) {
+ IncomingValue = CurrentValue;
+ SeenIncoming = true;
+ continue;
+ }
+
+ if (IncomingValue != CurrentValue)
+ return false;
+ }
+
+ return SeenIncoming;
+}
+
+static std::optional<bool>
+getIncomingBlockConditionValue(Value *Cond, PHINode *GuardPN,
+ unsigned IncomingIdx) {
+ BasicBlock *PhiBB = GuardPN->getParent();
+ BasicBlock *IncomingBB = GuardPN->getIncomingBlock(IncomingIdx);
+
+ auto *Cmp = dyn_cast<ICmpInst>(Cond);
+ if (!Cmp)
+ return std::nullopt;
+
+ PHINode *CmpPN = dyn_cast<PHINode>(Cmp->getOperand(0));
+ bool GuardOnLHS = true;
+ if (!CmpPN || CmpPN->getParent() != PhiBB) {
+ CmpPN = dyn_cast<PHINode>(Cmp->getOperand(1));
+ GuardOnLHS = false;
+ }
+
+ if (CmpPN != GuardPN)
+ return std::nullopt;
+
+ Value *LHS = GuardOnLHS ? GuardPN->getIncomingValue(IncomingIdx)
+ : Cmp->getOperand(0)->DoPHITranslation(PhiBB,
+ IncomingBB);
+ Value *RHS = GuardOnLHS ? Cmp->getOperand(1)->DoPHITranslation(PhiBB,
+ IncomingBB)
+ : GuardPN->getIncomingValue(IncomingIdx);
+ if (auto *C = dyn_cast_or_null<ConstantInt>(
+ simplifyCmpInst(Cmp->getPredicate(), LHS, RHS, {PhiBB->getDataLayout()})))
+ return !C->isZero();
+ return std::nullopt;
+}
+
+/// Try to infer a PHI value on the edge FromBB -> ToBB by using another PHI in
+/// the same block that controls the branch in FromBB. If the branch condition
+/// makes exactly one incoming block feasible, use the corresponding incoming
+/// value of V.
+static Constant *
+getKnownConstantFromCorrelatedPHI(Value *V, BasicBlock *FromBB,
+ BasicBlock *ToBB,
+ ConstantPreference Preference,
+ LazyValueInfo *LVI) {
+ auto *ValPN = dyn_cast<PHINode>(V);
+ if (!ValPN)
+ return nullptr;
+
+ auto *BI = dyn_cast<CondBrInst>(FromBB->getTerminator());
+ if (!BI || BI->getSuccessor(0) == BI->getSuccessor(1))
+ return nullptr;
+
+ const bool IsTrueDest = BI->getSuccessor(0) == ToBB;
+ if (!IsTrueDest && BI->getSuccessor(1) != ToBB)
+ return nullptr;
+
+ auto *Cond = BI->getCondition();
+ auto *Cmp = dyn_cast<ICmpInst>(Cond);
+ if (!Cmp)
+ return nullptr;
+
+ PHINode *GuardPN = dyn_cast<PHINode>(Cmp->getOperand(0));
+ if (!GuardPN || GuardPN->getParent() != ValPN->getParent())
+ GuardPN = dyn_cast<PHINode>(Cmp->getOperand(1));
+ if (!GuardPN || GuardPN->getParent() != ValPN->getParent())
+ return nullptr;
+
+ SmallPtrSet<BasicBlock *, 4> SeenIncomingBlocks;
+ SmallVector<BasicBlock *, 2> CandidateIncomingBlocks;
+ for (unsigned I = 0, E = GuardPN->getNumIncomingValues(); I != E; ++I) {
+ BasicBlock *IncomingBB = GuardPN->getIncomingBlock(I);
+ if (!SeenIncomingBlocks.insert(IncomingBB).second)
+ return nullptr;
+
+ std::optional<bool> CondValue =
+ getIncomingBlockConditionValue(Cond, GuardPN, I);
+ if (CondValue && *CondValue != IsTrueDest)
+ continue;
+
+ CandidateIncomingBlocks.push_back(IncomingBB);
+ if (CandidateIncomingBlocks.size() > 1)
+ return nullptr;
+ }
+
+ if (CandidateIncomingBlocks.size() != 1)
+ return nullptr;
+
+ Value *IncomingValue = nullptr;
+ if (!getUniqueIncomingValueForBlock(ValPN, CandidateIncomingBlocks.front(),
+ IncomingValue))
+ return nullptr;
+
+ return getKnownConstant(IncomingValue, Preference);
+}
+
/// 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
@@ -588,6 +702,9 @@ bool JumpThreadingPass::computeValueKnownInPredecessorsImpl(
// If the value is known by LazyValueInfo to be a constant in a
// predecessor, use that information to try to thread this block.
Constant *PredCst = LVI->getConstantOnEdge(V, P, BB, CxtI);
+ if (!PredCst)
+ PredCst = getKnownConstantFromCorrelatedPHI(
+ V, P, BB, Preference, LVI);
// If I is a non-local compare-with-constant instruction, use more-rich
// 'getPredicateOnEdge' method. This would be able to handle value
// inequalities better, for example if the compare is "X < 4" and "X < 3"
diff --git a/llvm/test/Transforms/JumpThreading/phi-correlated-edge.ll b/llvm/test/Transforms/JumpThreading/phi-correlated-edge.ll
new file mode 100644
index 0000000000000..b48616e177639
--- /dev/null
+++ b/llvm/test/Transforms/JumpThreading/phi-correlated-edge.ll
@@ -0,0 +1,72 @@
+; RUN: opt -S -passes=jump-threading,simplifycfg < %s | FileCheck %s
+
+declare ptr @g()
+declare i64 @h()
+declare void @side()
+
+define void @correlated_phi(i32 %n) {
+; CHECK-LABEL: define void @correlated_phi(
+entry:
+ %p = call ptr @g()
+ %isnull = icmp eq ptr %p, null
+ br label %preheader
+
+preheader:
+ %iv = phi i32 [ 0, %entry ], [ %n, %crit_edge ]
+ %flag = phi i1 [ %isnull, %entry ], [ true, %crit_edge ]
+ br label %loop
+
+issue_check:
+ br i1 %flag, label %ret, label %issue_dead
+
+crit_edge:
+; CHECK: crit_edge:
+; CHECK-NEXT: %exit = icmp eq i32 %iv, 1
+; CHECK-NEXT: br i1 %exit, label %ret, label %preheader
+ %exit = icmp eq i32 %iv, 1
+ br i1 %exit, label %issue_check, label %preheader
+
+loop:
+ %size = call i64 @h()
+ %empty = icmp eq i64 %size, 0
+ br i1 %empty, label %crit_edge, label %loop
+
+ret:
+ ret void
+
+issue_dead:
+ %extra = call i64 @h()
+ br label %ret
+}
+
+; CHECK-NOT: issue_dead:
+; CHECK-NOT: %flag = phi
+
+define void @correlated_phi_nonconstant(i1 %c, i1 %d) {
+; CHECK-LABEL: define void @correlated_phi_nonconstant(
+entry:
+ br label %preheader
+
+preheader:
+ %iv = phi i32 [ 0, %entry ], [ 1, %backedge ]
+ %flag = phi i1 [ %c, %entry ], [ %d, %backedge ]
+ br label %backedge
+
+backedge:
+ %exit = icmp eq i32 %iv, 1
+ br i1 %exit, label %neg_check, label %preheader
+
+neg_check:
+; CHECK: neg_check:
+; CHECK-NEXT: br i1 %flag, label %neg_ret, label %neg_dead
+ br i1 %flag, label %neg_ret, label %neg_dead
+
+neg_dead:
+; CHECK: neg_dead:
+; CHECK-NEXT: call void @side()
+ call void @side()
+ br label %neg_ret
+
+neg_ret:
+ ret void
+}
More information about the llvm-commits
mailing list