[llvm] [InstCombine] Add a >= b - a<=b to scmp and ucmp folds (PR #164009)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Oct 23 07:03:27 PDT 2025
https://github.com/AZero13 updated https://github.com/llvm/llvm-project/pull/164009
>From 3bce5f7f7c1195d901a7b489aedbfc8bd4a85512 Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Fri, 17 Oct 2025 15:00:42 -0400
Subject: [PATCH 1/3] Pre-commit test (NFC)
---
.../sext-a-lt-b-plus-zext-a-gt-b-to-uscmp.ll | 190 ++++++++++++++++++
1 file changed, 190 insertions(+)
diff --git a/llvm/test/Transforms/InstCombine/sext-a-lt-b-plus-zext-a-gt-b-to-uscmp.ll b/llvm/test/Transforms/InstCombine/sext-a-lt-b-plus-zext-a-gt-b-to-uscmp.ll
index 02ae7ce82f13c..53982601fb7d4 100644
--- a/llvm/test/Transforms/InstCombine/sext-a-lt-b-plus-zext-a-gt-b-to-uscmp.ll
+++ b/llvm/test/Transforms/InstCombine/sext-a-lt-b-plus-zext-a-gt-b-to-uscmp.ll
@@ -182,3 +182,193 @@ define i8 @signed_add_neg5(i32 %a, i32 %b) {
%r = add i8 %lt8, %gt8
ret i8 %r
}
+
+; sext(A s<= B) + zext(A s>= B) => scmp(A, B)
+define i8 @signed_add_ge_le(i32 %a, i32 %b) {
+; CHECK-LABEL: define i8 @signed_add_ge_le(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT: [[LE:%.*]] = icmp sle i32 [[A]], [[B]]
+; CHECK-NEXT: [[LE8:%.*]] = sext i1 [[LE]] to i8
+; CHECK-NEXT: [[GE:%.*]] = icmp sge i32 [[A]], [[B]]
+; CHECK-NEXT: [[GE8:%.*]] = zext i1 [[GE]] to i8
+; CHECK-NEXT: [[R:%.*]] = add nsw i8 [[LE8]], [[GE8]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %le = icmp sle i32 %a, %b
+ %le8 = sext i1 %le to i8
+ %ge = icmp sge i32 %a, %b
+ %ge8 = zext i1 %ge to i8
+ %r = add i8 %le8, %ge8
+ ret i8 %r
+}
+
+; Unsigned version of >= and <=
+define i8 @unsigned_add_ge_le(i32 %a, i32 %b) {
+; CHECK-LABEL: define i8 @unsigned_add_ge_le(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT: [[LE:%.*]] = icmp ule i32 [[A]], [[B]]
+; CHECK-NEXT: [[LE8:%.*]] = sext i1 [[LE]] to i8
+; CHECK-NEXT: [[GE:%.*]] = icmp uge i32 [[A]], [[B]]
+; CHECK-NEXT: [[GE8:%.*]] = zext i1 [[GE]] to i8
+; CHECK-NEXT: [[R:%.*]] = add nsw i8 [[LE8]], [[GE8]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %le = icmp ule i32 %a, %b
+ %le8 = sext i1 %le to i8
+ %ge = icmp uge i32 %a, %b
+ %ge8 = zext i1 %ge to i8
+ %r = add i8 %le8, %ge8
+ ret i8 %r
+}
+
+; zext(A s>= B) - zext(A s<= B) => scmp(A, B)
+define i8 @signed_sub_ge_le(i32 %a, i32 %b) {
+; CHECK-LABEL: define i8 @signed_sub_ge_le(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT: [[LE:%.*]] = icmp sle i32 [[A]], [[B]]
+; CHECK-NEXT: [[LE8_NEG:%.*]] = sext i1 [[LE]] to i8
+; CHECK-NEXT: [[GE:%.*]] = icmp sge i32 [[A]], [[B]]
+; CHECK-NEXT: [[GE8:%.*]] = zext i1 [[GE]] to i8
+; CHECK-NEXT: [[R:%.*]] = add nsw i8 [[LE8_NEG]], [[GE8]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %le = icmp sle i32 %a, %b
+ %le8 = zext i1 %le to i8
+ %ge = icmp sge i32 %a, %b
+ %ge8 = zext i1 %ge to i8
+ %r = sub i8 %ge8, %le8
+ ret i8 %r
+}
+
+; Unsigned version of >= and <= subtraction
+define i8 @unsigned_sub_ge_le(i32 %a, i32 %b) {
+; CHECK-LABEL: define i8 @unsigned_sub_ge_le(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT: [[LE:%.*]] = icmp ule i32 [[A]], [[B]]
+; CHECK-NEXT: [[LE8_NEG:%.*]] = sext i1 [[LE]] to i8
+; CHECK-NEXT: [[GE:%.*]] = icmp uge i32 [[A]], [[B]]
+; CHECK-NEXT: [[GE8:%.*]] = zext i1 [[GE]] to i8
+; CHECK-NEXT: [[R:%.*]] = add nsw i8 [[LE8_NEG]], [[GE8]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %le = icmp ule i32 %a, %b
+ %le8 = zext i1 %le to i8
+ %ge = icmp uge i32 %a, %b
+ %ge8 = zext i1 %ge to i8
+ %r = sub i8 %ge8, %le8
+ ret i8 %r
+}
+
+; Constant canonicalization: (a > 4) - (a < 6) => scmp(a, 5)
+define i8 @signed_sub_const_canonicalization(i32 %a) {
+; CHECK-LABEL: define i8 @signed_sub_const_canonicalization(
+; CHECK-SAME: i32 [[A:%.*]]) {
+; CHECK-NEXT: [[LT:%.*]] = icmp slt i32 [[A]], 6
+; CHECK-NEXT: [[LT8_NEG:%.*]] = sext i1 [[LT]] to i8
+; CHECK-NEXT: [[GT:%.*]] = icmp sgt i32 [[A]], 4
+; CHECK-NEXT: [[GT8:%.*]] = zext i1 [[GT]] to i8
+; CHECK-NEXT: [[R:%.*]] = add nsw i8 [[LT8_NEG]], [[GT8]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %lt = icmp slt i32 %a, 6
+ %lt8 = zext i1 %lt to i8
+ %gt = icmp sgt i32 %a, 4
+ %gt8 = zext i1 %gt to i8
+ %r = sub i8 %gt8, %lt8
+ ret i8 %r
+}
+
+; Constant canonicalization: (a >= 5) - (a <= 5) => scmp(a, 5)
+define i8 @signed_sub_const_canonicalization2(i32 %a) {
+; CHECK-LABEL: define i8 @signed_sub_const_canonicalization2(
+; CHECK-SAME: i32 [[A:%.*]]) {
+; CHECK-NEXT: [[LE:%.*]] = icmp slt i32 [[A]], 6
+; CHECK-NEXT: [[LE8_NEG:%.*]] = sext i1 [[LE]] to i8
+; CHECK-NEXT: [[GE:%.*]] = icmp sgt i32 [[A]], 4
+; CHECK-NEXT: [[GE8:%.*]] = zext i1 [[GE]] to i8
+; CHECK-NEXT: [[R:%.*]] = add nsw i8 [[LE8_NEG]], [[GE8]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %le = icmp sle i32 %a, 5
+ %le8 = zext i1 %le to i8
+ %ge = icmp sge i32 %a, 5
+ %ge8 = zext i1 %ge to i8
+ %r = sub i8 %ge8, %le8
+ ret i8 %r
+}
+
+; Unsigned constant canonicalization: (a > 4) - (a < 6) => ucmp(a, 5)
+define i8 @unsigned_sub_const_canonicalization(i32 %a) {
+; CHECK-LABEL: define i8 @unsigned_sub_const_canonicalization(
+; CHECK-SAME: i32 [[A:%.*]]) {
+; CHECK-NEXT: [[LT:%.*]] = icmp ult i32 [[A]], 6
+; CHECK-NEXT: [[LT8_NEG:%.*]] = sext i1 [[LT]] to i8
+; CHECK-NEXT: [[GT:%.*]] = icmp ugt i32 [[A]], 4
+; CHECK-NEXT: [[GT8:%.*]] = zext i1 [[GT]] to i8
+; CHECK-NEXT: [[R:%.*]] = add nsw i8 [[LT8_NEG]], [[GT8]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %lt = icmp ult i32 %a, 6
+ %lt8 = zext i1 %lt to i8
+ %gt = icmp ugt i32 %a, 4
+ %gt8 = zext i1 %gt to i8
+ %r = sub i8 %gt8, %lt8
+ ret i8 %r
+}
+
+; Constant canonicalization with >= and <=: (a >= 5) - (a <= 5) => scmp(a, 5)
+define i8 @signed_sub_const_canonicalization_ge_le(i32 %a) {
+; CHECK-LABEL: define i8 @signed_sub_const_canonicalization_ge_le(
+; CHECK-SAME: i32 [[A:%.*]]) {
+; CHECK-NEXT: [[LE:%.*]] = icmp slt i32 [[A]], 6
+; CHECK-NEXT: [[LE8_NEG:%.*]] = sext i1 [[LE]] to i8
+; CHECK-NEXT: [[GE:%.*]] = icmp sgt i32 [[A]], 4
+; CHECK-NEXT: [[GE8:%.*]] = zext i1 [[GE]] to i8
+; CHECK-NEXT: [[R:%.*]] = add nsw i8 [[LE8_NEG]], [[GE8]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %le = icmp sle i32 %a, 5
+ %le8 = zext i1 %le to i8
+ %ge = icmp sge i32 %a, 5
+ %ge8 = zext i1 %ge to i8
+ %r = sub i8 %ge8, %le8
+ ret i8 %r
+}
+
+; More constant canonicalization: (a > 2) - (a < 4) => scmp(a, 3)
+define i8 @signed_sub_const_canonicalization3(i32 %a) {
+; CHECK-LABEL: define i8 @signed_sub_const_canonicalization3(
+; CHECK-SAME: i32 [[A:%.*]]) {
+; CHECK-NEXT: [[LT:%.*]] = icmp slt i32 [[A]], 4
+; CHECK-NEXT: [[LT8_NEG:%.*]] = sext i1 [[LT]] to i8
+; CHECK-NEXT: [[GT:%.*]] = icmp sgt i32 [[A]], 2
+; CHECK-NEXT: [[GT8:%.*]] = zext i1 [[GT]] to i8
+; CHECK-NEXT: [[R:%.*]] = add nsw i8 [[LT8_NEG]], [[GT8]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %lt = icmp slt i32 %a, 4
+ %lt8 = zext i1 %lt to i8
+ %gt = icmp sgt i32 %a, 2
+ %gt8 = zext i1 %gt to i8
+ %r = sub i8 %gt8, %lt8
+ ret i8 %r
+}
+
+; Negative test: constants that are more than one apart - should NOT canonicalize
+define i8 @signed_sub_const_no_canonicalization(i32 %a) {
+; CHECK-LABEL: define i8 @signed_sub_const_no_canonicalization(
+; CHECK-SAME: i32 [[A:%.*]]) {
+; CHECK-NEXT: [[LT:%.*]] = icmp slt i32 [[A]], 10
+; CHECK-NEXT: [[LT8_NEG:%.*]] = sext i1 [[LT]] to i8
+; CHECK-NEXT: [[GT:%.*]] = icmp sgt i32 [[A]], 4
+; CHECK-NEXT: [[GT8:%.*]] = zext i1 [[GT]] to i8
+; CHECK-NEXT: [[R:%.*]] = add nsw i8 [[LT8_NEG]], [[GT8]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %lt = icmp slt i32 %a, 10
+ %lt8 = zext i1 %lt to i8
+ %gt = icmp sgt i32 %a, 4
+ %gt8 = zext i1 %gt to i8
+ %r = sub i8 %gt8, %lt8
+ ret i8 %r
+}
>From 859dc9e2371b4c5e0649d3114b1110bac5e31e53 Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Fri, 17 Oct 2025 16:06:06 -0400
Subject: [PATCH 2/3] [InstCombine] Add a >= b - a<=b to scmp and ucmp folds
https://alive2.llvm.org/ce/z/QiMA_i
---
.../InstCombine/InstCombineAddSub.cpp | 24 +++++++++++++------
.../sext-a-lt-b-plus-zext-a-gt-b-to-uscmp.ll | 24 ++++---------------
2 files changed, 21 insertions(+), 27 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
index 9bee523c7b7e5..15b43009b3ebb 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
@@ -1660,6 +1660,7 @@ Instruction *InstCombinerImpl::visitAdd(BinaryOperator &I) {
return replaceInstUsesWith(I, Constant::getNullValue(I.getType()));
// sext(A < B) + zext(A > B) => ucmp/scmp(A, B)
+ // sext(A <= B) + zext(A >= B) => ucmp/scmp(A, B)
CmpPredicate LTPred, GTPred;
if (match(&I,
m_c_Add(m_SExt(m_c_ICmp(LTPred, m_Value(A), m_Value(B))),
@@ -1670,13 +1671,22 @@ Instruction *InstCombinerImpl::visitAdd(BinaryOperator &I) {
std::swap(A, B);
}
- if (ICmpInst::isLT(LTPred) && ICmpInst::isGT(GTPred) &&
- ICmpInst::isSigned(LTPred) == ICmpInst::isSigned(GTPred))
- return replaceInstUsesWith(
- I, Builder.CreateIntrinsic(
- Ty,
- ICmpInst::isSigned(LTPred) ? Intrinsic::scmp : Intrinsic::ucmp,
- {A, B}));
+ if (ICmpInst::isSigned(LTPred) == ICmpInst::isSigned(GTPred)) {
+ Intrinsic::ID IID =
+ ICmpInst::isSigned(LTPred) ? Intrinsic::scmp : Intrinsic::ucmp;
+
+ // Handle strict inequalities: sext(A < B) + zext(A > B) => scmp/ucmp(A,
+ // B)
+ if (ICmpInst::isLT(LTPred) && ICmpInst::isGT(GTPred)) {
+ return replaceInstUsesWith(I, Builder.CreateIntrinsic(Ty, IID, {A, B}));
+ }
+
+ // Handle non-strict inequalities: sext(A <= B) + zext(A >= B) =>
+ // scmp/ucmp(A, B)
+ if (ICmpInst::isLE(LTPred) && ICmpInst::isGE(GTPred)) {
+ return replaceInstUsesWith(I, Builder.CreateIntrinsic(Ty, IID, {A, B}));
+ }
+ }
}
// A+B --> A|B iff A and B have no bits set in common.
diff --git a/llvm/test/Transforms/InstCombine/sext-a-lt-b-plus-zext-a-gt-b-to-uscmp.ll b/llvm/test/Transforms/InstCombine/sext-a-lt-b-plus-zext-a-gt-b-to-uscmp.ll
index 53982601fb7d4..720219bb49c5f 100644
--- a/llvm/test/Transforms/InstCombine/sext-a-lt-b-plus-zext-a-gt-b-to-uscmp.ll
+++ b/llvm/test/Transforms/InstCombine/sext-a-lt-b-plus-zext-a-gt-b-to-uscmp.ll
@@ -187,11 +187,7 @@ define i8 @signed_add_neg5(i32 %a, i32 %b) {
define i8 @signed_add_ge_le(i32 %a, i32 %b) {
; CHECK-LABEL: define i8 @signed_add_ge_le(
; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
-; CHECK-NEXT: [[LE:%.*]] = icmp sle i32 [[A]], [[B]]
-; CHECK-NEXT: [[LE8:%.*]] = sext i1 [[LE]] to i8
-; CHECK-NEXT: [[GE:%.*]] = icmp sge i32 [[A]], [[B]]
-; CHECK-NEXT: [[GE8:%.*]] = zext i1 [[GE]] to i8
-; CHECK-NEXT: [[R:%.*]] = add nsw i8 [[LE8]], [[GE8]]
+; CHECK-NEXT: [[R:%.*]] = call i8 @llvm.scmp.i8.i32(i32 [[A]], i32 [[B]])
; CHECK-NEXT: ret i8 [[R]]
;
%le = icmp sle i32 %a, %b
@@ -206,11 +202,7 @@ define i8 @signed_add_ge_le(i32 %a, i32 %b) {
define i8 @unsigned_add_ge_le(i32 %a, i32 %b) {
; CHECK-LABEL: define i8 @unsigned_add_ge_le(
; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
-; CHECK-NEXT: [[LE:%.*]] = icmp ule i32 [[A]], [[B]]
-; CHECK-NEXT: [[LE8:%.*]] = sext i1 [[LE]] to i8
-; CHECK-NEXT: [[GE:%.*]] = icmp uge i32 [[A]], [[B]]
-; CHECK-NEXT: [[GE8:%.*]] = zext i1 [[GE]] to i8
-; CHECK-NEXT: [[R:%.*]] = add nsw i8 [[LE8]], [[GE8]]
+; CHECK-NEXT: [[R:%.*]] = call i8 @llvm.ucmp.i8.i32(i32 [[A]], i32 [[B]])
; CHECK-NEXT: ret i8 [[R]]
;
%le = icmp ule i32 %a, %b
@@ -225,11 +217,7 @@ define i8 @unsigned_add_ge_le(i32 %a, i32 %b) {
define i8 @signed_sub_ge_le(i32 %a, i32 %b) {
; CHECK-LABEL: define i8 @signed_sub_ge_le(
; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
-; CHECK-NEXT: [[LE:%.*]] = icmp sle i32 [[A]], [[B]]
-; CHECK-NEXT: [[LE8_NEG:%.*]] = sext i1 [[LE]] to i8
-; CHECK-NEXT: [[GE:%.*]] = icmp sge i32 [[A]], [[B]]
-; CHECK-NEXT: [[GE8:%.*]] = zext i1 [[GE]] to i8
-; CHECK-NEXT: [[R:%.*]] = add nsw i8 [[LE8_NEG]], [[GE8]]
+; CHECK-NEXT: [[R:%.*]] = call i8 @llvm.scmp.i8.i32(i32 [[A]], i32 [[B]])
; CHECK-NEXT: ret i8 [[R]]
;
%le = icmp sle i32 %a, %b
@@ -244,11 +232,7 @@ define i8 @signed_sub_ge_le(i32 %a, i32 %b) {
define i8 @unsigned_sub_ge_le(i32 %a, i32 %b) {
; CHECK-LABEL: define i8 @unsigned_sub_ge_le(
; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
-; CHECK-NEXT: [[LE:%.*]] = icmp ule i32 [[A]], [[B]]
-; CHECK-NEXT: [[LE8_NEG:%.*]] = sext i1 [[LE]] to i8
-; CHECK-NEXT: [[GE:%.*]] = icmp uge i32 [[A]], [[B]]
-; CHECK-NEXT: [[GE8:%.*]] = zext i1 [[GE]] to i8
-; CHECK-NEXT: [[R:%.*]] = add nsw i8 [[LE8_NEG]], [[GE8]]
+; CHECK-NEXT: [[R:%.*]] = call i8 @llvm.ucmp.i8.i32(i32 [[A]], i32 [[B]])
; CHECK-NEXT: ret i8 [[R]]
;
%le = icmp ule i32 %a, %b
>From 5ddce2345f4db5dd6ccd679a22a8ccaae6e418c9 Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Thu, 23 Oct 2025 10:03:00 -0400
Subject: [PATCH 3/3] Handle constant case
---
.../InstCombine/InstCombineAddSub.cpp | 25 +++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
index 15b43009b3ebb..76d504234a4be 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
@@ -1689,6 +1689,31 @@ Instruction *InstCombinerImpl::visitAdd(BinaryOperator &I) {
}
}
+ // Handle constant case: sext(x > C1) + zext(x < C2) where C2 = C1 + 1
+ // This represents (x >= C1+1) - (x <= C1+1) => scmp/ucmp(x, C1+1)
+ Value *X, *C1, *C2;
+ CmpPredicate Pred1, Pred2;
+ if (match(&I,
+ m_c_Add(m_SExt(m_c_ICmp(Pred1, m_Value(X), m_Value(C1))),
+ m_ZExt(m_c_ICmp(Pred2, m_Deferred(X), m_Value(C2))))) &&
+ X->getType()->isIntOrIntVectorTy() &&
+ isa<ConstantInt>(C1) && isa<ConstantInt>(C2)) {
+
+ // Check if we have x > C1 and x < C2 where C2 = C1 + 2
+ if (ICmpInst::isGT(Pred1) && ICmpInst::isLT(Pred2)) {
+ ConstantInt *ConstC1 = cast<ConstantInt>(C1);
+ ConstantInt *ConstC2 = cast<ConstantInt>(C2);
+
+ // Check if C2 = C1 + 2
+ if (ConstC2->getValue() == ConstC1->getValue() + 2) {
+ // This represents (x >= C1+1) - (x <= C1+1) => scmp/ucmp(x, C1+1)
+ Intrinsic::ID IID = ICmpInst::isSigned(Pred1) ? Intrinsic::scmp : Intrinsic::ucmp;
+ Constant *C1Plus1 = ConstantInt::get(C1->getType(), ConstC1->getValue() + 1);
+ return replaceInstUsesWith(I, Builder.CreateIntrinsic(Ty, IID, {X, C1Plus1}));
+ }
+ }
+ }
+
// A+B --> A|B iff A and B have no bits set in common.
WithCache<const Value *> LHSCache(LHS), RHSCache(RHS);
if (haveNoCommonBitsSet(LHSCache, RHSCache, SQ.getWithInstruction(&I)))
More information about the llvm-commits
mailing list