Last active
April 10, 2024 14:06
-
-
Save 303248153/2ca436a0e44e5da1c711de050fa3c13f to your computer and use it in GitHub Desktop.
jit笔记
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
链接 | |
https://github.com/dotnet/coreclr/blob/master/Documentation/botr/ryujit-tutorial.md | |
https://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/botr/ryujit-overview.md | |
https://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/botr/porting-ryujit.md | |
https://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/building/viewing-jit-dumps.md | |
https://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/project-docs/clr-configuration-knobs.md | |
https://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/building/debugging-instructions.md | |
https://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/botr/clr-abi.md | |
https://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/design-docs/finally-optimizations.md | |
https://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/design-docs/jit-call-morphing.md | |
https://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/botr/type-system.md | |
https://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/botr/type-loader.md | |
https://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/botr/method-descriptor.md | |
https://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/botr/virtual-stub-dispatch.md | |
https://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/design-docs/jit-call-morphing.md | |
https://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes(v=vs.110).aspx | |
https://www.microsoft.com/en-us/research/wp-content/uploads/2001/01/designandimplementationofgenerics.pdf | |
https://www.cs.rice.edu/~keith/EMBED/dom.pdf | |
https://www.usenix.org/legacy/events/vee05/full_papers/p132-wimmer.pdf | |
http://aakinshin.net/ru/blog/dotnet/typehandle/ | |
https://en.wikipedia.org/wiki/List_of_CIL_instructions | |
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.arn0008c/index.html | |
http://wiki.osdev.org/X86-64_Instruction_Encoding | |
https://github.com/dotnet/coreclr/issues/12383 | |
https://github.com/dotnet/coreclr/issues/14414 | |
http://ref.x86asm.net/ | |
https://www.onlinedisassembler.com/odaweb/ | |
JIT入口点 | |
Compiler::compCompile | |
如何输出IR | |
env COMPlus_JitDump=Main ./coreapp_jit | |
第一个函数是如何被JIT编译的 | |
core-setup corehost\cli\coreclr.cpp | |
coreclr::initialize | |
return coreclr_initialize | |
dlls\mscoree\unixinterface.cpp | |
CorHost2::CreateAppDomainWithManager | |
hr = host->CreateAppDomainWithManager | |
vm\corhost.cpp | |
CorHost2::CreateAppDomain | |
_gc.setupInfo=prepareDataForSetup.Call_RetOBJECTREF(args); | |
vm\callhelpers.h | |
MDCALLDEF_REFTYPE( Call, FALSE, _RetOBJECTREF, Object*, OBJECTREF) | |
vm\callhelpers.cpp | |
MethodDescCallSite::CallTargetWorker | |
CallDescrWorkerWithHandler(&callDescrData); | |
vm\callhelpers.cpp | |
CallDescrWorkerWithHandler | |
CallDescrWorker(pCallDescrData); | |
vm\amd64\calldescrworkeramd64.S | |
CallDescrWorkerInternal | |
未解析出来 | |
未解析出来 | |
vm\amd64\theprestubamd64.S | |
ThePreStub | |
vm\prestub.cpp | |
PreStubWorker | |
pbRetVal = pMD->DoPrestub(pDispatchingMT); | |
vm\prestub.cpp | |
MethodDesc::DoPrestub | |
pStub = MakeUnboxingStubWorker(this); | |
vm\prestub.cpp | |
MakeUnboxingStubWorker | |
pstub = CreateUnboxingILStubForSharedGenericValueTypeMethods(pUnboxedMD); | |
vm\prestub.cpp | |
CreateUnboxingILStubForSharedGenericValueTypeMethods | |
RETURN Stub::NewStub(JitILStub(pStubMD)); | |
vm\dllimport.cpp | |
JitILStub | |
pCode = pStubMD->MakeJitWorker(NULL, dwFlags, 0); | |
vm\prestub.cpp | |
线程安全的通过JIT编译函数,如果多个线程同时编译会返回同一份编译后的代码 | |
MethodDesc::MakeJitWorker | |
pCode = UnsafeJitFunction(this, ILHeader, flags, flags2, &sizeOfCode); | |
vm\jitinterface.cpp | |
非线程安全的通过JIT编译函数 | |
UnsafeJitFunction | |
res = CallCompileMethodWithSEHWrapper(jitMgr | |
vm\jitinterface.cpp | |
CallCompileMethodWithSEHWrapper | |
pParam->res = invokeCompileMethod | |
vm\jitinterface.cpp | |
invokeCompileMethod | |
CorJitResult ret = invokeCompileMethodHelper | |
vm\jitinterface.cpp | |
invokeCompileMethodHelper | |
ret = jitMgr->m_jit->compileMethod(comp, ...) | |
jit\ee_il_dll.cpp | |
CILJit::compileMethod | |
result = jitNativeCode(methodHandle | |
jit\compiler.cpp | |
jitNativeCode | |
pParam->pComp->compCompile(pParam->methodHnd, pParam->classPtr, pParam->compHnd, pParam->methodInfo, | |
jit\compiler.cpp | |
Compiler::compCompile | |
缩写 | |
https://github.com/dotnet/coreclr/blob/master/Documentation/project-docs/glossary.md | |
HFA: homogeneous floating-point aggregate | |
HVA: homogeneous short vector aggregate | |
LSRA: Linear scan register alloc | |
GT_ASG: Assign (gtlist.h) | |
GT_CHS: flipsign | |
IND: load indirection (*ptr) | |
STOREIND: store indirection (*ptr = value) | |
CSE: Common subexpression elimination (https://en.wikipedia.org/wiki/Common_subexpression_elimination) | |
GC Cookie: https://msdn.microsoft.com/en-us/library/8dbf701c.aspx | |
SSA: Static Single Assignment | |
RMW: Read Modify Write | |
PSPSym: Previous Stack Pointer Symbol | |
LCG: Lightweight Code Generation. An early name for [dynamic methods] | |
问题 | |
BasicBlock的结束 (block.h) | |
BBjumpKinds | |
Each basic block ends with a jump which is described as a value of the following enumeration | |
BBJ_EHFINALLYRET, // block ends with 'endfinally' (for finally or fault) | |
BBJ_EHFILTERRET, // block ends with 'endfilter' | |
BBJ_EHCATCHRET, // block ends with a leave out of a catch (only #if FEATURE_EH_FUNCLETS) | |
BBJ_THROW, // block ends with 'throw' | |
BBJ_RETURN, // block ends with 'ret' | |
BBJ_NONE, // block flows into the next one (no jump) | |
BBJ_ALWAYS, // block always jumps to the target | |
BBJ_LEAVE, // block always jumps to the target, maybe out of guarded | |
// region. Used temporarily until importing | |
BBJ_CALLFINALLY, // block always calls the target finally | |
BBJ_COND, // block conditionally jumps to the target | |
BBJ_SWITCH, // block ends with a switch statement | |
BBJ_COUNT | |
BasicBlock的标志 (block.h) | |
#define BBF_VISITED 0x00000001 // BB visited during optimizations | |
#define BBF_MARKED 0x00000002 // BB marked during optimizations | |
#define BBF_CHANGED 0x00000004 // input/output of this block has changed | |
#define BBF_REMOVED 0x00000008 // BB has been removed from bb-list | |
#define BBF_DONT_REMOVE 0x00000010 // BB should not be removed during flow graph optimizations | |
#define BBF_IMPORTED 0x00000020 // BB byte-code has been imported | |
#define BBF_INTERNAL 0x00000040 // BB has been added by the compiler | |
#define BBF_FAILED_VERIFICATION 0x00000080 // BB has verification exception | |
#define BBF_TRY_BEG 0x00000100 // BB starts a 'try' block | |
#define BBF_FUNCLET_BEG 0x00000200 // BB is the beginning of a funclet | |
#define BBF_HAS_NULLCHECK 0x00000400 // BB contains a null check | |
#define BBF_NEEDS_GCPOLL 0x00000800 // This BB is the source of a back edge and needs a GC Poll | |
#define BBF_RUN_RARELY 0x00001000 // BB is rarely run (catch clauses, blocks with throws etc) | |
#define BBF_LOOP_HEAD 0x00002000 // BB is the head of a loop | |
#define BBF_LOOP_CALL0 0x00004000 // BB starts a loop that sometimes won't call | |
#define BBF_LOOP_CALL1 0x00008000 // BB starts a loop that will always call | |
#define BBF_HAS_LABEL 0x00010000 // BB needs a label | |
#define BBF_JMP_TARGET 0x00020000 // BB is a target of an implicit/explicit jump | |
#define BBF_HAS_JMP 0x00040000 // BB executes a JMP instruction (instead of return) | |
#define BBF_GC_SAFE_POINT 0x00080000 // BB has a GC safe point (a call). More abstractly, BB does not | |
// require a (further) poll -- this may be because this BB has a | |
// call, or, in some cases, because the BB occurs in a loop, and | |
// we've determined that all paths in the loop body leading to BB | |
// include a call. | |
#define BBF_HAS_VTABREF 0x00100000 // BB contains reference of vtable | |
#define BBF_HAS_IDX_LEN 0x00200000 // BB contains simple index or length expressions on an array local var. | |
#define BBF_HAS_NEWARRAY 0x00400000 // BB contains 'new' of an array | |
#define BBF_HAS_NEWOBJ 0x00800000 // BB contains 'new' of an object type. | |
#if FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_) | |
#define BBF_FINALLY_TARGET 0x01000000 // BB is the target of a finally return: where a finally will return during | |
// non-exceptional flow. Because the ARM calling sequence for calling a | |
// finally explicitly sets the return address to the finally target and jumps | |
// to the finally, instead of using a call instruction, ARM needs this to | |
// generate correct code at the finally target, to allow for proper stack | |
// unwind from within a non-exceptional call to a finally. | |
#endif // FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_) | |
#define BBF_BACKWARD_JUMP 0x02000000 // BB is surrounded by a backward jump/switch arc | |
#define BBF_RETLESS_CALL 0x04000000 // BBJ_CALLFINALLY that will never return (and therefore, won't need a paired | |
// BBJ_ALWAYS); see isBBCallAlwaysPair(). | |
#define BBF_LOOP_PREHEADER 0x08000000 // BB is a loop preheader block | |
#define BBF_COLD 0x10000000 // BB is cold | |
#define BBF_PROF_WEIGHT 0x20000000 // BB weight is computed from profile data | |
#ifdef LEGACY_BACKEND | |
#define BBF_FORWARD_SWITCH 0x40000000 // Aux flag used in FP codegen to know if a jmptable entry has been forwarded | |
#else // !LEGACY_BACKEND | |
#define BBF_IS_LIR 0x40000000 // Set if the basic block contains LIR (as opposed to HIR) | |
#endif // LEGACY_BACKEND | |
#define BBF_KEEP_BBJ_ALWAYS 0x80000000 // A special BBJ_ALWAYS block, used by EH code generation. Keep the jump kind | |
// as BBJ_ALWAYS. Used for the paired BBJ_ALWAYS block following the | |
// BBJ_CALLFINALLY block, as well as, on x86, the final step block out of a | |
// finally. | |
GenTree的标志 (gentree.h) | |
#define GTF_ASG 0x00000001 // sub-expression contains an assignment | |
#define GTF_CALL 0x00000002 // sub-expression contains a func. call | |
#define GTF_EXCEPT 0x00000004 // sub-expression might throw an exception | |
#define GTF_GLOB_REF 0x00000008 // sub-expression uses global variable(s) | |
#define GTF_ORDER_SIDEEFF 0x00000010 // sub-expression has a re-ordering side effect | |
// If you set these flags, make sure that code:gtExtractSideEffList knows how to find the tree, | |
// otherwise the C# (run csc /o-) | |
// var v = side_eff_operation | |
// with no use of v will drop your tree on the floor. | |
#define GTF_PERSISTENT_SIDE_EFFECTS (GTF_ASG | GTF_CALL) | |
#define GTF_SIDE_EFFECT (GTF_PERSISTENT_SIDE_EFFECTS | GTF_EXCEPT) | |
#define GTF_GLOB_EFFECT (GTF_SIDE_EFFECT | GTF_GLOB_REF) | |
#define GTF_ALL_EFFECT (GTF_GLOB_EFFECT | GTF_ORDER_SIDEEFF) | |
// The extra flag GTF_IS_IN_CSE is used to tell the consumer of these flags | |
// that we are calling in the context of performing a CSE, thus we | |
// should allow the run-once side effects of running a class constructor. | |
// | |
// The only requirement of this flag is that it not overlap any of the | |
// side-effect flags. The actual bit used is otherwise arbitrary. | |
#define GTF_IS_IN_CSE GTF_BOOLEAN | |
#define GTF_PERSISTENT_SIDE_EFFECTS_IN_CSE (GTF_ASG | GTF_CALL | GTF_IS_IN_CSE) | |
// Can any side-effects be observed externally, say by a caller method? | |
// For assignments, only assignments to global memory can be observed | |
// externally, whereas simple assignments to local variables can not. | |
// | |
// Be careful when using this inside a "try" protected region as the | |
// order of assignments to local variables would need to be preserved | |
// wrt side effects if the variables are alive on entry to the | |
// "catch/finally" region. In such cases, even assignments to locals | |
// will have to be restricted. | |
#define GTF_GLOBALLY_VISIBLE_SIDE_EFFECTS(flags) \ | |
(((flags) & (GTF_CALL | GTF_EXCEPT)) || (((flags) & (GTF_ASG | GTF_GLOB_REF)) == (GTF_ASG | GTF_GLOB_REF))) | |
#define GTF_REVERSE_OPS \ | |
0x00000020 // operand op2 should be evaluated before op1 (normally, op1 is evaluated first and op2 is evaluated | |
// second) | |
#define GTF_REG_VAL \ | |
0x00000040 // operand is sitting in a register (or part of a TYP_LONG operand is sitting in a register) | |
#define GTF_SPILLED 0x00000080 // the value has been spilled | |
#ifdef LEGACY_BACKEND | |
#define GTF_SPILLED_OPER 0x00000100 // op1 has been spilled | |
#define GTF_SPILLED_OP2 0x00000200 // op2 has been spilled | |
#else | |
#define GTF_NOREG_AT_USE 0x00000100 // tree node is in memory at the point of use | |
#endif // LEGACY_BACKEND | |
#define GTF_ZSF_SET 0x00000400 // the zero(ZF) and sign(SF) flags set to the operand | |
#if FEATURE_SET_FLAGS | |
#define GTF_SET_FLAGS 0x00000800 // Requires that codegen for this node set the flags | |
// Use gtSetFlags() to check this flags | |
#endif | |
#define GTF_IND_NONFAULTING 0x00000800 // An indir that cannot fault. GTF_SET_FLAGS is not used on indirs | |
#define GTF_MAKE_CSE 0x00002000 // Hoisted Expression: try hard to make this into CSE (see optPerformHoistExpr) | |
#define GTF_DONT_CSE 0x00004000 // don't bother CSE'ing this expr | |
#define GTF_COLON_COND 0x00008000 // this node is conditionally executed (part of ? :) | |
#define GTF_NODE_MASK (GTF_COLON_COND) | |
#define GTF_BOOLEAN 0x00040000 // value is known to be 0/1 | |
#define GTF_SMALL_OK 0x00080000 // actual small int sufficient | |
#define GTF_UNSIGNED 0x00100000 // with GT_CAST: the source operand is an unsigned type | |
// with operators: the specified node is an unsigned operator | |
#define GTF_LATE_ARG \ | |
0x00200000 // the specified node is evaluated to a temp in the arg list, and this temp is added to gtCallLateArgs. | |
#define GTF_SPILL 0x00400000 // needs to be spilled here | |
#define GTF_SPILL_HIGH 0x00040000 // shared with GTF_BOOLEAN | |
#define GTF_COMMON_MASK 0x007FFFFF // mask of all the flags above | |
#define GTF_REUSE_REG_VAL 0x00800000 // This is set by the register allocator on nodes whose value already exists in the | |
// register assigned to this node, so the code generator does not have to generate | |
// code to produce the value. | |
// It is currently used only on constant nodes. | |
// It CANNOT be set on var (GT_LCL*) nodes, or on indir (GT_IND or GT_STOREIND) nodes, since | |
// it is not needed for lclVars and is highly unlikely to be useful for indir nodes | |
//--------------------------------------------------------------------- | |
// The following flags can be used only with a small set of nodes, and | |
// thus their values need not be distinct (other than within the set | |
// that goes with a particular node/nodes, of course). That is, one can | |
// only test for one of these flags if the 'gtOper' value is tested as | |
// well to make sure it's the right operator for the particular flag. | |
//--------------------------------------------------------------------- | |
// NB: GTF_VAR_* and GTF_REG_* share the same namespace of flags, because | |
// GT_LCL_VAR nodes may be changed to GT_REG_VAR nodes without resetting | |
// the flags. These are also used by GT_LCL_FLD. | |
#define GTF_VAR_DEF 0x80000000 // GT_LCL_VAR -- this is a definition | |
#define GTF_VAR_USEASG 0x40000000 // GT_LCL_VAR -- this is a use/def for a x<op>=y | |
#define GTF_VAR_USEDEF 0x20000000 // GT_LCL_VAR -- this is a use/def as in x=x+y (only the lhs x is tagged) | |
#define GTF_VAR_CAST 0x10000000 // GT_LCL_VAR -- has been explictly cast (variable node may not be type of local) | |
#define GTF_VAR_ITERATOR 0x08000000 // GT_LCL_VAR -- this is a iterator reference in the loop condition | |
#define GTF_VAR_CLONED 0x01000000 // GT_LCL_VAR -- this node has been cloned or is a clone | |
// Relevant for inlining optimizations (see fgInlinePrependStatements) | |
// Cleanup: Currently, GTF_REG_BIRTH is used only by stackfp | |
// We should consider using it more generally for VAR_BIRTH, instead of | |
// GTF_VAR_DEF && !GTF_VAR_USEASG | |
#define GTF_REG_BIRTH 0x04000000 // GT_REG_VAR -- enregistered variable born here | |
#define GTF_VAR_DEATH 0x02000000 // GT_LCL_VAR, GT_REG_VAR -- variable dies here (last use) | |
#define GTF_VAR_ARR_INDEX 0x00000020 // The variable is part of (the index portion of) an array index expression. | |
// Shares a value with GTF_REVERSE_OPS, which is meaningless for local var. | |
#define GTF_LIVENESS_MASK (GTF_VAR_DEF | GTF_VAR_USEASG | GTF_VAR_USEDEF | GTF_REG_BIRTH | GTF_VAR_DEATH) | |
#define GTF_CALL_UNMANAGED 0x80000000 // GT_CALL -- direct call to unmanaged code | |
#define GTF_CALL_INLINE_CANDIDATE 0x40000000 // GT_CALL -- this call has been marked as an inline candidate | |
#define GTF_CALL_VIRT_KIND_MASK 0x30000000 | |
#define GTF_CALL_NONVIRT 0x00000000 // GT_CALL -- a non virtual call | |
#define GTF_CALL_VIRT_STUB 0x10000000 // GT_CALL -- a stub-dispatch virtual call | |
#define GTF_CALL_VIRT_VTABLE 0x20000000 // GT_CALL -- a vtable-based virtual call | |
#define GTF_CALL_NULLCHECK 0x08000000 // GT_CALL -- must check instance pointer for null | |
#define GTF_CALL_POP_ARGS 0x04000000 // GT_CALL -- caller pop arguments? | |
#define GTF_CALL_HOISTABLE 0x02000000 // GT_CALL -- call is hoistable | |
#define GTF_CALL_REG_SAVE 0x01000000 // GT_CALL -- This call preserves all integer regs | |
// For additional flags for GT_CALL node see GTF_CALL_M_ | |
#define GTF_NOP_DEATH 0x40000000 // GT_NOP -- operand dies here | |
#define GTF_FLD_NULLCHECK 0x80000000 // GT_FIELD -- need to nullcheck the "this" pointer | |
#define GTF_FLD_VOLATILE 0x40000000 // GT_FIELD/GT_CLS_VAR -- same as GTF_IND_VOLATILE | |
#define GTF_INX_RNGCHK 0x80000000 // GT_INDEX -- the array reference should be range-checked. | |
#define GTF_INX_REFARR_LAYOUT 0x20000000 // GT_INDEX -- same as GTF_IND_REFARR_LAYOUT | |
#define GTF_INX_STRING_LAYOUT 0x40000000 // GT_INDEX -- this uses the special string array layout | |
#define GTF_IND_VOLATILE 0x40000000 // GT_IND -- the load or store must use volatile sematics (this is a nop | |
// on X86) | |
#define GTF_IND_REFARR_LAYOUT 0x20000000 // GT_IND -- the array holds object refs (only effects layout of Arrays) | |
#define GTF_IND_TGTANYWHERE 0x10000000 // GT_IND -- the target could be anywhere | |
#define GTF_IND_TLS_REF 0x08000000 // GT_IND -- the target is accessed via TLS | |
#define GTF_IND_ASG_LHS 0x04000000 // GT_IND -- this GT_IND node is (the effective val) of the LHS of an | |
// assignment; don't evaluate it independently. | |
#define GTF_IND_UNALIGNED 0x02000000 // GT_IND -- the load or store is unaligned (we assume worst case | |
// alignment of 1 byte) | |
#define GTF_IND_INVARIANT 0x01000000 // GT_IND -- the target is invariant (a prejit indirection) | |
#define GTF_IND_ARR_LEN 0x80000000 // GT_IND -- the indirection represents an array length (of the REF | |
// contribution to its argument). | |
#define GTF_IND_ARR_INDEX 0x00800000 // GT_IND -- the indirection represents an (SZ) array index | |
#define GTF_IND_FLAGS \ | |
(GTF_IND_VOLATILE | GTF_IND_REFARR_LAYOUT | GTF_IND_TGTANYWHERE | GTF_IND_NONFAULTING | GTF_IND_TLS_REF | \ | |
GTF_IND_UNALIGNED | GTF_IND_INVARIANT | GTF_IND_ARR_INDEX) | |
#define GTF_CLS_VAR_ASG_LHS 0x04000000 // GT_CLS_VAR -- this GT_CLS_VAR node is (the effective val) of the LHS | |
// of an assignment; don't evaluate it independently. | |
#define GTF_ADDR_ONSTACK 0x80000000 // GT_ADDR -- this expression is guaranteed to be on the stack | |
#define GTF_ADDRMODE_NO_CSE 0x80000000 // GT_ADD/GT_MUL/GT_LSH -- Do not CSE this node only, forms complex | |
// addressing mode | |
#define GTF_MUL_64RSLT 0x40000000 // GT_MUL -- produce 64-bit result | |
#define GTF_MOD_INT_RESULT 0x80000000 // GT_MOD, -- the real tree represented by this | |
// GT_UMOD node evaluates to an int even though | |
// its type is long. The result is | |
// placed in the low member of the | |
// reg pair | |
#define GTF_RELOP_NAN_UN 0x80000000 // GT_<relop> -- Is branch taken if ops are NaN? | |
#define GTF_RELOP_JMP_USED 0x40000000 // GT_<relop> -- result of compare used for jump or ?: | |
#define GTF_RELOP_QMARK 0x20000000 // GT_<relop> -- the node is the condition for ?: | |
#define GTF_RELOP_SMALL 0x10000000 // GT_<relop> -- We should use a byte or short sized compare (op1->gtType | |
// is the small type) | |
#define GTF_RELOP_ZTT 0x08000000 // GT_<relop> -- Loop test cloned for converting while-loops into do-while | |
// with explicit "loop test" in the header block. | |
#define GTF_QMARK_CAST_INSTOF 0x80000000 // GT_QMARK -- Is this a top (not nested) level qmark created for | |
// castclass or instanceof? | |
#define GTF_BOX_VALUE 0x80000000 // GT_BOX -- "box" is on a value type | |
#define GTF_ICON_HDL_MASK 0xF0000000 // Bits used by handle types below | |
#define GTF_ICON_SCOPE_HDL 0x10000000 // GT_CNS_INT -- constant is a scope handle | |
#define GTF_ICON_CLASS_HDL 0x20000000 // GT_CNS_INT -- constant is a class handle | |
#define GTF_ICON_METHOD_HDL 0x30000000 // GT_CNS_INT -- constant is a method handle | |
#define GTF_ICON_FIELD_HDL 0x40000000 // GT_CNS_INT -- constant is a field handle | |
#define GTF_ICON_STATIC_HDL 0x50000000 // GT_CNS_INT -- constant is a handle to static data | |
#define GTF_ICON_STR_HDL 0x60000000 // GT_CNS_INT -- constant is a string handle | |
#define GTF_ICON_PSTR_HDL 0x70000000 // GT_CNS_INT -- constant is a ptr to a string handle | |
#define GTF_ICON_PTR_HDL 0x80000000 // GT_CNS_INT -- constant is a ldptr handle | |
#define GTF_ICON_VARG_HDL 0x90000000 // GT_CNS_INT -- constant is a var arg cookie handle | |
#define GTF_ICON_PINVKI_HDL 0xA0000000 // GT_CNS_INT -- constant is a pinvoke calli handle | |
#define GTF_ICON_TOKEN_HDL 0xB0000000 // GT_CNS_INT -- constant is a token handle | |
#define GTF_ICON_TLS_HDL 0xC0000000 // GT_CNS_INT -- constant is a TLS ref with offset | |
#define GTF_ICON_FTN_ADDR 0xD0000000 // GT_CNS_INT -- constant is a function address | |
#define GTF_ICON_CIDMID_HDL 0xE0000000 // GT_CNS_INT -- constant is a class or module ID handle | |
#define GTF_ICON_BBC_PTR 0xF0000000 // GT_CNS_INT -- constant is a basic block count pointer | |
#define GTF_ICON_FIELD_OFF 0x08000000 // GT_CNS_INT -- constant is a field offset | |
#define GTF_BLK_VOLATILE 0x40000000 // GT_ASG, GT_STORE_BLK, GT_STORE_OBJ, GT_STORE_DYNBLK | |
// -- is a volatile block operation | |
#define GTF_BLK_UNALIGNED 0x02000000 // GT_ASG, GT_STORE_BLK, GT_STORE_OBJ, GT_STORE_DYNBLK | |
// -- is an unaligned block operation | |
#define GTF_BLK_INIT 0x01000000 // GT_ASG, GT_STORE_BLK, GT_STORE_OBJ, GT_STORE_DYNBLK -- is an init block operation | |
#define GTF_OVERFLOW 0x10000000 // GT_ADD, GT_SUB, GT_MUL, - Need overflow check | |
// GT_ASG_ADD, GT_ASG_SUB, | |
// GT_CAST | |
// Use gtOverflow(Ex)() to check this flag | |
#define GTF_NO_OP_NO 0x80000000 // GT_NO_OP --Have the codegenerator generate a special nop | |
#define GTF_ARR_BOUND_INBND 0x80000000 // GT_ARR_BOUNDS_CHECK -- have proved this check is always in-bounds | |
#define GTF_ARRLEN_ARR_IDX 0x80000000 // GT_ARR_LENGTH -- Length which feeds into an array index expression | |
#define GTF_LIST_AGGREGATE 0x80000000 // GT_LIST -- Indicates that this list should be treated as an | |
// anonymous aggregate value (e.g. a multi-value argument). | |
//---------------------------------------------------------------- | |
#define GTF_STMT_CMPADD 0x80000000 // GT_STMT -- added by compiler | |
#define GTF_STMT_HAS_CSE 0x40000000 // GT_STMT -- CSE def or use was subsituted | |
//---------------------------------------------------------------- | |
#if defined(DEBUG) | |
#define GTF_DEBUG_NONE 0x00000000 // No debug flags. | |
#define GTF_DEBUG_NODE_MORPHED 0x00000001 // the node has been morphed (in the global morphing phase) | |
#define GTF_DEBUG_NODE_SMALL 0x00000002 | |
#define GTF_DEBUG_NODE_LARGE 0x00000004 | |
#define GTF_DEBUG_NODE_MASK 0x00000007 // These flags are all node (rather than operation) properties. | |
#define GTF_DEBUG_VAR_CSE_REF 0x00800000 // GT_LCL_VAR -- This is a CSE LCL_VAR node | |
#endif // defined(DEBUG) | |
bbCatchTyp 的类型 | |
// Some non-zero value that will not collide with real tokens for bbCatchTyp | |
#define BBCT_NONE 0x00000000 | |
#define BBCT_FAULT 0xFFFFFFFC | |
#define BBCT_FINALLY 0xFFFFFFFD | |
#define BBCT_FILTER 0xFFFFFFFE | |
#define BBCT_FILTER_HANDLER 0xFFFFFFFF | |
JIT中的数据结构和关系 | |
MethodTable (vm\methodtable.h) | |
表示各个普通的独立类型, Object指向的类型信息 | |
泛型类型实例化一个就会生成一个新的MethodTable | |
TypeDesc (vm\typedesc.h) | |
表示特殊类型, 包括 | |
TypeVarTypeDesc: 泛型类型, 例如List<T>中的T, 不共享 | |
FnPtrTypeDesc: 函数指针, C#不支持, Managed c++用 | |
ParamTypeDesc: byref或者指针类型, byref是使用ref或者out传递的类型, 指针类型Unsafe c#或者Managed c++用 | |
ArrayTypeDesc: 数组类型 | |
TypeHandle (vm\typehandle.h) | |
保存指向MethodTable或者TypeDesc的指针 | |
保存TypeDesc时指针值会|=2, 用于判断指针的类型 | |
CorElementType (inc\corhdr.h) | |
元素类型的枚举,有ELEMENT_TYPE_BOOLEAN ELEMENT_TYPE_CHAR等 | |
EEClass (vm\class.h) | |
EE使用的类型信息, 包含是否抽象或者是否接口等Cold Data(运行时不需要, 只在加载类型和反射和JIT时需要的数据) | |
一般和MethodTable是一对一的关系,除非MethodTable是泛型的实例化 | |
多个泛型的实例化的MethodTable会共享一个EEClass, 而EEClass.GetMethodTable会返回Canonical MT | |
MethodDesc (vm\method.hpp) | |
函数信息, 由EEClass引用, 保存在MDChunks中 | |
FieldDesc (vm\field.hpp) | |
字段信息, 由EEClass引用 | |
CORINFO_METHOD_INFO | |
从MethodDesc获取的公开函数信息, 包括ILCode和ILCodeSize | |
JIT里面会使用getMethodInfoHelper | |
CORINFO_EH_CLAUSE | |
例外处理器的信息 | |
成员 | |
Flags | |
CORINFO_EH_CLAUSE_FLAGS | |
CORINFO_EH_CLAUSE_NONE = 0, | |
CORINFO_EH_CLAUSE_FILTER = 0x0001, // If this bit is on, then this EH entry is for a filter | |
CORINFO_EH_CLAUSE_FINALLY = 0x0002, // This clause is a finally clause | |
CORINFO_EH_CLAUSE_FAULT | |
TryOffset try块的开始偏移值 | |
TryLength try块的长度 | |
HandlerOffset catch或finally块的开始偏移值 | |
HandlerLength catch或finally块的长度 | |
union { ClassToken, FilterOffset } | |
EHblkDsc | |
包含了例外处理器的块信息 | |
成员 | |
ebdTryBeg try开始的BasicBlock | |
ebdTryLast try结束的BasicBlock | |
ebdHndBeg handler开始的BasicBlock | |
ebdHndLast handler结束的BasicBlock | |
union { | |
ebdFilter 如果是filter, 这里保存filter开始的BasicBlock | |
ebdTyp 如果是catch, 这里保存捕捉的class token | |
} | |
ebdHandlerType handler类型, 有 catch filter fault finally | |
ebdEnclosingTryIndex 如果try有嵌套, 这里保存外层try的信息的索引值 | |
ebdEnclosingHndIndex 如果handler有嵌套, 这里保存外层handler的信息的索引值 | |
ebdFuncIndex eh funclet的索引值 | |
ebdTryBegOffset try开始的il偏移值 | |
ebdTryEndOffset try结束的il偏移值 | |
ebdFilterBegOffset filter开始的il偏移值 | |
ebdHndBegOffset handler开始的il偏移值 | |
ebdHndEndOffset handler结束的il偏移值 | |
LIR::Range | |
包含了一条或者多条IL指令 | |
GenTree | |
语法节点, 根据IL指令构建 | |
成员 | |
gtOper 运算符, 有 GT_NOP GT_ADDR 等 | |
gtType 评价后的类型, 有 TYP_VOID TYP_INT 等 | |
gtOperSave 销毁gentree时保存gtOper的成员, 仅用于debug | |
gtCSEnum 执行CSE优化时, 如果找到则设置optCSEhash中的索引值 | |
gtLIRFlags LIR中使用的标记 | |
gtAssertionNum 给tree分配的optAssertionTabPrivate中的索引值, 可断言两个tree相等等 | |
gtCostsInitialized gtCost是否已初始化 | |
_gtCostEx 执行成本 | |
_gtCostSz 代码体积成本 | |
gtRegTag gtRegNum和gtRegPair是否已分配, 仅用于debug | |
union { | |
_gtRegNum 对应的寄存器 | |
_gtRegPair 对应的两个寄存器, 仅在使用两个寄存器时使用 | |
} | |
gtFlags 标记, 见 GTF_ 开头的值 | |
gtDebugFlags 除错用的标记, 见 GTF_DEBUG_ 开头的值 | |
gtVNPair 对应的Value Number, 可以用于识别两个tree的值是否一定会一样, 可用于CSE优化 | |
gtRsvdRegs 执行后会销毁的寄存器集合 | |
gtLsraInfo 使用LSRA分配寄存器时使用的信息 | |
gtNext LIR中下一个tree | |
gtPrev LIR中上一个tree | |
gtTreeID tree的id, 在函数中唯一, 仅用于debug | |
gtSeqNum LIR中的tree的序列顺序, 仅用于debug | |
GenTreeStmt | |
一个完整的表达式, BasicBlock由一个或者多个GenTreeStmt组成 | |
BasicBlock | |
包含一批IL指令,最后一条指令可能是跳转或者返指令,其他指令都不应该是跳转或返回指令 | |
成员 | |
LIR::Range : LIR::ReadOnlyRange { | |
m_firstNode, LIR中的第一个tree | |
m_lastNode LIR中的最后一个tree | |
} | |
bbNext 后一个BasicBlock | |
bbPrev 前一个BasicBlock | |
bbNum 序号,按原始指令顺序排列 | |
bbPostOrderNum 使用postorder枚举block时的序列顺序 | |
bbRefs 引用数量,等于0表示死代码 | |
bbFlags 标志,看上面的BasicBlock的标志 | |
bbWeight block的重量, 值越大表示block越热(容易被执行) | |
bbJumpKind 跳转到其他BasicBlock的类型,看上面的BBjumpKinds | |
union { | |
bbJumpOffs, 跳转到的IL偏移值,会在后面替换成bbJumpDest | |
bbJumpDest, 跳转到的目标BasicBlock值 | |
bbJumpSwt, 跳转使用的Switch信息 | |
} | |
bbEntryState stack信息, 包含了this是否已初始化和StackEntry的数组 | |
bbStkTempsIn 来源溢出的临时变量的开始序号 | |
bbStkTempsOut 自身溢出的临时变量的开始序号 | |
bbTryIndex 如果代码在try中,对应的EHTable的序号 | |
bbHndIndex 如果代码在catch中,对应的EHTable的序号 | |
bbCatchTyp catch中的第一个BasicBlock会设置这个类型 | |
union { bbStkDepth, bbFPinVars } | |
union { bbCheapPreds, bbPreds } | |
bbReach 可以到达此block的block集合, 递归并包含block自身 | |
bbIDom block的dominator, 参考下面的"Reachable和Dominates的区别" | |
bbDfsNum 使用DFS reverse post order探索block是的序列顺序 | |
bbDoms block的所有dominator的集合, 仅用于assertion prop(断言传播) | |
bbCodeOffs 块中IL指令的开始地址 | |
bbCodeOffsEnd 块中IL指令的结束地址,不包含此地址上的指令 | |
bbVarUse 使用过的本地变量集合 | |
bbVarDef 修改过的本地变量集合 | |
bbVarTmp 临时变量 | |
bbLiveIn 进入block时存活的变量集合 | |
bbLiveOut 离开block后存活的变量集合 | |
bbHeapUse 是否使用过全局heap | |
bbHeapDef 是否修改过全局heap | |
bbHeapLiveIn 进入blob时全局heap是否存活 | |
bbHeapLiveOut 离开blob后全局heap是否存活 | |
bbHeapHavoc 是否会让全局heap进入未知的状态 | |
bbHeapSsaPhiFunc EmptyHeapPhiDefn或者HeapPhiArg的链表 | |
bbHeapSsaNumIn 进入block时全局heap的ssa序号 | |
bbHeapSsaNumOut 离开block时全局heap的ssa序号 | |
bbScope 哪些变量在block所在的scope, 用于debug支持(它们不一定会在bbVarUse和bbVarDef中) | |
union { bbCseGen, bbAssertionGen } 从block生成的cse或者assertion的索引值的bit集合 | |
union { bbAssertionKill } 该block结束的assertion的索引值的bit集合 | |
union { bbCseIn, bbAssertionIn } 进入block时有效的cse或者assertion的索引值的bit集合 | |
union { bbCseOut, bbAssertionOut } 离开block时有效的cse或者assertion的索引值的bit集合 | |
bbEmitCookie block对应的insGroup*(汇编指令的分组的指针) | |
bbLoopNum | |
bbNatLoopNum | |
bbTgtStkDepth | |
bbStmtNum | |
bbTraversalStamp | |
函数 | |
BasicBlock::NumSucc(Compiler* comp) | |
获取下一个BasicBlock的数量 | |
BBJ_THROW 和 BBJ_RETURN 返回 0 | |
BBJ_COND 返回 bbJumpDest == bbNext ? 1 : 2 | |
BBJ_SWITCH 返回 bbsCount 等 | |
BasicBlock::GetSucc(unsigned i, Compiler* comp) | |
获取指定位置的下一个Block | |
Compiler | |
编译单个函数使用的类 | |
会根据不同函数单独创建 | |
成员 | |
hbvGlobalData | |
verbose | |
dumpIR | |
dumpIRNodes | |
dumpIRTypes | |
dumpIRKinds | |
dumpIRLocals | |
dumpIRRegs | |
dumpIRSsa | |
dumpIRValnums | |
dumpIRCosts | |
dumpIRNoLists | |
dumpIRNoStmts | |
dumpIRTrees | |
dumpIRLinear | |
dumpIRDataflow | |
dumpIRBlockHeaders | |
dumpIRExit | |
dumpIRPhase | |
dumpIRFormat | |
verboseTrees | |
asciiTrees | |
verboseSsa | |
treesBeforeAfterMorph | |
morphNum | |
expensiveDebugCheckLevel | |
ehnTree | |
ehnNext | |
m_blockToEHPreds | |
fgNeedToSortEHTable | |
fgSafeBasicBlockCreation | |
lvaRefCountingStarted | |
lvaLocalVarRefCounted | |
lvaSortAgain | |
lvaTrackedFixed | |
lvaCount | |
lvaRefCount | |
lvaTable | |
lvaTableCnt | |
lvaRefSorted | |
lvaTrackedCount | |
lvaTrackedCountInSizeTUnits | |
lvaFirstStackIncomingArgNum | |
lvaTrackedVars | |
lvaFloatVars | |
lvaCurEpoch | |
lvaTrackedToVarNum | |
lvaVarPref | |
lvaVarargsHandleArg | |
lvaInlinedPInvokeFrameVar | |
lvaReversePInvokeFrameVar | |
lvaPInvokeFrameRegSaveVar | |
lvaMonAcquired | |
lvaArg0Var | |
lvaInlineeReturnSpillTemp | |
lvaOutgoingArgSpaceVar | |
lvaOutgoingArgSpaceSize | |
lvaReturnEspCheck | |
lvaGenericsContextUsed | |
lvaCachedGenericContextArgOffs | |
lvaLocAllocSPvar | |
lvaNewObjArrayArgs | |
lvaGSSecurityCookie | |
lvaSecurityObject | |
lvaStubArgumentVar | |
lvaPSPSym | |
impInlineInfo | |
m_inlineStrategy | |
fgNoStructPromotion | |
fgNoStructParamPromotion | |
lvaMarkRefsCurBlock | |
lvaMarkRefsCurStmt | |
lvaMarkRefsWeight | |
lvaHeapPerSsaData | |
lvaHeapNumSsaNames | |
impStkSize | |
impSmallStack | |
impTreeList | |
impTreeLast | |
impTokenLookupContextHandle | |
impCurOpcOffs | |
impCurOpcName | |
impNestedStackSpill | |
impLastILOffsStmt | |
impCurStmtOffs | |
impPendingList | |
impPendingFree | |
impPendingBlockMembers | |
impCanReimport | |
impSpillCliquePredMembers | |
impSpillCliqueSuccMembers | |
impBlockListNodeFreeList | |
seenConditionalJump | |
fgFirstBB | |
fgLastBB | |
fgFirstColdBlock | |
fgFirstFuncletBB | |
fgFirstBBScratch | |
fgReturnBlocks | |
fgEdgeCount | |
fgBBcount | |
fgBBcountAtCodeGen | |
fgBBNumMax | |
fgDomBBcount | |
fgBBInvPostOrder | |
fgDomTreePreOrder | |
fgDomTreePostOrder | |
fgBBVarSetsInited | |
fgCurBBEpoch | |
fgCurBBEpochSize | |
fgBBSetCountInSizeTUnits | |
fgMultipleNots | |
fgModified | |
fgComputePredsDone | |
fgCheapPredsValid | |
fgDomsComputed | |
fgHasSwitch | |
fgHasPostfix | |
fgIncrCount | |
fgEnterBlks | |
fgReachabilitySetsValid | |
fgEnterBlksSetValid | |
fgRemoveRestOfBlock | |
fgStmtRemoved | |
fgOrder | |
ftStmtRemoved | |
fgOrder | |
fgStmtListThreaded | |
fgCanRelocateEHRegions | |
fgEdgeWeightsComputed | |
fgHaveValidEdgeWeights | |
fgSlopUsedInEdgeWeights | |
fgRangeUsedInEdgeWeights | |
fgNeedsUpdateFlowGraph | |
fgCalledWeight | |
fgFuncletsCreated | |
fgGlobalMorph | |
fgExpandInline | |
impBoxTempInUse | |
impBoxTempInUsejitFallbackCompile | |
impInlinedCodeSize | |
fgReturnCount | |
fgMarkIntfUnionVS | |
m_opAsgnVarDefSsaNums | |
m_indirAssignMap | |
fgSsaPassesCompleted | |
vnStore | |
fgVNPassesCompleted | |
fgCurHeapVN | |
fgGCPollsCreated | |
m_switchDescMap | |
fgLoopCallMarked | |
fgBBs | |
fgProfileData_ILSizeMismatch | |
fgProfileBuffer | |
fgProfileBufferCount | |
fgNumProfileRuns | |
fgTreeSeqNum | |
fgTreeSeqBeg | |
fgPtrArgCntCur | |
fgPtrArgCntMax | |
fgOutgoingArgTemps | |
fgCurrentlyInUseArgTemps | |
fgPreviousCandidateSIMDFieldAsgStmt | |
fgMorphStmt | |
fgCurUseSet | |
fgCurDefSet | |
fgCurHeapUse | |
fgCurHeapDef | |
fgCurHeapHavoc | |
fgAddCodeList | |
fgAddCodeModf | |
fgRngChkThrowAdded | |
fgExcptnTargetCache | |
fgBigOffsetMorphingTemps | |
fgPrintInlinedMethods | |
fgHasLoops | |
optLoopTable | |
optLoopCount | |
optCallCount | |
optIndirectCallCount | |
optNativeCallCount | |
optLoopsCloned | |
optCSEhash | |
optCSEtab | |
optDoCSE | |
optValnumCSE_phase | |
optCSECandidateTotal | |
optCSECandidateCount | |
optCSEstart | |
optCSEcount | |
optCSEweight | |
optCopyPropKillSet | |
optMethodFlags | |
apTraits | |
apFull | |
apEmpty | |
optAddCopyLclNum | |
optAddCopyAsgnNode | |
optLocalAssertingProp | |
optAssertionPropagated | |
optAssertionPropagatedCurrentStmt | |
optAssertionPropCurrentTree | |
optComplementaryAssertionMap | |
optAssertionDep | |
optAssertionTabPrivate | |
optAssertionCount | |
optMaxAssertionCount | |
bbJtreeAssertionOut | |
optValueNumToAsserts | |
optRngChkCount | |
optLoopsMarked | |
raRegVarsMask | |
rpFrameType | |
rpMustCreateEBPCalled | |
m_pLinerScan | |
eeInfo | |
eeInfoInitialized | |
eeBoundariesCount | |
eeBoundaries | |
eeVarsCount | |
eeVars | |
tmpCount | |
tmpSize | |
tmpGetCount | |
tmpFree | |
tmpUsed | |
codeGen | |
genIPmappingList | |
genIPmappingLast | |
genCallSite2ILOffsetMap | |
genReturnLocal | |
genReturnBB | |
compFuncInfos | |
compCurrFuncIdx | |
compFuncInfoCount | |
compCurLife | |
compCurLifeTree | |
m_promotedStructDethVars | |
featureSIMD | |
lvaSIMDInitTempVarNum | |
SIMDFloatHandle | |
SIMDDoublHandle | |
SIMDIntHandle | |
SIMDUShortHandle | |
SIMDUByteHandle | |
SIMDLongHandle | |
SIMDUIntHandle | |
SIMDULongHandle | |
SIMDVector2Handle | |
SIMDVector3Handle | |
SIMDVector4Handle | |
SIMDVectorHandle | |
SIMDVectorFloat_set_Item | |
SIMDVectorFloat_get_Length | |
SIMDVectorFloat_op_Addition | |
InlineeCompiler | |
compInlineResult | |
compDoAggressiveInlining | |
compJmpOpUsed | |
compLongUsed | |
compFloatingPointUsed | |
compTailCallUsed | |
compLocallocUsed | |
compQmarkUsed | |
compQmarkRationalized | |
compUnsafeCastUsed | |
compQMarks | |
compBlkOpUsed | |
bRangeAllowStress | |
compCodeGenDone | |
compNumStatementLinksTraversed | |
fgNormalizeEHDone | |
compSizeEstimate | |
compCycleEstimate | |
fgLocalVarLivenessDone | |
fgLocalVarLivenessChanged | |
compStackProbePrologDone | |
compLSRADone | |
compRationalIRForm | |
compUsesThrowHelper | |
compGeneratingProlog | |
compGeneratingEpilog | |
compNeedsGSSecurityCookie | |
compGSReorderStackLayout | |
lvaDoneFrameLayout | |
inlRNG | |
s_compMethodsCount | |
compGenTreeID | |
compCurBB | |
compCurStmt | |
compCurStmtNum | |
compInfoBlkSize | |
compInfoBlkAddr | |
compHndBBtab | |
compHndBBtabCount | |
compHndBBtabAllocCount | |
syncStartEmitCookie | |
syncEndEmitCookie | |
previousCompletedPhase | |
compLclFrameSize | |
compCalleeRegsPushed | |
compCalleeFPRegsSavedMask | |
compVSQuirkStackPaddingNeeded | |
compQuirkForPPPflag | |
compArgSize | |
genMemStats | |
m_loopsConsidered | |
m_curLoopHasHoistedExpression | |
m_loopsWithHoistedExpressions | |
m_totalHoistedExpressions | |
compVarScopeMap | |
compAsIAllocator | |
compAsIAllocatorBitset | |
compAsIAllocatorGC | |
compAsIAllocatorLoopHoist | |
compAsIAllocatorDebugOnly | |
tiVerificationNeeded | |
tiIsVerifiableCode | |
tiRuntimeCalloutNeeded | |
tiSecurityCalloutNeeded | |
verCurrentState | |
verTrackObjCtorInitState | |
compMayHaveTransitionBlocks | |
raMaskDontEnregFloat | |
raLclRegIntfFloat | |
raCntStkStackFP | |
raCntWtdStkDblStackFP | |
raCntStkParamDblStackFP | |
raPayloadStackFP | |
raHeightsStackFP | |
raHeightsNonWeightedStackFP | |
compDebugBreak | |
gsGlobalSecurityCookieAddr | |
gsGlobalSecurityCookieVal | |
gsShadowVarInfo | |
gsMarkPtrsAndAssignGroups | |
gsReplaceShadowParams | |
pCompJitTimer | |
s_compJitTimerSummary | |
m_compCyclesAtEndOfInlining | |
m_compCycles | |
m_compTickCountAtEndOfInlining | |
compJitFuncInfoFilename | |
compJitFuncInfoFile | |
prevCompiler | |
m_nodeTestData | |
m_loopHoistCSEClass | |
m_fieldSeqStore | |
m_zeroOffsetFieldMap | |
m_arrayInfoMap | |
m_heapSsaMap | |
m_refAnyClass | |
typeInfo | |
类型信息, 包含类型标记(TI_REF, TI_I_IMPL等)和 class handle(或method table如果是TI_METHOD) | |
BasicBlockList | |
BasicBlock的链表, 有block和next成员 | |
flowList | |
BasicBlock的链表, 包含了block, edge weight和dup count | |
block是edge的来源 | |
edge weight参考下面的说明 | |
dup count是如果block有多个edge目标, 则记录目标次数(仅发生在switch block) | |
StackEntry | |
包含了gentree和typeInfo, 以数组形式保存在EntryState中 | |
EHSuccessorIter | |
用于枚举一个block的eh successor | |
例如 block 1 在try block里面, 则在catch(或finally)中的第一个block是block 1的eh successor | |
AllSuccessorIter | |
用于枚举一个block的普通successor和eh successor | |
FieldSeqNode | |
gentree使用的field信息链表(CORINFO_FIELD_HANDLE) | |
在GT_FIELD转换(lowered)为GT_IND等以后还会保留 | |
FieldSeqStore | |
用于针对同一个CORINFO_FIELD_HANDLE返回同一个FieldSeqNode(单例) | |
GenTreeUseEdgeIterator | |
使用(use)tree的范围的枚举器 | |
gentree有 IteratorPair<GenTreeUseEdgeIterator> UseEdges() 函数可以获取使用范围 | |
枚举器返回的类型是GenTree* | |
GenTreeOperandIterator | |
tree的参数的枚举器 | |
例如unary有一个参数, binary有两个参数, 部分tree有更多的参数 | |
枚举器返回的类型是GenTree* | |
GenTreeUnOp | |
unary operator的tree, 带一个参数, GT_ARR_LENGTH GT_BOX 等 | |
GenTreeVal | |
包含了一个size_t参数的tree, 是一个通用类型(GT_JMP GT_END_LFIN等) | |
GenTreePhysReg | |
包含了一个寄存器参数的tree, GT_PHYSREG (出现时表示使用该寄存器中的值) | |
GenTreeIntCon | |
constant int的tree, 包含int常量, GT_CNS_INT | |
GenTreeLngCon | |
constant long的tree, 包含long常量, GT_CNS_LNG | |
ICodeManager, EECodeManager (inc\eetwain.h, vm\eetwain.cpp) | |
保存了JIT编译后的函数的帧和GC信息 | |
负责处理例外和回滚帧和枚举GC根对象等 | |
RangeSection (codeman.h) | |
包含了JJT编译后的函数PC范围和对应的 PTR_IJitManager | |
用于根据PC定位属于的函数 | |
IJitManager | |
管理JIT编译后的代码, 包含了 ICodeManager | |
EEJitManager : IJitManager | |
包含了 ICorJitCompiler, 从 jit\ee_il_dll.cpp 的 getJit() 函数生成 | |
还有 NativeImageJitManager 和 ReadyToRunJitManager 等实现, 但这里不分析 | |
ExecutionManager (codeman.h) | |
包含了 RangeSection 的链表 (m_CodeRangeList) | |
包含了全局使用的 EEJitManager (m_pEEJitManager) | |
包含了全局使用的 NativeImageJitManager (m_pNativeImageJitManager) | |
包含了全局使用的 ReadyToRunJitManager (m_pReadyToRunJitManager) | |
包含了全局使用的 EECodeManager (m_pDefaultCodeMan) | |
MorphAddrContext | |
用于储存GenTree的地址相关的上下文信息 | |
例如byref节点, 是会被立刻ind还是会使用它的值例如传给其他参数, 可以影响到null检查的方式 | |
一共有三种类型 MACK_Ind, MACK_Addr, MACK_CopyBlock | |
CodeGen | |
负责JIT后端(代码生成)的类 | |
emitter | |
负责写入汇编代码的类 | |
emitLocation | |
用于记录汇编指令的位置(所在ig和ig中的偏移值), 参考CaptureLocation | |
insGroup | |
汇编指令的分组, 作用类似于BasicBlock, 缩写是ig | |
跳转指令只会出现在ig的最后, 跳转目标只能是ig的开头 | |
除了跳转以外, 还会按大小限制和是否可中断切割ig, 这点和BasicBlock不同 | |
instrDesc | |
单个汇编指令的信息, 有很多子类型, 例如 instrDescJmp instrDescCns | |
GCInfo | |
保存了当前函数使用的gc信息, 会使用GcInfoEncoder写入到函数头中 | |
GcInfoEncoder | |
用于写入gc信息到函数头的类 | |
先写入 m_Info1 和 m_Info2, 再合并复制到 pRealCodeHeader->phdrJitGCInfo | |
varPtrDsc | |
用于记录在栈上的gc变量的生命周期, 生成gcinfo时使用 | |
regPtrDsc | |
用于记录在寄存器上的gc变量的生命周期, 生成gcinfo时使用 | |
lclVar和lclFld的区别 | |
lclVar是读取本地变量,例如 var a = 0; 读取a | |
lclFld是读取字段,例如 var b = new MyStruct(); 读取b.x | |
什么是STUB | |
用于在方法第一次调用的时候JIT编译它, 后面再调用就调用JIT编译后的代码 | |
例如JIT前是 call PrecodeFixupThunk; pop esi; dword pMethodDesc; | |
JIT后会变为 jmp target; pop edi; dword pMethodDesc | |
什么是Funclet | |
给finally, catch等区域生成的小函数 | |
拥有独立的prolog和epilog, 调用时会通过call | |
在x86下不会生成 | |
Funclet会接受上一个函数的栈指针用于访问本地函数, 原因是Funclet有可能由EH处理库调用,这时就需要显式传递栈地址 | |
Funclet如果是catch或者filter会返回继续执行的地址 | |
Funclet的格式和内容 | |
下面说明的环境是x64, 不适用于其他平台 | |
来源是 codegencommon.cpp:9875 | |
funclet的传入参数 | |
catch/filter-handler: rcx = InitialSP, rdx = 捕捉到的例外对象(GT_CATCH_ARG) | |
filter: rcx = InitialSP, rdx = 捕捉到的例外对象(GT_CATCH_ARG) | |
finally/fault: rcx = InitialSP | |
funclet的返回参数 | |
catch/filter-handler: rax = 恢复执行的地址(BBJ_EHCATCHRET) | |
filter: rax = 不等于0则表示handler应该处理此例外, 等于0则表示不处理此例外 | |
finally/fault: 无返回值 | |
funclet frame的结构 | |
incoming arguments | |
===================== (Caller's SP) | |
return address | |
saved ebp | |
callee saved registers | |
possible 8 byte pad for alignment | |
PSP slot (本地的PSPSym, [rsp+0x20]) | |
Outgoing arg space (如果funclet会调用其他函数, 大小是0x20) | |
当前的rsp | |
funclet的例子 | |
代码 | |
int x = GetX(); | |
try { | |
Console.WriteLine(x); | |
throw new Exception("abc"); | |
} catch (Exception ex) { | |
Console.WriteLine(ex); | |
Console.WriteLine(x); | |
} | |
生成的主函数 | |
00007FFF0FEC0480 55 push rbp // 备份原rbp | |
00007FFF0FEC0481 56 push rsi // 备份原rsi | |
00007FFF0FEC0482 48 83 EC 38 sub rsp,38h // 预留本地变量空间, 大小0x38 | |
00007FFF0FEC0486 48 8D 6C 24 40 lea rbp,[rsp+40h] // rbp等于push rbp之前rsp的地址(0x38+0x8) | |
00007FFF0FEC048B 48 89 65 E0 mov qword ptr [rbp-20h],rsp // 保存预留本地变量后的rsp, 到本地变量[rbp-0x20], 也就是PSPSym | |
00007FFF0FEC048F E8 24 FC FF FF call 00007FFF0FEC00B8 // 调用GetX() | |
00007FFF0FEC0494 89 45 F4 mov dword ptr [rbp-0Ch],eax // 返回结果存本地变量[rbp-0x0c], 也就是x | |
185: try { | |
186: Console.WriteLine(x); | |
00007FFF0FEC0497 8B 4D F4 mov ecx,dword ptr [rbp-0Ch] // x => 第一个参数 | |
00007FFF0FEC049A E8 B9 FE FF FF call 00007FFF0FEC0358 // 调用Console.WriteLine | |
187: throw new Exception("abc"); | |
00007FFF0FEC049F 48 B9 B8 58 6C 6E FF 7F 00 00 mov rcx,7FFF6E6C58B8h // Exception的MethodTable => 第一个参数 | |
00007FFF0FEC04A9 E8 A2 35 B1 5F call 00007FFF6F9D3A50 // 调用CORINFO_HELP_NEWFAST(JIT_New, 或汇编版本) | |
00007FFF0FEC04AE 48 8B F0 mov rsi,rax // 例外对象存rsi | |
00007FFF0FEC04B1 B9 12 02 00 00 mov ecx,212h // rid => 第一个参数 | |
00007FFF0FEC04B6 48 BA 78 4D D6 0F FF 7F 00 00 mov rdx,7FFF0FD64D78h // module handle => 第二个参数 | |
00007FFF0FEC04C0 E8 6B 20 AF 5F call 00007FFF6F9B2530 // 调用CORINFO_HELP_STRCNS(JIT_StrCns), 用于lazy load字符串常量对象 | |
00007FFF0FEC04C5 48 8B D0 mov rdx,rax // 常量字符串对象 => 第二个参数 | |
00007FFF0FEC04C8 48 8B CE mov rcx,rsi // 例外对象 => 第一个参数 | |
00007FFF0FEC04CB E8 20 07 43 5E call 00007FFF6E2F0BF0 // 调用System.Exception:.ctor | |
00007FFF0FEC04D0 48 8B CE mov rcx,rsi // 例外对象 => 第一个参数 | |
00007FFF0FEC04D3 E8 48 FC A0 5F call 00007FFF6F8D0120 // 调用CORINFO_HELP_THROW(IL_Throw) | |
00007FFF0FEC04D8 CC int 3 // unreachable | |
00007FFF0FEC04D9 48 8D 65 F8 lea rsp,[rbp-8] // 恢复到备份rbp和rsi后的地址 | |
00007FFF0FEC04DD 5E pop rsi // 恢复rsi | |
00007FFF0FEC04DE 5D pop rbp // 恢复rbp | |
00007FFF0FEC04DF C3 ret | |
生成的funclet | |
00007FFF0FEC04E0 55 push rbp // 备份rbp | |
00007FFF0FEC04E1 56 push rsi // 备份rsi | |
00007FFF0FEC04E2 48 83 EC 28 sub rsp,28h // 本地的rsp预留0x28(PSP slot 0x8 + Outgoing arg space 0x20(如果funclet会调用其他函数)) | |
00007FFF0FEC04E6 48 8B 69 20 mov rbp,qword ptr [rcx+20h] // rcx是InitialSP(预留本地变量后的rsp) | |
// 原函数的rbp跟rsp差40, 所以[InitialSP+20h]等于[rbp-20h], 也就是PSPSym | |
// 这个例子中因为只有一层, PSPSym里面保存的值跟传入的rcx一样(InitialSP) | |
00007FFF0FEC04EA 48 89 6C 24 20 mov qword ptr [rsp+20h],rbp // 复制PSPSym到funclet自己的frame | |
00007FFF0FEC04EF 48 8D 6D 40 lea rbp,[rbp+40h] // 原函数的rbp跟rsp差40, 计算得出原函数的rbp | |
188: } catch (Exception ex) { | |
189: Console.WriteLine(ex); | |
00007FFF0FEC04F3 48 8B CA mov rcx,rdx // rdx例外对象, 移动到第一个参数 | |
00007FFF0FEC04F6 E8 7D FE FF FF call 00007FFF0FEC0378 // 调用Console.WriteLine | |
190: Console.WriteLine(x); | |
00007FFF0FEC04FB 8B 4D F4 mov ecx,dword ptr [rbp-0Ch] // [rbp-0xc]就是变量x, 移动到第一个参数 | |
00007FFF0FEC04FE E8 55 FE FF FF call 00007FFF0FEC0358 // 调用Console.WriteLine | |
00007FFF0FEC0503 48 8D 05 CF FF FF FF lea rax,[7FFF0FEC04D9h] // 恢复执行的地址 | |
00007FFF0FEC050A 48 83 C4 28 add rsp,28h // 释放本地的rsp预留的空间 | |
00007FFF0FEC050E 5E pop rsi // 恢复rsi | |
00007FFF0FEC050F 5D pop rbp // 恢复rbp | |
00007FFF0FEC0510 C3 ret | |
什么是Spill Temps | |
BasicBlock 完成后,残留在 ExecutionStack 中的值需要先保存到本地变量 | |
这些临时变量称为 Spill Temps | |
什么是Spill Cliques | |
Spill Temps的群体称为Spill Cliques | |
临时变量的开始地址保存在 bbStkTempsIn 和 bbStkTempsOut 中 | |
什么时候内部(Internal) BasicBlock 会被插入 | |
内部block会被标记为 BBF_INTERNAL | |
第一个block (fgFirstBB) | |
fgEnsureFirstBBisScratch 中插入 | |
多个return归并到一个return的block | |
fgAddInternal 中插入 | |
QMARK的THEN和ELSE | |
QMARK COLON中op1是else, op2是then, 详细见ThenNode和ElseNode函数 | |
什么是Back Edges | |
return 或者是 跳转到前面的 BasicBlock 的边缘 | |
如果一个 BasicBlock 在这个边缘, 则标记它为 BBF_NEEDS_GCPOLL | |
什么是GC Poll | |
GC在部分情况下需要停止所有Managed线程, 所以代码里面需要插入针对GC的检查 | |
如果 block 被标记为 BBF_NEEDS_GCPOLL, 则在它尾部插入调用 CORINFO_HELP_POLL_GC(JIT_PollGC) 的代码 | |
例如循环的跳转block(Back Edge)就需要插入, 否则死循环时会一直让GC等待 | |
JIT_PollGC 会调用 PulseGCMode | |
PulseGCMode 会调用 EnablePreemptiveGC, 然后调用 DisablePreemptiveGC | |
什么是Value Numbering | |
参考: https://en.wikipedia.org/wiki/Global_value_numbering | |
VN是tree值的唯一标记, 如果两个tree的VN相同可以确定两个tree生成的值是一样的, 这个时候就可以执行CSE优化 | |
计算VN需要先计算SSA | |
什么是Cold Block | |
不常运行的代码(basic block)都会归为cold block | |
实际生成汇编代码时会分别存到两处地方 | |
分hot code和cold code的原因是为了增加cpu cache的命中率, 改善代码执行的性能 | |
什么是Block Weight | |
BasicBlock 中的 bbWeight 代表 block 的重量, 值越大表示block越热(容易被执行) | |
什么是Edge Weight | |
flowList* bbPreds 中的 flEdgeWeightMin 和 flEdgeWeightMax | |
可以见 block.h 中的注释 | |
In compiler terminology the control flow between two BasicBlocks | |
is typically referred to as an "edge". Most well known are the | |
backward branches for loops, which are often called "back-edges". | |
两个block之间的跳转可以称作edge | |
edge weight可以代表该跳转发生是否频繁, 值越大越频繁 | |
flEdgeWeightMin和flEdgeWeightMax表示了值的范围, 它们通常是一样的 | |
edge weight的计算在fgComputeEdgeWeights函数中, 生成的信息可以用于fgReorderBlocks优化 | |
Reachable和Dominates的区别 | |
两个block可能互相为reachable但不能互相为dominator | |
如果两个block互相为reachable, 则在dom树中较高的节点是较低的节点的dominator | |
dom树按DFS的after order的相反顺序构建 | |
如果出现 A -> B, A -> C, B -> D, C -> D, 则D的dominator不是B或C而是A, 表示执行D必须经过A | |
参考: https://en.wikipedia.org/wiki/Dominator_(graph_theory) | |
参考: https://www.cs.rice.edu/~keith/EMBED/dom.pdf | |
什么是Dominance Frontier | |
如果出现 A -> B, A -> C, B -> D, C -> D, 则B和C的Dominance Frontier是D | |
因为D是不同的branch的join结果, 对join途中的节点来说D就是Dominance Frontier | |
计算的算法参考: https://en.wikipedia.org/wiki/Static_single_assignment_form | |
什么是Varargs里面的Cookie | |
如果目标函数有__arglist则需要传cookie,值是指向VASigCookie的指针 | |
VASigCookie包括 | |
sizeOfArgs: 在堆栈中的参数的大小 (在寄存器中的不会计算, 不属于__arglist的普通参数也会计算) | |
目前CoreCLR不支持 | |
Phi Node什么时候消失 | |
不会消失, 会一直用到CodeGen | |
ArgPlace是什么 | |
如果call的参数不需要使用临时变量保存, 且参数使用寄存器传递 | |
则把该参数替换为argplace, 然后把原来的式放到 gtCallLateArgs | |
此外标记了 needPlace 的参数(例如nested call)也会替换为 argPlace 节点 | |
最终 gtCallLateArgs 会包含使用寄存器传递和标记为 needPlace 的参数, gtCallArgs 会包含通过 outgoing arg area 或者 栈传递的参数 | |
参考: https://github.com/dotnet/coreclr/blob/fe98261c0fef181197380d0b42c482eb6eddcf94/Documentation/design-docs/jit-call-morphing.md | |
RBM和REG的区别是什么 | |
RBM 是 register bit mask | |
例如 REG_EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI 分别是 0 1 2 3 4 5 6 7 | |
但是 RBM_EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI 分别是 0x01 0x02 0x04 0x08 0x10 0x20 0x40 0x80 | |
此外RAX是EAX的alias, 其他也一样 | |
具体可以看 register.h 和 target.h | |
什么是GTF_CONTAINED | |
This node is contained (executed as part of its parent) | |
什么是Instruction Group | |
可以看作是保存汇编的BasicBlock | |
genProduceReg 和 genConsumeReg 的关系 | |
genConsumeReg | |
在需要使用寄存器的值时调用 | |
确保tree需要的寄存器有需要的内容 | |
如果tree标记了GTF_SPILLED则需要从堆栈上reload | |
这个函数还会更新 | |
codeGen.gcInfo.gcRegByrefSetCur // 当前包含byref的寄存器集合 | |
codeGen.gcInfo.gcRegGCrefSetCur // 当前包含gcref的寄存器集合 | |
codeGen.gcInfo.gcVarPtrSetCur // 当前包含byref或者gcref的栈变量集合 | |
codeGen.regSet // 当前存活的寄存器集合 | |
compiler.compCurLife // 当前存活的本地变量 | |
genProduceReg | |
在寄存器产生了新的值时调用 | |
表示tree产生了寄存器 | |
如果标记了GTF_SPILL则需要store值到堆栈 | |
这个函数还会更新 | |
codeGen.gcInfo.gcRegByrefSetCur // 当前包含byref的寄存器集合 | |
codeGen.gcInfo.gcRegGCrefSetCur // 当前包含gcref的寄存器集合 | |
codeGen.gcInfo.gcVarPtrSetCur // 当前包含byref或者gcref的栈变量集合 | |
codeGen.regSet // 当前存活的寄存器集合 | |
compiler.compCurLife // 当前存活的本地变量 | |
insGroup和instrDesc的结构 | |
构建当前ig的时候会使用 | |
emitCurIGfreeBase 当前ig对应的instrDesc数组的起始地址, 可以使用((instrDesc*)(emitCurIGfreeBase))[0]访问 | |
emitCurIGfreeNext 添加下一个instrDesc的地址, 添加一次增加sizeof(instrDesc) | |
emitCurIGfreeEndp instrDesc数组的结尾地址, 如果超过则会创建下一个ig | |
如果空间不够(emitCurIGfreeBase已用完) | |
会调用 emitNxtIG, 会先调用 emitSavIG 然后调用 emitNewIG | |
构建ig完毕后 | |
会调用 emitSavIG | |
复制 emitCurIGfreeBase ~ emitCurIGfreeNext 到 ig->igData | |
复制instr的数量到 ig->igInsCnt | |
重置 emitCurIGfreeNext = emitCurIGfreeBase | |
创建ig时 | |
调用 emitNewIG => emitGenIG(emitAllocAndLinkIG()) | |
保存ig的链表 | |
emitIGlist ig的链表的第一个元素 | |
emitIGlast ig的链表的最后一个元素 | |
emitCurIG 当前处理的ig | |
访问链表可以使用 emitIGlist->igNext | |
访问ig中的instrDesc可以使用 emitIGlist->igData | |
什么是ReJIT | |
参考: https://github.com/dotnet/coreclr/blob/master/Documentation/botr/clr-abi.md | |
为了支持profiler attach | |
JIT会让所有函数的前5 bytes都不可中断并且不是跳转目标 | |
如果启用了ReJIT并且prolog的大小小于5, 则会补充nop | |
后面需要热替换的时候JIT可以停止所有线程然后把在5个byte中写入跳转指令 | |
什么是PSPSym | |
参考: https://github.com/dotnet/coreclr/blob/master/Documentation/botr/clr-abi.md | |
如果支持eh funclet, | |
需要在调用eh funclet的时候恢复rsp到main function的rsp值, funclet就可以访问到原来的本地变量 | |
PSPSym的全称是Previous Stack Pointer Symbol, 是一个指针大小的值, 保存上一个函数的堆栈地址 | |
在x64上, 它的值是 InitialSP, 也就是fixed size portion(本地变量, 临时变量)已经分配后的大小, 不包括alloca分配的范围 | |
在其他平台上, 它的值是 CallerSP, 也就是调用函数之前的堆栈的值, 包括了前面用alloca分配的范围 | |
在调用 funclet 的时候, 调用者会传递一个 Establisher Frame Pointer (例如在x64上通过rcx) | |
然后 funclet 会通过 Establisher Frame Pointer 找到 PSPSym 的地址, 然后根据 PSPSym 找到之前堆栈的值 | |
最后就可以把 funclet 的 rsp 设为之前函数里面使用的 rsp 值 | |
如何从PC定位到函数的信息 | |
首先根据PC在 Nibble Map 里面找到对应的 pCode | |
pCode 前面是 CodeHeader | |
CodeHeader 里面包含了指向 _hpRealCodeHdr 的指针 pRealCodeHeader | |
pRealCodeHeader 里面包含了 | |
phdrDebugInfo 包含了PC到IL offset的索引 | |
结构: 见下面"Debug Info的生成和结构" | |
phdrJitEHInfo 包含了EH Clause的数组 | |
结构: 见下面"EHInfo的结构" | |
phdrJitGCInfo 包含了GC扫描栈和寄存器使用的信息 | |
结构: 见下面"GCInfo的结构" | |
phdrMDesc 函数的MethodDesc | |
nUnwindInfos unwindInfos的数量 | |
unindInfos unwind信息(栈回滚信息) | |
结构: 见下面"Unwind Info的结构" | |
emitter里面的u1和u2是什么 | |
用于记录push到堆栈的ref变量的状态 | |
u1是启用了 emitSimpleStkUsed 时使用的, 会使用bitmask保存 | |
u2是不启用 emitSimpleStkUsed 时使用的, 会使用一个数组保存 | |
例如 | |
emitArgTrackTab: [ TYPE_GC_NONE, TYPE_GC_NONE, TYPE_GC_REF, TYPE_GC_BYREF, TYPE_GC_NONE, ... ] | |
emitArgTrackTop: emitArgTrackTab + 5 // 下一次push会设在第六个元素 | |
emitGcArgTrackCnt: 2 | |
IF后面的格式 | |
来源: emitfmtsxarch.h | |
IF_XYY | |
X = // first operand | |
R - register | |
M - memory | |
S - stack | |
A - address mode | |
T - x87 st(x) | |
YY = // second operand | |
RD - read | |
WR - write | |
RW - read write | |
IF_CNS constant | |
IF_SHF - shift constant | |
JIT后的代码保存在什么地方 | |
jit的代码保存在loader heap中 | |
流程是 allocCode => allocCodeRaw => AllocMemForCode_NoThrow => UnlockedAllocMemForCode_NoThrow | |
分配时会分配 [ CodeHeader, code ], 函数头部信息总是在代码(汇编代码)前面 | |
实际函数头部信息中只有一个指针值, 指向真正的头部信息(_hpRealCodeHdr) | |
真正的头部信息如果是动态函数则放在代码后面, 否则放在GetLowFrequencyHeap后面 | |
JIT在什么线程中编译 | |
正常情况下会在调用(call)函数的线程中编译 | |
除非使用MulticoreJitProfilePlayer手动编译函数 | |
如何保证同一个函数只JIT一次 | |
会使用线程锁保证 | |
ListLock appdomain中保存entry的链表 | |
ListLockEntry 一个entry保存一个正在jit的methoddesc和线程锁 | |
jit时首先对ListLock上锁(全局锁), 然后获取或者创建ListLockEntry, 然后释放ListLock上的锁 | |
所有对同一个methoddesc编译的函数都会获取到同一个ListLockEntry | |
但是实际上锁可能会失败, 最终不能够保证"同一个函数只JIT一次" | |
如果上锁失败会有多个线程同时执行jit编译, 但是写到stub的只有一个, 另外一个的编译结果会浪费(内存空间) | |
正常情况下, 一个线程正在编译时其他线程会等待锁, 等编译完成后所有等待锁的线程都会得到同一个pCode | |
什么情况会触发JIT编译(懒编译) | |
需要jit懒编译的函数都会有 fixup precode (stub) | |
jit编译前precode中会是调用jit编译的call | |
jit编译后precode中会是跳转到jit编译结果的jmp | |
触发jit编译会在*第一次调用(call)*该函数时 | |
具体流程看下面"JIT Stub的调用和替换流程" | |
JIT Stub的调用和替换流程 | |
jit前 call => Fixup Precode => Fixup Precode Chunk => The PreStub => PreStub Worker => ... | |
jit后 call => Fixup Precode => Compile Result | |
参考: https://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/botr/method-descriptor.md | |
lldb分析的流程 | |
(lldb) b CallDescrWorkerInternal | |
(lldb) process handle -s false SIGUSR1 SIGUSR2 | |
(lldb) r | |
(lldb) p *((CallDescrData*)$rdi) | |
(CallDescrData) $0 = { | |
pSrc = 0x00007fffffffb7e8 | |
numStackSlots = 0 | |
pArgumentRegisters = 0x00007fffffffb780 | |
pFloatArgumentRegisters = 0x0000000000000000 | |
fpReturnSize = 0 | |
pTarget = 140735275988392 | |
returnValue = ([0] = 140737312810959, [1] = 140737488337632) | |
} | |
calldescrworkeramd64.S | |
-> 0x7ffff5bd95ac <CallDescrWorkerInternal+121>: ff 53 28 callq *0x28(%rbx) | |
(lldb) p *(intptr_t*)($rbx+0x28) | |
(intptr_t) $10 = 140735275787688 | |
Fixup Precode | |
(lldb) di --frame --bytes | |
-> 0x7fff7c21f5a8: e8 2b 6c fe ff callq 0x7fff7c2061d8 | |
0x7fff7c21f5ad: 5e popq %rsi | |
0x7fff7c21f5ae: 19 05 e8 23 6c fe sbbl %eax, -0x193dc18(%rip) | |
0x7fff7c21f5b4: ff 5e a8 lcalll *-0x58(%rsi) | |
0x7fff7c21f5b7: 04 e8 addb $-0x18, %al | |
0x7fff7c21f5b9: 1b 6c fe ff sbbl -0x1(%rsi,%rdi,8), %ebp | |
0x7fff7c21f5bd: 5e popq %rsi | |
0x7fff7c21f5be: 00 03 addb %al, (%rbx) | |
0x7fff7c21f5c0: e8 13 6c fe ff callq 0x7fff7c2061d8 | |
0x7fff7c21f5c5: 5e popq %rsi | |
0x7fff7c21f5c6: b0 02 movb $0x2, %al | |
(lldb) di --frame --bytes | |
-> 0x7fff7c2061d8: e9 13 3f 9d 79 jmp 0x7ffff5bda0f0 ; PrecodeFixupThunk | |
0x7fff7c2061dd: cc int3 | |
0x7fff7c2061de: cc int3 | |
0x7fff7c2061df: cc int3 | |
0x7fff7c2061e0: 49 ba 00 da d0 7b ff 7f 00 00 movabsq $0x7fff7bd0da00, %r10 | |
0x7fff7c2061ea: 40 e9 e0 ff ff ff jmp 0x7fff7c2061d0 | |
Fixup Precode Chunk | |
lldb) di --frame --bytes | |
-> 0x7ffff5bda0f0 <PrecodeFixupThunk>: 58 popq %rax ; rax = 0x7fff7c21f5ad | |
0x7ffff5bda0f1 <PrecodeFixupThunk+1>: 4c 0f b6 50 02 movzbq 0x2(%rax), %r10 ; r10 = 0x05 (precode chunk index) | |
0x7ffff5bda0f6 <PrecodeFixupThunk+6>: 4c 0f b6 58 01 movzbq 0x1(%rax), %r11 ; r11 = 0x19 (methoddesc chunk index) | |
0x7ffff5bda0fb <PrecodeFixupThunk+11>: 4a 8b 44 d0 03 movq 0x3(%rax,%r10,8), %rax ; rax = 0x7fff7bdd5040 (methoddesc chunk) | |
0x7ffff5bda100 <PrecodeFixupThunk+16>: 4e 8d 14 d8 leaq (%rax,%r11,8), %r10 ; r10 = 0x7fff7bdd5108 (methoddesc) | |
0x7ffff5bda104 <PrecodeFixupThunk+20>: e9 37 ff ff ff jmp 0x7ffff5bda040 ; ThePreStub | |
(lldb) me re -s1 -fx -c 51 0x7fff7c21f5ad | |
0x7fff7c21f5ad: 0x5e 0x19 0x05 0xe8 0x23 0x6c 0xfe 0xff | |
0x7fff7c21f5b5: 0x5e 0xa8 0x04 0xe8 0x1b 0x6c 0xfe 0xff | |
0x7fff7c21f5bd: 0x5e 0x00 0x03 0xe8 0x13 0x6c 0xfe 0xff | |
0x7fff7c21f5c5: 0x5e 0xb0 0x02 0xe8 0x0b 0x6c 0xfe 0xff | |
0x7fff7c21f5cd: 0x5e 0x3f 0x01 0xe8 0x03 0x6c 0xfe 0xff | |
0x7fff7c21f5d5: 0x5e 0xb8 0x00 [0x40 0x50 0xdd 0x7b 0xff | |
0x7fff7c21f5dd: 0x7f 0x00 0x00] | |
(lldb) dumpmd 0x7fff7bdd5108 | |
Method Name: System.AppDomain.SetupDomain(Boolean, System.String, System.String, System.String[], System.String[]) | |
Class: 00007fff7bce1af0 | |
MethodTable: 00007fff7cc39918 | |
mdToken: 0000000006002DE2 | |
Module: 00007fff7bc2a000 | |
IsJitted: yes | |
CodeAddr: 00007fff7c5c7d50 | |
Transparency: Critical | |
The PreStub (theprestubamd64.S) | |
(lldb) di --frame --bytes | |
-> 0x7ffff5bda040 <ThePreStub>: 55 pushq %rbp | |
0x7ffff5bda041 <ThePreStub+1>: 48 89 e5 movq %rsp, %rbp | |
0x7ffff5bda044 <ThePreStub+4>: 53 pushq %rbx | |
0x7ffff5bda045 <ThePreStub+5>: 41 57 pushq %r15 | |
0x7ffff5bda047 <ThePreStub+7>: 41 56 pushq %r14 | |
0x7ffff5bda049 <ThePreStub+9>: 41 55 pushq %r13 | |
0x7ffff5bda04b <ThePreStub+11>: 41 54 pushq %r12 | |
0x7ffff5bda04d <ThePreStub+13>: 41 51 pushq %r9 | |
0x7ffff5bda04f <ThePreStub+15>: 41 50 pushq %r8 | |
0x7ffff5bda051 <ThePreStub+17>: 51 pushq %rcx | |
0x7ffff5bda052 <ThePreStub+18>: 52 pushq %rdx | |
0x7ffff5bda053 <ThePreStub+19>: 56 pushq %rsi | |
0x7ffff5bda054 <ThePreStub+20>: 57 pushq %rdi | |
0x7ffff5bda055 <ThePreStub+21>: 48 8d a4 24 78 ff ff ff leaq -0x88(%rsp), %rsp ; allocate transition block | |
0x7ffff5bda05d <ThePreStub+29>: 66 0f 7f 04 24 movdqa %xmm0, (%rsp) ; fill transition block | |
0x7ffff5bda062 <ThePreStub+34>: 66 0f 7f 4c 24 10 movdqa %xmm1, 0x10(%rsp) ; fill transition block | |
0x7ffff5bda068 <ThePreStub+40>: 66 0f 7f 54 24 20 movdqa %xmm2, 0x20(%rsp) ; fill transition block | |
0x7ffff5bda06e <ThePreStub+46>: 66 0f 7f 5c 24 30 movdqa %xmm3, 0x30(%rsp) ; fill transition block | |
0x7ffff5bda074 <ThePreStub+52>: 66 0f 7f 64 24 40 movdqa %xmm4, 0x40(%rsp) ; fill transition block | |
0x7ffff5bda07a <ThePreStub+58>: 66 0f 7f 6c 24 50 movdqa %xmm5, 0x50(%rsp) ; fill transition block | |
0x7ffff5bda080 <ThePreStub+64>: 66 0f 7f 74 24 60 movdqa %xmm6, 0x60(%rsp) ; fill transition block | |
0x7ffff5bda086 <ThePreStub+70>: 66 0f 7f 7c 24 70 movdqa %xmm7, 0x70(%rsp) ; fill transition block | |
0x7ffff5bda08c <ThePreStub+76>: 48 8d bc 24 88 00 00 00 leaq 0x88(%rsp), %rdi ; arg 1 = transition block* | |
0x7ffff5bda094 <ThePreStub+84>: 4c 89 d6 movq %r10, %rsi ; arg 2 = methoddesc | |
0x7ffff5bda097 <ThePreStub+87>: e8 44 7e 11 00 callq 0x7ffff5cf1ee0 ; PreStubWorker at prestub.cpp:958 | |
0x7ffff5bda09c <ThePreStub+92>: 66 0f 6f 04 24 movdqa (%rsp), %xmm0 | |
0x7ffff5bda0a1 <ThePreStub+97>: 66 0f 6f 4c 24 10 movdqa 0x10(%rsp), %xmm1 | |
0x7ffff5bda0a7 <ThePreStub+103>: 66 0f 6f 54 24 20 movdqa 0x20(%rsp), %xmm2 | |
0x7ffff5bda0ad <ThePreStub+109>: 66 0f 6f 5c 24 30 movdqa 0x30(%rsp), %xmm3 | |
0x7ffff5bda0b3 <ThePreStub+115>: 66 0f 6f 64 24 40 movdqa 0x40(%rsp), %xmm4 | |
0x7ffff5bda0b9 <ThePreStub+121>: 66 0f 6f 6c 24 50 movdqa 0x50(%rsp), %xmm5 | |
0x7ffff5bda0bf <ThePreStub+127>: 66 0f 6f 74 24 60 movdqa 0x60(%rsp), %xmm6 | |
0x7ffff5bda0c5 <ThePreStub+133>: 66 0f 6f 7c 24 70 movdqa 0x70(%rsp), %xmm7 | |
0x7ffff5bda0cb <ThePreStub+139>: 48 8d a4 24 88 00 00 00 leaq 0x88(%rsp), %rsp | |
0x7ffff5bda0d3 <ThePreStub+147>: 5f popq %rdi | |
0x7ffff5bda0d4 <ThePreStub+148>: 5e popq %rsi | |
0x7ffff5bda0d5 <ThePreStub+149>: 5a popq %rdx | |
0x7ffff5bda0d6 <ThePreStub+150>: 59 popq %rcx | |
0x7ffff5bda0d7 <ThePreStub+151>: 41 58 popq %r8 | |
0x7ffff5bda0d9 <ThePreStub+153>: 41 59 popq %r9 | |
0x7ffff5bda0db <ThePreStub+155>: 41 5c popq %r12 | |
0x7ffff5bda0dd <ThePreStub+157>: 41 5d popq %r13 | |
0x7ffff5bda0df <ThePreStub+159>: 41 5e popq %r14 | |
0x7ffff5bda0e1 <ThePreStub+161>: 41 5f popq %r15 | |
0x7ffff5bda0e3 <ThePreStub+163>: 5b popq %rbx | |
0x7ffff5bda0e4 <ThePreStub+164>: 5d popq %rbp | |
0x7ffff5bda0e5 <ThePreStub+165>: 48 ff e0 jmpq *%rax | |
%rax should be patched fixup precode = 0x7fff7c21f5a8 | |
(%rsp) should be the return address before calling "Fixup Precode" | |
PreStub Worker (prestub.cpp) | |
957 extern "C" PCODE STDCALL PreStubWorker(TransitionBlock * pTransitionBlock, MethodDesc * pMD) | |
958 { | |
-> 959 PCODE pbRetVal = NULL; | |
SetupDomain has prejit code, calling flow would be, | |
PreStubWorker => DoPreStub => GetPreImplementedCode | |
frame #0: 0x00007ffff5cf3772 libcoreclr.so`MethodDesc::DoPrestub(this=0x00007fff7bdd5108, pDispatchingMT=0x0000000000000000) + 3970 at prestub.cpp:1585 | |
1582 | |
1583 if (pCode != NULL) | |
1584 { | |
-> 1585 if (HasPrecode()) | |
1586 GetPrecode()->SetTargetInterlocked(pCode); | |
1587 else | |
1588 if (!HasStableEntryPoint()) | |
frame #0: 0x00007ffff5874224 libcoreclr.so`MethodDesc::GetPrecode(this=0x00007fff7bdd5108) + 68 at method.hpp:293 | |
290 | |
291 PRECONDITION(HasPrecode()); | |
292 Precode* pPrecode = Precode::GetPrecodeFromEntryPoint(GetStableEntryPoint()); | |
-> 293 PREFIX_ASSUME(pPrecode != NULL); | |
294 return pPrecode; | |
295 } | |
296 | |
(lldb) p GetSlot() | |
(WORD) $79 = 69 | |
(lldb) p pPrecode | |
(Precode *) $76 = 0x00007fff7c21f5a8 | |
precode type is 5f (PRECODE_FIXUP = FixupPrecode::Type) | |
FixupPrecode::SetTargetInterlocked will alter the assembly code here | |
(lldb) di --bytes -s 0x7fff7c21f5a8 | |
0x7fff7c21f5a8: e9 a3 87 3a 00 jmp 0x7fff7c5c7d50 | |
0x7fff7c21f5ad: 5f popq %rdi | |
0x7fff7c21f5ae: 19 05 e8 23 6c fe sbbl %eax, -0x193dc18(%rip) | |
0x7fff7c21f5b4: ff 5e a8 lcalll *-0x58(%rsi) | |
0x7fff7c21f5b7: 04 e8 addb $-0x18, %al | |
0x7fff7c21f5b9: 1b 6c fe ff sbbl -0x1(%rsi,%rdi,8), %ebp | |
0x7fff7c21f5bd: 5e popq %rsi | |
0x7fff7c21f5be: 00 03 addb %al, (%rbx) | |
0x7fff7c21f5c0: e8 13 6c fe ff callq 0x7fff7c2061d8 | |
0x7fff7c21f5c5: 5e popq %rsi | |
0x7fff7c21f5c6: b0 02 movb $0x2, %al | |
什么是TransitionBlock | |
调用jit函数前, ThePreStub 会在栈上分配一个结构体 TransitionBlock 用于保存寄存器状态 | |
在x64上会用于保存 xmm0 ~ xmm7, 调用jit函数完毕后会恢复回原来的寄存器 | |
DebugInfo的生成和结构 | |
DebugInfo 在 invokeCompileMethodHelper => CompressDebugInfo => CompressBoundariesAndVars 中生成 | |
来源于以下的变量 | |
CEEJitInfo::m_pOffsetMapping // 类型是 ICorDebugInfo::OffsetMapping, 包含 nativeOffset 和 ilOffset | |
CEEJitInfo::m_iOffsetMapping // offset mapping 数组的长度 | |
CEEJitInfo::m_pNativeVarInfo // 类型是 ICorDebugInfo::NativeVarInfo, 包含内部变量所在的scope的信息(native offset range) | |
CEEJitInfo::m_iNativeVarInfo // native var 数组的长度 | |
格式 | |
保存到 phdrDebugInfo 的格式是 nibble stream, 以4bit为一个单位保存数字 | |
例如 0xa9 0xa0 0x03 代表 80, 19 两个数字 | |
0xa9 = 0b1010'1001 | |
0xa0 = 0b1010'0000 | |
0x03 = 0b0000'0011 | |
001 010 000 => 80 | |
010 011 => 19 | |
格式是 | |
header, 包含两个数字, 第一个是 offset mapping 编码后的长度(bytes), 第二个是 native vars 编码后的长度(bytes) | |
offset mapping | |
offset mapping 的数量 | |
native offset, 写入与前一条记录的偏移值 | |
il offset | |
source 标记(flags), 有 SOURCE_TYPE_INVALID, SEQUENCE_POINT, STACK_EMPTY 等 | |
native vars | |
native vars 的数量 | |
startOffset scope的开始偏移值 | |
endOffset scope的结束偏移值, 写入距离start的delta | |
var number 变量的序号 | |
var type (reg还是stack) | |
后面的信息根据var type而定, 具体参考 DoNativeVarInfo | |
EHInfo的结构 | |
EHInfo保存在 pRealCodeHeader->phdrJitEHInfo 中, 格式如下 | |
phdrJitEHInfo - sizeof(size_t) = EH Clause 的数量 | |
phdrJitEHInfo = CorILMethod_Sect_FatFormat 结构体的内容, 包含类型和长度 | |
phdrJitEHInfo + sizeof(CorILMethod_Sect_FatFormat) = EE_ILEXCEPTION_CLAUSE的数组 | |
具体的生成可以参考下面 genReportEH 函数的解释, 这里给出实际解析GCInfo的例子 | |
源代码 | |
var x = GetString(); | |
try { | |
Console.WriteLine(x); | |
throw new Exception("abc"); | |
} catch (Exception ex) { | |
Console.WriteLine(ex); | |
Console.WriteLine(x); | |
} | |
汇编代码 | |
IN0016: 000000 push rbp | |
IN0017: 000001 push rbx | |
IN0018: 000002 sub rsp, 24 | |
IN0019: 000006 lea rbp, [rsp+20H] | |
IN001a: 00000B mov qword ptr [V06 rbp-20H], rsp | |
G_M21556_IG02: ; offs=00000FH, size=0009H, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref | |
IN0001: 00000F call ConsoleApplication.Program:GetString():ref | |
IN0002: 000014 mov gword ptr [V01 rbp-10H], rax | |
G_M21556_IG03: ; offs=000018H, size=0043H, gcVars=0000000000000001 {V01}, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, gcvars, byref | |
IN0003: 000018 mov rdi, gword ptr [V01 rbp-10H] | |
IN0004: 00001C call System.Console:WriteLine(ref) | |
IN0005: 000021 mov rdi, 0x7F78892D3CE8 | |
IN0006: 00002B call CORINFO_HELP_NEWSFAST | |
IN0007: 000030 mov rbx, rax | |
IN0008: 000033 mov edi, 1 | |
IN0009: 000038 mov rsi, 0x7F78881BCE70 | |
IN000a: 000042 call CORINFO_HELP_STRCNS | |
IN000b: 000047 mov rsi, rax | |
IN000c: 00004A mov rdi, rbx | |
IN000d: 00004D call System.Exception:.ctor(ref):this | |
IN000e: 000052 mov rdi, rbx | |
IN000f: 000055 call CORINFO_HELP_THROW | |
IN0010: 00005A int3 | |
G_M21556_IG04: ; offs=00005BH, size=0007H, gcVars=0000000000000000 {}, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, gcvars, byref, epilog, nogc | |
IN001b: 00005B lea rsp, [rbp-08H] | |
IN001c: 00005F pop rbx | |
IN001d: 000060 pop rbp | |
IN001e: 000061 ret | |
G_M21556_IG05: ; func=01, offs=000062H, size=000EH, gcrefRegs=00000040 {rsi}, byrefRegs=00000000 {}, byref, funclet prolog, nogc | |
IN001f: 000062 push rbp | |
IN0020: 000063 push rbx | |
IN0021: 000064 push rax | |
IN0022: 000065 mov rbp, qword ptr [rdi] | |
IN0023: 000068 mov qword ptr [rsp], rbp | |
IN0024: 00006C lea rbp, [rbp+20H] | |
G_M21556_IG06: ; offs=000070H, size=0018H, gcVars=0000000000000001 {V01}, gcrefRegs=00000040 {rsi}, byrefRegs=00000000 {}, gcvars, byref, isz | |
IN0011: 000070 mov rdi, rsi | |
IN0012: 000073 call System.Console:WriteLine(ref) | |
IN0013: 000078 mov rdi, gword ptr [V01 rbp-10H] | |
IN0014: 00007C call System.Console:WriteLine(ref) | |
IN0015: 000081 lea rax, G_M21556_IG04 | |
G_M21556_IG07: ; offs=000088H, size=0007H, funclet epilog, nogc, emitadd | |
IN0025: 000088 add rsp, 8 | |
IN0026: 00008C pop rbx | |
IN0027: 00008D pop rbp | |
IN0028: 00008E ret | |
LLDB命令 | |
(lldb) p *codePtr | |
(void *) $1 = 0x00007fff7ceef920 | |
(lldb) p *(CodeHeader*)(0x00007fff7ceef920-8) | |
(CodeHeader) $2 = { | |
pRealCodeHeader = 0x00007fff7cf35c78 | |
} | |
(lldb) p *(_hpRealCodeHdr*)(0x00007fff7cf35c78) | |
(_hpRealCodeHdr) $3 = { | |
phdrDebugInfo = 0x0000000000000000 | |
phdrJitEHInfo = 0x00007fff7cf35ce0 | |
phdrJitGCInfo = 0x0000000000000000 | |
phdrMDesc = 0x00007fff7baf9200 | |
nUnwindInfos = 2 | |
unwindInfos = {} | |
} | |
(lldb) me re -s8 -c20 -fx 0x00007fff7cf35ce0-8 | |
0x7fff7cf35cd8: 0x0000000000000001 0x0000000000002040 | |
0x7fff7cf35ce8: 0x0000001800000000 0x000000620000005b | |
0x7fff7cf35cf8: 0x000000000000008f 0x000000000100000e | |
0x7fff7cf35d08: 0x0000000000000030 0x0000000000000001 | |
0x7fff7cf35d18: 0x00007ffff628f550 0x0000000000000b4a | |
0x7fff7cf35d28: 0x0000000000000000 0x0000000000000000 | |
0x7fff7cf35d38: 0x0000000000000000 0x0000000000000000 | |
0x7fff7cf35d48: 0x0000000000000000 0x0000000000000000 | |
0x7fff7cf35d58: 0x0000000000000000 0x0000000000000000 | |
0x7fff7cf35d68: 0x0000000000000000 0x0000000000000000 | |
内容解析 | |
0x0000000000000001: | |
phdrJitEHInfo - sizeof(size_t) is num clauses, here is 1 | |
0x0000000000002040: | |
memeber from base class IMAGE_COR_ILMETHOD_SECT_FAT | |
Kind = 0x40 = CorILMethod_Sect_FatFormat | |
DataSize = 0x20 = 32 = 1 * sizeof(EE_ILEXCEPTION_CLAUSE) | |
(lldb) p ((EE_ILEXCEPTION_CLAUSE*)(0x00007fff7cf35ce0+8))[0] | |
(EE_ILEXCEPTION_CLAUSE) $29 = { | |
Flags = COR_ILEXCEPTION_CLAUSE_NONE | |
TryStartPC = 24 | |
TryEndPC = 91 | |
HandlerStartPC = 98 | |
HandlerEndPC = 143 | |
= (TypeHandle = 0x000000000100000e, ClassToken = 16777230, FilterOffset = 16777230) | |
} | |
(lldb) sos Token2EE * 0x000000000100000e | |
Module: 00007fff7bc04000 | |
Assembly: System.Private.CoreLib.ni.dll | |
<invalid module token> | |
-------------------------------------- | |
Module: 00007fff7baf6e70 | |
Assembly: coreapp_jit.dll | |
Token: 000000000100000E | |
MethodTable: 00007fff7cc0dce8 | |
EEClass: 00007fff7bcb9400 | |
Name: mdToken: 0100000e (/home/ubuntu/git/coreapp_jitnew/bin/Release/netcoreapp1.1/ubuntu.16.04-x64/publish/coreapp_jit.dll) | |
(lldb) dumpmt 00007fff7cc0dce8 | |
EEClass: 00007FFF7BCB9400 | |
Module: 00007FFF7BC04000 | |
Name: System.Exception | |
mdToken: 0000000002000249 | |
File: /home/ubuntu/git/coreapp_jitnew/bin/Release/netcoreapp1.1/ubuntu.16.04-x64/publish/System.Private.CoreLib.ni.dll | |
BaseSize: 0x98 | |
ComponentSize: 0x0 | |
Slots in VTable: 51 | |
Number of IFaces in IFaceMap: 2 | |
GCInfo的结构 | |
GCInfo保存在 pRealCodeHeader->phdrJitGCInfo 中, 是一个bit数组 | |
具体的生成可以参考下面 genCreateAndStoreGCInfo 函数的解释, 这里给出实际解析GCInfo的例子 | |
源代码 | |
var x = GetString(); | |
try { | |
Console.WriteLine(x); | |
throw new Exception("abc"); | |
} catch (Exception ex) { | |
Console.WriteLine(ex); | |
Console.WriteLine(x); | |
} | |
汇编代码 | |
IN0016: 000000 push rbp | |
IN0017: 000001 push rbx | |
IN0018: 000002 sub rsp, 24 | |
IN0019: 000006 lea rbp, [rsp+20H] | |
IN001a: 00000B mov qword ptr [V06 rbp-20H], rsp | |
G_M21556_IG02: ; offs=00000FH, size=0009H, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref | |
IN0001: 00000F call ConsoleApplication.Program:GetString():ref | |
IN0002: 000014 mov gword ptr [V01 rbp-10H], rax | |
G_M21556_IG03: ; offs=000018H, size=0043H, gcVars=0000000000000001 {V01}, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, gcvars, byref | |
IN0003: 000018 mov rdi, gword ptr [V01 rbp-10H] | |
IN0004: 00001C call System.Console:WriteLine(ref) | |
IN0005: 000021 mov rdi, 0x7F78892D3CE8 | |
IN0006: 00002B call CORINFO_HELP_NEWSFAST | |
IN0007: 000030 mov rbx, rax | |
IN0008: 000033 mov edi, 1 | |
IN0009: 000038 mov rsi, 0x7F78881BCE70 | |
IN000a: 000042 call CORINFO_HELP_STRCNS | |
IN000b: 000047 mov rsi, rax | |
IN000c: 00004A mov rdi, rbx | |
IN000d: 00004D call System.Exception:.ctor(ref):this | |
IN000e: 000052 mov rdi, rbx | |
IN000f: 000055 call CORINFO_HELP_THROW | |
IN0010: 00005A int3 | |
G_M21556_IG04: ; offs=00005BH, size=0007H, gcVars=0000000000000000 {}, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, gcvars, byref, epilog, nogc | |
IN001b: 00005B lea rsp, [rbp-08H] | |
IN001c: 00005F pop rbx | |
IN001d: 000060 pop rbp | |
IN001e: 000061 ret | |
G_M21556_IG05: ; func=01, offs=000062H, size=000EH, gcrefRegs=00000040 {rsi}, byrefRegs=00000000 {}, byref, funclet prolog, nogc | |
IN001f: 000062 push rbp | |
IN0020: 000063 push rbx | |
IN0021: 000064 push rax | |
IN0022: 000065 mov rbp, qword ptr [rdi] | |
IN0023: 000068 mov qword ptr [rsp], rbp | |
IN0024: 00006C lea rbp, [rbp+20H] | |
G_M21556_IG06: ; offs=000070H, size=0018H, gcVars=0000000000000001 {V01}, gcrefRegs=00000040 {rsi}, byrefRegs=00000000 {}, gcvars, byref, isz | |
IN0011: 000070 mov rdi, rsi | |
IN0012: 000073 call System.Console:WriteLine(ref) | |
IN0013: 000078 mov rdi, gword ptr [V01 rbp-10H] | |
IN0014: 00007C call System.Console:WriteLine(ref) | |
IN0015: 000081 lea rax, G_M21556_IG04 | |
G_M21556_IG07: ; offs=000088H, size=0007H, funclet epilog, nogc, emitadd | |
IN0025: 000088 add rsp, 8 | |
IN0026: 00008C pop rbx | |
IN0027: 00008D pop rbp | |
IN0028: 00008E ret | |
LLDB命令 | |
(lldb) p *codePtr | |
(void *) $1 = 0x00007fff7cee3920 | |
(lldb) p *(CodeHeader*)(0x00007fff7cee3920-8) | |
(CodeHeader) $2 = { | |
pRealCodeHeader = 0x00007fff7cf29c78 | |
} | |
(lldb) p *(_hpRealCodeHdr*)(0x00007fff7cf29c78) | |
(_hpRealCodeHdr) $3 = { | |
phdrDebugInfo = 0x0000000000000000 | |
phdrJitEHInfo = 0x00007fff7cf29ce0 | |
phdrJitGCInfo = 0x00007fff7cf29d28 "\x91\x81G" | |
phdrMDesc = 0x00007fff7baed200 | |
nUnwindInfos = 2 | |
unwindInfos = {} | |
} | |
(lldb) me re -s8 -c20 -fx 0x00007fff7cf29d28 | |
0x7fff7cf29d28: 0x1963d80000478191 0x171f412003325ca8 | |
0x7fff7cf29d38: 0xee92864c5ffe0280 0x1c5c1c1f09bea536 | |
0x7fff7cf29d48: 0xed8a93e5c6872932 0x00000000000000c4 | |
0x7fff7cf29d58: 0x000000000000002a 0x0000000000000001 | |
0x7fff7cf29d68: 0x00007ffff628f550 0x0000000000000b2e | |
0x7fff7cf29d78: 0x0000000000000000 0x0000000000000000 | |
0x7fff7cf29d88: 0x0000000000000000 0x0000000000000000 | |
0x7fff7cf29d98: 0x0000000000000000 0x0000000000000000 | |
0x7fff7cf29da8: 0x0000000000000000 0x0000000000000000 | |
0x7fff7cf29db8: 0x0000000000000000 0x0000000000000000 | |
bit数组包含的内容 | |
10001001 | |
1: use fat encoding | |
0: no var arg | |
0: no security object | |
0: no gc cookie | |
1: have pspsym stack slot | |
0 0: no generic context parameter | |
1: have stack base register | |
1000000 | |
1: wants report only leaf | |
0: no edit and continue preserved area | |
0: no reverse pinvoke frame | |
0 0 0 0: return kind is RT_Scalar | |
1'11100010 | |
0 10001111: code length is 143 | |
0000000 | |
0 000000: pspsym stack slot is 0 | |
0'0000000 | |
0 000: stack base register is rbp (rbp is 5, normalize function will ^5 so it's 0) | |
0 000: size of stack outgoing and scratch area is 0 | |
0'000110 | |
0 00: 0 call sites | |
1 0 0 1: 2 interruptible ranges | |
11'11000 | |
0 001111: interruptible range 1 begins from 15 | |
110'10011000'000 | |
1 001011 0 000001: interruptible range 1 finished at 91 (15 + 75 + 1) | |
10101'00 | |
0 010101: interruptible range 2 begins from 112 (91 + 21) | |
111010'01001100 | |
0 010111: interruptible range 2 finished at 136 (112 + 23 + 1) | |
1: have register slots | |
1 00 0 01: 4 register slots | |
110000 | |
1: have stack slots | |
0 01: 1 tracked stack slots | |
0 0: 0 untracked stack slots | |
00'0000010 | |
0 000: register slot 1 is rax(0) | |
00: register slot 1 flag is GC_SLOT_IS_REGISTER(8 & 0b11 = 0) | |
0 10: register slot 2 is rbx(3) (0 + 2 + 1) | |
0'10000 | |
0 10: register slot 3 is rsi(6) (3 + 2 + 1) | |
0 00: register slot 4 is rdi(7) (6 + 0 + 1) | |
010'11111000 | |
01: stack slot 1 base on GC_FRAMEREG_REL(2) | |
0 111110: stack slot 1 offset is -16 (-16 / 8 = -2) | |
00: stack slot 1 flag is GC_SLOT_BASE(0) | |
111 01000 | |
111: num bits per pointer is 7 | |
00000001 | |
0 0000001: chunk 0's bit offset is 0 (1-1) | |
01000000: chunk 1's bit offset is 63 (64-1) | |
011111 | |
011111: chunk 0 could be live slot list, simple format, all could live | |
11'111 | |
11111: chunk 0 final state, all slot lives | |
1 1010'00 | |
1 000101: transition of register slot 1(rax) at 0x14 (20 = 15 + 5), becomes live | |
110010'01100001 | |
1 001001: transition of register slot 1(rax) at 0x18 (24 = 15 + 9), becomes dead | |
1 100001: transition of register slot 1(rax) at 0x30 (48 = 15 + 33), becomes live | |
01001001 | |
0: terminator, no more transition of register slot 1(rax) in this chunk | |
1 100100: transition of register slot 2(rbx) at 0x33 (51 = 15 + 36), becomes live | |
01110111 | |
0: terminator, no more transition of register slot 2(rbx) in this chunk | |
1 111110: transition of register slot 3(rsi) at 0x4d (77 = 15 + 62), becomes live | |
01101100 | |
0: terminator, no more transition of register slot 3(rsi) in this chunk | |
1 001101: transition of register slot 4(rdi) at 0x1c (28 = 15 + 13), becomes live | |
1010010 | |
1 010010: transition of register slot 4(rdi) at 0x21 (33 = 15 + 18), becomes dead | |
1'0111110 | |
1 111110: transition of register slot 4(rdi) at 0x4d (77 = 15 + 62), becomes live | |
0: terminator, no more transition of register slot 4(rdi) in this chunk | |
1'1001000 | |
1 001001: transition of stack slot 1(rbp-16) at 0x18 (24 = 15 + 9), becomes live | |
0: terminator, no more transition of stack slot 1(rbp-16) in this chunk | |
0'11111 | |
0 11111: chunk 1 could be live slot list, simple format, all could live | |
000'00 | |
00000: chunk 1 final state, all slot dead | |
111000'00 | |
1 000011: transition of register slot 1(rax) at 0x52 (15 + 64 + 3), becomes dead | |
0: terminator, no more transition of register slot 1(rax) in this chunk | |
111010'00 | |
1: 001011: transition of register slot 2(rbx) at 0x5a (15 + 64 + 11), becomes dead | |
0: terminator, no more transition of register slot 2(rbx) in this chunk | |
111000'01001100 | |
1 000011: transition of register slot 3(rsi) at 0x52 (15 + 64 + 3), becomes dead | |
1 001100: transition of register slot 3(rsi) at 0x70 (0x70 + (64+12 - (0x5b-0xf))), becomes live | |
10010100 | |
1 010100: transition of register slot 3(rsi) at 0x78 (0x70 + (64+20 - (0x5b-0xf))), becomes dead | |
0: terminator, no more transition of register slot 3(rsi) in this chunk | |
1110000 | |
1: 000011: transition of register slot 4(rdi) at 0x52 (15 + 64 + 3), becomes dead | |
1'011000 | |
1 000110: transition of register slot 4(rdi) at 0x55 (15 + 64 + 6), becomes live | |
11'10100 | |
1 001011: transition of register slot 4(rdi) at 0x5a (15 + 64 + 11), becomes dead | |
111'1100 | |
1: 001111: transition of register slot 4(rdi) at 0x73 (0x70 + (64+15 - (0x5b-0xf))), becomes live | |
1001'010 | |
1 010100: transition of register slot 4(rdi) at 0x78 (0x70 + (64+20 - (0x5b-0xf))), becomes dead | |
10001'10 | |
1 011000: transition of register slot 4(rdi) at 0x7c (0x70 + (64+24 - (0x5b-0xf))), becomes live | |
110111'00 | |
1 011101: transition of register slot 4(rdi) at 0x81 (0x70 + (64+29 - (0x5b-0xf))), becomes dead | |
0: terminator, no more transition of register slot 4(rdi) in this chunk | |
100011'00 | |
1 011000: transition of stack slot 1(rbp-16) at 0x7c (0x70 + (64+24 - (0x5b-0xf))), becomes dead | |
0: terminator, no more transition of stack slot 1(rbp-16) in this chunk | |
Unwind Info的结构 | |
Unwind Info保存在 pRealCodeHeader->nUnwindInfos 和 pRealCodeHeader->unwindInfos 中 | |
pRealCodeHeader->unwindInfos 是一个长度为 pRealCodeHeader->nUnwindInfos 的数组, 类型是 RUNTIME_FUNCTION | |
数量等于主函数 + funclet的数量 | |
RUNTIME_FUNCTION中又保存了UNWIND_INFO的数组, UNWIND_INFO保存了函数对栈指针的操作 | |
以下是实际的实例分析 | |
源代码 | |
var x = GetString(); | |
try { | |
Console.WriteLine(x); | |
throw new Exception("abc"); | |
} catch (Exception ex) { | |
Console.WriteLine(ex); | |
Console.WriteLine(x); | |
} finally { | |
Console.WriteLine("finally"); | |
} | |
汇编代码 | |
G_M21556_IG01: ; func=00, offs=000000H, size=000FH, gcVars=0000000000000000 {}, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, gcvars, byref, nogc <-- Prolog IG | |
IN001e: 000000 push rbp | |
IN001f: 000001 push rbx | |
IN0020: 000002 sub rsp, 24 | |
IN0021: 000006 lea rbp, [rsp+20H] | |
IN0022: 00000B mov qword ptr [V06 rbp-20H], rsp | |
G_M21556_IG02: ; offs=00000FH, size=0009H, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref | |
IN0001: 00000F call ConsoleApplication.Program:GetString():ref | |
IN0002: 000014 mov gword ptr [V01 rbp-10H], rax | |
G_M21556_IG03: ; offs=000018H, size=0043H, gcVars=0000000000000001 {V01}, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, gcvars, byref | |
IN0003: 000018 mov rdi, gword ptr [V01 rbp-10H] | |
IN0004: 00001C call System.Console:WriteLine(ref) | |
IN0005: 000021 mov rdi, 0x7F94DDF9CCE8 | |
IN0006: 00002B call CORINFO_HELP_NEWSFAST | |
IN0007: 000030 mov rbx, rax | |
IN0008: 000033 mov edi, 1 | |
IN0009: 000038 mov rsi, 0x7F94DCE85E70 | |
IN000a: 000042 call CORINFO_HELP_STRCNS | |
IN000b: 000047 mov rsi, rax | |
IN000c: 00004A mov rdi, rbx | |
IN000d: 00004D call System.Exception:.ctor(ref):this | |
IN000e: 000052 mov rdi, rbx | |
IN000f: 000055 call CORINFO_HELP_THROW | |
IN0010: 00005A int3 | |
G_M21556_IG04: ; offs=00005BH, size=0001H, gcVars=0000000000000000 {}, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, gcvars, byref | |
IN0011: 00005B nop | |
G_M21556_IG05: ; offs=00005CH, size=0008H, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref | |
IN0012: 00005C mov rdi, rsp | |
IN0013: 00005F call G_M21556_IG11 | |
G_M21556_IG06: ; offs=000064H, size=0001H, nogc, emitadd | |
IN0014: 000064 nop | |
G_M21556_IG07: ; offs=000065H, size=0007H, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref, epilog, nogc | |
IN0023: 000065 lea rsp, [rbp-08H] | |
IN0024: 000069 pop rbx | |
IN0025: 00006A pop rbp | |
IN0026: 00006B ret | |
G_M21556_IG08: ; func=01, offs=00006CH, size=000EH, gcVars=0000000000000001 {V01}, gcrefRegs=00000040 {rsi}, byrefRegs=00000000 {}, gcvars, byref, funclet prolog, nogc | |
IN0027: 00006C push rbp | |
IN0028: 00006D push rbx | |
IN0029: 00006E push rax | |
IN002a: 00006F mov rbp, qword ptr [rdi] | |
IN002b: 000072 mov qword ptr [rsp], rbp | |
IN002c: 000076 lea rbp, [rbp+20H] | |
G_M21556_IG09: ; offs=00007AH, size=0018H, gcVars=0000000000000001 {V01}, gcrefRegs=00000040 {rsi}, byrefRegs=00000000 {}, gcvars, byref, isz | |
IN0015: 00007A mov rdi, rsi | |
IN0016: 00007D call System.Console:WriteLine(ref) | |
IN0017: 000082 mov rdi, gword ptr [V01 rbp-10H] | |
IN0018: 000086 call System.Console:WriteLine(ref) | |
IN0019: 00008B lea rax, G_M21556_IG04 | |
G_M21556_IG10: ; offs=000092H, size=0007H, funclet epilog, nogc, emitadd | |
IN002d: 000092 add rsp, 8 | |
IN002e: 000096 pop rbx | |
IN002f: 000097 pop rbp | |
IN0030: 000098 ret | |
G_M21556_IG11: ; func=02, offs=000099H, size=000EH, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref, funclet prolog, nogc | |
IN0031: 000099 push rbp | |
IN0032: 00009A push rbx | |
IN0033: 00009B push rax | |
IN0034: 00009C mov rbp, qword ptr [rdi] | |
IN0035: 00009F mov qword ptr [rsp], rbp | |
IN0036: 0000A3 lea rbp, [rbp+20H] | |
G_M21556_IG12: ; offs=0000A7H, size=0013H, gcVars=0000000000000000 {}, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, gcvars, byref | |
IN001a: 0000A7 mov rdi, 0x7F94C8001068 | |
IN001b: 0000B1 mov rdi, gword ptr [rdi] | |
IN001c: 0000B4 call System.Console:WriteLine(ref) | |
IN001d: 0000B9 nop | |
G_M21556_IG13: ; offs=0000BAH, size=0007H, funclet epilog, nogc, emitadd | |
IN0037: 0000BA add rsp, 8 | |
IN0038: 0000BE pop rbx | |
IN0039: 0000BF pop rbp | |
IN003a: 0000C0 ret | |
LLDB命令 | |
(lldb) p *codePtr | |
(void *) $0 = 0x00007fff7ceee920 | |
(lldb) p *(CodeHeader*)(0x00007fff7ceee920-8) | |
(CodeHeader) $1 = { | |
pRealCodeHeader = 0x00007fff7cf34c78 | |
} | |
(lldb) p *(_hpRealCodeHdr*)(0x00007fff7cf34c78) | |
(_hpRealCodeHdr) $2 = { | |
phdrDebugInfo = 0x0000000000000000 | |
phdrJitEHInfo = 0x0000000000000000 | |
phdrJitGCInfo = 0x0000000000000000 | |
phdrMDesc = 0x00007fff7baf8200 | |
nUnwindInfos = 3 | |
unwindInfos = {} | |
} | |
(lldb) p ((_hpRealCodeHdr*)(0x00007fff7cf34c78))->unwindInfos[0] | |
(RUNTIME_FUNCTION) $3 = (BeginAddress = 2304, EndAddress = 2412, UnwindData = 2500) | |
(lldb) p ((_hpRealCodeHdr*)(0x00007fff7cf34c78))->unwindInfos[1] | |
(RUNTIME_FUNCTION) $4 = (BeginAddress = 2412, EndAddress = 2457, UnwindData = 2516) | |
(lldb) p ((_hpRealCodeHdr*)(0x00007fff7cf34c78))->unwindInfos[2] | |
(RUNTIME_FUNCTION) $5 = (BeginAddress = 2457, EndAddress = 2497, UnwindData = 2532) | |
first unwind info: | |
(lldb) p (void*)(((CEEJitInfo*)compiler->info.compCompHnd)->m_moduleBase + 2304) | |
(void *) $13 = 0x00007fff7ceee920 | |
(lldb) p (void*)(((CEEJitInfo*)compiler->info.compCompHnd)->m_moduleBase + 2412) | |
(void *) $14 = 0x00007fff7ceee98c | |
# range is [0, 0x6c) | |
(lldb) p *(UNWIND_INFO*)(((CEEJitInfo*)compiler->info.compCompHnd)->m_moduleBase + 2500) | |
(UNWIND_INFO) $16 = { | |
Version = '\x01' | |
Flags = '\x03' | |
SizeOfProlog = '\x06' | |
CountOfUnwindCodes = '\x03' | |
FrameRegister = '\0' | |
FrameOffset = '\0' | |
UnwindCode = { | |
[0] = { | |
= (CodeOffset = '\x06', UnwindOp = '\x02', OpInfo = '\x02') | |
EpilogueCode = (OffsetLow = '\x06', UnwindOp = '\x02', OffsetHigh = '\x02') | |
FrameOffset = 8710 | |
} | |
} | |
} | |
(lldb) p ((UNWIND_INFO*)(((CEEJitInfo*)compiler->info.compCompHnd)->m_moduleBase + 2500))->UnwindCode[0] | |
(UNWIND_CODE) $17 = { | |
= (CodeOffset = '\x06', UnwindOp = '\x02', OpInfo = '\x02') | |
EpilogueCode = (OffsetLow = '\x06', UnwindOp = '\x02', OffsetHigh = '\x02') | |
FrameOffset = 8710 | |
} | |
(lldb) p ((UNWIND_INFO*)(((CEEJitInfo*)compiler->info.compCompHnd)->m_moduleBase + 2500))->UnwindCode[1] | |
(UNWIND_CODE) $18 = { | |
= (CodeOffset = '\x02', UnwindOp = '\0', OpInfo = '\x03') | |
EpilogueCode = (OffsetLow = '\x02', UnwindOp = '\0', OffsetHigh = '\x03') | |
FrameOffset = 12290 | |
} | |
(lldb) p ((UNWIND_INFO*)(((CEEJitInfo*)compiler->info.compCompHnd)->m_moduleBase + 2500))->UnwindCode[2] | |
(UNWIND_CODE) $19 = { | |
= (CodeOffset = '\x01', UnwindOp = '\0', OpInfo = '\x05') | |
EpilogueCode = (OffsetLow = '\x01', UnwindOp = '\0', OffsetHigh = '\x05') | |
FrameOffset = 20481 | |
} | |
使用COMPlus_JitDump生成的除错信息 | |
Unwind Info: | |
>> Start offset : 0x000000 (not in unwind data) | |
>> End offset : 0x00006c (not in unwind data) | |
Version : 1 | |
Flags : 0x00 | |
SizeOfProlog : 0x06 | |
CountOfUnwindCodes: 3 | |
FrameRegister : none (0) | |
FrameOffset : N/A (no FrameRegister) (Value=0) | |
UnwindCodes : | |
CodeOffset: 0x06 UnwindOp: UWOP_ALLOC_SMALL (2) OpInfo: 2 * 8 + 8 = 24 = 0x18 | |
CodeOffset: 0x02 UnwindOp: UWOP_PUSH_NONVOL (0) OpInfo: rbx (3) | |
CodeOffset: 0x01 UnwindOp: UWOP_PUSH_NONVOL (0) OpInfo: rbp (5) | |
allocUnwindInfo(pHotCode=0x00007F94DE27E920, pColdCode=0x0000000000000000, startOffset=0x0, endOffset=0x6c, unwindSize=0xa, pUnwindBlock=0x0000000002029516, funKind=0 (main function)) | |
Unwind Info: | |
>> Start offset : 0x00006c (not in unwind data) | |
>> End offset : 0x000099 (not in unwind data) | |
Version : 1 | |
Flags : 0x00 | |
SizeOfProlog : 0x03 | |
CountOfUnwindCodes: 3 | |
FrameRegister : none (0) | |
FrameOffset : N/A (no FrameRegister) (Value=0) | |
UnwindCodes : | |
CodeOffset: 0x03 UnwindOp: UWOP_ALLOC_SMALL (2) OpInfo: 0 * 8 + 8 = 8 = 0x08 | |
CodeOffset: 0x02 UnwindOp: UWOP_PUSH_NONVOL (0) OpInfo: rbx (3) | |
CodeOffset: 0x01 UnwindOp: UWOP_PUSH_NONVOL (0) OpInfo: rbp (5) | |
allocUnwindInfo(pHotCode=0x00007F94DE27E920, pColdCode=0x0000000000000000, startOffset=0x6c, endOffset=0x99, unwindSize=0xa, pUnwindBlock=0x0000000002029756, funKind=1 (handler)) | |
Unwind Info: | |
>> Start offset : 0x000099 (not in unwind data) | |
>> End offset : 0x0000c1 (not in unwind data) | |
Version : 1 | |
Flags : 0x00 | |
SizeOfProlog : 0x03 | |
CountOfUnwindCodes: 3 | |
FrameRegister : none (0) | |
FrameOffset : N/A (no FrameRegister) (Value=0) | |
UnwindCodes : | |
CodeOffset: 0x03 UnwindOp: UWOP_ALLOC_SMALL (2) OpInfo: 0 * 8 + 8 = 8 = 0x08 | |
CodeOffset: 0x02 UnwindOp: UWOP_PUSH_NONVOL (0) OpInfo: rbx (3) | |
CodeOffset: 0x01 UnwindOp: UWOP_PUSH_NONVOL (0) OpInfo: rbp (5) | |
以第一个RUNTIME_FUNCTION(主函数)为例 | |
它包含了3个UnwindCode, 分别记录了 | |
push rbp | |
push rbx | |
sub rsp, 24 | |
实际运行时根据当前pc获取当前frame的顶部 => 获取return address => 根据return address获取上一个frame的顶部 => 循环 | |
这样即可获取调用链和各个调用源的frame的顶部, 这个流程也叫stack walking (或 stack crawling) | |
GCInfo的生成 | |
GCInfo在 genCreateAndStoreGCInfo 中生成, 生成后保存到 pRealCodeHeader->phdrJitGCInfo | |
生成的过程中会使用 GcInfoEncoder 这个类, 代码在gcinfo文件夹下 | |
EHInfo的生成 | |
EHInfo在 genReportEH 中生成, 生成后保存到 pRealCodeHeader->phdrJitEHInfo | |
Unwind Info的生成 | |
Unwind Info在 unwindEmit 中生成, 生成后保存到 pRealCodeHeader->nUnwindInfos 和 pRealCodeHeader->unindInfos[] | |
PersonalityRoutine的处理 | |
PersonalityRoutine是保存在heapList(代码块)中用于处理例外的代码 | |
默认是jmp到ProcessCLRException的代码 | |
lldb解析 | |
(lldb) b GetCLRPersonalityRoutineValue | |
(lldb) finish | |
(lldb) p *pPersonalityRoutine | |
(ULONG) $3 = 62 | |
(lldb) p (_HeapList*)baseAddress | |
(_HeapList *) $5 = 0x00007fff7ce93020 | |
(lldb) p *(_HeapList*)baseAddress | |
(_HeapList) $6 = { | |
hpNext = 0x0000000000000000 | |
pHeap = 0x00000000006e1710 | |
startAddress = 140735289045104 | |
endAddress = 140735289046236 | |
mapBase = 140735289044992 | |
pHdrMap = 0x00007fff7bae7090 | |
maxCodeHeapSize = 262032 | |
cBlocks = 2 | |
bFull = false | |
bFullForJumpStubs = false | |
CLRPersonalityRoutine = ([0] = 'H', [1] = '\xb8', [2] = '\x10', [3] = 'r', [4] = '\xbb', [5] = '\xf5', [6] = '\xff', [7] = '\x7f', [8] = '\0', [9] = '\0', [10] = '\xff', [11] = '\xe0') | |
} | |
(lldb) di -s (char*)((_HeapList*)baseAddress)->CLRPersonalityRoutine | |
0x7fff7ce9305e: movabsq $0x7ffff5bb7210, %rax | |
0x7fff7ce93068: jmpq *%rax | |
0x7fff7ce9306a: addb %al, (%rax) | |
0x7fff7ce9306c: addb %al, (%rax) | |
0x7fff7ce9306e: addb %al, (%rax) | |
0x7fff7ce93070: addb (%rax), %al | |
0x7fff7ce93072: addb %al, (%rax) | |
0x7fff7ce93074: addb %al, (%rax) | |
0x7fff7ce93076: addb %al, (%rax) | |
0x7fff7ce93078: callq 0x7ffff5bda0f0 ; PrecodeFixupThunk | |
0x7fff7ce9307d: popq %rsi | |
(lldb) di -s 0x7ffff5bb7210 | |
0x7ffff5bb7210 <ProcessCLRException>: pushq %rbp | |
0x7ffff5bb7211 <ProcessCLRException+1>: movq %rsp, %rbp | |
0x7ffff5bb7214 <ProcessCLRException+4>: subq $0x340, %rsp | |
0x7ffff5bb721b <ProcessCLRException+11>: movq %fs:0x28, %rax | |
0x7ffff5bb7224 <ProcessCLRException+20>: movq %rax, -0x8(%rbp) | |
0x7ffff5bb7228 <ProcessCLRException+24>: movq %rdi, -0x1c8(%rbp) | |
IL是如何获取的 | |
普通函数的IL可以通过MethodDesc->GetILHeader获取 | |
GetILHeader会使用pModule->GetIL(GetRVA())获取 | |
第一个可以调用GetILHeader获取的函数是Main | |
IL怎么转换成BasicBlock | |
IL的所在位置 | |
info.compMethodInfo->ILCode | |
info.compMethodInfo->ILCodeSize | |
compCompileHelper复制到 | |
info.compCode | |
info.compILCodeSize | |
流程 | |
compCompileHelper | |
compInitDebuggingInfo | |
fgEnsureFirstBBisScratch | |
在最开始插入一个用于支持 Debug 的 BasicBlock | |
bbFlags |= BBF_INTERNAL | BBF_IMPORTED | |
fgInsertStmtAtEnd(fgFirstBB, gtNewNothingNode()) | |
修改插入的 BasicBlock, 设置一个只有nop的GenTree | |
block->bbTreeList = stmt; // block->setBBTreeList(stmt) | |
fgFindBasicBlocks | |
fgFindJumpTargets | |
解析逐条指令,分析指令中的跳转,指令大小在 opcodeSizes 中 | |
对跳转目标调用 fgMarkJumpTarget | |
jumpTarget 保存跳转目标的数组,由 fgFindBasicBlocks 生成 | |
offs 是跳转目标的地址离开始地址的偏移值 | |
jumpTarget[offs] |= (jumpTarget[offs] & JT_JUMP) << 1 | JT_JUMP | |
第一次标记是JT_JUMP,第二次以后标记是JT_JUMP | JT_MULTI | |
对 CEE_LDARG 调用 pushedStack.PushArgument | |
pushedStack的类型是FgStack, 是一个深度最大为2的execution stack, 专门用于记录inlinee中的指令 | |
如果满足一定条件(例如传入参数是常量且使用了该参数)则可以在后面增加inline的成功率(multiplier) | |
参考fgObserveInlineConstants函数 | |
对 CEE_LDLEN 调用 pushedStack.PushArrayLen, 同上 | |
如果当前编译的函数是内联函数 | |
如果当前的函数需要根据利益判断(CALLEE_IS_DISCRETIONARY_INLINE) | |
调用compInlineResult->DetermineProfitability判断, 判断不应该内联则中断JIT | |
m_CalleeNativeSizeEstimate = DetermineNativeSizeEstimate() // 使用statemachine估算的机器代码大小 | |
m_CallsiteNativeSizeEstimate = DetermineCallsiteNativeSizeEstimate(methodInfo) // 估算调用此函数的指令花费的机器代码大小 | |
m_Multiplier = DetermineMultiplier() // 系数, 值越大越容易inline, 详见DetermineMultiplier | |
const int threshold = (int)(m_CallsiteNativeSizeEstimate * m_Multiplier) // 阈值 | |
if (m_CalleeNativeSizeEstimate > threshold) | |
设置不内联 | |
根据例外处理器设置 jumpTarget | |
compXcptnsCount(methodInfo->EHcount) > 0 时 | |
枚举例外处理器 | |
获取信息 | |
CORINFO_EH_CLAUSE clause; | |
info.compCompHnd->getEHinfo(info.compMethodHnd, XTnum, &clause); | |
try之前分割 | |
jumpTarget[clause.TryOffset] = JT_ADDR; | |
try之后分割 | |
tmpOffset = clause.TryOffset + clause.TryLength; | |
jumpTarget[tmpOffset] = JT_ADDR; | |
处理代码之前分割 | |
jumpTarget[clause.HandlerOffset] = JT_ADDR; | |
处理代码之后分割 | |
tmpOffset = clause.HandlerOffset + clause.HandlerLength; | |
jumpTarget[tmpOffset] = JT_ADDR; | |
如果使用了过滤器则过滤器之前分割 | |
jumpTarget[clause.FilterOffset] = JT_ADDR; | |
fgMakeBasicBlocks | |
枚举指令 | |
下一条指令的地址会在 nxtBBoffs 中 | |
如果下一条指令是某个跳转指令的目标,则需要分块 | |
if (jmpKind == BBJ_NONE) | |
bool makeBlock = (jumpTarget[nxtBBoffs] != JT_NONE); | |
如果当前指令是跳转,则需要分块 | |
jmpKind的种类看上面的BBjumpKinds | |
分块 | |
curBBdesc = fgNewBasicBlock(jmpKind); | |
curBBdesc->bbFlags |= bbFlags; | |
curBBdesc->bbRefs = 0; | |
curBBdesc->bbCodeOffs = curBBoffs; | |
curBBdesc->bbCodeOffsEnd = nxtBBoffs; | |
额外信息 | |
如果jmpKind是BBJ_SWITCH | |
curBBdesc->bbJumpSwt = swtDsc | |
如果jmpKind是BBJ_COND, BBJ_ALWAYS, BBJ_LEAVE | |
curBBdesc->bbJumpOffs = jmpAddr; | |
jmpAddr是跳转目标的地址离开始地址的偏移值 | |
保存分块 | |
fgFirstBB 指向第一个BasicBlock | |
fgLastBB 指向最后一个BasicBlock | |
调用 fgLinkBasicBlocks | |
调用 fgInitBBLookup | |
设置Compiler::fgBBs (BasicBlock**),把链表中的各个BasicBlock*保存到数组中 | |
如果jmpKind是BBJ_COND, BBJ_ALWAYS, BBJ_LEAVE | |
转换bbJumpOffs到bbJumpDest | |
增加目标BasicBlock的bbRefs | |
如果jmpKind是BBJ_NONE | |
增加下一个BasicBlock的bbRefs | |
如果jmpKind是BBJ_SWITCH | |
增加所有目标BasicBlock的bbRefs | |
如果目标BasicBlock的序号比当前BasicBlock的序号小 | |
调用 fgMarkBackwardJump | |
从目标到当前的所有BasicBlock的 bbFlags |= BBF_BACKWARD_JUMP | |
检查是否可以inline | |
if (compIsForInlining()) | |
以下处理仅在例外处理器存在时继续 | |
if (info.compXcptnsCount == 0) | |
return | |
例外处理器超过65534个时报错 | |
if (info.compXcptnsCount > MAX_XCPTN_INDEX) | |
IMPL_LIMITATION("too many exception clauses"); | |
fgAllocEHTable | |
分配例外处理器的块信息数组,块信息包含了try开始和结束的BasicBlock,处理器开始和结束的BasicBlock等 | |
compHndBBtab = new (this, CMK_BasicBlock) EHblkDsc[compHndBBtabAllocCount]; | |
compHndBBtabCount = info.compXcptnsCount; | |
verInitEHTree | |
初始化EH树所用的节点 | |
ehnNext = new (this, CMK_BasicBlock) EHNodeDsc[numEHClauses * 3]; | |
ehnTree = nullptr; | |
填充例外处理器的块信息数组 compHndBBtab 和 EH树 ehnTree | |
for (XTnum = 0, HBtab = compHndBBtab; XTnum < compHndBBtabCount; XTnum++, HBtab++) | |
填充 compHndBBtab | |
构建 ehnTree | |
verInsertEhNode(&clause, HBtab) | |
节点有 ehnNext, ehnChild, ehnTryNode, ehnHandlerNode 等属性 | |
try { } catch (ex_a) { } catch (ex_b) { } finally { } 会生成以下的树 | |
try (=>next) finally | |
(=>child) try (=>next) catch (=>next) catch | |
fgSortEHTable | |
对例外处理器的块信息数组 compHndBBtab 进行排序 | |
嵌套在里层的try catch会排在外层的前面 | |
让 try或catch或finally中的 BasicBlock 指向排序后的 compHndBBtab 的序号 | |
调用 setHndIndex 修改 bbHndIndex | |
调用 setTryIndex 修改 bbTryIndex | |
修改 ebdEnclosingTryIndex | |
修改 ebdEnclosingHndIndex | |
fgNormalizeEH | |
对嵌套的 try catch 插入空 BasicBlock | |
看 jiteh.cpp 中 fgNormalizeEH 的注释会比较清楚 | |
BasicBlock怎么转换成GenTree | |
流程 | |
compCompile (3参数, compiler.cpp:4078) | |
以下流程参考 compphases.h | |
PHASE_PRE_IMPORT | |
hashBv::Init(this) | |
清空compiler->hbvGlobalData | |
用于分配bitmap对象(hashBv*), 相当于一个allocator | |
后面的fgOutgoingArgTemps和fgCurrentlyInUseArgTemps会从这里分配 | |
VarSetOps::AssignAllowUninitRhs(this, compCurLife, VarSetOps::UninitVal()) | |
设置当前存活的变量的集合为未初始化, 后面会重新设为空集合 | |
PHASE_IMPORTATION | |
fgImport | |
impImport(fgFirstBB) | |
初始化运行堆栈 | |
verCurrentState.esStack = impSmallStack (maxstack小于16时使用SmallStack, 否则new) | |
verInitCurrentState() | |
初始化用于查找 Spill Cliques 的成员 | |
inlineRoot->impPendingBlockMembers.Reset(fgBBNumMax * 2) | |
inlineRoot->impSpillCliquePredMembers.Reset(fgBBNumMax * 2) | |
inlineRoot->impSpillCliqueSuccMembers.Reset(fgBBNumMax * 2) | |
处理标记 bbFlags 带 BBF_INTERNAL 的 BasicBlock | |
跳过这些 BasicBlock | |
for (; method->bbFlags & BBF_INTERNAL; method = method->bbNext) | |
设置 BBF_IMPORTED, bbFlags |= BBF_IMPORTED | |
impImportBlockPending | |
添加 BasicBlock 到 impPendingList 中 | |
这里会导入第一个非 BBF_INTERNAL 的 BasicBlock | |
后面这个函数还会用来导入跳转目标的 BasicBlock | |
处理 impPendingList 直到链表为空 | |
调用 impImportBlock(dsc->pdBB) | |
跳过 BBF_INTERNAL 的节点并且把它的所有 Successor 加到 impPendingList | |
调用 impVerifyEHBlock(pParam->block, true) | |
本地变量 HBtab = block(try block)所属的例外信息 | |
如果block是try start, 运行堆栈必须为空 | |
备份当前运行堆栈的状态 | |
while HBtab != nulltr | |
如果block是try start | |
this必须已初始化, 除非有fault block | |
处理 HBtab->ebdHndBeg (递归处理) | |
如果handler block接收例外对象 | |
调用 hndBegBB = impPushCatchArgOnStack(hndBegBB, clsHnd) | |
如果有其他block jump到hndBegBB, 需要插入一个新的hndBegBB并且spill clsHnd到temp | |
调用 impImportBlockPending(hndBegBB) // 把handler加入处理队列 | |
如果有filter | |
调用 filterBB = impPushCatchArgOnStack(filterBB, impGetObjectClass()), 同上 | |
调用 impImportBlockPending(filterBB), 同上 | |
如果有外层的try(ebdEnclosingHndIndex), 则HBtab=外层try的HBtab | |
恢复当前运行堆栈的状态 | |
调用 impImportBlockCode(pParam->block) | |
这个函数就是把 BasicBlock 转换为 GenTree 的主要函数, 有5000多行 | |
调用 impBeginTreeList | |
设置初始的 impTreeList = impTreeLast = new (this, GT_BEG_STMTS) GenTree(GT_BEG_STMTS, TYP_VOID) | |
处理 BasicBlock 中的各个指令 | |
switch (opcode) | |
CEE_NOP | |
op1 = new (this, GT_NO_OP) GenTree(GT_NO_OP, TYP_VOID) | |
impAppendTree(op1, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs) | |
添加到 impTreeLast 后面并更新 impTreeLast | |
CEE_NEWOBJ | |
调用 impSpillSpecialSideEff | |
如果当前BasicBlock是catch或finally中的第一个BasicBlock(funclet的开始)则 | |
枚举 ExecutionStack | |
如果节点是CatchArg(异常对象)并且, 并且节点有 GTF_ORDER_SIDEEFF 标志时 | |
impSpillStackEntry(level, BAD_VAR_NUM) | |
生成设置节点到临时变量的表达式并加到 impTreeList | |
把该节点替换到获取临时变量的节点 | |
设置 resolvedToken (CORINFO_RESOLVED_TOKEN) | |
_impResolveToken(CORINFO_TOKENKIND_NewObj) | |
#define _impResolveToken(kind) impResolveToken(codeAddr, &resolvedToken, kind) | |
获取指令后的token | |
pResolvedToken->token = getU4LittleEndian(addr) | |
获取token对应的 TypeHandle 和 MethodDesc | |
info.compCompHnd->resolveToken(pResolvedToken) (CEEINFO::resolveToken) | |
获取后可以查看到获取到的 TypeHandle 和 MethodDesc | |
dumpmt resolvedToken->hClass | |
dumpmd resolvedToken->hMethod | |
调用 eeGetCallInfo | |
info.compCompHnd->getCallInfo(pResolvedToken, pConstrainedToken, info.compMethodHnd, flags, pResult) (CEEINFO::resolveToken) | |
设置callInfo,并且检查函数是否可以调用 | |
调用 impHandleAccessAllowedInternal | |
判断是否可以调用函数 | |
CORINFO_ACCESS_ALLOWED 时跳过 | |
CORINFO_ACCESS_ILLEGAL 时抛出例外 | |
CORINFO_ACCESS_RUNTIME_CHECK 时插入运行时检查的代码 (callInfo.callsiteCalloutHelper) | |
对 callInfo.classFlags 进行判断 | |
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 | |
第三种情况时 | |
获取一个临时变量保存分配内存后的结果 | |
lclNum = lvaGrabTemp(true DEBUGARG("NewObj constructor temp")) | |
增加 lvaCount 并返回增加前的值 | |
生成 MethodTable 的参数节点 (Icon) | |
op1 = impParentClassTokenToHandle(&resolvedToken, nullptr, TRUE) | |
impTokenToHandle(pResolvedToken, pRuntimeLookup, mustRestoreHandle, TRUE) | |
impLookupToTree | |
gtNewIconEmbHndNode | |
这里的Icon是Int Const的意思 | |
生成 JIT_New 的参数节点 (AllocObj) | |
op1 = gtNewAllocObjNode( | |
info.compCompHnd->getNewHelper(&resolvedToken, info.compMethodHnd), | |
resolvedToken.hClass, TYP_REF, op1) | |
new (this, GT_ALLOCOBJ) GenTreeAllocObj(type, helper, clsHnd, op1) | |
生成设置分配内存的结果到临时变量的节点,然后添加到 impTreeList | |
impAssignTempGen(lclNum, op1, (unsigned)CHECK_SPILL_NONE); | |
GenTreePtr asg = gtNewTempAssign(tmp, val); | |
impAppendTree(asg, curLevel, impCurStmtOffs) | |
生成获取临时变量的节点 | |
newObjThisPtr = gtNewLclvNode(lclNum, TYP_REF) | |
跳转到生成函数调用节点的处理 | |
goto CALL | |
判断是否要优化尾递归 | |
bool isRecursive = (callInfo.hMethod == info.compMethodHnd) | |
添加调用构造函数的节点到 impTreeList | |
impImportCall | |
创建一个调用节点 | |
call = gtNewCallNode(CT_USER_FUNC, callInfo->hMethod, callRetTyp, nullptr, ilOffset) | |
设置参数节点 | |
args = call->gtCall.gtCallArgs = impPopList(sig->numArgs, &argFlags, sig, extraArg) | |
设置this | |
if (opcode == CEE_NEWOBJ) { obj = newobjThis; } | |
call->gtFlags |= obj->gtFlags & GTF_GLOB_EFFECT; | |
call->gtCall.gtCallObjp = obj; | |
添加到 impTreeList | |
impAppendTree(call, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs) | |
添加 this 到 ExecutionStack | |
impPushOnStack(gtNewLclvNode(newobjThis->gtLclVarCommon.gtLclNum, TYP_REF), typeInfo(TI_REF, clsHnd)) | |
verCurrentState.esStack[verCurrentState.esStackDepth].seTypeInfo = ti; | |
verCurrentState.esStack[verCurrentState.esStackDepth++].val = tree; | |
调用 impMarkInlineCandidate(call, exactContextHnd, callInfo) | |
判断函数是否可以inline | |
未开启优化时不内联 | |
函数是尾调用则不内联 | |
函数的gtFlags & GTF_CALL_VIRT_KIND_MASK不等于GTF_CALL_NONVIRT时不内联 | |
函数是helper call时不内联 | |
函数是indirect call时不内联 | |
环境设置了COMPlus_AggressiveInlining时, 设置 CORINFO_FLG_FORCEINLINE | |
未设置CORINFO_FLG_FORCEINLINE且函数在catch或者filter中时不内联 | |
之前尝试内联失败, 标记了CORINFO_FLG_DONT_INLINE时不内联 | |
同步函数(CORINFO_FLG_SYNCH)不内联 | |
函数需要安全检查(CORINFO_FLG_SECURITYCHECK)则不内联 | |
调用 impCheckCanInline 判断函数是否可以inline | |
调用 impCanInlineIL 判断函数是否可以inline | |
如果函数有例外处理器则不内联 | |
函数无内容(大小=0)则不内联 | |
函数参数是vararg时不内联 | |
methodInfo中的本地变量数量大于MAX_INL_LCLS(32)时不内联 | |
methodInfo中的参数数量大于MAX_INL_LCLS时不内联 | |
调用inlineResult->NoteInt通知CALLEE_NUMBER_OF_LOCALS | |
inline policy中不处理 | |
调用inlineResult->NoteInt通知CALLEE_NUMBER_OF_ARGUMENTS | |
inline policy中不处理 | |
调用inlineResult->NoteBool通知CALLEE_IS_FORCE_INLINE | |
设置 m_IsForceInline = value | |
调用inlineResult->NoteInt通知CALLEE_IL_CODE_SIZE | |
如果codesize <= CALLEE_IL_CODE_SIZE(16)则标记CALLEE_BELOW_ALWAYS_INLINE_SIZE | |
如果force inline则标记CALLEE_IS_FORCE_INLINE | |
如果codesize <= m_RootCompiler->m_inlineStrategy->GetMaxInlineILSize() | |
(本机是100, 也就是DEFAULT_MAX_INLINE_SIZE) | |
则标记CALLEE_IS_DISCRETIONARY_INLINE, 后面根据利益判断 | |
否则设置不内联(CALLEE_TOO_MUCH_IL) | |
调用inlineResult->NoteInt通知CALLEE_MAXSTACK | |
如果未要求强制内联且maxstack大小大于SMALL_STACK_SIZE(16)则不内联 | |
调用 CEEInfo::initClass 初始化函数所在的 class | |
如果class未初始化 | |
如果函数属于generic definition, 则不能内联 | |
如果类型需要在访问任何字段前初始化(IsBeforeFieldInit), 则不能内联 | |
如果未满足其他early out条件, 尝试了初始化class, 且失败了则不能内联 | |
调用 CEEInfo::canInline 判断函数是否可以inline | |
Boundary method | |
- 会创建StackCrawlMark查找它的caller的函数 | |
- 调用满足以上条件的函数的函数 (标记为IsMdRequireSecObject) | |
- 调用虚方法的函数 (虚方法可能满足以上的条件) | |
调用Boundary method的函数不内联 | |
如果caller和callee的grant set或refuse set不一致则不内联 | |
调用 canReplaceMethodOnStack | |
同一程序集的则判断可内联 | |
不同程序集时, 要求以下任意一项成立 | |
caller是full trust, refused set为空 | |
appdomain的IsHomogenous成立, 且caller和callee的refused set都为空 | |
IsHomogenous: https://msdn.microsoft.com/en-us/library/system.appdomain.ishomogenous(v=vs.110).aspx | |
如果callee和caller所在的module不一样, 且callee的string pool基于module | |
则标记dwRestrictions |= INLINE_NO_CALLEE_LDSTR (callee中不能有ldstr) | |
如果之前的判断全部通过则 | |
call->gtFlags |= GTF_CALL_INLINE_CANDIDATE | |
CEE_DUP | |
弹出 ExecutionStack 顶的值 | |
op1 = impPopStack(tiRetVal); | |
复制表达式 | |
op1 = impCloneExpr(op1, &op2, tiRetVal.GetClassHandle(), (unsigned)CHECK_SPILL_ALL, | |
nullptr DEBUGARG("DUP instruction")); | |
压入 ExecutionStack | |
impPushOnStack(op1, tiRetVal) | |
压入 ExecutionStack | |
impPushOnStack(op2, tiRetVal) | |
CEE_LDC_I4_S | |
获取常量 | |
cval.intVal = getI1LittleEndian(codeAddr); | |
跳到 PUSH_I4CON | |
goto PUSH_I4CON | |
压入 ExecutionStack | |
impPushOnStack(gtNewIconNode(cval.intVal), typeInfo(TI_INT)) | |
CEE_CALL | |
获取 callInfo | |
_impResolveToken(CORINFO_TOKENKIND_Method); | |
eeGetCallInfo | |
运行到 CALL | |
接下来和上面说的一样 | |
另外impImportCall时, 如果返回值不是void | |
并且如果该函数是inline候选 | |
使用gtNewInlineCandidateReturnExpr构建一个 retExpr 并推入 ExecutionStack | |
否则 | |
把函数的调用结果推入 ExecutionStack (中途有可能再隔一个cast) | |
CEE_STLOC_0 | |
获取设置到的本地变量 | |
lclNum = (opcode - CEE_STLOC_0) | |
lclNum += numArgs (跳过参数的本地变量) | |
从 ExecutionStack 顶中弹出 | |
StackEntry se = impPopStack(clsHnd); | |
op1 = se.val; | |
tiRetVal = se.seTypeInfo; | |
生成设置到的本地变量的节点 | |
op2 = gtNewLclvNode(lclNum, lclTyp, opcodeOffs + sz + 1); | |
生成赋值的节点 | |
op1 = gtNewAssignNode(op2, op1); | |
添加到 impTreeList | |
impAppendTree(op1, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs) | |
CEE_RET | |
impReturnInstruction | |
获取返回值的节点 | |
StackEntry se = impPopStack(retClsHnd) | |
op2 = se.val | |
生成返回节点 | |
op1 = gtNewOperNode(GT_RETURN, genActualType(info.compRetType), op2) | |
添加到 impTreeList | |
impAppendTree(op1, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs) | |
调用 impVerifyEHBlock(pParam->block, /* isTryStart */ false), 同上 | |
如果 ExecutionStack 中有残留的值则处理 | |
if (verCurrentState.esStackDepth != 0) | |
判断下一个 BasicBlock 是否有多个来源 | |
unsigned multRef = impCanReimport ? unsigned(~0) : 0 | |
设置临时变量的开始序号为目标的 bbStkTempsIn | |
baseTmp = block->bbNext->bbStkTempsIn | |
部分情况需要把最后一个表达式从GenTree中弹出来,下面插入的时候需要插入到它前面 | |
addStmt = impTreeLast; | |
impTreeLast = impTreeLast->gtPrev; | |
如果临时变量未分配,则按残留的值的数量分配,并设置 bbStkTempsIn 和 bbStkTempsOut | |
baseTmp = impGetSpillTmpBase(block) | |
lvaGrabTemps | |
分配后 lvaCount 会增加 verCurrentState.esStackDepth, baseTmp 会等于分配前的 lvaCount | |
impWalkSpillCliqueFromPred | |
简单的计算来源 BasicBlock | |
fgComputeCheapPreds | |
枚举所有目标 BasicBlock | |
如果 BasicBlock 未设置过 bbStkTempsIn | |
impSpillCliqueSuccMembers.Get(blk->bbInd()) == 0 | |
调用 SetSpillTempsBase::Visit 设置 bbStkTempsIn | |
添加到 succCliqueToDo 链表中 | |
枚举所有来源 BasicBlock | |
如果 BasicBlock 未设置过 bbStkTempsOut | |
impSpillCliquePredMembers.Get(blk->bbInd()) == 0 | |
调用 SetSpillTempsBase::Visit 设置 bbStkTempsOut | |
添加到 predCliqueToDo 链表中 | |
枚举 ExecutionStack 中残留的值 | |
调用 impSpillStackEntry(level, tempNum) | |
生成设置节点到临时变量的表达式并加到 impTreeList | |
把该节点替换到获取临时变量的节点 | |
把 addStmt 加回 impTreeList | |
保存生成的 GenTree (impTreeList) 到 BasicBlock | |
impEndTreeList(block) | |
impEndTreeList(block, firstTree, impTreeLast) | |
firstStmt->gtPrev = lastStmt | |
block->bbTreeList = firstStmt | |
block->bbFlags |= BBF_IMPORTED | |
#ifdef DEBUG | |
impTreeList = impTreeLast = nullptr | |
如果 reimportSpillClique 则调用 impReimportSpillClique | |
如果溢出的临时变量有类型转换 (int => native int, float => double) | |
重新导入所有目标 BasicBlock | |
把所有 Successor 加到 impPendingList | |
for (unsigned i = 0; i < block->NumSucc(); i++) | |
impImportBlockPending(block->GetSucc(i)) | |
fgRemovePreds | |
删除之前在 fgComputeCheapPreds 生成的 bbPreds, 防止inline出现问题 | |
fgRemoveEmptyBlocks | |
删除无法到达的 BasicBlock, 只在inline时处理, 如果是inline执行完这一步就会返回 | |
PHASE_POST_IMPORT | |
fgRemoveEH | |
如果当前环境不支持处理例外, 删除所有catch关联的 BasicBlock, 但保留try关联的 BasicBlock | |
fgInstrumentMethod | |
如果当前正在测试性能, 在第一个 BasicBlock 中插入调用 JIT_LogMethodEnter 的代码 | |
PHASE_MORPH | |
NewBasicBlockEpoch | |
更新当前的 BasicBlock 世代信息 | |
fgCurBBEpoch++ | |
fgCurBBEpochSize = fgBBNumMax + 1 // 当前世代的 BasicBlock 数量, fgBBcount增加后仍会维持原值 | |
fgBBSetCountInSizeTUnits = 使用bitset来保存 BasicBlock 时的大小, 单位是size_t | |
fgMorph | |
如果类型需要动态初始化 | |
例如类型是泛型并且有静态构造函数 | |
在第一个 BasicBlock 插入调用 JIT_ClassInitDynamicClass 的代码 | |
如果当前是除错模式 | |
如果设置了 opts.compGcChecks | |
在第一个 BasicBlock 插入调用 JIT_CheckObj 的代码, 检查所有引用类型的参数的指针是否合法 | |
如果设置了 opts.compStackCheckOnRet | |
添加一个临时变量 lvaReturnEspCheck (TYP_INT) | |
如果设置了 opts.compStackCheckOnCall | |
添加一个临时变量 lvaCallEspCheck (TYP_INT) | |
删除无法到达的 BasicBlock | |
删除未标记 BBF_IMPORTED 的 BasicBlock | |
如果try对应的 BasicBlock 已被删除, 同时删除EH Table中的元素 | |
调用 fgRenumberBlocks 重新编排 BasicBlock 的序号 | |
fgAddInternal | |
添加内部代码到第一个 BasicBlock (要求是BBF_INTERNAL) | |
如果第0个参数不是this参数 (lvaArg0Var != info.compThisArg) | |
设置第0个参数为this参数 | |
如果设置了 opts.compNeedSecurityCheck | |
添加一个临时变量 lvaSecurityObject (TYP_REF) | |
检测是否要只生成一个ret, 保存在 oneReturn | |
如果当前平台不是x86(32位), 则为同步方法生成代码 | |
fgAddSyncMethodEnterExit | |
unsigned byte acquired = 0; | |
try { | |
JIT_MonEnterWorker(<lock object>, &acquired); | |
*** all the preexisting user code goes here *** | |
JIT_MonExitWorker(<lock object>, &acquired); | |
} fault { | |
JIT_MonExitWorker(<lock object>, &acquired); | |
} | |
如果 oneReturn, 则生成一个合并用的 BasicBlock | |
genReturnBB = fgNewBBinRegion(BBJ_RETURN) | |
如果 oneReturn 并且有返回值 | |
添加一个临时变量 genReturnLocal | |
如果函数有调用非托管函数 | |
添加本地变量 lvaInlinedPInvokeFrameVar (TYP_BLK) | |
设置该变量大小 eeGetEEInfo()->inlinedCallFrameInfo.size | |
如果启用了JustMyCode | |
添加代码 if (*pFlag != 0) call JIT_DbgIsJustMyCode 到第一个 BasicBlock | |
注意这个代码包含了分支,会被标记为 GTF_RELOP_QMARK (?:) | |
QMARK节点会在后面继续分割到多个 BasicBlock | |
如果 tiSecurityCalloutNeeded 则 | |
添加代码 call JIT_Security_Prolog(MethodHnd, &SecurityObject) 到第一个 BasicBlock | |
如果当前平台是x86(32位), 则为同步方法生成代码 | |
插入 JIT_MonEnterWorker(<lock object>) 到第一个 BasicBlock | |
插入 JIT_MonExitWorker(<lock object>) 到返回的 BasicBlock, 并确保 oneReturn 成立 | |
x86下函数遇到例外时vm会自动释放锁 | |
如果 tiRuntimeCalloutNeeded 则 | |
添加代码 call verificationRuntimeCheck(MethodHnd) 到第一个 BasicBlock | |
如果 opts.IsReversePInvoke 则 (c调用的c#函数) | |
插入调用 CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER 的代码到第一个 BasicBlock | |
插入调用 CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT 的代码到返回的 BasicBlock | |
这两个函数目前都是未定义 | |
如果 oneReturn | |
生成 GT_RETURN 类型的节点并插入到返回的 BasicBlock (之前新创建的genReturnBB) | |
如果有返回值则使用之前创建的 genReturnLocal 变量 | |
到这里原有的用于返回的 BasicBlock 仍然不会指向新创建的 genReturnBB, 到后面的 fgMorphBlocks 才会修改 | |
fgInline | |
获取一个 InlineContext rootContext | |
设置所有 BasicBlock 中的所有 GenTreeStmt 的 gtInlineContext 到 rootContext | |
枚举所有 BasicBlock 中的所有 GenTreeStmt | |
如果包含的stmt中的expr类型是 GT_CALL 并且是inline候选 (GTF_CALL_INLINE_CANDIDATE) | |
调用 fgMorphCallInline | |
调用 fgMorphCallInlineHelper | |
如果本地变量有512个以上, 则标记inline失败 | |
如果调用是virtual, 则标记inline失败 | |
如果函数需要安全检查(compNeedSecurityCheck), 则标记inline失败 | |
调用 fgInvokeInlineeCompiler | |
调用 fgCheckInlineDepthAndRecursion | |
如果出现循环inline, 例如A inline B, B inline A则设置不内联 | |
如果层数大于InlineStrategy::IMPLEMENTATION_MAX_INLINE_DEPTH(1000)则设置不内联 | |
调用inlineResult->NoteInt(InlineObservation::CALLSITE_DEPTH, depth) | |
如果inline层数超过 m_RootCompiler->m_inlineStrategy->GetMaxInlineDepth() | |
(本机是20, 也就是DEFAULT_MAX_INLINE_DEPTH) | |
则设置不内联(CALLSITE_IS_TOO_DEEP) | |
返回inline层数 | |
调用 impInlineInitVars | |
初始化 pInlineInfo, 下面会传给 jitNativeCode | |
记录this arg的信息到 pInlineInfo->inlArgInfo[argNum], pInlineInfo->lclVarInfo[argNum] | |
记录传入参数的信息到 pInlineInfo->inlArgInfo[argNum], pInlineInfo->lclVarInfo[argNum] | |
记录函数本身的本地变量到 pInlineInfo->lclVarInfo[argNum] | |
调用 jitNativeCode | |
针对inline函数生成 BasicBlock 和 GenTree, 保存到 InlineeCompiler 中 | |
针对inline函数的利益分析将会在这里进行, 如果判断不值得内联则会返回失败 | |
流程是: jitNativeCode => compCompile => compCompileHelper => fgFindBasicBlocks => | |
fgFindJumpTargets => InlineResult::DetermineProfitability => | |
LegacyPolicy::DetermineProfitability | |
如果函数有返回类型但无返回表达式, 则标记inline失败 | |
例如中途throw了导致return的 BasicBlock 未导入 | |
如果允许立刻调用initClass但初始化失败, 则标记inline失败 | |
* 从这里开始不能再标记inline失败 | |
调用 fgInsertInlineeBlocks | |
如果 InlineeCompiler 中只有一个 BasicBlock | |
把该 BasicBlock 中的所有stmt插入到原stmt后面 | |
标记原来的stmt为空 | |
如果 InlineeCompiler 中有多个 BasicBlock | |
按原stmt的位置分割所在的 BasicBlock 到 topBlock 和 bottomBlock | |
插入inline生成的 BasicBlock 到 topBlock 和 bottomBlock 之间 | |
标记原stmt为空, 原stmt还在 topBlock 中 | |
原stmt下的call会被替换为inline后的返回表达式 | |
iciCall->CopyFrom(pInlineInfo->retExpr, this) | |
其他的retExpr的gtInlineCandidate会指向这个call节点, 指向不变但内容会改变 | |
可以参考 System.IO.ConsoleStream.Flush 这个函数的inline过程 | |
标记inline成功 | |
如果inline失败 | |
清理新创建的本地变量, 恢复原有的本地变量数量(lvaCount) | |
如果inline失败 | |
如果调用结果不是void | |
把stmt中的expr设为空 | |
原来的stmt仍会被retExpr引用, 后面会替换回来 | |
取消原expr(call)的inline候选 (GTF_CALL_INLINE_CANDIDATE) | |
如果原stmt被设为空则 | |
删除原stmt | |
替换inline placeholder(retExpr)到inline后的结果 | |
fgWalkTreePre(&stmt->gtStmtExpr, fgUpdateInlineReturnExpressionPlaceHolder); | |
如果stmt是GT_RET_EXPR | |
获取 stmt->gtRetExpr.gtInlineCandidate | |
替换表达式到gtInlineCandidate, 循环替换直到无GT_RET_EXPR | |
gtInlineCandidate有可能是call, 也有可能是lclVar或者lclFld | |
替换表达式到lclFld的例子可以看Sys.GetLastErrorInfo的inline处理 | |
RecordStateAtEndOfInlining | |
除错模式时记录inline完成后的时间 | |
m_compTickCountAtEndOfInlining = GetTickCount() | |
fgMarkImplicitByRefArgs | |
遍历本地变量 | |
如果本地变量是TYP_STRUCT, 并且大小不普通(x86下3, 5, 6, 7, >8, arm64下>16)则把类型修改为TYP_BYREF | |
fgPromoteStructs | |
用于提升本地的struct变量(把各个字段提取出来作为单独的变量) | |
遍历本地变量 | |
判断是否提升 | |
如果本地变量有512个以上则不提升 | |
如果变量不是struct则不提升 | |
调用 lvaCanPromoteStructVar 判断, 返回是否可以提升和字段列表 | |
如果变量在SIMD指令中使用则不提升 | |
如果变量是HFA(homogeneous floating-point aggregate)类型则不提升 | |
调用 lvaCanPromoteStructType | |
如果struct大小比sizeof(double) * 4更大则不提升 | |
如果struct有4个以上的字段则不提升 | |
如果struct有字段地址是重叠的(例如union)则不提升 | |
如果struct有自定义layout并且是HFA类型则不提升 | |
如果struct包含非primitive类型的字段则不提升 | |
如果struct包含有特殊对齐的字段(fldOffset % fldSize) != 0)则不提升 | |
标记可以提升, 并按偏移值排序StructPromotionInfo中的字段 | |
如果判断提升, 调用 lvaPromoteStructVar(lclNum, &structPromotionInfo) | |
检查字段是否包含float | |
如果包含则标记到Compiler::compFloatingPointUsed | |
后面LSRA(Linear scan register alloc)会跟踪float寄存器的使用 | |
添加字段作为一个单独的本地变量 | |
原struct字段仍会保留 | |
fgMarkAddressExposedLocals | |
标记所有地址被导出(传给了其他函数, 或者设到了全局变量)的本地变量, 这些本地变量将不能优化到寄存器中 | |
例如 addr byref | |
\--* lclVar int (AX) V01 loc1 | |
同时修改提升的struct的字段,把field改成lclVar | |
遍历所有 BasicBlock 中的所有 stmt | |
调用 fgMarkAddrTakenLocalsPreCB | |
如果tree类型是GT_FIELD, 判断是否可以替换为lclVar | |
调用 fgMorphStructField | |
如果field所属的struct已被提升,改成lclVar | |
如果变量的地址被导出 | |
调用 lvaSetVarAddrExposed | |
调用 fgMarkAddrTakenLocalsPostCB | |
如果当前是除错模式 | |
lvaStressLclFld | |
把部分本地变量转换为TYP_BLK, 添加padding | |
然后把本地变量对应的GT_LCL_VAR节点转为GT_LCL_FLD | |
fgStress64RsltMul | |
把 intOp1*intOp2 转换为 int(long(nop(intOp1))*long(intOp2)) | |
仅会转换不带checked的int*int | |
fgMorphBlocks | |
枚举 BasicBlock | |
设置 fgGlobalMorph = true | |
调用 fgMorphStmts(block, &mult, &lnot, &loadw) | |
枚举 BasicBlock 中的 GenTreeStmt | |
有 fgRemoveRestOfBlock 时删除该Block中剩余的表达式 | |
调用 fgMorphCombineSIMDFieldAssignments 整合SIMD赋值的操作 | |
var a = new Vector3(1, 2, 3); | |
var b = new Vector3(); | |
b.X = a.X; b.Y = a.Y; b.Z = a.Z; | |
三个赋值表达式会整合到一个 simd12 (copy) 表达式 | |
调用 fgMorphTree(tree) | |
除错模式且compStressCompile时复制树, 以发现漏更新的引用数量 | |
调用 optAssertionProp (if optLocalAssertionProp) | |
根据断言属性 (AssertionProp) 修改节点 | |
断言属性在哪里生成? | |
断言属性在 optAssertionPropMain 生成, 保存在 optAssertionTabPrivate 中 | |
获取使用 optGetAssertion, 创建使用 optCreateAssertion | |
标记节点拥有的断言属性用的是一个bitmap | |
调用 optAssertionProp_LclVar | |
如果确定本地变量等于常量,修改为该常量 | |
如果确定本地变量等于另一本地变量,修改为另一本地变量 (例如alloc obj用的临时变量) | |
调用 optAssertionProp_Ind | |
如果indir左边的节点是lclVar, 并且该节点确定不为null则 | |
tree->gtFlags &= ~GTF_EXCEPT // 该表达式不会抛出异常 | |
tree->gtFlags |= GTF_ORDER_SIDEEFF // 防止reorder | |
调用 optAssertionProp_BndsChk | |
如果数组的位置是常量并且确定不会溢出, 则标记不需要检查边界 | |
arrBndsChk->gtFlags |= GTF_ARR_BOUND_INBND | |
调用 optAssertionProp_Comma | |
如果前面标记了不需要检查边界, 则删除边界检查 | |
(comma bound_check, expr) => (expr) | |
调用 optAssertionProp_Cast | |
如果是小范围类型转换为大范围类型, tree->gtFlags &= ~GTF_OVERFLOW | |
如果是大范围类型转换为小范围类型, 且确定不会溢出则去除cast | |
调用 optAssertionProp_Call | |
如果this确定不为null | |
tree->gtFlags &= ~GTF_CALL_NULLCHECK | |
tree->gtFlags &= ~GTF_EXCEPT | |
如果在调用jit helper来转换类型, 并确定转换一定成功 | |
把call替换为第一个参数, 使用comma组合副作用列表 | |
调用 optAssertionProp_RelOp | |
如果设置了 optLocalAssertionProp (优化选项) | |
如果x值确定, 把 x == const 替换成 true 或 false | |
否则 | |
如果x值确定, 把 x == const 替换成 const == const 或 !(const == const) | |
如果节点 kind & GTK_CONST 调用 fgMorphConst | |
清除标记 tree->gtFlags &= ~(GTF_ALL_EFFECT | GTF_REVERSE_OPS) | |
这些标记可能是其他节点转为const时残留的 | |
如果节点是const string | |
如果所在block的跳转类型是BBJ_THROW表示该block不经常运行 | |
把节点替换为调用 getLazyStringLiteralHelper 帮助函数的节点, 以延迟构建字符串对象 | |
否则 | |
获取字符串对象然后构建 indir \--* const long 对象指针 | |
如果节点 kind & GTK_LEAF 调用 fgMorphLeaf | |
如果 tree->gtOper == GT_LCL_VAR 调用 fgMorphLocalVar | |
如果 lclVar 类型是 TYP_BOOL 或者 TYP_SHORT, 且满足 lvNormalizeOnLoad 则先cast到int | |
如果 tree->gtOper == GT_LCL_FLD 且 _TARGET_X86_ 则调用 fgMorphStackArgForVarArgs | |
对于使用栈传递的参数, 且函数参数不定长, 则修改使用 varargs cookie 获取参数 | |
非x86平台不需要 | |
如果 tree->gtOper == GT_FTN_ADDR | |
把 ftnAddr 节点转为 ind \--* const long 函数地址 或者 nop \--* const long 函数地址 | |
如果节点 kind & GTK_SMPOP 调用 fgMorphSmpOp (简单的unary或者binary操作) | |
调用 fgMorphForRegisterFP | |
如果操作是简单的加减乘除, 且结果类型是浮点数, cast两边的类型到结果类型 | |
如果操作是比较,且两边类型不一致, 则cast float的一边到double | |
如果是赋值 GT_ASG | |
如果需要cast则cast rhs | |
如果需要转换为SIMD复制则生成SIMD语句 | |
如果是算术赋值 GT_ASG_ADD, GT_ASG_SUB 等 | |
如果lhs是本地变量或者不是TYP_STRUCT, 禁止CSE优化 (lvalue) | |
如果是取地址 GT_ADDR | |
禁止CSE优化 (lvalue) | |
如果是 GT_QMARK, 标记cond ? x : y的cond为GTF_RELOP_JMP_USED | GTF_DONT_CSE | |
如果是 GT_INDEX, 调用 fgMorphArrayIndex | |
修改访问数组元素的GT_INDEX节点到COMMA(GT_ARR_BOUND_CHK, GT_IND)节点 | |
如果是 GT_CAST, 调用 fgMorphCast | |
修改GT_CAST节点, 确认不会溢出时移除这个节点 | |
如果有需要使用helper(例如long => double)则转换为helper call | |
如果是 GT_MUL | |
如果在32位上做乘法, 结果是long且有可能溢出则需要使用helper call | |
如果是 GT_DIV | |
如果在32位上做除法, 结果是long则需要使用helper call | |
如果是 GT_UDIV | |
如果在32位上做除法, 结果是long则需要使用helper call | |
如果是 GT_MOD | |
如果结果类型是float则cast节点到double并使用helper call | |
否则也使用helper call, 因为signed mod需要更多处理 | |
如果是 GT_UMOD | |
如果a%b的结果是long, b是int常数且在2~0x3fffffff之间则不使用helper call | |
否则使用helper call | |
arm上因为无mod对应的指令, 需要转换a % b = a - (a / b) * b | |
如果是 GT_RETURN | |
如果返回数值器且比int小则先cast到int | |
如果是 GT_EQ 或 GT_NE | |
优化 typeof(...) == obj.GetType() 或 typeof(...) == typeof(...) | |
优化 Nullable<T> == null, 直接访问hasValue字段 (例如 IsNull<T>(T arg) { arg == null }) | |
如果是 GT_INTRINSIC, 并且在 arm 平台下 | |
如果 gtIntrinsicId == CORINFO_INTRINSIC_Round 则转换为helper call | |
如果 cpu 不支持浮点数运算, 调用 fgMorphToEmulatedFP | |
转换节点到helper call | |
如果表达式有可能抛出例外 | |
tree->gtFlags |= GTF_EXCEPT | |
处理 op1 | |
如果tree是QMARK COLON, 则op1是then part | |
复制optAssertionTabPrivate到origAssertionTab | |
决定 fgMorphTree op1使用的 MorphAddrContext | |
如果当前是 GT_ADDR 且原来无上下文, 则op1用一个新的 MACK_Addr 上下文 | |
如果当前是 GT_COMMA, 则op1使用一个空的上下文 | |
如果当前是 GT_ASG 并且是块赋值, 则op1用一个新的 MACK_Ind 上下文 | |
如果当前是 GT_OBJ, GT_BLK, GT_DYN_BLK, GT_IND, 则op1用一个新的 MACK_Ind 上下文 | |
如果当前是 GT_ADD 并且 mac 不为null | |
则mac应该是IND或者ADDR | |
如果op2是常量且不会溢出则加到m_totalOffset, 否则设置m_allConstantOffsets = false | |
调用 fgMorphTree(op1, subMac1) | |
如果tree是QMARK COLON, 则op1是then part | |
复制optAssertionTabPrivate到thenAssertionTab | |
修改tree->gtFlags | |
如果tree不是GT_INTRINSIC, 或者tree是GT_INTRINSIC但不需要helper call则 gtFlags &= ~GTF_CALL | |
如果tree不会抛出例外则gtFlags &= ~GTF_EXCEPT | |
复制op1的副作用标志 tree->gtFlags |= (op1->gtFlags & GTF_ALL_EFFECT) | |
如果tree是GT_ADDR并且op1是GT_LCL_VAR或者GT_CLS_VAR则 gtFlags &= ~GTF_GLOB_REF (不用全局变量) | |
处理 op2 | |
如果tree是QMARK COLON, 则op2是else part | |
复制origAssertionTab到optAssertionTabPrivate | |
决定 fgMorphTree op2使用的 MorphAddrContext | |
如果当前是 GT_ADD 并且 mac 是 MACK_Ind | |
检查 op1 是否常量, 常量时添加到m_totalOffset, 否则设置m_allConstantOffsets = false | |
如果当前是 GT_ASG 并且是块赋值, 则op2用一个新的 MACK_Ind 上下文 | |
调用 fgMorphTree(op2, mac) | |
修改tree->gtFlags | |
复制op2的副作用标志 tree->gtFlags |= (op1->gtFlags & GTF_ALL_EFFECT) | |
如果tree是QMARK COLON, 则op2是else part | |
合并then part和else part的AssertionDsc | |
如果同时存在则保留, 否则删除 | |
非64位上(long)(x shift non_const)会转换为heler call, 标记gtFlags |= GTF_CALL | |
如果tree类型是GC类型(ref byref array), 但op1和op2均为非GC类型 | |
如果tree是GT_COMMA则修改tree类型为op2类型, 否则为op1类型 | |
调用 gtFoldExpr(tree) | |
简化tree | |
如果tree类型是unary op, 且op1是常量, 返回gtFoldExprConst(tree) | |
如果tree类型是binary op并且是比较并且当前无debug | |
如果op1和op2都是常量且tree不是atomic op则返回gtFoldExprConst(tree) | |
如果op1和op2其中一个是常量, 例如op1+0或者op1*1则返回op1, op1==null则返回!op1 | |
如果op1等于op2, 例如a==a或者a+b==b+a则返回true或false | |
如果QMARK COLON的两边都一样则转换为COMMA | |
如果返回值是op1, op2, qmarkOp1, qmarkOp2中任意一个可以直接返回tree | |
如果返回值是throw, 则需要fgMorphTree(tree->gtOp.gtOp1) | |
如果返回值不等于原tree, 或者原tree是变量则可以直接返回tree | |
如果tree是比较且op2是0 | |
调用 op1->gtRequestSetFlags() 设置 gtFlags |= GTF_SET_FLAGS | |
根据oper做出postorder morphing | |
GT_ASG | |
如果op1是const则转换为(ind const), 0x123 = 1 => *0x123 = 1 | |
小类型(bool~ushort)复制时可以省略cast | |
如果op2是比较且op1是byte则不需要额外的padding (op2->gtType = TYP_BYTE) | |
如果CSE优化把op1变成了lclVar则把op2的类型从TYP_BYTE改为op1的类型 | |
给tree的op1对应的本地变量设置gtFlags |= GTF_VAR_DEF | |
GT_ASG_ADD, GT_ASG_SUB, ..., GT_ASG_RSZ | |
赋值的左边不启用CSE优化 op1->gtFlags |= GTF_DONT_CSE | |
GT_EQ, GT_NE | |
转换(expr +/- icon1) ==/!= (non-zero-icon2) | |
例如 "x+icon1==icon2" 到 "x==icon2-icon1" | |
转换 "(== (comma x (op 1 2)) 0)" 到 "((rev op) (comma x 1) 2)" | |
转换 "(== (comma (= tmp (op 1 2)) tmp) 0)" 到 "(== (op 1 2) 0)" | |
转换 "(== (op 1 2) 0)" 到 "((rev op) 1 2)" | |
GT_LT, GT_LE, GT_GE, GT_GT | |
如果x是int类型 | |
转换 "x >= 1" 到 "x > 0" | |
转换 "x < 1" 到 "x <= 0" | |
转换 "x <= -1" 到 "x < 0" | |
转换 "x > -1" 到 "x >= 0" | |
GT_QMARK | |
转换共通的赋值项, 例如转换 (cond?(x=a):(x=b)) 到 (x=(cond?a:b)) | |
如果then和else都是nop则返回cond | |
如果else是nop则转换 (cond then else) 到 ((rev cond) else then) | |
转换 (cond)?0:1 到 cond | |
https://github.com/dotnet/coreclr/issues/12383 | |
GT_MUL | |
如果操作会检查是否溢出 (checked) 则调用 | |
fgAddCodeRef(compCurBB, bbThrowIndex(compCurBB), | |
SCK_OVERFLOW, fgPtrArgCntCur) | |
判断是否已经为当前 BasicBlock 创建过这个类型的 throw basic block | |
如果未创建则创建 | |
剩余操作同GT_OR, GT_XOR, GT_AND | |
修改 "(x * 0)" 到 "0", 如果有副作用则用COMMA | |
判断op2是否power of two | |
负数时修改op1为(neg op1), 并TODO | |
如果op2是常量op1也是常量, 复制op2的gtFieldSeq到op1 | |
乘以1时返回op1 | |
把op2改为log(op2), 并且设置changeToShift (后面改oper到GT_LSH) | |
例如转换 7 * 8 到 7 << 3 | |
判断lowestbit是否1248并且op2>>log(lowestbit)是否359 | |
shift = genLog2(lowestBit) | |
factor = abs_mult >> shift | |
修改op1为op1 * factor, 并且设置changeToShift (后面改oper到GT_LSH) | |
例如转换 7 * 72 到 7 * 9 << 3 | |
GT_SUB | |
修改 "op1 - cns2" 到 "op1 + (-cns2)" | |
修改 "cns1 - op2" 到 "(cns1 + (-op2))" | |
同GT_MUL, 检查是否溢出并添加抛出溢出用的 BasicBlock | |
剩余操作同GT_OR, GT_XOR, GT_AND | |
GT_DIV | |
仅ARM, 如果不是float则添加抛出溢出或零除的 BasicBlock | |
GT_UDIV | |
仅ARM, 添加抛出零除的 BasicBlock | |
GT_ADD | |
修改 "((x+icon1)+(y+icon2)) 到 ((x+y)+(icon1+icon2))" | |
修改 "((x+icon1)+icon2)" 到 "(x+(icon1+icon2))" | |
修改 "(x + 0)" 到 "x" | |
同GT_MUL, 检查是否溢出并添加抛出溢出用的 BasicBlock | |
剩余操作同GT_OR, GT_XOR, GT_AND | |
GT_OR, GT_XOR, GT_AND | |
如果op1是常量且不是ref, 交换op1和op2 (op2放常量) | |
如果oper是GT_OR或GT_XOR, 调用 fgRecognizeAndMorphBitwiseRotation | |
转换部分特殊的模式到Circular shift | |
GT_CHS, GT_NOT, GT_NEG | |
如果启用了优化并且不在optValnumCSE_phase则断言op1不是常数(已优化) | |
GT_CKFINITE | |
为当前 BasicBlock 创建类型为 SCK_ARITH_EXCPN 的 throw basic block | |
GT_OBJ | |
如果 GT_OBJ(GT_ADDR(X)) 的 X 有 GTF_GLOB_REF | |
设置当前节点的 gtFlags |= GTF_GLOB_REF | |
GT_IND | |
修改 "*(&X)" 到 "X" | |
修改 "*(&lcl + cns)" 到 "lcl[cns]" (GT_LCL_FLD) | |
修改 "IND(COMMA(x, ..., z))" 到 "COMMA(x, ..., IND(z))" | |
GT_ADDR | |
修改 "ADDR(IND(...))" 到 "(...)" | |
修改 "ADDR(OBJ(...))" 到 "(...)" | |
修改 "ADDR(COMMA(x, ..., z))" 到 "COMMA(x, ..., ADDR(z))" | |
GT_COLON | |
调用 fgWalkTreePre(&tree, gtMarkColonCond) | |
标记QMARK COLON下的节点gtFlags |= GTF_COLON_COND | |
GT_COMMA | |
如果op2不会产生值则修改typ = tree->gtType = TYP_VOID | |
提取op1中的副作用列表(gtExtractSideEffList) | |
如果有, 则替换op1为该副作用列表 | |
如果无, 返回op2 | |
如果op2是void nop且op1是void, 则返回op1 | |
GT_JTRUE | |
如果fgRemoveRestOfBlock则转换为COMMA(op1, nop) | |
如果当前不是 optValnumCSE_phase, 并且 oper 不是 GT_ASG, GT_COLON, GT_LIST(arglist) | |
如果op1是COMMA且op1的op1是throw | |
标记 fgRemoveRestOfBlock | |
如果tree是COMMA则 op1等于throw 返回tree | |
如果tree类型等于op1类型, 返回op1 | |
如果tree类型等于void, 返回throw | |
否则修改 op1->gtType = commaOp2->gtType = tree类型, 返回op1 | |
如果op2是COMMA且op2的op1是throw | |
标记 fgRemoveRestOfBlock | |
如果 op1 无副作用 | |
如果tree是赋值, 返回op2的op1(throw) | |
如果tree是GT_ARR_BOUNDS_CHECK, 返回op2的op1(throw) | |
如果tree是COMMA, 返回op2的op1(throw) | |
必要时修改op2的类型, 返回op2 | |
如果当前启用了 CLFLG_TREETRANS (优化选项), 则调用 fgMorphSmpOpOptional | |
判断 oper 是否 OperIsCommutative (满足交换律) | |
如果 tree->gtFlags & GTF_REVERSE_OPS 则交换 op1和op2 | |
修改 "(a op (b op c))" 到 "((a op b) op c)" | |
修改 "((x+icon)+y)" 到 "((x+y)+icon)" | |
转换 "a = a <op> x" 到 "a <op>= x" | |
转换 "a = x <op> a" 到 "a <op>= x" 如果满足交换律 | |
转换 "(val + icon) * icon" 到 "(val * icon) + (icon * icon)" | |
转换 "val / 1" 到 "val" | |
转换 "(val + icon) << icon" 到 "(icon << icon + icon << icon)" | |
转换 "x ^ -1" 到 "~x" | |
如果节点 tree->OperGet() == GT_FIELD 调用 fgMorphField | |
转换 field 节点为 ind 节点 | |
(field (lclVar V00) member) => | |
(comma (nullcheck (lclVar V00)) (indir (+ (lclVar V00) (const long 8)))) | |
如果 mac.m_totalOffset + fldOffset <= MAX_UNCHECKED_OFFSET_FOR_NULL_OBJECT, nullcheck可省略 | |
如果节点 tree->OperGet() == GT_CALL 调用 fgMorphCall | |
修改调用的各个参数, 如果参数不是单纯的表达式需要使用临时变量保存 | |
如果可以尾调用优化则去除call后面的return, 但仍需要其他修改 | |
如果 canFastTailCall | |
compCurBB->bbFlags |= BBF_HAS_JMP | |
否则 | |
compCurBB->bbJumpKind = BBJ_THROW | |
如果call结果不为null, 返回一个空节点(place holder), 上层的GT_RETURN节点会使用这个空节点 | |
如果节点 tree->OperGet() == GT_ARR_BOUNDS_CHECK 或 GT_SIMD_CHK 调用 fgSetRngChkTarget | |
如果delay或inline则延迟处理, 否则 | |
创建调用 CORINFO_HELP_RNGCHKFAIL 的 BasicBlock (fgRngChkTarget) | |
设置tree的gtIndRngFailBB 等于 gtNewCodeRef(rngErrBlk) | |
如果节点 tree->OperGet() == GT_ARR_ELEM | |
tree->gtArrElem.gtArrObj = fgMorphTree(tree->gtArrElem.gtArrObj) | |
调用 fgSetRngChkTarget(tree, false), 同上 | |
如果节点 tree->OperGet() == GT_ARR_OFFSET | |
tree->gtArrOffs.gtOffset = fgMorphTree(tree->gtArrOffs.gtOffset) | |
tree->gtArrOffs.gtIndex = fgMorphTree(tree->gtArrOffs.gtIndex) | |
tree->gtArrOffs.gtArrObj = fgMorphTree(tree->gtArrOffs.gtArrObj) | |
调用 fgSetRngChkTarget(tree, false), 同上 | |
如果节点 tree->OperGet() == GT_CMPXCHG | |
tree->gtCmpXchg.gtOpLocation = fgMorphTree(tree->gtCmpXchg.gtOpLocation) | |
tree->gtCmpXchg.gtOpValue = fgMorphTree(tree->gtCmpXchg.gtOpValue) | |
tree->gtCmpXchg.gtOpComparand = fgMorphTree(tree->gtCmpXchg.gtOpComparand) | |
如果节点 tree->OperGet() == GT_STORE_DYN_BLK | |
tree->gtDynBlk.Data() = fgMorphTree(tree->gtDynBlk.Data()) | |
如果节点 tree->OperGet() == GT_DYN_BLK | |
tree->gtDynBlk.Addr() = fgMorphTree(tree->gtDynBlk.Addr()) | |
tree->gtDynBlk.gtDynamicSize = fgMorphTree(tree->gtDynBlk.gtDynamicSize) | |
调用 fgMorphTreeDone(tree, oldTree DEBUGARG(thisMorphNum)) | |
如果tree有optAssertionCount并且是针对本地变量的赋值, 则调用fgKillDependentAssertions | |
删除本地变量和本地变量promoted出来的本地变量对应的assertion | |
调用 optAssertionGen | |
根据tree创建新的assertion | |
如果是GT_ASG则 OAK_EQUAL(op1, op2) | |
如果是GT_NULLCHECK或者GT_ARR_LENGTH则 OAK_NOT_EQUAL(op1, nullptr) | |
如果是GT_ARR_BOUNDS_CHECK则 OAK_NOT_EQUAL(tree, nullptr) | |
如果是GT_ARR_ELEM则 OAK_NOT_EQUAL(tree->gtArrElem.gtArrObj, nullptr) | |
如果是GT_CALL且gtFlags & GTF_CALL_NULLCHECK则 OAK_NOT_EQUAL(thisArg, nullptr) | |
如果是GT_CAST则 OAK_SUBRANGE(op1, tree) | |
如果是GT_JTRUE则调用optAssertionGenJtrue | |
assertionKind = GT_EQ ? OAK_EQUAL, GT_NE ? OAK_NOT_EQUAL | |
调用optCreateJtrueAssertions(op1, op2, assertionKind); | |
如果call节点被修改成return, 表示启用了尾调用优化 | |
这里检查原来的call是否尾调用 | |
如果compCurBB被修改了, 表示启用了尾调用优化 | |
这里检查原来的call是否尾调用 | |
除错模式且compStressCompile时复制树, 以发现漏更新的引用数量 | |
如果 morph 是 COMMA 且 op1 是 throw 则 morph = op1 并且 fgRemoveRestOfBlock = true | |
如果 fgRemoveRestOfBlock, 跳过后面的处理并返回 | |
调用 fgCheckRemoveStmt, 如果 stmt 无副作用则删除该 stmt, 跳过后面的处理并返回 | |
调用 fgFoldConditional, TODO | |
调用 ehBlockHasExnFlowDsc TODO | |
检测是否有连续的 += 或者 -=, 有则设置 *mult = true | |
检测 "x = a[i] & icon; x |= a[i] << 8", 有则设置 *loadw = true (检测似乎未完成) | |
如果 fgRemoveRestOfBlock 并且 block->bbJumpKind 等于 BBJ_COND 或 BBJ_SWITCH | |
如果 bbJumpKind == BBJ_COND 且 lastStmt->gtOper == GT_JTRUE | |
或者 bbJumpKind == BBJ_SWITCH 且 lastStmt->gtOper == GT_SWITCH | |
修改 last->gtStmt.gtStmtExpr = fgMorphTree(op1) (去掉判断只剩条件) | |
调用 fgConvertBBToThrowBB | |
设置 block->bbJumpKind = BBJ_THROW | |
如果 endsWithTailCallConvertibleToLoop (尾调用是否可以转换为循环) | |
调用 fgMorphRecursiveFastTailCallIntoLoop | |
把调用中的各个参数提取出来, 例如 | |
call(arg0 - 1, arg1, tmp0 = concat(arg1, arg2)) | |
转换到 | |
tmp0 = concat(arg1, arg2) | |
tmp1 = arg0 - 1 | |
arg2 = tmp0 | |
arg0 = tmp1 | |
删掉call, 把block的jumpKind改为BBJ_ALWAYS, 跳转目标是第一个非scratch的block | |
设置 fgRemoveRestOfBlock = false | |
整合连续的+=和-= | |
#if OPT_MULT_ADDSUB 到 #endif | |
如果之前设置了oneReturn则把所有return block整合到sgenReturnBB | |
如果有返回值返回值需要设置到genReturnLocal | |
设置 fgGlobalMorph = false | |
fgSetOptions | |
设置codeGen的选项, 包括 | |
genInterruptible: 是否生成完全可中断的代码, 用于debugger | |
setFramePointerRequired: 是否要求保存frame pointer(rbp) | |
setFramePointerRequiredEH: EH表有内容时要求frame pointer, 变量跟上面一样 | |
setFramePointerRequiredGCInfo: 如果参数太多, 要安全检查或者有动态长度参数则要求frame pointer, 同上 | |
fgExpandQmarkNodes | |
对所有block的所有stmt调用fgExpandQmarkStmt | |
分解QMARK节点 | |
分离block到 block condBlock elseBlock remainderBlock | |
调用 fgSplitBlockAfterStatement 和 fgRemoveRefPred 分割到 [before QMARK] [after] | |
调用 fgNewBBafter 插入 condBlock 和 elseBlock | |
如果同时有trueExpr和elseExpr | |
反转cond | |
condBlock->bbJumpDest = elseBlock | |
在elseBlock和remainderBlock中间插入thenBlock | |
thenBlock->bbJumpDest = remainderBlock | |
|===(如果反转的cond成立)===v | |
block => condBlock => thenBlock elseBlock => remainderBlock | |
|=========================^ | |
如果只有trueExpr | |
反转cond | |
condBlock->bbJumpDest = remainderBlock | |
thenBlock = elseBlock; elseBlock = nullptr | |
|===(如果反转的cond成立)===v | |
block => condBlock => thenBlock => remainderBlock | |
如果只有falseExpr | |
condBlock->bbJumpDest = remainderBlock | |
|======(如果cond成立)======v | |
block => condBlock => elseBlock => remainderBlock | |
移除原来的qmark表达式 | |
向thenBlock添加true时的表达式 | |
像elseBlock添加false时的表达式 | |
PHASE_GS_COOKIE | |
gsGSChecksInitCookie | |
添加一个本地变量 lvaGSSecurityCookie | |
设置这个本地变量的地址会暴露 lvaSetVarAddrExposed(lvaGSSecurityCookie) | |
调用 getGSCookie(&gsGlobalSecurityCookieVal, &gsGlobalSecurityCookieAddr) | |
这里的函数是 CEEINFO::getGSCookie (jitinterface.cpp) | |
会设置 gsGlobalSecurityCookieVal = s_gsCookie, gsGlobalSecurityCookieAddr = NULL | |
s_gsCookie会在 InitGSCookie中初始化, linux上的值是GetTickCount() | |
gsCopyShadowParams | |
gsShadowVarInfo = new ShadowParamVarInfo[lvaCount] | |
TODO: 找出 gsShadowVarInfo->assignGroup 是在哪里设置的 | |
调用 gsFindVulnerableParams | |
调用 fgWalkAllTreesPre(gsMarkPtrsAndAssignGroups, &info) | |
设置各个变量的 lvIsPtr (如果变量在indir下) | |
如果有任何本地变量是 lvIsPtr 或者 lvIsUnsafeBuffer 则 hasOneVulnerable = true | |
如果本地变量关联的 shadowInfo->assignGroup 中的任意一个本地变量有 lvIsPtr | |
设置 hasOneVulnerable = true | |
设置 shadowInfo->assignGroup 中的所有本地变量 lvIsPtr = TRUE | |
返回 hasOneVulnerable | |
如果返回true则调用 gsParamsToShadows | |
枚举本地变量 | |
如果变量不是 lvIsParam, 则跳过 | |
如果变量不是 lvIsPtr 且不是 lvIsUnsafeBuffer, 则跳过 | |
获取临时变量 shadowVar | |
复制本地变量的信息到 shadowVar | |
设置 gsShadowVarInfo[lclNum].shadowCopy = shadowVar | |
调用 fgWalkAllTreesPre(gsReplaceShadowParams, (void*)this) | |
替换树中访问参数变量为访问shadowVar | |
添加 shadowVar = 参数变量 的表达式到第一个 BasicBlock 中 | |
如果有尾调用则需要把 参数变量 = shadowVar 的表达式添加到最后 | |
PHASE_COMPUTE_PREDS | |
fgRenumberBlocks | |
修改各个 BasicBlock 的序号, 如果有序号被修改或者最大序号有变化则返回true | |
返回true会导致 block set epoch 增加 | |
枚举 BasicBlock | |
检查并设置序号, 非inline时 [0, 1, 2, 3...], inline时 [InlinerCompiler->fgBBNumMax, +1, +2, +3, ...] | |
如果 renumbered || newMaxBBNum | |
调用 NewBasicBlockEpoch, 更新 fgCurBBEpoch | |
调用 InvalidateUniqueSwitchSuccMap, 清除switch block的缓存(因为index是序号) | |
否则 | |
调用 EnsureBasicBlockEpoch, fgCurBBEpochSize != fgBBNumMax + 1时调用 NewBasicBlockEpoch | |
fgComputePreds | |
重新计算 BasicBlock 的 bbRefs 和 bbPreds | |
枚举 BasicBlock | |
block->bbRefs = 0 | |
调用 fgRemovePreds, 删除所有 BasicBlock 的 bbPreds | |
设置第一个 BasicBlock 的 fgFirstBB->bbRefs = 1 | |
枚举 BasicBlock | |
如果类型是 BBJ_LEAVE, BBJ_COND, BBJ_ALWAYS, BBJ_EHCATCHRET | |
调用 fgAddRefPred(block->bbJumpDest, block, nullptr, true) | |
如果类型是 BBJ_NONE | |
调用 fgAddRefPred(block->bbNext, block, nullptr, true) | |
如果类型是 BBJ_EHFILTERRET | |
调用 fgAddRefPred(block->bbJumpDest, block, nullptr, true) | |
如果类型是 BBJ_EHFINALLYRET | |
查找调用 finally funclet 的 block, 如果找到则 (调用完以后返回到bcall->bbNext) | |
fgAddRefPred(bcall->bbNext, block, nullptr, true) | |
如果类型是 BBJ_THROW, BBJ_RETURN | |
不做处理 | |
如果类型是 BBJ_SWITCH | |
设置所有跳转目标的 fgAddRefPred(*jumpTab, block, nullptr, true) | |
枚举 eh table | |
设置 ehDsc->ebdHndBeg->bbFlags |= BBF_JMP_TARGET | BBF_HAS_LABEL | |
PHASE_MARK_GC_POLL_BLOCKS | |
fgMarkGCPollBlocks | |
枚举 BasicBlock | |
如果block会跳转到前面的block, 或者block是ret | |
则标记 bbFlags |= BBF_NEEDS_GCPOLL | |
PHASE_COMPUTE_EDGE_WEIGHTS | |
fgComputeEdgeWeights | |
fgCalledWeight = 100 | |
枚举 BasicBlock (循环直到无改变或者达到10次) | |
如果block只有一个前置block, 且前置block只有一个后置block | |
newWeight = bSrc->bbWeight | |
如果block只有一个后置block, 且后置block只有一个前置block | |
newWeight = bOnlyNext->bbWeight | |
如果 newWeight != BB_MAX_WEIGHT 且 bDst->bbWeight != newWeight | |
changed = true | |
bDst->bbWeight = newWeight | |
如果 newWeight == 0, 设置 BBF_RUN_RARELY, 否则清理 BBF_RUN_RARELY | |
如果block是BBJ_RETURN或者BBJ_THROW | |
则合计returnWeight += bDst->bbWeight | |
枚举 BasicBlock | |
如果 block 是 fgFirstBB | |
bDstWeight -= fgCalledWeight | |
枚举 edge in bDst->bbPreds | |
slop = (max(bSrc->bbWeight, bDst->bbWeight) + 64) / 128 + 1 | |
调整 edge 的 flEdgeWeightMin 和 flEdgeWeightMax | |
继续调整 edge 的 flEdgeWeightMin 和 flEdgeWeightMax | |
如果任意的edge的 flEdgeWeightMin != flEdgeWeightMax | |
则设置 fgRangeUsedInEdgeWeights | |
PHASE_CREATE_FUNCLETS | |
fgCreateFunclets | |
调用 fgCreateFuncletPrologBlocks | |
枚举 eh table, 如果处理器的第一个block的preds中有不是来源于try的block | |
例如: finally中有do { } while(xxx); | |
调用 fgInsertFuncletPrologBlock 插入在head前面插入一个新的 BasicBlock | |
枚举 eh table | |
if (HBtab->HasFilter()) | |
funcInfo[funcIdx].funKind = FUNC_FILTER | |
funcInfo[funcIdx].funEHIndex = (unsigned short)XTnum | |
funcIdx++ | |
funcInfo[funcIdx].funKind = FUNC_HANDLER | |
funcInfo[funcIdx].funEHIndex = (unsigned short)XTnum | |
funcIdx++ | |
调用 fgRelocateEHRange(XTnum, FG_RELOCATE_HANDLER) | |
移动该例外处理器的 BasicBlock 范围到函数末尾 | |
如果 fgFirstFuncletBB 尚未设置则 fgFirstFuncletBB = bStart | |
设置 compFuncInfos = funcInfo | |
PHASE_OPTIMIZE_LAYOUT | |
optOptimizeLayout | |
枚举 BasicBlock, 调用 fgOptWhileLoop | |
前 jmp test; loop: ...; test: cond; jtrue loop; | |
后 cond; jfalse done; loop: ...; test: cond; jtrue loop; done: ...; | |
如果有修改则调用 fgComputeEdgeWeights | |
调用 fgUpdateFlowGraph(doTailDuplication: true) | |
删除空 BasicBlock, 无法到达的 BasicBlock, 和减少多余的jumps | |
doTailDuplication 等于 true | |
针对BBJ_ALWAYS的block调用 fgOptimizeUncondBranchToSimpleCond | |
如果接下来的 block 满足 fgBlockIsGoodTailDuplicationCandidate 则 | |
复制它到当前block, 修改当前block为BBJ_COND (上面要求target是BBJ_COND) | |
调用 fgReorderBlocks() | |
把比较少运行的 BasicBlock (rarely run) 放到末尾 | |
调用 fgUpdateFlowGraph(doTailDuplication: false) | |
无标识 | |
fgComputeReachability | |
枚举 BasicBlock, 构建只包含 BBJ_RETURN 的链表 fgReturnBlocks | |
循环, 最多10次 | |
调用 fgRenumberBlocks 重新编排 BasicBlock 的序号 | |
调用 fgComputeEnterBlocksSet 计算进入函数的 BasicBlock | |
fgFirstBB 会加入到 fgEnterBlks | |
所有例外处理器的第一个 block 都会加入到 fgEnterBlks | |
调用 fgComputeReachabilitySets | |
枚举 BasicBlock, 初始化各个 block 的 bbReach | |
计算各个 block 的 bbReach (block自身和所有preds的bbReach的union) | |
调用 fgRemoveUnreachableBlocks, 无变更时跳出循环 | |
枚举 BasicBlock, 如果 fgEnterBlks | block->bbReach 等于空则 | |
调用 fgUnreachableBlock 设置 block 为 BBF_REMOVED | |
如果 block 同时标记了 BBF_DONT_REMOVE, 则 | |
清除 BBF_REMOVED 和 BBF_INTERNAL 并标记 BBF_IMPORTED | |
如果有标记 BBF_REMOVED, 则删除标记的block | |
调用 fgComputeDoms | |
调用 fgDfsInvPostOrder 更新 fgBBInvPostOrder | |
fgBBInvPostOrder 保存的是 post order 探索 flow graph 后的 block 序号 | |
顺序是post order的相反顺序 例如 1=>2=>3, 6=>4=>5=>7=>8 会保存为 6 4 5 7 8 1 2 3 | |
fgBBInvPostOrder[0] 是 bbRoot | |
枚举 BasicBlock | |
如果 block->bbPreds == nullptr | |
block->bbPreds = &flRoot | |
block->bbIDom = &bbRoot | |
枚举 eh table | |
设置filter和处理器的第一个block | |
block->bbIDom = &bbRoot | |
设置各个 BasicBlock 的 bbIDom 为它们的 bbPreds 的共同的 bbIDom | |
恢复之前设为 &flRoot 的 bbPreds = nullptr | |
调用 fgBuildDomTree | |
创建一个 BasicBlockList** domTree | |
枚举 BasicBlock | |
bbNum = block->bbIDom->bbNum | |
domTree[bbNum] = 加入到链表(当前block) | |
枚举 BasicBlock | |
对dom树进行DFS探索, 设置 fgDomTreePreOrder 和 fgDomTreePostOrder | |
无子节点的树会加到最后面 | |
PHASE_ALLOCATE_OBJECTS | |
ObjectAllocator::Run | |
把 GT_ALLOCOBJ 转换为 GT_CALL (helper call) | |
PHASE_OPTIMIZE_LOOPS | |
optOptimizeLoops | |
调用 optSetBlockWeights, 根据dom树设置不能到达return block的block的weight /= 2 | |
调用 optFindNaturalLoops 查找所有自然的循环, 使用 optRecordLoop 设置到 optLoopTable | |
optRecordLoop 会查找 for (init; test; incr) { ... } 模式的循环 | |
找到时会设置 optLoopTable[loopInd].lpIterTree = incr, 后面优化可用 | |
枚举 BasicBlock | |
查找循环的top和最下面的bottom, 调用optMarkLoopBlocks标记 | |
如果 block 是任意 backedge block 的 dominator 则 weight *= 8 否则 *= 4 | |
PHASE_CLONE_LOOPS | |
optCloneLoops | |
调用 optObtainLoopCloningOpts, 传入 context | |
枚举 optLoopTable | |
如果 optIsLoopClonable 成立, 调用 optIdentifyLoopOptInfo | |
如果循环可以优化, 设置传入的 context.optInfo[loopNum] | |
枚举 optLoopTable | |
如果有对应的 LoopOptInfo 则调用 optCloneLoop | |
原始: | |
for (var x = 0; x < a.Length; ++x) { | |
b[x] = a[x]; | |
} | |
optOptimizeLoops后: | |
if (x < a.Length) { | |
do { | |
var tmp = a[x]; | |
b[x] = tmp; | |
x = x + 1; | |
} while (x < a.Length); | |
} | |
optCloneLoops后: | |
if (x < a.Length) { | |
if ((a != null && b != null) && (a.Length <= b.Length)) { | |
do { | |
var tmp = a[x]; // no bounds check | |
b[x] = tmp; // no bounds check | |
x = x + 1; | |
} while (x < a.Length); | |
} else { | |
do { | |
var tmp = a[x]; | |
b[x] = tmp; | |
x = x + 1; | |
} while (x < a.Length); | |
} | |
} | |
PHASE_UNROLL_LOOPS | |
optUnrollLoops | |
如果循环次数可以在编译时决定, 复制循环中的内容指定次数并删除循环的条件判断部分 | |
部分条件下不会执行unroll | |
编译debug代码, 或者需要小代码优化时 | |
循环中代码过多时 (UNROLL_LIMIT_SZ) | |
循环次数过多时 (ITER_LIMIT) | |
.Net Core 1.1的debug build有bug, 这个优化不会生效, 原因有 | |
optCanCloneLoops的条件判断反过来了, 设置COMPlus_JitCloneLoops=0才可以生效 | |
++x, x++, x+=1都会的incr树都为x = x + 1, 导致实际会跳过优化 | |
PHASE_MARK_LOCAL_VARS | |
lvaMarkLocalVars | |
计算各个本地变量的引用计数 | |
如果有pinvoke, 且不用helper则需要设置compLvFrameListRoot的引用计数为2 | |
调用 lvaAllocOutgoingArgSpace 添加本地变量 lvaOutgoingArgSpaceVar | |
lvaOutgoingArgSpaceVar的作用: x86以外通过栈传递参数时可以不使用push而是修改它的值 | |
如果不为例外处理器生成 funclet, 则添加本地变量 lvaShadowSPslotsVar | |
lvaShadowSPslotsVar的作用: x86处理例外时使用的内部变量, 参考clr-abi.md, x86以外会用PSPSym | |
如果需要为例外生成器生成 funclet, 则添加本地变量 lvaPSPSym | |
lvaPSPSym的作用: 参考上面PSPSym是什么 | |
如果使用了localloc(stackalloc), 则添加本地变量 lvaLocAllocSPvar | |
lvaLocAllocSPvar的作用: 用于保存修改后的sp地址(genLclHeap) | |
除错模式需要给各个本地变量分配序号 | |
varDsc->lvSlotNum = lclNum // 从0开始递增 | |
枚举 BasicBlock, 调用 lvaMarkLocalVars(block) | |
枚举 block 中的各个指令 | |
探索指令中的节点, 调用lvaMarkLclRefs(*pTree) | |
查找节点中的本地变量, 调用incRefCnts增加该变量的引用计数(lvRefCnt) | |
如果本地变量用于储存来源于寄存器的引用参数, 则添加两次引用次数 | |
如果lvaKeepAliveAndReportThis成立(例如同步函数需要unlock this) | |
并且如果该函数中无其他部分使用this, 则设置this的引用计数为1 | |
如果lvaReportParamTypeArg成立 | |
并且如果该函数中无其他部分使用这个变量, 则设置这个变量的引用计数为1 | |
paramTypeArg(Generic Context)的作用是调用时传入MethodDesc | |
例如 new A<string>().Generic<int>(123) 时会传入Generic<int>对应的MethodDesc | |
调用 lvaSortByRefCount | |
判断各个本地变量是否可以跟踪 (lvTracked), 和是否可以存到寄存器 (lvDoNotEnregister) | |
生成小代码时按 lvRefCnt, 否则按 lvRefCntWtd 从大到小排序本地变量 | |
排序后生成新的lvaCurEpoch | |
PHASE_OPTIMIZE_BOOLS | |
optOptimizeBools | |
合并相邻的两个根据条件跳转的BasicBlock, 要求第二个BasicBlock中只有一条语句 | |
转换 B1: brtrue(t1, BX) | |
B2: brtrue(t2, BX) | |
B3 | |
到 B1: brtrue(t1|t2, BX) | |
B3: | |
转换 B1: brtrue(t1, B3) | |
B2: brtrue(t2, BX) | |
B3: | |
到 B1: brtrue((!t1)&&t2, BX) | |
B3: | |
这个优化的生效条件很苛刻, 它要求条件必须是bool(1==1不行), 并且无副作用 | |
测试中用了if (a){goto x;} if (b){goto x;}才生效, if (!b) 不会生效 | |
PHASE_FIND_OPER_ORDER | |
fgFindOperOrder | |
判断所有GenTree的评价顺序, 设置运行成本和体积成本和x87使用的浮点数栈的最大深度等等 | |
参考: https://en.wikibooks.org/wiki/X86_Assembly/Floating_Point#FPU_Register_Stack | |
枚举 BasicBlock 中的所有 Stmt, 调用 gtSetStmtInfo(stmt) | |
调用 gtSetEvalOrder(expr) | |
作用: 设置 expr 中的 gtCostEx 和 gtCostSz, 必要时设置 GTF_REVERSE_OPS (例如asg, 或者右边level比左边高) | |
根据表达式的类型叠加 gtCostEx 和 gtCostSz, 例如GT_MUL的costEx是3, costSz是2 | |
读取float值到fp stack的时候(例如GT_IND)调用 genIncrementFPstkLevel 增加 genFPstkLevel | |
从fp stack取出值的时候(例如GT_ADDR)调用 genDecrementFPstkLevel 减少 genFPstkLevel | |
如果 GT_OR/GT_XOR/GT_AND 的任意一边有 gtFPlvl, 则设置 gtFPstLvlRedo = true | |
复制 expr 中的 gtCostEx 和 gtCostSz 到 stmt | |
如果 gtFPstLvlRedo 被设置为 true, 表示FP Level需要重新计算 | |
调用 gtComputeFPlvls(expr) 重新计算 genFPstkLevel | |
PHASE_SET_BLOCK_ORDER | |
fgSetBlockOrder | |
判断是否要生成可中断的代码(例如有循环时需要生成), 如果要则设置 genInterruptible = true | |
调用 fgCreateGCPolls 插入GC Poll的代码 | |
枚举 BasicBlock, 如果标记为 BBF_NEEDS_GCPOLL, 则调用 fgCreateGCPoll(pollType, block) | |
在 block 的末尾插入调用 CORINFO_HELP_POLL_GC(JIT_PollGC) 的代码 | |
枚举 BasicBlock, 调用 fgSetBlockOrder(block) | |
枚举 block->bbTreeList, 调用 fgSetStmtSeq(tree) | |
调用 fgSetTreeSeqHelper(tree->gtStmt.gtStmtExpr) | |
针对各个节点递归调用 fgSetTreeSeqHelper, 如果节点是叶子则调用 fgSetTreeSeqFinish | |
例如 a + b 会分别对 a, b, + 这3个节点调用 fgSetTreeSeqFinish | |
fgSetTreeSeqFinish 调用时会增加fgTreeSeqNum, 并且添加节点到链表 fgTreeSeqLst | |
设置 tree->gtStmt.gtStmtList = fgTreeSeqBeg | |
设置 fgStmtListThreaded = true, 标记表达式列表已经排列完毕 | |
PHASE_BUILD_SSA | |
fgSsaBuild | |
调用 SsaBuilder::Build(this, pIAllocator) | |
调用 SetupBBRoot, 确定第一个 block 在try外面, 表示dominator树只有一个根节点 | |
调用 TopologicalSort 对 BasicBlock 进行post order排序, 储存到数组 postOrder | |
调用 ComputeImmediateDom(postOrder, count), 计算 IDom(b) | |
因为之前的 fgComputeDoms 也计算了Dom树, 这里需要重设之前计算出的 blk->bbIDom = nullptr | |
计算 bbIDom 的算法请参考: https://www.cs.rice.edu/~keith/EMBED/dom.pdf | |
调用 ComputeDominators(postOrder, count, domTree), 构建 Dominator 树 | |
枚举 BasicBlock, 调用 ConstructDomTreeForBlock(pCompiler, block, domTree) | |
建立 block->IDom 到 block 的索引, 关系是一对多 | |
如果启用了 SSA_FEATURE_DOMARR | |
构建两个辅助性的数组 m_pDomPreOrder, m_pDomPostOrder, 可用于常数时间内判断 a dom? b | |
调用 InsertPhiFunctions(postOrder, count), 插入 phi 节点 | |
调用 fgLocalVarLiveness | |
设置 block->bbVarUse 表示在定义变量之前使用的变量集合 | |
设置 block->bbVarDef 表示在使用变量之前定义的变量集合 | |
设置 block->bbLiveIn 进入block时存活的变量集合 | |
设置 block->bbLiveOut 离开block后存活的变量集合 | |
调用 ComputeIteratedDominanceFrontier | |
计算 Dominator Frontier(DF), 算法参考: https://en.wikipedia.org/wiki/Static_single_assignment_form | |
计算 Iterated Dominance Frontiers(IDF) | |
如果A的DF包含B, B的DF包含C, 则A的IDF包含B和C | |
最终返回IDF | |
按post order枚举 BasicBlock | |
如果block无DF, 则跳过处理 | |
枚举 block->bbVarDef 中的本地变量 | |
枚举 block 的 IDF | |
检查本地变量是否存在于该DF中, 不存在时跳过 | |
如果之前已插入过phi节点, 则跳过 | |
插入一个`lcl = phi`节点, phi下的列表为空, 后面会补充 | |
如果block修改过heap (bbHeapDef) | |
设置该block的DF的bbHeapSsaPhiFunc = BasicBlock::EmptyHeapPhiDef | |
调用 RenameVariables(domTree, pRenameState), 设置使用本地变量的节点的 Use 和 Define 版本号 | |
枚举所有参数和需要清0初始化的本地变量 | |
设置这些本地变量的SSA版本为FIRST_SSA_NUM (计数器+1), FIRST_SSA_NUM目前等于2 | |
设置初始的Heap的SSA版本为FIRST_SSA_NUM (计数器+1) | |
设置所有不可到达的 BasicBlock 的 bbHeapSsaNumIn, bbHeapSsaNumOut 等于 initHeapCount | |
建立一个堆栈结构, 初始有 fgFirstBB, 循环处理到这个堆栈为空 | |
从堆栈取出 block | |
如果 block 未标记已处理 | |
标记已处理并推入堆栈 | |
调用 BlockRenameVariables(block, pRenameState) | |
如果 block->bbHeapSsaPhiFunc != nullptr, 则更新heap的SSA版本 | |
设置 block->bbHeapSsaNumIn = pRenameState->CountForHeapUse() | |
枚举 block 中的 stmt, 跳过 phi 节点 | |
按执行顺序枚举 stmt 中的 tree | |
调用 TreeRenameVariables(tree, block, pRenameState, isPhiDefn) | |
如果 tree 修改了 heap | |
调用 pRenameState->PushHeap 创建一个新的元素到 heapStack | |
给该tree设置一个新的SSA版本到m_pCompiler->GetHeapSsaMap | |
添加 block 到对应的eh handle的 bbHeapSsaPhiFunc 链表中, CountForHeapUse会使用这里的值 | |
如果是间接分配 (例如写入struct的成员) | |
如果struct并非只有一个成员, 则设置 pIndirAssign->m_useSsaNum | |
设置 pIndirAssign->m_defSsaNum 等于新的SSA版本 | |
调用 pRenameState->Push 创建一个新的元素到 stacks[lclNum], CountForUse会使用这里的值 | |
如果 GTF_VAR_DEF 成立, 表示该tree修改了本地变量 | |
如果 GTF_VAR_USEASG 成立, 表示该tree既读取又修改 (例如 += ) | |
设置 gtLclVarCommon.SetSsaNum(CountForUse(...)) | |
设置 m_pCompiler->GetOpAsgnVarDefSsaNums()->Set(tree, CountForDef(...)) | |
调用 pRenameState->Push 创建一个新的元素到 stacks[lclNum] | |
否则 | |
设置 tree->gtLclVarCommon.SetSsaNum(CountForDef(...)) | |
如果 GTF_VAR_DEF 不成立, 且不是phi节点 | |
设置 gtLclVarCommon.SetSsaNum(CountForUse(...)) | |
设置 block->bbHeapSsaNumOut = pRenameState->CountForHeapUse() | |
调用 AssignPhiNodeRhsVariables(block, pRenameState) | |
枚举 block 的 Successor | |
枚举 Successor block 的 phi 节点 | |
获取本地变量的当前SSA版本 ssaNum = pRenameState->CountForUse(lclNum) | |
这个SSA版本就是这个block处理完以后的SSA版本 | |
如果 ssaNum 不在 phi 节点的 argList 中则添加进去 | |
添加 block 到 Successor block 的 bbHeapSsaPhiFunc 链表中 | |
如果 Successor block 在 try 中, 但 block 不在 | |
枚举该try对应的handler | |
添加本地变量的当前的SSA版本到 handler 的 phi 节点中 | |
添加 block 到 handler 的 bbHeapSsaPhiFunc 链表中 | |
枚举 block 的Dom树下的子 block, 把它们推入堆栈 | |
如果 block 标记为已处理 | |
调用 BlockPopStacks(block, pRenameState) | |
弹出 stacks[lclNum] 中block对应的元素, CountForUse的值会变为前一个block的值 | |
弹出 heapStack 中block对应的元素 | |
记录 m_pCompiler->lvHeapNumSsaNames = pRenameState->HeapCount() | |
PHASE_EARLY_PROP | |
optEarlyProp | |
调用 optDoEarlyPropForFunc, 返回false时返回 | |
函数有 获取数组长度, 获取类型MT, 进行NULL检查 的任意一项才返回true | |
枚举 BasicBlock | |
调用 optDoEarlyPropForBlock, 返回false时返回 | |
block有 获取数组长度, 获取类型MT, 进行NULL检查 的任意一项才返回true | |
枚举 block 中的 stmt | |
按执行顺序枚举 stmt 中的 tree | |
调用 optEarlyPropRewriteTree(tree) | |
如果 tree 是 GT_ARR_LENGTH | |
设置 propKind = optPropKind::OPK_ARRAYLEN | |
如果 tree 是 GT_IND, 且tree不是struct | |
调用 optFoldNullCheck(tree) | |
对于 x = (nullcheck y, y + const), 如果const不是big offset则可以删除nullcheck | |
如果 tree 是vtable ref, 且 stmt 不只是用来检查vtable ref是否null | |
设置 propKind = optPropKind::OPK_OBJ_GETTYPE | |
调用 optPropGetValue(lclNum, ssaNum, propKind) 尝试获取实际的值 | |
递归追踪本地变量, 最多 optEarlyPropRecurBound(5) 层 | |
例如 a = new int[123]; b = a; c = b; 可以确认c的长度是100 | |
这个函数会追踪 数组的长度, 新建对象的MT 如果是常量则返回常量的tree | |
如果获取成功 | |
复制获取到的tree, 减去原来的tree的引用计数, 添加新的tree的引用计数 | |
调用 gtReplaceTree 替换 tree (如果大小足够复制到原来的tree里, 就不需要这步) | |
设置 isRewritten = true | |
如果stmt中的任意tree被修改则 | |
调用 gtSetStmtInfo(stmt), 流程前面有, 会重新计算成本和决定运行顺序 (GTF_REVERSE_OPS) | |
调用 fgSetStmtSeq(stmt), 流程前面有, 会生成评价顺序的链表 | |
PHASE_VALUE_NUMBER | |
fgValueNumber | |
作用: | |
计算各变量的ValueNumber, 参考 https://en.wikipedia.org/wiki/Global_value_numbering | |
两个[变量:SSA版本]拥有同一个ValueNumber表示它们的值同等 | |
ValueNumber有两种类型 | |
Liberal假定其他线程只有在同步点才会修改heap中的内容 | |
Conservative假定其他线程在任意两次访问之间都有可能修改heap中的内容 | |
如果是第一次计算, 生成ValueNumStore, 否则清空之前计算的数据 | |
调用 optComputeLoopSideEffects | |
TODO | |
枚举本地变量 | |
如果变量是参数 | |
获取变量的SSA版本是 SsaConfig::FIRST_SSA_NUM 时的数据, 根据变量序号分配一个新的VN(递增) | |
如果变量是需要清0, 或者需要跟踪(lvTracked)的变量 | |
如果变量是块, 则分配 vnStore->VNForExpr(fgFirstBB) | |
如果变量是引用且需要清0则分配 vnStore->VNForByrefCon(0) | |
如果变量是引用且不需要清0则分配 VNForFunc(typ, VNF_InitVal, vnStore->VNForIntCon(i)) | |
如果变量不是引用且需要清0的则分配 vnStore->VNZeroForType(typ) | |
如果变量不是引用且不需要清0的则分配 VNForFunc(typ, VNF_InitVal, vnStore->VNForIntCon(i)) | |
给第一个heap变量分配 vnStore->VNForIntCon(-1) | |
循环处理 BasicBlock | |
循环会使用两个bitset记录所有preds都已处理完的block, 和不是所有preds都已处理完的block | |
初始会把 fgFirstBB 放到 m_toDoAllPredsDone 中 | |
处理一个block以后会判断block的所有successor, 分别放入 m_toDoAllPredsDone 或 m_toDoNotAllPredsDone 中 | |
当 m_toDoAllPredsDone 处理完以后会处理 m_toDoNotAllPredsDone | |
处理 m_toDoNotAllPredsDone 时会判断是否循环, 如果是循环会在判断时忽略掉loop preds | |
对于 m_toDoAllPredsDone 中的block会调用 fgValueNumberBlock(toDo, /*newVNsForPhis*/ false) | |
对于 m_toDoNotAllPredsDone 中的block会调用 fgValueNumberBlock(toDo, /*newVNsForPhis*/ true) | |
处理block前面的phi节点 | |
如果来源的VN都一样, 就用一样的VN, 否则分配新的VN(VNF_PhiDef + GetLclNum + GetSsaNum) | |
处理block对应的heap变量 | |
如果来源的VN都一样, 就用一样的VN, 否则分配新的VN(VNF_PhiHeapDef + blk + union(phiArgSSANum)) | |
枚举block中的stmt, 跳过phi节点 | |
按执行顺序枚举stmt中的tree, 执行fgValueNumberTree(tree) | |
SIMD操作分配TYP_UNKNOWN, 不参与VN | |
如果是const, 且类型不是struct, 调用fgValueNumberTreeConst(tree) | |
根据常量获取和分配VN (例如VNForIntCon) | |
如果tree是leaf | |
设置tree的VN, 例如LCLVAR的时候根据本地变量[SSA]的VN设置tree的VN | |
如果tree是简单操作 | |
设置tree的VN, 例如赋值操作时设置lhs tree和对并的本地变量[SSA]的VN到rhs tree的VN | |
如果tree是GT_CALL, 调用 fgValueNumberCall(tree->AsCall()) | |
如果参数是GT_ARGPLACE, 设置参数的VN | |
如果call是helper call, 调用fgValueNumberHelperCall(call) | |
设置 call->gtVNPair = vnStore->VNPWithExc(vnpNorm, vnpExc) | |
且如果call修改了heap, 则调用fgMutateHeap更新fgCurHeapVN | |
否则如果call是void | |
设置 VN 等于 VNForVoid, 调用fgMutateHeap更新fgCurHeapVN | |
否则 | |
分配一个新的VN, 调用fgMutateHeap更新fgCurHeapVN | |
如果tree是GT_ARR_BOUNDS_CHECK或GT_SIMD_CHK | |
设置 VN 等于 VNF_IndexOutOfRangeExc + gtArrLen VN + gtIndex VN | |
其他情况 | |
调用 VNForExpr 分配一个新的VN | |
如果blk->bbHeapSsaNumOut != blk->bbHeapSsaNumIn | |
设置 GetHeapPerSsaData(blk->bbHeapSsaNumOut)->m_vnPair.SetLiberal(fgCurHeapVN) | |
fgCurHeapVN在heap改变时也会跟着改变, 这里设置的是处理完以后最终的heap变量[SSA]的VN | |
PHASE_HOIST_LOOP_CODE | |
optHoistLoopCode | |
创建一个 LoopHoistContext | |
枚举 optLoopTable, 如果循环是最外层的循环, 调用 optHoistLoopNest(lnum, &hoistCtxt) | |
调用 optHoistThisLoop(lnum, hoistCtxt) | |
调用 hoistCtxt->m_curLoopVnInvariantCache.RemoveAll | |
清空 optVNIsLoopInvariant 使用的缓存 | |
统计循环输入输出和使用的变量 | |
更新 pLoopDsc->lpLoopVarCount (pLoopDsc->lpVarInOut和pLoopDsc->lpVarUseDef交集的数量) | |
更新 pLoopDsc->lpVarInOutCount (pLoopDsc->lpVarInOut的数量 | |
查找一定会执行的 BasicBlock | |
如果循环只有一个exit, 则枚举exit的IDom到entry(entry到exit间必经的block) | |
如果循环有多个exit, 则只能保证entry一定会被执行 | |
枚举一定会执行的 BasicBlock, 顺序是执行顺序(dominator到dominatee) | |
调用 optHoistLoopExprsForBlock(blk, lnum, hoistCtxt) | |
枚举 block 中的 stmt | |
调用 optHoistLoopExprsForTree 判断是否 hoistable | |
递归判断 tree 的子节点, 如果全部通过则treeIsInvariant = true | |
设置 treeIsHoistable = treeIsInvariant (假定可以hoist) | |
额外判断 | |
如果tree不是CSE候选, 设置treeIsHoistable = false | |
如果tree是GT_CALL且不是pure的helper call, 设置treeIsHoistable = false | |
如果tree在第一个全局副作用后可能抛出例外, 设置treeIsHoistable = false | |
如果tree访问了heap中的变量(GT_CLS_VAR), 设置treeIsHoistable = false | |
调用 optVNIsLoopInvariant, 失败时设置treeIsHoistable = false | |
如果VN是VNF_PhiDef, 且来源在loop中, 返回失败 | |
如果VN是VNF_PhiHeapDef, 且来源在loop中, 返回失败 | |
如果VN是其他类型的VNF, 则调用 optVNIsLoopInvariant 判断所有参数, 任意参数失败时返回失败 | |
如果VN是新的VN, 且VN不在loop中, 返回失败 | |
返回值会缓存在 m_curLoopVnInvariantCache 中 | |
如果*pFirstBlockAndBeforeSideEffect为true, 则判断当前tree是否有全局副作用 | |
有时设置*pFirstBlockAndBeforeSideEffect = false | |
这个变量会在递归中传递, 用于检测当前tree是否在第一个副作用之前 | |
如果 treeIsHoistable 不成立 | |
枚举tree的子节点, 如果该子节点可以hoist, 则调用optHoistCandidate(child, lnum, hoistCtxt) | |
如果tree不在循环中, 跳过 | |
如果tree所在的loop未标记为LPFLG_HOISTABLE, 跳过 | |
调用 optTreeIsValidAtLoopHead 判断tree是否可以在循环开头, 失败时跳过 | |
递归判断该tree中的所有本地变量[SSA]是否都在循环外定义 | |
调用 optIsProfitableToHoistableTree 判断是否值得, 失败时跳过 | |
如果 loopVarCount 大于等于 availRegCount | |
假定循环时所有寄存器都会用于保存循环中的变量, 如果tree的gtCostEx不足则判断为不值得 | |
如果 varInOutCount 大于 availRegCount | |
假定进入循环时无空余的寄存器, 如果tree的gtCostEx不足则判断为不值得 | |
如果上层循环已经hoist过当前tree的VN, 则跳过 | |
如果当前循环已经hoist过当前tree的VN, 则跳过 | |
调用 optPerformHoistExpr(tree, lnum) | |
创建一个新的 BasicBlock 作为新的 pLoopDsc->lpHead | |
添加 tree 到新的 BasicBlock | |
注意在这个阶段: | |
添加的 tree 不会分配到临时变量, 会等到后面的 optOptimizeCSEs 分配 | |
原来的 tree 不会被删除, 会等到后面的 optOptimizeCSEs 整合和删除 | |
更新 lpHoistedExprCount 或者 lpHoistedFPExprCount | |
调用 hoistCtxt->GetHoistedInCurLoop(this)->Set(tree->gtVNPair.GetLiberal(), true) | |
记录这个tree的VN已经在当前循环hoist过 | |
返回 treeIsHoistable | |
如果 hoistable, 调用 optHoistCandidate(stmtTree, lnum, hoistCtxt) | |
流程同上 | |
设置 hoistedInCurLoop = hoistCtxt->ExtractHoistedInCurLoop() | |
获取在当前循环中hoist出来的tree | |
如果循环里面有嵌套的子循环 | |
如果 hoistedInCurLoop != nullptr | |
枚举 hoistedInCurLoop, 调用 hoistCtxt->m_hoistedInParentLoops.Set | |
设置这些tree到 m_hoistedInParentLoops | |
枚举子循环, 递归调用 optHoistLoopNest(child, hoistCtxt) | |
如果 hoistedInCurLoop != nullptr | |
枚举 hoistedInCurLoop, 调用 hoistCtxt->m_hoistedInParentLoops.Remove | |
把这些tree从 m_hoistedInParentLoops 删除 | |
PHASE_VN_COPY_PROP | |
optVnCopyProp | |
调用 SsaBuilder::ComputeDominators 重新计算dom树 | |
清空 compCurLife 和 optCopyPropKillSet | |
初始化本地变量 curSsaName, 用于保存lclNum到最近的定义(SSA) | |
初始化本地变量 worklist, 用于保存需要处理的 BasicBlock | |
入栈 fgFirstBB + 标记未未处理 | |
处理 worklist 中的 block | |
出栈 block, 如果标记为已处理则调用 optBlockCopyPropPopStacks(block, &curSsaName) | |
枚举 block 中的 stmt | |
按执行顺序枚举stmt中的tree | |
如果tree是本地变量的定义(赋值) | |
出栈 curSsaName 中本地变量对应的元素 | |
如果未处理则 | |
入栈 block + 标记为已处理 (一个copy) | |
调用 optBlockCopyProp(block, &curSsaName) | |
设置 compCurLife = block->bbLiveIn | |
枚举 block 中的 stmt | |
清空 optCopyPropKillSet | |
按执行顺序枚举stmt中的tree | |
调用 optCopyProp(block, stmt, tree, curSsaName) | |
如果block是finally或者fault, 跳过 | |
如果tree不是本地变量, 跳过 | |
如果tree是phi, 或者是读取成员(lclfld), 跳过 | |
如果tree是DEF或者USE+DEF, 跳过 | |
如果tree无SSA版本, 跳过 | |
到这里tree就是有SSA版本的, 只有使用(USE)的本地变量 | |
枚举 LclNumToGenTreePtrStack 中的所有本地变量 | |
newLclNum = 枚举到的本地变量 | |
op = newLclNum对应的最新的SSA对应的genTree | |
如果 lclNum == newLclNum, 跳过 | |
如果 newLclNum 在 optCopyPropKillSet 中, 跳过 | |
如果 op 带了GTF_VAR_CAST, 跳过 | |
如果 newLclNum 是 lclNum 的 shadowCopy, 跳过 | |
获取 op 对应的VN, 无VN时跳过 | |
如果 op 的类型不等于 tree 的类型, 跳过 | |
如果 op 对应的VN不等于 tree 的Conservative VN, 跳过 | |
这里需要分析 newLclNum 是否存活在这个 block | |
例如 if (...) { a = 0 } else { b = 1 } print (c) | |
c 跟 b 共享同一个值, 这里就不能替换 c 到 b | |
如果 newLclNum 不是this | |
如果 newLclNum 的地址expose了, 跳过 | |
如果 newLclNum 未被跟踪, 跳过 | |
如果 newLclNum 不在 compCurLife 中, 跳过 | |
获取 op 的SSA版本, 如果无SSA版本, 跳过 | |
替换 tree 中的 lclNum 和 ssaNum 到 newLclNum 和新的SSA版本 | |
如果tree是本地变量且有SSA版本号, 并且是对本地变量的定义(赋值) | |
添加本地变量的序号到 optCopyPropKillSet | |
考虑到comma | |
前面一个tree定义的SSA会在下面的循环中推入 curSsaName | |
所以comma后面使用的tree会是stmt之前的tree, 这样就不准确了 | |
这里需要临时记录修改过的本地变量到一个集合中 | |
按执行顺序枚举stmt中的tree | |
如果tree不是本地变量且有SSA版本号, 则跳过处理 | |
如果本地变量是定义(赋值) | |
把SSA版本号推入 curSsaName 中本地变量对应的栈 | |
对于第一次使用的函数的参数和this | |
因为他们的赋值会在外部, 第一次使用时就有已定义的SSA版本号 | |
把SSA版本号推入 curSsaName 中本地变量对应的栈 | |
把 block 的dom树中的子 block 入栈 + 标记为未处理 | |
PHASE_OPTIMIZE_VALNUM_CSES | |
optOptimizeCSEs | |
调用 optOptimizeValnumCSEs | |
调用 optValnumCSE_Init | |
初始化 optCSEhash | |
设置 optCSECandidateCount = 0 | |
设置 optDoCSE = false // 找到候选才设为true | |
调用 optValnumCSE_Locate, 如果返回0则不继续处理 | |
枚举 BasicBlock | |
枚举 block 中的 stmt, 跳过phi节点 | |
按执行顺序枚举stmt中的tree | |
调用 optIsCSEcandidate(tree), 如果不是CSE候选则跳过 | |
如果tree有GTF_ASG或者GTF_DONT_CSE标记则跳过(包含副作用) | |
如果tree的类型是struct或者void则跳过 | |
如果tree的类型是浮点数则跳过 | |
判断tree的成本 | |
优化小代码时判断gtCostSz否则判断gtCostEx是否小于MIN_CSE_COST | |
MIN_CSE_COST目前是2 | |
小于时跳过 | |
如果tree是常量则跳过 (CSE_CONSTS未启用时) | |
判断oper类型 | |
GT_CALL: 有副作用则返回false | |
GT_IND: 如果GT_IND(GT_ARR_ELEM)则返回false | |
GT_CNS_*: 返回true (CSE_CONSTS启用时) | |
GT_ARR_ELEM, GT_ARR_LENGTH, GT_CLS_VAR, GT_LCL_FLD: 返回true | |
GT_LCL_VAR: 返回false (volatile) | |
GT_NEG, GT_NOT, GT_CAST: 返回true | |
GT_SUB, GT_DIV, ...: 返回true | |
GT_ADD, GT_MUL, GT_LSH: 有GTF_ADDRMODE_NO_CSE标记时返回false | |
GT_EQ, GT_NE, GT_LT, ...: 返回true | |
GT_INTRINSIC: 返回true | |
GT_COMMA: 返回true | |
GT_COLON, GT_QMARK, GT_NOP, GT_RETURN: 返回false | |
其他: 返回false | |
判断tree的VN是否预留的VN, 如果是跳过 | |
判断tree的VN是否常量, 如果是跳过 | |
调用 optValnumCSE_Index(tree, stmt) | |
在 optCSEhash 中查找和tree的VN一致的元素 | |
如果找到则 | |
把tree添加到该元素下的链表中 | |
设置 optDoCSE = true | |
如果 hashDsc->csdIndex 已经分配过 (有两个或以上tree的VN相同) | |
设置 tree->gtCSEnum = hashDsc->csdIndex | |
返回 hashDsc->csdIndex | |
否则 | |
设置 newCSE = true | |
如果 newCSE == true | |
判断 optCSECandidateCount 是否等于 MAX_CSE_CNT(32或64) | |
等于时返回0 | |
设置 hashDsc->csdIndex = ++optCSECandidateCount | |
设置 tree->gtCSEnum = hashDsc->csdIndex | |
否则 | |
添加tree到optCSEhash中, hashDsc->csdIndex 设置0 | |
当第二个有相同VN的tree被发现时才会更新 csdIndex | |
如果 optDoCSE == true, 则调用 optCSEstop | |
根据 optCSEhash 构建 optCSEtab | |
optCSEtab 是一个 optCSECandidateCount 大小的数组, 储存了到 hashDsc 的索引 | |
统计CSE候选数量 | |
optCSECandidateTotal += optCSECandidateCount | |
调用 optValnumCSE_InitDataFlow | |
枚举 BasicBlock | |
设置 block->bbCseIn, fgFirstBB等于0, 否则等于EXPSET_ALL | |
设置 block->bbCseOut, 等于EXPSET_ALL | |
设置 block->bbCseGen, 等于0 | |
枚举 optCSECandidateCount | |
枚举 dsc->csdTreeList 里面的tree | |
设置 tree 所在的 block->bbCseGen |= genCSEnum2bit(CSEindex) | |
调用 optValnumCSE_DataFlow | |
生成 CSE_DataFlow cse(this) | |
生成 DataFlow cse_flow(this) | |
调用 cse_flow.ForwardAnalysis(cse) | |
准备一个worklist, 默认只有 fgFirstBB | |
循环直到worklist为空 | |
从worklist的开头取出block | |
调用 cse.StartMerge(block) | |
设置 m_preMergeOut = block->bbCseOut | |
枚举 block 的preds(如果block是ex hnd则同时枚举try中的block) | |
调用 cse.Merge(block, pred->flBlock, preds) | |
设置 block->bbCseIn &= predBlock->bbCseOut | |
调用 cse.EndMerge(block) | |
设置 block->bbCseOut = block->bbCseOut & (block->bbCseIn | block->bbCseGen) | |
返回 block->bbCseOut 是否不等于 m_preMergeOut | |
(如果无输出的CSE或者重复处理则不处理succs) | |
如果 cse.EndMerge 返回true, 则把block的succs添加到worklist | |
调用 optValnumCSE_Availablity | |
枚举 BasicBlock | |
设置 compCurBB = block | |
设置 available_cses = block->bbCseIn | |
枚举 block 中的 stmt, 跳过phi节点 | |
按执行顺序枚举stmt中的tree | |
如果 tree->gtCSEnum 等于 0 跳过 | |
设置 mask = genCSEnum2bit(tree->gtCSEnum) | |
如果 available_cses & mask, 表示这是一个CSE USE | |
设置 desc->csdUseCount += 1 | |
设置 desc->csdUseWtCnt += stmw | |
否则 | |
如果 tree 是colon(QMARK), 重设 tree->gtCSEnum 并跳过 | |
设置 desc->csdDefCount += 1 | |
设置 desc->csdDefWtCnt += stmw | |
标记 tree->gtCSEnum = TO_CSE_DEF(tree->gtCSEnum) // 负数 | |
设置 available_cses |= mask | |
调用 optValnumCSE_Heuristic | |
创建 CSE_Heuristic cse_heuristic(this) | |
调用 cse_heuristic.Initialize | |
枚举本地变量估算stack frame的大小, 超过0x400的话设置largeFrame, 超过0x10000的话设置hugeFrame | |
估算会进入寄存器的本地变量大小 enregCount | |
如果 aggressiveRefCnt 未设置且 enregCount > (CNT_CALLEE_ENREG * 3 / 2) | |
如果 optKind 是 SMALL_CODE | |
aggressiveRefCnt = varDsc->lvRefCnt + BB_UNITY_WEIGHT | |
否则 | |
aggressiveRefCnt = varDsc->lvRefCntWtd + BB_UNITY_WEIGHT | |
如果 moderateRefCnt 未设置且 enregCount > ((CNT_CALLEE_ENREG * 3) + (CNT_CALLEE_TRASH * 2)) | |
如果 optKind 是 SMALL_CODE | |
moderateRefCnt = varDsc->lvRefCnt | |
否则 | |
moderateRefCnt = varDsc->lvRefCntWtd | |
设置 mult = enregCount大于4时等于3, 大于2时等于2, 小于等于2时等于1 | |
设置 aggressiveRefCnt = max(BB_UNITY_WEIGHT * mult, aggressiveRefCnt) | |
设置 moderateRefCnt = max((BB_UNITY_WEIGHT * mult) / 2, moderateRefCnt) | |
调用 cse_heuristic.SortCandidates | |
生成一个 sortTab, 包含按cost倒叙排列后的 optCSEtab | |
如果 optKind 是 SMALL_CODE, 排序按 gtCostSz => csdUseCount => csdDefCount => csdIndex | |
否则排序按 gtCostEx => csdUseWtCnt => csdDefWtCnt => csdIndex | |
调用 cse_heuristic.ConsiderCandidates | |
枚举 sortTab | |
设置 Compiler::CSEdsc* dsc = *ptr | |
创建 CSE_Candidate candidate(this, dsc) | |
调用 candidate.InitializeCounts | |
根据传入的tree初始化 m_Cost, m_Size, m_defCount, m_useCount | |
如果 candidate.UseCount() == 0, 跳过 | |
如果 dsc->csdDefCount <= 0 || dsc->csdUseCount == 0, 跳过 | |
设置 doCSE = PromotionCheck(&candidate) | |
判断执行CSE的成本是否小于或等于不执行CSE | |
不执行CSE的成本 (def + use) * cost | |
执行CSE的成本 (def * (cost + cse-def-cost)) + (use * cse-use-cost) | |
如果CSE创建的临时变量可以放在寄存器则cse-def-cost和cse-use-cost可以很小(0 or 1, 1) | |
如果不能放在寄存器则cse-def-cost和cse-use-cost是IND_COST或以上(判断是否large frame和huge frame) | |
如果 doCSE, 调用 PerformCSE(&candidate) | |
创建cse用的临时变量 cseLclVarNum = m_pCompiler->lvaGrabTemp(...) | |
枚举 cse descriptor 中的 tree stmt list | |
如果tree不再被mark为cse候选(例如它的上级tree已执行过CSE), 跳过 | |
如果tree是cse use | |
替换 tree 到 lclVar cseLclVarNum | |
如果有副作用则使用COMMA | |
否则 | |
替换 tree 到 ASG cseLclVarNum <= old tree | |
调用 cse_heuristic.Cleanup | |
如果因为执行CSE导致创建了新的本地变量, 这个时候需要重新创建排序好的本地变量表 | |
如果 m_addCSEcount > 0 则 m_pCompiler->lvaSortAgain = true | |
PHASE_ASSERTION_PROP_MAIN | |
optAssertionPropMain | |
调用 optAssertionInit(false) | |
枚举 BasicBlock | |
设置 fgRemoveRestOfBlock = false | |
枚举 block 中的 stmt | |
如果 fgRemoveRestOfBlock == false, 删除stmt并继续 | |
设置 nextStmt = optVNAssertionPropCurStmt(block, stmt) | |
记录 prev = (stmt == block->firstStmt()) ? nullptr : stmt->gtPrev | |
设置 optAssertionPropagatedCurrentStmt = false | |
按 pre order 遍历 stmtTree, 调用 optVNAssertionPropCurStmtVisitor | |
调用 optVnNonNullPropCurStmt(pData->block, pData->stmt, *ppTree) | |
如果 tree 是 GT_CALL, 设置 newTree = optNonNullAssertionProp_Call(empty, tree, stmt) | |
检查this是否本地变量, 且根据this的VN检查是否确定不为null | |
如果确定不为null则 | |
gtFlags &= ~GTF_CALL_NULLCHECK | |
gtFlags &= ~GTF_EXCEPT | |
如果 tree 是 indir, 设置 newTree = optAssertionProp_Ind(empty, tree, stmt) | |
检查deref的对象是否本地变量, 且根据该tree的VN检查是否确定不为null | |
如果确定不为null则 | |
gtFlags &= ~GTF_EXCEPT | |
gtFlags |= GTF_ORDER_SIDEEFF // 防止reordering | |
如果有返回 newTree | |
确认 newTree == tree | |
调用 optAssertionProp_Update(newTree, tree, stmt) | |
optAssertionPropagated = true | |
optAssertionPropagatedCurrentStmt = true | |
调用 optVNConstantPropCurStmt(pData->block, pData->stmt, *ppTree) | |
确认tree是rvalue (检查oper是否GT_ADD, GT_SUB, GT_JTRUE等等), 不是时跳过(WALK_CONTINUE) | |
设置 newTree = optVNConstantPropOnTree(block, stmt, tree) | |
如果 oper 是 GT_JTRUE, 调用 optVNConstantPropOnJTrue | |
因为JTRUE节点的副作用不能转成comma, 要另建一个stmt | |
检查tree的conservative vn是否constant, 不是时跳过 | |
获取vn对应的const 值, 替换到tree中 | |
如果 newTree == null, 跳过(WALK_CONTINUE) | |
调用 optAssertionProp_Update(newTree, tree, stmt) | |
optAssertionPropagated = true | |
optAssertionPropagatedCurrentStmt = true | |
返回 WALK_SKIP_SUBTREES | |
如果 optAssertionPropagatedCurrentStmt | |
调用 fgMorphBlockStmt(block, stmt), 重新morph block | |
调用 gtSetStmtInfo(stmt), 重新计算成本等等 | |
调用 fgSetStmtSeq(stmt), 重新计算和设置评价顺序 | |
返回 (prev == nullptr) ? block->firstStmt() : prev->gtNext, 如果优化了gtNext就会改变 | |
如果 fgRemoveRestOfBlock == false, 删除stmt并继续 | |
如果 stmt != nextStmt, 设置 stmt = nextStmt 并继续 | |
按执行顺序枚举stmt中的tree | |
调用 optAssertionGen(tree) | |
根据 tree 生成 assertion, 生成 OAK_EQUAL 或 OAK_NOT_EQUAL 等 | |
添加 assertion 会使用 optAddAssertion | |
如果 tree 有VN, 还会设置到 optValueNumToAsserts | |
设置 tree->gtAssertionNum | |
设置 bbJtrueAssertionOut = optInitAssertionDataflowFlags() | |
apValidFull = optAssertionCount的bitset集合 | |
枚举 BasicBlock | |
设置 block->bbAssertionIn = bbIsHandlerBeg(block) ? empty : apValidFull | |
设置 block->bbAssertionGen = empty | |
设置 block->bbAssertionOut = apValidFull | |
设置 jumpDestOut[block->bbNum] = apValidFull | |
设置 fgFirstBB->bbAssertionIn = empty | |
设置 jumpDestGen = optComputeAssertionGen() | |
枚举 BasicBlock | |
设置 jumpDestGen[block->bbNum] = empty | |
设置 valueGen = empty | |
设置 jumpDestValueGen = empty | |
枚举 block 中的 stmt | |
按执行顺序枚举stmt中的tree | |
如果 tree 是 GT_JTRUE, 设置 jumpDestValueGen = valueGen | |
表示之前的assertion是共通的, 最后一个不一样 | |
设置 assertionIndex[2] 等于 | |
[0] => tree->GetAssertion() | |
[1] => GT_JTRUE ? optFindComplementary(tree->GetAssertion()) : 0 | |
optFindComplementary 用于查找相反的assertion | |
例如传入 a == b, 会查找是否有 a != b, 如果有则返回 | |
枚举 assertionIndex | |
如果 index == 0 且是 GT_JTRUE, 添加assertion到 jumpDestValueGen | |
否则添加assertion到 valueGen | |
简单的总结: | |
if GT_JTRUE: | |
jumpDestValueGen |= assertion | |
valueGen |= complementary(assertion) | |
else: | |
valueGen |= assertion | |
设置 block->bbAssertionGen = valueGen | |
设置 jumpDestGen[block->bbNum] = jumpDestValueGen // 表示block的JTRUE成立时生成的assertion | |
创建 DataFlow flow(this) | |
创建 AssertionPropFlowCallback ap(this, bbJtrueAssertionOut, jumpDestGen) | |
调用 flow.ForwardAnalysis(ap) | |
准备一个worklist, 默认只有 fgFirstBB | |
循环直到worklist为空 | |
从worklist的开头取出block | |
调用 ap.StartMerge(block) | |
设置 preMergeOut = block->bbAssertionOut | |
设置 preMergeJumpDestOut = mJumpDestOut[block->bbNum] | |
枚举 block 的preds(如果block是ex hnd则同时枚举try中的block) | |
调用 ap.Merge(block, pred->flBlock, preds) | |
如果 predBlock->bbJumpKind == BBJ_COND 且 predBlock->bbJumpDest == block | |
block->bbAssertionIn &= mJumpDestOut[predBlock->bbNum] | |
否则 | |
block->bbAssertionIn &= predBlock->bbAssertionOut | |
调用 ap.EndMerge(block) | |
block->bbAssertionOut &= block->bbAssertionIn | block->bbAssertionGen | |
mJumpDestOut[block->bbNum] &= block->bbAssertionIn | mJumpDestGen[block->bbNum] | |
如果 block->bbAssertionOut != preMergeOut 或 mJumpDestOut[block->bbNum] != preMergeJumpDestOut | |
返回 true (changed) | |
如果 ap.EndMerge 返回true, 则把block的succs添加到worklist | |
枚举 BasicBlock | |
调用 optImpliedByTypeOfAssertions(block->bbAssertionIn) | |
如果集合中包含了 OAK_EQUAL (O1K_SUBTYPE or O1K_EXACT_TYPE) | |
则同时向集合添加 not null assertion (查找和使用现有的assertion) | |
枚举 BasicBlock | |
设置 assertions = BitVecOps::MakeCopy(apTraits, block->bbAssertionIn) | |
设置 compCurBB = block | |
设置 fgRemoveRestOfBlock = false | |
枚举 block 中的 stmt, 跳过phi节点 | |
如果 fgRemoveRestOfBlock == false, 删除stmt并继续 | |
设置 optAssertionPropagatedCurrentStmt = false | |
按执行顺序枚举stmt中的tree | |
设置 newTree = optAssertionProp(assertions, tree, stmt) | |
根据 assertion 优化树 | |
上面 fgMorphTree 中有对 optAssertionProp 的分析, 请参考上面 | |
如果 newTree != nullptr | |
tree = newTree | |
如果 tree->HasAssertion() | |
设置 assertions |= tree->GetAssertion() - 1 | |
调用 optImpliedAssertions((AssertionIndex)tree->GetAssertion(), assertions) | |
参数 activeAssertions <= assertions | |
设置 chkAssertion = copy(tree 的VN对应的assertion) | |
如果tree的assertion是O2K_LCLVAR_COPY | |
chkAssertion |= op2 的VN对应的assertion | |
chkAssertion &= assertions | |
枚举 chkAssertion | |
如果 chkIndex == assertionIndex, 跳过 | |
如果 tree的assertion 是 copy assertion (OAK_EQUAL, O1K_LCLVAR, O2K_LCLVAR_COPY) | |
表示 curAssertion 基于 iterAssertion 成立 | |
调用 optImpliedByCopyAssertion(curAssertion, iterAssertion, activeAssertions) | |
枚举 impAssertion in optAssertionCount | |
如果 impAssertion == curAssertion || impAssertion == iterAssertion, 跳过 | |
设置 op1MatchesCopy = op1的lclNum和ssaNum是否匹配 | |
判断 op2 是否可以可以根据 depAssertion 推导出来 | |
如果 op1 和 op2 都符合条件 | |
result |= impIndex - 1 // 添加到assertions中 | |
否则如果 chkIndex的assertion 是 copy assertion | |
表示 iterAssertion 基于 curAssertion 成立 | |
调用 optImpliedByCopyAssertion(iterAssertion, curAssertion, activeAssertions) | |
同上 | |
如果 tree的assertion 是 GT_LVL_VAR X == GT_CNS_INT | |
调用 optImpliedByConstAssertion(curAssertion, activeAssertions) | |
设置 chkAssertion = op1 的VN对应的assertion | |
枚举 impAssertion in chkAssertions | |
如果 impAssertion == constAssertion, 跳过 | |
如果 impAssertion->op1.vn != constAssertion->op1.vn, 跳过 | |
检查 impAssertion 是否可以根据 constAssertion 推导 | |
如果可以则 | |
activeAssertions |= impAssertion | |
如果 optAssertionPropagatedCurrentStmt | |
调用 fgMorphBlockStmt(block, stmt), 重新morph block | |
调用 gtSetStmtInfo(stmt), 重新计算成本等等 | |
调用 fgSetStmtSeq(stmt), 重新计算和设置评价顺序 | |
设置 optAssertionPropagatedCurrentStmt = false | |
如果 optAssertionPropagated, 则设置 lvaSortAgain = true | |
PHASE_OPTIMIZE_INDEX_CHECKS | |
RangeCheck::OptimizeRangeChecks | |
枚举 BasicBlock | |
枚举block中的stmt | |
按执行顺序枚举stmt中的tree | |
如果调用 IsOverBudget 返回true, 则跳过 | |
调用 OptimizeRangeCheck(block, stmt, tree) | |
如果 oper 不是 GT_COMMA, 跳过 | |
如果 op1 不是 GT_ARR_BOUNDS_CHECK, 跳过 | |
获取 index 的VN (idxVN) | |
获取 数组长度的VN (arrLenVN) | |
根据 arrLenVN 获取 arrSize, 非常量时 arrSize = 0 | |
如果 idxVN 是常量且值已知且 arrSize > 0 | |
调用 optRemoveRangeCheck 删除边界检查 | |
把 COMMA 的左边换成sideeffect或者nop | |
否则判断 index 可取的范围值是否在 arrLen 可取的范围值内, 如果可以 | |
调用 optRemoveRangeCheck 删除边界检查, 同上 | |
PHASE_UPDATE_FLOW_GRAPH | |
fgUpdateFlowGraph | |
删除空 BasicBlock, 无法到达的 BasicBlock, 和减少多余的jumps | |
详细说明在上面 | |
PHASE_COMPUTE_EDGE_WEIGHTS2 | |
fgComputeEdgeWeights | |
重新计算 bbWeight | |
详细说明在上面 | |
PHASE_DETERMINE_FIRST_COLD_BLOCK | |
fgDetermineFirstColdBlock | |
查找第一个 cold block, 并标记这个block之后的block为 bbFlags |= BBF_COLD | |
枚举 BasicBlock | |
如果block是handler的开始, 且启用了 HANDLER_ENTRY_MUST_BE_IN_HOT_SECTION, 且block不是 isRunRarely | |
则设置已经决定的 firstColdBlock = nullptr | |
否则如果block是 isRunRarely | |
则设置 firstColdBlock = block | |
如果 firstColdBlock 是 fgFirstBB, 则设置 firstColdBlock = nullptr | |
如果 firstColdBlock->bbNext == nullptr 且block大小小于8则设置 firstColdBlock = nullptr | |
如果 prevToFirstColdBlock 是 bbFallsThrough | |
BBJ_CALLFINALLY: 设置 firstColdBlock = firstColdBlock->bbNext | |
BBJ_COND: 判断下一个block是否BBJ_ALWAYS, 如果不是则创建一个新的block | |
BBJ_NONE: 设置 bbJumpKind = BBJ_ALWAYS | |
这里的目的是为了后面可以生成jmp语句 | |
设置 firstColdBlock->bbFlags |= BBF_JMP_TARGET | |
设置 firstColdBlock 之后的block bbFlags |= BBF_COLD | |
设置 fgFirstColdBlock = firstColdBlock | |
PHASE_RATIONALIZE | |
Rationalizer::Run (Rationalizer::DoPhase) | |
Rationalization的目的 | |
对 GenTree 进行整形, 把 FGOrderTree 变为 FGOrderLinear, 并且把所有HIR的block变为LIR | |
整形后GenTree会按实际的执行顺序排列好, 可以更接近最终生成的机器代码的布局 | |
GT_ASG: 因为赋值时需要先评价右边再评价左边, 且lclvar要根据上下文判断含义, 这里需要把它转换为store | |
GT_ADDR: 同上, 子节点要根据上下文判断含义 | |
GT_COMMA: 分割到tree中, 无法分割时创建 embedded 式 | |
GT_QMARK: 在morphing阶段末尾已经处理过这种类型的节点 | |
最后让block中的代码的运行顺序完全由 GenTree 的 gtNext 和 gtPrev 决定 (不需要再枚举stmt) | |
LIR中的 t9 = ... 标记中t后面的数字是 GenTree::gtTreeID | |
枚举 BasicBlock | |
设置 firstStatement = block->firstStmt(), 不存在是 MakeLIR(nullptr, nullptr) 并跳过 | |
设置 lastStatement = block->lastStmt() | |
枚举 firstStatement 到 lastStatement | |
生成 SplitData splitData | |
使用 comp->fgWalkTreePost 枚举 statement->gtStmtExpr | |
如果 tree 是 GT_INTRINSIC 且 IsIntrinsicImplementedByUserCall 成立 | |
调用 RewriteIntrinsicAsUserCall(use, walkData) 重写目标平台不支持的语句到 call | |
调用 RewriteNodeAsCall(use, data, intrinsic->gtMethodHandle, ...) | |
替换 tree 到 GT_CALL | |
如果 tree 是 OperIsLocal | |
设置 gtFlags &= ~GTF_VAR_USEDEF | |
跨stmt连接genTree | |
lastNodeInPreviousStatement->gtNext = firstNodeInStatement | |
firstNodeInStatement->gtPrev = lastNodeInPreviousStatement | |
调用 block->MakeLIR(firstStatement->gtStmtList, lastStatement->gtStmtExpr) | |
设置 block->m_firstNode | |
设置 block->m_lastNode | |
设置 block->bbFlags |= BBF_IS_LIR | |
枚举 firstStatement 到 lastStatement | |
如果 statement 有 gtStmtILoffsx | |
把 statement node 转换为 GT_IL_OFFSET, 插入到statement下的第一个节点前面 | |
使用 comp->fgWalkTreePost 枚举 statement->gtStmtExpr | |
调用 Rationalizer::RewriteNode | |
如果 node 是 GT_LIST (lisp式的参数列表 (car cdr...)), 且node不是GTF_LIST_AGGREGATE | |
则删除该node | |
构建 LIR::Use use | |
Use有3个成员, 分别是 | |
m_range: block的genTree列表 | |
m_edge: 当前节点 (use-def的边界) | |
m_user: 使用当前节点的节点 | |
构建 LIR::USE 的时候首先会判断 parentStack.Height() < 2, 如果成立表示无上级节点 | |
use = LIR::Use::GetDummyUse(BlockRange(), *useEdge) // edge和user相同 | |
否则 | |
use = LIR::Use(BlockRange(), useEdge, parentStack.Index(1)) // edge是当前节点, user是上级节点 | |
判断oper类型 | |
GT_ASG 则调用 RewriteAssignment(use) | |
assignment = 当前节点 | |
location = 当前节点的op1 | |
value = 当前节点的op2 | |
判断 location 的 oper | |
GT_LCL_VAR, GT_LCL_FLD, GT_REG_VAR, GT_PHI_ARG | |
调用 RewriteAssignmentIntoStoreLclCore(assignment, location, value, locationOp) | |
把 assignment 的 oper 改为 GT_STORE_LCL_VAR 或者 GT_STORE_LCL_FLD | |
然后设置 assignment 的 lclNum 和 ssaNum 等于 location 中的值 | |
删除 location 节点 | |
原来 use 的 edge 指向 assignment, 所以不需要替换 | |
修改前 prev => value => location => assignments => succs | |
修改后 prev => value => store => succs | |
GT_IND | |
生成 GenTreeStoreInd 节点 | |
删除 location 节点 | |
插入 store 到 assignment 前 | |
替换 use 的 edge 到 store | |
删除 assignment 节点 | |
修改前 prev => value => location => assignments => succs | |
修改后 prev => value => store => succs | |
GT_CLS_VAR | |
修改 location 的 oper 到 GT_CLS_VAR_ADDR | |
修改 assignment 的 oper 到 GT_STOREIND | |
修改后 prev => value => cls var addr => store ind => succs | |
GT_BLK, GT_OBJ, GT_DYN_BLK | |
修改 location 的 oper 到 GT_STORE_BLK 或 GT_STORE_OBJ 或 GT_STORE_DYN_BLK | |
修改 use 的 edge 到 location (新的名字是 storeBlk) | |
删除 assignment | |
修改后 prev => value => store => succs | |
GT_BOX 则调用 use.ReplaceWith(comp, node->gtGetOp1()), 并删除 node | |
到这里针对valuetype的box都会被转换, 所以这个box是多余的, 可以删除 | |
把use里面的edge替换为op1, 如果user是call则替换argEntry | |
替换的时候不仅仅会替换use里面的成员, 还会实际替换树里面的参数节点 | |
GT_ADDR 则调用 RewriteAddress(use) | |
address = 当前节点 | |
location = addr的目标 | |
判断 location 的类型 | |
本地变量 | |
修改 location 的 oper 到 GT_LCL_VAR_ADDR 或者 GT_LCL_FLD_ADDR | |
替换 use 的 edge 到 location | |
删除 address 节点 | |
GT_CLS_VAR | |
修改 location 的 oper 到 GT_CLS_VAR_ADDR | |
替换 use 的 edge 到 location | |
删除 address 节点 | |
OperIsIndir (&*someVar => someVar) | |
替换 use 的 edge 到 location->gtGetOp1() | |
删除 location 节点 | |
删除 address 节点 | |
GT_NOP 则判断op1是否nullptr | |
如果不是则 use.ReplaceWith(comp, node->gtGetOp1()), 并删除 node | |
GT_COMMA | |
判断 op1 是否有 GTF_ALL_EFFECT, 无则删掉 op1 的range | |
如果 !use.IsDummyUse(), 则调用 use.ReplaceWith(comp, node->gtGetOp2()) | |
否则判断 op2 是否有 GTF_ALL_EFFECT, 无则删除 op2 的range (comma式的值未被使用可安全删除) | |
删除掉 comma 节点 | |
GT_ARGPLACE | |
删除掉 argplace node | |
GT_CLS_VAR (要求_TARGET_XARCH_成立) | |
在 node 后面添加 GT_IND, 转换为 GT_CLS_VAR_ADDR | |
GT_INTRINSIC | |
确保 IsTargetIntrinsic 成立 (平台支持这种类型的操作, 如果不支持会在前面替换成call) | |
GT_BLK, GT_OBJ (要求FEATURE_SIMD成立) | |
如果 node 是 GT_ASG 的 op1, 并且 | |
node 是 struct 或 | |
parent是 OperIsInitBlkOp 或 | |
lhs, rhs 两边都不是 isAddrOfSIMDType | |
则设置 keepBlk = true | |
调用 RewriteSIMDOperand(use, keepBlk) | |
如果当前节点是 indir 且目标类型是 SIMD 类型 | |
替换 GT_ADDR(GT_LCL_VAR) 到 GT_LCL_VAR | |
否则如果 !keepBlk | |
设置当前节点的 oper 等于 GT_IND | |
GT_LCL_FLD, GT_STORE_LCL_FLD | |
调用 FixupIfSIMDLocal(node->AsLclVarCommon()) | |
varDsc = 本地变量对应的 LclVarDsc | |
如果 varDsc 不是SIMD类型则跳过 | |
判断 node 的类型 | |
GT_LCL_FLD | |
如果目标 struct 的大小是 pointer size 且 field 的 offset 为0 | |
替换 oper 为 GT_LCL_VAR | |
否则返回 | |
GT_STORE_LCL_FLD | |
替换 oper 为 GT_STORE_LCL_VAR | |
simdSize = roundUp(varDsc->lvExactSize, TARGET_POINTER_SIZE) | |
node->gtType = comp->getSIMDTypeForSize(simdSize) | |
GT_SIMD | |
如果 simdNode->gtSIMD.gtSIMDIntrinsicID == SIMDIntrinsicInitArray | |
插入 address 和 ind 到 simd node 前面 | |
调用 use.ReplaceWith(comp, ind) | |
删除 simd node | |
否则 | |
如果 op1 的gtType == TYP_STRUCT, 设置 op1->gtType = simdType | |
如果 op2 的gtType == TYP_STRUCT, 设置 op2->gtType = simdType | |
如果 use.IsDummyUse() && node->OperIsLocalRead() | |
表示该读取本地变量的 node 并未被使用 | |
删除该 node | |
设置 comp->compRationalIRForm = true | |
PHASE_SIMPLE_LOWERING | |
fgSimpleLowering | |
枚举 BasicBlock | |
按 LIR 顺序枚举 block 中的 genTree | |
如果 tree 是 GT_ARR_LENGTH | |
修改 GT_ARR_LENGTH(arr) 为 GT_IND(arr + ArrLenOffset) | |
(arrLen lclVar => indir (lclVar +(ref) const 8)) | |
如果 tree 是 GT_ARR_BOUNDS_CHECK 或 GT_SIMD_CHK | |
调用 fgSetRngChkTarget(tree, false) | |
bndsChk = tree->AsBoundsChk() | |
kind = tree->gtBoundsChk.gtThrowKind | |
保存 callStkDepth 到 bndsChk->gtStkDepth | |
调用 fgRngChkTarget(compCurBB, stkDepth, kind) 获取或者创建报告错误的 rngErrBlk | |
设置 bndsChk->gtIndRngFailBB = gtNewCodeRef(rngErrBlk) | |
PHASE_LCLVARLIVENESS | |
fgLocalVarLiveness (仅在LEGACY_BACKEND时执行) | |
调用 fgLocalVarLivenessInit | |
清空 lvaVarIntf[0 ~ lclMAX_TRACKED] | |
设置所有本地变量的 lvaTable[lclNum].lvMustInit = false | |
调用 fgInitBlockVarSets | |
枚举 BasicBlock | |
调用 BasicBlock::InitVarSets | |
清空 bbVarUse, bbVarDef, bbVarTmp, bbLiveIn, bbLiveOut, bbScope | |
设置 bbHeapUse, bbHeapDef, bbHeapLiveIn, bbHeapLiveOut = false | |
枚举 compQMarks | |
清空 qmark->gtQmark.gtThenLiveSet | |
清空 qmark->gtQmark.gtElseLiveSet | |
设置 fgBBVarSetsInited = true | |
循环(do while)到 fgStmtRemoved && fgLocalVarLivenessChanged | |
调用 fgPerBlockLocalVarLiveness | |
DEF和USE | |
这个函数的很多代码会先判断是否DEF, 否则再添加到USE, 可以看这个的例子理解 | |
例如 { x = 0; print(x); }, 这个 block 先DEF了再USE之前DEF的值, 所以只需要设置DEF | |
例如 { print(x); x = 0; print(x); } 或者 block 先USE了再DEF再USE, 需要同时设置USE和DEF | |
DEF表示的是block定义了新的值 | |
USE表示的是USE了**block之前**的值 | |
如果不优化 | |
设置所有 block 的 bbVarUse, bbVarDef, bbLiveIn, bbLiveOut 为 liveAll 然后返回 | |
清空 fgCurUseSet | |
清空 fgCurDefSet | |
设置 fgCurHeapUse = false | |
设置 fgCurHeapDef = false | |
设置 fgCurHeapHavoc = false | |
枚举 BasicBlock | |
如果是 HIR 则枚举 stmt 并调用 fgPerStatementLocalVarLiveness(stmt->gtStmt.gtStmtList, asgdLclVar) | |
如果tree是GT_ASG, 则 asgdLclVar = tree->gtOp.gtOp1 | |
如果tree是GT_STORE_LCL_VAR, 则 asgdLclVar = tree | |
asgdLclVar 只在 HIR 的时候需要传 | |
按执行顺序枚举stmt中的tree | |
调用 fgPerNodeLocalVarLiveness(node, asgdLclVar), 接下来和下面一样 | |
如果是 LIR 则枚举 tree 并调用 fgPerNodeLocalVarLiveness(node, nullptr) | |
判断 tree 的oper | |
GT_LCL_VAR, GT_LCL_FLD, GT_LCL_VAR_ADDR, GT_LCL_FLD_ADDR, GT_STORE_LCL_VAR, GT_STORE_LCL_FLD | |
调用 fgMarkUseDef(tree->AsLclVarCommon(), asgdLclVar) | |
lclNum = tree对应的本地变量序号 | |
如果 asgdLclVar != nullptr | |
lhsLclNum = asgdLclVar->gtLclVarCommon.gtLclNum | |
如果发现 lhsLclNum == lclNum 则表示发现了 x = f(x) 式 | |
设置 asgdLclVar->gtFlags |= GTF_VAR_USEDEF | |
设置 rhsUSEDEF = true | |
如果 varDsc->lvTracked | |
如果 tree->gtFlags & GTF_VAR_DEF 且 !& (GTF_VAR_USEASG | GTF_VAR_USEDEF) | |
添加 varDsc->lvVarIndex 到 fgCurDefSet | |
否则 | |
如果 !& (GTF_VAR_USEASG | GTF_VAR_USEDEF)) 且 rhsUSEDEF 且 开启了优化 | |
返回 (不标记右边的节点为USE) | |
如果 varDsc->lvVarIndex 不在 fgCurDefSet 则加到 fgCurUseSet | |
如果 tree->gtFlags & GTF_VAR_DEF 则加到 fgCurDefSet | |
否则如果 varTypeIsStruct(varDsc) | |
如果 lvaGetPromotionType(varDsc) != PROMOTION_TYPE_NONE | |
创建一个bitmap bitsMask | |
添加 promoted 的本地变量字段到 bitsMask 中 | |
如果 tree->gtFlags & GTF_VAR_DEF 且 !& (GTF_VAR_USEASG | GTF_VAR_USEDEF) | |
fgCurDefSet |= bitsMask | |
否则如果 bitMask 不是 fgCurDefSet 的 subset | |
fgCurUseSet |= bitMask | |
GT_CLS_VAR | |
访问类成员 | |
如果 tree->gtFlags & GTF_FLD_VOLATILE成立 | |
设置 fgCurHeapDef = true | |
如果 !fgCurHeapDef && (tree->gtFlags !& GTF_CLS_VAR_ASG_LHS) | |
设置 fgCurHeapUse = true | |
GT_IND | |
如果 tree->gtFlags & GTF_IND_VOLATILE | |
设置 fgCurHeapDef = true | |
如果 tree->gtFlags !& GTF_IND_ASG_LHS | |
如果 ind 的目标不是 lclVar | |
且 !fgCurHeapDef 则标记 fgCurHeapUse = true | |
否则调用 fgMarkUseDef(dummyLclVarTree->AsLclVarCommon(), asgdLclVar) | |
GT_LOCKADD, GT_XADD, GT_XCHG, GT_CMPXCHG | |
如果 !fgCurHeapDef 则设置 fgCurHeapUse = true | |
设置 fgCurHeapDef = true | |
设置 fgCurHeapHavoc = true | |
GT_MEMORYBARRIER | |
设置 fgCurHeapDef = true | |
GT_CALL | |
除非 call 是 helper call 且 MutatesHeap 和 MayRunCctor 不成立 | |
如果 !fgCurHeapDef 则设置 fgCurHeapUse = true | |
设置 fgCurHeapDef = true | |
设置 fgCurHeapHavoc = true | |
如果是 unmanaged call, 且 compLvFrameListRoot 不在 fgCurDefSet 则加到 fgCurUseSet | |
default | |
如果 tree->OperIsAssignment() || tree->OperIsBlkOp() | |
并且 tree 无对应的本地变量 | |
设置 fgCurHeapDef = true | |
如果 block 是 BBJ_RETURN, 且 compCallUnmanaged | |
如果 compLvFrameListRoot 未在 fgCurDefSet 则添加到到 fgCurUseSet 中 | |
设置 block->bbVarUse = fgCurUseSet | |
设置 block->bbVarDef = fgCurDefSet | |
设置 block->bbHeapUse = fgCurHeapUse | |
设置 block->bbHeapDef = fgCurHeapDef | |
设置 block->bbHeapHavoc = fgCurHeapHavoc | |
清空 block->bbLiveIn 并设置 block->bbHeapLiveIn = false | |
设置 fgStmtRemoved = false | |
调用 fgInterBlockLocalVarLiveness | |
设置 fgStmtRemoved = false | |
设置 fgLocalVarLivenessChanged = false | |
调用 fgLiveVarAnalysis (updateInternalOnly: false) | |
这个函数的算法是 | |
block 的 liveOut 等于 | |
所有 succs 的 liveIn 的并集 | |
block 的 liveIn 等于 | |
block 的 liveOut | |
减去 block 自身的 bbVarDef | |
加上 block 自身的 bbVarUse | |
block 的 heapLiveOut 等于 | |
是否有任意一个 succs 的 bbHeapLiveIn | |
block 的 heapLiveIn 等于 | |
如果 bbHeapUse 成立 (DEF之前USE了之前的HEAP) | |
或者 bbHeapDef 不成立, 但 heapLiveOut 成立 (block未修改也未使用HEAP, 但下一个block需要使用HEAP) | |
hasPossibleBackEdge = false | |
循环 (do while) 直到 changed == false | |
changed = false | |
定义 bitset liveIn | |
定义 bitset liveOut | |
设置 heapLiveIn = false | |
设置 heapLiveOut = false | |
枚举 BasicBlock | |
如果 block->bbNext 的 bbNum 小于 block->bbNum (乱序) | |
则设置 hasPossibleBackEdge = true | |
清空 liveOut | |
设置 heapLiveOut = false | |
如果 block->endsWithJmpMethod | |
添加所有传入参数的本地变量到 liveOut | |
枚举 block 的 succs | |
liveOut |= succ->bbLiveIn | |
heapLiveOut = heapLiveOut || (*succs)->bbHeapLiveIn | |
如果 succ->bbNum <= block->bbNum | |
设置 hasPossibleBackEdge = true | |
如果 keepAliveThis (lvaKeepAliveAndReportThis) | |
添加 compThisArg 到 liveOut | |
计算 liveIn | |
设置 liveIn = liveOut | |
设置 liveIn &= ~block->bbVarDef (DEF的变量不属于liveIn) | |
设置 liveIn |= block->bbVarUse (USE的变量属于liveIn) | |
heapLiveIn = (heapLiveOut && !block->bbHeapDef) || block->bbHeapUse | |
如果 block 里面发生的例外可以被其他 block 处理 | |
查找 catch block 里面的 liveVars | |
设置 liveIn |= liveVars | |
设置 liveOut |= liveVars | |
如果 block->bbLiveIn != liveIn || block->bbLiveOut != liveOut | |
设置 block->bbLiveIn = liveIn | |
设置 block->bbLiveOut = liveOut | |
设置 change = true | |
如果 block->bbHeapLiveIn != heapLiveIn || block->bbHeapLiveOut != heapLiveOut | |
设置 bbHeapLiveIn = heapLiveIn | |
设置 bbHeapLiveOut = heapLiveOut | |
设置 change = true | |
如果 !hasPossibleBackEdge, 则跳出循环 (无向前跳的block, 不需要再处理一遍) | |
PHASE_LINEAR_SCAN | |
这个步骤只有非 LEGACY_BACKEND 会使用 (LSRA) | |
Lowering::Run (Lowering::DoPhase) | |
枚举 BasicBlock | |
如果不是64位, 则调用 DecomposeLongs::DecomposeBlock(block) | |
m_blockWeight = block->getBBWeight(m_compiler) | |
m_range = &LIR::AsRange(block) | |
调用 DecomposeRangeHelper | |
枚举 range 中的 node, 跳过 phi node | |
获取 node 对应的 LIR::Use(TryGetUse), 会包含使用了当前 node(edge) 的 node, 如果未发现则使用dummy use | |
调用 node = DecomposeNode(use) | |
如果 tree 是int且是long分割的后半部分, 增加 refCnt 并且返回 tree->gtNext | |
判断 tree 类型是否 TYP_LONG, 不是时返回 tree->gtNext | |
分割 tree 到两个 TYP_INT node, 并且插入 GT_LONG node | |
最终顺序是 loResult => hiResult => long | |
并且替换 use 的 edge 到新创建的 long node | |
调用 LowerBlock(block) | |
枚举 range 中的 node, 包含 phi node | |
node = LowerNode(node) | |
判断 oper 类型 | |
GT_IND | |
调用 TryCreateAddrMode(LIR::Use(BlockRange(), &node->gtOp.gtOp1, node), true) | |
检测 node 的 op1 是否可以替换为 lea node | |
例如 *(((v07 << 2) + v01) + 16) | |
可以替换为 *(lea(v01 + v07*4 + 16)) | |
GT_STOREIND | |
调用 LowerStoreInd(node) | |
调用 TryCreateAddrMode(LIR::Use(BlockRange(), &node->gtOp.gtOp1, node), true), 同上 | |
调用 node->AsStoreInd()->SetRMWStatusDefault() | |
如果 !CPU_LOAD_STORE_ARCH | |
设置 gtRMWStatus = STOREIND_RMW_STATUS_UNKNOWN | |
GT_ADD | |
返回调用 LowerAdd(node) | |
如果以下条件成立则不处理, 返回 node->gtNext | |
目标平台是ARM | |
类型不是int | |
不能获取到 node 对应的 use (包含node和使用node的node), 如果该node未被使用则不成立 | |
如果使用node的node是indir, 应该在上面的indir处理 | |
如果使用node的node是add, 应该在上面的add处理(topmost) | |
调用 TryCreateAddrMode(std::move(use), false) 尝试替换add到lea, 同上 | |
GT_UDIV, GT_UMOD | |
调用 LowerUnsignedDivOrMod(node) | |
如果 op2 是 power of 2 | |
转换udiv到rsz (16/2 => 16>>1) | |
转换umod到and (17/2 => 17&(2-1)) | |
GT_DIV, GT_MOD | |
返回调用 LowerSignedDivOrMod(node) | |
dividend = op1 | |
divisor = op2 | |
如果 divisor 是 int.MinValue 或者 long.MinValue | |
转换div到eq (只有MinValue / MinValue会等于1, 否则等于0) | |
如果 divisor 是 power of 2 | |
转换div到rsh (16/-2 => -(16>>1)) | |
转换mod (31%8 => 31-8*(31/8) => 31-((31>>3)<<3) => 31-(31& ~(8-1))) | |
GT_SWITCH | |
调用 LowerSwitch(node) | |
调用 ReplaceWithLclVar 替换switch下的节点到一个本地变量 | |
switch v01 - 100 => tmp = v01 - 100; switch tmp | |
添加判断并跳到default case的节点 | |
if (tmp > jumpTableLength - 2) { goto jumpTable[jumpTableLength - 1]; } | |
添加这个 jmpTrue 到 switch 后面 | |
创建一个新的 block, 把原来的 BBJ_SWITCH 转移到这个 block | |
原来的 block 会变为 BBJ_COND, 跳转目标是 jumpTab[jumpCnt - 1] // default case | |
修复 default case 的 block 的 preds ( -= afterDefaultCondBlock, += originalSwitchBB) | |
判断 uniqueSucc = 剩余的 jump target 是否都是同一个 block | |
如果 uniqueSucc != nullptr | |
可以省略掉switch, 直接跳过去 | |
如果 uniqueSucc 就是 afterDefaultCondBlock 的下一个 block | |
设置 afterDefaultCondBlock->bbJumpKind = BBJ_NONE | |
否则 | |
设置 afterDefaultCondBlock->bbJumpKind = BBJ_ALWAYS | |
否则如果 jumpCnt < minSwitchTabJumpCnt | |
转换switch到多个jtrue | |
否则 | |
在 afterDefaultCondBlock 插入 (GT_SWITCH_TABLE lclVar jumpTable) | |
删除原来的 switch 语句, 和它的 op1 (lclVar) | |
GT_CALL | |
调用 LowerCall(node) | |
调用 LowerArgsForCall(call) | |
如果 call->gtCallObjp != nullptr (有this), 调用 LowerArg(call, &call->gtCallObjp) | |
如果参数是 OperIsStore, IsArgPlaceHolderNode, IsNothingNode, OperIsCopyBlkOp 则跳过 | |
如果参数类型小于int, 则设为 TYP_INT | |
调用 NewPutArg(call, arg, info, type) 创建 putarg 节点 | |
oldTree => putarg oldTree or putarg_reg oldTree | |
调用 ReplaceArgWithPutArgOrCopy(ppArg, putArg) | |
替换原来的 arg node 并插入到 LIR 中 | |
枚举 call->gtCallArgs, 调用 LowerArg(call, &args->Current()), 同上 | |
枚举 call->gtCallLateArgs, 调用 LowerArg(call, &args->Current()), 同上 | |
如果是 Delegate.Invoke 则调用 LowerDelegateInvoke(call) | |
转换 call originalThis => call indir(lea(originalThis+24)) with indir(lea(originalThis+8)) | |
indir(lea(originalThis+24))是函数的地址, 保存到call的control expr中 | |
indir(lea(originalThis+8))是真正的this, 会替换掉原有的this式 | |
否则如果是 GTF_CALL_VIRT_STUB 则调用 LowerVirtualStubCall(call) | |
如果 call->gtCallType == CT_INDIRECT | |
替换 call->gtCallAddr 到 Ind(call->gtCallAddr) | |
否则 | |
void* stubAddr = call->gtStubCallStubAddr | |
GenTree* addr = AddrGen(stubAddr) | |
return Ind(addr) | |
否则如果是 GTF_CALL_VIRT_VTABLE 则调用 LowerVirtualVtableCall(call) | |
设置 control expr 为获取实际调用函数的地址, 例如 | |
ind(lea(ind(lea(ind(lea(this+0))+72))+32)) | |
否则如果是 GTF_CALL_NONVIRT | |
且 call->IsUnmanaged() 则调用 LowerNonvirtPinvokeCall(call) | |
在 call 之前插入pinvoke prolog | |
inlinedCallFrame.callTarget = methodHandle | |
inlinedCallFrame.m_pCallerReturnAddress = &label | |
thread->m_fPreemptiveGCDisabled = 0 | |
pinvoke_prolog | |
call | |
thread->m_fPreemptiveGCDisabled = 1 | |
returnTrap ind g_TrapReturningThreads (等待gc完毕) | |
否则且 call->gtCallType == CT_INDIRECT 则调用 LowerIndirectNonvirtCall(call) | |
确认 call->gtCallCookie == nullptr | |
否则调用 LowerDirectCall(call) | |
如果 call->gtCallType == CT_HELPER | |
调用 comp->info.compCompHnd->getHelperFtn(helperNum, ...) | |
否则 | |
调用 comp->info.compCompHnd->getFunctionEntryPoint(call->gtCallMethHnd, ...) | |
如果得到地址的值则 | |
result = AddrGen(addr) | |
如果得到指向地址的值则 | |
result = Ind(AddrGen(addr)) | |
如果得到指向指向地址的值则 | |
result = Ind(Ind(AddrGen(addr))) | |
返回 result, 会设置到 call->gtControlExpr | |
如果 call->IsTailCallViaHelper() 则调用 LowerTailCallViaHelper(call, result) | |
callTarget = 传入的result | |
如果是 x64, 替换第二个参数到 callTarget | |
如果是 x86, 替换第三个参数到 numberOfNewStackArgsWords, 第五个参数到 callTarget | |
把 call 替换为 helper CORINFO_HELP_TAILCALL | |
调用 LowerDirectCall(call) | |
否则如果 call->IsFastTailCall() 则调用 LowerFastTailCall(call) | |
处理在栈中传递的参数, 例如 | |
Caller(a, b, c, d, e) | |
Callee(e, d, c, b, a) | |
会这样传递参数 | |
tmp = e | |
Stack slot of e = a | |
R9 = b | |
R8 = c | |
RDx = d | |
RCX = tmp | |
如果上面调用返回的 result != nullptr | |
resultRange = LIR::SeqTree(comp, result) | |
insertionPoint = call | |
如果 !call->IsTailCallViaHelper() && call->gtCallType == CT_INDIRECT | |
如果 call->gtCallCookie != nullptr | |
insertionPoint = BlockRange().GetTreeRange(call->gtCallCookie, ...) | |
否则如果 call->gtCallAddr != nullptr | |
insertionPoint = BlockRange().GetTreeRange(call->gtCallAddr, ...) | |
调用 BlockRange().InsertBefore(insertionPoint, std::move(resultRange)) | |
设置 call->gtControlExpr = result | |
GT_JMP | |
调用 LowerJmpMethod(node) | |
如果 block 调用了 unmanaged 函数, 且以GT_JMP结束 | |
插入 PME(pinvoke method epilog) 到JMP前 | |
跟前面的call不同,这里调用的是 InsertPInvokeMethodEpilog 而不是 InsertPInvokeCallEpilog | |
GT_RETURN | |
调用 LowerRet(node) | |
如果 comp->info.compCallUnmanaged && (comp->compCurBB == comp->genReturnBB) | |
插入 PME(pinvoke method epilog) 到RET前 | |
GT_CAST | |
调用 LowerCast(node) | |
x86或x64 | |
转换 GT_CAST(small, float/double) 到 GT_CAST(GT_CAST(small, int), float/double) | |
转换 GT_CAST(float/double, small) 到 GT_CAST(GT_CAST(float/double, int), small) | |
arm64 | |
和上面基本一样, 除了不会检查tree是否GTF_UNSIGNED | |
GT_ARR_ELEM | |
返回调用 LowerArrElem(node) | |
转换 arrElem 到 lea | |
GT_ROL, GT_ROR | |
调用 LowerRotate(node) | |
arm64 | |
arm无rol指令, 转换rol到ror (rightIndex = bitSize - leftIndex) | |
GT_STORE_BLK, GT_STORE_OBJ, GT_STORE_DYN_BLK | |
调用 LowerBlockStore(node->AsBlk()) | |
调用 TryCreateAddrMode(LIR::Use(BlockRange(), &blkNode->Addr(), blkNode), false) | |
同上, 尝试转换地址计算到lea | |
GT_SIMD, GT_LCL_VAR, GT_STORE_LCL_VAR | |
如果类型是 TYP_SIMD12 则设置到 TYP_SIMD16 | |
返回 node->gtNext | |
如果 compCallUnmanaged | |
调用 InsertPInvokeMethodProlog | |
调用 fgLocalVarLiveness, 处理同上 | |
枚举 block in m_lsra->startBlockSequence() | |
currentLoc += 2 | |
m_block = block | |
枚举 node in BlockRange().NonPhiNodes() | |
调用 node->gtLsraInfo.Initialize(m_lsra, node, currentLoc) | |
如果 node->gtRegNum == REG_NA || node->gtOper == GT_NOP | |
本地变量 dstCandidates = lsra->allRegs(node->TypeGet()) | |
否则 | |
本地变量 dstCandidates = genRegMask(node->gtRegNum) | |
设置 internalIntCount = 0 | |
设置 internalFloatCount = 0 | |
设置 isLocalDefUse = false | |
设置 isHelperCallWithKills = false | |
设置 isLsraAdded = false | |
设置 definesAnyRegisters = false | |
设置 dstCandsIndex = lsra->GetIndexForRegMask(dstCandidates) | |
设置 srcCandsIndex = dstCandsIndex | |
设置 internalCandsIndex = lsra->GetIndexForRegMask(lsra->allRegs(TYP_INT)) | |
设置 loc = location | |
调用 node->gtClearReg(comp) | |
设置 gtRegNum = REG_NA // 重置分配到的寄存器序号 | |
枚举 operand in node->Operands() | |
设置 operand->gtLIRFlags &= ~LIR::Flags::IsUnusedValue | |
如果 node->IsValue() | |
设置 node->gtLIRFlags |= LIR::Flags::IsUnusedValue | |
currentLoc += 2 | |
枚举 node in BlockRange().NonPhiNodes() | |
调用 TreeNodeInfoInit(node) | |
作用 | |
计算tree需要的寄存器数量并保存在TreeNodeInfo | |
例如 st.lclVar 的dstCount是0, srcCount是1 (0=1) | |
例如 lclVar 的dstCount是1, srcCount是0 (1=0) | |
例如 add 的dstCount是1, srcCount是1 (1=1) | |
看 jitdump 的时候可以看 TreeNodeInfo 输出的信息 (参考 TreeNodeInfo::dump) | |
另外还会设置 contained, 如果一个节点设置为 contained 则表示它是上级节点的一部分, 本身不需要指令 | |
x86或x64 | |
TreeNodeInfo* info = &(tree->gtLsraInfo) | |
如果 tree 是浮点数, 则 SetContainsAVXFlags(true) | |
判断 tree 类型 | |
GT_LCL_FLD, GT_LCL_VAR | |
info->srcCount = 0 | |
info->dstCount = 1 | |
SIMD相关的处理未分析 | |
GT_STORE_LCL_FLD, GT_STORE_LCL_VAR | |
调用 TreeNodeInfoInitStoreLoc(tree->AsLclVarCommon()) | |
如果是x86且是long, 则 srcCount = 2 | |
否则如果 op1 是 contained, 则 srcCount = 0 | |
否则如果 op1->IsMultiRegCall, 则 srcCount = retTypeDesc->GetReturnRegCount | |
否则 srcCount = 1 | |
GT_LIST, GT_FIELD_LIST, GT_ARGPLACE, GT_NO_OP, GT_START_NONGC, GT_PROF_HOOK | |
info->srcCount = 0 | |
info->dstCount = 0 | |
GT_CNS_DBL | |
info->srcCount = 0 | |
info->dstCount = 1 | |
GT_LONG (!defined(_TARGET_64BIT_)) | |
info->srcCount = tree->IsUnusedValue() ? 2 : 0 | |
info->dstCount = 0 | |
GT_BOX, GT_COMMA, GT_QMARK, GT_COLON | |
报错, 这些类型的节点到了这一步不可能存在 | |
GT_RETURN | |
调用 TreeNodeInfoInitReturn(tree) | |
如果是x86且是long | |
info->srcCount = 2 | |
info->dstCount = 0 | |
loVal 的 srcCondidates 等于 RBM_LNGRET_LO (RBM_EAX) | |
hiVal 的 srcCondidates 等于 RBM_LNGRET_HI (RBM_EDX) | |
否则 | |
info->srcCount = ((tree->TypeGet() == TYP_VOID) || op1->isContained()) ? 0 : 1 | |
info->dstCount = 0 | |
根据返回类型设置 srcCondidates 等于 RBM_FLOATRET, RBM_DOUBLERET, RBM_LNGRET, RBM_INTRET | |
GT_RETFILT | |
如果 tree->TypeGet() == TYP_VOID | |
info->srcCount = 0 | |
info->dstCount = 0 | |
否则 (确保 tree->TypeGet() == TYP_INT) | |
info->srcCount = 1 | |
info->dstCount = 0 | |
info->setSrcCandidates(l, RBM_INTRET) | |
tree->gtOp.gtOp1->gtLsraInfo.setSrcCandidates(l, RBM_INTRET) | |
GT_NOP | |
info->srcCount = 0 | |
info->dstCount = (tree->TypeGet() != TYP_VOID && tree->gtOp.gtOp1 == nullptr) ? 1 : 0 | |
GT_JTRUE | |
info->srcCount = 0 | |
info->dstCount = 0 | |
GenTree* cmp = tree->gtGetOp1() | |
l->clearDstCount(cmp) | |
SIMD相关的处理未分析 | |
GT_JCC (根据flags跳转) | |
info->srcCount = 0 | |
info->dstCount = 0 | |
GT_SETCC (设置flags) | |
info->srcCount = 0 | |
info->dstCount = 1 | |
如果是x86, 则调用 info->setDstCandidates(m_lsra, RBM_BYTE_REGS) | |
GT_JMP | |
info->srcCount = 0 | |
info->dstCount = 0 | |
GT_SWITCH | |
报错, SWITCH节点到了这一步应该会变成JTRUE或者GT_SWITCH_TABLE | |
GT_JMPTABLE | |
info->srcCount = 0 | |
info->dstCount = 1 | |
GT_SWITCH_TABLE: | |
info->srcCount = 2 | |
info->internalIntCount = 1 | |
info->dstCount = 0 | |
GT_ASG, GT_ASG_ADD, GT_ASG_SUB | |
报错, 这些类型的节点到了这一步不可能存在 | |
GT_ADD, GT_SUB | |
GT_ADD_LO, GT_ADD_HI, GT_SUB_LO, GT_SUB_HI (x86) | |
info->srcCount += GetOperandSourceCount(tree->gtOp.gtOp1) | |
info->srcCount += GetOperandSourceCount(tree->gtOp.gtOp2) | |
info->dstCount = 1 | |
如果类型不是浮点数 | |
tree->gtFlags |= GTF_ZSF_SET | |
GT_AND, GT_OR, GT_XOR | |
info->srcCount += GetOperandSourceCount(tree->gtOp.gtOp1); | |
info->srcCount += GetOperandSourceCount(tree->gtOp.gtOp2); | |
info->dstCount = 1 | |
tree->gtFlags |= GTF_ZSF_SET | |
GT_RETURNTRAP | |
info->srcCount = tree->gtOp.gtOp1->isContained() ? 0 : 1 | |
info->dstCount = 0 | |
info->internalIntCount = 1 | |
info->setInternalCandidates(l, l->allRegs(TYP_INT)) | |
GT_MOD, GT_DIV, GT_UMOD, GT_UDIV | |
调用 TreeNodeInfoInitModDiv(tree->AsOp()) | |
info->srcCount = GetOperandSourceCount(op1) | |
info->srcCount += GetOperandSourceCount(op2) | |
info->dstCount = 1 | |
如果 tree 是 GT_MOD, GT_UMOD | |
info->setDstCandidates(l, RBM_RDX) // remainder | |
否则 | |
info->setDstCandidates(l, RBM_RAX) // quotient | |
如果是 x86 且type是long | |
loVal->gtLsraInfo.setSrcCandidates(l, RBM_EAX) | |
hiVal->gtLsraInfo.setSrcCandidates(l, RBM_EDX) | |
否则 | |
op1->gtLsraInfo.setSrcCandidates(l, RBM_RAX) | |
如果 op2->IsRegOptional() | |
op2->gtLsraInfo.setSrcCandidates(l, l->allRegs(TYP_INT) & ~(RBM_RAX | RBM_RDX)) | |
GT_MUL, GT_MULHI, GT_MUL_LONG(x86) | |
调用 TreeNodeInfoInitMul(tree->AsOp()) | |
info->srcCount = GetOperandSourceCount(op1) | |
info->srcCount += GetOperandSourceCount(op2) | |
info->dstCount = 1 | |
如果 type 是浮点数则返回 | |
isUnsignedMultiply = ((tree->gtFlags & GTF_UNSIGNED) != 0) | |
requiresOverflowCheck = tree->gtOverflowEx() | |
如果 isUnsignedMultiply && requiresOverflowCheck | |
info->setDstCandidates(m_lsra, RBM_RAX) // 需要使用 RDX:RAX = RAX * rm | |
否则如果 tree->OperGet() == GT_MULHI | |
info->setDstCandidates(m_lsra, RBM_RDX) // 需要使用 RDX:RAX = RAX * rm | |
否则如果是 x86 且type是long | |
info->setDstCandidates(m_lsra, RBM_RAX) // 需要使用 RDX:RAX = RAX * rm | |
设置 contained 且不是常量的 op1 或者 op2 的lifetime需要延长 | |
GT_INTRINSIC, | |
调用 TreeNodeInfoInitIntrinsic(tree->AsOp()) | |
info->srcCount = GetOperandSourceCount(op1) | |
info->dstCount = 1 | |
判断 gtIntrinsicId | |
CORINFO_INTRINSIC_Sqrt | |
不处理 | |
CORINFO_INTRINSIC_Abs | |
info->internalFloatCount = 1 | |
info->setInternalCandidates(l, l->internalFloatRegCandidates()) | |
CORINFO_INTRINSIC_Cos, CORINFO_INTRINSIC_Sin, CORINFO_INTRINSIC_Round (x86) | |
报错, x86上未实现 | |
默认 | |
报错, 目前只支持sqrt和abs | |
GT_SIMD(FEATURE_SIMD) | |
调用 TreeNodeInfoInitSIMD(tree->AsSIMD()) | |
SIMD相关的处理未分析 | |
GT_CAST | |
调用 TreeNodeInfoInitCast(tree) | |
info->srcCount = GetOperandSourceCount(castOp) | |
info->dstCount = 1 | |
如果 tree->gtOverflow() && (castToType == TYP_UINT) && genTypeSize(castOpType) == 8 | |
info->internalIntCount = 1 // GT_CAST from INT64/UINT64 to UINT32 需要额外reg检查overflow | |
GT_BITCAST | |
info->srcCount = 1 | |
info->dstCount = 1 | |
tree->AsUnOp()->gtOp1->gtLsraInfo.isTgtPref = true | |
GT_NEG | |
info->srcCount = 1 | |
info->dstCount = 1 | |
如果 varTypeIsFloating(tree) | |
info->internalFloatCount = 1 | |
info->setInternalCandidates(l, l->internalFloatRegCandidates()) | |
否则 | |
tree->gtFlags |= GTF_ZSF_SET | |
GT_NOT | |
info->srcCount = 1 | |
info->dstCount = 1 | |
GT_LSH, GT_RSH, GT_RSZ, GT_ROL, GT_ROR | |
GT_LSH_HI, GT_RSH_LO (x86) | |
调用 TreeNodeInfoInitShiftRotate(tree) | |
info->srcCount = 2 | |
info->dstCount = 1 | |
如果 !shiftBy->isContained() | |
source->gtLsraInfo.setSrcCandidates(l, l->allRegs(TYP_INT) & ~RBM_RCX) | |
shiftBy->gtLsraInfo.setSrcCandidates(l, RBM_RCX) | |
info->setDstCandidates(l, l->allRegs(TYP_INT) & ~RBM_RCX) | |
否则 (shiftBy可以嵌入指令) | |
如果 !tree->isContained() | |
info->srcCount = 1 | |
GT_EQ, GT_NE, GT_LT, GT_LE, GT_GE, GT_GT, GT_TEST_EQ, GT_TEST_NE, GT_CMP | |
调用 TreeNodeInfoInitCmp(tree) | |
info->srcCount = 0; | |
info->dstCount = tree->OperIs(GT_CMP) ? 0 : 1; | |
如果是x86 | |
info->setDstCandidates(m_lsra, RBM_BYTE_REGS) | |
info->srcCount += GetOperandSourceCount(op1) | |
info->srcCount += GetOperandSourceCount(op2) | |
如果是x86 | |
如果 varTypeIsLong(op1Type) 则 info->srcCount++ | |
如果 varTypeIsLong(op2Type) 则 info->srcCount++ | |
GT_CKFINITE | |
info->srcCount = 1 | |
info->dstCount = 1 | |
info->internalIntCount = 1 | |
GT_CMPXCHG | |
info->srcCount = 3 | |
info->dstCount = 1 | |
来源和目标需要rax, 其余两个需要rax以外 | |
tree->gtCmpXchg.gtOpComparand->gtLsraInfo.setSrcCandidates(l, RBM_RAX); | |
tree->gtCmpXchg.gtOpLocation->gtLsraInfo.setSrcCandidates(l, l->allRegs(TYP_INT) & ~RBM_RAX) | |
tree->gtCmpXchg.gtOpValue->gtLsraInfo.setSrcCandidates(l, l->allRegs(TYP_INT) & ~RBM_RAX) | |
tree->gtLsraInfo.setDstCandidates(l, RBM_RAX) | |
GT_LOCKADD | |
info->dstCount = (tree->TypeGet() == TYP_VOID) ? 0 : 1 | |
info->srcCount = CheckImmedAndMakeContained(tree, tree->gtOp.gtOp2) ? 1 : 2 | |
GT_CALL | |
调用 TreeNodeInfoInitCall(tree->AsCall()) | |
info->srcCount = 0; | |
如果 call->TypeGet() != TYP_VOID | |
如果 call->HasMultiRegRetVal() | |
info->dstCount = retTypeDesc->GetReturnRegCount() | |
否则 | |
info->dstCount = 1 | |
否则 | |
info->dstCount = 0 | |
ctrlExpr = call->gtControlExpr | |
如果 call->gtCallType == CT_INDIRECT | |
ctrlExpr = call->gtCallAddr | |
如果 ctrlExpr != nullptr | |
如果 !call->IsFastTailCall() | |
如果是x86 | |
调用 ctrlExpr->gtGetOp1()->gtLsraInfo.setSrcCandidates(l, RBM_VIRTUAL_STUB_TARGET) | |
调用 MakeSrcContained(call, ctrlExpr) | |
否则如果 ctrlExpr->isIndir() | |
调用 MakeSrcContained(call, ctrlExpr) | |
否则 | |
ctrlExpr->gtLsraInfo.setSrcCandidates(l, RBM_RAX) // 确保可以生成 jmp rax | |
info->srcCount += GetOperandSourceCount(ctrlExpr) | |
如果 call->IsVarargs() | |
调用 info->setInternalCandidates(l, RBM_NONE) | |
如果是x86且 call->IsHelperCall(compiler, CORINFO_HELP_INIT_PINVOKE_FRAME) | |
调用 info->setDstCandidates(l, RBM_PINVOKE_TCB); | |
否则如果 hasMultiRegRetVal | |
调用 info->setDstCandidates(l, retTypeDesc->GetABIReturnRegs()) | |
否则如果 varTypeIsFloating(registerType) | |
如果是x86 info->setDstCandidates(l, l->allRegs(registerType)) | |
否则 info->setDstCandidates(l, RBM_FLOATRET) | |
否则如果 返回类型 == TYP_LONG | |
info->setDstCandidates(l, RBM_LNGRET) | |
否则 | |
info->setDstCandidates(l, RBM_INTRET) | |
如果 call->gtCallObjp != nullptr (有this) | |
如果this是GT_PUTARG_REG | |
l->clearOperandCounts(thisPtrNode) | |
thisPtrNode->SetContained() | |
l->clearDstCount(thisPtrNode->gtOp.gtOp1) | |
否则 | |
l->clearDstCount(thisPtrNode) | |
枚举 call->gtCallLateArgs (寄存器参数) | |
如果 curArgTabEntry->regNum == REG_STK | |
argNode->gtLsraInfo.srcCount = 1 | |
argNode->gtLsraInfo.dstCount = 0 | |
否则 | |
调用 TreeNodeInfoInitPutArgReg(argNode->AsUnOp(), ...) | |
info.srcCount++; | |
argMask = genRegMask(argReg) | |
node->gtLsraInfo.setDstCandidates(m_lsra, argMask) | |
node->gtLsraInfo.setSrcCandidates(m_lsra, argMask) | |
op1的候选跟当前节点的候选一样, 可以避免多余的reg移动 | |
node->gtOp.gtOp1->gtLsraInfo.setSrcCandidates(m_lsra, | |
m_lsra->getUseCandidates(node)) | |
枚举 call->gtCallArgs (stack参数) | |
如果 !(args->gtFlags & GTF_LATE_ARG) | |
如果 argInfo->dstCount != 0 | |
argInfo->isLocalDefUse = true | |
argInfo->dstCount = 0 | |
如果 arg->gtOper == GT_PUTARG_STK | |
如果 IsContainableImmed(arg, op1) | |
且如果 !op1->IsIntegralConst(0) (x64) // 不把0嵌入指令而用xor可以减少代码体积 | |
MakeSrcContained(arg, op1) | |
arg->gtLsraInfo.srcCount-- | |
GT_ADDR | |
调用 MakeSrcContained(tree, tree->gtOp.gtOp1) | |
调用 childNode->SetContained() | |
设置 gtFlags |= GTF_CONTAINED | |
调用 m_lsra->clearOperandCounts(childNode) | |
设置 info.srcCount = 0 | |
设置 info.dstCount = 0 | |
设置 info.internalIntCount = 0 | |
设置 info.internalFloatCount = 0 | |
info->srcCount = 0 | |
info->dstCount = 1 | |
GT_BLK, GT_DYN_BLK | |
报错, 这些类型的节点到了这一步不可能存在 | |
GT_PUTARG_STK (FEATURE_PUT_STRUCT_ARG_STK) | |
调用 LowerPutArgStk(tree->AsPutArgStk()) | |
如果是x86且gtOper == GT_FIELD_LIST | |
fieldList = putArgStk->gtOp1->AsFieldList() | |
head = 反转顺序的 fieldList (因为push时顺序是反的) | |
替换 fieldList 到 head | |
allFieldsAreSlots = 是否所有field都可以对齐4 | |
如果 allFieldsAreSlots | |
putArgStk->gtPutArgStkKind = GenTreePutArgStk::Kind::PushAllSlots | |
否则 | |
putArgStk->gtPutArgStkKind = GenTreePutArgStk::Kind::Push | |
如果 putArgStk->TypeGet() != TYP_STRUCT, 返回 | |
helperThreshold = max(CPBLK_MOVS_LIMIT, CPBLK_UNROLL_LIMIT) | |
size = putArgStk->gtNumSlots * TARGET_POINTER_SIZE | |
如果 size <= CPBLK_UNROLL_LIMIT && putArgStk->gtNumberReferenceSlots == 0 | |
如果是x86且 size < XMM_REGSIZE_BYTES | |
putArgStk->gtPutArgStkKind = GenTreePutArgStk::Kind::Push | |
否则 | |
putArgStk->gtPutArgStkKind = GenTreePutArgStk::Kind::Unroll | |
否则如果 putArgStk->gtNumberReferenceSlots != 0 | |
putArgStk->gtPutArgStkKind = GenTreePutArgStk::Kind::Push | |
否则 | |
putArgStk->gtPutArgStkKind = GenTreePutArgStk::Kind::RepInstr | |
调用 TreeNodeInfoInitPutArgStk(tree->AsPutArgStk()) | |
如果 putArgStk->gtOp1->gtOper == GT_FIELD_LIST | |
调用 putArgStk->gtOp1->SetContained() | |
如果是x86 | |
枚举 putArgStk->gtOp1->AsFieldList() | |
如果 fieldNode 是常量或者int | |
如果 fieldNode->OperGet() == GT_LCL_VAR | |
如果 varDsc->lvTracked && !varDsc->lvDoNotEnregister | |
调用 SetRegOptional(fieldNode) | |
否则 | |
调用 MakeSrcContained(putArgStk, fieldNode) | |
否则如果 fieldNode->IsIntCnsFitsInI32() | |
调用 MakeSrcContained(putArgStk, fieldNode) | |
否则 | |
调用 SetRegOptional(fieldNode) | |
否则如果 current->gtFieldType == TYP_SIMD12 | |
needsSimdTemp = true | |
fieldIsSlot = fieldOffset 是否可以对齐4 | |
如果 !fieldIsSlot && varTypeIsByte(fieldType) | |
设置 needsByteTemp = true | |
如果 !fieldNode->isContained() | |
info->srcCount++; | |
info->dstCount = 0 | |
如果 putArgStk->gtPutArgStkKind == GenTreePutArgStk::Kind::Push | |
regMask = l->allRegs(TYP_INT) | |
如果 needsByteTemp | |
regMask &= ~RBM_NON_BYTE_REGS | |
info->setInternalCandidates(l, regMask) | |
如果 needsSimdTemp | |
info->internalFloatCount += 1 | |
info->addInternalCandidates(l, l->allSIMDRegs()) | |
返回 | |
如果 putArgStk->TypeGet() != TYP_STRUCT | |
调用 TreeNodeInfoInitSimple(putArgStk) 并返回 | |
haveLocalAddr = GT_OBJ或GT_IND目标地址是否本地变量 | |
info->dstCount = 0 | |
根据 putArgStk->gtPutArgStkKind 设置 internalIntCount 和 InternalCandidates | |
调用 MakeSrcContained(putArgStk, src) | |
如果 haveLocalAddr | |
调用 MakeSrcContained(putArgStk, srcAddr) | |
info->srcCount = GetOperandSourceCount(src) | |
GT_STORE_BLK, GT_STORE_OBJ, GT_STORE_DYN_BLK | |
调用 LowerBlockStore(tree->AsBlk()) | |
如果 isInitBlk | |
如果 size != 0 && size <= helperThreshold (不用helper) | |
如果 size <= INITBLK_UNROLL_LIMIT && initVal->IsCnsIntOrI() | |
扩展 initVal->gtIntCon.gtIconVal: 0xab => 0xabababab | |
设置 blkNode->gtBlkOpKind = GenTreeBlk::BlkOpKindUnroll | |
否则 | |
blkNode->gtBlkOpKind = GenTreeBlk::BlkOpKindRepInstr | |
否则 | |
如果是x64 blkNode->gtBlkOpKind = GenTreeBlk::BlkOpKindHelper | |
否则 blkNode->gtBlkOpKind = GenTreeBlk::BlkOpKindRepInstr | |
否则如果 blkNode->gtOper == GT_STORE_OBJ | |
判断 IsRepMovsProfitable | |
如果 IsRepMovsProfitable | |
blkNode->gtBlkOpKind = GenTreeBlk::BlkOpKindRepInstr | |
否则 | |
blkNode->gtBlkOpKind = GenTreeBlk::BlkOpKindUnroll | |
否则 (GT_STORE_BLK, GT_STORE_DYN_BLK) | |
如果 (size != 0) && (size <= helperThreshold) | |
如果 size <= CPBLK_UNROLL_LIMIT | |
blkNode->gtBlkOpKind = GenTreeBlk::BlkOpKindUnroll | |
否则 | |
blkNode->gtBlkOpKind = GenTreeBlk::BlkOpKindRepInstr | |
否则x64 | |
blkNode->gtBlkOpKind = GenTreeBlk::BlkOpKindHelper | |
否则x86 | |
blkNode->gtBlkOpKind = GenTreeBlk::BlkOpKindRepInstr | |
调用 TreeNodeInfoInitBlockStore(tree->AsBlk()) | |
如果 isInitBlk | |
根据 gtBlkOpKind 设置 contained, srcCount, candidates | |
否则 (obj或者blk) | |
根据 gtOper 和 gtBlkOpKind 设置 contained, srcCount, candidates | |
dstCount是0 | |
GT_INIT_VAL | |
info->srcCount = 0 | |
info->dstCount = 0 | |
GT_LCLHEAP (在stack分配空间的节点) | |
调用 TreeNodeInfoInitLclHeap(tree) | |
size = tree->gtOp.gtOp1 | |
如果 size->IsCnsIntOrI (是常量) | |
如果 size 是 0 | |
info->internalIntCount = 0; | |
否则 | |
sizeVal = AlignUp(sizeVal, STACK_ALIGN) | |
如果分配的大小小于或等于6个指针的大小, 会使用push 0 | |
info->internalIntCount = 0 | |
否则如果 !compiler->info.compInitMem | |
如果 sizeVal < compiler->eeGetPageSize() | |
info->internalIntCount = x86 ? 1 : 0 | |
否则 | |
info->internalIntCount = 2 // regCnt and regTmp | |
否则 | |
info->internalIntCount = 0 // 大于6个指针的大小且需要清0 | |
否则 | |
如果 !compiler->info.compInitMem | |
info->internalIntCount = 2 // 需要清0 | |
否则 | |
info->internalIntCount = 0 | |
GT_ARR_BOUNDS_CHECK, GT_SIMD_CHK | |
调用 ContainCheckBoundsChk(tree->AsBoundsChk()) | |
判断 node->gtIndex, node->gtArrLen 是否可以 contained | |
如果可以则设置 MakeSrcContained(node, other) | |
否则设置 SetRegOptional(other) | |
info->srcCount = GetOperandSourceCount(tree->AsBoundsChk()->gtIndex) | |
info->srcCount += GetOperandSourceCount(tree->AsBoundsChk()->gtArrLen) | |
info->dstCount = 0 | |
GT_ARR_ELEM | |
报错, 这些类型的节点到了这一步不可能存在 | |
GT_ARR_INDEX | |
info->srcCount = 2 | |
info->dstCount = 1 | |
需要扩展arrObj的lifetime | |
tree->AsArrIndex()->ArrObj()->gtLsraInfo.isDelayFree = true | |
info->hasDelayFreeSrc = true | |
GT_ARR_OFFSET | |
info->srcCount = 2 | |
info->dstCount = 1 | |
如果 tree->gtArrOffs.gtOffset->IsIntegralConst(0) | |
调用 MakeSrcContained(tree, tree->gtArrOffs.gtOffset), 同上 | |
否则 | |
info->srcCount++ | |
info->internalIntCount = 1 | |
GT_LEA | |
info->srcCount = 0 | |
如果 tree->AsAddrMode()->HasBase() | |
info->srcCount++ | |
如果 tree->AsAddrMode()->HasIndex() | |
info->srcCount++ | |
info->dstCount = 1 | |
GT_STOREIND | |
info->srcCount = 2 | |
info->dstCount = 0 | |
如果 compiler->codeGen->gcInfo.gcIsWriteBarrierAsgNode(tree) | |
调用 TreeNodeInfoInitGCWriteBarrier(tree) 并跳出 | |
如果是lea | |
lea = addr->AsAddrMode() | |
lea->gtLsraInfo.srcCount = lea->HasBase() + lea->HasIndex() | |
lea->gtLsraInfo.dstCount = 1 | |
如果 src 可以嵌入指令作为 imm | |
调用 MakeSrcContained(tree, src), 同上 | |
否则如果 tree 不是浮点数 | |
调用 TreeNodeInfoInitIfRMWMemOp(tree), 返回true时跳出 | |
调用 IsRMWMemOpRootedAtStoreInd(storeInd, &indirCandidate, &indirOpSource) | |
如果 storeInd->IsRMWMemoryOp() 则返回 true | |
否则检测 storeInd 是否可以作为 RMWMemOp, 不可以时返回false | |
如果调用返回false则返回false | |
设置 srcCount, dstCount, candidates | |
TODO | |
调用 TreeNodeInfoInitIndir(tree->AsIndir()) | |
设置 srcCount, dstCount, candidates | |
TODO | |
GT_NULLCHECK | |
info->dstCount = 0 | |
info->srcCount = 1 | |
info->isLocalDefUse = true | |
GT_IND | |
info->dstCount = 1 | |
info->srcCount = 1 | |
调用 TreeNodeInfoInitIndir(tree->AsIndir()), 同上 | |
GT_CATCH_ARG | |
info->srcCount = 0 | |
info->dstCount = 1 | |
info->setDstCandidates(l, RBM_EXCEPTION_OBJECT) | |
GT_END_LFIN (!FEATURE_EH_FUNCLETS) | |
info->srcCount = 0; | |
info->dstCount = 0; | |
GT_CLS_VAR | |
报错, 这些类型的节点到了这一步不可能存在 | |
其他类型 | |
调用 TreeNodeInfoInitSimple(tree) | |
info->dstCount = tree->IsValue() ? 1 : 0; | |
如果 kind & (GTK_CONST | GTK_LEAF | |
info->srcCount = 0 | |
否则如果 kind & (GTK_SMPOP) | |
如果 tree->gtGetOp2IfPresent() != nullptr | |
info->srcCount = 2 | |
否则 | |
info->srcCount = 1 | |
否则 | |
报错, 不应该进入这里 | |
如果 tree->OperIsBinary() && info->srcCount >= 1 && isRMWRegOper(tree) | |
GenTree* op1 = tree->gtOp.gtOp1 | |
GenTree* op2 = tree->gtOp.gtOp2 | |
如果 tree->OperIsCommutative() && op1->gtLsraInfo.dstCount == 0 && op2 != nullptr | |
交换 op1 和 op2 (仅本地变量交换, 原值不变) | |
设置 op1->gtLsraInfo.isTgtPref = true | |
本地变量 GenTree* delayUseSrc = nullptr (判断要延长lifetime的node) | |
如果 tree->OperGet() == GT_XADD || tree->OperGet() == GT_XCHG || tree->OperGet() == GT_LOCKADD | |
如果 tree->TypeGet() == TYP_VOID | |
tree->gtType = TYP_INT | |
tree->gtLsraInfo.isLocalDefUse = true | |
delayUseSrc = op1 | |
否则如果 (op2 != nullptr) && (!tree->OperIsCommutative() || | |
(IsContainableMemoryOp(op2, true) && (op2->gtLsraInfo.srcCount == 0))) | |
delayUseSrc = op2 | |
如果 delayUseSrc != nullptr | |
调用 SetDelayFree(delayUseSrc) | |
设置 info->hasDelayFreeSrc = true | |
调用 TreeNodeInfoInitCheckByteable(tree) | |
如果是x86 | |
如果 ExcludeNonByteableRegisters(tree) // 需要排除不能作为byte使用的寄存器 | |
如果 info->dstCount > 0 | |
调用 info->setDstCandidates(l, regMask & ~RBM_NON_BYTE_REGS) | |
如果 tree->OperIsSimple() && (info->srcCount > 0) | |
如果 op1 不是 containedNode | |
op1->gtLsraInfo.setSrcCandidates(l, regMask & ~RBM_NON_BYTE_REGS) | |
如果 op2 不是 containedNode | |
op2->gtLsraInfo.setSrcCandidates(l, regMask & ~RBM_NON_BYTE_REGS) | |
arm64 | |
和上面基本一样, 这里不做详细解释 | |
如果 node->gtLIRFlags & LIR::Flags::IsUnusedValue | |
设置 node->gtLsraInfo.isLocalDefUse = true | |
设置 node->gtLsraInfo.dstCount = 0 | |
LinearScanInterface::doLinearScan | |
调用 compiler->codeGen->regSet.rsClearRegsModified() | |
设置 rsModifiedRegsMask = RBM_NONE | |
调用 setFrameType() | |
判断应该使用EBP frame还是ESP frame | |
x86或者debug时前面的代码会调用setFramePointerRequired | |
如果isFramePointerRequired则会使用 FT_EBP_FRAME | |
FT_EBP_FRAME 表示用EBP保存当前函数进入后的ESP地址 | |
FT_ESP_FRAME 表示不需要用EBP, EBP可以当作一般的寄存器使用 | |
FT_DOUBLE_ALIGN_FRAME 表示FT_ESP_FRAME的基础上确保frame向8对齐(x64上默认会对齐8) | |
根据frameType调用 | |
FT_EBP_FRAME: compiler->codeGen->setFramePointerUsed(false) | |
FT_EBP_FRAME: compiler->codeGen->setFramePointerUsed(true) | |
FT_DOUBLE_ALIGN_FRAME: | |
compiler->codeGen->setFramePointerUsed(false) | |
compiler->codeGen->setDoubleAlign(true) | |
如果frameType == FT_EBP_FRAME | |
清除regMaskTable中各个元素的ebp对应的mask | |
清除availableIntRegs中的~RBM_FPBASE | |
设置 compiler->rpFrameType = frameType | |
调用 initMaxSpill() | |
设置 needDoubleTmpForFPCall = false | |
设置 needFloatTmpForFPCall = false | |
清空 maxSpill[0~TYP_COUNT] | |
清空 currentSpill[0~TYP_COUNT] | |
调用 buildIntervals() | |
什么是 DummyDefs, RefPosition, Interval 等等 | |
DummyDefs: | |
如果使用了未定义的变量, 则在block开始时添加一个DummyDef | |
TODO: DummyDef的处理 | |
RefPosition: | |
记录定义或使用变量的位置, 如果是Def或者Use则有所属的Interval | |
call之前会添加Kill标记callee可能覆盖的寄存器值已不确定 | |
dump时的 #数字 是编号(仅debug有), @数字 是代码位置 | |
Interval: | |
同一个变量(本地L, 内部T, 其他I)对应的使用期间, 包含多个RefPosition | |
本地变量的Interval会在一开始创建好, 其他(临时)的Interval会在需要使用寄存器(例如call返回值)时使用 | |
Interval有一个Preferences属性, 记录可以使用的寄存器集合 | |
LocationInfo: | |
代码位置, 在构建时会对LIR中的GenTree分配位置, 位置总会+2 | |
构建时还会使用一个本地变量 operandToLocationInfoMap 记录 tree 到 位置范围, 用于设置关联的Interval | |
RegState: | |
用于记录进入函数时的寄存器状态, 包括哪些寄存器被传入的参数使用了 | |
compiler下面的codeGen有两个regState, 分别是intRegState和floatRegState | |
RegRecord: | |
用于记录Interval分配的寄存器, 类型是 | |
设置 currentLoc = 1 | |
调用 buildPhysRegRecords() | |
枚举 physRegs (物理寄存器列表) | |
调用 curr->init(reg) | |
设置 regNum | |
设置 registerType 等于 IntRegisterType 或者 FloatRegisterType | |
设置 isCalleeSave (寄存器是否有可能被call里面的命令覆盖) | |
调用 identifyCandidates() | |
如果有compHndBBtabCount, 则调用 identifyCandidatesExceptionDataflow() | |
枚举 BasicBlock | |
如果是 handler 则添加 bbLiveIn 到 exceptVars | |
如果是 filter 则添加 bbLiveOut 到 filterVars | |
否则如果是 finally 则添加 bbLiveOut 到 finallyVars | |
如果支持把例外处理器设为 funclet | |
添加 funclet 对应的变量到 exceptVars | |
exceptVars |= filterVars | finallyVars | |
枚举在 exceptVars 中的变量 | |
设置 lvaSetVarDoNotEnregister | |
如果变量是引用类型且是 filterVars 且不是参数, 则设置 lvMustInit = true | |
初始化 localVarIntervals, [Interval*, Interval*, ...] 到 compiler->lvaCount | |
这是本地变量到创建的对应的 Interval 指针的索引 | |
清空 fpCalleeSaveCandidateVars | |
清空 fpMaybeCandidateVars | |
枚举 compiler->lvaTable | |
创建新的 Interval | |
新的 Interval 的 registerPreferences 会被设置为 allRegs(theRegisterType) | |
设置 isLocalVar = true | |
设置关联的 lclNum 和 localVarIntervals 的索引 | |
设置 varDsc->lvRegNum = REG_STK | |
如果是32位且类型是long, 则本地变量不能作为寄存器候选 | |
设置 varDsc->lvLRACandidate = 0 并跳过处理 | |
调用 isRegCandidate(varDsc) 判断, 失败时设置 varDsc->lvLRACandidate = 0 并跳过处理 | |
如果编译选项不允许优化本地变量到寄存器, 则返回false | |
如果函数中有jmp, 且本地变量是寄存器参数, 则返回false (必须放stack上) | |
如果本地变量未被跟踪, 则返回false | |
如果本地变量属于提升后的struct, 且struct整体还会被使用则返回false | |
如果变量的地址暴露, 或者不满足varTypeIsEnregisterableStruct, 则设置 lvLRACandidate = 0 | |
如果当前无优化且有函数中有例外处理, 则设置 lvLRACandidate = 0 并跳过处理 | |
如果本地变量标记为varDsc->lvDoNotEnregister, 则设置 lvLRACandidate = 0 并跳过处理 | |
如果类型是浮点数, 但是cpu不带浮点数支持则设置 lvLRACandidate = 0 | |
如果类型是struct则设置 lvLRACandidate = 0 | |
如果类型不是int, long, ref, byref, 或者支持simd的平台上的simd类型则设置 lvLRACandidate = 0 | |
如果 varDsc->lvLRACandidate 成立则 | |
设置 varDsc->lvMustInit = false | |
如果本地变量是浮点数 | |
如果是reg arg, refCntWtd -= BB_UNITY_WEIGHT | |
如果 refCntWtd >= thresholdFPRefCntWtd, 添加本地变量到 fpCalleeSaveCandidateVars | |
如果 refCntWtd >= maybeFPRefCntWtd, 添加本地变量到 fpMaybeCandidateVars | |
如果 floatVarCount > 6 && compiler->fgHasLoops && | |
(compiler->fgReturnBlocks == nullptr || compiler->fgReturnBlocks->next == nullptr) | |
把 fpMaybeCandidateVars 里面的本地变量也设置到 fpCalleeSaveCandidateVars (更激进的策略) | |
设置 currentLoc = 0 | |
枚举已跟踪的参数 (tracked parameters) | |
如果参数未被使用则跳过(不需要保持它们的寄存器存活) | |
如果是寄存器参数, 调用updateRegStateForArg(argDsc) | |
标记 rsCalleeRegArgMaskLiveIn, 设置函数一开始传进来的寄存器的 bitmask | |
如果需要LSRA跟踪 // isCandidateVar(argDsc) => lvLRACandidate | |
interval = getIntervalForLocalVar(lclNum) | |
返回 localVarIntervals[varNum] | |
mask = allRegs(TypeGet(argDsc)) | |
如果是寄存器参数 | |
mask = genRegMask(argDsc->lvArgReg) | |
assignPhysReg(inArgReg, interval) | |
调用 newRefPosition(interval, MinLocation, RefTypeParamDef, nullptr, mask) | |
insertFixedRef = mask是否只有一个寄存器, 且refType是Def或者Use | |
如果 insertFixedRef | |
调用 newRefPosition(physicalReg, theLocation, RefTypeFixedReg, nullptr, mask) 创建新的 RefPosition | |
调用 newRP = newRefPositionRaw(theLocation, theTreeNode, theRefType) 创建新的 RefPosition | |
向 refPositions 列表添加一个 RefPosition 元素, 返回添加后元素的指针地址 | |
设置 newRP | |
newRP->setInterval(theInterval) | |
newRP->isFixedRegRef = isFixedRegister | |
newRP->registerAssignment = mask | |
newRP->setMultiRegIdx(multiRegIdx) | |
newRP->setAllocateIfProfitable(0) | |
调用 associateRefPosWithInterval(newRP) | |
获取 RefPosition 的 referent, 有可能是 Interval 或者 RegRecord | |
如果 referent 是 Interval | |
调用 applyCalleeSaveHeuristics(rp) | |
调用 theInterval->updateRegisterPreferences(rp->registerAssignment) | |
commonPreferences = registerPreferences 和 registerAssignment 的交集 | |
如果 commonPreferences 不为空 | |
设置 registerPreferences = commonPreferences 并返回 | |
如果 registerAssignment 有多个寄存器 | |
设置 registerPreferences = registerAssignment 并返回 | |
如果 registerPreferences 有多个寄存器 | |
保留原有的 registerPreferences 并返回 | |
到这一步说明 registerPreferences 和 registerAssignment 都只有一个寄存器且不相同 | |
newPreferences = registerPreferences 和 registerAssignment 的并集 | |
如果并集中有 calleeSaveRegister (call后仍然会保留的寄存器) | |
registerPreferences = newPreferences & calleeSaveRegs(this->registerType) | |
否则 | |
registerPreferences = newPreferences | |
如果 rp 是 Use, 且不是本地变量 | |
确认是 SDSU(Single Define Single Use), 如果 firstRefPosition != prevRefPosition 则报错 | |
检查 prevRefPosition->registerAssignment & rp->registerAssignment | |
如果寄存器有交集 (newAssignment) | |
如果 interval 中无不可交换的 RMW Def, 或者交集有大于1个寄存器 | |
设置 prevRefPosition->registerAssignment = newAssignment | |
如果寄存器无交集 | |
设置 theInterval->hasConflictingDefUse = true | |
更新 referent 中 refPosition 的链表 | |
更新 referent 中的 recentRefPosition 和 lastRefPosition | |
返回 newRP | |
否则如果类型是struct | |
枚举各个字段 | |
如果需要LSRA跟踪 // fieldVarDsc->lvLRACandidate | |
interval = getIntervalForLocalVar(fieldVarNum) | |
调用 newRefPosition(interval, MinLocation, RefTypeParamDef, nullptr, allRegs(TypeGet(fieldVarDsc))) | |
否则 | |
确保(assert)可以重写对应的寄存器 | |
枚举未被跟踪的参数 | |
调用 updateRegStateForArg(argDsc), 同上 | |
如果有 compiler->info.compPublishStubParam | |
标记 intRegState->rsCalleeRegArgMaskLiveIn |= RBM_SECRET_STUB_PARAM | |
清空 currentLiveVars | |
调用 startBlockSequence 创建block序列 | |
第一次调用时调用 setBlockSequence | |
第一个block是 compiler->fgFirstBB | |
设置 blockSequence[bbSeqCount] = block | |
bbSeqCount++ | |
标记block为visited | |
如果block的任意preds有多于一个的succs | |
设置 blockInfo[block->bbNum].hasCriticalInEdge = true | |
枚举succs | |
如果succs有多于一个的preds | |
设置 blockInfo[block->bbNum].hasCriticalOutEdge = true | |
添加block到readySet, 如果block不在readySet | |
调用 addToBlockSequenceWorkList 添加block到worklist | |
worklist会按weight从大到小排序 | |
在worklist取出下一个未visited的block作为nextBlock | |
block = nextBlock | |
清除所有block的visited标记 | |
标记 fgFirstBB 为visited并返回 fgFirstBB | |
枚举刚才创建的block序列 | |
如果 block 是 fgFirstBB, 调用 insertZeroInitRefPositions() | |
枚举 fgFirstBB 的 bbLiveIn | |
如果变量不是参数且不是 lvLRACandidate 且变量需要初始化或是引用类型 | |
则调用 newRefPosition(interval, MinLocation, RefTypeZeroInit, firstNode, allRegs(interval->registerType)) | |
判断是否 DummyDefs | |
如果 block->bbLiveIn 包含了 predBlock->bbLiveOut 中不存在的变量 | |
代表 block 使用了未初始化的值, 这时候设置 needsDummyDefs = true | |
如果 needsDummyDefs | |
枚举未初始化的值 (newLiveIn) | |
如果需要LSRA跟踪, 且变量不是参数 | |
interval = getIntervalForLocalVar(varNum) | |
调用 newRefPosition(interval, currentLoc, RefTypeDummyDef, nullptr, allRegs(interval->registerType)) | |
调用 newRefPosition((Interval*)nullptr, currentLoc, RefTypeBB, nullptr, RBM_NONE) | |
设置 currentLiveVars = block->bbLiveIn | |
枚举 block 中非phi的node | |
设置 currentLoc = node->gtLsraInfo.loc | |
调用 buildRefPositionsForNode(node, block, listNodePool, operandToLocationInfoMap, currentLoc) | |
本地变量 consume = info.srcCount | |
本地变量 produce = info.dstCount | |
如果是 lclVar 且本地变量是 lvLRACandidate | |
interval = getIntervalForLocalVar(tree->gtLclVarCommon.gtLclNum) | |
candidates = getUseCandidates(tree) | |
fixedAssignment = fixedCandidateMask(tree->TypeGet(), candidates) | |
如果变量被标记为 GTF_VAR_DEATH 则从 currentLiveVars 中删除变量 | |
如果是 isLocalDefUse | |
调用 pos = newRefPosition(interval, currentLoc, RefTypeUse, tree, candidates) | |
设置 pos->isLocalDefUse = true // 只在当前节点定义使用 | |
设置 pos->lastUse = tree->gtFlags & GTF_VAR_DEATH // 最后一次使用 | |
调用 pos->setAllocateIfProfitable(tree->IsRegOptional()) // 可以分配寄存器也可以不分配 | |
否则 | |
如果 info.dstCount > 0 | |
创建 listNodePool.GetNode(currentLoc, interval, tree) | |
添加到 operandToLocationInfoMap.AddOrUpdate(tree, list) | |
设置 operandToLocationInfoMap.AddOrUpdate(tree, list) | |
返回 | |
本地变量 noAdd = info.isLocalDefUse | |
如果是 st.lclVar | |
如果本地变量是 lvLRACandidate | |
获取 varDefInterval = getIntervalForLocalVar(tree->gtLclVarCommon.gtLclNum) | |
如果 produce == 0 | |
设置 produce = 1, noAdd = true | |
设置 operandInfo = tree 的 op1 对应的 LocationInfo (设置到本地变量的来源) | |
如果 operandInfo.interval 无 relatedInterval | |
并且 operandInfo.interval 不是本地变量, 或者该本地变量是最后一次使用 | |
设置 relatedInterval = varDefInterval | |
或者如果 operandInfo.interval 不是本地变量 | |
设置 relatedInterval = varDefInterval | |
如果本地变量不是最后一次使用 | |
添加到 currentLiveVars 中 | |
否则如果 noAdd && produce == 0 | |
如果 tree 是 IsMultiRegCall, 则设置 produce = GetReturnRegCount | |
否则设置 produce = 1 | |
如果 tree 是 putarg, 且 op1 是 lvLRACandidate 且 op1 不是最后一次使用 | |
获取 op1 对应的 operandDefs | |
设置 prefSrcInterval = srcInterval (operandDefs.Begin()->interval) | |
设置 isSpecialPutArg = true | |
调用 internalCount = buildInternalRegisterDefsForNode(tree, currentLoc, internalRefs) | |
枚举 tree 的 internalIntCount 和 internalFloatCount (内部使用的临时变量) | |
调用 defineNewInternalTemp | |
current = newInterval(regType) | |
current->isInternal = true | |
newRefPosition(current, currentLoc, RefTypeDef, tree, regMask) | |
把创建的 refPosition 添加到 internalRefs | |
枚举 consume 次 | |
调用 newRefPosition(i, currentLoc, RefTypeUse, useNode, allRegs(i->registerType), multiRegIdx) | |
这里的 useNode 是空节点 | |
调用 buildInternalRegisterUsesForNode(tree, currentLoc, internalRefs, internalCount) | |
枚举 internalRefs | |
调用 newRefPosition(defs[i]->getInterval(), currentLoc, RefTypeUse, tree, mask) | |
创建 internal def 对应的 use | |
调用 buildKillPositionsForNode(tree, currentLoc + 1) | |
调用 killMask = getKillSetForNode(tree) | |
mul 且检查溢出时杀掉rax和rdx | |
mulhi 时杀掉rax和rdx | |
mod, div, umod, udiv且类型是float时杀掉rax和rdx | |
storeObj 且 isCopyBlkOp 时根据helper call获取killset | |
storeBlk, storeDynBlk 时根据 gtBlkOpKind 获取killset | |
lsh, rsh, rsz, rol, ror且是helpercall时杀掉RBM_CALLEE_TRASH(eax~r11等可以被callee覆盖的寄存器) | |
returntrap 时根据helper call获取killset | |
call 时杀掉RBM_CALLEE_TRASH | |
storeind 且有writebarrier时杀掉RBM_CALLEE_TRASH_NOGC | |
return 且有profiler hook时根据helper call获取killset | |
prof hook 时根据helper call获取killset | |
调用 compiler->codeGen->regSet.rsSetRegsModified(killMask) 设置当前函数修改过的寄存器集合 | |
调用 addRefsForPhysRegMask(killMask, currentLoc, RefTypeKill, true) | |
枚举 killMask 调用 newRefPosition(reg, currentLoc, RefTypeKill, nullptr, genRegMask(reg)) | |
枚举 currentLiveVars | |
调用 updateRegisterPreferences(allRegs(interval->registerType) & (~killMask)) | |
让当前存活的变量尽量避开kill的寄存器 | |
枚举 produce 次 | |
调用 newRefPosition(interval, defLocation, defRefType, defNode, currCandidates, (unsigned)i) | |
把 noAdd 的 locationInfo 添加到本地变量 locationInfoList | |
把 isContainedNode 的 Operands 的 operandList 添加到本地变量 locationInfoList | |
如果 locationInfoList 不为空 | |
设置 operandToLocationInfoMap[tree] = locationInfoList | |
设置 tree->gtLsraInfo.definesAnyRegisters = true | |
设置 currentLoc += 2 | |
+2的理由看论文 | |
https://www.usenix.org/legacy/events/vee05/full_papers/p132-wimmer.pdf | |
调用 markBlockVisited(block) | |
添加 block 到 bbVisitedSet | |
找出 block->bbLiveOut 中包含的, 但succs的bbLiveIn中不包含的变量 | |
代表当前block是 BBJ_HAS_JMP, 需要标记所有参数存活 | |
枚举这些变量 | |
如果需要LSRA跟踪 | |
interval = getIntervalForLocalVar(varNum) | |
调用 newRefPosition(interval, currentLoc, RefTypeExpUse, nullptr, allRegs(interval->registerType)) | |
如果当前开启了优化, 调用setLastUses(block) | |
不优化时不需要调用, 因为变量在整个函数期间都会存活 | |
枚举当前block创建的refPositions (从最后面一直枚举到 RefTypeBB) | |
如果 refPosition 对应的变量不在 block->bbLiveOut 且变量不是 keepAliveVarNum | |
标记 tree->gtFlags |= GTF_VAR_DEATH | |
设置 currentRefPosition->lastUse = true | |
记录标记过的本地变量, 只会标记最后一个 | |
否则 | |
设置 currentRefPosition->lastUse = false | |
标记 tree->gtFlags &= ~GTF_VAR_DEATH | |
如果 compiler->lvaKeepAliveAndReportThis // 需要保持this存活 | |
如果需要LSRA跟踪this | |
interval = getIntervalForLocalVar(keepAliveVarNum) | |
调用 newRefPosition(interval, currentLoc, RefTypeExpUse, nullptr, allRegs(interval->registerType)) | |
如果最后一个block有succs | |
调用 newRefPosition((Interval*)nullptr, currentLoc, RefTypeBB, nullptr, RBM_NONE) | |
清除 bbVisitedSet | |
调用 initVarRegMaps() | |
本地变量 regMapCount = roundUp(compiler->lvaTrackedCount, sizeof(int)); | |
初始化 inVarToRegMaps (regNumber*[fgBBNumMax + 1]) | |
初始化 outVarToRegMaps (regNumber*[fgBBNumMax + 1]) | |
如果 lvaTrackedCount > 0 | |
初始化 sharedCriticalVarToRegMap (regNumber[regMapCount]) | |
设置各个BasicBlock | |
inVarToRegMaps[blockIndex] = new regNumber[regMapCount] | |
outVarToRegMaps[blockIndex] = new regNumber[regMapCount] | |
设置各个 regMapCount | |
inVarToRegMap[regMapIndex] = REG_STK | |
outVarToRegMap[regMapIndex] = REG_STK | |
否则 | |
sharedCriticalVarToRegMap = nullptr | |
设置各个BasicBlock | |
inVarToRegMaps[blockIndex] = nullptr | |
outVarToRegMaps[blockIndex] = nullptr | |
调用 allocateRegisters() | |
枚举 Interval, 如果是本地变量且是参数则设置 currentInterval->isActive = true | |
枚举 physRegs[寄存器数量], 重置 recentRefPosition 和 isActive | |
本地变量 regMaskTP regsToFree = RBM_NONE | |
本地变量 regMaskTP delayRegsToFree = RBM_NONE | |
枚举 refPositions | |
设置 currentReferent = currentRefPosition->referent (Interval 或 RegRecord) | |
如果 spillAlways 成立且有 lastAllocatedRefPosition 且它是本地变量或者非内部的Def | |
调用 unassignPhysReg(regRecord, lastAllocatedRefPosition) | |
调用 checkAndClearInterval(regRec, spillRefPosition); | |
如果 spillRefPosition == nullptr, 检查 assignedInterval->isActive == false | |
否则检查 spillRefPosition->getInterval() == assignedInterval | |
设置 regRec->assignedInterval = nullptr // 重置寄存器记录对应的Interval | |
本地变量 nextRefPosition = spillRefPosition->nextRefPosition // 下一次引用 | |
如果 regRec->assignedInterval->physReg 有值且不等于 regRec->regNum | |
可能是临时的copy reg, 设置 regRec->assignedInterval = nullptr 并返回 | |
本地变量 spill = assignedInterval->isActive && nextRefPosition != nullptr | |
如果 spill | |
调用 spillInterval(assignedInterval, spillRefPosition, nextRefPosition) | |
如果 !fromRefPosition->lastUse | |
如果RefPosition不要求分配寄存器且Interval对应的不是引用类型的本地变量 | |
设置 fromRefPosition->registerAssignment = RBM_NONE | |
否则 | |
设置 fromRefPosition->spillAfter = true | |
设置 interval->isActive = false | |
设置 interval->isSpilled = true | |
如果 fromRefPosition 的位置小于 block 的开始位置 // 进入block前就已经存活在栈上 | |
调用 setInVarRegForBB(curBBNum, interval->varNum, REG_STK) | |
设置 inVarToRegMaps[bbNum][compiler->lvaTable[varNum].lvVarIndex] = reg | |
设置 lastAllocatedRefPosition = nullptr | |
如果 regsToFree | delayRegsToFree 中有值, 且 currentLocation > prevLocation 或者 refType == RefTypeBB | |
调用 freeRegisters(regsToFree) | |
枚举 regsToFree, 调用 freeRegister(getRegisterRecord(nextReg)) | |
如果有对应的 assignedInterval | |
设置 assignedInterval->isActive = false | |
如果 assignedInterval 不是常量 // 常量下次载入时不需要从栈上reload | |
如果该 interval 无下一个引用, 或者下一个引用是def // 可以安全释放寄存器 | |
调用 unassignPhysReg(physRegRecord, nullptr), 同上 | |
否则该寄存器会在下次冲突时spill | |
设置 regsToFree = delayRegsToFree | |
设置 delayRegsToFree = RBM_NONE | |
设置 prevLocation = currentLocation | |
如果有 currentReferent (不是 RefTypeBB 和 RefTypeKillGCRefs) | |
设置 previousRefPosition = currentReferent->recentRefPosition | |
设置 currentReferent->recentRefPosition = currentRefPosition | |
如果 handledBlockEnd == false 且 refType 是 RefTypeBB 或 RefTypeDummyDef | |
因为之前把 DummyDefs 排在了 RefTypeBB 后面, 这里需要先处理 DummyDefs 再处理 RefTypeBB | |
调用 freeRegisters(regsToFree), 同上 | |
设置 regsToFree = RBM_NONE | |
设置 handledBlockEnd = true | |
如果是第一个 block 则设置 currentBlock = startBlockSequence() | |
否则调用 processBlockEndAllocation(currentBlock) 并更新 currentBlock = moveToNextBlock() | |
这个函数会处理上一个block | |
调用 processBlockEndLocations(currentBlock) | |
本地变量 outVarToRegMap = getOutVarToRegMap(curBBNum) | |
枚举本地变量列表 | |
如果本地变量对应的interval是active | |
设置 outVarToRegMap[varIndex] = interval->physReg | |
否则 | |
设置 outVarToRegMap[varIndex] = REG_STK | |
调用 markBlockVisited(currentBlock), 同上 | |
获取序列(startBlockSequence)中的下一个 block, 如果存在则 | |
调用 processBlockStartLocations(nextBlock, allocationPass: true) | |
本地变量 predVarToRegMap = getOutVarToRegMap(predBBNum); | |
本地变量 inVarToRegMap = getInVarToRegMap(currentBlock->bbNum) | |
枚举本地变量列表, 跳过非lvLRACandidate的变量 | |
如果 allocationPass // allocateRegisters | |
本地变量 targetReg = predVarToRegMap[varIndex] | |
设置 inVarToRegMap[varIndex] = predVarToRegMap[varIndex] | |
否则 // resolveRegisters | |
本地变量 targetReg = inVarToRegMap[varIndex] | |
如果 inVarToRegMap[varIndex] != REG_STK | |
如果 predVarToRegMap[varIndex] != REG_STK | |
确保 predVarToRegMap[varIndex] == targetReg | |
否则 | |
设置 inVarToRegMap[varIndex] = REG_STK | |
设置 targetReg = REG_STK | |
如果 interval->physReg == targetReg | |
如果 interval->isActive, 标记 liveRegs |= genRegMask(targetReg) 并继续 | |
否则如果 interval->physReg != REG_NA // pred出来的寄存器跟当前block进入的寄存器不一致 | |
如果 targetReg != REG_STK | |
如果本地变量对应的 interval 有 assignedReg | |
设置 interval->isActive = false | |
调用 unassignPhysReg(getRegisterRecord(interval->physReg), nullptr), 同上 | |
否则 | |
设置 interval->physReg = REG_NA | |
否则如果 allocationPass // allocateRegisters | |
强行修改 inVarToRegMap[varIndex], 如果有冲突则留给 resolveRegisters 解决 | |
设置 interval->isActive = true | |
标记 liveRegs |= genRegMask(interval->physReg) | |
设置 inVarToRegMap[varIndex] = interval->physReg | |
否则 // resolveRegisters | |
interval->physReg = REG_NA | |
如果 targetReg != REG_STK | |
标记 liveRegs |= genRegMask(targetReg) | |
如果本地变量对应的 interval 不是 active | |
设置 interval->isActive = true | |
设置 interval->physReg = targetReg | |
设置 interval->assignedReg = targetRegRecord | |
判断 targetRegRecord->assignedInterval 是否等于本地变量对应的 interval | |
如果 targetRegRecord->assignedInterval != null | |
如果 targetRegRecord->assignedInterval->assignedReg == targetRegRecord | |
表示有另一个interval占用着这个寄存器 | |
调用 unassignPhysReg(targetRegRecord, nullptr) | |
设置 inVarToRegMap[targetRegRecord-> | |
assignedInterval->getVarIndex(compiler)] = REG_STK | |
否则 | |
表示另一个interval并未占用着这个寄存器 | |
设置 targetRegRecord->assignedInterval = nullptr // 寄存器也不指向interval | |
调用 assignPhysReg(targetRegRecord, interval), 分配寄存器到当前interval | |
设置 compiler->codeGen->regSet.rsSetRegsModified(assignedRegMask) | |
标记寄存器在这个函数里面修改过 | |
调用 checkAndAssignInterval(regRec, interval), 同上 | |
设置 interval->assignedReg = regRec // interval关联寄存器 | |
设置 interval->physReg = regRec->regNum | |
设置 interval->isActive = true | |
如果 interval 是本地变量 | |
调用 interval->updateRegisterPreferences(assignedRegMask), 同上 | |
如果 interval 的上一个 refPosition 的寄存器不指向 targetReg 且不是 copyReg | |
设置 interval->getNextRefPosition()->outOfOrder = true | |
枚举寄存器 | |
如果寄存器不在之前标记的 liveRegs 里面 | |
获取寄存器目前对应的 interval, 如果存在则 | |
如果目前对应的interval不是常量且正在占用寄存器 | |
设置 assignedInterval->isActive = false | |
如果无下一个RefPosition则调用 unassignPhysReg(physRegRecord, nullptr) | |
设置 inVarToRegMap[assignedInterval->getVarIndex(compiler)] = REG_STK | |
否则 | |
设置 physRegRecord->assignedInterval = nullptr | |
如果 refType == RefTypeBB | |
设置 handledBlockEnd = false 并继续 | |
如果 refType == RefTypeKillGCRefs | |
调用 spillGCRefs(currentRefPosition) 并继续 | |
枚举 killRefPosition->registerAssignment 里面的寄存器 | |
如果寄存器对应的interval是active, 且里面的值是ref类型的指针 | |
调用 unassignPhysReg(regRecord, assignedInterval->recentRefPosition), 同上 | |
如果 refType == RefTypeFixedReg | |
如果寄存器对应的interval不为空, 非active且是常量, 则清空 regRecord->assignedInterval 并继续 | |
如果 refType == RefTypeExpUse | |
不做任何事情并继续, 这个类型仅用于保证变量在整个过程中都有分配寄存器或spill到堆栈 (需要keepalive的变量) | |
如果 currentRefPosition->isIntervalRef() // 有对应的Interval | |
设置 currentInterval = currentRefPosition->getInterval() | |
设置 assignedRegister = currentInterval->physReg | |
如果 refType 是 RefTypeParamDef 或 RefTypeZeroInit | |
如果 refType 是 RefTypeParamDef 且引用计数比较少 | |
不需要分配寄存器 | |
否则如果 currentRefPosition->nextRefPosition == nullptr | |
设置的值不会被使用, 设置 currentRefPosition->lastUse = true | |
如果不需要分配寄存器 | |
调用 unassignPhysReg(getRegisterRecord(assignedRegister), currentRefPosition) | |
设置 currentRefPosition->registerAssignment = RBM_NONE | |
继续循环 | |
如果 currentInterval->isSpecialPutArg // PUTARG, 但来源之后还会被使用 | |
如果 refType == RefTypeDef | |
如果 srcInterval->isActive && | |
genRegMask(srcInterval->physReg) == currentRefPosition->registerAssignment && | |
currentInterval->getNextRefLocation() == physRegRecord->getNextRefLocation() | |
来源的寄存器会同时用于call | |
设置 physRegRecord->isBusyUntilNextKill = true | |
表示不允许该寄存器在call之前spill | |
否则 | |
设置 currentInterval->isSpecialPutArg = false | |
如果 currentInterval->isSpecialPutArg // 无法取消isSpecialPutArg | |
继续处理 | |
如果 assignedRegister == REG_NA 且 RefTypeIsUse(refType) | |
设置 currentRefPosition->reload = true | |
本地变量 assignedRegBit = RBM_NONE | |
本地变量 isInRegister = false | |
如果 assignedRegister != REG_NA // 需要一个指定的寄存器 | |
设置 assignedRegBit = genRegMask(assignedRegister) | |
设置 isInRegister = true | |
如果 !currentInterval->isActive | |
如果 refType 是 Use, 设置 isInRegister = false (取消设置) | |
否则设置 currentInterval->isActive = true | |
如果 currentRefPosition->isPhysRegRef (需要一个指定的寄存器) | |
如果 currentRefPosition 对应的寄存器已经有 assignedInterval | |
调用 unassignPhysReg(currentReg, assignedInterval->recentRefPosition), 同上 | |
设置 currentReg->isActive = true | |
设置 assignedRegister = currentReg->regNum | |
设置 assignedRegBit = genRegMask(assignedRegister) | |
如果 refType == RefTypeKill | |
设置 currentReg->isBusyUntilNextKill = false | |
或者如果 previousRefPosition != nullptr | |
不做处理 | |
或者如果 assignedRegister != REG_NA 且 currentInterval->isLocalVar | |
这是一个预先分配好的寄存器 (例如参数) | |
如果下一次使用该寄存器的位置小于等于 currentInterval->lastRefPosition->nodeLocation | |
或者 refType == RefTypeParamDef | |
并且 assignedRegister 和 currentInterval->registerPreferences 无交集 | |
调用 unassignPhysRegNoSpill(physRegRecord) | |
设置 assignedInterval->isActive = false | |
调用 unassignPhysReg(regRec, nullptr) // 不会设置spill | |
设置 assignedInterval->isActive = true | |
重置 currentInterval->registerPreferences 不包括 assignedRegBit | |
重置 assignedRegister = REG_NA | |
重置 assignedRegBit = RBM_NONE | |
如果 assignedRegister != REG_NA | |
本地变量 physRegRecord = getRegisterRecord(assignedRegister) // 当前分配寄存器的记录 | |
调用 physRegRecord->conflictingFixedRegReference(currentRefPosition) 判断是否有冲突 | |
如果 refPosition 不是 fixed register (不是必须使用该寄存器) | |
返回 false | |
如果该寄存器上次引用的refPosition的代码位置与这一次相同, 且上一次不是RefTypeKill | |
返回 true | |
如果该寄存器下次引用的refPosition位置是这次的+1, 且这次标记了delayRegFree | |
返回 true | |
返回 false | |
如果有冲突 | |
如果 physRegRecord->assignedInterval 仍对应 currentInterval // 未重新分配 | |
调用 unassignPhysRegNoSpill(physRegRecord), 同上 | |
设置 currentRefPosition->moveReg = true | |
重置 assignedRegister = REG_NA | |
否则如果分配到的寄存器跟当前 RefPosition 使用的寄存器有交集 | |
设置 currentRefPosition->registerAssignment = assignedRegBit | |
如果 !currentReferent->isActive | |
如果 refType == RefTypeDummyDef | |
设置 currentReferent->isActive = true | |
否则 | |
设置 currentRefPosition->reload = true | |
否则 | |
值已经在寄存器里, 但不是想要的寄存器 | |
如果 !currentRefPosition->isFixedRegRef || currentRefPosition->delayRegFree | |
(忽略 isFixedRegRef && !delayRegFree 的情况, 因为已经有FixedReg确保使用的寄存器是哪个) | |
如果 !RefTypeIsDef(currentRefPosition->refType) | |
调用 assignCopyReg(currentRefPosition) | |
给 refPosition 分配一个新的寄存器 (tryAllocateFreeReg => allocateBusyReg) | |
然后设置 refPosition->copyReg = true // 需要从这个refPosition复制到interval对应的寄存器 | |
设置 lastAllocatedRefPosition = currentRefPosition | |
如果 currentRefPosition->lastUse | |
如果 currentRefPosition->delayRegFree | |
标记 delayRegsToFree |= // 下下次再free | |
(genRegMask(assignedRegister) | currentRefPosition->registerAssignment); | |
否则 | |
标记 regsToFree |= // 下次free | |
(genRegMask(assignedRegister) | currentRefPosition->registerAssignment) | |
如果 !currentInterval->isLocalVar // 临时变量 | |
设置 currentRefPosition->moveReg = true | |
设置 currentRefPosition->copyReg = false | |
否则 | |
标记 regsToFree |= genRegMask(assignedRegister) | |
重置 assignedRegister = REG_NA | |
如果 physRegRecord->assignedInterval 仍对应 currentInterval // 未重新分配 | |
调用 unassignPhysRegNoSpill(physRegRecord), 同上 | |
如果 assignedRegister == REG_NA | |
本地变量 allocateReg = true | |
如果 currentRefPosition->AllocateIfProfitable 且 lastUse && reload | |
设置 allocateReg = false // 最后一次使用且原来不在寄存器时, 不分配寄存器 | |
如果 allocateReg | |
设置 assignedRegister = tryAllocateFreeReg(currentInterval, currentRefPosition) | |
论文里面说的first pass | |
本地变量 candidates = refPosition->registerAssignment // 当前refPosition分配的寄存器 | |
本地变量 preferences = currentInterval->registerPreferences // 当前interval倾向于使用的寄存器 | |
如果 RefTypeIsDef(refPosition->refType) | |
如果 currentInterval->hasConflictingDefUse 则调用 resolveConflictingDefAndUse | |
hasConflictingDefUse表示该interval是临时变量且Def和Use的registerAssignment无交集 | |
否则如果当前def是fixed reg, 且下一个ref是use, 且当前def用的寄存器下一次ref早于use | |
candidates |= nextRefPos->registerAssignment | |
如果 preferences 与 candidates 有交集 则 preferences = 交集, 否则 preferences = candidates | |
获取 relatedInterval = currentInterval->relatedInterval // 可能是值的来源 | |
如果 relatedInterval != nullptr | |
如果relatedInterval的下一个refPosition不是Def | |
设置 relatedInterval = nullptr | |
否则如果 relatedInterval->relatedInterval 存在 | |
且 relatedInterval 只剩两个refPosition, 位置都小于 relatedInterval->relatedInterval 的 | |
表示 relatedInterval->relatedInterval 应该是 relatedInterval 的复制 (Def Use => Def) | |
设置 relatedInterval = relatedInterval->relatedInterval | |
获取 relatedPreferences = relatedInterval->assignedReg 或 registerPreferences | |
获取 rangeEndRefPosition 等于 | |
如果interval是float则 rangeEndRefPosition = refPosition (不激进的使用callee-save registers) | |
设置 rangeEndRefPosition = currentInterval->lastRefPosition | |
如果 relatedInterval 的下一个refPosition大于 rangeEndRefPosition | |
延长 lastRefPosition = relatedInterval->lastRefPosition | |
设置 preferCalleeSave = relatedInterval->preferCalleeSave | |
如果 currentInterval->assignedReg // 当前interval有指定的寄存器 | |
如果 assignedReg 在 preferences 中 | |
如果寄存器可用, 且refPosition是fixed reg | |
设置 refPosition->registerAssignment = genRegMask(foundReg) | |
返回 foundReg | |
设置 currentInterval->assignedReg = nullptr // 重置分配的寄存器 | |
枚举 candidates 选取一个寄存器 | |
选取寄存器时会计分, 分数分别有 (从高到低可叠加) | |
VALUE_AVAILABLE: 值是常量且已经在该寄存器中 | |
COVERS: 在preference集合中且覆盖了整个生存期间 | |
OWN_PREFERENCE: 在preference集合中 | |
COVERS_RELATED: 在related interval的preference集合中, 且覆盖了related interval的生存期间 | |
RELATED_PREFERENCE: 在related interval的preference集合中 | |
CALLER_CALLEE: 寄存器在preferCalleeSave的倾向的集合中 | |
UNASSIGNED: 寄存器未分配到其他inactive的interval中 | |
如果寄存器已经对应当前的interval | |
设置 availablePhysRegInterval = physRegRecord | |
设置 intervalToUnassign = nullptr | |
跳出循环 | |
调用 registerIsAvailable(physRegRecord, currentLocation, &nextPhysRefLocation, regType)) | |
判断寄存器是否可用, 不可用时继续循环 | |
调用 physRegRecord->conflictingFixedRegReference(refPosition) | |
判断refPosition是否不能存在当前选择的寄存器, 不能时继续循环 | |
判断该寄存器中是否已经有相同常量的值, 有时标记 score |= VALUE_AVAILABLE | |
判断如果 candidateBit & preferences, 则标记 score |= OWN_PREFERENCE | |
并且如果 nextPhysRefLocation > rangeEndLocation, 则标记 score |= COVERS | |
判断如果 candidateBit & relatedPreferences, 则标记 score |= RELATED_PREFERENCE | |
并且如果 nextPhysRefLocation > relatedInterval->lastRefPosition->nodeLocation | |
则标记 score |= COVERS_RELATED | |
如果 preferCalleeSave && physRegRecord->isCalleeSave | |
或 !preferCalleeSave && !physRegRecord->isCalleeSave | |
则标记 score |= CALLER_CALLEE | |
如果寄存器无对应的interval, 或者对应的interval的下一个refPosition大于当前interval的末尾 | |
则标记 score |= UNASSIGNED | |
对比 score | |
如果当前 score 更高 | |
或者当前最优的寄存器未覆盖到最后的位置, 且当前的寄存器的下次引用位置更后面 | |
或者最优的寄存器和当前的寄存器都覆盖到最后的位置 | |
且当前的寄存器的下次引用位置更前面 (间隔更短) | |
或者下次引用位置相等但当前的寄存器等于之前使用的寄存器 | |
设置 bestLocation = nextPhysRefLocation // 寄存器下次引用的位置 | |
设置 availablePhysRegInterval = physRegRecord // 最优的寄存器对应的记录 | |
设置 intervalToUnassign = physRegRecord->assignedInterval // 寄存器之前对应的interval | |
设置 bestScore = score // 最优的寄存器对应的分数 | |
如果当前分数已经是最高且位置刚好是末尾+1, 则跳出 (不会得到更好的候选) | |
如果有 availablePhysRegInterval // 找到一个最优的寄存器 | |
如果有 intervalToUnassign | |
调用 unassignPhysReg(availablePhysRegInterval, intervalToUnassign->recentRefPosition) | |
调用 assignPhysReg(availablePhysRegInterval, currentInterval) | |
设置 foundReg = availablePhysRegInterval->regNum | |
设置 foundRegMask = genRegMask(foundReg) | |
设置 refPosition->registerAssignment = foundRegMask | |
调用 relatedInterval->updateRegisterPreferences(foundRegMask) | |
返回 foundReg | |
如果 assignedRegister == REG_NA | |
如果 currentRefPosition->RequiresRegister() || currentRefPosition->AllocateIfProfitable() | |
如果 allocateReg | |
设置 assignedRegister = allocateBusyReg(currentInterval, currentRefPosition, | |
currentRefPosition->AllocateIfProfitable()) | |
论文里面说的second pass | |
第三个参数如果为true, 则在其他refPosition更重要时不会分配 | |
本地变量 farthestRefPhysRegRecord = nullptr | |
本地变量 farthestLocation = MinLocation | |
本地变量 candidates = refPosition->registerAssignment | |
本地变量 preferences = (current->registerPreferences & candidates) 或 candidates | |
本地变量 farthestRefPosWeight = allocateIfProfitable ? getWeight(refPosition) : BB_MAX_WEIGHT | |
枚举 candidates | |
如果寄存器 isBusyUntilNextKill 则跳过 | |
如果 refPosition 与该寄存器有冲突 (conflictingFixedRegReference) 则跳过 | |
如果 refPosition 是 fixed reg 且等于当前寄存器, 则必须使用当前寄存器 | |
设置 physRegNextLocation = MaxLocation | |
设置 farthestRefPosWeight = BB_MAX_WEIGHT | |
否则 | |
设置 physRegNextLocation = physRegRecord->getNextRefLocation() | |
如果 refPosition 是 fixed erg 且当前寄存器的下次使用位置小于 farthestLocation 则跳过 | |
如果寄存器无对应的 interval, 表示当前位置有另一个fixed arg占用此寄存器或者是fixed loReg, 跳过 | |
因为正常情况下 tryAllocateFreeReg 已经会使用这个寄存器 | |
如果 !assignedInterval->isActive, 表示当前位置有另一个ref, 跳过 | |
因为正常情况下 tryAllocateFreeReg 已经会使用这个寄存器 | |
如果寄存器对应的interval的 recentAssignedRef != nullptr | |
如果该ref的位置和当前位置一样, 跳过 | |
如果ref设置为delayRegFree且当前位置是ref位置+1(寄存器需要在当前ref之后释放) | |
如果ref对应的节点的weight大于farthestRefPosWeight(更重所以不值得), 跳过 | |
如果 recentAssignedRefWeight < farthestRefPosWeight | |
表示要spill的节点权重比目前的最低权重低, 设置 isBetterLocation = true | |
否则 // 权重是相等的 | |
如果 allocateIfProfitable && farthestRefPhysRegRecord == nullptr | |
设置 isBetterLocation = false // 如果分配寄存器是可选的且权重一样, 则不分配 | |
否则 | |
如果旧的interval的下次引用位置大于farthestLocation | |
设置 isBetterLocation = true // 下次reload的时候不用spill回新的interval | |
否则如果旧的interval的下次引用位置等于farthestLocation | |
并且如果旧的interval的上次引用是reload且AllocateIfProfitable | |
代表该interval可以不需要存到寄存器中 | |
设置 isBetterLocation = true | |
否则 | |
设置 isBetterLocation = false | |
如果 isBetterLocation | |
设置 farthestLocation = nextLocation // 旧interval的下一个ref的位置 | |
设置 farthestRefPhysRegRecord = physRegRecord // 寄存器对应的RegRecord | |
设置 farthestRefPosWeight = recentAssignedRefWeight // 旧interval的上次ref的权重 | |
如果 farthestRefPhysRegRecord != nullptr | |
设置 foundReg = farthestRefPhysRegRecord->regNum | |
调用 unassignPhysReg(farthestRefPhysRegRecord, | |
farthestRefPhysRegRecord->assignedInterval->recentRefPosition) | |
会spill掉旧的interval | |
调用 assignPhysReg(farthestRefPhysRegRecord, current) | |
设置 refPosition->registerAssignment = genRegMask(foundReg) | |
如果还是 assignedRegister == REG_NA | |
确认是 currentRefPosition->AllocateIfProfitable() // requires时一定会分配 | |
设置 registerAssignment = RBM_NONE | |
设置 currentRefPosition->reload = false | |
否则 | |
设置 registerAssignment = RBM_NONE | |
设置 currentRefPosition->reload = false | |
如果 refType == RefTypeDummyDef && assignedRegister != REG_NA | |
调用 setInVarRegForBB(curBBNum, currentInterval->varNum, assignedRegister), 同上 | |
如果 currentInterval != nullptr && assignedRegister != REG_NA | |
已分配一个寄存器, 记录它 | |
设置 currentRefPosition->registerAssignment = genRegMask(assignedRegister) | |
设置 currentInterval->physReg = assignedRegister | |
设置 regsToFree &= ~assignedRegBit | |
如果 currentRefPosition->lastUse || currentRefPosition->nextRefPosition == nullptr | |
如果 refType != RefTypeExpUse && currentRefPosition->nextRefPosition == nullptr | |
使用后释放该寄存器 | |
如果 currentRefPosition->delayRegFree | |
设置 delayRegsToFree |= assignedRegBit | |
否则 | |
regsToFree |= assignedRegBit | |
否则 | |
设置 currentInterval->isActive = false // 后面还会使用这个interval, 标记为inactive | |
设置 lastAllocatedRefPosition = currentRefPosition | |
调用 freeRegisters(regsToFree | delayRegsToFree), 同上 | |
调用 resolveRegisters() | |
枚举寄存器 | |
重置assignedInterval和recentRefPosition | |
如果有关联的assignedInterval则同时重置该interval关联的寄存器 | |
枚举本地变量 | |
重置本地变量对应的interval的recentRefPosition, 并设置isActive = false | |
枚举函数开头的 RefTypeParamDef 和 RefTypeZeroInit (传入参数和初始化的变量) | |
调用 resolveLocalRef(nullptr, nullptr, currentRefPosition) | |
获取 refPosition 对应的 interval, 如果不是本地变量的 interval 则不处理并返回 | |
如果 currentRefPosition->registerAssignment == RBM_NONE // 无分配的寄存器 | |
设置 interval->isSpilled = true // 实际spill了, 真的需要保存到栈上 | |
设置 varDsc->lvRegNum = REG_STK | |
重置 interval->assignedReg->assignedInterval = nullptr | |
重置 interval->assignedReg = nullptr | |
重置 interval->physReg = REG_NA | |
返回 | |
本地变量 assignedReg = currentRefPosition->assignedReg() | |
本地变量 homeReg = assignedReg // 如果是copyReg会设置为新的寄存器 | |
如果 currentRefPosition 未标记 copyReg | |
如果 interval 的寄存器跟 refPosition 的寄存器不一致 | |
重置 interval 的寄存器的 oldRegRecord->assignedInterval = nullptr | |
如果 refPosition 是 use 且未标记为 reload, 并且 interval 无对应的寄存器 | |
可能在处理前面的block时spill了该变量 | |
设置 currentRefPosition->reload = true | |
如果当前 refPosition 需要 reload, 并且 refType 不是 def | |
设置 varDsc->lvRegNum = REG_STK | |
如果不需要 spillAfter | |
设置 interval->physReg = assignedReg | |
如果 treeNode != nullptr | |
标记 treeNode->gtFlags |= GTF_SPILLED | |
如果需要 spillAfter | |
如果 currentRefPosition->AllocateIfProfitable | |
该变量可以不reload到寄存器, 使用时作为contained即可(例如mov rcx, [rbp+offset]) | |
设置 interval->physReg = REG_NA | |
设置 treeNode->gtRegNum = REG_NA | |
设置 treeNode->gtFlags &= ~GTF_SPILLED | |
否则 | |
标记 treeNode->gtFlags |= GTF_SPILL | |
设置 interval->isSpilled = true 除非本地变量是通过栈传入的参数 | |
否则如果 refPosition 需要 spillAfter 并且 refType 不是 use | |
表示值可以直接写到栈上, 不需要分配寄存器 (例如 mov [rbp+偏移值], rax) | |
设置 interval->isSpilled = true | |
设置 varDsc->lvRegNum = REG_STK | |
设置 interval->physReg = REG_NA | |
设置 treeNode->gtRegNum = REG_NA | |
否则 | |
如果 currentRefPosition->copyReg || currentRefPosition->moveReg | |
设置 treeNode->gtRegNum = interval->physReg | |
如果 currentRefPosition->copyReg | |
设置 homeReg = interval->physReg | |
否则 | |
interval->physReg = assignedReg | |
如果 !currentRefPosition->isFixedRegRef || currentRefPosition->moveReg | |
调用 insertCopyOrReload(block, treeNode, currentRefPosition->getMultiRegIdx(), currentRefPosition) | |
生成 GT_RELOAD 或者 GT_COPY 并插入, 例如 "x; y; GT_ADD;" => "x; y; reload_x; GT_ADD;" | |
如果 refPosition->reload | |
生成一个 GT_RELOAD 节点并插入到tree后面, 替换使用tree的node的use到新node | |
否则 | |
生成一个 GT_COPY 节点并插入到tree后面, 替换使用tree的node的use到新node | |
否则 | |
设置 interval->physReg = assignedReg | |
如果 !interval->isSpilled && !interval->isSplit | |
如果 varDsc->lvRegNum != REG_STK | |
如果 varDsc->lvRegNum != assignedReg // 本地变量在同一interval中所用寄存器不一致 | |
设置 interval->isSplit = TRUE | |
设置 varDsc->lvRegNum = REG_STK | |
否则 | |
varDsc->lvRegNum = assignedReg // 设置本地变量使用的寄存器 | |
如果 spillAfter | |
设置 treeNode->gtFlags |= GTF_SPILL | |
设置 interval->isSpilled = true | |
设置 interval->physReg = REG_NA | |
设置 varDsc->lvRegNum = REG_STK | |
如果 treeNode != nullptr && !(treeNode->gtFlags & GTF_SPILLED) | |
标记 treeNode->gtFlags |= GTF_REG_VAL // 值在寄存器中 | |
如果 refPosition 需要 spillAfter 并且是 lastUse | |
设置 physRegRecord->assignedInterval = nullptr | |
设置 interval->assignedReg = nullptr | |
设置 interval->physReg = REG_NA | |
设置 interval->isActive = false | |
否则 | |
设置 interval->isActive = true | |
设置 physRegRecord->assignedInterval = interval | |
设置 interval->assignedReg = physRegRecord | |
设置 inVarToRegMaps[firstBB的序号][varIndex] = refPosition对应的寄存器 | |
枚举 block in m_lsra->startBlockSequence() | |
记录 curBBStartLocation = currentRefPosition->nodeLocation | |
如果 block 不是 fgFirstBB, 调用 processBlockStartLocations(block, false), 同上 | |
枚举 block 开头的 RefTypeDummyDef | |
调用 resolveLocalRef(nullptr, nullptr, currentRefPosition), 同上 | |
如果 currentRefPosition->registerAssignment != RBM_NONE | |
本地变量 reg = currentRefPosition->assignedReg() | |
否则 | |
本地变量 reg = REG_STK | |
设置 currentRefPosition->getInterval()->isActive = false | |
调用 setInVarRegForBB(curBBNum, currentRefPosition->getInterval()->varNum, reg), 同上 | |
枚举 block 中的 RefPosition (直到遇到RefTypeBB或者RefTypeDummyDef) | |
判断 currentRefPosition->refType | |
RefTypeUse, RefTypeDef | |
break, 在下面处理 | |
RefTypeKill, RefTypeFixedReg | |
设置 currentRefPosition->referent->recentRefPosition = currentRefPosition | |
继续循环 | |
RefTypeExpUse | |
设置 currentRefPosition->referent->recentRefPosition = currentRefPosition | |
继续循环 | |
RefTypeKillGCRefs | |
继续循环 | |
其他类型 (RefTypeDummyDef, RefTypeParamDef, RefTypeZeroInit) | |
报错, 不可能到达这里 | |
调用 updateMaxSpill(currentRefPosition) | |
统计非本地变量的spill层数 | |
maxSpill[int或float] = 最深的spill层数 (spill后++, reload后--, 这里记录的是最大值) | |
本地变量 treeNode = currentRefPosition->treeNode | |
如果 treeNode == nullptr | |
确保refPosition是Use, 或者无分配寄存器, 或者是struct | |
如果 interval 是本地变量且不是 struct | |
这个refPosition是dead def (refPosition无对应的treeNode) | |
设置 varDsc->lvRegNum = REG_STK // 本地变量使用堆栈 | |
继续循环 | |
如果 refPosition 有关联的 interval 且该 interval 是内部使用的 | |
如果节点是ind且地址节点不是arrElem | |
设置地址节点的 addrNode->gtRsvdRegs |= currentRefPosition->registerAssignment | |
如果节点是arrElem | |
设置第一个index对应的tree的 firstIndexTree->gtRsvdRegs = | |
(regMaskSmall)currentRefPosition->registerAssignment | |
设置 treeNode->gtRsvdRegs |= currentRefPosition->registerAssignment | |
否则 | |
调用 writeRegisters(currentRefPosition, treeNode) | |
调用 lsraAssignRegToTree(tree, | |
currentRefPosition->assignedReg(), currentRefPosition->getMultiRegIdx()) | |
如果 regIdx == 0, 设置 tree->gtRegNum = reg | |
否则设置 call->SetRegNumByIdx(reg, regIdx) // 设置 gtOtherRegs | |
如果 treeNode 是本地变量且 currentRefPosition->getInterval()->isLocalVar | |
调用 resolveLocalRef(block, treeNode, currentRefPosition), 同上 | |
否则如果 currentRefPosition->spillAfter 或者 currentRefPosition->nextRefPosition->moveReg | |
如果 currentRefPosition->spillAfter | |
标记 treeNode->gtFlags |= GTF_SPILL // 标记spill, 后面会变为GTF_SPILLED | |
重置 treeNode->gtFlags &= ~GTF_REUSE_REG_VAL // spill的时候需要重新设置常量值 | |
如果 treeNode->IsMultiRegCall() | |
调用 call->SetRegSpillFlagByIdx(GTF_SPILL, currentRefPosition->getMultiRegIdx()) | |
设置 gtSpillFlags[idx] = flags | |
如果 nextRefPosition->assignedReg() != currentRefPosition->assignedReg() | |
表示需要复制寄存器 | |
如果 nextRefPosition->assignedReg() != REG_NA | |
调用 insertCopyOrReload(block, treeNode, | |
currentRefPosition->getMultiRegIdx(), nextRefPosition), 同上 | |
否则 | |
如果当前refPos是Def, 下一个是Use则标记 | |
treeNode->gtFlags |= GTF_NOREG_AT_USE (它会被当作use的contained) | |
调用 processBlockEndLocations(block) | |
本地变量 outVarToRegMap = getOutVarToRegMap(curBBNum) | |
枚举本地变量列表 | |
本地变量 interval = getIntervalForLocalVar(varNum) | |
如果 interval->isActive | |
设置 outVarToRegMap[varIndex] = interval->physReg | |
否则 | |
设置 outVarToRegMap[varIndex] = REG_STK | |
调用 resolveEdges() | |
这个函数的处理 | |
枚举 BasicBlock | |
如果 block 只有一个 preds, 但不是刚好在前面, 则需要 split resolution | |
如果 block 有 critical incoming edges(多个preds), 处理它们 | |
如果 block 只有一个 succs 但该 succs 有多个 preds, 则需要 join resolution | |
bbNumMaxBeforeResolution是什么 | |
resolveEdges会创建新的BasicBlock, 创建的新block的bbNum将会大于bbNumMaxBeforeResolution | |
枚举 BasicBlock | |
如果 blockInfo[block->bbNum].hasCriticalOutEdge // succs有多于一个preds | |
调用 handleOutgoingCriticalEdges(block) | |
判断block结束时各个变量的寄存器跟succs中的寄存器是否一致 | |
有三种情况 | |
block结束时变量的寄存器跟succs中变量的寄存器一致, 这种情况无需resolution | |
block结束时变量的寄存器跟succs中变量的寄存器不一致, 且各个succs的寄存器也不一致, 记录到 diffResolutionSet | |
block结束时变量的寄存器跟succs中变量的寄存器不一致, 但各个succs的寄存器一致, 记录到 sameResolutionSet | |
针对 sameResolutionSet | |
调用 resolveEdge(block, nullptr, ResolveSharedCritical, sameResolutionSet) | |
本地变量 fromVarToRegMap = getOutVarToRegMap(fromBlock->bbNum) | |
本地变量 toVarToRegMap = sharedCriticalVarToRegMap | |
本地变量 block = fromBlock | |
在fromBlock的结尾插入GT_COPY, 有可能插入fromReg=>toReg, 也可能插入fromReg=>stk, stk=>toReg | |
针对 diffResolutionSet | |
枚举 succs | |
集合 edgeResolutionSet = diffResolutionSet 和 succBlock->bbLiveIn 的交集 | |
调用 resolveEdge(block, succBlock, ResolveCritical, edgeResolutionSet) | |
本地变量 fromVarToRegMap = getOutVarToRegMap(fromBlock->bbNum) | |
本地变量 toVarToRegMap = sharedCriticalVarToRegMap | |
本地变量 block = compiler->fgSplitEdge(fromBlock, toBlock) | |
在 fromBlock 和 toBlock 之间插入一个新的block并返回 | |
在新block插入GT_COPY, 有可能插入fromReg=>toReg, 也可能插入fromReg=>stk, stk=>toReg | |
枚举 BasicBlock | |
如果 block 只有一个 preds | |
while uniquePredBlock->bbNum > bbNumMaxBeforeResolution | |
uniquePredBlock = uniquePredBlock->GetUniquePred(compiler) | |
调用 resolveEdge(uniquePredBlock, block, ResolveSplit, block->bbLiveIn) | |
本地变量 fromVarToRegMap = getOutVarToRegMap(fromBlock->bbNum) | |
本地变量 toVarToRegMap = sharedCriticalVarToRegMap | |
本地变量 block = toBlock | |
在toBlock的开头(phi后)插入GT_COPY, 有可能插入fromReg=>toReg, 也可能插入fromReg=>stk, stk=>toReg | |
如果 block 只有一个 succs, 并且该 succs 有多个 preds | |
调用 resolveEdge(block, succBlock, ResolveJoin, succBlock->bbLiveIn) | |
本地变量 fromVarToRegMap = getOutVarToRegMap(fromBlock->bbNum) | |
本地变量 toVarToRegMap = sharedCriticalVarToRegMap | |
本地变量 block = fromBlock | |
在fromBlock的结尾插入GT_COPY, 有可能插入fromReg=>toReg, 也可能插入fromReg=>stk, stk=>toReg | |
枚举大于 bbNumMaxBeforeResolution 的 BasicBlock | |
这些 BasicBlock 是resolution创建的, 有唯一的 preds 和 succs | |
获取 inVarToRegMaps 或 outVarToRegMaps 时可以直接看它的 preds 和 succs | |
本地变量 SplitEdgeInfo info = {predBBNum, succBBNum} | |
调用 getSplitBBNumToTargetBBNumMap()->Set(block->bbNum, info) | |
设置创建的block的bbNum到SplitEdgeInfo的索引 | |
枚举本地变量, 再次整理寄存器相关的属性 | |
如果 !isCandidateVar(varDsc) | |
设置 varDsc->lvRegNum = REG_STK | |
否则 | |
如果变量是参数且通过栈传入, 确认它不是dependently-promoted structs的字段(确保struct的本地变量的值一直合法) | |
如果 varDsc->lvRegNum == REG_STK || interval->isSpilled || interval->isSplit | |
设置 varDsc->lvRegister = false | |
如果除了 RefTypeExpUse 以外无其他的 refPosition | |
如果 varDsc->lvRefCnt == 0 | |
设置 varDsc->lvOnFrame = false // 虽然有jmp block但是变量已死, 不需要放在栈上 | |
否则 | |
设置 varDsc->lvOnFrame = true // 放在栈上以防万一需要读取它 | |
否则 | |
如果 !interval->isSpilled 则设置 varDsc->lvOnFrame = false | |
如果 firstRefPosition->registerAssignment == RBM_NONE || firstRefPosition->spillAfter | |
设置 varDsc->lvRegNum = REG_STK | |
否则 | |
设置 varDsc->lvRegNum = firstRefPosition->assignedReg() | |
否则 | |
设置 varDsc->lvRegister = true | |
设置 varDsc->lvOnFrame = false | |
标记变量可以不需要放在栈上, 可以一直存在寄存器 | |
调用 compiler->raMarkStkVars() | |
枚举需要本地变量 | |
如果本地变量是dependently-promoted structs的字段则跳到 ON_STK | |
如果 varDsc->lvRegister 且所有寄存器(部分本地变量需要2个寄存器) 都不是 REG_STK 则跳到 NOT_STK | |
对于无引用的变量(死变量), 如果变量的指针地址未被使用, 且不需要生成可除错的代码则跳到 NOT_STK | |
ON_STK: 设置 varDsc->lvOnFrame = true | |
NOT_STK: 设置 varDsc->lvFramePointerBased = codeGen->isFramePointerUsed() // 是否需要frame pointer(x64不需要) | |
调用 recordMaxSpill() | |
针对 maxSpill[int] 和 maxSpill[float] 调用 compiler->tmpPreAllocateTemps(var_types(i), maxSpill[i]) | |
新建 TempDsc 并添加到 tmpFree[slot], slot是该类型需要的大小对应的索引值 | |
PHASE_RA_ASSIGN_VARS | |
这个步骤只有 LEGACY_BACKEND 会使用, 是 "classic" register allocation | |
因为coreclr在linux x64上不会启用LEGACY_BACKEND, 这个函数无法做具体的分析 | |
raAssignVars | |
调用 codeGen->regSet.rsClearRegsModified() | |
清空 rsModifiedRegsMask = RBM_NONE | |
调用 raEnregisterVarsStackFP() | |
调用 raInitStackFP() | |
重置 raLclRegIntfFloat[x] = 空集合 // 寄存器会干涉到的float变量集合 | |
重置 optAllFPregVars = 空集合 // 实际可以放到寄存器的float变量集合 | |
重置 optAllNonFPvars = 空集合 // 已跟踪的非float变量 | |
重置 optAllFloatVars = 空集合 // 已跟踪的float变量 | |
重置 raCntStkStackFP = 0 // 未编入寄存器的double变量的引用计数合计 | |
重置 raCntWtdStkDblStackFP = 0 // 未编入寄存器的double非参数变量的引用计数合计 | |
重置 raCntStkParamDblStackFP = 0 // 未编入寄存器的double参数变量的引用计数合计 | |
重置 raMaskDontEnregFloat = 空集合 // 不能使用寄存器保存的float变量集合 | |
添加所有已跟踪的float变量到optAllFloatVars | |
添加所有已跟踪的非float变量到optAllNonFPvars | |
调用 raEnregisterVarsPrePassStackFP() | |
清空 raHeightsStackFP = 空数组 // [本地变量][浮点数堆栈的深度] = 权重 | |
清空 raPayloadStackFP = 空数组 // [本地变量] = 权重 | |
本地数组 FPVars = 所有跟踪的float变量的数组 | |
枚举 BasicBlock | |
本地集合 blockLiveOutFloats = block->bbLiveOut & optAllFloatVars | |
如果 block 是 BBJ_COND 或者 BBJ_SWITCH, 且有例外处理的handler | |
标记集合 raMaskDontEnregFloat |= block->bbLiveOut | optAllFloatVars | |
本地集合 liveSet = block->bbLiveIn | |
枚举 stmt | |
按执行顺序枚举stmt中的tree | |
如果 tree 是 GT_CALL, 调用 raAddPayloadStackFP(liveSet, block->getBBWeight(this) * 2) | |
增加在liveSet中的变量在raPayloadStackFP中的权重 | |
如果 tree 是 GT_CAST, 并且是 long => double, 减少本地变量的 lvRefCntWtd | |
如果同一stmt中有不同的 tree->gtFPlvl, 则增加 raHeightsStackFP[FPVars[i]][height - 1] | |
如果函数中有jmp指令 | |
标记 raMaskDontEnregFloat |= optAllFloatVars (全部float变量都不用寄存器保存) | |
设置前两个变量干扰所有float变量 | |
设置 raLclRegIntfFloat[REG_FPV0] = optAllFloatVars | |
设置 raLclRegIntfFloat[REG_FPV1] = optAllFloatVars | |
构建一个数组 fpLclFPVars, 包含所有跟踪的浮点数本地变量 | |
排序 fpLclFPVars, 权重更高或者引用计数更多的变量排在前面 | |
枚举排序后的 fpLclFPVars | |
如果变量的权重不够高(balance<0), 跳过 | |
调用 raRegForVarStackFP(varDsc->lvVarIndex), 返回 REG_FPNONE 时跳过 | |
查找所有float寄存器, 如果 raLclRegIntfFloat[寄存器] 不包含该变量则返回寄存器 | |
如果 lvaIsFieldOfDependentlyPromotedStruct(varDsc) // 变量由struct提升但仍然需要struct变量本身 | |
跳过循环 | |
设置 varDsc->lvRegister = true | |
设置 varDsc->lvRegNum = reg | |
添加本地变量到 optAllFPregVars | |
更新 raLclRegIntfFloat[reg] 集合 | |
添加本地变了干涉的变量 lvaVarIntf[varIndex] 添加到这个集合中 | |
调用 raUpdateHeightsForVarsStackFP(intfFloats) | |
右移 raHeightsStackFP[本地变量][栈深度] 中记录的权重 (因为多push了一个变量?) | |
记录最大的 intfFloats 数量 (maxRegVars) | |
设置 tmpDoubleSpillMax += maxRegVars | |
调用 raEnregisterVarsPostPassStackFP() | |
枚举 BasicBlock | |
本地变量 lastlife = block->bbLiveIn | |
枚举 stmt | |
按执行顺序枚举stmt中的tree | |
如果节点是 GT_LCL_VAR, 调用 raSetRegLclBirthDeath(tree, lastlife, false) | |
如果变量不在 optAllFPregVars 中则不继续处理 | |
改变 tree 的类型为 GT_REG_VAR | |
标记 tree->gtFlags |= livenessFlags | |
设置 tree->gtRegNum = varDsc->lvRegNum | |
设置 tree->gtRegVar.gtRegNum = varDsc->lvRegNum | |
设置 tree->gtRegVar.SetLclNum(lclnum) | |
如果 tree->gtFlags 不包含 GTF_VAR_DEATH 但包含 GTF_VAR_DEF | |
标记 tree->gtFlags |= GTF_REG_BIRTH | |
如果节点是 GT_CALL, 是unmanaged call, 且不使用pinvoke helper | |
从 lastlife 中删除变量 info.compLvFrameListRoot | |
确保 lastlife == block->bbLiveOut | |
调用 raGenerateFPRefCounts() | |
枚举本地变量 | |
如果变量是double或者lvStructDoubleAlign, 且未设置 varDsc->lvRegister | |
添加 raCntStkStackFP += varDsc->lvRefCnt | |
如果变量是参数 | |
添加 raCntStkParamDblStackFP += varDsc->lvRefCnt | |
否则 | |
添加 raCntWtdStkDblStackFP += varDsc->lvRefCntWtd | |
调用 rpPredictRegUse() | |
这个是 Legacy Backend 使用的寄存器分配函数, 算法跟RyuJIT的LSRA不一样 | |
本地变量 regAvail = 当前可以使用的寄存器 | |
清空 raLclRegIntf[寄存器] | |
清空 lvaVarPref[本地变量] | |
循环 | |
本地变量 regUsed = rpPredictAssignRegVars(regAvail) | |
枚举按refCount排序的本地变量, 为本地变量分配寄存器并 | |
对于不能存入寄存器的变量, 统计 varDsc->lvRefCntWtd 到 rpStkPredict | |
rpStkPredict的值越小代表分配越好 | |
对于小函数(stmt小于12的函数), 继续下面的处理 | |
如果所有变量都成功分配了寄存器, 跳出循环 (找不到更好的分配) | |
调用 rpRecordPrediction() | |
如果 rpStkPredict < rpBestRecordedStkPredict | |
记录当前本地变量的寄存器分配状况到 rpBestRecordedPrediction | |
记录 rpBestRecordedStkPredict = rpStkPredict | |
如果下面的处理已经执行超过1次 | |
如果上一次的 rpStkPredict 小于这一次的 rpStkPredict * 2 则跳出循环 | |
如果 rpStkPredict < rpPasses * 8 则跳出循环 | |
如果 rpPasses >= (rpPassesMax/*6*/ - 1) 则跳出循环 | |
清空 raLclRegIntf[寄存器] | |
枚举 BasicBlock | |
枚举 stmt | |
清空 rpLastUseVars | |
清空 rpUseInPlace | |
调用 rpPredictTreeRegUse(stmt->gtStmt.gtStmtExpr, PREDICT_NONE, RBM_NONE, RBM_NONE) | |
3000行的函数, 不具体分析 | |
根据tree的类型分配 tree->gtUsedRegs | |
根据 spillMask = regMask & lockedRegs, 如果有 spillMask 则分配新的寄存器用于reload lockedRegs | |
如果 rpPredictSpillCnt > tmpIntSpillMax | |
设置 tmpIntSpillMax = rpPredictSpillCnt | |
调用 rpUseRecordedPredictionIfBetter() | |
如果 rpStkPredict > rpBestRecordedStkPredict | |
设置 lvaTable[k].lvRegister 到历史最佳的值 | |
调用 lvaTable[k].SetRegNum(历史最佳的值) | |
调用 lvaTable[k].SetOtherReg(历史最佳的值) | |
调用 codeGen->regSet.rsSetRegsModified(regUsed) 标记在函数中使用的寄存器集合 | |
调用 raMarkStkVars(), 同上 | |
枚举本地变量, 查找从struct promoted但从未被引用, 且不是参数的字段 | |
设置它们的类型为TYP_INT并且不在栈上, 让GC和lvMustInit忽略它们 | |
GenTree怎么转换成汇编 | |
续上面 compCompile 的流程 | |
compCompile (3参数, compiler.cpp:4078) | |
CodeGen::genGenerateCode | |
PHASE_GENERATE_CODE | |
调用 genPrepForCompiler() | |
设置 gcInfo.gcTrkStkPtrLcls = 本地变量中已跟踪的, 但是仍然存在于栈上的变量的集合 | |
设置 compiler->raRegVarsMask = 本地变量中整个生命周期都在寄存器上的变量的集合 | |
设置 genLastLiveSet = 空集合 | |
设置 genLastLiveMask = RBM_NONE | |
调用 getEmitter()->Init() | |
设置 emitPrevGCrefVars = 空集合 | |
设置 emitInitGCrefVars = 空集合 | |
设置 emitThisGCrefVars = 空集合 | |
如果是 RyuJIT | |
调用 genFinalizeFrame() | |
调用 compiler->m_pLinearScan->recordVarLocationsAtStartOfBB(compiler->fgFirstBB) | |
枚举 bbLiveIn, 设置本地变量的 varDsc->lvRegNum = inVarToRegMaps[block][variable] | |
因为变量在不同的block中分配的寄存器有可能不一样 | |
调用 genCheckUseBlockInit() | |
设置 genInitStkLclCnt = 需要清0的, 在栈上(未跟踪)的本地变量的数量 | |
设置 largeGcStructs = 大小大于指针大小*3的变量的数量, 最大值为5 | |
设置 genUseBlockInit = genInitStkLclCnt > (largeGcStructs + 4) | |
如果需要清零的本地数量较多, 应该整块清0而不是一个个清 (在genZeroInitFrame中使用) | |
根据 genUseBlockInit 标记有哪些变量会在这个函数中修改 | |
如果是x86且使用了 compiler->compTailCallUsed, 则设置 RBM_INT_CALLEE_SAVED 已修改 (需要保存原值) | |
如果是arm且帧大小大于2页, 则设置 VERY_LARGE_FRAME_SIZE_REG_MASK 已修改 (需要使用循环来访问栈上的页, 所以需要几个寄存器) | |
如果需要生成可debug的代码则设置部分寄存器已修改 | |
如果函数中调用了非托管函数则设置 RBM_INT_CALLEE_SAVED & ~RBM_FPBASE 已修改 | |
设置 compiler->compCalleeRegsPushed = 需要在函数进入时备份(push到栈)的寄存器集合, 已修改的寄存器 & RBM_CALLEE_SAVED | |
调用 compiler->lvaAssignFrameOffsets(Compiler::FINAL_FRAME_LAYOUT) | |
会分两步计算 | |
第一步设置一个虚拟的初始偏移值0, 然后以这个0为基准设置各个变量的偏移值, 参数为正数本地变量为负数 | |
第二步根据是否使用frame pointer调整各个偏移值 | |
调用 lvaAssignVirtualFrameOffsetsToArgs() | |
本地变量 argOffs = 0, 这个变量记录当前参数的偏移值 | |
参数在一些平台上会从左到右(low=>high)传递, 在一些平台会相反, x86是从右到左传递(low<=high) | |
调用 lvaUpdateArgsWithInitialReg() | |
更新通过寄存器传入的参数的 varDsc->lvRegNum | |
如果需要多个寄存器则 | |
varDsc->lvRegNum = genRegPairLo(varDsc->lvArgInitRegPair) | |
varDsc->lvOtherReg = genRegPairHi(varDsc->lvArgInitRegPair) | |
否则 | |
varDsc->lvRegNum = varDsc->lvArgInitReg | |
如果编译的函数非static(有this) | |
设置 argOffs = lvaAssignVirtualFrameOffsetToArg(lclNum++, REGSIZE_BYTES, argOffs) | |
这个函数有两个实现, 一个是Unix x64(System V)上的实现, 一个是其他的实现 | |
根据参数是否由寄存器传入, 和argOffs更新 varDsc->lvStkOffs | |
更新argOffs | |
如果函数需要返回struct(有hidden buffer) | |
设置 argOffs = lvaAssignVirtualFrameOffsetToArg(lclNum++, REGSIZE_BYTES, argOffs), 同上 | |
如果函数需要传入generic context | |
设置 argOffs = lvaAssignVirtualFrameOffsetToArg(lclNum++, REGSIZE_BYTES, argOffs), 同上 | |
如果函数有varargs | |
设置 argOffs = lvaAssignVirtualFrameOffsetToArg(lclNum++, REGSIZE_BYTES, argOffs), 同上 | |
枚举本地变量, ARM需要先枚举pre spilled然后再枚举non pre-spilled | |
设置 argOffs = lvaAssignVirtualFrameOffsetToArg(lclNum++, argumentSize, argOffs), 同上 | |
调用 lvaAssignVirtualFrameOffsetsToLocals() | |
计算栈上的本地变量, 包括临时变量距离virtual 0的偏移值, 这里算出的偏移值都会是负数 | |
本地变量 stkOffs = 0, 表示当前的偏移值 | |
计算过程中还会调用 lvaIncrementFrameSize 修改 compLclFrameSize | |
这个值表示需要用sub减去的栈大小, 使用push影响的大小不会算入这个值里面 | |
如果是x86/amd64, 设置 stkOffs -= sizeof(void*), 这是返回地址 | |
部分平台(x86)需要double align, 标记本地变量 mustDoubleAlign = true | |
如果是64位 | |
如果使用了varargs | |
设置 stkOffs -= MAX_REG_ARG * REGSIZE_BYTES // 保存所有整数寄存器 | |
如果使用了frame pointer | |
设置 stkOffs -= (compCalleeRegsPushed - 2) * REGSIZE_BYTES // 这里不减去FP和LR的大小 | |
否则 | |
设置 stkOffs -= compCalleeRegsPushed * REGSIZE_BYTES | |
否则 | |
设置 stkOffs -= compCalleeRegsPushed * REGSIZE_BYTES | |
如果是64位 | |
判断有多少浮点数寄存器需要保存(compCalleeFPRegsSavedMask), 把它们的大小减到 stkOffs | |
如果是arm, 并且启用了EH Funclet | |
需要在所有变量之前先分配 lvaPSPSym | |
设置 stkOffs = lvaAllocLocalAndSetVirtualOffset(lvaPSPSym, TARGET_POINTER_SIZE, stkOffs) | |
调用 lvaIncrementFrameSize(size) 增加 compLclFrameSize (需要使用sub分配的大小) | |
设置 stkOffs -= size | |
设置 lvaTable[lclNum].lvStkOffs = stkOffs | |
如果 mustDoubleAlign | |
如果 stkOffs 未向8对齐 | |
调用 lvaIncrementFrameSize(TARGET_POINTER_SIZE); | |
设置 stkOffs -= TARGET_POINTER_SIZE | |
如果是同步函数则需要分配monitor | |
设置 stkOffs = lvaAllocLocalAndSetVirtualOffset(lvaMonAcquired, lvaLclSize(lvaMonAcquired), stkOffs), 同上 | |
如果函数需要安全检查 | |
设置 stkOffs = lvaAllocLocalAndSetVirtualOffset(lvaSecurityObject, TARGET_POINTER_SIZE, stkOffs), 同上 | |
如果函数使用了localloc(栈上分配空间) | |
设置 stkOffs = lvaAllocLocalAndSetVirtualOffset(lvaLocAllocSPvar, TARGET_POINTER_SIZE, stkOffs), 同上 | |
如果需要保持generic context存活(lvaReportParamTypeArg), 或者如果需要保持this存活 | |
调用 lvaIncrementFrameSize(TARGET_POINTER_SIZE); | |
设置 stkOffs -= TARGET_POINTER_SIZE | |
如果需要shadow sp | |
需要保持generic context存活, 如果未保持则加上(减去)TARGET_POINTER_SIZE的大小 | |
设置 stkOffs = lvaAllocLocalAndSetVirtualOffset( | |
lvaShadowSPslotsVar, lvaLclSize(lvaShadowSPslotsVar), stkOffs), 同上 | |
如果需要 GS Security Cookie 且 compGSReorderStackLayout | |
设置 stkOffs = lvaAllocLocalAndSetVirtualOffset( | |
lvaGSSecurityCookie, lvaLclSize(lvaGSSecurityCookie), stkOffs), 同上 | |
如果 !compGSReorderStackLayout && !codeGen->isFramePointerUsed() | |
预先分配内部使用的临时变量(tmpLists) | |
设置 stkOffs = lvaAllocateTemps(stkOffs, mustDoubleAlign) | |
按以下顺序分配本地变量和临时变量 | |
非指针类型的本地变量 (ALLOC_NON_PTRS) | |
指针类型的本地变量 (ALLOC_PTRS) | |
指针类型的临时变量 (ALLOC_UNSAFE_BUFFERS_WITH_PTRS) | |
非指针类型的临时变量 (ALLOC_UNSAFE_BUFFERS) | |
按以上的顺序 | |
枚举本地变量, 判断是否符合要求 | |
符合要求时设置 stkOffs = lvaAllocLocalAndSetVirtualOffset(lclNum, lvaLclSize(lclNum), stkOffs), 同上 | |
如果需要 GS Security Cookie 且 !compGSReorderStackLayout | |
(compGSReorderStackLayout决定是否要放在前面) | |
设置 stkOffs = lvaAllocLocalAndSetVirtualOffset( | |
lvaGSSecurityCookie, lvaLclSize(lvaGSSecurityCookie), stkOffs), 同上 | |
如果之前未预先分配内部使用的临时变量(tmpLists) | |
设置 stkOffs = lvaAllocateTemps(stkOffs, mustDoubleAlign) | |
如果有 lvaStubArgumentVar | |
它需要放在 lvaInlinedPInvokeFrameVar 右边 | |
设置 stkOffs = lvaAllocLocalAndSetVirtualOffset( | |
lvaStubArgumentVar, lvaLclSize(lvaStubArgumentVar), stkOffs), 同上 | |
如果有 lvaInlinedPInvokeFrameVar | |
设置 stkOffs = lvaAllocLocalAndSetVirtualOffset( | |
lvaInlinedPInvokeFrameVar, lvaLclSize(lvaInlinedPInvokeFrameVar), stkOffs), 同上 | |
如果是x64, 并且启用了EH Funclet | |
设置 stkOffs = lvaAllocLocalAndSetVirtualOffset( | |
lvaInlinedPInvokeFrameVar, lvaLclSize(lvaInlinedPInvokeFrameVar), stkOffs), 同上 | |
如果是arm64, 并且使用了frame pointer | |
设置 stkOffs -= 2 * REGSIZE_BYTES // 保存FP和LR, 为什么在本地变量左边? | |
如果需要分配空间返回struct (lvaOutgoingArgSpaceSize > 0, 一般是caller分配) | |
设置 stkOffs = lvaAllocLocalAndSetVirtualOffset( | |
lvaOutgoingArgSpaceVar, lvaLclSize(lvaOutgoingArgSpaceVar), stkOffs) | |
确保 compLclFrameSize = - stkOffs + pushedCount * 指针大小 | |
compLclFrameSize 是需要手动sub分配的栈空间大小, pushedCount是使用push分配栈空间的次数 | |
调用 lvaAlignFrame() | |
如果是x64和arm64 | |
保证 compLclFrameSize 向8对齐 | |
调用 lvaFixVirtualFrameOffsets() | |
根据平台和是否使用frame pointer计算delta | |
例如linux x64上的delta是16, 包含return address和frame pointer(ebp) | |
枚举本地变量 | |
修改 varDsc->lvStkOffs += delta | |
枚举内部使用的临时变量 | |
调用 varDsc->lvStkOffs += delta (tdOffs += delta) | |
设置 lvaCachedGenericContextArgOffs += delta | |
调用 lvaAssignFrameOffsetsToPromotedStructs() | |
枚举是 lvIsStructField 的本地变量 (从struct提升的变量) | |
如果是 independent, 则不处理 (偏移值已在前面处理过) | |
否则设置 varDsc->lvStkOffs = parentvarDsc->lvStkOffs + varDsc->lvFldOffset | |
设置 getEmitter()->emitMaxTmpSize = compiler->tmpSize // 内部使用的临时变量的合计大小, 准确值 | |
本地变量 maxTmpSize = compiler->tmpSize // 准确值 | |
否则 (Legacy Backend) | |
本地变量 maxTmpSize += (compiler->tmpDoubleSpillMax * sizeof(double)) + (compiler->tmpIntSpillMax * sizeof(int)) // 估算值 | |
本地变量 lclSize = compiler->lvaFrameSize(Compiler::TENTATIVE_FRAME_LAYOUT) | |
设置 compCalleeRegsPushed = 需要使用push保存的寄存器数量 | |
调用 lvaAssignFrameOffsets(curState), 同上 // TENTATIVE_FRAME_LAYOUT | |
返回 compLclFrameSize + calleeSavedRegMaxSz | |
调用 getEmitter()->emitBegFN(isFramePointerUsed(), maxTmpSize) | |
初始化成员 | |
设置 emitCurIGfreeBase = nullptr // 当前ig的instrDesc数组的起始地址 | |
设置 emitIGbuffSize = 0 // emitCurIGfreeEndp - emitCurIGfreeBase 的值 | |
设置 emitHasFramePtr = hasFramePtr // 是否有frame pointer | |
设置 emitMaxTmpSize = maxTmpSize // 内部使用的临时变量的合计大小 | |
设置 emitEpilogSize = 0 // epilog 除去 ret 的大小 | |
设置 emitEpilogCnt = 0 // epilog的数量, 1表示只有一个ret, 不包含funclet的epilog | |
调用 emitExitSeqBegLoc.Init() // 用于记录 ret 前面的位置, 在 emitEpilogSize 和 emitExitSeqSize 的中间 | |
设置 emitExitSeqSize = INT_MAX // ret 的大小, emitEpilogSize + emitExitSeqSize 等于整个epilog ig的大小 | |
设置 emitPlaceholderList = emitPlaceholderLast = nullptr // 预留的ig, 用于生成prolog和epilog和funclet prolog和funclet epilog | |
设置 emitJumpList = emitJumpLast = nullptr // 函数中的跳转指令的列表, 可以用于优化long jmp到short jmp | |
设置 emitCurIGjmpList = nullptr // 当前ig的跳转指令的列表, 会归并到emitJumpList | |
设置 emitFwdJumps = false // TODO | |
设置 emitNoGCIG = false // 下次调用emitGenIG添加新ig时会标记新ig为IGF_NOGCINTERRUPT, 表示不可中断 | |
设置 emitForceNewIG = false // 下次调用emitAllocInstr分配下一条指令的时候强制新建新的ig, 下一条指令必须在新ig里面 | |
设置 emitThisGCrefRegs = RBM_NONE // 当前存有gcref的寄存器集合 | |
设置 emitInitGCrefRegs = RBM_NONE // 当前的ig开始时的emitThisGCrefRegs, 和prev可能不一样如果变量在当前ig不存活 | |
设置 emitPrevGCrefRegs = RBM_NONE // 上一个ig结束时的emitThisGCrefRegs | |
设置 emitThisByrefRegs = RBM_NONE // 当前存有byref的寄存器集合 | |
设置 emitInitByrefRegs = RBM_NONE // 当前的ig开始时的emitThisByrefRegs, 和prev可能不一样如果变量在当前ig不存活 | |
设置 emitPrevByrefRegs = RBM_NONE // 上一个ig结束时的emitThisByrefRegs | |
prev和init只用于比对是否需要保存这些集合到ig->igData, 在生成汇编的时候不会使用 | |
并且实际只有emitPrevGCrefVars和emitInitGCrefVars会使用 | |
设置 emitForceStoreGCState = false // 是否需要强制保存emitInitGCrefVars到ig->igData | |
设置 emitGCrFrameOffsMin = // stack frame上的gc ref的最小地址 | |
emitGCrFrameOffsMax = // stack frame上的gc ref的最大地址 | |
emitGCrFrameOffsCnt = 0 // stack frame上的gc ref数量 | |
设置 emitIGlist = emitIGlast = nullptr // ig列表 | |
设置 emitCurCodeOffset = 0 // 当前ig在函数中的偏移值, 调用emitInitIG时会分配到ig->igOffs | |
设置 emitFirstColdIG = nullptr // 属于cold code的第一个ig | |
设置 emitTotalCodeSize = 0 // 所有 ig->igSize 的合计 | |
设置 emitInsCount = 0 // 整个函数的指令数量, 在调用 emitAllocInstr 时增加 | |
设置 emitCurStackLvl = 0 | |
用于跟踪当前的堆栈深度, 已分配本地变量以后的rsp为基准 | |
在两处地方使用 | |
一处是构建ig的时候, 在 emitIns_ARR_R 等函数中计算 | |
一处是从ig构建汇编的时候, 由 emitOutputInstr 调用 emitStackPush 和 emitStackPop | |
历史最大值会保存在 emitMaxStackDepth | |
同时还会保存在各个 ig 的 ig->igStkLvl // 大部分情况下是0 | |
设置 emitMaxStackDepth = 0 // emitCurStackLvl的历史最大值 | |
设置 emitCntStackDepth = sizeof(int) // emitCurStackLvl增加和减少时使用的大小 | |
设置 emitDataSecCur = nullptr // TODO | |
设置 memset(&emitConsDsc, 0, sizeof(emitConsDsc)) // TODO | |
设置 emitNxtIGnum = 1 // TODO | |
创建一个新的 insGroup, 用于保存prolog | |
设置 emitPrologIG = emitIGlist = emitIGlast = emitCurIG = ig = emitAllocIG() | |
本地变量 insGroup = (insGroup*)emitGetMem(sizeof(insGroup)) // 与malloc作用一样 | |
调用 emitInitIG(ig) | |
设置 ig->igNum = emitNxtIGnum++ // instruct group的序号 | |
设置 ig->igOffs = emitCurCodeOffset // 当前ig在函数中的偏移值 | |
设置 ig->igFuncIdx = emitComp->compCurrFuncIdx // 对应的函数, 0表示主函数, 其他值表示funclet | |
设置 ig->igFlags = 0 // instruction group的标记 | |
设置 ig->igSize = 0 // group中的代码的大小, 单位是byte | |
设置 ig->igGCregs = RBM_NONE // 哪些寄存器会包含gc ref(对象指针) | |
设置 ig->igInsCnt = 0 // instruction的数量 | |
返回 ig | |
设置 emitLastIns = nullptr // TODO | |
设置 ig->igNext = nullptr // TODO | |
创建一个新的 insGroup, 用于保存实际属于函数的代码 | |
调用 emitNewIG() | |
本地变量 ig = emitAllocAndLinkIG() | |
本地变量 ig = emitAllocIG(), 同上 | |
调用 emitInsertIGAfter(emitCurIG, ig) | |
设置 emitCurIG->igNext 和更新 emitIGlast | |
设置 ig->igFlags |= (emitCurIG->igFlags & IGF_PROPAGATE_MASK) // 传播当前ig的部分标记 | |
设置 emitCurIG = ig | |
返回 ig | |
调用 emitGenIG(ig) | |
准备好ig, 让其可以存放代码 | |
设置 emitCurIG = ig | |
设置 ig->igStkLvl = emitCurStackLvl // overflow时提示 Too many arguments pushed on stack | |
如果 emitNoGCIG | |
设置 ig->igFlags |= IGF_NOGCINTERRUPT // GC不能中断这个ig里面的指令 | |
设置 emitCurIGinsCnt = 0 | |
设置 emitCurIGsize = 0 | |
如果 emitCurIGfreeBase == nullptr | |
// 用于保存[instrDesc, ...]的缓冲区 | |
// 供所有ig使用, 完成后会复制到另一份内存, 并且重设 emitCurIGfreeNext | |
设置 emitIGbuffSize = SC_IG_BUFFER_SIZE | |
设置 emitCurIGfreeBase = (BYTE*)emitGetMem(emitIGbuffSize) | |
设置 emitCurIGfreeNext = emitCurIGfreeBase // 写入下一个instrDesc的地址 | |
设置 emitCurIGfreeEndp = emitCurIGfreeBase + emitIGbuffSize // 缓冲区的结尾地址 | |
调用 genCodeForBBlist() | |
这个函数有 xarch(x86和x64), arm, arm64 版本, 这里写的是 xarch 版本 | |
调用 genPrepForEHCodegen() | |
因为需要计算各个eh region的大小, 需要标记eh region中第一个block和最后一个block的下一个block有label | |
枚举eh table | |
确保 ebdTryBeg->bbFlags & BBF_HAS_LABEL | |
确保 ebdHndBeg->bbFlags & BBF_HAS_LABEL | |
标记 ebdTryLast->bbNext->bbFlags |= BBF_HAS_LABEL | |
标记 ebdHndLast->bbNext->bbFlags |= BBF_HAS_LABEL | |
如果有任何finally且是x64 | |
查找 BBJ_CALLFINALLY 的 block 并标记它的下一个block的 bbToLabel->bbFlags |= BBF_HAS_LABEL | |
如果isBBCallAlwaysPair(下一个block是BBJ_ALWAYS)则标记下下个block | |
调用 regSet.rsSpillBeg() | |
调用 rsSpillChk() | |
确保 m_rsCompiler->tmpGetCount == 0 | |
确保所有寄存器对应的 rsSpillDesc[reg] 是 nullptr | |
如果启用了 DEBUGGING_SUPPORT | |
调用 siInit() | |
设置 siOpenScopeList.scNext = nullptr // TODO | |
设置 siOpenScopeLast = &siOpenScopeList // TODO | |
设置 siScopeLast = &siScopeList // TODO | |
设置 siScopeCnt = 0 // TODO | |
设置 siLastLife = 空集合 // TODO | |
设置 siLatestTrackedScopes[0~lclMAX_TRACKED] = nullptr // TODO | |
调用 compiler->compResetScopeLists() | |
设置 compNextEnterScope = compNextExitScope = 0 // TODO | |
如果 compiler->fgHasSwitch | |
标记 compiler->fgFirstBB->bbFlags |= BBF_JMP_TARGET // switch table要求fist block有一个label(以取得offset) | |
设置 genPendingCallLabel = nullptr | |
调用 gcInfo.gcRegPtrSetInit() | |
设置 gcRegGCrefSetCur = 0 // 当前在寄存器上的gcref的集合, 见gcUpdateForRegVarMove | |
设置 gcRegByrefSetCur = 0 // 当前在寄存器上的byref的集合, 见gcUpdateForRegVarMove | |
设置 gcRegPtrList = gcRegPtrLast = nullptr // TODO | |
调用 gcInfo.gcVarPtrSetInit() | |
设置 gcVarPtrSetCur = 空集合 // 当前在stack上的gcref和byref, 见gcUpdateForRegVarMove | |
设置 gcVarPtrList = gcVarPtrLast = nullptr // TODO | |
枚举通过寄存器传入的函数参数 | |
标记 regTracker.rsTrackRegLclVar(varDsc->lvRegNum, varNum) // 表示这些寄存器已经持有变量 | |
设置 rsRegValues[reg].rvdKind = RV_LCL_VAR | |
设置 rsRegValues[reg].rvdLclVarNum = var | |
设置 compiler->compCurLife = 空集合 | |
枚举 BasicBlock | |
查找哪些寄存器在进入block时会存有变量的值 | |
调用 regSet.ClearMaskVars() | |
设置 gcInfo.gcRegGCrefSetCur = RBM_NONE | |
设置 gcInfo.gcRegByrefSetCur = RBM_NONE | |
调用 compiler->m_pLinearScan->recordVarLocationsAtStartOfBB(block), 同上 | |
调用 genUpdateLife(block->bbLiveIn) | |
调用 compiler->compUpdateLife</*ForCodeGen*/ true>(newLife) | |
如果 compCurLife != newLife, 调用 compChangeLife<ForCodeGen>(newLife) | |
设置 deadSet = compCurLife & ~newLife | |
设置 bornSet = newLife & ~compCurLife | |
设置 compCurLife = newLife | |
枚举 deadSet | |
如果变量在寄存器 | |
如果是 isGCRef | |
设置 codeGen->gcInfo.gcRegGCrefSetCur &= ~regMask // 哪些寄存器有gcref | |
否则如果是 isByRef | |
设置 codeGen->gcInfo.gcRegByrefSetCur &= ~regMask // 哪些寄存器有byref | |
调用 codeGen->genUpdateRegLife(varDsc, false /*isBorn*/, true /*isDying*/) | |
根据isDying或者isBorn调用 RemoveMaskVars 或者 AddMaskVars | |
也就是更新当前CodeGen中的 regSet->rsMaskVars, 代表哪些寄存器当前存活 | |
否则如果是 isGCRef 或者 codeGen | |
从 codeGen->gcInfo.gcVarPtrSetCur 中删除对应的 deadVarIndex // 在栈上的集合 | |
枚举 bornSet | |
如果变量在寄存器 | |
从 codeGen->gcInfo.gcVarPtrSetCur 中删除对应的 bornVarIndex // 在栈上的集合 | |
如果是 isGCRef | |
设置 codeGen->gcInfo.gcRegGCrefSetCur |= regMask // 哪些寄存器有gcref | |
如果是 isByRef | |
设置 codeGen->gcInfo.gcRegByrefSetCur |= regMask // 哪些寄存器有byref | |
否则如果 lvaIsGCTracked(varDsc) | |
添加 bornVarIndex 到 codeGen->gcInfo.gcVarPtrSetCur // 在栈上的集合 | |
设置 regSet.rsMaskVars = bbLiveIn 中保存在寄存器的变量的寄存器集合 | |
调用 gcInfo.gcMarkRegSetGCref(rsMaskVars中是ref的寄存器的集合) | |
调用 gcInfo.gcMarkRegSetByref(rsMaskVars中是byref的寄存器的集合) | |
如果block是catch并且有使用GT_CATCH_ARG的, 表示会传入一个Exception对象 | |
标记 gcInfo.gcMarkRegSetGCref(RBM_EXCEPTION_OBJECT) // ex会在哪个寄存器传进来 | |
处理 eh funclet | |
调用 genUpdateCurrentFunclet(block) | |
如果 block 是 BBF_FUNCLET_BEG | |
调用 compiler->funSetCurrentFunc(compiler->funGetFuncIdx(block)) | |
设置 compCurrFuncIdx = (unsigned short)funcIdx // 当前函数的序号 | |
funGetFuncIdx会返回block对应的hbtab的ebdFuncIndex, 而ebdFuncIndex在fgCreateFunclets中设置 | |
如果当前 block 是 loop head | |
调用 getEmitter()->emitLoopAlign() | |
插入一个大小为15的INS_align, 目的是为了让下一条指令跟16对齐, 后面会转换为nop | |
设置 emitCurIGsize += 15 | |
如果 block 是 BBF_JMP_TARGET 或者 BBF_HAS_LABEL | |
设置 block->bbEmitCookie = getEmitter()->emitAddLabel( | |
gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, | |
/*isFinally*/ block->bbFlags & BBF_FINALLY_TARGET) | |
如果当前的 ig 非空(emitCurIGnonEmpty), 则创建新的 ig(调用 emitNxtIG(/* emitAdd */ false)) | |
调用 emitSavIG(emitAdd) | |
计算 ig 的代码大小 sz = emitCurIGfreeNext - emitCurIGfreeBase | |
计算需要分配的内存大小 gs = roundUp(sz) // sizeof(size_t) | |
如果 !(ig->igFlags & IGF_EMIT_ADD) | |
如果 emitForceStoreGCState 或者 emitPrevGCrefVars != emitInitGCrefVars | |
表示 gcrefvars 有变化, 需要保存 | |
标记 ig->igFlags |= IGF_GC_VARS | |
设置 gs += sizeof(VARSET_TP) | |
标记 ig->igFlags |= IGF_BYREF_REGS | |
设置 gs += sizeof(int) | |
获取一份内存 id = (BYTE*)emitGetMem(gs) | |
如果 ig->igFlags & IGF_BYREF_REGS | |
复制 (unsigned int)emitInitByrefRegs 到 id, 并且id增加sizeof(unsigned int) | |
如果 ig->igFlags & IGF_GC_VARS | |
复制 (VARSET_TP)emitInitGCrefVars 到 id, 并且id增加sizeof(VARSET_TP) | |
设置 ig->igData = id | |
复制 emitCurIGfreeBase ~ sz 到 id | |
设置 ig->igInsCnt = (BYTE)emitCurIGinsCnt // 指令数量 | |
设置 ig->igSize = (unsigned short)emitCurIGsize // 指令大小合计(byte) | |
设置 emitCurCodeOffset += emitCurIGsize // 下一个ig在函数中的偏移值 | |
如果 !(ig->igFlags & IGF_EMIT_ADD) | |
设置 ig->igGCregs = (regMaskSmall)emitInitGCrefRegs // 进入ig时包含gcref的寄存器集合 | |
如果 !emitAdd | |
设置 emitPrevGCrefVars = emitThisGCrefVars // 上一个ig结束时包含gcref的栈变量集合 | |
设置 emitPrevGCrefRegs = emitThisGCrefRegs // 上一个ig结束时包含gcref的寄存器集合 | |
设置 emitPrevByrefRegs = emitThisByrefRegs // 上一个ig结束时包含byref的寄存器集合 | |
设置 emitForceStoreGCState = false | |
如果有 emitCurIGjmpList // 当前的ig里面的jmp列表 | |
把 emitCurIGjmpList 移动到全局的 emitJumpList, 并设置 emitJumpLast | |
移动后 emitCurIGjmpList 会等于 nullptr | |
设置最后一条指令 | |
设置 emitLastIns = (instrDesc*)((BYTE*)id + ((BYTE*)emitLastIns - (BYTE*)emitCurIGfreeBase)) | |
设置 emitCurIGfreeNext = emitCurIGfreeBase // 重置写入指令用的buffer | |
如果 !emitAdd | |
设置 emitInitGCrefVars = emitThisGCrefVars // 当前ig开始时包含gcref的栈变量集合 | |
设置 emitInitGCrefRegs = emitThisGCrefRegs // 当前ig开始时包含gcref的寄存器集合 | |
设置 emitInitByrefRegs = emitThisByrefRegs // 当前ig开始时包含byref的寄存器集合 | |
调用 emitNewIG(), 同上 | |
如果 emitAdd | |
设置 emitCurIG->igFlags |= IGF_EMIT_ADD // 标记ig是由emit添加的 | |
设置 emitForceNewIG = false // 已经添加了新的ig, 可以取消这个标记 | |
设置 emitThisGCrefVars = GCvars // 当前包含gcref的栈变量集合 | |
设置 emitThisGCrefRegs = emitInitGCrefRegs = gcrefRegs // 当前包含gcref的寄存器集合 | |
设置 emitThisByrefRegs = emitInitByrefRegs = byrefRegs // 当前包含byref的寄存器集合 | |
返回 emitCurIG | |
如果 block 是 compiler->fgFirstColdBlock | |
调用 getEmitter()->emitSetFirstColdIGCookie(block->bbEmitCookie) | |
设置 emitFirstColdIG = (insGroup*)bbEmitCookie | |
设置 genStackLevel = 0 | |
设置 savedStkLvl = genStackLevel | |
设置 compiler->compCurBB = block | |
如果启用了 DEBUGGING_SUPPORT | |
调用 siBeginBlock(block) | |
枚举 compEnterScopeList, 如果 vsdLifeBeg 和 block 开始的偏移值相同 | |
则根据获取到的 vsdLVnum 和 vsdVarNum 创建新的siScope*, 并添加到siOpenScopeLast | |
设置 firstMapping = true | |
生成 block 中的指令 | |
如果 block 是 BBF_FUNCLET_BEG | |
调用 genReserveFuncletProlog(block) | |
调用 getEmitter()->emitCreatePlaceholderIG( | |
IGPT_FUNCLET_PROLOG, block, gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, | |
gcInfo.gcRegByrefSetCur, false) | |
这个函数的作用是创建一个新的placeholder ig, 可用于函数或者funlet的prolog, epilog | |
如果 emitCurIGnonEmpty() 则调用 emitNxtIG(emitAdd), 同上 | |
如果 !emitAdd | |
设置 emitThisGCrefRegs = emitInitGCrefRegs = gcrefRegs | |
设置 emitThisByrefRegs = emitInitByrefRegs = byrefRegs | |
标记 igPh->igFlags |= IGF_PLACEHOLDER // 标记是placeholder | |
设置 igPh->igFuncIdx = emitComp->compCurrFuncIdx // 设置为当前的函数序号 | |
设置 igPh->igPhData = new (emitComp, CMK_InstDesc) insPlaceholderGroupData // TODO | |
设置 igPh->igPhData->igPhNext = nullptr // 下一个place holder | |
设置 igPh->igPhData->igPhType = igType // 类型, func prolog, func epilog, funclet prolog, ... | |
设置 igPh->igPhData->igPhBB = igBB // 设置对应的 block | |
设置 igPh->igPhData->igPhPrevGCrefVars = emitPrevGCrefVars // TODO | |
设置 igPh->igPhData->igPhPrevGCrefRegs = emitPrevGCrefRegs // TODO | |
设置 igPh->igPhData->igPhPrevByrefRegs = emitPrevByrefRegs // TODO | |
设置 igPh->igPhData->igPhInitGCrefVars = emitInitGCrefVars // TODO | |
设置 igPh->igPhData->igPhInitGCrefRegs = emitInitGCrefRegs // TODO | |
设置 igPh->igPhData->igPhInitByrefRegs = emitInitByrefRegs // TODO | |
根据传入的 igType 设置 igFlags | |
IGPT_EPILOG => IGF_EPILOG | |
IGPT_FUNCLET_PROLOG => IGF_FUNCLET_PROLOG | |
IGPT_FUNCLET_EPILOG => IGF_FUNCLET_EPILOG | |
添加 igPh 到 emitPlaceholderList, 并修改 emitPlaceholderLast | |
设置 emitCurIGsize += MAX_PLACEHOLDER_IG_SIZE | |
设置 emitCurCodeOffset += emitCurIGsize | |
如果 !last | |
调用 emitNewIG(), 同上 | |
设置 emitForceStoreGCState = true // 后面的block还无法知道ph里面会修改什么寄存器, 需要强制保存 | |
设置 emitCurIG->igFlags &= ~IGF_PROPAGATE_MASK // 下一个group的标志不会像ph一样传播 | |
设置 compiler->compCurStmt = nullptr | |
设置 compiler->compCurLifeTree = nullptr | |
以 LIR 顺序枚举 block 中的节点, 跳过phi节点 | |
如果启用了 DEBUGGING_SUPPORT 且节点是 GT_IL_OFFSET | |
调用 genEnsureCodeEmitted(currentILOffset) | |
如果上次报告的iloffset就是当前的offset, 表示tree未生成代码 | |
调用 instGen(INS_nop) 插入nop指令确保两个iloffset之间有指令 | |
设置 currentILOffset = node->gtStmt.gtStmtILoffsx | |
调用 genIPmappingAdd(currentILOffset, firstMapping) | |
用于建立native offset到il offset的对应关系, 给debugger使用 | |
新建一个 Compiler::IPmappingDsc | |
调用 addMapping->ipmdNativeLoc.CaptureLocation(getEmitter()) | |
设置 ipmdNativeLoc.ig = emit->emitCurIG | |
设置 ipmdNativeLoc.codePos = emit->emitCurOffset() | |
设置 addMapping->ipmdILoffsx = offsx | |
设置 addMapping->ipmdIsLabel = isLabel | |
添加到 compiler->genIPmappingList 链表的末尾 (compiler->genIPmappingLast) | |
设置 firstMapping = false | |
调用 genCodeForTreeNode(node) | |
这个函数有多个版本, 这里只分析x86/x64的版本 | |
本地变量 targetReg = treeNode->gtRegNum // 如果是x86且类型是long则REG_NA | |
本地变量 targetType = treeNode->TypeGet() | |
如果节点已标记 GTF_REUSE_REG_VAL, 表示值已经在寄存器中(复用常量) | |
从函数返回 | |
如果节点已标记 isContained(), 表示节点应该在它们的上级节点中处理 | |
从函数返回 | |
判断节点类型 treeNode->gtOper | |
GT_START_NONGC | |
调用 getEmitter()->emitDisableGC() | |
如果当前block是空, 则标记 emitCurIG->igFlags |= IGF_NOGCINTERRUPT | |
否则生产一个新的block并标记 ig->igFlags |= IGF_NOGCINTERRUPT (emitGenIG: emitNoGCIG == true) | |
下面还有一个 emitEnableGC | |
调用后会设置 emitNoGCIG = false 并且 emitForceNewIG = true | |
GT_PROF_HOOK | |
调用 genProfilingLeaveCallback(CORINFO_HELP_PROF_FCN_TAILCALL) | |
添加调用 CORINFO_HELP_PROF_FCN_TAILCALL 的指令 | |
第一个参数是 compiler->compProfilerMethHnd | |
第二个参数是 genFramePointerReg | |
GT_LCLHEAP | |
调用 genLclHeap(treeNode) | |
生成在栈上分配内存的代码 | |
获取 amount = 分配的大小 | |
如果分配的大小较小 | |
生成几个 push 0 | |
否则如果不需要init小于一页的alloc | |
生成 sub rsp, amount | |
否则如果需要init mem | |
生成循环 push 0 的代码, 循环次数是 regCnt / TYP_I_IMPL | |
否则 | |
生成循环确保所有页都可以访问(test rsp, [rsp])的代码, 然后修改rsp | |
生成保存修改后的rsp地址到lvaLocAllocSPvar的指令 | |
GT_CNS_INT, GT_CNS_DBL | |
调用 genSetRegToConst(targetReg, targetType, treeNode) | |
如果值是0可以生成 xor targetReg, targetReg | |
否则生成 mov, targetReg, imm | |
调用 genProduceReg(treeNode) | |
如果 tree->gtFlags & GTF_SPILL | |
表示tree要把值保存到堆栈上 | |
如果是 genIsRegCandidateLocal(tree) | |
添加从寄存器复制到本地变量对应的堆栈地址的指令(inst_TT_RV) | |
否则 | |
如果是 IsMultiRegCall | |
和下面基本一样, 只是需要处理多个寄存器(GetReturnRegCount) | |
否则 | |
标记 tree->gtFlags |= GTF_REG_VAL // 值在寄存器中 | |
调用 regSet.rsSpillTree(tree->gtRegNum, tree) | |
获取一个临时变量TempDsc | |
生成保存到该临时变量的指令 (spillReg, emitIns_S_R) | |
新建一个 SpillDsc 并添加到 rsSpillDesc[reg] 链表中 | |
调用 gcInfo.gcMarkRegSetNpt(genRegMask(tree->gtRegNum)), 同上 | |
标记 tree->gtFlags |= GTF_SPILLED // 已保存到栈 | |
标记 tree->gtFlags &= ~GTF_SPILL | |
从函数返回 | |
调用 genUpdateLife(tree), 同上 | |
如果 tree->gtHasReg() | |
如果 !genIsRegCandidateLocal(tree) || tree->gtFlags !& GTF_VAR_DEATH | |
表示如果tree是从本地变量读取的, 并且是最后一次使用, 则不需要更新gcinfo | |
调用 gcInfo.gcMarkRegPtrVal 更新gcinfo | |
标记 tree->gtFlags |= GTF_REG_VAL // 值在寄存器中 | |
GT_NEG, GT_NOT | |
如果是float // 应该只有neg | |
调用 genSSE2BitwiseOp(treeNode) | |
如果是 neg, 生成xor sign bit (0x80000000)的指令 | |
否则 | |
取得op1和来源的寄存器(consume reg) | |
如果来源的寄存器跟目标寄存器(target reg)不一致则 | |
调用 inst_RV_RV(INS_mov, targetReg, operandReg, targetType) | |
调用 ins = genGetInsForOper(treeNode->OperGet(), targetType) | |
这里会返回 INS_neg 或 INS_not | |
调用 inst_RV(ins, targetReg, targetType) | |
调用 genProduceReg(treeNode), 同上 | |
GT_OR, GT_XOR, GT_AND, GT_ADD, GT_SUB | |
如果是x86, GT_ADD_LO, GT_ADD_HI, GT_SUB_LO, GT_SUB_HI | |
调用 genConsumeOperands(treeNode->AsOp()) | |
获取 op1 和 op2 | |
如果 tree->gtFlags & GTF_REVERSE_OPS | |
先调用 genConsumeRegs(op2) 再调用 genConsumeRegs(op1) | |
否则 | |
先调用 genConsumeRegs(op1) 再调用 genConsumeRegs(op2) | |
调用 genCodeForBinary(treeNode) | |
检测 targetReg 是否等于 op1 或者 op2, 如果等于可以生成 | |
op1 = op1 op op2 或者 op2 = op2 op op1 | |
否则如果oper是add, 并且op2是reg或者常量(前面会检测如果op1是则交换op1和op2) | |
生成 lea targetReg, op1reg, op2reg或常量 | |
返回 | |
否则 | |
表示 targetReg 跟 op1, op2 都不一样, 生成 mov targetReg, op1 | |
如果是 add 并且另一个寄存器是常量 | |
如果常量是1则生成 inc targetReg | |
如果常量是-1则生成 dec targetReg | |
添加 op, targetReg, 另一个寄存器 指令 | |
如果 treeNode->gtOverflowEx() | |
调用 genCheckOverflow(treeNode) | |
调用 genJumpToThrowHlpBlk(jumpKind, SCK_OVERFLOW) | |
生成如果溢出(检测eflags)则跳转到对应的block(fgFindExcptnTarget)的指令 | |
调用 genProduceReg(treeNode), 同上 | |
GT_LSH, GT_RSH, GT_RSZ, GT_ROL, GT_ROR | |
调用 genCodeForShift(treeNode) | |
添加 shl, sar, shr, rol 或 ror 指令 | |
如果 shift 是常量则可以直接操作 targetReg, 否则需要先移动 shift 到 rcx | |
调用 genProduceReg(treeNode), 同上 | |
GT_CAST | |
如果是float=>float, 调用 genFloatToFloatCast(treeNode) | |
添加转换的指令, 例如 float => double 是 INS_cvtss2sd | |
如果是float=>int, 调用 genFloatToIntCast(treeNode) | |
添加转换的指令, 例如 float => int 是 INS_cvttss2si | |
如果是int=>float, 调用 genIntToFloatCast(treeNode) | |
添加转换的指令, 例如 int => float 是 INS_cvtsi2ss | |
如果是int=>int, 调用 genIntToIntCast(treeNode) | |
添加转换的指令, 可以直接使用 mov 指令 | |
GT_LCL_VAR | |
如果tree已经InReg(值已在寄存器), 或者gtFlags & GTF_SPILLED(genConsumeReg会处理), 这里可以不处理 | |
否则 | |
添加从本地变量复制到寄存器的指令(emitIns_R_S) | |
调用 genProduceReg(treeNode), 同上 | |
GT_LCL_FLD_ADDR, GT_LCL_VAR_ADDR | |
添加 lea(寄存器, tree) 的指令(inst_RV_TT) | |
调用 genProduceReg(treeNode), 同上 | |
GT_LCL_FLD | |
添加从本地变量+偏移值(gtLclFld.gtLclOffs)复制到寄存器的指令(emitIns_R_S) | |
调用 genProduceReg(treeNode), 同上 | |
GT_STORE_LCL_FLD | |
调用 genConsumeRegs(op1), 同上 | |
调用 emit->emitInsBinary(ins_Store(targetType), emitTypeSize(treeNode), treeNode, op1) | |
添加从寄存器移动到本地变量的指令, 一般是mov | |
如果来源是常量并且是浮点数 | |
调用 emitFltOrDblConst(dblConst) => emitDataConst 标记只读数据到 emitConsDsc.dsdList | |
GT_STORE_LCL_VAR | |
如果 op1 是 IsMultiRegCall | |
调用 genMultiRegCallStoreToLocal(treeNode) | |
生成多条从寄存器移动到本地变量的指令, 一般是mov | |
否则如果是x86且类型是long | |
调用 genStoreLongLclVar(treeNode) | |
生成两条从寄存器移动到本地变量的指令, 一般是mov | |
否则如果是SIMD类型 && 目标寄存器存在 && op1是常量(只可能是0) | |
调用 genSIMDZero(targetType, varDsc->lvBaseType, targetReg) | |
添加 INS_pxor 指令 | |
调用 genProduceReg(treeNode), 同上 | |
否则 | |
调用 genConsumeRegs(op1) | |
如果 treeNode->gtRegNum == REG_NA | |
添加从寄存器复制到本地变量对应的堆栈地址的指令(emitInsMov) | |
设置 varDsc->lvRegNum = REG_STK // 当前本地变量在栈上 | |
否则 | |
如果 op1 是 contained | |
目前 op1 应该是常量 | |
调用 genSetRegToConst(treeNode->gtRegNum, targetType, op1), 同上 | |
否则如果 op1->gtRegNum != treeNode->gtRegNum | |
添加从op1->gtRegNum复制到treeNode->gtRegNum的指令 | |
如果 treeNode->gtRegNum != REG_NA | |
调用 genProduceReg(treeNode), 同上 | |
GT_RETFILT, GT_RETURN | |
调用 genReturn(treeNode) | |
如果是x86且是long | |
调用 genConsumeReg(loRetVal) | |
调用 genConsumeReg(hiRetVal) | |
添加 mov REG_LNGRET_LO, loRetVal->gtRegNum 如果寄存器不相等 | |
添加 mov REG_LNGRET_HI, hiRetVal->gtRegNum 如果寄存器不相等 | |
否则如果是struct | |
调用 genStructReturn(treeNode) | |
一般返回struct的函数都会接收一个参数(最后), 参数为在caller的stack frame分配的地址, 然后设置到里面 | |
但部分环境下如果struct的大小小于两个寄存器, 则会使用rax:rdx返回 | |
这个函数一般情况下不会做任何处理 | |
否则如果不是void | |
调用 genConsumeReg(op1) | |
添加 mov retReg, op1->gtRegNum 如果寄存器不相等 | |
ret指令不会在这里生成, 会在epilog里 | |
GT_LEA | |
调用 genLeaInstruction(treeNode->AsAddrMode()) | |
添加 lea lea->gtRegNum, [baseReg + indexReg * gtScale] 指令 | |
调用 genProduceReg(lea), 同上 | |
GT_IND | |
调用 genConsumeAddress(treeNode->AsIndir()->Addr()) | |
如果addr tree是lea | |
调用genConsumeAddrMode(addr->AsAddrMode()) | |
调用 genConsumeReg(addr->Base()), 同上 | |
调用 genConsumeReg(addr->Index()), 同上 | |
否则如果!addr->isContained() | |
调用 genConsumeReg(addr), 同上 | |
生成从dereference复制到寄存器的指令(emitInsMov), 例如 mov rax, [rax] | |
调用 genProduceReg(treeNode), 同上 | |
GT_MULHI | |
调用 genCodeForMulHi(treeNode->AsOp()) | |
添加 INS_imulEAX 指令, 再添加 mov targetReg, rdx 指令 (因为mul的结果会保存在rdx:rax) | |
调用 genProduceReg(treeNode), 同上 | |
GT_MUL | |
调用 genConsumeOperands(treeNode->AsOp()), 同上 | |
如果 op2->isContainedIntOrIImmed() | |
rmOp = op1 // 内存 | |
immOp = op2 // 即时值(常量) | |
否则如果 op1->isContainedIntOrIImmed() | |
rmOp = op2 // 内存 | |
immOp = op1 // 即时值(常量) | |
如果 immOp != nullptr | |
如果 rmOp 不是 contained, 且 imm 是 3, 5, 9 | |
添加指令 lea targetReg, [rmReg+rmReg*(2或4或8)] | |
否则 | |
添加指令 mul, targetReg, rmOp * immOp | |
否则 | |
添加指令 mov rax, regOp; mul rmOp; mov targetReg, rax; | |
如果 requiresOverflowCheck | |
调用 genCheckOverflow(treeNode), 同上 | |
调用 genProduceReg(treeNode), 同上 | |
GT_MOD, GT_UDIV, GT_UMOD, GT_DIV | |
调用 genCodeForDivMod(treeNode->AsOp()) | |
把分母移动到 rax, 如果是umod则清掉rdx, 否则插入cdq指令 | |
(分母是rdx:rax, 如果rax是signed需要用cdq扩展signed bit到rdx) | |
添加 div 或者 idiv 指令 | |
如果是div则移动rax到targetReg | |
如果是mod则移动rdx到targetReg | |
GT_INTRINSIC | |
调用 genIntrinsic(treeNode) | |
目前这里只会生成 sqrt => INS_sqrtsd 或者 abs => INS_and (0x7fffffff) | |
GT_SIMD | |
调用 genSIMDIntrinsic(treeNode->AsSIMD()) | |
根据 simdNode->gtSIMDIntrinsicID 添加各种simd指令, 这里不详细分析 | |
GT_CKFINITE | |
调用 genCkfinite(treeNode) | |
判断值是否 NaN +Inf 或 -Inf, 参考 https://www.h-schmidt.net/FloatConverter/IEEE754.html | |
如果是float把值 >> 20 然后 & 11111111000, 如果相等即可判断是, 如果是则跳到SCK_ARITH_EXCPN对应的block | |
原理是检查Exponent是否全部为1 | |
如果通过检查则复制float的值到targetReg | |
GT_EQ, GT_NE, GT_LT, GT_LE, GT_GE, GT_GT | |
如果是float | |
调用 genCompareFloat(treeNode) | |
调用 genConsumeOperands(tree), 同上 | |
添加 INS_ucomiss 或者 INS_ucomisd 比较 op1 和 op2 | |
如果 targetReg != REG_NA | |
调用 genSetRegToCond(targetReg, tree) | |
设置判断结果到targetReg, 只能是0或者1 | |
例如GT_EQ会使用sete targetReg | |
调用 genProduceReg(tree), 同上 | |
否则如果是x86且是long | |
如果 treeNode->gtRegNum != REG_NA | |
调用 genCompareLong(treeNode) | |
需要分别对比hi和lo | |
例如GT_EQ会生成 | |
cmp hiop1, hiop2; | |
jne label; // (targetReg = 0) | |
cmp loop1, loop2; | |
label: | |
sete targetReg | |
否则 // 应该是GT_JTRUE中使用的tree, 可以在JTRUE的时候生成, 但现在就要consume | |
调用 genConsumeOperands(treeNode->AsOp()), 同上 | |
否则 | |
调用 genCompareInt(treeNode) | |
调用 genConsumeOperands(tree), 同上 | |
添加比较 op1 和 op2 的指令 | |
如果 targetReg != REG_NA | |
调用 genSetRegToCond(targetReg, tree), 同上 | |
调用 genProduceReg(tree), 同上 | |
GT_JTRUE | |
如果是x86且是long | |
调用 genJTrueLong(cmp) | |
x86上要同时判断hi和lo | |
例如 GT_EQ 会生成 | |
cmp hiop1, hiop2 | |
jne falseLabel | |
cmp loop1, loop2 | |
je trueLabel | |
falseLabel: | |
... | |
trueLabel: | |
否则 | |
调用 genJumpKindsForTree(cmp, jumpKind, branchToTrueLabel) | |
jumpKind 的类型是 emitJumpKind[2], 2个跳转指令 | |
branchToTrueLabel 的类型是 bool[2], 上面的跳转指令应该跳到true branch还是false branch | |
需要两个跳转指令的原因是float eq需要对比两次, 第一次跳到true, 第二次跳到false | |
指令 ucomis[s|d] a, b; jpe L1; je <true label>; L1: | |
需要 PF=0 and ZF=1 才可以判断相等, jpe判断PF=1则跳到false label | |
如果 jumpKind[0] != EJ_NONE | |
jmpTarget = branchToTrueLabel[0] ? | |
compiler->compCurBB->bbJumpDest : | |
(skipLabel = genCreateTempLabel()) // float eq | |
inst_JMP(jumpKind[0], jmpTarget) | |
如果 jumpKind[1] != EJ_NONE | |
确保 branchToTrueLabel[1] == true // 如果有第二个一定会跳到true branch | |
inst_JMP(jumpKind[1], compiler->compCurBB->bbJumpDest) | |
如果 skipLabel != nullptr | |
调用 genDefineTempLabel(skipLabel) | |
调用 label->bbEmitCookie = getEmitter()->emitAddLabel( | |
gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur), 同上 | |
调用 regTracker.rsTrackRegClrPtr() | |
因为定义了一个非block的label, 这里需要清除寄存器的本地变量跟踪表 | |
枚举寄存器 | |
如果寄存器中当前保存的值是非0(nullptr)的常量则跳过 | |
如果寄存器中当前保存了本地变量但非gc类型则跳过 | |
设置 rsRegValues[reg].rvdKind = RV_TRASH | |
GT_RETURNTRAP | |
调用 genConsumeRegs(treeNode->gtOp.gtOp1), 同上 | |
生成 | |
cmp op1, 0 | |
je skipLabel | |
call CORINFO_HELP_STOP_FOR_GC | |
skipLabel: | |
GT_STOREIND | |
调用 genStoreInd(treeNode) | |
调用 gcInfo.gcIsWriteBarrierCandidate(storeInd, data) 判断是否需要 write barrier | |
复制对象到class field里面有可能会让gen 1的对象引用gen 0的对象 | |
需要write barrier标记card table | |
如果对象类型不是gc类型则返回 WBF_NoBarrier | |
如果值的VN确定是null则返回 WBF_NoBarrier (空指针不会引发跨代引用) | |
如果是 GT_IND | |
返回 gcWriteBarrierFormFromTargetAddress(tgt->gtOp.gtOp1) | |
根据地址类型返回 WBF_BarrierChecked, WBF_BarrierUnknown 或者 WBF_BarrierUnchecked | |
如果是 GT_LEA | |
返回 gcWriteBarrierFormFromTargetAddress(tgt->AsAddrMode()->Base()), 同上 | |
如果是 GT_ARR_ELEM, GT_CLS_VAR | |
返回 WBF_BarrierUnchecked // 目标一定在managed heap中, 不需要check | |
如果是 GT_REG_VAR, GT_LCL_VAR, GT_LCL_FLD, GT_STORE_LCL_VAR, GT_STORE_LCL_FLD | |
返回 WBF_NoBarrier // 目标一定不在managed heap中, 不需要write barrier | |
如果需要 write barrier | |
mov arg_0, addr->gtRegNum | |
mov arg_1, data->gtRegNum | |
call write barrier类型对应的helper call, 如果是checked的会检查指针是否在managed heap中 | |
否则 | |
如果是 RMW(Read-Modify-Write) 则调用 emitInsRMW 生成指令 | |
例如 add [rax], rcx | |
否则使用 mov 指令 | |
GT_COPY | |
已经在 genConsumeReg 里面处理过, 这里不需要处理 | |
GT_SWAP | |
插入 xchg oldOp1Reg, oldOp2Reg | |
更新 gcInfo.gcRegByrefSetCur &= ~(oldOp1RegMask | oldOp2RegMask) // 删除两个 | |
更新 gcInfo.gcRegGCrefSetCur &= ~(oldOp1RegMask | oldOp2RegMask) // 删除两个 | |
调用 gcInfo.gcMarkRegPtrVal(oldOp2Reg, type1), 同上 | |
调用 gcInfo.gcMarkRegPtrVal(oldOp1Reg, type2), 同上 | |
GT_LIST, GT_ARGPLACE | |
这里不需要处理 | |
GT_PUTARG_STK | |
调用 genPutArgStk(treeNode) | |
如果是x86 | |
如果值是常量 | |
添加push指令 | |
否则 | |
调用 genConsumeReg(data), 同上 | |
如果值类型是int则添加 push | |
否则添加 sub esp 大小; mov [esp], 值 | |
否则是x64 | |
64位下堆栈传参不会使用push, 而是使用sub rsp + mov, 例如 | |
sub rsp, 30h | |
mov [rsp+20], 1 | |
GT_PUTARG_REG | |
调用 genConsumeReg(op1), 同上 | |
如果 treeNode->gtRegNum != op1->gtRegNum | |
添加 mov treeNode->gtRegNum, op1->gtRegNum | |
调用 genProduceReg(treeNode), 同上 | |
GT_CALL | |
调用 genCallInstruction(treeNode) | |
枚举 call->gtCallLateArgs (通过寄存器传递的参数) | |
调用 genConsumeReg(argNode) | |
如果寄存器不一致则添加 mov 指令 | |
如果 call->NeedsNullCheck() | |
添加 cmp regThis, [regThis] 指令 | |
如果是fast tail call | |
把 target (call->gtCall.gtCallAddr) 存到rax, epilog会生成jmp rax | |
返回 | |
添加 call methodHnd 指令 | |
GT_JMP | |
调用 genJmpMethod(treeNode) | |
把所有在寄存器中的变量移动回它们的stack location | |
移动通过寄存器传入的参数回它们原来的寄存器 | |
实际的jmp会在block的结束生成, 这个函数只负责设置寄存器和本地变量 | |
GT_LOCKADD, GT_XCHG, GT_XADD | |
调用 genLockedInstructions(treeNode) | |
添加 lock add, xchg (自带lock) 或 lock xadd 指令 | |
GT_MEMORYBARRIER | |
调用 instGen_MemoryBarrier() | |
x86和x64上会生成 lock or [rsp], 0 | |
GT_CMPXCHG | |
取出3个参数 gtOpLocation, gtOpValue, gtOpComparand | |
调用 genConsumeReg(location), 同上 | |
调用 genConsumeReg(value), 同上 | |
调用 genConsumeReg(comparand), 同上 | |
确保 comparand 在 rax, 如果不在则添加 mov rax, comparand->gtRegNum | |
调用 instGen(INS_lock) // lock prefix | |
添加 cmpxchg value->gtRegNum, location->gtRegNum | |
如果 tree 对应的寄存器不是rax则添加 mov targetReg, rax | |
调用 genProduceReg(treeNode), 同上 | |
GT_RELOAD | |
这里不需要处理, 上级节点调用genConsumeReg的时候会触发unspill处理 | |
GT_NOP | |
这里不需要处理 | |
GT_NO_OP | |
插入 nop (1byte) | |
GT_ARR_BOUNDS_CHECK, GT_SIMD_CHK | |
调用 genRangeCheck(treeNode) | |
用于检查数组长度 | |
如果 arrIndex->isContainedIntOrIImmed() | |
添加 cmp arrLen, arrIndex; jbe bndsChk->gtIndRngFailBB; | |
否则 | |
添加 cmp arrIndex, arrLen; jae bndsChk->gtIndRngFailBB; | |
GT_PHYSREG | |
如果 treeNode->gtRegNum != treeNode->AsPhysReg()->gtSrcReg | |
添加 mov treeNode->gtRegNum, treeNode->AsPhysReg()->gtSrcReg | |
调用 genTransferRegGCState(treeNode->gtRegNum, treeNode->AsPhysReg()->gtSrcReg) | |
复制 gcinfo 中的状态 | |
如果 gcInfo.gcRegGCrefSetCur & srcMask | |
调用 gcInfo.gcMarkRegSetGCref(dstMask) | |
否则如果 gcInfo.gcRegByrefSetCur & srcMask | |
调用 gcInfo.gcMarkRegSetByref(dstMask) | |
否则 | |
调用 gcInfo.gcMarkRegSetNpt(dstMask) | |
调用 genProduceReg(treeNode), 同上 | |
GT_PHYSREGDST | |
这里不需要处理 | |
GT_NULLCHECK | |
调用 genConsumeReg(treeNode->gtOp.gtOp1) | |
添加 mov reg, [reg] // 如果为空会触发hardware exception (在linux上是SIGSEGV) | |
GT_CATCH_ARG | |
调用 genConsumeReg(treeNode), 同上 | |
GT_END_LFIN, 仅在eh funclet未启用时有效 | |
生成 mov ShadowSP(例如[ebp-0xc]), 0 | |
GT_PINVOKE_PROLOG | |
调用 emit->emitDisableRandomNops() | |
设置 emitRandomNops = false // 不允许在emitAllocInstr中随机插入nop | |
GT_LABEL | |
添加 mov targetReg, 目标BasicBlock的地址 | |
指令会添加到 emitCurIGjmpList, 地址会在后面解决 | |
GT_STORE_OBJ | |
如果是 OperIsCopyBlkOp 且 !gtBlkOpGcUnsafe | |
调用 genCodeForCpObj(treeNode->AsObj()) | |
对于非gc pointer, 使用mov | |
对于gc pointer, 调用CORINFO_HELP_ASSIGN_BYREF赋值(会调用memory barrier) | |
这是防止复制包含gc ref的struct引发跨代引用 | |
否则 fallthrough | |
GT_STORE_DYN_BLK, GT_STORE_BLK | |
调用 genCodeForStoreBlk(treeNode->AsBlk()) | |
如果 gtBlkOpGcUnsafe | |
调用 getEmitter()->emitDisableGC() // 下面的操作不允许gc中断 | |
本地变量 isCopyBlk = storeBlkNode->OperIsCopyBlkOp() | |
判断 storeBlkNode->gtBlkOpKind 类型 | |
GenTreeBlk::BlkOpKindHelper | |
如果是 isCopyBlk, 则调用 genCodeForCpBlk(storeBlkNode) | |
添加 call CORINFO_HELP_MEMCPY (参数 dst, src, size) | |
否则调用 genCodeForInitBlk(storeBlkNode) | |
添加 call CORINFO_HELP_MEMSET (参数 dst, initVal, size) | |
GenTreeBlk::BlkOpKindRepInstr | |
如果是 isCopyBlk, 则调用 genCodeForCpBlkRepMovs(storeBlkNode) | |
设置 rdi = dst, rsi = src, size = rcx | |
添加 movsb (移动*rsi到*rdi rcx次, 1次byte) | |
否则调用 genCodeForInitBlkRepStos(storeBlkNode) | |
设置 rdi = dst, rax = initVal, size = rcx | |
添加 stosb (设置rax到dst rcx次, 1次1byte) | |
GenTreeBlk::BlkOpKindUnroll | |
如果是 isCopyBlk, 则调用 genCodeForCpBlkUnroll(storeBlkNode) | |
如果size大于16bytes, 先添加一个到多个movdqu (xmm指令可移动128bit) | |
对于剩余的大小分别调用各个大小的mov移动 | |
否则调用 genCodeForInitBlkUnroll(storeBlkNode) | |
如果size大于16bytes | |
如果常量值不等于0 | |
mov tmpReg, valReg | |
punpckldq tmpReg, tmpReg // 把低4byte的内容复制到高12byte中 | |
否则 | |
xorpd tmpReg, tmpReg | |
movdqu [dstAddr->gtRegNum], tmpReg | |
对于剩余的大小分别调用各个大小的mov填充 | |
如果 storeBlkNode->gtBlkOpGcUnsafe | |
调用 getEmitter()->emitEnableGC() // 恢复允许gc中断 | |
GT_JMPTABLE | |
调用 genJumpTable(treeNode) | |
分配一块内存用于保存 jumpTable (emitBBTableDataGenBeg) | |
把各个block的指针移动过去 (emitDataGenData) | |
添加 lea treeNode->gtRegNum, compiler->eeFindJitDataOffs(jmpTabBase) | |
这里最终会保存一个偏移值表, 等于距离firstBB的偏移值 | |
例如 | |
lea rax,[7FF7F18504E0h] // 偏移值表, 例如 2a 00 00 00 3a 00 00 00 4a 00 00 00 | |
mov eax,dword ptr [rax+rcx*4] // 获取偏移值 | |
lea rdx,[7FF7F1850484h] // firstBB的基址 | |
add rax,rdx // case的地址 = 基址 + 偏移值 | |
jmp rax // 跳转到case | |
这个函数只会添加lea, 其他的在GT_SWITCH_TABLE生成 | |
GT_SWITCH_TABLE | |
调用 genTableBasedSwitch(treeNode), 见上面的说明 | |
GT_ARR_INDEX | |
调用 genCodeForArrIndex(treeNode->AsArrIndex()) | |
mov tgtReg, indexReg | |
sub tgtReg, genOffsetOfMDArrayLowerBound(elemType, rank, dim) | |
cmp tgtReg, genOffsetOfMDArrayDimensionSize(elemType, rank, dim) | |
jae SCK_RNGCHK_FAIL 对应的 block | |
调用 genProduceReg(arrIndex), 同上 | |
GT_ARR_OFFSET | |
调用 genCodeForArrOffset(treeNode->AsArrOffs()) | |
添加计算 tgtReg = offsetReg * dim_size + indexReg 的指令 | |
调用 genProduceReg(arrOffset), 同上 | |
GT_CLS_VAR_ADDR | |
添加 lea targetReg, treeNode->gtClsVar.gtClsVarHnd // 取class field的地址 | |
调用 genProduceReg(treeNode), 同上 | |
GT_LONG, 如果不是x86 | |
调用 genConsumeRegs(treeNode) | |
如果是x86且是long | |
调用 genConsumeRegs(tree->gtGetOp1()) | |
调用 genConsumeRegs(tree->gtGetOp2()) | |
返回 | |
如果 tree->isContained() // 只consume它们的参数, 不consume它们本身 | |
如果 tree->isContainedSpillTemp() // spill到的临时变量不需要跟踪 | |
否则如果 tree->isIndir() | |
调用 genConsumeAddress(tree->AsIndir()->Addr()) // 针对各个参数调用genConsumeReg | |
否则如果 tree->OperGet() == GT_AND | |
调用 genConsumeOperands(tree->AsOp()) // 针对各个参数调用genConsumeReg | |
否则如果 tree->OperGet() == GT_LCL_VAR | |
调用 genUpdateLife(tree) // 同上, 更新compCurLife, gcRegGCrefSetCur, gcRegByrefSetCur 和 gcVarPtrSetCur | |
否则 | |
调用 genConsumeReg(tree) | |
GT_IL_OFFSET | |
这里不需要处理 | |
如果 node->gtHasReg() && node->gtLsraInfo.isLocalDefUse | |
调用 genConsumeReg(node) | |
这个函数有多个版本, 这里只分析x86/x64的版本 | |
这个函数用于返回tree消费的寄存器 | |
如果 tree->OperGet() == GT_COPY | |
调用 genRegCopy(tree) | |
生成从op1的寄存器复制到tree的寄存器的代码 | |
如果类型不一致会调用 ins_CopyIntToFloat 或者 ins_CopyFloatToInt, 可以见 GT_CAST 的处理 | |
如果 genIsRegCandidateLocal(tree), 并且 varDsc->lvRegNum != tree->gtRegNum | |
调用 inst_RV_RV(INS_mov, tree->gtRegNum, varDsc->lvRegNum) | |
调用 genUnspillRegIfNeeded(tree) | |
本地变量 unspillTree = tree | |
如果 tree->gtOper == GT_RELOAD | |
设置 unspillTree = tree->gtOp.gtOp1 | |
如果 unspillTree->gtFlags & GTF_SPILLED | |
如果 genIsRegCandidateLocal(unspillTree) | |
tree对应的是本地变量, 可以从本地变量所在的堆栈(home)取值 | |
取消 unspillTree->gtFlags &= ~GTF_SPILLED | |
设置 unspillTree->gtFlags |= GTF_REG_VAL | |
插入从变量所在堆栈地址(home)复制到寄存器的指令(mov) | |
调用 gcInfo.gcMarkRegPtrVal(dstReg, unspillTree->TypeGet()) | |
获取寄存器对应的mask (genRegMask) | |
判断tree的类型 | |
如果是 TYP_REF, 调用 gcMarkRegSetGCref(regMask) | |
设置 gcRegByrefSetCur = gcRegByrefSetCur & ~regMask | |
设置 gcRegGCrefSetCur = gcRegGCrefSetCur | regMask | |
如果是 TYP_BYREF, 调用 gcMarkRegSetByref(regMask) | |
设置 gcRegByrefSetCur = gcRegByrefSetCur | regMask | |
设置 gcRegGCrefSetNew = gcRegGCrefSetCur & ~regMask | |
否则调用 gcMarkRegSetNpt(regMask) | |
设置 gcRegByrefSetCur = gcRegByrefSetCur & ~(regMask & ~regSet->rsMaskVars) | |
设置 gcRegGCrefSetNew = gcRegGCrefSetCur & ~(regMask & ~regSet->rsMaskVars) | |
rsMaskVars 是当前的 live register variables | |
否则如果 unspillTree->IsMultiRegCall() | |
跟下面基本一样, 但是需要复制到多个寄存器 (GetReturnRegCount) | |
否则 | |
tree对应的是内部临时变量(TempDsc), 需要获取TempDsc并且从里面取值 | |
调用 regSet.rsUnspillInPlace(unspillTree, unspillTree->gtRegNum) | |
获取tree对应的内部临时变量(TempDsc) | |
取消 unspillTree->gtFlags &= ~GTF_SPILLED | |
设置 unspillTree->gtFlags |= GTF_REG_VAL | |
调用 gcInfo.gcMarkRegPtrVal(dstReg, unspillTree->TypeGet()), 同上 | |
调用 genUpdateLife(tree), 同上 | |
如果 genIsRegCandidateLocal(tree) | |
如果 tree->gtFlags & GTF_VAR_DEATH // 最后一次使用 | |
调用 gcInfo.gcMarkRegSetNpt(genRegMask(varDsc->lvRegNum)), 同上 | |
否则如果 varDsc->lvRegNum == REG_STK // 在栈上 | |
调用 gcInfo.gcMarkRegSetNpt(genRegMask(tree->gtRegNum)), 同上 | |
否则 | |
调用 gcInfo.gcMarkRegSetNpt(tree->gtGetRegMask()), 同上 | |
调用 genCheckConsumeNode(tree), 只在debug时有效 | |
检查 tree 是否被 consume 了两次, 如果是则记录日志 | |
检查 treeNode.gtSeqNum <= lastConsumedNode->gtSeqNum, 如果是表示乱序消费, 记录日志 | |
设置 lastConsumedNode = treeNode | |
返回 tree->gtRegNum | |
如果启用了 DEBUGGING_SUPPORT | |
调用 genEnsureCodeEmitted(currentILOffset), 同上 | |
如果 compiler->opts.compScopeInfo && (compiler->info.compVarScopesCount > 0) | |
调用 siEndBlock(block) | |
如果当前block是最后一个block并且siOpenScopeList.scNext不是nullptr | |
调用 siCloseAllOpenScopes() | |
枚举 siOpenScopeList, 调用 siEndScope(siOpenScopeList.scNext) | |
调用 scope->scEndLoc.CaptureLocation(getEmitter()) | |
设置 scEndLoc.ig = emit->emitCurIG | |
设置 scEndLoc.codePos = emit->emitCurOffset() | |
调用 siRemoveFromOpenScopeList(scope) | |
从 siOpenScopeLast 删除 scope | |
添加 scope 到 siScopeLast (已经完成的列表) | |
如果scope对应的本地变量是 lvTracked | |
设置 siLatestTrackedScopes[lclVarDsc1.lvVarIndex] = nullptr | |
设置 genStackLevel -= savedStkLvl // TODO | |
确保 genStackLevel == 0 | |
如果是 x64 | |
如果block的最后一个指令是call, 并且不存在下一个block, | |
或下一个block跟当前block不在同一个eh region (不在同一个try中) | |
如果 block 是 BBJ_NONE, 在call后面插入nop // 为了让stack walker准确的计算指令所在的eh region | |
判断 block->bbJumpKind | |
BBJ_ALWAYS | |
调用 inst_JMP(EJ_jmp, block->bbJumpDest), 同上 | |
BBJ_RETURN | |
调用 genExitCode(block) | |
如果 compiler->getNeedsGSSecurityCookie() | |
调用 genEmitGSCookieCheck(jmpEpilog) | |
标记返回的寄存器中包含ref对象 (如果类型是ref) | |
添加 cmp 对比 gc security cookie, 如果不一致则调用 CORINFO_HELP_FAIL_FAST | |
gc security cookie包含了一个magic number, 可以用于检测是否发生了栈溢出 | |
如果 block->bbFlags & BBF_HAS_JMP | |
调用 gcMarkRegSetNpt 清除寄存器=>ref变量的标记 | |
设置 emitThisGCrefRegs = emitInitGCrefRegs = gcRegGCrefSetCur | |
设置 emitThisByrefRegs = emitInitByrefRegs = gcRegByrefSetCur | |
调用 genReserveEpilog(block) | |
标记返回的寄存器中包含ref对象 (如果类型是ref) | |
调用 emitCreatePlaceholderIG 创建 IGPT_EPILOG 类型的 placeholder ig | |
BBJ_THROW | |
如果不存在下一个block, 或下一个block跟当前block不在同一个eh region | |
调用 instGen(INS_BREAKPOINT) // 不会被执行 | |
BBJ_CALLFINALLY | |
插入 mov rcx, pspsym; call finally-funclet; jmp finally-return; 的指令 | |
如果block是retless则最后的指令不是jmp而是breakpoint(int3) | |
如果 !(block->bbFlags & BBF_RETLESS_CALL) | |
跳过下一个block(用于跳转到finally完成后返回目标的block) | |
BBJ_EHCATCHRET | |
REG_INTRET(rax)保存catch后应该返回的地址 | |
调用 getEmitter()->emitIns_R_L(INS_lea, EA_PTR_DSP_RELOC, block->bbJumpDest, REG_INTRET) | |
调用 genReserveFuncletEpilog(block) | |
调用 emitCreatePlaceholderIG 创建 IGPT_FUNCLET_EPILOG 类型的 placeholder ig | |
BBJ_EHFINALLYRET, BBJ_EHFILTERRET | |
调用 genReserveFuncletEpilog(block), 同上 | |
不支持 eh funclet 的情况这里不分析 | |
对 block 的处理到此结束 | |
调用 genUpdateLife(VarSetOps::MakeEmpty(compiler)), 同上, 清除所有存活的寄存器标记 | |
调用 regSet.rsSpillEnd() | |
debug模式下会检查rsSpillDesc[reg]为空, 非debug模式下不做任何处理 | |
调用 compiler->tmpEnd() | |
debug模式下打印tmpCount, 非debug模式下不做任何处理 | |
调用 genGeneratePrologsAndEpilogs() | |
调用 compiler->m_pLinearScan->recordVarLocationsAtStartOfBB(compiler->fgFirstBB) | |
同上, 这里因为要生成prolog所以要把所在的寄存器重置回第一个block的状态 | |
调用 getEmitter()->emitStartPrologEpilogGeneration() | |
如果当前的ig不为空, 保存当前的ig (前面生成的最后一个ig) | |
调用 gcInfo.gcResetForBB() | |
重置 gcRegGCrefSetCur, gcRegByrefSetCur, gcVarPtrSetCur | |
调用 genFnProlog() | |
调用 compiler->funSetCurrentFunc(0), 同上, 这里标记生成主函数而不是funclet的prolog | |
调用 getEmitter()->emitBegProlog() | |
设置 emitNoGCIG = true | |
设置 emitForceNewIG = false | |
调用 emitGenIG(emitPrologIG), 切换当前的ig到之前分配好的第一个ig | |
清空 emitInitGCrefVars // TODO | |
清空 emitPrevGCrefVars // TODO | |
清空 emitInitGCrefRegs // TODO | |
清空 emitPrevGCrefRegs // TODO | |
清空 emitInitByrefRegs // TODO | |
清空 emitPrevByrefRegs // TODO | |
调用 compiler->unwindBegProlog() | |
初始化 unwind 信息 | |
如果是 UNIX_AMD64_ABI 则调用 unwindBegPrologCFI // 实际linux下会调下面的函数 | |
基本同下面 | |
否则调用 unwindBegPrologWindows | |
初始化当前的 FuncInfoDsc (compiler.compFuncInfoRoot) | |
func->startLoc = nullptr // 代表函数开始, ig的地址或nullptr, 参考unwindGetFuncLocations | |
func->endLoc = nullptr // 代表函数结束, ig的地址或nullptr, 参考unwindGetFuncLocations | |
func->coldStartLoc = // cold block 的开始, 同上 | |
func->coldEndLoc = // cold block 的结束, 同上 | |
func->unwindCodeSlot = sizeof(func->unwindCodes) // 写入unwind信息的地址, 从后向前写入 | |
func->unwindHeader.Version = 1 | |
func->unwindHeader.Flags = 0 | |
func->unwindHeader.CountOfUnwindCodes = 0 | |
func->unwindHeader.FrameRegister = 0 | |
func->unwindHeader.FrameOffset = 0 | |
调用 genIPmappingAddToFront((IL_OFFSETX)ICorDebugInfo::PROLOG) | |
新建一个 Compiler::IPmappingDsc | |
调用 addMapping->ipmdNativeLoc.CaptureLocation(getEmitter()), 同上 | |
设置 addMapping->ipmdILoffsx = offsx | |
设置 addMapping->ipmdIsLabel = true | |
添加到 compiler->genIPmappingList 链表的开头 | |
如果 compiler->opts.compScopeInfo && (compiler->info.compVarScopesCount > 0) | |
调用 psiBegProlog() | |
TODO | |
枚举本地变量和内部使用的临时变量计算 | |
untrLclLo = 需要0初始化的, 在栈上的变量的起始地址 | |
untrLclHi = 需要0初始化的, 在栈上的变量的结束地址(最后一个变量的开始地址 + 变量大小) | |
hasUntrLcl = 栈上是否有需要初始化的变量 | |
GCrefLo = 需要0初始化的, 在栈上的gc ref变量的起始地址 | |
GCrefHi = 需要0初始化的, 在栈上的gc ref变量的结束地址 | |
hasGCRef = 栈上是否有需要初始化的gc ref变量 | |
initRegs = 需要初始化的寄存器集合 | |
initFltRegs = 需要初始化的float寄存器集合 | |
initDblRegs = 需要初始化的double寄存器集合 | |
选择 initReg = 用于初始化的临时寄存器, x86和x64上必须使用rax | |
如果是x64且compiler->info.compIsVarArgs | |
调用 getEmitter()->spillIntArgRegsToShadowSlots() | |
枚举可以接收int参数的寄存器(不会判断是否实际传入参数) | |
添加 mov [rsp + (index + 1) * ptrsize], reg // 复制到 rsp + 8 后面的地址 | |
如果是x86或者x64 | |
如果 doubleAlignOrFramePointerUsed // 如果使用了frame pointer, 或者需要double align frame pointer | |
添加 push rbp | |
调用 compiler->unwindPush(rbp) | |
添加 push rbp 的 unwind 信息 | |
如果是 UNIX_AMD64_ABI 则调用 unwindPushCFI // 实际linux下会调下面的函数 | |
调用 createCfiCode(func, cbProlog, CFI_ADJUST_CFA_OFFSET, DWARF_REG_ILLEGAL, 8) | |
向 func->cfiCodes 列表添加 CFI_CODE(codeOffset, cfiOpcode, dwarfReg, offset) | |
也就是 CFI_CODE(cbProlog, CFI_ADJUST_CFA_OFFSET, DWARF_REG_ILLEGAL, 8) | |
如果寄存器是 callee saved | |
调用 createCfiCode(func, cbProlog, CFI_REL_OFFSET, mapRegNumToDwarfReg(reg)) | |
向 func->cfiCodes 列表添加 CFI_CODE(cbProlog, CFI_REL_OFFSET, mapRegNumToDwarfReg(reg), 0) | |
否则调用 unwindPushWindows | |
本地变量 code = (UNWIND_CODE*)&func->unwindCodes[func->unwindCodeSlot -= sizeof(UNWIND_CODE)] | |
这里是从末尾开始添加 UNWIND_CODE 元素 | |
设置 code->CodeOffset = unwindGetCurrentOffset(func) // emitCurIGsize | |
如果寄存器是 callee saved | |
设置 code->UnwindOp = UWOP_PUSH_NONVOL | |
设置 code->OpInfo = (BYTE)reg | |
否则 // 不需要保存原寄存器内容 | |
设置 code->UnwindOp = UWOP_ALLOC_SMALL | |
设置 code->OpInfo = 0 | |
调用 psiAdjustStackLevel(REGSIZE_BYTES) | |
TODO | |
如果是x86, 调用 genEstablishFramePointer(0, /*reportUnwindData*/ true) | |
添加 mov rbp, rsp | |
调用 psiMoveESPtoEBP() | |
TODO | |
如果 reportUnwindData, 调用 compiler->unwindSetFrameReg(REG_FPBASE, delta) | |
添加 mov rbp, rsp 的 unwind 信息 | |
delta 是 0 表示当前的stack pointer需要加多少才可以访问到frame pointer (这里[rsp+0]即可访问到之前push的rbp) | |
如果是 UNIX_AMD64_ABI 则调用 unwindSetFrameRegCFI // 实际linux下会调下面的函数 | |
向 func->cfiCodes 列表添加 CFI_CODE(cbProlog, CFI_DEF_CFA_REGISTER, mapRegNumToDwarfReg(reg), 0) | |
如果 offset != 0 | |
向 func->cfiCodes 列表添加 CFI_CODE(cbProlog, CFI_ADJUST_CFA_OFFSET, DWARF_REG_ILLEGAL, offset) | |
否则调用 unwindSetFrameRegWindows(reg, offset) | |
设置 func->unwindHeader.FrameRegister = (BYTE)reg | |
如果当前平台是linux或者unix且offset > 240 | |
在 func->unwindCodes 分配(从尾到头)一个ULONG, 保存 offset / 16 | |
在 func->unwindCodes 分配(从尾到头)一个UNWIND_CODE | |
设置 code->CodeOffset = (BYTE)cbProlog | |
设置 code->OpInfo = 0 | |
设置 code->UnwindOp = UWOP_SET_FPREG_LARGE | |
设置 func->unwindHeader.FrameOffset = 15 | |
否则 | |
在 func->unwindCodes 分配(从尾到头)一个UNWIND_CODE | |
设置 code->CodeOffset = (BYTE)cbProlog | |
设置 code->OpInfo = 0 | |
设置 code->UnwindOp = UWOP_SET_FPREG | |
设置 func->unwindHeader.FrameOffset = offset / 16 | |
如果需要 double align | |
添加 and rsp, -8 (清掉末尾3位) | |
调用 genPushCalleeSavedRegisters() | |
枚举修改过的 callee saved register (除去rbp, 因为已经push过) | |
添加 push reg | |
调用 compiler->unwindPush(reg), 同上 | |
调用 genAllocLclFrame( | |
compiler->compLclFrameSize, initReg, &initRegZeroed, intRegState.rsCalleeRegArgMaskLiveIn) | |
如果 frameSize == 0, 可以不处理并从函数返回 | |
如果 frameSize == REGSIZE_BYTES | |
添加 push rax // frame大小刚好跟寄存器大小一样 | |
否则如果 frameSize < compiler->eeGetPageSize() | |
添加 sub rsp, frameSize // 从栈分配指定大小的空间 | |
否则如果 frameSize < compiler->getVeryLargeFrameSize() // 0x1000 ~ 0x3000 | |
需要确保分配到的虚拟内存都有对应的物理内存(添加test访问页里面的地址) | |
添加 test rax, [rsp - pageSize] | |
如果 frameSize >= 0x2000 | |
添加 test rax, [rsp - pageSize * 2] | |
添加 sub rsp, frameSize // 从栈分配指定大小的空间 | |
否则 // Frame size >= 0x3000 | |
添加test页内存的循环 | |
xor rax, rax | |
test [rsp + rax], rax | |
sub rax, 0x1000 | |
cmp rax, -frameSize | |
jge loop | |
添加 sub rsp, frameSize // 从栈分配指定大小的空间 | |
如果是x64, 且 doubleAlignOrFramePointerUsed | |
调用 genEstablishFramePointer( | |
compiler->codeGen->genSPtoFPdelta(), | |
compiler->compLocallocUsed || compiler->opts.compDbgEnC), 同上 (只是时机不一样) | |
如果 compiler->info.compPublishStubParam | |
添加 mov [lvaStubArgumentVar], rax | |
标记 intRegState.rsCalleeRegArgMaskLiveIn &= ~rax | |
如果 genNeedPrologStackProbe | |
调用 genPrologPadForReJit() | |
参考上面的 什么是ReJIT | |
如果 compiler->opts.eeFlags & CORJIT_FLG_PROF_REJIT_NOPS | |
如果 emitCurIGsize < 5 | |
添加 emitCurIGsize - 5 个 nop 指令 | |
调用 genGenerateStackProbe() | |
添加 test [rsp - CORINFO_STACKPROBE_DEPTH + JIT_RESERVED_STACK], rax | |
用于确保该处的页已分配物理内存 | |
调用 genZeroInitFrame(untrLclHi, untrLclLo, initReg, &initRegZeroed) | |
如果 genUseBlockInit // 查看前面genCheckUseBlockInit的算法 | |
添加一整块清0的代码, 例如 | |
lea edi, [ebp/esp-OFFS] | |
mov ecx, <size> | |
xor eax, eax | |
rep stosd | |
否则如果 genInitStkLclCnt > 0 | |
枚举需要0初始化的本地变量 | |
添加 mov [var], initReg // initReg是之前清0的寄存器, 使用寄存器=>寄存器可以减少指令大小 | |
如果支持 eh funclet | |
调用 genSetPSPSym(initReg, &initRegZeroed) | |
如果不需要生成PSPSym则返回 | |
如果是x64 | |
添加 mov [lvaPSPSym], rsp | |
x86不会有PSPSym, 因为x86不支持eh funclet | |
调用 genReportGenericContextArg(initReg, &initRegZeroed) | |
如果不需要报告 generic context arg 则返回 | |
保存 generic context arg 到本地变量 | |
添加 mov [ebp-lvaCachedGenericContextArgOffset()], reg // 如果不是通过reg传入前面还会先读到reg | |
如果 compiler->compLocallocUsed | |
添加 mov [lvaLocAllocSPvar], rsp | |
调用 genSetGSSecurityCookie(initReg, &initRegZeroed) | |
如果不需要 gs security cookie 则返回 | |
如果 compiler->gsGlobalSecurityCookieAddr == nullptr // 固定值 | |
添加 mov eax, compiler->gsGlobalSecurityCookieVal | |
添加 mov [frame.GSSecurityCookie], eax | |
否则 // 固定指针 | |
添加 mov reg, [compiler->gsGlobalSecurityCookieAddr] | |
添加 mov [frame.GSSecurityCookie], reg | |
如果 PROFILING_SUPPORTED | |
调用 genProfilingEnterCallback(initReg, &initRegZeroed) | |
如果不需要profiler hook, 返回 | |
添加 mov arg_0, compiler->compProfilerMethHnd | |
添加 lea arg_1, [rbp - callerSPOffset] | |
调用 genPrologPadForReJit(), 同上 | |
添加 call CORINFO_HELP_PROF_FCN_ENTER | |
添加重新加载各个传入参数到寄存器的指令 | |
如果 !genInterruptible // 不需要生成可中断代码 | |
调用 genPrologPadForReJit(), 同上 | |
调用 getEmitter()->emitMarkPrologEnd() | |
设置 emitPrologEndPos = emitCurOffset() | |
真正的prolog至此结束 | |
调用 compiler->lvaUpdateArgsWithInitialReg(), 同上 | |
枚举 this->intRegState, this->floatRegState | |
如果 regState->rsCalleeRegArgMaskLiveIn | |
调用 genFnPrologCalleeRegArgs(xtraReg /* 一般是rax */, &xtraRegClobbered, regState) | |
如果 xtraRegClobbered | |
设置 initRegZeroed = false // initReg的值是否已经为0, false表示不能保证为0 | |
调用 genEnregisterIncomingStackArgs() | |
如果通过栈传入的参数在LSRA里面分配了寄存器, 移动这些参数到对应的寄存器中 | |
如果有 initRegs // 需要初始化的寄存器集合 | |
枚举 initRegs 里面的寄存器 | |
使用 xor 或者 mov 清零寄存器 | |
如果是 initReg 并且 initRegZeroed == true, 可以跳过 | |
调用 genCodeForPrologStackFP() | |
枚举第一个 fgFirstBB->bbLiveIn & compiler->optAllFPregVars | |
如果是参数则添加 fld (读到float stack里面) | |
否则添加 fldz (值是0) | |
如果 genInterruptible // 对应前面的 !genInterruptible | |
调用 genPrologPadForReJit(), 同上 | |
调用 getEmitter()->emitMarkPrologEnd(), 同上 | |
真正的prolog至此结束 | |
如果 hasGCRef | |
调用 getEmitter()->emitSetFrameRangeGCRs(GCrefLo, GCrefHi) | |
设置 emitGCrFrameOffsMin = GCrefLo | |
设置 emitGCrFrameOffsMax = GCrefHi | |
设置 emitGCrFrameOffsCnt = (GCrefHi - GCrefLo) / sizeof(void*) | |
TODO: 如何保证这个范围内只有gc ref的变量? | |
如果是 x86 并且 compiler->info.compIsVarArgs && | |
compiler->lvaTable[compiler->lvaVarargsBaseOfStkArgs].lvRefCnt > 0 | |
添加 mov rax [varargs handle] // varargs handle是最后一个参数 | |
添加 mov rax, [rax] | |
添加 lea rax, rbp + rax | |
如果 varargs handle是寄存器 | |
添加 mov varargs handle对应的寄存器, rax | |
否则 | |
添加 mov [varargs handle], rax // vararg在栈上的初始地址 | |
调用 getEmitter()->emitEndProlog() | |
设置 emitNoGCIG = false | |
调用 emitSavIG 保存 emitPrologIG | |
调用 compiler->unwindEndProlog() | |
目前这个函数不做任何处理 | |
调用 genCaptureFuncletPrologEpilogInfo() | |
设置生成 funclet 的 prolog 和 epilog 所需要的信息 | |
这个函数有多个版本, 这里只分析x64版本的函数 (无x86版本, 因为x86不支持eh funclet) | |
设置 genFuncletInfo.fiFunction_InitialSP_to_FP_delta = compiler->lvaToInitialSPRelativeOffset(0, true) | |
等于 codeGen->genSPtoFPdelta() | |
等于 compiler->lvaOutgoingArgSpaceSize | |
设置 genFuncletInfo.fiPSP_slot_InitialSP_offset = compiler->lvaOutgoingArgSpaceSize | |
本地变量 totalFrameSize = | |
REGSIZE_BYTES // return address | |
+ REGSIZE_BYTES // pushed EBP | |
+ (compiler->compCalleeRegsPushed * REGSIZE_BYTES); // pushed callee-saved int regs, not including EBP | |
本地变量 calleeFPRegsSavedSize = genCountBits(compiler->compCalleeFPRegsSavedMask) * XMM_REGSIZE_BYTES | |
本地变量 FPRegsPad = (calleeFPRegsSavedSize > 0) ? AlignmentPad(totalFrameSize, XMM_REGSIZE_BYTES) : 0 | |
本地变量 totalFrameSize += | |
FPRegsPad // Padding before pushing entire xmm regs | |
+ calleeFPRegsSavedSize // pushed callee-saved float regs | |
// below calculated 'pad' will go here | |
+ REGSIZE_BYTES // PSPSym | |
+ compiler->lvaOutgoingArgSpaceSize // outgoing arg space | |
本地变量 pad = AlignmentPad(totalFrameSize, 16) | |
设置 genFuncletInfo.fiSpDelta = | |
FPRegsPad // Padding to align SP on XMM_REGSIZE_BYTES boundary | |
+ calleeFPRegsSavedSize // Callee saved xmm regs | |
+ pad + REGSIZE_BYTES // PSPSym | |
+ compiler->lvaOutgoingArgSpaceSize // outgoing arg space | |
TODO: 看不懂这里的计算 | |
调用 getEmitter()->emitGeneratePrologEpilog() | |
生成 epilog 和各个 funclet 的 prolog 和 epilog | |
枚举 emitPlaceholderList | |
判断 igPh->igPhData->igPhType 类型 | |
IGPT_PROLOG | |
前面的 genFnProlog 已经生成过, 这里可以跳过 | |
IGPT_EPILOG | |
调用 emitBegFnEpilog(igPh) | |
增加 emitEpilogCnt | |
调用 emitBegPrologEpilog(igPh) | |
如果之前的ig未保存则调用 emitSavIG() 保存 | |
设置 igPh->igFlags &= ~IGF_PLACEHOLDER // 不再是place holder | |
设置 emitNoGCIG = true // GC不可中断 | |
设置 emitForceNewIG = false // 不要求一定创建新ig | |
设置 emitPrevGCrefVars = igPh->igPhData->igPhPrevGCrefVars // TODO | |
设置 emitPrevGCrefRegs = igPh->igPhData->igPhPrevGCrefRegs // TODO | |
设置 emitPrevByrefRegs = igPh->igPhData->igPhPrevByrefRegs // TODO | |
设置 emitThisGCrefVars = igPh->igPhData->igPhInitGCrefVars // TODO | |
设置 emitInitGCrefVars = igPh->igPhData->igPhInitGCrefVars // TODO | |
设置 emitThisGCrefRegs = emitInitGCrefRegs = igPh->igPhData->igPhInitGCrefRegs // TODO | |
设置 emitThisByrefRegs = emitInitByrefRegs = igPh->igPhData->igPhInitByrefRegs // TODO | |
调用 emitComp->funSetCurrentFunc(ig->igFuncIdx), 设置当前的函数(funclet)的序号 | |
调用 emitGenIG(ig), 设置ig为当前生成代码的ig | |
重置 emitCntStackDepth = 0 // TODO | |
确保 emitCurStackLvl == 0 // TODO | |
调用 emitEpilogBegLoc.CaptureLocation(this) // 记录 ig 和 codePos | |
调用 codeGen->genFnEpilog(igPhBB) | |
这个函数有多个版本, 这里只分析x86和x64的版本 | |
设置 gcInfo.gcVarPtrSetCur = getEmitter()->emitInitGCrefVars | |
设置 gcInfo.gcRegGCrefSetCur = getEmitter()->emitInitGCrefRegs | |
设置 gcInfo.gcRegByrefSetCur = getEmitter()->emitInitByrefRegs | |
如果 !FEATURE_STACK_FP_X87 | |
调用 genRestoreCalleeSavedFltRegs(compiler->compLclFrameSize) | |
枚举进入函数时, 从xmm寄存器保存到栈上的float变量 | |
添加把它们恢复到xmm寄存器的指令 (movaps 或 movupd) | |
如果 !doubleAlignOrFramePointerUsed() // 不使用frame pointer(rbp)并且不需要double align | |
如果 compiler->compLclFrameSize // stack frame上有本地变量 | |
如果是 x86, 并且 compiler->compLclFrameSize == sizeof(void*) | |
添加 pop ecx // 只有一个, 可以直接pop | |
调用 regTracker.rsTrackRegTrash(REG_ECX) // rsRegValues[reg].rvdKind = RV_TRASH | |
否则 | |
添加 add rsp, compiler->compLclFrameSize // prolog中sub的大小这里add回去 | |
调用 genPopCalleeSavedRegisters() | |
这个函数有多个版本, 这里只分析x86和x64的版本 | |
如果 rbx 已修改, 添加 pop rbx | |
如果 rbp 已修改, 添加 pop rbp (只有在不使用frame pointer时才有可能发生) | |
如果 rsi 已修改, 添加 pop rsi (仅UNIX_AMD64_ABI) | |
如果 rdi 已修改, 添加 pop rdi (仅UNIX_AMD64_ABI) | |
如果 r12 已修改, 添加 pop r12 (仅_TARGET_AMD64_) | |
如果 r13 已修改, 添加 pop r13 (仅_TARGET_AMD64_) | |
如果 r14 已修改, 添加 pop r14 (仅_TARGET_AMD64_) | |
如果 r15 已修改, 添加 pop r15 (仅_TARGET_AMD64_) | |
确保 pop 的数量和之前 push 的数量一致 | |
否则 // 使用frame pointer(rbp) | |
如果 compiler->genDoubleAlign() | |
添加 add rsp, compiler->compLclFrameSize | |
本地变量 needMovEspEbp = true | |
否则 | |
如果 compiler->compLocallocUsed | |
本地变量 needLea = true | |
否则如果 RBM_CALLEE_SAVED 中的寄存器未被修改 | |
如果 compiler->compLclFrameSize != 0 // stack frame上有本地变量 | |
如果是 x64 | |
本地变量 needLea = true // x64不可以使用 mov rsp, rbp, 需要用lea代替 | |
否则 | |
本地变量 needMovEspEbp = true | |
否则如果 compiler->compLclFrameSize == 0 | |
不需要再 pop callee-saved registers 之前做任何事情 | |
否则如果 compiler->compLclFrameSize == REGSIZE_BYTES | |
添加 pop ecx // 只有一个, 可以直接pop | |
调用 regTracker.rsTrackRegTrash(REG_ECX) // rsRegValues[reg].rvdKind = RV_TRASH | |
否则 | |
本地变量 needLea = true | |
如果 needLea | |
布局: [ compLclFrameSize, compCalleeRegsPushed, ebp保存的地址 ] | |
如果是x64 | |
添加 lea esp, [ebp - (genSPtoFPdelta - compiler->compLclFrameSize)] | |
如果 localloc 未使用, 实际等于 [ebp - compCalleeRegsPushed * REGSIZE_BYTES] | |
如果 localloc 已使用, 也会让esp指向callee saved regs | |
否则 | |
添加 lea esp, [ebp - compiler->compCalleeRegsPushed * REGSIZE_BYTES] | |
调用 genPopCalleeSavedRegisters(), 同上 | |
如果 needMovEspEbp // 一定是x86, x64不允许(也不需要) | |
添加 mov esp, ebp | |
添加 pop ebp | |
调用 getEmitter()->emitStartExitSeq() | |
调用 emitExitSeqBegLoc.CaptureLocation(this) // 记录 ig 和 codePos | |
如果 block->bbFlags & BBF_HAS_JMP | |
如果 block->lastNode()->gtOper == GT_JMP | |
添加 call jmpNode->gtVal.gtVal1 // tail call | |
否则 | |
添加 jmp rax // fast tail call | |
否则 | |
如果是 x86, 添加 ret stkArgSize (同时减去通过栈传入的参数的大小) | |
否则添加 ret | |
调用 emitEndFnEpilog() | |
调用 emitEndPrologEpilog() | |
设置 emitNoGCIG = false | |
调用 emitSavIG() 保存当前的ig | |
重置 emitCurStackLvl = 0 | |
重置 emitCntStackDepth = sizeof(int) | |
如果是 x86 | |
计算 emitEpilogSize = emitExitSeqBegLoc - emitEpilogBegLoc | |
epilog 除去 ret 的大小 | |
计算 emitExitSeqSize = emitCodeOffset(emitCurIG, emitCurOffset()) - emitExitSeqBegLoc | |
ret 的大小 | |
IGPT_FUNCLET_PROLOG | |
调用 emitBegFuncletProlog(igPh) | |
调用 emitBegPrologEpilog(igPh), 同上 | |
调用 codeGen->genFuncletProlog(igPhBB) | |
这个函数有多个版本, 这里只分析x64版本的函数 | |
调用 gcInfo.gcResetForBB(), 同上 | |
调用 compiler->unwindBegProlog(), 同上 | |
添加 push rbp | |
调用 compiler->unwindPush(REG_FPBASE), 同上 | |
调用 genPushCalleeSavedRegisters(), 同上 | |
调用 genAllocLclFrame(genFuncletInfo.fiSpDelta, initReg, &initRegZeroed, maskArgRegsLiveIn) | |
从 rsp 分配本地变量需要的空间(PSP Slot + outgoing argument space), 同上 | |
TODO: fiSpDelta的计算 | |
调用 genPreserveCalleeSavedFltRegs(genFuncletInfo.fiSpDelta) | |
枚举compCalleeFPRegsSavedMask & RBM_FLT_CALLEE_SAVED | |
添加从xmm寄存器备份到栈的指令 (movaps 或 movupd) | |
调用 compiler->unwindEndProlog(), 同上 | |
添加 mov rbp, [rcx+fiPSP_slot_InitialSP_offset] // 例如rbp跟rsp差12, 原psp是[rbp-4], 则这里是8 | |
调用 regTracker.rsTrackRegTrash(REG_FPBASE), 同上 | |
添加 mov [rsp+fiPSP_slot_InitialSP_offset], rbp // 复制PSPSym到funclet自己的frame | |
如果 fiFunction_InitialSP_to_FP_delta != 0 | |
添加 lea rbp, [rbp+fiFunction_InitialSP_to_FP_delta] // 例如rbp跟rsp差12, 这里就是12 | |
根据InitialSP推算出原rbp, 现在rbp保存的就是原rbp, 可以使用[rbp-偏移值]访问主函数中的变量 | |
调用 regSet.rsRemoveRegsModified(RBM_FPBASE) | |
尽管这里修改了rbp, 但是rbp不应该在modified regs里面(防止genPushCalleeSavedRegisters这类函数处理) | |
调用 emitEndFuncletProlog() | |
调用 emitEndPrologEpilog(), 同上 | |
IGPT_FUNCLET_EPILOG | |
调用 emitBegFuncletEpilog(igPh) | |
调用 emitBegPrologEpilog(igPh), 同上 | |
调用 codeGen->genFuncletEpilog() | |
这个函数有多个版本, 这里只分析x64版本的函数 | |
调用 genRestoreCalleeSavedFltRegs(genFuncletInfo.fiSpDelta), 同上 | |
添加 add rsp, fiSpDelta // 释放之前预留的空间 | |
调用 genPopCalleeSavedRegisters(), 同上 | |
添加 pop rbp | |
添加 ret | |
调用 emitEndFuncletEpilog() | |
调用 emitEndPrologEpilog(), 同上 | |
调用 getEmitter()->emitFinishPrologEpilogGeneration() | |
调用 emitRecomputeIGoffsets() | |
枚举 emitIGlist | |
按 ig->igSize 重新计算 ig->igOffs | |
因为之前写入了不定长度的prolog, 所以需要重新s计算 | |
ig内部的跳转指令的 idjOffs 不需要更新, 因为它们都基于所属的ig而不是全局的开头 | |
更新 emitTotalCodeSize = 最终的offs | |
设置 emitCurIG = nullptr // 所有ig都生成完毕 | |
调用 getEmitter()->emitJumpDistBind() | |
枚举 emitJumpList | |
检查 long jmp 是否可以优化为 short jmp | |
算法 | |
本地变量 lstIG = 上一次处理的jmp所属的ig | |
本地变量 adjIG = 记录历史ig优化减少的代码大小 | |
本地变量 adjLJ = 记录当前ig优化减少的代码大小 | |
如果 jmp 所属的 ig 不等于前面的 lstIG | |
发现了新 ig 里面的第一个 jmp | |
枚举 [lstIG->igNext, jmp], 设置 lstIG->igOffs -= adjIG // 调整ig本身的偏移值 | |
设置 adjLJ = 0 | |
设置 lstIG = jmpIG | |
jmp->idjOffs -= adjLJ // jmp在当前ig里面的偏移值, 减去前面jmp优化的大小 | |
获取 tgtIG = jmp->idAddr()->iiaIGlabel // 跳转到的目标 | |
如果未绑定, 则先设置 iiaIGlabel = emitCodeGetCookie(jmp->idAddr()->iiaBBlabel) | |
本地变量 srcInstrOffs = jmpIG->igOffs + jmp->idjOffs // jmp指令的偏移值 = 所在ig的偏移值 + 指令偏移值 | |
本地变量 srcEncodingOffs = srcInstrOffs + ssz // 使用短跳转时下一条指令的地址(相对跳转时基于这个值计算) | |
本地变量 dstOffs = tgtIG->igOffs // 跳转目标的偏移值, 跳转目标一定会是ig的开头 | |
计算是否可以优化为短跳转 | |
如果 jmpIG->igNum < tgtIG->igNum // forward jump | |
jmpDist = (dstOffs -= adjIG) - srcEncodingOffs | |
如果jmpDist足够小代表可以优化为短跳转 | |
否则 // backward jump | |
jmpDist = srcEncodingOffs - dstOffs | |
如果jmpDist足够小代表可以优化为短跳转 | |
如果可以优化为短跳转 | |
调用 emitSetShortJump(jmp) 修改jmp指令的格式 | |
部分情况下会无法修改(例如包含hot => cold的跳转时必须kept long), 无法修改时跳过处理 | |
优化后调整 | |
adjIG += sizeDif // 叠加缩减的大小, 所有ig的值 | |
adjLJ += sizeDif // 叠加缩减的大小, 当前ig的值 | |
jmpIG->igSize -= (unsigned short)sizeDif // 缩减指令所占的大小 | |
emitTotalCodeSize -= sizeDif // 统计 | |
收尾处理 | |
如果前面有 adjIG, 调整剩余所有ig的 igOffs -= adjIG | |
调用 getEmitter()->emitComputeCodeSizes() | |
设置 emitComp->info.compTotalHotCodeSize = hot code的指令大小合计 (emitFirstColdIG->igOffs) | |
设置 emitComp->info.compTotalColdCodeSize = cold code的指令大小合计 (emitTotalCodeSize - emitTotalHotCodeSize) | |
调用 compiler->unwindReserve() | |
预留unwind信息所需的空间 | |
枚举main function和funclets | |
调用 unwindReserveFunc(funGetFunc(funcIdx)) | |
调用 unwindReserveFuncHelper(func, /* isHotCode */ true) | |
如果 isHotCode | |
设置 func->unwindHeader.SizeOfProlog = 最后写入的UNWIND_CODE的CodeOffset | |
设置 func->unwindHeader.CountOfUnwindCodes = 之前写入的 UNWIND_CODE 的数量 | |
在之前写入的unwind code前面, 写入UNWIND_INFO, 内容是func->unwindHeader的复制 | |
写入后的布局是 [ UNWIND_INFO, UNWIND_CODE, UNWIND_CODE, ... ] | |
本地变量 unwindCodeBytes = 包含刚才写入的UNWIND_INFO的总长度 | |
调用 eeReserveUnwindInfo(isFunclet, isColdCode, unwindCodeBytes) | |
设置 m_totalUnwindSize += ALIGN_UP(unwindSize + sizeof(ULONG)/* personality routine */, sizeof(ULONG)) | |
设置 m_totalUnwindInfos++ | |
如果 fgFirstColdBlock != nullptr | |
调用 unwindReserveFuncHelper(func, /* isHotCode */ false), 同上 | |
PHASE_EMIT_CODE | |
调用 getEmitter()->emitEndCodeGen( | |
compiler, // 函数对应的Compiler对象 | |
trackedStackPtrsContig, // 在栈上的受跟踪的ref是否保证连续, x64位是false, x86是!compiler->opts.compDbgEnC | |
genInterruptible, // 是否生成完全可中断的代码 | |
genFullPtrRegMap, // TODO, 等于 genInterruptible || !isFramePointerUsed | |
(compiler->info.compRetType == TYP_REF), // 函数是否返回gc ref | |
compiler->compHndBBtabCount, // eh handler的数量 | |
&prologSize, // out arg, prolog的大小 | |
&epilogSize, // out arg, epilog的大小 | |
codePtr, // out arg, 保存hot code汇编代码的地址 | |
&coldCodePtr, // out arg, 保存cold code汇编代码的地址 | |
&consPtr) // out arg, 保存只读数据的开始地址 | |
本地变量 ig = 当前处理的ig | |
本地变量 consBlock = 保存只读数据的开始地址, 用于传给 allocMem | |
本地变量 coldCodeBlock = 保存cold code的开始地址 | |
本地变量 cp = 当前写入指令使用的地址 | |
本地变量 emitSimpleStkUsed = TODO | |
设置 *epilogSize = emitEpilogSize + emitExitSeqSize | |
调用 emitCmpHandle->allocMem( // jitinterface.cpp | |
emitTotalHotCodeSize, // hot code的指令大小合计 | |
emitTotalColdCodeSize, // cold code的指令大小合计 | |
emitConsDsc.dsdOffs, // 只读内容的大小合计 | |
xcptnsCount, // eh handler的数量 | |
allocMemFlag, // 分配内存的参数, 默认是 CORJIT_ALLOCMEM_DEFAULT_CODE_ALIGN | |
(void**)&codeBlock, // out arg, 保存hot code的开始地址 | |
(void**)&coldCodeBlock, // out arg, 保存cold code的开始地址 | |
(void**)&consBlock) // out arg, 保存只读数据的开始地址 | |
函数 CEEJitInfo::allocMem 不支持分配 cold code | |
确保 emitTotalColdCodeSize 一定是 0 | |
设置 *coldCodeBlock = nullptr | |
调用 m_jitManager->allocCode( // codeman.cpp | |
m_pMethodBeingCompiled, // 当前编译函数的 MethodDesc, 来源是 UnsafeJitFunction 构建的 CEEJitInfo | |
totalSize.Value(), // hotCodeSize + roDataSize + m_totalUnwindSize | |
flag, // 分配内存的参数, 默认是 CORJIT_ALLOCMEM_DEFAULT_CODE_ALIGN | |
m_totalUnwindInfos, // unwind信息的数量(大小已经叠加到totalSize) | |
&m_moduleBase) // out arg, 使用的 HeapList 对象, 接收下面的pCodeHeap | |
对codeHeap上锁 // m_CodeHeapCritSec | |
如果是 LCGMethod (动态函数) | |
可以在代码后面保存 RealCodeHeader | |
设置 totalSize = totalSize + realHeaderSize | |
本地变量 pCode = allocCodeRaw( // codeman.cpp | |
&requestInfo, // 申请代码的要求, 这里只指定了MethodDesc, 其他项无要求 | |
sizeof(CodeHeader), // header的大小 | |
totalSize, // 总大小 | |
alignment, // 对其 | |
&pCodeHeap) // out arg, 使用的 HeapList 对象, 不传入时不设置 | |
调用 pInfo->setRequestSize(header+blockSize+(align-1)) | |
循环 | |
获取 pCodeHeap = pInfo->m_pAllocator->m_pLastUsedCodeHeap // 最后使用的heap | |
设置 pInfo->m_pAllocator->m_pLastUsedCodeHeap = nullptr | |
本地环境中 m_pAllocator == | |
m_pMD.GetLoaderModule()->GetLoaderAllocator() == | |
GlobalLoaderAllocator | |
如果 pCodeHeap == nullptr 或者不满足 requestInfo 指定的要求(例如地址范围) | |
获取 pCodeHeap = GetCodeHeap(pInfo), 会返回符合要求并且maxCodeHeapSize最大的HeapList | |
如果仍然获取失败则跳出循环 | |
设置 mem = (pCodeHeap->pHeap)->AllocMemForCode_NoThrow(header, blockSize, align) | |
如果 m_cbMinNextPad > header | |
设置 header = m_cbMinNextPad | |
这里用于防止code被放在同一个nibble map entry中, 如果地址接近需要错开(增加header的大小) | |
设置 p = m_LoaderHeap.AllocMemForCode_NoThrow(header, size, alignment) | |
这里的 m_LoaderHeap 是 ExplicitControlLoaderHeap | |
返回 UnlockedAllocMemForCode_NoThrow(header, size, alignment) | |
本地变量 cbAllocSize = dwHeaderSize + dwCodeSize + dwCodeAlignment - 1 // 上限 | |
如果 cbAllocSize.Value() > GetBytesAvailCommittedRegion() | |
调用 GetMoreCommittedPages(cbAllocSize.Value()) | |
本地变量 pResult = ALIGN_UP(m_pAllocPtr + dwHeaderSize, dwCodeAlignment) | |
设置 m_pAllocPtr = pResult + dwCodeSize // 这里的 dwCodeSize 是前面的 totalSize | |
返回 pResult | |
说明 | |
内存布局 [CodeHeader, code] | |
CodeHeader 只包含 pRealCodeHeader | |
pRealCodeHeader 的类型是 _hpRealCodeHdr 保存在其他位置 | |
m_pAllocPtr 增加了 header + code 的大小 | |
返回 code 指针 | |
如果 p != nullptr | |
设置 m_cbMinNextPad = ALIGN_UP((SIZE_T)p + 1, BYTES_PER_BUCKET) - ((SIZE_T)p + size) | |
== ALIGN_UP(p + 1, NIBBLES_PER_DWORD * CODE_ALIGN) - (p + size) | |
== ALIGN_UP(p + 1, ((8*sizeof(DWORD)) >> LOG2_NIBBLE_SIZE) * CODE_ALIGN) - (p + size) | |
== ALIGN_UP(p + 1, ((8*4) >> 2) * 4) - (p + size) | |
== ALIGN_UP(p + 1, 32) - (p + size) | |
m_cbMinNextPad 可能是负数, 如果是负数则下次不需要调整header的大小 | |
返回 p | |
如果 mem != nullptr | |
分配成功, 可以跳出循环 | |
标记 pCodeHeap->SetHeapFull(), 下次循环使用其他 HeapList | |
如果 mem == nullptr | |
表示现有的所有 HeapList 都无法满足要求或者已经用尽, 需要创建一个新的 | |
设置 pList = GetCodeHeapList(pInfo->m_pMD, pInfo->m_pAllocator) | |
返回 m_DomainCodeHeaps->m_pTable 中的对象(第一个 m_pAllocator 匹配的对象) | |
如果 pList == nullptr | |
设置 pList = CreateCodeHeapList(pInfo) | |
新建 pNewList = new DomainCodeHeapList(); | |
设置 pNewList->m_pAllocator = pInfo->m_pAllocator | |
添加 pNewList 到 m_DomainCodeHeaps->m_pTable 的末尾 | |
返回 pNewList->m_value | |
设置 pCodeHeap = NewCodeHeap(pInfo, pList) | |
设置 mem = (pCodeHeap->pHeap)->AllocMemForCode_NoThrow(header, blockSize, align), 同上 | |
如果 mem == nullptr | |
抛出 out of memory 错误 | |
设置 pInfo->m_pAllocator->m_pLastUsedCodeHeap = pCodeHeap // 最后一次使用的 HeapList | |
如果有传入 pCodeHeap 则设置它的值 | |
如果 mem + blockSize > pCodeHeap->endAddress | |
设置 pCodeHeap->endAddress = mem + blockSize // HeapList 最后使用的地址, CanUseCodeHeap函数中会用到 | |
返回 mem | |
如果是 LCGMethod (动态函数) | |
设置 pMD->AsDynamicMethodDesc()->GetLCGMethodResolver()->m_recordCodePointer = (void*) pCode | |
本地变量 pCodeHdr = ((CodeHeader *)pCode) - 1 // 看前面 UnlockedAllocMemForCode_NoThrow, header在code前面 | |
如果是 LCGMethod (动态函数) | |
设置 pCodeHdr->pRealCodeHeader = pCode + blockSize // 真正的header在code后面 | |
否则 | |
设置 pCodeHdr->pRealCodeHeader = pMD->GetLoaderAllocator()->GetLowFrequencyHeap()->AllocMem(realHeaderSize) | |
调用 pCodeHdr->SetDebugInfo(NULL) | |
设置 pCodeHdr->pRealCodeHeader->phdrDebugInfo = nullptr | |
调用 pCodeHdr->SetEHInfo(NULL) | |
设置 pCodeHdr->pRealCodeHeader->phdrJitEHInfo = nullptr | |
调用 pCodeHdr->SetGCInfo(NULL) | |
设置 pCodeHdr->pRealCodeHeader->phdrJitGCInfo = nullptr | |
调用 pCodeHdr->SetMethodDesc(pMD) | |
设置 pCodeHdr->pRealCodeHeader->phdrMDesc = nullptr | |
调用 pCodeHdr->SetNumberOfUnwindInfos(nUnwindInfos) | |
设置 pCodeHdr->pRealCodeHeader->nUnwindInfos = nUnwindInfos | |
设置 *pModuleBase = (TADDR)pCodeHeap | |
调用 NibbleMapSet(pCodeHeap, pCode, /* bSet */ TRUE) | |
用于建立代码地址的bitmap, 算法 | |
pHdrMap: [ DOWRD, DOWRD, ... ] | |
=[ [ NIBBLE(4bit), NIBBLE, ...(8个) ], [ NIBBLE, NIBBLE, ...(8个) ], ... ] | |
pos = delta / 32 决定使用哪一个 NIBBLE | |
value = ((delta % 32) / 4) + 1 决定 NIBBLE 的值 | |
本地变量 delta = pCode - pHp->mapBase | |
本地变量 pos = delta >> LOG2_BYTES_PER_BUCKET | |
LOG2_BYTES_PER_BUCKET == 5 | |
也可以看做 delta / 32 | |
本地变量 value = bSet ? (((delta & MASK_BYTES_PER_BUCKET) >> LOG2_CODE_ALIGN) + 1) : 0 | |
MASK_BYTES_PER_BUCKET == 31 | |
LOG2_CODE_ALIGN == 2 | |
也可以看做 ((delta % 32) / 4) + 1 (最高可以是28/4+1 => 8 => 0b1000, 逆算可以(8-1)*4 => 28) | |
本地变量 index = pos >> LOG2_NIBBLES_PER_DWORD | |
LOG2_NIBBLES_PER_DWORD == 3 | |
也可以看做 pos / 8 (一个DWORD可以保存几个NIBBLES, 32 / 4 => 8) | |
本地变量 mask = ~(HIGHEST_NIBBLE_MASK >> ((pos & NIBBLES_PER_DWORD_MASK) << LOG2_NIBBLE_SIZE)) | |
HIGHEST_NIBBLE_MASK == 0xf0000000 | |
NIBBLES_PER_DWORD_MASK == 7 | |
LOG2_NIBBLE_SIZE == 2 | |
也可以看做 ~(0xf0000000 >> ((pos % 8) * 4)) (用于清除DWORD中的某一个NIBBLE, 例如 (0%8*4) => 清0xf0000000) | |
设置 value = value << (HIGHEST_NIBBLE_BIT - (pos & NIBBLES_PER_DWORD_MASK) << LOG2_NIBBLE_SIZE) | |
HIGHEST_NIBBLE_BIT == 32 - NIBBLE_SIZE == 28 | |
NIBBLES_PER_DWORD_MASK == 7 | |
LOG2_NIBBLE_SIZE == 2 | |
也可以看做 value << (28 - (pos % 8) * 4) (用于设置DWORD中的某一个NIBBLE, 例如 0xf<<28 => 设0xf0000000) | |
设置 *(pHp->pHdrMap + index) = *(pHp->pHdrMap + index) & mask | value | |
修改 DWORD 中指定 NIBBLE 的值 | |
设置 pHp->cBlocks += (bSet ? 1 : -1) | |
返回 pCodeHdr | |
设置 *codeBlock = m_CodeHeader->GetCodeStartAddress() // 返回 ((PTR_CodeHeader)this) + 1 | |
如果 roDataSize > 0 | |
设置 *roDataBlock = ALIGN_UP(*codeBlock + codeSize, roDataAlignment) | |
设置 m_theUnwindBlock = 剩余的地址 (大小是 m_totalUnwindSize) | |
设置 *codeAddr = emitCodeBlock = codeBlock | |
设置 *coldCodeAddr = emitColdCodeBlock = coldCodeBlock | |
设置 *consAddr = emitConsBlock = consBlock | |
重置 emitThisGCrefVars, emitThisGCrefRegs, emitThisByrefRegs | |
重置 gcInfo.gcVarPtrList, gcInfo.gcVarPtrLast | |
如果 emitGCrFrameOffsCnt // 有gc ref在栈上 | |
设置 emitGCrFrameLiveTab = new varPtrDsc*[emitGCrFrameOffsCnt] | |
有值时表示当前存活的ref变量在gcinfo中的信息, 用于构建gcinfo, 见emitUpdateLiveGCvars | |
设置 emitTrkVarCnt = cnt = emitComp->lvaTrackedCount // stack frame上跟踪的gc ref变量的数量 | |
设置 emitGCrFrameOffsTab = tab = new int[cnt] | |
这个数组保存 stack frame上受跟踪的gc ref变量的偏移值(dsc->lvStkOffs) | |
默认值是-1, 如果是by ref, 还会保存 offs | byref_OFFSET_FLAG(1) | |
设置 cp = codeBlock // 当前写入指令使用的地址 | |
枚举 emitIGlist | |
如果 ig == emitFirstColdIG | |
填充 hotcode 剩余的空间到 int 3 | |
切换 cp = coldCodeBlock | |
调用 emitGenGCInfoIfFuncletRetTarget(ig, cp) | |
如果 FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_) | |
如果 ig 是 igFlags & IGF_FINALLY_TARGET | |
调用 emitStackPop(cp, /*isCall*/ true, /*callInstrSize*/ 1, /*args*/ 0) | |
TODO: 这里插入了什么指令? | |
如果 !emitFullGCinfo | |
调用 emitRecordGCcall(cp, /*callInstrSize*/ 1) | |
TODO | |
调整 ig->igOffs 到实际的偏移值 (一般会一致) | |
记录 emitOffsAdj = ig->igOffs - emitCurCodeOffs(cp) | |
如果是 hot code, emitCurCodeOffs(cp) == cp - emitCodeBlock | |
如果是 cold code, emitCurCodeOffs(cp) == cp - emitColdCodeBlock + emitTotalHotCodeSize | |
设置 ig->igOffs = emitCurCodeOffs(cp) | |
如果 ig->igStkLvl != emitCurStackLvl | |
调用 emitStackPushN(cp, (ig->igStkLvl - (unsigned)emitCurStackLvl) / sizeof(int)) | |
添加指定个数的push让 emitCurStackLvl 等于ig->igStkLvl ? | |
TODO | |
如果 ig->igFlags !& IGF_EMIT_ADD | |
如果 ig->igFlags & IGF_GC_VARS | |
调用 emitUpdateLiveGCvars(ig->igGCvars(), cp) | |
记录当前在栈上的gcref变量 | |
变量 emitThisGCrefVset 表示是否已经根据最新的 emitThisGCrefVars 构建了 gcinfo | |
如果 emitThisGCrefVset 并且 vars 和 emitThisGCrefVars 相同, 则不需要更新 gcinfo, 返回 | |
设置 emitThisGCrefVars = vars | |
枚举 emitGCrFrameOffsTab | |
如果变量在 vars, 调用 emitGCvarLiveUpd( | |
offs, INT_MAX, (val & byref_OFFSET_FLAG) ? GCT_BYREF : GCT_GCREF, addr) | |
参考前面对 byref_OFFSET_FLAG 的说明 (数组元素的最后1bit等于1表示是byref, 否则是gcref) | |
如果 offs >= emitGCrFrameOffsMin && offs < emitGCrFrameOffsMax && isTracked | |
本地变量 disp = (offs - emitGCrFrameOffsMin) / sizeof(void*) | |
如果 emitGCrFrameLiveTab[disp] == nullptr // 当前不是live | |
调用 emitGCvarLiveSet(offs, gcType, addr, disp) | |
新建 desc = new (emitComp, CMK_GC) varPtrDsc | |
设置 desc->vpdBegOfs = emitCurCodeOffs(addr) // 从什么时候开始存活 | |
设置 desc->vpdVarNum = offs // 变量在栈上的偏移值 | |
设置 desc->vpdNext = nullptr // 链表的下一个元素 | |
如果 offs == emitSyncThisObjOffs // 这是带monitor的函数的this变量 | |
标记 desc->vpdVarNum |= this_OFFSET_FLAG(2) | |
如果 gcType == GCT_BYREF // 是byref不是gcref | |
标记 desc->vpdVarNum |= byref_OFFSET_FLAG(1) | |
除了 this_OFFSET_FLAG(2) 和 byref_OFFSET_FLAG(1) 以外还有 pinned_OFFSET_FLAG(2) | |
添加 desc 到 codeGen->gcInfo.gcVarPtrList 链表 | |
设置 emitGCrFrameLiveTab[disp] = desc | |
设置 emitThisGCrefVset = false | |
否则调用 emitGCvarDeadUpd(offs, addr) | |
如果 offs >= emitGCrFrameOffsMin && offs < emitGCrFrameOffsMax | |
本地变量 disp = (offs - emitGCrFrameOffsMin) / sizeof(void*) | |
如果 emitGCrFrameLiveTab[disp] != nullptr // 当前是live | |
调用 emitGCvarDeadSet(offs, addr, disp) | |
设置 desc->vpdEndOfs = emitCurCodeOffs(addr) // 存活到什么时候 | |
设置 emitGCrFrameLiveTab[disp] = nullptr | |
设置 emitThisGCrefVset = false | |
设置 emitThisGCrefVset = true // 已经根据最新的 emitThisGCrefVars 构建了 gcinfo | |
否则如果 !emitThisGCrefVset | |
调用 emitUpdateLiveGCvars(emitThisGCrefVars, cp), 同上 | |
如果 ig->igGCregs != emitThisGCrefRegs | |
调用 emitUpdateLiveGCregs(GCT_GCREF, GCregs, cp) | |
本地变量 emitThisXXrefRegs = (gcType == GCT_GCREF) ? emitThisGCrefRegs : emitThisByrefRegs | |
本地变量 emitThisYYrefRegs = (gcType == GCT_GCREF) ? emitThisByrefRegs : emitThisGCrefRegs | |
如果 emitFullGCinfo // TODO emitFullGCinfo是什么 | |
本地变量 dead = (emitThisXXrefRegs & ~regs) // 进入ig时不再存活的gcref寄存器 | |
本地变量 life = (~emitThisXXrefRegs & regs) // 进入ig时存活的gcref寄存器 | |
枚举 live, 调用 emitGCregLiveUpd(gcType, reg, addr) | |
调用 emitGCregLiveSet(gcType, regMask, addr, isThis) | |
新建 regPtrNext = codeGen->gcInfo.gcRegPtrAllocDsc() // 类型 regPtrDsc | |
添加到 gcRegPtrList 链表 | |
设置 regPtrNext->rpdGCtype = gcType // gcref还是byref | |
设置 regPtrNext->rpdOffs = emitCurCodeOffs(addr) // 当前的位置 | |
设置 regPtrNext->rpdArg = FALSE // 是否有 rpdArgType | |
rpdArgType包含了 rpdARG_POP(0), rpdARG_PUSH(1), rpdARG_KILL(2) | |
设置 regPtrNext->rpdCall = FALSE // 是否由call指令添加的 (可能从栈pop多个值) | |
设置 regPtrNext->rpdIsThis = isThis // 是否this变量 | |
设置 regPtrNext->rpdCompiler.rpdAdd = (regMaskSmall)regMask // 从当前的位置开始存活的寄存器 | |
regPtrNext->rpdCompiler.rpdDel = 0 // 从当前的位置开始不再存活的寄存器 | |
标记 emitThisXXrefRegs |= regMask | |
枚举 dead, 调用 emitGCregDeadUpd(reg, addr) | |
调用 emitGCregDeadSet(gcType /* 自动检测 */, regMask, addr) | |
新建 regPtrNext = codeGen->gcInfo.gcRegPtrAllocDsc() // 类型 regPtrDsc, 同上 | |
设置 regPtrNext->rpdGCtype = gcType // gcref还是byref | |
设置 regPtrNext->rpdOffs = emitCurCodeOffs(addr) // 当前的位置 | |
设置 regPtrNext->rpdCall = FALSE // 是否由call指令添加的 (可能从栈pop多个值) | |
设置 regPtrNext->rpdIsThis = FALSE // 是否this变量 | |
设置 regPtrNext->rpdArg = FALSE // 是否有 rpdArgType | |
设置 regPtrNext->rpdCompiler.rpdAdd = 0 // 从当前的位置开始存活的寄存器 | |
设置 regPtrNext->rpdCompiler.rpdDel = (regMaskSmall)regMask // 从当前的位置开始不再存活的寄存器 | |
否则 | |
更新 emitThisYYrefRegs &= ~regs // byref集合-gcref集合, 或gcref集合-byref集合 | |
更新 emitThisXXrefRegs = regs // 替换gcref集合或byref集合 | |
如果 (ig->igFlags & IGF_BYREF_REGS) && (ig->igByrefRegs() != emitThisByrefRegs) | |
调用 emitUpdateLiveGCregs(GCT_BYREF, byrefRegs, cp), 同上 | |
本地变量 id = (instrDesc*)ig->igData | |
设置 emitCurIG = ig | |
枚举 ig 中的 instr | |
设置 id += emitIssue1Instr(ig, id /* 指令 */, &cp /* 写入汇编的地址 */) | |
本地变量 is = emitOutputInstr(ig, id, dp) | |
这个函数根据ig中的instrDesc*写入汇编代码到dp(也就是上面的&cp) | |
这个函数有多个版本, 这里只分析x86和x64的版本 | |
本地变量 dst = *dp // 写入汇编的地址 | |
本地变量 sz = sizeof(instrDesc) // 仅用于检查是否生成了正确的指令 | |
本地变量 ins = id->idIns() // 指令 | |
本地变量 size = id->idOpSize() // 指令占的大小 | |
本地变量 GCvars = 未初始化的值 // TODO | |
判断 id->idInsFmt() // 指令的格式 | |
本地变量 code // TODO | |
本地变量 regcode // TODO | |
本地变量 args // TODO | |
本地变量 cnsVal // TODO | |
本地变量 addr // TODO | |
本地变量 recCall // TODO | |
本地变量 gcrefRegs // TODO | |
本地变量 byrefRegs // TODO | |
IF_NONE | |
指令无参数 | |
ins == INS_align | |
添加一定数量的nop让dst对齐16 | |
调用 emitOutputNOP(dst, (-(int)(size_t)dst) & 0x0f) | |
向 dst 写入 0x90, 也可能根据数量写入其他序列 例如 xchg ax, ax | |
ins == INS_nop | |
添加id->idCodeSize()个nop | |
调用 emitOutputNOP(dst, id->idCodeSize()), 同上 | |
ins == INS_cdq | |
用于扩展signed的rax的signed bit到rdx的指令 | |
调用 emitGCregDeadUpd(REG_EDX, dst), 标记edx中的ref已不再存活 | |
与下面的 IF_TRD, IF_TWR, IF_TRW 处理相同 | |
IF_TRD, IF_TWR, IF_TRW | |
操作x87的ST栈, 仅启用 FEATURE_STACK_FP_X87 时, 也属于无参数的指令 | |
获取 code = insCodeMR(ins) // [r/m], reg 或 [r/m] 格式时指令对应的数值 | |
如果 code & 0xFF000000 // 4个字节长的指令 | |
分别写入高位和低位, 例如 0xaabbccdd 实际在内存中会写入 bb aa dd cc | |
否则如果 code & 0x00FF0000 // 3个字节长的指令 | |
分别写入高位和低位, 例如 0xf3a548 (rep movsq) 实际在内存中会写入 f3 48 a5 | |
否则如果 code & 0xFF00 // 2个字节长的指令 | |
写入低位, 例如 0xa548 实际在内存中会写入 48 a5 | |
否则 // 1个字节长的指令 | |
写入低位的单个字节 | |
IF_CNS | |
参数是常量 | |
调用 emitOutputIV(dst, id) | |
获取 val = emitGetInsSC(id) // 常量值 | |
如果 ins == INS_jge | |
写入 7d xx (1 byte的常量值) | |
如果 ins == INS_loop | |
写入 e2 xx (1 byte的常量值) | |
如果 ins == INS_ret | |
写入 c2 xx xx (2 byte的常量值) | |
如果 ins == INS_push_hide, INS_push | |
如果常量范围在1 byte | |
写入 6a xx | |
否则 | |
必要时添加 REX.W 前缀 | |
添加这个前缀后表示指令接收64位参数(8 bytes) | |
这里的 INS_push_hide 和 INS_push 不需要 (见TakesRexWPrefix) | |
rex.w 前缀是 48 | |
对于3字节的指令 例如 bb dd cc | |
如果 bb 是 prefix, 添加 bb 48 dd cc | |
对于4字节的指令 例如 bb aa dd cc | |
如果 bb 和 aa 都是 prefix, 添加 aa 48 bb dd cc | |
如果只有 aa 是 prefix, 添加 aa bb 48 dd cc | |
其他情况 | |
添加 48 xx ... (原指令) | |
写入 68 xx xx xx xx (写入的仍然是4 bytes) | |
如果 id->idIsCnsReloc() | |
调用 emitRecordRelocation( | |
(void*)(dst - sizeof(INT32)), (void*)(size_t)val, IMAGE_REL_BASED_HIGHLOW) | |
写入距离当前pc的偏移值 (IMAGE_REL_BASED_HIGHLOW => IMAGE_REL_BASED_REL32) | |
计算 | |
location = dst - sizeof(INT32) | |
target = val | |
baseAddr = location + sizeof(INT32) // 下一条指令的开始地址 | |
delta = target - baseAddr // 距离pc的偏移值, 负数表示backward jump, 正数表示forward jump | |
写入 delta 到 location (替换掉原来的 4byte), 如果32位不足以容纳 delta 则需要添加 jump stub | |
设置 sz = emitSizeOfInsDsc(id), 仅用于检查 | |
IF_LABEL, IF_RWR_LABEL, IF_SWR_LABEL | |
参数是label(目标ig的地址或者偏移值) | |
调用 emitOutputLJ(dst, id) | |
本地变量 ssz = short jump时的参数大小 | |
本地变量 lsz = long jump时的参数大小 | |
本地变量 jmp = 是否跳转指令, push_hide push mov lea 是 false, 其他是 true | |
计算 | |
srcOffs = emitCurCodeOffs(dst) // 跳转指令距离函数开头的偏移值 | |
dstOffs = id->idAddr()->iiaIGlabel->igOffs // 跳转目标距离函数的偏移值 | |
distVal = relAddr ? | |
emitOffsetToPtr(dstOffs) - emitOffsetToPtr(srcOffs) : // 偏移值的差, 会考虑code block | |
emitOffsetToPtr(dstOffs) // 绝对值 | |
如果 dstOffs <= srcOffs | |
是向前跳转 | |
检查是否可以优化为short jmp, 如果可以修改id中的成员 | |
前面已经优化过一次了, 这里是补充性优化 | |
否则 | |
是向后跳转 | |
记录 id->idjOffs = dstOffs | |
检查是否可以优化为short jmp, 如果可以修改id中的成员 | |
如果 relAddr | |
distVal -= id->idjShort ? ssz : lsz // 实际偏移值应该从下一条指令算起 | |
如果 id->idjShort | |
如果 emitInstCodeSz(id) != JMP_SIZE_SMALL | |
因为优化而调整了最终的偏移值 | |
记录调整的值 emitOffsAdj += emitInstCodeSz(id) - JMP_SIZE_SMALL | |
添加 指令 (1 byte) | |
添加 distVal (1 byte) | |
记录 id->idjTemp.idjAddr = (distVal > 0) ? dst : nullptr // forward jump时记录值 | |
否则 | |
转换指令, 例如 INS_jmp => INS_l_jmp | |
添加 指令 (1 ~ 2 byte) | |
添加 distVal (4 byte) | |
如果不是 relAddr 或者跳转跨越了 cold code 和 hot code | |
调用 emitRecordRelocation 调整值, 同上 | |
如果指令是call | |
调用 emitGCregDeadUpdMask(emitThisGCrefRegs | emitThisByrefRegs, dst) | |
标记所有寄存器中的ref不再存活, 同上 | |
设置 sz = (id->idInsFmt() == IF_SWR_LABEL ? sizeof(instrDescLbl) : sizeof(instrDescJmp)), 仅用于检查 | |
IF_METHOD, IF_METHPTR | |
参数是函数或者指向函数的指针 | |
本地变量 recCall = 是否应该记录call的位置 (用于gc), 默认是 true, idIsNoGC 时设为 false | |
本地变量 args = 通过栈传递进来的参数数量 (<0表示caller pop args) | |
本地变量 gcrefRegs = 调用函数后包含gcref的寄存器列表 | |
本地变量 byrefRegs = 调用函数后包含byref的寄存器列表 | |
本地变量 GCvars = 调用函数后包含byref的栈变量列表 | |
设置 sz = id->idIsLargeCall() ? sizeof(instrDescCGCA) : sizeof(instrDesc), 仅用于检查 | |
如果 id->idInsFmt() == IF_METHPTR // 需要deref获取call的地址 | |
如果 id->idIsDspReloc() | |
写入 指令 05 (2 byte) | |
写入地址并调用 emitRecordRelocation 调整 | |
否则 | |
如果是x86则写入 指令 05 (2 byte) (disp32) | |
如果是x64则写入 指令 04 25 (3 byte) (sib + sib byte) | |
写入地址 (4 byte) | |
否则 // call的地址已知 | |
写入指令 (1 byte) | |
写入地址 (4 byte), 并使用 emitRecordRelocation 调整 | |
调用 emitUpdateLiveGCvars(GCvars, *dp) | |
更新 call 之前的位置的 gcinfo | |
调用 emitUpdateLiveGCregs(GCT_GCREF, gcrefRegs, dst) | |
更新 call 之后的位置的 gcinfo | |
如果 call 返回了gcref gcrefRegs 还包含 rax | |
调用 byrefRegs != emitThisByrefRegs | |
更新 call 之后的位置的 gcinfo | |
如果 call 返回了byref byrefRegs 还包含 rax | |
如果 recCall || args | |
如果 args >= 0 // callee pop args | |
调用 emitStackPop(dst, /*isCall*/ true, callInstrSize, args) | |
按 count 更新 emitCurStackLvl, 这里 count 是 0 | |
调用 emitStackPopLargeStk(addr, isCall, callInstrSize, count), 同下 | |
否则 // caller pop args | |
调用 emitStackKillArgs(dst, -args, callInstrSize) | |
如果 emitFullGCinfo && gcCnt.Value() | |
添加一个新的记录到 gcRegPtrList | |
标记 regPtrNext->rpdArg = GCInfo::rpdARG_KILL // 标记参数所在的寄存器不存活 | |
标记 regPtrNext->rpdPtrArg = gcCnt.Value() // push level(push++, pop--) | |
调用 emitStackPopLargeStk(addr, isCall, callInstrSize, count), 同下 | |
如果 !emitFullGCinfo && recCall | |
调用 emitRecordGCcall(dst, callInstrSize) | |
添加一个新的记录(callDsc)到 codeGen->gcInfo.gcCallDescList | |
IF_RRD, IF_RWR, IF_RRW | |
参数是单个寄存器 | |
调用 emitOutputR(dst, id) | |
如果 ins == INS_inc, INS_dec | |
这两个指令有short form和long form | |
short form是 40+r 或者 48+r | |
long form是 fe xx (8 bit) 或者 ff xx (16 32 64 bit) | |
x64或者操作的寄存器大小等于1byte时需要使用long form | |
x64扩展的寄存器还要添加prefix, 例如r8d~r15d | |
如果 ins == INS_pop, INS_pop_hide, INS_push, INS_push_hide | |
push 是 50+r, pop 是 58+r, 都是1 byte | |
x64扩展的寄存器还要添加prefix, 例如r8d~r15d | |
如果 ins == INS_seto, INS_setno, INS_setb, INS_setae, INS_sete, INS_setne, | |
INS_setbe, INS_seta, INS_sets, INS_setns, INS_setpe, INS_setpo, INS_setl, | |
INS_setge, INS_setle, INS_setg | |
这些指令都是 3 byte, 例如 sete al 是 0f 94 c0 (c0是modrm, mod: rm, rm: ax) | |
如果 ins == INS_mulEAX, INS_imulEAX | |
调用 emitGCregDeadUpd(REG_EAX, dst), 同上 | |
调用 emitGCregDeadUpd(REG_EDX, dst), 同上 | |
共用下面的处理 | |
如果 ins == 其他 | |
写入 code, 无前缀时一般是 2 byte | |
例如 imul edx 是 f7 ea (mod: rm, reg: imul, rm: dx) | |
判断 id->idInsFmt() | |
IF_RRD | |
读寄存器, 不需要更新gcinfo | |
IF_RWR | |
写寄存器 | |
如果 id->idGCref() | |
调用 emitGCregLiveUpd(id->idGCref(), id->idReg1(), dst), 同上 | |
否则 | |
调用 emitGCregDeadUpd(id->idReg1(), dst), 同上 | |
IF_RRW | |
读写寄存器 | |
如果 id->idGCref() | |
调用 emitGCregLiveUpd(id->idGCref(), id->idReg1(), dst), 同上 | |
否则 | |
不标记已经不存活, 因为有可能未更新原来的内容, 但要确保该寄存器不包含gcref | |
设置 sz = TINY_IDSC_SIZE, 仅用于检查 | |
IF_RRW_SHF // TODO | |
第一个参数是寄存器(RMW), 第二个参数是shift常量 | |
无前缀时写入3 byte, op modrm shift | |
例如 sar edi, 4 是 c1 ff 04 (mod: rm, reg: sar, rm: edi) | |
IF_RRD_RRD, IF_RWR_RRD, IF_RRW_RRD, IF_RRW_RRW | |
第一个参数是寄存器(read, write, read write) | |
第二个参数也是寄存器(read, read write) | |
调用 emitOutputRR(dst, id) | |
例如 mov rbx, rax 是 48 8b d8 (48: rex.w (64 bit), 8b: mov, d8: { mod: rm, reg: rbx, rm: rax }) | |
必要时更新gcinfo | |
设置 sz = TINY_IDSC_SIZE, 仅用于检查 | |
IF_RRD_CNS, IF_RWR_CNS, IF_RRW_CNS | |
第一个参数是寄存器(read, write, read write) | |
第二个参数是常量 | |
调用 dst = emitOutputRI(dst, id) | |
例如 mov rsi, 0x7fff7c6b5bd0 是 48 be d0 5b 6b 7c ff 7f 00 00 | |
设置 sz = emitSizeOfInsDsc(id), 仅用于检查 | |
IF_RWR_RRD_RRD | |
第一个是寄存器(write), 第二三个参数是寄存器(read) (AVX指令) | |
调用 dst = emitOutputRRR(dst, id) | |
找不到具体的例子, 跳过分析 | |
设置 sz = emitSizeOfInsDsc(id), 仅用于检查 | |
IF_RRW_RRW_CNS | |
第一二个参数是寄存器(read write), 第三个参数是常量 | |
同上, 找不到具体的例子, 跳过分析 | |
设置 sz = emitSizeOfInsDsc(id), 仅用于检查 | |
IF_ARD, IF_AWR, IF_ARW, IF_TRD_ARD, IF_TWR_ARD, IF_TRW_ARD, IF_AWR_TRD | |
第一个参数是address mode | |
例如 call [rax+0x20] 是 ff 50 20 (mod: rm+disp8, reg: call rm64, rm: rax) | |
如果指令是 call 会复用上面 IF_METHOD, IF_METHPTR 的处理更新gcinfo | |
设置 sz = emitSizeOfInsDsc(id), 仅用于检查 | |
IF_RRD_ARD, IF_RWR_ARD, IF_RRW_ARD | |
第一个参数是寄存器(read, write, read write) | |
第二个参数是address mode(read) | |
调用 emitOutputAM(dst, id, code | regcode) | |
例如 cmp [rdi], edi 是 39 3f (mod: [rm], reg: edi, rm: rdi) | |
设置 sz = emitSizeOfInsDsc(id), 仅用于检查 | |
IF_ARD_RRD, IF_AWR_RRD, IF_ARW_RRD | |
第一个参数是address mode(read, write, read write) | |
第二个参数是寄存器(read) | |
和上面的处理完全一样 | |
IF_ARD_CNS, IF_AWR_CNS, IF_ARW_CNS | |
第一个参数是address mode(read, write, read write) | |
第二个参数是常量 | |
调用 dst = emitOutputAM(dst, id, insCodeMI(ins), &cnsVal) | |
例如 mov byte ptr [rax], 0 是 c6 00 00 | |
设置 sz = emitSizeOfInsDsc(id) | |
IF_ARW_SHF | |
第一个参数是address mode(read write) | |
第二个参数是shift常量 | |
和上面的处理完全一样 | |
IF_SRD, IF_SWR, IF_SRW, IF_TRD_SRD, IF_TWR_SRD, IF_TRW_SRD, IF_SWR_TRD | |
第一个参数是栈上的变量 | |
找不到具体的例子, 跳过分析 | |
IF_SRD_CNS, IF_SWR_CNS, IF_SRW_CNS | |
第一个参数是栈上的变量(read, write, read write) | |
第二个参数是常量 | |
调用 emitOutputSV(dst, id, insCodeMI(ins), &cnsVal) | |
例如 mov dword ptr [rbp-0x16c], 0xffffffff 是 c7 85 94 fe ff ff ff ff ff ff | |
c7: mov (rm 16 32 64), (imm 16 32) | |
85: { mod: rm+disp32, reg: 0, rm: rbp } | |
94 fe ff ff: -0x16c | |
ff ff ff ff: 0xffffffff | |
设置 sz = emitSizeOfInsDsc(id), 仅用于检查 | |
IF_SRW_SHF | |
第一个参数是栈上的变量(read write) | |
第二个参数是shift常量 | |
和上面的处理完全一样 | |
IF_RRD_SRD, IF_RWR_SRD, IF_RRW_SRD | |
第一个参数是寄存器(read, write, read write) | |
第二个参数是栈上的变量(read) | |
调用 emitOutputSV(dst, id, code | regcode) | |
例如 mov rax, qword ptr [rbp-0x8] 是 48 8b 45 f8 | |
48: rex.w | |
8b: mov (r16 32 64), (rm 16 32 64) | |
45: { mod: rm+disp8, reg: rax, rm: rbp } | |
f8: -8 | |
IF_SRD_RRD, IF_SWR_RRD, IF_SRW_RRD | |
第一个参数是栈上的变量(read, write, read write) | |
第二个参数是寄存器(read) | |
调用 emitOutputSV(dst, id, code | regcode) | |
例如 mov qword ptr [rbp-0x8], rax 是 48 89 45 f8 | |
48: rex.w | |
89: mov (rm 16 32 64), (r16 32 64) | |
45: { mod: rm+disp8, reg: rax, rm: rbp } | |
f8: -8 | |
IF_MRD, IF_MRW, IF_MWR, IF_TRD_MRD, IF_TWR_MRD, IF_TRW_MRD, IF_MWR_TRD | |
第一个参数是内存(read, write, read write) (一般用于访问静态变量) | |
调用 emitOutputCV(dst, id, insCodeMR(ins) | 0x0500); | |
找不到具体的例子, 跳过分析 | |
设置 sz = emitSizeOfInsDsc(id), 仅用于检查 | |
IF_MRD_OFF | |
第一个参数是内存(read), 第二个参数是offset (一般用于访问静态变量的class member) | |
调用 emitOutputCV(dst, id, insCodeMI(ins)) | |
找不到具体的例子, 跳过分析 | |
IF_RRD_MRD, IF_RWR_MRD, IF_RRW_MRD | |
第一个参数是寄存器(read, write, read write), 第二个参数是内存(read) | |
调用 emitOutputCV(dst, id, code | regcode | 0x0500) | |
例如 lea rcx, [rip+0x68] 是 48 8d 0d 68 00 00 00 | |
48: rex.w | |
8d: lea (r16 32 64), (m) | |
0d: { mod: rm, reg: rcx, rm: rip+disp32 } | |
68 00 00 00: 0x68 | |
设置 sz = emitSizeOfInsDsc(id), 仅用于检查 | |
IF_RWR_MRD_OFF | |
第一个参数是寄存器(write), 第二个参数是内存(read), 第三个参数是offset | |
调用 emitOutputCV(dst, id, code | 0x30 | regcode) | |
找不到具体的例子, 跳过分析 | |
设置 sz = emitSizeOfInsDsc(id), 仅用于检查 | |
IF_MRD_RRD, IF_MWR_RRD, IF_MRW_RRD | |
第一个参数是内存(read, write, read write) | |
第二个参数的寄存器(read) | |
调用 emitOutputCV(dst, id, code | regcode | 0x0500) | |
找不到具体的例子, 跳过分析 | |
设置 sz = emitSizeOfInsDsc(id), 仅用于检查 | |
IF_MRD_CNS, IF_MWR_CNS, IF_MRW_CNS | |
第一个参数是内存(read, write, read write) | |
第二个参数是常量 | |
调用 emitGetInsDcmCns(id, &cnsVal) | |
从instr中获取常量并设置到cnsVal | |
调用 emitOutputCV(dst, id, insCodeMI(ins) | 0x0500, &cnsVal) | |
找不到具体的例子, 跳过分析 | |
设置 sz = emitSizeOfInsDsc(id), 仅用于检查 | |
IF_MRW_SHF | |
第一个参数是内存(read, write, read write) | |
第二个参数是shift常量 | |
调用 emitGetInsDcmCns(id, &cnsVal), 同上 | |
调用emitOutputCV(dst, id, insCodeMR(ins) | 0x0500, &cnsVal) | |
找不到具体的例子, 跳过分析 | |
设置 sz = emitSizeOfInsDsc(id), 仅用于检查 | |
IF_TRD_FRD, IF_TWR_FRD, IF_TRW_FRD | |
调用 emitOutputWord(dst, insCodeMR(ins) | 0xC000 | (id->idReg1() << 8)) | |
找不到具体的例子, 跳过分析 | |
IF_FRD_TRD, IF_FWR_TRD, IF_FRW_TRD | |
调用 emitOutputWord(dst, insCodeMR(ins) | 0xC004 | (id->idReg1() << 8)) | |
找不到具体的例子, 跳过分析 | |
确保 sz == emitSizeOfInsDsc(id), 仅用于检查是否生成了正确的指令 | |
更新当前的栈等级, 如果当前ig不在prolog或者epilog | |
判断 ins | |
INS_push | |
调用 emitStackPush(dst, id->idGCref()) | |
调用 emitStackPushLargeStk(addr, gcType) | |
枚举 count | |
记录 *u2.emitArgTrackTop++ = (BYTE)gcType | |
如果 !emitHasFramePtr || needsGC(gcType) | |
如果 emitFullGCinfo | |
添加新的 regPtrDsc (rpdARG_PUSH) 到 gcinfo->gcRegPtrList | |
设置 u2.emitGcArgTrackCnt++ // 当前push了多少个ref | |
修改 emitCurStackLvl += sizeof(int) | |
INS_pop | |
调用 emitStackPop(dst, /* isCall */ false, /*callInstrSize*/ 0, /* count */ 1) | |
如果 count > 0 | |
调用 emitStackPopLargeStk(addr, isCall, callInstrSize, count) | |
枚举 count | |
获取 gcType = (GCtype)(*--u2.emitArgTrackTop) | |
设置 u2.emitGcArgTrackCnt-- // 当前push了多少个ref | |
本地变量 gcrefRegs = emitThisGCrefRegs & calleeSavedRegs | |
本地变量 byrefRegs = emitThisByrefRegs & calleeSavedRegs | |
添加新的 regPtrDsc (rpdARG_POP) 到 gcinfo->gcRegPtrList | |
如果 isCall 或 count > 1 则设置 regPtrDsc.rpdCall = true | |
修改 emitCurStackLvl -= count * sizeof(int) | |
否则 | |
确保 isCall == true | |
如果 emitFullGCinfo | |
调用 emitStackPopLargeStk(addr, isCall, callInstrSize, 0), 同上 | |
INS_sub, 且寄存器是rsp | |
调用 emitStackPushN(dst, (unsigned)(emitGetInsSC(id) / sizeof(void*))) | |
调用 emitStackPushLargeStk(addr, GCT_NONE, count), 同上 | |
设置 emitCurStackLvl += count * sizeof(int) | |
INS_add, 且寄存器是rsp | |
调用 emitStackPop(dst, /*isCall*/ false, /*callInstrSize*/ 0, | |
/* count */ (unsigned)(emitGetInsSC(id) / sizeof(void*))), 同上 | |
如果有生成代码 (*dp != dst) | |
调用 MapCode(id->idDebugOnlyInfo()->idilStart, *dp) | |
如果 emitIsPDBEnabled | |
调用 emitPDBOffsetTable->MapSrcToDest(ilOffset, (int)(imgDest - emitImgBaseOfCode)) | |
这个函数的实现在外部, 这里不分析 | |
设置 *dp = dst // 更新外部的cp的值 | |
设置 emitCurIG = nullptr | |
设置 ig->igSize = cp - bp // bp是写入代码之前的cp的值, 这里表示写入了多少byte的代码 | |
如果 emitConsDsc.dsdOffs // 有只读内容 | |
调用 emitOutputDataSec(&emitConsDsc, consBlock) | |
枚举 sec->dsdList | |
如果 dsc->dsType == dataSection::blockAbsoluteAddr // absolute label table | |
数量是 dsc->dsSize / TARGET_POINTER_SIZE | |
枚举 dsc 中的 BasicBlock | |
获取 BasicBlock 对应的 ig (lab) | |
获取 ig 地址的绝对值 emitOffsetToPtr(lab->igOffs) | |
设置 bDst[i] = target | |
调用 emitRecordRelocation(&(bDst[i]), target, IMAGE_REL_BASED_HIGHLOW) 调整地址 | |
否则如果 dsc->dsType == dataSection::blockRelative32 // relative label table | |
数量是 dsc->dsSize / 4 | |
获取 fgFirstBB 对应的 ig (labFirst) | |
枚举 dsc 中的 BasicBlock | |
获取 BasicBlock 对应的 ig (lab) | |
设置 uDst[i] = lab->igOffs - labFirst->igOffs | |
否则 | |
调用 memcpy(dst, dsc->dsCont, dscSize) | |
设置 dst += dsc->dsSize | |
枚举 emitGCrFrameLiveTab | |
调用 emitGCvarDeadSet(of /* 栈上的偏移值 */, cp /* 当前的代码位置 */, vn /* 数组中的索引值 */) | |
同上, 所有ig完毕以后不会有栈上的ref存活 | |
如果 emitThisByrefRegs | |
调用 emitUpdateLiveGCregs(GCT_BYREF, RBM_NONE, cp), 同上, 所有ig完毕以后不会有byref的寄存器存活 | |
如果 emitThisGCrefRegs | |
调用 emitUpdateLiveGCregs(GCT_GCREF, RBM_NONE, cp), 同上, 所有ig完毕以后不会有gcref的寄存器存活 | |
如果 emitFwdJumps | |
调整 forward jump 的偏移值 | |
枚举 emitJumpList | |
如果 jmp->idjTemp.idjAddr == nullptr, 跳过 (只有forward jump会记录) | |
本地变量 tgt = jmp->idAddr()->iiaIGlabel // 跳转目标 | |
前面的 emitOutputLJ 会记录 idjOffs = dstOffs, 这里对比跟之前记录的值是否一致 | |
如果 jmp->idjOffs != tgt->igOffs | |
本地变量 adr = jmp->idjTemp.idjAddr // 保存跳转偏移值的地址(在指令中的) | |
本地变量 adj = jmp->idjOffs - tgt->igOffs // 之前记录的值比现在的值大了多少 | |
如果 jmp->idjShort | |
设置 *(BYTE*)adr -= (BYTE)adj // 调整偏移值 | |
否则 | |
设置 *(int*)adr -= adj // 调整偏移值 | |
本地变量 actualCodeSize = emitCurCodeOffs(cp) | |
如果 emitCurCodeOffs(cp) < emitTotalCodeSize | |
针对 emitCurCodeOffs(cp) ~ emitTotalCodeSize 的部分写入 0xcc (int 3) | |
设置 *prologSize = emitCodeOffset(emitPrologIG, emitPrologEndPos) | |
返回 prolog 的大小 | |
返回 actualCodeSize | |
返回值保存在 codeSize 中 | |
PHASE_EMIT_GCEH | |
设置 *nativeSizeOfCode = codeSize, 返回输出的代码大小 | |
调用 compiler->unwindEmit(*codePtr, coldCodePtr) | |
枚举 compFuncInfoCount // 主函数和funclet | |
调用 unwindEmitFunc(funGetFunc(funcIdx), pHotCode, pColdCode) | |
调用 unwindEmitFuncHelper(func, pHotCode, pColdCode, /* isHotCode */ true) | |
如果 isHotCode | |
本地变量 startOffset = 0(函数开始) 或 func->startLoc->CodeOffset(genEmitter) | |
本地变量 endOffset = info.compNativeCodeSize(函数结束) 或 func->endLoc->CodeOffset(genEmitter) | |
本地变量 unwindCodeBytes = sizeof(func->unwindCodes) - func->unwindCodeSlot // 写入的unwind信息的总大小 | |
本地变量 pUnwindBlock = &func->unwindCodes[func->unwindCodeSlot] // unwind信息的开头的指针 (UNWIND_INFO*) | |
参数 pColdCode = nullptr // 不把 pColdCode 传给下面的函数 | |
否则 | |
本地变量 startOffset = 0(函数开始) 或 func->coldStartLoc->CodeOffset(genEmitter) | |
本地变量 endOffset = info.compNativeCodeSize(函数结束) 或 func->coldEndLoc->CodeOffset(genEmitter) | |
设置 startOffset -= info.compTotalHotCodeSize // 应该从 emitColdCodeBlock 开始计算 | |
设置 endOffset -= info.compTotalHotCodeSize // 同上 | |
调用 eeAllocUnwindInfo( | |
(BYTE*)pHotCode, (BYTE*)pColdCode, startOffset, endOffset, unwindCodeBytes, pUnwindBlock, | |
(CorJitFuncKind)func->funKind); | |
调用 info.compCompHnd->allocUnwindInfo( // jitinterface.cpp | |
pHotCode, pColdCode, startOffset, endOffset, unwindSize, pUnwindBlock, funcKind) | |
本地变量 pRuntimeFunction = &pRealCodeHeader->unindInfos[m_usedUnwindInfos] | |
函数前的 codeHeader 会指向 readCodeHeader | |
readCodeHeader 中包含了 unwindInfos 数组 | |
unwindInfos 数组的元素类型是 PT_RUNTIME_FUNCTION, 包含 BeginAddress, EndAddress, UnwindData | |
本地变量 pUnwindInfo = (UNWIND_INFO*)&(m_theUnwindBlock[m_usedUnwindSize]) | |
储存 unwind 信息的内容的最终位置, 在前面的 allocCode 会分配到函数后面 | |
设置 m_usedUnwindInfos++ | |
设置 m_usedUnwindSize += unwindSize | |
如果是 x64 | |
m_usedUnwindSize += sizeof(ULONG) // 用于保存 personality routine | |
本地变量 currentCodeOffset = pHotCode - m_moduleBase // 距离所在 HeapList 的开头的偏移值 | |
本地变量 unwindInfoDelta = pUnwindInfo - m_moduleBase // 距离所在 HeapList 的开头的偏移值 | |
设置 pRuntimeFunction->BeginAddress = currentCodeOffset + startOffset // 对应范围开始距离 HeapList 的开头的偏移值 | |
设置 pRuntimeFunction->EndAddress = currentCodeOffset + endOffset // 对应范围结束距离 HeapList 的开头的偏移值 | |
设置 pRuntimeFunction->UnwindData = unwindInfoDelta // unwind信息距离 HeapList 的开头的偏移值 | |
调用 memcpy(pUnwindInfo, pUnwindBlock, unwindSize) | |
复制 unwind 信息到最终的储存位置 | |
设置 pUnwindInfo->Flags = UNW_FLAG_EHANDLER | UNW_FLAG_UHANDLER | |
如果是 x64 | |
设置 *((ULONG*)pUnwindInfo->UnwindCode[pUnwindInfo->CountOfUnwindCodes]) = | |
ExecutionManager::GetCLRPersonalityRoutineValue() | |
如果 pColdCode != nullptr | |
调用 unwindEmitFuncHelper(func, pHotCode, pColdCode, /* isHotCode */ false), 同上 | |
调用 genReportEH() | |
计算 EHCount = 需要保存的 EH Clause 数量 | |
调用 compiler->eeSetEHcount(EHCount) | |
调用 info.compCompHnd->setEHcount(cEH) // jitinterface.cpp | |
调用 m_jitManager->allocEHInfo(m_CodeHeader, cEH, &m_EHinfo_len) // codeman.cpp | |
获取 EHInfo = (BYTE*)allocEHInfoRaw(pCodeHeader, blockSize, pAllocationSize) | |
返回 GetJitMetaHeap(pMD)->AllocMem(S_SIZE_T(blockSize)) | |
调用 pCodeHeader->SetEHInfo((EE_ILEXCEPTION*) (EHInfo + sizeof(size_t))) | |
设置 pRealCodeHeader->phdrJitEHInfo = pEH | |
调用 pCodeHeader->GetEHInfo()->Init(numClauses) | |
设置 Kind = CorILMethod_Sect_FatFormat (byte 1) | |
设置 DataSize = sizeof(EE_ILEXCEPTION_CLAUSE) * ehCount (byte 234) | |
设置 *((size_t *)EHInfo) = numClauses // 一开始是 clause 的数量 | |
计算需要添加的 EH Clause | |
新建 CORINFO_EH_CLAUSE CORINFO_EH_CLAUSE | |
设置 clause.ClassToken = hndTyp // 捕捉的类型, 如果是filter则是filter offset | |
设置 clause.Flags = ToCORINFO_EH_CLAUSE_FLAGS(HBtab->ebdHandlerType) // 类型 | |
EH_HANDLER_CATCH => CORINFO_EH_CLAUSE_NONE | |
EH_HANDLER_FILTER => CORINFO_EH_CLAUSE_FILTER | |
EH_HANDLER_FAULT => CORINFO_EH_CLAUSE_FAULT | |
EH_HANDLER_FINALLY => CORINFO_EH_CLAUSE_FINALLY | |
设置 clause.TryOffset = compiler->ehCodeOffset(HBtab->ebdTryBeg) // try开始距离函数开始的偏移值 | |
等于 genEmitter->emitCodeOffset(ehEmitCookie(block), 0) | |
设置 clause.TryLength = HBtab->ebdTryLast == compiler->fgLastBB ? // try结束距离函数开始的偏移值 | |
compiler->info.compNativeCodeSize : | |
compiler->ehCodeOffset(HBtab->ebdTryLast->bbNext) | |
设置 clause.HandlerOffset = compiler->ehCodeOffset(HBtab->ebdHndBeg) // catch结束距离函数开始的偏移值 | |
设置 clause.HandlerLength = HBtab->ebdHndLast == compiler->fgLastBB ? // catch结束距离函数开始的偏移值 | |
compiler->info.compNativeCodeSize : | |
compiler->ehCodeOffset(HBtab->ebdHndLast->bbNext) | |
调用 compiler->eeSetEHinfo(XTnum, &clause) | |
调用 info.compCompHnd->setEHinfo(EHnumber, clause) // jitinterface.cpp | |
获取 pEHClause = m_CodeHeader->GetEHInfo()->EHClause(EHnumber) | |
类型是 EE_ILEXCEPTION_CLAUSE* | |
等于 pRealCodeHeader->phdrJitEHInfo->Clauses[EHnumber] | |
设置 pEHClause->TryStartPC = clause->TryOffset | |
设置 pEHClause->TryEndPC = clause->TryLength | |
设置 pEHClause->HandlerStartPC = clause->HandlerOffset | |
设置 pEHClause->HandlerEndPC = clause->HandlerLength | |
设置 pEHClause->ClassToken = clause->ClassToken | |
设置 pEHClause->Flags = (CorExceptionFlag)clause->Flags | |
如果是动态函数并且 ClassToken 对应类型 | |
设置 pEHClause->TypeHandle = 类型对应的 handle | |
设置 pEHClause->Flags |= COR_ILEXCEPTION_CLAUSE_CACHED_CLASS | |
设置 ++XTnum | |
例子 | |
EH Clause 涵括了代码抛出例外后可能跳转到的目标 | |
这个例子从 CodeGen::genReportEH 摘抄 | |
A | |
try (1) { | |
B | |
try (2) { | |
C | |
} catch (3) { | |
D | |
} catch (4) { | |
E | |
} | |
F | |
} catch (5) { | |
G | |
} | |
H | |
这样的代码会生成 | |
ABCFH // "main" code | |
D // funclet | |
E // funclet | |
G // funclet | |
包含以下的EH Clause | |
C -> D | |
C -> E | |
BCF -> G | |
D -> G // "duplicate" clause | |
E -> G // "duplicate" clause | |
调用 genCreateAndStoreGCInfo(codeSize, prologSize, epilogSize DEBUGARG(codePtr)) | |
调用 genCreateAndStoreGCInfoX64(codeSize, prologSize DEBUGARG(codePtr)) | |
新建 allowZeroAlloc = new AllowZeroAllocator(compiler->getAllocatorGC()) | |
新建 gcInfoEncoder = new GcInfoEncoder( | |
compiler->info.compCompHnd, compiler->info.compMethodInfo, allowZeroAlloc, NOMEM) | |
调用 gcInfo.gcInfoBlockHdrSave(gcInfoEncoder, codeSize, prologSize) | |
构建 gcinfo 的头部, 保存函数的各种信息 | |
设置 gcInfoEncoder->m_CodeLength = methodSize // gcinfo 文件夹下的 gcinfoencoder.cpp | |
本地变量 returnKind = 函数的返回类型 // RT_Object, RT_ByRef, RT_Scalar | |
设置 gcInfoEncoder->m_ReturnKind = returnKind | |
如果 compiler->isFramePointerUsed() | |
设置 gcInfoEncoder->m_StackBaseRegister = REG_FPBASE (rbp) | |
如果 compiler->info.compIsVarArgs | |
设置 gcInfoEncoder->m_IsVarArg = true | |
如果 compiler->lvaReportParamTypeArg() | |
本地变量 ctxtParamType = generic context的类型, GENERIC_CONTEXTPARAM_MD 或 GENERIC_CONTEXTPARAM_MT | |
调用 gcInfoEncoder->SetGenericsInstContextStackSlot( | |
compiler->lvaToCallerSPRelativeOffset( | |
compiler->lvaCachedGenericContextArgOffset(), | |
compiler->isFramePointerUsed()), | |
ctxtParamType) | |
设置 gcInfoEncoder->m_GenericsInstContextStackSlot = spOffsetGenericsContext | |
等于 isFpBased ? 本地变量距离rbp的偏移值 : 本地变量距离initial sp的偏移值 | |
设置 gcInfoEncoder->m_contextParamType = type | |
否则如果 compiler->lvaKeepAliveAndReportThis() | |
调用 gcInfoEncoder->SetGenericsInstContextStackSlot( | |
compiler->lvaToCallerSPRelativeOffset( | |
compiler->lvaCachedGenericContextArgOffset(), | |
compiler->isFramePointerUsed()), | |
GENERIC_CONTEXTPARAM_THIS), 同上 | |
如果 compiler->getNeedsGSSecurityCookie() | |
调用 gcInfoEncoder->SetGSCookieStackSlot( | |
compiler->lvaGetCallerSPRelativeOffset(compiler->lvaGSSecurityCookie), | |
prologSize, methodSize) | |
设置 gcInfoEncoder->m_GSCookieStackSlot = spOffsetGSCookie | |
设置 gcInfoEncoder->m_GSCookieValidRangeStart = validRangeStart // prologSize | |
设置 gcInfoEncoder->m_GSCookieValidRangeEnd = validRangeEnd // methodSize | |
否则如果 compiler->opts.compNeedSecurityCheck || | |
compiler->lvaReportParamTypeArg() || | |
compiler->lvaKeepAliveAndReportThis() | |
调用 gcInfoEncoder->SetPrologSize(prologSize) | |
设置 gcInfoEncoder->m_GSCookieValidRangeStart = prologSize | |
设置 gcInfoEncoder->m_GSCookieValidRangeEnd = prologSize+1 // 仅用于满足assert的要求 | |
如果 compiler->opts.compNeedSecurityCheck | |
调用 gcInfoEncoder->SetSecurityObjectStackSlot( | |
compiler->lvaGetCallerSPRelativeOffset(compiler->lvaSecurityObject)) | |
设置 gcInfoEncoder->m_SecurityObjectStackSlot = spOffset | |
如果 compiler->ehNeedsPSPSym() | |
调用 gcInfoEncoder->SetPSPSymStackSlot( | |
compiler->lvaGetInitialSPRelativeOffset(compiler->lvaPSPSym)) | |
设置 gcInfoEncoder->m_PSPSymStackSlot= spOffsetPSPSym | |
如果 compiler->ehAnyFunclets() | |
调用 gcInfoEncoder->SetWantsReportOnlyLeaf() | |
设置 gcInfoEncoder->m_WantsReportOnlyLeaf = true | |
调用 gcInfoEncoder->SetSizeOfStackOutgoingAndScratchArea(compiler->lvaOutgoingArgSpaceSize) | |
设置 gcInfoEncoder->m_SizeOfStackOutgoingAndScratchArea = size | |
调用 gcInfo.gcMakeRegPtrTable(gcInfoEncoder, codeSize, prologSize, /* mode */ GCInfo::MAKE_REG_PTR_MODE_ASSIGN_SLOTS) | |
这个函数有多个版本, 下面分析的是 gcencode.cpp:3768 | |
如果 mode == MAKE_REG_PTR_MODE_ASSIGN_SLOTS // first pass | |
设置 m_regSlotMap = new RegSlotMap | |
设置 m_stackSlotMap = new StackSlotMap | |
添加栈上的gc变量的信息到 gcInfoEncoder.m_SlotTable 并关联到 gcInfo.m_stackSlotMap | |
枚举 compiler->lvaTable 中, 存在于栈上的gc本地变量和通过栈传入的gc参数 | |
本地变量 GcSlotFlags flags | |
如果 varDsc->TypeGet() == TYP_BYREF 则 |= GC_SLOT_INTERIOR | |
如果 varDsc->lvPinned 则 |= GC_SLOT_PINNED | |
本地变量 GcStackSlotBase stackSlotBase | |
varDsc->lvFramePointerBased ? GC_FRAMEREG_REL : GC_SP_REL | |
本地变量 StackSlotIdKey sskey | |
m_offset = varDsc->lvStkOffs | |
m_fpRel = varDsc->lvFramePointerBased | |
m_flags = flags | |
根据 sskey 从 m_stackSlotMap 获取或者添加 GcSlotId varSlotId | |
添加时 varSlotId = gcInfoEncoderWithLog->GetStackSlotId(varDsc->lvStkOffs, flags, stackSlotBase) | |
设置 m_SlotTable[ m_NumSlots ].Slot.Stack.SpOffset = spOffset | |
设置 m_SlotTable[ m_NumSlots ].Slot.Stack.Base = spBase | |
设置 m_SlotTable[ m_NumSlots ].Flags = flags | |
返回 m_NumSlots++ | |
然后关联 m_stackSlotMap->Set(sskey, varSlotId) | |
如果本地变量是 struct 并且存在于栈上 | |
枚举它的成员并做出跟上面一样的处理 (分配和关联 stack slot) | |
添加内部使用的gc临时变量的信息到 gcInfoEncoder.m_SlotTable 并关联到 gcInfo.m_stackSlotMap | |
处理基本同上 | |
如果 compiler->lvaKeepAliveAndReportThis() | |
添加 this 变量的信息到 gcInfoEncoder.m_SlotTable 并关联到 gcInfo.m_stackSlotMap | |
处理基本同上 | |
调用 gcMakeVarPtrTable(gcInfoEncoder, mode) | |
如果 mode == MAKE_REG_PTR_MODE_ASSIGN_SLOTS && compiler->ehAnyFunclets() // first pass | |
调用 gcMarkFilterVarsPinned() | |
标记 filter 中的所有变量的生存范围, 加上 pinned_OFFSET_FLAG | |
枚举 compiler->compHndBBtab | |
如果 HBtab->HasFilter() | |
枚举 gcVarPtrList | |
如果生存周期和 filter 的代码范围无重叠, 跳过处理 | |
根据重叠的范围添加新的 desc 到 gcVarPtrList, 并标记filter中的范围为 pinned_OFFSET_FLAG | |
例如 begOffs < filterBeg < filterEnd < endOffs | |
原来的 desc 会由 begOffs ~ endOffs 变为 begOffs ~ filterBeg | |
添加 desc filterBeg ~ filterEnd, 标记 pinned_OFFSET_FLAG | |
添加 desc filterEnd ~ endOffs | |
枚举 gcVarPtrList (保存了栈上的gc变量生存周期的链表, | |
由 emitGCvarLiveSet, emitGCvarLiveUpd, emitGCvarDeadSet, emitGCvarDeadUpd 生成) | |
本地变量 lowBits = varTmp->vpdVarNum & OFFSET_MASK (this_OFFSET_FLAG 或 byref_OFFSET_FLAG) | |
本地变量 varOffs = varTmp->vpdVarNum & ~OFFSET_MASK | |
本地变量 begOffs = varTmp->vpdBegOfs | |
本地变量 endOffs = varTmp->vpdEndOfs | |
如果 endOffs == begOffs | |
变量存活周期是0, 跳过处理 | |
本地变量 GcSlotFlags flags | |
如果 lowBits & byref_OFFSET_FLAG 则 |= GC_SLOT_INTERIOR | |
如果 lowBits & pinned_OFFSET_FLAG 则 |= GC_SLOT_PINNED | |
本地变量 GcStackSlotBase stackSlotBase | |
compiler->isFramePointerUsed() ? GC_FRAMEREG_REL : GC_SP_REL | |
本地变量 StackSlotIdKey sskey | |
m_offset = varOffs | |
m_fpRel = compiler->isFramePointerUsed() | |
m_flags = flags | |
如果 mode == MAKE_REG_PTR_MODE_ASSIGN_SLOTS // first pass | |
根据 sskey 从 m_stackSlotMap 获取或者添加 GcSlotId varSlotId, 同上 | |
否则 // second pass | |
根据 ssKey 从 m_stackSlotMap 获取 GcSlotId varSlotId | |
调用 gcInfoEncoderWithLog->SetSlotState(begOffs, varSlotId, GC_SLOT_LIVE) | |
添加新的 LifetimeTransition transition 到 m_LifetimeTransitions 列表 | |
transition.SlotId = slotId // m_SlotTable中的序号 | |
transition.CodeOffset = instructionOffset // 代码偏移值 | |
transition.BecomesLive = ( slotState == GC_SLOT_LIVE ) // true: 变为存活, false: 变为不存活 | |
transition.IsDeleted = FALSE // 是否已删除 | |
调用 gcInfoEncoderWithLog->SetSlotState(endOffs, varSlotId, GC_SLOT_DEAD), 同上 | |
添加寄存器上的gc变量的信息到 gcInfoEncoder.m_SlotTable 并关联到 gcInfo.m_stackSlotMap | |
如果 compiler->codeGen->genInterruptible // 需要完全可中断 (添加整个期间的寄存器上的gc变量信息) | |
枚举 gcRegPtrList (保存了寄存器上的gc变量生存周期的链表) | |
由 emitGCregLiveSet, emitGCregLiveUpd, emitGCregDeadSet, emitGCregDeadUpd 生成) | |
如果 genRegPtrTemp->rpdArg // 有 rpdArgType | |
记录通过栈传递的变量(kill: dead once, push: live each, pop: dead once) | |
如果 genRegPtrTemp->rpdArgTypeGet() == rpdARG_KILL | |
如果 mode == MAKE_REG_PTR_MODE_DO_WORK && regStackArgFirst != nullptr // second pass | |
调用 gcInfoRecordGCStackArgsDead( | |
gcInfoEncoder, genRegPtrTemp->rpdOffs, regStackArgFirst, genRegPtrTemp) | |
枚举 [ regStackArgFirst ~ genRegPtrTemp ) 里面类型是 rpdARG_PUSH 的记录 | |
本地变量 StackSlotIdKey sskey | |
m_offset = genRegPtrTemp->rpdPtrArg // stack level | |
m_fpRel = false | |
m_flags = genRegPtrTemp->rpdGCtypeGet() == GCT_BYREF ? GC_SLOT_INTERIOR : GC_SLOT_BASE | |
根据 sskey 从 m_stackSlotMap 获取 varSlotId | |
调用 gcInfoEncoderWithLog->SetSlotState(instrOffset, varSlotId, GC_SLOT_DEAD), 同上 | |
设置 regStackArgFirst = nullptr | |
否则如果 genRegPtrTemp->rpdGCtypeGet() != GCT_NONE | |
如果 genRegPtrTemp->rpdArgTypeGet() == rpdARG_PUSH | |
确保 genRegPtrTemp->rpdPtrArg != 0 | |
调用 gcInfoRecordGCStackArgLive(gcInfoEncoder, mode, genRegPtrTemp) | |
本地变量 StackSlotIdKey sskey | |
m_offset = genRegPtrTemp->rpdPtrArg // stack level | |
m_fpRel = false | |
m_flags = genRegPtrTemp->rpdGCtypeGet() == GCT_BYREF ? GC_SLOT_INTERIOR : GC_SLOT_BASE | |
如果 mode == MAKE_REG_PTR_MODE_ASSIGN_SLOTS // first pass | |
根据 sskey 从 m_stackSlotMap 获取或者添加 GcSlotId varSlotId, 同上 | |
否则 // second pass | |
根据 sskey 从 m_stackSlotMap 获取 varSlotId | |
调用 gcInfoEncoderWithLog->SetSlotState(genStackPtr->rpdOffs, varSlotId, GC_SLOT_LIVE), 同上 | |
如果 regStackArgFirst == nullptr | |
设置 regStackArgFirst = genRegPtrTemp // 通过栈传递的第一个gc参数 | |
否则 // rpdARG_pop | |
如果 mode == MAKE_REG_PTR_MODE_DO_WORK && regStackArgFirst != nullptr // second pass | |
调用 gcInfoRecordGCStackArgsDead( | |
gcInfoEncoder, genRegPtrTemp->rpdOffs, regStackArgFirst, genRegPtrTemp), 同上 | |
设置 regStackArgFirst = nullptr | |
否则 // 无 rpdArgType | |
记录不再存活的寄存器 (增量) | |
本地变量 regMask = genRegPtrTemp->rpdCompiler.rpdDel & ptrRegs | |
本地变量 byRefMask = genRegPtrTemp->rpdGCtypeGet() == GCT_BYREF ? regMask : 0 | |
调用 gcInfoRecordGCRegStateChange( | |
gcInfoEncoder, mode, genRegPtrTemp->rpdOffs, regMask, GC_SLOT_DEAD, byRefMask, &ptrRegs) | |
枚举 regMask | |
更新 ptrRegs, 用于增量添加减少记录数量 | |
如果 newState == GC_SLOT_DEAD, 标记 *pPtrRegs &= ~tmpMask | |
否则标记 *pPtrRegs |= tmpMask | |
本地变量 GcSlotFlags regFlags = GC_SLOT_BASE | |
如果 tmpMask & byRefMask 则 |= GC_SLOT_INTERIOR | |
本地变量 RegSlotIdKey rskey | |
m_regNum = regNum | |
m_flags = regFlags | |
如果 mode == MAKE_REG_PTR_MODE_ASSIGN_SLOTS // first pass | |
根据 rskey 从 m_regSlotMap 获取或者添加 regSlotId | |
添加时 regSlotId = gcInfoEncoderWithLog->GetRegisterSlotId(regNum, regFlags) | |
m_SlotTable[ m_NumSlots ].Slot.RegisterNumber = regNum | |
m_SlotTable[ m_NumSlots ].Flags |= GC_SLOT_IS_REGISTER | |
返回 m_NumSlots++ | |
然后关联 m_regSlotMap->Set(rskey, regSlotId) | |
否则 // second pass | |
根据 rskey 从 m_regSlotMap 获取 regSlotId | |
调用 gcInfoEncoderWithLog->SetSlotState(instrOffset, regSlotId, newState), 同上 | |
记录开始存活的寄存器 (增量) | |
本地变量 regMask = genRegPtrTemp->rpdCompiler.rpdAdd & ~ptrRegs | |
本地变量 byRefMask = genRegPtrTemp->rpdGCtypeGet() == GCT_BYREF ? regMask : 0 | |
调用 gcInfoRecordGCRegStateChange( | |
gcInfoEncoder, mode, genRegPtrTemp->rpdOffs, regMask, GC_SLOT_LIVE, byRefMask, &ptrRegs), 同上 | |
如果 mode == MAKE_REG_PTR_MODE_DO_WORK // second pass | |
用于计算哪些范围是可以中断的, 例如 | |
m_InterruptibleRanges: [ ig3 begin ~ ig5 finish, ig7 begin ~ ig9 finish, ... ] | |
新建 InterruptibleRangeReporter reporter(prologSize, gcInfoEncoderWithLog) | |
调用 compiler->getEmitter()->emitGenNoGCLst(reporter) | |
枚举 emitIGlist 中标记为 IGF_NOGCINTERRUPT 的 ig // gc不可中断 | |
调用 reporter(ig->igFuncIdx, ig->igOffs, ig->igSize), 返回false时返回 | |
如果 igOffs < prevStart, 返回true // 已经处理过 | |
如果 igOffs > prevStart | |
调用 gcInfoEncoderWithLog->DefineInterruptibleRange(prevStart, igOffs - prevStart) | |
添加 InterruptibleRange range 到 m_InterruptibleRanges 列表 | |
range.NormStartOffset = normStartOffset | |
range.NormStopOffset = normStopOffset | |
设置 prevStart = igOffs + igSize | |
设置 prologSize = reporter.prevStart // 只影响本地变量 | |
如果 prologSize < codeSize // 需要报告剩余的部分 | |
调用 gcInfoEncoderWithLog->DefineInterruptibleRange(prologSize, codeSize - prologSize), 同上 | |
否则如果 compiler->isFramePointerUsed() // 不需要完全可中断, 但使用了rbp (只针对call生成寄存器上的gc变量信息) | |
如果 mode == MAKE_REG_PTR_MODE_DO_WORK // second pass | |
如果 gcCallDescList != nullptr // 计算链表长度并分配数组 | |
本地变量 numCallSites = gcCallDescList 的长度 | |
本地变量 pCallSites = new unsigned[numCallSites] | |
本地变量 pCallSiteSizes = new BYTE[numCallSites] | |
枚举 gcCallDescList // 记录call指令的链表, 从 emitOutputInstr => emitRecordGCcall 生成 | |
如果 mode == MAKE_REG_PTR_MODE_DO_WORK // second pass | |
设置 pCallSites[callSiteNum] = call->cdOffs - call->cdCallInstrSize // 指令偏移值(开头) | |
设置 pCallSiteSizes[callSiteNum] = call->cdCallInstrSize // 指令大小 | |
增加 callSiteNum++ | |
本地变量 | |
regMaskSmall gcrefRegMask = call->cdGCrefRegs & RBM_CALLEE_SAVED | |
regMaskSmall byrefRegMask = call->cdByrefRegs & RBM_CALLEE_SAVED | |
regMaskSmall regMask = gcrefRegMask | byrefRegMask // call前寄存器上的gc变量 & call可能覆盖的寄存器 | |
unsigned callOffset = call->cdOffs - call->cdCallInstrSize // call指令偏移值(开头) | |
调用 gcInfoRecordGCRegStateChange( | |
gcInfoEncoder, mode, callOffset, regMask, GC_SLOT_LIVE, byrefRegMask, nullptr), 同上 | |
标记call前寄存器上存活的gc变量 | |
调用 gcInfoRecordGCRegStateChange( | |
gcInfoEncoder, mode, call->cdOffs, regMask, GC_SLOT_DEAD, byrefRegMask, nullptr), 同上 | |
标记call后寄存器上不再存活的gc变量 | |
如果 mode == MAKE_REG_PTR_MODE_DO_WORK // second pass | |
调用 gcInfoEncoderWithLog->DefineCallSites(pCallSites, pCallSiteSizes, numCallSites) | |
设置 m_pCallSites = pCallSites | |
设置 m_pCallSiteSizes = pCallSiteSizes | |
设置 m_NumCallSites = numCallSites | |
否则 // 既不需要生成可中断的代码, 也不使用rbp (只针对有rpgArgType的call生成寄存器上的gc变量信息) | |
如果 mode == MAKE_REG_PTR_MODE_DO_WORK // second pass | |
本地变量 numCallSites | |
枚举 gcRegPtrList (保存了寄存器上的gc变量生存周期的链表) | |
如果 genRegPtrTemp->rpdArg && genRegPtrTemp->rpdIsCallInstr() | |
只处理有rpdArgType的call指令(push pop kill) | |
实际环境这里只有pop | |
numCallSites++ | |
本地变量 pCallSites = new unsigned[numCallSites] | |
本地变量 pCallSiteSizes = new BYTE[numCallSites] | |
枚举 gcRegPtrList (保存了寄存器上的gc变量生存周期的链表) | |
如果 genRegPtrTemp->rpdArg && genRegPtrTemp->rpdIsCallInstr() | |
本地变量 gcrefRegMask = genRegMaskFromCalleeSavedMask(genRegPtrTemp->rpdCallGCrefRegs) | |
本地变量 byrefRegMask = genRegMaskFromCalleeSavedMask(genRegPtrTemp->rpdCallByrefRegs) | |
本地变量 regMask = gcrefRegMask | byrefRegMask // call前寄存器上的gc变量 & call可能覆盖的寄存器 | |
本地变量 callOffset = genRegPtrTemp->rpdOffs - genRegPtrTemp->rpdCallInstrSize // call指令偏移值(开头) | |
调用 gcInfoRecordGCRegStateChange( | |
gcInfoEncoder, mode, callOffset, regMask, GC_SLOT_LIVE, byrefRegMask, nullptr), 同上 | |
标记call前寄存器上存活的gc变量 | |
调用 gcInfoRecordGCRegStateChange( | |
gcInfoEncoder, mode, genRegPtrTemp->rpdOffs, regMask, GC_SLOT_DEAD, byrefRegMask, nullptr), 同上 | |
标记call后寄存器上不再存活的gc变量 | |
如果 mode == MAKE_REG_PTR_MODE_DO_WORK // second pass | |
设置 pCallSites[callSiteNum] = callOffset // call指令偏移值(开头) | |
设置 pCallSiteSizes[callSiteNum] = genRegPtrTemp->rpdCallInstrSize // call指令大小 | |
增加 callSiteNum++ | |
如果 mode == MAKE_REG_PTR_MODE_DO_WORK // second pass | |
调用 gcInfoEncoderWithLog->DefineCallSites(pCallSites, pCallSiteSizes, numCallSites), 同上 | |
调用 gcInfoEncoder->FinalizeSlotIds() | |
带DEBUG编译时会设置 m_IsSlotTableFrozen = TRUE, 否则不做处理 | |
调用 gcInfo.gcMakeRegPtrTable(gcInfoEncoder, codeSize, prologSize, /* mode */ GCInfo::MAKE_REG_PTR_MODE_DO_WORK) | |
这个函数有多个版本, 下面分析的是 gcencode.cpp:3768 | |
除了mode以外和上面的调用一样 // second pass | |
如果 compiler->opts.compDbgEnC | |
计算预留的大小 (供EditAndContinue使用) | |
preservedAreaSize = 4 * REGSIZE_BYTES // return address + RBP + RSI + RDI | |
如果 compiler->info.compFlags & CORINFO_FLG_SYNCH // 同步函数 | |
如果 !(compiler->info.compFlags & CORINFO_FLG_STATIC) | |
设置 preservedAreaSize += REGSIZE_BYTES // this pointer | |
设置 preservedAreaSize += 4 // bool in synchronized methods that tracks whether the lock has been taken | |
调用 gcInfoEncoder->SetSizeOfEditAndContinuePreservedArea(preservedAreaSize) | |
设置 gcInfoEncoder->m_SizeOfEditAndContinuePreservedArea = slots | |
调用 gcInfoEncoder->Build() | |
变量解释 | |
m_Info1 主要的gc信息, 部分信息包含m_Info2中的bit索引值 | |
m_Info2 写入 call site livestates 和 transition chunks, 分开的原因是索引值不受影响 | |
函数解释 | |
GCINFO_WRITE(writer, val, numBits, counter) | |
写入bit到bit stream, 例如 1 0 0 1 => 9, counter是统计用的(可选) | |
反过来 [ 9 8 ] 代表 [ 1 0 0 1 0 0 0 0 ..., 0 0 0 1 0 0 0 0 ... ] | |
GCINFO_WRITE_VARL_U(writer, val, base, counter) | |
写入一个size_t val, base用于分chunk (类似dwarf, 数值小时可以使用更少的储存空间) | |
例如 val = 0b'11101110'1111011, base = 8 时按顺序写入 1 01111011 0 11101110 | |
GCINFO_WRITE_VAR_VECTOR(writer, vector, baseSkip, baseRun, counter) | |
写入 BitArray vector, 注意只会写入内容不会写入长度 | |
有3种格式 | |
simple: 写入 0, 然后写入各个bit | |
rle: 写入1 0, 然后写入 [ skip x(x bit是0), run x(x bit是1), skip x, ... ] | |
rle neg: 写入 1 1, 然后同上写入, 但使用的baseSkip和baseRun相反(例如1更多的情况下run>skip可以更小) | |
写入头部 | |
如果大部分参数都是默认值, 可以使用slim encoding | |
GCINFO_WRITE(m_Info1, 0, 1, FlagsSize) // slim encoding | |
GCINFO_WRITE(m_Info1, (m_StackBaseRegister == NO_STACK_BASE_REGISTER) ? 0 : 1, 1, FlagsSize) | |
GCINFO_WRITE(m_Info1, m_ReturnKind, SIZE_OF_RETURN_KIND_IN_SLIM_HEADER, RetKindSize) | |
否则要使用fat encoding | |
GCINFO_WRITE(m_Info1, 1, 1, FlagsSize); // fat encoding | |
GCINFO_WRITE(m_Info1, (m_IsVarArg ? 1 : 0), 1, FlagsSize) | |
GCINFO_WRITE(m_Info1, (hasSecurityObject ? 1 : 0), 1, FlagsSize) | |
GCINFO_WRITE(m_Info1, (hasGSCookie ? 1 : 0), 1, FlagsSize) | |
GCINFO_WRITE(m_Info1, ((m_PSPSymStackSlot != NO_PSP_SYM) ? 1 : 0), 1, FlagsSize) | |
GCINFO_WRITE(m_Info1, ((m_StackBaseRegister != NO_STACK_BASE_REGISTER) ? 1 : 0), 1, FlagsSize) | |
GCINFO_WRITE(m_Info1, (m_WantsReportOnlyLeaf ? 1 : 0), 1, FlagsSize) | |
GCINFO_WRITE(m_Info1, | |
((m_SizeOfEditAndContinuePreservedArea != NO_SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA) ? 1 : 0), | |
1, FlagsSize) | |
GCINFO_WRITE(m_Info1, (hasReversePInvokeFrame ? 1 : 0), 1, FlagsSize) | |
GCINFO_WRITE(m_Info1, m_ReturnKind, SIZE_OF_RETURN_KIND_IN_FAT_HEADER, RetKindSize) | |
写入代码长度 | |
GCINFO_WRITE_VARL_U(m_Info1, NORMALIZE_CODE_LENGTH(m_CodeLength), CODE_LENGTH_ENCBASE /*8*/, CodeLengthSize) | |
写入gscookie验证的代码范围 | |
如果 hasGSCookie | |
本地变量 normPrologSize = NORMALIZE_CODE_OFFSET(m_GSCookieValidRangeStart) | |
本地变量 normEpilogSize = NORMALIZE_CODE_OFFSET(m_CodeLength) - NORMALIZE_CODE_OFFSET(m_GSCookieValidRangeEnd) | |
GCINFO_WRITE_VARL_U(m_Info1, normPrologSize-1, NORM_PROLOG_SIZE_ENCBASE, ProEpilogSize) | |
GCINFO_WRITE_VARL_U(m_Info1, normEpilogSize, NORM_EPILOG_SIZE_ENCBASE, ProEpilogSize) | |
否则如果 hasSecurityObject || hasContextParamType | |
本地变量 normPrologSize = NORMALIZE_CODE_OFFSET(m_GSCookieValidRangeStart) | |
GCINFO_WRITE_VARL_U(m_Info1, normPrologSize-1, NORM_PROLOG_SIZE_ENCBASE, ProEpilogSize) | |
写入安全检查对象变量的堆栈偏移值 | |
如果 hasSecurityObject | |
GCINFO_WRITE_VARL_S(m_Info1, | |
NORMALIZE_STACK_SLOT(m_SecurityObjectStackSlot), SECURITY_OBJECT_STACK_SLOT_ENCBASE, SecObjSize) | |
写入gscookie变量的堆栈偏移值 | |
如果 hasGSCookie | |
GCINFO_WRITE_VARL_S(m_Info1, | |
NORMALIZE_STACK_SLOT(m_GSCookieStackSlot), GS_COOKIE_STACK_SLOT_ENCBASE, GsCookieSize) | |
写入pspsym变量的堆栈偏移值 | |
如果 m_PSPSymStackSlot != NO_PSP_SYM | |
GCINFO_WRITE_VARL_S(m_Info1, | |
NORMALIZE_STACK_SLOT(m_PSPSymStackSlot), PSP_SYM_STACK_SLOT_ENCBASE, PspSymSize) | |
写入generic context变量的堆栈偏移值 | |
如果 m_GenericsInstContextStackSlot != NO_GENERICS_INST_CONTEXT | |
GCINFO_WRITE_VARL_S(m_Info1, | |
NORMALIZE_STACK_SLOT(m_GenericsInstContextStackSlot), | |
GENERICS_INST_CONTEXT_STACK_SLOT_ENCBASE, GenericsCtxSize) | |
写入frame pointer使用的寄存器(x86和x64上是rbp) | |
GCINFO_WRITE_VARL_U(m_Info1, | |
NORMALIZE_STACK_BASE_REGISTER(m_StackBaseRegister), STACK_BASE_REGISTER_ENCBASE, StackBaseSize) | |
写入edit and continue预留的空间大小 | |
GCINFO_WRITE_VARL_U(m_Info1, | |
m_SizeOfEditAndContinuePreservedArea, SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA_ENCBASE, EncPreservedSlots) | |
写入pinvoke frame变量的堆栈偏移值 | |
GCINFO_WRITE_VARL_S(m_Info1, | |
NORMALIZE_STACK_SLOT(m_ReversePInvokeFrameSlot), REVERSE_PINVOKE_FRAME_ENCBASE, ReversePInvokeFrameSize) | |
写入TODO | |
如果使用了fat encoding | |
GCINFO_WRITE_VARL_U(m_Info1, | |
NORMALIZE_SIZE_OF_STACK_AREA(m_SizeOfStackOutgoingAndScratchArea), | |
SIZE_OF_STACK_AREA_ENCBASE, FixedAreaSize) | |
复制可中断的代码范围到本地变量 | |
UINT32 numInterruptibleRanges = (UINT32) m_InterruptibleRanges.Count() | |
InterruptibleRange *pRanges = m_pAllocator->Alloc(numInterruptibleRanges * sizeof(InterruptibleRange)) | |
m_InterruptibleRanges.CopyTo(pRanges) | |
删除在可中断范围内的call site | |
如果 PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED | |
本地变量 numCallSites = 0 | |
枚举 m_NumCallSites // call指令的偏移值(开头)的列表 | |
本地变量 callSite = m_pCallSites[callSiteIndex] + m_pCallSiteSizes[callSiteIndex] - 1 // 下一条指令-1 | |
本地变量 normOffset = NORMALIZE_CODE_OFFSET(callSite) | |
如果 normOffset 不在任意一个可中断的范围中 | |
更新 m_pCallSites[numCallSites++] = normOffset | |
GCINFO_WRITE_VARL_U(m_Info1, | |
NORMALIZE_NUM_SAFE_POINTS(numCallSites), NUM_SAFE_POINTS_ENCBASE, NumCallSitesSize) | |
更新 m_NumCallSites = numCallSites | |
写入可中断的代码范围的数量 | |
如果使用了fat encoding | |
GCINFO_WRITE_VARL_U(m_Info1, | |
NORMALIZE_NUM_INTERRUPTIBLE_RANGES(numInterruptibleRanges), | |
NUM_INTERRUPTIBLE_RANGES_ENCBASE, NumRangesSize) | |
写入各个call site | |
本地变量 numBitsPerOffset = CeilOfLog2(NORMALIZE_CODE_OFFSET(m_CodeLength)) | |
枚举 m_pCallSites | |
GCINFO_WRITE(m_Info1, normOffset, numBitsPerOffset, CallSitePosSize) | |
写入各个可中断的代码范围 | |
本地变量 lastStopOffset = 0 | |
枚举前面复制的本地变量 pRanges | |
本地变量 normStartDelta = pRanges[i].NormStartOffset - lastStopOffset | |
本地变量 normStopDelta = pRanges[i].NormStopOffset - pRanges[i].NormStartOffset | |
设置 lastStopOffset = normStopOffset | |
GCINFO_WRITE_VARL_U(m_Info1, normStartDelta, INTERRUPTIBLE_RANGE_DELTA1_ENCBASE, RangeSize) | |
GCINFO_WRITE_VARL_U(m_Info1, normStopDelta-1, INTERRUPTIBLE_RANGE_DELTA2_ENCBASE, RangeSize) | |
例如 3 ~ 5, 7 ~ 9 会写入 [ 3, 1 | 2, 1 ] | |
复制gc变量的生命周期记录到本地变量 | |
size_t numTransitions = m_LifetimeTransitions.Count() | |
LifetimeTransition *pTransitions = m_pAllocator->Alloc(numTransitions * sizeof(LifetimeTransition)) | |
m_LifetimeTransitions.CopyTo(pTransitions) | |
排序gc变量的生命周期记录 | |
调用 qsort(pTransitions, numTransitions, sizeof(LifetimeTransition), | |
CompareLifetimeTransitionsByOffsetThenSlot) | |
首先按代码偏移值排序, 如果偏移值一致按对应的slot id排序 | |
删除在函数外(CodeOffset >= m_CodeLength)的记录 | |
删除重复(slot和offset都相同)的记录 | |
调用 EliminateRedundantLiveDeadPairs(&pTransitions, &numTransitions, &pEndTransitions) | |
排序 m_SlotTable // 保存了堆栈上或寄存器上的gc变量的列表, 一个变量对应多个 LifetimeTransition | |
本地变量 sortedSlots = map(\x => { m_SlotDesc: x, m_SlotId: index }, m_SlotTable) | |
调用 qsort(sortedSlots, m_NumSlots, sizeof(GcSlotDescAndId), CompareSlotDescAndIdBySlotDesc) | |
首先按flags排序, !GC_SLOT_UNTRACKED < GC_SLOT_PINNED < GC_SLOT_INTERIOR < GC_SLOT_BASE | |
然后按寄存器排序, 寄存器变量先于栈变量, 都是寄存器则序号小的寄存器优先 | |
然后按栈偏移排序, 偏移值更小的优先(更小的负数), 偏移值一样则按基于的寄存器的序号排序(基于的寄存器必须不同) | |
本地变量 sortOrder = 索引 { 原slot id: 新slot id } | |
更新 m_SlotTable <= sortedSlots, 只更新 m_SlotDesc, 原来的slot id还是有序的 | |
更新 pTransitions 中的 SlotId, 使用索引 sortOrder | |
总结: 排序 m_SlotTable 并更新 LifetimeTransition 中保存的 slot id | |
判断 m_SlotTable 中的哪些 slot 是实际需要的 | |
本地变量 BitArray liveState(m_pAllocator, (m_NumSlots + BITS_PER_SIZE_T - 1) / BITS_PER_SIZE_T) | |
本地变量 BitArray couldBeLive(m_pAllocator, (m_NumSlots + BITS_PER_SIZE_T - 1) / BITS_PER_SIZE_T) | |
根据 callsite 更新 couldBeLive 集合 | |
调用 liveState.ClearAll() | |
本地变量 UINT32 callSiteIndex = 0 | |
本地变量 UINT32 callSite = m_pCallSites[callSiteIndex] | |
枚举 pTransitions | |
如果 pCurrent->CodeOffset > callSite | |
设置 couldBeLive |= liveState | |
增加 ++callSiteIndex, 如果超过则跳出 | |
设置 callSite = m_pCallSites[callSiteIndex] // 下一个 | |
否则 | |
本地变量 slotIndex = pCurrent->SlotId | |
如果 !IsAlwaysScratch(m_SlotTable[slotIndex]) // IsAlwaysScratch => 变量会被call覆盖 | |
调用 liveState.WriteBit(slotIndex, pCurrent->BecomesLive) | |
如果 callSiteIndex < m_NumCallSites // 有callsite在最后的transition后面 | |
设置 couldBeLive |= liveState | |
总结: couldBeLive |= 调用call前, 包含了gc变量, 且不会被call覆盖的slot的bit集合 | |
根据可中断的范围更新 couldBeLive 集合 | |
调用 liveState.ClearAll() | |
本地变量 InterruptibleRange *pCurrentRange = pRanges | |
本地变量 InterruptibleRange *pEndRanges = pRanges + numInterruptibleRanges | |
枚举 pTransitions | |
本地变量 LifetimeTransition *pFirstAfterStart = pCurrent | |
循环如果 pFirstAfterStart->CodeOffset <= pCurrentRange->NormStartOffset | |
设置 liveState.WriteBit(pFirstAfterStart->SlotId, pFirstAfterStart->BecomesLive) | |
增加 ++pFirstAfterStart, 如果超过则跳出 | |
设置 couldBeLive |= liveState // 进入可中断范围前, 包含了gc变量的slot的bit集合 | |
枚举在 pCurrentRange 中的 transitions | |
设置 liveState.WriteBit(slotIndex, becomesLive); | |
设置 couldBeLive.SetBit(slotIndex) // 只要包含在范围就设置为1 | |
增加 pCurrentRange++ // 下一个可中断的范围, 如果超过则跳出 | |
总结: | |
couldBeLive |= 进入可中断范围前, 包含了gc变量的slot的bit集合 | |
couldBeLive |= 可中断范围中所有pTransitions对应的slot(只要包含在范围就设置为1) | |
枚举 m_SlotTable | |
如果 slot 不是 untracked, 并且 couldBeLive 中对应的 slot == 0 | |
标记 m_SlotTable[i].Flags |= GC_SLOT_IS_DELETED | |
枚举 pTransitions, 如果对应的slot标记为 GC_SLOT_IS_DELETED 则删除该 transition | |
写入 m_SlotTable | |
本地变量 numRegisters = m_SlotTable中是寄存器变量的, 未删除的slot数量 | |
本地变量 numStackSlots = m_SlotTable中是已跟踪的栈变量的, 未删除的slot数量 | |
本地变量 numUntrackedSlots = m_SlotTable中是未跟踪的栈变量的, 未删除的slot数量 | |
如果 numRegisters > 0 | |
GCINFO_WRITE(m_Info1, 1, 1, FlagsSize) | |
GCINFO_WRITE_VARL_U(m_Info1, numRegisters, NUM_REGISTERS_ENCBASE, NumRegsSize) | |
否则 | |
GCINFO_WRITE(m_Info1, 0, 1, FlagsSize) | |
如果 numStackSlots || numUntrackedSlots | |
GCINFO_WRITE(m_Info1, 1, 1, FlagsSize) | |
GCINFO_WRITE_VARL_U(m_Info1, numStackSlots, NUM_STACK_SLOTS_ENCBASE, NumStackSize) | |
GCINFO_WRITE_VARL_U(m_Info1, numUntrackedSlots, NUM_UNTRACKED_SLOTS_ENCBASE, NumUntrackedSize) | |
否则 | |
GCINFO_WRITE(m_Info1, 0, 1, FlagsSize) | |
枚举 m_SlotTable 中是寄存器变量的, 未删除的slot | |
前面排序slot的时候会让拥有更多flags的slot拍前面, 于是只有GC_SLOT_IS_REGISTER的slot都会排在后面 | |
首先写入不只有GC_SLOT_IS_REGISTER的slot | |
GCINFO_WRITE_VARL_U(m_Info1, currentNormRegNum, REGISTER_ENCBASE, RegSlotSize) | |
GCINFO_WRITE(m_Info1, pSlotDesc->Flags, 2, RegSlotSize) | |
然后写入只有GC_SLOT_IS_REGISTER的slot | |
GCINFO_WRITE_VARL_U(m_Info1, | |
currentNormRegNum - lastNormRegNum - 1, REGISTER_DELTA_ENCBASE, RegSlotSize) | |
最终会写入 [ regnum, flags, regnum, flags, regnum flags(只有IS_REGISTER), delta, delta, delta ] | |
枚举 m_SlotTable 中是已跟踪的栈变量的, 未删除的slot | |
算法基本同上 | |
首先写入不只有GC_SLOT_BASE(0)的slot | |
GCINFO_WRITE_VARL_S(m_Info1, currentNormStackSlot, STACK_SLOT_ENCBASE, StackSlotSize) | |
GCINFO_WRITE(m_Info1, pSlotDesc->Flags, 2, StackSlotSize) | |
然后写入只有GC_SLOT_BASE(0)的slot | |
GCINFO_WRITE_VARL_U(m_Info1, | |
currentNormStackSlot - lastNormStackSlot, STACK_SLOT_DELTA_ENCBASE, StackSlotSize) | |
最终会写入 [ spOffset, flags, spOffset, flags, spOffset, flags(0), delta, delta, delta ] | |
枚举 m_SlotTable 中是未跟踪的栈变量的, 未删除的slot | |
算法基本同上 | |
首先写入不只有GC_SLOT_UNTRACKED的slot | |
GCINFO_WRITE_VARL_S(m_Info1, currentNormStackSlot, STACK_SLOT_ENCBASE, UntrackedSlotSize) | |
GCINFO_WRITE(m_Info1, pSlotDesc->Flags, 2, UntrackedSlotSize) | |
然后写入只有GC_SLOT_UNTRACKED的slot | |
GCINFO_WRITE_VARL_U(m_Info1, | |
currentNormStackSlot - lastNormStackSlot, STACK_SLOT_DELTA_ENCBASE, UntrackedSlotSize) | |
最终会写入 [ spOffset, flags, spOffset, flags, spOffset, flags(只有UNTRACKED), delta, delta, delta ] | |
写入 m_pCallSites | |
写入的格式有2种 | |
NoIndirection | |
bit数量等于 m_NumCallSites * (numRegisters + numStackSlots) | |
储存各个callsite前的slot存活状态 | |
Indirection | |
本地变量 LiveStateHashTable hashMap = { callsite前的liveState(哪些slot存活) : -1 } | |
本地变量 sizeofSets = hashMap本身的编码大小 | |
本地变量 numBitsPerPointer = CeilOfLog2((sizeofSets - 最后一个set的大小) + 1) // hashMap的索引要多少bit | |
bit数量等于 | |
numBitsPerPointer的编码大小 + | |
m_NumCallSites * numBitsPerPointer + // callsite数量 * 索引bit数 | |
7 // alignment padding | |
sizeofSets // hashMap本身的编码大小 | |
如果 Indirection 格式的总大小(bit数量)更少 | |
GCINFO_WRITE(m_Info1, 1, 1, FlagsSize) | |
GCINFO_WRITE_VARL_U(m_Info1, numBitsPerPointer - 1, POINTER_SIZE_ENCBASE, CallSiteStateSize) | |
枚举 hashMap | |
iter.SetValue((UINT32)m_Info2.GetBitCount()) // -1 => liveState 对应的bit offset | |
GCINFO_WRITE_VAR_VECTOR(m_Info2, | |
*iter.Get(), LIVESTATE_RLE_SKIP_ENCBASE, LIVESTATE_RLE_RUN_ENCBASE, CallSiteStateSize) | |
写入 liveState (slot是否存活的集合) | |
枚举 m_pCallSites | |
本地变量 liveState = call前的slot是否存活的集合 | |
本地变量 liveStateOffset = 上面写入的offset | |
GCINFO_WRITE(m_Info1, liveStateOffset, numBitsPerPointer, CallSiteStateSize) | |
总结: 先写入liveState的集合到 m_Info2, 然后写入各个callsite对应的liveState在集合中的偏移值 | |
否则 // 使用 NoIndirection 格式 | |
GCINFO_WRITE(m_Info1, 0, 1, FlagsSize) | |
枚举 m_pCallSites | |
本地变量 liveState = call前的slot是否存活的集合 | |
GCINFO_WRITE_VECTOR(m_Info1, liveState, CallSiteStateSize) | |
写入gc变量的生命周期记录 | |
本地变量 totalInterruptibleLength = 所有可中断范围的长度合计 | |
根据可终端的范围压缩 pTransitions, 并且修改 CodeOffset, 以可中断范围为基准 | |
算法比较长, 这里直接举例子 | |
缩写: 1+ => slot 1 live, 1- => slot 1 dead, { 可中断的范围 } | |
压缩前: [ 1+ 1- 1+ 2+ { 1- 1+ 2- 2+ } 1- 1+ 1- 2- 2+ { 1+ } ] | |
压缩后: [ 1+ 2+ { 1- 1+ 2- 2+ } 1- { 1+ } ] | |
总结: | |
修改不可中断范围内的transition, 对比当前可中断范围的开始和上一个可中断范围的结尾的liveState, 然后合并 | |
修改CodeOffset, 已可中断范围为基准, 不可中断范围的代码长度不计入CodeOffset | |
压缩后删除多余的 transition | |
按 ceil(totalInterruptibleLength / NUM_NORM_CODE_OFFSETS_PER_CHUNK(64)) 分 chunk | |
transition 会按 chunk 分成几个小组 | |
写入 m_Info2 | |
枚举各个 chunk | |
本地变量 liveState = 该chunk的最后存活的slot的集合 | |
本地变量 couldBeLive = 该chunk修改过的slot的集合 | 上一个chunk最后存活的slot的集合 | |
记录 pChunkPointers[currentChunk] = m_Info2.GetBitCount() + 1 // chunk的索引 | |
排序 chunk 中的 transition, 先按slot id再按code offset | |
GCINFO_WRITE_VAR_VECTOR(m_Info2, | |
couldBeLive, LIVESTATE_RLE_SKIP_ENCBASE, LIVESTATE_RLE_RUN_ENCBASE, ChunkMaskSize) | |
枚举 liveState 中的 bit | |
GCINFO_WRITE(m_Info2, liveState.ReadBit(i) ? 1 : 0, 1, ChunkFinalStateSize) | |
本地变量 normChunkBaseCodeOffset = currentChunk * NUM_NORM_CODE_OFFSETS_PER_CHUNK | |
枚举 couldBeLive 中的 bit | |
枚举 chunk 中的 slot id 等于当前bit的 transition (按couldBeLive分组) | |
本地变量 normCodeOffsetDelta = pT->CodeOffset - normChunkBaseCodeOffset | |
如果 normCodeOffsetDelta > 0 | |
GCINFO_WRITE(m_Info2, 1, 1, ChunkTransitionSize) | |
GCINFO_WRITE(m_Info2, normCodeOffsetDelta, | |
NUM_NORM_CODE_OFFSETS_PER_CHUNK_LOG2, ChunkTransitionSize) | |
GCINFO_WRITE(m_Info2, 0, 1, ChunkTransitionSize) // terminator | |
注意: | |
这里仅仅写入了transition距离当前chunk的偏移值的偏移值, 并不会写入becomesLive | |
这是因为上面对transition进行了排重, 同一slot的两个transition的becomesLive一定相反 | |
当前的状态可以由liveState + transition的出现次数推算出来 | |
写入 m_Info1 | |
本地变量 numBitsPerPointer = CeilOfLog2(max(pChunkPointers) + 1) | |
GCINFO_WRITE_VARL_U(m_Info1, numBitsPerPointer, POINTER_SIZE_ENCBASE, ChunkPtrSize) // bit数 | |
枚举 pChunkPointers | |
GCINFO_WRITE(m_Info1, pChunkPointers[i], numBitsPerPointer, ChunkPtrSize) | |
设置 compiler->compInfoBlkAddr = gcInfoEncoder->Emit() | |
分配 destBuffer = eeAllocGCInfo(m_Info1.GetByteCount() + m_Info2.GetByteCount()) | |
调用 m_pCorJitInfo->allocGCInfo(blockSize) | |
返回 m_jitManager->allocGCInfo(m_CodeHeader,(DWORD)size, &m_GCinfo_len) | |
如果是动态函数 (IsLCGMethod) | |
调用 pCodeHeader->SetGCInfo( | |
pMD->AsDynamicMethodDesc()->GetResolver()->GetJitMetaHeap()->New(blockSize)) | |
设置 pRealCodeHeader->phdrJitGCInfo = pGC | |
否则 | |
调用 pCodeHeader->SetGCInfo(GetJitMetaHeap(pMD)->AllocMem(S_SIZE_T(blockSize))), 同上 | |
复制 m_Info1 到 destBuffer | |
复制 m_Info2 到 destBuffer | |
返回 destBuffer | |
设置 compiler->compInfoBlkSize = 0 | |
调用 getEmitter()->emitEndFN() | |
当前无任何处理 | |
调用 regSet.rsSpillDone(), 同上 | |
调用 compiler->tmpDone(), 同上 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment