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

Reply via email to