[llvm] [InstCombine] Fold icmp with clamp into unsigned bound check (PR #161303)

via llvm-commits llvm-commits at lists.llvm.org
Tue Sep 30 13:01:05 PDT 2025


https://github.com/brandonxin updated https://github.com/llvm/llvm-project/pull/161303

>From cffbd0176cc5bdaa0f5a6d0150c548c23ca1bdfa Mon Sep 17 00:00:00 2001
From: Brandon <brandon.xin at outlook.com>
Date: Mon, 29 Sep 2025 17:23:27 -0500
Subject: [PATCH 1/4] [InstCombine] Add baseline tests for icmp with clamp
 operation

---
 .../test/Transforms/InstCombine/icmp-clamp.ll | 308 ++++++++++++++++++
 1 file changed, 308 insertions(+)
 create mode 100644 llvm/test/Transforms/InstCombine/icmp-clamp.ll

diff --git a/llvm/test/Transforms/InstCombine/icmp-clamp.ll b/llvm/test/Transforms/InstCombine/icmp-clamp.ll
new file mode 100644
index 0000000000000..9f0348ceba514
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/icmp-clamp.ll
@@ -0,0 +1,308 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+declare void @use(i32)
+
+define i1 @test_i32_eq(i32 %x) {
+; CHECK-LABEL: define i1 @test_i32_eq(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[V1:%.*]] = tail call i32 @llvm.smax.i32(i32 [[X]], i32 -95)
+; CHECK-NEXT:    [[V2:%.*]] = tail call i32 @llvm.smin.i32(i32 [[V1]], i32 160)
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[V2]], [[X]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %v1 = tail call i32 @llvm.smax.i32(i32 %x, i32 -95)
+  %v2 = tail call i32 @llvm.smin.i32(i32 %v1, i32 160)
+  %cmp = icmp eq i32 %v2, %x
+  ret i1 %cmp
+}
+
+define i1 @test_i32_ne(i32 %x) {
+; CHECK-LABEL: define i1 @test_i32_ne(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[V1:%.*]] = tail call i32 @llvm.smax.i32(i32 [[X]], i32 -95)
+; CHECK-NEXT:    [[V2:%.*]] = tail call i32 @llvm.smin.i32(i32 [[V1]], i32 160)
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ne i32 [[V2]], [[X]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %v1 = tail call i32 @llvm.smax.i32(i32 %x, i32 -95)
+  %v2 = tail call i32 @llvm.smin.i32(i32 %v1, i32 160)
+  %cmp = icmp ne i32 %v2, %x
+  ret i1 %cmp
+}
+
+define i1 @test_i32_eq_no_add(i32 %x) {
+; CHECK-LABEL: define i1 @test_i32_eq_no_add(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[V1:%.*]] = tail call i32 @llvm.smax.i32(i32 [[X]], i32 0)
+; CHECK-NEXT:    [[V2:%.*]] = tail call i32 @llvm.smin.i32(i32 [[V1]], i32 160)
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[V2]], [[X]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %v1 = tail call i32 @llvm.smax.i32(i32 %x, i32 0)
+  %v2 = tail call i32 @llvm.smin.i32(i32 %v1, i32 160)
+  %cmp = icmp eq i32 %v2, %x
+  ret i1 %cmp
+}
+
+define i1 @test_i32_ne_no_add(i32 %x) {
+; CHECK-LABEL: define i1 @test_i32_ne_no_add(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[V1:%.*]] = tail call i32 @llvm.smax.i32(i32 [[X]], i32 0)
+; CHECK-NEXT:    [[V2:%.*]] = tail call i32 @llvm.smin.i32(i32 [[V1]], i32 160)
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ne i32 [[V2]], [[X]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %v1 = tail call i32 @llvm.smax.i32(i32 %x, i32 0)
+  %v2 = tail call i32 @llvm.smin.i32(i32 %v1, i32 160)
+  %cmp = icmp ne i32 %v2, %x
+  ret i1 %cmp
+}
+
+define i1 @test_unsigned_eq(i32 %x) {
+; CHECK-LABEL: define i1 @test_unsigned_eq(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[V1:%.*]] = tail call i32 @llvm.umax.i32(i32 [[X]], i32 10)
+; CHECK-NEXT:    [[V2:%.*]] = tail call i32 @llvm.umin.i32(i32 [[V1]], i32 100)
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[V2]], [[X]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %v1 = tail call i32 @llvm.umax.i32(i32 %x, i32 10)
+  %v2 = tail call i32 @llvm.umin.i32(i32 %v1, i32 100)
+  %cmp = icmp eq i32 %v2, %x
+  ret i1 %cmp
+}
+
+define i1 @test_unsigned_ne(i32 %x) {
+; CHECK-LABEL: define i1 @test_unsigned_ne(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[V1:%.*]] = tail call i32 @llvm.umax.i32(i32 [[X]], i32 10)
+; CHECK-NEXT:    [[V2:%.*]] = tail call i32 @llvm.umin.i32(i32 [[V1]], i32 100)
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ne i32 [[V2]], [[X]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %v1 = tail call i32 @llvm.umax.i32(i32 %x, i32 10)
+  %v2 = tail call i32 @llvm.umin.i32(i32 %v1, i32 100)
+  %cmp = icmp ne i32 %v2, %x
+  ret i1 %cmp
+}
+
+
+; Different bit widths
+define i1 @test_i8_eq(i8 %x) {
+; CHECK-LABEL: define i1 @test_i8_eq(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT:    [[V1:%.*]] = tail call i8 @llvm.smax.i8(i8 [[X]], i8 -50)
+; CHECK-NEXT:    [[V2:%.*]] = tail call i8 @llvm.smin.i8(i8 [[V1]], i8 50)
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[V2]], [[X]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %v1 = tail call i8 @llvm.smax.i8(i8 %x, i8 -50)
+  %v2 = tail call i8 @llvm.smin.i8(i8 %v1, i8 50)
+  %cmp = icmp eq i8 %v2, %x
+  ret i1 %cmp
+}
+
+define i1 @test_i16_eq(i16 %x) {
+; CHECK-LABEL: define i1 @test_i16_eq(
+; CHECK-SAME: i16 [[X:%.*]]) {
+; CHECK-NEXT:    [[V1:%.*]] = tail call i16 @llvm.smax.i16(i16 [[X]], i16 -1000)
+; CHECK-NEXT:    [[V2:%.*]] = tail call i16 @llvm.smin.i16(i16 [[V1]], i16 1000)
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i16 [[V2]], [[X]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %v1 = tail call i16 @llvm.smax.i16(i16 %x, i16 -1000)
+  %v2 = tail call i16 @llvm.smin.i16(i16 %v1, i16 1000)
+  %cmp = icmp eq i16 %v2, %x
+  ret i1 %cmp
+}
+
+define i1 @test_i64_eq(i64 %x) {
+; CHECK-LABEL: define i1 @test_i64_eq(
+; CHECK-SAME: i64 [[X:%.*]]) {
+; CHECK-NEXT:    [[V1:%.*]] = tail call i64 @llvm.smax.i64(i64 [[X]], i64 -1)
+; CHECK-NEXT:    [[V2:%.*]] = tail call i64 @llvm.smin.i64(i64 [[V1]], i64 9223372036854775806)
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i64 [[V2]], [[X]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %v1 = tail call i64 @llvm.smax.i64(i64 %x, i64 -1)
+  %v2 = tail call i64 @llvm.smin.i64(i64 %v1, i64 9223372036854775806)
+  %cmp = icmp eq i64 %v2, %x
+  ret i1 %cmp
+}
+
+; Negative tests - wrong predicate
+define i1 @test_wrong_pred_slt(i32 %x) {
+; CHECK-LABEL: define i1 @test_wrong_pred_slt(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp sgt i32 [[X]], 160
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %v1 = tail call i32 @llvm.smax.i32(i32 %x, i32 -95)
+  %v2 = tail call i32 @llvm.smin.i32(i32 %v1, i32 160)
+  %cmp = icmp slt i32 %v2, %x
+  ret i1 %cmp
+}
+
+
+; Negative tests - not a clamp pattern
+define i1 @test_not_clamp_pattern(i32 %x, i32 %y) {
+; CHECK-LABEL: define i1 @test_not_clamp_pattern(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[V1:%.*]] = tail call i32 @llvm.smax.i32(i32 [[Y]], i32 -95)
+; CHECK-NEXT:    [[V2:%.*]] = tail call i32 @llvm.smin.i32(i32 [[V1]], i32 160)
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[V2]], [[X]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %v1 = tail call i32 @llvm.smax.i32(i32 %y, i32 -95)
+  %v2 = tail call i32 @llvm.smin.i32(i32 %v1, i32 160)
+  %cmp = icmp eq i32 %v2, %x
+  ret i1 %cmp
+}
+
+; Negative tests - Lo >= Hi
+define i1 @test_invalid_range(i32 %x) {
+; CHECK-LABEL: define i1 @test_invalid_range(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[X]], 50
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %v1 = tail call i32 @llvm.smax.i32(i32 %x, i32 100)
+  %v2 = tail call i32 @llvm.smin.i32(i32 %v1, i32 50)
+  %cmp = icmp eq i32 %v2, %x
+  ret i1 %cmp
+}
+
+; Negative tests - Lo is minimum signed value
+define i1 @test_lo_min_signed(i32 %x) {
+; CHECK-LABEL: define i1 @test_lo_min_signed(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i32 [[X]], 161
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %v1 = tail call i32 @llvm.smax.i32(i32 %x, i32 -2147483648)
+  %v2 = tail call i32 @llvm.smin.i32(i32 %v1, i32 160)
+  %cmp = icmp eq i32 %v2, %x
+  ret i1 %cmp
+}
+
+; Negative tests - Hi is maximum signed value
+define i1 @test_hi_max_signed(i32 %x) {
+; CHECK-LABEL: define i1 @test_hi_max_signed(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp sgt i32 [[X]], -96
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %v1 = tail call i32 @llvm.smax.i32(i32 %x, i32 -95)
+  %v2 = tail call i32 @llvm.smin.i32(i32 %v1, i32 2147483647)
+  %cmp = icmp eq i32 %v2, %x
+  ret i1 %cmp
+}
+
+; Negative tests - Hi is maximum unsigned value
+define i1 @test_hi_max_unsigned(i32 %x) {
+; CHECK-LABEL: define i1 @test_hi_max_unsigned(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ugt i32 [[X]], 9
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %v1 = tail call i32 @llvm.umax.i32(i32 %x, i32 10)
+  %v2 = tail call i32 @llvm.umin.i32(i32 %v1, i32 4294967295)
+  %cmp = icmp eq i32 %v2, %x
+  ret i1 %cmp
+}
+
+; Multi-use tests - multiple uses of max
+define i1 @test_multi_use_max(i32 %x) {
+; CHECK-LABEL: define i1 @test_multi_use_max(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[V1:%.*]] = tail call i32 @llvm.smax.i32(i32 [[X]], i32 -95)
+; CHECK-NEXT:    call void @use(i32 [[V1]])
+; CHECK-NEXT:    [[V2:%.*]] = tail call i32 @llvm.smin.i32(i32 [[V1]], i32 160)
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[V2]], [[X]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %v1 = tail call i32 @llvm.smax.i32(i32 %x, i32 -95)
+  call void @use(i32 %v1)
+  %v2 = tail call i32 @llvm.smin.i32(i32 %v1, i32 160)
+  %cmp = icmp eq i32 %v2, %x
+  ret i1 %cmp
+}
+
+; Multi-use tests - multiple uses of min
+define i1 @test_multi_use_min(i32 %x) {
+; CHECK-LABEL: define i1 @test_multi_use_min(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[V1:%.*]] = tail call i32 @llvm.smax.i32(i32 [[X]], i32 -95)
+; CHECK-NEXT:    [[V2:%.*]] = tail call i32 @llvm.smin.i32(i32 [[V1]], i32 160)
+; CHECK-NEXT:    call void @use(i32 [[V2]])
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[V2]], [[X]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %v1 = tail call i32 @llvm.smax.i32(i32 %x, i32 -95)
+  %v2 = tail call i32 @llvm.smin.i32(i32 %v1, i32 160)
+  call void @use(i32 %v2)
+  %cmp = icmp eq i32 %v2, %x
+  ret i1 %cmp
+}
+
+; Commuted tests
+define i1 @test_commuted_eq(i32 %x) {
+; CHECK-LABEL: define i1 @test_commuted_eq(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[V1:%.*]] = tail call i32 @llvm.smax.i32(i32 [[X]], i32 -95)
+; CHECK-NEXT:    [[V2:%.*]] = tail call i32 @llvm.smin.i32(i32 [[V1]], i32 160)
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[X]], [[V2]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %v1 = tail call i32 @llvm.smax.i32(i32 %x, i32 -95)
+  %v2 = tail call i32 @llvm.smin.i32(i32 %v1, i32 160)
+  %cmp = icmp eq i32 %x, %v2
+  ret i1 %cmp
+}
+
+
+; Vector tests - splat constants
+define <2 x i1> @test_vec_splat_eq(<2 x i32> %x) {
+; CHECK-LABEL: define <2 x i1> @test_vec_splat_eq(
+; CHECK-SAME: <2 x i32> [[X:%.*]]) {
+; CHECK-NEXT:    [[V1:%.*]] = tail call <2 x i32> @llvm.smax.v2i32(<2 x i32> [[X]], <2 x i32> splat (i32 -50))
+; CHECK-NEXT:    [[V2:%.*]] = tail call <2 x i32> @llvm.smin.v2i32(<2 x i32> [[V1]], <2 x i32> splat (i32 50))
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq <2 x i32> [[V2]], [[X]]
+; CHECK-NEXT:    ret <2 x i1> [[CMP]]
+;
+  %v1 = tail call <2 x i32> @llvm.smax.v2i32(<2 x i32> %x, <2 x i32> <i32 -50, i32 -50>)
+  %v2 = tail call <2 x i32> @llvm.smin.v2i32(<2 x i32> %v1, <2 x i32> <i32 50, i32 50>)
+  %cmp = icmp eq <2 x i32> %v2, %x
+  ret <2 x i1> %cmp
+}
+
+; Vector tests - poison elements
+define <2 x i1> @test_vec_poison_eq(<2 x i32> %x) {
+; CHECK-LABEL: define <2 x i1> @test_vec_poison_eq(
+; CHECK-SAME: <2 x i32> [[X:%.*]]) {
+; CHECK-NEXT:    [[V1:%.*]] = tail call <2 x i32> @llvm.smax.v2i32(<2 x i32> [[X]], <2 x i32> <i32 -50, i32 poison>)
+; CHECK-NEXT:    [[V2:%.*]] = tail call <2 x i32> @llvm.smin.v2i32(<2 x i32> [[V1]], <2 x i32> <i32 50, i32 poison>)
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq <2 x i32> [[V2]], [[X]]
+; CHECK-NEXT:    ret <2 x i1> [[CMP]]
+;
+  %v1 = tail call <2 x i32> @llvm.smax.v2i32(<2 x i32> %x, <2 x i32> <i32 -50, i32 poison>)
+  %v2 = tail call <2 x i32> @llvm.smin.v2i32(<2 x i32> %v1, <2 x i32> <i32 50, i32 poison>)
+  %cmp = icmp eq <2 x i32> %v2, %x
+  ret <2 x i1> %cmp
+}
+
+; Vector tests - non-splat
+define <2 x i1> @test_vec_non_splat_eq(<2 x i32> %x) {
+; CHECK-LABEL: define <2 x i1> @test_vec_non_splat_eq(
+; CHECK-SAME: <2 x i32> [[X:%.*]]) {
+; CHECK-NEXT:    [[V1:%.*]] = tail call <2 x i32> @llvm.smax.v2i32(<2 x i32> [[X]], <2 x i32> <i32 -50, i32 -30>)
+; CHECK-NEXT:    [[V2:%.*]] = tail call <2 x i32> @llvm.smin.v2i32(<2 x i32> [[V1]], <2 x i32> <i32 50, i32 70>)
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq <2 x i32> [[V2]], [[X]]
+; CHECK-NEXT:    ret <2 x i1> [[CMP]]
+;
+  %v1 = tail call <2 x i32> @llvm.smax.v2i32(<2 x i32> %x, <2 x i32> <i32 -50, i32 -30>)
+  %v2 = tail call <2 x i32> @llvm.smin.v2i32(<2 x i32> %v1, <2 x i32> <i32 50, i32 70>)
+  %cmp = icmp eq <2 x i32> %v2, %x
+  ret <2 x i1> %cmp
+}

>From eab6eba048dd4601f8e0bd67c67bd9e8a8ae6ad4 Mon Sep 17 00:00:00 2001
From: Brandon <brandon.xin at outlook.com>
Date: Mon, 29 Sep 2025 19:20:21 -0500
Subject: [PATCH 2/4] [InstCombine] Fold icmp with clamp to unsigned bound
 check

---
 .../InstCombine/InstCombineCompares.cpp       | 47 +++++++++++++++-
 .../InstCombine/InstCombineInternal.h         |  1 +
 .../test/Transforms/InstCombine/icmp-clamp.ll | 53 +++++++------------
 3 files changed, 67 insertions(+), 34 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index e4cb457499ef5..18bf3903715d5 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -5780,6 +5780,47 @@ Instruction *InstCombinerImpl::foldICmpWithMinMax(Instruction &I,
   return nullptr;
 }
 
+// Transform patterns like:
+//   icmp eq/ne X, min(max(X, Lo), Hi)
+// Into:
+//   (X - Lo) u< (Hi - Lo + 1)
+Instruction *InstCombinerImpl::foldICmpWithClamp(ICmpInst &I, Value *X,
+                                                 MinMaxIntrinsic *Min) {
+  if (!I.isEquality() || !Min->hasOneUse())
+    return nullptr;
+
+  const APInt *Lo = nullptr, *Hi = nullptr;
+  if (Min->isSigned()) {
+    if (!match(Min->getLHS(), m_OneUse(m_SMax(m_Specific(X), m_APInt(Lo)))) ||
+        !match(Min->getRHS(), m_APInt(Hi)) || !Lo->slt(*Hi))
+      return nullptr;
+  } else {
+    if (!match(Min->getLHS(), m_OneUse(m_UMax(m_Specific(X), m_APInt(Lo)))) ||
+        !match(Min->getRHS(), m_APInt(Hi)) || !Lo->ult(*Hi))
+      return nullptr;
+  }
+
+  // If Hi is the maximum value, the min operation becomes redundant and
+  // will be removed by other optimizations.
+  if ((Min->isSigned() && (Lo->isMinSignedValue() || Hi->isMaxSignedValue())) ||
+      (!Min->isSigned() && (Lo->isMinValue() || Hi->isMaxValue())))
+    return nullptr;
+
+  ConstantRange CR(*Lo, *Hi + 1);
+  ICmpInst::Predicate Pred;
+  APInt C, Offset;
+  if (I.getPredicate() == ICmpInst::ICMP_EQ)
+    CR.getEquivalentICmp(Pred, C, Offset);
+  else
+    CR.inverse().getEquivalentICmp(Pred, C, Offset);
+
+  if (Offset != 0)
+    X = Builder.CreateAdd(X, ConstantInt::get(X->getType(), Offset));
+
+  return replaceInstUsesWith(
+      I, Builder.CreateICmp(Pred, X, ConstantInt::get(X->getType(), C)));
+}
+
 // Canonicalize checking for a power-of-2-or-zero value:
 static Instruction *foldICmpPow2Test(ICmpInst &I,
                                      InstCombiner::BuilderTy &Builder) {
@@ -7467,10 +7508,14 @@ Instruction *InstCombinerImpl::foldICmpCommutative(CmpPredicate Pred,
     if (Instruction *NI = foldSelectICmp(Pred, SI, Op1, CxtI))
       return NI;
 
-  if (auto *MinMax = dyn_cast<MinMaxIntrinsic>(Op0))
+  if (auto *MinMax = dyn_cast<MinMaxIntrinsic>(Op0)) {
     if (Instruction *Res = foldICmpWithMinMax(CxtI, MinMax, Op1, Pred))
       return Res;
 
+    if (Instruction *Res = foldICmpWithClamp(CxtI, Op1, MinMax))
+      return Res;
+  }
+
   {
     Value *X;
     const APInt *C;
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineInternal.h b/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
index 4f94aa2d38541..e01c145bf5de3 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
+++ b/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
@@ -725,6 +725,7 @@ class LLVM_LIBRARY_VISIBILITY InstCombinerImpl final
   Instruction *foldICmpBinOp(ICmpInst &Cmp, const SimplifyQuery &SQ);
   Instruction *foldICmpWithMinMax(Instruction &I, MinMaxIntrinsic *MinMax,
                                   Value *Z, CmpPredicate Pred);
+  Instruction *foldICmpWithClamp(ICmpInst &Cmp, Value *X, MinMaxIntrinsic *Min);
   Instruction *foldICmpEquality(ICmpInst &Cmp);
   Instruction *foldIRemByPowerOfTwoToBitTest(ICmpInst &I);
   Instruction *foldSignBitTest(ICmpInst &I);
diff --git a/llvm/test/Transforms/InstCombine/icmp-clamp.ll b/llvm/test/Transforms/InstCombine/icmp-clamp.ll
index 9f0348ceba514..4866dbffb567a 100644
--- a/llvm/test/Transforms/InstCombine/icmp-clamp.ll
+++ b/llvm/test/Transforms/InstCombine/icmp-clamp.ll
@@ -6,9 +6,8 @@ declare void @use(i32)
 define i1 @test_i32_eq(i32 %x) {
 ; CHECK-LABEL: define i1 @test_i32_eq(
 ; CHECK-SAME: i32 [[X:%.*]]) {
-; CHECK-NEXT:    [[V1:%.*]] = tail call i32 @llvm.smax.i32(i32 [[X]], i32 -95)
-; CHECK-NEXT:    [[V2:%.*]] = tail call i32 @llvm.smin.i32(i32 [[V1]], i32 160)
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[V2]], [[X]]
+; CHECK-NEXT:    [[TMP1:%.*]] = add i32 [[X]], 95
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i32 [[TMP1]], 256
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %v1 = tail call i32 @llvm.smax.i32(i32 %x, i32 -95)
@@ -20,9 +19,8 @@ define i1 @test_i32_eq(i32 %x) {
 define i1 @test_i32_ne(i32 %x) {
 ; CHECK-LABEL: define i1 @test_i32_ne(
 ; CHECK-SAME: i32 [[X:%.*]]) {
-; CHECK-NEXT:    [[V1:%.*]] = tail call i32 @llvm.smax.i32(i32 [[X]], i32 -95)
-; CHECK-NEXT:    [[V2:%.*]] = tail call i32 @llvm.smin.i32(i32 [[V1]], i32 160)
-; CHECK-NEXT:    [[CMP:%.*]] = icmp ne i32 [[V2]], [[X]]
+; CHECK-NEXT:    [[TMP1:%.*]] = add i32 [[X]], -161
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i32 [[TMP1]], -256
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %v1 = tail call i32 @llvm.smax.i32(i32 %x, i32 -95)
@@ -34,9 +32,7 @@ define i1 @test_i32_ne(i32 %x) {
 define i1 @test_i32_eq_no_add(i32 %x) {
 ; CHECK-LABEL: define i1 @test_i32_eq_no_add(
 ; CHECK-SAME: i32 [[X:%.*]]) {
-; CHECK-NEXT:    [[V1:%.*]] = tail call i32 @llvm.smax.i32(i32 [[X]], i32 0)
-; CHECK-NEXT:    [[V2:%.*]] = tail call i32 @llvm.smin.i32(i32 [[V1]], i32 160)
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[V2]], [[X]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i32 [[X]], 161
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %v1 = tail call i32 @llvm.smax.i32(i32 %x, i32 0)
@@ -48,9 +44,7 @@ define i1 @test_i32_eq_no_add(i32 %x) {
 define i1 @test_i32_ne_no_add(i32 %x) {
 ; CHECK-LABEL: define i1 @test_i32_ne_no_add(
 ; CHECK-SAME: i32 [[X:%.*]]) {
-; CHECK-NEXT:    [[V1:%.*]] = tail call i32 @llvm.smax.i32(i32 [[X]], i32 0)
-; CHECK-NEXT:    [[V2:%.*]] = tail call i32 @llvm.smin.i32(i32 [[V1]], i32 160)
-; CHECK-NEXT:    [[CMP:%.*]] = icmp ne i32 [[V2]], [[X]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ugt i32 [[X]], 160
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %v1 = tail call i32 @llvm.smax.i32(i32 %x, i32 0)
@@ -62,9 +56,8 @@ define i1 @test_i32_ne_no_add(i32 %x) {
 define i1 @test_unsigned_eq(i32 %x) {
 ; CHECK-LABEL: define i1 @test_unsigned_eq(
 ; CHECK-SAME: i32 [[X:%.*]]) {
-; CHECK-NEXT:    [[V1:%.*]] = tail call i32 @llvm.umax.i32(i32 [[X]], i32 10)
-; CHECK-NEXT:    [[V2:%.*]] = tail call i32 @llvm.umin.i32(i32 [[V1]], i32 100)
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[V2]], [[X]]
+; CHECK-NEXT:    [[TMP1:%.*]] = add i32 [[X]], -10
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i32 [[TMP1]], 91
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %v1 = tail call i32 @llvm.umax.i32(i32 %x, i32 10)
@@ -76,9 +69,8 @@ define i1 @test_unsigned_eq(i32 %x) {
 define i1 @test_unsigned_ne(i32 %x) {
 ; CHECK-LABEL: define i1 @test_unsigned_ne(
 ; CHECK-SAME: i32 [[X:%.*]]) {
-; CHECK-NEXT:    [[V1:%.*]] = tail call i32 @llvm.umax.i32(i32 [[X]], i32 10)
-; CHECK-NEXT:    [[V2:%.*]] = tail call i32 @llvm.umin.i32(i32 [[V1]], i32 100)
-; CHECK-NEXT:    [[CMP:%.*]] = icmp ne i32 [[V2]], [[X]]
+; CHECK-NEXT:    [[TMP1:%.*]] = add i32 [[X]], -101
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i32 [[TMP1]], -91
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %v1 = tail call i32 @llvm.umax.i32(i32 %x, i32 10)
@@ -92,9 +84,8 @@ define i1 @test_unsigned_ne(i32 %x) {
 define i1 @test_i8_eq(i8 %x) {
 ; CHECK-LABEL: define i1 @test_i8_eq(
 ; CHECK-SAME: i8 [[X:%.*]]) {
-; CHECK-NEXT:    [[V1:%.*]] = tail call i8 @llvm.smax.i8(i8 [[X]], i8 -50)
-; CHECK-NEXT:    [[V2:%.*]] = tail call i8 @llvm.smin.i8(i8 [[V1]], i8 50)
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[V2]], [[X]]
+; CHECK-NEXT:    [[TMP1:%.*]] = add i8 [[X]], 50
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i8 [[TMP1]], 101
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %v1 = tail call i8 @llvm.smax.i8(i8 %x, i8 -50)
@@ -106,9 +97,8 @@ define i1 @test_i8_eq(i8 %x) {
 define i1 @test_i16_eq(i16 %x) {
 ; CHECK-LABEL: define i1 @test_i16_eq(
 ; CHECK-SAME: i16 [[X:%.*]]) {
-; CHECK-NEXT:    [[V1:%.*]] = tail call i16 @llvm.smax.i16(i16 [[X]], i16 -1000)
-; CHECK-NEXT:    [[V2:%.*]] = tail call i16 @llvm.smin.i16(i16 [[V1]], i16 1000)
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i16 [[V2]], [[X]]
+; CHECK-NEXT:    [[TMP1:%.*]] = add i16 [[X]], 1000
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i16 [[TMP1]], 2001
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %v1 = tail call i16 @llvm.smax.i16(i16 %x, i16 -1000)
@@ -120,9 +110,8 @@ define i1 @test_i16_eq(i16 %x) {
 define i1 @test_i64_eq(i64 %x) {
 ; CHECK-LABEL: define i1 @test_i64_eq(
 ; CHECK-SAME: i64 [[X:%.*]]) {
-; CHECK-NEXT:    [[V1:%.*]] = tail call i64 @llvm.smax.i64(i64 [[X]], i64 -1)
-; CHECK-NEXT:    [[V2:%.*]] = tail call i64 @llvm.smin.i64(i64 [[V1]], i64 9223372036854775806)
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i64 [[V2]], [[X]]
+; CHECK-NEXT:    [[TMP1:%.*]] = add i64 [[X]], 1
+; CHECK-NEXT:    [[CMP:%.*]] = icmp sgt i64 [[TMP1]], -1
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %v1 = tail call i64 @llvm.smax.i64(i64 %x, i64 -1)
@@ -250,9 +239,8 @@ define i1 @test_multi_use_min(i32 %x) {
 define i1 @test_commuted_eq(i32 %x) {
 ; CHECK-LABEL: define i1 @test_commuted_eq(
 ; CHECK-SAME: i32 [[X:%.*]]) {
-; CHECK-NEXT:    [[V1:%.*]] = tail call i32 @llvm.smax.i32(i32 [[X]], i32 -95)
-; CHECK-NEXT:    [[V2:%.*]] = tail call i32 @llvm.smin.i32(i32 [[V1]], i32 160)
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[X]], [[V2]]
+; CHECK-NEXT:    [[TMP1:%.*]] = add i32 [[X]], 95
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i32 [[TMP1]], 256
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %v1 = tail call i32 @llvm.smax.i32(i32 %x, i32 -95)
@@ -266,9 +254,8 @@ define i1 @test_commuted_eq(i32 %x) {
 define <2 x i1> @test_vec_splat_eq(<2 x i32> %x) {
 ; CHECK-LABEL: define <2 x i1> @test_vec_splat_eq(
 ; CHECK-SAME: <2 x i32> [[X:%.*]]) {
-; CHECK-NEXT:    [[V1:%.*]] = tail call <2 x i32> @llvm.smax.v2i32(<2 x i32> [[X]], <2 x i32> splat (i32 -50))
-; CHECK-NEXT:    [[V2:%.*]] = tail call <2 x i32> @llvm.smin.v2i32(<2 x i32> [[V1]], <2 x i32> splat (i32 50))
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq <2 x i32> [[V2]], [[X]]
+; CHECK-NEXT:    [[TMP1:%.*]] = add <2 x i32> [[X]], splat (i32 50)
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult <2 x i32> [[TMP1]], splat (i32 101)
 ; CHECK-NEXT:    ret <2 x i1> [[CMP]]
 ;
   %v1 = tail call <2 x i32> @llvm.smax.v2i32(<2 x i32> %x, <2 x i32> <i32 -50, i32 -50>)

>From 68c86f48ef16d8a7758f98efddd0d9a2e0722873 Mon Sep 17 00:00:00 2001
From: Brandon <brandon.xin at outlook.com>
Date: Tue, 30 Sep 2025 14:42:42 -0500
Subject: [PATCH 3/4] [InstCombine] Remove redundant checks

---
 llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp | 9 +--------
 1 file changed, 1 insertion(+), 8 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index 18bf3903715d5..a0535a2f360d8 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -5800,12 +5800,6 @@ Instruction *InstCombinerImpl::foldICmpWithClamp(ICmpInst &I, Value *X,
       return nullptr;
   }
 
-  // If Hi is the maximum value, the min operation becomes redundant and
-  // will be removed by other optimizations.
-  if ((Min->isSigned() && (Lo->isMinSignedValue() || Hi->isMaxSignedValue())) ||
-      (!Min->isSigned() && (Lo->isMinValue() || Hi->isMaxValue())))
-    return nullptr;
-
   ConstantRange CR(*Lo, *Hi + 1);
   ICmpInst::Predicate Pred;
   APInt C, Offset;
@@ -5814,8 +5808,7 @@ Instruction *InstCombinerImpl::foldICmpWithClamp(ICmpInst &I, Value *X,
   else
     CR.inverse().getEquivalentICmp(Pred, C, Offset);
 
-  if (Offset != 0)
-    X = Builder.CreateAdd(X, ConstantInt::get(X->getType(), Offset));
+  X = Builder.CreateAdd(X, ConstantInt::get(X->getType(), Offset));
 
   return replaceInstUsesWith(
       I, Builder.CreateICmp(Pred, X, ConstantInt::get(X->getType(), C)));

>From 4eefa5ef1172e9ca950e7edf318a11e2aa52fbf5 Mon Sep 17 00:00:00 2001
From: Brandon <brandon.xin at outlook.com>
Date: Tue, 30 Sep 2025 14:59:58 -0500
Subject: [PATCH 4/4] [InstCombine] Improve header comments

---
 .../Transforms/InstCombine/InstCombineCompares.cpp   | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index a0535a2f360d8..87458409af05f 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -5780,10 +5780,14 @@ Instruction *InstCombinerImpl::foldICmpWithMinMax(Instruction &I,
   return nullptr;
 }
 
-// Transform patterns like:
-//   icmp eq/ne X, min(max(X, Lo), Hi)
-// Into:
-//   (X - Lo) u< (Hi - Lo + 1)
+/// Match and fold patterns like:
+///   icmp eq/ne X, min(max(X, Lo), Hi)
+/// which represents a range check and can be repsented as a ConstantRange.
+///
+/// For icmp eq, build ConstantRange [Lo, Hi + 1) and convert to:
+///   (X - Lo) u< (Hi + 1 - Lo)
+/// For icmp ne, build ConstantRange [Hi + 1, Lo) and convert to:
+///   (X - (Hi + 1)) u< (Lo - (Hi + 1))
 Instruction *InstCombinerImpl::foldICmpWithClamp(ICmpInst &I, Value *X,
                                                  MinMaxIntrinsic *Min) {
   if (!I.isEquality() || !Min->hasOneUse())



More information about the llvm-commits mailing list