Pourquoi la valeur d'énumération d'un tableau multidimensionnel n'est-elle pas égale à elle-même?

151

Considérer:

using System;

public class Test
{
    enum State : sbyte { OK = 0, BUG = -1 }

    static void Main(string[] args)
    {
        var s = new State[1, 1];
        s[0, 0] = State.BUG;
        State a = s[0, 0];
        Console.WriteLine(a == s[0, 0]); // False
    }
}

Comment cela peut-il être expliqué? Cela se produit dans les versions de débogage dans Visual Studio 2015 lors de l'exécution dans le JIT x86. Une version publiée ou exécutée dans le JIT x64 imprime True comme prévu.

Pour reproduire à partir de la ligne de commande:

csc Test.cs /platform:x86 /debug

( /debug:pdbonly, /debug:portableet /debug:fullaussi reproduire.)

shingo
la source
2
ideone.com/li3EzY c'est vrai. ajouter plus d'informations sur la version .net, IDE, compilateur
Backs
1
Pareil ici. Mais après avoir manipulé les paramètres du projet, j'ai compris que décocher la case "Préférer 32 bits" dans l'onglet "Construire" le fait fonctionner comme prévu - renvoyer vrai. Donc, cela ressemble à un problème de WoW64 pour moi.
Dmitry Rotay
2
Il semble que vous ayez signalé un bug dans le framework.
Fabien PERRONNET
1
Fait intéressant, exécuter le code cassé ildasmpuis le ilasm«corriger» ...
Jon Skeet
2
Le /debug=IMPLdrapeau le laisse brisé; /debug=OPT"corrige".
Jon Skeet

Réponses:

163

Vous avez trouvé un bogue de génération de code dans la gigue .NET 4 x86. C'est très inhabituel, il n'échoue que lorsque le code n'est pas optimisé. Le code machine ressemble à ceci:

        State a = s[0, 0];
013F04A9  push        0                            ; index 2 = 0
013F04AB  mov         ecx,dword ptr [ebp-40h]      ; s[] reference
013F04AE  xor         edx,edx                      ; index 1 = 0
013F04B0  call        013F0058                     ; eax = s[0, 0]
013F04B5  mov         dword ptr [ebp-4Ch],eax      ; $temp1 = eax 
013F04B8  movsx       eax,byte ptr [ebp-4Ch]       ; convert sbyte to int
013F04BC  mov         dword ptr [ebp-44h],eax      ; a = s[0, 0]
        Console.WriteLine(a == s[0, 0]); // False
013F04BF  mov         eax,dword ptr [ebp-44h]      ; a
013F04C2  mov         dword ptr [ebp-50h],eax      ; $temp2 = a
013F04C5  push        0                            ; index 2 = 0
013F04C7  mov         ecx,dword ptr [ebp-40h]      ; s[] reference 
013F04CA  xor         edx,edx                      ; index 1 = 0
013F04CC  call        013F0058                     ; eax = s[0, 0]
013F04D1  mov         dword ptr [ebp-54h],eax      ; $temp3 = eax 
                                               ; <=== Bug here!
013F04D4  mov         eax,dword ptr [ebp-50h]      ; a == s[0, 0] 
013F04D7  cmp         eax,dword ptr [ebp-54h]  
013F04DA  sete        cl  
013F04DD  movzx       ecx,cl  
013F04E0  call        731C28F4  

Une affaire compliquée avec beaucoup de temporaires et de duplication de code, c'est normal pour du code non optimisé. L'instruction à 013F04B8 est remarquable, c'est là que la conversion nécessaire de sbyte en entier de 32 bits se produit. La fonction d'assistance de lecture de tableau a renvoyé 0x0000000FF, égal à State.BUG, et qui doit être converti en -1 (0xFFFFFFFF) avant que la valeur puisse être comparée. L'instruction MOVSX est une instruction Sign eXtension.

La même chose se produit à nouveau à 013F04CC, mais cette fois, il n'y a pas d' instruction MOVSX pour effectuer la même conversion. C'est là que les puces tombent, l'instruction CMP compare 0xFFFFFFFF avec 0x000000FF et c'est faux. Il s'agit donc d'une erreur d'omission, le générateur de code n'a pas réussi à émettre à nouveau MOVSX pour effectuer la même conversion sbyte en int.

Ce qui est particulièrement inhabituel dans ce bogue, c'est que cela fonctionne correctement lorsque vous activez l'optimiseur, il sait maintenant utiliser MOVSX dans les deux cas.

La raison probable pour laquelle ce bogue n'a pas été détecté pendant si longtemps est l'utilisation de sbyte comme type de base de l'énumération. Assez rare à faire. L'utilisation d'un tableau multidimensionnel est également instrumentale, la combinaison est fatale.

Sinon, un bug assez critique je dirais. Son étendue est difficile à deviner, je n'ai que la gigue 4.6.1 x86 à tester. Le jitter x64 et le jitter 3.5 x86 génèrent un code très différent et évitent ce bug. La solution de contournement temporaire pour continuer est de supprimer sbyte comme type de base enum et de le laisser être la valeur par défaut, int , de sorte qu'aucune extension de signe n'est nécessaire.

Vous pouvez déposer le bogue sur connect.microsoft.com, un lien vers ce Q + A devrait suffire à leur dire tout ce qu'ils doivent savoir. Faites-moi savoir si vous ne voulez pas prendre le temps et je m'en occuperai.

Hans Passant
la source
33
Bonnes données solides avec la cause exacte d'un problème aussi étrange, toujours un plaisir à lire, +1.
Lasse V. Karlsen
11
Veuillez publier un lien vers l'article connect.microsoft.com afin que nous puissions voter.
Hans Passant
Je suppose que l'utilisation byteau lieu de sbytedevrait être bien aussi et pourrait être préférable si le code réel est utilisé avec, par exemple, un ORM où vous ne voulez pas que vos drapeaux dans la base de données prennent de l'espace supplémentaire.
Voo le
6
Je publierais le bogue sur dotnet / coreclr plutôt que de me connecter, vous accéderez directement aux développeurs JIT.
Lucas Trzesniewski
8
Je suis dev dans l'équipe JIT de Microsoft. J'ai reproduit le bogue et ai ouvert un problème pour celui-ci en interne (l'expédition de x86 JIT n'est pas encore ouverte sur github). En ce qui concerne le moment où cela serait corrigé, je prévois que nous aurons ce correctif inclus dans la prochaine version majeure des outils. Si ce bogue a un impact commercial et que vous avez besoin d'un correctif plus tôt, veuillez déposer le problème de connexion (connect.microsoft.com) afin que nous puissions examiner l'impact et les alternatives dont nous disposons pour obtenir un correctif plus rapidement.
Russell C. Hadley
8

Considérons la déclaration d'OP:

enum State : sbyte { OK = 0, BUG = -1 }

Comme le bogue ne se produit que lorsque BUGest négatif (de -128 à -1) et que State est une énumération d' octets signés, j'ai commencé à supposer qu'il y avait un problème de distribution quelque part.

Si vous exécutez ceci:

Console.WriteLine((sbyte)s[0, 0]);
Console.WriteLine((sbyte)State.BUG);
Console.WriteLine(s[0, 0]);
unchecked
{
    Console.WriteLine((byte) State.BUG);
}

il affichera:

255

-1

PUNAISE

255

Pour une raison que j'ignore (à partir de maintenant), il s[0, 0] est converti en octet avant l'évaluation et c'est pourquoi il prétend que a == s[0,0]c'est faux.

Thomas Ayoub
la source