[PATCH] match.pd: merge two masked equality tests of one value

<[email protected]> Tue, 4 Aug 2026 12:37:43 +0200
Newsgroups gmane.comp.gcc.patches
Message-ID <[email protected]>
From: Kyrylo Tkachov <[email protected]>

Two masked tests of the same value examine one set of bits, so one mask and
one comparison do the whole job:

  ((X & C1) == C2) & ((X & C3) == C4)
    -> (X & (C1 | C3)) == (C2 | C4)   when C2 and C4 agree on the bits both
				      masks select
    -> false			      when they disagree

and the inclusive or of the two inequalities is the De Morgan dual.

  int f (unsigned x) { return ((x & 5) == 5) & ((x & 24) == 24); }

aarch64 -O2 before:

	and	w1, w0, 5
	and	w0, w0, 24
	cmp	w1, 5
	cset	w1, eq
	cmp	w0, 24
	cset	w0, eq
	and	w0, w0, w1

after:

	and	w0, w0, 29
	cmp	w0, 29
	cset	w0, eq

A test whose constant has a bit outside its own mask is decided already and
is left to the rules that decide it.

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

gcc/ChangeLog:

	* match.pd (((X & C1) == C2) &/| ((X & C3) == C4)): New
	simplification.

gcc/testsuite/ChangeLog:

	* gcc.dg/tree-ssa/maskcmp-merge-1.c: New test.

Signed-off-by: Kyrylo Tkachov <[email protected]>
---
 gcc/match.pd                                  | 28 +++++++++++++++++++
 .../gcc.dg/tree-ssa/maskcmp-merge-1.c         | 26 +++++++++++++++++
 2 files changed, 54 insertions(+)
 create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/maskcmp-merge-1.c

diff --git a/gcc/match.pd b/gcc/match.pd
index 41acf869f9c..fa3856fc50e 100644
--- a/gcc/match.pd
+++ b/gcc/match.pd
@@ -3939,6 +3939,34 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
       { constant_boolean_node (true, type); })
      ))))))
 
+/* Merge two masked equality tests of the same value.
+
+     ((X & C1) == C2) & ((X & C3) == C4)
+       -> (X & (C1 | C3)) == (C2 | C4)   when C2 and C4 agree on the bits
+					 both masks select
+       -> false when they disagree
+
+   and the inclusive or of the two inequalities is the De Morgan dual.  Two
+  masked tests of one value examine one set of bits, so one mask and one
+  comparison do the whole job.  */
+(for cmp (eq ne)
+     bitop (bit_and bit_ior)
+ (simplify
+  (bitop (cmp:s (bit_and:s @0 INTEGER_CST@1) INTEGER_CST@2)
+	 (cmp:s (bit_and:s @0 INTEGER_CST@3) INTEGER_CST@4))
+  (if (INTEGRAL_TYPE_P (TREE_TYPE (@0)))
+   (with { wide_int m1 = wi::to_wide (@1), v1 = wi::to_wide (@2);
+	   wide_int m2 = wi::to_wide (@3), v2 = wi::to_wide (@4);
+	   tree t0 = TREE_TYPE (@0); }
+    /* A test whose constant has a bit outside its mask is decided already
+       and is left to the rules that decide it.  */
+    (if (wi::bit_and_not (v1, m1) == 0
+	 && wi::bit_and_not (v2, m2) == 0)
+     (if ((m1 & m2 & (v1 ^ v2)) == 0)
+      (cmp (bit_and @0 { wide_int_to_tree (t0, m1 | m2); })
+	   { wide_int_to_tree (t0, v1 | v2); })
+      { constant_boolean_node (cmp == NE_EXPR, type); }))))))
+
 /* Combine two vector comparisons against zero into one:
      (A == 0) & (B == 0)  -->  (A | B) == 0
      (A != 0) | (B != 0)  -->  (A | B) != 0
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/maskcmp-merge-1.c b/gcc/testsuite/gcc.dg/tree-ssa/maskcmp-merge-1.c
new file mode 100644
index 00000000000..9509afce27e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/maskcmp-merge-1.c
@@ -0,0 +1,26 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+
+typedef unsigned int u;
+
+/* Two masked tests of one value examine one set of bits, so one mask and
+   one comparison do the whole job.  */
+
+int a1 (u x) { return ((x & 5) == 5) & ((x & 24) == 24); }
+int a2 (u x) { return ((x & 5) == 1) & ((x & 24) == 16); }
+int a3 (u x) { return ((x & 5) == 0) & ((x & 24) == 24); }
+int a4 (u x) { return ((x & 12) == 12) & ((x & 20) == 4); }
+
+/* The De Morgan dual.  */
+int b1 (u x) { return ((x & 5) != 5) | ((x & 24) != 24); }
+int b2 (u x) { return ((x & 5) != 1) | ((x & 24) != 16); }
+
+/* Bit 2 is selected by both masks and the two tests disagree on it, so the
+   conjunction is false and the disjunction is true.  */
+int c1 (u x) { return ((x & 6) == 6) & ((x & 12) == 8); }
+int c2 (u x) { return ((x & 6) != 6) | ((x & 12) != 8); }
+
+/* { dg-final { scan-tree-dump-times " & 29" 5 "optimized" } } */
+/* { dg-final { scan-tree-dump-times " & 28" 1 "optimized" } } */
+/* { dg-final { scan-tree-dump-times "return 0;" 1 "optimized" } } */
+/* { dg-final { scan-tree-dump-times "return 1;" 1 "optimized" } } */
-- 
2.50.1 (Apple Git-155)