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)
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);
[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>()
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);
// 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)));
+ }
+ }
}
}
}
}
- // 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
#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,
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)
{
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);
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)
QCFuncElement("OpenMutex", OpenMutexW)
QCFuncElement("OpenSemaphore", OpenSemaphoreW)
QCFuncElement("OutputDebugString", OutputDebugStringW)
- QCFuncElement("QueryPerformanceCounter", QueryPerformanceCounter)
- QCFuncElement("QueryPerformanceFrequency", QueryPerformanceFrequency)
QCFuncElement("ReleaseMutex", ReleaseMutex)
QCFuncElement("ReleaseSemaphore", ReleaseSemaphore)
QCFuncElement("ResetEvent", ResetEvent)