[NativeAOT] Unify common cases of Array.Copy with CoreCLR (#83351)
authorVladimir Sadov <vsadov@microsoft.com>
Tue, 14 Mar 2023 02:39:18 +0000 (19:39 -0700)
committerGitHub <noreply@github.com>
Tue, 14 Mar 2023 02:39:18 +0000 (19:39 -0700)
* Unify common cases of Array.Copy

* ContainsGCPointers

* removed EETypePtr.FastEquals

* AreSameType

* fix the build after rebase

15 files changed:
src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs
src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs
src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/MethodTable.Runtime.cs
src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/RuntimeExports.cs
src/coreclr/nativeaot/System.Private.CoreLib/src/System/Array.NativeAot.cs
src/coreclr/nativeaot/System.Private.CoreLib/src/System/EETypePtr.cs
src/coreclr/nativeaot/System.Private.CoreLib/src/System/Object.NativeAot.cs
src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs
src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/InteropExtensions.cs
src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.NativeAot.cs
src/coreclr/nativeaot/System.Private.CoreLib/src/System/ValueType.cs
src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/EETypeCreator.cs
src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilder.cs
src/libraries/System.Private.CoreLib/src/System/Array.cs

index 40dfe0c..8a32e8f 100644 (file)
@@ -18,73 +18,8 @@ namespace System
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern unsafe Array InternalCreate(RuntimeType elementType, int rank, int* pLengths, int* pLowerBounds);
 
-        // Copies length elements from sourceArray, starting at index 0, to
-        // destinationArray, starting at index 0.
-        //
-        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);
-
-            MethodTable* pMT = RuntimeHelpers.GetMethodTable(sourceArray);
-            if (pMT == RuntimeHelpers.GetMethodTable(destinationArray) &&
-                !pMT->IsMultiDimensionalArray &&
-                (uint)length <= sourceArray.NativeLength &&
-                (uint)length <= destinationArray.NativeLength)
-            {
-                nuint byteCount = (uint)length * (nuint)pMT->ComponentSize;
-                ref byte src = ref Unsafe.As<RawArrayData>(sourceArray).Data;
-                ref byte dst = ref Unsafe.As<RawArrayData>(destinationArray).Data;
-
-                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 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) <= sourceArray.NativeLength &&
-                    (uint)(destinationIndex + length) <= destinationArray.NativeLength)
-                {
-                    nuint elementSize = (nuint)pMT->ComponentSize;
-                    nuint byteCount = (uint)length * elementSize;
-                    ref byte src = ref Unsafe.AddByteOffset(ref Unsafe.As<RawArrayData>(sourceArray).Data, (uint)sourceIndex * elementSize);
-                    ref byte dst = ref Unsafe.AddByteOffset(ref Unsafe.As<RawArrayData>(destinationArray).Data, (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)
+        private static unsafe void CopyImpl(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, bool reliable)
         {
             if (sourceArray == null)
                 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.sourceArray);
@@ -155,7 +90,7 @@ namespace System
         // 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, reliable: true);
+            CopyImpl(sourceArray, sourceIndex, destinationArray, destinationIndex, length, reliable: true);
         }
 
         /// <summary>
index 40cfc2c..1b35082 100644 (file)
@@ -556,6 +556,8 @@ namespace System.Runtime.CompilerServices
 
         public bool HasTypeEquivalence => (Flags & enum_flag_HasTypeEquivalence) != 0;
 
+        internal static bool AreSameType(MethodTable* mt1, MethodTable* mt2) => mt1 == mt2;
+
         public bool HasDefaultConstructor => (Flags & (enum_flag_HasComponentSize | enum_flag_HasDefaultCtor)) == enum_flag_HasDefaultCtor;
 
         public bool IsMultiDimensionalArray
index 414f8ab..a6f39ac 100644 (file)
@@ -515,6 +515,16 @@ namespace Internal.Runtime
             }
         }
 
+        internal bool IsMultiDimensionalArray
+        {
+            get
+            {
+                Debug.Assert(HasComponentSize);
+                // See comment on RawArrayData for details
+                return BaseSize > (uint)(3 * sizeof(IntPtr));
+            }
+        }
+
         internal bool IsGeneric
         {
             get
@@ -762,7 +772,7 @@ namespace Internal.Runtime
             }
         }
 
-        internal bool HasGCPointers
+        internal bool ContainsGCPointers
         {
             get
             {
index dbd0bb3..8c90087 100644 (file)
@@ -98,6 +98,14 @@ namespace Internal.Runtime
             return ((pThis->_uFlags | pOther->_uFlags) & (uint)EETypeFlags.ComplexCastingMask) == 0;
         }
 
+        internal static bool AreSameType(MethodTable* mt1, MethodTable* mt2)
+        {
+            if (mt1 == mt2)
+                return true;
+
+            return mt1->IsEquivalentTo(mt2);
+        }
+
         internal bool IsEquivalentTo(MethodTable* pOtherEEType)
         {
             fixed (MethodTable* pThis = &this)
index 05f30f8..da166f2 100644 (file)
@@ -106,7 +106,7 @@ namespace System.Runtime
 
             // Copy the unboxed value type data into the new object.
             // Perform any write barriers necessary for embedded reference fields.
-            if (pEEType->HasGCPointers)
+            if (pEEType->ContainsGCPointers)
             {
                 InternalCalls.RhBulkMoveWithWriteBarrier(ref result.GetRawData(), ref dataAdjustedForNullable, pEEType->ValueTypeSize);
             }
@@ -263,7 +263,7 @@ namespace System.Runtime
 
             ref byte fields = ref obj.GetRawData();
 
-            if (pEEType->HasGCPointers)
+            if (pEEType->ContainsGCPointers)
             {
                 // Copy the boxed fields into the new location in a GC safe manner
                 InternalCalls.RhBulkMoveWithWriteBarrier(ref data, ref fields, pEEType->ValueTypeSize);
index c587b0b..3ca6538 100644 (file)
@@ -148,65 +148,6 @@ namespace System
             CopyImpl(sourceArray, sourceIndex, destinationArray, destinationIndex, length, reliable: true);
         }
 
-        public static void Copy(Array sourceArray, Array destinationArray, int length)
-        {
-            if (sourceArray is null)
-                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.sourceArray);
-            if (destinationArray is null)
-                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.destinationArray);
-
-            EETypePtr eeType = sourceArray.GetEETypePtr();
-            if (eeType.FastEquals(destinationArray.GetEETypePtr()) &&
-                eeType.IsSzArray &&
-                (uint)length <= sourceArray.NativeLength &&
-                (uint)length <= destinationArray.NativeLength)
-            {
-                nuint byteCount = (uint)length * (nuint)eeType.ComponentSize;
-                ref byte src = ref Unsafe.As<RawArrayData>(sourceArray).Data;
-                ref byte dst = ref Unsafe.As<RawArrayData>(destinationArray).Data;
-
-                if (eeType.HasPointers)
-                    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
-            CopyImpl(sourceArray, sourceArray.GetLowerBound(0), destinationArray, destinationArray.GetLowerBound(0), length, reliable: false);
-        }
-
-        public static unsafe void Copy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length)
-        {
-            if (sourceArray != null && destinationArray != null)
-            {
-                EETypePtr eeType = sourceArray.GetEETypePtr();
-                if (eeType.FastEquals(destinationArray.GetEETypePtr()) &&
-                    eeType.IsSzArray &&
-                    length >= 0 && sourceIndex >= 0 && destinationIndex >= 0 &&
-                    (uint)(sourceIndex + length) <= sourceArray.NativeLength &&
-                    (uint)(destinationIndex + length) <= destinationArray.NativeLength)
-                {
-                    nuint elementSize = (nuint)eeType.ComponentSize;
-                    nuint byteCount = (uint)length * elementSize;
-                    ref byte src = ref Unsafe.AddByteOffset(ref Unsafe.As<RawArrayData>(sourceArray).Data, (uint)sourceIndex * elementSize);
-                    ref byte dst = ref Unsafe.AddByteOffset(ref Unsafe.As<RawArrayData>(destinationArray).Data, (uint)destinationIndex * elementSize);
-
-                    if (eeType.HasPointers)
-                        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
-            CopyImpl(sourceArray!, sourceIndex, destinationArray!, destinationIndex, length, reliable: false);
-        }
 
         //
         // Funnel for all the Array.Copy() overloads. The "reliable" parameter indicates whether the caller for ConstrainedCopy()
@@ -261,7 +202,7 @@ namespace System
             {
                 if (RuntimeImports.AreTypesEquivalent(sourceElementEEType, destinationElementEEType))
                 {
-                    if (sourceElementEEType.HasPointers)
+                    if (sourceElementEEType.ContainsGCPointers)
                     {
                         CopyImplValueTypeArrayWithInnerGcRefs(sourceArray, sourceIndex, destinationArray, destinationIndex, length, reliable);
                     }
@@ -521,9 +462,9 @@ namespace System
         //
         private static unsafe void CopyImplValueTypeArrayNoInnerGcRefs(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length)
         {
-            Debug.Assert((sourceArray.ElementEEType.IsValueType && !sourceArray.ElementEEType.HasPointers) ||
+            Debug.Assert((sourceArray.ElementEEType.IsValueType && !sourceArray.ElementEEType.ContainsGCPointers) ||
                 sourceArray.ElementEEType.IsPointer);
-            Debug.Assert((destinationArray.ElementEEType.IsValueType && !destinationArray.ElementEEType.HasPointers) ||
+            Debug.Assert((destinationArray.ElementEEType.IsValueType && !destinationArray.ElementEEType.ContainsGCPointers) ||
                 destinationArray.ElementEEType.IsPointer);
 
             // Copy scenario: ValueType-array to value-type array with no embedded gc-refs.
@@ -822,7 +763,7 @@ namespace System
             nuint totalByteLength = eeType.ComponentSize * array.NativeLength;
             ref byte pStart = ref MemoryMarshal.GetArrayDataReference(array);
 
-            if (!eeType.HasPointers)
+            if (!eeType.ContainsGCPointers)
             {
                 SpanHelpers.ClearWithoutReferences(ref pStart, totalByteLength);
             }
@@ -859,7 +800,7 @@ namespace System
             ref byte ptr = ref Unsafe.AddByteOffset(ref p, (uint)offset * elementSize);
             nuint byteLength = (uint)length * elementSize;
 
-            if (eeType.HasPointers)
+            if (eeType.ContainsGCPointers)
             {
                 Debug.Assert(byteLength % (nuint)sizeof(IntPtr) == 0);
                 SpanHelpers.ClearWithReferences(ref Unsafe.As<byte, IntPtr>(ref ptr), byteLength / (uint)sizeof(IntPtr));
index cd6055f..5e51645 100644 (file)
@@ -79,20 +79,6 @@ namespace System
             return (int)_value->HashCode;
         }
 
-        //
-        // Faster version of Equals for use on EETypes that are known not to be null and where the "match" case is the hot path.
-        //
-        public bool FastEquals(EETypePtr other)
-        {
-            Debug.Assert(!this.IsNull);
-            Debug.Assert(!other.IsNull);
-
-            // Fast check for raw equality before making call to helper.
-            if (this.RawValue == other.RawValue)
-                return true;
-            return RuntimeImports.AreTypesEquivalent(this, other);
-        }
-
         // Caution: You cannot safely compare RawValue's as RH does NOT unify EETypes. Use the == or Equals() methods exposed by EETypePtr itself.
         internal IntPtr RawValue
         {
@@ -356,12 +342,12 @@ namespace System
             }
         }
 
-        // Has internal gc pointers.
-        internal bool HasPointers
+        // Instance contains pointers to managed objects.
+        internal bool ContainsGCPointers
         {
             get
             {
-                return _value->HasGCPointers;
+                return _value->ContainsGCPointers;
             }
         }
 
index 1d82928..caf1773 100644 (file)
@@ -38,7 +38,7 @@ namespace System
             ref byte src = ref this.GetRawData();
             ref byte dst = ref clone.GetRawData();
 
-            if (this.GetEETypePtr().HasPointers)
+            if (this.GetEETypePtr().ContainsGCPointers)
                 Buffer.BulkMoveWithWriteBarrier(ref dst, ref src, byteCount);
             else
                 Buffer.Memmove(ref dst, ref src, byteCount);
index 02d6362..fffb184 100644 (file)
@@ -189,7 +189,7 @@ namespace System.Runtime.CompilerServices
         public static bool IsReferenceOrContainsReferences<T>()
         {
             var pEEType = EETypePtr.EETypePtrOf<T>();
-            return !pEEType.IsValueType || pEEType.HasPointers;
+            return !pEEType.IsValueType || pEEType.ContainsGCPointers;
         }
 
         [Intrinsic]
index bd10d0d..e263397 100644 (file)
@@ -28,7 +28,7 @@ namespace System.Runtime.InteropServices
             // This is used as the approximate implementation of MethodTable::IsBlittable(). It  will err in the direction of declaring
             // things blittable since it is used for argument validation only.
             //
-            return !eeType.HasPointers;
+            return !eeType.ContainsGCPointers;
         }
 
         public static bool IsBlittable(this RuntimeTypeHandle handle)
index c0a0702..44ed71c 100644 (file)
@@ -212,7 +212,7 @@ namespace System.Runtime.InteropServices
 
         internal static bool IsPinnable(object o)
         {
-            return (o == null) || !o.GetEETypePtr().HasPointers;
+            return (o == null) || !o.GetEETypePtr().ContainsGCPointers;
         }
 
         [EditorBrowsable(EditorBrowsableState.Never)]
index a445c6d..df124a2 100644 (file)
@@ -60,7 +60,7 @@ namespace System
             if (numFields == UseFastHelper)
             {
                 // Sanity check - if there are GC references, we should not be comparing bytes
-                Debug.Assert(!this.GetEETypePtr().HasPointers);
+                Debug.Assert(!this.GetEETypePtr().ContainsGCPointers);
 
                 // Compare the memory
                 int valueTypeSize = (int)this.GetEETypePtr().ValueTypeSize;
@@ -115,7 +115,7 @@ namespace System
         private static unsafe int FastGetValueTypeHashCodeHelper(MethodTable* type, ref byte data)
         {
             // Sanity check - if there are GC references, we should not be hashing bytes
-            Debug.Assert(!type->HasGCPointers);
+            Debug.Assert(!type->ContainsGCPointers);
 
             int size = (int)type->ValueTypeSize;
             int hashCode = 0;
index 8fefd5e..6799608 100644 (file)
@@ -279,7 +279,7 @@ namespace Internal.Runtime.TypeLoader
                     bool isSzArray = isArray ? state.ArrayRank < 1 : false;
                     int arrayRank = isArray ? state.ArrayRank.Value : 0;
                     CreateInstanceGCDesc(state, pTemplateEEType, pEEType, baseSize, cbGCDesc, isValueType, isArray, isSzArray, arrayRank);
-                    Debug.Assert(pEEType->HasGCPointers == (cbGCDesc != 0));
+                    Debug.Assert(pEEType->ContainsGCPointers == (cbGCDesc != 0));
 
                     // Copy the encoded optional fields buffer to the newly allocated memory, and update the OptionalFields field on the MethodTable
                     if (cbOptionalFieldsSize > 0)
@@ -397,7 +397,7 @@ namespace Internal.Runtime.TypeLoader
             {
                 if (cbGCDesc != 0)
                 {
-                    pEEType->HasGCPointers = true;
+                    pEEType->ContainsGCPointers = true;
                     if (state.IsArrayOfReferenceTypes)
                     {
                         IntPtr* gcDescStart = (IntPtr*)((byte*)pEEType - cbGCDesc);
@@ -412,29 +412,29 @@ namespace Internal.Runtime.TypeLoader
                 }
                 else
                 {
-                    pEEType->HasGCPointers = false;
+                    pEEType->ContainsGCPointers = false;
                 }
             }
             else if (gcBitfield != null)
             {
                 if (cbGCDesc != 0)
                 {
-                    pEEType->HasGCPointers = true;
+                    pEEType->ContainsGCPointers = true;
                     CreateGCDesc(gcBitfield, baseSize, isValueType, false, ((void**)pEEType) - 1);
                 }
                 else
                 {
-                    pEEType->HasGCPointers = false;
+                    pEEType->ContainsGCPointers = false;
                 }
             }
             else if (pTemplateEEType != null)
             {
                 Buffer.MemoryCopy((byte*)pTemplateEEType - cbGCDesc, (byte*)pEEType - cbGCDesc, cbGCDesc, cbGCDesc);
-                pEEType->HasGCPointers = pTemplateEEType->HasGCPointers;
+                pEEType->ContainsGCPointers = pTemplateEEType->ContainsGCPointers;
             }
             else
             {
-                pEEType->HasGCPointers = false;
+                pEEType->ContainsGCPointers = false;
             }
         }
 
index 7d8cf24..50e13f0 100644 (file)
@@ -504,7 +504,7 @@ namespace Internal.Runtime.TypeLoader
 
                 _bitfield = null;
                 _isReferenceTypeGCLayout = false; // This field is only used for the LowLevelList<bool> path
-                _gcdesc = MethodTable->HasGCPointers ? (void**)MethodTable - 1 : null;
+                _gcdesc = MethodTable->ContainsGCPointers ? (void**)MethodTable - 1 : null;
                 _size = (int)MethodTable->BaseSize;
             }
 
index 9772545..78ca760 100644 (file)
@@ -10,6 +10,7 @@ using System.Numerics;
 using System.Reflection;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
+using Internal.Runtime;
 
 #pragma warning disable 8500 // sizeof of managed types
 
@@ -231,6 +232,73 @@ namespace System
             Copy(sourceArray, isourceIndex, destinationArray, idestinationIndex, ilength);
         }
 
+#if !MONO // implementation details of MethodTable
+
+        // Copies length elements from sourceArray, starting at index 0, to
+        // destinationArray, starting at index 0.
+        public static unsafe void Copy(Array sourceArray, Array destinationArray, int length)
+        {
+            if (sourceArray is null)
+                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.sourceArray);
+            if (destinationArray is null)
+                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.destinationArray);
+
+            MethodTable* pMT = RuntimeHelpers.GetMethodTable(sourceArray);
+            if (MethodTable.AreSameType(pMT, RuntimeHelpers.GetMethodTable(destinationArray)) &&
+                !pMT->IsMultiDimensionalArray &&
+                (uint)length <= sourceArray.NativeLength &&
+                (uint)length <= destinationArray.NativeLength)
+            {
+                nuint byteCount = (uint)length * (nuint)pMT->ComponentSize;
+                ref byte src = ref Unsafe.As<RawArrayData>(sourceArray).Data;
+                ref byte dst = ref Unsafe.As<RawArrayData>(destinationArray).Data;
+
+                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
+            CopyImpl(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 unsafe void Copy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length)
+        {
+            if (sourceArray != null && destinationArray != null)
+            {
+                MethodTable* pMT = RuntimeHelpers.GetMethodTable(sourceArray);
+                if (MethodTable.AreSameType(pMT, RuntimeHelpers.GetMethodTable(destinationArray)) &&
+                    !pMT->IsMultiDimensionalArray &&
+                    length >= 0 && sourceIndex >= 0 && destinationIndex >= 0 &&
+                    (uint)(sourceIndex + length) <= sourceArray.NativeLength &&
+                    (uint)(destinationIndex + length) <= destinationArray.NativeLength)
+                {
+                    nuint elementSize = (nuint)pMT->ComponentSize;
+                    nuint byteCount = (uint)length * elementSize;
+                    ref byte src = ref Unsafe.AddByteOffset(ref Unsafe.As<RawArrayData>(sourceArray).Data, (uint)sourceIndex * elementSize);
+                    ref byte dst = ref Unsafe.AddByteOffset(ref Unsafe.As<RawArrayData>(destinationArray).Data, (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
+            CopyImpl(sourceArray!, sourceIndex, destinationArray!, destinationIndex, length, reliable: false);
+        }
+#endif
+
         // The various Get values...
         public object? GetValue(params int[] indices)
         {