2010-01-13 42 views
381

以下代码在Visual Studio中运行发行版时提供了不同的输出,并在Visual Studio外运行发行版。我正在使用Visual Studio 2008并以.NET 3.5为目标。我也尝试过.NET 3.5 SP1。 (a)我错过了C#发生的某些细节,或者(b)JIT实际上是错误的。我怀疑的是,JIT可以去错了,但我跑出来其他possiblities的....NET JIT潜在错误?

输出在Visual Studio中运行时:

0 0, 
    0 1, 
    1 0, 
    1 1, 

输出Visual Studio之外运行时释放:

0 2, 
    0 2, 
    1 2, 
    1 2, 

是什么原因?

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 

namespace Test 
{ 
    struct IntVec 
    { 
     public int x; 
     public int y; 
    } 

    interface IDoSomething 
    { 
     void Do(IntVec o); 
    } 

    class DoSomething : IDoSomething 
    { 
     public void Do(IntVec o) 
     { 
      Console.WriteLine(o.x.ToString() + " " + o.y.ToString()+","); 
     } 
    } 

    class Program 
    { 
     static void Test(IDoSomething oDoesSomething) 
     { 
      IntVec oVec = new IntVec(); 
      for (oVec.x = 0; oVec.x < 2; oVec.x++) 
      { 
       for (oVec.y = 0; oVec.y < 2; oVec.y++) 
       { 
        oDoesSomething.Do(oVec); 
       } 
      } 
     } 

     static void Main(string[] args) 
     { 
      Test(new DoSomething()); 
      Console.ReadLine(); 
     } 
    } 
} 
+14

+1 - 这是一个多么小的问题:) –

+8

是的 - 那怎么样:找到一个像.Net JIT一样重要的严重错误 - 恭喜! –

+73

这似乎是在我12月9日在x86上构建的4.0框架中重新生成。我会把它传递给抖动团队。谢谢! –

回答

202

这是一个JIT优化器的bug。这是展开内部循环,但不能正常更新oVec.y值:

 for (oVec.x = 0; oVec.x < 2; oVec.x++) { 
0000000a xor   esi,esi       ; oVec.x = 0 
     for (oVec.y = 0; oVec.y < 2; oVec.y++) { 
0000000c mov   edi,2       ; oVec.y = 2, WRONG! 
      oDoesSomething.Do(oVec); 
00000011 push  edi 
00000012 push  esi 
00000013 mov   ecx,ebx 
00000015 call  dword ptr ds:[00170210h]  ; first unrolled call 
0000001b push  edi        ; WRONG! does not increment oVec.y 
0000001c push  esi 
0000001d mov   ecx,ebx 
0000001f call  dword ptr ds:[00170210h]  ; second unrolled call 
     for (oVec.x = 0; oVec.x < 2; oVec.x++) { 
00000025 inc   esi 
00000026 cmp   esi,2 
00000029 jl   0000000C 

的错误,当你让oVec.y增量4消失,这是太多调用解开。

一个解决办法是这样的:

for (int x = 0; x < 2; x++) { 
    for (int y = 0; y < 2; y++) { 
     oDoesSomething.Do(new IntVec(x, y)); 
    } 
    } 

更新:2012年8月重新检查,这个错误是固定在版本4.0.30319抖动。但是在v2.0.50727抖动中仍然存在。看起来他们不太可能在这么久之后在旧版本中修复这个问题。

+11

+1这绝对是一个错误。我发布了或多或少相同的答案作为我的答案的编辑,当它来到时。似乎回答这里的问题通常是一场比赛... –

+3

+1,肯定是一个错误 - 我可能已经确定了错误的条件(不是说nobugz因为我而发现它)!但是这个(和你的,尼克斯,所以你也+1)表明JIT是罪魁祸首。有趣的是,当IntVec被声明为一个类时,优化被删除或者不同。即使您在循环之前先显式初始化struct字段为0,也会看到相同的行为。 讨厌! –

+3

@Hans Passant你用什么工具输出汇编代码? – 2013-05-23 14:51:09

22

我将您的代码复制到新的控制台应用程序中。

  • 调试版本
    • 与调试器和没有调试正确的输出
  • 切换到发布版本
    • 再次,正确的输出两次
  • 创建一个新的x86配置(我在奔跑摹X64 Windows 2008和使用 '任何CPU')
  • 调试版本
    • 得到了正确的输出都F5和Ctrl + F5
  • 发行版
    • 与调试器附加正确的输出
    • 没有调试器 - 得到了错误的输出

所以这是x86 JIT错误地生成代码。删除了我有关重新排序循环等的原始文本。这里的其他一些答案已经证实JIT在x86上正确地展开循环。

要解决这个问题,您可以将IntVec的声明更改为一个类,并且它适用于所有类型。

认为这需要继续MS Connect ....

-1 to Microsoft!

+1

有趣的想法,但肯定这不是“优化”,但如果是这样的话,这是编译器中的一个非常重要的bug?现在会发现不是吗? –

+0

我同意你的意见。重新排序这样的循环可能会导致无法解决的问题。实际上这看起来更不可能,因为for循环无法达到2. –

+2

看起来像这些讨厌的Heisenbugs之一:P – arul

79

我相信这是一个真正的JIT编译错误。我会把它报告给微软,看看他们说了些什么。有趣的是,我发现x64 JIT没有相同的问题。

这是我读的x86 JIT。

// save context 
00000000 push  ebp 
00000001 mov   ebp,esp 
00000003 push  edi 
00000004 push  esi 
00000005 push  ebx 

// put oDoesSomething pointer in ebx 
00000006 mov   ebx,ecx 

// zero out edi, this will store oVec.y 
00000008 xor   edi,edi 

// zero out esi, this will store oVec.x 
0000000a xor   esi,esi 

// NOTE: the inner loop is unrolled here. 
// set oVec.y to 2 
0000000c mov   edi,2 

// call oDoesSomething.Do(oVec) -- y is always 2!?! 
00000011 push  edi 
00000012 push  esi 
00000013 mov   ecx,ebx 
00000015 call  dword ptr ds:[002F0010h] 

// call oDoesSomething.Do(oVec) -- y is always 2?!?! 
0000001b push  edi 
0000001c push  esi 
0000001d mov   ecx,ebx 
0000001f call  dword ptr ds:[002F0010h] 

// increment oVec.x 
00000025 inc   esi 

// loop back to 0000000C if oVec.x < 2 
00000026 cmp   esi,2 
00000029 jl   0000000C 

// restore context and return 
0000002b pop   ebx 
0000002c pop   esi 
0000002d pop   edi 
0000002e pop   ebp 
0000002f ret  

这看起来像坏了我的优化...