From b5b7386ba44c0f31cbe5b5f26da38cbb195bea4a Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 27 Jul 2026 21:04:42 -0700 Subject: [PATCH 1/8] Implement convertPInvokeCalliToCall for CoreCLR Unmanaged calli call sites that require marshalling used to be dispatched through CORINFO_HELP_PINVOKE_CALLI -> GenericPInvokeCalliHelper, which erected a PInvokeCalliFrame at call time and built the IL stub from a VASigCookie, passing the unmanaged target as the stub's secret argument. Instead, implement the convertPInvokeCalliToCall JIT-EE API on CoreCLR. During JIT of the caller the runtime creates (and caches by signature in the IL stub cache) the MethodDesc of the marshalling stub, and the JIT re-imports the call site as a regular call to it. The stub is JIT-compiled lazily through its precode on first call, since it cannot be compiled while the caller is being compiled. The stub IL itself is transient IL, generated when the stub is compiled and discarded afterwards, the same way non-vararg P/Invokes work. Everything the generator needs is recovered from the stub's own signature: it is the call site signature with a managed calling convention plus a trailing parameter typed as the function pointer type of the call site. That parameter carries the unmanaged target at run time and preserves the unmanaged calling convention (including modopts such as CallConvSuppressGCTransition). With no remaining way to reach the helper - crossgen2 already defers such methods to the runtime JIT via GetCookieForPInvokeCalliSig - the old machinery is removed: the GenericPInvokeCalliHelper assembly stubs on all architectures, GenericPInvokeCalliStubWorker, PInvokeCalliFrame and its data descriptor, and the stub manager support. CORINFO_HELP_PINVOKE_CALLI itself is left in the JIT-EE interface. Also fixes two problems this exposed: * TryGetUnmanagedCallingConventionFromModOptSigStartingAtRetType could never find a calling convention in a module independent signature. It skipped optional ELEMENT_TYPE_CMOD_INTERNAL modifiers, which is inverted relative to the token based case and to how ConvertToInternalExactlyOne encodes ELEMENT_TYPE_CMOD_OPT, and it resolved the token in the signature's module rather than the modifier type's module. * IL stubs now report CORINFO_FLG_DONT_INLINE. They can never be inlined anyway, but as call targets of jitted code the JIT would ask for their IL while evaluating them as inline candidates, and ILStubResolver::GetCodeInfo has no IL to hand back once the stub has been compiled. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/inc/corinfo.h | 1 + src/coreclr/inc/jithelpers.h | 2 +- src/coreclr/jit/importercalls.cpp | 14 +- .../JitInterface/CorInfoImpl.RyuJit.cs | 1 - src/coreclr/vm/FrameTypes.h | 1 - src/coreclr/vm/amd64/PInvokeStubs.asm | 54 -- src/coreclr/vm/amd64/pinvokestubs.S | 53 -- src/coreclr/vm/arm/pinvokestubs.S | 10 - src/coreclr/vm/arm64/PInvokeStubs.asm | 18 +- src/coreclr/vm/arm64/pinvokestubs.S | 13 +- src/coreclr/vm/callconvbuilder.cpp | 13 +- src/coreclr/vm/ceeload.cpp | 63 ++- src/coreclr/vm/ceeload.h | 4 + src/coreclr/vm/cgensys.h | 2 - .../vm/datadescriptor/datadescriptor.inc | 5 - src/coreclr/vm/dllimport.cpp | 480 ++++++++++++++---- src/coreclr/vm/dllimport.h | 20 + src/coreclr/vm/frames.cpp | 38 -- src/coreclr/vm/frames.h | 80 +-- src/coreclr/vm/i386/asmhelpers.S | 47 -- src/coreclr/vm/i386/asmhelpers.asm | 44 -- src/coreclr/vm/i386/cgenx86.cpp | 17 - src/coreclr/vm/jitinterface.cpp | 100 +++- src/coreclr/vm/loongarch64/pinvokestubs.S | 12 +- src/coreclr/vm/method.hpp | 5 + src/coreclr/vm/prestub.cpp | 21 +- src/coreclr/vm/riscv64/pinvokestubs.S | 12 +- src/coreclr/vm/stubmgr.cpp | 30 +- src/coreclr/vm/stubmgr.h | 1 - src/coreclr/vm/wasm/helpers.cpp | 5 - 30 files changed, 602 insertions(+), 564 deletions(-) diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index f85c006c9a1d1a..a01c79d6dbfbda 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -3533,6 +3533,7 @@ class ICorDynamicInfo : public ICorStaticInfo ) = 0; // Optionally, convert calli to regular method call. This is for PInvoke argument marshalling. + // On success, pResolvedToken->hMethod is set to the method that should be called instead. virtual bool convertPInvokeCalliToCall( CORINFO_RESOLVED_TOKEN * pResolvedToken, bool fMustConvert diff --git a/src/coreclr/inc/jithelpers.h b/src/coreclr/inc/jithelpers.h index 7742945b23e7e4..d9b3f7972f136e 100644 --- a/src/coreclr/inc/jithelpers.h +++ b/src/coreclr/inc/jithelpers.h @@ -216,7 +216,7 @@ DYNAMICJITHELPER(CORINFO_HELP_PROF_FCN_TAILCALL, JIT_ProfilerEnterLeaveTailcallStub, METHOD__NIL) // Miscellaneous - JITHELPER(CORINFO_HELP_PINVOKE_CALLI, GenericPInvokeCalliHelper, METHOD__NIL) + JITHELPER(CORINFO_HELP_PINVOKE_CALLI, NULL, METHOD__NIL) #if defined(TARGET_X86) && !defined(UNIX_X86_ABI) JITHELPER(CORINFO_HELP_TAILCALL, JIT_TailCall, METHOD__NIL) diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index a8fbed9a28b72d..bb905baf006cfc 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -339,10 +339,14 @@ var_types Compiler::impImportCall(OPCODE opcode, if (opcode == CEE_CALLI) { - if (IsTargetAbi(CORINFO_NATIVEAOT_ABI)) + // ReadyToRun does not support converting an unmanaged calli into a call to a + // marshalling stub; such call sites are handled by the PInvoke calli cookie below. + if (!IsReadyToRun()) { if (info.compCompHnd->convertPInvokeCalliToCall(pResolvedToken, !impCanPInvokeInlineCallSite(compCurBB))) { + // The VM only fills in hMethod; derive the rest of the token from it. + pResolvedToken->hClass = info.compCompHnd->getMethodClass(pResolvedToken->hMethod); eeGetCallInfo(pResolvedToken, nullptr, CORINFO_CALLINFO_ALLOWINSTPARAM, callInfo); return impImportCall(CEE_CALL, pResolvedToken, nullptr, nullptr, prefixFlags, callInfo, rawILOffset); } @@ -7325,11 +7329,11 @@ void Compiler::impCheckForPInvokeCall( } optNativeCallCount++; - if (methHnd == nullptr && (IsTargetAbi(CORINFO_NATIVEAOT_ABI) || - (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_IL_STUB) && !compIsForInlining()))) + if (methHnd == nullptr && + (!IsReadyToRun() || (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_IL_STUB) && !compIsForInlining()))) { - // PInvoke CALLI in NativeAOT ABI must be always inlined. Non-inlineable CALLI cases have been - // converted to regular method calls earlier using convertPInvokeCalliToCall. + // PInvoke CALLI outside of ReadyToRun must always be inlined. Non-inlineable CALLI cases + // have been converted to regular method calls earlier using convertPInvokeCalliToCall. // PInvoke CALLI in IL stubs must be inlined } diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs index 14f0f9b6c1e33a..0d486b639a870e 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs @@ -2021,7 +2021,6 @@ private bool convertPInvokeCalliToCall(ref CORINFO_RESOLVED_TOKEN pResolvedToken return false; pResolvedToken.hMethod = ObjectToHandle(stub); - pResolvedToken.hClass = ObjectToHandle(stub.OwningType); return true; } diff --git a/src/coreclr/vm/FrameTypes.h b/src/coreclr/vm/FrameTypes.h index 03ea0dd0f4cdb6..42601a1b8bcfea 100644 --- a/src/coreclr/vm/FrameTypes.h +++ b/src/coreclr/vm/FrameTypes.h @@ -20,7 +20,6 @@ FRAME_TYPE_NAME(FuncEvalFrame) #endif // DEBUGGING_SUPPORTED #ifdef FEATURE_COMINTEROP #endif // FEATURE_COMINTEROP -FRAME_TYPE_NAME(PInvokeCalliFrame) #ifdef FEATURE_HIJACK FRAME_TYPE_NAME(HijackFrame) #endif // FEATURE_HIJACK diff --git a/src/coreclr/vm/amd64/PInvokeStubs.asm b/src/coreclr/vm/amd64/PInvokeStubs.asm index 2238239e558b88..fae9b6ac676b6b 100644 --- a/src/coreclr/vm/amd64/PInvokeStubs.asm +++ b/src/coreclr/vm/amd64/PInvokeStubs.asm @@ -4,65 +4,11 @@ include AsmMacros.inc include AsmConstants.inc -extern GenericPInvokeCalliStubWorker:proc extern VarargPInvokeStubWorker:proc extern JIT_PInvokeEndRarePath:proc extern g_TrapReturningThreads:DWORD -; -; in: -; PINVOKE_CALLI_TARGET_REGISTER (r10) = unmanaged target -; PINVOKE_CALLI_SIGTOKEN_REGNUM (r11) = sig token -; -; out: -; METHODDESC_REGISTER (r10) = unmanaged target -; -LEAF_ENTRY GenericPInvokeCalliHelper, _TEXT - - ; - ; check for existing IL stub - ; - mov rax, [PINVOKE_CALLI_SIGTOKEN_REGISTER + OFFSETOF__VASigCookie__pPInvokeILStub] - test rax, rax - jz GenericPInvokeCalliGenILStub - - ; - ; jump to existing IL stub - ; - jmp rax - -LEAF_END GenericPInvokeCalliHelper, _TEXT - -NESTED_ENTRY GenericPInvokeCalliGenILStub, _TEXT - - PROLOG_WITH_TRANSITION_BLOCK - - ; - ; save target - ; - mov r12, METHODDESC_REGISTER - mov r13, PINVOKE_CALLI_SIGTOKEN_REGISTER - - ; - ; GenericPInvokeCalliStubWorker(TransitionBlock * pTransitionBlock, VASigCookie * pVASigCookie, PCODE pUnmanagedTarget) - ; - lea rcx, [rsp + __PWTB_TransitionBlock] ; pTransitionBlock* - mov rdx, PINVOKE_CALLI_SIGTOKEN_REGISTER ; pVASigCookie - mov r8, METHODDESC_REGISTER ; pUnmanagedTarget - call GenericPInvokeCalliStubWorker - - ; - ; restore target - ; - mov METHODDESC_REGISTER, r12 - mov PINVOKE_CALLI_SIGTOKEN_REGISTER, r13 - - EPILOG_WITH_TRANSITION_BLOCK_TAILCALL - jmp GenericPInvokeCalliHelper - -NESTED_END GenericPInvokeCalliGenILStub, _TEXT - LEAF_ENTRY VarargPInvokeStub, _TEXT mov PINVOKE_CALLI_SIGTOKEN_REGISTER, rcx jmp VarargPInvokeStubHelper diff --git a/src/coreclr/vm/amd64/pinvokestubs.S b/src/coreclr/vm/amd64/pinvokestubs.S index 58d96db85f0d4d..ae1e1b166f5fed 100644 --- a/src/coreclr/vm/amd64/pinvokestubs.S +++ b/src/coreclr/vm/amd64/pinvokestubs.S @@ -6,59 +6,6 @@ #include "asmconstants.h" -// -// in: -// PINVOKE_CALLI_TARGET_REGISTER (r10) = unmanaged target -// PINVOKE_CALLI_SIGTOKEN_REGNUM (r11) = sig token -// -// out: -// METHODDESC_REGISTER (r10) = unmanaged target -// -LEAF_ENTRY GenericPInvokeCalliHelper, _TEXT - - // - // check for existing IL stub - // - mov rax, [PINVOKE_CALLI_SIGTOKEN_REGISTER + OFFSETOF__VASigCookie__pPInvokeILStub] - test rax, rax - jz C_FUNC(GenericPInvokeCalliGenILStub) - - // - // jump to existing IL stub - // - jmp rax - -LEAF_END GenericPInvokeCalliHelper, _TEXT - -NESTED_ENTRY GenericPInvokeCalliGenILStub, _TEXT, NoHandler - - PROLOG_WITH_TRANSITION_BLOCK - - // - // save target - // - mov r12, METHODDESC_REGISTER - mov r13, PINVOKE_CALLI_SIGTOKEN_REGISTER - - // - // GenericPInvokeCalliStubWorker(TransitionBlock * pTransitionBlock, VASigCookie * pVASigCookie, PCODE pUnmanagedTarget) - // - lea rdi, [rsp + __PWTB_TransitionBlock] // pTransitionBlock* - mov rsi, PINVOKE_CALLI_SIGTOKEN_REGISTER // pVASigCookie - mov rdx, METHODDESC_REGISTER // pUnmanagedTarget - call C_FUNC(GenericPInvokeCalliStubWorker) - - // - // restore target - // - mov METHODDESC_REGISTER, r12 - mov PINVOKE_CALLI_SIGTOKEN_REGISTER, r13 - - EPILOG_WITH_TRANSITION_BLOCK_TAILCALL - jmp C_FUNC(GenericPInvokeCalliHelper) - -NESTED_END GenericPInvokeCalliGenILStub, _TEXT - LEAF_ENTRY VarargPInvokeStub, _TEXT mov PINVOKE_CALLI_SIGTOKEN_REGISTER, rdi jmp C_FUNC(VarargPInvokeStubHelper) diff --git a/src/coreclr/vm/arm/pinvokestubs.S b/src/coreclr/vm/arm/pinvokestubs.S index b102ae12d793e4..f0d22819a5fb9f 100644 --- a/src/coreclr/vm/arm/pinvokestubs.S +++ b/src/coreclr/vm/arm/pinvokestubs.S @@ -200,16 +200,6 @@ LOCAL_LABEL(RarePath): // PINVOKE_STUB VarargPInvokeStub, VarargPInvokeGenILStub, VarargPInvokeStubWorker, r0, 0 -// ------------------------------------------------------------------ -// GenericPInvokeCalliHelper & GenericPInvokeCalliGenILStub -// Helper for generic pinvoke calli instruction -// -// in: -// r4 = VASigCookie* -// r12 = Unmanaged target -// -PINVOKE_STUB GenericPInvokeCalliHelper, GenericPInvokeCalliGenILStub, GenericPInvokeCalliStubWorker r4, 1 - // ------------------------------------------------------------------ // VarargPInvokeStub_RetBuffArg & VarargPInvokeGenILStub_RetBuffArg // Vararg PInvoke Stub when the method has a hidden return buffer arg diff --git a/src/coreclr/vm/arm64/PInvokeStubs.asm b/src/coreclr/vm/arm64/PInvokeStubs.asm index e02cea49ac435c..dcbacd39d97722 100644 --- a/src/coreclr/vm/arm64/PInvokeStubs.asm +++ b/src/coreclr/vm/arm64/PInvokeStubs.asm @@ -9,7 +9,6 @@ IMPORT VarargPInvokeStubWorker - IMPORT GenericPInvokeCalliStubWorker IMPORT JIT_PInvokeEndRarePath IMPORT g_TrapReturningThreads @@ -21,7 +20,7 @@ ; ; Params :- ; $FuncPrefix : prefix of the function name for the stub -; Eg. VarargPinvoke, GenericPInvokeCalli +; Eg. VarargPinvoke ; $VASigCookieReg : register which contains the VASigCookie ; $SaveFPArgs : "Yes" or "No" . For varidic functions FP Args are not present in FP regs ; So need not save FP Args registers for vararg Pinvoke @@ -33,11 +32,7 @@ GBLS __PInvokeGenStubFuncName GBLS __PInvokeStubWorkerName - IF "$FuncPrefix" == "GenericPInvokeCalli" -__PInvokeStubFuncName SETS "$FuncPrefix":CC:"Helper" - ELSE __PInvokeStubFuncName SETS "$FuncPrefix":CC:"Stub" - ENDIF __PInvokeGenStubFuncName SETS "$FuncPrefix":CC:"GenILStub" __PInvokeStubWorkerName SETS "$FuncPrefix":CC:"StubWorker" @@ -176,16 +171,5 @@ RarePath PINVOKE_STUB VarargPInvoke, x0, x12, {false} -; ------------------------------------------------------------------ -; GenericPInvokeCalliHelper & GenericPInvokeCalliGenILStub -; Helper for generic pinvoke calli instruction -; -; in: -; x15 = VASigCookie* -; x12 = Unmanaged target -; - PINVOKE_STUB GenericPInvokeCalli, x15, x12, {true} - - ; Must be at very end of file END diff --git a/src/coreclr/vm/arm64/pinvokestubs.S b/src/coreclr/vm/arm64/pinvokestubs.S index 61b96e94f50b80..a6dce447ef5219 100644 --- a/src/coreclr/vm/arm64/pinvokestubs.S +++ b/src/coreclr/vm/arm64/pinvokestubs.S @@ -11,7 +11,7 @@ // // Params :- // $FuncPrefix : prefix of the function name for the stub -// Eg. VarargPinvoke, GenericPInvokeCalli +// Eg. VarargPinvoke // $VASigCookieReg : register which contains the VASigCookie // $SaveFPArgs : "Yes" or "No" . For varidic functions FP Args are not present in FP regs // So need not save FP Args registers for vararg Pinvoke @@ -157,14 +157,3 @@ LOCAL_LABEL(RarePath): // x12 = MethodDesc * // PINVOKE_STUB VarargPInvokeStub, VarargPInvokeGenILStub, VarargPInvokeStubWorker, x0, x12, 0 - - -// ------------------------------------------------------------------ -// GenericPInvokeCalliHelper & GenericPInvokeCalliGenILStub -// Helper for generic pinvoke calli instruction -// -// in: -// x15 = VASigCookie* -// x12 = Unmanaged target -// -PINVOKE_STUB GenericPInvokeCalliHelper, GenericPInvokeCalliGenILStub, GenericPInvokeCalliStubWorker, x15, x12, 1 diff --git a/src/coreclr/vm/callconvbuilder.cpp b/src/coreclr/vm/callconvbuilder.cpp index a42b3b908a07f7..c3ad9a2800b61e 100644 --- a/src/coreclr/vm/callconvbuilder.cpp +++ b/src/coreclr/vm/callconvbuilder.cpp @@ -393,7 +393,6 @@ HRESULT CallConv::TryGetUnmanagedCallingConventionFromModOptSigStartingAtRetType LPCSTR typeName; if (*pWalk == ELEMENT_TYPE_CMOD_INTERNAL) { - // Skip internal modifiers pWalk++; if (pWalk + 1 + sizeof(void*) > pSig + cSig) { @@ -405,10 +404,14 @@ HRESULT CallConv::TryGetUnmanagedCallingConventionFromModOptSigStartingAtRetType void* pType; pWalk += CorSigUncompressPointer(pWalk, &pType); TypeHandle type = TypeHandle::FromPtr(pType); - - if (!required) + + // Calling conventions are only ever expressed as optional modifiers. A module + // independent signature encodes ELEMENT_TYPE_CMOD_OPT as an internal modifier with + // the "is required" byte cleared (see code:SigPointer::ConvertToInternalExactlyOne), + // so skip the required ones just like the token based case below does. + if (required) continue; - + tokenLookupModule = GetScopeHandle(type.GetModule()); tk = type.GetCl(); } @@ -430,7 +433,7 @@ HRESULT CallConv::TryGetUnmanagedCallingConventionFromModOptSigStartingAtRetType } // Check for CallConv types specified in modopt - if (FAILED(GetNameOfTypeRefOrDef(pModule, tk, &typeNamespace, &typeName))) + if (FAILED(GetNameOfTypeRefOrDef(tokenLookupModule, tk, &typeNamespace, &typeName))) continue; if (::strcmp(typeNamespace, CMOD_CALLCONV_NAMESPACE) != 0) diff --git a/src/coreclr/vm/ceeload.cpp b/src/coreclr/vm/ceeload.cpp index c8e7e987be3f7a..c1c6346c34acb1 100644 --- a/src/coreclr/vm/ceeload.cpp +++ b/src/coreclr/vm/ceeload.cpp @@ -4231,46 +4231,59 @@ static bool MethodSignatureContainsGenericVariables(SigParser& sp) } //========================================================================== -// Enregisters a VASig. +// Computes the module that should own runtime artifacts (VASigCookies, CALLI IL stubs) +// created for the given standalone signature. +// +// The generic context is stripped if it is not actually used by the signature. This is +// necessary for both: +// - Performance: allows more sharing of the created artifacts +// - Functionality: built-in runtime marshalling is disallowed for generic signatures //========================================================================== -VASigCookie *Module::GetVASigCookie(Signature vaSignature, const SigTypeContext* typeContext) +Module* Module::GetLoaderModuleForSignature(Signature signature, SigTypeContext* pTypeContext) { CONTRACTL { INSTANCE_CHECK; STANDARD_VM_CHECK; - INJECT_FAULT(COMPlusThrowOM()); + PRECONDITION(CheckPointer(pTypeContext)); } CONTRACTL_END; - SigTypeContext emptyContext; + SigParser sigParser = signature.CreateSigParser(); - Module* pLoaderModule = this; - if (!typeContext->IsEmpty()) - { - // Strip the generic context if it is not actually used by the signature. It is nececessary for both: - // - Performance: allow more sharing of vasig cookies - // - Functionality: built-in runtime marshalling is disallowed for generic signatures - SigParser sigParser = vaSignature.CreateSigParser(); - if (MethodSignatureContainsGenericVariables(sigParser)) - { - pLoaderModule = ClassLoader::ComputeLoaderModuleWorker(this, mdTokenNil, typeContext->m_classInst, typeContext->m_methodInst); - } - else - { - typeContext = &emptyContext; - } - } - else + if (pTypeContext->IsEmpty()) { -#ifdef _DEBUG // The method signature should not contain any generic variables if the generic context is not provided. - SigParser sigParser = vaSignature.CreateSigParser(); _ASSERTE(!MethodSignatureContainsGenericVariables(sigParser)); -#endif + return this; } - VASigCookie *pCookie = GetVASigCookieWorker(this, pLoaderModule, vaSignature, typeContext); + if (!MethodSignatureContainsGenericVariables(sigParser)) + { + *pTypeContext = SigTypeContext(); + return this; + } + + return ClassLoader::ComputeLoaderModuleWorker(this, mdTokenNil, pTypeContext->m_classInst, pTypeContext->m_methodInst); +} + +//========================================================================== +// Enregisters a VASig. +//========================================================================== +VASigCookie *Module::GetVASigCookie(Signature vaSignature, const SigTypeContext* typeContext) +{ + CONTRACTL + { + INSTANCE_CHECK; + STANDARD_VM_CHECK; + INJECT_FAULT(COMPlusThrowOM()); + } + CONTRACTL_END; + + SigTypeContext localContext = *typeContext; + Module* pLoaderModule = GetLoaderModuleForSignature(vaSignature, &localContext); + + VASigCookie *pCookie = GetVASigCookieWorker(this, pLoaderModule, vaSignature, &localContext); return pCookie; } diff --git a/src/coreclr/vm/ceeload.h b/src/coreclr/vm/ceeload.h index 8f0d190464fa87..16a6d1afb66dd1 100644 --- a/src/coreclr/vm/ceeload.h +++ b/src/coreclr/vm/ceeload.h @@ -1418,6 +1418,10 @@ class Module : public ModuleBase // Enregisters a VASig. VASigCookie *GetVASigCookie(Signature vaSignature, const SigTypeContext* typeContext); + + // Computes the module that owns runtime artifacts created for a standalone signature. + // Clears *pTypeContext if the signature does not actually use the generic context. + Module* GetLoaderModuleForSignature(Signature signature, SigTypeContext* pTypeContext); private: static VASigCookie *GetVASigCookieWorker(Module* pDefiningModule, Module* pLoaderModule, Signature vaSignature, const SigTypeContext* typeContext); diff --git a/src/coreclr/vm/cgensys.h b/src/coreclr/vm/cgensys.h index 9f0a24383f8ff5..49f5da8e49a914 100644 --- a/src/coreclr/vm/cgensys.h +++ b/src/coreclr/vm/cgensys.h @@ -50,8 +50,6 @@ extern "C" void STDCALL VarargPInvokeStubWorker(TransitionBlock* pTransitionBloc extern "C" void STDCALL VarargPInvokeStub(void); extern "C" void STDCALL VarargPInvokeStub_RetBuffArg(void); -extern "C" void STDCALL GenericPInvokeCalliStubWorker(TransitionBlock * pTransitionBlock, VASigCookie * pVASigCookie, PCODE pUnmanagedTarget); -extern "C" void STDCALL GenericPInvokeCalliHelper(void); extern "C" PCODE STDCALL ExternalMethodFixupWorker(TransitionBlock * pTransitionBlock, TADDR pIndirection, DWORD sectionIndex, Module * pModule); diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index a8163e3d1dfb41..d60f7f99a749ff 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -1319,11 +1319,6 @@ CDAC_TYPE_SIZE(sizeof(ExternalMethodFrame)) CDAC_TYPE_FIELD(ExternalMethodFrame, T_POINTER, Indirection, cdac_data::Indirection) CDAC_TYPE_END(ExternalMethodFrame) -CDAC_TYPE_BEGIN(PInvokeCalliFrame) -CDAC_TYPE_SIZE(sizeof(PInvokeCalliFrame)) -CDAC_TYPE_FIELD(PInvokeCalliFrame, T_POINTER, VASigCookiePtr, cdac_data::VASigCookiePtr) -CDAC_TYPE_END(PInvokeCalliFrame) - CDAC_TYPE_BEGIN(DynamicHelperFrame) CDAC_TYPE_SIZE(sizeof(DynamicHelperFrame)) CDAC_TYPE_FIELD(DynamicHelperFrame, T_INT32, DynamicHelperFrameFlags, cdac_data::DynamicHelperFrameFlags) diff --git a/src/coreclr/vm/dllimport.cpp b/src/coreclr/vm/dllimport.cpp index 613e358e862fc1..d4f87c6fb47cbe 100644 --- a/src/coreclr/vm/dllimport.cpp +++ b/src/coreclr/vm/dllimport.cpp @@ -258,6 +258,12 @@ class ILStubState _ASSERTE(m_dwStubFlags == dwStubFlags); } + void SetCalliTargetArgIndex(UINT uArgIdx) + { + WRAPPER_NO_CONTRACT; + m_slIL.SetCalliTargetArgIndex(uArgIdx); + } + virtual void MarshalReturn(MarshalInfo* pInfo, int argOffset) { CONTRACTL @@ -771,10 +777,10 @@ class ILStubState // Forward delegate stubs get all the context they need in 'this' so they // don't use the secret parameter. } - else if (SF_IsForwardPInvokeStub(m_dwStubFlags) - && !SF_IsCALLIStub(m_dwStubFlags) && !SF_IsVarArgStub(m_dwStubFlags)) + else if (SF_IsForwardPInvokeStub(m_dwStubFlags) && !SF_IsVarArgStub(m_dwStubFlags)) { - // Regular PInvokes don't use the secret parameter + // Regular PInvokes and unmanaged CALLI stubs don't use the secret parameter. + // Unmanaged CALLI stubs receive the native target as their last argument. } else { @@ -794,7 +800,9 @@ class ILStubState // If we're not in a Reverse stub, the signatures are correct, // but we need to convert the signature into a module-independent form // if our signature is not backed by metadata. - if (pStubMD->IsDynamicMethod()) + // Stubs backed by transient IL already have a permanent signature and their IL can + // be generated more than once, so they must not be rewritten here. + if (pStubMD->IsDynamicMethod() && !pStubMD->AsDynamicMethodDesc()->UsesTransientIL()) { ConvertMethodDescSigToModuleIndependentSig(pStubMD); } @@ -1864,6 +1872,7 @@ PInvokeStubLinker::PInvokeStubLinker( m_ErrorResID(-1), m_ErrorParamIdx(-1), m_iLCIDParamIdx(iLCIDParamIdx), + m_uCalliTargetArgIdx(0), m_dwStubFlags(dwStubFlags) { STANDARD_VM_CONTRACT; @@ -1915,6 +1924,13 @@ void PInvokeStubLinker::SetCallingConvention(CorInfoCallConvExtension unmngCallC } } +void PInvokeStubLinker::SetCalliTargetArgIndex(UINT uArgIdx) +{ + LIMITED_METHOD_CONTRACT; + _ASSERTE(SF_IsCALLIStub(m_dwStubFlags)); + m_uCalliTargetArgIdx = uArgIdx; +} + void PInvokeStubLinker::EmitSetArgMarshalIndex(ILCodeStream* pcsEmit, UINT uArgIdx) { WRAPPER_NO_CONTRACT; @@ -2360,9 +2376,9 @@ void PInvokeStubLinker::DoPInvoke(ILCodeStream *pcsEmit, DWORD dwStubFlags, Meth #endif // FEATURE_COMINTEROP else if (SF_IsCALLIStub(dwStubFlags)) // unmanaged CALLI { - // for managed-to-unmanaged CALLI that requires marshaling, the target is passed - // as the secret argument to the stub by GenericPInvokeCalliHelper - EmitLoadStubContext(pcsEmit, dwStubFlags); + // For managed-to-unmanaged CALLI that requires marshaling, the JIT passes the native + // target as the last argument of the stub (see code:CEEInfo::convertPInvokeCalliToCall). + pcsEmit->EmitLDARG(m_uCalliTargetArgIdx); } else if (SF_IsVarArgStub(dwStubFlags)) // vararg P/Invoke { @@ -2436,8 +2452,8 @@ void PInvokeStubLinker::EmitLogNativeArgument(ILCodeStream* pslILEmit, DWORD dwP if (SF_IsCALLIStub(m_dwStubFlags)) { - // get the secret argument via intrinsic - pslILEmit->EmitCALL(METHOD__STUBHELPERS__GET_STUB_CONTEXT, 0, 1); + // The native target is passed as the last argument of the stub. + pslILEmit->EmitLDARG(m_uCalliTargetArgIdx); } else { @@ -3576,6 +3592,7 @@ static MarshalInfo::MarshalType DoMarshalReturnValue(MetaSig& msig, CorNativeLinkType nlType, CorNativeLinkFlags nlFlags, UINT argidx, // this is used for reverse pinvoke hresult swapping + int numArgs, // number of marshaled arguments ILStubState* pss, int argOffset, DWORD dwStubFlags, @@ -3621,7 +3638,7 @@ static MarshalInfo::MarshalType DoMarshalReturnValue(MetaSig& msig, nlFlags, FALSE, argidx, - msig.NumFixedArgs(), + numArgs, SF_IsBestFit(dwStubFlags), SF_IsThrowOnUnmappableChar(dwStubFlags), TRUE, @@ -3810,6 +3827,16 @@ static COR_ILMETHOD_DECODER* CreatePInvokeStubWorker( int numArgs = msig.NumFixedArgs(); + if (SF_IsCALLIStub(dwStubFlags)) + { + // The last argument of an unmanaged CALLI stub is the native target, which the JIT + // supplies from the top of the evaluation stack at the original calli site. It is not + // part of the native signature, so exclude it from marshaling. + _ASSERTE(numArgs > 0); + numArgs--; + pss->SetCalliTargetArgIndex((UINT)numArgs); + } + // thiscall must have at least one parameter (the "this") if (fThisCall && numArgs == 0) COMPlusThrow(kMarshalDirectiveException, IDS_EE_NDIRECT_BADNATL_THISCALL); @@ -3938,6 +3965,7 @@ static COR_ILMETHOD_DECODER* CreatePInvokeStubWorker( nlType, nlFlags, argidx, + numArgs, pss, argOffset, dwStubFlags, @@ -5478,6 +5506,87 @@ namespace return pStubMD; } + + // Creates (or finds in the IL stub cache) the MethodDesc of an interop IL stub without + // generating its IL. The IL is produced later, on demand, as transient IL while the stub is + // being compiled - see code:MethodDesc::TryGenerateTransientILImplementation. + MethodDesc* CreateInteropILStubMethodDesc( + StubSigDesc* pSigDesc, + CorNativeLinkType nlType, + CorNativeLinkFlags nlFlags, + CorInfoCallConvExtension unmgdCallConv, + DWORD dwStubFlags + ) + { + CONTRACTL + { + STANDARD_VM_CHECK; + + PRECONDITION(CheckPointer(pSigDesc)); + PRECONDITION(pSigDesc->m_pMD == NULL); + PRECONDITION(SF_IsCALLIStub(dwStubFlags)); + } + CONTRACTL_END; + + if (IsSharedStubScenario(dwStubFlags)) + dwStubFlags |= PINVOKESTUB_FL_SHARED_STUB; + + // The stub signature has no metadata behind it, so there are no parameter tokens. + int numArgs; + { + MetaSig msig(pSigDesc->m_sig, pSigDesc->m_pModule, &pSigDesc->m_typeContext); + numArgs = msig.NumFixedArgs(); + } + + int numParamTokens = numArgs + 1; + mdParamDef* pParamTokenArray = (mdParamDef*)_alloca(numParamTokens * sizeof(mdParamDef)); + memset(pParamTokenArray, 0, numParamTokens * sizeof(mdParamDef)); + + PInvokeStubParameters params(pSigDesc->m_sig, + &pSigDesc->m_typeContext, + pSigDesc->m_pModule, + pSigDesc->m_pLoaderModule, + nlType, + nlFlags, + unmgdCallConv, + dwStubFlags, + numParamTokens, + pParamTokenArray, + -1 /* iLCIDArg */, + pSigDesc->m_pMT + ); + + MethodDesc* pStubMD; + { + ILStubCreatorHelper ilStubCreatorHelper(NULL, ¶ms); + + // take the domain level lock so that the cache lookup and the chunk linking below + // are done consistently with code:CreateInteropILStub + ListLockHolder pILStubLock(AppDomain::GetCurrentDomain()->GetILStubGenLock()); + + ilStubCreatorHelper.GetStubMethodDesc(); + pStubMD = ilStubCreatorHelper.GetStubMD(); + + if (!pSigDesc->m_typeContext.IsEmpty()) + { + // For generic calli, we only support blittable types. This has to be checked + // against the stub signature because that is the instantiated one. + if (PInvoke::MarshalingRequired(NULL, pStubMD->GetSigPointer(), pSigDesc->m_pModule, &pSigDesc->m_typeContext)) + COMPlusThrow(kMarshalDirectiveException, IDS_EE_BADMARSHAL_GENERICS_RESTRICTION); + } + + // The stub's own resolver is what the runtime uses to identify the method behind the + // stub's dynamic scope. Normally IL generation establishes that link; with transient + // IL there is no eager generation, so do it here. + pStubMD->AsDynamicMethodDesc()->GetILStubResolver()->SetStubMethodDesc(pStubMD); + + AddMethodDescChunkWithLockTaken(¶ms, pStubMD); + + ilStubCreatorHelper.SuppressRelease(); + } + + return pStubMD; + } } MethodDesc* PInvoke::CreateCLRToNativeILStub( @@ -6031,10 +6140,10 @@ EXTERN_C void* PInvokeImportWorker(PInvokeMethodDesc* pMD) } //=========================================================================== -// Support for Pinvoke Calli instruction +// Support for vararg Pinvoke and the Pinvoke Calli instruction // //=========================================================================== -static void GetILStubForCalli(VASigCookie* pVASigCookie, MethodDesc* pMD) +static void GetILStubForVarargPInvoke(VASigCookie* pVASigCookie, MethodDesc* pMD) { CONTRACTL { @@ -6043,7 +6152,8 @@ static void GetILStubForCalli(VASigCookie* pVASigCookie, MethodDesc* pMD) ENTRY_POINT; MODE_ANY; PRECONDITION(CheckPointer(pVASigCookie)); - PRECONDITION(CheckPointer(pMD, NULL_OK)); + PRECONDITION(CheckPointer(pMD)); + PRECONDITION(pMD->IsPInvoke()); } CONTRACTL_END; @@ -6061,32 +6171,152 @@ static void GetILStubForCalli(VASigCookie* pVASigCookie, MethodDesc* pMD) GCX_PREEMP(); - Signature signature = pVASigCookie->signature; - CorInfoCallConvExtension unmgdCallConv = CorInfoCallConvExtension::Managed; + _ASSERTE(!pMD->IsAsyncMethod()); + + DWORD dwStubFlags = PINVOKESTUB_FL_BESTFIT | PINVOKESTUB_FL_CONVSIGASVARARG; + + // vararg P/Invoke must be cdecl + CorInfoCallConvExtension unmgdCallConv = CorInfoCallConvExtension::C; + + PInvokeStaticSigInfo sigInfo(pMD); + CorNativeLinkFlags nlFlags = sigInfo.GetLinkFlags(); + CorNativeLinkType nlType = sigInfo.GetCharSet(); + + StubSigDesc sigDesc(pMD, pVASigCookie->signature, pVASigCookie->pModule, pVASigCookie->pLoaderModule); + sigDesc.InitTypeContext(pVASigCookie->classInst, pVASigCookie->methodInst); - DWORD dwStubFlags = PINVOKESTUB_FL_BESTFIT; + MethodDesc* pStubMD = PInvoke::CreateCLRToNativeILStub(&sigDesc, + nlType, + nlFlags, + unmgdCallConv, + dwStubFlags); - if (pMD == NULL) + pTempILStub = JitILStub(pStubMD); + + InterlockedCompareExchangeT(&pVASigCookie->pPInvokeILStub, + pTempILStub, + (PCODE)NULL); + + UNINSTALL_UNWIND_AND_CONTINUE_HANDLER; + UNINSTALL_MANAGED_EXCEPTION_DISPATCHER; + UNINSTALL_RESUME_AFTER_CATCH_HANDLER_WITH_FRAME; +} + +// Build the managed signature of the IL stub that implements an unmanaged CALLI call site. +// +// The stub signature is the call site signature with a managed (default) calling convention +// and an extra trailing parameter that carries the unmanaged target. The target is the value at +// the top of the evaluation stack at the calli site, so appending it as the last parameter lets +// the JIT rewrite the calli into a direct call to the stub. +// +// That parameter is typed as the function pointer type of the call site rather than as a plain +// native int. It is still just a pointer as far as the calling convention and the JIT are +// concerned, but it preserves the unmanaged calling convention of the call site (including +// modopts such as CallConvSuppressGCTransition), which is what lets the stub regenerate its IL +// on demand - see code:PInvoke::CreateCalliStubIL. +static void BuildCalliILStubSignature( + const Signature& calliSignature, + SigBuilder* pSigBuilder) +{ + STANDARD_VM_CONTRACT; + + SigParser sigParser = calliSignature.CreateSigParser(); + PCCOR_SIGNATURE pCalliSigStart = sigParser.GetPtr(); + + uint32_t callConv; + IfFailThrow(sigParser.GetCallingConvInfo(&callConv)); + + // Only signatures accepted by CreateCalliILStub get here. + _ASSERTE((callConv & (IMAGE_CEE_CS_CALLCONV_GENERIC | IMAGE_CEE_CS_CALLCONV_HASTHIS | IMAGE_CEE_CS_CALLCONV_EXPLICITTHIS)) == 0); + + uint32_t numArgs; + IfFailThrow(sigParser.GetData(&numArgs)); + + pSigBuilder->AppendByte(IMAGE_CEE_CS_CALLCONV_DEFAULT); + pSigBuilder->AppendData(numArgs + 1); + + PCCOR_SIGNATURE pTypesStart = sigParser.GetPtr(); + for (uint32_t i = 0; i <= numArgs; i++) { - dwStubFlags |= PINVOKESTUB_FL_UNMANAGED_CALLI; + IfFailThrow(sigParser.SkipExactlyOne()); + } + PCCOR_SIGNATURE pCalliSigEnd = sigParser.GetPtr(); - // need to convert the CALLI signature to stub signature with managed calling convention - BYTE callConv = MetaSig::GetCallingConvention(signature); + // The return type and all of the original parameters, copied verbatim. + pSigBuilder->AppendBlob((PVOID)pTypesStart, (DWORD)(pCalliSigEnd - pTypesStart)); - // Unmanaged calling convention indicates modopt should be read - if (callConv != IMAGE_CEE_CS_CALLCONV_UNMANAGED) - { - unmgdCallConv = (CorInfoCallConvExtension)callConv; - } - else + // The unmanaged target is the last parameter. + pSigBuilder->AppendElementType(ELEMENT_TYPE_FNPTR); + pSigBuilder->AppendBlob((PVOID)pCalliSigStart, (DWORD)(pCalliSigEnd - pCalliSigStart)); +} + +// Recovers the call site signature that BuildCalliILStubSignature embedded in the trailing +// parameter of an unmanaged CALLI stub signature. +static Signature GetCalliSignatureFromStubSignature(const Signature& stubSignature) +{ + STANDARD_VM_CONTRACT; + + SigParser sigParser = stubSignature.CreateSigParser(); + + uint32_t callConv; + IfFailThrow(sigParser.GetCallingConvInfo(&callConv)); + + uint32_t numArgs; + IfFailThrow(sigParser.GetData(&numArgs)); + _ASSERTE(numArgs > 0); + + // Skip the return type and every parameter but the last one. + for (uint32_t i = 0; i < numArgs; i++) + { + IfFailThrow(sigParser.SkipExactlyOne()); + } + + PCCOR_SIGNATURE pFnPtr = sigParser.GetPtr(); + IfFailThrow(sigParser.SkipExactlyOne()); + PCCOR_SIGNATURE pEnd = sigParser.GetPtr(); + + _ASSERTE(*pFnPtr == ELEMENT_TYPE_FNPTR); + return Signature(pFnPtr + 1, (DWORD)(pEnd - pFnPtr - 1)); +} + +// Determines the unmanaged calling convention of an unmanaged CALLI call site and the +// corresponding stub flags. Returns false if the signature does not describe a P/Invoke. +static bool TryGetCalliStubCallConv( + Module* pModule, + const Signature& calliSignature, + CorInfoCallConvExtension* pUnmgdCallConv, + DWORD* pdwStubFlags) +{ + STANDARD_VM_CONTRACT; + + SigParser sigParser = calliSignature.CreateSigParser(); + uint32_t rawCallConv; + IfFailThrow(sigParser.GetCallingConvInfo(&rawCallConv)); + + // Signatures with an instance 'this' are not expressible as a static stub, so leave them + // for the JIT to handle inline. + if ((rawCallConv & (IMAGE_CEE_CS_CALLCONV_GENERIC | IMAGE_CEE_CS_CALLCONV_HASTHIS | IMAGE_CEE_CS_CALLCONV_EXPLICITTHIS)) != 0) + return false; + + switch (rawCallConv & IMAGE_CEE_CS_CALLCONV_MASK) + { + case IMAGE_CEE_CS_CALLCONV_C: + case IMAGE_CEE_CS_CALLCONV_STDCALL: + case IMAGE_CEE_CS_CALLCONV_THISCALL: + case IMAGE_CEE_CS_CALLCONV_FASTCALL: + *pUnmgdCallConv = (CorInfoCallConvExtension)(rawCallConv & IMAGE_CEE_CS_CALLCONV_MASK); + return true; + + case IMAGE_CEE_CS_CALLCONV_UNMANAGED: { + // The unmanaged calling convention is encoded in modopts. CallConvBuilder builder; UINT errorResID; - HRESULT hr = CallConv::TryGetUnmanagedCallingConventionFromModOpt(GetScopeHandle(pVASigCookie->pModule), signature.GetRawSig(), signature.GetRawSigLen(), &builder, &errorResID); + HRESULT hr = CallConv::TryGetUnmanagedCallingConventionFromModOpt(GetScopeHandle(pModule), calliSignature.GetRawSig(), calliSignature.GetRawSigLen(), &builder, &errorResID); if (FAILED(hr)) COMPlusThrowHR(hr, errorResID); - unmgdCallConv = builder.GetCurrentCallConv(); + CorInfoCallConvExtension unmgdCallConv = builder.GetCurrentCallConv(); if (unmgdCallConv == CallConvBuilder::UnsetValue) { unmgdCallConv = CallConv::GetDefaultUnmanagedCallingConvention(); @@ -6094,93 +6324,160 @@ static void GetILStubForCalli(VASigCookie* pVASigCookie, MethodDesc* pMD) if (builder.IsCurrentCallConvModSet(CallConvBuilder::CALL_CONV_MOD_SUPPRESSGCTRANSITION)) { - dwStubFlags |= PINVOKESTUB_FL_SUPPRESSGCTRANSITION; + *pdwStubFlags |= PINVOKESTUB_FL_SUPPRESSGCTRANSITION; } - } - LoaderHeap *pHeap = pVASigCookie->pLoaderModule->GetLoaderAllocator()->GetHighFrequencyHeap(); - PCOR_SIGNATURE new_sig = (PCOR_SIGNATURE)(void *)pHeap->AllocMem(S_SIZE_T(signature.GetRawSigLen())); - CopyMemory(new_sig, signature.GetRawSig(), signature.GetRawSigLen()); + *pUnmgdCallConv = unmgdCallConv; + return true; + } - // make the stub IMAGE_CEE_CS_CALLCONV_DEFAULT - *new_sig &= ~IMAGE_CEE_CS_CALLCONV_MASK; - *new_sig |= IMAGE_CEE_CS_CALLCONV_DEFAULT; + case IMAGE_CEE_CS_CALLCONV_DEFAULT: + case IMAGE_CEE_CS_CALLCONV_VARARG: + case IMAGE_CEE_CS_CALLCONV_NATIVEVARARG: + case IMAGE_CEE_CS_CALLCONV_ASYNC: + // Managed call sites - including the runtime-internal native-vararg and async + // conventions used by IL stubs - are not P/Invokes. + return false; - signature = Signature(new_sig, signature.GetRawSigLen()); + default: + // Not a method signature at all. + COMPlusThrowHR(COR_E_BADIMAGEFORMAT); } - else - { - _ASSERTE(pMD->IsPInvoke()); - dwStubFlags |= PINVOKESTUB_FL_CONVSIGASVARARG; +} - // vararg P/Invoke must be cdecl - unmgdCallConv = CorInfoCallConvExtension::C; +MethodDesc* PInvoke::CreateCalliILStub( + Module* pModule, + const Signature& calliSignature, + const SigTypeContext* pTypeContext, + bool fMustCreate) +{ + CONTRACTL + { + STANDARD_VM_CHECK; + PRECONDITION(CheckPointer(pModule)); + PRECONDITION(CheckPointer(pTypeContext)); } + CONTRACTL_END; - mdMethodDef md = mdMethodDefNil; - CorNativeLinkFlags nlFlags = nlfNone; - CorNativeLinkType nlType = nltAnsi; + DWORD dwStubFlags = PINVOKESTUB_FL_BESTFIT | PINVOKESTUB_FL_UNMANAGED_CALLI; + CorInfoCallConvExtension unmgdCallConv; + if (!TryGetCalliStubCallConv(pModule, calliSignature, &unmgdCallConv, &dwStubFlags)) + return NULL; - if (pMD != NULL) - { - _ASSERTE(pMD->IsPInvoke()); - _ASSERTE(!pMD->IsAsyncMethod()); - PInvokeStaticSigInfo sigInfo(pMD); + // The generic context is stripped if the signature does not actually use it. That is + // required for correctness: built-in runtime marshalling is disallowed for generic + // signatures, and it also lets the stub be shared across instantiations. + SigTypeContext typeContext = *pTypeContext; + Module* pLoaderModule = pModule->GetLoaderModuleForSignature(calliSignature, &typeContext); - md = pMD->GetMemberDef(); - nlFlags = sigInfo.GetLinkFlags(); - nlType = sigInfo.GetCharSet(); - } + bool marshalingRequired = !!PInvoke::MarshalingRequired(NULL, calliSignature.CreateSigPointer(), pModule, &typeContext); - StubSigDesc sigDesc(pMD, signature, pVASigCookie->pModule, pVASigCookie->pLoaderModule); - sigDesc.InitTypeContext(pVASigCookie->classInst, pVASigCookie->methodInst); - - MethodDesc* pStubMD = PInvoke::CreateCLRToNativeILStub(&sigDesc, - nlType, - nlFlags, - unmgdCallConv, - dwStubFlags); + // The JIT cannot emit these calling conventions inline (see code:Compiler::impCheckForPInvokeCall), + // so the call site has to go through a stub even when no marshaling is needed. Stub creation + // rejects them with a proper exception - see code:CreatePInvokeStubAccessMetadata. + bool jitCanInlineCallConv = unmgdCallConv != CorInfoCallConvExtension::Managed + && unmgdCallConv != CorInfoCallConvExtension::Fastcall + && unmgdCallConv != CorInfoCallConvExtension::FastcallMemberFunction; - pTempILStub = JitILStub(pStubMD); + // If no marshaling is needed, let the caller emit the unmanaged call inline unless it + // explicitly asked for a stub. + if (!fMustCreate && jitCanInlineCallConv && !marshalingRequired) + { + return NULL; + } - InterlockedCompareExchangeT(&pVASigCookie->pPInvokeILStub, - pTempILStub, - (PCODE)NULL); + SigBuilder sigBuilder; + BuildCalliILStubSignature(calliSignature, &sigBuilder); + + DWORD cbStubSig; + PCCOR_SIGNATURE pStubSig = (PCCOR_SIGNATURE)sigBuilder.GetSignature(&cbStubSig); + + // The stub MethodDesc references this buffer for as long as it lives, so it has to be owned + // by the loader allocator the stub lives in. It is backed out below if the stub did not keep it. + AllocMemHolder pStubSigCopy(pLoaderModule->GetLoaderAllocator()->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(cbStubSig))); + memcpy(pStubSigCopy, pStubSig, cbStubSig); + + StubSigDesc sigDesc(NULL, Signature((PCCOR_SIGNATURE)(BYTE*)pStubSigCopy, cbStubSig), pModule, pLoaderModule); + sigDesc.InitTypeContext(typeContext.m_classInst, typeContext.m_methodInst); + + // Only the MethodDesc is created here. The stub IL is generated lazily when the stub itself + // is compiled - see code:PInvoke::CreateCalliStubIL - so that jitting a method containing an + // unmanaged calli does not have to do the marshaling analysis for it. + MethodDesc* pStubMD = CreateInteropILStubMethodDesc( + &sigDesc, + nltAnsi, + nlfNone, + unmgdCallConv, + dwStubFlags); + + PCCOR_SIGNATURE pFinalSig; + DWORD cbFinalSig; + pStubMD->GetSig(&pFinalSig, &cbFinalSig); + if (pFinalSig == (PCCOR_SIGNATURE)(BYTE*)pStubSigCopy) + pStubSigCopy.SuppressRelease(); - UNINSTALL_UNWIND_AND_CONTINUE_HANDLER; - UNINSTALL_MANAGED_EXCEPTION_DISPATCHER; - UNINSTALL_RESUME_AFTER_CATCH_HANDLER_WITH_FRAME; + return pStubMD; } -EXTERN_C void STDCALL VarargPInvokeStubWorker(TransitionBlock* pTransitionBlock, VASigCookie *pVASigCookie, MethodDesc *pMD) +// Generates the IL of an unmanaged CALLI marshalling stub. This is transient IL: it is produced +// on demand while the stub is being compiled and discarded afterwards, so everything it needs is +// recovered from the stub's own signature (see code:BuildCalliILStubSignature). +COR_ILMETHOD_DECODER* PInvoke::CreateCalliStubIL(MethodDesc* pStubMD, DynamicResolver** ppResolver) { - PreserveLastErrorHolder preserveLastError; + CONTRACTL + { + STANDARD_VM_CHECK; + PRECONDITION(CheckPointer(pStubMD)); + PRECONDITION(pStubMD->IsILStub()); + PRECONDITION(pStubMD->AsDynamicMethodDesc()->IsPInvokeCalliStub()); + PRECONDITION(CheckPointer(ppResolver)); + } + CONTRACTL_END; - STATIC_CONTRACT_THROWS; - STATIC_CONTRACT_GC_TRIGGERS; - STATIC_CONTRACT_MODE_COOPERATIVE; - STATIC_CONTRACT_ENTRY_POINT; + // The stub signature is fully instantiated, so no type context is needed to interpret it. + Module* pModule = pStubMD->GetModule(); + Signature stubSignature = pStubMD->GetSignature(); - MAKE_CURRENT_THREAD_AVAILABLE(); + DWORD dwStubFlags = PINVOKESTUB_FL_BESTFIT | PINVOKESTUB_FL_UNMANAGED_CALLI; + CorInfoCallConvExtension unmgdCallConv; + if (!TryGetCalliStubCallConv(pModule, GetCalliSignatureFromStubSignature(stubSignature), &unmgdCallConv, &dwStubFlags)) + { + // The stub would not have been created for such a signature. + UNREACHABLE_MSG("Unmanaged CALLI stub with a managed call site signature"); + } -#ifdef _DEBUG - Thread::ObjectRefFlush(CURRENT_THREAD); -#endif + StubSigDesc sigDesc(NULL, stubSignature, pModule, pStubMD->GetLoaderModule()); - PrestubMethodFrame frame(pTransitionBlock, pMD); - PrestubMethodFrame * pFrame = &frame; + int iLCIDArg = 0; + int numArgs = 0; + CreatePInvokeStubAccessMetadata(&sigDesc, unmgdCallConv, &dwStubFlags, &iLCIDArg, &numArgs); - pFrame->Push(CURRENT_THREAD); + int numParamTokens = numArgs + 1; + mdParamDef* pParamTokenArray = (mdParamDef*)_alloca(numParamTokens * sizeof(mdParamDef)); + CollateParamTokens(pModule->GetMDImport(), sigDesc.m_tkMethodDef, numArgs, pParamTokenArray); - _ASSERTE(pVASigCookie == pFrame->GetVASigCookie()); - _ASSERTE(pMD == pFrame->GetFunction()); + PInvoke_ILStubState stubState(pModule, sigDesc.m_sig, &sigDesc.m_typeContext, dwStubFlags, unmgdCallConv, iLCIDArg, NULL); - GetILStubForCalli(pVASigCookie, pMD); + NewHolder pResolver = new ILStubResolver(); + pResolver->SetStubMethodDesc(pStubMD); + + COR_ILMETHOD_DECODER* pIL = CreatePInvokeStubWorker( + &stubState, + pResolver, + &sigDesc, + nltAnsi, + nlfNone, + unmgdCallConv, + stubState.GetFlags(), + pStubMD, + pParamTokenArray, + iLCIDArg); - pFrame->Pop(CURRENT_THREAD); + *ppResolver = pResolver.Extract(); + return pIL; } -EXTERN_C void STDCALL GenericPInvokeCalliStubWorker(TransitionBlock * pTransitionBlock, VASigCookie * pVASigCookie, PCODE pUnmanagedTarget) +EXTERN_C void STDCALL VarargPInvokeStubWorker(TransitionBlock* pTransitionBlock, VASigCookie *pVASigCookie, MethodDesc *pMD) { PreserveLastErrorHolder preserveLastError; @@ -6195,14 +6492,15 @@ EXTERN_C void STDCALL GenericPInvokeCalliStubWorker(TransitionBlock * pTransitio Thread::ObjectRefFlush(CURRENT_THREAD); #endif - PInvokeCalliFrame frame(pTransitionBlock, pVASigCookie, pUnmanagedTarget); - PInvokeCalliFrame * pFrame = &frame; + PrestubMethodFrame frame(pTransitionBlock, pMD); + PrestubMethodFrame * pFrame = &frame; pFrame->Push(CURRENT_THREAD); _ASSERTE(pVASigCookie == pFrame->GetVASigCookie()); + _ASSERTE(pMD == pFrame->GetFunction()); - GetILStubForCalli(pVASigCookie, NULL); + GetILStubForVarargPInvoke(pVASigCookie, pMD); pFrame->Pop(CURRENT_THREAD); } diff --git a/src/coreclr/vm/dllimport.h b/src/coreclr/vm/dllimport.h index 7e1fc77d5513e7..41357b04231197 100644 --- a/src/coreclr/vm/dllimport.h +++ b/src/coreclr/vm/dllimport.h @@ -127,6 +127,22 @@ class PInvoke CorInfoCallConvExtension unmgdCallConv, DWORD dwStubFlags); // PInvokeStubFlags + // Creates the IL stub that marshals an unmanaged calli call site described by + // calliSignature. Returns NULL if the call site does not describe an unmanaged call, or if + // no marshaling is required and fMustCreate is false (in which case the caller - the JIT - + // can emit the unmanaged call inline instead). + // Only the MethodDesc is created; the stub IL is generated on demand by CreateCalliStubIL. + static MethodDesc* CreateCalliILStub( + Module* pModule, + const Signature& calliSignature, + const SigTypeContext* pTypeContext, + bool fMustCreate); + + // Generates the transient IL of a stub created by CreateCalliILStub. + static COR_ILMETHOD_DECODER* CreateCalliStubIL( + MethodDesc* pStubMD, + DynamicResolver** ppResolver); + static COR_ILMETHOD_DECODER* CreatePInvokeMethodIL( PInvokeMethodDesc* pMD, DynamicResolver** ppResolver); @@ -461,6 +477,9 @@ class PInvokeStubLinker : public ILStubLinker void SetCallingConvention(CorInfoCallConvExtension unmngCallConv, BOOL fIsVarArg); + // For unmanaged CALLI stubs, the native target is passed in as the last argument of the stub. + void SetCalliTargetArgIndex(UINT uArgIdx); + void Begin(DWORD dwStubFlags); void End(DWORD dwStubFlags); void DoPInvoke(ILCodeStream *pcsEmit, DWORD dwStubFlags, MethodDesc * pStubMD); @@ -558,6 +577,7 @@ class PInvokeStubLinker : public ILStubLinker UINT m_ErrorResID; UINT m_ErrorParamIdx; int m_iLCIDParamIdx; + UINT m_uCalliTargetArgIdx; DWORD m_dwStubFlags; }; diff --git a/src/coreclr/vm/frames.cpp b/src/coreclr/vm/frames.cpp index 202864af98c65b..09e03980527329 100644 --- a/src/coreclr/vm/frames.cpp +++ b/src/coreclr/vm/frames.cpp @@ -328,16 +328,9 @@ void Frame::Log() { STRESS_LOG3(LF_STUBS, LL_INFO1000000, "STUBS: In Stub with Frame %p assoc Method %pM FrameType = %pV\n", this, method, *((void**) this)); - char buff[64]; const char* frameType; if (GetFrameIdentifier() == FrameIdentifier::PrestubMethodFrame) frameType = "PreStub"; - else if (GetFrameIdentifier() == FrameIdentifier::PInvokeCalliFrame) - { - sprintf_s(buff, ARRAY_SIZE(buff), "PInvoke CALLI target" FMT_ADDR, - DBG_ADDR(((PInvokeCalliFrame*)this)->GetPInvokeCalliTarget())); - frameType = buff; - } else if (GetFrameIdentifier() == FrameIdentifier::StubDispatchFrame) frameType = "StubDispatch"; else if (GetFrameIdentifier() == FrameIdentifier::ExternalMethodFrame) @@ -1706,37 +1699,6 @@ void TransitionFrame::PromoteCallerStackUsingGCRefMap(promote_func* fn, ScanCont } } -void PInvokeCalliFrame::PromoteCallerStack(promote_func* fn, ScanContext* sc) -{ - WRAPPER_NO_CONTRACT; - - LOG((LF_GC, INFO3, " Promoting CALLI caller Arguments\n" )); - - // get the signature - VASigCookie *varArgSig = GetVASigCookie(); - if (varArgSig->signature.IsEmpty()) - { - return; - } - - SigTypeContext typeContext(varArgSig->classInst, varArgSig->methodInst); - MetaSig msig(varArgSig->signature, - varArgSig->pModule, - &typeContext); - PromoteCallerStackHelper(fn, sc, NULL, &msig); -} - -#ifndef DACCESS_COMPILE -PInvokeCalliFrame::PInvokeCalliFrame(TransitionBlock * pTransitionBlock, VASigCookie * pVASigCookie, PCODE pUnmanagedTarget) - : FramedMethodFrame(FrameIdentifier::PInvokeCalliFrame, pTransitionBlock, NULL) -{ - LIMITED_METHOD_CONTRACT; - - m_pVASigCookie = pVASigCookie; - m_pUnmanagedTarget = pUnmanagedTarget; -} -#endif // #ifndef DACCESS_COMPILE - #if defined (_DEBUG) && !defined (DACCESS_COMPILE) // For IsProtectedByGCFrame, we need to know whether a given object ref is protected // by a TransitionFrame. Since GCScanRoots for those frames are diff --git a/src/coreclr/vm/frames.h b/src/coreclr/vm/frames.h index cd84feb5d49f3a..d59872c9e9afcb 100644 --- a/src/coreclr/vm/frames.h +++ b/src/coreclr/vm/frames.h @@ -64,9 +64,6 @@ // | | that generates a full-fledged frame. // | | // | | -// | +-PInvokeCalliFrame - protects arguments when a call to GetILStubForCalli is made -// | | to get or create IL stub for an unmanaged CALLI -// | | // | +-PrestubMethodFrame - represents a call to a prestub // | | // | +-StubDispatchFrame - represents a call into the virtual call stub manager @@ -114,10 +111,9 @@ Delegate over a native function pointer: goes through an IL stub). Calli: - The same as P/Invoke. - PInvokeCalliFrame is erected in stub generated by GenerateGetStubForPInvokeCalli - before calling to GetILStubForCalli which generates the IL stub. This happens only - the first time a call via the corresponding VASigCookie is made. + The same as P/Invoke. The IL stub is created when the caller is JIT-compiled + (see code:CEEInfo::convertPInvokeCalliToCall) and receives the unmanaged target + as its last argument. ClrToCom: Eventing: The stub does not erect any frames explicitly and calls back into managed code. @@ -1303,76 +1299,6 @@ struct cdac_data static constexpr size_t MethodDescPtr = offsetof(FramedMethodFrame, m_pMD); }; -//------------------------------------------------------------------------ -// This represents a call from a helper to GetILStubForCalli -//------------------------------------------------------------------------ - -typedef DPTR(class PInvokeCalliFrame) PTR_PInvokeCalliFrame; - -class PInvokeCalliFrame : public FramedMethodFrame -{ - PTR_VASigCookie m_pVASigCookie; - PCODE m_pUnmanagedTarget; - -public: - PInvokeCalliFrame(TransitionBlock * pTransitionBlock, VASigCookie * pVASigCookie, PCODE pUnmanagedTarget); - - void GcScanRoots_Impl(promote_func *fn, ScanContext* sc) - { - WRAPPER_NO_CONTRACT; - FramedMethodFrame::GcScanRoots_Impl(fn, sc); - PromoteCallerStack(fn, sc); - } - - void PromoteCallerStack(promote_func* fn, ScanContext* sc); - - // not a method - MethodDesc *GetFunction_Impl() - { - LIMITED_METHOD_DAC_CONTRACT; - return NULL; - } - - int GetFrameType_Impl() - { - LIMITED_METHOD_DAC_CONTRACT; - return TYPE_INTERCEPTION; - } - - PCODE GetPInvokeCalliTarget() - { - LIMITED_METHOD_CONTRACT; - return m_pUnmanagedTarget; - } - - PTR_VASigCookie GetVASigCookie() - { - LIMITED_METHOD_CONTRACT; - return m_pVASigCookie; - } - -#ifdef TARGET_X86 - void UpdateRegDisplay_Impl(const PREGDISPLAY, bool updateFloats = false); -#endif // TARGET_X86 - - BOOL TraceFrame_Impl(Thread *thread, BOOL fromPatch, - TraceDestination *trace, REGDISPLAY *regs) - { - WRAPPER_NO_CONTRACT; - - trace->InitForUnmanaged(GetPInvokeCalliTarget()); - return TRUE; - } - - friend struct ::cdac_data; -}; - -template <> -struct cdac_data -{ - static constexpr size_t VASigCookiePtr = offsetof(PInvokeCalliFrame, m_pVASigCookie); -}; - // Some context-related forwards. #ifdef FEATURE_HIJACK //------------------------------------------------------------------------ diff --git a/src/coreclr/vm/i386/asmhelpers.S b/src/coreclr/vm/i386/asmhelpers.S index 29b76b1f220ffb..b2284ad1c7cdf2 100644 --- a/src/coreclr/vm/i386/asmhelpers.S +++ b/src/coreclr/vm/i386/asmhelpers.S @@ -399,53 +399,6 @@ LOCAL_LABEL(GoCallVarargWorker): jmp C_FUNC(VarargPInvokeStub) NESTED_END VarargPInvokeStub, _TEXT -// ========================================================================== -// Invoked for marshaling-required unmanaged CALLI calls as a stub. -// EAX - the unmanaged target -// ECX, EDX - arguments -// EBX - the VASigCookie -// -LEAF_ENTRY GenericPInvokeCalliHelper, _TEXT - - cmp dword ptr [ebx + VASigCookie__StubOffset], 0 - jz LOCAL_LABEL(GoCallCalliWorker) - - // Stub is already prepared, just jump to it - jmp dword ptr [ebx + VASigCookie__StubOffset] - -LOCAL_LABEL(GoCallCalliWorker): - // - // call the stub generating worker - // target ptr in EAX, VASigCookie ptr in EBX - // - - STUB_PROLOG - - mov esi, esp - - // save target - push eax - - #define STACK_ALIGN_PADDING 4 - sub esp, STACK_ALIGN_PADDING // pass stack aligned to 0x10 - push eax // unmanaged target - push ebx // pVaSigCookie (first stack argument) - push esi // pTransitionBlock - - CHECK_STACK_ALIGNMENT - call C_FUNC(GenericPInvokeCalliStubWorker) - add esp, STACK_ALIGN_PADDING // restore alignment, callee pop args - #undef STACK_ALIGN_PADDING - - // restore target - pop eax - - STUB_EPILOG - - // jump back to the helper - this time it won't come back here as the stub already exists - jmp C_FUNC(GenericPInvokeCalliHelper) -LEAF_END GenericPInvokeCalliHelper, _TEXT - #ifdef FEATURE_READYTORUN NESTED_ENTRY DynamicHelperArgsStub, _TEXT, NoHandler .cfi_def_cfa_offset 16 diff --git a/src/coreclr/vm/i386/asmhelpers.asm b/src/coreclr/vm/i386/asmhelpers.asm index f160c43c27b485..3dd14e72f7dfe2 100644 --- a/src/coreclr/vm/i386/asmhelpers.asm +++ b/src/coreclr/vm/i386/asmhelpers.asm @@ -38,7 +38,6 @@ EXTERN __alloca_probe:PROC EXTERN _PInvokeImportWorker@4:PROC EXTERN _VarargPInvokeStubWorker@12:PROC -EXTERN _GenericPInvokeCalliStubWorker@12:PROC EXTERN _PreStubWorker@8:PROC EXTERN _TheUMEntryPrestubWorker@4:PROC @@ -701,49 +700,6 @@ GoCallVarargWorker: _VarargPInvokeStub@0 endp -;========================================================================== -; Invoked for marshaling-required unmanaged CALLI calls as a stub. -; EAX - the unmanaged target -; ECX, EDX - arguments -; EBX - the VASigCookie -; -_GenericPInvokeCalliHelper@0 proc public - - cmp dword ptr [ebx + VASigCookie__StubOffset], 0 - jz GoCallCalliWorker - - ; Stub is already prepared, just jump to it - jmp dword ptr [ebx + VASigCookie__StubOffset] - -GoCallCalliWorker: - ; - ; call the stub generating worker - ; target ptr in EAX, VASigCookie ptr in EBX - ; - - STUB_PROLOG - - mov esi, esp - - ; save target - push eax - - push eax ; unmanaged target - push ebx ; pVaSigCookie (first stack argument) - push esi ; pTransitionBlock - - call _GenericPInvokeCalliStubWorker@12 - - ; restore target - pop eax - - STUB_EPILOG - - ; jump back to the helper - this time it won't come back here as the stub already exists - jmp _GenericPInvokeCalliHelper@0 - -_GenericPInvokeCalliHelper@0 endp - ifdef FEATURE_READYTORUN ;========================================================================== _DelayLoad_MethodCall@0 proc public diff --git a/src/coreclr/vm/i386/cgenx86.cpp b/src/coreclr/vm/i386/cgenx86.cpp index 1500b80f90030f..bc2daec3b7cab1 100644 --- a/src/coreclr/vm/i386/cgenx86.cpp +++ b/src/coreclr/vm/i386/cgenx86.cpp @@ -372,23 +372,6 @@ void HijackFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool updateFloats #endif // FEATURE_HIJACK -void PInvokeCalliFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool updateFloats) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - SUPPORTS_DAC; - } - CONTRACTL_END; - - VASigCookie *pVASigCookie = GetVASigCookie(); - UpdateRegDisplayHelper(pRD, pVASigCookie->sizeOfArgs); - - LOG((LF_GCROOTS, LL_INFO100000, "STACKWALK PInvokeCalliFrame::UpdateRegDisplay_Impl(ip:%p, sp:%p)\n", pRD->ControlPC, pRD->SP)); -} - #ifndef UNIX_X86_ABI void TailCallFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool updateFloats) { diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 63f5380933ad5d..ad2e72d0abda07 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -6649,6 +6649,13 @@ DWORD CEEInfo::getMethodAttribsInternal (CORINFO_METHOD_HANDLE ftn) /* Function marked as not inlineable */ result |= CORINFO_FLG_DONT_INLINE; } + else if (pMD->IsILStub()) + { + // IL stubs have no metadata and their IL is only materialized while the stub itself is + // being compiled, so they can never be inlined into their callers. Reporting this here + // keeps the JIT from asking for the stub's IL (see code:CEEInfo::canInline). + result |= CORINFO_FLG_DONT_INLINE; + } // AggressiveInlining only makes sense for IL methods. else if (pMD->IsIL() && IsMiAggressiveInlining(ilMethodImplAttribs)) { @@ -7672,7 +7679,7 @@ COR_ILMETHOD_DECODER* CEEInfo::getMethodInfoWorker( localSig = SigPointer{ cxt.Header->LocalVarSig, cxt.Header->cbLocalVarSig }; } } - else if (ilFtn->IsDynamicMethod()) + else if (ilFtn->IsDynamicMethod() && !ilFtn->AsDynamicMethodDesc()->UsesTransientIL()) { DynamicResolver* pResolver = ilFtn->AsDynamicMethodDesc()->GetResolver(); scopeHnd = MakeDynamicScope(pResolver); @@ -13377,7 +13384,7 @@ void CEECodeGenInfo::getEHinfo( pMD = pMD->GetOrdinaryVariantIfAsyncVersion(); COR_ILMETHOD* pILHeader; - if (pMD->IsDynamicMethod()) + if (pMD->IsDynamicMethod() && !pMD->AsDynamicMethodDesc()->UsesTransientIL()) { pMD->AsDynamicMethodDesc()->GetResolver()->GetEHInfo(EHnumber, clause); } @@ -13390,7 +13397,7 @@ void CEECodeGenInfo::getEHinfo( COR_ILMETHOD_DECODER header(pILHeader, pMD->GetMDImport(), NULL); getEHinfoHelper(EHnumber, clause, &header); } - else if (pMD->IsIL()) + else if (pMD->IsIL() || (pMD->IsILStub() && pMD->AsDynamicMethodDesc()->UsesTransientIL())) { TransientMethodDetails* details; if (!FindTransientMethodDetails(pMD, &details)) @@ -15484,9 +15491,94 @@ CORINFO_METHOD_HANDLE CEEJitInfo::getAsyncResumptionStub(void** entryPoint) return CORINFO_METHOD_HANDLE(result); } +//--------------------------------------------------------------------------------------- +// +// Optionally converts an unmanaged calli call site into a call to an IL stub that performs +// the argument marshalling and the managed/native transition. +// +// The IL stub is created (and cached in the IL stub cache by signature) here, but it is only +// JIT-compiled the first time it is called - we cannot JIT it while we are jitting the caller. +// +// Arguments: +// pResolvedToken - the token of the calli call site. Only token, tokenScope and +// tokenContext are valid on entry. On success, hMethod is filled in +// with the IL stub. +// fMustConvert - true if the JIT cannot emit an inline P/Invoke at this call site and +// therefore requires the conversion. +// +// Return Value: +// true if the call site was converted to a call to an IL stub. +// bool CEEInfo::convertPInvokeCalliToCall(CORINFO_RESOLVED_TOKEN * pResolvedToken, bool fMustConvert) { - return false; + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } CONTRACTL_END; + + bool result = false; + + JIT_TO_EE_TRANSITION(); + + CORINFO_MODULE_HANDLE scopeHnd = pResolvedToken->tokenScope; + + SigPointer sig{}; + bool canConvert = true; + + if (IsDynamicScope(scopeHnd)) + { + // The calli that dispatches to the unmanaged target inside a marshalling stub is the + // P/Invoke itself and must stay inline. Those call sites always use + // TOKEN_ILSTUB_TARGET_SIG, and their IL is owned either by an IL stub MethodDesc or, + // for a plain P/Invoke, directly by the PInvokeMethodDesc. + if (pResolvedToken->token == (mdToken)TOKEN_ILSTUB_TARGET_SIG) + { + canConvert = false; + } + else + { + sig = GetDynamicResolver(scopeHnd)->ResolveSignature(pResolvedToken->token); + } + } + else + { + Module* pModule = (Module*)scopeHnd; + + PCCOR_SIGNATURE pSig = NULL; + uint32_t cbSig = 0; + IfFailThrow(pModule->GetMDImport()->GetSigFromToken( + (mdSignature)pResolvedToken->token, + (ULONG*)&cbSig, + &pSig)); + sig = SigPointer{ pSig, cbSig }; + } + + if (canConvert) + { + PCCOR_SIGNATURE pSig; + uint32_t cbSig; + sig.GetSignature(&pSig, &cbSig); + + SigTypeContext typeContext; + GetTypeContext(pResolvedToken->tokenContext, &typeContext); + + MethodDesc* pStubMD = PInvoke::CreateCalliILStub( + GetModule(scopeHnd), + Signature(pSig, cbSig), + &typeContext, + fMustConvert); + + if (pStubMD != NULL) + { + pResolvedToken->hMethod = (CORINFO_METHOD_HANDLE)pStubMD; + result = true; + } + } + + EE_TO_JIT_TRANSITION(); + + return result; } void CEEInfo::updateEntryPointForTailCall(CORINFO_CONST_LOOKUP* entryPoint) diff --git a/src/coreclr/vm/loongarch64/pinvokestubs.S b/src/coreclr/vm/loongarch64/pinvokestubs.S index 28a33a9db07c7c..8052571d1786a7 100644 --- a/src/coreclr/vm/loongarch64/pinvokestubs.S +++ b/src/coreclr/vm/loongarch64/pinvokestubs.S @@ -11,7 +11,7 @@ // // Params :- // $FuncPrefix : prefix of the function name for the stub -// Eg. VarargPinvoke, GenericPInvokeCalli +// Eg. VarargPinvoke // $VASigCookieReg : register which contains the VASigCookie // $SaveFPArgs : "Yes" or "No" . For varidic functions FP Args are not present in FP regs // So need not save FP Args registers for vararg Pinvoke @@ -156,16 +156,6 @@ LEAF_END JIT_PInvokeEnd, _TEXT PINVOKE_STUB VarargPInvokeStub, VarargPInvokeGenILStub, VarargPInvokeStubWorker, $a0, $t2, 0 -// ------------------------------------------------------------------ -// GenericPInvokeCalliHelper & GenericPInvokeCalliGenILStub -// Helper for generic pinvoke calli instruction -// -// in: -// $t3 = VASigCookie* -// $t2 = Unmanaged target -// -PINVOKE_STUB GenericPInvokeCalliHelper, GenericPInvokeCalliGenILStub, GenericPInvokeCalliStubWorker, $t3, $t2, 1 - //// ------------------------------------------------------------------ //// VarargPInvokeStub_RetBuffArg & VarargPInvokeGenILStub_RetBuffArg //// Vararg PInvoke Stub when the method has a hidden return buffer arg diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index fd6ddd35ca4c39..ebb51525cc5dfb 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -3082,6 +3082,11 @@ class DynamicMethodDesc : public StoredSigMethodDesc bool IsILStub() const { LIMITED_METHOD_DAC_CONTRACT; return HasFlags(FlagIsILStub); } bool IsLCGMethod() const { LIMITED_METHOD_DAC_CONTRACT; return HasFlags(FlagIsLCGMethod); } + // IL stubs whose IL is generated on demand while the stub is being compiled, and discarded + // afterwards, rather than being generated eagerly when the stub is created. + // See code:MethodDesc::TryGenerateTransientILImplementation. + bool UsesTransientIL() const { LIMITED_METHOD_DAC_CONTRACT; return IsILStub() && IsPInvokeCalliStub(); } + inline PTR_DynamicResolver GetResolver(); inline PTR_LCGMethodResolver GetLCGMethodResolver(); inline PTR_ILStubResolver GetILStubResolver(); diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index cb032e1536029d..695e965ddd5d2d 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -749,7 +749,7 @@ namespace { return GetAndVerifyMetadataILHeader(pMD, pConfig, pIlDecoderMemory); } - else if (pMD->IsILStub()) + else if (pMD->IsILStub() && !pMD->AsDynamicMethodDesc()->UsesTransientIL()) { ILStubResolver* pResolver = pMD->AsDynamicMethodDesc()->GetILStubResolver(); return pResolver->GetILHeader(); @@ -795,9 +795,17 @@ PCODE MethodDesc::JitCompileCodeLockedEventWrapper(PrepareCodeConfig* pConfig, J } else { - unsigned int ilSize, unused; + unsigned int ilSize = 0; + unsigned int unused; CorInfoOptions corOptions; - LPCBYTE ilHeaderPointer = this->AsDynamicMethodDesc()->GetResolver()->GetCodeInfo(&ilSize, &unused, &corOptions, &unused); + LPCBYTE ilHeaderPointer = NULL; + + // Stubs backed by transient IL have no IL yet - it is generated as part of the + // compilation that is just starting. + if (!AsDynamicMethodDesc()->UsesTransientIL()) + { + ilHeaderPointer = AsDynamicMethodDesc()->GetResolver()->GetCodeInfo(&ilSize, &unused, &corOptions, &unused); + } (&g_profControlBlock)->DynamicMethodJITCompilationStarted((FunctionID)this, TRUE, ilHeaderPointer, ilSize); } @@ -1116,6 +1124,13 @@ bool MethodDesc::TryGenerateTransientILImplementation(DynamicResolver** resolver return true; } + if (IsILStub() && AsDynamicMethodDesc()->UsesTransientIL()) + { + _ASSERTE(AsDynamicMethodDesc()->IsPInvokeCalliStub()); + *methodILDecoder = PInvoke::CreateCalliStubIL(this, resolver); + return true; + } + if (TryGenerateAsyncThunk(resolver, methodILDecoder)) { return true; diff --git a/src/coreclr/vm/riscv64/pinvokestubs.S b/src/coreclr/vm/riscv64/pinvokestubs.S index 027eec6cb01dfa..1d99a5b1b6d932 100644 --- a/src/coreclr/vm/riscv64/pinvokestubs.S +++ b/src/coreclr/vm/riscv64/pinvokestubs.S @@ -11,7 +11,7 @@ // // Params :- // FuncPrefix : prefix of the function name for the stub -// Eg. VarargPinvoke, GenericPInvokeCalli +// Eg. VarargPinvoke // VASigCookieReg : register which contains the VASigCookie // SaveFPArgs : "Yes" or "No" . For varidic functions FP Args are not present in FP regs // So need not save FP Args registers for vararg Pinvoke @@ -150,16 +150,6 @@ LEAF_END JIT_PInvokeEnd, _TEXT PINVOKE_STUB VarargPInvokeStub, VarargPInvokeGenILStub, VarargPInvokeStubWorker, a0, t2, 0 -// ------------------------------------------------------------------ -// GenericPInvokeCalliHelper & GenericPInvokeCalliGenILStub -// Helper for generic pinvoke calli instruction -// -// in: -// t3 = VASigCookie* -// t2 = Unmanaged target -// -PINVOKE_STUB GenericPInvokeCalliHelper, GenericPInvokeCalliGenILStub, GenericPInvokeCalliStubWorker, t3, t2, 1 - //// ------------------------------------------------------------------ //// VarargPInvokeStub_RetBuffArg & VarargPInvokeGenILStub_RetBuffArg //// Vararg PInvoke Stub when the method has a hidden return buffer arg diff --git a/src/coreclr/vm/stubmgr.cpp b/src/coreclr/vm/stubmgr.cpp index 87c559554f2ec0..e27ed7c492992a 100644 --- a/src/coreclr/vm/stubmgr.cpp +++ b/src/coreclr/vm/stubmgr.cpp @@ -1755,11 +1755,11 @@ BOOL ILStubManager::TraceManager(Thread *thread, } else if (pStubMD->IsPInvokeCalliStub()) { - // This is unmanaged CALLI stub, the argument is the target - target = (PCODE)arg; - - LOG((LF_CORDB, LL_INFO10000, "ILSM::TraceManager: Unmanaged CALLI case %p\n", target)); - trace->InitForUnmanaged(target); + // This is an unmanaged CALLI stub. The native target is passed as the last argument + // of the stub rather than in a hidden argument, so we cannot recover it from the + // register context here. + LOG((LF_CORDB, LL_INFO1000, "ILSM::TraceManager: Unmanaged CALLI stub - target is not traceable\n")); + return FALSE; } else if (pStubMD->IsStepThroughStub()) { @@ -1904,7 +1904,7 @@ BOOL PInvokeStubManager::DoTraceStub(PCODE stubStartAddress, #endif // !DACCESS_COMPILE } -// This is used to recognize VarargPInvokeStub, and GenericPInvokeCalliHelper. +// This is used to recognize VarargPInvokeStub. #ifndef DACCESS_COMPILE @@ -1951,12 +1951,6 @@ BOOL InteropDispatchStubManager::CheckIsStub_Internal(PCODE stubStartAddress) { return true; } - - if (stubStartAddress == GetEEFuncEntryPoint(GenericPInvokeCalliHelper)) - { - return true; - } - #endif // !DACCESS_COMPILE return false; } @@ -2018,18 +2012,6 @@ BOOL InteropDispatchStubManager::TraceManager(Thread *thread, trace->InitForUnmanaged(target); #endif //defined(TARGET_ARM64) && defined(__APPLE__) } - else if (stubIP == GetEEFuncEntryPoint(GenericPInvokeCalliHelper)) - { -#if defined(TARGET_ARM64) && defined(__APPLE__) - //On ARM64 Mac, we cannot put a breakpoint inside of GenericPInvokeCalliHelper - LOG((LF_CORDB, LL_INFO10000, "IDSM::TraceManager: Skipping on arm64-macOS\n")); - return FALSE; -#else - PCODE target = (PCODE)arg; - LOG((LF_CORDB, LL_INFO10000, "IDSM::TraceManager: Unmanaged CALLI case %p\n", target)); - trace->InitForUnmanaged(target); -#endif //defined(TARGET_ARM64) && defined(__APPLE__) - } return TRUE; } diff --git a/src/coreclr/vm/stubmgr.h b/src/coreclr/vm/stubmgr.h index 8418ca5f1e7681..1b9bcc2eaa8d57 100644 --- a/src/coreclr/vm/stubmgr.h +++ b/src/coreclr/vm/stubmgr.h @@ -641,7 +641,6 @@ class PInvokeStubManager : public StubManager // This is used to recognize // VarargPInvokeStub() -// GenericPInvokeCalliHelper() typedef VPTR(class InteropDispatchStubManager) PTR_InteropDispatchStubManager; class InteropDispatchStubManager : public StubManager diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index f97cffffb87021..3532f8d4659b2c 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -705,11 +705,6 @@ EXTERN_C VOID STDCALL ResetCurrentContext() { } -extern "C" void STDCALL GenericPInvokeCalliHelper(void) -{ - PORTABILITY_ASSERT("GenericPInvokeCalliHelper is not implemented on wasm"); -} - // Does the pinvoke frame transition; the naked wrappers below have already set the wasm // __stack_pointer global to sp so it is safe to run native code here. EXTERN_C void JIT_PInvokeBeginImpl(void* sp, InlinedCallFrame* pFrame) From ddf0f618bad9d61579510fd9e31972dd85706dcc Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 27 Jul 2026 21:24:58 -0700 Subject: [PATCH 2/8] Recover the unmanaged CALLI stub target for the debugger The stub manager used to read the native target of an unmanaged calli stub out of the hidden argument that GenericPInvokeCalliHelper set up. Now that the target is an ordinary trailing argument of the stub, locate it with an ArgIterator over the stub signature and read it out of the context captured at the stub's entry point, either from the argument register the calling convention assigned to it or from the incoming stack arguments. Also relaxes TryGetCalliStubTarget's contract to MODE_ANY - it only reads a context and raw memory and never touches object references. Verified on windows-x64 with a synthetic entry point context for stubs whose target lands in RDX, R8, R9 and in the first three stack slots. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/stubmgr.cpp | 115 +++++++++++++++++++++++++++++++++++-- 1 file changed, 110 insertions(+), 5 deletions(-) diff --git a/src/coreclr/vm/stubmgr.cpp b/src/coreclr/vm/stubmgr.cpp index e27ed7c492992a..4deafc3008d689 100644 --- a/src/coreclr/vm/stubmgr.cpp +++ b/src/coreclr/vm/stubmgr.cpp @@ -1702,6 +1702,105 @@ static PCODE GetLateBoundCOMTarget(Object *pThis, CLRToCOMCallInfo *pCLRToCOMCal } #endif // FEATURE_COMINTEROP +// Reads the incoming value of the index-th integer argument register from a context captured at +// a managed method's entry point. The registers are indexed the way a TransitionBlock lays them +// out, which is the same order the ArgIterator reports. +static bool TryGetArgumentRegister(T_CONTEXT *pContext, unsigned index, TADDR *pValue) +{ + LIMITED_METHOD_CONTRACT; + +#if defined(TARGET_X86) || defined(TARGET_AMD64) || defined(TARGET_ARM) || defined(TARGET_ARM64) + if (index >= NUM_ARGUMENT_REGISTERS) + return false; + + ArgumentRegisters regs; +#if defined(TARGET_X86) + regs.ECX = (INT32)pContext->Ecx; + regs.EDX = (INT32)pContext->Edx; +#elif defined(TARGET_AMD64) +#ifdef UNIX_AMD64_ABI + regs.RDI = (INT_PTR)pContext->Rdi; + regs.RSI = (INT_PTR)pContext->Rsi; + regs.RDX = (INT_PTR)pContext->Rdx; + regs.RCX = (INT_PTR)pContext->Rcx; + regs.R8 = (INT_PTR)pContext->R8; + regs.R9 = (INT_PTR)pContext->R9; +#else + regs.RCX = (INT_PTR)pContext->Rcx; + regs.RDX = (INT_PTR)pContext->Rdx; + regs.R8 = (INT_PTR)pContext->R8; + regs.R9 = (INT_PTR)pContext->R9; +#endif // UNIX_AMD64_ABI +#elif defined(TARGET_ARM) + regs.r[0] = (INT32)pContext->R0; + regs.r[1] = (INT32)pContext->R1; + regs.r[2] = (INT32)pContext->R2; + regs.r[3] = (INT32)pContext->R3; +#elif defined(TARGET_ARM64) + for (unsigned i = 0; i < NUM_ARGUMENT_REGISTERS; i++) + regs.x[i] = (INT64)pContext->X[i]; +#endif + + static_assert(sizeof(ArgumentRegisters) == NUM_ARGUMENT_REGISTERS * sizeof(TADDR), + "Argument registers are expected to be a flat array of pointer sized values"); + + *pValue = ((TADDR*)®s)[index]; + return true; +#else + // Not implemented for this architecture. + return false; +#endif +} + +// Recovers the unmanaged target of an unmanaged CALLI stub from a context captured at the stub's +// entry point. The target is passed as the last argument of the stub - see +// code:BuildCalliILStubSignature. +static bool TryGetCalliStubTarget(MethodDesc *pStubMD, T_CONTEXT *pContext, PCODE *pTarget) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(pStubMD->AsDynamicMethodDesc()->IsPInvokeCalliStub()); + } + CONTRACTL_END; + + MetaSig msig(pStubMD); + ArgIterator argit(&msig); + + int targetOffset = TransitionBlock::InvalidOffset; + int argOffset; + while ((argOffset = argit.GetNextOffset()) != TransitionBlock::InvalidOffset) + { + targetOffset = argOffset; + } + + if (targetOffset == TransitionBlock::InvalidOffset) + return false; + + if (TransitionBlock::IsArgumentRegisterOffset(targetOffset)) + { + TADDR value; + if (!TryGetArgumentRegister(pContext, TransitionBlock::GetArgumentIndexFromOffset(targetOffset), &value)) + return false; + + *pTarget = (PCODE)value; + return true; + } + + // The incoming stack arguments start right past the return address when the call instruction + // pushes it on the stack, and at the stack pointer itself when it is passed in a link register. +#if defined(TARGET_X86) || defined(TARGET_AMD64) + TADDR stackArgs = GetSP(pContext) + sizeof(TADDR); +#else + TADDR stackArgs = GetSP(pContext); +#endif + + *pTarget = *(PCODE*)(stackArgs + (targetOffset - TransitionBlock::GetOffsetOfArgs())); + return true; +} + BOOL ILStubManager::TraceManager(Thread *thread, TraceDestination *trace, T_CONTEXT *pContext, @@ -1755,11 +1854,17 @@ BOOL ILStubManager::TraceManager(Thread *thread, } else if (pStubMD->IsPInvokeCalliStub()) { - // This is an unmanaged CALLI stub. The native target is passed as the last argument - // of the stub rather than in a hidden argument, so we cannot recover it from the - // register context here. - LOG((LF_CORDB, LL_INFO1000, "ILSM::TraceManager: Unmanaged CALLI stub - target is not traceable\n")); - return FALSE; + // This is an unmanaged CALLI stub. The native target is passed as its last argument + // rather than in a hidden argument, so it has to be read out of the incoming argument + // location that the calling convention assigned to it. + if (!TryGetCalliStubTarget(pStubMD, pContext, &target)) + { + LOG((LF_CORDB, LL_INFO1000, "ILSM::TraceManager: Unmanaged CALLI stub - could not locate the target\n")); + return FALSE; + } + + LOG((LF_CORDB, LL_INFO10000, "ILSM::TraceManager: Unmanaged CALLI case %p\n", target)); + trace->InitForUnmanaged(target); } else if (pStubMD->IsStepThroughStub()) { From 473a27a7f8458d6d534198c3383089a2ac004e21 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 27 Jul 2026 23:09:42 -0700 Subject: [PATCH 3/8] Remove the dead PInvoke calli cookie path ReadyToRun never fell back to the runtime helper for an unmanaged calli that cannot be expanded inline - GetCookieForPInvokeCalliSig throws RequiresRuntimeJitException, so the cookie call was only ever an abort that left the whole method to the runtime JIT. Make that explicit by implementing convertPInvokeCalliToCall in crossgen2, which throws RequiresRuntimeJitException for exactly the call sites the JIT cannot expand: those where an inline P/Invoke frame is not allowed, a calling convention the JIT refuses to inline, an unmanaged signature carrying an instance 'this', or a signature that needs marshalling. The JIT then calls convertPInvokeCalliToCall unconditionally and the entire cookie mechanism goes away: GenTreeCall::gtCallCookie, the two well known args and their argument registers on all seven architectures, GTF_ICON_PINVKI_HDL, the morph rewrite into CORINFO_HELP_PINVOKE_CALLI, LowerIndirectNonvirtCall and eeConvertToLookup. CORINFO_HELP_PINVOKE_CALLI and GetCookieForPInvokeCalliSig stay declared on the JIT-EE interface, now unused, so this does not need a JIT-EE version bump. Also rejects unmanaged calli signatures that carry an instance 'this' with the same exception a P/Invoke declared on an instance method gets, instead of declining to build a stub for them. Such a signature is not expressible as a stub and the JIT cannot expand it inline either, so without this the JIT could emit a plain managed indirect call for one. A native member function is expressed as a static signature whose first parameter is the 'this' pointer and is unaffected. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- docs/design/coreclr/botr/clr-abi.md | 2 - docs/design/coreclr/botr/guide-for-porting.md | 6 +-- src/coreclr/inc/corinfo.h | 6 ++- src/coreclr/inc/jithelpers.h | 2 + src/coreclr/jit/compiler.h | 2 - src/coreclr/jit/ee_il_dll.cpp | 28 ---------- src/coreclr/jit/gentree.cpp | 36 ++----------- src/coreclr/jit/gentree.h | 3 -- src/coreclr/jit/handlekinds.h | 1 - src/coreclr/jit/importercalls.cpp | 52 ++++--------------- src/coreclr/jit/lower.cpp | 14 +---- src/coreclr/jit/lower.h | 1 - src/coreclr/jit/morph.cpp | 29 ----------- src/coreclr/jit/targetamd64.h | 8 --- src/coreclr/jit/targetarm.h | 8 --- src/coreclr/jit/targetarm64.h | 8 --- src/coreclr/jit/targetloongarch64.h | 8 --- src/coreclr/jit/targetriscv64.h | 8 --- src/coreclr/jit/targetwasm.h | 8 --- src/coreclr/jit/targetx86.h | 7 --- src/coreclr/jit/wellknownargs.h | 2 - .../Common/JitInterface/CorInfoHelpFunc.cs | 2 +- .../JitInterface/CorInfoImpl.ReadyToRun.cs | 28 +++++++++- .../superpmi-shim-collector/icorjitinfo.cpp | 2 +- .../tools/superpmi/superpmi/icorjitinfo.cpp | 2 +- src/coreclr/vm/amd64/asmconstants.h | 3 -- src/coreclr/vm/dllimport.cpp | 30 ++++++++--- src/coreclr/vm/ilstubresolver.cpp | 11 +++- src/coreclr/vm/ilstubresolver.h | 4 +- src/coreclr/vm/jitinterface.cpp | 8 +-- src/coreclr/vm/method.hpp | 9 ++-- src/coreclr/vm/prestub.cpp | 11 ++-- 32 files changed, 102 insertions(+), 247 deletions(-) diff --git a/docs/design/coreclr/botr/clr-abi.md b/docs/design/coreclr/botr/clr-abi.md index 0585bfee301291..f0510bfa4afeee 100644 --- a/docs/design/coreclr/botr/clr-abi.md +++ b/docs/design/coreclr/botr/clr-abi.md @@ -148,8 +148,6 @@ ARM64-only: When a method returns a structure that is larger than 16 bytes the c *Stub dispatch* - when a virtual call uses a VSD stub, rather than back-patching the calling code (or disassembling it), the JIT must place the address of the stub used to load the call target, the "stub indirection cell", in (x86) `EAX` / (AMD64) `R11` / (ARM) `R12` / (ARM64) `R11`. In the JIT, this is encapsulated in the `VirtualStubParamInfo` class. -*Calli Pinvoke* - The VM wants the address of the PInvoke in (AMD64) `R10` / (ARM) `R12` / (ARM64) `R14` (In the JIT: `REG_PINVOKE_TARGET_PARAM`), and the signature (the pinvoke cookie) in (AMD64) `R11` / (ARM) `R4` / (ARM64) `R15` (in the JIT: `REG_PINVOKE_COOKIE_PARAM`). - *Normal PInvoke* - The VM shares IL stubs based on signatures, but wants the right method to show up in call stack and exceptions, so the MethodDesc for the exact PInvoke is passed in the (x86) `EAX` / (AMD64) `R10` / (ARM, ARM64) `R12` (in the JIT: `REG_SECRET_STUB_PARAM`). Then in the IL stub, when the JIT gets `CORJIT_FLG_PUBLISH_SECRET_PARAM`, it must move the register into a compiler temp. The value is returned for the intrinsic `NI_System_StubHelpers_GetStubContext`. ## Small primitive returns diff --git a/docs/design/coreclr/botr/guide-for-porting.md b/docs/design/coreclr/botr/guide-for-porting.md index ff90ba5c7df419..f569807cb63389 100644 --- a/docs/design/coreclr/botr/guide-for-porting.md +++ b/docs/design/coreclr/botr/guide-for-porting.md @@ -400,10 +400,8 @@ Here is an annotated list of the stubs implemented for Unix on Arm64. for ReadyToRun pre-compiled pinvoke calls, so that they do not cause GC starvation - 15. `VarargPInvokeStub`/ `GenericPInvokeCalliHelper` Used to support calli - pinvokes. It is expected that C\# 8.0 will increase use of this feature. - Today use of this feature on Unix requires hand-written IL. On Windows - this feature is commonly used by C++/CLI + 15. `VarargPInvokeStub` – Used to support vararg pinvokes, which are commonly + used by C++/CLI. Not necessary for non-Windows platforms at this time. #### cgencpu.h diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index a01c79d6dbfbda..ab37c25996e65c 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -481,7 +481,8 @@ enum CorInfoHelpFunc /* Miscellaneous */ - CORINFO_HELP_PINVOKE_CALLI, // Indirect pinvoke call + CORINFO_HELP_PINVOKE_CALLI, // Unused; unmanaged calli is expanded inline or converted to a call + // to a marshalling stub by convertPInvokeCalliToCall CORINFO_HELP_TAILCALL, // Perform a tail call CORINFO_HELP_GETCURRENTMANAGEDTHREADID, @@ -3384,7 +3385,8 @@ class ICorDynamicInfo : public ICorStaticInfo CORINFO_CONST_LOOKUP * pLookup ) = 0; - // Generate a cookie based on the signature to pass to CORINFO_HELP_PINVOKE_CALLI + // Unused; unmanaged calli is expanded inline or converted to a call to a marshalling stub + // by convertPInvokeCalliToCall. virtual void* GetCookieForPInvokeCalliSig( CORINFO_SIG_INFO* szMetaSig, void** ppIndirection = NULL diff --git a/src/coreclr/inc/jithelpers.h b/src/coreclr/inc/jithelpers.h index d9b3f7972f136e..49526413092cb7 100644 --- a/src/coreclr/inc/jithelpers.h +++ b/src/coreclr/inc/jithelpers.h @@ -216,6 +216,8 @@ DYNAMICJITHELPER(CORINFO_HELP_PROF_FCN_TAILCALL, JIT_ProfilerEnterLeaveTailcallStub, METHOD__NIL) // Miscellaneous + // CORINFO_HELP_PINVOKE_CALLI is unused; unmanaged calli is expanded inline or converted to a + // call to a marshalling stub by convertPInvokeCalliToCall. JITHELPER(CORINFO_HELP_PINVOKE_CALLI, NULL, METHOD__NIL) #if defined(TARGET_X86) && !defined(UNIX_X86_ABI) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index a84e927a407dd2..132d726d9ee31d 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -9641,8 +9641,6 @@ class Compiler // Get the offset of a MDArray's lower bound for a given dimension. static unsigned eeGetMDArrayLowerBoundOffset(unsigned rank, unsigned dimension); - CORINFO_CONST_LOOKUP eeConvertToLookup(void* value, void* pValue); - // Returns the page size for the target machine as reported by the EE. target_size_t eeGetPageSize() { diff --git a/src/coreclr/jit/ee_il_dll.cpp b/src/coreclr/jit/ee_il_dll.cpp index 47af3c1f9b7e06..f996735d520c6f 100644 --- a/src/coreclr/jit/ee_il_dll.cpp +++ b/src/coreclr/jit/ee_il_dll.cpp @@ -504,34 +504,6 @@ unsigned Compiler::eeGetArgSizeAlignment(var_types type, bool isFloatHfa) } } -//------------------------------------------------------------------------ -// eeConvertToLookup: Convert a tuple of "{ value, pValue }" to "CORINFO_CONST_LOOKUP". -// -// Arguments: -// value - The direct value (IAT_VALUE) -// pValue - The indirect value (IAT_PVALUE) -// -// Return Value: -// The lookup. -// -CORINFO_CONST_LOOKUP Compiler::eeConvertToLookup(void* value, void* pValue) -{ - CORINFO_CONST_LOOKUP lookup; - if (value != nullptr) - { - assert(pValue == nullptr); - lookup.accessType = IAT_VALUE; - lookup.addr = value; - } - else - { - assert(pValue != nullptr); - lookup.accessType = IAT_PVALUE; - lookup.addr = pValue; - } - return lookup; -} - //------------------------------------------------------------------------ // eeGetArrayDataOffset: Gets the offset of a SDArray's first element // diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index e405606544b083..7d64f909262b4b 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -1608,14 +1608,6 @@ bool CallArgs::GetCustomRegister(Compiler* comp, CorInfoCallConvExtension cc, We *reg = comp->virtualStubParamInfo->GetReg(); return true; - case WellKnownArg::PInvokeCookie: - *reg = REG_PINVOKE_COOKIE_PARAM; - return true; - - case WellKnownArg::PInvokeTarget: - *reg = REG_PINVOKE_TARGET_PARAM; - return true; - case WellKnownArg::R2RIndirectionCell: *reg = REG_R2R_INDIRECT_PARAM; return true; @@ -2398,12 +2390,6 @@ int GenTreeCall::GetNonStandardAddedArgCount(Compiler* compiler) const // R11 = Virtual stub param return 1; } - else if ((gtCallType == CT_INDIRECT) && (gtCallCookie != nullptr)) - { - // R10 = PInvoke target param - // R11 = PInvoke cookie param - return 2; - } return 0; } @@ -2621,18 +2607,6 @@ bool GenTreeCall::Equals(GenTreeCall* c1, GenTreeCall* c2) } } } - else if (!c1->IsVirtualStub()) - { - if ((c1->gtCallCookie == nullptr) != (c2->gtCallCookie == nullptr)) - { - return false; - } - - if ((c1->gtCallCookie != nullptr) && !sameLookup(*c1->gtCallCookie, *c2->gtCallCookie)) - { - return false; - } - } auto sameArgMetadata = [](CallArg* a1, CallArg* a2) { return (a1->GetSignatureType() == a2->GetSignatureType()) && @@ -10121,11 +10095,10 @@ GenTreeCall* Compiler::gtNewCallNode(gtCallTypes callType, node->gtRetClsHnd = nullptr; node->gtCallMoreFlags = GTF_CALL_M_EMPTY; INDEBUG(node->gtCallDebugFlags = GTF_CALL_MD_EMPTY); - node->gtInlineInfoCount = 0; + node->ClearInlineInfo(); if (callType == CT_INDIRECT) { - node->gtCallCookie = nullptr; node->gtCallMethHnd = NO_METHOD_HANDLE; node->gtControlExpr = (GenTree*)callHnd; } @@ -10133,7 +10106,6 @@ GenTreeCall* Compiler::gtNewCallNode(gtCallTypes callType, { node->gtCallMethHnd = callHnd; node->gtControlExpr = nullptr; - node->ClearInlineInfo(); } node->gtReturnType = type; @@ -11752,15 +11724,15 @@ GenTreeCall* Compiler::gtCloneExprCallHelper(GenTreeCall* tree) copy->gtStubCallStubAddr = tree->gtStubCallStubAddr; /* Copy the union */ + copy->gtInlineCandidateInfo = tree->gtInlineCandidateInfo; + if (tree->gtCallType == CT_INDIRECT) { - copy->gtCallCookie = tree->gtCallCookie; copy->gtCallMethHnd = NO_METHOD_HANDLE; } else { - copy->gtCallMethHnd = tree->gtCallMethHnd; - copy->gtInlineCandidateInfo = tree->gtInlineCandidateInfo; + copy->gtCallMethHnd = tree->gtCallMethHnd; } copy->gtInlineInfoCount = tree->gtInlineInfoCount; diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index cce0ecb434e2fe..18905a4f700d20 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -5875,9 +5875,6 @@ struct GenTreeCall final : public GenTree union { - // The serialized CALLI unmanaged call (CT_INDIRECT) cookie; reified into argument IR in morph - CORINFO_CONST_LOOKUP* gtCallCookie; - // gtInlineCandidateInfo is only used when inlining methods InlineCandidateInfo* gtInlineCandidateInfo; // gtInlineCandidateInfoList is used when we have more than one GDV candidate diff --git a/src/coreclr/jit/handlekinds.h b/src/coreclr/jit/handlekinds.h index 9516c5193c8baf..d575617302b638 100644 --- a/src/coreclr/jit/handlekinds.h +++ b/src/coreclr/jit/handlekinds.h @@ -16,7 +16,6 @@ HANDLE_KIND(GTF_ICON_OBJ_HDL , "object" , 0) HANDLE_KIND(GTF_ICON_CONST_PTR , "const ptr" , HKF_INVARIANT) // pointer to immutable data, (e.g. IAT_PPVALUE) HANDLE_KIND(GTF_ICON_GLOBAL_PTR , "global ptr" , 0) // pointer to mutable data (e.g. from the VM state) HANDLE_KIND(GTF_ICON_VARG_HDL , "vararg" , HKF_INVARIANT) // var arg cookie handle -HANDLE_KIND(GTF_ICON_PINVKI_HDL , "pinvoke" , 0) // pinvoke calli handle HANDLE_KIND(GTF_ICON_TOKEN_HDL , "token" , HKF_INVARIANT) // token handle (other than class, method or field) HANDLE_KIND(GTF_ICON_TLS_HDL , "tls" , HKF_INVARIANT) // TLS ref with offset HANDLE_KIND(GTF_ICON_FTN_ADDR , "ftn" , 0) // function address diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index bb905baf006cfc..4df63847d95a6c 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -339,17 +339,12 @@ var_types Compiler::impImportCall(OPCODE opcode, if (opcode == CEE_CALLI) { - // ReadyToRun does not support converting an unmanaged calli into a call to a - // marshalling stub; such call sites are handled by the PInvoke calli cookie below. - if (!IsReadyToRun()) + if (info.compCompHnd->convertPInvokeCalliToCall(pResolvedToken, !impCanPInvokeInlineCallSite(compCurBB))) { - if (info.compCompHnd->convertPInvokeCalliToCall(pResolvedToken, !impCanPInvokeInlineCallSite(compCurBB))) - { - // The VM only fills in hMethod; derive the rest of the token from it. - pResolvedToken->hClass = info.compCompHnd->getMethodClass(pResolvedToken->hMethod); - eeGetCallInfo(pResolvedToken, nullptr, CORINFO_CALLINFO_ALLOWINSTPARAM, callInfo); - return impImportCall(CEE_CALL, pResolvedToken, nullptr, nullptr, prefixFlags, callInfo, rawILOffset); - } + // The VM only fills in hMethod; derive the rest of the token from it. + pResolvedToken->hClass = info.compCompHnd->getMethodClass(pResolvedToken->hMethod); + eeGetCallInfo(pResolvedToken, nullptr, CORINFO_CALLINFO_ALLOWINSTPARAM, callInfo); + return impImportCall(CEE_CALL, pResolvedToken, nullptr, nullptr, prefixFlags, callInfo, rawILOffset); } /* Get the call site sig */ @@ -939,20 +934,6 @@ var_types Compiler::impImportCall(OPCODE opcode, goto DONE; } - else if ((opcode == CEE_CALLI) && ((sig->callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_DEFAULT) && - ((sig->callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_VARARG)) - { - void* pCookie; - void* cookie = info.compCompHnd->GetCookieForPInvokeCalliSig(sig, &pCookie); - CORINFO_CONST_LOOKUP cookieLookup = eeConvertToLookup(cookie, pCookie); - call->AsCall()->gtCallCookie = new (getAllocator(CMK_ASTNode)) CORINFO_CONST_LOOKUP(cookieLookup); - - if (canTailCall) - { - canTailCall = false; - szCanTailCallFailReason = "PInvoke calli"; - } - } if (sig->isAsyncCall()) { @@ -7305,8 +7286,6 @@ void Compiler::impCheckForPInvokeCall( } unmanagedCallConv = info.compCompHnd->getUnmanagedCallConv(nullptr, sig, &suppressGCTransition); - - assert(!call->gtCallCookie); } if (suppressGCTransition) @@ -7329,13 +7308,11 @@ void Compiler::impCheckForPInvokeCall( } optNativeCallCount++; - if (methHnd == nullptr && - (!IsReadyToRun() || (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_IL_STUB) && !compIsForInlining()))) + if (methHnd == nullptr) { - // PInvoke CALLI outside of ReadyToRun must always be inlined. Non-inlineable CALLI cases - // have been converted to regular method calls earlier using convertPInvokeCalliToCall. - - // PInvoke CALLI in IL stubs must be inlined + // PInvoke CALLI must always be inlined. Call sites that cannot be inlined have been converted + // to a call to a marshalling stub earlier using convertPInvokeCalliToCall, or, in ReadyToRun, + // have aborted the compilation of this method. } else if (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_IL_STUB) && IsReadyToRun()) { @@ -8522,17 +8499,6 @@ void Compiler::addGuardedDevirtualizationCandidate(GenTreeCall* call, return; } - // CT_INDIRECT calls may use the cookie, bail if so... - // - // If transforming these provides a benefit, we could save this off in the same way - // we save the stub address below. - if ((call->gtCallType == CT_INDIRECT) && (call->AsCall()->gtCallCookie != nullptr)) - { - JITDUMP("NOT Marking call [%06u] as guarded devirtualization candidate -- CT_INDIRECT with cookie\n", - dspTreeID(call)); - return; - } - #ifdef DEBUG // See if disabled by range diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 099e0400e1794c..d5c287df9a3707 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -2903,11 +2903,7 @@ GenTree* Lowering::LowerCall(GenTree* node) { controlExpr = LowerNonvirtPinvokeCall(call); } - else if (call->gtCallType == CT_INDIRECT) - { - controlExpr = LowerIndirectNonvirtCall(call); - } - else + else if (call->gtCallType != CT_INDIRECT) { controlExpr = LowerDirectCall(call); } @@ -6659,14 +6655,6 @@ void Lowering::OptimizeCallIndirectTargetEvaluation(GenTreeCall* call) DISPTREERANGE(BlockRange(), call); } -GenTree* Lowering::LowerIndirectNonvirtCall(GenTreeCall* call) -{ - // Indirect cookie calls gets transformed by fgMorphArgs as indirect call with non-standard args. - // Hence we should never see this type of call in lower. - noway_assert(call->gtCallCookie == nullptr); - return nullptr; -} - //------------------------------------------------------------------------ // CreateReturnTrapSeq: Create a tree to perform a "return trap", used in PInvoke // epilogs to invoke a GC under a condition. The return trap checks some global diff --git a/src/coreclr/jit/lower.h b/src/coreclr/jit/lower.h index bcc17b9ae29630..01348695c23541 100644 --- a/src/coreclr/jit/lower.h +++ b/src/coreclr/jit/lower.h @@ -199,7 +199,6 @@ class Lowering final : public Phase #endif // WINDOWS_AMD64_ABI GenTree* LowerDelegateInvoke(GenTreeCall* call); void OptimizeCallIndirectTargetEvaluation(GenTreeCall* call); - GenTree* LowerIndirectNonvirtCall(GenTreeCall* call); GenTree* LowerDirectCall(GenTreeCall* call); GenTree* LowerNonvirtPinvokeCall(GenTreeCall* call); GenTree* LowerTailCallViaJitHelper(GenTreeCall* callNode, GenTree* callTarget); diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 3f58a31b3f0625..c5305b8bbccf7a 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -1756,34 +1756,6 @@ void CallArgs::AddFinalArgsAndDetermineABIInfo(Compiler* comp, GenTreeCall* call // add as a non-standard arg. } } - else if ((call->gtCallType == CT_INDIRECT) && !call->IsVirtualStub() && (call->gtCallCookie != nullptr)) - { - assert(!call->IsUnmanaged()); - - GenTree* arg = comp->gtNewIconEmbHndNode(call->gtCallCookie, GTF_ICON_PINVKI_HDL, nullptr); - call->gtCallCookie = nullptr; - - // TODO: this is preserving existing behavior, but do we actually need these NO_CSEs? - GenTree* argConst = arg->OperIs(GT_IND) ? arg->AsIndir()->Addr() : arg; - argConst->gtFlags |= GTF_DONT_CSE; - arg->gtFlags |= GTF_DONT_CSE; - - // All architectures pass the cookie in a register. - InsertAfterThisOrFirst(comp, NewCallArg::Primitive(arg).WellKnown(WellKnownArg::PInvokeCookie)); - // put destination into R10/EAX - arg = comp->gtClone(call->gtControlExpr, true); - // On x64 the pinvoke target is passed in r10 which is the same - // register as the gs cookie check may use. That would be a problem if - // this was a tailcall, but we do not tailcall functions with - // non-standard added args except indirection cells currently. - assert(!call->IsFastTailCall()); - InsertAfterThisOrFirst(comp, NewCallArg::Primitive(arg).WellKnown(WellKnownArg::PInvokeTarget)); - - // finally change this call to a helper call - call->gtCallType = CT_HELPER; - call->gtControlExpr = nullptr; - call->gtCallMethHnd = comp->eeFindHelper(CORINFO_HELP_PINVOKE_CALLI); - } #if defined(FEATURE_READYTORUN) #ifdef TARGET_WASM @@ -5824,7 +5796,6 @@ void Compiler::fgMorphTailCallViaJitHelper(GenTreeCall* call) // Check for PInvoke call types that we don't handle in codegen yet. assert(!call->IsUnmanaged()); - assert(call->IsVirtual() || (call->gtCallType != CT_INDIRECT) || (call->gtCallCookie == nullptr)); // Don't support tail calling helper methods assert(!call->IsHelperCall()); diff --git a/src/coreclr/jit/targetamd64.h b/src/coreclr/jit/targetamd64.h index f0ab6b24f44d78..56a4d5b0eeb876 100644 --- a/src/coreclr/jit/targetamd64.h +++ b/src/coreclr/jit/targetamd64.h @@ -384,14 +384,6 @@ // See ImportThunk.Kind.DelayLoadHelperWithExistingIndirectionCell in crossgen2. #define RBM_R2R_INDIRECT_PARAM RBM_RAX -// GenericPInvokeCalliHelper VASigCookie Parameter -#define REG_PINVOKE_COOKIE_PARAM REG_R11 -#define RBM_PINVOKE_COOKIE_PARAM RBM_R11 - -// GenericPInvokeCalliHelper unmanaged target Parameter -#define REG_PINVOKE_TARGET_PARAM REG_R10 -#define RBM_PINVOKE_TARGET_PARAM RBM_R10 - // IL stub's secret MethodDesc parameter (JitFlags::JIT_FLAG_PUBLISH_SECRET_PARAM) #define REG_SECRET_STUB_PARAM REG_R10 #define RBM_SECRET_STUB_PARAM RBM_R10 diff --git a/src/coreclr/jit/targetarm.h b/src/coreclr/jit/targetarm.h index 006bd4e0559b6b..9b431efcdaaa7c 100644 --- a/src/coreclr/jit/targetarm.h +++ b/src/coreclr/jit/targetarm.h @@ -162,14 +162,6 @@ // Registers no longer containing GC pointers after CORINFO_HELP_ASSIGN_REF and CORINFO_HELP_CHECKED_ASSIGN_REF. #define RBM_CALLEE_GCTRASH_WRITEBARRIER RBM_CALLEE_TRASH_WRITEBARRIER -// GenericPInvokeCalliHelper VASigCookie Parameter -#define REG_PINVOKE_COOKIE_PARAM REG_R4 -#define RBM_PINVOKE_COOKIE_PARAM RBM_R4 - -// GenericPInvokeCalliHelper unmanaged target Parameter -#define REG_PINVOKE_TARGET_PARAM REG_R12 -#define RBM_PINVOKE_TARGET_PARAM RBM_R12 - // IL stub's secret MethodDesc parameter (JitFlags::JIT_FLAG_PUBLISH_SECRET_PARAM) #define REG_SECRET_STUB_PARAM REG_R12 #define RBM_SECRET_STUB_PARAM RBM_R12 diff --git a/src/coreclr/jit/targetarm64.h b/src/coreclr/jit/targetarm64.h index 0f196cacf5e836..cc9c48583641c0 100644 --- a/src/coreclr/jit/targetarm64.h +++ b/src/coreclr/jit/targetarm64.h @@ -184,14 +184,6 @@ // Registers no longer containing GC pointers after CORINFO_HELP_ASSIGN_REF and CORINFO_HELP_CHECKED_ASSIGN_REF. #define RBM_CALLEE_GCTRASH_WRITEBARRIER RBM_CALLEE_TRASH_NOGC -// GenericPInvokeCalliHelper VASigCookie Parameter -#define REG_PINVOKE_COOKIE_PARAM REG_R15 -#define RBM_PINVOKE_COOKIE_PARAM RBM_R15 - -// GenericPInvokeCalliHelper unmanaged target Parameter -#define REG_PINVOKE_TARGET_PARAM REG_R12 -#define RBM_PINVOKE_TARGET_PARAM RBM_R12 - // IL stub's secret MethodDesc parameter (JitFlags::JIT_FLAG_PUBLISH_SECRET_PARAM) #define REG_SECRET_STUB_PARAM REG_R12 #define RBM_SECRET_STUB_PARAM RBM_R12 diff --git a/src/coreclr/jit/targetloongarch64.h b/src/coreclr/jit/targetloongarch64.h index 6acd2a67dfeebd..c4d9deae15789b 100644 --- a/src/coreclr/jit/targetloongarch64.h +++ b/src/coreclr/jit/targetloongarch64.h @@ -163,14 +163,6 @@ // Registers no longer containing GC pointers after CORINFO_HELP_ASSIGN_REF and CORINFO_HELP_CHECKED_ASSIGN_REF. #define RBM_CALLEE_GCTRASH_WRITEBARRIER RBM_CALLEE_TRASH_NOGC -// GenericPInvokeCalliHelper VASigCookie Parameter -#define REG_PINVOKE_COOKIE_PARAM REG_T3 -#define RBM_PINVOKE_COOKIE_PARAM RBM_T3 - -// GenericPInvokeCalliHelper unmanaged target Parameter -#define REG_PINVOKE_TARGET_PARAM REG_T2 -#define RBM_PINVOKE_TARGET_PARAM RBM_T2 - // IL stub's secret MethodDesc parameter (JitFlags::JIT_FLAG_PUBLISH_SECRET_PARAM) #define REG_SECRET_STUB_PARAM REG_T2 #define RBM_SECRET_STUB_PARAM RBM_T2 diff --git a/src/coreclr/jit/targetriscv64.h b/src/coreclr/jit/targetriscv64.h index 9679439d637025..3ec1ad135e5fd0 100644 --- a/src/coreclr/jit/targetriscv64.h +++ b/src/coreclr/jit/targetriscv64.h @@ -158,14 +158,6 @@ // Registers no longer containing GC pointers after CORINFO_HELP_ASSIGN_REF and CORINFO_HELP_CHECKED_ASSIGN_REF. #define RBM_CALLEE_GCTRASH_WRITEBARRIER RBM_CALLEE_TRASH_NOGC -// GenericPInvokeCalliHelper VASigCookie Parameter -#define REG_PINVOKE_COOKIE_PARAM REG_T3 -#define RBM_PINVOKE_COOKIE_PARAM RBM_T3 - -// GenericPInvokeCalliHelper unmanaged target Parameter -#define REG_PINVOKE_TARGET_PARAM REG_T2 -#define RBM_PINVOKE_TARGET_PARAM RBM_T2 - // IL stub's secret MethodDesc parameter (JitFlags::JIT_FLAG_PUBLISH_SECRET_PARAM) #define REG_SECRET_STUB_PARAM REG_T2 #define RBM_SECRET_STUB_PARAM RBM_T2 diff --git a/src/coreclr/jit/targetwasm.h b/src/coreclr/jit/targetwasm.h index 4c2ac831cb07d1..00ad95fe143f3b 100644 --- a/src/coreclr/jit/targetwasm.h +++ b/src/coreclr/jit/targetwasm.h @@ -155,14 +155,6 @@ // Registers no longer containing GC pointers after CORINFO_HELP_ASSIGN_REF and CORINFO_HELP_CHECKED_ASSIGN_REF. #define RBM_CALLEE_GCTRASH_WRITEBARRIER RBM_CALLEE_TRASH_NOGC -// GenericPInvokeCalliHelper VASigCookie Parameter -#define REG_PINVOKE_COOKIE_PARAM REG_NA -#define RBM_PINVOKE_COOKIE_PARAM RBM_NONE - -// GenericPInvokeCalliHelper unmanaged target Parameter -#define REG_PINVOKE_TARGET_PARAM REG_NA -#define RBM_PINVOKE_TARGET_PARAM RBM_NONE - // IL stub's secret MethodDesc parameter (JitFlags::JIT_FLAG_PUBLISH_SECRET_PARAM) #define REG_SECRET_STUB_PARAM REG_NA #define RBM_SECRET_STUB_PARAM RBM_NONE diff --git a/src/coreclr/jit/targetx86.h b/src/coreclr/jit/targetx86.h index dd338c316a3d0d..8d3d6c7eb5b8af 100644 --- a/src/coreclr/jit/targetx86.h +++ b/src/coreclr/jit/targetx86.h @@ -226,13 +226,6 @@ // Registers no longer containing GC pointers after CORINFO_HELP_ASSIGN_REF and CORINFO_HELP_CHECKED_ASSIGN_REF. #define RBM_CALLEE_GCTRASH_WRITEBARRIER RBM_EDX -// GenericPInvokeCalliHelper unmanaged target parameter -#define REG_PINVOKE_TARGET_PARAM REG_EAX -#define RBM_PINVOKE_TARGET_PARAM RBM_EAX - -// GenericPInvokeCalliHelper cookie parameter -#define REG_PINVOKE_COOKIE_PARAM REG_EBX - // IL stub's secret parameter (JitFlags::JIT_FLAG_PUBLISH_SECRET_PARAM) #define REG_SECRET_STUB_PARAM REG_EAX #define RBM_SECRET_STUB_PARAM RBM_EAX diff --git a/src/coreclr/jit/wellknownargs.h b/src/coreclr/jit/wellknownargs.h index 23976312e5e844..b08800173046c7 100644 --- a/src/coreclr/jit/wellknownargs.h +++ b/src/coreclr/jit/wellknownargs.h @@ -26,8 +26,6 @@ WELL_KNOWN_ARG(PInvokeFrame, "pinv frame", false, false) WELL_KNOWN_ARG(ShiftLow, "shift low", false, false) WELL_KNOWN_ARG(ShiftHigh, "shift high", false, false) WELL_KNOWN_ARG(VirtualStubCell, "vsd cell", false, true) -WELL_KNOWN_ARG(PInvokeCookie, "pinv cookie", false, true) -WELL_KNOWN_ARG(PInvokeTarget, "pinv tgt", false, true) WELL_KNOWN_ARG(R2RIndirectionCell, "r2r cell", false, true) WELL_KNOWN_ARG(ValidateIndirectCallTarget, "cfg tgt", false, false) WELL_KNOWN_ARG(DispatchIndirectCallTarget, "cfg tgt", false, false) diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs b/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs index 68f7b52f6ec048..bdc25a914d0553 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs @@ -179,7 +179,7 @@ which is the right helper to use to allocate an object of a given type. */ /* Miscellaneous */ - CORINFO_HELP_PINVOKE_CALLI, // Indirect pinvoke call + CORINFO_HELP_PINVOKE_CALLI, // Unused CORINFO_HELP_TAILCALL, // Perform a tail call CORINFO_HELP_GETCURRENTMANAGEDTHREADID, diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs index 565034235355b5..887a3774d2584d 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -3332,7 +3332,33 @@ private bool pInvokeMarshalingRequired(CORINFO_METHOD_STRUCT_* handle, CORINFO_S private bool convertPInvokeCalliToCall(ref CORINFO_RESOLVED_TOKEN pResolvedToken, bool mustConvert) { - throw new NotImplementedException(); + var methodIL = (MethodIL)HandleToObject((void*)pResolvedToken.tokenScope); + var signature = (MethodSignature)methodIL.GetObject((int)pResolvedToken.token); + + switch (signature.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask) + { + case MethodSignatureFlags.None: + case MethodSignatureFlags.CallingConventionVarargs: + // Not an unmanaged call site. + return false; + } + + CorInfoCallConvExtension unmanagedCallConv = GetUnmanagedCallConv(signature, out _); + + // ReadyToRun has no way to refer to the marshalling stub the runtime creates for an unmanaged + // calli, so the JIT has to expand the call site inline. Everything it cannot expand - a call site + // where an inline P/Invoke frame is not allowed, a calling convention the JIT refuses to inline, + // an unmanaged signature carrying an instance 'this', or a signature that needs marshalling - + // leaves the method to the runtime JIT. + if (mustConvert || + !signature.IsStatic || signature.IsExplicitThis || + unmanagedCallConv is CorInfoCallConvExtension.Fastcall or CorInfoCallConvExtension.FastcallMemberFunction || + Marshaller.IsMarshallingRequired(signature, Array.Empty(), ((MetadataType)methodIL.OwningMethod.OwningType).Module)) + { + throw new RequiresRuntimeJitException($"{MethodBeingCompiled} -> {nameof(convertPInvokeCalliToCall)}"); + } + + return false; } private int SizeOfPInvokeTransitionFrame => ReadyToRunRuntimeConstants.READYTORUN_PInvokeTransitionFrameSizeInPointerUnits * PointerSize; diff --git a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp index 82babd5a9d9964..f4220a4084c2d7 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp @@ -1635,7 +1635,7 @@ void interceptor_ICJI::getAddressOfPInvokeTarget(CORINFO_METHOD_HANDLE method, C mc->recGetAddressOfPInvokeTarget(method, pLookup); } -// Generate a cookie based on the signature to pass to CORINFO_HELP_PINVOKE_CALLI +// Unused LPVOID interceptor_ICJI::GetCookieForPInvokeCalliSig(CORINFO_SIG_INFO* szMetaSig, void** ppIndirection) { mc->cr->AddCall("GetCookieForPInvokeCalliSig"); diff --git a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp index 4b8c67aef84bb3..28bc08333f2daf 100644 --- a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp @@ -1401,7 +1401,7 @@ void MyICJI::getAddressOfPInvokeTarget(CORINFO_METHOD_HANDLE method, CORINFO_CON jitInstance->mc->repGetAddressOfPInvokeTarget(method, pLookup); } -// Generate a cookie based on the signature to pass to CORINFO_HELP_PINVOKE_CALLI +// Unused LPVOID MyICJI::GetCookieForPInvokeCalliSig(CORINFO_SIG_INFO* szMetaSig, void** ppIndirection) { jitInstance->mc->cr->AddCall("GetCookieForPInvokeCalliSig"); diff --git a/src/coreclr/vm/amd64/asmconstants.h b/src/coreclr/vm/amd64/asmconstants.h index f6507886d6bda0..4afd23572e6d53 100644 --- a/src/coreclr/vm/amd64/asmconstants.h +++ b/src/coreclr/vm/amd64/asmconstants.h @@ -53,9 +53,6 @@ ASMCONSTANTS_C_ASSERT(ASM_ELEMENT_TYPE_R8 == ELEMENT_TYPE_R8); #define METHODDESC_REGNUM 10 #define METHODDESC_REGISTER r10 -#define PINVOKE_CALLI_TARGET_REGNUM 10 -#define PINVOKE_CALLI_TARGET_REGISTER r10 - #define PINVOKE_CALLI_SIGTOKEN_REGNUM 11 #define PINVOKE_CALLI_SIGTOKEN_REGISTER r11 diff --git a/src/coreclr/vm/dllimport.cpp b/src/coreclr/vm/dllimport.cpp index d4f87c6fb47cbe..dab76c8b742e49 100644 --- a/src/coreclr/vm/dllimport.cpp +++ b/src/coreclr/vm/dllimport.cpp @@ -6280,7 +6280,8 @@ static Signature GetCalliSignatureFromStubSignature(const Signature& stubSignatu } // Determines the unmanaged calling convention of an unmanaged CALLI call site and the -// corresponding stub flags. Returns false if the signature does not describe a P/Invoke. +// corresponding stub flags. Returns false if the signature does not describe a P/Invoke, and +// throws if it describes one the runtime cannot express. static bool TryGetCalliStubCallConv( Module* pModule, const Signature& calliSignature, @@ -6293,11 +6294,6 @@ static bool TryGetCalliStubCallConv( uint32_t rawCallConv; IfFailThrow(sigParser.GetCallingConvInfo(&rawCallConv)); - // Signatures with an instance 'this' are not expressible as a static stub, so leave them - // for the JIT to handle inline. - if ((rawCallConv & (IMAGE_CEE_CS_CALLCONV_GENERIC | IMAGE_CEE_CS_CALLCONV_HASTHIS | IMAGE_CEE_CS_CALLCONV_EXPLICITTHIS)) != 0) - return false; - switch (rawCallConv & IMAGE_CEE_CS_CALLCONV_MASK) { case IMAGE_CEE_CS_CALLCONV_C: @@ -6305,7 +6301,7 @@ static bool TryGetCalliStubCallConv( case IMAGE_CEE_CS_CALLCONV_THISCALL: case IMAGE_CEE_CS_CALLCONV_FASTCALL: *pUnmgdCallConv = (CorInfoCallConvExtension)(rawCallConv & IMAGE_CEE_CS_CALLCONV_MASK); - return true; + break; case IMAGE_CEE_CS_CALLCONV_UNMANAGED: { @@ -6328,7 +6324,7 @@ static bool TryGetCalliStubCallConv( } *pUnmgdCallConv = unmgdCallConv; - return true; + break; } case IMAGE_CEE_CS_CALLCONV_DEFAULT: @@ -6343,6 +6339,24 @@ static bool TryGetCalliStubCallConv( // Not a method signature at all. COMPlusThrowHR(COR_E_BADIMAGEFORMAT); } + + if ((rawCallConv & IMAGE_CEE_CS_CALLCONV_GENERIC) != 0) + { + // An unmanaged standalone signature is never generic. + COMPlusThrowHR(COR_E_BADIMAGEFORMAT); + } + + // An unmanaged call site carrying an instance 'this' is not expressible as a stub, and the JIT + // cannot emit it inline either. Reject it the same way a P/Invoke declared on an instance method + // is rejected - see code:CreatePInvokeStubAccessMetadata. Note that a native member function is + // expressed as a static signature whose first parameter is the 'this' pointer, so this only + // rejects signatures that no calling convention can describe. + if ((rawCallConv & (IMAGE_CEE_CS_CALLCONV_HASTHIS | IMAGE_CEE_CS_CALLCONV_EXPLICITTHIS)) != 0) + { + COMPlusThrow(kInvalidProgramException, VLDTR_E_FMD_PINVOKENOTSTATIC); + } + + return true; } MethodDesc* PInvoke::CreateCalliILStub( diff --git a/src/coreclr/vm/ilstubresolver.cpp b/src/coreclr/vm/ilstubresolver.cpp index 653c007dcf6bff..605ab3a10acb72 100644 --- a/src/coreclr/vm/ilstubresolver.cpp +++ b/src/coreclr/vm/ilstubresolver.cpp @@ -329,18 +329,25 @@ ILStubResolver::ILStubResolver() : LIMITED_METHOD_CONTRACT; } -bool ILStubResolver::IsCompiled() +bool ILStubResolver::IsCompiled() const { LIMITED_METHOD_CONTRACT; return dac_cast(m_pCompileTimeState) == ILGeneratedAndFreed; } -bool ILStubResolver::IsILGenerated() +bool ILStubResolver::IsILGenerated() const { LIMITED_METHOD_CONTRACT; return dac_cast(m_pCompileTimeState) != ILNotYetGenerated; } +// Defined here rather than in method.inl because it needs the complete ILStubResolver type. +bool DynamicMethodDesc::UsesTransientIL() const +{ + LIMITED_METHOD_DAC_CONTRACT; + return IsILStub() && !PTR_ILStubResolver(m_pResolver)->IsILGenerated(); +} + MethodDesc* ILStubResolver::GetStubMethodDesc() { LIMITED_METHOD_CONTRACT; diff --git a/src/coreclr/vm/ilstubresolver.h b/src/coreclr/vm/ilstubresolver.h index ba9a013df15b95..f4881ad3c15672 100644 --- a/src/coreclr/vm/ilstubresolver.h +++ b/src/coreclr/vm/ilstubresolver.h @@ -49,8 +49,8 @@ class ILStubResolver : public DynamicResolver // ----------------------------------- ILStubResolver(); - bool IsCompiled(); - bool IsILGenerated(); + bool IsCompiled() const; + bool IsILGenerated() const; MethodDesc* GetStubMethodDesc(); MethodDesc* GetStubTargetMethodDesc(); diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index ad2e72d0abda07..559147e08b3f64 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -10202,14 +10202,14 @@ bool CEEInfo::pInvokeMarshalingRequired(CORINFO_METHOD_HANDLE method, CORINFO_SI } /*********************************************************************/ -// Generate a cookie based on the signature that would needs to be passed -// to CORINFO_HELP_PINVOKE_CALLI +// Unused; unmanaged calli is expanded inline or converted to a call to a marshalling +// stub by convertPInvokeCalliToCall. LPVOID CEEInfo::GetCookieForPInvokeCalliSig(CORINFO_SIG_INFO* szMetaSig, void **ppIndirection) { - WRAPPER_NO_CONTRACT; + LIMITED_METHOD_CONTRACT; - return getVarArgsHandle(szMetaSig, NULL, ppIndirection); + UNREACHABLE_RET(); } // Check any constraints on method type arguments diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index ebb51525cc5dfb..65d5f272b5f9c2 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -3082,10 +3082,11 @@ class DynamicMethodDesc : public StoredSigMethodDesc bool IsILStub() const { LIMITED_METHOD_DAC_CONTRACT; return HasFlags(FlagIsILStub); } bool IsLCGMethod() const { LIMITED_METHOD_DAC_CONTRACT; return HasFlags(FlagIsLCGMethod); } - // IL stubs whose IL is generated on demand while the stub is being compiled, and discarded - // afterwards, rather than being generated eagerly when the stub is created. - // See code:MethodDesc::TryGenerateTransientILImplementation. - bool UsesTransientIL() const { LIMITED_METHOD_DAC_CONTRACT; return IsILStub() && IsPInvokeCalliStub(); } + // IL stubs whose IL is generated on demand while the stub is being compiled and discarded + // afterwards, instead of being emitted into the stub's resolver when the stub is created. The + // resolver of such a stub therefore never holds any IL, which is what everything that wants to + // read the IL out of it keys off. See code:MethodDesc::TryGenerateTransientILImplementation. + bool UsesTransientIL() const; inline PTR_DynamicResolver GetResolver(); inline PTR_LCGMethodResolver GetLCGMethodResolver(); diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 695e965ddd5d2d..46e16d1019969b 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1126,9 +1126,14 @@ bool MethodDesc::TryGenerateTransientILImplementation(DynamicResolver** resolver if (IsILStub() && AsDynamicMethodDesc()->UsesTransientIL()) { - _ASSERTE(AsDynamicMethodDesc()->IsPInvokeCalliStub()); - *methodILDecoder = PInvoke::CreateCalliStubIL(this, resolver); - return true; + if (AsDynamicMethodDesc()->IsPInvokeCalliStub()) + { + *methodILDecoder = PInvoke::CreateCalliStubIL(this, resolver); + return true; + } + + _ASSERTE(!"IL stub with no generated IL has no transient IL generator"); + return false; } if (TryGenerateAsyncThunk(resolver, methodILDecoder)) From 5ddacb4e14962ebfabf304d57725857898085095 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 30 Jul 2026 13:52:23 -0700 Subject: [PATCH 4/8] Report unmanaged calli stub failures when the stub is called The calli stub is created while its caller is jitted, so any failure detected while building it would fail the compilation of a method that may never execute the call site. Marshalling failures were already reported per parameter through StubHelpers.ThrowInteropParamException; extend that to the failures that are not tied to a parameter with a ThrowInteropException helper, so the whole set - a signature that needs marshalling, a non-blittable generic instantiation, an unsupported calling convention, and an instance 'this' the runtime cannot express - becomes a stub body that throws when it is called. Classification no longer throws for those; it records the failure and returns a usable calling convention so stub creation runs to completion, and FinishEmit discards the marshalling it produced in favour of the throw. Only a signature that is not a standalone method signature at all - one carrying the generic bit or an unknown calling convention nibble, which no stub signature can be built from - still fails eagerly with COR_E_BADIMAGEFORMAT. With the failures deferred there is no longer a reason to delay the IL, so the stub goes back to the ordinary interop path and generates its IL eagerly into the ILStubResolver the stub already owns. That removes the transient IL support for DynamicMethodDescs added earlier, along with the second, IL-less ILStubResolver those stubs carried. The trailing parameter that passes the unmanaged target is now a plain native int rather than a function pointer over the call site signature. It only needed to be a function pointer to carry the calling convention across regeneration; the IL stub cache keys on the calling convention and the stub flags separately, so call sites differing only in calling convention or SuppressGCTransition still get distinct stubs. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/StubHelpers.cs | 3 + src/coreclr/vm/corelib.h | 1 + src/coreclr/vm/dllimport.cpp | 380 ++++++++---------- src/coreclr/vm/dllimport.h | 21 +- src/coreclr/vm/ilstubresolver.cpp | 11 +- src/coreclr/vm/ilstubresolver.h | 4 +- src/coreclr/vm/jitinterface.cpp | 6 +- src/coreclr/vm/method.hpp | 6 - src/coreclr/vm/mlinfo.cpp | 8 + src/coreclr/vm/prestub.cpp | 26 +- src/coreclr/vm/qcallentrypoints.cpp | 1 + src/coreclr/vm/stubhelpers.cpp | 12 + src/coreclr/vm/stubhelpers.h | 1 + 13 files changed, 220 insertions(+), 260 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index 289e5bd0623361..deb663e5a10bd3 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -2179,6 +2179,9 @@ internal static partial class StubHelpers [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "StubHelpers_ThrowInteropParamException")] internal static safe partial void ThrowInteropParamException(int resID, int paramIdx); + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "StubHelpers_ThrowInteropException")] + internal static partial void ThrowInteropException(int exceptionKind, int resID); + internal static IntPtr AddToCleanupList(ref CleanupWorkListElement? pCleanupWorkList, SafeHandle handle) { SafeHandleCleanupWorkListElement element = new SafeHandleCleanupWorkListElement(handle); diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index c955c16b2a52da..9cd459cd52ebd5 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -1067,6 +1067,7 @@ DEFINE_METHOD(STUBHELPERS, SET_LAST_ERROR, SetLastError, DEFINE_METHOD(STUBHELPERS, CLEAR_LAST_ERROR, ClearLastError, SM_RetVoid) DEFINE_METHOD(STUBHELPERS, THROW_INTEROP_PARAM_EXCEPTION, ThrowInteropParamException, SM_Int_Int_RetVoid) +DEFINE_METHOD(STUBHELPERS, THROW_INTEROP_EXCEPTION, ThrowInteropException, SM_Int_Int_RetVoid) DEFINE_METHOD(STUBHELPERS, ADD_TO_CLEANUP_LIST_SAFEHANDLE, AddToCleanupList, SM_RefCleanupWorkListElement_SafeHandle_RetIntPtr) DEFINE_METHOD(STUBHELPERS, DESTROY_CLEANUP_LIST, DestroyCleanupList, SM_RefCleanupWorkListElement_RetVoid) DEFINE_METHOD(STUBHELPERS, GET_HR_EXCEPTION_OBJECT, GetHRExceptionObject, SM_Int_RetException) diff --git a/src/coreclr/vm/dllimport.cpp b/src/coreclr/vm/dllimport.cpp index dab76c8b742e49..a78b2134b23c9d 100644 --- a/src/coreclr/vm/dllimport.cpp +++ b/src/coreclr/vm/dllimport.cpp @@ -238,6 +238,15 @@ class ILStubState m_fSetLastError = fSetLastError; } + // Records a failure that must be reported when the stub is called instead of while it is being + // generated. Marshaling still runs, but FinishEmit discards its output and emits only the throw. + void SetInteropExceptionInfo(RuntimeExceptionKind kind, UINT resID) + { + LIMITED_METHOD_CONTRACT; + + m_slIL.SetInteropExceptionInfo(kind, resID); + } + // We use three stub linkers to generate IL stubs. The pre linker is the main one. It does all the marshaling and // then calls the target method. The post return linker is only used to unmarshal the return value after we return // from the target method. The post linker handles all the unmarshaling for by ref arguments and clean-up. It @@ -761,7 +770,14 @@ class ILStubState CORJIT_FLAGS jitFlags(CORJIT_FLAGS::CORJIT_FLAG_IL_STUB); - if (m_slIL.HasInteropParamExceptionInfo()) + if (m_slIL.HasInteropExceptionInfo()) + { + // This code will not use the secret parameter, so we do not + // tell the JIT to bother with it. + m_slIL.ClearCode(); + m_slIL.GenerateInteropException(pcsMarshal); + } + else if (m_slIL.HasInteropParamExceptionInfo()) { // This code will not use the secret parameter, so we do not // tell the JIT to bother with it. @@ -800,9 +816,7 @@ class ILStubState // If we're not in a Reverse stub, the signatures are correct, // but we need to convert the signature into a module-independent form // if our signature is not backed by metadata. - // Stubs backed by transient IL already have a permanent signature and their IL can - // be generated more than once, so they must not be rewritten here. - if (pStubMD->IsDynamicMethod() && !pStubMD->AsDynamicMethodDesc()->UsesTransientIL()) + if (pStubMD->IsDynamicMethod()) { ConvertMethodDescSigToModuleIndependentSig(pStubMD); } @@ -1871,6 +1885,8 @@ PInvokeStubLinker::PInvokeStubLinker( m_dwRetValLocalNum(-1), m_ErrorResID(-1), m_ErrorParamIdx(-1), + m_ExceptionKind(kLastException), + m_ExceptionResID(-1), m_iLCIDParamIdx(iLCIDParamIdx), m_uCalliTargetArgIdx(0), m_dwStubFlags(dwStubFlags) @@ -2073,6 +2089,39 @@ void PInvokeStubLinker::GenerateInteropParamException(ILCodeStream* pcsEmit) pcsEmit->EmitTHROW(); } +void PInvokeStubLinker::SetInteropExceptionInfo(RuntimeExceptionKind kind, UINT resID) +{ + LIMITED_METHOD_CONTRACT; + + // only keep the first one + if (HasInteropExceptionInfo()) + { + return; + } + + m_ExceptionKind = kind; + m_ExceptionResID = resID; +} + +bool PInvokeStubLinker::HasInteropExceptionInfo() +{ + LIMITED_METHOD_CONTRACT; + + return m_ExceptionResID != (UINT)-1; +} + +void PInvokeStubLinker::GenerateInteropException(ILCodeStream* pcsEmit) +{ + STANDARD_VM_CONTRACT; + + pcsEmit->EmitLDC(static_cast(m_ExceptionKind)); + pcsEmit->EmitLDC(m_ExceptionResID); + pcsEmit->EmitCALL(METHOD__STUBHELPERS__THROW_INTEROP_EXCEPTION, 2, 0); + + pcsEmit->EmitLDNULL(); + pcsEmit->EmitTHROW(); +} + #ifdef FEATURE_COMINTEROP DWORD PInvokeStubLinker::GetTargetInterfacePointerLocalNum() { @@ -4571,7 +4620,12 @@ static void CreatePInvokeStubAccessMetadata( unmgdCallConv == CorInfoCallConvExtension::Fastcall || unmgdCallConv == CorInfoCallConvExtension::FastcallMemberFunction) { - COMPlusThrow(kTypeLoadException, IDS_INVALID_PINVOKE_CALLCONV); + // For an unmanaged CALLI stub the caller has already recorded this so that it is + // reported when the stub is called - see code:TryGetCalliStubCallConv. + if (!SF_IsCALLIStub(*pdwStubFlags)) + { + COMPlusThrow(kTypeLoadException, IDS_INVALID_PINVOKE_CALLCONV); + } } } @@ -5414,7 +5468,9 @@ namespace if (SF_IsCALLIStub(dwStubFlags) && PInvoke::MarshalingRequired(NULL, pStubMD->GetSigPointer(), pSigDesc->m_pModule, &pSigDesc->m_typeContext)) { - COMPlusThrow(kMarshalDirectiveException, IDS_EE_BADMARSHAL_GENERICS_RESTRICTION); + // The stub is created while the calli's caller is jitted, so report + // this when the stub is called instead of failing that compilation. + pss->SetInteropExceptionInfo(kMarshalDirectiveException, IDS_EE_BADMARSHAL_GENERICS_RESTRICTION); } // We don't want to support generic varargs, so block it else if (SF_IsVarArgStub(dwStubFlags)) @@ -5507,86 +5563,6 @@ namespace return pStubMD; } - // Creates (or finds in the IL stub cache) the MethodDesc of an interop IL stub without - // generating its IL. The IL is produced later, on demand, as transient IL while the stub is - // being compiled - see code:MethodDesc::TryGenerateTransientILImplementation. - MethodDesc* CreateInteropILStubMethodDesc( - StubSigDesc* pSigDesc, - CorNativeLinkType nlType, - CorNativeLinkFlags nlFlags, - CorInfoCallConvExtension unmgdCallConv, - DWORD dwStubFlags - ) - { - CONTRACTL - { - STANDARD_VM_CHECK; - - PRECONDITION(CheckPointer(pSigDesc)); - PRECONDITION(pSigDesc->m_pMD == NULL); - PRECONDITION(SF_IsCALLIStub(dwStubFlags)); - } - CONTRACTL_END; - - if (IsSharedStubScenario(dwStubFlags)) - dwStubFlags |= PINVOKESTUB_FL_SHARED_STUB; - - // The stub signature has no metadata behind it, so there are no parameter tokens. - int numArgs; - { - MetaSig msig(pSigDesc->m_sig, pSigDesc->m_pModule, &pSigDesc->m_typeContext); - numArgs = msig.NumFixedArgs(); - } - - int numParamTokens = numArgs + 1; - mdParamDef* pParamTokenArray = (mdParamDef*)_alloca(numParamTokens * sizeof(mdParamDef)); - memset(pParamTokenArray, 0, numParamTokens * sizeof(mdParamDef)); - - PInvokeStubParameters params(pSigDesc->m_sig, - &pSigDesc->m_typeContext, - pSigDesc->m_pModule, - pSigDesc->m_pLoaderModule, - nlType, - nlFlags, - unmgdCallConv, - dwStubFlags, - numParamTokens, - pParamTokenArray, - -1 /* iLCIDArg */, - pSigDesc->m_pMT - ); - - MethodDesc* pStubMD; - { - ILStubCreatorHelper ilStubCreatorHelper(NULL, ¶ms); - - // take the domain level lock so that the cache lookup and the chunk linking below - // are done consistently with code:CreateInteropILStub - ListLockHolder pILStubLock(AppDomain::GetCurrentDomain()->GetILStubGenLock()); - - ilStubCreatorHelper.GetStubMethodDesc(); - pStubMD = ilStubCreatorHelper.GetStubMD(); - - if (!pSigDesc->m_typeContext.IsEmpty()) - { - // For generic calli, we only support blittable types. This has to be checked - // against the stub signature because that is the instantiated one. - if (PInvoke::MarshalingRequired(NULL, pStubMD->GetSigPointer(), pSigDesc->m_pModule, &pSigDesc->m_typeContext)) - COMPlusThrow(kMarshalDirectiveException, IDS_EE_BADMARSHAL_GENERICS_RESTRICTION); - } - - // The stub's own resolver is what the runtime uses to identify the method behind the - // stub's dynamic scope. Normally IL generation establishes that link; with transient - // IL there is no eager generation, so do it here. - pStubMD->AsDynamicMethodDesc()->GetILStubResolver()->SetStubMethodDesc(pStubMD); - - AddMethodDescChunkWithLockTaken(¶ms, pStubMD); - - ilStubCreatorHelper.SuppressRelease(); - } - - return pStubMD; - } } MethodDesc* PInvoke::CreateCLRToNativeILStub( @@ -6205,15 +6181,19 @@ static void GetILStubForVarargPInvoke(VASigCookie* pVASigCookie, MethodDesc* pMD // Build the managed signature of the IL stub that implements an unmanaged CALLI call site. // // The stub signature is the call site signature with a managed (default) calling convention -// and an extra trailing parameter that carries the unmanaged target. The target is the value at -// the top of the evaluation stack at the calli site, so appending it as the last parameter lets -// the JIT rewrite the calli into a direct call to the stub. +// and an extra trailing native int parameter that carries the unmanaged target. The target is +// the value at the top of the evaluation stack at the calli site, so appending it as the last +// parameter lets the JIT rewrite the calli into a direct call to the stub. +// +// The calling convention of the call site is not part of this signature. It does not need to be: +// the IL stub cache keys on it separately through PInvokeStubHashBlob::m_unmgdCallConv and +// PInvokeStubHashBlob::m_StubFlags, so call sites that differ only in calling convention or in +// SuppressGCTransition still get distinct stubs. // -// That parameter is typed as the function pointer type of the call site rather than as a plain -// native int. It is still just a pointer as far as the calling convention and the JIT are -// concerned, but it preserves the unmanaged calling convention of the call site (including -// modopts such as CallConvSuppressGCTransition), which is what lets the stub regenerate its IL -// on demand - see code:PInvoke::CreateCalliStubIL. +// A call site with an instance 'this' cannot be described by any unmanaged calling convention and +// is rejected, but the rejection is reported when the stub is called. That requires a stub the JIT +// can call, so the 'this' becomes an explicit leading native int parameter and the stub pops the +// call site correctly. static void BuildCalliILStubSignature( const Signature& calliSignature, SigBuilder* pSigBuilder) @@ -6221,72 +6201,81 @@ static void BuildCalliILStubSignature( STANDARD_VM_CONTRACT; SigParser sigParser = calliSignature.CreateSigParser(); - PCCOR_SIGNATURE pCalliSigStart = sigParser.GetPtr(); uint32_t callConv; IfFailThrow(sigParser.GetCallingConvInfo(&callConv)); - // Only signatures accepted by CreateCalliILStub get here. - _ASSERTE((callConv & (IMAGE_CEE_CS_CALLCONV_GENERIC | IMAGE_CEE_CS_CALLCONV_HASTHIS | IMAGE_CEE_CS_CALLCONV_EXPLICITTHIS)) == 0); + // Only signatures that describe a standalone method signature get here. + _ASSERTE((callConv & IMAGE_CEE_CS_CALLCONV_GENERIC) == 0); + + bool hasThis = (callConv & (IMAGE_CEE_CS_CALLCONV_HASTHIS | IMAGE_CEE_CS_CALLCONV_EXPLICITTHIS)) != 0; uint32_t numArgs; IfFailThrow(sigParser.GetData(&numArgs)); pSigBuilder->AppendByte(IMAGE_CEE_CS_CALLCONV_DEFAULT); - pSigBuilder->AppendData(numArgs + 1); + pSigBuilder->AppendData(numArgs + (hasThis ? 2 : 1)); + + // The return type, copied verbatim. + PCCOR_SIGNATURE pRetTypeStart = sigParser.GetPtr(); + IfFailThrow(sigParser.SkipExactlyOne()); + PCCOR_SIGNATURE pRetTypeEnd = sigParser.GetPtr(); + pSigBuilder->AppendBlob((PVOID)pRetTypeStart, (DWORD)(pRetTypeEnd - pRetTypeStart)); - PCCOR_SIGNATURE pTypesStart = sigParser.GetPtr(); - for (uint32_t i = 0; i <= numArgs; i++) + // The instance pointer, if the call site has one, ahead of the declared parameters. + if (hasThis) { - IfFailThrow(sigParser.SkipExactlyOne()); + pSigBuilder->AppendElementType(ELEMENT_TYPE_I); } - PCCOR_SIGNATURE pCalliSigEnd = sigParser.GetPtr(); - // The return type and all of the original parameters, copied verbatim. - pSigBuilder->AppendBlob((PVOID)pTypesStart, (DWORD)(pCalliSigEnd - pTypesStart)); + // The declared parameters, copied verbatim. + PCCOR_SIGNATURE pArgsStart = sigParser.GetPtr(); + for (uint32_t i = 0; i < numArgs; i++) + { + IfFailThrow(sigParser.SkipExactlyOne()); + } + PCCOR_SIGNATURE pArgsEnd = sigParser.GetPtr(); + pSigBuilder->AppendBlob((PVOID)pArgsStart, (DWORD)(pArgsEnd - pArgsStart)); // The unmanaged target is the last parameter. - pSigBuilder->AppendElementType(ELEMENT_TYPE_FNPTR); - pSigBuilder->AppendBlob((PVOID)pCalliSigStart, (DWORD)(pCalliSigEnd - pCalliSigStart)); + pSigBuilder->AppendElementType(ELEMENT_TYPE_I); } -// Recovers the call site signature that BuildCalliILStubSignature embedded in the trailing -// parameter of an unmanaged CALLI stub signature. -static Signature GetCalliSignatureFromStubSignature(const Signature& stubSignature) +// A failure detected while classifying an unmanaged CALLI call site. It is reported when the stub +// is called rather than while the caller is being jitted, so it is carried alongside a usable +// calling convention that lets stub creation run to completion. +struct CalliStubDeferredError { - STANDARD_VM_CONTRACT; + RuntimeExceptionKind Kind = kLastException; + UINT ResID = 0; - SigParser sigParser = stubSignature.CreateSigParser(); + bool IsSet() const { LIMITED_METHOD_CONTRACT; return Kind != kLastException; } - uint32_t callConv; - IfFailThrow(sigParser.GetCallingConvInfo(&callConv)); - - uint32_t numArgs; - IfFailThrow(sigParser.GetData(&numArgs)); - _ASSERTE(numArgs > 0); - - // Skip the return type and every parameter but the last one. - for (uint32_t i = 0; i < numArgs; i++) + void Set(RuntimeExceptionKind kind, UINT resID) { - IfFailThrow(sigParser.SkipExactlyOne()); + LIMITED_METHOD_CONTRACT; + // Only keep the first one. + if (!IsSet()) + { + Kind = kind; + ResID = resID; + } } - - PCCOR_SIGNATURE pFnPtr = sigParser.GetPtr(); - IfFailThrow(sigParser.SkipExactlyOne()); - PCCOR_SIGNATURE pEnd = sigParser.GetPtr(); - - _ASSERTE(*pFnPtr == ELEMENT_TYPE_FNPTR); - return Signature(pFnPtr + 1, (DWORD)(pEnd - pFnPtr - 1)); -} +}; // Determines the unmanaged calling convention of an unmanaged CALLI call site and the -// corresponding stub flags. Returns false if the signature does not describe a P/Invoke, and -// throws if it describes one the runtime cannot express. +// corresponding stub flags. Returns false if the signature does not describe a P/Invoke. +// +// A signature that describes a P/Invoke the runtime cannot express records the failure in +// *pError and still returns a usable calling convention, so that a stub can be created to report +// it when it is called. Only a signature that is not a standalone method signature at all - one +// the stub signature cannot even be built from - throws. static bool TryGetCalliStubCallConv( Module* pModule, const Signature& calliSignature, CorInfoCallConvExtension* pUnmgdCallConv, - DWORD* pdwStubFlags) + DWORD* pdwStubFlags, + CalliStubDeferredError* pError) { STANDARD_VM_CONTRACT; @@ -6309,18 +6298,25 @@ static bool TryGetCalliStubCallConv( CallConvBuilder builder; UINT errorResID; HRESULT hr = CallConv::TryGetUnmanagedCallingConventionFromModOpt(GetScopeHandle(pModule), calliSignature.GetRawSig(), calliSignature.GetRawSigLen(), &builder, &errorResID); - if (FAILED(hr)) - COMPlusThrowHR(hr, errorResID); - CorInfoCallConvExtension unmgdCallConv = builder.GetCurrentCallConv(); - if (unmgdCallConv == CallConvBuilder::UnsetValue) + CorInfoCallConvExtension unmgdCallConv; + if (FAILED(hr)) { + pError->Set(kTypeLoadException, errorResID); unmgdCallConv = CallConv::GetDefaultUnmanagedCallingConvention(); } - - if (builder.IsCurrentCallConvModSet(CallConvBuilder::CALL_CONV_MOD_SUPPRESSGCTRANSITION)) + else { - *pdwStubFlags |= PINVOKESTUB_FL_SUPPRESSGCTRANSITION; + unmgdCallConv = builder.GetCurrentCallConv(); + if (unmgdCallConv == CallConvBuilder::UnsetValue) + { + unmgdCallConv = CallConv::GetDefaultUnmanagedCallingConvention(); + } + + if (builder.IsCurrentCallConvModSet(CallConvBuilder::CALL_CONV_MOD_SUPPRESSGCTRANSITION)) + { + *pdwStubFlags |= PINVOKESTUB_FL_SUPPRESSGCTRANSITION; + } } *pUnmgdCallConv = unmgdCallConv; @@ -6336,13 +6332,14 @@ static bool TryGetCalliStubCallConv( return false; default: - // Not a method signature at all. + // Not a method signature at all, so no stub signature can be built from it. COMPlusThrowHR(COR_E_BADIMAGEFORMAT); } if ((rawCallConv & IMAGE_CEE_CS_CALLCONV_GENERIC) != 0) { - // An unmanaged standalone signature is never generic. + // An unmanaged standalone signature is never generic, and its argument count is not where + // the stub signature builder expects it, so no stub can be built from it either. COMPlusThrowHR(COR_E_BADIMAGEFORMAT); } @@ -6353,7 +6350,15 @@ static bool TryGetCalliStubCallConv( // rejects signatures that no calling convention can describe. if ((rawCallConv & (IMAGE_CEE_CS_CALLCONV_HASTHIS | IMAGE_CEE_CS_CALLCONV_EXPLICITTHIS)) != 0) { - COMPlusThrow(kInvalidProgramException, VLDTR_E_FMD_PINVOKENOTSTATIC); + pError->Set(kInvalidProgramException, VLDTR_E_FMD_PINVOKENOTSTATIC); + } + + // The JIT refuses to emit these inline, and stub creation cannot express them either. + if (*pUnmgdCallConv == CorInfoCallConvExtension::Managed || + *pUnmgdCallConv == CorInfoCallConvExtension::Fastcall || + *pUnmgdCallConv == CorInfoCallConvExtension::FastcallMemberFunction) + { + pError->Set(kTypeLoadException, IDS_INVALID_PINVOKE_CALLCONV); } return true; @@ -6375,7 +6380,8 @@ MethodDesc* PInvoke::CreateCalliILStub( DWORD dwStubFlags = PINVOKESTUB_FL_BESTFIT | PINVOKESTUB_FL_UNMANAGED_CALLI; CorInfoCallConvExtension unmgdCallConv; - if (!TryGetCalliStubCallConv(pModule, calliSignature, &unmgdCallConv, &dwStubFlags)) + CalliStubDeferredError deferredError; + if (!TryGetCalliStubCallConv(pModule, calliSignature, &unmgdCallConv, &dwStubFlags, &deferredError)) return NULL; // The generic context is stripped if the signature does not actually use it. That is @@ -6384,18 +6390,11 @@ MethodDesc* PInvoke::CreateCalliILStub( SigTypeContext typeContext = *pTypeContext; Module* pLoaderModule = pModule->GetLoaderModuleForSignature(calliSignature, &typeContext); - bool marshalingRequired = !!PInvoke::MarshalingRequired(NULL, calliSignature.CreateSigPointer(), pModule, &typeContext); - - // The JIT cannot emit these calling conventions inline (see code:Compiler::impCheckForPInvokeCall), - // so the call site has to go through a stub even when no marshaling is needed. Stub creation - // rejects them with a proper exception - see code:CreatePInvokeStubAccessMetadata. - bool jitCanInlineCallConv = unmgdCallConv != CorInfoCallConvExtension::Managed - && unmgdCallConv != CorInfoCallConvExtension::Fastcall - && unmgdCallConv != CorInfoCallConvExtension::FastcallMemberFunction; - - // If no marshaling is needed, let the caller emit the unmanaged call inline unless it - // explicitly asked for a stub. - if (!fMustCreate && jitCanInlineCallConv && !marshalingRequired) + // If no marshaling is needed and nothing is wrong with the call site, let the caller emit the + // unmanaged call inline unless it explicitly asked for a stub. + if (!fMustCreate + && !deferredError.IsSet() + && !PInvoke::MarshalingRequired(NULL, calliSignature.CreateSigPointer(), pModule, &typeContext)) { return NULL; } @@ -6414,54 +6413,6 @@ MethodDesc* PInvoke::CreateCalliILStub( StubSigDesc sigDesc(NULL, Signature((PCCOR_SIGNATURE)(BYTE*)pStubSigCopy, cbStubSig), pModule, pLoaderModule); sigDesc.InitTypeContext(typeContext.m_classInst, typeContext.m_methodInst); - // Only the MethodDesc is created here. The stub IL is generated lazily when the stub itself - // is compiled - see code:PInvoke::CreateCalliStubIL - so that jitting a method containing an - // unmanaged calli does not have to do the marshaling analysis for it. - MethodDesc* pStubMD = CreateInteropILStubMethodDesc( - &sigDesc, - nltAnsi, - nlfNone, - unmgdCallConv, - dwStubFlags); - - PCCOR_SIGNATURE pFinalSig; - DWORD cbFinalSig; - pStubMD->GetSig(&pFinalSig, &cbFinalSig); - if (pFinalSig == (PCCOR_SIGNATURE)(BYTE*)pStubSigCopy) - pStubSigCopy.SuppressRelease(); - - return pStubMD; -} - -// Generates the IL of an unmanaged CALLI marshalling stub. This is transient IL: it is produced -// on demand while the stub is being compiled and discarded afterwards, so everything it needs is -// recovered from the stub's own signature (see code:BuildCalliILStubSignature). -COR_ILMETHOD_DECODER* PInvoke::CreateCalliStubIL(MethodDesc* pStubMD, DynamicResolver** ppResolver) -{ - CONTRACTL - { - STANDARD_VM_CHECK; - PRECONDITION(CheckPointer(pStubMD)); - PRECONDITION(pStubMD->IsILStub()); - PRECONDITION(pStubMD->AsDynamicMethodDesc()->IsPInvokeCalliStub()); - PRECONDITION(CheckPointer(ppResolver)); - } - CONTRACTL_END; - - // The stub signature is fully instantiated, so no type context is needed to interpret it. - Module* pModule = pStubMD->GetModule(); - Signature stubSignature = pStubMD->GetSignature(); - - DWORD dwStubFlags = PINVOKESTUB_FL_BESTFIT | PINVOKESTUB_FL_UNMANAGED_CALLI; - CorInfoCallConvExtension unmgdCallConv; - if (!TryGetCalliStubCallConv(pModule, GetCalliSignatureFromStubSignature(stubSignature), &unmgdCallConv, &dwStubFlags)) - { - // The stub would not have been created for such a signature. - UNREACHABLE_MSG("Unmanaged CALLI stub with a managed call site signature"); - } - - StubSigDesc sigDesc(NULL, stubSignature, pModule, pStubMD->GetLoaderModule()); - int iLCIDArg = 0; int numArgs = 0; CreatePInvokeStubAccessMetadata(&sigDesc, unmgdCallConv, &dwStubFlags, &iLCIDArg, &numArgs); @@ -6472,23 +6423,30 @@ COR_ILMETHOD_DECODER* PInvoke::CreateCalliStubIL(MethodDesc* pStubMD, DynamicRes PInvoke_ILStubState stubState(pModule, sigDesc.m_sig, &sigDesc.m_typeContext, dwStubFlags, unmgdCallConv, iLCIDArg, NULL); - NewHolder pResolver = new ILStubResolver(); - pResolver->SetStubMethodDesc(pStubMD); + if (deferredError.IsSet()) + { + // The call site cannot be marshaled. Generate a stub whose body throws so that the failure + // is reported when the call site executes rather than while its caller is jitted. + stubState.SetInteropExceptionInfo(deferredError.Kind, deferredError.ResID); + } - COR_ILMETHOD_DECODER* pIL = CreatePInvokeStubWorker( + MethodDesc* pStubMD = CreateInteropILStub( &stubState, - pResolver, &sigDesc, nltAnsi, nlfNone, unmgdCallConv, - stubState.GetFlags(), - pStubMD, + numParamTokens, pParamTokenArray, iLCIDArg); - *ppResolver = pResolver.Extract(); - return pIL; + PCCOR_SIGNATURE pFinalSig; + DWORD cbFinalSig; + pStubMD->GetSig(&pFinalSig, &cbFinalSig); + if (pFinalSig == (PCCOR_SIGNATURE)(BYTE*)pStubSigCopy) + pStubSigCopy.SuppressRelease(); + + return pStubMD; } EXTERN_C void STDCALL VarargPInvokeStubWorker(TransitionBlock* pTransitionBlock, VASigCookie *pVASigCookie, MethodDesc *pMD) diff --git a/src/coreclr/vm/dllimport.h b/src/coreclr/vm/dllimport.h index 41357b04231197..3c5b42b2ce92dd 100644 --- a/src/coreclr/vm/dllimport.h +++ b/src/coreclr/vm/dllimport.h @@ -131,18 +131,12 @@ class PInvoke // calliSignature. Returns NULL if the call site does not describe an unmanaged call, or if // no marshaling is required and fMustCreate is false (in which case the caller - the JIT - // can emit the unmanaged call inline instead). - // Only the MethodDesc is created; the stub IL is generated on demand by CreateCalliStubIL. static MethodDesc* CreateCalliILStub( Module* pModule, const Signature& calliSignature, const SigTypeContext* pTypeContext, bool fMustCreate); - // Generates the transient IL of a stub created by CreateCalliILStub. - static COR_ILMETHOD_DECODER* CreateCalliStubIL( - MethodDesc* pStubMD, - DynamicResolver** ppResolver); - static COR_ILMETHOD_DECODER* CreatePInvokeMethodIL( PInvokeMethodDesc* pMD, DynamicResolver** ppResolver); @@ -510,6 +504,19 @@ class PInvokeStubLinker : public ILStubLinker void SetInteropParamExceptionInfo(UINT resID, UINT paramIdx); bool HasInteropParamExceptionInfo(); + + // Records an interop failure that is not tied to a single parameter and that must be reported + // when the stub is called rather than while it is being generated. The stub body becomes a + // single throw - see code:PInvokeStubLinker::GenerateInteropException. + void SetInteropExceptionInfo(RuntimeExceptionKind kind, UINT resID); + bool HasInteropExceptionInfo(); + void GenerateInteropException(ILCodeStream* pcsEmit); + + DWORD GetStubFlags() const + { + LIMITED_METHOD_CONTRACT; + return m_dwStubFlags; + } bool TargetHasThis() { return m_targetHasThis == TRUE; @@ -576,6 +583,8 @@ class PInvokeStubLinker : public ILStubLinker UINT m_ErrorResID; UINT m_ErrorParamIdx; + RuntimeExceptionKind m_ExceptionKind; + UINT m_ExceptionResID; int m_iLCIDParamIdx; UINT m_uCalliTargetArgIdx; diff --git a/src/coreclr/vm/ilstubresolver.cpp b/src/coreclr/vm/ilstubresolver.cpp index 605ab3a10acb72..653c007dcf6bff 100644 --- a/src/coreclr/vm/ilstubresolver.cpp +++ b/src/coreclr/vm/ilstubresolver.cpp @@ -329,25 +329,18 @@ ILStubResolver::ILStubResolver() : LIMITED_METHOD_CONTRACT; } -bool ILStubResolver::IsCompiled() const +bool ILStubResolver::IsCompiled() { LIMITED_METHOD_CONTRACT; return dac_cast(m_pCompileTimeState) == ILGeneratedAndFreed; } -bool ILStubResolver::IsILGenerated() const +bool ILStubResolver::IsILGenerated() { LIMITED_METHOD_CONTRACT; return dac_cast(m_pCompileTimeState) != ILNotYetGenerated; } -// Defined here rather than in method.inl because it needs the complete ILStubResolver type. -bool DynamicMethodDesc::UsesTransientIL() const -{ - LIMITED_METHOD_DAC_CONTRACT; - return IsILStub() && !PTR_ILStubResolver(m_pResolver)->IsILGenerated(); -} - MethodDesc* ILStubResolver::GetStubMethodDesc() { LIMITED_METHOD_CONTRACT; diff --git a/src/coreclr/vm/ilstubresolver.h b/src/coreclr/vm/ilstubresolver.h index f4881ad3c15672..ba9a013df15b95 100644 --- a/src/coreclr/vm/ilstubresolver.h +++ b/src/coreclr/vm/ilstubresolver.h @@ -49,8 +49,8 @@ class ILStubResolver : public DynamicResolver // ----------------------------------- ILStubResolver(); - bool IsCompiled() const; - bool IsILGenerated() const; + bool IsCompiled(); + bool IsILGenerated(); MethodDesc* GetStubMethodDesc(); MethodDesc* GetStubTargetMethodDesc(); diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 559147e08b3f64..3f1db0663fccb3 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -7679,7 +7679,7 @@ COR_ILMETHOD_DECODER* CEEInfo::getMethodInfoWorker( localSig = SigPointer{ cxt.Header->LocalVarSig, cxt.Header->cbLocalVarSig }; } } - else if (ilFtn->IsDynamicMethod() && !ilFtn->AsDynamicMethodDesc()->UsesTransientIL()) + else if (ilFtn->IsDynamicMethod()) { DynamicResolver* pResolver = ilFtn->AsDynamicMethodDesc()->GetResolver(); scopeHnd = MakeDynamicScope(pResolver); @@ -13384,7 +13384,7 @@ void CEECodeGenInfo::getEHinfo( pMD = pMD->GetOrdinaryVariantIfAsyncVersion(); COR_ILMETHOD* pILHeader; - if (pMD->IsDynamicMethod() && !pMD->AsDynamicMethodDesc()->UsesTransientIL()) + if (pMD->IsDynamicMethod()) { pMD->AsDynamicMethodDesc()->GetResolver()->GetEHInfo(EHnumber, clause); } @@ -13397,7 +13397,7 @@ void CEECodeGenInfo::getEHinfo( COR_ILMETHOD_DECODER header(pILHeader, pMD->GetMDImport(), NULL); getEHinfoHelper(EHnumber, clause, &header); } - else if (pMD->IsIL() || (pMD->IsILStub() && pMD->AsDynamicMethodDesc()->UsesTransientIL())) + else if (pMD->IsIL()) { TransientMethodDetails* details; if (!FindTransientMethodDetails(pMD, &details)) diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 65d5f272b5f9c2..fd6ddd35ca4c39 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -3082,12 +3082,6 @@ class DynamicMethodDesc : public StoredSigMethodDesc bool IsILStub() const { LIMITED_METHOD_DAC_CONTRACT; return HasFlags(FlagIsILStub); } bool IsLCGMethod() const { LIMITED_METHOD_DAC_CONTRACT; return HasFlags(FlagIsLCGMethod); } - // IL stubs whose IL is generated on demand while the stub is being compiled and discarded - // afterwards, instead of being emitted into the stub's resolver when the stub is created. The - // resolver of such a stub therefore never holds any IL, which is what everything that wants to - // read the IL out of it keys off. See code:MethodDesc::TryGenerateTransientILImplementation. - bool UsesTransientIL() const; - inline PTR_DynamicResolver GetResolver(); inline PTR_LCGMethodResolver GetLCGMethodResolver(); inline PTR_ILStubResolver GetILStubResolver(); diff --git a/src/coreclr/vm/mlinfo.cpp b/src/coreclr/vm/mlinfo.cpp index b89c18524f1efc..80132ad83c1497 100644 --- a/src/coreclr/vm/mlinfo.cpp +++ b/src/coreclr/vm/mlinfo.cpp @@ -2084,6 +2084,14 @@ VOID MarshalInfo::EmitOrThrowInteropParamException(PInvokeStubLinker* psl, BOOL } #endif // FEATURE_COMINTEROP + // An unmanaged CALLI stub is created while the calli's caller is being jitted, so its failures + // have to be reported when the stub is called rather than failing that compilation. + if (SF_IsCALLIStub(psl->GetStubFlags())) + { + psl->SetInteropParamExceptionInfo(resID, paramIdx); + return; + } + ThrowInteropParamException(resID, paramIdx); } diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 46e16d1019969b..cb032e1536029d 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -749,7 +749,7 @@ namespace { return GetAndVerifyMetadataILHeader(pMD, pConfig, pIlDecoderMemory); } - else if (pMD->IsILStub() && !pMD->AsDynamicMethodDesc()->UsesTransientIL()) + else if (pMD->IsILStub()) { ILStubResolver* pResolver = pMD->AsDynamicMethodDesc()->GetILStubResolver(); return pResolver->GetILHeader(); @@ -795,17 +795,9 @@ PCODE MethodDesc::JitCompileCodeLockedEventWrapper(PrepareCodeConfig* pConfig, J } else { - unsigned int ilSize = 0; - unsigned int unused; + unsigned int ilSize, unused; CorInfoOptions corOptions; - LPCBYTE ilHeaderPointer = NULL; - - // Stubs backed by transient IL have no IL yet - it is generated as part of the - // compilation that is just starting. - if (!AsDynamicMethodDesc()->UsesTransientIL()) - { - ilHeaderPointer = AsDynamicMethodDesc()->GetResolver()->GetCodeInfo(&ilSize, &unused, &corOptions, &unused); - } + LPCBYTE ilHeaderPointer = this->AsDynamicMethodDesc()->GetResolver()->GetCodeInfo(&ilSize, &unused, &corOptions, &unused); (&g_profControlBlock)->DynamicMethodJITCompilationStarted((FunctionID)this, TRUE, ilHeaderPointer, ilSize); } @@ -1124,18 +1116,6 @@ bool MethodDesc::TryGenerateTransientILImplementation(DynamicResolver** resolver return true; } - if (IsILStub() && AsDynamicMethodDesc()->UsesTransientIL()) - { - if (AsDynamicMethodDesc()->IsPInvokeCalliStub()) - { - *methodILDecoder = PInvoke::CreateCalliStubIL(this, resolver); - return true; - } - - _ASSERTE(!"IL stub with no generated IL has no transient IL generator"); - return false; - } - if (TryGenerateAsyncThunk(resolver, methodILDecoder)) { return true; diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 41e7624c50466f..6f594713732591 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -500,6 +500,7 @@ static const Entry s_QCall[] = DllImportEntry(StubHelpers_CreateCustomMarshaler) DllImportEntry(StubHelpers_CreateLayoutClassMarshalStubs) DllImportEntry(StubHelpers_ThrowInteropParamException) + DllImportEntry(StubHelpers_ThrowInteropException) DllImportEntry(StubHelpers_MarshalToManagedVaList) DllImportEntry(StubHelpers_MarshalToUnmanagedVaList) DllImportEntry(StubHelpers_ValidateObject) diff --git a/src/coreclr/vm/stubhelpers.cpp b/src/coreclr/vm/stubhelpers.cpp index f122b797fad4fa..4c76dd47dea8b7 100644 --- a/src/coreclr/vm/stubhelpers.cpp +++ b/src/coreclr/vm/stubhelpers.cpp @@ -502,6 +502,18 @@ extern "C" void QCALLTYPE StubHelpers_ThrowInteropParamException(INT resID, INT END_QCALL; } +// Throws an interop failure that was detected while the stub was being generated. Stubs that are +// created while their caller is being jitted report their failures this way so that a call site +// that is never executed does not fail the compilation of the method containing it. +extern "C" void QCALLTYPE StubHelpers_ThrowInteropException(INT exceptionKind, INT resID) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + COMPlusThrow(static_cast(exceptionKind), static_cast(resID)); + END_QCALL; +} + #ifdef PROFILING_SUPPORTED extern "C" void* QCALLTYPE StubHelpers_ProfilerBeginTransitionCallback(MethodDesc* pTargetMD) { diff --git a/src/coreclr/vm/stubhelpers.h b/src/coreclr/vm/stubhelpers.h index 33d791390844a7..02627224198c2c 100644 --- a/src/coreclr/vm/stubhelpers.h +++ b/src/coreclr/vm/stubhelpers.h @@ -57,6 +57,7 @@ extern "C" void QCALLTYPE InterfaceMarshaler_ValidateComVisibilityForIUnknown(IU #endif extern "C" void QCALLTYPE StubHelpers_ThrowInteropParamException(INT resID, INT paramIdx); +extern "C" void QCALLTYPE StubHelpers_ThrowInteropException(INT exceptionKind, INT resID); extern "C" void QCALLTYPE StubHelpers_MarshalToManagedVaList(va_list va, VARARGS* pArgIterator); extern "C" void QCALLTYPE StubHelpers_MarshalToUnmanagedVaList(va_list va, DWORD cbVaListSize, const VARARGS* pArgIterator); From 405fdff4acc36bfd4b5c0cd30ef923e4e7c68a2f Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 30 Jul 2026 15:59:24 -0700 Subject: [PATCH 5/8] Remove the unused PInvoke calli cookie from the JIT-EE interface Nothing calls GetCookieForPInvokeCalliSig or CORINFO_HELP_PINVOKE_CALLI now that an unmanaged calli is either expanded inline or converted to a call to a marshalling stub, so drop both and bump the JIT-EE version GUID. The generated files were regenerated with ThunkGenerator/gen.bat. CORINFO_HELP_PINVOKE_CALLI is not a ReadyToRun helper, so removing it does not version the ReadyToRun format - R2R images encode READYTORUN_HELPER_* ids, which are unaffected. Also removes the SuperPMI recording for the cookie. Its two historical packet ids are left in place, matching Packet_CanGetCookieForPInvokeCalliSig which has been unused for some time. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/inc/corinfo.h | 9 ---- src/coreclr/inc/icorjitinfoimpl_generated.h | 4 -- src/coreclr/inc/jiteeversionguid.h | 10 ++-- src/coreclr/inc/jithelpers.h | 4 -- src/coreclr/jit/ICorJitInfo_names_generated.h | 1 - .../jit/ICorJitInfo_wrapper_generated.hpp | 10 ---- .../Common/JitInterface/CorInfoHelpFunc.cs | 1 - .../tools/Common/JitInterface/CorInfoImpl.cs | 8 ---- .../JitInterface/CorInfoImpl_generated.cs | 17 ------- .../ThunkGenerator/ThunkInput.txt | 1 - .../aot/jitinterface/jitinterface_generated.h | 11 ----- .../tools/superpmi/superpmi-shared/agnostic.h | 8 ---- .../tools/superpmi/superpmi-shared/lwmlist.h | 1 - .../superpmi-shared/methodcontext.cpp | 46 ------------------- .../superpmi/superpmi-shared/methodcontext.h | 4 -- .../superpmi-shim-collector/icorjitinfo.cpp | 9 ---- .../icorjitinfo_generated.cpp | 8 ---- .../icorjitinfo_generated.cpp | 7 --- .../tools/superpmi/superpmi/icorjitinfo.cpp | 7 --- src/coreclr/vm/jitinterface.cpp | 11 ----- 20 files changed, 5 insertions(+), 172 deletions(-) diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index ab37c25996e65c..2a1a3c6fdb832a 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -481,8 +481,6 @@ enum CorInfoHelpFunc /* Miscellaneous */ - CORINFO_HELP_PINVOKE_CALLI, // Unused; unmanaged calli is expanded inline or converted to a call - // to a marshalling stub by convertPInvokeCalliToCall CORINFO_HELP_TAILCALL, // Perform a tail call CORINFO_HELP_GETCURRENTMANAGEDTHREADID, @@ -3385,13 +3383,6 @@ class ICorDynamicInfo : public ICorStaticInfo CORINFO_CONST_LOOKUP * pLookup ) = 0; - // Unused; unmanaged calli is expanded inline or converted to a call to a marshalling stub - // by convertPInvokeCalliToCall. - virtual void* GetCookieForPInvokeCalliSig( - CORINFO_SIG_INFO* szMetaSig, - void** ppIndirection = NULL - ) = 0; - // Generate a cookie based on the signature to pass to INTOP_CALLI in the interpreter. virtual void* GetCookieForInterpreterCalliSig( CORINFO_SIG_INFO* szMetaSig) = 0; diff --git a/src/coreclr/inc/icorjitinfoimpl_generated.h b/src/coreclr/inc/icorjitinfoimpl_generated.h index 5a7fb30fe7e14d..7f99b8b87ad0d7 100644 --- a/src/coreclr/inc/icorjitinfoimpl_generated.h +++ b/src/coreclr/inc/icorjitinfoimpl_generated.h @@ -604,10 +604,6 @@ void getAddressOfPInvokeTarget( CORINFO_METHOD_HANDLE method, CORINFO_CONST_LOOKUP* pLookup) override; -void* GetCookieForPInvokeCalliSig( - CORINFO_SIG_INFO* szMetaSig, - void** ppIndirection) override; - void* GetCookieForInterpreterCalliSig( CORINFO_SIG_INFO* szMetaSig) override; diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index 578c88b80b5252..f2d46fcd173aa3 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -37,11 +37,11 @@ #include -constexpr GUID JITEEVersionIdentifier = { /* 305cdd16-2cee-49af-ae7c-2537eb37cae9 */ - 0x305cdd16, - 0x2cee, - 0x49af, - {0xae, 0x7c, 0x25, 0x37, 0xeb, 0x37, 0xca, 0xe9} +constexpr GUID JITEEVersionIdentifier = { /* 1ba67350-5cd4-47fb-81c6-907234e7dbb6 */ + 0x1ba67350, + 0x5cd4, + 0x47fb, + {0x81, 0xc6, 0x90, 0x72, 0x34, 0xe7, 0xdb, 0xb6} }; #endif // JIT_EE_VERSIONING_GUID_H diff --git a/src/coreclr/inc/jithelpers.h b/src/coreclr/inc/jithelpers.h index 49526413092cb7..3ba323077d060d 100644 --- a/src/coreclr/inc/jithelpers.h +++ b/src/coreclr/inc/jithelpers.h @@ -216,10 +216,6 @@ DYNAMICJITHELPER(CORINFO_HELP_PROF_FCN_TAILCALL, JIT_ProfilerEnterLeaveTailcallStub, METHOD__NIL) // Miscellaneous - // CORINFO_HELP_PINVOKE_CALLI is unused; unmanaged calli is expanded inline or converted to a - // call to a marshalling stub by convertPInvokeCalliToCall. - JITHELPER(CORINFO_HELP_PINVOKE_CALLI, NULL, METHOD__NIL) - #if defined(TARGET_X86) && !defined(UNIX_X86_ABI) JITHELPER(CORINFO_HELP_TAILCALL, JIT_TailCall, METHOD__NIL) #else diff --git a/src/coreclr/jit/ICorJitInfo_names_generated.h b/src/coreclr/jit/ICorJitInfo_names_generated.h index f3265a45960177..8bb8a99fd8494f 100644 --- a/src/coreclr/jit/ICorJitInfo_names_generated.h +++ b/src/coreclr/jit/ICorJitInfo_names_generated.h @@ -149,7 +149,6 @@ DEF_CLR_API(embedFieldHandle) DEF_CLR_API(embedGenericHandle) DEF_CLR_API(getLocationOfThisType) DEF_CLR_API(getAddressOfPInvokeTarget) -DEF_CLR_API(GetCookieForPInvokeCalliSig) DEF_CLR_API(GetCookieForInterpreterCalliSig) DEF_CLR_API(getJustMyCodeHandle) DEF_CLR_API(GetProfilingHandle) diff --git a/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp b/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp index 89e8e05606cfbb..b4df7303ea92bf 100644 --- a/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp +++ b/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp @@ -1426,16 +1426,6 @@ void WrapICorJitInfo::getAddressOfPInvokeTarget( API_LEAVE(getAddressOfPInvokeTarget); } -void* WrapICorJitInfo::GetCookieForPInvokeCalliSig( - CORINFO_SIG_INFO* szMetaSig, - void** ppIndirection) -{ - API_ENTER(GetCookieForPInvokeCalliSig); - void* temp = wrapHnd->GetCookieForPInvokeCalliSig(szMetaSig, ppIndirection); - API_LEAVE(GetCookieForPInvokeCalliSig); - return temp; -} - void* WrapICorJitInfo::GetCookieForInterpreterCalliSig( CORINFO_SIG_INFO* szMetaSig) { diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs b/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs index bdc25a914d0553..ab7ca8480c7a54 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs @@ -179,7 +179,6 @@ which is the right helper to use to allocate an object of a given type. */ /* Miscellaneous */ - CORINFO_HELP_PINVOKE_CALLI, // Unused CORINFO_HELP_TAILCALL, // Perform a tail call CORINFO_HELP_GETCURRENTMANAGEDTHREADID, diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 51be0ec02ec06a..d001e253a7d5c3 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -4151,14 +4151,6 @@ private void getLocationOfThisType(CORINFO_METHOD_STRUCT_* context, ref CORINFO_ private void* GetCookieForInterpreterCalliSig(CORINFO_SIG_INFO* szMetaSig) { throw new NotImplementedException("GetCookieForInterpreterCalliSig"); } - private void* GetCookieForPInvokeCalliSig(CORINFO_SIG_INFO* szMetaSig, ref void* ppIndirection) - { -#if READYTORUN - throw new RequiresRuntimeJitException($"{MethodBeingCompiled} -> {nameof(GetCookieForPInvokeCalliSig)}"); -#else - throw new NotImplementedException(nameof(GetCookieForPInvokeCalliSig)); -#endif - } #pragma warning disable CA1822 // Mark members as static private CORINFO_JUST_MY_CODE_HANDLE_* getJustMyCodeHandle(CORINFO_METHOD_STRUCT_* method, ref CORINFO_JUST_MY_CODE_HANDLE_* ppIndirection) #pragma warning restore CA1822 // Mark members as static diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs index 3cad520435442b..43703ca20dfe6a 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs @@ -165,7 +165,6 @@ static ICorJitInfoCallbacks() s_callbacks.embedGenericHandle = &_embedGenericHandle; s_callbacks.getLocationOfThisType = &_getLocationOfThisType; s_callbacks.getAddressOfPInvokeTarget = &_getAddressOfPInvokeTarget; - s_callbacks.GetCookieForPInvokeCalliSig = &_GetCookieForPInvokeCalliSig; s_callbacks.GetCookieForInterpreterCalliSig = &_GetCookieForInterpreterCalliSig; s_callbacks.getJustMyCodeHandle = &_getJustMyCodeHandle; s_callbacks.GetProfilingHandle = &_GetProfilingHandle; @@ -351,7 +350,6 @@ static ICorJitInfoCallbacks() public delegate* unmanaged embedGenericHandle; public delegate* unmanaged getLocationOfThisType; public delegate* unmanaged getAddressOfPInvokeTarget; - public delegate* unmanaged GetCookieForPInvokeCalliSig; public delegate* unmanaged GetCookieForInterpreterCalliSig; public delegate* unmanaged getJustMyCodeHandle; public delegate* unmanaged GetProfilingHandle; @@ -2533,21 +2531,6 @@ private static void _getAddressOfPInvokeTarget(IntPtr thisHandle, IntPtr* ppExce } } - [UnmanagedCallersOnly] - private static void* _GetCookieForPInvokeCalliSig(IntPtr thisHandle, IntPtr* ppException, CORINFO_SIG_INFO* szMetaSig, void** ppIndirection) - { - var _this = GetThis(thisHandle); - try - { - return _this.GetCookieForPInvokeCalliSig(szMetaSig, ref *ppIndirection); - } - catch (Exception ex) - { - *ppException = _this.AllocException(ex); - return default; - } - } - [UnmanagedCallersOnly] private static void* _GetCookieForInterpreterCalliSig(IntPtr thisHandle, IntPtr* ppException, CORINFO_SIG_INFO* szMetaSig) { diff --git a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt index 2408217150bfb6..668faa8e994b87 100644 --- a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt +++ b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt @@ -320,7 +320,6 @@ FUNCTIONS void embedGenericHandle(CORINFO_RESOLVED_TOKEN * pResolvedToken, bool fEmbedParent, CORINFO_METHOD_HANDLE callerHandle, CORINFO_GENERICHANDLE_RESULT * pResult); void getLocationOfThisType(CORINFO_METHOD_HANDLE context, CORINFO_LOOKUP_KIND* pLookupKind); void getAddressOfPInvokeTarget(CORINFO_METHOD_HANDLE method, REF_CORINFO_CONST_LOOKUP pLookup); - void* GetCookieForPInvokeCalliSig(CORINFO_SIG_INFO* szMetaSig, void ** ppIndirection); void* GetCookieForInterpreterCalliSig(CORINFO_SIG_INFO* szMetaSig); CORINFO_JUST_MY_CODE_HANDLE getJustMyCodeHandle(CORINFO_METHOD_HANDLE method, CORINFO_JUST_MY_CODE_HANDLE**ppIndirection); void GetProfilingHandle(bool* pbHookFunction, void **pProfilerHandle, bool* pbIndirectedHandles); diff --git a/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h b/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h index 63c3f1ea266574..e81f85ac47fa20 100644 --- a/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h +++ b/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h @@ -156,7 +156,6 @@ struct JitInterfaceCallbacks void (* embedGenericHandle)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_RESOLVED_TOKEN* pResolvedToken, bool fEmbedParent, CORINFO_METHOD_HANDLE callerHandle, CORINFO_GENERICHANDLE_RESULT* pResult); void (* getLocationOfThisType)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE context, CORINFO_LOOKUP_KIND* pLookupKind); void (* getAddressOfPInvokeTarget)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE method, CORINFO_CONST_LOOKUP* pLookup); - void* (* GetCookieForPInvokeCalliSig)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_SIG_INFO* szMetaSig, void** ppIndirection); void* (* GetCookieForInterpreterCalliSig)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_SIG_INFO* szMetaSig); CORINFO_JUST_MY_CODE_HANDLE (* getJustMyCodeHandle)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE method, CORINFO_JUST_MY_CODE_HANDLE** ppIndirection); void (* GetProfilingHandle)(void * thisHandle, CorInfoExceptionClass** ppException, bool* pbHookFunction, void** pProfilerHandle, bool* pbIndirectedHandles); @@ -1612,16 +1611,6 @@ class JitInterfaceWrapper : public ICorJitInfo if (pException != nullptr) throw pException; } - virtual void* GetCookieForPInvokeCalliSig( - CORINFO_SIG_INFO* szMetaSig, - void** ppIndirection) -{ - CorInfoExceptionClass* pException = nullptr; - void* temp = _callbacks->GetCookieForPInvokeCalliSig(_thisHandle, &pException, szMetaSig, ppIndirection); - if (pException != nullptr) throw pException; - return temp; -} - virtual void* GetCookieForInterpreterCalliSig( CORINFO_SIG_INFO* szMetaSig) { diff --git a/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h b/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h index eaf9e7c905322e..8ab7e6026528f1 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h @@ -735,14 +735,6 @@ struct GetVarArgsHandleValue DWORDLONG methHnd; }; -struct GetCookieForPInvokeCalliSigValue -{ - DWORD cbSig; - DWORD pSig_Index; - DWORDLONG scope; - DWORD token; -}; - struct GetCookieForInterpreterCalliSigValue { DWORD cbSig; diff --git a/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h b/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h index bc1bedc95085a8..86d13380397669 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h @@ -71,7 +71,6 @@ LWM(GetClassNumInstanceFields, DWORDLONG, DWORD) LWM(GetClassSize, DWORDLONG, DWORD) LWM(GetHeapClassSize, DWORDLONG, DWORD) LWM(CanAllocateOnStack, DWORDLONG, DWORD) -LWM(GetCookieForPInvokeCalliSig, GetCookieForPInvokeCalliSigValue, DLDL) LWM(GetCookieForInterpreterCalliSig, GetCookieForInterpreterCalliSigValue, DLDL) LWM(GetDefaultComparerClass, DWORDLONG, DWORDLONG) LWM(GetDefaultEqualityComparerClass, DWORDLONG, DWORDLONG) diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp index 560bfb72019717..cc0970f66123ab 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp @@ -6115,52 +6115,6 @@ TypeCompareState MethodContext::repIsEnum(CORINFO_CLASS_HANDLE cls, CORINFO_CLAS return (TypeCompareState)value.B; } -void MethodContext::recGetCookieForPInvokeCalliSig(CORINFO_SIG_INFO* szMetaSig, void** ppIndirection, LPVOID result) -{ - if (GetCookieForPInvokeCalliSig == nullptr) - GetCookieForPInvokeCalliSig = new LightWeightMap(); - - GetCookieForPInvokeCalliSigValue key; - ZeroMemory(&key, sizeof(key)); // Zero key including any struct padding - key.cbSig = (DWORD)szMetaSig->cbSig; - key.pSig_Index = (DWORD)GetCookieForPInvokeCalliSig->AddBuffer((unsigned char*)szMetaSig->pSig, szMetaSig->cbSig); - key.scope = CastHandle(szMetaSig->scope); - key.token = (DWORD)szMetaSig->token; - - DLDL value; - if (ppIndirection != nullptr) - value.A = CastPointer(*ppIndirection); - else - value.A = 0; - value.B = CastPointer(result); - - GetCookieForPInvokeCalliSig->Add(key, value); - DEBUG_REC(dmpGetCookieForPInvokeCalliSig(key, value)); -} -void MethodContext::dmpGetCookieForPInvokeCalliSig(const GetCookieForPInvokeCalliSigValue& key, DLDL value) -{ - printf("GetCookieForPInvokeCalliSig NYI"); -} -LPVOID MethodContext::repGetCookieForPInvokeCalliSig(CORINFO_SIG_INFO* szMetaSig, void** ppIndirection) -{ - AssertMapExistsNoMessage(GetCookieForPInvokeCalliSig); - - GetCookieForPInvokeCalliSigValue key; - ZeroMemory(&key, sizeof(key)); // Zero key including any struct padding - key.cbSig = (DWORD)szMetaSig->cbSig; - key.pSig_Index = (DWORD)GetCookieForPInvokeCalliSig->Contains((unsigned char*)szMetaSig->pSig, szMetaSig->cbSig); - key.scope = CastHandle(szMetaSig->scope); - key.token = (DWORD)szMetaSig->token; - - DLDL value = LookupByKeyOrMissNoMessage(GetCookieForPInvokeCalliSig, key); - - DEBUG_REP(dmpGetCookieForPInvokeCalliSig(key, value)); - - if (ppIndirection != nullptr) - *ppIndirection = (void*)value.A; - return (CORINFO_VARARGS_HANDLE)value.B; -} - void MethodContext::recGetCookieForInterpreterCalliSig(CORINFO_SIG_INFO* szMetaSig, LPVOID result) { if (GetCookieForInterpreterCalliSig == nullptr) diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h index 3a4664fab8af6e..c0a4c49d4141f2 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h @@ -757,10 +757,6 @@ class MethodContext void dmpIsEnum(DWORDLONG key, DLD value); TypeCompareState repIsEnum(CORINFO_CLASS_HANDLE cls, CORINFO_CLASS_HANDLE* underlyingType); - void recGetCookieForPInvokeCalliSig(CORINFO_SIG_INFO* szMetaSig, void** ppIndirection, LPVOID result); - void dmpGetCookieForPInvokeCalliSig(const GetCookieForPInvokeCalliSigValue& key, DLDL value); - LPVOID repGetCookieForPInvokeCalliSig(CORINFO_SIG_INFO* szMetaSig, void** ppIndirection); - LPVOID repGetCookieForInterpreterCalliSig(CORINFO_SIG_INFO* szMetaSig); void recGetCookieForInterpreterCalliSig(CORINFO_SIG_INFO* szMetaSig, LPVOID result); void dmpGetCookieForInterpreterCalliSig(const GetCookieForInterpreterCalliSigValue& key, DLDL value); diff --git a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp index f4220a4084c2d7..1d71c7713c27a9 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp @@ -1635,15 +1635,6 @@ void interceptor_ICJI::getAddressOfPInvokeTarget(CORINFO_METHOD_HANDLE method, C mc->recGetAddressOfPInvokeTarget(method, pLookup); } -// Unused -LPVOID interceptor_ICJI::GetCookieForPInvokeCalliSig(CORINFO_SIG_INFO* szMetaSig, void** ppIndirection) -{ - mc->cr->AddCall("GetCookieForPInvokeCalliSig"); - LPVOID temp = original_ICorJitInfo->GetCookieForPInvokeCalliSig(szMetaSig, ppIndirection); - mc->recGetCookieForPInvokeCalliSig(szMetaSig, ppIndirection, temp); - return temp; -} - // Generate a cookie based on the signature to pass to INTOP_CALLI LPVOID interceptor_ICJI::GetCookieForInterpreterCalliSig(CORINFO_SIG_INFO* szMetaSig) { diff --git a/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp b/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp index de53f39764e9db..51f6d501025f28 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp @@ -1175,14 +1175,6 @@ void interceptor_ICJI::getAddressOfPInvokeTarget( original_ICorJitInfo->getAddressOfPInvokeTarget(method, pLookup); } -void* interceptor_ICJI::GetCookieForPInvokeCalliSig( - CORINFO_SIG_INFO* szMetaSig, - void** ppIndirection) -{ - mcs->AddCall("GetCookieForPInvokeCalliSig"); - return original_ICorJitInfo->GetCookieForPInvokeCalliSig(szMetaSig, ppIndirection); -} - void* interceptor_ICJI::GetCookieForInterpreterCalliSig( CORINFO_SIG_INFO* szMetaSig) { diff --git a/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp b/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp index e51bb1ecbc4f1f..b04af78797d442 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp @@ -1030,13 +1030,6 @@ void interceptor_ICJI::getAddressOfPInvokeTarget( original_ICorJitInfo->getAddressOfPInvokeTarget(method, pLookup); } -void* interceptor_ICJI::GetCookieForPInvokeCalliSig( - CORINFO_SIG_INFO* szMetaSig, - void** ppIndirection) -{ - return original_ICorJitInfo->GetCookieForPInvokeCalliSig(szMetaSig, ppIndirection); -} - void* interceptor_ICJI::GetCookieForInterpreterCalliSig( CORINFO_SIG_INFO* szMetaSig) { diff --git a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp index 28bc08333f2daf..c23a2247efbea6 100644 --- a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp @@ -1401,13 +1401,6 @@ void MyICJI::getAddressOfPInvokeTarget(CORINFO_METHOD_HANDLE method, CORINFO_CON jitInstance->mc->repGetAddressOfPInvokeTarget(method, pLookup); } -// Unused -LPVOID MyICJI::GetCookieForPInvokeCalliSig(CORINFO_SIG_INFO* szMetaSig, void** ppIndirection) -{ - jitInstance->mc->cr->AddCall("GetCookieForPInvokeCalliSig"); - return jitInstance->mc->repGetCookieForPInvokeCalliSig(szMetaSig, ppIndirection); -} - // Generate a cookie based on the signature to pass to INTOP_CALLI LPVOID MyICJI::GetCookieForInterpreterCalliSig(CORINFO_SIG_INFO* szMetaSig) { diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 3f1db0663fccb3..fa5cedc118a717 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -10201,17 +10201,6 @@ bool CEEInfo::pInvokeMarshalingRequired(CORINFO_METHOD_HANDLE method, CORINFO_SI return result; } -/*********************************************************************/ -// Unused; unmanaged calli is expanded inline or converted to a call to a marshalling -// stub by convertPInvokeCalliToCall. -LPVOID CEEInfo::GetCookieForPInvokeCalliSig(CORINFO_SIG_INFO* szMetaSig, - void **ppIndirection) -{ - LIMITED_METHOD_CONTRACT; - - UNREACHABLE_RET(); -} - // Check any constraints on method type arguments bool CEEInfo::satisfiesMethodConstraints( CORINFO_CLASS_HANDLE parent, From aa959c74b29413afca24870c1df619047dfe999e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Jul 2026 17:52:17 +0000 Subject: [PATCH 6/8] Fix unused target variable and EXPLICITTHIS handling in calli stub - Remove unused #if HOST_64BIT 'target' variable in StubHelpers::GetDelegateTarget that was left over after removing the old GenericPInvokeCalliHelper mangling. - Fix BuildCalliILStubSignature to only prepend an extra leading native int for implicit HASTHIS (HASTHIS && !EXPLICITTHIS). For EXPLICITTHIS signatures the explicit this parameter is already counted in numArgs, so adding a second native int would double-count it and produce an incorrect stub signature. Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com> --- src/coreclr/vm/dllimport.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/coreclr/vm/dllimport.cpp b/src/coreclr/vm/dllimport.cpp index a78b2134b23c9d..d1cd48d1b6fb50 100644 --- a/src/coreclr/vm/dllimport.cpp +++ b/src/coreclr/vm/dllimport.cpp @@ -6208,13 +6208,14 @@ static void BuildCalliILStubSignature( // Only signatures that describe a standalone method signature get here. _ASSERTE((callConv & IMAGE_CEE_CS_CALLCONV_GENERIC) == 0); - bool hasThis = (callConv & (IMAGE_CEE_CS_CALLCONV_HASTHIS | IMAGE_CEE_CS_CALLCONV_EXPLICITTHIS)) != 0; + bool hasImplicitThis = (callConv & IMAGE_CEE_CS_CALLCONV_HASTHIS) != 0 && + (callConv & IMAGE_CEE_CS_CALLCONV_EXPLICITTHIS) == 0; uint32_t numArgs; IfFailThrow(sigParser.GetData(&numArgs)); pSigBuilder->AppendByte(IMAGE_CEE_CS_CALLCONV_DEFAULT); - pSigBuilder->AppendData(numArgs + (hasThis ? 2 : 1)); + pSigBuilder->AppendData(numArgs + (hasImplicitThis ? 2 : 1)); // The return type, copied verbatim. PCCOR_SIGNATURE pRetTypeStart = sigParser.GetPtr(); @@ -6222,8 +6223,10 @@ static void BuildCalliILStubSignature( PCCOR_SIGNATURE pRetTypeEnd = sigParser.GetPtr(); pSigBuilder->AppendBlob((PVOID)pRetTypeStart, (DWORD)(pRetTypeEnd - pRetTypeStart)); - // The instance pointer, if the call site has one, ahead of the declared parameters. - if (hasThis) + // The instance pointer, if the call site has an implicit 'this', ahead of the declared parameters. + // For EXPLICITTHIS signatures the this parameter is already part of numArgs, so no extra parameter + // is added. + if (hasImplicitThis) { pSigBuilder->AppendElementType(ELEMENT_TYPE_I); } From a55a9fd8e9f5814fcf76bd8d7cd853641d40e504 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 31 Jul 2026 15:30:48 -0700 Subject: [PATCH 7/8] Preserve the exception kind when deferring a calli modopt failure TryGetUnmanagedCallingConventionFromModOpt reports its failures as an HRESULT - COR_E_INVALIDPROGRAM for conflicting calling convention modopts, and COR_E_BADIMAGEFORMAT for a malformed one - and the original code threw them with COMPlusThrowHR, which derives the exception kind from that HRESULT. When the throw became a deferred error carrying a RuntimeExceptionKind, the HRESULT was dropped and the kind hardcoded to TypeLoadException, so a calli with two calling convention modopts reported TypeLoadException instead of InvalidProgramException. Derive the kind from the HRESULT with EEException::GetKindFromHR, which is what COMPlusThrowHR does internally, so the deferred throw is identical to the original one. Fixes baseservices/callconvs/TestCallingConventions. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/dllimport.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/coreclr/vm/dllimport.cpp b/src/coreclr/vm/dllimport.cpp index d1cd48d1b6fb50..0b2ed0147ed692 100644 --- a/src/coreclr/vm/dllimport.cpp +++ b/src/coreclr/vm/dllimport.cpp @@ -6305,7 +6305,9 @@ static bool TryGetCalliStubCallConv( CorInfoCallConvExtension unmgdCallConv; if (FAILED(hr)) { - pError->Set(kTypeLoadException, errorResID); + // Report the failure the same way COMPlusThrowHR(hr, errorResID) would have: + // the exception kind comes from the HRESULT, not from the parser's failure mode. + pError->Set(EEException::GetKindFromHR(hr), errorResID); unmgdCallConv = CallConv::GetDefaultUnmanagedCallingConvention(); } else From f0e4e51e4073182ed4feddee8e3207f170228d7d Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 31 Jul 2026 21:25:16 -0700 Subject: [PATCH 8/8] Allow an unmanaged CALLI stub to have a trailing target argument on x86 LowerSpecialCopyArgs implements the x86 IJW copy-constructor semantics by mapping each argument of the unmanaged call onto the IL stub argument at the same index, and asserted that the two counts are equal. An unmanaged CALLI stub takes the call target as an extra trailing argument that is not passed on to the unmanaged call, so its argument count is one higher and the assert fired for a calli with IsCopyConstructed modreqs. The index mapping itself is unaffected - the extra argument is last, and the loop only walks indices below the unmanaged call's argument count - so only the assert needs to allow the stub to have more arguments than the call. Fixes Interop/PInvoke/Miscellaneous/CopyCtor on windows-x86. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/jit/lower.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index d5c287df9a3707..668d434dd5f791 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -2044,7 +2044,10 @@ void Lowering::LowerSpecialCopyArgs(GenTreeCall* call) // which will be first in the list. // The this parameter is always passed in registers, so we can ignore it. unsigned argIndex = call->gtArgs.CountUserArgs() - 1; - assert(call->gtArgs.CountUserArgs() == m_compiler->info.compILargsCount); + // The arguments of the unmanaged call are the leading arguments of the IL stub, so the stub + // cannot have fewer of them. It can have more: an unmanaged CALLI stub takes the call target + // as an extra trailing argument that is not passed on to the unmanaged call. + assert(call->gtArgs.CountUserArgs() <= m_compiler->info.compILargsCount); bool checkForUnmanagedThisArg = call->GetUnmanagedCallConv() == CorInfoCallConvExtension::Thiscall; for (CallArg& arg : call->gtArgs.Args()) {