I have a recursive function emit : Map<string,LocalBuilder> -> exp -> unit where il : ILGenerator is global to the function and exp is a discriminant union representing a type-checked parsed language with case InstanceCall of exp * MethodInfo * exp list * Type and Type is a property on exp representing the Type of the expression.
In the following fragment, I am trying to emit IL opcodes for an instance call where instance.Type may or may not be a ValueType. So I understand I can use OpCodes.Constrained to flexibly and efficiently make virtual calls on reference, value, and enum types. I’m new to Reflection.Emit and machine languages in general so understanding the linked documentation for OpCodes.Constrained is not strong for me.
Here is my attempt, but it results in a VerificationException, “Operation could destabilize the runtime.”:
let rec emit lenv ast =
match ast with
...
| InstanceCall(instance,methodInfo,args,_) ->
instance::args |> List.iter (emit lenv)
il.Emit(OpCodes.Constrained, instance.Type)
il.Emit(OpCodes.Callvirt, methodInfo)
...
Looking at the docs, I think the key may be “A managed pointer, ptr, is pushed onto the stack. The type of ptr must be a managed pointer (&) to thisType. Note that this is different from the case of an unprefixed callvirt instruction, which expects a reference of thisType.”
Update
Thank you @Tomas and @desco, I now understand when to use OpCodes.Constrained (instance.Type is a ValueType, but methodInfo.DeclaringType is a reference type).
But it turns out I don’t need to consider that case just yet, and my real problem was the instance argument on the stack: it only took me 6 hours to learn it needs an address instead of the value (looking at the DLR source code gave me clues, and then using ilasm.exe on a simple C# program made it clear).
Here is my final working version:
let rec emit lenv ast =
match ast with
| Int32(x,_) ->
il.Emit(OpCodes.Ldc_I4, x)
...
| InstanceCall(instance,methodInfo,args,_) ->
emit lenv instance
//if value type, pop, put in field, then load the field address
if instance.Type.IsValueType then
let loc = il.DeclareLocal(instance.Type)
il.Emit(OpCodes.Stloc, loc)
il.Emit(OpCodes.Ldloca, loc)
for arg in args do emit lenv arg
if instance.Type.IsValueType then
il.Emit(OpCodes.Call, methodInfo)
else
il.Emit(OpCodes.Callvirt, methodInfo)
...
I think that the bit of documentation that you quoted at the end of the question is the source of the problem. I’m not quite sure what the
OpCodes.Constrainedprefix is for (I don’t understand the documentation better than you), but I tried looking how it is used by Microsoft :-).Here is a snippet from source code of Dynamic Language Runtime that emits a method call:
I think you’ll probably want to follow their behavior – it seems that the
constrainedprefix is only used for virtual calls on value types. My interpretation is that for value types, you know what is the actual type, so you don’t need actual (unconstrained) virtual call.