diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index f65920a61b207c..5c573ff0a53359 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -2181,6 +2181,27 @@ int32_t InterpCompiler::GetDataItemIndexForHelperFtn(CorInfoHelpFunc ftn) return GetDataItemIndex(addr); } +static int32_t GetLdindForType(InterpType interpType) +{ + switch (interpType) + { + case InterpTypeI1: return INTOP_LDIND_I1; + case InterpTypeU1: return INTOP_LDIND_U1; + case InterpTypeI2: return INTOP_LDIND_I2; + case InterpTypeU2: return INTOP_LDIND_U2; + case InterpTypeI4: return INTOP_LDIND_I4; + case InterpTypeI8: return INTOP_LDIND_I8; + case InterpTypeR4: return INTOP_LDIND_R4; + case InterpTypeR8: return INTOP_LDIND_R8; + case InterpTypeO: return INTOP_LDIND_I; + case InterpTypeVT: return INTOP_LDIND_VT; + case InterpTypeByRef: return INTOP_LDIND_I; + default: + assert(0); + } + return -1; +} + bool InterpCompiler::EmitNamedIntrinsicCall(NamedIntrinsic ni, CORINFO_CLASS_HANDLE clsHnd, CORINFO_METHOD_HANDLE method, CORINFO_SIG_INFO sig) { bool mustExpand = (method == m_methodHnd); @@ -2201,6 +2222,135 @@ bool InterpCompiler::EmitNamedIntrinsicCall(NamedIntrinsic ni, CORINFO_CLASS_HAN AddIns(INTOP_THROW_PNSE); return true; + case NI_System_Runtime_CompilerServices_StaticsHelpers_VolatileReadAsByref: + { + CHECK_STACK(1); + + m_pStackPointer--; + int32_t addrVar = m_pStackPointer[0].var; + + InterpType retType = GetInterpType(sig.retType); + int32_t opcode = GetLdindForType(retType); + AddIns(opcode); + m_pLastNewIns->SetSVar(addrVar); + + CORINFO_CLASS_HANDLE clsHnd = NULL; + if (sig.retType == CORINFO_TYPE_CLASS) + { + clsHnd = sig.retTypeClass; + } + PushInterpType(retType, clsHnd); + m_pLastNewIns->SetDVar(m_pStackPointer[-1].var); + + // Acquire barrier after the load + AddIns(INTOP_MEMBAR); + return true; + } + + case NI_System_Threading_Volatile_ReadBarrier: + AddIns(INTOP_MEMBAR); + return true; + + case NI_System_Runtime_CompilerServices_RuntimeHelpers_GetMethodTable: + { + CHECK_STACK(1); + m_pStackPointer--; + AddIns(INTOP_LDIND_I); + m_pLastNewIns->data[0] = 0; + m_pLastNewIns->SetSVar(m_pStackPointer[0].var); + PushStackType(StackTypeI, NULL); + m_pLastNewIns->SetDVar(m_pStackPointer[-1].var); + return true; + } + + case NI_System_Threading_Interlocked_CompareExchange: + { + CHECK_STACK(3); + InterpType retType = GetInterpType(sig.retType); + + int32_t opcode; + switch (retType) + { + case InterpTypeI4: + opcode = INTOP_COMPARE_EXCHANGE_I4; + break; + case InterpTypeI8: + opcode = INTOP_COMPARE_EXCHANGE_I8; + break; + default: + return false; + } + + AddIns(opcode); + m_pStackPointer -= 3; + + int32_t addrVar = m_pStackPointer[0].var; + int32_t valueVar = m_pStackPointer[1].var; + int32_t comparandVar = m_pStackPointer[2].var; + + PushInterpType(retType, nullptr); + m_pLastNewIns->SetSVars3(addrVar, valueVar, comparandVar); + m_pLastNewIns->SetDVar(m_pStackPointer[-1].var); + return true; + } + + case NI_System_Runtime_CompilerServices_RuntimeHelpers_IsReferenceOrContainsReferences: + { + CORINFO_CLASS_HANDLE clsHnd = sig.sigInst.methInst[0]; + bool isValueType = (m_compHnd->getClassAttribs(clsHnd) & CORINFO_FLG_VALUECLASS) != 0; + bool hasGCRefs = false; + + if (isValueType) + { + // Walk the layout to see if any field is a GC pointer + const uint32_t maxSlots = 256; + BYTE gcLayout[maxSlots]; + uint32_t numSlots = m_compHnd->getClassGClayout(clsHnd, gcLayout); + + for (uint32_t i = 0; i < numSlots; ++i) + { + if (gcLayout[i] != TYPE_GC_NONE) + { + hasGCRefs = true; + break; + } + } + } + + int32_t result = (!isValueType || hasGCRefs) ? 1 : 0; + + AddIns(INTOP_LDC_I4); + m_pLastNewIns->data[0] = result; + + PushInterpType(InterpTypeI4, nullptr); + m_pLastNewIns->SetDVar(m_pStackPointer[-1].var); + + return true; + } + case NI_System_Runtime_InteropService_MemoryMarshal_GetArrayDataReference: + { + CHECK_STACK(1); + + m_pStackPointer--; + int32_t arrayVar = m_pStackPointer[0].var; + + AddIns(INTOP_NULLCHECK); + m_pLastNewIns->SetSVar(arrayVar); + + AddIns(INTOP_ADD_P_IMM); + m_pLastNewIns->SetSVar(arrayVar); + m_pLastNewIns->data[0] = OFFSETOF__CORINFO_Array__data; + + PushInterpType(InterpTypeByRef, NULL); + m_pLastNewIns->SetDVar(m_pStackPointer[-1].var); + + return true; + } + + case NI_System_Threading_Thread_FastPollGC: + AddIns(INTOP_SAFEPOINT); + return true; + default: { #ifdef DEBUG @@ -2257,7 +2407,6 @@ bool InterpCompiler::EmitCallIntrinsics(CORINFO_METHOD_HANDLE method, CORINFO_SI return true; } } - // TODO: Add multi-dimensional array getters and setters } return false; @@ -2990,27 +3139,6 @@ void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool re m_ip += 5; } -static int32_t GetLdindForType(InterpType interpType) -{ - switch (interpType) - { - case InterpTypeI1: return INTOP_LDIND_I1; - case InterpTypeU1: return INTOP_LDIND_U1; - case InterpTypeI2: return INTOP_LDIND_I2; - case InterpTypeU2: return INTOP_LDIND_U2; - case InterpTypeI4: return INTOP_LDIND_I4; - case InterpTypeI8: return INTOP_LDIND_I8; - case InterpTypeR4: return INTOP_LDIND_R4; - case InterpTypeR8: return INTOP_LDIND_R8; - case InterpTypeO: return INTOP_LDIND_I; - case InterpTypeVT: return INTOP_LDIND_VT; - case InterpTypeByRef: return INTOP_LDIND_I; - default: - assert(0); - } - return -1; -} - static int32_t GetStindForType(InterpType interpType) { switch (interpType) @@ -5153,6 +5281,9 @@ void InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) volatile_ = true; m_ip++; break; + case CEE_UNALIGNED: + m_ip += 2; + break; case CEE_INITOBJ: { CHECK_STACK(1); diff --git a/src/coreclr/interpreter/intops.def b/src/coreclr/interpreter/intops.def index 14bd5d3ba0f587..2879e692084ebe 100644 --- a/src/coreclr/interpreter/intops.def +++ b/src/coreclr/interpreter/intops.def @@ -23,6 +23,7 @@ OPDEF(INTOP_LDC_R8, "ldc.r8", 4, 1, 0, InterpOpDouble) OPDEF(INTOP_LDPTR, "ldptr", 3, 1, 0, InterpOpLdPtr) OPDEF(INTOP_LDPTR_DEREF, "ldptr.deref", 3, 1, 0, InterpOpLdPtr) +OPDEF(INTOP_NULLCHECK, "nullcheck", 2, 1, 0, InterpOpNoArgs) OPDEF(INTOP_NEWARR, "newarr", 5, 1, 1, InterpOpPointerHelperFtn) OPDEF(INTOP_NEWARR_GENERIC, "newarr.generic", 6, 1, 2, InterpOpGenericHelperFtn) OPDEF(INTOP_NEWMDARR, "newmdarr", 5, 1, 1, InterpOpPointerInt) @@ -403,6 +404,10 @@ OPDEF(INTOP_GC_COLLECT, "gc.collect", 1, 0, 0, InterpOpNoArgs) OPDEF(INTOP_LOAD_FRAMEVAR, "load.framevar", 2, 1, 0, InterpOpNoArgs) +// Intrinsics +OPDEF(INTOP_COMPARE_EXCHANGE_I4, "compare.exchange.i4", 5, 1, 3, InterpOpNoArgs) +OPDEF(INTOP_COMPARE_EXCHANGE_I8, "compare.exchange.i8", 5, 1, 3, InterpOpNoArgs) + // All instructions after this point are IROPS, instructions that are not emitted/executed OPDEF(INTOP_NOP, "nop", 1, 0, 0, InterpOpNoArgs) OPDEF(INTOP_DEF, "def", 2, 1, 0, InterpOpNoArgs) diff --git a/src/coreclr/interpreter/intrinsics.cpp b/src/coreclr/interpreter/intrinsics.cpp index 1e86a3749a7906..c6bc8b05613b36 100644 --- a/src/coreclr/interpreter/intrinsics.cpp +++ b/src/coreclr/interpreter/intrinsics.cpp @@ -11,7 +11,8 @@ NamedIntrinsic GetNamedIntrinsic(COMP_HANDLE compHnd, CORINFO_METHOD_HANDLE comp { const char* className = NULL; const char* namespaceName = NULL; - const char* methodName = compHnd->getMethodNameFromMetadata(method, &className, &namespaceName, NULL, 0); + const char* enclosingClassNames[2] = {nullptr}; + const char* methodName = compHnd->getMethodNameFromMetadata(method, &className, &namespaceName, enclosingClassNames, ArrLen(enclosingClassNames)); // Array methods don't have metadata if (!namespaceName) @@ -76,6 +77,8 @@ NamedIntrinsic GetNamedIntrinsic(COMP_HANDLE compHnd, CORINFO_METHOD_HANDLE comp { if (!strcmp(methodName, "IsReferenceOrContainsReferences")) return NI_System_Runtime_CompilerServices_RuntimeHelpers_IsReferenceOrContainsReferences; + else if (!strcmp(methodName, "GetMethodTable")) + return NI_System_Runtime_CompilerServices_RuntimeHelpers_GetMethodTable; } } else if (!strcmp(namespaceName, "System.Runtime.InteropServices")) diff --git a/src/coreclr/interpreter/intrinsics.h b/src/coreclr/interpreter/intrinsics.h index d48d7b2bc0551a..0fb29eb417aaa6 100644 --- a/src/coreclr/interpreter/intrinsics.h +++ b/src/coreclr/interpreter/intrinsics.h @@ -8,4 +8,10 @@ NamedIntrinsic GetNamedIntrinsic(COMP_HANDLE compHnd, CORINFO_METHOD_HANDLE compMethod, CORINFO_METHOD_HANDLE method); +template +inline constexpr unsigned ArrLen(T (&)[size]) +{ + return size; +} + #endif // __INTERPRETER_INTRINSICS_H__ diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index 038530d1fccc47..ec1bc2da43aa31 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -329,7 +329,7 @@ template void ConvOvfHelper(int8_t *stack, void* DoGenericLookup(void* genericVarAsPtr, InterpGenericLookup* pLookup) { // TODO! If this becomes a performance bottleneck, we could expand out the various permutations of this - // so that we have 24 versions of lookup (or 48 is we allow for avoiding the null check), do the only + // so that we have 24 versions of lookup (or 48 is we allow for avoiding the null check), do the only // if check to figure out which one to use, and then have the rest of the logic be straight-line code. MethodTable *pMT = nullptr; MethodDesc* pMD = nullptr; @@ -506,6 +506,10 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr LOCAL_VAR(ip[1], void*) = *(void**)pMethod->pDataItems[ip[2]]; ip += 3; break; + case INTOP_NULLCHECK: + NULL_CHECK(LOCAL_VAR(ip[1], void*)); + ip += 2; + break; case INTOP_RET: // Return stack slot sized value *(int64_t*)pFrame->pRetVal = LOCAL_VAR(ip[1], int64_t); @@ -1946,6 +1950,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr // clear the valuetype memset(vtThis, 0, vtSize); + // pass the address of the valuetype LOCAL_VAR(callArgsOffset, void*) = vtThis; @@ -2360,6 +2365,29 @@ do { \ break; } +#define COMPARE_EXCHANGE(type) \ +do \ +{ \ + type* dst = (type*)LOCAL_VAR(ip[2], void*); \ + NULL_CHECK(dst); \ + type newValue = LOCAL_VAR(ip[3], type); \ + type comparand = LOCAL_VAR(ip[4], type); \ + type old = InterlockedCompareExchangeT(dst, newValue, comparand); \ + LOCAL_VAR(ip[1], type) = old; \ + ip += 5; \ +} while (0) + case INTOP_COMPARE_EXCHANGE_I4: + { + COMPARE_EXCHANGE(int32_t); + break; + } + + case INTOP_COMPARE_EXCHANGE_I8: + { + COMPARE_EXCHANGE(int64_t); + break; + } + case INTOP_CALL_FINALLY: { const int32_t* targetIp = ip + ip[1]; diff --git a/src/tests/JIT/interpreter/Interpreter.cs b/src/tests/JIT/interpreter/Interpreter.cs index 71a419cfdff908..f87bb14a405aa5 100644 --- a/src/tests/JIT/interpreter/Interpreter.cs +++ b/src/tests/JIT/interpreter/Interpreter.cs @@ -5,6 +5,8 @@ using System.Numerics; using System.Runtime.Intrinsics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; public interface ITest { @@ -949,11 +951,23 @@ public static void RunInterpreterTests() if (!TestPreciseInitCctors()) Environment.FailFast(null); - Console.WriteLine("Empty string length: {0}", string.Empty.Length); + Console.WriteLine("TestThreading_Interlocked_CompareExchange"); + if (!TestThreading_Interlocked_CompareExchange()) + Environment.FailFast(null); + + Console.WriteLine("TestRuntimeHelpers_IsReferenceOrContainsReferences"); + if (!TestRuntimeHelpers_IsReferenceOrContainsReferences()) + Environment.FailFast(null); + + Console.WriteLine("TestMemoryMarshal_GetArrayDataReference"); + if (!TestMemoryMarshal_GetArrayDataReference()) + Environment.FailFast(null); - Console.WriteLine("BitConverter.IsLittleEndian: {0}", BitConverter.IsLittleEndian); + Console.WriteLine("Empty string length: {0}", string.Empty.Length); - Console.WriteLine("IntPtr.Zero: {0}, UIntPtr.Zero: {1}", IntPtr.Zero, UIntPtr.Zero); + Console.WriteLine("BitConverter.IsLittleEndian: {0}", BitConverter.IsLittleEndian); + + Console.WriteLine("IntPtr.Zero: {0}, UIntPtr.Zero: {1}", IntPtr.Zero, UIntPtr.Zero); System.GC.Collect(); @@ -2040,7 +2054,7 @@ public static T[] TestNewArr(int len) public static T[,,] TestNewMDArr(int len) { - return new T[len,len-1,len-2]; + return new T[len, len - 1, len - 2]; } public static object Box(T value) @@ -2072,7 +2086,7 @@ public static void TriggerCctorClass() } public static void TriggerCctorMethod() - {} + { } } class MyClass @@ -2906,4 +2920,72 @@ public static bool TestStaticVirtualGeneric_CodePointerCase() return true; } + + public static bool TestThreading_Interlocked_CompareExchange() + { + // Value type test + int location = 1; + int value = 2; + int comparand = 1; + int result = System.Threading.Interlocked.CompareExchange(ref location, value, comparand); + if (!(result == 1 && location == 2)) + return false; + + // Reference type test + object objLocation = "a"; + object objValue = "b"; + object objComparand = "a"; + object objResult = System.Threading.Interlocked.CompareExchange(ref objLocation, objValue, objComparand); + if (!(object.ReferenceEquals(objResult, objComparand) && object.ReferenceEquals(objLocation, objValue))) + return false; + + // Reference type test (fail) + objLocation = "a"; + objValue = "b"; + objComparand = "c"; + objResult = System.Threading.Interlocked.CompareExchange(ref objLocation, objValue, objComparand); + if (!(object.ReferenceEquals(objResult, objLocation) && object.ReferenceEquals(objLocation, "a"))) + return false; + + // Null reference test + objLocation = null; + objValue = "b"; + objComparand = null; + objResult = System.Threading.Interlocked.CompareExchange(ref objLocation, objValue, objComparand); + if (!(objResult is null && object.ReferenceEquals(objLocation, objValue))) + return false; + + return true; + } + + public static bool TestRuntimeHelpers_IsReferenceOrContainsReferences() + { + if (!RuntimeHelpers.IsReferenceOrContainsReferences()) + return false; + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + return false; + if (!RuntimeHelpers.IsReferenceOrContainsReferences()) + return false; + return true; + } + + public static bool TestMemoryMarshal_GetArrayDataReference() + { + int[] arr = new int[1]; + ref int dataRef = ref MemoryMarshal.GetArrayDataReference(arr); + dataRef = 42; + if (arr[0] != 42) + return false; + + arr = null; + try + { + MemoryMarshal.GetArrayDataReference(arr); + return false; + } + catch (NullReferenceException) + { + return true; + } + } }