[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