为什么GCC避免多元素联合使用向量寄存器?

huwehgph  于 2023-01-05  发布在  其他
关注(0)|答案(1)|浏览(178)

我注意到,当给定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 在一个封闭的结构体或联合体中时,第二种情况也适用。

uidvcgyl

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参数类,并在寄存器的较低部分传递。
但是,聚合类型(数组、结构和类)和联合的参数类是根据它们的组合确定的。
因此,给定工会:

union simd
{
    __m128 vec;
    float vals[4];
};

__m128 vec向量福尔斯特殊情况规则,分类为SSE+SSEUP,因此可以通过单个寄存器传递。到目前为止一切顺利。但是,由于float vals[4]数组由2个(独立的!)8字节块,并且每个8字节块被分配SSE类,阵列本身又被分类为SSE+SSE,这不符合SSE+SSEUP要求,这又迫使使用2个单独的XMM寄存器的较低部分来传递它,并且作为最小公分母,导致并集本身被视为2个自变量并在2个寄存器中传递。
简言之,调用约定将数组视为2个单独的8字节参数,因此必须在2个单独的寄存器中传递它,而独立的__m128被视为单个参数,在单个寄存器中传递。
奇怪的是,这使得下面的结合

union simd
{
    __m128 vec;
    float vals[2];
};

实际上被视为SSE+ SSEUP类,因此在单个寄存器中传递。__m128 vec被视为SSE & SSEUP,而float vec[2]被视为单个SSE类。
不幸的是,似乎没有办法向编译器显式指定(或提示)参数类。

相关问题