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

Kunqiu Chen via llvm-commits llvm-commits at lists.llvm.org
Tue Mar 31 02:09:07 PDT 2026


https://github.com/Camsyn updated https://github.com/llvm/llvm-project/pull/189433

>From e86be54fc81e41abd1d29526b35e626932e3e3d1 Mon Sep 17 00:00:00 2001
From: Camsyn <camsyn at foxmail.com>
Date: Tue, 31 Mar 2026 17:07:25 +0800
Subject: [PATCH 1/2] Pre-commit tests

---
 .../lvi-switch-eq.ll                          | 179 ++++++++++++++++++
 1 file changed, 179 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..0b23bc174ada7
--- /dev/null
+++ b/llvm/test/Transforms/CorrelatedValuePropagation/lvi-switch-eq.ll
@@ -0,0 +1,179 @@
+; 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 @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
+}
+
+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
+}
+
+; Negative tests for default, as it requires the mapping injective.
+
+define i1 @default_eq_noninjective(i32 %x) {
+; CHECK-LABEL: define i1 @default_eq_noninjective(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[PRED:%.*]] = and 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:    ret i1 false
+;
+entry:
+  %pred = and 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_noninjective(i32 %x) {
+; CHECK-LABEL: define i1 @default_ne_noninjective(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[PRED:%.*]] = and 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:    ret i1 true
+;
+entry:
+  %pred = and 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
+}

>From bf8e0f9b30cce5fa0350e7e0788a716405d8790d Mon Sep 17 00:00:00 2001
From: Camsyn <camsyn at foxmail.com>
Date: Tue, 31 Mar 2026 17:08:19 +0800
Subject: [PATCH 2/2] [LVI] Augment getPredicateOnEdge for eq on switch

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

diff --git a/llvm/lib/Analysis/LazyValueInfo.cpp b/llvm/lib/Analysis/LazyValueInfo.cpp
index bea5f70b03f02..e063eb7a89f77 100644
--- a/llvm/lib/Analysis/LazyValueInfo.cpp
+++ b/llvm/lib/Analysis/LazyValueInfo.cpp
@@ -34,10 +34,13 @@
 #include "llvm/IR/Intrinsics.h"
 #include "llvm/IR/LLVMContext.h"
 #include "llvm/IR/Module.h"
+#include "llvm/IR/Operator.h"
 #include "llvm/IR/PatternMatch.h"
 #include "llvm/IR/ValueHandle.h"
 #include "llvm/InitializePasses.h"
+#include "llvm/Support/Casting.h"
 #include "llvm/Support/Debug.h"
+#include "llvm/Support/Errc.h"
 #include "llvm/Support/FormattedStream.h"
 #include "llvm/Support/KnownBits.h"
 #include "llvm/Support/raw_ostream.h"
@@ -1583,6 +1586,34 @@ static bool isOperationFoldable(User *Usr) {
   return isa<CastInst>(Usr) || isa<BinaryOperator>(Usr) || isa<FreezeInst>(Usr);
 }
 
+/// Judge whether a foldable operation is injective.
+static bool isInjectiveMapping(User *Usr) {
+  assert(isOperationFoldable(Usr) && "Only accept foldable user");
+  const APInt *C;
+  if (auto *BO = dyn_cast<BinaryOperator>(Usr)) {
+    switch (BO->getOpcode()) {
+    case Instruction::Add:
+    case Instruction::Sub:
+    case Instruction::Xor:
+      return true;
+    case Instruction::Mul:
+    case Instruction::Shl:
+    case Instruction::AShr:
+    case Instruction::LShr:
+      // These mappings are injective on specific range.
+      // E.g., (u8)x * 2 is injective if x ∈ R and |R| <= 128
+      // TODO: support such conditional injection judgement with the constant
+      // operand C and the Range given by LVI.
+      return false;
+    default:
+      return false;
+    }
+  }
+  assert((isa<CastInst>(Usr) || isa<FreezeInst>(Usr)) &&
+         "Expected cast or freeze");
+  return true;
+}
+
 // Check if Usr can be simplified to an integer constant when the value of one
 // of its operands Op is an integer constant OpConstVal. If so, return it as an
 // lattice value range with a single element or otherwise return an overdefined
@@ -2104,7 +2135,104 @@ Constant *LazyValueInfo::getPredicateOnEdge(CmpInst::Predicate Pred, Value *V,
   ValueLatticeElement Result =
       getOrCreateImpl().getValueOnEdge(V, FromBB, ToBB, CxtI);
 
-  return getPredicateResult(Pred, C, Result, FromBB->getDataLayout());
+  Constant *Ret = getPredicateResult(Pred, C, Result, FromBB->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)) {
+    bool ValUsesConditionAndMayBeFoldable = false;
+    auto *Condition = SW->getCondition();
+    const bool IsDefault = ToBB == SW->getDefaultDest();
+
+    if (Condition != V) {
+      User *Usr = dyn_cast<User>(V);
+      // Check if Val has Condition as an operand, i.e., Val = f(Condition).
+      ValUsesConditionAndMayBeFoldable =
+          Usr && isOperationFoldable(Usr) && usesOperand(Usr, Condition);
+      if (!ValUsesConditionAndMayBeFoldable)
+        return nullptr;
+      // If the mapping is not injective and the edge is switch->default, we can
+      // infer nothing about this predicate.
+      // E.g., V = x & 1
+      //       switch (x) {
+      //       case 0: break; // f(x) = 0 & 1 = 0
+      //       default: bool flag = (V == 0);
+      //       }
+      // flag == true still hold when x == 4.
+      // We can assert that flag = false only if V = f(x) is injective.
+      if (IsDefault && !isInjectiveMapping(Usr))
+        return nullptr;
+    }
+    // switch's condition must be an integer, and V = f(Cond) is a also integer.
+    assert(isa<ConstantInt>(C) && "Expected integer C");
+    const APInt &CmpInt = cast<ConstantInt>(C)->getValue();
+
+    auto GetValueOnCase = [&](const APInt &CaseVal) -> std::optional<APInt> {
+      return ValUsesConditionAndMayBeFoldable
+                 ? constantFoldUser(cast<User>(V), Condition, CaseVal,
+                                    ToBB->getDataLayout())
+                       .asConstantInteger()
+                 : CaseVal;
+    };
+    // 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 0b23bc174ada7..6bdf19efe7319 100644
--- a/llvm/test/Transforms/CorrelatedValuePropagation/lvi-switch-eq.ll
+++ b/llvm/test/Transforms/CorrelatedValuePropagation/lvi-switch-eq.ll
@@ -18,8 +18,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
@@ -48,8 +47,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
@@ -77,8 +75,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
@@ -105,8 +102,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



More information about the llvm-commits mailing list