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
(どちらもベクトルをレジスターに渡します。)
私の理解では、2 つのシステムは 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
すると、float
両方のコンパイラから次のようになります (同じコードが生成されます)。
scale:
mulps .LCPI0_0(%rip), %xmm0
ret
これらはすべて でコンパイルされました$CC -O3 -S -msse test.c
。
更新:単純な解決策は、LLVM を使用してトランポリンを作成し、構造体からベクトルに、またはその逆に変換することであることに突然気づきました。このようにして、相互運用性の問題は値渡し構造に縮小され、ABI によって突き止められます。ベクトルは LLVM ランドにのみ存在します。これは、LLVM 内で SIMD を使用することしかできないことを意味しますが、それを受け入れることができます。
ただし、上記の答えを知りたいです。ベクトルは素晴らしいので、もっと活用したいと思っています。
更新の更新: C が構造体を値で渡す方法は非常識であることがわかりました...えっと、非常識です! Astruct { double x, y, z; }
はポインターによって渡されます。aは%xmm レジスタのペアstruct { float x, y, z }
として渡されます: 最初のレジスタにパックされ、2 番目のレジスタにパックされます...x
y
z
シンプルで痛くない!