Skip to content

Commit e1fbcdc

Browse files
authored
Do not reset size of TailCallArgBuffer (#118365)
* TailCallArgBuffer:State is 32-bit int. The code incorrectly used native int operation to set. It lead to TailCallArgBuffer::Size to be reset as well and needlessly re-allocating the tail call buffer. * Switch to preemptive mode around malloc/free in TailCallArgBuffer allocation Move the fast path of the TailCallArgBuffer allocation helper to C# to avoid perf overload of switching to preemptive mode. Also, aggressively inline it to allow zero initialization to be unrolled by the JIT. * Skip early GC reporting de-action if there is nothing to report Fixes #118166
1 parent 0686df9 commit e1fbcdc

File tree

10 files changed

+102
-54
lines changed

10 files changed

+102
-54
lines changed

src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -488,15 +488,41 @@ public static IntPtr AllocateTypeAssociatedMemory(Type type, int size)
488488
private static partial IntPtr AllocateTypeAssociatedMemory(QCallTypeHandle type, uint size);
489489

490490
[MethodImpl(MethodImplOptions.InternalCall)]
491-
private static extern IntPtr AllocTailCallArgBufferWorker(int size, IntPtr gcDesc);
491+
private static extern unsafe TailCallArgBuffer* GetTailCallArgBuffer();
492492

493-
private static IntPtr AllocTailCallArgBuffer(int size, IntPtr gcDesc)
493+
[LibraryImport(QCall, EntryPoint = "TailCallHelp_AllocTailCallArgBufferInternal")]
494+
private static unsafe partial TailCallArgBuffer* AllocTailCallArgBufferInternal(int size);
495+
496+
private const int TAILCALLARGBUFFER_ACTIVE = 0;
497+
// private const int TAILCALLARGBUFFER_INSTARG_ONLY = 1;
498+
private const int TAILCALLARGBUFFER_INACTIVE = 2;
499+
500+
[MethodImpl(MethodImplOptions.AggressiveInlining)] // To allow unrolling of Span.Clear
501+
private static unsafe TailCallArgBuffer* AllocTailCallArgBuffer(int size, IntPtr gcDesc)
494502
{
495-
IntPtr buffer = AllocTailCallArgBufferWorker(size, gcDesc);
496-
if (buffer == IntPtr.Zero)
503+
TailCallArgBuffer* buffer = GetTailCallArgBuffer();
504+
if (buffer != null && buffer->Size >= size)
505+
{
506+
buffer->State = TAILCALLARGBUFFER_INACTIVE;
507+
}
508+
else
497509
{
498-
throw new OutOfMemoryException();
510+
buffer = AllocTailCallArgBufferWorker(size);
511+
512+
[MethodImpl(MethodImplOptions.NoInlining)]
513+
static TailCallArgBuffer* AllocTailCallArgBufferWorker(int size) => AllocTailCallArgBufferInternal(size);
499514
}
515+
Debug.Assert(buffer != null);
516+
Debug.Assert(buffer->Size >= size);
517+
Debug.Assert(buffer->State == TAILCALLARGBUFFER_INACTIVE);
518+
519+
buffer->GCDesc = gcDesc;
520+
521+
new Span<byte>(buffer + 1, size - sizeof(TailCallArgBuffer)).Clear();
522+
523+
// The buffer is now ready to be used.
524+
buffer->State = TAILCALLARGBUFFER_ACTIVE;
525+
500526
return buffer;
501527
}
502528

@@ -506,7 +532,7 @@ private static IntPtr AllocTailCallArgBuffer(int size, IntPtr gcDesc)
506532
[StackTraceHidden]
507533
private static unsafe void DispatchTailCalls(
508534
IntPtr callersRetAddrSlot,
509-
delegate*<IntPtr, ref byte, PortableTailCallFrame*, void> callTarget,
535+
delegate*<TailCallArgBuffer*, ref byte, PortableTailCallFrame*, void> callTarget,
510536
ref byte retVal)
511537
{
512538
IntPtr callersRetAddr;
@@ -537,11 +563,8 @@ private static unsafe void DispatchTailCalls(
537563
{
538564
tls->Frame = prevFrame;
539565

540-
// If the arg buffer is reporting inst argument, it is safe to abandon it now
541-
if (tls->ArgBuffer != IntPtr.Zero && *(int*)tls->ArgBuffer == 1 /* TAILCALLARGBUFFER_INSTARG_ONLY */)
542-
{
543-
*(int*)tls->ArgBuffer = 2 /* TAILCALLARGBUFFER_ABANDONED */;
544-
}
566+
// If the arg buffer is reporting inst argument (TAILCALLARGBUFFER_INSTARG_ONLY), it is safe to abandon it now.
567+
tls->ArgBuffer->State = TAILCALLARGBUFFER_INACTIVE;
545568
}
546569
}
547570

@@ -1217,13 +1240,22 @@ private static bool CanCastToWorker(TypeHandle srcTH, TypeHandle destTH, bool nu
12171240
internal unsafe struct PortableTailCallFrame
12181241
{
12191242
public IntPtr TailCallAwareReturnAddress;
1220-
public delegate*<IntPtr, ref byte, PortableTailCallFrame*, void> NextCall;
1243+
public delegate*<TailCallArgBuffer*, ref byte, PortableTailCallFrame*, void> NextCall;
1244+
}
1245+
1246+
[StructLayout(LayoutKind.Sequential)]
1247+
internal struct TailCallArgBuffer
1248+
{
1249+
public int State;
1250+
public int Size;
1251+
public IntPtr GCDesc;
1252+
// Args
12211253
}
12221254

12231255
[StructLayout(LayoutKind.Sequential)]
12241256
internal unsafe struct TailCallTls
12251257
{
12261258
public PortableTailCallFrame* Frame;
1227-
public IntPtr ArgBuffer;
1259+
public TailCallArgBuffer* ArgBuffer;
12281260
}
12291261
}

src/coreclr/vm/corelib.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -709,7 +709,7 @@ DEFINE_METHOD(RUNTIME_HELPERS, GET_RAW_DATA, GetRawData,
709709
DEFINE_METHOD(RUNTIME_HELPERS, GET_UNINITIALIZED_OBJECT, GetUninitializedObject, SM_Type_RetObj)
710710
DEFINE_METHOD(RUNTIME_HELPERS, ENUM_EQUALS, EnumEquals, NoSig)
711711
DEFINE_METHOD(RUNTIME_HELPERS, ENUM_COMPARE_TO, EnumCompareTo, NoSig)
712-
DEFINE_METHOD(RUNTIME_HELPERS, ALLOC_TAILCALL_ARG_BUFFER, AllocTailCallArgBuffer, SM_Int_IntPtr_RetIntPtr)
712+
DEFINE_METHOD(RUNTIME_HELPERS, ALLOC_TAILCALL_ARG_BUFFER, AllocTailCallArgBuffer, NoSig)
713713
DEFINE_METHOD(RUNTIME_HELPERS, GET_TAILCALL_INFO, GetTailCallInfo, NoSig)
714714
DEFINE_METHOD(RUNTIME_HELPERS, DISPATCH_TAILCALLS, DispatchTailCalls, NoSig)
715715
#ifdef FEATURE_IJW

src/coreclr/vm/ecalllist.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ FCFuncStart(gRuntimeHelpers)
334334
FCFuncElement("TryGetHashCode", ObjectNative::TryGetHashCode)
335335
FCFuncElement("ContentEquals", ObjectNative::ContentEquals)
336336
FCFuncElement("TryEnsureSufficientExecutionStack", ReflectionInvocation::TryEnsureSufficientExecutionStack)
337-
FCFuncElement("AllocTailCallArgBufferWorker", TailCallHelp::AllocTailCallArgBufferWorker)
337+
FCFuncElement("GetTailCallArgBuffer", TailCallHelp::GetTailCallArgBuffer)
338338
FCFuncElement("GetTailCallInfo", TailCallHelp::GetTailCallInfo)
339339
FCFuncEnd()
340340

src/coreclr/vm/gcenv.ee.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ static void ScanTailCallArgBufferRoots(Thread* pThread, promote_func* fn, ScanCo
235235
if (argBuffer == NULL || argBuffer->GCDesc == NULL)
236236
return;
237237

238-
if (argBuffer->State == TAILCALLARGBUFFER_ABANDONED)
238+
if (argBuffer->State == TAILCALLARGBUFFER_INACTIVE)
239239
return;
240240

241241
bool instArgOnly = argBuffer->State == TAILCALLARGBUFFER_INSTARG_ONLY;

src/coreclr/vm/metasig.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,6 @@ DEFINE_METASIG_T(SM(RetMethodBase, _, C(METHOD_BASE)))
330330
DEFINE_METASIG(SM(RetVoid, _, v))
331331
DEFINE_METASIG(SM(Str_IntPtr_Int_RetVoid, s I i, v))
332332
DEFINE_METASIG(SM(Int_RetIntPtr, i, I))
333-
DEFINE_METASIG(SM(Int_IntPtr_RetIntPtr, i I, I))
334333

335334
DEFINE_METASIG_T(SM(DateTime_RetDbl, g(DATE_TIME), d))
336335
DEFINE_METASIG(SM(Dbl_RetLong, d, l))

src/coreclr/vm/qcallentrypoints.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,7 @@ static const Entry s_QCall[] =
548548
DllImportEntry(ThrowInvalidCastException)
549549
DllImportEntry(IsInstanceOf_NoCacheLookup)
550550
DllImportEntry(VersionResilientHashCode_TypeHashCode)
551+
DllImportEntry(TailCallHelp_AllocTailCallArgBufferInternal)
551552
};
552553

553554
const void* QCallResolveDllImport(const char* name)

src/coreclr/vm/tailcallhelp.cpp

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,28 @@
1111
#include "threads.h"
1212

1313

14-
FCIMPL2(void*, TailCallHelp::AllocTailCallArgBufferWorker, INT32 size, void* gcDesc)
14+
FCIMPL0(void*, TailCallHelp::GetTailCallArgBuffer)
1515
{
1616
FCALL_CONTRACT;
17-
_ASSERTE(size >= 0);
18-
return GetThread()->GetTailCallTls()->AllocArgBuffer(size, gcDesc);
17+
return GetThread()->GetTailCallTls()->GetArgBuffer();
1918
}
2019
FCIMPLEND
2120

21+
extern "C" void* QCALLTYPE TailCallHelp_AllocTailCallArgBufferInternal(int size)
22+
{
23+
QCALL_CONTRACT;
24+
25+
void* retVal = NULL;
26+
27+
BEGIN_QCALL;
28+
29+
retVal = GetThread()->GetTailCallTls()->AllocArgBuffer(size);
30+
31+
END_QCALL;
32+
33+
return retVal;
34+
}
35+
2236
FCIMPL2(void*, TailCallHelp::GetTailCallInfo, void** retAddrSlot, void** retAddr)
2337
{
2438
FCALL_CONTRACT;
@@ -30,7 +44,6 @@ FCIMPL2(void*, TailCallHelp::GetTailCallInfo, void** retAddrSlot, void** retAddr
3044
}
3145
FCIMPLEND
3246

33-
3447
struct ArgBufferValue
3548
{
3649
TypeHandle TyHnd;
@@ -486,14 +499,17 @@ MethodDesc* TailCallHelp::CreateCallTargetStub(const TailCallInfo& info)
486499
EmitLoadTyHnd(pCode, arg.TyHnd);
487500
}
488501

489-
// All arguments are loaded on the stack, it is safe to disable the GC reporting of ArgBuffer now.
490-
// This is optimization to avoid extending argument lifetime unnecessarily.
491-
// We still need to report the inst argument of shared generic code to prevent it from being unloaded. The inst
492-
// argument is just a regular IntPtr on the stack. It is safe to stop reporting it only after the target method
493-
// takes over.
494-
pCode->EmitLDARG(ARG_ARG_BUFFER);
495-
pCode->EmitLDC(info.ArgBufLayout.HasInstArg ? TAILCALLARGBUFFER_INSTARG_ONLY : TAILCALLARGBUFFER_ABANDONED);
496-
pCode->EmitSTIND_I();
502+
if (info.HasGCDescriptor)
503+
{
504+
// All arguments are loaded on the stack, it is safe to disable the GC reporting of ArgBuffer now.
505+
// This is optimization to avoid extending argument lifetime unnecessarily.
506+
// We still need to report the inst argument of shared generic code to prevent it from being unloaded. The inst
507+
// argument is just a regular IntPtr on the stack. It is safe to stop reporting it only after the target method
508+
// takes over.
509+
pCode->EmitLDARG(ARG_ARG_BUFFER);
510+
pCode->EmitLDC(info.ArgBufLayout.HasInstArg ? TAILCALLARGBUFFER_INSTARG_ONLY : TAILCALLARGBUFFER_INACTIVE);
511+
pCode->EmitSTIND_I4();
512+
}
497513

498514
int numRetVals = info.CallSiteSig->IsReturnTypeVoid() ? 0 : 1;
499515
// Normally there will not be any target and we just emit a normal

src/coreclr/vm/tailcallhelp.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ struct ArgBufferLayout;
1313
class TailCallHelp
1414
{
1515
public:
16-
static FCDECL2(void*, AllocTailCallArgBufferWorker, INT32, void*);
16+
static FCDECL0(void*, GetTailCallArgBuffer);
1717
static FCDECL2(void*, GetTailCallInfo, void**, void**);
1818

1919
static void CreateTailCallHelperStubs(
@@ -47,4 +47,6 @@ class TailCallHelp
4747
static void* AllocateBlob(LoaderAllocator* alloc, const void* blob, size_t blobLen);
4848
};
4949

50+
extern "C" void* QCALLTYPE TailCallHelp_AllocTailCallArgBufferInternal(int size);
51+
5052
#endif

src/coreclr/vm/threads.cpp

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -81,39 +81,37 @@ Thread* STDCALL GetThreadHelper()
8181
return GetThreadNULLOk();
8282
}
8383

84-
TailCallArgBuffer* TailCallTls::AllocArgBuffer(int size, void* gcDesc)
84+
TailCallArgBuffer* TailCallTls::AllocArgBuffer(int size)
8585
{
86-
CONTRACTL
87-
{
88-
NOTHROW;
89-
GC_NOTRIGGER;
90-
}
91-
CONTRACTL_END
86+
STANDARD_VM_CONTRACT;
9287

9388
_ASSERTE(size >= (int)offsetof(TailCallArgBuffer, Args));
9489

95-
if (m_argBuffer != NULL && m_argBuffer->Size < size)
96-
{
97-
FreeArgBuffer();
98-
}
90+
// Round the size up
91+
size = max(size, (int)(8 * sizeof(void*)));
92+
93+
TailCallArgBuffer* pBuffer = (TailCallArgBuffer*)new BYTE[size];
94+
TailCallArgBuffer* pOldBuffer;
95+
96+
pBuffer->Size = size;
97+
pBuffer->State = TAILCALLARGBUFFER_INACTIVE;
9998

100-
if (m_argBuffer == NULL)
10199
{
102-
m_argBuffer = (TailCallArgBuffer*)new (nothrow) BYTE[size];
103-
if (m_argBuffer == NULL)
104-
return NULL;
105-
m_argBuffer->Size = size;
106-
}
100+
// We need to ensure that the GC does not run while we are switching the arg buffer.
101+
GCX_COOP();
107102

108-
m_argBuffer->State = TAILCALLARGBUFFER_ACTIVE;
103+
pOldBuffer = m_argBuffer;
104+
m_argBuffer = pBuffer;
105+
}
109106

110-
m_argBuffer->GCDesc = gcDesc;
111-
if (gcDesc != NULL)
107+
if (pOldBuffer != NULL)
112108
{
113-
memset(m_argBuffer->Args, 0, size - offsetof(TailCallArgBuffer, Args));
109+
_ASSERTE(pOldBuffer->Size < size);
110+
111+
delete[] (BYTE*)pOldBuffer;
114112
}
115113

116-
return m_argBuffer;
114+
return pBuffer;
117115
}
118116

119117
#if defined (_DEBUG_IMPL)

src/coreclr/vm/threads.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ class Module;
162162
// TailCallArgBuffer states
163163
#define TAILCALLARGBUFFER_ACTIVE 0
164164
#define TAILCALLARGBUFFER_INSTARG_ONLY 1
165-
#define TAILCALLARGBUFFER_ABANDONED 2
165+
#define TAILCALLARGBUFFER_INACTIVE 2
166166

167167
struct TailCallArgBuffer
168168
{
@@ -414,7 +414,7 @@ class TailCallTls
414414

415415
public:
416416
TailCallTls();
417-
TailCallArgBuffer* AllocArgBuffer(int size, void* gcDesc);
417+
TailCallArgBuffer* AllocArgBuffer(int size);
418418
void FreeArgBuffer() { delete[] (BYTE*)m_argBuffer; m_argBuffer = NULL; }
419419
TailCallArgBuffer* GetArgBuffer()
420420
{

0 commit comments

Comments
 (0)