[gcc r17-3016] match: fold a remainder compared with its dividend

Kyrylo Tkachov via Gcc-cvs <[email protected]> Thu, 6 Aug 2026 09:21:47 +0000 (GMT)
Newsgroups gmane.comp.gcc.cvs
Message-ID <[email protected]>
https://gcc.gnu.org/g:7ebe4eb29d8fc0167f98705b53b4fddb28f0cffb

commit r17-3016-g7ebe4eb29d8fc0167f98705b53b4fddb28f0cffb
Author: Kyrylo Tkachov <[email protected]>
Date:   Wed Jul 29 21:26:46 2026 +0200

    match: fold a remainder compared with its dividend
    
    A remainder of non-negative operands equals its dividend exactly when the
    dividend is smaller than the divisor.  Comparing the two therefore does
    not need the division at all.  This sits next to the (X / Y) == 0 rule,
    which has the same shape and the same non-negativity requirement.
    
      int f (unsigned x, unsigned y) { return x % y == x; }
    
    aarch64 -O2:
    
      before                          after
        udiv  w2, w0, w1                cmp   w0, w1
        msub  w2, w2, w1, w0            cset  w0, cc
        cmp   w2, w0
        cset  w0, eq
    
    A zero divisor can raise a non-call exception.  Keep the division or
    remainder when the divisor might be zero and non-call exceptions are
    enabled.  Also keep an explicit zero divisor for diagnostics.
    
    Bootstrapped and tested on aarch64-none-linux-gnu.
    
    gcc/ChangeLog:
    
            * match.pd ((X / Y) ==/!= 0): Preserve a possible zero-divisor
            exception.
            ((X % Y) ==/!= X): New simplification.  Preserve a possible
            zero-divisor exception.
    
    gcc/testsuite/ChangeLog:
    
            * gcc.dg/tree-ssa/modcmp-1.c: New test.
            * gcc.dg/tree-ssa/modcmp-noncall-1.c: Likewise.
    
    Signed-off-by: Kyrylo Tkachov <[email protected]>

Diff:
---
 gcc/match.pd                                     | 16 ++++++++++++--
 gcc/testsuite/gcc.dg/tree-ssa/modcmp-1.c         | 22 +++++++++++++++++++
 gcc/testsuite/gcc.dg/tree-ssa/modcmp-noncall-1.c | 27 ++++++++++++++++++++++++
 3 files changed, 63 insertions(+), 2 deletions(-)

diff --git a/gcc/match.pd b/gcc/match.pd
index e99a35046bc8..66cf628fc613 100644
--- a/gcc/match.pd
+++ b/gcc/match.pd
@@ -3113,13 +3113,25 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
 
 /* Transform:
    (X / Y) == 0 -> X < Y if X, Y are non-negative.
-   (X / Y) != 0 -> X >= Y, if X, Y are non-negative.  */
+   (X / Y) != 0 -> X >= Y, if X, Y are non-negative.
+   (X % Y) == X -> X < Y if X, Y are non-negative.
+   (X % Y) != X -> X >= Y, if X, Y are non-negative.  */
 (for cmp (eq ne)
      ocmp (lt ge)
  (simplify
   (cmp (trunc_div tree_expr_nonnegative_p@0 tree_expr_nonnegative_p@1) integer_zerop)
        /* Complex ==/!= is allowed, but not </>=.  */
-  (if (TREE_CODE (TREE_TYPE (@0)) != COMPLEX_TYPE
+  (if (!integer_zerop (@1)
+       && (!flag_non_call_exceptions || tree_expr_nonzero_p (@1))
+       && TREE_CODE (TREE_TYPE (@0)) != COMPLEX_TYPE
+       && (VECTOR_TYPE_P (type) || !VECTOR_TYPE_P (TREE_TYPE (@0))))
+   (ocmp @0 @1)))
+ (simplify
+  (cmp:c (trunc_mod tree_expr_nonnegative_p@0 tree_expr_nonnegative_p@1) @0)
+  /* A complex modulo cannot exist, so unlike the division above this
+     needs no complex exclusion.  */
+  (if (!integer_zerop (@1)
+       && (!flag_non_call_exceptions || tree_expr_nonzero_p (@1))
        && (VECTOR_TYPE_P (type) || !VECTOR_TYPE_P (TREE_TYPE (@0))))
    (ocmp @0 @1))))
 
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/modcmp-1.c b/gcc/testsuite/gcc.dg/tree-ssa/modcmp-1.c
new file mode 100644
index 000000000000..a59882e66a3a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/modcmp-1.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+
+/* A remainder of non-negative operands equals its dividend exactly when the
+   dividend is smaller than the divisor, so the division can go away.  */
+
+int f1 (unsigned int x, unsigned int y) { return x % y == x; }
+int f2 (unsigned int x, unsigned int y) { return x % y != x; }
+int f3 (unsigned long x, unsigned long y) { return x == x % y; }
+int f4 (int x, int y)
+{
+  x &= __INT_MAX__;
+  y &= __INT_MAX__;
+  return x % y == x;
+}
+
+/* Signed operands that may be negative do not have that property.  */
+int f5 (int x, int y) { return x % y == x; }
+
+/* { dg-final { scan-tree-dump-times " % " 1 "optimized" } } */
+/* { dg-final { scan-tree-dump-times " < " 3 "optimized" } } */
+/* { dg-final { scan-tree-dump-times " >= " 1 "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/modcmp-noncall-1.c b/gcc/testsuite/gcc.dg/tree-ssa/modcmp-noncall-1.c
new file mode 100644
index 000000000000..aa5074b45173
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/modcmp-noncall-1.c
@@ -0,0 +1,27 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fnon-call-exceptions -fdump-tree-optimized" } */
+
+/* A possibly zero divisor can trap when non-call exceptions are enabled,
+   so the comparison must keep the division or remainder.  */
+
+int f1 (unsigned int x, unsigned int y) { return x / y == 0; }
+int f2 (unsigned int x, unsigned int y) { return x / y != 0; }
+int f3 (unsigned int x, unsigned int y) { return x % y == x; }
+int f4 (unsigned int x, unsigned int y) { return x % y != x; }
+
+/* A known nonzero divisor can still fold.  */
+int f5 (unsigned int x) { return x / 3 == 0; }
+int f6 (unsigned int x) { return x % 3 == x; }
+
+/* An explicit zero must retain the front-end diagnostic and trap.  */
+int f7 (unsigned int x)
+{
+  return x / 0 == 0; /* { dg-warning "division by zero" } */
+}
+int f8 (unsigned int x)
+{
+  return x % 0 == x; /* { dg-warning "division by zero" } */
+}
+
+/* { dg-final { scan-tree-dump-times " / " 3 "optimized" } } */
+/* { dg-final { scan-tree-dump-times " % " 3 "optimized" } } */