Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6156856
Avoid JitTokenToMethodRegionInfo for hot R2R frames in JitCodeToMetho…
VSadov Oct 14, 2026
986853e
Cache GCInfoToken lazily in EECodeInfo
VSadov Oct 14, 2026
c88843c
Gate GetParamContextType in GcStackCrawlCallBack on generic context m…
VSadov Oct 14, 2026
66ed765
Merge generic param context type, token, and prolog decoding into sin…
VSadov Oct 14, 2026
4c72376
Avoid eager EnsureCallerContextIsValid in CheckForSkippedFrames
VSadov Oct 14, 2026
2f0e030
Skip FP/vector state copy in EnsureCallerContextIsValid during GC roo…
VSadov Oct 14, 2026
a69fb98
Use word reads and TrailingZeroCount in GcInfoDecoder bit-scanning loops
VSadov Oct 14, 2026
52ed104
Fix Checked/Debug build in StackFrameIterator::NextRaw assert
VSadov Oct 14, 2026
ccad1d1
Add GC_ROOT_SCAN flag check in stackwalk.cpp
VSadov Oct 15, 2026
f98d7da
Encapsulate lazy EnsureCallerContextIsValid in caller-state accessors
VSadov Oct 15, 2026
1bb86a1
Require GC-safe points for legacy thread-abort redirection
VSadov Oct 15, 2026
148d7a1
Report generic loader allocators while enumerating GC references
VSadov Oct 15, 2026
b05f4f4
Address caller-context stack-walk review feedback
VSadov Oct 15, 2026
e471c4b
PR feedback (comment)
VSadov Oct 20, 2026
4c76afb
Report x86 generic loader allocators during GC enumeration
VSadov Oct 20, 2026
e1c46e3
Merge upstream/main into dec001
VSadov Oct 21, 2026
e0608f7
Fix x86 case
VSadov Oct 21, 2026
a138483
Document skipped-frame GC fast-path invariants
VSadov Oct 21, 2026
8b05fbc
Remove the fragile GC skipped-frame ownership fast path
VSadov Oct 22, 2026
4115afc
Apply batched suggestions from code review
VSadov Oct 23, 2026
44b0d3c
build fix
VSadov Oct 23, 2026
4ba0fba
workaround for GC skipped-frame ownership fast path
VSadov Oct 23, 2026
91efd43
remove m_codeManFlags from StackFrameIterator
VSadov Oct 27, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,19 @@ class AsmOffsets

#if TARGET_64BIT
public const int OFFSETOF__REGDISPLAY__m_pCurrentContext = 0x8;
public const int SIZEOF__StackFrameIterator = 0x150;
public const int OFFSETOF__StackFrameIterator__m_AdjustedControlPC = 0x148;
public const int OFFSETOF__StackFrameIterator__m_isRuntimeWrappedExceptions = 0x132;
public const int SIZEOF__StackFrameIterator = 0x168;
public const int OFFSETOF__StackFrameIterator__m_AdjustedControlPC = 0x160;
public const int OFFSETOF__StackFrameIterator__m_isRuntimeWrappedExceptions = 0x14a;
#elif TARGET_X86
public const int OFFSETOF__REGDISPLAY__m_pCurrentContext = 0x4;
public const int SIZEOF__StackFrameIterator = 0x3d0;
public const int OFFSETOF__StackFrameIterator__m_isRuntimeWrappedExceptions = 0x3c2;
public const int OFFSETOF__StackFrameIterator__m_AdjustedControlPC = 0x3cc;
public const int SIZEOF__StackFrameIterator = 0x3dc;
public const int OFFSETOF__StackFrameIterator__m_isRuntimeWrappedExceptions = 0x3ce;
public const int OFFSETOF__StackFrameIterator__m_AdjustedControlPC = 0x3d8;
#else // TARGET_64BIT
public const int OFFSETOF__REGDISPLAY__m_pCurrentContext = 0x4;
public const int SIZEOF__StackFrameIterator = 0xc8;
public const int OFFSETOF__StackFrameIterator__m_AdjustedControlPC = 0xc4;
public const int OFFSETOF__StackFrameIterator__m_isRuntimeWrappedExceptions = 0xba;
public const int SIZEOF__StackFrameIterator = 0xd4;
public const int OFFSETOF__StackFrameIterator__m_AdjustedControlPC = 0xd0;
public const int OFFSETOF__StackFrameIterator__m_isRuntimeWrappedExceptions = 0xc6;
#endif // TARGET_64BIT

#else // DEBUG
Expand Down Expand Up @@ -119,19 +119,19 @@ class AsmOffsets

#if TARGET_64BIT
public const int OFFSETOF__REGDISPLAY__m_pCurrentContext = 0x8;
public const int SIZEOF__StackFrameIterator = 0x148;
public const int OFFSETOF__StackFrameIterator__m_AdjustedControlPC = 0x140;
public const int OFFSETOF__StackFrameIterator__m_isRuntimeWrappedExceptions = 0x12a;
public const int SIZEOF__StackFrameIterator = 0x160;
public const int OFFSETOF__StackFrameIterator__m_AdjustedControlPC = 0x158;
public const int OFFSETOF__StackFrameIterator__m_isRuntimeWrappedExceptions = 0x142;
#elif TARGET_X86
public const int OFFSETOF__REGDISPLAY__m_pCurrentContext = 0x4;
public const int SIZEOF__StackFrameIterator = 0x3c8;
public const int OFFSETOF__StackFrameIterator__m_isRuntimeWrappedExceptions = 0x3ba;
public const int OFFSETOF__StackFrameIterator__m_AdjustedControlPC = 0x3c4;
public const int SIZEOF__StackFrameIterator = 0x3d4;
public const int OFFSETOF__StackFrameIterator__m_isRuntimeWrappedExceptions = 0x3c6;
public const int OFFSETOF__StackFrameIterator__m_AdjustedControlPC = 0x3d0;
#else // TARGET_64BIT
public const int OFFSETOF__REGDISPLAY__m_pCurrentContext = 0x4;
public const int SIZEOF__StackFrameIterator = 0xc0;
public const int OFFSETOF__StackFrameIterator__m_AdjustedControlPC = 0xbc;
public const int OFFSETOF__StackFrameIterator__m_isRuntimeWrappedExceptions = 0xb2;
public const int SIZEOF__StackFrameIterator = 0xcc;
public const int OFFSETOF__StackFrameIterator__m_AdjustedControlPC = 0xc8;
public const int OFFSETOF__StackFrameIterator__m_isRuntimeWrappedExceptions = 0xbe;
#endif // TARGET_64BIT

#endif // DEBUG
Expand Down
10 changes: 8 additions & 2 deletions src/coreclr/debug/daccess/dacdbiimplstackwalk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -647,13 +647,16 @@ FramePointer DacDbiInterfaceImpl::GetFramePointerWorker(StackFrameIterator * pIt
FramePointer fp;
switch (pIter->GetFrameState())
{
// For managed methods, we have the full CONTEXT. Additionally, we also have the caller CONTEXT
// on WIN64.
// Managed methods have the full CONTEXT. Caller-SP-based frame markers
// additionally require the caller CONTEXT.
case StackFrameIterator::SFITER_FRAMELESS_METHOD:
{
#ifdef TARGET_X86
fp = ComputeX86FramePointer(pRD->pCurrentContext);
#else
#if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
pCF->EnsureCallerContextIsValid();
#endif // TARGET_ARM || TARGET_ARM64 || TARGET_LOONGARCH64 || TARGET_RISCV64
fp = FramePointer::MakeFramePointer(GetRegdisplayStackMark(pRD));
#endif
break;
Expand All @@ -673,6 +676,9 @@ FramePointer DacDbiInterfaceImpl::GetFramePointerWorker(StackFrameIterator * pIt
UINT_PTR PCTAddr = pHijackedContext->Esp - sizeof(DWORD);
fp = FramePointer::MakeFramePointer(PCTAddr);
#else
#if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
pCF->EnsureCallerContextIsValid();
#endif // TARGET_ARM || TARGET_ARM64 || TARGET_LOONGARCH64 || TARGET_RISCV64
fp = FramePointer::MakeFramePointer(GetRegdisplayStackMark(pRD));
#endif
break;
Expand Down
7 changes: 7 additions & 0 deletions src/coreclr/debug/ee/frameinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1743,6 +1743,13 @@ StackWalkAction DebuggerWalkStackProc(CrawlFrame *pCF, void *data)
}

d->info.md = md;
#if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
// These targets use caller SP as the frame marker in the saved REGDISPLAY.
if (pCF->IsFrameless())
{
pCF->EnsureCallerContextIsValid();
}
#endif // TARGET_ARM || TARGET_ARM64 || TARGET_LOONGARCH64 || TARGET_RISCV64
CopyREGDISPLAY(&(d->info.registers), &(d->regDisplay));

#if defined(TARGET_AMD64)
Expand Down
26 changes: 15 additions & 11 deletions src/coreclr/inc/eetwain.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ enum ICodeManagerFlags
ReportFPBasedSlotsOnly
= 0x0200, // EnumGCRefs/EnumerateLiveSlots should only include
// slots that are based on the frame pointer
GcRootScan = 0x0400, // GC root scan: skip FP/vector context copies and report generic loader allocators
};

//*****************************************************************************
Expand Down Expand Up @@ -199,6 +200,8 @@ virtual bool HasTailCalls(EECodeInfo *pCodeInfo) = 0;
the virtual register set. Same reference location cannot be enumerated
multiple times (but all differenct references pointing to the same
object have to be individually enumerated).
With GcRootScan, hCallBack is a GCCONTEXT and promotion also reports
the generic context's loader allocator.
Returns success of operation.
*/
virtual bool EnumGcRefs(PREGDISPLAY pContext,
Expand Down Expand Up @@ -228,9 +231,11 @@ virtual OBJECTREF GetInstance(PREGDISPLAY pContext,
virtual PTR_VOID GetParamTypeArg(PREGDISPLAY pContext,
EECodeInfo * pCodeInfo) = 0;

// Returns the type of the context parameter (this, methodtable, methoddesc, or none)
// Returns the type of the context parameter (this, methodtable, methoddesc, or none),
// and optionally retrieves the generics token.
virtual GenericParamContextType GetParamContextType(PREGDISPLAY pContext,
EECodeInfo * pCodeInfo) = 0;
EECodeInfo * pCodeInfo,
PTR_VOID * ppGenericsToken = NULL) = 0;

/*
Returns the offset of the GuardStack cookie if it exists.
Expand Down Expand Up @@ -426,18 +431,16 @@ virtual
PTR_VOID GetParamTypeArg(PREGDISPLAY pContext,
EECodeInfo * pCodeInfo);

// Returns the type of the context parameter (this, methodtable, methoddesc, or none)
// Returns the type of the context parameter (this, methodtable, methoddesc, or none),
// and optionally retrieves the generics token.
virtual GenericParamContextType GetParamContextType(PREGDISPLAY pContext,
EECodeInfo * pCodeInfo);
EECodeInfo * pCodeInfo,
PTR_VOID * ppGenericsToken = NULL);

#ifdef USE_GC_INFO_DECODER
/*
Returns the generics token. This is used by GetInstance() and GetParamTypeArg() on WIN64.
Returns the generics token. This is used by profiler stackwalking.
*/
static
PTR_VOID GetExactGenericsToken(PREGDISPLAY pContext,
EECodeInfo * pCodeInfo);

static
PTR_VOID GetExactGenericsToken(TADDR sp,
TADDR fp,
Expand Down Expand Up @@ -515,7 +518,7 @@ HRESULT FixContextForEnC(PCONTEXT pCtx,
#endif // #ifndef DACCESS_COMPILE

virtual void EnsureCallerContextIsValid( PREGDISPLAY pRD, EECodeInfo * pCodeInfo = NULL, unsigned flags = 0);
static size_t GetCallerSp( PREGDISPLAY pRD );
static size_t GetCallerSp( PREGDISPLAY pRD, EECodeInfo * pCodeInfo, unsigned flags );
#ifdef TARGET_X86
static size_t GetResumeSp( PCONTEXT pContext );
#endif // TARGET_X86
Expand Down Expand Up @@ -600,7 +603,8 @@ PTR_VOID GetParamTypeArg(PREGDISPLAY pContext,
EECodeInfo * pCodeInfo);

virtual GenericParamContextType GetParamContextType(PREGDISPLAY pContext,
EECodeInfo * pCodeInfo);
EECodeInfo * pCodeInfo,
PTR_VOID * ppGenericsToken = NULL);

virtual
void * GetGSCookieAddr(PREGDISPLAY pContext,
Expand Down
72 changes: 72 additions & 0 deletions src/coreclr/inc/gcinfodecoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,29 @@ enum ICodeManagerFlags

#include "gcinfotypes.h"

namespace BitOperations
{
FORCEINLINE UINT32 TrailingZeroCount(size_t value)
{
_ASSERTE(value != 0);
#if defined(_MSC_VER)
unsigned long result;
#if defined(HOST_64BIT)
::_BitScanForward64(&result, value);
#else
::_BitScanForward(&result, value);
#endif
return static_cast<UINT32>(result);
#else
#if defined(HOST_64BIT)
return static_cast<UINT32>(__builtin_ctzll(value));
#else
return static_cast<UINT32>(__builtin_ctz(value));
#endif
#endif
}
}

#ifdef _DEBUG
#define MAX_PREDECODED_SLOTS 4
#else
Expand Down Expand Up @@ -347,6 +370,55 @@ class BitStreamReader
return result;
}

// Reads up to the remaining bits in the current word (at most maxBits and at most BITS_PER_SIZE_T).
// Returns the read bits in the low bits of the result and sets bitsRead to the number of bits consumed.
FORCEINLINE size_t ReadWord(UINT32 maxBits, UINT32& bitsRead)
{
SUPPORTS_DAC;
_ASSERTE(maxBits > 0);

if (m_RelPos == BITS_PER_SIZE_T)
{
m_pCurrent++;
m_current = *m_pCurrent;
m_RelPos = 0;
}

UINT32 availBits = (UINT32)(BITS_PER_SIZE_T - m_RelPos);
UINT32 chunkBits = _min(maxBits, availBits);
bitsRead = chunkBits;

size_t mask = (chunkBits == BITS_PER_SIZE_T) ? ~(size_t)0 : ((size_t{ 1 } << chunkBits) - 1);
size_t result = m_current & mask;
m_current = (chunkBits == BITS_PER_SIZE_T) ? 0 : (m_current >> chunkBits);
m_RelPos += chunkBits;

return result;
}

// Counts consecutive zero bits up to the next set bit, consumes the zero bits and that set bit,
// and returns the number of zero bits skipped.
FORCEINLINE UINT32 SkipZerosAndReadOne()
{
SUPPORTS_DAC;

UINT32 skipped = 0;
while (m_current == 0)
{
skipped += (UINT32)(BITS_PER_SIZE_T - m_RelPos);
m_pCurrent++;
m_current = *m_pCurrent;
m_RelPos = 0;
}

UINT32 tz = BitOperations::TrailingZeroCount(m_current);
skipped += tz;
UINT32 consume = tz + 1;
m_RelPos += consume;
m_current = (consume == BITS_PER_SIZE_T) ? 0 : (m_current >> consume);
return skipped;
}


FORCEINLINE size_t GetCurrentPos()
{
Expand Down
6 changes: 3 additions & 3 deletions src/coreclr/vm/cdacstress.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -832,9 +832,9 @@ static HRESULT CollectRuntimeStackRefs(Thread* pThread, PCONTEXT regs, SArray<St

// Use a callback that matches DAC behavior (DacStackReferenceWalker::Callback):
// Only call EnumGcRefs for frameless frames and GcScanRoots for explicit frames.
// Deliberately skip the post-scan logic (LCG resolver promotion,
// GcReportLoaderAllocator, generic param context) that GcStackCrawlCallBack
// includes — the DAC's callback has that logic disabled (#if 0).
// Deliberately skip LCG resolver and loader allocator promotion, including generic
// contexts, by leaving GcRootScan unset and omitting GcStackCrawlCallBack's post-scan
// logic. The DAC's callback has that logic disabled (#if 0).
struct DiagContext { GCCONTEXT* gcctx; RuntimeRefCollectionContext* collectCtx; };
DiagContext diagCtx = { &gcctx, &collectCtx };

Expand Down
Loading
Loading