2017-09-15 1 views
1

J'essaie de gérer à la fois les compilateurs MSVC et GCC lors de la mise à jour de cette base de code pour travailler sur GCC. Mais je ne sais pas exactement comment fonctionne GCC en ligne ASM. Maintenant, je ne suis pas très doué pour traduire ASM en C sinon j'utiliserais simplement C au lieu d'ASM.MSVC Inline ASM à GCC

SLONG Div16(signed long a, signed long b) 
{ 
    signed long v; 
#ifdef __GNUC__ // GCC doesnt work. 
__asm() { 
#else // MSVC 
__asm { 
#endif 
     mov edx, a 
     mov ebx, b   
     mov eax, edx   
     shl eax, 16   
     sar edx, 16    
     idiv ebx    
     mov v, eax    
    } 
    return v; 
} 

signed long ROR13(signed long val) 
{ 
    _asm{ 
     ror val, 13 
    } 
} 

Je suppose ROR13 fonctionne quelque chose comme (val << 13) | (val >> (32 - 13)) mais le code ne produit pas la même sortie.

Quelle est la bonne façon de traduire cette ASM en ligne en GCC et/ou quelle est la traduction C de ce code?

+1

La syntaxe différente pour la directive "asm" n'est pas le seul problème que vous avez. GCC utilise une [syntaxe différente] (https://en.wikibooks.org/wiki/X86_Assembly/GAS_Syntax) même pour les instructions de l'assembleur. –

+0

'ror' est la rotation ** droite ** donc' (val >> 13) | (val << (32 - 13)) ' – Jester

+0

[Compiler intrinsics] (https://en.wikipedia.org/wiki/Intrinsic_function) peut vous aider. Par exemple, cette Visual Studio [x86 Intrinsics List] (https://msdn.microsoft.com/en-us/library/hh977023.aspx) –

Répondre

3

GCC uses a completely different syntax for inline assembly que MSVC, donc c'est un peu de travail pour maintenir les deux formes. Ce n'est pas une très bonne idée non plus. There are many problems with inline assembly. Les gens l'utilisent souvent parce qu'ils pensent que cela accélèrera le code, mais cela a généralement l'effet inverse. Unless you're an expert in both assembly language and the compiler's code-generation strategies, you are far better off letting the compiler's optimizer generate the code. Lorsque vous essayez de faire cela, vous devrez faire attention ici: les décalages droits signés sont définis en C, donc si vous vous souciez de la portabilité, vous devez convertir la valeur en un équivalent non signé type:

#include <limits.h> // for CHAR_BIT 

signed long ROR13(signed long val) 
{ 
    return ((unsigned long)val >> 13) | 
      ((unsigned long)val << ((sizeof(val) * CHAR_BIT) - 13)); 
} 

(Voir aussi Best practices for circular shift (rotate) operations in C++).

Ceci aura la même sémantique que votre code original: ROR val, 13. En fait, MSVC générera précisément ce code objet, tout comme GCC. (Clang, intéressant, fera ROL val, 19, ce qui produit le même résultat, compte tenu de la façon dont les rotations fonctionnent.CI 17 génère un décalage étendu à la place: SHLD val, val, 19. Je ne sais pas pourquoi, peut-être que c'est plus rapide que la rotation sur certains processeurs Intel, ou . peut-être est la même chose sur Intel, mais plus lent sur AMD)

Pour mettre en œuvre Div16 en C pur, vous voulez:

signed long Div16(signed long a, signed long b) 
{ 
    return ((long long)a << 16)/b; 
} 

sur une architecture 64 bits qui peut faire la division 64 bits native, (en supposant long est toujours un type 32 bits comme sur Windows) cela sera transformé en:

movsxd rax, a # sign-extend from 32 to 64, if long wasn't already 64-bit 
shl  rax, 16 
cqo    # sign-extend rax into rdx:rax 
movsxd rcx, b 
idiv rcx  # or idiv b if the inputs were already 64-bit 
ret 

Malheureusement, sur x86 32 bits, le code n'est pas aussi bon. Les compilateurs émettent un appel dans leur fonction de bibliothèque interne qui fournit une division 64 bits étendue, car ils ne peuvent pas prouver que l'utilisation d'un seul 64b/32b => 32b idiv instruction n'aboutira pas. (Il déclenche une exception #DE si le quotient ne correspond pas à eax, plutôt que tronquer)

En d'autres termes, la transformation:

int32_t Divide(int64_t a, int32_t b) 
{ 
    return (a/b); 
} 

dans:

mov eax, a_low 
mov edx, a_high 
idiv b     # will fault if a/b is outside [-2^32, 2^32-1] 
ret 

n'est pas une optimisation légale: le compilateur est incapable d'émettre ce code. La norme linguistique stipule qu'une division 64/32 est promue en une division 64/64, ce qui produit toujours un résultat 64 bits. Le fait de lancer ou de contraindre ultérieurement ce résultat 64 bits à une valeur de 32 bits n'est pas pertinent pour la sémantique de l'opération de division proprement dite. La défaillance de certaines combinaisons de a et b violerait la règle as-if, à moins que le compilateur puisse prouver que ces combinaisons de a et b sont impossibles.(Par exemple, si b était connu pour être supérieur à 1<<16, cela pourrait être une optimisation légale pour a = (int32_t)input; a <<= 16; Mais même si cela produirait le même comportement que la machine abstraite C pour toutes les entrées, gcc et clang ne le font pas actuellement optimisation.)


Il est tout simplement pas une bonne façon de passer outre les règles imposées par la norme linguistique et forcer le compilateur à émettre le code objet désiré. MSVC ne propose pas d'intrinsèque (bien qu'il existe une fonction Windows API, MulDiv, ce n'est pas rapide, et utilise simplement l'assemblage en ligne pour sa propre implémentation — et avec a bug in a certain case, maintenant cimenté grâce au besoin de rétrocompatibilité). Vous n'avez essentiellement pas d'autre choix que de recourir à l'assemblage, que ce soit en ligne ou à partir d'un module externe.

Ainsi, vous devenez laid. Il ressemble à ceci:

signed long Div16(signed long a, signed long b) 
{ 
#ifdef __GNUC__  // A GNU-style compiler (e.g., GCC, Clang, etc.) 
    signed long quotient; 
    signed long remainder; // (unused, but necessary to signal clobbering) 
    __asm__("idivl %[divisor]" 
      :   "=a" (quotient), 
         "=d" (remainder) 
      :   "0" ((unsigned long)a << 16), 
         "1" (a >> 16), 
      [divisor] "rm" (b) 
      : 
      ); 
    return quotient; 
#elif _MSC_VER  // A Microsoft-style compiler (i.e., MSVC) 
    __asm 
    { 
     mov eax, DWORD PTR [a] 
     mov edx, eax 
     shl eax, 16 
     sar edx, 16 
     idiv DWORD PTR [b] 
     // leave result in EAX, where it will be returned 
    } 
#else 
    #error "Unsupported compiler" 
#endif 
} 

Cela donne la sortie désirée sur les compilateurs Microsoft et GNU.

Eh bien, surtout. Pour une raison quelconque, lorsque vous utilisez la contrainte rm, qui donne au compilateur la liberté de choisir de traiter le diviseur comme opérande de mémoire ou de le charger dans un registre, Clang génère un code objet pire que si vous utilisez simplement r (qui force pour le charger dans un registre). Cela n'affecte pas GCC ou ICC. Si vous vous souciez de la qualité de la sortie sur Clang, vous voudrez probablement utiliser r, car cela donnera un bon code objet sur tous les compilateurs.

Live Demo on Godbolt Compiler Explorer

(Note: GCC utilise la SAL mnémoniques dans sa production, au lieu de la SHL mnémotechnique Ce sont identiques instructions de la différence que les questions de quarts de travail et à droite tous les programmeurs d'assemblage sain d'esprit utilisent SHL.. Je n'ai aucune idée pourquoi GCC émet SAL, mais vous pouvez juste le convertir mentalement en SHL.)

+0

Pourquoi ICC tourne avec SHLD: la raison cynique est que c'est * plus lent * sur AMD (6 uops, 3c de latence, sur Bulldozer/Ryzen), mais le même coût que ROR sur Intel après Nehalem. Le moins cynique est qu'il ne reconnaît pas tellement l'idiome de rotation et au lieu de cela implémente simplement shift-and-OR avec SHLD quand le décalage compte se compléter correctement. –

+0

Il reconnaît cependant les rotations et utilise BMI2 RORX quand c'est possible. https://godbolt.org/g/GL5YQw. (Voir aussi https://stackoverflow.com/questions/776508/best-practices-for-circular-shift-rotate-operations-in-c pour des rotations de nombre variable sûres sans UB, ce qui montre aussi que vous avez vraiment veux nommer votre fonction 'ror32' au lieu de tourner quelle que soit la largeur de' long', si vous essayez de faire une fonction portable!) –

+0

Votre exemple de division native 64 bits est bizarre/faux.Un compilateur C utilisera la taille d'opérande 64 bits pour 'idiv' ainsi que les décalages. Donc ça va 'movsx rcx, b' /' movsx rax, a'/'shl rax, 16' /' cqo' (pas cdq)/'idiv rcx' (https://godbolt.org/g/9Gf9Z3), car les entrées ne sont pas déjà étendues à 64 bits (en supposant que vos entrées sont 'int32_t', comme' long' sur Windows ou en général sur 32 bits). Et c'est bizarre de montrer 'shl a, 16' au lieu de se déplacer après avoir copié dans un registre connu. –