diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index 1586ba4132120..f1282ff6768c4 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -197,6 +197,7 @@ + diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/StackAllocatedBox.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/StackAllocatedBox.cs new file mode 100644 index 0000000000000..885a2d93c6242 --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/StackAllocatedBox.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +namespace System.Runtime.CompilerServices +{ + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct StackAllocatedBox + { + // These fields are only accessed from jitted code + private MethodTable* _pMethodTable; + private T _value; + } +} diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index 89c61ebeac690..cc1ca62f47485 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -445,6 +445,7 @@ enum CorInfoHelpFunc CORINFO_HELP_BOX, // Fast box helper. Only possible exception is OutOfMemory CORINFO_HELP_BOX_NULLABLE, // special form of boxing for Nullable CORINFO_HELP_UNBOX, + CORINFO_HELP_UNBOX_TYPETEST, // Verify unbox type, throws if incompatible CORINFO_HELP_UNBOX_NULLABLE, // special form of unboxing for Nullable CORINFO_HELP_GETREFANY, // Extract the byref from a TypedReference, checking that it is the expected type @@ -2552,6 +2553,14 @@ class ICorStaticInfo CORINFO_CLASS_HANDLE cls ) = 0; + // Get a representation for a stack-allocated boxed value type. + // + // This differs from getTypeForBox in that it includes an explicit field + // for the method table pointer. + virtual CORINFO_CLASS_HANDLE getTypeForBoxOnStack( + CORINFO_CLASS_HANDLE cls + ) = 0; + // returns the correct box helper for a particular class. Note // that if this returns CORINFO_HELP_BOX, the JIT can assume // 'standard' boxing (allocate object and copy), and optimize diff --git a/src/coreclr/inc/icorjitinfoimpl_generated.h b/src/coreclr/inc/icorjitinfoimpl_generated.h index 38f9dac0e2e4d..ee537e0c3be54 100644 --- a/src/coreclr/inc/icorjitinfoimpl_generated.h +++ b/src/coreclr/inc/icorjitinfoimpl_generated.h @@ -275,6 +275,9 @@ CorInfoHelpFunc getSharedCCtorHelper( CORINFO_CLASS_HANDLE getTypeForBox( CORINFO_CLASS_HANDLE cls) override; +CORINFO_CLASS_HANDLE getTypeForBoxOnStack( + CORINFO_CLASS_HANDLE cls) override; + CorInfoHelpFunc getBoxHelper( CORINFO_CLASS_HANDLE cls) override; diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index 76a1bbc466d79..ff7129df56b56 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 = { /* e428e66d-5e0e-4320-ad8a-fa5a50f6da07 */ - 0xe428e66d, - 0x5e0e, - 0x4320, - {0xad, 0x8a, 0xfa, 0x5a, 0x50, 0xf6, 0xda, 0x07} +constexpr GUID JITEEVersionIdentifier = { /* 748cd07e-c686-4085-8f5f-54c1b9cff44c */ + 0x748cd07e, + 0xc686, + 0x4085, + {0x8f, 0x5f, 0x54, 0xc1, 0xb9, 0xcf, 0xf4, 0x4c} }; ////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/coreclr/inc/jithelpers.h b/src/coreclr/inc/jithelpers.h index 50beaabe66e90..4273bebca40b7 100644 --- a/src/coreclr/inc/jithelpers.h +++ b/src/coreclr/inc/jithelpers.h @@ -106,6 +106,7 @@ DYNAMICJITHELPER(CORINFO_HELP_BOX, JIT_Box, CORINFO_HELP_SIG_REG_ONLY) JITHELPER(CORINFO_HELP_BOX_NULLABLE, JIT_Box, CORINFO_HELP_SIG_REG_ONLY) DYNAMICJITHELPER(CORINFO_HELP_UNBOX, NULL, CORINFO_HELP_SIG_REG_ONLY) + JITHELPER(CORINFO_HELP_UNBOX_TYPETEST, JIT_Unbox_TypeTest, CORINFO_HELP_SIG_REG_ONLY) JITHELPER(CORINFO_HELP_UNBOX_NULLABLE, JIT_Unbox_Nullable, CORINFO_HELP_SIG_4_STACK) JITHELPER(CORINFO_HELP_GETREFANY, JIT_GetRefAny, CORINFO_HELP_SIG_8_STACK) diff --git a/src/coreclr/jit/ICorJitInfo_names_generated.h b/src/coreclr/jit/ICorJitInfo_names_generated.h index a2bfe1de9c80c..27ed43df0dcd3 100644 --- a/src/coreclr/jit/ICorJitInfo_names_generated.h +++ b/src/coreclr/jit/ICorJitInfo_names_generated.h @@ -68,6 +68,7 @@ DEF_CLR_API(getNewArrHelper) DEF_CLR_API(getCastingHelper) DEF_CLR_API(getSharedCCtorHelper) DEF_CLR_API(getTypeForBox) +DEF_CLR_API(getTypeForBoxOnStack) DEF_CLR_API(getBoxHelper) DEF_CLR_API(getUnBoxHelper) DEF_CLR_API(getRuntimeTypePointer) diff --git a/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp b/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp index ff54b778f2a2c..997405624d2cd 100644 --- a/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp +++ b/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp @@ -635,6 +635,15 @@ CORINFO_CLASS_HANDLE WrapICorJitInfo::getTypeForBox( return temp; } +CORINFO_CLASS_HANDLE WrapICorJitInfo::getTypeForBoxOnStack( + CORINFO_CLASS_HANDLE cls) +{ + API_ENTER(getTypeForBoxOnStack); + CORINFO_CLASS_HANDLE temp = wrapHnd->getTypeForBoxOnStack(cls); + API_LEAVE(getTypeForBoxOnStack); + return temp; +} + CorInfoHelpFunc WrapICorJitInfo::getBoxHelper( CORINFO_CLASS_HANDLE cls) { diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 316ff3c6da943..bb6adaaa5472a 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -690,7 +690,8 @@ class LclVarDsc unsigned char lvSingleDefDisqualifyReason = 'H'; #endif - unsigned char lvAllDefsAreNoGc : 1; // For pinned locals: true if all defs of this local are no-gc + unsigned char lvAllDefsAreNoGc : 1; // For pinned locals: true if all defs of this local are no-gc + unsigned char lvStackAllocatedBox : 1; // Local is a stack allocated box #if FEATURE_MULTIREG_ARGS regNumber lvRegNumForSlot(unsigned slotNum) @@ -805,6 +806,11 @@ class LclVarDsc return lvIsMultiRegArg || lvIsMultiRegRet; } + bool IsStackAllocatedBox() const + { + return lvStackAllocatedBox; + } + #if defined(DEBUG) private: DoNotEnregisterReason m_doNotEnregReason; @@ -3523,7 +3529,7 @@ class Compiler GenTree* gtNewRuntimeLookup(CORINFO_GENERIC_HANDLE hnd, CorInfoGenericHandleType hndTyp, GenTree* lookupTree); - GenTreeIndir* gtNewMethodTableLookup(GenTree* obj); + GenTreeIndir* gtNewMethodTableLookup(GenTree* obj, bool onStack = false); //------------------------------------------------------------------------ // Other GenTree functions diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index 21bd4fc5a1efd..69e95d60d58ae 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -1872,9 +1872,9 @@ inline GenTreeCast* Compiler::gtNewCastNodeL(var_types typ, GenTree* op1, bool f return cast; } -inline GenTreeIndir* Compiler::gtNewMethodTableLookup(GenTree* object) +inline GenTreeIndir* Compiler::gtNewMethodTableLookup(GenTree* object, bool onStack) { - assert(object->TypeIs(TYP_REF)); + assert(onStack || object->TypeIs(TYP_REF)); GenTreeIndir* result = gtNewIndir(TYP_I_IMPL, object, GTF_IND_INVARIANT); return result; } diff --git a/src/coreclr/jit/inlinepolicy.cpp b/src/coreclr/jit/inlinepolicy.cpp index 3b771f291607b..0c6ac9241fca6 100644 --- a/src/coreclr/jit/inlinepolicy.cpp +++ b/src/coreclr/jit/inlinepolicy.cpp @@ -1628,7 +1628,7 @@ double ExtendedDefaultPolicy::DetermineMultiplier() // // void Caller() => DoNothing(42); // 42 is going to be boxed at the call site. // - multiplier += 0.5; + multiplier += 0.5 * m_ArgIsBoxedAtCallsite; JITDUMP("\nCallsite is going to box %d arguments. Multiplier increased to %g.", m_ArgIsBoxedAtCallsite, multiplier); } diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index 771c86dbcbb74..c83fdb48fb326 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -646,7 +646,10 @@ RELEASE_CONFIG_INTEGER(JitExtDefaultPolicyProfScale, W("JitExtDefaultPolicyProfS RELEASE_CONFIG_INTEGER(JitInlinePolicyModel, W("JitInlinePolicyModel"), 0) RELEASE_CONFIG_INTEGER(JitInlinePolicyProfile, W("JitInlinePolicyProfile"), 0) RELEASE_CONFIG_INTEGER(JitInlinePolicyProfileThreshold, W("JitInlinePolicyProfileThreshold"), 40) -RELEASE_CONFIG_INTEGER(JitObjectStackAllocation, W("JitObjectStackAllocation"), 0) +CONFIG_STRING(JitObjectStackAllocationRange, W("JitObjectStackAllocationRange")) +RELEASE_CONFIG_INTEGER(JitObjectStackAllocation, W("JitObjectStackAllocation"), 1) +RELEASE_CONFIG_INTEGER(JitObjectStackAllocationRefClass, W("JitObjectStackAllocationRefClass"), 1) +RELEASE_CONFIG_INTEGER(JitObjectStackAllocationBoxedValueClass, W("JitObjectStackAllocationBoxedValueClass"), 1) RELEASE_CONFIG_INTEGER(JitEECallTimingInfo, W("JitEECallTimingInfo"), 0) diff --git a/src/coreclr/jit/jitmetadatalist.h b/src/coreclr/jit/jitmetadatalist.h index 209133f4324b0..10da7249177f8 100644 --- a/src/coreclr/jit/jitmetadatalist.h +++ b/src/coreclr/jit/jitmetadatalist.h @@ -71,6 +71,10 @@ JITMETADATAMETRIC(ProfileInconsistentInlineeScale, int, 0) JITMETADATAMETRIC(ProfileInconsistentInlinee, int, 0) JITMETADATAMETRIC(ProfileInconsistentNoReturnInlinee, int, 0) JITMETADATAMETRIC(ProfileInconsistentMayThrowInlinee, int, 0) +JITMETADATAMETRIC(NewRefClassHelperCalls, int, 0) +JITMETADATAMETRIC(StackAllocatedRefClasses, int, 0) +JITMETADATAMETRIC(NewBoxedValueClassHelperCalls, int, 0) +JITMETADATAMETRIC(StackAllocatedBoxedValueClasses, int, 0) #undef JITMETADATA #undef JITMETADATAINFO diff --git a/src/coreclr/jit/lclmorph.cpp b/src/coreclr/jit/lclmorph.cpp index 821dade4c5d67..3ad2c9e7a7e77 100644 --- a/src/coreclr/jit/lclmorph.cpp +++ b/src/coreclr/jit/lclmorph.cpp @@ -340,6 +340,7 @@ class LocalEqualsLocalAddrAssertions // void OnExposed(unsigned lclNum) { + JITDUMP("On exposed: V%02u\n", lclNum); BitVecTraits localsTraits(m_comp->lvaCount, m_comp); BitVecOps::AddElemD(&localsTraits, m_localsToExpose, lclNum); } @@ -515,6 +516,13 @@ class LocalAddressVisitor final : public GenTreeVisitor return m_offset; } + bool IsSameAddress(const Value& other) const + { + assert(IsAddress() && other.IsAddress()); + + return ((LclNum() == other.LclNum()) && (Offset() == other.Offset())); + } + //------------------------------------------------------------------------ // Address: Produce an address value from a LCL_ADDR node. // @@ -806,7 +814,6 @@ class LocalAddressVisitor final : public GenTreeVisitor } PushValue(use); - return Compiler::WALK_CONTINUE; } @@ -1028,6 +1035,77 @@ class LocalAddressVisitor final : public GenTreeVisitor SequenceCall(node->AsCall()); break; + case GT_EQ: + case GT_NE: + { + // If we see &lcl EQ/NE null, rewrite to 0/1 comparison + // to reduce overall address exposure. + // + assert(TopValue(2).Node() == node); + assert(TopValue(1).Node() == node->AsOp()->gtOp1); + assert(TopValue(0).Node() == node->AsOp()->gtOp2); + + Value& lhs = TopValue(1); + Value& rhs = TopValue(0); + + if ((lhs.IsAddress() && rhs.Node()->IsIntegralConst(0)) || + (rhs.IsAddress() && lhs.Node()->IsIntegralConst(0))) + { + JITDUMP("Rewriting known address vs null comparison [%06u]\n", m_compiler->dspTreeID(node)); + *lhs.Use() = m_compiler->gtNewIconNode(0); + *rhs.Use() = m_compiler->gtNewIconNode(1); + m_stmtModified = true; + + INDEBUG(TopValue(0).Consume()); + INDEBUG(TopValue(1).Consume()); + PopValue(); + PopValue(); + } + else if (lhs.IsAddress() && rhs.IsAddress()) + { + JITDUMP("Rewriting known address vs address comparison [%06u]\n", m_compiler->dspTreeID(node)); + bool isSameAddress = lhs.IsSameAddress(rhs); + *lhs.Use() = m_compiler->gtNewIconNode(0); + *rhs.Use() = m_compiler->gtNewIconNode(isSameAddress ? 0 : 1); + m_stmtModified = true; + + INDEBUG(TopValue(0).Consume()); + INDEBUG(TopValue(1).Consume()); + PopValue(); + PopValue(); + } + else + { + EscapeValue(TopValue(0), node); + PopValue(); + EscapeValue(TopValue(0), node); + PopValue(); + } + + break; + } + + case GT_NULLCHECK: + { + assert(TopValue(1).Node() == node); + assert(TopValue(0).Node() == node->AsOp()->gtOp1); + Value& op = TopValue(0); + if (op.IsAddress()) + { + JITDUMP("Bashing nullcheck of local [%06u] to NOP\n", m_compiler->dspTreeID(node)); + node->gtBashToNOP(); + INDEBUG(TopValue(0).Consume()); + PopValue(); + m_stmtModified = true; + } + else + { + EscapeValue(TopValue(0), node); + PopValue(); + } + break; + } + default: while (TopValue(0).Node() != node) { diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index 2b8b854c8ff2e..52623e920b5fa 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -2572,6 +2572,13 @@ bool Compiler::StructPromotionHelper::CanPromoteStructVar(unsigned lclNum) if (varDsc->GetLayout()->IsBlockLayout()) { + JITDUMP(" struct promotion of V%02u is disabled because it has block layout\n", lclNum); + return false; + } + + if (varDsc->lvStackAllocatedBox) + { + JITDUMP(" struct promotion of V%02u is disabled because it is a stack allocated box\n", lclNum); return false; } @@ -3359,6 +3366,8 @@ bool Compiler::lvaIsLocalImplicitlyAccessedByRef(unsigned lclNum) const // this information is already available on the CallArgABIInformation, and shouldn't need to be // recomputed. // +// Also seems like this info could be cached in the layout. +// bool Compiler::lvaIsMultiregStruct(LclVarDsc* varDsc, bool isVarArg) { if (varTypeIsStruct(varDsc->TypeGet())) diff --git a/src/coreclr/jit/objectalloc.cpp b/src/coreclr/jit/objectalloc.cpp index 3d6206e0f723c..89e28c5978c6d 100644 --- a/src/coreclr/jit/objectalloc.cpp +++ b/src/coreclr/jit/objectalloc.cpp @@ -36,14 +36,31 @@ PhaseStatus ObjectAllocator::DoPhase() return PhaseStatus::MODIFIED_NOTHING; } - if (IsObjectStackAllocationEnabled()) + bool enabled = IsObjectStackAllocationEnabled(); + const char* disableReason = ": global config"; + +#ifdef DEBUG + // Allow disabling based on method hash + // + if (enabled) + { + static ConfigMethodRange JitObjectStackAllocationRange; + JitObjectStackAllocationRange.EnsureInit(JitConfig.JitObjectStackAllocationRange()); + const unsigned hash = comp->info.compMethodHash(); + enabled &= JitObjectStackAllocationRange.Contains(hash); + disableReason = ": range config"; + } +#endif + + if (enabled) { JITDUMP("enabled, analyzing...\n"); DoAnalysis(); } else { - JITDUMP("disabled, punting\n"); + JITDUMP("disabled%s, punting\n", IsObjectStackAllocationEnabled() ? disableReason : ""); + m_IsObjectStackAllocationEnabled = false; } const bool didStackAllocate = MorphAllocObjNodes(); @@ -170,9 +187,17 @@ void ObjectAllocator::MarkEscapingVarsAndBuildConnGraph() Compiler::fgWalkResult PreOrderVisit(GenTree** use, GenTree* user) { - GenTree* tree = *use; - unsigned lclNum = tree->AsLclVarCommon()->GetLclNum(); - bool lclEscapes = true; + GenTree* const tree = *use; + unsigned const lclNum = tree->AsLclVarCommon()->GetLclNum(); + + // If this local already escapes, no need to look further. + // + if (m_allocator->CanLclVarEscape(lclNum)) + { + return Compiler::fgWalkResult::WALK_CONTINUE; + } + + bool lclEscapes = true; if (tree->OperIsLocalStore()) { @@ -305,7 +330,9 @@ void ObjectAllocator::ComputeStackObjectPointers(BitVecTraits* bitVecTraits) MarkLclVarAsPossiblyStackPointing(lclNum); // Check if this pointer always points to the stack. - if (lclVarDsc->lvSingleDef == 1) + // For OSR the reference may be pointing at the heap-allocated Tier0 version. + // + if ((lclVarDsc->lvSingleDef == 1) && !comp->opts.IsOSR()) { // Check if we know what is assigned to this pointer. unsigned bitCount = BitVecOps::Count(bitVecTraits, m_ConnGraphAdjacencyMatrix[lclNum]); @@ -386,17 +413,53 @@ bool ObjectAllocator::MorphAllocObjNodes() // \--* CNS_INT(h) long //------------------------------------------------------------------------ - GenTreeAllocObj* asAllocObj = data->AsAllocObj(); - unsigned int lclNum = stmtExpr->AsLclVar()->GetLclNum(); - CORINFO_CLASS_HANDLE clsHnd = data->AsAllocObj()->gtAllocObjClsHnd; + GenTreeAllocObj* asAllocObj = data->AsAllocObj(); + unsigned int lclNum = stmtExpr->AsLclVar()->GetLclNum(); + CORINFO_CLASS_HANDLE clsHnd = data->AsAllocObj()->gtAllocObjClsHnd; + CORINFO_CLASS_HANDLE stackClsHnd = clsHnd; + const bool isValueClass = comp->info.compCompHnd->isValueClass(clsHnd); + const char* onHeapReason = nullptr; + bool canStack = false; - // Don't attempt to do stack allocations inside basic blocks that may be in a loop. - if (IsObjectStackAllocationEnabled() && !basicBlockHasBackwardJump && - CanAllocateLclVarOnStack(lclNum, clsHnd)) + if (isValueClass) + { + comp->Metrics.NewBoxedValueClassHelperCalls++; + stackClsHnd = comp->info.compCompHnd->getTypeForBoxOnStack(clsHnd); + } + else { - JITDUMP("Allocating local variable V%02u on the stack\n", lclNum); + comp->Metrics.NewRefClassHelperCalls++; + } - const unsigned int stackLclNum = MorphAllocObjNodeIntoStackAlloc(asAllocObj, block, stmt); + // Don't attempt to do stack allocations inside basic blocks that may be in a loop. + // + if (!IsObjectStackAllocationEnabled()) + { + onHeapReason = "[object stack allocation disabled]"; + canStack = false; + } + else if (basicBlockHasBackwardJump) + { + onHeapReason = "[alloc in loop]"; + canStack = false; + } + else if (!CanAllocateLclVarOnStack(lclNum, clsHnd, &onHeapReason)) + { + // reason set by the call + canStack = false; + } + else if (stackClsHnd == NO_CLASS_HANDLE) + { + assert(isValueClass); + onHeapReason = "[no class handle for this boxed value class]"; + canStack = false; + } + else + { + JITDUMP("Allocating V%02u on the stack\n", lclNum); + canStack = true; + const unsigned int stackLclNum = + MorphAllocObjNodeIntoStackAlloc(asAllocObj, stackClsHnd, isValueClass, block, stmt); m_HeapLocalToStackLocalMap.AddOrUpdate(lclNum, stackLclNum); // We keep the set of possibly-stack-pointing pointers as a superset of the set of // definitely-stack-pointing pointers. All definitely-stack-pointing pointers are in both sets. @@ -406,13 +469,22 @@ bool ObjectAllocator::MorphAllocObjNodes() comp->optMethodFlags |= OMF_HAS_OBJSTACKALLOC; didStackAllocate = true; } - else + + if (canStack) { - if (IsObjectStackAllocationEnabled()) + if (isValueClass) { - JITDUMP("Allocating local variable V%02u on the heap\n", lclNum); + comp->Metrics.StackAllocatedBoxedValueClasses++; } - + else + { + comp->Metrics.StackAllocatedRefClasses++; + } + } + else + { + assert(onHeapReason != nullptr); + JITDUMP("Allocating V%02u on the heap: %s\n", lclNum, onHeapReason); data = MorphAllocObjNodeIntoHelperCall(asAllocObj); stmtExpr->AsLclVar()->Data() = data; stmtExpr->AddAllEffectsFlags(data); @@ -487,32 +559,36 @@ GenTree* ObjectAllocator::MorphAllocObjNodeIntoHelperCall(GenTreeAllocObj* alloc // MorphAllocObjNodeIntoStackAlloc: Morph a GT_ALLOCOBJ node into stack // allocation. // Arguments: -// allocObj - GT_ALLOCOBJ that will be replaced by a stack allocation -// block - a basic block where allocObj is -// stmt - a statement where allocObj is +// allocObj - GT_ALLOCOBJ that will be replaced by a stack allocation +// clsHnd - class representing the stack allocated object +// isValueClass - we are stack allocating a boxed value class +// block - a basic block where allocObj is +// stmt - a statement where allocObj is // // Return Value: // local num for the new stack allocated local // // Notes: // This function can insert additional statements before stmt. - -unsigned int ObjectAllocator::MorphAllocObjNodeIntoStackAlloc(GenTreeAllocObj* allocObj, - BasicBlock* block, - Statement* stmt) +// +unsigned int ObjectAllocator::MorphAllocObjNodeIntoStackAlloc( + GenTreeAllocObj* allocObj, CORINFO_CLASS_HANDLE clsHnd, bool isValueClass, BasicBlock* block, Statement* stmt) { assert(allocObj != nullptr); assert(m_AnalysisDone); + assert(clsHnd != NO_CLASS_HANDLE); const bool shortLifetime = false; - const unsigned int lclNum = comp->lvaGrabTemp(shortLifetime DEBUGARG("MorphAllocObjNodeIntoStackAlloc temp")); - const int unsafeValueClsCheck = true; - comp->lvaSetStruct(lclNum, allocObj->gtAllocObjClsHnd, unsafeValueClsCheck); + const unsigned int lclNum = comp->lvaGrabTemp(shortLifetime DEBUGARG( + isValueClass ? "stack allocated boxed value class temp" : "stack allocated ref class temp")); + + comp->lvaSetStruct(lclNum, clsHnd, /* unsafeValueClsCheck */ false); // Initialize the object memory if necessary. bool bbInALoop = block->HasFlag(BBF_BACKWARD_JUMP); bool bbIsReturn = block->KindIs(BBJ_RETURN); LclVarDsc* const lclDsc = comp->lvaGetDesc(lclNum); + lclDsc->lvStackAllocatedBox = isValueClass; if (comp->fgVarNeedsExplicitZeroInit(lclNum, bbInALoop, bbIsReturn)) { //------------------------------------------------------------------------ @@ -533,6 +609,8 @@ unsigned int ObjectAllocator::MorphAllocObjNodeIntoStackAlloc(GenTreeAllocObj* a comp->compSuppressedZeroInit = true; } + // Initialize the vtable slot. + // //------------------------------------------------------------------------ // STMTx (IL 0x... ???) // * STORE_LCL_FLD long @@ -584,6 +662,8 @@ bool ObjectAllocator::CanLclVarEscapeViaParentStack(ArrayStack* parent GenTree* parent = parentStack->Top(parentIndex); keepChecking = false; + JITDUMP("... L%02u ... checking [%06u]\n", lclNum, comp->dspTreeID(parent)); + switch (parent->OperGet()) { // Update the connection graph if we are storing to a local. @@ -601,6 +681,7 @@ bool ObjectAllocator::CanLclVarEscapeViaParentStack(ArrayStack* parent case GT_EQ: case GT_NE: + case GT_NULLCHECK: canLclVarEscapeViaParentStack = false; break; @@ -615,6 +696,7 @@ bool ObjectAllocator::CanLclVarEscapeViaParentStack(ArrayStack* parent case GT_COLON: case GT_QMARK: case GT_ADD: + case GT_BOX: case GT_FIELD_ADDR: // Check whether the local escapes via its grandparent. ++parentIndex; @@ -622,6 +704,8 @@ bool ObjectAllocator::CanLclVarEscapeViaParentStack(ArrayStack* parent break; case GT_STOREIND: + case GT_STORE_BLK: + case GT_BLK: if (tree != parent->AsIndir()->Addr()) { // TODO-ObjectStackAllocation: track stores to fields. @@ -639,12 +723,8 @@ bool ObjectAllocator::CanLclVarEscapeViaParentStack(ArrayStack* parent if (asCall->IsHelperCall()) { - // TODO-ObjectStackAllocation: Special-case helpers here that - // 1. Don't make objects escape. - // 2. Protect objects as interior (GCPROTECT_BEGININTERIOR() instead of GCPROTECT_BEGIN()). - // 3. Don't check that the object is in the heap in ValidateInner. - - canLclVarEscapeViaParentStack = true; + canLclVarEscapeViaParentStack = + !Compiler::s_helperCallProperties.IsNoEscape(comp->eeGetHelperNum(asCall->gtCallMethHnd)); } break; } @@ -688,6 +768,7 @@ void ObjectAllocator::UpdateAncestorTypes(GenTree* tree, ArrayStack* p switch (parent->OperGet()) { case GT_STORE_LCL_VAR: + case GT_BOX: if (parent->TypeGet() == TYP_REF) { parent->ChangeType(newType); @@ -696,6 +777,7 @@ void ObjectAllocator::UpdateAncestorTypes(GenTree* tree, ArrayStack* p case GT_EQ: case GT_NE: + case GT_NULLCHECK: break; case GT_COMMA: @@ -705,7 +787,6 @@ void ObjectAllocator::UpdateAncestorTypes(GenTree* tree, ArrayStack* p break; } FALLTHROUGH; - case GT_COLON: case GT_QMARK: case GT_ADD: case GT_FIELD_ADDR: @@ -717,7 +798,35 @@ void ObjectAllocator::UpdateAncestorTypes(GenTree* tree, ArrayStack* p keepChecking = true; break; + case GT_COLON: + { + GenTree* const lhs = parent->AsOp()->gtGetOp1(); + GenTree* const rhs = parent->AsOp()->gtGetOp2(); + + // We may see sibling null refs. Retype them as appropriate. + // + if (lhs == tree) + { + assert(rhs->IsIntegralConst(0)); + rhs->ChangeType(newType); + } + else + { + assert(rhs == tree); + assert(lhs->IsIntegralConst(0)); + lhs->ChangeType(newType); + } + + parent->ChangeType(newType); + + ++parentIndex; + keepChecking = true; + } + break; + case GT_STOREIND: + case GT_STORE_BLK: + case GT_BLK: assert(tree == parent->AsIndir()->Addr()); // The new target could be *not* on the heap. @@ -733,6 +842,7 @@ void ObjectAllocator::UpdateAncestorTypes(GenTree* tree, ArrayStack* p break; case GT_IND: + case GT_CALL: break; default: @@ -761,9 +871,9 @@ void ObjectAllocator::RewriteUses() public: enum { - DoPreOrder = true, - DoLclVarsOnly = true, - ComputeStack = true, + DoPreOrder = true, + DoPostOrder = true, + ComputeStack = true, }; RewriteUsesVisitor(ObjectAllocator* allocator) @@ -775,7 +885,11 @@ void ObjectAllocator::RewriteUses() Compiler::fgWalkResult PreOrderVisit(GenTree** use, GenTree* user) { GenTree* tree = *use; - assert(tree->OperIsAnyLocal()); + + if (!tree->OperIsAnyLocal()) + { + return Compiler::fgWalkResult::WALK_CONTINUE; + } const unsigned int lclNum = tree->AsLclVarCommon()->GetLclNum(); unsigned int newLclNum = BAD_VAR_NUM; @@ -815,6 +929,105 @@ void ObjectAllocator::RewriteUses() return Compiler::fgWalkResult::WALK_CONTINUE; } + + Compiler::fgWalkResult PostOrderVisit(GenTree** use, GenTree* user) + { + GenTree* const tree = *use; + + // Remove GT_BOX, if stack allocated + // + if (tree->OperIs(GT_BOX)) + { + GenTree* const boxLcl = tree->AsOp()->gtGetOp1(); + assert(boxLcl->OperIs(GT_LCL_VAR, GT_LCL_ADDR)); + if (boxLcl->OperIs(GT_LCL_ADDR)) + { + JITDUMP("Removing BOX wrapper [%06u]\n", m_compiler->dspTreeID(tree)); + *use = boxLcl; + } + } + // Make box accesses explicit for UNBOX_HELPER + // + else if (tree->IsCall()) + { + GenTreeCall* const call = tree->AsCall(); + + if (call->IsHelperCall(m_compiler, CORINFO_HELP_UNBOX)) + { + JITDUMP("Found unbox helper call [%06u]\n", m_compiler->dspTreeID(call)); + + // See if second arg is possibly a stack allocated box or ref class + // (arg will have been retyped local or local address) + // + CallArg* secondArg = call->gtArgs.GetArgByIndex(1); + GenTree* const secondArgNode = secondArg->GetNode(); + + if ((secondArgNode->OperIsLocal() || secondArgNode->OperIs(GT_LCL_ADDR)) && + !secondArgNode->TypeIs(TYP_REF)) + { + const bool isForEffect = (user == nullptr) || call->TypeIs(TYP_VOID); + GenTreeLclVarCommon* const lcl = secondArgNode->AsLclVarCommon(); + + // Rewrite the call to make the box accesses explicit in jitted code. + // user = COMMA( + // CALL(UNBOX_HELPER_TYPETEST, obj->MethodTable, type), + // ADD(obj, TARGET_POINTER_SIZE)) + // + JITDUMP("Rewriting to invoke box type test helper%s\n", isForEffect ? " for side effect" : ""); + + call->gtCallMethHnd = m_compiler->eeFindHelper(CORINFO_HELP_UNBOX_TYPETEST); + GenTree* const mt = m_compiler->gtNewMethodTableLookup(lcl, /* onStack */ true); + call->gtArgs.Remove(secondArg); + call->gtArgs.PushBack(m_compiler, NewCallArg::Primitive(mt)); + + if (isForEffect) + { + // call was just for effect, we're done. + } + else + { + GenTree* const lclCopy = m_compiler->gtCloneExpr(lcl); + GenTree* const payloadAddr = + m_compiler->gtNewOperNode(GT_ADD, TYP_BYREF, lclCopy, + m_compiler->gtNewIconNode(TARGET_POINTER_SIZE, TYP_I_IMPL)); + GenTree* const comma = m_compiler->gtNewOperNode(GT_COMMA, TYP_BYREF, call, payloadAddr); + *use = comma; + } + } + } + } + else if (tree->OperIsIndir()) + { + // Look for cases where the addr is a comma created above, and + // sink the indir into the comma so later phases can see the access more cleanly. + // + GenTreeIndir* const indir = tree->AsIndir(); + GenTree* const addr = indir->Addr(); + + if (addr->OperIs(GT_COMMA)) + { + GenTree* const lastEffect = addr->AsOp()->gtGetOp1(); + + if (lastEffect->IsCall() && + lastEffect->AsCall()->IsHelperCall(m_compiler, CORINFO_HELP_UNBOX_TYPETEST)) + { + GenTree* const actualAddr = addr->gtEffectiveVal(); + GenTree* sideEffects = nullptr; + m_compiler->gtExtractSideEffList(indir, &sideEffects, GTF_SIDE_EFFECT, /* ignore root */ true); + + // indir is based on a local address, no side effect possible. + // + indir->Addr() = actualAddr; + indir->gtFlags &= ~GTF_SIDE_EFFECT; + GenTree* const newComma = + m_compiler->gtNewOperNode(GT_COMMA, indir->TypeGet(), sideEffects, indir); + *use = newComma; + } + } + } + + return Compiler::fgWalkResult::WALK_CONTINUE; + } }; for (BasicBlock* const block : comp->Blocks()) diff --git a/src/coreclr/jit/objectalloc.h b/src/coreclr/jit/objectalloc.h index 07307161da002..283216d79482b 100644 --- a/src/coreclr/jit/objectalloc.h +++ b/src/coreclr/jit/objectalloc.h @@ -47,7 +47,7 @@ class ObjectAllocator final : public Phase virtual PhaseStatus DoPhase() override; private: - bool CanAllocateLclVarOnStack(unsigned int lclNum, CORINFO_CLASS_HANDLE clsHnd); + bool CanAllocateLclVarOnStack(unsigned int lclNum, CORINFO_CLASS_HANDLE clsHnd, const char** reason); bool CanLclVarEscape(unsigned int lclNum); void MarkLclVarAsPossiblyStackPointing(unsigned int lclNum); void MarkLclVarAsDefinitelyStackPointing(unsigned int lclNum); @@ -62,7 +62,8 @@ class ObjectAllocator final : public Phase bool MorphAllocObjNodes(); void RewriteUses(); GenTree* MorphAllocObjNodeIntoHelperCall(GenTreeAllocObj* allocObj); - unsigned int MorphAllocObjNodeIntoStackAlloc(GenTreeAllocObj* allocObj, BasicBlock* block, Statement* stmt); + unsigned int MorphAllocObjNodeIntoStackAlloc( + GenTreeAllocObj* allocObj, CORINFO_CLASS_HANDLE clsHnd, bool isValueClass, BasicBlock* block, Statement* stmt); struct BuildConnGraphVisitorCallbackData; bool CanLclVarEscapeViaParentStack(ArrayStack* parentStack, unsigned int lclNum); void UpdateAncestorTypes(GenTree* tree, ArrayStack* parentStack, var_types newType); @@ -110,34 +111,86 @@ inline void ObjectAllocator::EnableObjectStackAllocation() // // Arguments: // lclNum - Local variable number -// clsHnd - Class handle of the variable class +// clsHnd - Class/struct handle of the variable class +// reason - [out, required] if result is false, reason why // // Return Value: // Returns true iff local variable can be allocated on the stack. // -// Notes: -// Stack allocation of objects with gc fields and boxed objects is currently disabled. - -inline bool ObjectAllocator::CanAllocateLclVarOnStack(unsigned int lclNum, CORINFO_CLASS_HANDLE clsHnd) +inline bool ObjectAllocator::CanAllocateLclVarOnStack(unsigned int lclNum, + CORINFO_CLASS_HANDLE clsHnd, + const char** reason) { assert(m_AnalysisDone); - DWORD classAttribs = comp->info.compCompHnd->getClassAttribs(clsHnd); + bool enableBoxedValueClasses = true; + bool enableRefClasses = true; + *reason = "[ok]"; + +#ifdef DEBUG + enableBoxedValueClasses = (JitConfig.JitObjectStackAllocationBoxedValueClass() != 0); + enableRefClasses = (JitConfig.JitObjectStackAllocationRefClass() != 0); +#endif + + unsigned int classSize = 0; - if ((classAttribs & CORINFO_FLG_VALUECLASS) != 0) + if (comp->info.compCompHnd->isValueClass(clsHnd)) { - // TODO-ObjectStackAllocation: enable stack allocation of boxed structs - return false; + if (!enableBoxedValueClasses) + { + *reason = "[disabled by config]"; + return false; + } + + if (comp->info.compCompHnd->getTypeForBoxOnStack(clsHnd) == NO_CLASS_HANDLE) + { + *reason = "[no boxed type available]"; + return false; + } + + classSize = comp->info.compCompHnd->getClassSize(clsHnd); + } + else + { + if (!enableRefClasses) + { + *reason = "[disabled by config]"; + return false; + } + + if (!comp->info.compCompHnd->canAllocateOnStack(clsHnd)) + { + *reason = "[runtime disallows]"; + return false; + } + +#ifdef FEATURE_READYTORUN + if (comp->opts.IsReadyToRun()) + { + // Need to fix getClassGClayout and maybe more + *reason = "[R2R/NAOT support NYI]"; + return false; + } +#endif + + classSize = comp->info.compCompHnd->getHeapClassSize(clsHnd); } - if (!comp->info.compCompHnd->canAllocateOnStack(clsHnd)) + if (classSize > s_StackAllocMaxSize) { + *reason = "[too large]"; return false; } - const unsigned int classSize = comp->info.compCompHnd->getHeapClassSize(clsHnd); + const bool escapes = CanLclVarEscape(lclNum); + + if (escapes) + { + *reason = "[escapes]"; + return false; + } - return !CanLclVarEscape(lclNum) && (classSize <= s_StackAllocMaxSize); + return true; } //------------------------------------------------------------------------ diff --git a/src/coreclr/jit/utils.cpp b/src/coreclr/jit/utils.cpp index 5042987770860..81d426939d3df 100644 --- a/src/coreclr/jit/utils.cpp +++ b/src/coreclr/jit/utils.cpp @@ -1519,6 +1519,7 @@ void HelperCallProperties::init() bool isAllocator = false; // true if the result is usually a newly created heap item, or may throw OutOfMemory bool mutatesHeap = false; // true if any previous heap objects [are|can be] modified bool mayRunCctor = false; // true if the helper call may cause a static constructor to be run. + bool isNoEscape = false; // true if none of the GC ref arguments can escape switch (helper) { @@ -1659,6 +1660,7 @@ void HelperCallProperties::init() case CORINFO_HELP_CHKCASTANY: case CORINFO_HELP_CHKCASTCLASS_SPECIAL: case CORINFO_HELP_READYTORUN_CHKCAST: + case CORINFO_HELP_UNBOX_TYPETEST: // These throw for a failing cast // But if given a null input arg will return null @@ -1667,8 +1669,11 @@ void HelperCallProperties::init() // helpers returning addresses, these can also throw case CORINFO_HELP_UNBOX: - case CORINFO_HELP_LDELEMA_REF: + isNoEscape = true; + isPure = true; + break; + case CORINFO_HELP_LDELEMA_REF: isPure = true; break; @@ -1830,6 +1835,7 @@ void HelperCallProperties::init() m_isAllocator[helper] = isAllocator; m_mutatesHeap[helper] = mutatesHeap; m_mayRunCctor[helper] = mayRunCctor; + m_isNoEscape[helper] = isNoEscape; } } diff --git a/src/coreclr/jit/utils.h b/src/coreclr/jit/utils.h index f665a4e813eff..29a1d7d29c933 100644 --- a/src/coreclr/jit/utils.h +++ b/src/coreclr/jit/utils.h @@ -590,6 +590,7 @@ class HelperCallProperties bool m_isAllocator[CORINFO_HELP_COUNT]; bool m_mutatesHeap[CORINFO_HELP_COUNT]; bool m_mayRunCctor[CORINFO_HELP_COUNT]; + bool m_isNoEscape[CORINFO_HELP_COUNT]; void init(); @@ -647,6 +648,13 @@ class HelperCallProperties assert(helperId < CORINFO_HELP_COUNT); return m_mayRunCctor[helperId]; } + + bool IsNoEscape(CorInfoHelpFunc helperId) + { + assert(helperId > CORINFO_HELP_UNDEF); + assert(helperId < CORINFO_HELP_COUNT); + return m_isNoEscape[helperId]; + } }; //***************************************************************************** diff --git a/src/coreclr/jit/valuenum.cpp b/src/coreclr/jit/valuenum.cpp index 8c8b215cf9298..624b0a5396796 100644 --- a/src/coreclr/jit/valuenum.cpp +++ b/src/coreclr/jit/valuenum.cpp @@ -13404,6 +13404,10 @@ VNFunc Compiler::fgValueNumberJitHelperMethodVNFunc(CorInfoHelpFunc helpFunc) vnf = VNF_Unbox; break; + case CORINFO_HELP_UNBOX_TYPETEST: + vnf = VNF_Unbox_TypeTest; + break; + // A constant within any method. case CORINFO_HELP_GETCURRENTMANAGEDTHREADID: vnf = VNF_ManagedThreadId; diff --git a/src/coreclr/jit/valuenumfuncs.h b/src/coreclr/jit/valuenumfuncs.h index 869cce00c9c3c..70c950fccfc7e 100644 --- a/src/coreclr/jit/valuenumfuncs.h +++ b/src/coreclr/jit/valuenumfuncs.h @@ -161,6 +161,7 @@ ValueNumFuncDef(LazyStrCns, 2, false, true, false, false) // Lazy-ini ValueNumFuncDef(InvariantLoad, 1, false, false, false, false) // Args: 0: (VN of) the address. ValueNumFuncDef(InvariantNonNullLoad, 1, false, true, false, false) // Args: 0: (VN of) the address. ValueNumFuncDef(Unbox, 2, false, false, false, false) +ValueNumFuncDef(Unbox_TypeTest, 2, false, false, false, false) ValueNumFuncDef(LT_UN, 2, false, false, false, false) // unsigned or unordered comparisons ValueNumFuncDef(LE_UN, 2, false, false, false, false) diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs b/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs index 47d202909bce3..0ba59363c8f9c 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs @@ -87,6 +87,7 @@ which is the right helper to use to allocate an object of a given type. */ CORINFO_HELP_BOX, // Fast box helper. Only possible exception is OutOfMemory CORINFO_HELP_BOX_NULLABLE, // special form of boxing for Nullable CORINFO_HELP_UNBOX, + CORINFO_HELP_UNBOX_TYPETEST, CORINFO_HELP_UNBOX_NULLABLE, // special form of unboxing for Nullable CORINFO_HELP_GETREFANY, // Extract the byref from a TypedReference, checking that it is the expected type diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index a695d07be6fcf..22cb30e58a6ab 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -2604,6 +2604,13 @@ private CorInfoHelpFunc getSharedCCtorHelper(CORINFO_CLASS_STRUCT_* clsHnd) return ObjectToHandle(typeForBox); } + private CORINFO_CLASS_STRUCT_* getTypeForBoxOnStack(CORINFO_CLASS_STRUCT_* cls) + { + // Todo: implement... + _ = HandleToObject(cls); + return null; + } + private CorInfoHelpFunc getBoxHelper(CORINFO_CLASS_STRUCT_* cls) { var type = HandleToObject(cls); diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs index 950ce7f4a1223..16840f73581a1 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs @@ -957,6 +957,21 @@ private static CorInfoHelpFunc _getSharedCCtorHelper(IntPtr thisHandle, IntPtr* } } + [UnmanagedCallersOnly] + private static CORINFO_CLASS_STRUCT_* _getTypeForBoxOnStack(IntPtr thisHandle, IntPtr* ppException, CORINFO_CLASS_STRUCT_* cls) + { + var _this = GetThis(thisHandle); + try + { + return _this.getTypeForBoxOnStack(cls); + } + catch (Exception ex) + { + *ppException = _this.AllocException(ex); + return default; + } + } + [UnmanagedCallersOnly] private static CorInfoHelpFunc _getBoxHelper(IntPtr thisHandle, IntPtr* ppException, CORINFO_CLASS_STRUCT_* cls) { @@ -2609,7 +2624,7 @@ private static uint _getJitFlags(IntPtr thisHandle, IntPtr* ppException, CORJIT_ private static IntPtr GetUnmanagedCallbacks() { - void** callbacks = (void**)Marshal.AllocCoTaskMem(sizeof(IntPtr) * 176); + void** callbacks = (void**)Marshal.AllocCoTaskMem(sizeof(IntPtr) * 177); callbacks[0] = (delegate* unmanaged)&_isIntrinsic; callbacks[1] = (delegate* unmanaged)&_notifyMethodInfoUsage; @@ -2675,118 +2690,119 @@ private static IntPtr GetUnmanagedCallbacks() callbacks[61] = (delegate* unmanaged)&_getCastingHelper; callbacks[62] = (delegate* unmanaged)&_getSharedCCtorHelper; callbacks[63] = (delegate* unmanaged)&_getTypeForBox; - callbacks[64] = (delegate* unmanaged)&_getBoxHelper; - callbacks[65] = (delegate* unmanaged)&_getUnBoxHelper; - callbacks[66] = (delegate* unmanaged)&_getRuntimeTypePointer; - callbacks[67] = (delegate* unmanaged)&_isObjectImmutable; - callbacks[68] = (delegate* unmanaged)&_getStringChar; - callbacks[69] = (delegate* unmanaged)&_getObjectType; - callbacks[70] = (delegate* unmanaged)&_getReadyToRunHelper; - callbacks[71] = (delegate* unmanaged)&_getReadyToRunDelegateCtorHelper; - callbacks[72] = (delegate* unmanaged)&_initClass; - callbacks[73] = (delegate* unmanaged)&_classMustBeLoadedBeforeCodeIsRun; - callbacks[74] = (delegate* unmanaged)&_getBuiltinClass; - callbacks[75] = (delegate* unmanaged)&_getTypeForPrimitiveValueClass; - callbacks[76] = (delegate* unmanaged)&_getTypeForPrimitiveNumericClass; - callbacks[77] = (delegate* unmanaged)&_canCast; - callbacks[78] = (delegate* unmanaged)&_compareTypesForCast; - callbacks[79] = (delegate* unmanaged)&_compareTypesForEquality; - callbacks[80] = (delegate* unmanaged)&_isMoreSpecificType; - callbacks[81] = (delegate* unmanaged)&_isExactType; - callbacks[82] = (delegate* unmanaged)&_isGenericType; - callbacks[83] = (delegate* unmanaged)&_isNullableType; - callbacks[84] = (delegate* unmanaged)&_isEnum; - callbacks[85] = (delegate* unmanaged)&_getParentType; - callbacks[86] = (delegate* unmanaged)&_getChildType; - callbacks[87] = (delegate* unmanaged)&_isSDArray; - callbacks[88] = (delegate* unmanaged)&_getArrayRank; - callbacks[89] = (delegate* unmanaged)&_getArrayIntrinsicID; - callbacks[90] = (delegate* unmanaged)&_getArrayInitializationData; - callbacks[91] = (delegate* unmanaged)&_canAccessClass; - callbacks[92] = (delegate* unmanaged)&_printFieldName; - callbacks[93] = (delegate* unmanaged)&_getFieldClass; - callbacks[94] = (delegate* unmanaged)&_getFieldType; - callbacks[95] = (delegate* unmanaged)&_getFieldOffset; - callbacks[96] = (delegate* unmanaged)&_getFieldInfo; - callbacks[97] = (delegate* unmanaged)&_getThreadLocalFieldInfo; - callbacks[98] = (delegate* unmanaged)&_getThreadLocalStaticBlocksInfo; - callbacks[99] = (delegate* unmanaged)&_getThreadLocalStaticInfo_NativeAOT; - callbacks[100] = (delegate* unmanaged)&_isFieldStatic; - callbacks[101] = (delegate* unmanaged)&_getArrayOrStringLength; - callbacks[102] = (delegate* unmanaged)&_getBoundaries; - callbacks[103] = (delegate* unmanaged)&_setBoundaries; - callbacks[104] = (delegate* unmanaged)&_getVars; - callbacks[105] = (delegate* unmanaged)&_setVars; - callbacks[106] = (delegate* unmanaged)&_reportRichMappings; - callbacks[107] = (delegate* unmanaged)&_reportMetadata; - callbacks[108] = (delegate* unmanaged)&_allocateArray; - callbacks[109] = (delegate* unmanaged)&_freeArray; - callbacks[110] = (delegate* unmanaged)&_getArgNext; - callbacks[111] = (delegate* unmanaged)&_getArgType; - callbacks[112] = (delegate* unmanaged)&_getExactClasses; - callbacks[113] = (delegate* unmanaged)&_getArgClass; - callbacks[114] = (delegate* unmanaged)&_getHFAType; - callbacks[115] = (delegate* unmanaged)&_runWithErrorTrap; - callbacks[116] = (delegate* unmanaged)&_runWithSPMIErrorTrap; - callbacks[117] = (delegate* unmanaged)&_getEEInfo; - callbacks[118] = (delegate* unmanaged)&_getJitTimeLogFilename; - callbacks[119] = (delegate* unmanaged)&_getMethodDefFromMethod; - callbacks[120] = (delegate* unmanaged)&_printMethodName; - callbacks[121] = (delegate* unmanaged)&_getMethodNameFromMetadata; - callbacks[122] = (delegate* unmanaged)&_getMethodHash; - callbacks[123] = (delegate* unmanaged)&_getSystemVAmd64PassStructInRegisterDescriptor; - callbacks[124] = (delegate* unmanaged)&_getSwiftLowering; - callbacks[125] = (delegate* unmanaged)&_getLoongArch64PassStructInRegisterFlags; - callbacks[126] = (delegate* unmanaged)&_getRISCV64PassStructInRegisterFlags; - callbacks[127] = (delegate* unmanaged)&_getThreadTLSIndex; - callbacks[128] = (delegate* unmanaged)&_getAddrOfCaptureThreadGlobal; - callbacks[129] = (delegate* unmanaged)&_getHelperFtn; - callbacks[130] = (delegate* unmanaged)&_getFunctionEntryPoint; - callbacks[131] = (delegate* unmanaged)&_getFunctionFixedEntryPoint; - callbacks[132] = (delegate* unmanaged)&_getMethodSync; - callbacks[133] = (delegate* unmanaged)&_getLazyStringLiteralHelper; - callbacks[134] = (delegate* unmanaged)&_embedModuleHandle; - callbacks[135] = (delegate* unmanaged)&_embedClassHandle; - callbacks[136] = (delegate* unmanaged)&_embedMethodHandle; - callbacks[137] = (delegate* unmanaged)&_embedFieldHandle; - callbacks[138] = (delegate* unmanaged)&_embedGenericHandle; - callbacks[139] = (delegate* unmanaged)&_getLocationOfThisType; - callbacks[140] = (delegate* unmanaged)&_getAddressOfPInvokeTarget; - callbacks[141] = (delegate* unmanaged)&_GetCookieForPInvokeCalliSig; - callbacks[142] = (delegate* unmanaged)&_canGetCookieForPInvokeCalliSig; - callbacks[143] = (delegate* unmanaged)&_getJustMyCodeHandle; - callbacks[144] = (delegate* unmanaged)&_GetProfilingHandle; - callbacks[145] = (delegate* unmanaged)&_getCallInfo; - callbacks[146] = (delegate* unmanaged)&_getStaticFieldContent; - callbacks[147] = (delegate* unmanaged)&_getObjectContent; - callbacks[148] = (delegate* unmanaged)&_getStaticFieldCurrentClass; - callbacks[149] = (delegate* unmanaged)&_getVarArgsHandle; - callbacks[150] = (delegate* unmanaged)&_canGetVarArgsHandle; - callbacks[151] = (delegate* unmanaged)&_constructStringLiteral; - callbacks[152] = (delegate* unmanaged)&_emptyStringLiteral; - callbacks[153] = (delegate* unmanaged)&_getFieldThreadLocalStoreID; - callbacks[154] = (delegate* unmanaged)&_GetDelegateCtor; - callbacks[155] = (delegate* unmanaged)&_MethodCompileComplete; - callbacks[156] = (delegate* unmanaged)&_getTailCallHelpers; - callbacks[157] = (delegate* unmanaged)&_convertPInvokeCalliToCall; - callbacks[158] = (delegate* unmanaged)&_notifyInstructionSetUsage; - callbacks[159] = (delegate* unmanaged)&_updateEntryPointForTailCall; - callbacks[160] = (delegate* unmanaged)&_allocMem; - callbacks[161] = (delegate* unmanaged)&_reserveUnwindInfo; - callbacks[162] = (delegate* unmanaged)&_allocUnwindInfo; - callbacks[163] = (delegate* unmanaged)&_allocGCInfo; - callbacks[164] = (delegate* unmanaged)&_setEHcount; - callbacks[165] = (delegate* unmanaged)&_setEHinfo; - callbacks[166] = (delegate* unmanaged)&_logMsg; - callbacks[167] = (delegate* unmanaged)&_doAssert; - callbacks[168] = (delegate* unmanaged)&_reportFatalError; - callbacks[169] = (delegate* unmanaged)&_getPgoInstrumentationResults; - callbacks[170] = (delegate* unmanaged)&_allocPgoInstrumentationBySchema; - callbacks[171] = (delegate* unmanaged)&_recordCallSite; - callbacks[172] = (delegate* unmanaged)&_recordRelocation; - callbacks[173] = (delegate* unmanaged)&_getRelocTypeHint; - callbacks[174] = (delegate* unmanaged)&_getExpectedTargetArchitecture; - callbacks[175] = (delegate* unmanaged)&_getJitFlags; + callbacks[64] = (delegate* unmanaged)&_getTypeForBoxOnStack; + callbacks[65] = (delegate* unmanaged)&_getBoxHelper; + callbacks[66] = (delegate* unmanaged)&_getUnBoxHelper; + callbacks[67] = (delegate* unmanaged)&_getRuntimeTypePointer; + callbacks[68] = (delegate* unmanaged)&_isObjectImmutable; + callbacks[69] = (delegate* unmanaged)&_getStringChar; + callbacks[70] = (delegate* unmanaged)&_getObjectType; + callbacks[71] = (delegate* unmanaged)&_getReadyToRunHelper; + callbacks[72] = (delegate* unmanaged)&_getReadyToRunDelegateCtorHelper; + callbacks[73] = (delegate* unmanaged)&_initClass; + callbacks[74] = (delegate* unmanaged)&_classMustBeLoadedBeforeCodeIsRun; + callbacks[75] = (delegate* unmanaged)&_getBuiltinClass; + callbacks[76] = (delegate* unmanaged)&_getTypeForPrimitiveValueClass; + callbacks[77] = (delegate* unmanaged)&_getTypeForPrimitiveNumericClass; + callbacks[78] = (delegate* unmanaged)&_canCast; + callbacks[79] = (delegate* unmanaged)&_compareTypesForCast; + callbacks[80] = (delegate* unmanaged)&_compareTypesForEquality; + callbacks[81] = (delegate* unmanaged)&_isMoreSpecificType; + callbacks[82] = (delegate* unmanaged)&_isExactType; + callbacks[83] = (delegate* unmanaged)&_isGenericType; + callbacks[84] = (delegate* unmanaged)&_isNullableType; + callbacks[85] = (delegate* unmanaged)&_isEnum; + callbacks[86] = (delegate* unmanaged)&_getParentType; + callbacks[87] = (delegate* unmanaged)&_getChildType; + callbacks[88] = (delegate* unmanaged)&_isSDArray; + callbacks[89] = (delegate* unmanaged)&_getArrayRank; + callbacks[90] = (delegate* unmanaged)&_getArrayIntrinsicID; + callbacks[91] = (delegate* unmanaged)&_getArrayInitializationData; + callbacks[92] = (delegate* unmanaged)&_canAccessClass; + callbacks[93] = (delegate* unmanaged)&_printFieldName; + callbacks[94] = (delegate* unmanaged)&_getFieldClass; + callbacks[95] = (delegate* unmanaged)&_getFieldType; + callbacks[96] = (delegate* unmanaged)&_getFieldOffset; + callbacks[97] = (delegate* unmanaged)&_getFieldInfo; + callbacks[98] = (delegate* unmanaged)&_getThreadLocalFieldInfo; + callbacks[99] = (delegate* unmanaged)&_getThreadLocalStaticBlocksInfo; + callbacks[100] = (delegate* unmanaged)&_getThreadLocalStaticInfo_NativeAOT; + callbacks[101] = (delegate* unmanaged)&_isFieldStatic; + callbacks[102] = (delegate* unmanaged)&_getArrayOrStringLength; + callbacks[103] = (delegate* unmanaged)&_getBoundaries; + callbacks[104] = (delegate* unmanaged)&_setBoundaries; + callbacks[105] = (delegate* unmanaged)&_getVars; + callbacks[106] = (delegate* unmanaged)&_setVars; + callbacks[107] = (delegate* unmanaged)&_reportRichMappings; + callbacks[108] = (delegate* unmanaged)&_reportMetadata; + callbacks[109] = (delegate* unmanaged)&_allocateArray; + callbacks[110] = (delegate* unmanaged)&_freeArray; + callbacks[111] = (delegate* unmanaged)&_getArgNext; + callbacks[112] = (delegate* unmanaged)&_getArgType; + callbacks[113] = (delegate* unmanaged)&_getExactClasses; + callbacks[114] = (delegate* unmanaged)&_getArgClass; + callbacks[115] = (delegate* unmanaged)&_getHFAType; + callbacks[116] = (delegate* unmanaged)&_runWithErrorTrap; + callbacks[117] = (delegate* unmanaged)&_runWithSPMIErrorTrap; + callbacks[118] = (delegate* unmanaged)&_getEEInfo; + callbacks[119] = (delegate* unmanaged)&_getJitTimeLogFilename; + callbacks[120] = (delegate* unmanaged)&_getMethodDefFromMethod; + callbacks[121] = (delegate* unmanaged)&_printMethodName; + callbacks[122] = (delegate* unmanaged)&_getMethodNameFromMetadata; + callbacks[123] = (delegate* unmanaged)&_getMethodHash; + callbacks[124] = (delegate* unmanaged)&_getSystemVAmd64PassStructInRegisterDescriptor; + callbacks[125] = (delegate* unmanaged)&_getSwiftLowering; + callbacks[126] = (delegate* unmanaged)&_getLoongArch64PassStructInRegisterFlags; + callbacks[127] = (delegate* unmanaged)&_getRISCV64PassStructInRegisterFlags; + callbacks[128] = (delegate* unmanaged)&_getThreadTLSIndex; + callbacks[129] = (delegate* unmanaged)&_getAddrOfCaptureThreadGlobal; + callbacks[130] = (delegate* unmanaged)&_getHelperFtn; + callbacks[131] = (delegate* unmanaged)&_getFunctionEntryPoint; + callbacks[132] = (delegate* unmanaged)&_getFunctionFixedEntryPoint; + callbacks[133] = (delegate* unmanaged)&_getMethodSync; + callbacks[134] = (delegate* unmanaged)&_getLazyStringLiteralHelper; + callbacks[135] = (delegate* unmanaged)&_embedModuleHandle; + callbacks[136] = (delegate* unmanaged)&_embedClassHandle; + callbacks[137] = (delegate* unmanaged)&_embedMethodHandle; + callbacks[138] = (delegate* unmanaged)&_embedFieldHandle; + callbacks[139] = (delegate* unmanaged)&_embedGenericHandle; + callbacks[140] = (delegate* unmanaged)&_getLocationOfThisType; + callbacks[141] = (delegate* unmanaged)&_getAddressOfPInvokeTarget; + callbacks[142] = (delegate* unmanaged)&_GetCookieForPInvokeCalliSig; + callbacks[143] = (delegate* unmanaged)&_canGetCookieForPInvokeCalliSig; + callbacks[144] = (delegate* unmanaged)&_getJustMyCodeHandle; + callbacks[145] = (delegate* unmanaged)&_GetProfilingHandle; + callbacks[146] = (delegate* unmanaged)&_getCallInfo; + callbacks[147] = (delegate* unmanaged)&_getStaticFieldContent; + callbacks[148] = (delegate* unmanaged)&_getObjectContent; + callbacks[149] = (delegate* unmanaged)&_getStaticFieldCurrentClass; + callbacks[150] = (delegate* unmanaged)&_getVarArgsHandle; + callbacks[151] = (delegate* unmanaged)&_canGetVarArgsHandle; + callbacks[152] = (delegate* unmanaged)&_constructStringLiteral; + callbacks[153] = (delegate* unmanaged)&_emptyStringLiteral; + callbacks[154] = (delegate* unmanaged)&_getFieldThreadLocalStoreID; + callbacks[155] = (delegate* unmanaged)&_GetDelegateCtor; + callbacks[156] = (delegate* unmanaged)&_MethodCompileComplete; + callbacks[157] = (delegate* unmanaged)&_getTailCallHelpers; + callbacks[158] = (delegate* unmanaged)&_convertPInvokeCalliToCall; + callbacks[159] = (delegate* unmanaged)&_notifyInstructionSetUsage; + callbacks[160] = (delegate* unmanaged)&_updateEntryPointForTailCall; + callbacks[161] = (delegate* unmanaged)&_allocMem; + callbacks[162] = (delegate* unmanaged)&_reserveUnwindInfo; + callbacks[163] = (delegate* unmanaged)&_allocUnwindInfo; + callbacks[164] = (delegate* unmanaged)&_allocGCInfo; + callbacks[165] = (delegate* unmanaged)&_setEHcount; + callbacks[166] = (delegate* unmanaged)&_setEHinfo; + callbacks[167] = (delegate* unmanaged)&_logMsg; + callbacks[168] = (delegate* unmanaged)&_doAssert; + callbacks[169] = (delegate* unmanaged)&_reportFatalError; + callbacks[170] = (delegate* unmanaged)&_getPgoInstrumentationResults; + callbacks[171] = (delegate* unmanaged)&_allocPgoInstrumentationBySchema; + callbacks[172] = (delegate* unmanaged)&_recordCallSite; + callbacks[173] = (delegate* unmanaged)&_recordRelocation; + callbacks[174] = (delegate* unmanaged)&_getRelocTypeHint; + callbacks[175] = (delegate* unmanaged)&_getExpectedTargetArchitecture; + callbacks[176] = (delegate* unmanaged)&_getJitFlags; return (IntPtr)callbacks; } diff --git a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt index 9fc314fe56a37..e2157fb43e4d4 100644 --- a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt +++ b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt @@ -227,6 +227,7 @@ FUNCTIONS CorInfoHelpFunc getCastingHelper(CORINFO_RESOLVED_TOKEN* pResolvedToken, bool fThrowing) CorInfoHelpFunc getSharedCCtorHelper(CORINFO_CLASS_HANDLE clsHnd) CORINFO_CLASS_HANDLE getTypeForBox(CORINFO_CLASS_HANDLE cls) + CORINFO_CLASS_HANDLE getTypeForBoxOnStack(CORINFO_CLASS_HANDLE cls) CorInfoHelpFunc getBoxHelper(CORINFO_CLASS_HANDLE cls) CorInfoHelpFunc getUnBoxHelper(CORINFO_CLASS_HANDLE cls) CORINFO_OBJECT_HANDLE getRuntimeTypePointer(CORINFO_CLASS_HANDLE cls) diff --git a/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h b/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h index 75722274d9e9b..a8e95b43bc38e 100644 --- a/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h +++ b/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h @@ -75,6 +75,7 @@ struct JitInterfaceCallbacks CorInfoHelpFunc (* getCastingHelper)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_RESOLVED_TOKEN* pResolvedToken, bool fThrowing); CorInfoHelpFunc (* getSharedCCtorHelper)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE clsHnd); CORINFO_CLASS_HANDLE (* getTypeForBox)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE cls); + CORINFO_CLASS_HANDLE (* getTypeForBoxOnStack)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE cls); CorInfoHelpFunc (* getBoxHelper)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE cls); CorInfoHelpFunc (* getUnBoxHelper)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE cls); CORINFO_OBJECT_HANDLE (* getRuntimeTypePointer)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE cls); @@ -825,6 +826,15 @@ class JitInterfaceWrapper : public ICorJitInfo return temp; } + virtual CORINFO_CLASS_HANDLE getTypeForBoxOnStack( + CORINFO_CLASS_HANDLE cls) +{ + CorInfoExceptionClass* pException = nullptr; + CORINFO_CLASS_HANDLE temp = _callbacks->getTypeForBoxOnStack(_thisHandle, &pException, cls); + if (pException != nullptr) throw pException; + return temp; +} + virtual CorInfoHelpFunc getBoxHelper( CORINFO_CLASS_HANDLE cls) { diff --git a/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h b/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h index fd85fdd97e892..eb4968fbb4488 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h @@ -134,6 +134,7 @@ LWM(UpdateEntryPointForTailCall, Agnostic_CORINFO_CONST_LOOKUP, Agnostic_CORINFO LWM(GetThreadTLSIndex, DWORD, DLD) LWM(GetTokenTypeAsHandle, GetTokenTypeAsHandleValue, DWORDLONG) LWM(GetTypeForBox, DWORDLONG, DWORDLONG) +LWM(GetTypeForBoxOnStack, DWORDLONG, DWORDLONG) LWM(GetTypeForPrimitiveValueClass, DWORDLONG, DWORD) LWM(GetTypeForPrimitiveNumericClass, DWORDLONG, DWORD) LWM(GetUnboxedEntry, DWORDLONG, DLD); diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp index 0e0fa438e727a..53bb5a2f00c42 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp @@ -1941,6 +1941,30 @@ CORINFO_CLASS_HANDLE MethodContext::repGetTypeForBox(CORINFO_CLASS_HANDLE cls) return result; } +void MethodContext::recGetTypeForBoxOnStack(CORINFO_CLASS_HANDLE cls, CORINFO_CLASS_HANDLE result) +{ + if (GetTypeForBoxOnStack == nullptr) + GetTypeForBoxOnStack = new LightWeightMap(); + + DWORDLONG key = CastHandle(cls); + DWORDLONG value = CastHandle(result); + GetTypeForBoxOnStack->Add(key, value); + DEBUG_REC(dmpGetTypeForBoxOnStack(key, value)); +} +void MethodContext::dmpGetTypeForBoxOnStack(DWORDLONG key, DWORDLONG value) +{ + printf("GetTypeForBoxOnStack key cls-%016" PRIX64 ", value res-%016" PRIX64 "", key, value); +} + +CORINFO_CLASS_HANDLE MethodContext::repGetTypeForBoxOnStack(CORINFO_CLASS_HANDLE cls) +{ + DWORDLONG key = CastHandle(cls); + DWORDLONG value = LookupByKeyOrMiss(GetTypeForBoxOnStack, key, ": key %016" PRIX64 "", key); + DEBUG_REP(dmpGetTypeForBoxOnStack(key, value)); + CORINFO_CLASS_HANDLE result = (CORINFO_CLASS_HANDLE)value; + return result; +} + void MethodContext::recGetBoxHelper(CORINFO_CLASS_HANDLE cls, CorInfoHelpFunc result) { if (GetBoxHelper == nullptr) diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h index c970dd9a7b226..679fa2e0355e1 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h @@ -248,6 +248,10 @@ class MethodContext void dmpGetTypeForBox(DWORDLONG key, DWORDLONG value); CORINFO_CLASS_HANDLE repGetTypeForBox(CORINFO_CLASS_HANDLE cls); + void recGetTypeForBoxOnStack(CORINFO_CLASS_HANDLE cls, CORINFO_CLASS_HANDLE result); + void dmpGetTypeForBoxOnStack(DWORDLONG key, DWORDLONG value); + CORINFO_CLASS_HANDLE repGetTypeForBoxOnStack(CORINFO_CLASS_HANDLE cls); + void recGetBoxHelper(CORINFO_CLASS_HANDLE cls, CorInfoHelpFunc result); void dmpGetBoxHelper(DWORDLONG key, DWORD value); CorInfoHelpFunc repGetBoxHelper(CORINFO_CLASS_HANDLE cls); @@ -1181,6 +1185,7 @@ enum mcPackets Packet_GetClassStaticDynamicInfo = 218, Packet_GetClassThreadStaticDynamicInfo = 219, Packet_IsGenericType = 220, + Packet_GetTypeForBoxOnStack = 221, }; void SetDebugDumpVariables(); diff --git a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp index 84ad616c28eb9..611edfaf034e5 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp @@ -585,7 +585,7 @@ unsigned interceptor_ICJI::getClassAlignmentRequirement(CORINFO_CLASS_HANDLE cls return temp; } -// This is only called for Value classes. It returns a boolean array +// This is called for ref and value classes. It returns a boolean array // in representing of 'cls' from a GC perspective. The class is // assumed to be an array of machine words // (of length // getClassSize(cls) / sizeof(void*)), @@ -600,7 +600,15 @@ unsigned interceptor_ICJI::getClassGClayout(CORINFO_CLASS_HANDLE cls, /* IN */ { mc->cr->AddCall("getClassGClayout"); unsigned temp = original_ICorJitInfo->getClassGClayout(cls, gcPtrs); - unsigned len = (getClassSize(cls) + sizeof(void*) - 1) / sizeof(void*); + unsigned len = 0; + if (isValueClass(cls)) + { + len = (getClassSize(cls) + sizeof(void*) - 1) / sizeof(void*); + } + else + { + len = (getHeapClassSize(cls) + sizeof(void*) - 1) / sizeof(void*); + } mc->recGetClassGClayout(cls, gcPtrs, len, temp); return temp; } @@ -707,6 +715,15 @@ CORINFO_CLASS_HANDLE interceptor_ICJI::getTypeForBox(CORINFO_CLASS_HANDLE cls) return temp; } +// Class handle for a boxed value type, on the stack. +CORINFO_CLASS_HANDLE interceptor_ICJI::getTypeForBoxOnStack(CORINFO_CLASS_HANDLE cls) +{ + mc->cr->AddCall("getTypeForBoxOnStack"); + CORINFO_CLASS_HANDLE temp = original_ICorJitInfo->getTypeForBoxOnStack(cls); + mc->recGetTypeForBoxOnStack(cls, temp); + return temp; +} + // returns the correct box helper for a particular class. Note // that if this returns CORINFO_HELP_BOX, the JIT can assume // 'standard' boxing (allocate object and copy), and optimize 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 2d6faf2c65728..db3573b30e418 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp @@ -522,6 +522,13 @@ CORINFO_CLASS_HANDLE interceptor_ICJI::getTypeForBox( return original_ICorJitInfo->getTypeForBox(cls); } +CORINFO_CLASS_HANDLE interceptor_ICJI::getTypeForBoxOnStack( + CORINFO_CLASS_HANDLE cls) +{ + mcs->AddCall("getTypeForBoxOnStack"); + return original_ICorJitInfo->getTypeForBoxOnStack(cls); +} + CorInfoHelpFunc interceptor_ICJI::getBoxHelper( CORINFO_CLASS_HANDLE cls) { 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 210485e727c9b..152a3d9ba7cf1 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp @@ -458,6 +458,12 @@ CORINFO_CLASS_HANDLE interceptor_ICJI::getTypeForBox( return original_ICorJitInfo->getTypeForBox(cls); } +CORINFO_CLASS_HANDLE interceptor_ICJI::getTypeForBoxOnStack( + CORINFO_CLASS_HANDLE cls) +{ + return original_ICorJitInfo->getTypeForBoxOnStack(cls); +} + CorInfoHelpFunc interceptor_ICJI::getBoxHelper( CORINFO_CLASS_HANDLE cls) { diff --git a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp index 9e29f43e03ec3..e42b41ee8365d 100644 --- a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp @@ -509,7 +509,7 @@ unsigned MyICJI::getClassAlignmentRequirement(CORINFO_CLASS_HANDLE cls, bool fDo return jitInstance->mc->repGetClassAlignmentRequirement(cls, fDoubleAlignHint); } -// This is only called for Value classes. It returns a boolean array +// This called for ref and value classes. It returns a boolean array // in representing of 'cls' from a GC perspective. The class is // assumed to be an array of machine words // (of length // getClassSize(cls) / sizeof(void*)), @@ -598,6 +598,13 @@ CORINFO_CLASS_HANDLE MyICJI::getTypeForBox(CORINFO_CLASS_HANDLE cls) return jitInstance->mc->repGetTypeForBox(cls); } +// Class handle for a boxed value type, on the stack. +CORINFO_CLASS_HANDLE MyICJI::getTypeForBoxOnStack(CORINFO_CLASS_HANDLE cls) +{ + jitInstance->mc->cr->AddCall("getTypeForBoxOnStack"); + return jitInstance->mc->repGetTypeForBoxOnStack(cls); +} + // returns the correct box helper for a particular class. Note // that if this returns CORINFO_HELP_BOX, the JIT can assume // 'standard' boxing (allocate object and copy), and optimize diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 0941e376d83e7..84a57404fed42 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -1113,6 +1113,8 @@ DEFINE_METHOD(ICASTABLEHELPERS, GETIMPLTYPE, GetImplType, SM_ICast #endif // FEATURE_ICASTABLE +DEFINE_CLASS(STACKALLOCATEDBOX, CompilerServices, StackAllocatedBox`1) + DEFINE_CLASS(UTF8STRINGMARSHALLER, Marshalling, Utf8StringMarshaller) DEFINE_METHOD(UTF8STRINGMARSHALLER, CONVERT_TO_MANAGED, ConvertToManaged, SM_PtrByte_RetStr) DEFINE_METHOD(UTF8STRINGMARSHALLER, CONVERT_TO_UNMANAGED, ConvertToUnmanaged, SM_Str_RetPtrByte) diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index 523261cfc0e9e..db21d429daa9a 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -352,6 +352,7 @@ FCFuncStart(gCastHelpers) FCFuncElement("IsInstanceOfAny_NoCacheLookup", ::IsInstanceOfAny_NoCacheLookup) FCFuncElement("ChkCastAny_NoCacheLookup", ::ChkCastAny_NoCacheLookup) FCFuncElement("Unbox_Helper", ::Unbox_Helper) + FCFuncElement("JIT_Unbox_TypeTest", ::JIT_Unbox_TypeTest) FCFuncElement("WriteBarrier", ::WriteBarrier_Helper) FCFuncEnd() diff --git a/src/coreclr/vm/jithelpers.cpp b/src/coreclr/vm/jithelpers.cpp index 7683018a5da41..1bfeaf2b03928 100644 --- a/src/coreclr/vm/jithelpers.cpp +++ b/src/coreclr/vm/jithelpers.cpp @@ -2305,6 +2305,65 @@ HCIMPL2(LPVOID, Unbox_Helper, CORINFO_CLASS_HANDLE type, Object* obj) } HCIMPLEND +/* framed Unbox type test helper that handles enums and full-blown type equivalence */ +NOINLINE HCIMPL2(void, JIT_Unbox_TypeTest_Framed, MethodTable* pMT1, MethodTable* pMT2) +{ + FCALL_CONTRACT; + + HELPER_METHOD_FRAME_BEGIN_0(); + HELPER_METHOD_POLL(); + + if (pMT1->GetInternalCorElementType() == pMT2->GetInternalCorElementType() && + (pMT1->IsEnum() || pMT1->IsTruePrimitive()) && + (pMT2->IsEnum() || pMT2->IsTruePrimitive())) + { + // type test test passes + } + else if (pMT1->IsEquivalentTo(pMT2)) + { + // the structures are equivalent + } + else + { + COMPlusThrowInvalidCastException(TypeHandle(pMT2), TypeHandle(pMT1)); + } + HELPER_METHOD_FRAME_END(); +} +HCIMPLEND + +/*************************************************************/ +/* Unbox type test that handles enums */ +HCIMPL2(void, JIT_Unbox_TypeTest, CORINFO_CLASS_HANDLE type, CORINFO_CLASS_HANDLE boxType) +{ + FCALL_CONTRACT; + + TypeHandle typeHnd(type); + // boxable types have method tables + _ASSERTE(!typeHnd.IsTypeDesc()); + + MethodTable* pMT1 = typeHnd.AsMethodTable(); + // must be a value type + _ASSERTE(pMT1->IsValueType()); + + TypeHandle boxTypeHnd(boxType); + MethodTable* pMT2 = boxTypeHnd.AsMethodTable(); + + // we allow enums and their primitive type to be interchangeable. + // if suspension is requested, defer to the framed helper. + if (pMT1->GetInternalCorElementType() == pMT2->GetInternalCorElementType() && + (pMT1->IsEnum() || pMT1->IsTruePrimitive()) && + (pMT2->IsEnum() || pMT2->IsTruePrimitive()) && + g_TrapReturningThreads == 0) + { + return; + } + + // Fall back to a framed helper that can also handle GC suspension and type equivalence. + ENDFORBIDGC(); + HCCALL2(JIT_Unbox_TypeTest_Framed, pMT1, pMT2); +} +HCIMPLEND + /*************************************************************/ HCIMPL2_IV(LPVOID, JIT_GetRefAny, CORINFO_CLASS_HANDLE type, TypedByRef typedByRef) { diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 4b0180d2fd757..9bcedace5d240 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -1906,11 +1906,22 @@ CEEInfo::getHeapClassSize( TypeHandle VMClsHnd(clsHnd); MethodTable* pMT = VMClsHnd.GetMethodTable(); _ASSERTE(pMT); - _ASSERTE(!pMT->IsValueType()); _ASSERTE(!pMT->HasComponentSize()); +#ifdef FEATURE_READYTORUN_COMPILER + _ASSERTE(!IsReadyToRunCompilation() || pMT->IsInheritanceChainLayoutFixedInCurrentVersionBubble()); +#endif + // Add OBJECT_SIZE to account for method table pointer. - result = pMT->GetNumInstanceFieldBytes() + OBJECT_SIZE; + // + if (pMT->IsValueType()) + { + result = VMClsHnd.GetSize() + OBJECT_SIZE; + } + else + { + result = pMT->GetNumInstanceFieldBytes() + OBJECT_SIZE; + } EE_TO_JIT_TRANSITION_LEAF(); return result; @@ -1934,10 +1945,13 @@ bool CEEInfo::canAllocateOnStack(CORINFO_CLASS_HANDLE clsHnd) TypeHandle VMClsHnd(clsHnd); MethodTable* pMT = VMClsHnd.GetMethodTable(); _ASSERTE(pMT); - _ASSERTE(!pMT->IsValueType()); result = !pMT->HasFinalizer(); +#ifdef FEATURE_COMINTEROP + result &= !pMT->IsComObjectType(); +#endif // FEATURE_COMINTEROP + EE_TO_JIT_TRANSITION_LEAF(); return result; } @@ -6226,6 +6240,47 @@ CORINFO_CLASS_HANDLE CEEInfo::getTypeForBox(CORINFO_CLASS_HANDLE cls) return static_cast(VMClsHnd.AsPtr()); } +/***********************************************************************/ +// Get a representation for a stack-allocated boxed value type +// +CORINFO_CLASS_HANDLE CEEInfo::getTypeForBoxOnStack(CORINFO_CLASS_HANDLE cls) +{ + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } CONTRACTL_END; + + CORINFO_CLASS_HANDLE result = NULL; + + JIT_TO_EE_TRANSITION(); + + TypeHandle VMClsHnd(cls); + if (Nullable::IsNullableType(VMClsHnd)) + { + VMClsHnd = VMClsHnd.AsMethodTable()->GetInstantiation()[0]; + } + +#ifdef FEATURE_64BIT_ALIGNMENT + if (VMClsHnd.RequiresAlign8()) + { + // TODO: Maybe handle 32 bit platforms with 8 byte alignments + result = NULL; + } + else +#endif + { + Instantiation boxedFieldsInst(&VMClsHnd, 1); + TypeHandle stackAllocatedBox = CoreLibBinder::GetClass(CLASS__STACKALLOCATEDBOX); + TypeHandle stackAllocatedBoxInst = stackAllocatedBox.Instantiate(boxedFieldsInst); + result = static_cast(stackAllocatedBoxInst.AsPtr()); + } + + EE_TO_JIT_TRANSITION(); + + return result; +} + /***********************************************************************/ // see code:Nullable#NullableVerification CorInfoHelpFunc CEEInfo::getBoxHelper(CORINFO_CLASS_HANDLE clsHnd) diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index f34786e2e994f..68617d39305bd 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -243,6 +243,7 @@ extern "C" FCDECL2(VOID, JIT_WriteBarrierEnsureNonHeapTarget, Object **dst, Obje extern "C" FCDECL2(Object*, ChkCastAny_NoCacheLookup, CORINFO_CLASS_HANDLE type, Object* obj); extern "C" FCDECL2(Object*, IsInstanceOfAny_NoCacheLookup, CORINFO_CLASS_HANDLE type, Object* obj); extern "C" FCDECL2(LPVOID, Unbox_Helper, CORINFO_CLASS_HANDLE type, Object* obj); +extern "C" FCDECL2(void, JIT_Unbox_TypeTest, CORINFO_CLASS_HANDLE type, CORINFO_CLASS_HANDLE boxType); extern "C" FCDECL3(void, JIT_Unbox_Nullable, void * destPtr, CORINFO_CLASS_HANDLE type, Object* obj); // ARM64 JIT_WriteBarrier uses speciall ABI and thus is not callable directly diff --git a/src/tests/JIT/opt/ObjectStackAllocation/ObjectStackAllocationTests.cs b/src/tests/JIT/opt/ObjectStackAllocation/ObjectStackAllocationTests.cs index 9faf9b4cce4f0..5a45f43dcc7ca 100644 --- a/src/tests/JIT/opt/ObjectStackAllocation/ObjectStackAllocationTests.cs +++ b/src/tests/JIT/opt/ObjectStackAllocation/ObjectStackAllocationTests.cs @@ -152,6 +152,9 @@ public static int TestEntryPoint() CallTestAndVerifyAllocation(AllocateClassWithGcFieldAndInt, 5, expectedAllocationKind); + // Stack allocation of boxed structs is now enabled + CallTestAndVerifyAllocation(BoxSimpleStructAndAddFields, 12, expectedAllocationKind); + // The remaining tests currently never allocate on the stack if (expectedAllocationKind == AllocationKind.Stack) { expectedAllocationKind = AllocationKind.Heap; @@ -163,9 +166,6 @@ public static int TestEntryPoint() // This test calls CORINFO_HELP_CHKCASTCLASS_SPECIAL CallTestAndVerifyAllocation(AllocateSimpleClassAndCast, 7, expectedAllocationKind); - // Stack allocation of boxed structs is currently disabled - CallTestAndVerifyAllocation(BoxSimpleStructAndAddFields, 12, expectedAllocationKind); - return methodResult; }