// Copies length elements from sourceArray, starting at index 0, to
// destinationArray, starting at index 0.
//
- public static void Copy(Array sourceArray, Array destinationArray, int length)
+ public static unsafe void Copy(Array sourceArray, Array destinationArray, int length)
{
if (sourceArray == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.sourceArray);
if (destinationArray == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.destinationArray);
- Copy(sourceArray, sourceArray.GetLowerBound(0), destinationArray, destinationArray.GetLowerBound(0), length, false);
+ MethodTable* pMT = RuntimeHelpers.GetMethodTable(sourceArray);
+ if (pMT == RuntimeHelpers.GetMethodTable(destinationArray) &&
+ !pMT->IsMultiDimensionalArray &&
+ (uint)length <= (uint)sourceArray.Length &&
+ (uint)length <= (uint)destinationArray.Length)
+ {
+ nuint byteCount = (uint)length * (nuint)pMT->ComponentSize;
+ ref byte src = ref sourceArray.GetRawSzArrayData();
+ ref byte dst = ref destinationArray.GetRawSzArrayData();
+
+ if (pMT->ContainsGCPointers)
+ Buffer.BulkMoveWithWriteBarrier(ref dst, ref src, byteCount);
+ else
+ Buffer.Memmove(ref dst, ref src, byteCount);
+
+ // GC.KeepAlive(sourceArray) not required. pMT kept alive via sourceArray
+ return;
+ }
+
+ // Less common
+ Copy(sourceArray, sourceArray.GetLowerBound(0), destinationArray, destinationArray.GetLowerBound(0), length, reliable: false);
}
// Copies length elements from sourceArray, starting at sourceIndex, to
// destinationArray, starting at destinationIndex.
//
- public static void Copy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length)
+ public static unsafe void Copy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length)
+ {
+ if (sourceArray != null && destinationArray != null)
+ {
+ MethodTable* pMT = RuntimeHelpers.GetMethodTable(sourceArray);
+ if (pMT == RuntimeHelpers.GetMethodTable(destinationArray) &&
+ !pMT->IsMultiDimensionalArray &&
+ length >= 0 && sourceIndex >= 0 && destinationIndex >= 0 &&
+ (uint)(sourceIndex + length) <= (uint)sourceArray.Length &&
+ (uint)(destinationIndex + length) <= (uint)destinationArray.Length)
+ {
+ nuint elementSize = (nuint)pMT->ComponentSize;
+ nuint byteCount = (uint)length * elementSize;
+ ref byte src = ref Unsafe.AddByteOffset(ref sourceArray.GetRawSzArrayData(), (uint)sourceIndex * elementSize);
+ ref byte dst = ref Unsafe.AddByteOffset(ref destinationArray.GetRawSzArrayData(), (uint)destinationIndex * elementSize);
+
+ if (pMT->ContainsGCPointers)
+ Buffer.BulkMoveWithWriteBarrier(ref dst, ref src, byteCount);
+ else
+ Buffer.Memmove(ref dst, ref src, byteCount);
+
+ // GC.KeepAlive(sourceArray) not required. pMT kept alive via sourceArray
+ return;
+ }
+ }
+
+ // Less common
+ Copy(sourceArray!, sourceIndex, destinationArray!, destinationIndex, length, reliable: false);
+ }
+
+ private static unsafe void Copy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, bool reliable)
{
- Copy(sourceArray, sourceIndex, destinationArray, destinationIndex, length, false);
+ if (sourceArray == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.sourceArray);
+ if (destinationArray == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.destinationArray);
+
+ if (sourceArray.GetType() != destinationArray.GetType() && sourceArray.Rank != destinationArray.Rank)
+ throw new RankException(SR.Rank_MustMatch);
+
+ if (length < 0)
+ throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ int srcLB = sourceArray.GetLowerBound(0);
+ if (sourceIndex < srcLB || sourceIndex - srcLB < 0)
+ throw new ArgumentOutOfRangeException(nameof(sourceIndex), SR.ArgumentOutOfRange_ArrayLB);
+ sourceIndex -= srcLB;
+
+ int dstLB = destinationArray.GetLowerBound(0);
+ if (destinationIndex < dstLB || destinationIndex - dstLB < 0)
+ throw new ArgumentOutOfRangeException(nameof(destinationIndex), SR.ArgumentOutOfRange_ArrayLB);
+ destinationIndex -= dstLB;
+
+ if ((uint)(sourceIndex + length) > (uint)sourceArray.Length)
+ throw new ArgumentException(SR.Arg_LongerThanSrcArray, nameof(sourceArray));
+ if ((uint)(destinationIndex + length) > (uint)destinationArray.Length)
+ throw new ArgumentException(SR.Arg_LongerThanDestArray, nameof(destinationArray));
+
+ if (sourceArray.GetType() == destinationArray.GetType() || IsSimpleCopy(sourceArray, destinationArray))
+ {
+ MethodTable* pMT = RuntimeHelpers.GetMethodTable(sourceArray);
+
+ nuint elementSize = (nuint)pMT->ComponentSize;
+ nuint byteCount = (uint)length * elementSize;
+ ref byte src = ref Unsafe.AddByteOffset(ref sourceArray.GetRawArrayData(), (uint)sourceIndex * elementSize);
+ ref byte dst = ref Unsafe.AddByteOffset(ref destinationArray.GetRawArrayData(), (uint)destinationIndex * elementSize);
+
+ if (pMT->ContainsGCPointers)
+ Buffer.BulkMoveWithWriteBarrier(ref dst, ref src, byteCount);
+ else
+ Buffer.Memmove(ref dst, ref src, byteCount);
+
+ // GC.KeepAlive(sourceArray) not required. pMT kept alive via sourceArray
+ return;
+ }
+
+ // If we were called from Array.ConstrainedCopy, ensure that the array copy
+ // is guaranteed to succeed.
+ if (reliable)
+ throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_ConstrainedCopy);
+
+ // Rare
+ CopySlow(sourceArray, sourceIndex, destinationArray, destinationIndex, length);
}
+ [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.
[MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern void Copy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, bool reliable);
+ private static extern void CopySlow(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length);
// Provides a strong exception guarantee - either it succeeds, or
// it throws an exception with no side effects. The arrays must be
// It will up-cast, assuming the array types are correct.
public static void ConstrainedCopy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length)
{
- Copy(sourceArray, sourceIndex, destinationArray, destinationIndex, length, true);
+ Copy(sourceArray, sourceIndex, destinationArray, destinationIndex, length, reliable: true);
}
// Sets length elements in array to 0 (or null for Object arrays), starting
if (index < lowerBound || offset < 0 || length < 0 || (uint)(offset + length) > (uint)array.Length)
ThrowHelper.ThrowIndexOutOfRangeException();
- uint elementSize = pMT->ComponentSize;
+ nuint elementSize = pMT->ComponentSize;
- ref byte ptr = ref Unsafe.AddByteOffset(ref p, (uint)offset * (nuint)elementSize);
- nuint byteLength = (uint)length * (nuint)elementSize;
+ ref byte ptr = ref Unsafe.AddByteOffset(ref p, (uint)offset * elementSize);
+ nuint byteLength = (uint)length * elementSize;
if (pMT->ContainsGCPointers)
SpanHelpers.ClearWithReferences(ref Unsafe.As<byte, IntPtr>(ref ptr), byteLength / (uint)sizeof(IntPtr));
FCIMPLEND
-
-
-
-
- // Returns an enum saying whether you can copy an array of srcType into destType.
-ArrayNative::AssignArrayEnum ArrayNative::CanAssignArrayTypeNoGC(const BASEARRAYREF pSrc, const BASEARRAYREF pDest)
+ // Returns whether you can directly copy an array of srcType into destType.
+FCIMPL2(FC_BOOL_RET, ArrayNative::IsSimpleCopy, ArrayBase* pSrc, ArrayBase* pDst)
{
- CONTRACTL
- {
- NOTHROW;
- GC_NOTRIGGER;
- MODE_COOPERATIVE;
- PRECONDITION(pSrc != NULL);
- PRECONDITION(pDest != NULL);
- }
- CONTRACTL_END;
+ FCALL_CONTRACT;
- // The next 50 lines are a little tricky. Change them with great care.
- //
+ _ASSERTE(pSrc != NULL);
+ _ASSERTE(pDst != NULL);
- // 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();
- if (pSrcMT == pDestMT)
- return AssignWillWork;
+ // This case is expected to be handled by the fast path
+ _ASSERTE(pSrc->GetMethodTable() != pDst->GetMethodTable());
- TypeHandle srcTH = pSrcMT->GetApproxArrayElementTypeHandle();
- TypeHandle destTH = pDestMT->GetApproxArrayElementTypeHandle();
+ TypeHandle srcTH = pSrc->GetMethodTable()->GetApproxArrayElementTypeHandle();
+ TypeHandle destTH = pDst->GetMethodTable()->GetApproxArrayElementTypeHandle();
if (srcTH == destTH) // This check kicks for different array kind or dimensions
- return AssignWillWork;
+ FC_RETURN_BOOL(true);
- // Value class boxing
- if (srcTH.IsValueType() && !destTH.IsValueType())
+ if (srcTH.IsValueType())
{
- switch (srcTH.CanCastToCached(destTH))
+ // 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))
{
- case TypeHandle::CanCast : return AssignBoxValueClassOrPrimitive;
- case TypeHandle::CannotCast : return AssignWrongType;
- default : return AssignDontKnow;
+ if (GetNormalizedIntegralArrayElementType(srcElType) == GetNormalizedIntegralArrayElementType(destElType))
+ FC_RETURN_BOOL(true);
}
}
-
- // Value class unboxing.
- if (!srcTH.IsValueType() && destTH.IsValueType())
+ else
{
- if (srcTH.CanCastToCached(destTH) == TypeHandle::CanCast)
- return AssignUnboxValueClass;
- else if (destTH.CanCastToCached(srcTH) == TypeHandle::CanCast) // V extends IV. Copying from IV to V, or Object to V.
- return AssignUnboxValueClass;
- else
- return AssignDontKnow;
+ // Value class unboxing
+ 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))
+ TypeHandle::CastResult r = srcTH.CanCastToCached(destTH);
+ if (r != TypeHandle::MaybeCast)
{
- if (GetNormalizedIntegralArrayElementType(srcElType) == GetNormalizedIntegralArrayElementType(destElType))
- return AssignWillWork;
-
- if (InvokeUtil::CanPrimitiveWiden(destElType, srcElType))
- return AssignPrimitiveWiden;
- else
- return AssignWrongType;
+ FC_RETURN_BOOL(r);
}
- // dest Object extends src
- if (srcTH.CanCastToCached(destTH) == TypeHandle::CanCast)
- return AssignWillWork;
+ struct
+ {
+ OBJECTREF src;
+ OBJECTREF dst;
+ } gc;
- // src Object extends dest
- if (destTH.CanCastToCached(srcTH) == TypeHandle::CanCast)
- return AssignMustCast;
+ gc.src = ObjectToOBJECTREF(pSrc);
+ gc.dst = ObjectToOBJECTREF(pDst);
- // class X extends/implements src and implements dest.
- if (destTH.IsInterface() && srcElType != ELEMENT_TYPE_VALUETYPE)
- return AssignMustCast;
+ BOOL iRetVal = FALSE;
- // class X implements src and extends/implements dest
- if (srcTH.IsInterface() && destElType != ELEMENT_TYPE_VALUETYPE)
- return AssignMustCast;
+ HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc);
+ iRetVal = srcTH.CanCastTo(destTH);
+ HELPER_METHOD_FRAME_END();
- return AssignDontKnow;
+ FC_RETURN_BOOL(iRetVal);
}
+FCIMPLEND
// Returns an enum saying whether you can copy an array of srcType into destType.
}
CONTRACTL_END;
- // The next 50 lines are a little tricky. Change them with great care.
- //
-
// 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();
- if (pSrcMT == pDestMT)
- return AssignWillWork;
+ _ASSERTE(pSrcMT != pDestMT); // Handled by fast path
TypeHandle srcTH = pSrcMT->GetApproxArrayElementTypeHandle();
TypeHandle destTH = pDestMT->GetApproxArrayElementTypeHandle();
- if (srcTH == destTH) // This check kicks for different array kind or dimensions
- return AssignWillWork;
-
+ _ASSERTE(srcTH != destTH); // Handled by fast path
+
// Value class boxing
if (srcTH.IsValueType() && !destTH.IsValueType())
{
// Copying primitives from one type to another
if (CorTypeInfo::IsPrimitiveType_NoThrow(srcElType) && CorTypeInfo::IsPrimitiveType_NoThrow(destElType))
{
- if (srcElType == destElType)
- return AssignWillWork;
+ _ASSERTE(srcElType != destElType); // Handled by fast path
if (InvokeUtil::CanPrimitiveWiden(destElType, srcElType))
return AssignPrimitiveWiden;
else
}
// dest Object extends src
- if (srcTH.CanCastTo(destTH))
- return AssignWillWork;
+ _ASSERTE(!srcTH.CanCastTo(destTH)); // Handled by fast path
// src Object extends dest
if (destTH.CanCastTo(srcTH))
}
}
-void ArrayNative::ArrayCopyNoTypeCheck(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(length > 0);
- }
- CONTRACTL_END;
-
- BYTE *src = (BYTE*)pSrc->GetDataPtr();
- BYTE *dst = (BYTE*)pDest->GetDataPtr();
- SIZE_T size = pSrc->GetComponentSize();
-
- src += srcIndex * size;
- dst += destIndex * size;
-
- if (pDest->GetMethodTable()->ContainsPointers())
- {
- memmoveGCRefs(dst, src, length * size);
- }
- else
- {
- memmove(dst, src, length * size);
- }
-}
-
-FCIMPL6(void, ArrayNative::ArrayCopy, ArrayBase* m_pSrc, INT32 m_iSrcIndex, ArrayBase* m_pDst, INT32 m_iDstIndex, INT32 m_iLength, CLR_BOOL reliable)
+FCIMPL5(void, ArrayNative::CopySlow, ArrayBase* pSrc, INT32 iSrcIndex, ArrayBase* pDst, INT32 iDstIndex, INT32 iLength)
{
FCALL_CONTRACT;
BASEARRAYREF pDst;
} gc;
- gc.pSrc = (BASEARRAYREF)m_pSrc;
- gc.pDst = (BASEARRAYREF)m_pDst;
-
- //
- // creating a HelperMethodFrame is quite expensive,
- // so we want to delay this for the most common case which doesn't trigger a GC.
- // FCThrow is needed to throw an exception without a HelperMethodFrame
- //
+ gc.pSrc = (BASEARRAYREF)pSrc;
+ gc.pDst = (BASEARRAYREF)pDst;
// cannot pass null for source or destination
- if (gc.pSrc == NULL || gc.pDst == NULL) {
- FCThrowArgumentNullVoid(gc.pSrc==NULL ? W("sourceArray") : W("destinationArray"));
- }
+ _ASSERTE(gc.pSrc != NULL && gc.pDst != NULL);
// source and destination must be arrays
_ASSERTE(gc.pSrc->GetMethodTable()->IsArray());
_ASSERTE(gc.pDst->GetMethodTable()->IsArray());
- // Equal method tables should imply equal rank
- _ASSERTE(!(gc.pSrc->GetMethodTable() == gc.pDst->GetMethodTable() && gc.pSrc->GetRank() != gc.pDst->GetRank()));
+ _ASSERTE(gc.pSrc->GetRank() == gc.pDst->GetRank());
- // Which enables us to avoid touching the EEClass in simple cases
- if (gc.pSrc->GetMethodTable() != gc.pDst->GetMethodTable() && gc.pSrc->GetRank() != gc.pDst->GetRank()) {
- FCThrowResVoid(kRankException, W("Rank_MustMatch"));
- }
-
- g_IBCLogger.LogMethodTableAccess(gc.pSrc->GetMethodTable());
- g_IBCLogger.LogMethodTableAccess(gc.pDst->GetMethodTable());
-
- int srcLB = gc.pSrc->GetLowerBoundsPtr()[0];
- int destLB = gc.pDst->GetLowerBoundsPtr()[0];
// array bounds checking
- const unsigned int srcLen = gc.pSrc->GetNumComponents();
- const unsigned int destLen = gc.pDst->GetNumComponents();
- if (m_iLength < 0)
- FCThrowArgumentOutOfRangeVoid(W("length"), W("ArgumentOutOfRange_NeedNonNegNum"));
-
- if (m_iSrcIndex < srcLB || (m_iSrcIndex - srcLB < 0))
- FCThrowArgumentOutOfRangeVoid(W("sourceIndex"), W("ArgumentOutOfRange_ArrayLB"));
-
- if (m_iDstIndex < destLB || (m_iDstIndex - destLB < 0))
- FCThrowArgumentOutOfRangeVoid(W("destinationIndex"), W("ArgumentOutOfRange_ArrayLB"));
-
- if ((DWORD)(m_iSrcIndex - srcLB + m_iLength) > srcLen)
- FCThrowArgumentVoid(W("sourceArray"), W("Arg_LongerThanSrcArray"));
-
- if ((DWORD)(m_iDstIndex - destLB + m_iLength) > destLen)
- FCThrowArgumentVoid(W("destinationArray"), W("Arg_LongerThanDestArray"));
-
- int r = 0;
-
- // Small perf optimization - we copy from one portion of an array back to
- // itself a lot when resizing collections, etc. The cost of doing the type
- // checking is significant for copying small numbers of bytes (~half of the time
- // for copying 1 byte within one array from element 0 to element 1).
- if (gc.pSrc == gc.pDst)
- r = AssignWillWork;
- else
- r = CanAssignArrayTypeNoGC(gc.pSrc, gc.pDst);
-
- if (r == AssignWrongType) {
- FCThrowResVoid(kArrayTypeMismatchException, W("ArrayTypeMismatch_CantAssignType"));
- }
-
- if (r == AssignWillWork) {
- if (m_iLength > 0)
- ArrayCopyNoTypeCheck(gc.pSrc, m_iSrcIndex - srcLB, gc.pDst, m_iDstIndex - destLB, m_iLength);
-
- FC_GC_POLL();
- return;
- }
+ _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);
- if (r == AssignDontKnow)
- {
- r = CanAssignArrayType(gc.pSrc, gc.pDst);
- }
- CONSISTENCY_CHECK(r != AssignDontKnow);
- // If we were called from Array.ConstrainedCopy, ensure that the array copy
- // is guaranteed to succeed.
- if (reliable && r != AssignWillWork)
- COMPlusThrow(kArrayTypeMismatchException, W("ArrayTypeMismatch_ConstrainedCopy"));
+ int r = CanAssignArrayType(gc.pSrc, gc.pDst);
if (r == AssignWrongType)
COMPlusThrow(kArrayTypeMismatchException, W("ArrayTypeMismatch_CantAssignType"));
- if (m_iLength > 0)
+ if (iLength > 0)
{
switch (r)
{
- case AssignWillWork:
- ArrayCopyNoTypeCheck(gc.pSrc, m_iSrcIndex - srcLB, gc.pDst, m_iDstIndex - destLB, m_iLength);
- break;
-
case AssignUnboxValueClass:
- UnBoxEachElement(gc.pSrc, m_iSrcIndex - srcLB, gc.pDst, m_iDstIndex - destLB, m_iLength);
+ UnBoxEachElement(gc.pSrc, iSrcIndex, gc.pDst, iDstIndex, iLength);
break;
case AssignBoxValueClassOrPrimitive:
- BoxEachElement(gc.pSrc, m_iSrcIndex - srcLB, gc.pDst, m_iDstIndex - destLB, m_iLength);
+ BoxEachElement(gc.pSrc, iSrcIndex, gc.pDst, iDstIndex, iLength);
break;
case AssignMustCast:
- CastCheckEachElement(gc.pSrc, m_iSrcIndex - srcLB, gc.pDst, m_iDstIndex - destLB, m_iLength);
+ CastCheckEachElement(gc.pSrc, iSrcIndex, gc.pDst, iDstIndex, iLength);
break;
case AssignPrimitiveWiden:
- PrimitiveWiden(gc.pSrc, m_iSrcIndex - srcLB, gc.pDst, m_iDstIndex - destLB, m_iLength);
+ PrimitiveWiden(gc.pSrc, iSrcIndex, gc.pDst, iDstIndex, iLength);
break;
default: