From 6dde4a45ddf645a0c6464c8b81ef1cd043d0e6ca Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Sat, 8 May 2021 00:32:08 -0700 Subject: [PATCH 1/8] Add UnmanagedCallConvAttribute --- src/coreclr/vm/wellknownattributes.h | 3 +++ .../System.Private.CoreLib.Shared.projitems | 1 + .../UnmanagedCallConvAttribute.cs | 25 +++++++++++++++++++ .../ref/System.Runtime.InteropServices.cs | 6 +++++ 4 files changed, 35 insertions(+) create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/UnmanagedCallConvAttribute.cs diff --git a/src/coreclr/vm/wellknownattributes.h b/src/coreclr/vm/wellknownattributes.h index 8da3de283e1c61..b247d62e052d15 100644 --- a/src/coreclr/vm/wellknownattributes.h +++ b/src/coreclr/vm/wellknownattributes.h @@ -28,6 +28,7 @@ enum class WellKnownAttribute : DWORD IsByRefLike, PrimaryInteropAssembly, ManagedToNativeComInteropStub, + UnmanagedCallConv, UnmanagedCallersOnly, NativeCallableInternal, // This is needed to support MCG scenarios TypeIdentifier, @@ -88,6 +89,8 @@ inline const char *GetWellKnownAttributeName(WellKnownAttribute attribute) return "System.Runtime.InteropServices.PrimaryInteropAssemblyAttribute"; case WellKnownAttribute::ManagedToNativeComInteropStub: return "System.Runtime.InteropServices.ManagedToNativeComInteropStubAttribute"; + case WellKnownAttribute::UnmanagedCallConv: + return "System.Runtime.InteropServices.UnmanagedCallConvAttribute"; case WellKnownAttribute::UnmanagedCallersOnly: return "System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute"; case WellKnownAttribute::NativeCallableInternal: diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 6272a772fe21c2..0487d5f07646ff 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -796,6 +796,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/UnmanagedCallConvAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/UnmanagedCallConvAttribute.cs new file mode 100644 index 00000000000000..cdbc064cf11456 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/UnmanagedCallConvAttribute.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Runtime.InteropServices +{ + /// + /// Provides an equivalent to for native + /// functions declared in .NET. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] + public sealed class UnmanagedCallConvAttribute : Attribute + { + public UnmanagedCallConvAttribute() + { + } + + /// + /// Types indicating calling conventions for the unmanaged target. + /// + /// + /// If null, the semantics are identical to CallingConvention.Winapi. + /// + public Type[]? CallConvs; + } +} \ No newline at end of file diff --git a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs index 7ea73701620a3b..b9179b6de00243 100644 --- a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs +++ b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs @@ -1091,6 +1091,12 @@ public static void RegisterForTrackerSupport(ComWrappers instance) { } public static void RegisterForMarshalling(ComWrappers instance) { } protected static void GetIUnknownImpl(out System.IntPtr fpQueryInterface, out System.IntPtr fpAddRef, out System.IntPtr fpRelease) { throw null; } } + [System.AttributeUsageAttribute(System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)] + public sealed class UnmanagedCallConvAttribute : System.Attribute + { + public UnmanagedCallConvAttribute() { } + public System.Type[]? CallConvs; + } [System.AttributeUsageAttribute(System.AttributeTargets.Method, Inherited = false)] public sealed class UnmanagedCallersOnlyAttribute : System.Attribute { From d68a3b9e007cfec37235c1b49e1381a74cf41aa6 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Sat, 8 May 2021 00:33:34 -0700 Subject: [PATCH 2/8] Get calling convention from UnmanagedCallConv --- src/coreclr/inc/corcompile.h | 3 ++ src/coreclr/vm/callconvbuilder.cpp | 68 +++++++++++++++++++++++++++--- src/coreclr/vm/callconvbuilder.hpp | 14 ++++++ src/coreclr/vm/compile.cpp | 8 ++++ src/coreclr/vm/compile.h | 2 + src/coreclr/vm/dllimport.cpp | 51 ++++++++++++++++++++-- src/coreclr/vm/dllimport.h | 9 ++-- src/coreclr/vm/jitinterface.cpp | 5 +++ src/coreclr/vm/method.cpp | 32 ++++++++++++++ src/coreclr/vm/method.hpp | 1 + src/coreclr/zap/zapinfo.cpp | 7 +++ 11 files changed, 187 insertions(+), 13 deletions(-) diff --git a/src/coreclr/inc/corcompile.h b/src/coreclr/inc/corcompile.h index 8226fba3d8f0a0..2fb5d6fabc7f96 100644 --- a/src/coreclr/inc/corcompile.h +++ b/src/coreclr/inc/corcompile.h @@ -1713,6 +1713,9 @@ class ICorCompileInfo // to 1 on the clone. The buffer has to be large enough to hold the stub object and the code virtual HRESULT GetStubClone(void *pStub, BYTE *pBuffer, DWORD dwBufferSize) = 0; + // true if the method has [UnmanagedCallConvAttribute] + virtual BOOL IsUnmanagedCallConvMethod(CORINFO_METHOD_HANDLE handle) = 0; + // true if the method has [UnmanagedCallersOnlyAttribute] virtual BOOL IsUnmanagedCallersOnlyMethod(CORINFO_METHOD_HANDLE handle) = 0; diff --git a/src/coreclr/vm/callconvbuilder.cpp b/src/coreclr/vm/callconvbuilder.cpp index 8146289c7a667e..b56cd1695e4cb7 100644 --- a/src/coreclr/vm/callconvbuilder.cpp +++ b/src/coreclr/vm/callconvbuilder.cpp @@ -387,32 +387,86 @@ HRESULT CallConv::TryGetUnmanagedCallingConventionFromModOpt( #ifndef CROSSGEN_COMPILE namespace { - bool TryGetCallingConventionFromTypeArray(_In_ CaValue* arrayOfTypes, _Out_ CorInfoCallConvExtension* callConv) + bool TryGetCallingConventionFromTypeArray(_In_ CaValue* arrayOfTypes, _Inout_ CallConvBuilder* builder) { CONTRACTL { STANDARD_VM_CHECK; PRECONDITION(arrayOfTypes != NULL); - PRECONDITION(callConv != NULL); + PRECONDITION(builder != NULL); } CONTRACTL_END - CallConvBuilder builder; for (ULONG i = 0; i < arrayOfTypes->arr.length; i++) { CaValue& typeNameValue = arrayOfTypes->arr[i]; - if (!builder.AddFullyQualifiedTypeName(typeNameValue.str.cbStr, typeNameValue.str.pStr)) + if (!builder->AddFullyQualifiedTypeName(typeNameValue.str.cbStr, typeNameValue.str.pStr)) { // We found a second base calling convention. return false; } } - *callConv = builder.GetCurrentCallConv(); return true; } } +HRESULT CallConv::TryGetCallingConventionFromUnmanagedCallConv( + _In_ MethodDesc* pMD, + _Inout_ CallConvBuilder* builder, + _Out_opt_ UINT* errorResID) +{ + CONTRACTL + { + STANDARD_VM_CHECK; + PRECONDITION(pMD != NULL); + PRECONDITION(builder != NULL); + } + CONTRACTL_END; + + BYTE *pData = NULL; + LONG cData = 0; + + // System.Runtime.InteropServices.UnmanagedCallConvAttribute + HRESULT hr = pMD->GetCustomAttribute(WellKnownAttribute::UnmanagedCallConv, (const VOID **)(&pData), (ULONG *)&cData); + if (hr != S_OK) // GetCustomAttribute returns S_FALSE if the method does not have the attribute + return hr; + + _ASSERTE(cData > 0); + CustomAttributeParser ca(pData, cData); + + // CallConvs named argument + CaTypeCtor callConvsType(SERIALIZATION_TYPE_SZARRAY, SERIALIZATION_TYPE_TYPE, SERIALIZATION_TYPE_UNDEFINED, NULL, 0); + CaNamedArg callConvsArg; + callConvsArg.Init("CallConvs", SERIALIZATION_TYPE_SZARRAY, callConvsType); + + InlineFactory, 4> caValueArrayFactory; + DomainAssembly* domainAssembly = pMD->GetLoaderModule()->GetDomainAssembly(); + IfFailThrow(Attribute::ParseAttributeArgumentValues( + pData, + cData, + &caValueArrayFactory, + NULL, + 0, + &callConvsArg, + 1, + domainAssembly)); + + // Value isn't defined + if (callConvsArg.val.type.tag == SERIALIZATION_TYPE_UNDEFINED) + return S_FALSE; + + if (!TryGetCallingConventionFromTypeArray(&callConvsArg.val, builder)) + { + if (errorResID != NULL) + *errorResID = IDS_EE_MULTIPLE_CALLCONV_UNSUPPORTED; + + return COR_E_INVALIDPROGRAM; + } + + return S_OK; +} + bool CallConv::TryGetCallingConventionFromUnmanagedCallersOnly(_In_ MethodDesc* pMD, _Out_ CorInfoCallConvExtension* pCallConv) { STANDARD_VM_CONTRACT; @@ -484,12 +538,14 @@ bool CallConv::TryGetCallingConventionFromUnmanagedCallersOnly(_In_ MethodDesc* } else { - if (!TryGetCallingConventionFromTypeArray(&namedArgs[0].val, &callConvLocal)) + CallConvBuilder builder; + if (!TryGetCallingConventionFromTypeArray(&namedArgs[0].val, &builder)) { // We found a second base calling convention. return false; } + callConvLocal = builder.GetCurrentCallConv(); if (callConvLocal == CallConvBuilder::UnsetValue) { callConvLocal = CallConv::GetDefaultUnmanagedCallingConvention(); diff --git a/src/coreclr/vm/callconvbuilder.hpp b/src/coreclr/vm/callconvbuilder.hpp index 6f479012ac7cc9..6f197d92029dd4 100644 --- a/src/coreclr/vm/callconvbuilder.hpp +++ b/src/coreclr/vm/callconvbuilder.hpp @@ -80,6 +80,20 @@ namespace CallConv _Inout_ CallConvBuilder *builder, _Out_ UINT *errorResID); + //------------------------------------------------------------------------- + // Gets the calling convention from the UnmanagedCallConv attribute + // + // Returns: + // S_OK - No errors + // S_FALSE - UnmanagedCallConv or UnmanagedCallConv.CallConvs not specified + // COR_E_INVALIDPROGRAM - Program is considered invalid (more + // than one calling convention specified) + //------------------------------------------------------------------------- + HRESULT TryGetCallingConventionFromUnmanagedCallConv( + _In_ MethodDesc* pMD, + _Inout_ CallConvBuilder* builder, + _Out_opt_ UINT* errorResID); + //------------------------------------------------------------------------- // Gets the unmanaged calling convention from the UnmanagedCallersOnly attribute. // diff --git a/src/coreclr/vm/compile.cpp b/src/coreclr/vm/compile.cpp index 4edd220728a0ef..486844c7f79a48 100644 --- a/src/coreclr/vm/compile.cpp +++ b/src/coreclr/vm/compile.cpp @@ -968,6 +968,14 @@ BOOL CEEPreloader::DoesMethodNeedRestoringBeforePrestubIsRun( return FALSE; } +BOOL CEECompileInfo::IsUnmanagedCallConvMethod(CORINFO_METHOD_HANDLE handle) +{ + WRAPPER_NO_CONTRACT; + + MethodDesc * pMethod = GetMethod(handle); + return pMethod->HasUnmanagedCallConvAttribute(); +} + BOOL CEECompileInfo::IsUnmanagedCallersOnlyMethod(CORINFO_METHOD_HANDLE handle) { WRAPPER_NO_CONTRACT; diff --git a/src/coreclr/vm/compile.h b/src/coreclr/vm/compile.h index a04e19307f3e9b..6280497d13cba7 100644 --- a/src/coreclr/vm/compile.h +++ b/src/coreclr/vm/compile.h @@ -280,6 +280,8 @@ class CEECompileInfo : public ICorCompileInfo BOOL IsEmptyString(mdString token, CORINFO_MODULE_HANDLE module); + BOOL IsUnmanagedCallConvMethod(CORINFO_METHOD_HANDLE handle); + BOOL IsUnmanagedCallersOnlyMethod(CORINFO_METHOD_HANDLE handle); BOOL IsCachingOfInliningHintsEnabled() diff --git a/src/coreclr/vm/dllimport.cpp b/src/coreclr/vm/dllimport.cpp index b6a8a8031a225a..dd113fccb48a42 100644 --- a/src/coreclr/vm/dllimport.cpp +++ b/src/coreclr/vm/dllimport.cpp @@ -2780,7 +2780,7 @@ void PInvokeStaticSigInfo::DllImportInit(_In_ MethodDesc* pMD, _Outptr_opt_ LPCU mdModuleRef modref = mdModuleRefNil; if (FAILED(pInternalImport->GetPinvokeMap(pMD->GetMemberDef(), (DWORD*)&mappingFlags, ppEntryPointName, &modref))) { - InitCallConv(CallConvWinApiSentinel, pMD->IsVarArg()); + InitCallConv(CallConvWinApiSentinel, pMD); return; } @@ -2798,7 +2798,7 @@ void PInvokeStaticSigInfo::DllImportInit(_In_ MethodDesc* pMD, _Outptr_opt_ LPCU } // m_callConv - InitCallConv(GetCallConvValueForPInvokeCallConv((CorPinvokeMap)(mappingFlags & pmCallConvMask)), pMD->IsVarArg()); + InitCallConv(GetCallConvValueForPInvokeCallConv((CorPinvokeMap)(mappingFlags & pmCallConvMask)), pMD); // m_bestFit CorPinvokeMap bestFitMask = (CorPinvokeMap)(mappingFlags & pmBestFitMask); @@ -2954,6 +2954,39 @@ CorInfoCallConvExtension GetDefaultCallConv(BOOL bIsVarArg) return bIsVarArg ? CorInfoCallConvExtension::C : CallConv::GetDefaultUnmanagedCallingConvention(); } +void PInvokeStaticSigInfo::InitCallConv(_In_ CorInfoCallConvExtension callConv, _In_ MethodDesc* pMD) +{ + CONTRACTL + { + STANDARD_VM_CHECK; + PRECONDITION(pMD != NULL); + } + CONTRACTL_END; + +#ifdef CROSSGEN_COMPILE + _ASSERTE_MSG(!pMD->HasUnmanagedCallConvAttribute(), "UnmanagedCallConv methods are not supported in crossgen and should be rejected before getting here."); +#else + // If the calling convention has not been determined yet, check the UnmanagedCallConv attribute + if (callConv == CallConvWinApiSentinel) + { + CallConvBuilder builder; + UINT errorResID = 0; + + // System.Runtime.InteropServices.UnmanagedCallConvAttribute + HRESULT hr = CallConv::TryGetCallingConventionFromUnmanagedCallConv(pMD, &builder, &errorResID); + if (FAILED(hr)) + { + // Use a generic error message for P/Invokes or UnmanagedFunction if no specific one was provided + ThrowError(errorResID == 0 ? IDS_EE_NDIRECT_BADNATL : errorResID); + } + + callConv = builder.GetCurrentCallConv(); + } +#endif // CROSSGEN_COMPILE + + InitCallConv(callConv, pMD->IsVarArg()); +} + void PInvokeStaticSigInfo::InitCallConv(CorInfoCallConvExtension callConv, BOOL bIsVarArg) { STANDARD_VM_CONTRACT; @@ -3045,14 +3078,26 @@ CorInfoCallConvExtension NDirect::GetCallingConvention_IgnoreErrors(_In_ MethodD if (callConv != CallConvWinApiSentinel) return callConv; } + +#ifdef CROSSGEN_COMPILE + _ASSERTE_MSG(!pMD->HasUnmanagedCallConvAttribute(), "UnmanagedCallConv methods are not supported in crossgen and should be rejected before getting here."); +#else + // System.Runtime.InteropServices.UnmanagedCallConvAttribute + CallConvBuilder unmanagedCallConvBuilder; + (void)CallConv::TryGetCallingConventionFromUnmanagedCallConv(pMD, &unmanagedCallConvBuilder, NULL); + callConv = unmanagedCallConvBuilder.GetCurrentCallConv(); + if (callConv != CallConvWinApiSentinel) + return callConv; +#endif // CROSSGEN_COMPILE } const Signature& sig = pMD->GetSignature(); Module* module = pMD->GetModule(); - // modopts CallConvBuilder builder; UINT errorResID; + + // modopts (void)CallConv::TryGetUnmanagedCallingConventionFromModOpt(GetScopeHandle(module), sig.GetRawSig(), sig.GetRawSigLen(), &builder, &errorResID); callConv = builder.GetCurrentCallConv(); if (callConv != CallConvWinApiSentinel) diff --git a/src/coreclr/vm/dllimport.h b/src/coreclr/vm/dllimport.h index d713cb9bb5cc3d..978987d3ecffd3 100644 --- a/src/coreclr/vm/dllimport.h +++ b/src/coreclr/vm/dllimport.h @@ -315,11 +315,12 @@ struct PInvokeStaticSigInfo PInvokeStaticSigInfo(_In_ MethodDesc* pMD, _Outptr_opt_ LPCUTF8 *pLibName, _Outptr_opt_ LPCUTF8 *pEntryPointName); private: - void ThrowError(WORD errorResourceID); - void InitCallConv(CorInfoCallConvExtension callConv, BOOL bIsVarArg); + void ThrowError(_In_ WORD errorResourceID); + void InitCallConv(_In_ CorInfoCallConvExtension callConv, _In_ MethodDesc* pMD); + void InitCallConv(_In_ CorInfoCallConvExtension callConv, _In_ BOOL bIsVarArg); void DllImportInit(_In_ MethodDesc* pMD, _Outptr_opt_ LPCUTF8 *pLibName, _Outptr_opt_ LPCUTF8 *pEntryPointName); - void PreInit(Module* pModule, MethodTable *pClass); - void PreInit(MethodDesc* pMD); + void PreInit(_In_ Module* pModule, _In_ MethodTable *pClass); + void PreInit(_In_ MethodDesc* pMD); private: enum diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 0cbee305ddb33e..42ccb97983b98b 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -9997,6 +9997,11 @@ namespace _ASSERTE(pMD->IsNDirect() || pMD->HasUnmanagedCallersOnlyAttribute()); if (pMD->IsNDirect()) { +#ifdef CROSSGEN_COMPILE + // Return CorInfoCallConvExtension::Managed to indicate that crossgen does not support handling UnmanagedCallConv + if (pMD->HasUnmanagedCallConvAttribute()) + return CorInfoCallConvExtension::Managed; +#endif // CROSSGEN_COMPILE if (pSuppressGCTransition) { *pSuppressGCTransition = pMD->ShouldSuppressGCTransition(); diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index 75981105f3cdfc..6381db39d67da7 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -5423,6 +5423,38 @@ void NDirectMethodDesc::InitEarlyBoundNDirectTarget() } #endif // !CROSSGEN_COMPILE +//******************************************************************************* +BOOL MethodDesc::HasUnmanagedCallConvAttribute() +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + FORBID_FAULT; + } + CONTRACTL_END; + + MethodDesc* tgt = this; + if (IsILStub()) + { + // From the IL stub, determine if the actual target has been + // marked with UnmanagedCallConv. + PTR_DynamicMethodDesc ilStubMD = AsDynamicMethodDesc(); + PTR_ILStubResolver ilStubResolver = ilStubMD->GetILStubResolver(); + tgt = ilStubResolver->GetStubTargetMethodDesc(); + if (tgt == nullptr) + return FALSE; + } + + _ASSERTE(tgt != nullptr); + HRESULT hr = tgt->GetCustomAttribute( + WellKnownAttribute::UnmanagedCallConv, + nullptr, + nullptr); + + return (hr == S_OK) ? TRUE : FALSE; +} + //******************************************************************************* BOOL MethodDesc::HasUnmanagedCallersOnlyAttribute() { diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index b536101c5dd91a..76bc1a2fb68849 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -668,6 +668,7 @@ class MethodDesc return GetMethodTable()->IsInterface(); } + BOOL HasUnmanagedCallConvAttribute(); BOOL HasUnmanagedCallersOnlyAttribute(); BOOL ShouldSuppressGCTransition(); diff --git a/src/coreclr/zap/zapinfo.cpp b/src/coreclr/zap/zapinfo.cpp index 347f357c7de363..e69335bb110a0b 100644 --- a/src/coreclr/zap/zapinfo.cpp +++ b/src/coreclr/zap/zapinfo.cpp @@ -421,6 +421,13 @@ void ZapInfo::CompileMethod() ThrowHR(E_NOTIMPL); } + if (GetCompileInfo()->IsUnmanagedCallConvMethod(m_currentMethodHandle)) + { + if (m_zapper->m_pOpt->m_verbose) + m_zapper->Warning(W("ReadyToRun: Methods with UnmanagedCallConvAttribute not implemented\n")); + ThrowHR(E_NOTIMPL); + } + m_currentMethodInfo = CORINFO_METHOD_INFO(); if (!getMethodInfo(m_currentMethodHandle, &m_currentMethodInfo)) { From c40d4e59d38049e0931a46bca4339e710cd60cdd Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 10 May 2021 19:41:35 -0700 Subject: [PATCH 3/8] Handle UnmanagedCallConv in crossgen2 --- .../tools/Common/JitInterface/CorInfoImpl.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 65137fad8c029d..46baad4876f788 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -637,12 +637,12 @@ private void Get_CORINFO_SIG_INFO(MethodDesc method, CORINFO_SIG_INFO* sig, bool } } - private CorInfoCallConvExtension GetUnmanagedCallingConventionFromAttribute(CustomAttributeValue unmanagedCallersOnlyAttribute) + private CorInfoCallConvExtension GetUnmanagedCallingConventionFromAttribute(CustomAttributeValue attributeWithCallConvsArray) { CorInfoCallConvExtension callConv = (CorInfoCallConvExtension)PlatformDefaultUnmanagedCallingConvention(); ImmutableArray> callConvArray = default; - foreach (var arg in unmanagedCallersOnlyAttribute.NamedArguments) + foreach (var arg in attributeWithCallConvsArray.NamedArguments) { if (arg.Name == "CallConvs") { @@ -1262,7 +1262,21 @@ private CorInfoCallConvExtension GetUnmanagedCallConv(MethodDesc methodDesc, out MethodSignatureFlags unmanagedCallConv = methodDesc.GetPInvokeMethodMetadata().Flags.UnmanagedCallingConvention; if (unmanagedCallConv == MethodSignatureFlags.None) + { + MethodDesc methodDescLocal = methodDesc; + if (methodDesc is IL.Stubs.PInvokeTargetNativeMethod rawPInvoke) + { + methodDescLocal = rawPInvoke.Target; + } + + CustomAttributeValue? unmanagedCallConvAttribute = ((EcmaMethod)methodDescLocal).GetDecodedCustomAttribute("System.Runtime.InteropServices", "UnmanagedCallConvAttribute"); + if (unmanagedCallConvAttribute != null) + { + return GetUnmanagedCallingConventionFromAttribute(unmanagedCallConvAttribute.Value); + } + unmanagedCallConv = PlatformDefaultUnmanagedCallingConvention(); + } // Verify that it is safe to convert MethodSignatureFlags.UnmanagedCallingConvention to CorInfoCallConvExtension via a simple cast Debug.Assert((int)CorInfoCallConvExtension.C == (int)MethodSignatureFlags.UnmanagedCallingConventionCdecl); From 4ef56e645efc1566dcadf9df665379ad9d64672c Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 17 May 2021 11:19:10 -0700 Subject: [PATCH 4/8] Handle CallConvSuppressGCTransition on UnmanagedCallConv --- src/coreclr/vm/dllimport.cpp | 164 ++++++++++++++++++++++++++------ src/coreclr/vm/dllimport.h | 33 +++++-- src/coreclr/vm/jitinterface.cpp | 10 +- src/coreclr/vm/method.cpp | 14 ++- 4 files changed, 172 insertions(+), 49 deletions(-) diff --git a/src/coreclr/vm/dllimport.cpp b/src/coreclr/vm/dllimport.cpp index dd113fccb48a42..419514219d6f2a 100644 --- a/src/coreclr/vm/dllimport.cpp +++ b/src/coreclr/vm/dllimport.cpp @@ -1371,7 +1371,8 @@ class PInvoke_ILStubState : public ILStubState { dwStubFlags |= NDIRECTSTUB_FL_STUB_HAS_THIS; } - if (TargetSuppressGCTransition(dwStubFlags, pTargetMD)) + if ((dwStubFlags & NDIRECTSTUB_FL_SUPPRESSGCTRANSITION) == 0 + && TargetSuppressGCTransition(dwStubFlags, pTargetMD)) { dwStubFlags |= NDIRECTSTUB_FL_SUPPRESSGCTRANSITION; } @@ -2954,7 +2955,7 @@ CorInfoCallConvExtension GetDefaultCallConv(BOOL bIsVarArg) return bIsVarArg ? CorInfoCallConvExtension::C : CallConv::GetDefaultUnmanagedCallingConvention(); } -void PInvokeStaticSigInfo::InitCallConv(_In_ CorInfoCallConvExtension callConv, _In_ MethodDesc* pMD) +void PInvokeStaticSigInfo::InitCallConv(_In_ CorInfoCallConvExtension callConv, _In_ MethodDesc *pMD) { CONTRACTL { @@ -2980,7 +2981,14 @@ void PInvokeStaticSigInfo::InitCallConv(_In_ CorInfoCallConvExtension callConv, ThrowError(errorResID == 0 ? IDS_EE_NDIRECT_BADNATL : errorResID); } - callConv = builder.GetCurrentCallConv(); + if (hr == S_OK) + { + callConv = builder.GetCurrentCallConv(); + if (builder.IsCurrentCallConvModSet(CallConvBuilder::CALL_CONV_MOD_SUPPRESSGCTRANSITION)) + { + SetShouldSuppressGCTransition(TRUE); + } + } } #endif // CROSSGEN_COMPILE @@ -3036,47 +3044,124 @@ void PInvokeStaticSigInfo::ThrowError(WORD errorResourceID) COMPlusThrow(kTypeLoadException, errorResourceID); } -CorInfoCallConvExtension NDirect::GetCallingConvention_IgnoreErrors(_In_ MethodDesc* pMD) +namespace +{ + bool HasSuppressGCTransitionAttribute(_In_ MethodDesc* pMD) + { + LIMITED_METHOD_CONTRACT; + + HRESULT hr = pMD->GetCustomAttribute( + WellKnownAttribute::SuppressGCTransition, + nullptr, + nullptr); + return hr == S_OK; + } + + bool TryGetCallingConventionFromPInvokeMetadata(_In_ MethodDesc* pMD, _Out_ CorInfoCallConvExtension* callConv) + { + CONTRACTL + { + STANDARD_VM_CHECK; + PRECONDITION(pMD != NULL); + PRECONDITION(pMD->IsNDirect()); + PRECONDITION(callConv != NULL); + } + CONTRACTL_END; + + CorInfoCallConvExtension callConvLocal; + IMDInternalImport* pInternalImport = pMD->GetMDImport(); + CorPinvokeMap mappingFlags = pmMaxValue; + HRESULT hr = pInternalImport->GetPinvokeMap(pMD->GetMemberDef(), (DWORD*)&mappingFlags, NULL /*pszImportName*/, NULL /*pmrImportDLL*/); + if (FAILED(hr)) + return false; + + callConvLocal = GetCallConvValueForPInvokeCallConv((CorPinvokeMap)(mappingFlags & pmCallConvMask)); + if (callConvLocal != CallConvWinApiSentinel) + { + *callConv = callConvLocal; + return true; + } + + return false; + } + + bool TryGetCallingConventionFromUnmanagedFunctionPointer(_In_ MethodTable* pMT, _Out_ CorInfoCallConvExtension* callConv) + { + CONTRACTL + { + STANDARD_VM_CHECK; + PRECONDITION(pMT != NULL); + PRECONDITION(pMT->IsDelegate()); + PRECONDITION(callConv != NULL); + } + CONTRACTL_END; + + BYTE* pData = NULL; + LONG cData = 0; + HRESULT hr = pMT->GetCustomAttribute(WellKnownAttribute::UnmanagedFunctionPointer, (const VOID **)(&pData), (ULONG *)&cData); + if (hr != S_OK) + return false; + + _ASSERTE(cData > 0); + CustomAttributeParser ca(pData, cData); + CorInfoCallConvExtension callConvLocal; + hr = ParseCallingConventionFromAttributeConstructor(ca, &callConvLocal); + if (SUCCEEDED(hr) && callConvLocal != CallConvWinApiSentinel) + { + *callConv = callConvLocal; + return true; + } + + return false; + } +} + +void NDirect::GetCallingConvention_IgnoreErrors(_In_ MethodDesc* pMD, _Out_opt_ CorInfoCallConvExtension* callConv, _Out_opt_ bool* suppressGCTransition) { CONTRACTL { STANDARD_VM_CHECK; PRECONDITION(pMD != NULL); PRECONDITION(pMD->IsNDirect()); + PRECONDITION(callConv != NULL || suppressGCTransition != NULL); } CONTRACTL_END; + if (suppressGCTransition != NULL) + { + *suppressGCTransition = HasSuppressGCTransitionAttribute(pMD); + + // Caller only cares about SuppressGCTransition and we have already determined it is true. + if (callConv == NULL && *suppressGCTransition) + return; + } + // This method intentionally does not check that any calling convention specified through // attributes match that in the signature. We just return once a non-sentinel calling // convention is found. - CorInfoCallConvExtension callConv; + CorInfoCallConvExtension callConvLocal; MethodTable* pMT = pMD->GetMethodTable(); if (pMT->IsDelegate()) { + if (callConv == NULL) + return; + // System.Runtime.InteropServices.UnmanagedFunctionPointerAttribute - BYTE* pData = NULL; - LONG cData = 0; - HRESULT hr = pMT->GetCustomAttribute(WellKnownAttribute::UnmanagedFunctionPointer, (const VOID **)(&pData), (ULONG *)&cData); - if (hr == S_OK) + if (TryGetCallingConventionFromUnmanagedFunctionPointer(pMT, &callConvLocal)) { - _ASSERTE(cData > 0); - CustomAttributeParser ca(pData, cData); - hr = ParseCallingConventionFromAttributeConstructor(ca, &callConv); - if (SUCCEEDED(hr) && callConv != CallConvWinApiSentinel) - return callConv; + *callConv = callConvLocal; + return; } } else { // P/Invoke metadata - IMDInternalImport* pInternalImport = pMD->GetMDImport(); - CorPinvokeMap mappingFlags = pmMaxValue; - HRESULT hr = pInternalImport->GetPinvokeMap(pMD->GetMemberDef(), (DWORD*)&mappingFlags, NULL /*pszImportName*/, NULL /*pmrImportDLL*/); - if (SUCCEEDED(hr)) + if (TryGetCallingConventionFromPInvokeMetadata(pMD, &callConvLocal)) { - callConv = GetCallConvValueForPInvokeCallConv((CorPinvokeMap)(mappingFlags & pmCallConvMask)); - if (callConv != CallConvWinApiSentinel) - return callConv; + if (callConv != NULL) + *callConv = callConvLocal; + + return; } #ifdef CROSSGEN_COMPILE @@ -3084,13 +3169,31 @@ CorInfoCallConvExtension NDirect::GetCallingConvention_IgnoreErrors(_In_ MethodD #else // System.Runtime.InteropServices.UnmanagedCallConvAttribute CallConvBuilder unmanagedCallConvBuilder; - (void)CallConv::TryGetCallingConventionFromUnmanagedCallConv(pMD, &unmanagedCallConvBuilder, NULL); - callConv = unmanagedCallConvBuilder.GetCurrentCallConv(); - if (callConv != CallConvWinApiSentinel) - return callConv; + if (CallConv::TryGetCallingConventionFromUnmanagedCallConv(pMD, &unmanagedCallConvBuilder, NULL /*errorResID*/) == S_OK) + { + if (suppressGCTransition != NULL) + { + (*suppressGCTransition) |= unmanagedCallConvBuilder.IsCurrentCallConvModSet(CallConvBuilder::CALL_CONV_MOD_SUPPRESSGCTRANSITION); + } + + callConvLocal = unmanagedCallConvBuilder.GetCurrentCallConv(); + if (callConvLocal != CallConvWinApiSentinel) + { + if (callConv != NULL) + *callConv = callConvLocal; + + return; + } + } #endif // CROSSGEN_COMPILE + + // Caller only cares about SuppressGCTransition - we have checked SuppressGCTransition and UnmanagedCallConv + if (callConv == NULL) + return; } + _ASSERTE(callConv != NULL); + const Signature& sig = pMD->GetSignature(); Module* module = pMD->GetModule(); @@ -3099,11 +3202,14 @@ CorInfoCallConvExtension NDirect::GetCallingConvention_IgnoreErrors(_In_ MethodD // modopts (void)CallConv::TryGetUnmanagedCallingConventionFromModOpt(GetScopeHandle(module), sig.GetRawSig(), sig.GetRawSigLen(), &builder, &errorResID); - callConv = builder.GetCurrentCallConv(); - if (callConv != CallConvWinApiSentinel) - return callConv; + callConvLocal = builder.GetCurrentCallConv(); + if (callConvLocal != CallConvWinApiSentinel) + { + *callConv = callConvLocal; + return; + } - return GetDefaultCallConv(pMD->IsVarArg()); + *callConv = GetDefaultCallConv(pMD->IsVarArg()); } //--------------------------------------------------------- diff --git a/src/coreclr/vm/dllimport.h b/src/coreclr/vm/dllimport.h index 978987d3ecffd3..8b34526ddbea16 100644 --- a/src/coreclr/vm/dllimport.h +++ b/src/coreclr/vm/dllimport.h @@ -56,14 +56,15 @@ struct StubSigDesc class NDirect { public: - // Get the calling convention for a method by checking: + // Get the calling convention and whether to suppress GC transition for a method by checking: + // - SuppressGCTransition attribute // - For delegates: UnmanagedFunctionPointer attribute // - For non-delegates: P/Invoke metadata // - Any modopts encoded in the method signature // If no calling convention is specified, the default calling convention is returned // This function ignores any errors when reading attributes/metadata, treating them as // if no calling convention was specified through that mechanism. - static CorInfoCallConvExtension GetCallingConvention_IgnoreErrors(_In_ MethodDesc* pMD); + static void GetCallingConvention_IgnoreErrors(_In_ MethodDesc* pMD, _Out_opt_ CorInfoCallConvExtension* callConv, _Out_opt_ bool* suppressGCTransition); //--------------------------------------------------------- // Does a class or method have a NAT_L CustomAttribute? @@ -325,7 +326,7 @@ struct PInvokeStaticSigInfo private: enum { - PINVOKE_STATIC_SIGINFO_IS_STATIC = 0x0001, + PINVOKE_STATIC_SIGINFO_SUPPRESS_GC_TRANSITION = 0x0001, PINVOKE_STATIC_SIGINFO_THROW_ON_UNMAPPABLE_CHAR = 0x0002, PINVOKE_STATIC_SIGINFO_BEST_FIT = 0x0004, @@ -343,9 +344,20 @@ struct PInvokeStaticSigInfo DWORD GetStubFlags() const { WRAPPER_NO_CONTRACT; - return (GetThrowOnUnmappableChar() ? NDIRECTSTUB_FL_THROWONUNMAPPABLECHAR : 0) | - (GetBestFitMapping() ? NDIRECTSTUB_FL_BESTFIT : 0) | - (IsDelegateInterop() ? NDIRECTSTUB_FL_DELEGATE : 0); + DWORD flags = 0; + if (GetThrowOnUnmappableChar()) + flags |= NDIRECTSTUB_FL_THROWONUNMAPPABLECHAR; + + if (GetBestFitMapping()) + flags |= NDIRECTSTUB_FL_BESTFIT; + + if (IsDelegateInterop()) + flags |= NDIRECTSTUB_FL_DELEGATE; + + if (ShouldSuppressGCTransition()) + flags |= NDIRECTSTUB_FL_SUPPRESSGCTRANSITION; + + return flags; } Module* GetModule() const { LIMITED_METHOD_CONTRACT; return m_pModule; } BOOL IsDelegateInterop() const { LIMITED_METHOD_CONTRACT; return m_wFlags & PINVOKE_STATIC_SIGINFO_IS_DELEGATE_INTEROP; } @@ -357,6 +369,7 @@ struct PInvokeStaticSigInfo public: // private getters BOOL GetThrowOnUnmappableChar() const { LIMITED_METHOD_CONTRACT; return m_wFlags & PINVOKE_STATIC_SIGINFO_THROW_ON_UNMAPPABLE_CHAR; } BOOL GetBestFitMapping() const { LIMITED_METHOD_CONTRACT; return m_wFlags & PINVOKE_STATIC_SIGINFO_BEST_FIT; } + BOOL ShouldSuppressGCTransition() const { LIMITED_METHOD_CONTRACT; return m_wFlags & PINVOKE_STATIC_SIGINFO_SUPPRESS_GC_TRANSITION; } private: // setters void SetThrowOnUnmappableChar(BOOL throwOnUnmappableChar) @@ -375,6 +388,14 @@ struct PInvokeStaticSigInfo else m_wFlags &= ~PINVOKE_STATIC_SIGINFO_BEST_FIT; } + void SetShouldSuppressGCTransition(BOOL suppress) + { + LIMITED_METHOD_CONTRACT; + if (suppress) + m_wFlags |= PINVOKE_STATIC_SIGINFO_SUPPRESS_GC_TRANSITION; + else + m_wFlags &= ~PINVOKE_STATIC_SIGINFO_SUPPRESS_GC_TRANSITION; + } void SetIsDelegateInterop(BOOL delegateInterop) { LIMITED_METHOD_CONTRACT; diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 42ccb97983b98b..fcb973e69e070f 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -5368,7 +5368,7 @@ void CEEInfo::getCallInfo( // of shared generic code calling a shared generic implementation method, which should be rare. // // An alternative design would be to add a new generic dictionary entry kind to hold the MethodDesc - // of the constrained target instead, and use that in some circumstances; however, implementation of + // of the constrained target instead, and use that in some circumstances; however, implementation of // that design requires refactoring variuos parts of the JIT interface as well as // TryResolveConstraintMethodApprox. In particular we would need to be abled to embed a constrained lookup // via EmbedGenericHandle, as well as decide in TryResolveConstraintMethodApprox if the call can be made @@ -10002,12 +10002,10 @@ namespace if (pMD->HasUnmanagedCallConvAttribute()) return CorInfoCallConvExtension::Managed; #endif // CROSSGEN_COMPILE - if (pSuppressGCTransition) - { - *pSuppressGCTransition = pMD->ShouldSuppressGCTransition(); - } - return NDirect::GetCallingConvention_IgnoreErrors(pMD); + CorInfoCallConvExtension unmanagedCallConv; + NDirect::GetCallingConvention_IgnoreErrors(pMD, &unmanagedCallConv, pSuppressGCTransition); + return unmanagedCallConv; } else { diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index 6381db39d67da7..5f19b8ce3f49eb 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -5492,9 +5492,9 @@ BOOL MethodDesc::ShouldSuppressGCTransition() { CONTRACTL { - NOTHROW; - GC_NOTRIGGER; - FORBID_FAULT; + THROWS; + GC_TRIGGERS; + INJECT_FAULT(COMPlusThrowOM()); } CONTRACTL_END; @@ -5522,11 +5522,9 @@ BOOL MethodDesc::ShouldSuppressGCTransition() } _ASSERTE(tgt != nullptr); - HRESULT hr = tgt->GetCustomAttribute( - WellKnownAttribute::SuppressGCTransition, - nullptr, - nullptr); - return (hr == S_OK) ? TRUE : FALSE; + bool suppressGCTransition; + NDirect::GetCallingConvention_IgnoreErrors(tgt, NULL /*callConv*/, &suppressGCTransition); + return suppressGCTransition ? TRUE : FALSE; } #ifdef FEATURE_COMINTEROP From 125ad4820ada6696eb45e24da80c45c82a406273 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 17 May 2021 13:10:59 -0700 Subject: [PATCH 5/8] Handle CallConvSuppressGCTransition on UnmanagedCallConv in crossgen2 --- .../Common/JitInterface/CallConvHelper.cs | 42 +++++++++++++++++++ .../tools/Common/JitInterface/CorInfoImpl.cs | 41 +++++++----------- .../Compiler/MethodExtensions.cs | 40 +++++++++++++++++- .../ILCompiler.ReadyToRun.csproj | 3 ++ 4 files changed, 98 insertions(+), 28 deletions(-) create mode 100644 src/coreclr/tools/Common/JitInterface/CallConvHelper.cs diff --git a/src/coreclr/tools/Common/JitInterface/CallConvHelper.cs b/src/coreclr/tools/Common/JitInterface/CallConvHelper.cs new file mode 100644 index 00000000000000..ad92b5b36e34c1 --- /dev/null +++ b/src/coreclr/tools/Common/JitInterface/CallConvHelper.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Reflection.Metadata; + +using Internal.TypeSystem; + +namespace Internal.JitInterface +{ + internal static unsafe class CallConvHelper + { + internal static IEnumerable EnumerateCallConvsFromAttribute(CustomAttributeValue attributeWithCallConvsArray) + { + ImmutableArray> callConvArray = default; + foreach (var arg in attributeWithCallConvsArray.NamedArguments) + { + if (arg.Name == "CallConvs") + { + callConvArray = (ImmutableArray>)arg.Value; + } + } + + // No calling convention was specified in the attribute + if (callConvArray.IsDefault) + yield break; + + foreach (CustomAttributeTypedArgument type in callConvArray) + { + if (!(type.Value is DefType defType)) + continue; + + if (defType.Namespace != "System.Runtime.CompilerServices") + continue; + + yield return defType; + } + } + } +} diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 46baad4876f788..be2c200f2d63a6 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -637,41 +637,27 @@ private void Get_CORINFO_SIG_INFO(MethodDesc method, CORINFO_SIG_INFO* sig, bool } } - private CorInfoCallConvExtension GetUnmanagedCallingConventionFromAttribute(CustomAttributeValue attributeWithCallConvsArray) + private CorInfoCallConvExtension GetUnmanagedCallingConventionFromAttribute(CustomAttributeValue attributeWithCallConvsArray, out bool suppressGCTransition) { + suppressGCTransition = false; CorInfoCallConvExtension callConv = (CorInfoCallConvExtension)PlatformDefaultUnmanagedCallingConvention(); - ImmutableArray> callConvArray = default; - foreach (var arg in attributeWithCallConvsArray.NamedArguments) - { - if (arg.Name == "CallConvs") - { - callConvArray = (ImmutableArray>)arg.Value; - } - } - - // No calling convention was specified in the attribute, so return the default. - if (callConvArray.IsDefault) - { - return callConv; - } - bool found = false; bool memberFunctionVariant = false; - foreach (CustomAttributeTypedArgument type in callConvArray) + foreach (DefType defType in CallConvHelper.EnumerateCallConvsFromAttribute(attributeWithCallConvsArray)) { - if (!(type.Value is DefType defType)) - continue; - - if (defType.Namespace != "System.Runtime.CompilerServices") - continue; - if (defType.Name == "CallConvMemberFunction") { memberFunctionVariant = true; continue; } + if (defType.Name == "CallConvSuppressGCTransition") + { + suppressGCTransition = true; + continue; + } + CorInfoCallConvExtension? callConvLocal = GetCallingConventionForCallConvType(defType); if (callConvLocal.HasValue) @@ -1258,7 +1244,7 @@ private CorInfoCallConvExtension GetUnmanagedCallConv(MethodDesc methodDesc, out { if (methodDesc.IsPInvoke) { - suppressGCTransition = methodDesc.IsSuppressGCTransition(); + suppressGCTransition = methodDesc.HasSuppressGCTransitionAttribute(); MethodSignatureFlags unmanagedCallConv = methodDesc.GetPInvokeMethodMetadata().Flags.UnmanagedCallingConvention; if (unmanagedCallConv == MethodSignatureFlags.None) @@ -1272,7 +1258,10 @@ private CorInfoCallConvExtension GetUnmanagedCallConv(MethodDesc methodDesc, out CustomAttributeValue? unmanagedCallConvAttribute = ((EcmaMethod)methodDescLocal).GetDecodedCustomAttribute("System.Runtime.InteropServices", "UnmanagedCallConvAttribute"); if (unmanagedCallConvAttribute != null) { - return GetUnmanagedCallingConventionFromAttribute(unmanagedCallConvAttribute.Value); + bool suppressGCTransitionLocal; + CorInfoCallConvExtension callConvFromAttribute = GetUnmanagedCallingConventionFromAttribute(unmanagedCallConvAttribute.Value, out suppressGCTransitionLocal); + suppressGCTransition |= suppressGCTransitionLocal; + return callConvFromAttribute; } unmanagedCallConv = PlatformDefaultUnmanagedCallingConvention(); @@ -1289,7 +1278,7 @@ private CorInfoCallConvExtension GetUnmanagedCallConv(MethodDesc methodDesc, out { Debug.Assert(methodDesc.IsUnmanagedCallersOnly); CustomAttributeValue unmanagedCallersOnlyAttribute = ((EcmaMethod)methodDesc).GetDecodedCustomAttribute("System.Runtime.InteropServices", "UnmanagedCallersOnlyAttribute").Value; - return GetUnmanagedCallingConventionFromAttribute(unmanagedCallersOnlyAttribute); + return GetUnmanagedCallingConventionFromAttribute(unmanagedCallersOnlyAttribute, out _); } } return GetUnmanagedCallConv(methodDesc.Signature, out suppressGCTransition); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/MethodExtensions.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/MethodExtensions.cs index eabc035af09268..f9b363778a6b04 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/MethodExtensions.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/MethodExtensions.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; using Debug = System.Diagnostics.Debug; @@ -21,18 +22,53 @@ public static bool IsRawPInvoke(this MethodDesc method) } /// - /// Gets a value indicating whether GC transition should be suppressed on the given p/invoke. + /// Gets a value indicating whether the method has the SuppressGCTransition attribute /// - public static bool IsSuppressGCTransition(this MethodDesc method) + public static bool HasSuppressGCTransitionAttribute(this MethodDesc method) { Debug.Assert(method.IsPInvoke); if (method is Internal.IL.Stubs.PInvokeTargetNativeMethod rawPinvoke) method = rawPinvoke.Target; + // Check SuppressGCTransition attribute return method.HasCustomAttribute("System.Runtime.InteropServices", "SuppressGCTransitionAttribute"); } + /// + /// Gets a value indicating whether GC transition should be suppressed on the given p/invoke. + /// + public static bool IsSuppressGCTransition(this MethodDesc method) + { + Debug.Assert(method.IsPInvoke); + + // Check SuppressGCTransition attribute + if (method.HasSuppressGCTransitionAttribute()) + return true; + + MethodSignatureFlags unmanagedCallConv = method.GetPInvokeMethodMetadata().Flags.UnmanagedCallingConvention; + if (unmanagedCallConv != MethodSignatureFlags.None) + return false; + + if (!(method is Internal.TypeSystem.Ecma.EcmaMethod ecmaMethod)) + return false; + + // Check UnmanagedCallConv attribute + System.Reflection.Metadata.CustomAttributeValue? unmanagedCallConvAttribute = ecmaMethod.GetDecodedCustomAttribute("System.Runtime.InteropServices", "UnmanagedCallConvAttribute"); + if (unmanagedCallConvAttribute == null) + return false; + + foreach (DefType defType in Internal.JitInterface.CallConvHelper.EnumerateCallConvsFromAttribute(unmanagedCallConvAttribute.Value)) + { + if (defType.Name == "CallConvSuppressGCTransition") + { + return true; + } + } + + return false; + } + /// /// Return true when the method is marked as non-versionable. Non-versionable methods /// may be freely inlined into ReadyToRun images even when they don't reside in the diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj index 8c387fa3c980a1..6299418227bde3 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj @@ -248,6 +248,9 @@ JitInterface\TypeString.cs + + JitInterface\CallConvHelper.cs + JitInterface\CorInfoBase.cs From 58b254682d63444996182ef8f8783b8a1692be06 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 17 May 2021 13:50:25 -0700 Subject: [PATCH 6/8] Add tests --- src/tests/Interop/CMakeLists.txt | 1 + .../SuppressGCTransitionTest.cs | 55 +-- .../SuppressGCTransitionTest.csproj | 1 + .../Interop/UnmanagedCallConv/CMakeLists.txt | 10 + .../Interop/UnmanagedCallConv/PInvokesCS.cs | 232 ++++++++++ .../Interop/UnmanagedCallConv/PInvokesIL.il | 151 +++++++ .../UnmanagedCallConv/PInvokesIL.ilproj | 8 + .../UnmanagedCallConvNative.cpp | 42 ++ .../UnmanagedCallConvTest.cs | 414 ++++++++++++++++++ .../UnmanagedCallConvTest.csproj | 16 + src/tests/Interop/common/CheckGCMode.cs | 37 ++ src/tests/issues.targets | 3 + 12 files changed, 929 insertions(+), 41 deletions(-) create mode 100644 src/tests/Interop/UnmanagedCallConv/CMakeLists.txt create mode 100644 src/tests/Interop/UnmanagedCallConv/PInvokesCS.cs create mode 100644 src/tests/Interop/UnmanagedCallConv/PInvokesIL.il create mode 100644 src/tests/Interop/UnmanagedCallConv/PInvokesIL.ilproj create mode 100644 src/tests/Interop/UnmanagedCallConv/UnmanagedCallConvNative.cpp create mode 100644 src/tests/Interop/UnmanagedCallConv/UnmanagedCallConvTest.cs create mode 100644 src/tests/Interop/UnmanagedCallConv/UnmanagedCallConvTest.csproj create mode 100644 src/tests/Interop/common/CheckGCMode.cs diff --git a/src/tests/Interop/CMakeLists.txt b/src/tests/Interop/CMakeLists.txt index 65295cb5688f2f..b8f39f741985f6 100644 --- a/src/tests/Interop/CMakeLists.txt +++ b/src/tests/Interop/CMakeLists.txt @@ -34,6 +34,7 @@ add_subdirectory(PInvoke/Generics) add_subdirectory(PInvoke/AsAny) add_subdirectory(PInvoke/SafeHandles) add_subdirectory(PInvoke/Vector2_3_4) +add_subdirectory(UnmanagedCallConv) add_subdirectory(UnmanagedCallersOnly) add_subdirectory(PrimitiveMarshalling/Bool) add_subdirectory(PrimitiveMarshalling/UIntPtr) diff --git a/src/tests/Interop/SuppressGCTransition/SuppressGCTransitionTest.cs b/src/tests/Interop/SuppressGCTransition/SuppressGCTransitionTest.cs index 2200f1dc4ef1fc..2c494865bef64b 100644 --- a/src/tests/Interop/SuppressGCTransition/SuppressGCTransitionTest.cs +++ b/src/tests/Interop/SuppressGCTransition/SuppressGCTransitionTest.cs @@ -109,33 +109,6 @@ private static IntPtr GetNativeLibrary() unsafe class SuppressGCTransitionTest { - private static bool ExplicitModeCheckEnabled = false; - private static void InitializeExplicitModeCheck() - { - // GetIsInCooperativeGCModeFunctionPointer is conditionally included based on the runtime build configuration, - // so we check for its existence and only do the explicit mode validation if it is available. - Type marshalType = typeof(object).Assembly.GetType(typeof(System.Runtime.InteropServices.Marshal).FullName); - MethodInfo getFunctionPtr = marshalType.GetMethod("GetIsInCooperativeGCModeFunctionPointer", BindingFlags.NonPublic | BindingFlags.Static); - if (getFunctionPtr != null) - { - var isInCooperativeModeFunc = (delegate* unmanaged)(IntPtr)getFunctionPtr.Invoke(null, null); - SuppressGCTransitionNative.SetIsInCooperativeModeFunction(isInCooperativeModeFunc); - ExplicitModeCheckEnabled = true; - Console.WriteLine("Explicit GC mode check is enabled"); - } - } - - private static void ValidateMode(bool transitionSuppressed, int inCooperativeMode) - => ValidateMode(transitionSuppressed, inCooperativeMode != 0); - - private static void ValidateMode(bool transitionSuppressed, bool inCooperativeMode) - { - if (!ExplicitModeCheckEnabled) - return; - - Assert.AreEqual(transitionSuppressed, inCooperativeMode, $"GC transition should{(transitionSuppressed ? "" : " not")} have been suppressed"); - } - [MethodImpl(MethodImplOptions.NoInlining)] private static int Inline_NoGCTransition(int expected) { @@ -143,7 +116,7 @@ private static int Inline_NoGCTransition(int expected) int n; int ret = SuppressGCTransitionNative.NextUInt_Inline_NoGCTransition(&n); Assert.AreEqual(expected, n); - ValidateMode(transitionSuppressed: true, ret); + CheckGCMode.Validate(transitionSuppressed: true, ret); return n + 1; } [MethodImpl(MethodImplOptions.NoInlining)] @@ -153,7 +126,7 @@ private static int Inline_GCTransition(int expected) int n; int ret = SuppressGCTransitionNative.NextUInt_Inline_GCTransition(&n); Assert.AreEqual(expected, n); - ValidateMode(transitionSuppressed: false, ret); + CheckGCMode.Validate(transitionSuppressed: false, ret); return n + 1; } [MethodImpl(MethodImplOptions.NoInlining)] @@ -163,7 +136,7 @@ private static int NoInline_NoGCTransition(int expected) int n; bool ret = SuppressGCTransitionNative.NextUInt_NoInline_NoGCTransition(&n); Assert.AreEqual(expected, n); - ValidateMode(transitionSuppressed: true, ret); + CheckGCMode.Validate(transitionSuppressed: true, ret); return n + 1; } [MethodImpl(MethodImplOptions.NoInlining)] @@ -173,7 +146,7 @@ private static int NoInline_GCTransition(int expected) int n; bool ret = SuppressGCTransitionNative.NextUInt_NoInline_GCTransition(&n); Assert.AreEqual(expected, n); - ValidateMode(transitionSuppressed: false, ret); + CheckGCMode.Validate(transitionSuppressed: false, ret); return n + 1; } [MethodImpl(MethodImplOptions.NoInlining)] @@ -185,18 +158,18 @@ private static int Mixed(int expected) { bool ret = SuppressGCTransitionNative.NextUInt_NoInline_GCTransition(&n); Assert.AreEqual(expected++, n); - ValidateMode(transitionSuppressed: false, ret); + CheckGCMode.Validate(transitionSuppressed: false, ret); ret = SuppressGCTransitionNative.NextUInt_NoInline_NoGCTransition(&n); Assert.AreEqual(expected++, n); - ValidateMode(transitionSuppressed: true, ret); + CheckGCMode.Validate(transitionSuppressed: true, ret); } { int ret = SuppressGCTransitionNative.NextUInt_Inline_GCTransition(&n); Assert.AreEqual(expected++, n); - ValidateMode(transitionSuppressed: false, ret); + CheckGCMode.Validate(transitionSuppressed: false, ret); ret = SuppressGCTransitionNative.NextUInt_Inline_NoGCTransition(&n); Assert.AreEqual(expected++, n); - ValidateMode(transitionSuppressed: true, ret); + CheckGCMode.Validate(transitionSuppressed: true, ret); } return n + 1; } @@ -224,7 +197,7 @@ private static int Inline_NoGCTransition_FunctionPointer(int expected) int n; int ret = SuppressGCTransitionNative.GetNextUIntFunctionPointer_Inline_NoGCTransition()(&n); Assert.AreEqual(expected, n); - ValidateMode(transitionSuppressed: true, ret); + CheckGCMode.Validate(transitionSuppressed: true, ret); return n + 1; } [MethodImpl(MethodImplOptions.NoInlining)] @@ -234,7 +207,7 @@ private static int Inline_GCTransition_FunctionPointer(int expected) int n; int ret = SuppressGCTransitionNative.GetNextUIntFunctionPointer_Inline_GCTransition()(&n); Assert.AreEqual(expected, n); - ValidateMode(transitionSuppressed: false, ret); + CheckGCMode.Validate(transitionSuppressed: false, ret); return n + 1; } [MethodImpl(MethodImplOptions.NoInlining)] @@ -244,7 +217,7 @@ private static int NoInline_NoGCTransition_FunctionPointer(int expected) int n; bool ret = SuppressGCTransitionNative.GetNextUIntFunctionPointer_NoInline_NoGCTransition()(&n); Assert.AreEqual(expected, n); - ValidateMode(transitionSuppressed: true, ret); + CheckGCMode.Validate(transitionSuppressed: true, ret); return n + 1; } [MethodImpl(MethodImplOptions.NoInlining)] @@ -254,7 +227,7 @@ private static int NoInline_GCTransition_FunctionPointer(int expected) int n; bool ret = SuppressGCTransitionNative.GetNextUIntFunctionPointer_NoInline_GCTransition()(&n); Assert.AreEqual(expected, n); - ValidateMode(transitionSuppressed: false, ret); + CheckGCMode.Validate(transitionSuppressed: false, ret); return n + 1; } @@ -272,7 +245,7 @@ private static int CallAsFunctionPointer(int expected) MethodInfo callNextUInt = typeof(FunctionPointer).GetMethod("Call_NextUInt"); int ret = (int)callNextUInt.Invoke(null, new object[] { fptr, boxedN }); Assert.AreEqual(expected, n); - ValidateMode(transitionSuppressed: false, ret); + CheckGCMode.Validate(transitionSuppressed: false, ret); return n + 1; } [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] @@ -351,7 +324,7 @@ public static int Main(string[] args) { try { - InitializeExplicitModeCheck(); + CheckGCMode.Initialize(&SuppressGCTransitionNative.SetIsInCooperativeModeFunction); int n = 1; n = Inline_NoGCTransition(n); diff --git a/src/tests/Interop/SuppressGCTransition/SuppressGCTransitionTest.csproj b/src/tests/Interop/SuppressGCTransition/SuppressGCTransitionTest.csproj index b2fe46353a4a28..f2034ae7c1447c 100644 --- a/src/tests/Interop/SuppressGCTransition/SuppressGCTransitionTest.csproj +++ b/src/tests/Interop/SuppressGCTransition/SuppressGCTransitionTest.csproj @@ -4,6 +4,7 @@ True + diff --git a/src/tests/Interop/UnmanagedCallConv/CMakeLists.txt b/src/tests/Interop/UnmanagedCallConv/CMakeLists.txt new file mode 100644 index 00000000000000..4e8d8692a07ba3 --- /dev/null +++ b/src/tests/Interop/UnmanagedCallConv/CMakeLists.txt @@ -0,0 +1,10 @@ +project(UnmanagedCallConvNative) +Include("${CLR_INTEROP_TEST_ROOT}/Interop.cmake") +set(SOURCES UnmanagedCallConvNative.cpp ) + +# add the executable +add_library(${PROJECT_NAME} SHARED ${SOURCES}) +target_link_libraries(${PROJECT_NAME} ${LINK_LIBRARIES_ADDITIONAL}) + +# add the install targets +install (TARGETS ${PROJECT_NAME} DESTINATION bin) diff --git a/src/tests/Interop/UnmanagedCallConv/PInvokesCS.cs b/src/tests/Interop/UnmanagedCallConv/PInvokesCS.cs new file mode 100644 index 00000000000000..60dcc10d955c66 --- /dev/null +++ b/src/tests/Interop/UnmanagedCallConv/PInvokesCS.cs @@ -0,0 +1,232 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; +using TestLibrary; + +public unsafe static class PInvokesCS +{ + private const string UnmanagedCallConvNative = nameof(UnmanagedCallConvNative); + + private const string Double_Default = nameof(Double_Default); + private const string Double_Cdecl = nameof(Double_Cdecl); + private const string Double_Stdcall = nameof(Double_Stdcall); + + private const string Invert_Stdcall = nameof(Invert_Stdcall); + + [DllImport(nameof(UnmanagedCallConvNative))] + public static extern unsafe void SetIsInCooperativeModeFunction(delegate* unmanaged fn); + + public static class DefaultDllImport + { + public static class Default + { + [UnmanagedCallConv] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Default)] + public static extern int Blittable_Double_DefaultUnmanagedCallConv(int a, int* b); + + [UnmanagedCallConv] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Default)] + public static extern bool NotBlittable_Double_DefaultUnmanagedCallConv(int a, int* b); + } + + public static class Cdecl + { + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvCdecl) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Cdecl)] + public static extern int Blittable_Double_CdeclUnmanagedCallConv(int a, int* b); + + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvCdecl) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Cdecl)] + public static extern bool NotBlittable_Double_CdeclUnmanagedCallConv(int a, int* b); + } + + public static class Stdcall + { + // Mismatch + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvCdecl) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Stdcall)] + public static extern int Blittable_Double_CdeclUnmanagedCallConv(int a, int* b); + + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvStdcall) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Stdcall)] + public static extern int Blittable_Double_StdcallUnmanagedCallConv(int a, int* b); + + // Mismatch + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvCdecl) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Stdcall)] + public static extern bool NotBlittable_Double_CdeclUnmanagedCallConv(int a, int* b); + + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvStdcall) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Stdcall)] + public static extern bool NotBlittable_Double_StdcallUnmanagedCallConv(int a, int* b); + } + } + + public static class WinapiDllImport + { + public static class Default + { + [UnmanagedCallConv] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Default, CallingConvention = CallingConvention.Winapi)] + public static extern int Blittable_Double_DefaultUnmanagedCallConv(int a, int* b); + + [UnmanagedCallConv] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Default, CallingConvention = CallingConvention.Winapi)] + public static extern bool NotBlittable_Double_DefaultUnmanagedCallConv(int a, int* b); + } + + public static class Cdecl + { + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvCdecl) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Cdecl, CallingConvention = CallingConvention.Winapi)] + public static extern int Blittable_Double_CdeclUnmanagedCallConv(int a, int* b); + + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvCdecl) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Cdecl, CallingConvention = CallingConvention.Winapi)] + public static extern bool NotBlittable_Double_CdeclUnmanagedCallConv(int a, int* b); + } + + public static class Stdcall + { + // Mismatch + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvCdecl) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Stdcall, CallingConvention = CallingConvention.Winapi)] + public static extern int Blittable_Double_CdeclUnmanagedCallConv(int a, int* b); + + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvStdcall) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Stdcall, CallingConvention = CallingConvention.Winapi)] + public static extern int Blittable_Double_StdcallUnmanagedCallConv(int a, int* b); + + // Mismatch + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvCdecl) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Stdcall, CallingConvention = CallingConvention.Winapi)] + public static extern bool NotBlittable_Double_CdeclUnmanagedCallConv(int a, int* b); + + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvStdcall) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Stdcall, CallingConvention = CallingConvention.Winapi)] + public static extern bool NotBlittable_Double_StdcallUnmanagedCallConv(int a, int* b); + } + } + + public static class SuppressGCTransition + { + public static class Default + { + [SuppressGCTransition] + [UnmanagedCallConv] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Default)] + public static extern int Blittable_Double_DefaultUnmanagedCallConv_SuppressGCAttr(int a, int* b); + + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSuppressGCTransition) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Default)] + public static extern int Blittable_Double_DefaultUnmanagedCallConv_SuppressGC(int a, int* b); + + [SuppressGCTransition] + [UnmanagedCallConv] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Default)] + public static extern bool NotBlittable_Double_DefaultUnmanagedCallConv_SuppressGCAttr(int a, int* b); + + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSuppressGCTransition) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Default)] + public static extern bool NotBlittable_Double_DefaultUnmanagedCallConv_SuppressGC(int a, int* b); + } + + public static class Cdecl + { + [SuppressGCTransition] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvCdecl) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Cdecl)] + public static extern int Blittable_Double_CdeclUnmanagedCallConv_SuppressGCAttr(int a, int* b); + + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvCdecl), typeof(CallConvSuppressGCTransition) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Cdecl)] + public static extern int Blittable_Double_CdeclUnmanagedCallConv_SuppressGC(int a, int* b); + + [SuppressGCTransition] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvCdecl) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Cdecl)] + public static extern bool NotBlittable_Double_CdeclUnmanagedCallConv_SuppressGCAttr(int a, int* b); + + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvCdecl), typeof(CallConvSuppressGCTransition) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Cdecl)] + public static extern bool NotBlittable_Double_CdeclUnmanagedCallConv_SuppressGC(int a, int* b); + } + + public static class Stdcall + { + [SuppressGCTransition] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvStdcall) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Stdcall)] + public static extern int Blittable_Double_StdcallUnmanagedCallConv_SuppressGCAttr(int a, int* b); + + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvStdcall), typeof(CallConvSuppressGCTransition) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Stdcall)] + public static extern int Blittable_Double_StdcallUnmanagedCallConv_SuppressGC(int a, int* b); + + [SuppressGCTransition] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvStdcall) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Stdcall)] + public static extern bool NotBlittable_Double_StdcallUnmanagedCallConv_SuppressGCAttr(int a, int* b); + + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvStdcall), typeof(CallConvSuppressGCTransition) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Stdcall)] + public static extern bool NotBlittable_Double_StdcallUnmanagedCallConv_SuppressGC(int a, int* b); + } + } + + public static class MatchingDllImport + { + public static class Cdecl + { + // UnmanagedCallConv should not be used + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvStdcall) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Cdecl, CallingConvention = CallingConvention.Cdecl)] + public static extern int Blittable_Double_StdcallUnmanagedCallConv(int a, int* b); + + // UnmanagedCallConv should not be used + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSuppressGCTransition) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Cdecl, CallingConvention = CallingConvention.Cdecl)] + public static extern int Blittable_Double_SuppressGCUnmanagedCallConv(int a, int* b); + + // UnmanagedCallConv should not be used + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvStdcall) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Cdecl, CallingConvention = CallingConvention.Cdecl)] + public static extern bool NotBlittable_Double_StdcallUnmanagedCallConv(int a, int* b); + + // UnmanagedCallConv should not be used + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSuppressGCTransition) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Cdecl, CallingConvention = CallingConvention.Cdecl)] + public static extern bool NotBlittable_Double_SuppressGCUnmanagedCallConv(int a, int* b); + } + + public static class Stdcall + { + // UnmanagedCallConv should not be used + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvCdecl) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Stdcall, CallingConvention = CallingConvention.StdCall)] + public static extern int Blittable_Double_CdeclUnmanagedCallConv(int a, int* b); + + // UnmanagedCallConv should not be used + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSuppressGCTransition) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Stdcall, CallingConvention = CallingConvention.StdCall)] + public static extern int Blittable_Double_SuppressGCUnmanagedCallConv(int a, int* b); + + // UnmanagedCallConv should not be used + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvCdecl) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Stdcall, CallingConvention = CallingConvention.StdCall)] + public static extern bool NotBlittable_Double_CdeclUnmanagedCallConv(int a, int* b); + + // UnmanagedCallConv should not be used + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSuppressGCTransition) })] + [DllImport(nameof(UnmanagedCallConvNative), EntryPoint = Double_Stdcall, CallingConvention = CallingConvention.StdCall)] + public static extern bool NotBlittable_Double_SuppressGCUnmanagedCallConv(int a, int* b); + } + } +} diff --git a/src/tests/Interop/UnmanagedCallConv/PInvokesIL.il b/src/tests/Interop/UnmanagedCallConv/PInvokesIL.il new file mode 100644 index 00000000000000..841eba21c1ad95 --- /dev/null +++ b/src/tests/Interop/UnmanagedCallConv/PInvokesIL.il @@ -0,0 +1,151 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +.assembly extern System.Runtime +{ + .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) +} +.assembly extern System.Runtime.InteropServices +{ + .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) +} + +.assembly PInvokesIL { } + +.class public auto ansi abstract sealed beforefieldinit PInvokesIL + extends [System.Runtime]System.Object +{ + .class nested public auto ansi abstract sealed beforefieldinit UnsetPInvokeImpl + extends [System.Runtime]System.Object + { + .class nested public auto ansi abstract sealed beforefieldinit Default + extends [System.Runtime]System.Object + { + .method public hidebysig static pinvokeimpl("UnmanagedCallConvNative" as "Double_Default") + int32 Blittable_Double_DefaultUnmanagedCallConv (int32 a, int32* b) cil managed preservesig + { + .custom instance void [System.Runtime.InteropServices]System.Runtime.InteropServices.UnmanagedCallConvAttribute::.ctor() = ( + 01 00 00 00 + ) + } + + .method public hidebysig static pinvokeimpl("UnmanagedCallConvNative" as "Double_Default") + bool NotBlittable_Double_DefaultUnmanagedCallConv (int32 a, int32* b) cil managed preservesig + { + .custom instance void [System.Runtime.InteropServices]System.Runtime.InteropServices.UnmanagedCallConvAttribute::.ctor() = ( + 01 00 00 00 + ) + } + } + + .class nested public auto ansi abstract sealed beforefieldinit Cdecl + extends [System.Runtime]System.Object + { + .method public hidebysig static pinvokeimpl("UnmanagedCallConvNative" as "Double_Cdecl") + int32 Blittable_Double_CdeclUnmanagedCallConv (int32 a, int32* b) cil managed preservesig + { + .custom instance void [System.Runtime.InteropServices]System.Runtime.InteropServices.UnmanagedCallConvAttribute::.ctor() = ( + 01 00 01 00 53 1d 50 09 43 61 6c 6c 43 6f 6e 76 + 73 01 00 00 00 80 88 53 79 73 74 65 6d 2e 52 75 + 6e 74 69 6d 65 2e 43 6f 6d 70 69 6c 65 72 53 65 + 72 76 69 63 65 73 2e 43 61 6c 6c 43 6f 6e 76 43 + 64 65 63 6c 2c 20 53 79 73 74 65 6d 2e 50 72 69 + 76 61 74 65 2e 43 6f 72 65 4c 69 62 2c 20 56 65 + 72 73 69 6f 6e 3d 35 2e 30 2e 30 2e 30 2c 20 43 + 75 6c 74 75 72 65 3d 6e 65 75 74 72 61 6c 2c 20 + 50 75 62 6c 69 63 4b 65 79 54 6f 6b 65 6e 3d 37 + 63 65 63 38 35 64 37 62 65 61 37 37 39 38 65 + ) + } + + .method public hidebysig static pinvokeimpl("UnmanagedCallConvNative" as "Double_Cdecl") + bool NotBlittable_Double_CdeclUnmanagedCallConv (int32 a, int32* b) cil managed preservesig + { + .custom instance void [System.Runtime.InteropServices]System.Runtime.InteropServices.UnmanagedCallConvAttribute::.ctor() = ( + 01 00 01 00 53 1d 50 09 43 61 6c 6c 43 6f 6e 76 + 73 01 00 00 00 80 88 53 79 73 74 65 6d 2e 52 75 + 6e 74 69 6d 65 2e 43 6f 6d 70 69 6c 65 72 53 65 + 72 76 69 63 65 73 2e 43 61 6c 6c 43 6f 6e 76 43 + 64 65 63 6c 2c 20 53 79 73 74 65 6d 2e 50 72 69 + 76 61 74 65 2e 43 6f 72 65 4c 69 62 2c 20 56 65 + 72 73 69 6f 6e 3d 35 2e 30 2e 30 2e 30 2c 20 43 + 75 6c 74 75 72 65 3d 6e 65 75 74 72 61 6c 2c 20 + 50 75 62 6c 69 63 4b 65 79 54 6f 6b 65 6e 3d 37 + 63 65 63 38 35 64 37 62 65 61 37 37 39 38 65 + ) + } + } + + .class nested public auto ansi abstract sealed beforefieldinit Stdcall + extends [System.Runtime]System.Object + { + .method public hidebysig static pinvokeimpl("UnmanagedCallConvNative" as "Double_Stdcall") + int32 Blittable_Double_CdeclUnmanagedCallConv (int32 a, int32* b) cil managed preservesig + { + .custom instance void [System.Runtime.InteropServices]System.Runtime.InteropServices.UnmanagedCallConvAttribute::.ctor() = ( + 01 00 01 00 53 1d 50 09 43 61 6c 6c 43 6f 6e 76 + 73 01 00 00 00 80 88 53 79 73 74 65 6d 2e 52 75 + 6e 74 69 6d 65 2e 43 6f 6d 70 69 6c 65 72 53 65 + 72 76 69 63 65 73 2e 43 61 6c 6c 43 6f 6e 76 43 + 64 65 63 6c 2c 20 53 79 73 74 65 6d 2e 50 72 69 + 76 61 74 65 2e 43 6f 72 65 4c 69 62 2c 20 56 65 + 72 73 69 6f 6e 3d 35 2e 30 2e 30 2e 30 2c 20 43 + 75 6c 74 75 72 65 3d 6e 65 75 74 72 61 6c 2c 20 + 50 75 62 6c 69 63 4b 65 79 54 6f 6b 65 6e 3d 37 + 63 65 63 38 35 64 37 62 65 61 37 37 39 38 65 + ) + } + + .method public hidebysig static pinvokeimpl("UnmanagedCallConvNative" as "Double_Stdcall") + int32 Blittable_Double_StdcallUnmanagedCallConv (int32 a, int32* b) cil managed preservesig + { + .custom instance void [System.Runtime.InteropServices]System.Runtime.InteropServices.UnmanagedCallConvAttribute::.ctor() = ( + 01 00 01 00 53 1d 50 09 43 61 6c 6c 43 6f 6e 76 + 73 01 00 00 00 80 8a 53 79 73 74 65 6d 2e 52 75 + 6e 74 69 6d 65 2e 43 6f 6d 70 69 6c 65 72 53 65 + 72 76 69 63 65 73 2e 43 61 6c 6c 43 6f 6e 76 53 + 74 64 63 61 6c 6c 2c 20 53 79 73 74 65 6d 2e 50 + 72 69 76 61 74 65 2e 43 6f 72 65 4c 69 62 2c 20 + 56 65 72 73 69 6f 6e 3d 35 2e 30 2e 30 2e 30 2c + 20 43 75 6c 74 75 72 65 3d 6e 65 75 74 72 61 6c + 2c 20 50 75 62 6c 69 63 4b 65 79 54 6f 6b 65 6e + 3d 37 63 65 63 38 35 64 37 62 65 61 37 37 39 38 65 + ) + } + + .method public hidebysig static pinvokeimpl("UnmanagedCallConvNative" as "Double_Stdcall") + bool NotBlittable_Double_CdeclUnmanagedCallConv (int32 a, int32* b) cil managed preservesig + { + .custom instance void [System.Runtime.InteropServices]System.Runtime.InteropServices.UnmanagedCallConvAttribute::.ctor() = ( + 01 00 01 00 53 1d 50 09 43 61 6c 6c 43 6f 6e 76 + 73 01 00 00 00 80 88 53 79 73 74 65 6d 2e 52 75 + 6e 74 69 6d 65 2e 43 6f 6d 70 69 6c 65 72 53 65 + 72 76 69 63 65 73 2e 43 61 6c 6c 43 6f 6e 76 43 + 64 65 63 6c 2c 20 53 79 73 74 65 6d 2e 50 72 69 + 76 61 74 65 2e 43 6f 72 65 4c 69 62 2c 20 56 65 + 72 73 69 6f 6e 3d 35 2e 30 2e 30 2e 30 2c 20 43 + 75 6c 74 75 72 65 3d 6e 65 75 74 72 61 6c 2c 20 + 50 75 62 6c 69 63 4b 65 79 54 6f 6b 65 6e 3d 37 + 63 65 63 38 35 64 37 62 65 61 37 37 39 38 65 + ) + } + + .method public hidebysig static pinvokeimpl("UnmanagedCallConvNative" as "Double_Stdcall") + bool NotBlittable_Double_StdcallUnmanagedCallConv (int32 a, int32* b) cil managed preservesig + { + .custom instance void [System.Runtime.InteropServices]System.Runtime.InteropServices.UnmanagedCallConvAttribute::.ctor() = ( + 01 00 01 00 53 1d 50 09 43 61 6c 6c 43 6f 6e 76 + 73 01 00 00 00 80 8a 53 79 73 74 65 6d 2e 52 75 + 6e 74 69 6d 65 2e 43 6f 6d 70 69 6c 65 72 53 65 + 72 76 69 63 65 73 2e 43 61 6c 6c 43 6f 6e 76 53 + 74 64 63 61 6c 6c 2c 20 53 79 73 74 65 6d 2e 50 + 72 69 76 61 74 65 2e 43 6f 72 65 4c 69 62 2c 20 + 56 65 72 73 69 6f 6e 3d 35 2e 30 2e 30 2e 30 2c + 20 43 75 6c 74 75 72 65 3d 6e 65 75 74 72 61 6c + 2c 20 50 75 62 6c 69 63 4b 65 79 54 6f 6b 65 6e + 3d 37 63 65 63 38 35 64 37 62 65 61 37 37 39 38 65 + ) + } + } + } +} diff --git a/src/tests/Interop/UnmanagedCallConv/PInvokesIL.ilproj b/src/tests/Interop/UnmanagedCallConv/PInvokesIL.ilproj new file mode 100644 index 00000000000000..329b583c8965fe --- /dev/null +++ b/src/tests/Interop/UnmanagedCallConv/PInvokesIL.ilproj @@ -0,0 +1,8 @@ + + + library + + + + + diff --git a/src/tests/Interop/UnmanagedCallConv/UnmanagedCallConvNative.cpp b/src/tests/Interop/UnmanagedCallConv/UnmanagedCallConvNative.cpp new file mode 100644 index 00000000000000..7abbf8a6508741 --- /dev/null +++ b/src/tests/Interop/UnmanagedCallConv/UnmanagedCallConvNative.cpp @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include + +namespace +{ + using IsInCooperativeMode_fn = BOOL(STDMETHODCALLTYPE*)(void); + IsInCooperativeMode_fn s_isInCooperativeMode = nullptr; + + BOOL Double(int a, int* b) + { + if (b != NULL) + *b = a * 2; + + BOOL ret = FALSE; + if (s_isInCooperativeMode != nullptr) + ret = s_isInCooperativeMode(); + + return ret; + } +} + +extern "C" void DLL_EXPORT STDMETHODCALLTYPE SetIsInCooperativeModeFunction(IsInCooperativeMode_fn fn) +{ + s_isInCooperativeMode = fn; +} + +extern "C" DLL_EXPORT BOOL Double_Default(int a, int* b) +{ + return Double(a, b); +} + +extern "C" DLL_EXPORT int __cdecl Double_Cdecl(int a, int* b) +{ + return Double(a, b); +} + +extern "C" DLL_EXPORT int __stdcall Double_Stdcall(int a, int* b) +{ + return Double(a, b); +} diff --git a/src/tests/Interop/UnmanagedCallConv/UnmanagedCallConvTest.cs b/src/tests/Interop/UnmanagedCallConv/UnmanagedCallConvTest.cs new file mode 100644 index 00000000000000..d6e0b16095731f --- /dev/null +++ b/src/tests/Interop/UnmanagedCallConv/UnmanagedCallConvTest.cs @@ -0,0 +1,414 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; +using TestLibrary; + +public unsafe class Program +{ + // We test for mismatched callling convention by checking for a failure to + // find the native entry point when the exported function is stdcall, but + // the defined p/invoke is cdecl. This is only relevant on Windows x86. + private static bool ValidateMismatch = OperatingSystem.IsWindows() && TestLibrary.Utilities.IsX86; + + private static void DefaultDllImport_Blittable() + { + Console.WriteLine($"Running {nameof(DefaultDllImport_Blittable)}..."); + + const int a = 11; + const int expected = a * 2; + { + Console.WriteLine($" -- default: UnmanagedCallConv()"); + int b; + PInvokesCS.DefaultDllImport.Default.Blittable_Double_DefaultUnmanagedCallConv(a, &b); + Assert.AreEqual(expected, b); + } + { + Console.WriteLine($" -- cdecl: UnmanagedCallConv(cdecl)"); + int b; + PInvokesCS.DefaultDllImport.Cdecl.Blittable_Double_CdeclUnmanagedCallConv(a, &b); + Assert.AreEqual(expected, b); + } + { + Console.WriteLine($" -- stdcall: UnmanagedCallConv(stdcall)"); + int b; + PInvokesCS.DefaultDllImport.Stdcall.Blittable_Double_StdcallUnmanagedCallConv(a, &b); + Assert.AreEqual(expected, b); + } + + if (ValidateMismatch) + { + Console.WriteLine($" -- stdcall: UnmanagedCallConv(cdecl)"); + Assert.Throws(() => PInvokesCS.DefaultDllImport.Stdcall.Blittable_Double_CdeclUnmanagedCallConv(a, null)); + } + } + + private static void DefaultDllImport_NotBlittable() + { + Console.WriteLine($"Running {nameof(DefaultDllImport_NotBlittable)}..."); + + const int a = 11; + const int expected = a * 2; + { + Console.WriteLine($" -- default: UnmanagedCallConv()"); + int b; + PInvokesCS.DefaultDllImport.Default.NotBlittable_Double_DefaultUnmanagedCallConv(a, &b); + Assert.AreEqual(expected, b); + } + { + Console.WriteLine($" -- cdecl: UnmanagedCallConv(cdecl)"); + int b; + PInvokesCS.DefaultDllImport.Cdecl.NotBlittable_Double_CdeclUnmanagedCallConv(a, &b); + Assert.AreEqual(expected, b); + } + { + Console.WriteLine($" -- stdcall: UnmanagedCallConv(stdcall)"); + int b; + PInvokesCS.DefaultDllImport.Stdcall.NotBlittable_Double_StdcallUnmanagedCallConv(a, &b); + Assert.AreEqual(expected, b); + } + + if (ValidateMismatch) + { + Console.WriteLine($" -- stdcall: UnmanagedCallConv(cdecl)"); + Assert.Throws(() => PInvokesCS.DefaultDllImport.Stdcall.NotBlittable_Double_CdeclUnmanagedCallConv(a, null)); + } + } + + private static void WinapiDllImport_Blittable() + { + Console.WriteLine($"Running {nameof(WinapiDllImport_Blittable)}..."); + + const int a = 11; + const int expected = a * 2; + { + Console.WriteLine($" -- default: UnmanagedCallConv()"); + int b; + PInvokesCS.WinapiDllImport.Default.Blittable_Double_DefaultUnmanagedCallConv(a, &b); + Assert.AreEqual(expected, b); + } + { + Console.WriteLine($" -- cdecl: UnmanagedCallConv(cdecl)"); + int b; + PInvokesCS.WinapiDllImport.Cdecl.Blittable_Double_CdeclUnmanagedCallConv(a, &b); + Assert.AreEqual(expected, b); + } + { + Console.WriteLine($" -- stdcall: UnmanagedCallConv(stdcall)"); + int b; + PInvokesCS.WinapiDllImport.Stdcall.Blittable_Double_StdcallUnmanagedCallConv(a, &b); + Assert.AreEqual(expected, b); + } + + if (ValidateMismatch) + { + Console.WriteLine($" -- stdcall: UnmanagedCallConv(cdecl)"); + Assert.Throws(() => PInvokesCS.WinapiDllImport.Stdcall.Blittable_Double_CdeclUnmanagedCallConv(a, null)); + } + } + + private static void WinapiDllImport_NotBlittable() + { + Console.WriteLine($"Running {nameof(WinapiDllImport_NotBlittable)}..."); + + const int a = 11; + const int expected = a * 2; + { + Console.WriteLine($" -- default: UnmanagedCallConv()"); + int b; + PInvokesCS.WinapiDllImport.Default.NotBlittable_Double_DefaultUnmanagedCallConv(a, &b); + Assert.AreEqual(expected, b); + } + { + Console.WriteLine($" -- cdecl: UnmanagedCallConv(cdecl)"); + int b; + PInvokesCS.WinapiDllImport.Cdecl.NotBlittable_Double_CdeclUnmanagedCallConv(a, &b); + Assert.AreEqual(expected, b); + } + { + Console.WriteLine($" -- stdcall: UnmanagedCallConv(stdcall)"); + int b; + PInvokesCS.WinapiDllImport.Stdcall.NotBlittable_Double_StdcallUnmanagedCallConv(a, &b); + Assert.AreEqual(expected, b); + } + + if (ValidateMismatch) + { + Console.WriteLine($" -- stdcall: UnmanagedCallConv(cdecl)"); + Assert.Throws(() => PInvokesCS.WinapiDllImport.Stdcall.NotBlittable_Double_CdeclUnmanagedCallConv(a, null)); + } + } + + private static void UnsetPInvokeImpl_Blittable() + { + Console.WriteLine($"Running {nameof(UnsetPInvokeImpl_Blittable)}..."); + + const int a = 11; + const int expected = a * 2; + { + Console.WriteLine($" -- default: UnmanagedCallConv()"); + int b; + PInvokesIL.UnsetPInvokeImpl.Default.Blittable_Double_DefaultUnmanagedCallConv(a, &b); + Assert.AreEqual(expected, b); + } + { + Console.WriteLine($" -- cdecl: UnmanagedCallConv(cdecl)"); + int b; + PInvokesIL.UnsetPInvokeImpl.Cdecl.Blittable_Double_CdeclUnmanagedCallConv(a, &b); + Assert.AreEqual(expected, b); + } + { + Console.WriteLine($" -- stdcall: UnmanagedCallConv(stdcall)"); + int b; + PInvokesIL.UnsetPInvokeImpl.Stdcall.Blittable_Double_StdcallUnmanagedCallConv(a, &b); + Assert.AreEqual(expected, b); + } + + if (ValidateMismatch) + { + Console.WriteLine($" -- stdcall: UnmanagedCallConv(cdecl)"); + Assert.Throws(() => PInvokesIL.UnsetPInvokeImpl.Stdcall.Blittable_Double_CdeclUnmanagedCallConv(a, null)); + } + } + + private static void UnsetPInvokeImpl_NotBlittable() + { + Console.WriteLine($"Running {nameof(UnsetPInvokeImpl_NotBlittable)}..."); + + const int a = 11; + const int expected = a * 2; + { + Console.WriteLine($" -- default: UnmanagedCallConv()"); + int b; + PInvokesIL.UnsetPInvokeImpl.Default.NotBlittable_Double_DefaultUnmanagedCallConv(a, &b); + Assert.AreEqual(expected, b); + } + { + Console.WriteLine($" -- cdecl: UnmanagedCallConv(cdecl)"); + int b; + PInvokesIL.UnsetPInvokeImpl.Cdecl.NotBlittable_Double_CdeclUnmanagedCallConv(a, &b); + Assert.AreEqual(expected, b); + } + { + Console.WriteLine($" -- stdcall: UnmanagedCallConv(stdcall)"); + int b; + PInvokesIL.UnsetPInvokeImpl.Stdcall.NotBlittable_Double_StdcallUnmanagedCallConv(a, &b); + Assert.AreEqual(expected, b); + } + + if (ValidateMismatch) + { + Console.WriteLine($" -- stdcall: UnmanagedCallConv(cdecl)"); + Assert.Throws(() => PInvokesIL.UnsetPInvokeImpl.Stdcall.NotBlittable_Double_CdeclUnmanagedCallConv(a, null)); + } + } + + private static void SuppressGCTransition_Blittable() + { + Console.WriteLine($"Running {nameof(SuppressGCTransition_Blittable)}..."); + + const int a = 11; + const int expected = a * 2; + { + Console.WriteLine($" -- default: SuppressGCTransition, UnmanagedCallConv()"); + int b; + int ret = PInvokesCS.SuppressGCTransition.Default.Blittable_Double_DefaultUnmanagedCallConv_SuppressGCAttr(a, &b); + Assert.AreEqual(expected, b); + CheckGCMode.Validate(transitionSuppressed: true, ret); + } + { + Console.WriteLine($" -- default: UnmanagedCallConv(suppressgctransition)"); + int b; + int ret = PInvokesCS.SuppressGCTransition.Default.Blittable_Double_DefaultUnmanagedCallConv_SuppressGC(a, &b); + Assert.AreEqual(expected, b); + CheckGCMode.Validate(transitionSuppressed: true, ret); + } + { + Console.WriteLine($" -- cdecl: SuppressGCTransition, UnmanagedCallConv(cdecl)"); + int b; + int ret = PInvokesCS.SuppressGCTransition.Cdecl.Blittable_Double_CdeclUnmanagedCallConv_SuppressGCAttr(a, &b); + Assert.AreEqual(expected, b); + CheckGCMode.Validate(transitionSuppressed: true, ret); + } + { + Console.WriteLine($" -- cdecl: UnmanagedCallConv(cdecl, suppressgctransition)"); + int b; + int ret = PInvokesCS.SuppressGCTransition.Cdecl.Blittable_Double_CdeclUnmanagedCallConv_SuppressGC(a, &b); + Assert.AreEqual(expected, b); + CheckGCMode.Validate(transitionSuppressed: true, ret); + } + { + Console.WriteLine($" -- stdcall: SuppressGCTransition, UnmanagedCallConv(stdcall)"); + int b; + int ret = PInvokesCS.SuppressGCTransition.Stdcall.Blittable_Double_StdcallUnmanagedCallConv_SuppressGCAttr(a, &b); + Assert.AreEqual(expected, b); + CheckGCMode.Validate(transitionSuppressed: true, ret); + } + { + Console.WriteLine($" -- stdcall: UnmanagedCallConv(stdcall, suppressgctransition)"); + int b; + int ret = PInvokesCS.SuppressGCTransition.Stdcall.Blittable_Double_StdcallUnmanagedCallConv_SuppressGC(a, &b); + Assert.AreEqual(expected, b); + CheckGCMode.Validate(transitionSuppressed: true, ret); + } + } + + private static void SuppressGCTransition_NotBlittable() + { + Console.WriteLine($"Running {nameof(SuppressGCTransition_NotBlittable)}..."); + + const int a = 11; + const int expected = a * 2; + { + Console.WriteLine($" -- default: SuppressGCTransition, UnmanagedCallConv()"); + int b; + bool ret = PInvokesCS.SuppressGCTransition.Default.NotBlittable_Double_DefaultUnmanagedCallConv_SuppressGCAttr(a, &b); + Assert.AreEqual(expected, b); + CheckGCMode.Validate(transitionSuppressed: true, ret); + } + { + Console.WriteLine($" -- default: UnmanagedCallConv(suppressgctransition)"); + int b; + bool ret = PInvokesCS.SuppressGCTransition.Default.NotBlittable_Double_DefaultUnmanagedCallConv_SuppressGC(a, &b); + Assert.AreEqual(expected, b); + CheckGCMode.Validate(transitionSuppressed: true, ret); + } + { + Console.WriteLine($" -- cdecl: SuppressGCTransition, UnmanagedCallConv(cdecl)"); + int b; + bool ret = PInvokesCS.SuppressGCTransition.Cdecl.NotBlittable_Double_CdeclUnmanagedCallConv_SuppressGCAttr(a, &b); + Assert.AreEqual(expected, b); + CheckGCMode.Validate(transitionSuppressed: true, ret); + } + { + Console.WriteLine($" -- cdecl: UnmanagedCallConv(cdecl, suppressgctransition)"); + int b; + bool ret = PInvokesCS.SuppressGCTransition.Cdecl.NotBlittable_Double_CdeclUnmanagedCallConv_SuppressGC(a, &b); + Assert.AreEqual(expected, b); + CheckGCMode.Validate(transitionSuppressed: true, ret); + } + { + Console.WriteLine($" -- stdcall: SuppressGCTransition, UnmanagedCallConv(stdcall)"); + int b; + bool ret = PInvokesCS.SuppressGCTransition.Stdcall.NotBlittable_Double_StdcallUnmanagedCallConv_SuppressGCAttr(a, &b); + Assert.AreEqual(expected, b); + CheckGCMode.Validate(transitionSuppressed: true, ret); + } + { + Console.WriteLine($" -- stdcall: UnmanagedCallConv(stdcall, suppressgctransition)"); + int b; + bool ret = PInvokesCS.SuppressGCTransition.Stdcall.NotBlittable_Double_StdcallUnmanagedCallConv_SuppressGC(a, &b); + Assert.AreEqual(expected, b); + CheckGCMode.Validate(transitionSuppressed: true, ret); + } + } + + private static void MatchingDllImport_Blittable() + { + Console.WriteLine($"Running {nameof(MatchingDllImport_Blittable)}..."); + + // Calling convention is set in DllImport. UnmanagedCallConv should be ignored, + const int a = 11; + const int expected = a * 2; + { + // Should work despite the mismatched value in UnmanagedCallConv + Console.WriteLine($" -- cdecl: UnmanagedCallConv(stdcall)"); + int b; + PInvokesCS.MatchingDllImport.Cdecl.Blittable_Double_StdcallUnmanagedCallConv(a, &b); + Assert.AreEqual(expected, b); + } + { + // Should not suppress GC transition + Console.WriteLine($" -- cdecl: UnmanagedCallConv(suppressgctransition)"); + int b; + int ret = PInvokesCS.MatchingDllImport.Cdecl.Blittable_Double_SuppressGCUnmanagedCallConv(a, &b); + Assert.AreEqual(expected, b); + CheckGCMode.Validate(transitionSuppressed: false, ret); + } + { + // Should work despite the mismatched value in UnmanagedCallConv + Console.WriteLine($" -- stdcall: UnmanagedCallConv(cdecl)"); + int b; + PInvokesCS.MatchingDllImport.Stdcall.Blittable_Double_CdeclUnmanagedCallConv(a, &b); + Assert.AreEqual(expected, b); + } + { + // Should not suppress GC transition + Console.WriteLine($" -- stdcall: UnmanagedCallConv(suppressgctransition)"); + int b; + int ret = PInvokesCS.MatchingDllImport.Stdcall.Blittable_Double_SuppressGCUnmanagedCallConv(a, &b); + Assert.AreEqual(expected, b); + CheckGCMode.Validate(transitionSuppressed: false, ret); + } + } + + private static void MatchingDllImport_NotBlittable() + { + Console.WriteLine($"Running {nameof(MatchingDllImport_NotBlittable)}..."); + + // Calling convention is set in DllImport. UnmanagedCallConv should be ignored, + const int a = 11; + const int expected = a * 2; + { + // Should work despite the mismatched value in UnmanagedCallConv + Console.WriteLine($" -- cdecl: UnmanagedCallConv(stdcall)"); + int b; + PInvokesCS.MatchingDllImport.Cdecl.NotBlittable_Double_StdcallUnmanagedCallConv(a, &b); + Assert.AreEqual(expected, b); + } + { + // Should not suppress GC transition + Console.WriteLine($" -- cdecl: UnmanagedCallConv(suppressgctransition)"); + int b; + bool ret = PInvokesCS.MatchingDllImport.Cdecl.NotBlittable_Double_SuppressGCUnmanagedCallConv(a, &b); + Assert.AreEqual(expected, b); + CheckGCMode.Validate(transitionSuppressed: false, ret); + } + { + // Should work despite the mismatched value in UnmanagedCallConv + Console.WriteLine($" -- stdcall: UnmanagedCallConv(cdecl)"); + int b; + PInvokesCS.MatchingDllImport.Stdcall.NotBlittable_Double_CdeclUnmanagedCallConv(a, &b); + Assert.AreEqual(expected, b); + } + { + // Should not suppress GC transition + Console.WriteLine($" -- stdcall: UnmanagedCallConv(suppressgctransition)"); + int b; + bool ret = PInvokesCS.MatchingDllImport.Stdcall.NotBlittable_Double_SuppressGCUnmanagedCallConv(a, &b); + Assert.AreEqual(expected, b); + CheckGCMode.Validate(transitionSuppressed: false, ret); + } + } + + public static int Main(string[] args) + { + try + { + DefaultDllImport_Blittable(); + DefaultDllImport_NotBlittable(); + WinapiDllImport_Blittable(); + WinapiDllImport_NotBlittable(); + UnsetPInvokeImpl_Blittable(); + UnsetPInvokeImpl_NotBlittable(); + + // Following tests explicitly check GC mode when possible + CheckGCMode.Initialize(&PInvokesCS.SetIsInCooperativeModeFunction); + SuppressGCTransition_Blittable(); + SuppressGCTransition_NotBlittable(); + MatchingDllImport_Blittable(); + MatchingDllImport_NotBlittable(); + + } + catch (Exception e) + { + Console.WriteLine($"Test Failure: {e}"); + return 101; + } + + return 100; + } +} diff --git a/src/tests/Interop/UnmanagedCallConv/UnmanagedCallConvTest.csproj b/src/tests/Interop/UnmanagedCallConv/UnmanagedCallConvTest.csproj new file mode 100644 index 00000000000000..91b3a6a6a1df79 --- /dev/null +++ b/src/tests/Interop/UnmanagedCallConv/UnmanagedCallConvTest.csproj @@ -0,0 +1,16 @@ + + + Exe + true + + + + + + + + + + + + diff --git a/src/tests/Interop/common/CheckGCMode.cs b/src/tests/Interop/common/CheckGCMode.cs new file mode 100644 index 00000000000000..e895cd594713e6 --- /dev/null +++ b/src/tests/Interop/common/CheckGCMode.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Reflection; + +using TestLibrary; + +unsafe internal class CheckGCMode +{ + internal static bool Enabled = false; + internal static void Initialize(delegate* , void> setIsInCooperativeModeFunction) + { + // GetIsInCooperativeGCModeFunctionPointer is conditionally included based on the runtime build configuration, + // so we check for its existence and only do the explicit mode validation if it is available. + Type marshalType = typeof(object).Assembly.GetType(typeof(System.Runtime.InteropServices.Marshal).FullName); + MethodInfo getFunctionPtr = marshalType.GetMethod("GetIsInCooperativeGCModeFunctionPointer", BindingFlags.NonPublic | BindingFlags.Static); + if (getFunctionPtr != null) + { + var isInCooperativeModeFunc = (delegate* unmanaged)(IntPtr)getFunctionPtr.Invoke(null, null); + setIsInCooperativeModeFunction(isInCooperativeModeFunc); + Enabled = true; + Console.WriteLine("Explicit GC mode check is enabled"); + } + } + + internal static void Validate(bool transitionSuppressed, bool inCooperativeMode) + { + if (!Enabled) + return; + + Assert.AreEqual(transitionSuppressed, inCooperativeMode, $"GC transition should{(transitionSuppressed ? "" : " not")} have been suppressed"); + } + + internal static void Validate(bool transitionSuppressed, int inCooperativeMode) + => Validate(transitionSuppressed, inCooperativeMode != 0); +} diff --git a/src/tests/issues.targets b/src/tests/issues.targets index 61648036eb9eb2..4810f26c1f8644 100644 --- a/src/tests/issues.targets +++ b/src/tests/issues.targets @@ -2716,6 +2716,9 @@ https://github.com/dotnet/runtime/issues/41519 + + https://github.com/dotnet/runtime/issues/41519 + https://github.com/dotnet/runtime/issues/41519 From 8b36d3902eff7acd21e4196a75f527a14e38c940 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Tue, 18 May 2021 08:58:41 -0700 Subject: [PATCH 7/8] Handle UnmanagedCallConv in mono --- src/mono/mono/metadata/marshal.c | 98 +++++++++++++++++++++++++++----- 1 file changed, 85 insertions(+), 13 deletions(-) diff --git a/src/mono/mono/metadata/marshal.c b/src/mono/mono/metadata/marshal.c index 0fa1b9fd3a9684..0d91e6846ad39a 100644 --- a/src/mono/mono/metadata/marshal.c +++ b/src/mono/mono/metadata/marshal.c @@ -112,6 +112,7 @@ static GENERATE_TRY_GET_CLASS_WITH_CACHE (unmanaged_function_pointer_attribute, static GENERATE_TRY_GET_CLASS_WITH_CACHE (suppress_gc_transition_attribute, "System.Runtime.InteropServices", "SuppressGCTransitionAttribute") static GENERATE_TRY_GET_CLASS_WITH_CACHE (unmanaged_callers_only_attribute, "System.Runtime.InteropServices", "UnmanagedCallersOnlyAttribute") +static GENERATE_TRY_GET_CLASS_WITH_CACHE (unmanaged_callconv_attribute, "System.Runtime.InteropServices", "UnmanagedCallConvAttribute") static gboolean type_is_blittable (MonoType *type); @@ -3038,14 +3039,33 @@ mono_emit_marshal (EmitMarshalContext *m, int argnum, MonoType *t, } } -static void +static void +mono_marshal_set_callconv_for_type(MonoType *type, MonoMethodSignature *csig, gboolean *skip_gc_trans /*out*/) +{ + MonoClass *callconv_class_maybe = mono_class_from_mono_type_internal (type); + if ((m_class_get_image (callconv_class_maybe) == mono_defaults.corlib) && !strcmp (m_class_get_name_space (callconv_class_maybe), "System.Runtime.CompilerServices")) { + const char *class_name = m_class_get_name (callconv_class_maybe); + if (!strcmp (class_name, "CallConvCdecl")) + csig->call_convention = MONO_CALL_C; + else if (!strcmp (class_name, "CallConvStdcall")) + csig->call_convention = MONO_CALL_STDCALL; + else if (!strcmp (class_name, "CallConvFastcall")) + csig->call_convention = MONO_CALL_FASTCALL; + else if (!strcmp (class_name, "CallConvThiscall")) + csig->call_convention = MONO_CALL_THISCALL; + else if (!strcmp (class_name, "CallConvSuppressGCTransition") && skip_gc_trans != NULL) + *skip_gc_trans = TRUE; + } +} + +static void mono_marshal_set_callconv_from_modopt (MonoMethod *method, MonoMethodSignature *csig, gboolean set_default) { MonoMethodSignature *sig; int i; #ifdef TARGET_WIN32 - /* + /* * Under windows, delegates passed to native code must use the STDCALL * calling convention. */ @@ -3069,19 +3089,65 @@ mono_marshal_set_callconv_from_modopt (MonoMethod *method, MonoMethodSignature * gboolean required; MonoType *cmod_type = mono_type_get_custom_modifier (sig->ret, i, &required, error); mono_error_assert_ok (error); - MonoClass *cmod_class = mono_class_from_mono_type_internal (cmod_type); - if ((m_class_get_image (cmod_class) == mono_defaults.corlib) && !strcmp (m_class_get_name_space (cmod_class), "System.Runtime.CompilerServices")) { - const char *cmod_class_name = m_class_get_name (cmod_class); - if (!strcmp (cmod_class_name, "CallConvCdecl")) - csig->call_convention = MONO_CALL_C; - else if (!strcmp (cmod_class_name, "CallConvStdcall")) - csig->call_convention = MONO_CALL_STDCALL; - else if (!strcmp (cmod_class_name, "CallConvFastcall")) - csig->call_convention = MONO_CALL_FASTCALL; - else if (!strcmp (cmod_class_name, "CallConvThiscall")) - csig->call_convention = MONO_CALL_THISCALL; + mono_marshal_set_callconv_for_type (cmod_type, csig, NULL); + } +} + +static void +mono_marshal_set_callconv_from_unmanaged_callconv_attribute (MonoMethod *method, MonoMethodSignature *csig, gboolean *skip_gc_trans /*out*/) +{ + MonoClass *attr_class = mono_class_try_get_unmanaged_callconv_attribute_class (); + if (!attr_class) + return; + + ERROR_DECL (error); + MonoCustomAttrInfo *cinfo = mono_custom_attrs_from_method_checked (method, error); + if (!is_ok (error) || !cinfo) { + mono_error_cleanup (error); + return; + } + + int i; + MonoCustomAttrEntry *attr = NULL; + for (i = 0; i < cinfo->num_attrs; ++i) { + MonoClass *ctor_class = cinfo->attrs [i].ctor->klass; + if (ctor_class == attr_class) { + attr = &cinfo->attrs [i]; + break; + } + } + + if (attr == NULL) + return; + + CattrNamedArg *arginfo; + MonoArrayHandleOut typed_args_h = MONO_HANDLE_NEW (MonoArray, NULL); + MonoArrayHandleOut named_args_h = MONO_HANDLE_NEW (MonoArray, NULL); + mono_reflection_create_custom_attr_data_args (mono_defaults.corlib, attr->ctor, attr->data, attr->data_size, typed_args_h, named_args_h, &arginfo, error); + if (!is_ok (error)) { + mono_error_cleanup (error); + return; + } + + MonoArray *named_args = MONO_HANDLE_RAW (named_args_h); + if (named_args) + { + for (i = 0; i < mono_array_length_internal (named_args); ++i) { + CattrNamedArg *info = &arginfo [i]; + g_assert (info->field); + if (strcmp (info->field->name, "CallConvs") != 0) + continue; + + /* CallConvs is an array of types */ + MonoArray *callconv_array = mono_array_get_internal (named_args, MonoArray*, i); + for (int j = 0; j < mono_array_length_internal (callconv_array); ++j) { + MonoReflectionType *callconv_type = mono_array_get_internal (callconv_array, MonoReflectionType*, j); + mono_marshal_set_callconv_for_type (callconv_type->type, csig, skip_gc_trans); + } } } + + g_free (arginfo); } /** @@ -3324,6 +3390,12 @@ mono_marshal_get_native_wrapper (MonoMethod *method, gboolean check_exceptions, mono_custom_attrs_free (cinfo); } + if (csig->call_convention == MONO_CALL_DEFAULT) + { + /* If the calling convention has not been set, check the UnmanagedCallConv attribute */ + mono_marshal_set_callconv_from_unmanaged_callconv_attribute (method, csig, &skip_gc_trans); + } + MonoNativeWrapperFlags flags = aot ? EMIT_NATIVE_WRAPPER_AOT : (MonoNativeWrapperFlags)0; flags |= check_exceptions ? EMIT_NATIVE_WRAPPER_CHECK_EXCEPTIONS : (MonoNativeWrapperFlags)0; flags |= skip_gc_trans ? EMIT_NATIVE_WRAPPER_SKIP_GC_TRANS : (MonoNativeWrapperFlags)0; From 7151dc7b73633c9b8edaf2870a77ae6643b6ddef Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Tue, 18 May 2021 21:58:57 -0700 Subject: [PATCH 8/8] PR feedback --- src/mono/mono/metadata/marshal.c | 66 +++++++++++-------- .../UnmanagedCallConvNative.cpp | 4 +- 2 files changed, 40 insertions(+), 30 deletions(-) diff --git a/src/mono/mono/metadata/marshal.c b/src/mono/mono/metadata/marshal.c index 0d91e6846ad39a..0b5cdf06407980 100644 --- a/src/mono/mono/metadata/marshal.c +++ b/src/mono/mono/metadata/marshal.c @@ -3093,6 +3093,22 @@ mono_marshal_set_callconv_from_modopt (MonoMethod *method, MonoMethodSignature * } } +static MonoArray* +mono_marshal_get_callconvs_array_from_attribute (MonoCustomAttrEntry *attr, CattrNamedArg **arginfo) +{ + HANDLE_FUNCTION_ENTER (); + + ERROR_DECL (error); + MonoArrayHandleOut typed_args_h = MONO_HANDLE_NEW (MonoArray, NULL); + MonoArrayHandleOut named_args_h = MONO_HANDLE_NEW (MonoArray, NULL); + mono_reflection_create_custom_attr_data_args (mono_defaults.corlib, attr->ctor, attr->data, attr->data_size, typed_args_h, named_args_h, arginfo, error); + if (!is_ok (error)) { + mono_error_cleanup (error); + } + + HANDLE_FUNCTION_RETURN_OBJ (named_args_h); +} + static void mono_marshal_set_callconv_from_unmanaged_callconv_attribute (MonoMethod *method, MonoMethodSignature *csig, gboolean *skip_gc_trans /*out*/) { @@ -3117,37 +3133,32 @@ mono_marshal_set_callconv_from_unmanaged_callconv_attribute (MonoMethod *method, } } - if (attr == NULL) - return; - - CattrNamedArg *arginfo; - MonoArrayHandleOut typed_args_h = MONO_HANDLE_NEW (MonoArray, NULL); - MonoArrayHandleOut named_args_h = MONO_HANDLE_NEW (MonoArray, NULL); - mono_reflection_create_custom_attr_data_args (mono_defaults.corlib, attr->ctor, attr->data, attr->data_size, typed_args_h, named_args_h, &arginfo, error); - if (!is_ok (error)) { - mono_error_cleanup (error); - return; - } - - MonoArray *named_args = MONO_HANDLE_RAW (named_args_h); - if (named_args) + if (attr != NULL) { - for (i = 0; i < mono_array_length_internal (named_args); ++i) { - CattrNamedArg *info = &arginfo [i]; - g_assert (info->field); - if (strcmp (info->field->name, "CallConvs") != 0) - continue; - - /* CallConvs is an array of types */ - MonoArray *callconv_array = mono_array_get_internal (named_args, MonoArray*, i); - for (int j = 0; j < mono_array_length_internal (callconv_array); ++j) { - MonoReflectionType *callconv_type = mono_array_get_internal (callconv_array, MonoReflectionType*, j); - mono_marshal_set_callconv_for_type (callconv_type->type, csig, skip_gc_trans); + CattrNamedArg *arginfo; + MonoArray *named_args = mono_marshal_get_callconvs_array_from_attribute(attr, &arginfo); + if (named_args) + { + for (i = 0; i < mono_array_length_internal(named_args); ++i) { + CattrNamedArg *info = &arginfo[i]; + g_assert(info->field); + if (strcmp(info->field->name, "CallConvs") != 0) + continue; + + /* CallConvs is an array of types */ + MonoArray *callconv_array = mono_array_get_internal(named_args, MonoArray *, i); + for (int j = 0; j < mono_array_length_internal(callconv_array); ++j) { + MonoReflectionType *callconv_type = mono_array_get_internal(callconv_array, MonoReflectionType *, j); + mono_marshal_set_callconv_for_type(callconv_type->type, csig, skip_gc_trans); + } } } + + g_free (arginfo); } - g_free (arginfo); + if (!cinfo->cached) + mono_custom_attrs_free(cinfo); } /** @@ -3390,8 +3401,7 @@ mono_marshal_get_native_wrapper (MonoMethod *method, gboolean check_exceptions, mono_custom_attrs_free (cinfo); } - if (csig->call_convention == MONO_CALL_DEFAULT) - { + if (csig->call_convention == MONO_CALL_DEFAULT) { /* If the calling convention has not been set, check the UnmanagedCallConv attribute */ mono_marshal_set_callconv_from_unmanaged_callconv_attribute (method, csig, &skip_gc_trans); } diff --git a/src/tests/Interop/UnmanagedCallConv/UnmanagedCallConvNative.cpp b/src/tests/Interop/UnmanagedCallConv/UnmanagedCallConvNative.cpp index 7abbf8a6508741..e3ecd95ef8be38 100644 --- a/src/tests/Interop/UnmanagedCallConv/UnmanagedCallConvNative.cpp +++ b/src/tests/Interop/UnmanagedCallConv/UnmanagedCallConvNative.cpp @@ -10,8 +10,8 @@ namespace BOOL Double(int a, int* b) { - if (b != NULL) - *b = a * 2; + if (b != NULL) + *b = a * 2; BOOL ret = FALSE; if (s_isInCooperativeMode != nullptr)