On Mon, May 25, 2026 at 3:15 AM Andrew Pinski < [email protected]> wrote:
> On Sun, May 24, 2026 at 1:51 PM Shivam Gupta <[email protected]> > wrote: > > > > 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))))) > > We can combine this into one simplify and also let's just use `((a ^b) > & CST) ==/!= 0` as the result as it will simplify anyways into that so > let's fold directly. > > We can do better even by doing something like: > ``` > (for ocmp (eq ne bit_xor) > (for icmp0 (eq eq ne ne) > icmp1 (eq ne eq ne) > (simplify > (ocmp > (icmp0 (bit_and @0 integer_pow2p@1) integer_zerop@3) > (icmp1 (bit_and @2 @1) integer_zerop)) > (if (types_match (@0, @2)) > (with { > bool innersame = (icmp0 == EQ_EXPR) == (icmp1 == EQ_EXPR); > } > (if (innersame == (ocmp == EQ_EXPR)) > (eq (bit_and (bit_xor @0 @2) @1) @3) > (ne (bit_and (bit_xor @0 @2) @1) @3))))))) > ``` > This should catch all cases, and I think I did the logic correctly for > the resulting pattern too. > Please make sure I did the logic correct when I combined these and > make sure you add a few testcases. > The cases you were missing in your pattern was : > ((X & C) == 0) != ((Y & C) == 0) > ((X & C) != 0) == ((Y & C) == 0) > Even though you handled that case for ^ (which is the same as != for > booleans). > > Thanks, > Andrea > > Hello, I agree with suggestion to simplify. But then I need to add a another simplify rule since _Bool f (u32 a, u32 b) { u32 t1 = a & 1; u32 t2 = b & 1; return (t1 == 0) ^ (t2 != 0); } is going different route in GIMPLE form : t1_5 = a_4(D) & 1; t2_7 = b_6(D) & 1; _1 = t1_5 == 0; _2 = (int) _1; _3 = (int) t2_7; _8 = _2 != _3; return _8; Please check v3 of the patch. Thanks, Shivam > + > > (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 > > >
