RE: [PATCH] [v2] aarch64: Implement vector DImode high-part multiply using SVE

Tamar Christina <[email protected]> Thu, 6 Aug 2026 09:26:57 +0000
Newsgroups gmane.comp.gcc.patches
Message-ID <VI0PR08MB103923E491F79086598497A7BFFD22@VI0PR08MB10392.eurprd08.prod.outlook.com>
> -----Original Message-----
> From: [email protected] <[email protected]>
> Sent: 06 August 2026 10:03
> To: [email protected]
> Cc: Tamar Christina <[email protected]>;
> [email protected]; Kyrylo Tkachov <[email protected]>
> Subject: [PATCH] [v2] aarch64: Implement vector DImode high-part multiply
> using SVE
> 
> From: Kyrylo Tkachov <[email protected]>
> 
> Advanced SIMD has no 64x64 high-part multiply, so the vectorizer
> currently cannot use its modes for loops such as:
> 
>   for (int i = 0; i < n; i++)
>     d[i] = (uint64_t) (((unsigned __int128) a[i] * b[i]) >> 64);
> 
> SVE has UMULH and SMULH for D elements and the Z registers overlap the V
> registers, so the Advanced SIMD optab can be implemented on top of the SVE
> instruction.  Extend the high-part multiply patterns from SVE_I to
> SVE_I_SIMD_DI, exactly as mul<mode>3 already does for vector DImode
> multiplication, and print the operands with %Z so that the V2DI form uses
> the Z register names.
> 
> For the loop above on a 128-bit SVE implementation we now generate:
> 
> 	ldr	q31, [x1, x4]
> 	ldr	q30, [x2, x4]
> 	umulh	z30.d, z31.d, z30.d
> 	str	q30, [x0, x4]
> 
> rather than:
> 
> 	ldr	x5, [x1, x4]
> 	ldr	x6, [x2, x4]
> 	umulh	x5, x5, x6
> 	str	x5, [x0, x4]
> 
> I don't think people often run the testuite with -march=armv8.2-a+sve
> -mautovec-preference=asimd-only and I don't know how clean it is, so

Not very wrt to codegen.

> I've added the runtime test generically to torture and included it in
> gcc.target/aarch64/ with the aarch64 flags added on top.
> 
> Bootstrapped and regression-tested on aarch64-unknown-linux-gnu.
> Ok for trunk?

Yeah, LGTM.

Thanks,
Tamar
> Thanks,
> Kyrill
> 
> gcc/ChangeLog:
> 
> 	* config/aarch64/aarch64-sve.md (<su>mul<mode>3_highpart):
> Extend
> 	from SVE_I to SVE_I_SIMD_DI.
> 	(@aarch64_pred_<optab><mode>): Likewise for the MUL_HIGHPART
> form,
> 	and print the data operands with %Z.
> 	* config/aarch64/aarch64-sve2.md
> (@aarch64_sve2_<optab><mode>):
> 	Likewise.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* gcc.target/aarch64/sve/mul_highpart_v2di_1.c: New test.
> 	* gcc.target/aarch64/sve/mul_highpart_v2di_2.c: New test.
> 	* gcc.target/aarch64/sve/mul_highpart_v2di_3.c: New test.
> 	* gcc.target/aarch64/sve/mul_highpart_v2di_run.c: New test.
> 	* gcc.dg/torture/mul-highpart-1.c: New test.
> 
> Signed-off-by: Kyrylo Tkachov <[email protected]>
> ---
>  gcc/config/aarch64/aarch64-sve.md             | 29 +++++----
>  gcc/config/aarch64/aarch64-sve2.md            | 13 ++--
>  gcc/testsuite/gcc.dg/torture/mul-highpart-1.c | 62 +++++++++++++++++++
>  .../aarch64/sve/mul_highpart_v2di_1.c         | 37 +++++++++++
>  .../aarch64/sve/mul_highpart_v2di_2.c         | 35 +++++++++++
>  .../aarch64/sve/mul_highpart_v2di_3.c         | 26 ++++++++
>  .../aarch64/sve/mul_highpart_v2di_run.c       |  7 +++
>  7 files changed, 190 insertions(+), 19 deletions(-)
>  create mode 100644 gcc/testsuite/gcc.dg/torture/mul-highpart-1.c
>  create mode 100644
> gcc/testsuite/gcc.target/aarch64/sve/mul_highpart_v2di_1.c
>  create mode 100644
> gcc/testsuite/gcc.target/aarch64/sve/mul_highpart_v2di_2.c
>  create mode 100644
> gcc/testsuite/gcc.target/aarch64/sve/mul_highpart_v2di_3.c
>  create mode 100644
> gcc/testsuite/gcc.target/aarch64/sve/mul_highpart_v2di_run.c
> 
> diff --git a/gcc/config/aarch64/aarch64-sve.md
> b/gcc/config/aarch64/aarch64-sve.md
> index f665417dba4..5f9a19c42e9 100644
> --- a/gcc/config/aarch64/aarch64-sve.md
> +++ b/gcc/config/aarch64/aarch64-sve.md
> @@ -4881,13 +4881,16 @@
>  ;; -------------------------------------------------------------------------
> 
>  ;; Unpredicated highpart multiplication.
> +;; Advanced SIMD has no vector DImode high-part multiply, but SVE does.
> +;; Make use of the overlap between Z and V registers to implement the V2DI
> +;; optab for TARGET_SVE, in the same way as the mul<mode>3 expander
> above.
>  (define_expand "<su>mul<mode>3_highpart"
> -  [(set (match_operand:SVE_I 0 "register_operand")
> -	(unspec:SVE_I
> +  [(set (match_operand:SVE_I_SIMD_DI 0 "register_operand")
> +	(unspec:SVE_I_SIMD_DI
>  	  [(match_dup 3)
> -	   (unspec:SVE_I
> -	     [(match_operand:SVE_I 1 "register_operand")
> -	      (match_operand:SVE_I 2 "register_operand")]
> +	   (unspec:SVE_I_SIMD_DI
> +	     [(match_operand:SVE_I_SIMD_DI 1 "register_operand")
> +	      (match_operand:SVE_I_SIMD_DI 2 "register_operand")]
>  	     MUL_HIGHPART)]
>  	  UNSPEC_PRED_X))]
>    "TARGET_SVE"
> @@ -4898,22 +4901,22 @@
> 
>  ;; Predicated highpart multiplication.
>  (define_insn_and_split "@aarch64_pred_<optab><mode>"
> -  [(set (match_operand:SVE_I 0 "register_operand")
> -	(unspec:SVE_I
> +  [(set (match_operand:SVE_I_SIMD_DI 0 "register_operand")
> +	(unspec:SVE_I_SIMD_DI
>  	  [(match_operand:<VPRED> 1 "register_operand")
> -	   (unspec:SVE_I
> -	     [(match_operand:SVE_I 2 "register_operand")
> -	      (match_operand:SVE_I 3 "register_operand")]
> +	   (unspec:SVE_I_SIMD_DI
> +	     [(match_operand:SVE_I_SIMD_DI 2 "register_operand")
> +	      (match_operand:SVE_I_SIMD_DI 3 "register_operand")]
>  	     MUL_HIGHPART)]
>  	  UNSPEC_PRED_X))]
>    "TARGET_SVE"
>    {@ [ cons: =0 , 1   , %2 , 3 ; attrs: movprfx ]
> -     [ w        , Upl , 0  , w ; *              ] <su>mulh\t%0.<Vetype>, %1/m,
> %0.<Vetype>, %3.<Vetype>
> -     [ ?&w      , Upl , w  , w ; yes            ] movprfx\t%0,
> %2\;<su>mulh\t%0.<Vetype>, %1/m, %0.<Vetype>, %3.<Vetype>
> +     [ w        , Upl , 0  , w ; *              ] <su>mulh\t%Z0.<Vetype>, %1/m,
> %Z0.<Vetype>, %Z3.<Vetype>
> +     [ ?&w      , Upl , w  , w ; yes            ] movprfx\t%Z0,
> %Z2\;<su>mulh\t%Z0.<Vetype>, %1/m, %Z0.<Vetype>, %Z3.<Vetype>
>    }
>    "TARGET_SVE2"
>    [(set (match_dup 0)
> -	(unspec:SVE_I
> +	(unspec:SVE_I_SIMD_DI
>  	  [(match_dup 2)
>  	   (match_dup 3)]
>  	  MUL_HIGHPART))]
> diff --git a/gcc/config/aarch64/aarch64-sve2.md
> b/gcc/config/aarch64/aarch64-sve2.md
> index a8bee6b6dc7..1cc0cefbee7 100644
> --- a/gcc/config/aarch64/aarch64-sve2.md
> +++ b/gcc/config/aarch64/aarch64-sve2.md
> @@ -983,15 +983,16 @@
>  ;; ---- [INT] Unpredicated high-part multiplication
>  ;; -------------------------------------------------------------------------
> 
> -;; SVE2 unpredicated SMULH/UMULH.
> +;; SVE2 unpredicated SMULH/UMULH.  V2DI is included so that the
> Advanced
> +;; SIMD high-part multiply optabs can be implemented on top of SVE.
>  (define_insn "@aarch64_sve2_<optab><mode>"
> -  [(set (match_operand:SVE_I 0 "register_operand" "=w")
> -	(unspec:SVE_I
> -	  [(match_operand:SVE_I 1 "register_operand" "w")
> -	   (match_operand:SVE_I 2 "register_operand" "w")]
> +  [(set (match_operand:SVE_I_SIMD_DI 0 "register_operand" "=w")
> +	(unspec:SVE_I_SIMD_DI
> +	  [(match_operand:SVE_I_SIMD_DI 1 "register_operand" "w")
> +	   (match_operand:SVE_I_SIMD_DI 2 "register_operand" "w")]
>  	  MUL_HIGHPART))]
>    "TARGET_SVE2"
> -  "<su>mulh\t%0.<Vetype>, %1.<Vetype>, %2.<Vetype>"
> +  "<su>mulh\t%Z0.<Vetype>, %Z1.<Vetype>, %Z2.<Vetype>"
>    [(set_attr "sve_type" "sve_int_mul")]
>  )
> 
> diff --git a/gcc/testsuite/gcc.dg/torture/mul-highpart-1.c
> b/gcc/testsuite/gcc.dg/torture/mul-highpart-1.c
> new file mode 100644
> index 00000000000..0c6b7812863
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/torture/mul-highpart-1.c
> @@ -0,0 +1,62 @@
> +/* Check that a 64-bit high-part multiply gives the same answer whether or
> not
> +   it is vectorized.  */
> +/* { dg-do run } */
> +/* { dg-require-effective-target int128 } */
> +/* { dg-additional-options "-ftree-vectorize" } */
> +
> +#include <stdint.h>
> +
> +#define N 77
> +
> +static int64_t sa[N], sb[N], sd[N];
> +static uint64_t ua[N], ub[N], ud[N];
> +
> +void __attribute__ ((noipa))
> +mulh_s64 (int64_t *restrict dst, int64_t *restrict a, int64_t *restrict b,
> +	  int count)
> +{
> +  for (int i = 0; i < count; ++i)
> +    dst[i] = (int64_t) (((__int128) a[i] * b[i]) >> 64);
> +}
> +
> +void __attribute__ ((noipa))
> +mulh_u64 (uint64_t *restrict dst, uint64_t *restrict a, uint64_t *restrict b,
> +	  int count)
> +{
> +  for (int i = 0; i < count; ++i)
> +    dst[i] = (uint64_t) (((unsigned __int128) a[i] * b[i]) >> 64);
> +}
> +
> +int
> +main (void)
> +{
> +  uint64_t s = 0x243f6a8885a308d3ULL;
> +  for (int i = 0; i < N; ++i)
> +    {
> +      s ^= s << 13; s ^= s >> 7; s ^= s << 17;
> +      sa[i] = (int64_t) s;
> +      ua[i] = s;
> +      s ^= s << 13; s ^= s >> 7; s ^= s << 17;
> +      sb[i] = (int64_t) s;
> +      ub[i] = s;
> +    }
> +  /* Boundary values.  */
> +  sa[0] = INT64_MIN; sb[0] = INT64_MIN;
> +  sa[1] = INT64_MIN; sb[1] = -1;
> +  sa[2] = -1; sb[2] = -1;
> +  ua[0] = 0; ub[0] = ~(uint64_t) 0;
> +  ua[1] = ~(uint64_t) 0; ub[1] = ~(uint64_t) 0;
> +  ua[2] = (uint64_t) 1 << 63; ub[2] = (uint64_t) 1 << 63;
> +
> +  mulh_s64 (sd, sa, sb, N);
> +  mulh_u64 (ud, ua, ub, N);
> +
> +  for (int i = 0; i < N; ++i)
> +    {
> +      if (sd[i] != (int64_t) (((__int128) sa[i] * sb[i]) >> 64))
> +	__builtin_abort ();
> +      if (ud[i] != (uint64_t) (((unsigned __int128) ua[i] * ub[i]) >> 64))
> +	__builtin_abort ();
> +    }
> +  return 0;
> +}
> diff --git a/gcc/testsuite/gcc.target/aarch64/sve/mul_highpart_v2di_1.c
> b/gcc/testsuite/gcc.target/aarch64/sve/mul_highpart_v2di_1.c
> new file mode 100644
> index 00000000000..2544e4c6b79
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/sve/mul_highpart_v2di_1.c
> @@ -0,0 +1,37 @@
> +/* Advanced SIMD has no 64-bit high-part multiply, but SVE does, and the
> two
> +   register files overlap.  Check that the SVE instruction is used for a
> +   128-bit vector high-part multiply when the vectoriser is restricted to
> +   Advanced SIMD modes.  */
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -ftree-vectorize -march=armv8.2-a+sve -mautovec-
> preference=asimd-only" } */
> +/* { dg-final { check-function-bodies "**" "" } } */
> +
> +#include <stdint.h>
> +
> +/*
> +** mulh_s64:
> +** ...
> +**	smulh	z[0-9]+\.d, p[0-7]/m, z[0-9]+\.d, z[0-9]+\.d
> +** ...
> +*/
> +void __attribute__ ((noipa))
> +mulh_s64 (int64_t *restrict dst, int64_t *restrict a, int64_t *restrict b,
> +	  int count)
> +{
> +  for (int i = 0; i < count; ++i)
> +    dst[i] = (int64_t) (((__int128) a[i] * b[i]) >> 64);
> +}
> +
> +/*
> +** mulh_u64:
> +** ...
> +**	umulh	z[0-9]+\.d, p[0-7]/m, z[0-9]+\.d, z[0-9]+\.d
> +** ...
> +*/
> +void __attribute__ ((noipa))
> +mulh_u64 (uint64_t *restrict dst, uint64_t *restrict a, uint64_t *restrict b,
> +	  int count)
> +{
> +  for (int i = 0; i < count; ++i)
> +    dst[i] = (uint64_t) (((unsigned __int128) a[i] * b[i]) >> 64);
> +}
> diff --git a/gcc/testsuite/gcc.target/aarch64/sve/mul_highpart_v2di_2.c
> b/gcc/testsuite/gcc.target/aarch64/sve/mul_highpart_v2di_2.c
> new file mode 100644
> index 00000000000..3f3989fa901
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/sve/mul_highpart_v2di_2.c
> @@ -0,0 +1,35 @@
> +/* Same as mul_highpart_v2di_1.c, but for SVE2, where the high-part
> multiply
> +   has an unpredicated form.  */
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -ftree-vectorize -march=armv8.2-a+sve2 -mautovec-
> preference=asimd-only" } */
> +/* { dg-final { check-function-bodies "**" "" } } */
> +
> +#include <stdint.h>
> +
> +/*
> +** mulh_s64:
> +** ...
> +**	smulh	z[0-9]+\.d, z[0-9]+\.d, z[0-9]+\.d
> +** ...
> +*/
> +void __attribute__ ((noipa))
> +mulh_s64 (int64_t *restrict dst, int64_t *restrict a, int64_t *restrict b,
> +	  int count)
> +{
> +  for (int i = 0; i < count; ++i)
> +    dst[i] = (int64_t) (((__int128) a[i] * b[i]) >> 64);
> +}
> +
> +/*
> +** mulh_u64:
> +** ...
> +**	umulh	z[0-9]+\.d, z[0-9]+\.d, z[0-9]+\.d
> +** ...
> +*/
> +void __attribute__ ((noipa))
> +mulh_u64 (uint64_t *restrict dst, uint64_t *restrict a, uint64_t *restrict b,
> +	  int count)
> +{
> +  for (int i = 0; i < count; ++i)
> +    dst[i] = (uint64_t) (((unsigned __int128) a[i] * b[i]) >> 64);
> +}
> diff --git a/gcc/testsuite/gcc.target/aarch64/sve/mul_highpart_v2di_3.c
> b/gcc/testsuite/gcc.target/aarch64/sve/mul_highpart_v2di_3.c
> new file mode 100644
> index 00000000000..b459bbc27cc
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/sve/mul_highpart_v2di_3.c
> @@ -0,0 +1,26 @@
> +/* The Shoup modular multiply used by lattice cryptography.  The high-part
> +   multiply is the only operation Advanced SIMD lacks, so without a 128-bit
> +   vector form of it the whole loop stays scalar.  */
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -ftree-vectorize -march=armv8.2-a+sve2 -mautovec-
> preference=asimd-only" } */
> +/* { dg-final { check-function-bodies "**" "" } } */
> +
> +#include <stdint.h>
> +
> +/*
> +** mul_mod:
> +** ...
> +**	umulh	z[0-9]+\.d, z[0-9]+\.d, z[0-9]+\.d
> +** ...
> +*/
> +void __attribute__ ((noipa))
> +mul_mod (uint64_t *restrict dst, const uint64_t *restrict src,
> +	 uint64_t operand, uint64_t quotient, uint64_t modulus, int count)
> +{
> +  for (int i = 0; i < count; ++i)
> +    {
> +      uint64_t hi = (uint64_t) (((unsigned __int128) src[i] * quotient) >> 64);
> +      uint64_t t = operand * src[i] - hi * modulus;
> +      dst[i] = t >= modulus ? t - modulus : t;
> +    }
> +}
> diff --git a/gcc/testsuite/gcc.target/aarch64/sve/mul_highpart_v2di_run.c
> b/gcc/testsuite/gcc.target/aarch64/sve/mul_highpart_v2di_run.c
> new file mode 100644
> index 00000000000..a77f2ef2207
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/sve/mul_highpart_v2di_run.c
> @@ -0,0 +1,7 @@
> +/* Run the generic high-part multiply correctness test with the vectoriser
> +   restricted to Advanced SIMD modes, so that the V2DI patterns are the ones
> +   being exercised.  */
> +/* { dg-do run { target aarch64_sve_hw } } */
> +/* { dg-options "-O2 -ftree-vectorize -march=armv8.2-a+sve -mautovec-
> preference=asimd-only" } */
> +
> +#include "../../../gcc.dg/torture/mul-highpart-1.c"
> --
> 2.50.1 (Apple Git-155)