我注意到,当给定SIMD向量类型和任何其他大小相同、对齐方式相同但不是向量类型的并集时,GCC生成的代码非常不同(而且效率较低)。
特别地,正如在这个Godbolt示例中可以看到的,当__m128
向量类型与非向量类型放在一个并集中时,该并集被传递到两个XMM寄存器中(每个参数)然后加载到堆栈上以便与addps
一起使用,而不是在单个XMM寄存器中传递并直接与addps
一起使用。对于仅包含__m128
和__m128
向量本身的并集的另外两种情况,自变量和返回直接在XMM寄存器中传递,并且不使用堆栈。
是什么导致了这种差异?有没有办法"强制" GCC在XMM寄存器中传递多元素联合?
带接头:
#include <immintrin.h>
#include <array>
union simd
{
__m128 vec;
alignas(__m128) std::array<float, 4> values;
};
simd add(simd a, simd b) noexcept
{
simd ret;
ret.vec = _mm_add_ps(a.vec, b.vec);
return ret;
}
add(simd, simd):
movq QWORD PTR [rsp-40], xmm0
movq QWORD PTR [rsp-32], xmm1
movq QWORD PTR [rsp-24], xmm2
movq QWORD PTR [rsp-16], xmm3
movaps xmm4, XMMWORD PTR [rsp-24]
addps xmm4, XMMWORD PTR [rsp-40]
movaps XMMWORD PTR [rsp-40], xmm4
movq xmm1, QWORD PTR [rsp-32]
movq xmm0, QWORD PTR [rsp-40]
ret
未愈合:
一个二个一个一个
注意,当__m128
向量被 Package 在一个封闭的结构体或联合体中时,第二种情况也适用。
1条答案
按热度按时间uidvcgyl1#
正如Homer 512所怀疑的,答案就在AMD 64调用约定中。
根据System V AMD 64 ABI第3.2.3节,每8个字节接收其自己的参数类(小于8个字节的参数分组在一起或填充)。
对于在单个向量寄存器中传递的参数,它必须至少包含一个SSE类,后跟任意数量的SSEUP类。SSE类表示寄存器的低64位,而SSEUP表示高64位。
例如,
__m128
和其他向量被视为由SSE和SSEUP类组成的多个8字节参数,因此它们在单个寄存器中传递。反过来,每个标量float
被分配SSE参数类,并在寄存器的较低部分传递。但是,聚合类型(数组、结构和类)和联合的参数类是根据它们的组合确定的。
因此,给定工会:
__m128 vec
向量福尔斯特殊情况规则,分类为SSE+SSEUP,因此可以通过单个寄存器传递。到目前为止一切顺利。但是,由于float vals[4]
数组由2个(独立的!)8字节块,并且每个8字节块被分配SSE类,阵列本身又被分类为SSE+SSE,这不符合SSE+SSEUP要求,这又迫使使用2个单独的XMM寄存器的较低部分来传递它,并且作为最小公分母,导致并集本身被视为2个自变量并在2个寄存器中传递。简言之,调用约定将数组视为2个单独的8字节参数,因此必须在2个单独的寄存器中传递它,而独立的
__m128
被视为单个参数,在单个寄存器中传递。奇怪的是,这使得下面的结合
实际上被视为SSE+ SSEUP类,因此在单个寄存器中传递。
__m128 vec
被视为SSE & SSEUP,而float vec[2]
被视为单个SSE类。不幸的是,似乎没有办法向编译器显式指定(或提示)参数类。