Search code examples
c#cilmono.cecil

How in CIL (MSIL) "call instance void valuetype [..type]" return or save value? (Common Intermediate Language)


I am creating an .Net intermediate code emulator that executes CIL instruction after instruction. I'm having trouble emulating the call instance void valuetype more precisely with saving its result

I have C# code :DateTime? startDate = DateTime.Now;

that's compiled to:

IL_010d: ldloca.s     startDate
IL_010f: call         valuetype [mscorlib]System.DateTime [mscorlib]System.DateTime::get_Now()
IL_0114: call         instance void valuetype [mscorlib]System.Nullable`1<valuetype [mscorlib]System.DateTime>::.ctor(!0/*valuetype [mscorlib]System.DateTime*/)
  • on IL_010d - ldloca push on stack reference to local variable startDate
  • on IL_010f - call push on stack valuetype DateTime==Now

My first question is: what happen on IL_0114? What the stack should look like after executing the call method? How will the DateTime.Now value be entered into the local startData variable and at what point?

Alternative if I change C# code to DateTime startDate = DateTime.Now; (without ?) is compiled to:

IL_010d: call         valuetype [mscorlib]System.DateTime [mscorlib]System.DateTime::get_Now()
IL_0112: stloc.2      // startDate
  • on IL_010d - call push value on to the stack
  • on IL_0112 - stloc pop value from stack and save in local variable

In this case, everything is clear to me. i.e. after executing the block, there is no object on the stack and DateTime.Now has been written to the local variable startDate.

My second question is what is the difference between these two calls? call instance void valuetype(IL_0114) vs call valuetype(IL_010d)


Solution

  • There is no "call instance void valuetype". There is only call and the rest is a method reference.

    In the case of the simpler code, valuetype [mscorlib]System.DateTime [mscorlib]System.DateTime::get_Now() is the method reference, but valuetype [mscorlib]System.DateTime is just part of the return type (having valuetype to indicate an unboxed form of the type, as opposed to boxed or class).

    After the call to get_Now, the value gets to the stack. However, when you convert it to a variable of type Nullable<DateTime>, you actually call its constructor which takes care of properly initializing the value.

    The method that is called here is instance void valuetype [mscorlib]System.Nullable`1<valuetype [mscorlib]System.DateTime>::.ctor(!0), i.e. the constructor. Every constructor actually returns void, so without any special instruction, the stack is empty after the call, and since the method is also instance, it needs a this reference. Again valuetype here indicates that you are calling the method on an unboxed value of the type.

    This is one of the common ways to initialize a value type when its storage location is already allocated. You simply obtain its address and call the constructor in-place, as opposed to using newobj. Similarly, the initobj instruction zero-initializes it, as if the default constructor had been called.