diff --git a/gcc/ChangeLog b/gcc/ChangeLog
index 7fec75887b5ab11800df6f1b2c24eb980e6d6be7..42dc4316b4b246d828d16483c2e288a3f15a5ab8 100644
--- a/gcc/ChangeLog
+++ b/gcc/ChangeLog
@@ -1,3 +1,10 @@
+2009-08-05  Uros Bizjak  <ubizjak@gmail.com>
+	    Mikulas Patocka  <mikulas@artax.karlin.mff.cuni.cz>
+
+	PR target/40906
+	* config/i386/i386.c (ix86_split_long_move): Fix push of multi-part
+	source operand.
+
 2009-08-05  Jakub Jelinek  <jakub@redhat.com>
 
 	PR rtl-optimization/40924
diff --git a/gcc/config/i386/i386.c b/gcc/config/i386/i386.c
index ff6373ef00e3ec211a824c177fcecc3ce8a70085..d0a8ba65f760b0746215f6f570d84af84c0eb8ae 100644
--- a/gcc/config/i386/i386.c
+++ b/gcc/config/i386/i386.c
@@ -16764,10 +16764,20 @@ ix86_split_long_move (rtx operands[])
   /* When emitting push, take care for source operands on the stack.  */
   if (push && MEM_P (operands[1])
       && reg_overlap_mentioned_p (stack_pointer_rtx, operands[1]))
-    for (i = 0; i < nparts - 1; i++)
-      part[1][i] = change_address (part[1][i],
-				   GET_MODE (part[1][i]),
-				   XEXP (part[1][i + 1], 0));
+    {
+      rtx src_base = XEXP (part[1][nparts - 1], 0);
+
+      /* Compensate for the stack decrement by 4.  */
+      if (!TARGET_64BIT && nparts == 3
+	  && mode == XFmode && TARGET_128BIT_LONG_DOUBLE)
+	src_base = plus_constant (src_base, 4);
+
+      /* src_base refers to the stack pointer and is
+	 automatically decreased by emitted push.  */
+      for (i = 0; i < nparts; i++)
+	part[1][i] = change_address (part[1][i],
+				     GET_MODE (part[1][i]), src_base);
+    }
 
   /* We need to do copy in the right order in case an address register
      of the source overlaps the destination.  */
@@ -16837,7 +16847,8 @@ ix86_split_long_move (rtx operands[])
 	  if (nparts == 3)
 	    {
 	      if (TARGET_128BIT_LONG_DOUBLE && mode == XFmode)
-                emit_insn (gen_addsi3 (stack_pointer_rtx, stack_pointer_rtx, GEN_INT (-4)));
+                emit_insn (gen_addsi3 (stack_pointer_rtx,
+				       stack_pointer_rtx, GEN_INT (-4)));
 	      emit_move_insn (part[0][2], part[1][2]);
 	    }
 	  else if (nparts == 4)
diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog
index e34aa8d8b2e91c2408107d07ab3e3f0198cfe254..050d1dfda48913f6a288797fd1419bd0f61a8ba4 100644
--- a/gcc/testsuite/ChangeLog
+++ b/gcc/testsuite/ChangeLog
@@ -1,3 +1,11 @@
+2009-08-05  Uros Bizjak  <ubizjak@gmail.com>
+	    Mikulas Patocka  <mikulas@artax.karlin.mff.cuni.cz>
+
+	PR target/40906
+	* gcc.target/i386/pr40906-1.c: New test.
+	* gcc.target/i386/pr40906-2.c: Ditto.
+	* gcc.target/i386/pr40906-3.c: Ditto.
+
 2009-08-05  Jakub Jelinek  <jakub@redhat.com>
 
 	PR rtl-optimization/40924
diff --git a/gcc/testsuite/gcc.target/i386/pr40906-1.c b/gcc/testsuite/gcc.target/i386/pr40906-1.c
new file mode 100644
index 0000000000000000000000000000000000000000..c14bbfa3a4fa3ff937d66a605992fd5d89215c56
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr40906-1.c
@@ -0,0 +1,26 @@
+/* { dg-do run } */
+/* { dg-require-effective-target ilp32 } */
+/* { dg-options "-O2 -fomit-frame-pointer -mpush-args -mno-accumulate-outgoing-args" } */
+
+void abort (void);
+
+void __attribute__((noinline))
+f (long double a)
+{
+  if (a != 1.23L)
+    abort ();
+}
+
+int __attribute__((noinline))
+g (long double b)
+{
+  f (b);
+  return 0;
+}
+
+int
+main (void)
+{
+  g (1.23L);
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/i386/pr40906-2.c b/gcc/testsuite/gcc.target/i386/pr40906-2.c
new file mode 100644
index 0000000000000000000000000000000000000000..66e146b0ca015077546f79ad05551510d018f88b
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr40906-2.c
@@ -0,0 +1,26 @@
+/* { dg-do run } */
+/* { dg-require-effective-target ilp32 } */
+/* { dg-options "-O2 -fomit-frame-pointer -mpush-args -mno-accumulate-outgoing-args -m128bit-long-double" } */
+
+void abort (void);
+
+void __attribute__((noinline))
+f (long double a)
+{
+  if (a != 1.23L)
+    abort ();
+}
+
+int __attribute__((noinline))
+g (long double b)
+{
+  f (b);
+  return 0;
+}
+
+int
+main (void)
+{
+  g (1.23L);
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/i386/pr40906-3.c b/gcc/testsuite/gcc.target/i386/pr40906-3.c
new file mode 100644
index 0000000000000000000000000000000000000000..f95308d108dc4fbb625f0b0eac1ef3522b7a826d
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr40906-3.c
@@ -0,0 +1,25 @@
+/* { dg-do run } */
+/* { dg-require-effective-target ilp32 } */
+/* { dg-options "-O2 -fomit-frame-pointer -msse2 -mpush-args -mno-accumulate-outgoing-args" } */
+
+#include "sse2-check.h"
+
+void __attribute__((noinline))
+f (__float128 a)
+{
+  if (a != 1.23Q)
+    abort ();
+}
+
+int __attribute__((noinline))
+g (__float128 b)
+{
+  f (b);
+  return 0;
+}
+
+static void
+sse2_test (void)
+{
+  g (1.23Q);
+}