diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index 3f6cc68519a608..ccdbefd7311cb7 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -1148,6 +1148,19 @@ static InterpByteCodeStart* PrepareInterpreterCode(MethodDesc* targetMethod, Int // small subset of frames high. pFrame->ip = ip; pInterpreterFrame->SetTopInterpMethodContextFrame(pFrame); + +#ifdef FEATURE_PORTABLE_ENTRYPOINTS + // Resolve .override before compilation: if a MethodImpl has remapped + // targetMethod's vtable slot, switch to the overriding method so we + // compile the correct body. Cache the result on the original MethodDesc + // so callers that check IsInterpreterCodeInitialized don't re-resolve. + MethodDesc* pOriginalMethod = targetMethod; + if (targetMethod->IsVtableSlot()) + { + targetMethod = MethodTable::MapMethodDeclToMethodImpl(targetMethod); + } +#endif // FEATURE_PORTABLE_ENTRYPOINTS + { GCX_PREEMP(); if (targetMethod->ShouldCallPrestub()) @@ -1159,11 +1172,22 @@ static InterpByteCodeStart* PrepareInterpreterCode(MethodDesc* targetMethod, Int } } InterpByteCodeStart* targetIp = targetMethod->GetInterpreterCode(); + if (targetIp == NULL) { // The prestub wasn't able to setup an interpreter code, so it will never be able to. targetMethod->PoisonInterpreterCode(); +#ifdef FEATURE_PORTABLE_ENTRYPOINTS + if (pOriginalMethod != targetMethod) + pOriginalMethod->PoisonInterpreterCode(); +#endif + } +#ifdef FEATURE_PORTABLE_ENTRYPOINTS + else if (pOriginalMethod != targetMethod) + { + pOriginalMethod->SetInterpreterCode(targetIp); } +#endif return targetIp; } diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index 6e4772c539db5c..67085f91b6420a 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -2483,6 +2483,15 @@ BOOL MethodDesc::ShouldCallPrestub() #ifdef FEATURE_PORTABLE_ENTRYPOINTS methodEntryPoint = GetStableEntryPoint(); + // When the .override directive remaps this method's vtable slot, the entry point + // may be a different method's PortableEntryPoint (the overriding method). In that case, check this + // method's own PortableEntryPoint instead. + if (IsVtableSlot()) + { + PCODE ownEntryPoint = GetPortableEntryPointIfExists(); + if (ownEntryPoint != (PCODE)NULL && ownEntryPoint != methodEntryPoint) + methodEntryPoint = ownEntryPoint; + } return methodEntryPoint == (PCODE)NULL || (!PortableEntryPoint::HasInterpreterData(methodEntryPoint) && !PortableEntryPoint::HasNativeEntryPoint(methodEntryPoint)); diff --git a/src/tests/Loader/classloader/MethodImpl/Desktop/self_override5.il b/src/tests/Loader/classloader/MethodImpl/Desktop/self_override5.il index 6a4f305a4e34dc..157eca1c829e4a 100644 --- a/src/tests/Loader/classloader/MethodImpl/Desktop/self_override5.il +++ b/src/tests/Loader/classloader/MethodImpl/Desktop/self_override5.il @@ -124,6 +124,30 @@ L0: L1: + // Test delegate path: create a delegate to DoBar on a MyBar instance. + // Since DoBarOverride .overrides DoBar, invoking the delegate should + // execute MyBar::DoBarOverride and return 5. + ldc.i4.5 + newobj instance void MyBar::.ctor() + call bool CMain::TestDelegateDoBar(int32, class MyBar) + brtrue.s L2 + ldc.i4.0 + stloc.0 + +L2: + + // Test delegate path: create a delegate to DoBar on a MyFoo instance. + // Virtual dispatch through the delegate should execute + // MyFoo::DoBarOverride and return 6. + ldc.i4.6 + newobj instance void MyFoo::.ctor() + call bool CMain::TestDelegateDoBar(int32, class MyBar) + brtrue.s L3 + ldc.i4.0 + stloc.0 + +L3: + // return a status IL_0034: ldloc.0 IL_0035: brtrue.s IL_003b @@ -144,6 +168,43 @@ L1: IL_0041: ret } // end of method CMain::Main + // Helper method: creates a Func delegate targeting MyBar::DoBar on the + // given object, invokes it, and checks the result against the expected value. + .method public hidebysig static bool TestDelegateDoBar(int32 expected, class MyBar obj) cil managed + { + .maxstack 8 + .locals init (class [mscorlib]System.Func`1 del, + int32 result) + + // Func del = (Func)Delegate.CreateDelegate(typeof(Func), obj, "DoBar"); + ldtoken class [mscorlib]System.Func`1 + call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) + ldarg.1 + ldstr "DoBar" + call class [mscorlib]System.Delegate [mscorlib]System.Delegate::CreateDelegate(class [mscorlib]System.Type, object, string) + castclass class [mscorlib]System.Func`1 + stloc.0 + + // int result = del(); + ldloc.0 + callvirt instance !0 class [mscorlib]System.Func`1::Invoke() + stloc.1 + + // if (result == expected) return true; + ldloc.1 + ldarg.0 + beq.s DELEGATE_PASS + + ldstr "FAIL: delegate to DoBar returned wrong value" + call void [mscorlib]System.Console::WriteLine(string) + ldc.i4.0 + ret + +DELEGATE_PASS: + ldc.i4.1 + ret + } // end of method CMain::TestDelegateDoBar + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { diff --git a/src/tests/Loader/classloader/MethodImpl/Desktop/self_override_generic.il b/src/tests/Loader/classloader/MethodImpl/Desktop/self_override_generic.il new file mode 100644 index 00000000000000..b1cab5f7c9c713 --- /dev/null +++ b/src/tests/Loader/classloader/MethodImpl/Desktop/self_override_generic.il @@ -0,0 +1,130 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// Tests .override (MethodImpl) on generic methods. +// Program::B() explicitly overrides Program::A(). +// Both virtual and non-virtual calls to A() should execute B(), +// for both reference type (string) and value type (int32) instantiations. + +.assembly extern mscorlib{} +.assembly extern xunit.core {} +.assembly self_override_generic{} + +.class public Program extends [mscorlib]System.Object +{ + .method public virtual newslot instance int32 A() cil managed + { + ldstr "In A" + call void [mscorlib]System.Console::WriteLine(string) + ldc.i4.1 + ret + } + + .method public virtual newslot instance int32 B() cil managed + { + .override method instance int32 Program::A<[1]>() + ldstr "In B" + call void [mscorlib]System.Console::WriteLine(string) + ldc.i4.2 + ret + } + + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed + { + ldarg.0 + call instance void [mscorlib]System.Object::.ctor() + ret + } +} + +.class public beforefieldinit CMain extends [mscorlib]System.Object +{ + .method public hidebysig static int32 Main() cil managed + { + .custom instance void [xunit.core]Xunit.FactAttribute::.ctor() = ( + 01 00 00 00 + ) + .entrypoint + .locals init (class Program o, bool pass) + + ldc.i4.1 + stloc.1 + + newobj instance void Program::.ctor() + stloc.0 + + // Test 1: o.A() virtually - should return 2 (B called) + ldstr "Test 1: calling A() virtually..." + call void [mscorlib]System.Console::WriteLine(string) + ldc.i4.2 + ldloc.0 + callvirt instance int32 Program::A() + beq.s T1_PASS + ldstr "FAIL: A() virtual call did not execute B" + call void [mscorlib]System.Console::WriteLine(string) + ldc.i4.0 + stloc.1 +T1_PASS: + + // Test 2: o.A() virtually - should return 2 (B called) + ldstr "Test 2: calling A() virtually..." + call void [mscorlib]System.Console::WriteLine(string) + ldc.i4.2 + ldloc.0 + callvirt instance int32 Program::A() + beq.s T2_PASS + ldstr "FAIL: A() virtual call did not execute B" + call void [mscorlib]System.Console::WriteLine(string) + ldc.i4.0 + stloc.1 +T2_PASS: + + // Test 3: o.A() non-virtually - should return 2 (body replaced) + ldstr "Test 3: calling A() non-virtually..." + call void [mscorlib]System.Console::WriteLine(string) + ldc.i4.2 + ldloc.0 + call instance int32 Program::A() + beq.s T3_PASS + ldstr "FAIL: A() non-virtual call did not execute B" + call void [mscorlib]System.Console::WriteLine(string) + ldc.i4.0 + stloc.1 +T3_PASS: + + // Test 4: o.A() non-virtually - should return 2 (body replaced) + ldstr "Test 4: calling A() non-virtually..." + call void [mscorlib]System.Console::WriteLine(string) + ldc.i4.2 + ldloc.0 + call instance int32 Program::A() + beq.s T4_PASS + ldstr "FAIL: A() non-virtual call did not execute B" + call void [mscorlib]System.Console::WriteLine(string) + ldc.i4.0 + stloc.1 +T4_PASS: + + // Return result + ldloc.1 + brtrue.s PASS + + ldstr "FAIL" + call void [mscorlib]System.Console::WriteLine(string) + ldc.i4.s 101 + ret + +PASS: + ldstr "PASS" + call void [mscorlib]System.Console::WriteLine(string) + ldc.i4.s 100 + ret + } + + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed + { + ldarg.0 + call instance void [mscorlib]System.Object::.ctor() + ret + } +} diff --git a/src/tests/Loader/classloader/MethodImpl/self_override_generic.ilproj b/src/tests/Loader/classloader/MethodImpl/self_override_generic.ilproj new file mode 100644 index 00000000000000..b4274e96c9993e --- /dev/null +++ b/src/tests/Loader/classloader/MethodImpl/self_override_generic.ilproj @@ -0,0 +1,8 @@ + + + 1 + + + + +