У меня есть рекурсивная функция emit : Map<string,LocalBuilder> -> exp -> unit
, где il : ILGenerator
является глобальным для функции, а exp
является дискриминантным объединением, представляющим анализируемый язык с проверкой типов с регистром InstanceCall of exp * MethodInfo * exp list * Type
, а Type
является свойством exp
, представляющим тип выражения.
В следующем фрагменте я пытаюсь создать коды операций IL для вызова экземпляра, где instance.Type
может быть или не быть ValueType
. Итак, я понимаю, что могу использовать OpCodes.Constrained
гибко и эффективно выполнять виртуальные вызовы типов ссылок, значений и перечислений. Я новичок в Reflection.Emit и машинных языках в целом, поэтому понимание связанной документации для OpCodes.Constrained
для меня не сильно.
Вот моя попытка, но она приводит к VerificationException
, «Операция может дестабилизировать среду выполнения»:
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)
...
Глядя на документы, я думаю, что ключ может быть «Управляемый указатель, ptr, помещается в стек. Тип ptr должен быть управляемым указателем (&) на thisType. Обратите внимание, что это отличается от случая без префикса callvirt, которая ожидает ссылку этого типа».
Обновить
Спасибо @Tomas и @desco, теперь я понимаю, когда использовать OpCodes.Constrained
(instance.Type
— это ValueType, а methodInfo.DeclaringType
— это ссылочный тип).
Но оказалось, что мне пока не нужно рассматривать этот случай, и моей настоящей проблемой был аргумент экземпляра в стеке: мне потребовалось всего 6 часов, чтобы узнать, что ему нужен адрес вместо значения (смотря на источник DLR код дал мне подсказки, а затем использование ilasm.exe в простой программе C # прояснило это).
Вот мой окончательный рабочий вариант:
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)
...