RE: [PATCH] aarch64: Use SVE SPLICE for scalable recurrences
Tamar Christina <[email protected]>
| Newsgroups | gmane.comp.gcc.patches |
|---|---|
| Message-ID | <VI0PR08MB103929B0739301AAFFD735D39FFA42@VI0PR08MB10392.eurprd08.prod.outlook.com> |
> -----Original Message----- > From: Kyrylo Tkachov <[email protected]> > Sent: 20 August 2026 15:32 > To: Tamar Christina <[email protected]> > Cc: [email protected] > Subject: Re: [PATCH] aarch64: Use SVE SPLICE for scalable recurrences > > Hi Tamar, > > > On 20 Aug 2026, at 15:52, Tamar Christina <[email protected]> > wrote: > > > >> -----Original Message----- > >> From: [email protected] <[email protected]> > >> Sent: 20 August 2026 13:34 > >> To: [email protected] > >> Cc: Tamar Christina <[email protected]>; Kyrylo Tkachov > >> <[email protected]> > >> Subject: [PATCH] aarch64: Use SVE SPLICE for scalable recurrences > >> > >> From: Kyrylo Tkachov <[email protected]> > >> > >> The vectorizer represents a first-order recurrence as a scalable > >> permutation that takes the final D elements from one vector and the > >> remaining elements from the next vector. The AArch64 permutation > >> expander did not recognize this variable-length shape. > >> > >> Recognize the exact two-input permutation and build a predicate for D > >> elements. Reverse that predicate and use the SVE SPLICE pattern. Reject > >> fixed-length vectors, rotations, and distances that cannot be represented > >> at the minimum vector length. > >> > >> The compile test checks seven direct SPLICE sequences across byte, > >> halfword, word, and doubleword modes. A 67-element runtime test > covers > >> distances 1 and 2 with two seed values, and partial final vectors. > > > > Nice! > > > >> > >> On an example testcase: > >> > >> typedef __UINT32_TYPE__ uint32_t; > >> > >> void > >> recur (const uint32_t *__restrict a, uint32_t *__restrict b, > >> const uint32_t *__restrict init) > >> { > >> uint32_t prev = *init; > >> for (int i = 0; i < 67; ++i) > >> { > >> b[i] = a[i] - prev; > >> prev = a[i]; > >> } > >> } > >> > >> With -O3 -march=armv8.2-a+sve > >> We emitted before: > >> > >> ldr w4, [x2] > >> mov w3, 0 > >> .p2align 5,,15 > >> .L2: > >> mov w2, w4 > >> ldr w4, [x0, x3] > >> sub w2, w4, w2 > >> str w2, [x1, x3] > >> add x3, x3, 4 > >> cmp x3, 268 > >> bne .L2 > >> > >> And after this patch: > >> mov w3, 0 > >> cntw x5 > >> ptrue p5.b, all > >> mov w4, 67 > >> ptrue p6.b, vl1 > >> mov p7.b, p5.b > >> ld1rw z31.s, p5/z, [x2] > >> rev p6.s, p6.s > >> .p2align 5,,15 > >> .L2: > >> mov z30.d, z31.d > >> ld1w z31.s, p7/z, [x0, x3, lsl 2] > >> splice z30.s, p6, z30.s, z31.s > >> sub z30.s, z31.s, z30.s > >> st1w z30.s, p7, [x1, x3, lsl 2] > >> add x3, x3, x5 > >> whilelo p7.s, w3, w4 > >> b.any .L2 > >> > >> This also vectorizes some loops in OpenEXR 4.0.0. > >> > >> Upstream PXR24, RLE, ZIP, and ZIPS tests pass, and the baseline and > >> patched encoders produced identical files. A paired exrmetrics run on an > >> NVIDIA Vera system measured the following write-time improvements. > >> > >> Flowers.exr multipart.0001.exr > >> PXR24, float 2.28% 4.58% > >> PXR24, original 3.22% 8.07% > >> RLE 13.52% 23.27% > >> ZIP 3.67% 9.59% > >> ZIPS 3.15% 5.62% > >> > >> Bootstrapped and tested on aarch64-none-linux-gnu. > >> Ok for trunk? > >> Thanks, > >> Kyrill > >> > >> gcc/ChangeLog: > >> > >> * config/aarch64/aarch64.cc (aarch64_evpc_splice): New function. > >> (aarch64_expand_vec_perm_const_1): Use it. > >> > >> gcc/testsuite/ChangeLog: > >> > >> * gcc.target/aarch64/sve/perm_splice_1.c: New test. > >> * gcc.target/aarch64/sve/perm_splice_1-run.c: Likewise. > >> > >> Signed-off-by: Kyrylo Tkachov <[email protected]> > >> --- > >> gcc/config/aarch64/aarch64.cc | 42 +++++++++ > >> .../aarch64/sve/perm_splice_1-run.c | 94 +++++++++++++++++++ > >> .../gcc.target/aarch64/sve/perm_splice_1.c | 63 +++++++++++++ > >> 3 files changed, 199 insertions(+) > >> create mode 100644 gcc/testsuite/gcc.target/aarch64/sve/perm_splice_1- > >> run.c > >> create mode 100644 > gcc/testsuite/gcc.target/aarch64/sve/perm_splice_1.c > >> > >> diff --git a/gcc/config/aarch64/aarch64.cc > b/gcc/config/aarch64/aarch64.cc > >> index fdffb13ad22..2e440ce916d 100644 > >> --- a/gcc/config/aarch64/aarch64.cc > >> +++ b/gcc/config/aarch64/aarch64.cc > >> @@ -28261,6 +28261,46 @@ aarch64_evpc_ext (struct > expand_vec_perm_d > >> *d) > >> return true; > >> } > >> > >> +/* Return true if D describes a scalable-vector permutation that takes the > >> + last DIST elements from the first input and the remaining elements from > >> + the second input. */ > >> + > >> +static bool > >> +aarch64_evpc_splice (struct expand_vec_perm_d *d) > >> +{ > >> + poly_int64 nelt = d->perm.length (); > >> + HOST_WIDE_INT dist; > >> + > >> + if (d->vec_flags != VEC_SVE_DATA > >> + || d->one_vector_p > >> + || nelt.is_constant () > >> + || !(nelt - d->perm[0]).is_constant (&dist) > >> + || !IN_RANGE (dist, 1, INT_MAX) > >> + || !d->perm.series_p (0, 1, nelt - dist, 1)) > >> + return false; > >> + > >> + machine_mode pred_mode = aarch64_sve_pred_mode (d->vmode); > >> + if (aarch64_svpattern_for_vl (pred_mode, dist) > >> + == AARCH64_NUM_SVPATTERNS) > >> + return false; > >> + > >> + if (d->testing_p) > >> + return true; > >> + > >> + rtx_vector_builder builder (pred_mode, dist, 2); > >> + for (HOST_WIDE_INT i = 0; i < dist; ++i) > >> + builder.quick_push (CONST1_RTX (BImode)); > >> + for (HOST_WIDE_INT i = 0; i < dist; ++i) > >> + builder.quick_push (CONST0_RTX (BImode)); > >> + > >> + rtx head = force_reg (pred_mode, builder.build ()); > > > > Since you already have the pattern above, why not save the result of the > > aarch64_svpattern_for... > > > > and then replace this builder code with > > > > rtx head = aarch64_ptrue_reg (pred_mode, pattern); > > > > which is slightly cleaner? > > There is no aarch64_ptrue_reg overload taking an aarch64_svpattern. The > three are (machine_mode), (machine_mode, unsigned int), (machine_mode, > machine_mode), so this silently selects the unsigned int one via enum-to-int > conversion. > > If we add one such overload I think it’d be a separate patch that can clean up > other opportunities like this around here? Sure, that works for me. Thanks, Tamar > Thanks, > Kyrill > > > > > LGTM with that change. > > > > Thanks, > > Tamar > > > >> + rtx pred = gen_reg_rtx (pred_mode); > >> + emit_insn (gen_aarch64_sve_rev (pred_mode, pred, head)); > >> + emit_insn (gen_aarch64_sve_splice (d->vmode, d->target, pred, > >> + d->op0, d->op1)); > >> + return true; > >> +} > >> + > >> /* Recognize patterns for the REV{64,32,16} insns, which reverse elements > >> within each 64-bit, 32-bit or 16-bit granule. */ > >> > >> @@ -28786,6 +28826,8 @@ aarch64_expand_vec_perm_const_1 (struct > >> expand_vec_perm_d *d) > >> return true; > >> else if (aarch64_evpc_rev_global (d)) > >> return true; > >> + else if (aarch64_evpc_splice (d)) > >> + return true; > >> else if (aarch64_evpc_ext (d)) > >> return true; > >> else if (aarch64_evpc_dup (d)) > >> diff --git a/gcc/testsuite/gcc.target/aarch64/sve/perm_splice_1-run.c > >> b/gcc/testsuite/gcc.target/aarch64/sve/perm_splice_1-run.c > >> new file mode 100644 > >> index 00000000000..b2c13120c53 > >> --- /dev/null > >> +++ b/gcc/testsuite/gcc.target/aarch64/sve/perm_splice_1-run.c > >> @@ -0,0 +1,94 @@ > >> +/* { dg-do run } */ > >> +/* { dg-require-effective-target aarch64_sve_hw } */ > >> +/* { dg-options "-O3 -march=armv8.2-a+sve -mautovec-preference=sve- > >> only" } */ > >> +/* { dg-additional-options "-msve-vector-bits=scalable" } */ > >> +/* { dg-additional-options "-fvect-cost-model=unlimited" } */ > >> + > >> +typedef __UINT8_TYPE__ uint8_t; > >> +typedef __UINT16_TYPE__ uint16_t; > >> +typedef __UINT32_TYPE__ uint32_t; > >> +typedef __UINT64_TYPE__ uint64_t; > >> + > >> +#define N 67 > >> + > >> +#define DEFINE_RECUR(TYPE, SUFFIX) \ > >> + __attribute__((noipa)) \ > >> + static void \ > >> + recur1_##SUFFIX (TYPE *__restrict a, TYPE *__restrict b, \ > >> + const TYPE *__restrict init) \ > >> + { \ > >> + TYPE prev = init[0]; \ > >> + for (int i = 0; i < N; ++i) \ > >> + { \ > >> + b[i] = a[i] - prev; \ > >> + prev = a[i]; \ > >> + } \ > >> + } \ > >> + \ > >> + __attribute__((noipa)) \ > >> + static void \ > >> + recur2_##SUFFIX (TYPE *__restrict a, TYPE *__restrict b, \ > >> + const TYPE *__restrict init) \ > >> + { \ > >> + TYPE prev0 = init[0]; \ > >> + TYPE prev1 = init[0]; \ > >> + for (int i = 0; i + 1 < N; i += 2) \ > >> + { \ > >> + b[i] = a[i] - prev0; \ > >> + prev0 = a[i]; \ > >> + b[i + 1] = a[i + 1] - prev1; \ > >> + prev1 = a[i + 1]; \ > >> + } \ > >> + b[N - 1] = 0; \ > >> + } > >> + > >> +DEFINE_RECUR (uint8_t, u8) > >> +DEFINE_RECUR (uint16_t, u16) > >> +DEFINE_RECUR (uint32_t, u32) > >> +DEFINE_RECUR (uint64_t, u64) > >> + > >> +#define CHECK_RECUR(TYPE, SUFFIX) \ > >> + do \ > >> + { \ > >> + TYPE a[N]; \ > >> + TYPE b[N]; \ > >> + const TYPE init[2] = { (TYPE) 19, (TYPE) 43 }; \ > >> + \ > >> + for (int i = 0; i < N; ++i) \ > >> + a[i] = (TYPE) ((unsigned int) i * (unsigned int) i \ > >> + + 5U * (unsigned int) i + 11U); \ > >> + \ > >> + for (int seed = 0; seed < 2; ++seed) \ > >> + { \ > >> + recur1_##SUFFIX (a, b, &init[seed]); \ > >> + if (b[0] != (TYPE) (a[0] - init[seed])) \ > >> + __builtin_abort (); \ > >> + _Pragma ("GCC novector") \ > >> + for (int i = 1; i < N; ++i) \ > >> + if (b[i] != (TYPE) (a[i] - a[i - 1])) \ > >> + __builtin_abort (); \ > >> + \ > >> + recur2_##SUFFIX (a, b, &init[seed]); \ > >> + if (b[0] != (TYPE) (a[0] - init[seed]) \ > >> + || b[1] != (TYPE) (a[1] - init[seed])) \ > >> + __builtin_abort (); \ > >> + _Pragma ("GCC novector") \ > >> + for (int i = 2; i < N - 1; ++i) \ > >> + if (b[i] != (TYPE) (a[i] - a[i - 2])) \ > >> + __builtin_abort (); \ > >> + if (b[N - 1] != 0) \ > >> + __builtin_abort (); \ > >> + } \ > >> + } \ > >> + while (0) > >> + > >> +int > >> +main (void) > >> +{ > >> + CHECK_RECUR (uint8_t, u8); > >> + CHECK_RECUR (uint16_t, u16); > >> + CHECK_RECUR (uint32_t, u32); > >> + CHECK_RECUR (uint64_t, u64); > >> + > >> + return 0; > >> +} > >> diff --git a/gcc/testsuite/gcc.target/aarch64/sve/perm_splice_1.c > >> b/gcc/testsuite/gcc.target/aarch64/sve/perm_splice_1.c > >> new file mode 100644 > >> index 00000000000..c2a9879008b > >> --- /dev/null > >> +++ b/gcc/testsuite/gcc.target/aarch64/sve/perm_splice_1.c > >> @@ -0,0 +1,63 @@ > >> +/* { dg-do compile } */ > >> +/* { dg-options "-O3 -march=armv8.2-a+sve -mautovec-preference=sve- > >> only" } */ > >> +/* { dg-additional-options "-msve-vector-bits=scalable" } */ > >> +/* { dg-additional-options "-fvect-cost-model=unlimited -fdump-tree- > vect- > >> details" } */ > >> + > >> +typedef __UINT8_TYPE__ uint8_t; > >> +typedef __UINT16_TYPE__ uint16_t; > >> +typedef __UINT32_TYPE__ uint32_t; > >> +typedef __UINT64_TYPE__ uint64_t; > >> + > >> +#define DEF_RECUR1(TYPE, SUFFIX) \ > >> + __attribute__((noipa)) \ > >> + void \ > >> + recur1_##SUFFIX (TYPE *__restrict a, TYPE *__restrict b, \ > >> + const TYPE *__restrict init) \ > >> + { \ > >> + TYPE prev = *init; \ > >> + for (int i = 0; i < 67; ++i) \ > >> + { \ > >> + b[i] = a[i] - prev; \ > >> + prev = a[i]; \ > >> + } \ > >> + } > >> + > >> +#define DEF_RECUR2(TYPE, SUFFIX) \ > >> + __attribute__((noipa)) \ > >> + void \ > >> + recur2_##SUFFIX (TYPE *__restrict a, TYPE *__restrict b, \ > >> + const TYPE *__restrict init) \ > >> + { \ > >> + TYPE prev0 = *init; \ > >> + TYPE prev1 = *init; \ > >> + for (int i = 0; i < 66; i += 2) \ > >> + { \ > >> + b[i] = a[i] - prev0; \ > >> + prev0 = a[i]; \ > >> + b[i + 1] = a[i + 1] - prev1; \ > >> + prev1 = a[i + 1]; \ > >> + } \ > >> + } > >> + > >> +DEF_RECUR1 (uint8_t, u8) > >> +DEF_RECUR1 (uint16_t, u16) > >> +DEF_RECUR1 (uint32_t, u32) > >> +DEF_RECUR1 (uint64_t, u64) > >> + > >> +DEF_RECUR2 (uint8_t, u8) > >> +DEF_RECUR2 (uint16_t, u16) > >> +DEF_RECUR2 (uint32_t, u32) > >> + > >> +/* { dg-final { scan-assembler-times {\tptrue\tp[0-9]+\.b, vl1} 4 } } */ > >> +/* { dg-final { scan-assembler-times {\tptrue\tp[0-9]+\.b, vl2} 1 } } */ > >> +/* { dg-final { scan-assembler-times {\tptrue\tp[0-9]+\.h, vl2} 1 } } */ > >> +/* { dg-final { scan-assembler-times {\tptrue\tp[0-9]+\.s, vl2} 1 } } */ > >> +/* { dg-final { scan-assembler-times {\trev\tp[0-9]+\.b, p[0-9]+\.b} 2 } } */ > >> +/* { dg-final { scan-assembler-times {\trev\tp[0-9]+\.h, p[0-9]+\.h} 2 } } */ > >> +/* { dg-final { scan-assembler-times {\trev\tp[0-9]+\.s, p[0-9]+\.s} 2 } } */ > >> +/* { dg-final { scan-assembler-times {\trev\tp[0-9]+\.d, p[0-9]+\.d} 1 } } */ > >> +/* { dg-final { scan-assembler-times {\tsplice\tz[0-9]+\.b, p[0-9]+, z[0- > 9]+\.b, > >> z[0-9]+\.b} 2 } } */ > >> +/* { dg-final { scan-assembler-times {\tsplice\tz[0-9]+\.h, p[0-9]+, z[0- > 9]+\.h, > >> z[0-9]+\.h} 2 } } */ > >> +/* { dg-final { scan-assembler-times {\tsplice\tz[0-9]+\.s, p[0-9]+, z[0- > 9]+\.s, > >> z[0-9]+\.s} 2 } } */ > >> +/* { dg-final { scan-assembler-times {\tsplice\tz[0-9]+\.d, p[0-9]+, z[0- > 9]+\.d, > >> z[0-9]+\.d} 1 } } */ > >> +/* { dg-final { scan-tree-dump-times "vectorized 1 loops in function" 7 > "vect" > >> } } */ > >> -- > >> 2.50.1 (Apple Git-155) >