CoreCLR源码探索(二) new是什么(一)


风晓
风晓 2023-12-31 10:40:33 48826 赞同 0 反对 0
分类: 资源
前一篇我们看到了CoreCLR中对Object的定义,这一篇我们将会看CoreCLR中对new的定义和处理 new对于.Net程序员们来说同样是耳熟能详的关键词,我们每天都会用到new,然而new究竟是什么?

使用到的名词和缩写

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

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

.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

OPDEF(CEE_NEWOBJ, "newobj", VarPop, PushRef, InlineMethod, IObjModel, 1, 0xFF, 0x73, CALL)
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在哪里

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

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

int jitNativeCode(CORINFO_METHOD_HANDLE methodHnd,
                  CORINFO_MODULE_HANDLE classPtr,
                  COMP_HANDLE           compHnd,
                  CORINFO_METHOD_INFO*  methodInfo,
                  void**                methodCodePtr,
                  ULONG*                methodCodeSize,
                  JitFlags*             compileFlags,
                  void*                 inlineInfoPtr)
{
    // 省略部分代码......
    pParam->pComp->compInit(pParam->pAlloc, pParam->inlineInfo);
    pParam->pComp->jitFallbackCompile = pParam->jitFallbackCompile;
    // Now generate the code
    pParam->result =
        pParam->pComp->compCompile(pParam->methodHnd, pParam->classPtr, pParam->compHnd, pParam->methodInfo,
                                   pParam->methodCodePtr, pParam->methodCodeSize, pParam->compileFlags);
    // 省略部分代码......
    return result;
}

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

int Compiler::compCompile(CORINFO_METHOD_HANDLE methodHnd,
                          CORINFO_MODULE_HANDLE classPtr,
                          COMP_HANDLE           compHnd,
                          CORINFO_METHOD_INFO*  methodInfo,
                          void**                methodCodePtr,
                          ULONG*                methodCodeSize,
                          JitFlags*             compileFlags)
{
    // 省略部分代码......
    pParam->result = pParam->pThis->compCompileHelper(pParam->classPtr, pParam->compHnd, pParam->methodInfo,
                                                      pParam->methodCodePtr, pParam->methodCodeSize,
                                                      pParam->compileFlags, pParam->instVerInfo);
    // 省略部分代码......
    return param.result;
}

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

int Compiler::compCompileHelper(CORINFO_MODULE_HANDLE            classPtr,
                                COMP_HANDLE                      compHnd,
                                CORINFO_METHOD_INFO*             methodInfo,
                                void**                           methodCodePtr,
                                ULONG*                           methodCodeSize,
                                JitFlags*                        compileFlags,
                                CorInfoInstantiationVerification instVerInfo)
{
    // 省略部分代码......
    // 初始化本地变量表
    lvaInitTypeRef();
    
    // 省略部分代码......
    // 查找所有BasicBlock
    fgFindBasicBlocks();

    // 省略部分代码......
    // 调用3个参数的compCompile函数,注意不是7个函数的compCompile函数
    compCompile(methodCodePtr, methodCodeSize, compileFlags);

    // 省略部分代码......
    return CORJIT_OK;
}

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

//*********************************************************************************************
// #Phases
//
// This is the most interesting 'toplevel' function in the JIT.  It goes through the operations of
// importing, morphing, optimizations and code generation.  This is called from the EE through the
// code:CILJit::compileMethod function.
//
// For an overview of the structure of the JIT, see:
//   https://github.com/dotnet/coreclr/blob/master/Documentation/botr/ryujit-overview.md
//
void Compiler::compCompile(void** methodCodePtr, ULONG* methodCodeSize, JitFlags* compileFlags)
{
    // 省略部分代码......
    // 转换BasicBlock(基本代码块)到GenTree(语句树)
    fgImport();

    // 省略部分代码......
    // 这里会进行各个处理步骤(Phases),如Inline和优化等
    
    // 省略部分代码......
    // 转换GT_ALLOCOBJ节点到GT_CALL节点(分配内存=调用帮助函数)
    ObjectAllocator objectAllocator(this);
    objectAllocator.Run();

    // 省略部分代码......
    // 创建本地变量表和计算各个变量的引用计数
    lvaMarkLocalVars();

    // 省略部分代码......
    // 具体化语句树
    Lowering lower(this, m_pLinearScan); // PHASE_LOWERING
    lower.Run();

    // 省略部分代码......
    // 生成机器码
    codeGen->genGenerateCode(methodCodePtr, methodCodeSize);
}

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

void Compiler::fgImport()
{
    // 省略部分代码......
    impImport(fgFirstBB);
    // 省略部分代码......
}

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

void Compiler::impImport(BasicBlock* method)
{
    // 省略部分代码......
    /* Import blocks in the worker-list until there are no more */
    while (impPendingList)
    {
        PendingDsc* dsc = impPendingList;
        impPendingList  = impPendingList->pdNext;
        // 省略部分代码......
        /* Now import the block */
        impImportBlock(dsc->pdBB);
    }
}

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

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

}

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

/*****************************************************************************
 *  Import the instr for the given basic block
 */
void Compiler::impImportBlockCode(BasicBlock* block)
{
    // 省略部分代码......
    // 处理CEE_NEWOBJ指令
    case CEE_NEWOBJ:
        // 在这里微软给出了有三种情况
        // 一种是对象是array,一种是对象有活动的长度(例如string),一种是普通的class
        // 在这里我们只分析第三种情况
        // There are three different cases for new
        // Object size is variable (depends on arguments)
        //      1) Object is an array (arrays treated specially by the EE)
        //      2) Object is some other variable sized object (e.g. String)
        //      3) Class Size can be determined beforehand (normal case)
        // In the first case, we need to call a NEWOBJ helper (multinewarray)
        // in the second case we call the constructor with a '0' this pointer
        // In the third case we alloc the memory, then call the constuctor
        
        // 省略部分代码......
        // 创建一个GT_ALLOCOBJ类型的GenTree(语句树)节点,用于分配内存
        op1 = gtNewAllocObjNode(info.compCompHnd->getNewHelper(&resolvedToken, info.compMethodHnd),
                                resolvedToken.hClass, TYP_REF, op1);
        
        // 省略部分代码......
        // 因为GT_ALLOCOBJ仅负责分配内存,我们还需要调用构造函数
        // 这里复用了CEE_CALL指令的处理
        goto CALL;

        // 省略部分代码......
        CALL: // memberRef should be set.
        
            // 省略部分代码......
            // 创建一个GT_CALL类型的GenTree(语句树)节点,用于调用构造函数
            callTyp = impImportCall(opcode, &resolvedToken, constraintCall ? &constrainedResolvedToken : nullptr,
                                    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

CorInfoHelpFunc CEEInfo::getNewHelper(CORINFO_RESOLVED_TOKEN * pResolvedToken, CORINFO_METHOD_HANDLE callerHandle)
{
    // 省略部分代码......
    MethodTable* pMT = VMClsHnd.AsMethodTable();
    
    // 省略部分代码......
    result = getNewHelperStatic(pMT);
    
    // 省略部分代码......
    return result;
}

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

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

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

  • 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

void ObjectAllocator::DoPhase()
{
    // 省略部分代码......
    MorphAllocObjNodes();
}

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

void ObjectAllocator::MorphAllocObjNodes()
{
    // 省略部分代码......
    for (GenTreeStmt* stmt = block->firstStmt(); stmt; stmt = stmt->gtNextStmt)
    {
        // 省略部分代码......
        bool canonicalAllocObjFound = false;

        // 省略部分代码......
        if (op2->OperGet() == GT_ALLOCOBJ)
            canonicalAllocObjFound = true;
        
        // 省略部分代码......
        if (canonicalAllocObjFound)
        {
            // 省略部分代码......
            op2 = MorphAllocObjNodeIntoHelperCall(asAllocObj);
        }
    }
}

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

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

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

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

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

  • 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

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

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

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

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

void* CEEJitInfo::getHelperFtn(CorInfoHelpFunc    ftnNum,         /* IN  */
                               void **            ppIndirection)  /* OUT */
{
    // 省略部分代码......
    pfnHelper = hlpDynamicFuncTable[dynamicFtnNum].pfnHelper;

    // 省略部分代码......
    result = (LPVOID)GetEEFuncEntryPoint(pfnHelper);
    return result;
}

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

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

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

如果您发现该资源为电子书等存在侵权的资源或对该资源描述不正确等,可点击“私信”按钮向作者进行反馈;如作者无回复可进行平台仲裁,我们会在第一时间进行处理!

评价 0 条
风晓L1
粉丝 1 资源 2038 + 关注 私信
最近热门资源
银河麒麟桌面操作系统备份用户数据  125
统信桌面专业版【全盘安装UOS系统】介绍  120
银河麒麟桌面操作系统安装佳能打印机驱动方法  111
银河麒麟桌面操作系统 V10-SP1用户密码修改  105
最近下载排行榜
银河麒麟桌面操作系统备份用户数据 0
统信桌面专业版【全盘安装UOS系统】介绍 0
银河麒麟桌面操作系统安装佳能打印机驱动方法 0
银河麒麟桌面操作系统 V10-SP1用户密码修改 0
作者收入月榜
1

prtyaa 收益393.62元

2

zlj141319 收益218元

3

1843880570 收益214.2元

4

IT-feng 收益209.03元

5

风晓 收益208.24元

6

777 收益172.71元

7

Fhawking 收益106.6元

8

信创来了 收益105.84元

9

克里斯蒂亚诺诺 收益91.08元

10

技术-小陈 收益79.5元

请使用微信扫码

加入交流群

请使用微信扫一扫!