[llvm] [InstCombine] Recognize X - 1 as nonnegative when X is positive (PR #223168)

via llvm-commits llvm-commits at lists.llvm.org
Sat Sep 12 12:39:53 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-analysis

@llvm/pr-subscribers-llvm-transforms

Author: Addmisol (addmisol)

<details>
<summary>Changes</summary>

## Summary

Fixes #<!-- -->221943

This patch teaches `computeKnownBitsAddSub` to recognize that `X - 1` is nonnegative when `X` is known to be positive (nonnegative AND nonzero).

Previously, InstCombine would fail to fold the copysign idiom in cases like:

%mag = add nsw i32 %magx, -1
%positive = icmp sgt i32 %magx, 0
call void @<!-- -->llvm.assume(i1 %positive)
; ... copysign bit manipulation pattern ...

The assumption %magx > 0 implies %magx >= 1, so %mag = %magx - 1 >= 0. However, the known bits analysis was not propagating this nonnegativity, causing isKnownNonNegative() to return false and blocking the copysign fold.

The fix handles both:
- sub nsw X, 1 - direct subtraction
- add nsw X, -1 - canonicalized form (matches m_AllOnes())

Alive2 proof: https://alive2.llvm.org/ce/z/QWeNKE

### Test plan

- Added @<!-- -->copysign_idiom_sub1_positive test case to bitcast.ll
- Verified existing copysign_idiom_commuted and related tests still pass
- Ran FileCheck on bitcast.ll, assume.ll, assume-i32.ll

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


3 Files Affected:

- (modified) llvm/lib/Analysis/ValueTracking.cpp (+19-5) 
- (modified) llvm/test/Transforms/InstCombine/bitcast.ll (+22) 
- (modified) llvm/test/Transforms/InstCombine/divceil.ll (+1-1) 


``````````diff
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 1580571a01451..74c078867ee14 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -531,11 +531,25 @@ static void computeKnownBitsAddSub(bool Add, const Value *Op0, const Value *Op1,
   computeKnownBits(Op0, DemandedElts, Known2, Q, Depth + 1);
   KnownOut = KnownBits::computeForAddSub(Add, NSW, NUW, Known2, KnownOut);
 
-  if (!Add && NSW && !KnownOut.isNonNegative() &&
-      (isImpliedByDomCondition(ICmpInst::ICMP_SLE, Op1, Op0, Q.CxtI, Q.DL)
-           .value_or(false) ||
-       match(Op1, m_c_SMin(m_Specific(Op0), m_Value()))))
-    KnownOut.makeNonNegative();
+  if (NSW && !KnownOut.isNonNegative()) {
+    if (!Add) {
+      // X - Y is nonnegative if Y <= X (signed).
+      if (isImpliedByDomCondition(ICmpInst::ICMP_SLE, Op1, Op0, Q.CxtI, Q.DL)
+              .value_or(false) ||
+          match(Op1, m_c_SMin(m_Specific(Op0), m_Value())))
+        KnownOut.makeNonNegative();
+      // X - 1 is nonnegative if X is known positive (X > 0 implies X >= 1).
+      else if (match(Op1, m_One()) && Known2.isNonNegative() &&
+               (Known2.isNonZero() || isKnownNonZero(Op0, Q, Depth + 1)))
+        KnownOut.makeNonNegative();
+    } else {
+      // X + (-1) is nonnegative if X is known positive (X > 0 implies X >= 1).
+      // This handles "add nsw X, -1" which is semantically "X - 1".
+      if (match(Op1, m_AllOnes()) && Known2.isNonNegative() &&
+          (Known2.isNonZero() || isKnownNonZero(Op0, Q, Depth + 1)))
+        KnownOut.makeNonNegative();
+    }
+  }
 
   if (Add)
     // Try to match lerp pattern and combine results
diff --git a/llvm/test/Transforms/InstCombine/bitcast.ll b/llvm/test/Transforms/InstCombine/bitcast.ll
index a81eb5000f63c..34e75a7e90951 100644
--- a/llvm/test/Transforms/InstCombine/bitcast.ll
+++ b/llvm/test/Transforms/InstCombine/bitcast.ll
@@ -903,3 +903,25 @@ end:                                        ; preds = %unreachable, %entry
   %3 = extractelement <2 x i16> %2, i64 0
   ret i16 %3
 }
+
+; Issue #221943: X - 1 should be nonnegative when X > 0.
+; The assume that %magx > 0 should allow InstCombine to recognize that
+; %mag = %magx - 1 is nonnegative, enabling the copysign idiom fold.
+define float @copysign_idiom_sub1_positive(float %x, i32 %magx) {
+; CHECK-LABEL: @copysign_idiom_sub1_positive(
+; CHECK-NEXT:    [[MAG:%.*]] = add nsw i32 [[MAGX:%.*]], -1
+; CHECK-NEXT:    [[POSITIVE:%.*]] = icmp sgt i32 [[MAGX]], 0
+; CHECK-NEXT:    call void @llvm.assume(i1 [[POSITIVE]])
+; CHECK-NEXT:    [[TMP1:%.*]] = bitcast i32 [[MAG]] to float
+; CHECK-NEXT:    [[Y:%.*]] = call float @llvm.copysign.f32(float [[TMP1]], float [[X:%.*]])
+; CHECK-NEXT:    ret float [[Y]]
+;
+  %mag = add nsw i32 %magx, -1
+  %positive = icmp sgt i32 %magx, 0
+  call void @llvm.assume(i1 %positive)
+  %bits = bitcast float %x to i32
+  %sign = and i32 %bits, -2147483648
+  %res = or i32 %mag, %sign
+  %y = bitcast i32 %res to float
+  ret float %y
+}
diff --git a/llvm/test/Transforms/InstCombine/divceil.ll b/llvm/test/Transforms/InstCombine/divceil.ll
index c9f3ba750d7ec..e43d6c28fcd27 100644
--- a/llvm/test/Transforms/InstCombine/divceil.ll
+++ b/llvm/test/Transforms/InstCombine/divceil.ll
@@ -138,7 +138,7 @@ define i32 @divceil_i16_var_divisor_zext(i16 range(i16 0, 101) %x, i16 range(i16
 ; CHECK-NEXT:    [[TMP1:%.*]] = add nsw i16 [[Y:%.*]], -1
 ; CHECK-NEXT:    [[TMP2:%.*]] = add nuw nsw i16 [[X:%.*]], [[TMP1]]
 ; CHECK-NEXT:    [[TMP3:%.*]] = udiv i16 [[TMP2]], [[Y]]
-; CHECK-NEXT:    [[RESULT:%.*]] = zext i16 [[TMP3]] to i32
+; CHECK-NEXT:    [[RESULT:%.*]] = zext nneg i16 [[TMP3]] to i32
 ; CHECK-NEXT:    ret i32 [[RESULT]]
 ;
   %q = udiv i16 %x, %y

``````````

</details>


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


More information about the llvm-commits mailing list