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

    private void Job()
        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  

Leave a Reply