match.pd: combine a pair of vector comparisons against zero
Checks
| Context |
Check |
Description |
| linaro-tcwg-bot/tcwg_simplebootstrap_build--master-arm-bootstrap |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_simplebootstrap_build--master-aarch64-bootstrap |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_gcc_build--master-arm |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 |
success
|
Build passed
|
Commit Message
From: Kyrylo Tkachov <ktkachov@nvidia.com>
A lane of A | B is zero exactly when the corresponding lanes of A and of B
are both zero, so
(A == 0) & (B == 0) -> (A | B) == 0
and the De Morgan dual for the inequality. One vector comparison goes
away. Reassociation performs this for scalars, but it never runs on
vector masks, so the vector form is left alone today.
typedef int v4si __attribute__((vector_size (16)));
v4si f (v4si a, v4si b) { v4si z = {0,0,0,0}; return (a == z) & (b == z); }
aarch64 -O3 before:
cmeq v0.4s, v0.4s, #0
cmeq v1.4s, v1.4s, #0
and v0.16b, v0.16b, v1.16b
after:
orr v0.16b, v0.16b, v1.16b
cmeq v0.4s, v0.4s, #0
Bootstrapped and tested on aarch64-none-linux-gnu.
Ok for trunk?
Thanks,
Kyrill
gcc/ChangeLog:
* match.pd ((A == 0) & (B == 0), (A != 0) | (B != 0)): New
simplifications for vector operands.
gcc/testsuite/ChangeLog:
* gcc.dg/tree-ssa/vec-mask-zero-1.c: New test.
Signed-off-by: Kyrylo Tkachov <ktkachov@nvidia.com>
---
gcc/match.pd | 14 ++++++++++++++
gcc/testsuite/gcc.dg/tree-ssa/vec-mask-zero-1.c | 12 ++++++++++++
2 files changed, 26 insertions(+)
create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vec-mask-zero-1.c
Comments
On Tue, Aug 4, 2026 at 2:53 AM <ktkachov@nvidia.com> wrote:
>
> From: Kyrylo Tkachov <ktkachov@nvidia.com>
>
> A lane of A | B is zero exactly when the corresponding lanes of A and of B
> are both zero, so
>
> (A == 0) & (B == 0) -> (A | B) == 0
>
> and the De Morgan dual for the inequality. One vector comparison goes
> away. Reassociation performs this for scalars, but it never runs on
> vector masks, so the vector form is left alone today.
>
> typedef int v4si __attribute__((vector_size (16)));
> v4si f (v4si a, v4si b) { v4si z = {0,0,0,0}; return (a == z) & (b == z); }
>
> aarch64 -O3 before:
>
> cmeq v0.4s, v0.4s, #0
> cmeq v1.4s, v1.4s, #0
> and v0.16b, v0.16b, v1.16b
>
> after:
>
> orr v0.16b, v0.16b, v1.16b
> cmeq v0.4s, v0.4s, #0
>
> Bootstrapped and tested on aarch64-none-linux-gnu.
> Ok for trunk?
> Thanks,
> Kyrill
>
> gcc/ChangeLog:
>
> * match.pd ((A == 0) & (B == 0), (A != 0) | (B != 0)): New
> simplifications for vector operands.
>
> gcc/testsuite/ChangeLog:
>
> * gcc.dg/tree-ssa/vec-mask-zero-1.c: New test.
>
> Signed-off-by: Kyrylo Tkachov <ktkachov@nvidia.com>
> ---
> gcc/match.pd | 14 ++++++++++++++
> gcc/testsuite/gcc.dg/tree-ssa/vec-mask-zero-1.c | 12 ++++++++++++
> 2 files changed, 26 insertions(+)
> create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vec-mask-zero-1.c
>
> diff --git a/gcc/match.pd b/gcc/match.pd
> index 22202af2cc1..fc81dfc5e66 100644
> --- a/gcc/match.pd
> +++ b/gcc/match.pd
> @@ -3823,6 +3823,20 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
> { constant_boolean_node (true, type); })
> ))))))
>
> +/* Combine two vector comparisons against zero into one:
> + (A == 0) & (B == 0) --> (A | B) == 0
> + (A != 0) | (B != 0) --> (A | B) != 0
> + Reassociation does this for scalars only, it never runs on vector
> + masks. */
> +(for eqne (eq ne)
> + bitop (bit_and bit_ior)
> + (simplify
> + (bitop (eqne:s @0 integer_zerop) (eqne:s @1 integer_zerop))
> + (if (VECTOR_INTEGER_TYPE_P (TREE_TYPE (@0))
> + && types_match (TREE_TYPE (@0), TREE_TYPE (@1)))
> + (eqne (bit_ior @0 @1)
> + { build_zero_cst (TREE_TYPE (@0)); }))))
So I think you should be able to combine it with these patterns instead:
```
(for bitop (bit_and bit_ior)
cmp (eq ne)
/* PR35691: Transform
(x == 0 & y == 0) -> (x | typeof(x)(y)) == 0.
(x != 0 | y != 0) -> (x | typeof(x)(y)) != 0. */
(simplify
(bitop (cmp @0 integer_zerop@2) (cmp @1 integer_zerop))
(if (INTEGRAL_TYPE_P (TREE_TYPE (@0))
&& INTEGRAL_TYPE_P (TREE_TYPE (@1))
&& TYPE_PRECISION (TREE_TYPE (@0)) == TYPE_PRECISION (TREE_TYPE (@1)))
(cmp (bit_ior @0 (convert @1)) @2)))
/* Transform:
(x == -1 & y == -1) -> (x & typeof(x)(y)) == -1.
(x != -1 | y != -1) -> (x & typeof(x)(y)) != -1. */
(simplify
(bitop (cmp @0 integer_all_onesp@2) (cmp @1 integer_all_onesp))
(if (INTEGRAL_TYPE_P (TREE_TYPE (@0))
&& INTEGRAL_TYPE_P (TREE_TYPE (@1))
&& TYPE_PRECISION (TREE_TYPE (@0)) == TYPE_PRECISION (TREE_TYPE (@1)))
(cmp (bit_and @0 (convert @1)) @2))))
```
Instead of convert, use view_convert and instead of TYPE_PRECISION
check you need something more like:
```
(if (VECTOR_TYPE_P (type) && VECTOR_TYPE_P (TREE_TYPE (@0))
&& known_eq (TYPE_VECTOR_SUBPARTS (type),
TYPE_VECTOR_SUBPARTS (TREE_TYPE (@0)))
&& tree_nop_conversion_p (TREE_TYPE (type), TREE_TYPE (TREE_TYPE (@0))))))
```
Which might make sense to make in a helper function and use that also
in nop_convert match pattern; maybe vector_nop_conversion_p.
And then also add a testcase for -1.
> +
> /* Optimize (a CMP b) ^ (a CMP b) */
> /* Optimize (a CMP b) != (a CMP b) */
> (for op (bit_xor ne)
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vec-mask-zero-1.c b/gcc/testsuite/gcc.dg/tree-ssa/vec-mask-zero-1.c
> new file mode 100644
> index 00000000000..f8c669636fd
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/vec-mask-zero-1.c
> @@ -0,0 +1,12 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fdump-tree-optimized" } */
> +/* A lane of A | B is zero exactly when both lanes are, so a pair of vector
> + comparisons against zero becomes one. Reassociation does this for
> + scalars but never runs on vector masks. */
> +typedef int v4si __attribute__((vector_size (16)));
> +v4si f (v4si a, v4si b) { v4si z = {0,0,0,0}; return (a == z) & (b == z); }
> +v4si g (v4si a, v4si b) { v4si z = {0,0,0,0}; return (a != z) | (b != z); }
> +/* { dg-final { scan-tree-dump-times " == " 1 "optimized" } } */
> +/* { dg-final { scan-tree-dump-times " != " 1 "optimized" } } */
> +/* Each function keeps one OR and one comparison. */
> +/* { dg-final { scan-tree-dump-times " \\| " 2 "optimized" } } */
> --
> 2.50.1 (Apple Git-155)
>
@@ -3823,6 +3823,20 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
{ constant_boolean_node (true, type); })
))))))
+/* Combine two vector comparisons against zero into one:
+ (A == 0) & (B == 0) --> (A | B) == 0
+ (A != 0) | (B != 0) --> (A | B) != 0
+ Reassociation does this for scalars only, it never runs on vector
+ masks. */
+(for eqne (eq ne)
+ bitop (bit_and bit_ior)
+ (simplify
+ (bitop (eqne:s @0 integer_zerop) (eqne:s @1 integer_zerop))
+ (if (VECTOR_INTEGER_TYPE_P (TREE_TYPE (@0))
+ && types_match (TREE_TYPE (@0), TREE_TYPE (@1)))
+ (eqne (bit_ior @0 @1)
+ { build_zero_cst (TREE_TYPE (@0)); }))))
+
/* Optimize (a CMP b) ^ (a CMP b) */
/* Optimize (a CMP b) != (a CMP b) */
(for op (bit_xor ne)
new file mode 100644
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+/* A lane of A | B is zero exactly when both lanes are, so a pair of vector
+ comparisons against zero becomes one. Reassociation does this for
+ scalars but never runs on vector masks. */
+typedef int v4si __attribute__((vector_size (16)));
+v4si f (v4si a, v4si b) { v4si z = {0,0,0,0}; return (a == z) & (b == z); }
+v4si g (v4si a, v4si b) { v4si z = {0,0,0,0}; return (a != z) | (b != z); }
+/* { dg-final { scan-tree-dump-times " == " 1 "optimized" } } */
+/* { dg-final { scan-tree-dump-times " != " 1 "optimized" } } */
+/* Each function keeps one OR and one comparison. */
+/* { dg-final { scan-tree-dump-times " \\| " 2 "optimized" } } */