[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