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.
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 jmp
s 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".
The return
statement indicates:
finally
blocks and Dispose calls in using
blocks will run.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.
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.
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