[llvm] [InstCombine] Fold comparisons of llvm.usub.sat result with its LHS (PR #214108)

Haram Jeong via llvm-commits llvm-commits at lists.llvm.org
Tue Sep 29 18:41:38 PDT 2026


https://github.com/haramj updated https://github.com/llvm/llvm-project/pull/214108

>From 62b8cfc39f1c4d618c60f8c9e151e5fb9e11fa8a Mon Sep 17 00:00:00 2001
From: haramjeong <04harams77 at gmail.com>
Date: Mon, 7 Sep 2026 13:14:33 +0900
Subject: [PATCH 1/2] [InstCombine] Add tests for usub.sat comparisons with its
 LHS

---
 .../Transforms/InstCombine/icmp-usub-sat.ll   | 182 ++++++++++++++++++
 1 file changed, 182 insertions(+)

diff --git a/llvm/test/Transforms/InstCombine/icmp-usub-sat.ll b/llvm/test/Transforms/InstCombine/icmp-usub-sat.ll
index 2cd07b17af580..3a62fe60f2713 100644
--- a/llvm/test/Transforms/InstCombine/icmp-usub-sat.ll
+++ b/llvm/test/Transforms/InstCombine/icmp-usub-sat.ll
@@ -425,6 +425,185 @@ define <2 x i1> @icmp_eq_vector_multiuse_negative_equal(<2 x i8> %arg) {
   ret <2 x i1> %cmp
 }
 
+; usub.sat(X, C) == X  --> X == 0 (C is nonzero constant)
+define i1 @icmp_eq_usub_sat_lhs_nonzero_constant(i64 %x) {
+; CHECK-LABEL: define i1 @icmp_eq_usub_sat_lhs_nonzero_constant
+; CHECK-SAME: (i64 [[X:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i64 [[X]], 0
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %sat = call i64 @llvm.usub.sat.i64(i64 %x, i64 10)
+  %cmp = icmp eq i64 %sat, %x
+  ret i1 %cmp
+}
+
+; Commuted form: icmp eq X, usub.sat(X, C)  --> X == 0
+define i1 @icmp_eq_usub_sat_lhs_nonzero_constant_commuted(i64 %x) {
+; CHECK-LABEL: define i1 @icmp_eq_usub_sat_lhs_nonzero_constant_commuted
+; CHECK-SAME: (i64 [[X:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i64 [[X]], 0
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %sat = call i64 @llvm.usub.sat.i64(i64 %x, i64 10)
+  %cmp = icmp eq i64 %x, %sat
+  ret i1 %cmp
+}
+
+; samesign form: icmp samesign eq usub.sat(X, C), X  --> X == 0
+define i1 @icmp_samesign_eq_usub_sat_lhs_nonzero_constant(i64 %x) {
+; CHECK-LABEL: define i1 @icmp_samesign_eq_usub_sat_lhs_nonzero_constant
+; CHECK-SAME: (i64 [[X:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i64 [[X]], 0
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %sat = call i64 @llvm.usub.sat.i64(i64 %x, i64 10)
+  %cmp = icmp samesign eq i64 %sat, %x
+  ret i1 %cmp
+}
+
+; usub.sat(X, C) < X  --> X != 0 (ULT is the same as < for unsigned)
+define i1 @icmp_ult_usub_sat_lhs_nonzero_constant(i64 %x) {
+; CHECK-LABEL: define i1 @icmp_ult_usub_sat_lhs_nonzero_constant
+; CHECK-SAME: (i64 [[X:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ne i64 [[X]], 0
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %sat = call i64 @llvm.usub.sat.i64(i64 %x, i64 10)
+  %cmp = icmp ult i64 %sat, %x
+  ret i1 %cmp
+}
+
+; samesign ULT form
+define i1 @icmp_samesign_ult_usub_sat_lhs_nonzero_constant(i64 %x) {
+; CHECK-LABEL: define i1 @icmp_samesign_ult_usub_sat_lhs_nonzero_constant
+; CHECK-SAME: (i64 [[X:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ne i64 [[X]], 0
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %sat = call i64 @llvm.usub.sat.i64(i64 %x, i64 10)
+  %cmp = icmp samesign ult i64 %sat, %x
+  ret i1 %cmp
+}
+
+; usub.sat(X, C) != X  --> X != 0
+define i1 @icmp_ne_usub_sat_lhs_nonzero_constant(i64 %x) {
+; CHECK-LABEL: define i1 @icmp_ne_usub_sat_lhs_nonzero_constant
+; CHECK-SAME: (i64 [[X:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ne i64 [[X]], 0
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %sat = call i64 @llvm.usub.sat.i64(i64 %x, i64 10)
+  %cmp = icmp ne i64 %sat, %x
+  ret i1 %cmp
+}
+
+; X > usub.sat(X, C)  --> X != 0 (UGT commuted)
+define i1 @icmp_ugt_usub_sat_lhs_nonzero_constant(i64 %x) {
+; CHECK-LABEL: define i1 @icmp_ugt_usub_sat_lhs_nonzero_constant
+; CHECK-SAME: (i64 [[X:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ne i64 [[X]], 0
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %sat = call i64 @llvm.usub.sat.i64(i64 %x, i64 10)
+  %cmp = icmp ugt i64 %x, %sat
+  ret i1 %cmp
+}
+
+; Multiuse: intrinsic is used elsewhere, only icmp is folded
+define i1 @icmp_eq_usub_sat_lhs_nonzero_constant_multiuse(i64 %x) {
+; CHECK-LABEL: define i1 @icmp_eq_usub_sat_lhs_nonzero_constant_multiuse
+; CHECK-SAME: (i64 [[X:%.*]]) {
+; CHECK-NEXT:    [[SAT:%.*]] = call i64 @llvm.usub.sat.i64(i64 [[X]], i64 10)
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i64 [[X]], 0
+; CHECK-NEXT:    call void @use.i64(i64 [[SAT]])
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %sat = call i64 @llvm.usub.sat.i64(i64 %x, i64 10)
+  %cmp = icmp eq i64 %sat, %x
+  call void @use.i64(i64 %sat)
+  ret i1 %cmp
+}
+
+; Multiuse ULT
+define i1 @icmp_ult_usub_sat_lhs_nonzero_constant_multiuse(i64 %x) {
+; CHECK-LABEL: define i1 @icmp_ult_usub_sat_lhs_nonzero_constant_multiuse
+; CHECK-SAME: (i64 [[X:%.*]]) {
+; CHECK-NEXT:    [[SAT:%.*]] = call i64 @llvm.usub.sat.i64(i64 [[X]], i64 10)
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ne i64 [[X]], 0
+; CHECK-NEXT:    call void @use.i64(i64 [[SAT]])
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %sat = call i64 @llvm.usub.sat.i64(i64 %x, i64 10)
+  %cmp = icmp ult i64 %sat, %x
+  call void @use.i64(i64 %sat)
+  ret i1 %cmp
+}
+
+; Zero constant: usub.sat(X, 0) == X is always true (negative test)
+define i1 @icmp_eq_usub_sat_lhs_zero_constant(i64 %x) {
+; CHECK-LABEL: define i1 @icmp_eq_usub_sat_lhs_zero_constant
+; CHECK-SAME: (i64 [[X:%.*]]) {
+; CHECK-NEXT:    ret i1 true
+;
+  %sat = call i64 @llvm.usub.sat.i64(i64 %x, i64 0)
+  %cmp = icmp eq i64 %sat, %x
+  ret i1 %cmp
+}
+
+; Vector types
+define <2 x i1> @icmp_ult_usub_sat_lhs_nonzero_constant_vector(<2 x i8> %x) {
+; CHECK-LABEL: define <2 x i1> @icmp_ult_usub_sat_lhs_nonzero_constant_vector
+; CHECK-SAME: (<2 x i8> [[X:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ne <2 x i8> [[X]], zeroinitializer
+; CHECK-NEXT:    ret <2 x i1> [[CMP]]
+;
+  %sat = call <2 x i8> @llvm.usub.sat.v2i8(<2 x i8> %x, <2 x i8> <i8 10, i8 10>)
+  %cmp = icmp ult <2 x i8> %sat, %x
+  ret <2 x i1> %cmp
+}
+
+; Vector nonsplat constant (all elements nonzero)
+define <2 x i1> @icmp_ult_usub_sat_lhs_nonzero_constant_vector_nonsplat(<2 x i8> %x) {
+; CHECK-LABEL: define <2 x i1> @icmp_ult_usub_sat_lhs_nonzero_constant_vector_nonsplat
+; CHECK-SAME: (<2 x i8> [[X:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ne <2 x i8> [[X]], zeroinitializer
+; CHECK-NEXT:    ret <2 x i1> [[CMP]]
+;
+  %sat = call <2 x i8> @llvm.usub.sat.v2i8(<2 x i8> %x, <2 x i8> <i8 1, i8 10>)
+  %cmp = icmp ult <2 x i8> %sat, %x
+  ret <2 x i1> %cmp
+}
+
+; Vector with some zero elements (negative test - fold not applicable)
+define <2 x i1> @icmp_ult_usub_sat_lhs_zero_constant_vector(<2 x i8> %x) {
+; CHECK-LABEL: define <2 x i1> @icmp_ult_usub_sat_lhs_zero_constant_vector
+; CHECK-SAME: (<2 x i8> [[X:%.*]]) {
+; CHECK-NEXT:    [[SAT:%.*]] = call <2 x i8> @llvm.usub.sat.v2i8(<2 x i8> [[X]], <2 x i8> <i8 1, i8 0>)
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult <2 x i8> [[SAT]], [[X]]
+; CHECK-NEXT:    ret <2 x i1> [[CMP]]
+;
+  %sat = call <2 x i8> @llvm.usub.sat.v2i8(<2 x i8> %x, <2 x i8> <i8 1, i8 0>)
+  %cmp = icmp ult <2 x i8> %sat, %x
+  ret <2 x i1> %cmp
+}
+
+; Non-constant but known-nonzero RHS via assume (negative test - fold only applies to constants)
+define i1 @icmp_eq_usub_sat_lhs_known_nonzero_rhs(i64 %x, i64 %y) {
+; CHECK-LABEL: define i1 @icmp_eq_usub_sat_lhs_known_nonzero_rhs
+; CHECK-SAME: (i64 [[X:%.*]], i64 [[Y:%.*]]) {
+; CHECK-NEXT:    [[COND:%.*]] = icmp ne i64 [[Y]], 0
+; CHECK-NEXT:    call void @llvm.assume(i1 [[COND]])
+; CHECK-NEXT:    [[SAT:%.*]] = call i64 @llvm.usub.sat.i64(i64 [[X]], i64 [[Y]])
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i64 [[SAT]], [[X]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %cond = icmp ne i64 %y, 0
+  call void @llvm.assume(i1 %cond)
+  %sat = call i64 @llvm.usub.sat.i64(i64 %x, i64 %y)
+  %cmp = icmp eq i64 %sat, %x
+  ret i1 %cmp
+}
+
 declare i8 @llvm.usub.sat.i8(i8, i8)
 declare i16 @llvm.usub.sat.i16(i16, i16)
 declare i32 @llvm.usub.sat.i32(i32, i32)
@@ -435,5 +614,8 @@ declare <2 x i32> @llvm.usub.sat.v2i32(<2 x i32>, <2 x i32>)
 declare <2 x i16> @llvm.usub.sat.v2i16(<2 x i16>, <2 x i16>)
 declare <2 x i8> @llvm.usub.sat.v2i8(<2 x i8>, <2 x i8>)
 
+declare void @llvm.assume(i1)
+
 declare void @use.i8(i8)
 declare void @use.v2i8(<2 x i8>)
+declare void @use.i64(i64)

>From 23ac5de9286d82af979b8134c92e0810d7681eab Mon Sep 17 00:00:00 2001
From: haramjeong <04harams77 at gmail.com>
Date: Mon, 7 Sep 2026 13:42:54 +0900
Subject: [PATCH 2/2] [InstCombine] Fold usub.sat comparisons with its LHS

Fold comparisons between the result of llvm.usub.sat(X, C) and the original
left-hand side when C is a nonzero constant.

For C != 0:

    %sat = call iN @llvm.usub.sat.iN(iN %x, iN C)
    %cmp = icmp eq iN %sat, %x
can be folded to:

    %cmp = icmp eq iN %x, 0
Similarly:

    %sat = call iN @llvm.usub.sat.iN(iN %x, iN C)
    %cmp = icmp ult iN %sat, %x
can be folded to:

    %cmp = icmp ne iN %x, 0
This also handles the commuted equality form:

    icmp eq iN %x, %sat
The fold does not require the intrinsic to have one use, so if the usub.sat
result is used elsewhere, only the comparison is canonicalized and the
intrinsic remains live.

Proofs: https://alive2.llvm.org/ce/z/ZmAzBE

Fixes #213832
---
 .../Transforms/InstCombine/InstCombineCompares.cpp | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index 717cb65239189..a6e972a748c83 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -7873,6 +7873,20 @@ Instruction *InstCombinerImpl::foldICmpCommutative(CmpPredicate Pred,
     }
   }
 
+  {
+    // For a nonzero constant C:
+    // usub.sat(X, C) == X  --> X == 0
+    // usub.sat(X, C) != X  --> X != 0
+    // usub.sat(X, C) <  X  --> X != 0
+    if (match(Op0, m_Intrinsic<Intrinsic::usub_sat>(m_Specific(Op1),
+                                                    m_NonZeroInt())) &&
+        (CmpInst::isEquality(Pred) || Pred == ICmpInst::ICMP_ULT)) {
+      ICmpInst::Predicate NewPred =
+          CmpInst::isEquality(Pred) ? Pred.dropSameSign() : ICmpInst::ICMP_NE;
+      return new ICmpInst(NewPred, Op1, Constant::getNullValue(Op1->getType()));
+    }
+  }
+
   const SimplifyQuery Q = SQ.getWithInstruction(&CtxI);
   if (Value *V = foldICmpWithLowBitMaskedVal(Pred, Op0, Op1, Q, *this))
     return replaceInstUsesWith(CtxI, V);



More information about the llvm-commits mailing list