2011-07-30 1 views
5

에 필요한 MethodInfo 및 인스턴스 유형을 제공 OpCodes.Callvirt와 OpCodes.Constrained을 방출하는 방법 il : ILGenerator 함수에 글로벌과 exp는 형식 검사 구문 분석 된 언어를 나타내는 판별 조합입니다 emit : Map<string,LocalBuilder> -> exp -> unit 대소 문자가 InstanceCall of exp * MethodInfo * exp list * Type이고 Type 인 경우는 표현식의 유형을 나타내는 exp의 속성입니다.내가 재귀 함수를 가지고 손

다음 코드에서 instance.TypeValueType 일 수도 있고 아닐 수도있는 인스턴스 호출에 대해 IL 연산 코드를 내보내려고합니다. 그래서 OpCodes.Constrained을 사용하여 참조, 값 및 열거 형에 대해 유연하고 효율적으로 가상 호출을 수행 할 수 있음을 알고 있습니다. Reflection에 익숙하지 않습니다. 일반적으로 기계 언어와 언어를 삭제하면 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) 
    ... 

이 문서를 보면, 내가 키가 될 수있다 생각한다 "A는, 포인터, PTR 관리 ptr의 유형은 thisType에 대한 관리 포인터 (&) 여야합니다.이 유형의 참조를 기대하는 접두어가없는 callvirt 명령어의 경우와는 다릅니다. "

업데이트는 당신에게 @Tomas 감사 OpCodes.Constrained를 사용하는 경우 @desco, 지금 (instance.Type이 치 형이지만, 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) 
     ... 

답변

1

:

은 여기 내 최종 작업 버전입니다. 나는 OpCodes.Constrained 접두사가 무엇인지 (나는 당신보다 더 잘 문서를 이해하지 못한다)는 모르겠다. 그러나 나는 마이크로 소프트에 의해 어떻게 사용되는지 살펴 보았다 :-). 여기

는 메서드 호출 방출 source code of Dynamic Language Runtime에서 미리보기입니다 : 당신은 아마 그들의 행동을 따라 할 수있을 거라 생각

// 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 접두사 만 값 형식의 가상 호출에 사용되는 것 같다 . 내 해석은 값 유형에 대해 실제 유형이 무엇인지 알기 때문에 실제 (제한되지 않은) 가상 호출이 필요하지 않습니다.

2

기본적으로 Tomas에 동의합니다. 컴파일 타임에 정확한 유형을 알고 있으면 정확한 호출 명령을 직접 내릴 수 있습니다. 제약 접두사는 일반적으로 일반적인 코드

사용하지만 문서는 말한다되어 값 형식입니다

제한된 연산 코드는 IL 컴파일러가 있는지 여부 PTR의 독립적 인 균일 한 방식으로 가상 함수를 호출 할 수 있습니다 또는 참조 유형.thisTypeType이 제네릭 형식 변수 인 경우에도 사용되지만 제한적 접두사는 비 일반 형식에서도 작동하며 값 형식과 참조 형식 간의 구분을 숨기는 언어로 가상 호출을 생성하는 복잡성을 줄일 수 있습니다. ...

제한된 접두어를 사용하면 값 유형에 잠재적 인 버전 문제가 발생하지 않습니다. 제한된 접두사가 사용되지 않으면 값 형식이 System.Object의 메서드를 재정의하는지 여부에 따라 다른 IL을 내 보내야합니다. 예를 들어, 값 형식 V가 Object.ToString() 메서드를 재정의하는 경우 V.ToString() 호출 명령이 방출됩니다. 그렇지 않으면 상자 명령과 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