File: System\Array.NativeAot.cs
Web Access
Project: src\src\runtime\src\coreclr\nativeaot\System.Private.CoreLib\src\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;

using Internal.IntrinsicSupport;
using Internal.Runtime.Augments;
using Internal.Runtime.CompilerServices;

using EETypeElementType = Internal.Runtime.EETypeElementType;
using MethodTable = Internal.Runtime.MethodTable;

namespace System
{
    // Note that we make a T[] (single-dimensional w/ zero as the lower bound) implement both
    // IList<U> and IReadOnlyList<U>, where T : U dynamically.  See the SZArrayHelper class for details.
    public abstract partial class Array : ICollection, IEnumerable, IList, IStructuralComparable, IStructuralEquatable, ICloneable
    {
        // CS0169: The field 'Array._numComponents' is never used
        // CA1823: Unused field '_numComponents'
#pragma warning disable 0169
#pragma warning disable CA1823
        // This field should be the first field in Array as the runtime/compilers depend on it
        [NonSerialized]
        private int _numComponents;
#pragma warning restore

        public int Length => checked((int)Unsafe.As<RawArrayData>(this).Length);

        // This could return a length greater than int.MaxValue
        internal nuint NativeLength => Unsafe.As<RawArrayData>(this).Length;

        public long LongLength => (long)NativeLength;

        // 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")]
        private static unsafe MethodTable* GetSystemArrayEEType()
        {
            return MethodTable.Of<Array>();
        }

        [RequiresDynamicCode("The code for an array of the specified type might not be available.")]
        private static unsafe Array InternalCreate(RuntimeType elementType, int rank, int* pLengths, int* pLowerBounds)
        {
            if (elementType.IsByRef || elementType.IsByRefLike)
                throw new NotSupportedException(SR.NotSupported_ByRefLikeArray);
            if (elementType == typeof(void))
                throw new NotSupportedException(SR.NotSupported_VoidArray);
            if (elementType.ContainsGenericParameters)
                throw new NotSupportedException(SR.NotSupported_OpenType);

            if (pLowerBounds != null)
            {
                for (int i = 0; i < rank; i++)
                {
                    if (pLowerBounds[i] != 0)
                        throw new PlatformNotSupportedException(SR.PlatformNotSupported_NonZeroLowerBound);
                }
            }

            if (rank == 1)
            {
                return RuntimeAugments.NewArray(elementType.MakeArrayType().TypeHandle, pLengths[0]);
            }
            else
            {
                Type arrayType = elementType.MakeArrayType(rank);

                // Create a local copy of the lengths that cannot be modified by the caller
                int* pImmutableLengths = stackalloc int[rank];
                for (int i = 0; i < rank; i++)
                    pImmutableLengths[i] = pLengths[i];

                return NewMultiDimArray(arrayType.TypeHandle.ToMethodTable(), pImmutableLengths, rank);
            }
        }

        [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
            Justification = "The compiler ensures that if we have a TypeHandle of a Rank-1 MdArray, we also generated the SzArray.")]
        private static unsafe Array InternalCreateFromArrayType(RuntimeType arrayType, int rank, int* pLengths, int* pLowerBounds)
        {
            Debug.Assert(arrayType.IsArray);
            Debug.Assert(arrayType.GetArrayRank() == rank);

            if (arrayType.ContainsGenericParameters)
                throw new NotSupportedException(SR.NotSupported_OpenType);

            if (pLowerBounds != null)
            {
                for (int i = 0; i < rank; i++)
                {
                    if (pLowerBounds[i] != 0)
                        throw new PlatformNotSupportedException(SR.PlatformNotSupported_NonZeroLowerBound);
                }
            }

            if (rank == 1)
            {
                // Multidimensional array of rank 1 with 0 lower bounds gets actually allocated
                // as an SzArray. SzArray is castable to MdArray rank 1.
                RuntimeTypeHandle arrayTypeHandle = arrayType.IsSZArray
                    ? arrayType.TypeHandle
                    : arrayType.GetElementType().MakeArrayType().TypeHandle;

                return RuntimeAugments.NewArray(arrayTypeHandle, pLengths[0]);
            }
            else
            {
                // Create a local copy of the lengths that cannot be modified by the caller
                int* pImmutableLengths = stackalloc int[rank];
                for (int i = 0; i < rank; i++)
                    pImmutableLengths[i] = pLengths[i];

                MethodTable* eeType = arrayType.TypeHandle.ToMethodTable();
                return NewMultiDimArray(eeType, pImmutableLengths, rank);
            }
        }

        // Allocate new multidimensional array of given dimensions. Assumes that pLengths is immutable.
        internal static unsafe Array NewMultiDimArray(MethodTable* eeType, int* pLengths, int rank)
        {
            Debug.Assert(eeType->IsArray && !eeType->IsSzArray);
            Debug.Assert(rank == eeType->ArrayRank);

            // Code below assumes 0 lower bounds. MdArray of rank 1 with zero lower bounds should never be allocated.
            // The runtime always allocates an SzArray for those:
            // * newobj instance void int32[0...]::.ctor(int32)" actually gives you int[]
            // * int[] is castable to int[*] to make it mostly transparent
            // The callers need to check for this.
            Debug.Assert(rank != 1);

            ulong totalLength = 1;
            bool maxArrayDimensionLengthOverflow = false;

            for (int i = 0; i < rank; i++)
            {
                int length = pLengths[i];
                if (length < 0)
                    throw new OverflowException();
                if (length > MaxLength)
                    maxArrayDimensionLengthOverflow = true;
                totalLength *= (ulong)length;
                if (totalLength > int.MaxValue)
                    throw new OutOfMemoryException(); // "Array dimensions exceeded supported range."
            }

            // Throw this exception only after everything else was validated for backward compatibility.
            if (maxArrayDimensionLengthOverflow)
                throw new OutOfMemoryException(); // "Array dimensions exceeded supported range."

            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.GetMultiDimensionalArrayBounds();
            for (int i = 0; i < rank; i++)
            {
                Unsafe.Add(ref bounds, i) = pLengths[i];
            }

            return ret;
        }

        /// <summary>
        /// Implementation of CORINFO_HELP_NEW_MDARR
        /// Helper for array allocations via `newobj` IL instruction. Dimensions are passed in as block of integers.
        /// The content of the dimensions block may be modified by the helper.
        /// </summary>
        internal static unsafe Array Ctor(MethodTable* pEEType, int nDimensions, int* pDimensions)
        {
            Debug.Assert(pEEType->IsArray && !pEEType->IsSzArray);
            Debug.Assert(nDimensions > 0);

            // Rank 1 arrays are handled below.
            Debug.Assert(pEEType->ArrayRank > 1);

            // Multidimensional arrays have two ctors, one with and one without lower bounds
            int rank = pEEType->ArrayRank;
            Debug.Assert(rank == nDimensions || 2 * rank == nDimensions);

            if (rank < nDimensions)
            {
                for (int i = 0; i < rank; i++)
                {
                    if (pDimensions[2 * i] != 0)
                        throw new PlatformNotSupportedException(SR.PlatformNotSupported_NonZeroLowerBound);

                    pDimensions[i] = pDimensions[2 * i + 1];
                }
            }

            return NewMultiDimArray(pEEType, pDimensions, rank);
        }

        /// <summary>
        /// Implementation of CORINFO_HELP_NEW_MDARR_RARE
        /// Helper for array allocations via `newobj` IL instruction. Dimensions are passed in as block of integers.
        /// The content of the dimensions block may be modified by the helper.
        /// </summary>
        [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
            Justification = "The compiler ensures that if we have a TypeHandle of a Rank-1 MdArray, we also generated the SzArray.")]
        internal static unsafe Array CtorRare(MethodTable* pEEType, int nDimensions, int* pDimensions)
        {
            Debug.Assert(pEEType->IsArray);
            Debug.Assert(nDimensions > 0);

            Debug.Assert(pEEType->ArrayRank == 1);

            if (pEEType->IsSzArray)
            {
                Array ret = RuntimeImports.RhNewArray(pEEType, pDimensions[0]);

                if (nDimensions > 1)
                {
                    // Jagged arrays have constructor for each possible depth
                    MethodTable* elementType = pEEType->RelatedParameterType;
                    Debug.Assert(elementType->IsSzArray);

                    Array[] arrayOfArrays = Unsafe.As<Array[]>(ret);
                    for (int i = 0; i < arrayOfArrays.Length; i++)
                        arrayOfArrays[i] = CtorRare(elementType, nDimensions - 1, pDimensions + 1);
                }

                return ret;
            }
            else
            {
                // Multidimensional arrays have two ctors, one with and one without lower bounds
                const int rank = 1;
                Debug.Assert(rank == nDimensions || 2 * rank == nDimensions);

                if (rank < nDimensions)
                {
                    if (pDimensions[0] != 0)
                        throw new PlatformNotSupportedException(SR.PlatformNotSupported_NonZeroLowerBound);

                    pDimensions[0] = pDimensions[1];
                }

                // Multidimensional array of rank 1 with 0 lower bounds gets actually allocated
                // as an SzArray. SzArray is castable to MdArray rank 1.
                RuntimeType elementType = Type.GetTypeFromMethodTable(pEEType->RelatedParameterType);
                return RuntimeImports.RhNewArray(elementType.MakeArrayType().TypeHandle.ToMethodTable(), pDimensions[0]);
            }
        }

        public unsafe void Initialize()
        {
            MethodTable* pElementEEType = ElementMethodTable;
            if (!pElementEEType->IsValueType)
                return;

            IntPtr constructorEntryPoint = RuntimeAugments.TypeLoaderCallbacks.TryGetDefaultConstructorForType(new RuntimeTypeHandle(pElementEEType));
            if (constructorEntryPoint == IntPtr.Zero)
                return;

            IntPtr constructorFtn = RuntimeAugments.TypeLoaderCallbacks.ConvertUnboxingFunctionPointerToUnderlyingNonUnboxingPointer(constructorEntryPoint, new RuntimeTypeHandle(pElementEEType));

            ref byte arrayRef = ref MemoryMarshal.GetArrayDataReference(this);
            nuint elementSize = ElementSize;

            for (nuint i = 0; i < NativeLength; i++)
            {
                RawCalliHelper.CallDefaultStructConstructor(constructorFtn, ref arrayRef);
                arrayRef = ref Unsafe.Add(ref arrayRef, elementSize);
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private unsafe ref int GetMultiDimensionalArrayBounds()
        {
            Debug.Assert(!this.GetMethodTable()->IsSzArray);
            return ref Unsafe.As<byte, int>(ref Unsafe.As<RawArrayData>(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;
            MethodTable* destinationElementEEType = destinationArray.ElementMethodTable;

            if (sourceElementEEType == destinationElementEEType) // This check kicks for different array kind or dimensions
                return ArrayAssignType.SimpleCopy;

            if (sourceElementEEType->IsPointer || sourceElementEEType->IsFunctionPointer
                || destinationElementEEType->IsPointer || destinationElementEEType->IsFunctionPointer)
            {
                // Compatible pointers
                if (RuntimeImports.AreTypesAssignable(sourceElementEEType, destinationElementEEType))
                    return ArrayAssignType.SimpleCopy;
                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;
            }

            // Copying primitives from one type to another
            if (sourceElementEEType->IsPrimitive && destinationElementEEType->IsPrimitive)
            {
                EETypeElementType sourceElementType = sourceElementEEType->ElementType;
                EETypeElementType destElementType = destinationElementEEType->ElementType;

                if (TypeCast.GetNormalizedIntegralArrayElementType(sourceElementType) == TypeCast.GetNormalizedIntegralArrayElementType(destElementType))
                    return ArrayAssignType.SimpleCopy;
                else if (InvokeUtils.CanPrimitiveWiden(destElementType, sourceElementType))
                    return ArrayAssignType.PrimitiveWiden;
                else
                    return ArrayAssignType.WrongType;
            }

            // Different value types
            if (sourceElementEEType->IsValueType && destinationElementEEType->IsValueType)
            {
                // Different from CanCastTo in coreclr, AreTypesAssignable also allows T -> Nullable<T> conversion.
                // Kick for this path explicitly.
                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;

            return ArrayAssignType.WrongType;
        }

        public unsafe int Rank
        {
            get
            {
                return this.GetMethodTable()->ArrayRank;
            }
        }

        internal unsafe object? InternalGetValue(nint flattenedIndex)
        {
            Debug.Assert((nuint)flattenedIndex < NativeLength);

            if (ElementMethodTable->IsPointer || ElementMethodTable->IsFunctionPointer)
                throw new NotSupportedException(SR.NotSupported_Type);

            ref byte element = ref Unsafe.AddByteOffset(ref MemoryMarshal.GetArrayDataReference(this), (nuint)flattenedIndex * ElementSize);

            MethodTable* pElementEEType = ElementMethodTable;
            if (pElementEEType->IsValueType)
            {
                return RuntimeExports.RhBox(pElementEEType, ref element);
            }
            else
            {
                Debug.Assert(!pElementEEType->IsPointer && !pElementEEType->IsFunctionPointer);
                return Unsafe.As<byte, object>(ref element);
            }
        }

        private unsafe void InternalSetValue(object? value, nint flattenedIndex)
        {
            Debug.Assert((nuint)flattenedIndex < NativeLength);

            ref byte element = ref Unsafe.AddByteOffset(ref MemoryMarshal.GetArrayDataReference(this), (nuint)flattenedIndex * ElementSize);

            MethodTable* pElementEEType = ElementMethodTable;
            if (pElementEEType->IsValueType)
            {
                // Unlike most callers of InvokeUtils.ChangeType(), Array.SetValue() does *not* permit conversion from a primitive to an Enum.
                if (value != null && !(value.GetMethodTable() == pElementEEType) && pElementEEType->IsEnum)
                    throw new InvalidCastException(SR.Format(SR.Arg_ObjObjEx, value.GetType(), Type.GetTypeFromHandle(new RuntimeTypeHandle(pElementEEType))));

                value = InvokeUtils.CheckArgument(value, pElementEEType, InvokeUtils.CheckArgumentSemantics.ArraySet, binderBundle: null);
                Debug.Assert(value == null || RuntimeImports.AreTypesAssignable(value.GetMethodTable(), pElementEEType));

                RuntimeImports.RhUnbox(value, ref element, pElementEEType);
            }
            else if (pElementEEType->IsPointer || pElementEEType->IsFunctionPointer)
            {
                throw new NotSupportedException(SR.NotSupported_Type);
            }
            else
            {
                try
                {
                    RuntimeImports.RhCheckArrayStore(this, value);
                }
                catch (ArrayTypeMismatchException)
                {
                    throw new InvalidCastException(SR.InvalidCast_StoreArrayElement);
                }
                Unsafe.As<byte, object?>(ref element) = value;
            }
        }

        internal unsafe MethodTable* ElementMethodTable => this.GetMethodTable()->RelatedParameterType;

        internal unsafe CorElementType GetCorElementTypeOfElementType()
        {
            return new EETypePtr(ElementMethodTable).CorElementType;
        }

        internal unsafe bool IsValueOfElementType(object o)
        {
            return ElementMethodTable == o.GetMethodTable();
        }

        //
        // Return storage size of an individual element in bytes.
        //
        internal unsafe nuint ElementSize
        {
            get
            {
                return this.GetMethodTable()->ComponentSize;
            }
        }

        private static int IndexOfImpl<T>(T[] array, T value, int startIndex, int count)
        {
            // See comment in EqualityComparerHelpers.GetComparerForReferenceTypesOnly for details
            EqualityComparer<T> comparer = EqualityComparerHelpers.GetComparerForReferenceTypesOnly<T>();

            int endIndex = startIndex + count;
            if (comparer != null)
            {
                for (int i = startIndex; i < endIndex; i++)
                {
                    if (comparer.Equals(array[i], value))
                        return i;
                }
            }
            else
            {
                for (int i = startIndex; i < endIndex; i++)
                {
                    if (EqualityComparerHelpers.StructOnlyEquals<T>(array[i], value))
                        return i;
                }
            }

            return -1;
        }

        private static int LastIndexOfImpl<T>(T[] array, T value, int startIndex, int count)
        {
            // See comment in EqualityComparerHelpers.GetComparerForReferenceTypesOnly for details
            EqualityComparer<T> comparer = EqualityComparerHelpers.GetComparerForReferenceTypesOnly<T>();

            int endIndex = startIndex - count + 1;
            if (comparer != null)
            {
                for (int i = startIndex; i >= endIndex; i--)
                {
                    if (comparer.Equals(array[i], value))
                        return i;
                }
            }
            else
            {
                for (int i = startIndex; i >= endIndex; i--)
                {
                    if (EqualityComparerHelpers.StructOnlyEquals<T>(array[i], value))
                        return i;
                }
            }

            return -1;
        }
    }

    //
    // Note: the declared base type and interface list also determines what Reflection returns from TypeInfo.BaseType and TypeInfo.ImplementedInterfaces for array types.
    //
    [Intrinsic]
    internal class Array<T> : Array, IEnumerable<T>, ICollection<T>, IList<T>, IReadOnlyList<T>
    {
        // Prevent the C# compiler from generating a public default constructor
        private Array() { }

        [Intrinsic]
        public new IEnumerator<T> GetEnumerator()
        {
            T[] @this = Unsafe.As<T[]>(this);
            // get length so we don't have to call the Length property again in ArrayEnumerator constructor
            // and avoid more checking there too.
            int length = @this.Length;
            return length == 0 ? SZGenericArrayEnumerator<T>.Empty : new SZGenericArrayEnumerator<T>(@this, length);
        }

        public int Count
        {
            get
            {
                return Unsafe.As<T[]>(this).Length;
            }
        }

        //
        // Fun fact:
        //
        //  ((int[])a).IsReadOnly returns false.
        //  ((IList<int>)a).IsReadOnly returns true.
        //
        public new bool IsReadOnly
        {
            get
            {
                return true;
            }
        }

        public void Add(T item)
        {
            ThrowHelper.ThrowNotSupportedException();
        }

        public void Clear()
        {
            ThrowHelper.ThrowNotSupportedException();
        }

        public bool Contains(T item)
        {
            T[] @this = Unsafe.As<T[]>(this);
            return Array.IndexOf(@this, item, 0, @this.Length) >= 0;
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            T[] @this = Unsafe.As<T[]>(this);
            Array.Copy(@this, 0, array, arrayIndex, @this.Length);
        }

        public bool Remove(T item)
        {
            ThrowHelper.ThrowNotSupportedException();
            return false; // unreachable
        }

        public T this[int index]
        {
            get
            {
                try
                {
                    return Unsafe.As<T[]>(this)[index];
                }
                catch (IndexOutOfRangeException)
                {
                    ThrowHelper.ThrowArgumentOutOfRange_IndexMustBeLessException();
                    return default; // unreachable
                }
            }
            set
            {
                try
                {
                    Unsafe.As<T[]>(this)[index] = value;
                }
                catch (IndexOutOfRangeException)
                {
                    ThrowHelper.ThrowArgumentOutOfRange_IndexMustBeLessException();
                }
            }
        }

        public int IndexOf(T item)
        {
            T[] @this = Unsafe.As<T[]>(this);
            return Array.IndexOf(@this, item, 0, @this.Length);
        }

        public void Insert(int index, T item)
        {
            ThrowHelper.ThrowNotSupportedException();
        }

        public void RemoveAt(int index)
        {
            ThrowHelper.ThrowNotSupportedException();
        }
    }

    public class MDArray
    {
        public const int MinRank = 1;
        public const int MaxRank = 32;
    }
}