10

gccでは、asmコードを介して2つのC変数で128ビットのxorを実行したい:どのように?

asm (
    "movdqa %1, %%xmm1;"
    "movdqa %0, %%xmm0;"
     "pxor %%xmm1,%%xmm0;"
     "movdqa %%xmm0, %0;"

    :"=x"(buff) /* output operand */
    :"x"(bu), "x"(buff)
    :"%xmm0","%xmm1"
    );

しかし、セグメンテーション違反エラーがあります。これはobjdumpの出力です。

movq   -0x80(%rbp),%xmm2

movq   -0x88(%rbp),%xmm3

movdqa %xmm2,%xmm1

movdqa %xmm2,%xmm0

pxor   %xmm1,%xmm0

movdqa %xmm0,%xmm2

movq   %xmm2,-0x78(%rbp)
4

3 に答える 3

19

変数が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
于 2010-01-02T06:56:00.837 に答える
2

うーん、__builtin_ia32_pxor組み込みを使用してみませんか?

于 2010-01-02T06:41:30.553 に答える
1

後期モデルの gcc (私の場合は 4.5.5) では、オプション -O2 以上は、上記-fstrict-aliasingのコードが不平を言う原因となることを意味します。

supersuds.cpp:31: warning: dereferencing pointer ‘v64’ does break strict-aliasing rules
supersuds.cpp:30: note: initialized from here

これは、次のように型属性を追加することで解決できます。

typedef int64_t __attribute__((__may_alias__)) alias_int64_t; 
void print128(__m128i value) {
    alias_int64_t *v64 = (int64_t*)  &value;
    printf("%.16lx %.16lx\n", v64[1], v64[0]); 
}

最初に、typedef なしで属性を直接試しました。受け入れられましたが、それでも警告を受けました。typedef は、必要な魔法のようです。

ところで、これはここでの 2 番目の回答であり、編集が許可されている場所がまだわからないという事実をいまだに嫌っているため、これが属する場所に投稿できませんでした。

もう 1 つ、AMD64 では、%llx フォーマット指定子を %lx に変更する必要があります。

于 2010-12-09T00:44:25.013 に答える