Skip to content
Closed
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
122 changes: 89 additions & 33 deletions src/coreclr/interpreter/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -735,7 +735,22 @@ uint32_t InterpCompiler::ConvertOffset(int32_t offset)
return offset * sizeof(int32_t) + sizeof(void*);
}

int32_t* InterpCompiler::EmitCodeIns(int32_t *ip, InterpInst *ins, TArray<Reloc*, MemPoolAllocator> *relocs)
int32_t InterpCompiler::GetFuncletAdjustedVarOffset(int varIndex, bool forFunclet)
{
#ifndef FEATURE_REUSE_INTERPRETER_STACK_FOR_NORMAL_FUNCLETS
if (forFunclet && m_pVars[varIndex].global)
{
// In funclets, global vars are accessed relative to the main method frame pointer
return -(m_pVars[varIndex].offset + FUNCLET_STACK_ADJUSTMENT_OFFSET);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not an absolute requirement, but if var offsets are no longer actual offsets in certain configurations, I'd like to see var.offset become something other than an int, so the type system stops us from using it incorrectly - I want any misuse of .offset to fail to compile instead of silently doing the wrong thing.

}
else
#endif // FEATURE_REUSE_INTERPRETER_STACK_FOR_NORMAL_FUNCLETS
{
return m_pVars[varIndex].offset;
}
}

int32_t* InterpCompiler::EmitCodeIns(int32_t *ip, InterpInst *ins, TArray<Reloc*, MemPoolAllocator> *relocs, bool forFunclet)
{
ins->nativeOffset = (int32_t)(ip - m_pMethodCode);

Expand All @@ -749,7 +764,7 @@ int32_t* InterpCompiler::EmitCodeIns(int32_t *ip, InterpInst *ins, TArray<Reloc*
if (opcode == INTOP_SWITCH)
{
int32_t numLabels = ins->data [0];
*ip++ = m_pVars[ins->sVars[0]].offset;
*ip++ = GetFuncletAdjustedVarOffset(ins->sVars[0], forFunclet);
Copy link
Copy Markdown
Member

@kg kg Sep 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re the comment I just left, in this context: could we do something like:

  • Change var's offset to private: m_offset
  • Add a new int32_t var.offset(bool forFunclet) { ... }

Then we aren't mixing InterpVar and indexOfInterpVar in various places and accessing the offset is always done in a way that makes the funclet-ness explicit.

*ip++ = numLabels;
// Add relocation for each label
for (int32_t i = 0; i < numLabels; i++)
Expand All @@ -764,7 +779,7 @@ int32_t* InterpCompiler::EmitCodeIns(int32_t *ip, InterpInst *ins, TArray<Reloc*
{
int32_t brBaseOffset = (int32_t)(startIp - m_pMethodCode);
for (int i = 0; i < g_interpOpSVars[opcode]; i++)
*ip++ = m_pVars[ins->sVars[i]].offset;
*ip++ = GetFuncletAdjustedVarOffset(ins->sVars[i], forFunclet);

if (ins->info.pTargetBB->nativeOffset >= 0)
{
Expand Down Expand Up @@ -796,9 +811,13 @@ int32_t* InterpCompiler::EmitCodeIns(int32_t *ip, InterpInst *ins, TArray<Reloc*
// Revert opcode emit
ip--;

int destOffset = m_pVars[ins->dVar].offset;
int srcOffset = m_pVars[ins->sVars[0]].offset;
srcOffset += fOffset;
int destOffset = GetFuncletAdjustedVarOffset(ins->dVar, forFunclet);
int srcOffset = GetFuncletAdjustedVarOffset(ins->sVars[0], forFunclet);
if (srcOffset >= 0)
srcOffset += fOffset;
else
srcOffset -= fOffset;
Comment on lines +816 to +819
Copy link

Copilot AI Sep 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for adjusting srcOffset based on its sign should include a comment explaining why negative offsets are subtracted from fOffset while positive offsets are added to it, as this relates to the funclet frame pointer handling.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love this. If we go with my idea of making var.offset(...) an accessor maybe it should have an optional parameter (default value 0) that specifies an offset within the var, and we won't need this weird 'if' everywhere we're accessing locations relative to the offset of a var.

(This also just looks wrong to me, but it might make sense once I've read the whole PR and thought about it.)


if (fSize)
opcode = INTOP_MOV_VT;
else
Expand All @@ -819,8 +838,8 @@ int32_t* InterpCompiler::EmitCodeIns(int32_t *ip, InterpInst *ins, TArray<Reloc*
{
// This opcode references a var, int sVars[0], but it is not registered as a source for it
// aka g_interpOpSVars[INTOP_LDLOCA] is 0.
*ip++ = m_pVars[ins->dVar].offset;
*ip++ = m_pVars[ins->sVars[0]].offset;
*ip++ = GetFuncletAdjustedVarOffset(ins->dVar, forFunclet);
*ip++ = GetFuncletAdjustedVarOffset(ins->sVars[0], forFunclet);
}
else
{
Expand All @@ -829,7 +848,7 @@ int32_t* InterpCompiler::EmitCodeIns(int32_t *ip, InterpInst *ins, TArray<Reloc*
// variable we emit another offset. Finally, we will emit any additional data needed
// by the instruction.
if (g_interpOpDVars[opcode])
*ip++ = m_pVars[ins->dVar].offset;
*ip++ = GetFuncletAdjustedVarOffset(ins->dVar, forFunclet);

if (g_interpOpSVars[opcode])
{
Expand All @@ -841,7 +860,7 @@ int32_t* InterpCompiler::EmitCodeIns(int32_t *ip, InterpInst *ins, TArray<Reloc*
}
else
{
*ip++ = m_pVars[ins->sVars[i]].offset;
*ip++ = GetFuncletAdjustedVarOffset(ins->sVars[i], forFunclet);
}
}
}
Expand Down Expand Up @@ -927,7 +946,7 @@ int32_t *InterpCompiler::EmitBBCode(int32_t *ip, InterpBasicBlock *bb, TArray<Re
continue;
}

ip = EmitCodeIns(ip, ins, relocs);
ip = EmitCodeIns(ip, ins, relocs, bb->clauseNonTryType != BBClauseNone);
}

m_pCBB->nativeEndOffset = (int32_t)(ip - m_pMethodCode);
Expand Down Expand Up @@ -1156,17 +1175,28 @@ class InterpGcSlotAllocator

void AllocateOrReuseGcSlot(uint32_t offsetBytes, GcSlotFlags flags)
{
GcSlotId *pSlot = LocateGcSlotTableEntry(offsetBytes, flags);
bool allocateNewSlot = *pSlot == ((GcSlotId)-1);

if (allocateNewSlot)
GcSlotId slot;
bool allocateNewSlot;
if (flags & GC_SLOT_UNTRACKED)
{
// Important to pass GC_FRAMEREG_REL, the default is broken due to GET_CALLER_SP being unimplemented
*pSlot = m_encoder->GetStackSlotId(offsetBytes, flags, GC_FRAMEREG_REL);
allocateNewSlot = true;
slot = m_encoder->GetStackSlotId(offsetBytes, flags, (flags & GC_SLOT_UNTRACKED) ? GC_FRAMEREG_REL : GC_SP_REL);;
}
else
{
assert((flags & GC_SLOT_UNTRACKED) == 0);
GcSlotId *pSlot = LocateGcSlotTableEntry(offsetBytes, flags);

allocateNewSlot = *pSlot == ((GcSlotId)-1);

if (allocateNewSlot)
{
// Important to pass GC_FRAMEREG_REL, the default is broken due to GET_CALLER_SP being unimplemented
slot = *pSlot = m_encoder->GetStackSlotId(offsetBytes, flags, (flags & GC_SLOT_UNTRACKED) ? GC_FRAMEREG_REL : GC_SP_REL);
}
else
{
slot = *pSlot;
}
}

INTERP_DUMP(
Expand All @@ -1175,7 +1205,7 @@ class InterpGcSlotAllocator
(flags & GC_SLOT_UNTRACKED) ? "global " : "",
(flags & GC_SLOT_INTERIOR) ? "interior " : "",
(flags & GC_SLOT_PINNED) ? "pinned " : "",
*pSlot,
slot,
offsetBytes
);
}
Expand Down Expand Up @@ -2084,17 +2114,29 @@ void InterpCompiler::InitializeClauseBuildingBlocks(CORINFO_METHOD_INFO* methodI
for (uint32_t j = clause.HandlerOffset; j < (clause.HandlerOffset + clause.HandlerLength); j++)
{
InterpBasicBlock* pBB = m_ppOffsetToBB[j];
if (pBB != NULL && pBB->clauseType == BBClauseNone)

if (pBB == NULL)
continue;

uint8_t clauseType;
if ((clause.Flags == CORINFO_EH_CLAUSE_NONE) || (clause.Flags == CORINFO_EH_CLAUSE_FILTER))
{
if ((clause.Flags == CORINFO_EH_CLAUSE_NONE) || (clause.Flags == CORINFO_EH_CLAUSE_FILTER))
{
pBB->clauseType = BBClauseCatch;
}
else
{
assert((clause.Flags == CORINFO_EH_CLAUSE_FINALLY) || (clause.Flags == CORINFO_EH_CLAUSE_FAULT));
pBB->clauseType = BBClauseFinally;
}
clauseType = BBClauseCatch;
}
else
{
assert((clause.Flags == CORINFO_EH_CLAUSE_FINALLY) || (clause.Flags == CORINFO_EH_CLAUSE_FAULT));
clauseType = BBClauseFinally;
}

if (pBB->clauseType == BBClauseNone)
{
pBB->clauseType = clauseType;
}

if (pBB->clauseNonTryType == BBClauseNone)
{
pBB->clauseNonTryType = clauseType;
}
}

Expand All @@ -2118,10 +2160,17 @@ void InterpCompiler::InitializeClauseBuildingBlocks(CORINFO_METHOD_INFO* methodI
for (uint32_t j = clause.FilterOffset; j < clause.HandlerOffset; j++)
{
InterpBasicBlock* pBB = m_ppOffsetToBB[j];
if (pBB != NULL && pBB->clauseType == BBClauseNone)
if (pBB == NULL)
continue;

if (pBB->clauseType == BBClauseNone)
{
pBB->clauseType = BBClauseFilter;
}
if (pBB->clauseNonTryType == BBClauseNone)
{
pBB->clauseNonTryType = BBClauseFilter;
}
}
}
else if (clause.Flags == CORINFO_EH_CLAUSE_FINALLY|| clause.Flags == CORINFO_EH_CLAUSE_FAULT)
Expand Down Expand Up @@ -2165,6 +2214,7 @@ void InterpCompiler::InitializeClauseBuildingBlocks(CORINFO_METHOD_INFO* methodI
InterpBasicBlock* pAfterFinallyBB = m_ppOffsetToBB[clause.HandlerOffset + clause.HandlerLength];
assert(pAfterFinallyBB != NULL);
pFinallyCallIslandBB->clauseType = pAfterFinallyBB->clauseType;
pFinallyCallIslandBB->clauseNonTryType = pAfterFinallyBB->clauseNonTryType;
pFinallyCallIslandBB = pFinallyCallIslandBB->pNextBB;
}
}
Expand Down Expand Up @@ -2266,7 +2316,8 @@ void InterpCompiler::EmitLoadVar(int32_t var)
InterpType interpType = m_pVars[var].interpType;
CORINFO_CLASS_HANDLE clsHnd = m_pVars[var].clsHnd;

if (m_pCBB->clauseType == BBClauseFilter)
#ifdef FEATURE_REUSE_INTERPRETER_STACK_FOR_NORMAL_FUNCLETS
if (m_pCBB->clauseNonTryType == BBClauseFilter)
{
assert(m_pVars[var].ILGlobal);
AddIns(INTOP_LOAD_FRAMEVAR);
Expand All @@ -2275,6 +2326,7 @@ void InterpCompiler::EmitLoadVar(int32_t var)
EmitLdind(interpType, clsHnd, m_pVars[var].offset);
return;
}
#endif // FEATURE_REUSE_INTERPRETER_STACK_FOR_NORMAL_FUNCLETS

int32_t size = m_pVars[var].size;

Expand All @@ -2301,14 +2353,16 @@ void InterpCompiler::EmitStoreVar(int32_t var)
InterpType interpType = m_pVars[var].interpType;
CHECK_STACK(1);

if (m_pCBB->clauseType == BBClauseFilter)
#ifdef FEATURE_REUSE_INTERPRETER_STACK_FOR_NORMAL_FUNCLETS
if (m_pCBB->clauseNonTryType == BBClauseFilter)
{
AddIns(INTOP_LOAD_FRAMEVAR);
PushInterpType(InterpTypeI, NULL);
m_pLastNewIns->SetDVar(m_pStackPointer[-1].var);
EmitStind(interpType, m_pVars[var].clsHnd, m_pVars[var].offset, true /* reverseSVarOrder */);
return;
}
#endif // FEATURE_REUSE_INTERPRETER_STACK_FOR_NORMAL_FUNCLETS

#ifdef TARGET_64BIT
// nint and int32 can be used interchangeably. Add implicit conversions.
Expand Down Expand Up @@ -3972,7 +4026,8 @@ void InterpCompiler::EmitLdLocA(int32_t var)
m_shadowCopyOfThisPointerActuallyNeeded = true;
}

if (m_pCBB->clauseType == BBClauseFilter)
#ifdef FEATURE_REUSE_INTERPRETER_STACK_FOR_NORMAL_FUNCLETS
if (m_pCBB->clauseNonTryType == BBClauseFilter)
{
AddIns(INTOP_LOAD_FRAMEVAR);
PushInterpType(InterpTypeI, NULL);
Expand All @@ -3985,6 +4040,7 @@ void InterpCompiler::EmitLdLocA(int32_t var)
m_pLastNewIns->SetDVar(m_pStackPointer[-1].var);
return;
}
#endif // FEATURE_REUSE_INTERPRETER_STACK_FOR_NORMAL_FUNCLETS

AddIns(INTOP_LDLOCA);
m_pLastNewIns->SetSVar(var);
Expand Down
7 changes: 6 additions & 1 deletion src/coreclr/interpreter/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,9 @@ struct InterpBasicBlock
// Type of the innermost try block, catch, filter, or finally that contains this basic block.
uint8_t clauseType;

// Type of the innermost catch, filter, or finally that contains this basic block. Try blocks within catch, filter or finally blocks are accounted as catch, filter or finally blocks.
uint8_t clauseNonTryType;

// True indicates that this basic block is the first block of a filter, catch or filtered handler funclet.
bool isFilterOrCatchFuncletEntry;

Expand Down Expand Up @@ -358,6 +361,7 @@ struct InterpBasicBlock
emitState = BBStateNotEmitted;

clauseType = BBClauseNone;
clauseNonTryType = BBClauseNone;
isFilterOrCatchFuncletEntry = false;
clauseVarIndex = -1;
overlappingEHClauseCount = 0;
Expand Down Expand Up @@ -775,7 +779,8 @@ class InterpCompiler
uint32_t ConvertOffset(int32_t offset);
void EmitCode();
int32_t* EmitBBCode(int32_t *ip, InterpBasicBlock *bb, TArray<Reloc*, MemPoolAllocator> *relocs);
int32_t* EmitCodeIns(int32_t *ip, InterpInst *pIns, TArray<Reloc*, MemPoolAllocator> *relocs);
int32_t* EmitCodeIns(int32_t *ip, InterpInst *pIns, TArray<Reloc*, MemPoolAllocator> *relocs, bool forFunclet);
int32_t GetFuncletAdjustedVarOffset(int varIndex, bool forFunclet);
void PatchRelocations(TArray<Reloc*, MemPoolAllocator> *relocs);
InterpMethod* CreateInterpMethod();
void CreateBasicBlocks(CORINFO_METHOD_INFO* methodInfo);
Expand Down
5 changes: 5 additions & 0 deletions src/coreclr/interpreter/compileropt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,12 @@ void InterpCompiler::AllocOffsets()
ForEachInsVar(pIns, pIns, &InterpCompiler::SetVarLiveRangeCB);
insIndex++;
}
#ifdef FEATURE_REUSE_INTERPRETER_STACK_FOR_NORMAL_FUNCLETS
int32_t currentOffset = m_totalVarsStackSize;
#else
// The first interp stack slot in a funclet is reserved to hold the pointer to the parent frame's stack, the rest is available for local vars
int32_t currentOffset = pBB->clauseNonTryType == BBClauseNone ? m_totalVarsStackSize : INTERP_STACK_SLOT_SIZE;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need an extra variable for the parent stack frame pointer? Cannot we just read it from the InterpMethodContextFrame?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe that works for funclets not invoked a non-exception finallys. The InterpMethodContextFrame parent pointer doesn't point to the outer frame in those cases.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could set it, the INTOP_CALL_FINALLY initializes the InterpMethodContextFrame explicitly.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, my concern is actually the case for when finallys are NOT invoked via INTOP_CALL_FINALLY. In that case it isn't set. But as we are probably going to go with the move the second pass out of native code, I'm not likely to invest in this change any more.

#endif // FEATURE_REUSE_INTERPRETER_STACK_FOR_NORMAL_FUNCLETS

insIndex = 0;
for (pIns = pBB->pFirstIns; pIns != NULL; pIns = pIns->pNext) {
Expand Down
10 changes: 10 additions & 0 deletions src/coreclr/interpreter/inc/interpretershared.h
Original file line number Diff line number Diff line change
Expand Up @@ -178,4 +178,14 @@ enum class CalliFlags : int32_t
PInvoke = 1 << 2, // The call is a PInvoke call
};

// FEATURE_REUSE_INTERPRETER_STACK_FOR_NORMAL_FUNCLETS can be enabled to reuse the stack frame of the outer function for executing catch, finally and fault funclets
// This feature is not compatible with fully interpreted code, however, as while we execute the funclet we may overwrite the stack space of the DispatchEx
// function which is part of the actual funclet dispatch path. We may wish to re-enable this feature in the future if we run on a platform where
// we can guarantee that the DispatchEx and other related functions that make of the EH subsystem will not be interpreted.
//#define FEATURE_REUSE_INTERPRETER_STACK_FOR_NORMAL_FUNCLETS

#ifndef FEATURE_REUSE_INTERPRETER_STACK_FOR_NORMAL_FUNCLETS
#define FUNCLET_STACK_ADJUSTMENT_OFFSET 8
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an explanation somewhere of why this define exists and why it's 8? I haven't found it yet, so you can ignore this if the answer is 'yes'. I expected the answer to be here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, the reason is that we need the "negative" numbers to be distinguishable from the normal accesses, and most notably, we need access to the 0 offset of the normal frame from the funclet. To make the handling of the negative offsets very simple in the interpreter, we adjust those offsets by an arbitrary value. Any positive number here would actually work. I happened to choose 8 since that is the size of a interpreter stack slot, but it might actually be easier to read if we chose a much bigger number like 10000 or something, so that the negated offsets would match up more easily to the positive space offsets.

#endif // FEATURE_REUSE_INTERPRETER_STACK_FOR_NORMAL_FUNCLETS

#endif
30 changes: 22 additions & 8 deletions src/coreclr/vm/eetwain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2157,7 +2157,7 @@ DWORD_PTR InterpreterCodeManager::CallFunclet(OBJECTREF throwable, void* pHandle
// InterpMethodContextFrame. This is important for the stack walking code.
struct Frames
{
InterpMethodContextFrame interpMethodContextFrame = {0};
InterpMethodContextFrame interpMethodContextFrame;
Comment thread
kg marked this conversation as resolved.
InterpreterFrame interpreterFrame;

Frames(TransitionBlock* pTransitionBlock)
Expand All @@ -2176,9 +2176,25 @@ DWORD_PTR InterpreterCodeManager::CallFunclet(OBJECTREF throwable, void* pHandle

StackVal retVal;

frames.interpMethodContextFrame.startIp = pOriginalFrame->startIp;
frames.interpMethodContextFrame.pStack = isFilter ? sp : pOriginalFrame->pStack;
frames.interpMethodContextFrame.pRetVal = (int8_t*)&retVal;
#ifndef FEATURE_REUSE_INTERPRETER_STACK_FOR_NORMAL_FUNCLETS
// Frame pointer for original method
int8_t* originalStack;
if (pOriginalFrame->IsFuncletFrame())
{
originalStack = *(int8_t**)pOriginalFrame->pStack;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checking: It's a funclet frame, so we reserved the first sizeof(void*) bytes of the funclet frame's stack to contain the address of the original frame's stack, and we're extracting it from there.

}
else
{
originalStack = pOriginalFrame->pStack - FUNCLET_STACK_ADJUSTMENT_OFFSET;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love having this manual adjustment here. Is it possible to bake it into var offset allocation instead and just reserve a magic var at offset 0 for this? Then the stack would be at pStack, I think?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternately, could this data live in the frame instead of being tucked into a weird spot on the stack with offset math in multiple places?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm moving this logic to a helper method on the InterpMethodContextFrame.

}
*(int8_t**)sp = originalStack;
InterpreterFrameReporting frameReporting = isFilter ? InterpreterFrameReporting::FuncletNoReportGlobals : InterpreterFrameReporting::FuncletReportGlobals;
#else
sp = isFilter ? sp : pOriginalFrame->pStack;
InterpreterFrameReporting frameReporting = InterpreterFrameReporting::Normal;
#endif

frames.interpMethodContextFrame.ReInit(NULL, pOriginalFrame->startIp, (int8_t*)&retVal, frameReporting, sp);

ExceptionClauseArgs exceptionClauseArgs;
exceptionClauseArgs.ip = (const int32_t *)pHandler;
Expand Down Expand Up @@ -2604,8 +2620,7 @@ OBJECTREF InterpreterCodeManager::GetInstance(PREGDISPLAY pContext,
EECodeInfo * pCodeInfo)
{
PTR_InterpMethodContextFrame frame = dac_cast<PTR_InterpMethodContextFrame>(GetSP(pContext->pCurrentContext));
TADDR baseStackSlot = dac_cast<TADDR>((uintptr_t)frame->pStack);
return *dac_cast<PTR_OBJECTREF>(baseStackSlot);
return *dac_cast<PTR_OBJECTREF>(frame->GetFunctionFrameStack());
}

PTR_VOID InterpreterCodeManager::GetParamTypeArg(PREGDISPLAY pContext,
Expand All @@ -2624,8 +2639,7 @@ PTR_VOID InterpreterCodeManager::GetParamTypeArg(PREGDISPLAY pContext,
if (spOffsetGenericsContext != NO_GENERICS_INST_CONTEXT)
{
PTR_InterpMethodContextFrame frame = dac_cast<PTR_InterpMethodContextFrame>(GetSP(pContext->pCurrentContext));
TADDR baseStackSlot = dac_cast<TADDR>((uintptr_t)frame->pStack);
TADDR taSlot = (TADDR)( spOffsetGenericsContext + baseStackSlot );
TADDR taSlot = (TADDR)(spOffsetGenericsContext + frame->GetFunctionFrameStack());
TADDR taExactGenericsToken = *PTR_TADDR(taSlot);
return PTR_VOID(taExactGenericsToken);
}
Expand Down
Loading
Loading