[PATCH 2/2] match.pd: combine two single-bit tests into one masked compare

Dominic P <[email protected]>
Newsgroups gmane.comp.gcc.patches
Message-ID <[email protected]>
The PR35691 rules combine x == 0 & y == 0 into (x | y) == 0, which
covers two of the eight ways a pair of single-bit tests of the same
value can be combined; the remaining combinations were left as two
tests and a boolean operation.  For single bits C1 and C2 of X, with
M = C1 | C2:

  (X & C1) != 0 & (X & C2) != 0  ->  (X & M) == M
  (X & C1) == 0 | (X & C2) == 0  ->  (X & M) != M
  (X & C1) != 0 & (X & C2) == 0  ->  (X & M) == C1
  (X & C1) != 0 | (X & C2) == 0  ->  (X & M) != C2

These hold only for power-of-two masks, where bit-set and bit-clear are
both single-bit tests; multi-bit masks are not combinable this way and
are rejected by the integer_pow2p guards.  The comparisons and inner
masks are required to be single-use so the rewrite never adds a
statement when the individual tests have other consumers.  On ARM each
combination reduces from 4-8 instructions to the uniform
and-compare-branch sequence.

Assisted-by: Claude Opus 4.8 (Anthropic)

gcc/ChangeLog:

	* match.pd ((X & C1) cmp 0 op (X & C2) cmp 0): Combine pairs of
	single-bit tests of the same operand into a single compare of
	the union mask.

gcc/testsuite/ChangeLog:

	* gcc.dg/fold-bit-test-combine-1.c: New test.

Signed-off-by: Dominic P <[email protected]>
---
 gcc/match.pd                                  | 30 +++++++++++++++++
 .../gcc.dg/fold-bit-test-combine-1.c          | 33 +++++++++++++++++++
 2 files changed, 63 insertions(+)
 create mode 100644 gcc/testsuite/gcc.dg/fold-bit-test-combine-1.c

diff --git a/gcc/match.pd b/gcc/match.pd
index 90fb2acede4..c160e9e4725 100644
--- a/gcc/match.pd
+++ b/gcc/match.pd
@@ -1603,6 +1603,36 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
 	&& TYPE_PRECISION (TREE_TYPE (@0)) == TYPE_PRECISION (TREE_TYPE (@1)))
     (cmp (bit_and @0 (convert @1)) @2))))
 
+/* Combine two single-bit tests of the same value X into one masked
+   compare.  For distinct single bits C1, C2 and M = C1 | C2:
+     (X & C1) != 0 & (X & C2) != 0  ->  (X & M) == M
+     (X & C1) == 0 | (X & C2) == 0  ->  (X & M) != M
+     (X & C1) != 0 & (X & C2) == 0  ->  (X & M) == C1
+     (X & C1) != 0 | (X & C2) == 0  ->  (X & M) != C2
+   The == 0 & == 0 and != 0 | != 0 cases are handled by the more general
+   rule above.  Restricted to single-bit masks: for a multi-bit mask
+   (X & C) != 0 is not a single-bit test.  */
+(for bitop (bit_and bit_ior)
+     cmp (ne eq)
+     rcmp (eq ne)
+ (simplify
+  (bitop (cmp:s (bit_and:s @0 INTEGER_CST@1) integer_zerop)
+	 (cmp:s (bit_and:s @0 INTEGER_CST@2) integer_zerop))
+  (if (integer_pow2p (@1) && integer_pow2p (@2)
+       && wi::to_wide (@1) != wi::to_wide (@2))
+   (with { tree m = wide_int_to_tree (TREE_TYPE (@0),
+				      wi::to_wide (@1) | wi::to_wide (@2)); }
+    (rcmp (bit_and @0 { m; }) { m; }))))
+ (simplify
+  (bitop:c (ne:s (bit_and:s @0 INTEGER_CST@1) integer_zerop)
+	   (eq:s (bit_and:s @0 INTEGER_CST@2) integer_zerop))
+  (if (integer_pow2p (@1) && integer_pow2p (@2)
+       && wi::to_wide (@1) != wi::to_wide (@2))
+   (with { tree m = wide_int_to_tree (TREE_TYPE (@0),
+				      wi::to_wide (@1) | wi::to_wide (@2)); }
+    (rcmp (bit_and @0 { m; })
+	  { bitop == BIT_AND_EXPR ? @1 : @2; })))))
+
 /* Fold (A & ~B) - (A & B) into (A ^ B) - B.  */
 (simplify
  (minus (bit_and:cs @0 (bit_not @1)) (bit_and:cs @0 @1))
diff --git a/gcc/testsuite/gcc.dg/fold-bit-test-combine-1.c b/gcc/testsuite/gcc.dg/fold-bit-test-combine-1.c
new file mode 100644
index 00000000000..69de70649dc
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/fold-bit-test-combine-1.c
@@ -0,0 +1,33 @@
+/* Two single-bit tests of the same value combine into one masked compare.  */
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+
+int
+t_and_set_set (unsigned x)		/* (x&8)!=0 & (x&16)!=0 -> (x&24)==24 */
+{
+  return ((x & 8) != 0) & ((x & 16) != 0);
+}
+
+int
+t_and_set_clr (unsigned x)		/* (x&8)!=0 & (x&16)==0 -> (x&24)==8 */
+{
+  return ((x & 8) != 0) & ((x & 16) == 0);
+}
+
+int
+t_or_clr_clr (unsigned x)		/* (x&8)==0 | (x&16)==0 -> (x&24)!=24 */
+{
+  return ((x & 8) == 0) | ((x & 16) == 0);
+}
+
+int
+t_or_set_clr (unsigned x)		/* (x&8)!=0 | (x&16)==0 -> (x&24)!=16 */
+{
+  return ((x & 8) != 0) | ((x & 16) == 0);
+}
+
+/* Each becomes a single (x & 24) compare; the separate & 8 / & 16 masks and
+   the boolean combiner are gone.  */
+/* { dg-final { scan-tree-dump-times " & 24;" 4 "optimized" } } */
+/* { dg-final { scan-tree-dump-not " & 8;" "optimized" } } */
+/* { dg-final { scan-tree-dump-not " & 16;" "optimized" } } */
-- 
2.55.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.