Por que o valor de enumeração de uma matriz multidimensional não é igual a si mesmo?

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

Como isso pode ser explicado? Ocorre em compilações de depuração no Visual Studio 2015 ao executar no x86 JIT. Uma versão compilada ou em execução no JIT x64 imprime True conforme o esperado.

Para reproduzir a partir da linha de comando:

csc Test.cs /platform:x86 /debug

( /debug:pdbonly, /debug:portablee /debug:fulltambém reproduzir.)

shingo
fonte
2
ideone.com/li3EzY é verdade. adicione mais informações sobre a versão .net, IDE, compilador
Back
1
O mesmo aqui. Mas depois de mexer nas configurações do projeto, descobri que desmarcar a caixa de seleção "Prefer 32 bits" na guia "Build" faz com que funcione conforme o esperado - retornando verdadeiro. Portanto, parece-me um problema WoW64.
precisa saber é o seguinte
2
Parece que você apontou um bug na estrutura.
Fabien PERRONNET
1
Curiosamente, a execução do código rompido ildasme, em seguida, ilasm"correções" que ...
Jon Skeet
2
A /debug=IMPLbandeira a deixa quebrada; /debug=OPT"corrige" isso.
precisa

Respostas:

163

Você encontrou um bug de geração de código no jitter do .NET 4 x86. É muito incomum, só falha quando o código não é otimizado. O código da máquina fica assim:

        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  

Um caso difícil com muitos temporários e duplicação de código, isso é normal para código não otimizado. A instrução em 013F04B8 é notável; é aí que ocorre a conversão necessária de sbyte para um número inteiro de 32 bits. A função auxiliar do getter da matriz retornou 0x0000000FF, igual a State.BUG, e precisa ser convertida em -1 (0xFFFFFFFF) antes que o valor possa ser comparado. A instrução MOVSX é uma instrução Sign eXtension.

A mesma coisa acontece novamente em 013F04CC, mas desta vez não instruções MOVSX para fazer a mesma conversão. É aí que os chips caem, a instrução CMP compara 0xFFFFFFFF com 0x000000FF e isso é falso. Portanto, este é um erro de omissão, o gerador de código falhou ao emitir o MOVSX novamente para executar a mesma conversão de sbyte em int.

O que é particularmente incomum nesse bug é que ele funciona corretamente quando você ativa o otimizador, que agora sabe usar o MOVSX nos dois casos.

O motivo provável pelo qual esse bug ficou sem ser detectado por tanto tempo é o uso de sbyte como o tipo base do enum. Muito raro de se fazer. O uso de uma matriz multidimensional também é fundamental, a combinação é fatal.

Caso contrário, um bug bastante crítico, eu diria. É difícil adivinhar quão difundida pode ser, só tenho o jitter 4.6.1 x86 para testar. O jitter x64 e o jitter 3,5 x86 geram códigos muito diferentes e evitam esse bug. A solução temporária para continuar é remover sbyte como o tipo base de enum e deixá-lo como padrão, int , para que nenhuma extensão de sinal seja necessária.

Você pode registrar o bug em connect.microsoft.com, vincular a este Q + A deve ser suficiente para dizer a eles tudo o que eles precisam saber. Deixe-me saber se você não quiser reservar um tempo e eu cuido disso.

Hans Passant
fonte
33
Dados bons e sólidos com a causa exata de um problema tão estranho, sempre um prazer de ler, +1.
Lasse V. Karlsen
11
Envie um link para o artigo connect.microsoft.com para que possamos votar nele.
Hans Passant
Eu suponho que o uso em bytevez de sbytedeve ser bom também e pode ser preferível se o código real for usado com um ORM, onde você não deseja que seus sinalizadores no banco de dados ocupem espaço extra.
Voo
6
Eu postaria o bug no dotnet / coreclr ao invés de conectar, você vai direto para os desenvolvedores do JIT.
Lucas Trzesniewski
8
Sou desenvolvedor da equipe JIT da Microsoft. Reproduzi o bug e abri um problema para ele internamente (o envio do x86 JIT ainda não está disponível no github). Em termos de tempo de quando isso seria corrigido, prevejo que teremos essa correção incluída no próximo grande lançamento das ferramentas. Se esse bug estiver causando impacto nos negócios e você precisar de uma correção mais cedo, registre o problema connect (connect.microsoft.com) para que possamos analisar o impacto e quais alternativas temos para obter uma correção mais rapidamente.
Russell C. Hadley
8

Vamos considerar a declaração do OP:

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

Como o bug ocorre apenas quando BUGé negativo (de -128 a -1) e State é um enum de byte assinado , comecei a supor que havia um problema de conversão em algum lugar.

Se você executar isso:

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

ele produzirá:

255

-1

ERRO

255

Por um motivo que eu ignoro (a partir de agora) s[0, 0] é convertido em um byte antes da avaliação e é por isso que afirma a == s[0,0]ser falso.

Thomas Ayoub
fonte