Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Intrinsify Array GetArrayDataReference for SZ arrays #87374

Merged
merged 12 commits into from
Jul 17, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public static ref T GetArrayDataReference<T>(T[] array) =>
/// 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)
{
Expand Down
33 changes: 26 additions & 7 deletions src/coreclr/jit/importercalls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2762,14 +2762,33 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis,
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);
GenTree* array = impStackTop().val;
bool notNull = false;
CORINFO_CLASS_HANDLE elemHnd = NO_CLASS_HANDLE;
CorInfoType jitType;
if (sig->sigInst.methInstCount == 1)
{
elemHnd = sig->sigInst.methInst[0];
jitType = info.compCompHnd->asCorInfoType(elemHnd);
}
else
{
bool isExact = false;
CORINFO_CLASS_HANDLE arrayHnd = gtGetClassHandle(array, &isExact, &notNull);
if ((arrayHnd == NO_CLASS_HANDLE) || !info.compCompHnd->isSDArray(arrayHnd))
{
return nullptr;
}
jitType = info.compCompHnd->getChildType(arrayHnd, &elemHnd);
}

array = impPopStack().val;

assert(jitType != CORINFO_TYPE_UNDEF);
assert((jitType != CORINFO_TYPE_VALUECLASS) || (elemHnd != NO_CLASS_HANDLE));

if (fgAddrCouldBeNull(array))
if (!notNull && fgAddrCouldBeNull(array))
{
GenTree* arrayClone;
array = impCloneExpr(array, &arrayClone, CHECK_SPILL_ALL,
Expand All @@ -2780,7 +2799,7 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis,
}

GenTree* index = gtNewIconNode(0, TYP_I_IMPL);
GenTreeIndexAddr* indexAddr = gtNewArrayIndexAddr(array, index, elemType, elemHnd);
GenTreeIndexAddr* indexAddr = gtNewArrayIndexAddr(array, index, JITtype2varType(jitType), elemHnd);
indexAddr->gtFlags &= ~GTF_INX_RNGCHK;
indexAddr->gtFlags |= GTF_INX_ADDR_NONNULL;
retNode = indexAddr;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public static ref T GetArrayDataReference<T>(T[] array) =>
/// 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)
{
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/vm/jitinterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4632,7 +4632,7 @@ CorInfoType CEEInfo::getChildType (
}

/*********************************************************************/
// Check if this is a single dimensional array type
// Check if this is a single dimensional, zero based array type
bool CEEInfo::isSDArray(CORINFO_CLASS_HANDLE cls)
{
CONTRACTL {
Expand Down
92 changes: 92 additions & 0 deletions src/tests/JIT/Intrinsics/MemoryMarshalGetArrayDataReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,98 @@ unsafe static int Main(string[] args)
ThrowsNRE(() => ref ptrByte(NoInline<byte[]>(null)));
ThrowsNRE(() => ref ptrString(NoInline<string[]>(null)));

// use no inline methods to avoid indirect call inlining in the future
[MethodImpl(MethodImplOptions.NoInlining)]
static delegate*<Array, ref byte> GetMdPtr() => &MemoryMarshal.GetArrayDataReference;
delegate*<Array, ref byte> ptrMd = GetMdPtr();

IsTrue(Unsafe.AreSame(ref MemoryMarshal.GetArrayDataReference((Array)testByteArray), ref testByteArray[0]));
IsTrue(Unsafe.AreSame(ref ptrMd(testByteArray), ref testByteArray[0]));

IsTrue(Unsafe.AreSame(ref MemoryMarshal.GetArrayDataReference((Array)NoInline(testByteArray)), ref testByteArray[0]));
IsTrue(Unsafe.AreSame(ref MemoryMarshal.GetArrayDataReference(NoInline<Array>(testByteArray)), ref testByteArray[0]));
IsTrue(Unsafe.AreSame(ref ptrMd(NoInline(testByteArray)), ref testByteArray[0]));

IsTrue(Unsafe.AreSame(ref Unsafe.As<byte, string>(ref MemoryMarshal.GetArrayDataReference((Array)testStringArray)), ref testStringArray[0]));
IsTrue(Unsafe.AreSame(ref Unsafe.As<byte, string>(ref ptrMd(testStringArray)), ref testStringArray[0]));

IsTrue(Unsafe.AreSame(ref Unsafe.As<byte, string>(ref MemoryMarshal.GetArrayDataReference((Array)NoInline(testStringArray))), ref testStringArray[0]));
IsTrue(Unsafe.AreSame(ref Unsafe.As<byte, string>(ref MemoryMarshal.GetArrayDataReference(NoInline<Array>(testStringArray))), ref testStringArray[0]));
IsTrue(Unsafe.AreSame(ref Unsafe.As<byte, string>(ref ptrMd(NoInline(testStringArray))), ref testStringArray[0]));

byte[,] testByteMdArray = new byte[1, 1];
IsTrue(Unsafe.AreSame(ref MemoryMarshal.GetArrayDataReference(testByteMdArray), ref testByteMdArray[0, 0]));
IsTrue(Unsafe.AreSame(ref ptrMd(testByteMdArray), ref testByteMdArray[0, 0]));

IsTrue(Unsafe.AreSame(ref MemoryMarshal.GetArrayDataReference(NoInline(testByteMdArray)), ref testByteMdArray[0, 0]));
IsTrue(Unsafe.AreSame(ref ptrMd(NoInline(testByteMdArray)), ref testByteMdArray[0, 0]));

string[,] testStringMdArray = new string[1, 1];
IsTrue(Unsafe.AreSame(ref Unsafe.As<byte, string>(ref MemoryMarshal.GetArrayDataReference(testStringMdArray)), ref testStringMdArray[0, 0]));
IsTrue(Unsafe.AreSame(ref Unsafe.As<byte, string>(ref ptrMd(testStringMdArray)), ref testStringMdArray[0, 0]));

IsTrue(Unsafe.AreSame(ref Unsafe.As<byte, string>(ref MemoryMarshal.GetArrayDataReference(NoInline(testStringMdArray))), ref testStringMdArray[0, 0]));
IsTrue(Unsafe.AreSame(ref Unsafe.As<byte, string>(ref ptrMd(NoInline(testStringMdArray))), ref testStringMdArray[0, 0]));

Array nonZeroArray = Array.CreateInstance(typeof(string), new [] { 1 }, new [] { -1 });
string test = "test";
nonZeroArray.SetValue(test, -1);
IsTrue(ReferenceEquals(Unsafe.As<byte, string>(ref MemoryMarshal.GetArrayDataReference(nonZeroArray)), test));
IsTrue(ReferenceEquals(Unsafe.As<byte, string>(ref ptrMd(nonZeroArray)), test));

IsTrue(ReferenceEquals(Unsafe.As<byte, string>(ref MemoryMarshal.GetArrayDataReference(NoInline(nonZeroArray))), test));
IsTrue(ReferenceEquals(Unsafe.As<byte, string>(ref ptrMd(NoInline(nonZeroArray))), test));

IsFalse(Unsafe.IsNullRef(ref MemoryMarshal.GetArrayDataReference((Array)new byte[0])));
IsFalse(Unsafe.IsNullRef(ref MemoryMarshal.GetArrayDataReference((Array)new string[0])));
IsFalse(Unsafe.IsNullRef(ref MemoryMarshal.GetArrayDataReference(new byte[0, 0])));
IsFalse(Unsafe.IsNullRef(ref MemoryMarshal.GetArrayDataReference(new string[0, 0])));

IsFalse(Unsafe.IsNullRef(ref ptrMd(new byte[0])));
IsFalse(Unsafe.IsNullRef(ref ptrMd(new string[0])));
IsFalse(Unsafe.IsNullRef(ref ptrMd(new byte[0, 0])));
IsFalse(Unsafe.IsNullRef(ref ptrMd(new string[0, 0])));

IsFalse(Unsafe.IsNullRef(ref MemoryMarshal.GetArrayDataReference((Array)NoInline(new byte[0]))));
IsFalse(Unsafe.IsNullRef(ref MemoryMarshal.GetArrayDataReference((Array)NoInline(new string[0]))));
IsFalse(Unsafe.IsNullRef(ref MemoryMarshal.GetArrayDataReference(NoInline(new byte[0, 0]))));
IsFalse(Unsafe.IsNullRef(ref MemoryMarshal.GetArrayDataReference(NoInline(new string[0, 0]))));
IsFalse(Unsafe.IsNullRef(ref MemoryMarshal.GetArrayDataReference(NoInline<Array>(new byte[0]))));
IsFalse(Unsafe.IsNullRef(ref MemoryMarshal.GetArrayDataReference(NoInline<Array>(new string[0]))));
IsFalse(Unsafe.IsNullRef(ref MemoryMarshal.GetArrayDataReference(NoInline<Array>(new byte[0, 0]))));
IsFalse(Unsafe.IsNullRef(ref MemoryMarshal.GetArrayDataReference(NoInline<Array>(new string[0, 0]))));

IsFalse(Unsafe.IsNullRef(ref ptrMd(NoInline(new byte[0]))));
IsFalse(Unsafe.IsNullRef(ref ptrMd(NoInline(new string[0]))));
IsFalse(Unsafe.IsNullRef(ref ptrMd(NoInline(new byte[0, 0]))));
IsFalse(Unsafe.IsNullRef(ref ptrMd(NoInline(new string[0, 0]))));
IsFalse(Unsafe.IsNullRef(ref ptrMd(NoInline<Array>(new byte[0]))));
IsFalse(Unsafe.IsNullRef(ref ptrMd(NoInline<Array>(new string[0]))));
IsFalse(Unsafe.IsNullRef(ref ptrMd(NoInline<Array>(new byte[0, 0]))));
IsFalse(Unsafe.IsNullRef(ref ptrMd(NoInline<Array>(new string[0, 0]))));

ThrowsNRE(() => { _ = ref MemoryMarshal.GetArrayDataReference((Array)null); });
ThrowsNRE(() => { _ = ref ptrMd(null); });

ThrowsNRE(() => { _ = ref MemoryMarshal.GetArrayDataReference((Array)NoInline<byte[]>(null)); });
ThrowsNRE(() => { _ = ref MemoryMarshal.GetArrayDataReference((Array)NoInline<string[]>(null)); });
ThrowsNRE(() => { _ = ref MemoryMarshal.GetArrayDataReference(NoInline<Array>(null)); });

ThrowsNRE(() => { _ = ref ptrMd(NoInline<byte[]>(null)); });
ThrowsNRE(() => { _ = ref ptrMd(NoInline<string[]>(null)); });
ThrowsNRE(() => { _ = ref ptrMd(NoInline<Array>(null)); });

ThrowsNRE(() => ref MemoryMarshal.GetArrayDataReference((Array)null));
ThrowsNRE(() => ref ptrMd(null));

ThrowsNRE(() => ref MemoryMarshal.GetArrayDataReference((Array)NoInline<byte[]>(null)));
ThrowsNRE(() => ref MemoryMarshal.GetArrayDataReference((Array)NoInline<string[]>(null)));
ThrowsNRE(() => ref MemoryMarshal.GetArrayDataReference(NoInline<Array>(null)));

ThrowsNRE(() => ref ptrMd(NoInline<byte[]>(null)));
ThrowsNRE(() => ref ptrMd(NoInline<string[]>(null)));
ThrowsNRE(() => ref ptrMd(NoInline<Array>(null)));

// from https://github.com/dotnet/runtime/issues/58312#issuecomment-993491291
[MethodImpl(MethodImplOptions.NoInlining)]
static int Problem1(StructWithByte[] a)
Expand Down