https://gcc.gnu.org/g:93ac5163b5d02c2dfde88c3ec7b1f366caab9b45
commit r17-3926-g93ac5163b5d02c2dfde88c3ec7b1f366caab9b45 Author: Dominic P <[email protected]> Date: Sun Aug 2 11:56:05 2026 +0100 arm: fix vshll_n intrinsics with boundary shift [PR111609] The NEON widening-shift-left-long intrinsics (vshll_n_s8/s16/s32 and vshll_n_u8/u16/u32) accept a shift amount in the range [0, element_size]. The bounds check in neon_vshll<sup>_n<mode> already permits a shift of zero (its stale comment said "0 < imm" while the code implements "0 <= imm"), but it then emitted a literal "vshll.<sz> qD, dN, #0". The VSHLL encoding cannot represent a shift of zero -- valid immediates are 1..element_size -- so the assembler rejected the output with "Error: immediate value out of range". The ACLE semantics of a widening shift-left by zero are exactly those of a widening move, so divert the imm == 0 case to VMOVL, which is the correct and encodable instruction. This turns previously-broken valid ACLE code into working code (better QoI than rejecting it). Nonzero shifts, including a shift by the element size, are unchanged. A related issue exists when the shift size is equal to the element size. For these cases the mnemonic should use a signless form (i8, i16, i32). Assisted-by: Claude Opus 5 (Anthropic) PR target/111609 gcc/ChangeLog: * config/arm/neon.md (neon_vshll<sup>_n<mode>): Emit vmovl for a shift of zero. Use signless form when the shift amount == element size. gcc/testsuite/ChangeLog: * gcc.target/arm/pr111609.c: New test. Signed-off-by: Dominic P <[email protected]> Co-authored-by: Richard Earnshaw <[email protected]> Diff: --- gcc/config/arm/neon.md | 12 +++++++++--- gcc/testsuite/gcc.target/arm/pr111609.c | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/gcc/config/arm/neon.md b/gcc/config/arm/neon.md index bb1fc4818e11..3c908db4538a 100644 --- a/gcc/config/arm/neon.md +++ b/gcc/config/arm/neon.md @@ -4710,9 +4710,15 @@ if (BYTES_BIG_ENDIAN) VSHLL_N))] "TARGET_NEON" { - /* The boundaries are: 0 < imm <= size. */ - arm_const_bounds (operands[2], 0, neon_element_bits (<MODE>mode) + 1); - return "vshll.<sup>%#<V_sz_elem>\t%q0, %P1, %2"; + /* The boundaries are: 0 <= imm <= size, but the upper and lower + bounds need specific handling. */ + HOST_WIDE_INT elt_size = neon_element_bits (<MODE>mode); + arm_const_bounds (operands[2], 0, elt_size + 1); + if (INTVAL (operands[2]) == 0) + return "vmovl.<sup><V_sz_elem>\t%q0, %P1"; + else if (INTVAL (operands[2]) == elt_size) + return "vshll.i<V_sz_elem>\t%q0, %P1, %2"; + return "vshll.<sup><V_sz_elem>\t%q0, %P1, %2"; } [(set_attr "type" "neon_shift_imm_long")] ) diff --git a/gcc/testsuite/gcc.target/arm/pr111609.c b/gcc/testsuite/gcc.target/arm/pr111609.c new file mode 100644 index 000000000000..1b060624f28a --- /dev/null +++ b/gcc/testsuite/gcc.target/arm/pr111609.c @@ -0,0 +1,33 @@ +/* PR target/111609 : a widening shift-left by zero must not emit an + unencodable "vshll #0"; it is a plain widening move (vmovl). */ +/* { dg-do assemble } */ +/* { dg-require-effective-target arm_neon_ok } */ +/* { dg-options "-O2 --save-temps" } */ +/* { dg-add-options arm_neon } */ + +#include <arm_neon.h> + +int16x8_t f_s8 (int8x8_t a) { return vshll_n_s8 (a, 0); } +/* { dg-final { scan-assembler-times {vmovl\.s8\t} 1 } } */ +int32x4_t f_s16 (int16x4_t a) { return vshll_n_s16 (a, 0); } +/* { dg-final { scan-assembler-times {vmovl\.s16\t} 1 } } */ +int64x2_t f_s32 (int32x2_t a) { return vshll_n_s32 (a, 0); } +/* { dg-final { scan-assembler-times {vmovl\.s32\t} 1 } } */ +uint16x8_t f_u8 (uint8x8_t a) { return vshll_n_u8 (a, 0); } +/* { dg-final { scan-assembler-times {vmovl\.u8\t} 1 } } */ +uint32x4_t f_u16 (uint16x4_t a) { return vshll_n_u16 (a, 0); } +/* { dg-final { scan-assembler-times {vmovl\.u16\t} 1 } } */ +uint64x2_t f_u32 (uint32x2_t a) { return vshll_n_u32 (a, 0); } +/* { dg-final { scan-assembler-times {vmovl\.u32\t} 1 } } */ + +int16x8_t g_s8 (int8x8_t a) { return vshll_n_s8 (a, 1); } +/* { dg-final { scan-assembler-times {vshll\.s8\tq[0-9]+, d[0-9]+, #1} 1 } } */ + +int16x8_t h_s8 (int8x8_t a) { return vshll_n_s8 (a, 8); } +/* { dg-final { scan-assembler-times {vshll\.i8\tq[0-9]+, d[0-9]+, #8} 1 } } */ +uint32x4_t h_u16 (uint16x4_t a) { return vshll_n_u16 (a, 16); } +/* { dg-final { scan-assembler-times {vshll\.i16\tq[0-9]+, d[0-9]+, #16} 1 } } */ +int64x2_t h_s32 (int32x2_t a) { return vshll_n_s32 (a, 32); } +/* { dg-final { scan-assembler-times {vshll\.i32\tq[0-9]+, d[0-9]+, #32} 1 } } */ + +/* { dg-final { scan-assembler-not {vshll\.[su][0-9]+\t[^\n]*#0\n} } } */
