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