diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index 08c46a06b421d6..92196d4423628c 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -3042,7 +3042,7 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere #if defined(UNIX_AMD64_ABI) if (varTypeIsStruct(varDsc)) { - CORINFO_CLASS_HANDLE typeHnd = varDsc->GetStructHnd(); + CORINFO_CLASS_HANDLE typeHnd = varDsc->GetLayout()->GetClassHandle(); assert(typeHnd != nullptr); SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR structDesc; compiler->eeGetSystemVAmd64PassStructInRegisterDescriptor(typeHnd, &structDesc); @@ -3350,7 +3350,7 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere { // This must be a SIMD type that's fully enregistered, but is passed as an HFA. // Each field will be inserted into the same destination register. - assert(varTypeIsSIMD(varDsc) && !compiler->isOpaqueSIMDType(varDsc->GetStructHnd())); + assert(varTypeIsSIMD(varDsc) && !compiler->isOpaqueSIMDType(varDsc->GetLayout())); assert(regArgTab[argNum].slot <= (int)varDsc->lvHfaSlots()); assert(argNum > 0); assert(regArgTab[argNum - 1].varNum == varNum); diff --git a/src/coreclr/jit/codegenxarch.cpp b/src/coreclr/jit/codegenxarch.cpp index 8a0bdd96dfbba1..2540e0b629ab0e 100644 --- a/src/coreclr/jit/codegenxarch.cpp +++ b/src/coreclr/jit/codegenxarch.cpp @@ -6361,7 +6361,7 @@ void CodeGen::genJmpMethod(GenTree* jmp) #if defined(UNIX_AMD64_ABI) if (varTypeIsStruct(varDsc)) { - CORINFO_CLASS_HANDLE typeHnd = varDsc->GetStructHnd(); + CORINFO_CLASS_HANDLE typeHnd = varDsc->GetLayout()->GetClassHandle(); assert(typeHnd != nullptr); SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR structDesc; @@ -6411,9 +6411,9 @@ void CodeGen::genJmpMethod(GenTree* jmp) // Register argument CLANG_FORMAT_COMMENT_ANCHOR; #ifdef TARGET_X86 - noway_assert( - isRegParamType(genActualType(varDsc->TypeGet())) || - (varTypeIsStruct(varDsc->TypeGet()) && compiler->isTrivialPointerSizedStruct(varDsc->GetStructHnd()))); + noway_assert(isRegParamType(genActualType(varDsc->TypeGet())) || + ((varDsc->TypeGet() == TYP_STRUCT) && + compiler->isTrivialPointerSizedStruct(varDsc->GetLayout()->GetClassHandle()))); #else noway_assert(isRegParamType(genActualType(varDsc->TypeGet()))); #endif // TARGET_X86 diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index 791c0740835edc..c14230290d4d25 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -10280,6 +10280,10 @@ void Compiler::EnregisterStats::RecordLocal(const LclVarDsc* varDsc) m_stressPoisonImplicitByrefs++; break; + case AddressExposedReason::EXTERNALLY_VISIBLE_IMPLICITLY: + m_externallyVisibleImplicitly++; + break; + default: unreached(); break; @@ -10376,5 +10380,6 @@ void Compiler::EnregisterStats::Dump(FILE* fout) const PRINT_STATS(m_stressLclFld, m_addrExposed); PRINT_STATS(m_dispatchRetBuf, m_addrExposed); PRINT_STATS(m_stressPoisonImplicitByrefs, m_addrExposed); + PRINT_STATS(m_externallyVisibleImplicitly, m_addrExposed); } #endif // TRACK_ENREG_STATS diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 6010e312e49d7f..912f52d7f27385 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -476,6 +476,9 @@ enum class AddressExposedReason STRESS_LCL_FLD, // Stress mode replaces localVar with localFld and makes them addrExposed. DISPATCH_RET_BUF, // Caller return buffer dispatch. STRESS_POISON_IMPLICIT_BYREFS, // This is an implicit byref we want to poison. + EXTERNALLY_VISIBLE_IMPLICITLY, // Local is visible externally without explicit escape in JIT IR. + // For example because it is used by GC or is the outgoing arg area + // that belongs to callees. }; #endif // DEBUG @@ -1082,6 +1085,7 @@ class LclVarDsc lvStkOffs = offset; } + // TODO-Cleanup: Remove this in favor of GetLayout()->Size/genTypeSize(lvType). unsigned lvExactSize; // (exact) size of a STRUCT/SIMD/BLK local in bytes. unsigned lvSize() const; @@ -1090,25 +1094,9 @@ class LclVarDsc unsigned lvSlotNum; // original slot # (if remapped) - // class handle for the local or null if not known or not a class, - // for a struct handle use `GetStructHnd()`. + // class handle for the local or null if not known or not a class CORINFO_CLASS_HANDLE lvClassHnd; - // Get class handle for a struct local or implicitByRef struct local. - CORINFO_CLASS_HANDLE GetStructHnd() const - { -#ifdef FEATURE_SIMD - if (lvSIMDType && (m_layout == nullptr)) - { - return NO_CLASS_HANDLE; - } -#endif - - CORINFO_CLASS_HANDLE structHnd = GetLayout()->GetClassHandle(); - assert(structHnd != NO_CLASS_HANDLE); - return structHnd; - } - private: ClassLayout* m_layout; // layout info for structs @@ -1191,6 +1179,16 @@ class LclVarDsc m_layout = layout; } + // Grow the size of a block layout local. + void GrowBlockLayout(ClassLayout* layout) + { + assert(varTypeIsStruct(lvType)); + assert((m_layout == nullptr) || (m_layout->IsBlockLayout() && (m_layout->GetSize() <= layout->GetSize()))); + assert(layout->IsBlockLayout()); + m_layout = layout; + lvExactSize = layout->GetSize(); + } + SsaDefArray lvPerSsaData; // Returns the address of the per-Ssa data for the given ssaNum (which is required @@ -3272,7 +3270,7 @@ class Compiler // or if the inlinee has GC ref locals. #if FEATURE_FIXED_OUT_ARGS - unsigned lvaOutgoingArgSpaceVar; // dummy TYP_LCLBLK var for fixed outgoing argument space + unsigned lvaOutgoingArgSpaceVar; // var that represents outgoing argument space PhasedVar lvaOutgoingArgSpaceSize; // size of fixed outgoing argument space #endif // FEATURE_FIXED_OUT_ARGS @@ -3309,7 +3307,7 @@ class Compiler #if !defined(FEATURE_EH_FUNCLETS) // This is used for the callable handlers - unsigned lvaShadowSPslotsVar; // TYP_BLK variable for all the shadow SP slots + unsigned lvaShadowSPslotsVar; // Block-layout TYP_STRUCT variable for all the shadow SP slots #endif // FEATURE_EH_FUNCLETS int lvaCachedGenericContextArgOffs; @@ -3520,7 +3518,7 @@ class Compiler bool lvaIsMultiregStruct(LclVarDsc* varDsc, bool isVararg); // If the local is a TYP_STRUCT, get/set a class handle describing it - CORINFO_CLASS_HANDLE lvaGetStruct(unsigned varNum); + void lvaSetStruct(unsigned varNum, ClassLayout* layout, bool unsafeValueClsCheck); void lvaSetStruct(unsigned varNum, CORINFO_CLASS_HANDLE typeHnd, bool unsafeValueClsCheck); void lvaSetStructUsedAsVarArg(unsigned varNum); @@ -8715,6 +8713,16 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX return true; } + bool isOpaqueSIMDType(ClassLayout* layout) const + { + if (layout->IsBlockLayout()) + { + return true; + } + + return isOpaqueSIMDType(layout->GetClassHandle()); + } + // Returns true if the lclVar is an opaque SIMD type. bool isOpaqueSIMDLclVar(const LclVarDsc* varDsc) const { @@ -8722,7 +8730,13 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX { return false; } - return isOpaqueSIMDType(varDsc->GetStructHnd()); + + if (varDsc->GetLayout() == nullptr) + { + return true; + } + + return isOpaqueSIMDType(varDsc->GetLayout()); } bool isNumericsNamespace(const char* ns) @@ -10328,6 +10342,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX unsigned m_dispatchRetBuf; unsigned m_wideIndir; unsigned m_stressPoisonImplicitByrefs; + unsigned m_externallyVisibleImplicitly; public: void RecordLocal(const LclVarDsc* varDsc); diff --git a/src/coreclr/jit/flowgraph.cpp b/src/coreclr/jit/flowgraph.cpp index 1fc11f2be50b77..00690d08e43013 100644 --- a/src/coreclr/jit/flowgraph.cpp +++ b/src/coreclr/jit/flowgraph.cpp @@ -1894,9 +1894,8 @@ void Compiler::fgAddReversePInvokeEnterExit() lvaReversePInvokeFrameVar = lvaGrabTempWithImplicitUse(false DEBUGARG("Reverse Pinvoke FrameVar")); - LclVarDsc* varDsc = lvaGetDesc(lvaReversePInvokeFrameVar); - varDsc->lvType = TYP_BLK; - varDsc->lvExactSize = eeGetEEInfo()->sizeOfReversePInvokeFrame; + LclVarDsc* varDsc = lvaGetDesc(lvaReversePInvokeFrameVar); + lvaSetStruct(lvaReversePInvokeFrameVar, typGetBlkLayout(eeGetEEInfo()->sizeOfReversePInvokeFrame), false); // Add enter pinvoke exit callout at the start of prolog @@ -2667,9 +2666,8 @@ PhaseStatus Compiler::fgAddInternal() lvaSetVarAddrExposed(lvaInlinedPInvokeFrameVar DEBUGARG(AddressExposedReason::ESCAPE_ADDRESS)); LclVarDsc* varDsc = lvaGetDesc(lvaInlinedPInvokeFrameVar); - varDsc->lvType = TYP_BLK; // Make room for the inlined frame. - varDsc->lvExactSize = eeGetEEInfo()->inlinedCallFrameInfo.size; + lvaSetStruct(lvaInlinedPInvokeFrameVar, typGetBlkLayout(eeGetEEInfo()->inlinedCallFrameInfo.size), false); #if FEATURE_FIXED_OUT_ARGS // Grab and reserve space for TCB, Frame regs used in PInvoke epilog to pop the inlined frame. // See genPInvokeMethodEpilog() for use of the grabbed var. This is only necessary if we are @@ -2678,8 +2676,7 @@ PhaseStatus Compiler::fgAddInternal() { lvaPInvokeFrameRegSaveVar = lvaGrabTempWithImplicitUse(false DEBUGARG("PInvokeFrameRegSave Var")); varDsc = lvaGetDesc(lvaPInvokeFrameRegSaveVar); - varDsc->lvType = TYP_BLK; - varDsc->lvExactSize = 2 * REGSIZE_BYTES; + lvaSetStruct(lvaPInvokeFrameRegSaveVar, typGetBlkLayout(2 * REGSIZE_BYTES), false); } #endif } @@ -3043,6 +3040,7 @@ PhaseStatus Compiler::fgSimpleLowering() // attempt later will cause an assert. lvaOutgoingArgSpaceSize = outgoingArgSpaceSize; lvaOutgoingArgSpaceSize.MarkAsReadOnly(); + lvaGetDesc(lvaOutgoingArgSpaceVar)->GrowBlockLayout(typGetBlkLayout(outgoingArgSpaceSize)); #endif // FEATURE_FIXED_OUT_ARGS diff --git a/src/coreclr/jit/gcencode.cpp b/src/coreclr/jit/gcencode.cpp index fe710f509fcdb3..cd0b3ae918b294 100644 --- a/src/coreclr/jit/gcencode.cpp +++ b/src/coreclr/jit/gcencode.cpp @@ -4183,7 +4183,8 @@ void GCInfo::gcMakeRegPtrTable( // If this is a TYP_STRUCT, handle its GC pointers. // Note that the enregisterable struct types cannot have GC pointers in them. - if ((varDsc->TypeGet() == TYP_STRUCT) && varDsc->lvOnFrame && (varDsc->lvExactSize >= TARGET_POINTER_SIZE)) + if ((varDsc->TypeGet() == TYP_STRUCT) && varDsc->GetLayout()->HasGCPtr() && varDsc->lvOnFrame && + (varDsc->lvExactSize >= TARGET_POINTER_SIZE)) { ClassLayout* layout = varDsc->GetLayout(); unsigned slots = layout->GetSlotCount(); diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 2fc111827f6edd..23a8d51a48cbf9 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -11302,27 +11302,6 @@ void Compiler::gtDispLclVarStructType(unsigned lclNum) assert(layout != nullptr); gtDispClassLayout(layout, type); } - else if (type == TYP_LCLBLK) - { -#if FEATURE_FIXED_OUT_ARGS - assert(lclNum == lvaOutgoingArgSpaceVar); - // Since lvaOutgoingArgSpaceSize is a PhasedVar we can't read it for Dumping until - // after we set it to something. - if (lvaOutgoingArgSpaceSize.HasFinalValue()) - { - // A PhasedVar can't be directly used as an arg to a variadic function - unsigned value = lvaOutgoingArgSpaceSize; - printf("<%u> ", value); - } - else - { - printf(" "); // The value hasn't yet been determined - } -#else - assert(!"Unknown size"); - NO_WAY("Target doesn't support TYP_LCLBLK"); -#endif // FEATURE_FIXED_OUT_ARGS - } } #if defined(DEBUG) && defined(TARGET_ARM64) @@ -11719,7 +11698,7 @@ void Compiler::gtDispLeaf(GenTree* tree, IndentStack* indentStack) else #endif // !defined(TARGET_64BIT) { - CORINFO_CLASS_HANDLE typeHnd = varDsc->GetStructHnd(); + CORINFO_CLASS_HANDLE typeHnd = varDsc->GetLayout()->GetClassHandle(); CORINFO_FIELD_HANDLE fldHnd = info.compCompHnd->getFieldInClass(typeHnd, fieldVarDsc->lvFldOrdinal); fieldName = eeGetFieldName(fldHnd, true, buffer, sizeof(buffer)); @@ -15813,6 +15792,21 @@ GenTree* Compiler::gtNewTempAssign( if (dstTyp == TYP_UNDEF) { varDsc->lvType = dstTyp = genActualType(valTyp); + + if (varTypeIsStruct(dstTyp)) + { + if (varTypeIsSIMD(dstTyp)) + { + varDsc->lvExactSize = genTypeSize(dstTyp); +#ifdef FEATURE_SIMD + varDsc->lvSIMDType = 1; +#endif + } + else + { + lvaSetStruct(tmp, val->GetLayout(this), false); + } + } } #if FEATURE_SIMD @@ -15880,40 +15874,13 @@ GenTree* Compiler::gtNewTempAssign( GenTree* asg; GenTree* dest = gtNewLclvNode(tmp, dstTyp); - // With first-class structs, we should be propagating the class handle on all non-primitive - // struct types. We don't have a convenient way to do that for all SIMD temps, since some - // internal trees use SIMD types that are not used by the input IL. In this case, we allow - // a null type handle and derive the necessary information about the type from its varType. - CORINFO_CLASS_HANDLE valStructHnd = gtGetStructHandleIfPresent(val); - if (varTypeIsStruct(varDsc) && (valStructHnd == NO_CLASS_HANDLE) && !varTypeIsSIMD(valTyp)) - { - // There are some cases where we do not have a struct handle on the return value: - // 1. Handle-less BLK/LCL_FLD nodes. - // 2. The zero constant created by local assertion propagation. - // In these cases, we can use the type of the local for the assignment. - assert(val->gtEffectiveVal(true)->OperIs(GT_IND, GT_BLK, GT_LCL_FLD, GT_CNS_INT)); - valStructHnd = lvaGetDesc(tmp)->GetStructHnd(); - assert(valStructHnd != NO_CLASS_HANDLE); - } - - if ((valStructHnd != NO_CLASS_HANDLE) && val->IsConstInitVal()) + if (val->IsConstInitVal()) { asg = gtNewAssignNode(dest, val); } - else if (varTypeIsStruct(varDsc) && ((valStructHnd != NO_CLASS_HANDLE) || varTypeIsSIMD(valTyp))) + else if (varTypeIsStruct(varDsc)) { - // The struct value may be be a child of a GT_COMMA due to explicit null checks of indirs/fields. GenTree* valx = val->gtEffectiveVal(/*commaOnly*/ true); - - if (valStructHnd != NO_CLASS_HANDLE) - { - lvaSetStruct(tmp, valStructHnd, false); - } - else - { - assert(valx->gtOper != GT_OBJ); - } - valx->gtFlags |= GTF_DONT_CSE; asg = impAssignStruct(dest, val, CHECK_SPILL_NONE, pAfterStmt, di, block); } @@ -17705,8 +17672,14 @@ CORINFO_CLASS_HANDLE Compiler::gtGetStructHandleIfPresent(GenTree* tree) } break; case GT_LCL_VAR: - structHnd = lvaGetDesc(tree->AsLclVar())->GetStructHnd(); + { + LclVarDsc* dsc = lvaGetDesc(tree->AsLclVar()); + if ((dsc->GetLayout() != nullptr) && !dsc->GetLayout()->IsBlockLayout()) + { + structHnd = dsc->GetLayout()->GetClassHandle(); + } break; + } case GT_RETURN: structHnd = gtGetStructHandleIfPresent(tree->AsOp()->gtOp1); break; diff --git a/src/coreclr/jit/gschecks.cpp b/src/coreclr/jit/gschecks.cpp index 9890c19d49678b..cfdf3c4269d563 100644 --- a/src/coreclr/jit/gschecks.cpp +++ b/src/coreclr/jit/gschecks.cpp @@ -454,7 +454,7 @@ void Compiler::gsParamsToShadows() { // We don't need unsafe value cls check here since we are copying the params and this flag // would have been set on the original param before reaching here. - lvaSetStruct(shadowVarNum, varDsc->GetStructHnd(), false); + lvaSetStruct(shadowVarNum, varDsc->GetLayout(), false); shadowVarDsc->lvIsMultiRegArg = varDsc->lvIsMultiRegArg; shadowVarDsc->lvIsMultiRegRet = varDsc->lvIsMultiRegRet; } diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index f09909d0597829..65d8da8190d092 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -2798,10 +2798,6 @@ typeInfo Compiler::verMakeTypeInfoForLocal(unsigned lclNum) { LclVarDsc* varDsc = lvaGetDesc(lclNum); - if ((varDsc->TypeGet() == TYP_BLK) || (varDsc->TypeGet() == TYP_LCLBLK)) - { - return typeInfo(); - } if (varDsc->TypeGet() == TYP_BYREF) { // Pretend all byrefs are pointing to bytes. @@ -2809,7 +2805,7 @@ typeInfo Compiler::verMakeTypeInfoForLocal(unsigned lclNum) } if (varTypeIsStruct(varDsc)) { - return typeInfo(TI_STRUCT, varDsc->GetStructHnd()); + return typeInfo(TI_STRUCT, varDsc->GetLayout()->GetClassHandle()); } return typeInfo(varDsc->TypeGet()); @@ -3808,20 +3804,22 @@ void Compiler::impImportNewObjArray(CORINFO_RESOLVED_TOKEN* pResolvedToken, CORI GenTree* node; + unsigned dimensionsSize = pCallInfo->sig.numArgs * sizeof(INT32); // Reuse the temp used to pass the array dimensions to avoid bloating // the stack frame in case there are multiple calls to multi-dim array // constructors within a single method. if (lvaNewObjArrayArgs == BAD_VAR_NUM) { - lvaNewObjArrayArgs = lvaGrabTemp(false DEBUGARG("NewObjArrayArgs")); - lvaTable[lvaNewObjArrayArgs].lvType = TYP_BLK; - lvaTable[lvaNewObjArrayArgs].lvExactSize = 0; + lvaNewObjArrayArgs = lvaGrabTemp(false DEBUGARG("NewObjArrayArgs")); + lvaSetStruct(lvaNewObjArrayArgs, typGetBlkLayout(dimensionsSize), false); } // Increase size of lvaNewObjArrayArgs to be the largest size needed to hold 'numArgs' integers // for our call to CORINFO_HELP_NEW_MDARR. - lvaTable[lvaNewObjArrayArgs].lvExactSize = - max(lvaTable[lvaNewObjArrayArgs].lvExactSize, pCallInfo->sig.numArgs * sizeof(INT32)); + if (dimensionsSize > lvaTable[lvaNewObjArrayArgs].lvExactSize) + { + lvaTable[lvaNewObjArrayArgs].GrowBlockLayout(typGetBlkLayout(dimensionsSize)); + } // The side-effects may include allocation of more multi-dimensional arrays. Spill all side-effects // to ensure that the shared lvaNewObjArrayArgs local variable is only ever used to pass arguments @@ -9863,7 +9861,7 @@ void Compiler::impImportBlockCode(BasicBlock* block) } // If the localloc is not in a loop and its size is a small constant, - // create a new local var of TYP_BLK and return its address. + // create a new block layout struct local var and return its address. { bool convertedToLocal = false; @@ -9899,13 +9897,11 @@ void Compiler::impImportBlockCode(BasicBlock* block) const unsigned stackallocAsLocal = lvaGrabTemp(false DEBUGARG("stackallocLocal")); JITDUMP("Converting stackalloc of %zd bytes to new local V%02u\n", allocSize, stackallocAsLocal); - lvaTable[stackallocAsLocal].lvType = TYP_BLK; - lvaTable[stackallocAsLocal].lvExactSize = (unsigned)allocSize; + lvaSetStruct(stackallocAsLocal, typGetBlkLayout((unsigned)allocSize), false); lvaTable[stackallocAsLocal].lvHasLdAddrOp = true; lvaTable[stackallocAsLocal].lvIsUnsafeBuffer = true; - op1 = gtNewLclvNode(stackallocAsLocal, TYP_BLK); - op1 = gtNewOperNode(GT_ADDR, TYP_I_IMPL, op1); - convertedToLocal = true; + op1 = gtNewLclVarAddrNode(stackallocAsLocal); + convertedToLocal = true; if (!this->opts.compDbgEnC) { diff --git a/src/coreclr/jit/layout.cpp b/src/coreclr/jit/layout.cpp index 26c77f9dfbcff9..2344c301bc9e69 100644 --- a/src/coreclr/jit/layout.cpp +++ b/src/coreclr/jit/layout.cpp @@ -15,7 +15,8 @@ class ClassLayoutTable // Each layout is assigned a number, starting with TYP_UNKNOWN + 1. This way one could use a single // unsigned value to represent the notion of type - values below TYP_UNKNOWN are var_types and values // above it are struct layouts. - static constexpr unsigned FirstLayoutNum = TYP_UNKNOWN + 1; + static constexpr unsigned ZeroSizedBlockLayoutNum = TYP_UNKNOWN + 1; + static constexpr unsigned FirstLayoutNum = TYP_UNKNOWN + 2; typedef JitHashTable, unsigned> BlkLayoutIndexMap; typedef JitHashTable, unsigned> ObjLayoutIndexMap; @@ -36,21 +37,36 @@ class ClassLayoutTable unsigned m_layoutCount; // The capacity of m_layoutLargeArray (when more than 3 layouts are stored). unsigned m_layoutLargeCapacity; + // We furthermore fast-path the 0-sized block layout which is used for + // block locals that may grow (e.g. the outgoing arg area in every non-x86 + // compilation). + ClassLayout m_zeroSizedBlockLayout; public: - ClassLayoutTable() : m_layoutCount(0), m_layoutLargeCapacity(0) + ClassLayoutTable() : m_layoutCount(0), m_layoutLargeCapacity(0), m_zeroSizedBlockLayout(0) { } - // Get the layout number (FirstLayoutNum-based) of the specified layout. + // Get a number that uniquely identifies the specified layout. unsigned GetLayoutNum(ClassLayout* layout) const { + if (layout == &m_zeroSizedBlockLayout) + { + return ZeroSizedBlockLayoutNum; + } + return GetLayoutIndex(layout) + FirstLayoutNum; } - // Get the layout having the specified layout number (FirstLayoutNum-based) + // Get the layout that corresponds to the specified identifier number. ClassLayout* GetLayoutByNum(unsigned num) const { + if (num == ZeroSizedBlockLayoutNum) + { + // Fine to cast away const as ClassLayout is immutable + return const_cast(&m_zeroSizedBlockLayout); + } + assert(num >= FirstLayoutNum); return GetLayoutByIndex(num - FirstLayoutNum); } @@ -58,12 +74,22 @@ class ClassLayoutTable // Get the layout having the specified size but no class handle. ClassLayout* GetBlkLayout(Compiler* compiler, unsigned blockSize) { + if (blockSize == 0) + { + return &m_zeroSizedBlockLayout; + } + return GetLayoutByIndex(GetBlkLayoutIndex(compiler, blockSize)); } - // Get the number of a layout having the specified size but no class handle. + // Get a number that uniquely identifies a layout having the specified size but no class handle. unsigned GetBlkLayoutNum(Compiler* compiler, unsigned blockSize) { + if (blockSize == 0) + { + return ZeroSizedBlockLayoutNum; + } + return GetBlkLayoutIndex(compiler, blockSize) + FirstLayoutNum; } @@ -73,7 +99,7 @@ class ClassLayoutTable return GetLayoutByIndex(GetObjLayoutIndex(compiler, classHandle)); } - // Get the number of a layout for the specified class handle. + // Get a number that uniquely identifies a layout for the specified class handle. unsigned GetObjLayoutNum(Compiler* compiler, CORINFO_CLASS_HANDLE classHandle) { return GetObjLayoutIndex(compiler, classHandle) + FirstLayoutNum; @@ -102,6 +128,7 @@ class ClassLayoutTable unsigned GetLayoutIndex(ClassLayout* layout) const { assert(layout != nullptr); + assert(layout != &m_zeroSizedBlockLayout); if (HasSmallCapacity()) { @@ -128,6 +155,9 @@ class ClassLayoutTable unsigned GetBlkLayoutIndex(Compiler* compiler, unsigned blockSize) { + // The 0-sized block layout has its own fast path. + assert(blockSize != 0); + if (HasSmallCapacity()) { for (unsigned i = 0; i < m_layoutCount; i++) diff --git a/src/coreclr/jit/layout.h b/src/coreclr/jit/layout.h index b444a9aa63b410..732b46fdb89570 100644 --- a/src/coreclr/jit/layout.h +++ b/src/coreclr/jit/layout.h @@ -116,8 +116,6 @@ class ClassLayout bool IsValueClass() const { - assert(!IsBlockLayout()); - return m_isValueClass; } diff --git a/src/coreclr/jit/lclmorph.cpp b/src/coreclr/jit/lclmorph.cpp index 78994398456b0a..0a55c73d1272e4 100644 --- a/src/coreclr/jit/lclmorph.cpp +++ b/src/coreclr/jit/lclmorph.cpp @@ -988,7 +988,7 @@ class LocalAddressVisitor final : public GenTreeVisitor { isWide = endOffset.Value() > m_compiler->lvaLclExactSize(lclNum); - if (varDsc->TypeGet() == TYP_BLK) + if ((varDsc->TypeGet() == TYP_STRUCT) && varDsc->GetLayout()->IsBlockLayout()) { // TODO-CQ: TYP_BLK used to always be exposed here. This is in principle not necessary, but // not doing so would require VN changes. For now, exposing gets better CQ as otherwise the diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index 054734114cf77f..7bca65eda06b80 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -1636,16 +1636,6 @@ void Compiler::lvSetMinOptsDoNotEnreg() } } -/***************************************************************************** - * Returns the handle to the class of the local variable varNum - */ - -CORINFO_CLASS_HANDLE Compiler::lvaGetStruct(unsigned varNum) -{ - const LclVarDsc* varDsc = lvaGetDesc(varNum); - return varDsc->GetStructHnd(); -} - //-------------------------------------------------------------------------------------------- // lvaFieldOffsetCmp - a static compare function passed to jitstd::sort() by Compiler::StructPromotionHelper; // compares fields' offsets. @@ -1994,7 +1984,12 @@ bool Compiler::StructPromotionHelper::CanPromoteStructVar(unsigned lclNum) return false; } - CORINFO_CLASS_HANDLE typeHnd = varDsc->GetStructHnd(); + if (varDsc->GetLayout()->IsBlockLayout()) + { + return false; + } + + CORINFO_CLASS_HANDLE typeHnd = varDsc->GetLayout()->GetClassHandle(); assert(typeHnd != NO_CLASS_HANDLE); bool canPromote = CanPromoteStructType(typeHnd); @@ -2088,7 +2083,7 @@ bool Compiler::StructPromotionHelper::ShouldPromoteStructVar(unsigned lclNum) { LclVarDsc* varDsc = compiler->lvaGetDesc(lclNum); assert(varTypeIsStruct(varDsc)); - assert(varDsc->GetStructHnd() == structPromotionInfo.typeHnd); + assert(varDsc->GetLayout()->GetClassHandle() == structPromotionInfo.typeHnd); assert(structPromotionInfo.canPromote); bool shouldPromote = true; @@ -2290,10 +2285,6 @@ Compiler::lvaStructFieldInfo Compiler::StructPromotionHelper::GetFieldInfo(CORIN // bool Compiler::StructPromotionHelper::TryPromoteStructField(lvaStructFieldInfo& fieldInfo) { - // Size of TYP_BLK, TYP_FUNC, TYP_VOID and TYP_STRUCT is zero. - // Early out if field type is other than TYP_STRUCT. - // This is a defensive check as we don't expect a struct to have - // fields of TYP_BLK, TYP_FUNC or TYP_VOID. if (fieldInfo.fldType != TYP_STRUCT) { return false; @@ -2382,7 +2373,7 @@ void Compiler::StructPromotionHelper::PromoteStructVar(unsigned lclNum) // We should never see a reg-sized non-field-addressed struct here. assert(!varDsc->lvRegStruct); - assert(varDsc->GetStructHnd() == structPromotionInfo.typeHnd); + assert(varDsc->GetLayout()->GetClassHandle() == structPromotionInfo.typeHnd); assert(structPromotionInfo.canPromote); varDsc->lvFieldCnt = structPromotionInfo.fieldCnt; @@ -2392,14 +2383,15 @@ void Compiler::StructPromotionHelper::PromoteStructVar(unsigned lclNum) varDsc->lvCustomLayout = structPromotionInfo.customLayout; #ifdef DEBUG - // Don't change the source to a TYP_BLK either. + // Don't stress this in LCL_FLD stress. varDsc->lvKeepType = 1; #endif #ifdef DEBUG if (compiler->verbose) { - printf("\nPromoting struct local V%02u (%s):", lclNum, compiler->eeGetClassName(varDsc->GetStructHnd())); + printf("\nPromoting struct local V%02u (%s):", lclNum, + compiler->eeGetClassName(varDsc->GetLayout()->GetClassHandle())); } #endif @@ -2879,7 +2871,7 @@ bool Compiler::lvaIsMultiregStruct(LclVarDsc* varDsc, bool isVarArg) { if (varTypeIsStruct(varDsc->TypeGet())) { - CORINFO_CLASS_HANDLE clsHnd = varDsc->GetStructHnd(); + CORINFO_CLASS_HANDLE clsHnd = varDsc->GetLayout()->GetClassHandle(); structPassingKind howToPassStruct; var_types type = getArgTypeForStruct(clsHnd, &howToPassStruct, isVarArg, varDsc->lvExactSize); @@ -2901,10 +2893,15 @@ bool Compiler::lvaIsMultiregStruct(LclVarDsc* varDsc, bool isVarArg) return false; } -/***************************************************************************** - * Set the lvClass for a local variable of a struct type */ - -void Compiler::lvaSetStruct(unsigned varNum, CORINFO_CLASS_HANDLE typeHnd, bool unsafeValueClsCheck) +//------------------------------------------------------------------------ +// lvaSetStruct: Set the type of a local to a struct, given a layout. +// +// Arguments: +// varNum - The local +// layout - The layout +// unsafeValueClsCheck - Whether to check if we should potentially emit a GS cookie due to this local. +// +void Compiler::lvaSetStruct(unsigned varNum, ClassLayout* layout, bool unsafeValueClsCheck) { LclVarDsc* varDsc = lvaGetDesc(varNum); @@ -2915,24 +2912,23 @@ void Compiler::lvaSetStruct(unsigned varNum, CORINFO_CLASS_HANDLE typeHnd, bool } if (varDsc->GetLayout() == nullptr) { - ClassLayout* layout = typGetObjLayout(typeHnd); varDsc->SetLayout(layout); assert(varDsc->lvExactSize == 0); varDsc->lvExactSize = layout->GetSize(); - assert(varDsc->lvExactSize != 0); + assert(layout->IsBlockLayout() || (varDsc->lvExactSize != 0)); if (layout->IsValueClass()) { - CorInfoType simdBaseJitType = CORINFO_TYPE_UNDEF; - varDsc->lvType = impNormStructType(typeHnd, &simdBaseJitType); + varDsc->lvType = layout->GetType(); #if FEATURE_IMPLICIT_BYREFS // Mark implicit byref struct parameters if (varDsc->lvIsParam && !varDsc->lvIsStructField) { structPassingKind howToReturnStruct; - getArgTypeForStruct(typeHnd, &howToReturnStruct, this->info.compIsVarArgs, varDsc->lvExactSize); + getArgTypeForStruct(layout->GetClassHandle(), &howToReturnStruct, this->info.compIsVarArgs, + varDsc->lvExactSize); if (howToReturnStruct == SPK_ByReference) { @@ -2943,31 +2939,34 @@ void Compiler::lvaSetStruct(unsigned varNum, CORINFO_CLASS_HANDLE typeHnd, bool #endif // FEATURE_IMPLICIT_BYREFS #if FEATURE_SIMD - if (simdBaseJitType != CORINFO_TYPE_UNDEF) + if (varTypeIsSIMD(layout->GetType())) { - assert(varTypeIsSIMD(varDsc)); - varDsc->lvSIMDType = true; - varDsc->SetSimdBaseJitType(simdBaseJitType); + CorInfoType simdBaseJitType = CORINFO_TYPE_UNDEF; + impNormStructType(layout->GetClassHandle(), &simdBaseJitType); + + if (simdBaseJitType != CORINFO_TYPE_UNDEF) + { + assert(varTypeIsSIMD(varDsc)); + varDsc->lvSIMDType = true; + varDsc->SetSimdBaseJitType(simdBaseJitType); + } } #endif // FEATURE_SIMD - if (GlobalJitOptions::compFeatureHfa) + // For structs that are small enough, we check and set HFA element type + if (GlobalJitOptions::compFeatureHfa && (layout->GetSize() <= MAX_PASS_MULTIREG_BYTES)) { - // For structs that are small enough, we check and set HFA element type - if (varDsc->lvExactSize <= MAX_PASS_MULTIREG_BYTES) + // hfaType is set to float, double or SIMD type if it is an HFA, otherwise TYP_UNDEF + var_types hfaType = GetHfaType(layout->GetClassHandle()); + if (varTypeIsValidHfaType(hfaType)) { - // hfaType is set to float, double or SIMD type if it is an HFA, otherwise TYP_UNDEF - var_types hfaType = GetHfaType(typeHnd); - if (varTypeIsValidHfaType(hfaType)) - { - varDsc->SetHfaType(hfaType); - - // hfa variables can never contain GC pointers - assert(!layout->HasGCPtr()); - // The size of this struct should be evenly divisible by 4 or 8 - assert((varDsc->lvExactSize % genTypeSize(hfaType)) == 0); - // The number of elements in the HFA should fit into our MAX_ARG_REG_COUNT limit - assert((varDsc->lvExactSize / genTypeSize(hfaType)) <= MAX_ARG_REG_COUNT); - } + varDsc->SetHfaType(hfaType); + + // hfa variables can never contain GC pointers + assert(!layout->HasGCPtr()); + // The size of this struct should be evenly divisible by 4 or 8 + assert((varDsc->lvExactSize % genTypeSize(hfaType)) == 0); + // The number of elements in the HFA should fit into our MAX_ARG_REG_COUNT limit + assert((varDsc->lvExactSize / genTypeSize(hfaType)) <= MAX_ARG_REG_COUNT); } } } @@ -2977,47 +2976,62 @@ void Compiler::lvaSetStruct(unsigned varNum, CORINFO_CLASS_HANDLE typeHnd, bool #if FEATURE_SIMD assert(!varTypeIsSIMD(varDsc) || (varDsc->GetSimdBaseType() != TYP_UNKNOWN)); #endif // FEATURE_SIMD - ClassLayout* layout = typGetObjLayout(typeHnd); assert(ClassLayout::AreCompatible(varDsc->GetLayout(), layout)); // Inlining could replace a canon struct type with an exact one. varDsc->SetLayout(layout); - assert(varDsc->lvExactSize != 0); + assert(layout->IsBlockLayout() || (varDsc->lvExactSize != 0)); } + if (!layout->IsBlockLayout()) + { #ifndef TARGET_64BIT - bool fDoubleAlignHint = false; + bool fDoubleAlignHint = false; #ifdef TARGET_X86 - fDoubleAlignHint = true; + fDoubleAlignHint = true; #endif - if (info.compCompHnd->getClassAlignmentRequirement(typeHnd, fDoubleAlignHint) == 8) - { -#ifdef DEBUG - if (verbose) + if (info.compCompHnd->getClassAlignmentRequirement(layout->GetClassHandle(), fDoubleAlignHint) == 8) { - printf("Marking struct in V%02i with double align flag\n", varNum); - } +#ifdef DEBUG + if (verbose) + { + printf("Marking struct in V%02i with double align flag\n", varNum); + } #endif - varDsc->lvStructDoubleAlign = 1; - } + varDsc->lvStructDoubleAlign = 1; + } #endif // not TARGET_64BIT - unsigned classAttribs = info.compCompHnd->getClassAttribs(typeHnd); + unsigned classAttribs = info.compCompHnd->getClassAttribs(layout->GetClassHandle()); - // Check whether this local is an unsafe value type and requires GS cookie protection. - // GS checks require the stack to be re-ordered, which can't be done with EnC. - if (unsafeValueClsCheck && (classAttribs & CORINFO_FLG_UNSAFE_VALUECLASS) && !opts.compDbgEnC) - { - setNeedsGSSecurityCookie(); - compGSReorderStackLayout = true; - varDsc->lvIsUnsafeBuffer = true; - } + // Check whether this local is an unsafe value type and requires GS cookie protection. + // GS checks require the stack to be re-ordered, which can't be done with EnC. + if (unsafeValueClsCheck && (classAttribs & CORINFO_FLG_UNSAFE_VALUECLASS) && !opts.compDbgEnC) + { + setNeedsGSSecurityCookie(); + compGSReorderStackLayout = true; + varDsc->lvIsUnsafeBuffer = true; + } #ifdef DEBUG - if (JitConfig.EnableExtraSuperPmiQueries()) - { - makeExtraStructQueries(typeHnd, 2); - } + if (JitConfig.EnableExtraSuperPmiQueries()) + { + makeExtraStructQueries(layout->GetClassHandle(), 2); + } #endif // DEBUG + } +} + +//------------------------------------------------------------------------ +// lvaSetStruct: Set the type of a local to a struct, given its type handle. +// +// Arguments: +// varNum - The local +// typeHnd - The type handle +// unsafeValueClsCheck - Whether to check if we should potentially emit a GS cookie due to this local. +// +void Compiler::lvaSetStruct(unsigned varNum, CORINFO_CLASS_HANDLE typeHnd, bool unsafeValueClsCheck) +{ + lvaSetStruct(varNum, typGetObjLayout(typeHnd), unsafeValueClsCheck); } #ifdef DEBUG @@ -3320,26 +3334,11 @@ unsigned Compiler::lvaLclSize(unsigned varNum) var_types varType = lvaTable[varNum].TypeGet(); - switch (varType) + if (varType == TYP_STRUCT) { - case TYP_STRUCT: - case TYP_BLK: - return lvaTable[varNum].lvSize(); - - case TYP_LCLBLK: -#if FEATURE_FIXED_OUT_ARGS - // Note that this operation performs a read of a PhasedVar - noway_assert(varNum == lvaOutgoingArgSpaceVar); - return lvaOutgoingArgSpaceSize; -#else // FEATURE_FIXED_OUT_ARGS - assert(!"Unknown size"); - NO_WAY("Target doesn't support TYP_LCLBLK"); - -#endif // FEATURE_FIXED_OUT_ARGS - - default: // This must be a primitive var. Fall out of switch statement - break; + return lvaTable[varNum].lvSize(); } + #ifdef TARGET_64BIT // We only need this Quirk for TARGET_64BIT if (lvaTable[varNum].lvQuirkToLong) @@ -3361,27 +3360,9 @@ unsigned Compiler::lvaLclExactSize(unsigned varNum) var_types varType = lvaTable[varNum].TypeGet(); - switch (varType) + if (varType == TYP_STRUCT) { - case TYP_STRUCT: - case TYP_BLK: - return lvaTable[varNum].lvExactSize; - - case TYP_LCLBLK: -#if FEATURE_FIXED_OUT_ARGS - // Note that this operation performs a read of a PhasedVar - noway_assert(lvaOutgoingArgSpaceSize >= 0); - noway_assert(varNum == lvaOutgoingArgSpaceVar); - return lvaOutgoingArgSpaceSize; - -#else // FEATURE_FIXED_OUT_ARGS - assert(!"Unknown size"); - NO_WAY("Target doesn't support TYP_LCLBLK"); - -#endif // FEATURE_FIXED_OUT_ARGS - - default: // This must be a primitive var. Fall out of switch statement - break; + return lvaTable[varNum].lvExactSize; } return genTypeSize(varType); @@ -3901,7 +3882,7 @@ var_types LclVarDsc::GetSimdBaseType() const } #endif // FEATURE_SIMD -unsigned LclVarDsc::lvSize() const // Size needed for storage representation. Only used for structs or TYP_BLK. +unsigned LclVarDsc::lvSize() const // Size needed for storage representation. Only used for structs. { // TODO-Review: Sometimes we get called on ARM with HFA struct variables that have been promoted, // where the struct itself is no longer used because all access is via its member fields. @@ -3915,7 +3896,7 @@ unsigned LclVarDsc::lvSize() const // Size needed for storage representation. On // Here, the "struct(U)" shows that the "V03 loc2" variable is unused. Not shown is that V03 // is now TYP_INT in the local variable table. It's not really unused, because it's in the tree. - assert(varTypeIsStruct(lvType) || (lvType == TYP_BLK) || (lvPromoted && lvUnusedStruct)); + assert(varTypeIsStruct(lvType) || (lvPromoted && lvUnusedStruct)); if (lvIsParam) { @@ -4544,10 +4525,9 @@ PhaseStatus Compiler::lvaMarkLocalVars() // For zero-termination of the shadow-Stack-pointer chain slotsNeeded++; - lvaShadowSPslotsVar = lvaGrabTempWithImplicitUse(false DEBUGARG("lvaShadowSPslotsVar")); - LclVarDsc* shadowSPslotsVar = lvaGetDesc(lvaShadowSPslotsVar); - shadowSPslotsVar->lvType = TYP_BLK; - shadowSPslotsVar->lvExactSize = (slotsNeeded * TARGET_POINTER_SIZE); + lvaShadowSPslotsVar = lvaGrabTempWithImplicitUse(false DEBUGARG("lvaShadowSPslotsVar")); + lvaSetStruct(lvaShadowSPslotsVar, typGetBlkLayout(slotsNeeded * TARGET_POINTER_SIZE), false); + lvaSetVarAddrExposed(lvaShadowSPslotsVar DEBUGARG(AddressExposedReason::EXTERNALLY_VISIBLE_IMPLICITLY)); } #endif // !FEATURE_EH_FUNCLETS @@ -4885,10 +4865,9 @@ void Compiler::lvaAllocOutgoingArgSpaceVar() if (lvaOutgoingArgSpaceVar == BAD_VAR_NUM) { - lvaOutgoingArgSpaceVar = lvaGrabTemp(false DEBUGARG("OutgoingArgSpace")); - - lvaTable[lvaOutgoingArgSpaceVar].lvType = TYP_LCLBLK; - lvaTable[lvaOutgoingArgSpaceVar].lvImplicitlyReferenced = 1; + lvaOutgoingArgSpaceVar = lvaGrabTempWithImplicitUse(false DEBUGARG("OutgoingArgSpace")); + lvaSetStruct(lvaOutgoingArgSpaceVar, typGetBlkLayout(0), false); + lvaSetVarAddrExposed(lvaOutgoingArgSpaceVar DEBUGARG(AddressExposedReason::EXTERNALLY_VISIBLE_IMPLICITLY)); } noway_assert(lvaOutgoingArgSpaceVar >= info.compLocalsCount && lvaOutgoingArgSpaceVar < lvaCount); @@ -7077,6 +7056,16 @@ void Compiler::lvaAssignVirtualFrameOffsetsToLocals() lvaAllocLocalAndSetVirtualOffset(lvaInlinedPInvokeFrameVar, lvaLclSize(lvaInlinedPInvokeFrameVar), stkOffs); } +#ifdef JIT32_GCENCODER + // JIT32 encoder cannot handle GS cookie at fp+0 since NO_GS_COOKIE == 0. + // Add some padding if it is the last allocated local. + if ((lvaGSSecurityCookie != BAD_VAR_NUM) && (lvaGetDesc(lvaGSSecurityCookie)->GetStackOffset() == stkOffs)) + { + lvaIncrementFrameSize(TARGET_POINTER_SIZE); + stkOffs -= TARGET_POINTER_SIZE; + } +#endif + if (mustDoubleAlign) { if (lvaDoneFrameLayout != FINAL_FRAME_LAYOUT) @@ -7852,7 +7841,7 @@ void Compiler::lvaDumpEntry(unsigned lclNum, FrameLayoutState curState, size_t r else #endif // !defined(TARGET_64BIT) { - CORINFO_CLASS_HANDLE typeHnd = parentvarDsc->GetStructHnd(); + CORINFO_CLASS_HANDLE typeHnd = parentvarDsc->GetLayout()->GetClassHandle(); CORINFO_FIELD_HANDLE fldHnd = info.compCompHnd->getFieldInClass(typeHnd, varDsc->lvFldOrdinal); char buffer[128]; @@ -8309,6 +8298,8 @@ Compiler::fgWalkResult Compiler::lvaStressLclFldCB(GenTree** pTree, fgWalkData* // Converting tail calls to loops may require insertion of explicit // zero initialization for IL locals. The JIT does not support this for // TYP_BLK locals. + // TODO-Cleanup: Can probably be removed now since TYP_BLK does not + // exist anymore. if (pComp->compMayConvertTailCallToLoop) { varDsc->lvNoLclFldStress = true; @@ -8322,16 +8313,16 @@ Compiler::fgWalkResult Compiler::lvaStressLclFldCB(GenTree** pTree, fgWalkData* return WALK_SKIP_SUBTREES; } - // Can't have GC ptrs in TYP_BLK. + // Can't have GC ptrs in block layouts. if (!varTypeIsArithmetic(lclType)) { varDsc->lvNoLclFldStress = true; return WALK_SKIP_SUBTREES; } - // The noway_assert in the second pass below, requires that these types match, or we have a TYP_BLK + // The noway_assert in the second pass below, requires that these types match // - if ((varType != lclType) && (varType != TYP_BLK)) + if (varType != lclType) { varDsc->lvNoLclFldStress = true; return WALK_SKIP_SUBTREES; @@ -8341,7 +8332,7 @@ Compiler::fgWalkResult Compiler::lvaStressLclFldCB(GenTree** pTree, fgWalkData* // node with the accurate small type. If we bash lvaTable[].lvType, // then there will be no indication that it was ever a small type. - if ((varType != TYP_BLK) && genTypeSize(varType) != genTypeSize(genActualType(varType))) + if (genTypeSize(varType) != genTypeSize(genActualType(varType))) { varDsc->lvNoLclFldStress = true; return WALK_SKIP_SUBTREES; @@ -8359,7 +8350,7 @@ Compiler::fgWalkResult Compiler::lvaStressLclFldCB(GenTree** pTree, fgWalkData* else { // Do the morphing - noway_assert((varType == lclType) || (varType == TYP_BLK)); + noway_assert((varType == lclType) || ((varType == TYP_STRUCT) && varDsc->GetLayout()->IsBlockLayout())); // Calculate padding unsigned padding = pComp->lvaStressLclFldPadding(lclNum); @@ -8372,11 +8363,12 @@ Compiler::fgWalkResult Compiler::lvaStressLclFldCB(GenTree** pTree, fgWalkData* padding = roundUp(padding, alignment); #endif // TARGET_ARMARCH || TARGET_LOONGARCH64 - // Change the variable to a TYP_BLK - if (varType != TYP_BLK) + if (varType != TYP_STRUCT) { + // Change the variable to a block struct varDsc->lvExactSize = roundUp(padding + pComp->lvaLclSize(lclNum), TARGET_POINTER_SIZE); - varDsc->lvType = TYP_BLK; + varDsc->lvType = TYP_STRUCT; + varDsc->SetLayout(pComp->typGetBlkLayout(varDsc->lvExactSize)); pComp->lvaSetVarAddrExposed(lclNum DEBUGARG(AddressExposedReason::STRESS_LCL_FLD)); JITDUMP("Converting V%02u to %u sized block with LCL_FLD at offset (padding %u)\n", lclNum, diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index c25922157de2fe..253e08e057f7e1 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -2359,7 +2359,7 @@ void Lowering::RehomeArgForFastTailCall(unsigned int lclNum, if (tmpTyp == TYP_STRUCT) { - comp->lvaSetStruct(tmpLclNum, comp->lvaGetStruct(lclNum), false); + comp->lvaSetStruct(tmpLclNum, comp->lvaGetDesc(lclNum)->GetLayout(), false); } GenTreeLclVar* storeLclVar = comp->gtNewStoreLclVar(tmpLclNum, value); BlockRange().InsertBefore(insertTempBefore, LIR::SeqTree(comp, storeLclVar)); diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index d45948e30bffde..32031b9e84565f 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -1726,24 +1726,9 @@ void CallArgs::EvalArgsToTemps(Compiler* comp, GenTreeCall* call) { setupArg = comp->fgMorphCopyBlock(setupArg); #if defined(TARGET_ARMARCH) || defined(UNIX_AMD64_ABI) || defined(TARGET_LOONGARCH64) -#if defined(TARGET_LOONGARCH64) - // On LoongArch64, "getPrimitiveTypeForStruct" will incorrectly return "TYP_LONG" - // for "struct { float, float }", and retyping to a primitive here will cause the - // multi-reg morphing to not kick in (the struct in question needs to be passed in - // two FP registers). - // TODO-LoongArch64: fix "getPrimitiveTypeForStruct" or use the ABI information in - // the arg entry instead of calling it here. - if ((lclVarType == TYP_STRUCT) && (arg.AbiInfo.NumRegs == 1)) -#else - if (lclVarType == TYP_STRUCT) -#endif + if ((lclVarType == TYP_STRUCT) && (arg.AbiInfo.ArgType != TYP_STRUCT)) { - // This scalar LclVar widening step is only performed for ARM architectures. - // - CORINFO_CLASS_HANDLE clsHnd = comp->lvaGetStruct(tmpVarNum); - unsigned structSize = varDsc->lvExactSize; - - scalarType = comp->getPrimitiveTypeForStruct(structSize, clsHnd, m_isVarArgs); + scalarType = arg.AbiInfo.ArgType; } #endif // TARGET_ARMARCH || defined (UNIX_AMD64_ABI) || defined(TARGET_LOONGARCH64) } @@ -4033,8 +4018,9 @@ void Compiler::fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg) if (!opts.MinOpts()) { found = ForEachHbvBitSet(*fgAvailableOutgoingArgTemps, [&](indexType lclNum) { - LclVarDsc* varDsc = lvaGetDesc((unsigned)lclNum); - if ((varDsc->GetStructHnd() == copyBlkClass)) + LclVarDsc* varDsc = lvaGetDesc((unsigned)lclNum); + ClassLayout* layout = varDsc->GetLayout(); + if (!layout->IsBlockLayout() && (layout->GetClassHandle() == copyBlkClass)) { tmp = (unsigned)lclNum; JITDUMP("reusing outgoing struct arg V%02u\n", tmp); @@ -6021,6 +6007,12 @@ GenTree* Compiler::fgMorphPotentialTailCall(GenTreeCall* call) assert(lvaIsImplicitByRefLocal(lvaTable[varDsc->lvFieldLclStart].lvParentLcl)); assert(fgGlobalMorph); } +#if FEATURE_FIXED_OUT_ARGS + else if (varNum == lvaOutgoingArgSpaceVar) + { + // The outgoing arg space is exposed only at callees, which is ok for our purposes. + } +#endif else { failTailCall("Local address taken", varNum); @@ -14877,15 +14869,15 @@ PhaseStatus Compiler::fgRetypeImplicitByRefArgs() // This implicit-by-ref was promoted; create a new temp to represent the // promoted struct before rewriting this parameter as a pointer. unsigned newLclNum = lvaGrabTemp(false DEBUGARG("Promoted implicit byref")); - lvaSetStruct(newLclNum, lvaGetStruct(lclNum), true); + // Update varDsc since lvaGrabTemp might have re-allocated the var dsc array. + varDsc = lvaGetDesc(lclNum); + + lvaSetStruct(newLclNum, varDsc->GetLayout(), true); if (info.compIsVarArgs) { lvaSetStructUsedAsVarArg(newLclNum); } - // Update varDsc since lvaGrabTemp might have re-allocated the var dsc array. - varDsc = lvaGetDesc(lclNum); - // Copy the struct promotion annotations to the new temp. LclVarDsc* newVarDsc = lvaGetDesc(newLclNum); newVarDsc->lvPromoted = true; diff --git a/src/coreclr/jit/morphblock.cpp b/src/coreclr/jit/morphblock.cpp index 272c31edca0769..bcc9f8f6638109 100644 --- a/src/coreclr/jit/morphblock.cpp +++ b/src/coreclr/jit/morphblock.cpp @@ -936,7 +936,7 @@ void MorphCopyBlockHelper::MorphStructCases() // Both structs should be of the same type, or have the same number of fields of the same type. // If not we will use a copy block. bool misMatchedTypes = false; - if (m_dstVarDsc->GetStructHnd() != m_srcVarDsc->GetStructHnd()) + if (m_dstVarDsc->GetLayout() != m_srcVarDsc->GetLayout()) { if (m_dstVarDsc->lvFieldCnt != m_srcVarDsc->lvFieldCnt) { diff --git a/src/coreclr/jit/optcse.cpp b/src/coreclr/jit/optcse.cpp index 6a810d0b9d3573..7715c49ed52a50 100644 --- a/src/coreclr/jit/optcse.cpp +++ b/src/coreclr/jit/optcse.cpp @@ -1805,7 +1805,7 @@ class CSE_Heuristic bool onStack = (regAvailEstimate == 0); // true when it is likely that this LclVar will have a stack home // Some LclVars always have stack homes - if ((varDsc->lvDoNotEnregister) || (varDsc->lvType == TYP_LCLBLK)) + if (varDsc->lvDoNotEnregister) { onStack = true; } @@ -1918,7 +1918,7 @@ class CSE_Heuristic } // Some LclVars always have stack homes - if ((varDsc->lvDoNotEnregister) || (varDsc->lvType == TYP_LCLBLK)) + if (varDsc->lvDoNotEnregister) { continue; } diff --git a/src/coreclr/jit/regalloc.cpp b/src/coreclr/jit/regalloc.cpp index aa45c85a070d9f..b78165b9ed6f41 100644 --- a/src/coreclr/jit/regalloc.cpp +++ b/src/coreclr/jit/regalloc.cpp @@ -165,7 +165,7 @@ regNumber Compiler::raUpdateRegStateForArg(RegState* regState, LclVarDsc* argDsc if (argDsc->lvIsHfaRegArg()) { assert(regState->rsIsFloat); - unsigned cSlots = GetHfaCount(argDsc->GetStructHnd()); + unsigned cSlots = GetHfaCount(argDsc->GetLayout()->GetClassHandle()); for (unsigned i = 1; i < cSlots; i++) { assert(inArgReg + i <= LAST_FP_ARGREG); @@ -320,13 +320,6 @@ void Compiler::raMarkStkVars() needSlot |= varDsc->IsAddressExposed(); } -#if FEATURE_FIXED_OUT_ARGS - - /* Is this the dummy variable representing GT_LCLBLK ? */ - needSlot |= (lclNum == lvaOutgoingArgSpaceVar); - -#endif // FEATURE_FIXED_OUT_ARGS - #ifdef DEBUG /* For debugging, note that we have to reserve space even for unused variables if they are ever in scope. However, this is not diff --git a/src/coreclr/jit/scopeinfo.cpp b/src/coreclr/jit/scopeinfo.cpp index dd7eecb5a4d0cc..5c9c4077ec4ea1 100644 --- a/src/coreclr/jit/scopeinfo.cpp +++ b/src/coreclr/jit/scopeinfo.cpp @@ -284,7 +284,6 @@ void CodeGenInterface::siVarLoc::siFillStackVarLoc( case TYP_BYREF: case TYP_FLOAT: case TYP_STRUCT: - case TYP_BLK: // Needed because of the TYP_BLK stress mode #ifdef FEATURE_SIMD case TYP_SIMD8: case TYP_SIMD12: @@ -898,7 +897,7 @@ void CodeGen::psiBegProlog() SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR structDesc; if (varTypeIsStruct(lclVarDsc)) { - CORINFO_CLASS_HANDLE typeHnd = lclVarDsc->GetStructHnd(); + CORINFO_CLASS_HANDLE typeHnd = lclVarDsc->GetLayout()->GetClassHandle(); assert(typeHnd != nullptr); compiler->eeGetSystemVAmd64PassStructInRegisterDescriptor(typeHnd, &structDesc); if (structDesc.passedInRegisters) diff --git a/src/coreclr/jit/typelist.h b/src/coreclr/jit/typelist.h index b9140aa601f33a..6cb1cb0e466539 100644 --- a/src/coreclr/jit/typelist.h +++ b/src/coreclr/jit/typelist.h @@ -53,9 +53,6 @@ DEF_TP(REF ,"ref" , TYP_REF, TI_REF, PS,GCS,GCS, PST,PS, VTF_ANY|VT DEF_TP(BYREF ,"byref" , TYP_BYREF, TI_ERROR,PS,BRS,BRS, PST,PS, VTF_ANY|VTF_BYR|VTF_I) DEF_TP(STRUCT ,"struct" , TYP_STRUCT, TI_STRUCT,0, 0, 0, 1, 4, VTF_S) -DEF_TP(BLK ,"blk" , TYP_BLK, TI_ERROR, 0, 0, 0, 1, 4, VTF_ANY) // blob of memory -DEF_TP(LCLBLK ,"lclBlk" , TYP_LCLBLK, TI_ERROR, 0, 0, 0, 1, 4, VTF_ANY) // preallocated memory for locspace - #ifdef FEATURE_SIMD DEF_TP(SIMD8 ,"simd8" , TYP_SIMD8, TI_STRUCT, 8, 8, 8, 2, 8, VTF_S|VTF_VEC) DEF_TP(SIMD12 ,"simd12" , TYP_SIMD12, TI_STRUCT,12,16, 16, 4,16, VTF_S|VTF_VEC) diff --git a/src/coreclr/jit/valuenum.cpp b/src/coreclr/jit/valuenum.cpp index 39bd8898548e53..e3126ed75578f4 100644 --- a/src/coreclr/jit/valuenum.cpp +++ b/src/coreclr/jit/valuenum.cpp @@ -9038,43 +9038,15 @@ PhaseStatus Compiler::fgValueNumber() ValueNum initVal = ValueNumStore::NoVN; // We must assign a new value to initVal var_types typ = varDsc->TypeGet(); - switch (typ) + if (isZeroed) { - case TYP_LCLBLK: // The outgoing args area for arm and x64 - case TYP_BLK: // A blob of memory - // TYP_BLK is used for the EHSlots LclVar on x86 (aka shadowSPslotsVar) - // and for the lvaInlinedPInvokeFrameVar on x64, arm and x86 - // The stack associated with these LclVars are not zero initialized - // thus we set 'initVN' to a new, unique VN. - // - initVal = vnStore->VNForExpr(fgFirstBB); - break; - - case TYP_BYREF: - if (isZeroed) - { - // LclVars of TYP_BYREF can be zero-inited. - initVal = vnStore->VNForByrefCon(0); - } - else - { - // Here we have uninitialized TYP_BYREF - initVal = vnStore->VNForFunc(typ, VNF_InitVal, vnStore->VNForIntCon(lclNum)); - } - break; - - default: - if (isZeroed) - { - // By default we will zero init these LclVars - initVal = (typ == TYP_STRUCT) ? vnStore->VNForZeroObj(varDsc->GetLayout()) - : vnStore->VNZeroForType(typ); - } - else - { - initVal = vnStore->VNForFunc(typ, VNF_InitVal, vnStore->VNForIntCon(lclNum)); - } - break; + // By default we will zero init these LclVars + initVal = + (typ == TYP_STRUCT) ? vnStore->VNForZeroObj(varDsc->GetLayout()) : vnStore->VNZeroForType(typ); + } + else + { + initVal = vnStore->VNForFunc(typ, VNF_InitVal, vnStore->VNForIntCon(lclNum)); } #ifdef TARGET_X86 bool isVarargParam = (lclNum == lvaVarargsBaseOfStkArgs || lclNum == lvaVarargsHandleArg); diff --git a/src/coreclr/jit/vartype.h b/src/coreclr/jit/vartype.h index a51dbff3b82acd..3c05a169933176 100644 --- a/src/coreclr/jit/vartype.h +++ b/src/coreclr/jit/vartype.h @@ -272,11 +272,14 @@ inline bool varTypeIsComposite(T vt) template inline bool varTypeIsPromotable(T vt) { - return (varTypeIsStruct(vt) || (TypeGet(vt) == TYP_BLK) -#if !defined(TARGET_64BIT) - || varTypeIsLong(vt) -#endif // !defined(TARGET_64BIT) - ); +#ifndef TARGET_64BIT + if (varTypeIsLong(vt)) + { + return true; + } +#endif + + return varTypeIsStruct(vt); } template diff --git a/src/coreclr/tools/SuperFileCheck/Program.cs b/src/coreclr/tools/SuperFileCheck/Program.cs index 64203bedcf3e0e..cb3732b06c8fe8 100644 --- a/src/coreclr/tools/SuperFileCheck/Program.cs +++ b/src/coreclr/tools/SuperFileCheck/Program.cs @@ -392,6 +392,7 @@ static string PreProcessMethod(MethodDeclarationInfo methodDeclInfo, string[] ch // Create anchors from the first prefix. var startAnchorText = $"// {checkPrefixes[0]}-LABEL: for method {methodName}"; + var startInstrsAnchorText = $"// {checkPrefixes[0]}: Lcl frame size ="; var endAnchorText = $"// {checkPrefixes[0]}: for method {methodName}"; // Create temp source file based on the source text of the method. @@ -405,6 +406,7 @@ static string PreProcessMethod(MethodDeclarationInfo methodDeclInfo, string[] ch tmpSrc.AppendLine(String.Empty); } tmpSrc.AppendLine(startAnchorText); + tmpSrc.AppendLine(startInstrsAnchorText); tmpSrc.AppendLine(TransformMethod(methodDecl, checkPrefixes)); tmpSrc.AppendLine(endAnchorText);