This is the eleventh part of the .NET Inside Out series. For your convenience you can find other parts in the table of contents in Part 1 – Virtual and non-virtual calls in C#
Last time we saw how type markers can result in code optimizations because .NET knows types with structs and can get rid of some code. Today we are going to see that it can actually devirtualize calls.
Let’s get this code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
using System; public class Class { public static void Main(string[] args) { Bar(new A()); Bar(new B()); Bar(new C()); } public static void Bar<T>(T val) where T : IFoo{ val.Foo(); } } public interface IFoo{ void Foo(); } public class A : IFoo{ public void Foo(){ Console.WriteLine("A"); } } public class B : IFoo{ public void Foo(){ Console.WriteLine("B"); } } public struct C : IFoo{ public void Foo(){ Console.WriteLine("C"); } } |
Generic method for value types must be JITted differently than for normal reference types. This is the code of Main
method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
Class.Main(System.String[]) L0000: sub rsp, 0x28 L0004: mov rcx, 0x7ffe7390d580 L000e: call 0x7ffec7e9c520 L0013: mov rcx, rax L0016: mov r11, 0x7ffe73907020 L0020: cmp [rcx], ecx L0022: call qword [rip+0x6b58] L0028: mov rcx, 0x7ffe7390d6f8 L0032: call 0x7ffec7e9c520 L0037: mov rcx, rax L003a: mov r11, 0x7ffe73907028 L0044: cmp [rcx], ecx L0046: call qword [rip+0x6b3c] L004c: mov rcx, 0x211bffd7ef0 L0056: mov rcx, [rcx] L0059: call System.Console.WriteLine(System.String) L005e: nop L005f: add rsp, 0x28 L0063: ret |
We see that call to C.Foo()
was inlined and we print to the output directly. If you now change it to
1 2 3 4 5 6 |
public struct C : IFoo{ public void Foo(){ Console.WriteLine("C"); Console.WriteLine("C"); } } |
You get the following Main
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
Class.Main(System.String[]) L0000: sub rsp, 0x38 L0004: xor eax, eax L0006: mov [rsp+0x30], rax L000b: mov rcx, 0x7ffe7391d580 L0015: call 0x7ffec7e9c520 L001a: mov rcx, rax L001d: mov r11, 0x7ffe73917020 L0027: cmp [rcx], ecx L0029: call qword [rip+0x6b51] L002f: mov rcx, 0x7ffe7391d6f8 L0039: call 0x7ffec7e9c520 L003e: mov rcx, rax L0041: mov r11, 0x7ffe73917028 L004b: cmp [rcx], ecx L004d: call qword [rip+0x6b35] L0053: mov byte [rsp+0x30], 0x0 L0058: movsx rcx, byte [rsp+0x30] L005e: mov [rsp+0x28], cl L0062: lea rcx, [rsp+0x28] L0067: call C.Foo() L006c: nop L006d: add rsp, 0x38 L0071: ret |
So you can see the direct call to the final implementation.