[gcc r17-3002] match.pd: add the inclusive or dual of the masked comparison rule

Kyrylo Tkachov via Gcc-cvs <[email protected]> Thu, 6 Aug 2026 04:54:36 +0000 (GMT)
Newsgroups gmane.comp.gcc.cvs
Message-ID <[email protected]>
https://gcc.gnu.org/g:fd1bc5303748a7b3b0a157a8688f3cb2af16abc6

commit r17-3002-gfd1bc5303748a7b3b0a157a8688f3cb2af16abc6
Author: Kyrylo Tkachov <[email protected]>
Date:   Thu Jul 30 18:30:00 2026 +0200

    match.pd: add the inclusive or dual of the masked comparison rule
    
    (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.
    
    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 <[email protected]>

Diff:
---
 gcc/match.pd                                  | 11 ++++++++++-
 gcc/testsuite/gcc.dg/tree-ssa/ior-cmp-xor-1.c | 14 ++++++++++++++
 2 files changed, 24 insertions(+), 1 deletion(-)

diff --git a/gcc/match.pd b/gcc/match.pd
index 9bd6d2f7e077..e99a35046bc8 100644
--- a/gcc/match.pd
+++ b/gcc/match.pd
@@ -8296,7 +8296,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 000000000000..0abd54049c33
--- /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" } } */