> On 10 Sep 2026, at 19:51, Dominic P <[email protected]> wrote:
>
> RSHRN is only reachable from the ACLE intrinsics. The nearest
> recognisers, *aarch64_<shrn_op>rshrn_n<mode>_insn and its high-half
> forms, do the rounding add and the shift in <V2XWIDE> over an extended
> operand -- the shape the intrinsic expanders build -- so C that writes
> (x + (1 << (n - 1))) >> n and narrows the result, which does both at the
> source width, gets movi + add + shrn/shrn2 instead. Widening those
> recognisers would need V16HI, V8SI and V4DI arithmetic, which the
> vectoriser does not produce, so add source-width companions.
>
> With W the source element width and w = W/2 the destination width, both
> forms keep bits [n+w-1:n] of x + 2^(n-1) and differ only above bit W-1,
> so they agree for n <= W/2; aarch64_int_rnd_operand and
> aarch64_simd_shift_imm_vec_<vn_mode> each cap n there. The saturating
> members of the family (SQRSHRN, SQRSHRUN, UQRSHRN) read the full shifted
> value and are deliberately left alone: vqrshrun_n_s16 (32767, 6) is 255
> where the source-width C form is 0.
>
> The intrinsics gain too. A shift equal to the destination element width
> expands to RADDHN against a shareable zero, and combine now folds that
> zero away into a single RSHRN. advsimd-intrinsics/shrn-1.c therefore
> loses its movi and drops from six instructions to five, and shrn-2.c,
> shrn-3.c and shrn-4.c drop from four to three; their raddhn expectations
> are updated here to the shorter rshrn sequence. Combine does not run at
> -O0, so the three that lacked it gain the -O0 skip shrn-1.c carries.
>
> x265 dct.cpp's partialButterfly output stage, reduced to
> for (int i = 0; i < 64; i++) d[i] = (int16_t) ((s[i] + 128) >> 8);
> goes from 46 to 38 instructions at -O3 -mcpu=cortex-a76; ffmpeg's h264
> chroma stage, (uint8_t) ((s[i] + 32) >> 6) over uint16_t, from 88 to 85.
>
> Cross-tested aarch64-linux-gnu under qemu-aarch64 (aarch64.exp, simd.exp,
> advsimd-intrinsics.exp, vect.exp, like for like against the same tree
> unpatched) and bootstrapped on x86_64-pc-linux-gnu.
>
> Assisted-by: Claude Opus 5 (Anthropic)
>
> gcc/ChangeLog:
>
> * config/aarch64/aarch64-simd.md
> (*aarch64_<sra_op>rshrn_n<mode>_insn<vczle><vczbe>): New pattern.
> (*aarch64_<sra_op>rshrn2_n<mode>_insn_le): Likewise.
> (*aarch64_<sra_op>rshrn2_n<mode>_insn_be): Likewise.
>
> gcc/testsuite/ChangeLog:
>
> * gcc.target/aarch64/advsimd-intrinsics/shrn-1.c: Expect rshrn
> and rshrn2 in place of raddhn and raddhn2, and no movi.
> * gcc.target/aarch64/advsimd-intrinsics/shrn-2.c: Expect rshrn
> and rshrn2 in place of raddhn and raddhn2. Skip at -O0.
> * gcc.target/aarch64/advsimd-intrinsics/shrn-3.c: Likewise.
> * gcc.target/aarch64/advsimd-intrinsics/shrn-4.c: Likewise.
> * gcc.target/aarch64/simd_rshrn_1.c: New test.
> * gcc.target/aarch64/simd_rshrn_2.c: New test.
>
> Signed-off-by: Dominic P <[email protected]>
> ---
> gcc/config/aarch64/aarch64-simd.md | 46 ++++++++
> .../aarch64/advsimd-intrinsics/shrn-1.c | 6 +-
> .../aarch64/advsimd-intrinsics/shrn-2.c | 5 +-
> .../aarch64/advsimd-intrinsics/shrn-3.c | 5 +-
> .../aarch64/advsimd-intrinsics/shrn-4.c | 5 +-
> .../gcc.target/aarch64/simd_rshrn_1.c | 106 ++++++++++++++++++
> .../gcc.target/aarch64/simd_rshrn_2.c | 64 +++++++++++
> 7 files changed, 228 insertions(+), 9 deletions(-)
> create mode 100644 gcc/testsuite/gcc.target/aarch64/simd_rshrn_1.c
> create mode 100644 gcc/testsuite/gcc.target/aarch64/simd_rshrn_2.c
>
> diff --git a/gcc/config/aarch64/aarch64-simd.md
> b/gcc/config/aarch64/aarch64-simd.md
> index 12dcabb7a15..a07e43d91d3 100644
> --- a/gcc/config/aarch64/aarch64-simd.md
> +++ b/gcc/config/aarch64/aarch64-simd.md
> @@ -7584,6 +7584,20 @@
> }
> )
>
> +(define_insn "*aarch64_<sra_op>rshrn_n<mode>_insn<vczle><vczbe>"
> + [(set (match_operand:<VNARROWQ> 0 "register_operand" "=w")
> + (truncate:<VNARROWQ>
> + (SHIFTRT:VQN
> + (plus:VQN
> + (match_operand:VQN 1 "register_operand" "w")
> + (match_operand:VQN 3 "aarch64_int_rnd_operand"))
> + (match_operand:VQN 2 "aarch64_simd_shift_imm_vec_<vn_mode>"))))]
> + "TARGET_SIMD
> + && aarch64_const_vec_rnd_cst_p (operands[3], operands[2])"
> + "rshrn\t%<vn2>0<Vmntype>, %<v>1<Vmtype>, %2"
> + [(set_attr "type" "neon_shift_imm_narrow_q")]
> +)
> +
> (define_insn "*aarch64_sqshrun_n<mode>_insn<vczle><vczbe>"
> [(set (match_operand:<VNARROWQ> 0 "register_operand" "=w")
> (truncate:<VNARROWQ>
> @@ -7811,6 +7825,38 @@
> [(set_attr "type" "neon_shift_imm_narrow_q")]
> )
>
> +(define_insn "*aarch64_<sra_op>rshrn2_n<mode>_insn_le"
> + [(set (match_operand:<VNARROWQ2> 0 "register_operand" "=w")
> + (vec_concat:<VNARROWQ2>
> + (match_operand:<VNARROWQ> 1 "register_operand" "0")
> + (truncate:<VNARROWQ>
> + (SHIFTRT:VQN
> + (plus:VQN
> + (match_operand:VQN 2 "register_operand" "w")
> + (match_operand:VQN 4 "aarch64_int_rnd_operand"))
> + (match_operand:VQN 3 "aarch64_simd_shift_imm_vec_<vn_mode>")))))]
> + "TARGET_SIMD && !BYTES_BIG_ENDIAN
> + && aarch64_const_vec_rnd_cst_p (operands[4], operands[3])"
> + "rshrn2\t%<vn2>0.<V2ntype>, %<v>2.<Vtype>, %3"
> + [(set_attr "type" "neon_shift_imm_narrow_q")]
> +)
> +
> +(define_insn "*aarch64_<sra_op>rshrn2_n<mode>_insn_be"
> + [(set (match_operand:<VNARROWQ2> 0 "register_operand" "=w")
> + (vec_concat:<VNARROWQ2>
> + (truncate:<VNARROWQ>
> + (SHIFTRT:VQN
> + (plus:VQN
> + (match_operand:VQN 2 "register_operand" "w")
> + (match_operand:VQN 4 "aarch64_int_rnd_operand"))
> + (match_operand:VQN 3 "aarch64_simd_shift_imm_vec_<vn_mode>")))
> + (match_operand:<VNARROWQ> 1 "register_operand" "0")))]
> + "TARGET_SIMD && BYTES_BIG_ENDIAN
> + && aarch64_const_vec_rnd_cst_p (operands[4], operands[3])"
> + "rshrn2\t%<vn2>0.<V2ntype>, %<v>2.<Vtype>, %3"
> + [(set_attr "type" "neon_shift_imm_narrow_q")]
> +)
I was going to ask if there’s a way we can merge these patterns with the
existing rshrn patterns using some smart iterator tricks (and excluding the
invalid combinations of shift+truncation in the instruction predicate).
But if there’s no clean way of doing it, then this is okay.
Thanks,
Kyrill
> +
> (define_expand "aarch64_<shrn_op>rshrn2_n<mode>"
> [(match_operand:<VNARROWQ2> 0 "register_operand")
> (match_operand:<VNARROWQ> 1 "register_operand")
> diff --git a/gcc/testsuite/gcc.target/aarch64/advsimd-intrinsics/shrn-1.c
> b/gcc/testsuite/gcc.target/aarch64/advsimd-intrinsics/shrn-1.c
> index 4bc3aa9563e..a66b0e09828 100644
> --- a/gcc/testsuite/gcc.target/aarch64/advsimd-intrinsics/shrn-1.c
> +++ b/gcc/testsuite/gcc.target/aarch64/advsimd-intrinsics/shrn-1.c
> @@ -10,6 +10,6 @@ uint8x16_t foo (uint32x4_t a, uint32x4_t b)
> return vrshrn_high_n_u16 (vrshrn_n_u16 (b1, 8), b1, 8);
> }
>
> -/* { dg-final { scan-assembler-times {\tmovi\t} 1 } } */
> -/* { dg-final { scan-assembler-times {\traddhn\t} 2 } } */
> -/* { dg-final { scan-assembler-times {\traddhn2\t} 2 } } */
> +/* { dg-final { scan-assembler-not {\tmovi\t} } } */
> +/* { dg-final { scan-assembler-times {\trshrn\t} 2 } } */
> +/* { dg-final { scan-assembler-times {\trshrn2\t} 2 } } */
> diff --git a/gcc/testsuite/gcc.target/aarch64/advsimd-intrinsics/shrn-2.c
> b/gcc/testsuite/gcc.target/aarch64/advsimd-intrinsics/shrn-2.c
> index 09d913e8552..6a3992832d8 100644
> --- a/gcc/testsuite/gcc.target/aarch64/advsimd-intrinsics/shrn-2.c
> +++ b/gcc/testsuite/gcc.target/aarch64/advsimd-intrinsics/shrn-2.c
> @@ -1,4 +1,5 @@
> /* { dg-do compile { target { aarch64*-*-* } } } */
> +/* { dg-skip-if "" { *-*-* } { "-O0" } { "" } } */
>
> #include <arm_neon.h>
>
> @@ -7,5 +8,5 @@ uint32x4_t foo (uint64x2_t a, uint64x2_t b)
> return vrshrn_high_n_u64 (vrshrn_n_u64 (a, 32), b, 32);
> }
>
> -/* { dg-final { scan-assembler-times {\traddhn\t} 1 } } */
> -/* { dg-final { scan-assembler-times {\traddhn2\t} 1 } } */
> +/* { dg-final { scan-assembler-times {\trshrn\t} 1 } } */
> +/* { dg-final { scan-assembler-times {\trshrn2\t} 1 } } */
> diff --git a/gcc/testsuite/gcc.target/aarch64/advsimd-intrinsics/shrn-3.c
> b/gcc/testsuite/gcc.target/aarch64/advsimd-intrinsics/shrn-3.c
> index bdccbb3410f..46c5f807e4f 100644
> --- a/gcc/testsuite/gcc.target/aarch64/advsimd-intrinsics/shrn-3.c
> +++ b/gcc/testsuite/gcc.target/aarch64/advsimd-intrinsics/shrn-3.c
> @@ -1,4 +1,5 @@
> /* { dg-do compile { target { aarch64*-*-* } } } */
> +/* { dg-skip-if "" { *-*-* } { "-O0" } { "" } } */
>
> #include <arm_neon.h>
>
> @@ -7,5 +8,5 @@ uint16x8_t foo (uint32x4_t a, uint32x4_t b)
> return vrshrn_high_n_u32 (vrshrn_n_u32 (a, 16), b, 16);
> }
>
> -/* { dg-final { scan-assembler-times {\traddhn\t} 1 } } */
> -/* { dg-final { scan-assembler-times {\traddhn2\t} 1 } } */
> +/* { dg-final { scan-assembler-times {\trshrn\t} 1 } } */
> +/* { dg-final { scan-assembler-times {\trshrn2\t} 1 } } */
> diff --git a/gcc/testsuite/gcc.target/aarch64/advsimd-intrinsics/shrn-4.c
> b/gcc/testsuite/gcc.target/aarch64/advsimd-intrinsics/shrn-4.c
> index 4b23eddb858..10f31d045b6 100644
> --- a/gcc/testsuite/gcc.target/aarch64/advsimd-intrinsics/shrn-4.c
> +++ b/gcc/testsuite/gcc.target/aarch64/advsimd-intrinsics/shrn-4.c
> @@ -1,4 +1,5 @@
> /* { dg-do compile { target { aarch64*-*-* } } } */
> +/* { dg-skip-if "" { *-*-* } { "-O0" } { "" } } */
>
> #include <arm_neon.h>
>
> @@ -7,5 +8,5 @@ uint8x16_t foo (uint16x8_t a, uint16x8_t b)
> return vrshrn_high_n_u16 (vrshrn_n_u16 (a, 8), b, 8);
> }
>
> -/* { dg-final { scan-assembler-times {\traddhn\t} 1 } } */
> -/* { dg-final { scan-assembler-times {\traddhn2\t} 1 } } */
> +/* { dg-final { scan-assembler-times {\trshrn\t} 1 } } */
> +/* { dg-final { scan-assembler-times {\trshrn2\t} 1 } } */
> diff --git a/gcc/testsuite/gcc.target/aarch64/simd_rshrn_1.c
> b/gcc/testsuite/gcc.target/aarch64/simd_rshrn_1.c
> new file mode 100644
> index 00000000000..72a6af622ec
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/simd_rshrn_1.c
> @@ -0,0 +1,106 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O3 -march=armv8-a" } */
> +/* { dg-final { check-function-bodies "**" "" } } */
> +
> +#include <stdint.h>
> +
> +typedef int16_t v8hi __attribute__((vector_size (16)));
> +typedef int8_t v8qi __attribute__((vector_size (8)));
> +typedef uint16_t v8uhi __attribute__((vector_size (16)));
> +typedef uint8_t v8uqi __attribute__((vector_size (8)));
> +typedef int32_t v4si __attribute__((vector_size (16)));
> +typedef int16_t v4hi __attribute__((vector_size (8)));
> +typedef uint32_t v4usi __attribute__((vector_size (16)));
> +typedef uint16_t v4uhi __attribute__((vector_size (8)));
> +
> +/*
> +** s16_s8:
> +** rshrn v0\.8b, v0\.8h, 6
> +** ret
> +*/
> +v8qi
> +s16_s8 (v8hi x)
> +{
> + return __builtin_convertvector ((x + 32) >> 6, v8qi);
> +}
> +
> +/*
> +** u16_u8:
> +** rshrn v0\.8b, v0\.8h, 5
> +** ret
> +*/
> +v8uqi
> +u16_u8 (v8uhi x)
> +{
> + return __builtin_convertvector ((x + 16) >> 5, v8uqi);
> +}
> +
> +/*
> +** s32_s16:
> +** rshrn v0\.4h, v0\.4s, 8
> +** ret
> +*/
> +v4hi
> +s32_s16 (v4si x)
> +{
> + return __builtin_convertvector ((x + 128) >> 8, v4hi);
> +}
> +
> +/*
> +** u32_u16:
> +** rshrn v0\.4h, v0\.4s, 12
> +** ret
> +*/
> +v4uhi
> +u32_u16 (v4usi x)
> +{
> + return __builtin_convertvector ((x + 2048) >> 12, v4uhi);
> +}
> +
> +#define ROUND_NARROW(NAME, TO, FROM, N, COUNT) \
> + void \
> + NAME (TO *__restrict d, const FROM *__restrict s) \
> + { \
> + for (int i = 0; i < COUNT; i++) \
> + d[i] = (TO) ((s[i] + (1 << (N - 1))) >> N); \
> + }
> +
> +/*
> +** loop_s16_s8:
> +** ldp q31, q30, \[x1\]
> +** rshrn v31\.8b, v31\.8h, 6
> +** rshrn2 v31\.16b, v30\.8h, 6
> +** str q31, \[x0\]
> +** ret
> +*/
> +ROUND_NARROW (loop_s16_s8, int8_t, int16_t, 6, 16)
> +
> +/*
> +** loop_u16_u8:
> +** ldp q31, q30, \[x1\]
> +** rshrn v31\.8b, v31\.8h, 5
> +** rshrn2 v31\.16b, v30\.8h, 5
> +** str q31, \[x0\]
> +** ret
> +*/
> +ROUND_NARROW (loop_u16_u8, uint8_t, uint16_t, 5, 16)
> +
> +/*
> +** loop_s32_s16:
> +** ldp q31, q30, \[x1\]
> +** rshrn v31\.4h, v31\.4s, 8
> +** rshrn2 v31\.8h, v30\.4s, 8
> +** str q31, \[x0\]
> +** ret
> +*/
> +ROUND_NARROW (loop_s32_s16, int16_t, int32_t, 8, 8)
> +
> +/*
> +** loop_u32_u16:
> +** ldp q31, q30, \[x1\]
> +** rshrn v31\.4h, v31\.4s, 12
> +** rshrn2 v31\.8h, v30\.4s, 12
> +** str q31, \[x0\]
> +** ret
> +*/
> +ROUND_NARROW (loop_u32_u16, uint16_t, uint32_t, 12, 8)
> diff --git a/gcc/testsuite/gcc.target/aarch64/simd_rshrn_2.c
> b/gcc/testsuite/gcc.target/aarch64/simd_rshrn_2.c
> new file mode 100644
> index 00000000000..d056f753ddb
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/simd_rshrn_2.c
> @@ -0,0 +1,64 @@
> +/* { dg-do run } */
> +/* { dg-options "-O3 -march=armv8-a" } */
> +
> +#include <stdint.h>
> +
> +#define NELEMS 67
> +
> +#define ROUND_NARROW(NAME, TO, FROM, N, DIV) \
> + __attribute__((noipa)) static void \
> + NAME (TO *__restrict d, const FROM *__restrict s, int n) \
> + { \
> + for (int i = 0; i < n; i++) \
> + d[i] = (TO) ((s[i] + (1 << (N - 1))) >> N); \
> + } \
> + __attribute__((noipa, optimize ("O0"))) static void \
> + NAME##_ref (TO *__restrict d, const FROM *__restrict s, int n) \
> + { \
> + for (int i = 0; i < n; i++) \
> + d[i] = (TO) ((s[i] + (1 << (N - 1))) >> N); \
> + } \
> + static void \
> + NAME##_check (void) \
> + { \
> + FROM s[NELEMS]; \
> + TO d[NELEMS], r[NELEMS]; \
> + for (int i = 0; i < NELEMS; i++) \
> + s[i] = (FROM) rnd () / (DIV); \
> + NAME (d, s, NELEMS); \
> + NAME##_ref (r, s, NELEMS); \
> + for (int i = 0; i < NELEMS; i++) \
> + if (d[i] != r[i]) \
> + __builtin_abort (); \
> + }
> +
> +static uint64_t seed = 0x123456789abcdefULL;
> +
> +static uint64_t
> +rnd (void)
> +{
> + seed = seed * 6364136223846793005ULL + 1442695040888963407ULL;
> + return seed >> 11;
> +}
> +
> +/* The signed 32-bit inputs are halved: signed overflow in the reference
> + addition would be undefined. The unsigned inputs are not halved, so the
> + wrapping add is covered. */
> +
> +ROUND_NARROW (s16_s8, int8_t, int16_t, 6, 1)
> +ROUND_NARROW (u16_u8, uint8_t, uint16_t, 5, 1)
> +ROUND_NARROW (s32_s16, int16_t, int32_t, 8, 2)
> +ROUND_NARROW (u32_u16, uint16_t, uint32_t, 12, 1)
> +
> +int
> +main (void)
> +{
> + for (int rep = 0; rep < 64; rep++)
> + {
> + s16_s8_check ();
> + u16_u8_check ();
> + s32_s16_check ();
> + u32_u16_check ();
> + }
> + return 0;
> +}
> --
> 2.55.0
>