From 71230ed1334e2caea6e1c84e758fdd01a15e8860 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Mon, 3 Aug 2026 12:45:18 +0200 Subject: [PATCH 1/9] JIT: Treat the indicator variable as transition-preserved In OSR functions we recomputed this from the async continuation argument (which itself is transition-preserved). That approach is not sufficient for EnC. Instead just preserve the state across EnC/OSR transitions in the same way as other state. --- src/coreclr/inc/patchpointinfo.h | 19 +++++++++++++ src/coreclr/jit/async.cpp | 45 ++++++++++-------------------- src/coreclr/jit/codegenarm.cpp | 4 +++ src/coreclr/jit/codegenarm64.cpp | 5 ++++ src/coreclr/jit/codegenarmarch.cpp | 7 +++++ src/coreclr/jit/codegenxarch.cpp | 7 +++++ src/coreclr/jit/compiler.cpp | 12 ++++++++ src/coreclr/jit/compiler.h | 4 +-- src/coreclr/jit/lclvars.cpp | 14 ++++++++-- 9 files changed, 80 insertions(+), 37 deletions(-) diff --git a/src/coreclr/inc/patchpointinfo.h b/src/coreclr/inc/patchpointinfo.h index 53bd8ab26d9f4b..1f305226106410 100644 --- a/src/coreclr/inc/patchpointinfo.h +++ b/src/coreclr/inc/patchpointinfo.h @@ -47,6 +47,7 @@ struct PatchpointInfo m_keptAliveThisOffset = -1; m_securityCookieOffset = -1; m_monitorAcquiredOffset = -1; + m_resumedIndicatorOffset = -1; m_asyncThreadObjectOffset = -1; m_asyncExecutionContextOffset = -1; m_asyncSynchronizationContextOffset = -1; @@ -61,6 +62,7 @@ struct PatchpointInfo m_keptAliveThisOffset = original->m_keptAliveThisOffset; m_securityCookieOffset = original->m_securityCookieOffset; m_monitorAcquiredOffset = original->m_monitorAcquiredOffset; + m_resumedIndicatorOffset = original->m_resumedIndicatorOffset; m_asyncThreadObjectOffset = original->m_asyncThreadObjectOffset; m_asyncExecutionContextOffset = original->m_asyncExecutionContextOffset; m_asyncSynchronizationContextOffset = original->m_asyncSynchronizationContextOffset; @@ -153,6 +155,22 @@ struct PatchpointInfo m_monitorAcquiredOffset = offset; } + // Original method FP relative offset for resumed indicator + int32_t ResumedIndicatorOffset() const + { + return m_resumedIndicatorOffset; + } + + bool HasResumedIndicator() const + { + return m_resumedIndicatorOffset != -1; + } + + void SetResumedIndicatorOffset(int32_t offset) + { + m_resumedIndicatorOffset = offset; + } + // Original method FP relative offset for async thread/contexts int32_t AsyncThreadOffset() const { @@ -256,6 +274,7 @@ struct PatchpointInfo int32_t m_keptAliveThisOffset; int32_t m_securityCookieOffset; int32_t m_monitorAcquiredOffset; + int32_t m_resumedIndicatorOffset; int32_t m_asyncThreadObjectOffset; int32_t m_asyncExecutionContextOffset; int32_t m_asyncSynchronizationContextOffset; diff --git a/src/coreclr/jit/async.cpp b/src/coreclr/jit/async.cpp index 6a47dcacc6a77b..1545d357d0d52b 100644 --- a/src/coreclr/jit/async.cpp +++ b/src/coreclr/jit/async.cpp @@ -67,23 +67,22 @@ PhaseStatus Compiler::SaveAsyncContexts() } // Create locals for Thread, ExecutionContext and SynchronizationContext - lvaAsyncThreadObjectVar = lvaGrabTemp(false DEBUGARG("Async Thread")); - lvaGetDesc(lvaAsyncThreadObjectVar)->lvType = TYP_REF; + lvaAsyncThreadObjectVar = lvaGrabTemp(false DEBUGARG("Async Thread")); + lvaAsyncExecutionContextVar = lvaGrabTemp(false DEBUGARG("Async ExecutionContext")); + lvaAsyncSynchronizationContextVar = lvaGrabTemp(false DEBUGARG("Async SynchronizationContext")); + lvaResumedIndicator = lvaGrabTemp(false DEBUGARG("Async Resumed")); - lvaAsyncExecutionContextVar = lvaGrabTemp(false DEBUGARG("Async ExecutionContext")); - lvaGetDesc(lvaAsyncExecutionContextVar)->lvType = TYP_REF; - - lvaAsyncSynchronizationContextVar = lvaGrabTemp(false DEBUGARG("Async SynchronizationContext")); + lvaGetDesc(lvaAsyncThreadObjectVar)->lvType = TYP_REF; + lvaGetDesc(lvaAsyncExecutionContextVar)->lvType = TYP_REF; lvaGetDesc(lvaAsyncSynchronizationContextVar)->lvType = TYP_REF; - - lvaResumedIndicator = lvaGrabTemp(false DEBUGARG("Async Resumed")); - lvaGetDesc(lvaResumedIndicator)->lvType = TYP_UBYTE; + lvaGetDesc(lvaResumedIndicator)->lvType = TYP_I_IMPL; if (opts.IsOSR()) { lvaGetDesc(lvaAsyncThreadObjectVar)->lvIsOSRLocal = true; lvaGetDesc(lvaAsyncExecutionContextVar)->lvIsOSRLocal = true; lvaGetDesc(lvaAsyncSynchronizationContextVar)->lvIsOSRLocal = true; + lvaGetDesc(lvaResumedIndicator)->lvIsOSRLocal = true; } // Create try-fault structure. This is actually a try-finally, but we @@ -168,17 +167,7 @@ PhaseStatus Compiler::SaveAsyncContexts() // Insert CaptureContexts call before the try (keep it before so the // try/finally can be removed if there is no exception side effects). // For OSR, we did this in the tier0 method. - if (opts.IsOSR()) - { - // In the OSR method we compute the initial value of the resumption indicator based on the continuation arg. - GenTree* continuation = gtNewLclVarNode(lvaAsyncContinuationArg, TYP_REF); - GenTree* null = gtNewNull(); - GenTree* contNeNull = gtNewOperNode(GT_NE, TYP_INT, continuation, null); - GenTree* storeIndicator = gtNewStoreLclVarNode(lvaResumedIndicator, contNeNull); - Statement* storeIndicatorStmt = fgNewStmtFromTree(storeIndicator); - fgInsertStmtAtBeg(fgFirstBB, storeIndicatorStmt); - } - else + if (!opts.IsOSR()) { GenTreeCall* captureCall = gtNewUserCallNode(asyncInfo->captureContextsMethHnd, TYP_VOID); captureCall->gtArgs.PushFront(this, @@ -207,7 +196,7 @@ PhaseStatus Compiler::SaveAsyncContexts() if ((inALoop && !isReturn) || !impInlineRoot()->info.compInitMem) { - GenTree* storeIndicator = gtNewStoreLclVarNode(lvaResumedIndicator, gtNewIconNode(0)); + GenTree* storeIndicator = gtNewStoreLclVarNode(lvaResumedIndicator, gtNewIconNode(0, TYP_I_IMPL)); Statement* storeIndicatorStmt = fgNewStmtFromTree(storeIndicator); fgInsertStmtAtBeg(fgFirstBB, storeIndicatorStmt); @@ -221,15 +210,12 @@ PhaseStatus Compiler::SaveAsyncContexts() } // Insert RestoreContexts call in fault (exceptional case) - // First argument: resumed = (continuation != null) - GenTree* resumed = gtNewLclvNode(lvaResumedIndicator, TYP_INT); - GenTreeCall* restoreCall = gtNewUserCallNode(asyncInfo->restoreContextsMethHnd, TYP_VOID); restoreCall->gtArgs.PushFront(this, NewCallArg::Primitive(gtNewLclVarNode(lvaAsyncSynchronizationContextVar, TYP_REF))); restoreCall->gtArgs.PushFront(this, NewCallArg::Primitive(gtNewLclVarNode(lvaAsyncExecutionContextVar, TYP_REF))); restoreCall->gtArgs.PushFront(this, NewCallArg::Primitive(gtNewLclVarNode(lvaAsyncThreadObjectVar, TYP_REF))); - restoreCall->gtArgs.PushFront(this, NewCallArg::Primitive(resumed)); + restoreCall->gtArgs.PushFront(this, NewCallArg::Primitive(gtNewLclVarNode(lvaResumedIndicator, TYP_INT))); Statement* restoreStmt = fgNewStmtFromTree(restoreCall); fgInsertStmtAtEnd(faultBB, restoreStmt); @@ -385,16 +371,13 @@ BasicBlock* Compiler::CreateReturnBB(unsigned* mergedReturnLcl) JITDUMP("Created new BBJ_RETURN block " FMT_BB "\n", newReturnBB->bbNum); // Insert "restore" call - CORINFO_ASYNC_INFO* asyncInfo = eeGetAsyncInfo(); - - GenTree* resumed = gtNewLclvNode(lvaResumedIndicator, TYP_INT); - - GenTreeCall* restoreCall = gtNewUserCallNode(asyncInfo->restoreContextsMethHnd, TYP_VOID); + CORINFO_ASYNC_INFO* asyncInfo = eeGetAsyncInfo(); + GenTreeCall* restoreCall = gtNewUserCallNode(asyncInfo->restoreContextsMethHnd, TYP_VOID); restoreCall->gtArgs.PushFront(this, NewCallArg::Primitive(gtNewLclVarNode(lvaAsyncSynchronizationContextVar, TYP_REF))); restoreCall->gtArgs.PushFront(this, NewCallArg::Primitive(gtNewLclVarNode(lvaAsyncExecutionContextVar, TYP_REF))); restoreCall->gtArgs.PushFront(this, NewCallArg::Primitive(gtNewLclVarNode(lvaAsyncThreadObjectVar, TYP_REF))); - restoreCall->gtArgs.PushFront(this, NewCallArg::Primitive(resumed)); + restoreCall->gtArgs.PushFront(this, NewCallArg::Primitive(gtNewLclVarNode(lvaResumedIndicator, TYP_INT))); // This restore is an inline candidate (unlike the fault one) CORINFO_CALL_INFO callInfo = {}; diff --git a/src/coreclr/jit/codegenarm.cpp b/src/coreclr/jit/codegenarm.cpp index 2bae2090b07035..f1418dd07bd231 100644 --- a/src/coreclr/jit/codegenarm.cpp +++ b/src/coreclr/jit/codegenarm.cpp @@ -2396,6 +2396,10 @@ void CodeGen::genCaptureFuncletPrologEpilogInfo() { saveSizeWithPSP += TARGET_POINTER_SIZE; } + if (m_compiler->lvaResumedIndicator != BAD_VAR_NUM) + { + saveSizeWithPSP += TARGET_POINTER_SIZE; + } if (m_compiler->lvaAsyncThreadObjectVar != BAD_VAR_NUM) { saveSizeWithPSP += TARGET_POINTER_SIZE; diff --git a/src/coreclr/jit/codegenarm64.cpp b/src/coreclr/jit/codegenarm64.cpp index 04f48619cfa8a7..b1dfa28f51720f 100644 --- a/src/coreclr/jit/codegenarm64.cpp +++ b/src/coreclr/jit/codegenarm64.cpp @@ -1731,6 +1731,11 @@ void CodeGen::genCaptureFuncletPrologEpilogInfo() saveRegsSize += m_compiler->lvaLclStackHomeSize(m_compiler->lvaMonAcquired); } + if ((m_compiler->lvaResumedIndicator != BAD_VAR_NUM) && !m_compiler->opts.IsOSR()) + { + saveRegsSize += m_compiler->lvaLclStackHomeSize(m_compiler->lvaResumedIndicator); + } + if ((m_compiler->lvaAsyncThreadObjectVar != BAD_VAR_NUM) && !m_compiler->opts.IsOSR()) { saveRegsSize += m_compiler->lvaLclStackHomeSize(m_compiler->lvaAsyncThreadObjectVar); diff --git a/src/coreclr/jit/codegenarmarch.cpp b/src/coreclr/jit/codegenarmarch.cpp index 91527e38610c1d..e1801ae7432c77 100644 --- a/src/coreclr/jit/codegenarmarch.cpp +++ b/src/coreclr/jit/codegenarmarch.cpp @@ -3928,6 +3928,13 @@ void CodeGen::genCreateAndStoreGCInfo(unsigned codeSize, assert(m_compiler->lvaGetCallerSPRelativeOffset(m_compiler->lvaMonAcquired) == -preservedAreaSize); } + if (m_compiler->lvaResumedIndicator != BAD_VAR_NUM) + { + preservedAreaSize += TARGET_POINTER_SIZE; + + assert(m_compiler->lvaGetCallerSPRelativeOffset(m_compiler->lvaResumedIndicator) == -preservedAreaSize); + } + if (m_compiler->lvaAsyncThreadObjectVar != BAD_VAR_NUM) { preservedAreaSize += TARGET_POINTER_SIZE; diff --git a/src/coreclr/jit/codegenxarch.cpp b/src/coreclr/jit/codegenxarch.cpp index 5308e680efa374..81eddf480ec367 100644 --- a/src/coreclr/jit/codegenxarch.cpp +++ b/src/coreclr/jit/codegenxarch.cpp @@ -8374,6 +8374,13 @@ void CodeGen::genCreateAndStoreGCInfoX64(unsigned codeSize, unsigned prologSize assert(m_compiler->lvaGetCallerSPRelativeOffset(m_compiler->lvaMonAcquired) == -preservedAreaSize); } + if (m_compiler->lvaResumedIndicator != BAD_VAR_NUM) + { + preservedAreaSize += TARGET_POINTER_SIZE; + + assert(m_compiler->lvaGetCallerSPRelativeOffset(m_compiler->lvaResumedIndicator) == -preservedAreaSize); + } + if (m_compiler->lvaAsyncThreadObjectVar != BAD_VAR_NUM) { preservedAreaSize += TARGET_POINTER_SIZE; diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index 2ba637e907092f..60fae26c756cad 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -5720,6 +5720,14 @@ void Compiler::generatePatchpointInfo() patchpointInfo->MonitorAcquiredOffset()); } + if (lvaResumedIndicator != BAD_VAR_NUM) + { + LclVarDsc* const varDsc = lvaGetDesc(lvaResumedIndicator); + patchpointInfo->SetResumedIndicatorOffset(varDsc->GetStackOffset() + offsetAdjust); + JITDUMP("--OSR-- resumed indicator V%02u virtual offset is %d\n", lvaResumedIndicator, + patchpointInfo->ResumedIndicatorOffset()); + } + if (lvaAsyncThreadObjectVar != BAD_VAR_NUM) { LclVarDsc* const varDsc = lvaGetDesc(lvaAsyncThreadObjectVar); @@ -10422,6 +10430,10 @@ int Compiler::lvaOSRLocalTier0FrameOffset(unsigned varNum) { return info.compPatchpointInfo->MonitorAcquiredOffset(); } + if (varNum == lvaResumedIndicator) + { + return info.compPatchpointInfo->ResumedIndicatorOffset(); + } if (varNum == lvaAsyncThreadObjectVar) { return info.compPatchpointInfo->AsyncThreadOffset(); diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index f9cf55b8feeecb..8e322068f9a04f 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -4347,6 +4347,7 @@ class Compiler unsigned lvaAsyncThreadObjectVar = BAD_VAR_NUM; // Thread local for async methods unsigned lvaAsyncExecutionContextVar = BAD_VAR_NUM; // ExecutionContext local for async methods unsigned lvaAsyncSynchronizationContextVar = BAD_VAR_NUM; // SynchronizationContext local for async methods + unsigned lvaResumedIndicator = BAD_VAR_NUM; // Variable representing "have we resumed?" for async methods unsigned short asyncContextRestoreEHID = USHRT_MAX; @@ -4389,9 +4390,6 @@ class Compiler // Variable representing async continuation argument passed. unsigned lvaAsyncContinuationArg = BAD_VAR_NUM; - // Variable representing "have we resumed?" for async methods - unsigned lvaResumedIndicator = BAD_VAR_NUM; - #if defined(DEBUG) && defined(TARGET_XARCH) unsigned lvaReturnSpCheck = BAD_VAR_NUM; // Stores SP to confirm it is not corrupted on return. diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index 703e2f0b35c25d..b7ad4c5acf2bd7 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -5328,7 +5328,7 @@ void Compiler::lvaAssignVirtualFrameOffsetsToLocals() continue; } - if ((lclNum == lvaMonAcquired) || (lclNum == lvaAsyncThreadObjectVar) || + if ((lclNum == lvaMonAcquired) || (lclNum == lvaResumedIndicator) || (lclNum == lvaAsyncThreadObjectVar) || (lclNum == lvaAsyncExecutionContextVar) || (lclNum == lvaAsyncSynchronizationContextVar)) { continue; @@ -5857,6 +5857,16 @@ int Compiler::lvaAllocLocalAndSetVirtualOffset(unsigned lclNum, unsigned size, i // int Compiler::lvaAllocAsyncContexts(int stkOffs) { + if (lvaResumedIndicator != BAD_VAR_NUM) + { + stkOffs = + lvaAllocLocalAndSetVirtualOffset(lvaResumedIndicator, lvaLclStackHomeSize(lvaResumedIndicator), stkOffs); + } + else + { + assert((info.compMethodInfo->options & CORINFO_ASYNC_SAVE_CONTEXTS) == 0); + } + if (lvaAsyncThreadObjectVar != BAD_VAR_NUM) { stkOffs = lvaAllocLocalAndSetVirtualOffset(lvaAsyncThreadObjectVar, @@ -5876,8 +5886,6 @@ int Compiler::lvaAllocAsyncContexts(int stkOffs) } else { - // For x86 EnC the VM expects that we always allocate stack space - // for this local when contexts were saved. assert((info.compMethodInfo->options & CORINFO_ASYNC_SAVE_CONTEXTS) == 0); } From 135b2a36057818586c42b65f759d7b76e76f80e2 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Mon, 3 Aug 2026 12:50:13 +0200 Subject: [PATCH 2/9] x86 EnC has 4 slots now --- src/coreclr/vm/gc_unwind_x86.inl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/gc_unwind_x86.inl b/src/coreclr/vm/gc_unwind_x86.inl index 30866e5a2ac137..26cecde6267668 100644 --- a/src/coreclr/vm/gc_unwind_x86.inl +++ b/src/coreclr/vm/gc_unwind_x86.inl @@ -556,7 +556,7 @@ inline size_t GetSizeOfFrameHeaderForEnC(MethodDesc* pMD, hdrInfo * info) info->localloc + info->genericsContext + // For CORINFO_GENERICS_CTXT_FROM_PARAMTYPEARG ((info->syncStartOffset != INVALID_SYNC_OFFSET) ? 1 : 0) + // Is this method synchronized - (pMD->RequiresAsyncContextSaveAndRestore() ? 3 : 0) + // Does this method save thread + async contexts? + (pMD->RequiresAsyncContextSaveAndRestore() ? 4 : 0) + // Does this method save thread + indicator + async contexts? 1; // for ebpFrame return position * sizeof(TADDR); } From 6154259c444a6da2e5a13f690e4bfb71c6cbe5eb Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Mon, 3 Aug 2026 12:53:37 +0200 Subject: [PATCH 3/9] Nits --- src/coreclr/jit/async.cpp | 8 ++++---- src/coreclr/jit/lclvars.cpp | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/coreclr/jit/async.cpp b/src/coreclr/jit/async.cpp index 1545d357d0d52b..b5c6c89931a99f 100644 --- a/src/coreclr/jit/async.cpp +++ b/src/coreclr/jit/async.cpp @@ -66,23 +66,23 @@ PhaseStatus Compiler::SaveAsyncContexts() return PhaseStatus::MODIFIED_NOTHING; } - // Create locals for Thread, ExecutionContext and SynchronizationContext + // Create locals for indicator, Thread, ExecutionContext and SynchronizationContext + lvaResumedIndicator = lvaGrabTemp(false DEBUGARG("Async Resumed")); lvaAsyncThreadObjectVar = lvaGrabTemp(false DEBUGARG("Async Thread")); lvaAsyncExecutionContextVar = lvaGrabTemp(false DEBUGARG("Async ExecutionContext")); lvaAsyncSynchronizationContextVar = lvaGrabTemp(false DEBUGARG("Async SynchronizationContext")); - lvaResumedIndicator = lvaGrabTemp(false DEBUGARG("Async Resumed")); + lvaGetDesc(lvaResumedIndicator)->lvType = TYP_I_IMPL; lvaGetDesc(lvaAsyncThreadObjectVar)->lvType = TYP_REF; lvaGetDesc(lvaAsyncExecutionContextVar)->lvType = TYP_REF; lvaGetDesc(lvaAsyncSynchronizationContextVar)->lvType = TYP_REF; - lvaGetDesc(lvaResumedIndicator)->lvType = TYP_I_IMPL; if (opts.IsOSR()) { + lvaGetDesc(lvaResumedIndicator)->lvIsOSRLocal = true; lvaGetDesc(lvaAsyncThreadObjectVar)->lvIsOSRLocal = true; lvaGetDesc(lvaAsyncExecutionContextVar)->lvIsOSRLocal = true; lvaGetDesc(lvaAsyncSynchronizationContextVar)->lvIsOSRLocal = true; - lvaGetDesc(lvaResumedIndicator)->lvIsOSRLocal = true; } // Create try-fault structure. This is actually a try-finally, but we diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index b7ad4c5acf2bd7..578958d7f4c402 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -5864,6 +5864,8 @@ int Compiler::lvaAllocAsyncContexts(int stkOffs) } else { + // For x86 EnC the VM expects that we always allocate stack space + // for these locals when contexts were saved. assert((info.compMethodInfo->options & CORINFO_ASYNC_SAVE_CONTEXTS) == 0); } @@ -5874,8 +5876,6 @@ int Compiler::lvaAllocAsyncContexts(int stkOffs) } else { - // For x86 EnC the VM expects that we always allocate stack space - // for this local when contexts were saved. assert((info.compMethodInfo->options & CORINFO_ASYNC_SAVE_CONTEXTS) == 0); } From f945d3b3a6de04bb7f6a9fbec2f10db6af6c9b39 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Mon, 3 Aug 2026 12:54:16 +0200 Subject: [PATCH 4/9] More nits --- src/coreclr/jit/compiler.h | 2 +- src/coreclr/vm/gc_unwind_x86.inl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 8e322068f9a04f..5760671b9174ec 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -4344,10 +4344,10 @@ class Compiler unsigned lvaMonAcquired = BAD_VAR_NUM; // boolean variable introduced into in synchronized methods // that tracks whether the lock has been taken + unsigned lvaResumedIndicator = BAD_VAR_NUM; // Variable representing "have we resumed?" for async methods unsigned lvaAsyncThreadObjectVar = BAD_VAR_NUM; // Thread local for async methods unsigned lvaAsyncExecutionContextVar = BAD_VAR_NUM; // ExecutionContext local for async methods unsigned lvaAsyncSynchronizationContextVar = BAD_VAR_NUM; // SynchronizationContext local for async methods - unsigned lvaResumedIndicator = BAD_VAR_NUM; // Variable representing "have we resumed?" for async methods unsigned short asyncContextRestoreEHID = USHRT_MAX; diff --git a/src/coreclr/vm/gc_unwind_x86.inl b/src/coreclr/vm/gc_unwind_x86.inl index 26cecde6267668..ec99912bd15d8f 100644 --- a/src/coreclr/vm/gc_unwind_x86.inl +++ b/src/coreclr/vm/gc_unwind_x86.inl @@ -556,7 +556,7 @@ inline size_t GetSizeOfFrameHeaderForEnC(MethodDesc* pMD, hdrInfo * info) info->localloc + info->genericsContext + // For CORINFO_GENERICS_CTXT_FROM_PARAMTYPEARG ((info->syncStartOffset != INVALID_SYNC_OFFSET) ? 1 : 0) + // Is this method synchronized - (pMD->RequiresAsyncContextSaveAndRestore() ? 4 : 0) + // Does this method save thread + indicator + async contexts? + (pMD->RequiresAsyncContextSaveAndRestore() ? 4 : 0) + // Does this method save indicator + thread + async contexts? 1; // for ebpFrame return position * sizeof(TADDR); } From 24277fa8a3723a205e818a8cf35fb6555d04ef02 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Mon, 3 Aug 2026 13:42:06 +0200 Subject: [PATCH 5/9] Make it TYP_UBYTE but over-allocate for it --- src/coreclr/jit/async.cpp | 4 ++-- src/coreclr/jit/lclvars.cpp | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/coreclr/jit/async.cpp b/src/coreclr/jit/async.cpp index b5c6c89931a99f..2c5a996c07e48b 100644 --- a/src/coreclr/jit/async.cpp +++ b/src/coreclr/jit/async.cpp @@ -72,7 +72,7 @@ PhaseStatus Compiler::SaveAsyncContexts() lvaAsyncExecutionContextVar = lvaGrabTemp(false DEBUGARG("Async ExecutionContext")); lvaAsyncSynchronizationContextVar = lvaGrabTemp(false DEBUGARG("Async SynchronizationContext")); - lvaGetDesc(lvaResumedIndicator)->lvType = TYP_I_IMPL; + lvaGetDesc(lvaResumedIndicator)->lvType = TYP_UBYTE; lvaGetDesc(lvaAsyncThreadObjectVar)->lvType = TYP_REF; lvaGetDesc(lvaAsyncExecutionContextVar)->lvType = TYP_REF; lvaGetDesc(lvaAsyncSynchronizationContextVar)->lvType = TYP_REF; @@ -196,7 +196,7 @@ PhaseStatus Compiler::SaveAsyncContexts() if ((inALoop && !isReturn) || !impInlineRoot()->info.compInitMem) { - GenTree* storeIndicator = gtNewStoreLclVarNode(lvaResumedIndicator, gtNewIconNode(0, TYP_I_IMPL)); + GenTree* storeIndicator = gtNewStoreLclVarNode(lvaResumedIndicator, gtNewIconNode(0)); Statement* storeIndicatorStmt = fgNewStmtFromTree(storeIndicator); fgInsertStmtAtBeg(fgFirstBB, storeIndicatorStmt); diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index 578958d7f4c402..4a38138bc77a6f 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -5859,8 +5859,10 @@ int Compiler::lvaAllocAsyncContexts(int stkOffs) { if (lvaResumedIndicator != BAD_VAR_NUM) { + // The indicator is actually a bool, but we allocate 8 bytes for it to + // simplify EnC remapping. stkOffs = - lvaAllocLocalAndSetVirtualOffset(lvaResumedIndicator, lvaLclStackHomeSize(lvaResumedIndicator), stkOffs); + lvaAllocLocalAndSetVirtualOffset(lvaResumedIndicator, TARGET_POINTER_SIZE, stkOffs); } else { From e704064aa96ba761e333766c271933a4ad2618dc Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Mon, 3 Aug 2026 14:09:42 +0200 Subject: [PATCH 6/9] Run jit-format --- src/coreclr/jit/lclvars.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index 4a38138bc77a6f..9a7a70b376321e 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -5861,8 +5861,7 @@ int Compiler::lvaAllocAsyncContexts(int stkOffs) { // The indicator is actually a bool, but we allocate 8 bytes for it to // simplify EnC remapping. - stkOffs = - lvaAllocLocalAndSetVirtualOffset(lvaResumedIndicator, TARGET_POINTER_SIZE, stkOffs); + stkOffs = lvaAllocLocalAndSetVirtualOffset(lvaResumedIndicator, TARGET_POINTER_SIZE, stkOffs); } else { From 33850533f0e6f6d584839f2b9d4da71551994f75 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Mon, 3 Aug 2026 15:17:09 +0200 Subject: [PATCH 7/9] Consistently treat as TYP_I_IMPL --- src/coreclr/inc/patchpointinfo.h | 20 ++++++++++---------- src/coreclr/jit/async.cpp | 16 ++++++++-------- src/coreclr/jit/compiler.hpp | 4 ++-- src/coreclr/jit/lclmorph.cpp | 2 +- src/coreclr/jit/lclvars.cpp | 4 +--- 5 files changed, 22 insertions(+), 24 deletions(-) diff --git a/src/coreclr/inc/patchpointinfo.h b/src/coreclr/inc/patchpointinfo.h index 1f305226106410..5592cece541570 100644 --- a/src/coreclr/inc/patchpointinfo.h +++ b/src/coreclr/inc/patchpointinfo.h @@ -269,16 +269,16 @@ struct PatchpointInfo uint64_t m_calleeSaveRegisters; PCODE m_tier0Version; uint32_t m_numberOfLocals; - int32_t m_totalFrameSize; - int32_t m_genericContextArgOffset; - int32_t m_keptAliveThisOffset; - int32_t m_securityCookieOffset; - int32_t m_monitorAcquiredOffset; - int32_t m_resumedIndicatorOffset; - int32_t m_asyncThreadObjectOffset; - int32_t m_asyncExecutionContextOffset; - int32_t m_asyncSynchronizationContextOffset; - int32_t m_offsetAndExposureData[]; + int32_t m_totalFrameSize; + int32_t m_genericContextArgOffset; + int32_t m_keptAliveThisOffset; + int32_t m_securityCookieOffset; + int32_t m_monitorAcquiredOffset; + int32_t m_resumedIndicatorOffset; + int32_t m_asyncThreadObjectOffset; + int32_t m_asyncExecutionContextOffset; + int32_t m_asyncSynchronizationContextOffset; + int32_t m_offsetAndExposureData[]; }; typedef DPTR(struct PatchpointInfo) PTR_PatchpointInfo; diff --git a/src/coreclr/jit/async.cpp b/src/coreclr/jit/async.cpp index 2c5a996c07e48b..82471f412f2d58 100644 --- a/src/coreclr/jit/async.cpp +++ b/src/coreclr/jit/async.cpp @@ -72,7 +72,7 @@ PhaseStatus Compiler::SaveAsyncContexts() lvaAsyncExecutionContextVar = lvaGrabTemp(false DEBUGARG("Async ExecutionContext")); lvaAsyncSynchronizationContextVar = lvaGrabTemp(false DEBUGARG("Async SynchronizationContext")); - lvaGetDesc(lvaResumedIndicator)->lvType = TYP_UBYTE; + lvaGetDesc(lvaResumedIndicator)->lvType = TYP_I_IMPL; lvaGetDesc(lvaAsyncThreadObjectVar)->lvType = TYP_REF; lvaGetDesc(lvaAsyncExecutionContextVar)->lvType = TYP_REF; lvaGetDesc(lvaAsyncSynchronizationContextVar)->lvType = TYP_REF; @@ -196,7 +196,7 @@ PhaseStatus Compiler::SaveAsyncContexts() if ((inALoop && !isReturn) || !impInlineRoot()->info.compInitMem) { - GenTree* storeIndicator = gtNewStoreLclVarNode(lvaResumedIndicator, gtNewIconNode(0)); + GenTree* storeIndicator = gtNewStoreLclVarNode(lvaResumedIndicator, gtNewIconNode(0, TYP_I_IMPL)); Statement* storeIndicatorStmt = fgNewStmtFromTree(storeIndicator); fgInsertStmtAtBeg(fgFirstBB, storeIndicatorStmt); @@ -3055,14 +3055,14 @@ void AsyncTransformation::StoreResumedDef(GenTreeLclVarCommon* resumedDef, Basic LclVarDsc* varDsc = m_compiler->lvaGetDesc(resumedDef); GenTree* store; - if ((resumedDef->GetLclOffs() == 0) && varDsc->TypeIs(TYP_UBYTE)) + if ((resumedDef->GetLclOffs() == 0) && varDsc->TypeIs(TYP_I_IMPL)) { - store = m_compiler->gtNewStoreLclVarNode(resumedDef->GetLclNum(), m_compiler->gtNewIconNode(1)); + store = m_compiler->gtNewStoreLclVarNode(resumedDef->GetLclNum(), m_compiler->gtNewIconNode(1, TYP_I_IMPL)); } else { - store = m_compiler->gtNewStoreLclFldNode(resumedDef->GetLclNum(), TYP_UBYTE, resumedDef->GetLclOffs(), - m_compiler->gtNewIconNode(1)); + store = m_compiler->gtNewStoreLclFldNode(resumedDef->GetLclNum(), TYP_I_IMPL, resumedDef->GetLclOffs(), + m_compiler->gtNewIconNode(1, TYP_I_IMPL)); m_compiler->lvaSetVarDoNotEnregister(resumedDef->GetLclNum() DEBUGARG(DoNotEnregisterReason::LocalField)); } @@ -3605,10 +3605,10 @@ BasicBlock* AsyncTransformation::CreateSharedFinishContextHandlingBB(SuspensionC { m_sharedFinishContextHandlingResumedVar = m_compiler->lvaGrabTemp(false DEBUGARG("'resumed' for shared finish context handling")); - m_compiler->lvaGetDesc(m_sharedFinishContextHandlingResumedVar)->lvType = TYP_UBYTE; + m_compiler->lvaGetDesc(m_sharedFinishContextHandlingResumedVar)->lvType = TYP_INT; } - resumed = m_compiler->gtNewLclVarNode(m_sharedFinishContextHandlingResumedVar, TYP_UBYTE); + resumed = m_compiler->gtNewLclVarNode(m_sharedFinishContextHandlingResumedVar, TYP_INT); } else { diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index 31c17ca2eb5e34..3dda8ea0c8d0ae 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -4642,10 +4642,10 @@ GenTree::VisitResult GenTree::VisitLocalDefs(Compiler* comp, TVisitor visitor) GenTreeLclVarCommon* asyncResumedLclAddr = comp->gtCallGetDefinedAsyncResumedLclAddr(call); if (asyncResumedLclAddr != nullptr) { - bool isEntire = comp->lvaLclExactSize(asyncResumedLclAddr->GetLclNum()) == 1; + bool isEntire = comp->lvaLclExactSize(asyncResumedLclAddr->GetLclNum()) == TARGET_POINTER_SIZE; RETURN_IF_ABORT( - visitor(LocalDef(asyncResumedLclAddr, isEntire, asyncResumedLclAddr->GetLclOffs(), ValueSize(1)))); + visitor(LocalDef(asyncResumedLclAddr, isEntire, asyncResumedLclAddr->GetLclOffs(), ValueSize(TARGET_POINTER_SIZE)))); } GenTreeLclVarCommon* retBufLclAddr = comp->gtCallGetDefinedRetBufLclAddr(call); diff --git a/src/coreclr/jit/lclmorph.cpp b/src/coreclr/jit/lclmorph.cpp index 8c96b0d5c166be..6cafff96b2d7a7 100644 --- a/src/coreclr/jit/lclmorph.cpp +++ b/src/coreclr/jit/lclmorph.cpp @@ -1526,7 +1526,7 @@ class LocalAddressVisitor final : public GenTreeVisitor CallArg* asyncResumedDef = callUser->gtArgs.FindWellKnownArg(WellKnownArg::AsyncResumedDef); if ((asyncResumedDef != nullptr) && (val.Node() == asyncResumedDef->GetNode())) { - defSize = 1; + defSize = TARGET_POINTER_SIZE; } } diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index 9a7a70b376321e..e82c00d5ce50cc 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -5859,9 +5859,7 @@ int Compiler::lvaAllocAsyncContexts(int stkOffs) { if (lvaResumedIndicator != BAD_VAR_NUM) { - // The indicator is actually a bool, but we allocate 8 bytes for it to - // simplify EnC remapping. - stkOffs = lvaAllocLocalAndSetVirtualOffset(lvaResumedIndicator, TARGET_POINTER_SIZE, stkOffs); + stkOffs = lvaAllocLocalAndSetVirtualOffset(lvaResumedIndicator, lvaLclStackHomeSize(lvaResumedIndicator), stkOffs); } else { From 6a8d48b25eece11f439cb6df2b64c621f87ca65c Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Mon, 3 Aug 2026 15:17:51 +0200 Subject: [PATCH 8/9] Run gen.bat --- src/coreclr/inc/jiteeversionguid.h | 10 +++++----- src/coreclr/inc/readytoruninstructionset.h | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index ed763baa880121..46f88959ccb836 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -37,11 +37,11 @@ #include -constexpr GUID JITEEVersionIdentifier = { /* 134b051a-e1ec-4f52-a1bf-f919908aa33c */ - 0x134b051a, - 0xe1ec, - 0x4f52, - {0xa1, 0xbf, 0xf9, 0x19, 0x90, 0x8a, 0xa3, 0x3c} +constexpr GUID JITEEVersionIdentifier = { /* 2712d834-36d1-475f-97b9-3b2eeb4533b0 */ + 0x2712d834, + 0x36d1, + 0x475f, + {0x97, 0xb9, 0x3b, 0x2e, 0xeb, 0x45, 0x33, 0xb0} }; #endif // JIT_EE_VERSIONING_GUID_H diff --git a/src/coreclr/inc/readytoruninstructionset.h b/src/coreclr/inc/readytoruninstructionset.h index 19ffdad8863f2e..2afbe10ef26e0f 100644 --- a/src/coreclr/inc/readytoruninstructionset.h +++ b/src/coreclr/inc/readytoruninstructionset.h @@ -102,6 +102,7 @@ enum ReadyToRunInstructionSet READYTORUN_INSTRUCTION_PackedSimd=92, READYTORUN_INSTRUCTION_Cssc=93, READYTORUN_INSTRUCTION_Zicond=94, + }; #endif // READYTORUNINSTRUCTIONSET_H From b1b46de50bf37ee5fa222ec92c398238f57e9273 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Mon, 3 Aug 2026 15:28:28 +0200 Subject: [PATCH 9/9] Run jit-format --- src/coreclr/jit/compiler.hpp | 4 ++-- src/coreclr/jit/lclvars.cpp | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index 3dda8ea0c8d0ae..ff316205567597 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -4644,8 +4644,8 @@ GenTree::VisitResult GenTree::VisitLocalDefs(Compiler* comp, TVisitor visitor) { bool isEntire = comp->lvaLclExactSize(asyncResumedLclAddr->GetLclNum()) == TARGET_POINTER_SIZE; - RETURN_IF_ABORT( - visitor(LocalDef(asyncResumedLclAddr, isEntire, asyncResumedLclAddr->GetLclOffs(), ValueSize(TARGET_POINTER_SIZE)))); + RETURN_IF_ABORT(visitor(LocalDef(asyncResumedLclAddr, isEntire, asyncResumedLclAddr->GetLclOffs(), + ValueSize(TARGET_POINTER_SIZE)))); } GenTreeLclVarCommon* retBufLclAddr = comp->gtCallGetDefinedRetBufLclAddr(call); diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index e82c00d5ce50cc..578958d7f4c402 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -5859,7 +5859,8 @@ int Compiler::lvaAllocAsyncContexts(int stkOffs) { if (lvaResumedIndicator != BAD_VAR_NUM) { - stkOffs = lvaAllocLocalAndSetVirtualOffset(lvaResumedIndicator, lvaLclStackHomeSize(lvaResumedIndicator), stkOffs); + stkOffs = + lvaAllocLocalAndSetVirtualOffset(lvaResumedIndicator, lvaLclStackHomeSize(lvaResumedIndicator), stkOffs); } else {