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)
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.