From a1af0f2ad6cf51107a636dbe280cf939abd46b34 Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Fri, 13 Mar 2020 16:50:58 -0700 Subject: [PATCH] Make NativeCallableAttribute public. (#33005) * Avoid creating a COM Delegate when calling NativeCallableAttribute. * Create reverse P/Invoke frame. Create Preempt and Coop PreStubWorker(). * Limit the exclusion for NativeCallableAttribute to Win-x86. * Add Reverse P/Invoke JIT helpers to CrossGen2. * Add test for generic class with NativeCallableAttribute method. * Implement Unix EH change for NativeCallableAttribute method. Co-authored-by: Jan Vorlicek Co-authored-by: Jan Kotas --- src/coreclr/src/inc/corcompile.h | 1 - src/coreclr/src/inc/jithelpers.h | 4 +- src/coreclr/src/inc/readytorun.h | 5 +- src/coreclr/src/inc/readytorunhelpers.h | 2 + .../Common/Internal/Runtime/ReadyToRunConstants.cs | 7 +- .../src/tools/Common/JitInterface/CorInfoImpl.cs | 12 +- .../Common/TypeSystem/Common/ExceptionStringID.cs | 3 + .../JitInterface/CorInfoImpl.ReadyToRun.cs | 38 +- .../ReadyToRunSignature.cs | 8 + src/coreclr/src/vm/cgensys.h | 8 + src/coreclr/src/vm/codeversion.cpp | 6 +- src/coreclr/src/vm/codeversion.h | 7 +- src/coreclr/src/vm/comdelegate.cpp | 23 +- src/coreclr/src/vm/comdelegate.h | 6 +- src/coreclr/src/vm/exceptionhandling.cpp | 15 +- src/coreclr/src/vm/frames.h | 6 + src/coreclr/src/vm/jithelpers.cpp | 53 +++ src/coreclr/src/vm/jitinterface.cpp | 94 +++-- src/coreclr/src/vm/jitinterface.h | 8 +- src/coreclr/src/vm/method.hpp | 9 +- src/coreclr/src/vm/prestub.cpp | 244 +++++++++---- src/coreclr/src/vm/stacksampler.cpp | 5 +- src/coreclr/src/vm/threads.h | 11 + src/coreclr/src/vm/tieredcompilation.cpp | 5 + src/coreclr/src/zap/zapimport.cpp | 15 +- src/coreclr/src/zap/zapinfo.cpp | 25 +- .../Interop/NativeCallable/NativeCallableDll.cpp | 44 ++- .../Interop/NativeCallable/NativeCallableTest.cs | 393 ++++++++++++++++++--- .../NativeCallable/NativeCallableTest.csproj | 2 +- .../src/Resources/Strings.resx | 12 +- .../InteropServices/NativeCallableAttribute.cs | 16 +- .../ref/System.Runtime.InteropServices.cs | 7 + 32 files changed, 863 insertions(+), 231 deletions(-) diff --git a/src/coreclr/src/inc/corcompile.h b/src/coreclr/src/inc/corcompile.h index 9f41fd1..c34f31e 100644 --- a/src/coreclr/src/inc/corcompile.h +++ b/src/coreclr/src/inc/corcompile.h @@ -702,7 +702,6 @@ enum CORCOMPILE_FIXUP_BLOB_KIND ENCODE_VARARGS_METHODREF, ENCODE_VARARGS_SIG, ENCODE_ACTIVE_DEPENDENCY, /* Conditional active dependency */ - ENCODE_METHOD_NATIVE_ENTRY, /* NativeCallable method token */ }; enum EncodeMethodSigFlags diff --git a/src/coreclr/src/inc/jithelpers.h b/src/coreclr/src/inc/jithelpers.h index 8087f4f..8e92cbd 100644 --- a/src/coreclr/src/inc/jithelpers.h +++ b/src/coreclr/src/inc/jithelpers.h @@ -346,8 +346,8 @@ JITHELPER(CORINFO_HELP_JIT_PINVOKE_BEGIN, JIT_PInvokeBegin, CORINFO_HELP_SIG_REG_ONLY) JITHELPER(CORINFO_HELP_JIT_PINVOKE_END, JIT_PInvokeEnd, CORINFO_HELP_SIG_REG_ONLY) - JITHELPER(CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER, NULL, CORINFO_HELP_SIG_UNDEF) - JITHELPER(CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT, NULL, CORINFO_HELP_SIG_UNDEF) + JITHELPER(CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER, JIT_ReversePInvokeEnter, CORINFO_HELP_SIG_REG_ONLY) + JITHELPER(CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT, JIT_ReversePInvokeExit, CORINFO_HELP_SIG_REG_ONLY) JITHELPER(CORINFO_HELP_GVMLOOKUP_FOR_SLOT, NULL, CORINFO_HELP_SIG_NO_ALIGN_STUB) diff --git a/src/coreclr/src/inc/readytorun.h b/src/coreclr/src/inc/readytorun.h index dac75da..44e4d02 100644 --- a/src/coreclr/src/inc/readytorun.h +++ b/src/coreclr/src/inc/readytorun.h @@ -256,6 +256,8 @@ enum ReadyToRunHelper READYTORUN_HELPER_PInvokeBegin = 0x42, READYTORUN_HELPER_PInvokeEnd = 0x43, READYTORUN_HELPER_GCPoll = 0x44, + READYTORUN_HELPER_ReversePInvokeEnter = 0x45, + READYTORUN_HELPER_ReversePInvokeExit = 0x46, // Get string handle lazily READYTORUN_HELPER_GetString = 0x50, @@ -382,7 +384,8 @@ struct READYTORUN_EXCEPTION_CLAUSE enum ReadyToRunRuntimeConstants : DWORD { - READYTORUN_PInvokeTransitionFrameSizeInPointerUnits = 11 + READYTORUN_PInvokeTransitionFrameSizeInPointerUnits = 11, + READYTORUN_ReversePInvokeTransitionFrameSizeInPointerUnits = 2 }; #endif // __READYTORUN_H__ diff --git a/src/coreclr/src/inc/readytorunhelpers.h b/src/coreclr/src/inc/readytorunhelpers.h index c9a60af..3703412 100644 --- a/src/coreclr/src/inc/readytorunhelpers.h +++ b/src/coreclr/src/inc/readytorunhelpers.h @@ -114,6 +114,8 @@ HELPER(READYTORUN_HELPER_EndCatch, CORINFO_HELP_ENDCATCH, HELPER(READYTORUN_HELPER_PInvokeBegin, CORINFO_HELP_JIT_PINVOKE_BEGIN, ) HELPER(READYTORUN_HELPER_PInvokeEnd, CORINFO_HELP_JIT_PINVOKE_END, ) HELPER(READYTORUN_HELPER_GCPoll, CORINFO_HELP_POLL_GC, ) +HELPER(READYTORUN_HELPER_ReversePInvokeEnter, CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER, ) +HELPER(READYTORUN_HELPER_ReversePInvokeExit, CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT, ) HELPER(READYTORUN_HELPER_MonitorEnter, CORINFO_HELP_MON_ENTER, ) HELPER(READYTORUN_HELPER_MonitorExit, CORINFO_HELP_MON_EXIT, ) diff --git a/src/coreclr/src/tools/Common/Internal/Runtime/ReadyToRunConstants.cs b/src/coreclr/src/tools/Common/Internal/Runtime/ReadyToRunConstants.cs index ade2439..a360b8b 100644 --- a/src/coreclr/src/tools/Common/Internal/Runtime/ReadyToRunConstants.cs +++ b/src/coreclr/src/tools/Common/Internal/Runtime/ReadyToRunConstants.cs @@ -175,6 +175,8 @@ namespace Internal.ReadyToRunConstants PInvokeBegin = 0x42, PInvokeEnd = 0x43, GCPoll = 0x44, + ReversePInvokeEnter = 0x45, + ReversePInvokeExit = 0x46, // Get string handle lazily GetString = 0x50, @@ -295,10 +297,6 @@ namespace Internal.ReadyToRunConstants CheckCastInterface, CheckInstanceInterface, - // P/Invoke support - ReversePInvokeEnter, - ReversePInvokeExit, - MonitorEnterStatic, MonitorExitStatic, @@ -314,5 +312,6 @@ namespace Internal.ReadyToRunConstants public static class ReadyToRunRuntimeConstants { public const int READYTORUN_PInvokeTransitionFrameSizeInPointerUnits = 11; + public const int READYTORUN_ReversePInvokeTransitionFrameSizeInPointerUnits = 2; } } diff --git a/src/coreclr/src/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/src/tools/Common/JitInterface/CorInfoImpl.cs index 939d829..1fadcd5 100644 --- a/src/coreclr/src/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/src/tools/Common/JitInterface/CorInfoImpl.cs @@ -2220,7 +2220,7 @@ namespace Internal.JitInterface pEEInfoOut.offsetOfDelegateInstance = (uint)pointerSize; // Delegate::m_firstParameter pEEInfoOut.offsetOfDelegateFirstTarget = OffsetOfDelegateFirstTarget; - pEEInfoOut.sizeOfReversePInvokeFrame = (uint)(2 * pointerSize); + pEEInfoOut.sizeOfReversePInvokeFrame = (uint)SizeOfReversePInvokeTransitionFrame; pEEInfoOut.osPageSize = new UIntPtr(0x1000); @@ -2882,7 +2882,17 @@ namespace Internal.JitInterface } if (this.MethodBeingCompiled.IsNativeCallable) + { +#if READYTORUN + if (targetArchitecture == TargetArchitecture.X86 + && _compilation.TypeSystemContext.Target.OperatingSystem == TargetOS.Windows) + { + throw new RequiresRuntimeJitException("ReadyToRun: Methods with NativeCallableAttribute not implemented"); + } +#endif + flags.Set(CorJitFlag.CORJIT_FLAG_REVERSE_PINVOKE); + } if (this.MethodBeingCompiled.IsPInvoke) { diff --git a/src/coreclr/src/tools/Common/TypeSystem/Common/ExceptionStringID.cs b/src/coreclr/src/tools/Common/TypeSystem/Common/ExceptionStringID.cs index 7de12aa..9c0ef31 100644 --- a/src/coreclr/src/tools/Common/TypeSystem/Common/ExceptionStringID.cs +++ b/src/coreclr/src/tools/Common/TypeSystem/Common/ExceptionStringID.cs @@ -34,6 +34,9 @@ namespace Internal.TypeSystem InvalidProgramNativeCallable, InvalidProgramCallAbstractMethod, InvalidProgramCallVirtStatic, + InvalidProgramNonStaticMethod, + InvalidProgramGenericMethod, + InvalidProgramNonBlittableTypes, // BadImageFormatException BadImageFormatGeneric, diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs index f9ca5b5..2e72b4c 100644 --- a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -608,6 +608,14 @@ namespace Internal.JitInterface id = ReadyToRunHelper.GCPoll; break; + case CorInfoHelpFunc.CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER: + id = ReadyToRunHelper.ReversePInvokeEnter; + break; + + case CorInfoHelpFunc.CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT: + id = ReadyToRunHelper.ReversePInvokeExit; + break; + case CorInfoHelpFunc.CORINFO_HELP_INITCLASS: case CorInfoHelpFunc.CORINFO_HELP_INITINSTCLASS: case CorInfoHelpFunc.CORINFO_HELP_THROW_ARGUMENTEXCEPTION: @@ -616,8 +624,6 @@ namespace Internal.JitInterface case CorInfoHelpFunc.CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE_MAYBENULL: case CorInfoHelpFunc.CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPEHANDLE_MAYBENULL: case CorInfoHelpFunc.CORINFO_HELP_GETREFANY: - case CorInfoHelpFunc.CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER: - case CorInfoHelpFunc.CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT: throw new RequiresRuntimeJitException(ftnNum.ToString()); default: @@ -1145,6 +1151,25 @@ namespace Internal.JitInterface ThrowHelper.ThrowInvalidProgramException(ExceptionStringID.InvalidProgramCallVirtStatic, originalMethod); } + if ((flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_LDFTN) != 0 + && originalMethod.IsNativeCallable) + { + if (!originalMethod.Signature.IsStatic) // Must be a static method + { + ThrowHelper.ThrowInvalidProgramException(ExceptionStringID.InvalidProgramNonStaticMethod, originalMethod); + } + + if (originalMethod.HasInstantiation || originalMethod.OwningType.HasInstantiation) // No generics involved + { + ThrowHelper.ThrowInvalidProgramException(ExceptionStringID.InvalidProgramGenericMethod, originalMethod); + } + + if (Marshaller.IsMarshallingRequired(originalMethod.Signature, Array.Empty())) // Only blittable arguments + { + ThrowHelper.ThrowInvalidProgramException(ExceptionStringID.InvalidProgramNonBlittableTypes, originalMethod); + } + } + exactType = type; constrainedType = null; @@ -1635,6 +1660,14 @@ namespace Internal.JitInterface pResult->methodFlags = FilterNamedIntrinsicMethodAttribs(pResult->methodFlags, methodToCall); + var targetDetails = _compilation.TypeSystemContext.Target; + if (targetDetails.Architecture == TargetArchitecture.X86 + && targetDetails.OperatingSystem == TargetOS.Windows + && targetMethod.IsNativeCallable) + { + throw new RequiresRuntimeJitException("ReadyToRun: References to methods with NativeCallableAttribute not implemented"); + } + if (pResult->thisTransform == CORINFO_THIS_TRANSFORM.CORINFO_BOX_THIS) { // READYTORUN: FUTURE: Optionally create boxing stub at runtime @@ -2297,6 +2330,7 @@ namespace Internal.JitInterface } private int SizeOfPInvokeTransitionFrame => ReadyToRunRuntimeConstants.READYTORUN_PInvokeTransitionFrameSizeInPointerUnits * _compilation.NodeFactory.Target.PointerSize; + private int SizeOfReversePInvokeTransitionFrame => ReadyToRunRuntimeConstants.READYTORUN_ReversePInvokeTransitionFrameSizeInPointerUnits * _compilation.NodeFactory.Target.PointerSize; private void setEHcount(uint cEH) { diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs index e5f5ec7..e977f30 100644 --- a/src/coreclr/src/tools/crossgen2/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs @@ -1366,6 +1366,14 @@ namespace ILCompiler.Reflection.ReadyToRun builder.Append("GCPOLL"); break; + case ReadyToRunHelper.ReversePInvokeEnter: + builder.Append("REVERSE_PINVOKE_ENTER"); + break; + + case ReadyToRunHelper.ReversePInvokeExit: + builder.Append("REVERSE_PINVOKE_EXIT"); + break; + // Get string handle lazily case ReadyToRunHelper.GetString: builder.Append("GET_STRING"); diff --git a/src/coreclr/src/vm/cgensys.h b/src/coreclr/src/vm/cgensys.h index 540ed6c..3c8c928 100644 --- a/src/coreclr/src/vm/cgensys.h +++ b/src/coreclr/src/vm/cgensys.h @@ -53,6 +53,14 @@ extern "C" void GenericComPlusCallStub(void); extern "C" void GenericComCallStub(void); #endif // FEATURE_COMINTEROP +// The GC mode for the thread that initially called ThePreStub(). +enum class CallerGCMode +{ + Unknown, + Coop, + Preemptive // (e.g. NativeCallableAttribute) +}; + // Non-CPU-specific helper functions called by the CPU-dependent code extern "C" PCODE STDCALL PreStubWorker(TransitionBlock * pTransitionBlock, MethodDesc * pMD); diff --git a/src/coreclr/src/vm/codeversion.cpp b/src/coreclr/src/vm/codeversion.cpp index 9ff6416..4d56c1d 100644 --- a/src/coreclr/src/vm/codeversion.cpp +++ b/src/coreclr/src/vm/codeversion.cpp @@ -1611,6 +1611,7 @@ HRESULT CodeVersionManager::AddNativeCodeVersion( PCODE CodeVersionManager::PublishVersionableCodeIfNecessary( MethodDesc* pMethodDesc, + CallerGCMode callerGCMode, bool *doBackpatchRef, bool *doFullBackpatchRef) { @@ -1655,7 +1656,7 @@ PCODE CodeVersionManager::PublishVersionableCodeIfNecessary( _ASSERTE(hr == E_OUTOFMEMORY); ReportCodePublishError(pMethodDesc, hr); *doBackpatchRef = false; - return pCode != NULL ? pCode : pMethodDesc->PrepareInitialCode(); + return pCode != NULL ? pCode : pMethodDesc->PrepareInitialCode(callerGCMode); } while (false); while (true) @@ -1670,6 +1671,9 @@ PCODE CodeVersionManager::PublishVersionableCodeIfNecessary( { PrepareCodeConfigBuffer configBuffer(activeVersion); PrepareCodeConfig *config = configBuffer.GetConfig(); + + // Record the caller's GC mode. + config->SetCallerGCMode(callerGCMode); pCode = pMethodDesc->PrepareCode(config); #ifdef FEATURE_CODE_VERSIONING diff --git a/src/coreclr/src/vm/codeversion.h b/src/coreclr/src/vm/codeversion.h index 43d2093..1cb3f2a 100644 --- a/src/coreclr/src/vm/codeversion.h +++ b/src/coreclr/src/vm/codeversion.h @@ -10,7 +10,6 @@ #ifndef CODE_VERSION_H #define CODE_VERSION_H -class NativeCodeVersion; class ILCodeVersion; typedef DWORD NativeCodeVersionId; @@ -572,7 +571,11 @@ public: HRESULT AddILCodeVersion(Module* pModule, mdMethodDef methodDef, ReJITID rejitId, ILCodeVersion* pILCodeVersion); HRESULT AddNativeCodeVersion(ILCodeVersion ilCodeVersion, MethodDesc* pClosedMethodDesc, NativeCodeVersion::OptimizationTier optimizationTier, NativeCodeVersion* pNativeCodeVersion); - PCODE PublishVersionableCodeIfNecessary(MethodDesc* pMethodDesc, bool *doBackpatchRef, bool *doFullBackpatchRef); + PCODE PublishVersionableCodeIfNecessary( + MethodDesc* pMethodDesc, + CallerGCMode callerGCMode, + bool *doBackpatchRef, + bool *doFullBackpatchRef); HRESULT PublishNativeCodeVersion(MethodDesc* pMethodDesc, NativeCodeVersion nativeCodeVersion); HRESULT GetOrCreateMethodDescVersioningState(MethodDesc* pMethod, MethodDescVersioningState** ppMethodDescVersioningState); HRESULT GetOrCreateILCodeVersioningState(Module* pModule, mdMethodDef methodDef, ILCodeVersioningState** ppILCodeVersioningState); diff --git a/src/coreclr/src/vm/comdelegate.cpp b/src/coreclr/src/vm/comdelegate.cpp index a2f0fd7..ac3c4d0 100644 --- a/src/coreclr/src/vm/comdelegate.cpp +++ b/src/coreclr/src/vm/comdelegate.cpp @@ -1132,6 +1132,7 @@ void COMDelegate::BindToMethod(DELEGATEREF *pRefThis, GCPROTECT_END(); } +#if defined(TARGET_X86) && defined(TARGET_WINDOWS) // Marshals a managed method to an unmanaged callback provided the // managed method is static and it's parameters require no marshalling. PCODE COMDelegate::ConvertToCallback(MethodDesc* pMD) @@ -1139,29 +1140,18 @@ PCODE COMDelegate::ConvertToCallback(MethodDesc* pMD) CONTRACTL { THROWS; - GC_TRIGGERS; - INJECT_FAULT(COMPlusThrowOM()); + GC_TRIGGERS; + PRECONDITION(pMD != NULL); + INJECT_FAULT(COMPlusThrowOM()); } CONTRACTL_END; PCODE pCode = NULL; - // only static methods are allowed - if (!pMD->IsStatic()) - COMPlusThrow(kNotSupportedException, W("NotSupported_NonStaticMethod")); - - // no generic methods - if (pMD->IsGenericMethodDefinition()) - COMPlusThrow(kNotSupportedException, W("NotSupported_GenericMethod")); - - // Arguments - if (NDirect::MarshalingRequired(pMD, pMD->GetSig(), pMD->GetModule())) - COMPlusThrow(kNotSupportedException, W("NotSupported_NonBlittableTypes")); - // Get UMEntryThunk from the thunk cache. UMEntryThunk *pUMEntryThunk = pMD->GetLoaderAllocator()->GetUMEntryThunkCache()->GetUMEntryThunk(pMD); -#if defined(TARGET_X86) && !defined(FEATURE_STUBS_AS_IL) +#if !defined(FEATURE_STUBS_AS_IL) // System.Runtime.InteropServices.NativeCallableAttribute BYTE* pData = NULL; @@ -1193,12 +1183,13 @@ PCODE COMDelegate::ConvertToCallback(MethodDesc* pMD) pUMThunkMarshalInfo->SetCallingConvention(callConv); } } -#endif //TARGET_X86 && !FEATURE_STUBS_AS_IL +#endif // !FEATURE_STUBS_AS_IL pCode = (PCODE)pUMEntryThunk->GetCode(); _ASSERTE(pCode != NULL); return pCode; } +#endif // defined(TARGET_X86) && defined(TARGET_WINDOWS) // Marshals a delegate to a unmanaged callback. LPVOID COMDelegate::ConvertToCallback(OBJECTREF pDelegateObj) diff --git a/src/coreclr/src/vm/comdelegate.h b/src/coreclr/src/vm/comdelegate.h index cb22cc5..a87845d 100644 --- a/src/coreclr/src/vm/comdelegate.h +++ b/src/coreclr/src/vm/comdelegate.h @@ -84,9 +84,11 @@ public: // Marshals a delegate to a unmanaged callback. static LPVOID ConvertToCallback(OBJECTREF pDelegate); - // Marshals a managed method to an unmanaged callback , provided the method is static and uses only - // blittable parameter types. +#if defined(TARGET_X86) && defined(TARGET_WINDOWS) + // Marshals a managed method to an unmanaged callback. + // This is only used on x86 Windows. See usage for further details. static PCODE ConvertToCallback(MethodDesc* pMD); +#endif // defined(TARGET_X86) && defined(TARGET_WINDOWS) // Marshals an unmanaged callback to Delegate static OBJECTREF ConvertToDelegate(LPVOID pCallback, MethodTable* pMT); diff --git a/src/coreclr/src/vm/exceptionhandling.cpp b/src/coreclr/src/vm/exceptionhandling.cpp index e473814..655a102 100644 --- a/src/coreclr/src/vm/exceptionhandling.cpp +++ b/src/coreclr/src/vm/exceptionhandling.cpp @@ -4655,7 +4655,6 @@ VOID DECLSPEC_NORETURN UnwindManagedExceptionPass1(PAL_SEHException& ex, CONTEXT } else { - // TODO: This needs to implemented. Make it fail for now. UNREACHABLE(); } } @@ -4664,6 +4663,20 @@ VOID DECLSPEC_NORETURN UnwindManagedExceptionPass1(PAL_SEHException& ex, CONTEXT controlPc = Thread::VirtualUnwindLeafCallFrame(frameContext); } + GcInfoDecoder gcInfoDecoder(codeInfo.GetGCInfoToken(), DECODE_REVERSE_PINVOKE_VAR); + + if (gcInfoDecoder.GetReversePInvokeFrameStackSlot() != NO_REVERSE_PINVOKE_FRAME) + { + // Propagating exception from a method marked by NativeCallable attribute is prohibited on Unix + if (!GetThread()->HasThreadStateNC(Thread::TSNC_ProcessedUnhandledException)) + { + LONG disposition = InternalUnhandledExceptionFilter_Worker(&ex.ExceptionPointers); + _ASSERTE(disposition == EXCEPTION_CONTINUE_SEARCH); + } + TerminateProcess(GetCurrentProcess(), 1); + UNREACHABLE(); + } + // Check whether we are crossing managed-to-native boundary while (!ExecutionManager::IsManagedCode(controlPc)) { diff --git a/src/coreclr/src/vm/frames.h b/src/coreclr/src/vm/frames.h index 0709cf0..1976d73 100644 --- a/src/coreclr/src/vm/frames.h +++ b/src/coreclr/src/vm/frames.h @@ -2785,6 +2785,12 @@ protected: }; #endif // TARGET_X86 && !TARGET_UNIX +// Frame for the Reverse PInvoke (i.e. NativeCallableAttribute). +struct ReversePInvokeFrame +{ + Thread* currentThread; +}; + #if defined(TARGET_X86) && defined(FEATURE_COMINTEROP) //------------------------------------------------------------------------- // Exception handler for COM to managed frame diff --git a/src/coreclr/src/vm/jithelpers.cpp b/src/coreclr/src/vm/jithelpers.cpp index bb59a80..6fcb1c8 100644 --- a/src/coreclr/src/vm/jithelpers.cpp +++ b/src/coreclr/src/vm/jithelpers.cpp @@ -5041,6 +5041,59 @@ Thread * __stdcall JIT_InitPInvokeFrame(InlinedCallFrame *pFrame, PTR_VOID StubS EXTERN_C void JIT_PInvokeBegin(InlinedCallFrame* pFrame); EXTERN_C void JIT_PInvokeEnd(InlinedCallFrame* pFrame); +// Forward declaration +EXTERN_C void STDCALL ReversePInvokeBadTransition(); + +// This is a slower version of the reverse PInvoke enter function. +NOINLINE static void JIT_ReversePInvokeEnterRare(ReversePInvokeFrame* frame) +{ + _ASSERTE(frame != NULL); + + Thread* thread = GetThreadNULLOk(); + if (thread == NULL) + CREATETHREAD_IF_NULL_FAILFAST(thread, W("Failed to setup new thread during reverse P/Invoke")); + + // Verify the current thread isn't in COOP mode. + if (thread->PreemptiveGCDisabled()) + ReversePInvokeBadTransition(); + + thread->DisablePreemptiveGC(); + frame->currentThread = thread; +} + +EXTERN_C void JIT_ReversePInvokeEnter(ReversePInvokeFrame* frame) +{ + _ASSERTE(frame != NULL); + Thread* thread = GetThreadNULLOk(); + + // If a thread instance exists and is in the + // correct GC mode attempt a quick transition. + if (thread != NULL + && !thread->PreemptiveGCDisabled()) + { + // Manually inline the fast path in Thread::DisablePreemptiveGC(). + thread->m_fPreemptiveGCDisabled.StoreWithoutBarrier(1); + if (g_TrapReturningThreads.LoadWithoutBarrier() == 0) + { + frame->currentThread = thread; + return; + } + } + + JIT_ReversePInvokeEnterRare(frame); +} + +EXTERN_C void JIT_ReversePInvokeExit(ReversePInvokeFrame* frame) +{ + _ASSERTE(frame != NULL); + _ASSERTE(frame->currentThread == GetThread()); + + // Manually inline the fast path in Thread::EnablePreemptiveGC(). + // This is a trade off with GC suspend performance. We are opting + // to make this exit faster. + frame->currentThread->m_fPreemptiveGCDisabled.StoreWithoutBarrier(0); +} + //======================================================================== // // JIT HELPERS INITIALIZATION diff --git a/src/coreclr/src/vm/jitinterface.cpp b/src/coreclr/src/vm/jitinterface.cpp index 2d55a9f..2c7b37d 100644 --- a/src/coreclr/src/vm/jitinterface.cpp +++ b/src/coreclr/src/vm/jitinterface.cpp @@ -5085,6 +5085,22 @@ void CEEInfo::getCallInfo( EX_THROW(EEMessageException, (kMissingMethodException, IDS_EE_MISSING_METHOD, W("?"))); } + // If this call is for a LDFTN and the target method has the NativeCallableAttribute, + // then validate it adheres to the limitations. + if ((flags & CORINFO_CALLINFO_LDFTN) && pMD->HasNativeCallableAttribute()) + { + if (!pMD->IsStatic()) + EX_THROW(EEResourceException, (kInvalidProgramException, W("InvalidProgram_NonStaticMethod"))); + + // No generic methods + if (pMD->HasClassOrMethodInstantiation()) + EX_THROW(EEResourceException, (kInvalidProgramException, W("InvalidProgram_GenericMethod"))); + + // Arguments + if (NDirect::MarshalingRequired(pMD, pMD->GetSig(), pMD->GetModule())) + EX_THROW(EEResourceException, (kInvalidProgramException, W("InvalidProgram_NonBlittableTypes"))); + } + TypeHandle exactType = TypeHandle(pResolvedToken->hClass); TypeHandle constrainedType; @@ -9192,18 +9208,25 @@ void CEEInfo::getFunctionFixedEntryPoint(CORINFO_METHOD_HANDLE ftn, pResult->accessType = IAT_VALUE; - -#ifndef CROSSGEN_COMPILE - // If LDFTN target has [NativeCallable] attribute , then create a UMEntryThunk. +// Also see GetBaseCompileFlags() below for an additional check. +#if defined(TARGET_X86) && defined(TARGET_WINDOWS) && !defined(CROSSGEN_COMPILE) + // Deferring X86 support until a need is observed or + // time permits investigation into all the potential issues. if (pMD->HasNativeCallableAttribute()) { pResult->addr = (void*)COMDelegate::ConvertToCallback(pMD); } else -#endif //CROSSGEN_COMPILE { - pResult->addr = (void *)pMD->GetMultiCallableAddrOfCode(); + pResult->addr = (void*)pMD->GetMultiCallableAddrOfCode(); } + +#else + + pResult->addr = (void*)pMD->GetMultiCallableAddrOfCode(); + +#endif // !(TARGET_X86 && TARGET_WINDOWS) || CROSSGEN_COMPILE + EE_TO_JIT_TRANSITION(); } @@ -10081,7 +10104,8 @@ void CEEInfo::getEEInfo(CORINFO_EE_INFO *pEEInfoOut) // Wrapper delegate offsets pEEInfoOut->offsetOfWrapperDelegateIndirectCell = OFFSETOF__DelegateObject__methodPtrAux; - pEEInfoOut->sizeOfReversePInvokeFrame = (DWORD)-1; + pEEInfoOut->sizeOfReversePInvokeFrame = TARGET_POINTER_SIZE * READYTORUN_ReversePInvokeTransitionFrameSizeInPointerUnits; + _ASSERTE(sizeof(ReversePInvokeFrame) <= pEEInfoOut->sizeOfReversePInvokeFrame); pEEInfoOut->osPageSize = GetOsPageSize(); pEEInfoOut->maxUncheckedOffsetForNullObject = MAX_UNCHECKED_OFFSET_FOR_NULL_OBJECT; @@ -12362,6 +12386,11 @@ CorJitResult CallCompileMethodWithSEHWrapper(EEJitManager *jitMgr, } } +#if !defined(TARGET_X86) || !defined(TARGET_WINDOWS) + if (ftn->HasNativeCallableAttribute()) + flags.Set(CORJIT_FLAGS::CORJIT_FLAG_REVERSE_PINVOKE); +#endif // !TARGET_X86 || !TARGET_WINDOWS + return flags; } @@ -12521,6 +12550,7 @@ BOOL g_fAllowRel32 = TRUE; #endif +#ifndef CROSSGEN_COMPILE // ******************************************************************** // README!! // ******************************************************************** @@ -12534,12 +12564,14 @@ BOOL g_fAllowRel32 = TRUE; // // Calls to this method that occur to check if inlining can occur on x86, // are OK since they discard the return value of this method. - -PCODE UnsafeJitFunction(NativeCodeVersion nativeCodeVersion, COR_ILMETHOD_DECODER* ILHeader, CORJIT_FLAGS flags, +PCODE UnsafeJitFunction(PrepareCodeConfig* config, + COR_ILMETHOD_DECODER* ILHeader, + CORJIT_FLAGS flags, ULONG * pSizeOfCode) { STANDARD_VM_CONTRACT; + NativeCodeVersion nativeCodeVersion = config->GetCodeVersion(); MethodDesc* ftn = nativeCodeVersion.GetMethodDesc(); PCODE ret = NULL; @@ -12571,7 +12603,6 @@ PCODE UnsafeJitFunction(NativeCodeVersion nativeCodeVersion, COR_ILMETHOD_DECODE #endif // FEATURE_PREJIT -#ifndef CROSSGEN_COMPILE EEJitManager *jitMgr = ExecutionManager::GetEEJitManager(); if (!jitMgr->LoadJIT()) { @@ -12591,7 +12622,6 @@ PCODE UnsafeJitFunction(NativeCodeVersion nativeCodeVersion, COR_ILMETHOD_DECODE EEPOLICY_HANDLE_FATAL_ERROR_WITH_MESSAGE(COR_E_EXECUTIONENGINE, W("Failed to load JIT compiler")); #endif // ALLOW_SXS_JIT } -#endif // CROSSGEN_COMPILE #ifdef _DEBUG // This is here so we can see the name and class easily in the debugger @@ -12648,6 +12678,21 @@ PCODE UnsafeJitFunction(NativeCodeVersion nativeCodeVersion, COR_ILMETHOD_DECODE flags = GetCompileFlags(ftn, flags, &methodInfo); + // If the reverse P/Invoke flag is used, we aren't going to support + // any tiered compilation. + if (flags.IsSet(CORJIT_FLAGS::CORJIT_FLAG_REVERSE_PINVOKE)) + { + _ASSERTE(config->GetCallerGCMode() != CallerGCMode::Coop); + + // Clear all possible states. + flags.Clear(CORJIT_FLAGS::CORJIT_FLAG_TIER0); + flags.Clear(CORJIT_FLAGS::CORJIT_FLAG_TIER1); + +#ifdef FEATURE_TIERED_COMPILATION + config->SetJitSwitchedToOptimized(); +#endif // FEATURE_TIERED_COMPILATION + } + #ifdef _DEBUG if (!flags.IsSet(CORJIT_FLAGS::CORJIT_FLAG_SKIP_VERIFICATION)) { @@ -12678,17 +12723,10 @@ PCODE UnsafeJitFunction(NativeCodeVersion nativeCodeVersion, COR_ILMETHOD_DECODE for (;;) { -#ifndef CROSSGEN_COMPILE CEEJitInfo jitInfo(ftn, ILHeader, jitMgr, flags.IsSet(CORJIT_FLAGS::CORJIT_FLAG_IMPORT_ONLY), !flags.IsSet(CORJIT_FLAGS::CORJIT_FLAG_NO_INLINING)); -#else - // This path should be only ever used for verification in crossgen and so we should not need EEJitManager - _ASSERTE(flags.IsSet(CORJIT_FLAGS::CORJIT_FLAG_IMPORT_ONLY)); - CEEInfo jitInfo(ftn, true); - EEJitManager *jitMgr = NULL; -#endif -#if (defined(TARGET_AMD64) || defined(TARGET_ARM64)) && !defined(CROSSGEN_COMPILE) +#if (defined(TARGET_AMD64) || defined(TARGET_ARM64)) #ifdef TARGET_AMD64 if (fForceJumpStubOverflow) jitInfo.SetJumpStubOverflow(fAllowRel32); @@ -12804,10 +12842,7 @@ PCODE UnsafeJitFunction(NativeCodeVersion nativeCodeVersion, COR_ILMETHOD_DECODE if (!SUCCEEDED(res)) { -#ifndef CROSSGEN_COMPILE jitInfo.BackoutJitData(jitMgr); -#endif - ThrowExceptionForJit(res); } @@ -12820,7 +12855,7 @@ PCODE UnsafeJitFunction(NativeCodeVersion nativeCodeVersion, COR_ILMETHOD_DECODE if (!nativeEntry) COMPlusThrow(kInvalidProgramException); -#if (defined(TARGET_AMD64) || defined(TARGET_ARM64)) && !defined(CROSSGEN_COMPILE) +#if (defined(TARGET_AMD64) || defined(TARGET_ARM64)) if (jitInfo.IsJumpStubOverflow()) { // Backout and try again with fAllowRel32 == FALSE. @@ -12839,7 +12874,7 @@ PCODE UnsafeJitFunction(NativeCodeVersion nativeCodeVersion, COR_ILMETHOD_DECODE reserveForJumpStubs = jitInfo.GetReserveForJumpStubs(); continue; } -#endif // (TARGET_AMD64 || TARGET_ARM64) && !CROSSGEN_COMPILE +#endif // (TARGET_AMD64 || TARGET_ARM64) LOG((LF_JIT, LL_INFO10000, "Jitted Entry at" FMT_ADDR "method %s::%s %s\n", DBG_ADDR(nativeEntry), @@ -12887,6 +12922,7 @@ PCODE UnsafeJitFunction(NativeCodeVersion nativeCodeVersion, COR_ILMETHOD_DECODE COOPERATIVE_TRANSITION_END(); return ret; } +#endif // CROSSGEN_COMPILE extern "C" unsigned __stdcall PartialNGenStressPercentage() { @@ -13293,9 +13329,6 @@ BOOL LoadDynamicInfoEntry(Module *currentModule, } break; - // ENCODE_METHOD_NATIVECALLABLE_HANDLE is same as ENCODE_METHOD_ENTRY_DEF_TOKEN - // except for AddrOfCode - case ENCODE_METHOD_NATIVE_ENTRY: case ENCODE_METHOD_ENTRY_DEF_TOKEN: { mdToken MethodDef = TokenFromRid(CorSigUncompressData(pBlob), mdtMethodDef); @@ -13351,14 +13384,7 @@ BOOL LoadDynamicInfoEntry(Module *currentModule, } MethodEntry: - if (kind == ENCODE_METHOD_NATIVE_ENTRY) - { - result = COMDelegate::ConvertToCallback(pMD); - } - else - { - result = pMD->GetMultiCallableAddrOfCode(CORINFO_ACCESS_ANY); - } + result = pMD->GetMultiCallableAddrOfCode(CORINFO_ACCESS_ANY); #ifndef TARGET_ARM if (CORCOMPILE_IS_PCODE_TAGGED(result)) diff --git a/src/coreclr/src/vm/jitinterface.h b/src/coreclr/src/vm/jitinterface.h index 79be98c..62faa9e 100644 --- a/src/coreclr/src/vm/jitinterface.h +++ b/src/coreclr/src/vm/jitinterface.h @@ -70,8 +70,12 @@ bool SigInfoFlagsAreValid (CORINFO_SIG_INFO *sig) void InitJITHelpers1(); void InitJITHelpers2(); -PCODE UnsafeJitFunction(NativeCodeVersion nativeCodeVersion, COR_ILMETHOD_DECODER* header, - CORJIT_FLAGS flags, ULONG* sizeOfCode = NULL); +#ifndef CROSSGEN_COMPILE +PCODE UnsafeJitFunction(PrepareCodeConfig* config, + COR_ILMETHOD_DECODER* header, + CORJIT_FLAGS flags, + ULONG* sizeOfCode = NULL); +#endif // CROSSGEN_COMPILE void getMethodInfoHelper(MethodDesc * ftn, CORINFO_METHOD_HANDLE ftnHnd, diff --git a/src/coreclr/src/vm/method.hpp b/src/coreclr/src/vm/method.hpp index 64936b4..7efecae 100644 --- a/src/coreclr/src/vm/method.hpp +++ b/src/coreclr/src/vm/method.hpp @@ -16,7 +16,6 @@ #include "cor.h" #include "util.hpp" #include "clsload.hpp" -#include "codeman.h" #include "class.h" #include "siginfo.hpp" #include "methodimpl.h" @@ -24,7 +23,6 @@ #include #include "eeconfig.h" #include "precode.h" -#include "codeversion.h" #ifndef FEATURE_PREJIT #include "fixuppointer.h" @@ -1788,7 +1786,7 @@ public: // PCODE DoBackpatch(MethodTable * pMT, MethodTable * pDispatchingMT, BOOL fFullBackPatch); - PCODE DoPrestub(MethodTable *pDispatchingMT); + PCODE DoPrestub(MethodTable *pDispatchingMT, CallerGCMode callerGCMode = CallerGCMode::Unknown); VOID GetMethodInfo(SString &namespaceOrClassName, SString &methodName, SString &methodSignature); VOID GetMethodInfoWithNewSig(SString &namespaceOrClassName, SString &methodName, SString &methodSignature); @@ -2000,7 +1998,7 @@ public: #ifndef DACCESS_COMPILE public: - PCODE PrepareInitialCode(); + PCODE PrepareInitialCode(CallerGCMode callerGCMode = CallerGCMode::Unknown); PCODE PrepareCode(PrepareCodeConfig* pConfig); private: @@ -2044,6 +2042,8 @@ public: BOOL ReadyToRunRejectedPrecompiledCode(); void SetProfilerRejectedPrecompiledCode(); void SetReadyToRunRejectedPrecompiledCode(); + CallerGCMode GetCallerGCMode(); + void SetCallerGCMode(CallerGCMode mode); #ifdef FEATURE_CODE_VERSIONING public: @@ -2165,6 +2165,7 @@ protected: BOOL m_mayUsePrecompiledCode; BOOL m_ProfilerRejectedPrecompiledCode; BOOL m_ReadyToRunRejectedPrecompiledCode; + CallerGCMode m_callerGCMode; #ifdef FEATURE_CODE_VERSIONING private: diff --git a/src/coreclr/src/vm/prestub.cpp b/src/coreclr/src/vm/prestub.cpp index 87e0763..6d7eb7e 100644 --- a/src/coreclr/src/vm/prestub.cpp +++ b/src/coreclr/src/vm/prestub.cpp @@ -314,10 +314,11 @@ void DACNotifyCompilationFinished(MethodDesc *methodDesc, PCODE pCode) #endif // -PCODE MethodDesc::PrepareInitialCode() +PCODE MethodDesc::PrepareInitialCode(CallerGCMode callerGCMode) { STANDARD_VM_CONTRACT; PrepareCodeConfig config(NativeCodeVersion(this), TRUE, TRUE); + config.SetCallerGCMode(callerGCMode); PCODE pCode = PrepareCode(&config); #if defined(FEATURE_GDBJIT) && defined(TARGET_UNIX) && !defined(CROSSGEN_COMPILE) @@ -436,18 +437,42 @@ PCODE MethodDesc::GetPrecompiledCode(PrepareCodeConfig* pConfig) { LOG_USING_R2R_CODE(this); +#ifdef FEATURE_TIERED_COMPILATION + bool shouldTier = pConfig->GetMethodDesc()->IsEligibleForTieredCompilation(); +#if !defined(TARGET_X86) || !defined(TARGET_WINDOWS) + CallerGCMode callerGcMode = pConfig->GetCallerGCMode(); + // If the method is eligible for tiering but is being + // called from a Preemptive GC Mode thread or the method + // has the NativeCallableAttribute then the Tiered Compilation + // should be disabled. + if (shouldTier + && (callerGcMode == CallerGCMode::Preemptive + || (callerGcMode == CallerGCMode::Unknown + && HasNativeCallableAttribute()))) + { + NativeCodeVersion codeVersion = pConfig->GetCodeVersion(); + if (codeVersion.IsDefaultVersion()) + { + pConfig->GetMethodDesc()->GetLoaderAllocator()->GetCallCountingManager()->DisableCallCounting(codeVersion); + } + codeVersion.SetOptimizationTier(NativeCodeVersion::OptimizationTierOptimized); + shouldTier = false; + } +#endif // !TARGET_X86 || !TARGET_WINDOWS +#endif // FEATURE_TIERED_COMPILATION + if (pConfig->SetNativeCode(pCode, &pCode)) { - #ifdef FEATURE_CODE_VERSIONING +#ifdef FEATURE_CODE_VERSIONING pConfig->SetGeneratedOrLoadedNewCode(); - #endif - #ifdef FEATURE_TIERED_COMPILATION - if (pConfig->GetMethodDesc()->IsEligibleForTieredCompilation()) +#endif +#ifdef FEATURE_TIERED_COMPILATION + if (shouldTier) { _ASSERTE(pConfig->GetCodeVersion().GetOptimizationTier() == NativeCodeVersion::OptimizationTier0); pConfig->SetShouldCountCalls(); } - #endif +#endif } } } @@ -982,7 +1007,7 @@ PCODE MethodDesc::JitCompileCodeLocked(PrepareCodeConfig* pConfig, JitListLockEn Thread::CurrentPrepareCodeConfigHolder threadPrepareCodeConfigHolder(GetThread(), pConfig); #endif - pCode = UnsafeJitFunction(pConfig->GetCodeVersion(), pilHeader, *pFlags, pSizeOfCode); + pCode = UnsafeJitFunction(pConfig, pilHeader, *pFlags, pSizeOfCode); } EX_CATCH { @@ -1115,6 +1140,7 @@ PrepareCodeConfig::PrepareCodeConfig(NativeCodeVersion codeVersion, BOOL needsMu m_mayUsePrecompiledCode(mayUsePrecompiledCode), m_ProfilerRejectedPrecompiledCode(FALSE), m_ReadyToRunRejectedPrecompiledCode(FALSE), + m_callerGCMode(CallerGCMode::Unknown), #ifdef FEATURE_CODE_VERSIONING m_profilerMayHaveActivatedNonDefaultCodeVersion(false), m_generatedOrLoadedNewCode(false), @@ -1171,6 +1197,18 @@ void PrepareCodeConfig::SetReadyToRunRejectedPrecompiledCode() m_ReadyToRunRejectedPrecompiledCode = TRUE; } +CallerGCMode PrepareCodeConfig::GetCallerGCMode() +{ + LIMITED_METHOD_CONTRACT; + return m_callerGCMode; +} + +void PrepareCodeConfig::SetCallerGCMode(CallerGCMode mode) +{ + LIMITED_METHOD_CONTRACT; + m_callerGCMode = mode; +} + NativeCodeVersion PrepareCodeConfig::GetCodeVersion() { LIMITED_METHOD_CONTRACT; @@ -1760,12 +1798,67 @@ extern "C" MethodDesc * STDCALL PreStubGetMethodDescForCompactEntryPoint (PCODE #endif // defined (HAS_COMPACT_ENTRYPOINTS) && defined (TARGET_ARM) //============================================================================= +// This function generates the real code when from Preemptive mode. +// It is specifically designed to work with the NativeCallableAttribute. +//============================================================================= +static PCODE PreStubWorker_Preemptive( + _In_ TransitionBlock* pTransitionBlock, + _In_ MethodDesc* pMD, + _In_opt_ Thread* currentThread) +{ + _ASSERTE(pMD->HasNativeCallableAttribute()); + + PCODE pbRetVal = NULL; + + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_MODE_PREEMPTIVE; + + // Starting from preemptive mode means the possibility exists + // that the thread is new to the runtime so we might have to + // create one. + if (currentThread == NULL) + { + // If our attempt to create a thread fails, there is nothing + // more we can do except fail fast. The reverse P/Invoke isn't + // going to work. + CREATETHREAD_IF_NULL_FAILFAST(currentThread, W("Failed to setup new thread during reverse P/Invoke")); + } + + MAKE_CURRENT_THREAD_AVAILABLE_EX(currentThread); + + // No GC frame is needed here since there should be no OBJECTREFs involved + // in this call due to NativeCallableAttribute semantics. + + INSTALL_MANAGED_EXCEPTION_DISPATCHER; + INSTALL_UNWIND_AND_CONTINUE_HANDLER; + + // Make sure the method table is restored, and method instantiation if present + pMD->CheckRestore(); + CONSISTENCY_CHECK(GetAppDomain()->CheckCanExecuteManagedCode(pMD)); + + pbRetVal = pMD->DoPrestub(NULL, CallerGCMode::Preemptive); + + UNINSTALL_UNWIND_AND_CONTINUE_HANDLER; + UNINSTALL_MANAGED_EXCEPTION_DISPATCHER; + + { + HardwareExceptionHolder; + + // Give debugger opportunity to stop here + ThePreStubPatch(); + } + + return pbRetVal; +} + +//============================================================================= // This function generates the real code for a method and installs it into // the methoddesc. Usually ***BUT NOT ALWAYS***, this function runs only once // per methoddesc. In addition to installing the new code, this function // returns a pointer to the new code for the prestub's convenience. //============================================================================= -extern "C" PCODE STDCALL PreStubWorker(TransitionBlock * pTransitionBlock, MethodDesc * pMD) +extern "C" PCODE STDCALL PreStubWorker(TransitionBlock* pTransitionBlock, MethodDesc* pMD) { PCODE pbRetVal = NULL; @@ -1773,99 +1866,98 @@ extern "C" PCODE STDCALL PreStubWorker(TransitionBlock * pTransitionBlock, Metho STATIC_CONTRACT_THROWS; STATIC_CONTRACT_GC_TRIGGERS; - STATIC_CONTRACT_MODE_COOPERATIVE; + STATIC_CONTRACT_MODE_ANY; STATIC_CONTRACT_ENTRY_POINT; - MAKE_CURRENT_THREAD_AVAILABLE(); - -#ifdef _DEBUG - Thread::ObjectRefFlush(CURRENT_THREAD); -#endif - - FrameWithCookie frame(pTransitionBlock, pMD); - PrestubMethodFrame * pPFrame = &frame; - - pPFrame->Push(CURRENT_THREAD); - - INSTALL_MANAGED_EXCEPTION_DISPATCHER; - INSTALL_UNWIND_AND_CONTINUE_HANDLER; + _ASSERTE(!NingenEnabled() && "You cannot invoke managed code inside the ngen compilation process."); - ETWOnStartup (PrestubWorker_V1,PrestubWorkerEnd_V1); + ETWOnStartup(PrestubWorker_V1, PrestubWorkerEnd_V1); - _ASSERTE(!NingenEnabled() && "You cannot invoke managed code inside the ngen compilation process."); + MAKE_CURRENT_THREAD_AVAILABLE(); - // Running the PreStubWorker on a method causes us to access its MethodTable - g_IBCLogger.LogMethodDescAccess(pMD); + // Attempt to check what GC mode we are running under. + if (CURRENT_THREAD == NULL + || !CURRENT_THREAD->PreemptiveGCDisabled()) + { + pbRetVal = PreStubWorker_Preemptive(pTransitionBlock, pMD, CURRENT_THREAD); + } + else + { + // This is the typical case (i.e. COOP mode). - // Make sure the method table is restored, and method instantiation if present - pMD->CheckRestore(); +#ifdef _DEBUG + Thread::ObjectRefFlush(CURRENT_THREAD); +#endif - CONSISTENCY_CHECK(GetAppDomain()->CheckCanExecuteManagedCode(pMD)); + FrameWithCookie frame(pTransitionBlock, pMD); + PrestubMethodFrame* pPFrame = &frame; - // Note this is redundant with the above check but we do it anyway for safety - // - // This has been disabled so we have a better chance of catching these. Note that this check is - // NOT sufficient for domain neutral and ngen cases. - // - // pMD->EnsureActive(); + pPFrame->Push(CURRENT_THREAD); - MethodTable *pDispatchingMT = NULL; + INSTALL_MANAGED_EXCEPTION_DISPATCHER; + INSTALL_UNWIND_AND_CONTINUE_HANDLER; - if (pMD->IsVtableMethod()) - { - OBJECTREF curobj = pPFrame->GetThis(); + // Make sure the method table is restored, and method instantiation if present + pMD->CheckRestore(); + CONSISTENCY_CHECK(GetAppDomain()->CheckCanExecuteManagedCode(pMD)); - if (curobj != NULL) // Check for virtual function called non-virtually on a NULL object + MethodTable* pDispatchingMT = NULL; + if (pMD->IsVtableMethod()) { - pDispatchingMT = curobj->GetMethodTable(); + OBJECTREF curobj = pPFrame->GetThis(); -#ifdef FEATURE_ICASTABLE - if (pDispatchingMT->IsICastable()) + if (curobj != NULL) // Check for virtual function called non-virtually on a NULL object { - MethodTable *pMDMT = pMD->GetMethodTable(); - TypeHandle objectType(pDispatchingMT); - TypeHandle methodType(pMDMT); + pDispatchingMT = curobj->GetMethodTable(); - GCStress::MaybeTrigger(); - INDEBUG(curobj = NULL); // curobj is unprotected and CanCastTo() can trigger GC - if (!objectType.CanCastTo(methodType)) +#ifdef FEATURE_ICASTABLE + if (pDispatchingMT->IsICastable()) { - // Apparently ICastable magic was involved when we chose this method to be called - // that's why we better stick to the MethodTable it belongs to, otherwise - // DoPrestub() will fail not being able to find implementation for pMD in pDispatchingMT. + MethodTable* pMDMT = pMD->GetMethodTable(); + TypeHandle objectType(pDispatchingMT); + TypeHandle methodType(pMDMT); + + GCStress::MaybeTrigger(); + INDEBUG(curobj = NULL); // curobj is unprotected and CanCastTo() can trigger GC + if (!objectType.CanCastTo(methodType)) + { + // Apparently ICastable magic was involved when we chose this method to be called + // that's why we better stick to the MethodTable it belongs to, otherwise + // DoPrestub() will fail not being able to find implementation for pMD in pDispatchingMT. - pDispatchingMT = pMDMT; + pDispatchingMT = pMDMT; + } } - } #endif // FEATURE_ICASTABLE - // For value types, the only virtual methods are interface implementations. - // Thus pDispatching == pMT because there - // is no inheritance in value types. Note the BoxedEntryPointStubs are shared - // between all sharable generic instantiations, so the == test is on - // canonical method tables. + // For value types, the only virtual methods are interface implementations. + // Thus pDispatching == pMT because there + // is no inheritance in value types. Note the BoxedEntryPointStubs are shared + // between all sharable generic instantiations, so the == test is on + // canonical method tables. #ifdef _DEBUG - MethodTable *pMDMT = pMD->GetMethodTable(); // put this here to see what the MT is in debug mode - _ASSERTE(!pMD->GetMethodTable()->IsValueType() || - (pMD->IsUnboxingStub() && (pDispatchingMT->GetCanonicalMethodTable() == pMDMT->GetCanonicalMethodTable()))); + MethodTable* pMDMT = pMD->GetMethodTable(); // put this here to see what the MT is in debug mode + _ASSERTE(!pMD->GetMethodTable()->IsValueType() || + (pMD->IsUnboxingStub() && (pDispatchingMT->GetCanonicalMethodTable() == pMDMT->GetCanonicalMethodTable()))); #endif // _DEBUG + } } - } - GCX_PREEMP_THREAD_EXISTS(CURRENT_THREAD); - pbRetVal = pMD->DoPrestub(pDispatchingMT); + GCX_PREEMP_THREAD_EXISTS(CURRENT_THREAD); + pbRetVal = pMD->DoPrestub(pDispatchingMT, CallerGCMode::Coop); - UNINSTALL_UNWIND_AND_CONTINUE_HANDLER; - UNINSTALL_MANAGED_EXCEPTION_DISPATCHER; + UNINSTALL_UNWIND_AND_CONTINUE_HANDLER; + UNINSTALL_MANAGED_EXCEPTION_DISPATCHER; - { - HardwareExceptionHolder + { + HardwareExceptionHolder; - // Give debugger opportunity to stop here - ThePreStubPatch(); - } + // Give debugger opportunity to stop here + ThePreStubPatch(); + } - pPFrame->Pop(CURRENT_THREAD); + pPFrame->Pop(CURRENT_THREAD); + } POSTCONDITION(pbRetVal != NULL); @@ -1920,7 +2012,7 @@ static void TestSEHGuardPageRestore() // the case of methods that require stubs to be executed first (e.g., remoted methods // that require remoting stubs to be executed first), this stable entrypoint would be a // pointer to the stub, and not a pointer directly to the JITted code. -PCODE MethodDesc::DoPrestub(MethodTable *pDispatchingMT) +PCODE MethodDesc::DoPrestub(MethodTable *pDispatchingMT, CallerGCMode callerGCMode) { CONTRACT(PCODE) { @@ -2025,7 +2117,7 @@ PCODE MethodDesc::DoPrestub(MethodTable *pDispatchingMT) { bool doBackpatch = true; bool doFullBackpatch = false; - pCode = GetCodeVersionManager()->PublishVersionableCodeIfNecessary(this, &doBackpatch, &doFullBackpatch); + pCode = GetCodeVersionManager()->PublishVersionableCodeIfNecessary(this, callerGCMode, &doBackpatch, &doFullBackpatch); if (doBackpatch) { @@ -2066,7 +2158,7 @@ PCODE MethodDesc::DoPrestub(MethodTable *pDispatchingMT) { GetOrCreatePrecode(); } - pCode = PrepareInitialCode(); + pCode = PrepareInitialCode(callerGCMode); } // end else if (IsIL() || IsNoMetadata()) else if (IsNDirect()) { diff --git a/src/coreclr/src/vm/stacksampler.cpp b/src/coreclr/src/vm/stacksampler.cpp index 2cd3c1e..b4cec17 100644 --- a/src/coreclr/src/vm/stacksampler.cpp +++ b/src/coreclr/src/vm/stacksampler.cpp @@ -413,8 +413,9 @@ void StackSampler::JitAndCollectTrace(MethodDesc* pMD) LOG((LF_JIT, LL_INFO100000, "Jitting the hot method desc using SuperPMI in the background thread -> ")); LOG((LF_JIT, LL_INFO100000, "%s:%s\n", pMD->GetMethodTable()->GetClass()->GetDebugClassName(), pMD->GetName())); #endif - - PCODE pCode = UnsafeJitFunction(NativeCodeVersion(pMD), pDecoder, flags); + NativeCodeVersion natCodeVer(pMD); + PrepareCodeConfigBuffer cfgBuffer(natCodeVer); + PCODE pCode = UnsafeJitFunction(cfgBuffer.GetConfig(), pDecoder, flags); } // Update that this method has been already JITted. diff --git a/src/coreclr/src/vm/threads.h b/src/coreclr/src/vm/threads.h index 48929bd..a830887 100644 --- a/src/coreclr/src/vm/threads.h +++ b/src/coreclr/src/vm/threads.h @@ -603,6 +603,17 @@ DWORD GetRuntimeId(); EXTERN_C Thread* WINAPI CreateThreadBlockThrow(); +#define CREATETHREAD_IF_NULL_FAILFAST(__thread, __msg) \ +{ \ + HRESULT __ctinffhr; \ + __thread = SetupThreadNoThrow(&__ctinffhr); \ + if (__thread == NULL) \ + { \ + EEPOLICY_HANDLE_FATAL_ERROR_WITH_MESSAGE(__ctinffhr, __msg); \ + UNREACHABLE(); \ + } \ +} + //--------------------------------------------------------------------------- // One-time initialization. Called during Dll initialization. //--------------------------------------------------------------------------- diff --git a/src/coreclr/src/vm/tieredcompilation.cpp b/src/coreclr/src/vm/tieredcompilation.cpp index 06f0047..ffd986a 100644 --- a/src/coreclr/src/vm/tieredcompilation.cpp +++ b/src/coreclr/src/vm/tieredcompilation.cpp @@ -772,6 +772,11 @@ BOOL TieredCompilationManager::CompileCodeVersion(NativeCodeVersion nativeCodeVe { PrepareCodeConfigBuffer configBuffer(nativeCodeVersion); PrepareCodeConfig *config = configBuffer.GetConfig(); + + // This is a recompiling request which means the caller was + // in COOP mode since the code already ran. + _ASSERTE(!pMethod->HasNativeCallableAttribute()); + config->SetCallerGCMode(CallerGCMode::Coop); pCode = pMethod->PrepareCode(config); LOG((LF_TIEREDCOMPILATION, LL_INFO10000, "TieredCompilationManager::CompileCodeVersion Method=0x%pM (%s::%s), code version id=0x%x, code ptr=0x%p\n", pMethod, pMethod->m_pszDebugClassName, pMethod->m_pszDebugMethodName, diff --git a/src/coreclr/src/zap/zapimport.cpp b/src/coreclr/src/zap/zapimport.cpp index f8a5271..599fbce 100644 --- a/src/coreclr/src/zap/zapimport.cpp +++ b/src/coreclr/src/zap/zapimport.cpp @@ -1249,18 +1249,11 @@ public: if (token != mdTokenNil) { _ASSERTE(TypeFromToken(token) == mdtMethodDef || TypeFromToken(token) == mdtMemberRef); + _ASSERTE(!pTable->GetCompileInfo()->IsNativeCallableMethod(handle)); - // It's a NativeCallable method , then encode it as ENCODE_METHOD_NATIVE_ENTRY - if (pTable->GetCompileInfo()->IsNativeCallableMethod(handle)) - { - pTable->EncodeModule(ENCODE_METHOD_NATIVE_ENTRY, referencingModule, pSigBuilder); - } - else - { - pTable->EncodeModule( - (TypeFromToken(token) == mdtMethodDef) ? ENCODE_METHOD_ENTRY_DEF_TOKEN : ENCODE_METHOD_ENTRY_REF_TOKEN, - referencingModule, pSigBuilder); - } + pTable->EncodeModule( + (TypeFromToken(token) == mdtMethodDef) ? ENCODE_METHOD_ENTRY_DEF_TOKEN : ENCODE_METHOD_ENTRY_REF_TOKEN, + referencingModule, pSigBuilder); pSigBuilder->AppendData(RidFromToken(token)); } diff --git a/src/coreclr/src/zap/zapinfo.cpp b/src/coreclr/src/zap/zapinfo.cpp index 2516484..f844361 100644 --- a/src/coreclr/src/zap/zapinfo.cpp +++ b/src/coreclr/src/zap/zapinfo.cpp @@ -482,6 +482,15 @@ void ZapInfo::CompileMethod() } #endif +#if defined(TARGET_X86) && defined(TARGET_WINDOWS) + if (GetCompileInfo()->IsNativeCallableMethod(m_currentMethodHandle)) + { + if (m_zapper->m_pOpt->m_verbose) + m_zapper->Warning(W("ReadyToRun: Methods with NativeCallableAttribute not implemented\n")); + ThrowHR(E_NOTIMPL); + } +#endif // (TARGET_X86) && defined(TARGET_WINDOWS) + if (m_pImage->m_stats) { m_pImage->m_stats->m_methods++; @@ -2268,16 +2277,18 @@ void ZapInfo::getCallInfo(CORINFO_RESOLVED_TOKEN * pResolvedToken, m_zapper->Warning(W("ReadyToRun: Runtime method access checks not supported\n")); ThrowHR(E_NOTIMPL); } - - if (GetCompileInfo()->IsNativeCallableMethod(pResult->hMethod)) - { - if (m_zapper->m_pOpt->m_verbose) - m_zapper->Warning(W("ReadyToRun: References to methods with NativeCallableAttribute not supported\n")); - ThrowHR(E_NOTIMPL); - } } #endif +#if defined(TARGET_X86) && defined(TARGET_WINDOWS) + if (GetCompileInfo()->IsNativeCallableMethod(pResult->hMethod)) + { + if (m_zapper->m_pOpt->m_verbose) + m_zapper->Warning(W("ReadyToRun: References to methods with NativeCallableAttribute not implemented\n")); + ThrowHR(E_NOTIMPL); + } +#endif // (TARGET_X86) && defined(TARGET_WINDOWS) + if (flags & CORINFO_CALLINFO_KINDONLY) return; diff --git a/src/coreclr/tests/src/Interop/NativeCallable/NativeCallableDll.cpp b/src/coreclr/tests/src/Interop/NativeCallable/NativeCallableDll.cpp index 277d8cc..49a82b7 100644 --- a/src/coreclr/tests/src/Interop/NativeCallable/NativeCallableDll.cpp +++ b/src/coreclr/tests/src/Interop/NativeCallable/NativeCallableDll.cpp @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#include +#include + +#include typedef int (STDMETHODCALLTYPE *CALLBACKPROC)(int n); @@ -10,3 +12,43 @@ extern "C" DLL_EXPORT int STDMETHODCALLTYPE CallManagedProc(CALLBACKPROC pCallba { return pCallbackProc(n); } + +namespace +{ + struct ProxyCallContext + { + CALLBACKPROC CallbackProc; + int N; + int Result; + }; + + void ProxyCall(ProxyCallContext* cxt) + { + cxt->Result = CallManagedProc(cxt->CallbackProc, cxt->N); + } +} + +extern "C" DLL_EXPORT int STDMETHODCALLTYPE CallManagedProcOnNewThread(CALLBACKPROC pCallbackProc, int n) +{ + ProxyCallContext cxt{ pCallbackProc, n, 0 }; + std::thread newThreadToRuntime{ ProxyCall, &cxt }; + + // Wait for new thread to complete + newThreadToRuntime.join(); + + return cxt.Result; +} + +#ifdef _WIN32 +extern "C" DLL_EXPORT int STDMETHODCALLTYPE CallManagedProcCatchException(CALLBACKPROC pCallbackProc, int n) +{ + __try + { + return CallManagedProc(pCallbackProc, n); + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + return -1; + } +} +#endif // _WIN32 diff --git a/src/coreclr/tests/src/Interop/NativeCallable/NativeCallableTest.cs b/src/coreclr/tests/src/Interop/NativeCallable/NativeCallableTest.cs index 36b2600..121379c 100644 --- a/src/coreclr/tests/src/Interop/NativeCallable/NativeCallableTest.cs +++ b/src/coreclr/tests/src/Interop/NativeCallable/NativeCallableTest.cs @@ -11,14 +11,19 @@ using System.Runtime.InteropServices; using System.Threading; using TestLibrary; -using Console = Internal.Console; - public class Program { - public static class NativeMethods + public static class NativeCallableDll { - [DllImport("NativeCallableDll")] + [DllImport(nameof(NativeCallableDll))] public static extern int CallManagedProc(IntPtr callbackProc, int n); + + [DllImport(nameof(NativeCallableDll))] + public static extern int CallManagedProcOnNewThread(IntPtr callbackProc, int n); + + [DllImport(nameof(NativeCallableDll))] + // Returns -1 if exception was throw and caught. + public static extern int CallManagedProcCatchException(IntPtr callbackProc, int n); } private delegate int IntNativeMethodInvoker(); @@ -29,10 +34,22 @@ public class Program try { TestNativeCallableValid(); + TestNativeCallableValid_OnNewNativeThread(); + TestNativeCallableValid_PrepareMethod(); + NegativeTest_NonStaticMethod(); NegativeTest_ViaDelegate(); NegativeTest_NonBlittable(); - NegativeTest_GenericArguments(); - NativeCallableViaUnmanagedCalli(); + NegativeTest_NonInstantiatedGenericArguments(); + NegativeTest_InstantiatedGenericArguments(); + NegativeTest_FromInstantiatedGenericClass(); + TestNativeCallableViaUnmanagedCalli(); + + // Exception handling is only supported on Windows. + if (TestLibrary.Utilities.IsWindows) + { + TestNativeCallableValid_ThrowException(); + TestNativeCallableViaUnmanagedCalli_ThrowException(); + } if (args.Length != 0 && args[0].Equals("calli")) { @@ -61,10 +78,10 @@ public class Program public static void TestNativeCallableValid() { - Console.WriteLine($"{nameof(NativeCallableAttribute)} function"); + Console.WriteLine($"Running {nameof(TestNativeCallableValid)}..."); /* - void TestNativeCallable() + void NativeCallable() { .locals init ([0] native int ptr) IL_0000: nop @@ -73,12 +90,12 @@ public class Program IL_0008: ldloc.0 IL_0009: ldc.i4 local - IL_000e: call bool NativeMethods::CallManagedProc(native int, int) + IL_000e: call bool NativeCallableDll::CallManagedProc(native int, int) IL_0013: ret } */ - DynamicMethod testNativeCallable = new DynamicMethod("TestNativeCallable", typeof(int), null, typeof(Program).Module); + DynamicMethod testNativeCallable = new DynamicMethod("NativeCallable", typeof(int), null, typeof(Program).Module); ILGenerator il = testNativeCallable.GetILGenerator(); il.DeclareLocal(typeof(IntPtr)); il.Emit(OpCodes.Nop); @@ -90,7 +107,7 @@ public class Program int n = 12345; il.Emit(OpCodes.Ldc_I4, n); - il.Emit(OpCodes.Call, typeof(NativeMethods).GetMethod("CallManagedProc")); + il.Emit(OpCodes.Call, typeof(NativeCallableDll).GetMethod("CallManagedProc")); il.Emit(OpCodes.Ret); var testNativeMethod = (IntNativeMethodInvoker)testNativeCallable.CreateDelegate(typeof(IntNativeMethodInvoker)); @@ -98,19 +115,151 @@ public class Program Assert.AreEqual(expected, testNativeMethod()); } - public static void NegativeTest_ViaDelegate() + public static void TestNativeCallableValid_OnNewNativeThread() { - Console.WriteLine($"{nameof(NativeCallableAttribute)} function as delegate"); + Console.WriteLine($"Running {nameof(TestNativeCallableValid_OnNewNativeThread)}..."); - // Try invoking method directly - try - { - CallAsDelegate(); - Assert.Fail($"Invalid to call {nameof(ManagedDoubleCallback)} as delegate"); - } - catch (NotSupportedException) + /* + void NativeCallableOnNewNativeThread() + { + .locals init ([0] native int ptr) + IL_0000: nop + IL_0001: ldftn int32 ManagedDoubleCallback(int32) + IL_0007: stloc.0 + + IL_0008: ldloc.0 + IL_0009: ldc.i4 local + IL_000e: call bool NativeCallableDll::CallManagedProcOnNewThread(native int, int) + + IL_0013: ret + } + */ + DynamicMethod testNativeCallable = new DynamicMethod("NativeCallableOnNewNativeThread", typeof(int), null, typeof(Program).Module); + ILGenerator il = testNativeCallable.GetILGenerator(); + il.DeclareLocal(typeof(IntPtr)); + il.Emit(OpCodes.Nop); + + // Get native function pointer of the callback + il.Emit(OpCodes.Ldftn, typeof(Program).GetMethod(nameof(ManagedDoubleCallback))); + il.Emit(OpCodes.Stloc_0); + il.Emit(OpCodes.Ldloc_0); + + int n = 12345; + il.Emit(OpCodes.Ldc_I4, n); + il.Emit(OpCodes.Call, typeof(NativeCallableDll).GetMethod("CallManagedProcOnNewThread")); + il.Emit(OpCodes.Ret); + var testNativeMethod = (IntNativeMethodInvoker)testNativeCallable.CreateDelegate(typeof(IntNativeMethodInvoker)); + + int expected = DoubleImpl(n); + Assert.AreEqual(expected, testNativeMethod()); + } + + [NativeCallable] + public static int ManagedCallback_Prepared(int n) + { + return DoubleImpl(n); + } + + // This test is about the interaction between Tiered Compilation and the NativeCallableAttribute. + public static void TestNativeCallableValid_PrepareMethod() + { + Console.WriteLine($"Running {nameof(TestNativeCallableValid_PrepareMethod)}..."); + + /* + void NativeCallableOnNewNativeThread() + { + .locals init ([0] native int ptr) + IL_0000: nop + IL_0001: ldftn int32 ManagedCallback_Prepared(int32) + IL_0007: stloc.0 + + IL_0008: ldloc.0 + IL_0009: ldc.i4 local + IL_000e: call bool NativeCallableDll::CallManagedProcOnNewThread(native int, int) + + IL_0013: ret + } + */ + DynamicMethod testNativeCallable = new DynamicMethod("NativeCallableValid_PrepareMethod", typeof(int), null, typeof(Program).Module); + ILGenerator il = testNativeCallable.GetILGenerator(); + il.DeclareLocal(typeof(IntPtr)); + il.Emit(OpCodes.Nop); + + // Prepare the managed callback. + var preparedCallback = typeof(Program).GetMethod(nameof(ManagedCallback_Prepared)); + RuntimeHelpers.PrepareMethod(preparedCallback.MethodHandle); + + // Get native function pointer of the callback + il.Emit(OpCodes.Ldftn, preparedCallback); + il.Emit(OpCodes.Stloc_0); + il.Emit(OpCodes.Ldloc_0); + + int n = 12345; + il.Emit(OpCodes.Ldc_I4, n); + il.Emit(OpCodes.Call, typeof(NativeCallableDll).GetMethod("CallManagedProcOnNewThread")); + il.Emit(OpCodes.Ret); + var testNativeMethod = (IntNativeMethodInvoker)testNativeCallable.CreateDelegate(typeof(IntNativeMethodInvoker)); + + // Call enough to attempt to trigger Tiered Compilation from a new thread. + for (int i = 0; i < 100; ++i) { + testNativeMethod(); } + } + + private const int CallbackThrowsErrorCode = 27; + + [NativeCallable] + public static int CallbackThrows(int val) + { + throw new Exception() { HResult = CallbackThrowsErrorCode }; + } + + public static void TestNativeCallableValid_ThrowException() + { + Console.WriteLine($"Running {nameof(TestNativeCallableValid_ThrowException)}..."); + + /* + void NativeCallableValid_ThrowException() + { + .locals init ([0] native int ptr) + IL_0000: nop + IL_0001: ldftn int32 CallbackThrows(int32) + IL_0007: stloc.0 + + IL_0008: ldloc.0 + IL_0009: ldc.i4 local + IL_000e: call bool NativeCallableDll::CallManagedProcCatchException(native int, int) + + IL_0013: ret + } + */ + DynamicMethod testNativeCallable = new DynamicMethod("NativeCallableValid_ThrowException", typeof(int), null, typeof(Program).Module); + ILGenerator il = testNativeCallable.GetILGenerator(); + il.DeclareLocal(typeof(IntPtr)); + il.Emit(OpCodes.Nop); + + // Get native function pointer of the callback + il.Emit(OpCodes.Ldftn, typeof(Program).GetMethod(nameof(CallbackThrows))); + il.Emit(OpCodes.Stloc_0); + il.Emit(OpCodes.Ldloc_0); + + int n = 12345; + il.Emit(OpCodes.Ldc_I4, n); + il.Emit(OpCodes.Call, typeof(NativeCallableDll).GetMethod("CallManagedProcCatchException")); + il.Emit(OpCodes.Ret); + var testNativeMethod = (IntNativeMethodInvoker)testNativeCallable.CreateDelegate(typeof(IntNativeMethodInvoker)); + + // Method should have thrown and caught an exception. + Assert.AreEqual(-1, testNativeMethod()); + } + + public static void NegativeTest_ViaDelegate() + { + Console.WriteLine($"Running {nameof(NegativeTest_ViaDelegate)}..."); + + // Try invoking method directly + Assert.Throws(() => { CallAsDelegate(); }); // Local function to delay exception thrown during JIT void CallAsDelegate() @@ -121,22 +270,57 @@ public class Program } [NativeCallable] - public static int CallbackMethodNonBlittable(bool x1) + public void CallbackNonStatic() + { + Assert.Fail($"Instance functions with attribute {nameof(NativeCallableAttribute)} are invalid"); + } + + public static void NegativeTest_NonStaticMethod() + { + Console.WriteLine($"Running {nameof(NegativeTest_NonStaticMethod)}..."); + + /* + void TestNativeCallableNonStatic() + { + .locals init ([0] native int ptr) + IL_0000: nop + IL_0001: ldftn void CallbackNonStatic() + IL_0007: stloc.0 + IL_0008: ret + } + */ + DynamicMethod testNativeCallable = new DynamicMethod("TestNativeCallableNonStatic", null, null, typeof(Program).Module); + ILGenerator il = testNativeCallable.GetILGenerator(); + il.DeclareLocal(typeof(IntPtr)); + il.Emit(OpCodes.Nop); + + // Get native function pointer of the callback + il.Emit(OpCodes.Ldftn, typeof(Program).GetMethod(nameof(CallbackNonStatic))); + il.Emit(OpCodes.Stloc_0); + + il.Emit(OpCodes.Ret); + var testNativeMethod = (NativeMethodInvoker)testNativeCallable.CreateDelegate(typeof(NativeMethodInvoker)); + + // Try invoking method + Assert.Throws(() => { testNativeMethod(); }); + } + + [NativeCallable] + public static void CallbackMethodNonBlittable(bool x1) { Assert.Fail($"Functions with attribute {nameof(NativeCallableAttribute)} cannot have non-blittable arguments"); - return -1; } public static void NegativeTest_NonBlittable() { - Console.WriteLine($"{nameof(NativeCallableAttribute)} function with non-blittable arguments"); + Console.WriteLine($"Running {nameof(NegativeTest_NonBlittable)}..."); /* void TestNativeCallableNonBlittable() { .locals init ([0] native int ptr) IL_0000: nop - IL_0001: ldftn int32 CallbackMethodNonBlittable(bool) + IL_0001: ldftn void CallbackMethodNonBlittable(bool) IL_0007: stloc.0 IL_0008: ret } @@ -154,36 +338,30 @@ public class Program var testNativeMethod = (NativeMethodInvoker)testNativeCallable.CreateDelegate(typeof(NativeMethodInvoker)); // Try invoking method - try - { - testNativeMethod(); - Assert.Fail($"Function {nameof(CallbackMethodNonBlittable)} has non-blittable types"); - } - catch (NotSupportedException) - { - } + Assert.Throws(() => { testNativeMethod(); }); } [NativeCallable] - public static int CallbackMethodGeneric(T arg) + public static void CallbackMethodGeneric(T arg) { Assert.Fail($"Functions with attribute {nameof(NativeCallableAttribute)} cannot have generic arguments"); - return -1; } - public static void NegativeTest_GenericArguments() + public static void NegativeTest_NonInstantiatedGenericArguments() { + Console.WriteLine($"Running {nameof(NegativeTest_NonInstantiatedGenericArguments)}..."); + /* - void TestNativeCallableGenericArguments() + void TestNativeCallableNonInstGenericArguments() { .locals init ([0] native int ptr) IL_0000: nop - IL_0001: ldftn int32 CallbackMethodGeneric(T) + IL_0001: ldftn void CallbackMethodGeneric(T) IL_0007: stloc.0 IL_0008: ret } */ - DynamicMethod testNativeCallable = new DynamicMethod("TestNativeCallableGenericArguments", null, null, typeof(Program).Module); + DynamicMethod testNativeCallable = new DynamicMethod("TestNativeCallableNonInstGenericArguments", null, null, typeof(Program).Module); ILGenerator il = testNativeCallable.GetILGenerator(); il.DeclareLocal(typeof(IntPtr)); il.Emit(OpCodes.Nop); @@ -196,16 +374,78 @@ public class Program var testNativeMethod = (NativeMethodInvoker)testNativeCallable.CreateDelegate(typeof(NativeMethodInvoker)); // Try invoking method - try - { - testNativeMethod(); - Assert.Fail($"Function {nameof(CallbackMethodGeneric)} has generic types"); - } - catch (InvalidProgramException) + Assert.Throws(() => { testNativeMethod(); }); + } + + public static void NegativeTest_InstantiatedGenericArguments() + { + Console.WriteLine($"Running {nameof(NegativeTest_InstantiatedGenericArguments)}..."); + + /* + void TestNativeCallableInstGenericArguments() + { + .locals init ([0] native int ptr) + IL_0000: nop + IL_0001: ldftn void CallbackMethodGeneric(int) + IL_0007: stloc.0 + IL_0008: ret + } + */ + DynamicMethod testNativeCallable = new DynamicMethod("TestNativeCallableInstGenericArguments", null, null, typeof(Program).Module); + ILGenerator il = testNativeCallable.GetILGenerator(); + il.DeclareLocal(typeof(IntPtr)); + il.Emit(OpCodes.Nop); + + // Get native function pointer of the instantiated generic callback + il.Emit(OpCodes.Ldftn, typeof(Program).GetMethod(nameof(CallbackMethodGeneric)).MakeGenericMethod(new [] { typeof(int) })); + il.Emit(OpCodes.Stloc_0); + + il.Emit(OpCodes.Ret); + var testNativeMethod = (NativeMethodInvoker)testNativeCallable.CreateDelegate(typeof(NativeMethodInvoker)); + + // Try invoking method + Assert.Throws(() => { testNativeMethod(); }); + } + + public class GenericClass + { + [NativeCallable] + public static void CallbackMethod(int n) { + Assert.Fail($"Functions with attribute {nameof(NativeCallableAttribute)} within a generic type are invalid"); } } + public static void NegativeTest_FromInstantiatedGenericClass() + { + Console.WriteLine($"Running {nameof(NegativeTest_FromInstantiatedGenericClass)}..."); + + /* + void TestNativeCallableInstGenericType() + { + .locals init ([0] native int ptr) + IL_0000: nop + IL_0001: ldftn void GenericClass::CallbackMethod(int) + IL_0007: stloc.0 + IL_0008: ret + } + */ + DynamicMethod testNativeCallable = new DynamicMethod("TestNativeCallableInstGenericClass", null, null, typeof(Program).Module); + ILGenerator il = testNativeCallable.GetILGenerator(); + il.DeclareLocal(typeof(IntPtr)); + il.Emit(OpCodes.Nop); + + // Get native function pointer of the callback from the instantiated generic class. + il.Emit(OpCodes.Ldftn, typeof(GenericClass).GetMethod(nameof(GenericClass.CallbackMethod))); + il.Emit(OpCodes.Stloc_0); + + il.Emit(OpCodes.Ret); + var testNativeMethod = (NativeMethodInvoker)testNativeCallable.CreateDelegate(typeof(NativeMethodInvoker)); + + // Try invoking method + Assert.Throws(() => { testNativeMethod(); }); + } + [NativeCallable] public static void CallbackViaCalli(int val) { @@ -214,7 +454,7 @@ public class Program public static void NegativeTest_ViaCalli() { - Console.WriteLine($"{nameof(NativeCallableAttribute)} function via calli instruction. The CLR _will_ crash."); + Console.WriteLine($"{nameof(NegativeTest_ViaCalli)} function via calli instruction. The CLR _will_ crash."); /* void TestNativeCallableViaCalli() @@ -262,12 +502,12 @@ public class Program return DoubleImpl(val); } - public static void NativeCallableViaUnmanagedCalli() + public static void TestNativeCallableViaUnmanagedCalli() { - Console.WriteLine($"{nameof(NativeCallableAttribute)} function via calli instruction with unmanaged calling convention."); + Console.WriteLine($"Running {nameof(TestNativeCallableViaUnmanagedCalli)}..."); /* - void TestNativeCallableViaCalli() + void NativeCallableViaCalli() { .locals init (native int V_0) IL_0000: nop @@ -281,7 +521,7 @@ public class Program IL_0014: ret } */ - DynamicMethod testNativeCallable = new DynamicMethod("TestNativeCallableViaUnmanagedCalli", typeof(int), null, typeof(Program).Module); + DynamicMethod testNativeCallable = new DynamicMethod("NativeCallableViaUnmanagedCalli", typeof(int), null, typeof(Program).Module); ILGenerator il = testNativeCallable.GetILGenerator(); il.DeclareLocal(typeof(IntPtr)); il.Emit(OpCodes.Nop); @@ -303,4 +543,59 @@ public class Program int expected = DoubleImpl(n); Assert.AreEqual(expected, testNativeMethod()); } + + [NativeCallable(CallingConvention = CallingConvention.StdCall)] + public static int CallbackViaUnmanagedCalliThrows(int val) + { + throw new Exception() { HResult = CallbackThrowsErrorCode }; + } + + public static void TestNativeCallableViaUnmanagedCalli_ThrowException() + { + Console.WriteLine($"Running {nameof(TestNativeCallableViaUnmanagedCalli_ThrowException)}..."); + + /* + void NativeCallableViaUnmanagedCalli_ThrowException() + { + .locals init (native int V_0) + IL_0000: nop + IL_0001: ldftn int CallbackViaUnmanagedCalliThrows(int32) + IL_0007: stloc.0 + + IL_0008: ldc.i4 1234 + IL_000d: ldloc.0 + IL_000e: calli int32 stdcall(int32) + + IL_0014: ret + } + */ + DynamicMethod testNativeCallable = new DynamicMethod("NativeCallableViaUnmanagedCalli_ThrowException", typeof(int), null, typeof(Program).Module); + ILGenerator il = testNativeCallable.GetILGenerator(); + il.DeclareLocal(typeof(IntPtr)); + il.Emit(OpCodes.Nop); + + // Get native function pointer of the callback + il.Emit(OpCodes.Ldftn, typeof(Program).GetMethod(nameof(CallbackViaUnmanagedCalliThrows))); + il.Emit(OpCodes.Stloc_0); + + int n = 1234; + + il.Emit(OpCodes.Ldc_I4, n); + il.Emit(OpCodes.Ldloc_0); + il.EmitCalli(OpCodes.Calli, CallingConvention.StdCall, typeof(int), new Type[] { typeof(int) }); + + il.Emit(OpCodes.Ret); + + IntNativeMethodInvoker testNativeMethod = (IntNativeMethodInvoker)testNativeCallable.CreateDelegate(typeof(IntNativeMethodInvoker)); + + try + { + testNativeMethod(); + Assert.Fail($"Function {nameof(CallbackViaUnmanagedCalliThrows)} should throw"); + } + catch (Exception e) + { + Assert.AreEqual(CallbackThrowsErrorCode, e.HResult); + } + } } diff --git a/src/coreclr/tests/src/Interop/NativeCallable/NativeCallableTest.csproj b/src/coreclr/tests/src/Interop/NativeCallable/NativeCallableTest.csproj index 0774784..a485b23 100644 --- a/src/coreclr/tests/src/Interop/NativeCallable/NativeCallableTest.csproj +++ b/src/coreclr/tests/src/Interop/NativeCallable/NativeCallableTest.csproj @@ -1,7 +1,6 @@ Exe - true 1 @@ -11,5 +10,6 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 0639dc7..5449ed6 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -2875,8 +2875,8 @@ Collection was of a fixed size. - - Generic methods with NativeCallableAttribute are not supported. + + Generic methods with NativeCallableAttribute are invalid. This operation is invalid on overlapping buffers. @@ -2911,14 +2911,14 @@ No data is available for encoding {0}. For information on defining a custom encoding, see the documentation for the Encoding.RegisterProvider method. - - Non-blittable parameter types are not supported for NativeCallable methods. + + Non-blittable parameter types are invalid for NativeCallable methods. Not supported in a non-reflected type. - - Non-static methods with NativeCallableAttribute are not supported. + + Non-static methods with NativeCallableAttribute are invalid. Parent does not have a default constructor. The default constructor must be explicitly defined. diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeCallableAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeCallableAttribute.cs index 5fa60f4..459815f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeCallableAttribute.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeCallableAttribute.cs @@ -5,10 +5,16 @@ namespace System.Runtime.InteropServices { /// - /// Any method marked with NativeCallableAttribute can be directly called from - /// native code. The function token can be loaded to a local variable using LDFTN - /// and passed as a callback to native method. + /// Any method marked with can be directly called from + /// native code. The function token can be loaded to a local variable using the address-of operator + /// in C# and passed as a callback to a native method. /// + /// + /// Methods marked with this attribute have the following restrictions: + /// * Method must be marked "static". + /// * Must not be called from managed code. + /// * Must only have blittable arguments. + /// [AttributeUsage(AttributeTargets.Method)] public sealed class NativeCallableAttribute : Attribute { @@ -17,12 +23,12 @@ namespace System.Runtime.InteropServices } /// - /// Optional. If omitted, compiler will choose one for you. + /// Optional. If omitted, the runtime will use the default platform calling convention. /// public CallingConvention CallingConvention; /// - /// Optional. If omitted, then the method is native callable, but no EAT is emitted. + /// Optional. If omitted, then the method is native callable, but no export is emitted during compilation. /// public string? EntryPoint; } diff --git a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs index 2613285..ba71f66 100644 --- a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs +++ b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs @@ -1019,6 +1019,13 @@ namespace System.Runtime.InteropServices public void RegisterAsGlobalInstance() { } protected static void GetIUnknownImpl(out System.IntPtr fpQueryInterface, out System.IntPtr fpAddRef, out System.IntPtr fpRelease) { throw null; } } + [System.AttributeUsageAttribute(System.AttributeTargets.Method)] + public sealed class NativeCallableAttribute : System.Attribute + { + public NativeCallableAttribute() { } + public System.Runtime.InteropServices.CallingConvention CallingConvention; + public string? EntryPoint; + } } namespace System.Runtime.InteropServices.ComTypes { -- 2.7.4