[llvm] [ValueTracking] Fold zero compares using conditional non-zero reasoning (PR #192860)

via llvm-commits llvm-commits at lists.llvm.org
Sun Apr 19 10:20:27 PDT 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-analysis

Author: Max Graey (MaxGraey)

<details>
<summary>Changes</summary>

Partially fix: https://github.com/llvm/llvm-project/issues/189320

- Extend `isImpliedCondICmps` in `ValueTracking` with conditional non-zero propagation
- Dedece Expr is never zero under an implied `icmp` and through or/zero-preserving wrappers
- Fold` icmp Pred wrap(Expr), 0` accordingly

---
Full diff: https://github.com/llvm/llvm-project/pull/192860.diff


2 Files Affected:

- (modified) llvm/lib/Analysis/ValueTracking.cpp (+70) 
- (added) llvm/test/Analysis/ValueTracking/implied-condition-known-nonzero.ll (+194) 


``````````diff
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 3227708f9c26f..cc62dbd7235d4 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -875,6 +875,45 @@ 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, recursively looking 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 +9669,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 +9768,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
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/192860


More information about the llvm-commits mailing list