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
.)
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. –
'ror' est la rotation ** droite ** donc' (val >> 13) | (val << (32 - 13)) ' – Jester
[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) –