Skip to main content

.NET JIT potential error?


The following code gives different output when running the release inside Visual Studio, and running the release outside Visual Studio. I'm using Visual Studio 2008 and targeting .NET 3.5. I've also tried .NET 3.5 SP1.



When running outside Visual Studio, the JIT should kick in. Either (a) there's something subtle going on with C# that I'm missing or (b) the JIT is actually in error. I'm doubtful that the JIT can go wrong, but I'm running out of other possiblities...



Output when running inside Visual Studio:




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



Output when running release outside of Visual Studio:




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



What is the reason?




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


Source: Tips4allCCNA FINAL EXAM

Comments

  1. It is a JIT optimizer bug. It is unrolling the inner loop but not updating the oVec.y value properly:

    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


    The bug disappears when you let oVec.y increment to 4, that's too many calls to unroll.

    One workaround is this:

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

    ReplyDelete
  2. I believe this is in a genuine JIT compilation bug. I would report it to Microsoft and see what they say. Interestingly, I found that the x64 JIT does not have the same problem.

    Here is my reading of the 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 y
    00000008 xor edi,edi

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

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

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

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

    // increment x
    00000025 inc esi

    // loop back to 0000000C if 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


    This looks like an optimization gone bad to me...

    ReplyDelete
  3. I copied your code into a new Console App.


    Debug Build

    Correct output with both debugger and no debugger

    Switched to Release Build

    Again, correct output both times

    Created a new x86 configuration (I'm on running X64 Windows 2008 and was using 'Any CPU')
    Debug Build

    Got the correct output both F5 and CTRL+F5

    Release Build

    Correct output with Debugger attached
    No debugger - Got the incorrect output



    So it is the x86 JIT incorrectly generating the code. Have deleted my original text about reordering of loops etc. A few other answers on here have confirmed that the JIT is unwinding the loop incorrectly when on x86.

    To fix the problem you can change the declaration of IntVec to a class and it works in all flavours.

    Think this needs to go on MS Connect....

    -1 to Microsoft!

    ReplyDelete
  4. The reason why this bug doesn't reproduce when you run the program with an attached debugger, is because by default VS will force the JIT not to use optimizations when compiling the CIL (since that would make the task of actually debugging the code to be much harder .. and actually impossible at times).

    ReplyDelete

Post a Comment

Popular posts from this blog

[韓日関係] 首相含む大幅な内閣改造の可能性…早ければ来月10日ごろ=韓国

div not scrolling properly with slimScroll plugin

I am using the slimScroll plugin for jQuery by Piotr Rochala Which is a great plugin for nice scrollbars on most browsers but I am stuck because I am using it for a chat box and whenever the user appends new text to the boxit does scroll using the .scrollTop() method however the plugin's scrollbar doesnt scroll with it and when the user wants to look though the chat history it will start scrolling from near the top. I have made a quick demo of my situation http://jsfiddle.net/DY9CT/2/ Does anyone know how to solve this problem?

Why does this javascript based printing cause Safari to refresh the page?

The page I am working on has a javascript function executed to print parts of the page. For some reason, printing in Safari, causes the window to somehow update. I say somehow, because it does not really refresh as in reload the page, but rather it starts the "rendering" of the page from start, i.e. scroll to top, flash animations start from 0, and so forth. The effect is reproduced by this fiddle: http://jsfiddle.net/fYmnB/ Clicking the print button and finishing or cancelling a print in Safari causes the screen to "go white" for a sec, which in my real website manifests itself as something "like" a reload. While running print button with, let's say, Firefox, just opens and closes the print dialogue without affecting the fiddle page in any way. Is there something with my way of calling the browsers print method that causes this, or how can it be explained - and preferably, avoided? P.S.: On my real site the same occurs with Chrome. In the ex