Since r14-2335 (PR110474) the epilogue of an unrolled main loop needs a
VF below the main loop's VF before unrolling, so when the main loop
already has the lowest VF the leftover iterations stay scalar. This
makes that rule a preference in vect_analyze_loop and otherwise accepts
any VF below the main loop's; without the preference PR110474 comes back
(a double dot product on neoverse-n2 executes twice the instructions).
With -mcpu=neoverse-v2, -v1, -n2 and cortex-a710 an 8-byte SAD over
rows (VF 1, unrolled four times) goes from 189 to 82 instructions and
executes 27% fewer over 0 to 40 rows. In gcc.dg/vect and
gcc.target/aarch64 at -O3 the only code changes are added epilogues
(neoverse-v2: 93 files, x86_64: 4).
Bootstrapped and regtested on x86_64-pc-linux-gnu; regtested on
aarch64-linux-gnu (cross, qemu; vect.exp and execute.exp also with
-march=armv8-a+sve -msve-vector-bits=256).
Assisted-by: Claude Opus 5 (Anthropic)
gcc/ChangeLog:
* tree-vect-loop.cc (vect_analyze_loop_2): Only require a
non-partial epilogue to have a lower VF than the main loop.
(vect_analyze_loop): Prefer an epilogue whose VF is lower than
the main loop's VF before unrolling, or that uses partial vectors,
and keep looking for one after finding another epilogue.
gcc/testsuite/ChangeLog:
* gcc.dg/vect/vect-unroll-epilogue-1.c: New test.
* gcc.target/aarch64/vect-unroll-epilogue-1.c: New test.
Signed-off-by: Dominic P <[email protected]>
---
OK for trunk?
.../gcc.dg/vect/vect-unroll-epilogue-1.c | 75 +++++++++++++++++++
.../aarch64/vect-unroll-epilogue-1.c | 19 +++++
gcc/tree-vect-loop.cc | 40 ++++++----
3 files changed, 119 insertions(+), 15 deletions(-)
create mode 100644 gcc/testsuite/gcc.dg/vect/vect-unroll-epilogue-1.c
create mode 100644 gcc/testsuite/gcc.target/aarch64/vect-unroll-epilogue-1.c
diff --git a/gcc/testsuite/gcc.dg/vect/vect-unroll-epilogue-1.c
b/gcc/testsuite/gcc.dg/vect/vect-unroll-epilogue-1.c
new file mode 100644
index 00000000000..dfe901d5c4a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/vect/vect-unroll-epilogue-1.c
@@ -0,0 +1,75 @@
+/* { dg-additional-options "-O3" } */
+
+#include "tree-vect.h"
+
+#define N 40
+
+__attribute__((noipa)) int
+sad16 (unsigned char *p, unsigned char *q, int h)
+{
+ int s = 0;
+#pragma GCC unroll 4
+ for (int i = 0; i < h; i++, p += 16, q += 16)
+ for (int k = 0; k < 16; k++)
+ s += __builtin_abs (p[k] - q[k]);
+ return s;
+}
+
+__attribute__((noipa)) void
+st16 (unsigned char *restrict o, unsigned char *p, int h)
+{
+#pragma GCC unroll 4
+ for (int i = 0; i < h; i++, p += 16, o += 16)
+ for (int k = 0; k < 16; k++)
+ o[k] = p[k] + 3;
+}
+
+__attribute__((noipa)) void
+add (double *restrict a, double *b, double *c, int n)
+{
+#pragma GCC unroll 8
+ for (int i = 0; i < n; i++)
+ a[i] = b[i] + c[i];
+}
+
+unsigned char p[16 * N], q[16 * N], o[16 * N + 1];
+double a[N + 1], b[N], c[N];
+
+int
+main (void)
+{
+ check_vect ();
+ for (int i = 0; i < 16 * N; i++)
+ {
+ p[i] = i * 7;
+ q[i] = i * 13;
+ }
+ for (int i = 0; i < N; i++)
+ {
+ b[i] = i;
+ c[i] = 2 * i;
+ }
+ for (int n = 0; n <= N; n++)
+ {
+ int s = 0;
+ for (int i = 0; i < 16 * n; i++)
+ s += __builtin_abs (p[i] - q[i]);
+ if (sad16 (p, q, n) != s)
+ abort ();
+ o[16 * n] = 1;
+ st16 (o, p, n);
+ for (int i = 0; i < 16 * n; i++)
+ if (o[i] != (unsigned char) (p[i] + 3))
+ abort ();
+ if (o[16 * n] != 1)
+ abort ();
+ a[n] = -1;
+ add (a, b, c, n);
+ for (int i = 0; i < n; i++)
+ if (a[i] != 3 * i)
+ abort ();
+ if (a[n] != -1)
+ abort ();
+ }
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/aarch64/vect-unroll-epilogue-1.c
b/gcc/testsuite/gcc.target/aarch64/vect-unroll-epilogue-1.c
new file mode 100644
index 00000000000..e8e62655089
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/vect-unroll-epilogue-1.c
@@ -0,0 +1,19 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -fdump-tree-vect-details" } */
+
+/* The main loop has VF 1 and is unrolled; its epilogue can only have VF 1. */
+
+#pragma GCC target "+nosve"
+
+int
+f (unsigned char *p, unsigned char *q, int h)
+{
+ int s = 0;
+#pragma GCC unroll 4
+ for (int i = 0; i < h; i++, p += 16, q += 16)
+ for (int k = 0; k < 16; k++)
+ s += __builtin_abs (p[k] - q[k]);
+ return s;
+}
+
+/* { dg-final { scan-tree-dump "Choosing epilogue vector mode V16QI" "vect" }
} */
diff --git a/gcc/tree-vect-loop.cc b/gcc/tree-vect-loop.cc
index 9b2afad6034..c3fb64fa362 100644
--- a/gcc/tree-vect-loop.cc
+++ b/gcc/tree-vect-loop.cc
@@ -2518,16 +2518,12 @@ start_over:
to be able to handle fewer than VF scalars, or needs to have a lower VF
than the main loop. */
if (LOOP_VINFO_EPILOGUE_P (loop_vinfo)
- && !LOOP_VINFO_USING_PARTIAL_VECTORS_P (loop_vinfo))
- {
- poly_uint64 unscaled_vf
- = exact_div (LOOP_VINFO_VECT_FACTOR (orig_loop_vinfo),
- orig_loop_vinfo->suggested_unroll_factor);
- if (maybe_ge (LOOP_VINFO_VECT_FACTOR (loop_vinfo), unscaled_vf))
- return opt_result::failure_at (vect_location,
- "Vectorization factor too high for"
- " epilogue loop.\n");
- }
+ && !LOOP_VINFO_USING_PARTIAL_VECTORS_P (loop_vinfo)
+ && maybe_ge (LOOP_VINFO_VECT_FACTOR (loop_vinfo),
+ LOOP_VINFO_VECT_FACTOR (orig_loop_vinfo)))
+ return opt_result::failure_at (vect_location,
+ "Vectorization factor too high for"
+ " epilogue loop.\n");
/* If the epilogue needs peeling for gaps but the main loop doesn't give
up on the epilogue. */
@@ -3110,6 +3106,17 @@ vect_analyze_loop (class loop *loop, gimple
*loop_vectorized_call,
loop_vec_info orig_loop_vinfo = first_loop_vinfo;
do
{
+ /* Prefer an epilogue with a VF below that of the main loop before
+ unrolling. */
+ poly_uint64 unscaled_vf
+ = exact_div (LOOP_VINFO_VECT_FACTOR (orig_loop_vinfo),
+ orig_loop_vinfo->suggested_unroll_factor);
+ auto low_vf_p = [&] (loop_vec_info vinfo)
+ {
+ return (LOOP_VINFO_USING_PARTIAL_VECTORS_P (vinfo)
+ || known_lt (LOOP_VINFO_VECT_FACTOR (vinfo), unscaled_vf));
+ };
+
/* Let the user override what the target suggests. */
if (OPTION_SET_P (param_vect_partial_vector_usage))
masked_p = -1;
@@ -3161,10 +3168,12 @@ vect_analyze_loop (class loop *loop, gimple
*loop_vectorized_call,
if (loop_vinfo)
{
- if (pick_lowest_cost_p
- && orig_loop_vinfo->epilogue_vinfo
- && vect_joust_loop_vinfos (loop_vinfo,
- orig_loop_vinfo->epilogue_vinfo))
+ loop_vec_info old = orig_loop_vinfo->epilogue_vinfo;
+ if (old
+ && (low_vf_p (loop_vinfo) != low_vf_p (old)
+ ? low_vf_p (loop_vinfo)
+ : (pick_lowest_cost_p
+ && vect_joust_loop_vinfos (loop_vinfo, old))))
{
gcc_assert (vect_epilogues);
delete orig_loop_vinfo->epilogue_vinfo;
@@ -3181,7 +3190,8 @@ vect_analyze_loop (class loop *loop, gimple
*loop_vectorized_call,
/* For now only allow one epilogue loop, but allow
pick_lowest_cost_p to replace it, so commit to the
first epilogue if we have no reason to try alternatives. */
- if (!pick_lowest_cost_p)
+ if (!pick_lowest_cost_p
+ && low_vf_p (orig_loop_vinfo->epilogue_vinfo))
break;
}
--
2.55.0