很遗憾,英特尔的内在函数不是以constexpr
定义的。
实际上,它们完全可以被定义为constexpr
。编译器可以在编译时评估它们以进行常量传播和其他优化。这也是内建函数/内在函数比单个指令的内联汇编包装更好的一个重要原因。
GCC的解决方案。(不适用于clang或MSVC)。
ICC编译它时,但在您尝试将其用作constexpr __m128i
的初始化程序的一部分时会出现问题。
constexpr
__m128i pcmpeqd(__m128i a, __m128i b) {
return (v4si)a == (v4si)b;
}
在Godbolt编译器浏览器上查看,带有两个测试调用者(一个带有变量,一个带有
constexpr __m128i v1 {0x100000000, 0x300000002};
输入)。有趣的是,ICC 不会通过pcmpeqd
或_mm_cmpeq_epi32
进行常量传播;即使启用了优化,它也会加载两个常量并使用实际的pcmpeqd
。无论是否使用constexpr,都会发生相同的事情。我认为它通常会进行优化。
gcc可以接受
constexpr __m128i vector_const { pcmpeqd(__m128i{0,0}, __m128i{-1,-1}) };
GCC(但不是clang)将__builtin_ia32
函数视为constexpr
兼容。GNU C x86内置函数文档没有提到这一点,但可能只是因为它是C文档,而不是C ++。
GNU C本地向量语法也是constexpr
兼容的;这是第二个选项,如果您不关心MSVC,则再次可行。
GNU C将__m128i
定义为两个long long
元素的向量。因此,对于整数SIMD,您需要定义其他类型(或使用由gcc / clang / ICC的immintrin.h
定义的类型)
唯一奇怪的是,static const __m128i foo = _mm_set1_epi32(2);
并不会变成常量初始化程序;它会在运行时从.rodata
复制,因此非常糟糕,使用了一个保护变量,在每次函数调用时都会检查变量是否需要静态初始化。
GCC的xmmintrin.h
和emmintrin.h
以本机向量运算符(如*
)或__builtin_ia32
函数的方式定义了英特尔指令。在可能的情况下,它们更喜欢使用运算符,而不是(__m128i)__builtin_ia32_pcmpeqd128((v4si)a, (v4si)b);
gcc需要不同向量类型之间的显式强制转换。
来自gcc7.3的emmintrin.h
(SSE2):
extern __inline __m128i __attribute__((__gnu_inline__, __always_inline__, __artificial__))
_mm_cmpeq_epi32 (__m128i __A, __m128i __B)
{
return (__m128i) ((__v4si)__A == (__v4si)__B);
}
#ifdef __OPTIMIZE__
extern __inline __m128i __attribute__((__gnu_inline__, __always_inline__, __artificial__))
_mm_shuffle_epi32 (__m128i __A, const int __mask)
{
return (__m128i)__builtin_ia32_pshufd ((__v4si)__A, __mask);
}
#else
#define _mm_shuffle_epi32(A, N) \
((__m128i)__builtin_ia32_pshufd ((__v4si)(__m128i)(A), (int)(N)))
#endif
有趣的是:如果在禁用优化的情况下编译,gcc的头文件会避免在某些情况下使用内联函数。我猜这会导致更好的调试符号,这样你就不会在使用GDB中的
stepi
时单步进入内联函数的定义(当在TUI源窗口中显示优化代码时会发生这种情况)。
__builtin_constant_p
扩展,允许使用技巧,如#define FOO(x) (__builtin_constant_p(x) ? foo_constexpr(x) : foo_asm(x))
- 如果编译器可以将x
评估为常量,则将使用纯C++实现,从而允许进一步的内联和编译时优化。 - dewaffledconstexpr
函数进行求值。如果您的编译器不知道如何评估一些/所有SIMD内置函数,使用这些函数的方法就无法成为constexpr
。请注意,在编译时评估函数与编译函数是完全不同的; 您可能正在交叉编译到另一个平台,因此编译器甚至无法在编译后运行该函数并获取其值。因此,编译器需要特殊的仿真代码来在“纯C ++”中模拟该函数,但显然不存在。 - JimmyB__builtin_ia32
函数;它可以像标量int
的+
运算符一样通过它们进行常量传播。这完全是 C++ 语法和声明方式的不幸问题。(唯一奇怪的是,static const __m128 foo = _mm_set1_ps(2.0f);
没有变成常量初始化程序;它在运行时从.rodata
复制,因此很糟糕。) - Peter Cordes