変数が16バイトに整列されていない場合、セグメンテーション違反の問題が発生します。CPUは、アラインされていないメモリアドレスとの間でMOVDQAを実行できず、プロセッサレベルの「GP例外」を生成し、OSにアプリのセグメンテーション違反を促します。
宣言する(スタック、グローバル)、またはヒープに割り当てるC変数は、通常、16バイト境界に整列されませんが、偶然に整列される場合があります。__m128または__m128iデータ型を使用して、適切なアライメントを確保するようにコンパイラーに指示できます。それらのそれぞれは、適切に調整された128ビット値を宣言します。
さらに、objdumpを読み取ると、コンパイラは、MOVQ命令を使用してオペランドをスタックからxmm2およびxmm3レジスタにコピーするコードでasmシーケンスをラップし、asmコードで値をxmm0およびxmm1にコピーするように見えます。xmm0にxor-ingした後、ラッパーは結果をxmm2にのみコピーして、スタックにコピーし直します。全体的に、それほど効率的ではありません。MOVQは一度に8バイトをコピーし、(状況によっては)8バイトにアラインされたアドレスを期待します。アラインされていないアドレスを取得すると、MOVDQAと同じように失敗する可能性があります。ただし、ラッパーコードは、整列されたオフセット(-0x80、-0x88、およびそれ以降の-0x78)をBPレジスタに追加します。これには、整列された値が含まれる場合と含まれない場合があります。全体として、生成されたコードに整合性の保証はありません。
以下は、引数と結果が正しく整列されたメモリ位置に格納され、正常に機能しているように見えることを保証します。
#include <stdio.h>
#include <emmintrin.h>
void print128(__m128i value) {
int64_t *v64 = (int64_t*) &value;
printf("%.16llx %.16llx\n", v64[1], v64[0]);
}
void main() {
__m128i a = _mm_setr_epi32(0x00ffff00, 0x00ffff00, 0x00ffff00, 0x10ffff00), /* low dword first! */
b = _mm_setr_epi32(0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff),
x;
asm (
"movdqa %1, %%xmm0;" /* xmm0 <- a */
"movdqa %2, %%xmm1;" /* xmm1 <- b */
"pxor %%xmm1, %%xmm0;" /* xmm0 <- xmm0 xor xmm1 */
"movdqa %%xmm0, %0;" /* x <- xmm0 */
:"=x"(x) /* output operand, %0 */
:"x"(a), "x"(b) /* input operands, %1, %2 */
:"%xmm0","%xmm1" /* clobbered registers */
);
/* printf the arguments and result as 2 64-bit hex values */
print128(a);
print128(b);
print128(x);
}
(gcc、ubuntu 32ビット)でコンパイルする
gcc -msse2 -o app app.c
出力:
10ffff0000ffff00 00ffff0000ffff00
0000ffff0000ffff 0000ffff0000ffff
10ff00ff00ff00ff 00ff00ff00ff00ff
上記のコードでは、コンパイラが128の整数リテラルをサポートしていない可能性があるため、_mm_setr_epi32を使用してaとbを128ビット値で初期化します。
print128は、128ビット整数の16進表現を書き出すことができない場合があるため、書き出します。
以下は短く、重複コピーの一部を回避します。コンパイラーは、隠しラッピングmovdqaを追加して、レジスターを自分でロードしなくても、pxor%2、%0が魔法のように機能するようにします。
#include <stdio.h>
#include <emmintrin.h>
void print128(__m128i value) {
int64_t *px = (int64_t*) &value;
printf("%.16llx %.16llx\n", px[1], px[0]);
}
void main() {
__m128i a = _mm_setr_epi32(0x00ffff00, 0x00ffff00, 0x00ffff00, 0x10ffff00),
b = _mm_setr_epi32(0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff);
asm (
"pxor %2, %0;" /* a <- b xor a */
:"=x"(a) /* output operand, %0 */
:"x"(a), "x"(b) /* input operands, %1, %2 */
);
print128(a);
}
以前と同じようにコンパイルします。
gcc -msse2 -o app app.c
出力:
10ff00ff00ff00ff 00ff00ff00ff00ff
または、インラインアセンブリを避けたい場合は、代わりにSSE組み込み関数を使用できます(PDF)。これらは、MMX/SSE命令をCのような構文でカプセル化するインライン関数/マクロです。_mm_xor_si128は、タスクを1回の呼び出しに減らします。
#include <stdio.h>
#include <emmintrin.h>
void print128(__m128i value) {
int64_t *v64 = (int64_t*) &value;
printf("%.16llx %.16llx\n", v64[1], v64[0]);
}
void main()
{
__m128i x = _mm_xor_si128(
_mm_setr_epi32(0x00ffff00, 0x00ffff00, 0x00ffff00, 0x10ffff00), /* low dword first !*/
_mm_setr_epi32(0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff));
print128(x);
}
コンパイル:
gcc -msse2 -o app app.c
出力:
10ff00ff00ff00ff 00ff00ff00ff00ff