Skip to content

Commit 1959692

Browse files
[clr-interp] Improve the performance of interpreter calls (#120067)
* [clr-interp] Improve the performance of interpreter calls - Instead of setting the target in the CallStubHeader at each invocation, store the target in the CallStubHeader immediately. - Avoid invoking the prestub unnecessarily. Instead, detect that we've already run the prestub to completion at least once before poisoning the m_interpreterCode field on MethodDesc. This may be enough to make some of our tests which are currently timing out pass. * Add a tweak so that CallPreStub is able to succeed with methods which were not quite ready to execute. * Fix build breaks * Change NULL check to PCODE comparison
1 parent e3e2d9f commit 1959692

File tree

4 files changed

+50
-9
lines changed

4 files changed

+50
-9
lines changed

src/coreclr/vm/callstubgenerator.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ struct CallStubHeader
4545
Routines[NumRoutines - 1] = target;
4646
}
4747

48+
PCODE GetTarget()
49+
{
50+
LIMITED_METHOD_CONTRACT;
51+
52+
return Routines[NumRoutines - 1];
53+
}
54+
4855
size_t GetSize()
4956
{
5057
LIMITED_METHOD_CONTRACT;

src/coreclr/vm/interpexec.cpp

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ void InvokeUnmanagedCalliWithTransition(PCODE ftn, void *cookie, int8_t *stack,
6868
#ifndef TARGET_WASM
6969
#include "callstubgenerator.h"
7070

71-
static CallStubHeader *UpdateCallStubForMethod(MethodDesc *pMD)
71+
static CallStubHeader *UpdateCallStubForMethod(MethodDesc *pMD, PCODE target)
7272
{
7373
CONTRACTL
7474
{
@@ -85,6 +85,11 @@ static CallStubHeader *UpdateCallStubForMethod(MethodDesc *pMD)
8585
AllocMemTracker amTracker;
8686
CallStubHeader *header = callStubGenerator.GenerateCallStub(pMD, &amTracker, true /* interpreterToNative */);
8787

88+
if (target != (PCODE)NULL)
89+
{
90+
header->SetTarget(target);
91+
}
92+
8893
if (pMD->SetCallStub(header))
8994
{
9095
amTracker.SuppressRelease();
@@ -114,11 +119,13 @@ void InvokeManagedMethod(MethodDesc *pMD, int8_t *pArgs, int8_t *pRet, PCODE tar
114119
CallStubHeader *pHeader = pMD->GetCallStub();
115120
if (pHeader == NULL)
116121
{
117-
pHeader = UpdateCallStubForMethod(pMD);
122+
pHeader = UpdateCallStubForMethod(pMD, target == (PCODE)NULL ? pMD->GetMultiCallableAddrOfCode(CORINFO_ACCESS_ANY) : target);
118123
}
119124

120-
// Interpreter-FIXME: Potential race condition if a single CallStubHeader is reused for multiple targets.
121-
pHeader->SetTarget(target); // The method to call
125+
if (target != (PCODE)NULL)
126+
{
127+
_ASSERTE(pHeader->GetTarget() == target);
128+
}
122129

123130
pHeader->Invoke(pHeader->Routines, pArgs, pRet, pHeader->TotalStackSize);
124131
}
@@ -143,7 +150,7 @@ void InvokeDelegateInvokeMethod(MethodDesc *pMDDelegateInvoke, int8_t *pArgs, in
143150
CallStubHeader *stubHeaderTemplate = pMDDelegateInvoke->GetCallStub();
144151
if (stubHeaderTemplate == NULL)
145152
{
146-
stubHeaderTemplate = UpdateCallStubForMethod(pMDDelegateInvoke);
153+
stubHeaderTemplate = UpdateCallStubForMethod(pMDDelegateInvoke, (PCODE)NULL);
147154
}
148155

149156
// CallStubHeaders encode their destination addresses in the Routines array, so they need to be
@@ -627,7 +634,9 @@ static void CallPreStub(MethodDesc *pMD)
627634
STATIC_STANDARD_VM_CONTRACT;
628635
_ASSERTE(pMD != NULL);
629636

630-
if (!pMD->IsPointingToPrestub())
637+
if (!pMD->IsPointingToPrestub() &&
638+
pMD->GetTemporaryEntryPoint() && // The prestub may not yet be ready to be used, so force temporary entry point creation, and check again.
639+
!pMD->IsPointingToPrestub())
631640
return;
632641

633642
struct Param
@@ -2440,6 +2449,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
24402449
InterpByteCodeStart* targetIp = targetMethod->GetInterpreterCode();
24412450
if (targetIp == NULL)
24422451
{
2452+
if (!targetMethod->IsInterpreterCodePoisoned())
24432453
{
24442454
// This is an optimization to ensure that the stack walk will not have to search
24452455
// for the topmost frame in the current InterpExecMethod. It is not required
@@ -2453,12 +2463,17 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
24532463
CallPreStub(targetMethod);
24542464

24552465
targetIp = targetMethod->GetInterpreterCode();
2466+
if (targetIp == NULL)
2467+
{
2468+
// The prestub wasn't able to setup an interpreter code, so it will never be able to.
2469+
targetMethod->PoisonInterpreterCode();
2470+
}
24562471
}
24572472
if (targetIp == NULL)
24582473
{
24592474
// If we didn't get the interpreter code pointer setup, then this is a method we need to invoke as a compiled method.
24602475
// Interpreter-FIXME: Implement tailcall via helpers, see https://github.com/dotnet/runtime/blob/main/docs/design/features/tailcalls-with-helpers.md
2461-
InvokeManagedMethod(targetMethod, callArgsAddress, returnValueAddress, targetMethod->GetMultiCallableAddrOfCode(CORINFO_ACCESS_ANY));
2476+
InvokeManagedMethod(targetMethod, callArgsAddress, returnValueAddress, (PCODE)NULL);
24622477
break;
24632478
}
24642479
}

src/coreclr/vm/method.hpp

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1805,18 +1805,37 @@ class MethodDesc
18051805
WORD m_wFlags; // See MethodDescFlags
18061806
PTR_MethodDescCodeData m_codeData;
18071807
#ifdef FEATURE_INTERPRETER
1808+
#define INTERPRETER_CODE_POISON 1
18081809
PTR_InterpByteCodeStart m_interpreterCode;
18091810
public:
18101811
const PTR_InterpByteCodeStart GetInterpreterCode() const
18111812
{
18121813
LIMITED_METHOD_DAC_CONTRACT;
1813-
return m_interpreterCode;
1814+
PTR_InterpByteCodeStart interpreterCode = VolatileLoadWithoutBarrier(&m_interpreterCode);
1815+
if (dac_cast<TADDR>(interpreterCode) == INTERPRETER_CODE_POISON)
1816+
return NULL;
1817+
return interpreterCode;
18141818
}
18151819
void SetInterpreterCode(PTR_InterpByteCodeStart interpreterCode)
18161820
{
18171821
LIMITED_METHOD_CONTRACT;
1822+
_ASSERTE(dac_cast<TADDR>(m_interpreterCode) != INTERPRETER_CODE_POISON);
18181823
VolatileStore(&m_interpreterCode, interpreterCode);
18191824
}
1825+
1826+
// Call this if the m_interpreterCode will never be set to a valid value
1827+
void PoisonInterpreterCode()
1828+
{
1829+
LIMITED_METHOD_CONTRACT;
1830+
VolatileStore(&m_interpreterCode, dac_cast<PTR_InterpByteCodeStart>((TADDR)INTERPRETER_CODE_POISON));
1831+
}
1832+
1833+
bool IsInterpreterCodePoisoned() const
1834+
{
1835+
LIMITED_METHOD_DAC_CONTRACT;
1836+
return dac_cast<TADDR>(VolatileLoadWithoutBarrier(&m_interpreterCode)) == INTERPRETER_CODE_POISON;
1837+
}
1838+
18201839
void ClearInterpreterCodePointer()
18211840
{
18221841
LIMITED_METHOD_CONTRACT;

src/coreclr/vm/wasm/helpers.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -802,7 +802,7 @@ void InvokeManagedMethod(MethodDesc *pMD, int8_t *pArgs, int8_t *pRet, PCODE tar
802802

803803
_ASSERTE(cookie != NULL);
804804

805-
InvokeCalliStub(target, cookie, pArgs, pRet);
805+
InvokeCalliStub(target == NULL ? pMD->GetMultiCallableAddrOfCode(CORINFO_ACCESS_ANY) : target, cookie, pArgs, pRet);
806806
}
807807

808808
void InvokeUnmanagedMethod(MethodDesc *targetMethod, int8_t *pArgs, int8_t *pRet, PCODE callTarget)

0 commit comments

Comments
 (0)