From 7d927f3b5651fea12184eb140aeee0964c4eea8e 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 | 61 --- src/coreclr/vm/amd64/pinvokestubs.S | 61 --- src/coreclr/vm/arm/pinvokestubs.S | 10 - src/coreclr/vm/arm64/PInvokeStubs.asm | 28 +- src/coreclr/vm/arm64/pinvokestubs.S | 25 +- 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 | 485 ++++++++++++++---- src/coreclr/vm/dllimport.h | 20 + src/coreclr/vm/frames.cpp | 38 -- src/coreclr/vm/frames.h | 83 +-- 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 | 24 +- src/coreclr/vm/method.hpp | 5 + src/coreclr/vm/prestub.cpp | 21 +- src/coreclr/vm/riscv64/pinvokestubs.S | 24 +- src/coreclr/vm/stubhelpers.cpp | 4 - src/coreclr/vm/stubmgr.cpp | 35 +- src/coreclr/vm/stubmgr.h | 1 - src/coreclr/vm/wasm/helpers.cpp | 5 - 31 files changed, 607 insertions(+), 637 deletions(-) diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index 4c39bed1aea091..30fdf0ca0a3112 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -3512,6 +3512,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 775339048a6af1..6faba04088b4ac 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -230,10 +230,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); } @@ -7209,11 +7213,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 9913dbd3c53f5b..6a837c67a983e0 100644 --- a/src/coreclr/vm/amd64/PInvokeStubs.asm +++ b/src/coreclr/vm/amd64/PInvokeStubs.asm @@ -4,72 +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 - - ; - ; We need to distinguish between a MethodDesc* and an unmanaged target. - ; The way we do this is to shift the managed target to the left by one bit and then set the - ; least significant bit to 1. This works because MethodDesc* are always 8-byte aligned. - ; - shl PINVOKE_CALLI_TARGET_REGISTER, 1 - or PINVOKE_CALLI_TARGET_REGISTER, 1 - - ; - ; 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 diff --git a/src/coreclr/vm/amd64/pinvokestubs.S b/src/coreclr/vm/amd64/pinvokestubs.S index 49bc602afee105..ae1e1b166f5fed 100644 --- a/src/coreclr/vm/amd64/pinvokestubs.S +++ b/src/coreclr/vm/amd64/pinvokestubs.S @@ -6,67 +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) - - // - // We need to distinguish between a MethodDesc* and an unmanaged target. - // The way we do this is to shift the managed target to the left by one bit and then set the - // least significant bit to 1. This works because MethodDesc* are always 8-byte aligned. - // - shl PINVOKE_CALLI_TARGET_REGISTER, 1 - or PINVOKE_CALLI_TARGET_REGISTER, 1 - - // - // 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 180dca18de7f69..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" @@ -49,16 +44,6 @@ __PInvokeStubWorkerName SETS "$FuncPrefix":CC:"StubWorker" ; if null goto stub generation cbz x9, %0 - IF "$FuncPrefix" == "GenericPInvokeCalli" - ; - ; We need to distinguish between a MethodDesc* and an unmanaged target. - ; The way we do this is to shift the managed target to the left by one bit and then set the - ; least significant bit to 1. This works because MethodDesc* are always 8-byte aligned. - ; - lsl $HiddenArg, $HiddenArg, #1 - orr $HiddenArg, $HiddenArg, #1 - ENDIF - EPILOG_BRANCH_REG x9 0 @@ -186,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 de2a23cf06cb31..a6dce447ef5219 100644 --- a/src/coreclr/vm/arm64/pinvokestubs.S +++ b/src/coreclr/vm/arm64/pinvokestubs.S @@ -11,11 +11,11 @@ // // 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 -.macro PINVOKE_STUB __PInvokeStubFuncName,__PInvokeGenStubFuncName,__PInvokeStubWorkerName,VASigCookieReg,HiddenArg,SaveFPArgs,ShiftLeftAndOrSecret=0 +.macro PINVOKE_STUB __PInvokeStubFuncName,__PInvokeGenStubFuncName,__PInvokeStubWorkerName,VASigCookieReg,HiddenArg,SaveFPArgs NESTED_ENTRY \__PInvokeStubFuncName, _TEXT, NoHandler @@ -26,16 +26,6 @@ // if null goto stub generation cbz x9, LOCAL_LABEL(\__PInvokeStubFuncName\()_0) - .if (\ShiftLeftAndOrSecret == 1) - // - // We need to distinguish between a MethodDesc* and an unmanaged target. - // The way we do this is to shift the managed target to the left by one bit and then set the - // least significant bit to 1. This works because MethodDesc* are always 8-byte aligned. - // - lsl \HiddenArg, \HiddenArg, #1 - orr \HiddenArg, \HiddenArg, #1 - .endif - EPILOG_BRANCH_REG x9 LOCAL_LABEL(\__PInvokeStubFuncName\()_0): @@ -167,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, 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 3f32aca5cdcecb..bdb5345d3832ba 100644 --- a/src/coreclr/vm/cgensys.h +++ b/src/coreclr/vm/cgensys.h @@ -51,8 +51,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 6242f9f49da888..c0acd22dee842f 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -1306,11 +1306,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 1598b2b45e498e..b4c24b9e4c7114 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 @@ -772,10 +778,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 { @@ -795,7 +801,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); } @@ -1865,6 +1873,7 @@ PInvokeStubLinker::PInvokeStubLinker( m_ErrorResID(-1), m_ErrorParamIdx(-1), m_iLCIDParamIdx(iLCIDParamIdx), + m_uCalliTargetArgIdx(0), m_dwStubFlags(dwStubFlags) { STANDARD_VM_CONTRACT; @@ -1916,6 +1925,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; @@ -2361,14 +2377,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 (asmhelpers.asm) - EmitLoadStubContext(pcsEmit, dwStubFlags); -#ifdef TARGET_64BIT - // the secret arg has been shifted to left and ORed with 1 (see code:GenericPInvokeCalliHelper) - pcsEmit->EmitLDC(1); - pcsEmit->EmitSHR_UN(); -#endif // TARGET_64BIT + // 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 { @@ -2442,8 +2453,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 { @@ -3582,6 +3593,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, @@ -3627,7 +3639,7 @@ static MarshalInfo::MarshalType DoMarshalReturnValue(MetaSig& msig, nlFlags, FALSE, argidx, - msig.NumFixedArgs(), + numArgs, SF_IsBestFit(dwStubFlags), SF_IsThrowOnUnmappableChar(dwStubFlags), TRUE, @@ -3816,6 +3828,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); @@ -3944,6 +3966,7 @@ static COR_ILMETHOD_DECODER* CreatePInvokeStubWorker( nlType, nlFlags, argidx, + numArgs, pss, argOffset, dwStubFlags, @@ -5484,6 +5507,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( @@ -6037,10 +6141,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 { @@ -6049,7 +6153,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; @@ -6067,32 +6172,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(); @@ -6100,93 +6325,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; @@ -6201,14 +6493,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 856a9270d0fffd..7dfd2fd1ac6dee 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 //------------------------------------------------------------------------ @@ -2101,7 +2027,8 @@ class InlinedCallFrame : public Frame WRAPPER_NO_CONTRACT; #ifdef HOST_64BIT - // See code:GenericPInvokeCalliHelper + // m_Datum is a MethodDesc* when it is set. It is NULL for indirect calls that do not + // have a stub secret argument. return ((m_Datum != NULL) && !(dac_cast(m_Datum) & 0x1)); #else // HOST_64BIT return ((dac_cast(m_Datum) & ~0xffff) != 0); 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 c8ffc31b0d3583..9c1d4b923902d9 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); @@ -13312,7 +13319,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); } @@ -13325,7 +13332,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)) @@ -15419,9 +15426,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 d1bb4e462481a9..8052571d1786a7 100644 --- a/src/coreclr/vm/loongarch64/pinvokestubs.S +++ b/src/coreclr/vm/loongarch64/pinvokestubs.S @@ -11,12 +11,12 @@ // // 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 -.macro PINVOKE_STUB __PInvokeStubFuncName,__PInvokeGenStubFuncName,__PInvokeStubWorkerName,VASigCookieReg,HiddenArg,SaveFPArgs,ShiftLeftAndOrSecret=0 +.macro PINVOKE_STUB __PInvokeStubFuncName,__PInvokeGenStubFuncName,__PInvokeStubWorkerName,VASigCookieReg,HiddenArg,SaveFPArgs NESTED_ENTRY \__PInvokeStubFuncName, _TEXT, NoHandler @@ -26,16 +26,6 @@ // if null goto stub generation beq $t0, $zero, \__PInvokeGenStubFuncName - .if (\ShiftLeftAndOrSecret == 1) - // - // We need to distinguish between a MethodDesc* and an unmanaged target. - // The way we do this is to shift the managed target to the left by one bit and then set the - // least significant bit to 1. This works because MethodDesc* are always 8-byte aligned. - // - slli.d \HiddenArg, \HiddenArg, 1 - ori \HiddenArg, \HiddenArg, 1 - .endif - jirl $r0, $t0, 0 NESTED_END \__PInvokeStubFuncName, _TEXT @@ -166,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, 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 3c7f0e733a612f..9c0700911fae8d 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 85eb386154f905..1d99a5b1b6d932 100644 --- a/src/coreclr/vm/riscv64/pinvokestubs.S +++ b/src/coreclr/vm/riscv64/pinvokestubs.S @@ -11,11 +11,11 @@ // // 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 -.macro PINVOKE_STUB __PInvokeStubFuncName,__PInvokeGenStubFuncName,__PInvokeStubWorkerName,VASigCookieReg,HiddenArg,SaveFPArgs,ShiftLeftAndOrSecret=0 +.macro PINVOKE_STUB __PInvokeStubFuncName,__PInvokeGenStubFuncName,__PInvokeStubWorkerName,VASigCookieReg,HiddenArg,SaveFPArgs NESTED_ENTRY \__PInvokeStubFuncName, _TEXT, NoHandler @@ -26,16 +26,6 @@ // if null goto stub generation beq t0, zero, \__PInvokeGenStubFuncName - .if (\ShiftLeftAndOrSecret == 1) - // - // We need to distinguish between a MethodDesc* and an unmanaged target. - // The way we do this is to shift the managed target to the left by one bit and then set the - // least significant bit to 1. This works because MethodDesc* are always 8-byte aligned. - // - slli \HiddenArg, \HiddenArg, 1 - ori \HiddenArg, \HiddenArg, 1 - .endif - jr t0 NESTED_END \__PInvokeStubFuncName, _TEXT @@ -160,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, 1 - //// ------------------------------------------------------------------ //// VarargPInvokeStub_RetBuffArg & VarargPInvokeGenILStub_RetBuffArg //// Vararg PInvoke Stub when the method has a hidden return buffer arg diff --git a/src/coreclr/vm/stubhelpers.cpp b/src/coreclr/vm/stubhelpers.cpp index c0b51077fda7e5..ffa16faf24f75b 100644 --- a/src/coreclr/vm/stubhelpers.cpp +++ b/src/coreclr/vm/stubhelpers.cpp @@ -512,10 +512,6 @@ FCIMPL1(void*, StubHelpers::GetDelegateTarget, DelegateObject *pThisUNSAFE) #if defined(HOST_64BIT) UINT_PTR target = (UINT_PTR)orefThis->GetMethodPtrAux(); - - // See code:GenericPInvokeCalliHelper - // The lowest bit is used to distinguish between MD and target on 64-bit. - target = (target << 1) | 1; #endif // HOST_64BIT pEntryPoint = orefThis->GetMethodPtrAux(); diff --git a/src/coreclr/vm/stubmgr.cpp b/src/coreclr/vm/stubmgr.cpp index 258fb74f509658..224dbd825f41ff 100644 --- a/src/coreclr/vm/stubmgr.cpp +++ b/src/coreclr/vm/stubmgr.cpp @@ -1740,16 +1740,11 @@ BOOL ILStubManager::TraceManager(Thread *thread, } else if (pStubMD->IsPInvokeCalliStub()) { - // This is unmanaged CALLI stub, the argument is the target - target = (PCODE)arg; - - // The value is mangled on 64-bit -#ifdef TARGET_AMD64 - target = target >> 1; // call target is encoded as (addr << 1) | 1 -#endif // TARGET_AMD64 - - 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()) { @@ -1894,7 +1889,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 @@ -1941,12 +1936,6 @@ BOOL InteropDispatchStubManager::CheckIsStub_Internal(PCODE stubStartAddress) { return true; } - - if (stubStartAddress == GetEEFuncEntryPoint(GenericPInvokeCalliHelper)) - { - return true; - } - #endif // !DACCESS_COMPILE return false; } @@ -2008,18 +1997,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 da04e1f11dbe63..9d61bd75bc34a5 100644 --- a/src/coreclr/vm/stubmgr.h +++ b/src/coreclr/vm/stubmgr.h @@ -653,7 +653,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 3d6c96c5bb8ea7..709dd9040449d7 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 d139689b107fd28377ad8672227225390da53047 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 224dbd825f41ff..1fbb0a42b4aed2 100644 --- a/src/coreclr/vm/stubmgr.cpp +++ b/src/coreclr/vm/stubmgr.cpp @@ -1687,6 +1687,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, @@ -1740,11 +1839,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 9f7f710c329d025bd8a6344c9fc5d53103ce0a8f 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 30fdf0ca0a3112..276068744f1fbb 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, @@ -3363,7 +3364,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 f9cf55b8feeecb..8de5a895d6b170 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -9574,8 +9574,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 0cb12dfca24794..c75f990052ce30 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -1607,14 +1607,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; @@ -2396,12 +2388,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; } @@ -2619,18 +2605,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()) && @@ -10118,11 +10092,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; } @@ -10130,7 +10103,6 @@ GenTreeCall* Compiler::gtNewCallNode(gtCallTypes callType, { node->gtCallMethHnd = callHnd; node->gtControlExpr = nullptr; - node->ClearInlineInfo(); } node->gtReturnType = type; @@ -11741,15 +11713,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 ab02812f445863..6c339cafe9dc11 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 6faba04088b4ac..42d49f17e21430 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -230,17 +230,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 */ @@ -830,20 +825,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()) { @@ -7189,8 +7170,6 @@ void Compiler::impCheckForPInvokeCall( } unmanagedCallConv = info.compCompHnd->getUnmanagedCallConv(nullptr, sig, &suppressGCTransition); - - assert(!call->gtCallCookie); } if (suppressGCTransition) @@ -7213,13 +7192,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()) { @@ -8406,17 +8383,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 54bca2b4a8995c..f3b0ee121abd56 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); } @@ -6631,14 +6627,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 eb7b257709ac3f..a748ee110b0ed2 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 bebaf78c3f59ea..3e3e495345314f 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 @@ -5811,7 +5783,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 56e3ad962095dd..02e4b480df6bc1 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 9a708cd3f7b420..30b286ba924b70 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp @@ -1621,7 +1621,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 e5d65f8e046f0a..a30210bbfbc458 100644 --- a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp @@ -1389,7 +1389,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 b4c24b9e4c7114..58cc56824a27d4 100644 --- a/src/coreclr/vm/dllimport.cpp +++ b/src/coreclr/vm/dllimport.cpp @@ -6281,7 +6281,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, @@ -6294,11 +6295,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: @@ -6306,7 +6302,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: { @@ -6329,7 +6325,7 @@ static bool TryGetCalliStubCallConv( } *pUnmgdCallConv = unmgdCallConv; - return true; + break; } case IMAGE_CEE_CS_CALLCONV_DEFAULT: @@ -6344,6 +6340,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 9c1d4b923902d9..d7faccc1a50e73 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 9c0700911fae8d..16e06b03dcea50 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 625846f08cf2a69d5f42de1d70f60d22def8917b 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 3d5f7da15005ef..2daa7a7b2e4148 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -2180,6 +2180,9 @@ internal static partial class StubHelpers [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "StubHelpers_ThrowInteropParamException")] internal static 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 84f6ff9bf88230..cabb495628270c 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -1066,6 +1066,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 58cc56824a27d4..e7a89cf5d4be12 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 @@ -762,7 +771,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. @@ -801,9 +817,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); } @@ -1872,6 +1886,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) @@ -2074,6 +2090,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() { @@ -4572,7 +4621,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); + } } } @@ -5415,7 +5469,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)) @@ -5508,86 +5564,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( @@ -6206,15 +6182,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) @@ -6222,72 +6202,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; @@ -6310,18 +6299,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; @@ -6337,13 +6333,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); } @@ -6354,7 +6351,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; @@ -6376,7 +6381,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 @@ -6385,18 +6391,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; } @@ -6415,54 +6414,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); @@ -6473,23 +6424,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 d7faccc1a50e73..82ba262fe65c6e 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); @@ -13319,7 +13319,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); } @@ -13332,7 +13332,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 16e06b03dcea50..3c7f0e733a612f 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 ffa16faf24f75b..15a82960fb0767 100644 --- a/src/coreclr/vm/stubhelpers.cpp +++ b/src/coreclr/vm/stubhelpers.cpp @@ -529,6 +529,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 69da8a9c6c64d4..08bd8e4df4cb49 100644 --- a/src/coreclr/vm/stubhelpers.h +++ b/src/coreclr/vm/stubhelpers.h @@ -58,6 +58,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 2d846b4f847f114a879e9bbdf54b7ced01bfe8c5 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 276068744f1fbb..42c824deb19f1f 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, @@ -3364,13 +3362,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 21dfb633498260..61b2f2a16f2269 100644 --- a/src/coreclr/inc/icorjitinfoimpl_generated.h +++ b/src/coreclr/inc/icorjitinfoimpl_generated.h @@ -597,10 +597,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 ed763baa880121..75775cf0096d46 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -37,11 +37,11 @@ #include -constexpr GUID JITEEVersionIdentifier = { /* 134b051a-e1ec-4f52-a1bf-f919908aa33c */ - 0x134b051a, - 0xe1ec, - 0x4f52, - {0xa1, 0xbf, 0xf9, 0x19, 0x90, 0x8a, 0xa3, 0x3c} +constexpr GUID JITEEVersionIdentifier = { /* 311b47c4-f2ef-4500-887d-a493343940d7 */ + 0x311b47c4, + 0xf2ef, + 0x4500, + {0x88, 0x7d, 0xa4, 0x93, 0x34, 0x39, 0x40, 0xd7} }; #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 fe8fa377332a9a..f55abaf7dc98e4 100644 --- a/src/coreclr/jit/ICorJitInfo_names_generated.h +++ b/src/coreclr/jit/ICorJitInfo_names_generated.h @@ -148,7 +148,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 5291f7a8c8d287..b5ccc82ea89a59 100644 --- a/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp +++ b/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp @@ -1413,16 +1413,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 acbf063ada8c5b..7a52094b772b69 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -4072,14 +4072,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 5be5fd01b69eb9..1f45c9bf3f3602 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs @@ -164,7 +164,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; @@ -349,7 +348,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; @@ -2516,21 +2514,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 f478dc79563f29..bcb7e99a428b80 100644 --- a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt +++ b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt @@ -319,7 +319,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 75648980ac0a7b..04baed86f08c8e 100644 --- a/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h +++ b/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h @@ -155,7 +155,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); @@ -1598,16 +1597,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 42c1f462755b4d..4855a7f851845d 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h @@ -728,14 +728,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 1616fc79d2916f..2165d8aba1991c 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 7d90db055b4d3d..61a96fc2b11ff8 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp @@ -6049,52 +6049,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 32eb7aef721b6d..78ba8c9d3575f6 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h @@ -743,10 +743,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 30b286ba924b70..969f5d09dc8163 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp @@ -1621,15 +1621,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 931dad3bb9cce0..f288135513ae0e 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp @@ -1164,14 +1164,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 023658229bbf74..bdc9f19050b835 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp @@ -1020,13 +1020,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 a30210bbfbc458..e77b71b5a9cb0d 100644 --- a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp @@ -1389,13 +1389,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 82ba262fe65c6e..873b9e95e3e8a6 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 123f0fdbbda6a390cb8bb2d4926f61dc58654766 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 +++++++---- src/coreclr/vm/stubhelpers.cpp | 4 ---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/coreclr/vm/dllimport.cpp b/src/coreclr/vm/dllimport.cpp index e7a89cf5d4be12..5c66b51b384db9 100644 --- a/src/coreclr/vm/dllimport.cpp +++ b/src/coreclr/vm/dllimport.cpp @@ -6209,13 +6209,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(); @@ -6223,8 +6224,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); } diff --git a/src/coreclr/vm/stubhelpers.cpp b/src/coreclr/vm/stubhelpers.cpp index 15a82960fb0767..83ea29ad8d6dfd 100644 --- a/src/coreclr/vm/stubhelpers.cpp +++ b/src/coreclr/vm/stubhelpers.cpp @@ -510,10 +510,6 @@ FCIMPL1(void*, StubHelpers::GetDelegateTarget, DelegateObject *pThisUNSAFE) DELEGATEREF orefThis = (DELEGATEREF)ObjectToOBJECTREF(pThisUNSAFE); -#if defined(HOST_64BIT) - UINT_PTR target = (UINT_PTR)orefThis->GetMethodPtrAux(); -#endif // HOST_64BIT - pEntryPoint = orefThis->GetMethodPtrAux(); return (PVOID)pEntryPoint; From dfbf6ac26b5f520ffb248ca952a549249f736d21 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 5c66b51b384db9..589a72690c5b25 100644 --- a/src/coreclr/vm/dllimport.cpp +++ b/src/coreclr/vm/dllimport.cpp @@ -6306,7 +6306,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 60169d736eff899b11adcba904ecc64547e78f2c 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 f3b0ee121abd56..379c83c2b3f2ef 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()) {