当前位置: 欣欣网 > 码农

.NET9 Pre4 UnsafeAccessor泛型(二)

2024-05-31码农

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




前言

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

分析

例子承接上篇

public class class<T>{private T _field;privatevoid M<U>(T t, U u) { Console.WriteLine(t);Console.WriteLine(u); }} classAccessors<V>{ [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_field")]publicexternstatic ref V GetSetPrivateField( class<V> c); [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M")]publicexternstaticvoid CallM<W>( class<V> c, V v, W w);}

GetSetPrivateField的JIT HIR如下:

STMT00000 ( 0x000[E-] ... ??? )[000002]---X------- * RETURNbyref[000001]---X------- \--* FIELD_ADDRbyrefConsoleApp1.Program+ class`1[int]:_field[000000]----------- \--* LCL_VARrefV00arg0

那么它的伪代码即是:

return (new class<T>)._field;

然则它的JIT Import则是:

ILto import:IL_000002 ldarg.0IL_00017c 01 00 00 0a ldflda 0xA000001IL_00062a ret

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

TypeRef#1 (01000001)-------------------------------------------------------Token: 0x01000001ResolutionScope: 0x23000001TypeRefName: System.Runtime.CompilerServices.CompilationRelaxationsAttributeMemberRef#1 (0a000001)-------------------------------------------------------Member: (0a000001) .ctor: CallCnvntn: [DEFAULT]hasThisReturnType: Void1Arguments 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) = ( 0100030000000100540E 044E 616D 6506// ........T..Name.5F6669656C 64 ) // _field// end of method Accessors`1::GetSetPrivateField

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

HIR

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

[000001]---X------- \--* FIELD_ADDRbyrefConsoleApp1.Program+ class`1[int]:_field

看下它的堆栈

> clrjit.dll!Compiler::gtDispTree::__l59::<lambda_1>::operator()() 行 12740 C++ clrjit.dll!Compiler::gtDispTree(GenTree * tree, IndentStack * indentStack, constchar * msg, bool topOnly, bool isLIR) 行 12745 C++ clrjit.dll!Compiler::gtDispChild(GenTree * child, IndentStack * indentStack, Compiler::IndentInfo arcType, constchar * msg, bool topOnly) 行 12543 C++ clrjit.dll!Compiler::gtDispTree(GenTree * tree, IndentStack * indentStack, constchar * msg, bool topOnly, bool isLIR) 行 12912 C++ clrjit.dll!Compiler::gtDispStmt(Statement * stmt, constchar * msg) 行 13395 C++ clrjit.dll!Compiler::impAppendStmt(Statement * stmt, unsignedint chkLevel, bool checkConsumedDebugInfo) 行 555 C++ clrjit.dll!Compiler::impAppendTree(GenTree * tree, unsignedint 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(constunsignedchar * 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(constint argc, constwchar_t * * argv) 行 624 C++ [外部代码]

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

总结

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

往期精彩回顾