Why would the .NET JIT compiler decide to not inline or optimize away calls to empty static methods that have no side effects?

StackOverflow https://stackoverflow.com/questions/22892766

  •  28-06-2023
  •  | 
  •  

Question

I think I'm observing the .NET JIT compiler not inlining or optimizing away calls to empty static methods that have no side effects, which is a bit surprising given some bespoken online resources.

My environment is Visual Studio 2013 on x64, Windows 8.1, .NET Framework 4.5.

Given this simple test program (https://ideone.com/2BRCpC)

class Program
{
    static void EmptyBody()
    {
    }

    static void Main()
    {
        EmptyBody();
    }
}

A release build with optimizations of the above program produces the following MSIL for Main and EmptyBody:

.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       6 (0x6)
  .maxstack  8
  IL_0000:  call       void Program::EmptyBody()
  IL_0005:  ret
} // end of method Program::Main

.method private hidebysig static void  EmptyBody() cil managed
{
  // Code size       1 (0x1)
  .maxstack  8
  IL_0000:  ret
} // end of method Program::EmptyBody

It's not surprising that the MSIL contains a call from Main to EmptyBody, since the C# compiler isn't expected to inline or optimize away calls like that. However, I thought that the JIT compiler would then inline or optimize away that call. But that doesn't seem to happen.

If I run the above program and break into the debugger in Main, the generated assembly is this:

00572621  mov         ebp,esp  
00572623  cmp         dword ptr ds:[4320B84h],0  
0057262A  je          00572631  
0057262C  call        73E6AF20  
00572631  call        dword ptr ds:[4321578h]  

The instruction pointer is immediately set to the last line at 00572631, which is the call to EmptyBody. Stepping into EmptyBody, the generated assembly is found to be

00BD2651  mov         ebp,esp  
00BD2653  cmp         dword ptr ds:[4B00B84h],0  
00BD265A  je          00BD2661  
00BD265C  call        73E6AF20  
00BD2661  nop  
00BD2662  pop         ebp  
00BD2663  ret

The instruction pointer is immediately set to the nop line at 00BD2661, which doesn't do anything, and I cannot guess why it's generated in the first place.

Given that the two assembly snippets above share the same 4-instruction header, I assume that's just the regular method entry boiler plate where the stack and such is set up. I'm keen to learn to know what these recurring instructions would do, though:

00BD2653  cmp         dword ptr ds:[4B00B84h],0  
00BD265A  je          00BD2661  
00BD265C  call        73E6AF20  

Anyhow, the main question is: Why does the JIT compiler produce assembly that calls the empty-bodied static method EmptyBody?

Was it helpful?

Solution

After digging a bit further, it turns out I can answer this question myself. As explained at http://blogs.msdn.com/b/vancem/archive/2006/02/20/535807.aspx , observing the disassembly of an optimized release build under the debugger will by default affect the JIT compiler.

Unchecking these

  • ‘Suppress JIT optimization on module load’
  • ‘Enable Just My Code’

under VS > Tools > Debugging > General, will show the "real" JIT compilation result, which for the call to EmptyBody in my Main above is this:

004C2620  ret

Meaning that the call to EmptyBody is completely removed, which is what was expected and the world is still a happy and somewhat predictable place to live in :)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top