[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