[llvm] [InstCombine] Use samesign constraints in unsigned known-bits folds (PR #209097)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Jul 13 00:17:30 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-transforms
Author: Kiva (imkiva)
<details>
<summary>Changes</summary>
For a non-poison unsigned `icmp samesign`, a known sign bit on either operand also constrains the sign bit of the other operand. Propagate this information between the temporary KnownBits values so existing range folds can simplify comparisons at the endpoints of either signed half of the integer range.
Also preserve `samesign` when canonicalizing comparison strictness, provided that adjusting the constant does not change its sign bit.
------
Although propagating the sign constraint is sound for any non-poison icmp samesign, applying it to signed and equality predicates is unnecessarily aggressive here. `foldICmpUsingKnownBits()` may refine such a comparison in isolation before higher-level select or logical combines can recognize a better canonical form. This caused an instruction-count regression:
```
%masked = and i32 %a, -1073741825
%cmp1 = icmp eq i32 %masked, 0
%cmp2 = icmp samesign sgt i32 %a, -1
%result = select i1 %cmp1, i1 true, i1 %cmp2
ret i1 %result
```
Without the restriction, the known-bits fold observes that a non-poison %cmp2 constrains %a to be negative and folds %cmp2 to false. The surrounding select then becomes:
```
%masked = and i32 %a, -1073741825
%cmp1 = icmp eq i32 %masked, 0
ret i1 %cmp1
```
The existing canonical result is the cheaper one-instruction sign test:
```
%cmp = icmp sgt i32 %a, -1
ret i1 %cmp
```
Therefore I restricted sign propagating to only `I.hasSameSign() && I.isUnsigned()` to avoid such regression.
---
Full diff: https://github.com/llvm/llvm-project/pull/209097.diff
5 Files Affected:
- (modified) llvm/include/llvm/Analysis/ValueTracking.h (+2)
- (modified) llvm/lib/Analysis/ValueTracking.cpp (+14-3)
- (modified) llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp (+22-2)
- (added) llvm/test/Transforms/InstCombine/icmp-samesign-known-bits.ll (+188)
- (modified) llvm/test/Transforms/LoopVectorize/runtime-check.ll (+1-1)
``````````diff
diff --git a/llvm/include/llvm/Analysis/ValueTracking.h b/llvm/include/llvm/Analysis/ValueTracking.h
index c87e39bca8215..f622fd2ad8491 100644
--- a/llvm/include/llvm/Analysis/ValueTracking.h
+++ b/llvm/include/llvm/Analysis/ValueTracking.h
@@ -864,6 +864,8 @@ LLVM_ABI bool mustExecuteUBIfPoisonOnPathTo(Instruction *Root,
/// form with the strictness flipped predicate. Return the new predicate and
/// corresponding constant RHS if possible. Otherwise return std::nullopt.
/// E.g., (icmp sgt X, 0) -> (icmp sle X, 1).
+/// For a samesign predicate, fail if adjusting the constant would change its
+/// sign bit, because that would change the comparison's poison domain.
LLVM_ABI std::optional<std::pair<CmpPredicate, Constant *>>
getFlippedStrictnessPredicateAndConstant(CmpPredicate Pred, Constant *C);
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index efc14f9a639da..4307c92c30172 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -8925,8 +8925,18 @@ llvm::getFlippedStrictnessPredicateAndConstant(CmpPredicate Pred, Constant *C) {
// Check if the constant operand can be safely incremented/decremented
// without overflowing/underflowing.
- auto ConstantIsOk = [WillIncrement, IsSigned](ConstantInt *C) {
- return WillIncrement ? !C->isMaxValue(IsSigned) : !C->isMinValue(IsSigned);
+ auto ConstantIsOk = [Pred, WillIncrement, IsSigned](ConstantInt *C) {
+ if (WillIncrement ? C->isMaxValue(IsSigned) : C->isMinValue(IsSigned))
+ return false;
+
+ if (!Pred.hasSameSign())
+ return true;
+
+ // Preserve samesign only if adjusting the constant does not change its
+ // sign bit, and therefore does not change the poison domain.
+ const APInt &Value = C->getValue();
+ APInt Adjusted = WillIncrement ? Value + 1 : Value - 1;
+ return Value.isNegative() == Adjusted.isNegative();
};
Constant *SafeReplacementConstant = nullptr;
@@ -8974,7 +8984,8 @@ llvm::getFlippedStrictnessPredicateAndConstant(CmpPredicate Pred, Constant *C) {
C = Constant::replaceUndefsWith(C, SafeReplacementConstant);
}
- CmpInst::Predicate NewPred = CmpInst::getFlippedStrictnessPredicate(Pred);
+ CmpPredicate NewPred(CmpInst::getFlippedStrictnessPredicate(Pred),
+ Pred.hasSameSign());
// Increment or decrement the constant.
Constant *OneOrNegOne = ConstantInt::get(Type, WillIncrement ? 1 : -1, true);
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index 42c2983034e22..0a73791b0d8ed 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -6991,6 +6991,22 @@ Instruction *InstCombinerImpl::foldICmpUsingKnownBits(ICmpInst &I) {
return &I;
}
+ // If an unsigned samesign comparison is not poison, both operands have the
+ // same sign bit. Propagate a known sign bit between the temporary KnownBits
+ // values so the existing range folds can use that constraint.
+ if (I.hasSameSign() && I.isUnsigned()) {
+ auto PropagateSignBit = [](const KnownBits &From, KnownBits &To) {
+ if (To.isNegative() || To.isNonNegative())
+ return;
+ if (From.isNegative())
+ To.makeNegative();
+ else if (From.isNonNegative())
+ To.makeNonNegative();
+ };
+ PropagateSignBit(Op0Known, Op1Known);
+ PropagateSignBit(Op1Known, Op0Known);
+ }
+
if (!isa<Constant>(Op0) && Op0Known.isConstant())
return new ICmpInst(
Pred, ConstantExpr::getIntegerValue(Ty, Op0Known.getConstant()), Op1);
@@ -7276,11 +7292,15 @@ static ICmpInst *canonicalizeCmpWithConstant(ICmpInst &I) {
if (!Op1C)
return nullptr;
- auto FlippedStrictness = getFlippedStrictnessPredicateAndConstant(Pred, Op1C);
+ auto FlippedStrictness =
+ getFlippedStrictnessPredicateAndConstant(I.getCmpPredicate(), Op1C);
if (!FlippedStrictness)
return nullptr;
- return new ICmpInst(FlippedStrictness->first, Op0, FlippedStrictness->second);
+ auto *NewCmp =
+ new ICmpInst(FlippedStrictness->first, Op0, FlippedStrictness->second);
+ NewCmp->setSameSign(FlippedStrictness->first.hasSameSign());
+ return NewCmp;
}
/// If we have a comparison with a non-canonical predicate, if we can update
diff --git a/llvm/test/Transforms/InstCombine/icmp-samesign-known-bits.ll b/llvm/test/Transforms/InstCombine/icmp-samesign-known-bits.ll
new file mode 100644
index 0000000000000..b3f075bc297fb
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/icmp-samesign-known-bits.ll
@@ -0,0 +1,188 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+define i1 @ugt_smax_minus_one(i8 %x) {
+; CHECK-LABEL: define i1 @ugt_smax_minus_one(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp eq i8 [[X]], 127
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %cmp = icmp samesign ugt i8 %x, 126
+ ret i1 %cmp
+}
+
+define i1 @ult_smax(i8 %x) {
+; CHECK-LABEL: define i1 @ult_smax(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp ne i8 [[X]], 127
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %cmp = icmp samesign ult i8 %x, 127
+ ret i1 %cmp
+}
+
+define i1 @ugt_smax(i8 %x) {
+; CHECK-LABEL: define i1 @ugt_smax(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: ret i1 false
+;
+ %cmp = icmp samesign ugt i8 %x, 127
+ ret i1 %cmp
+}
+
+define i1 @ule_smax(i8 %x) {
+; CHECK-LABEL: define i1 @ule_smax(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: ret i1 true
+;
+ %cmp = icmp samesign ule i8 %x, 127
+ ret i1 %cmp
+}
+
+define i1 @uge_smax(i8 %x) {
+; CHECK-LABEL: define i1 @uge_smax(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp eq i8 [[X]], 127
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %cmp = icmp samesign uge i8 %x, 127
+ ret i1 %cmp
+}
+
+define i1 @uge_non_endpoint(i8 %x) {
+; CHECK-LABEL: define i1 @uge_non_endpoint(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp samesign ugt i8 [[X]], 41
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %cmp = icmp samesign uge i8 %x, 42
+ ret i1 %cmp
+}
+
+define <2 x i1> @ule_non_endpoint_vec(<2 x i8> %x) {
+; CHECK-LABEL: define <2 x i1> @ule_non_endpoint_vec(
+; CHECK-SAME: <2 x i8> [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp samesign ult <2 x i8> [[X]], splat (i8 43)
+; CHECK-NEXT: ret <2 x i1> [[CMP]]
+;
+ %cmp = icmp samesign ule <2 x i8> %x, splat (i8 42)
+ ret <2 x i1> %cmp
+}
+
+define i1 @ugt_zero(i8 %x) {
+; CHECK-LABEL: define i1 @ugt_zero(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp ne i8 [[X]], 0
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %cmp = icmp samesign ugt i8 %x, 0
+ ret i1 %cmp
+}
+
+define i1 @ult_zero(i8 %x) {
+; CHECK-LABEL: define i1 @ult_zero(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: ret i1 false
+;
+ %cmp = icmp samesign ult i8 %x, 0
+ ret i1 %cmp
+}
+
+define i1 @uge_smin(i8 %x) {
+; CHECK-LABEL: define i1 @uge_smin(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: ret i1 true
+;
+ %cmp = icmp samesign uge i8 %x, -128
+ ret i1 %cmp
+}
+
+define i1 @ugt_negative_endpoint(i8 %x) {
+; CHECK-LABEL: define i1 @ugt_negative_endpoint(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp eq i8 [[X]], -1
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %cmp = icmp samesign ugt i8 %x, -2
+ ret i1 %cmp
+}
+
+define i1 @ult_negative_endpoint(i8 %x) {
+; CHECK-LABEL: define i1 @ult_negative_endpoint(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp eq i8 [[X]], -128
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %cmp = icmp samesign ult i8 %x, -127
+ ret i1 %cmp
+}
+
+define <2 x i1> @ugt_smax_minus_one_vec(<2 x i8> %x) {
+; CHECK-LABEL: define <2 x i1> @ugt_smax_minus_one_vec(
+; CHECK-SAME: <2 x i8> [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp eq <2 x i8> [[X]], splat (i8 127)
+; CHECK-NEXT: ret <2 x i1> [[CMP]]
+;
+ %cmp = icmp samesign ugt <2 x i8> %x, splat (i8 126)
+ ret <2 x i1> %cmp
+}
+
+define i1 @ugt_smax_minus_one_no_samesign(i8 %x) {
+; CHECK-LABEL: define i1 @ugt_smax_minus_one_no_samesign(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp ugt i8 [[X]], 126
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %cmp = icmp ugt i8 %x, 126
+ ret i1 %cmp
+}
+
+define i1 @ule_smax_no_samesign(i8 %x) {
+; CHECK-LABEL: define i1 @ule_smax_no_samesign(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i8 [[X]], -1
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %cmp = icmp ule i8 %x, 127
+ ret i1 %cmp
+}
+
+define i1 @sge_zero_sign_crossing(i8 %x) {
+; CHECK-LABEL: define i1 @sge_zero_sign_crossing(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp samesign sge i8 [[X]], 0
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %cmp = icmp samesign sge i8 %x, 0
+ ret i1 %cmp
+}
+
+define i1 @sle_minus_one_sign_crossing(i8 %x) {
+; CHECK-LABEL: define i1 @sle_minus_one_sign_crossing(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp samesign sle i8 [[X]], -1
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %cmp = icmp samesign sle i8 %x, -1
+ ret i1 %cmp
+}
+
+define i1 @sge_non_endpoint(i8 %x) {
+; CHECK-LABEL: define i1 @sge_non_endpoint(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp samesign sgt i8 [[X]], 41
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %cmp = icmp samesign sge i8 %x, 42
+ ret i1 %cmp
+}
+
+define <2 x i1> @uge_mixed_sign_crossing_vec(<2 x i8> %x) {
+; CHECK-LABEL: define <2 x i1> @uge_mixed_sign_crossing_vec(
+; CHECK-SAME: <2 x i8> [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp samesign uge <2 x i8> [[X]], <i8 42, i8 -128>
+; CHECK-NEXT: ret <2 x i1> [[CMP]]
+;
+ %cmp = icmp samesign uge <2 x i8> %x, <i8 42, i8 -128>
+ ret <2 x i1> %cmp
+}
diff --git a/llvm/test/Transforms/LoopVectorize/runtime-check.ll b/llvm/test/Transforms/LoopVectorize/runtime-check.ll
index d7a747bfe6d66..82ff133f2c04f 100644
--- a/llvm/test/Transforms/LoopVectorize/runtime-check.ll
+++ b/llvm/test/Transforms/LoopVectorize/runtime-check.ll
@@ -478,7 +478,7 @@ define void @test_scev_check_mul_add_expansion(ptr %out, ptr %in, i32 %len, i32
; CHECK-NEXT: entry:
; CHECK-NEXT: [[PRE_1:%.*]] = icmp samesign ugt i32 [[D:%.*]], 5
; CHECK-NEXT: tail call void @llvm.assume(i1 [[PRE_1]])
-; CHECK-NEXT: [[PRE_2:%.*]] = icmp ult i32 [[D]], 7
+; CHECK-NEXT: [[PRE_2:%.*]] = icmp samesign ult i32 [[D]], 7
; CHECK-NEXT: tail call void @llvm.assume(i1 [[PRE_2]])
; CHECK-NEXT: [[PRE_3:%.*]] = icmp slt i32 [[D]], [[LEN:%.*]]
; CHECK-NEXT: tail call void @llvm.assume(i1 [[PRE_3]])
``````````
</details>
https://github.com/llvm/llvm-project/pull/209097
More information about the llvm-commits
mailing list