.NET languages compiles to IL code (aka msil or cil) The IL code contains instructions that in run time the jit compiler translate it to machine code.
In this post I want to talk about method call instructions.
When we writing the following in C#
The code will translate to one of the following: call or callvirt. Theoretically there is one more option named calli but c# compiler won’t generate calli instruction so I’ll skip this.
What is the difference and why should I care?
The difference is if the code will translating to direct call to supplied address (call) or to a late-bound call to specific address that we have to find via the object vtable (callvirt).
Callvirt: The callvirt instruction calls a late-bound method on an object.
Call: The call instruction calls the method indicated by the method descriptor passed with the instruction.
And why should I care? The first reason came up is performance, but actually you don’t need to care about this. First because you can’t do anything about this (except of writing your own CLR) and second because when you write your code you don’t really need to think “how I make it to emit call instead of callvirt” this is not so serious performance problem in .NET section. If you want to tweak these things you need to change language.
So as I see it, the reason is just a curiosity of what going on under the hood and why this happen and not this etc. And for me this is the most important reason. Because certainly knowing what happen under the hood give a lot in way of understanding what you do in your code and give a good debugging abilities,.
Back to topic, I think it’s reasonably to say that “direct call” mean static methods, sealed class methods, because these methods can be knowing at compile time and late-bound call mean virtual methods because these type of methods can’t be known at compile time and need to be determined in run time.
But this is not the true…
Again from MSDN:
Note that a delegate’s Invoke method can be called with either the call or callvirt instruction.
It is valid to call a virtual method using call (rather than callvirt).
So we saw that even virtual methods can be called via call instruction. But more surprising it that a non virtual methods will be called via callvirt instruction, It was understate if regular non virtual method called via callvirt because there is a change to them to being overridden, but for sealed classes it’s completely don’t make sense. Sealed class methods never been overridden. So why is this behaviour?
The answer is that callvirt do one more thing that we need even if of non virtual methods. The thing is a null check.
In disassembly its look like this:
cmp dword ptr [ecx], ecx
This will raise a NullReferenceException if the pointer in ecx register is invalid.
Note: If you want to read more about this, read this post at The Old New Thing.
Why we need the null check?
When we call to method on object the this pointer pass as the first argument, this can be a null.
What will be happen without the null check?
Case one: We try to use the this and we will get a NullReferenceException . This is an expected behaviour.
Case two: We don’t use this in the method and everything work just great. Some people will say that this is a little wired but after all this is a legal.
Case three: We don’t use this in the method but we call other method and there we try to use this. Then we get a NullReferenceException from the second method and this is more problematic from case two because its a little confusing that we doesn’t get the exception in the first when we use the object instance to call the first method..
So the design decision was to check for null every time we call an instance method. (as Eric Gunnerson explained here)
So to summarize it, methods calls generate a callvirt instruction because we need to make sure that the this pointer isn’t null.
Do I miss something here?
Virtual calls is not just about null check, its a different way to call methods as we see from MSDN. So just because the null check we use vtable for non virtual methods? Why to search for direct method in vtable…
Fortunately Iv’e founded this mail in mono mailing list that said that indeed we not really do virtual call for non-virtual method but we just relay on the null check of the callvirt instruction and then we call the method directly.
There is also the constrained opcode that can change the behavior from callvirt to call.
In the next post I’ll show that not all methods actually use the callvirt instruction and also put some IL examples to demonstrate it.
In C# 6 there is a new null check operator ‘?’
When you use ‘?’ on an non virtual method, C# will generate call instruction because the ‘?’ ensure that is not null by check for null and copy the reference to a local value.
See this question in SO