Search code examples
c#cililildasmno-op

Superfluous NOPs and branches in unoptimized MSIL


When I compile the following code as debug...

public class MyClass
{
    private int myField;

    public int MyProperty
    {
        get { return myField; }
        set { myField = value; }
    }
}

...strange bytecode with seemingly useless instructions is produced by the compiler. For instance see what is generated for the getter of property MyProperty (disassembled with ildasm.exe):

.method public hidebysig specialname instance int32 
        get_MyProperty() cil managed
{
    // Code size       12 (0xc)
    .maxstack  1
    .locals init ([0] int32 CS$1$0000)
    IL_0000:  nop
    IL_0001:  ldarg.0
    IL_0002:  ldfld      int32 MSILTest.MyClass::myField
    IL_0007:  stloc.0
    IL_0008:  br.s       IL_000a
    IL_000a:  ldloc.0
    IL_000b:  ret
} // end of method MyClass::get_MyProperty

Specifically, what is the nop at IL_0000 doing there? And why does the compiler generate this useless br.s instruction at IL_0008 literally out of nowhere? Why does it create the temporary local variable CS$1$0000?

For a release configuration the instruction set is generated as expected though:

IL_0000:  ldarg.0
IL_0001:  ldfld      int32 MSILTest.MyClass::myField
IL_0006:  ret

EDIT

I think I've found an answer to the question why the branch and the temporary local variable is there in a different question: it's probably to easy setting break points during debugging. So the question that remains is why the nop instruction is genereated.


Solution

  • The compiler creates these opcodes to provide a better debugging experience. At the start of each of your c# lines the compiler will introduce a nop so the debugger can break there. The branch and the temporary local variable are emitted to let you analyse the functions return value.