x86-64 零标志在内联调用之间清除(和另一个问题)

Dea*_*n P 0 c x86 assembly gcc inline-assembly

我正在使用此处bsf找到的英特尔开发人员手册第 210 页上的x86-64 指令。本质上,如果找到最低有效位 1,则将其位索引存储在目标操作数中。

此外,如果所有源操作数为 0,则 ZF 标志设置为 1;否则,ZF 标志被清除。

我正在使用内联 x86-64 汇编指令编译我的 C 代码。我已经定义了一个调用bsf指令的 C 函数:

uint64_t bitScanForward(T_bitboard b) {
    __asm__(
       "bsf %rcx,%rax\n"
       "leave\n"
       "ret\n"
    );
}
Run Code Online (Sandbox Code Playgroud)

还有另一个 C 函数,它检查标志寄存器中 ZF 位的状态:

uint64_t isZFSet() {
    printf("\n"); <- This is another problem I am having (see below)...
    __asm__(
        "jz true\n"
        "movq $0,%rax\n"//return false
        "jmp end\n"
        "true:\n"
        "movq $1,%rax\n"//return true
        "end:\n"
        "leave\n"
        "ret\n"
    );
}
Run Code Online (Sandbox Code Playgroud)

我已经对这些进行了测试,发现即使将 bsf 命令应用于数字零,ZF 标志也始终被清除,这似乎违反了规范。

//Calling function...
//Do stuff...
bitScanForward(0ULL);//ULL is 64 bit on my machine
if(isZFSet()){//ZF flag *should* be set here but its not
   printf("ZF flag is set\n");
}
//More stuff...
Run Code Online (Sandbox Code Playgroud)

我怀疑清除 ZF 标志的原因是将一组内联指令输入和保留给另一组指令。

如何确保上述代码中的标志设置为文档中指定的?(我不想改变我的大部分代码或设计)

我的“另一个问题”是,如果我不在 isZFFlagSet 中包含 printf 语句,该函数似乎不会执行。完全奇怪。谁能解释为什么?

zwo*_*wol 9

您将积极优化的 C 编译器视为宏汇编程序。那样简单是行不通的。为了让 GCC 在存在汇编插入的情况下发出正确的代码,您必须使用有关受汇编代码影响的寄存器和内存区域的完整信息来注释插入,并且必须使用辅助 C 语句将它们与周围代码。即便如此,也有装配插件根本无法完成的事情。我敦促你摒弃整个混乱,而是使用 __builtin_ctzll内在的,正如对问题的评论中所建议的那样。

现在,具体来说。您的第一个函数不正确,因为 GCC 不支持使用leaveret在程序集插入内部。(更一般地说,程序集插入可能不会改变堆栈指针,并且只能跳转到同一函数内的指定标签。)bsf从 GCC 风格的程序集插入中使用的正确方法是使用带有输入和输出操作数的“扩展 asm ”:

uint64_t bitScanForward(uint64_t b) {
    uint64_t ret;
    asm ("bsf %1, %0" : "=r" (ret) : "r" (b));
    return ret;
}
Run Code Online (Sandbox Code Playgroud)

必须声明一个 C 变量来接收操作的输出,并明确声明return该变量;有bsf%rax不会(与它是如何在旧MSVC)工作。BSF 接受任意两个寄存器作为操作数,因此无需使用比r.

您的第二个函数不正确,因为您没有告诉 GCC 条件代码在 之后有意义bitScanForward并且因为 GCC 不支持使用条件代码寄存器作为程序集插入的输入。为了从bsf您的 ZF 输出中读取您必须在调用的同一程序集插入中执行此操作bsf

uint64_t countTrailingZeroes(uint64_t b) {
    uint64_t ret;
    asm ("bsf %1, %0\n\t"
         "cmove %2, %0"  
         : "=&r" (ret) 
         : "r" (b), "rm" (64));
    return ret;
}
Run Code Online (Sandbox Code Playgroud)

这需要特别注意——看看操作数 0 上的约束现在如何=&r而不是仅仅=r?否则,GCC 很容易认为它可以将操作数 2 与操作数 0 放在同一个寄存器中。

或者,您可以指定 ZF 是一个output,它受支持的(请参阅手册的“标志输出操作数”部分),然后从 C 提供默认值:

uint64_t countTrailingZeroes(uint64_t b) {
    uint64_t ret;
    int zf;
    asm ("bsf %2, %0"  
         : "=r" (ret), "=@ccz" (zf) : "r" (b));
    if (zf) ret = 64;
    return ret;
}
Run Code Online (Sandbox Code Playgroud)