We're missing the handling of one use case for PR101179:
int f2(int y)
{
const bool x = y % 100 == 0;
return y % (4 << (x * 2)) == 0;
}
This couldn't be handled by the previous PR101179 work because it won't
generate PHI nodes for the constants.
We're adding a match.pd that handles it directly, along with another
pattern that is a minor variation of it.
As a bonus we're also adding a more generic match.pd pattern that
handles all pow2 SSA_NAMEs by querying ranger. This generic pattern
won't be as efficient as the more specialized patterns, since it won't
be able to generate a gcond that produces two CSTs, but it'll turn a
trunc_mod into a bit_and and generate better code for cases that we
wouldn't detect otherwise.
Bootstrapped and regression tested in x86_64 and aarch64.
gcc/ChangeLog:
* match.pd (`(A % (CST shift zero_one)) EQ|NE 0`): New pattern.
(`(A % (CST shift (CST2 * zero_one)) EQ|NE 0`): New pattern.
(`(A % POW2_SSA_NAME) EQ|NE 0`): New pattern.
gcc/testsuite/ChangeLog:
* gcc.dg/tree-ssa/pr101179-2.c: New test.
---
Changes from v1:
- remove the new ranger-based pattern from the (for shift (lshift rshift))
iterator;
- match a discrete range (i.e. lower_bound == upper_bound) instead of a
contiguous range;
- add negative test to check we're not simplifying a non-pow2 range.
- v1 link: https://gcc.gnu.org/pipermail/gcc-patches/2026-September/731838.html
gcc/match.pd | 98 +++++++++++++++++++++-
gcc/testsuite/gcc.dg/tree-ssa/pr101179-2.c | 42 ++++++++++
2 files changed, 139 insertions(+), 1 deletion(-)
create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/pr101179-2.c
diff --git a/gcc/match.pd b/gcc/match.pd
index 2d1161b860b..8f51256403a 100644
--- a/gcc/match.pd
+++ b/gcc/match.pd
@@ -9465,7 +9465,103 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
(simplify
(op @3 (cond @0 @1 @2))
(cond @0 (op!:type @3 @1) (op!:type @3 @2))))
-#endif
+
+(for cmp (eq ne)
+ (for shift (lshift rshift)
+
+ /* PR101179: turn (A % (CST shift zero_one)) EQ|NE 0 into:
+
+ (A & ((zero_one ? CST shift 1 : CST) - 1)) EQ|NE 0
+
+ I.e. convert MOD to BIT_AND and "shift zero_one" into
+ a ternary. */
+ (simplify
+ (cmp (trunc_mod@3 @4
+ (shift integer_pow2p@0 (convert zero_one_valued_p@1)))
+ integer_zerop@2)
+ (if (single_use (@3)
+ && INTEGRAL_TYPE_P (TREE_TYPE (@0))
+ && INTEGRAL_TYPE_P (TREE_TYPE (@4))
+ && tree_int_cst_sgn (@0) > 0)
+ (cmp
+ (bit_and
+ @4
+ (minus (cond (ne @1 { build_zero_cst (TREE_TYPE (@1)); })
+ (shift @0 { build_one_cst (TREE_TYPE (@0)); })
+ @0)
+ { build_one_cst (TREE_TYPE (@0)); }))
+ @2)))
+
+ /* PR101179: a variation from above using 'mult':
+
+ (A % (CST shift (CST2 * zero_one))) EQ|NE 0 ->
+ (A & ((zero_one ? CST shift CST2 : CST) + -1)) EQ|NE 0; */
+ (simplify
+ (cmp (trunc_mod@3
+ @5
+ (shift integer_pow2p@0
+ (mult:c INTEGER_CST@4 (convert zero_one_valued_p@1))))
+ integer_zerop@2)
+ (if (single_use (@3)
+ && INTEGRAL_TYPE_P (TREE_TYPE (@0))
+ && INTEGRAL_TYPE_P (TREE_TYPE (@5))
+ && tree_int_cst_sgn (@0) > 0
+ && tree_int_cst_sgn (@4) > 0)
+ (cmp
+ (bit_and
+ @5
+ (minus (cond (ne @1 { build_zero_cst (TREE_TYPE (@1)); })
+ (shift @0 (mult @4 { build_one_cst (TREE_TYPE (@0)); }))
+ @0)
+ { build_one_cst (TREE_TYPE (@0)); }))
+ @2)))))
+
+ /* PR101179: this is a generic "catch all" pattern that uses ranger
+ to determine if a given SSA_NAME can be assumed to be a pow2:
+
+ (A % POW2_SSA_NAME) EQ|NE 0 -> (A & (POW_SSA_NAME -1)) EQ|NE 0
+
+ This will not generate better code than re-creating the CSTs
+ but it will match more often than the specialized patterns
+ from above. */
+(for cmp (eq ne)
+ (simplify
+ (cmp (trunc_mod@3 @0 SSA_NAME@1) integer_zerop@2)
+ (if (single_use (@3)
+ && INTEGRAL_TYPE_P (TREE_TYPE (@0))
+ && INTEGRAL_TYPE_P (TREE_TYPE (@1)))
+ (with {
+ int_range_max vr;
+ bool pow2_range = true;
+
+ if (gimple_match_range_of_expr (vr, @1, @0)
+ && !vr.varying_p ()
+ && !vr.undefined_p ()
+ && vr.nonnegative_p ()
+ && vr.num_pairs () > 0)
+ {
+ for (unsigned int i = 0; i < vr.num_pairs (); i++)
+ {
+ wide_int lb = vr.lower_bound (i);
+ wide_int ub = vr.upper_bound (i);
+
+ /* What we're looking for are discrete pow2 range
+ pairs, e.g. [2,2][8,8][16,16]. */
+ if (!wi::eq_p (lb, ub)
+ || wi::exact_log2 (lb) == -1)
+ {
+ pow2_range = false;
+ break;
+ }
+ }
+ }
+ else
+ pow2_range = false; }
+ (if (pow2_range)
+ (cmp
+ (bit_and @0 (minus @1 { build_one_cst (TREE_TYPE (@1)); }))
+ @2))))))
+#endif /* if GIMPLE; */
/* Transform comparisons of the form (X & Y) CMP 0 to X CMP2 Z
where ~Y + 1 == pow2 and Z = ~Y. */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/pr101179-2.c
b/gcc/testsuite/gcc.dg/tree-ssa/pr101179-2.c
new file mode 100644
index 00000000000..751d827070b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/pr101179-2.c
@@ -0,0 +1,42 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fdump-tree-optimized" } */
+
+int f1 (int y, _Bool x)
+{
+ return y % (4 << x) == 0;
+}
+/* { dg-final { scan-tree-dump-times " ? 7 : 3;" 1 "optimized" } } */
+
+int f2 (int y, _Bool x)
+{
+ return y % (4 >> x) == 0;
+}
+/* { dg-final { scan-tree-dump-times " ? 1 : 3;" 1 "optimized" } } */
+
+int f3 (int y, _Bool x)
+{
+ return y % (8 << (x * 2)) == 0;
+}
+/* { dg-final { scan-tree-dump-times " ? 31 : 7;" 1 "optimized" } } */
+
+int f4 (int y, _Bool x)
+{
+ return y % (8 >> (x * 2)) == 0;
+}
+/* { dg-final { scan-tree-dump-times " ? 1 : 7;" 1 "optimized" } } */
+
+int f5 (int y, int n)
+{
+ if (n != 2 && n != 8) __builtin_unreachable () ;
+ return y % n == 0;
+}
+
+/* Negative test: shouldn't be simplified. */
+int f6 (int y, int n)
+{
+ if (n < 1 || n > 4) return 0;
+ return y % n == 0;
+}
+
+/* { dg-final { scan-tree-dump-times " \& " 5 "optimized" } } */
+/* { dg-final { scan-tree-dump-times " \% " 1 "optimized" } } */
--
2.43.0