我想通过使用 LLVM 的 JIT 动态生成代码来加速我正在处理的程序。该算法可以对向量进行操作,我宁愿使用 LLVM 中的 SIMD 向量扩展来执行此操作(它不仅使某些操作更快,而且实际上使代码生成更简单)。
我是否有机会以合理的便携方式完成这项工作?
在 C 方面,我将使用 gcc、clang 或者可能是 icc 进行编译。float x 4
我的向量会很简单double x 4
。这个世界上非平台特定向量操作的事实标准似乎是 gcc 向量扩展:
typedef double Vector4 __attribute__ ((vector_size (sizeof(double)*4)));
检查生成的代码表明,clang 将double x 4
在寄存器中传递一个向量,而 gcc 则希望它在堆栈上 --- 这很糟糕。(它们都float x 4
在寄存器中传递向量。)
我的理解是这两个系统应该是 ABI 兼容的,但显然向量不计算在内。我真的可以这样做吗?
我的示例程序是::
typedef double real;
typedef real Vector4 __attribute__ ((vector_size (sizeof(real)*4)));
Vector4 scale(Vector4 a)
{
Vector4 s = {2, 2, 2, 2};
return a*s;
}
这与 LLVM 一起编译为:
scale:
movapd .LCPI0_0(%rip), %xmm2
mulpd %xmm2, %xmm0
mulpd %xmm2, %xmm1
ret
...但是 gcc 产生了这种恐怖:
scale:
subq $64, %rsp
movq %rdi, %rax
movsd .LC0(%rip), %xmm0
movapd 72(%rsp), %xmm1
movsd %xmm0, -56(%rsp)
movsd %xmm0, -48(%rsp)
movsd %xmm0, -72(%rsp)
movsd %xmm0, -64(%rsp)
mulpd -56(%rsp), %xmm1
movapd 88(%rsp), %xmm0
mulpd -72(%rsp), %xmm0
movapd %xmm1, -104(%rsp)
movq -104(%rsp), %rdx
movapd %xmm1, -24(%rsp)
movapd %xmm0, -8(%rsp)
movq %rdx, (%rdi)
movq -16(%rsp), %rdx
movq %rdx, 8(%rdi)
movq -8(%rsp), %rdx
movq %rdx, 16(%rdi)
movq (%rsp), %rdx
movq %rdx, 24(%rdi)
addq $64, %rsp
ret
如果我重新定义real
为 a float
,我会从两个编译器中得到这个(它们产生相同的代码):
scale:
mulps .LCPI0_0(%rip), %xmm0
ret
这些都是用$CC -O3 -S -msse test.c
.
更新:我突然想到,简单的解决方案就是使用 LLVM 创建一个蹦床,将结构转换为向量,反之亦然。这样,互操作性问题就被简化为由 ABI 确定的按值传递结构;这些向量仅存在于 LLVM-land 中。这意味着我只能在 LLVM 中使用 SIMD 的东西,但我可以忍受。
但是,我仍然想知道上述问题的答案;矢量很棒,我希望能够更多地使用它们。
更新更新:事实证明,C 通过值传递结构的方式非常疯狂......呃,疯狂!Astruct { double x, y, z; }
通过指针传递;a作为一对%xmm 寄存器struct { float x, y, z }
传递:并被打包到第一个中,而is 在第二个中...x
y
z
简单而无痛不是!