x64 支持是否意味着 BMI1 支持?

Pav*_*l P 1 assembly x86-64 instruction-set bmi

可以安全地假设 x64 构建可以使用TZCNT而无需通过 cpu 标志检查其支持吗?

Pet*_*des 10

不,当然不是!x86-64 是 2003 年末的新版本(AMD K8),只有旧版本bsfbsr位扫描指令,没有 BMI1 的其余部分。

第一个支持 BMI1 的 Intel CPU 是 2013 年的 Haswell。(也引入了 BMI2。)
第一个支持 BMI1 的 AMD CPU 是 2012 年的 Piledriver。K10 中的
AMD ABM(高级位操作)和后来的 AMD CPU 仅添加popcntlzcnt,没有tzcnt

维基百科位操作指令集:支持 CPU。请注意,Celeron/Pentium 品牌的 CPU 不解码 VEX 前缀,因此它们禁用了 AVX 和 BMI1/BMI2,因为 BMI1 和 2 均包含一些 VEX 编码的指令,例如andnblsr。这很糟糕;当编译器可以在整个可执行文件中的任何地方使用BMI1/2 以实现更高效的可变计数移位和窥视孔时,BMI1/2最有用,因此仍然销售没有 BMI1/2 的新 CPU 并不能让我们更接近于能够像我们一样将它们视为基线为 P6 做cmov在 32 位模式下。


旧 CPU 上的 TZCNT 解码

由于您tzcnt特别提到,它的机器代码编码是rep bsf如此旧的 CPU 将其作为 BSF 执行。这会产生与tzcnt输入非零相同的结果。即tzcnt当输入非零时在所有 x86 CPU(自 386 起)上“工作”。

但是当它为零时,tzcnt会产生操作数大小(例如 64),但不会bsf修改目标寄存器。 tzcnt根据结果bsf和输入设置 FLAGS 。AMD 在其 ISA 参考手册中记录了未修改的 dst 行为。英特尔仅将其记录为“未定义值”,但实现了与 AMD 相同的行为,至少在现有 CPU 中是如此。

(这就是为什么bsf/bsr对所有 CPU 都有输出依赖性,例如add。不幸的是tzcnt/lzcnt在 Skylake 之前/也对 Intel Sandybridge 系列有错误依赖性:为什么打破 LZCNT 的“输出依赖性”很重要?。为什么popcnt对 SnB 系列在 Cannon / Ice Lake 之前,因为它共享相同的执行单元。)


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 位输入的成本较低。