[PATCH][v2] match.pd: combine a pair of vector comparisons against zero

<[email protected]> Wed, 5 Aug 2026 13:45:18 +0200
Newsgroups gmane.comp.gcc.patches
Message-ID <[email protected]>
From: Kyrylo Tkachov <[email protected]>

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 inequality.  The existing scalar rule already
implements this identity.  Extend it to vector integers and use a view
conversion when the operands differ only in element signedness.  Extend the
related all-ones rule in the same way.

  typedef int v4si __attribute__((vector_size (16)));
  v4si f (v4si a, v4si b) { return (a == 0) & (b == 0); }

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

Add vector_nop_conversion_p for the element-wise property shared by these
rules and the existing nop_convert matcher.

Bootstrapped and tested on aarch64-none-linux-gnu.
Ok for trunk?
Thanks,
Kyrill

gcc/ChangeLog:

	* match.pd (nop_convert): Use vector_nop_conversion_p.
	((A == 0) & (B == 0), (A != 0) | (B != 0)): Extend the
	existing simplifications to vector operands.
	((A == -1) & (B == -1), (A != -1) | (B != -1)): Likewise.
	* tree.cc (vector_nop_conversion_p): New function.
	* tree.h (vector_nop_conversion_p): Declare.

gcc/testsuite/ChangeLog:

	* gcc.dg/tree-ssa/vec-mask-zero-1.c: New test.

Signed-off-by: Kyrylo Tkachov <[email protected]>
---
 gcc/match.pd                                  | 26 +++++++++++++------
 .../gcc.dg/tree-ssa/vec-mask-zero-1.c         | 17 ++++++++++++
 gcc/tree.cc                                   | 15 +++++++++++
 gcc/tree.h                                    |  1 +
 4 files changed, 51 insertions(+), 8 deletions(-)
 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 da002001399..efa821bff59 100644
--- a/gcc/match.pd
+++ b/gcc/match.pd
@@ -161,10 +161,7 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
  (if (tree_nop_conversion_p (type, TREE_TYPE (@0)))))
 (match (nop_convert @0)
  (view_convert @0)
- (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))))))
+ (if (vector_nop_conversion_p (type, TREE_TYPE (@0)))))
 
 /* These are used by gimple_bitwise_inverted_equal_p to simplify
    detection of BIT_NOT and comparisons. */
@@ -1560,20 +1557,33 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
     (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))
+  (bitop (cmp@3 @0 integer_zerop@2) (cmp@4 @1 integer_zerop))
+  (switch
    (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)))
+    (cmp (bit_ior @0 (convert @1)) @2))
+   (if (VECTOR_INTEGER_TYPE_P (TREE_TYPE (@0))
+	&& VECTOR_INTEGER_TYPE_P (TREE_TYPE (@1))
+	&& vector_nop_conversion_p (TREE_TYPE (@0), TREE_TYPE (@1))
+	&& single_use (@3) && single_use (@4))
+    (cmp (bit_ior @0 (view_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))
+  (bitop (cmp@3 @0 integer_all_onesp@2)
+	 (cmp@4 @1 integer_all_onesp))
+  (switch
    (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))))
+    (cmp (bit_and @0 (convert @1)) @2))
+   (if (VECTOR_INTEGER_TYPE_P (TREE_TYPE (@0))
+	&& VECTOR_INTEGER_TYPE_P (TREE_TYPE (@1))
+	&& vector_nop_conversion_p (TREE_TYPE (@0), TREE_TYPE (@1))
+	&& single_use (@3) && single_use (@4))
+    (cmp (bit_and @0 (view_convert @1)) @2)))))
 
 /* Fold (A & ~B) - (A & B) into (A ^ B) - B.  */
 (simplify
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..1b31f727488
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/vec-mask-zero-1.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+/* Combine pairs of vector comparisons against zero or all-ones.  Use
+   different signedness for the operands to exercise the view conversion.  */
+typedef int v4si __attribute__((vector_size (16)));
+typedef unsigned int v4ui __attribute__((vector_size (16)));
+
+v4si f1 (v4si a, v4ui b) { return (a == 0) & (b == 0); }
+v4si f2 (v4si a, v4ui b) { return (a != 0) | (b != 0); }
+v4si f3 (v4si a, v4ui b) { return (a == -1) & (b == -1u); }
+v4si f4 (v4si a, v4ui b) { return (a != -1) | (b != -1u); }
+
+/* Each function keeps one bitwise operation and one comparison.  */
+/* { dg-final { scan-tree-dump-times " == " 2 "optimized" } } */
+/* { dg-final { scan-tree-dump-times " != " 2 "optimized" } } */
+/* { dg-final { scan-tree-dump-times " \\| " 2 "optimized" } } */
+/* { dg-final { scan-tree-dump-times " & " 2 "optimized" } } */
diff --git a/gcc/tree.cc b/gcc/tree.cc
index c8aa42b3e10..157f453e49c 100644
--- a/gcc/tree.cc
+++ b/gcc/tree.cc
@@ -12384,6 +12384,21 @@ tree_nop_conversion_p (const_tree outer_type, const_tree inner_type)
   return TYPE_MODE (outer_type) == TYPE_MODE (inner_type);
 }
 
+/* Return true iff a view conversion from vector type INNER_TYPE to vector
+   type OUTER_TYPE has the same number of elements and does not change the
+   representation of an element.  */
+
+bool
+vector_nop_conversion_p (const_tree outer_type, const_tree inner_type)
+{
+  return (VECTOR_TYPE_P (outer_type)
+	  && VECTOR_TYPE_P (inner_type)
+	  && known_eq (TYPE_VECTOR_SUBPARTS (outer_type),
+		       TYPE_VECTOR_SUBPARTS (inner_type))
+	  && tree_nop_conversion_p (TREE_TYPE (outer_type),
+				    TREE_TYPE (inner_type)));
+}
+
 /* Return true iff conversion in EXP generates no instruction.  Mark
    it inline so that we fully inline into the stripping functions even
    though we have two uses of this function.  */
diff --git a/gcc/tree.h b/gcc/tree.h
index 1ccbf848d9b..56863bdfc18 100644
--- a/gcc/tree.h
+++ b/gcc/tree.h
@@ -5784,6 +5784,7 @@ extern bool auto_var_p (const_tree);
 extern bool auto_var_in_fn_p (const_tree, const_tree);
 extern tree build_low_bits_mask (tree, unsigned);
 extern bool tree_nop_conversion_p (const_tree, const_tree);
+extern bool vector_nop_conversion_p (const_tree, const_tree);
 extern tree tree_strip_nop_conversions (tree);
 extern tree tree_strip_sign_nop_conversions (tree);
 extern const_tree strip_invariant_refs (const_tree);
-- 
2.50.1 (Apple Git-155)