[llvm] [ValueTracking] Fold zero compares using conditional non-zero reasoning (PR #192860)
Max Graey via llvm-commits
llvm-commits at lists.llvm.org
Sun Apr 19 10:20:45 PDT 2026
https://github.com/MaxGraey updated https://github.com/llvm/llvm-project/pull/192860
>From 8588bdecd514a8022c1dda127f57100bc3c6cae1 Mon Sep 17 00:00:00 2001
From: MaxGraey <maxgraey at gmail.com>
Date: Sun, 19 Apr 2026 16:37:59 +0300
Subject: [PATCH] init
---
llvm/lib/Analysis/ValueTracking.cpp | 71 +++++++
.../implied-condition-known-nonzero.ll | 194 ++++++++++++++++++
2 files changed, 265 insertions(+)
create mode 100644 llvm/test/Analysis/ValueTracking/implied-condition-known-nonzero.ll
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 3227708f9c26f..74597cb462b0d 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -875,6 +875,46 @@ static bool isKnownNonZeroFromAssume(const Value *V, const SimplifyQuery &Q) {
return false;
}
+/// Return true if Expr is known to be non-zero under the assumption that
+/// icmp Pred L0, L1 is true. Unlike isKnownNonZero(), this can use
+/// non-zero facts implied by that comparison and propagate them through
+/// a small set of zero-preserving wrappers.
+static bool isKnownNonZeroUnderICmp(const Value *Expr, CmpPredicate Pred,
+ const Value *L0, const Value *L1,
+ unsigned Depth) {
+ if (Depth == MaxAnalysisRecursionDepth)
+ return false;
+ if (Expr == L0)
+ return cmpExcludesZero(Pred, L1);
+ if (Expr == L1)
+ return cmpExcludesZero(ICmpInst::getSwappedCmpPredicate(Pred), L0);
+
+ if (const auto *Op = dyn_cast<Operator>(Expr)) {
+ switch (Op->getOpcode()) {
+ case Instruction::Or:
+ return isKnownNonZeroUnderICmp(Op->getOperand(0), Pred, L0, L1,
+ Depth + 1) ||
+ isKnownNonZeroUnderICmp(Op->getOperand(1), Pred, L0, L1,
+ Depth + 1);
+ case Instruction::SExt:
+ case Instruction::ZExt:
+ case Instruction::Freeze:
+ return isKnownNonZeroUnderICmp(Op->getOperand(0), Pred, L0, L1,
+ Depth + 1);
+ case Instruction::Trunc:
+ if (const auto *TI = dyn_cast<TruncInst>(Op))
+ if (TI->hasNoSignedWrap() || TI->hasNoUnsignedWrap())
+ return isKnownNonZeroUnderICmp(Op->getOperand(0), Pred, L0, L1,
+ Depth + 1);
+ return false;
+ default:
+ return false;
+ }
+ }
+
+ return false;
+}
+
static void computeKnownBitsFromCmp(const Value *V, CmpInst::Predicate Pred,
Value *LHS, Value *RHS, KnownBits &Known,
const SimplifyQuery &Q) {
@@ -9630,6 +9670,22 @@ isImpliedCondCommonOperandWithCR(CmpPredicate LPred, const ConstantRange &LCR,
return std::nullopt;
}
+/// Return the result of "icmp Pred Expr, 0" if Expr is known non-zero.
+static std::optional<bool> getICmpZeroResultIfNonZero(CmpPredicate Pred) {
+ switch (Pred.dropSameSign()) {
+ case ICmpInst::ICMP_ULT:
+ case ICmpInst::ICMP_EQ:
+ case ICmpInst::ICMP_ULE:
+ return false;
+ case ICmpInst::ICMP_UGE:
+ case ICmpInst::ICMP_NE:
+ case ICmpInst::ICMP_UGT:
+ return true;
+ default:
+ return std::nullopt;
+ }
+}
+
/// Return true if LHS implies RHS (expanded to its components as "R0 RPred R1")
/// is true. Return false if LHS implies RHS is false. Otherwise, return
/// std::nullopt if we can't infer anything.
@@ -9713,6 +9769,21 @@ isImpliedCondICmps(CmpPredicate LPred, const Value *L0, const Value *L1,
return true;
}
+ // icmp Pred Expr, 0 -> true/false, if Expr is known non-zero.
+ const Value *RZeroCmpLHS = nullptr;
+ CmpPredicate RZeroCmpPred = RPred;
+ if (match(R1, m_Zero()))
+ RZeroCmpLHS = R0;
+ else if (match(R0, m_Zero())) {
+ RZeroCmpLHS = R1;
+ RZeroCmpPred = ICmpInst::getSwappedCmpPredicate(RZeroCmpPred);
+ }
+ if (RZeroCmpLHS)
+ if (std::optional<bool> Res = getICmpZeroResultIfNonZero(RZeroCmpPred))
+ if (isKnownNonZeroUnderICmp(RZeroCmpLHS, LPred.dropSameSign(), L0, L1,
+ /*Depth=*/0))
+ return *Res;
+
// a - b == NonZero -> a != b
// ptrtoint(a) - ptrtoint(b) == NonZero -> a != b
const APInt *L1C;
diff --git a/llvm/test/Analysis/ValueTracking/implied-condition-known-nonzero.ll b/llvm/test/Analysis/ValueTracking/implied-condition-known-nonzero.ll
new file mode 100644
index 0000000000000..86b3d1e8d859a
--- /dev/null
+++ b/llvm/test/Analysis/ValueTracking/implied-condition-known-nonzero.ll
@@ -0,0 +1,194 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -passes=instsimplify -S %s | FileCheck %s
+
+define i1 @x_nonzero_implies_or_nonzero(i32 %x, i32 %y) {
+; CHECK-LABEL: define i1 @x_nonzero_implies_or_nonzero(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[XNZ:%.*]] = icmp ne i32 [[X]], 0
+; CHECK-NEXT: ret i1 [[XNZ]]
+;
+ %xnz = icmp ne i32 %x, 0
+ %xy = or i32 %x, %y
+ %ornz = icmp ne i32 %xy, 0
+ %r = and i1 %xnz, %ornz
+ ret i1 %r
+}
+
+define i1 @y_nonzero_implies_or_zero_false(i32 %x, i32 %y) {
+; CHECK-LABEL: define i1 @y_nonzero_implies_or_zero_false(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT: ret i1 false
+;
+ %ynz = icmp ne i32 %y, 0
+ %xy = or i32 %x, %y
+ %orz = icmp eq i32 %xy, 0
+ %r = and i1 %ynz, %orz
+ ret i1 %r
+}
+
+define i1 @eq_nonzero_implies_or_nonzero(i32 %x, i32 %y) {
+; CHECK-LABEL: define i1 @eq_nonzero_implies_or_nonzero(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[X42:%.*]] = icmp eq i32 [[X]], 42
+; CHECK-NEXT: ret i1 [[X42]]
+;
+ %x42 = icmp eq i32 %x, 42
+ %xy = or i32 %x, %y
+ %ornz = icmp ne i32 %xy, 0
+ %r = and i1 %x42, %ornz
+ ret i1 %r
+}
+
+define i1 @x_nonzero_implies_or_ugt_zero(i32 %x, i32 %y) {
+; CHECK-LABEL: define i1 @x_nonzero_implies_or_ugt_zero(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[XNZ:%.*]] = icmp ne i32 [[X]], 0
+; CHECK-NEXT: ret i1 [[XNZ]]
+;
+ %xnz = icmp ne i32 %x, 0
+ %xy = or i32 %x, %y
+ %orugt = icmp ugt i32 %xy, 0
+ %r = and i1 %xnz, %orugt
+ ret i1 %r
+}
+
+define i1 @y_nonzero_implies_or_ule_zero_false(i32 %x, i32 %y) {
+; CHECK-LABEL: define i1 @y_nonzero_implies_or_ule_zero_false(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT: ret i1 false
+;
+ %ynz = icmp ne i32 %y, 0
+ %xy = or i32 %x, %y
+ %orule = icmp ule i32 %xy, 0
+ %r = and i1 %ynz, %orule
+ ret i1 %r
+}
+
+define i1 @x_nonzero_implies_or_ult_zero_false(i32 %x, i32 %y) {
+; CHECK-LABEL: define i1 @x_nonzero_implies_or_ult_zero_false(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT: ret i1 false
+;
+ %xnz = icmp ne i32 %x, 0
+ %xy = or i32 %x, %y
+ %orult = icmp ult i32 %xy, 0
+ %r = and i1 %xnz, %orult
+ ret i1 %r
+}
+
+define i1 @y_nonzero_implies_or_uge_zero(i32 %x, i32 %y) {
+; CHECK-LABEL: define i1 @y_nonzero_implies_or_uge_zero(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[YNZ:%.*]] = icmp ne i32 [[Y]], 0
+; CHECK-NEXT: ret i1 [[YNZ]]
+;
+ %ynz = icmp ne i32 %y, 0
+ %xy = or i32 %x, %y
+ %oruge = icmp uge i32 %xy, 0
+ %r = and i1 %ynz, %oruge
+ ret i1 %r
+}
+
+define i1 @x_nonzero_implies_zero_eq_or_false(i32 %x, i32 %y) {
+; CHECK-LABEL: define i1 @x_nonzero_implies_zero_eq_or_false(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT: ret i1 false
+;
+ %xnz = icmp ne i32 %x, 0
+ %xy = or i32 %x, %y
+ %eq = icmp eq i32 0, %xy
+ %r = and i1 %xnz, %eq
+ ret i1 %r
+}
+
+define i1 @x_nonzero_implies_zero_ne_or(i32 %x, i32 %y) {
+; CHECK-LABEL: define i1 @x_nonzero_implies_zero_ne_or(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[XNZ:%.*]] = icmp ne i32 [[X]], 0
+; CHECK-NEXT: ret i1 [[XNZ]]
+;
+ %xnz = icmp ne i32 %x, 0
+ %xy = or i32 %x, %y
+ %ne = icmp ne i32 0, %xy
+ %r = and i1 %xnz, %ne
+ ret i1 %r
+}
+
+define i1 @x_nonzero_implies_zero_ne_swap_or(i32 %x, i32 %y) {
+; CHECK-LABEL: define i1 @x_nonzero_implies_zero_ne_swap_or(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[XNZ:%.*]] = icmp ne i32 0, [[X]]
+; CHECK-NEXT: ret i1 [[XNZ]]
+;
+ %xnz = icmp ne i32 0, %x
+ %xy = or i32 %x, %y
+ %ne = icmp ne i32 %xy, 0
+ %r = and i1 %xnz, %ne
+ ret i1 %r
+}
+
+define i1 @y_nonzero_implies_zero_uge_or_false(i32 %x, i32 %y) {
+; CHECK-LABEL: define i1 @y_nonzero_implies_zero_uge_or_false(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT: ret i1 false
+;
+ %ynz = icmp ne i32 %y, 0
+ %xy = or i32 %x, %y
+ %uge = icmp uge i32 0, %xy
+ %r = and i1 %ynz, %uge
+ ret i1 %r
+}
+
+define i1 @y_nonzero_implies_zero_ule_or(i32 %x, i32 %y) {
+; CHECK-LABEL: define i1 @y_nonzero_implies_zero_ule_or(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[YNZ:%.*]] = icmp ne i32 [[Y]], 0
+; CHECK-NEXT: ret i1 [[YNZ]]
+;
+ %ynz = icmp ne i32 %y, 0
+ %xy = or i32 %x, %y
+ %ule = icmp ule i32 0, %xy
+ %r = and i1 %ynz, %ule
+ ret i1 %r
+}
+
+define i1 @x_nonzero_implies_zext_or_nonzero(i32 %x, i32 %y) {
+; CHECK-LABEL: define i1 @x_nonzero_implies_zext_or_nonzero(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[XNZ:%.*]] = icmp ne i32 [[X]], 0
+; CHECK-NEXT: ret i1 [[XNZ]]
+;
+ %xnz = icmp ne i32 %x, 0
+ %xy = or i32 %x, %y
+ %xy.zext = zext i32 %xy to i64
+ %ornz = icmp ne i64 %xy.zext, 0
+ %r = and i1 %xnz, %ornz
+ ret i1 %r
+}
+
+define i1 @y_nonzero_implies_sext_or_ugt_zero(i32 %x, i32 %y) {
+; CHECK-LABEL: define i1 @y_nonzero_implies_sext_or_ugt_zero(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[YNZ:%.*]] = icmp ne i32 [[Y]], 0
+; CHECK-NEXT: ret i1 [[YNZ]]
+;
+ %ynz = icmp ne i32 %y, 0
+ %xy = or i32 %x, %y
+ %xy.sext = sext i32 %xy to i64
+ %orugt = icmp ugt i64 %xy.sext, 0
+ %r = and i1 %ynz, %orugt
+ ret i1 %r
+}
+
+define i1 @x_slt_zero_implies_or_nonzero(i32 %x, i32 %y) {
+; CHECK-LABEL: define i1 @x_slt_zero_implies_or_nonzero(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[XNEG:%.*]] = icmp slt i32 [[X]], 0
+; CHECK-NEXT: ret i1 [[XNEG]]
+;
+ %xneg = icmp slt i32 %x, 0
+ %xy = or i32 %x, %y
+ %ornz = icmp ne i32 %xy, 0
+ %r = and i1 %xneg, %ornz
+ ret i1 %r
+}
More information about the llvm-commits
mailing list