Skip to content

Commit d874371

Browse files
authored
[clr-interp] Add support for virtual method calls (#114529)
* [clr-interp] Add support for virtual calls (including interface) This follows a trivial approach where we resolve the MethodDesc of the target method by reusing existing functionality. Long term, virtual dispatching should go through slots continaing interpreter IR code pointer, in a similar fashion to JIT. * Add test for virtual method dispatch * Disable test for now No gc support yet
1 parent 50c020e commit d874371

File tree

6 files changed

+124
-27
lines changed

6 files changed

+124
-27
lines changed

src/coreclr/interpreter/compiler.cpp

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1539,12 +1539,18 @@ CORINFO_CLASS_HANDLE InterpCompiler::ResolveClassToken(uint32_t token)
15391539
void InterpCompiler::EmitCall(CORINFO_CLASS_HANDLE constrainedClass, bool readonly, bool tailcall)
15401540
{
15411541
uint32_t token = getU4LittleEndian(m_ip + 1);
1542+
bool isVirtual = (*m_ip == CEE_CALLVIRT);
15421543

15431544
CORINFO_METHOD_HANDLE targetMethod = ResolveMethodToken(token);
15441545

15451546
CORINFO_SIG_INFO targetSignature;
15461547
m_compHnd->getMethodSig(targetMethod, &targetSignature);
15471548

1549+
uint32_t mflags = m_compHnd->getMethodAttribs(targetMethod);
1550+
1551+
if (isVirtual && (!(mflags & CORINFO_FLG_VIRTUAL) || (mflags & CORINFO_FLG_FINAL)))
1552+
isVirtual = false;
1553+
15481554
if (EmitCallIntrinsics(targetMethod, targetSignature))
15491555
{
15501556
m_ip += 5;
@@ -1588,10 +1594,18 @@ void InterpCompiler::EmitCall(CORINFO_CLASS_HANDLE constrainedClass, bool readon
15881594
}
15891595

15901596
// Emit call instruction
1591-
AddIns(INTOP_CALL);
1597+
if (isVirtual)
1598+
{
1599+
AddIns(INTOP_CALLVIRT);
1600+
m_pLastNewIns->data[0] = GetDataItemIndex(targetMethod);
1601+
}
1602+
else
1603+
{
1604+
AddIns(INTOP_CALL);
1605+
m_pLastNewIns->data[0] = GetMethodDataItemIndex(targetMethod);
1606+
}
15921607
m_pLastNewIns->SetDVar(dVar);
15931608
m_pLastNewIns->SetSVar(CALL_ARGS_SVAR);
1594-
m_pLastNewIns->data[0] = GetMethodDataItemIndex(targetMethod);
15951609

15961610
m_pLastNewIns->flags |= INTERP_INST_FLAG_CALL;
15971611
m_pLastNewIns->info.pCallInfo = (InterpCallInfo*)AllocMemPool0(sizeof (InterpCallInfo));
@@ -2630,6 +2644,7 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo)
26302644
EmitUnaryArithmeticOp(INTOP_NOT_I4);
26312645
m_ip++;
26322646
break;
2647+
case CEE_CALLVIRT:
26332648
case CEE_CALL:
26342649
EmitCall(constrainedClass, readonly, tailcall);
26352650
constrainedClass = NULL;

src/coreclr/interpreter/intops.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ OPDEF(INTOP_LDFLDA, "ldflda", 4, 1, 1, InterpOpInt)
246246

247247
// Calls
248248
OPDEF(INTOP_CALL, "call", 4, 1, 1, InterpOpMethodToken)
249+
OPDEF(INTOP_CALLVIRT, "callvirt", 4, 1, 1, InterpOpMethodToken)
249250
OPDEF(INTOP_NEWOBJ, "newobj", 5, 1, 1, InterpOpMethodToken)
250251
OPDEF(INTOP_NEWOBJ_VT, "newobj.vt", 5, 1, 1, InterpOpMethodToken)
251252

src/coreclr/vm/interpexec.cpp

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
5454
stack = pFrame->pStack;
5555

5656
int32_t returnOffset, callArgsOffset, methodSlot;
57+
const int32_t *targetIp;
5758

5859
MAIN_LOOP:
5960
while (true)
@@ -972,6 +973,35 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
972973
ip += 5;
973974
break;
974975
}
976+
case INTOP_CALLVIRT:
977+
{
978+
returnOffset = ip[1];
979+
callArgsOffset = ip[2];
980+
methodSlot = ip[3];
981+
982+
MethodDesc *pMD = (MethodDesc*)pMethod->pDataItems[methodSlot];
983+
984+
OBJECTREF *pThisArg = LOCAL_VAR_ADDR(callArgsOffset, OBJECTREF);
985+
NULL_CHECK(*pThisArg);
986+
987+
// Interpreter-TODO
988+
// This needs to be optimized, not operating at MethodDesc level, rather with ftnptr
989+
// slots containing the interpreter IR pointer
990+
pMD = pMD->GetMethodDescOfVirtualizedCode(pThisArg, pMD->GetMethodTable());
991+
992+
PCODE code = pMD->GetNativeCode();
993+
if (!code)
994+
{
995+
pInterpreterFrame->SetTopInterpMethodContextFrame(pFrame);
996+
GCX_PREEMP();
997+
pMD->PrepareInitialCode(CallerGCMode::Coop);
998+
code = pMD->GetNativeCode();
999+
}
1000+
targetIp = (const int32_t*)code;
1001+
ip += 4;
1002+
// Interpreter-TODO unbox if target method class is valuetype
1003+
goto CALL_TARGET_IP;
1004+
}
9751005

9761006
case INTOP_CALL:
9771007
{
@@ -981,7 +1011,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
9811011

9821012
ip += 4;
9831013
CALL_INTERP_SLOT:
984-
const int32_t *targetIp;
1014+
{
9851015
size_t targetMethod = (size_t)pMethod->pDataItems[methodSlot];
9861016
if (targetMethod & INTERP_METHOD_DESC_TAG)
9871017
{
@@ -1012,7 +1042,8 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
10121042
// for interpreter call or normal pointer for JIT/R2R call.
10131043
targetIp = (const int32_t*)targetMethod;
10141044
}
1015-
1045+
}
1046+
CALL_TARGET_IP:
10161047
// Save current execution state for when we return from called method
10171048
pFrame->ip = ip;
10181049

src/coreclr/vm/method.cpp

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2000,13 +2000,9 @@ PCODE MethodDesc::GetSingleCallableAddrOfVirtualizedCode(OBJECTREF *orThis, Type
20002000
return pObjMT->GetRestoredSlot(GetSlot());
20012001
}
20022002

2003-
//*******************************************************************************
2004-
// The following resolve virtual dispatch for the given method on the given
2005-
// object down to an actual address to call, including any
2006-
// handling of context proxies and other thunking layers.
2007-
PCODE MethodDesc::GetMultiCallableAddrOfVirtualizedCode(OBJECTREF *orThis, TypeHandle staticTH)
2003+
MethodDesc* MethodDesc::GetMethodDescOfVirtualizedCode(OBJECTREF *orThis, TypeHandle staticTH)
20082004
{
2009-
CONTRACT(PCODE)
2005+
CONTRACT(MethodDesc*)
20102006
{
20112007
THROWS;
20122008
GC_TRIGGERS;
@@ -2016,41 +2012,43 @@ PCODE MethodDesc::GetMultiCallableAddrOfVirtualizedCode(OBJECTREF *orThis, TypeH
20162012
POSTCONDITION(RETVAL != NULL);
20172013
}
20182014
CONTRACT_END;
2019-
20202015
// Method table of target (might be instantiated)
20212016
MethodTable *pObjMT = (*orThis)->GetMethodTable();
20222017

20232018
// This is the static method descriptor describing the call.
20242019
// It is not the destination of the call, which we must compute.
20252020
MethodDesc* pStaticMD = this;
2026-
MethodDesc *pTargetMD;
20272021

20282022
if (pStaticMD->HasMethodInstantiation())
20292023
{
20302024
CheckRestore();
2031-
pTargetMD = ResolveGenericVirtualMethod(orThis);
2032-
2033-
// If we're remoting this call we can't call directly on the returned
2034-
// method desc, we need to go through a stub that guarantees we end up
2035-
// in the remoting handler. The stub we use below is normally just for
2036-
// non-virtual calls on virtual methods (that have the same problem
2037-
// where we could end up bypassing the remoting system), but it serves
2038-
// our purpose here (basically pushes our correctly instantiated,
2039-
// resolved method desc on the stack and calls the remoting code).
2040-
2041-
RETURN(pTargetMD->GetMultiCallableAddrOfCode());
2025+
RETURN(ResolveGenericVirtualMethod(orThis));
20422026
}
20432027

20442028
if (pStaticMD->IsInterface())
20452029
{
2046-
pTargetMD = MethodTable::GetMethodDescForInterfaceMethodAndServer(staticTH,pStaticMD,orThis);
2047-
RETURN(pTargetMD->GetMultiCallableAddrOfCode());
2030+
RETURN(MethodTable::GetMethodDescForInterfaceMethodAndServer(staticTH, pStaticMD, orThis));
20482031
}
20492032

2033+
RETURN(pObjMT->GetMethodDescForSlot(pStaticMD->GetSlot()));
2034+
}
20502035

2051-
pTargetMD = pObjMT->GetMethodDescForSlot(pStaticMD->GetSlot());
2036+
//*******************************************************************************
2037+
// The following resolve virtual dispatch for the given method on the given
2038+
// object down to an actual address to call, including any
2039+
// handling of context proxies and other thunking layers.
2040+
PCODE MethodDesc::GetMultiCallableAddrOfVirtualizedCode(OBJECTREF *orThis, TypeHandle staticTH)
2041+
{
2042+
CONTRACT(PCODE)
2043+
{
2044+
THROWS;
2045+
GC_TRIGGERS;
2046+
POSTCONDITION(RETVAL != NULL);
2047+
}
2048+
CONTRACT_END;
20522049

2053-
RETURN (pTargetMD->GetMultiCallableAddrOfCode());
2050+
MethodDesc *pTargetMD = GetMethodDescOfVirtualizedCode(orThis, staticTH);
2051+
RETURN(pTargetMD->GetMultiCallableAddrOfCode());
20542052
}
20552053

20562054
//*******************************************************************************

src/coreclr/vm/method.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1429,6 +1429,7 @@ class MethodDesc
14291429
// indirect call via slot in this case.
14301430
PCODE TryGetMultiCallableAddrOfCode(CORINFO_ACCESS_FLAGS accessFlags);
14311431

1432+
MethodDesc* GetMethodDescOfVirtualizedCode(OBJECTREF *orThis, TypeHandle staticTH);
14321433
// These return an address after resolving "virtual methods" correctly, including any
14331434
// handling of context proxies, other thunking layers and also including
14341435
// instantiation of generic virtual methods if required.

src/tests/JIT/interpreter/Interpreter.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,33 @@
44
using System;
55
using System.Runtime.CompilerServices;
66

7+
public interface ITest
8+
{
9+
public int VirtualMethod();
10+
}
11+
12+
public class BaseClass : ITest
13+
{
14+
public int NonVirtualMethod()
15+
{
16+
return 0xbaba;
17+
}
18+
19+
public virtual int VirtualMethod()
20+
{
21+
return 0xbebe;
22+
}
23+
}
24+
25+
public class DerivedClass : BaseClass
26+
{
27+
public override int VirtualMethod()
28+
{
29+
return 0xdede;
30+
}
31+
32+
}
33+
734
public struct MyStruct
835
{
936
public int a;
@@ -71,6 +98,8 @@ public static void RunInterpreterTests()
7198
// Environment.FailFast(null);
7299
if (!TestFloat())
73100
Environment.FailFast(null);
101+
// if (!TestVirtual())
102+
// Environment.FailFast(null);
74103
}
75104

76105
public static int Mul4(int a, int b, int c, int d)
@@ -209,4 +238,26 @@ public static bool TestFloat()
209238

210239
return true;
211240
}
241+
242+
public static bool TestVirtual()
243+
{
244+
BaseClass bc = new DerivedClass();
245+
ITest itest = bc;
246+
247+
if (bc.NonVirtualMethod() != 0xbaba)
248+
return false;
249+
if (bc.VirtualMethod() != 0xdede)
250+
return false;
251+
if (itest.VirtualMethod() != 0xdede)
252+
return false;
253+
bc = new BaseClass();
254+
itest = bc;
255+
if (bc.NonVirtualMethod() != 0xbaba)
256+
return false;
257+
if (bc.VirtualMethod() != 0xbebe)
258+
return false;
259+
if (itest.VirtualMethod() != 0xbebe)
260+
return false;
261+
return true;
262+
}
212263
}

0 commit comments

Comments
 (0)