diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index d921aa458f6e10..e14f3439ee643e 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -9365,9 +9365,9 @@ void cTreeFlags(Compiler* comp, GenTree* tree) { chars += printf("[IND_TGT_HEAP]"); } - if (tree->gtFlags & GTF_IND_TLS_REF) + if (tree->gtFlags & GTF_IND_REQ_ADDR_IN_REG) { - chars += printf("[IND_TLS_REF]"); + chars += printf("[IND_REQ_ADDR_IN_REG]"); } if (tree->gtFlags & GTF_IND_ASG_LHS) { diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 1204ea9af4d6ea..b99770734d57dd 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -2643,6 +2643,11 @@ class Compiler GenTreeField* gtNewFieldRef(var_types type, CORINFO_FIELD_HANDLE fldHnd, GenTree* obj = nullptr, DWORD offset = 0); + GenTreeField* gtNewFieldAddrNode(var_types type, + CORINFO_FIELD_HANDLE fldHnd, + GenTree* obj = nullptr, + DWORD offset = 0); + GenTreeIndexAddr* gtNewIndexAddr(GenTree* arrayOp, GenTree* indexOp, var_types elemType, @@ -2665,7 +2670,7 @@ class Compiler GenTreeMDArr* gtNewMDArrLowerBound(GenTree* arrayOp, unsigned dim, unsigned rank, BasicBlock* block); - GenTreeIndir* gtNewIndir(var_types typ, GenTree* addr); + GenTreeIndir* gtNewIndir(var_types typ, GenTree* addr, GenTreeFlags indirFlags = GTF_EMPTY); GenTree* gtNewNullCheck(GenTree* addr, BasicBlock* basicBlock); @@ -5725,6 +5730,9 @@ class Compiler private: GenTree* fgMorphField(GenTree* tree, MorphAddrContext* mac); + GenTree* fgMorphExpandInstanceField(GenTree* tree, MorphAddrContext* mac); + GenTree* fgMorphExpandTlsFieldAddr(GenTree* tree); + GenTree* fgMorphExpandStaticField(GenTree* tree); bool fgCanFastTailCall(GenTreeCall* call, const char** failReason); #if FEATURE_FASTTAILCALL bool fgCallHasMustCopyByrefParameter(GenTreeCall* callee); @@ -10835,6 +10843,7 @@ class GenTreeVisitor case GT_RETURNTRAP: case GT_NOP: case GT_FIELD: + case GT_FIELD_ADDR: case GT_RETURN: case GT_RETFILT: case GT_RUNTIMELOOKUP: diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index 274514bbf9831f..b89ebaf08715e0 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -1069,61 +1069,6 @@ inline GenTree* Compiler::gtNewRuntimeLookup(CORINFO_GENERIC_HANDLE hnd, CorInfo return node; } -//------------------------------------------------------------------------ -// gtNewFieldRef: a helper for creating GT_FIELD nodes. -// -// Normalizes struct types (for SIMD vectors). Sets GTF_GLOB_REF for fields -// that may be pointing into globally visible memory. -// -// Arguments: -// type - type for the field node -// fldHnd - the field handle -// obj - the instance, an address -// offset - the field offset -// -// Return Value: -// The created node. -// -inline GenTreeField* Compiler::gtNewFieldRef(var_types type, CORINFO_FIELD_HANDLE fldHnd, GenTree* obj, DWORD offset) -{ - // GT_FIELD nodes are transformed into GT_IND nodes. - assert(GenTree::s_gtNodeSizes[GT_IND] <= GenTree::s_gtNodeSizes[GT_FIELD]); - - if (type == TYP_STRUCT) - { - CORINFO_CLASS_HANDLE structHnd; - eeGetFieldType(fldHnd, &structHnd); - type = impNormStructType(structHnd); - } - - GenTreeField* fieldNode = new (this, GT_FIELD) GenTreeField(type, obj, fldHnd, offset); - - // If "obj" is the address of a local, note that a field of that struct local has been accessed. - if ((obj != nullptr) && obj->OperIs(GT_ADDR) && varTypeIsStruct(obj->AsUnOp()->gtOp1) && - obj->AsUnOp()->gtOp1->OperIs(GT_LCL_VAR)) - { - LclVarDsc* varDsc = lvaGetDesc(obj->AsUnOp()->gtOp1->AsLclVarCommon()); - - varDsc->lvFieldAccessed = 1; - - if (lvaIsImplicitByRefLocal(lvaGetLclNum(varDsc))) - { - // These structs are passed by reference and can easily become global references if those - // references are exposed. We clear out address-exposure information for these parameters - // when they are converted into references in fgRetypeImplicitByRefArgs() so we do not have - // the necessary information in morph to know if these indirections are actually global - // references, so we have to be conservative here. - fieldNode->gtFlags |= GTF_GLOB_REF; - } - } - else - { - fieldNode->gtFlags |= GTF_GLOB_REF; - } - - return fieldNode; -} - inline GenTreeIndexAddr* Compiler::gtNewIndexAddr(GenTree* arrayOp, GenTree* indexOp, var_types elemType, @@ -1267,10 +1212,14 @@ inline GenTreeMDArr* Compiler::gtNewMDArrLowerBound(GenTree* arrayOp, unsigned d // Return Value: // New GT_IND node -inline GenTreeIndir* Compiler::gtNewIndir(var_types typ, GenTree* addr) +inline GenTreeIndir* Compiler::gtNewIndir(var_types typ, GenTree* addr, GenTreeFlags indirFlags) { + assert((indirFlags & ~GTF_IND_FLAGS) == GTF_EMPTY); + GenTree* indir = gtNewOperNode(GT_IND, typ, addr); + indir->gtFlags |= indirFlags; indir->SetIndirExceptionFlags(this); + return indir->AsIndir(); } @@ -4130,6 +4079,7 @@ void GenTree::VisitOperands(TVisitor visitor) // Unary operators with an optional operand case GT_NOP: case GT_FIELD: + case GT_FIELD_ADDR: case GT_RETURN: case GT_RETFILT: if (this->AsUnOp()->gtOp1 == nullptr) diff --git a/src/coreclr/jit/flowgraph.cpp b/src/coreclr/jit/flowgraph.cpp index 605b06ea86320c..4fc2b9b0d9d9e5 100644 --- a/src/coreclr/jit/flowgraph.cpp +++ b/src/coreclr/jit/flowgraph.cpp @@ -936,102 +936,89 @@ GenTreeCall* Compiler::fgGetSharedCCtor(CORINFO_CLASS_HANDLE cls) // bool Compiler::fgAddrCouldBeNull(GenTree* addr) { - addr = addr->gtEffectiveVal(); - if (addr->IsIconHandle()) - { - return false; - } - else if (addr->OperIs(GT_CNS_STR, GT_CLS_VAR_ADDR)) - { - return false; - } - else if (addr->OperIs(GT_INDEX_ADDR)) - { - return !addr->AsIndexAddr()->IsNotNull(); - } - else if (addr->OperIs(GT_ARR_ADDR)) - { - return (addr->gtFlags & GTF_ARR_ADDR_NONNULL) == 0; - } - else if (addr->OperIs(GT_IND)) - { - return (addr->gtFlags & GTF_IND_NONNULL) == 0; - } - else if (addr->gtOper == GT_LCL_VAR) - { - unsigned varNum = addr->AsLclVarCommon()->GetLclNum(); - - if (lvaIsImplicitByRefLocal(varNum)) - { + switch (addr->OperGet()) + { + case GT_CNS_INT: + return !addr->IsIconHandle(); + + case GT_CNS_STR: + case GT_ADDR: + case GT_FIELD_ADDR: + case GT_CLS_VAR_ADDR: + // A GT_ADDR node, by itself, never requires null checking. The expression whose address is being + // taken is either a local or static variable, whose address is necessarily non-null, or else it is + // a field dereference, which will do its own bounds checking if necessary. return false; - } - } - else if (addr->gtOper == GT_ADDR) - { - if (addr->AsOp()->gtOp1->gtOper == GT_CNS_INT) - { - GenTree* cns1Tree = addr->AsOp()->gtOp1; - if (!cns1Tree->IsIconHandle()) - { - // Indirection of some random constant... - // It is safest just to return true - return true; - } - } - return false; // we can't have a null address - } - else if (addr->gtOper == GT_ADD) - { - if (addr->AsOp()->gtOp1->gtOper == GT_CNS_INT) - { - GenTree* cns1Tree = addr->AsOp()->gtOp1; - if (!cns1Tree->IsIconHandle()) + case GT_IND: + return (addr->gtFlags & GTF_IND_NONNULL) == 0; + + case GT_INDEX_ADDR: + return !addr->AsIndexAddr()->IsNotNull(); + + case GT_ARR_ADDR: + return (addr->gtFlags & GTF_ARR_ADDR_NONNULL) == 0; + + case GT_LCL_VAR: + return !lvaIsImplicitByRefLocal(addr->AsLclVar()->GetLclNum()); + + case GT_COMMA: + return fgAddrCouldBeNull(addr->AsOp()->gtOp2); + + case GT_ADD: + if (addr->AsOp()->gtOp1->gtOper == GT_CNS_INT) { - if (!fgIsBigOffset(cns1Tree->AsIntCon()->gtIconVal)) + GenTree* cns1Tree = addr->AsOp()->gtOp1; + if (!cns1Tree->IsIconHandle()) + { + if (!fgIsBigOffset(cns1Tree->AsIntCon()->gtIconVal)) + { + // Op1 was an ordinary small constant + return fgAddrCouldBeNull(addr->AsOp()->gtOp2); + } + } + else // Op1 was a handle represented as a constant { - // Op1 was an ordinary small constant - return fgAddrCouldBeNull(addr->AsOp()->gtOp2); + // Is Op2 also a constant? + if (addr->AsOp()->gtOp2->gtOper == GT_CNS_INT) + { + GenTree* cns2Tree = addr->AsOp()->gtOp2; + // Is this an addition of a handle and constant + if (!cns2Tree->IsIconHandle()) + { + if (!fgIsBigOffset(cns2Tree->AsIntCon()->gtIconVal)) + { + // Op2 was an ordinary small constant + return false; // we can't have a null address + } + } + } } } - else // Op1 was a handle represented as a constant + else { - // Is Op2 also a constant? + // Op1 is not a constant. What about Op2? if (addr->AsOp()->gtOp2->gtOper == GT_CNS_INT) { GenTree* cns2Tree = addr->AsOp()->gtOp2; - // Is this an addition of a handle and constant + // Is this an addition of a small constant if (!cns2Tree->IsIconHandle()) { if (!fgIsBigOffset(cns2Tree->AsIntCon()->gtIconVal)) { // Op2 was an ordinary small constant - return false; // we can't have a null address + return fgAddrCouldBeNull(addr->AsOp()->gtOp1); } } } } - } - else - { - // Op1 is not a constant - // What about Op2? - if (addr->AsOp()->gtOp2->gtOper == GT_CNS_INT) - { - GenTree* cns2Tree = addr->AsOp()->gtOp2; - // Is this an addition of a small constant - if (!cns2Tree->IsIconHandle()) - { - if (!fgIsBigOffset(cns2Tree->AsIntCon()->gtIconVal)) - { - // Op2 was an ordinary small constant - return fgAddrCouldBeNull(addr->AsOp()->gtOp1); - } - } - } - } + break; + + default: + break; } - return true; // default result: addr could be null + + return true; // default result: addr could be null. } //------------------------------------------------------------------------------ diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index ff4b09747f835e..5cb987a33bb7c6 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -265,6 +265,7 @@ void GenTree::InitNodeSize() GenTree::s_gtNodeSizes[GT_ARR_OFFSET] = TREE_NODE_SZ_LARGE; GenTree::s_gtNodeSizes[GT_RET_EXPR] = TREE_NODE_SZ_LARGE; GenTree::s_gtNodeSizes[GT_FIELD] = TREE_NODE_SZ_LARGE; + GenTree::s_gtNodeSizes[GT_FIELD_ADDR] = TREE_NODE_SZ_LARGE; GenTree::s_gtNodeSizes[GT_CMPXCHG] = TREE_NODE_SZ_LARGE; GenTree::s_gtNodeSizes[GT_QMARK] = TREE_NODE_SZ_LARGE; GenTree::s_gtNodeSizes[GT_STORE_DYN_BLK] = TREE_NODE_SZ_LARGE; @@ -2339,9 +2340,6 @@ bool GenTree::Compare(GenTree* op1, GenTree* op2, bool swapOK) genTreeOps oper; unsigned kind; -// printf("tree1:\n"); gtDispTree(op1); -// printf("tree2:\n"); gtDispTree(op2); - AGAIN: if (op1 == nullptr) @@ -2520,6 +2518,7 @@ bool GenTree::Compare(GenTree* op1, GenTree* op2, bool swapOK) break; case GT_FIELD: + case GT_FIELD_ADDR: if (op1->AsField()->gtFldHnd != op2->AsField()->gtFldHnd) { return false; @@ -3005,6 +3004,7 @@ unsigned Compiler::gtHashValue(GenTree* tree) break; case GT_FIELD: + case GT_FIELD_ADDR: hash = genTreeHashAdd(hash, tree->AsField()->gtFldHnd); break; @@ -6572,19 +6572,13 @@ ExceptionSetFlags GenTree::OperExceptions(Compiler* comp) return ExceptionSetFlags::IndexOutOfRangeException; case GT_FIELD: - { - GenTree* fldObj = this->AsField()->GetFldObj(); - - if (fldObj != nullptr) + case GT_FIELD_ADDR: + if (AsField()->IsInstance() && comp->fgAddrCouldBeNull(AsField()->GetFldObj())) { - if (comp->fgAddrCouldBeNull(fldObj)) - { - return ExceptionSetFlags::NullReferenceException; - } + return ExceptionSetFlags::NullReferenceException; } return ExceptionSetFlags::None; - } case GT_BOUNDS_CHECK: case GT_INDEX_ADDR: @@ -7511,6 +7505,86 @@ GenTreeRetExpr* Compiler::gtNewInlineCandidateReturnExpr(GenTreeCall* inlineCand return node; } +//------------------------------------------------------------------------ +// gtNewFieldRef: Create a new GT_FIELD node. +// +// Normalizes struct types (for SIMD vectors). Sets GTF_GLOB_REF for fields +// that may be pointing into globally visible memory. +// +// Arguments: +// type - type for the field node +// fldHnd - the field handle +// obj - the instance, an address +// offset - the field offset +// +// Return Value: +// The created node. +// +GenTreeField* Compiler::gtNewFieldRef(var_types type, CORINFO_FIELD_HANDLE fldHnd, GenTree* obj, DWORD offset) +{ + // GT_FIELD nodes are transformed into GT_IND nodes. + assert(GenTree::s_gtNodeSizes[GT_IND] <= GenTree::s_gtNodeSizes[GT_FIELD]); + + if (type == TYP_STRUCT) + { + CORINFO_CLASS_HANDLE structHnd; + eeGetFieldType(fldHnd, &structHnd); + type = impNormStructType(structHnd); + } + + GenTreeField* fieldNode = new (this, GT_FIELD) GenTreeField(GT_FIELD, type, obj, fldHnd, offset); + + // If "obj" is the address of a local, note that a field of that struct local has been accessed. + if ((obj != nullptr) && obj->OperIs(GT_ADDR) && varTypeIsStruct(obj->AsUnOp()->gtOp1) && + obj->AsUnOp()->gtOp1->OperIs(GT_LCL_VAR)) + { + LclVarDsc* varDsc = lvaGetDesc(obj->AsUnOp()->gtOp1->AsLclVarCommon()); + + varDsc->lvFieldAccessed = 1; + + if (lvaIsImplicitByRefLocal(lvaGetLclNum(varDsc))) + { + // These structs are passed by reference and can easily become global references if those + // references are exposed. We clear out address-exposure information for these parameters + // when they are converted into references in fgRetypeImplicitByRefArgs() so we do not have + // the necessary information in morph to know if these indirections are actually global + // references, so we have to be conservative here. + fieldNode->gtFlags |= GTF_GLOB_REF; + } + } + else + { + fieldNode->gtFlags |= GTF_GLOB_REF; + } + + return fieldNode; +} + +//------------------------------------------------------------------------ +// gtNewFieldRef: Create a new GT_FIELD_ADDR node. +// +// Arguments: +// type - type for the address node +// fldHnd - the field handle +// obj - the instance, an address +// offset - the field offset +// +// Return Value: +// The created node. +// +GenTreeField* Compiler::gtNewFieldAddrNode(var_types type, CORINFO_FIELD_HANDLE fldHnd, GenTree* obj, DWORD offset) +{ + assert(varTypeIsI(genActualType(type))); + + GenTreeField* fieldNode = new (this, GT_FIELD_ADDR) GenTreeField(GT_FIELD_ADDR, type, obj, fldHnd, offset); + + // TODO-ADDR: add GTF_EXCEPT handling here and delete it from callers. + // TODO-ADDR: delete this zero-diff quirk. + fieldNode->gtFlags |= GTF_GLOB_REF; + + return fieldNode; +} + /***************************************************************************** * * Create a node that will assign 'src' to 'dst'. @@ -8657,8 +8731,10 @@ GenTree* Compiler::gtCloneExpr( break; case GT_FIELD: - copy = new (this, GT_FIELD) GenTreeField(tree->TypeGet(), tree->AsField()->GetFldObj(), - tree->AsField()->gtFldHnd, tree->AsField()->gtFldOffset); + case GT_FIELD_ADDR: + copy = new (this, tree->OperGet()) + GenTreeField(tree->OperGet(), tree->TypeGet(), tree->AsField()->GetFldObj(), + tree->AsField()->gtFldHnd, tree->AsField()->gtFldOffset); copy->AsField()->gtFldMayOverlap = tree->AsField()->gtFldMayOverlap; #ifdef FEATURE_READYTORUN copy->AsField()->gtFieldLookup = tree->AsField()->gtFieldLookup; @@ -9459,6 +9535,7 @@ GenTreeUseEdgeIterator::GenTreeUseEdgeIterator(GenTree* node) // Unary operators with an optional operand case GT_NOP: case GT_FIELD: + case GT_FIELD_ADDR: case GT_RETURN: case GT_RETFILT: if (m_node->AsUnOp()->gtOp1 == nullptr) @@ -11893,7 +11970,7 @@ void Compiler::gtDispTree(GenTree* tree, #endif // FEATURE_ARG_SPLIT #endif // FEATURE_PUT_STRUCT_ARG_STK - if (tree->OperIs(GT_FIELD)) + if (tree->OperIs(GT_FIELD, GT_FIELD_ADDR)) { printf(" %s", eeGetFieldName(tree->AsField()->gtFldHnd), 0); } diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index aa9a9c8d04516f..c8c16028f93bf9 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -489,33 +489,29 @@ enum GenTreeFlags : unsigned int GTF_MEMORYBARRIER_LOAD = 0x40000000, // GT_MEMORYBARRIER -- Load barrier + GTF_FLD_TLS = 0x80000000, // GT_FIELD_ADDR -- field address is a Windows x86 TLS reference GTF_FLD_VOLATILE = 0x40000000, // GT_FIELD -- same as GTF_IND_VOLATILE - GTF_FLD_INITCLASS = 0x20000000, // GT_FIELD -- field access requires preceding class/static init helper + GTF_FLD_INITCLASS = 0x20000000, // GT_FIELD/GT_FIELD_ADDR -- field access requires preceding class/static init helper GTF_FLD_TGT_HEAP = 0x10000000, // GT_FIELD -- same as GTF_IND_TGT_HEAP GTF_INX_RNGCHK = 0x80000000, // GT_INDEX_ADDR -- this array address should be range-checked GTF_INX_ADDR_NONNULL = 0x40000000, // GT_INDEX_ADDR -- this array address is not null - GTF_IND_TGT_NOT_HEAP = 0x80000000, // GT_IND -- the target is not on the heap - GTF_IND_VOLATILE = 0x40000000, // GT_IND -- the load or store must use volatile semantics (this is a nop on X86) + GTF_IND_TGT_NOT_HEAP = 0x80000000, // GT_IND -- the target is not on the heap + GTF_IND_VOLATILE = 0x40000000, // GT_IND -- the load or store must use volatile semantics (this is a nop on X86) GTF_IND_NONFAULTING = 0x20000000, // Operations for which OperIsIndir() is true -- An indir that cannot fault. - // Same as GTF_ARRLEN_NONFAULTING. - GTF_IND_TGT_HEAP = 0x10000000, // GT_IND -- the target is on the heap - GTF_IND_TLS_REF = 0x08000000, // GT_IND -- the target is accessed via TLS - 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. - GTF_IND_REQ_ADDR_IN_REG = GTF_IND_ASG_LHS, // GT_IND -- requires its addr operand to be evaluated - // into a register. This flag is useful in cases where it - // is required to generate register indirect addressing mode. - // One such case is virtual stub calls on xarch. This is only - // valid in the backend, where GTF_IND_ASG_LHS is not necessary - // (all such indirections will be lowered to GT_STOREIND). - GTF_IND_UNALIGNED = 0x02000000, // GT_IND -- the load or store is unaligned (we assume worst case - // alignment of 1 byte) - GTF_IND_INVARIANT = 0x01000000, // GT_IND -- the target is invariant (a prejit indirection) - GTF_IND_NONNULL = 0x00400000, // GT_IND -- the indirection never returns null (zero) - - GTF_IND_FLAGS = GTF_IND_VOLATILE | GTF_IND_NONFAULTING | GTF_IND_TLS_REF | GTF_IND_UNALIGNED | GTF_IND_INVARIANT | + GTF_IND_TGT_HEAP = 0x10000000, // GT_IND -- the target is on the heap + GTF_IND_REQ_ADDR_IN_REG = 0x08000000, // GT_IND -- requires its addr operand to be evaluated into a register. + // This flag is useful in cases where it is required to generate register + // indirect addressing mode. One such case is virtual stub calls on xarch. + 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. + GTF_IND_UNALIGNED = 0x02000000, // GT_IND -- the load or store is unaligned (we assume worst case + // alignment of 1 byte) + GTF_IND_INVARIANT = 0x01000000, // GT_IND -- the target is invariant (a prejit indirection) + GTF_IND_NONNULL = 0x00400000, // GT_IND -- the indirection never returns null (zero) + + GTF_IND_FLAGS = GTF_IND_VOLATILE | GTF_IND_NONFAULTING | GTF_IND_UNALIGNED | GTF_IND_INVARIANT | GTF_IND_NONNULL | GTF_IND_TGT_NOT_HEAP | GTF_IND_TGT_HEAP, GTF_ADDRMODE_NO_CSE = 0x80000000, // GT_ADD/GT_MUL/GT_LSH -- Do not CSE this node only, forms complex @@ -1743,6 +1739,7 @@ struct GenTree case GT_RETFILT: case GT_NOP: case GT_FIELD: + case GT_FIELD_ADDR: return true; case GT_RETURN: return gtType == TYP_VOID; @@ -4030,8 +4027,8 @@ struct GenTreeField : public GenTreeUnOp CORINFO_CONST_LOOKUP gtFieldLookup; #endif - GenTreeField(var_types type, GenTree* obj, CORINFO_FIELD_HANDLE fldHnd, DWORD offs) - : GenTreeUnOp(GT_FIELD, type, obj), gtFldHnd(fldHnd), gtFldOffset(offs), gtFldMayOverlap(false) + GenTreeField(genTreeOps oper, var_types type, GenTree* obj, CORINFO_FIELD_HANDLE fldHnd, DWORD offs) + : GenTreeUnOp(oper, type, obj), gtFldHnd(fldHnd), gtFldOffset(offs), gtFldMayOverlap(false) { #ifdef FEATURE_READYTORUN gtFieldLookup.addr = nullptr; @@ -4062,6 +4059,17 @@ struct GenTreeField : public GenTreeUnOp return GetFldObj() != nullptr; } + bool IsStatic() const + { + return !IsInstance(); + } + + bool IsTlsStatic() const + { + assert(((gtFlags & GTF_FLD_TLS) == 0) || IsStatic()); + return (gtFlags & GTF_FLD_TLS) != 0; + } + bool IsOffsetKnown() const { #ifdef FEATURE_READYTORUN @@ -7021,8 +7029,7 @@ struct GenTreeIndir : public GenTreeOp void SetAddr(GenTree* addr) { - assert(addr != nullptr); - assert(addr->TypeIs(TYP_I_IMPL, TYP_BYREF)); + assert(varTypeIsI(addr)); gtOp1 = addr; } diff --git a/src/coreclr/jit/gtlist.h b/src/coreclr/jit/gtlist.h index 65a77bf6a3e0aa..cde991961ab81c 100644 --- a/src/coreclr/jit/gtlist.h +++ b/src/coreclr/jit/gtlist.h @@ -93,7 +93,8 @@ GTNODE(NULLCHECK , GenTreeIndir ,0,GTK_UNOP|GTK_NOVALUE) GTNODE(ARR_LENGTH , GenTreeArrLen ,0,GTK_UNOP|GTK_EXOP) // single-dimension (SZ) array length GTNODE(MDARR_LENGTH , GenTreeMDArr ,0,GTK_UNOP|GTK_EXOP) // multi-dimension (MD) array length of a specific dimension GTNODE(MDARR_LOWER_BOUND, GenTreeMDArr ,0,GTK_UNOP|GTK_EXOP) // multi-dimension (MD) array lower bound of a specific dimension -GTNODE(FIELD , GenTreeField ,0,GTK_UNOP|GTK_EXOP|DBK_NOTLIR) // Member-field +GTNODE(FIELD , GenTreeField ,0,GTK_UNOP|GTK_EXOP|DBK_NOTLIR) // Field load +GTNODE(FIELD_ADDR , GenTreeField ,0,GTK_UNOP|GTK_EXOP|DBK_NOTLIR) // Field address GTNODE(ALLOCOBJ , GenTreeAllocObj ,0,GTK_UNOP|GTK_EXOP|DBK_NOTLIR) // object allocator GTNODE(INIT_VAL , GenTreeOp ,0,GTK_UNOP) // Initialization value for an initBlk diff --git a/src/coreclr/jit/gtstructs.h b/src/coreclr/jit/gtstructs.h index 7d50adbca39f97..ae62bde014a5a0 100644 --- a/src/coreclr/jit/gtstructs.h +++ b/src/coreclr/jit/gtstructs.h @@ -66,7 +66,7 @@ GTSTRUCT_3(LclVar , GT_LCL_VAR, GT_LCL_VAR_ADDR, GT_STORE_LCL_VAR) GTSTRUCT_3(LclFld , GT_LCL_FLD, GT_STORE_LCL_FLD, GT_LCL_FLD_ADDR) GTSTRUCT_1(Cast , GT_CAST) GTSTRUCT_1(Box , GT_BOX) -GTSTRUCT_1(Field , GT_FIELD) +GTSTRUCT_2(Field , GT_FIELD, GT_FIELD_ADDR) GTSTRUCT_1(Call , GT_CALL) GTSTRUCT_1(FieldList , GT_FIELD_LIST) GTSTRUCT_1(Colon , GT_COLON) diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index efc2da6ec1c767..6f78ea8ff0ca8d 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -4528,7 +4528,6 @@ GenTree* Compiler::impImportStaticFieldAccess(CORINFO_RESOLVED_TOKEN* pResolvedT } else // We need the value of a static field { - // In future, it may be better to just create the right tree here instead of folding it later. op1 = gtNewFieldRef(lclTyp, pResolvedToken->hField); if (pFieldInfo->fieldFlags & CORINFO_FLG_FIELD_INITCLASS) @@ -4561,9 +4560,7 @@ GenTree* Compiler::impImportStaticFieldAccess(CORINFO_RESOLVED_TOKEN* pResolvedT if (isBoxedStatic) { - op1 = gtNewOperNode(GT_IND, TYP_REF, op1); - op1->gtFlags |= (GTF_IND_INVARIANT | GTF_IND_NONFAULTING | GTF_IND_NONNULL); - + op1 = gtNewIndir(TYP_REF, op1, GTF_IND_INVARIANT | GTF_IND_NONFAULTING | GTF_IND_NONNULL); op1 = gtNewOperNode(GT_ADD, TYP_BYREF, op1, gtNewIconNode(TARGET_POINTER_SIZE, outerFldSeq)); } @@ -9411,12 +9408,10 @@ void Compiler::impImportBlockCode(BasicBlock* block) int aflags = isLoadAddress ? CORINFO_ACCESS_ADDRESS : CORINFO_ACCESS_GET; GenTree* obj = nullptr; - typeInfo* tiObj = nullptr; CORINFO_CLASS_HANDLE objType = nullptr; // used for fields - if (opcode == CEE_LDFLD || opcode == CEE_LDFLDA) + if ((opcode == CEE_LDFLD) || (opcode == CEE_LDFLDA)) { - tiObj = &impStackTop().seTypeInfo; StackEntry se = impPopStack(); objType = se.seTypeInfo.GetClassHandle(); obj = se.val; @@ -9538,8 +9533,19 @@ void Compiler::impImportBlockCode(BasicBlock* block) obj = impGetStructAddr(obj, objType, CHECK_SPILL_ALL, true); } - /* Create the data member node */ - op1 = gtNewFieldRef(lclTyp, resolvedToken.hField, obj, fieldInfo.offset); + DWORD typeFlags = info.compCompHnd->getClassAttribs(resolvedToken.hClass); + + // TODO-ADDR: use FIELD_ADDR for all fields, not just those of classes. + // + if (isLoadAddress && ((typeFlags & CORINFO_FLG_VALUECLASS) == 0)) + { + op1 = gtNewFieldAddrNode(varTypeIsGC(obj) ? TYP_BYREF : TYP_I_IMPL, resolvedToken.hField, + obj, fieldInfo.offset); + } + else + { + op1 = gtNewFieldRef(lclTyp, resolvedToken.hField, obj, fieldInfo.offset); + } #ifdef FEATURE_READYTORUN if (fieldInfo.fieldAccessor == CORINFO_FIELD_INSTANCE_WITH_BASE) @@ -9553,26 +9559,22 @@ void Compiler::impImportBlockCode(BasicBlock* block) op1->gtFlags |= GTF_EXCEPT; } - DWORD typeFlags = info.compCompHnd->getClassAttribs(resolvedToken.hClass); if (StructHasOverlappingFields(typeFlags)) { op1->AsField()->gtFldMayOverlap = true; } - // wrap it in a address of operator if necessary - if (isLoadAddress) + // Wrap it in a address of operator if necessary. + if (isLoadAddress && op1->OperIs(GT_FIELD)) { - op1 = gtNewOperNode(GT_ADDR, - (var_types)(varTypeIsGC(obj->TypeGet()) ? TYP_BYREF : TYP_I_IMPL), op1); + op1 = gtNewOperNode(GT_ADDR, varTypeIsGC(obj) ? TYP_BYREF : TYP_I_IMPL, op1); } - else + + if (!isLoadAddress && compIsForInlining() && + impInlineIsGuaranteedThisDerefBeforeAnySideEffects(nullptr, nullptr, obj, + impInlineInfo->inlArgInfo)) { - if (compIsForInlining() && - impInlineIsGuaranteedThisDerefBeforeAnySideEffects(nullptr, nullptr, obj, - impInlineInfo->inlArgInfo)) - { - impInlineInfo->thisDereferencedFirst = true; - } + impInlineInfo->thisDereferencedFirst = true; } } break; @@ -9580,22 +9582,27 @@ void Compiler::impImportBlockCode(BasicBlock* block) case CORINFO_FIELD_STATIC_TLS: #ifdef TARGET_X86 // Legacy TLS access is implemented as intrinsic on x86 only + op1 = gtNewFieldAddrNode(TYP_I_IMPL, resolvedToken.hField, nullptr, fieldInfo.offset); + op1->gtFlags |= GTF_FLD_TLS; // fgMorphExpandTlsField will handle the transformation. - /* Create the data member node */ - op1 = gtNewFieldRef(lclTyp, resolvedToken.hField, NULL, fieldInfo.offset); - op1->gtFlags |= GTF_IND_TLS_REF; // fgMorphField will handle the transformation - - if (isLoadAddress) + if (!isLoadAddress) { - op1 = gtNewOperNode(GT_ADDR, (var_types)TYP_I_IMPL, op1); + if (varTypeIsStruct(lclTyp)) + { + op1 = gtNewObjNode(fieldInfo.structType, op1); + op1->gtFlags |= GTF_IND_NONFAULTING; + } + else + { + op1 = gtNewIndir(lclTyp, op1, GTF_IND_NONFAULTING); + op1->gtFlags |= GTF_GLOB_REF; + } } break; #else fieldInfo.fieldAccessor = CORINFO_FIELD_STATIC_ADDR_HELPER; - FALLTHROUGH; #endif - case CORINFO_FIELD_STATIC_ADDR_HELPER: case CORINFO_FIELD_INSTANCE_HELPER: case CORINFO_FIELD_INSTANCE_ADDR_HELPER: @@ -9673,8 +9680,7 @@ void Compiler::impImportBlockCode(BasicBlock* block) if (!usesHelper) { - assert((op1->OperGet() == GT_FIELD) || (op1->OperGet() == GT_IND) || - (op1->OperGet() == GT_OBJ)); + assert(op1->OperIs(GT_FIELD, GT_IND, GT_OBJ)); op1->gtFlags |= GTF_IND_VOLATILE; } } @@ -9683,15 +9689,13 @@ void Compiler::impImportBlockCode(BasicBlock* block) { if (!usesHelper) { - assert((op1->OperGet() == GT_FIELD) || (op1->OperGet() == GT_IND) || - (op1->OperGet() == GT_OBJ)); + assert(op1->OperIs(GT_FIELD, GT_IND, GT_OBJ)); op1->gtFlags |= GTF_IND_UNALIGNED; } } } - /* Check if the class needs explicit initialization */ - + // Check if the class needs explicit initialization. if (fieldInfo.fieldFlags & CORINFO_FLG_FIELD_INITCLASS) { GenTree* helperNode = impInitClass(&resolvedToken); @@ -9726,21 +9730,17 @@ void Compiler::impImportBlockCode(BasicBlock* block) JITDUMP(" %08X", resolvedToken.token); - int aflags = CORINFO_ACCESS_SET; - GenTree* obj = nullptr; - typeInfo* tiObj = nullptr; - typeInfo tiVal; + int aflags = CORINFO_ACCESS_SET; + GenTree* obj = nullptr; - /* Pull the value from the stack */ + // Pull the value from the stack. StackEntry se = impPopStack(); op2 = se.val; - tiVal = se.seTypeInfo; - clsHnd = tiVal.GetClassHandle(); + clsHnd = se.seTypeInfo.GetClassHandle(); if (opcode == CEE_STFLD) { - tiObj = &impStackTop().seTypeInfo; - obj = impPopStack().val; + obj = impPopStack().val; if (impIsThis(obj)) { @@ -9848,19 +9848,25 @@ void Compiler::impImportBlockCode(BasicBlock* block) case CORINFO_FIELD_STATIC_TLS: #ifdef TARGET_X86 - // Legacy TLS access is implemented as intrinsic on x86 only - - /* Create the data member node */ - op1 = gtNewFieldRef(lclTyp, resolvedToken.hField, NULL, fieldInfo.offset); - op1->gtFlags |= GTF_IND_TLS_REF; // fgMorphField will handle the transformation + // Legacy TLS access is implemented as intrinsic on x86 only. + op1 = gtNewFieldAddrNode(TYP_I_IMPL, resolvedToken.hField, nullptr, fieldInfo.offset); + op1->gtFlags |= GTF_FLD_TLS; // fgMorphExpandTlsField will handle the transformation. + if (varTypeIsStruct(lclTyp)) + { + op1 = gtNewObjNode(fieldInfo.structType, op1); + op1->gtFlags |= GTF_IND_NONFAULTING; + } + else + { + op1 = gtNewIndir(lclTyp, op1, GTF_IND_NONFAULTING); + op1->gtFlags |= GTF_GLOB_REF; + } break; #else fieldInfo.fieldAccessor = CORINFO_FIELD_STATIC_ADDR_HELPER; - FALLTHROUGH; #endif - case CORINFO_FIELD_STATIC_ADDR_HELPER: case CORINFO_FIELD_INSTANCE_HELPER: case CORINFO_FIELD_INSTANCE_ADDR_HELPER: @@ -9958,14 +9964,8 @@ void Compiler::impImportBlockCode(BasicBlock* block) } #endif - // We can generate an assignment to a TYP_FLOAT from a TYP_DOUBLE - // We insert a cast to the dest 'op1' type - // - if ((op1->TypeGet() != op2->TypeGet()) && varTypeIsFloating(op1->gtType) && - varTypeIsFloating(op2->gtType)) - { - op2 = gtNewCastNode(op1->TypeGet(), op2, false, op1->TypeGet()); - } + // Insert an implicit FLOAT<->DOUBLE cast if needed. + op2 = impImplicitR4orR8Cast(op2, op1->TypeGet()); op1 = gtNewAssignNode(op1, op2); } diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 34c15bb737b2bc..698ee12196597e 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -4963,30 +4963,34 @@ unsigned Compiler::fgGetFieldMorphingTemp(GenTreeField* fieldNode) return lclNum; } -/***************************************************************************** - * - * Transform the given GT_FIELD tree for code generation. - */ - +//------------------------------------------------------------------------ +// fgMorphField: Fully morph a FIELD/FIELD_ADDR tree. +// +// Expands the field node into explicit additions and indirections. +// +// Arguments: +// tree - The FIELD/FIELD_ADDR tree +// mac - The morphing context, used to elide adding null checks +// +// Return Value: +// The fully morphed "tree". +// GenTree* Compiler::fgMorphField(GenTree* tree, MorphAddrContext* mac) { - assert(tree->gtOper == GT_FIELD); + assert(tree->OperIs(GT_FIELD, GT_FIELD_ADDR)); - CORINFO_FIELD_HANDLE symHnd = tree->AsField()->gtFldHnd; - unsigned fldOffset = tree->AsField()->gtFldOffset; - GenTree* objRef = tree->AsField()->GetFldObj(); - bool fldMayOverlap = tree->AsField()->gtFldMayOverlap; - FieldSeq* fieldSeq = nullptr; + GenTreeField* fieldNode = tree->AsField(); + GenTree* objRef = fieldNode->GetFldObj(); - // Reset the flag because we may reuse the node. - tree->AsField()->gtFldMayOverlap = false; - - noway_assert(((objRef != nullptr) && (objRef->IsLocalAddrExpr() != nullptr)) || - ((tree->gtFlags & GTF_GLOB_REF) != 0)); + if (tree->OperIs(GT_FIELD)) + { + noway_assert(((objRef != nullptr) && (objRef->IsLocalAddrExpr() != nullptr)) || + ((tree->gtFlags & GTF_GLOB_REF) != 0)); + } #ifdef FEATURE_SIMD // if this field belongs to simd struct, translate it to simd intrinsic. - if (mac == nullptr) + if ((mac == nullptr) && tree->OperIs(GT_FIELD)) { if (IsBaselineSimdIsaSupported()) { @@ -5008,38 +5012,92 @@ GenTree* Compiler::fgMorphField(GenTree* tree, MorphAddrContext* mac) } #endif - // Create a default MorphAddrContext early so it doesn't go out of scope - // before it is used. - MorphAddrContext defMAC(MACK_Ind); + MorphAddrContext indMAC(MACK_Ind); + MorphAddrContext addrMAC(MACK_Addr); + bool isAddr = tree->OperIs(GT_FIELD_ADDR); - // Is this an instance data member? - if (objRef != nullptr) + if (fieldNode->IsInstance()) { - if (tree->gtFlags & GTF_IND_TLS_REF) + // NULL mac means we encounter the GT_FIELD/GT_FIELD_ADDR first (and don't know our parent). + if (mac == nullptr) { - NO_WAY("instance field can not be a TLS ref."); + // FIELD denotes a dereference of the field, equivalent to a MACK_Ind with zero offset. + mac = tree->OperIs(GT_FIELD) ? &indMAC : &addrMAC; } - /* We'll create the expression "*(objRef + mem_offs)" */ + tree = fgMorphExpandInstanceField(tree, mac); + } + else if (fieldNode->IsTlsStatic()) + { + tree = fgMorphExpandTlsFieldAddr(tree); + } + else + { + tree = fgMorphExpandStaticField(tree); + } - noway_assert(varTypeIsGC(objRef->TypeGet()) || objRef->TypeGet() == TYP_I_IMPL); + // Pass down the current mac; if non null we are computing an address + GenTree* result; + if (tree->OperIsSimple()) + { + result = fgMorphSmpOp(tree, mac); + DBEXEC(result != fieldNode, result->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED); - /* - Now we have a tree like this: + // Quirk: preserve previous behavior with this NO_CSE. + if (isAddr && result->OperIs(GT_COMMA)) + { + result->SetDoNotCSE(); + } + } + else + { + result = fgMorphTree(tree, mac); + DBEXEC(result == fieldNode, result->gtDebugFlags &= ~GTF_DEBUG_NODE_MORPHED); + } + + JITDUMP("\nFinal value of Compiler::fgMorphField after morphing:\n"); + DISPTREE(result); + + return result; +} + +//------------------------------------------------------------------------ +// fgMorphExpandInstanceField: Expand an instance field reference. +// +// Expands the field node into explicit additions and indirections, adding +// explicit null checks if necessary. +// +// Arguments: +// tree - The FIELD/FIELD_ADDR tree +// mac - The morphing context, used to elide adding null checks +// +// Return Value: +// The expanded "tree" of an arbitrary shape. +// +GenTree* Compiler::fgMorphExpandInstanceField(GenTree* tree, MorphAddrContext* mac) +{ + assert(tree->OperIs(GT_FIELD, GT_FIELD_ADDR) && tree->AsField()->IsInstance()); + + GenTree* objRef = tree->AsField()->GetFldObj(); + CORINFO_FIELD_HANDLE fieldHandle = tree->AsField()->gtFldHnd; + unsigned fieldOffset = tree->AsField()->gtFldOffset; + + noway_assert(varTypeIsI(genActualType(objRef))); + + /* Now we have a tree like this: +--------------------+ - | GT_FIELD | tree + | GT_FIELD[_ADDR] | tree +----------+---------+ | +--------------+-------------+ |tree->AsField()->GetFldObj()| +--------------+-------------+ - We want to make it like this (when fldOffset is <= MAX_UNCHECKED_OFFSET_FOR_NULL_OBJECT): +--------------------+ - | GT_IND/GT_OBJ | tree + | GT_IND/GT_OBJ | tree (for FIELD) +---------+----------+ | | @@ -5050,37 +5108,37 @@ GenTree* Compiler::fgMorphField(GenTree* tree, MorphAddrContext* mac) / \ / \ / \ - +-------------------+ +----------------------+ - | objRef | | fldOffset | - | | | (when fldOffset !=0) | - +-------------------+ +----------------------+ + +-------------------+ +----------------------+ + | objRef | | fldOffset | + | | | (when fldOffset !=0) | + +-------------------+ +----------------------+ or this (when fldOffset is > MAX_UNCHECKED_OFFSET_FOR_NULL_OBJECT): +--------------------+ - | GT_IND/GT_OBJ | tree + | GT_IND/GT_OBJ | tree (for FIELD) +----------+---------+ | +----------+---------+ - | GT_COMMA | comma2 + | GT_COMMA | comma2 +----------+---------+ | / \ / \ / \ / \ - +---------+----------+ +---------+----------+ - comma | GT_COMMA | | "+" (i.e. GT_ADD) | addr - +---------+----------+ +---------+----------+ - | | - / \ / \ - / \ / \ - / \ / \ - +-----+-----+ +-----+-----+ +---------+ +-----------+ - asg | GT_ASG | ind | GT_IND | | tmpLcl | | fldOffset | - +-----+-----+ +-----+-----+ +---------+ +-----------+ + +---------+----------+ +---------+----------+ + comma | GT_COMMA | | "+" (i.e. GT_ADD) | addr + +---------+----------+ +---------+----------+ + | | + / \ / \ + / \ / \ + / \ / \ + +-----+-----+ +-----+-----+ +---------+ +-----------+ + asg | GT_ASG | ind | GT_IND | | tmpLcl | | fldOffset | + +-----+-----+ +-----+-----+ +---------+ +-----------+ | | / \ | / \ | @@ -5089,325 +5147,324 @@ GenTree* Compiler::fgMorphField(GenTree* tree, MorphAddrContext* mac) | tmpLcl | | objRef | | tmpLcl | +-----------+ +-----------+ +-----------+ + */ - */ - - var_types objRefType = objRef->TypeGet(); - GenTree* addr = nullptr; - GenTree* comma = nullptr; + var_types objRefType = objRef->TypeGet(); + GenTree* addr = nullptr; + GenTree* comma = nullptr; + bool addExplicitNullCheck = false; - // NULL mac means we encounter the GT_FIELD first. This denotes a dereference of the field, - // and thus is equivalent to a MACK_Ind with zero offset. - if (mac == nullptr) + if (fgAddrCouldBeNull(objRef)) + { + if (!mac->m_allConstantOffsets || fgIsBigOffset(mac->m_totalOffset + fieldOffset)) { - mac = &defMAC; + addExplicitNullCheck = true; } - - bool addExplicitNullCheck = false; - - // Implicit byref locals and string literals are never null. - if (fgAddrCouldBeNull(objRef)) + else { - if (!mac->m_allConstantOffsets || fgIsBigOffset(mac->m_totalOffset + fldOffset)) - { - addExplicitNullCheck = true; - } - else - { - addExplicitNullCheck = mac->m_kind == MACK_Addr; - } + addExplicitNullCheck = mac->m_kind == MACK_Addr; } + } - if (addExplicitNullCheck) - { -#ifdef DEBUG - if (verbose) - { - printf("Before explicit null check morphing:\n"); - gtDispTree(tree); - } -#endif - - // - // Create the "comma" subtree - // - GenTree* asg = nullptr; - - unsigned lclNum; + if (addExplicitNullCheck) + { + JITDUMP("Before explicit null check morphing:\n"); + DISPTREE(tree); - if (!objRef->OperIs(GT_LCL_VAR) || lvaIsLocalImplicitlyAccessedByRef(objRef->AsLclVar()->GetLclNum())) - { - lclNum = fgGetFieldMorphingTemp(tree->AsField()); + // Create the "comma" subtree. + GenTree* asg = nullptr; + unsigned lclNum; - // Create the "asg" node - asg = gtNewTempAssign(lclNum, objRef); - } - else - { - lclNum = objRef->AsLclVarCommon()->GetLclNum(); - } + if (!objRef->OperIs(GT_LCL_VAR) || lvaIsLocalImplicitlyAccessedByRef(objRef->AsLclVar()->GetLclNum())) + { + lclNum = fgGetFieldMorphingTemp(tree->AsField()); - GenTree* lclVar = gtNewLclvNode(lclNum, objRefType); - GenTree* nullchk = gtNewNullCheck(lclVar, compCurBB); + // Create the "asg" node + asg = gtNewTempAssign(lclNum, objRef); + } + else + { + lclNum = objRef->AsLclVarCommon()->GetLclNum(); + } - if (asg != nullptr) - { - // Create the "comma" node. - comma = gtNewOperNode(GT_COMMA, TYP_VOID, asg, nullchk); - } - else - { - comma = nullchk; - } + GenTree* lclVar = gtNewLclvNode(lclNum, objRefType); + GenTree* nullchk = gtNewNullCheck(lclVar, compCurBB); - addr = gtNewLclvNode(lclNum, objRefType); // Use "tmpLcl" to create "addr" node. + if (asg != nullptr) + { + // Create the "comma" node. + comma = gtNewOperNode(GT_COMMA, TYP_VOID, asg, nullchk); } else { - addr = objRef; + comma = nullchk; } + addr = gtNewLclvNode(lclNum, objRefType); // Use "tmpLcl" to create "addr" node. + } + else + { + addr = objRef; + } + #ifdef FEATURE_READYTORUN - if (tree->AsField()->gtFieldLookup.addr != nullptr) + if (tree->AsField()->gtFieldLookup.addr != nullptr) + { + GenTree* offsetNode = nullptr; + if (tree->AsField()->gtFieldLookup.accessType == IAT_PVALUE) { - GenTree* offsetNode = nullptr; - if (tree->AsField()->gtFieldLookup.accessType == IAT_PVALUE) - { - offsetNode = gtNewIndOfIconHandleNode(TYP_I_IMPL, (size_t)tree->AsField()->gtFieldLookup.addr, - GTF_ICON_CONST_PTR, true); + offsetNode = gtNewIndOfIconHandleNode(TYP_I_IMPL, (size_t)tree->AsField()->gtFieldLookup.addr, + GTF_ICON_CONST_PTR, true); #ifdef DEBUG - offsetNode->gtGetOp1()->AsIntCon()->gtTargetHandle = (size_t)symHnd; + offsetNode->gtGetOp1()->AsIntCon()->gtTargetHandle = (size_t)fieldHandle; #endif - } - else - { - noway_assert(!"unexpected accessType for R2R field access"); - } - - var_types addType = (objRefType == TYP_I_IMPL) ? TYP_I_IMPL : TYP_BYREF; - addr = gtNewOperNode(GT_ADD, addType, addr, offsetNode); } -#endif - - // We only need to attach the field offset information for class fields. - if ((objRefType == TYP_REF) && !fldMayOverlap) + else { - fieldSeq = GetFieldSeqStore()->Create(symHnd, fldOffset, FieldSeq::FieldKind::Instance); + noway_assert(!"unexpected accessType for R2R field access"); } - // Add the member offset to the object's address. - if (fldOffset != 0) - { - addr = gtNewOperNode(GT_ADD, (objRefType == TYP_I_IMPL) ? TYP_I_IMPL : TYP_BYREF, addr, - gtNewIconNode(fldOffset, fieldSeq)); - } + addr = gtNewOperNode(GT_ADD, (objRefType == TYP_I_IMPL) ? TYP_I_IMPL : TYP_BYREF, addr, offsetNode); + } +#endif + + // We only need to attach the field offset information for class fields. + FieldSeq* fieldSeq = nullptr; + if ((objRefType == TYP_REF) && !tree->AsField()->gtFldMayOverlap) + { + fieldSeq = GetFieldSeqStore()->Create(fieldHandle, fieldOffset, FieldSeq::FieldKind::Instance); + } - // Now let's set the "tree" as a GT_IND tree. + // Add the member offset to the object's address. + if (fieldOffset != 0) + { + addr = gtNewOperNode(GT_ADD, (objRefType == TYP_I_IMPL) ? TYP_I_IMPL : TYP_BYREF, addr, + gtNewIconNode(fieldOffset, fieldSeq)); + } + if (addExplicitNullCheck) + { + // Create the "comma2" tree. + addr = gtNewOperNode(GT_COMMA, addr->TypeGet(), comma, addr); + } + + if (tree->OperIs(GT_FIELD)) + { tree->SetOper(GT_IND); - tree->AsOp()->gtOp1 = addr; + tree->AsIndir()->SetAddr(addr); + } + else // Otherwise, we have a FIELD_ADDR. + { + tree = addr; + } - if (addExplicitNullCheck) - { - // - // Create "comma2" node and link it to "tree". - // - GenTree* comma2 = gtNewOperNode(GT_COMMA, addr->TypeGet(), comma, addr); - tree->AsOp()->gtOp1 = comma2; - } + if (addExplicitNullCheck) + { + JITDUMP("After adding explicit null check:\n"); + DISPTREE(tree); + } -#ifdef DEBUG - if (verbose) + return tree; +} + +//------------------------------------------------------------------------ +// fgMorphExpandTlsFieldAddr: Expand a TLS field address. +// +// Expands ".tls"-style statics, produced by the C++/CLI compiler for +// "__declspec(thread)" variables. An overview of the underlying native +// mechanism can be found here: http://www.nynaeve.net/?p=180. +// +// Arguments: +// tree - The GT_FIELD_ADDR tree +// +// Return Value: +// The expanded tree - a GT_ADD. +// +GenTree* Compiler::fgMorphExpandTlsFieldAddr(GenTree* tree) +{ + // Note we do not support "FIELD"s for TLS statics, for simplicity. + assert(tree->OperIs(GT_FIELD_ADDR) && tree->AsField()->IsTlsStatic()); + + CORINFO_FIELD_HANDLE fieldHandle = tree->AsField()->gtFldHnd; + int fieldOffset = tree->AsField()->gtFldOffset; + + // Thread Local Storage static field reference + // + // Field ref is a TLS 'Thread-Local-Storage' reference + // + // Build this tree: ADD(I_IMPL) # + // / \. + // / CNS(fldOffset) + // / + // / + // / + // IND(I_IMPL) == [Base of this DLL's TLS] + // | + // ADD(I_IMPL) + // / \. + // / CNS(IdValue*4) or MUL + // / / \. + // IND(I_IMPL) / CNS(4) + // | / + // CNS(TLS_HDL,0x2C) IND + // | + // CNS(pIdAddr) + // + // # Denotes the original node + // + void** pIdAddr = nullptr; + unsigned IdValue = info.compCompHnd->getFieldThreadLocalStoreID(fieldHandle, (void**)&pIdAddr); + + // + // If we can we access the TLS DLL index ID value directly + // then pIdAddr will be NULL and + // IdValue will be the actual TLS DLL index ID + // + GenTree* dllRef = nullptr; + if (pIdAddr == nullptr) + { + if (IdValue != 0) { - if (addExplicitNullCheck) - { - printf("After adding explicit null check:\n"); - gtDispTree(tree); - } + dllRef = gtNewIconNode(IdValue * 4, TYP_I_IMPL); } -#endif } - else /* This is a static data member */ + else { - if (tree->gtFlags & GTF_IND_TLS_REF) - { - // Thread Local Storage static field reference - // - // Field ref is a TLS 'Thread-Local-Storage' reference - // - // Build this tree: IND(*) # - // | - // ADD(I_IMPL) - // / \. - // / CNS(fldOffset) - // / - // / - // / - // IND(I_IMPL) == [Base of this DLL's TLS] - // | - // ADD(I_IMPL) - // / \. - // / CNS(IdValue*4) or MUL - // / / \. - // IND(I_IMPL) / CNS(4) - // | / - // CNS(TLS_HDL,0x2C) IND - // | - // CNS(pIdAddr) - // - // # Denotes the original node - // - void** pIdAddr = nullptr; - unsigned IdValue = info.compCompHnd->getFieldThreadLocalStoreID(symHnd, (void**)&pIdAddr); - - // - // If we can we access the TLS DLL index ID value directly - // then pIdAddr will be NULL and - // IdValue will be the actual TLS DLL index ID - // - GenTree* dllRef = nullptr; - if (pIdAddr == nullptr) - { - if (IdValue != 0) - { - dllRef = gtNewIconNode(IdValue * 4, TYP_I_IMPL); - } - } - else - { - dllRef = gtNewIndOfIconHandleNode(TYP_I_IMPL, (size_t)pIdAddr, GTF_ICON_CONST_PTR, true); + dllRef = gtNewIndOfIconHandleNode(TYP_I_IMPL, (size_t)pIdAddr, GTF_ICON_CONST_PTR, true); - // Next we multiply by 4 - dllRef = gtNewOperNode(GT_MUL, TYP_I_IMPL, dllRef, gtNewIconNode(4, TYP_I_IMPL)); - } + // Next we multiply by 4 + dllRef = gtNewOperNode(GT_MUL, TYP_I_IMPL, dllRef, gtNewIconNode(4, TYP_I_IMPL)); + } #define WIN32_TLS_SLOTS (0x2C) // Offset from fs:[0] where the pointer to the slots resides - // Mark this ICON as a TLS_HDL, codegen will use FS:[cns] + // Mark this ICON as a TLS_HDL, codegen will use FS:[cns] + GenTree* tlsRef = gtNewIconHandleNode(WIN32_TLS_SLOTS, GTF_ICON_TLS_HDL); - GenTree* tlsRef = gtNewIconHandleNode(WIN32_TLS_SLOTS, GTF_ICON_TLS_HDL); + // Translate GTF_FLD_INITCLASS to GTF_ICON_INITCLASS + if ((tree->gtFlags & GTF_FLD_INITCLASS) != 0) + { + tree->gtFlags &= ~GTF_FLD_INITCLASS; + tlsRef->gtFlags |= GTF_ICON_INITCLASS; + } - // Translate GTF_FLD_INITCLASS to GTF_ICON_INITCLASS - if ((tree->gtFlags & GTF_FLD_INITCLASS) != 0) - { - tree->gtFlags &= ~GTF_FLD_INITCLASS; - tlsRef->gtFlags |= GTF_ICON_INITCLASS; - } + tlsRef = gtNewIndir(TYP_I_IMPL, tlsRef, GTF_IND_NONFAULTING | GTF_IND_INVARIANT); - tlsRef = gtNewOperNode(GT_IND, TYP_I_IMPL, tlsRef); + if (dllRef != nullptr) + { + // Add the dllRef. + tlsRef = gtNewOperNode(GT_ADD, TYP_I_IMPL, tlsRef, dllRef); + } - if (dllRef != nullptr) - { - /* Add the dllRef */ - tlsRef = gtNewOperNode(GT_ADD, TYP_I_IMPL, tlsRef, dllRef); - } + // indirect to have tlsRef point at the base of the DLLs Thread Local Storage. + tlsRef = gtNewOperNode(GT_IND, TYP_I_IMPL, tlsRef); - /* indirect to have tlsRef point at the base of the DLLs Thread Local Storage */ - tlsRef = gtNewOperNode(GT_IND, TYP_I_IMPL, tlsRef); + // Add the TLS static field offset to the address. + assert(!tree->AsField()->gtFldMayOverlap); + FieldSeq* fieldSeq = GetFieldSeqStore()->Create(fieldHandle, fieldOffset, FieldSeq::FieldKind::SimpleStatic); + GenTree* offsetNode = gtNewIconNode(fieldOffset, fieldSeq); - // Add the TLS static field offset to the address. - assert(!fldMayOverlap); - fieldSeq = GetFieldSeqStore()->Create(symHnd, fldOffset, FieldSeq::FieldKind::SimpleStatic); - tlsRef = gtNewOperNode(GT_ADD, TYP_I_IMPL, tlsRef, gtNewIconNode(fldOffset, fieldSeq)); + tree->ChangeOper(GT_ADD); + tree->AsOp()->gtOp1 = tlsRef; + tree->AsOp()->gtOp2 = offsetNode; - // Final indirect to get to actual value of TLS static field + return tree; +} - tree->SetOper(GT_IND); - tree->AsOp()->gtOp1 = tlsRef; +//------------------------------------------------------------------------ +// fgMorphExpandStaticField: Expand a simple static field load. +// +// Transforms the field into an explicit indirection off of a constant +// address. +// +// Arguments: +// tree - The GT_FIELD tree +// +// Return Value: +// The expanded tree - a GT_IND. +// +GenTree* Compiler::fgMorphExpandStaticField(GenTree* tree) +{ + // Note we do not support "FIELD_ADDR"s for simple statics. + assert(tree->OperIs(GT_FIELD) && tree->AsField()->IsStatic()); - noway_assert(tree->gtFlags & GTF_IND_TLS_REF); - } - else - { - // Normal static field reference - // - // If we can we access the static's address directly - // then pFldAddr will be NULL and - // fldAddr will be the actual address of the static field - // - void** pFldAddr = nullptr; - void* fldAddr = info.compCompHnd->getFieldAddress(symHnd, (void**)&pFldAddr); + // If we can we access the static's address directly + // then pFldAddr will be NULL and + // fldAddr will be the actual address of the static field + // + CORINFO_FIELD_HANDLE fieldHandle = tree->AsField()->gtFldHnd; + void** pFldAddr = nullptr; + void* fldAddr = info.compCompHnd->getFieldAddress(fieldHandle, (void**)&pFldAddr); - // We should always be able to access this static field address directly - // - assert(pFldAddr == nullptr); + // We should always be able to access this static field address directly + // + assert(pFldAddr == nullptr); - // For boxed statics, this direct address will be for the box. We have already added - // the indirection for the field itself and attached the sequence, in importation. - bool isBoxedStatic = gtIsStaticFieldPtrToBoxedStruct(tree->TypeGet(), symHnd); - if (!isBoxedStatic) - { - // Only simple statics get importred as GT_FIELDs. - fieldSeq = GetFieldSeqStore()->Create(symHnd, reinterpret_cast(fldAddr), - FieldSeq::FieldKind::SimpleStatic); - } + // For boxed statics, this direct address will be for the box. We have already added + // the indirection for the field itself and attached the sequence, in importation. + FieldSeq* fieldSeq = nullptr; + bool isBoxedStatic = gtIsStaticFieldPtrToBoxedStruct(tree->TypeGet(), fieldHandle); + if (!isBoxedStatic) + { + // Only simple statics get importred as GT_FIELDs. + fieldSeq = GetFieldSeqStore()->Create(fieldHandle, reinterpret_cast(fldAddr), + FieldSeq::FieldKind::SimpleStatic); + } - // TODO-CQ: enable this optimization for 32 bit targets. - bool isStaticReadOnlyInited = false; + // TODO-CQ: enable this optimization for 32 bit targets. + bool isStaticReadOnlyInited = false; #ifdef TARGET_64BIT - if (tree->TypeIs(TYP_REF) && !isBoxedStatic) - { - bool pIsSpeculative = true; - if (info.compCompHnd->getStaticFieldCurrentClass(symHnd, &pIsSpeculative) != NO_CLASS_HANDLE) - { - isStaticReadOnlyInited = !pIsSpeculative; - } - } + if (tree->TypeIs(TYP_REF) && !isBoxedStatic) + { + bool pIsSpeculative = true; + if (info.compCompHnd->getStaticFieldCurrentClass(fieldHandle, &pIsSpeculative) != NO_CLASS_HANDLE) + { + isStaticReadOnlyInited = !pIsSpeculative; + } + } #endif // TARGET_64BIT - GenTreeFlags handleKind = GTF_EMPTY; - if (isBoxedStatic) - { - handleKind = GTF_ICON_STATIC_BOX_PTR; - } - else if (isStaticReadOnlyInited) - { - handleKind = GTF_ICON_CONST_PTR; - } - else - { - handleKind = GTF_ICON_STATIC_HDL; - } - GenTreeIntCon* addr = gtNewIconHandleNode((size_t)fldAddr, handleKind, fieldSeq); - INDEBUG(addr->gtTargetHandle = reinterpret_cast(symHnd)); - - // Translate GTF_FLD_INITCLASS to GTF_ICON_INITCLASS, if we need to. - if (((tree->gtFlags & GTF_FLD_INITCLASS) != 0) && !isStaticReadOnlyInited) - { - tree->gtFlags &= ~GTF_FLD_INITCLASS; - addr->gtFlags |= GTF_ICON_INITCLASS; - } - - tree->SetOper(GT_IND); - tree->AsOp()->gtOp1 = addr; - - if (isBoxedStatic) - { - // The box for the static cannot be null, and is logically invariant, since it - // represents (a base for) the static's address. - tree->gtFlags |= (GTF_IND_INVARIANT | GTF_IND_NONFAULTING | GTF_IND_NONNULL); - } - else if (isStaticReadOnlyInited) - { - JITDUMP("Marking initialized static read-only field '%s' as invariant.\n", eeGetFieldName(symHnd)); - - // Static readonly field is not null at this point (see getStaticFieldCurrentClass impl). - tree->gtFlags |= (GTF_IND_INVARIANT | GTF_IND_NONFAULTING | GTF_IND_NONNULL); - } + GenTreeFlags handleKind = GTF_EMPTY; + if (isBoxedStatic) + { + handleKind = GTF_ICON_STATIC_BOX_PTR; + } + else if (isStaticReadOnlyInited) + { + handleKind = GTF_ICON_CONST_PTR; + } + else + { + handleKind = GTF_ICON_STATIC_HDL; + } + GenTreeIntCon* addr = gtNewIconHandleNode((size_t)fldAddr, handleKind, fieldSeq); + INDEBUG(addr->gtTargetHandle = reinterpret_cast(fieldHandle)); - return fgMorphSmpOp(tree, /* mac */ nullptr); - } + // Translate GTF_FLD_INITCLASS to GTF_ICON_INITCLASS, if we need to. + if (((tree->gtFlags & GTF_FLD_INITCLASS) != 0) && !isStaticReadOnlyInited) + { + tree->gtFlags &= ~GTF_FLD_INITCLASS; + addr->gtFlags |= GTF_ICON_INITCLASS; } - noway_assert(tree->OperIs(GT_IND)); + tree->SetOper(GT_IND); + tree->AsOp()->gtOp1 = addr; - // Pass down the current mac; if non null we are computing an address - GenTree* result = fgMorphSmpOp(tree, mac); + if (isBoxedStatic) + { + // The box for the static cannot be null, and is logically invariant, since it + // represents (a base for) the static's address. + tree->gtFlags |= (GTF_IND_INVARIANT | GTF_IND_NONFAULTING | GTF_IND_NONNULL); + } + else if (isStaticReadOnlyInited) + { + JITDUMP("Marking initialized static read-only field '%s' as invariant.\n", eeGetFieldName(fieldHandle)); - JITDUMP("\nFinal value of Compiler::fgMorphField after calling fgMorphSmpOp:\n"); - DISPTREE(result); + // Static readonly field is not null at this point (see getStaticFieldCurrentClass impl). + tree->gtFlags |= (GTF_IND_INVARIANT | GTF_IND_NONFAULTING | GTF_IND_NONNULL); + } - return result; + return tree; } //------------------------------------------------------------------------------ @@ -9664,6 +9721,7 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA break; case GT_FIELD: + case GT_FIELD_ADDR: return fgMorphField(tree, mac); case GT_INDEX_ADDR: diff --git a/src/coreclr/jit/objectalloc.cpp b/src/coreclr/jit/objectalloc.cpp index 1f251f48d38d17..94072a00eb797a 100644 --- a/src/coreclr/jit/objectalloc.cpp +++ b/src/coreclr/jit/objectalloc.cpp @@ -656,6 +656,7 @@ bool ObjectAllocator::CanLclVarEscapeViaParentStack(ArrayStack* parent case GT_COLON: case GT_QMARK: case GT_ADD: + case GT_FIELD_ADDR: // Check whether the local escapes via its grandparent. ++parentIndex; keepChecking = true; @@ -761,6 +762,7 @@ void ObjectAllocator::UpdateAncestorTypes(GenTree* tree, ArrayStack* p case GT_COLON: case GT_QMARK: case GT_ADD: + case GT_FIELD_ADDR: if (parent->TypeGet() == TYP_REF) { parent->ChangeType(newType); diff --git a/src/coreclr/jit/rationalize.cpp b/src/coreclr/jit/rationalize.cpp index dad87564355ac0..299dfc1d17a0f2 100644 --- a/src/coreclr/jit/rationalize.cpp +++ b/src/coreclr/jit/rationalize.cpp @@ -53,9 +53,6 @@ void Rationalizer::RewriteIndir(LIR::Use& use) GenTreeIndir* indir = use.Def()->AsIndir(); assert(indir->OperIs(GT_IND, GT_BLK, GT_OBJ)); - // Clear the `GTF_IND_ASG_LHS` flag, which overlaps with `GTF_IND_REQ_ADDR_IN_REG`. - indir->gtFlags &= ~GTF_IND_ASG_LHS; - if (varTypeIsSIMD(indir)) { if (indir->OperIs(GT_BLK, GT_OBJ))