From 318c0e6708ced35180fd5218170f82246e2f2bac Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 27 Jun 2023 22:07:00 +0200 Subject: [PATCH] Rework JIT<->EE communication for struct promotion (#87917) Rework the communication so that the JIT asks the EE for struct field related information relevant to promotion in a single JIT-EE call of a new function called getTypeLayout. Crossgen2 can then more easily provide the information that the JIT is allowed to depend on. As a side effect, removes CORINFO_FLG_CUSTOMLAYOUT (which fixes #71711) and CORINFO_FLG_DONT_DIG_FIELDS. Fix #85511 Fix #71711 --- src/coreclr/inc/corinfo.h | 81 ++- src/coreclr/inc/icorjitinfoimpl_generated.h | 5 + src/coreclr/inc/jiteeversionguid.h | 10 +- src/coreclr/jit/ICorJitInfo_names_generated.h | 1 + .../jit/ICorJitInfo_wrapper_generated.hpp | 11 + src/coreclr/jit/compiler.h | 63 +- src/coreclr/jit/compiler.hpp | 10 - src/coreclr/jit/gentree.cpp | 23 +- src/coreclr/jit/lclvars.cpp | 540 ++++++++---------- src/coreclr/jit/liveness.cpp | 4 +- src/coreclr/jit/morph.cpp | 12 +- src/coreclr/jit/morphblock.cpp | 13 +- src/coreclr/jit/promotion.cpp | 67 +-- src/coreclr/jit/promotiondecomposition.cpp | 2 +- src/coreclr/jit/simd.cpp | 6 + .../tools/Common/JitInterface/CorInfoImpl.cs | 188 +++++- .../JitInterface/CorInfoImpl_generated.cs | 244 ++++---- .../tools/Common/JitInterface/CorInfoTypes.cs | 25 +- .../ThunkGenerator/ThunkInput.txt | 3 + .../aot/jitinterface/jitinterface_generated.h | 12 + .../tools/superpmi/superpmi-shared/agnostic.h | 19 + .../tools/superpmi/superpmi-shared/lwmlist.h | 1 + .../superpmi-shared/methodcontext.cpp | 114 ++++ .../superpmi/superpmi-shared/methodcontext.h | 6 + .../superpmi-shared/spmidumphelper.cpp | 1 - .../superpmi-shared/spmirecordhelper.h | 31 + .../superpmi-shim-collector/icorjitinfo.cpp | 11 + .../icorjitinfo_generated.cpp | 9 + .../icorjitinfo_generated.cpp | 8 + .../tools/superpmi/superpmi/icorjitinfo.cpp | 9 + src/coreclr/vm/jitinterface.cpp | 178 +++++- 31 files changed, 1129 insertions(+), 578 deletions(-) diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index 83d007e5a436b..d65bf19a222ee 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -857,8 +857,6 @@ enum CorInfoFlag CORINFO_FLG_ARRAY = 0x00080000, // class is an array class (initialized differently) CORINFO_FLG_OVERLAPPING_FIELDS = 0x00100000, // struct or class has fields that overlap (aka union) CORINFO_FLG_INTERFACE = 0x00200000, // it is an interface - CORINFO_FLG_DONT_DIG_FIELDS = 0x00400000, // don't ask field info (used for types outside of AOT compilation version bubble) - CORINFO_FLG_CUSTOMLAYOUT = 0x00800000, // does this struct have custom layout? CORINFO_FLG_CONTAINS_GC_PTR = 0x01000000, // does the class contain a gc ptr ? CORINFO_FLG_DELEGATE = 0x02000000, // is this a subclass of delegate or multicast delegate ? CORINFO_FLG_INDEXABLE_FIELDS = 0x04000000, // struct fields may be accessed via indexing (used for inline arrays) @@ -1934,6 +1932,46 @@ struct CORINFO_VarArgInfo // (The CORINFO_VARARGS_HANDLE counts as an arg) }; +struct CORINFO_TYPE_LAYOUT_NODE +{ + // Type handle if this is a SIMD type, i.e. for intrinsic types in + // System.Numerics and System.Runtime.Intrinsics namespaces. This handle + // should be used for SIMD type recognition ONLY. During prejitting the + // returned handle cannot safely be used for arbitrary JIT-EE calls. The + // safe operations on this handle are: + // - getClassNameFromMetadata + // - getClassSize + // - getHfaType + // - getTypeInstantiationArgument, but only under the assumption that the returned type handle + // is used for primitive type recognition via getTypeForPrimitiveNumericClass + CORINFO_CLASS_HANDLE simdTypeHnd; + // Field handle that should only be used for diagnostic purposes. During + // prejit we cannot allow arbitrary JIT-EE calls with this field handle, but it can be used + // for diagnostic purposes (e.g. to obtain the field name). + CORINFO_FIELD_HANDLE diagFieldHnd; + // Index of parent node in the tree + unsigned parent; + // Offset into the root type of the field + unsigned offset; + // Size of the type. + unsigned size; + // Number of fields for type == CORINFO_TYPE_VALUECLASS. This is the number of nodes added. + unsigned numFields; + // Type of the field. + CorInfoType type; + // For type == CORINFO_TYPE_VALUECLASS indicates whether the type has significant padding. + // That is, whether or not the JIT always needs to preserve data stored in + // the parts that are not covered by fields. + bool hasSignificantPadding; +}; + +enum class GetTypeLayoutResult +{ + Success, + Partial, + Failure, +}; + #define SIZEOF__CORINFO_Object TARGET_POINTER_SIZE /* methTable */ #define CORINFO_Array_MaxLength 0x7FFFFFC7 @@ -2403,6 +2441,45 @@ class ICorStaticInfo int32_t num ) = 0; + //------------------------------------------------------------------------------ + // getTypeLayout: Obtain a tree describing the layout of a type. + // + // Parameters: + // typeHnd - Handle of the type. + // treeNodes - [in, out] Pointer to tree node entries to write. + // numTreeNodes - [in, out] Size of 'treeNodes' on entry. Updated to contain + // the number of entries written in 'treeNodes'. + // + // Returns: + // A result indicating whether the type layout was successfully + // retrieved and whether the result is partial or not. + // + // Remarks: + // The type layout should be stored in preorder in 'treeNodes': the root + // node is always at index 0, and the first child of any node is at its + // own index + 1. The fields returned are NOT guaranteed to be ordered + // by offset. + // + // SIMD and HW SIMD types are returned as a single entry without any + // children. For those, CORINFO_TYPE_LAYOUT_NODE::simdTypeHnd is set, but + // can only be used in a very restricted capacity, see + // CORINFO_TYPE_LAYOUT_NODE. Note that this special treatment is only for + // fields; if typeHnd itself is a SIMD type this function will treat it + // like a normal struct type and expand its fields. + // + // IMPORTANT: except for GC pointers the fields returned to the JIT by + // this function should be considered as a hint only. The JIT CANNOT make + // assumptions in its codegen that the specified fields are actually part + // of the type when the code finally runs. This means the JIT should not + // make optimizations based on the field information returned by this + // function that would break if those fields were removed or shifted + // around. + // + virtual GetTypeLayoutResult getTypeLayout( + CORINFO_CLASS_HANDLE typeHnd, + CORINFO_TYPE_LAYOUT_NODE* treeNodes, + size_t* numTreeNodes) = 0; + virtual bool checkMethodModifier( CORINFO_METHOD_HANDLE hMethod, const char * modifier, diff --git a/src/coreclr/inc/icorjitinfoimpl_generated.h b/src/coreclr/inc/icorjitinfoimpl_generated.h index 5a7b737b85168..9df17c6674718 100644 --- a/src/coreclr/inc/icorjitinfoimpl_generated.h +++ b/src/coreclr/inc/icorjitinfoimpl_generated.h @@ -242,6 +242,11 @@ CORINFO_FIELD_HANDLE getFieldInClass( CORINFO_CLASS_HANDLE clsHnd, int32_t num) override; +GetTypeLayoutResult getTypeLayout( + CORINFO_CLASS_HANDLE typeHnd, + CORINFO_TYPE_LAYOUT_NODE* treeNodes, + size_t* numTreeNodes) override; + bool checkMethodModifier( CORINFO_METHOD_HANDLE hMethod, const char* modifier, diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index 36f906df5e7ed..bdc9c7be36cf0 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -43,11 +43,11 @@ typedef const GUID *LPCGUID; #define GUID_DEFINED #endif // !GUID_DEFINED -constexpr GUID JITEEVersionIdentifier = { /* 88398e9f-093a-4212-85bc-bebb8c14cd24 */ - 0x88398e9f, - 0x093a, - 0x4212, - {0x85, 0xbc, 0xbe, 0xbb, 0x8c, 0x14, 0xcd, 0x24} +constexpr GUID JITEEVersionIdentifier = { /* 878d63a7-ffc9-421f-81f7-db4729f0ed5c */ + 0x878d63a7, + 0xffc9, + 0x421f, + {0x81, 0xf7, 0xdb, 0x47, 0x29, 0xf0, 0xed, 0x5c} }; ////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/coreclr/jit/ICorJitInfo_names_generated.h b/src/coreclr/jit/ICorJitInfo_names_generated.h index 7a7f975e50f99..79ca7d43da06e 100644 --- a/src/coreclr/jit/ICorJitInfo_names_generated.h +++ b/src/coreclr/jit/ICorJitInfo_names_generated.h @@ -59,6 +59,7 @@ DEF_CLR_API(getClassAlignmentRequirement) DEF_CLR_API(getClassGClayout) DEF_CLR_API(getClassNumInstanceFields) DEF_CLR_API(getFieldInClass) +DEF_CLR_API(getTypeLayout) DEF_CLR_API(checkMethodModifier) DEF_CLR_API(getNewHelper) DEF_CLR_API(getNewArrHelper) diff --git a/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp b/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp index 6d08b04259688..8fa67226763ba 100644 --- a/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp +++ b/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp @@ -548,6 +548,17 @@ CORINFO_FIELD_HANDLE WrapICorJitInfo::getFieldInClass( return temp; } +GetTypeLayoutResult WrapICorJitInfo::getTypeLayout( + CORINFO_CLASS_HANDLE typeHnd, + CORINFO_TYPE_LAYOUT_NODE* treeNodes, + size_t* numTreeNodes) +{ + API_ENTER(getTypeLayout); + GetTypeLayoutResult temp = wrapHnd->getTypeLayout(typeHnd, treeNodes, numTreeNodes); + API_LEAVE(getTypeLayout); + return temp; +} + bool WrapICorJitInfo::checkMethodModifier( CORINFO_METHOD_HANDLE hMethod, const char* modifier, diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index db07bec2c5b70..806c2f559dd5c 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -593,8 +593,13 @@ class LclVarDsc // fgRetypeImplicitByRefArgs and fgMarkDemotedImplicitByRefArgs to indicate whether // references to the arg are being rewritten as references to a promoted shadow local. unsigned char lvIsStructField : 1; // Is this local var a field of a promoted struct local? - unsigned char lvContainsHoles : 1; // True when we have a promoted struct that contains holes - unsigned char lvCustomLayout : 1; // True when this struct has "CustomLayout" + unsigned char lvContainsHoles : 1; // Is this a promoted struct whose fields do not cover the struct local? + + // True for a promoted struct that has significant padding in it. + // Significant padding is any data in the struct that is not covered by a + // promoted field and that the EE told us we need to preserve on block + // copies/inits. + unsigned char lvAnySignificantPadding : 1; unsigned char lvIsMultiRegArg : 1; // true if this is a multireg LclVar struct used in an argument context unsigned char lvIsMultiRegRet : 1; // true if this is a multireg LclVar struct assigned from a multireg call @@ -2055,7 +2060,6 @@ class Compiler bool shouldUseVerboseSsa(); bool treesBeforeAfterMorph; // If true, print trees before/after morphing (paired by an intra-compilation id: int morphNum; // This counts the trees that have been morphed, allowing us to label each uniquely. - bool doExtraSuperPmiQueries; void makeExtraStructQueries(CORINFO_CLASS_HANDLE structHandle, int level); // Make queries recursively 'level' deep. const char* VarNameToStr(VarName name) @@ -3568,17 +3572,18 @@ class Compiler // Info about struct type fields. struct lvaStructFieldInfo { - CORINFO_FIELD_HANDLE fldHnd; - unsigned char fldOffset; - unsigned char fldOrdinal; - var_types fldType; - unsigned fldSize; - CORINFO_CLASS_HANDLE fldTypeHnd; + // Class handle for SIMD type recognition, see CORINFO_TYPE_LAYOUT_NODE + // for more details on the restrictions. + CORINFO_CLASS_HANDLE fldSIMDTypeHnd = NO_CLASS_HANDLE; + uint8_t fldOffset = 0; + uint8_t fldOrdinal = 0; + var_types fldType = TYP_UNDEF; + unsigned fldSize = 0; - lvaStructFieldInfo() - : fldHnd(nullptr), fldOffset(0), fldOrdinal(0), fldType(TYP_UNDEF), fldSize(0), fldTypeHnd(nullptr) - { - } +#ifdef DEBUG + // Field handle for diagnostic purposes only. See CORINFO_TYPE_LAYOUT_NODE. + CORINFO_FIELD_HANDLE diagFldHnd = NO_FIELD_HANDLE; +#endif }; // Info about a struct type, instances of which may be candidates for promotion. @@ -3587,7 +3592,7 @@ class Compiler CORINFO_CLASS_HANDLE typeHnd; bool canPromote; bool containsHoles; - bool customLayout; + bool anySignificantPadding; bool fieldsSorted; unsigned char fieldCnt; lvaStructFieldInfo fields[MAX_NumOfFieldsInPromotableStruct]; @@ -3596,18 +3601,13 @@ class Compiler : typeHnd(typeHnd) , canPromote(false) , containsHoles(false) - , customLayout(false) + , anySignificantPadding(false) , fieldsSorted(false) , fieldCnt(0) { } }; - struct lvaFieldOffsetCmp - { - bool operator()(const lvaStructFieldInfo& field1, const lvaStructFieldInfo& field2); - }; - // This class is responsible for checking validity and profitability of struct promotion. // If it is both legal and profitable, then TryPromoteStructVar promotes the struct and initializes // necessary information for fgMorphStructField to use. @@ -3629,10 +3629,8 @@ class Compiler void PromoteStructVar(unsigned lclNum); void SortStructFields(); - bool CanConstructAndPromoteField(lvaStructPromotionInfo* structPromotionInfo); - - lvaStructFieldInfo GetFieldInfo(CORINFO_FIELD_HANDLE fieldHnd, BYTE ordinal); - bool TryPromoteStructField(lvaStructFieldInfo& outerFieldInfo); + var_types TryPromoteValueClassAsPrimitive(CORINFO_TYPE_LAYOUT_NODE* treeNodes, size_t maxTreeNodes, size_t index); + void AdvanceSubTree(CORINFO_TYPE_LAYOUT_NODE* treeNodes, size_t maxTreeNodes, size_t* index); private: Compiler* compiler; @@ -8509,6 +8507,16 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX return info.compCompHnd->getTypeInstantiationArgument(cls, index); } + bool isNumericsNamespace(const char* ns) + { + return strcmp(ns, "System.Numerics") == 0; + } + + bool isRuntimeIntrinsicsNamespace(const char* ns) + { + return strcmp(ns, "System.Runtime.Intrinsics") == 0; + } + #ifdef FEATURE_SIMD // Have we identified any SIMD types? // This is currently used by struct promotion to avoid getting type information for a struct @@ -8613,11 +8621,6 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX return isOpaqueSIMDType(varDsc->GetLayout()); } - bool isNumericsNamespace(const char* ns) - { - return strcmp(ns, "System.Numerics") == 0; - } - bool isSIMDClass(CORINFO_CLASS_HANDLE clsHnd) { if (isIntrinsicType(clsHnd)) @@ -8636,7 +8639,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX { const char* namespaceName = nullptr; (void)getClassNameFromMetadata(clsHnd, &namespaceName); - return strcmp(namespaceName, "System.Runtime.Intrinsics") == 0; + return isRuntimeIntrinsicsNamespace(namespaceName); } #endif // FEATURE_HW_INTRINSICS return false; diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index 2283fdb389d81..fd7809cdf848b 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -4544,16 +4544,6 @@ inline static bool StructHasOverlappingFields(DWORD attribs) return ((attribs & CORINFO_FLG_OVERLAPPING_FIELDS) != 0); } -inline static bool StructHasCustomLayout(DWORD attribs) -{ - return ((attribs & CORINFO_FLG_CUSTOMLAYOUT) != 0); -} - -inline static bool StructHasDontDigFieldsFlagSet(DWORD attribs) -{ - return ((attribs & CORINFO_FLG_DONT_DIG_FIELDS) != 0); -} - inline static bool StructHasIndexableFields(DWORD attribs) { return ((attribs & CORINFO_FLG_INDEXABLE_FIELDS) != 0); diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 6fdbeebc666fe..deee04b0a3d7f 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -11914,31 +11914,15 @@ void Compiler::gtDispLocal(GenTreeLclVarCommon* tree, IndentStack* indentStack) } else { - char buffer[256]; - for (unsigned index = 0; index < varDsc->lvFieldCnt; index++) { - unsigned fieldLclNum = varDsc->lvFieldLclStart + index; - LclVarDsc* fieldVarDsc = lvaGetDesc(fieldLclNum); - const char* fieldName; -#if !defined(TARGET_64BIT) - if (varTypeIsLong(varDsc)) - { - fieldName = (index == 0) ? "lo" : "hi"; - } - else -#endif // !defined(TARGET_64BIT) - { - CORINFO_CLASS_HANDLE typeHnd = varDsc->GetLayout()->GetClassHandle(); - CORINFO_FIELD_HANDLE fldHnd = info.compCompHnd->getFieldInClass(typeHnd, fieldVarDsc->lvFldOrdinal); - fieldName = eeGetFieldName(fldHnd, true, buffer, sizeof(buffer)); - } + unsigned fieldLclNum = varDsc->lvFieldLclStart + index; + LclVarDsc* fieldVarDsc = lvaGetDesc(fieldLclNum); printf("\n"); printf(" "); printIndent(indentStack); - printf(" %-6s V%02u.%s (offs=0x%02x) -> ", varTypeName(fieldVarDsc->TypeGet()), tree->GetLclNum(), - fieldName, fieldVarDsc->lvFldOffset); + printf(" %-6s %s -> ", varTypeName(fieldVarDsc->TypeGet()), fieldVarDsc->lvReason); gtDispLclVar(fieldLclNum); gtDispSsaName(fieldLclNum, tree->GetSsaNum(this, index), isDef); @@ -11947,6 +11931,7 @@ void Compiler::gtDispLocal(GenTreeLclVarCommon* tree, IndentStack* indentStack) printf(" "); fieldVarDsc->PrintVarReg(); } + if (fieldVarDsc->lvTracked && fgLocalVarLivenessDone && tree->IsLastUse(index)) { printf(" (last use)"); diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index a75aeab36a1fe..a1cfe959fd7b6 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -1664,22 +1664,6 @@ void Compiler::lvSetMinOptsDoNotEnreg() } } -//-------------------------------------------------------------------------------------------- -// lvaFieldOffsetCmp - a static compare function passed to jitstd::sort() by Compiler::StructPromotionHelper; -// compares fields' offsets. -// -// Arguments: -// field1 - pointer to the first field; -// field2 - pointer to the second field. -// -// Return value: -// 0 if the fields' offsets are equal, 1 if the first field has bigger offset, -1 otherwise. -// -bool Compiler::lvaFieldOffsetCmp::operator()(const lvaStructFieldInfo& field1, const lvaStructFieldInfo& field2) -{ - return field1.fldOffset < field2.fldOffset; -} - //------------------------------------------------------------------------ // StructPromotionHelper constructor. // @@ -1770,8 +1754,6 @@ bool Compiler::StructPromotionHelper::CanPromoteStructType(CORINFO_CLASS_HANDLE // lvaStructFieldInfo.fieldCnt is byte-sized assert(static_cast(MAX_NumOfFieldsInPromotableStruct) == MAX_NumOfFieldsInPromotableStruct); - bool containsGCpointers = false; - COMP_HANDLE compHandle = compiler->info.compCompHnd; unsigned structSize = compHandle->getClassSize(typeHnd); @@ -1780,17 +1762,9 @@ bool Compiler::StructPromotionHelper::CanPromoteStructType(CORINFO_CLASS_HANDLE return false; // struct is too large } - unsigned fieldCnt = compHandle->getClassNumInstanceFields(typeHnd); - if (fieldCnt == 0 || fieldCnt > MAX_NumOfFieldsInPromotableStruct) - { - return false; // struct must have between 1 and MAX_NumOfFieldsInPromotableStruct fields - } - - structPromotionInfo.fieldCnt = (unsigned char)fieldCnt; - DWORD typeFlags = compHandle->getClassAttribs(typeHnd); + DWORD typeFlags = compHandle->getClassAttribs(typeHnd); - bool overlappingFields = StructHasOverlappingFields(typeFlags); - if (overlappingFields) + if (StructHasOverlappingFields(typeFlags)) { return false; } @@ -1800,54 +1774,84 @@ bool Compiler::StructPromotionHelper::CanPromoteStructType(CORINFO_CLASS_HANDLE return false; } - // Don't struct promote if we have an CUSTOMLAYOUT flag on an HFA type - if (StructHasCustomLayout(typeFlags) && compiler->IsHfa(typeHnd)) - { - return false; - } - #ifdef TARGET_ARM // On ARM, we have a requirement on the struct alignment; see below. unsigned structAlignment = roundUp(compHandle->getClassAlignmentRequirement(typeHnd), TARGET_POINTER_SIZE); #endif // TARGET_ARM - // If we have "Custom Layout" then we might have an explicit Size attribute - // Managed C++ uses this for its structs, such C++ types will not contain GC pointers. - // - // The current VM implementation also incorrectly sets the CORINFO_FLG_CUSTOMLAYOUT - // whenever a managed value class contains any GC pointers. - // (See the comment for VMFLAG_NOT_TIGHTLY_PACKED in class.h) - // - // It is important to struct promote managed value classes that have GC pointers - // So we compute the correct value for "CustomLayout" here - // - if (StructHasCustomLayout(typeFlags) && ((typeFlags & CORINFO_FLG_CONTAINS_GC_PTR) == 0)) + // At most 1 (root node) + (4 promoted fields) + (each could be a wrapped primitive) + CORINFO_TYPE_LAYOUT_NODE treeNodes[1 + MAX_NumOfFieldsInPromotableStruct * 2]; + size_t numTreeNodes = ArrLen(treeNodes); + GetTypeLayoutResult result = compHandle->getTypeLayout(typeHnd, treeNodes, &numTreeNodes); + + if ((result != GetTypeLayoutResult::Success) || (numTreeNodes <= 1)) { - structPromotionInfo.customLayout = true; + return false; } - if (StructHasDontDigFieldsFlagSet(typeFlags)) + // TODO-Quirk: The old logic disallowed promotion for "custom layout" HFAs. + // The equivalent check now is the following, but it is quite meaningless. + if (treeNodes[0].hasSignificantPadding && compiler->IsHfa(typeHnd)) { - return CanConstructAndPromoteField(&structPromotionInfo); + return false; } + assert(treeNodes[0].size == structSize); + + structPromotionInfo.fieldCnt = 0; + unsigned fieldsSize = 0; - for (BYTE ordinal = 0; ordinal < fieldCnt; ++ordinal) + // Some notes on the following: + // 1. At most MAX_NumOfFieldsInPromotableStruct fields can be promoted + // 2. Recursive promotion is not enabled as the rest of the JIT cannot + // handle some of the patterns produced efficiently + // 3. The exception to the above is structs wrapping primitive types; we do + // support promoting those, but only through one layer of nesting (as a + // quirk -- this can probably be relaxed). + + for (size_t i = 1; i < numTreeNodes;) { - CORINFO_FIELD_HANDLE fieldHnd = compHandle->getFieldInClass(typeHnd, ordinal); - structPromotionInfo.fields[ordinal] = GetFieldInfo(fieldHnd, ordinal); - const lvaStructFieldInfo& fieldInfo = structPromotionInfo.fields[ordinal]; + if (structPromotionInfo.fieldCnt >= MAX_NumOfFieldsInPromotableStruct) + { + return false; + } + + const CORINFO_TYPE_LAYOUT_NODE& node = treeNodes[i]; + assert(node.parent == 0); + lvaStructFieldInfo& promField = structPromotionInfo.fields[structPromotionInfo.fieldCnt]; + INDEBUG(promField.diagFldHnd = node.diagFieldHnd); + + // Ensured by assertion on size above. + assert(FitsIn(node.offset)); + promField.fldOffset = (uint8_t)node.offset; - noway_assert(fieldInfo.fldOffset < structSize); + promField.fldOrdinal = structPromotionInfo.fieldCnt; + promField.fldSize = node.size; - if (fieldInfo.fldSize == 0) + structPromotionInfo.fieldCnt++; + + if (node.type == CORINFO_TYPE_VALUECLASS) { - // Not a scalar type. - return false; + var_types fldType = TryPromoteValueClassAsPrimitive(treeNodes, numTreeNodes, i); + if (fldType == TYP_UNDEF) + { + return false; + } + + promField.fldType = fldType; + promField.fldSIMDTypeHnd = node.simdTypeHnd; + AdvanceSubTree(treeNodes, numTreeNodes, &i); + } + else + { + promField.fldType = JITtype2varType(node.type); + i++; } - if ((fieldInfo.fldOffset % fieldInfo.fldSize) != 0) + fieldsSize += promField.fldSize; + + if ((promField.fldOffset % promField.fldSize) != 0) { // The code in Compiler::genPushArgList that reconstitutes // struct values on the stack from promoted fields expects @@ -1855,21 +1859,13 @@ bool Compiler::StructPromotionHelper::CanPromoteStructType(CORINFO_CLASS_HANDLE return false; } - if (varTypeIsGC(fieldInfo.fldType)) - { - containsGCpointers = true; - } - - // The end offset for this field should never be larger than our structSize. - noway_assert(fieldInfo.fldOffset + fieldInfo.fldSize <= structSize); - - fieldsSize += fieldInfo.fldSize; + noway_assert(promField.fldOffset + promField.fldSize <= structSize); #ifdef TARGET_ARM // On ARM, for struct types that don't use explicit layout, the alignment of the struct is // at least the max alignment of its fields. We take advantage of this invariant in struct promotion, // so verify it here. - if (fieldInfo.fldSize > structAlignment) + if (promField.fldSize > structAlignment) { // Don't promote vars whose struct types violates the invariant. (Alignment == size for primitives.) return false; @@ -1877,20 +1873,13 @@ bool Compiler::StructPromotionHelper::CanPromoteStructType(CORINFO_CLASS_HANDLE #endif // TARGET_ARM } - // If we saw any GC pointer or by-ref fields above then CORINFO_FLG_CONTAINS_GC_PTR or - // CORINFO_FLG_BYREF_LIKE has to be set! - noway_assert((containsGCpointers == false) || - ((typeFlags & (CORINFO_FLG_CONTAINS_GC_PTR | CORINFO_FLG_BYREF_LIKE)) != 0)); - - // Check if this promoted struct contains any holes. - assert(!overlappingFields); - if (fieldsSize != structSize) + if (fieldsSize != treeNodes[0].size) { - // If sizes do not match it means we have an overlapping fields or holes. - // Overlapping fields were rejected early, so here it can mean only holes. structPromotionInfo.containsHoles = true; } + structPromotionInfo.anySignificantPadding = treeNodes[0].hasSignificantPadding && structPromotionInfo.containsHoles; + // Cool, this struct is promotable. structPromotionInfo.canPromote = true; @@ -1898,59 +1887,134 @@ bool Compiler::StructPromotionHelper::CanPromoteStructType(CORINFO_CLASS_HANDLE } //-------------------------------------------------------------------------------------------- -// CanConstructAndPromoteField - checks if we can construct field types without asking about them directly. +// TryPromoteValueClassAsPrimitive - Attempt to promote a value type as a primitive type. // // Arguments: -// structPromotionInfo - struct promotion candidate information. +// treeNodes - Layout tree +// maxTreeNodes - Size of 'treeNodes' +// index - Index of layout tree node corresponding to the value class // // Return value: -// true if we can figure out the fields from available knowledge. +// Primitive type to promote the field as. // -// Notes: -// This is needed for AOT R2R compilation when we can't cross compilation bubble borders -// so we should not ask about fields that are not directly referenced. If we do VM will have -// to emit a type check for this field type but it does not have enough information about it. -// As a workaround for performance critical corner case: struct with 1 gcref, we try to construct -// the field information from indirect observations. -// -bool Compiler::StructPromotionHelper::CanConstructAndPromoteField(lvaStructPromotionInfo* structPromotionInfo) +var_types Compiler::StructPromotionHelper::TryPromoteValueClassAsPrimitive(CORINFO_TYPE_LAYOUT_NODE* treeNodes, + size_t maxTreeNodes, + size_t index) { - const CORINFO_CLASS_HANDLE typeHnd = structPromotionInfo->typeHnd; - const COMP_HANDLE compHandle = compiler->info.compCompHnd; - const DWORD typeFlags = compHandle->getClassAttribs(typeHnd); - if (structPromotionInfo->fieldCnt != 1) + assert(index < maxTreeNodes); + CORINFO_TYPE_LAYOUT_NODE& node = treeNodes[index]; + assert(node.type == CORINFO_TYPE_VALUECLASS); + + if (node.simdTypeHnd != NO_CLASS_HANDLE) { - // Can't find out values for several fields. - return false; + const char* namespaceName = nullptr; + const char* className = compiler->info.compCompHnd->getClassNameFromMetadata(node.simdTypeHnd, &namespaceName); + +#ifdef FEATURE_SIMD + if (compiler->usesSIMDTypes() && + (compiler->isRuntimeIntrinsicsNamespace(namespaceName) || compiler->isNumericsNamespace(namespaceName))) + { + unsigned simdSize; + CorInfoType simdBaseJitType = compiler->getBaseJitTypeAndSizeOfSIMDType(node.simdTypeHnd, &simdSize); + // We will only promote fields of SIMD types that fit into a SIMD register. + if (simdBaseJitType != CORINFO_TYPE_UNDEF) + { + if (compiler->structSizeMightRepresentSIMDType(simdSize)) + { + return compiler->getSIMDTypeForSize(simdSize); + } + } + } +#endif + +#ifdef TARGET_64BIT + // TODO-Quirk: Vector64 is a SIMD type with one 64-bit field, so when + // compiler->usesSIMDTypes() == false, it used to be promoted as a long + // field. + if (compiler->isRuntimeIntrinsicsNamespace(namespaceName) && (strcmp(className, "Vector64`1") == 0)) + { + return TYP_LONG; + } +#endif } - if ((typeFlags & CORINFO_FLG_CONTAINS_GC_PTR) == 0) + + // Check for a single primitive wrapper. + if (node.numFields != 1) { - // Can't find out type of a non-gc field. - return false; + return TYP_UNDEF; } - const unsigned structSize = compHandle->getClassSize(typeHnd); - if (structSize != TARGET_POINTER_SIZE) + if (index + 1 >= maxTreeNodes) { - return false; + return TYP_UNDEF; } - assert(!structPromotionInfo->containsHoles); - assert(!structPromotionInfo->customLayout); - lvaStructFieldInfo& fldInfo = structPromotionInfo->fields[0]; + CORINFO_TYPE_LAYOUT_NODE& primNode = treeNodes[index + 1]; - fldInfo.fldHnd = compHandle->getFieldInClass(typeHnd, 0); + // Do not promote if the field is not a primitive. + // TODO-CQ: We could likely permit recursive primitive wrappers here quite easily. + if (primNode.type == CORINFO_TYPE_VALUECLASS) + { + return TYP_UNDEF; + } - // We should not read it anymore. - fldInfo.fldTypeHnd = 0; + // Do not promote if the single field is not aligned at its natural boundary within + // the struct field. + if (primNode.offset != node.offset) + { + return TYP_UNDEF; + } - fldInfo.fldOffset = 0; - fldInfo.fldOrdinal = 0; - fldInfo.fldSize = TARGET_POINTER_SIZE; - fldInfo.fldType = TYP_REF; + // Insist this wrapped field occupies all of its parent storage. + if (primNode.size != node.size) + { + JITDUMP("Promotion blocked: struct contains struct field with one field," + " but that field is not the same size as its parent.\n"); + return TYP_UNDEF; + } - structPromotionInfo->canPromote = true; - return true; + // Only promote up to pointer sized fields. + // TODO-CQ: Right now we only promote an actual SIMD typed field, which would cause + // a nested SIMD type to fail promotion. + if (primNode.size > TARGET_POINTER_SIZE) + { + JITDUMP("Promotion blocked: struct contains struct field with one field," + " but that field has invalid size.\n"); + return TYP_UNDEF; + } + + if ((primNode.size != TARGET_POINTER_SIZE) && ((node.offset % primNode.size) != 0)) + { + JITDUMP("Promotion blocked: struct contains struct field with one field," + " but the outer struct offset %u is not a multiple of the inner field size %u.\n", + node.offset, primNode.size); + return TYP_UNDEF; + } + + return JITtype2varType(primNode.type); +} + +//-------------------------------------------------------------------------------------------- +// AdvanceSubTree - Skip over a tree node and all its children. +// +// Arguments: +// treeNodes - array of type layout nodes, stored in preorder. +// maxTreeNodes - size of 'treeNodes' +// index - [in, out] Index pointing to root of subtree to skip. +// +// Remarks: +// Requires the tree nodes to be stored in preorder (as guaranteed by getTypeLayout). +// +void Compiler::StructPromotionHelper::AdvanceSubTree(CORINFO_TYPE_LAYOUT_NODE* treeNodes, + size_t maxTreeNodes, + size_t* index) +{ + size_t parIndex = *index; + (*index)++; + while ((*index < maxTreeNodes) && (treeNodes[*index].parent >= parIndex)) + { + (*index)++; + } } //-------------------------------------------------------------------------------------------- @@ -2048,7 +2112,8 @@ bool Compiler::StructPromotionHelper::CanPromoteStructVar(unsigned lclNum) // If we have a register-passed struct with mixed non-opaque SIMD types (i.e. with defined fields) // and non-SIMD types, we don't currently handle that case in the prolog, so we can't promote. else if ((fieldCnt > 1) && varTypeIsStruct(fieldType) && - !compiler->isOpaqueSIMDType(structPromotionInfo.fields[i].fldTypeHnd)) + (structPromotionInfo.fields[i].fldSIMDTypeHnd != NO_CLASS_HANDLE) && + !compiler->isOpaqueSIMDType(structPromotionInfo.fields[i].fldSIMDTypeHnd)) { canPromote = false; } @@ -2139,9 +2204,9 @@ bool Compiler::StructPromotionHelper::ShouldPromoteStructVar(unsigned lclNum) structPromotionInfo.fieldCnt, varDsc->lvFieldAccessed); shouldPromote = false; } - else if (varDsc->lvIsMultiRegRet && structPromotionInfo.containsHoles && structPromotionInfo.customLayout) + else if (varDsc->lvIsMultiRegRet && structPromotionInfo.anySignificantPadding) { - JITDUMP("Not promoting multi-reg returned struct local V%02u with holes.\n", lclNum); + JITDUMP("Not promoting multi-reg returned struct local V%02u with significant padding.\n", lclNum); shouldPromote = false; } #if defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) @@ -2162,9 +2227,9 @@ bool Compiler::StructPromotionHelper::ShouldPromoteStructVar(unsigned lclNum) // multiple registers? if (compiler->lvaIsMultiregStruct(varDsc, compiler->info.compIsVarArgs)) { - if (structPromotionInfo.containsHoles && structPromotionInfo.customLayout) + if (structPromotionInfo.anySignificantPadding) { - JITDUMP("Not promoting multi-reg struct local V%02u with holes.\n", lclNum); + JITDUMP("Not promoting multi-reg struct local V%02u with significant padding.\n", lclNum); shouldPromote = false; } else if ((structPromotionInfo.fieldCnt != 2) && @@ -2235,142 +2300,13 @@ void Compiler::StructPromotionHelper::SortStructFields() if (!structPromotionInfo.fieldsSorted) { jitstd::sort(structPromotionInfo.fields, structPromotionInfo.fields + structPromotionInfo.fieldCnt, - lvaFieldOffsetCmp()); + [](const lvaStructFieldInfo& lhs, const lvaStructFieldInfo& rhs) { + return lhs.fldOffset < rhs.fldOffset; + }); structPromotionInfo.fieldsSorted = true; } } -//-------------------------------------------------------------------------------------------- -// GetFieldInfo - get struct field information. -// Arguments: -// fieldHnd - field handle to get info for; -// ordinal - field ordinal. -// -// Return value: -// field information. -// -Compiler::lvaStructFieldInfo Compiler::StructPromotionHelper::GetFieldInfo(CORINFO_FIELD_HANDLE fieldHnd, BYTE ordinal) -{ - lvaStructFieldInfo fieldInfo; - fieldInfo.fldHnd = fieldHnd; - - unsigned fldOffset = compiler->info.compCompHnd->getFieldOffset(fieldInfo.fldHnd); - fieldInfo.fldOffset = (BYTE)fldOffset; - - fieldInfo.fldOrdinal = ordinal; - CorInfoType corType = compiler->info.compCompHnd->getFieldType(fieldInfo.fldHnd, &fieldInfo.fldTypeHnd); - fieldInfo.fldType = JITtype2varType(corType); - fieldInfo.fldSize = genTypeSize(fieldInfo.fldType); - -#ifdef FEATURE_SIMD - // Check to see if this is a SIMD type. - // We will only check this if we have already found a SIMD type, which will be true if - // we have encountered any SIMD intrinsics. - if (compiler->usesSIMDTypes() && (fieldInfo.fldSize == 0) && compiler->isSIMDorHWSIMDClass(fieldInfo.fldTypeHnd)) - { - unsigned simdSize; - CorInfoType simdBaseJitType = compiler->getBaseJitTypeAndSizeOfSIMDType(fieldInfo.fldTypeHnd, &simdSize); - // We will only promote fields of SIMD types that fit into a SIMD register. - if (simdBaseJitType != CORINFO_TYPE_UNDEF) - { - if (compiler->structSizeMightRepresentSIMDType(simdSize)) - { - fieldInfo.fldType = compiler->getSIMDTypeForSize(simdSize); - fieldInfo.fldSize = simdSize; - } - } - } -#endif // FEATURE_SIMD - - if (fieldInfo.fldSize == 0) - { - TryPromoteStructField(fieldInfo); - } - - return fieldInfo; -} - -//-------------------------------------------------------------------------------------------- -// TryPromoteStructField - checks that this struct's field is a struct that can be promoted as scalar type -// aligned at its natural boundary. Promotes the field as a scalar if the check succeeded. -// -// Arguments: -// fieldInfo - information about the field in the outer struct. -// -// Return value: -// true if the internal struct was promoted. -// -bool Compiler::StructPromotionHelper::TryPromoteStructField(lvaStructFieldInfo& fieldInfo) -{ - if (fieldInfo.fldType != TYP_STRUCT) - { - return false; - } - - COMP_HANDLE compHandle = compiler->info.compCompHnd; - - // Do not promote if the struct field in turn has more than one field. - if (compHandle->getClassNumInstanceFields(fieldInfo.fldTypeHnd) != 1) - { - return false; - } - - // Do not promote if the single field is not aligned at its natural boundary within - // the struct field. - CORINFO_FIELD_HANDLE innerFieldHndl = compHandle->getFieldInClass(fieldInfo.fldTypeHnd, 0); - unsigned innerFieldOffset = compHandle->getFieldOffset(innerFieldHndl); - if (innerFieldOffset != 0) - { - return false; - } - - CorInfoType fieldCorType = compHandle->getFieldType(innerFieldHndl); - var_types fieldVarType = JITtype2varType(fieldCorType); - unsigned fieldSize = genTypeSize(fieldVarType); - - // Do not promote if the field is not a primitive type or is not properly aligned. - // - // TODO-CQ: Right now we only promote an actual SIMD typed field, which would cause - // a nested SIMD type to fail promotion. - if (fieldSize == 0 || fieldSize > TARGET_POINTER_SIZE) - { - JITDUMP("Promotion blocked: struct contains struct field with one field," - " but that field has invalid size.\n"); - return false; - } - - if (fieldSize != TARGET_POINTER_SIZE) - { - unsigned outerFieldOffset = compHandle->getFieldOffset(fieldInfo.fldHnd); - - if ((outerFieldOffset % fieldSize) != 0) - { - JITDUMP("Promotion blocked: struct contains struct field with one field," - " but the outer struct offset %u is not a multiple of the inner field size %u.\n", - outerFieldOffset, fieldSize); - return false; - } - } - - // Insist this wrapped field occupy all of its parent storage. - unsigned innerStructSize = compHandle->getClassSize(fieldInfo.fldTypeHnd); - - if (fieldSize != innerStructSize) - { - JITDUMP("Promotion blocked: struct contains struct field with one field," - " but that field is not the same size as its parent.\n"); - return false; - } - - // Retype the field as the type of the single field of the struct. - // This is a hack that allows us to promote such fields before we support recursive struct promotion - // (tracked by #10019). - fieldInfo.fldType = fieldVarType; - fieldInfo.fldSize = fieldSize; - - return true; -} - //-------------------------------------------------------------------------------------------- // PromoteStructVar - promote struct variable. // @@ -2387,11 +2323,11 @@ void Compiler::StructPromotionHelper::PromoteStructVar(unsigned lclNum) assert(varDsc->GetLayout()->GetClassHandle() == structPromotionInfo.typeHnd); assert(structPromotionInfo.canPromote); - varDsc->lvFieldCnt = structPromotionInfo.fieldCnt; - varDsc->lvFieldLclStart = compiler->lvaCount; - varDsc->lvPromoted = true; - varDsc->lvContainsHoles = structPromotionInfo.containsHoles; - varDsc->lvCustomLayout = structPromotionInfo.customLayout; + varDsc->lvFieldCnt = structPromotionInfo.fieldCnt; + varDsc->lvFieldLclStart = compiler->lvaCount; + varDsc->lvPromoted = true; + varDsc->lvContainsHoles = structPromotionInfo.containsHoles; + varDsc->lvAnySignificantPadding = structPromotionInfo.anySignificantPadding; #ifdef DEBUG // Don't stress this in LCL_FLD stress. @@ -2424,10 +2360,10 @@ void Compiler::StructPromotionHelper::PromoteStructVar(unsigned lclNum) // Now grab the temp for the field local. #ifdef DEBUG + char buf[200]; char fieldNameBuffer[128]; const char* fieldName = - compiler->eeGetFieldName(pFieldInfo->fldHnd, false, fieldNameBuffer, sizeof(fieldNameBuffer)); - char buf[200]; + compiler->eeGetFieldName(pFieldInfo->diagFldHnd, false, fieldNameBuffer, sizeof(fieldNameBuffer)); sprintf_s(buf, sizeof(buf), "field V%02u.%s (fldOffset=0x%x)", lclNum, fieldName, pFieldInfo->fldOffset); // We need to copy 'buf' as lvaGrabTemp() below caches a copy to its argument. @@ -2533,7 +2469,7 @@ void Compiler::StructPromotionHelper::PromoteStructVar(unsigned lclNum) if (GlobalJitOptions::compFeatureHfa && (pFieldInfo->fldSize <= MAX_PASS_MULTIREG_BYTES)) { // hfaType is set to float, double or SIMD type if it is an HFA, otherwise TYP_UNDEF - var_types hfaType = compiler->GetHfaType(pFieldInfo->fldTypeHnd); + var_types hfaType = compiler->GetHfaType(pFieldInfo->fldSIMDTypeHnd); if (varTypeIsValidHfaType(hfaType)) { fieldVarDsc->SetHfaType(hfaType); @@ -3052,12 +2988,6 @@ void Compiler::makeExtraStructQueries(CORINFO_CLASS_HANDLE structHandle, int lev assert(structHandle != NO_CLASS_HANDLE); (void)typGetObjLayout(structHandle); DWORD typeFlags = info.compCompHnd->getClassAttribs(structHandle); - if (StructHasDontDigFieldsFlagSet(typeFlags)) - { - // In AOT ReadyToRun compilation, don't query fields of types - // outside of the current version bubble. - return; - } unsigned const fieldCnt = info.compCompHnd->getClassNumInstanceFields(structHandle); impNormStructType(structHandle); @@ -3065,6 +2995,14 @@ void Compiler::makeExtraStructQueries(CORINFO_CLASS_HANDLE structHandle, int lev GetHfaType(structHandle); #endif + // In a lambda since this requires a lot of stack and this function is recursive. + auto queryLayout = [this, structHandle]() { + CORINFO_TYPE_LAYOUT_NODE nodes[256]; + size_t numNodes = ArrLen(nodes); + info.compCompHnd->getTypeLayout(structHandle, nodes, &numNodes); + }; + queryLayout(); + // Bypass fetching instance fields of ref classes for now, // as it requires traversing the class hierarchy. // @@ -3073,18 +3011,24 @@ void Compiler::makeExtraStructQueries(CORINFO_CLASS_HANDLE structHandle, int lev return; } - for (unsigned int i = 0; i < fieldCnt; i++) + // In R2R we cannot query arbitrary information about struct fields, so + // skip it there. Note that the getTypeLayout call above is enough to cover + // us for promotion at least. + if (!opts.IsReadyToRun()) { - CORINFO_FIELD_HANDLE fieldHandle = info.compCompHnd->getFieldInClass(structHandle, i); - unsigned fldOffset = info.compCompHnd->getFieldOffset(fieldHandle); - CORINFO_CLASS_HANDLE fieldClassHandle = NO_CLASS_HANDLE; - CorInfoType fieldCorType = info.compCompHnd->getFieldType(fieldHandle, &fieldClassHandle); - var_types fieldVarType = JITtype2varType(fieldCorType); - if (fieldClassHandle != NO_CLASS_HANDLE) + for (unsigned int i = 0; i < fieldCnt; i++) { - if (varTypeIsStruct(fieldVarType)) + CORINFO_FIELD_HANDLE fieldHandle = info.compCompHnd->getFieldInClass(structHandle, i); + unsigned fldOffset = info.compCompHnd->getFieldOffset(fieldHandle); + CORINFO_CLASS_HANDLE fieldClassHandle = NO_CLASS_HANDLE; + CorInfoType fieldCorType = info.compCompHnd->getFieldType(fieldHandle, &fieldClassHandle); + var_types fieldVarType = JITtype2varType(fieldCorType); + if (fieldClassHandle != NO_CLASS_HANDLE) { - makeExtraStructQueries(fieldClassHandle, level - 1); + if (varTypeIsStruct(fieldVarType)) + { + makeExtraStructQueries(fieldClassHandle, level - 1); + } } } } @@ -7678,46 +7622,30 @@ void Compiler::lvaDumpEntry(unsigned lclNum, FrameLayoutState curState, size_t r printf(" unsafe-buffer"); } } - if (varDsc->lvIsStructField) - { - LclVarDsc* parentvarDsc = lvaGetDesc(varDsc->lvParentLcl); -#if !defined(TARGET_64BIT) - if (varTypeIsLong(parentvarDsc)) - { - bool isLo = (lclNum == parentvarDsc->lvFieldLclStart); - printf(" V%02u.%s(offs=0x%02x)", varDsc->lvParentLcl, isLo ? "lo" : "hi", isLo ? 0 : genTypeSize(TYP_INT)); - } - else -#endif // !defined(TARGET_64BIT) - { - CORINFO_CLASS_HANDLE typeHnd = parentvarDsc->GetLayout()->GetClassHandle(); - CORINFO_FIELD_HANDLE fldHnd = info.compCompHnd->getFieldInClass(typeHnd, varDsc->lvFldOrdinal); - - char buffer[128]; - printf(" V%02u.%s(offs=0x%02x)", varDsc->lvParentLcl, eeGetFieldName(fldHnd, false, buffer, sizeof(buffer)), - varDsc->lvFldOffset); - - lvaPromotionType promotionType = lvaGetPromotionType(parentvarDsc); - switch (promotionType) - { - case PROMOTION_TYPE_NONE: - printf(" P-NONE"); - break; - case PROMOTION_TYPE_DEPENDENT: - printf(" P-DEP"); - break; - case PROMOTION_TYPE_INDEPENDENT: - printf(" P-INDEP"); - break; - } - } - } if (varDsc->lvReason != nullptr) { printf(" \"%s\"", varDsc->lvReason); } + if (varDsc->lvIsStructField) + { + LclVarDsc* parentVarDsc = lvaGetDesc(varDsc->lvParentLcl); + lvaPromotionType promotionType = lvaGetPromotionType(parentVarDsc); + switch (promotionType) + { + case PROMOTION_TYPE_NONE: + printf(" P-NONE"); + break; + case PROMOTION_TYPE_DEPENDENT: + printf(" P-DEP"); + break; + case PROMOTION_TYPE_INDEPENDENT: + printf(" P-INDEP"); + break; + } + } + printf("\n"); } diff --git a/src/coreclr/jit/liveness.cpp b/src/coreclr/jit/liveness.cpp index 15875075ebcea..d45d90b90df2b 100644 --- a/src/coreclr/jit/liveness.cpp +++ b/src/coreclr/jit/liveness.cpp @@ -1706,8 +1706,8 @@ bool Compiler::fgComputeLifeUntrackedLocal(VARSET_TP& life, if (isDef && !anyFieldLive && !opts.MinOpts()) { // Do not consider this store dead if the parent local variable is an address exposed local or - // if the struct has a custom layout and holes. - return !(varDsc.IsAddressExposed() || (varDsc.lvCustomLayout && varDsc.lvContainsHoles)); + // if the struct has any significant padding we must retain the value of. + return !varDsc.IsAddressExposed() && !varDsc.lvAnySignificantPadding; } return false; diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 8e4c6612b41a1..aa336cb59e046 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -14719,12 +14719,12 @@ PhaseStatus Compiler::fgRetypeImplicitByRefArgs() } // Copy the struct promotion annotations to the new temp. - LclVarDsc* newVarDsc = lvaGetDesc(newLclNum); - newVarDsc->lvPromoted = true; - newVarDsc->lvFieldLclStart = varDsc->lvFieldLclStart; - newVarDsc->lvFieldCnt = varDsc->lvFieldCnt; - newVarDsc->lvContainsHoles = varDsc->lvContainsHoles; - newVarDsc->lvCustomLayout = varDsc->lvCustomLayout; + LclVarDsc* newVarDsc = lvaGetDesc(newLclNum); + newVarDsc->lvPromoted = true; + newVarDsc->lvFieldLclStart = varDsc->lvFieldLclStart; + newVarDsc->lvFieldCnt = varDsc->lvFieldCnt; + newVarDsc->lvContainsHoles = varDsc->lvContainsHoles; + newVarDsc->lvAnySignificantPadding = varDsc->lvAnySignificantPadding; #ifdef DEBUG newVarDsc->lvKeepType = true; #endif // DEBUG diff --git a/src/coreclr/jit/morphblock.cpp b/src/coreclr/jit/morphblock.cpp index 074abb1f77cd3..cb1fe5106d31e 100644 --- a/src/coreclr/jit/morphblock.cpp +++ b/src/coreclr/jit/morphblock.cpp @@ -359,10 +359,9 @@ void MorphInitBlockHelper::TryInitFieldByField() return; } - if (destLclVar->lvCustomLayout && destLclVar->lvContainsHoles) + if (destLclVar->lvAnySignificantPadding) { - // TODO-1stClassStructs: there are no reasons for this pessimization, delete it. - JITDUMP(" dest has custom layout and contains holes.\n"); + JITDUMP(" dest has significant padding.\n"); return; } @@ -776,17 +775,17 @@ void MorphCopyBlockHelper::MorphStructCases() } // Can we use field by field assignment for the dest? - if (m_dstDoFldStore && m_dstVarDsc->lvCustomLayout && m_dstVarDsc->lvContainsHoles) + if (m_dstDoFldStore && m_dstVarDsc->lvAnySignificantPadding) { - JITDUMP(" dest contains custom layout and contains holes"); + JITDUMP(" dest has significant padding"); // C++ style CopyBlock with holes requiresCopyBlock = true; } // Can we use field by field assignment for the src? - if (m_srcDoFldStore && m_srcVarDsc->lvCustomLayout && m_srcVarDsc->lvContainsHoles) + if (m_srcDoFldStore && m_srcVarDsc->lvAnySignificantPadding) { - JITDUMP(" src contains custom layout and contains holes"); + JITDUMP(" src has significant padding"); // C++ style CopyBlock with holes requiresCopyBlock = true; } diff --git a/src/coreclr/jit/promotion.cpp b/src/coreclr/jit/promotion.cpp index daf3f21f13bcc..aff66b69dfab1 100644 --- a/src/coreclr/jit/promotion.cpp +++ b/src/coreclr/jit/promotion.cpp @@ -1198,7 +1198,6 @@ class LocalsUseVisitor : public GenTreeVisitor } #endif - JITDUMP("Computing unpromoted remainder for V%02u\n", agg->LclNum); StructSegments unpromotedParts = m_prom->SignificantSegments(m_compiler->lvaGetDesc(agg->LclNum)->GetLayout()); for (Replacement& rep : reps) @@ -1206,7 +1205,7 @@ class LocalsUseVisitor : public GenTreeVisitor unpromotedParts.Subtract(StructSegments::Segment(rep.Offset, rep.Offset + genTypeSize(rep.AccessType))); } - JITDUMP(" Remainder: "); + JITDUMP(" Unpromoted remainder: "); DBEXEC(m_compiler->verbose, unpromotedParts.Dump()); JITDUMP("\n\n"); @@ -1715,65 +1714,33 @@ StructSegments Promotion::SignificantSegments(ClassLayout* layout) COMP_HANDLE compHnd = m_compiler->info.compCompHnd; - bool significantPadding; + StructSegments segments(m_compiler->getAllocator(CMK_Promotion)); + if (layout->IsBlockLayout()) { - significantPadding = true; - JITDUMP(" Block op has significant padding due to block layout\n"); + segments.Add(StructSegments::Segment(0, layout->GetSize())); } else { - uint32_t attribs = compHnd->getClassAttribs(layout->GetClassHandle()); - if ((attribs & CORINFO_FLG_INDEXABLE_FIELDS) != 0) - { - significantPadding = true; - JITDUMP(" Block op has significant padding due to indexable fields\n"); - } - else if ((attribs & CORINFO_FLG_DONT_DIG_FIELDS) != 0) - { - significantPadding = true; - JITDUMP(" Block op has significant padding due to CORINFO_FLG_DONT_DIG_FIELDS\n"); - } - else if (((attribs & CORINFO_FLG_CUSTOMLAYOUT) != 0) && ((attribs & CORINFO_FLG_CONTAINS_GC_PTR) == 0)) + CORINFO_TYPE_LAYOUT_NODE nodes[256]; + size_t numNodes = ArrLen(nodes); + GetTypeLayoutResult result = compHnd->getTypeLayout(layout->GetClassHandle(), nodes, &numNodes); + + if (result != GetTypeLayoutResult::Success) { - significantPadding = true; - JITDUMP(" Block op has significant padding due to CUSTOMLAYOUT without GC pointers\n"); + segments.Add(StructSegments::Segment(0, layout->GetSize())); } else { - significantPadding = false; - } - } - - StructSegments segments(m_compiler->getAllocator(CMK_Promotion)); - - if (significantPadding) - { - segments.Add(StructSegments::Segment(0, layout->GetSize())); - } - else - { - unsigned numFields = compHnd->getClassNumInstanceFields(layout->GetClassHandle()); - for (unsigned i = 0; i < numFields; i++) - { - CORINFO_FIELD_HANDLE fieldHnd = compHnd->getFieldInClass(layout->GetClassHandle(), (int)i); - unsigned fldOffset = compHnd->getFieldOffset(fieldHnd); - CORINFO_CLASS_HANDLE fieldClassHandle; - CorInfoType corType = compHnd->getFieldType(fieldHnd, &fieldClassHandle); - var_types varType = JITtype2varType(corType); - unsigned size = genTypeSize(varType); - if (size == 0) + for (size_t i = 0; i < numNodes; i++) { - // TODO-CQ: Recursively handle padding in sub structures - // here. Might be better to introduce a single JIT-EE call - // to query the significant segments -- that would also be - // usable by R2R even outside the version bubble in many - // cases. - size = compHnd->getClassSize(fieldClassHandle); - assert(size != 0); + const CORINFO_TYPE_LAYOUT_NODE& node = nodes[i]; + if ((node.type != CORINFO_TYPE_VALUECLASS) || (node.simdTypeHnd != NO_CLASS_HANDLE) || + node.hasSignificantPadding) + { + segments.Add(StructSegments::Segment(node.offset, node.offset + node.size)); + } } - - segments.Add(StructSegments::Segment(fldOffset, fldOffset + size)); } } diff --git a/src/coreclr/jit/promotiondecomposition.cpp b/src/coreclr/jit/promotiondecomposition.cpp index f90f0849484cc..8e75314a0bbf9 100644 --- a/src/coreclr/jit/promotiondecomposition.cpp +++ b/src/coreclr/jit/promotiondecomposition.cpp @@ -288,7 +288,7 @@ class DecompositionPlan #ifdef DEBUG if (m_compiler->verbose) { - printf(" Remainder: "); + printf(" Block op remainder: "); segments.Dump(); printf("\n"); } diff --git a/src/coreclr/jit/simd.cpp b/src/coreclr/jit/simd.cpp index 3c9c7f57c7edd..d69730ad520ed 100644 --- a/src/coreclr/jit/simd.cpp +++ b/src/coreclr/jit/simd.cpp @@ -145,9 +145,15 @@ unsigned Compiler::getSIMDInitTempVarNum(var_types simdType) // If the size of the struct is already known call structSizeMightRepresentSIMDType // to determine if this api needs to be called. // +// The type handle passed here can only be used in a subset of JIT-EE calls +// since it may be called by promotion during prejit of a method that does +// not version with SPC. See CORINFO_TYPE_LAYOUT_NODE for the contract on +// the supported JIT-EE calls. +// // TODO-Throughput: current implementation parses class name to find base type. Change // this when we implement SIMD intrinsic identification for the final // product. +// CorInfoType Compiler::getBaseJitTypeAndSizeOfSIMDType(CORINFO_CLASS_HANDLE typeHnd, unsigned* sizeBytes /*= nullptr */) { if (m_simdHandleCache == nullptr) diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 5c3eb76470fa3..579e933d4e2f4 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1983,10 +1983,6 @@ private uint getClassAttribsInternal(TypeDesc type) if (metadataType.IsByRefLike) result |= CorInfoFlag.CORINFO_FLG_BYREF_LIKE; - // The CLR has more complicated rules around CUSTOMLAYOUT, but this will do. - if (metadataType.IsExplicitLayout || (metadataType.IsSequentialLayout && metadataType.GetClassLayout().Size != 0) || metadataType.IsWellKnownType(WellKnownType.TypedReference)) - result |= CorInfoFlag.CORINFO_FLG_CUSTOMLAYOUT; - if (metadataType.IsUnsafeValueType) result |= CorInfoFlag.CORINFO_FLG_UNSAFE_VALUECLASS; @@ -2015,7 +2011,18 @@ private uint getClassAttribsInternal(TypeDesc type) result |= CorInfoFlag.CORINFO_FLG_CONTAINS_GC_PTR; if (metadataType.IsBeforeFieldInit) - result |= CorInfoFlag.CORINFO_FLG_BEFOREFIELDINIT; + { + bool makeBeforeFieldInit = true; + +#if READYTORUN + makeBeforeFieldInit &= _compilation.CompilationModuleGroup.VersionsWithType(type); +#endif + + if (makeBeforeFieldInit) + { + result |= CorInfoFlag.CORINFO_FLG_BEFOREFIELDINIT; + } + } // Assume overlapping fields for explicit layout. if (metadataType.IsExplicitLayout) @@ -2025,15 +2032,6 @@ private uint getClassAttribsInternal(TypeDesc type) result |= CorInfoFlag.CORINFO_FLG_ABSTRACT; } -#if READYTORUN - if (!_compilation.CompilationModuleGroup.VersionsWithType(type)) - { - // Prevent the JIT from drilling into types outside of the current versioning bubble - result |= CorInfoFlag.CORINFO_FLG_DONT_DIG_FIELDS; - result &= ~CorInfoFlag.CORINFO_FLG_BEFOREFIELDINIT; - } -#endif - return (uint)result; } @@ -2354,6 +2352,168 @@ private uint getClassNumInstanceFields(CORINFO_CLASS_STRUCT_* cls) throw new InvalidOperationException(); } + private GetTypeLayoutResult GetTypeLayoutHelper(MetadataType type, uint parentIndex, uint baseOffs, FieldDesc field, CORINFO_TYPE_LAYOUT_NODE* treeNodes, nuint maxTreeNodes, nuint* numTreeNodes) + { + if (*numTreeNodes >= maxTreeNodes) + { + return GetTypeLayoutResult.Partial; + } + + uint structNodeIndex = (uint)(*numTreeNodes)++; + CORINFO_TYPE_LAYOUT_NODE* parNode = &treeNodes[structNodeIndex]; + parNode->simdTypeHnd = null; + parNode->diagFieldHnd = field == null ? null : ObjectToHandle(field); + parNode->parent = parentIndex; + parNode->offset = baseOffs; + parNode->size = (uint)type.GetElementSize().AsInt; + parNode->numFields = 0; + parNode->type = CorInfoType.CORINFO_TYPE_VALUECLASS; + parNode->hasSignificantPadding = false; + +#if READYTORUN + // The contract of getTypeLayout is carefully crafted to still + // allow us to return hints about fields even for types outside the + // version bubble. The general idea is that the JIT does not use + // the information returned by this function to create new + // optimizations out of the blue, but only as a hint to optimize + // existing field uses more thoroughly. In particular the uses of + // fields outside the version bubble are only non-opaque and + // amenable to the optimizations that this unlocks if they already + // went through EncodeFieldBaseOffset. + // + if (!_compilation.IsLayoutFixedInCurrentVersionBubble(type)) + { + // For types without fixed layout the JIT is not allowed to + // rely on padding bits being insignificant, since fields could + // be added later inside that padding without invalidating the + // generated code. + parNode->hasSignificantPadding = true; + } +#endif + + if (type.IsExplicitLayout || (type.IsSequentialLayout && type.GetClassLayout().Size != 0) || type.IsInlineArray) + { + if (!type.ContainsGCPointers && !type.IsByRefLike) + { + parNode->hasSignificantPadding = true; + } + } + + // The intrinsic SIMD/HW SIMD types have a lot of fields that the JIT does + // not care about since they are considered primitives by the JIT. + if (type.IsIntrinsic) + { + string ns = type.Namespace; + if (ns == "System.Runtime.Intrinsics" || ns == "System.Numerics") + { + parNode->simdTypeHnd = ObjectToHandle(type); + if (parentIndex != uint.MaxValue) + { +#if READYTORUN + if (NeedsTypeLayoutCheck(type)) + { + // We cannot allow the JIT to call getClassSize for + // arbitrary types of fields as it will insert a fixup + // that we may not be able to encode. We could skip the + // field, but that will make prejit promotion different + // from the runtime promotion. We could also change the + // JIT to avoid calling getClassSize and just use the + // size from the returned node, but for that we would + // need to be sure that the type layout check fixup + // added in getTypeLayout is sufficient to guarantee + // the size of all these intrinsically handled SIMD + // types. + return GetTypeLayoutResult.Failure; + } +#endif + + return GetTypeLayoutResult.Success; + } + } + } + + foreach (FieldDesc fd in type.GetFields()) + { + if (fd.IsStatic) + continue; + + parNode->numFields++; + + Debug.Assert(fd.Offset != FieldAndOffset.InvalidOffset); + + TypeDesc fieldType = fd.FieldType; + CorInfoType corInfoType = asCorInfoType(fieldType); + if (corInfoType == CorInfoType.CORINFO_TYPE_VALUECLASS) + { + Debug.Assert(fieldType is MetadataType); + GetTypeLayoutResult result = GetTypeLayoutHelper((MetadataType)fieldType, structNodeIndex, baseOffs + (uint)fd.Offset.AsInt, fd, treeNodes, maxTreeNodes, numTreeNodes); + if (result != GetTypeLayoutResult.Success) + return result; + } + else + { + if (*numTreeNodes >= maxTreeNodes) + return GetTypeLayoutResult.Partial; + + CORINFO_TYPE_LAYOUT_NODE* treeNode = &treeNodes[(*numTreeNodes)++]; + treeNode->simdTypeHnd = null; + treeNode->diagFieldHnd = ObjectToHandle(fd); + treeNode->parent = structNodeIndex; + treeNode->offset = baseOffs + (uint)fd.Offset.AsInt; + treeNode->size = (uint)fieldType.GetElementSize().AsInt; + treeNode->numFields = 0; + treeNode->type = corInfoType; + treeNode->hasSignificantPadding = false; + } + + if (type.IsInlineArray) + { + nuint treeNodeEnd = *numTreeNodes; + int elemSize = fieldType.GetElementSize().AsInt; + int arrSize = type.GetElementSize().AsInt; + + for (int elemOffset = elemSize; elemOffset < arrSize; elemOffset += elemSize) + { + for (nuint templateTreeNodeIndex = structNodeIndex + 1; templateTreeNodeIndex < treeNodeEnd; templateTreeNodeIndex++) + { + if (*numTreeNodes >= maxTreeNodes) + return GetTypeLayoutResult.Partial; + + CORINFO_TYPE_LAYOUT_NODE* treeNode = &treeNodes[(*numTreeNodes)++]; + *treeNode = treeNodes[templateTreeNodeIndex]; + treeNode->offset += (uint)elemOffset; + + parNode->numFields++; + } + } + } + } + + return GetTypeLayoutResult.Success; + } + + private GetTypeLayoutResult getTypeLayout(CORINFO_CLASS_STRUCT_* typeHnd, CORINFO_TYPE_LAYOUT_NODE* treeNodes, UIntPtr* numTreeNodes) + { + TypeDesc type = HandleToObject(typeHnd); + + if (type is not MetadataType metadataType || !type.IsValueType) + return GetTypeLayoutResult.Failure; + + nuint maxFields = *numTreeNodes; + *numTreeNodes = 0; + GetTypeLayoutResult result = GetTypeLayoutHelper(metadataType, uint.MaxValue, 0, null, treeNodes, maxFields, numTreeNodes); + +#if READYTORUN + if (NeedsTypeLayoutCheck(type)) + { + ISymbolNode node = _compilation.SymbolNodeFactory.CheckTypeLayout(type); + AddPrecodeFixup(node); + } +#endif + + return result; + } + private bool checkMethodModifier(CORINFO_METHOD_STRUCT_* hMethod, byte* modifier, bool fOptional) { throw new NotImplementedException("checkMethodModifier"); } diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs index b31a87daec7c7..fccfdce9b83d0 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs @@ -822,6 +822,21 @@ private static uint _getClassNumInstanceFields(IntPtr thisHandle, IntPtr* ppExce } } + [UnmanagedCallersOnly] + private static GetTypeLayoutResult _getTypeLayout(IntPtr thisHandle, IntPtr* ppException, CORINFO_CLASS_STRUCT_* typeHnd, CORINFO_TYPE_LAYOUT_NODE* treeNodes, UIntPtr* numTreeNodes) + { + var _this = GetThis(thisHandle); + try + { + return _this.getTypeLayout(typeHnd, treeNodes, numTreeNodes); + } + catch (Exception ex) + { + *ppException = _this.AllocException(ex); + return default; + } + } + [UnmanagedCallersOnly] private static byte _checkMethodModifier(IntPtr thisHandle, IntPtr* ppException, CORINFO_METHOD_STRUCT_* hMethod, byte* modifier, byte fOptional) { @@ -2492,7 +2507,7 @@ private static uint _getJitFlags(IntPtr thisHandle, IntPtr* ppException, CORJIT_ private static IntPtr GetUnmanagedCallbacks() { - void** callbacks = (void**)Marshal.AllocCoTaskMem(sizeof(IntPtr) * 168); + void** callbacks = (void**)Marshal.AllocCoTaskMem(sizeof(IntPtr) * 169); callbacks[0] = (delegate* unmanaged)&_isIntrinsic; callbacks[1] = (delegate* unmanaged)&_getMethodAttribs; @@ -2549,119 +2564,120 @@ private static IntPtr GetUnmanagedCallbacks() callbacks[52] = (delegate* unmanaged)&_getClassGClayout; callbacks[53] = (delegate* unmanaged)&_getClassNumInstanceFields; callbacks[54] = (delegate* unmanaged)&_getFieldInClass; - callbacks[55] = (delegate* unmanaged)&_checkMethodModifier; - callbacks[56] = (delegate* unmanaged)&_getNewHelper; - callbacks[57] = (delegate* unmanaged)&_getNewArrHelper; - callbacks[58] = (delegate* unmanaged)&_getCastingHelper; - callbacks[59] = (delegate* unmanaged)&_getSharedCCtorHelper; - callbacks[60] = (delegate* unmanaged)&_getTypeForBox; - callbacks[61] = (delegate* unmanaged)&_getBoxHelper; - callbacks[62] = (delegate* unmanaged)&_getUnBoxHelper; - callbacks[63] = (delegate* unmanaged)&_getRuntimeTypePointer; - callbacks[64] = (delegate* unmanaged)&_isObjectImmutable; - callbacks[65] = (delegate* unmanaged)&_getStringChar; - callbacks[66] = (delegate* unmanaged)&_getObjectType; - callbacks[67] = (delegate* unmanaged)&_getReadyToRunHelper; - callbacks[68] = (delegate* unmanaged)&_getReadyToRunDelegateCtorHelper; - callbacks[69] = (delegate* unmanaged)&_initClass; - callbacks[70] = (delegate* unmanaged)&_classMustBeLoadedBeforeCodeIsRun; - callbacks[71] = (delegate* unmanaged)&_getBuiltinClass; - callbacks[72] = (delegate* unmanaged)&_getTypeForPrimitiveValueClass; - callbacks[73] = (delegate* unmanaged)&_getTypeForPrimitiveNumericClass; - callbacks[74] = (delegate* unmanaged)&_canCast; - callbacks[75] = (delegate* unmanaged)&_compareTypesForCast; - callbacks[76] = (delegate* unmanaged)&_compareTypesForEquality; - callbacks[77] = (delegate* unmanaged)&_isMoreSpecificType; - callbacks[78] = (delegate* unmanaged)&_isEnum; - callbacks[79] = (delegate* unmanaged)&_getParentType; - callbacks[80] = (delegate* unmanaged)&_getChildType; - callbacks[81] = (delegate* unmanaged)&_isSDArray; - callbacks[82] = (delegate* unmanaged)&_getArrayRank; - callbacks[83] = (delegate* unmanaged)&_getArrayIntrinsicID; - callbacks[84] = (delegate* unmanaged)&_getArrayInitializationData; - callbacks[85] = (delegate* unmanaged)&_canAccessClass; - callbacks[86] = (delegate* unmanaged)&_printFieldName; - callbacks[87] = (delegate* unmanaged)&_getFieldClass; - callbacks[88] = (delegate* unmanaged)&_getFieldType; - callbacks[89] = (delegate* unmanaged)&_getFieldOffset; - callbacks[90] = (delegate* unmanaged)&_getFieldInfo; - callbacks[91] = (delegate* unmanaged)&_getThreadLocalFieldInfo; - callbacks[92] = (delegate* unmanaged)&_getThreadLocalStaticBlocksInfo; - callbacks[93] = (delegate* unmanaged)&_isFieldStatic; - callbacks[94] = (delegate* unmanaged)&_getArrayOrStringLength; - callbacks[95] = (delegate* unmanaged)&_getBoundaries; - callbacks[96] = (delegate* unmanaged)&_setBoundaries; - callbacks[97] = (delegate* unmanaged)&_getVars; - callbacks[98] = (delegate* unmanaged)&_setVars; - callbacks[99] = (delegate* unmanaged)&_reportRichMappings; - callbacks[100] = (delegate* unmanaged)&_allocateArray; - callbacks[101] = (delegate* unmanaged)&_freeArray; - callbacks[102] = (delegate* unmanaged)&_getArgNext; - callbacks[103] = (delegate* unmanaged)&_getArgType; - callbacks[104] = (delegate* unmanaged)&_getExactClasses; - callbacks[105] = (delegate* unmanaged)&_getArgClass; - callbacks[106] = (delegate* unmanaged)&_getHFAType; - callbacks[107] = (delegate* unmanaged)&_runWithErrorTrap; - callbacks[108] = (delegate* unmanaged)&_runWithSPMIErrorTrap; - callbacks[109] = (delegate* unmanaged)&_getEEInfo; - callbacks[110] = (delegate* unmanaged)&_getJitTimeLogFilename; - callbacks[111] = (delegate* unmanaged)&_getMethodDefFromMethod; - callbacks[112] = (delegate* unmanaged)&_printMethodName; - callbacks[113] = (delegate* unmanaged)&_getMethodNameFromMetadata; - callbacks[114] = (delegate* unmanaged)&_getMethodHash; - callbacks[115] = (delegate* unmanaged)&_getSystemVAmd64PassStructInRegisterDescriptor; - callbacks[116] = (delegate* unmanaged)&_getLoongArch64PassStructInRegisterFlags; - callbacks[117] = (delegate* unmanaged)&_getRISCV64PassStructInRegisterFlags; - callbacks[118] = (delegate* unmanaged)&_getThreadTLSIndex; - callbacks[119] = (delegate* unmanaged)&_getAddrOfCaptureThreadGlobal; - callbacks[120] = (delegate* unmanaged)&_getHelperFtn; - callbacks[121] = (delegate* unmanaged)&_getFunctionEntryPoint; - callbacks[122] = (delegate* unmanaged)&_getFunctionFixedEntryPoint; - callbacks[123] = (delegate* unmanaged)&_getMethodSync; - callbacks[124] = (delegate* unmanaged)&_getLazyStringLiteralHelper; - callbacks[125] = (delegate* unmanaged)&_embedModuleHandle; - callbacks[126] = (delegate* unmanaged)&_embedClassHandle; - callbacks[127] = (delegate* unmanaged)&_embedMethodHandle; - callbacks[128] = (delegate* unmanaged)&_embedFieldHandle; - callbacks[129] = (delegate* unmanaged)&_embedGenericHandle; - callbacks[130] = (delegate* unmanaged)&_getLocationOfThisType; - callbacks[131] = (delegate* unmanaged)&_getAddressOfPInvokeTarget; - callbacks[132] = (delegate* unmanaged)&_GetCookieForPInvokeCalliSig; - callbacks[133] = (delegate* unmanaged)&_canGetCookieForPInvokeCalliSig; - callbacks[134] = (delegate* unmanaged)&_getJustMyCodeHandle; - callbacks[135] = (delegate* unmanaged)&_GetProfilingHandle; - callbacks[136] = (delegate* unmanaged)&_getCallInfo; - callbacks[137] = (delegate* unmanaged)&_getClassDomainID; - callbacks[138] = (delegate* unmanaged)&_getStaticFieldContent; - callbacks[139] = (delegate* unmanaged)&_getObjectContent; - callbacks[140] = (delegate* unmanaged)&_getStaticFieldCurrentClass; - callbacks[141] = (delegate* unmanaged)&_getVarArgsHandle; - callbacks[142] = (delegate* unmanaged)&_canGetVarArgsHandle; - callbacks[143] = (delegate* unmanaged)&_constructStringLiteral; - callbacks[144] = (delegate* unmanaged)&_emptyStringLiteral; - callbacks[145] = (delegate* unmanaged)&_getFieldThreadLocalStoreID; - callbacks[146] = (delegate* unmanaged)&_GetDelegateCtor; - callbacks[147] = (delegate* unmanaged)&_MethodCompileComplete; - callbacks[148] = (delegate* unmanaged)&_getTailCallHelpers; - callbacks[149] = (delegate* unmanaged)&_convertPInvokeCalliToCall; - callbacks[150] = (delegate* unmanaged)&_notifyInstructionSetUsage; - callbacks[151] = (delegate* unmanaged)&_updateEntryPointForTailCall; - callbacks[152] = (delegate* unmanaged)&_allocMem; - callbacks[153] = (delegate* unmanaged)&_reserveUnwindInfo; - callbacks[154] = (delegate* unmanaged)&_allocUnwindInfo; - callbacks[155] = (delegate* unmanaged)&_allocGCInfo; - callbacks[156] = (delegate* unmanaged)&_setEHcount; - callbacks[157] = (delegate* unmanaged)&_setEHinfo; - callbacks[158] = (delegate* unmanaged)&_logMsg; - callbacks[159] = (delegate* unmanaged)&_doAssert; - callbacks[160] = (delegate* unmanaged)&_reportFatalError; - callbacks[161] = (delegate* unmanaged)&_getPgoInstrumentationResults; - callbacks[162] = (delegate* unmanaged)&_allocPgoInstrumentationBySchema; - callbacks[163] = (delegate* unmanaged)&_recordCallSite; - callbacks[164] = (delegate* unmanaged)&_recordRelocation; - callbacks[165] = (delegate* unmanaged)&_getRelocTypeHint; - callbacks[166] = (delegate* unmanaged)&_getExpectedTargetArchitecture; - callbacks[167] = (delegate* unmanaged)&_getJitFlags; + callbacks[55] = (delegate* unmanaged)&_getTypeLayout; + callbacks[56] = (delegate* unmanaged)&_checkMethodModifier; + callbacks[57] = (delegate* unmanaged)&_getNewHelper; + callbacks[58] = (delegate* unmanaged)&_getNewArrHelper; + callbacks[59] = (delegate* unmanaged)&_getCastingHelper; + callbacks[60] = (delegate* unmanaged)&_getSharedCCtorHelper; + callbacks[61] = (delegate* unmanaged)&_getTypeForBox; + callbacks[62] = (delegate* unmanaged)&_getBoxHelper; + callbacks[63] = (delegate* unmanaged)&_getUnBoxHelper; + callbacks[64] = (delegate* unmanaged)&_getRuntimeTypePointer; + callbacks[65] = (delegate* unmanaged)&_isObjectImmutable; + callbacks[66] = (delegate* unmanaged)&_getStringChar; + callbacks[67] = (delegate* unmanaged)&_getObjectType; + callbacks[68] = (delegate* unmanaged)&_getReadyToRunHelper; + callbacks[69] = (delegate* unmanaged)&_getReadyToRunDelegateCtorHelper; + callbacks[70] = (delegate* unmanaged)&_initClass; + callbacks[71] = (delegate* unmanaged)&_classMustBeLoadedBeforeCodeIsRun; + callbacks[72] = (delegate* unmanaged)&_getBuiltinClass; + callbacks[73] = (delegate* unmanaged)&_getTypeForPrimitiveValueClass; + callbacks[74] = (delegate* unmanaged)&_getTypeForPrimitiveNumericClass; + callbacks[75] = (delegate* unmanaged)&_canCast; + callbacks[76] = (delegate* unmanaged)&_compareTypesForCast; + callbacks[77] = (delegate* unmanaged)&_compareTypesForEquality; + callbacks[78] = (delegate* unmanaged)&_isMoreSpecificType; + callbacks[79] = (delegate* unmanaged)&_isEnum; + callbacks[80] = (delegate* unmanaged)&_getParentType; + callbacks[81] = (delegate* unmanaged)&_getChildType; + callbacks[82] = (delegate* unmanaged)&_isSDArray; + callbacks[83] = (delegate* unmanaged)&_getArrayRank; + callbacks[84] = (delegate* unmanaged)&_getArrayIntrinsicID; + callbacks[85] = (delegate* unmanaged)&_getArrayInitializationData; + callbacks[86] = (delegate* unmanaged)&_canAccessClass; + callbacks[87] = (delegate* unmanaged)&_printFieldName; + callbacks[88] = (delegate* unmanaged)&_getFieldClass; + callbacks[89] = (delegate* unmanaged)&_getFieldType; + callbacks[90] = (delegate* unmanaged)&_getFieldOffset; + callbacks[91] = (delegate* unmanaged)&_getFieldInfo; + callbacks[92] = (delegate* unmanaged)&_getThreadLocalFieldInfo; + callbacks[93] = (delegate* unmanaged)&_getThreadLocalStaticBlocksInfo; + callbacks[94] = (delegate* unmanaged)&_isFieldStatic; + callbacks[95] = (delegate* unmanaged)&_getArrayOrStringLength; + callbacks[96] = (delegate* unmanaged)&_getBoundaries; + callbacks[97] = (delegate* unmanaged)&_setBoundaries; + callbacks[98] = (delegate* unmanaged)&_getVars; + callbacks[99] = (delegate* unmanaged)&_setVars; + callbacks[100] = (delegate* unmanaged)&_reportRichMappings; + callbacks[101] = (delegate* unmanaged)&_allocateArray; + callbacks[102] = (delegate* unmanaged)&_freeArray; + callbacks[103] = (delegate* unmanaged)&_getArgNext; + callbacks[104] = (delegate* unmanaged)&_getArgType; + callbacks[105] = (delegate* unmanaged)&_getExactClasses; + callbacks[106] = (delegate* unmanaged)&_getArgClass; + callbacks[107] = (delegate* unmanaged)&_getHFAType; + callbacks[108] = (delegate* unmanaged)&_runWithErrorTrap; + callbacks[109] = (delegate* unmanaged)&_runWithSPMIErrorTrap; + callbacks[110] = (delegate* unmanaged)&_getEEInfo; + callbacks[111] = (delegate* unmanaged)&_getJitTimeLogFilename; + callbacks[112] = (delegate* unmanaged)&_getMethodDefFromMethod; + callbacks[113] = (delegate* unmanaged)&_printMethodName; + callbacks[114] = (delegate* unmanaged)&_getMethodNameFromMetadata; + callbacks[115] = (delegate* unmanaged)&_getMethodHash; + callbacks[116] = (delegate* unmanaged)&_getSystemVAmd64PassStructInRegisterDescriptor; + callbacks[117] = (delegate* unmanaged)&_getLoongArch64PassStructInRegisterFlags; + callbacks[118] = (delegate* unmanaged)&_getRISCV64PassStructInRegisterFlags; + callbacks[119] = (delegate* unmanaged)&_getThreadTLSIndex; + callbacks[120] = (delegate* unmanaged)&_getAddrOfCaptureThreadGlobal; + callbacks[121] = (delegate* unmanaged)&_getHelperFtn; + callbacks[122] = (delegate* unmanaged)&_getFunctionEntryPoint; + callbacks[123] = (delegate* unmanaged)&_getFunctionFixedEntryPoint; + callbacks[124] = (delegate* unmanaged)&_getMethodSync; + callbacks[125] = (delegate* unmanaged)&_getLazyStringLiteralHelper; + callbacks[126] = (delegate* unmanaged)&_embedModuleHandle; + callbacks[127] = (delegate* unmanaged)&_embedClassHandle; + callbacks[128] = (delegate* unmanaged)&_embedMethodHandle; + callbacks[129] = (delegate* unmanaged)&_embedFieldHandle; + callbacks[130] = (delegate* unmanaged)&_embedGenericHandle; + callbacks[131] = (delegate* unmanaged)&_getLocationOfThisType; + callbacks[132] = (delegate* unmanaged)&_getAddressOfPInvokeTarget; + callbacks[133] = (delegate* unmanaged)&_GetCookieForPInvokeCalliSig; + callbacks[134] = (delegate* unmanaged)&_canGetCookieForPInvokeCalliSig; + callbacks[135] = (delegate* unmanaged)&_getJustMyCodeHandle; + callbacks[136] = (delegate* unmanaged)&_GetProfilingHandle; + callbacks[137] = (delegate* unmanaged)&_getCallInfo; + callbacks[138] = (delegate* unmanaged)&_getClassDomainID; + callbacks[139] = (delegate* unmanaged)&_getStaticFieldContent; + callbacks[140] = (delegate* unmanaged)&_getObjectContent; + callbacks[141] = (delegate* unmanaged)&_getStaticFieldCurrentClass; + callbacks[142] = (delegate* unmanaged)&_getVarArgsHandle; + callbacks[143] = (delegate* unmanaged)&_canGetVarArgsHandle; + callbacks[144] = (delegate* unmanaged)&_constructStringLiteral; + callbacks[145] = (delegate* unmanaged)&_emptyStringLiteral; + callbacks[146] = (delegate* unmanaged)&_getFieldThreadLocalStoreID; + callbacks[147] = (delegate* unmanaged)&_GetDelegateCtor; + callbacks[148] = (delegate* unmanaged)&_MethodCompileComplete; + callbacks[149] = (delegate* unmanaged)&_getTailCallHelpers; + callbacks[150] = (delegate* unmanaged)&_convertPInvokeCalliToCall; + callbacks[151] = (delegate* unmanaged)&_notifyInstructionSetUsage; + callbacks[152] = (delegate* unmanaged)&_updateEntryPointForTailCall; + callbacks[153] = (delegate* unmanaged)&_allocMem; + callbacks[154] = (delegate* unmanaged)&_reserveUnwindInfo; + callbacks[155] = (delegate* unmanaged)&_allocUnwindInfo; + callbacks[156] = (delegate* unmanaged)&_allocGCInfo; + callbacks[157] = (delegate* unmanaged)&_setEHcount; + callbacks[158] = (delegate* unmanaged)&_setEHinfo; + callbacks[159] = (delegate* unmanaged)&_logMsg; + callbacks[160] = (delegate* unmanaged)&_doAssert; + callbacks[161] = (delegate* unmanaged)&_reportFatalError; + callbacks[162] = (delegate* unmanaged)&_getPgoInstrumentationResults; + callbacks[163] = (delegate* unmanaged)&_allocPgoInstrumentationBySchema; + callbacks[164] = (delegate* unmanaged)&_recordCallSite; + callbacks[165] = (delegate* unmanaged)&_recordRelocation; + callbacks[166] = (delegate* unmanaged)&_getRelocTypeHint; + callbacks[167] = (delegate* unmanaged)&_getExpectedTargetArchitecture; + callbacks[168] = (delegate* unmanaged)&_getJitFlags; return (IntPtr)callbacks; } diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index 0f0d974e76c97..e3c6fc173e73a 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -599,8 +599,6 @@ public enum CorInfoFlag : uint CORINFO_FLG_ARRAY = 0x00080000, // class is an array class (initialized differently) CORINFO_FLG_OVERLAPPING_FIELDS = 0x00100000, // struct or class has fields that overlap (aka union) CORINFO_FLG_INTERFACE = 0x00200000, // it is an interface - CORINFO_FLG_DONT_DIG_FIELDS = 0x00400000, // don't try to ask about fields outside of AOT compilation version bubble - CORINFO_FLG_CUSTOMLAYOUT = 0x00800000, // does this struct have custom layout? CORINFO_FLG_CONTAINS_GC_PTR = 0x01000000, // does the class contain a gc ptr ? CORINFO_FLG_DELEGATE = 0x02000000, // is this a subclass of delegate or multicast delegate ? CORINFO_FLG_INDEXABLE_FIELDS = 0x04000000, // struct fields may be accessed via indexing (used for inline arrays) @@ -1455,4 +1453,27 @@ public bool IsSet(CorJitFlag flag) return (_corJitFlags & (1UL << (int)flag)) != 0; } } + + public enum GetTypeLayoutResult + { + Success = 0, + Partial = 1, + Failure = 2, + } + + // See comments in interface declaration. There are important restrictions + // on the fields of this structure and how the JIT uses them and is allowed + // to use them. + public unsafe struct CORINFO_TYPE_LAYOUT_NODE + { + public CORINFO_CLASS_STRUCT_* simdTypeHnd; + public CORINFO_FIELD_STRUCT_* diagFieldHnd; + public uint parent; + public uint offset; + public uint size; + public uint numFields; + public CorInfoType type; + private byte _hasSignificantPadding; + public bool hasSignificantPadding { get => _hasSignificantPadding != 0; set => _hasSignificantPadding = value ? (byte)1 : (byte)0; } + } } diff --git a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt index 4bec715c809bd..5067102b6d17e 100644 --- a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt +++ b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt @@ -89,6 +89,7 @@ CORINFO_FIELD_INFO*,CORINFO_FIELD_INFO* CORINFO_THREAD_STATIC_BLOCKS_INFO*,CORINFO_THREAD_STATIC_BLOCKS_INFO* CORINFO_CALL_INFO*,CORINFO_CALL_INFO* CORINFO_DEVIRTUALIZATION_INFO*,CORINFO_DEVIRTUALIZATION_INFO* +CORINFO_TYPE_LAYOUT_NODE*,CORINFO_TYPE_LAYOUT_NODE* PatchpointInfo* DelegateCtorArgs*,ref DelegateCtorArgs ICorDynamicInfo*,IntPtr @@ -132,6 +133,7 @@ TypeCompareState CORINFO_InstructionSet,InstructionSet ICorJitInfo::PgoSource, PgoSource ICorJitInfo::PgoSource*, ref PgoSource +GetTypeLayoutResult ; Handle types CORINFO_MODULE_HANDLE,CORINFO_MODULE_STRUCT_* @@ -214,6 +216,7 @@ FUNCTIONS unsigned getClassGClayout(CORINFO_CLASS_HANDLE cls, uint8_t* gcPtrs) unsigned getClassNumInstanceFields(CORINFO_CLASS_HANDLE cls) CORINFO_FIELD_HANDLE getFieldInClass(CORINFO_CLASS_HANDLE clsHnd, int32_t num) + GetTypeLayoutResult getTypeLayout(CORINFO_CLASS_HANDLE typeHnd, CORINFO_TYPE_LAYOUT_NODE* treeNodes, size_t* numTreeNodes) bool checkMethodModifier(CORINFO_METHOD_HANDLE hMethod, const char * modifier, bool fOptional) CorInfoHelpFunc getNewHelper(CORINFO_RESOLVED_TOKEN* pResolvedToken, CORINFO_METHOD_HANDLE callerHandle, bool* pHasSideEffects) CorInfoHelpFunc getNewArrHelper(CORINFO_CLASS_HANDLE arrayCls) diff --git a/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h b/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h index 24c21759692ab..38083a09be974 100644 --- a/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h +++ b/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h @@ -66,6 +66,7 @@ struct JitInterfaceCallbacks unsigned (* getClassGClayout)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE cls, uint8_t* gcPtrs); unsigned (* getClassNumInstanceFields)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE cls); CORINFO_FIELD_HANDLE (* getFieldInClass)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE clsHnd, int32_t num); + GetTypeLayoutResult (* getTypeLayout)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE typeHnd, CORINFO_TYPE_LAYOUT_NODE* treeNodes, size_t* numTreeNodes); bool (* checkMethodModifier)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE hMethod, const char* modifier, bool fOptional); CorInfoHelpFunc (* getNewHelper)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_RESOLVED_TOKEN* pResolvedToken, CORINFO_METHOD_HANDLE callerHandle, bool* pHasSideEffects); CorInfoHelpFunc (* getNewArrHelper)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE arrayCls); @@ -730,6 +731,17 @@ class JitInterfaceWrapper : public ICorJitInfo return temp; } + virtual GetTypeLayoutResult getTypeLayout( + CORINFO_CLASS_HANDLE typeHnd, + CORINFO_TYPE_LAYOUT_NODE* treeNodes, + size_t* numTreeNodes) +{ + CorInfoExceptionClass* pException = nullptr; + GetTypeLayoutResult temp = _callbacks->getTypeLayout(_thisHandle, &pException, typeHnd, treeNodes, numTreeNodes); + if (pException != nullptr) throw pException; + return temp; +} + virtual bool checkMethodModifier( CORINFO_METHOD_HANDLE hMethod, const char* modifier, diff --git a/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h b/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h index 6b1b578b36ee3..975091688c453 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h @@ -204,6 +204,25 @@ struct Agnostic_GetStaticFieldCurrentClass bool isSpeculative; }; +struct Agnostic_CORINFO_TYPE_LAYOUT_NODE +{ + DWORDLONG simdTypeHnd; + DWORDLONG diagFieldHnd; + DWORD parent; + DWORD offset; + DWORD size; + DWORD numFields; + BYTE type; + bool hasSignificantPadding; +}; + +struct Agnostic_GetTypeLayoutResult +{ + DWORD result; + DWORD nodesBuffer; + DWORD numNodes; +}; + struct Agnostic_CORINFO_RESOLVED_TOKEN { Agnostic_CORINFO_RESOLVED_TOKENin inValue; diff --git a/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h b/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h index 3eb6ddd698fd5..0d5af3e388270 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h @@ -82,6 +82,7 @@ LWM(GetObjectContent, DLDD, DD) LWM(GetStaticFieldCurrentClass, DLD, Agnostic_GetStaticFieldCurrentClass) LWM(GetFieldClass, DWORDLONG, DWORDLONG) LWM(GetFieldInClass, DLD, DWORDLONG) +LWM(GetTypeLayout, DWORDLONG, Agnostic_GetTypeLayoutResult) LWM(GetFieldInfo, Agnostic_GetFieldInfo, Agnostic_CORINFO_FIELD_INFO) LWM(GetFieldOffset, DWORDLONG, DWORD) LWM(GetFieldThreadLocalStoreID, DWORDLONG, DLD) diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp index 96a5c32fee8b6..b5ba78ef623b2 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp @@ -4735,6 +4735,120 @@ CORINFO_FIELD_HANDLE MethodContext::repGetFieldInClass(CORINFO_CLASS_HANDLE clsH return result; } +void MethodContext::recGetTypeLayout(GetTypeLayoutResult result, CORINFO_CLASS_HANDLE typeHnd, CORINFO_TYPE_LAYOUT_NODE* nodes, size_t numNodes) +{ + if (GetTypeLayout == nullptr) + GetTypeLayout = new LightWeightMap(); + + DWORDLONG key = CastHandle(typeHnd); + + int index = GetTypeLayout->GetIndex(key); + if (index != -1) + { + // If we already have this then just skip. + Agnostic_GetTypeLayoutResult existing = GetTypeLayout->GetItem(index); + if ((existing.result != (DWORD)GetTypeLayoutResult::Partial) || + (result == GetTypeLayoutResult::Failure) || + (numNodes <= existing.numNodes)) + { + // No new data to add + return; + } + + // Otherwise fall through and update the map with more information. + } + + Agnostic_GetTypeLayoutResult value; + ZeroMemory(&value, sizeof(value)); + + value.result = (DWORD)result; + if (result == GetTypeLayoutResult::Failure) + { + value.nodesBuffer = UINT_MAX; + } + else + { + Agnostic_CORINFO_TYPE_LAYOUT_NODE* agnosticFields = new Agnostic_CORINFO_TYPE_LAYOUT_NODE[numNodes]; + for (size_t i = 0; i < numNodes; i++) + { + agnosticFields[i] = SpmiRecordsHelper::StoreAgnostic_CORINFO_TYPE_LAYOUT_NODE(nodes[i]); + } + + value.nodesBuffer = GetTypeLayout->AddBuffer((unsigned char*)agnosticFields, (unsigned int)(sizeof(Agnostic_CORINFO_TYPE_LAYOUT_NODE) * numNodes)); + value.numNodes = (DWORD)numNodes; + + delete[] agnosticFields; + } + + if (index != -1) + { + GetTypeLayout->Update(index, value); + } + else + { + GetTypeLayout->Add(key, value); + } +} +void MethodContext::dmpGetTypeLayout(DWORDLONG key, const Agnostic_GetTypeLayoutResult& value) +{ + printf("GetTypeLayout key type-%016" PRIX64 " value result=%d numNodes=%d", key, (DWORD)value.result, value.numNodes); + if (value.numNodes > 0) + { + Agnostic_CORINFO_TYPE_LAYOUT_NODE* nodes = reinterpret_cast(GetTypeLayout->GetBuffer(value.nodesBuffer)); + size_t index = 0; + dmpTypeLayoutTree(nodes, value.numNodes, &index, 1); + } +} +void MethodContext::dmpTypeLayoutTree(const Agnostic_CORINFO_TYPE_LAYOUT_NODE* nodes, size_t maxNodes, size_t* index, size_t indent) +{ + for (size_t i = 0; i < indent; i++) + { + printf(" "); + } + + const Agnostic_CORINFO_TYPE_LAYOUT_NODE* node = &nodes[*index]; + printf("%zu: parent %u offset %u size %u type %s numFields %u hasSignificantPadding %s simdTypeHnd %016" PRIX64 " diagFieldHnd %016" PRIX64, + *index, + node->parent, + node->offset, + node->size, + toString((CorInfoType)node->type), + node->numFields, + node->hasSignificantPadding ? "yes" : "no", + node->simdTypeHnd, + node->diagFieldHnd); + + (*index)++; + for (size_t i = 0; i < node->numFields; i++) + { + if (i >= maxNodes) + break; + + dmpTypeLayoutTree(nodes, maxNodes, index, indent + 1); + } +} +GetTypeLayoutResult MethodContext::repGetTypeLayout(CORINFO_CLASS_HANDLE typeHnd, CORINFO_TYPE_LAYOUT_NODE* nodes, size_t* numNodes) +{ + DWORDLONG key = CastHandle(typeHnd); + Agnostic_GetTypeLayoutResult value = LookupByKeyOrMiss(GetTypeLayout, key, ": key type-%016" PRIX64, key); + + GetTypeLayoutResult result = (GetTypeLayoutResult)value.result; + if (result == GetTypeLayoutResult::Failure) + { + return result; + } + + Agnostic_CORINFO_TYPE_LAYOUT_NODE* valueFields = (Agnostic_CORINFO_TYPE_LAYOUT_NODE*)GetTypeLayout->GetBuffer(value.nodesBuffer); + size_t nodesToWrite = min((size_t)value.numNodes, *numNodes); + for (size_t i = 0; i < nodesToWrite; i++) + { + nodes[i] = SpmiRecordsHelper::RestoreCORINFO_TYPE_LAYOUT_NODE(valueFields[i]); + } + + *numNodes = nodesToWrite; + return (result == GetTypeLayoutResult::Partial) || (value.numNodes > *numNodes) ? GetTypeLayoutResult::Partial : GetTypeLayoutResult::Success; +} + void MethodContext::recGetFieldType(CORINFO_FIELD_HANDLE field, CORINFO_CLASS_HANDLE* structType, CORINFO_CLASS_HANDLE memberParent, diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h index 1300451268026..4ac4554ea3696 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h @@ -601,6 +601,11 @@ class MethodContext void dmpGetFieldInClass(DLD key, DWORDLONG value); CORINFO_FIELD_HANDLE repGetFieldInClass(CORINFO_CLASS_HANDLE clsHnd, INT num); + void recGetTypeLayout(GetTypeLayoutResult result, CORINFO_CLASS_HANDLE typeHnd, CORINFO_TYPE_LAYOUT_NODE* nodes, size_t numNodes); + void dmpGetTypeLayout(DWORDLONG key, const Agnostic_GetTypeLayoutResult& value); + void dmpTypeLayoutTree(const Agnostic_CORINFO_TYPE_LAYOUT_NODE* nodes, size_t maxNodes, size_t* index, size_t indent); + GetTypeLayoutResult repGetTypeLayout(CORINFO_CLASS_HANDLE typeHnd, CORINFO_TYPE_LAYOUT_NODE* nodes, size_t* numNodes); + void recGetFieldType(CORINFO_FIELD_HANDLE field, CORINFO_CLASS_HANDLE* structType, CORINFO_CLASS_HANDLE memberParent, @@ -1127,6 +1132,7 @@ enum mcPackets Packet_GetThreadLocalStaticBlocksInfo = 208, Packet_GetRISCV64PassStructInRegisterFlags = 209, Packet_GetObjectContent = 210, + Packet_GetTypeLayout = 211, }; void SetDebugDumpVariables(); diff --git a/src/coreclr/tools/superpmi/superpmi-shared/spmidumphelper.cpp b/src/coreclr/tools/superpmi/superpmi-shared/spmidumphelper.cpp index 9595d88ba68e6..083b80c03286c 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/spmidumphelper.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shared/spmidumphelper.cpp @@ -183,7 +183,6 @@ std::string SpmiDumpHelper::DumpCorInfoFlag(CorInfoFlag flags) AddFlag(CORINFO_FLG_ARRAY); AddFlag(CORINFO_FLG_OVERLAPPING_FIELDS); AddFlag(CORINFO_FLG_INTERFACE); - AddFlag(CORINFO_FLG_CUSTOMLAYOUT); AddFlag(CORINFO_FLG_CONTAINS_GC_PTR); AddFlag(CORINFO_FLG_DELEGATE); AddFlag(CORINFO_FLG_BYREF_LIKE); diff --git a/src/coreclr/tools/superpmi/superpmi-shared/spmirecordhelper.h b/src/coreclr/tools/superpmi/superpmi-shared/spmirecordhelper.h index 513e1b063e8b6..1214894b231ab 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/spmirecordhelper.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/spmirecordhelper.h @@ -109,6 +109,9 @@ class SpmiRecordsHelper static Agnostic_CORINFO_LOOKUP StoreAgnostic_CORINFO_LOOKUP(CORINFO_LOOKUP* pLookup); static CORINFO_LOOKUP RestoreCORINFO_LOOKUP(Agnostic_CORINFO_LOOKUP& agnosticLookup); + + static Agnostic_CORINFO_TYPE_LAYOUT_NODE StoreAgnostic_CORINFO_TYPE_LAYOUT_NODE(const CORINFO_TYPE_LAYOUT_NODE& node); + static CORINFO_TYPE_LAYOUT_NODE RestoreCORINFO_TYPE_LAYOUT_NODE(const Agnostic_CORINFO_TYPE_LAYOUT_NODE& node); }; inline Agnostic_CORINFO_RESOLVED_TOKENin SpmiRecordsHelper::CreateAgnostic_CORINFO_RESOLVED_TOKENin( @@ -542,4 +545,32 @@ inline CORINFO_LOOKUP SpmiRecordsHelper::RestoreCORINFO_LOOKUP(Agnostic_CORINFO_ return lookup; } +inline Agnostic_CORINFO_TYPE_LAYOUT_NODE SpmiRecordsHelper::StoreAgnostic_CORINFO_TYPE_LAYOUT_NODE(const CORINFO_TYPE_LAYOUT_NODE& node) +{ + Agnostic_CORINFO_TYPE_LAYOUT_NODE result; + result.simdTypeHnd = CastHandle(node.simdTypeHnd); + result.diagFieldHnd = CastHandle(node.diagFieldHnd); + result.parent = node.parent; + result.offset = node.offset; + result.size = node.size; + result.numFields = node.numFields; + result.type = (BYTE)node.type; + result.hasSignificantPadding = node.hasSignificantPadding; + return result; +} + +inline CORINFO_TYPE_LAYOUT_NODE SpmiRecordsHelper::RestoreCORINFO_TYPE_LAYOUT_NODE(const Agnostic_CORINFO_TYPE_LAYOUT_NODE& node) +{ + CORINFO_TYPE_LAYOUT_NODE result; + result.simdTypeHnd = (CORINFO_CLASS_HANDLE)node.simdTypeHnd; + result.diagFieldHnd = (CORINFO_FIELD_HANDLE)node.diagFieldHnd; + result.parent = node.parent; + result.offset = node.offset; + result.size = node.size; + result.numFields = node.numFields; + result.type = (CorInfoType)node.type; + result.hasSignificantPadding = node.hasSignificantPadding; + return result; +} + #endif diff --git a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp index 145b7ee9fce3f..73ac91637d51a 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp @@ -624,6 +624,17 @@ CORINFO_FIELD_HANDLE interceptor_ICJI::getFieldInClass(CORINFO_CLASS_HANDLE clsH return temp; } +GetTypeLayoutResult interceptor_ICJI::getTypeLayout( + CORINFO_CLASS_HANDLE typeHnd, + CORINFO_TYPE_LAYOUT_NODE* nodes, + size_t* numNodes) +{ + mc->cr->AddCall("getTypeLayout"); + GetTypeLayoutResult result = original_ICorJitInfo->getTypeLayout(typeHnd, nodes, numNodes); + mc->recGetTypeLayout(result, typeHnd, nodes, *numNodes); + return result; +} + bool interceptor_ICJI::checkMethodModifier(CORINFO_METHOD_HANDLE hMethod, LPCSTR modifier, bool fOptional) { mc->cr->AddCall("checkMethodModifier"); diff --git a/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp b/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp index 70f64123a4790..2740cce346a24 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp @@ -453,6 +453,15 @@ CORINFO_FIELD_HANDLE interceptor_ICJI::getFieldInClass( return original_ICorJitInfo->getFieldInClass(clsHnd, num); } +GetTypeLayoutResult interceptor_ICJI::getTypeLayout( + CORINFO_CLASS_HANDLE typeHnd, + CORINFO_TYPE_LAYOUT_NODE* treeNodes, + size_t* numTreeNodes) +{ + mcs->AddCall("getTypeLayout"); + return original_ICorJitInfo->getTypeLayout(typeHnd, treeNodes, numTreeNodes); +} + bool interceptor_ICJI::checkMethodModifier( CORINFO_METHOD_HANDLE hMethod, const char* modifier, diff --git a/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp b/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp index fb2d3246a9689..357f2b7b9c945 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp @@ -398,6 +398,14 @@ CORINFO_FIELD_HANDLE interceptor_ICJI::getFieldInClass( return original_ICorJitInfo->getFieldInClass(clsHnd, num); } +GetTypeLayoutResult interceptor_ICJI::getTypeLayout( + CORINFO_CLASS_HANDLE typeHnd, + CORINFO_TYPE_LAYOUT_NODE* treeNodes, + size_t* numTreeNodes) +{ + return original_ICorJitInfo->getTypeLayout(typeHnd, treeNodes, numTreeNodes); +} + bool interceptor_ICJI::checkMethodModifier( CORINFO_METHOD_HANDLE hMethod, const char* modifier, diff --git a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp index 756443dce9399..271af591c9bd4 100644 --- a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp @@ -529,6 +529,15 @@ CORINFO_FIELD_HANDLE MyICJI::getFieldInClass(CORINFO_CLASS_HANDLE clsHnd, INT nu return jitInstance->mc->repGetFieldInClass(clsHnd, num); } +GetTypeLayoutResult MyICJI::getTypeLayout( + CORINFO_CLASS_HANDLE typeHnd, + CORINFO_TYPE_LAYOUT_NODE* nodes, + size_t* numNodes) +{ + jitInstance->mc->cr->AddCall("getTypeLayout"); + return jitInstance->mc->repGetTypeLayout(typeHnd, nodes, numNodes); +} + bool MyICJI::checkMethodModifier(CORINFO_METHOD_HANDLE hMethod, LPCSTR modifier, bool fOptional) { jitInstance->mc->cr->AddCall("checkMethodModifier"); diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 889d60bc37faa..8cea09127fd1e 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -2081,6 +2081,175 @@ CEEInfo::getFieldInClass(CORINFO_CLASS_HANDLE clsHnd, INT num) return result; } +static GetTypeLayoutResult GetTypeLayoutHelper( + MethodTable* pMT, + unsigned parentIndex, + unsigned baseOffs, + FieldDesc* pFD, + CORINFO_TYPE_LAYOUT_NODE* treeNodes, + size_t maxTreeNodes, + size_t* numTreeNodes) +{ + STANDARD_VM_CONTRACT; + + if (*numTreeNodes >= maxTreeNodes) + { + return GetTypeLayoutResult::Partial; + } + + unsigned structNodeIndex = (unsigned)(*numTreeNodes)++; + CORINFO_TYPE_LAYOUT_NODE& parNode = treeNodes[structNodeIndex]; + parNode.simdTypeHnd = NULL; + parNode.diagFieldHnd = CORINFO_FIELD_HANDLE(pFD); + parNode.parent = parentIndex; + parNode.offset = baseOffs; + parNode.size = pMT->GetNumInstanceFieldBytes(); + parNode.numFields = 0; + parNode.type = CorInfoType::CORINFO_TYPE_VALUECLASS; + parNode.hasSignificantPadding = false; + + EEClass* pClass = pMT->GetClass(); + if (pClass->IsNotTightlyPacked() && (!pClass->IsManagedSequential() || pClass->HasExplicitSize() || pClass->IsInlineArray())) + { + // Historically on the JIT side we did not consider types with GC + // pointers to have significant padding, even when they have explicit + // layout attributes. This retains the more liberal treatment and + // lets the JIT still optimize these cases. + if (!pMT->ContainsPointers() && pMT != g_TypedReferenceMT) + { + parNode.hasSignificantPadding = true; + } + } + + // The intrinsic SIMD/HW SIMD types have a lot of fields that the JIT does + // not care about since they are considered primitives by the JIT. + if (pMT->IsIntrinsicType()) + { + const char* nsName; + pMT->GetFullyQualifiedNameInfo(&nsName); + + if ((strcmp(nsName, "System.Runtime.Intrinsics") == 0) || + (strcmp(nsName, "System.Numerics") == 0)) + { + parNode.simdTypeHnd = CORINFO_CLASS_HANDLE(pMT); + if (parentIndex != UINT32_MAX) + { + return GetTypeLayoutResult::Success; + } + } + } + + ApproxFieldDescIterator fieldIterator(pMT, ApproxFieldDescIterator::INSTANCE_FIELDS); + for (FieldDesc* pFD = fieldIterator.Next(); pFD != NULL; pFD = fieldIterator.Next()) + { + CorElementType fieldType = pFD->GetFieldType(); + parNode.numFields++; + + if (fieldType == ELEMENT_TYPE_VALUETYPE) + { + MethodTable* pFieldMT = pFD->GetApproxFieldTypeHandleThrowing().AsMethodTable(); + GetTypeLayoutResult result = GetTypeLayoutHelper(pFieldMT, structNodeIndex, baseOffs + pFD->GetOffset(), pFD, treeNodes, maxTreeNodes, numTreeNodes); + if (result != GetTypeLayoutResult::Success) + { + return result; + } + } + else + { + if (*numTreeNodes >= maxTreeNodes) + { + return GetTypeLayoutResult::Partial; + } + + CorInfoType corInfoType = CEEInfo::asCorInfoType(fieldType); + _ASSERTE(corInfoType != CORINFO_TYPE_UNDEF); + + CORINFO_TYPE_LAYOUT_NODE& treeNode = treeNodes[(*numTreeNodes)++]; + treeNode.simdTypeHnd = NULL; + treeNode.diagFieldHnd = CORINFO_FIELD_HANDLE(pFD); + treeNode.parent = structNodeIndex; + treeNode.offset = baseOffs + pFD->GetOffset(); + treeNode.size = GetSizeForCorElementType(fieldType); + treeNode.numFields = 0; + treeNode.type = corInfoType; + treeNode.hasSignificantPadding = false; + } + + if (pMT->GetClass()->IsInlineArray()) + { + size_t treeNodeEnd = *numTreeNodes; + uint32_t elemSize = pFD->GetSize(); + uint32_t arrSize = pMT->GetNumInstanceFieldBytes(); + + for (uint32_t elemOffset = elemSize; elemOffset < arrSize; elemOffset += elemSize) + { + for (size_t templateTreeNodeIndex = structNodeIndex + 1; templateTreeNodeIndex < treeNodeEnd; templateTreeNodeIndex++) + { + if (*numTreeNodes >= maxTreeNodes) + { + return GetTypeLayoutResult::Partial; + } + + CORINFO_TYPE_LAYOUT_NODE& treeNode = treeNodes[(*numTreeNodes)++]; + treeNode = treeNodes[templateTreeNodeIndex]; + treeNode.offset += elemOffset; + + parNode.numFields++; + } + } + } + } + + return GetTypeLayoutResult::Success; +} + +GetTypeLayoutResult CEEInfo::getTypeLayout( + CORINFO_CLASS_HANDLE clsHnd, + CORINFO_TYPE_LAYOUT_NODE* treeNodes, + size_t* numTreeNodes) +{ + STANDARD_VM_CONTRACT; + + GetTypeLayoutResult result = GetTypeLayoutResult::Failure; + + JIT_TO_EE_TRANSITION_LEAF(); + + TypeHandle typeHnd(clsHnd); + + if (typeHnd.IsNativeValueType()) + { + if (*numTreeNodes > 0) + { + *numTreeNodes = 1; + treeNodes[0].simdTypeHnd = NULL; + treeNodes[0].diagFieldHnd = NULL; + treeNodes[0].parent = UINT32_MAX; + treeNodes[0].offset = 0; + treeNodes[0].size = typeHnd.GetSize(); + treeNodes[0].numFields = 0; + treeNodes[0].type = CORINFO_TYPE_VALUECLASS; + treeNodes[0].hasSignificantPadding = true; + result = GetTypeLayoutResult::Success; + } + else + { + result = GetTypeLayoutResult::Partial; + } + } + else if (typeHnd.IsValueType()) + { + MethodTable* pMT = typeHnd.AsMethodTable(); + + size_t maxTreeNodes = *numTreeNodes; + *numTreeNodes = 0; + result = GetTypeLayoutHelper(pMT, UINT32_MAX, 0, NULL, treeNodes, maxTreeNodes, numTreeNodes); + } + + EE_TO_JIT_TRANSITION_LEAF(); + + return result; +} + mdMethodDef CEEInfo::getMethodDefFromMethod(CORINFO_METHOD_HANDLE hMethod) { @@ -3657,13 +3826,6 @@ uint32_t CEEInfo::getClassAttribsInternal (CORINFO_CLASS_HANDLE clsHnd) if (pMT->IsByRefLike()) ret |= CORINFO_FLG_BYREF_LIKE; - if ((pClass->IsNotTightlyPacked() && (!pClass->IsManagedSequential() || pClass->HasExplicitSize())) || - pMT == g_TypedReferenceMT || - VMClsHnd.IsNativeValueType()) - { - ret |= CORINFO_FLG_CUSTOMLAYOUT; - } - if (pClass->IsUnsafeValueClass()) ret |= CORINFO_FLG_UNSAFE_VALUECLASS; } @@ -3686,9 +3848,7 @@ uint32_t CEEInfo::getClassAttribsInternal (CORINFO_CLASS_HANDLE clsHnd) ret |= CORINFO_FLG_DELEGATE; if (pClass->IsBeforeFieldInit()) - { ret |= CORINFO_FLG_BEFOREFIELDINIT; - } if (pClass->IsAbstract()) ret |= CORINFO_FLG_ABSTRACT;