这可以通过一些 asm 黑客来实现,但通常你最好使用内部函数,像这样:
https://gcc.gnu.org/wiki/DontUseInlineAsm
#include <immintrin.h>
template<int size, int element>
int foo(__m128i x, __m128i y) {
if (size==32) // if constexpr if you want to be C++17 fancy
x = _mm_cmpgt_epi32(x, y);
else
x = _mm_cmpgt_epi64(x, y); // needs -msse4.2 or -march=native or whatever
return x[element]; // GNU C extension to index vectors with [].
// GCC defines __m128i as a vector of two long long
// cast to typedef int v4si __attribute__((vector_size(16)))
// or use _mm_extract_epi8 or whatever with element*size/8
// if you want to access one of 4 dword elements.
}
int test(__m128i x, __m128i y) {
return foo<32, 0>(x, y);
}
// compiles to pcmpgtd %xmm1, %xmm0 ; movq %xmm0, %rax ; ret
您甚至可以完全使用GNU C native vector 样式并在将它们转换为v4si 之后执行x = x>y,具体取决于您想要比较32 位还是64 位元素。如果 SSE4.2 不适用于 pcmpgtq,GCC 将实现运算符 >,但它可以使用多指令仿真。不过,这与内在函数之间没有其他根本区别。编译器不需要发出pcmpgtq 只是因为源包含_mm_cmpgt_epi64,例如如果 x 和 y 都是编译时常量,或者如果 y 已知为 LONG_MAX,那么它可以通过它进行常量传播,所以没有什么比它更大了。
使用内联汇编
只有 C 预处理器可以按照您希望的方式工作; asm 模板在编译时必须是字符串文字,而 AFAIK C++ 模板 constexpr 东西不能将变量的值字符串化并将其传递为实际的字符串文字。模板评估发生在解析之后。
我想出了一个有趣的技巧,让 GCC 使用 %p4 打印 d 或 q 作为全局(或静态)变量的 asm 符号名称(参见 @ GCC 手册中的 987654323@。)像constexpr char d[] = {}; 这样的空数组在这里可能是一个不错的选择。无论如何,您都不能将字符串文字传递给模板参数。
(我还修复了您的内联 asm 语句中的低效率和错误:例如,让编译器选择寄存器,并要求 XMM regs 中的输入,而不是内存。您缺少“xmm7”clobber,但这个版本没有不需要任何破坏者。对于输入可能是编译时常量,或者一个在对齐内存中以便可以使用内存操作数或各种其他可能的优化的情况,这仍然比内在函数更糟糕。我本可以使用"xm"作为来源,但 clang 总是选择“m”。**https://gcc.gnu.org/wiki/DontUseInlineAsm**.)
如果您不希望它针对 valgrind 测试进行优化,即使不需要输出,也可以设置为 asm volatile 强制它运行。这就是您想要使用内联 asm 而不是内在函数或 GNU C 本机向量语法的唯一原因 (x > y)
typedef long long V64x2 __attribute__((vector_size(16), may_alias));
// or #include <immintrin.h> and use __m128i which is defined the same way
static constexpr char q[0] asm("q") = {}; // override asm symbol name which gets mangled for const or constexpr
static constexpr char d[0] asm("d") = {};
template <const char *op, int element_select>
static int cmpGT64Sx2(V64x2 x, V64x2 y)
{
int result;
__asm__(
// AT&T style has destination second.
"pcmpgt%p[op] %[src],%[dst]\n\t" // %p4 - print the bare name, not $d or $q
"pextrb %3, %[dst], %0"
: "=r" (result), [dst]"+x"(x)
: [src]"x"(y), "i" (element_select*8),
[op]"i"(op) // address as an immediate = symbol name
: /* no clobbers */);
return result;
}
int gt64(V64x2 x, V64x2 y) {
return cmpGT64Sx2<q, 1>(x,y);
}
int gt32(V64x2 x, V64x2 y) {
return cmpGT64Sx2<d, 1>(x,y);
}
因此,以d 和q 作为此文件中的全局范围名称为代价(!??),我们可以使用看起来像我们想要的指令的<d, 2> 或<q, 0> 模板参数。
请注意,在 x86 SIMD 术语中,“通道”是 AVX 或 AVX-512 向量的 128 位块。如vpermilps(32 位浮点元素的 In-Lane Permute)。
这使用 GCC10 -O3 (https://godbolt.org/z/ovxWd8) 编译为以下 asm
gt64(long long __vector(2), long long __vector(2)):
pcmpgtq %xmm1,%xmm0
pextrb $8, %xmm0, %eax
ret
gt32(long long __vector(2), long long __vector(2)):
pcmpgtd %xmm1,%xmm0
pextrb $8, %xmm0, %eax // This is actually element 2 of 4, not 1, because your scale doesn't account for the size.
ret
您可以对模板用户隐藏全局范围的变量,并让他们传递一个整数大小。我还修复了元素索引以考虑可变元素大小。
static constexpr char q[0] asm("q") = {}; // override asm symbol name which gets mangled for const or constexpr
static constexpr char d[0] asm("d") = {};
template <int size, int element_select>
static int cmpGT64Sx2_new(V64x2 x, V64x2 y)
{
//static constexpr char dd[0] asm("d") = {}; // nope, asm symbol name overrides don't work on local-scope static vars
constexpr int bytepos = size/8 * element_select;
constexpr const char *op = (size==32) ? d : q;
// maybe static_assert( size == 32 || size == 64 )
int result;
__asm__(
// AT&T style has destination second.
"pcmpgt%p[op] %[src],%[dst]\n\t" // SSE2 or SSE4.2
"pextrb %[byte], %[dst], %0" // SSE4.1
: "=r" (result), [dst]"+x"(x)
: [src]"x"(y), [byte]"i" (bytepos),
[op]"i"(op) // address as an immediate = symbol name
: /* no clobbers */);
return result;
}
// Note *not* referencing d or q static vars, but the template is
int gt64_new(V64x2 x, V64x2 y) {
return cmpGT64Sx2_new<64, 1>(x,y);
}
int gt32_new(V64x2 x, V64x2 y) {
return cmpGT64Sx2_new<32, 1>(x,y);
}
这也可以像我们想要的那样编译,例如
gt32_new(long long __vector(2), long long __vector(2)):
pcmpgtd %xmm1,%xmm0
pextrb $4, %xmm0, %eax # note the correct element 1 position
ret
顺便说一句,如果您的 asm 语句仅在与 "0"(x) 输入相同的寄存器中生成该类型的 "=x" 输出,则您可以使用 typedef int v4si __attribute__((vector_size(16))) 然后 v[element] 让 GCC 为您执行此操作.
没有全局范围的变量名称,使用 GAS .if / .else
我们可以很容易地让 GCC 将一个裸数字打印到 asm 模板中,例如用作a .if %[size] == 32 directive 的操作数。 GNU 汇编器有一些条件汇编特性,所以我们只需让 GCC 为其提供正确的文本输入以使用它。 C++ 方面的黑客攻击要少得多,但源代码不那么紧凑。如果您想比较它而不是尺寸编号,您的模板参数可以是 'd' 或 'q' 尺寸代码字符。
template <int size, int element_select>
static int cmpGT64Sx2_mask(V64x2 x, V64x2 y)
{
constexpr int bytepos = size/8 * element_select;
unsigned int result;
__asm__(
// AT&T style has destination second.
".if %c[opsize] == 32\n\t" // note Godbolt hides directives; use binary mode to verify the assemble-time condition worked
"pcmpgtd %[src],%[dst]\n\t" // SSE2
".else \n\t"
"pcmpgtq %[src],%[dst]\n\t" // SSE4.2
".endif \n\t"
"pmovmskb %[dst], %0"
: "=r" (result), [dst]"+x"(x)
: [src]"x"(y), [opsize]"i"(size) // address as an immediate = symbol name
: /* no clobbers */);
return (result >> bytepos) & 1; // can just be TEST when branching on it
}
我还改为使用 SSE2 pmovmskb 来提取两个/所有元素比较结果,并使用标量内容来选择要查看的位。这是正交的,可以与任何其他人一起使用。内联后,它通常会更高效,允许test $imm32, %eax。 (pmovmskb 比 pextrb 便宜,它让整个事情只需要 pcmpgtd 版本的 SSE2)。
编译器的 asm 输出看起来像
.if 64 == 32
pcmpgtd %xmm1,%xmm0
.else
pcmpgtq %xmm1,%xmm0
.endif
pmovmskb %xmm0, %eax
为了确保我们想要的,我们可以组装成二进制并查看反汇编(https://godbolt.org/z/5zGdfv):
gt32_mask(long long __vector(2), long long __vector(2)):
pcmpgtd %xmm1,%xmm0
pmovmskb %xmm0,%eax
shr $0x4,%eax
and $0x1,%eax
(并且 gt64_mask 使用 pcmpgtq 和 shr 8。)