Skip to content

Interpreter EH implementation #116046

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 2, 2025
Merged
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
688 changes: 607 additions & 81 deletions src/coreclr/interpreter/compiler.cpp

Large diffs are not rendered by default.

66 changes: 64 additions & 2 deletions src/coreclr/interpreter/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,46 +137,84 @@ enum InterpBBState
BBStateEmitted
};

enum InterpBBClauseType
{
BBClauseNone,
BBClauseTry,
BBClauseCatch,
BBClauseFinally,
BBClauseFilter,
};

struct InterpBasicBlock
{
int32_t index;
int32_t ilOffset, nativeOffset;
int32_t nativeEndOffset;
int32_t stackHeight;
StackInfo *pStackState;

InterpInst *pFirstIns, *pLastIns;
InterpBasicBlock *pNextBB;

// * If this basic block is a finally, this points to a finally call island that is located where the finally
// was before all funclets were moved to the end of the method.
// * If this basic block is a call island, this points to the next finally call island basic block.
// * Otherwise, this is NULL.
InterpBasicBlock *pFinallyCallIslandBB;
// Target of a leave instruction that is located in this basic block. NULL if there is none.
InterpBasicBlock *pLeaveTargetBB;

int inCount, outCount;
InterpBasicBlock **ppInBBs;
InterpBasicBlock **ppOutBBs;

InterpBBState emitState;

// Type of the innermost try block, catch, filter, or finally that contains this basic block.
uint8_t clauseType;

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

// If this basic block is a catch or filter funclet entry, this is the index of the variable
// that holds the exception object.
int clauseVarIndex;

// Number of catch, filter or finally clauses that overlap with this basic block.
int32_t overlappingEHClauseCount;

InterpBasicBlock(int32_t index) : InterpBasicBlock(index, 0) { }

InterpBasicBlock(int32_t index, int32_t ilOffset)
{
this->index = index;
this->ilOffset = ilOffset;
nativeOffset = -1;
nativeEndOffset = -1;
stackHeight = -1;

pFirstIns = pLastIns = NULL;
pNextBB = NULL;
pFinallyCallIslandBB = NULL;
pLeaveTargetBB = NULL;

inCount = 0;
outCount = 0;

emitState = BBStateNotEmitted;

clauseType = BBClauseNone;
isFilterOrCatchFuncletEntry = false;
clauseVarIndex = -1;
overlappingEHClauseCount = 0;
}
};

struct InterpVar
{
CORINFO_CLASS_HANDLE clsHnd;
InterpType interpType;
int indirects;
int offset;
int size;
// live_start and live_end are used by the offset allocator
Expand All @@ -202,7 +240,6 @@ struct InterpVar
offset = -1;
liveStart = NULL;
bbIndex = -1;
indirects = 0;

callArgs = false;
noCallArgs = false;
Expand Down Expand Up @@ -261,6 +298,17 @@ typedef class ICorJitInfo* COMP_HANDLE;

class InterpIAllocator;

// Entry of the table where for each leave instruction we store the first finally call island
// to be executed when the leave instruction is executed.
struct LeavesTableEntry
{
// offset of the CEE_LEAVE instruction
int32_t ilOffset;
// The BB of the call island BB that will be the first to call when the leave
// instruction is executed.
InterpBasicBlock *pFinallyCallIslandBB;
};

class InterpCompiler
{
friend class InterpIAllocator;
Expand All @@ -284,6 +332,10 @@ class InterpCompiler
int32_t m_currentILOffset;
InterpInst* m_pInitLocalsIns;

// Table of mappings of leave instructions to the first finally call island the leave
// needs to execute.
TArray<LeavesTableEntry> m_leavesTable;

// This represents a mapping from indexes to pointer sized data. During compilation, an
// instruction can request an index for some data (like a MethodDesc pointer), that it
// will then embed in the instruction stream. The data item table will be referenced
Expand All @@ -295,6 +347,7 @@ class InterpCompiler
int32_t GetDataItemIndexForHelperFtn(CorInfoHelpFunc ftn);

int GenerateCode(CORINFO_METHOD_INFO* methodInfo);
InterpBasicBlock* GenerateCodeForFinallyCallIslands(InterpBasicBlock *pNewBB, InterpBasicBlock *pPrevBB);
void PatchInitLocals(CORINFO_METHOD_INFO* methodInfo);

void ResolveToken(uint32_t token, CorInfoTokenKind tokenKind, CORINFO_RESOLVED_TOKEN *pResolvedToken);
Expand Down Expand Up @@ -347,6 +400,7 @@ class InterpCompiler
void EmitBranch(InterpOpcode opcode, int ilOffset);
void EmitOneArgBranch(InterpOpcode opcode, int ilOffset, int insSize);
void EmitTwoArgBranch(InterpOpcode opcode, int ilOffset, int insSize);
void EmitBranchToBB(InterpOpcode opcode, InterpBasicBlock *pTargetBB);

void EmitBBEndVarMoves(InterpBasicBlock *pTargetBB);
void InitBBStackState(InterpBasicBlock *pBB);
Expand All @@ -357,6 +411,9 @@ class InterpCompiler
int32_t m_varsSize = 0;
int32_t m_varsCapacity = 0;
int32_t m_numILVars = 0;
// For each catch or filter clause, we create a variable that holds the exception object.
// This is the index of the first such variable.
int32_t m_clauseVarsIndex = 0;

int32_t CreateVarExplicit(InterpType interpType, CORINFO_CLASS_HANDLE clsHnd, int size);

Expand Down Expand Up @@ -423,10 +480,14 @@ class InterpCompiler
int32_t ComputeCodeSize();
uint32_t ConvertOffset(int32_t offset);
void EmitCode();
int32_t* EmitBBCode(int32_t *ip, InterpBasicBlock *bb, TArray<Reloc*> *relocs);
int32_t* EmitCodeIns(int32_t *ip, InterpInst *pIns, TArray<Reloc*> *relocs);
void PatchRelocations(TArray<Reloc*> *relocs);
InterpMethod* CreateInterpMethod();
bool CreateBasicBlocks(CORINFO_METHOD_INFO* methodInfo);
bool InitializeClauseBuildingBlocks(CORINFO_METHOD_INFO* methodInfo);
void CreateFinallyCallIslandBasicBlocks(CORINFO_METHOD_INFO* methodInfo, int32_t leaveOffset, InterpBasicBlock* pLeaveTargetBB);
void GetNativeRangeForClause(uint32_t startILOffset, uint32_t endILOffset, int32_t *nativeStartOffset, int32_t* nativeEndOffset);

// Debug
void PrintClassName(CORINFO_CLASS_HANDLE cls);
Expand All @@ -443,6 +504,7 @@ class InterpCompiler

InterpMethod* CompileMethod();
void BuildGCInfo(InterpMethod *pInterpMethod);
void BuildEHInfo();

int32_t* GetCode(int32_t *pCodeSize);
};
Expand Down
6 changes: 5 additions & 1 deletion src/coreclr/interpreter/compileropt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,12 @@ void InterpCompiler::AllocOffsets()

int32_t opcode = InterpGetMovForType(m_pVars[newVar].interpType, false);
InterpInst *newInst = InsertInsBB(pBB, pIns->pPrev, opcode);
// The InsertInsBB assigns m_currentILOffset to ins->ilOffset, which is incorrect for
// instructions injected here. Copy the ilOffset from the call instruction instead.
newInst->ilOffset = pIns->ilOffset;

newInst->SetDVar(newVar);
newInst->SetSVar(newVar);
newInst->SetSVar(var);
if (opcode == INTOP_MOV_VT)
newInst->data[0] = m_pVars[var].size;
// The arg of the call is no longer global
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/interpreter/eeinterp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ CorJitResult CILInterp::compileMethod(ICorJitInfo* compHnd,

// We can't do this until we've called allocMem
compiler.BuildGCInfo(pMethod);
compiler.BuildEHInfo();

return CORJIT_OK;
}
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/interpreter/intops.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ int32_t CEEOpcodeSize(const uint8_t *ip, const uint8_t *codeEnd)
assert(0);
}

if ((ip + size) >= codeEnd)
if ((ip + size) > codeEnd)
return -1;

return (int32_t)((p - ip) + size);
Expand Down
11 changes: 10 additions & 1 deletion src/coreclr/interpreter/intops.def
Original file line number Diff line number Diff line change
Expand Up @@ -282,17 +282,26 @@ OPDEF(INTOP_CALLVIRT, "callvirt", 4, 1, 1, InterpOpMethodHandle)
OPDEF(INTOP_NEWOBJ, "newobj", 5, 1, 1, InterpOpMethodHandle)
OPDEF(INTOP_NEWOBJ_VT, "newobj.vt", 5, 1, 1, InterpOpMethodHandle)

OPDEF(INTOP_CALL_HELPER_PP, "call.helper.pp", 5, 1, 0, InterpOpThreeInts)
OPDEF(INTOP_CALL_HELPER_PP, "call.helper.pp", 4, 1, 0, InterpOpTwoInts)
OPDEF(INTOP_CALL_HELPER_PP_2, "call.helper.pp.2", 5, 1, 1, InterpOpTwoInts)

OPDEF(INTOP_CALL_FINALLY, "call.finally", 2, 0, 0, InterpOpBranch)

OPDEF(INTOP_ZEROBLK_IMM, "zeroblk.imm", 3, 0, 1, InterpOpInt)
OPDEF(INTOP_LOCALLOC, "localloc", 3, 1, 1, InterpOpNoArgs)
OPDEF(INTOP_BREAKPOINT, "breakpoint", 1, 0, 0, InterpOpNoArgs)

OPDEF(INTOP_THROW, "throw", 4, 0, 1, InterpOpInt)
OPDEF(INTOP_RETHROW, "rethrow", 1, 0, 0, InterpOpInt)
OPDEF(INTOP_LEAVE_FILTER, "leavefilter", 2, 0, 1, InterpOpNoArgs)
OPDEF(INTOP_LEAVE_CATCH, "leavecatch", 2, 0, 0, InterpOpBranch)
OPDEF(INTOP_LOAD_EXCEPTION, "load.exception", 2, 1, 0, InterpOpNoArgs)

OPDEF(INTOP_FAILFAST, "failfast", 1, 0, 0, InterpOpNoArgs)
OPDEF(INTOP_GC_COLLECT, "gc.collect", 1, 0, 0, InterpOpNoArgs)

OPDEF(INTOP_LOAD_FRAMEVAR, "load.framevar", 2, 1, 0, InterpOpNoArgs)

// All instructions after this point are IROPS, instructions that are not emitted/executed
OPDEF(INTOP_NOP, "nop", 1, 0, 0, InterpOpNoArgs)
OPDEF(INTOP_DEF, "def", 2, 1, 0, InterpOpNoArgs)
Expand Down
67 changes: 65 additions & 2 deletions src/coreclr/vm/codeman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4375,8 +4375,71 @@ BOOL InterpreterJitManager::JitCodeToMethodInfo(

TADDR InterpreterJitManager::GetFuncletStartAddress(EECodeInfo * pCodeInfo)
{
// Interpreter-TODO: Verify that this is correct
return pCodeInfo->GetCodeAddress() - pCodeInfo->GetRelOffset();
EH_CLAUSE_ENUMERATOR enumState;
unsigned ehCount;

IJitManager *pJitMan = pCodeInfo->GetJitManager();
ehCount = pJitMan->InitializeEHEnumeration(pCodeInfo->GetMethodToken(), &enumState);
DWORD relOffset = pCodeInfo->GetRelOffset();
TADDR methodBaseAddress = pCodeInfo->GetCodeAddress() - relOffset;

for (unsigned i = 0; i < ehCount; i++)
{
EE_ILEXCEPTION_CLAUSE ehClause;
pJitMan->GetNextEHClause(&enumState, &ehClause);

if ((ehClause.HandlerStartPC <= relOffset) && (relOffset < ehClause.HandlerEndPC))
{
return methodBaseAddress + ehClause.HandlerStartPC;
}

// For filters, we also need to check the filter funclet range. The filter funclet is always stored right
// before its handler funclet (according to ECMA-355). So the filter end offset is equal to the start offset of the handler funclet.
if (IsFilterHandler(&ehClause) && (ehClause.FilterOffset <= relOffset) && (relOffset < ehClause.HandlerStartPC))
{
return methodBaseAddress + ehClause.FilterOffset;
}
}

return methodBaseAddress;
}

DWORD InterpreterJitManager::GetFuncletStartOffsets(const METHODTOKEN& MethodToken, DWORD* pStartFuncletOffsets, DWORD dwLength)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
}
CONTRACTL_END;

EH_CLAUSE_ENUMERATOR enumState;
unsigned ehCount;

ehCount = InitializeEHEnumeration(MethodToken, &enumState);

DWORD nFunclets = 0;
for (unsigned i = 0; i < ehCount; i++)
{
EE_ILEXCEPTION_CLAUSE ehClause;
GetNextEHClause(&enumState, &ehClause);
if (nFunclets < dwLength)
{
pStartFuncletOffsets[nFunclets] = ehClause.HandlerStartPC;
}
nFunclets++;
if (IsFilterHandler(&ehClause))
{
if (nFunclets < dwLength)
{
pStartFuncletOffsets[nFunclets] = ehClause.FilterOffset;
}

nFunclets++;
}
}

return nFunclets;
}

void InterpreterJitManager::JitTokenToMethodRegionInfo(const METHODTOKEN& MethodToken, MethodRegionInfo * methodRegionInfo)
Expand Down
8 changes: 1 addition & 7 deletions src/coreclr/vm/codeman.h
Original file line number Diff line number Diff line change
Expand Up @@ -2759,13 +2759,7 @@ class InterpreterJitManager final : public EECodeGenManager
}

virtual TADDR GetFuncletStartAddress(EECodeInfo * pCodeInfo);

virtual DWORD GetFuncletStartOffsets(const METHODTOKEN& MethodToken, DWORD* pStartFuncletOffsets, DWORD dwLength)
{
// Not used for the interpreter
_ASSERTE(FALSE);
return 0;
}
virtual DWORD GetFuncletStartOffsets(const METHODTOKEN& MethodToken, DWORD* pStartFuncletOffsets, DWORD dwLength);

#if !defined DACCESS_COMPILE
protected:
Expand Down
53 changes: 50 additions & 3 deletions src/coreclr/vm/eetwain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2154,9 +2154,56 @@ void EECodeManager::UpdateSSP(PREGDISPLAY pRD)
#ifdef FEATURE_INTERPRETER
DWORD_PTR InterpreterCodeManager::CallFunclet(OBJECTREF throwable, void* pHandler, REGDISPLAY *pRD, ExInfo *pExInfo, bool isFilter)
{
// Interpreter-TODO: implement calling the funclet in the intepreted code
_ASSERTE(FALSE);
return 0;
Thread *pThread = GetThread();
InterpThreadContext *threadContext = pThread->GetInterpThreadContext();
if (threadContext == nullptr || threadContext->pStackStart == nullptr)
{
COMPlusThrow(kOutOfMemoryException);
}
int8_t *sp = threadContext->pStackPointer;

// This construct ensures that the InterpreterFrame is always stored at a higher address than the
// InterpMethodContextFrame. This is important for the stack walking code.
struct Frames
{
InterpMethodContextFrame interpMethodContextFrame = {0};
InterpreterFrame interpreterFrame;

Frames(TransitionBlock* pTransitionBlock)
: interpreterFrame(pTransitionBlock, &interpMethodContextFrame)
{
}
}
frames(NULL);

InterpMethodContextFrame *pOriginalFrame = (InterpMethodContextFrame*)GetRegdisplaySP(pRD);

StackVal retVal;

frames.interpMethodContextFrame.startIp = pOriginalFrame->startIp;
frames.interpMethodContextFrame.pStack = isFilter ? sp : pOriginalFrame->pStack;
frames.interpMethodContextFrame.pRetVal = (int8_t*)&retVal;

ExceptionClauseArgs exceptionClauseArgs;
exceptionClauseArgs.ip = (const int32_t *)pHandler;
exceptionClauseArgs.pFrame = pOriginalFrame;
exceptionClauseArgs.isFilter = isFilter;
exceptionClauseArgs.throwable = throwable;

InterpExecMethod(&frames.interpreterFrame, &frames.interpMethodContextFrame, threadContext, &exceptionClauseArgs);

frames.interpreterFrame.Pop();

if (isFilter)
{
// The filter funclet returns the result of the filter funclet (EXCEPTION_CONTINUE_SEARCH (0) or EXCEPTION_EXECUTE_HANDLER (1))
return retVal.data.i;
}
else
{
// The catch funclet returns the address to resume at after the catch returns.
return (DWORD_PTR)retVal.data.s;
}
}

void InterpreterCodeManager::ResumeAfterCatch(CONTEXT *pContext, size_t targetSSP, bool fIntercepted)
Expand Down
Loading
Loading