Leowuqunqun
10/16/2017 - 8:52 AM

jit笔记

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://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.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在什么线程中编译
		TODO
	
	如何保证同一个函数只JIT一次
		TODO
	
	什么情况会触发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个UNWIND_INFO, 分别记录了
				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的处理
		TODO
	
	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
							TODO 作用不清楚
						对 CEE_LDLEN 调用 pushedStack.PushArrayLen
							TODO 作用不清楚
						如果当前编译的函数是内联函数
							如果当前的函数需要根据利益判断(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)
						TODO
					VarSetOps::AssignAllowUninitRhs(this, compCurLife, VarSetOps::UninitVal())
						TODO
				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)
										TODO
									调用 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, false)
										TODO
									如果 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
													TODO
												调用 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
							优化 while 到 do while
							前 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有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的作用: TODO
						如果不为例外处理器生成 funclet, 则添加本地变量 lvaShadowSPslotsVar
							lvaShadowSPslotsVar的作用: TODO
						如果需要为例外生成器生成 funclet, 则添加本地变量 lvaPSPSym
							lvaPSPSym的作用: TODO
						如果使用了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
						转换	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, B3)
							B3:
						这个优化的生效条件很苛刻, 它要求条件必须是bool(1==1不行), 并且无副作用
						测试中用了if (a){goto x;} if (b){goto x;}才生效, if (!b) 不会生效
				PHASE_FIND_OPER_ORDER
					fgFindOperOrder
						设置所有 BasicBlock 中的所有 Stmt 的评价顺序, 运行成本, 体积成本和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_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, phpsym; 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( // jitinterface.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(), 同上