[arm] Don't generate invalid LDRD insns
authorAlex Coplan <alex.coplan@arm.com>
Mon, 18 May 2020 15:29:04 +0000 (16:29 +0100)
committerAlex Coplan <alex.coplan@arm.com>
Mon, 18 May 2020 15:31:43 +0000 (16:31 +0100)
This fixes a bug in the arm backend where GCC generates invalid LDRD
instructions. The LDRD instruction requires the first transfer register to be
even, but GCC attempts to use odd registers here. For example, with the
following C code:

    struct c {
      double a;
    } __attribute((aligned)) __attribute((packed));
    struct c d;
    struct c f(struct c);
    void e() { f(d); }

The struct d is passed in registers r1 and r2 to the function f, and GCC
attempted to do this with a LDRD instruction when compiling with -march=armv7-a
on a soft float toolchain.

The fix is analogous to the corresponding one for STRD in the same function:
https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=52057dc4ac5295caebf83147f688d769c93cbc8d

2020-05-18  Alex Coplan  <alex.coplan@arm.com>

gcc/:
* config/arm/arm.c (output_move_double): Fix codegen when loading into
a register pair with an odd base register.

gcc/testsuite/:
* gcc.c-torture/compile/packed-aligned-1.c: New test.
* gcc.c-torture/execute/packed-aligned.c: New test.

gcc/ChangeLog
gcc/config/arm/arm.c
gcc/testsuite/ChangeLog
gcc/testsuite/gcc.c-torture/compile/packed-aligned-1.c [new file with mode: 0644]
gcc/testsuite/gcc.c-torture/execute/packed-aligned.c [new file with mode: 0644]

index 580b3cda64cd1bef40fa5513488243f6962e3cc9..dffe88fdcca713d869a4d3d15d727a5c2208c338 100644 (file)
@@ -1,3 +1,8 @@
+2020-05-18  Alex Coplan  <alex.coplan@arm.com>
+
+       * config/arm/arm.c (output_move_double): Fix codegen when loading into
+       a register pair with an odd base register.
+
 2020-05-18  Uroš Bizjak  <ubizjak@gmail.com>
 
        * config/i386/i386-expand.c (ix86_expand_fp_absneg_operator):
index 349918a9bfa60bb8fbba7e3b4a213a1aa83e997e..635e7adac452ed06b8947747e5d53c04eca324d1 100644 (file)
@@ -19575,6 +19575,7 @@ output_move_double (rtx *operands, bool emit, int *count)
   if (code0 == REG)
     {
       unsigned int reg0 = REGNO (operands[0]);
+      const bool can_ldrd = TARGET_LDRD && (TARGET_THUMB2 || (reg0 % 2 == 0));
 
       otherops[0] = gen_rtx_REG (SImode, 1 + reg0);
 
@@ -19586,7 +19587,7 @@ output_move_double (rtx *operands, bool emit, int *count)
 
          if (emit)
            {
-             if (TARGET_LDRD
+             if (can_ldrd
                  && !(fix_cm3_ldrd && reg0 == REGNO(XEXP (operands[1], 0))))
                output_asm_insn ("ldrd%?\t%0, [%m1]", operands);
              else
@@ -19595,7 +19596,7 @@ output_move_double (rtx *operands, bool emit, int *count)
          break;
 
        case PRE_INC:
-         gcc_assert (TARGET_LDRD);
+         gcc_assert (can_ldrd);
          if (emit)
            output_asm_insn ("ldrd%?\t%0, [%m1, #8]!", operands);
          break;
@@ -19603,7 +19604,7 @@ output_move_double (rtx *operands, bool emit, int *count)
        case PRE_DEC:
          if (emit)
            {
-             if (TARGET_LDRD)
+             if (can_ldrd)
                output_asm_insn ("ldrd%?\t%0, [%m1, #-8]!", operands);
              else
                output_asm_insn ("ldmdb%?\t%m1!, %M0", operands);
@@ -19613,7 +19614,7 @@ output_move_double (rtx *operands, bool emit, int *count)
        case POST_INC:
          if (emit)
            {
-             if (TARGET_LDRD)
+             if (can_ldrd)
                output_asm_insn ("ldrd%?\t%0, [%m1], #8", operands);
              else
                output_asm_insn ("ldmia%?\t%m1!, %M0", operands);
@@ -19621,7 +19622,7 @@ output_move_double (rtx *operands, bool emit, int *count)
          break;
 
        case POST_DEC:
-         gcc_assert (TARGET_LDRD);
+         gcc_assert (can_ldrd);
          if (emit)
            output_asm_insn ("ldrd%?\t%0, [%m1], #-8", operands);
          break;
@@ -19643,6 +19644,7 @@ output_move_double (rtx *operands, bool emit, int *count)
                  /* Registers overlap so split out the increment.  */
                  if (emit)
                    {
+                     gcc_assert (can_ldrd);
                      output_asm_insn ("add%?\t%1, %1, %2", otherops);
                      output_asm_insn ("ldrd%?\t%0, [%1] @split", otherops);
                    }
@@ -19654,10 +19656,11 @@ output_move_double (rtx *operands, bool emit, int *count)
                  /* Use a single insn if we can.
                     FIXME: IWMMXT allows offsets larger than ldrd can
                     handle, fix these up with a pair of ldr.  */
-                 if (TARGET_THUMB2
+                 if (can_ldrd
+                     && (TARGET_THUMB2
                      || !CONST_INT_P (otherops[2])
                      || (INTVAL (otherops[2]) > -256
-                         && INTVAL (otherops[2]) < 256))
+                         && INTVAL (otherops[2]) < 256)))
                    {
                      if (emit)
                        output_asm_insn ("ldrd%?\t%0, [%1, %2]!", otherops);
@@ -19680,10 +19683,11 @@ output_move_double (rtx *operands, bool emit, int *count)
              /* Use a single insn if we can.
                 FIXME: IWMMXT allows offsets larger than ldrd can handle,
                 fix these up with a pair of ldr.  */
-             if (TARGET_THUMB2
+             if (can_ldrd
+                 && (TARGET_THUMB2
                  || !CONST_INT_P (otherops[2])
                  || (INTVAL (otherops[2]) > -256
-                     && INTVAL (otherops[2]) < 256))
+                     && INTVAL (otherops[2]) < 256)))
                {
                  if (emit)
                    output_asm_insn ("ldrd%?\t%0, [%1], %2", otherops);
@@ -19714,7 +19718,7 @@ output_move_double (rtx *operands, bool emit, int *count)
          operands[1] = otherops[0];
          if (emit)
            {
-             if (TARGET_LDRD)
+             if (can_ldrd)
                output_asm_insn ("ldrd%?\t%0, [%1]", operands);
              else
                output_asm_insn ("ldmia%?\t%1, %M0", operands);
@@ -19759,7 +19763,7 @@ output_move_double (rtx *operands, bool emit, int *count)
                    }
                  otherops[0] = gen_rtx_REG(SImode, REGNO(operands[0]) + 1);
                  operands[1] = otherops[0];
-                 if (TARGET_LDRD
+                 if (can_ldrd
                      && (REG_P (otherops[2])
                          || TARGET_THUMB2
                          || (CONST_INT_P (otherops[2])
@@ -19820,7 +19824,7 @@ output_move_double (rtx *operands, bool emit, int *count)
              if (count)
                *count = 2;
 
-             if (TARGET_LDRD)
+             if (can_ldrd)
                return "ldrd%?\t%0, [%1]";
 
              return "ldmia%?\t%1, %M0";
index 33425f259b47ebf9262d5796b348906074412b0e..89423f5dfb9967510af0e49f0a2760aa042d7ba7 100644 (file)
@@ -1,3 +1,7 @@
+2020-05-18  Alex Coplan  <alex.coplan@arm.com>
+       * gcc.c-torture/compile/packed-aligned-1.c: New test.
+       * gcc.c-torture/execute/packed-aligned.c: New test.
+
 2020-05-18  Richard Biener  <rguenther@suse.de>
 
        PR middle-end/95171
diff --git a/gcc/testsuite/gcc.c-torture/compile/packed-aligned-1.c b/gcc/testsuite/gcc.c-torture/compile/packed-aligned-1.c
new file mode 100644 (file)
index 0000000..9f0923e
--- /dev/null
@@ -0,0 +1,11 @@
+struct c {
+  double a;
+} __attribute((packed)) __attribute((aligned));
+
+void f(struct c *, struct c);
+
+void g(struct c *ptr)
+{
+  ptr++;
+  f(ptr, *ptr);
+}
diff --git a/gcc/testsuite/gcc.c-torture/execute/packed-aligned.c b/gcc/testsuite/gcc.c-torture/execute/packed-aligned.c
new file mode 100644 (file)
index 0000000..f768af0
--- /dev/null
@@ -0,0 +1,28 @@
+struct c {
+  double a;
+} __attribute((packed)) __attribute((aligned));
+
+extern void abort(void);
+
+double g_expect = 32.25;
+
+void f(unsigned x, struct c y)
+{
+  if (x != 0)
+    abort();
+
+  if (y.a != g_expect)
+    abort();
+}
+
+struct c e = { 64.25 };
+
+int main(void)
+{
+  struct c d = { 32.25 };
+  f(0, d);
+
+  g_expect = 64.25;
+  f(0, e);
+  return 0;
+}