[PATCH 3/6] arm: fix vshll_n intrinsics with a zero shift [PR111609]

Dominic P <[email protected]> Sun, 2 Aug 2026 11:56:05 +0100
Newsgroups gmane.comp.gcc.patches
Message-ID <[email protected]>
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".  A shift by the element size is
encodable and continues to use VSHLL.

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.

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, which VSHLL cannot encode.  Fix stale comment.

gcc/testsuite/ChangeLog:

	* gcc.target/arm/pr111609.c: New test.

Signed-off-by: Dominic P <[email protected]>
---
 gcc/config/arm/neon.md                  |  7 ++++++-
 gcc/testsuite/gcc.target/arm/pr111609.c | 28 +++++++++++++++++++++++++
 2 files changed, 34 insertions(+), 1 deletion(-)
 create mode 100644 gcc/testsuite/gcc.target/arm/pr111609.c

diff --git a/gcc/config/arm/neon.md b/gcc/config/arm/neon.md
index 603bdc1ab..3b12572bb 100644
--- a/gcc/config/arm/neon.md
+++ b/gcc/config/arm/neon.md
@@ -4620,8 +4620,13 @@ if (BYTES_BIG_ENDIAN)
 			  VSHLL_N))]
   "TARGET_NEON"
 {
-  /* The boundaries are: 0 < imm <= size.  */
+  /* The boundaries are: 0 <= imm <= size.  A shift by the element size is
+     encodable, but a shift by zero is not; the ACLE semantics of a widening
+     shift-left by zero are exactly those of a widening move, so emit VMOVL
+     in that case (PR111609).  */
   arm_const_bounds (operands[2], 0, neon_element_bits (<MODE>mode) + 1);
+  if (INTVAL (operands[2]) == 0)
+    return "vmovl.<sup>%#<V_sz_elem>\t%q0, %P1";
   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 000000000..16642f40b
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/pr111609.c
@@ -0,0 +1,28 @@
+/* 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); }
+int32x4_t  f_s16 (int16x4_t a)  { return vshll_n_s16 (a, 0); }
+int64x2_t  f_s32 (int32x2_t a)  { return vshll_n_s32 (a, 0); }
+uint16x8_t f_u8  (uint8x8_t a)  { return vshll_n_u8  (a, 0); }
+uint32x4_t f_u16 (uint16x4_t a) { return vshll_n_u16 (a, 0); }
+uint64x2_t f_u32 (uint32x2_t a) { return vshll_n_u32 (a, 0); }
+
+/* A nonzero shift, and a shift by the element size, remain vshll.  */
+int16x8_t  g_s8  (int8x8_t a)   { return vshll_n_s8  (a, 1); }
+int16x8_t  h_s8  (int8x8_t a)   { return vshll_n_s8  (a, 8); }
+
+/* The zero-shift cases must lower to vmovl, never to "vshll ... #0".  */
+/* { dg-final { scan-assembler-times {vmovl\.s8\t} 1 } } */
+/* { dg-final { scan-assembler-times {vmovl\.s16\t} 1 } } */
+/* { dg-final { scan-assembler-times {vmovl\.s32\t} 1 } } */
+/* { dg-final { scan-assembler-times {vmovl\.u8\t} 1 } } */
+/* { dg-final { scan-assembler-times {vmovl\.u16\t} 1 } } */
+/* { dg-final { scan-assembler-times {vmovl\.u32\t} 1 } } */
+/* { dg-final { scan-assembler-not {vshll\.[su][0-9]+\t[^\n]*#0\n} } } */
-- 
2.55.0