[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:06:49 PDT 2026
================
@@ -0,0 +1,246 @@
+; 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: [[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_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
----------------
ChrisLee02 wrote:
https://github.com/llvm/llvm-project/pull/213709/commits/790b1499b40afc4ec3009eab7026fd5d18002fde
👍 👍
https://github.com/llvm/llvm-project/pull/213709
More information about the llvm-commits
mailing list