.NET JIT潜在错误?


404

当在Visual Studio中运行发行版并在Visual Studio之外运行发行版时,以下代码给出不同的输出。我使用的是Visual Studio 2008,目标是.NET 3.5。我也尝试过.NET 3.5 SP1。

在Visual Studio外部运行时,应该启动JIT。或者(a)我缺少的C#发生了一些微妙的变化,或者(b)JIT实际上是错误的。我怀疑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();
        }
    }
}

8
是的-那怎么做:在.Net JIT等必不可少的东西中发现严重的错误-恭喜!
Andras Zoltan 2010年

73
这似乎在我于12月9日在x86上构建4.0框架时得到了再现。我将其传递给抖动团队。谢谢!
埃里克·利珀特

28
这是实际上值得一枚金牌的极少数问题之一。
Mehrdad Afshari 2010年

28
我们大家都对此问题感兴趣,这一事实表明,我们并不希望 .NET JIT中的错误,也不会是Microsoft的出色表现。
伊恩·林罗斯

2
我们都在等待微软的急切答复...
Talha 2012年

Answers:


211

这是一个JIT优化程序错误。它正在展开内部循环,但未正确更新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抖动中仍然存在。这么长时间后,他们似乎不太可能在旧版本中解决此问题。


3
+1,绝对是一个错误-我可能已经确定了错误的条件(虽然不是说nobugz是因为我而找到它的!),但这(以及您的尼克,所以也为您+1)表明JIT是罪魁祸首。有趣的是,在将IntVec声明为类时,优化将被删除或有所不同。即使在循环之前先将struct字段显式初始化为0,也会看到相同的行为。讨厌!
安德拉斯·佐尔坦

3
@Hans Passant您使用了什么工具来输出汇编代码?

3
@Joan-Just Visual Studio,从调试器的“反汇编”窗口复制/粘贴,并手动添加注释。
汉斯·帕桑

82

我相信这是一个真正的JIT编译错误。我会向Microsoft报告,看看他们怎么说。有趣的是,我发现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     

看来优化对我不利...


23

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

  • 调试版本
    • 使用调试器和不使用调试器均可正确输出
  • 切换到发布版本
    • 再次,两次都校正输出
  • 创建了一个新的x86配置(我正在运行X64 Windows 2008,并且正在使用“任何CPU”)
  • 调试版本
    • 获得正确的输出F5和CTRL + F5
  • 发布版本
    • 附带调试器的正确输出
    • 没有调试器- 得到了错误的输出

因此,是x86 JIT错误地生成了代码。已经删除了有关循环等的重新排序的原始文本。此处的其他一些答案已确认JIT在x86上错误地展开了循环。

要解决此问题,您可以将IntVec的声明更改为一个类,并且可以使用所有样式。

认为这需要继续进行MS Connect。

-1对微软!


1
有趣的主意,但是可以肯定的是,这不是“优化”,而是编译器中的一个非常主要的错误?现在会被发现吗?
David M 2010年

我同意你的看法。像这样对循环进行重新排序可能会导致无法解决的问题。其实,这似乎更不可能,因为for循环永远不能达到2
安德拉斯·佐尔坦

2
看起来像是这些令人讨厌的
Heisenbugs

如果OP(或使用他的应用程序的任何人)使用32位x86机器,则任何CPU将无法工作。问题是启用优化的x86 JIT生成错误代码。
尼克·格雷拉
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.