当前位置:   article > 正文

CoreCLR源码探索(二) new是什么_hook jit compilemethod

hook jit compilemethod

前一篇我们看到了CoreCLR中对Object的定义,这一篇我们将会看CoreCLR中对new的定义和处理
new对于.Net程序员们来说同样是耳熟能详的关键词,我们每天都会用到new,然而new究竟是什么?

因为篇幅限制和避免难度跳的太高,这一篇将不会详细讲解以下的内容,请耐心等待后续的文章

  • GC如何分配内存
  • JIT如何解析IL
  • JIT如何生成机器码

使用到的名词和缩写

以下的内容将会使用到一些名词和缩写,如果碰到看不懂的可以到这里来对照

<code class="hljs">BasicBlock: 在同一个分支(Branch)的一群指令,使用双向链表连接
GenTree: 语句树,节点类型以GT开头
Importation: 从BasicBlock生成GenTree的过程
Lowering: 具体化语句树,让语句树的各个节点可以明确的转换到机器码
SSA: Static Single Assignment
R2R: Ready To Run
Phases: JIT编译IL到机器码经过的各个阶段
JIT: Just In Time
CEE: CLR Execute Engine
ee: Execute Engine
EH: Exception Handling
Cor: CoreCLR
comp: Compiler
fg: FlowGraph
imp: Import
LDLOCA: Load Local Variable
gt: Generate
hlp: Help
Ftn: Function
MP: Multi Process
CER: Constrained Execution Regions
TLS: Thread Local Storage</code>

.Net中的三种new

请看图中的代码和生成的IL,我们可以看到尽管同样是new,却生成了三种不同的IL代码


  • 对class的new,IL指令是newobj
  • 对array的new,IL指令是newarr
  • 对struct的new,因为myStruct已经在本地变量里面了,new的时候仅仅是调用ldloca加载然后调用构造函数

我们先来看newobj和newarr这两个指令在coreclr中是怎么定义的
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/inc/opcode.def#L153

  1. OPDEF(CEE_NEWOBJ, "newobj", VarPop, PushRef, InlineMethod, IObjModel, 1, 0xFF, 0x73, CALL)
  2. OPDEF(CEE_NEWARR, "newarr", PopI, PushRef, InlineType, IObjModel, 1, 0xFF, 0x8D, NEXT)

我们可以看到这两个指令的定义,名称分别是CEE_NEWOBJ和CEE_NEWARR,请记住这两个名称

第一种new(对class的new)生成了什么机器码

接下来我们将看看coreclr是如何把CEE_NEWOBJ指令变为机器码的
在讲解之前请先大概了解JIT的工作流程,JIT编译按函数为单位,当调用函数时会自动触发JIT编译

  • 把函数的IL转换为BasicBlock(基本代码块)
  • 从BasicBlock(基本代码块)生成GenTree(语句树)
  • 对GenTree(语句树)进行Morph(变形)
  • 对GenTree(语句树)进行Lowering(具体化)
  • 根据GenTree(语句树)生成机器码

下面的代码虽然进过努力的提取,但仍然比较长,请耐心阅读

我们从JIT的入口函数开始看,这个函数会被EE(运行引擎)调用
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/inc/corjit.h#L350
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/ee_il_dll.cpp#L279
注: 按微软文档中说CILJit是32位上的实现,PreJit是64位上的实现,但实际我找不到PreJit在哪里

  1. CorJitResult CILJit::compileMethod(
  2. ICorJitInfo* compHnd, CORINFO_METHOD_INFO* methodInfo, unsigned flags, BYTE** entryAddress, ULONG* nativeSizeOfCode)
  3. {
  4. // 省略部分代码......
  5. assert(methodInfo->ILCode);
  6. result = jitNativeCode(methodHandle, methodInfo->scope, compHnd, methodInfo, &methodCodePtr, nativeSizeOfCode,
  7. &jitFlags, nullptr);
  8. // 省略部分代码......
  9. return CorJitResult(result);
  10. }

jitNativeCode是一个负责使用JIT编译单个函数的静态函数,会在内部为编译的函数创建单独的Compiler实例
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/compiler.cpp#L6075

  1. int jitNativeCode(CORINFO_METHOD_HANDLE methodHnd,
  2. CORINFO_MODULE_HANDLE classPtr,
  3. COMP_HANDLE compHnd,
  4. CORINFO_METHOD_INFO* methodInfo,
  5. void** methodCodePtr,
  6. ULONG* methodCodeSize,
  7. JitFlags* compileFlags,
  8. void* inlineInfoPtr)
  9. {
  10. // 省略部分代码......
  11. pParam->pComp->compInit(pParam->pAlloc, pParam->inlineInfo);
  12. pParam->pComp->jitFallbackCompile = pParam->jitFallbackCompile;
  13. // Now generate the code
  14. pParam->result =
  15. pParam->pComp->compCompile(pParam->methodHnd, pParam->classPtr, pParam->compHnd, pParam->methodInfo,
  16. pParam->methodCodePtr, pParam->methodCodeSize, pParam->compileFlags);
  17. // 省略部分代码......
  18. return result;
  19. }

Compiler::compCompile是Compiler类提供的入口函数,作用同样是编译函数
注意这个函数有7个参数,等一会还会有一个同名但只有3个参数的函数
这个函数主要调用了Compiler::compCompileHelper函数
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/compiler.cpp#L4693

  1. int Compiler::compCompile(CORINFO_METHOD_HANDLE methodHnd,
  2. CORINFO_MODULE_HANDLE classPtr,
  3. COMP_HANDLE compHnd,
  4. CORINFO_METHOD_INFO* methodInfo,
  5. void** methodCodePtr,
  6. ULONG* methodCodeSize,
  7. JitFlags* compileFlags)
  8. {
  9. // 省略部分代码......
  10. pParam->result = pParam->pThis->compCompileHelper(pParam->classPtr, pParam->compHnd, pParam->methodInfo,
  11. pParam->methodCodePtr, pParam->methodCodeSize,
  12. pParam->compileFlags, pParam->instVerInfo);
  13. // 省略部分代码......
  14. return param.result;
  15. }

让我们继续看Compiler::compCompileHelper
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/compiler.cpp#L5294

  1. int Compiler::compCompileHelper(CORINFO_MODULE_HANDLE classPtr,
  2. COMP_HANDLE compHnd,
  3. CORINFO_METHOD_INFO* methodInfo,
  4. void** methodCodePtr,
  5. ULONG* methodCodeSize,
  6. JitFlags* compileFlags,
  7. CorInfoInstantiationVerification instVerInfo)
  8. {
  9. // 省略部分代码......
  10. // 初始化本地变量表
  11. lvaInitTypeRef();
  12. // 省略部分代码......
  13. // 查找所有BasicBlock
  14. fgFindBasicBlocks();
  15. // 省略部分代码......
  16. // 调用3个参数的compCompile函数,注意不是7个函数的compCompile函数
  17. compCompile(methodCodePtr, methodCodeSize, compileFlags);
  18. // 省略部分代码......
  19. return CORJIT_OK;
  20. }

现在到了3个参数的compCompile,这个函数被微软认为是JIT最被感兴趣的入口函数
你可以额外阅读一下微软的JIT介绍文档
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/compiler.cpp#L4078

  1. //*********************************************************************************************
  2. // #Phases
  3. //
  4. // This is the most interesting 'toplevel' function in the JIT. It goes through the operations of
  5. // importing, morphing, optimizations and code generation. This is called from the EE through the
  6. // code:CILJit::compileMethod function.
  7. //
  8. // For an overview of the structure of the JIT, see:
  9. // https://github.com/dotnet/coreclr/blob/master/Documentation/botr/ryujit-overview.md
  10. //
  11. void Compiler::compCompile(void** methodCodePtr, ULONG* methodCodeSize, JitFlags* compileFlags)
  12. {
  13. // 省略部分代码......
  14. // 转换BasicBlock(基本代码块)到GenTree(语句树)
  15. fgImport();
  16. // 省略部分代码......
  17. // 这里会进行各个处理步骤(Phases),如Inline和优化等
  18. // 省略部分代码......
  19. // 转换GT_ALLOCOBJ节点到GT_CALL节点(分配内存=调用帮助函数)
  20. ObjectAllocator objectAllocator(this);
  21. objectAllocator.Run();
  22. // 省略部分代码......
  23. // 创建本地变量表和计算各个变量的引用计数
  24. lvaMarkLocalVars();
  25. // 省略部分代码......
  26. // 具体化语句树
  27. Lowering lower(this, m_pLinearScan); // PHASE_LOWERING
  28. lower.Run();
  29. // 省略部分代码......
  30. // 生成机器码
  31. codeGen->genGenerateCode(methodCodePtr, methodCodeSize);
  32. }

到这里你应该大概知道JIT在总体上做了什么事情
接下来我们来看Compiler::fgImport函数,这个函数负责把BasicBlock(基本代码块)转换到GenTree(语句树)
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/flowgraph.cpp#L6663

  1. void Compiler::fgImport()
  2. {
  3. // 省略部分代码......
  4. impImport(fgFirstBB);
  5. // 省略部分代码......
  6. }

再看Compiler::impImport
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/importer.cpp#L9207

  1. void Compiler::impImport(BasicBlock* method)
  2. {
  3. // 省略部分代码......
  4. /* Import blocks in the worker-list until there are no more */
  5. while (impPendingList)
  6. {
  7. PendingDsc* dsc = impPendingList;
  8. impPendingList = impPendingList->pdNext;
  9. // 省略部分代码......
  10. /* Now import the block */
  11. impImportBlock(dsc->pdBB);
  12. }
  13. }

再看Compiler::impImportBlock
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/importer.cpp#L15321

  1. //***************************************************************
  2. // Import the instructions for the given basic block. Perform
  3. // verification, throwing an exception on failure. Push any successor blocks that are enabled for the first
  4. // time, or whose verification pre-state is changed.
  5. void Compiler::impImportBlock(BasicBlock* block)
  6. {
  7. // 省略部分代码......
  8. pParam->pThis->impImportBlockCode(pParam->block);
  9. }

在接下来的Compiler::impImportBlockCode函数里面我们终于可以看到对CEE_NEWOBJ指令的处理了
这个函数有5000多行,推荐直接搜索case CEE_NEWOBJ来看以下的部分
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/importer.cpp#L9207

  1. /*****************************************************************************
  2. * Import the instr for the given basic block
  3. */
  4. void Compiler::impImportBlockCode(BasicBlock* block)
  5. {
  6. // 省略部分代码......
  7. // 处理CEE_NEWOBJ指令
  8. case CEE_NEWOBJ:
  9. // 在这里微软给出了有三种情况
  10. // 一种是对象是array,一种是对象有活动的长度(例如string),一种是普通的class
  11. // 在这里我们只分析第三种情况
  12. // There are three different cases for new
  13. // Object size is variable (depends on arguments)
  14. // 1) Object is an array (arrays treated specially by the EE)
  15. // 2) Object is some other variable sized object (e.g. String)
  16. // 3) Class Size can be determined beforehand (normal case)
  17. // In the first case, we need to call a NEWOBJ helper (multinewarray)
  18. // in the second case we call the constructor with a '0' this pointer
  19. // In the third case we alloc the memory, then call the constuctor
  20. // 省略部分代码......
  21. // 创建一个GT_ALLOCOBJ类型的GenTree(语句树)节点,用于分配内存
  22. op1 = gtNewAllocObjNode(info.compCompHnd->getNewHelper(&resolvedToken, info.compMethodHnd),
  23. resolvedToken.hClass, TYP_REF, op1);
  24. // 省略部分代码......
  25. // 因为GT_ALLOCOBJ仅负责分配内存,我们还需要调用构造函数
  26. // 这里复用了CEE_CALL指令的处理
  27. goto CALL;
  28. // 省略部分代码......
  29. CALL: // memberRef should be set.
  30. // 省略部分代码......
  31. // 创建一个GT_CALL类型的GenTree(语句树)节点,用于调用构造函数
  32. callTyp = impImportCall(opcode, &resolvedToken, constraintCall ? &constrainedResolvedToken : nullptr,
  33. newObjThisPtr, prefixFlags, &callInfo, opcodeOffs);

请记住上面代码中新建的两个GenTree(语句树)节点

  • 节点GT_ALLOCOBJ用于分配内存
  • 节点GT_CALL用于调用构造函数

在上面的代码我们可以看到在生成GT_ALLOCOBJ类型的节点时还传入了一个newHelper参数,这个newHelper正是分配内存函数的一个标识(索引值)
在CoreCLR中有很多HelperFunc(帮助函数)供JIT生成的代码调用
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/vm/jitinterface.cpp#L5894

  1. CorInfoHelpFunc CEEInfo::getNewHelper(CORINFO_RESOLVED_TOKEN * pResolvedToken, CORINFO_METHOD_HANDLE callerHandle)
  2. {
  3. // 省略部分代码......
  4. MethodTable* pMT = VMClsHnd.AsMethodTable();
  5. // 省略部分代码......
  6. result = getNewHelperStatic(pMT);
  7. // 省略部分代码......
  8. return result;
  9. }

看CEEInfo::getNewHelperStatic
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/vm/jitinterface.cpp#L5941

  1. CorInfoHelpFunc CEEInfo::getNewHelperStatic(MethodTable * pMT)
  2. {
  3. // 省略部分代码......
  4. // 这里有很多判断,例如是否是Com对象或拥有析构函数,默认会返回CORINFO_HELP_NEWFAST
  5. // Slow helper is the default
  6. CorInfoHelpFunc helper = CORINFO_HELP_NEWFAST;
  7. // 省略部分代码......
  8. return helper;
  9. }

到这里,我们可以知道新建的两个节点带有以下的信息

  • GT_ALLOCOBJ节点
    • 分配内存的帮助函数标识,默认是CORINFO_HELP_NEWFAST
  • GT_CALL节点
    • 构造函数的句柄

在使用fgImport生成了GenTree(语句树)以后,还不能直接用这个树来生成机器代码,需要经过很多步的变换
其中的一步变换会把GT_ALLOCOBJ节点转换为GT_CALL节点,因为分配内存实际上是一个对JIT专用的帮助函数的调用
这个变换在ObjectAllocator中实现,ObjectAllocator是JIT编译过程中的一个阶段(Phase)
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/objectalloc.cpp#L27

  1. void ObjectAllocator::DoPhase()
  2. {
  3. // 省略部分代码......
  4. MorphAllocObjNodes();
  5. }

MorphAllocObjNodes用于查找所有节点,如果是GT_ALLOCOBJ则进行转换
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/objectalloc.cpp#L63

  1. void ObjectAllocator::MorphAllocObjNodes()
  2. {
  3. // 省略部分代码......
  4. for (GenTreeStmt* stmt = block->firstStmt(); stmt; stmt = stmt->gtNextStmt)
  5. {
  6. // 省略部分代码......
  7. bool canonicalAllocObjFound = false;
  8. // 省略部分代码......
  9. if (op2->OperGet() == GT_ALLOCOBJ)
  10. canonicalAllocObjFound = true;
  11. // 省略部分代码......
  12. if (canonicalAllocObjFound)
  13. {
  14. // 省略部分代码......
  15. op2 = MorphAllocObjNodeIntoHelperCall(asAllocObj);
  16. }
  17. }
  18. }

MorphAllocObjNodeIntoHelperCall的定义
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/objectalloc.cpp#L152

  1. // MorphAllocObjNodeIntoHelperCall: Morph a GT_ALLOCOBJ node into an
  2. // allocation helper call.
  3. GenTreePtr ObjectAllocator::MorphAllocObjNodeIntoHelperCall(GenTreeAllocObj* allocObj)
  4. {
  5. // 省略部分代码......
  6. GenTreePtr helperCall = comp->fgMorphIntoHelperCall(allocObj, allocObj->gtNewHelper, comp->gtNewArgList(op1));
  7. return helperCall;
  8. }

fgMorphIntoHelperCall的定义
这个函数转换GT_ALLOCOBJ节点到GT_CALL节点,并且获取指向分配内存的函数的指针
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/morph.cpp#L61

  1. GenTreePtr Compiler::fgMorphIntoHelperCall(GenTreePtr tree, int helper, GenTreeArgList* args)
  2. {
  3. tree->ChangeOper(GT_CALL);
  4. tree->gtFlags |= GTF_CALL;
  5. // 省略部分代码......
  6. // 如果GT_ALLOCOBJ中帮助函数的标识是CORINFO_HELP_NEWFAST,这里就是eeFindHelper(CORINFO_HELP_NEWFAST)
  7. // eeFindHelper会把帮助函数的表示转换为帮助函数的句柄
  8. tree->gtCall.gtCallType = CT_HELPER;
  9. tree->gtCall.gtCallMethHnd = eeFindHelper(helper);
  10. // 省略部分代码......
  11. tree = fgMorphArgs(tree->AsCall());
  12. return tree;
  13. }

到这里,我们可以知道新建的两个节点变成了这样

  • GT_CALL节点 (调用帮助函数)
    • 分配内存的帮助函数的句柄
  • GT_CALL节点 (调用Managed函数)
    • 构造函数的句柄

接下来JIT还会对GenTree(语句树)做出大量处理,这里省略说明,接下来我们来看机器码的生成
函数CodeGen::genCallInstruction负责把GT_CALL节点转换为汇编
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/codegenxarch.cpp#L5934

  1. // Produce code for a GT_CALL node
  2. void CodeGen::genCallInstruction(GenTreePtr node)
  3. {
  4. // 省略部分代码......
  5. if (callType == CT_HELPER)
  6. {
  7. // 把句柄转换为帮助函数的句柄,默认是CORINFO_HELP_NEWFAST
  8. helperNum = compiler->eeGetHelperNum(methHnd);
  9. // 获取指向帮助函数的指针
  10. // 这里等于调用compiler->compGetHelperFtn(CORINFO_HELP_NEWFAST, ...)
  11. addr = compiler->compGetHelperFtn(helperNum, (void**)&pAddr);
  12. }
  13. else
  14. {
  15. // 调用普通函数
  16. // Direct call to a non-virtual user function.
  17. addr = call->gtDirectCallAddress;
  18. }
  19. }

我们来看下compGetHelperFtn究竟把CORINFO_HELP_NEWFAST转换到了什么函数
compGetHelperFtn的定义
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/compiler.hpp#L1907

  1. void* Compiler::compGetHelperFtn(CorInfoHelpFunc ftnNum, /* IN */
  2. void** ppIndirection) /* OUT */
  3. {
  4. // 省略部分代码......
  5. addr = info.compCompHnd->getHelperFtn(ftnNum, ppIndirection);
  6. return addr;
  7. }

getHelperFtn的定义
这里我们可以看到获取了hlpDynamicFuncTable这个函数表中的函数
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/vm/jitinterface.cpp#L10369

  1. void* CEEJitInfo::getHelperFtn(CorInfoHelpFunc ftnNum, /* IN */
  2. void ** ppIndirection) /* OUT */
  3. {
  4. // 省略部分代码......
  5. pfnHelper = hlpDynamicFuncTable[dynamicFtnNum].pfnHelper;
  6. // 省略部分代码......
  7. result = (LPVOID)GetEEFuncEntryPoint(pfnHelper);
  8. return result;
  9. }

hlpDynamicFuncTable函数表使用了jithelpers.h中的定义,其中CORINFO_HELP_NEWFAST对应的函数如下
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/inc/jithelpers.h#L78

JITHELPER(CORINFO_HELP_NEWFAST,                     JIT_New,    CORINFO_HELP_SIG_REG_ONLY)

可以看到对应了JIT_New,这个就是JIT生成的代码调用分配内存的函数了,JIT_New的定义如下
需要注意的是函数表中的JIT_New在满足一定条件时会被替换为更快的实现,但作用和JIT_New是一样的,这一块将在后面提及
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/vm/jithelpers.cpp#L2908

  1. HCIMPL1(Object*, JIT_New, CORINFO_CLASS_HANDLE typeHnd_)
  2. {
  3. // 省略部分代码......
  4. MethodTable *pMT = typeHnd.AsMethodTable();
  5. // 省略部分代码......
  6. // AllocateObject是分配内存的函数,这个函数供CoreCLR的内部代码或非托管代码调用
  7. // JIT_New是对这个函数的一个包装,仅供JIT生成的代码调用
  8. newobj = AllocateObject(pMT);
  9. // 省略部分代码......
  10. return(OBJECTREFToObject(newobj));
  11. }
  12. HCIMPLEND

总结:
JIT从CEE_NEWOBJ生成了两段代码,一段是调用JIT_New函数分配内存的代码,一段是调用构造函数的代码

第二种new(对array的new)生成了什么机器码

我们来看一下CEE_NEWARR指令是怎样处理的,因为前面已经花了很大篇幅介绍对CEE_NEWOBJ的处理,这里仅列出不同的部分
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/importer.cpp#L13334

  1. /*****************************************************************************
  2. * Import the instr for the given basic block
  3. */
  4. void Compiler::impImportBlockCode(BasicBlock* block)
  5. {
  6. // 省略部分代码......
  7. // 处理CEE_NEWARR指令
  8. case CEE_NEWARR:
  9. // 省略部分代码......
  10. args = gtNewArgList(op1, op2);
  11. // 生成GT_CALL类型的节点调用帮助函数
  12. /* Create a call to 'new' */
  13. // Note that this only works for shared generic code because the same helper is used for all
  14. // reference array types
  15. op1 = gtNewHelperCallNode(info.compCompHnd->getNewArrHelper(resolvedToken.hClass), TYP_REF, 0, args);
  16. }

我们可以看到CEE_NEWARR直接生成了GT_CALL节点,不像CEE_NEWOBJ需要进一步的转换
getNewArrHelper返回了调用的帮助函数,我们来看一下getNewArrHelper
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/vm/jitinterface.cpp#L6035

  1. /***********************************************************************/
  2. // <REVIEW> this only works for shared generic code because all the
  3. // helpers are actually the same. If they were different then things might
  4. // break because the same helper would end up getting used for different but
  5. // representation-compatible arrays (e.g. one with a default constructor
  6. // and one without) </REVIEW>
  7. CorInfoHelpFunc CEEInfo::getNewArrHelper (CORINFO_CLASS_HANDLE arrayClsHnd)
  8. {
  9. // 省略部分代码......
  10. TypeHandle arrayType(arrayClsHnd);
  11. result = getNewArrHelperStatic(arrayType);
  12. // 省略部分代码......
  13. return result;
  14. }

再看getNewArrHelperStatic,我们可以看到一般情况下会返回CORINFO_HELP_NEWARR_1_OBJ
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/vm/jitinterface.cpp#L6060

  1. CorInfoHelpFunc CEEInfo::getNewArrHelperStatic(TypeHandle clsHnd)
  2. {
  3. // 省略部分代码......
  4. if (CorTypeInfo::IsGenericVariable(elemType))
  5. {
  6. result = CORINFO_HELP_NEWARR_1_OBJ;
  7. }
  8. else if (CorTypeInfo::IsObjRef(elemType))
  9. {
  10. // It is an array of object refs
  11. result = CORINFO_HELP_NEWARR_1_OBJ;
  12. }
  13. else
  14. {
  15. // These cases always must use the slow helper
  16. // 省略部分代码......
  17. }
  18. return result;
  19. {

CORINFO_HELP_NEWARR_1_OBJ对应的函数如下
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/inc/jithelpers.h#L86

DYNAMICJITHELPER(CORINFO_HELP_NEWARR_1_OBJ, JIT_NewArr1,CORINFO_HELP_SIG_REG_ONLY)

可以看到对应了JIT_NewArr1这个包装给JIT调用的帮助函数
和JIT_New一样,在满足一定条件时会被替换为更快的实现
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/vm/jithelpers.cpp#L3303

  1. HCIMPL2(Object*, JIT_NewArr1, CORINFO_CLASS_HANDLE arrayTypeHnd_, INT_PTR size)
  2. {
  3. // 省略部分代码......
  4. CorElementType elemType = pArrayClassRef->GetArrayElementTypeHandle().GetSignatureCorElementType();
  5. if (CorTypeInfo::IsPrimitiveType(elemType)
  6. {
  7. // 省略部分代码......
  8. // 如果类型是基元类型(int, double等)则使用更快的FastAllocatePrimitiveArray函数
  9. newArray = FastAllocatePrimitiveArray(pArrayClassRef->GetMethodTable(), static_cast<DWORD>(size), bAllocateInLargeHeap);
  10. }
  11. else
  12. {
  13. // 省略部分代码......
  14. // 默认使用AllocateArrayEx函数
  15. INT32 size32 = (INT32)size;
  16. newArray = AllocateArrayEx(typeHnd, &size32, 1);
  17. }
  18. // 省略部分代码......
  19. return(OBJECTREFToObject(newArray));
  20. }
  21. HCIMPLEND

总结:
JIT从CEE_NEWARR只生成了一段代码,就是调用JIT_NewArr1函数的代码

第三种new(对struct的new)生成了什么机器码

这种new会在栈(stack)分配内存,所以不需要调用任何分配内存的函数
在一开始的例子中,myStruct在编译时就已经定义为一个本地变量,对本地变量的需要的内存会在函数刚进入的时候一并分配
这里我们先来看本地变量所需要的内存是怎么计算的

先看Compiler::lvaAssignVirtualFrameOffsetsToLocals
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/lclvars.cpp#L4863

  1. /*****************************************************************************
  2. * lvaAssignVirtualFrameOffsetsToLocals() : Assign virtual stack offsets to
  3. * locals, temps, and anything else. These will all be negative offsets
  4. * (stack grows down) relative to the virtual '0'/return address
  5. */
  6. void Compiler::lvaAssignVirtualFrameOffsetsToLocals()
  7. {
  8. // 省略部分代码......
  9. for (cur = 0; alloc_order[cur]; cur++)
  10. {
  11. // 省略部分代码......
  12. for (lclNum = 0, varDsc = lvaTable; lclNum < lvaCount; lclNum++, varDsc++)
  13. {
  14. // 省略部分代码......
  15. // Reserve the stack space for this variable
  16. stkOffs = lvaAllocLocalAndSetVirtualOffset(lclNum, lvaLclSize(lclNum), stkOffs);
  17. }
  18. }
  19. }

再看Compiler::lvaAllocLocalAndSetVirtualOffset
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/lclvars.cpp#L5537

  1. int Compiler::lvaAllocLocalAndSetVirtualOffset(unsigned lclNum, unsigned size, int stkOffs)
  2. {
  3. // 省略部分代码......
  4. /* Reserve space on the stack by bumping the frame size */
  5. lvaIncrementFrameSize(size);
  6. stkOffs -= size;
  7. lvaTable[lclNum].lvStkOffs = stkOffs;
  8. // 省略部分代码......
  9. return stkOffs;
  10. }

再看Compiler::lvaIncrementFrameSize
我们可以看到最终会加到compLclFrameSize这个变量中,这个变量就是当前函数总共需要在栈(Stack)分配的内存大小
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/lclvars.cpp#L3528

  1. inline void Compiler::lvaIncrementFrameSize(unsigned size)
  2. {
  3. if (size > MAX_FrameSize || compLclFrameSize + size > MAX_FrameSize)
  4. {
  5. BADCODE("Frame size overflow");
  6. }
  7. compLclFrameSize += size;
  8. }

现在来看生成机器码的代码,在栈分配内存的代码会在CodeGen::genFnProlog生成
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/codegencommon.cpp#L8140

  1. void CodeGen::genFnProlog()
  2. {
  3. // 省略部分代码......
  4. // ARM64和其他平台的调用时机不一样,但是参数一样
  5. genAllocLclFrame(compiler->compLclFrameSize, initReg, &initRegZeroed, intRegState.rsCalleeRegArgMaskLiveIn);
  6. }

再看CodeGen::genAllocLclFrame,这里就是分配栈内存的代码了,简单的rsp(esp)减去了frameSize
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/codegencommon.cpp#L5846

  1. /*-----------------------------------------------------------------------------
  2. *
  3. * Probe the stack and allocate the local stack frame: subtract from SP.
  4. * On ARM64, this only does the probing; allocating the frame is done when callee-saved registers are saved.
  5. */
  6. void CodeGen::genAllocLclFrame(unsigned frameSize, regNumber initReg, bool* pInitRegZeroed, regMaskTP maskArgRegsLiveIn)
  7. {
  8. // 省略部分代码......
  9. // sub esp, frameSize 6
  10. inst_RV_IV(INS_sub, REG_SPBASE, frameSize, EA_PTRSIZE);
  11. }

总结:
JIT对struct的new会生成统一在栈分配内存的代码,所以你在IL中看不到new struct的指令
调用构造函数的代码会从后面的call指令生成

第一种new(对class的new)做了什么

从上面的分析我们可以知道第一种new先调用JIT_New分配内存,然后调用构造函数
在上面JIT_New的源代码中可以看到,JIT_New内部调用了AllocateObject

先看AllocateObject函数
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/vm/gchelpers.cpp#L931

  1. // AllocateObject will throw OutOfMemoryException so don't need to check
  2. // for NULL return value from it.
  3. OBJECTREF AllocateObject(MethodTable *pMT
  4. #ifdef FEATURE_COMINTEROP
  5. , bool fHandleCom
  6. #endif
  7. )
  8. {
  9. // 省略部分代码......
  10. Object *orObject = NULL;
  11. // 如果类型有重要的析构函数,预编译所有相关的函数(详细可以搜索CER)
  12. // 同一个类型只会处理一次
  13. if (pMT->HasCriticalFinalizer())
  14. PrepareCriticalFinalizerObject(pMT);
  15. // 省略部分代码......
  16. DWORD baseSize = pMT->GetBaseSize();
  17. // 调用gc的帮助函数分配内存,如果需要向8对齐则调用AllocAlign8,否则调用Alloc
  18. if (pMT->RequiresAlign8())
  19. {
  20. // 省略部分代码......
  21. orObject = (Object *) AllocAlign8(baseSize,
  22. pMT->HasFinalizer(),
  23. pMT->ContainsPointers(),
  24. pMT->IsValueType());
  25. }
  26. else
  27. {
  28. orObject = (Object *) Alloc(baseSize,
  29. pMT->HasFinalizer(),
  30. pMT->ContainsPointers());
  31. }
  32. // 检查同步块索引(SyncBlock)是否为0
  33. // verify zero'd memory (at least for sync block)
  34. _ASSERTE( orObject->HasEmptySyncBlockInfo() );
  35. // 设置类型信息(MethodTable)
  36. if ((baseSize >= LARGE_OBJECT_SIZE))
  37. {
  38. orObject->SetMethodTableForLargeObject(pMT);
  39. GCHeap::GetGCHeap()->PublishObject((BYTE*)orObject);
  40. }
  41. else
  42. {
  43. orObject->SetMethodTable(pMT);
  44. }
  45. // 省略部分代码......
  46. return UNCHECKED_OBJECTREF_TO_OBJECTREF(oref);
  47. }

再看Alloc函数
源代码:

  1. // There are only three ways to get into allocate an object.
  2. // * Call optimized helpers that were generated on the fly. This is how JIT compiled code does most
  3. // allocations, however they fall back code:Alloc, when for all but the most common code paths. These
  4. // helpers are NOT used if profiler has asked to track GC allocation (see code:TrackAllocations)
  5. // * Call code:Alloc - When the jit helpers fall back, or we do allocations within the runtime code
  6. // itself, we ultimately call here.
  7. // * Call code:AllocLHeap - Used very rarely to force allocation to be on the large object heap.
  8. //
  9. // While this is a choke point into allocating an object, it is primitive (it does not want to know about
  10. // MethodTable and thus does not initialize that poitner. It also does not know if the object is finalizable
  11. // or contains pointers. Thus we quickly wrap this function in more user-friendly ones that know about
  12. // MethodTables etc. (see code:FastAllocatePrimitiveArray code:AllocateArrayEx code:AllocateObject)
  13. //
  14. // You can get an exhaustive list of code sites that allocate GC objects by finding all calls to
  15. // code:ProfilerObjectAllocatedCallback (since the profiler has to hook them all).
  16. inline Object* Alloc(size_t size, BOOL bFinalize, BOOL bContainsPointers )
  17. {
  18. // 省略部分代码......
  19. // We don't want to throw an SO during the GC, so make sure we have plenty
  20. // of stack before calling in.
  21. INTERIOR_STACK_PROBE_FOR(GetThread(), static_cast<unsigned>(DEFAULT_ENTRY_PROBE_AMOUNT * 1.5));
  22. if (GCHeapUtilities::UseAllocationContexts())
  23. retVal = GCHeapUtilities::GetGCHeap()->Alloc(GetThreadAllocContext(), size, flags);
  24. else
  25. retVal = GCHeapUtilities::GetGCHeap()->Alloc(size, flags);
  26. if (!retVal)
  27. {
  28. ThrowOutOfMemory();
  29. }
  30. END_INTERIOR_STACK_PROBE;
  31. return retVal;
  32. }

总结:
第一种new做的事情主要有

  • 调用JIT_New
    • 从GCHeap中申请一块内存
    • 设置类型信息(MethodTable)
    • 同步块索引默认为0,不需要设置
  • 调用构造函数

第二种new(对array的new)做了什么

第二种new只调用了JIT_NewArr1,从上面JIT_NewArr1的源代码可以看到
如果元素的类型是基元类型(int, double等)则会调用FastAllocatePrimitiveArray,否则会调用AllocateArrayEx

先看FastAllocatePrimitiveArray函数
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/vm/gchelpers.cpp#L563

  1. /*
  2. * Allocates a single dimensional array of primitive types.
  3. */
  4. OBJECTREF FastAllocatePrimitiveArray(MethodTable* pMT, DWORD cElements, BOOL bAllocateInLargeHeap)
  5. {
  6. // 省略部分代码......
  7. // 检查元素数量不能大于一个硬性限制
  8. SIZE_T componentSize = pMT->GetComponentSize();
  9. if (cElements > MaxArrayLength(componentSize))
  10. ThrowOutOfMemory();
  11. // 检查总大小不能溢出
  12. S_SIZE_T safeTotalSize = S_SIZE_T(cElements) * S_SIZE_T(componentSize) + S_SIZE_T(pMT->GetBaseSize());
  13. if (safeTotalSize.IsOverflow())
  14. ThrowOutOfMemory();
  15. size_t totalSize = safeTotalSize.Value();
  16. // 省略部分代码......
  17. // 调用gc的帮助函数分配内存
  18. ArrayBase* orObject;
  19. if (bAllocateInLargeHeap)
  20. {
  21. orObject = (ArrayBase*) AllocLHeap(totalSize, FALSE, FALSE);
  22. }
  23. else
  24. {
  25. ArrayTypeDesc *pArrayR8TypeDesc = g_pPredefinedArrayTypes[ELEMENT_TYPE_R8];
  26. if (DATA_ALIGNMENT < sizeof(double) && pArrayR8TypeDesc != NULL && pMT == pArrayR8TypeDesc->GetMethodTable() && totalSize < LARGE_OBJECT_SIZE - MIN_OBJECT_SIZE)
  27. {
  28. // Creation of an array of doubles, not in the large object heap.
  29. // We want to align the doubles to 8 byte boundaries, but the GC gives us pointers aligned
  30. // to 4 bytes only (on 32 bit platforms). To align, we ask for 12 bytes more to fill with a
  31. // dummy object.
  32. // If the GC gives us a 8 byte aligned address, we use it for the array and place the dummy
  33. // object after the array, otherwise we put the dummy object first, shifting the base of
  34. // the array to an 8 byte aligned address.
  35. // Note: on 64 bit platforms, the GC always returns 8 byte aligned addresses, and we don't
  36. // execute this code because DATA_ALIGNMENT < sizeof(double) is false.
  37. _ASSERTE(DATA_ALIGNMENT == sizeof(double)/2);
  38. _ASSERTE((MIN_OBJECT_SIZE % sizeof(double)) == DATA_ALIGNMENT); // used to change alignment
  39. _ASSERTE(pMT->GetComponentSize() == sizeof(double));
  40. _ASSERTE(g_pObjectClass->GetBaseSize() == MIN_OBJECT_SIZE);
  41. _ASSERTE(totalSize < totalSize + MIN_OBJECT_SIZE);
  42. orObject = (ArrayBase*) Alloc(totalSize + MIN_OBJECT_SIZE, FALSE, FALSE);
  43. Object *orDummyObject;
  44. if((size_t)orObject % sizeof(double))
  45. {
  46. orDummyObject = orObject;
  47. orObject = (ArrayBase*) ((size_t)orObject + MIN_OBJECT_SIZE);
  48. }
  49. else
  50. {
  51. orDummyObject = (Object*) ((size_t)orObject + totalSize);
  52. }
  53. _ASSERTE(((size_t)orObject % sizeof(double)) == 0);
  54. orDummyObject->SetMethodTable(g_pObjectClass);
  55. }
  56. else
  57. {
  58. orObject = (ArrayBase*) Alloc(totalSize, FALSE, FALSE);
  59. bPublish = (totalSize >= LARGE_OBJECT_SIZE);
  60. }
  61. }
  62. // 设置类型信息(MethodTable)
  63. // Initialize Object
  64. orObject->SetMethodTable( pMT );
  65. _ASSERTE(orObject->GetMethodTable() != NULL);
  66. // 设置数组长度
  67. orObject->m_NumComponents = cElements;
  68. // 省略部分代码......
  69. return( ObjectToOBJECTREF((Object*)orObject) );
  70. }

再看AllocateArrayEx函数,这个函数比起上面的函数多出了对多维数组的处理
JIT_NewArr1调用AllocateArrayEx时传了3个参数,剩下2个参数是可选参数
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/vm/gchelpers.cpp#L282

  1. // Handles arrays of arbitrary dimensions
  2. //
  3. // If dwNumArgs is set to greater than 1 for a SZARRAY this function will recursively
  4. // allocate sub-arrays and fill them in.
  5. //
  6. // For arrays with lower bounds, pBounds is <lower bound 1>, <count 1>, <lower bound 2>, ...
  7. OBJECTREF AllocateArrayEx(TypeHandle arrayType, INT32 *pArgs, DWORD dwNumArgs, BOOL bAllocateInLargeHeap
  8. DEBUG_ARG(BOOL bDontSetAppDomain))
  9. {
  10. // 省略部分代码......
  11. ArrayBase * orArray = NULL;
  12. // 省略部分代码......
  13. // 调用gc的帮助函数分配内存
  14. if (bAllocateInLargeHeap)
  15. {
  16. orArray = (ArrayBase *) AllocLHeap(totalSize, FALSE, pArrayMT->ContainsPointers());
  17. // 设置类型信息(MethodTable)
  18. orArray->SetMethodTableForLargeObject(pArrayMT);
  19. }
  20. else
  21. {
  22. #ifdef FEATURE_64BIT_ALIGNMENT
  23. MethodTable *pElementMT = arrayDesc->GetTypeParam().GetMethodTable();
  24. if (pElementMT->RequiresAlign8() && pElementMT->IsValueType())
  25. {
  26. // This platform requires that certain fields are 8-byte aligned (and the runtime doesn't provide
  27. // this guarantee implicitly, e.g. on 32-bit platforms). Since it's the array payload, not the
  28. // header that requires alignment we need to be careful. However it just so happens that all the
  29. // cases we care about (single and multi-dim arrays of value types) have an even number of DWORDs
  30. // in their headers so the alignment requirements for the header and the payload are the same.
  31. _ASSERTE(((pArrayMT->GetBaseSize() - SIZEOF_OBJHEADER) & 7) == 0);
  32. orArray = (ArrayBase *) AllocAlign8(totalSize, FALSE, pArrayMT->ContainsPointers(), FALSE);
  33. }
  34. else
  35. #endif
  36. {
  37. orArray = (ArrayBase *) Alloc(totalSize, FALSE, pArrayMT->ContainsPointers());
  38. }
  39. // 设置类型信息(MethodTable)
  40. orArray->SetMethodTable(pArrayMT);
  41. }
  42. // 设置数组长度
  43. // Initialize Object
  44. orArray->m_NumComponents = cElements;
  45. // 省略部分代码......
  46. return ObjectToOBJECTREF((Object *) orArray);
  47. }

总结:
第二种new做的事情主要有

  • 调用JIT_NewArr1
    • 从GCHeap中申请一块内存
    • 设置类型信息(MethodTable)
    • 设置数组长度(m_NumComponents)
    • 不会调用构造函数,所以所有内容都会为0(所有成员都会为默认值)

第三种new(对struct的new)做了什么

对struct的new不会从GCHeap申请内存,也不会设置类型信息(MethodTable),所以可以直接进入总结

总结:
第三种new做的事情主要有

  • 在进入函数时统一从栈(Stack)分配内存
    • 分配的内存不会包含同步块索引(SyncBlock)和类型信息(MethodTable)
  • 调用构造函数

验证第一种new(对class的new)

打开VS反汇编和内存窗口,让我们来看看第一种new实际做了什么事情

第一种new的反汇编结果如下,一共有两个call

  1. 00007FF919570B53 mov rcx,7FF9194161A0h // 设置第一个参数(指向MethodTable的指针)
  2. 00007FF919570B5D call 00007FF97905E350 // 调用分配内存的函数,默认是JIT_New
  3. 00007FF919570B62 mov qword ptr [rbp+38h],rax // 把地址设置到临时变量(rbp+38)
  4. 00007FF919570B66 mov r8,37BFC73068h
  5. 00007FF919570B70 mov r8,qword ptr [r8] // 设置第三个参数("hello")
  6. 00007FF919570B73 mov rcx,qword ptr [rbp+38h] // 设置第一个参数(this)
  7. 00007FF919570B77 mov edx,12345678h // 设置第二个参数(0x12345678)
  8. 00007FF919570B7C call 00007FF9195700B8 // 调用构造函数
  9. 00007FF919570B81 mov rcx,qword ptr [rbp+38h]
  10. 00007FF919570B85 mov qword ptr [rbp+50h],rcx // 把临时变量复制到myClass变量中

第一个call是分配内存使用的帮助函数,默认调用JIT_New
但是这里实际调用的不是JIT_New而是JIT_TrialAllocSFastMP_InlineGetThread函数,这是一个优化版本允许从TLS(Thread Local Storage)中快速分配内存
我们来看一下JIT_TrialAllocSFastMP_InlineGetThread函数的定义

源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/vm/amd64/JitHelpers_InlineGetThread.asm#L59

  1. ; IN: rcx: MethodTable*
  2. ; OUT: rax: new object
  3. LEAF_ENTRY JIT_TrialAllocSFastMP_InlineGetThread, _TEXT
  4. mov edx, [rcx + OFFSET__MethodTable__m_BaseSize] // 从MethodTable获取需要分配的内存大小,放到edx
  5. ; m_BaseSize is guaranteed to be a multiple of 8.
  6. PATCHABLE_INLINE_GETTHREAD r11, JIT_TrialAllocSFastMP_InlineGetThread__PatchTLSOffset
  7. mov r10, [r11 + OFFSET__Thread__m_alloc_context__alloc_limit] // 获取从TLS分配内存的限制地址,放到r10
  8. mov rax, [r11 + OFFSET__Thread__m_alloc_context__alloc_ptr] // 获取从TLS分配内存的当前地址,放到rax
  9. add rdx, rax // 地址 + 需要分配的内存大小,放到rdx
  10. cmp rdx, r10 // 判断是否可以从TLS分配内存
  11. ja AllocFailed // if (rdx > r10)
  12. mov [r11 + OFFSET__Thread__m_alloc_context__alloc_ptr], rdx // 设置新的当前地址
  13. mov [rax], rcx // 给刚刚分配到的内存设置MethodTable
  14. ifdef _DEBUG
  15. call DEBUG_TrialAllocSetAppDomain_NoScratchArea
  16. endif ; _DEBUG
  17. ret // 分配成功,返回
  18. AllocFailed:
  19. jmp JIT_NEW // 分配失败,调用默认的JIT_New函数
  20. LEAF_END JIT_TrialAllocSFastMP_InlineGetThread, _TEXT

可以看到做的事情和JIT_New相同,但不是从堆而是从TLS中分配内存
第二个call调用构造函数,call的地址和下面的地址不一致可能是因为中间有一层包装,目前还未解明包装中的处理

最后一个call调用的是JIT_WriteBarrier

验证第二种new(对array的new)

反汇编可以看到第二种new只有一个call

  1. 00007FF919570B93 mov rcx,7FF9195B4CFAh // 设置第一个参数(指向MethodTable的指针)
  2. 00007FF919570B9D mov edx,378h // 设置第二个参数(数组的大小)
  3. 00007FF919570BA2 call 00007FF97905E440 // 调用分配内存的函数,默认是JIT_NewArr1
  4. 00007FF919570BA7 mov qword ptr [rbp+30h],rax // 设置到临时变量(rbp+30)
  5. 00007FF919570BAB mov rcx,qword ptr [rbp+30h]
  6. 00007FF919570BAF mov qword ptr [rbp+48h],rcx // 把临时变量复制到myArray变量中

call实际调用的是JIT_NewArr1VC_MP_InlineGetThread这个函数
和JIT_TrialAllocSFastMP_InlineGetThread一样,同样是从TLS(Thread Local Storage)中快速分配内存的函数
源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/vm/amd64/JitHelpers_InlineGetThread.asm#L207
具体代码这里就不再分析,有兴趣的可以去阅读上面的源代码

验证第三种new(对struct的new)

对struct的new会在函数进入的时候从栈分配内存,这里是减少rsp寄存器(栈顶)的值

  1. 00007FF919570B22 push rsi // 保存原rsi
  2. 00007FF919570B23 sub rsp,60h // 从栈分配内存
  3. 00007FF919570B27 mov rbp,rsp // 复制值到rbp
  4. 00007FF919570B2A mov rsi,rcx // 保存原rcx到rsi
  5. 00007FF919570B2D lea rdi,[rbp+28h] // rdi = rbp+28,有28个字节需要清零
  6. 00007FF919570B31 mov ecx,0Eh // rcx = 14 (计数)
  7. 00007FF919570B36 xor eax,eax // eax = 0
  8. 00007FF919570B38 rep stos dword ptr [rdi] // 把eax的值(short)设置到rdi直到rcx为0,总共清空14*2=28个字节
  9. 00007FF919570B3A mov rcx,rsi // 恢复原rcx

因为分配的内存已经在栈里面,后面只需要直接调构造函数

  1. 00007FF919570BBD lea rcx,[rbp+40h] // 第一个参数 (this)
  2. 00007FF919570BC1 mov edx,55667788h // 第二个参数 (0x55667788)
  3. 00007FF919570BC6 call 00007FF9195700A0 // 调用构造函数

构造函数的反编译

中间有一个call 00007FF97942E260调用的是JIT_DbgIsJustMyCode

在函数结束时会自动释放从栈分配的内存,在最后会让rsp = rbp + 0x60,这样rsp就恢复原值了

参考

http://stackoverflow.com/questions/1255803/does-the-net-clr-jit-compile-every-method-every-time
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/vm/gchelpers.h
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/vm/gchelpers.cpp#L986
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/vm/jithelpers.cpp#L2908
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/vm/jitinterface.cpp
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/vm/jitinterfacegen.cpp
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/vm/amd64/JitHelpers_InlineGetThread.asm
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcinterface.h#L230
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gc.h
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gc.cpp
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/inc/opcode.def#L153
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/inc/readytorunhelpers.h#L46
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/inc/readytorun.h#L236
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/inc/corinfo.h##L1147
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/inc/corjit.h#L350
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/ee_il_dll.cpp#L279
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/inc/jithelpers.h
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/compiler.hpp
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/compiler.h
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/compiler.cpp
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/flowgraph.cpp
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/importer.cpp
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/gentree.cpp
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/objectalloc.cpp
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/morph.cpp
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/jit/codegenxarch.cpp#L8404
https://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/botr/ryujit-overview.md
https://github.com/dotnet/coreclr/blob/master/Documentation/building/viewing-jit-dumps.md
https://github.com/dotnet/coreclr/blob/master/Documentation/building/linux-instructions.md
https://en.wikipedia.org/wiki/Basic_block
https://en.wikipedia.org/wiki/Control_flow_graph
https://en.wikipedia.org/wiki/Static_single_assignment_form
https://msdn.microsoft.com/en-us/library/windows/hardware/ff561499(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/ms228973(v=vs.110).aspx
https://msdn.microsoft.com/en-us/library/system.runtime.constrainedexecution.criticalfinalizerobject(v=vs.110).aspx
https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.safehandle(v=vs.110).aspx
https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.criticalhandle(v=vs.110).aspx
https://dotnet.myget.org/feed/dotnet-core/package/nuget/runtime.win7-x64.Microsoft.NETCore.Runtime.CoreCLR
http://www.codemachine.com/article_x64deepdive.html

这一篇相对前一篇多了很多c++和汇编代码,也在表面上涉及到了JIT,你们可能会说看不懂
这是正常的,我也不是完全看懂这篇提到的所有处理
欢迎大神们勘误,也欢迎小白们提问

接下来我会重点分析GC分配内存的算法,敬请期待

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/100684
推荐阅读
  

闽ICP备14008679号