Skip to content

Commit 4573751

Browse files
authored
Fix GC hole with collectible assemblies and tailcalls (#51315)
We must take special care to keep the tailcall dispatcher targets alive while tailcalls are in-flight. In particular, given the following callstack: [B]M2() [SPC]DispatchTailCalls [A]M() it could happen that [B]M2() queued a tail call to a function [B]M3(). Since there is a live dispatcher on the call stack, this would result in [B]M2() storing a function pointer pointing to [B]M3() and returning to this dispatcher to let it take care of the tailcall. If B was loaded in a collectible ALC, it would then be possible for there to be nothing keeping this ALC alive, and for the assembly to be unloaded before the dispatcher invoked the function pointer. I was unable to come up with a test case where this happened without making changes to the dispatcher; the window otherwise seems to be too small. To reproduce the problem I thus had to add a Thread.Sleep(50) into the dispatcher, which quickly resulted in an AccessViolationException in the scenario above. With the changes in this commit I was then no logner able to reproduce the problem. Fix #41314
1 parent 6895217 commit 4573751

File tree

3 files changed

+34
-11
lines changed

3 files changed

+34
-11
lines changed

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ public static IntPtr AllocateTypeAssociatedMemory(Type type, int size)
316316
[StackTraceHidden]
317317
private static unsafe void DispatchTailCalls(
318318
IntPtr callersRetAddrSlot,
319-
delegate*<IntPtr, IntPtr, IntPtr*, void> callTarget,
319+
delegate*<IntPtr, IntPtr, PortableTailCallFrame*, void> callTarget,
320320
IntPtr retVal)
321321
{
322322
IntPtr callersRetAddr;
@@ -330,15 +330,17 @@ private static unsafe void DispatchTailCalls(
330330

331331
PortableTailCallFrame newFrame;
332332
newFrame.Prev = prevFrame;
333+
// GC uses NextCall to keep LoaderAllocator alive after we link it below,
334+
// so we must null it out before that.
335+
newFrame.NextCall = null;
333336

334337
try
335338
{
336339
tls->Frame = &newFrame;
337340

338341
do
339342
{
340-
newFrame.NextCall = null;
341-
callTarget(tls->ArgBuffer, retVal, &newFrame.TailCallAwareReturnAddress);
343+
callTarget(tls->ArgBuffer, retVal, &newFrame);
342344
callTarget = newFrame.NextCall;
343345
} while (callTarget != null);
344346
}
@@ -501,7 +503,7 @@ internal unsafe struct PortableTailCallFrame
501503
{
502504
public PortableTailCallFrame* Prev;
503505
public IntPtr TailCallAwareReturnAddress;
504-
public delegate*<IntPtr, IntPtr, IntPtr*, void> NextCall;
506+
public delegate*<IntPtr, IntPtr, PortableTailCallFrame*, void> NextCall;
505507
}
506508

507509
[StructLayout(LayoutKind.Sequential)]

src/coreclr/vm/gcenv.ee.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,22 @@ static void ScanStackRoots(Thread * pThread, promote_func* fn, ScanContext* sc)
157157

158158
static void ScanTailCallArgBufferRoots(Thread* pThread, promote_func* fn, ScanContext* sc)
159159
{
160-
TailCallArgBuffer* argBuffer = pThread->GetTailCallTls()->GetArgBuffer();
160+
TailCallTls* tls = pThread->GetTailCallTls();
161+
// Keep loader associated with CallTailCallTarget alive.
162+
if (sc->promotion)
163+
{
164+
#ifndef DACCESS_COMPILE
165+
const PortableTailCallFrame* frame = tls->GetFrame();
166+
if (frame->NextCall != NULL)
167+
{
168+
MethodDesc* pMD = Entry2MethodDesc((PCODE)frame->NextCall, NULL);
169+
if (pMD != NULL)
170+
GcReportLoaderAllocator(fn, sc, pMD->GetLoaderAllocator());
171+
}
172+
#endif
173+
}
174+
175+
TailCallArgBuffer* argBuffer = tls->GetArgBuffer();
161176
if (argBuffer == NULL || argBuffer->GCDesc == NULL)
162177
return;
163178

src/coreclr/vm/tailcallhelp.cpp

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -465,10 +465,10 @@ MethodDesc* TailCallHelp::CreateCallTargetStub(const TailCallInfo& info)
465465

466466
ILCodeStream* pCode = sl.NewCodeStream(ILStubLinker::kDispatch);
467467

468-
// void CallTarget(void* argBuffer, void* retVal, void** pTailCallAwareRetAddress)
468+
// void CallTarget(void* argBuffer, void* retVal, PortableTailCallFrame* pFrame)
469469
const int ARG_ARG_BUFFER = 0;
470470
const int ARG_RET_VAL = 1;
471-
const int ARG_PTR_TAILCALL_AWARE_RET_ADDR = 2;
471+
const int ARG_PTR_FRAME = 2;
472472

473473
auto emitOffs = [&](UINT offs)
474474
{
@@ -477,10 +477,16 @@ MethodDesc* TailCallHelp::CreateCallTargetStub(const TailCallInfo& info)
477477
pCode->EmitADD();
478478
};
479479

480-
// *pTailCallAwareRetAddr = NextCallReturnAddress();
481-
pCode->EmitLDARG(ARG_PTR_TAILCALL_AWARE_RET_ADDR);
480+
// pFrame->NextCall = 0;
481+
pCode->EmitLDARG(ARG_PTR_FRAME);
482+
pCode->EmitLDC(0);
483+
pCode->EmitCONV_U();
484+
pCode->EmitSTFLD(FIELD__PORTABLE_TAIL_CALL_FRAME__NEXT_CALL);
485+
486+
// pFrame->TailCallAwareReturnAddress = NextCallReturnAddress();
487+
pCode->EmitLDARG(ARG_PTR_FRAME);
482488
pCode->EmitCALL(METHOD__STUBHELPERS__NEXT_CALL_RETURN_ADDRESS, 0, 1);
483-
pCode->EmitSTIND_I();
489+
pCode->EmitSTFLD(FIELD__PORTABLE_TAIL_CALL_FRAME__TAILCALL_AWARE_RETURN_ADDRESS);
484490

485491
for (COUNT_T i = 0; i < info.ArgBufLayout.Values.GetCount(); i++)
486492
{
@@ -613,7 +619,7 @@ void TailCallHelp::CreateCallTargetStubSig(const TailCallInfo& info, SigBuilder*
613619
// Return value
614620
sig->AppendElementType(ELEMENT_TYPE_I);
615621

616-
// Pointer to tail call aware return address
622+
// Pointer to tail call frame
617623
sig->AppendElementType(ELEMENT_TYPE_I);
618624

619625
#ifdef _DEBUG

0 commit comments

Comments
 (0)