match.pd: add the inclusive or dual of the masked comparison rule

Message ID 20260804100343.56241-1-ktkachov@nvidia.com
State New
Headers
Series match.pd: add the inclusive or dual of the masked comparison rule |

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

Kyrylo Tkachov Aug. 4, 2026, 10:03 a.m. UTC
  From: Kyrylo Tkachov <ktkachov@nvidia.com>

(X & C) == (Y & C) already folds to ((X ^ Y) & C) == 0.  The dual was
missing: two values ored with the same constant agree on the bits that
constant forces, so only the bits outside it can differ.

  int f (unsigned char a, unsigned char b) { return (a | 32) == (b | 32); }

aarch64 -O2 before:

	orr	w1, w1, 32
	orr	w0, w0, 32
	cmp	w1, w0
	cset	w0, eq

after:

	eor	w0, w0, w1
	tst	w0, 223
	cset	w0, eq

This is the case insensitive ASCII comparison, and any equality of two
values on a masked field.  The rule sits directly beside its bit_and dual,
inside the same eq and ne iterator.

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

gcc/ChangeLog:

	* match.pd ((X | C) ==/!= (Y | C)): New simplification.

gcc/testsuite/ChangeLog:

	* gcc.dg/tree-ssa/ior-cmp-xor-1.c: New test.

Signed-off-by: Kyrylo Tkachov <ktkachov@nvidia.com>
---
 gcc/match.pd                                  | 11 ++++++++++-
 gcc/testsuite/gcc.dg/tree-ssa/ior-cmp-xor-1.c | 14 ++++++++++++++
 2 files changed, 24 insertions(+), 1 deletion(-)
 create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/ior-cmp-xor-1.c
  

Comments

Jeffrey Law Aug. 6, 2026, 4:45 a.m. UTC | #1
On 8/4/2026 4:03 AM, ktkachov@nvidia.com wrote:
> From: Kyrylo Tkachov <ktkachov@nvidia.com>
>
> (X & C) == (Y & C) already folds to ((X ^ Y) & C) == 0.  The dual was
> missing: two values ored with the same constant agree on the bits that
> constant forces, so only the bits outside it can differ.
>
>    int f (unsigned char a, unsigned char b) { return (a | 32) == (b | 32); }
>
> aarch64 -O2 before:
>
> 	orr	w1, w1, 32
> 	orr	w0, w0, 32
> 	cmp	w1, w0
> 	cset	w0, eq
>
> after:
>
> 	eor	w0, w0, w1
> 	tst	w0, 223
> 	cset	w0, eq
>
> This is the case insensitive ASCII comparison, and any equality of two
> values on a masked field.  The rule sits directly beside its bit_and dual,
> inside the same eq and ne iterator.
>
> Bootstrapped and tested on aarch64-none-linux-gnu.
> Ok for trunk?
> Thanks,
> Kyrill
>
> gcc/ChangeLog:
>
> 	* match.pd ((X | C) ==/!= (Y | C)): New simplification.
>
> gcc/testsuite/ChangeLog:
>
> 	* gcc.dg/tree-ssa/ior-cmp-xor-1.c: New test.
>
> Signed-off-by: Kyrylo Tkachov <ktkachov@nvidia.com>
OK.

Jeff
  

Patch

diff --git a/gcc/match.pd b/gcc/match.pd
index 85fea9fa18f..d5b5924d41f 100644
--- a/gcc/match.pd
+++ b/gcc/match.pd
@@ -8555,7 +8555,16 @@  DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
  /* (X & C) op (Y & C) into (X ^ Y) & C op 0.  */
  (simplify
   (cmp (bit_and:cs @0 @2) (bit_and:cs @1 @2))
-  (cmp (bit_and (bit_xor @0 @1) @2) { build_zero_cst (TREE_TYPE (@2)); })))
+  (cmp (bit_and (bit_xor @0 @1) @2)
+       { build_zero_cst (TREE_TYPE (@2)); }))
+
+ /* (X | C) op (Y | C) into (X ^ Y) & ~C op 0, the dual of the rule above.
+    The two values agree on the bits C forces, so only the bits outside C
+    can differ.  */
+ (simplify
+  (cmp (bit_ior:cs @0 @2) (bit_ior:cs @1 @2))
+  (cmp (bit_and (bit_xor @0 @1) (bit_not! @2))
+       { build_zero_cst (TREE_TYPE (@2)); })))
 
 /* (X < 0) != (Y < 0) into (X ^ Y) < 0.
    (X >= 0) != (Y >= 0) into (X ^ Y) < 0.
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/ior-cmp-xor-1.c b/gcc/testsuite/gcc.dg/tree-ssa/ior-cmp-xor-1.c
new file mode 100644
index 00000000000..0abd54049c3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/ior-cmp-xor-1.c
@@ -0,0 +1,14 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+
+/* Two values ored with the same constant agree on the bits that constant
+   forces, so only the bits outside it can differ.  The dual of the existing
+   (X & C) == (Y & C) rule.  */
+
+int f1 (unsigned char a, unsigned char b) { return (a | 32) == (b | 32); }
+int f2 (unsigned a, unsigned b) { return (a | 32) != (b | 32); }
+int f3 (unsigned a, unsigned b) { return (a | 0xff000000u) == (b | 0xff000000u); }
+
+/* { dg-final { scan-tree-dump-not " \\| 32" "optimized" } } */
+/* { dg-final { scan-tree-dump-not " \\| 4278190080" "optimized" } } */
+/* { dg-final { scan-tree-dump-times " \\^ " 3 "optimized" } } */