[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