Commit c32074f6 by Alex Coplan

arm: Don't generate invalid LDRD insns

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

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.

(cherry picked from commit 8b8f3117263ca79b3febadadb07732588d99d5f6)
parent 6e1d5e76
...@@ -19623,6 +19623,7 @@ output_move_double (rtx *operands, bool emit, int *count) ...@@ -19623,6 +19623,7 @@ output_move_double (rtx *operands, bool emit, int *count)
if (code0 == REG) if (code0 == REG)
{ {
unsigned int reg0 = REGNO (operands[0]); 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); otherops[0] = gen_rtx_REG (SImode, 1 + reg0);
...@@ -19634,7 +19635,7 @@ output_move_double (rtx *operands, bool emit, int *count) ...@@ -19634,7 +19635,7 @@ output_move_double (rtx *operands, bool emit, int *count)
if (emit) if (emit)
{ {
if (TARGET_LDRD if (can_ldrd
&& !(fix_cm3_ldrd && reg0 == REGNO(XEXP (operands[1], 0)))) && !(fix_cm3_ldrd && reg0 == REGNO(XEXP (operands[1], 0))))
output_asm_insn ("ldrd%?\t%0, [%m1]", operands); output_asm_insn ("ldrd%?\t%0, [%m1]", operands);
else else
...@@ -19643,7 +19644,7 @@ output_move_double (rtx *operands, bool emit, int *count) ...@@ -19643,7 +19644,7 @@ output_move_double (rtx *operands, bool emit, int *count)
break; break;
case PRE_INC: case PRE_INC:
gcc_assert (TARGET_LDRD); gcc_assert (can_ldrd);
if (emit) if (emit)
output_asm_insn ("ldrd%?\t%0, [%m1, #8]!", operands); output_asm_insn ("ldrd%?\t%0, [%m1, #8]!", operands);
break; break;
...@@ -19651,7 +19652,7 @@ output_move_double (rtx *operands, bool emit, int *count) ...@@ -19651,7 +19652,7 @@ output_move_double (rtx *operands, bool emit, int *count)
case PRE_DEC: case PRE_DEC:
if (emit) if (emit)
{ {
if (TARGET_LDRD) if (can_ldrd)
output_asm_insn ("ldrd%?\t%0, [%m1, #-8]!", operands); output_asm_insn ("ldrd%?\t%0, [%m1, #-8]!", operands);
else else
output_asm_insn ("ldmdb%?\t%m1!, %M0", operands); output_asm_insn ("ldmdb%?\t%m1!, %M0", operands);
...@@ -19661,7 +19662,7 @@ output_move_double (rtx *operands, bool emit, int *count) ...@@ -19661,7 +19662,7 @@ output_move_double (rtx *operands, bool emit, int *count)
case POST_INC: case POST_INC:
if (emit) if (emit)
{ {
if (TARGET_LDRD) if (can_ldrd)
output_asm_insn ("ldrd%?\t%0, [%m1], #8", operands); output_asm_insn ("ldrd%?\t%0, [%m1], #8", operands);
else else
output_asm_insn ("ldmia%?\t%m1!, %M0", operands); output_asm_insn ("ldmia%?\t%m1!, %M0", operands);
...@@ -19669,7 +19670,7 @@ output_move_double (rtx *operands, bool emit, int *count) ...@@ -19669,7 +19670,7 @@ output_move_double (rtx *operands, bool emit, int *count)
break; break;
case POST_DEC: case POST_DEC:
gcc_assert (TARGET_LDRD); gcc_assert (can_ldrd);
if (emit) if (emit)
output_asm_insn ("ldrd%?\t%0, [%m1], #-8", operands); output_asm_insn ("ldrd%?\t%0, [%m1], #-8", operands);
break; break;
...@@ -19691,6 +19692,7 @@ output_move_double (rtx *operands, bool emit, int *count) ...@@ -19691,6 +19692,7 @@ output_move_double (rtx *operands, bool emit, int *count)
/* Registers overlap so split out the increment. */ /* Registers overlap so split out the increment. */
if (emit) if (emit)
{ {
gcc_assert (can_ldrd);
output_asm_insn ("add%?\t%1, %1, %2", otherops); output_asm_insn ("add%?\t%1, %1, %2", otherops);
output_asm_insn ("ldrd%?\t%0, [%1] @split", otherops); output_asm_insn ("ldrd%?\t%0, [%1] @split", otherops);
} }
...@@ -19702,10 +19704,11 @@ output_move_double (rtx *operands, bool emit, int *count) ...@@ -19702,10 +19704,11 @@ output_move_double (rtx *operands, bool emit, int *count)
/* Use a single insn if we can. /* Use a single insn if we can.
FIXME: IWMMXT allows offsets larger than ldrd can FIXME: IWMMXT allows offsets larger than ldrd can
handle, fix these up with a pair of ldr. */ handle, fix these up with a pair of ldr. */
if (TARGET_THUMB2 if (can_ldrd
&& (TARGET_THUMB2
|| !CONST_INT_P (otherops[2]) || !CONST_INT_P (otherops[2])
|| (INTVAL (otherops[2]) > -256 || (INTVAL (otherops[2]) > -256
&& INTVAL (otherops[2]) < 256)) && INTVAL (otherops[2]) < 256)))
{ {
if (emit) if (emit)
output_asm_insn ("ldrd%?\t%0, [%1, %2]!", otherops); output_asm_insn ("ldrd%?\t%0, [%1, %2]!", otherops);
...@@ -19728,10 +19731,11 @@ output_move_double (rtx *operands, bool emit, int *count) ...@@ -19728,10 +19731,11 @@ output_move_double (rtx *operands, bool emit, int *count)
/* Use a single insn if we can. /* Use a single insn if we can.
FIXME: IWMMXT allows offsets larger than ldrd can handle, FIXME: IWMMXT allows offsets larger than ldrd can handle,
fix these up with a pair of ldr. */ fix these up with a pair of ldr. */
if (TARGET_THUMB2 if (can_ldrd
&& (TARGET_THUMB2
|| !CONST_INT_P (otherops[2]) || !CONST_INT_P (otherops[2])
|| (INTVAL (otherops[2]) > -256 || (INTVAL (otherops[2]) > -256
&& INTVAL (otherops[2]) < 256)) && INTVAL (otherops[2]) < 256)))
{ {
if (emit) if (emit)
output_asm_insn ("ldrd%?\t%0, [%1], %2", otherops); output_asm_insn ("ldrd%?\t%0, [%1], %2", otherops);
...@@ -19762,7 +19766,7 @@ output_move_double (rtx *operands, bool emit, int *count) ...@@ -19762,7 +19766,7 @@ output_move_double (rtx *operands, bool emit, int *count)
operands[1] = otherops[0]; operands[1] = otherops[0];
if (emit) if (emit)
{ {
if (TARGET_LDRD) if (can_ldrd)
output_asm_insn ("ldrd%?\t%0, [%1]", operands); output_asm_insn ("ldrd%?\t%0, [%1]", operands);
else else
output_asm_insn ("ldmia%?\t%1, %M0", operands); output_asm_insn ("ldmia%?\t%1, %M0", operands);
...@@ -19807,7 +19811,7 @@ output_move_double (rtx *operands, bool emit, int *count) ...@@ -19807,7 +19811,7 @@ output_move_double (rtx *operands, bool emit, int *count)
} }
otherops[0] = gen_rtx_REG(SImode, REGNO(operands[0]) + 1); otherops[0] = gen_rtx_REG(SImode, REGNO(operands[0]) + 1);
operands[1] = otherops[0]; operands[1] = otherops[0];
if (TARGET_LDRD if (can_ldrd
&& (REG_P (otherops[2]) && (REG_P (otherops[2])
|| TARGET_THUMB2 || TARGET_THUMB2
|| (CONST_INT_P (otherops[2]) || (CONST_INT_P (otherops[2])
...@@ -19868,7 +19872,7 @@ output_move_double (rtx *operands, bool emit, int *count) ...@@ -19868,7 +19872,7 @@ output_move_double (rtx *operands, bool emit, int *count)
if (count) if (count)
*count = 2; *count = 2;
if (TARGET_LDRD) if (can_ldrd)
return "ldrd%?\t%0, [%1]"; return "ldrd%?\t%0, [%1]";
return "ldmia%?\t%1, %M0"; return "ldmia%?\t%1, %M0";
......
struct c {
double a;
} __attribute((packed)) __attribute((aligned));
void f(struct c *, struct c);
void g(struct c *ptr)
{
ptr++;
f(ptr, *ptr);
}
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;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment