https://gcc.gnu.org/g:e88c76e0a7ef568ebff9c09f18c7f9516d6a0d24
commit r17-5677-ge88c76e0a7ef568ebff9c09f18c7f9516d6a0d24 Author: Igor Shevlyakov <[email protected]> Date: Sun Jul 19 14:12:16 2026 -0700 vect-generic: Handle signed division by a negative power of two expand_vector_divmod only used the power-of-two shift path for positive divisors; for a negative divisor it cleared use_pow2 and fell back to the multiply-high sequence. The scalar expand_divmod (expmed.cc) instead computes op0 / |d| with the round-toward-zero shift idiom and negates the quotient when d < 0, since x / -2^k == -(x / 2^k). Do the same for vectors. The analysis loop now accepts a divisor whose absolute value is a power of two. For TRUNC_DIV it records the sign, which must be the same in every lane, in negate_p, and negates the quotient when the divisor was negative; without a vector negate pattern that expands as 0 - q. TRUNC_MOD reuses the mask path unchanged, for any mix of signs, since x % d == x % |d|. A mixed-sign division, a target that can neither negate nor subtract vectors, or the INT_MIN-magnitude divisor (log2 == prec - 1, which has no positive representation for the shift idiom) fall back to the existing multiply-high path. The positive-signed and unsigned paths are unchanged: for those lanes this_neg is 0, wi::exact_log2 matches the previous tree_log2, and the disable conditions reduce to the originals. gcc/ChangeLog: * tree-vect-generic.cc (expand_vector_divmod): Handle division by a negative power of two. gcc/testsuite/ChangeLog: * gcc.c-torture/execute/vector-div-neg-pow2.c: New test. * gcc.dg/vect/veclower/vec-div-neg-pow2.c: New test. Signed-off-by: Igor Shevlyakov <[email protected]> Diff: --- .../gcc.c-torture/execute/vector-div-neg-pow2.c | 92 ++++++++++++++++++++++ .../gcc.dg/vect/veclower/vec-div-neg-pow2.c | 38 +++++++++ gcc/tree-vect-generic.cc | 58 +++++++++++--- 3 files changed, 179 insertions(+), 9 deletions(-) diff --git a/gcc/testsuite/gcc.c-torture/execute/vector-div-neg-pow2.c b/gcc/testsuite/gcc.c-torture/execute/vector-div-neg-pow2.c new file mode 100644 index 000000000000..ece02fbb54c3 --- /dev/null +++ b/gcc/testsuite/gcc.c-torture/execute/vector-div-neg-pow2.c @@ -0,0 +1,92 @@ +/* Signed vector division and modulus by negative powers of two. */ + +typedef int v4si __attribute__ ((vector_size (16))); +typedef short v8hi __attribute__ ((vector_size (16))); +typedef long long v2di __attribute__ ((vector_size (16))); + +#define SI_MIN (-__INT_MAX__ - 1) +#define DI_MIN (-__LONG_LONG_MAX__ - 1) + +static const long long vals[] + = { 0, 1, -1, 2, -2, 7, -7, 31, -31, 32, -32, 33, -33, 1000, -1000, + 0x7fff, -0x8000, __INT_MAX__, SI_MIN, __LONG_LONG_MAX__, DI_MIN, + -1234567890123LL }; +#define NVALS (sizeof (vals) / sizeof (vals[0])) + +__attribute__ ((noipa)) static long long +sdiv (long long x, long long d) +{ + return x / d; +} + +__attribute__ ((noipa)) static long long +smod (long long x, long long d) +{ + return x % d; +} + +/* Divide and take the remainder of rotations of VALS by the constant + vector {...}, and check every lane against the scalar result. X / -1 + is undefined for the minimum value TMIN, so that lane gets 0 instead. */ +#define TEST(T, VT, N, TMIN, NAME, ...) \ +__attribute__ ((noipa)) static VT \ +q##NAME (VT x) \ +{ \ + return x / (VT) { __VA_ARGS__ }; \ +} \ +__attribute__ ((noipa)) static VT \ +r##NAME (VT x) \ +{ \ + return x % (VT) { __VA_ARGS__ }; \ +} \ +static void \ +check##NAME (void) \ +{ \ + static const T d[N] = { __VA_ARGS__ }; \ + for (unsigned j = 0; j < NVALS; j++) \ + { \ + VT x, q, r; \ + for (int i = 0; i < N; i++) \ + { \ + x[i] = (T) vals[(i + j) % NVALS]; \ + if (d[i] == -1 && x[i] == TMIN) \ + x[i] = 0; \ + } \ + q = q##NAME (x); \ + r = r##NAME (x); \ + for (int i = 0; i < N; i++) \ + if (q[i] != (T) sdiv (x[i], d[i]) \ + || r[i] != (T) smod (x[i], d[i])) \ + __builtin_abort (); \ + } \ +} + +TEST (int, v4si, 4, SI_MIN, si1, -32, -32, -32, -32) +TEST (int, v4si, 4, SI_MIN, si2, -2, -4, -8, -16) +TEST (int, v4si, 4, SI_MIN, si3, -2, -2, -2, -(1 << 30)) +TEST (int, v4si, 4, SI_MIN, si4, -4, 4, -4, 4) +TEST (int, v4si, 4, SI_MIN, si5, -4, -2, -1, -8) +TEST (int, v4si, 4, SI_MIN, si6, SI_MIN, SI_MIN, SI_MIN, SI_MIN) +TEST (short, v8hi, 8, -0x8000, hi1, -8, -8, -8, -8, -8, -8, -8, -8) +TEST (short, v8hi, 8, -0x8000, hi2, -2, -4, -8, -16, -32, -64, -128, + -0x4000) +TEST (long long, v2di, 2, DI_MIN, di1, -8, -8) +TEST (long long, v2di, 2, DI_MIN, di2, -2, -(1LL << 62)) +TEST (long long, v2di, 2, DI_MIN, di3, DI_MIN, DI_MIN) + +int +main (void) +{ + checksi1 (); + checksi2 (); + checksi3 (); + checksi4 (); + checksi5 (); + checksi6 (); + checkhi1 (); + checkhi2 (); + checkdi1 (); + checkdi2 (); + checkdi3 (); + return 0; +} diff --git a/gcc/testsuite/gcc.dg/vect/veclower/vec-div-neg-pow2.c b/gcc/testsuite/gcc.dg/vect/veclower/vec-div-neg-pow2.c new file mode 100644 index 000000000000..7d4757991d52 --- /dev/null +++ b/gcc/testsuite/gcc.dg/vect/veclower/vec-div-neg-pow2.c @@ -0,0 +1,38 @@ +/* { dg-do compile } */ +/* { dg-require-effective-target vect_int } */ +/* { dg-require-effective-target vect_shift } */ +/* { dg-require-effective-target vect128 } */ + +/* Division by a negative power of two is a shift and a negation, and the + remainder a mask, as for a positive divisor; no multiply-high. */ + +typedef int v4si __attribute__ ((vector_size (16))); +typedef short v8hi __attribute__ ((vector_size (16))); + +v4si +f1 (v4si x) +{ + return x / -32; +} + +v8hi +f2 (v8hi x) +{ + return x / -8; +} + +v4si +f3 (v4si x) +{ + return x % -32; +} + +v4si +f4 (v4si x) +{ + return x % (v4si) { -4, 4, -4, 4 }; +} + +/* { dg-final { scan-tree-dump-not " h\\* " "veclower21" } } */ +/* Targets with vector division keep it. */ +/* { dg-final { scan-tree-dump-times " = -_| / \{ -" 2 "veclower21" } } */ diff --git a/gcc/tree-vect-generic.cc b/gcc/tree-vect-generic.cc index 5f9086615e35..79d7bd995507 100644 --- a/gcc/tree-vect-generic.cc +++ b/gcc/tree-vect-generic.cc @@ -701,7 +701,11 @@ expand_vector_divmod (gimple_stmt_iterator *gsi, tree type, tree op0, { bool use_pow2 = true; bool has_vector_shift = true; + bool has_vector_negate = true; bool use_abs_op1 = false; + /* For the power-of-two path: 1 if the quotient is negated (all divisors + are negative), 0 if not, -1 if undetermined. */ + int negate_p = -1; int mode = -1, this_mode; int pre_shift = -1, post_shift; unsigned int nunits = nunits_for_known_piecewise_op (type); @@ -725,6 +729,17 @@ expand_vector_divmod (gimple_stmt_iterator *gsi, tree type, tree op0, || !can_implement_p (op, TYPE_MODE (type))) has_vector_shift = false; + /* expand_unop falls back to 0 - x for a missing vector negation. */ + op = optab_for_tree_code (NEGATE_EXPR, type, optab_default); + if (op == unknown_optab + || !can_implement_p (op, TYPE_MODE (type))) + { + op = optab_for_tree_code (MINUS_EXPR, type, optab_default); + if (op == unknown_optab + || !can_implement_p (op, TYPE_MODE (type))) + has_vector_negate = false; + } + /* Analysis phase. Determine if all op1 elements are either power of two and it is possible to expand it using shifts (or for remainder using masking). Additionally compute the multiplicative constants @@ -740,16 +755,35 @@ expand_vector_divmod (gimple_stmt_iterator *gsi, tree type, tree op0, pre_shifts[i] = 0; post_shifts[i] = 0; mulc[i] = 0; - if (use_pow2 - && (!integer_pow2p (cst) || tree_int_cst_sgn (cst) != 1)) - use_pow2 = false; if (use_pow2) { - shifts[i] = tree_log2 (cst); - if (shifts[i] != shifts[0] - && code == TRUNC_DIV_EXPR - && !has_vector_shift) + /* Divide by a negative power of two as by its absolute value and + negate the quotient, as expand_divmod does. The quotient needs + all elements to have the same sign; the remainder does not + depend on the sign, as x % d == x % -d. INT_MIN is excluded, + as its magnitude does not fit. */ + int this_neg = 0; + wide_int w = wi::to_wide (cst); + if (sign_p == SIGNED && wi::neg_p (w)) + { + this_neg = code == TRUNC_DIV_EXPR; + w = wi::neg (w); + } + int log2 = wi::exact_log2 (w); + if (log2 < 0 + || (sign_p == SIGNED && log2 >= prec - 1) + || (this_neg && !has_vector_negate) + || (negate_p >= 0 && negate_p != this_neg)) use_pow2 = false; + else + { + negate_p = this_neg; + shifts[i] = log2; + if (shifts[i] != shifts[0] + && code == TRUNC_DIV_EXPR + && !has_vector_shift) + use_pow2 = false; + } } if (mode == -2) continue; @@ -952,7 +986,8 @@ expand_vector_divmod (gimple_stmt_iterator *gsi, tree type, tree op0, else if (addend != NULL_TREE) { /* t1 = op0 + addend; - q = t1 >> shift; */ + q = t1 >> shift; + and, for a negative power-of-two divisor, q = -q. */ op = optab_for_tree_code (PLUS_EXPR, type, optab_default); if (op != unknown_optab && can_implement_p (op, TYPE_MODE (type))) @@ -960,7 +995,12 @@ expand_vector_divmod (gimple_stmt_iterator *gsi, tree type, tree op0, cur_op = gimplify_build2 (gsi, PLUS_EXPR, type, op0, addend); cur_op = add_shift (gsi, type, cur_op, shifts, RSHIFT_EXPR); if (cur_op != NULL_TREE) - return cur_op; + { + if (negate_p == 1) + cur_op = gimplify_build1 (gsi, NEGATE_EXPR, type, + cur_op); + return cur_op; + } } } }
