In the previous post, I asked why the following code behaves differently when compilation is made in Release and Debug mode.

class Program
{
    static void Main(string[] args)
    {
        var w = new Worker();
        while (!w.IsDone);
        Console.WriteLine("The work is done.");
    }
}

class Worker
{
    public bool IsDone;

    public Worker()
    {
        var thread = new Thread(Job);
        thread.Start();
    }

    private void Job()
    {
        Thread.Sleep(3000);
        IsDone = true;
    }
}

Let’s check the assembly code generated by the JIT in these two situations.

The following assembly code is what we get when compiling in Debug mode.

{
00007FF9AE2E14A2  push        rsi  
00007FF9AE2E14A3  sub         rsp,40h  
00007FF9AE2E14A7  mov         rbp,rsp  
00007FF9AE2E14AA  mov         rsi,rcx  
00007FF9AE2E14AD  lea         rdi,[rbp+20h]  
00007FF9AE2E14B1  mov         ecx,8  
00007FF9AE2E14B6  xor         eax,eax  
00007FF9AE2E14B8  rep stos    dword ptr [rdi]  
00007FF9AE2E14BA  mov         rcx,rsi  
00007FF9AE2E14BD  mov         qword ptr [rbp+60h],rcx  
00007FF9AE2E14C1  cmp         dword ptr [7FF9AE1C49D8h],0  
00007FF9AE2E14C8  je          00007FF9AE2E14CF  
00007FF9AE2E14CA  call        00007FFA0DF1FDA0  
00007FF9AE2E14CF  nop  
        var w = new Worker();
00007FF9AE2E14D0  mov         rcx,7FF9AE1C55F8h  
00007FF9AE2E14DA  call        00007FFA0DDB00F0  
00007FF9AE2E14DF  mov         qword ptr [rbp+20h],rax  
00007FF9AE2E14E3  mov         rcx,qword ptr [rbp+20h]  
00007FF9AE2E14E7  call        00007FF9AE2E10B0  
00007FF9AE2E14EC  mov         rcx,qword ptr [rbp+20h]  
00007FF9AE2E14F0  mov         qword ptr [rbp+30h],rcx  
00007FF9AE2E14F4  nop  
00007FF9AE2E14F5  jmp         00007FF9AE2E14F8  
        while (!w.IsDone);
00007FF9AE2E14F7  nop  
00007FF9AE2E14F8  mov         rcx,qword ptr [rbp+30h]  
00007FF9AE2E14FC  movzx       ecx,byte ptr [rcx+8]  
00007FF9AE2E1500  test        ecx,ecx  
00007FF9AE2E1502  sete        cl  
00007FF9AE2E1505  movzx       ecx,cl  
00007FF9AE2E1508  mov         dword ptr [rbp+2Ch],ecx  
00007FF9AE2E150B  cmp         dword ptr [rbp+2Ch],0  
00007FF9AE2E150F  jne         00007FF9AE2E14F7  
        Console.WriteLine("The work is done.");
00007FF9AE2E1511  mov         rcx,236EBF53068h  
00007FF9AE2E151B  mov         rcx,qword ptr [rcx]  
00007FF9AE2E151E  call        00007FF9AE2E1368  
00007FF9AE2E1523  nop  
    }
00007FF9AE2E1524  nop  
00007FF9AE2E1525  lea         rsp,[rbp+40h]  
00007FF9AE2E1529  pop         rsi  
00007FF9AE2E152A  pop         rdi  
00007FF9AE2E152B  pop         rbp  
00007FF9AE2E152C  ret  

And the following assembly code is what we get when compiling in Release mode.

        var w = new Worker();
00007FF9AE2E14A2  sub         esp,20h  
00007FF9AE2E14A5  mov         rcx,7FF9AE1C55C8h  
00007FF9AE2E14AF  call        00007FFA0DDB00F0  
00007FF9AE2E14B4  mov         rsi,rax  
00007FF9AE2E14B7  mov         rcx,rsi  
00007FF9AE2E14BA  call        00007FF9AE2E10B0  
00007FF9AE2E14BF  movzx       ecx,byte ptr [rsi+8]  
        while (!w.IsDone);
00007FF9AE2E14C3  test        ecx,ecx  
00007FF9AE2E14C5  je          00007FF9AE2E14C3  
        Console.WriteLine("The work is done.");
00007FF9AE2E14C7  mov         rcx,16EC2D73068h  
00007FF9AE2E14D1  mov         rcx,qword ptr [rcx]  
00007FF9AE2E14D4  call        00007FF9AE2E1368  
00007FF9AE2E14D9  nop  
00007FF9AE2E14DA  add         rsp,20h  
00007FF9AE2E14DE  pop         rsi  
00007FF9AE2E14DF  ret  

As you can see, the JIT does not update the register. That is is perfect for single-threaded applications. But, it does not work with multi-threading.

The solution is to mark the variable as volatile, forcing the runtime to get the updated value all the time.

        var w = new Worker();
00007FF9AE2D14A2  sub         esp,20h  
00007FF9AE2D14A5  mov         rcx,7FF9AE1B55C8h  
00007FF9AE2D14AF  call        00007FFA0DDB00F0  
00007FF9AE2D14B4  mov         rsi,rax  
00007FF9AE2D14B7  mov         rcx,rsi  
00007FF9AE2D14BA  call        00007FF9AE2D10B0  
        while (!w.IsDone);
00007FF9AE2D14BF  cmp         byte ptr [rsi+8],0  
00007FF9AE2D14C3  je          00007FF9AE2D14BF  
        Console.WriteLine("The work is done.");
00007FF9AE2D14C5  mov         rcx,2772C0D3068h  
00007FF9AE2D14CF  mov         rcx,qword ptr [rcx]  
00007FF9AE2D14D2  call        00007FF9AE2D1368  
00007FF9AE2D14D7  nop  
00007FF9AE2D14D8  add         rsp,20h  
00007FF9AE2D14DC  pop         rsi  
00007FF9AE2D14DD  ret  

More posts in ".NET Quiz" series:

Leave a Reply

Your email address will not be published. Required fields are marked *