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..83287c4a4d0cd4 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs @@ -49,62 +49,7 @@ 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 bool SupportsNonZeroLowerBound => true; private static CorElementType GetNormalizedIntegralArrayElementType(CorElementType elementType) { @@ -119,54 +64,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 enum ArrayAssignType - { - SimpleCopy, - WrongType, - MustCast, - BoxValueClassOrPrimitive, - UnboxValueClass, - PrimitiveWiden, - } - private static unsafe ArrayAssignType CanAssignArrayType(Array sourceArray, Array destinationArray) { TypeHandle srcTH = RuntimeHelpers.GetMethodTable(sourceArray)->GetArrayElementTypeHandle(); @@ -175,295 +72,71 @@ 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) - { - 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; - } - - // 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; - } - else + if (srcTH.IsTypeDesc || destTH.IsTypeDesc) { // Only pointers are valid for TypeDesc in array element // Compatible pointers if (srcTH.CanCastTo(destTH)) return ArrayAssignType.SimpleCopy; - } - - 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); - } + return ArrayAssignType.WrongType; } - } - - // 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(); + MethodTable* pMTsrc = srcTH.AsMethodTable(); + MethodTable* pMTdest = destTH.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++) + // Value class boxing + if (pMTsrc->IsValueType && !pMTdest->IsValueType) { - 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); + if (srcTH.CanCastTo(destTH)) + return ArrayAssignType.BoxValueClassOrPrimitive; + else + return ArrayAssignType.WrongType; } - } - - // 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++) + // Value class unboxing. + if (!pMTsrc->IsValueType && pMTdest->IsValueType) { - InvokeUtils.PrimitiveWiden(ref srcData, ref data, srcElType, destElType); - srcData = ref Unsafe.AddByteOffset(ref srcData, srcElSize); - data = ref Unsafe.AddByteOffset(ref data, destElSize); + 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; } - } - - // 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. - /// - /// 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 + // Copying primitives from one type to another + if (pMTsrc->IsPrimitive && pMTdest->IsPrimitive) { - 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); + CorElementType srcElType = pMTsrc->GetPrimitiveCorElementType(); + CorElementType destElType = pMTdest->GetPrimitiveCorElementType(); - 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 + if (GetNormalizedIntegralArrayElementType(srcElType) == GetNormalizedIntegralArrayElementType(destElType)) + return ArrayAssignType.SimpleCopy; + else if (RuntimeHelpers.CanPrimitiveWiden(srcElType, destElType)) + return ArrayAssignType.PrimitiveWiden; + else + return ArrayAssignType.WrongType; } - 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 - Debug.Assert(Rank == 1); + // src Object extends dest + if (srcTH.CanCastTo(destTH)) + return ArrayAssignType.SimpleCopy; - if (RuntimeHelpers.GetMethodTable(this)->IsMultiDimensionalArray) - { - ref int bounds = ref RuntimeHelpers.GetMultiDimensionalArrayBounds(this); - rawIndex -= Unsafe.Add(ref bounds, 1); - } + // dest Object extends src + if (destTH.CanCastTo(srcTH)) + return ArrayAssignType.MustCast; - if ((uint)rawIndex >= (uint)LongLength) - ThrowHelper.ThrowIndexOutOfRangeException(); - return rawIndex; - } + // class X extends/implements src and implements dest. + if (pMTdest->IsInterface) + return ArrayAssignType.MustCast; - private unsafe nint GetFlattenedIndex(ReadOnlySpan indices) - { - // Checked by the caller - Debug.Assert(indices.Length == Rank); + // class X implements src and extends/implements dest + if (pMTsrc->IsInterface) + return ArrayAssignType.MustCast; - 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; - } + return ArrayAssignType.WrongType; } internal unsafe object? InternalGetValue(nint flattenedIndex) @@ -628,49 +301,11 @@ 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(); + private unsafe MethodTable* ElementMethodTable => RuntimeHelpers.GetMethodTable(this)->GetArrayElementTypeHandle().AsMethodTable(); + private unsafe bool IsValueOfElementType(object value) { MethodTable* thisMT = 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 96bdc5c84b8ebf..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 @@ -415,7 +415,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 @@ -423,7 +423,7 @@ internal static ref int GetMultiDimensionalArrayBounds(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 @@ -822,6 +822,16 @@ internal unsafe struct MethodTable public bool HasDefaultConstructor => (Flags & (enum_flag_HasComponentSize | enum_flag_HasDefaultCtor)) == enum_flag_HasDefaultCtor; + public bool IsSzArray + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + Debug.Assert(IsArray); + return BaseSize == (uint)(3 * sizeof(IntPtr)); + } + } + 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..e4eb2775977bf2 100644 --- a/src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs +++ b/src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs @@ -408,17 +408,31 @@ 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; } } + // Returns rank of multi-dimensional array rank, 0 for sz arrays + internal int MultiDimensionalArrayRank + { + get + { + Debug.Assert(this.IsArray); + + 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 (int)((uint)boundsSize / (uint)(2 * sizeof(int))); + } + } + internal bool IsSzArray { get 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; } 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..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 @@ -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,470 +149,109 @@ public unsafe void Initialize() } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ref int GetRawMultiDimArrayBounds() + private unsafe ref int GetMultiDimensionalArrayBounds() { - Debug.Assert(!IsSzArray); + Debug.Assert(!this.GetMethodTable()->IsSzArray); 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) - { - CopyImpl(sourceArray, sourceIndex, destinationArray, destinationIndex, length, reliable: true); - } - - - // - // 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) + private unsafe int GetMultiDimensionalArrayRank() { - 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); - } - } + return this.GetMethodTable()->MultiDimensionalArrayRank; } - 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; - } + private static bool SupportsNonZeroLowerBound => false; - // - // 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) + private static unsafe ArrayAssignType CanAssignArrayType(Array sourceArray, Array destinationArray) { - // 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); + if (sourceElementEEType == destinationElementEEType) // This check kicks for different array kind or dimensions + return ArrayAssignType.SimpleCopy; - bool attemptCopy = RuntimeImports.AreTypesAssignable(sourceElementEEType, destinationElementEEType); - bool mustCastCheckEachElement = !attemptCopy; - if (reliable) + if (sourceElementEEType->IsPointer || sourceElementEEType->IsFunctionPointer + || destinationElementEEType->IsPointer || destinationElementEEType->IsFunctionPointer) { - 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: 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) - { - 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)); - - if (reliable) - throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_ConstrainedCopy); - - 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 CopyImplReferenceArrayToValueTypeArray(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, bool reliable) - { - 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; - - 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; - } + // Compatible pointers + if (RuntimeImports.AreTypesAssignable(sourceElementEEType, destinationElementEEType)) + return ArrayAssignType.SimpleCopy; + else + return ArrayAssignType.WrongType; } - } - - - // - // 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) + // Value class boxing + if (sourceElementEEType->IsValueType && !destinationElementEEType->IsValueType) { - boxedElements = new object[length]; - reverseCopy = false; + if (RuntimeImports.AreTypesAssignable(sourceElementEEType, destinationElementEEType)) + return ArrayAssignType.BoxValueClassOrPrimitive; + else + return ArrayAssignType.WrongType; } - fixed (byte* pDstArray = &MemoryMarshal.GetArrayDataReference(destinationArray), pSrcArray = &MemoryMarshal.GetArrayDataReference(sourceArray)) + // Value class unboxing. + if (!sourceElementEEType->IsValueType && destinationElementEEType->IsValueType) { - 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 (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 (boxedElements != null) + // Copying primitives from one type to another + if (sourceElementEEType->IsPrimitive && destinationElementEEType->IsPrimitive) { - 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 - // - private static unsafe void CopyImplPrimitiveTypeWithWidening(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, bool reliable) - { - 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; + EETypeElementType sourceElementType = sourceElementEEType->ElementType; + EETypeElementType destElementType = destinationElementEEType->ElementType; - 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); + if (GetNormalizedIntegralArrayElementType(sourceElementType) == GetNormalizedIntegralArrayElementType(destElementType)) + return ArrayAssignType.SimpleCopy; + else if (InvokeUtils.CanPrimitiveWiden(destElementType, sourceElementType)) + return ArrayAssignType.PrimitiveWiden; + else + return ArrayAssignType.WrongType; } - 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) + // Different value types + if (sourceElementEEType->IsValueType && destinationElementEEType->IsValueType) { - // Multidim arrays and enum->int copies can still reach this path. - SpanHelpers.Memmove(ref dstData, ref srcData, (nuint)length * srcElementSize); - return; + // Different from CanCastTo in coreclr, AreTypesAssignable also allows T -> Nullable conversion. + // Kick for this path explicitly. + return ArrayAssignType.WrongType; } - if (!InvokeUtils.CanPrimitiveWiden(destElementType, sourceElementType)) - { - throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_CantAssignType); - } + // src Object extends dest + if (RuntimeImports.AreTypesAssignable(sourceElementEEType, destinationElementEEType)) + return ArrayAssignType.SimpleCopy; - 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); - } - } + // dest Object extends src + if (RuntimeImports.AreTypesAssignable(destinationElementEEType, sourceElementEEType)) + return ArrayAssignType.MustCast; - public static unsafe void Clear(Array array) - { - if (array == null) - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + // class X extends/implements src and implements dest. + if (destinationElementEEType->IsInterface) + return ArrayAssignType.MustCast; - MethodTable* mt = array.GetMethodTable(); - nuint totalByteLength = mt->ComponentSize * array.NativeLength; - ref byte pStart = ref MemoryMarshal.GetArrayDataReference(array); + // class X implements src and extends/implements dest + if (sourceElementEEType->IsInterface) + return ArrayAssignType.MustCast; - 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)); - } + return ArrayAssignType.WrongType; } - public static unsafe void Clear(Array array, int index, int length) + private static EETypeElementType GetNormalizedIntegralArrayElementType(EETypeElementType elementType) { - 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 - } + Debug.Assert(elementType >= EETypeElementType.Boolean && elementType <= EETypeElementType.Double); - int offset = index - lowerBound; + // 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 - 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) - { - 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; + // U1/U2/U4/U8/U + int shift = (0b0010_1010_1010_0000 >> (int)elementType) & 1; + return (EETypeElementType)((int)elementType - shift); } public unsafe int Rank @@ -666,7 +297,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]; @@ -675,89 +306,6 @@ internal static unsafe Array NewMultiDimArray(MethodTable* eeType, int* pLengths return ret; } - [Intrinsic] - public int GetLowerBound(int dimension) - { - if (!IsSzArray) - { - int rank = Rank; - if ((uint)dimension >= rank) - throw new IndexOutOfRangeException(); - - return Unsafe.Add(ref GetRawMultiDimArrayBounds(), rank + dimension); - } - - if (dimension != 0) - throw new IndexOutOfRangeException(); - return 0; - } - - [Intrinsic] - public int GetUpperBound(int dimension) - { - if (!IsSzArray) - { - int rank = Rank; - if ((uint)dimension >= rank) - throw new IndexOutOfRangeException(); - - ref int bounds = ref GetRawMultiDimArrayBounds(); - - 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; - } - - 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/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 6df854d28b5460..a98f5b0256c122 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; @@ -351,38 +352,46 @@ 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 // destinationArray, starting at index 0. public static unsafe void Copy(Array sourceArray, Array destinationArray, int length) { - if (sourceArray is null) - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.sourceArray); - if (destinationArray is null) - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.destinationArray); - - MethodTable* pMT = RuntimeHelpers.GetMethodTable(sourceArray); - if (MethodTable.AreSameType(pMT, RuntimeHelpers.GetMethodTable(destinationArray)) && - !pMT->IsMultiDimensionalArray && - (uint)length <= sourceArray.NativeLength && - (uint)length <= destinationArray.NativeLength) + 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 - 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 @@ -393,7 +402,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) @@ -414,11 +423,389 @@ 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); + } + + // 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); + ArgumentNullException.ThrowIfNull(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); + 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); + destinationIndex -= dstLB; + if ((destinationIndex < 0) || ((uint)(destinationIndex + length) > destinationArray.NativeLength)) + throw new ArgumentException(SR.Arg_LongerThanDestArray, nameof(destinationArray)); + + ArrayAssignType assignType; + + 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 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, + WrongType, + MustCast, + BoxValueClassOrPrimitive, + UnboxValueClass, + 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); + Debug.Assert(pDestMT->IsValueType); + + nuint destSize = pDestArrayMT->ComponentSize; + ref object? srcData = ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(sourceArray)), sourceIndex); + ref byte destData = 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. + + if (pDestMT->IsNullable) + { +#if NATIVEAOT + RuntimeExports.RhUnboxNullable(ref destData, pDestMT, obj); +#else + CastHelpers.Unbox_Nullable(ref destData, 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 destData, ref obj.GetRawData(), destSize); + } + else + { + SpanHelpers.Memmove(ref destData, ref obj.GetRawData(), destSize); + } + + destData = ref Unsafe.AddByteOffset(ref destData, 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 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 srcData); +#else + object? obj = RuntimeHelpers.Box(pSrcMT, ref srcData); +#endif + Unsafe.Add(ref destData, i) = obj; + srcData = ref Unsafe.AddByteOffset(ref srcData, srcSize); + } + } + + // 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; + + 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); + + 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) + { + Debug.Assert(sourceArray.ElementMethodTable->IsPrimitive); + Debug.Assert(destinationArray.ElementMethodTable->IsPrimitive); + + // Get appropriate sizes, which requires method tables. + +#if NATIVEAOT + EETypeElementType srcElType = sourceArray.ElementMethodTable->ElementType; + EETypeElementType destElType = destinationArray.ElementMethodTable->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 destData = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(destinationArray), (nuint)destinationIndex * destElSize); + + for (int i = 0; i < length; i++) + { + InvokeUtils.PrimitiveWiden(ref srcData, ref destData, srcElType, destElType); + srcData = ref Unsafe.AddByteOffset(ref srcData, srcElSize); + destData = ref Unsafe.AddByteOffset(ref destData, destElSize); + } + } + + /// + /// 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` + } + + 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; + } + } + + [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... public object? GetValue(params int[] indices) { if (indices == null) 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, 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..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 @@ -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() @@ -1582,6 +1586,12 @@ 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] }; + + // ValueType[] -> Nullable[] never works + yield return new object[] { new int[1], new int?[1] }; } [Theory] 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..fa2abf42a567ce 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); @@ -108,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);