Implement convertPInvokeCalliToCall for CoreCLR - #131654
Conversation
|
Tagging subscribers to this area: @agocke |
There was a problem hiding this comment.
Pull request overview
This PR updates CoreCLR to use the JIT-EE convertPInvokeCalliToCall path for unmanaged calli, aligning behavior with NativeAOT by creating/caching an IL stub at JIT-time (instead of the legacy runtime helper + per-arch assembly mechanism).
Changes:
- Implement
CEEInfo::convertPInvokeCalliToCalland plumb VM support to create CALLI IL stubs and defer/report stub-generation failures at stub-execution time. - Remove the legacy
GenericPInvokeCalliHelper/ cookie-based JIT path and associated helper/assembly plumbing across architectures, plus related frame/cDAC entries. - Update debugger stub tracing to recover the unmanaged target from the new trailing
native intargument.
Reviewed changes
Copilot reviewed 71 out of 71 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/coreclr/vm/wasm/helpers.cpp | Removes wasm placeholder for the deleted GenericPInvokeCalliHelper. |
| src/coreclr/vm/stubmgr.h | Removes mention of GenericPInvokeCalliHelper from comments. |
| src/coreclr/vm/stubmgr.cpp | Updates IL stub tracing to locate CALLI target from the last argument (not hidden arg); removes helper tracing paths. |
| src/coreclr/vm/stubhelpers.h | Adds QCall entrypoint declaration for throwing deferred interop exceptions. |
| src/coreclr/vm/stubhelpers.cpp | Adds QCall implementation for deferred interop exception throwing; removes old CALLI target mangling. |
| src/coreclr/vm/riscv64/pinvokestubs.S | Deletes CALLI helper stub macro usage and associated “secret” mangling logic. |
| src/coreclr/vm/qcallentrypoints.cpp | Registers new StubHelpers_ThrowInteropException QCall entrypoint. |
| src/coreclr/vm/mlinfo.cpp | Defers CALLI-stub marshal failures to runtime (records exception info instead of throwing during stub generation). |
| src/coreclr/vm/loongarch64/pinvokestubs.S | Deletes CALLI helper stub macro usage and associated “secret” mangling logic. |
| src/coreclr/vm/jitinterface.cpp | Implements convertPInvokeCalliToCall; marks IL stubs as non-inlineable. |
| src/coreclr/vm/i386/cgenx86.cpp | Removes x86 PInvokeCalliFrame regdisplay support (frame removed). |
| src/coreclr/vm/i386/asmhelpers.S | Removes x86 GenericPInvokeCalliHelper assembly helper. |
| src/coreclr/vm/i386/asmhelpers.asm | Removes x86 GenericPInvokeCalliHelper assembly helper and worker import. |
| src/coreclr/vm/FrameTypes.h | Removes PInvokeCalliFrame from frame type list. |
| src/coreclr/vm/frames.h | Removes PInvokeCalliFrame definition and updates CALLI documentation comments. |
| src/coreclr/vm/frames.cpp | Removes PInvokeCalliFrame promotion/logging and constructor. |
| src/coreclr/vm/dllimport.h | Adds VM API for creating CALLI IL stubs; adds stub linker state for deferred exceptions and target-arg index. |
| src/coreclr/vm/dllimport.cpp | Adds CALLI IL stub creation pipeline and deferred-throw stub generation; refactors vararg stub worker path. |
| src/coreclr/vm/datadescriptor/datadescriptor.inc | Removes cDAC descriptor for the deleted PInvokeCalliFrame. |
| src/coreclr/vm/corelib.h | Adds binder mapping for StubHelpers.ThrowInteropException. |
| src/coreclr/vm/cgensys.h | Removes GenericPInvokeCalli* extern declarations. |
| src/coreclr/vm/ceeload.h | Adds Module::GetLoaderModuleForSignature for standalone-signature-owned artifacts. |
| src/coreclr/vm/ceeload.cpp | Implements GetLoaderModuleForSignature and reuses it from GetVASigCookie. |
| src/coreclr/vm/callconvbuilder.cpp | Fixes module-independent signature modopt parsing for unmanaged calling conventions. |
| src/coreclr/vm/arm64/pinvokestubs.S | Deletes CALLI helper stub macro usage and associated “secret” mangling logic. |
| src/coreclr/vm/arm64/PInvokeStubs.asm | Deletes CALLI helper stub macro usage and associated “secret” mangling logic. |
| src/coreclr/vm/arm/pinvokestubs.S | Deletes CALLI helper stub macro usage. |
| src/coreclr/vm/amd64/pinvokestubs.S | Deletes GenericPInvokeCalliHelper implementation (Unix asm). |
| src/coreclr/vm/amd64/PInvokeStubs.asm | Deletes GenericPInvokeCalliHelper implementation (Windows asm). |
| src/coreclr/vm/amd64/asmconstants.h | Removes now-unused pinvoke-calli target register constants. |
| src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp | Removes SuperPMI interception for deleted JIT-EE method GetCookieForPInvokeCalliSig. |
| src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp | Removes shim forwarding for deleted GetCookieForPInvokeCalliSig. |
| src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp | Removes shim forwarding/counter for deleted GetCookieForPInvokeCalliSig. |
| src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp | Removes recording for deleted GetCookieForPInvokeCalliSig. |
| src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h | Removes recording/replay surface for deleted GetCookieForPInvokeCalliSig. |
| src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp | Removes recording/replay implementation for deleted GetCookieForPInvokeCalliSig. |
| src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h | Removes LWM map entry for deleted GetCookieForPInvokeCalliSig. |
| src/coreclr/tools/superpmi/superpmi-shared/agnostic.h | Removes serialized key struct for deleted GetCookieForPInvokeCalliSig. |
| src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt | Removes deleted JIT-EE interface method from thunk generator input. |
| src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs | Removes managed-side implementation stub for deleted GetCookieForPInvokeCalliSig. |
| src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs | Removes generated callback plumbing for deleted GetCookieForPInvokeCalliSig. |
| src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs | Removes helper id CORINFO_HELP_PINVOKE_CALLI. |
| src/coreclr/tools/aot/jitinterface/jitinterface_generated.h | Removes AOT wrapper method for deleted GetCookieForPInvokeCalliSig. |
| src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs | Removes setting hClass for converted calli stub (RyuJit AOT side). |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs | Implements ReadyToRun behavior for convertPInvokeCalliToCall (throws to force runtime JIT when needed). |
| src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs | Adds managed QCall declaration for ThrowInteropException. |
| src/coreclr/jit/wellknownargs.h | Removes well-known args for the deleted cookie/target register convention. |
| src/coreclr/jit/targetx86.h | Removes x86 pinvoke-calli cookie/target register definitions. |
| src/coreclr/jit/targetwasm.h | Removes wasm pinvoke-calli cookie/target register definitions. |
| src/coreclr/jit/targetriscv64.h | Removes riscv64 pinvoke-calli cookie/target register definitions. |
| src/coreclr/jit/targetloongarch64.h | Removes loongarch64 pinvoke-calli cookie/target register definitions. |
| src/coreclr/jit/targetarm64.h | Removes arm64 pinvoke-calli cookie/target register definitions. |
| src/coreclr/jit/targetarm.h | Removes arm pinvoke-calli cookie/target register definitions. |
| src/coreclr/jit/targetamd64.h | Removes amd64 pinvoke-calli cookie/target register definitions. |
| src/coreclr/jit/morph.cpp | Removes cookie-based indirect-call morphing to CORINFO_HELP_PINVOKE_CALLI. |
| src/coreclr/jit/lower.h | Removes lowering hook that asserted cookie calls should not reach lowering. |
| src/coreclr/jit/lower.cpp | Removes lowering helper that asserted cookie calls do not exist. |
| src/coreclr/jit/importercalls.cpp | Always attempts calli conversion via convertPInvokeCalliToCall; removes old cookie-building calli path. |
| src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp | Removes wrapper for deleted GetCookieForPInvokeCalliSig. |
| src/coreclr/jit/ICorJitInfo_names_generated.h | Removes API name entry for deleted GetCookieForPInvokeCalliSig. |
| src/coreclr/jit/handlekinds.h | Removes handle kind for the deleted pinvoke-calli cookie handle. |
| src/coreclr/jit/gentree.h | Removes GenTreeCall::gtCallCookie storage. |
| src/coreclr/jit/gentree.cpp | Removes cookie argument/register plumbing and updates call cloning/init. |
| src/coreclr/jit/ee_il_dll.cpp | Removes now-unused eeConvertToLookup helper (used by cookie path). |
| src/coreclr/jit/compiler.h | Removes eeConvertToLookup declaration. |
| src/coreclr/inc/jithelpers.h | Removes CORINFO_HELP_PINVOKE_CALLI helper mapping. |
| src/coreclr/inc/jiteeversionguid.h | Bumps JIT-EE interface version GUID (interface change). |
| src/coreclr/inc/icorjitinfoimpl_generated.h | Removes override for deleted GetCookieForPInvokeCalliSig. |
| src/coreclr/inc/corinfo.h | Removes CORINFO_HELP_PINVOKE_CALLI and deletes GetCookieForPInvokeCalliSig; clarifies convertPInvokeCalliToCall contract. |
| docs/design/coreclr/botr/guide-for-porting.md | Updates BOTR documentation to remove mention of the deleted CALLI helper. |
| docs/design/coreclr/botr/clr-abi.md | Removes documentation for the deleted CALLI pinvoke cookie/target register convention. |
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>
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>
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>
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>
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>
9fd25b3 to
2d846b4
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 71 out of 71 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/coreclr/vm/stubhelpers.cpp:515
- The local
targetinStubHelpers::GetDelegateTargetis now unused on 64-bit builds, which is likely to trigger an unreferenced-local warning (often treated as error in CoreCLR builds). It looks like the old target-mangling logic was removed but the temporary wasn’t cleaned up.
src/coreclr/vm/stubmgr.cpp:1733 *pValue = ((TADDR*)®s)[index];type-puns anArgumentRegistersstruct as aTADDR[], which is undefined behavior under C++ strict-aliasing rules (and is a new pattern in this file). This can be made aliasing-safe by copying bytes instead of reinterpreting the pointer.
- 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>
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>
|
Azure Pipelines: Successfully started running 8 pipeline(s). 8 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
|
x86 failure seems related? |
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>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 71 out of 71 changed files in this pull request and generated no new comments.
Suppressed comments (1)
src/coreclr/vm/stubmgr.cpp:1785
TryGetCalliStubTargetreads the target value from the incoming stack args via a raw pointer dereference. This code is used by stub managers that are also built for DAC (DACCESS_COMPILE), where direct dereferences should go throughdac_cast<PTR_...>to safely read target memory (seeStubManagerHelpers::GetReturnAddress). Using*(PCODE*)can misread or AV in DAC scenarios.
Replaces CoreCLR's lazy, call-time marshalling mechanism for unmanaged
calliwith theconvertPInvokeCalliToCallJIT-EE path that NativeAOT already uses.Background
An unmanaged
callithat needed marshalling used to compile into a call toCORINFO_HELP_PINVOKE_CALLI. At run timeGenericPInvokeCalliHelper— hand-written assembly onevery architecture — pushed a
PInvokeCalliFrame, calledGenericPInvokeCalliStubWorker, andgenerated the marshalling IL stub from a
VASigCookieon first call. The unmanaged target wassmuggled through a secret register, shifted
<<1|1on 64-bit.Now the stub
MethodDescis created while the caller is being JIT-compiled, cached by signaturein the IL stub cache exactly as before, and the stub is JIT-compiled on first call. The unmanaged
target is passed as an ordinary trailing
native intparameter.Failures are reported when the stub is called
Because the stub is built while its caller is jitted, a failure detected while building it would
otherwise fail the compilation of a method that may never execute the call site.
Marshalling failures were already reported per parameter through
StubHelpers.ThrowInteropParamException. That is extended to the failures which are not tied to aparameter, so the whole set — a signature that needs marshalling, a non-blittable generic
instantiation, an unsupported calling convention, and an instance
thisthe runtime cannotexpress — becomes a stub body that throws when it is called. Classification records the failure and
returns a usable calling convention so stub creation runs to completion, and
FinishEmitdiscardsthe 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, from which no stub signature can be built — still fails
eagerly with
COR_E_BADIMAGEFORMAT.One case is a genuine behavior change: an unmanaged
calliwhose signature carries an instancethisis not expressible as a static stub and the JIT will not expand it inline. Previously theruntime declined to build a stub, which — once the helper fallback was gone — would have let the
JIT emit a plain managed indirect call to an unmanaged target. It now reports
InvalidProgramException/VLDTR_E_FMD_PINVOKENOTSTATIC, matching a P/Invoke declared on aninstance method. Native member functions are unaffected: they are static signatures whose first
parameter is the
thispointer.Removed
GenericPInvokeCalliHelperon amd64/arm/arm64/i386/loongarch64/riscv64/wasm,GenericPInvokeCalliStubWorker,PInvokeCalliFramewith its stack-walking and data-descriptorsupport, and the PInvoke-calli cookie mechanism in the JIT:
GenTreeCall::gtCallCookie,WellKnownArg::PInvokeCookie/PInvokeTargetwith their argument registers on all sevenarchitectures,
GTF_ICON_PINVKI_HDL, thefgMorphArgsrewrite into the helper call,LowerIndirectNonvirtCall, andeeConvertToLookup.Nothing then referenced
GetCookieForPInvokeCalliSigorCORINFO_HELP_PINVOKE_CALLI, so both aredropped from the JIT-EE interface and the JIT-EE version GUID is bumped. The generated files
were regenerated with
ThunkGenerator/gen.bat.CORINFO_HELP_PINVOKE_CALLIis not a ReadyToRunhelper, so removing it does not version the ReadyToRun format — R2R images encode
READYTORUN_HELPER_*ids, which are unaffected.ReadyToRun
ReadyToRun never fell back to the runtime helper:
GetCookieForPInvokeCalliSigthrewRequiresRuntimeJitException, so the cookie call was only ever an abort that left the wholemethod to the runtime JIT. crossgen2 now implements
convertPInvokeCalliToCalland throws forexactly the call sites the JIT cannot expand inline —
mustConvert, a fastcall convention, anunmanaged signature carrying an instance
this, or a signature that needs marshalling.Verified equivalent: an assembly exercising blittable stdcall / platform-default /
SuppressGCTransition/ managed / marshalling / fastcallcalliwas crossgen2'd before and after.The set of precompiled methods and their exact code sizes are byte-identical, and
MarshallingCalli/FastcallCalliare left to the runtime JIT in both.Debugger
ILStubManager::TraceManagercan no longer read the target from the removed hidden argument. Itlocates the trailing target parameter with
MetaSig/ArgIteratorand reads it from the contextcaptured at the stub's entry point — from the argument register the calling convention assigned, or
from the incoming stack arguments. The register mapping is implemented for x86/amd64/arm/arm64 and
returns
FALSE(no trace) elsewhere, matching the existingStubManagerHelpers::GetFirstArgprecedent.
Two latent fixes worth reviewing independently
In
CallConv::TryGetUnmanagedCallingConventionFromModOptSigStartingAtRetType, both pre-existing andboth required for generic
callistubs whose signatures are converted to module-independent form:ELEMENT_TYPE_CMOD_INTERNALmodifiers, inverted relative to the tokenpath.
ConvertToInternalExactlyOneencodesCMOD_OPTasrequired = 0andCallConv*modoptsare always optional, so the branch was dead.
tokenLookupModulebut passedpModuletoGetNameOfTypeRefOrDef, producing aspurious
InvalidProgramExceptionfordelegate* unmanaged<U,T>.These change behavior for any module-independent signature carrying calling-convention modopts.
Validation
windows-x64 Checked:
DisabledRuntimeMarshalling environment failures, unchanged from baseline.
JIT/Directed/pinvokeandJIT/Directed/callconv.cmdtests pass.calli, nested layout classes, fastcall,collectible ALC unload (5/5 assemblies collected), and a hand-written IL matrix of
calli instance unmanagedvariants.ArgIteratortarget recovery was checked against a synthetic entry-point context for targetslanding in RDX, R8, R9 and the first three stack slots.
Not built or executed locally: every non-windows-x64 target. The assembly deletions, the
non-x64 argument-register mappings in the stub manager, and the non-x64 target-header edits are
covered by inspection only.
Fixes #127473
Fixes #131606 in main (though this fix is probably not one we want to backport)
Note
This pull request description was generated by GitHub Copilot.