For educational purposes I'm learning a bit of IL (mainly because I was curious what happens to '%' under the hood (which turns out to be rem) and started digressing...).
I wrote a method, just returning true to break things down a bit and was wondering about the 'br.s' opcode:
.method public hidebysig static bool ReturnTrue() cil managed
{
// Code size 7 (0x7)
.maxstack 1
.locals init ([0] bool CS$1$0000)
IL_0000: nop
IL_0001: ldc.i4.1
IL_0002: stloc.0
IL_0003: br.s IL_0005
IL_0005: ldloc.0
IL_0006: ret
} // End of method Primes::ReturnTrue
After ldc.i4.1 pushes 1 on the stack and stloc.0 places this in the 0th local, br.s basically (as far as I know) does a 'goto' to ldloc.0 at line IL_0005.
Why is this? Why is there simply no IL_0004 line so this could be omitted?
That branch is for debugging purposes, the return value has been calculated and stored away and now the debugger can be "called". It's the same with the NOP
in the method entry.
With regards to IL_0004
, as @hvd states, br.s
has an address and doesn't fit in "one row", one byte here (I don't know how familiar you are with addressing, but one instruction usually is one byte, that is, 8-bit, as well as the address or offset, typically 8-, 16- or 32-bit. In this case we have an 8-bit opcode with an 8-bit offset. Wikipedia has a good article on CIL-OP-codes).
Additionally, let's say your method has multiple returns and via for example if
-branches, all of them jump to the end, IL_0005
in your case, so only one breakpoint is needed at function return.
This is a very common artifact of a recursive-descent parser, like the one the C# compiler uses. Getting rid of these branches requires a peephole optimizer.
Likely to occur when the compiler has itself optimized a trivial operation whose outcome can be determined at compile time. The C# compiler does not have a peephole optimizer because it isn't necessary, the jitter takes care of eliminating these unnecessary branches. Putting the optimizer in the jitter is in general a winning strategy, every language compiler benefits from it. Keeps the compilers very simple and the considerable expense of writing and maintaining a code optimizer in just one (or a few) places.
That's not where the jitter optimizer ends, your entire method is going to disappear at runtime. With a high likelyhood that whatever code that calls the method is going to be substantially optimized as well since your method's return value is known at compile time. Seeing this kind of MSIL is otherwise a strong hint that your code can easily be simplified or has a bug :)
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