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]>
---
Rebased onto current trunk (ce6a2e93490, which includes patch 1/2) so it
applies with git am there; no changes to the fold or the test. Re-run at
that revision: clean x86_64-pc-linux-gnu bootstrap with the stage2/stage3
comparison successful, dg.exp and tree-ssa.exp with no unexpected
results, and the test's four checks passing.
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 0c399a11f8d..15f20277ddd 100644
--- a/gcc/match.pd
+++ b/gcc/match.pd
@@ -1637,6 +1637,36 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
&& single_use (@3) && single_use (@4))
(cmp (bit_and @0 (view_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