2010-12-11

Generics+specialization vs plain Interfaces.

For those who is interested in what comes out of generics and interfaces uses. Let's look at example:
using System;


namespace Generic
{
    class MainClass
    {
        public interface VectorProperties<T>
        {
            T Length
            { get; }
        }
       
        public struct Vec2 : VectorProperties<double>
        {
            public double X;
            public double Y;
           
            public double Length
            {
                get { return Math.Sqrt(X*X + Y*Y); }
            }
        }

        public struct Vec3 : VectorProperties<double>
        {
            public double X;
            public double Y;
            public double Z;
           
            public double Length
            {
                get { return Math.Sqrt(X*X + Y*Y + Z*Z); }
            }
        }
       
        public static double VectorsLengthA(VectorProperties<double> vec1, VectorProperties<double> vec2)
        {
            return (vec1.Length + vec2.Length);
        }

        public static double VectorsLengthB<V1,V2>(V1 vec1, V2 vec2)
            where V1 : VectorProperties<double>
            where V2 : VectorProperties<double>
        {
            return (vec1.Length + vec2.Length);
        }

        public static void Main (string[] args)
        {
            var a = new Vec2 { X = Convert.ToDouble(args[0]), Y = Convert.ToDouble(args[1]) };
            var b = new Vec3 { X = Convert.ToDouble(args[0]), Y = Convert.ToDouble(args[1]), Z = Convert.ToDouble(args[2]) };
            Console.WriteLine("Length a = {0}", a.Length);
            Console.WriteLine("Length b = {0}", b.Length);
            Console.WriteLine("1: Length a,b = {0}", VectorsLengthA(a, b));
            Console.WriteLine("2: Length a,b = {0}", VectorsLengthB(a, b));
        }
    }
}
 
See the CIL for VectorsLengthA and VectorsLengthB. Notice the way both those methods is called.
// ...

.namespace Generic
{
  .class private auto ansi beforefieldinit MainClass
    extends [mscorlib]System.Object
  {

    // ...

    // method line 2
    .method public static hidebysig
           default float64 VectorsLengthA (class Generic.MainClass/VectorProperties`1<float64> vec1, class Generic.MainClass/VectorProperties`1<float64> vec2)  cil managed
    {
        // Method begins at RVA 0x20f4
    // Code size 14 (0xe)
    .maxstack 8
    IL_0000:  ldarg.0
    IL_0001:  callvirt instance !0 class Generic.MainClass/VectorProperties`1<float64>::get_Length()
    IL_0006:  ldarg.1
    IL_0007:  callvirt instance !0 class Generic.MainClass/VectorProperties`1<float64>::get_Length()
    IL_000c:  add
    IL_000d:  ret
    } // end of method MainClass::VectorsLengthA

    // method line 3
    .method public static hidebysig
           default float64 VectorsLengthB<(class Generic.MainClass/VectorProperties`1<float64>) V1,(class Generic.MainClass/VectorProperties`1<float64>) V2> (!!V1 vec1, !!V2 vec2)  cil managed
    {
        // Method begins at RVA 0x2104
    // Code size 28 (0x1c)
    .maxstack 8
    IL_0000:  ldarga.s 0
    IL_0002:  constrained. !!0
    IL_0008:  callvirt instance !0 class Generic.MainClass/VectorProperties`1<float64>::get_Length()
    IL_000d:  ldarga.s 1
    IL_000f:  constrained. !!1
    IL_0015:  callvirt instance !0 class Generic.MainClass/VectorProperties`1<float64>::get_Length()
    IL_001a:  add
    IL_001b:  ret
    } // end of method MainClass::VectorsLengthB

    // method line 4
    .method public static hidebysig
           default void Main (string[] args)  cil managed
    {
        // Method begins at RVA 0x2124
    .entrypoint
    // Code size 198 (0xc6)
    .maxstack 17
    .locals init (
        valuetype Generic.MainClass/Vec2    V_0,
        valuetype Generic.MainClass/Vec3    V_1,
        valuetype Generic.MainClass/Vec2    V_2,
        valuetype Generic.MainClass/Vec3    V_3)
    // ...
    IL_0062:  stloc.1
    IL_0063:  ldstr "Length a = {0}"
    IL_0068:  ldloca.s 0
    IL_006a:  call instance float64 valuetype Generic.MainClass/Vec2::get_Length()
    IL_006f:  box [mscorlib]System.Double
    IL_0074:  call void class [mscorlib]System.Console::WriteLine(string, object)
    IL_0079:  ldstr "Length b = {0}"
    IL_007e:  ldloca.s 1
    IL_0080:  call instance float64 valuetype Generic.MainClass/Vec3::get_Length()
    IL_0085:  box [mscorlib]System.Double
    IL_008a:  call void class [mscorlib]System.Console::WriteLine(string, object)

    IL_008f:  ldstr "1: Length a,b = {0}"
    IL_0094:  ldloc.0
    IL_0095:  box Generic.MainClass/Vec2
    IL_009a:  ldloc.1
    IL_009b:  box Generic.MainClass/Vec3
    IL_00a0:  call float64 class Generic.MainClass::VectorsLengthA(class Generic.MainClass/VectorProperties`1<float64>, class Generic.MainClass/VectorProperties`1<float64>)
    IL_00a5:  box [mscorlib]System.Double
    IL_00aa:  call void class [mscorlib]System.Console::WriteLine(string, object)

    IL_00af:  ldstr "2: Length a,b = {0}"
    IL_00b4:  ldloc.0
    IL_00b5:  ldloc.1
    IL_00b6:  call float64 class Generic.MainClass::VectorsLengthB<valuetype Generic.MainClass/Vec2, valuetype Generic.MainClass/Vec3> (!!0, !!1)
    IL_00bb:  box [mscorlib]System.Double
    IL_00c0:  call void class [mscorlib]System.Console::WriteLine(string, object)
    IL_00c5:  ret
    } // end of method MainClass::Main

  // ...

  } // end of class Generic.MainClass
}

// ex:filetype=ilasm
 
After JIT it becomes:

Generic.exe.so:     file format elf64-x86-64


Disassembly of section .text:

0000000000001000 <methods>:
    ...

0000000000001010 <Generic_MainClass__ctor>:
    ; Ehmm.... Setting ZF from %rsp? What for? And why in that way?
    1010:   48 83 ec 08             sub    $0x8,%rsp
    1014:   48 83 c4 08             add    $0x8,%rsp
    1018:   c3                      retq  
    1019:   00 00                   add    %al,(%rax)
    101b:   00 00                   add    %al,(%rax)
    101d:   00 00                   add    %al,(%rax)
    ...

; interface-based VectorsLengthA
0000000000001020 <Generic_MainClass_VectorsLengthA_Generic_MainClass_VectorProperties_1_double_Generic_MainClass_VectorProperties_1_double>:
    1020:   48 83 ec 18             sub    $0x18,%rsp ; 3 words
    1024:   48 89 3c 24             mov    %rdi,(%rsp) ; vecA
    1028:   48 89 74 24 08          mov    %rsi,0x8(%rsp) ; vecB

    102d:   48 8b c7                mov    %rdi,%rax
    1030:   48 8b 00                mov    (%rax),%rax
    1033:   4d 8b 15 de 24 00 00    mov    0x24de(%rip),%r10        # 3518 <__bss_start+0x18>
    103a:   ff 90 70 ff ff ff       callq  *-0x90(%rax)
    1040:   f2 0f 11 44 24 10       movsd  %xmm0,0x10(%rsp) ; temp var for sum

    1046:   48 8b 7c 24 08          mov    0x8(%rsp),%rdi
    104b:   48 8b c7                mov    %rdi,%rax
    104e:   48 8b 00                mov    (%rax),%rax
    1051:   4d 8b 15 c0 24 00 00    mov    0x24c0(%rip),%r10        # 3518 <__bss_start+0x18>
    1058:   ff 90 70 ff ff ff       callq  *-0x90(%rax)

    ; ???
    105e:   f2 0f 10 c8             movsd  %xmm0,%xmm1
    1062:   f2 0f 10 44 24 10       movsd  0x10(%rsp),%xmm0
    1068:   f2 0f 58 c1             addsd  %xmm1,%xmm0

    106c:   48 83 c4 18             add    $0x18,%rsp
    1070:   c3                      retq  
    ...

; generic-based VectorsLengthB (so we can work with any generic)
; not so much difference from VectorsLengthA
; except of storing r10 on stack
0000000000001080 <Generic_MainClass_VectorsLengthB_V1_V2_V1_V2>:
    1080:   48 83 ec 28             sub    $0x28,%rsp ; 4 words
    1084:   4c 89 14 24             mov    %r10,(%rsp) ; unknown
    1088:   48 89 7c 24 08          mov    %rdi,0x8(%rsp) ; vecA
    108d:   48 89 74 24 10          mov    %rsi,0x10(%rsp)

    1092:   48 8b c7                mov    %rdi,%rax ; vecA
    1095:   48 8b f8                mov    %rax,%rdi ; what a???...

    1098:   48 8b 00                mov    (%rax),%rax ; vecA
    109b:   4d 8b 15 76 24 00 00    mov    0x2476(%rip),%r10        # 3518 <__bss_start+0x18>
    10a2:   ff 90 70 ff ff ff       callq  *-0x90(%rax)
    10a8:   f2 0f 11 44 24 18       movsd  %xmm0,0x18(%rsp) ; temp var for sum

    10ae:   48 8b 44 24 10          mov    0x10(%rsp),%rax ; vecB
    10b3:   48 8b f8                mov    %rax,%rdi
    10b6:   48 8b 00                mov    (%rax),%rax
    10b9:   4d 8b 15 58 24 00 00    mov    0x2458(%rip),%r10        # 3518 <__bss_start+0x18>
    10c0:   ff 90 70 ff ff ff       callq  *-0x90(%rax)
    10c6:   f2 0f 10 c8             movsd  %xmm0,%xmm1
    10ca:   f2 0f 10 44 24 18       movsd  0x18(%rsp),%xmm0
    10d0:   f2 0f 58 c1             addsd  %xmm1,%xmm0
    10d4:   48 83 c4 28             add    $0x28,%rsp
    10d8:   c3                      retq  
    10d9:   00 00                   add    %al,(%rax)
    10db:   00 00                   add    %al,(%rax)
    10dd:   00 00                   add    %al,(%rax)
    ...

00000000000010e0 <Generic_MainClass_Main_string__>:
    10e0:   55                      push   %rbp
    10e1:   48 8b ec                mov    %rsp,%rbp
    10e4:   41 57                   push   %r15
    10e6:   48 81 ec 08 01 00 00    sub    $0x108,%rsp
    10ed:   4c 8b ff                mov    %rdi,%r15
    10f0:   33 c0                   xor    %eax,%eax
    10f2:   48 89 85 58 ff ff ff    mov    %rax,-0xa8(%rbp)
    ; ...
    1130:   48 89 85 60 ff ff ff    mov    %rax,-0xa0(%rbp)
    ; ...

    ; direct call to interface implementation
    1218:   48 8b 45 90             mov    -0x70(%rbp),%rax
    121c:   48 89 85 68 ff ff ff    mov    %rax,-0x98(%rbp)
    1223:   48 8b 45 98             mov    -0x68(%rbp),%rax
    1227:   48 89 85 70 ff ff ff    mov    %rax,-0x90(%rbp)
    122e:   48 8b 45 a0             mov    -0x60(%rbp),%rax
    1232:   48 89 85 78 ff ff ff    mov    %rax,-0x88(%rbp)
    1239:   49 8b 05 e0 22 00 00    mov    0x22e0(%rip),%rax        # 3520 <__bss_start+0x20>
    1240:   48 89 85 18 ff ff ff    mov    %rax,-0xe8(%rbp)
    1247:   48 8b fd                mov    %rbp,%rdi
    124a:   48 81 c7 58 ff ff ff    add    $0xffffffffffffff58,%rdi
    1251:   e8 2a 02 00 00          callq  1480 <Generic_MainClass_Vec2_get_Length>
    1256:   f2 0f 11 85 10 ff ff    movsd  %xmm0,-0xf0(%rbp)
    125d:   ff

    ; indirect call with boxing
    125e:   49 8b 3d c3 22 00 00    mov    0x22c3(%rip),%rdi        # 3528 <__bss_start+0x28>
    1265:   e8 1a 03 00 00          callq  1584 <plt_wrapper_alloc_object_AllocSmall_intptr>
    126a:   48 8b f0                mov    %rax,%rsi
    126d:   48 8b bd 18 ff ff ff    mov    -0xe8(%rbp),%rdi
    1274:   f2 0f 10 85 10 ff ff    movsd  -0xf0(%rbp),%xmm0
    127b:   ff
    127c:   f2 0f 11 46 10          movsd  %xmm0,0x10(%rsi)
    1281:   e8 08 03 00 00          callq  158e <plt_System_Console_WriteLine_string_object>

    ; ...

    ; indirect call to local function with boxing
    12d3:   49 8b 05 5e 22 00 00    mov    0x225e(%rip),%rax        # 3538 <__bss_start+0x38>
    12da:   48 89 85 40 ff ff ff    mov    %rax,-0xc0(%rbp)
    12e1:   48 8b 85 58 ff ff ff    mov    -0xa8(%rbp),%rax
    12e8:   48 89 45 a8             mov    %rax,-0x58(%rbp)
    12ec:   48 8b 85 60 ff ff ff    mov    -0xa0(%rbp),%rax
    12f3:   48 89 45 b0             mov    %rax,-0x50(%rbp)
    12f7:   49 8b 3d 42 22 00 00    mov    0x2242(%rip),%rdi        # 3540 <__bss_start+0x40>
    12fe:   e8 81 02 00 00          callq  1584 <plt_wrapper_alloc_object_AllocSmall_intptr>
    1303:   48 89 85 30 ff ff ff    mov    %rax,-0xd0(%rbp)
    130a:   48 83 c0 10             add    $0x10,%rax
    130e:   48 8b 4d a8             mov    -0x58(%rbp),%rcx
    1312:   48 89 08                mov    %rcx,(%rax)
    1315:   48 8b 4d b0             mov    -0x50(%rbp),%rcx
    1319:   48 89 48 08             mov    %rcx,0x8(%rax)
    131d:   48 8b 85 68 ff ff ff    mov    -0x98(%rbp),%rax
    1324:   48 89 45 b8             mov    %rax,-0x48(%rbp)
    1328:   48 8b 85 70 ff ff ff    mov    -0x90(%rbp),%rax
    132f:   48 89 45 c0             mov    %rax,-0x40(%rbp)
    1333:   48 8b 85 78 ff ff ff    mov    -0x88(%rbp),%rax
    133a:   48 89 45 c8             mov    %rax,-0x38(%rbp)
    133e:   49 8b 3d 03 22 00 00    mov    0x2203(%rip),%rdi        # 3548 <__bss_start+0x48>
    1345:   e8 3a 02 00 00          callq  1584 <plt_wrapper_alloc_object_AllocSmall_intptr>
    134a:   48 8b f0                mov    %rax,%rsi
    134d:   48 8b bd 30 ff ff ff    mov    -0xd0(%rbp),%rdi
    1354:   48 8b c6                mov    %rsi,%rax
    1357:   48 83 c0 10             add    $0x10,%rax
    135b:   48 8b 4d b8             mov    -0x48(%rbp),%rcx
    135f:   48 89 08                mov    %rcx,(%rax)
    1362:   48 8b 4d c0             mov    -0x40(%rbp),%rcx
    1366:   48 89 48 08             mov    %rcx,0x8(%rax)
    136a:   48 8b 4d c8             mov    -0x38(%rbp),%rcx
    136e:   48 89 48 10             mov    %rcx,0x10(%rax)
    1372:   e8 21 02 00 00          callq  1598 <plt_Generic_MainClass_VectorsLengthA_Generic_MainClass_VectorProperties_1_double_Generic_MainClass_VectorProperties_1_double>
    1377:   f2 0f 11 85 38 ff ff    movsd  %xmm0,-0xc8(%rbp)
    137e:   ff

    ; ...

    ; direct call to generic
    13a7:   49 8b 05 a2 21 00 00    mov    0x21a2(%rip),%rax        # 3550 <__bss_start+0x50>
    13ae:   48 89 85 50 ff ff ff    mov    %rax,-0xb0(%rbp)
    13b5:   48 8b 85 58 ff ff ff    mov    -0xa8(%rbp),%rax
    13bc:   48 89 45 d0             mov    %rax,-0x30(%rbp)
    13c0:   48 8b 85 60 ff ff ff    mov    -0xa0(%rbp),%rax
    13c7:   48 89 45 d8             mov    %rax,-0x28(%rbp)
    13cb:   48 8b 85 68 ff ff ff    mov    -0x98(%rbp),%rax
    13d2:   48 89 45 e0             mov    %rax,-0x20(%rbp)
    13d6:   48 8b 85 70 ff ff ff    mov    -0x90(%rbp),%rax
    13dd:   48 89 45 e8             mov    %rax,-0x18(%rbp)
    13e1:   48 8b 85 78 ff ff ff    mov    -0x88(%rbp),%rax
    13e8:   48 89 45 f0             mov    %rax,-0x10(%rbp)
    13ec:   48 8b 45 e0             mov    -0x20(%rbp),%rax
    13f0:   48 89 04 24             mov    %rax,(%rsp)
    13f4:   48 8b 45 e8             mov    -0x18(%rbp),%rax
    13f8:   48 89 44 24 08          mov    %rax,0x8(%rsp)
    13fd:   48 8b 45 f0             mov    -0x10(%rbp),%rax
    1401:   48 89 44 24 10          mov    %rax,0x10(%rsp)
    1406:   48 8b 7d d0             mov    -0x30(%rbp),%rdi
    140a:   48 8b 75 d8             mov    -0x28(%rbp),%rsi
    140e:   e8 1d 01 00 00          callq  1530 <Generic_MainClass_VectorsLengthB_Generic_MainClass_Vec2_Generic_MainClass_Vec3_Generic_MainClass_Vec2_Generic_MainClass_Vec3>
    1413:   f2 0f 11 85 48 ff ff    movsd  %xmm0,-0xb8(%rbp)
    141a:   ff

    ; ...
    ...

    ; ...

; Specialization of VectorsLengthB<Vec2,Vec3>
0000000000001530 <Generic_MainClass_VectorsLengthB_Generic_MainClass_Vec2_Generic_MainClass_Vec3_Generic_MainClass_Vec2_Generic_MainClass_Vec3>:
    1530:   55                      push   %rbp
    1531:   48 8b ec                mov    %rsp,%rbp
    1534:   48 83 ec 20             sub    $0x20,%rsp
    1538:   48 89 7d f0             mov    %rdi,-0x10(%rbp)
    153c:   48 89 75 f8             mov    %rsi,-0x8(%rbp)
    1540:   48 8b fd                mov    %rbp,%rdi

    1543:   48 83 c7 f0             add    $0xfffffffffffffff0,%rdi
    1547:   e8 34 ff ff ff          callq  1480 <Generic_MainClass_Vec2_get_Length>
    154c:   f2 0f 11 45 e8          movsd  %xmm0,-0x18(%rbp)

    1551:   48 8d 7d 10             lea    0x10(%rbp),%rdi
    1555:   e8 76 ff ff ff          callq  14d0 <Generic_MainClass_Vec3_get_Length>
    155a:   f2 0f 10 c8             movsd  %xmm0,%xmm1

    155e:   f2 0f 10 45 e8          movsd  -0x18(%rbp),%xmm0
    1563:   f2 0f 58 c1             addsd  %xmm1,%xmm0

    1567:   c9                      leaveq
    1568:   c3                      retq  
    ; ...
    ...

    ; ... plt ...
You can see that B-variant was specialized for <Vec2, Vec3> and that function was called directly instead of being called through resolving table (or whatever it was). The reason for using indirect call to method within non-partial class is still remains mistery. P.S.: Mono JIT compiler version 2.8.1