Repository navigation
CreateILDelegateShuffleThunk's calli cannot pass the hidden argument that open virtual delegate dispatch stubs expect #134733
Description
Description
CreateILDelegateShuffleThunk (src/coreclr/vm/comdelegate.cpp) generates an IL stub that loads Delegate._methodPtrAux and invokes it with a plain calli:
runtime/src/coreclr/vm/comdelegate.cpp
Lines 816 to 818 in e2234ae
| pCode->EmitLoadThis(); | |
| pCode->EmitLDFLD(pCode->GetToken(CoreLibBinder::GetField(FIELD__DELEGATE__METHOD_PTR_AUX))); | |
| pCode->EmitCALLI(TOKEN_ILSTUB_TARGET_SIG, sig.NumFixedArgs(), sig.IsReturnTypeVoid() ? 0 : 1); |
For an open virtual delegate, _methodPtrAux holds a virtual dispatch stub from GetVirtualCallStub (comdelegate.cpp:990). The stub is:
CID_VirtualOpenDelegateDispatchunderFEATURE_CACHED_INTERFACE_DISPATCH, or- a virtual stub dispatch stub under
FEATURE_VIRTUAL_STUB_DISPATCH.
Both kinds of stub expect the address of _methodPtrAux (the indirection cell) in a hidden argument register, for example r11 on amd64 or x11 on arm64. calli cannot express that argument, so the stub reads whatever happens to be in that register.
The hand-written shuffle thunks do set up the hidden argument, for example:
runtime/src/coreclr/vm/i386/stublinkerx86.cpp
Lines 1196 to 1199 in e2234ae
| // mov r10, [r11 + Delegate._methodptraux] | |
| X86EmitIndexRegLoad(kR10, kR11, DelegateObject::GetOffsetOfMethodPtrAux()); | |
| // add r11, DelegateObject::GetOffsetOfMethodPtrAux() - load the indirection cell into r11 | |
| X86EmitAddReg(kR11, DelegateObject::GetOffsetOfMethodPtrAux()); |
Where the IL shuffle thunk is used
- No dynamic code (
FEATURE_DYNAMIC_CODE_COMPILEDoff; iOS, tvOS, maccatalyst, browser-wasm):FEATURE_PORTABLE_SHUFFLE_THUNKSis not defined, so the IL shuffle thunk is always used and it runs in the interpreter.- On maccatalyst-arm64 CoreCLR this crashed in
CID_VirtualOpenDelegateDispatchWorker. - On browser-wasm (ReadyToRun) it trapped in
PrepareInterpreterCode([browser][CoreCLR][R2R] System.Linq.Expressions tests trap in PrepareInterpreterCode #134261). - [interpreter] Resolve open virtual delegates invoked through the shuffle thunk (wasm, Apple mobile) #134699 works around both in the interpreter:
INTOP_CALLIrecognizes the stub and resolves the target itself, asINTOP_CALLDELEGATEalready does.
- On maccatalyst-arm64 CoreCLR this crashed in
- riscv64 / loongarch64 (JIT):
GenerateShuffleArrayreturns false when a shuffle would move arguments between the integer and floating-point calling conventions (comdelegate.cpp:~340).SetupShuffleThunkthen falls back toCreateILDelegateShuffleThunk, and the JIT-compiledcallidoes not set the hidden argument. - amd64 / arm64 (JIT): the portable stub-linker thunk is used today, so this configuration isn't hit in regular testing. It can be reproduced for investigation by forcing the
CreateILDelegateShuffleThunkfallback inSetupShuffleThunk.
Reproduction (sketch)
Create and invoke an open virtual delegate through a path that uses the IL shuffle thunk, for example:
Func<object, string> f = typeof(object).GetMethod(nameof(object.ToString)).CreateDelegate<Func<object, string>>();
f(new object());Use either riscv64/loongarch64 with a signature that forces the FP/integer fallback, or x64 with the fallback forced.
Expected behavior
Open virtual delegates invoked through the IL shuffle thunk dispatch to the correct override.
Notes
A general fix needs the IL shuffle thunk to supply the hidden argument, or to avoid calli to the dispatch stub for open virtual delegates, rather than each consumer special-casing it. The interpreter change in #134699 is a workaround limited to the interpreter's calli.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsNo status
Activity
dotnet-policy-service commentedon Sep 27, 2026
Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.
[release/11.0] [interpreter] Resolve open virtual delegates invoked t…