[llvm] [instcombine] fold range checks for bitwise and/or (PR #176642)

via llvm-commits llvm-commits at lists.llvm.org
Sun Jan 18 03:35:35 PST 2026


https://github.com/Serosh-commits created https://github.com/llvm/llvm-project/pull/176642

this patch lets instcombine fold range comparisons when logical-and/or is using bitwise and/or instead of select. it handles patterns like (cond & cmp1) & cmp2.

fixes #176554

>From 2530dac48caf39c4b267a49a5b995b56a9d00e82 Mon Sep 17 00:00:00 2001
From: Serosh <janmejayapanda400 at gmail.com>
Date: Sun, 18 Jan 2026 00:35:08 +0530
Subject: [PATCH] [instcombine] fold range checks for bitwise and/or

this patch lets instcombine fold range comparisons when logical-and/or is using bitwise and/or instead of select. it handles patterns like (cond & cmp1) & cmp2.

fixes #176554
---
 .../InstCombine/InstCombineAndOrXor.cpp       |  37 ++++++
 .../InstCombine/range-check-and-form.ll       | 121 ++++++++++++++++++
 2 files changed, 158 insertions(+)
 create mode 100644 llvm/test/Transforms/InstCombine/range-check-and-form.ll

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index aacb2e2a91c57..04095325db542 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -3612,6 +3612,43 @@ Value *InstCombinerImpl::foldBooleanAndOr(Value *LHS, Value *RHS,
   if (Value *Res = foldEqOfParts(LHS, RHS, IsAnd))
     return Res;
 
+  auto TryFold = [&](Value *L, Value *R) -> Value * {
+    Value *A, *B;
+    Instruction::BinaryOps Opcode = IsAnd ? Instruction::And : Instruction::Or;
+
+    if (match(L, m_BinOp(Opcode, m_Value(A), m_Value(B))) ||
+        (IsLogical && (IsAnd ? match(L, m_LogicalAnd(m_Value(A), m_Value(B)))
+                             : match(L, m_LogicalOr(m_Value(A), m_Value(B)))))) {
+      for (int i = 0; i < 2; ++i) {
+        Value *Inner = i == 0 ? A : B;
+        Value *Other = i == 0 ? B : A;
+        auto *ICmpInner = dyn_cast<ICmpInst>(Inner);
+        auto *ICmpR = dyn_cast<ICmpInst>(R);
+        if (!ICmpInner || !ICmpR)
+          continue;
+
+        Value *Res = foldAndOrOfICmps(ICmpInner, ICmpR, I, IsAnd, IsLogical);
+        if (!Res)
+          continue;
+
+        if (IsLogical) {
+          Value *FalseV = Constant::getNullValue(LHS->getType());
+          Value *TrueV = Constant::getAllOnesValue(LHS->getType());
+          return IsAnd ? Builder.CreateSelect(Other, Res, FalseV)
+                       : Builder.CreateSelect(Other, TrueV, Res);
+        }
+
+        return Builder.CreateBinOp(Opcode, Other, Res);
+      }
+    }
+    return nullptr;
+  };
+
+  if (Value *V = TryFold(LHS, RHS))
+    return V;
+  if (Value *V = TryFold(RHS, LHS))
+    return V;
+
   return nullptr;
 }
 
diff --git a/llvm/test/Transforms/InstCombine/range-check-and-form.ll b/llvm/test/Transforms/InstCombine/range-check-and-form.ll
new file mode 100644
index 0000000000000..cab76141a3f97
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/range-check-and-form.ll
@@ -0,0 +1,121 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+; Test for GitHub issue #176554
+; InstCombine should fold range comparisons even when the logical-and
+; uses bitwise 'and' instead of 'select' form.
+
+; This is the buggy pattern that was not being optimized:
+;   %cmp1 = icmp ugt i32 %y, 65535
+;   %sel11 = and i1 %cond, %cmp1  ; <-- and instead of select
+;   %cmp2 = icmp ult i32 %y, 1114112
+;   %sel2 = select i1 %sel11, i1 %cmp2, i1 false
+;
+; Should be optimized to a single range check.
+
+define i1 @test_and_form(i1 %cond, i32 %y) {
+; CHECK-LABEL: @test_and_form(
+; CHECK-NEXT:    [[TMP1:%.*]] = add i32 [[Y:%.*]], -65536
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp ult i32 [[TMP1]], 1048576
+; CHECK-NEXT:    [[SEL2:%.*]] = and i1 [[COND:%.*]], [[TMP2]]
+; CHECK-NEXT:    ret i1 [[SEL2]]
+;
+  %cmp1 = icmp ugt i32 %y, 65535
+  %sel11 = and i1 %cond, %cmp1
+  %cmp2 = icmp ult i32 %y, 1114112
+  %sel2 = select i1 %sel11, i1 %cmp2, i1 false
+  ret i1 %sel2
+}
+
+; Reference: the canonical select form that already works
+define i1 @test_select_form(i1 %cond, i32 %y) {
+; CHECK-LABEL: @test_select_form(
+; CHECK-NEXT:    [[TMP1:%.*]] = add i32 [[Y:%.*]], -65536
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp ult i32 [[TMP1]], 1048576
+; CHECK-NEXT:    [[SEL2:%.*]] = select i1 [[COND:%.*]], i1 [[TMP2]], i1 false
+; CHECK-NEXT:    ret i1 [[SEL2]]
+;
+  %cmp1 = icmp ugt i32 %y, 65535
+  %sel1 = select i1 %cond, i1 %cmp1, i1 false
+  %cmp2 = icmp ult i32 %y, 1114112
+  %sel2 = select i1 %sel1, i1 %cmp2, i1 false
+  ret i1 %sel2
+}
+
+; Test with the icmp on the other side of the and
+define i1 @test_and_form_swapped(i1 %cond, i32 %y) {
+; CHECK-LABEL: @test_and_form_swapped(
+; CHECK-NEXT:    [[TMP1:%.*]] = add i32 [[Y:%.*]], -65536
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp ult i32 [[TMP1]], 1048576
+; CHECK-NEXT:    [[SEL2:%.*]] = and i1 [[TMP2]], [[COND:%.*]]
+; CHECK-NEXT:    ret i1 [[SEL2]]
+;
+  %cmp1 = icmp ugt i32 %y, 65535
+  %sel11 = and i1 %cmp1, %cond  ; swapped operands
+  %cmp2 = icmp ult i32 %y, 1114112
+  %sel2 = select i1 %sel11, i1 %cmp2, i1 false
+  ret i1 %sel2
+}
+
+; Test logical-or form: select (or A, cmp1), true, cmp2
+define i1 @test_or_form(i1 %cond, i32 %y) {
+; CHECK-LABEL: @test_or_form(
+; CHECK-NEXT:    [[TMP1:%.*]] = add i32 [[Y:%.*]], -1114112
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp ult i32 [[TMP1]], -1048576
+; CHECK-NEXT:    [[SEL2:%.*]] = or i1 [[COND:%.*]], [[TMP2]]
+; CHECK-NEXT:    ret i1 [[SEL2]]
+;
+  %cmp1 = icmp ugt i32 %y, 1114111
+  %sel11 = or i1 %cond, %cmp1
+  %cmp2 = icmp ult i32 %y, 65536
+  %sel2 = select i1 %sel11, i1 true, i1 %cmp2
+  ret i1 %sel2
+}
+
+; Test bitwise and reassociation: (cond & cmp1) & cmp2
+define i1 @test_bitwise_and(i1 %cond, i32 %y) {
+; CHECK-LABEL: @test_bitwise_and(
+; CHECK-NEXT:    [[TMP1:%.*]] = add i32 [[Y:%.*]], -65536
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp ult i32 [[TMP1]], 1048576
+; CHECK-NEXT:    [[SEL2:%.*]] = and i1 [[COND:%.*]], [[TMP2]]
+; CHECK-NEXT:    ret i1 [[SEL2]]
+;
+  %cmp1 = icmp ugt i32 %y, 65535
+  %sel11 = and i1 %cond, %cmp1
+  %cmp2 = icmp ult i32 %y, 1114112
+  %sel2 = and i1 %sel11, %cmp2
+  ret i1 %sel2
+}
+
+; Test bitwise or reassociation: (cond | cmp1) | cmp2
+define i1 @test_bitwise_or(i1 %cond, i32 %y) {
+; CHECK-LABEL: @test_bitwise_or(
+; CHECK-NEXT:    [[TMP1:%.*]] = add i32 [[Y:%.*]], -1114112
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp ult i32 [[TMP1]], -1048576
+; CHECK-NEXT:    [[SEL2:%.*]] = or i1 [[COND:%.*]], [[TMP2]]
+; CHECK-NEXT:    ret i1 [[SEL2]]
+;
+  %cmp1 = icmp ugt i32 %y, 1114111
+  %sel11 = or i1 %cond, %cmp1
+  %cmp2 = icmp ult i32 %y, 65536
+  %sel2 = or i1 %sel11, %cmp2
+  ret i1 %sel2
+}
+
+
+; Negative test: different values in the icmps should not be folded
+define i1 @test_and_form_different_values(i1 %cond, i32 %y, i32 %z) {
+; CHECK-LABEL: @test_and_form_different_values(
+; CHECK-NEXT:    [[CMP1:%.*]] = icmp ugt i32 [[Y:%.*]], 65535
+; CHECK-NEXT:    [[SEL11:%.*]] = and i1 [[COND:%.*]], [[CMP1]]
+; CHECK-NEXT:    [[CMP2:%.*]] = icmp ult i32 [[Z:%.*]], 1114112
+; CHECK-NEXT:    [[SEL2:%.*]] = select i1 [[SEL11]], i1 [[CMP2]], i1 false
+; CHECK-NEXT:    ret i1 [[SEL2]]
+;
+  %cmp1 = icmp ugt i32 %y, 65535
+  %sel11 = and i1 %cond, %cmp1
+  %cmp2 = icmp ult i32 %z, 1114112  ; different value!
+  %sel2 = select i1 %sel11, i1 %cmp2, i1 false
+  ret i1 %sel2
+}
+



More information about the llvm-commits mailing list