[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