diff --git a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs index d72672e7238be..78b3f7dd0b731 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs @@ -62,10 +62,7 @@ private static unsafe void CopyImpl(Array sourceArray, int sourceIndex, Array de if ((uint)(destinationIndex + length) > destinationArray.NativeLength) throw new ArgumentException(SR.Arg_LongerThanDestArray, nameof(destinationArray)); - ArrayAssignType assignType = ArrayAssignType.WrongType; - - if (sourceArray.GetType() == destinationArray.GetType() - || (assignType = CanAssignArrayType(sourceArray, destinationArray)) == ArrayAssignType.SimpleCopy) + if (sourceArray.GetType() == destinationArray.GetType() || IsSimpleCopy(sourceArray, destinationArray)) { MethodTable* pMT = RuntimeHelpers.GetMethodTable(sourceArray); @@ -89,57 +86,44 @@ private static unsafe void CopyImpl(Array sourceArray, int sourceIndex, Array de throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_ConstrainedCopy); // Rare - CopySlow(sourceArray, sourceIndex, destinationArray, destinationIndex, length, assignType); + CopySlow(sourceArray, sourceIndex, destinationArray, destinationIndex, length); } - private static CorElementType GetNormalizedIntegralArrayElementType(CorElementType elementType) - { - Debug.Assert(elementType.IsPrimitiveType()); - - // Array Primitive types such as E_T_I4 and E_T_U4 are interchangeable - // Enums with interchangeable underlying types are interchangeable - // BOOL is NOT interchangeable with I1/U1, neither CHAR -- with I2/U2 - switch (elementType) - { - case CorElementType.ELEMENT_TYPE_U1: - case CorElementType.ELEMENT_TYPE_U2: - case CorElementType.ELEMENT_TYPE_U4: - case CorElementType.ELEMENT_TYPE_U8: - case CorElementType.ELEMENT_TYPE_U: - return elementType - 1; // normalize to signed type - default: - return elementType; - } - } + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern bool IsSimpleCopy(Array sourceArray, Array destinationArray); // Reliability-wise, this method will either possibly corrupt your // instance & might fail when called from within a CER, or if the // reliable flag is true, it will either always succeed or always // throw an exception with no side effects. - private static unsafe void CopySlow(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, ArrayAssignType assignType) + private static unsafe void CopySlow(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length) { Debug.Assert(sourceArray.Rank == destinationArray.Rank); - if (assignType == ArrayAssignType.WrongType) + void* srcTH = RuntimeHelpers.GetMethodTable(sourceArray)->ElementType; + void* destTH = RuntimeHelpers.GetMethodTable(destinationArray)->ElementType; + AssignArrayEnum r = CanAssignArrayType(srcTH, destTH); + + if (r == AssignArrayEnum.AssignWrongType) throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_CantAssignType); if (length > 0) { - switch (assignType) + switch (r) { - case ArrayAssignType.UnboxValueClass: + case AssignArrayEnum.AssignUnboxValueClass: CopyImplUnBoxEachElement(sourceArray, sourceIndex, destinationArray, destinationIndex, length); break; - case ArrayAssignType.BoxValueClassOrPrimitive: + case AssignArrayEnum.AssignBoxValueClassOrPrimitive: CopyImplBoxEachElement(sourceArray, sourceIndex, destinationArray, destinationIndex, length); break; - case ArrayAssignType.MustCast: + case AssignArrayEnum.AssignMustCast: CopyImplCastCheckEachElement(sourceArray, sourceIndex, destinationArray, destinationIndex, length); break; - case ArrayAssignType.PrimitiveWiden: + case AssignArrayEnum.AssignPrimitiveWiden: CopyImplPrimitiveWiden(sourceArray, sourceIndex, destinationArray, destinationIndex, length); break; @@ -150,76 +134,18 @@ private static unsafe void CopySlow(Array sourceArray, int sourceIndex, Array de } } - private enum ArrayAssignType + // Must match the definition in arraynative.cpp + private enum AssignArrayEnum { - SimpleCopy, - WrongType, - MustCast, - BoxValueClassOrPrimitive, - UnboxValueClass, - PrimitiveWiden, + AssignWrongType, + AssignMustCast, + AssignBoxValueClassOrPrimitive, + AssignUnboxValueClass, + AssignPrimitiveWiden, } - private static unsafe ArrayAssignType CanAssignArrayType(Array sourceArray, Array destinationArray) - { - TypeHandle srcTH = RuntimeHelpers.GetMethodTable(sourceArray)->GetArrayElementTypeHandle(); - TypeHandle destTH = RuntimeHelpers.GetMethodTable(destinationArray)->GetArrayElementTypeHandle(); - - if (TypeHandle.AreSameType(srcTH, destTH)) // This check kicks for different array kind or dimensions - return ArrayAssignType.SimpleCopy; - - // Value class boxing - if (srcTH.IsValueType && !destTH.IsValueType) - { - if (srcTH.CanCastTo(destTH)) - return ArrayAssignType.BoxValueClassOrPrimitive; - else - return ArrayAssignType.WrongType; - } - - // Value class unboxing. - if (!srcTH.IsValueType && destTH.IsValueType) - { - if (srcTH.CanCastTo(destTH)) - return ArrayAssignType.UnboxValueClass; - else if (destTH.CanCastTo(srcTH)) // V extends IV. Copying from IV to V, or Object to V. - return ArrayAssignType.UnboxValueClass; - else - return ArrayAssignType.WrongType; - } - - CorElementType srcElType = srcTH.GetVerifierCorElementType(); - CorElementType destElType = destTH.GetVerifierCorElementType(); - - // Copying primitives from one type to another - if (srcElType.IsPrimitiveType() && destElType.IsPrimitiveType()) - { - if (GetNormalizedIntegralArrayElementType(srcElType) == GetNormalizedIntegralArrayElementType(destElType)) - return ArrayAssignType.SimpleCopy; - else if (RuntimeHelpers.CanPrimitiveWiden(srcElType, destElType)) - return ArrayAssignType.PrimitiveWiden; - else - return ArrayAssignType.WrongType; - } - - // src Object extends dest - if (srcTH.CanCastTo(destTH)) - return ArrayAssignType.SimpleCopy; - - // dest Object extends src - if (destTH.CanCastTo(srcTH)) - return ArrayAssignType.MustCast; - - // class X extends/implements src and implements dest. - if (destTH.IsInterface && srcElType != CorElementType.ELEMENT_TYPE_VALUETYPE) - return ArrayAssignType.MustCast; - - // class X implements src and extends/implements dest - if (srcTH.IsInterface && srcElType != CorElementType.ELEMENT_TYPE_VALUETYPE) - return ArrayAssignType.MustCast; - - return ArrayAssignType.WrongType; - } + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Array_CanAssignArrayType")] + private static unsafe partial AssignArrayEnum CanAssignArrayType(void* srcTH, void* dstTH); // Unboxes from an Object[] into a value class or primitive array. private static unsafe void CopyImplUnBoxEachElement(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs index 461922f7f017b..95de0fcc1863b 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs @@ -862,34 +862,6 @@ public static TypeHandle TypeHandleOf() public static bool AreSameType(TypeHandle left, TypeHandle right) => left.m_asTAddr == right.m_asTAddr; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool CanCastTo(TypeHandle destTH) - { - CastResult result = CastCache.TryGet(CastHelpers.s_table!, (nuint)m_asTAddr, (nuint)destTH.m_asTAddr); - - if (result != CastResult.MaybeCast) - return result == CastResult.CanCast; - - return CanCastTo_NoCacheLookup(m_asTAddr, destTH.m_asTAddr); - } - - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "TypeHandle_CanCastTo")] - [return: MarshalAs(UnmanagedType.Bool)] - private static partial bool CanCastTo_NoCacheLookup(void* fromTypeHnd, void* toTypeHnd); - - public bool IsValueType - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return IsTypeDesc - ? AsTypeDesc()->GetInternalCorElementType() == CorElementType.ELEMENT_TYPE_VALUETYPE - : AsMethodTable()->IsValueType; - } - } - - public bool IsInterface => !IsTypeDesc && AsMethodTable()->IsInterface; - public CorElementType GetVerifierCorElementType() => IsTypeDesc ? AsTypeDesc()->GetInternalCorElementType() : AsMethodTable()->GetVerifierCorElementType(); diff --git a/src/coreclr/classlibnative/bcltype/arraynative.cpp b/src/coreclr/classlibnative/bcltype/arraynative.cpp index 246c539827607..2e8a41a827910 100644 --- a/src/coreclr/classlibnative/bcltype/arraynative.cpp +++ b/src/coreclr/classlibnative/bcltype/arraynative.cpp @@ -36,6 +36,165 @@ extern "C" PCODE QCALLTYPE Array_GetElementConstructorEntrypoint(QCall::TypeHand return ctorEntrypoint; } + // Returns whether you can directly copy an array of srcType into destType. +FCIMPL2(FC_BOOL_RET, ArrayNative::IsSimpleCopy, ArrayBase* pSrc, ArrayBase* pDst) +{ + FCALL_CONTRACT; + + _ASSERTE(pSrc != NULL); + _ASSERTE(pDst != NULL); + + // This case is expected to be handled by the fast path + _ASSERTE(pSrc->GetMethodTable() != pDst->GetMethodTable()); + + TypeHandle srcTH = pSrc->GetMethodTable()->GetArrayElementTypeHandle(); + TypeHandle destTH = pDst->GetMethodTable()->GetArrayElementTypeHandle(); + if (srcTH == destTH) // This check kicks for different array kind or dimensions + FC_RETURN_BOOL(true); + + if (srcTH.IsValueType()) + { + // Value class boxing + if (!destTH.IsValueType()) + FC_RETURN_BOOL(false); + + const CorElementType srcElType = srcTH.GetVerifierCorElementType(); + const CorElementType destElType = destTH.GetVerifierCorElementType(); + _ASSERTE(srcElType < ELEMENT_TYPE_MAX); + _ASSERTE(destElType < ELEMENT_TYPE_MAX); + + // Copying primitives from one type to another + if (CorTypeInfo::IsPrimitiveType_NoThrow(srcElType) && CorTypeInfo::IsPrimitiveType_NoThrow(destElType)) + { + if (GetNormalizedIntegralArrayElementType(srcElType) == GetNormalizedIntegralArrayElementType(destElType)) + FC_RETURN_BOOL(true); + } + } + else + { + // Value class unboxing + if (destTH.IsValueType()) + FC_RETURN_BOOL(false); + } + + TypeHandle::CastResult r = srcTH.CanCastToCached(destTH); + if (r != TypeHandle::MaybeCast) + { + FC_RETURN_BOOL(r); + } + + struct + { + OBJECTREF src; + OBJECTREF dst; + } gc; + + gc.src = ObjectToOBJECTREF(pSrc); + gc.dst = ObjectToOBJECTREF(pDst); + + BOOL iRetVal = FALSE; + + HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); + iRetVal = srcTH.CanCastTo(destTH); + HELPER_METHOD_FRAME_END(); + + FC_RETURN_BOOL(iRetVal); +} +FCIMPLEND + + +// Return values for CanAssignArrayType +enum AssignArrayEnum +{ + AssignWrongType, + AssignMustCast, + AssignBoxValueClassOrPrimitive, + AssignUnboxValueClass, + AssignPrimitiveWiden, +}; + +// Returns an enum saying whether you can copy an array of srcType into destType. +static AssignArrayEnum CanAssignArrayType(const TypeHandle srcTH, const TypeHandle destTH) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + PRECONDITION(!srcTH.IsNull()); + PRECONDITION(!destTH.IsNull()); + } + CONTRACTL_END; + + _ASSERTE(srcTH != destTH); // Handled by fast path + + // Value class boxing + if (srcTH.IsValueType() && !destTH.IsValueType()) + { + if (srcTH.CanCastTo(destTH)) + return AssignBoxValueClassOrPrimitive; + else + return AssignWrongType; + } + + // Value class unboxing. + if (!srcTH.IsValueType() && destTH.IsValueType()) + { + if (srcTH.CanCastTo(destTH)) + return AssignUnboxValueClass; + else if (destTH.CanCastTo(srcTH)) // V extends IV. Copying from IV to V, or Object to V. + return AssignUnboxValueClass; + else + return AssignWrongType; + } + + const CorElementType srcElType = srcTH.GetVerifierCorElementType(); + const CorElementType destElType = destTH.GetVerifierCorElementType(); + _ASSERTE(srcElType < ELEMENT_TYPE_MAX); + _ASSERTE(destElType < ELEMENT_TYPE_MAX); + + // Copying primitives from one type to another + if (CorTypeInfo::IsPrimitiveType_NoThrow(srcElType) && CorTypeInfo::IsPrimitiveType_NoThrow(destElType)) + { + _ASSERTE(srcElType != destElType); // Handled by fast path + if (InvokeUtil::CanPrimitiveWiden(destElType, srcElType)) + return AssignPrimitiveWiden; + else + return AssignWrongType; + } + + // dest Object extends src + _ASSERTE(!srcTH.CanCastTo(destTH)); // Handled by fast path + + // src Object extends dest + if (destTH.CanCastTo(srcTH)) + return AssignMustCast; + + // class X extends/implements src and implements dest. + if (destTH.IsInterface() && srcElType != ELEMENT_TYPE_VALUETYPE) + return AssignMustCast; + + // class X implements src and extends/implements dest + if (srcTH.IsInterface() && destElType != ELEMENT_TYPE_VALUETYPE) + return AssignMustCast; + + return AssignWrongType; +} + +extern "C" int QCALLTYPE Array_CanAssignArrayType(void* srcTH, void* destTH) +{ + QCALL_CONTRACT; + + INT32 ret = 0; + + BEGIN_QCALL; + + ret = CanAssignArrayType(TypeHandle::FromPtr(srcTH), TypeHandle::FromPtr(destTH)); + + END_QCALL; + + return ret; +} + // // This is a GC safe variant of the memmove intrinsic. It sets the cards, and guarantees that the object references in the GC heap are diff --git a/src/coreclr/classlibnative/bcltype/arraynative.h b/src/coreclr/classlibnative/bcltype/arraynative.h index 5dd3570ce5cbb..c7ce27eb5c91b 100644 --- a/src/coreclr/classlibnative/bcltype/arraynative.h +++ b/src/coreclr/classlibnative/bcltype/arraynative.h @@ -13,9 +13,17 @@ #ifndef _ARRAYNATIVE_H_ #define _ARRAYNATIVE_H_ +#include "fcall.h" #include "qcall.h" +class ArrayNative +{ +public: + static FCDECL2(FC_BOOL_RET, IsSimpleCopy, ArrayBase* pSrc, ArrayBase* pDst); +}; + extern "C" void QCALLTYPE Array_CreateInstance(QCall::TypeHandle pTypeHnd, INT32 rank, INT32* pLengths, INT32* pBounds, BOOL createFromArrayType, QCall::ObjectHandleOnStack retArray); extern "C" PCODE QCALLTYPE Array_GetElementConstructorEntrypoint(QCall::TypeHandle pArrayTypeHnd); +extern "C" INT32 QCALLTYPE Array_CanAssignArrayType(void* srcTH, void* destTH); #endif // _ARRAYNATIVE_H_ diff --git a/src/coreclr/vm/comutilnative.cpp b/src/coreclr/vm/comutilnative.cpp index 1823ffa2c07ac..b569143bbaa51 100644 --- a/src/coreclr/vm/comutilnative.cpp +++ b/src/coreclr/vm/comutilnative.cpp @@ -1809,21 +1809,6 @@ extern "C" BOOL QCALLTYPE MethodTable_AreTypesEquivalent(MethodTable* mta, Metho return bResult; } -extern "C" BOOL QCALLTYPE TypeHandle_CanCastTo(void* fromTypeHnd, void* toTypeHnd) -{ - QCALL_CONTRACT; - - BOOL ret = false; - - BEGIN_QCALL; - - ret = TypeHandle::FromPtr(fromTypeHnd).CanCastTo(TypeHandle::FromPtr(toTypeHnd)); - - END_QCALL; - - return ret; -} - static MethodTable * g_pStreamMT; static WORD g_slotBeginRead, g_slotEndRead; static WORD g_slotBeginWrite, g_slotEndWrite; diff --git a/src/coreclr/vm/comutilnative.h b/src/coreclr/vm/comutilnative.h index fb1038d7f3f5d..74f0c7967e745 100644 --- a/src/coreclr/vm/comutilnative.h +++ b/src/coreclr/vm/comutilnative.h @@ -249,7 +249,6 @@ class MethodTableNative { extern "C" BOOL QCALLTYPE MethodTable_AreTypesEquivalent(MethodTable* mta, MethodTable* mtb); extern "C" BOOL QCALLTYPE MethodTable_CanCompareBitsOrUseFastGetHashCode(MethodTable* mt); -extern "C" BOOL QCALLTYPE TypeHandle_CanCastTo(void* fromTypeHnd, void* toTypeHnd); extern "C" INT32 QCALLTYPE ValueType_GetHashCodeStrategy(MethodTable* mt, QCall::ObjectHandleOnStack objHandle, UINT32* fieldOffset, UINT32* fieldSize, MethodTable** fieldMT); class StreamNative { diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index 3ad62b22824ac..7b866bf9bd242 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -359,6 +359,10 @@ FCFuncStart(gCastHelpers) FCFuncElement("WriteBarrier", ::WriteBarrier_Helper) FCFuncEnd() +FCFuncStart(gArrayFuncs) + FCFuncElement("IsSimpleCopy", ArrayNative::IsSimpleCopy) +FCFuncEnd() + FCFuncStart(gBufferFuncs) FCFuncElement("__BulkMoveWithWriteBarrier", Buffer::BulkMoveWithWriteBarrier) FCFuncEnd() @@ -531,6 +535,7 @@ FCFuncEnd() // Note these have to remain sorted by name:namespace pair (Assert will wack you if you don't) // The sorting is case-sensitive +FCClassElement("Array", "System", gArrayFuncs) FCClassElement("AssemblyLoadContext", "System.Runtime.Loader", gAssemblyLoadContextFuncs) FCClassElement("Buffer", "System", gBufferFuncs) FCClassElement("CastHelpers", "System.Runtime.CompilerServices", gCastHelpers) diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 2ece683380ef5..7d28f0864d4cc 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -103,7 +103,6 @@ static const Entry s_QCall[] = DllImportEntry(QCall_FreeGCHandleForTypeHandle) DllImportEntry(MethodTable_AreTypesEquivalent) DllImportEntry(MethodTable_CanCompareBitsOrUseFastGetHashCode) - DllImportEntry(TypeHandle_CanCastTo) DllImportEntry(ValueType_GetHashCodeStrategy) DllImportEntry(RuntimeTypeHandle_MakePointer) DllImportEntry(RuntimeTypeHandle_MakeByRef) @@ -173,6 +172,7 @@ static const Entry s_QCall[] = DllImportEntry(MdUtf8String_EqualsCaseInsensitive) DllImportEntry(Array_CreateInstance) DllImportEntry(Array_GetElementConstructorEntrypoint) + DllImportEntry(Array_CanAssignArrayType) DllImportEntry(AssemblyName_InitializeAssemblySpec) DllImportEntry(AssemblyNative_GetFullName) DllImportEntry(AssemblyNative_GetLocation)