2010-01-02 3 views
10

en gcc, je veux faire un xor 128 bits avec 2 variables C, via le code asm: comment?comment travailler avec 128 bits C variable et xmm 128 bits asm?

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

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

mais j'ai une erreur de segmentation de segmentation; la sortie est la 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) 

Répondre

1

Umm, pourquoi ne pas utiliser la __builtin_ia32_pxor intrinsèque?

18

Vous verriez des problèmes de segmentation si les variables ne sont pas alignées sur 16 octets. The CPU can't MOVDQA to/from unaligned memory addresses, et générerait une «exception GP» au niveau du processeur, invitant l'OS à segfault votre application. Les variables C déclarées (pile, globale) ou allouées sur le tas ne sont généralement pas alignées sur une limite de 16 octets, bien que vous puissiez occasionnellement en obtenir une par hasard. Vous pouvez diriger le compilateur pour assurer un alignement correct en utilisant les types de données __m128 ou __m128i. Chacun de ceux-ci déclare une valeur 128 bits correctement alignée. En outre, en lisant objdump, il semblerait que le compilateur ait encapsulé la séquence asm avec du code pour copier les opérandes de la pile dans les registres xmm2 et xmm3 en utilisant l'instruction MOVQ, pour que votre code asm copie ensuite les valeurs dans xmm0 et xmm1. Après xor-ing dans xmm0, l'encapsuleur copie le résultat à xmm2 uniquement pour ensuite le recopier dans la pile. Dans l'ensemble, pas très efficace. MOVQ copie 8 octets à la fois, and expects (under some circumstances), an 8-byte aligned address. Obtenir une adresse non alignée, il pourrait échouer tout comme MOVDQA. Le code d'encapsulation, cependant, ajoute un décalage aligné (-0x80, -0x88 et plus tard -0x78) au registre BP, qui peut contenir ou non une valeur alignée. Dans l'ensemble, il n'y a aucune garantie d'alignement dans le code généré.

Ce qui suit assure les arguments et les résultats sont stockés dans des emplacements de mémoire alignés correctement, et semble fonctionner correctement:

#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); 
} 

compiler avec (gcc, ubuntu 32 bits)

gcc -msse2 -o app app.c 

sortie:

10ffff0000ffff00 00ffff0000ffff00 
0000ffff0000ffff 0000ffff0000ffff 
10ff00ff00ff00ff 00ff00ff00ff00ff 

Dans le code ci-dessus, _mm_setr_epi32 est utilisé pour initialiser a et b avec des valeurs de 128 bits, car le compilateur ne peut pas prendre en charge 128 entiers. Print128 écrit la représentation hexadécimale d'un entier de 128 bits, car printf ne peut pas le faire.


Ce qui suit est plus court et évite une partie de la copie.Le compilateur ajoute son movdqa emballage caché de faire pxor% 2,% 0 travailler comme par magie sans avoir à charger les registres sur votre propre:

#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); 
} 

compilation comme avant:

gcc -msse2 -o app app.c 

sortie:

10ff00ff00ff00ff 00ff00ff00ff00ff 

Sinon, si vous souhaitez éviter l'assemblage en ligne, vous pouvez utiliser le SSE intrinsics instead (PDF). Ce sont des fonctions/macros inline qui encapsulent les instructions MMX/SSE avec une syntaxe de type C. _mm_xor_si128 réduit votre tâche à un seul appel:

#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); 
} 

compilation:

gcc -msse2 -o app app.c 

sortie:

10ff00ff00ff00ff 00ff00ff00ff00ff 
+0

Roberto, pour répondre à votre question sur la durée: Pour xor alignés 128 bits valeurs que vous avez déjà, appelez _mm_xor_si128. Il est dans emmintrin.h, et est documenté dans l'intel PDF répertorié dans la réponse. En assembleur, il s'agit d'un opcode unique (pxor) qui fonctionne sur les registres xmm 128 bits. Il commence à ajouter de la valeur si le reste de votre code traite également des valeurs de 128 bits; sinon, vous seriez simplement en train de pousser des valeurs vers/depuis les regs xmm. Pour les valeurs de 64 bits et moins, je suppose que vous savez que vous pouvez simplement utiliser l'opérateur C xor,^ –

+0

Aussi, en lisant objdump, il semble que le code généré charge les valeurs dans xmm2/3 seulement pour ensuite les copier dans xmm0/1 , faites le xor, puis copiez le résultat sur xmm2 avant de le copier dans la mémoire. Dans l'ensemble, pas très efficace. Je suggère (1) d'activer les optimisations, et (2) envisage d'écrire la boucle contenant dans asm, pour éviter les copies redondantes. –

1

Sous gcc fin de modèle (le mien est 4.5.5) l'option -O2 ou ci-dessus implique -fstrict-aliasing ce qui provoque le fait de se plaindre du code ci-dessus:

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

Cela peut être résolu en fournissant attributs de types comme suit:

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]); 
} 

J'ai d'abord essayé l'attribut directement sans typedef. Cela a été accepté, mais j'ai quand même reçu l'avertissement. Le typedef semble être un morceau nécessaire de la magie.

BTW, ceci est ma deuxième réponse ici et je encore déteste le fait que je ne peux pas encore dire où je suis autorisé à modifier, donc je ne pouvais pas publier ce à quoi il appartenait.

Et encore une chose, sous AMD64, le spécificateur de format% llx doit être changé en% lx.

Questions connexes