A packed, over-aligned 8-byte object is passed in an odd register pair, so
a DImode move of it cannot use ldrd or strd.  output_move_double then falls
back to a load or store multiple, but only for PRE_DEC and POST_INC; the
PRE_INC and POST_DEC cases assert, and with checking disabled fall through
to an odd-register ldrd that the assembler rejects.

No single multiple can express those two forms, so emit two instructions:
for PRE_INC two indexed transfers with the writeback folded into the first,
as the PRE_MODIFY case already does; for POST_DEC a multiple followed by a
base adjustment.  Only the load arms are reachable, since the odd pairs
come from the ABI's argument registers; the store arms are kept for
symmetry.

Tested on arm-none-eabi (armv7-a) with gcc.target/arm/arm.exp: no
pre-existing test changes status.

Assisted-by: Claude Opus 5 (Anthropic)

        PR target/97807

gcc/ChangeLog:

        PR target/97807
        * config/arm/arm.cc (output_move_double): Emit two instructions
        for the PRE_INC and POST_DEC cases, on both the load and the
        store side, when ldrd or strd cannot be used.

gcc/testsuite/ChangeLog:

        PR target/97807
        * gcc.target/arm/pr97807.c: New test.
        * gcc.target/arm/pr97807-2.c: New test.

Signed-off-by: Dominic P <[email protected]>
---
 gcc/config/arm/arm.cc                    | 77 ++++++++++++++++++++----
 gcc/testsuite/gcc.target/arm/pr97807-2.c | 22 +++++++
 gcc/testsuite/gcc.target/arm/pr97807.c   | 25 ++++++++
 3 files changed, 112 insertions(+), 12 deletions(-)
 create mode 100644 gcc/testsuite/gcc.target/arm/pr97807-2.c
 create mode 100644 gcc/testsuite/gcc.target/arm/pr97807.c

diff --git a/gcc/config/arm/arm.cc b/gcc/config/arm/arm.cc
index d39eb4bf177..3809113c44b 100644
--- a/gcc/config/arm/arm.cc
+++ b/gcc/config/arm/arm.cc
@@ -20377,9 +20377,22 @@ output_move_double (rtx *operands, bool emit, int 
*count)
          break;
 
        case PRE_INC:
-         gcc_assert (can_ldrd);
-         if (emit)
-           output_asm_insn ("ldrd%?\t%0, [%m1, #8]!", operands);
+         if (can_ldrd)
+           {
+             if (emit)
+               output_asm_insn ("ldrd%?\t%0, [%m1, #8]!", operands);
+           }
+         else
+           {
+             /* Two indexed loads, as the PRE_MODIFY case below does.  */
+             if (emit)
+               {
+                 output_asm_insn ("ldr%?\t%0, [%m1, #8]!", operands);
+                 output_asm_insn ("ldr%?\t%H0, [%m1, #4]", operands);
+               }
+             if (count)
+               *count = 2;
+           }
          break;
 
        case PRE_DEC:
@@ -20403,9 +20416,23 @@ output_move_double (rtx *operands, bool emit, int 
*count)
          break;
 
        case POST_DEC:
-         gcc_assert (can_ldrd);
-         if (emit)
-           output_asm_insn ("ldrd%?\t%0, [%m1], #-8", operands);
+         if (can_ldrd)
+           {
+             if (emit)
+               output_asm_insn ("ldrd%?\t%0, [%m1], #-8", operands);
+           }
+         else
+           {
+             /* The transfer is at the base itself, so load there and
+                decrement afterwards.  */
+             if (emit)
+               {
+                 output_asm_insn ("ldmia%?\t%m1, %M0", operands);
+                 output_asm_insn ("sub%?\t%m1, %m1, #8", operands);
+               }
+             if (count)
+               *count = 2;
+           }
          break;
 
        case PRE_MODIFY:
@@ -20660,9 +20687,22 @@ output_move_double (rtx *operands, bool emit, int 
*count)
          break;
 
         case PRE_INC:
-         gcc_assert (allow_strd);
-         if (emit)
-           output_asm_insn ("strd%?\t%1, [%m0, #8]!", operands);
+         if (allow_strd)
+           {
+             if (emit)
+               output_asm_insn ("strd%?\t%1, [%m0, #8]!", operands);
+           }
+         else
+           {
+             /* Two indexed stores, as the PRE_MODIFY case does for loads.  */
+             if (emit)
+               {
+                 output_asm_insn ("str%?\t%1, [%m0, #8]!", operands);
+                 output_asm_insn ("str%?\t%H1, [%m0, #4]", operands);
+               }
+             if (count)
+               *count = 2;
+           }
          break;
 
         case PRE_DEC:
@@ -20686,9 +20726,22 @@ output_move_double (rtx *operands, bool emit, int 
*count)
          break;
 
         case POST_DEC:
-         gcc_assert (allow_strd);
-         if (emit)
-           output_asm_insn ("strd%?\t%1, [%m0], #-8", operands);
+         if (allow_strd)
+           {
+             if (emit)
+               output_asm_insn ("strd%?\t%1, [%m0], #-8", operands);
+           }
+         else
+           {
+             /* Store at the base itself and decrement afterwards.  */
+             if (emit)
+               {
+                 output_asm_insn ("stmia%?\t%m0, %M1", operands);
+                 output_asm_insn ("sub%?\t%m0, %m0, #8", operands);
+               }
+             if (count)
+               *count = 2;
+           }
          break;
 
        case PRE_MODIFY:
diff --git a/gcc/testsuite/gcc.target/arm/pr97807-2.c 
b/gcc/testsuite/gcc.target/arm/pr97807-2.c
new file mode 100644
index 00000000000..af288cf267f
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/pr97807-2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target arm_arm_ok } */
+/* { dg-require-effective-target arm_arch_v6_ok } */
+/* { dg-options "-Os -marm" } */
+/* { dg-add-options arm_arch_v6 } */
+
+struct c {
+  double a;
+} __attribute((packed)) __attribute((aligned));
+
+void f (struct c *, struct c);
+
+void g (struct c *ptr)
+{
+  struct c t = *ptr;
+  ptr--;
+  f (ptr, t);
+}
+
+/* { dg-final { scan-assembler {ldmia\tr[0-9]+, \{r[0-9]+-r[0-9]+\}} } } */
+/* { dg-final { scan-assembler {sub\tr[0-9]+, r[0-9]+, #8} } } */
+/* { dg-final { scan-assembler-not "ldrd" } } */
diff --git a/gcc/testsuite/gcc.target/arm/pr97807.c 
b/gcc/testsuite/gcc.target/arm/pr97807.c
new file mode 100644
index 00000000000..caa0dc95fce
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/pr97807.c
@@ -0,0 +1,25 @@
+/* The packed, over-aligned struct lands in an odd register pair.  */
+
+/* { dg-do compile } */
+/* { dg-require-effective-target arm_arm_ok } */
+/* { dg-require-effective-target arm_arch_v6_ok } */
+/* { dg-options "-Os -marm -fno-forward-propagate" } */
+/* { dg-add-options arm_arch_v6 } */
+
+struct c {
+  double a;
+} __attribute((packed)) __attribute((aligned));
+
+void f (struct c *, struct c);
+
+void g (struct c *ptr)
+{
+  ptr++;
+  f (ptr, *ptr);
+}
+
+/* ldrd is unavailable for this register pair, so expect two indexed word
+   loads, the first carrying the base writeback, and no load multiple.  */
+/* { dg-final { scan-assembler {ldr\tr[0-9]+, \[r[0-9]+, #8\]!} } } */
+/* { dg-final { scan-assembler-not "ldrd" } } */
+/* { dg-final { scan-assembler-not "ldmia" } } */
-- 
2.55.0

Reply via email to