【问题标题】:Does x64 support imply BMI1 support?x64 支持是否意味着 BMI1 支持?
【发布时间】:2020-08-08 21:07:06
【问题描述】:

假设 x64 构建可以使用 TZCNT 而不通过 cpu 标志检查其支持是否安全?

【问题讨论】:

  • 我很好奇你为什么还要问。当然,用谷歌搜索 BMI1 并看到它在 Haswell 中对英特尔来说是新的很容易。我猜你遇到tzcnt 没有在以前的 CPU 上出现故障,或者看到编译器在没有-mbmi1-march=haswell 的情况下使用它并且怀疑其他来源,所以我在回答中详细介绍了这一点。但从表面上看,这个关于 BMI1 的问题通常会因为缺乏研究努力而被否决。 (我考虑过但没有。)
  • 我看到 zlib-ng 在运行时有条件地使用 tzcount。我想建议他们始终将 bsf 用于 x86(他们无论如何都会检查 0)并将 tzcnt 用于 x64 构建。但是无法找到所有 x64 CPU 是否支持的明确信息。
  • 如果他们已经排除了 input=0,那么他们应该使用rep bsf = tzcnt 无条件(包括 32 位模式),所以它在 AMD 上更快,在 Intel 上也是如此,代价是 1 个指令字节。
  • 哦,它是 C 而不是 asm,所以他们不能只使用 _tzcnt_u32。 Err 等等,它在 ifdef _MSC_VER 中,所以它永远不会与需要为 _tzcnt_u32 启用 -mbmi1 的编译器一起使用。所以是的,他们应该使用_tzcnt_u32。当x86_cpu_has_tzcnt 不是编译时常量时,我​​很好奇这对 MSVC 来说有多可怕。
  • 这很糟糕,因为x86_cpu_has_tzcnt 不是编译时间常数(如果是,编译器显然会删除它)。如果 CPU 支持 BMI1,则在运行时设置

标签: assembly x86-64 instruction-set bmi


【解决方案1】:

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

第一个支持 BMI1 的 Intel CPU 是 2013 年的 Haswell。(同时引入了 BMI2。)
第一个支持 BMI1 的 AMD CPU 是 2012 年的 Piledriver。
K10 中的AMD ABM (Advanced Bit Manipulation) 和后来的 AMD CPU 只添加了popcntlzcnt,而不是tzcnt

维基百科Bit Manipulation Instruction Sets: Supporting CPUs。请注意,Celeron/Pentium 品牌的 CPU 不解码 VEX 前缀,因此它们禁用了 AVX 和 BMI1/BMI2,因为 BMI1 和 2 都包含一些 VEX 编码指令,例如 andnblsr。这很糟糕; BMI1/2 在整个可执行文件中都是most useful when compilers can use it everywhere,以实现更有效的变量计数转换和窥视孔,所以仍然销售没有 BMI1/2 的新 CPU 并不能让我们更接近能够像 P6 那样将它们视为基线​​ @987654340 @ 在 32 位模式下。


旧 CPU 上的 TZCNT 解码

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

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

(这就是为什么bsf / bsr 对所有 CPU 都有输出依赖性,例如 add。不幸的是,tzcnt / lzcnt 在 Skylake 之前也对 Intel Sandybridge 系列有错误的依赖性:@987654328 @. 以及为什么 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 很便宜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 位输入的成本较低。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-09-30
    • 2011-04-13
    • 2015-03-12
    • 1970-01-01
    • 2011-04-06
    • 2021-03-28
    • 2017-08-16
    • 2010-09-18
    相关资源
    最近更新 更多