From ea09fe269774115ad1afed2867e2fa487c5ca286 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 11 Jun 2024 12:00:30 +0800 Subject: [PATCH 01/14] Revert "Push back changes around IsSimpleCopy and CanAssignArrayType" This reverts commit 4ce4f5118c20f438c1e33a5a73d626f1180c86d5. --- .../src/System/Array.CoreCLR.cs | 122 ++++++++++--- .../RuntimeHelpers.CoreCLR.cs | 40 +++++ .../classlibnative/bcltype/arraynative.cpp | 160 ------------------ .../classlibnative/bcltype/arraynative.h | 4 - src/coreclr/vm/comutilnative.cpp | 15 ++ src/coreclr/vm/comutilnative.h | 1 + src/coreclr/vm/ecalllist.h | 2 - src/coreclr/vm/qcallentrypoints.cpp | 2 +- 8 files changed, 155 insertions(+), 191 deletions(-) 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 2efcb649844afc..cdfa095e9a9be1 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs @@ -62,7 +62,10 @@ 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)); - if (sourceArray.GetType() == destinationArray.GetType() || IsSimpleCopy(sourceArray, destinationArray)) + ArrayAssignType assignType = ArrayAssignType.WrongType; + + if (sourceArray.GetType() == destinationArray.GetType() + || (assignType = CanAssignArrayType(sourceArray, destinationArray)) == ArrayAssignType.SimpleCopy) { MethodTable* pMT = RuntimeHelpers.GetMethodTable(sourceArray); @@ -86,44 +89,57 @@ private static unsafe void CopyImpl(Array sourceArray, int sourceIndex, Array de throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_ConstrainedCopy); // Rare - CopySlow(sourceArray, sourceIndex, destinationArray, destinationIndex, length); + CopySlow(sourceArray, sourceIndex, destinationArray, destinationIndex, length, assignType); } - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool IsSimpleCopy(Array sourceArray, Array destinationArray); + 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; + } + } // 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) + private static unsafe void CopySlow(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, ArrayAssignType assignType) { Debug.Assert(sourceArray.Rank == destinationArray.Rank); - void* srcTH = RuntimeHelpers.GetMethodTable(sourceArray)->ElementType; - void* destTH = RuntimeHelpers.GetMethodTable(destinationArray)->ElementType; - AssignArrayEnum r = CanAssignArrayType(srcTH, destTH); - - if (r == AssignArrayEnum.AssignWrongType) + if (assignType == ArrayAssignType.WrongType) throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_CantAssignType); if (length > 0) { - switch (r) + switch (assignType) { - case AssignArrayEnum.AssignUnboxValueClass: + case ArrayAssignType.UnboxValueClass: CopyImplUnBoxEachElement(sourceArray, sourceIndex, destinationArray, destinationIndex, length); break; - case AssignArrayEnum.AssignBoxValueClassOrPrimitive: + case ArrayAssignType.BoxValueClassOrPrimitive: CopyImplBoxEachElement(sourceArray, sourceIndex, destinationArray, destinationIndex, length); break; - case AssignArrayEnum.AssignMustCast: + case ArrayAssignType.MustCast: CopyImplCastCheckEachElement(sourceArray, sourceIndex, destinationArray, destinationIndex, length); break; - case AssignArrayEnum.AssignPrimitiveWiden: + case ArrayAssignType.PrimitiveWiden: CopyImplPrimitiveWiden(sourceArray, sourceIndex, destinationArray, destinationIndex, length); break; @@ -134,18 +150,76 @@ private static unsafe void CopySlow(Array sourceArray, int sourceIndex, Array de } } - // Must match the definition in arraynative.cpp - private enum AssignArrayEnum + private enum ArrayAssignType { - AssignWrongType, - AssignMustCast, - AssignBoxValueClassOrPrimitive, - AssignUnboxValueClass, - AssignPrimitiveWiden, + SimpleCopy, + WrongType, + MustCast, + BoxValueClassOrPrimitive, + UnboxValueClass, + PrimitiveWiden, } - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Array_CanAssignArrayType")] - private static unsafe partial AssignArrayEnum CanAssignArrayType(void* srcTH, void* dstTH); + 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; + } // 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 052f6970a298e1..714ee38d9b7bf5 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 @@ -865,6 +865,46 @@ public static TypeHandle TypeHandleOf() { return new TypeHandle((void*)RuntimeTypeHandle.ToIntPtr(typeof(T).TypeHandle)); } + + 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; + } + + internal unsafe struct TypeDesc + { + private readonly int m_typeAndFlags; + + // This is the ELEMENT_TYPE* that would be used in the type sig for this type + // For enums this is the underlying type + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CorElementType GetInternalCorElementType() => (CorElementType)(m_typeAndFlags & 0xFF); } // Helper structs used for tail calls via helper. diff --git a/src/coreclr/classlibnative/bcltype/arraynative.cpp b/src/coreclr/classlibnative/bcltype/arraynative.cpp index 7a42245f76888a..c5e95e7e824709 100644 --- a/src/coreclr/classlibnative/bcltype/arraynative.cpp +++ b/src/coreclr/classlibnative/bcltype/arraynative.cpp @@ -47,166 +47,6 @@ 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 // updated atomically. diff --git a/src/coreclr/classlibnative/bcltype/arraynative.h b/src/coreclr/classlibnative/bcltype/arraynative.h index 86b125c416bf0b..aa4430c9b21d8f 100644 --- a/src/coreclr/classlibnative/bcltype/arraynative.h +++ b/src/coreclr/classlibnative/bcltype/arraynative.h @@ -13,19 +13,15 @@ #ifndef _ARRAYNATIVE_H_ #define _ARRAYNATIVE_H_ -#include "fcall.h" #include "qcall.h" class ArrayNative { public: static FCDECL1(INT32, GetCorElementTypeOfElementType, ArrayBase* arrayUNSAFE); - - 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 73547a59e4df3c..ad0659559799a2 100644 --- a/src/coreclr/vm/comutilnative.cpp +++ b/src/coreclr/vm/comutilnative.cpp @@ -1846,6 +1846,21 @@ 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 0f00aca48466a1..48cf22dece227b 100644 --- a/src/coreclr/vm/comutilnative.h +++ b/src/coreclr/vm/comutilnative.h @@ -252,6 +252,7 @@ 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 d820c4d8e33b23..2df95bd3c83390 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -357,7 +357,6 @@ FCFuncEnd() FCFuncStart(gArrayFuncs) FCFuncElement("GetCorElementTypeOfElementType", ArrayNative::GetCorElementTypeOfElementType) - FCFuncElement("IsSimpleCopy", ArrayNative::IsSimpleCopy) FCFuncEnd() FCFuncStart(gBufferFuncs) @@ -528,7 +527,6 @@ 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 329989cde6db48..3ec00ffa06947c 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -103,6 +103,7 @@ 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) @@ -172,7 +173,6 @@ 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) From afd067bea6fccff3831037b92627c6e7a0de2e09 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 25 Jun 2024 21:10:46 +0800 Subject: [PATCH 02/14] Reduce use range of CorElementType --- .../src/System/Array.CoreCLR.cs | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) 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 cdfa095e9a9be1..05b65d72010234 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs @@ -99,17 +99,10 @@ private static CorElementType GetNormalizedIntegralArrayElementType(CorElementTy // 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; - } + + // U1/U2/U4/U8/U + int shift = (0b0001_0000_0000_0000_1010_1010_0000 >> (int)elementType) & 1; + return (CorElementType)((int)elementType - shift); } // Reliability-wise, this method will either possibly corrupt your @@ -188,12 +181,12 @@ private static unsafe ArrayAssignType CanAssignArrayType(Array sourceArray, Arra return ArrayAssignType.WrongType; } - CorElementType srcElType = srcTH.GetVerifierCorElementType(); - CorElementType destElType = destTH.GetVerifierCorElementType(); - // Copying primitives from one type to another - if (srcElType.IsPrimitiveType() && destElType.IsPrimitiveType()) + if (!srcTH.IsTypeDesc && srcTH.AsMethodTable()->IsPrimitive && !destTH.IsTypeDesc && destTH.AsMethodTable()->IsPrimitive) { + CorElementType srcElType = srcTH.AsMethodTable()->GetPrimitiveCorElementType(); + CorElementType destElType = destTH.AsMethodTable()->GetPrimitiveCorElementType(); + if (GetNormalizedIntegralArrayElementType(srcElType) == GetNormalizedIntegralArrayElementType(destElType)) return ArrayAssignType.SimpleCopy; else if (RuntimeHelpers.CanPrimitiveWiden(srcElType, destElType)) @@ -211,11 +204,11 @@ private static unsafe ArrayAssignType CanAssignArrayType(Array sourceArray, Arra return ArrayAssignType.MustCast; // class X extends/implements src and implements dest. - if (destTH.IsInterface && srcElType != CorElementType.ELEMENT_TYPE_VALUETYPE) + if (destTH.IsInterface) return ArrayAssignType.MustCast; // class X implements src and extends/implements dest - if (srcTH.IsInterface && srcElType != CorElementType.ELEMENT_TYPE_VALUETYPE) + if (srcTH.IsInterface) return ArrayAssignType.MustCast; return ArrayAssignType.WrongType; From 057c04a1393a2186e7b7126658889c33548bad72 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 25 Jun 2024 21:43:21 +0800 Subject: [PATCH 03/14] Rearrange TypeDesc and TypeHandle usage --- .../src/System/Array.CoreCLR.cs | 94 +++++++++++-------- .../RuntimeHelpers.CoreCLR.cs | 23 ----- 2 files changed, 54 insertions(+), 63 deletions(-) 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 05b65d72010234..96289a48aac096 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs @@ -161,55 +161,69 @@ private static unsafe ArrayAssignType CanAssignArrayType(Array sourceArray, Arra 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.IsTypeDesc && !destTH.IsTypeDesc) { - if (srcTH.CanCastTo(destTH)) - return ArrayAssignType.BoxValueClassOrPrimitive; - else - return ArrayAssignType.WrongType; - } + MethodTable* pMTsrc = srcTH.AsMethodTable(); + MethodTable* pMTdest = destTH.AsMethodTable(); - // 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; - } + // Value class boxing + if (pMTsrc->IsValueType && !pMTdest->IsValueType) + { + if (srcTH.CanCastTo(destTH)) + return ArrayAssignType.BoxValueClassOrPrimitive; + else + return ArrayAssignType.WrongType; + } - // Copying primitives from one type to another - if (!srcTH.IsTypeDesc && srcTH.AsMethodTable()->IsPrimitive && !destTH.IsTypeDesc && destTH.AsMethodTable()->IsPrimitive) - { - CorElementType srcElType = srcTH.AsMethodTable()->GetPrimitiveCorElementType(); - CorElementType destElType = destTH.AsMethodTable()->GetPrimitiveCorElementType(); + // Value class unboxing. + if (!pMTsrc->IsValueType && pMTdest->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; + } - if (GetNormalizedIntegralArrayElementType(srcElType) == GetNormalizedIntegralArrayElementType(destElType)) + // Copying primitives from one type to another + if (pMTsrc->IsPrimitive && pMTdest->IsPrimitive) + { + CorElementType srcElType = pMTsrc->GetPrimitiveCorElementType(); + CorElementType destElType = pMTdest->GetPrimitiveCorElementType(); + + 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; - 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; - // dest Object extends src - if (destTH.CanCastTo(srcTH)) - return ArrayAssignType.MustCast; + // class X extends/implements src and implements dest. + if (pMTdest->IsInterface) + return ArrayAssignType.MustCast; - // class X extends/implements src and implements dest. - if (destTH.IsInterface) - return ArrayAssignType.MustCast; + // class X implements src and extends/implements dest + if (pMTsrc->IsInterface) + return ArrayAssignType.MustCast; + } + else + { + // Only pointers are valid for TypeDesc in array element - // class X implements src and extends/implements dest - if (srcTH.IsInterface) - return ArrayAssignType.MustCast; + // Compatible pointers + if (srcTH.CanCastTo(destTH)) + return ArrayAssignType.SimpleCopy; + } return ArrayAssignType.WrongType; } 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 714ee38d9b7bf5..3e7037db80efc2 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 @@ -882,29 +882,6 @@ public bool CanCastTo(TypeHandle destTH) [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; - } - - internal unsafe struct TypeDesc - { - private readonly int m_typeAndFlags; - - // This is the ELEMENT_TYPE* that would be used in the type sig for this type - // For enums this is the underlying type - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public CorElementType GetInternalCorElementType() => (CorElementType)(m_typeAndFlags & 0xFF); } // Helper structs used for tail calls via helper. From 0770882ff2057222e590ca04edfb7cd4fa6febb6 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 25 Jun 2024 23:30:49 +0800 Subject: [PATCH 04/14] Fix --- src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs | 2 +- .../System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs | 2 +- src/coreclr/vm/ecalllist.h | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) 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 96289a48aac096..f05992c2352cf9 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs @@ -101,7 +101,7 @@ private static CorElementType GetNormalizedIntegralArrayElementType(CorElementTy // BOOL is NOT interchangeable with I1/U1, neither CHAR -- with I2/U2 // U1/U2/U4/U8/U - int shift = (0b0001_0000_0000_0000_1010_1010_0000 >> (int)elementType) & 1; + int shift = (0b0010_0000_0000_0000_1010_1010_0000 >> (int)elementType) & 1; return (CorElementType)((int)elementType - shift); } 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 3e7037db80efc2..f5f0b50416c4dd 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 @@ -818,7 +818,7 @@ public bool CanCompareBitsOrUseFastGetHashCode /// /// A type handle, which can wrap either a pointer to a TypeDesc or to a . /// - internal unsafe struct TypeHandle + internal readonly unsafe partial struct TypeHandle { // Subset of src\vm\typehandle.h diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index 2df95bd3c83390..bf7dbcc6dc1953 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -527,6 +527,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) From 0cd6e542dd5550ea2795f1b2519ac0a6ceacdf90 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 26 Jun 2024 00:59:07 +0800 Subject: [PATCH 05/14] Add test for pointer array --- .../System.Runtime.Tests/System/ArrayTests.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ArrayTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ArrayTests.cs index 832d890ab61c21..b465c945fd40ec 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ArrayTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ArrayTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -1666,6 +1667,31 @@ public static void ConstrainedCopy_UnreliableConversion_ThrowsArrayTypeMismatchE Assert.Throws(() => Array.ConstrainedCopy(sourceArray, sourceIndex, destinationArray, destinationIndex, length)); } + [Fact] + public static unsafe void Copy_PointerArray() + { + // Array of pointers is not supported by xUnit and test data properties with yield return + + // Can copy between compatible pointers + uint*[] uintPointerArray = new uint*[1]; + Array.ConstrainedCopy(new int*[1] { (int*)0x12345678 }, 0, uintPointerArray, 0, 1); + Assert.Equal((UIntPtr)0x12345678, (UIntPtr)uintPointerArray[0]); + + // Can't copy between pointer and object + Assert.Throws(() => Array.Copy(new int*[1] { (int*)0x12345678 }, new object[1], 1)); + Assert.Throws(() => Array.Copy(new object[1], new int*[1], 1)); + + // Can't copy between pointer and interface + + // .NET Framework and previous versions incorrectly allow copying between arrays of pointer and interface. + // Null will be successfully copied. Copying non-null object throws InvalidCastException. + // Copying non-null pointer tries to read it as an object reference and crashs in CLR. + Assert.Throws(() => Array.Copy(new uint*[1], new IConvertible[1], 1)); + Assert.Throws(() => Array.Copy(new int*[1] { (int*)0x12345678 }, new IConvertible[1], 1)); + Assert.Throws(() => Array.Copy(new IConvertible[1], new int*[1], 1)); + Assert.Throws(() => Array.Copy(new IConvertible[1] { 0x12345678 }, new int*[1], 1)); + } + [Fact] public static void Copy_NegativeLength_ThrowsArgumentOutOfRangeException() { From dd93e95c76773cd4ed4bd61a8c61e8bb4625d8a4 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Fri, 28 Jun 2024 21:47:26 +0800 Subject: [PATCH 06/14] Rearrange test --- .../System.Runtime.Tests/System/ArrayTests.cs | 48 +++++++++---------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ArrayTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ArrayTests.cs index b465c945fd40ec..1ae8a59686214c 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ArrayTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ArrayTests.cs @@ -1598,13 +1598,36 @@ public static void Copy_SourceAndDestinationNeverConvertible_ThrowsArrayTypeMism Assert.Throws(() => sourceArray.CopyTo(destinationArray, (long)destinationArray.GetLowerBound(0))); } + [Fact] + [SkipOnMono("Mono does not support pointer compatibility in Array.Copy")] + public static unsafe void Copy_CompatiblePointers() + { + // Can copy between compatible pointers + uint*[] uintPointerArray = new uint*[1]; + Array.ConstrainedCopy(new int*[1] { (int*)0x12345678 }, 0, uintPointerArray, 0, 1); + Assert.Equal((UIntPtr)0x12345678, (UIntPtr)uintPointerArray[0]); + } + [Fact] public static void Copy_SourceAndDestinationPointers_ThrowsArrayTypeMismatchException() { unsafe { + // Can't copy between pointer and object Assert.Throws(() => Array.Copy(new void*[1], new object[1], 0)); Assert.Throws(() => Array.Copy(new object[1], new void*[1], 0)); + + // Can't copy between pointer and interface + + // .NET Framework and previous versions incorrectly allow copying between arrays of pointer and interface. + // Null will be successfully copied. Copying non-null object throws InvalidCastException. + // Copying non-null pointer tries to read it as an object reference and crashs in CLR. + Assert.Throws(() => Array.Copy(new int*[1], new IConvertible[1], 1)); + Assert.Throws(() => Array.Copy(new IConvertible[1], new int*[1], 1)); + + // Can't copy between incompatible pointer types + Assert.Throws(() => Array.Copy(new int*[1], new bool*[1], 0)); + Assert.Throws(() => Array.Copy(new int*[1], new void*[1], 0)); } } @@ -1667,31 +1690,6 @@ public static void ConstrainedCopy_UnreliableConversion_ThrowsArrayTypeMismatchE Assert.Throws(() => Array.ConstrainedCopy(sourceArray, sourceIndex, destinationArray, destinationIndex, length)); } - [Fact] - public static unsafe void Copy_PointerArray() - { - // Array of pointers is not supported by xUnit and test data properties with yield return - - // Can copy between compatible pointers - uint*[] uintPointerArray = new uint*[1]; - Array.ConstrainedCopy(new int*[1] { (int*)0x12345678 }, 0, uintPointerArray, 0, 1); - Assert.Equal((UIntPtr)0x12345678, (UIntPtr)uintPointerArray[0]); - - // Can't copy between pointer and object - Assert.Throws(() => Array.Copy(new int*[1] { (int*)0x12345678 }, new object[1], 1)); - Assert.Throws(() => Array.Copy(new object[1], new int*[1], 1)); - - // Can't copy between pointer and interface - - // .NET Framework and previous versions incorrectly allow copying between arrays of pointer and interface. - // Null will be successfully copied. Copying non-null object throws InvalidCastException. - // Copying non-null pointer tries to read it as an object reference and crashs in CLR. - Assert.Throws(() => Array.Copy(new uint*[1], new IConvertible[1], 1)); - Assert.Throws(() => Array.Copy(new int*[1] { (int*)0x12345678 }, new IConvertible[1], 1)); - Assert.Throws(() => Array.Copy(new IConvertible[1], new int*[1], 1)); - Assert.Throws(() => Array.Copy(new IConvertible[1] { 0x12345678 }, new int*[1], 1)); - } - [Fact] public static void Copy_NegativeLength_ThrowsArgumentOutOfRangeException() { From 423c94b27c833cdccc41b2b32baf862ac85ff075 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 29 Jun 2024 15:21:09 +0800 Subject: [PATCH 07/14] Remove comment in test --- .../tests/System.Runtime.Tests/System/ArrayTests.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ArrayTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ArrayTests.cs index 1ae8a59686214c..2e7568f13f8419 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ArrayTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ArrayTests.cs @@ -1618,10 +1618,6 @@ public static void Copy_SourceAndDestinationPointers_ThrowsArrayTypeMismatchExce Assert.Throws(() => Array.Copy(new object[1], new void*[1], 0)); // Can't copy between pointer and interface - - // .NET Framework and previous versions incorrectly allow copying between arrays of pointer and interface. - // Null will be successfully copied. Copying non-null object throws InvalidCastException. - // Copying non-null pointer tries to read it as an object reference and crashs in CLR. Assert.Throws(() => Array.Copy(new int*[1], new IConvertible[1], 1)); Assert.Throws(() => Array.Copy(new IConvertible[1], new int*[1], 1)); From 0ef53704d18d7ac8e4e4f16e705b0377e11ce5ee Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 29 Jun 2024 15:21:43 +0800 Subject: [PATCH 08/14] NativeAot compat --- .../System.Private.CoreLib/src/System/Array.NativeAot.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Array.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Array.NativeAot.cs index 0c9c571ed0f7e8..334ae5bcc64b9d 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Array.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Array.NativeAot.cs @@ -239,9 +239,10 @@ private static unsafe void CopyImpl(Array sourceArray, int sourceIndex, Array de { // CLR compat note: CLR only allows Array.Copy between pointee types that would be assignable // to using array covariance rules (so int*[] can be copied to uint*[], but not to float*[]). - // This is rather weird since e.g. we don't allow casting int*[] to uint*[] otherwise. - // Instead of trying to replicate the behavior, we're choosing to be simply more permissive here. - CopyImplValueTypeArrayNoInnerGcRefs(sourceArray, sourceIndex, destinationArray, destinationIndex, length); + if (RuntimeImports.AreTypesAssignable(sourceArray.GetMethodTable()->RelatedParameterType, destinationArray.GetMethodTable()->RelatedParameterType)) + { + CopyImplValueTypeArrayNoInnerGcRefs(sourceArray, sourceIndex, destinationArray, destinationIndex, length); + } } else if (IsSourceElementABaseClassOrInterfaceOfDestinationValueType(sourceElementEEType, destinationElementEEType)) { From 4b6d8300f26fd5c9857d3a5d3227a09e3beea67c Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 29 Jun 2024 15:37:17 +0800 Subject: [PATCH 09/14] Mono compat --- .../tests/System.Runtime.Tests/System/ArrayTests.cs | 1 - src/mono/System.Private.CoreLib/src/System/Array.Mono.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ArrayTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ArrayTests.cs index 2e7568f13f8419..ccaba707130386 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ArrayTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ArrayTests.cs @@ -1599,7 +1599,6 @@ public static void Copy_SourceAndDestinationNeverConvertible_ThrowsArrayTypeMism } [Fact] - [SkipOnMono("Mono does not support pointer compatibility in Array.Copy")] public static unsafe void Copy_CompatiblePointers() { // Can copy between compatible pointers diff --git a/src/mono/System.Private.CoreLib/src/System/Array.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Array.Mono.cs index 7ca268d6cc050b..cded34be47b1f8 100644 --- a/src/mono/System.Private.CoreLib/src/System/Array.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Array.Mono.cs @@ -259,7 +259,7 @@ private static bool CanAssignArrayElement(Type source, Type target) } else if (source.IsPointer && target.IsPointer) { - return true; + return target.IsAssignableFrom(source); } else if (source.IsPrimitive && target.IsPrimitive) { From f8b8355664fddbdb2d0c7ee5813c72649b1eea9e Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 30 Jun 2024 00:07:18 +0800 Subject: [PATCH 10/14] Disable test on mono --- .../tests/System.Runtime.Tests/System/ArrayTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ArrayTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ArrayTests.cs index ccaba707130386..998094377be4f4 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ArrayTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ArrayTests.cs @@ -1599,6 +1599,7 @@ public static void Copy_SourceAndDestinationNeverConvertible_ThrowsArrayTypeMism } [Fact] + [SkipOnMono("https://github.com/dotnet/runtime/issues/104197")] public static unsafe void Copy_CompatiblePointers() { // Can copy between compatible pointers From 5f8783b1f835bdb928d69b9ad98dbf872559b94c Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 30 Jun 2024 00:28:37 +0800 Subject: [PATCH 11/14] Try fix nativeaot --- .../System.Private.CoreLib/src/System/Array.NativeAot.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Array.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Array.NativeAot.cs index 334ae5bcc64b9d..58378b92a9c5f0 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Array.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Array.NativeAot.cs @@ -239,10 +239,14 @@ private static unsafe void CopyImpl(Array sourceArray, int sourceIndex, Array de { // CLR compat note: CLR only allows Array.Copy between pointee types that would be assignable // to using array covariance rules (so int*[] can be copied to uint*[], but not to float*[]). - if (RuntimeImports.AreTypesAssignable(sourceArray.GetMethodTable()->RelatedParameterType, destinationArray.GetMethodTable()->RelatedParameterType)) + if (RuntimeImports.AreTypesAssignable(sourceElementEEType, destinationElementEEType)) { CopyImplValueTypeArrayNoInnerGcRefs(sourceArray, sourceIndex, destinationArray, destinationIndex, length); } + else + { + throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_CantAssignType); + } } else if (IsSourceElementABaseClassOrInterfaceOfDestinationValueType(sourceElementEEType, destinationElementEEType)) { From f95296f60cf55dafa40cda3f33cf69912f9c1631 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 6 Jul 2024 23:43:29 +0800 Subject: [PATCH 12/14] Don't lookup cast cache in QCall --- .../RuntimeHelpers.CoreCLR.cs | 6 ++++++ src/coreclr/vm/comutilnative.cpp | 20 ++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) 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 f5f0b50416c4dd..efe4dca3d304b8 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 @@ -871,6 +871,12 @@ public static TypeHandle TypeHandleOf() [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool CanCastTo(TypeHandle destTH) { + if (m_asTAddr == destTH.m_asTAddr) + return true; + + if (!IsTypeDesc && destTH.IsTypeDesc) + return false; + CastResult result = CastCache.TryGet(CastHelpers.s_table!, (nuint)m_asTAddr, (nuint)destTH.m_asTAddr); if (result != CastResult.MaybeCast) diff --git a/src/coreclr/vm/comutilnative.cpp b/src/coreclr/vm/comutilnative.cpp index ad0659559799a2..e0e9ce9ad5b12e 100644 --- a/src/coreclr/vm/comutilnative.cpp +++ b/src/coreclr/vm/comutilnative.cpp @@ -1854,7 +1854,25 @@ extern "C" BOOL QCALLTYPE TypeHandle_CanCastTo(void* fromTypeHnd, void* toTypeHn BEGIN_QCALL; - ret = TypeHandle::FromPtr(fromTypeHnd).CanCastTo(TypeHandle::FromPtr(toTypeHnd)); + // Cache lookup and trivial cases are already handled at managed side. Call the uncached versions directly. + _ASSERTE(fromTypeHnd != toTypeHnd); + + TypeHandle fromTH = TypeHandle::FromPtr(fromTypeHnd); + TypeHandle toTH = TypeHandle::FromPtr(toTypeHnd); + + if (fromTH.IsTypeDesc()) + { + ret = fromTH.AsTypeDesc()->CanCastTo(toTH, NULL); + } + else if (Nullable::IsNullableForType(toTH, fromTH.AsMethodTable())) + { + // do not allow type T to be cast to Nullable + ret = FALSE; + } + else + { + ret = fromTH.AsMethodTable()->CanCastTo(toTH.AsMethodTable(), NULL); + } END_QCALL; From d48f6f4ab32bbad2aa7e4ed91b5e84274a9c1908 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 7 Jul 2024 09:59:44 +0800 Subject: [PATCH 13/14] Fix GC mode --- src/coreclr/vm/comutilnative.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/coreclr/vm/comutilnative.cpp b/src/coreclr/vm/comutilnative.cpp index e0e9ce9ad5b12e..0f9d8425d7cc65 100644 --- a/src/coreclr/vm/comutilnative.cpp +++ b/src/coreclr/vm/comutilnative.cpp @@ -1857,6 +1857,8 @@ extern "C" BOOL QCALLTYPE TypeHandle_CanCastTo(void* fromTypeHnd, void* toTypeHn // Cache lookup and trivial cases are already handled at managed side. Call the uncached versions directly. _ASSERTE(fromTypeHnd != toTypeHnd); + GCX_COOP(); + TypeHandle fromTH = TypeHandle::FromPtr(fromTypeHnd); TypeHandle toTH = TypeHandle::FromPtr(toTypeHnd); From 4db934b7390ecec6d0c0c8a0d059913e2faea46c Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 7 Jul 2024 10:34:18 +0800 Subject: [PATCH 14/14] Update QCall name --- .../System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs | 2 +- src/coreclr/vm/comutilnative.cpp | 2 +- src/coreclr/vm/comutilnative.h | 2 +- src/coreclr/vm/qcallentrypoints.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) 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 efe4dca3d304b8..77e579ce4680bd 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 @@ -885,7 +885,7 @@ public bool CanCastTo(TypeHandle destTH) return CanCastTo_NoCacheLookup(m_asTAddr, destTH.m_asTAddr); } - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "TypeHandle_CanCastTo")] + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "TypeHandle_CanCastTo_NoCacheLookup")] [return: MarshalAs(UnmanagedType.Bool)] private static partial bool CanCastTo_NoCacheLookup(void* fromTypeHnd, void* toTypeHnd); } diff --git a/src/coreclr/vm/comutilnative.cpp b/src/coreclr/vm/comutilnative.cpp index 0f9d8425d7cc65..d30177f9dc7978 100644 --- a/src/coreclr/vm/comutilnative.cpp +++ b/src/coreclr/vm/comutilnative.cpp @@ -1846,7 +1846,7 @@ extern "C" BOOL QCALLTYPE MethodTable_AreTypesEquivalent(MethodTable* mta, Metho return bResult; } -extern "C" BOOL QCALLTYPE TypeHandle_CanCastTo(void* fromTypeHnd, void* toTypeHnd) +extern "C" BOOL QCALLTYPE TypeHandle_CanCastTo_NoCacheLookup(void* fromTypeHnd, void* toTypeHnd) { QCALL_CONTRACT; diff --git a/src/coreclr/vm/comutilnative.h b/src/coreclr/vm/comutilnative.h index 48cf22dece227b..4e70173758f887 100644 --- a/src/coreclr/vm/comutilnative.h +++ b/src/coreclr/vm/comutilnative.h @@ -252,7 +252,7 @@ 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" BOOL QCALLTYPE TypeHandle_CanCastTo_NoCacheLookup(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/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 3ec00ffa06947c..d4d073d9b3abf5 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -103,7 +103,7 @@ static const Entry s_QCall[] = DllImportEntry(QCall_FreeGCHandleForTypeHandle) DllImportEntry(MethodTable_AreTypesEquivalent) DllImportEntry(MethodTable_CanCompareBitsOrUseFastGetHashCode) - DllImportEntry(TypeHandle_CanCastTo) + DllImportEntry(TypeHandle_CanCastTo_NoCacheLookup) DllImportEntry(ValueType_GetHashCodeStrategy) DllImportEntry(RuntimeTypeHandle_MakePointer) DllImportEntry(RuntimeTypeHandle_MakeByRef)