Fix compare_double_and_swap_double for clang/x86 in PIC mode
authorIvan Maidanski <ivmai@mail.ru>
Wed, 5 Sep 2012 04:25:24 +0000 (08:25 +0400)
committerIvan Maidanski <ivmai@mail.ru>
Wed, 5 Sep 2012 12:03:04 +0000 (16:03 +0400)
* src/atomic_ops/sysdeps/gcc/x86.h
(AO_compare_double_and_swap_double_full): Re-implement for PIC mode
saving EBX to a local variable (instead of pushing it to stack) and
saving memory operand address to a register (edi which is manually
preserved), so that the whole code could also work even if EBX points
to memory operand (e.g., in Clang); test __PIC__ using ifdef (instead
of "if"); update comments; reformat code.

src/atomic_ops/sysdeps/gcc/x86.h

index 54c234d..75d822a 100644 (file)
@@ -182,42 +182,55 @@ AO_compare_double_and_swap_double_full(volatile AO_double_t *addr,
                                        AO_t new_val1, AO_t new_val2)
 {
   char result;
-#if __PIC__
-  /* If PIC is turned on, we can't use %ebx as it is reserved for the   */
-  /* GOT pointer.  We can save and restore %ebx because GCC won't be    */
-  /* using it for anything else (such as any of the m operands).        */
-# if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)
-    /* Starting from GCC 4.3, we use %edi (for new_val1) instead of a   */
-    /* memory operand and swap instruction instead of push/pop because  */
-    /* some GCC releases have a bug in processing memory operands (if   */
-    /* address base is %esp) in the inline assembly after push.         */
-    __asm__ __volatile__("xchg %%ebx,%6;" /* swap GOT ptr and new_val1 */
-                       "lock; cmpxchg8b %0; setz %1;"
-                       "xchg %%ebx,%6;" /* restore ebx and edi */
-                       : "=m"(*addr), "=a"(result)
-                       : "m"(*addr), "d" (old_val2), "a" (old_val1),
-                         "c" (new_val2), "D" (new_val1) : "memory");
+# ifdef __PIC__
+    AO_t saved_ebx;
+
+    /* If PIC is turned on, we cannot use ebx as it is reserved for the */
+    /* GOT pointer.  We should save and restore ebx.  The proposed      */
+    /* solution is not so efficient as the older alternatives using     */
+    /* push ebx or edi as new_val1 (w/o clobbering edi and temporary    */
+    /* local variable usage) but it is more portable (it works even if  */
+    /* ebx is not used as GOT pointer, and it works for the buggy GCC   */
+    /* releases that incorrectly evaluate memory operands offset in the */
+    /* inline assembly after push).                                     */
+#   ifdef __OPTIMIZE__
+      __asm__ __volatile__("mov %%ebx, %2\n\t" /* save ebx */
+                           "lea %0, %%edi\n\t" /* in case addr is in ebx */
+                           "mov %7, %%ebx\n\t" /* load new_val1 */
+                           "lock; cmpxchg8b (%%edi)\n\t"
+                           "mov %2, %%ebx\n\t" /* restore ebx */
+                           "setz %1"
+                        : "=m" (*addr), "=a" (result), "=m" (saved_ebx)
+                        : "m" (*addr), "d" (old_val2), "a" (old_val1),
+                          "c" (new_val2), "m" (new_val1)
+                        : "%edi", "memory");
+#   else
+      /* A less-efficient code manually preserving edi if GCC invoked   */
+      /* with -O0 option (otherwise it fails while finding a register   */
+      /* in class 'GENERAL_REGS').                                      */
+      AO_t saved_edi;
+      __asm__ __volatile__("mov %%edi, %3\n\t" /* save edi */
+                           "mov %%ebx, %2\n\t" /* save ebx */
+                           "lea %0, %%edi\n\t" /* in case addr is in ebx */
+                           "mov %8, %%ebx\n\t" /* load new_val1 */
+                           "lock; cmpxchg8b (%%edi)\n\t"
+                           "mov %2, %%ebx\n\t" /* restore ebx */
+                           "mov %3, %%edi\n\t" /* restore edi */
+                           "setz %1"
+                        : "=m" (*addr), "=a" (result),
+                          "=m" (saved_ebx), "=m" (saved_edi)
+                        : "m" (*addr), "d" (old_val2), "a" (old_val1),
+                          "c" (new_val2), "m" (new_val1) : "memory");
+#   endif
 # else
-    /* For older compiler releases, we continue to use push/pop as at   */
-    /* least GCC 4.2.1 does not recognize 'D' as a valid register name. */
-    __asm__ __volatile__("pushl %%ebx;" /* save ebx used for PIC GOT ptr */
-                       "movl %6,%%ebx;" /* move new_val1 to %ebx */
-                       "lock; cmpxchg8b %0; setz %1;"
-                       "pop %%ebx;"     /* restore %ebx */
-                       : "=m"(*addr), "=a"(result)
-                       : "m"(*addr), "d" (old_val2), "a" (old_val1),
-                         "c" (new_val2), "m" (new_val1) : "memory");
-# endif
-#else
-  /* We can't just do the same thing in non-PIC mode, because GCC
-   * might be using %ebx as the memory operand.  We could have ifdef'd
-   * in a clobber, but there's no point doing the push/pop if we don't
-   * have to. */
-  __asm__ __volatile__("lock; cmpxchg8b %0; setz %1;"
-                       : "=m"(*addr), "=a"(result)
-                       : "m"(*addr), "d" (old_val2), "a" (old_val1),
+    /* For non-PIC mode, this operation could be simplified (and be     */
+    /* faster) by using ebx as new_val1 (GCC would refuse to compile    */
+    /* such code for PIC mode).                                         */
+    __asm__ __volatile__("lock; cmpxchg8b %0; setz %1"
+                       : "=m" (*addr), "=a" (result)
+                       : "m" (*addr), "d" (old_val2), "a" (old_val1),
                          "c" (new_val2), "b" (new_val1) : "memory");
-#endif
+# endif
   return (int) result;
 }
 #define AO_HAVE_compare_double_and_swap_double_full