[llvm] 51fc705 - [InstCombine] Fold comparisons of x & -x with 0 and 1 (#213709)

via llvm-commits llvm-commits at lists.llvm.org
Sat Aug 8 11:11:17 PDT 2026


Author: Hadong Lee
Date: 2026-08-08T18:11:12Z
New Revision: 51fc70599559b2f04bef0d7adc47ecf16343b44c

URL: https://github.com/llvm/llvm-project/commit/51fc70599559b2f04bef0d7adc47ecf16343b44c
DIFF: https://github.com/llvm/llvm-project/commit/51fc70599559b2f04bef0d7adc47ecf16343b44c.diff

LOG: [InstCombine] Fold comparisons of x & -x with 0 and 1 (#213709)

This folds equality comparisons of `x & -x` with zero and one:

- `(x & -x) == 0` to `x == 0`
- `(x & -x) != 0` to `x != 0`
- `(x & -x) == 1` to `trunc x to i1`
- `(x & -x) != 1` to `!(trunc x to i1)`

The fold supports scalar, fixed-vector, and scalable-vector integer
types wider than `i1`. The `!= 1` fold is limited
to one-use `and` instructions to avoid increasing the instruction count.

Alive2: https://alive2.llvm.org/ce/z/Zwy2hW

Fixes #213708

Added: 
    llvm/test/Transforms/InstCombine/icmp-lowbit.ll

Modified: 
    llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
    llvm/test/Transforms/InstCombine/and-or-icmps.ll
    llvm/test/Transforms/InstCombine/ctpop-pow2.ll

Removed: 
    


################################################################################
diff  --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index 70d584740f5b9..2da5d2464b674 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -1989,6 +1989,31 @@ Instruction *InstCombinerImpl::foldICmpAndConstant(ICmpInst &Cmp,
   if (!Cmp.isEquality())
     return nullptr;
 
+  // (X & -X) == 0 --> X == 0
+  // (X & -X) != 0 --> X != 0
+  // (X & -X) == 1 --> trunc X to i1
+  // (X & -X) != 1 --> !(trunc X to i1)
+  // Cmp is == or != by the check above.
+  Value *MatchedX;
+  // Match X & -X in either operand order.
+  if (C.getBitWidth() > 1 && (C.isZero() || C.isOne()) &&
+      match(And, m_c_And(m_Neg(m_Value(MatchedX)), m_Deferred(MatchedX)))) {
+    // Preserve the predicate: (X & -X) ==/!= 0 --> X ==/!= 0.
+    if (C.isZero())
+      return new ICmpInst(Pred, MatchedX, Cmp.getOperand(1));
+
+    // (X & -X) == 1 iff the low bit of X is set.
+    if (Pred == CmpInst::ICMP_EQ)
+      return new TruncInst(MatchedX, Cmp.getType());
+
+    // The remaining case needs a trunc and not. Require the original and
+    // to become dead to avoid increasing the instruction count.
+    if (And->hasOneUse()) {
+      Value *Trunc = Builder.CreateTrunc(MatchedX, Cmp.getType());
+      return BinaryOperator::CreateNot(Trunc);
+    }
+  }
+
   // X & -C == -C -> X >  u ~C
   // X & -C != -C -> X <= u ~C
   //   iff C is a power of 2

diff  --git a/llvm/test/Transforms/InstCombine/and-or-icmps.ll b/llvm/test/Transforms/InstCombine/and-or-icmps.ll
index 91b9f702f4575..420b963110364 100644
--- a/llvm/test/Transforms/InstCombine/and-or-icmps.ll
+++ b/llvm/test/Transforms/InstCombine/and-or-icmps.ll
@@ -3341,9 +3341,7 @@ define i1 @icmp_eq_or_z_or_pow2orz_fail_nonzero_const(i8 %x, i8 %y) {
 
 define <2 x i1> @icmp_ne_and_z_and_pow2orz_fail_bad_pred(<2 x i8> %x, <2 x i8> %y) {
 ; CHECK-LABEL: @icmp_ne_and_z_and_pow2orz_fail_bad_pred(
-; CHECK-NEXT:    [[NY:%.*]] = sub <2 x i8> zeroinitializer, [[Y:%.*]]
-; CHECK-NEXT:    [[POW2ORZ:%.*]] = and <2 x i8> [[Y]], [[NY]]
-; CHECK-NEXT:    [[TMP1:%.*]] = or <2 x i8> [[X:%.*]], [[POW2ORZ]]
+; CHECK-NEXT:    [[TMP1:%.*]] = or <2 x i8> [[X:%.*]], [[Y:%.*]]
 ; CHECK-NEXT:    [[R:%.*]] = icmp eq <2 x i8> [[TMP1]], zeroinitializer
 ; CHECK-NEXT:    ret <2 x i1> [[R]]
 ;

diff  --git a/llvm/test/Transforms/InstCombine/ctpop-pow2.ll b/llvm/test/Transforms/InstCombine/ctpop-pow2.ll
index e86d32de51f36..859828de56d56 100644
--- a/llvm/test/Transforms/InstCombine/ctpop-pow2.ll
+++ b/llvm/test/Transforms/InstCombine/ctpop-pow2.ll
@@ -11,9 +11,7 @@ declare void @llvm.assume(i1)
 
 define i16 @ctpop_x_and_negx(i16 %x) {
 ; CHECK-LABEL: @ctpop_x_and_negx(
-; CHECK-NEXT:    [[V0:%.*]] = sub i16 0, [[X:%.*]]
-; CHECK-NEXT:    [[V1:%.*]] = and i16 [[X]], [[V0]]
-; CHECK-NEXT:    [[TMP1:%.*]] = icmp ne i16 [[V1]], 0
+; CHECK-NEXT:    [[TMP1:%.*]] = icmp ne i16 [[X:%.*]], 0
 ; CHECK-NEXT:    [[CNT:%.*]] = zext i1 [[TMP1]] to i16
 ; CHECK-NEXT:    ret i16 [[CNT]]
 ;
@@ -73,9 +71,7 @@ define i8 @ctpop_imin_plus1_lshr_nz(i8 %x) {
 
 define i64 @ctpop_x_and_negx_nz(i64 %x) {
 ; CHECK-LABEL: @ctpop_x_and_negx_nz(
-; CHECK-NEXT:    [[V0:%.*]] = sub i64 0, [[X:%.*]]
-; CHECK-NEXT:    [[V1:%.*]] = and i64 [[X]], [[V0]]
-; CHECK-NEXT:    [[CMP:%.*]] = icmp ne i64 [[V1]], 0
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ne i64 [[X:%.*]], 0
 ; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP]])
 ; CHECK-NEXT:    ret i64 1
 ;
@@ -126,9 +122,7 @@ define <2 x i32> @ctpop_shl2_1_vec_nz(<2 x i32> %x) {
 
 define <2 x i64> @ctpop_x_and_negx_vec(<2 x i64> %x) {
 ; CHECK-LABEL: @ctpop_x_and_negx_vec(
-; CHECK-NEXT:    [[SUB:%.*]] = sub <2 x i64> zeroinitializer, [[X:%.*]]
-; CHECK-NEXT:    [[AND:%.*]] = and <2 x i64> [[X]], [[SUB]]
-; CHECK-NEXT:    [[TMP1:%.*]] = icmp ne <2 x i64> [[AND]], zeroinitializer
+; CHECK-NEXT:    [[TMP1:%.*]] = icmp ne <2 x i64> [[X:%.*]], zeroinitializer
 ; CHECK-NEXT:    [[CNT:%.*]] = zext <2 x i1> [[TMP1]] to <2 x i64>
 ; CHECK-NEXT:    ret <2 x i64> [[CNT]]
 ;

diff  --git a/llvm/test/Transforms/InstCombine/icmp-lowbit.ll b/llvm/test/Transforms/InstCombine/icmp-lowbit.ll
new file mode 100644
index 0000000000000..838e07ee4d64a
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/icmp-lowbit.ll
@@ -0,0 +1,261 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+; x & -x isolates the lowest set bit of x, or produces zero if x is zero.
+; (x & -x) == 0 --> x == 0
+; (x & -x) != 0 --> x != 0
+; (x & -x) == 1 --> trunc x to i1
+; (x & -x) != 1 --> xor (trunc x to i1), true
+
+; Scalar tests with 
diff erent integer widths.
+
+; i2, (x & -x) == 0 --> x == 0
+define i1 @lowbit_eq_zero_i2(i2 %x) {
+; CHECK-LABEL: define i1 @lowbit_eq_zero_i2(
+; CHECK-SAME: i2 [[X:%.*]]) {
+; CHECK-NEXT:    [[R:%.*]] = icmp eq i2 [[X]], 0
+; CHECK-NEXT:    ret i1 [[R]]
+;
+  %neg = sub i2 0, %x
+  %lowbit = and i2 %x, %neg
+  %r = icmp eq i2 %lowbit, 0
+  ret i1 %r
+}
+
+; i8, (x & -x) != 0 --> x != 0
+define i1 @lowbit_ne_zero_i8(i8 %x) {
+; CHECK-LABEL: define i1 @lowbit_ne_zero_i8(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT:    [[R:%.*]] = icmp ne i8 [[X]], 0
+; CHECK-NEXT:    ret i1 [[R]]
+;
+  %neg = sub i8 0, %x
+  %lowbit = and i8 %x, %neg
+  %r = icmp ne i8 %lowbit, 0
+  ret i1 %r
+}
+
+; i32, (x & -x) == 1 --> trunc x to i1
+define i1 @lowbit_eq_one_i32(i32 %x) {
+; CHECK-LABEL: define i1 @lowbit_eq_one_i32(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[R:%.*]] = trunc i32 [[X]] to i1
+; CHECK-NEXT:    ret i1 [[R]]
+;
+  %neg = sub i32 0, %x
+  %lowbit = and i32 %x, %neg
+  %r = icmp eq i32 %lowbit, 1
+  ret i1 %r
+}
+
+; i64, (x & -x) != 1 --> xor (trunc x to i1), true
+define i1 @lowbit_ne_one_i64(i64 %x) {
+; CHECK-LABEL: define i1 @lowbit_ne_one_i64(
+; CHECK-SAME: i64 [[X:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = trunc i64 [[X]] to i1
+; CHECK-NEXT:    [[R:%.*]] = xor i1 [[TMP1]], true
+; CHECK-NEXT:    ret i1 [[R]]
+;
+  %neg = sub i64 0, %x
+  %lowbit = and i64 %x, %neg
+  %r = icmp ne i64 %lowbit, 1
+  ret i1 %r
+}
+
+; Fixed-width vector tests cover all four comparisons.
+
+; <2 x i8>, (x & -x) == 0 --> x == 0
+define <2 x i1> @lowbit_eq_zero_v2i8(<2 x i8> %x) {
+; CHECK-LABEL: define <2 x i1> @lowbit_eq_zero_v2i8(
+; CHECK-SAME: <2 x i8> [[X:%.*]]) {
+; CHECK-NEXT:    [[R:%.*]] = icmp eq <2 x i8> [[X]], zeroinitializer
+; CHECK-NEXT:    ret <2 x i1> [[R]]
+;
+  %neg = sub <2 x i8> zeroinitializer, %x
+  %lowbit = and <2 x i8> %x, %neg
+  %r = icmp eq <2 x i8> %lowbit, zeroinitializer
+  ret <2 x i1> %r
+}
+
+; <2 x i8>, (x & -x) != 0 --> x != 0
+define <2 x i1> @lowbit_ne_zero_v2i8(<2 x i8> %x) {
+; CHECK-LABEL: define <2 x i1> @lowbit_ne_zero_v2i8(
+; CHECK-SAME: <2 x i8> [[X:%.*]]) {
+; CHECK-NEXT:    [[R:%.*]] = icmp ne <2 x i8> [[X]], zeroinitializer
+; CHECK-NEXT:    ret <2 x i1> [[R]]
+;
+  %neg = sub <2 x i8> zeroinitializer, %x
+  %lowbit = and <2 x i8> %x, %neg
+  %r = icmp ne <2 x i8> %lowbit, zeroinitializer
+  ret <2 x i1> %r
+}
+
+; <2 x i8>, (x & -x) == 1 --> trunc x to <2 x i1>
+define <2 x i1> @lowbit_eq_one_v2i8(<2 x i8> %x) {
+; CHECK-LABEL: define <2 x i1> @lowbit_eq_one_v2i8(
+; CHECK-SAME: <2 x i8> [[X:%.*]]) {
+; CHECK-NEXT:    [[R:%.*]] = trunc <2 x i8> [[X]] to <2 x i1>
+; CHECK-NEXT:    ret <2 x i1> [[R]]
+;
+  %neg = sub <2 x i8> zeroinitializer, %x
+  %lowbit = and <2 x i8> %x, %neg
+  %r = icmp eq <2 x i8> %lowbit, splat (i8 1)
+  ret <2 x i1> %r
+}
+
+; <2 x i8>, (x & -x) != 1 --> xor (trunc x to <2 x i1>), true
+define <2 x i1> @lowbit_ne_one_v2i8(<2 x i8> %x) {
+; CHECK-LABEL: define <2 x i1> @lowbit_ne_one_v2i8(
+; CHECK-SAME: <2 x i8> [[X:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = trunc <2 x i8> [[X]] to <2 x i1>
+; CHECK-NEXT:    [[R:%.*]] = xor <2 x i1> [[TMP1]], splat (i1 true)
+; CHECK-NEXT:    ret <2 x i1> [[R]]
+;
+  %neg = sub <2 x i8> zeroinitializer, %x
+  %lowbit = and <2 x i8> %x, %neg
+  %r = icmp ne <2 x i8> %lowbit, splat (i8 1)
+  ret <2 x i1> %r
+}
+
+; Scalable vector tests cover all four comparisons.
+
+; <vscale x 2 x i8>, (x & -x) == 0 --> x == 0
+define <vscale x 2 x i1> @lowbit_eq_zero_nxv2i8(<vscale x 2 x i8> %x) {
+; CHECK-LABEL: define <vscale x 2 x i1> @lowbit_eq_zero_nxv2i8(
+; CHECK-SAME: <vscale x 2 x i8> [[X:%.*]]) {
+; CHECK-NEXT:    [[R:%.*]] = icmp eq <vscale x 2 x i8> [[X]], zeroinitializer
+; CHECK-NEXT:    ret <vscale x 2 x i1> [[R]]
+;
+  %neg = sub <vscale x 2 x i8> zeroinitializer, %x
+  %lowbit = and <vscale x 2 x i8> %x, %neg
+  %r = icmp eq <vscale x 2 x i8> %lowbit, zeroinitializer
+  ret <vscale x 2 x i1> %r
+}
+
+; <vscale x 2 x i8>, (x & -x) != 0 --> x != 0
+define <vscale x 2 x i1> @lowbit_ne_zero_nxv2i8(<vscale x 2 x i8> %x) {
+; CHECK-LABEL: define <vscale x 2 x i1> @lowbit_ne_zero_nxv2i8(
+; CHECK-SAME: <vscale x 2 x i8> [[X:%.*]]) {
+; CHECK-NEXT:    [[R:%.*]] = icmp ne <vscale x 2 x i8> [[X]], zeroinitializer
+; CHECK-NEXT:    ret <vscale x 2 x i1> [[R]]
+;
+  %neg = sub <vscale x 2 x i8> zeroinitializer, %x
+  %lowbit = and <vscale x 2 x i8> %x, %neg
+  %r = icmp ne <vscale x 2 x i8> %lowbit, zeroinitializer
+  ret <vscale x 2 x i1> %r
+}
+
+; <vscale x 2 x i8>, (x & -x) == 1 --> trunc x to <vscale x 2 x i1>
+define <vscale x 2 x i1> @lowbit_eq_one_nxv2i8(<vscale x 2 x i8> %x) {
+; CHECK-LABEL: define <vscale x 2 x i1> @lowbit_eq_one_nxv2i8(
+; CHECK-SAME: <vscale x 2 x i8> [[X:%.*]]) {
+; CHECK-NEXT:    [[R:%.*]] = trunc <vscale x 2 x i8> [[X]] to <vscale x 2 x i1>
+; CHECK-NEXT:    ret <vscale x 2 x i1> [[R]]
+;
+  %neg = sub <vscale x 2 x i8> zeroinitializer, %x
+  %lowbit = and <vscale x 2 x i8> %x, %neg
+  %r = icmp eq <vscale x 2 x i8> %lowbit, splat (i8 1)
+  ret <vscale x 2 x i1> %r
+}
+
+; <vscale x 2 x i8>, (x & -x) != 1 --> xor (trunc x to <vscale x 2 x i1>), true
+define <vscale x 2 x i1> @lowbit_ne_one_nxv2i8(<vscale x 2 x i8> %x) {
+; CHECK-LABEL: define <vscale x 2 x i1> @lowbit_ne_one_nxv2i8(
+; CHECK-SAME: <vscale x 2 x i8> [[X:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = trunc <vscale x 2 x i8> [[X]] to <vscale x 2 x i1>
+; CHECK-NEXT:    [[R:%.*]] = xor <vscale x 2 x i1> [[TMP1]], splat (i1 true)
+; CHECK-NEXT:    ret <vscale x 2 x i1> [[R]]
+;
+  %neg = sub <vscale x 2 x i8> zeroinitializer, %x
+  %lowbit = and <vscale x 2 x i8> %x, %neg
+  %r = icmp ne <vscale x 2 x i8> %lowbit, splat (i8 1)
+  ret <vscale x 2 x i1> %r
+}
+
+; The operands of the and may be commuted.
+
+define i1 @lowbit_ne_one_commuted_i8(i8 %x) {
+; CHECK-LABEL: define i1 @lowbit_ne_one_commuted_i8(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = trunc i8 [[X]] to i1
+; CHECK-NEXT:    [[R:%.*]] = xor i1 [[TMP1]], true
+; CHECK-NEXT:    ret i1 [[R]]
+;
+  %neg = sub i8 0, %x
+  %lowbit = and i8 %neg, %x
+  %r = icmp ne i8 %lowbit, 1
+  ret i1 %r
+}
+
+; Zero comparisons do not require the negation or lowbit to have one use.
+
+declare void @use_i8(i8)
+
+define i1 @lowbit_eq_zero_neg_extra_use_i8(i8 %x) {
+; CHECK-LABEL: define i1 @lowbit_eq_zero_neg_extra_use_i8(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT:    [[NEG:%.*]] = sub i8 0, [[X]]
+; CHECK-NEXT:    call void @use_i8(i8 [[NEG]])
+; CHECK-NEXT:    [[R:%.*]] = icmp eq i8 [[X]], 0
+; CHECK-NEXT:    ret i1 [[R]]
+;
+  %neg = sub i8 0, %x
+  call void @use_i8(i8 %neg)
+  %lowbit = and i8 %x, %neg
+  %r = icmp eq i8 %lowbit, 0
+  ret i1 %r
+}
+
+define i1 @lowbit_eq_zero_extra_uses_i8(i8 %x) {
+; CHECK-LABEL: define i1 @lowbit_eq_zero_extra_uses_i8(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT:    [[NEG:%.*]] = sub i8 0, [[X]]
+; CHECK-NEXT:    call void @use_i8(i8 [[NEG]])
+; CHECK-NEXT:    [[LOWBIT:%.*]] = and i8 [[X]], [[NEG]]
+; CHECK-NEXT:    call void @use_i8(i8 [[LOWBIT]])
+; CHECK-NEXT:    [[R:%.*]] = icmp eq i8 [[X]], 0
+; CHECK-NEXT:    ret i1 [[R]]
+;
+  %neg = sub i8 0, %x
+  call void @use_i8(i8 %neg)
+  %lowbit = and i8 %x, %neg
+  call void @use_i8(i8 %lowbit)
+  %r = icmp eq i8 %lowbit, 0
+  ret i1 %r
+}
+
+; Do not fold != 1 when lowbit has another use, as that would increase the
+; instruction count.
+
+define i1 @lowbit_ne_one_extra_use_i8(i8 %x) {
+; CHECK-LABEL: define i1 @lowbit_ne_one_extra_use_i8(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT:    [[NEG:%.*]] = sub i8 0, [[X]]
+; CHECK-NEXT:    [[LOWBIT:%.*]] = and i8 [[X]], [[NEG]]
+; CHECK-NEXT:    call void @use_i8(i8 [[LOWBIT]])
+; CHECK-NEXT:    [[R:%.*]] = icmp ne i8 [[LOWBIT]], 1
+; CHECK-NEXT:    ret i1 [[R]]
+;
+  %neg = sub i8 0, %x
+  %lowbit = and i8 %x, %neg
+  call void @use_i8(i8 %lowbit)
+  %r = icmp ne i8 %lowbit, 1
+  ret i1 %r
+}
+
+; Negative test: the negated operand must be the negation of the other and
+; operand.
+
+define i1 @not_lowbit_eq_zero_
diff erent_operands_i8(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @not_lowbit_eq_zero_
diff erent_operands_i8(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[NEG:%.*]] = sub i8 0, [[Y]]
+; CHECK-NEXT:    [[NOT_LOWBIT:%.*]] = and i8 [[X]], [[NEG]]
+; CHECK-NEXT:    [[R:%.*]] = icmp eq i8 [[NOT_LOWBIT]], 0
+; CHECK-NEXT:    ret i1 [[R]]
+;
+  %neg = sub i8 0, %y
+  %not_lowbit = and i8 %x, %neg
+  %r = icmp eq i8 %not_lowbit, 0
+  ret i1 %r
+}


        


More information about the llvm-commits mailing list