I just started looking at IL a bit and I'm curious if my attempt (shown below) to remove excess code from the output of the compiler had any unintended side effects.
A couple of quesiton about the results:
Original C# Code:
class Program {
public static int Main() {
return Add(1, 2);
}
public static int Add(int a, int b) {
return a + b;
}
}
Compiled with csc.exe
and disassembled it with ildasm.exe
(Original):
.method public hidebysig static int32 Main() cil managed
{
.entrypoint
.maxstack 2
.locals init (int32 V_0)
IL_0000: nop
IL_0001: ldc.i4.1
IL_0002: ldc.i4.2
IL_0003: call int32 Program::Add(int32, int32)
IL_0008: stloc.0
IL_0009: br.s IL_000b
IL_000b: ldloc.0
IL_000c: ret
}
.method public hidebysig static int32 Add(int32 a,
int32 b) cil managed
{
.maxstack 2
.locals init (int32 V_0)
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldarg.1
IL_0003: add
IL_0004: stloc.0
IL_0005: br.s IL_0007
IL_0007: ldloc.0
IL_0008: ret
}
Re-written (produces identical output):
.method public hidebysig static int32 Main() cil managed
{
.entrypoint
.maxstack 2
ldc.i4.1
ldc.i4.2
call int32 Program::Add(int32, int32)
ret
}
.method public hidebysig static int32 Add(int32 a, int32 b) cil managed
{
.maxstack 2
ldarg.0
ldarg.1
add
ret
}
All the 'excess' code you see is specific to debug builds (and generally gets optimized away for release builds) and allows you to perform something that you can't ordinarily do in release build.
Debug build code is such that it allows maximum independence in setting up breakpoints and changing/examining stack values during a debug session. Also the IL code should mimic the higher level code as far as possible so that every 'cause' and 'effect' can be mapped to higher level code lines.
Now to be specific to your questions:
What is the purpose of the nop operations in the original?
NOP allows you to set breakpoints at places which are not 'executed'. For e.g. the opening braces of a method, loop or if statement. Among these non-executable instructions, breaking at the opening brace allows you to modify/examine stack just before a block starts (though admittedly you can achieve this very easily by breaking at the first line of execution of the block instead of opening brace but it still allows you independence of breaking at the opening brace)
What is the purpose of the br.s at the end of the methods in the original?
Looking at the original code, you might find it to be nonsensical to 'jump' to the next line instead of allowing the code to naturally 'fall' to the next line. But read it as:
"In a debug build, whenever a method needs to return, jump to the end of the method, read the return value from stack and then return the value"
So what advantage does it offer to debugging?
If you have more than one return statement in your code, both of them will 'jump' to the end of the code before reading the return value from stack. This allows you exactly one place (the closing brace of the method) where you can put a break-point and modify the return value before it is actually returned to the calling method. Pretty helpful isn't it?
Is the re-written version improper in any way?
There is nothing improper in your code. In fact if you build the original in release mode and check generated CIL, you will notice that it is mostly same as yours.
Disclaimer: I'm not an IL expert by any means.
What is the purpose of the nop operation?
There was a big discussion about this in terms of x86 ASM on programmers.stackexchange.com a little while ago, see here: Purpose of NOP instruction and align statement in x86 assembly. It would essentially be the same.
What is the purpose of the br.s at the end of the methods in the original?
This is just a branch to the end of the method. If you were to have multiple return paths in this function it would make more sense to look at. As it stands, the compiler has included it instead of optimizing it away (possibly a compiler switch will optimize it away).
Is the re-written version improper in any way?
Not that I can see. You've just stripped the bulk of the compiler's work out that isn't required for such a simple application. If you were to make any further additions to this code then the extra IL here would be required to complete it's tasks.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With