Skip to content

CreateILDelegateShuffleThunk's calli cannot pass the hidden argument that open virtual delegate dispatch stubs expect #134733

Description

@lewing

Description

CreateILDelegateShuffleThunk (src/coreclr/vm/comdelegate.cpp) generates an IL stub that loads Delegate._methodPtrAux and invokes it with a plain calli:

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_VirtualOpenDelegateDispatch under FEATURE_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:

// 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_COMPILED off; iOS, tvOS, maccatalyst, browser-wasm): FEATURE_PORTABLE_SHUFFLE_THUNKS is not defined, so the IL shuffle thunk is always used and it runs in the interpreter.
  • riscv64 / loongarch64 (JIT): GenerateShuffleArray returns false when a shuffle would move arguments between the integer and floating-point calling conventions (comdelegate.cpp:~340). SetupShuffleThunk then falls back to CreateILDelegateShuffleThunk, and the JIT-compiled calli does 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 CreateILDelegateShuffleThunk fallback in SetupShuffleThunk.

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.

Note

This issue was generated with GitHub Copilot, based on review feedback from @jkotas on #134699.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-VM-coreclruntriagedNew issue has not been triaged by the area owner

    Type

    No type

    Projects

    • Status
      No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions