On 8/6/2026 8:42 AM, Kyrylo Tkachov wrote:

On 6 Aug 2026, at 06:16, Jeffrey Law <[email protected]> wrote:



On 8/4/2026 3:50 AM, [email protected] wrote:
From: Kyrylo Tkachov <[email protected]>

Truncating remainder keeps the sign of the dividend and its magnitude
modulo the divisor, so reducing X % C1 again modulo C2 gives the same
result as reducing X directly whenever C2 divides C1.  Folding the pair
removes one division.

   int f (int x) { return (x % 12) % 4; }

aarch64 -O2:

   before                          after
     mov   w1, 12                    negs  w1, w0
     sdiv  w1, w0, w1                and   w0, w0, 3
     add   w1, w1, w1, lsl 1         and   w1, w1, 3
     sub   w0, w0, w1, lsl 2         csneg w0, w0, w1, mi
     negs  w1, w0
     and   w0, w0, 3
     and   w1, w1, 3
     csneg w0, w0, w1, mi

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

gcc/ChangeLog:

* match.pd ((X % C1) % C2): New simplification.

gcc/testsuite/ChangeLog:

* gcc.dg/tree-ssa/modmod-1.c: New test.

Signed-off-by: Kyrylo Tkachov <[email protected]>
---
  gcc/match.pd                             | 12 ++++++++++++
  gcc/testsuite/gcc.dg/tree-ssa/modmod-1.c | 19 +++++++++++++++++++
  2 files changed, 31 insertions(+)
  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/modmod-1.c

diff --git a/gcc/match.pd b/gcc/match.pd
index 4fca75d6fb6..22202af2cc1 100644
--- a/gcc/match.pd
+++ b/gcc/match.pd
@@ -975,6 +975,18 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
      (with { tree utype = unsigned_type_for (TREE_TYPE (@0)); }
       (cmp (mod (convert:utype @0) (convert:utype @2)) (convert:utype @1)))))))
  +/* (X % C1) % C2 is X % C2 when C2 divides C1.  Truncating remainder keeps
+   the sign of X and the magnitude modulo C1, so reducing modulo C2 gives
+   the same result as reducing X directly.  */
+(simplify
+ (trunc_mod (trunc_mod @0 INTEGER_CST@1) INTEGER_CST@2)
+ (if (INTEGRAL_TYPE_P (type)
+      && !TYPE_OVERFLOW_TRAPS (type)
+      && !integer_zerop (@1)
+      && !integer_zerop (@2)
+      && wi::multiple_of_p (wi::to_widest (@1), wi::to_widest (@2), SIGNED))
Is SIGNED really correct for that argument to wi::multiple_of_p? I don't have a 
testcase where it matters.  Just a generic question.

I think so, yes. UNSIGNED would misinterpret negative constants.
One change I did make is guard against (X % -1) % C2 to avoid removing a trap 
or diagnostic under -ftrapv for x = INT_MIN.


+  (trunc_mod @0 @2)))
+
  /* X % -C is the same as X % C.  */
  (simplify
   (trunc_mod @0 INTEGER_CST@1)
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/modmod-1.c 
b/gcc/testsuite/gcc.dg/tree-ssa/modmod-1.c
new file mode 100644
index 00000000000..6689a518ff3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/modmod-1.c
@@ -0,0 +1,19 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+
+/* (X % C1) % C2 is X % C2 when C2 divides C1.  */
+
+int f1 (int x) { return (x % 12) % 4; }
+int f2 (int x) { return (x % 100) % 25; }
+int f3 (int x) { return (x % -12) % 4; }
+int f4 (int x) { return (x % 12) % -4; }
+unsigned int f5 (unsigned int x) { return (x % 12) % 4; }
For f5, do you want to verify it collapses to an & 3?  I guess the lack of % 12 or 
%4 for it is probably sufficient since the other counts would get thrown off if we 
failed to optimize f5 down to &3.
Yes, I’ve made that change

Generally it looks good.  Just like to nail down that the SIGNED argument is 
really what we want.
Thanks, attached is an updated version. Is this one ok?
Yea.  This is OK for the trunk.

jeff

Reply via email to