Pourquoi les instructions AND sont-elles générées?

Pour un code tel que celui-ci:

int res = 0; for (int i = 0; i < 32; i++) { res += 1 << i; } 

Ce code est généré (mode de publication, pas de débogueur attaché, 64 bits):

  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 l’intérêt de la plupart des instructions, mais qu’en est-il des instructions AND? De toute façon, ecx ne sera jamais plus que 0x1F dans ce code, mais je l’excuse pour ne pas l’avoir remarqué (et aussi pour ne pas avoir remarqué 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’parsing après tout. Mais plus important encore, SHL avec un opérande de 32 bits masque déjà cl par 0x1F. Il me semble donc que ces ET sont totalement inutiles. Pourquoi sont-ils générés? Ont-ils un but qui me manque?

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 pour l’instruction CIL shl dit:

La valeur de retour n’est pas spécifiée si shiftAmount est supérieur ou égal à la taille de la valeur .

La spécification C #, cependant, définit le décalage de 32 bits pour prendre le compte de décalage mod 32:

Lorsque le type de x est int ou uint, le compte de décalage est donné par les cinq bits de compte de poids faible. En d’autres termes, le nombre d’équipes est calculé à partir du count & 0x1F .

Dans cette situation, le compilateur C # ne peut pas vraiment faire mieux que d’émettre une opération explicite and . Le mieux que vous puissiez espérer, c’est que JITter le remarque et optimise le redondant, mais cela prend du temps et la rapidité de JIT est très importante. Donc, considérez ceci comme le prix payé pour un système basé sur JIT.

La vraie question, je suppose, est de savoir pourquoi CIL spécifie l’instruction shl cette façon, alors que C # et x86 spécifient tous deux le comportement de troncature. Cela, je ne le sais pas, mais je suppose qu’il est important que la spécification CIL évite de spécifier un comportement pouvant entraîner une perte d’argent pour quelque chose de coûteux avec certains jeux d’instructions. En même temps, il est important que C # ait aussi peu de comportements indéfinis que possible, car les utilisateurs finissent invariablement par utiliser de tels comportements jusqu’à la prochaine version du compilateur / framework / OS / quoi que ce soit qui les modifie, rompant le code.

Les cœurs x64 appliquent déjà le masque de 5 bits à la quantité de décalage. Dans le manuel Intel Processor, volume 2B page 4-362:

L’opérande de destination peut être un registre ou un emplacement de mémoire. L’opérande de comptage peut être une valeur immédiate ou le registre CL. Le compte est masqué sur 5 bits (ou 6 bits si en mode 64 bits et si REG.W est utilisé). Un codage d’opcode spécial est fourni pour un compte de 1.

Donc, c’est un code machine qui n’est pas nécessaire. Malheureusement, le compilateur C # ne peut émettre aucune hypothèse sur le comportement du processeur et doit appliquer les règles du langage C #. Et générez 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 native int) du nombre de bits spécifié par shiftAmount. shiftAmount est de type int32 ou natif int. La valeur de retour n’est pas spécifiée si shiftAmount est supérieur ou égal à la largeur de la valeur .

Non spécifié est le problème ici. Le fait de verrouiller le comportement spécifié au-dessus de détails d’implémentation non spécifiés génère le code inutile. Techniquement, la gigue pourrait optimiser l’opcode. Bien que ce soit difficile, il ne connaît pas la règle linguistique. Toute langue qui ne spécifie aucun masquage aura du mal à générer une IL appropriée. Vous pouvez poster sur connect.microsoft.com pour obtenir l’avis de l’équipe de gigue sur le sujet.

Le compilateur C # doit insérer ces instructions AND lors de la génération du code intermédiaire (indépendant de la machine), car l’opérateur de décalage à gauche C # doit utiliser uniquement les 5 bits de poids faible.

Lors de la génération du code x86, l’optimisation du compilateur peut supprimer ces instructions inutiles. Mais, apparemment, cette optimisation est ignorée (probablement parce qu’elle ne peut pas consacrer beaucoup de temps à l’parsing).