Skip to content

Commit dc3a6ac

Browse files
authored
[NativeAOT] Asynchronous thread suspension at GC safe points on Windows (#70316)
* simple case works * FEATURE_SUSPEND_REDIRECTION * AVX support * refactor * no need for asm * ARM64 * stub `IsSafePoint` on Unix * revert unnecessary changes to RhpGcPoll2 * no copy of redirect context * Specialcase active frames in EnumGcRefs * remove bogus assert.
1 parent e539d5f commit dc3a6ac

15 files changed

+445
-39
lines changed

src/coreclr/nativeaot/Runtime/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,10 @@ add_definitions(-D_LIB)
245245
if(WIN32)
246246
add_definitions(-DFEATURE_ETW)
247247
add_definitions(-DFEATURE_EVENT_TRACE)
248+
# redirection on ARM64 should work, but needs to be tested
249+
if (CLR_CMAKE_TARGET_ARCH_AMD64)
250+
add_definitions(-DFEATURE_SUSPEND_REDIRECTION)
251+
endif()
248252
else()
249253
add_definitions(-DNO_UI_ASSERT)
250254
include(unix/configure.cmake)

src/coreclr/nativeaot/Runtime/ICodeManager.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ enum class AssociatedDataFlags : unsigned char
139139
class ICodeManager
140140
{
141141
public:
142+
virtual bool IsSafePoint(PTR_VOID pvAddress) = 0;
143+
142144
virtual bool FindMethodInfo(PTR_VOID ControlPC,
143145
MethodInfo * pMethodInfoOut) = 0;
144146

@@ -150,7 +152,8 @@ class ICodeManager
150152
virtual void EnumGcRefs(MethodInfo * pMethodInfo,
151153
PTR_VOID safePointAddress,
152154
REGDISPLAY * pRegisterSet,
153-
GCEnumContext * hCallback) = 0;
155+
GCEnumContext * hCallback,
156+
bool isActiveStackFrame) = 0;
154157

155158
virtual bool UnwindStackFrame(MethodInfo * pMethodInfo,
156159
REGDISPLAY * pRegisterSet, // in/out

src/coreclr/nativeaot/Runtime/PalRedhawk.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,7 @@ typedef struct DECLSPEC_ALIGN(16) _CONTEXT {
405405
void SetArg1Reg(uintptr_t val) { X1 = val; }
406406
uintptr_t GetIp() { return Pc; }
407407
uintptr_t GetLr() { return Lr; }
408+
uintptr_t GetSp() { return Sp; }
408409
} CONTEXT, *PCONTEXT;
409410

410411
#elif defined(HOST_WASM)
@@ -590,6 +591,11 @@ REDHAWK_PALIMPORT bool REDHAWK_PALAPI PalInit();
590591
// Given the OS handle of a loaded module, compute the upper and lower virtual address bounds (inclusive).
591592
REDHAWK_PALIMPORT void REDHAWK_PALAPI PalGetModuleBounds(HANDLE hOsHandle, _Out_ uint8_t ** ppLowerBound, _Out_ uint8_t ** ppUpperBound);
592593

594+
REDHAWK_PALIMPORT CONTEXT* PalAllocateCompleteOSContext(_Out_ uint8_t** contextBuffer);
595+
REDHAWK_PALIMPORT bool REDHAWK_PALAPI PalGetCompleteThreadContext(HANDLE hThread, _Out_ CONTEXT * pCtx);
596+
REDHAWK_PALIMPORT bool REDHAWK_PALAPI PalSetThreadContext(HANDLE hThread, _Out_ CONTEXT * pCtx);
597+
REDHAWK_PALIMPORT void REDHAWK_PALAPI PalRestoreContext(CONTEXT * pCtx);
598+
593599
REDHAWK_PALIMPORT bool REDHAWK_PALAPI PalGetThreadContext(HANDLE hThread, _Out_ PAL_LIMITED_CONTEXT * pCtx);
594600

595601
REDHAWK_PALIMPORT int32_t REDHAWK_PALAPI PalGetProcessCpuCount();

src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp

Lines changed: 125 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,18 @@ StackFrameIterator::StackFrameIterator(Thread * pThreadToWalk, PInvokeTransition
9292
{
9393
STRESS_LOG0(LF_STACKWALK, LL_INFO10000, "----Init---- [ GC ]\n");
9494
ASSERT(!pThreadToWalk->DangerousCrossThreadIsHijacked());
95-
InternalInit(pThreadToWalk, pInitialTransitionFrame, GcStackWalkFlags);
95+
96+
#ifdef FEATURE_SUSPEND_REDIRECTION
97+
if (pInitialTransitionFrame == REDIRECTED_THREAD_MARKER)
98+
{
99+
InternalInit(pThreadToWalk, pThreadToWalk->GetRedirectionContext(), GcStackWalkFlags | ActiveStackFrame);
100+
}
101+
else
102+
#endif
103+
{
104+
InternalInit(pThreadToWalk, pInitialTransitionFrame, GcStackWalkFlags);
105+
}
106+
96107
PrepareToYieldFrame();
97108
}
98109

@@ -497,6 +508,112 @@ void StackFrameIterator::InternalInit(Thread * pThreadToWalk, PTR_PAL_LIMITED_CO
497508
#endif // TARGET_ARM
498509
}
499510

511+
// Prepare to start a stack walk from the context listed in the supplied CONTEXT.
512+
// The supplied context can describe a location in either managed or unmanaged code. In the
513+
// latter case the iterator is left in an invalid state when this function returns.
514+
void StackFrameIterator::InternalInit(Thread * pThreadToWalk, CONTEXT* pCtx, uint32_t dwFlags)
515+
{
516+
ASSERT((dwFlags & MethodStateCalculated) == 0);
517+
518+
EnterInitialInvalidState(pThreadToWalk);
519+
520+
m_dwFlags = dwFlags;
521+
522+
// We need to walk the ExInfo chain in parallel with the stackwalk so that we know when we cross over
523+
// exception throw points. So we must find our initial point in the ExInfo chain here so that we can
524+
// properly walk it in parallel.
525+
ResetNextExInfoForSP(pCtx->GetSp());
526+
527+
// This codepath is used by the hijack stackwalk and we can get arbitrary ControlPCs from there. If this
528+
// context has a non-managed control PC, then we're done.
529+
if (!m_pInstance->IsManaged(dac_cast<PTR_VOID>(pCtx->GetIp())))
530+
return;
531+
532+
//
533+
// control state
534+
//
535+
SetControlPC(dac_cast<PTR_VOID>(pCtx->GetIp()));
536+
m_RegDisplay.SP = pCtx->GetSp();
537+
m_RegDisplay.IP = pCtx->GetIp();
538+
539+
#ifdef TARGET_ARM64
540+
541+
m_RegDisplay.pIP = PTR_TO_MEMBER(CONTEXT, pCtx, Pc);
542+
543+
//
544+
// preserved regs
545+
//
546+
m_RegDisplay.pX19 = PTR_TO_MEMBER(CONTEXT, pCtx, X19);
547+
m_RegDisplay.pX20 = PTR_TO_MEMBER(CONTEXT, pCtx, X20);
548+
m_RegDisplay.pX21 = PTR_TO_MEMBER(CONTEXT, pCtx, X21);
549+
m_RegDisplay.pX22 = PTR_TO_MEMBER(CONTEXT, pCtx, X22);
550+
m_RegDisplay.pX23 = PTR_TO_MEMBER(CONTEXT, pCtx, X23);
551+
m_RegDisplay.pX24 = PTR_TO_MEMBER(CONTEXT, pCtx, X24);
552+
m_RegDisplay.pX25 = PTR_TO_MEMBER(CONTEXT, pCtx, X25);
553+
m_RegDisplay.pX26 = PTR_TO_MEMBER(CONTEXT, pCtx, X26);
554+
m_RegDisplay.pX27 = PTR_TO_MEMBER(CONTEXT, pCtx, X27);
555+
m_RegDisplay.pX28 = PTR_TO_MEMBER(CONTEXT, pCtx, X28);
556+
m_RegDisplay.pFP = PTR_TO_MEMBER(CONTEXT, pCtx, Fp);
557+
m_RegDisplay.pLR = PTR_TO_MEMBER(CONTEXT, pCtx, Lr);
558+
559+
//
560+
// scratch regs
561+
//
562+
m_RegDisplay.pX0 = PTR_TO_MEMBER(CONTEXT, pCtx, X0);
563+
m_RegDisplay.pX1 = PTR_TO_MEMBER(CONTEXT, pCtx, X1);
564+
m_RegDisplay.pX2 = PTR_TO_MEMBER(CONTEXT, pCtx, X2);
565+
m_RegDisplay.pX3 = PTR_TO_MEMBER(CONTEXT, pCtx, X3);
566+
m_RegDisplay.pX4 = PTR_TO_MEMBER(CONTEXT, pCtx, X4);
567+
m_RegDisplay.pX5 = PTR_TO_MEMBER(CONTEXT, pCtx, X5);
568+
m_RegDisplay.pX6 = PTR_TO_MEMBER(CONTEXT, pCtx, X6);
569+
m_RegDisplay.pX7 = PTR_TO_MEMBER(CONTEXT, pCtx, X7);
570+
m_RegDisplay.pX8 = PTR_TO_MEMBER(CONTEXT, pCtx, X8);
571+
m_RegDisplay.pX9 = PTR_TO_MEMBER(CONTEXT, pCtx, X9);
572+
m_RegDisplay.pX10 = PTR_TO_MEMBER(CONTEXT, pCtx, X10);
573+
m_RegDisplay.pX11 = PTR_TO_MEMBER(CONTEXT, pCtx, X11);
574+
m_RegDisplay.pX12 = PTR_TO_MEMBER(CONTEXT, pCtx, X12);
575+
m_RegDisplay.pX13 = PTR_TO_MEMBER(CONTEXT, pCtx, X13);
576+
m_RegDisplay.pX14 = PTR_TO_MEMBER(CONTEXT, pCtx, X14);
577+
m_RegDisplay.pX15 = PTR_TO_MEMBER(CONTEXT, pCtx, X15);
578+
m_RegDisplay.pX16 = PTR_TO_MEMBER(CONTEXT, pCtx, X16);
579+
m_RegDisplay.pX17 = PTR_TO_MEMBER(CONTEXT, pCtx, X17);
580+
m_RegDisplay.pX18 = PTR_TO_MEMBER(CONTEXT, pCtx, X18);
581+
582+
#elif defined(TARGET_X86) || defined(TARGET_AMD64)
583+
584+
m_RegDisplay.pIP = (PTR_PCODE)PTR_TO_MEMBER(CONTEXT, pCtx, Rip);
585+
586+
//
587+
// preserved regs
588+
//
589+
m_RegDisplay.pRbp = (PTR_UIntNative)PTR_TO_MEMBER(CONTEXT, pCtx, Rbp);
590+
m_RegDisplay.pRsi = (PTR_UIntNative)PTR_TO_MEMBER(CONTEXT, pCtx, Rsi);
591+
m_RegDisplay.pRdi = (PTR_UIntNative)PTR_TO_MEMBER(CONTEXT, pCtx, Rdi);
592+
m_RegDisplay.pRbx = (PTR_UIntNative)PTR_TO_MEMBER(CONTEXT, pCtx, Rbx);
593+
#ifdef TARGET_AMD64
594+
m_RegDisplay.pR12 = (PTR_UIntNative)PTR_TO_MEMBER(CONTEXT, pCtx, R12);
595+
m_RegDisplay.pR13 = (PTR_UIntNative)PTR_TO_MEMBER(CONTEXT, pCtx, R13);
596+
m_RegDisplay.pR14 = (PTR_UIntNative)PTR_TO_MEMBER(CONTEXT, pCtx, R14);
597+
m_RegDisplay.pR15 = (PTR_UIntNative)PTR_TO_MEMBER(CONTEXT, pCtx, R15);
598+
#endif // TARGET_AMD64
599+
600+
//
601+
// scratch regs
602+
//
603+
m_RegDisplay.pRax = (PTR_UIntNative)PTR_TO_MEMBER(CONTEXT, pCtx, Rax);
604+
m_RegDisplay.pRcx = (PTR_UIntNative)PTR_TO_MEMBER(CONTEXT, pCtx, Rcx);
605+
m_RegDisplay.pRdx = (PTR_UIntNative)PTR_TO_MEMBER(CONTEXT, pCtx, Rdx);
606+
#ifdef TARGET_AMD64
607+
m_RegDisplay.pR8 = (PTR_UIntNative)PTR_TO_MEMBER(CONTEXT, pCtx, R8);
608+
m_RegDisplay.pR9 = (PTR_UIntNative)PTR_TO_MEMBER(CONTEXT, pCtx, R9);
609+
m_RegDisplay.pR10 = (PTR_UIntNative)PTR_TO_MEMBER(CONTEXT, pCtx, R10);
610+
m_RegDisplay.pR11 = (PTR_UIntNative)PTR_TO_MEMBER(CONTEXT, pCtx, R11);
611+
#endif // TARGET_AMD64
612+
#else
613+
PORTABILITY_ASSERT("StackFrameIterator::InternalInit");
614+
#endif // TARGET_ARM
615+
}
616+
500617
PTR_VOID StackFrameIterator::HandleExCollide(PTR_ExInfo pExInfo)
501618
{
502619
STRESS_LOG3(LF_STACKWALK, LL_INFO10000, " [ ex collide ] kind = %d, pass = %d, idxCurClause = %d\n",
@@ -1290,7 +1407,7 @@ void StackFrameIterator::NextInternal()
12901407
{
12911408
UnwindOutOfCurrentManagedFrame:
12921409
ASSERT(m_dwFlags & MethodStateCalculated);
1293-
m_dwFlags &= ~(ExCollide|MethodStateCalculated|UnwoundReversePInvoke);
1410+
m_dwFlags &= ~(ExCollide|MethodStateCalculated|UnwoundReversePInvoke|ActiveStackFrame);
12941411
ASSERT(IsValid());
12951412

12961413
m_pHijackedReturnValue = NULL;
@@ -1640,6 +1757,12 @@ MethodInfo * StackFrameIterator::GetMethodInfo()
16401757
return &m_methodInfo;
16411758
}
16421759

1760+
bool StackFrameIterator::IsActiveStackFrame()
1761+
{
1762+
ASSERT(IsValid());
1763+
return (m_dwFlags & ActiveStackFrame) != 0;
1764+
}
1765+
16431766
#ifdef DACCESS_COMPILE
16441767
#define FAILFAST_OR_DAC_RETURN_FALSE(x) if(!(x)) return false;
16451768
#else

src/coreclr/nativeaot/Runtime/StackFrameIterator.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,14 @@ class StackFrameIterator
3636
StackFrameIterator(Thread * pThreadToWalk, PInvokeTransitionFrame* pInitialTransitionFrame);
3737
StackFrameIterator(Thread * pThreadToWalk, PTR_PAL_LIMITED_CONTEXT pCtx);
3838

39-
4039
bool IsValid();
4140
void CalculateCurrentMethodState();
4241
void Next();
4342
PTR_VOID GetEffectiveSafePointAddress();
4443
REGDISPLAY * GetRegisterSet();
4544
PTR_ICodeManager GetCodeManager();
4645
MethodInfo * GetMethodInfo();
46+
bool IsActiveStackFrame();
4747
bool GetHijackedReturnValueLocation(PTR_RtuObjectRef * pLocation, GCRefKind * pKind);
4848
void SetControlPC(PTR_VOID controlPC);
4949

@@ -82,6 +82,8 @@ class StackFrameIterator
8282

8383
void InternalInit(Thread * pThreadToWalk, PTR_PInvokeTransitionFrame pFrame, uint32_t dwFlags); // GC stackwalk
8484
void InternalInit(Thread * pThreadToWalk, PTR_PAL_LIMITED_CONTEXT pCtx, uint32_t dwFlags); // EH and hijack stackwalk, and collided unwind
85+
void InternalInit(Thread * pThreadToWalk, CONTEXT* pCtx, uint32_t dwFlags); // GC stackwalk of redirected thread
86+
8587
void InternalInitForEH(Thread * pThreadToWalk, PAL_LIMITED_CONTEXT * pCtx, bool instructionFault); // EH stackwalk
8688
void InternalInitForStackTrace(); // Environment.StackTrace
8789

@@ -137,6 +139,9 @@ class StackFrameIterator
137139
// This is a state returned by Next() which indicates that we just unwound a reverse pinvoke method
138140
UnwoundReversePInvoke = 0x20,
139141

142+
// The thread was interrupted in the current frame at the current IP by a signal, SuspendThread or similar.
143+
ActiveStackFrame = 0x40,
144+
140145
GcStackWalkFlags = (CollapseFunclets | RemapHardwareFaultsToSafePoint),
141146
EHStackWalkFlags = ApplyReturnAddressAdjustment,
142147
StackTraceStackWalkFlags = GcStackWalkFlags

src/coreclr/nativeaot/Runtime/arm64/GcProbe.asm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
EXTERN RhpGcPoll2
99
EXTERN g_fGcStressStarted
1010

11-
PROBE_SAVE_FLAGS_EVERYTHING equ DEFAULT_FRAME_SAVE_FLAGS + PTFF_SAVE_ALL_SCRATCH
11+
PROBE_SAVE_FLAGS_EVERYTHING equ DEFAULT_FRAME_SAVE_FLAGS + PTFF_SAVE_ALL_SCRATCH + PTFF_SAVE_LR
1212

1313
;; Build a map of symbols representing offsets into the transition frame (see PInvokeTransitionFrame in
1414
;; rhbinder.h) and keep these two in sync.

src/coreclr/nativeaot/Runtime/gcrhenv.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,8 @@ void RedhawkGCInterface::EnumGcRefs(ICodeManager * pCodeManager,
515515
PTR_VOID safePointAddress,
516516
REGDISPLAY * pRegisterSet,
517517
void * pfnEnumCallback,
518-
void * pvCallbackData)
518+
void * pvCallbackData,
519+
bool isActiveStackFrame)
519520
{
520521
EnumGcRefContext ctx;
521522
ctx.pCallback = EnumGcRefsCallback;
@@ -526,7 +527,8 @@ void RedhawkGCInterface::EnumGcRefs(ICodeManager * pCodeManager,
526527
pCodeManager->EnumGcRefs(pMethodInfo,
527528
safePointAddress,
528529
pRegisterSet,
529-
&ctx);
530+
&ctx,
531+
isActiveStackFrame);
530532
}
531533

532534
// static

src/coreclr/nativeaot/Runtime/gcrhinterface.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ class RedhawkGCInterface
120120
PTR_VOID safePointAddress,
121121
REGDISPLAY * pRegisterSet,
122122
void * pfnEnumCallback,
123-
void * pvCallbackData);
123+
void * pvCallbackData,
124+
bool isActiveStackFrame);
124125

125126
static void EnumGcRefsInRegionConservatively(PTR_RtuObjectRef pLowerBound,
126127
PTR_RtuObjectRef pUpperBound,

0 commit comments

Comments
 (0)