Skip to content

Commit 8d59055

Browse files
authored
Interpreter EH implementation (#116046)
* Interpreter EH implementation This change implements EH support in the interpreter compiler and execution parts. Here is a summary of the changes: On the compilation side: * Adds support for CEE_THROW, CEE_RETHROW, CEE_ENDFILTER, CEE_ENDFINALLY, CEE_LEAVE and CEE_ISINST opcodes * Adds building of EH info with IR code offsets * Implements proper funclet handling in the same way the JIT does. All handlers and filters are moved to the end of the method and out of any try ranges recursively to enable proper behavior of the EH. * Fixes a bug related to wrong SVar and and an issue with IL offset of inserted instructions in AllocOffsets * Fixes a bug in the CEEOpcodeSize - off by one check for the end of the code On the execution side: * Add funclet start address extraction * Add calling funclets in the interpreted code * Removed GCX_PREEMP_NO_DTOR from the CallDescrWorkerUnwindFrameChainHandler, because it was not correct * Added new IR opcodes: * INTOP_LOAD_FRAMEVAR to load parent frame stack pointer in filter funclets that need to run in the context of the parent, but at the top of the current interpreter stack. * INTOP_RETHROW to rethrow an exception * INTOP_CALL_FINALLY to call finally funclet in non-exceptional cases * INTOP_LEAVE_FILTER to exit a filter funclet and return the filter result * INTOP_LEAVE_CATCH to exit a catch handler and return the resume address * Added calls to COMPlusThrow for division by zero, stack overflow and few other exceptions where the interpreter had just TODO and assert for adding those. * Modified the InterpExecMethod so that extra information can be passed in case of a funclet invocation. It also adds tests to verify various interesting EH scenarios Here are some more details on moving out the funclets: For each finally funclet, we create a finally call island that stays in the code where the original finally was. That island calls the finally and then branches to the next (outer) finally if any. The last finally call island branches to the actual leave target. The interpreter compiler generates a separate sequence of finally call islands for each leave instruction target. And when the leave is executed, it jumps to the beginning or into the middle of the chain. In other words, for example, if all leave instructions in the method go to the same target, there would be just one call finally island chain. * PR feedback * Fix linux build - missing UNREACHABLE(); * Fix issue in checked build of the tests The call finally islands were not classified by the clause they are in. Their instructions had IL offsets set to the offset of the finally block, but they need to have no IL offset as they are not generated from IL. * PR feedback
1 parent 2d5a2ee commit 8d59055

File tree

16 files changed

+1554
-167
lines changed

16 files changed

+1554
-167
lines changed

src/coreclr/interpreter/compiler.cpp

Lines changed: 607 additions & 81 deletions
Large diffs are not rendered by default.

src/coreclr/interpreter/compiler.h

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,46 +137,84 @@ enum InterpBBState
137137
BBStateEmitted
138138
};
139139

140+
enum InterpBBClauseType
141+
{
142+
BBClauseNone,
143+
BBClauseTry,
144+
BBClauseCatch,
145+
BBClauseFinally,
146+
BBClauseFilter,
147+
};
148+
140149
struct InterpBasicBlock
141150
{
142151
int32_t index;
143152
int32_t ilOffset, nativeOffset;
153+
int32_t nativeEndOffset;
144154
int32_t stackHeight;
145155
StackInfo *pStackState;
146156

147157
InterpInst *pFirstIns, *pLastIns;
148158
InterpBasicBlock *pNextBB;
149159

160+
// * If this basic block is a finally, this points to a finally call island that is located where the finally
161+
// was before all funclets were moved to the end of the method.
162+
// * If this basic block is a call island, this points to the next finally call island basic block.
163+
// * Otherwise, this is NULL.
164+
InterpBasicBlock *pFinallyCallIslandBB;
165+
// Target of a leave instruction that is located in this basic block. NULL if there is none.
166+
InterpBasicBlock *pLeaveTargetBB;
167+
150168
int inCount, outCount;
151169
InterpBasicBlock **ppInBBs;
152170
InterpBasicBlock **ppOutBBs;
153171

154172
InterpBBState emitState;
155173

174+
// Type of the innermost try block, catch, filter, or finally that contains this basic block.
175+
uint8_t clauseType;
176+
177+
// True indicates that this basic block is the first block of a filter, catch or filtered handler funclet.
178+
bool isFilterOrCatchFuncletEntry;
179+
180+
// If this basic block is a catch or filter funclet entry, this is the index of the variable
181+
// that holds the exception object.
182+
int clauseVarIndex;
183+
184+
// Number of catch, filter or finally clauses that overlap with this basic block.
185+
int32_t overlappingEHClauseCount;
186+
156187
InterpBasicBlock(int32_t index) : InterpBasicBlock(index, 0) { }
157188

158189
InterpBasicBlock(int32_t index, int32_t ilOffset)
159190
{
160191
this->index = index;
161192
this->ilOffset = ilOffset;
162193
nativeOffset = -1;
194+
nativeEndOffset = -1;
163195
stackHeight = -1;
164196

165197
pFirstIns = pLastIns = NULL;
166198
pNextBB = NULL;
199+
pFinallyCallIslandBB = NULL;
200+
pLeaveTargetBB = NULL;
167201

168202
inCount = 0;
169203
outCount = 0;
170204

171205
emitState = BBStateNotEmitted;
206+
207+
clauseType = BBClauseNone;
208+
isFilterOrCatchFuncletEntry = false;
209+
clauseVarIndex = -1;
210+
overlappingEHClauseCount = 0;
172211
}
173212
};
174213

175214
struct InterpVar
176215
{
177216
CORINFO_CLASS_HANDLE clsHnd;
178217
InterpType interpType;
179-
int indirects;
180218
int offset;
181219
int size;
182220
// live_start and live_end are used by the offset allocator
@@ -202,7 +240,6 @@ struct InterpVar
202240
offset = -1;
203241
liveStart = NULL;
204242
bbIndex = -1;
205-
indirects = 0;
206243

207244
callArgs = false;
208245
noCallArgs = false;
@@ -261,6 +298,17 @@ typedef class ICorJitInfo* COMP_HANDLE;
261298

262299
class InterpIAllocator;
263300

301+
// Entry of the table where for each leave instruction we store the first finally call island
302+
// to be executed when the leave instruction is executed.
303+
struct LeavesTableEntry
304+
{
305+
// offset of the CEE_LEAVE instruction
306+
int32_t ilOffset;
307+
// The BB of the call island BB that will be the first to call when the leave
308+
// instruction is executed.
309+
InterpBasicBlock *pFinallyCallIslandBB;
310+
};
311+
264312
class InterpCompiler
265313
{
266314
friend class InterpIAllocator;
@@ -284,6 +332,10 @@ class InterpCompiler
284332
int32_t m_currentILOffset;
285333
InterpInst* m_pInitLocalsIns;
286334

335+
// Table of mappings of leave instructions to the first finally call island the leave
336+
// needs to execute.
337+
TArray<LeavesTableEntry> m_leavesTable;
338+
287339
// This represents a mapping from indexes to pointer sized data. During compilation, an
288340
// instruction can request an index for some data (like a MethodDesc pointer), that it
289341
// will then embed in the instruction stream. The data item table will be referenced
@@ -295,6 +347,7 @@ class InterpCompiler
295347
int32_t GetDataItemIndexForHelperFtn(CorInfoHelpFunc ftn);
296348

297349
int GenerateCode(CORINFO_METHOD_INFO* methodInfo);
350+
InterpBasicBlock* GenerateCodeForFinallyCallIslands(InterpBasicBlock *pNewBB, InterpBasicBlock *pPrevBB);
298351
void PatchInitLocals(CORINFO_METHOD_INFO* methodInfo);
299352

300353
void ResolveToken(uint32_t token, CorInfoTokenKind tokenKind, CORINFO_RESOLVED_TOKEN *pResolvedToken);
@@ -347,6 +400,7 @@ class InterpCompiler
347400
void EmitBranch(InterpOpcode opcode, int ilOffset);
348401
void EmitOneArgBranch(InterpOpcode opcode, int ilOffset, int insSize);
349402
void EmitTwoArgBranch(InterpOpcode opcode, int ilOffset, int insSize);
403+
void EmitBranchToBB(InterpOpcode opcode, InterpBasicBlock *pTargetBB);
350404

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

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

@@ -423,10 +480,14 @@ class InterpCompiler
423480
int32_t ComputeCodeSize();
424481
uint32_t ConvertOffset(int32_t offset);
425482
void EmitCode();
483+
int32_t* EmitBBCode(int32_t *ip, InterpBasicBlock *bb, TArray<Reloc*> *relocs);
426484
int32_t* EmitCodeIns(int32_t *ip, InterpInst *pIns, TArray<Reloc*> *relocs);
427485
void PatchRelocations(TArray<Reloc*> *relocs);
428486
InterpMethod* CreateInterpMethod();
429487
bool CreateBasicBlocks(CORINFO_METHOD_INFO* methodInfo);
488+
bool InitializeClauseBuildingBlocks(CORINFO_METHOD_INFO* methodInfo);
489+
void CreateFinallyCallIslandBasicBlocks(CORINFO_METHOD_INFO* methodInfo, int32_t leaveOffset, InterpBasicBlock* pLeaveTargetBB);
490+
void GetNativeRangeForClause(uint32_t startILOffset, uint32_t endILOffset, int32_t *nativeStartOffset, int32_t* nativeEndOffset);
430491

431492
// Debug
432493
void PrintClassName(CORINFO_CLASS_HANDLE cls);
@@ -443,6 +504,7 @@ class InterpCompiler
443504

444505
InterpMethod* CompileMethod();
445506
void BuildGCInfo(InterpMethod *pInterpMethod);
507+
void BuildEHInfo();
446508

447509
int32_t* GetCode(int32_t *pCodeSize);
448510
};

src/coreclr/interpreter/compileropt.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,12 @@ void InterpCompiler::AllocOffsets()
269269

270270
int32_t opcode = InterpGetMovForType(m_pVars[newVar].interpType, false);
271271
InterpInst *newInst = InsertInsBB(pBB, pIns->pPrev, opcode);
272+
// The InsertInsBB assigns m_currentILOffset to ins->ilOffset, which is incorrect for
273+
// instructions injected here. Copy the ilOffset from the call instruction instead.
274+
newInst->ilOffset = pIns->ilOffset;
275+
272276
newInst->SetDVar(newVar);
273-
newInst->SetSVar(newVar);
277+
newInst->SetSVar(var);
274278
if (opcode == INTOP_MOV_VT)
275279
newInst->data[0] = m_pVars[var].size;
276280
// The arg of the call is no longer global

src/coreclr/interpreter/eeinterp.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ CorJitResult CILInterp::compileMethod(ICorJitInfo* compHnd,
107107

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

111112
return CORJIT_OK;
112113
}

src/coreclr/interpreter/intops.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ int32_t CEEOpcodeSize(const uint8_t *ip, const uint8_t *codeEnd)
164164
assert(0);
165165
}
166166

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

170170
return (int32_t)((p - ip) + size);

src/coreclr/interpreter/intops.def

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,17 +282,26 @@ OPDEF(INTOP_CALLVIRT, "callvirt", 4, 1, 1, InterpOpMethodHandle)
282282
OPDEF(INTOP_NEWOBJ, "newobj", 5, 1, 1, InterpOpMethodHandle)
283283
OPDEF(INTOP_NEWOBJ_VT, "newobj.vt", 5, 1, 1, InterpOpMethodHandle)
284284

285-
OPDEF(INTOP_CALL_HELPER_PP, "call.helper.pp", 5, 1, 0, InterpOpThreeInts)
285+
OPDEF(INTOP_CALL_HELPER_PP, "call.helper.pp", 4, 1, 0, InterpOpTwoInts)
286+
OPDEF(INTOP_CALL_HELPER_PP_2, "call.helper.pp.2", 5, 1, 1, InterpOpTwoInts)
287+
288+
OPDEF(INTOP_CALL_FINALLY, "call.finally", 2, 0, 0, InterpOpBranch)
286289

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

291294
OPDEF(INTOP_THROW, "throw", 4, 0, 1, InterpOpInt)
295+
OPDEF(INTOP_RETHROW, "rethrow", 1, 0, 0, InterpOpInt)
296+
OPDEF(INTOP_LEAVE_FILTER, "leavefilter", 2, 0, 1, InterpOpNoArgs)
297+
OPDEF(INTOP_LEAVE_CATCH, "leavecatch", 2, 0, 0, InterpOpBranch)
298+
OPDEF(INTOP_LOAD_EXCEPTION, "load.exception", 2, 1, 0, InterpOpNoArgs)
292299

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

303+
OPDEF(INTOP_LOAD_FRAMEVAR, "load.framevar", 2, 1, 0, InterpOpNoArgs)
304+
296305
// All instructions after this point are IROPS, instructions that are not emitted/executed
297306
OPDEF(INTOP_NOP, "nop", 1, 0, 0, InterpOpNoArgs)
298307
OPDEF(INTOP_DEF, "def", 2, 1, 0, InterpOpNoArgs)

src/coreclr/vm/codeman.cpp

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4302,8 +4302,71 @@ BOOL InterpreterJitManager::JitCodeToMethodInfo(
43024302

43034303
TADDR InterpreterJitManager::GetFuncletStartAddress(EECodeInfo * pCodeInfo)
43044304
{
4305-
// Interpreter-TODO: Verify that this is correct
4306-
return pCodeInfo->GetCodeAddress() - pCodeInfo->GetRelOffset();
4305+
EH_CLAUSE_ENUMERATOR enumState;
4306+
unsigned ehCount;
4307+
4308+
IJitManager *pJitMan = pCodeInfo->GetJitManager();
4309+
ehCount = pJitMan->InitializeEHEnumeration(pCodeInfo->GetMethodToken(), &enumState);
4310+
DWORD relOffset = pCodeInfo->GetRelOffset();
4311+
TADDR methodBaseAddress = pCodeInfo->GetCodeAddress() - relOffset;
4312+
4313+
for (unsigned i = 0; i < ehCount; i++)
4314+
{
4315+
EE_ILEXCEPTION_CLAUSE ehClause;
4316+
pJitMan->GetNextEHClause(&enumState, &ehClause);
4317+
4318+
if ((ehClause.HandlerStartPC <= relOffset) && (relOffset < ehClause.HandlerEndPC))
4319+
{
4320+
return methodBaseAddress + ehClause.HandlerStartPC;
4321+
}
4322+
4323+
// For filters, we also need to check the filter funclet range. The filter funclet is always stored right
4324+
// before its handler funclet (according to ECMA-355). So the filter end offset is equal to the start offset of the handler funclet.
4325+
if (IsFilterHandler(&ehClause) && (ehClause.FilterOffset <= relOffset) && (relOffset < ehClause.HandlerStartPC))
4326+
{
4327+
return methodBaseAddress + ehClause.FilterOffset;
4328+
}
4329+
}
4330+
4331+
return methodBaseAddress;
4332+
}
4333+
4334+
DWORD InterpreterJitManager::GetFuncletStartOffsets(const METHODTOKEN& MethodToken, DWORD* pStartFuncletOffsets, DWORD dwLength)
4335+
{
4336+
CONTRACTL
4337+
{
4338+
NOTHROW;
4339+
GC_NOTRIGGER;
4340+
}
4341+
CONTRACTL_END;
4342+
4343+
EH_CLAUSE_ENUMERATOR enumState;
4344+
unsigned ehCount;
4345+
4346+
ehCount = InitializeEHEnumeration(MethodToken, &enumState);
4347+
4348+
DWORD nFunclets = 0;
4349+
for (unsigned i = 0; i < ehCount; i++)
4350+
{
4351+
EE_ILEXCEPTION_CLAUSE ehClause;
4352+
GetNextEHClause(&enumState, &ehClause);
4353+
if (nFunclets < dwLength)
4354+
{
4355+
pStartFuncletOffsets[nFunclets] = ehClause.HandlerStartPC;
4356+
}
4357+
nFunclets++;
4358+
if (IsFilterHandler(&ehClause))
4359+
{
4360+
if (nFunclets < dwLength)
4361+
{
4362+
pStartFuncletOffsets[nFunclets] = ehClause.FilterOffset;
4363+
}
4364+
4365+
nFunclets++;
4366+
}
4367+
}
4368+
4369+
return nFunclets;
43074370
}
43084371

43094372
void InterpreterJitManager::JitTokenToMethodRegionInfo(const METHODTOKEN& MethodToken, MethodRegionInfo * methodRegionInfo)

src/coreclr/vm/codeman.h

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2759,13 +2759,7 @@ class InterpreterJitManager final : public EECodeGenManager
27592759
}
27602760

27612761
virtual TADDR GetFuncletStartAddress(EECodeInfo * pCodeInfo);
2762-
2763-
virtual DWORD GetFuncletStartOffsets(const METHODTOKEN& MethodToken, DWORD* pStartFuncletOffsets, DWORD dwLength)
2764-
{
2765-
// Not used for the interpreter
2766-
_ASSERTE(FALSE);
2767-
return 0;
2768-
}
2762+
virtual DWORD GetFuncletStartOffsets(const METHODTOKEN& MethodToken, DWORD* pStartFuncletOffsets, DWORD dwLength);
27692763

27702764
#if !defined DACCESS_COMPILE
27712765
protected:

src/coreclr/vm/eetwain.cpp

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2154,9 +2154,56 @@ void EECodeManager::UpdateSSP(PREGDISPLAY pRD)
21542154
#ifdef FEATURE_INTERPRETER
21552155
DWORD_PTR InterpreterCodeManager::CallFunclet(OBJECTREF throwable, void* pHandler, REGDISPLAY *pRD, ExInfo *pExInfo, bool isFilter)
21562156
{
2157-
// Interpreter-TODO: implement calling the funclet in the intepreted code
2158-
_ASSERTE(FALSE);
2159-
return 0;
2157+
Thread *pThread = GetThread();
2158+
InterpThreadContext *threadContext = pThread->GetInterpThreadContext();
2159+
if (threadContext == nullptr || threadContext->pStackStart == nullptr)
2160+
{
2161+
COMPlusThrow(kOutOfMemoryException);
2162+
}
2163+
int8_t *sp = threadContext->pStackPointer;
2164+
2165+
// This construct ensures that the InterpreterFrame is always stored at a higher address than the
2166+
// InterpMethodContextFrame. This is important for the stack walking code.
2167+
struct Frames
2168+
{
2169+
InterpMethodContextFrame interpMethodContextFrame = {0};
2170+
InterpreterFrame interpreterFrame;
2171+
2172+
Frames(TransitionBlock* pTransitionBlock)
2173+
: interpreterFrame(pTransitionBlock, &interpMethodContextFrame)
2174+
{
2175+
}
2176+
}
2177+
frames(NULL);
2178+
2179+
InterpMethodContextFrame *pOriginalFrame = (InterpMethodContextFrame*)GetRegdisplaySP(pRD);
2180+
2181+
StackVal retVal;
2182+
2183+
frames.interpMethodContextFrame.startIp = pOriginalFrame->startIp;
2184+
frames.interpMethodContextFrame.pStack = isFilter ? sp : pOriginalFrame->pStack;
2185+
frames.interpMethodContextFrame.pRetVal = (int8_t*)&retVal;
2186+
2187+
ExceptionClauseArgs exceptionClauseArgs;
2188+
exceptionClauseArgs.ip = (const int32_t *)pHandler;
2189+
exceptionClauseArgs.pFrame = pOriginalFrame;
2190+
exceptionClauseArgs.isFilter = isFilter;
2191+
exceptionClauseArgs.throwable = throwable;
2192+
2193+
InterpExecMethod(&frames.interpreterFrame, &frames.interpMethodContextFrame, threadContext, &exceptionClauseArgs);
2194+
2195+
frames.interpreterFrame.Pop();
2196+
2197+
if (isFilter)
2198+
{
2199+
// The filter funclet returns the result of the filter funclet (EXCEPTION_CONTINUE_SEARCH (0) or EXCEPTION_EXECUTE_HANDLER (1))
2200+
return retVal.data.i;
2201+
}
2202+
else
2203+
{
2204+
// The catch funclet returns the address to resume at after the catch returns.
2205+
return (DWORD_PTR)retVal.data.s;
2206+
}
21602207
}
21612208

21622209
void InterpreterCodeManager::ResumeAfterCatch(CONTEXT *pContext, size_t targetSSP, bool fIntercepted)

0 commit comments

Comments
 (0)