[llvm] [InstCombine] (PR #231020)
via llvm-commits
llvm-commits at lists.llvm.org
Sun Oct 11 11:30:14 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-transforms
Author: Namdar MohammadMehdi (memehdi14)
<details>
<summary>Changes</summary>
test
---
Full diff: https://github.com/llvm/llvm-project/pull/231020.diff
2 Files Affected:
- (modified) llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp (+109-32)
- (added) llvm/test/Transforms/InstCombine/and-icmp-range-masked-eq.ll (+248)
``````````diff
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index ef3ccf8c647bf..48b5dc0c42011 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -16,6 +16,7 @@
#include "llvm/Analysis/FloatingPointPredicateUtils.h"
#include "llvm/Analysis/InstructionSimplify.h"
#include "llvm/IR/ConstantRange.h"
+#include "llvm/Support/KnownBits.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/Intrinsics.h"
@@ -1334,6 +1335,33 @@ foldAndOrOfICmpsWithConstEq(CmpPredicate PredL, Value *LHS0, Value *LHS1,
SubstituteCmp);
}
+/// Return (V, CR) for a range check idiom V in CR.
+static std::optional<std::pair<Value *, ConstantRange>>
+matchExactRangeCheck(CmpPredicate Pred, Value *LHS, Value *RHS) {
+ const APInt *C;
+ if (!match(RHS, m_APInt(C)))
+ return std::nullopt;
+
+ Value *X;
+ // Match (x & NegPow2) ==/!= C
+ const APInt *Mask;
+ if (ICmpInst::isEquality(Pred) &&
+ match(LHS, m_OneUse(m_And(m_Value(X), m_NegatedPower2(Mask)))) &&
+ C->countr_zero() >= Mask->countr_zero()) {
+ ConstantRange CR(*C, *C - *Mask);
+ if (Pred == ICmpInst::ICMP_NE)
+ CR = CR.inverse();
+ return std::make_pair(X, CR);
+ }
+ ConstantRange CR = ConstantRange::makeExactICmpRegion(Pred, *C);
+ // Match (add X, C1) pred C
+ // TODO: investigate whether we should apply the one-use check on m_AddLike.
+ const APInt *C1;
+ if (match(LHS, m_AddLike(m_Value(X), m_APInt(C1))))
+ return std::make_pair(X, CR.subtract(*C1));
+ return std::make_pair(LHS, CR);
+}
+
/// Fold (icmp Pred1 V1, C1) & (icmp Pred2 V2, C2)
/// or (icmp Pred1 V1, C1) | (icmp Pred2 V2, C2)
/// into a single comparison using range-based reasoning.
@@ -1341,39 +1369,11 @@ foldAndOrOfICmpsWithConstEq(CmpPredicate PredL, Value *LHS0, Value *LHS1,
Value *InstCombinerImpl::foldAndOrOfICmpsUsingRanges(
CmpPredicate PredL, Value *LHS0, Value *LHS1, bool LHSOneUse,
CmpPredicate PredR, Value *RHS0, Value *RHS1, bool RHSOneUse, bool IsAnd) {
- // Return (V, CR) for a range check idiom V in CR.
- auto MatchExactRangeCheck =
- [](CmpPredicate Pred, Value *LHS,
- Value *RHS) -> std::optional<std::pair<Value *, ConstantRange>> {
- const APInt *C;
- if (!match(RHS, m_APInt(C)))
- return std::nullopt;
-
- Value *X;
- // Match (x & NegPow2) ==/!= C
- const APInt *Mask;
- if (ICmpInst::isEquality(Pred) &&
- match(LHS, m_OneUse(m_And(m_Value(X), m_NegatedPower2(Mask)))) &&
- C->countr_zero() >= Mask->countr_zero()) {
- ConstantRange CR(*C, *C - *Mask);
- if (Pred == ICmpInst::ICMP_NE)
- CR = CR.inverse();
- return std::make_pair(X, CR);
- }
- ConstantRange CR = ConstantRange::makeExactICmpRegion(Pred, *C);
- // Match (add X, C1) pred C
- // TODO: investigate whether we should apply the one-use check on m_AddLike.
- const APInt *C1;
- if (match(LHS, m_AddLike(m_Value(X), m_APInt(C1))))
- return std::make_pair(X, CR.subtract(*C1));
- return std::make_pair(LHS, CR);
- };
-
- auto RC1 = MatchExactRangeCheck(PredL, LHS0, LHS1);
+ auto RC1 = matchExactRangeCheck(PredL, LHS0, LHS1);
if (!RC1)
return nullptr;
- auto RC2 = MatchExactRangeCheck(PredR, RHS0, RHS1);
+ auto RC2 = matchExactRangeCheck(PredR, RHS0, RHS1);
if (!RC2)
return nullptr;
@@ -1420,6 +1420,74 @@ Value *InstCombinerImpl::foldAndOrOfICmpsUsingRanges(
return Builder.CreateICmp(NewPred, NewV, ConstantInt::get(Ty, NewC));
}
+/// Fold (range check on X) & ((X & Mask) == C).
+///
+/// The masked equality fixes some bits of X, which bounds X to a (possibly
+/// loose) constant range. If intersecting that range with the exact range of
+/// the other compare leaves at most one candidate, the 'and' folds to 'false'
+/// or 'icmp eq X, Candidate'. The candidate is re-checked against both
+/// original predicates, so the loose range is only used to find it.
+///
+/// This only uses X, so it is also correct for logical and (poison-safe).
+static Value *foldAndOfRangeAndMaskedEq(
+ CmpPredicate PredL, Value *LHS0, Value *LHS1,
+ CmpPredicate PredR, Value *RHS0, Value *RHS1,
+ Instruction &I, InstCombiner::BuilderTy &Builder) {
+ for (int Swap = 0; Swap != 2; ++Swap) {
+ CmpPredicate RangePred = Swap ? PredR : PredL;
+ Value *RangeLHS = Swap ? RHS0 : LHS0;
+ Value *RangeRHS = Swap ? RHS1 : LHS1;
+
+ CmpPredicate MaskPred = Swap ? PredL : PredR;
+ Value *MaskLHS = Swap ? LHS0 : RHS0;
+ Value *MaskRHS = Swap ? LHS1 : RHS1;
+
+ // Masked compare: (X & Mask) == MaskedC
+ if (MaskPred.dropSameSign() != ICmpInst::ICMP_EQ)
+ continue;
+
+ Value *X;
+ const APInt *Mask, *MaskedC;
+ if (!match(MaskRHS, m_APInt(MaskedC)))
+ continue;
+ if (!match(MaskLHS, m_c_And(m_Value(X), m_APInt(Mask))))
+ continue;
+ if (!MaskedC->isSubsetOf(*Mask))
+ continue;
+
+ // Range compare on X
+ auto RC = matchExactRangeCheck(RangePred, RangeLHS, RangeRHS);
+ if (!RC)
+ continue;
+
+ auto &[RangeX, CR] = *RC;
+ if (RangeX != X)
+ continue;
+
+ // Loose set of X implied by the masked equality.
+ KnownBits Known(Mask->getBitWidth());
+ Known.One = *MaskedC;
+ Known.Zero = *Mask & ~*MaskedC;
+ ConstantRange Loose =
+ ConstantRange::fromKnownBits(Known, /*IsSigned=*/false)
+ .intersectWith(
+ ConstantRange::fromKnownBits(Known, /*IsSigned=*/true));
+
+ ConstantRange Cand = CR.intersectWith(Loose);
+ Type *ResTy = CmpInst::makeCmpResultType(X->getType());
+ if (Cand.isEmptySet())
+ return ConstantInt::getFalse(ResTy);
+
+ if (const APInt *V = Cand.getSingleElement()) {
+ if (!CR.contains(*V) || (*V & *Mask) != *MaskedC)
+ return ConstantInt::getFalse(ResTy);
+ return Builder.CreateICmpEQ(X, ConstantInt::get(X->getType(), *V));
+ }
+ }
+
+ return nullptr;
+}
+
/// Matches canonical form of isnan, fcmp ord x, 0
static bool matchIsNotNaN(FCmpInst::Predicate P, Value *LHS, Value *RHS) {
return P == FCmpInst::FCMP_ORD && match(RHS, m_AnyZeroFP());
@@ -3680,8 +3748,17 @@ Value *InstCombinerImpl::foldAndOrOfICmps(Value *LHS, Value *RHS,
return Builder.createIsFPClass(X, IsAnd ? FPClassTest::fcNormal
: ~FPClassTest::fcNormal);
- return foldAndOrOfICmpsUsingRanges(PredL, LHS0, LHS1, LHSOneUse, PredR, RHS0,
- RHS1, RHSOneUse, IsAnd);
+ if (Value *V = foldAndOrOfICmpsUsingRanges(PredL, LHS0, LHS1, LHSOneUse,
+ PredR, RHS0, RHS1, RHSOneUse,
+ IsAnd))
+ return V;
+
+ if (IsAnd)
+ if (Value *V = foldAndOfRangeAndMaskedEq(PredL, LHS0, LHS1, PredR, RHS0,
+ RHS1, I, Builder))
+ return V;
+
+ return nullptr;
}
/// If IsLogical is true, then the and/or is in select form and the transform
diff --git a/llvm/test/Transforms/InstCombine/and-icmp-range-masked-eq.ll b/llvm/test/Transforms/InstCombine/and-icmp-range-masked-eq.ll
new file mode 100644
index 0000000000000..e6c03dc1dc56e
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/and-icmp-range-masked-eq.ll
@@ -0,0 +1,248 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+declare void @use(i1)
+declare void @use_i8(i8)
+
+; Issue #230800: only -1 is in {-1,0,1} with bit 7 and bit 0 set.
+define i1 @range_and_mask_single(i8 %x) {
+; CHECK-LABEL: @range_and_mask_single(
+; CHECK-NEXT: [[BOTH:%.*]] = icmp eq i8 [[X:%.*]], -1
+; CHECK-NEXT: ret i1 [[BOTH]]
+;
+ %offset = add i8 %x, 1
+ %in_range = icmp ult i8 %offset, 3
+ %masked = and i8 %x, -127
+ %bits = icmp eq i8 %masked, -127
+ %both = and i1 %in_range, %bits
+ ret i1 %both
+}
+
+; Operands commuted.
+define i1 @range_and_mask_commuted(i8 %x) {
+; CHECK-LABEL: @range_and_mask_commuted(
+; CHECK-NEXT: [[BOTH:%.*]] = icmp eq i8 [[X:%.*]], -1
+; CHECK-NEXT: ret i1 [[BOTH]]
+;
+ %offset = add i8 %x, 1
+ %in_range = icmp ult i8 %offset, 3
+ %masked = and i8 %x, -127
+ %bits = icmp eq i8 %masked, -127
+ %both = and i1 %bits, %in_range
+ ret i1 %both
+}
+
+; Logical and must be handled too (poison-safe).
+define i1 @range_and_mask_logical(i8 %x) {
+; CHECK-LABEL: @range_and_mask_logical(
+; CHECK-NEXT: [[BOTH:%.*]] = icmp eq i8 [[X:%.*]], -1
+; CHECK-NEXT: ret i1 [[BOTH]]
+;
+ %offset = add i8 %x, 1
+ %in_range = icmp ult i8 %offset, 3
+ %masked = and i8 %x, -127
+ %bits = icmp eq i8 %masked, -127
+ %both = select i1 %in_range, i1 %bits, i1 false
+ ret i1 %both
+}
+
+; Logical and commuted.
+define i1 @range_and_mask_logical_commuted(i8 %x) {
+; CHECK-LABEL: @range_and_mask_logical_commuted(
+; CHECK-NEXT: [[BOTH:%.*]] = icmp eq i8 [[X:%.*]], -1
+; CHECK-NEXT: ret i1 [[BOTH]]
+;
+ %offset = add i8 %x, 1
+ %in_range = icmp ult i8 %offset, 3
+ %masked = and i8 %x, -127
+ %bits = icmp eq i8 %masked, -127
+ %both = select i1 %bits, i1 %in_range, i1 false
+ ret i1 %both
+}
+
+; nsw on the add does not matter.
+define i1 @range_and_mask_nsw(i8 %x) {
+; CHECK-LABEL: @range_and_mask_nsw(
+; CHECK-NEXT: [[BOTH:%.*]] = icmp eq i8 [[X:%.*]], -1
+; CHECK-NEXT: ret i1 [[BOTH]]
+;
+ %offset = add nsw i8 %x, 1
+ %in_range = icmp ult i8 %offset, 3
+ %masked = and i8 %x, -127
+ %bits = icmp eq i8 %masked, -127
+ %both = and i1 %in_range, %bits
+ ret i1 %both
+}
+
+; No offset: x in {0,1,2} can never have bit 7 set, so the result is false.
+define i1 @range_and_mask_empty(i8 %x) {
+; CHECK-LABEL: @range_and_mask_empty(
+; CHECK-NEXT: ret i1 false
+;
+ %in_range = icmp ult i8 %x, 3
+ %masked = and i8 %x, -127
+ %bits = icmp eq i8 %masked, -127
+ %both = and i1 %in_range, %bits
+ ret i1 %both
+}
+
+; Wider type: i16.
+define i1 @range_and_mask_i16(i16 %x) {
+; CHECK-LABEL: @range_and_mask_i16(
+; CHECK-NEXT: [[BOTH:%.*]] = icmp eq i16 [[X:%.*]], -1
+; CHECK-NEXT: ret i1 [[BOTH]]
+;
+ %offset = add i16 %x, 1
+ %in_range = icmp ult i16 %offset, 3
+ %masked = and i16 %x, -32767
+ %bits = icmp eq i16 %masked, -32767
+ %both = and i1 %in_range, %bits
+ ret i1 %both
+}
+
+; Wider type: i32.
+define i1 @range_and_mask_i32(i32 %x) {
+; CHECK-LABEL: @range_and_mask_i32(
+; CHECK-NEXT: [[BOTH:%.*]] = icmp eq i32 [[X:%.*]], -1
+; CHECK-NEXT: ret i1 [[BOTH]]
+;
+ %offset = add i32 %x, 1
+ %in_range = icmp ult i32 %offset, 3
+ %masked = and i32 %x, -2147483647
+ %bits = icmp eq i32 %masked, -2147483647
+ %both = and i1 %in_range, %bits
+ ret i1 %both
+}
+
+; Vector splat.
+define <2 x i1> @range_and_mask_vec(<2 x i8> %x) {
+; CHECK-LABEL: @range_and_mask_vec(
+; CHECK-NEXT: [[BOTH:%.*]] = icmp eq <2 x i8> [[X:%.*]], splat (i8 -1)
+; CHECK-NEXT: ret <2 x i1> [[BOTH]]
+;
+ %offset = add <2 x i8> %x, <i8 1, i8 1>
+ %in_range = icmp ult <2 x i8> %offset, <i8 3, i8 3>
+ %masked = and <2 x i8> %x, <i8 -127, i8 -127>
+ %bits = icmp eq <2 x i8> %masked, <i8 -127, i8 -127>
+ %both = and <2 x i1> %in_range, %bits
+ ret <2 x i1> %both
+}
+
+; Extra uses of the compares.
+define i1 @range_and_mask_extra_use(i8 %x) {
+; CHECK-LABEL: @range_and_mask_extra_use(
+; CHECK-NEXT: [[OFFSET:%.*]] = add i8 [[X:%.*]], 1
+; CHECK-NEXT: [[IN_RANGE:%.*]] = icmp ult i8 [[OFFSET]], 3
+; CHECK-NEXT: [[MASKED:%.*]] = and i8 [[X]], -127
+; CHECK-NEXT: [[BITS:%.*]] = icmp eq i8 [[MASKED]], -127
+; CHECK-NEXT: call void @use(i1 [[IN_RANGE]])
+; CHECK-NEXT: call void @use(i1 [[BITS]])
+; CHECK-NEXT: [[BOTH:%.*]] = icmp eq i8 [[X]], -1
+; CHECK-NEXT: ret i1 [[BOTH]]
+;
+ %offset = add i8 %x, 1
+ %in_range = icmp ult i8 %offset, 3
+ %masked = and i8 %x, -127
+ %bits = icmp eq i8 %masked, -127
+ call void @use(i1 %in_range)
+ call void @use(i1 %bits)
+ %both = and i1 %in_range, %bits
+ ret i1 %both
+}
+
+; Range check is a negated-power-of-2 mask: (x & -2) == -2 means x in {-2, -1}.
+; Combined with (x & 0x81) == 0x81, only -1 satisfies both.
+define i1 @range_neg_pow2_and_mask_single(i8 %x) {
+; CHECK-LABEL: @range_neg_pow2_and_mask_single(
+; CHECK-NEXT: [[BOTH:%.*]] = icmp eq i8 [[X:%.*]], -1
+; CHECK-NEXT: ret i1 [[BOTH]]
+;
+ %range_masked = and i8 %x, -2
+ %range = icmp eq i8 %range_masked, -2
+ %masked = and i8 %x, -127
+ %bits = icmp eq i8 %masked, -127
+ %both = and i1 %range, %bits
+ ret i1 %both
+}
+
+; Range check is a negated-power-of-2 mask: (x & -2) == 0 means x in {0, 1}.
+; Combined with (x & 0x81) == 0x81, intersection is empty (false).
+define i1 @range_neg_pow2_and_mask_empty(i8 %x) {
+; CHECK-LABEL: @range_neg_pow2_and_mask_empty(
+; CHECK-NEXT: ret i1 false
+;
+ %range_masked = and i8 %x, -2
+ %range = icmp eq i8 %range_masked, 0
+ %masked = and i8 %x, -127
+ %bits = icmp eq i8 %masked, -127
+ %both = and i1 %range, %bits
+ ret i1 %both
+}
+
+; Extra use on the negated-power-of-2 masked range check:
+; matchExactRangeCheck requires m_OneUse for (x & NegPow2), so this does not fold.
+define i1 @range_neg_pow2_and_mask_multi_use(i8 %x) {
+; CHECK-LABEL: @range_neg_pow2_and_mask_multi_use(
+; CHECK-NEXT: [[RANGE_MASKED:%.*]] = and i8 [[X:%.*]], -2
+; CHECK-NEXT: call void @use_i8(i8 [[RANGE_MASKED]])
+; CHECK-NEXT: [[BOTH:%.*]] = icmp eq i8 [[X]], -1
+; CHECK-NEXT: ret i1 [[BOTH]]
+;
+ %range_masked = and i8 %x, -2
+ call void @use_i8(i8 %range_masked)
+ %range = icmp eq i8 %range_masked, -2
+ %masked = and i8 %x, -127
+ %bits = icmp eq i8 %masked, -127
+ %both = and i1 %range, %bits
+ ret i1 %both
+}
+
+; Candidate 130 is in Loose [129, 256), but does not satisfy (130 & 0x81) == 0x81.
+; Regression test: existing constant-equality fold handles this case.
+define i1 @range_and_mask_candidate_fails_mask(i8 %x) {
+; CHECK-LABEL: @range_and_mask_candidate_fails_mask(
+; CHECK-NEXT: ret i1 false
+;
+ %offset = add i8 %x, -130
+ %in_range = icmp ult i8 %offset, 1
+ %masked = and i8 %x, -127
+ %bits = icmp eq i8 %masked, -127
+ %both = and i1 %in_range, %bits
+ ret i1 %both
+}
+
+; NEGATIVE: the true set is {-1, 1}. The loose range keeps {-1,0,1}, so there
+; is no single candidate and the IR must stay as is.
+define i1 @range_and_mask_two_candidates(i8 %x) {
+; CHECK-LABEL: @range_and_mask_two_candidates(
+; CHECK-NEXT: [[OFFSET:%.*]] = add i8 [[X:%.*]], 1
+; CHECK-NEXT: [[IN_RANGE:%.*]] = icmp ult i8 [[OFFSET]], 3
+; CHECK-NEXT: [[BITS:%.*]] = trunc i8 [[X]] to i1
+; CHECK-NEXT: [[BOTH:%.*]] = and i1 [[IN_RANGE]], [[BITS]]
+; CHECK-NEXT: ret i1 [[BOTH]]
+;
+ %offset = add i8 %x, 1
+ %in_range = icmp ult i8 %offset, 3
+ %masked = and i8 %x, 1
+ %bits = icmp eq i8 %masked, 1
+ %both = and i1 %in_range, %bits
+ ret i1 %both
+}
+
+; NEGATIVE: the mask applies to a different value.
+define i1 @range_and_mask_different_value(i8 %x, i8 %y) {
+; CHECK-LABEL: @range_and_mask_different_value(
+; CHECK-NEXT: [[OFFSET:%.*]] = add i8 [[X:%.*]], 1
+; CHECK-NEXT: [[IN_RANGE:%.*]] = icmp ult i8 [[OFFSET]], 3
+; CHECK-NEXT: [[MASKED:%.*]] = and i8 [[Y:%.*]], -127
+; CHECK-NEXT: [[BITS:%.*]] = icmp eq i8 [[MASKED]], -127
+; CHECK-NEXT: [[BOTH:%.*]] = and i1 [[IN_RANGE]], [[BITS]]
+; CHECK-NEXT: ret i1 [[BOTH]]
+;
+ %offset = add i8 %x, 1
+ %in_range = icmp ult i8 %offset, 3
+ %masked = and i8 %y, -127
+ %bits = icmp eq i8 %masked, -127
+ %both = and i1 %in_range, %bits
+ ret i1 %both
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/231020
More information about the llvm-commits
mailing list