在GCC内联汇编中包装CMPXCHG8B的正确方法,32位

时间:2011-07-20 04:23:27

标签: gcc assembly x86 inline-assembly ia-32

我正在尝试为ia32编写GCC内联asm for CMPXCHG8B。不,我不能使用__sync_bool_compare_and_swap。它必须与-fPIC一起使用。

到目前为止,我所做的最好(编辑:毕竟不起作用,详见下面我自己的答案)是

register int32 ebx_val asm("ebx")= set & 0xFFFFFFFF;
asm ("lock; cmpxchg8b %0;"
     "setz %1;"
     : "+m" (*a), "=q" (ret), "+A" (*cmp)
     : "r" (ebx_val), "c" ((int32)(set >> 32))
     : "flags")

但是我不确定这是否正确。

由于PIC,我无法为ebx_val执行"b" ((int32)(set & 0xFFFFFFFF)),但显然编译器接受了register asm("ebx")变量。

BONUS :ret变量用于分支,因此代码最终看起来像这样:

cmpxchg8b [edi];
setz cl;
cmp cl, 0;
je foo;

任何想法如何描述输出操作数,使其变为:

cmpxchg8b [edi]
jz foo

谢谢。

3 个答案:

答案 0 :(得分:2)

这就是我所拥有的:

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;
}

它使用asm goto功能,新功能与gcc 4.5,允许从内联汇编跳转到C标签。 (哦,我看到你关于必须支持旧版gcc的评论。好吧。我试过了。:-P)

答案 1 :(得分:2)

下面的内容如何,​​在一个小小的测试中似乎对我有用:

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或类似功能)。来自GNU C Language Extensions

  

寻找一种方法来访问汇编程序指令留下的条件代码是一个很自然的想法。但是,当我们尝试实现这一点时,我们发现无法使其可靠地工作。问题是输出操作数可能需要重新加载,这将导致额外的后续“存储”指令。在大多数机器上,这些指令会在有时间测试之前改变条件代码。普通的“测试”和“比较”指令不会出现这个问题,因为它们没有任何输出操作数。

编辑:我找不到任何指定这种或那种方式的源是否可以修改堆栈同时使用%N输入值(This古代链接说“你可以甚至将你的寄存器推入堆栈,使用它们并将它们放回去。“但是这个例子没有输入。”

但是应该可以通过将值固定到其他寄存器来实现:

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;
}

答案 2 :(得分:1)

令人惊讶的是,问题中的代码片段在某些情况下仍会被错误编译:如果在使用register asm设置EBX寄存器之前,通过EBX(PIC)可以间接寻址第0个asm操作数,那么gcc在将操作数分配给set & 0xFFFFFFFF后,继续通过EBX加载操作数!

这是我正在尝试制作的代码:(编辑:避免推/弹)

asm ("movl %%edi, -4(%%esp);"
     "leal %0, %%edi;" 
     "xchgl %%ebx, %%esi;"
     "lock; cmpxchg8b (%%edi);" // Sets ZF
     "movl %%esi, %%ebx;"       // Preserves ZF
     "movl -4(%%esp), %%edi;"   // Preserves ZF
     "setz %1;"                 // Reads ZF
     : "+m" (*a), "=q" (ret), "+A" (*cmp)
     : "S" ((int32)(set & 0xFFFFFFFF)), "c" ((int32)(set >> 32))
     : "flags")

这里的想法是在破坏EBX之前加载操作数,同时在为CMPXCHG8B设置EBX值时避免任何间接寻址。我为操作数的下半部分修复了硬寄存器ESI,因为如果我不这样做,GCC可以随意重用任何其他已经采用的寄存器,如果它可以证明值相等。 EDI寄存器是手动保存的,因为只需将其添加到修改后的寄存器列表中,就可能会因“寄存器压力过高”导致GCC“无法重新加载”。在保存EDI时避免使用PUSH / POP,因为其他操作数可能是ESP寻址的。