多次元配列の列挙値がそれ自体と等しくないのはなぜですか? 質問する

多次元配列の列挙値がそれ自体と等しくないのはなぜですか? 質問する

考慮する:

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

これはどのように説明できますか? これは、x86 JIT で実行しているときに Visual Studio 2015 のデバッグ ビルドで発生します。リリース ビルドまたは x64 JIT で実行している場合は、予想どおり True が出力されます。

コマンドラインから再現するには:

csc Test.cs /platform:x86 /debug

/debug:pdbonlyそして再現/debug:portable/debug:fullします。)

ベストアンサー1

.NET 4 x86 ジッターでコード生成のバグが見つかりました。これは非常に珍しいバグで、コードが最適化されていない場合にのみ失敗します。マシン コードは次のようになります。

        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  

最適化されていないコードではよくあることですが、多くの一時変数とコードの重複を伴​​う、時間のかかる処理です。013F04B8 の命令は注目に値します。ここで、sbyte から 32 ビット整数への必要な変換が行われます。配列取得ヘルパー関数は、State.BUG に等しい 0x0000000FF を返しましたが、値を比較する前に、これを -1 (0xFFFFFFFF) に変換する必要があります。MOVSX 命令は、Sign eXtension 命令です。

013F04CCでも同じことが起こりますが、今回はいいえMOVSX 命令を使用して同じ変換を行います。ここでチップが失敗するのですが、CMP 命令は 0xFFFFFFFF と 0x000000FF を比較しますが、これは誤りです。つまり、これは省略エラーであり、コード ジェネレーターは、同じ sbyte から int への変換を実行するために MOVSX を再度発行できませんでした。

このバグの特に珍しい点は、オプティマイザーを有効にすると正しく動作し、どちらの場合でも MOVSX を使用するように認識されることです。

このバグが長い間検出されなかった理由は、列挙型の基本型として sbyte が使用されていたためと考えられます。これは非常にまれなことです。多次元配列を使用することも有益ですが、その組み合わせは致命的です。

そうでなければ、かなり重大なバグだと思います。どの程度の規模になるかは推測しにくいですが、テストできるのは 4.6.1 x86 ジッターだけです。x64 と 3.5 x86 ジッターは、まったく異なるコードを生成し、このバグを回避します。一時的な回避策としては、列挙型の基本型として sbyte を削除し、それをデフォルトにすることです。整数したがって、符号拡張は必要ありません。

connect.microsoft.com でバグを報告できます。この Q+A にリンクするだけで、必要な情報をすべて提供できます。時間をかけたくない場合はお知らせください。私が対応します。

おすすめ記事