可以安全地假设x64构建可以使用TZCNT而无需通过cpu标志检查其支持吗?
答案 0 :(得分:8)
不,当然不是! x86-64是2003年底推出的新产品(AMD K8),仅具有传统的bsf
和bsr
位扫描指令,而其余的BMI1都没有。
2013年,第一个支持BMI1的Intel CPU是Haswell 1 。(还引入了BMI2。)
2012年,第一个支持BMI1的AMD CPU是打桩机。
在K10和更高版本的CPU中,AMD ABM (Advanced Bit Manipulation)仅添加了popcnt
和lzcnt
,而没有添加tzcnt
。
维基百科Bit Manipulation Instruction Sets: Supporting CPUs。请注意,Celeron / Pentium品牌的CPU不会解码VEX前缀,因此它们禁用了AVX和BMI1 / BMI2,因为它们包含诸如andn
和blsr
之类的指令。真烂BMI1 / 2在整个可执行文件中都是most useful when compilers can use it everywhere,可实现更有效的变量计数转换和窥视孔,因此仍在销售不带BMI1 / 2的新CPU并不能使我们更像P6那样将其视为基准cmov
(在32位模式下)。
自从您特别提到tzcnt
以来,它的机器代码编码为rep bsf
,因此较旧的CPU将把它作为BSF执行。如果输入非零,则产生与tzcnt
相同的结果。即 tzcnt
在输入为非零时,可以在所有x86 CPU(自386开始)上工作。
但是当它为零时,tzcnt
会产生操作数大小(例如64),但是bsf
会保留目标寄存器不变。 tzcnt
根据结果设置标志,bsf
根据输入设置标志。 AMD在其ISA参考手册中记录了未经修改的行为。英特尔仅将其记录为“未定义的值”,但至少在现有CPU中实现了与AMD相同的行为。
(这就是bsf
/ bsr
对所有CPU都有输出依赖性的原因。不幸的是,tzcnt
/ lzcnt
在Skylake之前也对Intel Sandybridge系列有错误的依赖性:Why does breaking the "output dependency" of LZCNT matter?。为什么popcnt
在SnB系列before Cannon / Ice Lake上也如此,因为it shares the same execution unit。)
tzcnt
在AMD上明显更快,因此针对“通用”或AMD CPU进行调整的编译器通常会使用tzcnt
而不是bsf
而不检查CPU功能。
例如用于GNU C __builtin_ctz
。该内在函数对input = 0的行为未定义,因此允许其仅使用bsf
而无需检查0。因此,由于在任何情况下都无法保证结果,因此也允许使用tzcnt
。>
Why does TZCNT work for my Sandy Bridge processor?
lzcnt
不存在这种向后/向前兼容。将其解码为rep bsr
,而忽略无意义的rep
前缀,则会得到31 - lzcnt(x)
,即位索引。 https://fgiesen.wordpress.com/2013/10/18/bit-scanning-equivalencies/
一个方便的技巧是ctz( x | 0x80000000 )
,因为OR很便宜(至少对于32位常量而言),并且保证bsf
总是有一个非零的位。但不会更改任何非零x
的结果,因为它是bsf
所要查看的最后一位。对于__builtin_clz(x|1)
/ bsr
,这是一个更好的技巧。