如何控制工会的 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;
}
我正在为 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;
}