Skip to content

Commit aee764e

Browse files
committed
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.
1 parent f9ad7c9 commit aee764e

File tree

16 files changed

+1464
-156
lines changed

16 files changed

+1464
-156
lines changed

src/coreclr/interpreter/compiler.cpp

Lines changed: 550 additions & 75 deletions
Large diffs are not rendered by default.

src/coreclr/interpreter/compiler.h

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,38 +137,77 @@ 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

@@ -261,6 +300,17 @@ typedef class ICorJitInfo* COMP_HANDLE;
261300

262301
class InterpIAllocator;
263302

303+
// Entry of the table where for each leave instruction we store the first finally call island
304+
// to be executed when the leave instruction is executed.
305+
struct LeavesTableEntry
306+
{
307+
// offset of the CEE_LEAVE instruction
308+
int32_t ilOffset;
309+
// The BB of the call island BB that will be the first to call when the leave
310+
// instruction is executed.
311+
InterpBasicBlock *pFinallyCallIslandBB;
312+
};
313+
264314
class InterpCompiler
265315
{
266316
friend class InterpIAllocator;
@@ -284,6 +334,10 @@ class InterpCompiler
284334
int32_t m_currentILOffset;
285335
InterpInst* m_pInitLocalsIns;
286336

337+
// Table of mappings of leave instructions to the first finally call island the leave
338+
// needs to execute.
339+
TArray<LeavesTableEntry> m_leavesTable;
340+
287341
// This represents a mapping from indexes to pointer sized data. During compilation, an
288342
// instruction can request an index for some data (like a MethodDesc pointer), that it
289343
// will then embed in the instruction stream. The data item table will be referenced
@@ -347,6 +401,7 @@ class InterpCompiler
347401
void EmitBranch(InterpOpcode opcode, int ilOffset);
348402
void EmitOneArgBranch(InterpOpcode opcode, int ilOffset, int insSize);
349403
void EmitTwoArgBranch(InterpOpcode opcode, int ilOffset, int insSize);
404+
void EmitBranchToBB(InterpOpcode opcode, InterpBasicBlock *pTargetBB);
350405

351406
void EmitBBEndVarMoves(InterpBasicBlock *pTargetBB);
352407
void InitBBStackState(InterpBasicBlock *pBB);
@@ -357,6 +412,9 @@ class InterpCompiler
357412
int32_t m_varsSize = 0;
358413
int32_t m_varsCapacity = 0;
359414
int32_t m_numILVars = 0;
415+
// For each catch or filter clause, we create a variable that holds the exception object.
416+
// This is the index of the first such variable.
417+
int32_t m_clauseVarsIndex = 0;
360418

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

@@ -423,10 +481,14 @@ class InterpCompiler
423481
int32_t ComputeCodeSize();
424482
uint32_t ConvertOffset(int32_t offset);
425483
void EmitCode();
484+
int32_t* EmitBBCode(int32_t *ip, InterpBasicBlock *bb, TArray<Reloc*> *relocs);
426485
int32_t* EmitCodeIns(int32_t *ip, InterpInst *pIns, TArray<Reloc*> *relocs);
427486
void PatchRelocations(TArray<Reloc*> *relocs);
428487
InterpMethod* CreateInterpMethod();
429488
bool CreateBasicBlocks(CORINFO_METHOD_INFO* methodInfo);
489+
bool InitializeClauseBuildingBlocks(CORINFO_METHOD_INFO* methodInfo);
490+
void CreateFinallyCallIslandBasicBlocks(CORINFO_METHOD_INFO* methodInfo, int32_t leaveOffset, InterpBasicBlock* pLeaveTargetBB);
491+
void GetNativeRangeForClause(uint32_t startILOffset, uint32_t endILOffset, int32_t *nativeStartOffset, int32_t* nativeEndOffset);
430492

431493
// Debug
432494
void PrintClassName(CORINFO_CLASS_HANDLE cls);
@@ -443,6 +505,7 @@ class InterpCompiler
443505

444506
InterpMethod* CompileMethod();
445507
void BuildGCInfo(InterpMethod *pInterpMethod);
508+
void BuildEHInfo();
446509

447510
int32_t* GetCode(int32_t *pCodeSize);
448511
};

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
@@ -108,6 +108,7 @@ CorJitResult CILInterp::compileMethod(ICorJitInfo* compHnd,
108108

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

112113
return CORJIT_OK;
113114
}

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: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,16 +281,25 @@ OPDEF(INTOP_NEWOBJ, "newobj", 5, 1, 1, InterpOpMethodHandle)
281281
OPDEF(INTOP_NEWOBJ_VT, "newobj.vt", 5, 1, 1, InterpOpMethodHandle)
282282

283283
OPDEF(INTOP_CALL_HELPER_PP, "call.helper.pp", 5, 1, 0, InterpOpThreeInts)
284+
OPDEF(INTOP_CALL_HELPER_PP_2, "call.helper.pp.2", 6, 1, 1, InterpOpThreeInts)
285+
286+
OPDEF(INTOP_CALL_FINALLY, "call.finally", 2, 0, 0, InterpOpBranch)
284287

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

289292
OPDEF(INTOP_THROW, "throw", 4, 0, 1, InterpOpInt)
293+
OPDEF(INTOP_RETHROW, "rethrow", 1, 0, 0, InterpOpInt)
294+
OPDEF(INTOP_LEAVE_FILTER, "leavefilter", 2, 0, 1, InterpOpNoArgs)
295+
OPDEF(INTOP_LEAVE_CATCH, "leavecatch", 2, 0, 0, InterpOpBranch)
296+
OPDEF(INTOP_LOAD_EXCEPTION, "load.exception", 2, 1, 0, InterpOpNoArgs)
290297

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

301+
OPDEF(INTOP_LOAD_FRAMEVAR, "load.framevar", 2, 1, 0, InterpOpNoArgs)
302+
294303
// All instructions after this point are IROPS, instructions that are not emitted/executed
295304
OPDEF(INTOP_NOP, "nop", 1, 0, 0, InterpOpNoArgs)
296305
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
@@ -4352,8 +4352,71 @@ BOOL InterpreterJitManager::JitCodeToMethodInfo(
43524352

43534353
TADDR InterpreterJitManager::GetFuncletStartAddress(EECodeInfo * pCodeInfo)
43544354
{
4355-
// Interpreter-TODO: Verify that this is correct
4356-
return pCodeInfo->GetCodeAddress() - pCodeInfo->GetRelOffset();
4355+
EH_CLAUSE_ENUMERATOR enumState;
4356+
unsigned ehCount;
4357+
4358+
IJitManager *pJitMan = pCodeInfo->GetJitManager();
4359+
ehCount = pJitMan->InitializeEHEnumeration(pCodeInfo->GetMethodToken(), &enumState);
4360+
DWORD relOffset = pCodeInfo->GetRelOffset();
4361+
TADDR methodBaseAddress = pCodeInfo->GetCodeAddress() - relOffset;
4362+
4363+
for (unsigned i = 0; i < ehCount; i++)
4364+
{
4365+
EE_ILEXCEPTION_CLAUSE ehClause;
4366+
pJitMan->GetNextEHClause(&enumState, &ehClause);
4367+
4368+
if ((ehClause.HandlerStartPC <= relOffset) && (relOffset < ehClause.HandlerEndPC))
4369+
{
4370+
return methodBaseAddress + ehClause.HandlerStartPC;
4371+
}
4372+
4373+
// For filters, we also need to check the filter funclet range. The filter funclet is always stored right
4374+
// before its handler funclet (according to ECMA-355). So the filter end offset is equal to the start offset of the handler funclet.
4375+
if (IsFilterHandler(&ehClause) && (ehClause.FilterOffset <= relOffset) && (relOffset < ehClause.HandlerStartPC))
4376+
{
4377+
return methodBaseAddress + ehClause.FilterOffset;
4378+
}
4379+
}
4380+
4381+
return methodBaseAddress;
4382+
}
4383+
4384+
DWORD InterpreterJitManager::GetFuncletStartOffsets(const METHODTOKEN& MethodToken, DWORD* pStartFuncletOffsets, DWORD dwLength)
4385+
{
4386+
CONTRACTL
4387+
{
4388+
NOTHROW;
4389+
GC_NOTRIGGER;
4390+
}
4391+
CONTRACTL_END;
4392+
4393+
EH_CLAUSE_ENUMERATOR enumState;
4394+
unsigned ehCount;
4395+
4396+
ehCount = InitializeEHEnumeration(MethodToken, &enumState);
4397+
4398+
DWORD nFunclets = 0;
4399+
for (unsigned i = 0; i < ehCount; i++)
4400+
{
4401+
EE_ILEXCEPTION_CLAUSE ehClause;
4402+
GetNextEHClause(&enumState, &ehClause);
4403+
if (nFunclets < dwLength)
4404+
{
4405+
pStartFuncletOffsets[nFunclets] = ehClause.HandlerStartPC;
4406+
}
4407+
nFunclets++;
4408+
if (IsFilterHandler(&ehClause))
4409+
{
4410+
if (nFunclets < dwLength)
4411+
{
4412+
pStartFuncletOffsets[nFunclets] = ehClause.FilterOffset;
4413+
}
4414+
4415+
nFunclets++;
4416+
}
4417+
}
4418+
4419+
return nFunclets;
43574420
}
43584421

43594422
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)