Make NativeCallableAttribute public. (#33005)
authorAaron Robinson <arobins@microsoft.com>
Fri, 13 Mar 2020 23:50:58 +0000 (16:50 -0700)
committerGitHub <noreply@github.com>
Fri, 13 Mar 2020 23:50:58 +0000 (16:50 -0700)
* 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 <janvorli@microsoft.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
32 files changed:
src/coreclr/src/inc/corcompile.h
src/coreclr/src/inc/jithelpers.h
src/coreclr/src/inc/readytorun.h
src/coreclr/src/inc/readytorunhelpers.h
src/coreclr/src/tools/Common/Internal/Runtime/ReadyToRunConstants.cs
src/coreclr/src/tools/Common/JitInterface/CorInfoImpl.cs
src/coreclr/src/tools/Common/TypeSystem/Common/ExceptionStringID.cs
src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs
src/coreclr/src/tools/crossgen2/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs
src/coreclr/src/vm/cgensys.h
src/coreclr/src/vm/codeversion.cpp
src/coreclr/src/vm/codeversion.h
src/coreclr/src/vm/comdelegate.cpp
src/coreclr/src/vm/comdelegate.h
src/coreclr/src/vm/exceptionhandling.cpp
src/coreclr/src/vm/frames.h
src/coreclr/src/vm/jithelpers.cpp
src/coreclr/src/vm/jitinterface.cpp
src/coreclr/src/vm/jitinterface.h
src/coreclr/src/vm/method.hpp
src/coreclr/src/vm/prestub.cpp
src/coreclr/src/vm/stacksampler.cpp
src/coreclr/src/vm/threads.h
src/coreclr/src/vm/tieredcompilation.cpp
src/coreclr/src/zap/zapimport.cpp
src/coreclr/src/zap/zapinfo.cpp
src/coreclr/tests/src/Interop/NativeCallable/NativeCallableDll.cpp
src/coreclr/tests/src/Interop/NativeCallable/NativeCallableTest.cs
src/coreclr/tests/src/Interop/NativeCallable/NativeCallableTest.csproj
src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeCallableAttribute.cs
src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs

index 9f41fd1..c34f31e 100644 (file)
@@ -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
index 8087f4f..8e92cbd 100644 (file)
     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)
 
index dac75da..44e4d02 100644 (file)
@@ -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__
index c9a60af..3703412 100644 (file)
@@ -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,                          )
index ade2439..a360b8b 100644 (file)
@@ -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;
     }
 }
index 939d829..1fadcd5 100644 (file)
@@ -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)
             {
index 7de12aa..9c0ef31 100644 (file)
@@ -34,6 +34,9 @@ namespace Internal.TypeSystem
         InvalidProgramNativeCallable,
         InvalidProgramCallAbstractMethod,
         InvalidProgramCallVirtStatic,
+        InvalidProgramNonStaticMethod,
+        InvalidProgramGenericMethod,
+        InvalidProgramNonBlittableTypes,
 
         // BadImageFormatException
         BadImageFormatGeneric,
index f9ca5b5..2e72b4c 100644 (file)
@@ -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<ParameterMetadata>())) // 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)
         {
index e5f5ec7..e977f30 100644 (file)
@@ -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");
index 540ed6c..3c8c928 100644 (file)
@@ -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);
 
index 9ff6416..4d56c1d 100644 (file)
@@ -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
index 43d2093..1cb3f2a 100644 (file)
@@ -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);
index a2f0fd7..ac3c4d0 100644 (file)
@@ -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)
index cb22cc5..a87845d 100644 (file)
@@ -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);
index e473814..655a102 100644 (file)
@@ -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))
         {
index 0709cf0..1976d73 100644 (file)
@@ -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
index bb59a80..6fcb1c8 100644 (file)
@@ -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
index 2d55a9f..2c7b37d 100644 (file)
@@ -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))
index 79be98c..62faa9e 100644 (file)
@@ -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,
index 64936b4..7efecae 100644 (file)
@@ -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 <stddef.h>
 #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:
index 87e0763..6d7eb7e 100644 (file)
@@ -314,10 +314,11 @@ void DACNotifyCompilationFinished(MethodDesc *methodDesc, PCODE pCode)
 #endif
 // </TODO>
 
-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<PrestubMethodFrame> 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<PrestubMethodFrame> 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<cfg_any>::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<cfg_any>::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())
     {
index 2cd3c1e..b4cec17 100644 (file)
@@ -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.
index 48929bd..a830887 100644 (file)
@@ -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.
 //---------------------------------------------------------------------------
index 06f0047..ffd986a 100644 (file)
@@ -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,
index f8a5271..599fbce 100644 (file)
@@ -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));
         }
index 2516484..f844361 100644 (file)
@@ -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;
 
index 277d8cc..49a82b7 100644 (file)
@@ -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 <platformdefines.h> 
+#include <platformdefines.h>
+
+#include <thread>
 
 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
index 36b2600..121379c 100644 (file)
@@ -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     <n> 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     <n> 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     <n> 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     <n> 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<NotSupportedException>(() => { 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<InvalidProgramException>(() => { 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<InvalidProgramException>(() => { testNativeMethod(); });
     }
 
     [NativeCallable]
-    public static int CallbackMethodGeneric<T>(T arg)
+    public static void CallbackMethodGeneric<T>(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<InvalidProgramException>(() => { 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<InvalidProgramException>(() => { testNativeMethod(); });
+    }
+
+    public class GenericClass<T>
+    {
+        [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<int>::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<int>).GetMethod(nameof(GenericClass<int>.CallbackMethod)));
+        il.Emit(OpCodes.Stloc_0);
+
+        il.Emit(OpCodes.Ret);
+        var testNativeMethod = (NativeMethodInvoker)testNativeCallable.CreateDelegate(typeof(NativeMethodInvoker));
+
+        // Try invoking method
+        Assert.Throws<InvalidProgramException>(() => { 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);
+        }
+    }
 }
index 0774784..a485b23 100644 (file)
@@ -1,7 +1,6 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <OutputType>Exe</OutputType>
-    <ReferenceSystemPrivateCoreLib>true</ReferenceSystemPrivateCoreLib>
     <CLRTestPriority>1</CLRTestPriority>
   </PropertyGroup>
   <Import Project="$([MSBuild]::GetPathOfFileAbove(Interop.settings.targets))" />
@@ -11,5 +10,6 @@
   <ItemGroup>
     <!-- This is needed to make sure native binary gets installed in the right location -->
     <ProjectReference Include="CMakeLists.txt" />
+    <ProjectReference Include="$(TestSourceDir)Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
   </ItemGroup>
 </Project>
index 0639dc7..5449ed6 100644 (file)
   <data name="NotSupported_FixedSizeCollection" xml:space="preserve">
     <value>Collection was of a fixed size.</value>
   </data>
-  <data name="NotSupported_GenericMethod" xml:space="preserve">
-    <value>Generic methods with NativeCallableAttribute are not supported.</value>
+  <data name="InvalidProgram_GenericMethod" xml:space="preserve">
+    <value>Generic methods with NativeCallableAttribute are invalid.</value>
   </data>
   <data name="InvalidOperation_SpanOverlappedOperation" xml:space="preserve">
     <value>This operation is invalid on overlapping buffers.</value>
   <data name="NotSupported_NoCodepageData" xml:space="preserve">
     <value>No data is available for encoding {0}. For information on defining a custom encoding, see the documentation for the Encoding.RegisterProvider method.</value>
   </data>
-  <data name="NotSupported_NonBlittableTypes" xml:space="preserve">
-    <value>Non-blittable parameter types are not supported for NativeCallable methods.</value>
+  <data name="InvalidProgram_NonBlittableTypes" xml:space="preserve">
+    <value>Non-blittable parameter types are invalid for NativeCallable methods.</value>
   </data>
   <data name="NotSupported_NonReflectedType" xml:space="preserve">
     <value>Not supported in a non-reflected type.</value>
   </data>
-  <data name="NotSupported_NonStaticMethod" xml:space="preserve">
-    <value>Non-static methods with NativeCallableAttribute are not supported.</value>
+  <data name="InvalidProgram_NonStaticMethod" xml:space="preserve">
+    <value>Non-static methods with NativeCallableAttribute are invalid.</value>
   </data>
   <data name="NotSupported_NoParentDefaultConstructor" xml:space="preserve">
     <value>Parent does not have a default constructor. The default constructor must be explicitly defined.</value>
index 5fa60f4..459815f 100644 (file)
@@ -5,10 +5,16 @@
 namespace System.Runtime.InteropServices
 {
     /// <summary>
-    /// 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 <see cref="System.Runtime.InteropServices.NativeCallableAttribute" /> can be directly called from
+    /// native code. The function token can be loaded to a local variable using the <see href="https://docs.microsoft.com/dotnet/csharp/language-reference/operators/pointer-related-operators#address-of-operator-">address-of</see> operator
+    /// in C# and passed as a callback to a native method.
     /// </summary>
+    /// <remarks>
+    /// Methods marked with this attribute have the following restrictions:
+    ///   * Method must be marked "static".
+    ///   * Must not be called from managed code.
+    ///   * Must only have <see href="https://docs.microsoft.com/dotnet/framework/interop/blittable-and-non-blittable-types">blittable</see> arguments.
+    /// </remarks>
     [AttributeUsage(AttributeTargets.Method)]
     public sealed class NativeCallableAttribute : Attribute
     {
@@ -17,12 +23,12 @@ namespace System.Runtime.InteropServices
         }
 
         /// <summary>
-        /// Optional. If omitted, compiler will choose one for you.
+        /// Optional. If omitted, the runtime will use the default platform calling convention.
         /// </summary>
         public CallingConvention CallingConvention;
 
         /// <summary>
-        /// 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.
         /// </summary>
         public string? EntryPoint;
     }
index 2613285..ba71f66 100644 (file)
@@ -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
 {