On Tue, Aug 4, 2026 at 11:44 AM <[email protected]> wrote:
>
> From: Kyrylo Tkachov <[email protected]>
>
>   ((X + C1) & LOWMASK) ==/!= C2 -> (X & LOWMASK) ==/!= C2 - C1
>   (X & HIGHMASK) >/<= C -> X >/<= (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 the
> 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) ==/!= C2): New simplification.
>         ((X & HIGHMASK) >/<= 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) == wi::to_wide (@2))
>       (cmp @1 { build_int_cst (TREE_TYPE (@1), cand); }))))))
>
> +/* Fold ((X + C1) & LOWMASK) ==/!= C2 into (X & LOWMASK) ==/!= C2 - C1.
> +   Only the low bits of the addition survive the mask, so the constant can
> +   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) == 1)
> +   (with { wide_int mask = wi::to_wide (@2);
> +          wide_int rhs = (wi::to_wide (@3) - wi::to_wide (@1)) & mask; }
> +    (if ((wi::to_wide (@3) & ~mask) == 0)
> +     (cmp (bit_and @0 @2)
> +         { wide_int_to_tree (TREE_TYPE (@0), rhs); })
> +     { constant_boolean_node (cmp == NE_EXPR, type); }))))
> +
> +/* Fold (X & HIGHMASK) >/<= C into X >/<= (C | ~HIGHMASK).  Clearing the
> +   low bits of X rounds it down, and a rounded value passes the comparison
> +   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 = 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/pr68217.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 folds
> +   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 = -1;
> -    long long b = (1LL << (sizeof (b) * 8 - 1)); // LLONG_MIN
> +    long long b = LLONG_MIN;
>      long long x = (a & b); // x == 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/gcc.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) == 5u; }
> +int f2 (unsigned int x) { return ((x + 300u) & 255u) != 7u; }
> +
> +/* The comparison constant does not fit the mask, the result is fixed.  */
> +int f3 (unsigned int x) { return ((x + 3u) & 7u) == 9u; }
> +
> +/* Clearing the low bits rounds down, so the comparison constant can absorb
> +   them and the mask goes away.  */
> +int f4 (int x) { return (x & -8) > 16; }
> +int f5 (int x) { return (x & -8) <= -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/testsuite/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) == 0;
> +}
> +
> +/* { dg-final { scan-tree-dump "\\+ 1" "optimized" } } */
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/maskcmp-overflow-2.c 
> b/gcc/testsuite/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=signed-integer-overflow 
> -fdump-tree-optimized" } */
> +
> +int
> +f (int x)
> +{
> +  return ((x + 1) & 7) == 0;
> +}
> +
> +/* { dg-final { scan-tree-dump "UBSAN_CHECK_ADD" "optimized" } } */
> --
> 2.50.1 (Apple Git-155)
>

Reply via email to