[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