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. */
