diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/MemoryMarshal.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/MemoryMarshal.CoreCLR.cs index 407fbc23678783..f4a87e587c55ef 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/MemoryMarshal.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/MemoryMarshal.CoreCLR.cs @@ -19,9 +19,8 @@ public static unsafe partial class MemoryMarshal /// [Intrinsic] [NonVersionable] - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref T GetArrayDataReference(T[] array) => - ref Unsafe.As(ref Unsafe.As(array).Data); + ref GetArrayDataReference(array); /// /// Returns a reference to the 0th element of . If the array is empty, returns a reference to where the 0th element diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index 946866d63c888f..c153944aa38f68 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -918,7 +918,7 @@ inline GenTreeIntCon* Compiler::gtNewIconHandleNode(size_t value, GenTreeFlags f #if defined(LATE_DISASM) node = new (this, LargeOpOpcode()) GenTreeIntCon(TYP_I_IMPL, value, fields DEBUGARG(/*largeNode*/ true)); #else - node = new (this, GT_CNS_INT) GenTreeIntCon(TYP_I_IMPL, value, fields); + node = new (this, GT_CNS_INT) GenTreeIntCon(TYP_I_IMPL, value, fields); #endif node->gtFlags |= flags; return node; @@ -1099,8 +1099,15 @@ inline GenTreeIndexAddr* Compiler::gtNewIndexAddr(GenTree* arrayOp, unsigned elemSize = (elemType == TYP_STRUCT) ? info.compCompHnd->getClassSize(elemClassHandle) : genTypeSize(elemType); - GenTreeIndexAddr* indexAddr = new (this, GT_INDEX_ADDR) - GenTreeIndexAddr(arrayOp, indexOp, elemType, elemClassHandle, elemSize, lengthOffset, firstElemOffset); +#ifdef DEBUG + bool boundsCheck = JitConfig.JitSkipArrayBoundCheck() != 1; +#else + bool boundsCheck = true; +#endif + + GenTreeIndexAddr* indexAddr = + new (this, GT_INDEX_ADDR) GenTreeIndexAddr(arrayOp, indexOp, elemType, elemClassHandle, elemSize, lengthOffset, + firstElemOffset, boundsCheck); return indexAddr; } diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index cfeff19efa8fe9..46f302338d4e2a 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -8580,7 +8580,7 @@ GenTree* Compiler::gtCloneExpr( copy = new (this, GT_INDEX_ADDR) GenTreeIndexAddr(asIndAddr->Arr(), asIndAddr->Index(), asIndAddr->gtElemType, asIndAddr->gtStructElemClass, asIndAddr->gtElemSize, asIndAddr->gtLenOffset, - asIndAddr->gtElemOffset); + asIndAddr->gtElemOffset, asIndAddr->IsBoundsChecked()); copy->AsIndexAddr()->gtIndRngFailBB = asIndAddr->gtIndRngFailBB; } break; diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index 6c29054b8ddc21..f27c18eb84ec29 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -6530,7 +6530,8 @@ struct GenTreeIndexAddr : public GenTreeOp CORINFO_CLASS_HANDLE structElemClass, unsigned elemSize, unsigned lenOffset, - unsigned elemOffset) + unsigned elemOffset, + bool boundsCheck) : GenTreeOp(GT_INDEX_ADDR, TYP_BYREF, arr, ind) , gtStructElemClass(structElemClass) , gtIndRngFailBB(nullptr) @@ -6540,13 +6541,8 @@ struct GenTreeIndexAddr : public GenTreeOp , gtElemOffset(elemOffset) { assert(!varTypeIsStruct(elemType) || (structElemClass != NO_CLASS_HANDLE)); -#ifdef DEBUG - if (JitConfig.JitSkipArrayBoundCheck() == 1) - { - // Skip bounds check - } - else -#endif + + if (boundsCheck) { // Do bounds check gtFlags |= GTF_INX_RNGCHK; diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 211420188476ca..c381b3a74a1dd1 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -2601,6 +2601,34 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, break; } + case NI_System_Runtime_InteropService_MemoryMarshal_GetArrayDataReference: + { + assert(sig->numArgs == 1); + assert(sig->sigInst.methInstCount == 1); + + GenTree* array = impPopStack().val; + CORINFO_CLASS_HANDLE elemHnd = sig->sigInst.methInst[0]; + CorInfoType jitType = info.compCompHnd->asCorInfoType(elemHnd); + var_types elemType = JITtype2varType(jitType); + + if (fgAddrCouldBeNull(array)) + { + GenTree* arrayClone; + array = impCloneExpr(array, &arrayClone, NO_CLASS_HANDLE, (unsigned)CHECK_SPILL_ALL, + nullptr DEBUGARG("MemoryMarshal.GetArrayDataReference array")); + + impAppendTree(gtNewNullCheck(array, compCurBB), (unsigned)CHECK_SPILL_ALL, impCurStmtDI); + array = arrayClone; + } + + GenTree* index = gtNewIconNode(0, TYP_I_IMPL); + GenTreeIndexAddr* indexAddr = gtNewArrayIndexAddr(array, index, elemType, elemHnd); + indexAddr->gtFlags &= ~GTF_INX_RNGCHK; + indexAddr->gtFlags |= GTF_INX_ADDR_NONNULL; + retNode = indexAddr; + break; + } + case NI_Internal_Runtime_MethodTable_Of: case NI_System_Activator_AllocatorOf: case NI_System_Activator_DefaultConstructorOf: @@ -7514,6 +7542,16 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) } } } + else if (strcmp(namespaceName, "System.Runtime.InteropServices") == 0) + { + if (strcmp(className, "MemoryMarshal") == 0) + { + if (strcmp(methodName, "GetArrayDataReference") == 0) + { + result = NI_System_Runtime_InteropService_MemoryMarshal_GetArrayDataReference; + } + } + } else if (strncmp(namespaceName, "System.Runtime.Intrinsics", 25) == 0) { // We go down this path even when FEATURE_HW_INTRINSICS isn't enabled diff --git a/src/coreclr/jit/namedintrinsiclist.h b/src/coreclr/jit/namedintrinsiclist.h index 37af303c0ab68d..5aee22dc2867b4 100644 --- a/src/coreclr/jit/namedintrinsiclist.h +++ b/src/coreclr/jit/namedintrinsiclist.h @@ -93,6 +93,8 @@ enum NamedIntrinsic : unsigned short NI_System_Runtime_CompilerServices_RuntimeHelpers_InitializeArray, NI_System_Runtime_CompilerServices_RuntimeHelpers_IsKnownConstant, + NI_System_Runtime_InteropService_MemoryMarshal_GetArrayDataReference, + NI_System_String_Equals, NI_System_String_get_Chars, NI_System_String_get_Length, diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/MemoryMarshal.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/MemoryMarshal.NativeAot.cs index f90969a3410370..5d97217a13c8bb 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/MemoryMarshal.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/MemoryMarshal.NativeAot.cs @@ -20,9 +20,8 @@ public static unsafe partial class MemoryMarshal /// [Intrinsic] [NonVersionable] - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref T GetArrayDataReference(T[] array) => - ref Unsafe.As(ref Unsafe.As(array).Data); + ref GetArrayDataReference(array); /// /// Returns a reference to the 0th element of . If the array is empty, returns a reference to where the 0th element diff --git a/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs b/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs index 2de9aa4f8c0fba..4fec12a673e11e 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs @@ -58,12 +58,6 @@ private static MethodIL TryGetIntrinsicMethodIL(MethodDesc method) return UnsafeIntrinsics.EmitIL(method); } break; - case "MemoryMarshal": - { - if (owningType.Namespace == "System.Runtime.InteropServices") - return MemoryMarshalIntrinsics.EmitIL(method); - } - break; case "Volatile": { if (owningType.Namespace == "System.Threading") diff --git a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/MemoryMarshalIntrinsics.cs b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/MemoryMarshalIntrinsics.cs deleted file mode 100644 index aaa56db0888c74..00000000000000 --- a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/MemoryMarshalIntrinsics.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -#if READYTORUN -using ILCompiler; -#endif -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 (method.Instantiation.Length != 1) - { - return null; // we only handle the generic method GetArrayDataReference(T[]) - } - - if (methodName == "GetArrayDataReference") - { - var rawArrayData = method.Context.SystemModule.GetKnownType("System.Runtime.CompilerServices", "RawArrayData"); -#if READYTORUN - if (!rawArrayData.IsNonVersionable()) - return null; // This is only an intrinsic if we can prove that RawArrayData is known to be of fixed offset -#endif - ILEmitter emit = new ILEmitter(); - ILCodeStream codeStream = emit.NewCodeStream(); - codeStream.EmitLdArg(0); - codeStream.Emit(ILOpcode.ldflda, emit.NewToken(rawArrayData.GetField("Data"))); - codeStream.Emit(ILOpcode.ret); - return emit.Link(method); - } - - // unknown method - return null; - } - } -} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index 5887e5d4ff2a7c..771e89bfcea63d 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -655,9 +655,6 @@ IL\Stubs\UnsafeIntrinsics.cs - - IL\Stubs\MemoryMarshalIntrinsics.cs - IL\Stubs\VolatileIntrinsics.cs diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/ReadyToRunILProvider.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/ReadyToRunILProvider.cs index 686557fcd25c75..4e7a89d670a03d 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/ReadyToRunILProvider.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/ReadyToRunILProvider.cs @@ -85,11 +85,6 @@ 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); @@ -171,7 +166,7 @@ public override MethodIL GetMethodIL(MethodDesc method) } // Check to see if there is an override for the EcmaMethodIL. If there is not - // then simply return the EcmaMethodIL. In theory this could call + // then simply return the EcmaMethodIL. In theory this could call // CreateCrossModuleInlineableTokensForILBody, but we explicitly do not want // to do that. The reason is that this method is called during the multithreaded // portion of compilation, and CreateCrossModuleInlineableTokensForILBody @@ -224,7 +219,7 @@ class ManifestModuleWrappedMethodIL : MethodIL, IEcmaMethodIL, IMethodTokensAreU MutableModule _mutableModule; public ManifestModuleWrappedMethodIL() {} - + public bool Initialize(MutableModule mutableModule, EcmaMethodIL wrappedMethod) { bool failedToReplaceToken = false; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj index 4d8b7c38dda4e5..6096d476c806c9 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj @@ -45,7 +45,6 @@ - diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index a2158726dd8303..c069124945a3ae 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -731,7 +731,6 @@ DEFINE_METHOD(UNSAFE, UNBOX, Unbox, NoSig) DEFINE_METHOD(UNSAFE, WRITE, Write, NoSig) DEFINE_CLASS(MEMORY_MARSHAL, Interop, MemoryMarshal) -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) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 149a8ff1e87290..95c6d41a64fd67 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -7001,39 +7001,6 @@ bool getILIntrinsicImplementationForUnsafe(MethodDesc * ftn, return false; } -bool getILIntrinsicImplementationForMemoryMarshal(MethodDesc * ftn, - CORINFO_METHOD_INFO * methInfo) -{ - STANDARD_VM_CONTRACT; - - _ASSERTE(CoreLibBinder::IsClass(ftn->GetMethodTable(), CLASS__MEMORY_MARSHAL)); - - mdMethodDef tk = ftn->GetMemberDef(); - - if (tk == CoreLibBinder::GetMethod(METHOD__MEMORY_MARSHAL__GET_ARRAY_DATA_REFERENCE_SZARRAY)->GetMemberDef()) - { - mdToken tokRawSzArrayData = CoreLibBinder::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) { @@ -7485,10 +7452,6 @@ getMethodInfoHelper( { fILIntrinsic = getILIntrinsicImplementationForUnsafe(ftn, methInfo); } - else if (CoreLibBinder::IsClass(pMT, CLASS__MEMORY_MARSHAL)) - { - fILIntrinsic = getILIntrinsicImplementationForMemoryMarshal(ftn, methInfo); - } else if (CoreLibBinder::IsClass(pMT, CLASS__INTERLOCKED)) { fILIntrinsic = getILIntrinsicImplementationForInterlocked(ftn, methInfo); diff --git a/src/tests/JIT/Intrinsics/MemoryMarshalGetArrayDataReference.cs b/src/tests/JIT/Intrinsics/MemoryMarshalGetArrayDataReference.cs new file mode 100644 index 00000000000000..c59af9750a057d --- /dev/null +++ b/src/tests/JIT/Intrinsics/MemoryMarshalGetArrayDataReference.cs @@ -0,0 +1,308 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// + +using System; +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; + +namespace MemoryMarshalGetArrayDataReferenceTest +{ + class Program + { + private static int _errors = 0; + + unsafe static int Main(string[] args) + { + // use no inline methods to avoid indirect call inlining in the future + [MethodImpl(MethodImplOptions.NoInlining)] + static delegate* GetBytePtr() => &MemoryMarshal.GetArrayDataReference; + delegate* ptrByte = GetBytePtr(); + [MethodImpl(MethodImplOptions.NoInlining)] + static delegate* GetStringPtr() => &MemoryMarshal.GetArrayDataReference; + delegate* ptrString = GetStringPtr(); + + [MethodImpl(MethodImplOptions.NoInlining)] + static T NoInline(T t) => t; + + byte[] testByteArray = new byte[1]; + IsTrue(Unsafe.AreSame(ref MemoryMarshal.GetArrayDataReference(testByteArray), ref testByteArray[0])); + IsTrue(Unsafe.AreSame(ref ptrByte(testByteArray), ref testByteArray[0])); + + IsTrue(Unsafe.AreSame(ref MemoryMarshal.GetArrayDataReference(NoInline(testByteArray)), ref testByteArray[0])); + IsTrue(Unsafe.AreSame(ref ptrByte(NoInline(testByteArray)), ref testByteArray[0])); + + string[] testStringArray = new string[1]; + IsTrue(Unsafe.AreSame(ref MemoryMarshal.GetArrayDataReference(testStringArray), ref testStringArray[0])); + IsTrue(Unsafe.AreSame(ref ptrString(testStringArray), ref testStringArray[0])); + + IsTrue(Unsafe.AreSame(ref MemoryMarshal.GetArrayDataReference(NoInline(testStringArray)), ref testStringArray[0])); + IsTrue(Unsafe.AreSame(ref ptrString(NoInline(testStringArray)), ref testStringArray[0])); + + IsFalse(Unsafe.IsNullRef(ref MemoryMarshal.GetArrayDataReference(new byte[0]))); + IsFalse(Unsafe.IsNullRef(ref MemoryMarshal.GetArrayDataReference(new string[0]))); + IsFalse(Unsafe.IsNullRef(ref MemoryMarshal.GetArrayDataReference(new Half[0]))); + IsFalse(Unsafe.IsNullRef(ref MemoryMarshal.GetArrayDataReference(new Vector128[0]))); + IsFalse(Unsafe.IsNullRef(ref MemoryMarshal.GetArrayDataReference(new StructWithByte[0]))); + IsFalse(Unsafe.IsNullRef(ref MemoryMarshal.GetArrayDataReference(new SimpleEnum[0]))); + IsFalse(Unsafe.IsNullRef(ref MemoryMarshal.GetArrayDataReference(new GenericStruct[0]))); + IsFalse(Unsafe.IsNullRef(ref MemoryMarshal.GetArrayDataReference(new GenericStruct[0]))); + + IsFalse(Unsafe.IsNullRef(ref ptrByte(new byte[0]))); + IsFalse(Unsafe.IsNullRef(ref ptrString(new string[0]))); + + IsFalse(Unsafe.IsNullRef(ref MemoryMarshal.GetArrayDataReference(NoInline(new byte[0])))); + IsFalse(Unsafe.IsNullRef(ref MemoryMarshal.GetArrayDataReference(NoInline(new string[0])))); + IsFalse(Unsafe.IsNullRef(ref MemoryMarshal.GetArrayDataReference(NoInline(new Half[0])))); + IsFalse(Unsafe.IsNullRef(ref MemoryMarshal.GetArrayDataReference(NoInline(new Vector128[0])))); + IsFalse(Unsafe.IsNullRef(ref MemoryMarshal.GetArrayDataReference(NoInline(new StructWithByte[0])))); + IsFalse(Unsafe.IsNullRef(ref MemoryMarshal.GetArrayDataReference(NoInline(new SimpleEnum[0])))); + IsFalse(Unsafe.IsNullRef(ref MemoryMarshal.GetArrayDataReference(NoInline(new GenericStruct[0])))); + IsFalse(Unsafe.IsNullRef(ref MemoryMarshal.GetArrayDataReference(NoInline(new GenericStruct[0])))); + + IsFalse(Unsafe.IsNullRef(ref ptrByte(NoInline(new byte[0])))); + IsFalse(Unsafe.IsNullRef(ref ptrString(NoInline(new string[0])))); + + ThrowsNRE(() => { _ = ref MemoryMarshal.GetArrayDataReference(null); }); + ThrowsNRE(() => { _ = ref MemoryMarshal.GetArrayDataReference(null); }); + ThrowsNRE(() => { _ = ref MemoryMarshal.GetArrayDataReference(null); }); + ThrowsNRE(() => { _ = ref MemoryMarshal.GetArrayDataReference>(null); }); + ThrowsNRE(() => { _ = ref MemoryMarshal.GetArrayDataReference(null); }); + ThrowsNRE(() => { _ = ref MemoryMarshal.GetArrayDataReference(null); }); + ThrowsNRE(() => { _ = ref MemoryMarshal.GetArrayDataReference>(null); }); + ThrowsNRE(() => { _ = ref MemoryMarshal.GetArrayDataReference>(null); }); + + ThrowsNRE(() => { _ = ref ptrByte(null); }); + ThrowsNRE(() => { _ = ref ptrString(null); }); + + ThrowsNRE(() => { _ = ref MemoryMarshal.GetArrayDataReference(NoInline(null)); }); + ThrowsNRE(() => { _ = ref MemoryMarshal.GetArrayDataReference(NoInline(null)); }); + ThrowsNRE(() => { _ = ref MemoryMarshal.GetArrayDataReference(NoInline(null)); }); + ThrowsNRE(() => { _ = ref MemoryMarshal.GetArrayDataReference(NoInline[]>(null)); }); + ThrowsNRE(() => { _ = ref MemoryMarshal.GetArrayDataReference(NoInline(null)); }); + ThrowsNRE(() => { _ = ref MemoryMarshal.GetArrayDataReference(NoInline(null)); }); + ThrowsNRE(() => { _ = ref MemoryMarshal.GetArrayDataReference(NoInline[]>(null)); }); + ThrowsNRE(() => { _ = ref MemoryMarshal.GetArrayDataReference(NoInline[]>(null)); }); + + ThrowsNRE(() => { _ = ref ptrByte(NoInline(null)); }); + ThrowsNRE(() => { _ = ref ptrString(NoInline(null)); }); + + ThrowsNRE(() => ref MemoryMarshal.GetArrayDataReference(null)); + ThrowsNRE(() => ref MemoryMarshal.GetArrayDataReference(null)); + ThrowsNRE(() => ref MemoryMarshal.GetArrayDataReference(null)); + ThrowsNRE(() => ref MemoryMarshal.GetArrayDataReference>(null)); + ThrowsNRE(() => ref MemoryMarshal.GetArrayDataReference(null)); + ThrowsNRE(() => ref MemoryMarshal.GetArrayDataReference(null)); + ThrowsNRE(() => ref MemoryMarshal.GetArrayDataReference>(null)); + ThrowsNRE(() => ref MemoryMarshal.GetArrayDataReference>(null)); + + ThrowsNRE(() => ref ptrByte(null)); + ThrowsNRE(() => ref ptrString(null)); + + ThrowsNRE(() => ref MemoryMarshal.GetArrayDataReference(NoInline(null))); + ThrowsNRE(() => ref MemoryMarshal.GetArrayDataReference(NoInline(null))); + ThrowsNRE(() => ref MemoryMarshal.GetArrayDataReference(NoInline(null))); + ThrowsNRE(() => ref MemoryMarshal.GetArrayDataReference(NoInline[]>(null))); + ThrowsNRE(() => ref MemoryMarshal.GetArrayDataReference(NoInline(null))); + ThrowsNRE(() => ref MemoryMarshal.GetArrayDataReference(NoInline(null))); + ThrowsNRE(() => ref MemoryMarshal.GetArrayDataReference(NoInline[]>(null))); + ThrowsNRE(() => ref MemoryMarshal.GetArrayDataReference(NoInline[]>(null))); + + ThrowsNRE(() => ref ptrByte(NoInline(null))); + ThrowsNRE(() => ref ptrString(NoInline(null))); + + // from https://github.com/dotnet/runtime/issues/58312#issuecomment-993491291 + [MethodImpl(MethodImplOptions.NoInlining)] + static int Problem1(StructWithByte[] a) + { + MemoryMarshal.GetArrayDataReference(a).Byte = 1; + + a[0].Byte = 2; + + return MemoryMarshal.GetArrayDataReference(a).Byte; + } + + Equals(Problem1(new StructWithByte[] { new StructWithByte { Byte = 1 } }), 2); + + [MethodImpl(MethodImplOptions.NoInlining)] + static int Problem2(StructWithByte[] a) + { + Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(a), 1).Byte = 1; + + a[1].Byte = 2; + + return Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(a), 1).Byte; + } + + Equals(Problem2(new StructWithByte[] { new StructWithByte { Byte = 1 }, new StructWithByte { Byte = 1 } }), 2); + + [MethodImpl(MethodImplOptions.NoInlining)] + static int Problem3(byte[] a) + { + if (MemoryMarshal.GetArrayDataReference(a) == 1) + { + a[0] = 2; + if (MemoryMarshal.GetArrayDataReference(a) == 1) + { + return -1; + } + } + + return 0; + } + + Equals(Problem3(new byte[] { 1 }), 0); + + [MethodImpl(MethodImplOptions.NoInlining)] + static int Problem4(byte[] a) + { + if (Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(a), 1) == 1) + { + a[1] = 2; + if (Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(a), 1) == 1) + { + return -1; + } + } + + return 0; + } + + Equals(Problem4(new byte[] { 1, 1 }), 0); + + [MethodImpl(MethodImplOptions.NoInlining)] + static void Problem5() + { + int[] inputArray = CreateArray(17); + + Create(out Vector v, inputArray, inputArray.Length); + + static void Create(out Vector result, int[] values, int index) + { + // We explicitly don't check for `null` because historically this has thrown `NullReferenceException` for perf reasons + + if ((index < 0) || ((values.Length - index) < Vector.Count)) + { + ThrowArgumentOutOfRangeException(); + } + + result = Unsafe.ReadUnaligned>(ref Unsafe.As(ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(values), index))); + } + + static void ThrowArgumentOutOfRangeException() + { + throw new ArgumentOutOfRangeException(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int[] CreateArray(int size) => new int[size]; + } + + try + { + Problem5(); + _errors++; + } + catch (ArgumentOutOfRangeException) + { + // expected + } + catch + { + _errors++; + } + + return 100 + _errors; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void Equals(T left, T right, [CallerLineNumber] int line = 0, [CallerFilePath] string file = "") + { + if (!EqualityComparer.Default.Equals(left, right)) + { + Console.WriteLine($"{file}:L{line} test failed (expected: equal, actual: {left}-{right})."); + _errors++; + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void IsTrue(bool expression, [CallerLineNumber] int line = 0, [CallerFilePath] string file = "") + { + if (!expression) + { + Console.WriteLine($"{file}:L{line} test failed (expected: true)."); + _errors++; + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void IsFalse(bool expression, [CallerLineNumber] int line = 0, [CallerFilePath] string file = "") + { + if (expression) + { + Console.WriteLine($"{file}:L{line} test failed (expected: false)."); + _errors++; + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void ThrowsNRE(Action action, [CallerLineNumber] int line = 0, [CallerFilePath] string file = "") + { + try + { + action(); + } + catch (NullReferenceException) + { + return; + } + catch (Exception exc) + { + Console.WriteLine($"{file}:L{line} {exc}"); + } + Console.WriteLine($"Line {line}: test failed (expected: NullReferenceException)"); + _errors++; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void ThrowsNRE(RefFunction function, [CallerLineNumber] int line = 0, [CallerFilePath] string file = "") + { + try + { + [MethodImpl(MethodImplOptions.NoInlining)] + static void Use(ref T t) { } + Use(ref function()); + } + catch (NullReferenceException) + { + return; + } + catch (Exception exc) + { + Console.WriteLine($"{file}:L{line} {exc}"); + } + Console.WriteLine($"Line {line}: test failed (expected: NullReferenceException)"); + _errors++; + } + + public struct GenericStruct + { + public T field; + } + + public enum SimpleEnum + { + A,B,C + } + + struct StructWithByte + { + public byte Byte; + } + + delegate ref T RefFunction(); + } +} diff --git a/src/tests/JIT/Intrinsics/MemoryMarshalGetArrayDataReference_r.csproj b/src/tests/JIT/Intrinsics/MemoryMarshalGetArrayDataReference_r.csproj new file mode 100644 index 00000000000000..d81aadf759378f --- /dev/null +++ b/src/tests/JIT/Intrinsics/MemoryMarshalGetArrayDataReference_r.csproj @@ -0,0 +1,13 @@ + + + Exe + + + true + None + + + + + + diff --git a/src/tests/JIT/Intrinsics/MemoryMarshalGetArrayDataReference_ro.csproj b/src/tests/JIT/Intrinsics/MemoryMarshalGetArrayDataReference_ro.csproj new file mode 100644 index 00000000000000..05f3a1f7575d05 --- /dev/null +++ b/src/tests/JIT/Intrinsics/MemoryMarshalGetArrayDataReference_ro.csproj @@ -0,0 +1,13 @@ + + + Exe + + + true + None + True + + + + +