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*/)
IL_010d
- ldloca
push on stack reference to local variable startDate
IL_010f
- call
push on stack valuetype DateTime==NowMy 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
IL_010d
- call
push value on to the stackIL_0112
- stloc
pop value from stack and save in local variableIn 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)
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.