[PATCH 1/6] arm: fix wrong-code for __fp16 homogeneous aggregate return [PR92999]

Dominic P <[email protected]> Sun, 2 Aug 2026 11:56:03 +0100
Newsgroups gmane.comp.gcc.patches
Message-ID <[email protected]>
With the hard-float (VFP) PCS, a homogeneous aggregate whose elements
are __fp16 (HFmode) was returned incorrectly: every element was placed
in s0 instead of consecutive VFP registers, so all elements collapsed onto
s0; on a round-trip every field read back as the last element's value.

aapcs_vfp_allocate_return_reg computed the per-element register stride
as GET_MODE_SIZE (ag_mode) / GET_MODE_SIZE (SFmode).  For an HFmode
element this is 2 / 4 == 0, so element i was allocated to
FIRST_VFP_REGNUM + i * 0, i.e. s0 for every element.

Fix it the same way aapcs_vfp_allocate already does for argument
passing: clamp the element size to at least the size of SFmode before
dividing, giving a stride of one VFP register per __fp16 element.  For
all other element modes (SF, DF, DI, vector) MAX has no effect, so the
generated code for every already-correct case is unchanged.

Assisted-by: Claude Opus 5 (Anthropic)

gcc/ChangeLog:

	PR target/92999
	* config/arm/arm.cc (aapcs_vfp_allocate_return_reg): Clamp the
	aggregate element size to at least the size of SFmode when
	computing the VFP register stride, mirroring aapcs_vfp_allocate.

gcc/testsuite/ChangeLog:

	PR target/92999
	* gcc.target/arm/pr92999.c: New test.

Signed-off-by: Dominic P <[email protected]>
---
 gcc/config/arm/arm.cc                  |  3 +-
 gcc/testsuite/gcc.target/arm/pr92999.c | 43 ++++++++++++++++++++++++++
 2 files changed, 45 insertions(+), 1 deletion(-)
 create mode 100644 gcc/testsuite/gcc.target/arm/pr92999.c

diff --git a/gcc/config/arm/arm.cc b/gcc/config/arm/arm.cc
index 0bc66abe2..4597b6bc0 100644
--- a/gcc/config/arm/arm.cc
+++ b/gcc/config/arm/arm.cc
@@ -6858,7 +6858,8 @@ aapcs_vfp_allocate_return_reg (enum arm_pcs pcs_variant ATTRIBUTE_UNUSED,
 	      count *= 2;
 	    }
 	}
-      shift = GET_MODE_SIZE(ag_mode) / GET_MODE_SIZE(SFmode);
+      shift = (MAX (GET_MODE_SIZE (ag_mode), GET_MODE_SIZE (SFmode))
+	       / GET_MODE_SIZE (SFmode));
       par = gen_rtx_PARALLEL (mode, rtvec_alloc (count));
       for (i = 0; i < count; i++)
 	{
diff --git a/gcc/testsuite/gcc.target/arm/pr92999.c b/gcc/testsuite/gcc.target/arm/pr92999.c
new file mode 100644
index 000000000..f69a019aa
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/pr92999.c
@@ -0,0 +1,43 @@
+/* PR target/92999: with the hard-float (VFP) ABI, returning a structure
+   that is a homogeneous aggregate of __fp16 elements placed every element
+   into s0, corrupting all but the last field.  Check that a round-trip
+   through the ABI preserves every field.  */
+
+/* { dg-do run { target arm_eabi } } */
+/* { dg-require-effective-target arm_hard_vfp_ok } */
+/* { dg-require-effective-target arm32 } */
+/* { dg-options "-O2 -mfpu=vfp -mfloat-abi=hard" } */
+
+struct h2 { __fp16 a; __fp16 b; };
+struct h3 { __fp16 a; __fp16 b; __fp16 c; };
+struct h4 { __fp16 a; __fp16 b; __fp16 c; __fp16 d; };
+
+struct h2 __attribute__((noipa)) ret2 (struct h2 s) { return s; }
+struct h3 __attribute__((noipa)) ret3 (struct h3 s) { return s; }
+struct h4 __attribute__((noipa)) ret4 (struct h4 s) { return s; }
+
+int
+main (void)
+{
+  union { struct h2 s; unsigned short u[2]; } i2, o2;
+  union { struct h3 s; unsigned short u[3]; } i3, o3;
+  union { struct h4 s; unsigned short u[4]; } i4, o4;
+
+  i2.u[0] = 0x3c00; i2.u[1] = 0x4000;			/* 1.0, 2.0 */
+  o2.s = ret2 (i2.s);
+  if (o2.u[0] != 0x3c00 || o2.u[1] != 0x4000)
+    __builtin_abort ();
+
+  i3.u[0] = 0x3c00; i3.u[1] = 0x4000; i3.u[2] = 0x4200;	/* 1, 2, 3 */
+  o3.s = ret3 (i3.s);
+  if (o3.u[0] != 0x3c00 || o3.u[1] != 0x4000 || o3.u[2] != 0x4200)
+    __builtin_abort ();
+
+  i4.u[0] = 0x3c00; i4.u[1] = 0x4000; i4.u[2] = 0x4200; i4.u[3] = 0x4400;
+  o4.s = ret4 (i4.s);
+  if (o4.u[0] != 0x3c00 || o4.u[1] != 0x4000
+      || o4.u[2] != 0x4200 || o4.u[3] != 0x4400)
+    __builtin_abort ();
+
+  return 0;
+}
-- 
2.55.0