Value type methods – call, callvirt, constrained and hidden boxing

Long time ago I wrote a post here on  call vs callvirt and the needed of the this null check. Here I want to wrote about this topic but on value types.

Look on these three ToString() calls:


struct Struct1
{
    public override string ToString() { return "";}
}

struct Struct2{}

class Program
{
    static void Main(string[] args)
    {
        // Case 1
        var s1 = new Struct1();
        s1.ToString();

        // Case 2
        var s2 = new Struct2();
        s2.ToString();

        // Case 3
        int i = 5;
        i.ToString();
    }
}

In each ToString() call, what will be called, call or callvirt?

In case 1 and case 2, callvirt will be generated by the compiler. But this is not the all story. Before callvirt, another instruction will be generated, called constrined. And this instruction cause to different behaviour between case 1 and case 2. And what will happen in case 3?

Lets start by see what is constrained MSDN

The constrained prefix is permitted only on a callvirt instruction.

The state of the MSIL stack at this point must be as follows:

  1. A managed pointer, ptr, is pushed onto the stack. The type of ptr must be a managed pointer (&) to thisType. Note that this is different from the case of an unprefixed callvirt instruction, which expects a reference of thisType.

And

When a callvirt method instruction has been prefixed by constrained thisType, the instruction is executed as follows:

  • If thisType is a value type and thisType implements method then ptr is passed unmodified as the ‘this’ pointer to a call method instruction, for the implementation of method by thisType.
  • If thisType is a value type and thisType does not implement method then ptr is dereferenced, boxed, and passed as the ‘this’ pointer to the callvirt method instruction.

Case 1:

It’s a value type that implement the method. Roslyn (C# compiler) generating constrained and callvirt but in the end, call is execute.

Case 2:

It’s a value type that doesn’t implement the method. Roslyn generating constrained and callvirt and then ptr is dereferenced and boxed and callvirt is execute.

Here happening what I wrote in title “hidden boxing”. Note that the ToString() call is made on boxed object and not on the one you mean to call it.

Case 3:

Here surprisingly, Roslyn generating call instruction. What the different between int and our custom value types?

The answer is, that in principle, for value type that override the method, call need to be emitted, and when it’s doesn’t override the method, an extra operation need to be done to be able to emit callvirt. Beside that there is another problem for value types that override method and the override has removed or the opposite.

To simplify this, as we saw, Roslyn emit, callvirt with constrained prefix.

But in the last case, Roslyn know that int override the method, and int is a special type (among other system types), so it can assume that override Tostring() is not will change, and emitting a call directly to the right method!

Advertisements
This entry was posted in .NET, c#, Roslyn and tagged , , , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s