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

x86-64 Zero Flag is clearing between inline calls (and another problem)

我正在使用 here 英特尔开发人员手册第 210 页上的 bsf 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"
    );
}

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

uint64_t isZFSet() {
    printf("\n"); <- This is another problem I am having (see below)...
    __asm__(
        "jz true\n"
        "movq [=11=],%rax\n"//return false
        "jmp end\n"
        "true:\n"
        "movq ,%rax\n"//return true
        "end:\n"
        "leave\n"
        "ret\n"
    );
}

我测试了这些,发现即使将 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...

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

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

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

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

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

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

必须 声明一个 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;
}

这需要特别注意——看看操作数 0 的约束现在是 =&r 而不是 =r?否则,GCC 可能会认为它可以将操作数 2 放入与操作数 0 相同的寄存器中。

或者,您可以指定 ZF 是一个 output 受支持(参见“flag output operands”部分手册),然后从 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;
}