gcc 5.3 内联汇编程序错误?

gcc 5.3 inline assembler bug?

我有这个 32 位代码:

unsigned long long load(volatile unsigned long long *target) {
   unsigned long long result;

   __asm__ __volatile__
       (
       "movl %%ecx, %%edx\n\t"
       "movl %%ebx, %%eax\n\t"
       "lock cmpxchg8b %0\n\t"
       "movl %%edx, 4%1\n\t"
       "movl %%eax, %1\n\t"
       : "+m" (*target)
       : "o" (result)
       : "eax", "ebx", "ecx", "edx", "memory", "cc"
       );
   return result;
}

当它用 gcc 5.3 版编译时,代码的尾部生成此汇编代码(为清楚起见略作编辑):

lock cmpxchg8b (%esi)
movl %edx, 48(%esp)
movl %eax, 8(%esp)
movl    8(%esp), %eax
movl    12(%esp), %edx

调用 cmpxchg8b 的结果在 EDX:EAX 中。生成的代码将 EDX 存储在 48(%esp),从 12(%esp) 重新加载 EDX,因此返回值是无意义的。其他版本的 gcc 可以做到这一点。

有人知道这个错误的解决方法吗?还是我误解了有关 gcc 的内联汇编的一些基本知识(这不会让我感到惊讶)?

o约束意味着一个小整数可以添加到地址,结果也是一个有效的内存地址,所以正确的表达是4+%1。如果生成的地址恰好使用负偏移量(例如 -8(%ebp)),您的版本可能会意外工作,在这种情况下,替换后它变为 4-8%(ebp)。如果偏移量是正数,就像在损坏的情况下 8(%esp) 它当然会扩展到 48(%esp) 这是错误的。 4+%1 在这两种情况下都能正常工作,因为 4+-8(%esp)4+8(%esp) 一样有效。这与编译器版本没有直接关系。

就是说,这个内联 asm 不是很有效,如果您只是将它们声明为输出并将其留给编译器来处理,则可以避免存储 eaxedx 的整个过程照顾它:

unsigned long long load(volatile unsigned long long *target) {
   unsigned long long result;

   __asm__ __volatile__
       (
       "movl %%ecx, %%edx\n\t"
       "movl %%ebx, %%eax\n\t"
       "lock cmpxchg8b %0\n\t"
       : "+m" (*target), "=&A" (result)
       :
       : "cc"
       );
   return result;
}

另请注意,ebxecx 都没有被修改,因此将它们列为 clobbers 毫无意义,当然也没有触及其他内存,因此 memory 也可以删除。

以上所有都不是真正必要的,因为 gcc 具有原子内置函数,所以整个事情归结为 __atomic_load_n(target, __ATOMIC_SEQ_CST)。编译器也知道 lock cmpxchg8b 很慢,并且可以 select 更有效的指令以适合目标环境。这个内置函数也更便携。