假设 x64 构建可以使用TZCNT而不通过 cpu 标志检查它的支持是否安全?
1 回答
不,当然不是!x86-64 是 2003 年底的新产品(AMD K8),只有遗留bsf
和bsr
位扫描指令,其余的 BMI1 没有。
第一个支持 BMI1 的 Intel CPU 是 2013 年的 Haswell。(也引入了 BMI2。)
第一个支持 BMI1 的 AMD CPU 是 2012 年的 Piledriver。K10和后来的 AMD CPU中的
AMD ABM(高级位操作)popcnt
只添加了和lzcnt
,而不是tzcnt
。
维基百科位操作指令集:支持 CPU。请注意,Celeron/Pentium 品牌的 CPU 不解码 VEX 前缀,因此它们禁用了 AVX 和 BMI1/BMI2,因为 BMI1 和 2 都包含一些 VEX 编码指令,例如andn
和blsr
。这很糟糕;当编译器可以在整个可执行文件中的任何地方使用BMI1/2 以实现更有效的变量计数转换和窥视孔时, BMI1/2最有用,因此仍然销售没有 BMI1/2 的新 CPU 并不能让我们更接近能够像我们一样将它们视为基线cmov
在 32 位模式下为 P6 做。
旧 CPU 上的 TZCNT 解码
由于您特别提到tzcnt
,它的机器代码编码是rep bsf
如此旧的 CPU 将其作为 BSF 执行。tzcnt
这会产生与输入非零相同的结果。即tzcnt
当输入为非零时,在所有 x86 CPU(自 386 起)上“工作”。
但是当它为零时,tzcnt
将产生操作数大小(例如 64),但bsf
保持目标寄存器不变。 tzcnt
根据结果bsf
和输入设置 FLAGS。AMD 在其 ISA 参考手册中记录了未修改的 dst 行为。英特尔仅将其记录为“未定义值”,但至少在现有 CPU 中实现了与 AMD 相同的行为。
(这就是为什么bsf
/对bsr
所有 CPU 都有add
输出依赖,tzcnt
例如在 Cannon / Ice Lake 之前,因为它共享相同的执行单元。)lzcnt
popcnt
tzcnt
在 AMD 上明显更快,因此为“通用”或 AMD CPU 调整的编译器通常会使用tzcnt
而不是bsf
不检查 CPU 功能。
例如对于 GNU C __builtin_ctz
。该内在函数对于 input=0 具有未定义的行为,因此允许仅使用bsf
而不检查 0。因此也允许使用tzcnt
,因为这种情况下的结果不能得到任何保证。
为什么 TZCNT 适用于我的 Sandy Bridge 处理器?
不存在这样的向后/向前兼容lzcnt
。将其解码为忽略rep bsr
无意义的rep
前缀将为您提供31 - lzcnt(x)
位索引。https://fgiesen.wordpress.com/2013/10/18/bit-scanning-equivalencies/
一个方便的技巧是ctz( x | 0x80000000 )
因为 OR 很便宜1,并保证总是有一个非零位可供bsf
查找。它不会改变任何非零的结果,x
因为它是最后一位bsf
。这个技巧也适用于__builtin_clz(x|1)
/ bsr
,它甚至更好,因为or reg, imm8
它甚至比imm32
.
脚注 1:or reg, imm32
适用于 32 位常量;bts reg,63
在某些 CPU 上实现x|(1ULL<<63)
64 位输入的成本较低。