https://gcc.gnu.org/g:87c2f1ce7ce4b3e1792d4acdc98fc7132c7ab9c7

commit r17-2001-g87c2f1ce7ce4b3e1792d4acdc98fc7132c7ab9c7
Author: Christopher Bazley <[email protected]>
Date:   Fri Jun 12 13:44:48 2026 +0000

    Handle divisor bigger than dividend in can_div_away_from_zero_p
    
    PR middle-end/125767
    
    When the magnitude of the divisor is known to be bigger
    than that of the dividend, the output quotient should be 1
    when divisor and dividend have the same sign or -1
    when they have opposite signs.
    
    Previously, can_div_away_from_zero_p was overreliant on
    can_div_trunc_p: if can_div_trunc_p returned a failure
    indication then can_div_away_from_zero_p did likewise.
    
    This matters because can_div_away_from_zero_p is commonly
    used to answer questions such as "How many registers does
    this value occupy?" If the register has a scalable vector
    type, then the answer should be 1 if the number of bits
    occupied by the value is known to be not greater than
    the minimum number of bits in the vector type (as well as
    if the number of bits occupied by the value is known to be
    smaller). Previously, can_div_away_from_zero_p had to be
    used with care to avoid wrongly concluding that a value of
    type V16QI would not fit in a register of type VNx16QI.
    That could cause selection of inefficient instruction
    sequences or even an ICE in cases where V8QI in VNx16QI
    behaved as expected.
    
    Because polynomial division truncated toward zero can
    fail to produce a constant quotient in cases where
    polynomial division rounded away from zero can produce a
    constant quotient, it is not sufficient for the
    implementation of can_div_away_from_zero_p to simply adjust
    the quotient produced by can_div_trunc_p.
    
    can_div_trunc_p requires |b * Q| <= |a| whereas
    can_div_away_from_zero_p requires |b * Q| >= |a|. The
    latter can be proven in cases where the former cannot.
    For example, when a = 16 + 0i and b = 16 + 16i, |b * Q|
    cannot be smaller than |a| unless Q = 0, which would
    violate a common requirement that |a - b * Q| < |b|
    (because |a| >= 16 and |b| >= 16). In contrast, |b * Q|
    is bigger than |a| if Q > 1 or i > 0. Crucially, it's
    impossible to know the value of i at compile time, so
    can_div_trunc_p must conservatively return false.
    
    Another way of looking at it is:
    Q = (16 + 0i) / (16 + 16i)
      = 16 / (16 + 16i)
      = 1 / (1 + i)
    
    which is not "some constant Q" that these functions must
    find in order to return true; however, we can be sure that
    whatever the unknowable value of 1 / (1 + i) is, it lies
    within the range 0 < Q <= 1. We cannot know whether that
    answer should be truncated to zero, but we can know that
    it should be rounded to one.
    
    gcc/ChangeLog:
    
            * poly-int.h (can_div_away_from_zero_p):
            Exit early if |a| <= |b| instead of calling
            can_div_trunc_p and returning false if it
            returned false.
    
    gcc/testsuite/ChangeLog:
    
            * gcc.dg/plugin/poly-int-tests.h: New test cases.
    
    Co-authored-by: Richard Sandiford <[email protected]>

Diff:
---
 gcc/poly-int.h                               | 40 +++++++++++++++++++++++++
 gcc/testsuite/gcc.dg/plugin/poly-int-tests.h | 44 ++++++++++++++++++++++++++++
 2 files changed, 84 insertions(+)

diff --git a/gcc/poly-int.h b/gcc/poly-int.h
index cacd1c21443c..01bc5cc5c170 100644
--- a/gcc/poly-int.h
+++ b/gcc/poly-int.h
@@ -2420,6 +2420,46 @@ inline typename if_nonpoly<Cq, bool>::type
 can_div_away_from_zero_p (const poly_int<N, Ca> &a, const poly_int<N, Cb> &b,
                          Cq *quotient)
 {
+  /* If both a and b have fixed signs, test 0 < |a| <= |b|.  (2) is then
+     satisfied with |Q| == 1.  Choose Q as 1 or -1 such that a and b * Q
+     have the same sign.  Together, these two conditions guarantee that
+     |b * Q - a| = |b * Q| - |a|.  Then:
+
+       |r| = |a - b * Q|
+           = |b * Q - a|
+           = |b * Q| - |a|
+           = |b| - |a|
+           < |b| using 0 < |a| above
+
+     Only negate values that are known to be positive, to avoid signed
+     overflow.  */
+  if (known_lt (a, POLY_INT_TYPE (Ca) (0)))
+    {
+      if (known_le (b, a))
+       {
+         *quotient = 1;
+         return true;
+       }
+      if (known_gt (b, POLY_INT_TYPE (Cb) (0)) && known_le (-b, a))
+       {
+         *quotient = -1;
+         return true;
+       }
+    }
+  else if (known_gt (a, POLY_INT_TYPE (Ca) (0)))
+    {
+      if (known_ge (b, a))
+       {
+         *quotient = 1;
+         return true;
+       }
+      if (known_lt (b, POLY_INT_TYPE (Cb) (0)) && known_le (b, -a))
+       {
+         *quotient = -1;
+         return true;
+       }
+    }
+
   if (!can_div_trunc_p (a, b, quotient))
     return false;
   if (maybe_ne (*quotient * b, a))
diff --git a/gcc/testsuite/gcc.dg/plugin/poly-int-tests.h 
b/gcc/testsuite/gcc.dg/plugin/poly-int-tests.h
index 022ccd69cea5..19a6959d0a10 100644
--- a/gcc/testsuite/gcc.dg/plugin/poly-int-tests.h
+++ b/gcc/testsuite/gcc.dg/plugin/poly-int-tests.h
@@ -2161,6 +2161,18 @@ test_can_div_away_from_zero_p ()
                                         ph::make (4, 5, 6),
                                         &const_quot));
   ASSERT_EQ (const_quot, C (0));
+  ASSERT_EQ (can_div_away_from_zero_p (ph::make (16, 0, 16),
+                                      ph::make (16, 16, 15), &const_quot),
+            N <= 2);
+  ASSERT_EQ (const_quot, N <= 2 ? C (1) : C (0));
+  ASSERT_TRUE (can_div_away_from_zero_p (ph::make (16, 0, 0),
+                                        ph::make (16, 16, 16),
+                                        &const_quot));
+  ASSERT_EQ (const_quot, C (1));
+  ASSERT_TRUE (can_div_away_from_zero_p (ph::make (8, 0, 0),
+                                        ph::make (16, 16, 16),
+                                        &const_quot));
+  ASSERT_EQ (const_quot, C (1));
 }
 
 /* Test known_size_p.  */
@@ -3388,6 +3400,38 @@ test_signed_can_div_away_from_zero_p ()
                                         ph::make (-8, -4, -5),
                                         &const_quot));
   ASSERT_EQ (const_quot, -3);
+  ASSERT_EQ (can_div_away_from_zero_p (ph::make (-16, 0, -16),
+                                      ph::make (-16, -16, -15), &const_quot),
+            N <= 2);
+  ASSERT_EQ (const_quot, N <= 2 ? C (1) : C (-3));
+  ASSERT_TRUE (can_div_away_from_zero_p (ph::make (-16, 0, 0),
+                                        ph::make (-16, -16, -16),
+                                        &const_quot));
+  ASSERT_EQ (const_quot, C (1));
+  ASSERT_TRUE (can_div_away_from_zero_p (ph::make (-8, 0, 0),
+                                        ph::make (-16, -16, -16),
+                                        &const_quot));
+  ASSERT_EQ (const_quot, C (1));
+  ASSERT_EQ (can_div_away_from_zero_p (ph::make (-16, 0, 16),
+                                      ph::make (16, 16, 15), &const_quot),
+            N <= 2);
+  ASSERT_EQ (const_quot, N <= 2 ? C (-1) : C (1));
+  ASSERT_TRUE (can_div_away_from_zero_p (ph::make (-16, 0, 0),
+                                        ph::make (16, 16, 16),
+                                        &const_quot));
+  ASSERT_EQ (const_quot, C (-1));
+  ASSERT_TRUE (can_div_away_from_zero_p (ph::make (-8, 0, 0),
+                                        ph::make (16, 16, 16),
+                                        &const_quot));
+  ASSERT_EQ (const_quot, C (-1));
+  ASSERT_TRUE (can_div_away_from_zero_p (ph::make (16, 0, 0),
+                                        ph::make (-16, -16, -16),
+                                        &const_quot));
+  ASSERT_EQ (const_quot, C (-1));
+  ASSERT_TRUE (can_div_away_from_zero_p (ph::make (8, 0, 0),
+                                        ph::make (-16, -16, -16),
+                                        &const_quot));
+  ASSERT_EQ (const_quot, C (-1));
 }
 
 /* Test maybe_in_range_p for signed C.  */

Reply via email to