// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // /*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XX XX XX GenTree XX XX XX XX This is the node in the semantic tree graph. It represents the operation XX XX corresponding to the node, and other information during code-gen. XX XX XX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */ /*****************************************************************************/ #ifndef _GENTREE_H_ #define _GENTREE_H_ /*****************************************************************************/ #include "vartype.h" // For "var_types" #include "target.h" // For "regNumber" #include "ssaconfig.h" // For "SsaConfig::RESERVED_SSA_NUM" #include "reglist.h" #include "valuenumtype.h" #include "simplerhash.h" #include "nodeinfo.h" #include "simd.h" // Debugging GenTree is much easier if we add a magic virtual function to make the debugger able to figure out what type it's got. // This is enabled by default in DEBUG. To enable it in RET builds (temporarily!), you need to change the build to define DEBUGGABLE_GENTREE=1, // as well as pass /OPT:NOICF to the linker (or else all the vtables get merged, making the debugging value supplied by them useless). // See protojit.nativeproj for a commented example of setting the build flags correctly. #ifndef DEBUGGABLE_GENTREE #ifdef DEBUG #define DEBUGGABLE_GENTREE 1 #else // !DEBUG #define DEBUGGABLE_GENTREE 0 #endif // !DEBUG #endif // !DEBUGGABLE_GENTREE /*****************************************************************************/ DECLARE_TYPED_ENUM(genTreeOps,BYTE) { #define GTNODE(en,sn,cm,ok) en, #include "gtlist.h" GT_COUNT, // GT_CNS_NATIVELONG is the gtOper symbol for GT_CNS_LNG or GT_CNS_INT, depending on the target. // For the 64-bit targets we will only use GT_CNS_INT as it used to represent all the possible sizes // For the 32-bit targets we use a GT_CNS_LNG to hold a 64-bit integer constant and GT_CNS_INT for all others. // In the future when we retarget the JIT for x86 we should consider eliminating GT_CNS_LNG // #ifdef _TARGET_64BIT_ GT_CNS_NATIVELONG = GT_CNS_INT, #else GT_CNS_NATIVELONG = GT_CNS_LNG, #endif } END_DECLARE_TYPED_ENUM(genTreeOps,BYTE) /***************************************************************************** * * The following enum defines a set of bit flags that can be used * to classify expression tree nodes. Note that some operators will * have more than one bit set, as follows: * * GTK_CONST implies GTK_LEAF * GTK_RELOP implies GTK_BINOP * GTK_LOGOP implies GTK_BINOP */ enum genTreeKinds { GTK_SPECIAL = 0x0000, // unclassified operator (special handling reqd) GTK_CONST = 0x0001, // constant operator GTK_LEAF = 0x0002, // leaf operator GTK_UNOP = 0x0004, // unary operator GTK_BINOP = 0x0008, // binary operator GTK_RELOP = 0x0010, // comparison operator GTK_LOGOP = 0x0020, // logical operator GTK_ASGOP = 0x0040, // assignment operator GTK_KINDMASK= 0x007F, // operator kind mask GTK_COMMUTE = 0x0080, // commutative operator GTK_EXOP = 0x0100, // Indicates that an oper for a node type that extends GenTreeOp (or GenTreeUnOp) // by adding non-node fields to unary or binary operator. GTK_LOCAL = 0x0200, // is a local access (load, store, phi) /* Define composite value(s) */ GTK_SMPOP = (GTK_UNOP|GTK_BINOP|GTK_RELOP|GTK_LOGOP) }; /*****************************************************************************/ #define SMALL_TREE_NODES 1 /*****************************************************************************/ DECLARE_TYPED_ENUM(gtCallTypes,BYTE) { CT_USER_FUNC, // User function CT_HELPER, // Jit-helper CT_INDIRECT, // Indirect call CT_COUNT // fake entry (must be last) } END_DECLARE_TYPED_ENUM(gtCallTypes,BYTE) /*****************************************************************************/ struct BasicBlock; struct InlineCandidateInfo; /*****************************************************************************/ // GT_FIELD nodes will be lowered into more "code-gen-able" representations, like // GT_IND's of addresses, or GT_LCL_FLD nodes. We'd like to preserve the more abstract // information, and will therefore annotate such lowered nodes with FieldSeq's. A FieldSeq // represents a (possibly) empty sequence of fields. The fields are in the order // in which they are dereferenced. The first field may be an object field or a struct field; // all subsequent fields must be struct fields. struct FieldSeqNode { CORINFO_FIELD_HANDLE m_fieldHnd; FieldSeqNode* m_next; FieldSeqNode(CORINFO_FIELD_HANDLE fieldHnd, FieldSeqNode* next) : m_fieldHnd(fieldHnd), m_next(next) {} // returns true when this is the pseudo #FirstElem field sequence bool IsFirstElemFieldSeq(); // returns true when this is the pseudo #ConstantIndex field sequence bool IsConstantIndexFieldSeq(); // returns true when this is the the pseudo #FirstElem field sequence or the pseudo #ConstantIndex field sequence bool IsPseudoField(); // Make sure this provides methods that allow it to be used as a KeyFuncs type in SimplerHash. static int GetHashCode(FieldSeqNode fsn) { return reinterpret_cast(fsn.m_fieldHnd) ^ reinterpret_cast(fsn.m_next); } static bool Equals(FieldSeqNode fsn1, FieldSeqNode fsn2) { return fsn1.m_fieldHnd == fsn2.m_fieldHnd && fsn1.m_next == fsn2.m_next; } }; // This class canonicalizes field sequences. class FieldSeqStore { typedef SimplerHashTable FieldSeqNodeCanonMap; IAllocator* m_alloc; FieldSeqNodeCanonMap* m_canonMap; static FieldSeqNode s_notAField; // No value, just exists to provide an address. // Dummy variables to provide the addresses for the "pseudo field handle" statics below. static int FirstElemPseudoFieldStruct; static int ConstantIndexPseudoFieldStruct; public: FieldSeqStore(IAllocator* alloc); // Returns the (canonical in the store) singleton field sequence for the given handle. FieldSeqNode* CreateSingleton(CORINFO_FIELD_HANDLE fieldHnd); // This is a special distinguished FieldSeqNode indicating that a constant does *not* // represent a valid field sequence. This is "infectious", in the sense that appending it // (on either side) to any field sequence yields the "NotAField()" sequence. static FieldSeqNode* NotAField() { return &s_notAField; } // Returns the (canonical in the store) field sequence representing the concatenation of // the sequences represented by "a" and "b". Assumes that "a" and "b" are canonical; that is, // they are the results of CreateSingleton, NotAField, or Append calls. If either of the arguments // are the "NotAField" value, so is the result. FieldSeqNode* Append(FieldSeqNode* a, FieldSeqNode* b); // We have a few "pseudo" field handles: // This treats the constant offset of the first element of something as if it were a field. // Works for method table offsets of boxed structs, or first elem offset of arrays/strings. static CORINFO_FIELD_HANDLE FirstElemPseudoField; // If there is a constant index, we make a psuedo field to correspond to the constant added to // offset of the indexed field. This keeps the field sequence structure "normalized", especially in the // case where the element type is a struct, so we might add a further struct field offset. static CORINFO_FIELD_HANDLE ConstantIndexPseudoField; static bool IsPseudoField(CORINFO_FIELD_HANDLE hnd) { return hnd == FirstElemPseudoField || hnd == ConstantIndexPseudoField; } }; /*****************************************************************************/ typedef struct GenTree * GenTreePtr; struct GenTreeArgList; // Forward declarations of the subtypes #define GTSTRUCT_0(fn, en) struct GenTree##fn; #define GTSTRUCT_1(fn, en) struct GenTree##fn; #define GTSTRUCT_2(fn, en, en2) struct GenTree##fn; #define GTSTRUCT_3(fn, en, en2, en3) struct GenTree##fn; #define GTSTRUCT_4(fn, en, en2, en3, en4) struct GenTree##fn; #define GTSTRUCT_N(fn, ...) struct GenTree##fn; #include "gtstructs.h" /*****************************************************************************/ #ifndef _WIN64 #include #endif struct GenTree { // We use GT_STRUCT_0 only for the category of simple ops. #define GTSTRUCT_0(fn, en) GenTree##fn* As##fn() \ { \ assert(this->OperIsSimple()); \ return reinterpret_cast(this); \ } \ GenTree##fn& As##fn##Ref() { return *As##fn(); } \ __declspec(property(get=As##fn##Ref)) GenTree##fn& gt##fn; #define GTSTRUCT_1(fn, en) GenTree##fn* As##fn() \ { \ assert(this->gtOper == en); \ return reinterpret_cast(this); \ } \ GenTree##fn& As##fn##Ref() { return *As##fn(); } \ __declspec(property(get=As##fn##Ref)) GenTree##fn& gt##fn; #define GTSTRUCT_2(fn, en, en2) GenTree##fn* As##fn() \ { \ assert(this->gtOper == en || this->gtOper == en2); \ return reinterpret_cast(this); \ } \ GenTree##fn& As##fn##Ref() { return *As##fn(); } \ __declspec(property(get=As##fn##Ref)) GenTree##fn& gt##fn; #define GTSTRUCT_3(fn, en, en2, en3) GenTree##fn* As##fn() \ { \ assert(this->gtOper == en || this->gtOper == en2 || this->gtOper == en3); \ return reinterpret_cast(this); \ } \ GenTree##fn& As##fn##Ref() { return *As##fn(); } \ __declspec(property(get=As##fn##Ref)) GenTree##fn& gt##fn; #define GTSTRUCT_4(fn, en, en2, en3, en4) GenTree##fn* As##fn() \ { \ assert(this->gtOper == en || this->gtOper == en2 || this->gtOper == en3 || this->gtOper == en4); \ return reinterpret_cast(this); \ } \ GenTree##fn& As##fn##Ref() { return *As##fn(); } \ __declspec(property(get=As##fn##Ref)) GenTree##fn& gt##fn; #ifdef DEBUG // VC does not optimize out this loop in retail even though the value it computes is unused // so we need a separate version for non-debug #define GTSTRUCT_N(fn, ...) GenTree##fn* As##fn() \ { \ genTreeOps validOps[] = {__VA_ARGS__}; \ bool found = false; \ for (unsigned i=0; igtOper == validOps[i]) \ { \ found = true; \ break; \ } \ } \ assert(found); \ return reinterpret_cast(this); \ } \ GenTree##fn& As##fn##Ref() { return *As##fn(); } \ __declspec(property(get=As##fn##Ref)) GenTree##fn& gt##fn; #else #define GTSTRUCT_N(fn, ...) GenTree##fn* As##fn() \ { \ return reinterpret_cast(this); \ } \ GenTree##fn& As##fn##Ref() { return *As##fn(); } \ __declspec(property(get=As##fn##Ref)) GenTree##fn& gt##fn; #endif #include "gtstructs.h" genTreeOps gtOper; // enum subtype BYTE var_types gtType; // enum subtype BYTE genTreeOps OperGet() const { return gtOper; } var_types TypeGet() const { return gtType; } #ifdef DEBUG genTreeOps gtOperSave; // Only used to save gtOper when we destroy a node, to aid debugging. #endif #if FEATURE_ANYCSE #define NO_CSE (0) #define IS_CSE_INDEX(x) (x != 0) #define IS_CSE_USE(x) (x > 0) #define IS_CSE_DEF(x) (x < 0) #define GET_CSE_INDEX(x) ((x > 0) ? x : -x) #define TO_CSE_DEF(x) (-x) signed char gtCSEnum; // 0 or the CSE index (negated if def) // valid only for CSE expressions #endif // FEATURE_ANYCSE #if ASSERTION_PROP unsigned char gtAssertionNum; // 0 or Assertion table index // valid only for non-GT_STMT nodes bool HasAssertion() const { return gtAssertionNum != 0; } void ClearAssertion() { gtAssertionNum = 0; } unsigned int GetAssertion() const { return gtAssertionNum; } void SetAssertion(unsigned int value) { assert((unsigned char)value == value); gtAssertionNum = (unsigned char)value; } #endif #if FEATURE_STACK_FP_X87 unsigned char gtFPlvl; // x87 stack depth at this node void gtCopyFPlvl(GenTree * other) { gtFPlvl = other->gtFPlvl; } void gtSetFPlvl(unsigned level) { noway_assert(FitsIn(level)); gtFPlvl = (unsigned char)level; } #else // FEATURE_STACK_FP_X87 void gtCopyFPlvl(GenTree * other) { } void gtSetFPlvl(unsigned level) { } #endif // FEATURE_STACK_FP_X87 // // Cost metrics on the node. Don't allow direct access to the variable for setting. // public: #ifdef DEBUG // You are not allowed to read the cost values before they have been set in gtSetEvalOrder(). // Keep track of whether the costs have been initialized, and assert if they are read before being initialized. // Obviously, this information does need to be initialized when a node is created. // This is public so the dumpers can see it. bool gtCostsInitialized; #endif // DEBUG #define MAX_COST UCHAR_MAX #define IND_COST_EX 3 // execution cost for an indirection __declspec(property(get=GetCostEx)) unsigned char gtCostEx; // estimate of expression execution cost __declspec(property(get=GetCostSz)) unsigned char gtCostSz; // estimate of expression code size cost unsigned char GetCostEx() const { assert(gtCostsInitialized); return _gtCostEx; } unsigned char GetCostSz() const { assert(gtCostsInitialized); return _gtCostSz; } // Set the costs. They are always both set at the same time. // Don't use the "put" property: force calling this function, to make it more obvious in the few places // that set the values. // Note that costs are only set in gtSetEvalOrder() and its callees. void SetCosts(unsigned costEx, unsigned costSz) { assert(costEx != (unsigned)-1); // looks bogus assert(costSz != (unsigned)-1); // looks bogus INDEBUG(gtCostsInitialized = true;) _gtCostEx = (costEx > MAX_COST) ? MAX_COST : (unsigned char)costEx; _gtCostSz = (costSz > MAX_COST) ? MAX_COST : (unsigned char)costSz; } // Opimized copy function, to avoid the SetCosts() function comparisons, and make it more clear that a node copy is happening. void CopyCosts(const GenTree* const tree) { INDEBUG(gtCostsInitialized = tree->gtCostsInitialized;) // If the 'tree' costs aren't initialized, we'll hit an assert below. _gtCostEx = tree->gtCostEx; _gtCostSz = tree->gtCostSz; } // Same as CopyCosts, but avoids asserts if the costs we are copying have not been initialized. // This is because the importer, for example, clones nodes, before these costs have been initialized. // Note that we directly access the 'tree' costs, not going through the accessor functions (either // directly or through the properties). void CopyRawCosts(const GenTree* const tree) { INDEBUG(gtCostsInitialized = tree->gtCostsInitialized;) _gtCostEx = tree->_gtCostEx; _gtCostSz = tree->_gtCostSz; } private: unsigned char _gtCostEx; // estimate of expression execution cost unsigned char _gtCostSz; // estimate of expression code size cost // // Register or register pair number of the node. // #ifdef DEBUG public: enum genRegTag { GT_REGTAG_NONE, // Nothing has been assigned to _gtRegNum/_gtRegPair GT_REGTAG_REG, // _gtRegNum has been assigned GT_REGTAG_REGPAIR // _gtRegPair has been assigned }; genRegTag GetRegTag() const { assert(gtRegTag == GT_REGTAG_NONE || gtRegTag == GT_REGTAG_REG || gtRegTag == GT_REGTAG_REGPAIR); return gtRegTag; } private: genRegTag gtRegTag; // What is in _gtRegNum/_gtRegPair? #endif // DEBUG private: union { // NOTE: After LSRA, one of these values may be valid even if GTF_REG_VAL is not set in gtFlags. // They store the register assigned to the node. If a register is not assigned, _gtRegNum is set to REG_NA // or _gtRegPair is set to REG_PAIR_NONE, depending on the node type. regNumberSmall _gtRegNum; // which register the value is in regPairNoSmall _gtRegPair; // which register pair the value is in }; public: // The register number is stored in a small format (8 bits), but the getters return and the setters take // a full-size (unsigned) format, to localize the casts here. __declspec(property(get=GetRegNum,put=SetRegNum)) regNumber gtRegNum; // for codegen purposes, is this node a subnode of its parent bool isContained() const; bool isContainedIndir() const; bool isIndirAddrMode(); bool isIndir() const; bool isContainedIntOrIImmed() const { return isContained() && IsCnsIntOrI(); } bool isContainedFltOrDblImmed() const { return isContained() && (OperGet() == GT_CNS_DBL); } bool isLclField() const { return OperGet() == GT_LCL_FLD || OperGet() == GT_STORE_LCL_FLD; } bool isContainedLclField() const { return isContained() && isLclField(); } // Indicates whether it is a memory op. // Right now it includes Indir and LclField ops. bool isMemoryOp() const { return isIndir() || isLclField(); } bool isContainedMemoryOp() const { return isContained() && isMemoryOp(); } regNumber GetRegNum() const { assert((gtRegTag == GT_REGTAG_REG) || (gtRegTag == GT_REGTAG_NONE)); // TODO-Cleanup: get rid of the NONE case, and fix everyplace that reads undefined values regNumber reg = (regNumber) _gtRegNum; assert((gtRegTag == GT_REGTAG_NONE) || // TODO-Cleanup: get rid of the NONE case, and fix everyplace that reads undefined values (reg >= REG_FIRST && reg <= REG_COUNT)); return reg; } void SetRegNum(regNumber reg) { assert(reg >= REG_FIRST && reg <= REG_COUNT); // Make sure the upper bits of _gtRegPair are clear _gtRegPair = (regPairNoSmall) 0; _gtRegNum = (regNumberSmall) reg; INDEBUG(gtRegTag = GT_REGTAG_REG;) assert(_gtRegNum == reg); } __declspec(property(get=GetRegPair,put=SetRegPair)) regPairNo gtRegPair; regPairNo GetRegPair() const { assert((gtRegTag == GT_REGTAG_REGPAIR) || (gtRegTag == GT_REGTAG_NONE)); // TODO-Cleanup: get rid of the NONE case, and fix everyplace that reads undefined values regPairNo regPair = (regPairNo) _gtRegPair; assert((gtRegTag == GT_REGTAG_NONE) || // TODO-Cleanup: get rid of the NONE case, and fix everyplace that reads undefined values (regPair >= REG_PAIR_FIRST && regPair <= REG_PAIR_LAST) || (regPair == REG_PAIR_NONE)); // allow initializing to an undefined value return regPair; } void SetRegPair(regPairNo regPair) { assert((regPair >= REG_PAIR_FIRST && regPair <= REG_PAIR_LAST) || (regPair == REG_PAIR_NONE)); // allow initializing to an undefined value _gtRegPair = (regPairNoSmall) regPair; INDEBUG(gtRegTag = GT_REGTAG_REGPAIR;) assert(_gtRegPair == regPair); } // Copy the _gtRegNum/_gtRegPair/gtRegTag fields void CopyReg(GenTreePtr from) { // To do the copy, use _gtRegPair, which must be bigger than _gtRegNum. Note that the values // might be undefined (so gtRegTag == GT_REGTAG_NONE). _gtRegPair = from->_gtRegPair; C_ASSERT(sizeof(_gtRegPair) >= sizeof(_gtRegNum)); INDEBUG(gtRegTag = from->gtRegTag;) } void gtClearReg(Compiler* compiler); bool gtHasReg() const { // Has the node been assigned a register by LSRA? // // In order for this to work properly, gtClearReg (above) must be called prior to setting // the register value. if (isRegPairType(TypeGet())) { assert(_gtRegNum != REG_NA); INDEBUG(assert(gtRegTag == GT_REGTAG_REGPAIR)); return gtRegPair != REG_PAIR_NONE; } else { assert(_gtRegNum != REG_PAIR_NONE); INDEBUG(assert(gtRegTag == GT_REGTAG_REG)); return gtRegNum != REG_NA; } } regMaskTP gtGetRegMask() const { if (isRegPairType(TypeGet())) { return genRegPairMask(gtRegPair); } else { return genRegMask(gtRegNum); } } unsigned gtFlags; // see GTF_xxxx below ValueNumPair gtVNPair; regMaskSmall gtRsvdRegs; // set of fixed trashed registers #ifdef LEGACY_BACKEND regMaskSmall gtUsedRegs; // set of used (trashed) registers #endif // LEGACY_BACKEND #ifndef LEGACY_BACKEND TreeNodeInfo gtLsraInfo; #endif // !LEGACY_BACKEND void SetVNsFromNode(GenTreePtr tree) { gtVNPair = tree->gtVNPair; } ValueNum GetVN(ValueNumKind vnk) const { if (vnk == VNK_Liberal) { return gtVNPair.GetLiberal(); } else { assert(vnk == VNK_Conservative); return gtVNPair.GetConservative(); } } void SetVN(ValueNumKind vnk, ValueNum vn) { if (vnk == VNK_Liberal) { return gtVNPair.SetLiberal(vn); } else { assert(vnk == VNK_Conservative); return gtVNPair.SetConservative(vn); } } //--------------------------------------------------------------------- // The first set of flags can be used with a large set of nodes, and // thus they must all have distinct values. That is, one can test any // expression node for one of these flags. //--------------------------------------------------------------------- #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_DEAD 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. // #define GTF_PERSISTENT_SIDE_EFFECTS_IN_CSE (GTF_ASG|GTF_CALL|GTF_DEAD) // 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 #define GTF_SPILLED_OPER 0x00000100 // op1 has been spilled #ifdef LEGACY_BACKEND #define GTF_SPILLED_OP2 0x00000200 // op2 has been spilled #endif // LEGACY_BACKEND #ifdef DEBUG #ifndef LEGACY_BACKEND #define GTF_MORPHED 0x00000200 // the node has been morphed (in the global morphing phase) #else // LEGACY_BACKEND // For LEGACY_BACKEND, 0x00000200 is in use, but we can use the same value as GTF_SPILLED since we // don't call gtSetEvalOrder(), which clears GTF_MORPHED, after GTF_SPILLED has been set. #define GTF_MORPHED 0x00000080 // the node has been morphed (in the global morphing phase) #endif // LEGACY_BACKEND #endif // DEBUG #define GTF_REDINDEX_CHECK 0x00000100 // Used for redundant range checks. Disjoint from GTF_SPILLED_OPER #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 #if FEATURE_ANYCSE #define GTF_DEAD 0x00001000 // this node won't be used any more #endif // FEATURE_ANYCSE #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 ? :) #if defined(DEBUG) && SMALL_TREE_NODES #define GTF_NODE_LARGE 0x00010000 #define GTF_NODE_SMALL 0x00020000 // Property of the node itself, not the gtOper #define GTF_NODE_MASK (GTF_COLON_COND | GTF_MORPHED | GTF_NODE_SMALL | GTF_NODE_LARGE ) #else #define GTF_NODE_MASK (GTF_COLON_COND) #endif #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 // 1) it is not needed for lclVars and is highly unlikely to be useful for indir nodes, and // 2) it conflicts with GTFD_VAR_CSE_REF for lclVars (though this is debug only, and // GTF_IND_ARR_INDEX for indirs. //--------------------------------------------------------------------- // 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=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) // TODO-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_ #ifdef DEBUG #define GTFD_VAR_CSE_REF 0x00800000 // GT_LCL_VAR -- This is a CSE LCL_VAR node #endif #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 (this shares the same value as GTFD_VAR_CSE_REF, // but is disjoint because a GT_LCL_VAR is never an ind (GT_IND or GT_STOREIND) #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 guarenteed 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_ -- Is branch taken if ops are NaN? #define GTF_RELOP_JMP_USED 0x40000000 // GT_ -- result of compare used for jump or ?: #define GTF_RELOP_QMARK 0x20000000 // GT_ -- the node is the condition for ?: #define GTF_RELOP_SMALL 0x10000000 // GT_ -- We should use a byte or short sized compare (op1->gtType is the small type) #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_HASGCPTR 0x80000000 // GT_COPYBLK -- This struct copy will copy GC Pointers #define GTF_BLK_VOLATILE 0x40000000 // GT_INITBLK/GT_COPYBLK -- is a volatile block operation #define GTF_BLK_UNALIGNED 0x02000000 // GT_INITBLK/GT_COPYBLK -- is an unaligned 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_STMT_CMPADD 0x80000000 // GT_STMT -- added by compiler #define GTF_STMT_HAS_CSE 0x40000000 // GT_STMT -- CSE def or use was subsituted #define GTF_STMT_TOP_LEVEL 0x20000000 // GT_STMT -- Top-level statement - true iff gtStmtList->gtPrev == nullptr // True for all stmts when in FGOrderTree #define GTF_STMT_SKIP_LOWER 0x10000000 // GT_STMT -- Skip lowering if we already lowered an embedded stmt. //---------------------------------------------------------------- GenTreePtr gtNext; GenTreePtr gtPrev; #ifdef DEBUG unsigned gtTreeID; unsigned gtSeqNum; // liveness traversal order within the current statement #endif static const unsigned short gtOperKindTable[]; static unsigned OperKind(unsigned gtOper) { assert(gtOper < GT_COUNT); return gtOperKindTable[gtOper]; } unsigned OperKind() const { assert(gtOper < GT_COUNT); return gtOperKindTable[gtOper]; } static bool IsExOp(unsigned opKind) { return (opKind & GTK_EXOP) != 0; } // Returns the operKind with the GTK_EX_OP bit removed (the // kind of operator, unary or binary, that is extended). static unsigned StripExOp(unsigned opKind) { return opKind & ~GTK_EXOP; } static int OperIsConst(genTreeOps gtOper) { return (OperKind(gtOper) & GTK_CONST ) != 0; } int OperIsConst() const { return (OperKind(gtOper) & GTK_CONST ) != 0; } static int OperIsLeaf(genTreeOps gtOper) { return (OperKind(gtOper) & GTK_LEAF ) != 0; } int OperIsLeaf() const { return (OperKind(gtOper) & GTK_LEAF ) != 0; } static int OperIsCompare(genTreeOps gtOper) { return (OperKind(gtOper) & GTK_RELOP ) != 0; } static bool OperIsLocal(genTreeOps gtOper) { bool result = (OperKind(gtOper) & GTK_LOCAL) != 0; assert(result == (gtOper == GT_LCL_VAR || gtOper == GT_PHI_ARG || gtOper == GT_REG_VAR || gtOper == GT_LCL_FLD || gtOper == GT_STORE_LCL_VAR || gtOper == GT_STORE_LCL_FLD)); return result; } static bool OperIsBlkOp(genTreeOps gtOper) { return (gtOper == GT_INITBLK || gtOper == GT_COPYBLK || gtOper == GT_COPYOBJ); } static bool OperIsCopyBlkOp(genTreeOps gtOper) { return (gtOper == GT_COPYOBJ || gtOper == GT_COPYBLK); } static bool OperIsLocalAddr(genTreeOps gtOper) { return (gtOper == GT_LCL_VAR_ADDR || gtOper == GT_LCL_FLD_ADDR); } static bool OperIsScalarLocal(genTreeOps gtOper) { return (gtOper == GT_LCL_VAR || gtOper == GT_REG_VAR || gtOper == GT_STORE_LCL_VAR); } static bool OperIsNonPhiLocal(genTreeOps gtOper) { return OperIsLocal(gtOper) && (gtOper != GT_PHI_ARG); } static bool OperIsLocalRead(genTreeOps gtOper) { return (OperIsLocal(gtOper) && !OperIsLocalStore(gtOper)); } static bool OperIsLocalStore(genTreeOps gtOper) { return (gtOper == GT_STORE_LCL_VAR || gtOper == GT_STORE_LCL_FLD); } static bool OperIsAddrMode(genTreeOps gtOper) { return (gtOper == GT_LEA); } bool OperIsBlkOp() const { return OperIsBlkOp(OperGet()); } bool OperIsCopyBlkOp() const { return OperIsCopyBlkOp(OperGet()); } bool OperIsAddrMode() const { return OperIsAddrMode(OperGet()); } bool OperIsLocal() const { return OperIsLocal(OperGet()); } bool OperIsLocalAddr() const { return OperIsLocalAddr(OperGet()); } bool OperIsScalarLocal() const { return OperIsScalarLocal(OperGet()); } bool OperIsNonPhiLocal() const { return OperIsNonPhiLocal(OperGet()); } bool OperIsLocalStore() const { return OperIsLocalStore(OperGet()); } bool OperIsLocalRead() const { return OperIsLocalRead(OperGet()); } int OperIsCompare() { return (OperKind(gtOper) & GTK_RELOP ) != 0; } static int OperIsLogical(genTreeOps gtOper) { return (OperKind(gtOper) & GTK_LOGOP ) != 0; } int OperIsLogical() const { return (OperKind(gtOper) & GTK_LOGOP ) != 0; } int OperIsArithmetic() const { genTreeOps op = OperGet(); return op==GT_ADD || op==GT_SUB || op==GT_MUL || op==GT_DIV || op==GT_MOD || op==GT_UDIV || op==GT_UMOD || op==GT_OR || op==GT_XOR || op==GT_AND || op==GT_LSH || op==GT_RSH || op==GT_RSZ; } static int OperIsUnary(genTreeOps gtOper) { return (OperKind(gtOper) & GTK_UNOP ) != 0; } int OperIsUnary() const { return OperIsUnary(gtOper); } static int OperIsBinary(genTreeOps gtOper) { return (OperKind(gtOper) & GTK_BINOP ) != 0; } int OperIsBinary() const { return OperIsBinary(gtOper); } static int OperIsSimple(genTreeOps gtOper) { return (OperKind(gtOper) & GTK_SMPOP ) != 0; } static int OperIsSpecial(genTreeOps gtOper) { return ((OperKind(gtOper) & GTK_KINDMASK) == GTK_SPECIAL); } int OperIsSimple() const { return OperIsSimple(gtOper); } #ifdef FEATURE_SIMD bool isCommutativeSIMDIntrinsic(); #else // ! bool isCommutativeSIMDIntrinsic() { return false; } #endif // FEATURE_SIMD static int OperIsCommutative(genTreeOps gtOper) { return (OperKind(gtOper) & GTK_COMMUTE) != 0; } int OperIsCommutative() { return OperIsCommutative(gtOper) || (OperIsSIMD(gtOper) && isCommutativeSIMDIntrinsic()); } static int OperIsAssignment(genTreeOps gtOper) { return (OperKind(gtOper) & GTK_ASGOP) != 0; } int OperIsAssignment() const { return OperIsAssignment(gtOper); } static int OperIsIndir(genTreeOps gtOper) { return gtOper == GT_IND || gtOper == GT_STOREIND || gtOper == GT_NULLCHECK; } int OperIsIndir() const { return OperIsIndir(gtOper); } static bool OperIsImplicitIndir(genTreeOps gtOper) { switch (gtOper) { case GT_LOCKADD: case GT_XADD: case GT_CMPXCHG: case GT_COPYBLK: case GT_COPYOBJ: case GT_INITBLK: case GT_LDOBJ: case GT_BOX: case GT_ARR_INDEX: case GT_ARR_ELEM: case GT_ARR_OFFSET: return true; default: return false; } } bool OperIsImplicitIndir() const { return OperIsImplicitIndir(gtOper); } bool OperIsStore() const { return OperIsStore(gtOper); } static bool OperIsStore(genTreeOps gtOper) { return (gtOper == GT_STOREIND || gtOper == GT_STORE_LCL_VAR || gtOper == GT_STORE_LCL_FLD || gtOper == GT_STORE_CLS_VAR); } static int OperIsAtomicOp(genTreeOps gtOper) { return (gtOper == GT_XADD || gtOper == GT_XCHG || gtOper == GT_LOCKADD || gtOper == GT_CMPXCHG); } int OperIsAtomicOp() { return OperIsAtomicOp(gtOper); } // This is basically here for cleaner FEATURE_SIMD #ifdefs. static bool OperIsSIMD(genTreeOps gtOper) { #ifdef FEATURE_SIMD return gtOper == GT_SIMD; #else // !FEATURE_SIMD return false; #endif // !FEATURE_SIMD } // Requires that "op" is an op= operator. Returns // the corresponding "op". static genTreeOps OpAsgToOper(genTreeOps op); #ifdef DEBUG bool NullOp1Legal() const { assert(OperIsSimple(gtOper)); switch (gtOper) { case GT_PHI: case GT_LEA: case GT_RETFILT: case GT_NOP: return true; case GT_RETURN: return gtType == TYP_VOID; default: return false; } } bool NullOp2Legal() const { assert(OperIsSimple(gtOper)); if (!OperIsBinary(gtOper)) { return true; } switch (gtOper) { case GT_LIST: case GT_MATH: case GT_LEA: case GT_STOREIND: case GT_INITBLK: case GT_COPYBLK: case GT_COPYOBJ: #ifdef FEATURE_SIMD case GT_SIMD: #endif // !FEATURE_SIMD return true; default: return false; } } static inline bool RequiresNonNullOp2(genTreeOps oper); #endif // DEBUG inline bool IsZero(); inline bool IsBoxedValue(); bool IsList() const { return gtOper == GT_LIST; } inline GenTreePtr MoveNext(); inline GenTreePtr Current(); inline GenTreePtr *pCurrent(); inline GenTreePtr gtGetOp1(); inline GenTreePtr gtGetOp2(); // Given a tree node, if this is a child of that node, return the pointer to the child node so that it // can be modified; otherwise, return null. GenTreePtr* gtGetChildPointer(GenTreePtr parent); // Get the parent of this node, and optionally capture the pointer to the child so that it can be modified. GenTreePtr gtGetParent(GenTreePtr** parentChildPtrPtr); inline GenTreePtr gtEffectiveVal(bool commaOnly = false); // Return the child of this node if it is a GT_RELOAD or GT_COPY; otherwise simply return the node itself inline GenTree* gtSkipReloadOrCopy(); bool OperMayThrow(); unsigned GetScaleIndexMul(); unsigned GetScaleIndexShf(); unsigned GetScaledIndex(); // Returns true if "addr" is a GT_ADD node, at least one of whose arguments is an integer // (<= 32 bit) constant. If it returns true, it sets "*offset" to (one of the) constant value(s), and // "*addr" to the other argument. bool IsAddWithI32Const(GenTreePtr* addr, int* offset); // Insert 'node' after this node in execution order. void InsertAfterSelf(GenTree* node, GenTreeStmt* stmt = nullptr); public: #if SMALL_TREE_NODES static unsigned char s_gtNodeSizes[]; #endif static void InitNodeSize(); size_t GetNodeSize() const; bool IsNodeProperlySized() const; void CopyFrom(const GenTree* src, Compiler* comp); static genTreeOps ReverseRelop(genTreeOps relop); static genTreeOps SwapRelop(genTreeOps relop); //--------------------------------------------------------------------- static bool Compare(GenTreePtr op1, GenTreePtr op2, bool swapOK = false); //--------------------------------------------------------------------- #ifdef DEBUG //--------------------------------------------------------------------- static const char * NodeName(genTreeOps op); //--------------------------------------------------------------------- #endif //--------------------------------------------------------------------- bool IsNothingNode () const; void gtBashToNOP (); void SetOper (genTreeOps oper); // set gtOper void SetOperResetFlags (genTreeOps oper); // set gtOper and reset flags void ChangeOperConst (genTreeOps oper); // ChangeOper(constOper) void ChangeOper (genTreeOps oper); // set gtOper and only keep GTF_COMMON_MASK flags void ChangeOperUnchecked (genTreeOps oper); bool IsLocal() const { return OperIsLocal(OperGet()); } // Returns "true" iff "this" defines a local variable. Requires "comp" to be the // current compilation. If returns "true", sets "*pLclVarTree" to the // tree for the local that is defined, and, if "pIsEntire" is non-null, sets "*pIsEntire" to // true or false, depending on whether the assignment writes to the entirety of the local // variable, or just a portion of it. bool DefinesLocal(Compiler* comp, GenTreeLclVarCommon** pLclVarTree, bool* pIsEntire = nullptr); // Returns true if "this" represents the address of a local, or a field of a local. If returns true, sets // "*pLclVarTree" to the node indicating the local variable. If the address is that of a field of this node, // sets "*pFldSeq" to the field sequence representing that field, else null. bool IsLocalAddrExpr(Compiler* comp, GenTreeLclVarCommon** pLclVarTree, FieldSeqNode** pFldSeq); // Simpler variant of the above which just returns the local node if this is an expression that // yields an address into a local GenTreeLclVarCommon* IsLocalAddrExpr(); // If returns "true", "this" may represent the address of a static or instance field // (or a field of such a field, in the case of an object field of type struct). // If returns "true", then either "*pObj" is set to the object reference, // or "*pStatic" is set to the baseAddr or offset to be added to the "*pFldSeq" // Only one of "*pObj" or "*pStatic" will be set, the other one will be null. // The boolean return value only indicates that "this" *may* be a field address // -- the field sequence must also be checked. // If it is a field address, the field sequence will be a sequence of length >= 1, // starting with an instance or static field, and optionally continuing with struct fields. bool IsFieldAddr(Compiler* comp, GenTreePtr* pObj, GenTreePtr* pStatic, FieldSeqNode** pFldSeq); // Requires "this" to be the address of an array (the child of a GT_IND labeled with GTF_IND_ARR_INDEX). // Sets "pArr" to the node representing the array (either an array object pointer, or perhaps a byref to the some element). // Sets "*pArrayType" to the class handle for the array type. // Sets "*inxVN" to the value number inferred for the array index. // Sets "*pFldSeq" to the sequence, if any, of struct fields used to index into the array element. void ParseArrayAddress(Compiler* comp, struct ArrayInfo* arrayInfo, GenTreePtr* pArr, ValueNum* pInxVN, FieldSeqNode** pFldSeq); // Helper method for the above. void ParseArrayAddressWork(Compiler* comp, ssize_t inputMul, GenTreePtr* pArr, ValueNum* pInxVN, ssize_t* pOffset, FieldSeqNode** pFldSeq); // Requires "this" to be a GT_IND. Requires the outermost caller to set "*pFldSeq" to nullptr. // Returns true if it is an array index expression, or access to a (sequence of) struct field(s) // within a struct array element. If it returns true, sets *arrayInfo to the array information, and sets *pFldSeq to the sequence // of struct field accesses. bool ParseArrayElemForm(Compiler* comp, ArrayInfo* arrayInfo, FieldSeqNode** pFldSeq); // Requires "this" to be the address of a (possible) array element (or struct field within that). // If it is, sets "*arrayInfo" to the array access info, "*pFldSeq" to the sequence of struct fields // accessed within the array element, and returns true. If not, returns "false". bool ParseArrayElemAddrForm(Compiler* comp, ArrayInfo* arrayInfo, FieldSeqNode** pFldSeq); // Requires "this" to be an int expression. If it is a sequence of one or more integer constants added together, // returns true and sets "*pFldSeq" to the sequence of fields with which those constants are annotated. bool ParseOffsetForm(Compiler* comp, FieldSeqNode** pFldSeq); // Labels "*this" as an array index expression: label all constants and variables that could contribute, as part of an affine expression, to the value of the // of the index. void LabelIndex(Compiler* comp, bool isConst = true); // Assumes that "this" occurs in a context where it is being dereferenced as the LHS of an assignment-like // statement (assignment, initblk, or copyblk). The "width" should be the number of bytes copied by the // operation. Returns "true" if "this" is an address of (or within) // a local variable; sets "*pLclVarTree" to that local variable instance; and, if "pIsEntire" is non-null, // sets "*pIsEntire" to true if this assignment writes the full width of the local. bool DefinesLocalAddr(Compiler* comp, unsigned width, GenTreeLclVarCommon** pLclVarTree, bool* pIsEntire); bool IsRegVar () const { return OperGet() == GT_REG_VAR?true:false; } bool InReg() const { return (gtFlags & GTF_REG_VAL)?true:false; } void SetInReg() { gtFlags |= GTF_REG_VAL; } regNumber GetReg() const { return InReg() ? gtRegNum : REG_NA; } bool IsRegVarDeath () const { assert(OperGet() == GT_REG_VAR); return (gtFlags & GTF_VAR_DEATH)?true:false; } bool IsRegVarBirth () const { assert(OperGet() == GT_REG_VAR); return (gtFlags & GTF_REG_BIRTH)?true:false; } bool IsReverseOp() const { return (gtFlags & GTF_REVERSE_OPS)?true:false; } inline bool IsCnsIntOrI () const; inline bool IsIntCnsFitsInI32 (); inline bool IsCnsFltOrDbl() const; inline bool IsCnsNonZeroFltOrDbl(); bool IsIconHandle () const { assert(gtOper == GT_CNS_INT); return (gtFlags & GTF_ICON_HDL_MASK) ? true : false; } bool IsIconHandle (unsigned handleType) const { assert(gtOper == GT_CNS_INT); assert((handleType & GTF_ICON_HDL_MASK) != 0); // check that handleType is one of the valid GTF_ICON_* values assert((handleType & ~GTF_ICON_HDL_MASK) == 0); return (gtFlags & GTF_ICON_HDL_MASK) == handleType; } // Return just the part of the flags corresponding to the GTF_ICON_*_HDL flag. For example, // GTF_ICON_SCOPE_HDL. The tree node must be a const int, but it might not be a handle, in which // case we'll return zero. unsigned GetIconHandleFlag () const { assert(gtOper == GT_CNS_INT); return (gtFlags & GTF_ICON_HDL_MASK); } // Mark this node as no longer being a handle; clear its GTF_ICON_*_HDL bits. void ClearIconHandleMask() { assert(gtOper == GT_CNS_INT); gtFlags &= ~GTF_ICON_HDL_MASK; } // Return true if the two GT_CNS_INT trees have the same handle flag (GTF_ICON_*_HDL). static bool SameIconHandleFlag(GenTree* t1, GenTree* t2) { return t1->GetIconHandleFlag() == t2->GetIconHandleFlag(); } bool IsArgPlaceHolderNode() const { return OperGet() == GT_ARGPLACE; } bool IsCall () const { return OperGet() == GT_CALL; } bool IsStatement () const { return OperGet() == GT_STMT; } inline bool IsHelperCall (); bool IsVarAddr () const; bool gtOverflow () const; bool gtOverflowEx () const; bool gtSetFlags () const; bool gtRequestSetFlags (); #ifdef DEBUG bool gtIsValid64RsltMul (); static int gtDispFlags (unsigned flags); #endif // cast operations inline var_types CastFromType(); inline var_types& CastToType(); // Returns "true" iff "*this" is an assignment (GT_ASG) tree that defines an SSA name (lcl = phi(...)); bool IsPhiDefn(); // Returns "true" iff "*this" is a statement containing an assignment that defines an SSA name (lcl = phi(...)); bool IsPhiDefnStmt(); // Can't use an assignment operator, because we need the extra "comp" argument // (to provide the allocator necessary for the VarSet assignment). // TODO-Cleanup: Not really needed now, w/o liveset on tree nodes void CopyTo(class Compiler* comp, const GenTree& gt); // Like the above, excepts assumes copying from small node to small node. // (Following the code it replaces, it does *not* copy the GenTree fields, // which CopyTo does.) void CopyToSmall(const GenTree& gt); // Because of the fact that we hid the assignment operator of "BitSet" (in DEBUG), // we can't synthesize an assignment operator. // TODO-Cleanup: Could change this w/o liveset on tree nodes // (This is also necessary for the VTable trick.) GenTree() {} // Returns the number of children of the current node. unsigned NumChildren(); // Requires "childNum < NumChildren()". Returns the "n"th child of "this." GenTreePtr GetChild(unsigned childNum); // The maximum possible # of children of any node. static const int MAX_CHILDREN = 6; bool IsReuseRegVal() const { // This can be extended to non-constant nodes, but not to local or indir nodes. if(OperIsConst() && ((gtFlags & GTF_REUSE_REG_VAL) != 0)) { return true; } return false; } void SetReuseRegVal() { assert(OperIsConst()); gtFlags |= GTF_REUSE_REG_VAL; } void ResetReuseRegVal() { assert(OperIsConst()); gtFlags &= ~GTF_REUSE_REG_VAL; } #ifdef DEBUG private: GenTree& operator=(const GenTree& gt) { } #endif // DEBUG #if DEBUGGABLE_GENTREE // In DEBUG builds, add a dummy virtual method, to give the debugger run-time type information. virtual void DummyVirt() {} typedef void* VtablePtr; VtablePtr GetVtableForOper(genTreeOps oper); void SetVtableForOper(genTreeOps oper); static VtablePtr s_vtablesForOpers[GT_COUNT]; static VtablePtr s_vtableForOp; #endif // DEBUGGABLE_GENTREE public: inline void* operator new(size_t sz, class Compiler*, genTreeOps oper); inline GenTree(genTreeOps oper, var_types type DEBUG_ARG(bool largeNode = false)); }; /*****************************************************************************/ // In the current design, we never instantiate GenTreeUnOp: it exists only to be // used as a base class. For unary operators, we instantiate GenTreeOp, with a NULL second // argument. We check that this is true dynamically. We could tighten this and get static // checking, but that would entail accessing the first child of a unary operator via something // like gtUnOp.gtOp1 instead of gtOp.gtOp1. struct GenTreeUnOp: public GenTree { GenTreePtr gtOp1; protected: GenTreeUnOp(genTreeOps oper, var_types type DEBUG_ARG(bool largeNode = false)) : GenTree(oper, type DEBUG_ARG(largeNode)), gtOp1(nullptr) {} GenTreeUnOp(genTreeOps oper, var_types type, GenTreePtr op1 DEBUG_ARG(bool largeNode = false)) : GenTree(oper, type DEBUG_ARG(largeNode)), gtOp1(op1) { assert(op1 != nullptr || NullOp1Legal()); if (op1 != nullptr) // Propagate effects flags from child. gtFlags |= op1->gtFlags & GTF_ALL_EFFECT; } #if DEBUGGABLE_GENTREE GenTreeUnOp() : GenTree(), gtOp1(nullptr) {} #endif }; struct GenTreeOp: public GenTreeUnOp { GenTreePtr gtOp2; GenTreeOp(genTreeOps oper, var_types type, GenTreePtr op1, GenTreePtr op2 DEBUG_ARG(bool largeNode = false)) : GenTreeUnOp(oper, type, op1 DEBUG_ARG(largeNode)), gtOp2(op2) { // comparisons are always integral types assert(!GenTree::OperIsCompare(oper) || varTypeIsIntegral(type)); // Binary operators, with a few exceptions, require a non-nullptr // second argument. assert(op2 != nullptr || NullOp2Legal()); // Unary operators, on the other hand, require a null second argument. assert(!OperIsUnary(oper) || op2 == nullptr); // Propagate effects flags from child. (UnOp handled this for first child.) if (op2 != nullptr) { gtFlags |= op2->gtFlags & GTF_ALL_EFFECT; } } // A small set of types are unary operators with optional arguments. We use // this constructor to build those. GenTreeOp(genTreeOps oper, var_types type DEBUG_ARG(bool largeNode = false)) : GenTreeUnOp(oper, type DEBUG_ARG(largeNode)), gtOp2(nullptr) { // Unary operators with optional arguments: assert(oper == GT_NOP || oper == GT_RETURN || oper == GT_RETFILT || OperIsBlkOp(oper)); } #if DEBUGGABLE_GENTREE GenTreeOp() : GenTreeUnOp(), gtOp2(nullptr) {} #endif }; struct GenTreeVal: public GenTree { size_t gtVal1; GenTreeVal(genTreeOps oper, var_types type, ssize_t val) : GenTree(oper, type), gtVal1(val) {} #if DEBUGGABLE_GENTREE GenTreeVal() : GenTree() {} #endif }; struct GenTreeIntConCommon: public GenTree { inline INT64 LngValue(); inline void SetLngValue(INT64 val); inline ssize_t IconValue(); inline void SetIconValue(ssize_t val); GenTreeIntConCommon(genTreeOps oper, var_types type DEBUG_ARG(bool largeNode = false)) : GenTree(oper, type DEBUG_ARG(largeNode)) {} #ifdef _TARGET_XARCH_ bool FitsInAddrBase(Compiler *comp); #endif #if DEBUGGABLE_GENTREE GenTreeIntConCommon() : GenTree() {} #endif }; // node representing a read from a physical register struct GenTreePhysReg: public GenTree { // physregs need a field beyond gtRegNum because // gtRegNum indicates the destination (and can be changed) // whereas reg indicates the source regNumber gtSrcReg; GenTreePhysReg(regNumber r, var_types type=TYP_I_IMPL) : GenTree(GT_PHYSREG, type), gtSrcReg(r) { } #if DEBUGGABLE_GENTREE GenTreePhysReg() : GenTree() {} #endif }; #ifndef LEGACY_BACKEND // gtJumpTable - Switch Jump Table // // This node stores a DWORD constant that represents the // absolute address of a jump table for switches. The code // generator uses this table to code the destination for every case // in an array of addresses which starting position is stored in // this constant. struct GenTreeJumpTable : public GenTreeIntConCommon { ssize_t gtJumpTableAddr; GenTreeJumpTable(var_types type DEBUG_ARG(bool largeNode = false)) : GenTreeIntConCommon(GT_JMPTABLE, type DEBUG_ARG(largeNode)) {} #if DEBUGGABLE_GENTREE GenTreeJumpTable() : GenTreeIntConCommon() {} #endif // DEBUG }; #endif // !LEGACY_BACKEND /* gtIntCon -- integer constant (GT_CNS_INT) */ struct GenTreeIntCon: public GenTreeIntConCommon { /* * This is the GT_CNS_INT struct definition. * It's used to hold for both int constants and pointer handle constants. * For the 64-bit targets we will only use GT_CNS_INT as it used to represent all the possible sizes * For the 32-bit targets we use a GT_CNS_LNG to hold a 64-bit integer constant and GT_CNS_INT for all others. * In the future when we retarget the JIT for x86 we should consider eliminating GT_CNS_LNG */ ssize_t gtIconVal; // Must overlap and have the same offset with the gtIconVal field in GenTreeLngCon below. /* The InitializeArray intrinsic needs to go back to the newarray statement to find the class handle of the array so that we can get its size. However, in ngen mode, the handle in that statement does not correspond to the compile time handle (rather it lets you get a handle at run-time). In that case, we also need to store a compile time handle, which goes in this gtCompileTimeHandle field. */ ssize_t gtCompileTimeHandle; // TODO-Cleanup: It's not clear what characterizes the cases where the field // above is used. It may be that its uses and those of the "gtFieldSeq" field below // are mutually exclusive, and they could be put in a union. Or else we should separate // this type into three subtypes. // If this constant represents the offset of one or more fields, "gtFieldSeq" represents that // sequence of fields. FieldSeqNode* gtFieldSeq; #if defined (LATE_DISASM) /* If the constant was morphed from some other node, these fields enable us to get back to what the node originally represented. See use of gtNewIconHandleNode() */ union { /* Template struct - The significant field of the other * structs should overlap exactly with this struct */ struct { unsigned gtIconHdl1; void * gtIconHdl2; } gtIconHdl; /* GT_FIELD, etc */ struct { unsigned gtIconCPX; CORINFO_CLASS_HANDLE gtIconCls; } gtIconFld; }; #endif GenTreeIntCon(var_types type, ssize_t value DEBUG_ARG(bool largeNode = false)) : GenTreeIntConCommon(GT_CNS_INT, type DEBUG_ARG(largeNode)), gtIconVal(value), gtCompileTimeHandle(0), gtFieldSeq(FieldSeqStore::NotAField()) {} GenTreeIntCon(var_types type, ssize_t value, FieldSeqNode* fields DEBUG_ARG(bool largeNode = false)) : GenTreeIntConCommon(GT_CNS_INT, type DEBUG_ARG(largeNode)), gtIconVal(value), gtCompileTimeHandle(0), gtFieldSeq(fields) { assert(fields != NULL); } #if DEBUGGABLE_GENTREE GenTreeIntCon() : GenTreeIntConCommon() {} #endif }; /* gtLngCon -- long constant (GT_CNS_LNG) */ struct GenTreeLngCon: public GenTreeIntConCommon { INT64 gtLconVal; // Must overlap and have the same offset with the gtIconVal field in GenTreeIntCon above. INT32 LoVal() { return (INT32)(gtLconVal & 0xffffffff); } INT32 HiVal() { return (INT32)(gtLconVal >> 32);; } GenTreeLngCon(INT64 val) : GenTreeIntConCommon(GT_CNS_NATIVELONG, TYP_LONG) { SetLngValue(val); } #if DEBUGGABLE_GENTREE GenTreeLngCon() : GenTreeIntConCommon() {} #endif }; inline INT64 GenTreeIntConCommon::LngValue() { #ifndef _TARGET_64BIT_ assert(gtOper == GT_CNS_LNG); return AsLngCon()->gtLconVal; #else return IconValue(); #endif } inline void GenTreeIntConCommon::SetLngValue(INT64 val) { #ifndef _TARGET_64BIT_ assert(gtOper == GT_CNS_LNG); AsLngCon()->gtLconVal = val; #else // Compile time asserts that these two fields overlap and have the same offsets: gtIconVal and gtLconVal C_ASSERT(offsetof(GenTreeLngCon, gtLconVal) == offsetof(GenTreeIntCon, gtIconVal)); C_ASSERT(sizeof(AsLngCon()->gtLconVal) == sizeof(AsIntCon()->gtIconVal)); SetIconValue(ssize_t(val)); #endif } inline ssize_t GenTreeIntConCommon::IconValue() { assert(gtOper == GT_CNS_INT); // We should never see a GT_CNS_LNG for a 64-bit target! return AsIntCon()->gtIconVal; } inline void GenTreeIntConCommon::SetIconValue(ssize_t val) { assert(gtOper == GT_CNS_INT); // We should never see a GT_CNS_LNG for a 64-bit target! AsIntCon()->gtIconVal = val; } /* gtDblCon -- double constant (GT_CNS_DBL) */ struct GenTreeDblCon: public GenTree { double gtDconVal; bool isBitwiseEqual(GenTreeDblCon* other) { unsigned __int64 bits = *(unsigned __int64 *)(>DconVal); unsigned __int64 otherBits = *(unsigned __int64 *)(&(other->gtDconVal)); return (bits == otherBits); } GenTreeDblCon(double val) : GenTree(GT_CNS_DBL, TYP_DOUBLE), gtDconVal(val) {} #if DEBUGGABLE_GENTREE GenTreeDblCon() : GenTree() {} #endif }; /* gtStrCon -- string constant (GT_CNS_STR) */ struct GenTreeStrCon: public GenTree { unsigned gtSconCPX; CORINFO_MODULE_HANDLE gtScpHnd; // Because this node can come from an inlined method we need to // have the scope handle, since it will become a helper call. GenTreeStrCon(unsigned sconCPX, CORINFO_MODULE_HANDLE mod DEBUG_ARG(bool largeNode = false)) : GenTree(GT_CNS_STR, TYP_REF DEBUG_ARG(largeNode)), gtSconCPX(sconCPX), gtScpHnd(mod) {} #if DEBUGGABLE_GENTREE GenTreeStrCon() : GenTree() {} #endif }; // Common supertype of LCL_VAR, LCL_FLD, REG_VAR, PHI_ARG // This inherits from UnOp because lclvar stores are Unops struct GenTreeLclVarCommon: public GenTreeUnOp { private: unsigned _gtLclNum; // The local number. An index into the Compiler::lvaTable array. unsigned _gtSsaNum; // The SSA number. public: GenTreeLclVarCommon(genTreeOps oper, var_types type, unsigned lclNum DEBUG_ARG(bool largeNode = false)) : GenTreeUnOp(oper, type DEBUG_ARG(largeNode)) { SetLclNum(lclNum); } unsigned GetLclNum() const { return _gtLclNum; } __declspec(property(get=GetLclNum)) unsigned gtLclNum; void SetLclNum(unsigned lclNum) { _gtLclNum = lclNum; _gtSsaNum = SsaConfig::RESERVED_SSA_NUM; } unsigned GetSsaNum() const { return _gtSsaNum; } __declspec(property(get=GetSsaNum)) unsigned gtSsaNum; void SetSsaNum(unsigned ssaNum) { _gtSsaNum = ssaNum; } bool HasSsaName() { return (gtSsaNum != SsaConfig::RESERVED_SSA_NUM); } #if DEBUGGABLE_GENTREE GenTreeLclVarCommon() : GenTreeUnOp() {} #endif }; // gtLclVar -- load/store/addr of local variable struct GenTreeLclVar: public GenTreeLclVarCommon { IL_OFFSET gtLclILoffs; // instr offset of ref (only for debug info) GenTreeLclVar(var_types type, unsigned lclNum, IL_OFFSET ilOffs DEBUG_ARG(bool largeNode = false)) : GenTreeLclVarCommon(GT_LCL_VAR, type, lclNum DEBUG_ARG(largeNode)), gtLclILoffs(ilOffs) {} GenTreeLclVar(genTreeOps oper, var_types type, unsigned lclNum, IL_OFFSET ilOffs DEBUG_ARG(bool largeNode = false)) : GenTreeLclVarCommon(oper, type, lclNum DEBUG_ARG(largeNode)), gtLclILoffs(ilOffs) { assert(OperIsLocal(oper)); } #if DEBUGGABLE_GENTREE GenTreeLclVar() : GenTreeLclVarCommon() {} #endif }; // gtLclFld -- load/store/addr of local variable field struct GenTreeLclFld: public GenTreeLclVarCommon { unsigned gtLclOffs; // offset into the variable to access FieldSeqNode* gtFieldSeq; // This LclFld node represents some sequences of accesses. // old/FE style constructor where load/store/addr share same opcode GenTreeLclFld(var_types type, unsigned lclNum, unsigned lclOffs) : GenTreeLclVarCommon(GT_LCL_FLD, type, lclNum), gtLclOffs(lclOffs), gtFieldSeq(NULL) { assert(sizeof(*this) <= s_gtNodeSizes[GT_LCL_FLD]); } GenTreeLclFld(genTreeOps oper, var_types type, unsigned lclNum, unsigned lclOffs) : GenTreeLclVarCommon(oper, type, lclNum), gtLclOffs(lclOffs), gtFieldSeq(NULL) { assert(sizeof(*this) <= s_gtNodeSizes[GT_LCL_FLD]); } #if DEBUGGABLE_GENTREE GenTreeLclFld() : GenTreeLclVarCommon() {} #endif }; struct GenTreeRegVar: public GenTreeLclVarCommon { // TODO-Cleanup: Note that the base class GenTree already has a gtRegNum field. // It's not clear exactly why a GT_REG_VAR has a separate field. When // GT_REG_VAR is created, the two are identical. It appears that they may // or may not remain so. In particular, there is a comment in stackfp.cpp // that states: // // There used to be an assertion: assert(src->gtRegNum == src->gtRegVar.gtRegNum, ...) // here, but there's actually no reason to assume that. AFAICT, for FP vars under stack FP, // src->gtRegVar.gtRegNum is the allocated stack pseudo-register, but src->gtRegNum is the // FP stack position into which that is loaded to represent a particular use of the variable. // // It might be the case that only for stackfp do they ever differ. // // The following might be possible: the GT_REG_VAR node has a last use prior to a complex // subtree being evaluated. It could then be spilled from the register. Later, // it could be unspilled into a different register, which would be recorded at // the unspill time in the GenTree::gtRegNum, whereas GenTreeRegVar::gtRegNum // is left alone. It's not clear why that is useful. // // Assuming there is a particular use, like stack fp, that requires it, maybe we // can get rid of GT_REG_VAR and just leave it as GT_LCL_VAR, using the base class gtRegNum field. // If we need it for stackfp, we could add a GenTreeStackFPRegVar type, which carries both the // pieces of information, in a clearer and more specific way (in particular, with // a different member name). // private: regNumberSmall _gtRegNum; public: GenTreeRegVar(var_types type, unsigned lclNum, regNumber regNum) : GenTreeLclVarCommon(GT_REG_VAR, type, lclNum ) { gtRegNum = regNum; } // The register number is stored in a small format (8 bits), but the getters return and the setters take // a full-size (unsigned) format, to localize the casts here. __declspec(property(get=GetRegNum,put=SetRegNum)) regNumber gtRegNum; regNumber GetRegNum() const { return (regNumber) _gtRegNum; } void SetRegNum(regNumber reg) { _gtRegNum = (regNumberSmall) reg; assert(_gtRegNum == reg); } #if DEBUGGABLE_GENTREE GenTreeRegVar() : GenTreeLclVarCommon() {} #endif }; /* gtCast -- conversion to a different type (GT_CAST) */ struct GenTreeCast: public GenTreeOp { GenTreePtr& CastOp() { return gtOp1; } var_types gtCastType; GenTreeCast(var_types type, GenTreePtr op, var_types castType DEBUG_ARG(bool largeNode = false)) : GenTreeOp(GT_CAST, type, op, nullptr DEBUG_ARG(largeNode)), gtCastType(castType) {} #if DEBUGGABLE_GENTREE GenTreeCast() : GenTreeOp() {} #endif }; // GT_BOX nodes are place markers for boxed values. The "real" tree // for most purposes is in gtBoxOp. struct GenTreeBox: public GenTreeUnOp { // An expanded helper call to implement the "box" if we don't get // rid of it any other way. Must be in same position as op1. GenTreePtr& BoxOp() { return gtOp1; } // This is the statement that contains the assignment tree when the node is an inlined GT_BOX on a value // type GenTreePtr gtAsgStmtWhenInlinedBoxValue; GenTreeBox(var_types type, GenTreePtr boxOp, GenTreePtr asgStmtWhenInlinedBoxValue) : GenTreeUnOp(GT_BOX, type, boxOp), gtAsgStmtWhenInlinedBoxValue(asgStmtWhenInlinedBoxValue) {} #if DEBUGGABLE_GENTREE GenTreeBox() : GenTreeUnOp() {} #endif }; /* gtField -- data member ref (GT_FIELD) */ struct GenTreeField: public GenTree { GenTreePtr gtFldObj; CORINFO_FIELD_HANDLE gtFldHnd; DWORD gtFldOffset; #ifdef FEATURE_READYTORUN_COMPILER CORINFO_CONST_LOOKUP gtFieldLookup; #endif GenTreeField(var_types type) : GenTree(GT_FIELD, type ) {} #if DEBUGGABLE_GENTREE GenTreeField() : GenTree() {} #endif }; // Represents the Argument list of a call node, as a Lisp-style linked list. // (Originally I had hoped that this could have *only* the m_arg/m_rest fields, but it turns out // that enough of the GenTree mechanism is used that it makes sense just to make it a subtype. But // note that in many ways, this is *not* a "real" node of the tree, but rather a mechanism for // giving call nodes a flexible number of children. GenTreeArgListNodes never evaluate to registers, // for example.) // Note that while this extends GenTreeOp, it is *not* an EXOP. We don't add any new fields, and one // is free to allocate a GenTreeOp of type GT_LIST. If you use this type, you get the convenient Current/Rest // method names for the arguments. struct GenTreeArgList: public GenTreeOp { GenTreePtr& Current() { return gtOp1; } GenTreeArgList*& Rest() { assert(gtOp2 == NULL || gtOp2->OperGet() == GT_LIST); return *reinterpret_cast(>Op2); } #if DEBUGGABLE_GENTREE GenTreeArgList() : GenTreeOp() {} #endif GenTreeArgList(GenTreePtr arg ) : GenTreeOp(GT_LIST, TYP_VOID, arg, NULL) {} GenTreeArgList(GenTreePtr arg, GenTreeArgList* rest) : GenTreeOp(GT_LIST, TYP_VOID, arg, rest) { assert (arg != NULL); gtFlags |= arg->gtFlags & GTF_ALL_EFFECT; if (rest != NULL) gtFlags |= rest->gtFlags & GTF_ALL_EFFECT; } }; // There was quite a bit of confusion in the code base about which of gtOp1 and gtOp2 was the // 'then' and 'else' clause of a colon node. Adding these accessors, while not enforcing anything, // at least *allows* the programmer to be obviously correct. // However, these conventions seem backward. // TODO-Cleanup: If we could get these accessors used everywhere, then we could switch them. struct GenTreeColon: public GenTreeOp { GenTreePtr& ThenNode() { return gtOp2; } GenTreePtr& ElseNode() { return gtOp1; } #if DEBUGGABLE_GENTREE GenTreeColon() : GenTreeOp() {} #endif GenTreeColon(var_types typ, GenTreePtr thenNode, GenTreePtr elseNode) : GenTreeOp(GT_COLON, typ, elseNode, thenNode) {} }; /* gtCall -- method call (GT_CALL) */ typedef class fgArgInfo * fgArgInfoPtr; struct GenTreeCall: public GenTree { GenTreePtr gtCallObjp; // The instance argument ('this' pointer) GenTreeArgList* gtCallArgs; // The list of arguments in original evaluation order GenTreeArgList* gtCallLateArgs; // On x86: The register arguments in an optimal order // On ARM/x64: - also includes any outgoing arg space arguments // - that were evaluated into a temp LclVar fgArgInfoPtr fgArgInfo; #if !FEATURE_FIXED_OUT_ARGS int regArgListCount; regList regArgList; #endif // TODO-Throughput: Revisit this (this used to be only defined if // FEATURE_FIXED_OUT_ARGS was enabled, so this makes GenTreeCall 4 bytes bigger on x86). CORINFO_SIG_INFO* callSig; // Used by tail calls and to register callsites with the EE regMaskTP gtCallRegUsedMask; // mask of registers used to pass parameters #define GTF_CALL_M_EXPLICIT_TAILCALL 0x0001 // GT_CALL -- the call is "tail" prefixed and importer has performed tail call checks #define GTF_CALL_M_TAILCALL 0x0002 // GT_CALL -- the call is a tailcall #define GTF_CALL_M_VARARGS 0x0004 // GT_CALL -- the call uses varargs ABI #define GTF_CALL_M_RETBUFFARG 0x0008 // GT_CALL -- first parameter is the return buffer argument #define GTF_CALL_M_DELEGATE_INV 0x0010 // GT_CALL -- call to Delegate.Invoke #define GTF_CALL_M_NOGCCHECK 0x0020 // GT_CALL -- not a call for computing full interruptability #define GTF_CALL_M_SPECIAL_INTRINSIC 0x0040 // GT_CALL -- function that could be optimized as an intrinsic // in special cases. Used to optimize fast way out in morphing #define GTF_CALL_M_UNMGD_THISCALL 0x0080 // "this" pointer (first argument) should be enregistered (only for GTF_CALL_UNMANAGED) #define GTF_CALL_M_VIRTSTUB_REL_INDIRECT 0x0080 // the virtstub is indirected through a relative address (only for GTF_CALL_VIRT_STUB) #define GTF_CALL_M_NONVIRT_SAME_THIS 0x0080 // callee "this" pointer is equal to caller this pointer (only for GTF_CALL_NONVIRT) #define GTF_CALL_M_FRAME_VAR_DEATH 0x0100 // GT_CALL -- the compLvFrameListRoot variable dies here (last use) #ifndef LEGACY_BACKEND #define GTF_CALL_M_TAILCALL_VIA_HELPER 0x0200 // GT_CALL -- call is a tail call dispatched via tail call JIT helper. #endif // !LEGACY_BACKEND #if FEATURE_TAILCALL_OPT #define GTF_CALL_M_IMPLICIT_TAILCALL 0x0400 // GT_CALL -- call is an opportunistic tail call and importer has performed tail call checks #endif #define GTF_CALL_M_PINVOKE 0x0800 // GT_CALL -- call is a pinvoke. This mirrors VM flag CORINFO_FLG_PINVOKE. // A call marked as Pinvoke is not necessarily a GT_CALL_UNMANAGED. For e.g. // an IL Stub dynamically generated for a PInvoke declaration is flagged as // a Pinvoke but not as an unmanaged call. See impCheckForPInvokeCall() to // know when these flags are set. bool IsUnmanaged() { return (gtFlags & GTF_CALL_UNMANAGED) != 0; } bool NeedsNullCheck() { return (gtFlags & GTF_CALL_NULLCHECK) != 0; } bool CallerPop() { return (gtFlags & GTF_CALL_POP_ARGS) != 0; } bool IsVirtual() { return (gtFlags & GTF_CALL_VIRT_KIND_MASK) != GTF_CALL_NONVIRT; } bool IsVirtualStub() { return (gtFlags & GTF_CALL_VIRT_KIND_MASK) == GTF_CALL_VIRT_STUB; } bool IsVirtualVtable() { return (gtFlags & GTF_CALL_VIRT_KIND_MASK) == GTF_CALL_VIRT_VTABLE; } bool IsInlineCandidate() { return (gtFlags & GTF_CALL_INLINE_CANDIDATE) != 0; } #ifndef LEGACY_BACKEND // Whether the method has non-standard args (i.e. passed in R10 or R11) // See fgMorphArgs() to know the call types for which non-standard args are inserted. bool HasNonStandardArgs() { return IsUnmanaged() || (gtCallType == CT_INDIRECT && (IsVirtualStub() || gtCallCookie)); } // Get the count of non-standard arg count int GetNonStandardArgCount() { if (IsUnmanaged()) { // R11 = PInvoke cookie param return 1; } else if (gtCallType == CT_INDIRECT) { if (IsVirtualStub()) { // R11 = Virtual stub param return 1; } else if (gtCallCookie != nullptr) { // R10 = PInvoke target param // R11 = PInvoke cookie param return 2; } } return 0; } #endif // !LEGACY_BACKEND // Returns true if VM has flagged this method as CORINFO_FLG_PINVOKE. bool IsPInvoke() { return (gtCallMoreFlags & GTF_CALL_M_PINVOKE) != 0; } // Note that the distinction of whether tail prefixed or an implicit tail call // is maintained on a call node till fgMorphCall() after which it will be // either a tail call (i.e. IsTailCall() is true) or a non-tail call. bool IsTailPrefixedCall() { return (gtCallMoreFlags & GTF_CALL_M_EXPLICIT_TAILCALL) != 0; } // This method returning "true" implies that tail call flowgraph morhphing has // performed final checks and committed to making a tail call. bool IsTailCall() { return (gtCallMoreFlags & GTF_CALL_M_TAILCALL) != 0; } // This method returning "true" implies that importer has performed tail call checks // and providing a hint that this can be converted to a tail call. bool CanTailCall() { return IsTailPrefixedCall() || IsImplicitTailCall(); } #ifndef LEGACY_BACKEND bool IsTailCallViaHelper() { return IsTailCall() && (gtCallMoreFlags & GTF_CALL_M_TAILCALL_VIA_HELPER); } #else // LEGACY_BACKEND bool IsTailCallViaHelper() { return true; } #endif // LEGACY_BACKEND #if FEATURE_FASTTAILCALL bool IsFastTailCall() { return IsTailCall() && !(gtCallMoreFlags & GTF_CALL_M_TAILCALL_VIA_HELPER); } #else // !FEATURE_FASTTAILCALL bool IsFastTailCall() { return false; } #endif // !FEATURE_FASTTAILCALL #if FEATURE_TAILCALL_OPT // Returns true if this is marked for opportunistic tail calling. // That is, can be tail called though not explicitly prefixed with "tail" prefix. bool IsImplicitTailCall() { return (gtCallMoreFlags & GTF_CALL_M_IMPLICIT_TAILCALL) != 0; } #else // !FEATURE_TAILCALL_OPT bool IsImplicitTailCall() { return false; } #endif // !FEATURE_TAILCALL_OPT bool IsSameThis() { return (gtCallMoreFlags & GTF_CALL_M_NONVIRT_SAME_THIS) != 0; } bool IsDelegateInvoke(){ return (gtCallMoreFlags & GTF_CALL_M_DELEGATE_INV) != 0; } bool IsVirtualStubRelativeIndir() { return (gtCallMoreFlags & GTF_CALL_M_VIRTSTUB_REL_INDIRECT) != 0; } bool IsVarargs() { return (gtCallMoreFlags & GTF_CALL_M_VARARGS) != 0; } unsigned short gtCallMoreFlags; // in addition to gtFlags unsigned char gtCallType :3; // value from the gtCallTypes enumeration unsigned char gtReturnType :5; // exact return type CORINFO_CLASS_HANDLE gtRetClsHnd; // The return type handle of the call if it is a struct; used for HFAs. union { // only used for CALLI unmanaged calls (CT_INDIRECT) GenTreePtr gtCallCookie; // gtInlineCandidateInfo is only used when inlining methods InlineCandidateInfo * gtInlineCandidateInfo; void * gtStubCallStubAddr; // GTF_CALL_VIRT_STUB - these are never inlined CORINFO_GENERIC_HANDLE compileTimeHelperArgumentHandle; // Used to track type handle argument of dynamic helpers }; // expression evaluated after args are placed which determines the control target GenTree * gtControlExpr; union { CORINFO_METHOD_HANDLE gtCallMethHnd; // CT_USER_FUNC GenTreePtr gtCallAddr; // CT_INDIRECT }; #ifdef FEATURE_READYTORUN_COMPILER // Call target lookup info for method call from a Ready To Run module CORINFO_CONST_LOOKUP gtEntryPoint; #endif GenTreeCall(var_types type) : GenTree(GT_CALL, type) {} #if DEBUGGABLE_GENTREE GenTreeCall() : GenTree() {} #endif }; struct GenTreeCmpXchg: public GenTree { GenTreePtr gtOpLocation; GenTreePtr gtOpValue; GenTreePtr gtOpComparand; GenTreeCmpXchg(var_types type, GenTreePtr loc, GenTreePtr val, GenTreePtr comparand) : GenTree(GT_CMPXCHG, type), gtOpLocation(loc), gtOpValue(val), gtOpComparand(comparand) { // There's no reason to do a compare-exchange on a local location, so we'll assume that all of these // have global effects. gtFlags |= GTF_GLOB_EFFECT; } #if DEBUGGABLE_GENTREE GenTreeCmpXchg() : GenTree() {} #endif }; struct GenTreeFptrVal: public GenTree { CORINFO_METHOD_HANDLE gtFptrMethod; #ifdef FEATURE_READYTORUN_COMPILER CORINFO_CONST_LOOKUP gtEntryPoint; CORINFO_CONST_LOOKUP gtDelegateCtor; #endif GenTreeFptrVal(var_types type, CORINFO_METHOD_HANDLE meth) : GenTree(GT_FTN_ADDR, type), gtFptrMethod(meth) {} #if DEBUGGABLE_GENTREE GenTreeFptrVal() : GenTree() {} #endif }; /* gtQmark */ struct GenTreeQmark : public GenTreeOp { // Livesets on entry to then and else subtrees VARSET_TP gtThenLiveSet; VARSET_TP gtElseLiveSet; // The "Compiler*" argument is not a DEBUG_ARG here because we use it to keep track of the set of // (possible) QMark nodes. GenTreeQmark(var_types type, GenTreePtr cond, GenTreePtr colonOp, class Compiler* comp); #if DEBUGGABLE_GENTREE GenTreeQmark() : GenTreeOp(GT_QMARK, TYP_INT, NULL, NULL) {} #endif }; #if INLINE_MATH /* gtMath -- math intrinsic (possibly-binary op [NULL op2 is allowed] with an additional field) */ struct GenTreeMath: public GenTreeOp { CorInfoIntrinsics gtMathFN; GenTreeMath(var_types type, GenTreePtr op1, CorInfoIntrinsics mathFN) : GenTreeOp(GT_MATH, type, op1, NULL), gtMathFN(mathFN) {} GenTreeMath(var_types type, GenTreePtr op1, GenTreePtr op2, CorInfoIntrinsics mathFN) : GenTreeOp(GT_MATH, type, op1, op2), gtMathFN(mathFN) {} #if DEBUGGABLE_GENTREE GenTreeMath() : GenTreeOp() {} #endif }; #endif // INLINE_MATH #ifdef FEATURE_SIMD /* gtSIMD -- SIMD intrinsic (possibly-binary op [NULL op2 is allowed] with additional fields) */ struct GenTreeSIMD: public GenTreeOp { SIMDIntrinsicID gtSIMDIntrinsicID; // operation Id var_types gtSIMDBaseType; // SIMD vector base type unsigned gtSIMDSize; // SIMD vector size in bytes GenTreeSIMD(var_types type, GenTreePtr op1, SIMDIntrinsicID simdIntrinsicID, var_types baseType, unsigned size) : GenTreeOp(GT_SIMD, type, op1, nullptr), gtSIMDIntrinsicID(simdIntrinsicID), gtSIMDBaseType(baseType), gtSIMDSize(size) {} GenTreeSIMD(var_types type, GenTreePtr op1, GenTreePtr op2, SIMDIntrinsicID simdIntrinsicID, var_types baseType, unsigned size) : GenTreeOp(GT_SIMD, type, op1, op2), gtSIMDIntrinsicID(simdIntrinsicID), gtSIMDBaseType(baseType), gtSIMDSize(size) {} #if DEBUGGABLE_GENTREE GenTreeSIMD() : GenTreeOp() {} #endif }; #endif // FEATURE_SIMD /* gtIndex -- array access */ struct GenTreeIndex: public GenTreeOp { GenTreePtr& Arr() { return gtOp1; } GenTreePtr& Index() { return gtOp2; } unsigned gtIndElemSize; // size of elements in the array CORINFO_CLASS_HANDLE gtStructElemClass; // If the element type is a struct, this is the struct type. GenTreeIndex(var_types type, GenTreePtr arr, GenTreePtr ind, unsigned indElemSize) : GenTreeOp(GT_INDEX, type, arr, ind), gtIndElemSize(indElemSize), gtStructElemClass(nullptr) // We always initialize this after construction. { #ifdef DEBUG static ConfigDWORD fJitSkipArrayBoundCheck; if (fJitSkipArrayBoundCheck.val(CLRConfig::INTERNAL_JitSkipArrayBoundCheck) == 1) { // Skip bounds check } else #endif { // Do bounds check gtFlags |= GTF_INX_RNGCHK; } if (type == TYP_REF) { gtFlags |= GTF_INX_REFARR_LAYOUT; } gtFlags |= GTF_EXCEPT|GTF_GLOB_REF; } #if DEBUGGABLE_GENTREE GenTreeIndex() : GenTreeOp() {} #endif }; /* gtArrLen -- array length (GT_ARR_LENGTH) GT_ARR_LENGTH is used for "arr.length" */ struct GenTreeArrLen: public GenTreeUnOp { GenTreePtr& ArrRef() { return gtOp1; } // the array address node private: int gtArrLenOffset; // constant to add to "gtArrRef" to get the address of the array length. public: inline int ArrLenOffset() { return gtArrLenOffset; } GenTreeArrLen(var_types type, GenTreePtr arrRef, int lenOffset) : GenTreeUnOp(GT_ARR_LENGTH, type, arrRef), gtArrLenOffset(lenOffset) {} #if DEBUGGABLE_GENTREE GenTreeArrLen() : GenTreeUnOp() {} #endif }; // This takes an array length,an index value, and the label to jump to if the index is out of range. // It generates no result. struct GenTreeBoundsChk: public GenTree { GenTreePtr gtArrLen; // An expression for the length of the array being indexed. GenTreePtr gtIndex; // The index expression. GenTreePtr gtIndRngFailBB; // Label to jump to for array-index-out-of-range /* Only out-of-ranges at same stack depth can jump to the same label (finding return address is easier) For delayed calling of fgSetRngChkTarget() so that the optimizer has a chance of eliminating some of the rng checks */ unsigned gtStkDepth; GenTreeBoundsChk(genTreeOps oper, var_types type, GenTreePtr arrLen, GenTreePtr index) : GenTree(oper, type), gtArrLen(arrLen), gtIndex(index), gtIndRngFailBB(NULL), gtStkDepth(0) { // Effects flags propagate upwards. gtFlags |= (arrLen->gtFlags & GTF_ALL_EFFECT); gtFlags |= GTF_EXCEPT; } #if DEBUGGABLE_GENTREE GenTreeBoundsChk() : GenTree() {} #endif // If the gtArrLen is really an array length, returns array reference, else "NULL". GenTreePtr GetArray() { if (gtArrLen->OperGet() == GT_ARR_LENGTH) { return gtArrLen->gtArrLen.ArrRef(); } else { return NULL; } } }; // gtArrElem -- general array element (GT_ARR_ELEM), for non "SZ_ARRAYS" // -- multidimensional arrays, or 1-d arrays with non-zero lower bounds. struct GenTreeArrElem: public GenTree { GenTreePtr gtArrObj; #define GT_ARR_MAX_RANK 3 GenTreePtr gtArrInds[GT_ARR_MAX_RANK]; // Indices unsigned char gtArrRank; // Rank of the array unsigned char gtArrElemSize; // !!! Caution, this is an "unsigned char", it is used only // on the optimization path of array intrisics. // It stores the size of array elements WHEN it can fit // into an "unsigned char". // This has caused VSW 571394. var_types gtArrElemType; // The array element type // Requires that "inds" is a pointer to an array of "rank" GenTreePtrs for the indices. GenTreeArrElem(var_types type, GenTreePtr arr, unsigned char rank, unsigned char elemSize, var_types elemType, GenTreePtr* inds) : GenTree(GT_ARR_ELEM, type), gtArrObj(arr), gtArrRank(rank), gtArrElemSize(elemSize), gtArrElemType(elemType) { for (unsigned char i = 0; i < rank; i++) gtArrInds[i] = inds[i]; gtFlags |= GTF_EXCEPT; } #if DEBUGGABLE_GENTREE GenTreeArrElem() : GenTree() {} #endif }; //-------------------------------------------- // // GenTreeArrIndex (gtArrIndex): Expression to bounds-check the index for one dimension of a // multi-dimensional or non-zero-based array., and compute the effective index // (i.e. subtracting the lower bound). // // Notes: // This node is similar in some ways to GenTreeBoundsChk, which ONLY performs the check. // The reason that this node incorporates the check into the effective index computation is // to avoid duplicating the codegen, as the effective index is required to compute the // offset anyway. // TODO-CQ: Enable optimization of the lower bound and length by replacing this: // /--* // +--* // +--* ArrIndex[i, ] // with something like: // /--* // /--* ArrLowerBound[i, ] // | /--* // +--* ArrLen[i, ] (either generalize GT_ARR_LENGTH or add a new node) // +--* // +--* ArrIndex[i, ] // Which could, for example, be optimized to the following when known to be within bounds: // /--* TempForLowerBoundDim0 // +--* // +--* - (GT_SUB) // struct GenTreeArrIndex: public GenTreeOp { // The array object - may be any expression producing an Array reference, but is likely to be a lclVar. GenTreePtr& ArrObj() { return gtOp1; } // The index expression - may be any integral expression. GenTreePtr& IndexExpr() { return gtOp2; } unsigned char gtCurrDim; // The current dimension unsigned char gtArrRank; // Rank of the array var_types gtArrElemType; // The array element type GenTreeArrIndex(var_types type, GenTreePtr arrObj, GenTreePtr indexExpr, unsigned char currDim, unsigned char arrRank, var_types elemType) : GenTreeOp(GT_ARR_INDEX, type, arrObj, indexExpr), gtCurrDim(currDim), gtArrRank(arrRank), gtArrElemType(elemType) { gtFlags |= GTF_EXCEPT; } #if DEBUGGABLE_GENTREE protected: friend GenTree; // Used only for GenTree::GetVtableForOper() GenTreeArrIndex() : GenTreeOp() {} #endif }; // Represents either an InitBlk, InitObj, CpBlk or CpObj // MSIL OpCode. struct GenTreeBlkOp : public GenTreeOp { public: // The destination for the CpBlk/CpObj/InitBlk/InitObj to copy bits to GenTreePtr Dest() { assert(gtOp1->gtOper == GT_LIST); return gtOp1->gtOp.gtOp1; } // True if this BlkOpNode is a volatile memory operation. bool IsVolatile() const { return (gtFlags & GTF_BLK_VOLATILE) != 0; } // Instruction selection: during codegen time, what code sequence we will be using // to encode this operation. enum { BlkOpKindInvalid, BlkOpKindHelper, BlkOpKindRepInstr, BlkOpKindUnroll, } gtBlkOpKind; bool gtBlkOpGcUnsafe; GenTreeBlkOp(genTreeOps oper) : GenTreeOp(oper, TYP_VOID DEBUG_ARG(true)), gtBlkOpKind(BlkOpKindInvalid), gtBlkOpGcUnsafe(false) { assert(OperIsBlkOp(oper)); } #if DEBUGGABLE_GENTREE protected: friend GenTree; GenTreeBlkOp() : GenTreeOp(){} #endif // DEBUGGABLE_GENTREE }; // Represents a CpObj MSIL Node. struct GenTreeCpObj : public GenTreeBlkOp { public: // The source for the CpBlk/CpObj to copy bits from GenTreePtr Source() { assert(gtOper == GT_COPYOBJ && gtOp1->gtOper == GT_LIST); return gtOp1->gtOp.gtOp2; } // In the case of CopyObj, this is the class token that represents the type that is being copied. GenTreePtr ClsTok() { return gtOp2; } // If non-null, this array represents the gc-layout of the class that is being copied // with CpObj. BYTE* gtGcPtrs; // If non-zero, this is the number of slots in the class layout that // contain gc-pointers. unsigned gtGcPtrCount; // If non-zero, the number of pointer-sized slots that constitutes the class token in CpObj. unsigned gtSlots; GenTreeCpObj(unsigned gcPtrCount, unsigned gtSlots, BYTE* gtGcPtrs) : GenTreeBlkOp(GT_COPYOBJ), gtGcPtrs(gtGcPtrs), gtGcPtrCount(gcPtrCount), gtSlots(gtSlots){ } #if DEBUGGABLE_GENTREE protected: friend GenTree; GenTreeCpObj() : GenTreeBlkOp(), gtGcPtrs(nullptr), gtGcPtrCount(0), gtSlots(0) {} #endif // DEBUGGABLE_GENTREE }; // Represents either an InitBlk or InitObj MSIL OpCode. struct GenTreeInitBlk : public GenTreeBlkOp { public: // The value used to fill the destination buffer. GenTreePtr InitVal() { assert(gtOp1->gtOper == GT_LIST); return gtOp1->gtOp.gtOp2; } // The size of the buffer to be copied. GenTreePtr Size() { return gtOp2; } GenTreeInitBlk() : GenTreeBlkOp(GT_INITBLK){} #if DEBUGGABLE_GENTREE protected: friend GenTree; #endif // DEBUGGABLE_GENTREE }; // Represents a CpBlk or CpObj with no GC-pointers MSIL OpCode. struct GenTreeCpBlk : public GenTreeBlkOp { public: // The value used to fill the destination buffer. // The source for the CpBlk/CpObj to copy bits from GenTreePtr Source() { assert(gtOp1->gtOper == GT_LIST); return gtOp1->gtOp.gtOp2; } // The size of the buffer to be copied. GenTreePtr Size() { return gtOp2; } GenTreeCpBlk() : GenTreeBlkOp(GT_COPYBLK){} #if DEBUGGABLE_GENTREE protected: friend GenTree; #endif // DEBUGGABLE_GENTREE }; //-------------------------------------------- // // GenTreeArrOffset (gtArrOffset): Expression to compute the accumulated offset for the address // of an element of a multi-dimensional or non-zero-based array. // // Notes: // The result of this expression is (gtOffset * dimSize) + gtIndex // where dimSize is the length/stride/size of the dimension, and is obtained from gtArrObj. // This node is generated in conjunction with the GenTreeArrIndex node, which computes the // effective index for a single dimension. The sub-trees can be separately optimized, e.g. // within a loop body where the expression for the 0th dimension may be invariant. // // Here is an example of how the tree might look for a two-dimension array reference: // /--* const 0 // | /--* // | +--* // +--* ArrIndex[i, ] // +--* // /--| arrOffs[i, ] // | +--* // | +--* // +--* ArrIndex[*,j] // +--* // /--| arrOffs[*,j] // TODO-CQ: see comment on GenTreeArrIndex for how its representation may change. When that // is done, we will also want to replace the argument to arrOffs with the // ArrLen as for GenTreeArrIndex. // struct GenTreeArrOffs: public GenTree { GenTreePtr gtOffset; // The accumulated offset for lower dimensions - must be TYP_I_IMPL, and // will either be a CSE temp, the constant 0, or another GenTreeArrOffs node. GenTreePtr gtIndex; // The effective index for the current dimension - must be non-negative // and can be any expression (though it is likely to be either a GenTreeArrIndex, // node, a lclVar, or a constant). GenTreePtr gtArrObj; // The array object - may be any expression producing an Array reference, // but is likely to be a lclVar. unsigned char gtCurrDim; // The current dimension unsigned char gtArrRank; // Rank of the array var_types gtArrElemType; // The array element type GenTreeArrOffs(var_types type, GenTreePtr offset, GenTreePtr index, GenTreePtr arrObj, unsigned char currDim, unsigned char rank, var_types elemType) : GenTree(GT_ARR_OFFSET, type), gtOffset(offset), gtIndex(index), gtArrObj(arrObj), gtCurrDim(currDim), gtArrRank(rank), gtArrElemType(elemType) { assert(index->gtFlags & GTF_EXCEPT); gtFlags |= GTF_EXCEPT; } #if DEBUGGABLE_GENTREE GenTreeArrOffs() : GenTree() {} #endif }; /* gtAddrMode -- Target-specific canonicalized addressing expression (GT_LEA) */ struct GenTreeAddrMode: public GenTreeOp { // Address is Base + Index*Scale + Offset. // These are the legal patterns: // // Base // Base != nullptr && Index == nullptr && Scale == 0 && Offset == 0 // Base + Index*Scale // Base != nullptr && Index != nullptr && Scale != 0 && Offset == 0 // Base + Offset // Base != nullptr && Index == nullptr && Scale == 0 && Offset != 0 // Base + Index*Scale + Offset // Base != nullptr && Index != nullptr && Scale != 0 && Offset != 0 // Index*Scale // Base == nullptr && Index != nullptr && Scale > 1 && Offset == 0 // Index*Scale + Offset // Base == nullptr && Index != nullptr && Scale > 1 && Offset != 0 // Offset // Base == nullptr && Index == nullptr && Scale == 0 && Offset != 0 // // So, for example: // 1. Base + Index is legal with Scale==1 // 2. If Index is null, Scale should be zero (or unintialized / unused) // 3. If Scale==1, then we should have "Base" instead of "Index*Scale", and "Base + Offset" instead of "Index*Scale + Offset". // First operand is base address/pointer bool HasBase() const { return gtOp1 != nullptr; } GenTreePtr& Base() { return gtOp1; } // Second operand is scaled index value bool HasIndex() const { return gtOp2 != nullptr; } GenTreePtr& Index() { return gtOp2; } unsigned gtScale; // The scale factor unsigned gtOffset; // The offset to add GenTreeAddrMode(var_types type, GenTreePtr base, GenTreePtr index, unsigned scale, unsigned offset) : GenTreeOp(GT_LEA, type, base, index ) { gtScale = scale; gtOffset = offset; } #if DEBUGGABLE_GENTREE protected: friend GenTree; // Used only for GenTree::GetVtableForOper() GenTreeAddrMode() : GenTreeOp() {} #endif }; // Indir is just an op, no additional data, but some additional abstractions struct GenTreeIndir: public GenTreeOp { // like an assign, op1 is the destination GenTreePtr& Addr() { return gtOp1; } // these methods provide an interface to the indirection node which bool HasBase(); bool HasIndex(); GenTree* Base(); GenTree* Index(); unsigned Scale(); size_t Offset(); GenTreeIndir(genTreeOps oper, var_types type, GenTree *addr, GenTree *data) : GenTreeOp(oper, type, addr, data) { } #if DEBUGGABLE_GENTREE protected: friend GenTree; // Used only for GenTree::GetVtableForOper() GenTreeIndir() : GenTreeOp() {} #endif }; // StoreInd is just a BinOp, no additional data struct GenTreeStoreInd: public GenTreeIndir { GenTreePtr& Data() { return gtOp2; } GenTreeStoreInd(var_types type, GenTree *destPtr, GenTree *data) : GenTreeIndir(GT_STOREIND, type, destPtr, data) { } #if DEBUGGABLE_GENTREE protected: friend GenTree; // Used only for GenTree::GetVtableForOper() GenTreeStoreInd() : GenTreeIndir() {} #endif }; /* gtRetExp -- Place holder for the return expression from an inline candidate (GT_RET_EXPR) */ struct GenTreeRetExpr: public GenTree { GenTreePtr gtInlineCandidate; #ifdef _TARGET_ARM_ CORINFO_CLASS_HANDLE gtRetClsHnd; #endif GenTreeRetExpr(var_types type) : GenTree(GT_RET_EXPR, type) {} #if DEBUGGABLE_GENTREE GenTreeRetExpr() : GenTree() {} #endif }; /* gtStmt -- 'statement expr' (GT_STMT) */ struct GenTreeStmt: public GenTree { GenTreePtr gtStmtExpr; // root of the expression tree GenTreePtr gtStmtList; // first node (for forward walks) inlExpPtr gtInlineExpList; // The inline expansion list of this statement. // This is a list of CORINFO_METHOD_HANDLEs // that shows the history of inline expansion // which leads to this statement. #if defined(DEBUGGING_SUPPORT) || defined(DEBUG) IL_OFFSETX gtStmtILoffsx; // instr offset (if available) #endif #ifdef DEBUG IL_OFFSET gtStmtLastILoffs;// instr offset at end of stmt #endif bool gtStmtIsTopLevel() { return (gtFlags & GTF_STMT_TOP_LEVEL) != 0; } bool gtStmtIsEmbedded() { return !gtStmtIsTopLevel(); } // Return the next statement, if it is embedded, otherwise nullptr GenTreeStmt* gtStmtNextIfEmbedded() { GenTree* nextStmt = gtNext; if (nextStmt != nullptr && nextStmt->gtStmt.gtStmtIsEmbedded()) { return nextStmt->AsStmt(); } else { return nullptr; } } GenTree* gtStmtNextTopLevelStmt() { GenTree* nextStmt = gtNext; while (nextStmt != nullptr && nextStmt->gtStmt.gtStmtIsEmbedded()) { nextStmt = nextStmt->gtNext; } return nextStmt; } __declspec(property(get=getNextStmt)) GenTreeStmt* gtNextStmt; __declspec(property(get=getPrevStmt)) GenTreeStmt* gtPrevStmt; GenTreeStmt* getNextStmt() { if (gtNext == nullptr) return nullptr; else return gtNext->AsStmt(); } GenTreeStmt* getPrevStmt() { if (gtPrev == nullptr) return nullptr; else return gtPrev->AsStmt(); } GenTreeStmt(GenTreePtr expr, IL_OFFSETX offset) : GenTree(GT_STMT, TYP_VOID) , gtStmtExpr(expr) , gtStmtList(nullptr) , gtInlineExpList(nullptr) #if defined(DEBUGGING_SUPPORT) || defined(DEBUG) , gtStmtILoffsx(offset) #endif #ifdef DEBUG , gtStmtLastILoffs(BAD_IL_OFFSET) #endif { // Statements can't have statements as part of their expression tree. assert(expr->gtOper != GT_STMT); gtFlags |= GTF_STMT_TOP_LEVEL; // Set the statement to have the same costs as the top node of the tree. // This is used long before costs have been assigned, so we need to copy // the raw costs. CopyRawCosts(expr); } #if DEBUGGABLE_GENTREE GenTreeStmt() : GenTree(GT_STMT, TYP_VOID) {} #endif }; /* gtLdObj -- 'push object' (GT_LDOBJ). */ struct GenTreeLdObj: public GenTreeUnOp { CORINFO_CLASS_HANDLE gtClass; // object being loaded GenTreePtr * gtFldTreeList; // The list of trees that represents the fields of this struct GenTreeLdObj(var_types type, GenTreePtr op, CORINFO_CLASS_HANDLE cls) : GenTreeUnOp(GT_LDOBJ, type, op), gtClass(cls), gtFldTreeList(NULL) { gtFlags |= GTF_GLOB_REF; // A LdObj is always a global reference. } #if DEBUGGABLE_GENTREE GenTreeLdObj() : GenTreeUnOp() {} #endif }; /* NOTE: Any tree nodes that are larger than 8 bytes (two ints or pointers) must be flagged as 'large' in GenTree::InitNodeSize(). */ /* gtClsVar -- 'static data member' (GT_CLS_VAR) */ struct GenTreeClsVar: public GenTree { CORINFO_FIELD_HANDLE gtClsVarHnd; FieldSeqNode* gtFieldSeq; GenTreeClsVar(var_types type, CORINFO_FIELD_HANDLE clsVarHnd, FieldSeqNode* fldSeq) : GenTree(GT_CLS_VAR, type), gtClsVarHnd(clsVarHnd), gtFieldSeq(fldSeq) { gtFlags |= GTF_GLOB_REF; } #if DEBUGGABLE_GENTREE GenTreeClsVar() : GenTree() {} #endif }; /* gtArgPlace -- 'register argument placeholder' (GT_ARGPLACE) */ struct GenTreeArgPlace: public GenTree { CORINFO_CLASS_HANDLE gtArgPlaceClsHnd; // Needed when we have a TYP_STRUCT argument GenTreeArgPlace(var_types type, CORINFO_CLASS_HANDLE clsHnd) : GenTree(GT_ARGPLACE, type), gtArgPlaceClsHnd(clsHnd) {} #if DEBUGGABLE_GENTREE GenTreeArgPlace() : GenTree() {} #endif }; /* gtLabel -- code label target (GT_LABEL) */ struct GenTreeLabel: public GenTree { BasicBlock* gtLabBB; GenTreeLabel(BasicBlock* bb) : GenTree(GT_LABEL, TYP_VOID), gtLabBB(bb) {} #if DEBUGGABLE_GENTREE GenTreeLabel() : GenTree() {} #endif }; /* gtPhiArg -- phi node rhs argument, var = phi(phiarg, phiarg, phiarg...); GT_PHI_ARG */ struct GenTreePhiArg: public GenTreeLclVarCommon { BasicBlock * gtPredBB; GenTreePhiArg(var_types type, unsigned lclNum, unsigned snum, BasicBlock* block) : GenTreeLclVarCommon(GT_PHI_ARG, type, lclNum) , gtPredBB(block) { SetSsaNum(snum); } #if DEBUGGABLE_GENTREE GenTreePhiArg() : GenTreeLclVarCommon() {} #endif }; /* gtPutArgStk -- Argument passed on stack */ struct GenTreePutArgStk: public GenTreeUnOp { unsigned gtSlotNum; // Slot number of the argument to be passed on stack #if FEATURE_FASTTAILCALL bool putInIncomingArgArea; // Whether this arg needs to be placed in incoming arg area. // By default this is false and will be placed in out-going arg area. // Fast tail calls set this to true. // In future if we need to add more such bool fields consider bit fields. GenTreePutArgStk(genTreeOps oper, var_types type, unsigned slotNum, bool _putInIncomingArgArea = false DEBUG_ARG(GenTreePtr callNode = NULL) DEBUG_ARG(bool largeNode = false)) : GenTreeUnOp(oper, type DEBUG_ARG(largeNode)), gtSlotNum(slotNum), putInIncomingArgArea(_putInIncomingArgArea) { #ifdef DEBUG gtCall = callNode; #endif } GenTreePutArgStk(genTreeOps oper, var_types type, GenTreePtr op1, unsigned slotNum, bool _putInIncomingArgArea = false DEBUG_ARG(GenTreePtr callNode = NULL) DEBUG_ARG(bool largeNode = false)) : GenTreeUnOp(oper, type, op1 DEBUG_ARG(largeNode)), gtSlotNum(slotNum), putInIncomingArgArea(_putInIncomingArgArea) { #ifdef DEBUG gtCall = callNode; #endif } #else // !FEATURE_FASTTAIL_CALL GenTreePutArgStk(genTreeOps oper, var_types type, unsigned slotNum DEBUG_ARG(GenTreePtr callNode = NULL) DEBUG_ARG(bool largeNode = false)) : GenTreeUnOp(oper, type DEBUG_ARG(largeNode)), gtSlotNum(slotNum) { #ifdef DEBUG gtCall = callNode; #endif } GenTreePutArgStk(genTreeOps oper, var_types type, GenTreePtr op1, unsigned slotNum DEBUG_ARG(GenTreePtr callNode = NULL) DEBUG_ARG(bool largeNode = false)) : GenTreeUnOp(oper, type, op1 DEBUG_ARG(largeNode)), gtSlotNum(slotNum) { #ifdef DEBUG gtCall = callNode; #endif } #endif // FEATURE_FASTTAILCALL #ifdef DEBUG GenTreePtr gtCall; // the call node to which this argument belongs #endif #if DEBUGGABLE_GENTREE GenTreePutArgStk() : GenTreeUnOp() {} #endif }; // Deferred inline functions of GenTree -- these need the subtypes above to // be defined already. inline bool GenTree::IsZero() { if ((gtOper == GT_CNS_INT) && (gtIntConCommon.IconValue() == 0)) return true; if ((gtOper == GT_CNS_LNG) && (gtIntConCommon.LngValue() == 0)) return true; if ((gtOper == GT_CNS_DBL) && (gtDblCon.gtDconVal == 0.0)) return true; return false; } inline bool GenTree::IsBoxedValue() { assert(gtOper != GT_BOX || gtBox.BoxOp() != NULL); return (gtOper == GT_BOX) && (gtFlags & GTF_BOX_VALUE); } inline GenTreePtr GenTree::MoveNext() { assert(IsList()); return gtOp.gtOp2; } inline GenTreePtr GenTree::Current() { assert(IsList()); return gtOp.gtOp1; } inline GenTreePtr *GenTree::pCurrent() { assert(IsList()); return &(gtOp.gtOp1); } inline GenTreePtr GenTree::gtGetOp1() { return gtOp.gtOp1; } #ifdef DEBUG /* static */ inline bool GenTree::RequiresNonNullOp2(genTreeOps oper) { switch (oper) { case GT_ADD: case GT_SUB: case GT_MUL: case GT_DIV: case GT_MOD: case GT_UDIV: case GT_UMOD: case GT_OR: case GT_XOR: case GT_AND: case GT_LSH: case GT_RSH: case GT_RSZ: case GT_INDEX: case GT_ASG: case GT_ASG_ADD: case GT_ASG_SUB: case GT_ASG_MUL: case GT_ASG_DIV: case GT_ASG_MOD: case GT_ASG_UDIV: case GT_ASG_UMOD: case GT_ASG_OR: case GT_ASG_XOR: case GT_ASG_AND: case GT_ASG_LSH: case GT_ASG_RSH: case GT_ASG_RSZ: case GT_EQ: case GT_NE: case GT_LT: case GT_LE: case GT_GE: case GT_GT: case GT_COMMA: case GT_QMARK: case GT_COLON: case GT_MKREFANY: case GT_INITBLK: case GT_COPYBLK: return true; default: return false; } } #endif // DEBUG inline GenTreePtr GenTree::gtGetOp2() { /* gtOp.gtOp2 is only valid for GTK_BINOP nodes. */ GenTreePtr op2 = OperIsBinary() ? gtOp.gtOp2 : nullptr; // This documents the genTreeOps for which gtOp.gtOp2 cannot be nullptr. // This helps prefix in its analyis of code which calls gtGetOp2() assert((op2 != nullptr) || !RequiresNonNullOp2(gtOper)); return op2; } inline GenTreePtr GenTree::gtEffectiveVal(bool commaOnly) { switch (gtOper) { case GT_COMMA: return gtOp.gtOp2->gtEffectiveVal(commaOnly); case GT_NOP: if (!commaOnly && gtOp.gtOp1 != NULL) return gtOp.gtOp1->gtEffectiveVal(); break; default: break; } return this; } inline GenTree* GenTree::gtSkipReloadOrCopy() { // There can be only one reload or copy (we can't have a reload/copy of a reload/copy) if (gtOper == GT_RELOAD || gtOper == GT_COPY) { assert(gtGetOp1()->OperGet() != GT_RELOAD && gtGetOp1()->OperGet() != GT_COPY); return gtGetOp1(); } return this; } inline bool GenTree::IsCnsIntOrI() const { return (gtOper == GT_CNS_INT); } inline bool GenTree::IsIntCnsFitsInI32() { #ifdef _TARGET_64BIT_ return IsCnsIntOrI() && ((int)gtIntConCommon.IconValue() == gtIntConCommon.IconValue()); #else // _TARGET_64BIT_ return IsCnsIntOrI(); #endif // _TARGET_64BIT_ } inline bool GenTree::IsCnsFltOrDbl() const { return OperGet() == GT_CNS_DBL; } inline bool GenTree::IsCnsNonZeroFltOrDbl() { if (OperGet() == GT_CNS_DBL) { double constValue = gtDblCon.gtDconVal; return *(__int64*)&constValue != 0; } return false; } inline bool GenTree::IsHelperCall() { return OperGet() == GT_CALL && gtCall.gtCallType == CT_HELPER; } inline var_types GenTree::CastFromType() { return this->gtCast.CastOp()->TypeGet(); } inline var_types& GenTree::CastToType() { return this->gtCast.gtCastType; } /*****************************************************************************/ #ifndef _WIN64 #include #endif /*****************************************************************************/ #if SMALL_TREE_NODES // In debug, on some platforms (e.g., when LATE_DISASM is defined), GenTreeIntCon is bigger than GenTreeLclFld. const size_t TREE_NODE_SZ_SMALL = max(sizeof(GenTreeIntCon), sizeof(GenTreeLclFld)); #endif // SMALL_TREE_NODES const size_t TREE_NODE_SZ_LARGE = sizeof(GenTreeCall); /***************************************************************************** * Types returned by GenTree::lvaLclVarRefs() */ enum varRefKinds { VR_INVARIANT = 0x00, // an invariant value VR_NONE = 0x00, VR_IND_REF = 0x01, // an object reference VR_IND_SCL = 0x02, // a non-object reference VR_GLB_VAR = 0x04, // a global (clsVar) }; // Add a temp define to avoid merge conflict. #define VR_IND_PTR VR_IND_REF /*****************************************************************************/ #endif // !GENTREE_H /*****************************************************************************/