From 3d7db85f1bea70fc22e665929b74cd1ba2079ed2 Mon Sep 17 00:00:00 2001 From: Pedro Lobo Date: Fri, 15 Aug 2025 12:56:34 +0100 Subject: [PATCH 1/2] Pre-commit tests --- .../Transforms/InstCombine/and-or-icmps.ll | 207 ++++++++++++++++++ 1 file changed, 207 insertions(+) diff --git a/llvm/test/Transforms/InstCombine/and-or-icmps.ll b/llvm/test/Transforms/InstCombine/and-or-icmps.ll index 42e5020748129..d679ab13baebc 100644 --- a/llvm/test/Transforms/InstCombine/and-or-icmps.ll +++ b/llvm/test/Transforms/InstCombine/and-or-icmps.ll @@ -3491,3 +3491,210 @@ define i1 @and_icmp_eq_with_binary_range_operands(i8 range(i8 0, 2) %x, i8 range %ret = and i1 %icmp1, %icmp2 ret i1 %ret } + +define i1 @or_icmp_eq_and_pow2(i32 %x) { +; CHECK-LABEL: @or_icmp_eq_and_pow2( +; CHECK-NEXT: [[ICMP1:%.*]] = icmp eq i32 [[X:%.*]], 127 +; CHECK-NEXT: [[AND:%.*]] = and i32 [[X]], -32 +; CHECK-NEXT: [[ICMP2:%.*]] = icmp eq i32 [[AND]], 128 +; CHECK-NEXT: [[RET:%.*]] = or i1 [[ICMP1]], [[ICMP2]] +; CHECK-NEXT: ret i1 [[RET]] +; + %icmp1 = icmp eq i32 %x, 127 + %and = and i32 %x, -32 + %icmp2 = icmp eq i32 %and, 128 + %ret = or i1 %icmp1, %icmp2 + ret i1 %ret +} + +define i1 @or_icmp_eq_and_pow2_mask_equal_to_icmp(i32 %x) { +; CHECK-LABEL: @or_icmp_eq_and_pow2_mask_equal_to_icmp( +; CHECK-NEXT: [[ICMP1:%.*]] = icmp eq i32 [[X:%.*]], 63 +; CHECK-NEXT: [[AND:%.*]] = and i32 [[X]], -64 +; CHECK-NEXT: [[ICMP2:%.*]] = icmp eq i32 [[AND]], 64 +; CHECK-NEXT: [[RET:%.*]] = or i1 [[ICMP1]], [[ICMP2]] +; CHECK-NEXT: ret i1 [[RET]] +; + %icmp1 = icmp eq i32 %x, 63 + %and = and i32 %x, -64 + %icmp2 = icmp eq i32 %and, 64 + %ret = or i1 %icmp1, %icmp2 + ret i1 %ret +} + +define i1 @and_icmp_ne_and_pow2(i32 %x) { +; CHECK-LABEL: @and_icmp_ne_and_pow2( +; CHECK-NEXT: [[ICMP1:%.*]] = icmp ne i32 [[X:%.*]], 127 +; CHECK-NEXT: [[AND:%.*]] = and i32 [[X]], -32 +; CHECK-NEXT: [[ICMP2:%.*]] = icmp ne i32 [[AND]], 128 +; CHECK-NEXT: [[RET:%.*]] = and i1 [[ICMP1]], [[ICMP2]] +; CHECK-NEXT: ret i1 [[RET]] +; + %icmp1 = icmp ne i32 %x, 127 + %and = and i32 %x, -32 + %icmp2 = icmp ne i32 %and, 128 + %ret = and i1 %icmp1, %icmp2 + ret i1 %ret +} + +define i1 @and_icmp_ne_and_pow2_mask_equal_to_icmp(i32 %x) { +; CHECK-LABEL: @and_icmp_ne_and_pow2_mask_equal_to_icmp( +; CHECK-NEXT: [[ICMP1:%.*]] = icmp ne i32 [[X:%.*]], 63 +; CHECK-NEXT: [[AND:%.*]] = and i32 [[X]], -64 +; CHECK-NEXT: [[ICMP2:%.*]] = icmp ne i32 [[AND]], 64 +; CHECK-NEXT: [[RET:%.*]] = and i1 [[ICMP1]], [[ICMP2]] +; CHECK-NEXT: ret i1 [[RET]] +; + %icmp1 = icmp ne i32 %x, 63 + %and = and i32 %x, -64 + %icmp2 = icmp ne i32 %and, 64 + %ret = and i1 %icmp1, %icmp2 + ret i1 %ret +} + +define i1 @or_icmp_eq_and_pow2_commute(i32 %x) { +; CHECK-LABEL: @or_icmp_eq_and_pow2_commute( +; CHECK-NEXT: [[ICMP1:%.*]] = and i32 [[X:%.*]], -32 +; CHECK-NEXT: [[AND:%.*]] = icmp eq i32 [[ICMP1]], 128 +; CHECK-NEXT: [[ICMP2:%.*]] = icmp eq i32 [[X]], 127 +; CHECK-NEXT: [[TMP4:%.*]] = or i1 [[AND]], [[ICMP2]] +; CHECK-NEXT: ret i1 [[TMP4]] +; + %and = and i32 %x, -32 + %icmp1 = icmp eq i32 %and, 128 + %icmp2 = icmp eq i32 %x, 127 + %ret = or i1 %icmp1, %icmp2 + ret i1 %ret +} + +define i1 @and_icmp_ne_and_pow2_commute(i32 %x) { +; CHECK-LABEL: @and_icmp_ne_and_pow2_commute( +; CHECK-NEXT: [[AND:%.*]] = and i32 [[X:%.*]], -32 +; CHECK-NEXT: [[ICMP1:%.*]] = icmp ne i32 [[AND]], 128 +; CHECK-NEXT: [[ICMP2:%.*]] = icmp ne i32 [[X]], 127 +; CHECK-NEXT: [[RET:%.*]] = and i1 [[ICMP1]], [[ICMP2]] +; CHECK-NEXT: ret i1 [[RET]] +; + %and = and i32 %x, -32 + %icmp1 = icmp ne i32 %and, 128 + %icmp2 = icmp ne i32 %x, 127 + %ret = and i1 %icmp1, %icmp2 + ret i1 %ret +} + +define i1 @neg_or_icmp_eq_and_pow2_multi_use(i32 %x) { +; CHECK-LABEL: @neg_or_icmp_eq_and_pow2_multi_use( +; CHECK-NEXT: [[ICMP1:%.*]] = icmp eq i32 [[X:%.*]], 127 +; CHECK-NEXT: [[AND:%.*]] = and i32 [[X]], -32 +; CHECK-NEXT: call void @use32(i32 [[AND]]) +; CHECK-NEXT: [[ICMP2:%.*]] = icmp eq i32 [[AND]], 128 +; CHECK-NEXT: [[RET:%.*]] = or i1 [[ICMP1]], [[ICMP2]] +; CHECK-NEXT: ret i1 [[RET]] +; + %icmp1 = icmp eq i32 %x, 127 + %and = and i32 %x, -32 + call void @use32(i32 %and) + %icmp2 = icmp eq i32 %and, 128 + %ret = or i1 %icmp1, %icmp2 + ret i1 %ret +} + +define i1 @neg_and_icmp_eq_and_pow2(i32 %x) { +; CHECK-LABEL: @neg_and_icmp_eq_and_pow2( +; CHECK-NEXT: ret i1 false +; + %icmp1 = icmp eq i32 %x, 127 + %and = and i32 %x, -32 + %icmp2 = icmp eq i32 %and, 128 + %ret = and i1 %icmp1, %icmp2 + ret i1 %ret +} + +define i1 @neg_or_icmp_eq_and_non_pow2_mask(i32 %x) { +; CHECK-LABEL: @neg_or_icmp_eq_and_non_pow2_mask( +; CHECK-NEXT: [[TMP1:%.*]] = icmp eq i32 [[X:%.*]], 127 +; CHECK-NEXT: [[TMP2:%.*]] = and i32 [[X]], -33 +; CHECK-NEXT: [[TMP3:%.*]] = icmp eq i32 [[TMP2]], 128 +; CHECK-NEXT: [[TMP4:%.*]] = or i1 [[TMP1]], [[TMP3]] +; CHECK-NEXT: ret i1 [[TMP4]] +; + %icmp1 = icmp eq i32 %x, 127 + %and = and i32 %x, -33 + %icmp2 = icmp eq i32 %and, 128 + %ret = or i1 %icmp1, %icmp2 + ret i1 %ret +} + +define i1 @neg_and_icmp_ne_and_non_pow2_icmp(i32 %x) { +; CHECK-LABEL: @neg_and_icmp_ne_and_non_pow2_icmp( +; CHECK-NEXT: [[ICMP1:%.*]] = icmp ne i32 [[X:%.*]], 127 +; CHECK-NEXT: [[AND:%.*]] = and i32 [[X]], -33 +; CHECK-NEXT: [[ICMP2:%.*]] = icmp ne i32 [[AND]], 128 +; CHECK-NEXT: [[RET:%.*]] = and i1 [[ICMP1]], [[ICMP2]] +; CHECK-NEXT: ret i1 [[RET]] +; + %icmp1 = icmp ne i32 %x, 127 + %and = and i32 %x, -33 + %icmp2 = icmp ne i32 %and, 128 + %ret = and i1 %icmp1, %icmp2 + ret i1 %ret +} + +define i1 @neg_or_icmp_eq_and_const_less_than_mask(i32 %x) { +; CHECK-LABEL: @neg_or_icmp_eq_and_const_less_than_mask( +; CHECK-NEXT: [[TMP1:%.*]] = icmp eq i32 [[X:%.*]], 15 +; CHECK-NEXT: ret i1 [[TMP1]] +; + %icmp1 = icmp eq i32 %x, 15 + %and = and i32 %x, -32 + %icmp2 = icmp eq i32 %and, 16 + %ret = or i1 %icmp1, %icmp2 + ret i1 %ret +} + +define i1 @neg_and_icmp_ne_and_pow2_disjoint(i32 %x) { +; CHECK-LABEL: @neg_and_icmp_ne_and_pow2_disjoint( +; CHECK-NEXT: [[ICMP1:%.*]] = icmp ne i32 [[X:%.*]], 126 +; CHECK-NEXT: [[AND:%.*]] = and i32 [[X]], -32 +; CHECK-NEXT: [[ICMP2:%.*]] = icmp ne i32 [[AND]], 128 +; CHECK-NEXT: [[RET:%.*]] = and i1 [[ICMP1]], [[ICMP2]] +; CHECK-NEXT: ret i1 [[RET]] +; + %icmp1 = icmp ne i32 %x, 126 + %and = and i32 %x, -32 + %icmp2 = icmp ne i32 %and, 128 + %ret = and i1 %icmp1, %icmp2 + ret i1 %ret +} + +define i1 @neg_or_icmp_eq_double_and_pow2(i32 %x) { +; CHECK-LABEL: @neg_or_icmp_eq_double_and_pow2( +; CHECK-NEXT: [[TMP1:%.*]] = and i32 [[X:%.*]], -16 +; CHECK-NEXT: [[TMP2:%.*]] = icmp eq i32 [[TMP1]], 64 +; CHECK-NEXT: [[AND2:%.*]] = and i32 [[X]], -32 +; CHECK-NEXT: [[TMP4:%.*]] = icmp eq i32 [[AND2]], 128 +; CHECK-NEXT: [[TMP5:%.*]] = or i1 [[TMP2]], [[TMP4]] +; CHECK-NEXT: ret i1 [[TMP5]] +; + %and1 = and i32 %x, -16 + %icmp1 = icmp eq i32 %and1, 64 + %and2 = and i32 %x, -32 + %icmp2 = icmp eq i32 %and2, 128 + %ret = or i1 %icmp1, %icmp2 + ret i1 %ret +} + +define i1 @neg_select_icmp_eq_and_pow2(i32 %x) { +; CHECK-LABEL: @neg_select_icmp_eq_and_pow2( +; CHECK-NEXT: [[ICMP1:%.*]] = icmp sgt i32 [[X:%.*]], 127 +; CHECK-NEXT: [[AND:%.*]] = and i32 [[X]], -32 +; CHECK-NEXT: [[ICMP2:%.*]] = icmp eq i32 [[AND]], 128 +; CHECK-NEXT: [[TMP1:%.*]] = and i1 [[ICMP1]], [[ICMP2]] +; CHECK-NEXT: ret i1 [[TMP1]] +; + %icmp1 = icmp sgt i32 %x, 127 + %and = and i32 %x, -32 + %icmp2 = icmp eq i32 %and, 128 + %1 = select i1 %icmp1, i1 %icmp2, i1 false + ret i1 %1 +} From 434c0927d9c49af70dd7d4d38c5b2afd4ce1099f Mon Sep 17 00:00:00 2001 From: Pedro Lobo Date: Fri, 15 Aug 2025 12:59:59 +0100 Subject: [PATCH 2/2] [InstCombine] Fold `(x == A) || (x & -Pow2) == A + 1` into range check Extends `foldAndOrOfICmpsUsingRanges` to recognize and fold idioms of the form `(x == A) || (x & -Pow2) == A + 1`, where A + 1 is aligned to the mask and A > |mask|. These patterns represent a special case of contiguous range checks and can be optimized into an addition and comparison. ```llvm define i1 @src(i32 %x) { %icmp1 = icmp eq i32 %x, 127 %and = and i32 %x, -32 %icmp2 = icmp eq i32 %and, 128 %ret = or i1 %icmp1, %icmp2 ret i1 %ret } define i1 @tgt(i32 %x) { %1 = add i32 %x, -127 %2 = icmp ult i32 %1, 33 ret i1 %2 } ``` The same can be done for a conjunction of inequalities, of the form `(x != A) && (x & -Pow2) != A + 1`. ```llvm define i1 @src(i32 %x) { %icmp1 = icmp ne i32 %x, 127 %and = and i32 %x, -32 %icmp2 = icmp ne i32 %and, 128 %result = and i1 %icmp1, %icmp2 ret i1 %result } define i1 @tgt(i32 %x) { %1 = add i32 %x, -160 %2 = icmp ult i32 %1, -33 ret i1 %2 } ``` Alive2 Proof: - Equality: https://alive2.llvm.org/ce/z/gwELqs - Inequality: https://alive2.llvm.org/ce/z/ZPdSDt --- .../InstCombine/InstCombineAndOrXor.cpp | 43 +++++++++++++++++-- .../Transforms/InstCombine/and-or-icmps.ll | 36 ++++++---------- 2 files changed, 51 insertions(+), 28 deletions(-) diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp index d7971e8e3caea..912ef5999e964 100644 --- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp +++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp @@ -1338,16 +1338,51 @@ Value *InstCombinerImpl::foldAndOrOfICmpsUsingRanges(ICmpInst *ICmp1, V2 = X; } + // Look through and with a negative power of 2 mask on V1 or V2. This + // detects idioms of the form `(x == A) || ((x & Mask) == A + 1)` where A + 1 + // is aligned to the mask and A + 1 >= |Mask|. This pattern corresponds to a + // contiguous range check, which can be folded into an addition and compare. + // The same applies for `(x != A) && ((x & Mask) != A + 1)`. + auto AreContiguousRangePredicates = [](CmpPredicate Pred1, CmpPredicate Pred2, + bool IsAnd) { + if (IsAnd) + return Pred1 == ICmpInst::ICMP_NE && Pred2 == ICmpInst::ICMP_NE; + return Pred1 == ICmpInst::ICMP_EQ && Pred2 == ICmpInst::ICMP_EQ; + }; + const APInt *Mask1 = nullptr, *Mask2 = nullptr; + bool MatchedAnd1 = false, MatchedAnd2 = false; + if (V1 != V2 && AreContiguousRangePredicates(Pred1, Pred2, IsAnd)) { + Value *X; + if (match(V1, m_OneUse(m_And(m_Value(X), m_NegatedPower2(Mask1)))) && + C1->getBitWidth() == C2->getBitWidth() && *C1 == *C2 + 1 && + C1->uge(Mask1->abs()) && C1->isPowerOf2()) { + MatchedAnd1 = true; + V1 = X; + } + if (match(V2, m_OneUse(m_And(m_Value(X), m_NegatedPower2(Mask2)))) && + C1->getBitWidth() == C2->getBitWidth() && *C2 == *C1 + 1 && + C2->uge(Mask2->abs()) && C2->isPowerOf2()) { + MatchedAnd2 = true; + V2 = X; + } + } + if (V1 != V2) return nullptr; - ConstantRange CR1 = ConstantRange::makeExactICmpRegion( - IsAnd ? ICmpInst::getInverseCmpPredicate(Pred1) : Pred1, *C1); + ConstantRange CR1 = + MatchedAnd1 + ? ConstantRange(*C1, *C1 - *Mask1) + : ConstantRange::makeExactICmpRegion( + IsAnd ? ICmpInst::getInverseCmpPredicate(Pred1) : Pred1, *C1); if (Offset1) CR1 = CR1.subtract(*Offset1); - ConstantRange CR2 = ConstantRange::makeExactICmpRegion( - IsAnd ? ICmpInst::getInverseCmpPredicate(Pred2) : Pred2, *C2); + ConstantRange CR2 = + MatchedAnd2 + ? ConstantRange(*C2, *C2 - *Mask2) + : ConstantRange::makeExactICmpRegion( + IsAnd ? ICmpInst::getInverseCmpPredicate(Pred2) : Pred2, *C2); if (Offset2) CR2 = CR2.subtract(*Offset2); diff --git a/llvm/test/Transforms/InstCombine/and-or-icmps.ll b/llvm/test/Transforms/InstCombine/and-or-icmps.ll index d679ab13baebc..553c7ac5af0e9 100644 --- a/llvm/test/Transforms/InstCombine/and-or-icmps.ll +++ b/llvm/test/Transforms/InstCombine/and-or-icmps.ll @@ -3494,10 +3494,8 @@ define i1 @and_icmp_eq_with_binary_range_operands(i8 range(i8 0, 2) %x, i8 range define i1 @or_icmp_eq_and_pow2(i32 %x) { ; CHECK-LABEL: @or_icmp_eq_and_pow2( -; CHECK-NEXT: [[ICMP1:%.*]] = icmp eq i32 [[X:%.*]], 127 -; CHECK-NEXT: [[AND:%.*]] = and i32 [[X]], -32 -; CHECK-NEXT: [[ICMP2:%.*]] = icmp eq i32 [[AND]], 128 -; CHECK-NEXT: [[RET:%.*]] = or i1 [[ICMP1]], [[ICMP2]] +; CHECK-NEXT: [[TMP1:%.*]] = add i32 [[X:%.*]], -127 +; CHECK-NEXT: [[RET:%.*]] = icmp ult i32 [[TMP1]], 33 ; CHECK-NEXT: ret i1 [[RET]] ; %icmp1 = icmp eq i32 %x, 127 @@ -3509,10 +3507,8 @@ define i1 @or_icmp_eq_and_pow2(i32 %x) { define i1 @or_icmp_eq_and_pow2_mask_equal_to_icmp(i32 %x) { ; CHECK-LABEL: @or_icmp_eq_and_pow2_mask_equal_to_icmp( -; CHECK-NEXT: [[ICMP1:%.*]] = icmp eq i32 [[X:%.*]], 63 -; CHECK-NEXT: [[AND:%.*]] = and i32 [[X]], -64 -; CHECK-NEXT: [[ICMP2:%.*]] = icmp eq i32 [[AND]], 64 -; CHECK-NEXT: [[RET:%.*]] = or i1 [[ICMP1]], [[ICMP2]] +; CHECK-NEXT: [[TMP1:%.*]] = add i32 [[X:%.*]], -63 +; CHECK-NEXT: [[RET:%.*]] = icmp ult i32 [[TMP1]], 65 ; CHECK-NEXT: ret i1 [[RET]] ; %icmp1 = icmp eq i32 %x, 63 @@ -3524,10 +3520,8 @@ define i1 @or_icmp_eq_and_pow2_mask_equal_to_icmp(i32 %x) { define i1 @and_icmp_ne_and_pow2(i32 %x) { ; CHECK-LABEL: @and_icmp_ne_and_pow2( -; CHECK-NEXT: [[ICMP1:%.*]] = icmp ne i32 [[X:%.*]], 127 -; CHECK-NEXT: [[AND:%.*]] = and i32 [[X]], -32 -; CHECK-NEXT: [[ICMP2:%.*]] = icmp ne i32 [[AND]], 128 -; CHECK-NEXT: [[RET:%.*]] = and i1 [[ICMP1]], [[ICMP2]] +; CHECK-NEXT: [[TMP1:%.*]] = add i32 [[X:%.*]], -160 +; CHECK-NEXT: [[RET:%.*]] = icmp ult i32 [[TMP1]], -33 ; CHECK-NEXT: ret i1 [[RET]] ; %icmp1 = icmp ne i32 %x, 127 @@ -3539,10 +3533,8 @@ define i1 @and_icmp_ne_and_pow2(i32 %x) { define i1 @and_icmp_ne_and_pow2_mask_equal_to_icmp(i32 %x) { ; CHECK-LABEL: @and_icmp_ne_and_pow2_mask_equal_to_icmp( -; CHECK-NEXT: [[ICMP1:%.*]] = icmp ne i32 [[X:%.*]], 63 -; CHECK-NEXT: [[AND:%.*]] = and i32 [[X]], -64 -; CHECK-NEXT: [[ICMP2:%.*]] = icmp ne i32 [[AND]], 64 -; CHECK-NEXT: [[RET:%.*]] = and i1 [[ICMP1]], [[ICMP2]] +; CHECK-NEXT: [[TMP1:%.*]] = add i32 [[X:%.*]], -128 +; CHECK-NEXT: [[RET:%.*]] = icmp ult i32 [[TMP1]], -65 ; CHECK-NEXT: ret i1 [[RET]] ; %icmp1 = icmp ne i32 %x, 63 @@ -3554,10 +3546,8 @@ define i1 @and_icmp_ne_and_pow2_mask_equal_to_icmp(i32 %x) { define i1 @or_icmp_eq_and_pow2_commute(i32 %x) { ; CHECK-LABEL: @or_icmp_eq_and_pow2_commute( -; CHECK-NEXT: [[ICMP1:%.*]] = and i32 [[X:%.*]], -32 -; CHECK-NEXT: [[AND:%.*]] = icmp eq i32 [[ICMP1]], 128 -; CHECK-NEXT: [[ICMP2:%.*]] = icmp eq i32 [[X]], 127 -; CHECK-NEXT: [[TMP4:%.*]] = or i1 [[AND]], [[ICMP2]] +; CHECK-NEXT: [[TMP1:%.*]] = add i32 [[X:%.*]], -127 +; CHECK-NEXT: [[TMP4:%.*]] = icmp ult i32 [[TMP1]], 33 ; CHECK-NEXT: ret i1 [[TMP4]] ; %and = and i32 %x, -32 @@ -3569,10 +3559,8 @@ define i1 @or_icmp_eq_and_pow2_commute(i32 %x) { define i1 @and_icmp_ne_and_pow2_commute(i32 %x) { ; CHECK-LABEL: @and_icmp_ne_and_pow2_commute( -; CHECK-NEXT: [[AND:%.*]] = and i32 [[X:%.*]], -32 -; CHECK-NEXT: [[ICMP1:%.*]] = icmp ne i32 [[AND]], 128 -; CHECK-NEXT: [[ICMP2:%.*]] = icmp ne i32 [[X]], 127 -; CHECK-NEXT: [[RET:%.*]] = and i1 [[ICMP1]], [[ICMP2]] +; CHECK-NEXT: [[TMP1:%.*]] = add i32 [[X:%.*]], -160 +; CHECK-NEXT: [[RET:%.*]] = icmp ult i32 [[TMP1]], -33 ; CHECK-NEXT: ret i1 [[RET]] ; %and = and i32 %x, -32