From dfc09e05686685f931170254e42096e88e54668a Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 22 Jun 2025 23:35:39 +0800 Subject: [PATCH 01/39] Adapt CopyImpl and CanAssignArrayType to NativeAot --- .../src/System/Array.CoreCLR.cs | 77 ----------------- .../src/System/Array.NativeAot.cs | 82 +++++++++++++++++-- .../src/System/Array.cs | 77 +++++++++++++++++ 3 files changed, 152 insertions(+), 84 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 3928cb3311d9ba..a9e922242db6b3 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs @@ -49,63 +49,6 @@ internal static unsafe object CreateInstanceMDArray(nint typeHandle, uint dwNumA return arr!; } - private static unsafe void CopyImpl(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, bool reliable) - { - 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); - - ArgumentOutOfRangeException.ThrowIfNegative(length); - - int srcLB = sourceArray.GetLowerBound(0); - ArgumentOutOfRangeException.ThrowIfLessThan(sourceIndex, srcLB); - ArgumentOutOfRangeException.ThrowIfNegative(sourceIndex - srcLB, nameof(sourceIndex)); - sourceIndex -= srcLB; - - int dstLB = destinationArray.GetLowerBound(0); - ArgumentOutOfRangeException.ThrowIfLessThan(destinationIndex, dstLB); - ArgumentOutOfRangeException.ThrowIfNegative(destinationIndex - dstLB, nameof(destinationIndex)); - destinationIndex -= dstLB; - - if ((uint)(sourceIndex + length) > sourceArray.NativeLength) - throw new ArgumentException(SR.Arg_LongerThanSrcArray, nameof(sourceArray)); - if ((uint)(destinationIndex + length) > destinationArray.NativeLength) - throw new ArgumentException(SR.Arg_LongerThanDestArray, nameof(destinationArray)); - - ArrayAssignType assignType = ArrayAssignType.WrongType; - - if (sourceArray.GetType() == destinationArray.GetType() - || (assignType = CanAssignArrayType(sourceArray, destinationArray)) == ArrayAssignType.SimpleCopy) - { - MethodTable* pMT = RuntimeHelpers.GetMethodTable(sourceArray); - - nuint elementSize = (nuint)pMT->ComponentSize; - nuint byteCount = (uint)length * elementSize; - ref byte src = ref Unsafe.AddByteOffset(ref MemoryMarshal.GetArrayDataReference(sourceArray), (uint)sourceIndex * elementSize); - ref byte dst = ref Unsafe.AddByteOffset(ref MemoryMarshal.GetArrayDataReference(destinationArray), (uint)destinationIndex * elementSize); - - if (pMT->ContainsGCPointers) - Buffer.BulkMoveWithWriteBarrier(ref dst, ref src, byteCount); - else - SpanHelpers.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, assignType); - } - private static CorElementType GetNormalizedIntegralArrayElementType(CorElementType elementType) { Debug.Assert(elementType.IsPrimitiveType()); @@ -157,16 +100,6 @@ private static void CopySlow(Array sourceArray, int sourceIndex, Array destinati } } - private enum ArrayAssignType - { - SimpleCopy, - WrongType, - MustCast, - BoxValueClassOrPrimitive, - UnboxValueClass, - PrimitiveWiden, - } - private static unsafe ArrayAssignType CanAssignArrayType(Array sourceArray, Array destinationArray) { TypeHandle srcTH = RuntimeHelpers.GetMethodTable(sourceArray)->GetArrayElementTypeHandle(); @@ -347,16 +280,6 @@ private static unsafe void CopyImplPrimitiveWiden(Array sourceArray, int sourceI } } - // Provides a strong exception guarantee - either it succeeds, or - // it throws an exception with no side effects. The arrays must be - // compatible array types based on the array element type - this - // method does not support casting, boxing, or primitive widening. - // It will up-cast, assuming the array types are correct. - public static void ConstrainedCopy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length) - { - CopyImpl(sourceArray, sourceIndex, destinationArray, destinationIndex, length, reliable: true); - } - /// /// Clears the contents of an array. /// 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 d83afdccd16814..3a47838ab3edff 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 @@ -163,16 +163,84 @@ private ref int GetRawMultiDimArrayBounds() return ref Unsafe.As(ref Unsafe.As(this).Data); } - // Provides a strong exception guarantee - either it succeeds, or - // it throws an exception with no side effects. The arrays must be - // compatible array types based on the array element type - this - // method does not support casting, boxing, or primitive widening. - // It will up-cast, assuming the array types are correct. - public static void ConstrainedCopy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length) + private static unsafe ArrayAssignType CanAssignArrayType(Array sourceArray, Array destinationArray) { - CopyImpl(sourceArray, sourceIndex, destinationArray, destinationIndex, length, reliable: true); + MethodTable* sourceElementEEType = sourceArray.ElementMethodTable; + MethodTable* destinationElementEEType = destinationArray.ElementMethodTable; + + if (sourceElementEEType == destinationElementEEType) // This check kicks for different array kind or dimensions + return ArrayAssignType.SimpleCopy; + + // Value class boxing + if (sourceElementEEType->IsValueType && !destinationElementEEType->IsValueType) + { + if (RuntimeImports.AreTypesAssignable(sourceElementEEType, destinationElementEEType)) + return ArrayAssignType.BoxValueClassOrPrimitive; + else + return ArrayAssignType.WrongType; + } + + // Value class unboxing. + if (!sourceElementEEType->IsValueType && destinationElementEEType->IsValueType) + { + if (RuntimeImports.AreTypesAssignable(sourceElementEEType, destinationElementEEType)) + return ArrayAssignType.UnboxValueClass; + else if (RuntimeImports.AreTypesAssignable(destinationElementEEType, sourceElementEEType)) // V extends IV. Copying from IV to V, or Object to V. + return ArrayAssignType.UnboxValueClass; + else + return ArrayAssignType.WrongType; + } + + // Copying primitives from one type to another + if (sourceElementEEType->IsPrimitive && destinationElementEEType->IsPrimitive) + { + EETypeElementType sourceElementType = sourceElementEEType->ElementType; + EETypeElementType destElementType = destinationElementEEType->ElementType; + + if (GetNormalizedIntegralArrayElementType(sourceElementType) == GetNormalizedIntegralArrayElementType(destElementType)) + return ArrayAssignType.SimpleCopy; + else if (InvokeUtils.CanPrimitiveWiden(sourceElementType, destElementType)) + return ArrayAssignType.PrimitiveWiden; + else + return ArrayAssignType.WrongType; + } + + // src Object extends dest + if (RuntimeImports.AreTypesAssignable(sourceElementEEType, destinationElementEEType)) + return ArrayAssignType.SimpleCopy; + + // dest Object extends src + if (RuntimeImports.AreTypesAssignable(destinationElementEEType, sourceElementEEType)) + return ArrayAssignType.MustCast; + + // class X extends/implements src and implements dest. + if (destinationElementEEType->IsInterface) + return ArrayAssignType.MustCast; + + // class X implements src and extends/implements dest + if (sourceElementEEType->IsInterface) + return ArrayAssignType.MustCast; + + // Compatible pointers + if (sourceElementEEType->IsPointer && destinationElementEEType->IsPointer + && RuntimeImports.AreTypesAssignable(sourceElementEEType, destinationElementEEType)) + return ArrayAssignType.SimpleCopy; + + return ArrayAssignType.WrongType; } + private static EETypeElementType GetNormalizedIntegralArrayElementType(EETypeElementType elementType) + { + Debug.Assert(elementType >= EETypeElementType.Boolean && elementType <= EETypeElementType.Double); + + // 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 + + // U1/U2/U4/U8/U + int shift = (0b0010_1010_1010_0000 >> (int)elementType) & 1; + return (EETypeElementType)((int)elementType - shift); + } // // Funnel for all the Array.Copy() overloads. The "reliable" parameter indicates whether the caller for ConstrainedCopy() diff --git a/src/libraries/System.Private.CoreLib/src/System/Array.cs b/src/libraries/System.Private.CoreLib/src/System/Array.cs index 6df854d28b5460..bf52791fa1dfcb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Array.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Array.cs @@ -416,6 +416,83 @@ public static unsafe void Copy(Array sourceArray, int sourceIndex, Array destina // Less common CopyImpl(sourceArray!, sourceIndex, destinationArray!, destinationIndex, length, reliable: false); } + + // Provides a strong exception guarantee - either it succeeds, or + // it throws an exception with no side effects. The arrays must be + // compatible array types based on the array element type - this + // method does not support casting, boxing, or primitive widening. + // It will up-cast, assuming the array types are correct. + public static void ConstrainedCopy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length) + { + CopyImpl(sourceArray, sourceIndex, destinationArray, destinationIndex, length, reliable: true); + } + + private static unsafe void CopyImpl(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, bool reliable) + { + 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); + + ArgumentOutOfRangeException.ThrowIfNegative(length); + + int srcLB = sourceArray.GetLowerBound(0); + ArgumentOutOfRangeException.ThrowIfLessThan(sourceIndex, srcLB); + ArgumentOutOfRangeException.ThrowIfNegative(sourceIndex - srcLB, nameof(sourceIndex)); + sourceIndex -= srcLB; + + int dstLB = destinationArray.GetLowerBound(0); + ArgumentOutOfRangeException.ThrowIfLessThan(destinationIndex, dstLB); + ArgumentOutOfRangeException.ThrowIfNegative(destinationIndex - dstLB, nameof(destinationIndex)); + destinationIndex -= dstLB; + + if ((uint)(sourceIndex + length) > sourceArray.NativeLength) + throw new ArgumentException(SR.Arg_LongerThanSrcArray, nameof(sourceArray)); + if ((uint)(destinationIndex + length) > destinationArray.NativeLength) + throw new ArgumentException(SR.Arg_LongerThanDestArray, nameof(destinationArray)); + + ArrayAssignType assignType = ArrayAssignType.WrongType; + + if (sourceArray.GetType() == destinationArray.GetType() + || (assignType = CanAssignArrayType(sourceArray, destinationArray)) == ArrayAssignType.SimpleCopy) + { + MethodTable* pMT = RuntimeHelpers.GetMethodTable(sourceArray); + + nuint elementSize = (nuint)pMT->ComponentSize; + nuint byteCount = (uint)length * elementSize; + ref byte src = ref Unsafe.AddByteOffset(ref MemoryMarshal.GetArrayDataReference(sourceArray), (uint)sourceIndex * elementSize); + ref byte dst = ref Unsafe.AddByteOffset(ref MemoryMarshal.GetArrayDataReference(destinationArray), (uint)destinationIndex * elementSize); + + if (pMT->ContainsGCPointers) + Buffer.BulkMoveWithWriteBarrier(ref dst, ref src, byteCount); + else + SpanHelpers.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, assignType); + } + + private enum ArrayAssignType + { + SimpleCopy, + WrongType, + MustCast, + BoxValueClassOrPrimitive, + UnboxValueClass, + PrimitiveWiden, + } #endif // The various Get values... From 3c031da11ebc04a0e20fab6c5d0c94374c7e6dfa Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 23 Jun 2025 00:00:31 +0800 Subject: [PATCH 02/39] Adapt CopySlow paths --- .../src/System/Array.CoreCLR.cs | 38 ------------ .../src/System/Array.NativeAot.cs | 62 +++++++++++++------ .../src/System/Array.cs | 38 ++++++++++++ 3 files changed, 81 insertions(+), 57 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 a9e922242db6b3..4bf12dcdbd8c40 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs @@ -62,44 +62,6 @@ private static CorElementType GetNormalizedIntegralArrayElementType(CorElementTy return (CorElementType)((int)elementType - shift); } - // 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 void CopySlow(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, ArrayAssignType assignType) - { - Debug.Assert(sourceArray.Rank == destinationArray.Rank); - - if (assignType == ArrayAssignType.WrongType) - throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_CantAssignType); - - if (length > 0) - { - switch (assignType) - { - case ArrayAssignType.UnboxValueClass: - CopyImplUnBoxEachElement(sourceArray, sourceIndex, destinationArray, destinationIndex, length); - break; - - case ArrayAssignType.BoxValueClassOrPrimitive: - CopyImplBoxEachElement(sourceArray, sourceIndex, destinationArray, destinationIndex, length); - break; - - case ArrayAssignType.MustCast: - CopyImplCastCheckEachElement(sourceArray, sourceIndex, destinationArray, destinationIndex, length); - break; - - case ArrayAssignType.PrimitiveWiden: - CopyImplPrimitiveWiden(sourceArray, sourceIndex, destinationArray, destinationIndex, length); - break; - - default: - Debug.Fail("Fell through switch in Array.Copy!"); - break; - } - } - } - private static unsafe ArrayAssignType CanAssignArrayType(Array sourceArray, Array destinationArray) { TypeHandle srcTH = RuntimeHelpers.GetMethodTable(sourceArray)->GetArrayElementTypeHandle(); 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 3a47838ab3edff..a68f82319ac923 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 @@ -416,10 +416,50 @@ private static unsafe void CopyImplGcRefArray(Array sourceArray, int sourceIndex } } + // + // Array.CopyImpl case: Gc-ref array to gc-ref array copy. + // + private static unsafe void CopyImplCastCheckEachElement(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length) + { + // For mismatched array types, the desktop Array.Copy has a policy that determines whether to throw an ArrayTypeMismatch without any attempt to copy + // or to throw an InvalidCastException in the middle of a copy. This code replicates that policy. + MethodTable* sourceElementEEType = sourceArray.ElementMethodTable; + MethodTable* destinationElementEEType = destinationArray.ElementMethodTable; + + Debug.Assert(!sourceElementEEType->IsValueType && !sourceElementEEType->IsPointer && !sourceElementEEType->IsFunctionPointer); + Debug.Assert(!destinationElementEEType->IsValueType && !destinationElementEEType->IsPointer && !destinationElementEEType->IsFunctionPointer); + + bool reverseCopy = ((object)sourceArray == (object)destinationArray) && (sourceIndex < destinationIndex); + ref object? refDestinationArray = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(destinationArray)); + ref object? refSourceArray = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(sourceArray)); + if (reverseCopy) + { + sourceIndex += length - 1; + destinationIndex += length - 1; + for (int i = 0; i < length; i++) + { + object? value = Unsafe.Add(ref refSourceArray, sourceIndex - i); + if (value != null && TypeCast.IsInstanceOfAny(destinationElementEEType, value) == null) + throw new InvalidCastException(SR.InvalidCast_DownCastArrayElement); + Unsafe.Add(ref refDestinationArray, destinationIndex - i) = value; + } + } + else + { + for (int i = 0; i < length; i++) + { + object? value = Unsafe.Add(ref refSourceArray, sourceIndex + i); + if (value != null && TypeCast.IsInstanceOfAny(destinationElementEEType, value) == null) + throw new InvalidCastException(SR.InvalidCast_DownCastArrayElement); + Unsafe.Add(ref refDestinationArray, destinationIndex + i) = value; + } + } + } + // // Array.CopyImpl case: Value-type array to Object[] or interface array copy. // - private static unsafe void CopyImplValueTypeArrayToReferenceArray(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, bool reliable) + private static unsafe void CopyImplBoxEachElement(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length) { Debug.Assert(sourceArray.ElementMethodTable->IsValueType); Debug.Assert(!destinationArray.ElementMethodTable->IsValueType && !destinationArray.ElementMethodTable->IsPointer && !destinationArray.ElementMethodTable->IsFunctionPointer); @@ -427,9 +467,6 @@ private static unsafe void CopyImplValueTypeArrayToReferenceArray(Array sourceAr // Caller has already validated this. Debug.Assert(RuntimeImports.AreTypesAssignable(sourceArray.ElementMethodTable, destinationArray.ElementMethodTable)); - if (reliable) - throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_ConstrainedCopy); - MethodTable* sourceElementEEType = sourceArray.ElementMethodTable; nuint sourceElementSize = sourceArray.ElementSize; @@ -449,14 +486,11 @@ private static unsafe void CopyImplValueTypeArrayToReferenceArray(Array sourceAr // // Array.CopyImpl case: Object[] or interface array to value-type array copy. // - private static unsafe void CopyImplReferenceArrayToValueTypeArray(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, bool reliable) + private static unsafe void CopyImplUnBoxEachElement(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length) { Debug.Assert(!sourceArray.ElementMethodTable->IsValueType && !sourceArray.ElementMethodTable->IsPointer && !sourceArray.ElementMethodTable->IsFunctionPointer); Debug.Assert(destinationArray.ElementMethodTable->IsValueType); - if (reliable) - throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_CantAssignType); - MethodTable* destinationElementEEType = destinationArray.ElementMethodTable; nuint destinationElementSize = destinationArray.ElementSize; bool isNullable = destinationElementEEType->IsNullable; @@ -577,7 +611,7 @@ ref Unsafe.AddByteOffset(ref MemoryMarshal.GetArrayDataReference(sourceArray), ( // // Array.CopyImpl case: Primitive types that have a widening conversion // - private static unsafe void CopyImplPrimitiveTypeWithWidening(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, bool reliable) + private static unsafe void CopyImplPrimitiveWiden(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length) { MethodTable* sourceElementEEType = sourceArray.ElementMethodTable; MethodTable* destinationElementEEType = destinationArray.ElementMethodTable; @@ -590,16 +624,6 @@ private static unsafe void CopyImplPrimitiveTypeWithWidening(Array sourceArray, nuint srcElementSize = sourceArray.ElementSize; nuint destElementSize = destinationArray.ElementSize; - if ((sourceElementEEType->IsEnum || destinationElementEEType->IsEnum) && sourceElementType != destElementType) - throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_CantAssignType); - - if (reliable) - { - // ConstrainedCopy() cannot even widen - it can only copy same type or enum to its exact integral subtype. - if (sourceElementType != destElementType) - throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_ConstrainedCopy); - } - ref byte srcData = ref Unsafe.AddByteOffset(ref MemoryMarshal.GetArrayDataReference(sourceArray), (nuint)sourceIndex * srcElementSize); ref byte dstData = ref Unsafe.AddByteOffset(ref MemoryMarshal.GetArrayDataReference(destinationArray), (nuint)destinationIndex * destElementSize); diff --git a/src/libraries/System.Private.CoreLib/src/System/Array.cs b/src/libraries/System.Private.CoreLib/src/System/Array.cs index bf52791fa1dfcb..14b2fc9c3b84a5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Array.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Array.cs @@ -484,6 +484,44 @@ private static unsafe void CopyImpl(Array sourceArray, int sourceIndex, Array de CopySlow(sourceArray, sourceIndex, destinationArray, destinationIndex, length, assignType); } + // 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 void CopySlow(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, ArrayAssignType assignType) + { + Debug.Assert(sourceArray.Rank == destinationArray.Rank); + + if (assignType == ArrayAssignType.WrongType) + throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_CantAssignType); + + if (length > 0) + { + switch (assignType) + { + case ArrayAssignType.UnboxValueClass: + CopyImplUnBoxEachElement(sourceArray, sourceIndex, destinationArray, destinationIndex, length); + break; + + case ArrayAssignType.BoxValueClassOrPrimitive: + CopyImplBoxEachElement(sourceArray, sourceIndex, destinationArray, destinationIndex, length); + break; + + case ArrayAssignType.MustCast: + CopyImplCastCheckEachElement(sourceArray, sourceIndex, destinationArray, destinationIndex, length); + break; + + case ArrayAssignType.PrimitiveWiden: + CopyImplPrimitiveWiden(sourceArray, sourceIndex, destinationArray, destinationIndex, length); + break; + + default: + Debug.Fail("Fell through switch in Array.Copy!"); + break; + } + } + } + private enum ArrayAssignType { SimpleCopy, From 8af6824676b4d907ac0ebe0473a050134d3d8171 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 23 Jun 2025 00:11:50 +0800 Subject: [PATCH 03/39] Cleanup old CopyImpl --- .../src/System/Array.NativeAot.cs | 267 +----------------- 1 file changed, 1 insertion(+), 266 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 a68f82319ac923..cb727f017d9f81 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 @@ -242,180 +242,6 @@ private static EETypeElementType GetNormalizedIntegralArrayElementType(EETypeEle return (EETypeElementType)((int)elementType - shift); } - // - // Funnel for all the Array.Copy() overloads. The "reliable" parameter indicates whether the caller for ConstrainedCopy() - // (must leave destination array unchanged on any exception.) - // - private static unsafe void CopyImpl(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, bool reliable) - { - if (sourceArray is null) - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.sourceArray); - if (destinationArray is null) - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.destinationArray); - - if (sourceArray.GetType() != destinationArray.GetType() && sourceArray.Rank != destinationArray.Rank) - throw new RankException(SR.Rank_MustMatch); - - ArgumentOutOfRangeException.ThrowIfNegative(length); - - const int srcLB = 0; - if (sourceIndex < srcLB || sourceIndex - srcLB < 0) - throw new ArgumentOutOfRangeException(nameof(sourceIndex), SR.ArgumentOutOfRange_ArrayLB); - sourceIndex -= srcLB; - - const int dstLB = 0; - if (destinationIndex < dstLB || destinationIndex - dstLB < 0) - throw new ArgumentOutOfRangeException(nameof(destinationIndex), SR.ArgumentOutOfRange_ArrayLB); - destinationIndex -= dstLB; - - if ((uint)(sourceIndex + length) > sourceArray.NativeLength) - throw new ArgumentException(SR.Arg_LongerThanSrcArray, nameof(sourceArray)); - if ((uint)(destinationIndex + length) > destinationArray.NativeLength) - throw new ArgumentException(SR.Arg_LongerThanDestArray, nameof(destinationArray)); - - MethodTable* sourceElementEEType = sourceArray.ElementMethodTable; - MethodTable* destinationElementEEType = destinationArray.ElementMethodTable; - - if (!destinationElementEEType->IsValueType && !destinationElementEEType->IsPointer && !destinationElementEEType->IsFunctionPointer) - { - if (!sourceElementEEType->IsValueType && !sourceElementEEType->IsPointer && !sourceElementEEType->IsFunctionPointer) - { - CopyImplGcRefArray(sourceArray, sourceIndex, destinationArray, destinationIndex, length, reliable); - } - else if (RuntimeImports.AreTypesAssignable(sourceElementEEType, destinationElementEEType)) - { - CopyImplValueTypeArrayToReferenceArray(sourceArray, sourceIndex, destinationArray, destinationIndex, length, reliable); - } - else - { - throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_CantAssignType); - } - } - else - { - if (sourceElementEEType == destinationElementEEType) - { - if (sourceElementEEType->ContainsGCPointers) - { - CopyImplValueTypeArrayWithInnerGcRefs(sourceArray, sourceIndex, destinationArray, destinationIndex, length, reliable); - } - else - { - CopyImplValueTypeArrayNoInnerGcRefs(sourceArray, sourceIndex, destinationArray, destinationIndex, length); - } - } - else if ((sourceElementEEType->IsPointer || sourceElementEEType->IsFunctionPointer) && (destinationElementEEType->IsPointer || destinationElementEEType->IsFunctionPointer)) - { - // 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(sourceElementEEType, destinationElementEEType)) - { - CopyImplValueTypeArrayNoInnerGcRefs(sourceArray, sourceIndex, destinationArray, destinationIndex, length); - } - else - { - throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_CantAssignType); - } - } - else if (IsSourceElementABaseClassOrInterfaceOfDestinationValueType(sourceElementEEType, destinationElementEEType)) - { - CopyImplReferenceArrayToValueTypeArray(sourceArray, sourceIndex, destinationArray, destinationIndex, length, reliable); - } - else if (sourceElementEEType->IsPrimitive && destinationElementEEType->IsPrimitive) - { - if (RuntimeImports.AreTypesAssignable(sourceArray.GetMethodTable(), destinationArray.GetMethodTable())) - { - // If we're okay casting between these two, we're also okay blitting the values over - CopyImplValueTypeArrayNoInnerGcRefs(sourceArray, sourceIndex, destinationArray, destinationIndex, length); - } - else - { - // The only case remaining is that primitive types could have a widening conversion between the source element type and the destination - // If a widening conversion does not exist we are going to throw an ArrayTypeMismatchException from it. - CopyImplPrimitiveTypeWithWidening(sourceArray, sourceIndex, destinationArray, destinationIndex, length, reliable); - } - } - else - { - throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_CantAssignType); - } - } - } - - private static unsafe bool IsSourceElementABaseClassOrInterfaceOfDestinationValueType(MethodTable* sourceElementEEType, MethodTable* destinationElementEEType) - { - if (sourceElementEEType->IsValueType || sourceElementEEType->IsPointer || sourceElementEEType->IsFunctionPointer) - return false; - - // It may look like we're passing the arguments to AreTypesAssignable in the wrong order but we're not. The source array is an interface or Object array, the destination - // array is a value type array. Our job is to check if the destination value type implements the interface - which is what this call to AreTypesAssignable does. - // The copy loop still checks each element to make sure it actually is the correct valuetype. - if (!RuntimeImports.AreTypesAssignable(destinationElementEEType, sourceElementEEType)) - return false; - return true; - } - - // - // Array.CopyImpl case: Gc-ref array to gc-ref array copy. - // - private static unsafe void CopyImplGcRefArray(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, bool reliable) - { - // For mismatched array types, the desktop Array.Copy has a policy that determines whether to throw an ArrayTypeMismatch without any attempt to copy - // or to throw an InvalidCastException in the middle of a copy. This code replicates that policy. - MethodTable* sourceElementEEType = sourceArray.ElementMethodTable; - MethodTable* destinationElementEEType = destinationArray.ElementMethodTable; - - Debug.Assert(!sourceElementEEType->IsValueType && !sourceElementEEType->IsPointer && !sourceElementEEType->IsFunctionPointer); - Debug.Assert(!destinationElementEEType->IsValueType && !destinationElementEEType->IsPointer && !destinationElementEEType->IsFunctionPointer); - - bool attemptCopy = RuntimeImports.AreTypesAssignable(sourceElementEEType, destinationElementEEType); - bool mustCastCheckEachElement = !attemptCopy; - if (reliable) - { - if (mustCastCheckEachElement) - throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_ConstrainedCopy); - } - else - { - attemptCopy = attemptCopy || RuntimeImports.AreTypesAssignable(destinationElementEEType, sourceElementEEType); - - // If either array is an interface array, we allow the attempt to copy even if the other element type does not statically implement the interface. - // We don't have an "IsInterface" property in EETypePtr so we instead check for a null BaseType. The only the other MethodTable with a null BaseType is - // System.Object but if that were the case, we would already have passed one of the AreTypesAssignable checks above. - attemptCopy = attemptCopy || sourceElementEEType->BaseType == null; - attemptCopy = attemptCopy || destinationElementEEType->BaseType == null; - - if (!attemptCopy) - throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_CantAssignType); - } - - bool reverseCopy = ((object)sourceArray == (object)destinationArray) && (sourceIndex < destinationIndex); - ref object? refDestinationArray = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(destinationArray)); - ref object? refSourceArray = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(sourceArray)); - if (reverseCopy) - { - sourceIndex += length - 1; - destinationIndex += length - 1; - for (int i = 0; i < length; i++) - { - object? value = Unsafe.Add(ref refSourceArray, sourceIndex - i); - if (mustCastCheckEachElement && value != null && TypeCast.IsInstanceOfAny(destinationElementEEType, value) == null) - throw new InvalidCastException(SR.InvalidCast_DownCastArrayElement); - Unsafe.Add(ref refDestinationArray, destinationIndex - i) = value; - } - } - else - { - for (int i = 0; i < length; i++) - { - object? value = Unsafe.Add(ref refSourceArray, sourceIndex + i); - if (mustCastCheckEachElement && value != null && TypeCast.IsInstanceOfAny(destinationElementEEType, value) == null) - throw new InvalidCastException(SR.InvalidCast_DownCastArrayElement); - Unsafe.Add(ref refDestinationArray, destinationIndex + i) = value; - } - } - } - // // Array.CopyImpl case: Gc-ref array to gc-ref array copy. // @@ -521,93 +347,6 @@ private static unsafe void CopyImplUnBoxEachElement(Array sourceArray, int sourc } } - - // - // Array.CopyImpl case: Value-type array with embedded gc-references. - // - private static unsafe void CopyImplValueTypeArrayWithInnerGcRefs(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, bool reliable) - { - Debug.Assert(sourceArray.GetMethodTable() == destinationArray.GetMethodTable()); - Debug.Assert(sourceArray.ElementMethodTable->IsValueType); - - MethodTable* sourceElementEEType = sourceArray.GetMethodTable()->RelatedParameterType; - bool reverseCopy = ((object)sourceArray == (object)destinationArray) && (sourceIndex < destinationIndex); - - // Copy scenario: ValueType-array to value-type array with embedded gc-refs. - object[]? boxedElements = null; - if (reliable) - { - boxedElements = new object[length]; - reverseCopy = false; - } - - fixed (byte* pDstArray = &MemoryMarshal.GetArrayDataReference(destinationArray), pSrcArray = &MemoryMarshal.GetArrayDataReference(sourceArray)) - { - nuint cbElementSize = sourceArray.ElementSize; - byte* pSourceElement = pSrcArray + (nuint)sourceIndex * cbElementSize; - byte* pDestinationElement = pDstArray + (nuint)destinationIndex * cbElementSize; - if (reverseCopy) - { - pSourceElement += (nuint)length * cbElementSize; - pDestinationElement += (nuint)length * cbElementSize; - } - - for (int i = 0; i < length; i++) - { - if (reverseCopy) - { - pSourceElement -= cbElementSize; - pDestinationElement -= cbElementSize; - } - - object boxedValue = RuntimeExports.RhBox(sourceElementEEType, ref *pSourceElement); - if (boxedElements != null) - boxedElements[i] = boxedValue; - else - RuntimeImports.RhUnbox(boxedValue, ref *pDestinationElement, sourceElementEEType); - - if (!reverseCopy) - { - pSourceElement += cbElementSize; - pDestinationElement += cbElementSize; - } - } - } - - if (boxedElements != null) - { - fixed (byte* pDstArray = &MemoryMarshal.GetArrayDataReference(destinationArray)) - { - nuint cbElementSize = sourceArray.ElementSize; - byte* pDestinationElement = pDstArray + (nuint)destinationIndex * cbElementSize; - for (int i = 0; i < length; i++) - { - RuntimeImports.RhUnbox(boxedElements[i], ref *pDestinationElement, sourceElementEEType); - pDestinationElement += cbElementSize; - } - } - } - } - - // - // Array.CopyImpl case: Value-type array without embedded gc-references. - // - private static unsafe void CopyImplValueTypeArrayNoInnerGcRefs(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length) - { - Debug.Assert((sourceArray.ElementMethodTable->IsValueType && !sourceArray.ElementMethodTable->ContainsGCPointers) || - sourceArray.ElementMethodTable->IsPointer || sourceArray.ElementMethodTable->IsFunctionPointer); - Debug.Assert((destinationArray.ElementMethodTable->IsValueType && !destinationArray.ElementMethodTable->ContainsGCPointers) || - destinationArray.ElementMethodTable->IsPointer || destinationArray.ElementMethodTable->IsFunctionPointer); - - // Copy scenario: ValueType-array to value-type array with no embedded gc-refs. - nuint elementSize = sourceArray.ElementSize; - - SpanHelpers.Memmove( - ref Unsafe.AddByteOffset(ref MemoryMarshal.GetArrayDataReference(destinationArray), (nuint)destinationIndex * elementSize), - ref Unsafe.AddByteOffset(ref MemoryMarshal.GetArrayDataReference(sourceArray), (nuint)sourceIndex * elementSize), - elementSize * (nuint)length); - } - // // Array.CopyImpl case: Primitive types that have a widening conversion // @@ -620,6 +359,7 @@ private static unsafe void CopyImplPrimitiveWiden(Array sourceArray, int sourceI EETypeElementType sourceElementType = sourceElementEEType->ElementType; EETypeElementType destElementType = destinationElementEEType->ElementType; + Debug.Assert(InvokeUtils.CanPrimitiveWiden(destElementType, sourceElementType)); nuint srcElementSize = sourceArray.ElementSize; nuint destElementSize = destinationArray.ElementSize; @@ -634,11 +374,6 @@ private static unsafe void CopyImplPrimitiveWiden(Array sourceArray, int sourceI return; } - if (!InvokeUtils.CanPrimitiveWiden(destElementType, sourceElementType)) - { - throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_CantAssignType); - } - for (int i = 0; i < length; i++) { InvokeUtils.PrimitiveWiden(destElementType, sourceElementType, ref dstData, ref srcData); From ae4a0d531eacd090353822aa885c43664149649d Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 23 Jun 2025 00:16:49 +0800 Subject: [PATCH 04/39] Add test for primitive widen with enum --- .../tests/System.Runtime.Tests/System/ArrayTests.cs | 4 ++++ 1 file changed, 4 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 ccaba707130386..98c39a757fd280 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 @@ -1220,6 +1220,10 @@ public static IEnumerable Copy_SZArray_PrimitiveWidening_TestData() // Single[] -> primitive[] yield return new object[] { new float[] { 1, 2.2f, 3 }, 0, new double[3], 0, 3, new double[] { 1, 2.2f, 3 } }; + + // SByteEnum[] -> primitive[] + yield return new object[] { new SByteEnum[] { (SByteEnum)1, (SByteEnum)2, (SByteEnum)3 }, 0, new int[3], 0, 3, new int[] { 1, 2, 3 } }; + yield return new object[] { new SByteEnum[] { (SByteEnum)1, (SByteEnum)2, (SByteEnum)3 }, 0, new Int32Enum[3], 0, 3, new Int32Enum[] { (Int32Enum)1, (Int32Enum)2, (Int32Enum)3 } }; } public static IEnumerable Copy_SZArray_UnreliableConversion_CanPerform_TestData() From ce29d081a7b5d50c9302f24315c50a5df9012005 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 23 Jun 2025 00:29:45 +0800 Subject: [PATCH 05/39] Cleanup unreachable path --- .../src/System/Array.NativeAot.cs | 35 ++++--------------- 1 file changed, 7 insertions(+), 28 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 cb727f017d9f81..8605d365a9123c 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 @@ -254,31 +254,16 @@ private static unsafe void CopyImplCastCheckEachElement(Array sourceArray, int s Debug.Assert(!sourceElementEEType->IsValueType && !sourceElementEEType->IsPointer && !sourceElementEEType->IsFunctionPointer); Debug.Assert(!destinationElementEEType->IsValueType && !destinationElementEEType->IsPointer && !destinationElementEEType->IsFunctionPointer); + Debug.Assert(sourceElementEEType != destinationElementEEType); - bool reverseCopy = ((object)sourceArray == (object)destinationArray) && (sourceIndex < destinationIndex); ref object? refDestinationArray = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(destinationArray)); ref object? refSourceArray = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(sourceArray)); - if (reverseCopy) - { - sourceIndex += length - 1; - destinationIndex += length - 1; - for (int i = 0; i < length; i++) - { - object? value = Unsafe.Add(ref refSourceArray, sourceIndex - i); - if (value != null && TypeCast.IsInstanceOfAny(destinationElementEEType, value) == null) - throw new InvalidCastException(SR.InvalidCast_DownCastArrayElement); - Unsafe.Add(ref refDestinationArray, destinationIndex - i) = value; - } - } - else + for (int i = 0; i < length; i++) { - for (int i = 0; i < length; i++) - { - object? value = Unsafe.Add(ref refSourceArray, sourceIndex + i); - if (value != null && TypeCast.IsInstanceOfAny(destinationElementEEType, value) == null) - throw new InvalidCastException(SR.InvalidCast_DownCastArrayElement); - Unsafe.Add(ref refDestinationArray, destinationIndex + i) = value; - } + object? value = Unsafe.Add(ref refSourceArray, sourceIndex + i); + if (value != null && TypeCast.IsInstanceOfAny(destinationElementEEType, value) == null) + throw new InvalidCastException(SR.InvalidCast_DownCastArrayElement); + Unsafe.Add(ref refDestinationArray, destinationIndex + i) = value; } } @@ -363,17 +348,11 @@ private static unsafe void CopyImplPrimitiveWiden(Array sourceArray, int sourceI nuint srcElementSize = sourceArray.ElementSize; nuint destElementSize = destinationArray.ElementSize; + Debug.Assert(srcElementSize != destElementSize); ref byte srcData = ref Unsafe.AddByteOffset(ref MemoryMarshal.GetArrayDataReference(sourceArray), (nuint)sourceIndex * srcElementSize); ref byte dstData = ref Unsafe.AddByteOffset(ref MemoryMarshal.GetArrayDataReference(destinationArray), (nuint)destinationIndex * destElementSize); - if (sourceElementType == destElementType) - { - // Multidim arrays and enum->int copies can still reach this path. - SpanHelpers.Memmove(ref dstData, ref srcData, (nuint)length * srcElementSize); - return; - } - for (int i = 0; i < length; i++) { InvokeUtils.PrimitiveWiden(destElementType, sourceElementType, ref dstData, ref srcData); From 97dc43969b266b47d635b9fc908c41d5db31edd9 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 23 Jun 2025 00:55:57 +0800 Subject: [PATCH 06/39] Fold identical ConstrainedCopy --- .../src/System/Array.cs | 20 +++++++++---------- .../src/System/Array.Mono.cs | 5 ----- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Array.cs b/src/libraries/System.Private.CoreLib/src/System/Array.cs index 14b2fc9c3b84a5..55b71e573f829a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Array.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Array.cs @@ -351,6 +351,16 @@ public static void Copy(Array sourceArray, long sourceIndex, Array destinationAr Copy(sourceArray, isourceIndex, destinationArray, idestinationIndex, ilength); } + // Provides a strong exception guarantee - either it succeeds, or + // it throws an exception with no side effects. The arrays must be + // compatible array types based on the array element type - this + // method does not support casting, boxing, or primitive widening. + // It will up-cast, assuming the array types are correct. + public static void ConstrainedCopy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length) + { + CopyImpl(sourceArray, sourceIndex, destinationArray, destinationIndex, length, reliable: true); + } + #if !MONO // implementation details of MethodTable // Copies length elements from sourceArray, starting at index 0, to @@ -417,16 +427,6 @@ public static unsafe void Copy(Array sourceArray, int sourceIndex, Array destina CopyImpl(sourceArray!, sourceIndex, destinationArray!, destinationIndex, length, reliable: false); } - // Provides a strong exception guarantee - either it succeeds, or - // it throws an exception with no side effects. The arrays must be - // compatible array types based on the array element type - this - // method does not support casting, boxing, or primitive widening. - // It will up-cast, assuming the array types are correct. - public static void ConstrainedCopy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length) - { - CopyImpl(sourceArray, sourceIndex, destinationArray, destinationIndex, length, reliable: true); - } - private static unsafe void CopyImpl(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, bool reliable) { if (sourceArray == null) 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 cded34be47b1f8..c92e0e69b9402a 100644 --- a/src/mono/System.Private.CoreLib/src/System/Array.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Array.Mono.cs @@ -92,11 +92,6 @@ public static unsafe void Clear(Array array, int index, int length) SpanHelpers.ClearWithoutReferences(ref ptr, byteLength); } - public static void ConstrainedCopy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length) - { - Copy(sourceArray, sourceIndex, destinationArray, destinationIndex, length, true); - } - public static void Copy(Array sourceArray, Array destinationArray, int length) { ArgumentNullException.ThrowIfNull(sourceArray); From c9e0ae20bcd08d3e399d0b87b34d0bcf3f180219 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 23 Jun 2025 01:38:44 +0800 Subject: [PATCH 07/39] Share Array.Clear --- .../src/System/Array.CoreCLR.cs | 64 ----------------- .../RuntimeHelpers.CoreCLR.cs | 10 +++ .../src/Internal/Runtime/MethodTable.cs | 14 ++++ .../src/System/Array.NativeAot.cs | 59 ---------------- .../src/System/Array.cs | 68 ++++++++++++++++++- 5 files changed, 90 insertions(+), 125 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 4bf12dcdbd8c40..67e6bcc1d5ea10 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs @@ -242,70 +242,6 @@ private static unsafe void CopyImplPrimitiveWiden(Array sourceArray, int sourceI } } - /// - /// Clears the contents of an array. - /// - /// The array to clear. - /// is null. - public static unsafe void Clear(Array array) - { - if (array == null) - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); - - MethodTable* pMT = RuntimeHelpers.GetMethodTable(array); - nuint totalByteLength = pMT->ComponentSize * array.NativeLength; - ref byte pStart = ref MemoryMarshal.GetArrayDataReference(array); - - if (!pMT->ContainsGCPointers) - { - SpanHelpers.ClearWithoutReferences(ref pStart, totalByteLength); - } - else - { - Debug.Assert(totalByteLength % (nuint)sizeof(IntPtr) == 0); - SpanHelpers.ClearWithReferences(ref Unsafe.As(ref pStart), totalByteLength / (nuint)sizeof(IntPtr)); - } - - // GC.KeepAlive(array) not required. pMT kept alive via `pStart` - } - - // Sets length elements in array to 0 (or null for Object arrays), starting - // at index. - // - public static unsafe void Clear(Array array, int index, int length) - { - if (array == null) - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); - - ref byte p = ref Unsafe.As(array).Data; - int lowerBound = 0; - - MethodTable* pMT = RuntimeHelpers.GetMethodTable(array); - if (pMT->IsMultiDimensionalArray) - { - int rank = pMT->MultiDimensionalArrayRank; - lowerBound = Unsafe.Add(ref Unsafe.As(ref p), rank); - p = ref Unsafe.Add(ref p, 2 * sizeof(int) * rank); // skip the bounds - } - - int offset = index - lowerBound; - - if (index < lowerBound || offset < 0 || length < 0 || (uint)(offset + length) > array.NativeLength) - ThrowHelper.ThrowIndexOutOfRangeException(); - - nuint elementSize = pMT->ComponentSize; - - ref byte ptr = ref Unsafe.AddByteOffset(ref p, (uint)offset * elementSize); - nuint byteLength = (uint)length * elementSize; - - if (pMT->ContainsGCPointers) - SpanHelpers.ClearWithReferences(ref Unsafe.As(ref ptr), byteLength / (uint)sizeof(IntPtr)); - else - SpanHelpers.ClearWithoutReferences(ref ptr, byteLength); - - // GC.KeepAlive(array) not required. pMT kept alive via `ptr` - } - private unsafe nint GetFlattenedIndex(int rawIndex) { // Checked by the caller 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 2f4a401b18a689..03875e457ccbdc 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 @@ -763,6 +763,7 @@ internal unsafe struct MethodTable private const uint enum_flag_Category_TruePrimitive = 0x00070000; // sub-category of ValueType, Primitive (ELEMENT_TYPE_I, etc.) private const uint enum_flag_Category_Array = 0x00080000; private const uint enum_flag_Category_Array_Mask = 0x000C0000; + private const uint enum_flag_Category_IfArrayThenSzArray = 0x00020000; // sub-category of Array private const uint enum_flag_Category_ValueType_Mask = 0x000C0000; private const uint enum_flag_Category_Interface = 0x000C0000; // Types that require non-trivial interface cast have this bit set in the category @@ -819,6 +820,15 @@ internal unsafe struct MethodTable public bool HasDefaultConstructor => (Flags & (enum_flag_HasComponentSize | enum_flag_HasDefaultCtor)) == enum_flag_HasDefaultCtor; + public bool IsSzArray + { + get + { + Debug.Assert(IsArray); + return (Flags & enum_flag_Category_IfArrayThenSzArray) != 0; + } + } + public bool IsMultiDimensionalArray { [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs b/src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs index 6aabd800f2915a..52bfed936c9d47 100644 --- a/src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs +++ b/src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs @@ -419,6 +419,20 @@ internal int ArrayRank } } + internal int MultiDimensionalArrayRank + { + get + { + Debug.Assert(this.IsArray); + + int boundsSize = (int)this.ParameterizedTypeShape - SZARRAY_BASE_SIZE; + Debug.Assert(boundsSize > 0); + // Multidim array case: Base size includes space for two Int32s + // (upper and lower bound) per each dimension of the array. + return boundsSize / (2 * sizeof(int)); + } + } + internal bool IsSzArray { get 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 8605d365a9123c..5a6f9af21f96de 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 @@ -361,65 +361,6 @@ private static unsafe void CopyImplPrimitiveWiden(Array sourceArray, int sourceI } } - public static unsafe void Clear(Array array) - { - if (array == null) - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); - - MethodTable* mt = array.GetMethodTable(); - nuint totalByteLength = mt->ComponentSize * array.NativeLength; - ref byte pStart = ref MemoryMarshal.GetArrayDataReference(array); - - if (!mt->ContainsGCPointers) - { - SpanHelpers.ClearWithoutReferences(ref pStart, totalByteLength); - } - else - { - Debug.Assert(totalByteLength % (nuint)sizeof(IntPtr) == 0); - SpanHelpers.ClearWithReferences(ref Unsafe.As(ref pStart), totalByteLength / (nuint)sizeof(IntPtr)); - } - } - - public static unsafe void Clear(Array array, int index, int length) - { - if (array == null) - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); - - ref byte p = ref Unsafe.As(array).Data; - int lowerBound = 0; - - MethodTable* mt = array.GetMethodTable(); - if (!mt->IsSzArray) - { - int rank = mt->ArrayRank; - lowerBound = Unsafe.Add(ref Unsafe.As(ref p), rank); - p = ref Unsafe.Add(ref p, 2 * sizeof(int) * rank); // skip the bounds - } - - int offset = index - lowerBound; - - if (index < lowerBound || offset < 0 || length < 0 || (uint)(offset + length) > array.NativeLength) - ThrowHelper.ThrowIndexOutOfRangeException(); - - nuint elementSize = mt->ComponentSize; - - ref byte ptr = ref Unsafe.AddByteOffset(ref p, (uint)offset * elementSize); - nuint byteLength = (uint)length * elementSize; - - if (mt->ContainsGCPointers) - { - Debug.Assert(byteLength % (nuint)sizeof(IntPtr) == 0); - SpanHelpers.ClearWithReferences(ref Unsafe.As(ref ptr), byteLength / (uint)sizeof(IntPtr)); - } - else - { - SpanHelpers.ClearWithoutReferences(ref ptr, byteLength); - } - - // GC.KeepAlive(array) not required. pMT kept alive via `ptr` - } - [Intrinsic] public int GetLength(int dimension) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Array.cs b/src/libraries/System.Private.CoreLib/src/System/Array.cs index 55b71e573f829a..5d44f30ab01a6a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Array.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Array.cs @@ -374,7 +374,7 @@ public static unsafe void Copy(Array sourceArray, Array destinationArray, int le MethodTable* pMT = RuntimeHelpers.GetMethodTable(sourceArray); if (MethodTable.AreSameType(pMT, RuntimeHelpers.GetMethodTable(destinationArray)) && - !pMT->IsMultiDimensionalArray && + pMT->IsSzArray && (uint)length <= sourceArray.NativeLength && (uint)length <= destinationArray.NativeLength) { @@ -403,7 +403,7 @@ public static unsafe void Copy(Array sourceArray, int sourceIndex, Array destina { MethodTable* pMT = RuntimeHelpers.GetMethodTable(sourceArray); if (MethodTable.AreSameType(pMT, RuntimeHelpers.GetMethodTable(destinationArray)) && - !pMT->IsMultiDimensionalArray && + pMT->IsSzArray && length >= 0 && sourceIndex >= 0 && destinationIndex >= 0 && (uint)(sourceIndex + length) <= sourceArray.NativeLength && (uint)(destinationIndex + length) <= destinationArray.NativeLength) @@ -531,6 +531,70 @@ private enum ArrayAssignType UnboxValueClass, PrimitiveWiden, } + + /// + /// Clears the contents of an array. + /// + /// The array to clear. + /// is null. + public static unsafe void Clear(Array array) + { + if (array == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + + MethodTable* pMT = RuntimeHelpers.GetMethodTable(array); + nuint totalByteLength = pMT->ComponentSize * array.NativeLength; + ref byte pStart = ref MemoryMarshal.GetArrayDataReference(array); + + if (!pMT->ContainsGCPointers) + { + SpanHelpers.ClearWithoutReferences(ref pStart, totalByteLength); + } + else + { + Debug.Assert(totalByteLength % (nuint)sizeof(IntPtr) == 0); + SpanHelpers.ClearWithReferences(ref Unsafe.As(ref pStart), totalByteLength / (nuint)sizeof(IntPtr)); + } + + // GC.KeepAlive(array) not required. pMT kept alive via `pStart` + } + + // Sets length elements in array to 0 (or null for Object arrays), starting + // at index. + // + public static unsafe void Clear(Array array, int index, int length) + { + if (array == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + + ref byte p = ref Unsafe.As(array).Data; + int lowerBound = 0; + + MethodTable* pMT = RuntimeHelpers.GetMethodTable(array); + if (!pMT->IsSzArray) + { + int rank = pMT->MultiDimensionalArrayRank; + lowerBound = Unsafe.Add(ref Unsafe.As(ref p), rank); + p = ref Unsafe.Add(ref p, 2 * sizeof(int) * rank); // skip the bounds + } + + int offset = index - lowerBound; + + if (index < lowerBound || offset < 0 || length < 0 || (uint)(offset + length) > array.NativeLength) + ThrowHelper.ThrowIndexOutOfRangeException(); + + nuint elementSize = pMT->ComponentSize; + + ref byte ptr = ref Unsafe.AddByteOffset(ref p, (uint)offset * elementSize); + nuint byteLength = (uint)length * elementSize; + + if (pMT->ContainsGCPointers) + SpanHelpers.ClearWithReferences(ref Unsafe.As(ref ptr), byteLength / (uint)sizeof(IntPtr)); + else + SpanHelpers.ClearWithoutReferences(ref ptr, byteLength); + + // GC.KeepAlive(array) not required. pMT kept alive via `ptr` + } #endif // The various Get values... From 2488fe9720f1081657e08ff1ab46e289cbdfcc86 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 23 Jun 2025 01:47:37 +0800 Subject: [PATCH 08/39] Share GetFlattenedIndex --- .../src/System/Array.CoreCLR.cs | 45 ---------------- .../RuntimeHelpers.CoreCLR.cs | 2 +- .../src/System/Array.NativeAot.cs | 53 ++----------------- .../src/System/Array.cs | 45 ++++++++++++++++ 4 files changed, 50 insertions(+), 95 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 67e6bcc1d5ea10..fe013381c4fc31 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs @@ -242,51 +242,6 @@ private static unsafe void CopyImplPrimitiveWiden(Array sourceArray, int sourceI } } - private unsafe nint GetFlattenedIndex(int rawIndex) - { - // Checked by the caller - Debug.Assert(Rank == 1); - - if (RuntimeHelpers.GetMethodTable(this)->IsMultiDimensionalArray) - { - ref int bounds = ref RuntimeHelpers.GetMultiDimensionalArrayBounds(this); - rawIndex -= Unsafe.Add(ref bounds, 1); - } - - if ((uint)rawIndex >= (uint)LongLength) - ThrowHelper.ThrowIndexOutOfRangeException(); - return rawIndex; - } - - private unsafe nint GetFlattenedIndex(ReadOnlySpan indices) - { - // Checked by the caller - Debug.Assert(indices.Length == Rank); - - if (RuntimeHelpers.GetMethodTable(this)->IsMultiDimensionalArray) - { - ref int bounds = ref RuntimeHelpers.GetMultiDimensionalArrayBounds(this); - nint flattenedIndex = 0; - for (int i = 0; i < indices.Length; i++) - { - int index = indices[i] - Unsafe.Add(ref bounds, indices.Length + i); - int length = Unsafe.Add(ref bounds, i); - if ((uint)index >= (uint)length) - ThrowHelper.ThrowIndexOutOfRangeException(); - flattenedIndex = (length * flattenedIndex) + index; - } - Debug.Assert((nuint)flattenedIndex < (nuint)LongLength); - return flattenedIndex; - } - else - { - int index = indices[0]; - if ((uint)index >= (uint)LongLength) - ThrowHelper.ThrowIndexOutOfRangeException(); - return index; - } - } - internal unsafe object? InternalGetValue(nint flattenedIndex) { MethodTable* pMethodTable = RuntimeHelpers.GetMethodTable(this); 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 03875e457ccbdc..1ef6b965b8a0c5 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 @@ -414,7 +414,7 @@ internal static unsafe ushort GetElementSize(this Array array) // Returns pointer to the multi-dimensional array bounds. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static ref int GetMultiDimensionalArrayBounds(Array array) + internal static ref int GetMultiDimensionalArrayBounds(this Array array) { Debug.Assert(GetMultiDimensionalArrayRank(array) > 0); // See comment on RawArrayData for details 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 5a6f9af21f96de..ed6b9f5e864bd4 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 @@ -157,7 +157,7 @@ public unsafe void Initialize() } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ref int GetRawMultiDimArrayBounds() + private ref int GetMultiDimensionalArrayBounds() { Debug.Assert(!IsSzArray); return ref Unsafe.As(ref Unsafe.As(this).Data); @@ -413,7 +413,7 @@ internal static unsafe Array NewMultiDimArray(MethodTable* eeType, int* pLengths Debug.Assert(eeType->NumVtableSlots != 0, "Compiler enforces we never have unconstructed MTs for multi-dim arrays since those can be template-constructed anytime"); Array ret = RuntimeImports.RhNewVariableSizeObject(eeType, (int)totalLength); - ref int bounds = ref ret.GetRawMultiDimArrayBounds(); + ref int bounds = ref ret.GetMultiDimensionalArrayBounds(); for (int i = 0; i < rank; i++) { Unsafe.Add(ref bounds, i) = pLengths[i]; @@ -431,7 +431,7 @@ public int GetLowerBound(int dimension) if ((uint)dimension >= rank) throw new IndexOutOfRangeException(); - return Unsafe.Add(ref GetRawMultiDimArrayBounds(), rank + dimension); + return Unsafe.Add(ref GetMultiDimensionalArrayBounds(), rank + dimension); } if (dimension != 0) @@ -448,7 +448,7 @@ public int GetUpperBound(int dimension) if ((uint)dimension >= rank) throw new IndexOutOfRangeException(); - ref int bounds = ref GetRawMultiDimArrayBounds(); + ref int bounds = ref GetMultiDimensionalArrayBounds(); int length = Unsafe.Add(ref bounds, dimension); int lowerBound = Unsafe.Add(ref bounds, rank + dimension); @@ -460,51 +460,6 @@ public int GetUpperBound(int dimension) return Length - 1; } - private unsafe nint GetFlattenedIndex(int rawIndex) - { - // Checked by the caller - Debug.Assert(Rank == 1); - - if (!IsSzArray) - { - ref int bounds = ref GetRawMultiDimArrayBounds(); - rawIndex -= Unsafe.Add(ref bounds, 1); - } - - if ((uint)rawIndex >= NativeLength) - ThrowHelper.ThrowIndexOutOfRangeException(); - return rawIndex; - } - - internal unsafe nint GetFlattenedIndex(ReadOnlySpan indices) - { - // Checked by the caller - Debug.Assert(indices.Length == Rank); - - if (!IsSzArray) - { - ref int bounds = ref GetRawMultiDimArrayBounds(); - nint flattenedIndex = 0; - for (int i = 0; i < indices.Length; i++) - { - int index = indices[i] - Unsafe.Add(ref bounds, indices.Length + i); - int length = Unsafe.Add(ref bounds, i); - if ((uint)index >= (uint)length) - ThrowHelper.ThrowIndexOutOfRangeException(); - flattenedIndex = (length * flattenedIndex) + index; - } - Debug.Assert((nuint)flattenedIndex < NativeLength); - return flattenedIndex; - } - else - { - int index = indices[0]; - if ((uint)index >= NativeLength) - ThrowHelper.ThrowIndexOutOfRangeException(); - return index; - } - } - internal unsafe object? InternalGetValue(nint flattenedIndex) { Debug.Assert((nuint)flattenedIndex < NativeLength); diff --git a/src/libraries/System.Private.CoreLib/src/System/Array.cs b/src/libraries/System.Private.CoreLib/src/System/Array.cs index 5d44f30ab01a6a..0769fb8b9a7593 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Array.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Array.cs @@ -595,6 +595,51 @@ public static unsafe void Clear(Array array, int index, int length) // GC.KeepAlive(array) not required. pMT kept alive via `ptr` } + + private unsafe nint GetFlattenedIndex(int rawIndex) + { + // Checked by the caller + Debug.Assert(Rank == 1); + + if (!RuntimeHelpers.GetMethodTable(this)->IsSzArray) + { + ref int bounds = ref this.GetMultiDimensionalArrayBounds(); + rawIndex -= Unsafe.Add(ref bounds, 1); + } + + if ((uint)rawIndex >= NativeLength) + ThrowHelper.ThrowIndexOutOfRangeException(); + return rawIndex; + } + + internal unsafe nint GetFlattenedIndex(ReadOnlySpan indices) + { + // Checked by the caller + Debug.Assert(indices.Length == Rank); + + if (!RuntimeHelpers.GetMethodTable(this)->IsSzArray) + { + ref int bounds = ref this.GetMultiDimensionalArrayBounds(); + nint flattenedIndex = 0; + for (int i = 0; i < indices.Length; i++) + { + int index = indices[i] - Unsafe.Add(ref bounds, indices.Length + i); + int length = Unsafe.Add(ref bounds, i); + if ((uint)index >= (uint)length) + ThrowHelper.ThrowIndexOutOfRangeException(); + flattenedIndex = (length * flattenedIndex) + index; + } + Debug.Assert((nuint)flattenedIndex < NativeLength); + return flattenedIndex; + } + else + { + int index = indices[0]; + if ((uint)index >= NativeLength) + ThrowHelper.ThrowIndexOutOfRangeException(); + return index; + } + } #endif // The various Get values... From b65a09378fee9b035fe337e0c1dec8fa51c2a5b2 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 23 Jun 2025 02:32:33 +0800 Subject: [PATCH 09/39] Reduce overhead of GetLength/LowerBound/UpperBound --- .../src/Internal/Runtime/MethodTable.cs | 2 +- .../src/System/Array.NativeAot.cs | 50 +++++++------------ 2 files changed, 20 insertions(+), 32 deletions(-) diff --git a/src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs b/src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs index 52bfed936c9d47..e5e1e62e35ffab 100644 --- a/src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs +++ b/src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs @@ -419,6 +419,7 @@ internal int ArrayRank } } + // Returns rank of multi-dimensional array rank, 0 for sz arrays internal int MultiDimensionalArrayRank { get @@ -426,7 +427,6 @@ internal int MultiDimensionalArrayRank Debug.Assert(this.IsArray); int boundsSize = (int)this.ParameterizedTypeShape - SZARRAY_BASE_SIZE; - Debug.Assert(boundsSize > 0); // Multidim array case: Base size includes space for two Int32s // (upper and lower bound) per each dimension of the array. return boundsSize / (2 * sizeof(int)); 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 ed6b9f5e864bd4..6daea5fb20422c 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 @@ -362,12 +362,16 @@ private static unsafe void CopyImplPrimitiveWiden(Array sourceArray, int sourceI } [Intrinsic] - public int GetLength(int dimension) + public unsafe int GetLength(int dimension) { - int length = GetUpperBound(dimension) + 1; - // We don't support non-zero lower bounds so don't incur the cost of obtaining it. - Debug.Assert(GetLowerBound(dimension) == 0); - return length; + int rank = this.GetMethodTable()->MultiDimensionalArrayRank; + if (rank == 0 && dimension == 0) + return Length; + + if ((uint)dimension >= (uint)rank) + throw new IndexOutOfRangeException(SR.IndexOutOfRange_ArrayRankIndex); + + return Unsafe.Add(ref GetMultiDimensionalArrayBounds(), dimension); } public unsafe int Rank @@ -423,41 +427,25 @@ internal static unsafe Array NewMultiDimArray(MethodTable* eeType, int* pLengths } [Intrinsic] - public int GetLowerBound(int dimension) + public unsafe int GetLowerBound(int dimension) { - if (!IsSzArray) - { - int rank = Rank; - if ((uint)dimension >= rank) - throw new IndexOutOfRangeException(); + if (dimension == 0) + return 0; - return Unsafe.Add(ref GetMultiDimensionalArrayBounds(), rank + dimension); - } - - if (dimension != 0) + // We don't support non-zero lower bounds so don't incur the cost of obtaining it. + if ((uint)dimension >= (uint)this.GetMethodTable()->MultiDimensionalArrayRank) throw new IndexOutOfRangeException(); + + Debug.Assert(IsSzArray || Unsafe.Add(ref GetMultiDimensionalArrayBounds(), Rank + dimension) == 0); return 0; } [Intrinsic] public int GetUpperBound(int dimension) { - if (!IsSzArray) - { - int rank = Rank; - if ((uint)dimension >= rank) - throw new IndexOutOfRangeException(); - - ref int bounds = ref GetMultiDimensionalArrayBounds(); - - int length = Unsafe.Add(ref bounds, dimension); - int lowerBound = Unsafe.Add(ref bounds, rank + dimension); - return length + lowerBound - 1; - } - - if (dimension != 0) - throw new IndexOutOfRangeException(); - return Length - 1; + // We don't support non-zero lower bounds so don't incur the cost of obtaining it. + Debug.Assert(GetLowerBound(dimension) == 0); + return GetLength(dimension) - 1; } internal unsafe object? InternalGetValue(nint flattenedIndex) From e975e11cb15472fa7d56ca951d2b3f7dde1e5f9b Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 23 Jun 2025 03:05:21 +0800 Subject: [PATCH 10/39] Fix mono build --- src/mono/System.Private.CoreLib/src/System/Array.Mono.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 c92e0e69b9402a..fa2abf42a567ce 100644 --- a/src/mono/System.Private.CoreLib/src/System/Array.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Array.Mono.cs @@ -103,10 +103,10 @@ public static void Copy(Array sourceArray, Array destinationArray, int length) public static void Copy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length) { - Copy(sourceArray, sourceIndex, destinationArray, destinationIndex, length, false); + CopyImpl(sourceArray, sourceIndex, destinationArray, destinationIndex, length, false); } - private static void Copy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, bool reliable) + private static void CopyImpl(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, bool reliable) { ArgumentNullException.ThrowIfNull(sourceArray); ArgumentNullException.ThrowIfNull(destinationArray); From e7fc11032886bde64839153922ed9e69b89c3e92 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 23 Jun 2025 10:04:03 +0800 Subject: [PATCH 11/39] Fix direction of CanPrimitiveWiden --- .../System.Private.CoreLib/src/System/Array.NativeAot.cs | 2 +- 1 file changed, 1 insertion(+), 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 6daea5fb20422c..e1d2ef33b87ce9 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 @@ -199,7 +199,7 @@ private static unsafe ArrayAssignType CanAssignArrayType(Array sourceArray, Arra if (GetNormalizedIntegralArrayElementType(sourceElementType) == GetNormalizedIntegralArrayElementType(destElementType)) return ArrayAssignType.SimpleCopy; - else if (InvokeUtils.CanPrimitiveWiden(sourceElementType, destElementType)) + else if (InvokeUtils.CanPrimitiveWiden(destElementType, sourceElementType)) return ArrayAssignType.PrimitiveWiden; else return ArrayAssignType.WrongType; From e366c6a22f179679f0372a319b9c122d5cfb966a Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 23 Jun 2025 11:54:48 +0800 Subject: [PATCH 12/39] Share GetLength/GetUpperBound/GetLowerBound --- .../src/System/Array.CoreCLR.cs | 42 +---------------- .../RuntimeHelpers.CoreCLR.cs | 2 +- .../src/System/Array.NativeAot.cs | 42 +++-------------- .../src/System/Array.cs | 45 +++++++++++++++++++ 4 files changed, 55 insertions(+), 76 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 fe013381c4fc31..aa92cda6314828 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs @@ -49,6 +49,8 @@ internal static unsafe object CreateInstanceMDArray(nint typeHandle, uint dwNumA return arr!; } + private static bool SupportsNonZeroLowerBound => true; + private static CorElementType GetNormalizedIntegralArrayElementType(CorElementType elementType) { Debug.Assert(elementType.IsPrimitiveType()); @@ -404,46 +406,6 @@ public int Rank } } - [Intrinsic] - public int GetLength(int dimension) - { - int rank = RuntimeHelpers.GetMultiDimensionalArrayRank(this); - if (rank == 0 && dimension == 0) - return Length; - - if ((uint)dimension >= (uint)rank) - throw new IndexOutOfRangeException(SR.IndexOutOfRange_ArrayRankIndex); - - return Unsafe.Add(ref RuntimeHelpers.GetMultiDimensionalArrayBounds(this), dimension); - } - - [Intrinsic] - public int GetUpperBound(int dimension) - { - int rank = RuntimeHelpers.GetMultiDimensionalArrayRank(this); - if (rank == 0 && dimension == 0) - return Length - 1; - - if ((uint)dimension >= (uint)rank) - throw new IndexOutOfRangeException(SR.IndexOutOfRange_ArrayRankIndex); - - ref int bounds = ref RuntimeHelpers.GetMultiDimensionalArrayBounds(this); - return Unsafe.Add(ref bounds, dimension) + Unsafe.Add(ref bounds, rank + dimension) - 1; - } - - [Intrinsic] - public int GetLowerBound(int dimension) - { - int rank = RuntimeHelpers.GetMultiDimensionalArrayRank(this); - if (rank == 0 && dimension == 0) - return 0; - - if ((uint)dimension >= (uint)rank) - throw new IndexOutOfRangeException(SR.IndexOutOfRange_ArrayRankIndex); - - return Unsafe.Add(ref RuntimeHelpers.GetMultiDimensionalArrayBounds(this), rank + dimension); - } - [MethodImpl(MethodImplOptions.InternalCall)] internal extern CorElementType GetCorElementTypeOfElementType(); 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 1ef6b965b8a0c5..85dfb272b5898c 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 @@ -422,7 +422,7 @@ internal static ref int GetMultiDimensionalArrayBounds(this Array array) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe int GetMultiDimensionalArrayRank(Array array) + internal static unsafe int GetMultiDimensionalArrayRank(this Array array) { int rank = GetMethodTable(array)->MultiDimensionalArrayRank; GC.KeepAlive(array); // Keep MethodTable alive 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 e1d2ef33b87ce9..80f181270beba8 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 @@ -163,6 +163,13 @@ private ref int GetMultiDimensionalArrayBounds() return ref Unsafe.As(ref Unsafe.As(this).Data); } + private unsafe int GetMultiDimensionalArrayRank() + { + return this.GetMethodTable()->MultiDimensionalArrayRank; + } + + private static bool SupportsNonZeroLowerBound => false; + private static unsafe ArrayAssignType CanAssignArrayType(Array sourceArray, Array destinationArray) { MethodTable* sourceElementEEType = sourceArray.ElementMethodTable; @@ -361,19 +368,6 @@ private static unsafe void CopyImplPrimitiveWiden(Array sourceArray, int sourceI } } - [Intrinsic] - public unsafe int GetLength(int dimension) - { - int rank = this.GetMethodTable()->MultiDimensionalArrayRank; - if (rank == 0 && dimension == 0) - return Length; - - if ((uint)dimension >= (uint)rank) - throw new IndexOutOfRangeException(SR.IndexOutOfRange_ArrayRankIndex); - - return Unsafe.Add(ref GetMultiDimensionalArrayBounds(), dimension); - } - public unsafe int Rank { get @@ -426,28 +420,6 @@ internal static unsafe Array NewMultiDimArray(MethodTable* eeType, int* pLengths return ret; } - [Intrinsic] - public unsafe int GetLowerBound(int dimension) - { - if (dimension == 0) - return 0; - - // We don't support non-zero lower bounds so don't incur the cost of obtaining it. - if ((uint)dimension >= (uint)this.GetMethodTable()->MultiDimensionalArrayRank) - throw new IndexOutOfRangeException(); - - Debug.Assert(IsSzArray || Unsafe.Add(ref GetMultiDimensionalArrayBounds(), Rank + dimension) == 0); - return 0; - } - - [Intrinsic] - public int GetUpperBound(int dimension) - { - // We don't support non-zero lower bounds so don't incur the cost of obtaining it. - Debug.Assert(GetLowerBound(dimension) == 0); - return GetLength(dimension) - 1; - } - internal unsafe object? InternalGetValue(nint flattenedIndex) { Debug.Assert((nuint)flattenedIndex < NativeLength); diff --git a/src/libraries/System.Private.CoreLib/src/System/Array.cs b/src/libraries/System.Private.CoreLib/src/System/Array.cs index 0769fb8b9a7593..853e5bb62bf933 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Array.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Array.cs @@ -640,6 +640,51 @@ internal unsafe nint GetFlattenedIndex(ReadOnlySpan indices) return index; } } + + [Intrinsic] + public int GetLength(int dimension) + { + int rank = this.GetMultiDimensionalArrayRank(); + if (rank == 0 && dimension == 0) + return Length; + + if ((uint)dimension >= (uint)rank) + throw new IndexOutOfRangeException(SR.IndexOutOfRange_ArrayRankIndex); + + return Unsafe.Add(ref this.GetMultiDimensionalArrayBounds(), dimension); + } + + [Intrinsic] + public int GetUpperBound(int dimension) + { + int rank = this.GetMultiDimensionalArrayRank(); + if (rank == 0 && dimension == 0) + return Length - 1; + + if ((uint)dimension >= (uint)rank) + throw new IndexOutOfRangeException(SR.IndexOutOfRange_ArrayRankIndex); + + ref int bounds = ref this.GetMultiDimensionalArrayBounds(); + return Unsafe.Add(ref bounds, dimension) + + (SupportsNonZeroLowerBound ? Unsafe.Add(ref bounds, rank + dimension) : 0) + - 1; + } + + [Intrinsic] + public int GetLowerBound(int dimension) + { + int rank = this.GetMultiDimensionalArrayRank(); + if (rank == 0 && dimension == 0) + return 0; + + if ((uint)dimension >= (uint)rank) + throw new IndexOutOfRangeException(SR.IndexOutOfRange_ArrayRankIndex); + + if (SupportsNonZeroLowerBound) + return Unsafe.Add(ref this.GetMultiDimensionalArrayBounds(), rank + dimension); + else + return 0; + } #endif // The various Get values... From bc584d1a760321152260117190b17bf4365405c4 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 23 Jun 2025 16:25:26 +0800 Subject: [PATCH 13/39] Cleanup IsSzArray --- .../src/System/Array.NativeAot.cs | 12 ++---------- 1 file changed, 2 insertions(+), 10 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 80f181270beba8..8bfe5d2f21f53b 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 @@ -41,14 +41,6 @@ public abstract partial class Array : ICollection, IEnumerable, IList, IStructur public long LongLength => (long)NativeLength; - internal unsafe bool IsSzArray - { - get - { - return this.GetMethodTable()->IsSzArray; - } - } - // This is the classlib-provided "get array MethodTable" function that will be invoked whenever the runtime // needs to know the base type of an array. [RuntimeExport("GetSystemArrayEEType")] @@ -157,9 +149,9 @@ public unsafe void Initialize() } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ref int GetMultiDimensionalArrayBounds() + private unsafe ref int GetMultiDimensionalArrayBounds() { - Debug.Assert(!IsSzArray); + Debug.Assert(!this.GetMethodTable()->IsSzArray); return ref Unsafe.As(ref Unsafe.As(this).Data); } From c409556f191a21f3c37fdf6cf68885bdf72031f8 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 23 Jun 2025 16:42:08 +0800 Subject: [PATCH 14/39] Encounter for pointer in IsSystemObject --- .../Runtime.Base/src/System/Runtime/MethodTable.Runtime.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/MethodTable.Runtime.cs b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/MethodTable.Runtime.cs index 1c786946c82bf0..135364f592552e 100644 --- a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/MethodTable.Runtime.cs +++ b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/MethodTable.Runtime.cs @@ -42,7 +42,7 @@ internal static class WellKnownEETypes // This is recognized by the fact that System.Object and interfaces are the only ones without a base type internal static unsafe bool IsSystemObject(MethodTable* pEEType) { - if (pEEType->IsArray) + if (!pEEType->IsCanonical) return false; return (pEEType->NonArrayBaseType == null) && !pEEType->IsInterface; } From 83b6ea8dd4c69f59b1656d1179558474e3b0033f Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 24 Jun 2025 00:30:59 +0800 Subject: [PATCH 15/39] Fix unit tests --- .../src/System/Array.NativeAot.cs | 87 ++++++++++--------- 1 file changed, 45 insertions(+), 42 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 8bfe5d2f21f53b..69cfaacaaabdc7 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 @@ -170,55 +170,59 @@ private static unsafe ArrayAssignType CanAssignArrayType(Array sourceArray, Arra if (sourceElementEEType == destinationElementEEType) // This check kicks for different array kind or dimensions return ArrayAssignType.SimpleCopy; - // Value class boxing - if (sourceElementEEType->IsValueType && !destinationElementEEType->IsValueType) + if (!sourceElementEEType->IsPointer && !sourceElementEEType->IsFunctionPointer + && !destinationElementEEType->IsPointer && !destinationElementEEType->IsFunctionPointer) { - if (RuntimeImports.AreTypesAssignable(sourceElementEEType, destinationElementEEType)) - return ArrayAssignType.BoxValueClassOrPrimitive; - else - return ArrayAssignType.WrongType; - } + // Value class boxing + if (sourceElementEEType->IsValueType && !destinationElementEEType->IsValueType) + { + if (RuntimeImports.AreTypesAssignable(sourceElementEEType, destinationElementEEType)) + return ArrayAssignType.BoxValueClassOrPrimitive; + else + return ArrayAssignType.WrongType; + } - // Value class unboxing. - if (!sourceElementEEType->IsValueType && destinationElementEEType->IsValueType) - { - if (RuntimeImports.AreTypesAssignable(sourceElementEEType, destinationElementEEType)) - return ArrayAssignType.UnboxValueClass; - else if (RuntimeImports.AreTypesAssignable(destinationElementEEType, sourceElementEEType)) // V extends IV. Copying from IV to V, or Object to V. - return ArrayAssignType.UnboxValueClass; - else - return ArrayAssignType.WrongType; - } + // Value class unboxing. + if (!sourceElementEEType->IsValueType && destinationElementEEType->IsValueType) + { + if (RuntimeImports.AreTypesAssignable(sourceElementEEType, destinationElementEEType)) + return ArrayAssignType.UnboxValueClass; + else if (RuntimeImports.AreTypesAssignable(destinationElementEEType, sourceElementEEType)) // V extends IV. Copying from IV to V, or Object to V. + return ArrayAssignType.UnboxValueClass; + else + return ArrayAssignType.WrongType; + } - // Copying primitives from one type to another - if (sourceElementEEType->IsPrimitive && destinationElementEEType->IsPrimitive) - { - EETypeElementType sourceElementType = sourceElementEEType->ElementType; - EETypeElementType destElementType = destinationElementEEType->ElementType; + // Copying primitives from one type to another + if (sourceElementEEType->IsPrimitive && destinationElementEEType->IsPrimitive) + { + EETypeElementType sourceElementType = sourceElementEEType->ElementType; + EETypeElementType destElementType = destinationElementEEType->ElementType; - if (GetNormalizedIntegralArrayElementType(sourceElementType) == GetNormalizedIntegralArrayElementType(destElementType)) - return ArrayAssignType.SimpleCopy; - else if (InvokeUtils.CanPrimitiveWiden(destElementType, sourceElementType)) - return ArrayAssignType.PrimitiveWiden; - else - return ArrayAssignType.WrongType; - } + if (GetNormalizedIntegralArrayElementType(sourceElementType) == GetNormalizedIntegralArrayElementType(destElementType)) + return ArrayAssignType.SimpleCopy; + else if (InvokeUtils.CanPrimitiveWiden(destElementType, sourceElementType)) + return ArrayAssignType.PrimitiveWiden; + else + return ArrayAssignType.WrongType; + } - // src Object extends dest - if (RuntimeImports.AreTypesAssignable(sourceElementEEType, destinationElementEEType)) - return ArrayAssignType.SimpleCopy; + // src Object extends dest + if (RuntimeImports.AreTypesAssignable(sourceElementEEType, destinationElementEEType)) + return ArrayAssignType.SimpleCopy; - // dest Object extends src - if (RuntimeImports.AreTypesAssignable(destinationElementEEType, sourceElementEEType)) - return ArrayAssignType.MustCast; + // dest Object extends src + if (RuntimeImports.AreTypesAssignable(destinationElementEEType, sourceElementEEType)) + return ArrayAssignType.MustCast; - // class X extends/implements src and implements dest. - if (destinationElementEEType->IsInterface) - return ArrayAssignType.MustCast; + // class X extends/implements src and implements dest. + if (destinationElementEEType->IsInterface) + return ArrayAssignType.MustCast; - // class X implements src and extends/implements dest - if (sourceElementEEType->IsInterface) - return ArrayAssignType.MustCast; + // class X implements src and extends/implements dest + if (sourceElementEEType->IsInterface) + return ArrayAssignType.MustCast; + } // Compatible pointers if (sourceElementEEType->IsPointer && destinationElementEEType->IsPointer @@ -347,7 +351,6 @@ private static unsafe void CopyImplPrimitiveWiden(Array sourceArray, int sourceI nuint srcElementSize = sourceArray.ElementSize; nuint destElementSize = destinationArray.ElementSize; - Debug.Assert(srcElementSize != destElementSize); ref byte srcData = ref Unsafe.AddByteOffset(ref MemoryMarshal.GetArrayDataReference(sourceArray), (nuint)sourceIndex * srcElementSize); ref byte dstData = ref Unsafe.AddByteOffset(ref MemoryMarshal.GetArrayDataReference(destinationArray), (nuint)destinationIndex * destElementSize); From 072519a756c6cdae7e8accf5ad39739c3f95b18a Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 30 Jun 2025 17:28:23 +0800 Subject: [PATCH 16/39] Cleanup exception throwing. --- .../src/System/Array.cs | 42 +++++++++---------- .../src/System/ThrowHelper.cs | 6 --- 2 files changed, 19 insertions(+), 29 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Array.cs b/src/libraries/System.Private.CoreLib/src/System/Array.cs index 853e5bb62bf933..8d50f571edb36a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Array.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Array.cs @@ -367,28 +367,26 @@ public static void ConstrainedCopy(Array sourceArray, int sourceIndex, Array des // 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->IsSzArray && - (uint)length <= sourceArray.NativeLength && - (uint)length <= destinationArray.NativeLength) + if (sourceArray != null && destinationArray != null) { - nuint byteCount = (uint)length * (nuint)pMT->ComponentSize; - ref byte src = ref Unsafe.As(sourceArray).Data; - ref byte dst = ref Unsafe.As(destinationArray).Data; + MethodTable* pMT = RuntimeHelpers.GetMethodTable(sourceArray); + if (MethodTable.AreSameType(pMT, RuntimeHelpers.GetMethodTable(destinationArray)) && + pMT->IsSzArray && + (uint)length <= sourceArray.NativeLength && + (uint)length <= destinationArray.NativeLength) + { + nuint byteCount = (uint)length * (nuint)pMT->ComponentSize; + ref byte src = ref Unsafe.As(sourceArray).Data; + ref byte dst = ref Unsafe.As(destinationArray).Data; - if (pMT->ContainsGCPointers) - Buffer.BulkMoveWithWriteBarrier(ref dst, ref src, byteCount); - else - SpanHelpers.Memmove(ref dst, ref src, byteCount); + if (pMT->ContainsGCPointers) + Buffer.BulkMoveWithWriteBarrier(ref dst, ref src, byteCount); + else + SpanHelpers.Memmove(ref dst, ref src, byteCount); - // GC.KeepAlive(sourceArray) not required. pMT kept alive via sourceArray - return; + // GC.KeepAlive(sourceArray) not required. pMT kept alive via sourceArray + return; + } } // Less common @@ -429,10 +427,8 @@ public static unsafe void Copy(Array sourceArray, int sourceIndex, Array destina private static unsafe void CopyImpl(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, bool reliable) { - if (sourceArray == null) - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.sourceArray); - if (destinationArray == null) - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.destinationArray); + ArgumentNullException.ThrowIfNull(sourceArray); + ArgumentNullException.ThrowIfNull(destinationArray); if (sourceArray.GetType() != destinationArray.GetType() && sourceArray.Rank != destinationArray.Rank) throw new RankException(SR.Rank_MustMatch); diff --git a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs index 192346f01dc510..90c8b2163d91f6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs @@ -990,12 +990,8 @@ private static string GetArgumentName(ExceptionArgument argument) return "type"; case ExceptionArgument.sourceIndex: return "sourceIndex"; - case ExceptionArgument.sourceArray: - return "sourceArray"; case ExceptionArgument.destinationIndex: return "destinationIndex"; - case ExceptionArgument.destinationArray: - return "destinationArray"; case ExceptionArgument.pHandle: return "pHandle"; case ExceptionArgument.handle: @@ -1317,9 +1313,7 @@ internal enum ExceptionArgument timeout, type, sourceIndex, - sourceArray, destinationIndex, - destinationArray, pHandle, handle, other, From 9c6ecaadf68829bbdb17adb60719ea16e2a3b4b7 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 30 Jun 2025 17:55:51 +0800 Subject: [PATCH 17/39] Invert condition for pointer check --- .../src/System/Array.CoreCLR.cs | 102 +++++++++--------- .../src/System/Array.NativeAot.cs | 97 ++++++++--------- 2 files changed, 100 insertions(+), 99 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 aa92cda6314828..ab6b0d5b5c7680 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs @@ -72,70 +72,70 @@ 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; - if (!srcTH.IsTypeDesc && !destTH.IsTypeDesc) + if (srcTH.IsTypeDesc || destTH.IsTypeDesc) { - MethodTable* pMTsrc = srcTH.AsMethodTable(); - MethodTable* pMTdest = destTH.AsMethodTable(); - - // Value class boxing - if (pMTsrc->IsValueType && !pMTdest->IsValueType) - { - if (srcTH.CanCastTo(destTH)) - return ArrayAssignType.BoxValueClassOrPrimitive; - else - return ArrayAssignType.WrongType; - } - - // 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; - } - - // 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; - } + // Only pointers are valid for TypeDesc in array element - // src Object extends dest + // Compatible pointers if (srcTH.CanCastTo(destTH)) return ArrayAssignType.SimpleCopy; + else + return ArrayAssignType.WrongType; + } - // dest Object extends src - if (destTH.CanCastTo(srcTH)) - return ArrayAssignType.MustCast; + MethodTable* pMTsrc = srcTH.AsMethodTable(); + MethodTable* pMTdest = destTH.AsMethodTable(); - // class X extends/implements src and implements dest. - if (pMTdest->IsInterface) - return ArrayAssignType.MustCast; + // Value class boxing + if (pMTsrc->IsValueType && !pMTdest->IsValueType) + { + if (srcTH.CanCastTo(destTH)) + return ArrayAssignType.BoxValueClassOrPrimitive; + else + return ArrayAssignType.WrongType; + } - // class X implements src and extends/implements dest - if (pMTsrc->IsInterface) - return ArrayAssignType.MustCast; + // 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; } - else + + // Copying primitives from one type to another + if (pMTsrc->IsPrimitive && pMTdest->IsPrimitive) { - // Only pointers are valid for TypeDesc in array element + CorElementType srcElType = pMTsrc->GetPrimitiveCorElementType(); + CorElementType destElType = pMTdest->GetPrimitiveCorElementType(); - // Compatible pointers - if (srcTH.CanCastTo(destTH)) + 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 (pMTdest->IsInterface) + return ArrayAssignType.MustCast; + + // class X implements src and extends/implements dest + if (pMTsrc->IsInterface) + return ArrayAssignType.MustCast; + return ArrayAssignType.WrongType; } 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 69cfaacaaabdc7..5dbd957d454482 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 @@ -170,64 +170,65 @@ private static unsafe ArrayAssignType CanAssignArrayType(Array sourceArray, Arra if (sourceElementEEType == destinationElementEEType) // This check kicks for different array kind or dimensions return ArrayAssignType.SimpleCopy; - if (!sourceElementEEType->IsPointer && !sourceElementEEType->IsFunctionPointer - && !destinationElementEEType->IsPointer && !destinationElementEEType->IsFunctionPointer) + if (sourceElementEEType->IsPointer || sourceElementEEType->IsFunctionPointer + || destinationElementEEType->IsPointer || destinationElementEEType->IsFunctionPointer) { - // Value class boxing - if (sourceElementEEType->IsValueType && !destinationElementEEType->IsValueType) - { - if (RuntimeImports.AreTypesAssignable(sourceElementEEType, destinationElementEEType)) - return ArrayAssignType.BoxValueClassOrPrimitive; - else - return ArrayAssignType.WrongType; - } + // Compatible pointers + if (RuntimeImports.AreTypesAssignable(sourceElementEEType, destinationElementEEType)) + return ArrayAssignType.SimpleCopy; + else + return ArrayAssignType.WrongType; + } - // Value class unboxing. - if (!sourceElementEEType->IsValueType && destinationElementEEType->IsValueType) - { - if (RuntimeImports.AreTypesAssignable(sourceElementEEType, destinationElementEEType)) - return ArrayAssignType.UnboxValueClass; - else if (RuntimeImports.AreTypesAssignable(destinationElementEEType, sourceElementEEType)) // V extends IV. Copying from IV to V, or Object to V. - return ArrayAssignType.UnboxValueClass; - else - return ArrayAssignType.WrongType; - } + // Value class boxing + if (sourceElementEEType->IsValueType && !destinationElementEEType->IsValueType) + { + if (RuntimeImports.AreTypesAssignable(sourceElementEEType, destinationElementEEType)) + return ArrayAssignType.BoxValueClassOrPrimitive; + else + return ArrayAssignType.WrongType; + } - // Copying primitives from one type to another - if (sourceElementEEType->IsPrimitive && destinationElementEEType->IsPrimitive) - { - EETypeElementType sourceElementType = sourceElementEEType->ElementType; - EETypeElementType destElementType = destinationElementEEType->ElementType; + // Value class unboxing. + if (!sourceElementEEType->IsValueType && destinationElementEEType->IsValueType) + { + if (RuntimeImports.AreTypesAssignable(sourceElementEEType, destinationElementEEType)) + return ArrayAssignType.UnboxValueClass; + else if (RuntimeImports.AreTypesAssignable(destinationElementEEType, sourceElementEEType)) // V extends IV. Copying from IV to V, or Object to V. + return ArrayAssignType.UnboxValueClass; + else + return ArrayAssignType.WrongType; + } - if (GetNormalizedIntegralArrayElementType(sourceElementType) == GetNormalizedIntegralArrayElementType(destElementType)) - return ArrayAssignType.SimpleCopy; - else if (InvokeUtils.CanPrimitiveWiden(destElementType, sourceElementType)) - return ArrayAssignType.PrimitiveWiden; - else - return ArrayAssignType.WrongType; - } + // Copying primitives from one type to another + if (sourceElementEEType->IsPrimitive && destinationElementEEType->IsPrimitive) + { + EETypeElementType sourceElementType = sourceElementEEType->ElementType; + EETypeElementType destElementType = destinationElementEEType->ElementType; - // src Object extends dest - if (RuntimeImports.AreTypesAssignable(sourceElementEEType, destinationElementEEType)) + if (GetNormalizedIntegralArrayElementType(sourceElementType) == GetNormalizedIntegralArrayElementType(destElementType)) return ArrayAssignType.SimpleCopy; + else if (InvokeUtils.CanPrimitiveWiden(destElementType, sourceElementType)) + return ArrayAssignType.PrimitiveWiden; + else + return ArrayAssignType.WrongType; + } - // dest Object extends src - if (RuntimeImports.AreTypesAssignable(destinationElementEEType, sourceElementEEType)) - return ArrayAssignType.MustCast; + // src Object extends dest + if (RuntimeImports.AreTypesAssignable(sourceElementEEType, destinationElementEEType)) + return ArrayAssignType.SimpleCopy; - // class X extends/implements src and implements dest. - if (destinationElementEEType->IsInterface) - return ArrayAssignType.MustCast; + // dest Object extends src + if (RuntimeImports.AreTypesAssignable(destinationElementEEType, sourceElementEEType)) + return ArrayAssignType.MustCast; - // class X implements src and extends/implements dest - if (sourceElementEEType->IsInterface) - return ArrayAssignType.MustCast; - } + // class X extends/implements src and implements dest. + if (destinationElementEEType->IsInterface) + return ArrayAssignType.MustCast; - // Compatible pointers - if (sourceElementEEType->IsPointer && destinationElementEEType->IsPointer - && RuntimeImports.AreTypesAssignable(sourceElementEEType, destinationElementEEType)) - return ArrayAssignType.SimpleCopy; + // class X implements src and extends/implements dest + if (sourceElementEEType->IsInterface) + return ArrayAssignType.MustCast; return ArrayAssignType.WrongType; } From 0766d2ca3f00aa0855a766df4d7093754a111bf6 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 1 Jul 2025 00:14:10 +0800 Subject: [PATCH 18/39] Remove redundant throwing condition --- src/libraries/System.Private.CoreLib/src/System/Array.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Array.cs b/src/libraries/System.Private.CoreLib/src/System/Array.cs index 8d50f571edb36a..46256b680ee23c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Array.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Array.cs @@ -437,12 +437,10 @@ private static unsafe void CopyImpl(Array sourceArray, int sourceIndex, Array de int srcLB = sourceArray.GetLowerBound(0); ArgumentOutOfRangeException.ThrowIfLessThan(sourceIndex, srcLB); - ArgumentOutOfRangeException.ThrowIfNegative(sourceIndex - srcLB, nameof(sourceIndex)); sourceIndex -= srcLB; int dstLB = destinationArray.GetLowerBound(0); ArgumentOutOfRangeException.ThrowIfLessThan(destinationIndex, dstLB); - ArgumentOutOfRangeException.ThrowIfNegative(destinationIndex - dstLB, nameof(destinationIndex)); destinationIndex -= dstLB; if ((uint)(sourceIndex + length) > sourceArray.NativeLength) From 9344eecbd36126389dd9df822c61b22b111ccfd8 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 1 Jul 2025 00:19:59 +0800 Subject: [PATCH 19/39] Fix nullability --- src/libraries/System.Private.CoreLib/src/System/Array.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Array.cs b/src/libraries/System.Private.CoreLib/src/System/Array.cs index 46256b680ee23c..0cc61a94651018 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Array.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Array.cs @@ -390,7 +390,7 @@ public static unsafe void Copy(Array sourceArray, Array destinationArray, int le } // Less common - CopyImpl(sourceArray, sourceArray.GetLowerBound(0), destinationArray, destinationArray.GetLowerBound(0), length, reliable: false); + CopyImpl(sourceArray, sourceArray?.GetLowerBound(0) ?? 0, destinationArray, destinationArray?.GetLowerBound(0) ?? 0, length, reliable: false); } // Copies length elements from sourceArray, starting at sourceIndex, to @@ -422,10 +422,10 @@ public static unsafe void Copy(Array sourceArray, int sourceIndex, Array destina } // Less common - CopyImpl(sourceArray!, sourceIndex, destinationArray!, destinationIndex, length, reliable: false); + CopyImpl(sourceArray, sourceIndex, destinationArray, destinationIndex, length, reliable: false); } - private static unsafe void CopyImpl(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) { ArgumentNullException.ThrowIfNull(sourceArray); ArgumentNullException.ThrowIfNull(destinationArray); From fb403f45def5ba41532da2e47fffe12c088ac551 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 1 Jul 2025 00:24:26 +0800 Subject: [PATCH 20/39] Reduce overhead of ArrayRank --- .../nativeaot/Common/src/Internal/Runtime/MethodTable.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs b/src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs index e5e1e62e35ffab..e4eb2775977bf2 100644 --- a/src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs +++ b/src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs @@ -408,12 +408,12 @@ internal int ArrayRank { Debug.Assert(this.IsArray); - int boundsSize = (int)this.ParameterizedTypeShape - SZARRAY_BASE_SIZE; + int boundsSize = (int)this.BaseSize - SZARRAY_BASE_SIZE; if (boundsSize > 0) { // Multidim array case: Base size includes space for two Int32s // (upper and lower bound) per each dimension of the array. - return boundsSize / (2 * sizeof(int)); + return (int)((uint)boundsSize / (uint)(2 * sizeof(int))); } return 1; } @@ -426,10 +426,10 @@ internal int MultiDimensionalArrayRank { Debug.Assert(this.IsArray); - int boundsSize = (int)this.ParameterizedTypeShape - SZARRAY_BASE_SIZE; + int boundsSize = (int)this.BaseSize - SZARRAY_BASE_SIZE; // Multidim array case: Base size includes space for two Int32s // (upper and lower bound) per each dimension of the array. - return boundsSize / (2 * sizeof(int)); + return (int)((uint)boundsSize / (uint)(2 * sizeof(int))); } } From e7e11b90b5bf219c01c5a0a19be21730327be568 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 1 Jul 2025 00:29:57 +0800 Subject: [PATCH 21/39] Cleanup --- src/libraries/System.Private.CoreLib/src/System/Array.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Array.cs b/src/libraries/System.Private.CoreLib/src/System/Array.cs index 0cc61a94651018..4005a03ad52d43 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Array.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Array.cs @@ -448,7 +448,7 @@ private static unsafe void CopyImpl(Array? sourceArray, int sourceIndex, Array? if ((uint)(destinationIndex + length) > destinationArray.NativeLength) throw new ArgumentException(SR.Arg_LongerThanDestArray, nameof(destinationArray)); - ArrayAssignType assignType = ArrayAssignType.WrongType; + ArrayAssignType assignType; if (sourceArray.GetType() == destinationArray.GetType() || (assignType = CanAssignArrayType(sourceArray, destinationArray)) == ArrayAssignType.SimpleCopy) From 3e6078090085a4c0008be7ef4fbe79f807cc294e Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 1 Jul 2025 00:34:19 +0800 Subject: [PATCH 22/39] Add test for copying value types --- .../tests/System.Runtime.Tests/System/ArrayTests.cs | 3 +++ 1 file changed, 3 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 98c39a757fd280..e3285fd1653f4d 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 @@ -1586,6 +1586,9 @@ public static IEnumerable Copy_SourceAndDestinationNeverConvertible_Te // ValueType[] -> InterfaceNotImplementedByValueType[] never works yield return new object[] { new StructWithNonGenericInterface1[1], new NonGenericInterface2[1] }; + + // ValueType[] -> ValueType[] never works + yield return new object[] { new StructWithNonGenericInterface1[1], new StructWithNonGenericInterface1_2[1] }; } [Theory] From 36cf2031db4b926ee20962b8a9471f8ccd60d72f Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 1 Jul 2025 00:36:08 +0800 Subject: [PATCH 23/39] Revert "Remove redundant throwing condition" This reverts commit 0766d2ca3f00aa0855a766df4d7093754a111bf6. --- src/libraries/System.Private.CoreLib/src/System/Array.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/Array.cs b/src/libraries/System.Private.CoreLib/src/System/Array.cs index 4005a03ad52d43..be6d2ba6a83466 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Array.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Array.cs @@ -437,10 +437,12 @@ private static unsafe void CopyImpl(Array? sourceArray, int sourceIndex, Array? int srcLB = sourceArray.GetLowerBound(0); ArgumentOutOfRangeException.ThrowIfLessThan(sourceIndex, srcLB); + ArgumentOutOfRangeException.ThrowIfNegative(sourceIndex - srcLB, nameof(sourceIndex)); sourceIndex -= srcLB; int dstLB = destinationArray.GetLowerBound(0); ArgumentOutOfRangeException.ThrowIfLessThan(destinationIndex, dstLB); + ArgumentOutOfRangeException.ThrowIfNegative(destinationIndex - dstLB, nameof(destinationIndex)); destinationIndex -= dstLB; if ((uint)(sourceIndex + length) > sourceArray.NativeLength) From 337098887fe1d514ed54b4323b2f769d1fb5326c Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 1 Jul 2025 00:38:54 +0800 Subject: [PATCH 24/39] Add back overflow check --- src/libraries/System.Private.CoreLib/src/System/Array.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Array.cs b/src/libraries/System.Private.CoreLib/src/System/Array.cs index be6d2ba6a83466..5bfe6035ca85da 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Array.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Array.cs @@ -437,12 +437,14 @@ private static unsafe void CopyImpl(Array? sourceArray, int sourceIndex, Array? int srcLB = sourceArray.GetLowerBound(0); ArgumentOutOfRangeException.ThrowIfLessThan(sourceIndex, srcLB); - ArgumentOutOfRangeException.ThrowIfNegative(sourceIndex - srcLB, nameof(sourceIndex)); + if (sourceIndex - srcLB < 0) + throw new ArgumentException(SR.Arg_LongerThanSrcArray, nameof(sourceArray)); sourceIndex -= srcLB; int dstLB = destinationArray.GetLowerBound(0); ArgumentOutOfRangeException.ThrowIfLessThan(destinationIndex, dstLB); - ArgumentOutOfRangeException.ThrowIfNegative(destinationIndex - dstLB, nameof(destinationIndex)); + if (destinationIndex - dstLB < 0) + throw new ArgumentException(SR.Arg_LongerThanDestArray, nameof(destinationArray)); destinationIndex -= dstLB; if ((uint)(sourceIndex + length) > sourceArray.NativeLength) From 1c0901a6cada138118bbcfa5409536747b6a4f23 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 1 Jul 2025 10:33:52 +0800 Subject: [PATCH 25/39] Update array type checking --- .../System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 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 ec6baa097bd01c..91582d975d9ee0 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 @@ -766,7 +766,6 @@ internal unsafe struct MethodTable private const uint enum_flag_Category_TruePrimitive = 0x00070000; // sub-category of ValueType, Primitive (ELEMENT_TYPE_I, etc.) private const uint enum_flag_Category_Array = 0x00080000; private const uint enum_flag_Category_Array_Mask = 0x000C0000; - private const uint enum_flag_Category_IfArrayThenSzArray = 0x00020000; // sub-category of Array private const uint enum_flag_Category_ValueType_Mask = 0x000C0000; private const uint enum_flag_Category_Interface = 0x000C0000; // Types that require non-trivial interface cast have this bit set in the category @@ -825,10 +824,11 @@ internal unsafe struct MethodTable public bool IsSzArray { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { Debug.Assert(IsArray); - return (Flags & enum_flag_Category_IfArrayThenSzArray) != 0; + return BaseSize == (uint)(3 * sizeof(IntPtr)); } } From 6044d8a08be0ece06c08389d5568ddb5495a8d4f Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 1 Jul 2025 11:50:33 +0800 Subject: [PATCH 26/39] Simplify bound checking --- .../System.Private.CoreLib/src/System/Array.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Array.cs b/src/libraries/System.Private.CoreLib/src/System/Array.cs index 5bfe6035ca85da..2b70ab8ba56874 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Array.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Array.cs @@ -437,19 +437,14 @@ private static unsafe void CopyImpl(Array? sourceArray, int sourceIndex, Array? int srcLB = sourceArray.GetLowerBound(0); ArgumentOutOfRangeException.ThrowIfLessThan(sourceIndex, srcLB); - if (sourceIndex - srcLB < 0) - throw new ArgumentException(SR.Arg_LongerThanSrcArray, nameof(sourceArray)); sourceIndex -= srcLB; + if ((sourceIndex < 0) || ((uint)(sourceIndex + length) > sourceArray.NativeLength)) + throw new ArgumentException(SR.Arg_LongerThanSrcArray, nameof(sourceArray)); int dstLB = destinationArray.GetLowerBound(0); ArgumentOutOfRangeException.ThrowIfLessThan(destinationIndex, dstLB); - if (destinationIndex - dstLB < 0) - throw new ArgumentException(SR.Arg_LongerThanDestArray, nameof(destinationArray)); destinationIndex -= dstLB; - - if ((uint)(sourceIndex + length) > sourceArray.NativeLength) - throw new ArgumentException(SR.Arg_LongerThanSrcArray, nameof(sourceArray)); - if ((uint)(destinationIndex + length) > destinationArray.NativeLength) + if ((destinationIndex < 0) || ((uint)(destinationIndex + length) > destinationArray.NativeLength)) throw new ArgumentException(SR.Arg_LongerThanDestArray, nameof(destinationArray)); ArrayAssignType assignType; From 1b9a1d858611db396cc79e60df28bebccd8c9fdf Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 1 Jul 2025 11:51:21 +0800 Subject: [PATCH 27/39] Cleanup CER comment --- src/libraries/System.Private.CoreLib/src/System/Array.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Array.cs b/src/libraries/System.Private.CoreLib/src/System/Array.cs index 2b70ab8ba56874..f81a1a7957e09f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Array.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Array.cs @@ -478,9 +478,8 @@ private static unsafe void CopyImpl(Array? sourceArray, int sourceIndex, Array? } // 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. + // instance, or if the reliable flag is true, it will either always + // succeed or always throw an exception with no side effects. private static void CopySlow(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, ArrayAssignType assignType) { Debug.Assert(sourceArray.Rank == destinationArray.Rank); From d8ce97edc7f135218d626fd5edfa87d0ceef8a98 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 1 Jul 2025 12:00:11 +0800 Subject: [PATCH 28/39] Add test for nullable case --- .../tests/System.Runtime.Tests/System/ArrayTests.cs | 3 +++ 1 file changed, 3 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 e3285fd1653f4d..8290a03428897d 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 @@ -1589,6 +1589,9 @@ public static IEnumerable Copy_SourceAndDestinationNeverConvertible_Te // ValueType[] -> ValueType[] never works yield return new object[] { new StructWithNonGenericInterface1[1], new StructWithNonGenericInterface1_2[1] }; + + // ValueType[] -> Nullable[] never works + yield return new object[] { new int[1], new int?[1] }; } [Theory] From 5fa7174c11f0507cd2d4c1fb2a6cf53f33df00e7 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 1 Jul 2025 13:18:41 +0800 Subject: [PATCH 29/39] Handle nullable conversion --- .../System.Private.CoreLib/src/System/Array.NativeAot.cs | 8 ++++++++ 1 file changed, 8 insertions(+) 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 5dbd957d454482..8e98730dbd0151 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 @@ -180,6 +180,14 @@ private static unsafe ArrayAssignType CanAssignArrayType(Array sourceArray, Arra return ArrayAssignType.WrongType; } + // Different value types + if (sourceElementEEType->IsValueType && destinationElementEEType->IsValueType) + { + // Different from CanCastTo in coreclr, AreTypesAssignable also allows T -> Nullable conversion. + // Kick for this path early. + return ArrayAssignType.WrongType; + } + // Value class boxing if (sourceElementEEType->IsValueType && !destinationElementEEType->IsValueType) { From 67f6774c3efda42d205be7c7445acf758786136a Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 1 Jul 2025 13:20:28 +0800 Subject: [PATCH 30/39] Move comment --- src/libraries/System.Private.CoreLib/src/System/Array.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Array.cs b/src/libraries/System.Private.CoreLib/src/System/Array.cs index f81a1a7957e09f..edd8999ff9f05b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Array.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Array.cs @@ -425,6 +425,9 @@ public static unsafe void Copy(Array sourceArray, int sourceIndex, Array destina CopyImpl(sourceArray, sourceIndex, destinationArray, destinationIndex, length, reliable: false); } + // Reliability-wise, this method will either possibly corrupt your + // instance, 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 CopyImpl(Array? sourceArray, int sourceIndex, Array? destinationArray, int destinationIndex, int length, bool reliable) { ArgumentNullException.ThrowIfNull(sourceArray); @@ -477,9 +480,6 @@ private static unsafe void CopyImpl(Array? sourceArray, int sourceIndex, Array? CopySlow(sourceArray, sourceIndex, destinationArray, destinationIndex, length, assignType); } - // Reliability-wise, this method will either possibly corrupt your - // instance, or if the reliable flag is true, it will either always - // succeed or always throw an exception with no side effects. private static void CopySlow(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, ArrayAssignType assignType) { Debug.Assert(sourceArray.Rank == destinationArray.Rank); From 11dcdca93d4d485b477e1460d17741fc6305f032 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 1 Jul 2025 14:20:24 +0800 Subject: [PATCH 31/39] Move copy cases to shared --- .../src/System/Array.CoreCLR.cs | 115 ++-------------- .../src/System/Array.NativeAot.cs | 118 ----------------- .../src/System/Array.cs | 125 +++++++++++++++++- 3 files changed, 134 insertions(+), 224 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 ab6b0d5b5c7680..3c7c5f6af71202 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs @@ -139,111 +139,6 @@ private static unsafe ArrayAssignType CanAssignArrayType(Array sourceArray, Arra 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) - { - 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) - { - CastHelpers.Unbox_Nullable(ref dest, pDestMT, obj); - } - else if (obj is null || RuntimeHelpers.GetMethodTable(obj) != pDestMT) - { - throw new InvalidCastException(SR.InvalidCast_DownCastArrayElement); - } - else if (pDestMT->ContainsGCPointers) - { - Buffer.BulkMoveWithWriteBarrier(ref dest, ref obj.GetRawData(), destSize); - } - else - { - SpanHelpers.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++) - { - InvokeUtils.PrimitiveWiden(ref srcData, ref data, srcElType, destElType); - srcData = ref Unsafe.AddByteOffset(ref srcData, srcElSize); - data = ref Unsafe.AddByteOffset(ref data, destElSize); - } - } - internal unsafe object? InternalGetValue(nint flattenedIndex) { MethodTable* pMethodTable = RuntimeHelpers.GetMethodTable(this); @@ -409,6 +304,16 @@ public int Rank [MethodImpl(MethodImplOptions.InternalCall)] internal extern CorElementType GetCorElementTypeOfElementType(); + private unsafe MethodTable* ElementMethodTable + { + get + { + TypeHandle elementTH = RuntimeHelpers.GetMethodTable(this)->GetArrayElementTypeHandle(); + Debug.Assert(!elementTH.IsTypeDesc); + return elementTH.AsMethodTable(); + } + } + private unsafe bool IsValueOfElementType(object value) { MethodTable* thisMT = RuntimeHelpers.GetMethodTable(this); 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 8e98730dbd0151..e9a1b98164fc09 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 @@ -254,124 +254,6 @@ private static EETypeElementType GetNormalizedIntegralArrayElementType(EETypeEle return (EETypeElementType)((int)elementType - shift); } - // - // Array.CopyImpl case: Gc-ref array to gc-ref array copy. - // - private static unsafe void CopyImplCastCheckEachElement(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length) - { - // For mismatched array types, the desktop Array.Copy has a policy that determines whether to throw an ArrayTypeMismatch without any attempt to copy - // or to throw an InvalidCastException in the middle of a copy. This code replicates that policy. - MethodTable* sourceElementEEType = sourceArray.ElementMethodTable; - MethodTable* destinationElementEEType = destinationArray.ElementMethodTable; - - Debug.Assert(!sourceElementEEType->IsValueType && !sourceElementEEType->IsPointer && !sourceElementEEType->IsFunctionPointer); - Debug.Assert(!destinationElementEEType->IsValueType && !destinationElementEEType->IsPointer && !destinationElementEEType->IsFunctionPointer); - Debug.Assert(sourceElementEEType != destinationElementEEType); - - ref object? refDestinationArray = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(destinationArray)); - ref object? refSourceArray = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(sourceArray)); - for (int i = 0; i < length; i++) - { - object? value = Unsafe.Add(ref refSourceArray, sourceIndex + i); - if (value != null && TypeCast.IsInstanceOfAny(destinationElementEEType, value) == null) - throw new InvalidCastException(SR.InvalidCast_DownCastArrayElement); - Unsafe.Add(ref refDestinationArray, destinationIndex + i) = value; - } - } - - // - // Array.CopyImpl case: Value-type array to Object[] or interface array copy. - // - private static unsafe void CopyImplBoxEachElement(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length) - { - Debug.Assert(sourceArray.ElementMethodTable->IsValueType); - Debug.Assert(!destinationArray.ElementMethodTable->IsValueType && !destinationArray.ElementMethodTable->IsPointer && !destinationArray.ElementMethodTable->IsFunctionPointer); - - // Caller has already validated this. - Debug.Assert(RuntimeImports.AreTypesAssignable(sourceArray.ElementMethodTable, destinationArray.ElementMethodTable)); - - MethodTable* sourceElementEEType = sourceArray.ElementMethodTable; - nuint sourceElementSize = sourceArray.ElementSize; - - fixed (byte* pSourceArray = &MemoryMarshal.GetArrayDataReference(sourceArray)) - { - byte* pElement = pSourceArray + (nuint)sourceIndex * sourceElementSize; - ref object refDestinationArray = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(destinationArray)); - for (int i = 0; i < length; i++) - { - object boxedValue = RuntimeExports.RhBox(sourceElementEEType, ref *pElement); - Unsafe.Add(ref refDestinationArray, destinationIndex + i) = boxedValue; - pElement += sourceElementSize; - } - } - } - - // - // Array.CopyImpl case: Object[] or interface array to value-type array copy. - // - private static unsafe void CopyImplUnBoxEachElement(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length) - { - Debug.Assert(!sourceArray.ElementMethodTable->IsValueType && !sourceArray.ElementMethodTable->IsPointer && !sourceArray.ElementMethodTable->IsFunctionPointer); - Debug.Assert(destinationArray.ElementMethodTable->IsValueType); - - MethodTable* destinationElementEEType = destinationArray.ElementMethodTable; - nuint destinationElementSize = destinationArray.ElementSize; - bool isNullable = destinationElementEEType->IsNullable; - - fixed (byte* pDestinationArray = &MemoryMarshal.GetArrayDataReference(destinationArray)) - { - ref object refSourceArray = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(sourceArray)); - byte* pElement = pDestinationArray + (nuint)destinationIndex * destinationElementSize; - - for (int i = 0; i < length; i++) - { - object boxedValue = Unsafe.Add(ref refSourceArray, sourceIndex + i); - if (boxedValue == null) - { - if (!isNullable) - throw new InvalidCastException(SR.InvalidCast_DownCastArrayElement); - } - else - { - MethodTable* eeType = boxedValue.GetMethodTable(); - if (!(RuntimeImports.AreTypesAssignable(eeType, destinationElementEEType))) - throw new InvalidCastException(SR.InvalidCast_DownCastArrayElement); - } - - RuntimeImports.RhUnbox(boxedValue, ref *pElement, destinationElementEEType); - pElement += destinationElementSize; - } - } - } - - // - // Array.CopyImpl case: Primitive types that have a widening conversion - // - private static unsafe void CopyImplPrimitiveWiden(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length) - { - MethodTable* sourceElementEEType = sourceArray.ElementMethodTable; - MethodTable* destinationElementEEType = destinationArray.ElementMethodTable; - - Debug.Assert(sourceElementEEType->IsPrimitive && destinationElementEEType->IsPrimitive); // Caller has already validated this. - - EETypeElementType sourceElementType = sourceElementEEType->ElementType; - EETypeElementType destElementType = destinationElementEEType->ElementType; - Debug.Assert(InvokeUtils.CanPrimitiveWiden(destElementType, sourceElementType)); - - nuint srcElementSize = sourceArray.ElementSize; - nuint destElementSize = destinationArray.ElementSize; - - ref byte srcData = ref Unsafe.AddByteOffset(ref MemoryMarshal.GetArrayDataReference(sourceArray), (nuint)sourceIndex * srcElementSize); - ref byte dstData = ref Unsafe.AddByteOffset(ref MemoryMarshal.GetArrayDataReference(destinationArray), (nuint)destinationIndex * destElementSize); - - for (int i = 0; i < length; i++) - { - InvokeUtils.PrimitiveWiden(destElementType, sourceElementType, ref dstData, ref srcData); - srcData = ref Unsafe.AddByteOffset(ref srcData, srcElementSize); - dstData = ref Unsafe.AddByteOffset(ref dstData, destElementSize); - } - } - public unsafe int Rank { get diff --git a/src/libraries/System.Private.CoreLib/src/System/Array.cs b/src/libraries/System.Private.CoreLib/src/System/Array.cs index edd8999ff9f05b..40c69980b3fd83 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Array.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Array.cs @@ -8,6 +8,7 @@ using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Reflection; +using System.Runtime; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Internal.Runtime; @@ -524,6 +525,128 @@ private enum ArrayAssignType PrimitiveWiden, } + // Array.CopyImpl case: Object[] or interface array to value-type array copy. + private static unsafe void CopyImplUnBoxEachElement(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length) + { + MethodTable* pDestArrayMT = RuntimeHelpers.GetMethodTable(destinationArray); + MethodTable* pDestMT = destinationArray.ElementMethodTable; + + Debug.Assert(!sourceArray.ElementMethodTable->IsValueType); + + 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) + { +#if NATIVEAOT + RuntimeExports.RhUnboxNullable(ref dest, pDestMT, obj); +#else + CastHelpers.Unbox_Nullable(ref dest, pDestMT, obj); +#endif + } + else if (obj is null || RuntimeHelpers.GetMethodTable(obj) != pDestMT) + { + throw new InvalidCastException(SR.InvalidCast_DownCastArrayElement); + } + else if (pDestMT->ContainsGCPointers) + { + Buffer.BulkMoveWithWriteBarrier(ref dest, ref obj.GetRawData(), destSize); + } + else + { + SpanHelpers.Memmove(ref dest, ref obj.GetRawData(), destSize); + } + } + } + + // Array.CopyImpl case: Value-type array to Object[] or interface array copy. + private static unsafe void CopyImplBoxEachElement(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length) + { + MethodTable* pSrcArrayMT = RuntimeHelpers.GetMethodTable(sourceArray); + MethodTable* pSrcMT = sourceArray.ElementMethodTable; + + Debug.Assert(pSrcMT->IsValueType); + Debug.Assert(!destinationArray.ElementMethodTable->IsValueType); + + 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++) + { +#if NATIVEAOT + object? obj = RuntimeExports.RhBox(pSrcMT, ref Unsafe.AddByteOffset(ref data, (nuint)i * srcSize)); +#else + object? obj = RuntimeHelpers.Box(pSrcMT, ref Unsafe.AddByteOffset(ref data, (nuint)i * srcSize)); +#endif + Unsafe.Add(ref destData, i) = obj; + } + } + + // Array.CopyImpl case: Casting copy from gc-ref array to gc-ref array. + private static unsafe void CopyImplCastCheckEachElement(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length) + { + MethodTable* pDestMT = destinationArray.ElementMethodTable; + + 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. + +#if NATIVEAOT + Unsafe.Add(ref destData, i) = TypeCast.CheckCastAny(pDestMT, obj); +#else + Unsafe.Add(ref destData, i) = CastHelpers.ChkCastAny(pDestMT, obj); +#endif + } + } + + // Array.CopyImpl case: Primitive types that have a widening conversion + private static unsafe void CopyImplPrimitiveWiden(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length) + { + // Get appropriate sizes, which requires method tables. + +#if NATIVEAOT + EETypeElementType srcElType = RuntimeHelpers.GetMethodTable(sourceArray)->ElementType; + EETypeElementType destElType = RuntimeHelpers.GetMethodTable(destinationArray)->ElementType; +#else + CorElementType srcElType = sourceArray.GetCorElementTypeOfElementType(); + CorElementType destElType = destinationArray.GetCorElementTypeOfElementType(); +#endif + + 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++) + { +#if NATIVEAOT + InvokeUtils.PrimitiveWiden(destElType, srcElType, ref data, ref srcData); +#else + InvokeUtils.PrimitiveWiden(ref srcData, ref data, srcElType, destElType); +#endif + srcData = ref Unsafe.AddByteOffset(ref srcData, srcElSize); + data = ref Unsafe.AddByteOffset(ref data, destElSize); + } + } + /// /// Clears the contents of an array. /// @@ -679,7 +802,7 @@ public int GetLowerBound(int dimension) } #endif - // The various Get values... + // The various Get values... public object? GetValue(params int[] indices) { if (indices == null) From 41d3318ef485eb1e8b8ae9cb29b555d095866168 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 1 Jul 2025 14:56:51 +0800 Subject: [PATCH 32/39] AsMethodTable() already asserts --- .../System.Private.CoreLib/src/System/Array.CoreCLR.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 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 3c7c5f6af71202..83287c4a4d0cd4 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs @@ -304,15 +304,7 @@ public int Rank [MethodImpl(MethodImplOptions.InternalCall)] internal extern CorElementType GetCorElementTypeOfElementType(); - private unsafe MethodTable* ElementMethodTable - { - get - { - TypeHandle elementTH = RuntimeHelpers.GetMethodTable(this)->GetArrayElementTypeHandle(); - Debug.Assert(!elementTH.IsTypeDesc); - return elementTH.AsMethodTable(); - } - } + private unsafe MethodTable* ElementMethodTable => RuntimeHelpers.GetMethodTable(this)->GetArrayElementTypeHandle().AsMethodTable(); private unsafe bool IsValueOfElementType(object value) { From 05f246ec42cf5c808e1a633f0178896d30f7e785 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 1 Jul 2025 15:09:13 +0800 Subject: [PATCH 33/39] Delete misaligned comment --- src/libraries/System.Private.CoreLib/src/System/Array.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Array.cs b/src/libraries/System.Private.CoreLib/src/System/Array.cs index 40c69980b3fd83..89675f4365a187 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Array.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Array.cs @@ -802,7 +802,6 @@ public int GetLowerBound(int dimension) } #endif - // The various Get values... public object? GetValue(params int[] indices) { if (indices == null) From b40e43310213ae1bd037ab75b62c3012b3cd2601 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 1 Jul 2025 15:10:17 +0800 Subject: [PATCH 34/39] Fix case ordering --- .../src/System/Array.NativeAot.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 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 e9a1b98164fc09..f249c18eb70762 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 @@ -180,14 +180,6 @@ private static unsafe ArrayAssignType CanAssignArrayType(Array sourceArray, Arra return ArrayAssignType.WrongType; } - // Different value types - if (sourceElementEEType->IsValueType && destinationElementEEType->IsValueType) - { - // Different from CanCastTo in coreclr, AreTypesAssignable also allows T -> Nullable conversion. - // Kick for this path early. - return ArrayAssignType.WrongType; - } - // Value class boxing if (sourceElementEEType->IsValueType && !destinationElementEEType->IsValueType) { @@ -222,6 +214,14 @@ private static unsafe ArrayAssignType CanAssignArrayType(Array sourceArray, Arra return ArrayAssignType.WrongType; } + // Different value types + if (sourceElementEEType->IsValueType && destinationElementEEType->IsValueType) + { + // Different from CanCastTo in coreclr, AreTypesAssignable also allows T -> Nullable conversion. + // Kick for this path explicitly. + return ArrayAssignType.WrongType; + } + // src Object extends dest if (RuntimeImports.AreTypesAssignable(sourceElementEEType, destinationElementEEType)) return ArrayAssignType.SimpleCopy; From b7d184f1ab339792bf6f7fc7841d6513e5f820e3 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 1 Jul 2025 15:45:59 +0800 Subject: [PATCH 35/39] Avoid multiplication by non-constant --- .../src/System/Array.cs | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Array.cs b/src/libraries/System.Private.CoreLib/src/System/Array.cs index 89675f4365a187..a818c0787357ee 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Array.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Array.cs @@ -535,7 +535,7 @@ private static unsafe void CopyImplUnBoxEachElement(Array sourceArray, int sourc 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); + ref byte destData = ref Unsafe.AddByteOffset(ref MemoryMarshal.GetArrayDataReference(destinationArray), (nuint)destinationIndex * destSize); for (int i = 0; i < length; i++) { @@ -544,14 +544,12 @@ private static unsafe void CopyImplUnBoxEachElement(Array sourceArray, int sourc // 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) { #if NATIVEAOT - RuntimeExports.RhUnboxNullable(ref dest, pDestMT, obj); + RuntimeExports.RhUnboxNullable(ref destData, pDestMT, obj); #else - CastHelpers.Unbox_Nullable(ref dest, pDestMT, obj); + CastHelpers.Unbox_Nullable(ref destData, pDestMT, obj); #endif } else if (obj is null || RuntimeHelpers.GetMethodTable(obj) != pDestMT) @@ -560,12 +558,14 @@ private static unsafe void CopyImplUnBoxEachElement(Array sourceArray, int sourc } else if (pDestMT->ContainsGCPointers) { - Buffer.BulkMoveWithWriteBarrier(ref dest, ref obj.GetRawData(), destSize); + Buffer.BulkMoveWithWriteBarrier(ref destData, ref obj.GetRawData(), destSize); } else { - SpanHelpers.Memmove(ref dest, ref obj.GetRawData(), destSize); + SpanHelpers.Memmove(ref destData, ref obj.GetRawData(), destSize); } + + destData = ref Unsafe.AddByteOffset(ref destData, destSize); } } @@ -579,17 +579,18 @@ private static unsafe void CopyImplBoxEachElement(Array sourceArray, int sourceI Debug.Assert(!destinationArray.ElementMethodTable->IsValueType); nuint srcSize = pSrcArrayMT->ComponentSize; - ref byte data = ref Unsafe.AddByteOffset(ref MemoryMarshal.GetArrayDataReference(sourceArray), (nuint)sourceIndex * srcSize); + ref byte srcData = 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++) { #if NATIVEAOT - object? obj = RuntimeExports.RhBox(pSrcMT, ref Unsafe.AddByteOffset(ref data, (nuint)i * srcSize)); + object? obj = RuntimeExports.RhBox(pSrcMT, ref srcData); #else - object? obj = RuntimeHelpers.Box(pSrcMT, ref Unsafe.AddByteOffset(ref data, (nuint)i * srcSize)); + object? obj = RuntimeHelpers.Box(pSrcMT, ref srcData); #endif Unsafe.Add(ref destData, i) = obj; + srcData = ref Unsafe.AddByteOffset(ref srcData, srcSize); } } From baf176f0c601477cb4a9e31174f22d4922bf6ab5 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 1 Jul 2025 15:50:52 +0800 Subject: [PATCH 36/39] Reorder PrimitiveWiden --- .../System.Private.CoreLib/src/System/InvokeUtils.cs | 4 ++-- src/libraries/System.Private.CoreLib/src/System/Array.cs | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/InvokeUtils.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/InvokeUtils.cs index 9cae9eb9198375..0c61f0bb55156d 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/InvokeUtils.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/InvokeUtils.cs @@ -144,13 +144,13 @@ private static Exception ConvertOrWidenPrimitivesEnumsAndPointersIfPossible(obje } dstObject = RuntimeImports.RhNewObject(dstEEType); - PrimitiveWiden(dstElementType, srcElementType, ref dstObject.GetRawData(), ref srcObject.GetRawData()); + PrimitiveWiden(ref srcObject.GetRawData(), ref dstObject.GetRawData(), srcElementType, dstElementType); } return null; } [MethodImpl(MethodImplOptions.AggressiveInlining)] // Two callers, one of them is potentially perf sensitive - internal static void PrimitiveWiden(EETypeElementType dstType, EETypeElementType srcType, ref byte dstValue, ref byte srcValue) + internal static void PrimitiveWiden(ref byte srcValue, ref byte dstValue, EETypeElementType srcType, EETypeElementType dstType) { // Caller must check that the conversion is valid and the source/destination types are different Debug.Assert(CanPrimitiveWiden(dstType, srcType) && dstType != srcType); diff --git a/src/libraries/System.Private.CoreLib/src/System/Array.cs b/src/libraries/System.Private.CoreLib/src/System/Array.cs index a818c0787357ee..c5d3bb119567da 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Array.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Array.cs @@ -638,11 +638,7 @@ private static unsafe void CopyImplPrimitiveWiden(Array sourceArray, int sourceI for (int i = 0; i < length; i++) { -#if NATIVEAOT - InvokeUtils.PrimitiveWiden(destElType, srcElType, ref data, ref srcData); -#else InvokeUtils.PrimitiveWiden(ref srcData, ref data, srcElType, destElType); -#endif srcData = ref Unsafe.AddByteOffset(ref srcData, srcElSize); data = ref Unsafe.AddByteOffset(ref data, destElSize); } From 88d2cf217df48e4f256cedf1e03ee93eaa6ffe99 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 1 Jul 2025 16:06:26 +0800 Subject: [PATCH 37/39] Align asserts --- src/libraries/System.Private.CoreLib/src/System/Array.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/Array.cs b/src/libraries/System.Private.CoreLib/src/System/Array.cs index c5d3bb119567da..4ab98cc6bd34fa 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Array.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Array.cs @@ -532,6 +532,7 @@ private static unsafe void CopyImplUnBoxEachElement(Array sourceArray, int sourc MethodTable* pDestMT = destinationArray.ElementMethodTable; Debug.Assert(!sourceArray.ElementMethodTable->IsValueType); + Debug.Assert(pDestMT->IsValueType); nuint destSize = pDestArrayMT->ComponentSize; ref object? srcData = ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(sourceArray)), sourceIndex); @@ -599,6 +600,9 @@ private static unsafe void CopyImplCastCheckEachElement(Array sourceArray, int s { MethodTable* pDestMT = destinationArray.ElementMethodTable; + Debug.Assert(!sourceArray.ElementMethodTable->IsValueType); + Debug.Assert(!pDestMT->IsValueType); + 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); @@ -620,6 +624,9 @@ private static unsafe void CopyImplCastCheckEachElement(Array sourceArray, int s // Array.CopyImpl case: Primitive types that have a widening conversion private static unsafe void CopyImplPrimitiveWiden(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length) { + Debug.Assert(sourceArray.ElementMethodTable->IsPrimitive); + Debug.Assert(destinationArray.ElementMethodTable->IsPrimitive); + // Get appropriate sizes, which requires method tables. #if NATIVEAOT From 5e65943c41ca874d76d3ceee44d2452a34402191 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 1 Jul 2025 21:48:08 +0800 Subject: [PATCH 38/39] Fix element type --- src/libraries/System.Private.CoreLib/src/System/Array.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Array.cs b/src/libraries/System.Private.CoreLib/src/System/Array.cs index 4ab98cc6bd34fa..0a64103b6c036c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Array.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Array.cs @@ -630,8 +630,8 @@ private static unsafe void CopyImplPrimitiveWiden(Array sourceArray, int sourceI // Get appropriate sizes, which requires method tables. #if NATIVEAOT - EETypeElementType srcElType = RuntimeHelpers.GetMethodTable(sourceArray)->ElementType; - EETypeElementType destElType = RuntimeHelpers.GetMethodTable(destinationArray)->ElementType; + EETypeElementType srcElType = sourceArray.ElementMethodTable->ElementType; + EETypeElementType destElType = destinationArray.ElementMethodTable->ElementType; #else CorElementType srcElType = sourceArray.GetCorElementTypeOfElementType(); CorElementType destElType = destinationArray.GetCorElementTypeOfElementType(); From 34cf41662f50a1d38fcd3cd59543ffd0cdd1727e Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Tue, 1 Jul 2025 17:15:58 -0700 Subject: [PATCH 39/39] Apply suggestions from code review --- src/libraries/System.Private.CoreLib/src/System/Array.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Array.cs b/src/libraries/System.Private.CoreLib/src/System/Array.cs index 0a64103b6c036c..a98f5b0256c122 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Array.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Array.cs @@ -641,13 +641,13 @@ private static unsafe void CopyImplPrimitiveWiden(Array sourceArray, int sourceI 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); + ref byte destData = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(destinationArray), (nuint)destinationIndex * destElSize); for (int i = 0; i < length; i++) { - InvokeUtils.PrimitiveWiden(ref srcData, ref data, srcElType, destElType); + InvokeUtils.PrimitiveWiden(ref srcData, ref destData, srcElType, destElType); srcData = ref Unsafe.AddByteOffset(ref srcData, srcElSize); - data = ref Unsafe.AddByteOffset(ref data, destElSize); + destData = ref Unsafe.AddByteOffset(ref destData, destElSize); } }