Compilateur MS C # et code non optimisé

Remarque: j’ai remarqué quelques erreurs dans mon exemple posté – modification pour y remédier

Le compilateur officiel C # fait des choses intéressantes si vous n’activez pas l’optimisation.

Par exemple, une simple déclaration if:

int x; // ... // if (x == 10) // do something 

devient quelque chose comme ce qui suit si optimisé:

 ldloc.0 ldc.i4.s 10 ceq bne.un.s do_not_do_something // do something do_not_do_something: 

mais si nous désactivons l’optimisation, cela devient quelque chose comme ceci:

 ldloc.0 ldc.i4.s 10 ceq ldc.i4.0 ceq stloc.1 ldloc.1 brtrue.s do_not_do_something // do something do_not_do_something: 

Je n’arrive pas à comprendre ce que je pense. Pourquoi tout ce code supplémentaire, qui n’est apparemment pas présent dans la source? En C #, cela équivaudrait à:

 int x, y; // ... // y = x == 10; if (y != 0) // do something 

Est-ce que quelqu’un sait pourquoi il fait ça?

Je ne comprends pas bien le but de la question. On dirait que vous vous demandez “pourquoi le compilateur produit-il du code non optimisé lorsque le commutateur d’optimisation est désactivé?” qui se répond un peu.

Cependant, je vais tenter le coup. Je pense que la question est en fait quelque chose du genre “quelle décision de conception amène le compilateur à émettre une déclaration, un magasin et une charge du local n ° 1, qui peuvent être optimisés à l’extérieur?”

La réponse est parce que le codegen non optimisé est conçu pour être clair, sans ambiguïté, facile à déboguer et pour encourager la gigue à générer du code qui ne collecte pas les déchets de manière agressive. L’un des moyens d’atteindre tous ces objectives est de générer des sections locales pour la plupart des valeurs, même temporaires , de la stack. Jetons un coup d’oeil à un exemple plus compliqué. Supposons que vous ayez:

 Foo(Bar(123), 456) 

Nous pourrions générer ceci comme:

 push 123 call Bar - this pops the 123 and pushes the result of Bar push 456 call Foo 

C’est gentil, efficace et petit, mais cela n’atteint pas nos objectives. C’est clair et sans ambiguïté, mais il n’est pas facile de déboguer car le ramasse-miettes peut devenir agressif. Si, pour une raison quelconque, Foo ne fait rien avec son premier argument, le GC est autorisé à récupérer la valeur de retour de Bar avant que Foo ne s’exécute.

Dans la version non optimisée, nous générerions quelque chose de plus semblable à

 push 123 call Bar - this pops the 123 and pushes the result of Bar store the top of the stack in a temporary location - this pops the stack, and we need it back, so push the value in the temporary location back onto the stack push 456 call Foo 

Maintenant, la gigue a un indice important qui dit “hé la gigue, maintenez cela en vie dans le local pendant un moment, même si Foo ne l’utilise pas

La règle générale est “créer des variables locales à partir de toutes les valeurs temporaires de la construction non optimisée”. Et alors là vous allez; afin d’évaluer le “si”, nous devons évaluer une condition et la convertir en bool. (Bien entendu, la condition ne doit pas nécessairement être de type bool; elle peut être d’un type implicitement convertible en bool ou implémenter une paire opérateur vrai / opérateur faux.) Le générateur de code non optimisé a été informé de “transformer de manière agressive toutes les valeurs temporaires. dans les locaux “, et c’est ce que vous obtenez.

Je suppose que dans ce cas, nous pourrions supprimer cela sur les postes temporaires qui sont des conditions dans les déclarations “if”, mais cela ressemble à un travail qui ne me procure aucun avantage pour le client . Puisque j’ai une stack de travail tant que votre arm a des avantages tangibles pour le client, je ne changerai pas le générateur de code non optimisé, qui génère du code non optimisé, exactement comme il est supposé.

Je ne vois pas vraiment le problème, tout le code optimisé a été d’optimiser un seul local référencé (combo stloc ldloc).

La raison pour laquelle il est présent dans la version de débogage est que vous pouvez voir la valeur de l’affectation au local avant de l’utiliser.

Edit: Je vois maintenant l’autre ceq supplémentaire.

Mise à jour 2:

Je vois ce qui se passe. Etant donné que les booléens sont représentés par 0 et! 0, la version de débogage effectue la deuxième comparaison. OTOH, l’optimiseur peut probablement prouver quelque chose sur la sécurité du code.

Le code non optimisé ressemblerait en réalité à:

 int x, _local; // _local is really bool _local = (x == 10) == 0; // ceq is ==, not <, not sure why you see that if (_local) // as in C, iow _local != 0 implied { ... } 

Pour obtenir une réponse spécifique, vous devrez attendre qu’un membre de l’équipe du compilateur C # ou un membre proche de ce groupe fournisse une explication détaillée de ce cas.

Cependant, il ne s’agit généralement que d’un artefact de la génération de code, dans lequel des routines courantes sont écrites pour traiter de nombreux cas différents pour une instruction particulière, comme dans votre cas.

Cette généralisation aboutit à un code fonctionnel mais souvent moins qu’optimal dans certains cas. C’est pourquoi les passes d’optimisation existent pour effectuer diverses optimisations sur le code généré afin de supprimer le code redondant, le déroulement de la boucle, l’optimisation des trous de visière, le partage de code, etc.

Lors de la compilation en mode débogage, il est également possible de prendre en charge le débogueur. Par exemple, une instruction NOP peut être insérée dans le code pour faciliter la création d’un point d’arrêt lors de son exécution dans le débogueur, mais elle est supprimée pour les versions validées.