如何从预处理器启用内部功能

How to enable instrinsic functions from the preprocessor

我可以通过查找找到 16 位值的第 n 个设置位 table,但是对于 32 位值,如果不分解它并使用多个,则无法做到这一点LUT。 How to efficiently find the n-th set bit? 中对此进行了讨论,建议使用类似于此

#include <immintrin.h>
//...
unsigned i = 0x6B5;                 // 11010110101
unsigned n = 4;                     //    ^
unsigned j = _pdep_u32(1u << n, i);
int bitnum = __builtin_ctz(j));     // result is bit 7

我已经介绍了这个建议的循环方法

j = i;
for (k = 0; k < n; k++)
    j &= j - 1;
return (__builtin_ctz(j));

Nominal Animal. The branching variant of the twiddle was the fastest of the three code versions, not by much. However in my actual code, the above snippet using __builtin_ctz was faster. There are other answers such as from fuz 的回答中反对位旋转代码的两种变体,他也提出了我的发现:位旋转花费与循环方法相似的时间。

所以我现在想尝试使用 _pdep_u32 但无法识别 _pdep_u32。我阅读 gcc.gnu.org

The following built-in functions are available when -mbmi2 is used.
...
unsigned int _pdep_u32 (unsigned int, unsigned int)
unsigned long long _pdep_u64 (unsigned long long, unsigned long long)
...

但是我使用的是在线编译器,无法访问这些选项。

有没有办法使用预处理器启用选项?

关于从命令行选项控制预处理器的内容很多material,但我找不到相反的方法。

最终我想使用64位版本_pdep_u64

除了内联 asm,gcc/clang 永远不会发出未通过命令行选项、函数属性或 pragma 启用的 asm 指令。

如果你不能像普通人一样在命令行上使用GCC目标选项(例如-march=native启用编译主机支持的所有内容,并针对该机器进行调整),您 可以 而不是使用 a pragma to set target-specific options. It doesn't work very well, though; it seems #pragma target("arch=skylake") breaks GCC's immintrin.h (if you put the pragma before the include), apparently trying to compile AVX512 functions without AVX512 enabled. The GCC docs do show examples of using "arch=core2" 作为函数属性,甚至 "sse4.1,arch=core2"

您可以设置 target("bmi2,tune=skylake") 但不会破坏任何内容(在 #include <immintrin.h> 之前或之后)。(与 arch= 不同,tune= 不会启用 ISA 扩展,只会影响 code-gen 选项。)

#include <immintrin.h>
// #pragma GCC target ("arch=haswell")  // also broken, disables BMI2??

#pragma GCC target ("bmi2,tune=skylake") // this can be after immintrin.h

int foo(unsigned x) {
  // unsigned i = 0x6B5;                 // 11010110101
  unsigned i = x;
  unsigned n = 4;                        //    ^
  unsigned j = _pdep_u32(1u << n, i);
  int bitnum = __builtin_ctz(j);     // result is bit 7
  return bitnum;
}

int constprop() {  // check that GCC can still "understand" the intrinsic
  return foo(0x6B5);
}

compiles cleanly on Godbolt 只有 -O3,没有 -m 选项。

foo:
        mov     r8d, edi
        mov     edi, 16
        pdep    edi, edi, r8d
        bsf     eax, edi
        ret

constprop:
        mov     eax, 7
        ret

如果您在 immintrin.h 定义正确的包装器时遇到困难,您可以查看 GCC 的 headers 以了解它们是如何定义的。例如_pdep_u32__builtin_ia32_pdep_si(单整数)的包装,u64 是 __builtin_ia32_pdep_di(双整数)的包装。

这些 __builtin 函数总是由 GCC 定义,即使没有 headers,但是这些 ia32 内置函数只能在具有兼容目标选项的函数中使用。对于不支持该指令的目标,没有像 __builtin_popcount 那样的回退。

但似乎至少对于 BMI2 指令,_pdep_u32_pdep_u64 由 GCC 的 immintrin.h 定义,无论命令行选项如何 (这将定义一个 __BMI2__ CPP 宏)。可能是这个用例的原因:具有目标属性的单个函数,或具有 pragma 的函数块。

_pdep_u64 版本需要 BMI2 + x86-64,而 32 位版本只需要 BMI2(即在 32 位模式下也可用)。 pdep is a scalar integer instruction 所以 64 位 operand-size 需要 64 位模式。


Aside: MSVC compiled the instrinsic without fuss

是的,MSVC 和 GCC/clang 对于内在函数有不同的哲学。 MSVC 允许您使用任何东西,假设您将进行运行时分派以确保执行永远不会达到本机不支持的内在函数。

这可能与 MSVC 本质上根本不优化内部函数有关,通常甚至不通过它们进行简单的 constant-propagation。因为它试图确保当 C++ 抽象机中未达到内在函数时,相应的 asm 指令永远不会执行,我猜,并采取缓慢而安全的方法。

but the program crashed (I suppose _pdep_u32 is not supported by my cpu as suggested in the linked question).

是的,并非所有 CPU 都有 BMI2。使用 GCC 所做的任何事情都不会改变这一点。