From C# to CLR Jitted Code – ByVal and ByRef


I am trying to understand the difference between ByVal and ByRef objects. In below C# code we pass different parameter types to the Test method. Let’s see how the runtime treat them differently at the IL and Assembly level.


 


using System;


using System.Runtime.CompilerServices;


namespace CodeGen


{


    public class MyClass


    {


        public static void Main()


        {


            MyType o1 = new MyType();


            MyType o2 = new MyType();


            MyType o3;          


            Test(o1, ref o2, out o3);


        }


        [MethodImpl(MethodImplOptions.NoInlining)] // Without this attribute, it will be inlined


        public static void Test(MyType o1, ref MyType o2, out MyType o3)


        {


            o1.X = 1;


            o2.X = 2;


            o3 = new MyType();


            o3.X = 3;


        }


    }


 


    public class MyType


    {


        public int X;


    }


}


I highlighted operation that is specific for ByRef object. ByVal object usually can be pushed or popped from stack by one IL opcode. ByRef object is usually accessed indirectly from a managed pointer. That is why we need those stind ldind opcode.


 


.method public hidebysig static void  Main() cil managed


{


  .entrypoint


  // Code size       25 (0x19)


  .maxstack  3


  .locals init (class CodeGen.MyType V_0,


           class CodeGen.MyType V_1,


           class CodeGen.MyType V_2)


  IL_0000:  nop


  IL_0001:  newobj     instance void CodeGen.MyType::.ctor()


  IL_0006:  stloc.0


  IL_0007:  newobj     instance void CodeGen.MyType::.ctor()


  IL_000c:  stloc.1


  IL_000d:  ldloc.0


  IL_000e:  ldloca.s   V_1


  IL_0010:  ldloca.s   V_2


  IL_0012:  call       void CodeGen.MyClass::Test(class CodeGen.MyType,


                                                  class CodeGen.MyType&,


                                                  class CodeGen.MyType&)


  IL_0017:  nop


  IL_0018:  ret


} // end of method MyClass::Main


 


 


.method public hidebysig static void  Test(class CodeGen.MyType o1,


                                           class CodeGen.MyType& o2,


                                           [out] class CodeGen.MyType& o3) cil managed noinlining


{


  // Code size       32 (0x20)


  .maxstack  8


  IL_0000:  nop


  IL_0001:  ldarg.0


  IL_0002:  ldc.i4.1


  IL_0003:  stfld      int32 CodeGen.MyType::X


  IL_0008:  ldarg.1


  IL_0009:  ldind.ref


  IL_000a:  ldc.i4.2


  IL_000b:  stfld      int32 CodeGen.MyType::X


  IL_0010:  ldarg.2                                          //It seems that below code can be optimized by reorder the stind.ref to the end of the method.


  IL_0011:  newobj     instance void CodeGen.MyType::.ctor()


  IL_0016:  stind.ref


  IL_0017:  ldarg.2


  IL_0018:  ldind.ref


  IL_0019:  ldc.i4.3


  IL_001a:  stfld      int32 CodeGen.MyType::X


  IL_001f:  ret


} // end of method MyClass::Test


 


 


CodeGen.MyClass.Main()


Begin 02570070, size 4e


02570070 57              push    edi    // Preserve edi esi, in case it is used in the caller code, since we are going to change both in below code


02570071 56              push    esi   


02570072 83ec08          sub     esp,8    // Leave some stack space and use them as local variables


02570075 33c0            xor     eax,eax


02570077 890424          mov     dword ptr [esp],eax     // set ref o3 to null


0257007a 89442404        mov     dword ptr [esp+4],eax   // set ref o2 to null


0257007e b97c34f301      mov     ecx,1F3347Ch            // !dumptype 1Fee47ch reveals that it is the MethodTable for CodeGen.MyType


02570083 e89c1f9bff      call    01f22024                // Allocate some space for above type


02570088 8bf0            mov     esi,eax                 // o1 -> esi       !do eax   can prove that


0257008a 8bce            mov     ecx,esi                 // ecx represents this pointer


0257008c e807440f59      call    mscorlib_ni!System.Object..ctor() (5b664498)     // Complete object construction


02570091 b97c34f301      mov     ecx,1F3347Ch   


02570096 e8891f9bff      call    01f22024                 


0257009b 8bf8            mov     edi,eax                 // o2 ->edi


0257009d 8bcf            mov     ecx,edi


0257009f e8f4430f59      call    mscorlib_ni!System.Object..ctor() (5b664498)


025700a4 893c24          mov     dword ptr [esp],edi           


025700a7 8d442404        lea     eax,[esp+4]             // ref o3 is still null


025700ab 50              push    eax                     // push ref o3 so that Callee can pick it up


025700ac 8d542404        lea     edx,[esp+4]             // edx = [edi]     ref o2


025700b0 8bce            mov     ecx,esi                 // o1 -> ecx


025700b2 ff15fc32f301    call    dword ptr ds:[1F332FCh] // Stub for Test, since Test is not Jitted yet


025700b8 83c408          add     esp,8                   // clean up stack and restore esi edi


025700bb 5e              pop     esi


025700bc 5f              pop     edi


025700bd c3              ret


 


 


CodeGen.MyClass.Test(CodeGen.MyType, CodeGen.MyType ByRef, CodeGen.MyType ByRef)


025700d2 57              push    edi


025700d3 56              push    esi


025700d4 8b7c240c        mov     edi,dword ptr [esp+0Ch] // ref o3 -> edi


025700d8 c7410401000000  mov     dword ptr [ecx+4],1   // o1 ->ecx     +4 is the offset for the field.  The first 4 bytes represents the method table.


025700df 8b02            mov     eax,dword ptr [edx]   // o2 ->[edx]   o2->eax  ByRef has to be accessed indirectly here 


025700e1 c7400402000000  mov     dword ptr [eax+4],2   // set field again 


025700e8 b97c34f301      mov     ecx,1F3347Ch //CodeGen.MyType)


025700ed e8321f9bff      call    01f22024     // Allocate memory


025700f2 8bf0            mov     esi,eax      // o3 -> esi


025700f4 8bce            mov     ecx,esi


025700f6 e89d430f59      call    mscorlib_ni!System.Object..ctor() (5b664498)


025700fb 8d17            lea     edx,[edi]    // This along with the WriteBarrier makes sure that GC can find the root of o3


025700fd e8eeaa5b5b      call    5db2abf0     // JitHelper for WriteBarrier


02570102 8b07            mov     eax,dword ptr [edi]


02570104 c7400403000000  mov     dword ptr [eax+4],3  // set field for o3


0257010b 5e              pop     esi


0257010c 5f              pop     edi


0257010d c20400          ret     4


Comments (0)