如何控制工会的ABI?

问题描述

我正在研究 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

这将打包的 XMM 寄存器用于 SIMD(__m128d ABI)。如果我改用联合,则会导致使用认浮点 ABI。

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

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

有没有办法明确选择 __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;
}