Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is the JIT generating the wrong code

Tags:

c#

jit

I have been looking in to you some code wasn't working. Everything looks fine except for the following line.

Transport = Transport?? MockITransportUtil.GetMock(true);

Before that line is executed Transport is null. I see the GetMock executed and that it returns a non null object. After that line Transport is still null;

I looked at the IL that was generated an it looks fine to me.

 IL_0002:  ldarg.0
  IL_0003:  ldfld      class [Moq]Moq.Mock`1<class [CommLibNet]CommLibNET.ITransport> Curex.Services.Common.UnitTests.Messaging.TestIGuaranteedSubscriptionBase::Transport
  IL_0008:  dup
  IL_0009:  brtrue.s   IL_0012
  IL_000b:  pop
  IL_000c:  ldc.i4.1
  IL_000d:  call       class [Moq]Moq.Mock`1<class [CommLibNet]CommLibNET.ITransport> Curex.Services.Common.UnitTests.Mocking.MockITransportUtil::GetMock(bool)
  IL_0012:  stfld      class [Moq]Moq.Mock`1<class [CommLibNet]CommLibNET.ITransport> Curex.Services.Common.UnitTests.Messaging.TestIGuaranteedSubscriptionBase::Transport

We see the function get called and stfld should take the return value and set the field.

So I then looked at the assembly I see the call get made but it looks like the return in RAX gets blown away by the next call and is lost.

            Transport = Transport?? MockITransportUtil.GetMock(true);
000007FE9236F776  mov         rax,qword ptr [rbp+0B0h]  
000007FE9236F77D  mov         rax,qword ptr [rax+20h]  
000007FE9236F781  mov         qword ptr [rbp+20h],rax  
000007FE9236F785  mov         rcx,qword ptr [rbp+20h]  
000007FE9236F789  mov         rax,qword ptr [rbp+0B0h]  
000007FE9236F790  mov         qword ptr [rbp+28h],rax  
000007FE9236F794  test        rcx,rcx  
000007FE9236F797  jne         000007FE9236F7AC  
000007FE9236F799  mov         cl,1  
000007FE9236F79B  call        000007FE92290608  

            //var x = ReferenceEquals(null, Transport) ? MockITransportUtil.GetMock(true) : Transport;
            ListerFactory = ListerFactory ?? MockIListenerUtil.GetMockSetupWithAction((a) => invokingAction = a);
000007FE9236F7A0  mov         qword ptr [rbp+30h],rax  
000007FE9236F7A4  mov         rax,qword ptr [rbp+30h]  
000007FE9236F7A8  mov         qword ptr [rbp+20h],rax  
000007FE9236F7AC  mov         rcx,qword ptr [rbp+28h]  

if I use an if statement or a ?: operator everyting works fine.

Visual Studio 2013

EDIT

I have create a psudo minimal reproduction.

class simple
{
    public A MyA = null;
    public B MyB = null;

    public void SetUp()
    {
        MyA = MyA ?? new A();
        MyB = new B();// Put breakpoint here
    }
}

If you set a breakpoint on the indicated line and look at the value of MyA in the debugger it will still be null(only if building in x64). if you execute the next line it will set the value. I have not been able to reproduce the assessment not happening at all. Its very clear in the disassembly the execution for the next line has begun before the assignment takes place.

Edit 2

Here is a link to the ms connect site

like image 559
rerun Avatar asked Mar 21 '14 18:03

rerun


People also ask

Does JIT compile to machine code?

The JIT compiler reads the bytecodes in many sections (or in full, rarely) and compiles them dynamically into machine code so the program can run faster.

How does a JIT work?

The JIT compiler is enabled by default, and is activated when a Java method is called. The JIT compiler compiles the bytecodes of that method into native machine code, compiling it "just in time" to run. When a method has been compiled, the JVM calls the compiled code of that method directly instead of interpreting it.

How does JIT compiler affect CIL code?

The JIT compiler converts the Microsoft Intermediate Language(MSIL) or Common Intermediate Language(CIL) into the machine code. This is done before the MSIL or CIL can be executed.

Is JIT a compiler or interpreter?

A Just-In-Time (JIT) compiler is a feature of the run-time interpreter, that instead of interpreting bytecode every time a method is invoked, will compile the bytecode into the machine code instructions of the running machine, and then invoke this object code instead.


2 Answers

    MyB = new B();// Put breakpoint here

The problem is the breakpoint, not the code generation. The x64 jitter flubs this, it generates inaccurate debugging info. It emits line number info for the statement incorrectly, using a code address that's still part of the previous statement.

You can tell from the disassembly you posted, the code at addresses F7A0 through F7A8 are still part of the ?? statement. The branch to F7AC is the real one, that's where the next statement starts. So it should have said that F7AC was the start of the next statement, not F7A0.

The consequences of this bug is that the debugger may never stop at the breakpoint. You can see this for yourself by altering your repro code and write public A MyA = new A(); And that if it does stop then the assignment isn't executed yet. So you still see the variable having the previous value, null in your case. A single step resolves it, albeit that it depends on what the next statement looks like.

Rest assured that this only goes wrong when you debug, the program still operates correctly. Just keep this quirk in mind, afaik it only goes wrong for the ?? operator. You can tell it doesn't get used much :) Albeit that most programmers only ever debug the 32-bit version of their program, the default project settings heavily encourage it.

The problem is being addressed as we speak, don't expect your Connect report to have an affect, Microsoft is well aware of this bug. The jitter team at Microsoft has rewritten the x64 jitter completely, it is currently in CTP2. I'd estimate another year or so before it is released.

like image 92
Hans Passant Avatar answered Oct 20 '22 03:10

Hans Passant


I got an update from MS that this was indeed a real problem and has been fixed in the upcoming release of the x64 jiter.

like image 32
rerun Avatar answered Oct 20 '22 02:10

rerun