CSharp - net Jit可能出现错误?

  显示原文与译文双语对照的内容

以下代码在 Visual Studio 内部运行发行版时给出不同的输出,并在 Visual Studio 之外运行 release 。 我正在使用 Visual Studio 2008并定位. NET 3.5. 我还尝试了. NET 3.5 SP1.

在 Visual Studio 外部运行时,JIT应该启动。 要么( 一个) C# 会有一些微妙的,我失踪或( b ) jit实际上是错误的。 我怀疑JIT是否会出错,但我没有其他 possiblities 。。

在 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();
 }
 }
}

时间:

它是一个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 

Bug 消失让 oVec.y 增量 4时,太多的要求展开。

解决方法之一是:


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

更新:re-checked在 2012年08月 中,这个 Bug 被固定在 4.0.303 19抖动的版本中。 但仍存在于 v2.0.507 27抖动中。 他们似乎不可能在这么长的时间内修复旧版本。

我相信这是一个真正的JIT编译 Bug 。 我会报告给微软,看看他们说什么。 有趣的是,我发现 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 

这看起来对我来说是一个优化。

...