Re: [PATCH] vect: Recognise high-part multiply when the wide type has no vector form
Kyrylo Tkachov <[email protected]> Thu, 6 Aug 2026 09:02:01 +0000
| Newsgroups | gmane.comp.gcc.patches |
|---|---|
| Message-ID | <[email protected]> |
> On 6 Aug 2026, at 10:36, Richard Biener <[email protected]> wrote: > > On Thu, 6 Aug 2026, [email protected] wrote: > >> From: Kyrylo Tkachov <[email protected]> >> >> vect_recog_mulhs_pattern turns (a * b) >> N into IFN_MULH and casts the >> result back to the type of the shift, which the over-widening machinery >> then drops. Building that cast needs a vector type for the wide type, and >> the pattern gives up when there is none. >> >> For a 64-bit high-part multiply the wide type is 128 bits, and no target >> has a vector of 128-bit integers, so on AArch64 the pattern never fires >> for scalable SVE and the loop stays scalar. It does fire under >> -msve-vector-bits=128, where the prevailing vector size is 128 bits and a >> one-element TImode vector exists, which is what makes the failure specific >> to variable-length vectors. >> >> The pattern has already established that the users of the result need at >> most min_output_precision bits and that NEW_TYPE holds at least that many, >> so when the wide vector type is unavailable we can simply leave the result >> at NEW_TYPE precision and skip the cast. >> >> for (int i = 0; i < n; i++) >> d[i] = (uint64_t) (((unsigned __int128) a[i] * b[i]) >> 64); >> >> on -march=armv8.2-a+sve2 now gives >> >> ld1d z28.d, p7/z, [x1, x6, lsl 3] >> ld1d z27.d, p7/z, [x2, x6, lsl 3] >> umulh z28.d, z28.d, z27.d >> st1d z28.d, p7, [x0, x6, lsl 3] >> >> instead of a scalar loop. >> >> Bootstrapped and regression-tested on aarch64-unknown-linux-gnu and >> x86_64-linux. >> >> Ok for trunk? >> Thanks, >> Kyrill >> >> gcc/ChangeLog: >> >> * tree-vect-patterns.cc (vect_recog_mulhs_pattern): Keep the >> pattern result at NEW_TYPE precision when LHS_TYPE has no vector >> type. >> >> gcc/testsuite/ChangeLog: >> >> * gcc.target/aarch64/sve/mul_highpart_scalable_1.c: New test. >> * gcc.target/aarch64/sve/mul_highpart_scalable_run.c: New test. >> >> Signed-off-by: Kyrylo Tkachov <[email protected]> >> --- >> .../aarch64/sve/mul_highpart_scalable_1.c | 23 ++++++++++ >> .../aarch64/sve/mul_highpart_scalable_run.c | 42 +++++++++++++++++++ >> gcc/tree-vect-patterns.cc | 17 ++++++-- >> 3 files changed, 78 insertions(+), 4 deletions(-) >> create mode 100644 gcc/testsuite/gcc.target/aarch64/sve/mul_highpart_scalable_1.c >> create mode 100644 gcc/testsuite/gcc.target/aarch64/sve/mul_highpart_scalable_run.c >> >> diff --git a/gcc/testsuite/gcc.target/aarch64/sve/mul_highpart_scalable_1.c b/gcc/testsuite/gcc.target/aarch64/sve/mul_highpart_scalable_1.c >> new file mode 100644 >> index 00000000000..f61d235b7a8 >> --- /dev/null >> +++ b/gcc/testsuite/gcc.target/aarch64/sve/mul_highpart_scalable_1.c >> @@ -0,0 +1,23 @@ >> +/* A 64-bit high-part multiply has a 128-bit product type, and there is no >> + scalable vector of 128-bit integers. Check that the vectoriser recognises >> + the high-part multiply anyway. */ >> +/* { dg-do assemble { target aarch64_asm_sve2_ok } } */ >> +/* { dg-options "-O2 -ftree-vectorize -march=armv8.2-a+sve2 -mautovec-preference=sve-only -msve-vector-bits=scalable --save-temps" } */ >> + >> +#include <stdint.h> >> + >> +#define DEF_LOOP(TYPE, WIDE) \ >> + void __attribute__ ((noipa)) \ >> + mulh_##TYPE (TYPE *restrict dst, TYPE *restrict a, \ >> + TYPE *restrict b, int count) \ >> + { \ >> + for (int i = 0; i < count; ++i) \ >> + dst[i] = (TYPE) (((WIDE) a[i] * b[i]) >> 64); \ >> + } >> + >> +DEF_LOOP (int64_t, __int128) >> +DEF_LOOP (uint64_t, unsigned __int128) >> + >> +/* { dg-final { scan-assembler-times {\tsmulh\tz[0-9]+\.d, z[0-9]+\.d, z[0-9]+\.d\n} 1 } } */ >> +/* { dg-final { scan-assembler-times {\tumulh\tz[0-9]+\.d, z[0-9]+\.d, z[0-9]+\.d\n} 1 } } */ >> +/* { dg-final { scan-assembler {\twhilelo\t} } } */ >> diff --git a/gcc/testsuite/gcc.target/aarch64/sve/mul_highpart_scalable_run.c b/gcc/testsuite/gcc.target/aarch64/sve/mul_highpart_scalable_run.c >> new file mode 100644 >> index 00000000000..d856302d7a1 >> --- /dev/null >> +++ b/gcc/testsuite/gcc.target/aarch64/sve/mul_highpart_scalable_run.c >> @@ -0,0 +1,42 @@ >> +/* { dg-do run { target aarch64_sve_hw } } */ >> +/* { dg-options "-O2 -ftree-vectorize -march=armv8.2-a+sve2 -mautovec-preference=sve-only -msve-vector-bits=scalable" } */ >> + >> +#include "mul_highpart_scalable_1.c" >> + >> +#define N 77 >> + >> +static int64_t sa[N], sb[N], sd[N]; >> +static uint64_t ua[N], ub[N], ud[N]; >> + >> +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; >> + } >> + 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] = ~0ULL; >> + ua[1] = ~0ULL; ub[1] = ~0ULL; >> + ua[2] = 1ULL << 63; ub[2] = 1ULL << 63; >> + >> + mulh_int64_t (sd, sa, sb, N); >> + mulh_uint64_t (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/tree-vect-patterns.cc b/gcc/tree-vect-patterns.cc >> index c57e215be57..9934ab8306a 100644 >> --- a/gcc/tree-vect-patterns.cc >> +++ b/gcc/tree-vect-patterns.cc >> @@ -3412,11 +3412,20 @@ vect_recog_mulhs_pattern (vec_info *vinfo, >> (ifn, new_vectype, OPTIMIZE_FOR_SPEED)) >> return NULL; >> >> - /* The IR requires a valid vector type for the cast result, even though >> - it's likely to be discarded. */ >> + /* The IFN result is cast back to LHS_TYPE, a cast that the over-widening >> + machinery then drops. Some targets have no vector form of LHS_TYPE at >> + all: a 64-bit high-part multiply has a 128-bit LHS_TYPE, and there is no >> + vector of 128-bit integers. Keep the pattern result at NEW_TYPE >> + precision in that case. This is safe because the check at the top of the >> + function proved that the users of the result need at most >> + TARGET_PRECISION bits, and NEW_TYPE holds at least that many. */ > > Huh, but this is not the type of the IFN, neither for scalar nor > for the vector case? > > I'll note that anticipating removal of a cast is OK, just make sure > to not specify a vector type for a pattern stmt that doesn't have one > (it's technically not necessary, we'll re-recognize one or refuse to > vectorize if there is none later). I’ve sent an updated patch (I hard reworked it earlier to add a generic test case as per Andrea’s feedback) with a reworked comment. Thanks, Kyrill > >> + tree out_type = lhs_type; >> *type_out = get_vectype_for_scalar_type (vinfo, lhs_type); >> if (!*type_out) >> - return NULL; >> + { >> + out_type = new_type; >> + *type_out = new_vectype; >> + } >> >> /* Generate the IFN_MULHRS call. */ >> tree new_var = vect_recog_temp_ssa_var (new_type, NULL); >> @@ -3432,7 +3441,7 @@ vect_recog_mulhs_pattern (vec_info *vinfo, >> dump_printf_loc (MSG_NOTE, vect_location, >> "created pattern stmt: %G", (gimple *) mulhrs_stmt); >> >> - return vect_convert_output (vinfo, last_stmt_info, lhs_type, >> + return vect_convert_output (vinfo, last_stmt_info, out_type, >> mulhrs_stmt, new_vectype); >> } >> >> > > -- > Richard Biener <[email protected]> > SUSE Software Solutions Germany GmbH, > Frankenstrasse 146, 90461 Nuernberg, Germany; > GF: Jochen Jaser, Andrew McDonald, Abhinav Puri; (HRB 36809, AG Nuernberg)