Re: [PATCH 2/2] aarch64: use [SU]DOT for the byte to word step of a widening sum

Kyrylo Tkachov <[email protected]>
Newsgroups gmane.comp.gcc.patches
Message-ID <[email protected]>

> On 12 Aug 2026, at 16:45, Tamar Christina <[email protected]> wrote:
> 
>> -----Original Message-----
>> From: [email protected] <[email protected]>
>> Sent: 12 August 2026 12:44
>> To: [email protected]
>> Cc: Tamar Christina <[email protected]>; Wilco Dijkstra
>> <[email protected]>; Kyrylo Tkachov <[email protected]>
>> Subject: [PATCH 2/2] aarch64: use [SU]DOT for the byte to word step of a
>> widening sum
>> 
>> From: Kyrylo Tkachov <[email protected]>
>> 
>> A widening sum from bytes into 64-bit elements spends two [SU]ADDLP
>> getting from bytes to words.  With dot product that step is a single
>> [SU]DOT against a vector of ones, which is what the byte to word expander
>> already does for a 4x reduction.  Each 32-bit element then holds the sum
>> of four input elements, at most 4 * 255 unsigned and within -512 to 508
>> signed, so no sum can overflow.
>> 
>> Move the dot product step into aarch64_expand_reduc_widen_sum, so that
>> any
>> chain that passes through a byte to word step uses it.  The only shape that
>> gains is V2DI <- V16QI, because the other shapes either do not start from
>> bytes or already stop at 32-bit elements:
>> 
>>  V8HI <- V16QI    [SU]ADALP                 word elements would be too wide
>>  V4SI <- V8HI     [SU]ADALP                 not a byte source
>>  V2DI <- V4SI     [SU]ADALP                 not a byte source
>>  V2SI <- V8QI     [SU]DOT                   unchanged
>>  V4SI <- V16QI    [SU]DOT                   unchanged
>>  V2DI <- V8HI     [SU]ADDLP + [SU]ADALP     not a byte source
>>  V2DI <- V16QI    [SU]DOT + [SU]ADALP       new
>> 
>> Without dot product every shape keeps the pairwise chain.
>> 
>> For a sum of unsigned char into long the inner loop changes from
>> 
>> ldr q31, [x2], 16
>> uaddlp v31.8h, v31.16b
>> uaddlp v31.4s, v31.8h
>> uadalp v30.2d, v31.4s
>> 
>> to
>> 
>> ldr q29, [x2], 16
>> movi v31.4s, 0
>> udot v31.4s, v29.16b, v27.16b
>> uadalp v30.2d, v31.4s
>> 
>> with the vector of ones in v27 hoisted out of the loop.  The instruction
>> count is unchanged but the vector work is spread better.
>> A sum of unsigned char into long runs about 24% faster at
>> -march=armv8.2-a+dotprod, and about 20% faster with an L1 resident
>> working
>> set at -mcpu=neoverse-v2, where the vectorizer unrolls the loop by four.
>> 
>> Bootstrapped and tested on aarch64-none-linux-gnu.
>> Ok for trunk?
> 
> This looks good to me, however do we not provide this optab for SVE?
> 
> https://godbolt.org/z/bxas763ze seems like we don't.  I was expecting
> it to do a widening load from b -> h and then use udot from h -> d.
> 
> instead of widening from b -> d, as that's a much lower VF..
> 
> Would you mind checking?
> 

I think we’d need either vect_recog_widen_sum_pattern to try an intermediate halfword type (it currently looks through the promotion to the original byte type and checks only a direct byte-to-doubleword optab)
Or we’d need to implement a VNx2DI <- VNx8QI widening sum expander for SVE.
I can look at either as a follow-up, but I’m guessing they are beyond the scope of this patch.
Thanks,
Kyrill


> Thanks,
> Tamar
> 
>> Thanks,
>> Kyrill
>> 
>> gcc/ChangeLog:
>> 
>> * config/aarch64/aarch64-simd.md
>> (reduc_widen_<su>sum<mode><vsi2qi>3):
>> Expand through aarch64_expand_reduc_widen_sum.
>> * config/aarch64/aarch64.cc (aarch64_expand_reduc_widen_sum):
>> Use
>> [SU]DOT for a step from byte to word elements.
>> 
>> gcc/testsuite/ChangeLog:
>> 
>> * gcc.target/aarch64/widen_sum_pairwise_2.c: Cover every widening
>> sum shape and check the dot product sequences.
>> * gcc.target/aarch64/widen_sum_pairwise_3.c: New test.
>> 
>> Signed-off-by: Kyrylo Tkachov <[email protected]>
>> ---
>> gcc/config/aarch64/aarch64-simd.md            | 16 +---
>> gcc/config/aarch64/aarch64.cc                 | 41 ++++++++-
>> .../gcc.target/aarch64/widen_sum_pairwise_2.c | 71 ++++++++++------
>> .../gcc.target/aarch64/widen_sum_pairwise_3.c | 83
>> +++++++++++++++++++
>> 4 files changed, 170 insertions(+), 41 deletions(-)
>> create mode 100644
>> gcc/testsuite/gcc.target/aarch64/widen_sum_pairwise_3.c
>> 
>> diff --git a/gcc/config/aarch64/aarch64-simd.md
>> b/gcc/config/aarch64/aarch64-simd.md
>> index 43be461ef04..6650cbb5f7e 100644
>> --- a/gcc/config/aarch64/aarch64-simd.md
>> +++ b/gcc/config/aarch64/aarch64-simd.md
>> @@ -5320,10 +5320,7 @@
>>   DONE;
>> })
>> 
>> -;; A widening sum reduction that quarters the lane count.  With dot product
>> -;; this is one [SU]DOT with a vector of ones, i.e. += a becomes += (a * 1).
>> -;; Otherwise it is a pairwise widening add feeding a pairwise widening
>> -;; accumulate.
>> +;; A widening sum reduction that quarters the lane count.
>> (define_expand "reduc_widen_<su>sum<mode><vsi2qi>3"
>>   [(set (match_operand:VS 0 "register_operand")
>> (plus:VS (ANY_EXTEND:VS
>> @@ -5331,15 +5328,8 @@
>>  (match_operand:VS 2 "register_operand")))]
>>   "TARGET_SIMD"
>>   {
>> -    if (TARGET_DOTPROD)
>> -      {
>> - rtx ones = force_reg (<VSI2QI>mode, CONST1_RTX (<VSI2QI>mode));
>> - emit_insn (gen_<su>dot_prod<mode><vsi2qi> (operands[0],
>> operands[1],
>> -    ones, operands[2]));
>> -      }
>> -    else
>> -      aarch64_expand_reduc_widen_sum (operands[0], operands[2],
>> operands[1],
>> -       <CODE>);
>> +    aarch64_expand_reduc_widen_sum (operands[0], operands[2],
>> operands[1],
>> +     <CODE>);
>>     DONE;
>>   }
>> )
>> diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc
>> index c1d57ca3964..fdffb13ad22 100644
>> --- a/gcc/config/aarch64/aarch64.cc
>> +++ b/gcc/config/aarch64/aarch64.cc
>> @@ -26331,8 +26331,9 @@ aarch64_expand_vector_init (rtx target, rtx vals)
>>    Advanced SIMD vector SRC holds an even multiple of the number of lanes
>>    of the accumulator ACC and of the result DEST.  EXTEND_CODE is
>>    SIGN_EXTEND or ZERO_EXTEND and selects the signed or unsigned form.
>> -   Halve the lane count with [SU]ADDLP until a single pairwise step is
>> -   left, then accumulate into ACC with [SU]ADALP.  */
>> +   Quarter the lane count of a vector of bytes with a [SU]DOT against a
>> +   vector of ones where that is available, halve it with [SU]ADDLP until a
>> +   single pairwise step is left, then accumulate into ACC with [SU]ADALP.  */
>> 
>> void
>> aarch64_expand_reduc_widen_sum (rtx dest, rtx acc, rtx src,
>> @@ -26340,7 +26341,41 @@ aarch64_expand_reduc_widen_sum (rtx dest,
>> rtx acc, rtx src,
>> {
>>   unsigned int dest_nunits = GET_MODE_NUNITS (GET_MODE
>> (dest)).to_constant ();
>>   machine_mode mode = GET_MODE (src);
>> -  gcc_assert (GET_MODE_NUNITS (mode).to_constant () % (dest_nunits * 2)
>> == 0);
>> +  unsigned int nunits = GET_MODE_NUNITS (mode).to_constant ();
>> +  gcc_assert (nunits % (dest_nunits * 2) == 0);
>> +
>> +  /* [SU]DOT against a vector of ones turns += a into += (a * 1), which
>> +     sums four bytes into each 32-bit element and so covers two halving
>> +     steps in one operation.  The widest intermediate is 4 * 255, so no
>> +     product sum can overflow.  Only a step from bytes to words qualifies,
>> +     and only if the accumulator is at least that wide.  */
>> +  if (TARGET_DOTPROD
>> +      && GET_MODE_INNER (mode) == QImode
>> +      && nunits >= dest_nunits * 4)
>> +    {
>> +      machine_mode sum_mode
>> + = related_vector_mode (mode, SImode, nunits / 4).require ();
>> +      convert_optab dot = (extend_code == SIGN_EXTEND
>> +    ? sdot_prod_optab : udot_prod_optab);
>> +      insn_code icode = convert_optab_handler (dot, sum_mode, mode);
>> +      rtx ones = force_reg (mode, CONST1_RTX (mode));
>> +
>> +      /* A dot product that already reaches the element width of DEST
>> +  accumulates into ACC itself, otherwise it starts from zero and the
>> +  remaining steps carry its result into ACC.  */
>> +      if (sum_mode == GET_MODE (dest))
>> + {
>> +   emit_insn (GEN_FCN (icode) (dest, src, ones, acc));
>> +   return;
>> + }
>> +
>> +      rtx tmp = gen_reg_rtx (sum_mode);
>> +      emit_insn (GEN_FCN (icode) (tmp, src, ones,
>> +   force_reg (sum_mode,
>> +      CONST0_RTX (sum_mode))));
>> +      src = tmp;
>> +      mode = sum_mode;
>> +    }
>> 
>>   while (GET_MODE_NUNITS (mode).to_constant () > dest_nunits * 2)
>>     {
>> diff --git a/gcc/testsuite/gcc.target/aarch64/widen_sum_pairwise_2.c
>> b/gcc/testsuite/gcc.target/aarch64/widen_sum_pairwise_2.c
>> index 01537deeb9f..9b3ba07637f 100644
>> --- a/gcc/testsuite/gcc.target/aarch64/widen_sum_pairwise_2.c
>> +++ b/gcc/testsuite/gcc.target/aarch64/widen_sum_pairwise_2.c
>> @@ -1,29 +1,50 @@
>> /* { dg-do compile } */
>> /* { dg-options "-O3 -march=armv8.2-a+dotprod -mautovec-
>> preference=asimd-only --param vect-epilogues-nomask=0" } */
>> 
>> -/* With dot product a 4x widening sum stays a single [SU]DOT, while a
>> -   sum into 64-bit elements uses the pairwise widening instructions.  */
>> -
>> -int
>> -sum_u8_i (const unsigned char *a, long n)
>> -{
>> -  int s = 0;
>> -  for (long i = 0; i < n; i++)
>> -    s += a[i];
>> -  return s;
>> -}
>> -
>> -long
>> -sum_u8_l (const unsigned char *a, long n)
>> -{
>> -  long s = 0;
>> -  for (long i = 0; i < n; i++)
>> -    s += a[i];
>> -  return s;
>> -}
>> -
>> -/* { dg-final { scan-assembler-times {\tudot\tv[0-9]+\.4s, v[0-9]+\.16b, v[0-
>> 9]+\.16b\n} 1 } } */
>> -/* { dg-final { scan-assembler-times {\tuaddlp\tv[0-9]+\.8h, v[0-9]+\.16b\n}
>> 1 } } */
>> +/* With dot product every widening sum that passes through a byte to word
>> +   step uses one [SU]DOT for that step.  A step that starts or ends
>> +   somewhere else still uses the pairwise widening instructions.  */
>> +
>> +#define DEF(NAME, ITYPE, OTYPE) \
>> +  OTYPE NAME (const ITYPE *a, long n) \
>> +  { \
>> +    OTYPE s = 0; \
>> +    for (long i = 0; i < n; i++) \
>> +      s += a[i]; \
>> +    return s; \
>> +  }
>> +
>> +/* 2x, no dot product: the result elements are too narrow.  */
>> +DEF (sum_u8_h, unsigned char, unsigned short)
>> +DEF (sum_i8_h, signed char, short)
>> +DEF (sum_u16_i, unsigned short, int)
>> +DEF (sum_i16_i, short, int)
>> +DEF (sum_u32_l, unsigned int, long)
>> +DEF (sum_i32_l, int, long)
>> +
>> +/* 4x from bytes: one dot product.  */
>> +DEF (sum_u8_i, unsigned char, int)
>> +DEF (sum_i8_i, signed char, int)
>> +
>> +/* 4x from halfwords: no dot product for that element size.  */
>> +DEF (sum_u16_l, unsigned short, long)
>> +DEF (sum_i16_l, short, long)
>> +
>> +/* 8x from bytes: a dot product followed by one pairwise accumulate.  */
>> +DEF (sum_u8_l, unsigned char, long)
>> +DEF (sum_i8_l, signed char, long)
>> +
>> +/* { dg-final { scan-assembler-times {\tudot\tv[0-9]+\.4s, v[0-9]+\.16b, v[0-
>> 9]+\.16b\n} 2 } } */
>> +/* { dg-final { scan-assembler-times {\tsdot\tv[0-9]+\.4s, v[0-9]+\.16b, v[0-
>> 9]+\.16b\n} 2 } } */
>> +/* { dg-final { scan-assembler-times {\tuadalp\tv[0-9]+\.8h, v[0-9]+\.16b\n}
>> 1 } } */
>> +/* { dg-final { scan-assembler-times {\tsadalp\tv[0-9]+\.8h, v[0-9]+\.16b\n}
>> 1 } } */
>> +/* { dg-final { scan-assembler-times {\tuadalp\tv[0-9]+\.4s, v[0-9]+\.8h\n} 1
>> } } */
>> +/* { dg-final { scan-assembler-times {\tsadalp\tv[0-9]+\.4s, v[0-9]+\.8h\n} 1
>> } } */
>> /* { dg-final { scan-assembler-times {\tuaddlp\tv[0-9]+\.4s, v[0-9]+\.8h\n} 1
>> } } */
>> -/* { dg-final { scan-assembler-times {\tuadalp\tv[0-9]+\.2d, v[0-9]+\.4s\n} 1
>> } } */
>> -/* { dg-final { scan-assembler-not {\tuaddw2?\t} } } */
>> +/* { dg-final { scan-assembler-times {\tsaddlp\tv[0-9]+\.4s, v[0-9]+\.8h\n} 1
>> } } */
>> +/* { dg-final { scan-assembler-times {\tuadalp\tv[0-9]+\.2d, v[0-9]+\.4s\n} 3
>> } } */
>> +/* { dg-final { scan-assembler-times {\tsadalp\tv[0-9]+\.2d, v[0-9]+\.4s\n} 3
>> } } */
>> +
>> +/* The byte to halfword step is what the dot product replaces.  */
>> +/* { dg-final { scan-assembler-not {\t[su]addlp\tv[0-9]+\.8h, v[0-
>> 9]+\.16b\n} } } */
>> +/* { dg-final { scan-assembler-not {\t[su]addw2?\t} } } */
>> diff --git a/gcc/testsuite/gcc.target/aarch64/widen_sum_pairwise_3.c
>> b/gcc/testsuite/gcc.target/aarch64/widen_sum_pairwise_3.c
>> new file mode 100644
>> index 00000000000..d2eb8152722
>> --- /dev/null
>> +++ b/gcc/testsuite/gcc.target/aarch64/widen_sum_pairwise_3.c
>> @@ -0,0 +1,83 @@
>> +/* { dg-do run } */
>> +/* { dg-require-effective-target arm_v8_2a_dotprod_neon_hw } */
>> +/* { dg-options "-O3 -march=armv8.2-a+dotprod -mautovec-
>> preference=asimd-only" } */
>> +
>> +/* Both expansions of a widening sum reduction, with and without dot
>> +   product, must agree with a scalar sum for every narrow to wide type
>> +   pair.  The accumulators are unsigned so that overflow wraps.  */
>> +
>> +#define TYPES(X) \
>> +  X (u8_h,  unsigned char,  unsigned short) \
>> +  X (i8_h,  signed char,    unsigned short) \
>> +  X (u8_i,  unsigned char,  unsigned int) \
>> +  X (i8_i,  signed char,    unsigned int) \
>> +  X (u8_l,  unsigned char,  unsigned long) \
>> +  X (i8_l,  signed char,    unsigned long) \
>> +  X (u16_i, unsigned short, unsigned int) \
>> +  X (i16_i, short,          unsigned int) \
>> +  X (u16_l, unsigned short, unsigned long) \
>> +  X (i16_l, short,          unsigned long) \
>> +  X (u32_l, unsigned int,   unsigned long) \
>> +  X (i32_l, int,            unsigned long)
>> +
>> +#define SUM(PREFIX, NAME, ITYPE, OTYPE) \
>> +  __attribute__ ((noipa)) \
>> +  OTYPE PREFIX##_##NAME (const ITYPE *a, int n) \
>> +  { \
>> +    OTYPE s = 0; \
>> +    for (int i = 0; i < n; i++) \
>> +      s += a[i]; \
>> +    return s; \
>> +  }
>> +
>> +#define DOT(NAME, ITYPE, OTYPE) SUM (dot, NAME, ITYPE, OTYPE)
>> +#define NODOT(NAME, ITYPE, OTYPE) SUM (nodot, NAME, ITYPE, OTYPE)
>> +
>> +/* A volatile accumulator keeps this loop scalar.  */
>> +#define REF(NAME, ITYPE, OTYPE) \
>> +  __attribute__ ((noipa)) \
>> +  OTYPE ref_##NAME (const ITYPE *a, int n) \
>> +  { \
>> +    volatile OTYPE s = 0; \
>> +    for (int i = 0; i < n; i++) \
>> +      s = s + a[i]; \
>> +    return s; \
>> +  }
>> +
>> +TYPES (DOT)
>> +TYPES (REF)
>> +
>> +#pragma GCC push_options
>> +#pragma GCC target ("+nodotprod")
>> +TYPES (NODOT)
>> +#pragma GCC pop_options
>> +
>> +#define BYTES 8192
>> +static unsigned char buf[BYTES] __attribute__ ((aligned (64)));
>> +
>> +#define CHECK(NAME, ITYPE, OTYPE) \
>> +  { \
>> +    const ITYPE *p = (const ITYPE *) (buf + off); \
>> +    OTYPE want = ref_##NAME (p, n); \
>> +    if (dot_##NAME (p, n) != want || nodot_##NAME (p, n) != want) \
>> +      __builtin_abort (); \
>> +  }
>> +
>> +int
>> +main (void)
>> +{
>> +  unsigned long x = 1;
>> +  for (int i = 0; i < BYTES; i++)
>> +    {
>> +      x = x * 6364136223846793005UL + 1442695040888963407UL;
>> +      buf[i] = x >> 40;
>> +    }
>> +
>> +  for (int off = 0; off < 8; off += 4)
>> +    for (int n = 0; n <= 260; n++)
>> +      {
>> + TYPES (CHECK)
>> +      }
>> +
>> +  return 0;
>> +}
>> --
>> 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.