diff --git a/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj
index 4684e0a9af4d43..44080e82e54977 100644
--- a/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj
+++ b/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj
@@ -251,6 +251,7 @@
+
diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Array.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Array.CoreCLR.cs
index 62dd79dfb85ad9..4ba8ed5077ca54 100644
--- a/src/coreclr/src/System.Private.CoreLib/src/System/Array.CoreCLR.cs
+++ b/src/coreclr/src/System.Private.CoreLib/src/System/Array.CoreCLR.cs
@@ -150,8 +150,8 @@ public static unsafe void Copy(Array sourceArray, Array destinationArray, int le
(uint)length <= (nuint)destinationArray.LongLength)
{
nuint byteCount = (uint)length * (nuint)pMT->ComponentSize;
- ref byte src = ref sourceArray.GetRawSzArrayData();
- ref byte dst = ref destinationArray.GetRawSzArrayData();
+ ref byte src = ref Unsafe.As(sourceArray).Data;
+ ref byte dst = ref Unsafe.As(destinationArray).Data;
if (pMT->ContainsGCPointers)
Buffer.BulkMoveWithWriteBarrier(ref dst, ref src, byteCount);
@@ -182,8 +182,8 @@ public static unsafe void Copy(Array sourceArray, int sourceIndex, Array destina
{
nuint elementSize = (nuint)pMT->ComponentSize;
nuint byteCount = (uint)length * elementSize;
- ref byte src = ref Unsafe.AddByteOffset(ref sourceArray.GetRawSzArrayData(), (uint)sourceIndex * elementSize);
- ref byte dst = ref Unsafe.AddByteOffset(ref destinationArray.GetRawSzArrayData(), (uint)destinationIndex * elementSize);
+ ref byte src = ref Unsafe.AddByteOffset(ref Unsafe.As(sourceArray).Data, (uint)sourceIndex * elementSize);
+ ref byte dst = ref Unsafe.AddByteOffset(ref Unsafe.As(destinationArray).Data, (uint)destinationIndex * elementSize);
if (pMT->ContainsGCPointers)
Buffer.BulkMoveWithWriteBarrier(ref dst, ref src, byteCount);
diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs
index 19cf8452c50a9a..368f1211455ed5 100644
--- a/src/coreclr/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs
+++ b/src/coreclr/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs
@@ -207,10 +207,6 @@ internal static unsafe nuint GetRawObjectDataSize(object obj)
return rawSize;
}
- [Intrinsic]
- internal static ref byte GetRawSzArrayData(this Array array) =>
- ref Unsafe.As(array).Data;
-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe ref byte GetRawArrayData(this Array array) =>
// See comment on RawArrayData for details
diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Runtime/InteropServices/MemoryMarshal.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Runtime/InteropServices/MemoryMarshal.CoreCLR.cs
new file mode 100644
index 00000000000000..d21e4b2bd8e165
--- /dev/null
+++ b/src/coreclr/src/System.Private.CoreLib/src/System/Runtime/InteropServices/MemoryMarshal.CoreCLR.cs
@@ -0,0 +1,27 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.CompilerServices;
+using System.Runtime.Versioning;
+using Internal.Runtime.CompilerServices;
+
+namespace System.Runtime.InteropServices
+{
+ public static partial class MemoryMarshal
+ {
+ ///
+ /// Returns a reference to the 0th element of . 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.
+ ///
+ /// is .
+ ///
+ /// This method 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.
+ ///
+ [Intrinsic]
+ [NonVersionable]
+ public static ref T GetArrayDataReference(T[] array) =>
+ ref Unsafe.As(ref Unsafe.As(array).Data);
+ }
+}
diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Text/Utf8Span.Conversion.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Text/Utf8Span.Conversion.cs
index 5a692d95595662..48754b0097b3cb 100644
--- a/src/coreclr/src/System.Private.CoreLib/src/System/Text/Utf8Span.Conversion.cs
+++ b/src/coreclr/src/System.Private.CoreLib/src/System/Text/Utf8Span.Conversion.cs
@@ -5,7 +5,7 @@
using System.Buffers;
using System.Diagnostics;
using System.Globalization;
-using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using System.Text.Unicode;
namespace System.Text
@@ -81,12 +81,12 @@ public unsafe char[] ToCharArray()
Debug.Assert(pbUtf8Invalid == pbUtf8 + this.Length, "Invalid UTF-8 data seen in buffer.");
char[] asUtf16 = new char[this.Length + utf16CodeUnitCountAdjustment];
- fixed (byte* pbUtf16 = &asUtf16.GetRawSzArrayData())
+ fixed (char* pbUtf16 = &MemoryMarshal.GetArrayDataReference(asUtf16))
{
- OperationStatus status = Utf8Utility.TranscodeToUtf16(pbUtf8, this.Length, (char*)pbUtf16, asUtf16.Length, out byte* pbUtf8End, out char* pchUtf16End);
+ OperationStatus status = Utf8Utility.TranscodeToUtf16(pbUtf8, this.Length, pbUtf16, asUtf16.Length, out byte* pbUtf8End, out char* pchUtf16End);
Debug.Assert(status == OperationStatus.Done, "The buffer changed out from under us unexpectedly?");
Debug.Assert(pbUtf8End == pbUtf8 + this.Length, "The buffer changed out from under us unexpectedly?");
- Debug.Assert(pchUtf16End == ((char*)pbUtf16) + asUtf16.Length, "The buffer changed out from under us unexpectedly?");
+ Debug.Assert(pchUtf16End == pbUtf16 + asUtf16.Length, "The buffer changed out from under us unexpectedly?");
return asUtf16;
}
diff --git a/src/coreclr/src/tools/Common/TypeSystem/IL/Stubs/MemoryMarshalIntrinsics.cs b/src/coreclr/src/tools/Common/TypeSystem/IL/Stubs/MemoryMarshalIntrinsics.cs
new file mode 100644
index 00000000000000..dc4c6a7a197a99
--- /dev/null
+++ b/src/coreclr/src/tools/Common/TypeSystem/IL/Stubs/MemoryMarshalIntrinsics.cs
@@ -0,0 +1,35 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Internal.TypeSystem;
+
+using Debug = System.Diagnostics.Debug;
+
+namespace Internal.IL.Stubs
+{
+ ///
+ /// Provides method bodies for System.Runtime.InteropServices.MemoryMarshal intrinsics.
+ ///
+ public static class MemoryMarshalIntrinsics
+ {
+ public static MethodIL EmitIL(MethodDesc method)
+ {
+ Debug.Assert(((MetadataType)method.OwningType).Name == "MemoryMarshal");
+ string methodName = method.Name;
+
+ if (methodName == "GetArrayDataReference")
+ {
+ ILEmitter emit = new ILEmitter();
+ ILCodeStream codeStream = emit.NewCodeStream();
+ codeStream.EmitLdArg(0);
+ codeStream.Emit(ILOpcode.ldflda, emit.NewToken(method.Context.SystemModule.GetKnownType("System.Runtime.CompilerServices", "RawArrayData").GetField("Data")));
+ codeStream.Emit(ILOpcode.ret);
+ return emit.Link(method);
+ }
+
+ // unknown method
+ return null;
+ }
+ }
+}
diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/IL/ReadyToRunILProvider.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/IL/ReadyToRunILProvider.cs
index 44f6b791de4bea..311aed6f3d13b4 100644
--- a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/IL/ReadyToRunILProvider.cs
+++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/IL/ReadyToRunILProvider.cs
@@ -54,6 +54,11 @@ private MethodIL TryGetIntrinsicMethodIL(MethodDesc method)
return UnsafeIntrinsics.EmitIL(method);
}
+ if (mdType.Name == "MemoryMarshal" && mdType.Namespace == "System.Runtime.InteropServices")
+ {
+ return MemoryMarshalIntrinsics.EmitIL(method);
+ }
+
if (mdType.Name == "Volatile" && mdType.Namespace == "System.Threading")
{
return VolatileIntrinsics.EmitIL(method);
diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj
index 886f80a59e265f..e1efb7fbba9249 100644
--- a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj
+++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj
@@ -40,6 +40,7 @@
+
diff --git a/src/coreclr/src/vm/jitinterface.cpp b/src/coreclr/src/vm/jitinterface.cpp
index dd479a5bf375bd..507da32d65734b 100644
--- a/src/coreclr/src/vm/jitinterface.cpp
+++ b/src/coreclr/src/vm/jitinterface.cpp
@@ -7194,6 +7194,39 @@ bool getILIntrinsicImplementationForUnsafe(MethodDesc * ftn,
return false;
}
+bool getILIntrinsicImplementationForMemoryMarshal(MethodDesc * ftn,
+ CORINFO_METHOD_INFO * methInfo)
+{
+ STANDARD_VM_CONTRACT;
+
+ _ASSERTE(MscorlibBinder::IsClass(ftn->GetMethodTable(), CLASS__MEMORY_MARSHAL));
+
+ mdMethodDef tk = ftn->GetMemberDef();
+
+ if (tk == MscorlibBinder::GetMethod(METHOD__MEMORY_MARSHAL__GET_ARRAY_DATA_REFERENCE)->GetMemberDef())
+ {
+ mdToken tokRawSzArrayData = MscorlibBinder::GetField(FIELD__RAW_ARRAY_DATA__DATA)->GetMemberDef();
+
+ static BYTE ilcode[] = { CEE_LDARG_0,
+ CEE_LDFLDA,0,0,0,0,
+ CEE_RET };
+
+ ilcode[2] = (BYTE)(tokRawSzArrayData);
+ ilcode[3] = (BYTE)(tokRawSzArrayData >> 8);
+ ilcode[4] = (BYTE)(tokRawSzArrayData >> 16);
+ ilcode[5] = (BYTE)(tokRawSzArrayData >> 24);
+
+ methInfo->ILCode = const_cast(ilcode);
+ methInfo->ILCodeSize = sizeof(ilcode);
+ methInfo->maxStack = 1;
+ methInfo->EHcount = 0;
+ methInfo->options = (CorInfoOptions)0;
+ return true;
+ }
+
+ return false;
+}
+
bool getILIntrinsicImplementationForVolatile(MethodDesc * ftn,
CORINFO_METHOD_INFO * methInfo)
{
@@ -7415,27 +7448,6 @@ bool getILIntrinsicImplementationForRuntimeHelpers(MethodDesc * ftn,
return true;
}
- if (tk == MscorlibBinder::GetMethod(METHOD__RUNTIME_HELPERS__GET_RAW_SZ_ARRAY_DATA)->GetMemberDef())
- {
- mdToken tokRawSzArrayData = MscorlibBinder::GetField(FIELD__RAW_ARRAY_DATA__DATA)->GetMemberDef();
-
- static BYTE ilcode[] = { CEE_LDARG_0,
- CEE_LDFLDA,0,0,0,0,
- CEE_RET };
-
- ilcode[2] = (BYTE)(tokRawSzArrayData);
- ilcode[3] = (BYTE)(tokRawSzArrayData >> 8);
- ilcode[4] = (BYTE)(tokRawSzArrayData >> 16);
- ilcode[5] = (BYTE)(tokRawSzArrayData >> 24);
-
- methInfo->ILCode = const_cast(ilcode);
- methInfo->ILCodeSize = sizeof(ilcode);
- methInfo->maxStack = 1;
- methInfo->EHcount = 0;
- methInfo->options = (CorInfoOptions)0;
- return true;
- }
-
if (tk == MscorlibBinder::GetMethod(METHOD__RUNTIME_HELPERS__GET_METHOD_TABLE)->GetMemberDef())
{
mdToken tokRawData = MscorlibBinder::GetField(FIELD__RAW_DATA__DATA)->GetMemberDef();
@@ -7639,6 +7651,10 @@ getMethodInfoHelper(
{
fILIntrinsic = getILIntrinsicImplementationForUnsafe(ftn, methInfo);
}
+ else if (MscorlibBinder::IsClass(pMT, CLASS__MEMORY_MARSHAL))
+ {
+ fILIntrinsic = getILIntrinsicImplementationForMemoryMarshal(ftn, methInfo);
+ }
else if (MscorlibBinder::IsClass(pMT, CLASS__INTERLOCKED))
{
fILIntrinsic = getILIntrinsicImplementationForInterlocked(ftn, methInfo);
diff --git a/src/coreclr/src/vm/mscorlib.h b/src/coreclr/src/vm/mscorlib.h
index bafcc278d1e40a..b9f3e45649b492 100644
--- a/src/coreclr/src/vm/mscorlib.h
+++ b/src/coreclr/src/vm/mscorlib.h
@@ -713,7 +713,6 @@ DEFINE_METHOD(RUNTIME_HELPERS, IS_REFERENCE_OR_CONTAINS_REFERENCES, IsRefer
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_SZ_ARRAY_DATA, GetRawSzArrayData, NoSig)
DEFINE_METHOD(RUNTIME_HELPERS, GET_RAW_ARRAY_DATA, GetRawArrayData, NoSig)
DEFINE_METHOD(RUNTIME_HELPERS, GET_UNINITIALIZED_OBJECT, GetUninitializedObject, NoSig)
DEFINE_METHOD(RUNTIME_HELPERS, ENUM_EQUALS, EnumEquals, NoSig)
@@ -743,6 +742,9 @@ DEFINE_METHOD(UNSAFE, PTR_READ_UNALIGNED, ReadUnaligned, GM_Pt
DEFINE_METHOD(UNSAFE, PTR_WRITE_UNALIGNED, WriteUnaligned, GM_PtrVoid_T_RetVoid)
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_CLASS(INTERLOCKED, Threading, Interlocked)
DEFINE_METHOD(INTERLOCKED, COMPARE_EXCHANGE_T, CompareExchange, GM_RefT_T_T_RetT)
DEFINE_METHOD(INTERLOCKED, COMPARE_EXCHANGE_OBJECT,CompareExchange, SM_RefObject_Object_Object_RetObject)
diff --git a/src/libraries/System.Memory/ref/System.Memory.cs b/src/libraries/System.Memory/ref/System.Memory.cs
index e3056f06405e19..bc17f4d0780fc9 100644
--- a/src/libraries/System.Memory/ref/System.Memory.cs
+++ b/src/libraries/System.Memory/ref/System.Memory.cs
@@ -473,6 +473,7 @@ public static partial class MemoryMarshal
public static System.Memory CreateFromPinnedArray(T[]? array, int start, int length) { throw null; }
public static System.ReadOnlySpan CreateReadOnlySpan(ref T reference, int length) { throw null; }
public static System.Span CreateSpan(ref T reference, int length) { throw null; }
+ public static ref T GetArrayDataReference(T[] array) { throw null; }
public static ref T GetReference(System.ReadOnlySpan span) { throw null; }
public static ref T GetReference(System.Span span) { throw null; }
public static T Read(System.ReadOnlySpan source) where T : struct { throw null; }
diff --git a/src/libraries/System.Memory/tests/MemoryMarshal/GetArrayDataReference.cs b/src/libraries/System.Memory/tests/MemoryMarshal/GetArrayDataReference.cs
new file mode 100644
index 00000000000000..64a1282db580b8
--- /dev/null
+++ b/src/libraries/System.Memory/tests/MemoryMarshal/GetArrayDataReference.cs
@@ -0,0 +1,49 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Xunit;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace System.SpanTests
+{
+ public static partial class MemoryMarshalTests
+ {
+ [Fact]
+ public static void GetArrayDataReference_NullInput_ThrowsNullRef()
+ {
+ Assert.Throws(() => MemoryMarshal.GetArrayDataReference((object[])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)));
+ }
+
+ [Fact]
+ public static unsafe void GetArrayDataReference_EmptyInput_ReturnsRefToWhereFirstElementWouldBe()
+ {
+ int[] theArray = new int[0];
+
+ ref int theRef = ref MemoryMarshal.GetArrayDataReference(theArray);
+
+ Assert.True(Unsafe.AsPointer(ref theRef) != null);
+ Assert.True(Unsafe.AreSame(ref theRef, ref MemoryMarshal.GetReference(theArray.AsSpan())));
+ }
+
+ [Fact]
+ public static void GetArrayDataReference_IgnoresArrayVarianceChecks()
+ {
+ string[] strArr = new string[] { "Hello" };
+
+ // 'ref object' instead of 'ref string' because GetArrayDataReference skips array variance checks.
+ // We can deref it but we must not write to it unless we know the value being written is also a string.
+ ref object refObj = ref MemoryMarshal.GetArrayDataReference