将CMPXCHG8B封装在GCC内联汇编中的正确方法,32位
我正在尝试为ia32的CMPXCHG8B编写GCC内联asm。不,我不能使用将CMPXCHG8B封装在GCC内联汇编中的正确方法,32位,gcc,assembly,inline-assembly,x86,Gcc,Assembly,Inline Assembly,X86,我正在尝试为ia32的CMPXCHG8B编写GCC内联asm。不,我不能使用\u同步\u bool\u比较\u和\u交换。它必须使用和不使用-fPIC 到目前为止,我所做的最好的(编辑:毕竟不起作用,请参阅下面我自己的答案了解详细信息)是 但我不确定这是否正确 由于PIC的原因,我无法对ebx值执行“b”((int32)(set&0xFFFFFFFF)),但显然寄存器asm(“ebx”)变量被编译器接受 BONUS:ret变量用于分支,因此代码最终如下所示: cmpxchg8b [edi]; s
\u同步\u bool\u比较\u和\u交换
。它必须使用和不使用-fPIC
到目前为止,我所做的最好的(编辑:毕竟不起作用,请参阅下面我自己的答案了解详细信息)是
但我不确定这是否正确
由于PIC的原因,我无法对ebx值执行“b”((int32)(set&0xFFFFFFFF))
,但显然寄存器asm(“ebx”)
变量被编译器接受
BONUS:ret变量用于分支,因此代码最终如下所示:
cmpxchg8b [edi];
setz cl;
cmp cl, 0;
je foo;
了解如何描述输出操作数,使其成为:
cmpxchg8b [edi]
jz foo
?
谢谢。这是我的:
bool
spin_lock(int64_t* lock, int64_t thread_id, int tries)
{
register int32_t pic_hack asm("ebx") = thread_id & 0xffffffff;
retry:
if (tries-- > 0) {
asm goto ("lock cmpxchg8b %0; jnz %l[retry]"
:
: "m" (*lock), "A" ((int64_t) 0),
"c" ((int32_t) (thread_id >> 32)), "r" (pic_hack)
:
: retry);
return true;
}
return false;
}
它使用了GCC4.5新增的
asm goto
特性,允许从内联程序集跳转到C标签。(哦,我看到你关于必须支持gcc旧版本的评论。哦,好吧。我试过了。:-P)令人惊讶的是,在某些情况下,问题中的代码片段仍然会出错:如果在EBX寄存器设置为
寄存器asm
之前,第0个asm操作数可以通过EBX(PIC)间接寻址,然后,gcc在将操作数分配给set&0xFFFFFFFF
后,继续通过EBX加载操作数
这就是我现在正在努力实现的代码:(编辑:避免推/弹出)
这里的想法是在关闭EBX之前加载操作数,同时在为CMPXCHG8B设置EBX值时避免任何间接寻址。我为操作数的下半部分修复了硬寄存器ESI,因为如果我不修复,GCC可以随意重用任何其他已经占用的寄存器,只要它可以证明值相等。EDI寄存器是手动保存的,因为简单地将其添加到被删除的寄存器列表中会导致GCC“无法重新加载”,这可能是由于寄存器压力高。保存EDI时避免了PUSH/POP,因为其他操作数可能是ESP寻址的。以下内容如何,在一个小测试中似乎对我有效:
int sbcas(uint64_t* ptr, uint64_t oldval, uint64_t newval)
{
int changed = 0;
__asm__ (
"push %%ebx\n\t" // -fPIC uses ebx, so save it
"mov %5, %%ebx\n\t" // load ebx with needed value
"lock\n\t"
"cmpxchg8b %0\n\t" // perform CAS operation
"setz %%al\n\t" // eax potentially modified anyway
"movzx %%al, %1\n\t" // store result of comparison in 'changed'
"pop %%ebx\n\t" // restore ebx
: "+m" (*ptr), "=r" (changed)
: "d" ((uint32_t)(oldval >> 32)), "a" ((uint32_t)(oldval & 0xffffffff)), "c" ((uint32_t)(newval >> 32)), "r" ((uint32_t)(newval & 0xffffffff))
: "flags", "memory"
);
return changed;
}
如果这也被弄错了,你能不能加入一个触发这种行为的小片段
关于奖金问题,我认为不可能使用cmpxchg8b
指令中的条件代码在汇编程序块之后进行分支(除非您使用asm goto
或类似功能)。发件人:
寻找一种方法来访问汇编指令留下的条件代码是很自然的想法。然而,当我们试图实现它时,我们发现没有办法使它可靠地工作。问题是,输出操作数可能需要重新加载,这将导致附加的“存储”指令。在大多数机器上,这些指令会在测试之前改变条件代码。普通的“测试”和“比较”指令不会出现此问题,因为它们没有任何输出操作数
编辑:在使用%N
输入值的同时,我找不到任何指定修改堆栈的方法的源代码(古代链接说“您甚至可以将寄存器推到堆栈上,使用它们,然后再放回去。”但示例中没有输入)
但也可以不通过将值固定到其他寄存器:
int sbcas(uint64_t* ptr, uint64_t oldval, uint64_t newval)
{
int changed = 0;
__asm__ (
"push %%ebx\n\t" // -fPIC uses ebx
"mov %%edi, %%ebx\n\t" // load ebx with needed value
"lock\n\t"
"cmpxchg8b (%%esi)\n\t"
"setz %%al\n\t" // eax potentially modified anyway
"movzx %%al, %1\n\t"
"pop %%ebx\n\t"
: "+S" (ptr), "=a" (changed)
: "0" (ptr), "d" ((uint32_t)(oldval >> 32)), "a" ((uint32_t)(oldval & 0xffffffff)), "c" ((uint32_t)(newval >> 32)), "D" ((uint32_t)(newval & 0xffffffff))
: "flags", "memory"
);
return changed;
}
编译器内部不与-fPIC一起工作的事实只是一个彻底的编译器错误:当你不得不处理一个坏掉的编译器时,它很糟糕,所以你可能想把自己放在该错误的CC列表上。你在IA32上使用
-fPIC
?我很好奇为什么。@Gabe-这在编写共享库对象时是最重要的。Ulrich Drepper在这方面有一篇很好的论文:@Crash:这是一篇有趣的论文,但它似乎没有提到PIC在IA32上的相对优势。我知道它在链接方面有优势——加载速度更快,占用的内存更少——但共享库通常每个进程只加载一次,现在的计算机往往有千兆字节的内存。我可以理解为什么您希望在相对便宜的x64或IA64上使用PIC,但IA32(x86)呢?@Crashworks,即使不存在该缺陷,我也无法使用内置代码:(必须支持非常旧的编译器。谢谢!我喜欢我的代码已经与你的代码非常相似,尽管我当然不能使用asm goto。不过有几个问题:1)为什么*锁操作数仅为输入而不是输入/输出?2)为什么EFLAGS不在已关闭的寄存器列表中?@Laurynas:1<代码>asm goto不能有任何输出限制(当前限制可能在以后的gcc版本中删除);因为我不“关心”锁的当前值(我们不尝试递归锁定;-),所以这是可以接受的。2.因为asm goto
的示例中也没有它(是的,它也有条件跳转),所以我假设asm goto
默认情况下会有一个clobbered标志。对于未来的读者:gcc对x86和x86-64的机器定义使得每个内联asm语句都隐式包含一个“cc”
clobber。您永远不需要为x86 asm显式编写一个。asm语句确实会修改内容,因此它需要使用“memory”
clobber,并在它可以更改的所有reg上注册clobber。此外,为了实际使用,在重试情况下,通常最好在暂停
+非锁定加载上旋转,而不是用锁定
ed指令敲打(导致争用,从而延迟线程尝试解锁)。我知道您仅将cmpxchg8b
用于自旋锁作为示例来演示asm goto
,但不需要将cmpxchg用于自旋锁。非常感谢。我看到的一个问题与我的代码段相同:编译器可能通过ESP寻址%0,但无法判断ESP是否已通过push/pop更改。另外,谢谢你提供的信息。输出中的条件代码,它已经证实了我的怀疑。我不知道
int sbcas(uint64_t* ptr, uint64_t oldval, uint64_t newval)
{
int changed = 0;
__asm__ (
"push %%ebx\n\t" // -fPIC uses ebx, so save it
"mov %5, %%ebx\n\t" // load ebx with needed value
"lock\n\t"
"cmpxchg8b %0\n\t" // perform CAS operation
"setz %%al\n\t" // eax potentially modified anyway
"movzx %%al, %1\n\t" // store result of comparison in 'changed'
"pop %%ebx\n\t" // restore ebx
: "+m" (*ptr), "=r" (changed)
: "d" ((uint32_t)(oldval >> 32)), "a" ((uint32_t)(oldval & 0xffffffff)), "c" ((uint32_t)(newval >> 32)), "r" ((uint32_t)(newval & 0xffffffff))
: "flags", "memory"
);
return changed;
}
int sbcas(uint64_t* ptr, uint64_t oldval, uint64_t newval)
{
int changed = 0;
__asm__ (
"push %%ebx\n\t" // -fPIC uses ebx
"mov %%edi, %%ebx\n\t" // load ebx with needed value
"lock\n\t"
"cmpxchg8b (%%esi)\n\t"
"setz %%al\n\t" // eax potentially modified anyway
"movzx %%al, %1\n\t"
"pop %%ebx\n\t"
: "+S" (ptr), "=a" (changed)
: "0" (ptr), "d" ((uint32_t)(oldval >> 32)), "a" ((uint32_t)(oldval & 0xffffffff)), "c" ((uint32_t)(newval >> 32)), "D" ((uint32_t)(newval & 0xffffffff))
: "flags", "memory"
);
return changed;
}