From 732d90cd9a2c4acc1a659c9fce01f65e3b251597 Mon Sep 17 00:00:00 2001 From: Jan Vorlicek Date: Sat, 17 May 2025 02:04:50 +0200 Subject: [PATCH 1/5] 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. --- src/coreclr/interpreter/compiler.cpp | 625 ++++++++++++++++++++--- src/coreclr/interpreter/compiler.h | 63 +++ src/coreclr/interpreter/compileropt.cpp | 6 +- src/coreclr/interpreter/eeinterp.cpp | 1 + src/coreclr/interpreter/intops.cpp | 2 +- src/coreclr/interpreter/intops.def | 9 + src/coreclr/vm/codeman.cpp | 67 ++- src/coreclr/vm/codeman.h | 8 +- src/coreclr/vm/eetwain.cpp | 53 +- src/coreclr/vm/exceptionhandling.cpp | 58 ++- src/coreclr/vm/exceptionhandling.h | 2 + src/coreclr/vm/interpexec.cpp | 143 ++++-- src/coreclr/vm/interpexec.h | 15 +- src/coreclr/vm/jithelpers.cpp | 17 +- src/coreclr/vm/prestub.cpp | 4 +- src/tests/JIT/interpreter/Interpreter.cs | 548 ++++++++++++++++++++ 16 files changed, 1465 insertions(+), 156 deletions(-) diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index d0d5b90e2090ab..1bc60fc1b70578 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -653,40 +653,13 @@ int32_t* InterpCompiler::EmitCodeIns(int32_t *ip, InterpInst *ins, TArraynativeOffset = (int32_t)(ip - m_pMethodCode); - if (ins->ilOffset != -1) - { - assert(ins->ilOffset >= 0); - assert(ins->nativeOffset >= 0); - uint32_t ilOffset = ins->ilOffset; - uint32_t nativeOffset = ConvertOffset(ins->nativeOffset); - if ((m_ILToNativeMapSize == 0) || (m_pILToNativeMap[m_ILToNativeMapSize - 1].ilOffset != ilOffset)) - { - // - // This code assumes IL offsets in the actual opcode stream with valid IL offsets is monotonically - // increasing, so the generated map contains strictly increasing IL offsets. - // - // Native offsets are obviously strictly increasing by construction here. - // - assert((m_ILToNativeMapSize == 0) || (m_pILToNativeMap[m_ILToNativeMapSize - 1].ilOffset < ilOffset)); - assert((m_ILToNativeMapSize == 0) || (m_pILToNativeMap[m_ILToNativeMapSize - 1].nativeOffset < nativeOffset)); - - // - // Since we can have at most one entry per IL offset, - // this map cannot possibly use more entries than the size of the IL code - // - assert(m_ILToNativeMapSize < m_ILCodeSize); - - m_pILToNativeMap[m_ILToNativeMapSize].ilOffset = ilOffset; - m_pILToNativeMap[m_ILToNativeMapSize].nativeOffset = nativeOffset; - m_ILToNativeMapSize++; - } - } - int32_t opcode = ins->opcode; int32_t *startIp = ip; - *ip++ = opcode; + // Set to true if the instruction was completely reverted. + bool isReverted = false; + if (opcode == INTOP_SWITCH) { int32_t numLabels = ins->data [0]; @@ -701,7 +674,7 @@ int32_t* InterpCompiler::EmitCodeIns(int32_t *ip, InterpInst *ins, TArrayinfo.pTargetBB == m_pCBB->pNextBB) { // Ignore branch to the next basic block. Revert the added INTOP_BR. + isReverted = true; ip--; } else @@ -786,6 +760,33 @@ int32_t* InterpCompiler::EmitCodeIns(int32_t *ip, InterpInst *ins, TArraydata[i]; } + if ((ins->ilOffset != -1) && !isReverted) + { + assert(ins->ilOffset >= 0); + assert(ins->nativeOffset >= 0); + uint32_t ilOffset = ins->ilOffset; + uint32_t nativeOffset = ConvertOffset(ins->nativeOffset); + if ((m_ILToNativeMapSize == 0) || (m_pILToNativeMap[m_ILToNativeMapSize - 1].ilOffset != ilOffset)) + { + // This code assumes that instructions for the same IL offset are emitted in an sequence without + // any other IL offsets in between. +#ifdef _DEBUG + for (int i = 0; i < m_ILToNativeMapSize; i++) + { + assert(m_pILToNativeMap[i].ilOffset != ilOffset); + } +#endif // _DEBUG + + // Since we can have at most one entry per IL offset, + // this map cannot possibly use more entries than the size of the IL code + assert(m_ILToNativeMapSize < m_ILCodeSize); + + m_pILToNativeMap[m_ILToNativeMapSize].ilOffset = ilOffset; + m_pILToNativeMap[m_ILToNativeMapSize].nativeOffset = nativeOffset; + m_ILToNativeMapSize++; + } + } + return ip; } @@ -811,6 +812,27 @@ void InterpCompiler::PatchRelocations(TArray *relocs) } } +int32_t *InterpCompiler::EmitBBCode(int32_t *ip, InterpBasicBlock *bb, TArray *relocs) +{ + m_pCBB = bb; + m_pCBB->nativeOffset = (int32_t)(ip - m_pMethodCode); + + for (InterpInst *ins = bb->pFirstIns; ins != NULL; ins = ins->pNext) + { + if (InterpOpIsEmitNop(ins->opcode)) + { + ins->nativeOffset = (int32_t)(ip - m_pMethodCode); + continue; + } + + ip = EmitCodeIns(ip, ins, relocs); + } + + m_pCBB->nativeEndOffset = (int32_t)(ip - m_pMethodCode); + + return ip; +} + void InterpCompiler::EmitCode() { TArray relocs; @@ -825,23 +847,48 @@ void InterpCompiler::EmitCode() eeVars = new ICorDebugInfo::NativeVarInfo[m_numILVars]; } - int32_t *ip = m_pMethodCode; - for (InterpBasicBlock *bb = m_pEntryBB; bb != NULL; bb = bb->pNextBB) + // For each BB, compute the number of EH clauses that overlap with it. + for (unsigned int i = 0; i < m_methodInfo->EHcount; i++) { - bb->nativeOffset = (int32_t)(ip - m_pMethodCode); - m_pCBB = bb; - for (InterpInst *ins = bb->pFirstIns; ins != NULL; ins = ins->pNext) + CORINFO_EH_CLAUSE clause; + m_compHnd->getEHinfo(m_methodInfo->ftn, i, &clause); + for (InterpBasicBlock *bb = m_pEntryBB; bb != NULL; bb = bb->pNextBB) { - if (InterpOpIsEmitNop(ins->opcode)) + if (clause.HandlerOffset <= (uint32_t)bb->ilOffset && (clause.HandlerOffset + clause.HandlerLength) > (uint32_t)bb->ilOffset) { - ins->nativeOffset = (int32_t)(ip - m_pMethodCode); - continue; + bb->overlappingEHClauseCount++; } - ip = EmitCodeIns(ip, ins, &relocs); + if (clause.Flags == CORINFO_EH_CLAUSE_FILTER && clause.FilterOffset <= (uint32_t)bb->ilOffset && clause.HandlerOffset > (uint32_t)bb->ilOffset) + { + bb->overlappingEHClauseCount++; + } } } + // Emit all the code in waves. First emit all blocks that are not inside any EH clauses. + // Then emit blocks that are inside of a single EH clause, then ones that are inside of + // two EH clauses, etc. + // The goal is to move all clauses to the end of the method code recursively so that + // no handler is inside of a try block. + int32_t *ip = m_pMethodCode; + bool emittedBlock; + int clauseDepth = 0; + do + { + emittedBlock = false; + for (InterpBasicBlock *bb = m_pEntryBB; bb != NULL; bb = bb->pNextBB) + { + if (bb->overlappingEHClauseCount == clauseDepth) + { + ip = EmitBBCode(ip, bb, &relocs); + emittedBlock = true; + } + } + clauseDepth++; + } + while (emittedBlock); + m_methodCodeSize = (int32_t)(ip - m_pMethodCode); PatchRelocations(&relocs); @@ -1022,6 +1069,95 @@ void InterpCompiler::BuildGCInfo(InterpMethod *pInterpMethod) #endif } +void InterpCompiler::GetNativeRangeForClause(uint32_t startILOffset, uint32_t endILOffset, int32_t *nativeStartOffset, int32_t* nativeEndOffset) +{ + InterpBasicBlock* pStartBB = m_ppOffsetToBB[startILOffset]; + InterpBasicBlock* pEndBB = pStartBB; + for (InterpBasicBlock* pBB = pStartBB->pNextBB; (pBB != NULL) && ((uint32_t)pBB->ilOffset < endILOffset); pBB = pBB->pNextBB) + { + if ((pBB->clauseType == pStartBB->clauseType) && (pBB->overlappingEHClauseCount == pStartBB->overlappingEHClauseCount)) + { + pEndBB = pBB; + } + } + + *nativeStartOffset = pStartBB->nativeOffset; + *nativeEndOffset = pEndBB->nativeEndOffset; +} + +void InterpCompiler::BuildEHInfo() +{ + uint32_t lastTryILOffset = 0; + uint32_t lastTryILLength = 0; + + INTERP_DUMP("EH info:\n"); + + if (m_methodInfo->EHcount == 0) + { + INTERP_DUMP(" None\n"); + return; + } + + m_compHnd->setEHcount(m_methodInfo->EHcount); + for (unsigned int i = 0; i < m_methodInfo->EHcount; i++) + { + CORINFO_EH_CLAUSE clause; + CORINFO_EH_CLAUSE nativeClause; + + m_compHnd->getEHinfo(m_methodInfo->ftn, i, &clause); + + int32_t tryStartNativeOffset; + int32_t tryEndNativeOffset; + GetNativeRangeForClause(clause.TryOffset, clause.TryOffset + clause.TryLength, &tryStartNativeOffset, &tryEndNativeOffset); + + int32_t handlerStartNativeOffset; + int32_t handlerEndNativeOffset; + GetNativeRangeForClause(clause.HandlerOffset, clause.HandlerOffset + clause.HandlerLength, &handlerStartNativeOffset, &handlerEndNativeOffset); + + nativeClause.TryOffset = ConvertOffset(tryStartNativeOffset); + nativeClause.TryLength = ConvertOffset(tryEndNativeOffset); + + nativeClause.HandlerOffset = ConvertOffset(handlerStartNativeOffset); + nativeClause.HandlerLength = ConvertOffset(handlerEndNativeOffset); + InterpBasicBlock* pFilterStartBB = NULL; + if (clause.Flags == CORINFO_EH_CLAUSE_FILTER) + { + pFilterStartBB = m_ppOffsetToBB[clause.FilterOffset]; + nativeClause.FilterOffset = ConvertOffset(pFilterStartBB->nativeOffset); + } + else + { + nativeClause.ClassToken = clause.ClassToken; + } + + nativeClause.Flags = clause.Flags; + + // A try region can have multiple catch / filter handlers. All except of the first one need to be marked by + // the COR_ILEXCEPTION_CLAUSE_SAMETRY flag so that runtime can distinguish this case from a case when + // the native try region is the same for multiple clauses, but the IL try region is different. + if ((lastTryILOffset == clause.TryOffset) && (lastTryILLength == clause.TryLength)) + { + nativeClause.Flags = (CORINFO_EH_CLAUSE_FLAGS)((int)nativeClause.Flags | COR_ILEXCEPTION_CLAUSE_SAMETRY); + } + + m_compHnd->setEHinfo(i, &nativeClause); + + INTERP_DUMP(" try [IR_%04x(%x), IR_%04x(%x)) ", tryStartNativeOffset, clause.TryOffset, tryEndNativeOffset, clause.TryOffset + clause.TryLength); + if (clause.Flags == CORINFO_EH_CLAUSE_FILTER) + { + INTERP_DUMP("filter IR_%04x(%x), handler [IR_%04x(%x), IR_%04x(%x))%s\n", pFilterStartBB->nativeOffset, clause.FilterOffset, handlerStartNativeOffset, clause.HandlerOffset, handlerEndNativeOffset, clause.HandlerOffset + clause.HandlerLength, ((int)nativeClause.Flags & COR_ILEXCEPTION_CLAUSE_SAMETRY) ? " (same try)" : ""); + } + else if (nativeClause.Flags == CORINFO_EH_CLAUSE_FINALLY) + { + INTERP_DUMP("finally handler [IR_%04x(%x), IR_%04x(%x))\n", handlerStartNativeOffset, clause.HandlerOffset, handlerEndNativeOffset, clause.HandlerOffset + clause.HandlerLength); + } + else + { + INTERP_DUMP("catch handler [IR_%04x(%x), IR_%04x(%x))%s\n", handlerStartNativeOffset, clause.HandlerOffset, handlerEndNativeOffset, clause.HandlerOffset + clause.HandlerLength, ((int)nativeClause.Flags & COR_ILEXCEPTION_CLAUSE_SAMETRY) ? " (same try)" : ""); + } + } +} + InterpMethod* InterpCompiler::CreateInterpMethod() { int numDataItems = m_dataItems.GetSize(); @@ -1271,8 +1407,9 @@ void InterpCompiler::CreateILVars() sigArg = m_methodInfo->locals.args; m_ILLocalsOffset = offset; + int index = numArgs; + for (int i = 0; i < numILLocals; i++) { - int index = numArgs + i; InterpType interpType; CORINFO_CLASS_HANDLE argClass; @@ -1289,49 +1426,103 @@ void InterpCompiler::CreateILVars() INTERP_DUMP("alloc local var %d to offset %d\n", index, offset); offset += size; sigArg = m_compHnd->getArgNext(sigArg); + index++; } - offset = ALIGN_UP_TO(offset, INTERP_STACK_ALIGNMENT); + offset = ALIGN_UP_TO(offset, INTERP_STACK_ALIGNMENT); m_ILLocalsSize = offset - m_ILLocalsOffset; + + INTERP_DUMP("\nCreate clause Vars:\n"); + + m_clauseVarsIndex = index; + + for (unsigned int i = 0; i < m_methodInfo->EHcount; i++) + { + new (&m_pVars[index]) InterpVar(InterpTypeO, NULL, INTERP_STACK_SLOT_SIZE); + m_pVars[index].global = true; + m_pVars[index].ILGlobal = true; + m_pVars[index].offset = offset; + INTERP_DUMP("alloc clause var %d to offset %d\n", index, offset); + offset += INTERP_STACK_SLOT_SIZE; + index++; + } + m_totalVarsStackSize = offset; } -bool InterpCompiler::CreateBasicBlocks(CORINFO_METHOD_INFO* methodInfo) +void InterpCompiler::CreateFinallyCallIslandBasicBlocks(CORINFO_METHOD_INFO* methodInfo, int32_t leaveOffset, InterpBasicBlock* pLeaveTargetBB) { - int32_t codeSize = methodInfo->ILCodeSize; - uint8_t *codeStart = methodInfo->ILCode; - uint8_t *codeEnd = codeStart + codeSize; - const uint8_t *ip = codeStart; - - m_ppOffsetToBB = (InterpBasicBlock**)AllocMemPool0(sizeof(InterpBasicBlock*) * (methodInfo->ILCodeSize + 1)); - GetBB(0); - + // Create finally call island basic blocks for all try regions with finally clauses that the leave exits. + // That means when the leaveOffset is inside the try region and the target is outside of it. + // These finally call island blocks are used for non-exceptional finally execution. + bool firstFinallyCallIsland = true; + InterpBasicBlock* pInnerFinallyCallIslandBB = NULL; for (unsigned int i = 0; i < methodInfo->EHcount; i++) { CORINFO_EH_CLAUSE clause; m_compHnd->getEHinfo(methodInfo->ftn, i, &clause); + if (clause.Flags != CORINFO_EH_CLAUSE_FINALLY) + { + continue; + } - if ((codeStart + clause.TryOffset) > codeEnd || - (codeStart + clause.TryOffset + clause.TryLength) > codeEnd) + if ((uint32_t)leaveOffset < clause.TryOffset || (uint32_t)leaveOffset > (clause.TryOffset + clause.TryLength)) { - return false; + continue; } - GetBB(clause.TryOffset); - if ((codeStart + clause.HandlerOffset) > codeEnd || - (codeStart + clause.HandlerOffset + clause.HandlerLength) > codeEnd) + if ((uint32_t)pLeaveTargetBB->ilOffset >= clause.TryOffset && (uint32_t)pLeaveTargetBB->ilOffset <= (clause.TryOffset + clause.TryLength)) { - return false; + continue; } - GetBB(clause.HandlerOffset); - if (clause.Flags == CORINFO_EH_CLAUSE_FILTER) + InterpBasicBlock* pHandlerBB = GetBB(clause.HandlerOffset); + InterpBasicBlock* pFinallyCallIslandBB = AllocBB(clause.HandlerOffset + clause.HandlerLength); + + InterpBasicBlock** ppLastBBNext = &pHandlerBB->pFinallyCallIslandBB; + while (*ppLastBBNext != NULL) { - if ((codeStart + clause.FilterOffset) > codeEnd) - return false; - GetBB(clause.FilterOffset); + if (*ppLastBBNext == pFinallyCallIslandBB) + { + // We already have this finally call island block + assert(pFinallyCallIslandBB->pLeaveTargetBB == pLeaveTargetBB); + break; + } + ppLastBBNext = &((*ppLastBBNext)->pFinallyCallIslandBB); + } + + pFinallyCallIslandBB->pLeaveTargetBB = pLeaveTargetBB; + + *ppLastBBNext = pFinallyCallIslandBB; + + if (pInnerFinallyCallIslandBB != NULL) + { + pInnerFinallyCallIslandBB->pFinallyCallIslandBB = pFinallyCallIslandBB; + } + pInnerFinallyCallIslandBB = pFinallyCallIslandBB; + + if (firstFinallyCallIsland) + { + // The leaves table entry points to the first finally call island block + firstFinallyCallIsland = false; + + LeavesTableEntry leavesEntry; + leavesEntry.ilOffset = leaveOffset; + leavesEntry.pFinallyCallIslandBB = pFinallyCallIslandBB; + m_leavesTable.Add(leavesEntry); } } +} + +bool InterpCompiler::CreateBasicBlocks(CORINFO_METHOD_INFO* methodInfo) +{ + int32_t codeSize = methodInfo->ILCodeSize; + uint8_t *codeStart = methodInfo->ILCode; + uint8_t *codeEnd = codeStart + codeSize; + const uint8_t *ip = codeStart; + + m_ppOffsetToBB = (InterpBasicBlock**)AllocMemPool0(sizeof(InterpBasicBlock*) * (methodInfo->ILCodeSize + 1)); + GetBB(0); while (ip < codeEnd) { @@ -1339,6 +1530,7 @@ bool InterpCompiler::CreateBasicBlocks(CORINFO_METHOD_INFO* methodInfo) OPCODE opcode = CEEDecodeOpcode(&ip); OPCODE_FORMAT opArgs = g_CEEOpArgs[opcode]; int32_t target; + InterpBasicBlock *pLeaveTargetBB; switch (opArgs) { @@ -1366,7 +1558,11 @@ bool InterpCompiler::CreateBasicBlocks(CORINFO_METHOD_INFO* methodInfo) target = insOffset + 2 + (int8_t)ip [1]; if (target >= codeSize) return false; - GetBB(target); + pLeaveTargetBB = GetBB(target); + if (opcode == CEE_LEAVE_S || opcode == CEE_LEAVE) + { + CreateFinallyCallIslandBasicBlocks(methodInfo, insOffset, pLeaveTargetBB); + } ip += 2; GetBB((int32_t)(ip - codeStart)); break; @@ -1374,7 +1570,11 @@ bool InterpCompiler::CreateBasicBlocks(CORINFO_METHOD_INFO* methodInfo) target = insOffset + 5 + getI4LittleEndian(ip + 1); if (target >= codeSize) return false; - GetBB(target); + pLeaveTargetBB = GetBB(target); + if (opcode == CEE_LEAVE_S || opcode == CEE_LEAVE) + { + CreateFinallyCallIslandBasicBlocks(methodInfo, insOffset, pLeaveTargetBB); + } ip += 5; GetBB((int32_t)(ip - codeStart)); break; @@ -1411,6 +1611,126 @@ bool InterpCompiler::CreateBasicBlocks(CORINFO_METHOD_INFO* methodInfo) return true; } +bool InterpCompiler::InitializeClauseBuildingBlocks(CORINFO_METHOD_INFO* methodInfo) +{ + int32_t codeSize = methodInfo->ILCodeSize; + uint8_t *codeStart = methodInfo->ILCode; + uint8_t *codeEnd = codeStart + codeSize; + + for (unsigned int i = 0; i < methodInfo->EHcount; i++) + { + CORINFO_EH_CLAUSE clause; + m_compHnd->getEHinfo(methodInfo->ftn, i, &clause); + + if ((codeStart + clause.TryOffset) > codeEnd || + (codeStart + clause.TryOffset + clause.TryLength) > codeEnd) + { + return false; + } + + InterpBasicBlock* pTryBB = GetBB(clause.TryOffset); + + if ((codeStart + clause.HandlerOffset) > codeEnd || + (codeStart + clause.HandlerOffset + clause.HandlerLength) > codeEnd) + { + return false; + } + + // Find and mark all basic blocks that are part of the try region. + for (uint32_t j = clause.TryOffset; j < (clause.TryOffset + clause.TryLength); j++) + { + InterpBasicBlock* pBB = m_ppOffsetToBB[j]; + if (pBB != NULL && pBB->clauseType == BBClauseNone) + { + pBB->clauseType = BBClauseTry; + } + } + + InterpBasicBlock* pHandlerBB = GetBB(clause.HandlerOffset); + + // Find and mark all basic blocks that are part of the handler region. + for (uint32_t j = clause.HandlerOffset; j < (clause.HandlerOffset + clause.HandlerLength); j++) + { + InterpBasicBlock* pBB = m_ppOffsetToBB[j]; + if (pBB != NULL && pBB->clauseType == BBClauseNone) + { + 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; + } + } + } + + if (clause.Flags == CORINFO_EH_CLAUSE_FILTER) + { + if ((codeStart + clause.FilterOffset) > codeEnd) + return false; + + // The filter funclet is always stored right before its handler funclet. + // So the filter end offset is equal to the start offset of the handler funclet. + InterpBasicBlock* pFilterBB = GetBB(clause.FilterOffset); + pFilterBB->isFilterOrCatchFuncletEntry = true; + pFilterBB->clauseVarIndex = m_clauseVarsIndex + i; + + // Initialize the filter stack state. It initially contains the exception object. + pFilterBB->stackHeight = 1; + pFilterBB->pStackState = (StackInfo*)AllocMemPool(sizeof (StackInfo)); + pFilterBB->pStackState[0].type = StackTypeO; + pFilterBB->pStackState[0].size = INTERP_STACK_SLOT_SIZE; + pFilterBB->pStackState[0].clsHnd = NULL; + pFilterBB->pStackState[0].var = pFilterBB->clauseVarIndex; + + // Find and mark all basic blocks that are part of the filter region. + for (uint32_t j = clause.FilterOffset; j < clause.HandlerOffset; j++) + { + InterpBasicBlock* pBB = m_ppOffsetToBB[j]; + if (pBB != NULL && pBB->clauseType == BBClauseNone) + { + pBB->clauseType = BBClauseFilter; + } + } + } + else if (clause.Flags == CORINFO_EH_CLAUSE_FINALLY|| clause.Flags == CORINFO_EH_CLAUSE_FAULT) + { + InterpBasicBlock* pFinallyBB = GetBB(clause.HandlerOffset); + + // Initialize finally handler stack state to empty. + pFinallyBB->stackHeight = 0; + } + + if (clause.Flags == CORINFO_EH_CLAUSE_NONE || clause.Flags == CORINFO_EH_CLAUSE_FILTER) + { + InterpBasicBlock* pCatchBB = GetBB(clause.HandlerOffset); + pCatchBB->isFilterOrCatchFuncletEntry = true; + pCatchBB->clauseVarIndex = m_clauseVarsIndex + i; + + // Initialize the catch / filtered handler stack state. It initially contains the exception object. + pCatchBB->stackHeight = 1; + pCatchBB->pStackState = (StackInfo*)AllocMemPool(sizeof (StackInfo)); + pCatchBB->pStackState[0].type = StackTypeO; + pCatchBB->pStackState[0].size = INTERP_STACK_SLOT_SIZE; + pCatchBB->pStackState[0].var = pCatchBB->clauseVarIndex; + pCatchBB->pStackState[0].clsHnd = NULL; + } + } + + return true; +} + +void InterpCompiler::EmitBranchToBB(InterpOpcode opcode, InterpBasicBlock *pTargetBB) +{ + EmitBBEndVarMoves(pTargetBB); + InitBBStackState(pTargetBB); + + AddIns(opcode); + m_pLastNewIns->info.pTargetBB = pTargetBB; +} + // ilOffset represents relative branch offset void InterpCompiler::EmitBranch(InterpOpcode opcode, int32_t ilOffset) { @@ -1425,11 +1745,7 @@ void InterpCompiler::EmitBranch(InterpOpcode opcode, int32_t ilOffset) InterpBasicBlock *pTargetBB = m_ppOffsetToBB[target]; assert(pTargetBB != NULL); - EmitBBEndVarMoves(pTargetBB); - InitBBStackState(pTargetBB); - - AddIns(opcode); - m_pLastNewIns->info.pTargetBB = pTargetBB; + EmitBranchToBB(opcode, pTargetBB); } void InterpCompiler::EmitOneArgBranch(InterpOpcode opcode, int32_t ilOffset, int insSize) @@ -1497,13 +1813,23 @@ void InterpCompiler::EmitTwoArgBranch(InterpOpcode opcode, int32_t ilOffset, int } } - void InterpCompiler::EmitLoadVar(int32_t var) { InterpType interpType = m_pVars[var].interpType; - int32_t size = m_pVars[var].size; CORINFO_CLASS_HANDLE clsHnd = m_pVars[var].clsHnd; + if (m_pCBB->clauseType == BBClauseFilter) + { + AddIns(INTOP_LOAD_FRAMEVAR); + PushInterpType(InterpTypeI, NULL); + m_pLastNewIns->SetDVar(m_pStackPointer[-1].var); + m_pVars[var].indirects++; + EmitLdind(interpType, clsHnd, m_pVars[var].offset); + return; + } + + int32_t size = m_pVars[var].size; + if (interpType == InterpTypeVT) PushTypeVT(clsHnd, size); else @@ -1521,6 +1847,15 @@ void InterpCompiler::EmitStoreVar(int32_t var) InterpType interpType = m_pVars[var].interpType; CHECK_STACK_RET_VOID(1); + if (m_pCBB->clauseType == 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; + } + #ifdef TARGET_64BIT // nint and int32 can be used interchangeably. Add implicit conversions. if (m_pStackPointer[-1].type == StackTypeI4 && g_stackTypeFromInterpType[interpType] == StackTypeI8) @@ -2142,12 +2477,21 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) m_pEntryBB->stackHeight = 0; m_pCBB = m_pEntryBB; + InterpBasicBlock *pFirstFuncletBB = NULL; + InterpBasicBlock *pLastFuncletBB = NULL; + if (!CreateBasicBlocks(methodInfo)) { m_hasInvalidCode = true; goto exit_bad_code; } + if (!InitializeClauseBuildingBlocks(methodInfo)) + { + m_hasInvalidCode = true; + goto exit_bad_code; + } + m_currentILOffset = -1; #if DEBUG @@ -2171,6 +2515,7 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) linkBBlocks = true; needsRetryEmit = false; + retry_emit: emittedBBlocks = false; while (m_ip < codeEnd) @@ -2190,7 +2535,7 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) if (m_pCBB->emitState == BBStateEmitting) m_pCBB->emitState = BBStateEmitted; // If the new bblock was already emitted, skip its instructions - if (pNewBB->emitState == BBStateEmitted) + if ((pNewBB->emitState == BBStateEmitted)) { if (linkBBlocks) { @@ -2216,6 +2561,7 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) { assert (pNewBB->emitState == BBStateNotEmitted); } + // We are starting a new basic block. Change cbb and link them together if (linkBBlocks) { @@ -2262,9 +2608,52 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) // We will just skip all instructions instead, since it doesn't seem that problematic. } } - if (!m_pCBB->pNextBB) - m_pCBB->pNextBB = pNewBB; + + InterpBasicBlock *pPrevBB = m_pCBB; + + InterpBasicBlock *pFinallyCallIslandBB = pNewBB->pFinallyCallIslandBB; + + while (pFinallyCallIslandBB != NULL) + { + INTERP_DUMP("Injecting BB%d\n", pFinallyCallIslandBB->index); + if (pFinallyCallIslandBB->emitState != BBStateEmitted) + { + // Set the finally call island BB as current so that the instructions are emitted into it + m_pCBB = pFinallyCallIslandBB; + InitBBStackState(m_pCBB); + EmitBranchToBB(INTOP_CALL_FINALLY, pNewBB); // The pNewBB is the finally BB + // Try to get the next finally call island block (for an outer try's finally) + if (pFinallyCallIslandBB->pFinallyCallIslandBB) + { + // Branch to the next finally call island (at an outer try block) + EmitBranchToBB(INTOP_BR, pFinallyCallIslandBB->pFinallyCallIslandBB); + } + else + { + // This is the last finally call island, so we need to emit a branch to the leave target + EmitBranchToBB(INTOP_BR, pFinallyCallIslandBB->pLeaveTargetBB); + } + m_pCBB->emitState = BBStateEmitted; + INTERP_DUMP("Chaining BB%d -> BB%d\n", pPrevBB->index, pFinallyCallIslandBB->index); + } + assert(pPrevBB->pNextBB == NULL || pPrevBB->pNextBB == pFinallyCallIslandBB); + pPrevBB->pNextBB = pFinallyCallIslandBB; + pPrevBB = pFinallyCallIslandBB; + pFinallyCallIslandBB = pFinallyCallIslandBB->pNextBB; + } + + if (!pPrevBB->pNextBB) + { + INTERP_DUMP("Chaining BB%d -> BB%d\n" , pPrevBB->index, pNewBB->index); + pPrevBB->pNextBB = pNewBB; + } + m_pCBB = pNewBB; + if (m_pCBB->isFilterOrCatchFuncletEntry && (m_pCBB->emitState == BBStateEmitting)) + { + AddIns(INTOP_LOAD_EXCEPTION); + m_pLastNewIns->SetDVar(m_pCBB->clauseVarIndex); + } } int32_t opcodeSize = CEEOpcodeSize(m_ip, codeEnd); @@ -3530,15 +3919,84 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) m_ip += 5; break; } + case CEE_ENDFILTER: + AddIns(INTOP_LEAVE_FILTER); + m_pStackPointer--; + m_pLastNewIns->SetSVar(m_pStackPointer[0].var); + m_ip++; + linkBBlocks = false; + break; + case CEE_RETHROW: + AddIns(INTOP_RETHROW); + m_ip++; + linkBBlocks = false; + break; default: assert(0); break; } break; + + case CEE_ENDFINALLY: + { + AddIns(INTOP_RET_VOID); + m_ip++; + linkBBlocks = false; + break; + } + case CEE_LEAVE: + case CEE_LEAVE_S: + { + int32_t ilOffset = (int32_t)(m_ip - m_pILCode); + int32_t target = (opcode == CEE_LEAVE) ? ilOffset + 5 + *(int32_t*)(m_ip + 1) : (ilOffset + 2 + (int8_t)m_ip[1]); + InterpBasicBlock *pTargetBB = m_ppOffsetToBB[target]; + + m_pStackPointer = m_pStackBase; + + // The leave will jump: + // * directly to its target if it doesn't jump out of any try regions with finally. + // * to a finally call island of the first try region with finally that it jumps out of. + + for (int i = 0; i < m_leavesTable.GetSize(); i++) + { + if (m_leavesTable.Get(i).ilOffset == ilOffset) + { + // There is a finally call island for this leave, so we will jump to it + // instead of the target. The chain of these islands will end up on + // the target in the end. + // NOTE: we need to use basic block to branch and not an IL offset extracted + // from the building block, because the finally call islands share the same IL + // offset with another block of original code in front of which it is injected. + // The EmitBranch would to that block instead of the finally call island. + pTargetBB = m_leavesTable.Get(i).pFinallyCallIslandBB; + break; + } + } + + // The leave doesn't jump out of any try region with finally, so we can just emit a branch + // to the target. + if (m_pCBB->clauseType == BBClauseCatch) + { + // leave out of catch is different from a leave out of finally. It + // exits the catch handler and returns the address of the finally + // call island as the continuation address to the EH code. + EmitBranchToBB(INTOP_LEAVE_CATCH, pTargetBB); + } + else + { + EmitBranchToBB(INTOP_BR, pTargetBB); + } + + m_ip += (opcode == CEE_LEAVE) ? 5 : 2; + linkBBlocks = false; + break; + } + case CEE_THROW: AddIns(INTOP_THROW); m_pLastNewIns->SetSVar(m_pStackPointer[-1].var); m_ip += 1; + linkBBlocks = false; break; case CEE_BOX: @@ -3769,6 +4227,23 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) break; } + case CEE_ISINST: + { + CHECK_STACK(1); + CORINFO_CLASS_HANDLE clsHnd = ResolveClassToken(getU4LittleEndian(m_ip + 1)); + void* helperFtnSlot = nullptr; + void *helperFtn = m_compHnd->getHelperFtn(CORINFO_HELP_ISINSTANCEOFANY, &helperFtnSlot); + AddIns(INTOP_CALL_HELPER_PP_2); + m_pLastNewIns->data[0] = GetDataItemIndex(helperFtn); + m_pLastNewIns->data[1] = GetDataItemIndex(helperFtnSlot); + m_pLastNewIns->data[2] = GetDataItemIndex(clsHnd); + m_pLastNewIns->SetSVar(m_pStackPointer[-1].var); + m_pStackPointer--; + PushInterpType(InterpTypeI, NULL); + m_pLastNewIns->SetDVar(m_pStackPointer[-1].var); + m_ip += 5; + break; + } default: assert(0); break; diff --git a/src/coreclr/interpreter/compiler.h b/src/coreclr/interpreter/compiler.h index 1afaca1dbb53f6..c43fe7240f2b6b 100644 --- a/src/coreclr/interpreter/compiler.h +++ b/src/coreclr/interpreter/compiler.h @@ -137,22 +137,53 @@ 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) @@ -160,15 +191,23 @@ struct InterpBasicBlock 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; } }; @@ -261,6 +300,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; @@ -284,6 +334,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 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 @@ -347,6 +401,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); @@ -357,6 +412,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); @@ -423,10 +481,14 @@ class InterpCompiler int32_t ComputeCodeSize(); uint32_t ConvertOffset(int32_t offset); void EmitCode(); + int32_t* EmitBBCode(int32_t *ip, InterpBasicBlock *bb, TArray *relocs); int32_t* EmitCodeIns(int32_t *ip, InterpInst *pIns, TArray *relocs); void PatchRelocations(TArray *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); @@ -443,6 +505,7 @@ class InterpCompiler InterpMethod* CompileMethod(); void BuildGCInfo(InterpMethod *pInterpMethod); + void BuildEHInfo(); int32_t* GetCode(int32_t *pCodeSize); }; diff --git a/src/coreclr/interpreter/compileropt.cpp b/src/coreclr/interpreter/compileropt.cpp index d8237fae633f93..c0be1655390211 100644 --- a/src/coreclr/interpreter/compileropt.cpp +++ b/src/coreclr/interpreter/compileropt.cpp @@ -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 diff --git a/src/coreclr/interpreter/eeinterp.cpp b/src/coreclr/interpreter/eeinterp.cpp index e503fa72786b40..b1e00897679b7a 100644 --- a/src/coreclr/interpreter/eeinterp.cpp +++ b/src/coreclr/interpreter/eeinterp.cpp @@ -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; } diff --git a/src/coreclr/interpreter/intops.cpp b/src/coreclr/interpreter/intops.cpp index 94f259d14d10bc..7616b2d82c2d84 100644 --- a/src/coreclr/interpreter/intops.cpp +++ b/src/coreclr/interpreter/intops.cpp @@ -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); diff --git a/src/coreclr/interpreter/intops.def b/src/coreclr/interpreter/intops.def index 8f1a45f8611ae0..411b91ec4e4ef4 100644 --- a/src/coreclr/interpreter/intops.def +++ b/src/coreclr/interpreter/intops.def @@ -283,16 +283,25 @@ 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_2, "call.helper.pp.2", 6, 1, 1, InterpOpThreeInts) + +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) diff --git a/src/coreclr/vm/codeman.cpp b/src/coreclr/vm/codeman.cpp index 0df03a93cb9aba..f0fca7c633b8fe 100644 --- a/src/coreclr/vm/codeman.cpp +++ b/src/coreclr/vm/codeman.cpp @@ -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) diff --git a/src/coreclr/vm/codeman.h b/src/coreclr/vm/codeman.h index 3a16bacf735cd5..4e4401c7589919 100644 --- a/src/coreclr/vm/codeman.h +++ b/src/coreclr/vm/codeman.h @@ -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: diff --git a/src/coreclr/vm/eetwain.cpp b/src/coreclr/vm/eetwain.cpp index 8933ebd37117ae..213ec0c9831417 100644 --- a/src/coreclr/vm/eetwain.cpp +++ b/src/coreclr/vm/eetwain.cpp @@ -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) diff --git a/src/coreclr/vm/exceptionhandling.cpp b/src/coreclr/vm/exceptionhandling.cpp index ca3fb070ae63bc..11b75db76050b8 100644 --- a/src/coreclr/vm/exceptionhandling.cpp +++ b/src/coreclr/vm/exceptionhandling.cpp @@ -1698,6 +1698,44 @@ VOID DECLSPEC_NORETURN DispatchManagedException(RuntimeExceptionKind reKind) DispatchManagedException(throwable); } +VOID DECLSPEC_NORETURN DispatchRethrownManagedException(CONTEXT* pExceptionContext) +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_MODE_COOPERATIVE; + + Thread *pThread = GetThread(); + + ExInfo *pActiveExInfo = (ExInfo*)pThread->GetExceptionState()->GetCurrentExceptionTracker(); + + ExInfo exInfo(pThread, pActiveExInfo->m_ptrs.ExceptionRecord, pExceptionContext, ExKind::None); + + GCPROTECT_BEGIN(exInfo.m_exception); + PREPARE_NONVIRTUAL_CALLSITE(METHOD__EH__RH_RETHROW); + DECLARE_ARGHOLDER_ARRAY(args, 2); + + args[ARGNUM_0] = PTR_TO_ARGHOLDER(pActiveExInfo); + args[ARGNUM_1] = PTR_TO_ARGHOLDER(&exInfo); + + pThread->IncPreventAbort(); + + //Ex.RhRethrow(ref ExInfo activeExInfo, ref ExInfo exInfo) + CALL_MANAGED_METHOD_NORET(args) + GCPROTECT_END(); +} + +VOID DECLSPEC_NORETURN DispatchRethrownManagedException() +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_MODE_COOPERATIVE; + + CONTEXT exceptionContext; + ClrCaptureContext(&exceptionContext); + + DispatchRethrownManagedException(&exceptionContext); +} + #ifndef TARGET_UNIX void ClrUnwindEx(EXCEPTION_RECORD* pExceptionRecord, UINT_PTR ReturnValue, UINT_PTR TargetIP, UINT_PTR TargetFrameSp) { @@ -2140,27 +2178,13 @@ CallDescrWorkerUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionReco InterlockedAnd((LONG*)&pThread->m_fPreemptiveGCDisabled, 0); // We'll let the SO infrastructure handle this exception... at that point, we // know that we'll have enough stack to do it. - return ExceptionContinueSearch; } - - EXCEPTION_DISPOSITION retVal; - - retVal = ExceptionContinueSearch; - - if (retVal == ExceptionContinueSearch) + else if (IS_UNWINDING(pExceptionRecord->ExceptionFlags)) { - - if (IS_UNWINDING(pExceptionRecord->ExceptionFlags)) - { - CleanUpForSecondPass(pThread, false, pEstablisherFrame, pEstablisherFrame); - } - - // We're scanning out from CallDescr and potentially through the EE and out to unmanaged. - // So switch to preemptive mode. - GCX_PREEMP_NO_DTOR(); + CleanUpForSecondPass(pThread, false, pEstablisherFrame, pEstablisherFrame); } - return retVal; + return ExceptionContinueSearch; } #endif // TARGET_UNIX diff --git a/src/coreclr/vm/exceptionhandling.h b/src/coreclr/vm/exceptionhandling.h index 50edc30507dd29..0199b7d26e692a 100644 --- a/src/coreclr/vm/exceptionhandling.h +++ b/src/coreclr/vm/exceptionhandling.h @@ -31,6 +31,8 @@ CallDescrWorkerUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionRe VOID DECLSPEC_NORETURN DispatchManagedException(OBJECTREF throwable, CONTEXT *pExceptionContext, EXCEPTION_RECORD *pExceptionRecord = NULL); VOID DECLSPEC_NORETURN DispatchManagedException(OBJECTREF throwable); VOID DECLSPEC_NORETURN DispatchManagedException(RuntimeExceptionKind reKind); +VOID DECLSPEC_NORETURN DispatchRethrownManagedException(); +VOID DECLSPEC_NORETURN DispatchRethrownManagedException(CONTEXT* pExceptionContext); enum CLRUnwindStatus { UnwindPending, FirstPassComplete, SecondPassComplete }; diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index 15d91a4cf5398c..79789f198702e4 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -49,6 +49,7 @@ void InvokeCompiledMethod(MethodDesc *pMD, int8_t *pArgs, int8_t *pRet) typedef void* (*HELPER_FTN_PP)(void*); typedef void* (*HELPER_FTN_BOX_UNBOX)(MethodTable*, void*); typedef Object* (*HELPER_FTN_NEWARR)(CORINFO_CLASS_HANDLE, intptr_t); +typedef void* (*HELPER_FTN_PP_2)(void*, void*); InterpThreadContext::InterpThreadContext() { @@ -71,10 +72,9 @@ static void InterpBreakpoint() #define LOCAL_VAR_ADDR(offset,type) ((type*)(stack + (offset))) #define LOCAL_VAR(offset,type) (*LOCAL_VAR_ADDR(offset, type)) -// TODO once we have basic EH support -#define NULL_CHECK(o) +#define NULL_CHECK(o) do { if ((o) == NULL) { COMPlusThrow(kNullReferenceException); } } while (0) -void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFrame *pFrame, InterpThreadContext *pThreadContext) +void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFrame *pFrame, InterpThreadContext *pThreadContext, ExceptionClauseArgs *pExceptionClauseArgs) { CONTRACTL { @@ -90,13 +90,32 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr const int32_t *ip; int8_t *stack; + // When executing a filter funclet, this points to the parent frame of the filter + int8_t *frame = NULL; + InterpMethod *pMethod = *(InterpMethod**)pFrame->startIp; assert(pMethod->CheckIntegrity()); pThreadContext->pStackPointer = pFrame->pStack + pMethod->allocaSize; - ip = pFrame->startIp + sizeof(InterpMethod*) / sizeof(int32_t); stack = pFrame->pStack; + if (pExceptionClauseArgs == NULL) + { + // Start executing at the beginning of the method + ip = pFrame->startIp + sizeof(InterpMethod*) / sizeof(int32_t); + } + else + { + // * Filter funclets are executed in the current frame, because they are executed + // in the first pass of EH when the frames between the current frame and the + // parent frame are still alive. All accesses to the locals and arguments + // in this case use the pExceptionClauseArgs->pFrame->pStack as a frame pointer. + // * Catch and finally funclets are running in the parent frame directly + + // Start executing at the beginning of the exception clause + ip = pExceptionClauseArgs->ip; + } + int32_t returnOffset, callArgsOffset, methodSlot; const int32_t *targetIp; @@ -114,6 +133,9 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr // keep it for such purposes until we don't need it anymore. pFrame->ip = (int32_t*)ip; + + int offset = (int)(ip - (pFrame->startIp + sizeof(InterpMethod*) / sizeof(int32_t))); + switch (*ip) { #ifdef DEBUG @@ -171,7 +193,12 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr case INTOP_LDLOCA: LOCAL_VAR(ip[1], void*) = stack + ip[2]; ip += 3; - break;; + break; + case INTOP_LOAD_FRAMEVAR: + _ASSERTE((pExceptionClauseArgs != NULL) && (pExceptionClauseArgs->isFilter)); + LOCAL_VAR(ip[1], void*) = pExceptionClauseArgs->pFrame->pStack; + ip += 2; + break; #define MOV(argtype1,argtype2) \ LOCAL_VAR(ip [1], argtype1) = LOCAL_VAR(ip [2], argtype2); \ @@ -644,7 +671,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr int32_t i2 = LOCAL_VAR(ip[3], int32_t); int32_t i3; if (!ClrSafeInt::multiply(i1, i2, i3)) - assert(0); // Interpreter-TODO: OverflowException + COMPlusThrow(kOverflowException); LOCAL_VAR(ip[1], int32_t) = i3; ip += 4; break; @@ -656,7 +683,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr int64_t i2 = LOCAL_VAR(ip[3], int64_t); int64_t i3; if (!ClrSafeInt::multiply(i1, i2, i3)) - assert(0); // Interpreter-TODO: OverflowException + COMPlusThrow(kOverflowException); LOCAL_VAR(ip[1], int64_t) = i3; ip += 4; break; @@ -668,7 +695,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr uint32_t i2 = LOCAL_VAR(ip[3], uint32_t); uint32_t i3; if (!ClrSafeInt::multiply(i1, i2, i3)) - assert(0); // Interpreter-TODO: OverflowException + COMPlusThrow(kOverflowException); LOCAL_VAR(ip[1], uint32_t) = i3; ip += 4; break; @@ -680,7 +707,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr uint64_t i2 = LOCAL_VAR(ip[3], uint64_t); uint64_t i3; if (!ClrSafeInt::multiply(i1, i2, i3)) - assert(0); // Interpreter-TODO: OverflowException + COMPlusThrow(kOverflowException); LOCAL_VAR(ip[1], uint64_t) = i3; ip += 4; break; @@ -690,9 +717,9 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr int32_t i1 = LOCAL_VAR(ip[2], int32_t); int32_t i2 = LOCAL_VAR(ip[3], int32_t); if (i2 == 0) - assert(0); // Interpreter-TODO: DivideByZeroException + COMPlusThrow(kDivideByZeroException); if (i2 == -1 && i1 == INT32_MIN) - assert(0); // Interpreter-TODO: OverflowException + COMPlusThrow(kOverflowException); LOCAL_VAR(ip[1], int32_t) = i1 / i2; ip += 4; break; @@ -702,9 +729,9 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr int64_t l1 = LOCAL_VAR(ip[2], int64_t); int64_t l2 = LOCAL_VAR(ip[3], int64_t); if (l2 == 0) - assert(0); // Interpreter-TODO: DivideByZeroException + COMPlusThrow(kDivideByZeroException); if (l2 == -1 && l1 == INT64_MIN) - assert(0); // Interpreter-TODO: OverflowException + COMPlusThrow(kOverflowException); LOCAL_VAR(ip[1], int64_t) = l1 / l2; ip += 4; break; @@ -721,7 +748,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr { uint32_t i2 = LOCAL_VAR(ip[3], uint32_t); if (i2 == 0) - assert(0); // Interpreter-TODO: DivideByZeroException + COMPlusThrow(kDivideByZeroException); LOCAL_VAR(ip[1], uint32_t) = LOCAL_VAR(ip[2], uint32_t) / i2; ip += 4; break; @@ -730,7 +757,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr { uint64_t l2 = LOCAL_VAR(ip[3], uint64_t); if (l2 == 0) - assert(0); // Interpreter-TODO: DivideByZeroException + COMPlusThrow(kDivideByZeroException); LOCAL_VAR(ip[1], uint64_t) = LOCAL_VAR(ip[2], uint64_t) / l2; ip += 4; break; @@ -741,9 +768,9 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr int32_t i1 = LOCAL_VAR(ip[2], int32_t); int32_t i2 = LOCAL_VAR(ip[3], int32_t); if (i2 == 0) - assert(0); // Interpreter-TODO: DivideByZeroException + COMPlusThrow(kDivideByZeroException); if (i2 == -1 && i1 == INT32_MIN) - assert(0); // Interpreter-TODO: OverflowException + COMPlusThrow(kOverflowException); LOCAL_VAR(ip[1], int32_t) = i1 % i2; ip += 4; break; @@ -753,9 +780,9 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr int64_t l1 = LOCAL_VAR(ip[2], int64_t); int64_t l2 = LOCAL_VAR(ip[3], int64_t); if (l2 == 0) - assert(0); // Interpreter-TODO: DivideByZeroException + COMPlusThrow(kDivideByZeroException); if (l2 == -1 && l1 == INT64_MIN) - assert(0); // Interpreter-TODO: OverflowException + COMPlusThrow(kOverflowException); LOCAL_VAR(ip[1], int64_t) = l1 % l2; ip += 4; break; @@ -772,7 +799,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr { uint32_t i2 = LOCAL_VAR(ip[3], uint32_t); if (i2 == 0) - assert(0); // Interpreter-TODO: DivideByZeroException + COMPlusThrow(kDivideByZeroException); LOCAL_VAR(ip[1], uint32_t) = LOCAL_VAR(ip[2], uint32_t) % i2; ip += 4; break; @@ -781,7 +808,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr { uint64_t l2 = LOCAL_VAR(ip[3], uint64_t); if (l2 == 0) - assert(0); // Interpreter-TODO: DivideByZeroException + COMPlusThrow(kDivideByZeroException); LOCAL_VAR(ip[1], uint64_t) = LOCAL_VAR(ip[2], uint64_t) % l2; ip += 4; break; @@ -983,7 +1010,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr case INTOP_LDIND_VT: { char *src = LOCAL_VAR(ip[2], char*); - NULL_CHECK(obj); + NULL_CHECK(src); memcpy(stack + ip[1], (char*)src + ip[3], ip[4]); ip += 5; break; @@ -1026,7 +1053,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr { char *dst = LOCAL_VAR(ip[1], char*); OBJECTREF storeObj = LOCAL_VAR(ip[2], OBJECTREF); - NULL_CHECK(obj); + NULL_CHECK(dst); SetObjectReferenceUnchecked((OBJECTREF*)(dst + ip[3]), storeObj); ip += 4; break; @@ -1058,19 +1085,29 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr } case INTOP_CALL_HELPER_PP: + case INTOP_CALL_HELPER_PP_2: { - HELPER_FTN_PP helperFtn = (HELPER_FTN_PP)pMethod->pDataItems[ip[2]]; - HELPER_FTN_PP* helperFtnSlot = (HELPER_FTN_PP*)pMethod->pDataItems[ip[3]]; - void* helperArg = pMethod->pDataItems[ip[4]]; + int base = (*ip == INTOP_CALL_HELPER_PP) ? 2 : 3; + void* helperFtn = pMethod->pDataItems[ip[base]]; + void** helperFtnSlot = (void**)pMethod->pDataItems[ip[base + 1]]; + void* helperArg = pMethod->pDataItems[ip[base + 2]]; if (!helperFtn) helperFtn = *helperFtnSlot; // This can call either native or compiled managed code. For an interpreter // only configuration, this might be problematic, at least performance wise. - // FIXME We will need to handle exception throwing here. - LOCAL_VAR(ip[1], void*) = helperFtn(helperArg); - ip += 5; + if (*ip == INTOP_CALL_HELPER_PP) + { + LOCAL_VAR(ip[1], void*) = ((HELPER_FTN_PP)helperFtn)(helperArg); + ip += 5; + } + else + { + LOCAL_VAR(ip[1], void*) = ((HELPER_FTN_PP_2)helperFtn)(helperArg, LOCAL_VAR(ip[2], void*)); + ip += 6; + } + break; } case INTOP_CALLVIRT: @@ -1227,8 +1264,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr pMemory = pThreadContext->frameDataAllocator.Alloc(pFrame, len); if (pMemory == NULL) { - // Interpreter-TODO: OutOfMemoryException - assert(0); + COMPlusThrowOM(); } if (pMethod->initLocals) { @@ -1268,6 +1304,18 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr UNREACHABLE(); break; } + case INTOP_RETHROW: + { + DispatchRethrownManagedException(); + UNREACHABLE(); + break; + } + case INTOP_LOAD_EXCEPTION: + // This opcode loads the exception object coming from a catch / filter funclet caller to a variable. + assert(pExceptionClauseArgs != NULL); + LOCAL_VAR(ip[1], OBJECTREF) = pExceptionClauseArgs->throwable; + ip += 2; + break; case INTOP_BOX: case INTOP_UNBOX: case INTOP_UNBOX_ANY: @@ -1304,7 +1352,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr { int32_t length = LOCAL_VAR(ip[2], int32_t); if (length < 0) - assert(0); // Interpreter-TODO: Invalid array length + COMPlusThrow(kArgumentOutOfRangeException); CORINFO_CLASS_HANDLE arrayClsHnd = (CORINFO_CLASS_HANDLE)pMethod->pDataItems[ip[3]]; size_t helperDirectOrIndirect = (size_t)pMethod->pDataItems[ip[4]]; @@ -1441,6 +1489,37 @@ do { \ ip += 4; break; } + case INTOP_CALL_FINALLY: + { + const int32_t* targetIp = ip + ip[1]; + // Save current execution state for when we return from called method + pFrame->ip = ip + 2; + + // Allocate child frame. + { + InterpMethodContextFrame *pChildFrame = pFrame->pNext; + if (!pChildFrame) + { + pChildFrame = (InterpMethodContextFrame*)alloca(sizeof(InterpMethodContextFrame)); + pChildFrame->pNext = NULL; + pFrame->pNext = pChildFrame; + } + // Set the frame to the same values as the caller frame. + pChildFrame->ReInit(pFrame, pFrame->startIp, pFrame->pRetVal, pFrame->pStack); + pFrame = pChildFrame; + } + assert (((size_t)pFrame->pStack % INTERP_STACK_ALIGNMENT) == 0); + + // Set execution state for the new frame + ip = targetIp; + break; + } + case INTOP_LEAVE_FILTER: + *(int64_t*)pFrame->pRetVal = LOCAL_VAR(ip[1], int32_t); + goto EXIT_FRAME; + case INTOP_LEAVE_CATCH: + *(const int32_t**)pFrame->pRetVal = ip + ip[1]; + goto EXIT_FRAME; case INTOP_FAILFAST: assert(0); break; diff --git a/src/coreclr/vm/interpexec.h b/src/coreclr/vm/interpexec.h index 7306f9796c137c..f79b8cf39c4128 100644 --- a/src/coreclr/vm/interpexec.h +++ b/src/coreclr/vm/interpexec.h @@ -16,6 +16,7 @@ struct StackVal { int32_t i; int64_t l; + size_t s; float f; double d; void *o; @@ -61,6 +62,18 @@ struct InterpThreadContext ~InterpThreadContext(); }; -void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFrame *pFrame, InterpThreadContext *pThreadContext); +struct ExceptionClauseArgs +{ + // Address of the exception clause IR code + const int32_t *ip; + // Frame in which context the exception clause is executed + InterpMethodContextFrame *pFrame; + // Set to true if the exception clause is a filter + bool isFilter; + // The exception object passed to the filter or catch clause + OBJECTREF throwable; +}; + +void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFrame *pFrame, InterpThreadContext *pThreadContext, ExceptionClauseArgs *pExceptionClauseArgs = NULL); #endif diff --git a/src/coreclr/vm/jithelpers.cpp b/src/coreclr/vm/jithelpers.cpp index 296069a9d170c4..74d550ee0ae3bc 100644 --- a/src/coreclr/vm/jithelpers.cpp +++ b/src/coreclr/vm/jithelpers.cpp @@ -1370,22 +1370,7 @@ HCIMPL0(void, IL_Rethrow) FC_CAN_TRIGGER_GC(); #ifdef FEATURE_EH_FUNCLETS - ExInfo *pActiveExInfo = (ExInfo*)pThread->GetExceptionState()->GetCurrentExceptionTracker(); - - ExInfo exInfo(pThread, pActiveExInfo->m_ptrs.ExceptionRecord, exceptionFrame.GetContext(), ExKind::None); - - GCPROTECT_BEGIN(exInfo.m_exception); - PREPARE_NONVIRTUAL_CALLSITE(METHOD__EH__RH_RETHROW); - DECLARE_ARGHOLDER_ARRAY(args, 2); - - args[ARGNUM_0] = PTR_TO_ARGHOLDER(pActiveExInfo); - args[ARGNUM_1] = PTR_TO_ARGHOLDER(&exInfo); - - pThread->IncPreventAbort(); - - //Ex.RhRethrow(ref ExInfo activeExInfo, ref ExInfo exInfo) - CALL_MANAGED_METHOD_NORET(args) - GCPROTECT_END(); + DispatchRethrownManagedException(exceptionFrame.GetContext()); #elif defined(TARGET_X86) INSTALL_MANAGED_EXCEPTION_DISPATCHER; INSTALL_UNWIND_AND_CONTINUE_HANDLER; diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 816371b97501dc..2f5c506ccb6000 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -2016,9 +2016,11 @@ extern "C" void STDCALL ExecuteInterpretedMethod(TransitionBlock* pTransitionBlo } frames(pTransitionBlock); + StackVal retVal; + frames.interpMethodContextFrame.startIp = (int32_t*)byteCodeAddr; frames.interpMethodContextFrame.pStack = sp; - frames.interpMethodContextFrame.pRetVal = sp; + frames.interpMethodContextFrame.pRetVal = (int8_t*)&retVal; InterpExecMethod(&frames.interpreterFrame, &frames.interpMethodContextFrame, threadContext); diff --git a/src/tests/JIT/interpreter/Interpreter.cs b/src/tests/JIT/interpreter/Interpreter.cs index b0b059a422e62c..068f876c8b855c 100644 --- a/src/tests/JIT/interpreter/Interpreter.cs +++ b/src/tests/JIT/interpreter/Interpreter.cs @@ -428,10 +428,558 @@ public static void RunInterpreterTests() if (!TestMdArray()) Environment.FailFast(null); */ + TestExceptionHandling(); System.GC.Collect(); } + public static void TestExceptionHandling() + { + TestTryFinally(); + TestCatchCurrent(); + TestCatchFinally(); + TestFilterCatchCurrent(); + TestFilterFailedCatchCurrent(); + TestFilterCatchFinallyCurrent(); + TestFilterFailedCatchFinallyCurrent(); + TestCatchNested(); + TestCatchFinallyNested(); + TestFilterCatchNested(); + TestFilterFailedCatchNested(); + TestFilterCatchFinallyNested(); + TestFilterFailedCatchFinallyNested(); + TestFinallyBeforeCatch(); + TestModifyAlias(); + + TestThrowWithinCatch(); + TestThrowWithinFinally(); + TestFinallyWithInnerTryBeforeCatch(); + TestFuncletAccessToLocals(); + } + + public static void TestFuncletAccessToLocals() + { + int a = 7; + int b = 3; + + try + { + Console.WriteLine(1); + try + { + Console.WriteLine(2); + throw null; + } + catch (Exception e) when (b == 3) + { + Console.WriteLine(b); + Console.WriteLine(e.Message); + try + { + Console.WriteLine(4); + } + catch (Exception e1) + { + Console.WriteLine(5); + Console.WriteLine(e1.Message); + } + finally + { + Console.WriteLine(6); + } + } + finally + { + Console.WriteLine(a); + } + } + catch (Exception e2) + { + Console.WriteLine(8); + Console.WriteLine(e2.Message); + } + } + + public static void TestTryFinally() + { + int x = 0; + try + { + x *= 10; + x += 1; + } + finally + { + x *= 10; + x += 2; + } + + if (x != 12) + { + throw null; + } + } + + public static void TestNestedTryFinally() + { + int x = 0; + try + { + x *= 10; + x += 1; + try + { + x *= 10; + x += 2; + } + finally + { + x *= 10; + x += 3; + } + } + finally + { + x *= 10; + x += 4; + } + if (x != 1234) + { + throw null; + } + } + + public static void TestFinallyBeforeCatch() + { + int x = 0; + try + { + x *= 10; + x += 1; + try + { + x *= 10; + x += 2; + throw null; + } + finally + { + x *= 10; + x += 3; + } + } catch (Exception) { + x *= 10; + x += 4; + } + if (x != 1234) + { + throw null; + } + } + + public static unsafe void TestModifyAlias() + { + int x = 1; + int* y = &x; + try + { + throw null; + } + catch (Exception) + { + // At this point, we are modifying the slot in the original frame + *y = 2; + // But then we check the value in the current frame, this will fail + if (x != 2) + { + throw null; + } + } + } + + public static void TestThrowWithinCatch() + { + try + { + try + { + throw null; + } + catch (Exception) + { + throw null; + } + } + catch (Exception) + { + } + } + + public static void TestThrowWithinFinally() + { + try + { + try + { + throw null; + } + catch (Exception) + { + } + finally + { + throw null; + } + } + catch (Exception) + { + } + } + + public static void Throw() + { + throw null; // Simulating the throw operation + } + + public static void TestCatchCurrent() + { + int x = 0; + try + { + x *= 10; + x += 1; + throw null; + } + catch (Exception) + { + x *= 10; + x += 2; + } + if (x != 12) + { + throw null; + } + } + + public static void TestCatchFinally() + { + int x = 0; + try + { + x *= 10; + x += 1; + throw null; + } + catch (Exception) + { + x *= 10; + x += 2; + } + finally + { + // Copied from PowLoop + // This small block of code require retry in GenerateCode + // and this test that the retry logic is correct even when the retry happen within a funclet + + int n = 5; + int nr = 10; + long ret = 1; + for (int i = 0; i < n; i++) + ret *= nr; + bool dummy= (int)ret == 100; + + x *= 10; + x += 3; + } + if (x != 123) + { + throw null; + } + } + + public static void TestFilterCatchCurrent() + { + int x = 0; + try + { + x *= 10; + x += 1; + throw null; + } + catch (Exception) when (x == 1) + { + x *= 10; + x += 2; + } + if (x != 12) + { + throw null; + } + } + + public static void TestFinallyWithInnerTryBeforeCatch() + { + int x = 0; + try + { + x *= 10; + x += 1; + try + { + x *= 10; + x += 2; + throw null; + } + finally + { + try + { + x *= 10; + x += 3; + } + finally + { + x *= 10; + x += 4; + } + x *= 10; + x += 5; + } + } catch (Exception) { + x *= 10; + x += 6; + } + if (x != 123456) + { + throw null; + } + } + + public static void TestFilterFailedCatchCurrent() + { + int x = 0; + try + { + x *= 10; + x += 1; + throw null; + } + catch (Exception) when (x != 1) + { + x *= 10; + x += 2; + } + catch (Exception) when (x == 1) + { + x *= 10; + x += 3; + } + if (x != 13) + { + throw null; + } + } + + public static void TestFilterCatchFinallyCurrent() + { + int x = 0; + try + { + x *= 10; + x += 1; + throw null; + } + catch (Exception) when (x == 1) + { + x *= 10; + x += 2; + } + finally + { + x *= 10; + x += 3; + } + if (x != 123) + { + throw null; + } + } + + + public static void TestFilterFailedCatchFinallyCurrent() + { + int x = 0; + try + { + x *= 10; + x += 1; + throw null; + } + catch (Exception) when (x != 1) + { + x *= 10; + x += 2; + } + catch (Exception) when (x == 1) + { + x *= 10; + x += 3; + } + finally + { + x *= 10; + x += 4; + } + if (x != 134) + { + throw null; + } + } + public static void TestCatchNested() + { + int x = 0; + try + { + x *= 10; + x += 1; + Throw(); + } + catch (Exception) + { + x *= 10; + x += 2; + } + if (x != 12) + { + throw null; + } + } + + public static void TestCatchFinallyNested() + { + int x = 0; + try + { + x *= 10; + x += 1; + Throw(); + } + catch (Exception) + { + x *= 10; + x += 2; + } + finally + { + x *= 10; + x += 3; + } + if (x != 123) + { + throw null; + } + } + + public static void TestFilterCatchNested() + { + int x = 0; + try + { + x *= 10; + x += 1; + Throw(); + } + catch (Exception) when (x == 1) + { + x *= 10; + x += 2; + } + if (x != 12) + { + throw null; + } + } + + public static void TestFilterFailedCatchNested() + { + int x = 0; + try + { + x *= 10; + x += 1; + Throw(); + } + catch (Exception) when (x != 1) + { + x *= 10; + x += 2; + } + catch (Exception) when (x == 1) + { + x *= 10; + x += 3; + } + if (x != 13) + { + throw null; + } + } + + public static void TestFilterCatchFinallyNested() + { + int x = 0; + try + { + x *= 10; + x += 1; + Throw(); + } + catch (Exception) when (x == 1) + { + x *= 10; + x += 2; + } + finally + { + x *= 10; + x += 3; + } + if (x != 123) + { + throw null; + } + } + + public static void TestFilterFailedCatchFinallyNested() + { + int x = 0; + try + { + x *= 10; + x += 1; + Throw(); + } + catch (Exception) when (x != 1) + { + x *= 10; + x += 2; + } + catch (Exception) when (x == 1) + { + x *= 10; + x += 3; + } + finally + { + x *= 10; + x += 4; + } + if (x != 134) + { + throw null; + } + } + public static int Mul4(int a, int b, int c, int d) { return a * b * c * d; From 4974e46e43ad42e8ff79dff3d4a7095353eddee9 Mon Sep 17 00:00:00 2001 From: Jan Vorlicek Date: Wed, 28 May 2025 18:44:05 +0200 Subject: [PATCH 2/5] PR feedback --- src/coreclr/interpreter/compiler.cpp | 47 ++++++++++++++++-------- src/coreclr/interpreter/intops.def | 4 +- src/coreclr/vm/interpexec.cpp | 28 +++++++++----- src/tests/JIT/interpreter/Interpreter.cs | 21 +++++++++++ 4 files changed, 72 insertions(+), 28 deletions(-) diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index 1bc60fc1b70578..defe7312b89dad 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -768,8 +768,8 @@ int32_t* InterpCompiler::EmitCodeIns(int32_t *ip, InterpInst *ins, TArraynativeOffset); if ((m_ILToNativeMapSize == 0) || (m_pILToNativeMap[m_ILToNativeMapSize - 1].ilOffset != ilOffset)) { - // This code assumes that instructions for the same IL offset are emitted in an sequence without - // any other IL offsets in between. + // This code assumes that instructions for the same IL offset are emitted in a single run without + // any other IL offsets in between and that they don't repeat again after the run ends. #ifdef _DEBUG for (int i = 0; i < m_ILToNativeMapSize; i++) { @@ -1072,6 +1072,8 @@ void InterpCompiler::BuildGCInfo(InterpMethod *pInterpMethod) void InterpCompiler::GetNativeRangeForClause(uint32_t startILOffset, uint32_t endILOffset, int32_t *nativeStartOffset, int32_t* nativeEndOffset) { InterpBasicBlock* pStartBB = m_ppOffsetToBB[startILOffset]; + assert(pStartBB != NULL); + InterpBasicBlock* pEndBB = pStartBB; for (InterpBasicBlock* pBB = pStartBB->pNextBB; (pBB != NULL) && ((uint32_t)pBB->ilOffset < endILOffset); pBB = pBB->pNextBB) { @@ -1559,7 +1561,7 @@ bool InterpCompiler::CreateBasicBlocks(CORINFO_METHOD_INFO* methodInfo) if (target >= codeSize) return false; pLeaveTargetBB = GetBB(target); - if (opcode == CEE_LEAVE_S || opcode == CEE_LEAVE) + if (opcode == CEE_LEAVE_S) { CreateFinallyCallIslandBasicBlocks(methodInfo, insOffset, pLeaveTargetBB); } @@ -1571,7 +1573,7 @@ bool InterpCompiler::CreateBasicBlocks(CORINFO_METHOD_INFO* methodInfo) if (target >= codeSize) return false; pLeaveTargetBB = GetBB(target); - if (opcode == CEE_LEAVE_S || opcode == CEE_LEAVE) + if (opcode == CEE_LEAVE) { CreateFinallyCallIslandBasicBlocks(methodInfo, insOffset, pLeaveTargetBB); } @@ -1820,6 +1822,7 @@ void InterpCompiler::EmitLoadVar(int32_t var) if (m_pCBB->clauseType == BBClauseFilter) { + assert(m_pVars[var].ILGlobal); AddIns(INTOP_LOAD_FRAMEVAR); PushInterpType(InterpTypeI, NULL); m_pLastNewIns->SetDVar(m_pStackPointer[-1].var); @@ -2394,13 +2397,10 @@ void InterpCompiler::EmitStaticFieldAddress(CORINFO_FIELD_INFO *pFieldInfo, CORI assert(0); break; } - void *helperFtnSlot; - void *helperFtn = m_compHnd->getHelperFtn(pFieldInfo->helper, &helperFtnSlot); // Call helper to obtain thread static base address AddIns(INTOP_CALL_HELPER_PP); - m_pLastNewIns->data[0] = GetDataItemIndex(helperFtn); - m_pLastNewIns->data[1] = GetDataItemIndex(helperFtnSlot); - m_pLastNewIns->data[2] = GetDataItemIndex(helperArg); + m_pLastNewIns->data[0] = GetDataItemIndexForHelperFtn(pFieldInfo->helper); + m_pLastNewIns->data[1] = GetDataItemIndex(helperArg); PushInterpType(InterpTypeByRef, NULL); m_pLastNewIns->SetDVar(m_pStackPointer[-1].var); @@ -2450,6 +2450,21 @@ void InterpCompiler::EmitStaticFieldAccess(InterpType interpFieldType, CORINFO_F void InterpCompiler::EmitLdLocA(int32_t var) { + if (m_pCBB->clauseType == BBClauseFilter) + { + AddIns(INTOP_LOAD_FRAMEVAR); + PushInterpType(InterpTypeI, NULL); + m_pLastNewIns->SetDVar(m_pStackPointer[-1].var); + m_pVars[var].indirects++; + AddIns(INTOP_ADD_P_IMM); + m_pLastNewIns->data[0] = var; + m_pLastNewIns->SetSVar(m_pStackPointer[-1].var); + m_pStackPointer--; + PushInterpType(InterpTypeByRef, NULL); + m_pLastNewIns->SetDVar(m_pStackPointer[-1].var); + return; + } + AddIns(INTOP_LDLOCA); m_pLastNewIns->SetSVar(var); m_pVars[var].indirects++; @@ -2535,7 +2550,7 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) if (m_pCBB->emitState == BBStateEmitting) m_pCBB->emitState = BBStateEmitted; // If the new bblock was already emitted, skip its instructions - if ((pNewBB->emitState == BBStateEmitted)) + if (pNewBB->emitState == BBStateEmitted) { if (linkBBlocks) { @@ -4230,13 +4245,13 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) case CEE_ISINST: { CHECK_STACK(1); - CORINFO_CLASS_HANDLE clsHnd = ResolveClassToken(getU4LittleEndian(m_ip + 1)); - void* helperFtnSlot = nullptr; - void *helperFtn = m_compHnd->getHelperFtn(CORINFO_HELP_ISINSTANCEOFANY, &helperFtnSlot); + CORINFO_RESOLVED_TOKEN resolvedToken; + ResolveToken(getU4LittleEndian(m_ip + 1), CORINFO_TOKENKIND_Casting, &resolvedToken); + + CorInfoHelpFunc castingHelper = m_compHnd->getCastingHelper(&resolvedToken, false /* throwing */); AddIns(INTOP_CALL_HELPER_PP_2); - m_pLastNewIns->data[0] = GetDataItemIndex(helperFtn); - m_pLastNewIns->data[1] = GetDataItemIndex(helperFtnSlot); - m_pLastNewIns->data[2] = GetDataItemIndex(clsHnd); + m_pLastNewIns->data[0] = GetDataItemIndexForHelperFtn(castingHelper); + m_pLastNewIns->data[1] = GetDataItemIndex(resolvedToken.hClass); m_pLastNewIns->SetSVar(m_pStackPointer[-1].var); m_pStackPointer--; PushInterpType(InterpTypeI, NULL); diff --git a/src/coreclr/interpreter/intops.def b/src/coreclr/interpreter/intops.def index 411b91ec4e4ef4..3fe446900541cd 100644 --- a/src/coreclr/interpreter/intops.def +++ b/src/coreclr/interpreter/intops.def @@ -282,8 +282,8 @@ 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_2, "call.helper.pp.2", 6, 1, 1, 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) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index 79789f198702e4..52417aae1ccf25 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -90,9 +90,6 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr const int32_t *ip; int8_t *stack; - // When executing a filter funclet, this points to the parent frame of the filter - int8_t *frame = NULL; - InterpMethod *pMethod = *(InterpMethod**)pFrame->startIp; assert(pMethod->CheckIntegrity()); @@ -112,6 +109,13 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr // in this case use the pExceptionClauseArgs->pFrame->pStack as a frame pointer. // * Catch and finally funclets are running in the parent frame directly + if (pExceptionClauseArgs->isFilter) + { + // Since filters run in their own frame, we need to clear the global variables + // so that GC doesn't pick garbage in variables that were not yet written to. + memset(pFrame->pStack, 0, pMethod->allocaSize); + } + // Start executing at the beginning of the exception clause ip = pExceptionClauseArgs->ip; } @@ -1088,24 +1092,28 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr case INTOP_CALL_HELPER_PP_2: { int base = (*ip == INTOP_CALL_HELPER_PP) ? 2 : 3; - void* helperFtn = pMethod->pDataItems[ip[base]]; - void** helperFtnSlot = (void**)pMethod->pDataItems[ip[base + 1]]; - void* helperArg = pMethod->pDataItems[ip[base + 2]]; - if (!helperFtn) - helperFtn = *helperFtnSlot; + size_t helperDirectOrIndirect = (size_t)pMethod->pDataItems[ip[base]]; + HELPER_FTN_PP helperFtn = nullptr; + if (helperDirectOrIndirect & INTERP_INDIRECT_HELPER_TAG) + helperFtn = *(HELPER_FTN_PP *)(helperDirectOrIndirect & ~INTERP_INDIRECT_HELPER_TAG); + else + helperFtn = (HELPER_FTN_PP)helperDirectOrIndirect; + + void* helperArg = pMethod->pDataItems[ip[base + 1]]; + // This can call either native or compiled managed code. For an interpreter // only configuration, this might be problematic, at least performance wise. if (*ip == INTOP_CALL_HELPER_PP) { LOCAL_VAR(ip[1], void*) = ((HELPER_FTN_PP)helperFtn)(helperArg); - ip += 5; + ip += 4; } else { LOCAL_VAR(ip[1], void*) = ((HELPER_FTN_PP_2)helperFtn)(helperArg, LOCAL_VAR(ip[2], void*)); - ip += 6; + ip += 5; } break; diff --git a/src/tests/JIT/interpreter/Interpreter.cs b/src/tests/JIT/interpreter/Interpreter.cs index 068f876c8b855c..a9461d001f08b4 100644 --- a/src/tests/JIT/interpreter/Interpreter.cs +++ b/src/tests/JIT/interpreter/Interpreter.cs @@ -455,12 +455,14 @@ public static void TestExceptionHandling() TestThrowWithinFinally(); TestFinallyWithInnerTryBeforeCatch(); TestFuncletAccessToLocals(); + TestFinallyRefLocal(); } public static void TestFuncletAccessToLocals() { int a = 7; int b = 3; + MyStruct2 str = new MyStruct2(2); try { @@ -500,6 +502,25 @@ public static void TestFuncletAccessToLocals() } } + public static bool TestFilter(ref TestStruct2 s) + { + return s.a == 1; + } + + public static void TestFinallyRefLocal() + { + TestStruct2 s; + s.a = 1; + s.b = 2; + try + { + throw null; + } + catch (Exception e) when (TestFilter(ref s)) + { + } + } + public static void TestTryFinally() { int x = 0; From 06309b3ecac95f34d5969638797017023cc2cff7 Mon Sep 17 00:00:00 2001 From: Jan Vorlicek Date: Wed, 28 May 2025 19:20:13 +0200 Subject: [PATCH 3/5] Fix linux build - missing UNREACHABLE(); --- src/coreclr/vm/exceptionhandling.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/coreclr/vm/exceptionhandling.cpp b/src/coreclr/vm/exceptionhandling.cpp index 11b75db76050b8..2d1c8a21f79e0b 100644 --- a/src/coreclr/vm/exceptionhandling.cpp +++ b/src/coreclr/vm/exceptionhandling.cpp @@ -1722,6 +1722,8 @@ VOID DECLSPEC_NORETURN DispatchRethrownManagedException(CONTEXT* pExceptionConte //Ex.RhRethrow(ref ExInfo activeExInfo, ref ExInfo exInfo) CALL_MANAGED_METHOD_NORET(args) GCPROTECT_END(); + + UNREACHABLE(); } VOID DECLSPEC_NORETURN DispatchRethrownManagedException() From 24b481f6fc6ea50ddecee060e026fc1a0613d3cd Mon Sep 17 00:00:00 2001 From: Jan Vorlicek Date: Thu, 29 May 2025 19:22:35 +0200 Subject: [PATCH 4/5] 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. --- src/coreclr/interpreter/compiler.cpp | 58 +++++++++++++++++++++------- src/coreclr/interpreter/compiler.h | 2 - 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index defe7312b89dad..ddeb905c8dee8b 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -1452,11 +1452,13 @@ void InterpCompiler::CreateILVars() m_totalVarsStackSize = offset; } +// Create finally call island basic blocks for all try regions with finally clauses that the leave exits. +// That means when the leaveOffset is inside the try region and the target is outside of it. +// These finally call island blocks are used for non-exceptional finally execution. +// The linked list of finally call island blocks is stored in the pFinallyCallIslandBB field of the finally basic block. +// The pFinallyCallIslandBB in the actual finally call island block points to the outer try region's finally call island block. void InterpCompiler::CreateFinallyCallIslandBasicBlocks(CORINFO_METHOD_INFO* methodInfo, int32_t leaveOffset, InterpBasicBlock* pLeaveTargetBB) { - // Create finally call island basic blocks for all try regions with finally clauses that the leave exits. - // That means when the leaveOffset is inside the try region and the target is outside of it. - // These finally call island blocks are used for non-exceptional finally execution. bool firstFinallyCallIsland = true; InterpBasicBlock* pInnerFinallyCallIslandBB = NULL; for (unsigned int i = 0; i < methodInfo->EHcount; i++) @@ -1468,34 +1470,39 @@ void InterpCompiler::CreateFinallyCallIslandBasicBlocks(CORINFO_METHOD_INFO* met continue; } + // Only try regions in which the leave instruction is located are considered. if ((uint32_t)leaveOffset < clause.TryOffset || (uint32_t)leaveOffset > (clause.TryOffset + clause.TryLength)) { continue; } + // If the leave target is inside the try region, we don't need to create a finally call island block. if ((uint32_t)pLeaveTargetBB->ilOffset >= clause.TryOffset && (uint32_t)pLeaveTargetBB->ilOffset <= (clause.TryOffset + clause.TryLength)) { continue; } InterpBasicBlock* pHandlerBB = GetBB(clause.HandlerOffset); - InterpBasicBlock* pFinallyCallIslandBB = AllocBB(clause.HandlerOffset + clause.HandlerLength); + InterpBasicBlock* pFinallyCallIslandBB = NULL; InterpBasicBlock** ppLastBBNext = &pHandlerBB->pFinallyCallIslandBB; while (*ppLastBBNext != NULL) { - if (*ppLastBBNext == pFinallyCallIslandBB) + if ((*ppLastBBNext)->pLeaveTargetBB == pLeaveTargetBB) { - // We already have this finally call island block - assert(pFinallyCallIslandBB->pLeaveTargetBB == pLeaveTargetBB); + // We already have finally call island block for the leave target + pFinallyCallIslandBB = (*ppLastBBNext); break; } ppLastBBNext = &((*ppLastBBNext)->pFinallyCallIslandBB); } - pFinallyCallIslandBB->pLeaveTargetBB = pLeaveTargetBB; - - *ppLastBBNext = pFinallyCallIslandBB; + if (pFinallyCallIslandBB == NULL) + { + pFinallyCallIslandBB = AllocBB(clause.HandlerOffset + clause.HandlerLength); + pFinallyCallIslandBB->pLeaveTargetBB = pLeaveTargetBB; + *ppLastBBNext = pFinallyCallIslandBB; + } if (pInnerFinallyCallIslandBB != NULL) { @@ -1721,6 +1728,30 @@ bool InterpCompiler::InitializeClauseBuildingBlocks(CORINFO_METHOD_INFO* methodI } } + // Now that we have classified all the basic blocks, we can set the clause type for the finally call island blocks. + // We set it to the same type as the basic block after the finally handler. + for (unsigned int i = 0; i < methodInfo->EHcount; i++) + { + CORINFO_EH_CLAUSE clause; + m_compHnd->getEHinfo(methodInfo->ftn, i, &clause); + + if (clause.Flags != CORINFO_EH_CLAUSE_FINALLY) + { + continue; + } + + InterpBasicBlock* pFinallyBB = GetBB(clause.HandlerOffset); + + InterpBasicBlock* pFinallyCallIslandBB = pFinallyBB->pFinallyCallIslandBB; + while (pFinallyCallIslandBB != NULL) + { + InterpBasicBlock* pAfterFinallyBB = m_ppOffsetToBB[clause.HandlerOffset + clause.HandlerLength]; + assert(pAfterFinallyBB != NULL); + pFinallyCallIslandBB->clauseType = pAfterFinallyBB->clauseType; + pFinallyCallIslandBB = pFinallyCallIslandBB->pNextBB; + } + } + return true; } @@ -1826,7 +1857,6 @@ void InterpCompiler::EmitLoadVar(int32_t var) AddIns(INTOP_LOAD_FRAMEVAR); PushInterpType(InterpTypeI, NULL); m_pLastNewIns->SetDVar(m_pStackPointer[-1].var); - m_pVars[var].indirects++; EmitLdind(interpType, clsHnd, m_pVars[var].offset); return; } @@ -2455,7 +2485,6 @@ void InterpCompiler::EmitLdLocA(int32_t var) AddIns(INTOP_LOAD_FRAMEVAR); PushInterpType(InterpTypeI, NULL); m_pLastNewIns->SetDVar(m_pStackPointer[-1].var); - m_pVars[var].indirects++; AddIns(INTOP_ADD_P_IMM); m_pLastNewIns->data[0] = var; m_pLastNewIns->SetSVar(m_pStackPointer[-1].var); @@ -2467,7 +2496,6 @@ void InterpCompiler::EmitLdLocA(int32_t var) AddIns(INTOP_LDLOCA); m_pLastNewIns->SetSVar(var); - m_pVars[var].indirects++; PushInterpType(InterpTypeByRef, NULL); m_pLastNewIns->SetDVar(m_pStackPointer[-1].var); } @@ -2630,13 +2658,14 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) while (pFinallyCallIslandBB != NULL) { - INTERP_DUMP("Injecting BB%d\n", pFinallyCallIslandBB->index); + INTERP_DUMP("Injecting finally call island BB%d\n", pFinallyCallIslandBB->index); if (pFinallyCallIslandBB->emitState != BBStateEmitted) { // Set the finally call island BB as current so that the instructions are emitted into it m_pCBB = pFinallyCallIslandBB; InitBBStackState(m_pCBB); EmitBranchToBB(INTOP_CALL_FINALLY, pNewBB); // The pNewBB is the finally BB + m_pLastNewIns->ilOffset = -1; // Try to get the next finally call island block (for an outer try's finally) if (pFinallyCallIslandBB->pFinallyCallIslandBB) { @@ -2648,6 +2677,7 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) // This is the last finally call island, so we need to emit a branch to the leave target EmitBranchToBB(INTOP_BR, pFinallyCallIslandBB->pLeaveTargetBB); } + m_pLastNewIns->ilOffset = -1; m_pCBB->emitState = BBStateEmitted; INTERP_DUMP("Chaining BB%d -> BB%d\n", pPrevBB->index, pFinallyCallIslandBB->index); } diff --git a/src/coreclr/interpreter/compiler.h b/src/coreclr/interpreter/compiler.h index c43fe7240f2b6b..b3f19a6d80f960 100644 --- a/src/coreclr/interpreter/compiler.h +++ b/src/coreclr/interpreter/compiler.h @@ -215,7 +215,6 @@ 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 @@ -241,7 +240,6 @@ struct InterpVar offset = -1; liveStart = NULL; bbIndex = -1; - indirects = 0; callArgs = false; noCallArgs = false; From 5ae46940eb18408fb6d1f2cb37ca2bb8cd37c408 Mon Sep 17 00:00:00 2001 From: Jan Vorlicek Date: Mon, 2 Jun 2025 14:41:21 +0200 Subject: [PATCH 5/5] PR feedback --- src/coreclr/interpreter/compiler.cpp | 84 +++++++++++++++------------- src/coreclr/interpreter/compiler.h | 1 + src/coreclr/vm/interpexec.cpp | 3 - 3 files changed, 46 insertions(+), 42 deletions(-) diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index ddeb905c8dee8b..466193eb228682 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -1365,7 +1365,7 @@ void InterpCompiler::CreateILVars() m_numILVars = numArgs + numILLocals; // add some starting extra space for new vars - m_varsCapacity = m_numILVars + 64; + m_varsCapacity = m_numILVars + m_methodInfo->EHcount + 64; m_pVars = (InterpVar*)AllocTemporary0(m_varsCapacity * sizeof (InterpVar)); m_varsSize = m_numILVars; @@ -1539,7 +1539,7 @@ bool InterpCompiler::CreateBasicBlocks(CORINFO_METHOD_INFO* methodInfo) OPCODE opcode = CEEDecodeOpcode(&ip); OPCODE_FORMAT opArgs = g_CEEOpArgs[opcode]; int32_t target; - InterpBasicBlock *pLeaveTargetBB; + InterpBasicBlock *pTargetBB; switch (opArgs) { @@ -1567,10 +1567,10 @@ bool InterpCompiler::CreateBasicBlocks(CORINFO_METHOD_INFO* methodInfo) target = insOffset + 2 + (int8_t)ip [1]; if (target >= codeSize) return false; - pLeaveTargetBB = GetBB(target); + pTargetBB = GetBB(target); if (opcode == CEE_LEAVE_S) { - CreateFinallyCallIslandBasicBlocks(methodInfo, insOffset, pLeaveTargetBB); + CreateFinallyCallIslandBasicBlocks(methodInfo, insOffset, pTargetBB); } ip += 2; GetBB((int32_t)(ip - codeStart)); @@ -1579,10 +1579,10 @@ bool InterpCompiler::CreateBasicBlocks(CORINFO_METHOD_INFO* methodInfo) target = insOffset + 5 + getI4LittleEndian(ip + 1); if (target >= codeSize) return false; - pLeaveTargetBB = GetBB(target); + pTargetBB = GetBB(target); if (opcode == CEE_LEAVE) { - CreateFinallyCallIslandBasicBlocks(methodInfo, insOffset, pLeaveTargetBB); + CreateFinallyCallIslandBasicBlocks(methodInfo, insOffset, pTargetBB); } ip += 5; GetBB((int32_t)(ip - codeStart)); @@ -2486,7 +2486,7 @@ void InterpCompiler::EmitLdLocA(int32_t var) PushInterpType(InterpTypeI, NULL); m_pLastNewIns->SetDVar(m_pStackPointer[-1].var); AddIns(INTOP_ADD_P_IMM); - m_pLastNewIns->data[0] = var; + m_pLastNewIns->data[0] = m_pVars[var].offset; m_pLastNewIns->SetSVar(m_pStackPointer[-1].var); m_pStackPointer--; PushInterpType(InterpTypeByRef, NULL); @@ -2654,38 +2654,7 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) InterpBasicBlock *pPrevBB = m_pCBB; - InterpBasicBlock *pFinallyCallIslandBB = pNewBB->pFinallyCallIslandBB; - - while (pFinallyCallIslandBB != NULL) - { - INTERP_DUMP("Injecting finally call island BB%d\n", pFinallyCallIslandBB->index); - if (pFinallyCallIslandBB->emitState != BBStateEmitted) - { - // Set the finally call island BB as current so that the instructions are emitted into it - m_pCBB = pFinallyCallIslandBB; - InitBBStackState(m_pCBB); - EmitBranchToBB(INTOP_CALL_FINALLY, pNewBB); // The pNewBB is the finally BB - m_pLastNewIns->ilOffset = -1; - // Try to get the next finally call island block (for an outer try's finally) - if (pFinallyCallIslandBB->pFinallyCallIslandBB) - { - // Branch to the next finally call island (at an outer try block) - EmitBranchToBB(INTOP_BR, pFinallyCallIslandBB->pFinallyCallIslandBB); - } - else - { - // This is the last finally call island, so we need to emit a branch to the leave target - EmitBranchToBB(INTOP_BR, pFinallyCallIslandBB->pLeaveTargetBB); - } - m_pLastNewIns->ilOffset = -1; - m_pCBB->emitState = BBStateEmitted; - INTERP_DUMP("Chaining BB%d -> BB%d\n", pPrevBB->index, pFinallyCallIslandBB->index); - } - assert(pPrevBB->pNextBB == NULL || pPrevBB->pNextBB == pFinallyCallIslandBB); - pPrevBB->pNextBB = pFinallyCallIslandBB; - pPrevBB = pFinallyCallIslandBB; - pFinallyCallIslandBB = pFinallyCallIslandBB->pNextBB; - } + pPrevBB = GenerateCodeForFinallyCallIslands(pNewBB, pPrevBB); if (!pPrevBB->pNextBB) { @@ -4318,6 +4287,43 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) return CORJIT_BADCODE; } +InterpBasicBlock *InterpCompiler::GenerateCodeForFinallyCallIslands(InterpBasicBlock *pNewBB, InterpBasicBlock *pPrevBB) +{ + InterpBasicBlock *pFinallyCallIslandBB = pNewBB->pFinallyCallIslandBB; + + while (pFinallyCallIslandBB != NULL) + { + INTERP_DUMP("Injecting finally call island BB%d\n", pFinallyCallIslandBB->index); + if (pFinallyCallIslandBB->emitState != BBStateEmitted) + { + // Set the finally call island BB as current so that the instructions are emitted into it + m_pCBB = pFinallyCallIslandBB; + InitBBStackState(m_pCBB); + EmitBranchToBB(INTOP_CALL_FINALLY, pNewBB); // The pNewBB is the finally BB + m_pLastNewIns->ilOffset = -1; + // Try to get the next finally call island block (for an outer try's finally) + if (pFinallyCallIslandBB->pFinallyCallIslandBB) + { + // Branch to the next finally call island (at an outer try block) + EmitBranchToBB(INTOP_BR, pFinallyCallIslandBB->pFinallyCallIslandBB); + } + else + { + // This is the last finally call island, so we need to emit a branch to the leave target + EmitBranchToBB(INTOP_BR, pFinallyCallIslandBB->pLeaveTargetBB); + } + m_pLastNewIns->ilOffset = -1; + m_pCBB->emitState = BBStateEmitted; + INTERP_DUMP("Chaining BB%d -> BB%d\n", pPrevBB->index, pFinallyCallIslandBB->index); + } + assert(pPrevBB->pNextBB == NULL || pPrevBB->pNextBB == pFinallyCallIslandBB); + pPrevBB->pNextBB = pFinallyCallIslandBB; + pPrevBB = pFinallyCallIslandBB; + pFinallyCallIslandBB = pFinallyCallIslandBB->pNextBB; + } + + return pPrevBB; +} void InterpCompiler::UnlinkUnreachableBBlocks() { // Unlink unreachable bblocks, prevBB is always an emitted bblock diff --git a/src/coreclr/interpreter/compiler.h b/src/coreclr/interpreter/compiler.h index b3f19a6d80f960..9ee206edd34098 100644 --- a/src/coreclr/interpreter/compiler.h +++ b/src/coreclr/interpreter/compiler.h @@ -347,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); diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index 52417aae1ccf25..b85e99751f685c 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -137,9 +137,6 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr // keep it for such purposes until we don't need it anymore. pFrame->ip = (int32_t*)ip; - - int offset = (int)(ip - (pFrame->startIp + sizeof(InterpMethod*) / sizeof(int32_t))); - switch (*ip) { #ifdef DEBUG