diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index d0d5b90e2090ab..466193eb228682 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 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++) + { + 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,22 +847,47 @@ 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++; + } + + if (clause.Flags == CORINFO_EH_CLAUSE_FILTER && clause.FilterOffset <= (uint32_t)bb->ilOffset && clause.HandlerOffset > (uint32_t)bb->ilOffset) + { + bb->overlappingEHClauseCount++; } + } + } - ip = EmitCodeIns(ip, ins, &relocs); + // 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); @@ -1022,6 +1069,97 @@ 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]; + assert(pStartBB != NULL); + + 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(); @@ -1227,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; @@ -1271,8 +1409,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 +1428,110 @@ 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) +// 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) { - 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); - + 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) + // 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)) { - return false; + continue; } - GetBB(clause.TryOffset); - if ((codeStart + clause.HandlerOffset) > codeEnd || - (codeStart + clause.HandlerOffset + clause.HandlerLength) > codeEnd) + // 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)) { - return false; + continue; } - GetBB(clause.HandlerOffset); - if (clause.Flags == CORINFO_EH_CLAUSE_FILTER) + InterpBasicBlock* pHandlerBB = GetBB(clause.HandlerOffset); + InterpBasicBlock* pFinallyCallIslandBB = NULL; + + InterpBasicBlock** ppLastBBNext = &pHandlerBB->pFinallyCallIslandBB; + while (*ppLastBBNext != NULL) { - if ((codeStart + clause.FilterOffset) > codeEnd) - return false; - GetBB(clause.FilterOffset); + if ((*ppLastBBNext)->pLeaveTargetBB == pLeaveTargetBB) + { + // We already have finally call island block for the leave target + pFinallyCallIslandBB = (*ppLastBBNext); + break; + } + ppLastBBNext = &((*ppLastBBNext)->pFinallyCallIslandBB); + } + + if (pFinallyCallIslandBB == NULL) + { + pFinallyCallIslandBB = AllocBB(clause.HandlerOffset + clause.HandlerLength); + 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 +1539,7 @@ bool InterpCompiler::CreateBasicBlocks(CORINFO_METHOD_INFO* methodInfo) OPCODE opcode = CEEDecodeOpcode(&ip); OPCODE_FORMAT opArgs = g_CEEOpArgs[opcode]; int32_t target; + InterpBasicBlock *pTargetBB; switch (opArgs) { @@ -1366,7 +1567,11 @@ bool InterpCompiler::CreateBasicBlocks(CORINFO_METHOD_INFO* methodInfo) target = insOffset + 2 + (int8_t)ip [1]; if (target >= codeSize) return false; - GetBB(target); + pTargetBB = GetBB(target); + if (opcode == CEE_LEAVE_S) + { + CreateFinallyCallIslandBasicBlocks(methodInfo, insOffset, pTargetBB); + } ip += 2; GetBB((int32_t)(ip - codeStart)); break; @@ -1374,7 +1579,11 @@ bool InterpCompiler::CreateBasicBlocks(CORINFO_METHOD_INFO* methodInfo) target = insOffset + 5 + getI4LittleEndian(ip + 1); if (target >= codeSize) return false; - GetBB(target); + pTargetBB = GetBB(target); + if (opcode == CEE_LEAVE) + { + CreateFinallyCallIslandBasicBlocks(methodInfo, insOffset, pTargetBB); + } ip += 5; GetBB((int32_t)(ip - codeStart)); break; @@ -1411,6 +1620,150 @@ 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; + } + } + + // 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; +} + +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 +1778,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 +1846,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) + { + assert(m_pVars[var].ILGlobal); + AddIns(INTOP_LOAD_FRAMEVAR); + PushInterpType(InterpTypeI, NULL); + m_pLastNewIns->SetDVar(m_pStackPointer[-1].var); + EmitLdind(interpType, clsHnd, m_pVars[var].offset); + return; + } + + int32_t size = m_pVars[var].size; + if (interpType == InterpTypeVT) PushTypeVT(clsHnd, size); else @@ -1521,6 +1880,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) @@ -2059,13 +2427,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); @@ -2115,9 +2480,22 @@ 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); + AddIns(INTOP_ADD_P_IMM); + m_pLastNewIns->data[0] = m_pVars[var].offset; + 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++; PushInterpType(InterpTypeByRef, NULL); m_pLastNewIns->SetDVar(m_pStackPointer[-1].var); } @@ -2142,12 +2520,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 +2558,7 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) linkBBlocks = true; needsRetryEmit = false; + retry_emit: emittedBBlocks = false; while (m_ip < codeEnd) @@ -2216,6 +2604,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 +2651,23 @@ 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; + + pPrevBB = GenerateCodeForFinallyCallIslands(pNewBB, pPrevBB); + + 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 +3933,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 +4241,23 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) break; } + case CEE_ISINST: + { + CHECK_STACK(1); + 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] = GetDataItemIndexForHelperFtn(castingHelper); + m_pLastNewIns->data[1] = GetDataItemIndex(resolvedToken.hClass); + 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; @@ -3798,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 1afaca1dbb53f6..9ee206edd34098 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; } }; @@ -176,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 @@ -202,7 +240,6 @@ struct InterpVar offset = -1; liveStart = NULL; bbIndex = -1; - indirects = 0; callArgs = false; noCallArgs = false; @@ -261,6 +298,17 @@ typedef class ICorJitInfo* COMP_HANDLE; class InterpIAllocator; +// Entry of the table where for each leave instruction we store the first finally call island +// to be executed when the leave instruction is executed. +struct LeavesTableEntry +{ + // offset of the CEE_LEAVE instruction + int32_t ilOffset; + // The BB of the call island BB that will be the first to call when the leave + // instruction is executed. + InterpBasicBlock *pFinallyCallIslandBB; +}; + class InterpCompiler { friend class InterpIAllocator; @@ -284,6 +332,10 @@ class InterpCompiler int32_t m_currentILOffset; InterpInst* m_pInitLocalsIns; + // Table of mappings of leave instructions to the first finally call island the leave + // needs to execute. + TArray 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 @@ -295,6 +347,7 @@ class InterpCompiler int32_t GetDataItemIndexForHelperFtn(CorInfoHelpFunc ftn); int GenerateCode(CORINFO_METHOD_INFO* methodInfo); + InterpBasicBlock* GenerateCodeForFinallyCallIslands(InterpBasicBlock *pNewBB, InterpBasicBlock *pPrevBB); void PatchInitLocals(CORINFO_METHOD_INFO* methodInfo); void ResolveToken(uint32_t token, CorInfoTokenKind tokenKind, CORINFO_RESOLVED_TOKEN *pResolvedToken); @@ -347,6 +400,7 @@ class InterpCompiler void EmitBranch(InterpOpcode opcode, int ilOffset); void EmitOneArgBranch(InterpOpcode opcode, int ilOffset, int insSize); void EmitTwoArgBranch(InterpOpcode opcode, int ilOffset, int insSize); + void EmitBranchToBB(InterpOpcode opcode, InterpBasicBlock *pTargetBB); void EmitBBEndVarMoves(InterpBasicBlock *pTargetBB); void InitBBStackState(InterpBasicBlock *pBB); @@ -357,6 +411,9 @@ class InterpCompiler int32_t m_varsSize = 0; int32_t m_varsCapacity = 0; int32_t m_numILVars = 0; + // For each catch or filter clause, we create a variable that holds the exception object. + // This is the index of the first such variable. + int32_t m_clauseVarsIndex = 0; int32_t CreateVarExplicit(InterpType interpType, CORINFO_CLASS_HANDLE clsHnd, int size); @@ -423,10 +480,14 @@ class InterpCompiler int32_t ComputeCodeSize(); uint32_t ConvertOffset(int32_t offset); void EmitCode(); + int32_t* EmitBBCode(int32_t *ip, InterpBasicBlock *bb, TArray *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 +504,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..3fe446900541cd 100644 --- a/src/coreclr/interpreter/intops.def +++ b/src/coreclr/interpreter/intops.def @@ -282,17 +282,26 @@ OPDEF(INTOP_CALLVIRT, "callvirt", 4, 1, 1, InterpOpMethodHandle) OPDEF(INTOP_NEWOBJ, "newobj", 5, 1, 1, InterpOpMethodHandle) OPDEF(INTOP_NEWOBJ_VT, "newobj.vt", 5, 1, 1, InterpOpMethodHandle) -OPDEF(INTOP_CALL_HELPER_PP, "call.helper.pp", 5, 1, 0, InterpOpThreeInts) +OPDEF(INTOP_CALL_HELPER_PP, "call.helper.pp", 4, 1, 0, InterpOpTwoInts) +OPDEF(INTOP_CALL_HELPER_PP_2, "call.helper.pp.2", 5, 1, 1, InterpOpTwoInts) + +OPDEF(INTOP_CALL_FINALLY, "call.finally", 2, 0, 0, InterpOpBranch) OPDEF(INTOP_ZEROBLK_IMM, "zeroblk.imm", 3, 0, 1, InterpOpInt) OPDEF(INTOP_LOCALLOC, "localloc", 3, 1, 1, InterpOpNoArgs) OPDEF(INTOP_BREAKPOINT, "breakpoint", 1, 0, 0, InterpOpNoArgs) OPDEF(INTOP_THROW, "throw", 4, 0, 1, InterpOpInt) +OPDEF(INTOP_RETHROW, "rethrow", 1, 0, 0, InterpOpInt) +OPDEF(INTOP_LEAVE_FILTER, "leavefilter", 2, 0, 1, InterpOpNoArgs) +OPDEF(INTOP_LEAVE_CATCH, "leavecatch", 2, 0, 0, InterpOpBranch) +OPDEF(INTOP_LOAD_EXCEPTION, "load.exception", 2, 1, 0, InterpOpNoArgs) OPDEF(INTOP_FAILFAST, "failfast", 1, 0, 0, InterpOpNoArgs) OPDEF(INTOP_GC_COLLECT, "gc.collect", 1, 0, 0, InterpOpNoArgs) +OPDEF(INTOP_LOAD_FRAMEVAR, "load.framevar", 2, 1, 0, InterpOpNoArgs) + // All instructions after this point are IROPS, instructions that are not emitted/executed OPDEF(INTOP_NOP, "nop", 1, 0, 0, InterpOpNoArgs) OPDEF(INTOP_DEF, "def", 2, 1, 0, InterpOpNoArgs) 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..2d1c8a21f79e0b 100644 --- a/src/coreclr/vm/exceptionhandling.cpp +++ b/src/coreclr/vm/exceptionhandling.cpp @@ -1698,6 +1698,46 @@ 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(); + + UNREACHABLE(); +} + +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 +2180,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..b85e99751f685c 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 { @@ -94,9 +94,32 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr 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 + + 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; + } + int32_t returnOffset, callArgsOffset, methodSlot; const int32_t *targetIp; @@ -171,7 +194,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 +672,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 +684,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 +696,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 +708,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 +718,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 +730,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 +749,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 +758,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 +769,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 +781,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 +800,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 +809,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 +1011,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 +1054,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 +1086,33 @@ 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; + + 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]]; - 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 += 4; + } + else + { + LOCAL_VAR(ip[1], void*) = ((HELPER_FTN_PP_2)helperFtn)(helperArg, LOCAL_VAR(ip[2], void*)); + ip += 5; + } + break; } case INTOP_CALLVIRT: @@ -1227,8 +1269,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 +1309,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 +1357,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 +1494,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..a9461d001f08b4 100644 --- a/src/tests/JIT/interpreter/Interpreter.cs +++ b/src/tests/JIT/interpreter/Interpreter.cs @@ -428,10 +428,579 @@ 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(); + TestFinallyRefLocal(); + } + + public static void TestFuncletAccessToLocals() + { + int a = 7; + int b = 3; + MyStruct2 str = new MyStruct2(2); + + 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 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; + 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;