Mat*_*att 4 c++ assembly gcc intrinsics undefined-behavior
只有1种情况__builtin_clz
给出错误的答案。我很好奇是什么导致了这种行为。
当我使用文字值0时,我总是得到32的期望值。但是0作为变量将产生31。为什么存储值0的方法很重要?
我上过架构课程,但不了解差异化的程序集。看起来当给定字面值0时,即使不进行优化,该汇编总会以某种方式始终具有32个硬编码的正确答案。使用-march = native时,用于计算前导零的方法也不同。
这篇文章关于模拟__builtin_clz
与_BitScanReverse
和行bsrl %eax, %eax
似乎意味着位扫描反向不起作用0。
+-------------------+-------------+--------------+
| Compile | literal.cpp | variable.cpp |
+-------------------+-------------+--------------+
| g++ | 32 | 31 |
| g++ -O | 32 | 32 |
| g++ -march=native | 32 | 32 |
+-------------------+-------------+--------------+
Run Code Online (Sandbox Code Playgroud)
#include <iostream>
int main(){
int i = 0;
std::cout << __builtin_clz(0) << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
#include <iostream>
int main(){
int i = 0;
std::cout << __builtin_clz(i) << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
1c1
< .file "literal.cpp"
---
> .file "variable.cpp"
23c23,25
< movl $32, %esi
---
> movl -4(%rbp), %eax
> lzcntl %eax, %eax
> movl %eax, %esi
Run Code Online (Sandbox Code Playgroud)
1c1
< .file "literal.cpp"
---
> .file "variable.cpp"
Run Code Online (Sandbox Code Playgroud)
在禁用优化的情况下进行编译时,编译器不会在语句之间进行常量传播。这部分是为什么FPE会被-1除以整数(负数)的结果的重复。-在那里阅读我的答案,和/或为什么clang用-O0产生效率低的asm(对于这个简单的浮点数总和)?
这就是为什么字面零与值= 0
的变量可以不同的原因。只有禁用优化的变量才能在运行时生成bsr+xor $31, %reg
。
如GCC手册中所述__builtin_clz
从最高有效位位置开始,返回x中前导0位的数目。如果
x
为0,则结果不确定。
这允许clz
/ 在x86上分别ctz
编译为31- bsr
或bsf
指令。 31-bsr
借助2的补码的魔力实现了bsr
+的实现xor $31,%reg
。(BSR产生最高设置位的索引,而不是前导零计数)。
注意,它仅表示结果,而不表示行为。它不是C ++ UB(整个程序可以做任何事情),它仅限于这种结果,就像在x86 asm中一样。但是无论如何,似乎当输入是一个编译时常量0时,GCC会产生类似x86的类型宽度lzcnt
,以及类似clz
其他ISA上的指令。(这可能发生在与目标无关的GIMPLE树优化中,其中通过包含内置函数的操作进行了恒定传播。)
Intel文档bsf
/ bsr
as 如果内容源操作数为0,则目标操作数的内容未定义。 在现实生活中,英特尔硬件实现了与AMD文档相同的行为:在这种情况下,请不要更改目标。
但是由于英特尔拒绝对其进行文档化,因此编译器不允许您编写利用此文档的代码。GCC不了解或不关心这种行为,因此无法提供利用它的方法。即使MSVC的内在函数需要一个输出指针arg,MSVC也不会这样做,因此很容易以这种方式工作。请参阅VS:_BitScanReverse64固有的意外优化行为
使用-march=native
,GCC可以lzcnt
直接使用BMI1 ,对于每个可能的输入位模式(包括)都已明确定义了BMI10
。它直接产生前导零计数,而不是第一个设置位的索引。
(这就是为什么BSR / BSF对input = 0毫无意义;没有索引可供他们查找。有趣的事实:bsr %eax, %eax
“工作”对eax=0
。在asm中,指令还根据输入是否为零设置ZF。因此您可以检测到输出何时是“未定义的”,而不是之前的单独的test + branch bsr
。或者在AMD和现实生活中的所有其他情况下,输出未更改。
在直到Skylake的Intel上,lzcnt
/ tzcnt
都对输出寄存器具有错误的依赖关系,即使结果不再依赖于它。IIRC,Coffee Lake也修复了错误的dep popcnt
。(所有这些都与BSR / BSF在同一执行单元上运行。)
归档时间: |
|
查看次数: |
90 次 |
最近记录: |