From cffbd0176cc5bdaa0f5a6d0150c548c23ca1bdfa Mon Sep 17 00:00:00 2001 From: Brandon Date: Mon, 29 Sep 2025 17:23:27 -0500 Subject: [PATCH 1/7] [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> ) + %v2 = tail call <2 x i32> @llvm.smin.v2i32(<2 x i32> %v1, <2 x i32> ) + %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> ) +; CHECK-NEXT: [[V2:%.*]] = tail call <2 x i32> @llvm.smin.v2i32(<2 x i32> [[V1]], <2 x i32> ) +; 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> ) + %v2 = tail call <2 x i32> @llvm.smin.v2i32(<2 x i32> %v1, <2 x i32> ) + %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> ) +; CHECK-NEXT: [[V2:%.*]] = tail call <2 x i32> @llvm.smin.v2i32(<2 x i32> [[V1]], <2 x i32> ) +; 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> ) + %v2 = tail call <2 x i32> @llvm.smin.v2i32(<2 x i32> %v1, <2 x i32> ) + %cmp = icmp eq <2 x i32> %v2, %x + ret <2 x i1> %cmp +} From eab6eba048dd4601f8e0bd67c67bd9e8a8ae6ad4 Mon Sep 17 00:00:00 2001 From: Brandon Date: Mon, 29 Sep 2025 19:20:21 -0500 Subject: [PATCH 2/7] [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(Op0)) + if (auto *MinMax = dyn_cast(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> ) From 68c86f48ef16d8a7758f98efddd0d9a2e0722873 Mon Sep 17 00:00:00 2001 From: Brandon Date: Tue, 30 Sep 2025 14:42:42 -0500 Subject: [PATCH 3/7] [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 Date: Tue, 30 Sep 2025 14:59:58 -0500 Subject: [PATCH 4/7] [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()) From 9cde0b26a9e39a5dd70484aa17726cae01c1bc35 Mon Sep 17 00:00:00 2001 From: Brandon Date: Wed, 1 Oct 2025 10:46:52 -0500 Subject: [PATCH 5/7] [InstCombine] Check if Min is actually a Min. --- llvm/include/llvm/IR/IntrinsicInst.h | 20 +++++++++++++++++++ .../InstCombine/InstCombineCompares.cpp | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/llvm/include/llvm/IR/IntrinsicInst.h b/llvm/include/llvm/IR/IntrinsicInst.h index eb0440f500735..0622bfae2c845 100644 --- a/llvm/include/llvm/IR/IntrinsicInst.h +++ b/llvm/include/llvm/IR/IntrinsicInst.h @@ -810,6 +810,26 @@ class MinMaxIntrinsic : public IntrinsicInst { /// Whether the intrinsic is signed or unsigned. bool isSigned() const { return isSigned(getIntrinsicID()); }; + /// Whether the intrinsic is a smin or umin. + static bool isMin(Intrinsic::ID ID) { + switch (ID) { + case Intrinsic::umin: + case Intrinsic::smin: + return true; + case Intrinsic::umax: + case Intrinsic::smax: + return false; + default: + llvm_unreachable("Invalid intrinsic"); + } + } + + /// Whether the intrinsic is a smin or a umin. + bool isMin() const { return isMin(getIntrinsicID()); } + + /// Whether the intrinsic is a smax or a umax. + bool isMax() const { return !isMin(getIntrinsicID()); } + /// Min/max intrinsics are monotonic, they operate on a fixed-bitwidth values, /// so there is a certain threshold value, upon reaching which, /// their value can no longer change. Return said threshold. diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp index 87458409af05f..5a0cabb71371f 100644 --- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp +++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp @@ -5790,7 +5790,7 @@ Instruction *InstCombinerImpl::foldICmpWithMinMax(Instruction &I, /// (X - (Hi + 1)) u< (Lo - (Hi + 1)) Instruction *InstCombinerImpl::foldICmpWithClamp(ICmpInst &I, Value *X, MinMaxIntrinsic *Min) { - if (!I.isEquality() || !Min->hasOneUse()) + if (!I.isEquality() || !Min->hasOneUse() || !Min->isMin()) return nullptr; const APInt *Lo = nullptr, *Hi = nullptr; From f3068541c25e9b7de6c076cf5ee6953a343580f9 Mon Sep 17 00:00:00 2001 From: Brandon Date: Wed, 1 Oct 2025 10:56:50 -0500 Subject: [PATCH 6/7] [InstCombine] Avoid unnecessary addition when offset is zero --- llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp index 5a0cabb71371f..bbdf7b6979508 100644 --- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp +++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp @@ -5812,7 +5812,8 @@ Instruction *InstCombinerImpl::foldICmpWithClamp(ICmpInst &I, Value *X, else CR.inverse().getEquivalentICmp(Pred, C, Offset); - X = Builder.CreateAdd(X, ConstantInt::get(X->getType(), Offset)); + if (!Offset.isZero()) + X = Builder.CreateAdd(X, ConstantInt::get(X->getType(), Offset)); return replaceInstUsesWith( I, Builder.CreateICmp(Pred, X, ConstantInt::get(X->getType(), C))); From af64a82a130b5d2ede46761751527a49b26d5544 Mon Sep 17 00:00:00 2001 From: Brandon <61314499+brandonxin@users.noreply.github.com> Date: Wed, 1 Oct 2025 11:14:38 -0500 Subject: [PATCH 7/7] [InstComine] Use getNonEmpty to handle edge case Co-authored-by: Yingwei Zheng --- llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp index bbdf7b6979508..74f035e401bde 100644 --- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp +++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp @@ -5804,7 +5804,7 @@ Instruction *InstCombinerImpl::foldICmpWithClamp(ICmpInst &I, Value *X, return nullptr; } - ConstantRange CR(*Lo, *Hi + 1); + ConstantRange CR = ConstantRange::getNonEmpty(*Lo, *Hi + 1); ICmpInst::Predicate Pred; APInt C, Offset; if (I.getPredicate() == ICmpInst::ICMP_EQ)