<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      ValueTpye boxing及虛方法重寫及CallVirt指令實現解析

      問題的提出,是源自Justin提出的一個case里面的一個問題,討論了n久沒得到一個答案,昨天justin周一早上一起來就又回憶起了這個問題,看來一直把這個問題放在腦子里面沒有放下,佩服啊佩服 ^_^ 遂決定深入研究一番,下面是問題的提出:

       

      Boxed value type

      In C#, the value type instance having pure user data is resided at stack without any type pointer. In some case, the value need be boxed, then a new boxed object is created from heap. My questions are:

      l         Assume the value type overrides an virtual function, such as ToString(). When call the function using value instance, the this points to the pure user data part of value instance in stack, however, if calling function using boxed value object, the this should point to the beginning of heap object (the type pointer, four bytes before pure user data). How does the ToString() implementation of value type distinguish between the two cases?

      From debug of assembly instruction, I find the ToString() function always gets this pointing to the pure user data part even if it’s called from boxed object. But I have no idea which code move the this pointer forward four bytes?

       

             首先,對于什么是值類型啥是引用類型,及其區別,以及派生結構層次,不屬于這里的話題,探討主要圍繞針對上面的這個問題的提出展開。

             可以定義一個Struct的類型,來實現對ValueType的繼承。這個從Struct繼承出來的ValueType,還可以重寫從基類繼承的方法,一共只有三個方法可供重寫:Equals(),

      GetHashCode(),ToString()

             好吧,看看上面的問題,首先解釋下上面的問題提出之前的一段文字:

       

             Assume the value type overrides an virtual function, such as ToString(). When call the function using value instance, the this points to the pure user data part of value instance in stack, however, if calling function using boxed value object, the this should point to the beginning of heap object (the type pointer, four bytes before pure user data).

            

             厄,首先明確一下,這段話是沒有任何問題的。我也曾懷疑過這句話里面的細微的地方的觀點,但是事實證明我是錯誤的…..

             來構造一個所提出問題的案例先:

             class Program

          {

              static void Main(string[] args)

              {

                  TestValueType testValueType = new TestValueType(1214926);

                  Console.WriteLine(testValueType.ToString());

       

                  Object o = testValueType;

                  Console.WriteLine(o.ToString());

       

                  int i = 12345;

                  Console.WriteLine(i.ToString());

       

                  Console.ReadLine();

              }

       

              internal struct TestValueType

              {

                  private int i;

                  public TestValueType(int initValue)

                  {

                      i = initValue;

                  }

       

                  //Can only override 3 Methods:Equals(),GetHashCode(),ToString()

                  public override string ToString()

                  {

                      return "Valuetype virtual method tostring() Override.";

                  }

              }

          }

            

             對于提出的第一個問題:

       

      How does the ToString() implementation of value type distinguish between the two cases?

       

             我想,這個地方不僅僅是two case,而是有四種case

      1.         一個自定義的值類型沒有重寫tostring()方法的時候,對tostring的調用是如何實現的?tostring()方法的實現是存儲在什么地方的?

      2.         一個自定義的值類型overridevirtualbase classtostring方法之后,對tostring的調用是如何進行的?這個tostring的實現是存放在什么地方的?也就是調用的哪個實現,如何調用的問題。

      3.         一個boxed了的自定義的值類型的boxed狀態下,沒有重寫tostring方法的時候,這個tostring是如何調用的?

      4.         上面的一個問題,一個重寫了tostring方法的自定義valuetypeboxed狀態下面,tostring的調用是如何實現的,存放在哪兒。

       

      Ok,這個是回答他的問題的序言,當然,不是全部,還有Managed PointerInstance Pointerthis指針的關系callvirt的具體細節和幾個il指令背著我做的小動作問題..

            

             好吧,解決這些問題,先從il語言入手,下面是Main方法的反編譯之后的il代碼:

       

      .method private hidebysig static void  Main(string[] args) cil managed

      {

        .entrypoint

        // Code size       78 (0x4e)

        .maxstack  2

        .locals init ([0] valuetype ValutTypeTest.Program/TestValueType

      testValueType,

                 [1] object o,

                 [2] int32 i)

        IL_0000:  nop

        IL_0001:  ldloca.s   testValueType

        IL_0003:  ldc.i4     0x1289ce

        IL_0008:  call       instance void

      ValutTypeTest.Program/TestValueType::.ctor(int32)

        IL_000d:  nop

        IL_000e:  ldloca.s   testValueType

        IL_0010:  constrained. ValutTypeTest.Program/TestValueType

        IL_0016:  callvirt   instance string [mscorlib]System.Object::ToString()

        IL_001b:  call       void [mscorlib]System.Console::WriteLine(string)

        IL_0020:  nop

        IL_0021:  ldloc.0

        IL_0022:  box        ValutTypeTest.Program/TestValueType

        IL_0027:  stloc.1

        IL_0028:  ldloc.1

        IL_0029:  callvirt   instance string [mscorlib]System.Object::ToString()

        IL_002e:  call       void [mscorlib]System.Console::WriteLine(string)

        IL_0033:  nop

        IL_0034:  ldc.i4     0x3039

        IL_0039:  stloc.2

        IL_003a:  ldloca.s   i

        IL_003c:  call       instance string [mscorlib]System.Int32::ToString()

        IL_0041:  call       void [mscorlib]System.Console::WriteLine(string)

        IL_0046:  nop

        IL_0047:  call       string [mscorlib]System.Console::ReadLine()

        IL_004c:  pop

        IL_004d:  ret

      } // end of method Program::Main

       

      首先說說幾個pointer的關系。

      對于一個放在計算堆棧里面的value type或者是自定義value type來說,如果想要使用這個類型里面的fields或者是members,需要提供Managed Pointer。也就是&valuetypename的值。在C++里面,沒有類似的Dotnet的值類型之內的概念,所以,這里的Managed Pointer就相當于一個objectinstance Pointer,或者說是instance reference。對于Value TypeManaged Pointer,指向的是計算stack上面的data part。而對于一個對象來說,this pointerinstance Pointer)指向的是Obj ref,一個四個字節的地方。這個地方存放的數據,指向的是MethodTable。這四個字節的上面,是控制同步的一個塊和一個Object Header,這四個字節的下面,就是instance fields,包括一些fields和一些member方法的實現。

             This指針在對value type的時候,指向的是stack上面的數據部分。

       

             對于il的一個call方法指令,不管是value type,還是ref type,有的時候是需要指針的,有的時候不需要this指針。Metadata里面并不包含this指針,但是方法的簽名,使用一個叫做HASTHISbit位來標識是否需要this指針:Signature: If the method is static, then the HASTHIS (0x20) bit in the calling convention shall be 0”。具體說來,如果一個方法的實現,是保持在type相關的內存中的,就不要這個標識位,如果每個typeinstance保存一份實現,就用這個標識。區別這兩種情況,可以用C#里面的Stact關鍵字。

            

             對于:

      Object o = testValueType;

      Console.WriteLine(o.ToString());

       

      Il代碼是:

       

      IL_000e:  ldloca.s   testValueType

      IL_0010:  constrained. ValutTypeTest.Program/TestValueType

      IL_0016:  callvirt   instance string [mscorlib]System.Object::ToString()

      IL_001b:  call       void [mscorlib]System.Console::WriteLine(string)

            

             Constrained前綴修飾符,是一個非常有意思的修飾符,他必須和callvirt關鍵字一起使用,首先看起Stack轉換:

       

      constrained. thisType(這里是ValutTypeTest.Program/TestValueType

       

      Stack Transition:

      之前:…, ptr, arg1, … argN ?    之后:…, ptr, arg1, … argN

       

      Ms沒有什么變換,但是PtrManaged Pointer)的內容,卻是有一個轉換邏輯:

      l         If thisType is a reference type (as opposed to a value type) thenptr is dereferenced and passed as the ‘this’ pointer to the callvirt of method

      l         If thisType is a value type and thisType implements method thenptr is passed unmodified as the ‘this’ pointer to a call of method implemented by thisType  

      l         If thisType is a value type and thisType does not implement method thenptr is dereferenced, boxed, and passed as the ‘this’ pointer to the callvirt of method

       

      這樣,就得到了問題的回答

      對于一個value type(或者自定義的value type),仍然可以調用由類型繼承或者是從重寫的虛方法。比如EqualsGetHashCode,或者Tostring,因為CLR正好可以以非虛的方式調用這些方法。

      如果重寫了一個自定義的value type類型的tostring方法,首先,this pointermanaged Pointer)指向的是stack上面的這個變量的data field,數據開始部分。然后在調用tostring方法的時候,首先讓constrained來檢驗下,接著發現了這個自定義的value type實現了tostring這個方法,好,這個時候不執行boxing的動作,直接采用il指令里面的call指令,然后直接調用stack上面的這個value typedata filed之后的tostring方法。

      這種情況下,是沒有MethodTable,不和MethodTable進行交互。很重要的一點,值類型隱式為sealed類型,所以不可將一個值類型做為另外一個類型的基類使用。

      如果自定義的value type類型,沒有實現了tostring方法,這個時候,constrained前綴的處理邏輯是根據“ldloca.s testValueType”剛剛放到stack上面的value typemanaged pointer,先把這個value type boxing,裝箱,然后把在heap上面新創建的objectref,替換掉這個ptrvalue typemanaged pointer),(很重要的一點:這個時候,this指針指向的是obj ref。這個時候,就換成了使用callvirt指令調用。這個時候,ptr上面的內容,就是object refheap上面的value typeinstace fields的前四個字節的地方。這個obj ref里面的內容,指向的就是MethodTable。這個時候調用virtual方法,需要通過Vtable map,來具體定位到使用那個基類的方法。

      尋找的流程是先看看當前的instance里面實現了相同簽名的方法沒,如果沒有,就找基類或者父類里面的相同簽名的。

       

      如果,calling function using boxed value object,這個時候是如何實現的呢?還是上面的C#代碼,當如下面操作的時候:

      Object o = testValueType;

      Console.WriteLine(o.ToString());

       

      不管這個時候testValueType實現沒實現tostring方法,這個時候,想當于直接調用一個Object Type的某個方法,是通過走MethodTable來尋找其實現的。

      這種情況下,無論如何,在剛剛準備執行這個方法之前,this指針的內容,是一個Ref Typeinstance fields的開始的部分。而具體的使用哪個方法,則是根據MethodTable來的。

       

      另外一點需要了解,CLR的特性里面,提供可以以非虛的方式調用從類型或者基類繼承的方法。而且System.ValueType重寫了這些虛方法,由struct定義的自定義value type沒重寫這些方法。

      了解了這點,如何還有疑惑的話,可以看下下面這個有助于理解的sample

      .class public value XXX

      {

      .method public void YYY( )

      {

      ...

      }

      .method public virtual void ZZZ( )

      {

      ...

      }

      }

      .method public static void Exec( )

      {

      .entrypoint

      .locals init(valuetype XXX xxx)  // Variable xxx is an Instance of XXX

      ldloca xxx                               // Load managed ptr to xxx

      call instance void XXX::YYY( )  // Legal: access to value

      // type member

      // by managed ptr

      ldloca xxx

      callvirt instance void XXX::ZZZ( ) // Illegal: virtual call of

      // methods possible only

      // by object reference.

      ldloca xxx

      call instance void XXX::ZZZ( )  // Legal: nonvirtual call, access to value type member

      // by managed ptr.

      ldloc xxx                                  // Load instance of XXX.

      box valuetype XXX                    // Convert it to object reference.

      callvirt instance void XXX::ZZZ( ) // Legal

      ...

      }

       

       

      這時,就涉及到文章最開始提出的第二個問題

       

      From debug of assembly instruction, I find the ToString() function always gets this pointing to the pure user data part even if it’s called from boxed object. But I have no idea which code move the this pointer forward four bytes?

       

      這里,有一個比較重要的特性,也是callvirt指令來實現的,在具體調用每個方法開始的時候之前,callvirt實現了一個justin和問題的提出者,叫做“this指針偏移”的功能。當然,文章到此為止,還沒有證明這一點。

      這個解釋邏輯,也是參考了大量的資料之后得到的一個假設吧。Callvirt,在執行的時候,有很多種不同的情況下都可以調用callvirt,譬如interface,虛方法,多態等等,callvirt會進行一個判斷,來判斷具體是哪種情況。如果是我們上面的對boxed value type的情況,就有一個this指針偏移的處理邏輯。

       

      為了證明這點,咱可以參考Rotor是如何實現callvirt方法的:

      首先查看Fjit.cpp的實現,這個頁面有萬把行,實現了大部分IL指令具體做了些啥。

       

      switch (opcode)

      {

      case CEE_CALLVIRT:

      JitResult = compileCEE_CALLVIRT();

      break;

      }

       

      好吧,查看compileCEE_CALLVIRT的實現:

       

      FJitResult FJit::compileHelperCEE_CALLVIRT(unsigned int token,

                                    bool isReadOnly /* = false */)

      {

          jitInfo->getCallInfo(methodInfo->ftn,

                               tokenScope,

                               token,

                               0, // constraintToken -                                                            

                               tokenContext,

                               CORINFO_CALLINFO_CALLVIRT,

                               &virtCallInfo);

       

          if (virtCallInfo.kind == CORINFO_VIRTUALCALL_LDVIRTFTN)

          {

             int this_ptr = findOffsetOfThisPtr(targetSigInfo);

             emit_getSP((STACK_BUFFER + this_ptr - SIZE_STACK_SLOT));

             emit_LDIND_I4(false);

             emit_ldvirtftn_helper(token, jitInfo->getMemberParent(methodInfo->scope, token));

             emit_save_TOS();        // squirel away the target ftn address

             emit_POP_PTR();         // and remove from stack

          }

       

          argBytes = buildCall(&targetSigInfo, CALL_NONE, stackPadorRetBase, false);

       

          sizeRetBuff = targetSigInfo.hasRetBuffArg() ? typeSizeInBytes(jitInfo, targetSigInfo.retTypeClass) : 0;

       

          _ASSERTE (virtCallInfo.kind != CORINFO_CALL_CODE_POINTER);

       

          if (virtCallInfo.kind == CORINFO_VIRTUALCALL_LDVIRTFTN)

          {

              emit_restore_TOS(); //push the saved target ftn address

       

              // Now we can use the sequence for CALLI.

              emit_calli(targetSigInfo.hasRetBuffArg() ? typeSizeInBytes(jitInfo,

      targetSigInfo.retTypeClass) : 0);

          }

          else if (virtCallInfo.kind == CORINFO_VIRTUALCALL_STUB)

          {

              _ASSERTE (!virtCallInfo.stubLookup.lookupKind.needsRuntimeLookup);

              _ASSERTE (virtCallInfo.stubLookup.constLookup.addr != NULL);

              _ASSERTE(virtCallInfo.stubLookup.constLookup.accessType == IAT_PVALUE);

              emit_call_stub((unsigned int) virtCallInfo.stubLookup.constLookup.addr);

          }

          else if (virtCallInfo.kind == CORINFO_CALL)

          {

              if (virtCallInfo.nullInstanceCheck)

              {

                  emit_check_null_reference(false);

              }

       

              CORINFO_CONST_LOOKUP addrInfo;

              jitInfo->getFunctionEntryPoint(targetMethod, IAT_VALUE, &addrInfo);

              VALIDITY_CHECK(addrInfo.addr);

              VALIDITY_CHECK(addrInfo.accessType == IAT_VALUE ||

      addrInfo.accessType == IAT_PVALUE);

       

              emit_callnonvirt((unsigned)addrInfo.addr, sizeRetBuff, addrInfo.accessType == IAT_PVALUE);

          }

          else if (virtCallInfo.kind == CORINFO_VIRTUALCALL_VTABLE)

          {

              if (jitInfo->getClassAttribs(targetClass,methodInfo->ftn) &

      CORINFO_FLG_INTERFACE)

              {

                  offset = jitInfo->getMethodVTableOffset(targetMethod);

                  _ASSERTE(!(methodAttribs & CORINFO_FLG_EnC));

                  unsigned InterfaceTableOffset;

                  InterfaceTableOffset = jitInfo->getInterfaceTableOffset(targetClass);

                  emit_callinterface_new(InterfaceTableOffset*4,

                      offset, sizeRetBuff );

              }

              else

              {

       

                  offset = jitInfo->getMethodVTableOffset(targetMethod);

                  _ASSERTE(!(methodAttribs & CORINFO_FLG_DELEGATE_INVOKE));

                  emit_callvirt(offset, sizeRetBuff);

              }

          }

      }

       

      這里只截取了最后的一段,前面的完整性檢查之內的略掉。virtCallInfo,看到這樣的結構,字眼和判斷,和咱估計的情況差不多。然后來查看CORINFO_CALL_KIND這個結構體的定義:

       

      enum CORINFO_CALL_KIND

      {

          CORINFO_CALL,

             //下面的兩個CallVirt指令里面沒用

          CORINFO_CALL_CODE_POINTER,

      CORINFO_VIRTUALCALL_RESOLVED,

       

          CORINFO_VIRTUALCALL_STUB,

          CORINFO_VIRTUALCALL_LDVIRTFTN,

          CORINFO_VIRTUALCALL_VTABLE

      };

       

      轉到corinfo.h文件里面,getCallInfo and CORINFO_CALL_INFO,這兩個東西是EE用來指示Fjit如何具體編譯不同情況下面的callvirt指令。

      查看都是什么情況下使用不同的結構體,發現了

      CORINFO_VIRTUALCALL_LDVIRTFTN這種情況下對this指針偏移的支持:

            

             //找到this指針的地址

             int this_ptr = findOffsetOfThisPtr(targetSigInfo);

             //減去四個字節指向到instance fields

             // #define SIZE_STACK_SLOT      4     

      emit_getSP((STACK_BUFFER + this_ptr - SIZE_STACK_SLOT));

      emit_LDIND_I4(false);

      emit_ldvirtftn_helper(token, jitInfo->getMemberParent(methodInfo->scope, token));

      emit_save_TOS();        // squirel away the target ftn address

      emit_POP_PTR();         // and remove from stack

       

      emit_getSP方法最終指向了xp平臺下x86fjit.h的實現:

      // push a pointer pointing 'n' bytes back in the stack

      #define x86_getSP(n)                                                                                      deregisterTOS;                                                                                                 

             if (n == 0)                                                                                                

                 x86_mov_reg(x86DirTo, x86Big, x86_mod_reg(X86_EAX, X86_ESP));  

             else                                                                                                          

                    x86_lea(x86_mod_base_scale_disp(X86_EAX, X86_ESP,

      X86_NO_IDX_REG, n, 0));  

      inRegTOS = true;

       

             最后,再說下,再看這些il指令的實現的時候,發現constrained的實現比較有意思,也就是基于上面給出的三種判斷邏輯情況下,使用了一個結構體:

       

      enum CORINFO_THIS_TRANSFORM

      {

          CORINFO_NO_THIS_TRANSFORM,

          CORINFO_BOX_THIS,

          CORINFO_DEREF_THIS

      };

             來支持三種不同情況下面是this指針的轉換方式:

          switch (callInfo.thisTransform)

          {

          case CORINFO_NO_THIS_TRANSFORM:

              {

                           //不需要改變this指針(managed Pointerptr)的情況下直接調用call指令

                  return this->compileHelperCEE_CALL(funcToken,

      callInfo.targetMethodHandle,false /*readonly*/);

              }

      //根據managed ptr的內容來裝箱(If thisType is a value type and thisType implements //method)

          case CORINFO_BOX_THIS:

              {

                  CORINFO_SIG_INFO targetSigInfo;

                  jitInfo->getMethodSig(callInfo.targetMethodHandle, &targetSigInfo);

                  // this is slightly ineffecient, especially when dealing with large

                  // valuetypes but effeciency is not paramount in fjit

       

                  // {... , objPtr, args} -> {..., objPtr, args, objPtr }

                  copyPtrUpAroundArgs(targetSigInfo);

       

                  // {..., objPtr, args, objPtr } -> {..., objPtr, args, *objPtr }

                  if( (retval = this->compileHelperCEE_LDOBJ(constraintToken)) != FJIT_OK)

                      return retval;

       

                  // {..., objPtr, args, *objPtr } -> {..., objPtr, args, boxedPtr }

                  if( (retval = this->compileHelperCEE_BOX(constraintToken)) != FJIT_OK)

                      return retval;

       

                  // {..., objPtr, args, boxedPtr } -> {... , boxedPtr, args}

                  copyPtrDownAroundArgs(targetSigInfo);

       

                  return this->compileHelperCEE_CALLVIRT(funcToken);

              }

             //最后一種情況(If thisType is a value type and thisType does not implement method),直接

      //調用CALLVIRT

          case CORINFO_DEREF_THIS:

              {

                  CORINFO_SIG_INFO targetSigInfo;

                  jitInfo->getMethodSig(callInfo.targetMethodHandle, &targetSigInfo);

       

                  // {... , &this, args} -> {..., &this, args, &this }

                  copyPtrUpAroundArgs(targetSigInfo);

       

                  // it was a reference type

                  if( (retval = this->compileCEE_LDIND_REF()) != FJIT_OK)

                      return retval;

       

                  // {... , &this, args, this} -> {..., this, args}

                  copyPtrDownAroundArgs(targetSigInfo);

       

                  return this->compileHelperCEE_CALLVIRT(funcToken);

              }

       

             文章寫的匆忙,很多看資料的時候看到的細節可能忘記了沒寫上。歡迎大伙討論

             有寫的不正確的地方,歡迎指正。^_^

       

             6/24/2008 4:57:24 PM  lbq1221119

      posted on 2008-06-24 16:59  lbq1221119  閱讀(2389)  評論(15)    收藏  舉報

      導航

      主站蜘蛛池模板: 欧美一区二区三区欧美日韩亚洲| 韩国三级网一区二区三区| 少妇被黑人到高潮喷出白浆| 亚洲成av人片无码天堂下载| 国产成人午夜精品福利| 国产麻豆剧果冻传媒一区| 一本色道久久—综合亚洲| 久久综合给合久久狠狠97色| 亚洲人成人网站色www| 无码人妻精品一区二区在线视频 | 蜜桃av亚洲精品一区二区| 大胸美女吃奶爽死视频| 年轻女教师hd中字3| 亚洲国产精品久久久久秋霞影院| 国产极品视频一区二区三区 | 午夜精品福利亚洲国产| 国产亚洲av夜间福利香蕉149| 欧美日韩一区二区综合| 久久国产精品伊人青青草| 久久人人97超碰人人澡爱香蕉| 无码人妻久久一区二区三区app| 桃园市| 国产精品不卡一区二区三区| 国产精品中文字幕二区| 日本一区二区三区专线| 色欲国产精品一区成人精品| 色综合久久久久综合99| 久久精品国产清自在天天线| 亚洲男人av香蕉爽爽爽爽| 久久三级国内外久久三级| 日韩精品中文字幕亚洲| 国产首页一区二区不卡| 久久国产精品不只是精品| 亚洲综合精品第一页| 风韵丰满妇啪啪区老老熟女杏吧| 亚洲人成在线播放网站| 麻豆亚洲精品一区二区| 风韵丰满妇啪啪区老老熟女杏吧 | 狠狠亚洲色一日本高清色| 国产精品免费视频不卡| 亚洲国产一区二区三区亚瑟|