Parfois, les compilateurs génèrent du code avec des duplications d'instructions étranges qui peuvent être supprimées en toute sécurité. Considérez le morceau de code suivant:
int gcd(unsigned x, unsigned y) {
return x == 0 ? y : gcd(y % x, x);
}
Voici le code d'assemblage (généré par clang 5. avec optimisations activées):
gcd(unsigned int, unsigned int): # @gcd(unsigned int, unsigned int)
mov eax, esi
mov edx, edi
test edx, edx
je .LBB0_1
.LBB0_2: # =>This Inner Loop Header: Depth=1
mov ecx, edx
xor edx, edx
div ecx
test edx, edx
mov eax, ecx
jne .LBB0_2
mov eax, ecx
ret
.LBB0_1:
ret
Dans l'extrait de code suivant:
mov eax, ecx
jne .LBB0_2
mov eax, ecx
Si le saut ne se produit pas, eax
est réaffecté sans raison évidente.
L'autre exemple est deux ret à la fin de la fonction: l'un fonctionnerait parfaitement aussi.
Le compilateur n'est-il tout simplement pas assez intelligent ou il y a une raison pour ne pas supprimer les doublons?
Les compilateurs peuvent effectuer des optimisations qui ne sont pas évidentes pour les gens et la suppression des instructions ne rend pas toujours les choses plus rapides.
Une petite quantité de recherche montre que divers processeurs AMD ont des problèmes de prédiction de branche lorsqu'un RET est immédiatement après une branche conditionnelle. En remplissant cet emplacement avec ce qui est essentiellement un no-op, le problème de performances est évité.
Mise à jour:
Exemple de référence, la section 6.2 du "Guide d'optimisation des logiciels pour les processeurs AMD64" (voir http://support.AMD.com/TechDocs/25112.PDF ) dit:
Plus précisément, évitez les deux situations suivantes:
Tout type de branche (conditionnelle ou inconditionnelle) qui a pour cible l'instruction RET à retour unique sur un octet. Voir "Exemples".
Une branche conditionnelle qui se produit dans le code juste avant l'instruction RET de retour proche à un octet.
Il explique également en détail pourquoi les cibles de saut doivent être alignées, ce qui est également susceptible d'expliquer les RET en double à la fin de la fonction.
Tout compilateur aura un tas de transformations pour renommer les registres, dérouler, hisser, etc. La combinaison de leurs sorties peut conduire à des cas sous-optimaux tels que ce que vous avez montré. Marc Glisse offre de bons conseils: cela vaut la peine de rapporter un bug. Vous décrivez la possibilité pour un optimiseur de judas de supprimer les instructions qui
Cela ressemble à une opportunité pour exécution symbolique techniques. Si le solveur de contraintes ne trouve aucun point de branchement pour un MOV donné, c'est peut-être vraiment un NOP.