Rewrite a few more array FCalls in C# (dotnet/coreclr#27603)
authorJan Kotas <jkotas@microsoft.com>
Sat, 2 Nov 2019 04:39:41 +0000 (21:39 -0700)
committerGitHub <noreply@github.com>
Sat, 2 Nov 2019 04:39:41 +0000 (21:39 -0700)
Commit migrated from https://github.com/dotnet/coreclr/commit/cbe5ac16fedcccba1d4d90fd7329d92835265b97

src/coreclr/src/System.Private.CoreLib/src/System/Array.CoreCLR.cs
src/coreclr/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs
src/coreclr/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/jithelpers.cs
src/coreclr/src/classlibnative/bcltype/arraynative.cpp
src/coreclr/src/classlibnative/bcltype/arraynative.h
src/coreclr/src/vm/ecalllist.h

index c9154fa..1a65a8f 100644 (file)
@@ -178,24 +178,34 @@ namespace System
             if (array == null)
                 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
 
-            ref byte p = ref GetRawArrayGeometry(array, out uint numComponents, out uint elementSize, out int lowerBound, out bool containsGCPointers);
+            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) > numComponents)
+            if (index < lowerBound || offset < 0 || length < 0 || (uint)(offset + length) > (uint)array.Length)
                 ThrowHelper.ThrowIndexOutOfRangeException();
 
+            uint elementSize = pMT->ComponentSize;
+
             ref byte ptr = ref Unsafe.AddByteOffset(ref p, (uint)offset * (nuint)elementSize);
             nuint byteLength = (uint)length * (nuint)elementSize;
 
-            if (containsGCPointers)
+            if (pMT->ContainsGCPointers)
                 SpanHelpers.ClearWithReferences(ref Unsafe.As<byte, IntPtr>(ref ptr), byteLength / (uint)sizeof(IntPtr));
             else
                 SpanHelpers.ClearWithoutReferences(ref ptr, byteLength);
-        }
 
-        [MethodImpl(MethodImplOptions.InternalCall)]
-        private static extern ref byte GetRawArrayGeometry(Array array, out uint numComponents, out uint elementSize, out int lowerBound, out bool containsGCPointers);
+            // GC.KeepAlive(array) not required. pMT kept alive via `ptr`
+        }
 
         // The various Get values...
         public unsafe object? GetValue(params int[] indices)
@@ -342,20 +352,51 @@ namespace System
 
         public long LongLength => Unsafe.As<RawArrayData>(this).Length;
 
-        [MethodImpl(MethodImplOptions.InternalCall)]
-        public extern int GetLength(int dimension);
+        public unsafe int Rank
+        {
+            get
+            {
+                int rank = RuntimeHelpers.GetMultiDimensionalArrayRank(this);
+                return (rank != 0) ? rank : 1;
+            }
+        }
 
-        public extern int Rank
+        public unsafe int GetLength(int dimension)
         {
-            [MethodImpl(MethodImplOptions.InternalCall)]
-            get;
+            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);
         }
 
-        [MethodImpl(MethodImplOptions.InternalCall)]
-        public extern int GetUpperBound(int dimension);
+        public unsafe int GetUpperBound(int dimension)
+        {
+            int rank = RuntimeHelpers.GetMultiDimensionalArrayRank(this);
+            if (rank == 0 && dimension == 0)
+                return Length - 1;
 
-        [MethodImpl(MethodImplOptions.InternalCall)]
-        public extern int GetLowerBound(int dimension);
+            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;
+        }
+
+        public unsafe 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)]
         private static extern bool TrySZBinarySearch(Array sourceArray, int sourceIndex, int count, object? value, out int retVal);
index 02b2934..0ea8682 100644 (file)
@@ -143,6 +143,9 @@ namespace System.Runtime.CompilerServices
         [MethodImpl(MethodImplOptions.InternalCall)]
         public static extern bool TryEnsureSufficientExecutionStack();
 
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern object GetUninitializedObjectInternal(Type type);
+
         /// <returns>true if given type is reference type or value type that contains references</returns>
         [Intrinsic]
         public static bool IsReferenceOrContainsReferences<T>()
@@ -171,51 +174,55 @@ namespace System.Runtime.CompilerServices
         internal static ref byte GetRawSzArrayData(this Array array) =>
             ref Unsafe.As<RawArrayData>(array).Data;
 
-        // CLR arrays are laid out in memory as follows (multidimensional array bounds are optional):
-        // [ sync block || pMethodTable || num components || MD array bounds || array data .. ]
-        //                 ^               ^                                    ^ returned reference
-        //                 |               \-- ref Unsafe.As<RawData>(array).Data
-        //                 \-- array
-        // The BaseSize of an array includes all the fields before the array data,
-        // including the sync block and method table. The reference to RawData.Data
-        // points at the number of components, skipping over these two pointer-sized fields.
-        // So substrate those from BaseSize before adding to the RawData.Data reference.
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal static unsafe ref byte GetRawArrayData(this Array array) =>
-            ref Unsafe.AddByteOffset(ref Unsafe.As<RawData>(array).Data, (nuint)GetObjectMethodTablePointer(array)->BaseSize - (nuint)(2 * sizeof(IntPtr)));
+            // See comment on RawArrayData for details
+            ref Unsafe.AddByteOffset(ref Unsafe.As<RawData>(array).Data, (nuint)GetMethodTable(array)->BaseSize - (nuint)(2 * sizeof(IntPtr)));
 
         internal static unsafe ushort GetElementSize(this Array array)
         {
             Debug.Assert(ObjectHasComponentSize(array));
-            return GetObjectMethodTablePointer(array)->ComponentSize;
+            return GetMethodTable(array)->ComponentSize;
         }
 
-        // Returns true iff the object has a component size;
-        // i.e., is variable length like System.String or Array.
+        // Returns pointer to the multi-dimensional array bounds.
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        internal static unsafe bool ObjectHasComponentSize(object obj)
+        internal static unsafe ref int GetMultiDimensionalArrayBounds(Array array)
         {
-            // The Flags field of the method table class will have its high bit set if the
-            // method table has component size info stored somewhere. See member
-            // MethodTable:IsStringOrArray in src\vm\methodtable.h for full details.
-            return (int)GetObjectMethodTablePointer(obj)->Flags < 0;
+            Debug.Assert(GetMultiDimensionalArrayRank(array) > 0);
+            // See comment on RawArrayData for details
+            return ref Unsafe.As<byte, int>(ref Unsafe.As<RawArrayData>(array).Data);
         }
 
-        // Subset of src\vm\methodtable.h
-        [StructLayout(LayoutKind.Explicit)]
-        private struct MethodTable
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static unsafe int GetMultiDimensionalArrayRank(Array array)
         {
-            [FieldOffset(0)]
-            public ushort ComponentSize;
-            [FieldOffset(0)]
-            public uint Flags;
-            [FieldOffset(4)]
-            public uint BaseSize;
+            int rank = GetMethodTable(array)->MultiDimensionalArrayRank;
+            GC.KeepAlive(array); // Keep MethodTable alive
+            return rank;
+        }
+
+        // Returns true iff the object has a component size;
+        // i.e., is variable length like System.String or Array.
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static unsafe bool ObjectHasComponentSize(object obj)
+        {
+            return GetMethodTable(obj)->HasComponentSize;
         }
 
         // Given an object reference, returns its MethodTable*.
+        //
+        // WARNING: The caller has to ensure that MethodTable* does not get unloaded. The most robust way
+        // to achieve this is by using GC.KeepAlive on the object that the MethodTable* was fetched from, e.g.:
+        //
+        // MethodTable* pMT = GetMethodTable(o);
+        //
+        // ... work with pMT ...
+        //
+        // GC.KeepAlive(o);
+        //
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private static unsafe MethodTable* GetObjectMethodTablePointer(object obj)
+        internal static unsafe MethodTable* GetMethodTable(object obj)
         {
             Debug.Assert(obj != null);
 
@@ -232,8 +239,84 @@ namespace System.Runtime.CompilerServices
             // Ideally this would just be a single dereference:
             // mov tmp, qword ptr [rax] ; rax = obj ref, tmp = MethodTable* pointer
         }
+    }
 
-        [MethodImpl(MethodImplOptions.InternalCall)]
-        private static extern object GetUninitializedObjectInternal(Type type);
+    // Helper class to assist with unsafe pinning of arbitrary objects.
+    // It's used by VM code.
+    internal class RawData
+    {
+        public byte Data;
+    }
+
+    // CLR arrays are laid out in memory as follows (multidimensional array bounds are optional):
+    // [ sync block || pMethodTable || num components || MD array bounds || array data .. ]
+    //                 ^               ^                                    ^ returned reference
+    //                 |               \-- ref Unsafe.As<RawData>(array).Data
+    //                 \-- array
+    // The BaseSize of an array includes all the fields before the array data,
+    // including the sync block and method table. The reference to RawData.Data
+    // points at the number of components, skipping over these two pointer-sized fields.
+    internal class RawArrayData
+    {
+        public uint Length; // Array._numComponents padded to IntPtr
+#if BIT64
+        public uint Padding;
+#endif
+        public byte Data;
+    }
+
+    // Subset of src\vm\methodtable.h
+    [StructLayout(LayoutKind.Explicit)]
+    internal unsafe struct MethodTable
+    {
+        [FieldOffset(0)]
+        public ushort ComponentSize;
+        [FieldOffset(0)]
+        private uint Flags;
+        [FieldOffset(4)]
+        public uint BaseSize;
+
+        // WFLAGS_HIGH_ENUM
+        private const uint enum_flag_ContainsPointers = 0x01000000;
+        private const uint enum_flag_HasComponentSize = 0x80000000;
+
+        public bool HasComponentSize
+        {
+            get
+            {
+                return (Flags & enum_flag_HasComponentSize) != 0;
+            }
+        }
+
+        public bool ContainsGCPointers
+        {
+            get
+            {
+                return (Flags & enum_flag_ContainsPointers) != 0;
+            }
+        }
+
+        public bool IsMultiDimensionalArray
+        {
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            get
+            {
+                Debug.Assert(HasComponentSize);
+                // See comment on RawArrayData for details
+                return BaseSize > (uint)(3 * sizeof(IntPtr));
+            }
+        }
+
+        // Returns rank of multi-dimensional array rank, 0 for sz arrays
+        public int MultiDimensionalArrayRank
+        {
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            get
+            {
+                Debug.Assert(HasComponentSize);
+                // See comment on RawArrayData for details
+                return (int)((BaseSize - (uint)(3 * sizeof(IntPtr))) / (uint)(2 * sizeof(int)));
+            }
+        }
     }
 }
index 5170499..ca6d188 100644 (file)
@@ -87,22 +87,6 @@ namespace System.Runtime.CompilerServices
         }
     }
 
-    // Helper class to assist with unsafe pinning of arbitrary objects.
-    // It's used by VM code.
-    internal class RawData
-    {
-        public byte Data;
-    }
-
-    internal class RawArrayData
-    {
-        public uint Length; // Array._numComponents padded to IntPtr
-#if BIT64
-        public uint Padding;
-#endif
-        public byte Data;
-    }
-
     internal static unsafe class JitHelpers
     {
         // The special dll name to be used for DllImport of QCalls
index a02048a..34b7cb6 100644 (file)
 
 #include "arraynative.inl"
 
-FCIMPL1(INT32, ArrayNative::GetRank, ArrayBase* array)
-{
-    FCALL_CONTRACT;
-
-    VALIDATEOBJECT(array);
-
-    if (array == NULL)
-        FCThrow(kNullReferenceException);
-
-    return array->GetRank();
-}
-FCIMPLEND
-
-
-FCIMPL2(INT32, ArrayNative::GetLowerBound, ArrayBase* array, unsigned int dimension)
-{
-    FCALL_CONTRACT;
-
-    VALIDATEOBJECT(array);
-
-    if (array == NULL)
-        FCThrow(kNullReferenceException);
-    
-    if (dimension != 0)
-    {
-        // Check the dimension is within our rank
-        unsigned int rank = array->GetRank();
-    
-        if (dimension >= rank)
-            FCThrowRes(kIndexOutOfRangeException, W("IndexOutOfRange_ArrayRankIndex"));
-    }
-
-    return array->GetLowerBoundsPtr()[dimension];
-}
-FCIMPLEND
-
-
-// Get inclusive upper bound
-FCIMPL2(INT32, ArrayNative::GetUpperBound, ArrayBase* array, unsigned int dimension)
-{
-    FCALL_CONTRACT;
-
-    VALIDATEOBJECT(array);
-
-    if (array == NULL)
-        FCThrow(kNullReferenceException);
-    
-    if (dimension != 0)
-    {
-        // Check the dimension is within our rank
-        unsigned int rank = array->GetRank();
-    
-        if (dimension >= rank)
-            FCThrowRes(kIndexOutOfRangeException, W("IndexOutOfRange_ArrayRankIndex"));
-    }
-
-    return array->GetBoundsPtr()[dimension] + array->GetLowerBoundsPtr()[dimension] - 1;
-}
-FCIMPLEND
-
-
-FCIMPL2(INT32, ArrayNative::GetLength, ArrayBase* array, unsigned int dimension)
-{
-    FCALL_CONTRACT;
-
-    VALIDATEOBJECT(array);
-
-    if (array==NULL)
-        FCThrow(kNullReferenceException);
-    
-    if (dimension != 0)
-    {
-        // Check the dimension is within our rank
-        unsigned int rank = array->GetRank();
-        if (dimension >= rank)
-            FCThrow(kIndexOutOfRangeException);
-    }
-    
-    return array->GetBoundsPtr()[dimension];
-}
-FCIMPLEND
-
-
 // array is GC protected by caller
 void ArrayInitializeWorker(ARRAYBASEREF * arrayRef,
                            MethodTable* pArrayMT,
@@ -1011,25 +928,6 @@ FCIMPL6(void, ArrayNative::ArrayCopy, ArrayBase* m_pSrc, INT32 m_iSrcIndex, Arra
 FCIMPLEND
 
 
-FCIMPL5(void*, ArrayNative::GetRawArrayGeometry, ArrayBase* pArray, UINT32* pNumComponents, UINT32* pElementSize, INT32* pLowerBound, CLR_BOOL* pContainsGCPointers)
-{
-   VALIDATEOBJECT(pArray);
-
-   _ASSERTE(pArray != NULL);
-
-    MethodTable *pMT = pArray->GetMethodTable();
-
-    *pNumComponents = pArray->GetNumComponents();
-    *pElementSize = pMT->RawGetComponentSize();
-    *pLowerBound = pArray->GetLowerBoundsPtr()[0];
-    *pContainsGCPointers = !!pMT->ContainsPointers();
-
-    return (BYTE*)pArray + ArrayBase::GetDataPtrOffset(pMT);
-}
-FCIMPLEND
-
-
-
 // Check we're allowed to create an array with the given element type.
 void ArrayNative::CheckElementType(TypeHandle elementType)
 {
index b3135cb..02197bf 100644 (file)
@@ -25,16 +25,10 @@ struct FCALLRuntimeFieldHandle
 class ArrayNative
 {
 public:
-    static FCDECL1(INT32, GetRank, ArrayBase* pArray);
-    static FCDECL2(INT32, GetLowerBound, ArrayBase* pArray, unsigned int dimension);
-    static FCDECL2(INT32, GetUpperBound, ArrayBase* pArray, unsigned int dimension);
-    static FCDECL2(INT32, GetLength, ArrayBase* pArray, unsigned int dimension);
     static FCDECL1(void, Initialize, ArrayBase* pArray);
 
     static FCDECL6(void, ArrayCopy, ArrayBase* m_pSrc, INT32 m_iSrcIndex, ArrayBase* m_pDst, INT32 m_iDstIndex, INT32 m_iLength, CLR_BOOL reliable);
 
-    static FCDECL5(void*, GetRawArrayGeometry, ArrayBase* pArray, UINT32* pNumComponents, UINT32* pElementSize, INT32* pLowerBound, CLR_BOOL* pContainsGCPointers);
-
     // This method will create a new array of type type, with zero lower
     // bounds and rank.
     static FCDECL4(Object*, CreateInstance, void* elementTypeHandle, INT32 rank, INT32* pLengths, INT32* pBounds);
index 72201fa..56de255 100644 (file)
@@ -715,13 +715,8 @@ FCFuncStart(gClrConfig)
 FCFuncEnd()
 
 FCFuncStart(gArrayFuncs)
-    FCFuncElement("get_Rank", ArrayNative::GetRank)
-    FCFuncElement("GetLowerBound", ArrayNative::GetLowerBound)
-    FCFuncElement("GetUpperBound", ArrayNative::GetUpperBound)
-    FCIntrinsicSig("GetLength", &gsig_IM_Int_RetInt, ArrayNative::GetLength, CORINFO_INTRINSIC_Array_GetDimLength)
     FCFuncElement("Initialize", ArrayNative::Initialize)
     FCFuncElement("Copy", ArrayNative::ArrayCopy)
-    FCFuncElement("GetRawArrayGeometry", ArrayNative::GetRawArrayGeometry)
     FCFuncElement("InternalCreate", ArrayNative::CreateInstance)
     FCFuncElement("InternalGetReference", ArrayNative::GetReference)
     FCFuncElement("InternalSetValue", ArrayNative::SetValue)
@@ -1141,8 +1136,6 @@ FCFuncStart(gPalKernel32Funcs)
     QCFuncElement("OpenMutex", OpenMutexW)
     QCFuncElement("OpenSemaphore", OpenSemaphoreW)
     QCFuncElement("OutputDebugString", OutputDebugStringW)
-    QCFuncElement("QueryPerformanceCounter", QueryPerformanceCounter)
-    QCFuncElement("QueryPerformanceFrequency", QueryPerformanceFrequency)
     QCFuncElement("ReleaseMutex", ReleaseMutex)
     QCFuncElement("ReleaseSemaphore", ReleaseSemaphore)
     QCFuncElement("ResetEvent", ResetEvent)