Skip to content

Instantly share code, notes, and snippets.

@303248153
Last active April 10, 2024 14:06
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save 303248153/2ca436a0e44e5da1c711de050fa3c13f to your computer and use it in GitHub Desktop.
Save 303248153/2ca436a0e44e5da1c711de050fa3c13f to your computer and use it in GitHub Desktop.
jit笔记
链接
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