Skip to content
Merged
2 changes: 2 additions & 0 deletions src/coreclr/vm/comdelegate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,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
44 changes: 34 additions & 10 deletions src/coreclr/vm/interpexec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1358,6 +1358,21 @@ 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();
MethodDesc* pTarget;
GCX_PREEMP_REGION_BEGIN();
pTarget = pDeclMD->GetMethodDescOfVirtualizedCode(pThisArg, pMT, pDeclMD->GetMethodTable());
GCX_PREEMP_REGION_END();
return pTarget;
});
}

static void UpdateFrameForTailCall(InterpMethodContextFrame *pFrame, PTR_InterpByteCodeStart targetIp, int8_t *callArgsAddress)
{
InterpMethod *pTargetMethod = targetIp->Method;
Expand Down Expand Up @@ -3376,6 +3391,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 @@ -3500,18 +3533,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();
MethodDesc *pTarget;
GCX_PREEMP_REGION_BEGIN();
pTarget = targetMethod->GetMethodDescOfVirtualizedCode(pThisArg, pMT, targetMethod->GetMethodTable());
GCX_PREEMP_REGION_END();
return pTarget;
});
targetMethod = ResolveOpenVirtualDelegateTarget(*delegateObj, pThisArg);
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,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 @@ -588,6 +606,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
2 changes: 0 additions & 2 deletions src/libraries/tests.proj
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,6 @@
<ItemGroup Condition="'$(TargetOS)' == 'browser' and '$(RuntimeFlavor)' == 'CoreCLR' and '$(TestWasmReadyToRun)' == 'true' and '$(PublishReadyToRun)' != 'false' and '$(RunDisabledWasmTests)' != 'true'">
<!-- ActiveIssue https://github.com/dotnet/runtime/issues/134200 -->
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Runtime\tests\System.Dynamic.Runtime.Tests\System.Dynamic.Runtime.Tests.csproj" />
<!-- ActiveIssue https://github.com/dotnet/runtime/issues/134261 -->
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Linq.Expressions\tests\System.Linq.Expressions.Tests.csproj" />
</ItemGroup>

<!-- Wasm aot on all platforms -->
Expand Down
Loading