-
Notifications
You must be signed in to change notification settings - Fork 15.1k
[InstCombine] Fold icmp with clamp into unsigned bound check #161303
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
cffbd01
eab6eba
68c86f4
4eefa5e
9cde0b2
f306854
af64a82
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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); | ||
brandonxin marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,295 @@ | ||
| ; 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: [[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) | ||
| %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: [[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) | ||
| %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: [[CMP:%.*]] = icmp ult i32 [[X]], 161 | ||
| ; 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: [[CMP:%.*]] = icmp ugt i32 [[X]], 160 | ||
| ; 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: [[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) | ||
| %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: [[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) | ||
| %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: [[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) | ||
| %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: [[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) | ||
| %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: [[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) | ||
| %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: [[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) | ||
| %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: [[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>) | ||
| %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 | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use
///for header comments.