Skip to content

Implement convertPInvokeCalliToCall for CoreCLR - #131654

Open
jkoritzinsky wants to merge 8 commits into
mainfrom
dev/jkoritzinsky/calli-pinvoke
Open

Implement convertPInvokeCalliToCall for CoreCLR#131654
jkoritzinsky wants to merge 8 commits into
mainfrom
dev/jkoritzinsky/calli-pinvoke

Conversation

@jkoritzinsky

@jkoritzinsky jkoritzinsky commented Jul 31, 2026

Copy link
Copy Markdown
Member

Replaces CoreCLR's lazy, call-time marshalling mechanism for unmanaged calli with the
convertPInvokeCalliToCall JIT-EE path that NativeAOT already uses.

Background

An unmanaged calli that needed marshalling used to compile into a call to
CORINFO_HELP_PINVOKE_CALLI. At run time GenericPInvokeCalliHelper — hand-written assembly on
every architecture — pushed a PInvokeCalliFrame, called GenericPInvokeCalliStubWorker, and
generated the marshalling IL stub from a VASigCookie on first call. The unmanaged target was
smuggled through a secret register, shifted <<1|1 on 64-bit.

Now the stub MethodDesc is created while the caller is being JIT-compiled, cached by signature
in 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 int parameter.

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 a
parameter, 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 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, from which no stub signature can be built — still fails
eagerly with COR_E_BADIMAGEFORMAT.

One case is a genuine behavior change: an unmanaged calli whose signature carries an instance
this is not expressible as a static stub and the JIT will not expand it inline. Previously the
runtime 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 an
instance method. Native member functions are unaffected: they are static signatures whose first
parameter is the this pointer.

Removed

GenericPInvokeCalliHelper on amd64/arm/arm64/i386/loongarch64/riscv64/wasm,
GenericPInvokeCalliStubWorker, PInvokeCalliFrame with its stack-walking and data-descriptor
support, and the PInvoke-calli cookie mechanism in the JIT: GenTreeCall::gtCallCookie,
WellKnownArg::PInvokeCookie/PInvokeTarget with their argument registers on all seven
architectures, GTF_ICON_PINVKI_HDL, the fgMorphArgs rewrite into the helper call,
LowerIndirectNonvirtCall, and eeConvertToLookup.

Nothing then referenced GetCookieForPInvokeCalliSig or CORINFO_HELP_PINVOKE_CALLI, so both are
dropped 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_CALLI is not a ReadyToRun
helper, 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: GetCookieForPInvokeCalliSig threw
RequiresRuntimeJitException, so the cookie call was only ever an abort that left the whole
method to the runtime JIT. crossgen2 now implements convertPInvokeCalliToCall and throws for
exactly the call sites the JIT cannot expand inline — mustConvert, a fastcall convention, an
unmanaged signature carrying an instance this, or a signature that needs marshalling.

Verified equivalent: an assembly exercising blittable stdcall / platform-default /
SuppressGCTransition / managed / marshalling / fastcall calli was crossgen2'd before and after.
The set of precompiled methods and their exact code sizes are byte-identical, and
MarshallingCalli / FastcallCalli are left to the runtime JIT in both.

Debugger

ILStubManager::TraceManager can no longer read the target from the removed hidden argument. It
locates the trailing target parameter with MetaSig/ArgIterator and reads it from the context
captured 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 existing StubManagerHelpers::GetFirstArg
precedent.

Two latent fixes worth reviewing independently

In CallConv::TryGetUnmanagedCallingConventionFromModOptSigStartingAtRetType, both pre-existing and
both required for generic calli stubs whose signatures are converted to module-independent form:

  • It skipped optional ELEMENT_TYPE_CMOD_INTERNAL modifiers, inverted relative to the token
    path. ConvertToInternalExactlyOne encodes CMOD_OPT as required = 0 and CallConv* modopts
    are always optional, so the branch was dead.
  • It computed tokenLookupModule but passed pModule to GetNameOfTypeRefOrDef, producing a
    spurious InvalidProgramException for delegate* unmanaged<U,T>.

These change behavior for any module-independent signature carrying calling-convention modopts.

Validation

windows-x64 Checked:

  • Interop tree: 333 total, 321 passed, 10 failed — the 10 are pre-existing out-of-proc COM and
    DisabledRuntimeMarshalling environment failures, unchanged from baseline.
  • JIT/Directed tree: 686 total, 679 passed, 0 failed, 7 skipped.
  • All 13 JIT/Directed/pinvoke and JIT/Directed/callconv .cmd tests pass.
  • Ad hoc coverage: marshalling / blittable / tiered calli, nested layout classes, fastcall,
    collectible ALC unload (5/5 assemblies collected), and a hand-written IL matrix of
    calli instance unmanaged variants.
  • The ArgIterator target recovery was checked against a synthetic entry-point context for targets
    landing 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.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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::convertPInvokeCalliToCall and 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 int argument.

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.

Comment thread src/coreclr/vm/stubhelpers.cpp Outdated
Comment thread src/coreclr/vm/stubmgr.cpp
Comment thread src/coreclr/vm/dllimport.cpp Outdated
@jkoritzinsky
jkoritzinsky marked this pull request as draft July 31, 2026 17:18
jkoritzinsky and others added 5 commits July 31, 2026 10:30
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>
Copilot AI review requested due to automatic review settings July 31, 2026 17:31
@jkoritzinsky
jkoritzinsky force-pushed the dev/jkoritzinsky/calli-pinvoke branch from 9fd25b3 to 2d846b4 Compare July 31, 2026 17:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 target in StubHelpers::GetDelegateTarget is 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*)&regs)[index]; type-puns an ArgumentRegisters struct as a TADDR[], 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>
Copilot AI review requested due to automatic review settings July 31, 2026 17:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 71 out of 71 changed files in this pull request and generated no new comments.

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>
Copilot AI review requested due to automatic review settings July 31, 2026 22:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 71 out of 71 changed files in this pull request and generated no new comments.

@jkoritzinsky
jkoritzinsky marked this pull request as ready for review July 31, 2026 23:34
@azure-pipelines

Copy link
Copy Markdown
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.

@MichalPetryka

Copy link
Copy Markdown
Contributor

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>
Copilot AI review requested due to automatic review settings August 1, 2026 04:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  • TryGetCalliStubTarget reads 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 through dac_cast<PTR_...> to safely read target memory (see StubManagerHelpers::GetReturnAddress). Using *(PCODE*) can misread or AV in DAC scenarios.

@MichalPetryka

Copy link
Copy Markdown
Contributor

@MihuBot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status

4 participants