如何控制工会的 ABI?

How to control the ABI for unions?

我正在为 C++ 开发 SIMD 包装器,基本类型类似于以下联合:

union u{
    __m128d sse;
    double c[2]; 
};

在下文中,我想查看 Linux 的 ABI。

例如

__m128d f(__m128d a, __m128d b){
    return b;
}

编译为

f(double __vector(2), double __vector(2)):
    vmovaps xmm0, xmm1
    ret

这对 SIMD(__m128d ABI)使用打包的 XMM 寄存器。如果我改用 union,它会导致使用默认的 float ABI。

f(u, u):
    vmovaps xmm1, xmm3
    vmovaps xmm0, xmm2
    ret

在这种情况下,只生成了一条指令。但情况可能会变得更糟,在某些情况下我必须使用堆栈,而我应该只使用寄存器。

有没有办法显式 select __m128d ABI?

先退一步,对比一下:

union u{
    __m128d sse;
    double c[2]; 
};

double getx(u a){
    return a.c[0];
}

u add(u a, u b) {
    return { _mm_add_pd(a.sse, b.see) };
}

有了这个:

double getx(__m128d a){
    return a[0];
}

__m128d add(__m128d a, __m128d b) {
    return _mm_add_pd(a, b);
}

你更喜欢哪个?

如果这是基于 linux 的 ABI,并且您使用的是 clang 或 gcc,则后者可以正常工作。所以我不完全确定你的联合类型在这里旨在解决什么问题?

顺便说一句,鼓励 SIMD 类型的用户避免访问向量中的元素通常是个好主意。除了访问元素 0 之外,它总是会产生运行时成本,因此请尽可能避免。

上述工作中的扳手是 visual C++ 没有定义这些运算符 :( 在那种特殊情况下,我只会为 Visual C+ 打扰一个包装器,然后离开 linux/Mac使用本机类型,例如

#ifdef _WIN32
// If you want decent performance for Windows :(
#define VCALL __vectorcall
struct d128 {
    inline d128() = default;
    inline d128(const d128&) = default;
    inline d128(const __m128d v) { x = v; }
    __m128d x;
    inline VCALL operator __m128d() const { return x; }
    inline double VCALL operator [](int i) const { return ((const double*)(this))[i]; }
    inline double& VCALL operator [](int i) { return ((double*)(this))[i]; }
};
#else
#define VCALL 
typedef __m128d d128;
#endif

现在这项工作在所有平台上都能很好地工作:

d128 VCALL add(d128 a, d128 b){
    return _mm_add_pd(a, b);
}

同样如此:

double VCALL getx(d128 a) {
    return a[0];
}

(嗯,在 VC++ 下访问单个元素有点不愉快,无论您采用哪种方式!)

如果您仍然坚持使用特定类型(因为您想重载 +、-、/、* 运算符),请注意 gcc 和 clang 已经重载了所有常用运算符,所以对于 gcc/clang 我可以这样写:

d128 VCALL add(d128 a, d128 b){
    return a + b;
}