Search code examples
cilil

Calling code as labels or as functions in CIL?


I've spent the past few days of my free time learning CIL and was wondering about branching to a label (br ) vs calling a method (.method declaration).

I know that if you declare a method, you will be able to access that from outside the assembly, but what about making private methods labels and just using br to branch to it? Is there any performance gain to be had with that?

To clear up the confusion, here is a simplified example (due to space and time restrictions):

// calling code
ldc.i4 5
call int32 testmethod(int32)
// other code

// method
.method public int32 testmethod(int32) 
{
    ldc.i4 10
    add
    ldc.i4 20
    mul
    ret
}

So instead of doing that method, I could do that with labels and branches:

ldc.i4 5
br testlabel
leftoff:
// remaining instructions

testlabel:
.lcd.i4 10
add
ldc.i4 20
mul
br leftoff

So the method/label testlabel takes and int32 and then adds 10 and multiplies that result by 20. Simple enough. I realize that the one thing that a drawback (that I didn't mention originally) is readability, but if this is generated by a compiler readability becomes less important. So by using the second example, would using the labels and branching to the code offer any performance benefits? If not, what about if I would be able to fit it in a short branch? (br.s)


Solution

  • What you are describing is basically method inlining. Although it is possible that inlining a method will improve performance, there are several reasons that you might choose not to inline a method:

    • If the method is used several times, then it will probably need to be inlined several times. This can result in an overall increase in the size of the generated IL, and can possibly result in worse performance.
    • Sometimes, a method can't be inlined. For instance, a recursive method clearly can't be inlined (since it would need to be inlined again in the inlined body, ad infinitum), nor can a virtual method (assuming that you don't know the runtime type of the callee).
    • Even if you don't inline the method when writing IL, the JIT compiler may inline the method when it generates machine code anyway.

    UPDATE

    Okay, given your concrete example, let me explain the connection that I see to inlining. If the method is called several times (e.g. calling testmethod twice in a row), then your approach won't work, because you'd need to branch back to different locations depending on which call was being simulated, but there's no simple way to do this (you can add a local variable to track the additional state and then use a conditional branch, but that's complex enough that it would probably undo any performance gains). If it's only called once, then your transformation is basically equivalent to inlining, except with additional unconditional branches to and from the inlined method. I think that it would make more sense to actually inline the method and get rid of the branches. That is, given your example, it would look like this:

    ldc.i4 5
    // start of inlined call to testmethod
    ldc.i4 10
    add
    ldc.i4 20
    mul
    // end of inlined call to testmethod
    // other code from caller goes here
    

    Then, my above comments about inlining apply.