From bc26aee9bd4dccbb7a8e9cf17eae25cb6b49a006 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Wed, 12 Aug 2026 13:07:53 -0700 Subject: [PATCH 1/4] Represent cached generic context as a local Replace the anonymous generic-context frame offset with an early-created internal local while preserving existing reporting, OSR, and tailcall behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 207fb832-c84e-4e9c-b949-3921e19731ce --- src/coreclr/jit/compiler.h | 2 +- src/coreclr/jit/compiler.hpp | 3 +- src/coreclr/jit/lclvars.cpp | 74 +++++++++++++++++++++++++----------- 3 files changed, 54 insertions(+), 25 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 78d7bd0774783d..31e15436edf836 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -4467,7 +4467,7 @@ class Compiler //------------------------------------------------------------------------- // All these frame offsets are inter-related and must be kept in sync - int lvaCachedGenericContextArgOffs; + unsigned lvaCachedGenericContextArg = BAD_VAR_NUM; int lvaCachedGenericContextArgOffset(); // For CORINFO_CALLCONV_PARAMTYPE and if generic context is passed as // THIS pointer diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index ff316205567597..7afd3e45101b6d 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -2723,8 +2723,9 @@ inline bool Compiler::lvaReportParamTypeArg() inline int Compiler::lvaCachedGenericContextArgOffset() { assert(lvaDoneFrameLayout == FINAL_FRAME_LAYOUT); + assert(lvaCachedGenericContextArg != BAD_VAR_NUM); - return lvaCachedGenericContextArgOffs; + return lvaGetDesc(lvaCachedGenericContextArg)->GetStackOffset(); } //------------------------------------------------------------------------ diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index 578958d7f4c402..16e9b2162659e8 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -316,6 +316,25 @@ void Compiler::lvaInitTypeRef() } } + const bool mayNeedGenericContextArg = (info.compMethodInfo->options & (CORINFO_GENERICS_CTXT_FROM_METHODDESC | + CORINFO_GENERICS_CTXT_FROM_METHODTABLE | + CORINFO_GENERICS_CTXT_FROM_THIS)) != 0; + +#ifdef JIT32_GCENCODER + const bool mayNeedSynchronizedThis = !info.compIsStatic && ((info.compFlags & CORINFO_FLG_SYNCH) != 0); +#else + const bool mayNeedSynchronizedThis = false; +#endif + + if (mayNeedGenericContextArg || mayNeedSynchronizedThis) + { + lvaCachedGenericContextArg = lvaGrabTemp(true DEBUGARG("cached generic context")); + + LclVarDsc* cachedGenericContextArg = lvaGetDesc(lvaCachedGenericContextArg); + cachedGenericContextArg->lvType = TYP_I_IMPL; + cachedGenericContextArg->lvOnFrame = false; + } + if (getNeedsGSSecurityCookie()) { // Ensure that there will be at least one stack variable since @@ -3439,6 +3458,22 @@ PhaseStatus Compiler::lvaMarkLocalVars() const bool isRecompute = false; lvaComputeRefCounts(isRecompute, setSlotNumbers); + if (lvaCachedGenericContextArg != BAD_VAR_NUM) + { + LclVarDsc* cachedGenericContextArg = lvaGetDesc(lvaCachedGenericContextArg); + + if (lvaReportParamTypeArg() || lvaKeepAliveAndReportThis()) + { + cachedGenericContextArg->lvImplicitlyReferenced = 1; + cachedGenericContextArg->lvOnFrame = true; + lvaSetVarDoNotEnregister(lvaCachedGenericContextArg DEBUGARG(DoNotEnregisterReason::VMNeedsStackAddr)); + } + else + { + cachedGenericContextArg->lvImplicitlyReferenced = 0; + } + } + // If we don't need precise reference counts, e.g. we're not optimizing, we're done. if (!PreciseRefCountsRequired()) { @@ -3507,10 +3542,11 @@ void Compiler::lvaComputeRefCounts(bool isRecompute, bool setSlotNumbers) // and not tracked. for (unsigned lclNum = 0; lclNum < lvaCount; lclNum++) { - LclVarDsc* varDsc = lvaGetDesc(lclNum); - const bool isSpecialVarargsParam = varDsc->lvIsParam && lvaIsArgAccessedViaVarArgsCookie(lclNum); + LclVarDsc* varDsc = lvaGetDesc(lclNum); + const bool isSpecialVarargsParam = varDsc->lvIsParam && lvaIsArgAccessedViaVarArgsCookie(lclNum); + const bool isDormantGenericContextArg = (lclNum == lvaCachedGenericContextArg) && !varDsc->lvOnFrame; - if (isSpecialVarargsParam) + if (isSpecialVarargsParam || isDormantGenericContextArg) { assert(varDsc->lvRefCnt() == 0); } @@ -3541,9 +3577,10 @@ void Compiler::lvaComputeRefCounts(bool isRecompute, bool setSlotNumbers) // Special case for some varargs params ... these must // remain unreferenced. - const bool isSpecialVarargsParam = varDsc->lvIsParam && lvaIsArgAccessedViaVarArgsCookie(lclNum); + const bool isSpecialVarargsParam = varDsc->lvIsParam && lvaIsArgAccessedViaVarArgsCookie(lclNum); + const bool isDormantGenericContextArg = (lclNum == lvaCachedGenericContextArg) && !varDsc->lvOnFrame; - if (!isSpecialVarargsParam) + if (!isSpecialVarargsParam && !isDormantGenericContextArg) { varDsc->lvImplicitlyReferenced = 1; } @@ -4565,12 +4602,6 @@ void Compiler::lvaFixVirtualFrameOffsets() temp->tdAdjustTempOffs(delta + frameLocalsDelta); } - if (lvaCachedGenericContextArgOffs < frameBoundary) - { - lvaCachedGenericContextArgOffs += frameLocalsDelta; - } - lvaCachedGenericContextArgOffs += delta; - #if FEATURE_FIXED_OUT_ARGS if (lvaOutgoingArgSpaceVar != BAD_VAR_NUM) @@ -5063,15 +5094,13 @@ void Compiler::lvaAssignVirtualFrameOffsetsToLocals() { PatchpointInfo* ppInfo = info.compPatchpointInfo; assert(ppInfo->HasGenericContextArgOffset()); - const int originalOffset = ppInfo->GenericContextArgOffset(); - lvaCachedGenericContextArgOffs = originalFrameStkOffs + originalOffset; + const int originalOffset = ppInfo->GenericContextArgOffset(); + lvaGetDesc(lvaCachedGenericContextArg)->SetStackOffset(originalFrameStkOffs + originalOffset); } else { // For CORINFO_CALLCONV_PARAMTYPE (if needed) - lvaIncrementFrameSize(TARGET_POINTER_SIZE); - stkOffs -= TARGET_POINTER_SIZE; - lvaCachedGenericContextArgOffs = stkOffs; + stkOffs = lvaAllocLocalAndSetVirtualOffset(lvaCachedGenericContextArg, TARGET_POINTER_SIZE, stkOffs); } } #ifndef JIT32_GCENCODER @@ -5083,18 +5112,16 @@ void Compiler::lvaAssignVirtualFrameOffsetsToLocals() PatchpointInfo* ppInfo = info.compPatchpointInfo; if (ppInfo->HasKeptAliveThis()) { - const int originalOffset = ppInfo->KeptAliveThisOffset(); - lvaCachedGenericContextArgOffs = originalFrameStkOffs + originalOffset; - canUseExistingSlot = true; + const int originalOffset = ppInfo->KeptAliveThisOffset(); + lvaGetDesc(lvaCachedGenericContextArg)->SetStackOffset(originalFrameStkOffs + originalOffset); + canUseExistingSlot = true; } } if (!canUseExistingSlot) { // When "this" is also used as generic context arg. - lvaIncrementFrameSize(TARGET_POINTER_SIZE); - stkOffs -= TARGET_POINTER_SIZE; - lvaCachedGenericContextArgOffs = stkOffs; + stkOffs = lvaAllocLocalAndSetVirtualOffset(lvaCachedGenericContextArg, TARGET_POINTER_SIZE, stkOffs); } } #endif @@ -5328,7 +5355,8 @@ void Compiler::lvaAssignVirtualFrameOffsetsToLocals() continue; } - if ((lclNum == lvaMonAcquired) || (lclNum == lvaResumedIndicator) || (lclNum == lvaAsyncThreadObjectVar) || + if ((lclNum == lvaMonAcquired) || (lclNum == lvaCachedGenericContextArg) || + (lclNum == lvaResumedIndicator) || (lclNum == lvaAsyncThreadObjectVar) || (lclNum == lvaAsyncExecutionContextVar) || (lclNum == lvaAsyncSynchronizationContextVar)) { continue; From 6ce5b29a4b3a15765d02ab5279a5c8110faa59b7 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Mon, 17 Aug 2026 11:01:44 -0700 Subject: [PATCH 2/4] Mark generic context local externally visible Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 207fb832-c84e-4e9c-b949-3921e19731ce --- src/coreclr/jit/lclvars.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index 16e9b2162659e8..db9cbef8d63730 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -3467,6 +3467,8 @@ PhaseStatus Compiler::lvaMarkLocalVars() cachedGenericContextArg->lvImplicitlyReferenced = 1; cachedGenericContextArg->lvOnFrame = true; lvaSetVarDoNotEnregister(lvaCachedGenericContextArg DEBUGARG(DoNotEnregisterReason::VMNeedsStackAddr)); + lvaSetVarAddrExposed( + lvaCachedGenericContextArg DEBUGARG(AddressExposedReason::EXTERNALLY_VISIBLE_IMPLICITLY)); } else { From b9c8ef9c3f5e54a33f45a6c4f6d8a9b1533ae9cb Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Mon, 17 Aug 2026 11:52:46 -0700 Subject: [PATCH 3/4] Fix generic context local handling Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 207fb832-c84e-4e9c-b949-3921e19731ce --- src/coreclr/jit/compiler.cpp | 13 +++++++- src/coreclr/jit/lclvars.cpp | 62 ++++++++++++++++++------------------ 2 files changed, 43 insertions(+), 32 deletions(-) diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index bc94966334d44f..5875f0f44a5c83 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -10401,7 +10401,7 @@ bool Compiler::lvaIsOSRLocal(unsigned varNum) // if ((varNum >= info.compLocalsCount) && (varNum != lvaMonAcquired) && (varNum != lvaAsyncThreadObjectVar) && (varNum != lvaResumedIndicator) && (varNum != lvaAsyncExecutionContextVar) && - (varNum != lvaAsyncSynchronizationContextVar)) + (varNum != lvaAsyncSynchronizationContextVar) && (varNum != lvaCachedGenericContextArg)) { assert(varDsc->lvIsStructField); assert(varDsc->lvParentLcl < info.compLocalsCount); @@ -10431,6 +10431,17 @@ int Compiler::lvaOSRLocalTier0FrameOffset(unsigned varNum) { assert(lvaIsOSRLocal(varNum)); + if (varNum == lvaCachedGenericContextArg) + { + if (lvaReportParamTypeArg()) + { + return info.compPatchpointInfo->GenericContextArgOffset(); + } + + assert(lvaKeepAliveAndReportThis()); + assert(info.compPatchpointInfo->HasKeptAliveThis()); + return info.compPatchpointInfo->KeptAliveThisOffset(); + } if (varNum == lvaMonAcquired) { return info.compPatchpointInfo->MonitorAcquiredOffset(); diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index db9cbef8d63730..32bc5051dbc3c2 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -316,17 +316,15 @@ void Compiler::lvaInitTypeRef() } } - const bool mayNeedGenericContextArg = (info.compMethodInfo->options & (CORINFO_GENERICS_CTXT_FROM_METHODDESC | - CORINFO_GENERICS_CTXT_FROM_METHODTABLE | - CORINFO_GENERICS_CTXT_FROM_THIS)) != 0; - #ifdef JIT32_GCENCODER - const bool mayNeedSynchronizedThis = !info.compIsStatic && ((info.compFlags & CORINFO_FLG_SYNCH) != 0); + const unsigned genericContextOptions = + CORINFO_GENERICS_CTXT_FROM_METHODDESC | CORINFO_GENERICS_CTXT_FROM_METHODTABLE; #else - const bool mayNeedSynchronizedThis = false; + const unsigned genericContextOptions = CORINFO_GENERICS_CTXT_FROM_METHODDESC | + CORINFO_GENERICS_CTXT_FROM_METHODTABLE | CORINFO_GENERICS_CTXT_FROM_THIS; #endif - if (mayNeedGenericContextArg || mayNeedSynchronizedThis) + if ((info.compMethodInfo->options & genericContextOptions) != 0) { lvaCachedGenericContextArg = lvaGrabTemp(true DEBUGARG("cached generic context")); @@ -3462,13 +3460,34 @@ PhaseStatus Compiler::lvaMarkLocalVars() { LclVarDsc* cachedGenericContextArg = lvaGetDesc(lvaCachedGenericContextArg); - if (lvaReportParamTypeArg() || lvaKeepAliveAndReportThis()) + bool reportGenericContextArg = lvaReportParamTypeArg(); +#ifndef JIT32_GCENCODER + reportGenericContextArg |= lvaKeepAliveAndReportThis(); +#endif + + if (reportGenericContextArg) { cachedGenericContextArg->lvImplicitlyReferenced = 1; cachedGenericContextArg->lvOnFrame = true; lvaSetVarDoNotEnregister(lvaCachedGenericContextArg DEBUGARG(DoNotEnregisterReason::VMNeedsStackAddr)); lvaSetVarAddrExposed( lvaCachedGenericContextArg DEBUGARG(AddressExposedReason::EXTERNALLY_VISIBLE_IMPLICITLY)); + + if (opts.IsOSR()) + { + PatchpointInfo* patchpointInfo = info.compPatchpointInfo; + if (lvaReportParamTypeArg()) + { + assert(patchpointInfo->HasGenericContextArgOffset()); + cachedGenericContextArg->lvIsOSRLocal = true; + } +#ifndef JIT32_GCENCODER + else if (patchpointInfo->HasKeptAliveThis()) + { + cachedGenericContextArg->lvIsOSRLocal = true; + } +#endif + } } else { @@ -5092,35 +5111,15 @@ void Compiler::lvaAssignVirtualFrameOffsetsToLocals() #ifdef JIT32_GCENCODER noway_assert(codeGen->isFramePointerUsed()); #endif - if (opts.IsOSR()) - { - PatchpointInfo* ppInfo = info.compPatchpointInfo; - assert(ppInfo->HasGenericContextArgOffset()); - const int originalOffset = ppInfo->GenericContextArgOffset(); - lvaGetDesc(lvaCachedGenericContextArg)->SetStackOffset(originalFrameStkOffs + originalOffset); - } - else + if (!lvaIsOSRLocal(lvaCachedGenericContextArg)) { - // For CORINFO_CALLCONV_PARAMTYPE (if needed) stkOffs = lvaAllocLocalAndSetVirtualOffset(lvaCachedGenericContextArg, TARGET_POINTER_SIZE, stkOffs); } } #ifndef JIT32_GCENCODER else if (lvaKeepAliveAndReportThis()) { - bool canUseExistingSlot = false; - if (opts.IsOSR()) - { - PatchpointInfo* ppInfo = info.compPatchpointInfo; - if (ppInfo->HasKeptAliveThis()) - { - const int originalOffset = ppInfo->KeptAliveThisOffset(); - lvaGetDesc(lvaCachedGenericContextArg)->SetStackOffset(originalFrameStkOffs + originalOffset); - canUseExistingSlot = true; - } - } - - if (!canUseExistingSlot) + if (!lvaIsOSRLocal(lvaCachedGenericContextArg)) { // When "this" is also used as generic context arg. stkOffs = lvaAllocLocalAndSetVirtualOffset(lvaCachedGenericContextArg, TARGET_POINTER_SIZE, stkOffs); @@ -5357,7 +5356,8 @@ void Compiler::lvaAssignVirtualFrameOffsetsToLocals() continue; } - if ((lclNum == lvaMonAcquired) || (lclNum == lvaCachedGenericContextArg) || + if ((lclNum == lvaMonAcquired) || + ((lclNum == lvaCachedGenericContextArg) && !lvaIsOSRLocal(lvaCachedGenericContextArg)) || (lclNum == lvaResumedIndicator) || (lclNum == lvaAsyncThreadObjectVar) || (lclNum == lvaAsyncExecutionContextVar) || (lclNum == lvaAsyncSynchronizationContextVar)) { From 550e7bdd31fb7c73ac077d17999105fcff1d3fce Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Mon, 17 Aug 2026 15:04:39 -0700 Subject: [PATCH 4/4] Fix generic context OSR slot lookup Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 207fb832-c84e-4e9c-b949-3921e19731ce --- src/coreclr/jit/compiler.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index 143c6501fc91e0..6c0557bccf66f9 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -10435,12 +10435,11 @@ int Compiler::lvaOSRLocalTier0FrameOffset(unsigned varNum) if (varNum == lvaCachedGenericContextArg) { - if (lvaReportParamTypeArg()) + if (info.compPatchpointInfo->HasGenericContextArgOffset()) { return info.compPatchpointInfo->GenericContextArgOffset(); } - assert(lvaKeepAliveAndReportThis()); assert(info.compPatchpointInfo->HasKeptAliveThis()); return info.compPatchpointInfo->KeptAliveThisOffset(); }