@@ -543,6 +543,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
543
543
}
544
544
545
545
int32_t returnOffset, callArgsOffset, methodSlot;
546
+ bool isTailcall = false ;
546
547
MethodDesc* targetMethod;
547
548
548
549
MAIN_LOOP:
@@ -1895,8 +1896,10 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
1895
1896
break ;
1896
1897
}
1897
1898
1899
+ case INTOP_CALLVIRT_TAIL:
1898
1900
case INTOP_CALLVIRT:
1899
1901
{
1902
+ isTailcall = (*ip == INTOP_CALLVIRT_TAIL);
1900
1903
returnOffset = ip[1 ];
1901
1904
callArgsOffset = ip[2 ];
1902
1905
methodSlot = ip[3 ];
@@ -1914,8 +1917,10 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
1914
1917
goto CALL_INTERP_METHOD;
1915
1918
}
1916
1919
1920
+ case INTOP_CALLI_TAIL:
1917
1921
case INTOP_CALLI:
1918
1922
{
1923
+ isTailcall = (*ip == INTOP_CALLI_TAIL);
1919
1924
returnOffset = ip[1 ];
1920
1925
callArgsOffset = ip[2 ];
1921
1926
int32_t calliFunctionPointerVar = ip[3 ];
@@ -1927,6 +1932,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
1927
1932
// Save current execution state for when we return from called method
1928
1933
pFrame->ip = ip;
1929
1934
1935
+ // Interpreter-FIXME: isTailcall
1930
1936
InvokeCalliStub (LOCAL_VAR (calliFunctionPointerVar, PCODE), pCallStub, stack + callArgsOffset, stack + returnOffset);
1931
1937
break ;
1932
1938
}
@@ -1936,6 +1942,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
1936
1942
// This opcode handles p/invokes that don't use a managed wrapper for marshaling. These
1937
1943
// calls are special in that they need an InlinedCallFrame in order for proper EH to happen
1938
1944
1945
+ isTailcall = false ;
1939
1946
returnOffset = ip[1 ];
1940
1947
callArgsOffset = ip[2 ];
1941
1948
methodSlot = ip[3 ];
@@ -1971,6 +1978,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
1971
1978
1972
1979
case INTOP_CALLDELEGATE:
1973
1980
{
1981
+ isTailcall = false ;
1974
1982
returnOffset = ip[1 ];
1975
1983
callArgsOffset = ip[2 ];
1976
1984
methodSlot = ip[3 ];
@@ -1996,8 +2004,10 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
1996
2004
break ;
1997
2005
}
1998
2006
2007
+ case INTOP_CALL_TAIL:
1999
2008
case INTOP_CALL:
2000
2009
{
2010
+ isTailcall = (*ip == INTOP_CALL_TAIL);
2001
2011
returnOffset = ip[1 ];
2002
2012
callArgsOffset = ip[2 ];
2003
2013
methodSlot = ip[3 ];
@@ -2032,24 +2042,44 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
2032
2042
if (targetIp == NULL )
2033
2043
{
2034
2044
// If we didn't get the interpreter code pointer setup, then this is a method we need to invoke as a compiled method.
2045
+ // Interpreter-FIXME: Implement tailcall via helpers, see https://github.com/dotnet/runtime/blob/main/docs/design/features/tailcalls-with-helpers.md
2035
2046
InvokeCompiledMethod (targetMethod, stack + callArgsOffset, stack + returnOffset, targetMethod->GetMultiCallableAddrOfCode (CORINFO_ACCESS_ANY));
2036
2047
break ;
2037
2048
}
2038
2049
}
2039
2050
2040
- // Allocate child frame.
2051
+ if (isTailcall)
2041
2052
{
2042
- InterpMethodContextFrame *pChildFrame = pFrame->pNext ;
2043
- if (!pChildFrame)
2053
+ // Move args from callArgsOffset to start of stack frame.
2054
+ InterpMethod* pTargetMethod = targetIp->Method ;
2055
+ assert (pTargetMethod->CheckIntegrity ());
2056
+ // It is safe to use memcpy because the source and destination are both on the interp stack, not in the GC heap.
2057
+ // We need to use the target method's argsSize, not our argsSize, because tail calls (unlike CEE_JMP) can have a
2058
+ // different signature from the caller.
2059
+ memcpy (pFrame->pStack , stack + callArgsOffset, pTargetMethod->argsSize );
2060
+ // Reuse current stack frame. We discard the call insn's returnOffset because it's not important and tail calls are
2061
+ // required to be followed by a ret, so we know nothing is going to read from stack[returnOffset] after the call.
2062
+ pFrame->ReInit (pFrame->pParent , targetIp, pFrame->pRetVal , pFrame->pStack );
2063
+ }
2064
+ else
2065
+ {
2066
+ // Save current execution state for when we return from called method
2067
+ pFrame->ip = ip;
2068
+
2069
+ // Allocate child frame.
2044
2070
{
2045
- pChildFrame = (InterpMethodContextFrame*)alloca (sizeof (InterpMethodContextFrame));
2046
- pChildFrame->pNext = NULL ;
2047
- pFrame->pNext = pChildFrame;
2071
+ InterpMethodContextFrame *pChildFrame = pFrame->pNext ;
2072
+ if (!pChildFrame)
2073
+ {
2074
+ pChildFrame = (InterpMethodContextFrame*)alloca (sizeof (InterpMethodContextFrame));
2075
+ pChildFrame->pNext = NULL ;
2076
+ pFrame->pNext = pChildFrame;
2077
+ }
2078
+ pChildFrame->ReInit (pFrame, targetIp, stack + returnOffset, stack + callArgsOffset);
2079
+ pFrame = pChildFrame;
2048
2080
}
2049
- pChildFrame->ReInit (pFrame, targetIp, stack + returnOffset, stack + callArgsOffset);
2050
- pFrame = pChildFrame;
2081
+ assert (((size_t )pFrame->pStack % INTERP_STACK_ALIGNMENT) == 0 );
2051
2082
}
2052
- assert (((size_t )pFrame->pStack % INTERP_STACK_ALIGNMENT) == 0 );
2053
2083
2054
2084
// Set execution state for the new frame
2055
2085
pMethod = pFrame->startIp ->Method ;
@@ -2061,6 +2091,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
2061
2091
}
2062
2092
case INTOP_NEWOBJ_GENERIC:
2063
2093
{
2094
+ isTailcall = false ;
2064
2095
returnOffset = ip[1 ];
2065
2096
callArgsOffset = ip[2 ];
2066
2097
methodSlot = ip[4 ];
@@ -2079,6 +2110,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
2079
2110
}
2080
2111
case INTOP_NEWOBJ:
2081
2112
{
2113
+ isTailcall = false ;
2082
2114
returnOffset = ip[1 ];
2083
2115
callArgsOffset = ip[2 ];
2084
2116
methodSlot = ip[3 ];
@@ -2110,6 +2142,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
2110
2142
}
2111
2143
case INTOP_NEWOBJ_VT:
2112
2144
{
2145
+ isTailcall = false ;
2113
2146
returnOffset = ip[1 ];
2114
2147
callArgsOffset = ip[2 ];
2115
2148
methodSlot = ip[3 ];
0 commit comments