On Mon, 25 Aug 2025, dhr...@nvidia.com wrote: > From: Dhruv Chawla <dhr...@nvidia.com> > > For ==, < and <=, the fold is to 0. For !=, > and >=, the fold is to 1. > This only applies when C != 0.
So -50 << 1 < 1 is true, so does this only work for unsigned types, or tree_expr_nonnegative_p in addition to tree_expr_nonzero_p? I've probably mislead you when asking to simply extend it to other compare operators. For == it should of course work when nonzero only. Or am I missing something? Why did you restrict this to #if GIMPLE in v2? Thanks, Richard. > Bootstrapped and regtested on aarch64-linux-gnu. > > Signed-off-by: Dhruv Chawla <dhr...@nvidia.com> > > gcc/ChangeLog: > > * match.pd: New patterns. > > gcc/testsuite/ChangeLog: > > * gcc.dg/match-constant-shift-1.c: New test. > * gcc.dg/match-constant-shift-1.c: Likewise. > --- > gcc/match.pd | 18 ++++++ > gcc/testsuite/gcc.dg/match-constant-shift-1.c | 46 +++++++++++++++ > gcc/testsuite/gcc.dg/match-constant-shift-2.c | 59 +++++++++++++++++++ > 3 files changed, 123 insertions(+) > create mode 100644 gcc/testsuite/gcc.dg/match-constant-shift-1.c > create mode 100644 gcc/testsuite/gcc.dg/match-constant-shift-2.c > > diff --git a/gcc/match.pd b/gcc/match.pd > index b1d7a3a1b73..4e8fbaa4dd9 100644 > --- a/gcc/match.pd > +++ b/gcc/match.pd > @@ -1316,6 +1316,24 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT) > (if (INTEGRAL_TYPE_P (type)) > (rshift (op @0 @2) @1)))) > > +#if GIMPLE > +/* (y << x) {==,<,<=} x -> 0 when y != 0. */ > +(for cmp (eq lt le) > + (simplify > + (cmp:c (nop_convert1? (lshift @0 @1)) (nop_convert2? @1)) > + (if (INTEGRAL_TYPE_P (TREE_TYPE (@1)) > + && tree_expr_nonzero_p (@0)) > + { build_zero_cst (type); }))) > + > +/* (y << x) {!=,>,>=} x -> 1 when y != 0. */ > +(for cmp (ne gt ge) > + (simplify > + (cmp:c (nop_convert1? (lshift @0 @1)) (nop_convert2? @1)) > + (if (INTEGRAL_TYPE_P (TREE_TYPE (@1)) > + && tree_expr_nonzero_p (@0)) > + { build_one_cst (type); }))) > +#endif > + > /* Fold (1 << (C - x)) where C = precision(type) - 1 > into ((1 << C) >> x). */ > (simplify > diff --git a/gcc/testsuite/gcc.dg/match-constant-shift-1.c > b/gcc/testsuite/gcc.dg/match-constant-shift-1.c > new file mode 100644 > index 00000000000..8e7a620e951 > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/match-constant-shift-1.c > @@ -0,0 +1,46 @@ > +/* { dg-do compile } */ > +/* { dg-options "-O3 -fdump-tree-optimized" } */ > + > +#define TEST_ONE_CST(n, op, type, cst) > \ > + bool lshift_cst_##type##n (type x) { return (cst << x) op x; } > + > +#define TEST_OP_CST(n, op) > \ > + TEST_ONE_CST (n, op, unsigned, n) > \ > + TEST_ONE_CST (n, op, int, n) > \ > + TEST_ONE_CST (n, op, bool, n) > \ > + TEST_ONE_CST (n, op, test_enum, n) > + > +#define TEST_ONE(n, op, type) > \ > + bool lshift_##type##n (type x, type y) > \ > + { > \ > + if (y <= 0) > \ > + __builtin_unreachable (); > \ > + return (y << x) op x; > \ > + } > + > +#define TEST_OP(n, op) > \ > + TEST_ONE (n, op, unsigned) > \ > + TEST_ONE (n, op, int) > \ > + TEST_ONE (n, op, bool) > \ > + TEST_ONE (n, op, test_enum) > + > +typedef enum > +{ > + ZERO > +} test_enum; > + > +TEST_OP_CST (1, ==) > +TEST_OP_CST (2, !=) > +TEST_OP_CST (3, <) > +TEST_OP_CST (4, >) > +TEST_OP_CST (5, <=) > +TEST_OP_CST (6, >=) > + > +TEST_OP (1, ==) > +TEST_OP (2, !=) > +TEST_OP (3, <) > +TEST_OP (4, >) > +TEST_OP (5, <=) > +TEST_OP (6, >=) > + > +/* { dg-final { scan-tree-dump-not "<<" optimized } } */ > diff --git a/gcc/testsuite/gcc.dg/match-constant-shift-2.c > b/gcc/testsuite/gcc.dg/match-constant-shift-2.c > new file mode 100644 > index 00000000000..d6a4a3012f2 > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/match-constant-shift-2.c > @@ -0,0 +1,59 @@ > +/* { dg-do compile } */ > +/* { dg-options "-O2 -fdump-tree-optimized" } */ > + > +/* The fold (C << x) <op> x -> 0|1 shouldn't trigger when C is 0. */ > + > +#define TEST_ONE_CST(n, op, type, cst) > \ > + type lshift_cst_##type##n (type x) { return (type) ((cst << x) op x); } > + > +#define TEST_OP_CST(n, op, cst) > \ > + TEST_ONE_CST (n, op, unsigned, cst) > \ > + TEST_ONE_CST (n, op, int, cst) > \ > + TEST_ONE_CST (n, op, bool, cst) > \ > + TEST_ONE_CST (n, op, test_enum, cst) > + > +#define TEST_ONE(n, op, type) > \ > + type lshift_##type##n (type x, type y) \ > + { > \ > + if (y != 0) > \ > + __builtin_unreachable (); > \ > + return (type) ((y << x) op x); > \ > + } > + > +#define TEST_OP(n, op) > \ > + TEST_ONE (n, op, unsigned) > \ > + TEST_ONE (n, op, int) > \ > + TEST_ONE (n, op, bool) > \ > + TEST_ONE (n, op, test_enum) > + > +typedef enum > +{ > + ZERO > +} test_enum; > + > +TEST_OP_CST (1, ==, 0) > +TEST_OP_CST (2, !=, 0) > +TEST_OP_CST (3, <, 0) > +TEST_OP_CST (4, >, 0) > +TEST_OP_CST (5, <=, 0) > +TEST_OP_CST (6, >=, 0) > + > +TEST_OP (1, ==) > +TEST_OP (2, !=) > +TEST_OP (3, <) > +TEST_OP (4, >) > +TEST_OP (5, <=) > +TEST_OP (6, >=) > + > +/* These end up getting folded by other patterns. */ > +/* { dg-final { scan-tree-dump-times "x_\\d\\(D\\) == 0" 10 optimized} } */ > +/* { dg-final { scan-tree-dump-times "x_\\d\\(D\\) != 0" 10 optimized } } */ > +/* { dg-final { scan-tree-dump-times "x_\\d\\(D\\) > 0" 2 optimized } } */ > +/* { dg-final { scan-tree-dump-times "x_\\d\\(D\\) < 0" 2 optimized} } */ > +/* { dg-final { scan-tree-dump-times "x_\\d\\(D\\) >= 0" 2 optimized } } */ > +/* { dg-final { scan-tree-dump-times "x_\\d\\(D\\) <= 0" 2 optimized} } */ > +/* { dg-final { scan-tree-dump-times "~x_\\d\\(D\\)" 4 optimized } } */ > +/* { dg-final { scan-tree-dump-times "return x_\\d\\(D\\);" 4 optimized } } > */ > +/* { dg-final { scan-tree-dump-times "return 0;" 6 optimized } } */ > +/* { dg-final { scan-tree-dump-times "return 1;" 6 optimized } } */ > +/* Total: 48. */ > -- Richard Biener <rguent...@suse.de> SUSE Software Solutions Germany GmbH, Frankenstrasse 146, 90461 Nuernberg, Germany; GF: Ivo Totev, Andrew McDonald, Werner Knoblich; (HRB 36809, AG Nuernberg)