关于c#:. NET JIT潜在的错误?

.NET JIT potential error?

以下代码在Visual Studio内部运行版本,在Visual Studio外部运行版本时提供不同的输出。我使用的是Visual Studio 2008,目标是.NET 3.5。我还尝试了.NET 3.5 SP1。

在Visual Studio外部运行时,JIT应该启动。要么(a)我找不到与c有关的微妙之处,要么(b)JIT实际上出错了。我怀疑JIT会出问题,但我正在用尽其他可能性…

在Visual Studio中运行时的输出:

1
2
3
4
    0 0,
    0 1,
    1 0,
    1 1,

在Visual Studio外部运行版本时输出:

1
2
3
4
    0 2,
    0 2,
    1 2,
    1 2,

原因是什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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();
        }
    }
}


这是一个JIT优化器错误。它正在展开内部循环,但未正确更新ovec.y值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
      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时,bug就消失了,这是太多的调用无法展开。

解决方法之一是:

1
2
3
4
5
  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抖动中被修复。但仍然存在于2.0.50727抖动中。他们似乎不太可能在这么长时间后在旧版本中修复这个问题。


我相信这是一个真正的JIT编译错误。我会向微软报告,看看他们怎么说。有趣的是,我发现X64 JIT没有同样的问题。

这是我对x86 JIT的理解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 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

这看起来优化对我不好…


我把你的代码复制到一个新的控制台应用程序中。

  • 调试版本
    • 使用调试器和不使用调试器更正输出
  • 切换到发布版本
    • 再次纠正两次输出
  • 创建了一个新的x86配置(我正在运行x64 Windows 2008,并且正在使用'any cpu')
  • 调试版本
    • 正确输出了f5和ctrl+f5
  • 释放构建
    • 使用附加的调试程序更正输出
    • 没有调试程序-得到了不正确的输出

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

要解决这个问题,您可以将intvec的声明更改为类,它可以在所有风格中工作。

认为这需要在MS Connect上进行….

-1到Microsoft!