Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/coreclr/vm/comdelegate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,8 @@ static PCODE CreateILDelegateShuffleThunk(MethodDesc* pDelegateMD, bool callTarg

pCode->EmitLoadThis();
pCode->EmitLDFLD(pCode->GetToken(CoreLibBinder::GetField(FIELD__DELEGATE__METHOD_PTR_AUX)));
// TODO: For open virtual delegates, _methodPtrAux is a dispatch stub that expects the address of _methodPtrAux in a
// hidden argument, which calli does not pass: https://github.com/dotnet/runtime/issues/134733
pCode->EmitCALLI(TOKEN_ILSTUB_TARGET_SIG, sig.NumFixedArgs(), sig.IsReturnTypeVoid() ? 0 : 1);
pCode->EmitRET();

Expand Down
38 changes: 31 additions & 7 deletions src/coreclr/vm/interpexec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1332,6 +1332,18 @@ static void ShiftDelegateCallArgs(int8_t* stack, int32_t callArgsOffset, int32_t
}
}

// Resolves the target of an open virtual delegate for the given 'this' argument.
static MethodDesc* ResolveOpenVirtualDelegateTarget(DELEGATEREF delegateObj, OBJECTREF* pThisArg)
{
MethodDesc* pDeclMD = COMDelegate::GetMethodDescForOpenVirtualDelegate(delegateObj);
return CallWithSEHWrapper(
[pDeclMD, pThisArg]() {
MethodTable* pMT = (*pThisArg)->GetMethodTable();
GCX_PREEMP();
return pDeclMD->GetMethodDescOfVirtualizedCode(pThisArg, pMT, pDeclMD->GetMethodTable());
});
}

static void UpdateFrameForTailCall(InterpMethodContextFrame *pFrame, PTR_InterpByteCodeStart targetIp, int8_t *callArgsAddress)
{
InterpMethod *pTargetMethod = targetIp->Method;
Expand Down Expand Up @@ -3307,6 +3319,24 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
InvokeUnmanagedCalliWithTransition(calliFunctionPointer, cookie, stack, pFrame, callArgsAddress, returnValueAddress);
}
}
#ifdef FEATURE_CACHED_INTERFACE_DISPATCH
else if (calliFunctionPointer == (PCODE)CID_VirtualOpenDelegateDispatch)
{
// For an open virtual delegate, _methodPtrAux is CID_VirtualOpenDelegateDispatch,
// which expects the address of _methodPtrAux in a hidden argument that calli cannot
// express; resolve the target as INTOP_CALLDELEGATE does.
// Workaround for https://github.com/dotnet/runtime/issues/134733.

// The shuffle thunk's 'this' is the delegate.
DELEGATEREF delegateObj = LOCAL_VAR(0, DELEGATEREF);
_ASSERTE(((MethodDesc*)pMethod->methodHnd)->IsILStub() && ((MethodDesc*)pMethod->methodHnd)->AsDynamicMethodDesc()->IsDelegateShuffleThunk());
_ASSERTE(delegateObj != NULL && delegateObj->GetMethodPtrAux() == calliFunctionPointer);
OBJECTREF *pThisArg = (OBJECTREF*)callArgsAddress;
NULL_CHECK(*pThisArg);
targetMethod = ResolveOpenVirtualDelegateTarget(delegateObj, pThisArg);
goto CALL_INTERP_METHOD;
}
#endif // FEATURE_CACHED_INTERFACE_DISPATCH
#ifndef FEATURE_PORTABLE_ENTRYPOINTS
// If we're not using portable entrypoints, we can use NonVirtualEntry2MethodDesc to figure out where tailcalls go. Since this is
// somewhat expensive, we only do it for tailcalls which are relatively rare.
Expand Down Expand Up @@ -3432,15 +3462,9 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr

if (isOpenVirtual)
{
targetMethod = COMDelegate::GetMethodDescForOpenVirtualDelegate(*delegateObj);
OBJECTREF *pThisArg = LOCAL_VAR_ADDR(callArgsOffset + INTERP_STACK_SLOT_SIZE, OBJECTREF);
NULL_CHECK(*pThisArg);
targetMethod = CallWithSEHWrapper(
[&targetMethod, &pThisArg]() {
MethodTable* pMT = (*pThisArg)->GetMethodTable();
GCX_PREEMP();
return targetMethod->GetMethodDescOfVirtualizedCode(pThisArg, pMT, targetMethod->GetMethodTable());
});
targetMethod = ResolveOpenVirtualDelegateTarget(*delegateObj, pThisArg);
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,24 @@ public static void DifferentOpenVirtualDelegates()
Assert.Equal(m2, b.Method);
}

[Fact]
public static void OpenVirtualDelegates_InvokeResolvesOverride()
{
Func<object, string> toString = typeof(object).GetMethod(nameof(object.ToString)).CreateDelegate<Func<object, string>>();
Assert.Equal(nameof(OpenVirtualDerived), toString(new OpenVirtualDerived()));
Assert.Equal(typeof(Struct).ToString(), toString(new Struct()));
Assert.Equal(nameof(DayOfWeek.Monday), toString(DayOfWeek.Monday));
}

[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/134707", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT))]
public static void OpenVirtualDelegates_InterfaceMethod_InvokeResolvesImplementation()
{
Func<IOpenVirtual, int> interfaceMethod = typeof(IOpenVirtual).GetMethod(nameof(IOpenVirtual.M)).CreateDelegate<Func<IOpenVirtual, int>>();
Assert.Equal(1, interfaceMethod(new OpenVirtualDerived()));
Assert.Equal(2, interfaceMethod(new OpenVirtualStruct()));
}

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsTypeEquivalenceSupported))]
public static void TypeEquivalentDelegatesPointingToSameMethod_AreEqualAndHaveSameHashCode()
{
Expand Down Expand Up @@ -580,6 +598,14 @@ internal virtual void M1() { }
internal virtual void M2() { }
}

interface IOpenVirtual { int M(); }
class OpenVirtualDerived : IOpenVirtual
{
public int M() => 1;
public override string ToString() => nameof(OpenVirtualDerived);
}
struct OpenVirtualStruct : IOpenVirtual { public int M() => 2; }

class Base { public virtual void M() { } }
class Derived : Base { public override void M() { } }

Expand Down
Loading