* 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>
ENCODE_VARARGS_METHODREF,
ENCODE_VARARGS_SIG,
ENCODE_ACTIVE_DEPENDENCY, /* Conditional active dependency */
- ENCODE_METHOD_NATIVE_ENTRY, /* NativeCallable method token */
};
enum EncodeMethodSigFlags
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)
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,
enum ReadyToRunRuntimeConstants : DWORD
{
- READYTORUN_PInvokeTransitionFrameSizeInPointerUnits = 11
+ READYTORUN_PInvokeTransitionFrameSizeInPointerUnits = 11,
+ READYTORUN_ReversePInvokeTransitionFrameSizeInPointerUnits = 2
};
#endif // __READYTORUN_H__
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, )
PInvokeBegin = 0x42,
PInvokeEnd = 0x43,
GCPoll = 0x44,
+ ReversePInvokeEnter = 0x45,
+ ReversePInvokeExit = 0x46,
// Get string handle lazily
GetString = 0x50,
CheckCastInterface,
CheckInstanceInterface,
- // P/Invoke support
- ReversePInvokeEnter,
- ReversePInvokeExit,
-
MonitorEnterStatic,
MonitorExitStatic,
public static class ReadyToRunRuntimeConstants
{
public const int READYTORUN_PInvokeTransitionFrameSizeInPointerUnits = 11;
+ public const int READYTORUN_ReversePInvokeTransitionFrameSizeInPointerUnits = 2;
}
}
pEEInfoOut.offsetOfDelegateInstance = (uint)pointerSize; // Delegate::m_firstParameter
pEEInfoOut.offsetOfDelegateFirstTarget = OffsetOfDelegateFirstTarget;
- pEEInfoOut.sizeOfReversePInvokeFrame = (uint)(2 * pointerSize);
+ pEEInfoOut.sizeOfReversePInvokeFrame = (uint)SizeOfReversePInvokeTransitionFrame;
pEEInfoOut.osPageSize = new UIntPtr(0x1000);
}
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)
{
InvalidProgramNativeCallable,
InvalidProgramCallAbstractMethod,
InvalidProgramCallVirtStatic,
+ InvalidProgramNonStaticMethod,
+ InvalidProgramGenericMethod,
+ InvalidProgramNonBlittableTypes,
// BadImageFormatException
BadImageFormatGeneric,
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:
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:
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;
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
}
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)
{
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");
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);
PCODE CodeVersionManager::PublishVersionableCodeIfNecessary(
MethodDesc* pMethodDesc,
+ CallerGCMode callerGCMode,
bool *doBackpatchRef,
bool *doFullBackpatchRef)
{
_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)
{
PrepareCodeConfigBuffer configBuffer(activeVersion);
PrepareCodeConfig *config = configBuffer.GetConfig();
+
+ // Record the caller's GC mode.
+ config->SetCallerGCMode(callerGCMode);
pCode = pMethodDesc->PrepareCode(config);
#ifdef FEATURE_CODE_VERSIONING
#ifndef CODE_VERSION_H
#define CODE_VERSION_H
-class NativeCodeVersion;
class ILCodeVersion;
typedef DWORD NativeCodeVersionId;
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);
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)
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;
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)
// 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);
}
else
{
- // TODO: This needs to implemented. Make it fail for now.
UNREACHABLE();
}
}
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))
{
};
#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
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
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;
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();
}
// 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;
}
}
+#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;
}
#endif
+#ifndef CROSSGEN_COMPILE
// ********************************************************************
// README!!
// ********************************************************************
//
// 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;
#endif // FEATURE_PREJIT
-#ifndef CROSSGEN_COMPILE
EEJitManager *jitMgr = ExecutionManager::GetEEJitManager();
if (!jitMgr->LoadJIT())
{
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
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))
{
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);
if (!SUCCEEDED(res))
{
-#ifndef CROSSGEN_COMPILE
jitInfo.BackoutJitData(jitMgr);
-#endif
-
ThrowExceptionForJit(res);
}
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.
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),
COOPERATIVE_TRANSITION_END();
return ret;
}
+#endif // CROSSGEN_COMPILE
extern "C" unsigned __stdcall PartialNGenStressPercentage()
{
}
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);
}
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))
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,
#include "cor.h"
#include "util.hpp"
#include "clsload.hpp"
-#include "codeman.h"
#include "class.h"
#include "siginfo.hpp"
#include "methodimpl.h"
#include <stddef.h>
#include "eeconfig.h"
#include "precode.h"
-#include "codeversion.h"
#ifndef FEATURE_PREJIT
#include "fixuppointer.h"
//
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);
#ifndef DACCESS_COMPILE
public:
- PCODE PrepareInitialCode();
+ PCODE PrepareInitialCode(CallerGCMode callerGCMode = CallerGCMode::Unknown);
PCODE PrepareCode(PrepareCodeConfig* pConfig);
private:
BOOL ReadyToRunRejectedPrecompiledCode();
void SetProfilerRejectedPrecompiledCode();
void SetReadyToRunRejectedPrecompiledCode();
+ CallerGCMode GetCallerGCMode();
+ void SetCallerGCMode(CallerGCMode mode);
#ifdef FEATURE_CODE_VERSIONING
public:
BOOL m_mayUsePrecompiledCode;
BOOL m_ProfilerRejectedPrecompiledCode;
BOOL m_ReadyToRunRejectedPrecompiledCode;
+ CallerGCMode m_callerGCMode;
#ifdef FEATURE_CODE_VERSIONING
private:
#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)
{
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
}
}
}
Thread::CurrentPrepareCodeConfigHolder threadPrepareCodeConfigHolder(GetThread(), pConfig);
#endif
- pCode = UnsafeJitFunction(pConfig->GetCodeVersion(), pilHeader, *pFlags, pSizeOfCode);
+ pCode = UnsafeJitFunction(pConfig, pilHeader, *pFlags, pSizeOfCode);
}
EX_CATCH
{
m_mayUsePrecompiledCode(mayUsePrecompiledCode),
m_ProfilerRejectedPrecompiledCode(FALSE),
m_ReadyToRunRejectedPrecompiledCode(FALSE),
+ m_callerGCMode(CallerGCMode::Unknown),
#ifdef FEATURE_CODE_VERSIONING
m_profilerMayHaveActivatedNonDefaultCodeVersion(false),
m_generatedOrLoadedNewCode(false),
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;
#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;
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);
// 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)
{
{
bool doBackpatch = true;
bool doFullBackpatch = false;
- pCode = GetCodeVersionManager()->PublishVersionableCodeIfNecessary(this, &doBackpatch, &doFullBackpatch);
+ pCode = GetCodeVersionManager()->PublishVersionableCodeIfNecessary(this, callerGCMode, &doBackpatch, &doFullBackpatch);
if (doBackpatch)
{
{
GetOrCreatePrecode();
}
- pCode = PrepareInitialCode();
+ pCode = PrepareInitialCode(callerGCMode);
} // end else if (IsIL() || IsNoMetadata())
else if (IsNDirect())
{
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.
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.
//---------------------------------------------------------------------------
{
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,
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));
}
}
#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++;
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;
// 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);
{
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
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();
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"))
{
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
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);
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));
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()
}
[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
}
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);
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)
{
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()
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
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);
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);
+ }
+ }
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
- <ReferenceSystemPrivateCoreLib>true</ReferenceSystemPrivateCoreLib>
<CLRTestPriority>1</CLRTestPriority>
</PropertyGroup>
<Import Project="$([MSBuild]::GetPathOfFileAbove(Interop.settings.targets))" />
<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>
<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>
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
{
}
/// <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;
}
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
{