On Mon, 17 Aug 2026 at 14:03, Richard Biener <[email protected]> wrote:

> On Thu, 13 Aug 2026, Dominic P wrote:
>
> > The PR35691 rules combine x == 0 & y == 0 into (x | y) == 0, which
> > covers two of the eight ways a pair of single-bit tests of the same
> > value can be combined; the remaining combinations were left as two
> > tests and a boolean operation.  For single bits C1 and C2 of X, with
> > M = C1 | C2:
> >
> >   (X & C1) != 0 & (X & C2) != 0  ->  (X & M) == M
> >   (X & C1) == 0 | (X & C2) == 0  ->  (X & M) != M
> >   (X & C1) != 0 & (X & C2) == 0  ->  (X & M) == C1
> >   (X & C1) != 0 | (X & C2) == 0  ->  (X & M) != C2
> >
> > These hold only for power-of-two masks, where bit-set and bit-clear are
> > both single-bit tests; multi-bit masks are not combinable this way and
> > are rejected by the integer_pow2p guards.  The comparisons and inner
> > masks are required to be single-use so the rewrite never adds a
> > statement when the individual tests have other consumers.  On ARM each
> > combination reduces from 4-8 instructions to the uniform
> > and-compare-branch sequence.
> >
> > Assisted-by: Claude Opus 4.8 (Anthropic)
>
> Looks good, but how did you test this?
>
> Bootstrapped and regtested on x86_64-pc-linux-gnu, no regressions.


> Thanks,
> Richard.
>
> > gcc/ChangeLog:
> >
> >       * match.pd ((X & C1) cmp 0 op (X & C2) cmp 0): Combine pairs of
> >       single-bit tests of the same operand into a single compare of
> >       the union mask.
> >
> > gcc/testsuite/ChangeLog:
> >
> >       * gcc.dg/fold-bit-test-combine-1.c: New test.
> >
> > Signed-off-by: Dominic P <[email protected]>
> > ---
> >  gcc/match.pd                                  | 30 +++++++++++++++++
> >  .../gcc.dg/fold-bit-test-combine-1.c          | 33 +++++++++++++++++++
> >  2 files changed, 63 insertions(+)
> >  create mode 100644 gcc/testsuite/gcc.dg/fold-bit-test-combine-1.c
> >
> > diff --git a/gcc/match.pd b/gcc/match.pd
> > index 90fb2acede4..c160e9e4725 100644
> > --- a/gcc/match.pd
> > +++ b/gcc/match.pd
> > @@ -1603,6 +1603,36 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
> >       && TYPE_PRECISION (TREE_TYPE (@0)) == TYPE_PRECISION (TREE_TYPE
> (@1)))
> >      (cmp (bit_and @0 (convert @1)) @2))))
> >
> > +/* Combine two single-bit tests of the same value X into one masked
> > +   compare.  For distinct single bits C1, C2 and M = C1 | C2:
> > +     (X & C1) != 0 & (X & C2) != 0  ->  (X & M) == M
> > +     (X & C1) == 0 | (X & C2) == 0  ->  (X & M) != M
> > +     (X & C1) != 0 & (X & C2) == 0  ->  (X & M) == C1
> > +     (X & C1) != 0 | (X & C2) == 0  ->  (X & M) != C2
> > +   The == 0 & == 0 and != 0 | != 0 cases are handled by the more general
> > +   rule above.  Restricted to single-bit masks: for a multi-bit mask
> > +   (X & C) != 0 is not a single-bit test.  */
> > +(for bitop (bit_and bit_ior)
> > +     cmp (ne eq)
> > +     rcmp (eq ne)
> > + (simplify
> > +  (bitop (cmp:s (bit_and:s @0 INTEGER_CST@1) integer_zerop)
> > +      (cmp:s (bit_and:s @0 INTEGER_CST@2) integer_zerop))
> > +  (if (integer_pow2p (@1) && integer_pow2p (@2)
> > +       && wi::to_wide (@1) != wi::to_wide (@2))
> > +   (with { tree m = wide_int_to_tree (TREE_TYPE (@0),
> > +                                   wi::to_wide (@1) | wi::to_wide
> (@2)); }
> > +    (rcmp (bit_and @0 { m; }) { m; }))))
> > + (simplify
> > +  (bitop:c (ne:s (bit_and:s @0 INTEGER_CST@1) integer_zerop)
> > +        (eq:s (bit_and:s @0 INTEGER_CST@2) integer_zerop))
> > +  (if (integer_pow2p (@1) && integer_pow2p (@2)
> > +       && wi::to_wide (@1) != wi::to_wide (@2))
> > +   (with { tree m = wide_int_to_tree (TREE_TYPE (@0),
> > +                                   wi::to_wide (@1) | wi::to_wide
> (@2)); }
> > +    (rcmp (bit_and @0 { m; })
> > +       { bitop == BIT_AND_EXPR ? @1 : @2; })))))
> > +
> >  /* Fold (A & ~B) - (A & B) into (A ^ B) - B.  */
> >  (simplify
> >   (minus (bit_and:cs @0 (bit_not @1)) (bit_and:cs @0 @1))
> > diff --git a/gcc/testsuite/gcc.dg/fold-bit-test-combine-1.c
> b/gcc/testsuite/gcc.dg/fold-bit-test-combine-1.c
> > new file mode 100644
> > index 00000000000..69de70649dc
> > --- /dev/null
> > +++ b/gcc/testsuite/gcc.dg/fold-bit-test-combine-1.c
> > @@ -0,0 +1,33 @@
> > +/* Two single-bit tests of the same value combine into one masked
> compare.  */
> > +/* { dg-do compile } */
> > +/* { dg-options "-O2 -fdump-tree-optimized" } */
> > +
> > +int
> > +t_and_set_set (unsigned x)           /* (x&8)!=0 & (x&16)!=0 ->
> (x&24)==24 */
> > +{
> > +  return ((x & 8) != 0) & ((x & 16) != 0);
> > +}
> > +
> > +int
> > +t_and_set_clr (unsigned x)           /* (x&8)!=0 & (x&16)==0 ->
> (x&24)==8 */
> > +{
> > +  return ((x & 8) != 0) & ((x & 16) == 0);
> > +}
> > +
> > +int
> > +t_or_clr_clr (unsigned x)            /* (x&8)==0 | (x&16)==0 ->
> (x&24)!=24 */
> > +{
> > +  return ((x & 8) == 0) | ((x & 16) == 0);
> > +}
> > +
> > +int
> > +t_or_set_clr (unsigned x)            /* (x&8)!=0 | (x&16)==0 ->
> (x&24)!=16 */
> > +{
> > +  return ((x & 8) != 0) | ((x & 16) == 0);
> > +}
> > +
> > +/* Each becomes a single (x & 24) compare; the separate & 8 / & 16
> masks and
> > +   the boolean combiner are gone.  */
> > +/* { dg-final { scan-tree-dump-times " & 24;" 4 "optimized" } } */
> > +/* { dg-final { scan-tree-dump-not " & 8;" "optimized" } } */
> > +/* { dg-final { scan-tree-dump-not " & 16;" "optimized" } } */
> >
>
> --
> Richard Biener <[email protected]>
> SUSE Software Solutions Germany GmbH,
> Frankenstrasse 146, 90461 Nuernberg, Germany;
> GF: Jochen Jaser, Andrew McDonald, Abhinav Puri; (HRB 36809, AG Nuernberg)
>
>

Reply via email to