Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions src/coreclr/jit/fgwasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3334,8 +3334,6 @@ void Compiler::fgWasmEhTransformTry(ArrayStack<BasicBlock*>* catchRetBlocks,
//
BlockToBlockMap resumePads(getAllocator(CMK_FlowEdge));
BlockToFlowEdgeMap continuationEdges(getAllocator(CMK_FlowEdge));
bool const verifyGCModeTransitions =
IsReadyToRun() && opts.jitFlags->IsSet(JitFlags::JIT_FLAG_VERIFY_GC_MODE_TRANSITIONS);

for (BasicBlock* const catchRetBlock : catchRetBlocks->TopDownOrder())
{
Expand Down Expand Up @@ -3385,10 +3383,13 @@ void Compiler::fgWasmEhTransformTry(ArrayStack<BasicBlock*>* catchRetBlocks,
LIR::Range range = LIR::SeqTree(this, store);
LIR::AsRange(resumePad).InsertAtEnd(std::move(range));

if (verifyGCModeTransitions)
if (IsReadyToRun())
{
// Reaching this pad means this dispatcher accepted the resumption. A nonmatching
// inner dispatcher takes the rethrow edge and must leave transitions forbidden.
// Reaching this pad means this dispatcher accepted the resumption, so tell the runtime
// that managed code runs again. When permitted, the helper raises a thread abort that is
// still pending at the end of the catch. It also lifts the restriction on GC mode switches
// for images built with --verify-gc-mode-transitions. A nonmatching inner dispatcher takes
// the rethrow edge and must leave transitions forbidden.
GenTree* resumeAfterCatch = gtNewHelperCallNode(CORINFO_HELP_JIT_RESUME_AFTER_CATCH, TYP_VOID);
resumeAfterCatch = fgMorphCall(resumeAfterCatch->AsCall());
gtSetEvalOrder(resumeAfterCatch);
Expand Down
7 changes: 7 additions & 0 deletions src/coreclr/vm/eetwain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1804,11 +1804,18 @@ void EECodeManager::ResumeAfterCatch(CONTEXT *pContext, size_t targetSSP, bool f
Thread *pThread = GetThread();
DWORD_PTR dwResumePC = GetIP(pContext);
UINT_PTR uAbortAddr = 0;
#ifndef TARGET_WASM
if (!fIntercepted)
{
CopyOSContext(pThread->m_OSContext, pContext);
uAbortAddr = (UINT_PTR)COMPlusCheckForAbort(dwResumePC);
}
#else // !TARGET_WASM
// On Wasm, the IP in pContext is not the address of the code that resumes: the frame being
// resumed selects its continuation itself, and RtlRestoreContext does not use the context. So a
// pending thread abort can neither be checked for with this context nor be redirected to. The
// resumed code raises it instead, from JIT_ResumeAfterCatch.
#endif // !TARGET_WASM

if (uAbortAddr)
{
Expand Down
51 changes: 50 additions & 1 deletion src/coreclr/vm/jithelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2191,9 +2191,45 @@ Thread * JIT_InitPInvokeFrame(InlinedCallFrame *pFrame)
EXTERN_C void JIT_PInvokeBegin(InlinedCallFrame* pFrame);
EXTERN_C void JIT_PInvokeEnd(InlinedCallFrame* pFrame);

#ifdef TARGET_WASM
// Raises a thread abort that is still pending when compiled code resumes after a catch. On other
// targets, EECodeManager::ResumeAfterCatch does this by redirecting the resumption to
// THROW_CONTROL_FOR_THREAD_FUNCTION. On Wasm it cannot: the frame being resumed selects its
// continuation itself, and RtlRestoreContext does not use the context it is given. Instead, the
// frame calls JIT_ResumeAfterCatch on its way to the continuation. The virtual IP that the frame has
// stored is then that of the region it resumes in, so the transition block describes the place where
// execution resumes.
static void RaiseThreadAbortAfterCatch(TransitionBlock* transitionBlock)
{
FCALL_CONTRACT;

Thread *pThread = GetThread();

SoftwareExceptionFrame exceptionFrame;
exceptionFrame.UpdateContextFromTransitionBlock(transitionBlock);
exceptionFrame.InitAndLink(pThread);

FC_CAN_TRIGGER_GC();

// COMPlusCheckForAbort expects Thread::m_OSContext to describe the place where execution resumes.
CopyOSContext(pThread->m_OSContext, exceptionFrame.GetContext());
if (COMPlusCheckForAbort((UINT_PTR)GetIP(exceptionFrame.GetContext())) != NULL)
{
EEException ex(kThreadAbortException);
OBJECTREF throwable = ex.CreateThrowable();
DispatchManagedException(throwable, exceptionFrame.GetContext());
}

exceptionFrame.Pop(pThread);

FC_CAN_TRIGGER_GC_END();
}
#endif // TARGET_WASM

// Called at a catch resumption point, once the restore-context unwind has completed and managed
// code is about to run again. This does not touch the thread's GC mode; it only lifts the
// code is about to run again. This does not touch the thread's GC mode; it lifts the
// restriction installed before ResumeAfterCatch -- see the comment on t_gcModeSwitchPermitted.
// On Wasm, it also raises a pending thread abort when permitted; see RaiseThreadAbortAfterCatch.
// Only WebAssembly emits a call to this helper, but the definition is unconditional so the helper
// table links everywhere.
EXTERN_C FCDECL0(void, JIT_ResumeAfterCatch);
Expand All @@ -2204,6 +2240,19 @@ FCIMPL0(void, JIT_ResumeAfterCatch)
#ifdef _DEBUG
t_gcModeSwitchPermitted = true;
#endif // _DEBUG

#ifdef TARGET_WASM
// Use the same check as IL_Throw: build a context only when the caller passed a frame of compiled code.
// COMPlusCheckForAbort then decides whether the abort may be raised at that frame's virtual IP;
// it is not raised while the continuation is inside a handler.
if (GetThread()->IsAbortRequested() && (callersStackPointer != 0) && (*(int*)callersStackPointer != TERMINATE_R2R_STACK_WALK))
{
TransitionBlock block;
block.m_ReturnAddress = 0;
block.m_StackPointer = callersStackPointer;
RaiseThreadAbortAfterCatch(&block);
}
#endif // TARGET_WASM
}
FCIMPLEND

Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/vm/threads.h
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ extern thread_local bool t_gcModeSwitchPermitted;
#endif // !DACCESS_COMPILE && _DEBUG

#if defined(TARGET_WASM) && !defined(DACCESS_COMPILE) && defined(_DEBUG)
// True when a catch resuming into the frame at handlerFrameControlPC will call
// True when a catch resuming into the frame at handlerFrameControlPC is known to call
// CORINFO_HELP_JIT_RESUME_AFTER_CATCH, and so is able to lift a GC mode switch restriction
// imposed for the duration of the restore-context unwind. See vm/wasm/helpers.cpp.
bool ResumeTargetVerifiesGCModeTransitions(PCODE handlerFrameControlPC);
Expand Down
8 changes: 4 additions & 4 deletions src/coreclr/vm/wasm/helpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,10 +229,10 @@ extern "C" __attribute__((naked)) void RuntimeAsync_StoreAsyncContinuation(uint3

#ifdef _DEBUG
// Answers whether the code at handlerFrameControlPC lives in a ReadyToRun image compiled with
// --verify-gc-mode-transitions, and so ends its catch resumption points with a call to
// CORINFO_HELP_JIT_RESUME_AFTER_CATCH. Only such an image can lift a GC mode switch restriction
// once managed code resumes; forbidding switches for any other resume target would leave them
// forbidden for the rest of the thread's life.
// --verify-gc-mode-transitions, and so is known to end its catch resumption points with a call to
// CORINFO_HELP_JIT_RESUME_AFTER_CATCH. Only for such an image is it known that the restriction is
// lifted once managed code resumes; forbidding switches for any other resume target could leave
// them forbidden for the rest of the thread's life.
bool ResumeTargetVerifiesGCModeTransitions(PCODE handlerFrameControlPC)
{
LIMITED_METHOD_CONTRACT;
Expand Down
Loading