Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does a method with an absolute return path gets inlined?

Tags:

c#

inline

I mostly develop using C#, but I think this question might be suitable for other languages as well.
Also, it seems like there is a lot of code here but the question is very simple.

Inlining, as I understand it, is the compiler (in the case of C# the Virtual Machine) replacing a method call by inserting the body of the method in every place the method was called from.

Let's say I have the following program:

static Main()
{        
    int number = 7;
    bool a;
    a = IsEven(number);
    Console.WriteLine(a);
}

... the body of the method IsEven:

bool IsEven(int n) 
{
    if (n % 2 == 0) // Two conditional return paths
        return true;
    else
        return false;
}

I could understand how code will look like after inlining the method:

static Main()
{
    int number = 7;
    bool a;
    if (number % 2 == 0)
        a = true;
    else
        a = false;
    Console.WriteLine(a); // Will print true if 'number' is even, otherwise false
}

An obviously simple and correct program.

But if I tweak the body of IsEven a little bit to include an absolute return path...

bool IsEven(int n)
{
    if (n % 2 == 0)
        return true;
    return false; // <- Absolute return path!
}

I personally like this style a bit more in some situations. Some refractoring tools might even suggest that I do change the first version to look like this one - but when I tried to imagine how this method would look like when it's inlined I was stumped.
If we inline the second version of the method:

static Main()
{
    int number = 7;
    bool a;
    if (number % 2 == 0)
        a = true;
    a = false;
    Console.WriteLine(a); // Will always print false!
}

The question to be asked:
How does the Compiler / Virtual Machine deal with inlining a method that has an absolute return path?
It seems extremely unlikely that something like this would really prevent method inlining, so I wonder how such things are dealt with. Perhaps the process of inlining isn't as simple as this? Maybe one version is more likely to being inlined by the VM?

Edit: Profiling both methods (and manual inlining of the first one) showed no difference in performance, so I can only assume that both methods get inlined and work in the same or similar manner (at least on my VM).
Also, these methods are extremely simple and seem almost interchangeable, but complex methods with absolute return paths might be much more difficult to change into versions without absolute return paths.

like image 705
Acidic Avatar asked Apr 01 '12 04:04

Acidic


3 Answers

It's can be kind of hard to explain what the JITter does when it inlines - it does not chage the C# code to do the inlining - it will (always?) work on the produced bytes (the compiled version) - and the "tools" you have when producing assembly-code (the actual machine code bytes) are much more fine grained than what you have in C# (or IL for that matter).

That said, you can have an idea of how it works, in C# terms by considering the break keyword..

Consider the possiblity that every inline function that's not trivial is enclosed in a while (true) loop (or do while(false) loop) - and that every source return is translated to a localVar = result; break; set of statements. Then you get something like this:

static Main() 
{ 
    int number = 7; 
    bool a; 
    while (true)
    {
       if (number % 2 == 0) 
       {
          a = true; 
          break;
        }
        a = false; 
        break;
    }

    Console.WriteLine(a); // Will always print the right thing! Yey!
}

Similarly, when producing assembly, you will see A LOT of jmps being generated - those are the moral equivalent of the break statements, but they are MUCH more flexible (think about them as anonymous gotos or something).

So you can see, the jitter (and any compiler that compiles to native) has A LOT of tools at hand it can use to do "the right thing".

like image 93
Shahar Prish Avatar answered Oct 22 '22 20:10

Shahar Prish


The return statement indicates:

  • The result value
  • Destruction of local variables (This applies to C++, not C#) In C#, finally blocks and Dispose calls in using blocks will run.
  • A jump out of the function

All these things still happen after inlining. The jump will be local instead of cross-functions after inlining, but it will still be there.

Inlining is NOT textual substitution like C/C++ macros are.

Other things that are different between inlining and textual substitution are treatment of variables with the same name.

like image 3
Ben Voigt Avatar answered Oct 22 '22 22:10

Ben Voigt


The machine code that the cpu executes is a very simple language. It doesn't have the notion of the return statement, a subroutine has a single point of entry and a single point of exit. So your IsEven() method like this:

bool IsEven(int n)
{
    if (n % 2 == 0)
        return true;
    return false;
}

needs to be rewritten by the jitter to something that resembles this (not valid C#):

void IsEvent(int n) 
{
    if (n % 2 == 0) {
       $retval = true;
       goto exit;
    }
    $retval = false;
exit:
}  // $retval becomes the function return value

The $retval variable might look fake here. It is not, it is the EAX register on an x86 core. You'll now see that this code is simple to inline, it can be transplanted directly into the body of Main(). The $retval variable can be equated to the a variable with a simple logical substitution.

like image 2
Hans Passant Avatar answered Oct 22 '22 20:10

Hans Passant