vec_extract's only register alternative writes a core register, so a
single-precision lane comes back through the integer side:

  float foo (float32x2_t v)
  { return vget_lane_f32 (v, 0) + vget_lane_f32 (v, 1); }

  vmov.32       r3, d0[1]
  vmov          s15, r3
  vadd.f32      s0, s15, s0

Such a lane is a whole S register, so extract it with a subreg move that the
register allocator can coalesce, and the example becomes

  vadd.f32      s0, s1, s0

In the vec_extract expanders this covers the vget_lane_f32 and vgetq_lane_f32
intrinsics, the generic v[i] on Advanced SIMD and the generic v[i] on MVE.
The Advanced SIMD D-register extract had no expander, so its insn is renamed
the way the Q-register one already is.  The MVE intrinsics expand straight to
mve_vec_extract in arm-mve-builtins-base.cc and are unchanged.

Big-endian keeps the old path: arm_can_change_mode_class refuses to change
the mode of a multi-word value in VFP_REGS there, so the subreg would only
force a spill.  Integer lanes keep it too; their destination is a core
register, which vec_extract already reaches in one vmov.

This is the approach Jim Wilson proposed in the PR (attachment 35114),
restricted to the case where the lane really is a register; as he notes
there, a constraint alone still leaves the extra vmov.

An SF subreg can only name s0-s31, so a vector a lane is read from is
kept in d0-d15; with more than eight such vectors live at once the
allocator spills where the core-register path did not.

Assisted-by: Claude Opus 5 (Anthropic)

gcc/ChangeLog:

        PR target/60408
        * config/arm/neon.md (vec_extract<mode><V_elem_l>): New expander;
        extract a single-precision lane with a subreg move.
        (neon_vec_extract<mode><V_elem_l>): Rename from
        vec_extract<mode><V_elem_l>.
        * config/arm/vec-common.md (vec_extract<mode><V_elem_l>): Extract a
        single-precision lane with a subreg move.

gcc/testsuite/ChangeLog:

        PR target/60408
        * gcc.target/arm/pr60408.c: New test.
        * gcc.target/arm/pr60408-be.c: New test.

Signed-off-by: Dominic P <[email protected]>
---
Bootstrapped on x86_64-pc-linux-gnu; regression tested on arm-none-eabi 
(arm.exp, simd.exp,
mve.exp, advsimd-intrinsics.exp) with no new failures.

Found by reading the vget_lane_f32 expansion in the PR, and measured at
-O1/-O2/-O3/-Os, -marm and -mthumb, -mfpu=neon.  Reading all four lanes of a
float32x4_t and summing them is 11 -> 4 instructions, with the intrinsics and
with v[i] alike; a single lane is 3 -> 2.  Integer lanes, __fp16 lanes,
vset_lane and big-endian output are byte-identical.

The one case that grows is -mfloat-abi=softfp -Os for a function taking the
vector by value: the vector arrives in a core-register pair, and the allocator
then materialises each lane with its own "vmov sN, rN" rather than moving the
whole vector once with "vmov d7, r0, r1" (4 -> 5 instructions).  It is
confined to that ABI boundary -- with the vector loaded from memory or
computed, softfp -Os is 4 -> 4, 6 -> 6 and 12 -> 6 -- and over the same
examples softfp totals 216 -> 177 instructions at -Os and 230 -> 176 at -O2.
 gcc/config/arm/neon.md                    | 19 ++++++-
 gcc/config/arm/vec-common.md              |  7 ++-
 gcc/testsuite/gcc.target/arm/pr60408-be.c | 35 ++++++++++++
 gcc/testsuite/gcc.target/arm/pr60408.c    | 67 +++++++++++++++++++++++
 4 files changed, 126 insertions(+), 2 deletions(-)
 create mode 100644 gcc/testsuite/gcc.target/arm/pr60408-be.c
 create mode 100644 gcc/testsuite/gcc.target/arm/pr60408.c

diff --git a/gcc/config/arm/neon.md b/gcc/config/arm/neon.md
index 3c908db4538..004a2c3f8cc 100644
--- a/gcc/config/arm/neon.md
+++ b/gcc/config/arm/neon.md
@@ -393,7 +393,24 @@
   [(set_attr "type" "neon_load1_all_lanes_q,neon_from_gp_q")]
 )
 
-(define_insn "vec_extract<mode><V_elem_l>"
+(define_expand "vec_extract<mode><V_elem_l>"
+  [(match_operand:<V_elem> 0 "nonimmediate_operand")
+   (match_operand:VD_LANE 1 "s_register_operand")
+   (match_operand:SI 2 "immediate_operand")]
+  "TARGET_NEON"
+{
+  if (<V_elem>mode == SFmode && !BYTES_BIG_ENDIAN)
+    emit_move_insn (operands[0],
+                   simplify_gen_subreg (SFmode, operands[1], <MODE>mode,
+                                        INTVAL (operands[2])
+                                        * GET_MODE_UNIT_SIZE (<MODE>mode)));
+  else
+    emit_insn (gen_neon_vec_extract<mode><V_elem_l> (operands[0], operands[1],
+                                                    operands[2]));
+  DONE;
+})
+
+(define_insn "neon_vec_extract<mode><V_elem_l>"
   [(set (match_operand:<V_elem> 0 "nonimmediate_operand" "=Um,r")
         (vec_select:<V_elem>
           (match_operand:VD_LANE 1 "s_register_operand" "w,w")
diff --git a/gcc/config/arm/vec-common.md b/gcc/config/arm/vec-common.md
index 87e4ae6901d..0d4b422183a 100644
--- a/gcc/config/arm/vec-common.md
+++ b/gcc/config/arm/vec-common.md
@@ -160,7 +160,12 @@
   (match_operand:SI 2 "immediate_operand")]
  "TARGET_NEON || TARGET_HAVE_MVE"
 {
-  if (TARGET_NEON)
+  if (<V_elem>mode == SFmode && !BYTES_BIG_ENDIAN)
+    emit_move_insn (operands[0],
+                   simplify_gen_subreg (SFmode, operands[1], <MODE>mode,
+                                        INTVAL (operands[2])
+                                        * GET_MODE_UNIT_SIZE (<MODE>mode)));
+  else if (TARGET_NEON)
     emit_insn (gen_neon_vec_extract<mode><V_elem_l> (operands[0], operands[1],
                                                     operands[2]));
   else if (TARGET_HAVE_MVE)
diff --git a/gcc/testsuite/gcc.target/arm/pr60408-be.c 
b/gcc/testsuite/gcc.target/arm/pr60408-be.c
new file mode 100644
index 00000000000..d69efa6600b
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/pr60408-be.c
@@ -0,0 +1,35 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target arm_neon_ok } */
+/* { dg-skip-if "avoid conflicts with multilib options" { *-*-* } { 
"-mfloat-abi=*" } { "-mfloat-abi=hard" } } */
+/* { dg-options "-O2" } */
+/* { dg-add-options arm_neon } */
+/* { dg-additional-options "-mfloat-abi=hard -mbig-endian" } */
+/* { dg-final { check-function-bodies "**" "" } } */
+
+/* A big-endian lane keeps coming back through a core register.  */
+
+#include "arm_neon.h"
+
+/*
+**high_lane:
+**     vmov\.32        (r[0-9]+), d1\[0\]
+**     vmov    s0, \1
+**     bx      lr
+*/
+float
+high_lane (float32x4_t v)
+{
+  return vgetq_lane_f32 (v, 2);
+}
+
+/*
+**sub_lane:
+**     vmov\.32        (r[0-9]+), d0\[0\]
+**     vmov    s0, \1
+**     bx      lr
+*/
+float
+sub_lane (float32x2_t v)
+{
+  return v[1];
+}
diff --git a/gcc/testsuite/gcc.target/arm/pr60408.c 
b/gcc/testsuite/gcc.target/arm/pr60408.c
new file mode 100644
index 00000000000..e2c3b297dc5
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/pr60408.c
@@ -0,0 +1,67 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target arm_neon_ok } */
+/* { dg-require-effective-target arm_little_endian } */
+/* { dg-skip-if "avoid conflicts with multilib options" { *-*-* } { 
"-mfloat-abi=*" } { "-mfloat-abi=hard" } } */
+/* { dg-options "-O2" } */
+/* { dg-add-options arm_neon } */
+/* { dg-additional-options "-mfloat-abi=hard" } */
+/* { dg-final { check-function-bodies "**" "" } } */
+
+#include "arm_neon.h"
+
+/*
+**add_lanes:
+**     vadd\.f32       s0, s1, s0
+**     bx      lr
+*/
+float
+add_lanes (float32x2_t v)
+{
+  return vget_lane_f32 (v, 0) + vget_lane_f32 (v, 1);
+}
+
+/*
+**high_lane:
+**     vmov\.f32       s0, s2
+**     bx      lr
+*/
+float
+high_lane (float32x4_t v)
+{
+  return vgetq_lane_f32 (v, 2);
+}
+
+/*
+**sub_lane:
+**     vmov\.f32       s0, s1
+**     bx      lr
+*/
+float
+sub_lane (float32x2_t v)
+{
+  return v[1];
+}
+
+/*
+**sub_lane_q:
+**     vmov\.f32       s0, s3
+**     bx      lr
+*/
+float
+sub_lane_q (float32x4_t v)
+{
+  return v[3];
+}
+
+/*
+**sum_lanes:
+**     vadd\.f32       (s[0-9]+), s0, s1
+**     vadd\.f32       \1, \1, s2
+**     vadd\.f32       s0, \1, s3
+**     bx      lr
+*/
+float
+sum_lanes (float32x4_t v)
+{
+  return v[0] + v[1] + v[2] + v[3];
+}
-- 
2.55.0

Reply via email to