Why does this very simple C# method produce such illogical CIL code?

I've been digging into IL recently, and I noticed some odd behavior of the C# compiler. The following method is a very simple and verifiable application, it will immediately exit with exit code 1:

static int Main(string[] args)
{
    return 1;
}

When I compile this with Visual Studio Community 2015, the following IL code is generated (comments added):

.method private hidebysig static int32 Main(string[] args) cil managed
{
  .entrypoint
  .maxstack  1
  .locals init ([0] int32 V_0)     // Local variable init
  IL_0000:  nop                    // Do nothing
  IL_0001:  ldc.i4.1               // Push '1' to stack
  IL_0002:  stloc.0                // Pop stack to local variable 0
  IL_0003:  br.s       IL_0005     // Jump to next instruction
  IL_0005:  ldloc.0                // Load local variable 0 onto stack
  IL_0006:  ret                    // Return
}

If I were to handwrite this method, seemingly the same result could be achieved with the following IL:

.method static int32 Main()
{
  .entrypoint
  ldc.i4.1               // Push '1' to stack
  ret                    // Return
}

Are there underlying reasons that I'm not aware of that make this the expected behaviour?

Or is just that the assembled IL object code further optimized down the line, so the C# compiler does not have to worry about optimization?

Jon Skeet
people
quotationmark

The output you've shown is for a debug build. With a release build (or basically with optimizations turned on) the C# compiler generates the same IL you'd have written by hand.

I strongly suspect that this is all to make the debugger's work easier, basically - to make it simpler to break, and also see the return value before it's returned.

Moral: when you want to run optimized code, make sure you're not asking the compiler to generate code that's aimed at debugging :)

people

See more on this question at Stackoverflow