Skip to content

Commit 977d1bf

Browse files
committed
Implement tailcalls
Don't assert on tail calli, just do a non-tail call for now so the application will at least run (but potentially run out of stack) Cleanup fixme comments
1 parent d978828 commit 977d1bf

File tree

3 files changed

+46
-12
lines changed

3 files changed

+46
-12
lines changed

src/coreclr/interpreter/compiler.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1241,7 +1241,7 @@ InterpMethod* InterpCompiler::CreateInterpMethod()
12411241

12421242
bool unmanagedCallersOnly = corJitFlags.IsSet(CORJIT_FLAGS::CORJIT_FLAG_REVERSE_PINVOKE);
12431243

1244-
InterpMethod *pMethod = new InterpMethod(m_methodHnd, m_totalVarsStackSize, pDataItems, initLocals, unmanagedCallersOnly);
1244+
InterpMethod *pMethod = new InterpMethod(m_methodHnd, m_ILLocalsOffset, m_totalVarsStackSize, pDataItems, initLocals, unmanagedCallersOnly);
12451245

12461246
return pMethod;
12471247
}

src/coreclr/interpreter/interpretershared.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,20 @@ struct InterpMethod
3030
InterpMethod *self;
3131
#endif
3232
CORINFO_METHOD_HANDLE methodHnd;
33-
int32_t allocaSize;
33+
int32_t argsSize, allocaSize;
3434
void** pDataItems;
3535
// This stub is used for calling the interpreted method from JITted/AOTed code
3636
CallStubHeader *pCallStub;
3737
bool initLocals;
3838
bool unmanagedCallersOnly;
3939

40-
InterpMethod(CORINFO_METHOD_HANDLE methodHnd, int32_t allocaSize, void** pDataItems, bool initLocals, bool unmanagedCallersOnly)
40+
InterpMethod(CORINFO_METHOD_HANDLE methodHnd, int32_t argsSize, int32_t allocaSize, void** pDataItems, bool initLocals, bool unmanagedCallersOnly)
4141
{
4242
#if DEBUG
4343
this->self = this;
4444
#endif
4545
this->methodHnd = methodHnd;
46+
this->argsSize = argsSize;
4647
this->allocaSize = allocaSize;
4748
this->pDataItems = pDataItems;
4849
this->initLocals = initLocals;

src/coreclr/vm/interpexec.cpp

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
543543
}
544544

545545
int32_t returnOffset, callArgsOffset, methodSlot;
546+
bool isTailcall = false;
546547
MethodDesc* targetMethod;
547548

548549
MAIN_LOOP:
@@ -1895,8 +1896,10 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
18951896
break;
18961897
}
18971898

1899+
case INTOP_CALLVIRT_TAIL:
18981900
case INTOP_CALLVIRT:
18991901
{
1902+
isTailcall = (*ip == INTOP_CALLVIRT_TAIL);
19001903
returnOffset = ip[1];
19011904
callArgsOffset = ip[2];
19021905
methodSlot = ip[3];
@@ -1914,8 +1917,10 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
19141917
goto CALL_INTERP_METHOD;
19151918
}
19161919

1920+
case INTOP_CALLI_TAIL:
19171921
case INTOP_CALLI:
19181922
{
1923+
isTailcall = (*ip == INTOP_CALLI_TAIL);
19191924
returnOffset = ip[1];
19201925
callArgsOffset = ip[2];
19211926
int32_t calliFunctionPointerVar = ip[3];
@@ -1927,6 +1932,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
19271932
// Save current execution state for when we return from called method
19281933
pFrame->ip = ip;
19291934

1935+
// Interpreter-FIXME: isTailcall
19301936
InvokeCalliStub(LOCAL_VAR(calliFunctionPointerVar, PCODE), pCallStub, stack + callArgsOffset, stack + returnOffset);
19311937
break;
19321938
}
@@ -1936,6 +1942,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
19361942
// This opcode handles p/invokes that don't use a managed wrapper for marshaling. These
19371943
// calls are special in that they need an InlinedCallFrame in order for proper EH to happen
19381944

1945+
isTailcall = false;
19391946
returnOffset = ip[1];
19401947
callArgsOffset = ip[2];
19411948
methodSlot = ip[3];
@@ -1971,6 +1978,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
19711978

19721979
case INTOP_CALLDELEGATE:
19731980
{
1981+
isTailcall = false;
19741982
returnOffset = ip[1];
19751983
callArgsOffset = ip[2];
19761984
methodSlot = ip[3];
@@ -1996,8 +2004,10 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
19962004
break;
19972005
}
19982006

2007+
case INTOP_CALL_TAIL:
19992008
case INTOP_CALL:
20002009
{
2010+
isTailcall = (*ip == INTOP_CALL_TAIL);
20012011
returnOffset = ip[1];
20022012
callArgsOffset = ip[2];
20032013
methodSlot = ip[3];
@@ -2032,24 +2042,44 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
20322042
if (targetIp == NULL)
20332043
{
20342044
// 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
20352046
InvokeCompiledMethod(targetMethod, stack + callArgsOffset, stack + returnOffset, targetMethod->GetMultiCallableAddrOfCode(CORINFO_ACCESS_ANY));
20362047
break;
20372048
}
20382049
}
20392050

2040-
// Allocate child frame.
2051+
if (isTailcall)
20412052
{
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.
20442070
{
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;
20482080
}
2049-
pChildFrame->ReInit(pFrame, targetIp, stack + returnOffset, stack + callArgsOffset);
2050-
pFrame = pChildFrame;
2081+
assert (((size_t)pFrame->pStack % INTERP_STACK_ALIGNMENT) == 0);
20512082
}
2052-
assert (((size_t)pFrame->pStack % INTERP_STACK_ALIGNMENT) == 0);
20532083

20542084
// Set execution state for the new frame
20552085
pMethod = pFrame->startIp->Method;
@@ -2061,6 +2091,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
20612091
}
20622092
case INTOP_NEWOBJ_GENERIC:
20632093
{
2094+
isTailcall = false;
20642095
returnOffset = ip[1];
20652096
callArgsOffset = ip[2];
20662097
methodSlot = ip[4];
@@ -2079,6 +2110,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
20792110
}
20802111
case INTOP_NEWOBJ:
20812112
{
2113+
isTailcall = false;
20822114
returnOffset = ip[1];
20832115
callArgsOffset = ip[2];
20842116
methodSlot = ip[3];
@@ -2110,6 +2142,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
21102142
}
21112143
case INTOP_NEWOBJ_VT:
21122144
{
2145+
isTailcall = false;
21132146
returnOffset = ip[1];
21142147
callArgsOffset = ip[2];
21152148
methodSlot = ip[3];

0 commit comments

Comments
 (0)