using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
-using System.Runtime.CompilerServices;
using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using Internal.Runtime.CompilerServices;
namespace System
nuint elementSize = (nuint)pMT->ComponentSize;
nuint byteCount = (uint)length * elementSize;
- ref byte src = ref Unsafe.AddByteOffset(ref sourceArray.GetRawArrayData(), (uint)sourceIndex * elementSize);
- ref byte dst = ref Unsafe.AddByteOffset(ref destinationArray.GetRawArrayData(), (uint)destinationIndex * 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);
MethodTable* pMT = RuntimeHelpers.GetMethodTable(array);
nuint totalByteLength = pMT->ComponentSize * array.NativeLength;
- ref byte pStart = ref array.GetRawArrayData();
+ ref byte pStart = ref MemoryMarshal.GetArrayDataReference(array);
if (!pMT->ContainsGCPointers)
{
return rawSize;
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static unsafe ref byte GetRawArrayData(this Array array) =>
- // 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));
namespace System.Runtime.InteropServices
{
- public static partial class MemoryMarshal
+ public static unsafe partial class MemoryMarshal
{
/// <summary>
/// Returns a reference to the 0th element of <paramref name="array"/>. If the array is empty, returns a reference to where the 0th element
/// </remarks>
[Intrinsic]
[NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T GetArrayDataReference<T>(T[] array) =>
ref Unsafe.As<byte, T>(ref Unsafe.As<RawArrayData>(array).Data);
+
+ /// <summary>
+ /// Returns a reference to the 0th element of <paramref name="array"/>. If the array is empty, returns a reference to where the 0th element
+ /// would have been stored. Such a reference may be used for pinning but must never be dereferenced.
+ /// </summary>
+ /// <exception cref="NullReferenceException"><paramref name="array"/> is <see langword="null"/>.</exception>
+ /// <remarks>
+ /// The caller must manually reinterpret the returned <em>ref byte</em> as a ref to the array's underlying elemental type,
+ /// perhaps utilizing an API such as <em>System.Runtime.CompilerServices.Unsafe.As</em> to assist with the reinterpretation.
+ /// This technique does not perform array variance checks. The caller must manually perform any array variance checks
+ /// if the caller wishes to write to the returned reference.
+ /// </remarks>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref byte GetArrayDataReference(Array array)
+ {
+ // If needed, we can save one or two instructions per call by marking this method as intrinsic and asking the JIT
+ // to special-case arrays of known type and dimension.
+
+ // See comment on RawArrayData (in RuntimeHelpers.CoreCLR.cs) for details
+ return ref Unsafe.AddByteOffset(ref Unsafe.As<RawData>(array).Data, (nuint)RuntimeHelpers.GetMethodTable(array)->BaseSize - (nuint)(2 * sizeof(IntPtr)));
+ }
}
}
Debug.Assert(((MetadataType)method.OwningType).Name == "MemoryMarshal");
string methodName = method.Name;
+ if (method.Instantiation.Length != 1)
+ {
+ return null; // we only handle the generic method GetArrayDataReference<T>(T[])
+ }
+
if (methodName == "GetArrayDataReference")
{
ILEmitter emit = new ILEmitter();
DEFINE_METHOD(RUNTIME_HELPERS, IS_BITWISE_EQUATABLE, IsBitwiseEquatable, NoSig)
DEFINE_METHOD(RUNTIME_HELPERS, GET_METHOD_TABLE, GetMethodTable, NoSig)
DEFINE_METHOD(RUNTIME_HELPERS, GET_RAW_DATA, GetRawData, NoSig)
-DEFINE_METHOD(RUNTIME_HELPERS, GET_RAW_ARRAY_DATA, GetRawArrayData, NoSig)
DEFINE_METHOD(RUNTIME_HELPERS, GET_UNINITIALIZED_OBJECT, GetUninitializedObject, SM_Type_RetObj)
DEFINE_METHOD(RUNTIME_HELPERS, ENUM_EQUALS, EnumEquals, NoSig)
DEFINE_METHOD(RUNTIME_HELPERS, ENUM_COMPARE_TO, EnumCompareTo, NoSig)
DEFINE_METHOD(UNSAFE, SKIPINIT, SkipInit, GM_RefT_RetVoid)
DEFINE_CLASS(MEMORY_MARSHAL, Interop, MemoryMarshal)
-DEFINE_METHOD(MEMORY_MARSHAL, GET_ARRAY_DATA_REFERENCE, GetArrayDataReference, NoSig)
+DEFINE_METHOD(MEMORY_MARSHAL, GET_ARRAY_DATA_REFERENCE_SZARRAY, GetArrayDataReference, GM_ArrT_RetRefT)
+DEFINE_METHOD(MEMORY_MARSHAL, GET_ARRAY_DATA_REFERENCE_MDARRAY, GetArrayDataReference, SM_Array_RetRefByte)
DEFINE_CLASS(INTERLOCKED, Threading, Interlocked)
DEFINE_METHOD(INTERLOCKED, COMPARE_EXCHANGE_T, CompareExchange, GM_RefT_T_T_RetT)
EmitLoadNativeValue(pslILEmit); // dest
pslILEmit->EmitLDLOC(m_dwPinnedLocalNum);
- pslILEmit->EmitCALL(METHOD__RUNTIME_HELPERS__GET_RAW_ARRAY_DATA, 1, 1);
+ pslILEmit->EmitCALL(METHOD__MEMORY_MARSHAL__GET_ARRAY_DATA_REFERENCE_MDARRAY, 1, 1);
pslILEmit->EmitCONV_I();
EmitLoadManagedValue(pslILEmit);
pslILEmit->EmitSTLOC(m_dwPinnedLocalNum);
pslILEmit->EmitLDLOC(m_dwPinnedLocalNum);
- pslILEmit->EmitCALL(METHOD__RUNTIME_HELPERS__GET_RAW_ARRAY_DATA, 1, 1);
+ pslILEmit->EmitCALL(METHOD__MEMORY_MARSHAL__GET_ARRAY_DATA_REFERENCE_MDARRAY, 1, 1);
pslILEmit->EmitCONV_I();
pslILEmit->EmitLDLOC(m_dwOffsetLocalNum);
mdMethodDef tk = ftn->GetMemberDef();
- if (tk == CoreLibBinder::GetMethod(METHOD__MEMORY_MARSHAL__GET_ARRAY_DATA_REFERENCE)->GetMemberDef())
+ if (tk == CoreLibBinder::GetMethod(METHOD__MEMORY_MARSHAL__GET_ARRAY_DATA_REFERENCE_SZARRAY)->GetMemberDef())
{
mdToken tokRawSzArrayData = CoreLibBinder::GetField(FIELD__RAW_ARRAY_DATA__DATA)->GetMemberDef();
DEFINE_METASIG(GM(PtrVoid_Int_RetPtrVoid, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, P(v) i, P(v)))
DEFINE_METASIG(GM(RefT_RetVoid, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, r(M(0)), v))
+DEFINE_METASIG(GM(ArrT_RetRefT, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, a(M(0)), r(M(0))))
+DEFINE_METASIG_T(SM(Array_RetRefByte, C(ARRAY), r(b)))
+
DEFINE_METASIG_T(SM(SafeHandle_RefBool_RetIntPtr, C(SAFE_HANDLE) r(F), I ))
DEFINE_METASIG_T(SM(SafeHandle_RetVoid, C(SAFE_HANDLE), v ))
public static unsafe ReadOnlySpan<char> CreateReadOnlySpanFromNullTerminated(char* value) { throw null; }
public static System.Span<T> CreateSpan<T>(ref T reference, int length) { throw null; }
public static ref T GetArrayDataReference<T>(T[] array) { throw null; }
+ public static ref byte GetArrayDataReference(System.Array array) { throw null; }
public static ref T GetReference<T>(System.ReadOnlySpan<T> span) { throw null; }
public static ref T GetReference<T>(System.Span<T> span) { throw null; }
public static T Read<T>(System.ReadOnlySpan<byte> source) where T : struct { throw null; }
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using Xunit;
+using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using Xunit;
namespace System.SpanTests
{
[ActiveIssue("https://github.com/dotnet/runtime/issues/36885", TestPlatforms.tvOS)]
public static void GetArrayDataReference_NullInput_ThrowsNullRef()
{
- Assert.Throws<NullReferenceException>(() => MemoryMarshal.GetArrayDataReference((object[])null));
+ Assert.Throws<NullReferenceException>(() => MemoryMarshal.GetArrayDataReference<object>((object[])null));
+ Assert.Throws<NullReferenceException>(() => MemoryMarshal.GetArrayDataReference((Array)null));
}
[Fact]
public static void GetArrayDataReference_NonEmptyInput_ReturnsRefToFirstElement()
{
- int[] theArray = new int[] { 10, 20, 30 };
- Assert.True(Unsafe.AreSame(ref theArray[0], ref MemoryMarshal.GetArrayDataReference(theArray)));
+ // szarray
+ int[] szArray = new int[] { 10, 20, 30 };
+ Assert.True(Unsafe.AreSame(ref szArray[0], ref MemoryMarshal.GetArrayDataReference(szArray)));
+ Assert.True(Unsafe.AreSame(ref szArray[0], ref Unsafe.As<byte, int>(ref MemoryMarshal.GetArrayDataReference((Array)szArray))));
+
+ // mdarray, rank 2
+ int[,] mdArrayRank2 = new int[3, 2];
+ Assert.True(Unsafe.AreSame(ref mdArrayRank2[0, 0], ref Unsafe.As<byte, int>(ref MemoryMarshal.GetArrayDataReference(mdArrayRank2))));
+
+ // mdarray, custom bounds
+ // there's no baseline way to get a ref to element (10, 20, 30), so we'll deref the result of GetArrayDataReference and compare
+ Array mdArrayCustomBounds = Array.CreateInstance(typeof(int), new[] { 1, 2, 3 }, new int[] { 10, 20, 30 });
+ mdArrayCustomBounds.SetValue(0x12345678, new[] { 10, 20, 30 });
+ Assert.Equal(0x12345678, Unsafe.As<byte, int>(ref MemoryMarshal.GetArrayDataReference(mdArrayCustomBounds)));
}
[Fact]
- public static unsafe void GetArrayDataReference_EmptyInput_ReturnsRefToWhereFirstElementWouldBe()
+ public static unsafe void GetArrayDataReference_EmptyInput_ReturnsRefToWhereFirstElementWouldBe_SzArray()
{
int[] theArray = new int[0];
Assert.True(Unsafe.AsPointer(ref theRef) != null);
Assert.True(Unsafe.AreSame(ref theRef, ref MemoryMarshal.GetReference(theArray.AsSpan())));
+
+ ref int theMdArrayRef = ref Unsafe.As<byte, int>(ref MemoryMarshal.GetArrayDataReference((Array)theArray)); // szarray passed to generalized Array helper
+ Assert.True(Unsafe.AreSame(ref theRef, ref theMdArrayRef));
+ }
+
+ [Theory]
+ [InlineData(1)]
+ [InlineData(2)]
+ [InlineData(3)]
+ [InlineData(4)]
+ public static unsafe void GetArrayDataReference_EmptyInput_ReturnsRefToWhereFirstElementWouldBe_MdArray(int rank)
+ {
+ // First, compute how much distance there is between the start of the object data and the first element
+ // of a baseline (non-empty) array.
+
+ int[] lowerDims = Enumerable.Range(100, rank).ToArray();
+ int[] lengths = Enumerable.Range(1, rank).ToArray();
+
+ Array baselineArray = Array.CreateInstance(typeof(int), lengths, lowerDims);
+ IntPtr baselineOffset = Unsafe.ByteOffset(ref Unsafe.As<RawObject>(baselineArray).Data, ref MemoryMarshal.GetArrayDataReference(baselineArray));
+
+ // Then, perform the same calculation with an empty array of equal rank, and ensure the offsets are identical.
+
+ lengths = new int[rank]; // = { 0, 0, 0, ... }
+
+ Array emptyArray = Array.CreateInstance(typeof(int), lengths, lowerDims);
+ IntPtr emptyArrayOffset = Unsafe.ByteOffset(ref Unsafe.As<RawObject>(emptyArray).Data, ref MemoryMarshal.GetArrayDataReference(emptyArray));
+
+ Assert.Equal(baselineOffset, emptyArrayOffset);
}
[Fact]
Assert.True(Unsafe.AreSame(ref refObj, ref Unsafe.As<string, object>(ref strArr[0])));
}
+
+ private sealed class RawObject
+ {
+ internal byte Data;
+ }
}
}
}
private static Span<T> UnsafeArrayAsSpan<T>(Array array, int adjustedIndex, int length) =>
- new Span<T>(ref Unsafe.As<byte, T>(ref array.GetRawArrayData()), array.Length).Slice(adjustedIndex, length);
+ new Span<T>(ref Unsafe.As<byte, T>(ref MemoryMarshal.GetArrayDataReference(array)), array.Length).Slice(adjustedIndex, length);
public IEnumerator GetEnumerator()
{
if ((uSrcLen < uSrcOffset + uCount) || (uDstLen < uDstOffset + uCount))
throw new ArgumentException(SR.Argument_InvalidOffLen);
- Memmove(ref Unsafe.AddByteOffset(ref dst.GetRawArrayData(), uDstOffset), ref Unsafe.AddByteOffset(ref src.GetRawArrayData(), uSrcOffset), uCount);
+ Memmove(ref Unsafe.AddByteOffset(ref MemoryMarshal.GetArrayDataReference(dst), uDstOffset), ref Unsafe.AddByteOffset(ref MemoryMarshal.GetArrayDataReference(src), uSrcOffset), uCount);
}
public static int ByteLength(Array array)
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index);
}
- return Unsafe.Add<byte>(ref array.GetRawArrayData(), index);
+ return Unsafe.Add<byte>(ref MemoryMarshal.GetArrayDataReference(array), index);
}
public static void SetByte(Array array, int index, byte value)
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index);
}
- Unsafe.Add<byte>(ref array.GetRawArrayData(), index) = value;
+ Unsafe.Add<byte>(ref MemoryMarshal.GetArrayDataReference(array), index) = value;
}
internal static unsafe void ZeroMemory(byte* dest, nuint len)
}
Debug.Assert(target is Array);
- return (IntPtr)Unsafe.AsPointer(ref Unsafe.As<Array>(target).GetRawArrayData());
+ return (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(Unsafe.As<Array>(target)));
}
return (IntPtr)Unsafe.AsPointer(ref target.GetRawData());
if (arr is null)
throw new ArgumentNullException(nameof(arr));
- void* pRawData = Unsafe.AsPointer(ref arr.GetRawArrayData());
+ void* pRawData = Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(arr));
return (IntPtr)((byte*)pRawData + (uint)index * (nuint)arr.GetElementSize());
}
public partial class Array
{
[StructLayout(LayoutKind.Sequential)]
- private sealed class RawData
+ internal sealed class RawData
{
public IntPtr Bounds;
// The following is to prevent a mismatch between the managed and runtime
if (array == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
- ref byte ptr = ref array.GetRawSzArrayData();
+ ref byte ptr = ref MemoryMarshal.GetArrayDataReference(array);
nuint byteLength = array.NativeLength * (nuint)(uint)array.GetElementSize() /* force zero-extension */;
if (RuntimeHelpers.ObjectHasReferences(array))
if (index < lowerBound || offset < 0 || length < 0 || (uint)(offset + length) > numComponents)
ThrowHelper.ThrowIndexOutOfRangeException();
- ref byte ptr = ref Unsafe.AddByteOffset(ref array.GetRawSzArrayData(), (uint)offset * (nuint)elementSize);
+ ref byte ptr = ref Unsafe.AddByteOffset(ref MemoryMarshal.GetArrayDataReference(array), (uint)offset * (nuint)elementSize);
nuint byteLength = (uint)length * (nuint)elementSize;
if (RuntimeHelpers.ObjectHasReferences(array))
return GetLowerBound(dimension) + GetLength(dimension) - 1;
}
- [Intrinsic]
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal ref byte GetRawSzArrayData()
- {
- // TODO: Missing intrinsic in interpreter
- return ref Unsafe.As<RawData>(this).Data;
- }
-
- [Intrinsic]
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal ref byte GetRawArrayData()
- {
- // TODO: Missing intrinsic in interpreter
- return ref Unsafe.As<RawData>(this).Data;
- }
-
[Intrinsic]
internal int GetElementSize() => GetElementSize();
namespace System.Runtime.InteropServices
{
- public static partial class MemoryMarshal
+ public static unsafe partial class MemoryMarshal
{
/// <summary>
/// Returns a reference to the 0th element of <paramref name="array"/>. If the array is empty, returns a reference to where the 0th element
/// </remarks>
[Intrinsic]
[NonVersionable]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T GetArrayDataReference<T>(T[] array) =>
- ref Unsafe.As<byte, T>(ref array.GetRawArrayData());
+ // Mono uses same layout for SZARRAY and MDARRAY; see comment on Array+RawData (in Array.Mono.cs) for details
+ ref Unsafe.As<byte, T>(ref Unsafe.As<Array.RawData>(array).Data);
+
+ /// <summary>
+ /// Returns a reference to the 0th element of <paramref name="array"/>. If the array is empty, returns a reference to where the 0th element
+ /// would have been stored. Such a reference may be used for pinning but must never be dereferenced.
+ /// </summary>
+ /// <exception cref="NullReferenceException"><paramref name="array"/> is <see langword="null"/>.</exception>
+ /// <remarks>
+ /// The caller must manually reinterpret the returned <em>ref byte</em> as a ref to the array's underlying elemental type,
+ /// perhaps utilizing an API such as <em>System.Runtime.CompilerServices.Unsafe.As</em> to assist with the reinterpretation.
+ /// This technique does not perform array variance checks. The caller must manually perform any array variance checks
+ /// if the caller wishes to write to the returned reference.
+ /// </remarks>
+ [Intrinsic]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref byte GetArrayDataReference(Array array) =>
+ // Mono uses same layout for SZARRAY and MDARRAY; see comment on Array+RawData (in Array.Mono.cs) for details
+ ref Unsafe.As<Array.RawData>(array).Data;
}
}
}
} else if (in_corlib && !strcmp (klass_name_space, "System.Runtime.InteropServices") && !strcmp (klass_name, "MemoryMarshal")) {
if (!strcmp (tm, "GetArrayDataReference"))
- *op = MINT_INTRINS_MEMORYMARSHAL_GETARRAYDATAREF;
+ *op = MINT_INTRINS_MEMORYMARSHAL_GETARRAYDATAREF; // valid for both SZARRAY and MDARRAY
} else if (in_corlib && !strcmp (klass_name_space, "System.Text.Unicode") && !strcmp (klass_name, "Utf16Utility")) {
if (!strcmp (tm, "ConvertAllAsciiCharsInUInt32ToUppercase"))
*op = MINT_INTRINS_ASCII_CHARS_TO_UPPERCASE;
#include <mono/utils/mono-memory-model.h>
static GENERATE_GET_CLASS_WITH_CACHE (runtime_helpers, "System.Runtime.CompilerServices", "RuntimeHelpers")
+static GENERATE_TRY_GET_CLASS_WITH_CACHE (memory_marshal, "System.Runtime.InteropServices", "MemoryMarshal")
static GENERATE_TRY_GET_CLASS_WITH_CACHE (math, "System", "Math")
/* optimize the simple GetGenericValueImpl/SetGenericValueImpl generic calls */
return emit_array_generic_access (cfg, fsig, args, FALSE);
else if (fsig->param_count + fsig->hasthis == 3 && !cfg->gsharedvt && strcmp (cmethod->name, "SetGenericValueImpl") == 0)
return emit_array_generic_access (cfg, fsig, args, TRUE);
- else if (!strcmp (cmethod->name, "GetRawSzArrayData") || !strcmp (cmethod->name, "GetRawArrayData")) {
- int dreg = alloc_preg (cfg);
- MONO_EMIT_NULL_CHECK (cfg, args [0]->dreg, FALSE);
- EMIT_NEW_BIALU_IMM (cfg, ins, OP_PADD_IMM, dreg, args [0]->dreg, MONO_STRUCT_OFFSET (MonoArray, vector));
- return ins;
- }
else if (!strcmp (cmethod->name, "GetElementSize")) {
int vt_reg = alloc_preg (cfg);
int class_reg = alloc_preg (cfg);
return ins;
} else
return NULL;
+ } else if (cmethod->klass == mono_class_try_get_memory_marshal_class ()) {
+ if (!strcmp (cmethod->name, "GetArrayDataReference")) {
+ // Logic below works for both SZARRAY and MDARRAY
+ int dreg = alloc_preg (cfg);
+ MONO_EMIT_NULL_CHECK (cfg, args [0]->dreg, FALSE);
+ EMIT_NEW_BIALU_IMM (cfg, ins, OP_PADD_IMM, dreg, args [0]->dreg, MONO_STRUCT_OFFSET (MonoArray, vector));
+ return ins;
+ }
} else if (cmethod->klass == mono_defaults.monitor_class) {
gboolean is_enter = FALSE;
gboolean is_v4 = FALSE;