diff --git a/src/coreclr/interpreter/CMakeLists.txt b/src/coreclr/interpreter/CMakeLists.txt index 06536580d1a440..00536cb13a8286 100644 --- a/src/coreclr/interpreter/CMakeLists.txt +++ b/src/coreclr/interpreter/CMakeLists.txt @@ -12,6 +12,7 @@ set(INTERPRETER_SOURCES stackmap.cpp naming.cpp methodset.cpp + intrinsics.cpp ../../native/containers/dn-simdhash.c ../../native/containers/dn-simdhash-ght-compatible.c ../../native/containers/dn-simdhash-ptr-ptr.c) diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index 6b49b7bcd3d3a7..4535b1e195e867 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -2180,6 +2180,44 @@ int32_t InterpCompiler::GetDataItemIndexForHelperFtn(CorInfoHelpFunc ftn) return GetDataItemIndex(addr); } +bool InterpCompiler::EmitNamedIntrinsicCall(NamedIntrinsic ni, CORINFO_CLASS_HANDLE clsHnd, CORINFO_METHOD_HANDLE method, CORINFO_SIG_INFO sig) +{ + bool mustExpand = (method == m_methodHnd); + if (!mustExpand && (ni == NI_Illegal)) + return false; + + switch (ni) + { + case NI_IsSupported_False: + case NI_IsSupported_True: + AddIns(INTOP_LDC_I4); + m_pLastNewIns->data[0] = ni == NI_IsSupported_True; + PushStackType(StackTypeI4, NULL); + m_pLastNewIns->SetDVar(m_pStackPointer[-1].var); + return true; + + case NI_Throw_PlatformNotSupportedException: + AddIns(INTOP_THROW_PNSE); + return true; + + default: + { +#ifdef DEBUG + if (m_verbose) + { + const char* className = NULL; + const char* namespaceName = NULL; + const char* methodName = m_compHnd->getMethodNameFromMetadata(method, &className, &namespaceName, NULL, 0); + printf("WARNING: Intrinsic not implemented in EmitNamedIntrinsicCall: %d (for %s.%s.%s)\n", ni, namespaceName, className, methodName); + } +#endif + if (mustExpand) + NO_WAY("EmitNamedIntrinsicCall not implemented must-expand intrinsic"); + return false; + } + } +} + bool InterpCompiler::EmitCallIntrinsics(CORINFO_METHOD_HANDLE method, CORINFO_SIG_INFO sig) { const char *className = NULL; @@ -2384,7 +2422,7 @@ void InterpCompiler::EmitPushUnboxAnyNullable(const CORINFO_GENERICHANDLE_RESULT AddIns(INTOP_CALL_HELPER_V_AGS); m_pLastNewIns->data[0] = GetDataItemIndexForHelperFtn(CORINFO_HELP_UNBOX_NULLABLE); m_pLastNewIns->data[1] = handleData.dataItemIndex; - + m_pLastNewIns->SetSVars2(handleData.genericVar, arg2); m_pLastNewIns->SetDVar(resultVar); } @@ -2484,7 +2522,7 @@ int InterpCompiler::EmitGenericHandleAsVar(const CORINFO_GENERICHANDLE_RESULT &e PushStackType(StackTypeI, NULL); int resultVar = m_pStackPointer[-1].var; m_pStackPointer--; - + GenericHandleData handleData = GenericHandleToGenericHandleData(embedInfo); if (handleData.argType == HelperArgType::GenericResolution) @@ -2584,6 +2622,19 @@ void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool re flags = (CORINFO_CALLINFO_FLAGS)(flags | CORINFO_CALLINFO_CALLVIRT); m_compHnd->getCallInfo(&resolvedCallToken, pConstrainedToken, m_methodInfo->ftn, flags, &callInfo); + if (callInfo.methodFlags & CORINFO_FLG_INTRINSIC) + { + if (InterpConfig.InterpMode() >= 3) + { + NamedIntrinsic ni = GetNamedIntrinsic(m_compHnd, m_methodHnd, callInfo.hMethod); + if (EmitNamedIntrinsicCall(ni, resolvedCallToken.hClass, callInfo.hMethod, callInfo.sig)) + { + m_ip += 5; + return; + } + } + } + if (EmitCallIntrinsics(callInfo.hMethod, callInfo.sig)) { m_ip += 5; @@ -2675,7 +2726,7 @@ void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool re PushInterpType(ctorType, resolvedCallToken.hClass); CORINFO_GENERICHANDLE_RESULT newObjGenericHandleEmbedInfo; - m_compHnd->embedGenericHandle(&resolvedCallToken, true, m_methodInfo->ftn, &newObjGenericHandleEmbedInfo); + m_compHnd->embedGenericHandle(&resolvedCallToken, true, m_methodInfo->ftn, &newObjGenericHandleEmbedInfo); newObjData = GenericHandleToGenericHandleData(newObjGenericHandleEmbedInfo); } newObjDVar = m_pStackPointer[-2].var; @@ -2823,9 +2874,9 @@ void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool re else if ((callInfo.classFlags & CORINFO_FLG_ARRAY) && newObj) { CORINFO_GENERICHANDLE_RESULT newObjGenericHandleEmbedInfo; - m_compHnd->embedGenericHandle(&resolvedCallToken, true, m_methodInfo->ftn, &newObjGenericHandleEmbedInfo); + m_compHnd->embedGenericHandle(&resolvedCallToken, true, m_methodInfo->ftn, &newObjGenericHandleEmbedInfo); newObjData = GenericHandleToGenericHandleData(newObjGenericHandleEmbedInfo); - + if (newObjData.argType == HelperArgType::GenericResolution) { AddIns(INTOP_NEWMDARR_GENERIC); @@ -5378,17 +5429,17 @@ void InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) { // Unbox.any of a reference type is just a cast CorInfoHelpFunc castingHelper = m_compHnd->getCastingHelper(&resolvedToken, true /* throwing */); - + CORINFO_GENERICHANDLE_RESULT embedInfo; InterpEmbedGenericResult result; m_compHnd->embedGenericHandle(&resolvedToken, false, m_methodInfo->ftn, &embedInfo); - + EmitPushHelperCall_2(castingHelper, embedInfo, m_pStackPointer[0].var, g_stackTypeFromInterpType[InterpTypeO], NULL); } else { CorInfoHelpFunc helpFunc = m_compHnd->getUnBoxHelper((CORINFO_CLASS_HANDLE)embedInfo.compileTimeHandle); - + if (helpFunc == CORINFO_HELP_UNBOX) { EmitPushUnboxAny(embedInfo, m_pStackPointer[0].var, g_stackTypeFromInterpType[GetInterpType(m_compHnd->asCorInfoType(resolvedToken.hClass))], resolvedToken.hClass); @@ -5431,7 +5482,7 @@ void InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) AddIns(INTOP_NEWARR_GENERIC); m_pLastNewIns->data[0] = GetDataItemIndexForHelperFtn(helpFunc); m_pLastNewIns->data[1] = handleData.dataItemIndex; - + m_pLastNewIns->SetSVars2(handleData.genericVar, newArrLenVar); m_pLastNewIns->SetDVar(m_pStackPointer[-1].var); } diff --git a/src/coreclr/interpreter/compiler.h b/src/coreclr/interpreter/compiler.h index 95a04bc2378ade..1ea75c8db439bb 100644 --- a/src/coreclr/interpreter/compiler.h +++ b/src/coreclr/interpreter/compiler.h @@ -11,6 +11,7 @@ #include #include "failures.h" #include "simdhash.h" +#include "intrinsics.h" struct InterpException { @@ -695,6 +696,7 @@ class InterpCompiler void EmitShiftOp(int32_t opBase); void EmitCompareOp(int32_t opBase); void EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool readonly, bool tailcall, bool newObj, bool isCalli); + bool EmitNamedIntrinsicCall(NamedIntrinsic ni, CORINFO_CLASS_HANDLE clsHnd, CORINFO_METHOD_HANDLE method, CORINFO_SIG_INFO sig); bool EmitCallIntrinsics(CORINFO_METHOD_HANDLE method, CORINFO_SIG_INFO sig); void EmitLdind(InterpType type, CORINFO_CLASS_HANDLE clsHnd, int32_t offset); void EmitStind(InterpType type, CORINFO_CLASS_HANDLE clsHnd, int32_t offset, bool reverseSVarOrder); diff --git a/src/coreclr/interpreter/eeinterp.cpp b/src/coreclr/interpreter/eeinterp.cpp index c449a53714949a..d0351825e06120 100644 --- a/src/coreclr/interpreter/eeinterp.cpp +++ b/src/coreclr/interpreter/eeinterp.cpp @@ -48,27 +48,55 @@ CorJitResult CILInterp::compileMethod(ICorJitInfo* compHnd, uint32_t* nativeSizeOfCode) { - bool doInterpret; + bool doInterpret = false; + + if ((g_interpModule != NULL) && (methodInfo->scope == g_interpModule)) + doInterpret = true; - if (g_interpModule != NULL) - { - if (methodInfo->scope == g_interpModule) - doInterpret = true; - else - doInterpret = false; - } - else { - const char *methodName = compHnd->getMethodNameFromMetadata(methodInfo->ftn, nullptr, nullptr, nullptr, 0); + switch (InterpConfig.InterpMode()) + { + // 0: default, do not use interpreter except explicit opt-in via DOTNET_Interpreter + case 0: + break; + + // 1: use interpreter for everything except (1) methods that have R2R compiled code and (2) all code in System.Private.CoreLib. All code in System.Private.CoreLib falls back to JIT if there is no R2R available for it. + case 1: + { + doInterpret = true; + const char *assemblyName = compHnd->getClassAssemblyName(compHnd->getMethodClass(methodInfo->ftn)); + if (assemblyName && !strcmp(assemblyName, "System.Private.CoreLib")) + doInterpret = false; + break; + } + + // 2: use interpreter for everything except intrinsics. All intrinsics fallback to JIT. Implies DOTNET_ReadyToRun=0. + case 2: + doInterpret = !(compHnd->getMethodAttribs(methodInfo->ftn) & CORINFO_FLG_INTRINSIC); + break; + + // 3: use interpreter for everything, the full interpreter-only mode, no fallbacks to R2R or JIT whatsoever. Implies DOTNET_ReadyToRun=0, DOTNET_EnableHWIntrinsic=0 + case 3: + doInterpret = true; + break; + + default: + NO_WAY("Unsupported value for DOTNET_InterpMode"); + break; + } + #ifdef TARGET_WASM // interpret everything on wasm doInterpret = true; #else - doInterpret = (InterpConfig.Interpreter().contains(compHnd, methodInfo->ftn, compHnd->getMethodClass(methodInfo->ftn), &methodInfo->args)); -#endif - - if (doInterpret) + // NOTE: We do this check even if doInterpret==true in order to populate g_interpModule + const char *methodName = compHnd->getMethodNameFromMetadata(methodInfo->ftn, nullptr, nullptr, nullptr, 0); + if (InterpConfig.Interpreter().contains(compHnd, methodInfo->ftn, compHnd->getMethodClass(methodInfo->ftn), &methodInfo->args)) + { + doInterpret = true; g_interpModule = methodInfo->scope; + } +#endif } if (!doInterpret) diff --git a/src/coreclr/interpreter/interpconfigvalues.h b/src/coreclr/interpreter/interpconfigvalues.h index 53ff3cbf7c491a..a76685a1fc7011 100644 --- a/src/coreclr/interpreter/interpconfigvalues.h +++ b/src/coreclr/interpreter/interpconfigvalues.h @@ -23,6 +23,11 @@ RELEASE_CONFIG_METHODSET(Interpreter, "Interpreter") CONFIG_METHODSET(InterpHalt, "InterpHalt"); CONFIG_METHODSET(InterpDump, "InterpDump"); CONFIG_INTEGER(InterpList, "InterpList", 0); // List the methods which are compiled by the interpreter JIT +RELEASE_CONFIG_INTEGER(InterpMode, "InterpMode", 0); // Interpreter mode, one of the following: +// 0: default, do not use interpreter except explicit opt-in via DOTNET_Interpreter +// 1: use interpreter for everything except (1) methods that have R2R compiled code and (2) all code in System.Private.CoreLib. All code in System.Private.CoreLib falls back to JIT if there is no R2R available for it. +// 2: use interpreter for everything except intrinsics. All intrinsics fallback to JIT. Implies DOTNET_ReadyToRun=0. +// 3: use interpreter for everything, the full interpreter-only mode, no fallbacks to R2R or JIT whatsoever. Implies DOTNET_ReadyToRun=0, DOTNET_EnableHWIntrinsic=0 #undef CONFIG_STRING #undef RELEASE_CONFIG_STRING diff --git a/src/coreclr/interpreter/intops.def b/src/coreclr/interpreter/intops.def index 3bb90e14cb5d1e..14bd5d3ba0f587 100644 --- a/src/coreclr/interpreter/intops.def +++ b/src/coreclr/interpreter/intops.def @@ -397,6 +397,7 @@ OPDEF(INTOP_LEAVE_FILTER, "leavefilter", 2, 0, 1, InterpOpNoArgs) OPDEF(INTOP_LEAVE_CATCH, "leavecatch", 2, 0, 0, InterpOpBranch) OPDEF(INTOP_LOAD_EXCEPTION, "load.exception", 2, 1, 0, InterpOpNoArgs) +OPDEF(INTOP_THROW_PNSE, "throw.pnse", 1, 0, 0, InterpOpNoArgs) OPDEF(INTOP_FAILFAST, "failfast", 1, 0, 0, InterpOpNoArgs) OPDEF(INTOP_GC_COLLECT, "gc.collect", 1, 0, 0, InterpOpNoArgs) diff --git a/src/coreclr/interpreter/intrinsics.cpp b/src/coreclr/interpreter/intrinsics.cpp new file mode 100644 index 00000000000000..1e86a3749a7906 --- /dev/null +++ b/src/coreclr/interpreter/intrinsics.cpp @@ -0,0 +1,114 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "interpreter.h" +#include "intrinsics.h" + +#define HAS_PREFIX(haystack, needle) \ + (strncmp(haystack, needle, strlen(needle)) == 0) + +NamedIntrinsic GetNamedIntrinsic(COMP_HANDLE compHnd, CORINFO_METHOD_HANDLE compMethod, CORINFO_METHOD_HANDLE method) +{ + const char* className = NULL; + const char* namespaceName = NULL; + const char* methodName = compHnd->getMethodNameFromMetadata(method, &className, &namespaceName, NULL, 0); + + // Array methods don't have metadata + if (!namespaceName) + return NI_Illegal; + + if (!HAS_PREFIX(namespaceName, "System")) + return NI_Illegal; + + if (!strcmp(namespaceName, "System")) + { + if (!strcmp(className, "Double") || !strcmp(className, "Single")) + { + if (!strcmp(methodName, "ConvertToIntegerNative")) + return NI_PRIMITIVE_ConvertToIntegerNative; + else if (!strcmp(methodName, "MultiplyAddEstimate")) + return NI_System_Math_MultiplyAddEstimate; + } + else if (!strcmp(className, "Math") || !strcmp(className, "MathF")) + { + if (!strcmp(methodName, "ReciprocalEstimate")) + return NI_System_Math_ReciprocalEstimate; + else if (!strcmp(methodName, "ReciprocalSqrtEstimate")) + return NI_System_Math_ReciprocalSqrtEstimate; + } + } + else if (!strcmp(namespaceName, "System.Numerics")) + { + if (!strcmp(className, "Vector") && !strcmp(methodName, "get_IsHardwareAccelerated")) + return NI_IsSupported_False; + + // Fall back to managed implementation for everything else. + return NI_Illegal; + } + else if (!strcmp(namespaceName, "System.Runtime.Intrinsics")) + { + // Vector128 etc + if (HAS_PREFIX(className, "Vector") && !strcmp(methodName, "get_IsHardwareAccelerated")) + return NI_IsSupported_False; + + // Fall back to managed implementation for everything else. + return NI_Illegal; + } + else if (HAS_PREFIX(namespaceName, "System.Runtime.Intrinsics")) + { + // Architecture-specific intrinsics. + if (!strcmp(methodName, "get_IsSupported")) + return NI_IsSupported_False; + + // Every intrinsic except IsSupported is PNSE in interpreted-only mode. + return NI_Throw_PlatformNotSupportedException; + } + else if (HAS_PREFIX(namespaceName, "System.Runtime")) + { + if (!strcmp(namespaceName, "System.Runtime.CompilerServices")) + { + if (!strcmp(className, "StaticsHelpers")) + { + if (!strcmp(methodName, "VolatileReadAsByref")) + return NI_System_Runtime_CompilerServices_StaticsHelpers_VolatileReadAsByref; + } + else if (!strcmp(className, "RuntimeHelpers")) + { + if (!strcmp(methodName, "IsReferenceOrContainsReferences")) + return NI_System_Runtime_CompilerServices_RuntimeHelpers_IsReferenceOrContainsReferences; + } + } + else if (!strcmp(namespaceName, "System.Runtime.InteropServices")) + { + if (!strcmp(className, "MemoryMarshal")) + { + if (!strcmp(methodName, "GetArrayDataReference")) + return NI_System_Runtime_InteropService_MemoryMarshal_GetArrayDataReference; + } + } + } + else if (!strcmp(namespaceName, "System.Threading")) + { + if (!strcmp(className, "Interlocked")) + { + if (!strcmp(methodName, "CompareExchange")) + return NI_System_Threading_Interlocked_CompareExchange; + else if (!strcmp(methodName, "MemoryBarrier")) + return NI_System_Threading_Interlocked_MemoryBarrier; + } + else if (!strcmp(className, "Thread")) + { + if (!strcmp(methodName, "FastPollGC")) + return NI_System_Threading_Thread_FastPollGC; + } + else if (!strcmp(className, "Volatile")) + { + if (!strcmp(methodName, "ReadBarrier")) + return NI_System_Threading_Volatile_ReadBarrier; + else if (!strcmp(methodName, "WriteBarrier")) + return NI_System_Threading_Volatile_WriteBarrier; + } + } + + return NI_Illegal; +} diff --git a/src/coreclr/interpreter/intrinsics.h b/src/coreclr/interpreter/intrinsics.h new file mode 100644 index 00000000000000..d48d7b2bc0551a --- /dev/null +++ b/src/coreclr/interpreter/intrinsics.h @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef __INTERPRETER_INTRINSICS_H__ +#define __INTERPRETER_INTRINSICS_H__ + +#include "../jit/namedintrinsiclist.h" + +NamedIntrinsic GetNamedIntrinsic(COMP_HANDLE compHnd, CORINFO_METHOD_HANDLE compMethod, CORINFO_METHOD_HANDLE method); + +#endif // __INTERPRETER_INTRINSICS_H__ diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index e95739b504fd29..038530d1fccc47 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -44,7 +44,7 @@ void InvokeCompiledMethod(MethodDesc *pMD, int8_t *pArgs, int8_t *pRet) } } - pHeader->SetTarget(pMD->GetMultiCallableAddrOfCode()); // The method to call + pHeader->SetTarget(pMD->GetMultiCallableAddrOfCode(CORINFO_ACCESS_ANY)); // The method to call pHeader->Invoke(pHeader->Routines, pArgs, pRet, pHeader->TotalStackSize); } @@ -2391,6 +2391,9 @@ do { \ case INTOP_LEAVE_CATCH: *(const int32_t**)pFrame->pRetVal = ip + ip[1]; goto EXIT_FRAME; + case INTOP_THROW_PNSE: + COMPlusThrow(kPlatformNotSupportedException); + break; case INTOP_FAILFAST: assert(0); break; diff --git a/src/tests/JIT/interpreter/Interpreter.cs b/src/tests/JIT/interpreter/Interpreter.cs index fe4ef19be4424f..71a419cfdff908 100644 --- a/src/tests/JIT/interpreter/Interpreter.cs +++ b/src/tests/JIT/interpreter/Interpreter.cs @@ -3,6 +3,7 @@ using System; using System.Numerics; +using System.Runtime.Intrinsics; using System.Runtime.CompilerServices; public interface ITest @@ -932,6 +933,10 @@ public static void RunInterpreterTests() if (!TestDelegate()) Environment.FailFast(null); + Console.WriteLine("TestIntrinsics"); + if (!TestIntrinsics()) + Environment.FailFast(null); + Console.WriteLine("TestCalli"); if (!TestCalli()) Environment.FailFast(null); @@ -955,6 +960,17 @@ public static void RunInterpreterTests() Console.WriteLine("All tests passed successfully!"); } + public static bool TestIntrinsics() + { + Console.WriteLine("Vector128.IsHardwareAccelerated="); + Console.WriteLine(Vector128.IsHardwareAccelerated); + Console.WriteLine("X86Base.IsSupported="); + Console.WriteLine(System.Runtime.Intrinsics.X86.X86Base.IsSupported); + Console.WriteLine("ArmBase.IsSupported="); + Console.WriteLine(System.Runtime.Intrinsics.Arm.ArmBase.IsSupported); + return true; + } + public static void TestExceptionHandling() { TestTryFinally();