在 GCC 内联汇编中检索 ZF

Posted

技术标签:

【中文标题】在 GCC 内联汇编中检索 ZF【英文标题】:Retrieving the ZF in GCC inline assembly 【发布时间】:2011-08-01 08:10:46 【问题描述】:

我需要使用一些没有 GCC 内在函数的 x86 指令,例如 BSF 和 BSR。 使用 GCC 内联汇编,我可以编写如下内容

__INTRIN_INLINE unsigned char bsf64(unsigned long* const index, const uint64_t mask)

__asm__("bsf %[mask], %[index]" : [index] "=r" (*index) : [mask] "mr" (mask));
return mask ? 1 : 0;

if (bsf64(x, y)) /* use x */ 这样的代码被 GCC 翻译成类似的东西

0x000000010001bf04 <bsf64+0>:   bsf    %rax,%rdx
0x000000010001bf08 <bsf64+4>:   test   %rax,%rax
0x000000010001bf0b <bsf64+7>:   jne    0x10001bf44 <...>

但是如果mask 为零,BSF 已经设置了 ZF 标志,所以bsf 之后的test 是多余的。

除了返回mask ? 1 : 0,是否可以检索ZF标志并返回它,使GCC不生成test

编辑:使if 示例更加清晰

编辑:作为对 Damon 的回应,__builtin_ffsl 生成的优化代码更差。如果我使用以下代码

    int b = __builtin_ffsl(mask);
    if (b) 
        *index = b - 1;
        return true;
     else 
        return false;
    

GCC 生成这个程序集

   0x000000000044736d <+1101>:  bsf    %r14,%r14
   0x0000000000447371 <+1105>:  cmove  %r12,%r14
   0x0000000000447375 <+1109>:  add    $0x1,%r14d
   0x0000000000447379 <+1113>:  je     0x4471c0 <...>
   0x000000000044737f <+1119>:  lea    -0x1(%r14),%ecx

所以test 消失了,但产生了冗余的条件移动、递增和递减。

【问题讨论】:

【参考方案1】:

几点说明:

这是一个“反优化”。您正在尝试对编译器已经支持的内容进行微优化。 在我的 gcc 版本打开所有优化开关的情况下,您的代码根本不会生成 bsf 指令。查看代码,这并不奇怪,因为您返回 mask,它是 操作数,而不是目标操作数(gcc 使用 AT&T 语法!)。编译器足够聪明,可以解决这个问题,并将汇编代码(不执行任何操作)全部丢弃。 有一个内在函数__builtin_ffsl 与您的内联汇编完全相同(尽管正确)。内在函数的可移植性不亚于内联汇编程序,但更易于编译器优化。 使用内部函数会在我的编译器上产生一个bsf cmov 序列(假设调用代码强制它实际发出指令),这表明编译器使用零标志就可以了,无需额外的测试指令。 当你想要一个bool 时返回一个char 并不是编译器的最佳提示,尽管它可能会在大多数情况下解决这个问题。但是,当您真的只对“零或非零”感兴趣时,告诉编译器使用位扫描指令肯定是次优的。 if(x)if(!x) 在这方面工作得很好。如果您将结果作为参考返回,情况会有所不同,因此您可以在其他地方重用它,但实际上,您的代码只是编写if(x) 的一种非常复杂的方式。

【讨论】:

首先,感谢内置函数——我不知道。不幸的是,我找不到类似的 BSR 内置函数—— clz 在 x=0 上未定义,所以无论如何我都需要明确地进行测试。所以我认为这个问题仍然没有答案。 关于您的其他评论:我认为您在一些死代码中测试了我的函数,因为对我来说它正确生成了 bsf(上面的 disass 来自真实代码,使用 gdb 介入)和操作数是正确的(否则我的代码中的任何内容都不会起作用,这在很大程度上取决于此功能)。当然,我没有将 bsf 用于“零或非零”,我所有的代码都是“如果至少有一位告诉我 LSB 否则做其他事情”的形式,这就是为什么我需要一个 jmp 之后bsf. 对于 BSR,您需要 8*sizeof(type) - __builtin_clz(x)(或 __builtin_clzl 对于 long)。这有点扭曲,因为内在是“相反的方式”,但编译器会为该构造发出一个 BSR(我过去曾使用过它)。现在,不要问我为什么这个内在函数是相反的......可能是因为“前导零”是一个已知的习语或其他东西:-) "当然,我没有将 bsf 用于“零或非零”,我所有的代码都是“如果有至少一位告诉我 LSB 否则做其他事情”的形式'" 所以换句话说,你正在写类似if((x = bsr64(y,z))) /* use x */ 的东西。好的,这会起作用,但它与问题向我建议的不同。这种事情确实应该在内在和启用 CSE 的情况下也能正常工作(对我来说,无论如何)。 另一个选项当然是在调用 asm 例程之前 测试操作数是否为零。如果这是最常见的情况,它甚至可能是一种优化!

以上是关于在 GCC 内联汇编中检索 ZF的主要内容,如果未能解决你的问题,请参考以下文章

操作系统学习之GCC内联汇编

GCC内联汇编常见陷阱

GCC 扩展内联汇编简介

GCC 内联汇编到 IAR 内联汇编

如何在没有扩展内联 asm 的情况下在 gcc 内联汇编中声明和初始化局部变量?

GCC内联汇编中的C数组?