At -O1, GCC lowers (~a & 1) == (~b & 1) into two different GIMPLE
forms depending on whether the boolean values come from a function
return boundary or an inline expression:
1. Via function return (e.g. inlined is_even), GCC preserves the
eq/ne form because the function boundary hides the internal
structure from the gimplifier:
((a&1)==0) == ((b&1)==0) outer node is eq/ne
2. Via inline expression (directly written), GCC sees the full
boolean expression tree at once and folds to bit_xor during
gimplification:
((a&1)!=0) ^ ((b&1)==0) outer node is bit_xor
Add two match.pd rules to handle both forms:
1. (cmp (eq (x&c) 0) (eq (y&c) 0)) -> (cmp (x&c) (y&c))
2. (bit_xor (cmp (x&c) 0) (eq (y&c) 0)) -> (xcmp (x&c) (y&c))
Subsequent passes further simplify these to (x^y)&c == 0.
Bootstrapped and tested on aarch64-linux-gnu.
Changes since v1:
* v2: Generalize mask from integer_onep to integer_pow2p.
Combine both patterns into single one.
Split test into bool-eq-evenness.c, bool-eq-bitxor.c,
bool-eq-pow2.c.
PR tree-optimization/112533
gcc/ChangeLog:
* match.pd: Simplify ((x&c)==0) cmp ((y&c)==0) to (x&c) cmp (y&c)
and ((x&c) cmp 0)^((y&c)==0) to (x&c) xcmp (y&c) for pow2 c.
gcc/testsuite/ChangeLog:
* gcc.dg/tree-ssa/bool-eq-bitxor.c: New test.
* gcc.dg/tree-ssa/bool-eq-evenness.c: New test.
* gcc.dg/tree-ssa/bool-eq-pow2.c: New test.
Signed-off-by: Shivam Gupta <[email protected]>
---
gcc/match.pd | 20 +++++++++
.../gcc.dg/tree-ssa/bool-eq-bitxor.c | 23 ++++++++++
.../gcc.dg/tree-ssa/bool-eq-evenness.c | 43 +++++++++++++++++++
gcc/testsuite/gcc.dg/tree-ssa/bool-eq-pow2.c | 33 ++++++++++++++
4 files changed, 119 insertions(+)
create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/bool-eq-bitxor.c
create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/bool-eq-evenness.c
create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/bool-eq-pow2.c
diff --git a/gcc/match.pd b/gcc/match.pd
index ff13a07ea94..f5a2844b43e 100644
--- a/gcc/match.pd
+++ b/gcc/match.pd
@@ -1464,6 +1464,26 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
(if (TYPE_UNSIGNED (type))
(bit_and @0 (bit_not (lshift { build_all_ones_cst (type); } @1)))))
+/* PR112533: Transform
+ ((X & C) == 0) == ((Y & C) == 0) -> (X & C) == (Y & C).
+ ((X & C) == 0) != ((Y & C) == 0) -> (X & C) != (Y & C).
+ ((X & C) != 0) ^ ((Y & C) == 0) -> (X & C) == (Y & C).
+ ((X & C) == 0) ^ ((Y & C) == 0) -> (X & C) != (Y & C). */
+(for cmp (eq ne)
+ xcmp (ne eq)
+ (simplify
+ (cmp
+ (eq (bit_and @0 integer_pow2p@1) integer_zerop)
+ (eq (bit_and @2 @1) integer_zerop))
+ (if (types_match (@0, @2))
+ (cmp (bit_and @0 @1) (bit_and @2 @1))))
+ (simplify
+ (bit_xor:c
+ (cmp (bit_and @0 integer_pow2p@1) integer_zerop)
+ (eq (bit_and @2 @1) integer_zerop))
+ (if (types_match (@0, @2))
+ (xcmp (bit_and @0 @1) (bit_and @2 @1)))))
+
(for bitop (bit_and bit_ior)
cmp (eq ne)
/* PR35691: Transform
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/bool-eq-bitxor.c
b/gcc/testsuite/gcc.dg/tree-ssa/bool-eq-bitxor.c
new file mode 100644
index 00000000000..119dbce0fae
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/bool-eq-bitxor.c
@@ -0,0 +1,23 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fdump-tree-optimized" } */
+
+typedef unsigned int u32;
+
+_Bool
+xor_ne_eq (u32 a, u32 b)
+{
+ u32 t1 = a & 1;
+ u32 t2 = b & 1;
+ return (t1 != 0) ^ (t2 == 0);
+}
+
+_Bool
+xor_eq_eq (u32 a, u32 b)
+{
+ u32 t1 = a & 1;
+ u32 t2 = b & 1;
+ return (t1 == 0) ^ (t2 == 0);
+}
+
+/* Verify boolean xor-of-comparisons is eliminated. */
+/* { dg-final { scan-tree-dump-not "== 0" "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/bool-eq-evenness.c
b/gcc/testsuite/gcc.dg/tree-ssa/bool-eq-evenness.c
new file mode 100644
index 00000000000..75df70dc6a4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/bool-eq-evenness.c
@@ -0,0 +1,43 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fdump-tree-optimized" } */
+
+/* Verify the GIMPLE-level fix for PR112533. */
+
+typedef unsigned int u32;
+
+static _Bool
+is_even (u32 a)
+{
+ return a % 2 == 0;
+}
+
+_Bool
+same_evenness (u32 a, u32 b)
+{
+ return is_even (a) == is_even (b);
+}
+
+_Bool
+diff_evenness (u32 a, u32 b)
+{
+ return is_even (a) != is_even (b);
+}
+
+_Bool
+same_evenness_cmp_zero (u32 a, u32 b)
+{
+ u32 t1 = a & 1;
+ u32 t2 = b & 1;
+ return (t1 == 0) == (t2 == 0);
+}
+
+_Bool
+diff_evenness_cmp_zero (u32 a, u32 b)
+{
+ u32 t1 = a & 1;
+ u32 t2 = b & 1;
+ return (t1 == 0) != (t2 == 0);
+}
+
+/* Verify all functions canonicalize to xor forms. */
+/* { dg-final { scan-tree-dump-times "\\^" 4 "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/bool-eq-pow2.c
b/gcc/testsuite/gcc.dg/tree-ssa/bool-eq-pow2.c
new file mode 100644
index 00000000000..44e3b662e86
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/bool-eq-pow2.c
@@ -0,0 +1,33 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fdump-tree-optimized" } */
+
+typedef unsigned int u32;
+
+_Bool
+same_bit1 (u32 a, u32 b)
+{
+ u32 t1 = a & 2;
+ u32 t2 = b & 2;
+ return (t1 == 0) == (t2 == 0);
+}
+
+_Bool
+same_bit2 (u32 a, u32 b)
+{
+ u32 t1 = a & 4;
+ u32 t2 = b & 4;
+ return (t1 == 0) == (t2 == 0);
+}
+
+/* Non-power-of-2: should not be simplified. */
+_Bool
+same_bits_nonpow2 (u32 a, u32 b)
+{
+ u32 t1 = a & 3;
+ u32 t2 = b & 3;
+ return (t1 == 0) == (t2 == 0);
+}
+
+/* Verify power-of-2 masks are simplified, non-power-of-2 masks are not. */
+/* { dg-final { scan-tree-dump-times "\\^" 3 "optimized" } } */
+/* { dg-final { scan-tree-dump-times "& 3" 2 "optimized" } } */
--
2.34.1