Pourquoi les instructions et sont-elles générées?
Pour un code comme celui-ci:
int res = 0;
for (int i = 0; i < 32; i++)
{
res += 1 << i;
}
Ce code est généré (mode de libération, aucun débogueur attaché, 64bit):
xor edx,edx
mov r8d,1
_loop:
lea ecx,[r8-1]
and ecx,1Fh ; why?
mov eax,1
shl eax,cl
add edx,eax
mov ecx,r8d
and ecx,1Fh ; why?
mov eax,1
shl eax,cl
add edx,eax
lea ecx,[r8+1]
and ecx,1Fh ; why?
mov eax,1
shl eax,cl
add edx,eax
lea ecx,[r8+2]
and ecx,1Fh ; why?
mov eax,1
shl eax,cl
add edx,eax
add r8d,4
cmp r8d,21h
jl _loop
Maintenant, je peux voir le point de la plupart des instructions là - bas, mais qu'est-ce qui se passe avec les instructions et? ecx ne sera jamais Plus que 0x1F dans ce code de toute façon, mais je l'excuse de ne pas le remarquer (et aussi de ne pas remarquer que le résultat est une constante), ce n'est pas un compilateur à l'avance qui peut se permettre de passer beaucoup de temps sur l'analyse après tout. Mais plus surtout, SHL avec un opérande 32 bits masque déjà cl par 0x1F. Il me semble donc que ces and sont entièrement inutiles. Pourquoi sont-ils générés? Ont-ils un but que je manque?
3 réponses
Le and
est déjà présent dans le code CIL émis par le compilateur C#:
IL_0009: ldc.i4.s 31
IL_000b: and
IL_000c: shl
La spécification de L'instruction CIL shl
indique:
, La valeur de retour n'est pas spécifié si shiftAmount est supérieure ou égale à la taille de valeur.
La spécification C#, cependant, définit le décalage 32 bits pour prendre le compte de décalage mod 32:
Lorsque le type de x est
int
ouuint,
le décalage est donné par la faible-de l'ordre de cinq les bits du comte. En d'autres termes, le nombre de postes est calculé à partir decount & 0x1F
.
Dans cette situation, le compilateur C# ne peut pas vraiment faire beaucoup mieux que d'émettre une opération and
explicite. Le mieux que vous pouvez espérer est que la gigue remarquera cela et optimisera le and
redondant, mais cela prend du temps, et la vitesse de JIT est assez importante. Alors considérez ceci le prix payé pour un système basé sur JIT.
La vraie question, je suppose, est pourquoi le CIL spécifie l'instruction shl
de cette façon, lorsque C# et x86 spécifient tous les deux le comportement de troncature. Que je ne sais pas, mais je suppose qu'il est important pour la spécification CIL d'éviter de spécifier un comportement qui peut JIT à quelque chose de cher sur certains ensembles d'instructions. En même temps, il est important que C# ait le moins de comportements indéfinis possible, car les gens finissent invariablement par utiliser de tels comportements indéfinis jusqu'à ce que la prochaine version du compilateur/framework/OS/whatever les Change, brisant le code.
Les cœurs X64 appliquent déjà le masque de 5 bits à la quantité de décalage. Du manuel du processeur Intel, volume 2B page 4-362:
L'opérande de destination peut être un registre ou un emplacement mémoire. L'opérande count peut être une valeur immédiate ou le registre CL. le nombre est masqué à 5 bits (ou 6 bits si en mode 64 bits et REG.W est utilisé). Un codage opcode spécial est fourni pour un nombre de 1.
Donc c'est du code machine qui n'est pas nécessaire. Malheureusement, le compilateur C# ne peut pas faire d'hypothèses sur le comportement du processeur et doit appliquer des règles de langage C#. Et générer IL dont le comportement est spécifié dans la spécification CLI. Ecma-335, Partion III, chapitre 3.58 dit ceci à propos de L'opcode SHL:
L'instruction shl décale la valeur (int32, int64 ou INT natif) laissée par le nombre de bits spécifié par shiftAmount. shiftAmount est de type int32 ou int natif. la valeur de retour n'est pas spécifiée si shiftAmount est supérieur à ou égale à la largeur de la valeur .
Non spécifié est le problème ici. Le boulonnage du comportement spécifié au-dessus des détails d'implémentation Non spécifiés produit le code inutile. Techniquement, la gigue pourrait optimiser l'opcode. Bien que ce soit délicat, il ne connaît pas la règle de la langue. Toute langue qui spécifie aucun masquage aura du mal à générer une IL appropriée. Vous pouvez poster à connect.microsoft.com pour obtenir le point de vue de l'équipe de gigue sur la question.
Le compilateur C # doit insérer ces instructions et tout en générant du code intermédiaire (indépendant de la machine), car L'opérateur de décalage gauche C# est nécessaire pour utiliser seulement 5 bits les moins significatifs.
Lors de la génération de code x86, l'optimisation du compilateur peut supprimer ces instructions inutiles. Mais, apparemment, il saute cette optimisation (probablement, parce qu'il ne peut pas se permettre de passer beaucoup de temps sur l'analyse).