Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
dfc09e0
Adapt CopyImpl and CanAssignArrayType to NativeAot
huoyaoyuan Jun 22, 2025
3c031da
Adapt CopySlow paths
huoyaoyuan Jun 22, 2025
8af6824
Cleanup old CopyImpl
huoyaoyuan Jun 22, 2025
ae4a0d5
Add test for primitive widen with enum
huoyaoyuan Jun 22, 2025
ce29d08
Cleanup unreachable path
huoyaoyuan Jun 22, 2025
97dc439
Fold identical ConstrainedCopy
huoyaoyuan Jun 22, 2025
c9e0ae2
Share Array.Clear
huoyaoyuan Jun 22, 2025
2488fe9
Share GetFlattenedIndex
huoyaoyuan Jun 22, 2025
b65a093
Reduce overhead of GetLength/LowerBound/UpperBound
huoyaoyuan Jun 22, 2025
e975e11
Fix mono build
huoyaoyuan Jun 22, 2025
e7fc110
Fix direction of CanPrimitiveWiden
huoyaoyuan Jun 23, 2025
e366c6a
Share GetLength/GetUpperBound/GetLowerBound
huoyaoyuan Jun 23, 2025
bc584d1
Cleanup IsSzArray
huoyaoyuan Jun 23, 2025
c409556
Encounter for pointer in IsSystemObject
huoyaoyuan Jun 23, 2025
83b6ea8
Fix unit tests
huoyaoyuan Jun 23, 2025
b3c5374
Merge branch 'main' into array-shared
huoyaoyuan Jun 24, 2025
11da64e
Merge branch 'main' into array-shared
huoyaoyuan Jun 30, 2025
072519a
Cleanup exception throwing.
huoyaoyuan Jun 30, 2025
9c6ecaa
Invert condition for pointer check
huoyaoyuan Jun 30, 2025
0766d2c
Remove redundant throwing condition
huoyaoyuan Jun 30, 2025
9344eec
Fix nullability
huoyaoyuan Jun 30, 2025
fb403f4
Reduce overhead of ArrayRank
huoyaoyuan Jun 30, 2025
e7e11b9
Cleanup
huoyaoyuan Jun 30, 2025
3e60780
Add test for copying value types
huoyaoyuan Jun 30, 2025
36cf203
Revert "Remove redundant throwing condition"
huoyaoyuan Jun 30, 2025
3370988
Add back overflow check
huoyaoyuan Jun 30, 2025
1c0901a
Update array type checking
huoyaoyuan Jul 1, 2025
6044d8a
Simplify bound checking
huoyaoyuan Jul 1, 2025
1b9a1d8
Cleanup CER comment
huoyaoyuan Jul 1, 2025
d8ce97e
Add test for nullable case
huoyaoyuan Jul 1, 2025
5fa7174
Handle nullable conversion
huoyaoyuan Jul 1, 2025
67f6774
Move comment
huoyaoyuan Jul 1, 2025
11dcdca
Move copy cases to shared
huoyaoyuan Jul 1, 2025
41d3318
AsMethodTable() already asserts
huoyaoyuan Jul 1, 2025
05f246e
Delete misaligned comment
huoyaoyuan Jul 1, 2025
b40e433
Fix case ordering
huoyaoyuan Jul 1, 2025
b7d184f
Avoid multiplication by non-constant
huoyaoyuan Jul 1, 2025
baf176f
Reorder PrimitiveWiden
huoyaoyuan Jul 1, 2025
88d2cf2
Align asserts
huoyaoyuan Jul 1, 2025
5e65943
Fix element type
huoyaoyuan Jul 1, 2025
34cf416
Apply suggestions from code review
jkotas Jul 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 0 additions & 224 deletions src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -119,54 +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 enum ArrayAssignType
{
SimpleCopy,
WrongType,
MustCast,
BoxValueClassOrPrimitive,
UnboxValueClass,
PrimitiveWiden,
}

private static unsafe ArrayAssignType CanAssignArrayType(Array sourceArray, Array destinationArray)
{
TypeHandle srcTH = RuntimeHelpers.GetMethodTable(sourceArray)->GetArrayElementTypeHandle();
Expand Down Expand Up @@ -347,125 +242,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);
}

/// <summary>
/// Clears the contents of an array.
/// </summary>
/// <param name="array">The array to clear.</param>
/// <exception cref="ArgumentNullException"><paramref name="array"/> is null.</exception>
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<byte, IntPtr>(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<RawArrayData>(array).Data;
int lowerBound = 0;

MethodTable* pMT = RuntimeHelpers.GetMethodTable(array);
if (pMT->IsMultiDimensionalArray)
{
int rank = pMT->MultiDimensionalArrayRank;
lowerBound = Unsafe.Add(ref Unsafe.As<byte, int>(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<byte, IntPtr>(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)->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<int> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,20 @@ internal int ArrayRank
}
}

// Returns rank of multi-dimensional array rank, 0 for sz arrays
internal int MultiDimensionalArrayRank
{
get
{
Debug.Assert(this.IsArray);

int boundsSize = (int)this.ParameterizedTypeShape - 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));
}
}

internal bool IsSzArray
{
get
Expand Down
Loading
Loading