diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index 272d6efb346d1e..7ed21e762fb219 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -1,5 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. + #include "gcinfoencoder.h" // HACK: debugreturn.h (included by gcinfoencoder.h) breaks constexpr @@ -305,7 +306,10 @@ InterpBasicBlock* InterpCompiler::GetBB(int32_t ilOffset) if (!bb) { bb = AllocBB(ilOffset); - +#ifdef DEBUG + // We should not be creating any more basic block after CreateBasicBlocks + assert(!m_generatedAllBasicBlocks); +#endif m_ppOffsetToBB[ilOffset] = bb; } @@ -568,6 +572,7 @@ bool InterpCompiler::CheckStackHelper(int n) int32_t currentSize = (int32_t)(m_pStackPointer - m_pStackBase); if (currentSize < n) { + assert(false); m_hasInvalidCode = true; return false; } @@ -648,43 +653,15 @@ uint32_t InterpCompiler::ConvertOffset(int32_t offset) return offset * sizeof(int32_t) + sizeof(void*); } -int32_t* InterpCompiler::EmitCodeIns(int32_t *ip, InterpInst *ins, TArray *relocs) +int32_t* InterpCompiler::EmitCodeIns(int32_t *ip, InterpInst *ins, TArray *relocs, bool enteringFunclet) { ins->nativeOffset = (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; + bool reverted = false; if (opcode == INTOP_SWITCH) { @@ -703,6 +680,8 @@ int32_t* InterpCompiler::EmitCodeIns(int32_t *ip, InterpInst *ins, TArraydVar].offset; for (int i = 0; i < g_interpOpSVars[opcode]; i++) *ip++ = m_pVars[ins->sVars[i]].offset; @@ -714,12 +693,14 @@ int32_t* InterpCompiler::EmitCodeIns(int32_t *ip, InterpInst *ins, TArrayflags |= INTERP_INST_FLAG_REVERTED; } else { // We don't know yet the IR offset of the target, add a reloc instead Reloc *reloc = (Reloc*)AllocMemPool(sizeof(Reloc)); - new (reloc) Reloc(RelocLongBranch, brBaseOffset, ins->info.pTargetBB, g_interpOpSVars[opcode]); + new (reloc) Reloc(RelocLongBranch, brBaseOffset, ins->info.pTargetBB, g_interpOpDVars[opcode] + g_interpOpSVars[opcode]); relocs->Add(reloc); *ip++ = (int32_t)0xdeadbeef; } @@ -734,6 +715,8 @@ int32_t* InterpCompiler::EmitCodeIns(int32_t *ip, InterpInst *ins, TArraydata[2]; // Revert opcode emit ip--; + reverted = true; + ins->flags |= INTERP_INST_FLAG_REVERTED; int destOffset = m_pVars[ins->dVar].offset; int srcOffset = m_pVars[ins->sVars[0]].offset; @@ -785,6 +768,47 @@ int32_t* InterpCompiler::EmitCodeIns(int32_t *ip, InterpInst *ins, TArraydata[i]; } + if (!reverted && (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)) + { + // + // Native offsets are obviously strictly increasing by construction here - note the possibility + // of an instruction emission being reverted above is handled + // + assert((m_ILToNativeMapSize == 0) || (m_pILToNativeMap[m_ILToNativeMapSize - 1].nativeOffset < nativeOffset)); + + // + // IL offsets are arranged into two blocks - the body and the funclets + // Both blocks are monotonically increasing, and the check above make sure we get a sequence + // of two strictly increasing ilOffsets + // + // Therefore, the only case where the ilOffset decrease is when we enter a funclet + // + assert((m_ILToNativeMapSize == 0) || enteringFunclet || (m_pILToNativeMap[m_ILToNativeMapSize - 1].ilOffset < ilOffset)); + + // + // 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; +#ifdef DEBUG + if (m_verbose) + { + printf("Reporting IL_%04x is mapped to IR_%04x(%d)\n", ins->ilOffset, ins->nativeOffset, nativeOffset); + } +#endif + m_ILToNativeMapSize++; + } + } + return ip; } @@ -825,16 +849,29 @@ void InterpCompiler::EmitCode() } int32_t *ip = m_pMethodCode; + bool insideFunclet = false; for (InterpBasicBlock *bb = m_pEntryBB; bb != NULL; bb = bb->pNextBB) { + bool enteringFunclet = false; + if (!insideFunclet) + { + if ((bb->funclet_type == FILTER_ENTRY) || (bb->funclet_type == CATCH_ENTRY) || (bb->funclet_type == FINALLY_ENTRY)) + { + enteringFunclet = true; + } + } bb->nativeOffset = (int32_t)(ip - m_pMethodCode); m_pCBB = bb; for (InterpInst *ins = bb->pFirstIns; ins != NULL; ins = ins->pNext) { if (InterpOpIsEmitNop(ins->opcode)) + { + ins->flags |= INTERP_INST_FLAG_REVERTED; continue; + } - ip = EmitCodeIns(ip, ins, &relocs); + ip = EmitCodeIns(ip, ins, &relocs, enteringFunclet); + enteringFunclet = false; } } @@ -852,6 +889,12 @@ void InterpCompiler::EmitCode() eeVars[j].loc.vlType = ICorDebugInfo::VLT_STK; // This is a stack slot eeVars[j].loc.vlStk.vlsBaseReg = ICorDebugInfo::REGNUM_FP; // This specifies which register this offset is based off eeVars[j].loc.vlStk.vlsOffset = m_pVars[i].offset; // This specifies starting from the offset, how much offset is this from +#ifdef DEBUG + if (m_verbose) + { + printf("Reporting var[%d] has life time [%d(%d), %d(%d)) on stack+%d\n", i, GetLiveStartOffset(i), eeVars[j].startOffset, GetLiveEndOffset(i), eeVars[j].endOffset, m_pVars[i].offset); + } +#endif j++; } @@ -1139,6 +1182,39 @@ bool InterpCompiler::CreateBasicBlocks(CORINFO_METHOD_INFO* methodInfo) m_ppOffsetToBB = (InterpBasicBlock**)AllocMemPool0(sizeof(InterpBasicBlock*) * (methodInfo->ILCodeSize + 1)); GetBB(0); + // + // Parsing the try/catch/finally block + // + // The goal of parsing these blocks are: + // 1.) Assigning the funclet_type of these blocks + // 2.) Assigning the try_exit_block of the associated handler block (i.e. catch/finally), we don't need that for filter, and + // 3.) Assign the finally start and finally id + // + + // + // Phase 1: For each funclet entry block, allocate sufficient space to store the exits + // + for (unsigned int i = 0; i < methodInfo->EHcount; i++) + { + CORINFO_EH_CLAUSE clause; + m_compHnd->getEHinfo(methodInfo->ftn, i, &clause); + InterpBasicBlock* tryBB = GetBB(clause.TryOffset); + tryBB->funclet_exit_count += 1; + if (clause.Flags == CORINFO_EH_CLAUSE_FILTER) + { + InterpBasicBlock* filterBB = GetBB(clause.FilterOffset); + filterBB->funclet_exit_count += 1; + } + InterpBasicBlock* handlerBB = GetBB(clause.HandlerOffset); + handlerBB->funclet_exit_count += 1; + } + + // + // Phase 2: For each funclet entry block, populate their exits into them, we have the space ready now + // For each finally block, allocate a global variable for it to store the frame pointer and + // save the finally index in the associated try and finally entry basic block + // + int finally_index = 0; for (unsigned int i = 0; i < methodInfo->EHcount; i++) { CORINFO_EH_CLAUSE clause; @@ -1149,23 +1225,120 @@ bool InterpCompiler::CreateBasicBlocks(CORINFO_METHOD_INFO* methodInfo) { return false; } - GetBB(clause.TryOffset); + InterpBasicBlock* tryBB = GetBB(clause.TryOffset); + tryBB->funclet_type = TRY_ENTRY; + if (tryBB->funclet_exit_current == 0) + { + tryBB->funclet_exits = (int*)AllocMemPool0(sizeof(int) * (tryBB->funclet_exit_count)); + tryBB->funclet_handlers = (InterpBasicBlock**)AllocMemPool0(sizeof(InterpBasicBlock**) * (tryBB->funclet_exit_count)); + } + tryBB->funclet_exits[tryBB->funclet_exit_current] = clause.TryOffset + clause.TryLength; + tryBB->funclet_handlers[tryBB->funclet_exit_current] = GetBB(clause.HandlerOffset); + tryBB->funclet_exit_current++; + if (tryBB->funclet_exit_current == tryBB->funclet_exit_count) + { + // + // For try block, we could have multiple try block start on the same address. + // We arrange their exits in a decreasing ilOffset order + // + for (int i = 0; i < tryBB->funclet_exit_count; i++) + { + for (int j = i + 1; j < tryBB->funclet_exit_count; j++) + { + if (tryBB->funclet_exits[i] < tryBB->funclet_exits[j]) + { + int temp = tryBB->funclet_exits[i]; + tryBB->funclet_exits[i] = tryBB->funclet_exits[j]; + tryBB->funclet_exits[j] = temp; + + InterpBasicBlock* tempBlock = tryBB->funclet_handlers[i]; + tryBB->funclet_handlers[i] = tryBB->funclet_handlers[j]; + tryBB->funclet_handlers[j] = tempBlock; + } + } + } + } if ((codeStart + clause.HandlerOffset) > codeEnd || (codeStart + clause.HandlerOffset + clause.HandlerLength) > codeEnd) { return false; } - GetBB(clause.HandlerOffset); + + InterpBasicBlock* handlerBB = GetBB(clause.HandlerOffset); + assert(handlerBB->funclet_exit_count == 1); + if (handlerBB->funclet_exit_current == 0) + { + handlerBB->funclet_exits = (int*)AllocMemPool0(sizeof(int) * (handlerBB->funclet_exit_count)); + } + handlerBB->funclet_exits[handlerBB->funclet_exit_current++] = clause.HandlerOffset + clause.HandlerLength; + handlerBB->stackHeight = 0; if (clause.Flags == CORINFO_EH_CLAUSE_FILTER) { if ((codeStart + clause.FilterOffset) > codeEnd) return false; - GetBB(clause.FilterOffset); + InterpBasicBlock* filterBB = GetBB(clause.FilterOffset); + assert(filterBB->funclet_exit_count == 1); + filterBB->funclet_type = FILTER_ENTRY; + if (filterBB->funclet_exit_current == 0) + { + filterBB->funclet_exits = (int*)AllocMemPool0(sizeof(int) * (filterBB->funclet_exit_count)); + } + filterBB->funclet_exits[filterBB->funclet_exit_current++] = clause.HandlerOffset; + filterBB->stackHeight = 0; + } + if (clause.Flags != CORINFO_EH_CLAUSE_FINALLY) + { + handlerBB->funclet_type = CATCH_ENTRY; + } + else + { + handlerBB->funclet_type = FINALLY_ENTRY; + tryBB->finally_index = finally_index; + handlerBB->finally_index = finally_index; + finally_index++; + // For each finally block, a variable is created to store a return address + // so that we know where to return to when the finally block ends. + CreateVarExplicit(InterpTypeI, NULL, sizeof(int32_t)); } } + // + // Phase 3: Perform a linear scan on the IL instructions to assign the funclet type + // + // As we walk the instructions, we create the blocks on the fly, for funclet type assignment purpose + // there are two types of interesting 'events': + // + // Entering a funclet entry block - where we already marked above + // - In this case, we already know the exits of these blocks, so we can produce a queue of these future events into + // the stacks below, the top of the stack should correspond to the event with the least ilOffset + // + // or + // + // Leaving a funclet block - where the stack will tell us when that happens earliest on the tip of the stack + // - In this case, pop away the event from the stacks + // + + // TODO: Make a stack of 1 struct instead of 4 stacks + int *exceptionBlockTypeStack = (int*) AllocMemPool((methodInfo->EHcount + 1) * sizeof(int)); + InterpBasicBlock **exceptionBlockStack = (InterpBasicBlock**) AllocMemPool((methodInfo->EHcount + 1) * sizeof(InterpBasicBlock*)); + InterpBasicBlock **exceptionHandlerStack = (InterpBasicBlock**) AllocMemPool((methodInfo->EHcount + 1) * sizeof(InterpBasicBlock*)); + int *exceptionExitOffsetStack = (int*) AllocMemPool((methodInfo->EHcount + 1) * sizeof(int)); + int *finallyIndexStack = (int*) AllocMemPool((methodInfo->EHcount + 1) * sizeof(int)); + + int exceptionStackPointer = 0; + + exceptionBlockTypeStack[exceptionStackPointer] = NOT_A_FUNCLET; + exceptionBlockStack[exceptionStackPointer] = nullptr; + exceptionHandlerStack[exceptionStackPointer] = nullptr; + exceptionExitOffsetStack[exceptionStackPointer] = -1; + exceptionBlockStack[exceptionStackPointer] = nullptr; + finallyIndexStack[exceptionStackPointer] = -1; + exceptionStackPointer++; + + InterpBasicBlock *lastBodyBB = nullptr; + InterpBasicBlock *lastFuncletBB = nullptr; while (ip < codeEnd) { int32_t insOffset = (int32_t)(ip - codeStart); @@ -1173,6 +1346,79 @@ bool InterpCompiler::CreateBasicBlocks(CORINFO_METHOD_INFO* methodInfo) OPCODE_FORMAT opArgs = g_CEEOpArgs[opcode]; int32_t target; + { + InterpBasicBlock *bb = m_ppOffsetToBB [insOffset]; + // + // Funclet type change can happen only when we switch basic blocks + // + if (bb != nullptr) + { + // TODO: Assert stack pointer inside range + while (insOffset == exceptionExitOffsetStack[exceptionStackPointer - 1]) + { + exceptionBlockStack[exceptionStackPointer - 1]->funclet_exit_block = lastFuncletBB; + if (exceptionHandlerStack[exceptionStackPointer - 1] != nullptr) + { + exceptionHandlerStack[exceptionStackPointer - 1]->try_exit_block = lastBodyBB; + } + exceptionStackPointer -= 1; + } + if (bb->funclet_type == NOT_A_FUNCLET) + { + // + // At this point, we are entering a basic block that is unclassified earlier + // + if (insOffset < exceptionExitOffsetStack[exceptionStackPointer - 1]) + { + // In case we are not inside a exception handling block, the exit offset should be -1 + // which mean the above expression cannot be true. This means we are inside an exception + // handling block, and we are not leaving it yet. That's why we should continue the labeling + bb->funclet_type = (FuncletType)(exceptionBlockTypeStack[exceptionStackPointer - 1]); + if ((bb->funclet_type == FINALLY) || (bb->funclet_type == TRY)) + { + bb->finally_index = finallyIndexStack[exceptionStackPointer - 1]; + if (bb->funclet_type == TRY) + { + bb->finally_start = exceptionHandlerStack[exceptionStackPointer - 1]->ilOffset; + } + } + } + } + else + { + // At this point, we are entering an entry, so we should push the state onto the stack + for (int i = 0; i < bb->funclet_exit_count; i++) + { + exceptionBlockTypeStack[exceptionStackPointer] = bb->funclet_type + funclet_leave_entry; + if (bb->funclet_type == TRY_ENTRY) + { + exceptionHandlerStack[exceptionStackPointer] = bb->funclet_handlers[i]; + finallyIndexStack[exceptionStackPointer] = bb->funclet_handlers[i]->finally_index; + bb->finally_index = finallyIndexStack[exceptionStackPointer]; + bb->finally_start = exceptionHandlerStack[exceptionStackPointer]->ilOffset; + } + else + { + exceptionHandlerStack[exceptionStackPointer] = nullptr; + finallyIndexStack[exceptionStackPointer] = bb->finally_index; + } + exceptionBlockStack[exceptionStackPointer] = bb; + exceptionExitOffsetStack[exceptionStackPointer] = bb->funclet_exits[i]; + exceptionStackPointer += 1; + } + } + if ((bb->funclet_type == NOT_A_FUNCLET) || (bb->funclet_type == TRY_ENTRY) || (bb->funclet_type == TRY)) + { + lastBodyBB = bb; + } + else + { + lastFuncletBB = bb; + } + } + } + // TODO: Assert stack pointer not inside any scope + switch (opArgs) { case InlineNone: @@ -1328,6 +1574,16 @@ void InterpCompiler::EmitTwoArgBranch(InterpOpcode opcode, int32_t ilOffset, int void InterpCompiler::EmitLoadVar(int32_t var) +{ + InterpCompiler::EmitLoadVarHelper(var, 2); +} + +void InterpCompiler::EmitLoadFrameVar(int32_t var) +{ + InterpCompiler::EmitLoadVarHelper(var, 1); +} + +void InterpCompiler::EmitLoadVarHelper(int32_t var, int mode) { InterpType interpType = m_pVars[var].interpType; int32_t size = m_pVars[var].size; @@ -1338,7 +1594,13 @@ void InterpCompiler::EmitLoadVar(int32_t var) else PushInterpType(interpType, clsHnd); - AddIns(InterpGetMovForType(interpType, true)); + int32_t op = InterpGetMovForType(interpType, true); + if ((mode == 1) && (m_pCBB->funclet_type != TRY_ENTRY) && (m_pCBB->funclet_type != TRY) && (m_pCBB->funclet_type != NOT_A_FUNCLET)) + { + op += (INTOP_LOAD_I4_I1 - INTOP_MOV_I4_I1); + } + AddIns(op); + m_pLastNewIns->SetSVar(var); m_pLastNewIns->SetDVar(m_pStackPointer[-1].var); if (interpType == InterpTypeVT) @@ -1346,6 +1608,16 @@ void InterpCompiler::EmitLoadVar(int32_t var) } void InterpCompiler::EmitStoreVar(int32_t var) +{ + EmitStoreVarHelper(var, 2); +} + +void InterpCompiler::EmitStoreFrameVar(int32_t var) +{ + EmitStoreVarHelper(var, 1); +} + +void InterpCompiler::EmitStoreVarHelper(int32_t var, int mode) { InterpType interpType = m_pVars[var].interpType; CHECK_STACK_RET_VOID(1); @@ -1361,7 +1633,13 @@ void InterpCompiler::EmitStoreVar(int32_t var) EmitConv(m_pStackPointer - 1, NULL, StackTypeR4, INTOP_CONV_R4_R8); m_pStackPointer--; - AddIns(InterpGetMovForType(interpType, false)); + int32_t op = InterpGetMovForType(interpType, false); + if ((mode == 1) && (m_pCBB->funclet_type != TRY_ENTRY) && (m_pCBB->funclet_type != TRY) && (m_pCBB->funclet_type != NOT_A_FUNCLET)) + { + // Access of IL variable inside a funclet need to use STORE + op += (INTOP_STORE_I4_I1 - INTOP_MOV_I4_I1); + } + AddIns(op); m_pLastNewIns->SetSVar(m_pStackPointer[0].var); m_pLastNewIns->SetDVar(var); if (interpType == InterpTypeVT) @@ -1903,11 +2181,38 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) m_pEntryBB->stackHeight = 0; m_pCBB = m_pEntryBB; + // + // We will create two separated linked list of basic blocks, the blocks of funclets + // and the blocks of others. + // + // The reason of doing that is that the runtime requires the funclets to be separated from + // the body so that funclets are never inside try + // + // All the funclet entries have initialized their stack height to be 0, so they are guaranteed + // to be generated in the first pass. + // + // After the first pass, we will join the body part with the funclet part so it becomes one + // linked list of blocks. + // + // Then, we revisit the blocks in this new order and retry the generation of missed blocks earlier + // these missed block is because there are not reachable from the normal control flow so we can't + // initialize their stack yet. + // + + InterpBasicBlock funclet_head = {0}; + InterpBasicBlock* funclet_tail = &funclet_head; + InterpBasicBlock* body_tail = m_pEntryBB; + bool first_pass = true; + bool is_first_exception_funclet_instruction = false; + if (!CreateBasicBlocks(methodInfo)) { m_hasInvalidCode = true; goto exit_bad_code; } +#ifdef DEBUG + m_generatedAllBasicBlocks = true; +#endif m_currentILOffset = -1; @@ -1927,8 +2232,10 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) linkBBlocks = true; needsRetryEmit = false; + retry_emit: emittedBBlocks = false; + while (m_ip < codeEnd) { // Check here for every opcode to avoid code bloat @@ -1959,7 +2266,19 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) // Skip through all emitted bblocks. m_pCBB = pNewBB; while (m_pCBB->pNextBB && m_pCBB->pNextBB->emitState == BBStateEmitted) + { + INTERP_DUMP("Skipping BB%d (IL_%04x):\n", m_pCBB->index, m_pCBB->ilOffset); m_pCBB = m_pCBB->pNextBB; + } + + if (m_pCBB->funclet_type == NOT_A_FUNCLET) + { + body_tail = m_pCBB; + } + else + { + funclet_tail = m_pCBB; + } if (m_pCBB->pNextBB) m_ip = m_pILCode + m_pCBB->pNextBB->ilOffset; @@ -1970,7 +2289,12 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) } else { - assert (pNewBB->emitState == BBStateNotEmitted); + assert(pNewBB->emitState == BBStateNotEmitted); + if ((pNewBB->funclet_type == CATCH_ENTRY) || (pNewBB->funclet_type == FILTER_ENTRY)) + { + assert(first_pass); + is_first_exception_funclet_instruction = true; + } } // We are starting a new basic block. Change cbb and link them together if (linkBBlocks) @@ -2018,8 +2342,24 @@ 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* prev = m_pCBB; + if ((pNewBB->funclet_type == NOT_A_FUNCLET) || (pNewBB->funclet_type == TRY_ENTRY) || (pNewBB->funclet_type == TRY)) + { + prev = body_tail; + body_tail = pNewBB; + } + else + { + prev = funclet_tail; + funclet_tail = pNewBB; + } + + if (first_pass) + { + assert(prev != nullptr); + prev->pNextBB = pNewBB; + } m_pCBB = pNewBB; } @@ -2033,6 +2373,15 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) m_ppOffsetToBB[insOffset] = m_pCBB; + if (is_first_exception_funclet_instruction) + { + AddIns(INTOP_ENTER_FUNCLET); + // TODO, the type should be type of the exception caught, do we care? + PushStackType(StackTypeO, NULL); + m_pLastNewIns->SetDVar(m_pStackPointer[-1].var); + is_first_exception_funclet_instruction = false; + } + #ifdef DEBUG if (m_verbose) { @@ -2138,14 +2487,14 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) break; } case CEE_LDARG_S: - EmitLoadVar(m_ip[1]); + EmitLoadFrameVar(m_ip[1]); m_ip += 2; break; case CEE_LDARG_0: case CEE_LDARG_1: case CEE_LDARG_2: case CEE_LDARG_3: - EmitLoadVar(*m_ip - CEE_LDARG_0); + EmitLoadFrameVar(*m_ip - CEE_LDARG_0); m_ip++; break; case CEE_LDARGA_S: @@ -2153,18 +2502,18 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) m_ip += 2; break; case CEE_STARG_S: - EmitStoreVar(m_ip[1]); + EmitStoreFrameVar(m_ip[1]); m_ip += 2; break; case CEE_LDLOC_S: - EmitLoadVar(numArgs + m_ip[1]); + EmitLoadFrameVar(numArgs + m_ip[1]); m_ip += 2; break; case CEE_LDLOC_0: case CEE_LDLOC_1: case CEE_LDLOC_2: case CEE_LDLOC_3: - EmitLoadVar(numArgs + *m_ip - CEE_LDLOC_0); + EmitLoadFrameVar(numArgs + *m_ip - CEE_LDLOC_0); m_ip++; break; case CEE_LDLOCA_S: @@ -2172,14 +2521,14 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) m_ip += 2; break; case CEE_STLOC_S: - EmitStoreVar(numArgs + m_ip[1]); + EmitStoreFrameVar(numArgs + m_ip[1]); m_ip += 2; break; case CEE_STLOC_0: case CEE_STLOC_1: case CEE_STLOC_2: case CEE_STLOC_3: - EmitStoreVar(numArgs + *m_ip - CEE_STLOC_0); + EmitStoreFrameVar(numArgs + *m_ip - CEE_STLOC_0); m_ip++; break; @@ -3141,7 +3490,7 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) switch (*m_ip + 256) { case CEE_LDARG: - EmitLoadVar(getU2LittleEndian(m_ip + 1)); + EmitLoadFrameVar(getU2LittleEndian(m_ip + 1)); m_ip += 3; break; case CEE_LDARGA: @@ -3149,11 +3498,11 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) m_ip += 3; break; case CEE_STARG: - EmitStoreVar(getU2LittleEndian(m_ip + 1)); + EmitStoreFrameVar(getU2LittleEndian(m_ip + 1)); m_ip += 3; break; case CEE_LDLOC: - EmitLoadVar(numArgs + getU2LittleEndian(m_ip + 1)); + EmitLoadFrameVar(numArgs + getU2LittleEndian(m_ip + 1)); m_ip += 3; break; case CEE_LDLOCA: @@ -3161,7 +3510,7 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) m_ip += 3; break; case CEE_STLOC: - EmitStoreVar(numArgs + getU2LittleEndian(m_ip + 1));\ + EmitStoreFrameVar(numArgs + getU2LittleEndian(m_ip + 1));\ m_ip += 3; break; case CEE_CEQ: @@ -3257,20 +3606,91 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) m_pLastNewIns->SetDVar(m_pStackPointer[-1].var); m_ip++; break; + + case CEE_ENDFILTER: + // At this point, we expect the top of the stack is the decision of the filter + // below it is the exception object, and nothing else + assert((m_pStackPointer - m_pStackBase) == 2); + AddIns(INTOP_LEAVE_FILTER); + m_pStackPointer--; + m_pLastNewIns->SetSVar(m_pStackPointer[0].var); + m_ip++; + break; default: assert(0); break; } break; + case CEE_LEAVE: + case CEE_LEAVE_S: + { + // At this point, we are leaving the block, we should not have anything on the stack + assert(m_pStackPointer == m_pStackBase); + // We should not have a LEAVE instruction in a filter or a finally block + assert((m_pCBB->funclet_type == TRY_ENTRY) || (m_pCBB->funclet_type == TRY) || (m_pCBB->funclet_type == CATCH_ENTRY) || (m_pCBB->funclet_type == CATCH)); + InterpInst* prev = AddIns(INTOP_NOP); + InterpInst* next = AddIns(INTOP_NOP); + int ilOffset = (int)(m_ip - m_pILCode); + int target = (opcode == CEE_LEAVE) ? ilOffset + 5 + *(int32_t*)(m_ip + 1) : (ilOffset + 2 + (int8_t)m_ip[1]); + if (m_pCBB->finally_index != -1) + { + EmitBranch(INTOP_CALL_FINALLY, m_pCBB->finally_start - (int)(m_ip - m_pILCode)); + m_pLastNewIns->SetDVar(m_numILVars + m_pCBB->finally_index); + } + if ((m_pCBB->funclet_type == CATCH_ENTRY) || (m_pCBB->funclet_type == CATCH)) + { + // When catch block execution completes, returns to InterpreterCodeManager::CallFunclet + InterpInst* leave = NewIns(INTOP_LEAVE_CATCH, GetDataLen(INTOP_LEAVE_CATCH)); + prev->pNext = leave; + leave->pNext = next; + next->pPrev = leave; + leave->pPrev = prev; + } + EmitBranch(INTOP_BR, target - ilOffset); + linkBBlocks = false; + } + m_ip += (opcode == CEE_LEAVE) ? 5 : 2; + break; + + case CEE_ENDFINALLY: + { + AddIns(INTOP_RET_FINALLY); + m_ip += 1; + m_pLastNewIns->SetSVar(m_numILVars + m_pCBB->finally_index); + assert((m_pCBB->funclet_type == FINALLY_ENTRY) || (m_pCBB->funclet_type == FINALLY)); + linkBBlocks = false; + } + + break; + case CEE_THROW: + CHECK_STACK(1); AddIns(INTOP_THROW); m_pLastNewIns->SetSVar(m_pStackPointer[-1].var); m_ip += 1; + linkBBlocks = false; 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); + PushInterpType(InterpTypeI, NULL); + m_pLastNewIns->SetDVar(m_pStackPointer[-1].var); + m_ip += 5; + break; + } + default: - assert(0); + assert(!"NYI: Unexpected opcode"); break; } } @@ -3278,6 +3698,15 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) if (m_pCBB->emitState == BBStateEmitting) m_pCBB->emitState = BBStateEmitted; + if (first_pass) + { + first_pass = false; + if (body_tail != nullptr) + { + body_tail->pNextBB = funclet_head.pNextBB; + } + } + // If no bblocks were emitted during the last iteration, there is no point to try again // Some bblocks are just unreachable in the code. if (needsRetryEmit && emittedBBlocks) @@ -3335,6 +3764,8 @@ void InterpCompiler::PrintMethodName(CORINFO_METHOD_HANDLE method) m_compHnd->printMethodName(method, methodName, 100); printf(".%s", methodName); + + // m_interesting = (strcmp(methodName, "TestCatchFinally") == 0); } void InterpCompiler::PrintCode() @@ -3345,12 +3776,41 @@ void InterpCompiler::PrintCode() void InterpCompiler::PrintBBCode(InterpBasicBlock *pBB) { - printf("BB%d:\n", pBB->index); + printf("BB%d (%d, %d):\n", pBB->index, pBB->funclet_type, pBB->finally_index); + switch(pBB->funclet_type) + { + case CATCH_ENTRY: printf("Enter catch\n"); break; + case FILTER_ENTRY: printf("Enter filter\n"); break; + case FINALLY_ENTRY: printf("Enter finally\n"); break; + default: break; + } for (InterpInst *ins = pBB->pFirstIns; ins != NULL; ins = ins->pNext) { PrintIns(ins); printf("\n"); } + + int funclet_type = pBB->funclet_type; + int next_funclet_type = (pBB->pNextBB == nullptr) ? NOT_A_FUNCLET : pBB->pNextBB->funclet_type; + if ((funclet_type != NOT_A_FUNCLET) && ((next_funclet_type < FILTER) || ((next_funclet_type - funclet_type) % funclet_leave_entry != 0))) + { + switch(pBB->funclet_type) + { + case CATCH_ENTRY: + case CATCH: + printf("Exit catch\n"); + break; + case FILTER_ENTRY: + case FILTER: + printf("Exit filter\n"); + break; + case FINALLY: + case FINALLY_ENTRY: + printf("Exit finally\n"); + break; + default: break; + } + } } void InterpCompiler::PrintIns(InterpInst *ins) @@ -3516,9 +3976,73 @@ extern "C" void assertAbort(const char* why, const char* file, unsigned line) return; } + #ifdef _MSC_VER __debugbreak(); #else // _MSC_VER __builtin_trap(); #endif // _MSC_VER } + +void InterpCompiler::BuildEHInfo() +{ + if (m_methodInfo->EHcount > 0) + { + m_compHnd->setEHcount(m_methodInfo->EHcount); + for (unsigned int i = 0; i < m_methodInfo->EHcount; i++) + { + CORINFO_EH_CLAUSE clause; + + m_compHnd->getEHinfo(m_methodInfo->ftn, i, &clause); + + InterpBasicBlock* tryStart = GetBB(clause.TryOffset); + InterpBasicBlock* filterStart = (clause.Flags == CORINFO_EH_CLAUSE_FILTER) ? GetBB(clause.FilterOffset) : nullptr; + InterpBasicBlock* handlerStart = GetBB(clause.HandlerOffset); + + InterpBasicBlock* tryEnd = handlerStart->try_exit_block; + assert(tryEnd != nullptr); + InterpInst* lastTryInst = tryEnd->pLastIns; + while (lastTryInst->flags & INTERP_INST_FLAG_REVERTED) + { + lastTryInst = lastTryInst->pPrev; + } + int tryEndOffset = lastTryInst->nativeOffset + GetInsLength(lastTryInst); + + InterpBasicBlock* handlerEnd = handlerStart->funclet_exit_block; + assert(handlerEnd != nullptr); + InterpInst* lastHandlerInst = handlerEnd->pLastIns; + int handlerEndOffset = lastHandlerInst->nativeOffset + GetInsLength(lastHandlerInst); + + // Note - TryLength and HandlerLength are actually interpreted as the exclusive end of the block by the VM + // I believe the fields are abused (see how CEECodeGenInfo::setEHinfoWorker interpret the field) + + clause.TryOffset = ConvertOffset(tryStart->nativeOffset); + clause.TryLength = ConvertOffset(tryEndOffset); + clause.HandlerOffset = ConvertOffset(handlerStart->nativeOffset); + clause.HandlerLength = ConvertOffset(handlerEndOffset); + if (clause.Flags == CORINFO_EH_CLAUSE_FILTER) + { + clause.FilterOffset = ConvertOffset(filterStart->nativeOffset); + } +#ifdef DEBUG + if (m_verbose) + { + printf("Reporting try [IR_%04x(%d), IR_%04x(%d)) ", tryStart->nativeOffset, clause.TryOffset, tryEndOffset, clause.TryLength); + if (clause.Flags == CORINFO_EH_CLAUSE_FILTER) + { + printf("filter [IR_%04x(%d), IR_%04x(%d)) ", filterStart->nativeOffset, clause.FilterOffset, handlerStart->nativeOffset, clause.HandlerOffset); + } + if (clause.Flags == CORINFO_EH_CLAUSE_FINALLY) + { + printf("finally [IR_%04x(%d), IR_%04x(%d))\n", handlerStart->nativeOffset, clause.HandlerOffset, handlerEndOffset, clause.HandlerLength); + } + else + { + printf("catch [IR_%04x(%d), IR_%04x(%d))\n", handlerStart->nativeOffset, clause.HandlerOffset, handlerEndOffset, clause.HandlerLength); + } + } +#endif + m_compHnd->setEHinfo(i, &clause); + } + } +} diff --git a/src/coreclr/interpreter/compiler.h b/src/coreclr/interpreter/compiler.h index de1884b763f4b0..c5e7240618bfa1 100644 --- a/src/coreclr/interpreter/compiler.h +++ b/src/coreclr/interpreter/compiler.h @@ -79,7 +79,9 @@ enum InterpInstFlags { INTERP_INST_FLAG_CALL = 0x01, // Flag used internally by the var offset allocator - INTERP_INST_FLAG_ACTIVE_CALL = 0x02 + INTERP_INST_FLAG_ACTIVE_CALL = 0x02, + // The instruction is not emitted to the final stream + INTERP_INST_FLAG_REVERTED = 0x04 }; struct InterpInst @@ -137,6 +139,23 @@ enum InterpBBState BBStateEmitted }; +// TODO: Try is not a funclet - rename this +enum FuncletType +{ + NOT_A_FUNCLET, + TRY_ENTRY, + FILTER_ENTRY, + CATCH_ENTRY, + FINALLY_ENTRY, + TRY, + FILTER, + CATCH, + FINALLY, +}; + +// The idea of this constant is that by plus or minus 4, you stay in the same funclet type +const int funclet_leave_entry = 4; + struct InterpBasicBlock { int32_t index; @@ -144,6 +163,16 @@ struct InterpBasicBlock int32_t stackHeight; StackInfo *pStackState; + FuncletType funclet_type; + int32_t funclet_exit_count; + int32_t funclet_exit_current; + int32_t* funclet_exits; + InterpBasicBlock** funclet_handlers; + InterpBasicBlock* funclet_exit_block; + InterpBasicBlock* try_exit_block; + int finally_index; + int finally_start; + InterpInst *pFirstIns, *pLastIns; InterpBasicBlock *pNextBB; @@ -167,6 +196,14 @@ struct InterpBasicBlock inCount = 0; outCount = 0; + funclet_type = NOT_A_FUNCLET; + funclet_exit_count = 0; + funclet_exit_current = 0; + funclet_exits = nullptr; + funclet_handlers = nullptr; + funclet_exit_block = nullptr; + try_exit_block = nullptr; + finally_index = -1; emitState = BBStateNotEmitted; } @@ -263,6 +300,9 @@ class InterpIAllocator; class InterpCompiler { +#ifdef DEBUG + bool m_interesting; +#endif friend class InterpIAllocator; private: @@ -331,6 +371,9 @@ class InterpCompiler // Basic blocks int m_BBCount = 0; InterpBasicBlock** m_ppOffsetToBB; +#ifdef DEBUG + bool m_generatedAllBasicBlocks = false; +#endif ICorDebugInfo::OffsetMapping* m_pILToNativeMap = NULL; int32_t m_ILToNativeMapSize = 0; @@ -382,7 +425,11 @@ class InterpCompiler // Code emit void EmitConv(StackInfo *sp, InterpInst *prevIns, StackType type, InterpOpcode convOp); void EmitLoadVar(int var); + void EmitLoadFrameVar(int var); + void EmitLoadVarHelper(int var, int mode); void EmitStoreVar(int var); + void EmitStoreFrameVar(int var); + void EmitStoreVarHelper(int var, int mode); void EmitBinaryArithmeticOp(int32_t opBase); void EmitUnaryArithmeticOp(int32_t opBase); void EmitShiftOp(int32_t opBase); @@ -417,7 +464,7 @@ class InterpCompiler int32_t ComputeCodeSize(); uint32_t ConvertOffset(int32_t offset); void EmitCode(); - int32_t* EmitCodeIns(int32_t *ip, InterpInst *pIns, TArray *relocs); + int32_t* EmitCodeIns(int32_t *ip, InterpInst *pIns, TArray *relocs, bool enteringFunclet); void PatchRelocations(TArray *relocs); InterpMethod* CreateInterpMethod(); bool CreateBasicBlocks(CORINFO_METHOD_INFO* methodInfo); @@ -437,6 +484,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 0d586a71968cd4..c40d1472288c46 100644 --- a/src/coreclr/interpreter/compileropt.cpp +++ b/src/coreclr/interpreter/compileropt.cpp @@ -254,7 +254,7 @@ void InterpCompiler::AllocOffsets() int32_t *callArgs = pIns->info.pCallInfo->pCallArgs; int32_t var = *callArgs; - while (var != -1) + while (var != CALL_ARGS_TERMINATOR) { if (m_pVars[var].global || m_pVars[var].noCallArgs) { diff --git a/src/coreclr/interpreter/eeinterp.cpp b/src/coreclr/interpreter/eeinterp.cpp index 338b429e78ee65..a6bf960c2dbecf 100644 --- a/src/coreclr/interpreter/eeinterp.cpp +++ b/src/coreclr/interpreter/eeinterp.cpp @@ -108,6 +108,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.def b/src/coreclr/interpreter/intops.def index efdb1c4d4fe414..21864c9e4c99e5 100644 --- a/src/coreclr/interpreter/intops.def +++ b/src/coreclr/interpreter/intops.def @@ -31,6 +31,22 @@ OPDEF(INTOP_MOV_4, "mov.4", 3, 1, 1, InterpOpNoArgs) OPDEF(INTOP_MOV_8, "mov.8", 3, 1, 1, InterpOpNoArgs) OPDEF(INTOP_MOV_VT, "mov.vt", 4, 1, 1, InterpOpInt) +OPDEF(INTOP_LOAD_I4_I1, "load.i4.i1", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_LOAD_I4_U1, "load.i4.u1", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_LOAD_I4_I2, "load.i4.i2", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_LOAD_I4_U2, "load.i4.u2", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_LOAD_4, "load.4", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_LOAD_8, "load.8", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_LOAD_VT, "load.vt", 4, 1, 1, InterpOpInt) + +OPDEF(INTOP_STORE_I4_I1, "store.i4.i1", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_STORE_I4_U1, "store.i4.u1", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_STORE_I4_I2, "store.i4.i2", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_STORE_I4_U2, "store.i4.u2", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_STORE_4, "store.4", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_STORE_8, "store.8", 3, 1, 1, InterpOpNoArgs) +OPDEF(INTOP_STORE_VT, "store.vt", 4, 1, 1, InterpOpInt) + OPDEF(INTOP_LDLOCA, "ldloca", 3, 1, 0, InterpOpInt) OPDEF(INTOP_SWITCH, "switch", 0, 0, 1, InterpOpSwitch) @@ -257,12 +273,19 @@ OPDEF(INTOP_NEWOBJ, "newobj", 5, 1, 1, InterpOpMethodToken) OPDEF(INTOP_NEWOBJ_VT, "newobj.vt", 5, 1, 1, InterpOpMethodToken) 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_ZEROBLK_IMM, "zeroblk.imm", 3, 0, 1, InterpOpInt) OPDEF(INTOP_LOCALLOC, "localloc", 3, 1, 1, InterpOpNoArgs) OPDEF(INTOP_BREAKPOINT, "breakpoint", 1, 0, 0, InterpOpNoArgs) +// Exception OPDEF(INTOP_THROW, "throw", 4, 0, 1, InterpOpInt) +OPDEF(INTOP_ENTER_FUNCLET, "enterfunclet", 2, 1, 0, InterpOpNoArgs) +OPDEF(INTOP_LEAVE_CATCH, "leavecatch", 1, 0, 0, InterpOpNoArgs) +OPDEF(INTOP_LEAVE_FILTER, "leavefilter", 2, 0, 1, InterpOpNoArgs) +OPDEF(INTOP_CALL_FINALLY, "call.finally", 3, 1, 0, InterpOpBranch) +OPDEF(INTOP_RET_FINALLY, "ret.finally", 2, 0, 1, InterpOpNoArgs) OPDEF(INTOP_FAILFAST, "failfast", 1, 0, 0, InterpOpNoArgs) OPDEF(INTOP_GC_COLLECT, "gc.collect", 1, 0, 0, InterpOpNoArgs) diff --git a/src/coreclr/interpreter/intops.h b/src/coreclr/interpreter/intops.h index 103893a9567ed7..cde48d2587192b 100644 --- a/src/coreclr/interpreter/intops.h +++ b/src/coreclr/interpreter/intops.h @@ -62,7 +62,7 @@ static inline bool InterpOpIsEmitNop(int32_t opcode) static inline bool InterpOpIsUncondBranch(int32_t opcode) { - return opcode == INTOP_BR; + return opcode == INTOP_BR || opcode == INTOP_CALL_FINALLY; } static inline bool InterpOpIsCondBranch(int32_t opcode) diff --git a/src/coreclr/vm/codeman.cpp b/src/coreclr/vm/codeman.cpp index bdd91baf3ede35..18203b4078ca89 100644 --- a/src/coreclr/vm/codeman.cpp +++ b/src/coreclr/vm/codeman.cpp @@ -4339,8 +4339,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. 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 a531ff1e42d878..75972399e05579 100644 --- a/src/coreclr/vm/codeman.h +++ b/src/coreclr/vm/codeman.h @@ -2760,12 +2760,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 d6832774c126a9..354ac374c4fd3a 100644 --- a/src/coreclr/vm/eetwain.cpp +++ b/src/coreclr/vm/eetwain.cpp @@ -2144,9 +2144,8 @@ 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; + InterpMethodContextFrame* faultingFrame = (InterpMethodContextFrame*)pRD->SP; + return ExecuteInterpretedCode(nullptr, 0, throwable, pHandler, faultingFrame, isFilter); } void InterpreterCodeManager::ResumeAfterCatch(CONTEXT *pContext, size_t targetSSP, bool fIntercepted) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index c01008f89eee39..419f32275cda7e 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -8,6 +8,7 @@ #include "interpexec.h" typedef void* (*HELPER_FTN_PP)(void*); +typedef void* (*HELPER_FTN_PP_2)(void*, void*); InterpThreadContext::InterpThreadContext() { @@ -30,27 +31,69 @@ static void InterpBreakpoint() #define LOCAL_VAR_ADDR(offset,type) ((type*)(stack + (offset))) #define LOCAL_VAR(offset,type) (*LOCAL_VAR_ADDR(offset, type)) +#define FRAME_VAR_ADDR(offset,type) ((type*)(frame + (offset))) +#define FRAME_VAR(offset,type) (*FRAME_VAR_ADDR(offset, type)) // TODO once we have basic EH support #define NULL_CHECK(o) -void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFrame *pFrame, InterpThreadContext *pThreadContext) +DWORD_PTR ExecuteInterpretedCode(TransitionBlock* pTransitionBlock, TADDR byteCodeAddr, OBJECTREF throwable, void* pHandler, InterpMethodContextFrame* handlerFrame, bool isFilter) { + // Argument registers are in the TransitionBlock + // The stack arguments are right after the pTransitionBlock + 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(pTransitionBlock); + + frames.interpMethodContextFrame.pStack = sp; + frames.interpMethodContextFrame.pRetVal = sp; + frames.interpMethodContextFrame.startIp = handlerFrame ? handlerFrame->startIp : (int32_t*)byteCodeAddr; + + DWORD_PTR funcletResult = (DWORD_PTR)InterpExecMethod(&frames.interpreterFrame, &frames.interpMethodContextFrame, throwable, (const int32_t*)pHandler, handlerFrame ? handlerFrame->pStack : nullptr, threadContext); + + frames.interpreterFrame.Pop(); + return handlerFrame ? funcletResult : (DWORD_PTR)nullptr; +} + +void* InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFrame *pFrame, OBJECTREF throwable, const int32_t *ip, int8_t *frame, InterpThreadContext *pThreadContext) +{ + int non_exceptional_finally_count = 0; #if defined(HOST_AMD64) && defined(HOST_WINDOWS) pInterpreterFrame->SetInterpExecMethodSSP((TADDR)_rdsspq()); #endif // HOST_AMD64 && HOST_WINDOWS - const int32_t *ip; int8_t *stack; InterpMethod *pMethod = *(InterpMethod**)pFrame->startIp; pThreadContext->pStackPointer = pFrame->pStack + pMethod->allocaSize; - ip = pFrame->startIp + sizeof(InterpMethod*) / sizeof(int32_t); + if (ip == nullptr) + { + ip = pFrame->startIp + sizeof(InterpMethod*) / sizeof(int32_t); + } stack = pFrame->pStack; int32_t returnOffset, callArgsOffset, methodSlot; const int32_t *targetIp; MAIN_LOOP: + try { INSTALL_MANAGED_EXCEPTION_DISPATCHER; @@ -64,6 +107,11 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr // keep it for such purposes until we don't need it anymore. pFrame->ip = (int32_t*)ip; +#ifdef DEBUG + // int offset = (int)(ip - (pFrame->startIp + sizeof(InterpMethod*) / sizeof(int32_t))); + // printf("Executing %s IR_%04x\n", ((MethodDesc*)pMethod->methodHnd)->GetName(), offset); +#endif + switch (*ip) { #ifdef DEBUG @@ -126,6 +174,15 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr #define MOV(argtype1,argtype2) \ LOCAL_VAR(ip [1], argtype1) = LOCAL_VAR(ip [2], argtype2); \ ip += 3; + +#define LOAD(argtype1,argtype2) \ + LOCAL_VAR(ip [1], argtype1) = FRAME_VAR(ip [2], argtype2); \ + ip += 3; + +#define STORE(argtype1,argtype2) \ + FRAME_VAR(ip [1], argtype1) = LOCAL_VAR(ip [2], argtype2); \ + ip += 3; + // When loading from a local, we might need to sign / zero extend to 4 bytes // which is our minimum "register" size in interp. They are only needed when // the address of the local is taken and we should try to optimize them out @@ -134,15 +191,35 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr case INTOP_MOV_I4_U1: MOV(int32_t, uint8_t); break; case INTOP_MOV_I4_I2: MOV(int32_t, int16_t); break; case INTOP_MOV_I4_U2: MOV(int32_t, uint16_t); break; - // Normal moves between vars case INTOP_MOV_4: MOV(int32_t, int32_t); break; case INTOP_MOV_8: MOV(int64_t, int64_t); break; - case INTOP_MOV_VT: memmove(stack + ip[1], stack + ip[2], ip[3]); ip += 4; break; + case INTOP_LOAD_I4_I1: LOAD(int32_t, int8_t); break; + case INTOP_LOAD_I4_U1: LOAD(int32_t, uint8_t); break; + case INTOP_LOAD_I4_I2: LOAD(int32_t, int16_t); break; + case INTOP_LOAD_I4_U2: LOAD(int32_t, uint16_t); break; + case INTOP_LOAD_4: LOAD(int32_t, int32_t); break; + case INTOP_LOAD_8: LOAD(int64_t, int64_t); break; + case INTOP_LOAD_VT: + memmove(stack + ip[1], frame + ip[2], ip[3]); + ip += 4; + break; + + case INTOP_STORE_I4_I1: STORE(int32_t, int8_t); break; + case INTOP_STORE_I4_U1: STORE(int32_t, uint8_t); break; + case INTOP_STORE_I4_I2: STORE(int32_t, int16_t); break; + case INTOP_STORE_I4_U2: STORE(int32_t, uint16_t); break; + case INTOP_STORE_4: STORE(int32_t, int32_t); break; + case INTOP_STORE_8: STORE(int64_t, int64_t); break; + case INTOP_STORE_VT: + memmove(frame + ip[1], stack + ip[2], ip[3]); + ip += 4; + break; + case INTOP_CONV_R_UN_I4: LOCAL_VAR(ip[1], double) = (double)LOCAL_VAR(ip[2], uint32_t); ip += 3; @@ -1002,19 +1079,28 @@ 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: @@ -1153,7 +1239,6 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr { size_t len = LOCAL_VAR(ip[2], size_t); void* pMemory = NULL; - if (len > 0) { pMemory = pThreadContext->frameDataAllocator.Alloc(pFrame, len); @@ -1167,7 +1252,6 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr memset(pMemory, 0, len); } } - LOCAL_VAR(ip[1], void*) = pMemory; ip += 3; break; @@ -1184,6 +1268,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr ip++; break; } + case INTOP_THROW: { OBJECTREF throwable; @@ -1200,11 +1285,83 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr UNREACHABLE(); break; } + + case INTOP_ENTER_FUNCLET: + // This opcode loads the exception object coming from the caller to a variable. + LOCAL_VAR(ip[1], OBJECTREF) = throwable; + ip += 2; + break; + + case INTOP_LEAVE_CATCH: + // This opcode temporarily return the control back to InterpreterCodeManager::CallFunclet + // The VM will resume execution on the address we return here. + return (void*)(ip + 1); + + case INTOP_LEAVE_FILTER: + // This opcode temporarily return the control back to InterpreterCodeManager::CallFunclet + // The VM will either continue the search for another exception handler or execute the handler. + // depending on the result we return here. + return (void*)(int64_t)((LOCAL_VAR(ip[1], int32_t) != 0) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH); + + case INTOP_CALL_FINALLY: + { + non_exceptional_finally_count += 1; + LOCAL_VAR(ip[1], int8_t*) = frame; + frame = stack; + + // Save current execution state for when we return from finally + pFrame->ip = ip + 3; + const int32_t* targetIp = ip + ip[2]; + + // Allocate child frame. + { + InterpMethodContextFrame *pChildFrame = pFrame->pNext; + if (!pChildFrame) + { + pChildFrame = (InterpMethodContextFrame*)alloca(sizeof(InterpMethodContextFrame)); + pChildFrame->pNext = NULL; + pFrame->pNext = pChildFrame; + } + + pChildFrame->ReInit(pFrame, pFrame->startIp, nullptr, stack + pMethod->allocaSize); + pFrame = pChildFrame; + } + assert (((size_t)pFrame->pStack % INTERP_STACK_ALIGNMENT) == 0); + + stack = pFrame->pStack; + ip = targetIp; + pThreadContext->pStackPointer = stack + pMethod->allocaSize; + } + break; + + case INTOP_RET_FINALLY: + if (non_exceptional_finally_count > 0) + { + // This opcode returns from the finally block + non_exceptional_finally_count -= 1; + frame = FRAME_VAR(ip[1], int8_t*); + + pFrame->ip = NULL; + pFrame = pFrame->pParent; + ip = pFrame->ip; + stack = pFrame->pStack; + pFrame->ip = NULL; + + pThreadContext->pStackPointer = pFrame->pStack + pMethod->allocaSize; + } + else + { + // The finally is called by CallFinallyFunclet, this is time to return the control back + // to the VM + return nullptr; + } + break; + case INTOP_FAILFAST: assert(0); break; default: - assert(0); + assert(!"NYI - Unsupported interp bytecode"); break; } } @@ -1213,6 +1370,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr } catch (const ResumeAfterCatchException& ex) { + GCX_COOP_NO_DTOR(); TADDR resumeSP; TADDR resumeIP; ex.GetResumeContext(&resumeSP, &resumeIP); @@ -1256,6 +1414,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr } pThreadContext->pStackPointer = pFrame->pStack; + return nullptr; } #endif // FEATURE_INTERPRETER diff --git a/src/coreclr/vm/interpexec.h b/src/coreclr/vm/interpexec.h index 7306f9796c137c..48f0fa0bf31519 100644 --- a/src/coreclr/vm/interpexec.h +++ b/src/coreclr/vm/interpexec.h @@ -61,6 +61,18 @@ struct InterpThreadContext ~InterpThreadContext(); }; -void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFrame *pFrame, InterpThreadContext *pThreadContext); +// +// This overloaded function serves two purposes: +// +// It could be used to call a function, where throwable, pHandler and handlerFrame is null, or +// It could be used to call a funclet, where throwable should be the exception object, ip is the bytecode offset of the handler, +// +// In the case of calling a function, it will return nullptr +// In the case of catch, it will return the bytecode address where the execution should resume, or +// In the case of filter, it will return the decision to either execute on the current handler or continue searching for another handler. +// +DWORD_PTR ExecuteInterpretedCode(TransitionBlock* pTransitionBlock, TADDR byteCodeAddr, OBJECTREF throwable, void* pHandler, InterpMethodContextFrame* handlerFrame, bool isFilter); + +void* InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFrame *pFrame, OBJECTREF throwable, const int32_t* ip, int8_t *frame, InterpThreadContext *pThreadContext); #endif diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 57b0af8fdbb755..a603631976b50a 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1994,37 +1994,8 @@ extern "C" PCODE STDCALL PreStubWorker(TransitionBlock* pTransitionBlock, Method #ifdef FEATURE_INTERPRETER extern "C" void STDCALL ExecuteInterpretedMethod(TransitionBlock* pTransitionBlock, TADDR byteCodeAddr) { - // Argument registers are in the TransitionBlock - // The stack arguments are right after the pTransitionBlock - 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(pTransitionBlock); - - frames.interpMethodContextFrame.startIp = (int32_t*)byteCodeAddr; - frames.interpMethodContextFrame.pStack = sp; - frames.interpMethodContextFrame.pRetVal = sp; - - InterpExecMethod(&frames.interpreterFrame, &frames.interpMethodContextFrame, threadContext); - - frames.interpreterFrame.Pop(); + // ignore the return here + ExecuteInterpretedCode(pTransitionBlock, byteCodeAddr, nullptr, nullptr, nullptr, false); } #endif // FEATURE_INTERPRETER diff --git a/src/tests/JIT/interpreter/Interpreter.cs b/src/tests/JIT/interpreter/Interpreter.cs index daec2582cc8fe5..99a05482db3a2b 100644 --- a/src/tests/JIT/interpreter/Interpreter.cs +++ b/src/tests/JIT/interpreter/Interpreter.cs @@ -108,6 +108,7 @@ public static void RunInterpreterTests() // For stackwalking validation System.GC.Collect(); + TestExceptionHandling(); } public static int Mul4(int a, int b, int c, int d) @@ -115,6 +116,477 @@ public static int Mul4(int a, int b, int c, int d) return a * b * c * d; } + public static void TestExceptionHandling() + { + TestTryFinally(); + TestCatchCurrent(); + TestFilterCatchCurrent(); + TestFilterFailedCatchCurrent(); + TestCatchNested(); + TestFilterCatchNested(); + TestFilterFailedCatchNested(); + TestFinallyBeforeCatch(); + TestModifyAlias(); + TestThrowWithinCatch(); + TestThrowWithinFinally(); + + // Known failure for now + // These test cases should pass on debug build + // On optimized build - the implementation right now cannot handle an optimization done by Roslyn yet + // +#if NO_LEAVE_OPTIMIZATION + TestCatchFinally(); + TestFilterCatchFinallyCurrent(); + TestFilterFailedCatchFinallyCurrent(); + TestCatchFinallyNested(); + TestFilterCatchFinallyNested(); + TestFilterFailedCatchFinallyNested(); + TestNestedTryFinally(); +#endif + } + + 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 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 long SumN(int n) { if (n == 1) diff --git a/src/tests/JIT/interpreter/Interpreter.csproj b/src/tests/JIT/interpreter/Interpreter.csproj index 98b0af809b2826..bedc58f6527d6f 100644 --- a/src/tests/JIT/interpreter/Interpreter.csproj +++ b/src/tests/JIT/interpreter/Interpreter.csproj @@ -5,6 +5,10 @@ false BuildOnly + + + NO_LEAVE_OPTIMIZATION +