點選上方 藍字 江湖評談 設為關註/星標
前言
探尋下.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.0
IL_00017c 01 00 00 0a ldflda 0xA000001
IL_00062a 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
1Arguments
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。
往期精彩回顧