[llvm] [InstCombine] Fold unsigned range checks with small complements (PR #223482)
Lakshay Chauhan via llvm-commits
llvm-commits at lists.llvm.org
Tue Sep 15 07:52:36 PDT 2026
https://github.com/nos1dot618 updated https://github.com/llvm/llvm-project/pull/223482
>From 546ad8f5138c0e78eb7343a76bfe751fc7b49e2e Mon Sep 17 00:00:00 2001
From: ninthcircle <mainlakshayhoon at gmail.com>
Date: Mon, 14 Sep 2026 23:36:03 +0530
Subject: [PATCH] [InstCombine] Fold unsigned range checks with small
complements
Fold patterns of the form
(icmp ugt (X + Offset), N) | (icmp eq (X + Offset), K)
when the complement of the unsigned range is small.
The transformation converts the excluded values into a conjunction of icmp ne checks on the original value. Limit the number of generated comparisons to avoid increasing IR size excessively.
Handle modular arithmetic with APInt so that wrapped offsets are transformed correctly.
---
.../InstCombine/InstCombineAndOrXor.cpp | 86 +++++++++++++++++++
.../test/Transforms/InstCombine/and-xor-or.ll | 74 ++++++++++++++++
2 files changed, 160 insertions(+)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index 7fd49370fab28..83d360e17bf70 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -3439,6 +3439,86 @@ static Value *foldAndOrOfICmpEqConstantAndICmp(CmpPredicate PredL, Value *LHS0,
Other);
}
+// (icmp ugt (X + Offset), N) | (icmp eq (X + Offset), K)
+// -> conjunction of != comparisons for the small set of values
+// where the original expression is false.
+static Value *foldUnsignedRangeWithSmallComplement(CmpPredicate PredL,
+ Value *LHS0, Value *LHS1,
+ CmpPredicate PredR,
+ Value *RHS0, Value *RHS1,
+ IRBuilderBase &Builder) {
+ if (!((PredL == ICmpInst::ICMP_UGT && PredR == ICmpInst::ICMP_EQ) ||
+ (PredR == ICmpInst::ICMP_UGT && PredL == ICmpInst::ICMP_EQ)))
+ return nullptr;
+
+ Value *RangeValue; // Y = X + Offset
+ Value *EqValue; // Y
+ Value *RangeLimit; // N
+ Value *EqConstant; // K
+
+ if (PredL == ICmpInst::ICMP_UGT) {
+ RangeValue = LHS0;
+ RangeLimit = LHS1;
+ EqValue = RHS0;
+ EqConstant = RHS1;
+ } else {
+ RangeValue = RHS0;
+ RangeLimit = RHS1;
+ EqValue = LHS0;
+ EqConstant = LHS1;
+ }
+
+ const APInt *N;
+ const APInt *K;
+ if (!match(RangeLimit, m_APInt(N)) || !match(EqConstant, m_APInt(K)) ||
+ RangeValue != EqValue)
+ return nullptr;
+ // N and K must be constants, and both comparisons must use the same value Y.
+
+ Value *X;
+ const APInt *Offset;
+
+ if (!match(RangeValue, m_Add(m_Value(X), m_APInt(Offset))) &&
+ !match(RangeValue, m_Add(m_APInt(Offset), m_Value(X))))
+ return nullptr;
+ // Y must be of the form (X + Offset) or (Offset + X).
+
+ // Y >u N is false for Y in [0, N].
+ // If K is in this range, Y == K makes the OR true, so remove K.
+ if (N->isMaxValue())
+ return nullptr;
+ // If N is UINT_MAX, Y >u N is always false.
+
+ APInt FalseRangeSize = *N + 1;
+ APInt NumExcluded = FalseRangeSize;
+ if (K->ule(*N))
+ --NumExcluded;
+
+ // Avoid increasing IR size when the complement is large.
+ constexpr unsigned MaxExcludedValues = 4;
+ if (NumExcluded.ugt(MaxExcludedValues))
+ return nullptr;
+
+ if (NumExcluded.isZero())
+ return ConstantInt::getTrue(Builder.getContext());
+
+ Value *Result = nullptr;
+ APInt Y = APInt::getZero(N->getBitWidth());
+
+ for (unsigned I = 0, E = FalseRangeSize.getZExtValue(); I != E; ++I, ++Y) {
+ if (Y == *K)
+ continue;
+
+ APInt XValue = Y - *Offset;
+ Value *Cmp = Builder.CreateICmp(ICmpInst::ICMP_NE, X,
+ ConstantInt::get(X->getType(), XValue));
+
+ Result = Result ? Builder.CreateAnd(Result, Cmp) : Cmp;
+ }
+
+ return Result;
+}
+
/// Fold (icmp)&(icmp) or (icmp)|(icmp) if possible.
/// If IsLogical is true, then the and/or is in select form and the transform
/// must be poison-safe.
@@ -3527,6 +3607,12 @@ Value *InstCombinerImpl::foldAndOrOfICmps(Value *LHS, Value *RHS,
return V;
}
+ if (!IsAnd && !IsLogical) {
+ if (Value *V = foldUnsignedRangeWithSmallComplement(
+ PredL, LHS0, LHS1, PredR, RHS0, RHS1, Builder))
+ return V;
+ }
+
// TODO: Add conjugated or fold, check whether it is safe for logical and/or.
if (IsAnd && !IsLogical)
if (Value *V = foldSignedTruncationCheck(PredL, LHS0, LHS1, PredR, RHS0,
diff --git a/llvm/test/Transforms/InstCombine/and-xor-or.ll b/llvm/test/Transforms/InstCombine/and-xor-or.ll
index 1750aea8e1eaa..6117a72cd11c4 100644
--- a/llvm/test/Transforms/InstCombine/and-xor-or.ll
+++ b/llvm/test/Transforms/InstCombine/and-xor-or.ll
@@ -5313,3 +5313,77 @@ define i1 @test_and_xor_freely_invertable_multiuse(i32 %x, i32 %y, i1 %z) {
%and = and i1 %xor, %z
ret i1 %and
}
+
+define i1 @fold_unsigned_range_with_small_complement(i8 %x) {
+; CHECK-LABEL: define {{[^@]+}}@fold_unsigned_range_with_small_complement
+; CHECK-SAME: (i8 [[X:%.*]]) {
+; CHECK-NEXT: [[A:%.*]] = icmp ne i8 [[X]], 8
+; CHECK-NEXT: [[B:%.*]] = icmp ne i8 [[X]], 10
+; CHECK-NEXT: [[R:%.*]] = and i1 [[A]], [[B]]
+; CHECK-NEXT: ret i1 [[R]]
+;
+ %v1 = add i8 %x, -8
+ %v3 = icmp ugt i8 %v1, 2
+ %v5 = icmp eq i8 %v1, 1
+ %r = or i1 %v3, %v5
+ ret i1 %r
+}
+
+define i1 @fold_unsigned_range_with_small_complement_commuted(i8 %x) {
+; CHECK-LABEL: define {{[^@]+}}@fold_unsigned_range_with_small_complement_commuted
+; CHECK-SAME: (i8 [[X:%.*]]) {
+; CHECK-NEXT: [[A:%.*]] = icmp ne i8 [[X]], 8
+; CHECK-NEXT: [[B:%.*]] = icmp ne i8 [[X]], 10
+; CHECK-NEXT: [[R:%.*]] = and i1 [[A]], [[B]]
+; CHECK-NEXT: ret i1 [[R]]
+;
+ %v1 = add i8 %x, -8
+ %v3 = icmp ugt i8 %v1, 2
+ %v5 = icmp eq i8 %v1, 1
+ %r = or i1 %v5, %v3
+ ret i1 %r
+}
+
+define i1 @fold_unsigned_range_with_wraparound(i8 %x) {
+; CHECK-LABEL: define {{[^@]+}}@fold_unsigned_range_with_wraparound
+; CHECK-SAME: (i8 [[X:%.*]]) {
+; CHECK-NEXT: [[A:%.*]] = icmp ne i8 [[X]], -10
+; CHECK-NEXT: [[B:%.*]] = icmp ne i8 [[X]], -8
+; CHECK-NEXT: [[R:%.*]] = and i1 [[A]], [[B]]
+; CHECK-NEXT: ret i1 [[R]]
+;
+ %v1 = add i8 %x, 10
+ %v3 = icmp ugt i8 %v1, 2
+ %v5 = icmp eq i8 %v1, 1
+ %r = or i1 %v3, %v5
+ ret i1 %r
+}
+
+define i1 @fold_unsigned_range_without_hole(i8 %x) {
+; CHECK-LABEL: define {{[^@]+}}@fold_unsigned_range_without_hole
+; CHECK-SAME: (i8 [[X:%.*]]) {
+; CHECK-NEXT: [[R:%.*]] = icmp ult i8 [[X]], -11
+; CHECK-NEXT: ret i1 [[R]]
+;
+ %v1 = add i8 %x, -8
+ %v3 = icmp ugt i8 %v1, 2
+ %v5 = icmp eq i8 %v1, 5
+ %r = or i1 %v3, %v5
+ ret i1 %r
+}
+
+define i1 @do_not_expand_large_complement(i8 %x) {
+; CHECK-LABEL: define {{[^@]+}}@do_not_expand_large_complement
+; CHECK-SAME: (i8 [[X:%.*]]) {
+; CHECK-NEXT: [[V:%.*]] = add i8 [[X]], -8
+; CHECK-NEXT: [[A:%.*]] = icmp ugt i8 [[V]], 4
+; CHECK-NEXT: [[B:%.*]] = icmp eq i8 [[V]], 0
+; CHECK-NEXT: [[R:%.*]] = or i1 [[A]], [[B]]
+; CHECK-NEXT: ret i1 [[R]]
+;
+ %v1 = add i8 %x, -8
+ %v3 = icmp ugt i8 %v1, 4
+ %v5 = icmp eq i8 %v1, 0
+ %r = or i1 %v3, %v5
+ ret i1 %r
+}
More information about the llvm-commits
mailing list