¿Por qué el valor de enumeración de una matriz multidimensional no es igual a sí mismo?

151

Considerar:

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
    }
}

¿Cómo se puede explicar esto? Se produce en versiones de depuración en Visual Studio 2015 cuando se ejecuta en el xIT JIT. Una compilación de lanzamiento o ejecución en el x64 JIT imprime True como se esperaba.

Para reproducir desde la línea de comando:

csc Test.cs /platform:x86 /debug

( /debug:pdbonly, /debug:portableY /debug:fulltambién reproducirse.)

shingo
fuente
2
ideone.com/li3EzY es verdad. agregue más información sobre la versión .net, IDE, compilador
Retrocede el
1
Igual que aquí. Pero después de jugar con la configuración del proyecto, descubrí que desmarcar la casilla de verificación "Preferir 32 bits" en la pestaña "Construir" hace que funcione según lo previsto, volviendo verdadero. Entonces, me parece un problema de WoW64.
Dmitry Rotay
2
Parece que apuntaste un error en el marco.
Fabien PERRONNET
1
Curiosamente, la ejecución del código abierto paso ildasmy luego ilasm"soluciones" que ...
Jon Skeet
2
La /debug=IMPLbandera la deja rota; /debug=OPT"lo arregla".
Jon Skeet

Respuestas:

163

Encontró un error de generación de código en .NET 4 x86 jitter. Es muy inusual, solo falla cuando el código no está optimizado. El código de la máquina se ve así:

        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  

Un asunto pesado con muchos temporales y duplicación de código, eso es normal para el código no optimizado. La instrucción en 013F04B8 es notable, ahí es donde ocurre la conversión necesaria de sbyte a un entero de 32 bits. La función auxiliar de obtención de matriz devolvió 0x0000000FF, igual a State.BUG, y eso debe convertirse a -1 (0xFFFFFFFF) antes de que se pueda comparar el valor. La instrucción MOVSX es una instrucción Sign eXtension.

Lo mismo sucede nuevamente en 013F04CC, pero esta vez no hay instrucciones MOVSX para hacer la misma conversión. Ahí es donde se caen los chips, la instrucción CMP compara 0xFFFFFFFF con 0x000000FF y eso es falso. Entonces, este es un error de omisión, el generador de código no pudo emitir MOVSX nuevamente para realizar la misma conversión de sbyte a int.

Lo que es particularmente inusual acerca de este error es que funciona correctamente cuando habilita el optimizador, ahora sabe usar MOVSX en ambos casos.

La razón probable de que este error no se haya detectado durante tanto tiempo es el uso de sbyte como el tipo base de la enumeración. Muy raro de hacer. El uso de una matriz multidimensional también es instrumental, la combinación es fatal.

De lo contrario, diría que es un error bastante crítico. Es difícil adivinar cuán extendido podría ser, solo tengo que probar la fluctuación de fase 4.6.1 x86. El x64 y el jitter 3.5 x86 generan un código muy diferente y evitan este error. La solución temporal para continuar es eliminar sbyte como el tipo base enum y dejar que sea el valor predeterminado, int , para que no sea necesaria la extensión de signo.

Puede presentar el error en connect.microsoft.com, el enlace a este Q + A debería ser suficiente para decirles todo lo que necesitan saber. Avísame si no quieres tomarte el tiempo y yo me encargaré.

Hans Passant
fuente
33
Datos buenos y sólidos con la causa exacta de un problema tan extraño, siempre es un placer leer, +1.
Lasse V. Karlsen
11
Publique un enlace al artículo de connect.microsoft.com para que podamos votarlo.
Hans Passant
Supongo que usar en bytelugar de sbytedebería estar bien también y podría ser preferible si el código real se usa con decir un ORM donde no desea que sus banderas en la base de datos ocupen espacio adicional.
Voo
66
Publicaría el error en dotnet / coreclr en lugar de conectarse, llegarás directamente a los desarrolladores de JIT.
Lucas Trzesniewski el
8
Soy un desarrollador del equipo JIT de Microsoft. He reproducido el error y he abierto un problema internamente (el envío x86 JIT aún no está abierto en github). En cuanto al momento en que esto se solucionaría, anticipo que tendremos esta solución incluida en la próxima versión principal de las herramientas. Si este error tiene un impacto comercial y necesita una solución antes, presente el problema de conexión (connect.microsoft.com) para que podamos ver el impacto y las alternativas que tenemos para obtener una solución más rápida.
Russell C. Hadley
8

Consideremos la declaración de OP:

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

Dado que el error solo ocurre cuando BUGes negativo (de -128 a -1) y el estado es una enumeración de bytes firmados , comencé a suponer que había un problema de transmisión en alguna parte.

Si ejecuta esto:

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

saldrá:

255

-1

INSECTO

255

Por una razón que ignoro (a partir de ahora) s[0, 0] se convierte en un byte antes de la evaluación y es por eso que afirma que a == s[0,0]es falso.

Thomas Ayoub
fuente