[llvm] [LVI] Augment getPredicateOnEdge for edge from switch (PR #189056)

Kunqiu Chen via llvm-commits llvm-commits at lists.llvm.org
Fri Mar 27 09:53:26 PDT 2026


https://github.com/Camsyn created https://github.com/llvm/llvm-project/pull/189056

Previously, `getPredicateOnEdge(Pred, V, C)` only relied on the lattice value of V to judge whether Pred(V, C) is true or false.
And the lattice value, based on interval analysis, might lose some precision (e.g., [1, 2) ∪ [5, 6) = [1, 6) ).

Nonetheless, when the edge is from `switch` and the predicate is `ne` or `eq`, we can directly use the switch-arms to judge Pred(V, C) to improve the precision.

E.g., 
```cpp
switch(c) {
case 1: 
case 3: 
  // must be false
  if (c == 2) {...}
}
```

This patch is very useful for jump-threading to thread some BBs with eq/ne icmp, and for promoting simplifycfg to fold eq/ne into switch.


Actually, this patch is very similar to what `getPredicateAt` did (refer to aeefae0cc58e3afab940875e466926d90c285929).

---


>From 3d3bfc526a1cef78bc00c09e527c1906ba7e8b32 Mon Sep 17 00:00:00 2001
From: Camsyn <camsyn at foxmail.com>
Date: Sat, 28 Mar 2026 00:34:19 +0800
Subject: [PATCH 1/2] Pre-commit tests

---
 .../lvi-switch-eq.ll                          | 123 ++++++++++++++++++
 1 file changed, 123 insertions(+)
 create mode 100644 llvm/test/Transforms/CorrelatedValuePropagation/lvi-switch-eq.ll

diff --git a/llvm/test/Transforms/CorrelatedValuePropagation/lvi-switch-eq.ll b/llvm/test/Transforms/CorrelatedValuePropagation/lvi-switch-eq.ll
new file mode 100644
index 0000000000000..1b3fff8bba91d
--- /dev/null
+++ b/llvm/test/Transforms/CorrelatedValuePropagation/lvi-switch-eq.ll
@@ -0,0 +1,123 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes=correlated-propagation -S | FileCheck %s
+
+; Exercise the four getPredicateOnEdge() switch eq/ne cases using a live-in
+; value defined in the switch block. This forces CVP to query
+; getPredicateAt(), which in turn pushes the predicate onto the incoming switch
+; edge via getPredicateOnEdge().
+
+define i1 @default_eq_false(i32 %x) {
+; CHECK-LABEL: define i1 @default_eq_false(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[PRED:%.*]] = add i32 [[X]], 1
+; CHECK-NEXT:    switch i32 [[X]], label %[[DEFAULT:.*]] [
+; CHECK-NEXT:      i32 2, label %[[OTHER:.*]]
+; CHECK-NEXT:    ]
+; CHECK:       [[OTHER]]:
+; CHECK-NEXT:    ret i1 false
+; CHECK:       [[DEFAULT]]:
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[PRED]], 3
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+entry:
+  %pred = add i32 %x, 1
+  switch i32 %x, label %default [
+  i32 2, label %other
+  ]
+
+other:
+  ret i1 false
+
+default:
+  %cmp = icmp eq i32 %pred, 3
+  ret i1 %cmp
+}
+
+define i1 @default_ne_true(i32 %x) {
+; CHECK-LABEL: define i1 @default_ne_true(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[PRED:%.*]] = add i32 [[X]], 1
+; CHECK-NEXT:    switch i32 [[X]], label %[[DEFAULT:.*]] [
+; CHECK-NEXT:      i32 2, label %[[OTHER:.*]]
+; CHECK-NEXT:    ]
+; CHECK:       [[OTHER]]:
+; CHECK-NEXT:    ret i1 false
+; CHECK:       [[DEFAULT]]:
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ne i32 [[PRED]], 3
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+entry:
+  %pred = add i32 %x, 1
+  switch i32 %x, label %default [
+  i32 2, label %other
+  ]
+
+other:
+  ret i1 false
+
+default:
+  %cmp = icmp ne i32 %pred, 3
+  ret i1 %cmp
+}
+
+define i1 @case_eq_false(i32 %x) {
+; CHECK-LABEL: define i1 @case_eq_false(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[PRED:%.*]] = add i32 [[X]], 1
+; CHECK-NEXT:    switch i32 [[X]], label %[[OTHER:.*]] [
+; CHECK-NEXT:      i32 1, label %[[CASE:.*]]
+; CHECK-NEXT:      i32 3, label %[[CASE]]
+; CHECK-NEXT:    ]
+; CHECK:       [[OTHER]]:
+; CHECK-NEXT:    ret i1 false
+; CHECK:       [[CASE]]:
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[PRED]], 3
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+entry:
+  %pred = add i32 %x, 1
+  switch i32 %x, label %other [
+  i32 1, label %case
+  i32 3, label %case
+  ]
+
+other:
+  ret i1 false
+
+case:
+  %cmp = icmp eq i32 %pred, 3
+  ret i1 %cmp
+}
+
+define i1 @case_ne_true(i32 %x) {
+; CHECK-LABEL: define i1 @case_ne_true(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[PRED:%.*]] = add i32 [[X]], 1
+; CHECK-NEXT:    switch i32 [[X]], label %[[OTHER:.*]] [
+; CHECK-NEXT:      i32 1, label %[[CASE:.*]]
+; CHECK-NEXT:      i32 3, label %[[CASE]]
+; CHECK-NEXT:    ]
+; CHECK:       [[OTHER]]:
+; CHECK-NEXT:    ret i1 false
+; CHECK:       [[CASE]]:
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ne i32 [[PRED]], 3
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+entry:
+  %pred = add i32 %x, 1
+  switch i32 %x, label %other [
+  i32 1, label %case
+  i32 3, label %case
+  ]
+
+other:
+  ret i1 false
+
+case:
+  %cmp = icmp ne i32 %pred, 3
+  ret i1 %cmp
+}

>From f4d794819119e2edf973885a8b2fd81c87d7c224 Mon Sep 17 00:00:00 2001
From: Camsyn <camsyn at foxmail.com>
Date: Sat, 28 Mar 2026 00:36:38 +0800
Subject: [PATCH 2/2] [LVI] Augment getPredicateOnEdge for eq on switch

---
 llvm/lib/Analysis/LazyValueInfo.cpp           | 86 ++++++++++++++++++-
 .../lvi-switch-eq.ll                          | 12 +--
 2 files changed, 89 insertions(+), 9 deletions(-)

diff --git a/llvm/lib/Analysis/LazyValueInfo.cpp b/llvm/lib/Analysis/LazyValueInfo.cpp
index b4d64536a5aca..3010c6ac7c7fb 100644
--- a/llvm/lib/Analysis/LazyValueInfo.cpp
+++ b/llvm/lib/Analysis/LazyValueInfo.cpp
@@ -2096,7 +2096,91 @@ Constant *LazyValueInfo::getPredicateOnEdge(CmpInst::Predicate Pred, Value *V,
   ValueLatticeElement Result =
       getOrCreateImpl(M).getValueOnEdge(V, FromBB, ToBB, CxtI);
 
-  return getPredicateResult(Pred, C, Result, M->getDataLayout());
+  Constant *Ret = getPredicateResult(Pred, C, Result, M->getDataLayout());
+  if (Ret)
+    return Ret;
+
+  // Note: The following bit of code is somewhat distinct from the rest of LVI;
+  // LVI as a whole tries to compute a lattice value which is conservatively
+  // correct on a given edge, and then checks the predicate against that
+  // merged edge result. In this case, we have an equality predicate on a
+  // switch edge which we weren't able to prove from the edge lattice alone,
+  // and we're reasoning directly from the switch partition instead. As a
+  // motivating example, consider:
+  // sw:
+  //   switch i32 %x, label %default [
+  //     i32 1, label %case
+  //     i32 3, label %case
+  //   ]
+  // case:
+  //   ; sw -> case: %x ∈ [1, 4) = [1, 2) ∪ [3, 4)
+  //   %pred = icmp eq i32 %x, 2
+  // On the edge sw -> case, the generic edge lattice for %x may conservatively
+  // merge the incoming values 1 and 3 into a range like [1, 4), which is too
+  // imprecise to prove '%pred' false because 2 is still inside that range. By
+  // looking at the switch partition directly, we know the only values reaching
+  // %case are exactly 1 and 3, so '%pred' must be false. We limit this
+  // reasoning to the originating switch and value, optionally constant-folding
+  // a single foldable use of the switch condition per case.
+  if (auto *SW = dyn_cast<SwitchInst>(FromBB->getTerminator());
+      SW && ICmpInst::isEquality(Pred)) {
+    assert(isa<ConstantInt>(C) && "Expected integer C");
+    const APInt &CmpInt = cast<ConstantInt>(C)->getValue();
+    bool ValUsesConditionAndMayBeFoldable = false;
+    auto *Condition = SW->getCondition();
+    if (Condition != V) {
+      User *Usr = dyn_cast<User>(V);
+      // Check if Val has Condition as an operand.
+      ValUsesConditionAndMayBeFoldable =
+          Usr && isOperationFoldable(Usr) && usesOperand(Usr, Condition);
+      if (!ValUsesConditionAndMayBeFoldable)
+        return nullptr;
+    }
+
+    auto GetValueOnCase = [&](const APInt &CaseVal) -> std::optional<APInt> {
+      return ValUsesConditionAndMayBeFoldable
+                 ? constantFoldUser(cast<User>(V), Condition, CaseVal,
+                                    ToBB->getDataLayout())
+                       .asConstantInteger()
+                 : CaseVal;
+    };
+    const bool IsDefault = ToBB == SW->getDefaultDest();
+    // Collect related cases
+    auto Filter = [&](const SwitchInst::CaseHandle &Case) {
+      return IsDefault ? Case.getCaseSuccessor() != ToBB
+                       : Case.getCaseSuccessor() == ToBB;
+    };
+    auto EqC = [&](const SwitchInst::CaseHandle &Case) {
+      auto Value = GetValueOnCase(Case.getCaseValue()->getValue());
+      return Value ? *Value == CmpInt : false;
+    };
+    auto NeC = [&](const SwitchInst::CaseHandle &Case) {
+      auto Value = GetValueOnCase(Case.getCaseValue()->getValue());
+      return Value ? *Value != CmpInt : false;
+    };
+    // For V eq/ne C in edge SW -> ToBB, we consider the following situations:
+    // ToBB == SW.getDefaultDest():
+    //   cases = { case.value | case.dest != ToBB }, i.e., all arms --/-> ToBB
+    //       Pred = eq: ∃ c ∈ cases, c == C --> V == C must be false
+    //       Pred = ne: ∃ c ∈ cases, c == C --> V != C must be true
+    // ToBB != SW.getDefaultDest():
+    //   cases = { case.value | case.dest == ToBB }, i.e., all arms ----> ToBB
+    //       Pred = eq: ∀ c ∈ cases, c != C --> V == C must be false
+    //       Pred = ne: ∀ c ∈ cases, c != C --> V != C must be true
+    auto FilteredCases = make_filter_range(SW->cases(), Filter);
+
+    std::optional<bool> Res =
+        IsDefault ? (any_of(FilteredCases, EqC)
+                         ? std::optional(Pred == ICmpInst::ICMP_NE)
+                         : std::nullopt)
+                  : (all_of(FilteredCases, NeC)
+                         ? std::optional(Pred == ICmpInst::ICMP_NE)
+                         : std::nullopt);
+    if (!Res)
+      return nullptr;
+    return ConstantInt::getBool(Type::getInt1Ty(V->getContext()), *Res);
+  }
+  return nullptr;
 }
 
 Constant *LazyValueInfo::getPredicateAt(CmpInst::Predicate Pred, Value *V,
diff --git a/llvm/test/Transforms/CorrelatedValuePropagation/lvi-switch-eq.ll b/llvm/test/Transforms/CorrelatedValuePropagation/lvi-switch-eq.ll
index 1b3fff8bba91d..d5ff17d4db649 100644
--- a/llvm/test/Transforms/CorrelatedValuePropagation/lvi-switch-eq.ll
+++ b/llvm/test/Transforms/CorrelatedValuePropagation/lvi-switch-eq.ll
@@ -17,8 +17,7 @@ define i1 @default_eq_false(i32 %x) {
 ; CHECK:       [[OTHER]]:
 ; CHECK-NEXT:    ret i1 false
 ; CHECK:       [[DEFAULT]]:
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[PRED]], 3
-; CHECK-NEXT:    ret i1 [[CMP]]
+; CHECK-NEXT:    ret i1 false
 ;
 entry:
   %pred = add i32 %x, 1
@@ -45,8 +44,7 @@ define i1 @default_ne_true(i32 %x) {
 ; CHECK:       [[OTHER]]:
 ; CHECK-NEXT:    ret i1 false
 ; CHECK:       [[DEFAULT]]:
-; CHECK-NEXT:    [[CMP:%.*]] = icmp ne i32 [[PRED]], 3
-; CHECK-NEXT:    ret i1 [[CMP]]
+; CHECK-NEXT:    ret i1 true
 ;
 entry:
   %pred = add i32 %x, 1
@@ -74,8 +72,7 @@ define i1 @case_eq_false(i32 %x) {
 ; CHECK:       [[OTHER]]:
 ; CHECK-NEXT:    ret i1 false
 ; CHECK:       [[CASE]]:
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[PRED]], 3
-; CHECK-NEXT:    ret i1 [[CMP]]
+; CHECK-NEXT:    ret i1 false
 ;
 entry:
   %pred = add i32 %x, 1
@@ -104,8 +101,7 @@ define i1 @case_ne_true(i32 %x) {
 ; CHECK:       [[OTHER]]:
 ; CHECK-NEXT:    ret i1 false
 ; CHECK:       [[CASE]]:
-; CHECK-NEXT:    [[CMP:%.*]] = icmp ne i32 [[PRED]], 3
-; CHECK-NEXT:    ret i1 [[CMP]]
+; CHECK-NEXT:    ret i1 true
 ;
 entry:
   %pred = add i32 %x, 1



More information about the llvm-commits mailing list