[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