在 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的主要内容,如果未能解决你的问题,请参考以下文章