Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is the 'br.s' IL opcode used in this case?

Tags:

c#

il

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?

like image 920
Apeiron Avatar asked Oct 12 '14 08:10

Apeiron


2 Answers

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.

like image 51
flindeberg Avatar answered Oct 06 '22 01:10

flindeberg


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 :)

like image 27
Hans Passant Avatar answered Oct 06 '22 01:10

Hans Passant