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 d18caf9a39410a..16d9067567ee58 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs @@ -4,6 +4,7 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -95,8 +96,302 @@ private static unsafe void CopyImpl(Array sourceArray, int sourceIndex, Array de // 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. - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern 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) + { + 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) + ThrowHelper.ThrowArrayTypeMismatchException_CantAssignType(); + + if (length > 0) + { + switch (r) + { + case AssignArrayEnum.AssignUnboxValueClass: + CopyImplUnBoxEachElement(sourceArray, sourceIndex, destinationArray, destinationIndex, length); + break; + + case AssignArrayEnum.AssignBoxValueClassOrPrimitive: + CopyImplBoxEachElement(sourceArray, sourceIndex, destinationArray, destinationIndex, length); + break; + + case AssignArrayEnum.AssignMustCast: + CopyImplCastCheckEachElement(sourceArray, sourceIndex, destinationArray, destinationIndex, length); + break; + + case AssignArrayEnum.AssignPrimitiveWiden: + CopyImplPrimitiveWiden(sourceArray, sourceIndex, destinationArray, destinationIndex, length); + break; + + default: + Debug.Fail("Fell through switch in Array.Copy!"); + break; + } + } + } + + // Must match the definition in arraynative.cpp + private enum AssignArrayEnum + { + AssignWrongType, + AssignMustCast, + AssignBoxValueClassOrPrimitive, + AssignUnboxValueClass, + AssignPrimitiveWiden, + } + + [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) + { + MethodTable* pDestArrayMT = RuntimeHelpers.GetMethodTable(destinationArray); + TypeHandle destTH = pDestArrayMT->GetArrayElementTypeHandle(); + + Debug.Assert(!destTH.IsTypeDesc && destTH.AsMethodTable()->IsValueType); + Debug.Assert(!RuntimeHelpers.GetMethodTable(sourceArray)->GetArrayElementTypeHandle().AsMethodTable()->IsValueType); + + MethodTable* pDestMT = destTH.AsMethodTable(); + nuint destSize = pDestArrayMT->ComponentSize; + ref object? srcData = ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(sourceArray)), sourceIndex); + ref byte data = ref Unsafe.AddByteOffset(ref MemoryMarshal.GetArrayDataReference(destinationArray), (nuint)destinationIndex * destSize); + + for (int i = 0; i < length; i++) + { + object? obj = Unsafe.Add(ref srcData, i); + + // Now that we have retrieved the element, we are no longer subject to race + // conditions from another array mutator. + + ref byte dest = ref Unsafe.AddByteOffset(ref data, (nuint)i * destSize); + + if (pDestMT->IsNullable) + { + RuntimeHelpers.Unbox_Nullable(ref dest, pDestMT, obj); + } + else if (obj is null || RuntimeHelpers.GetMethodTable(obj) != pDestMT) + { + ThrowHelper.ThrowInvalidCastException_DownCastArrayElement(); + } + else if (pDestMT->ContainsGCPointers) + { + Buffer.BulkMoveWithWriteBarrier(ref dest, ref obj.GetRawData(), destSize); + } + else + { + Buffer.Memmove(ref dest, ref obj.GetRawData(), destSize); + } + } + } + + // Will box each element in an array of value classes or primitives into an array of Objects. + private static unsafe void CopyImplBoxEachElement(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length) + { + MethodTable* pSrcArrayMT = RuntimeHelpers.GetMethodTable(sourceArray); + TypeHandle srcTH = pSrcArrayMT->GetArrayElementTypeHandle(); + + Debug.Assert(!srcTH.IsTypeDesc && srcTH.AsMethodTable()->IsValueType); + Debug.Assert(!RuntimeHelpers.GetMethodTable(destinationArray)->GetArrayElementTypeHandle().AsMethodTable()->IsValueType); + + MethodTable* pSrcMT = srcTH.AsMethodTable(); + + nuint srcSize = pSrcArrayMT->ComponentSize; + ref byte data = ref Unsafe.AddByteOffset(ref MemoryMarshal.GetArrayDataReference(sourceArray), (nuint)sourceIndex * srcSize); + ref object? destData = ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(destinationArray)), destinationIndex); + + for (int i = 0; i < length; i++) + { + object? obj = RuntimeHelpers.Box(pSrcMT, ref Unsafe.AddByteOffset(ref data, (nuint)i * srcSize)); + Unsafe.Add(ref destData, i) = obj; + } + } + + // Casts and assigns each element of src array to the dest array type. + private static unsafe void CopyImplCastCheckEachElement(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length) + { + void* destTH = RuntimeHelpers.GetMethodTable(destinationArray)->ElementType; + + ref object? srcData = ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(sourceArray)), sourceIndex); + ref object? destData = ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(destinationArray)), destinationIndex); + + for (int i = 0; i < length; i++) + { + object? obj = Unsafe.Add(ref srcData, i); + + // Now that we have grabbed obj, we are no longer subject to races from another + // mutator thread. + + Unsafe.Add(ref destData, i) = CastHelpers.ChkCastAny(destTH, obj); + } + } + + // Widen primitive types to another primitive type. + private static unsafe void CopyImplPrimitiveWiden(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length) + { + // Get appropriate sizes, which requires method tables. + + CorElementType srcElType = sourceArray.GetCorElementTypeOfElementType(); + CorElementType destElType = destinationArray.GetCorElementTypeOfElementType(); + + nuint srcElSize = RuntimeHelpers.GetMethodTable(sourceArray)->ComponentSize; + nuint destElSize = RuntimeHelpers.GetMethodTable(destinationArray)->ComponentSize; + + ref byte srcData = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(sourceArray), (nuint)sourceIndex * srcElSize); + ref byte data = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(destinationArray), (nuint)destinationIndex * destElSize); + + for (int i = 0; i < length; i++) + { + ref byte srcElement = ref Unsafe.Add(ref srcData, (nuint)i * srcElSize); + ref byte destElement = ref Unsafe.Add(ref data, (nuint)i * destElSize); + + switch (srcElType) + { + case CorElementType.ELEMENT_TYPE_U1: + switch (destElType) + { + case CorElementType.ELEMENT_TYPE_CHAR: + case CorElementType.ELEMENT_TYPE_I2: + case CorElementType.ELEMENT_TYPE_U2: + Unsafe.As(ref destElement) = srcElement; break; + case CorElementType.ELEMENT_TYPE_I4: + case CorElementType.ELEMENT_TYPE_U4: + Unsafe.As(ref destElement) = srcElement; break; + case CorElementType.ELEMENT_TYPE_I8: + case CorElementType.ELEMENT_TYPE_U8: + Unsafe.As(ref destElement) = srcElement; break; + case CorElementType.ELEMENT_TYPE_R4: + Unsafe.As(ref destElement) = srcElement; break; + case CorElementType.ELEMENT_TYPE_R8: + Unsafe.As(ref destElement) = srcElement; break; + default: + Debug.Fail("Array.Copy from U1 to another type hit unsupported widening conversion"); break; + } + break; + + case CorElementType.ELEMENT_TYPE_I1: + switch (destElType) + { + case CorElementType.ELEMENT_TYPE_I2: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_I4: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_I8: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_R4: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_R8: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + default: + Debug.Fail("Array.Copy from I1 to another type hit unsupported widening conversion"); break; + } + break; + + case CorElementType.ELEMENT_TYPE_U2: + case CorElementType.ELEMENT_TYPE_CHAR: + switch (destElType) + { + case CorElementType.ELEMENT_TYPE_U2: + case CorElementType.ELEMENT_TYPE_CHAR: + // U2 and CHAR are identical in conversion + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_I4: + case CorElementType.ELEMENT_TYPE_U4: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_I8: + case CorElementType.ELEMENT_TYPE_U8: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_R4: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_R8: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + default: + Debug.Fail("Array.Copy from U2 to another type hit unsupported widening conversion"); break; + } + break; + + case CorElementType.ELEMENT_TYPE_I2: + switch (destElType) + { + case CorElementType.ELEMENT_TYPE_I4: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_I8: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_R4: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_R8: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + default: + Debug.Fail("Array.Copy from I2 to another type hit unsupported widening conversion"); break; + } + break; + + case CorElementType.ELEMENT_TYPE_U4: + switch (destElType) + { + case CorElementType.ELEMENT_TYPE_I8: + case CorElementType.ELEMENT_TYPE_U8: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_R4: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_R8: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + default: + Debug.Fail("Array.Copy from U4 to another type hit unsupported widening conversion"); break; + } + break; + + case CorElementType.ELEMENT_TYPE_I4: + switch (destElType) + { + case CorElementType.ELEMENT_TYPE_I8: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_R4: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_R8: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + default: + Debug.Fail("Array.Copy from I4 to another type hit unsupported widening conversion"); break; + } + break; + + case CorElementType.ELEMENT_TYPE_U8: + switch (destElType) + { + case CorElementType.ELEMENT_TYPE_R4: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_R8: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + default: + Debug.Fail("Array.Copy from U8 to another type hit unsupported widening conversion"); break; + } + break; + + case CorElementType.ELEMENT_TYPE_I8: + switch (destElType) + { + case CorElementType.ELEMENT_TYPE_R4: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + case CorElementType.ELEMENT_TYPE_R8: + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + default: + Debug.Fail("Array.Copy from I8 to another type hit unsupported widening conversion"); break; + } + break; + + case CorElementType.ELEMENT_TYPE_R4: + Debug.Assert(destElType == CorElementType.ELEMENT_TYPE_R8); + Unsafe.As(ref destElement) = Unsafe.As(ref srcElement); break; + + default: + Debug.Fail("Fell through outer switch in PrimitiveWiden! Unknown primitive type for source array!"); break; + } + } + } // Provides a strong exception guarantee - either it succeeds, or // it throws an exception with no side effects. The arrays must be diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/CastHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/CastHelpers.cs index 053490540ffc27..5f6c7070958ac1 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/CastHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/CastHelpers.cs @@ -209,7 +209,7 @@ internal static unsafe class CastHelpers [DebuggerHidden] [StackTraceHidden] [DebuggerStepThrough] - private static object? ChkCastAny(void* toTypeHnd, object? obj) + internal static object? ChkCastAny(void* toTypeHnd, object? obj) { CastResult result; 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 112729d383ac1a..534a251630cdac 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 @@ -301,6 +301,9 @@ internal static unsafe bool ObjectHasComponentSize(object obj) [MethodImpl(MethodImplOptions.InternalCall)] internal static extern unsafe object? Box(MethodTable* methodTable, ref byte data); + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern unsafe void Unbox_Nullable(ref byte destination, MethodTable* toTypeHnd, object? obj); + // Given an object reference, returns its MethodTable*. // // WARNING: The caller has to ensure that MethodTable* does not get unloaded. The most robust way diff --git a/src/coreclr/classlibnative/bcltype/arraynative.cpp b/src/coreclr/classlibnative/bcltype/arraynative.cpp index 0c1afdcbfd8b7c..32bc52b96f1a1d 100644 --- a/src/coreclr/classlibnative/bcltype/arraynative.cpp +++ b/src/coreclr/classlibnative/bcltype/arraynative.cpp @@ -114,27 +114,28 @@ FCIMPL2(FC_BOOL_RET, ArrayNative::IsSimpleCopy, ArrayBase* pSrc, ArrayBase* pDst 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. -ArrayNative::AssignArrayEnum ArrayNative::CanAssignArrayType(const BASEARRAYREF pSrc, const BASEARRAYREF pDest) +static AssignArrayEnum CanAssignArrayType(const TypeHandle srcTH, const TypeHandle destTH) { CONTRACTL { THROWS; GC_TRIGGERS; - MODE_COOPERATIVE; - PRECONDITION(pSrc != NULL); - PRECONDITION(pDest != NULL); + PRECONDITION(!srcTH.IsNull()); + PRECONDITION(!destTH.IsNull()); } CONTRACTL_END; - // This first bit is a minor optimization: e.g. when copying byte[] to byte[] - // we do not need to call GetArrayElementTypeHandle(). - MethodTable *pSrcMT = pSrc->GetMethodTable(); - MethodTable *pDestMT = pDest->GetMethodTable(); - _ASSERTE(pSrcMT != pDestMT); // Handled by fast path - - TypeHandle srcTH = pSrcMT->GetArrayElementTypeHandle(); - TypeHandle destTH = pDestMT->GetArrayElementTypeHandle(); _ASSERTE(srcTH != destTH); // Handled by fast path // Value class boxing @@ -190,435 +191,22 @@ ArrayNative::AssignArrayEnum ArrayNative::CanAssignArrayType(const BASEARRAYREF return AssignWrongType; } - -// Casts and assigns each element of src array to the dest array type. -void ArrayNative::CastCheckEachElement(const BASEARRAYREF pSrcUnsafe, const unsigned int srcIndex, BASEARRAYREF pDestUnsafe, unsigned int destIndex, const unsigned int len) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - PRECONDITION(pSrcUnsafe != NULL); - PRECONDITION(srcIndex >= 0); - PRECONDITION(pDestUnsafe != NULL); - PRECONDITION(len > 0); - } - CONTRACTL_END; - - // pSrc is either a PTRARRAYREF or a multidimensional array. - TypeHandle destTH = pDestUnsafe->GetArrayElementTypeHandle(); - - struct _gc - { - OBJECTREF obj; - BASEARRAYREF pDest; - BASEARRAYREF pSrc; - } gc; - - gc.obj = NULL; - gc.pDest = pDestUnsafe; - gc.pSrc = pSrcUnsafe; - - GCPROTECT_BEGIN(gc); - - for(unsigned int i=srcIndex; iGetDataPtr() + i)); - - // Now that we have grabbed obj, we are no longer subject to races from another - // mutator thread. - if (gc.obj != NULL && !ObjIsInstanceOf(OBJECTREFToObject(gc.obj), destTH)) - COMPlusThrow(kInvalidCastException, W("InvalidCast_DownCastArrayElement")); - - OBJECTREF * destData = (OBJECTREF*)(gc.pDest->GetDataPtr()) + i - srcIndex + destIndex; - SetObjectReference(destData, gc.obj); - } - - GCPROTECT_END(); - - return; -} - - -// Will box each element in an array of value classes or primitives into an array of Objects. -void ArrayNative::BoxEachElement(BASEARRAYREF pSrc, unsigned int srcIndex, BASEARRAYREF pDest, unsigned int destIndex, unsigned int length) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - PRECONDITION(pSrc != NULL); - PRECONDITION(srcIndex >= 0); - PRECONDITION(pDest != NULL); - PRECONDITION(length > 0); - } - CONTRACTL_END; - - // pDest is either a PTRARRAYREF or a multidimensional array. - _ASSERTE(pSrc!=NULL && srcIndex>=0 && pDest!=NULL && destIndex>=0 && length>=0); - TypeHandle srcTH = pSrc->GetArrayElementTypeHandle(); -#ifdef _DEBUG - TypeHandle destTH = pDest->GetArrayElementTypeHandle(); -#endif - _ASSERTE(srcTH.GetSignatureCorElementType() == ELEMENT_TYPE_CLASS || srcTH.GetSignatureCorElementType() == ELEMENT_TYPE_VALUETYPE || CorTypeInfo::IsPrimitiveType(pSrc->GetArrayElementType())); - _ASSERTE(!destTH.GetMethodTable()->IsValueType()); - - // Get method table of type we're copying from - we need to allocate objects of that type. - MethodTable * pSrcMT = srcTH.AsMethodTable(); - PREFIX_ASSUME(pSrcMT != NULL); - - if (!pSrcMT->IsClassInited()) - { - BASEARRAYREF pSrcTmp = pSrc; - BASEARRAYREF pDestTmp = pDest; - GCPROTECT_BEGIN (pSrcTmp); - GCPROTECT_BEGIN (pDestTmp); - pSrcMT->CheckRunClassInitThrowing(); - pSrc = pSrcTmp; - pDest = pDestTmp; - GCPROTECT_END (); - GCPROTECT_END (); - } - - const unsigned int srcSize = pSrcMT->GetNumInstanceFieldBytes(); - unsigned int srcArrayOffset = srcIndex * srcSize; - - struct _gc - { - BASEARRAYREF src; - BASEARRAYREF dest; - OBJECTREF obj; - } gc; - - gc.src = pSrc; - gc.dest = pDest; - gc.obj = NULL; - - void* srcPtr = 0; - GCPROTECT_BEGIN(gc); - GCPROTECT_BEGININTERIOR(srcPtr); - for (unsigned int i=destIndex; i < destIndex+length; i++, srcArrayOffset += srcSize) - { - srcPtr = (BYTE*)gc.src->GetDataPtr() + srcArrayOffset; - gc.obj = pSrcMT->FastBox(&srcPtr); - - OBJECTREF * destData = (OBJECTREF*)((gc.dest)->GetDataPtr()) + i; - SetObjectReference(destData, gc.obj); - } - GCPROTECT_END(); - GCPROTECT_END(); -} - - -// Unboxes from an Object[] into a value class or primitive array. -void ArrayNative::UnBoxEachElement(BASEARRAYREF pSrc, unsigned int srcIndex, BASEARRAYREF pDest, unsigned int destIndex, unsigned int length) +extern "C" int QCALLTYPE Array_CanAssignArrayType(void* srcTH, void* destTH) { - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - PRECONDITION(pSrc != NULL); - PRECONDITION(srcIndex >= 0); - PRECONDITION(pDest != NULL); - PRECONDITION(destIndex >= 0); - PRECONDITION(length > 0); - } - CONTRACTL_END; - -#ifdef _DEBUG - TypeHandle srcTH = pSrc->GetArrayElementTypeHandle(); -#endif - TypeHandle destTH = pDest->GetArrayElementTypeHandle(); - _ASSERTE(destTH.GetSignatureCorElementType() == ELEMENT_TYPE_CLASS || destTH.GetSignatureCorElementType() == ELEMENT_TYPE_VALUETYPE || CorTypeInfo::IsPrimitiveType(pDest->GetArrayElementType())); - _ASSERTE(!srcTH.GetMethodTable()->IsValueType()); - - MethodTable * pDestMT = destTH.AsMethodTable(); - PREFIX_ASSUME(pDestMT != NULL); + QCALL_CONTRACT; - SIZE_T destSize = pDest->GetComponentSize(); - BYTE* srcData = (BYTE*) pSrc->GetDataPtr() + srcIndex * sizeof(OBJECTREF); - BYTE* data = (BYTE*) pDest->GetDataPtr() + destIndex * destSize; + INT32 ret = 0; - for(; length>0; length--, srcData += sizeof(OBJECTREF), data += destSize) - { - OBJECTREF obj = ObjectToOBJECTREF(*(Object**)srcData); + BEGIN_QCALL; - // Now that we have retrieved the element, we are no longer subject to race - // conditions from another array mutator. + ret = CanAssignArrayType(TypeHandle::FromPtr(srcTH), TypeHandle::FromPtr(destTH)); - if (!pDestMT->UnBoxInto(data, obj)) - goto fail; - } - return; + END_QCALL; -fail: - COMPlusThrow(kInvalidCastException, W("InvalidCast_DownCastArrayElement")); + return ret; } -// Widen primitive types to another primitive type. -void ArrayNative::PrimitiveWiden(BASEARRAYREF pSrc, unsigned int srcIndex, BASEARRAYREF pDest, unsigned int destIndex, unsigned int length) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_COOPERATIVE; - PRECONDITION(pSrc != NULL); - PRECONDITION(srcIndex >= 0); - PRECONDITION(pDest != NULL); - PRECONDITION(destIndex >= 0); - PRECONDITION(length > 0); - } - CONTRACTL_END; - - // Get appropriate sizes, which requires method tables. - TypeHandle srcTH = pSrc->GetArrayElementTypeHandle(); - TypeHandle destTH = pDest->GetArrayElementTypeHandle(); - - const CorElementType srcElType = srcTH.GetVerifierCorElementType(); - const CorElementType destElType = destTH.GetVerifierCorElementType(); - const unsigned int srcSize = GetSizeForCorElementType(srcElType); - const unsigned int destSize = GetSizeForCorElementType(destElType); - - BYTE* srcData = (BYTE*) pSrc->GetDataPtr() + srcIndex * srcSize; - BYTE* data = (BYTE*) pDest->GetDataPtr() + destIndex * destSize; - - _ASSERTE(srcElType != destElType); // We shouldn't be here if these are the same type. - _ASSERTE(CorTypeInfo::IsPrimitiveType_NoThrow(srcElType) && CorTypeInfo::IsPrimitiveType_NoThrow(destElType)); - - for(; length>0; length--, srcData += srcSize, data += destSize) - { - // We pretty much have to do some fancy datatype mangling every time here, for - // converting w/ sign extension and floating point conversions. - switch (srcElType) - { - case ELEMENT_TYPE_U1: - switch (destElType) - { - case ELEMENT_TYPE_R4: - *(float*)data = *(UINT8*)srcData; - break; - - case ELEMENT_TYPE_R8: - *(double*)data = *(UINT8*)srcData; - break; -#ifndef BIGENDIAN - default: - *(UINT8*)data = *(UINT8*)srcData; - memset(data+1, 0, destSize - 1); - break; -#else // BIGENDIAN - case ELEMENT_TYPE_CHAR: - case ELEMENT_TYPE_I2: - case ELEMENT_TYPE_U2: - *(INT16*)data = *(UINT8*)srcData; - break; - - case ELEMENT_TYPE_I4: - case ELEMENT_TYPE_U4: - *(INT32*)data = *(UINT8*)srcData; - break; - - case ELEMENT_TYPE_I8: - case ELEMENT_TYPE_U8: - *(INT64*)data = *(UINT8*)srcData; - break; - - default: - _ASSERTE(!"Array.Copy from U1 to another type hit unsupported widening conversion"); -#endif // BIGENDIAN - } - break; - - - case ELEMENT_TYPE_I1: - switch (destElType) - { - case ELEMENT_TYPE_I2: - *(INT16*)data = *(INT8*)srcData; - break; - - case ELEMENT_TYPE_I4: - *(INT32*)data = *(INT8*)srcData; - break; - - case ELEMENT_TYPE_I8: - *(INT64*)data = *(INT8*)srcData; - break; - - case ELEMENT_TYPE_R4: - *(float*)data = *(INT8*)srcData; - break; - - case ELEMENT_TYPE_R8: - *(double*)data = *(INT8*)srcData; - break; - - default: - _ASSERTE(!"Array.Copy from I1 to another type hit unsupported widening conversion"); - } - break; - - - case ELEMENT_TYPE_U2: - case ELEMENT_TYPE_CHAR: - switch (destElType) - { - case ELEMENT_TYPE_R4: - *(float*)data = *(UINT16*)srcData; - break; - - case ELEMENT_TYPE_R8: - *(double*)data = *(UINT16*)srcData; - break; -#ifndef BIGENDIAN - default: - *(UINT16*)data = *(UINT16*)srcData; - memset(data+2, 0, destSize - 2); - break; -#else // BIGENDIAN - case ELEMENT_TYPE_U2: - case ELEMENT_TYPE_CHAR: - *(UINT16*)data = *(UINT16*)srcData; - break; - - case ELEMENT_TYPE_I4: - case ELEMENT_TYPE_U4: - *(UINT32*)data = *(UINT16*)srcData; - break; - - case ELEMENT_TYPE_I8: - case ELEMENT_TYPE_U8: - *(UINT64*)data = *(UINT16*)srcData; - break; - - default: - _ASSERTE(!"Array.Copy from U1 to another type hit unsupported widening conversion"); -#endif // BIGENDIAN - } - break; - - - case ELEMENT_TYPE_I2: - switch (destElType) - { - case ELEMENT_TYPE_I4: - *(INT32*)data = *(INT16*)srcData; - break; - - case ELEMENT_TYPE_I8: - *(INT64*)data = *(INT16*)srcData; - break; - - case ELEMENT_TYPE_R4: - *(float*)data = *(INT16*)srcData; - break; - - case ELEMENT_TYPE_R8: - *(double*)data = *(INT16*)srcData; - break; - - default: - _ASSERTE(!"Array.Copy from I2 to another type hit unsupported widening conversion"); - } - break; - - - case ELEMENT_TYPE_I4: - switch (destElType) - { - case ELEMENT_TYPE_I8: - *(INT64*)data = *(INT32*)srcData; - break; - - case ELEMENT_TYPE_R4: - *(float*)data = (float)*(INT32*)srcData; - break; - - case ELEMENT_TYPE_R8: - *(double*)data = *(INT32*)srcData; - break; - - default: - _ASSERTE(!"Array.Copy from I4 to another type hit unsupported widening conversion"); - } - break; - - - case ELEMENT_TYPE_U4: - switch (destElType) - { - case ELEMENT_TYPE_I8: - case ELEMENT_TYPE_U8: - *(INT64*)data = *(UINT32*)srcData; - break; - - case ELEMENT_TYPE_R4: - *(float*)data = (float)*(UINT32*)srcData; - break; - - case ELEMENT_TYPE_R8: - *(double*)data = *(UINT32*)srcData; - break; - - default: - _ASSERTE(!"Array.Copy from U4 to another type hit unsupported widening conversion"); - } - break; - - - case ELEMENT_TYPE_I8: - if (destElType == ELEMENT_TYPE_R4) - { - *(float*) data = (float) *(INT64*)srcData; - } - else - { - _ASSERTE(destElType==ELEMENT_TYPE_R8); - *(double*) data = (double) *(INT64*)srcData; - } - break; - - - case ELEMENT_TYPE_U8: - if (destElType == ELEMENT_TYPE_R4) - { - //*(float*) data = (float) *(UINT64*)srcData; - INT64 srcVal = *(INT64*)srcData; - float f = (float) srcVal; - if (srcVal < 0) - f += 4294967296.0f * 4294967296.0f; // This is 2^64 - - *(float*) data = f; - } - else - { - _ASSERTE(destElType==ELEMENT_TYPE_R8); - //*(double*) data = (double) *(UINT64*)srcData; - INT64 srcVal = *(INT64*)srcData; - double d = (double) srcVal; - if (srcVal < 0) - d += 4294967296.0 * 4294967296.0; // This is 2^64 - - *(double*) data = d; - } - break; - - - case ELEMENT_TYPE_R4: - *(double*) data = *(float*)srcData; - break; - - default: - _ASSERTE(!"Fell through outer switch in PrimitiveWiden! Unknown primitive type for source array!"); - } - } -} - // // 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. @@ -653,71 +241,6 @@ void memmoveGCRefs(void *dest, const void *src, size_t len) } } -FCIMPL5(void, ArrayNative::CopySlow, ArrayBase* pSrc, INT32 iSrcIndex, ArrayBase* pDst, INT32 iDstIndex, INT32 iLength) -{ - FCALL_CONTRACT; - - struct _gc - { - BASEARRAYREF pSrc; - BASEARRAYREF pDst; - } gc; - - gc.pSrc = (BASEARRAYREF)pSrc; - gc.pDst = (BASEARRAYREF)pDst; - - // cannot pass null for source or destination - _ASSERTE(gc.pSrc != NULL && gc.pDst != NULL); - - // source and destination must be arrays - _ASSERTE(gc.pSrc->GetMethodTable()->IsArray()); - _ASSERTE(gc.pDst->GetMethodTable()->IsArray()); - - _ASSERTE(gc.pSrc->GetRank() == gc.pDst->GetRank()); - - // array bounds checking - _ASSERTE(iLength >= 0); - _ASSERTE(iSrcIndex >= 0); - _ASSERTE(iDstIndex >= 0); - _ASSERTE((DWORD)(iSrcIndex + iLength) <= gc.pSrc->GetNumComponents()); - _ASSERTE((DWORD)(iDstIndex + iLength) <= gc.pDst->GetNumComponents()); - - HELPER_METHOD_FRAME_BEGIN_PROTECT(gc); - - int r = CanAssignArrayType(gc.pSrc, gc.pDst); - - if (r == AssignWrongType) - COMPlusThrow(kArrayTypeMismatchException, W("ArrayTypeMismatch_CantAssignType")); - - if (iLength > 0) - { - switch (r) - { - case AssignUnboxValueClass: - UnBoxEachElement(gc.pSrc, iSrcIndex, gc.pDst, iDstIndex, iLength); - break; - - case AssignBoxValueClassOrPrimitive: - BoxEachElement(gc.pSrc, iSrcIndex, gc.pDst, iDstIndex, iLength); - break; - - case AssignMustCast: - CastCheckEachElement(gc.pSrc, iSrcIndex, gc.pDst, iDstIndex, iLength); - break; - - case AssignPrimitiveWiden: - PrimitiveWiden(gc.pSrc, iSrcIndex, gc.pDst, iDstIndex, iLength); - break; - - default: - _ASSERTE(!"Fell through switch in Array.Copy!"); - } - } - - HELPER_METHOD_FRAME_END(); -} -FCIMPLEND - // Check we're allowed to create an array with the given element type. static void CheckElementType(TypeHandle elementType) diff --git a/src/coreclr/classlibnative/bcltype/arraynative.h b/src/coreclr/classlibnative/bcltype/arraynative.h index fae30384693688..aeb264a9b28ceb 100644 --- a/src/coreclr/classlibnative/bcltype/arraynative.h +++ b/src/coreclr/classlibnative/bcltype/arraynative.h @@ -28,7 +28,6 @@ class ArrayNative static FCDECL1(INT32, GetCorElementTypeOfElementType, ArrayBase* arrayUNSAFE); static FCDECL2(FC_BOOL_RET, IsSimpleCopy, ArrayBase* pSrc, ArrayBase* pDst); - static FCDECL5(void, CopySlow, ArrayBase* pSrc, INT32 iSrcIndex, ArrayBase* pDst, INT32 iDstIndex, INT32 iLength); // This set of methods will set a value in an array static FCDECL3(void, SetValue, ArrayBase* refThisUNSAFE, Object* objUNSAFE, INT_PTR flattenedIndex); @@ -40,28 +39,10 @@ class ArrayNative // This method will acquire data to create a span from a TypeHandle // to a field. static FCDECL3_VVI(void*, GetSpanDataFrom, FCALLRuntimeFieldHandle structField, FCALLRuntimeTypeHandle targetTypeUnsafe, INT32* count); - -private: - // Return values for CanAssignArrayType - enum AssignArrayEnum - { - AssignWrongType, - AssignMustCast, - AssignBoxValueClassOrPrimitive, - AssignUnboxValueClass, - AssignPrimitiveWiden, - }; - - // The following functions are all helpers for ArrayCopy - static AssignArrayEnum CanAssignArrayType(const BASEARRAYREF pSrc, const BASEARRAYREF pDest); - static void CastCheckEachElement(BASEARRAYREF pSrc, unsigned int srcIndex, BASEARRAYREF pDest, unsigned int destIndex, unsigned int length); - static void BoxEachElement(BASEARRAYREF pSrc, unsigned int srcIndex, BASEARRAYREF pDest, unsigned int destIndex, unsigned int length); - static void UnBoxEachElement(BASEARRAYREF pSrc, unsigned int srcIndex, BASEARRAYREF pDest, unsigned int destIndex, unsigned int length); - static void PrimitiveWiden(BASEARRAYREF pSrc, unsigned int srcIndex, BASEARRAYREF pDest, unsigned int destIndex, unsigned int length); - }; 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/ecalllist.h b/src/coreclr/vm/ecalllist.h index e6b1ae38cd77bc..cbaaf8456c8821 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -390,7 +390,6 @@ FCFuncEnd() FCFuncStart(gArrayFuncs) FCFuncElement("GetCorElementTypeOfElementType", ArrayNative::GetCorElementTypeOfElementType) FCFuncElement("IsSimpleCopy", ArrayNative::IsSimpleCopy) - FCFuncElement("CopySlow", ArrayNative::CopySlow) FCFuncElement("InternalSetValue", ArrayNative::SetValue) FCFuncEnd() @@ -482,6 +481,7 @@ FCFuncStart(gRuntimeHelpers) FCFuncElement("AllocTailCallArgBuffer", TailCallHelp::AllocTailCallArgBuffer) FCFuncElement("GetTailCallInfo", TailCallHelp::GetTailCallInfo) FCFuncElement("Box", JIT_Box) + FCFuncElement("Unbox_Nullable", JIT_Unbox_Nullable) FCFuncEnd() FCFuncStart(gMethodTableFuncs) diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index d1ae48e2df5cab..f8aa5ea3e7e162 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -241,6 +241,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" 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 // Copied write barriers must be called at a different location diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 3a7264543a6716..3e149e1a763a28 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -166,6 +166,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) diff --git a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs index 100222ef29deb0..5108a8b22fb9a7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs @@ -65,6 +65,18 @@ internal static void ThrowArrayTypeMismatchException() throw new ArrayTypeMismatchException(); } + [DoesNotReturn] + internal static void ThrowArrayTypeMismatchException_CantAssignType() + { + throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_CantAssignType); + } + + [DoesNotReturn] + internal static void ThrowInvalidCastException_DownCastArrayElement() + { + throw new InvalidCastException(SR.InvalidCast_DownCastArrayElement); + } + [DoesNotReturn] internal static void ThrowInvalidTypeWithPointersNotSupported(Type targetType) {