Re: [PATCH] match: fold two comparisons of a masked value
Richard Biener <[email protected]> Tue, 4 Aug 2026 13:26:57 +0200
| Newsgroups | gmane.comp.gcc.patches |
|---|---|
| Message-ID | <CAFiYyc0Fbcz_afUME8-DbHFHXKHZnJa72wNw6TFOPKHR=2pAcA@mail.gmail.com> |
On Tue, Aug 4, 2026 at 11:44=E2=80=AFAM <[email protected]> wrote: > > From: Kyrylo Tkachov <[email protected]> > > ((X + C1) & LOWMASK) =3D=3D/!=3D C2 -> (X & LOWMASK) =3D=3D/!=3D C2 - C= 1 > (X & HIGHMASK) >/<=3D C -> X >/<=3D (C | ~HIGHMASK) > > Only the low bits of the addition survive the first mask, so the constant > moves to the other side of the comparison and the addition goes away. A > comparison constant that does not fit the mask makes the result fixed. > > Clearing the low bits rounds the value down, and a rounded value passes a > relational comparison exactly when the value itself passes it against the > constant with those bits set, so the second mask goes away too. The > other two predicates are already canonicalised into these. > > int f (int x) { return (x & -8) > 16; } > > aarch64 -O2: > > before after > and w0, w0, -8 cmp w0, 23 > cmp w0, 16 cset w0, gt > cset w0, gt > > Two existing tests observe shapes this rewrite now folds. bic-bitmask-19= .c > scans for "> 1" and the loop guard (n & -16) > 0 becomes n > 15, which th= e > regex also matches, so it is anchored on the statement end. pr68217.c > relies on the comparison keeping the masked value live, so the value is > returned instead. > > Keep signed additions that trap or carry sanitizer instrumentation. > Also accept high-mask constants whose discarded low bits are already set. > > Bootstrapped and tested on aarch64-none-linux-gnu. > Ok for trunk? OK. Thanks, Richard. > Thanks, > Kyrill > > gcc/ChangeLog: > > * match.pd (((X + C1) & LOWMASK) =3D=3D/!=3D C2): New simplificat= ion. > ((X & HIGHMASK) >/<=3D C): Likewise. > > gcc/testsuite/ChangeLog: > > * gcc.dg/tree-ssa/maskcmp-1.c: New test. > * gcc.dg/bic-bitmask-19.c: Anchor the comparison scan. > * gcc.dg/pr68217.c: Return the masked value. > * gcc.dg/tree-ssa/maskcmp-overflow-1.c: New test. > * gcc.dg/tree-ssa/maskcmp-overflow-2.c: New test. > > Signed-off-by: Kyrylo Tkachov <[email protected]> > --- > gcc/match.pd | 28 +++++++++++++++++++ > gcc/testsuite/gcc.dg/bic-bitmask-19.c | 4 ++- > gcc/testsuite/gcc.dg/pr68217.c | 12 ++++++-- > gcc/testsuite/gcc.dg/tree-ssa/maskcmp-1.c | 23 +++++++++++++++ > .../gcc.dg/tree-ssa/maskcmp-overflow-1.c | 10 +++++++ > .../gcc.dg/tree-ssa/maskcmp-overflow-2.c | 10 +++++++ > 6 files changed, 83 insertions(+), 4 deletions(-) > create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/maskcmp-1.c > create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/maskcmp-overflow-1.c > create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/maskcmp-overflow-2.c > > diff --git a/gcc/match.pd b/gcc/match.pd > index 9924f95c4cc..b27d9a0bad0 100644 > --- a/gcc/match.pd > +++ b/gcc/match.pd > @@ -5354,6 +5354,34 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT) > && wi::lshift (wi::to_wide (@0), cand) =3D=3D wi::to_wide (@2)) > (cmp @1 { build_int_cst (TREE_TYPE (@1), cand); })))))) > > +/* Fold ((X + C1) & LOWMASK) =3D=3D/!=3D C2 into (X & LOWMASK) =3D=3D/!= =3D C2 - C1. > + Only the low bits of the addition survive the mask, so the constant c= an > + move to the other side of the comparison and the addition goes away. = */ > +(for cmp (eq ne) > + (simplify > + (cmp (bit_and:s (plus:s @0 INTEGER_CST@1) INTEGER_CST@2) INTEGER_CST@3= ) > + (if (INTEGRAL_TYPE_P (TREE_TYPE (@0)) > + && !TYPE_OVERFLOW_TRAPS (TREE_TYPE (@0)) > + && !TYPE_OVERFLOW_SANITIZED (TREE_TYPE (@0)) > + && wi::popcount (wi::to_wide (@2) + 1) =3D=3D 1) > + (with { wide_int mask =3D wi::to_wide (@2); > + wide_int rhs =3D (wi::to_wide (@3) - wi::to_wide (@1)) & mask;= } > + (if ((wi::to_wide (@3) & ~mask) =3D=3D 0) > + (cmp (bit_and @0 @2) > + { wide_int_to_tree (TREE_TYPE (@0), rhs); }) > + { constant_boolean_node (cmp =3D=3D NE_EXPR, type); })))) > + > +/* Fold (X & HIGHMASK) >/<=3D C into X >/<=3D (C | ~HIGHMASK). Clearing= the > + low bits of X rounds it down, and a rounded value passes the comparis= on > + exactly when X passes it against the constant with those bits set. *= / > +(for cmp (gt le) > + (simplify > + (cmp (bit_and:s @0 INTEGER_CST@1) INTEGER_CST@2) > + (if (INTEGRAL_TYPE_P (TREE_TYPE (@0)) > + && bitmask_inv_cst_vector_p (@1)) > + (with { wide_int c =3D wi::to_wide (@2) | wi::bit_not (wi::to_wide (@= 1)); } > + (cmp @0 { wide_int_to_tree (TREE_TYPE (@0), c); }))))) > + > /* Fold ((X << C1) & C2) cmp C3 into (X & (C2 >> C1)) cmp (C3 >> C1) > ((X >> C1) & C2) cmp C3 into (X & (C2 << C1)) cmp (C3 << C1). */ > (for cmp (ne eq) > diff --git a/gcc/testsuite/gcc.dg/bic-bitmask-19.c b/gcc/testsuite/gcc.dg= /bic-bitmask-19.c > index aa139da5c1e..d1574fbff80 100644 > --- a/gcc/testsuite/gcc.dg/bic-bitmask-19.c > +++ b/gcc/testsuite/gcc.dg/bic-bitmask-19.c > @@ -19,6 +19,8 @@ void fun2(uint32_t *x, int n) > > #include "bic-bitmask.h" > > -/* { dg-final { scan-tree-dump-times {>\s* 1} 1 dce7 { target vect_int }= } } */ > +/* The loop guard (n & -16) > 0 now folds to n > 15, so anchor the scan = on > + the statement end to keep it matching only the comparison under test.= */ > +/* { dg-final { scan-tree-dump-times {>\s* 1;} 1 dce7 { target vect_int = } } } */ > /* { dg-final { scan-tree-dump-not {&\s* 4294967294} dce7 { target vect_= int } } } */ > > diff --git a/gcc/testsuite/gcc.dg/pr68217.c b/gcc/testsuite/gcc.dg/pr6821= 7.c > index 60c80106760..279fedd86f4 100644 > --- a/gcc/testsuite/gcc.dg/pr68217.c > +++ b/gcc/testsuite/gcc.dg/pr68217.c > @@ -1,13 +1,19 @@ > /* { dg-do compile } */ > /* { dg-options "-O2 -fdisable-tree-evrp -fdump-tree-vrp1 -fno-tree-ccp"= } */ > > -int foo (void) > +#include <limits.h> > + > +/* Return x so that its range is still exported after the comparison fol= ds > + away. (x & LLONG_MIN) < 1 is true for both values x can take, so the > + test of it no longer keeps x live by itself. */ > + > +long long foo (void) > { > volatile int a =3D -1; > - long long b =3D (1LL << (sizeof (b) * 8 - 1)); // LLONG_MIN > + long long b =3D LLONG_MIN; > long long x =3D (a & b); // x =3D=3D 0x8000000000000000 > if (x < 1LL) { ; } else { __builtin_abort(); } > - return 0; > + return x; > } > > /* { dg-final { scan-tree-dump "\\\[-INF, -INF\\\]\\\[0, 0\\\]" "vrp1" }= } */ > diff --git a/gcc/testsuite/gcc.dg/tree-ssa/maskcmp-1.c b/gcc/testsuite/gc= c.dg/tree-ssa/maskcmp-1.c > new file mode 100644 > index 00000000000..97025c45b1d > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/tree-ssa/maskcmp-1.c > @@ -0,0 +1,23 @@ > +/* { dg-do compile } */ > +/* { dg-options "-O2 -fdump-tree-optimized" } */ > + > +/* Only the low bits of the addition survive the mask, so the constant > + moves to the other side of the comparison. */ > +int f1 (unsigned int x) { return ((x + 3u) & 7u) =3D=3D 5u; } > +int f2 (unsigned int x) { return ((x + 300u) & 255u) !=3D 7u; } > + > +/* The comparison constant does not fit the mask, the result is fixed. = */ > +int f3 (unsigned int x) { return ((x + 3u) & 7u) =3D=3D 9u; } > + > +/* Clearing the low bits rounds down, so the comparison constant can abs= orb > + them and the mask goes away. */ > +int f4 (int x) { return (x & -8) > 16; } > +int f5 (int x) { return (x & -8) <=3D -16; } > +int f6 (unsigned int x) { return (x & 0xfffffff0u) > 100u; } > + > +/* { dg-final { scan-tree-dump-not " \\+ " "optimized" } } */ > +/* { dg-final { scan-tree-dump-times " & 7;" 1 "optimized" } } */ > +/* { dg-final { scan-tree-dump-times " & 255" 1 "optimized" } } */ > +/* { dg-final { scan-tree-dump-times " > 23" 1 "optimized" } } */ > +/* { dg-final { scan-tree-dump-times " < -8" 1 "optimized" } } */ > +/* { dg-final { scan-tree-dump-times " > 111" 1 "optimized" } } */ > diff --git a/gcc/testsuite/gcc.dg/tree-ssa/maskcmp-overflow-1.c b/gcc/tes= tsuite/gcc.dg/tree-ssa/maskcmp-overflow-1.c > new file mode 100644 > index 00000000000..02e6d04bfff > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/tree-ssa/maskcmp-overflow-1.c > @@ -0,0 +1,10 @@ > +/* { dg-do compile } */ > +/* { dg-options "-O2 -ftrapv -fdump-tree-optimized" } */ > + > +int > +f (int x) > +{ > + return ((x + 1) & 7) =3D=3D 0; > +} > + > +/* { dg-final { scan-tree-dump "\\+ 1" "optimized" } } */ > diff --git a/gcc/testsuite/gcc.dg/tree-ssa/maskcmp-overflow-2.c b/gcc/tes= tsuite/gcc.dg/tree-ssa/maskcmp-overflow-2.c > new file mode 100644 > index 00000000000..bacec863f28 > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/tree-ssa/maskcmp-overflow-2.c > @@ -0,0 +1,10 @@ > +/* { dg-do compile } */ > +/* { dg-options "-O2 -fsanitize=3Dsigned-integer-overflow -fdump-tree-op= timized" } */ > + > +int > +f (int x) > +{ > + return ((x + 1) & 7) =3D=3D 0; > +} > + > +/* { dg-final { scan-tree-dump "UBSAN_CHECK_ADD" "optimized" } } */ > -- > 2.50.1 (Apple Git-155) >