__builtin_clz
给出错误答案的情况只有1种。我很好奇是什么导致了这种行为。
当我使用文字值0时,我总是得到32的期望值。但是0作为变量会产生31。为什么存储值0的方法很重要?
我参加了体系结构课程,但不了解差异化的程序集。看起来,当给定文字值0时,即使不进行优化,该汇编总会以某种方式始终具有32个硬编码的正确答案。使用-march = native时,用于计算前导零的方法也不同。
[This post关于用__builtin_clz
模拟_BitScanReverse
和第bsrl %eax, %eax
行似乎暗示位扫描反向不适用于0。
+-------------------+-------------+--------------+
| Compile | literal.cpp | variable.cpp |
+-------------------+-------------+--------------+
| g++ | 32 | 31 |
| g++ -O | 32 | 32 |
| g++ -march=native | 32 | 32 |
+-------------------+-------------+--------------+
#include <iostream>
int main(){
int i = 0;
std::cout << __builtin_clz(0) << std::endl;
}
#include <iostream>
int main(){
int i = 0;
std::cout << __builtin_clz(i) << std::endl;
}
1c1
< .file "literal.cpp"
---
> .file "variable.cpp"
23c23,26
< movl $32, %esi
---
> movl -4(%rbp), %eax
> bsrl %eax, %eax
> xorl $31, %eax
> movl %eax, %esi
1c1
< .file "literal.cpp"
---
> .file "variable.cpp"
23c23,25
< movl $32, %esi
---
> movl -4(%rbp), %eax
> lzcntl %eax, %eax
> movl %eax, %esi
1c1
< .file "literal.cpp"
---
> .file "variable.cpp"
当禁用优化功能进行编译时,编译器不会在语句之间进行常数传播。该部分是Why does integer division by -1 (negative one) result in FPE?的副本-在此处阅读我的答案,和/或Why does clang produce inefficient asm with -O0 (for this simple floating point sum)?
这就是为什么字面零可以不同于值= 0的变量。]>只有禁用优化的变量才会在运行时bsr+xor $31, %reg
中产生。
如in the GCC manual的__builtin_clz
所示>
,而不是行为。它不是C ++ UB(整个程序可以绝对做任何事情),它仅限于这种结果,就像在x86 asm中一样。但是无论如何,似乎当输入为编译时常量0时,GCC会像x86从最高有效位位置开始,返回x中前导0位的数目。 如果
x
为0,则结果不确定。这将
clz
/ctz
分别编译为x86上的31-bsr
或bsf
指令。得益于2的补码,31-bsr
用bsr
+xor $31,%reg
实现。 (BSR产生最高设置位的索引,而不是前导零计数)。请注意,它仅显示结果
lzcnt
以及其他ISA上的clz
指令那样生成类型宽度。 (这可能发生在与目标无关的GIMPLE树优化中,其中通过包含内置函数的操作进行了恒定传播。)Intel文档将bsf
/ bsf
设置为如果内容源操作数为0,则目标操作数的内容未定义。在现实生活中,Intel硬件实现了相同的行为AMD文档:保持目标不变在这种情况下。
但是由于英特尔拒绝对其进行文档化,因此编译器不会让您编写利用它的代码。 GCC不了解或不关心这种行为,因此无法提供利用它的方法。即使MSVC的内在函数需要一个输出指针arg,MSVC也不会这样做,因此很容易以这种方式工作。参见bsr
对于bsr
,GCC可以直接使用BMI1 VS: unexpected optimization behavior with _BitScanReverse64 intrinsic,对于包括-march=native
]的每个可能的输入位模式,它都已明确定义。它直接产生前导零计数,而不是第一个置位的index
(这就是为什么BSR / BSF对于input = 0毫无意义;没有索引可供他们查找。有趣的事实:lzcnt
对lzcnt
起作用。在asm中,指令也设置为ZF根据输入是否为零,因此您可以检测到输出何时为“未定义”,而不是在0
之前进行单独的test +分支。或者在AMD和现实生活中的所有其他事情上,其未更改目标。) >
在直到Skylake的Intel上,bsr %eax, %eax
/ eax=0
对输出寄存器都有错误的依赖性,即使结果与not无关。 IIRC,Coffee Lake还修复了bsr
的错误深度。 (所有这些都与BSR / BSF在同一执行单元上运行。)
lzcnt
tzcnt
popcnt