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

Hadong Lee via llvm-commits llvm-commits at lists.llvm.org
Tue Aug 4 07:05:58 PDT 2026


https://github.com/ChrisLee02 updated https://github.com/llvm/llvm-project/pull/213709

>From 34ea841dab77e2df02cba58268d03303602a5b82 Mon Sep 17 00:00:00 2001
From: Chrislee02 <hdchris765 at snu.ac.kr>
Date: Tue, 4 Aug 2026 01:02:36 +0900
Subject: [PATCH 1/3] [InstCombine] Add tests for comparisons of x & -x with 0
 and 1

---
 .../Transforms/InstCombine/icmp-lowbit.ll     | 266 ++++++++++++++++++
 1 file changed, 266 insertions(+)
 create mode 100644 llvm/test/Transforms/InstCombine/icmp-lowbit.ll

diff --git a/llvm/test/Transforms/InstCombine/icmp-lowbit.ll b/llvm/test/Transforms/InstCombine/icmp-lowbit.ll
new file mode 100644
index 0000000000000..813be77334d7d
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/icmp-lowbit.ll
@@ -0,0 +1,266 @@
+; 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 different 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:    [[NEG:%.*]] = sub i2 0, [[X]]
+; CHECK-NEXT:    [[LOWBIT:%.*]] = and i2 [[X]], [[NEG]]
+; CHECK-NEXT:    [[R:%.*]] = icmp eq i2 [[LOWBIT]], 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:    [[NEG:%.*]] = sub i8 0, [[X]]
+; CHECK-NEXT:    [[LOWBIT:%.*]] = and i8 [[X]], [[NEG]]
+; CHECK-NEXT:    [[R:%.*]] = icmp ne i8 [[LOWBIT]], 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:    [[NEG:%.*]] = sub i32 0, [[X]]
+; CHECK-NEXT:    [[LOWBIT:%.*]] = and i32 [[X]], [[NEG]]
+; CHECK-NEXT:    [[R:%.*]] = icmp eq i32 [[LOWBIT]], 1
+; 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:    [[NEG:%.*]] = sub i64 0, [[X]]
+; CHECK-NEXT:    [[LOWBIT:%.*]] = and i64 [[X]], [[NEG]]
+; CHECK-NEXT:    [[R:%.*]] = icmp ne i64 [[LOWBIT]], 1
+; 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:    [[NEG:%.*]] = sub <2 x i8> zeroinitializer, [[X]]
+; CHECK-NEXT:    [[LOWBIT:%.*]] = and <2 x i8> [[X]], [[NEG]]
+; CHECK-NEXT:    [[R:%.*]] = icmp eq <2 x i8> [[LOWBIT]], 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:    [[NEG:%.*]] = sub <2 x i8> zeroinitializer, [[X]]
+; CHECK-NEXT:    [[LOWBIT:%.*]] = and <2 x i8> [[X]], [[NEG]]
+; CHECK-NEXT:    [[R:%.*]] = icmp ne <2 x i8> [[LOWBIT]], 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:    [[NEG:%.*]] = sub <2 x i8> zeroinitializer, [[X]]
+; CHECK-NEXT:    [[LOWBIT:%.*]] = and <2 x i8> [[X]], [[NEG]]
+; CHECK-NEXT:    [[R:%.*]] = icmp eq <2 x i8> [[LOWBIT]], splat (i8 1)
+; 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:    [[NEG:%.*]] = sub <2 x i8> zeroinitializer, [[X]]
+; CHECK-NEXT:    [[LOWBIT:%.*]] = and <2 x i8> [[X]], [[NEG]]
+; CHECK-NEXT:    [[R:%.*]] = icmp ne <2 x i8> [[LOWBIT]], splat (i8 1)
+; 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:    [[NEG:%.*]] = sub <vscale x 2 x i8> zeroinitializer, [[X]]
+; CHECK-NEXT:    [[LOWBIT:%.*]] = and <vscale x 2 x i8> [[X]], [[NEG]]
+; CHECK-NEXT:    [[R:%.*]] = icmp eq <vscale x 2 x i8> [[LOWBIT]], 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:    [[NEG:%.*]] = sub <vscale x 2 x i8> zeroinitializer, [[X]]
+; CHECK-NEXT:    [[LOWBIT:%.*]] = and <vscale x 2 x i8> [[X]], [[NEG]]
+; CHECK-NEXT:    [[R:%.*]] = icmp ne <vscale x 2 x i8> [[LOWBIT]], 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:    [[NEG:%.*]] = sub <vscale x 2 x i8> zeroinitializer, [[X]]
+; CHECK-NEXT:    [[LOWBIT:%.*]] = and <vscale x 2 x i8> [[X]], [[NEG]]
+; CHECK-NEXT:    [[R:%.*]] = icmp eq <vscale x 2 x i8> [[LOWBIT]], splat (i8 1)
+; 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:    [[NEG:%.*]] = sub <vscale x 2 x i8> zeroinitializer, [[X]]
+; CHECK-NEXT:    [[LOWBIT:%.*]] = and <vscale x 2 x i8> [[X]], [[NEG]]
+; CHECK-NEXT:    [[R:%.*]] = icmp ne <vscale x 2 x i8> [[LOWBIT]], splat (i8 1)
+; 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:    [[NEG:%.*]] = sub i8 0, [[X]]
+; CHECK-NEXT:    [[LOWBIT:%.*]] = and i8 [[X]], [[NEG]]
+; CHECK-NEXT:    [[R:%.*]] = icmp ne i8 [[LOWBIT]], 1
+; 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_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 [[LOWBIT]], 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_different_operands_i8(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @not_lowbit_eq_zero_different_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
+}

>From d95dda30652e80e298aa8fd395ed46cc71265853 Mon Sep 17 00:00:00 2001
From: Chrislee02 <hdchris765 at snu.ac.kr>
Date: Tue, 4 Aug 2026 01:05:09 +0900
Subject: [PATCH 2/3] [InstCombine] Fold comparisons of x & -x with 0 and 1

---
 .../InstCombine/InstCombineCompares.cpp       | 25 ++++++++
 .../Transforms/InstCombine/and-or-icmps.ll    |  4 +-
 .../test/Transforms/InstCombine/ctpop-pow2.ll | 12 +---
 .../Transforms/InstCombine/icmp-lowbit.ll     | 64 +++++++------------
 4 files changed, 51 insertions(+), 54 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index 70d584740f5b9..f3166f915b4ad 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 *LowBitX;
+  // Match X & -X in either operand order.
+  if (C.getBitWidth() > 1 && (C.isZero() || C.isOne()) &&
+      match(And, m_c_And(m_Neg(m_Value(LowBitX)), m_Deferred(LowBitX)))) {
+    // Preserve the predicate: (X & -X) ==/!= 0 --> X ==/!= 0.
+    if (C.isZero())
+      return new ICmpInst(Pred, LowBitX, Cmp.getOperand(1));
+
+    // (X & -X) == 1 iff the low bit of X is set.
+    if (Pred == CmpInst::ICMP_EQ)
+      return new TruncInst(LowBitX, 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(LowBitX, 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
index 813be77334d7d..3741cea8c4f45 100644
--- a/llvm/test/Transforms/InstCombine/icmp-lowbit.ll
+++ b/llvm/test/Transforms/InstCombine/icmp-lowbit.ll
@@ -13,9 +13,7 @@
 define i1 @lowbit_eq_zero_i2(i2 %x) {
 ; CHECK-LABEL: define i1 @lowbit_eq_zero_i2(
 ; CHECK-SAME: i2 [[X:%.*]]) {
-; CHECK-NEXT:    [[NEG:%.*]] = sub i2 0, [[X]]
-; CHECK-NEXT:    [[LOWBIT:%.*]] = and i2 [[X]], [[NEG]]
-; CHECK-NEXT:    [[R:%.*]] = icmp eq i2 [[LOWBIT]], 0
+; CHECK-NEXT:    [[R:%.*]] = icmp eq i2 [[X]], 0
 ; CHECK-NEXT:    ret i1 [[R]]
 ;
   %neg = sub i2 0, %x
@@ -28,9 +26,7 @@ define i1 @lowbit_eq_zero_i2(i2 %x) {
 define i1 @lowbit_ne_zero_i8(i8 %x) {
 ; CHECK-LABEL: define i1 @lowbit_ne_zero_i8(
 ; CHECK-SAME: i8 [[X:%.*]]) {
-; CHECK-NEXT:    [[NEG:%.*]] = sub i8 0, [[X]]
-; CHECK-NEXT:    [[LOWBIT:%.*]] = and i8 [[X]], [[NEG]]
-; CHECK-NEXT:    [[R:%.*]] = icmp ne i8 [[LOWBIT]], 0
+; CHECK-NEXT:    [[R:%.*]] = icmp ne i8 [[X]], 0
 ; CHECK-NEXT:    ret i1 [[R]]
 ;
   %neg = sub i8 0, %x
@@ -43,9 +39,7 @@ define i1 @lowbit_ne_zero_i8(i8 %x) {
 define i1 @lowbit_eq_one_i32(i32 %x) {
 ; CHECK-LABEL: define i1 @lowbit_eq_one_i32(
 ; CHECK-SAME: i32 [[X:%.*]]) {
-; CHECK-NEXT:    [[NEG:%.*]] = sub i32 0, [[X]]
-; CHECK-NEXT:    [[LOWBIT:%.*]] = and i32 [[X]], [[NEG]]
-; CHECK-NEXT:    [[R:%.*]] = icmp eq i32 [[LOWBIT]], 1
+; CHECK-NEXT:    [[R:%.*]] = trunc i32 [[X]] to i1
 ; CHECK-NEXT:    ret i1 [[R]]
 ;
   %neg = sub i32 0, %x
@@ -58,9 +52,8 @@ define i1 @lowbit_eq_one_i32(i32 %x) {
 define i1 @lowbit_ne_one_i64(i64 %x) {
 ; CHECK-LABEL: define i1 @lowbit_ne_one_i64(
 ; CHECK-SAME: i64 [[X:%.*]]) {
-; CHECK-NEXT:    [[NEG:%.*]] = sub i64 0, [[X]]
-; CHECK-NEXT:    [[LOWBIT:%.*]] = and i64 [[X]], [[NEG]]
-; CHECK-NEXT:    [[R:%.*]] = icmp ne i64 [[LOWBIT]], 1
+; 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
@@ -75,9 +68,7 @@ define i1 @lowbit_ne_one_i64(i64 %x) {
 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:    [[NEG:%.*]] = sub <2 x i8> zeroinitializer, [[X]]
-; CHECK-NEXT:    [[LOWBIT:%.*]] = and <2 x i8> [[X]], [[NEG]]
-; CHECK-NEXT:    [[R:%.*]] = icmp eq <2 x i8> [[LOWBIT]], zeroinitializer
+; CHECK-NEXT:    [[R:%.*]] = icmp eq <2 x i8> [[X]], zeroinitializer
 ; CHECK-NEXT:    ret <2 x i1> [[R]]
 ;
   %neg = sub <2 x i8> zeroinitializer, %x
@@ -90,9 +81,7 @@ define <2 x i1> @lowbit_eq_zero_v2i8(<2 x i8> %x) {
 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:    [[NEG:%.*]] = sub <2 x i8> zeroinitializer, [[X]]
-; CHECK-NEXT:    [[LOWBIT:%.*]] = and <2 x i8> [[X]], [[NEG]]
-; CHECK-NEXT:    [[R:%.*]] = icmp ne <2 x i8> [[LOWBIT]], zeroinitializer
+; CHECK-NEXT:    [[R:%.*]] = icmp ne <2 x i8> [[X]], zeroinitializer
 ; CHECK-NEXT:    ret <2 x i1> [[R]]
 ;
   %neg = sub <2 x i8> zeroinitializer, %x
@@ -105,9 +94,7 @@ define <2 x i1> @lowbit_ne_zero_v2i8(<2 x i8> %x) {
 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:    [[NEG:%.*]] = sub <2 x i8> zeroinitializer, [[X]]
-; CHECK-NEXT:    [[LOWBIT:%.*]] = and <2 x i8> [[X]], [[NEG]]
-; CHECK-NEXT:    [[R:%.*]] = icmp eq <2 x i8> [[LOWBIT]], splat (i8 1)
+; 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
@@ -120,9 +107,8 @@ define <2 x i1> @lowbit_eq_one_v2i8(<2 x i8> %x) {
 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:    [[NEG:%.*]] = sub <2 x i8> zeroinitializer, [[X]]
-; CHECK-NEXT:    [[LOWBIT:%.*]] = and <2 x i8> [[X]], [[NEG]]
-; CHECK-NEXT:    [[R:%.*]] = icmp ne <2 x i8> [[LOWBIT]], splat (i8 1)
+; 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
@@ -137,9 +123,7 @@ define <2 x i1> @lowbit_ne_one_v2i8(<2 x i8> %x) {
 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:    [[NEG:%.*]] = sub <vscale x 2 x i8> zeroinitializer, [[X]]
-; CHECK-NEXT:    [[LOWBIT:%.*]] = and <vscale x 2 x i8> [[X]], [[NEG]]
-; CHECK-NEXT:    [[R:%.*]] = icmp eq <vscale x 2 x i8> [[LOWBIT]], zeroinitializer
+; 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
@@ -152,9 +136,7 @@ define <vscale x 2 x i1> @lowbit_eq_zero_nxv2i8(<vscale x 2 x i8> %x) {
 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:    [[NEG:%.*]] = sub <vscale x 2 x i8> zeroinitializer, [[X]]
-; CHECK-NEXT:    [[LOWBIT:%.*]] = and <vscale x 2 x i8> [[X]], [[NEG]]
-; CHECK-NEXT:    [[R:%.*]] = icmp ne <vscale x 2 x i8> [[LOWBIT]], zeroinitializer
+; 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
@@ -167,9 +149,7 @@ define <vscale x 2 x i1> @lowbit_ne_zero_nxv2i8(<vscale x 2 x i8> %x) {
 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:    [[NEG:%.*]] = sub <vscale x 2 x i8> zeroinitializer, [[X]]
-; CHECK-NEXT:    [[LOWBIT:%.*]] = and <vscale x 2 x i8> [[X]], [[NEG]]
-; CHECK-NEXT:    [[R:%.*]] = icmp eq <vscale x 2 x i8> [[LOWBIT]], splat (i8 1)
+; 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
@@ -182,9 +162,8 @@ define <vscale x 2 x i1> @lowbit_eq_one_nxv2i8(<vscale x 2 x i8> %x) {
 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:    [[NEG:%.*]] = sub <vscale x 2 x i8> zeroinitializer, [[X]]
-; CHECK-NEXT:    [[LOWBIT:%.*]] = and <vscale x 2 x i8> [[X]], [[NEG]]
-; CHECK-NEXT:    [[R:%.*]] = icmp ne <vscale x 2 x i8> [[LOWBIT]], splat (i8 1)
+; 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
@@ -198,9 +177,8 @@ define <vscale x 2 x i1> @lowbit_ne_one_nxv2i8(<vscale x 2 x i8> %x) {
 define i1 @lowbit_ne_one_commuted_i8(i8 %x) {
 ; CHECK-LABEL: define i1 @lowbit_ne_one_commuted_i8(
 ; CHECK-SAME: i8 [[X:%.*]]) {
-; CHECK-NEXT:    [[NEG:%.*]] = sub i8 0, [[X]]
-; CHECK-NEXT:    [[LOWBIT:%.*]] = and i8 [[X]], [[NEG]]
-; CHECK-NEXT:    [[R:%.*]] = icmp ne i8 [[LOWBIT]], 1
+; 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
@@ -220,7 +198,7 @@ define i1 @lowbit_eq_zero_extra_uses_i8(i8 %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 [[LOWBIT]], 0
+; CHECK-NEXT:    [[R:%.*]] = icmp eq i8 [[X]], 0
 ; CHECK-NEXT:    ret i1 [[R]]
 ;
   %neg = sub i8 0, %x
@@ -231,7 +209,8 @@ define i1 @lowbit_eq_zero_extra_uses_i8(i8 %x) {
   ret i1 %r
 }
 
-; Do not fold != 1 when lowbit has another use, as that would increase the instruction count.
+; 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(
@@ -249,7 +228,8 @@ define i1 @lowbit_ne_one_extra_use_i8(i8 %x) {
   ret i1 %r
 }
 
-; Negative test: the negated operand must be the negation of the other and operand.
+; Negative test: the negated operand must be the negation of the other and
+; operand.
 
 define i1 @not_lowbit_eq_zero_different_operands_i8(i8 %x, i8 %y) {
 ; CHECK-LABEL: define i1 @not_lowbit_eq_zero_different_operands_i8(

>From 790b1499b40afc4ec3009eab7026fd5d18002fde Mon Sep 17 00:00:00 2001
From: Chrislee02 <hdchris765 at snu.ac.kr>
Date: Tue, 4 Aug 2026 22:53:12 +0900
Subject: [PATCH 3/3] [InstCombine] Add test for lowbit fold with multi-use neg

---
 llvm/test/Transforms/InstCombine/icmp-lowbit.ll | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/llvm/test/Transforms/InstCombine/icmp-lowbit.ll b/llvm/test/Transforms/InstCombine/icmp-lowbit.ll
index 3741cea8c4f45..838e07ee4d64a 100644
--- a/llvm/test/Transforms/InstCombine/icmp-lowbit.ll
+++ b/llvm/test/Transforms/InstCombine/icmp-lowbit.ll
@@ -191,6 +191,21 @@ define i1 @lowbit_ne_one_commuted_i8(i8 %x) {
 
 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:%.*]]) {



More information about the llvm-commits mailing list