.net 如何发出操作码。受限于操作码。Callvirt给定我手头有所需的MethodInfo和实例类型

.net 如何发出操作码。受限于操作码。Callvirt给定我手头有所需的MethodInfo和实例类型,.net,f#,reflection.emit,il,.net,F#,Reflection.emit,Il,我有一个递归函数emit:Map->exp->unit,其中il:ILGenerator是该函数的全局函数,exp是一个判别式并集,表示一种经过类型检查的解析语言,其大小写为exp表示表达式的类型 在下面的片段中,我试图为一个实例调用发出IL操作码,其中instance.Type可能是也可能不是ValueType。所以我知道我可以灵活有效地对引用、值和枚举类型进行虚拟调用。我对Reflection.Emit和机器语言都是新手,所以理解操作码.Constrained的链接文档对我来说不是很强 这是

我有一个递归函数
emit:Map->exp->unit
,其中
il:ILGenerator
是该函数的全局函数,
exp
是一个判别式并集,表示一种经过类型检查的解析语言,其大小写为
exp
表示表达式的类型

在下面的片段中,我试图为一个实例调用发出IL操作码,其中
instance.Type
可能是也可能不是
ValueType
。所以我知道我可以灵活有效地对引用、值和枚举类型进行虚拟调用。我对Reflection.Emit和机器语言都是新手,所以理解
操作码.Constrained的链接文档对我来说不是很强

这是我的尝试,但它导致了一个
验证异常
,“操作可能会破坏运行时的稳定性。”

查看文档,我认为关键可能是“一个托管指针ptr被推到堆栈上。ptr的类型必须是指向此类型的托管指针(&)。请注意,这与不固定的callvirt指令不同,后者需要此类型的引用。”

更新

谢谢@Tomas和@desco,我现在知道什么时候使用
opcode.constrated
instance.Type
是一个ValueType,但是
methodInfo.DeclaringType
是一个参考类型)


但是我现在还不需要考虑这个问题,我真正的问题是堆栈上的实例参数:它只花了我6个小时来学习它需要一个地址而不是值(查看DLR源代码给了我线索,然后在一个简单的C语言程序上使用ILAS.EXE使它变得清晰)。 这是我的最终工作版本:

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)
        ...

我认为你在问题末尾引用的一些文档是问题的根源。我不太确定
OpCodes.constrated
前缀的作用(我对文档的理解不如您),但我尝试查看了Microsoft是如何使用它的:-)

以下是中发出方法调用的代码段:

// Emit arguments
List<WriteBack> wb = EmitArguments(mi, args);

// Emit the actual call
OpCode callOp = UseVirtual(mi) ? OpCodes.Callvirt : OpCodes.Call;
if (callOp == OpCodes.Callvirt && objectType.IsValueType) {
    // This automatically boxes value types if necessary.
    _ilg.Emit(OpCodes.Constrained, objectType);
}
// The method call can be a tail call if [...]
if ((flags & CompilationFlags.EmitAsTailCallMask) == CompilationFlags.EmitAsTail && 
    !MethodHasByRefParameter(mi)) {
    _ilg.Emit(OpCodes.Tailcall);
}
if (mi.CallingConvention == CallingConventions.VarArgs) {
    _ilg.EmitCall(callOp, mi, args.Map(a => a.Type));
} else {
    _ilg.Emit(callOp, mi);
}

// Emit writebacks for properties passed as "ref" arguments
EmitWriteBack(wb);
//发出参数
列出wb=EmitArguments(mi,args);
//发出实际调用
操作码callOp=UseVirtual(mi)?操作码.Callvirt:opcode.Call;
if(callOp==OpCodes.Callvirt&&objectType.IsValueType){
//如有必要,此操作会自动对值类型进行装箱。
_ilg.Emit(操作码.constrated,objectType);
}
//方法调用可以是尾部调用,如果[…]
if((flags&compliationflags.EmitAsTailCallMask)==compliationflags.EmitAsTail&&
!MethodHasByRefParameter(mi)){
_ilg.Emit(操作码.Tailcall);
}
if(mi.CallingConvention==CallingConventions.VarArgs){
_EmitCall(callOp、mi、args.Map(a=>a.Type));
}否则{
_ilg.Emit(callOp,mi);
}
//为作为“ref”参数传递的属性发出写回
写回(wb);

我认为您可能希望了解他们的行为-似乎
constrated
前缀仅用于对值类型的虚拟调用。我的解释是,对于值类型,您知道什么是实际类型,因此不需要实际(无约束)的虚拟调用。

基本上我同意Tomas的观点:如果您在编译时知道确切的类型,那么您可以自己发出正确的调用指令。约束前缀通常用于泛型代码

但文件也指出:

受约束的操作码允许IL编译器以统一的方式调用虚拟函数,而与ptr是值类型还是引用类型无关。虽然它适用于thisType是泛型类型变量的情况,但约束前缀也适用于非泛型类型,并可以降低在隐藏值类型和引用类型之间的区别的语言中生成虚拟调用的复杂性。

使用受限前缀还可以避免值类型的潜在版本控制问题。如果未使用受约束前缀,则必须根据值类型是否覆盖System.Object的方法发出不同的IL。例如,如果值类型V重写Object.ToString()方法,则会发出调用V.ToString()指令;否则,将发出一条box指令和一条callvirt Object.ToString()指令。在前一种情况下,如果稍后删除覆盖,则可能会出现版本控制问题;在后一种情况下,如果稍后添加覆盖,则可能会出现版本控制问题

小演示(我真丢脸,我的上网本上没有F#):


但是,我还不需要考虑这个问题,我真正的问题是堆栈上的实例论证:它只花了我6个小时来学习它需要一个地址而不是“我现在真的笑得很厉害”,因为每一个进入写作IL的人都会碰到这个问题,并对错误进行了长时间的精确搜索。他们中的许多人也把他们的问题发到。。。像我一样!
// Emit arguments
List<WriteBack> wb = EmitArguments(mi, args);

// Emit the actual call
OpCode callOp = UseVirtual(mi) ? OpCodes.Callvirt : OpCodes.Call;
if (callOp == OpCodes.Callvirt && objectType.IsValueType) {
    // This automatically boxes value types if necessary.
    _ilg.Emit(OpCodes.Constrained, objectType);
}
// The method call can be a tail call if [...]
if ((flags & CompilationFlags.EmitAsTailCallMask) == CompilationFlags.EmitAsTail && 
    !MethodHasByRefParameter(mi)) {
    _ilg.Emit(OpCodes.Tailcall);
}
if (mi.CallingConvention == CallingConventions.VarArgs) {
    _ilg.EmitCall(callOp, mi, args.Map(a => a.Type));
} else {
    _ilg.Emit(callOp, mi);
}

// Emit writebacks for properties passed as "ref" arguments
EmitWriteBack(wb);
using System;
using System.Reflection;
using System.Reflection.Emit;

public struct EvilMutableStruct
{
    int i;
    public override string ToString()
    {
            i++;
            return i.ToString();
    }
}

class Program
{
    public static void Main()
    {
            var intToString = Make<int>();
            var stringToString = Make<string>();
            var structToString = Make<EvilMutableStruct>();
            Console.WriteLine(intToString(5));
            Console.WriteLine(stringToString("!!!"));   
            Console.WriteLine(structToString (new EvilMutableStruct())); 
    }

    static MethodInfo ToStringMethod = new Func<string>(new object().ToString).Method;
    static MethodInfo ConcatMethod = new Func<string, string, string>(String.Concat).Method;

    // x => x.ToString() + x.ToString()
    private static Func<T, string> Make<T>()
    {
            var dynamicMethod = new DynamicMethod("ToString", typeof(string), new[] {typeof(T)});
            var il = dynamicMethod.GetILGenerator();

            il.Emit(OpCodes.Ldarga_S, 0);
            il.Emit(OpCodes.Constrained, typeof(T));
            il.Emit(OpCodes.Callvirt, ToStringMethod);

            il.Emit(OpCodes.Ldarga_S, 0);
            il.Emit(OpCodes.Constrained, typeof(T));
            il.Emit(OpCodes.Callvirt, ToStringMethod);

            il.Emit(OpCodes.Call, ConcatMethod);

            il.Emit(OpCodes.Ret);
            return (Func<T, string>)dynamicMethod.CreateDelegate(typeof(Func<T, string>));
     }
}
55
!!!!!!
12