.NET9 Pre4 UnsafeAccessor泛型(二)

点击上方蓝字 江湖评谈设为关注/星标

760efb1734403960485acd64da83514a.png

前言

探寻下.NET9 Pre4如何通过UnsafeAccessorAttribute特性访问private字段,这里面有很多细节,本篇来看下这些细节。上一篇:.NET9 Pre4 UnsafeAccessor泛型

分析

例子承接上篇

public class Class<T>
{
   private T _field;
   private void M<U>(T t, U u) { Console.WriteLine(t);Console.WriteLine(u); }
}
class Accessors<V>
{
   [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_field")]
   public extern static ref V GetSetPrivateField(Class<V> c);


   [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M")]
   public extern static void CallM<W>(Class<V> c, V v, W w);
}

GetSetPrivateField的JIT HIR如下:

STMT00000 ( 0x000[E-] ... ??? )
               [000002] ---X-------      *  RETURN    byref
               [000001] ---X-------          \--*  FIELD_ADDR byref  ConsoleApp1.Program+Class`1[int]:_field
               [000000] -----------             \--*  LCL_VAR   ref    V00 arg0

那么它的伪代码即是:

return  (new Class<T>)._field;

然则它的JIT Import则是:

IL to import:
IL_0000  02                ldarg.0
IL_0001  7c 01 00 00 0a    ldflda       0xA000001
IL_0006  2a                ret

0xA000001是什么呢?它是一个引用的类型,如下:

TypeRef #1 (01000001)
-------------------------------------------------------
Token:             0x01000001
ResolutionScope:   0x23000001
TypeRefName:       System.Runtime.CompilerServices.CompilationRelaxationsAttribute
  MemberRef #1 (0a000001)
  -------------------------------------------------------
    Member: (0a000001) .ctor: 
    CallCnvntn: [DEFAULT]
    hasThis 
    ReturnType: Void
    1 Arguments
      Argument #1:  I4

所以,如何从0xA000001转变成了ConsoleApp1.Program+Class`1[int]:_field的呢?看下IL的实际内容:

.method public hidebysig static !V&  GetSetPrivateField(class ConsoleApp1.Program/Class`1<!V> c) cil managed
{
  .custom instance void [System.Runtime]System.Runtime.CompilerServices.UnsafeAccessorAttribute::.ctor(valuetype [System.Runtime]System.Runtime.CompilerServices.UnsafeAccessorKind) = ( 01 00 03 00 00 00 01 00 54 0E 04 4E 61 6D 65 06   // ........T..Name.
                                                                                                                                                                                         5F 66 69 65 6C 64 )                               // _field
} // end of method Accessors`1::GetSetPrivateField

它这里面是一个UnsafeAccessorAttribute特性的.ctor函数调用,大致的对应的上上面的TypeRef的MemberRef也即是0xA000001。但是如何转换的呢?还没解决,所以这里需看下JIT的HIR部分。

HIR

GetSetPrivateField的HIR,如下代码如何来的,则需单独看

[000001] ---X-------          \--*  FIELD_ADDR byref  ConsoleApp1.Program+Class`1[int]:_field

看下它的堆栈

>  clrjit.dll!Compiler::gtDispTree::__l59::<lambda_1>::operator()() 行 12740  C++
   clrjit.dll!Compiler::gtDispTree(GenTree * tree, IndentStack * indentStack, const char * msg, bool topOnly, bool isLIR) 行 12745  C++
   clrjit.dll!Compiler::gtDispChild(GenTree * child, IndentStack * indentStack, Compiler::IndentInfo arcType, const char * msg, bool topOnly) 行 12543  C++
   clrjit.dll!Compiler::gtDispTree(GenTree * tree, IndentStack * indentStack, const char * msg, bool topOnly, bool isLIR) 行 12912  C++
   clrjit.dll!Compiler::gtDispStmt(Statement * stmt, const char * msg) 行 13395  C++
   clrjit.dll!Compiler::impAppendStmt(Statement * stmt, unsigned int chkLevel, bool checkConsumedDebugInfo) 行 555  C++
   clrjit.dll!Compiler::impAppendTree(GenTree * tree, unsigned int chkLevel, const DebugInfo & di, bool checkConsumedDebugInfo) 行 658  C++
   clrjit.dll!Compiler::impReturnInstruction(int prefixFlags, opcode_t & opcode) 行 10963  C++
   clrjit.dll!Compiler::impImportBlockCode(BasicBlock * block) 行 6657  C++
   clrjit.dll!Compiler::impImportBlock(BasicBlock * block) 行 11228  C++
   clrjit.dll!Compiler::impImport() 行 12160  C++
   clrjit.dll!Compiler::fgImport() 行 546  C++
   //此处省略以方便观看
   coreclr.dll!ThePreStub() 行 21  未知
   [外部代码]

当前的代码是:

if (tree->OperIs(GT_FIELD_ADDR))
{
    auto disp = [&]() {
         char buffer[256];
         printf(" %s", eeGetFieldName(tree->AsFieldAddr()->gtFldHnd, true, buffer, sizeof(buffer)));
     };
     disp();
}

gtFldHnd赋值

GenTreeFieldAddr(var_types type, GenTree* obj, CORINFO_FIELD_HANDLE fldHnd, DWORD offs)
        : GenTreeUnOp(GT_FIELD_ADDR, type, obj)
        , gtFldHnd(fldHnd)
        , gtFldOffset(offs)
        , gtFldMayOverlap(false)
        , gtFldIsSpanLength(false)
    {
#ifdef FEATURE_READYTORUN
        gtFieldLookup.addr = nullptr;
#endif
    }

gtFldHnd哪里来的呢?resolvedToken.hField

op1 = gtNewFieldAddrNode(resolvedToken.hField, obj, fieldInfo.offset);

hField则如下:

void CEEInfo::resolveToken(/* IN, OUT */ CORINFO_RESOLVED_TOKEN * pResolvedToken)
{
    //此处省略便于观看
    pResolvedToken->hField = CORINFO_FIELD_HANDLE(pFD);
    EE_TO_JIT_TRANSITION();
}

pFD(FieldDesc)跟MethodDesc一样,它是描述字段的

pFD = MemberLoader::GetFieldDescFromFieldDef(pModule, metaTOK, (tokenType != CORINFO_TOKENKIND_Ldtoken));

这里可以清晰的看到它是利用了Module来获取pFD的,在导入即import IL节点处获取的,堆栈:

>  coreclr.dll!CEEInfo::resolveToken(CORINFO_RESOLVED_TOKEN * pResolvedToken) 行 1118  C++
   clrjit.dll!Compiler::impResolveToken(const unsigned char * addr, CORINFO_RESOLVED_TOKEN * pResolvedToken, CorInfoTokenKind kind) 行 123  C++
   clrjit.dll!Compiler::impImportBlockCode(BasicBlock * block) 行 8622  C++
   clrjit.dll!Compiler::impImportBlock(BasicBlock * block) 行 11228  C++
   clrjit.dll!Compiler::impImport() 行 12160  C++
   clrjit.dll!Compiler::fgImport() 行 546  C++
   //此处省略,以方便观看
   corerun.exe!wmain(const int argc, const wchar_t * * argv) 行 624  C++
   [外部代码]

如此,即是UnsafeAccessorAttribute大致运行原理

总结

特性会在Roslyn里面进行变形成调用:System.Runtime.CompilerServices.CompilationRelaxationsAttribute的.ctor。然实则真正的过程是在JIT的HIR里面通过module获取filed对HIR进行填充运行的。最后通过LIR的计算,生成ASM。

往期精彩回顾

.NET9 GC标记原理(超核技术)

9e9fd75cda360a431b549c702266422c.jpeg

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值