我有这个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也是如此。
有没有人知道这个bug的解决方法?或者我误解了关于gcc的内联asm的基本信息(这不会让我感到惊讶)?
答案 0 :(得分:4)
o
约束意味着一个小整数可以添加到该地址,结果也是一个有效的内存地址,所以正确的表达式为{ {1}}。如果生成的地址恰好使用4+%1
之类的负偏移量,那么您的版本可能会意外运行,在这种情况下,替换后它将变为-8(%ebp)
。如果偏移是正的,就像在破碎的情况4-8%(ebp)
中一样,它当然会扩展为8(%esp)
,这是错误的。 48(%esp)
在两种情况下均可正常使用,因为4+%1
与4+-8(%esp)
一样有效。这与编译器版本没有直接关系。
也就是说,这个内联asm效率不高,如果你只是将它们声明为输出并将它留给编译器来处理,那么可以避免存储4+8(%esp)
和eax
的整个业务。它:
edx
另请注意,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;
}
和ebx
都没有被修改,所以没有必要将它们列为clobbers,当然也没有触及其他内存,因此ecx
也可以删除。
以上所有都不是必需的,因为gcc有原子内置,所以整个事情只归结为memory
。编译器还知道__atomic_load_n(target, __ATOMIC_SEQ_CST)
很慢,并且可以根据目标环境选择更有效的指令。这种内置也更便携。