2010-01-02 100 views
10

在gcc中,我想通過asm代碼做一個帶有2個C變量的128位xor:怎麼樣?如何使用128位C變量和xmm 128位asm?

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) 

回答

1

嗯,爲什麼不使用__builtin_ia32_pxor內在?

18

如果變量不是16字節對齊的,你會看到段錯誤問題。 The CPU can't MOVDQA to/from unaligned memory addresses,並且會生成處理器級別的「GP異常」,提示操作系統對您的應用進行段錯誤檢測。

您聲明(堆棧,全局)或堆上分配的C變量通常不與16字節的邊界對齊,但有時候偶爾會得到一個對齊的變量。您可以通過使用__m128或__m128i數據類型來指示編譯器確保正確對齊。其中每個都聲明一個正確對齊的128位值。

此外,讀取objdump時,它看起來像編譯器用代碼包裝asm序列,使用MOVQ指令將操作數從堆棧複製到xmm2和xmm3寄存器,然後將您的asm代碼複製到xmm0和xmm1。在進入xmm0之後,包裝器將結果複製到xmm2,然後將其複製回堆棧。總的來說,效率不是很高。 MOVQ一次複製8個字節,and expects (under some circumstances), an 8-byte aligned address。獲取未對齊的地址,它可能會像MOVDQA一樣失敗。但是,包裝器代碼會向BP寄存器添加一個對齊的偏移量(-0x80,-0x88和之後的-0x78),該寄存器可能包含或不包含對齊的值。總的來說,在生成的代碼中沒有對齊的保證。

下保證了參數和結果被存儲在正確對準的存儲器位置,並且似乎很好地工作:

#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 

在上面的代碼中,_mm_setr_epi32用於初始化ab 128位值,因爲編譯器可能不支持128整數文字。

print128寫出128位整數的十六進制表示,因爲printf可能無法這樣做。


以下內容較短,避免了一些重複複製。編譯器會將其隱藏的包裝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 intrinsics instead(PDF)。這些內聯函數/宏使用類C語法封裝MMX/SSE指令。 _mm_xor_si128降低你的任務是單一的呼叫:

#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 
+0

Roberto,回答你的問題,它有多長時間:要xor對齊已有的128位值,只需調用_mm_xor_si128。它位於emmintrin.h中,並在答案中列出的英特爾PDF中記錄。彙編時,它是一個在128位xmm寄存器上工作的單個操作碼(pxor)。如果代碼的其餘部分也處理128位值,它會開始增加值;否則你只需要向xmm regs推送值。對於64位或更少的值,我假設你知道你可以使用C xor操作符^ –

+0

同樣,讀取objdump時,它看起來像生成的代碼將值加載到xmm2/3中,然後將它們複製到xmm0/1中,執行xor,然後在將結果複製到內存之前將結果複製回xmm2。總的來說,效率不高。我建議(1)開啓優化,並且(2)考慮在asm中編寫包含循環,以避免冗餘副本。 –

1

在最新型號的海灣合作委員會(我的是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似乎是魔術中必不可少的一部分。

順便說一句,這是我的第二個答案在這裏,我仍然討厭我不知道我被允許編輯的地方的事實,所以我無法發佈它在它屬於哪裏。

還有一點,在AMD64下,%llx格式說明符需要更改爲%lx。