5

我有一个递归函数emit : Map<string,LocalBuilder> -> exp -> unitil : ILGenerator它是函数的全局函数,exp是一个判别联合,表示带大小写的类型检查解析语言,InstanceCall of exp * MethodInfo * exp list * Type并且Typeexp表示表达式类型的属性。

在下面的片段中,我试图为实例调用发出 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 指令,它需要 thisType 的引用。”

更新

谢谢@Tomas 和@desco,我现在明白什么时候使用OpCodes.Constrainedinstance.Type是ValueType,但是methodInfo.DeclaringType是引用类型)。

但事实证明我还不需要考虑这种情况,我真正的问题是堆栈上的实例参数:我只花了 6 个小时就知道它需要一个地址而不是值(查看 DLR 源代码给了我线索,然后在一个简单的 C# 程序上使用 ilasm.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)
        ...
4

2 回答 2

3

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

但文件还说:

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

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

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

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
于 2011-07-30T16:44:51.680 回答
1

我认为您在问题末尾引用的那段文档是问题的根源。我不太确定OpCodes.Constrained前缀是什么(我不比你更了解文档),但我试着看看微软是如何使用它的:-)。

下面是一个来自动态语言运行时源代码的片段,它发出一个方法调用:

// 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);

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

于 2011-07-30T16:04:53.010 回答