diff --git a/src/coreclr/inc/cordebuginfo.h b/src/coreclr/inc/cordebuginfo.h index 4de783aae9845..e2683d36ddcd7 100644 --- a/src/coreclr/inc/cordebuginfo.h +++ b/src/coreclr/inc/cordebuginfo.h @@ -52,7 +52,7 @@ class ICorDebugInfo struct OffsetMapping { uint32_t nativeOffset; - uint32_t ilOffset; + uint32_t ilOffset; // IL offset or one of the special values in MappingTypes SourceTypes source; // The debugger needs this so that // we don't put Edit and Continue breakpoints where // the stack isn't empty. We can put regular breakpoints diff --git a/src/coreclr/jit/CMakeLists.txt b/src/coreclr/jit/CMakeLists.txt index e468b3a59c7c7..e3dbfc5993b3d 100644 --- a/src/coreclr/jit/CMakeLists.txt +++ b/src/coreclr/jit/CMakeLists.txt @@ -80,6 +80,7 @@ set( JIT_SOURCES codegenlinear.cpp compiler.cpp copyprop.cpp + debuginfo.cpp disasm.cpp earlyprop.cpp ee_il_dll.cpp @@ -178,6 +179,7 @@ if (CLR_CMAKE_TARGET_WIN32) compmemkind.h compphases.h dataflow.h + debuginfo.h decomposelongs.h disasm.h emit.h diff --git a/src/coreclr/jit/codegen.h b/src/coreclr/jit/codegen.h index 0fa215587eba2..74420b2883260 100644 --- a/src/coreclr/jit/codegen.h +++ b/src/coreclr/jit/codegen.h @@ -434,10 +434,10 @@ class CodeGen final : public CodeGenInterface CORINFO_METHOD_HANDLE methHnd, INDEBUG_LDISASM_COMMA(CORINFO_SIG_INFO* sigInfo) void* addr - X86_ARG(int argSize), + X86_ARG(int argSize), emitAttr retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(emitAttr secondRetSize), - IL_OFFSETX ilOffset, + const DebugInfo& di, regNumber base, bool isJump); // clang-format on @@ -447,10 +447,10 @@ class CodeGen final : public CodeGenInterface CORINFO_METHOD_HANDLE methHnd, INDEBUG_LDISASM_COMMA(CORINFO_SIG_INFO* sigInfo) GenTreeIndir* indir - X86_ARG(int argSize), + X86_ARG(int argSize), emitAttr retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(emitAttr secondRetSize), - IL_OFFSETX ilOffset, + const DebugInfo& di, bool isJump); // clang-format on @@ -552,15 +552,16 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */ #ifdef DEBUG - void genIPmappingDisp(unsigned mappingNum, Compiler::IPmappingDsc* ipMapping); + void genIPmappingDisp(unsigned mappingNum, IPmappingDsc* ipMapping); void genIPmappingListDisp(); #endif // DEBUG - void genIPmappingAdd(IL_OFFSETX offset, bool isLabel); - void genIPmappingAddToFront(IL_OFFSETX offset); + IPmappingDsc* genCreateIPMapping(IPmappingDscKind kind, const DebugInfo& di, bool isLabel); + void genIPmappingAdd(IPmappingDscKind kind, const DebugInfo& di, bool isLabel); + void genIPmappingAddToFront(IPmappingDscKind kind, const DebugInfo& di, bool isLabel); void genIPmappingGen(); - void genEnsureCodeEmitted(IL_OFFSETX offsx); + void genEnsureCodeEmitted(const DebugInfo& di); //------------------------------------------------------------------------- // scope info for the variables diff --git a/src/coreclr/jit/codegenarm.cpp b/src/coreclr/jit/codegenarm.cpp index 89c3d4f88328e..93527f0b1845b 100644 --- a/src/coreclr/jit/codegenarm.cpp +++ b/src/coreclr/jit/codegenarm.cpp @@ -1632,8 +1632,7 @@ void CodeGen::genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize, GetEmitter()->emitIns_Call(emitter::EC_INDIR_R, compiler->eeFindHelper(helper), INDEBUG_LDISASM_COMMA(nullptr) NULL, // addr argSize, retSize, gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, - gcInfo.gcRegByrefSetCur, - BAD_IL_OFFSET, // ilOffset + gcInfo.gcRegByrefSetCur, DebugInfo(), callTargetReg, // ireg REG_NA, 0, 0, // xreg, xmul, disp false // isJump @@ -1643,7 +1642,7 @@ void CodeGen::genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize, { GetEmitter()->emitIns_Call(emitter::EC_FUNC_TOKEN, compiler->eeFindHelper(helper), INDEBUG_LDISASM_COMMA(nullptr) addr, argSize, retSize, gcInfo.gcVarPtrSetCur, - gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, BAD_IL_OFFSET, REG_NA, REG_NA, 0, + gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, DebugInfo(), REG_NA, REG_NA, 0, 0, /* ilOffset, ireg, xreg, xmul, disp */ false /* isJump */ ); diff --git a/src/coreclr/jit/codegenarm64.cpp b/src/coreclr/jit/codegenarm64.cpp index aa609801ac645..317e059c683de 100644 --- a/src/coreclr/jit/codegenarm64.cpp +++ b/src/coreclr/jit/codegenarm64.cpp @@ -3846,10 +3846,9 @@ void CodeGen::genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize, GetEmitter()->emitIns_Call(callType, compiler->eeFindHelper(helper), INDEBUG_LDISASM_COMMA(nullptr) addr, argSize, retSize, EA_UNKNOWN, gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, - gcInfo.gcRegByrefSetCur, BAD_IL_OFFSET, /* IL offset */ - callTarget, /* ireg */ - REG_NA, 0, 0, /* xreg, xmul, disp */ - false /* isJump */ + gcInfo.gcRegByrefSetCur, DebugInfo(), callTarget, /* ireg */ + REG_NA, 0, 0, /* xreg, xmul, disp */ + false /* isJump */ ); regMaskTP killMask = compiler->compHelperCallKillSet((CorInfoHelpFunc)helper); diff --git a/src/coreclr/jit/codegenarmarch.cpp b/src/coreclr/jit/codegenarmarch.cpp index 8ae5ebd143725..9faf0c603370b 100644 --- a/src/coreclr/jit/codegenarmarch.cpp +++ b/src/coreclr/jit/codegenarmarch.cpp @@ -2463,16 +2463,14 @@ void CodeGen::genCallInstruction(GenTreeCall* call) } } - // We need to propagate the IL offset information to the call instruction, so we can emit + DebugInfo di; + // We need to propagate the debug information to the call instruction, so we can emit // an IL to native mapping record for the call, to support managed return value debugging. // We don't want tail call helper calls that were converted from normal calls to get a record, // so we skip this hash table lookup logic in that case. - - IL_OFFSETX ilOffset = BAD_IL_OFFSET; - - if (compiler->opts.compDbgInfo && compiler->genCallSite2ILOffsetMap != nullptr && !call->IsTailCall()) + if (compiler->opts.compDbgInfo && compiler->genCallSite2DebugInfoMap != nullptr && !call->IsTailCall()) { - (void)compiler->genCallSite2ILOffsetMap->Lookup(call, &ilOffset); + (void)compiler->genCallSite2DebugInfoMap->Lookup(call, &di); } CORINFO_SIG_INFO* sigInfo = nullptr; @@ -2512,7 +2510,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call) nullptr, // addr retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), - ilOffset, + di, target->GetRegNum(), call->IsFastTailCall()); // clang-format on @@ -2552,7 +2550,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call) nullptr, // addr retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), - ilOffset, + di, targetAddrReg, call->IsFastTailCall()); // clang-format on @@ -2600,7 +2598,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call) INDEBUG_LDISASM_COMMA(sigInfo) NULL, retSize, - ilOffset, + di, tmpReg, call->IsFastTailCall()); // clang-format on @@ -2615,7 +2613,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call) addr, retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), - ilOffset, + di, REG_NA, call->IsFastTailCall()); // clang-format on diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index 8c788286f292d..a33b4b94a5994 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -123,9 +123,9 @@ CodeGen::CodeGen(Compiler* theCompiler) : CodeGenInterface(theCompiler) #endif // TARGET_AMD64 // Initialize the IP-mapping logic. - compiler->genIPmappingList = nullptr; - compiler->genIPmappingLast = nullptr; - compiler->genCallSite2ILOffsetMap = nullptr; + compiler->genIPmappingList = nullptr; + compiler->genIPmappingLast = nullptr; + compiler->genCallSite2DebugInfoMap = nullptr; /* Assume that we not fully interruptible */ @@ -1798,7 +1798,7 @@ void CodeGen::genExitCode(BasicBlock* block) that this is ok */ // For non-optimized debuggable code, there is only one epilog. - genIPmappingAdd((IL_OFFSETX)ICorDebugInfo::EPILOG, true); + genIPmappingAdd(IPmappingDscKind::Epilog, DebugInfo(), true); bool jmpEpilog = ((block->bbFlags & BBF_HAS_JMP) != 0); if (compiler->getNeedsGSSecurityCookie()) @@ -6978,7 +6978,7 @@ void CodeGen::genFnProlog() // Do this so we can put the prolog instruction group ahead of // other instruction groups - genIPmappingAddToFront((IL_OFFSETX)ICorDebugInfo::PROLOG); + genIPmappingAddToFront(IPmappingDscKind::Prolog, DebugInfo(), true); #ifdef DEBUG if (compiler->opts.dspCode) @@ -8068,7 +8068,7 @@ void CodeGen::genFnEpilog(BasicBlock* block) gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, - BAD_IL_OFFSET, // IL offset + DebugInfo(), indCallReg, // ireg REG_NA, // xreg 0, // xmul @@ -8469,7 +8469,8 @@ void CodeGen::genFnEpilog(BasicBlock* block) gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, - BAD_IL_OFFSET, indCallReg, REG_NA, 0, 0, /* iloffset, ireg, xreg, xmul, disp */ + DebugInfo(), + indCallReg, REG_NA, 0, 0, /* ireg, xreg, xmul, disp */ true /* isJump */ ); // clang-format on @@ -10401,32 +10402,38 @@ const char* CodeGen::siStackVarName(size_t offs, size_t size, unsigned reg, unsi * Display a IPmappingDsc. Pass -1 as mappingNum to not display a mapping number. */ -void CodeGen::genIPmappingDisp(unsigned mappingNum, Compiler::IPmappingDsc* ipMapping) +void CodeGen::genIPmappingDisp(unsigned mappingNum, IPmappingDsc* ipMapping) { if (mappingNum != unsigned(-1)) { printf("%d: ", mappingNum); } - IL_OFFSETX offsx = ipMapping->ipmdILoffsx; - - if (offsx == BAD_IL_OFFSET) - { - printf("???"); - } - else + switch (ipMapping->ipmdKind) { - Compiler::eeDispILOffs(jitGetILoffsAny(offsx)); + case IPmappingDscKind::Prolog: + printf("PROLOG"); + break; + case IPmappingDscKind::Epilog: + printf("EPILOG"); + break; + case IPmappingDscKind::NoMapping: + printf("NO_MAP"); + break; + case IPmappingDscKind::Normal: + const ILLocation& loc = ipMapping->ipmdLoc; + Compiler::eeDispILOffs(loc.GetOffset()); + if (loc.IsStackEmpty()) + { + printf(" STACK_EMPTY"); + } - if (jitIsStackEmpty(offsx)) - { - printf(" STACK_EMPTY"); - } + if (loc.IsCall()) + { + printf(" CALL_INSTRUCTION"); + } - if (jitIsCallInstruction(offsx)) - { - printf(" CALL_INSTRUCTION"); - } + break; } printf(" "); @@ -10444,8 +10451,8 @@ void CodeGen::genIPmappingDisp(unsigned mappingNum, Compiler::IPmappingDsc* ipMa void CodeGen::genIPmappingListDisp() { - unsigned mappingNum = 0; - Compiler::IPmappingDsc* ipMapping; + unsigned mappingNum = 0; + IPmappingDsc* ipMapping; for (ipMapping = compiler->genIPmappingList; ipMapping != nullptr; ipMapping = ipMapping->ipmdNext) { @@ -10463,34 +10470,35 @@ void CodeGen::genIPmappingListDisp() * Record the instr offset as being at the current code gen position. */ -void CodeGen::genIPmappingAdd(IL_OFFSETX offsx, bool isLabel) +void CodeGen::genIPmappingAdd(IPmappingDscKind kind, const DebugInfo& di, bool isLabel) { if (!compiler->opts.compDbgInfo) { return; } - assert(offsx != BAD_IL_OFFSET); + assert((kind == IPmappingDscKind::Normal) == di.IsValid()); - switch ((int)offsx) // Need the cast since offs is unsigned and the case statements are comparing to signed. + switch (kind) { - case ICorDebugInfo::PROLOG: - case ICorDebugInfo::EPILOG: + case IPmappingDscKind::Prolog: + case IPmappingDscKind::Epilog: break; default: - if (offsx != (IL_OFFSETX)ICorDebugInfo::NO_MAPPING) + if (kind == IPmappingDscKind::Normal) { - noway_assert(jitGetILoffs(offsx) <= compiler->info.compILCodeSize); + noway_assert(di.GetLocation().GetOffset() <= compiler->info.compILCodeSize); } - // Ignore this one if it's the same IL offset as the last one we saw. + // Ignore this one if it's the same IL location as the last one we saw. // Note that we'll let through two identical IL offsets if the flag bits // differ, or two identical "special" mappings (e.g., PROLOG). - if ((compiler->genIPmappingLast != nullptr) && (offsx == compiler->genIPmappingLast->ipmdILoffsx)) + if ((compiler->genIPmappingLast != nullptr) && (kind == compiler->genIPmappingLast->ipmdKind) && + (di.GetLocation() == compiler->genIPmappingLast->ipmdLoc)) { - JITDUMP("genIPmappingAdd: ignoring duplicate IL offset 0x%x\n", offsx); + JITDUMP("genIPmappingAdd: ignoring duplicate IL offset 0x%x\n", di.GetLocation().GetOffset()); return; } break; @@ -10498,12 +10506,15 @@ void CodeGen::genIPmappingAdd(IL_OFFSETX offsx, bool isLabel) /* Create a mapping entry and append it to the list */ - Compiler::IPmappingDsc* addMapping = compiler->getAllocator(CMK_DebugInfo).allocate(1); + IPmappingDsc* addMapping = compiler->getAllocator(CMK_DebugInfo).allocate(1); addMapping->ipmdNativeLoc.CaptureLocation(GetEmitter()); - addMapping->ipmdILoffsx = offsx; + addMapping->ipmdKind = kind; + addMapping->ipmdLoc = di.GetLocation(); addMapping->ipmdIsLabel = isLabel; addMapping->ipmdNext = nullptr; + assert((kind == IPmappingDscKind::Normal) == addMapping->ipmdLoc.IsValid()); + if (compiler->genIPmappingList != nullptr) { assert(compiler->genIPmappingLast != nullptr); @@ -10531,37 +10542,24 @@ void CodeGen::genIPmappingAdd(IL_OFFSETX offsx, bool isLabel) * * Prepend an IPmappingDsc struct to the list that we're maintaining * for the debugger. - * Record the instr offset as being at the current code gen position. */ -void CodeGen::genIPmappingAddToFront(IL_OFFSETX offsx) +void CodeGen::genIPmappingAddToFront(IPmappingDscKind kind, const DebugInfo& di, bool isLabel) { if (!compiler->opts.compDbgInfo) { return; } - assert(offsx != BAD_IL_OFFSET); - assert(compiler->compGeneratingProlog); // We only ever do this during prolog generation. - - switch ((int)offsx) // Need the cast since offs is unsigned and the case statements are comparing to signed. - { - case ICorDebugInfo::NO_MAPPING: - case ICorDebugInfo::PROLOG: - case ICorDebugInfo::EPILOG: - break; - - default: - noway_assert(jitGetILoffs(offsx) <= compiler->info.compILCodeSize); - break; - } + noway_assert((kind != IPmappingDscKind::Normal) || + (di.IsValid() && (di.GetLocation().GetOffset() <= compiler->info.compILCodeSize))); /* Create a mapping entry and prepend it to the list */ - Compiler::IPmappingDsc* addMapping = compiler->getAllocator(CMK_DebugInfo).allocate(1); + IPmappingDsc* addMapping = compiler->getAllocator(CMK_DebugInfo).allocate(1); addMapping->ipmdNativeLoc.CaptureLocation(GetEmitter()); - addMapping->ipmdILoffsx = offsx; - addMapping->ipmdIsLabel = true; - addMapping->ipmdNext = nullptr; + addMapping->ipmdKind = kind; + addMapping->ipmdLoc = di.GetLocation(); + addMapping->ipmdIsLabel = isLabel; addMapping->ipmdNext = compiler->genIPmappingList; compiler->genIPmappingList = addMapping; @@ -10582,130 +10580,14 @@ void CodeGen::genIPmappingAddToFront(IL_OFFSETX offsx) /*****************************************************************************/ -C_ASSERT(IL_OFFSETX(ICorDebugInfo::NO_MAPPING) != IL_OFFSETX(BAD_IL_OFFSET)); -C_ASSERT(IL_OFFSETX(ICorDebugInfo::PROLOG) != IL_OFFSETX(BAD_IL_OFFSET)); -C_ASSERT(IL_OFFSETX(ICorDebugInfo::EPILOG) != IL_OFFSETX(BAD_IL_OFFSET)); - -C_ASSERT(IL_OFFSETX(BAD_IL_OFFSET) > MAX_IL_OFFSET); -C_ASSERT(IL_OFFSETX(ICorDebugInfo::NO_MAPPING) > MAX_IL_OFFSET); -C_ASSERT(IL_OFFSETX(ICorDebugInfo::PROLOG) > MAX_IL_OFFSET); -C_ASSERT(IL_OFFSETX(ICorDebugInfo::EPILOG) > MAX_IL_OFFSET); - -//------------------------------------------------------------------------ -// jitGetILoffs: Returns the IL offset portion of the IL_OFFSETX type. -// Asserts if any ICorDebugInfo distinguished value (like ICorDebugInfo::NO_MAPPING) -// is seen; these are unexpected here. Also asserts if passed BAD_IL_OFFSET. -// -// Arguments: -// offsx - the IL_OFFSETX value with the IL offset to extract. -// -// Return Value: -// The IL offset. - -IL_OFFSET jitGetILoffs(IL_OFFSETX offsx) -{ - assert(offsx != BAD_IL_OFFSET); - - switch ((int)offsx) // Need the cast since offs is unsigned and the case statements are comparing to signed. - { - case ICorDebugInfo::NO_MAPPING: - case ICorDebugInfo::PROLOG: - case ICorDebugInfo::EPILOG: - unreached(); - - default: - return IL_OFFSET(offsx & ~IL_OFFSETX_BITS); - } -} - -//------------------------------------------------------------------------ -// jitGetILoffsAny: Similar to jitGetILoffs(), but passes through ICorDebugInfo -// distinguished values. Asserts if passed BAD_IL_OFFSET. -// -// Arguments: -// offsx - the IL_OFFSETX value with the IL offset to extract. -// -// Return Value: -// The IL offset. - -IL_OFFSET jitGetILoffsAny(IL_OFFSETX offsx) -{ - assert(offsx != BAD_IL_OFFSET); - - switch ((int)offsx) // Need the cast since offs is unsigned and the case statements are comparing to signed. - { - case ICorDebugInfo::NO_MAPPING: - case ICorDebugInfo::PROLOG: - case ICorDebugInfo::EPILOG: - return IL_OFFSET(offsx); - - default: - return IL_OFFSET(offsx & ~IL_OFFSETX_BITS); - } -} - -//------------------------------------------------------------------------ -// jitIsStackEmpty: Does the IL offset have the stack empty bit set? -// Asserts if passed BAD_IL_OFFSET. -// -// Arguments: -// offsx - the IL_OFFSETX value to check -// -// Return Value: -// 'true' if the stack empty bit is set; 'false' otherwise. - -bool jitIsStackEmpty(IL_OFFSETX offsx) -{ - assert(offsx != BAD_IL_OFFSET); - - switch ((int)offsx) // Need the cast since offs is unsigned and the case statements are comparing to signed. - { - case ICorDebugInfo::NO_MAPPING: - case ICorDebugInfo::PROLOG: - case ICorDebugInfo::EPILOG: - return true; - - default: - return (offsx & IL_OFFSETX_STKBIT) == 0; - } -} - -//------------------------------------------------------------------------ -// jitIsCallInstruction: Does the IL offset have the call instruction bit set? -// Asserts if passed BAD_IL_OFFSET. -// -// Arguments: -// offsx - the IL_OFFSETX value to check -// -// Return Value: -// 'true' if the call instruction bit is set; 'false' otherwise. - -bool jitIsCallInstruction(IL_OFFSETX offsx) -{ - assert(offsx != BAD_IL_OFFSET); - - switch ((int)offsx) // Need the cast since offs is unsigned and the case statements are comparing to signed. - { - case ICorDebugInfo::NO_MAPPING: - case ICorDebugInfo::PROLOG: - case ICorDebugInfo::EPILOG: - return false; - - default: - return (offsx & IL_OFFSETX_CALLINSTRUCTIONBIT) != 0; - } -} - -/*****************************************************************************/ - -void CodeGen::genEnsureCodeEmitted(IL_OFFSETX offsx) +void CodeGen::genEnsureCodeEmitted(const DebugInfo& di) { if (!compiler->opts.compDbgCode) { return; } - if (offsx == BAD_IL_OFFSET) + if (!di.IsValid()) { return; } @@ -10717,7 +10599,7 @@ void CodeGen::genEnsureCodeEmitted(IL_OFFSETX offsx) return; } - if (compiler->genIPmappingLast->ipmdILoffsx != offsx) + if (compiler->genIPmappingLast->ipmdLoc != di.GetLocation()) { return; } @@ -10756,10 +10638,10 @@ void CodeGen::genIPmappingGen() return; } - Compiler::IPmappingDsc* tmpMapping; - Compiler::IPmappingDsc* prevMapping; - unsigned mappingCnt; - UNATIVE_OFFSET lastNativeOfs; + IPmappingDsc* tmpMapping; + IPmappingDsc* prevMapping; + unsigned mappingCnt; + UNATIVE_OFFSET lastNativeOfs; /* First count the number of distinct mapping records */ @@ -10769,12 +10651,10 @@ void CodeGen::genIPmappingGen() for (prevMapping = nullptr, tmpMapping = compiler->genIPmappingList; tmpMapping != nullptr; tmpMapping = tmpMapping->ipmdNext) { - IL_OFFSETX srcIP = tmpMapping->ipmdILoffsx; - // Managed RetVal - since new sequence points are emitted to identify IL calls, // make sure that those are not filtered and do not interfere with filtering of // other sequence points. - if (jitIsCallInstruction(srcIP)) + if (tmpMapping->ipmdKind == IPmappingDscKind::Normal && tmpMapping->ipmdLoc.IsCall()) { mappingCnt++; continue; @@ -10797,19 +10677,20 @@ void CodeGen::genIPmappingGen() */ PREFIX_ASSUME(prevMapping != nullptr); // We would exit before if this was true - if (prevMapping->ipmdILoffsx == (IL_OFFSETX)ICorDebugInfo::NO_MAPPING) + if (prevMapping->ipmdKind == IPmappingDscKind::NoMapping) { // If the previous entry was NO_MAPPING, ignore it prevMapping->ipmdNativeLoc.Init(); prevMapping = tmpMapping; } - else if (srcIP == (IL_OFFSETX)ICorDebugInfo::NO_MAPPING) + else if (tmpMapping->ipmdKind == IPmappingDscKind::NoMapping) { // If the current entry is NO_MAPPING, ignore it // Leave prevMapping unchanged as tmpMapping is no longer valid tmpMapping->ipmdNativeLoc.Init(); } - else if (srcIP == (IL_OFFSETX)ICorDebugInfo::EPILOG || srcIP == 0) + else if ((tmpMapping->ipmdKind == IPmappingDscKind::Epilog) || + (tmpMapping->ipmdKind == IPmappingDscKind::Normal && tmpMapping->ipmdLoc.GetOffset() == 0)) { // counting for special cases: see below mappingCnt++; @@ -10856,18 +10737,18 @@ void CodeGen::genIPmappingGen() } UNATIVE_OFFSET nextNativeOfs = tmpMapping->ipmdNativeLoc.CodeOffset(GetEmitter()); - IL_OFFSETX srcIP = tmpMapping->ipmdILoffsx; - if (jitIsCallInstruction(srcIP)) + if (tmpMapping->ipmdKind == IPmappingDscKind::Normal && tmpMapping->ipmdLoc.IsCall()) { - compiler->eeSetLIinfo(mappingCnt++, nextNativeOfs, jitGetILoffs(srcIP), jitIsStackEmpty(srcIP), true); + compiler->eeSetLIinfo(mappingCnt++, nextNativeOfs, IPmappingDscKind::Normal, tmpMapping->ipmdLoc); } else if (nextNativeOfs != lastNativeOfs) { - compiler->eeSetLIinfo(mappingCnt++, nextNativeOfs, jitGetILoffsAny(srcIP), jitIsStackEmpty(srcIP), false); + compiler->eeSetLIinfo(mappingCnt++, nextNativeOfs, tmpMapping->ipmdKind, tmpMapping->ipmdLoc); lastNativeOfs = nextNativeOfs; } - else if (srcIP == (IL_OFFSETX)ICorDebugInfo::EPILOG || srcIP == 0) + else if (tmpMapping->ipmdKind == IPmappingDscKind::Epilog || + (tmpMapping->ipmdKind == IPmappingDscKind::Normal && tmpMapping->ipmdLoc.GetOffset() == 0)) { // For the special case of an IL instruction with no body // followed by the epilog (say ret void immediately preceding @@ -10875,7 +10756,7 @@ void CodeGen::genIPmappingGen() // at the (empty) ret statement if the user tries to put a // breakpoint there, and then have the option of seeing the // epilog or not based on SetUnmappedStopMask for the stepper. - compiler->eeSetLIinfo(mappingCnt++, nextNativeOfs, jitGetILoffsAny(srcIP), jitIsStackEmpty(srcIP), false); + compiler->eeSetLIinfo(mappingCnt++, nextNativeOfs, tmpMapping->ipmdKind, tmpMapping->ipmdLoc); } } @@ -10899,12 +10780,12 @@ void CodeGen::genIPmappingGen() if ((block->bbRefs > 1) && (stmt != nullptr)) { bool found = false; - if (stmt->GetILOffsetX() != BAD_IL_OFFSET) + DebugInfo rootInfo = stmt->GetDebugInfo().GetRoot(); + if (rootInfo.IsValid()) { - IL_OFFSET ilOffs = jitGetILoffs(stmt->GetILOffsetX()); - for (unsigned i = 0; i < eeBoundariesCount; ++i) + for (unsigned i = 0; i < compiler->eeBoundariesCount; ++i) { - if (eeBoundaries[i].ilOffset == ilOffs) + if (compiler->eeBoundaries[i].ilOffset == rootInfo.GetLocation().GetOffset()) { found = true; break; diff --git a/src/coreclr/jit/codegenlinear.cpp b/src/coreclr/jit/codegenlinear.cpp index e0f48954c65cd..c95fba37af4f0 100644 --- a/src/coreclr/jit/codegenlinear.cpp +++ b/src/coreclr/jit/codegenlinear.cpp @@ -376,7 +376,7 @@ void CodeGen::genCodeForBBlist() !compiler->fgBBisScratch(block)) // If the block is the distinguished first scratch block, then no need to // emit a NO_MAPPING entry, immediately after the prolog. { - genIPmappingAdd((IL_OFFSETX)ICorDebugInfo::NO_MAPPING, true); + genIPmappingAdd(IPmappingDscKind::NoMapping, DebugInfo(), true); } bool firstMapping = true; @@ -425,16 +425,16 @@ void CodeGen::genCodeForBBlist() } #endif // DEBUG - IL_OFFSETX currentILOffset = BAD_IL_OFFSET; + DebugInfo currentDI; for (GenTree* node : LIR::AsRange(block)) { // Do we have a new IL offset? if (node->OperGet() == GT_IL_OFFSET) { GenTreeILOffset* ilOffset = node->AsILOffset(); - genEnsureCodeEmitted(currentILOffset); - currentILOffset = ilOffset->gtStmtILoffsx; - genIPmappingAdd(currentILOffset, firstMapping); + genEnsureCodeEmitted(currentDI); + currentDI = ilOffset->gtStmtDI; + genIPmappingAdd(IPmappingDscKind::Normal, currentDI, firstMapping); firstMapping = false; #ifdef DEBUG assert(ilOffset->gtStmtLastILoffs <= compiler->info.compILCodeSize || @@ -529,7 +529,7 @@ void CodeGen::genCodeForBBlist() // // This can lead to problems when debugging the generated code. To prevent these issues, make sure // we've generated code for the last IL offset we saw in the block. - genEnsureCodeEmitted(currentILOffset); + genEnsureCodeEmitted(currentDI); /* Is this the last block, and are there any open scopes left ? */ @@ -2302,7 +2302,7 @@ void CodeGen::genEmitCall(int callType, X86_ARG(int argSize), emitAttr retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(emitAttr secondRetSize), - IL_OFFSETX ilOffset, + const DebugInfo& di, regNumber base, bool isJump) { @@ -2324,7 +2324,7 @@ void CodeGen::genEmitCall(int callType, gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, - ilOffset, base, REG_NA, 0, 0, isJump); + di, base, REG_NA, 0, 0, isJump); } // clang-format on @@ -2340,7 +2340,7 @@ void CodeGen::genEmitCallIndir(int callType, X86_ARG(int argSize), emitAttr retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(emitAttr secondRetSize), - IL_OFFSETX ilOffset, + const DebugInfo& di, bool isJump) { #if !defined(TARGET_X86) @@ -2365,7 +2365,7 @@ void CodeGen::genEmitCallIndir(int callType, gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, - ilOffset, + di, iReg, xReg, indir->Scale(), diff --git a/src/coreclr/jit/codegenxarch.cpp b/src/coreclr/jit/codegenxarch.cpp index 128de7c9fcd5b..5c8e0bdd5c2ae 100644 --- a/src/coreclr/jit/codegenxarch.cpp +++ b/src/coreclr/jit/codegenxarch.cpp @@ -5506,11 +5506,11 @@ void CodeGen::genCallInstruction(GenTreeCall* call X86_ARG(target_ssize_t stackA // We don't want tail call helper calls that were converted from normal calls to get a record, // so we skip this hash table lookup logic in that case. - IL_OFFSETX ilOffset = BAD_IL_OFFSET; + DebugInfo di; - if (compiler->opts.compDbgInfo && compiler->genCallSite2ILOffsetMap != nullptr && !call->IsTailCall()) + if (compiler->opts.compDbgInfo && compiler->genCallSite2DebugInfoMap != nullptr && !call->IsTailCall()) { - (void)compiler->genCallSite2ILOffsetMap->Lookup(call, &ilOffset); + (void)compiler->genCallSite2DebugInfoMap->Lookup(call, &di); } CORINFO_SIG_INFO* sigInfo = nullptr; @@ -5562,7 +5562,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call X86_ARG(target_ssize_t stackA gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, - ilOffset, REG_VIRTUAL_STUB_TARGET, REG_NA, 1, 0); + di, REG_VIRTUAL_STUB_TARGET, REG_NA, 1, 0); // clang-format on } else @@ -5583,7 +5583,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call X86_ARG(target_ssize_t stackA X86_ARG(argSizeForEmitter), retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), - ilOffset, + di, REG_NA, call->IsFastTailCall()); // clang-format on @@ -5605,7 +5605,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call X86_ARG(target_ssize_t stackA X86_ARG(argSizeForEmitter), retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), - ilOffset, + di, call->IsFastTailCall()); // clang-format on } @@ -5631,7 +5631,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call X86_ARG(target_ssize_t stackA X86_ARG(argSizeForEmitter), retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), - ilOffset, + di, target->GetRegNum(), call->IsFastTailCall()); // clang-format on @@ -5661,7 +5661,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call X86_ARG(target_ssize_t stackA gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, - ilOffset, indirCellReg, REG_NA, 0, 0, + di, indirCellReg, REG_NA, 0, 0, call->IsFastTailCall()); // clang-format on } @@ -5678,7 +5678,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call X86_ARG(target_ssize_t stackA X86_ARG(argSizeForEmitter), retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), - ilOffset, + di, REG_NA, call->IsFastTailCall()); // clang-format on @@ -5718,7 +5718,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call X86_ARG(target_ssize_t stackA X86_ARG(argSizeForEmitter), retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), - ilOffset, + di, REG_NA, call->IsFastTailCall()); // clang-format on @@ -8408,7 +8408,7 @@ void CodeGen::genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize, gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, - BAD_IL_OFFSET, // IL offset + DebugInfo(), callTarget, // ireg REG_NA, 0, 0, // xreg, xmul, disp false // isJump diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index d4f616892c927..e35cf807734ed 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -174,128 +174,6 @@ void Compiler::JitLogEE(unsigned level, const char* fmt, ...) va_end(args); } -void Compiler::compDspSrcLinesByLineNum(unsigned line, bool seek) -{ - if (!jitSrcFilePtr) - { - return; - } - - if (jitCurSrcLine == line) - { - return; - } - - if (jitCurSrcLine > line) - { - if (!seek) - { - return; - } - - if (fseek(jitSrcFilePtr, 0, SEEK_SET) != 0) - { - printf("Compiler::compDspSrcLinesByLineNum: fseek returned an error.\n"); - } - jitCurSrcLine = 0; - } - - if (!seek) - { - printf(";\n"); - } - - do - { - char temp[128]; - size_t llen; - - if (!fgets(temp, sizeof(temp), jitSrcFilePtr)) - { - return; - } - - if (seek) - { - continue; - } - - llen = strlen(temp); - if (llen && temp[llen - 1] == '\n') - { - temp[llen - 1] = 0; - } - - printf("; %s\n", temp); - } while (++jitCurSrcLine < line); - - if (!seek) - { - printf(";\n"); - } -} - -/*****************************************************************************/ - -void Compiler::compDspSrcLinesByNativeIP(UNATIVE_OFFSET curIP) -{ - static IPmappingDsc* nextMappingDsc; - static unsigned lastLine; - - if (!opts.dspLines) - { - return; - } - - if (curIP == 0) - { - if (genIPmappingList) - { - nextMappingDsc = genIPmappingList; - lastLine = jitGetILoffs(nextMappingDsc->ipmdILoffsx); - - unsigned firstLine = jitGetILoffs(nextMappingDsc->ipmdILoffsx); - - unsigned earlierLine = (firstLine < 5) ? 0 : firstLine - 5; - - compDspSrcLinesByLineNum(earlierLine, true); // display previous 5 lines - compDspSrcLinesByLineNum(firstLine, false); - } - else - { - nextMappingDsc = nullptr; - } - - return; - } - - if (nextMappingDsc) - { - UNATIVE_OFFSET offset = nextMappingDsc->ipmdNativeLoc.CodeOffset(GetEmitter()); - - if (offset <= curIP) - { - IL_OFFSET nextOffs = jitGetILoffs(nextMappingDsc->ipmdILoffsx); - - if (lastLine < nextOffs) - { - compDspSrcLinesByLineNum(nextOffs); - } - else - { - // This offset corresponds to a previous line. Rewind to that line - - compDspSrcLinesByLineNum(nextOffs - 2, true); - compDspSrcLinesByLineNum(nextOffs); - } - - lastLine = nextOffs; - nextMappingDsc = nextMappingDsc->ipmdNext; - } - } -} - -/*****************************************************************************/ #endif // DEBUG /*****************************************************************************/ @@ -2616,7 +2494,6 @@ void Compiler::compInitOptions(JitFlags* jitFlags) assert(!jitFlags->IsSet(JitFlags::JIT_FLAG_BBINSTR)); assert(!jitFlags->IsSet(JitFlags::JIT_FLAG_PROF_ENTERLEAVE)); assert(!jitFlags->IsSet(JitFlags::JIT_FLAG_DEBUG_EnC)); - assert(!jitFlags->IsSet(JitFlags::JIT_FLAG_DEBUG_INFO)); assert(!jitFlags->IsSet(JitFlags::JIT_FLAG_REVERSE_PINVOKE)); assert(!jitFlags->IsSet(JitFlags::JIT_FLAG_TRACK_TRANSITIONS)); } @@ -2744,6 +2621,7 @@ void Compiler::compInitOptions(JitFlags* jitFlags) verboseSsa = verbose && shouldUseVerboseSsa(); asciiTrees = shouldDumpASCIITrees(); opts.dspDiffable = compIsForInlining() ? impInlineInfo->InlinerCompiler->opts.dspDiffable : false; + #endif opts.altJit = false; @@ -3740,8 +3618,6 @@ bool Compiler::compPromoteFewerStructs(unsigned lclNum) void Compiler::compInitDebuggingInfo() { - assert(!compIsForInlining()); - #ifdef DEBUG if (verbose) { @@ -6207,7 +6083,8 @@ int Compiler::compCompileHelper(CORINFO_MODULE_HANDLE classPtr, assert((methAttr_Old & (~flagsToIgnore)) == (methAttr_New & (~flagsToIgnore))); #endif - info.compFlags = impInlineInfo->inlineCandidateInfo->methAttr; + info.compFlags = impInlineInfo->inlineCandidateInfo->methAttr; + compInlineContext = impInlineInfo->inlineContext; } else { @@ -6215,6 +6092,7 @@ int Compiler::compCompileHelper(CORINFO_MODULE_HANDLE classPtr, #ifdef PSEUDORANDOM_NOP_INSERTION info.compChecksum = getMethodBodyChecksum((char*)methodInfo->ILCode, methodInfo->ILCodeSize); #endif + compInlineContext = m_inlineStrategy->GetRootContext(); } compSwitchedToOptimized = false; @@ -6352,10 +6230,7 @@ int Compiler::compCompileHelper(CORINFO_MODULE_HANDLE classPtr, lvaInitTypeRef(); - if (!compIsForInlining()) - { - compInitDebuggingInfo(); - } + compInitDebuggingInfo(); #ifdef DEBUG if (compIsForInlining()) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 188c583b3dca7..16aae97a6956e 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -27,6 +27,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX #include "jitstd.h" #include "jithashtable.h" #include "gentree.h" +#include "debuginfo.h" #include "lir.h" #include "block.h" #include "inline.h" @@ -2515,6 +2516,25 @@ inline LoopFlags& operator&=(LoopFlags& a, LoopFlags b) return a = (LoopFlags)((unsigned short)a & (unsigned short)b); } +// The following holds information about instr offsets in terms of generated code. + +enum class IPmappingDscKind +{ + Prolog, // The mapping represents the start of a prolog. + Epilog, // The mapping represents the start of an epilog. + NoMapping, // This does not map to any IL offset. + Normal, // The mapping maps to an IL offset. +}; + +struct IPmappingDsc +{ + IPmappingDsc* ipmdNext; // next line# record + emitLocation ipmdNativeLoc; // the emitter location of the native code corresponding to the IL offset + IPmappingDscKind ipmdKind; // The kind of mapping + ILLocation ipmdLoc; // The location for normal mappings + bool ipmdIsLabel; // Can this code be a branch label? +}; + /* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX @@ -2987,7 +3007,8 @@ class Compiler */ // Functions to create nodes - Statement* gtNewStmt(GenTree* expr = nullptr, IL_OFFSETX offset = BAD_IL_OFFSET); + Statement* gtNewStmt(GenTree* expr = nullptr); + Statement* gtNewStmt(GenTree* expr, const DebugInfo& di); // For unary opers. GenTree* gtNewOperNode(genTreeOps oper, var_types type, GenTree* op1, bool doSimplifications = TRUE); @@ -3071,12 +3092,12 @@ class Compiler CORINFO_METHOD_HANDLE handle, var_types type, GenTreeCall::Use* args, - IL_OFFSETX ilOffset = BAD_IL_OFFSET); + const DebugInfo& di = DebugInfo()); GenTreeCall* gtNewIndCallNode(GenTree* addr, var_types type, GenTreeCall::Use* args, - IL_OFFSETX ilOffset = BAD_IL_OFFSET); + const DebugInfo& di = DebugInfo()); GenTreeCall* gtNewHelperCallNode(unsigned helper, var_types type, GenTreeCall::Use* args = nullptr); @@ -3084,8 +3105,8 @@ class Compiler GenTree* ctxTree, void* compileTimeHandle); - GenTreeLclVar* gtNewLclvNode(unsigned lnum, var_types type DEBUGARG(IL_OFFSETX ILoffs = BAD_IL_OFFSET)); - GenTreeLclVar* gtNewLclLNode(unsigned lnum, var_types type DEBUGARG(IL_OFFSETX ILoffs = BAD_IL_OFFSET)); + GenTreeLclVar* gtNewLclvNode(unsigned lnum, var_types type DEBUGARG(IL_OFFSET offs = BAD_IL_OFFSET)); + GenTreeLclVar* gtNewLclLNode(unsigned lnum, var_types type DEBUGARG(IL_OFFSET offs = BAD_IL_OFFSET)); GenTreeLclVar* gtNewLclVarAddrNode(unsigned lclNum, var_types type = TYP_I_IMPL); GenTreeLclFld* gtNewLclFldAddrNode(unsigned lclNum, @@ -3328,11 +3349,11 @@ class Compiler GenTreeOp* gtNewAssignNode(GenTree* dst, GenTree* src); - GenTree* gtNewTempAssign(unsigned tmp, - GenTree* val, - Statement** pAfterStmt = nullptr, - IL_OFFSETX ilOffset = BAD_IL_OFFSET, - BasicBlock* block = nullptr); + GenTree* gtNewTempAssign(unsigned tmp, + GenTree* val, + Statement** pAfterStmt = nullptr, + const DebugInfo& di = DebugInfo(), + BasicBlock* block = nullptr); GenTree* gtNewRefCOMfield(GenTree* objPtr, CORINFO_RESOLVED_TOKEN* pResolvedToken, @@ -3387,7 +3408,7 @@ class Compiler Statement* gtCloneStmt(Statement* stmt) { GenTree* exprClone = gtCloneExpr(stmt->GetRootNode()); - return gtNewStmt(exprClone, stmt->GetILOffsetX()); + return gtNewStmt(exprClone, stmt->GetDebugInfo()); } // Internal helper for cloning a call @@ -3580,6 +3601,7 @@ class Compiler void gtDispLclVar(unsigned lclNum, bool padForBiggestDisp = true); void gtDispLclVarStructType(unsigned lclNum); void gtDispClassLayout(ClassLayout* layout, var_types type); + void gtDispILLocation(const ILLocation& loc); void gtDispStmt(Statement* stmt, const char* msg = nullptr); void gtDispBlockStmts(BasicBlock* block); void gtGetArgMsg(GenTreeCall* call, GenTree* arg, unsigned argNum, char* bufp, unsigned bufLength); @@ -4155,9 +4177,11 @@ class Compiler unsigned lvaPSPSym; // variable representing the PSPSym #endif - InlineInfo* impInlineInfo; + InlineInfo* impInlineInfo; // Only present for inlinees InlineStrategy* m_inlineStrategy; + InlineContext* compInlineContext; // Always present + // The Compiler* that is the root of the inlining tree of which "this" is a member. Compiler* impInlineRoot(); @@ -4251,7 +4275,7 @@ class Compiler CORINFO_CONTEXT_HANDLE* exactContextHandle, bool isLateDevirtualization, bool isExplicitTailCall, - IL_OFFSETX ilOffset = BAD_IL_OFFSET); + IL_OFFSET ilOffset = BAD_IL_OFFSET); //========================================================================= // PROTECTED @@ -4298,7 +4322,7 @@ class Compiler bool impCanPInvokeInlineCallSite(BasicBlock* block); void impCheckForPInvokeCall( GenTreeCall* call, CORINFO_METHOD_HANDLE methHnd, CORINFO_SIG_INFO* sig, unsigned mflags, BasicBlock* block); - GenTreeCall* impImportIndirectCall(CORINFO_SIG_INFO* sig, IL_OFFSETX ilOffset = BAD_IL_OFFSET); + GenTreeCall* impImportIndirectCall(CORINFO_SIG_INFO* sig, const DebugInfo& di = DebugInfo()); void impPopArgsForUnmanagedCall(GenTree* call, CORINFO_SIG_INFO* sig); void impInsertHelperCall(CORINFO_HELPER_DESC* helperCall); @@ -4462,20 +4486,20 @@ class Compiler void impAppendStmt(Statement* stmt, unsigned chkLevel); void impAppendStmt(Statement* stmt); void impInsertStmtBefore(Statement* stmt, Statement* stmtBefore); - Statement* impAppendTree(GenTree* tree, unsigned chkLevel, IL_OFFSETX offset); - void impInsertTreeBefore(GenTree* tree, IL_OFFSETX offset, Statement* stmtBefore); - void impAssignTempGen(unsigned tmp, - GenTree* val, - unsigned curLevel, - Statement** pAfterStmt = nullptr, - IL_OFFSETX ilOffset = BAD_IL_OFFSET, - BasicBlock* block = nullptr); + Statement* impAppendTree(GenTree* tree, unsigned chkLevel, const DebugInfo& di); + void impInsertTreeBefore(GenTree* tree, const DebugInfo& di, Statement* stmtBefore); + void impAssignTempGen(unsigned tmp, + GenTree* val, + unsigned curLevel, + Statement** pAfterStmt = nullptr, + const DebugInfo& di = DebugInfo(), + BasicBlock* block = nullptr); void impAssignTempGen(unsigned tmpNum, GenTree* val, CORINFO_CLASS_HANDLE structHnd, unsigned curLevel, Statement** pAfterStmt = nullptr, - IL_OFFSETX ilOffset = BAD_IL_OFFSET, + const DebugInfo& di = DebugInfo(), BasicBlock* block = nullptr); Statement* impExtractLastStmt(); @@ -4489,14 +4513,14 @@ class Compiler CORINFO_CLASS_HANDLE structHnd, unsigned curLevel, Statement** pAfterStmt = nullptr, - IL_OFFSETX ilOffset = BAD_IL_OFFSET, + const DebugInfo& di = DebugInfo(), BasicBlock* block = nullptr); GenTree* impAssignStructPtr(GenTree* dest, GenTree* src, CORINFO_CLASS_HANDLE structHnd, unsigned curLevel, Statement** pAfterStmt = nullptr, - IL_OFFSETX ilOffset = BAD_IL_OFFSET, + const DebugInfo& di = DebugInfo(), BasicBlock* block = nullptr); GenTree* impGetStructAddr(GenTree* structVal, CORINFO_CLASS_HANDLE structHnd, unsigned curLevel, bool willDeref); @@ -4573,14 +4597,14 @@ class Compiler void impNoteLastILoffs(); #endif - /* IL offset of the stmt currently being imported. It gets set to - BAD_IL_OFFSET after it has been set in the appended trees. Then it gets - updated at IL offsets for which we have to report mapping info. - It also includes flag bits, so use jitGetILoffs() - to get the actual IL offset value. - */ + // Debug info of current statement being imported. It gets set to contain + // no IL location (!impCurStmtDI.GetLocation().IsValid) after it has been + // set in the appended trees. Then it gets updated at IL instructions for + // which we have to report mapping info. + // It will always contain the current inline context. + DebugInfo impCurStmtDI; - IL_OFFSETX impCurStmtOffs; + DebugInfo impCreateDIWithCurrentStackInfo(IL_OFFSET offs, bool isCall); void impCurStmtOffsSet(IL_OFFSET offs); void impNoteBranchOffs(); @@ -4603,11 +4627,6 @@ class Compiler GenTreeCall::Use* impPopReverseCallArgs(unsigned count, CORINFO_SIG_INFO* sig, unsigned skipReverseCount = 0); - /* - * Get current IL offset with stack-empty info incoporated - */ - IL_OFFSETX impCurILOffset(IL_OFFSET offs, bool callInstruction = false); - //---------------- Spilling the importer stack ---------------------------- // The maximum number of bytes of IL processed without clean stack state. @@ -5210,10 +5229,10 @@ class Compiler BasicBlock* fgSplitBlockAfterNode(BasicBlock* curr, GenTree* node); // for LIR BasicBlock* fgSplitEdge(BasicBlock* curr, BasicBlock* succ); - Statement* fgNewStmtFromTree(GenTree* tree, BasicBlock* block, IL_OFFSETX offs); + Statement* fgNewStmtFromTree(GenTree* tree, BasicBlock* block, const DebugInfo& di); Statement* fgNewStmtFromTree(GenTree* tree); Statement* fgNewStmtFromTree(GenTree* tree, BasicBlock* block); - Statement* fgNewStmtFromTree(GenTree* tree, IL_OFFSETX offs); + Statement* fgNewStmtFromTree(GenTree* tree, const DebugInfo& di); GenTree* fgGetTopLevelQmark(GenTree* expr, GenTree** ppDst = nullptr); void fgExpandQmarkForCastInstOf(BasicBlock* block, Statement* stmt); @@ -6144,10 +6163,10 @@ class Compiler #endif public: - Statement* fgNewStmtAtBeg(BasicBlock* block, GenTree* tree, IL_OFFSETX offs = BAD_IL_OFFSET); + Statement* fgNewStmtAtBeg(BasicBlock* block, GenTree* tree, const DebugInfo& di = DebugInfo()); void fgInsertStmtAtEnd(BasicBlock* block, Statement* stmt); - Statement* fgNewStmtAtEnd(BasicBlock* block, GenTree* tree, IL_OFFSETX offs = BAD_IL_OFFSET); - Statement* fgNewStmtNearEnd(BasicBlock* block, GenTree* tree, IL_OFFSETX offs = BAD_IL_OFFSET); + Statement* fgNewStmtAtEnd(BasicBlock* block, GenTree* tree, const DebugInfo& di = DebugInfo()); + Statement* fgNewStmtNearEnd(BasicBlock* block, GenTree* tree, const DebugInfo& di = DebugInfo()); private: void fgInsertStmtNearEnd(BasicBlock* block, Statement* stmt); @@ -6331,17 +6350,17 @@ class Compiler GenTree* fgGetStubAddrArg(GenTreeCall* call); unsigned fgGetArgTabEntryParameterLclNum(GenTreeCall* call, fgArgTabEntry* argTabEntry); void fgMorphRecursiveFastTailCallIntoLoop(BasicBlock* block, GenTreeCall* recursiveTailCall); - Statement* fgAssignRecursiveCallArgToCallerParam(GenTree* arg, - fgArgTabEntry* argTabEntry, - unsigned lclParamNum, - BasicBlock* block, - IL_OFFSETX callILOffset, - Statement* tmpAssignmentInsertionPoint, - Statement* paramAssignmentInsertionPoint); + Statement* fgAssignRecursiveCallArgToCallerParam(GenTree* arg, + fgArgTabEntry* argTabEntry, + unsigned lclParamNum, + BasicBlock* block, + const DebugInfo& callDI, + Statement* tmpAssignmentInsertionPoint, + Statement* paramAssignmentInsertionPoint); GenTree* fgMorphCall(GenTreeCall* call); GenTree* fgExpandVirtualVtableCallTarget(GenTreeCall* call); void fgMorphCallInline(GenTreeCall* call, InlineResult* result); - void fgMorphCallInlineHelper(GenTreeCall* call, InlineResult* result); + void fgMorphCallInlineHelper(GenTreeCall* call, InlineResult* result, InlineContext** createdContext); #if DEBUG void fgNoteNonInlineCandidate(Statement* stmt, GenTreeCall* call); static fgWalkPreFn fgFindNonInlineCandidate; @@ -6467,7 +6486,7 @@ class Compiler unsigned fgBigOffsetMorphingTemps[TYP_COUNT]; unsigned fgCheckInlineDepthAndRecursion(InlineInfo* inlineInfo); - void fgInvokeInlineeCompiler(GenTreeCall* call, InlineResult* result); + void fgInvokeInlineeCompiler(GenTreeCall* call, InlineResult* result, InlineContext** createdContext); void fgInsertInlineeBlocks(InlineInfo* pInlineInfo); Statement* fgInlinePrependStatements(InlineInfo* inlineInfo); void fgInlineAppendStatements(InlineInfo* inlineInfo, BasicBlock* block, Statement* stmt); @@ -7363,7 +7382,7 @@ class Compiler } void considerGuardedDevirtualization(GenTreeCall* call, - IL_OFFSETX iloffset, + IL_OFFSET ilOffset, bool isInterface, CORINFO_METHOD_HANDLE baseMethod, CORINFO_CLASS_HANDLE baseClass, @@ -8157,19 +8176,15 @@ class Compiler unsigned eeBoundariesCount; - struct boundariesDsc - { - UNATIVE_OFFSET nativeIP; - IL_OFFSET ilOffset; - unsigned sourceReason; - } * eeBoundaries; // Boundaries to report to EE + ICorDebugInfo::OffsetMapping* eeBoundaries; // Boundaries to report to the EE void eeSetLIcount(unsigned count); - void eeSetLIinfo(unsigned which, UNATIVE_OFFSET offs, unsigned srcIP, bool stkEmpty, bool callInstruction); + void eeSetLIinfo(unsigned which, UNATIVE_OFFSET offs, IPmappingDscKind kind, const ILLocation& loc); void eeSetLIdone(); #ifdef DEBUG static void eeDispILOffs(IL_OFFSET offs); - static void eeDispLineInfo(const boundariesDsc* line); + static void eeDispSourceMappingOffs(uint32_t offs); + static void eeDispLineInfo(const ICorDebugInfo::OffsetMapping* line); void eeDispLineInfos(); #endif // DEBUG @@ -8285,34 +8300,24 @@ class Compiler public: CodeGenInterface* codeGen; - // The following holds information about instr offsets in terms of generated code. - - struct IPmappingDsc - { - IPmappingDsc* ipmdNext; // next line# record - emitLocation ipmdNativeLoc; // the emitter location of the native code corresponding to the IL offset - IL_OFFSETX ipmdILoffsx; // the instr offset - bool ipmdIsLabel; // Can this code be a branch label? - }; - // Record the instr offset mapping to the generated code IPmappingDsc* genIPmappingList; IPmappingDsc* genIPmappingLast; // Managed RetVal - A side hash table meant to record the mapping from a - // GT_CALL node to its IL offset. This info is used to emit sequence points + // GT_CALL node to its debug info. This info is used to emit sequence points // that can be used by debugger to determine the native offset at which the // managed RetVal will be available. // - // In fact we can store IL offset in a GT_CALL node. This was ruled out in - // favor of a side table for two reasons: 1) We need IL offset for only those + // In fact we can store debug info in a GT_CALL node. This was ruled out in + // favor of a side table for two reasons: 1) We need debug info for only those // GT_CALL nodes (created during importation) that correspond to an IL call and // whose return type is other than TYP_VOID. 2) GT_CALL node is a frequently used // structure and IL offset is needed only when generating debuggable code. Therefore // it is desirable to avoid memory size penalty in retail scenarios. - typedef JitHashTable, IL_OFFSETX> CallSiteILOffsetTable; - CallSiteILOffsetTable* genCallSite2ILOffsetMap; + typedef JitHashTable, DebugInfo> CallSiteDebugInfoTable; + CallSiteDebugInfoTable* genCallSite2DebugInfoMap; unsigned genReturnLocal; // Local number for the return value when applicable. BasicBlock* genReturnBB; // jumped to when not optimizing for speed. @@ -10453,6 +10458,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX VarName compVarName(regNumber reg, bool isFloatReg = false); const char* compRegVarName(regNumber reg, bool displayVar = false, bool isFloatReg = false); const char* compRegNameForSize(regNumber reg, size_t size); + const char* compFPregVarName(unsigned fpReg, bool displayVar = false); void compDspSrcLinesByNativeIP(UNATIVE_OFFSET curIP); void compDspSrcLinesByLineNum(unsigned line, bool seek = false); #endif // DEBUG diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index 0c446f2020d0b..7fec5719abc36 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -822,9 +822,16 @@ inline GenTree::GenTree(genTreeOps oper, var_types type DEBUGARG(bool largeNode) /*****************************************************************************/ -inline Statement* Compiler::gtNewStmt(GenTree* expr, IL_OFFSETX offset) +inline Statement* Compiler::gtNewStmt(GenTree* expr) { - Statement* stmt = new (this->getAllocator(CMK_ASTNode)) Statement(expr, offset DEBUGARG(compStatementID++)); + Statement* stmt = new (this->getAllocator(CMK_ASTNode)) Statement(expr DEBUGARG(compStatementID++)); + return stmt; +} + +inline Statement* Compiler::gtNewStmt(GenTree* expr, const DebugInfo& di) +{ + Statement* stmt = gtNewStmt(expr); + stmt->SetDebugInfo(di); return stmt; } diff --git a/src/coreclr/jit/debuginfo.cpp b/src/coreclr/jit/debuginfo.cpp new file mode 100644 index 0000000000000..ed5edeb113da3 --- /dev/null +++ b/src/coreclr/jit/debuginfo.cpp @@ -0,0 +1,145 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "jitpch.h" +#include "debuginfo.h" + +#ifdef DEBUG +//------------------------------------------------------------------------ +// Dump: Print a textual representation of this ILLocation. +// +// Notes: +// For invalid ILLocations, we print '???'. +// Otherwise the offset and flags are printed in the format 0xabc[EC]. +// +void ILLocation::Dump() const +{ + if (!IsValid()) + { + printf("???"); + } + else + { + printf("0x%03X[", GetOffset()); + printf("%c", IsStackEmpty() ? 'E' : '-'); + printf("%c", IsCall() ? 'C' : '-'); + printf("]"); + } +} + +//------------------------------------------------------------------------ +// Dump: Print a textual representation of this DebugInfo. +// +// Parameters: +// recurse - print the full path back to the root, separated by arrows. +// +// Notes: +// The DebugInfo is printed in the format +// +// INL02 @ 0xabc[EC] +// +// Before '@' is the ordinal of the inline context, then comes the IL +// offset, and then comes the IL location flags (stack Empty, isCall). +// +// If 'recurse' is specified then dump the full DebugInfo path to the +// root in the format +// +// INL02 @ 0xabc[EC] <- INL01 @ 0x123[EC] <- ... <- INLRT @ 0x456[EC] +// +// with the left most entry being the inner most inlined statement. +void DebugInfo::Dump(bool recurse) const +{ + InlineContext* context = GetInlineContext(); + if (context != nullptr) + { + if (context->IsRoot()) + { + printf("INLRT @ "); + } + else if (context->GetOrdinal() != 0) + { + printf(FMT_INL_CTX " @ ", context->GetOrdinal()); + } + } + + GetLocation().Dump(); + + DebugInfo par; + if (recurse && GetParent(&par)) + { + printf(" <- "); + par.Dump(recurse); + } +} + +//------------------------------------------------------------------------ +// Validate: Validate this DebugInfo instance. +// +// Notes: +// This validates that if there is DebugInfo, then it looks sane by checking +// that the IL location correctly points to the beginning of an IL instruction. +// +void DebugInfo::Validate() const +{ + DebugInfo di = *this; + do + { + if (!di.IsValid()) + continue; + + bool isValidOffs = di.GetLocation().GetOffset() < di.GetInlineContext()->GetILSize(); + if (isValidOffs) + { + bool isValidStart = di.GetInlineContext()->GetILInstsSet()->bitVectTest(di.GetLocation().GetOffset()); + assert(isValidStart && + "Detected invalid debug info: IL offset does not refer to the start of an IL instruction"); + } + else + { + assert(!"Detected invalid debug info: IL offset is out of range"); + } + + } while (di.GetParent(&di)); +} +#endif + +//------------------------------------------------------------------------ +// GetParent: Get debug info for the parent statement that inlined the +// statement for this debug info. +// +// Parameters: +// parent [out] - Debug info for the location that inlined this statement. +// +// Return Value: +// True if the current debug info is valid and has a parent; otherwise false. +// On false return, the 'parent' parameter is unaffected. +// +bool DebugInfo::GetParent(DebugInfo* parent) const +{ + if ((m_inlineContext == nullptr) || m_inlineContext->IsRoot()) + return false; + + *parent = DebugInfo(m_inlineContext->GetParent(), m_inlineContext->GetLocation()); + return true; +} + +//------------------------------------------------------------------------ +// GetRoot: Get debug info for the statement in the root function that +// eventually led to this debug info through inlines. +// +// Return Value: +// If this DebugInfo instance is valid, returns a DebugInfo instance +// representing the call in the root function that eventually inlined the +// statement this DebugInfo describes. +// +// If this DebugInfo instance is invalid, returns an invalid DebugInfo instance. +// +DebugInfo DebugInfo::GetRoot() const +{ + DebugInfo result = *this; + while (result.GetParent(&result)) + { + } + + return result; +} diff --git a/src/coreclr/jit/debuginfo.h b/src/coreclr/jit/debuginfo.h new file mode 100644 index 0000000000000..304b258dc12af --- /dev/null +++ b/src/coreclr/jit/debuginfo.h @@ -0,0 +1,135 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef _DEBUGINFO_H_ +#define _DEBUGINFO_H_ + +#include "jit.h" + +class InlineContext; + +// Represents information about the location of an IL instruction. +class ILLocation +{ +public: + ILLocation() : m_offset(BAD_IL_OFFSET), m_isStackEmpty(false), m_isCall(false) + { + } + + ILLocation(IL_OFFSET offset, bool isStackEmpty, bool isCall) + : m_offset(offset), m_isStackEmpty(isStackEmpty), m_isCall(isCall) + { + } + + IL_OFFSET GetOffset() const + { + return m_offset; + } + + // Is this source location at a stack empty point? We need to be able to + // report this information back to the debugger since we only allow EnC + // transitions at stack empty points. + bool IsStackEmpty() const + { + return m_isStackEmpty; + } + + bool IsCall() const + { + return m_isCall; + } + + bool IsValid() const + { + return m_offset != BAD_IL_OFFSET; + } + + inline bool operator==(const ILLocation& other) const + { + return (m_offset == other.m_offset) && (m_isStackEmpty == other.m_isStackEmpty) && (m_isCall == other.m_isCall); + } + + inline bool operator!=(const ILLocation& other) const + { + return !(*this == other); + } + +#ifdef DEBUG + // Dump textual representation of this ILLocation to jitstdout. + void Dump() const; +#endif + +private: + IL_OFFSET m_offset; + bool m_isStackEmpty : 1; + bool m_isCall : 1; +}; + +// Represents debug information about a statement. +class DebugInfo +{ +public: + DebugInfo() : m_inlineContext(nullptr) + { + } + + DebugInfo(InlineContext* inlineContext, ILLocation loc) : m_inlineContext(inlineContext), m_location(loc) + { + } + + InlineContext* GetInlineContext() const + { + return m_inlineContext; + } + + ILLocation GetLocation() const + { + return m_location; + } + + // Retrieve information about the location that inlined this statement. + // Note that there can be associated parent information even when IsValid + // below returns false. + bool GetParent(DebugInfo* parent) const; + + // Get debug info in the root. If this debug info is in the root, then + // returns *this. Otherwise returns information of the call in the root + // that eventually produced this statement through inlines. + DebugInfo GetRoot() const; + +#ifdef DEBUG + void Validate() const; +#else + void Validate() const + { + } +#endif + +#ifdef DEBUG + // Dump textual representation of this DebugInfo to jitstdout. + void Dump(bool recurse) const; +#endif + + // Check if this debug info has both a valid inline context and valid + // location. + bool IsValid() const + { + return m_inlineContext != nullptr && m_location.IsValid(); + } + + inline bool operator==(const DebugInfo& other) const + { + return (m_inlineContext == other.m_inlineContext) && (m_location == other.m_location); + } + + inline bool operator!=(const DebugInfo& other) const + { + return !(*this == other); + } + +private: + InlineContext* m_inlineContext; + ILLocation m_location; +}; + +#endif diff --git a/src/coreclr/jit/ee_il_dll.cpp b/src/coreclr/jit/ee_il_dll.cpp index 07ad7dfc90b9c..ebf1ea2945195 100644 --- a/src/coreclr/jit/ee_il_dll.cpp +++ b/src/coreclr/jit/ee_il_dll.cpp @@ -608,7 +608,17 @@ void Compiler::eeGetStmtOffsets() uint32_t* offsets; ICorDebugInfo::BoundaryTypes offsetsImplicit; - info.compCompHnd->getBoundaries(info.compMethodHnd, &offsetsCount, &offsets, &offsetsImplicit); + if (compIsForInlining()) + { + // We do not get explicit boundaries for inlinees, only implicit ones. + offsetsImplicit = impInlineRoot()->info.compStmtOffsetsImplicit; + offsetsCount = 0; + offsets = nullptr; + } + else + { + info.compCompHnd->getBoundaries(info.compMethodHnd, &offsetsCount, &offsets, &offsetsImplicit); + } /* Set the implicit boundaries */ @@ -960,7 +970,8 @@ void Compiler::eeSetLIcount(unsigned count) eeBoundariesCount = count; if (eeBoundariesCount) { - eeBoundaries = (boundariesDsc*)info.compCompHnd->allocateArray(eeBoundariesCount * sizeof(eeBoundaries[0])); + eeBoundaries = + (ICorDebugInfo::OffsetMapping*)info.compCompHnd->allocateArray(eeBoundariesCount * sizeof(eeBoundaries[0])); } else { @@ -968,19 +979,39 @@ void Compiler::eeSetLIcount(unsigned count) } } -void Compiler::eeSetLIinfo( - unsigned which, UNATIVE_OFFSET nativeOffset, IL_OFFSET ilOffset, bool stkEmpty, bool callInstruction) +void Compiler::eeSetLIinfo(unsigned which, UNATIVE_OFFSET nativeOffset, IPmappingDscKind kind, const ILLocation& loc) { assert(opts.compDbgInfo); - assert(eeBoundariesCount > 0); + assert(eeBoundariesCount > 0 && eeBoundaries != nullptr); assert(which < eeBoundariesCount); - if (eeBoundaries != nullptr) + eeBoundaries[which].nativeOffset = nativeOffset; + eeBoundaries[which].source = (ICorDebugInfo::SourceTypes)0; + + switch (kind) { - eeBoundaries[which].nativeIP = nativeOffset; - eeBoundaries[which].ilOffset = ilOffset; - eeBoundaries[which].sourceReason = stkEmpty ? ICorDebugInfo::STACK_EMPTY : 0; - eeBoundaries[which].sourceReason |= callInstruction ? ICorDebugInfo::CALL_INSTRUCTION : 0; + int source; + + case IPmappingDscKind::Normal: + eeBoundaries[which].ilOffset = loc.GetOffset(); + source = loc.IsStackEmpty() ? ICorDebugInfo::STACK_EMPTY : 0; + source |= loc.IsCall() ? ICorDebugInfo::CALL_INSTRUCTION : 0; + eeBoundaries[which].source = (ICorDebugInfo::SourceTypes)source; + break; + case IPmappingDscKind::Prolog: + eeBoundaries[which].ilOffset = ICorDebugInfo::PROLOG; + eeBoundaries[which].source = ICorDebugInfo::STACK_EMPTY; + break; + case IPmappingDscKind::Epilog: + eeBoundaries[which].ilOffset = ICorDebugInfo::EPILOG; + eeBoundaries[which].source = ICorDebugInfo::STACK_EMPTY; + break; + case IPmappingDscKind::NoMapping: + eeBoundaries[which].ilOffset = ICorDebugInfo::NO_MAPPING; + eeBoundaries[which].source = ICorDebugInfo::STACK_EMPTY; + break; + default: + unreached(); } } @@ -1005,8 +1036,13 @@ void Compiler::eeSetLIdone() #if defined(DEBUG) -/* static */ void Compiler::eeDispILOffs(IL_OFFSET offs) +{ + printf("0x%04X", offs); +} + +/* static */ +void Compiler::eeDispSourceMappingOffs(uint32_t offs) { const char* specialOffs[] = {"EPILOG", "PROLOG", "NO_MAP"}; @@ -1022,33 +1058,34 @@ void Compiler::eeDispILOffs(IL_OFFSET offs) printf("%s", specialOffs[specialOffsNum]); break; default: - printf("0x%04X", offs); + eeDispILOffs(offs); + break; } } /* static */ -void Compiler::eeDispLineInfo(const boundariesDsc* line) +void Compiler::eeDispLineInfo(const ICorDebugInfo::OffsetMapping* line) { printf("IL offs "); - eeDispILOffs(line->ilOffset); + eeDispSourceMappingOffs(line->ilOffset); - printf(" : 0x%08X", line->nativeIP); - if (line->sourceReason != 0) + printf(" : 0x%08X", line->nativeOffset); + if (line->source != 0) { // It seems like it should probably never be zero since ICorDebugInfo::SOURCE_TYPE_INVALID is zero. // However, the JIT has always generated this and printed "stack non-empty". printf(" ( "); - if ((line->sourceReason & ICorDebugInfo::STACK_EMPTY) != 0) + if ((line->source & ICorDebugInfo::STACK_EMPTY) != 0) { printf("STACK_EMPTY "); } - if ((line->sourceReason & ICorDebugInfo::CALL_INSTRUCTION) != 0) + if ((line->source & ICorDebugInfo::CALL_INSTRUCTION) != 0) { printf("CALL_INSTRUCTION "); } - if ((line->sourceReason & ICorDebugInfo::CALL_SITE) != 0) + if ((line->source & ICorDebugInfo::CALL_SITE) != 0) { printf("CALL_SITE "); } @@ -1057,7 +1094,7 @@ void Compiler::eeDispLineInfo(const boundariesDsc* line) printf("\n"); // We don't expect to see any other bits. - assert((line->sourceReason & ~(ICorDebugInfo::STACK_EMPTY | ICorDebugInfo::CALL_INSTRUCTION)) == 0); + assert((line->source & ~(ICorDebugInfo::STACK_EMPTY | ICorDebugInfo::CALL_INSTRUCTION)) == 0); } void Compiler::eeDispLineInfos() diff --git a/src/coreclr/jit/emit.cpp b/src/coreclr/jit/emit.cpp index 9975cd5041945..54d57dd22b2f7 100644 --- a/src/coreclr/jit/emit.cpp +++ b/src/coreclr/jit/emit.cpp @@ -1815,11 +1815,11 @@ void emitter::emitCreatePlaceholderIG(insGroupPlaceholderType igType, { if (igType == IGPT_FUNCLET_PROLOG) { - codeGen->genIPmappingAdd((IL_OFFSETX)ICorDebugInfo::PROLOG, true); + codeGen->genIPmappingAdd(IPmappingDscKind::Prolog, DebugInfo(), true); } else if (igType == IGPT_FUNCLET_EPILOG) { - codeGen->genIPmappingAdd((IL_OFFSETX)ICorDebugInfo::EPILOG, true); + codeGen->genIPmappingAdd(IPmappingDscKind::Epilog, DebugInfo(), true); } } #endif // FEATURE_EH_FUNCLETS diff --git a/src/coreclr/jit/emitarm.cpp b/src/coreclr/jit/emitarm.cpp index 1a955ce88211f..9c51dbc7c54f2 100644 --- a/src/coreclr/jit/emitarm.cpp +++ b/src/coreclr/jit/emitarm.cpp @@ -4678,7 +4678,7 @@ void emitter::emitIns_Call(EmitCallType callType, VARSET_VALARG_TP ptrVars, regMaskTP gcrefRegs, regMaskTP byrefRegs, - IL_OFFSETX ilOffset /* = BAD_IL_OFFSET */, + const DebugInfo& di /* = DebugInfo() */, regNumber ireg /* = REG_NA */, regNumber xreg /* = REG_NA */, unsigned xmul /* = 0 */, @@ -4719,9 +4719,9 @@ void emitter::emitIns_Call(EmitCallType callType, #endif /* Managed RetVal: emit sequence point for the call */ - if (emitComp->opts.compDbgInfo && ilOffset != BAD_IL_OFFSET) + if (emitComp->opts.compDbgInfo && di.GetLocation().IsValid()) { - codeGen->genIPmappingAdd(ilOffset, false); + codeGen->genIPmappingAdd(IPmappingDscKind::Normal, di, false); } /* diff --git a/src/coreclr/jit/emitarm.h b/src/coreclr/jit/emitarm.h index 744b8676c45b2..c93c15dff8ea9 100644 --- a/src/coreclr/jit/emitarm.h +++ b/src/coreclr/jit/emitarm.h @@ -328,12 +328,12 @@ void emitIns_Call(EmitCallType callType, VARSET_VALARG_TP ptrVars, regMaskTP gcrefRegs, regMaskTP byrefRegs, - IL_OFFSETX ilOffset = BAD_IL_OFFSET, - regNumber ireg = REG_NA, - regNumber xreg = REG_NA, - unsigned xmul = 0, - ssize_t disp = 0, - bool isJump = false); + const DebugInfo& di = DebugInfo(), + regNumber ireg = REG_NA, + regNumber xreg = REG_NA, + unsigned xmul = 0, + ssize_t disp = 0, + bool isJump = false); /***************************************************************************** * diff --git a/src/coreclr/jit/emitarm64.cpp b/src/coreclr/jit/emitarm64.cpp index 414d1eaffa0d1..ddf47e8cbe831 100644 --- a/src/coreclr/jit/emitarm64.cpp +++ b/src/coreclr/jit/emitarm64.cpp @@ -8520,7 +8520,7 @@ void emitter::emitIns_Call(EmitCallType callType, VARSET_VALARG_TP ptrVars, regMaskTP gcrefRegs, regMaskTP byrefRegs, - IL_OFFSETX ilOffset /* = BAD_IL_OFFSET */, + const DebugInfo& di /* = DebugInfo() */, regNumber ireg /* = REG_NA */, regNumber xreg /* = REG_NA */, unsigned xmul /* = 0 */, @@ -8561,9 +8561,9 @@ void emitter::emitIns_Call(EmitCallType callType, #endif /* Managed RetVal: emit sequence point for the call */ - if (emitComp->opts.compDbgInfo && ilOffset != BAD_IL_OFFSET) + if (emitComp->opts.compDbgInfo && di.GetLocation().IsValid()) { - codeGen->genIPmappingAdd(ilOffset, false); + codeGen->genIPmappingAdd(IPmappingDscKind::Normal, di, false); } /* diff --git a/src/coreclr/jit/emitarm64.h b/src/coreclr/jit/emitarm64.h index 77a71a9005157..ab06c5e25c700 100644 --- a/src/coreclr/jit/emitarm64.h +++ b/src/coreclr/jit/emitarm64.h @@ -858,7 +858,7 @@ void emitIns_Call(EmitCallType callType, VARSET_VALARG_TP ptrVars, regMaskTP gcrefRegs, regMaskTP byrefRegs, - IL_OFFSETX ilOffset, + const DebugInfo& di, regNumber ireg, regNumber xreg, unsigned xmul, diff --git a/src/coreclr/jit/emitxarch.cpp b/src/coreclr/jit/emitxarch.cpp index e07984760e018..7f88c5049acba 100644 --- a/src/coreclr/jit/emitxarch.cpp +++ b/src/coreclr/jit/emitxarch.cpp @@ -7536,7 +7536,7 @@ void emitter::emitIns_Call(EmitCallType callType, VARSET_VALARG_TP ptrVars, regMaskTP gcrefRegs, regMaskTP byrefRegs, - IL_OFFSETX ilOffset, + const DebugInfo& di, regNumber ireg, regNumber xreg, unsigned xmul, @@ -7577,9 +7577,9 @@ void emitter::emitIns_Call(EmitCallType callType, #endif /* Managed RetVal: emit sequence point for the call */ - if (emitComp->opts.compDbgInfo && ilOffset != BAD_IL_OFFSET) + if (emitComp->opts.compDbgInfo && di.IsValid()) { - codeGen->genIPmappingAdd(ilOffset, false); + codeGen->genIPmappingAdd(IPmappingDscKind::Normal, di, false); } /* diff --git a/src/coreclr/jit/emitxarch.h b/src/coreclr/jit/emitxarch.h index 8217d088417cc..39458eade16cf 100644 --- a/src/coreclr/jit/emitxarch.h +++ b/src/coreclr/jit/emitxarch.h @@ -539,7 +539,7 @@ void emitIns_Call(EmitCallType callType, VARSET_VALARG_TP ptrVars, regMaskTP gcrefRegs, regMaskTP byrefRegs, - IL_OFFSETX ilOffset = BAD_IL_OFFSET, + const DebugInfo& di = DebugInfo(), regNumber ireg = REG_NA, regNumber xreg = REG_NA, unsigned xmul = 0, diff --git a/src/coreclr/jit/fgbasic.cpp b/src/coreclr/jit/fgbasic.cpp index da643cfd6d4db..00e5592fccabb 100644 --- a/src/coreclr/jit/fgbasic.cpp +++ b/src/coreclr/jit/fgbasic.cpp @@ -895,6 +895,11 @@ void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, Fixed bool preciseScan = makeInlineObservations && compInlineResult->GetPolicy()->RequiresPreciseScan(); const bool resolveTokens = preciseScan && (isPreJit || isTier1); + // Track offsets where IL instructions begin in DEBUG builds. Used to + // validate debug info generated by the JIT. + assert(codeSize == compInlineContext->GetILSize()); + INDEBUG(FixedBitVect* ilInstsSet = FixedBitVect::bitVectInit(codeSize, this)); + if (makeInlineObservations) { // Set default values for profile (to avoid NoteFailed in CALLEE_IL_CODE_SIZE's handler) @@ -946,6 +951,9 @@ void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, Fixed { prevOpcode = opcode; opcode = (OPCODE)getU1LittleEndian(codeAddr); + + INDEBUG(ilInstsSet->bitVectSet((UINT)(codeAddr - codeBegp))); + codeAddr += sizeof(__int8); if (!handled && preciseScan) @@ -2092,6 +2100,8 @@ void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, Fixed " at offset %04X", (IL_OFFSET)(codeAddr - codeBegp)); } + INDEBUG(compInlineContext->SetILInstsSet(ilInstsSet)); + if (makeInlineObservations) { compInlineResult->Note(InlineObservation::CALLEE_END_OPCODE_SCAN); @@ -4055,9 +4065,11 @@ IL_OFFSET Compiler::fgFindBlockILOffset(BasicBlock* block) for (Statement* const stmt : block->Statements()) { - if (stmt->GetILOffsetX() != BAD_IL_OFFSET) + // Blocks always contain IL offsets in the root. + DebugInfo di = stmt->GetDebugInfo().GetRoot(); + if (di.IsValid()) { - return jitGetILoffs(stmt->GetILOffsetX()); + return di.GetLocation().GetOffset(); } } @@ -4228,9 +4240,10 @@ BasicBlock* Compiler::fgSplitBlockAfterNode(BasicBlock* curr, GenTree* node) if ((*riter)->gtOper == GT_IL_OFFSET) { GenTreeILOffset* ilOffset = (*riter)->AsILOffset(); - if (ilOffset->gtStmtILoffsx != BAD_IL_OFFSET) + if (ilOffset->gtStmtDI.IsValid()) { - splitPointILOffset = jitGetILoffs(ilOffset->gtStmtILoffsx); + assert(ilOffset->gtStmtDI.GetInlineContext()->IsRoot()); + splitPointILOffset = ilOffset->gtStmtDI.GetLocation().GetOffset(); break; } } diff --git a/src/coreclr/jit/fginline.cpp b/src/coreclr/jit/fginline.cpp index e9cf9ecb852b6..cf73fa123e6fd 100644 --- a/src/coreclr/jit/fginline.cpp +++ b/src/coreclr/jit/fginline.cpp @@ -29,11 +29,12 @@ unsigned Compiler::fgCheckInlineDepthAndRecursion(InlineInfo* inlineInfo) { BYTE* candidateCode = inlineInfo->inlineCandidateInfo->methInfo.ILCode; - InlineContext* inlineContext = inlineInfo->iciStmt->GetInlineContext(); + InlineContext* inlineContext = inlineInfo->inlineCandidateInfo->inlinersContext; InlineResult* inlineResult = inlineInfo->inlineResult; // There should be a context for all candidates. assert(inlineContext != nullptr); + int depth = 0; for (; inlineContext != nullptr; inlineContext = inlineContext->GetParent()) @@ -101,17 +102,6 @@ PhaseStatus Compiler::fgInline() noway_assert(fgFirstBB != nullptr); - // Set the root inline context on all statements - InlineContext* rootContext = m_inlineStrategy->GetRootContext(); - - for (BasicBlock* const block : Blocks()) - { - for (Statement* const stmt : block->Statements()) - { - stmt->SetInlineContext(rootContext); - } - } - BasicBlock* block = fgFirstBB; bool madeChanges = false; @@ -123,7 +113,7 @@ PhaseStatus Compiler::fgInline() for (Statement* const stmt : block->Statements()) { -#ifdef DEBUG +#if defined(DEBUG) || defined(INLINE_DATA) // In debug builds we want the inline tree to show all failed // inlines. Some inlines may fail very early and never make it to // candidate stage. So scan the tree looking for those early failures. @@ -234,7 +224,7 @@ PhaseStatus Compiler::fgInline() return madeChanges ? PhaseStatus::MODIFIED_EVERYTHING : PhaseStatus::MODIFIED_NOTHING; } -#ifdef DEBUG +#if defined(DEBUG) || defined(INLINE_DATA) //------------------------------------------------------------------------ // fgFindNonInlineCandidate: tree walk helper to ensure that a tree node @@ -283,7 +273,7 @@ void Compiler::fgNoteNonInlineCandidate(Statement* stmt, GenTreeCall* call) return; } - InlineResult inlineResult(this, call, nullptr, "fgNotInlineCandidate"); + InlineResult inlineResult(this, call, nullptr, "fgNoteNonInlineCandidate"); InlineObservation currentObservation = InlineObservation::CALLSITE_NOT_CANDIDATE; // Try and recover the reason left behind when the jit decided @@ -301,8 +291,7 @@ void Compiler::fgNoteNonInlineCandidate(Statement* stmt, GenTreeCall* call) if (call->gtCallType == CT_USER_FUNC) { - // Create InlineContext for the failure - m_inlineStrategy->NewFailure(stmt, &inlineResult); + m_inlineStrategy->NewContext(call->gtInlineContext, stmt, call)->SetFailed(&inlineResult); } } @@ -863,7 +852,7 @@ Compiler::fgWalkResult Compiler::fgDebugCheckInlineCandidates(GenTree** pTree, f #endif // DEBUG -void Compiler::fgInvokeInlineeCompiler(GenTreeCall* call, InlineResult* inlineResult) +void Compiler::fgInvokeInlineeCompiler(GenTreeCall* call, InlineResult* inlineResult, InlineContext** createdContext) { noway_assert(call->gtOper == GT_CALL); noway_assert((call->gtFlags & GTF_CALL_INLINE_CANDIDATE) != 0); @@ -945,6 +934,14 @@ void Compiler::fgInvokeInlineeCompiler(GenTreeCall* call, InlineResult* inlineRe { pParam->inlineInfo->InlineRoot = pParam->pThis->impInlineInfo->InlineRoot; } + + // The inline context is part of debug info and must be created + // before we start creating statements; we lazily create it as + // late as possible, which is here. + pParam->inlineInfo->inlineContext = + pParam->inlineInfo->InlineRoot->m_inlineStrategy + ->NewContext(pParam->inlineInfo->inlineCandidateInfo->inlinersContext, + pParam->inlineInfo->iciStmt, pParam->inlineInfo->iciCall); pParam->inlineInfo->argCnt = pParam->inlineCandidateInfo->methInfo.args.totalILArgs(); pParam->inlineInfo->tokenLookupContextHandle = pParam->inlineCandidateInfo->exactContextHnd; @@ -960,7 +957,6 @@ void Compiler::fgInvokeInlineeCompiler(GenTreeCall* call, InlineResult* inlineRe compileFlagsForInlinee.Clear(JitFlags::JIT_FLAG_BBINSTR); compileFlagsForInlinee.Clear(JitFlags::JIT_FLAG_PROF_ENTERLEAVE); compileFlagsForInlinee.Clear(JitFlags::JIT_FLAG_DEBUG_EnC); - compileFlagsForInlinee.Clear(JitFlags::JIT_FLAG_DEBUG_INFO); compileFlagsForInlinee.Clear(JitFlags::JIT_FLAG_REVERSE_PINVOKE); compileFlagsForInlinee.Clear(JitFlags::JIT_FLAG_TRACK_TRANSITIONS); @@ -1011,6 +1007,8 @@ void Compiler::fgInvokeInlineeCompiler(GenTreeCall* call, InlineResult* inlineRe } } + *createdContext = inlineInfo.inlineContext; + if (inlineResult->IsFailure()) { return; @@ -1117,16 +1115,8 @@ void Compiler::fgInsertInlineeBlocks(InlineInfo* pInlineInfo) #endif // DEBUG - // Create a new inline context and mark the inlined statements with it - InlineContext* calleeContext = m_inlineStrategy->NewSuccess(pInlineInfo); - - for (BasicBlock* const block : InlineeCompiler->Blocks()) - { - for (Statement* const stmt : block->Statements()) - { - stmt->SetInlineContext(calleeContext); - } - } + // Mark success. + pInlineInfo->inlineContext->SetSucceeded(pInlineInfo); // Prepend statements Statement* stmtAfter = fgInlinePrependStatements(pInlineInfo); @@ -1281,9 +1271,10 @@ void Compiler::fgInsertInlineeBlocks(InlineInfo* pInlineInfo) block->copyEHRegion(iciBlock); block->bbFlags |= iciBlock->bbFlags & BBF_BACKWARD_JUMP; - if (iciStmt->GetILOffsetX() != BAD_IL_OFFSET) + DebugInfo di = iciStmt->GetDebugInfo().GetRoot(); + if (di.IsValid()) { - block->bbCodeOffs = jitGetILoffs(iciStmt->GetILOffsetX()); + block->bbCodeOffs = di.GetLocation().GetOffset(); block->bbCodeOffsEnd = block->bbCodeOffs + 1; // TODO: is code size of 1 some magic number for inlining? } else @@ -1469,13 +1460,13 @@ void Compiler::fgInsertInlineeBlocks(InlineInfo* pInlineInfo) Statement* Compiler::fgInlinePrependStatements(InlineInfo* inlineInfo) { - BasicBlock* block = inlineInfo->iciBlock; - Statement* callStmt = inlineInfo->iciStmt; - IL_OFFSETX callILOffset = callStmt->GetILOffsetX(); - Statement* postStmt = callStmt->GetNextStmt(); - Statement* afterStmt = callStmt; // afterStmt is the place where the new statements should be inserted after. - Statement* newStmt = nullptr; - GenTreeCall* call = inlineInfo->iciCall->AsCall(); + BasicBlock* block = inlineInfo->iciBlock; + Statement* callStmt = inlineInfo->iciStmt; + const DebugInfo& callDI = callStmt->GetDebugInfo(); + Statement* postStmt = callStmt->GetNextStmt(); + Statement* afterStmt = callStmt; // afterStmt is the place where the new statements should be inserted after. + Statement* newStmt = nullptr; + GenTreeCall* call = inlineInfo->iciCall->AsCall(); noway_assert(call->gtOper == GT_CALL); @@ -1589,8 +1580,7 @@ Statement* Compiler::fgInlinePrependStatements(InlineInfo* inlineInfo) // argTmpNum here since in-linee compiler instance // would have iterated over these and marked them // accordingly. - impAssignTempGen(tmpNum, argNode, structHnd, (unsigned)CHECK_SPILL_NONE, &afterStmt, callILOffset, - block); + impAssignTempGen(tmpNum, argNode, structHnd, (unsigned)CHECK_SPILL_NONE, &afterStmt, callDI, block); // We used to refine the temp type here based on // the actual arg, but we now do this up front, when @@ -1639,7 +1629,7 @@ Statement* Compiler::fgInlinePrependStatements(InlineInfo* inlineInfo) // Don't put GT_OBJ node under a GT_COMMA. // Codegen can't deal with it. // Just hang the address here in case there are side-effect. - newStmt = gtNewStmt(gtUnusedValNode(argNode->AsOp()->gtOp1), callILOffset); + newStmt = gtNewStmt(gtUnusedValNode(argNode->AsOp()->gtOp1), callDI); } else { @@ -1715,7 +1705,7 @@ Statement* Compiler::fgInlinePrependStatements(InlineInfo* inlineInfo) // just append the arg node as an unused value. if (newStmt == nullptr) { - newStmt = gtNewStmt(gtUnusedValNode(argNode), callILOffset); + newStmt = gtNewStmt(gtUnusedValNode(argNode), callDI); } fgInsertStmtAfter(block, afterStmt, newStmt); @@ -1751,7 +1741,7 @@ Statement* Compiler::fgInlinePrependStatements(InlineInfo* inlineInfo) CORINFO_CLASS_HANDLE exactClass = eeGetClassFromContext(inlineInfo->inlineCandidateInfo->exactContextHnd); tree = fgGetSharedCCtor(exactClass); - newStmt = gtNewStmt(tree, callILOffset); + newStmt = gtNewStmt(tree, callDI); fgInsertStmtAfter(block, afterStmt, newStmt); afterStmt = newStmt; } @@ -1759,7 +1749,7 @@ Statement* Compiler::fgInlinePrependStatements(InlineInfo* inlineInfo) // Insert the nullcheck statement now. if (nullcheck) { - newStmt = gtNewStmt(nullcheck, callILOffset); + newStmt = gtNewStmt(nullcheck, callDI); fgInsertStmtAfter(block, afterStmt, newStmt); afterStmt = newStmt; } @@ -1812,7 +1802,7 @@ Statement* Compiler::fgInlinePrependStatements(InlineInfo* inlineInfo) // Unsafe value cls check is not needed here since in-linee compiler instance would have // iterated over locals and marked accordingly. impAssignTempGen(tmpNum, gtNewZeroConNode(genActualType(lclTyp)), NO_CLASS_HANDLE, - (unsigned)CHECK_SPILL_NONE, &afterStmt, callILOffset, block); + (unsigned)CHECK_SPILL_NONE, &afterStmt, callDI, block); } else { @@ -1821,7 +1811,7 @@ Statement* Compiler::fgInlinePrependStatements(InlineInfo* inlineInfo) false, // isVolatile false); // not copyBlock - newStmt = gtNewStmt(tree, callILOffset); + newStmt = gtNewStmt(tree, callDI); fgInsertStmtAfter(block, afterStmt, newStmt); afterStmt = newStmt; } @@ -1836,15 +1826,6 @@ Statement* Compiler::fgInlinePrependStatements(InlineInfo* inlineInfo) } } - // Update any newly added statements with the appropriate context. - InlineContext* context = callStmt->GetInlineContext(); - assert(context != nullptr); - for (Statement* addedStmt = callStmt->GetNextStmt(); addedStmt != postStmt; addedStmt = addedStmt->GetNextStmt()) - { - assert(addedStmt->GetInlineContext() == nullptr); - addedStmt->SetInlineContext(context); - } - return afterStmt; } @@ -1881,7 +1862,7 @@ void Compiler::fgInlineAppendStatements(InlineInfo* inlineInfo, BasicBlock* bloc JITDUMP("fgInlineAppendStatements: nulling out gc ref inlinee locals.\n"); Statement* callStmt = inlineInfo->iciStmt; - IL_OFFSETX callILOffset = callStmt->GetILOffsetX(); + const DebugInfo& callDI = callStmt->GetDebugInfo(); CORINFO_METHOD_INFO* InlineeMethodInfo = InlineeCompiler->info.compMethodInfo; const unsigned lclCnt = InlineeMethodInfo->locals.numArgs; InlLclVarInfo* lclVarInfo = inlineInfo->lclVarInfo; @@ -1930,7 +1911,7 @@ void Compiler::fgInlineAppendStatements(InlineInfo* inlineInfo, BasicBlock* bloc // Assign null to the local. GenTree* nullExpr = gtNewTempAssign(tmpNum, gtNewZeroConNode(lclTyp)); - Statement* nullStmt = gtNewStmt(nullExpr, callILOffset); + Statement* nullStmt = gtNewStmt(nullExpr, callDI); if (stmtAfter == nullptr) { diff --git a/src/coreclr/jit/fgopt.cpp b/src/coreclr/jit/fgopt.cpp index 59f326b0fc13a..c0c4158b7800d 100644 --- a/src/coreclr/jit/fgopt.cpp +++ b/src/coreclr/jit/fgopt.cpp @@ -3953,7 +3953,7 @@ bool Compiler::fgOptimizeSwitchJumps() // GenTree* const dominantCaseCompare = gtNewOperNode(GT_EQ, TYP_INT, switchValue, gtNewIconNode(dominantCase)); GenTree* const jmpTree = gtNewOperNode(GT_JTRUE, TYP_VOID, dominantCaseCompare); - Statement* const jmpStmt = fgNewStmtFromTree(jmpTree, switchStmt->GetILOffsetX()); + Statement* const jmpStmt = fgNewStmtFromTree(jmpTree, switchStmt->GetDebugInfo()); fgInsertStmtAtEnd(block, jmpStmt); // Reattach switch value to the switch. This may introduce a comma diff --git a/src/coreclr/jit/fgprofile.cpp b/src/coreclr/jit/fgprofile.cpp index d301166966fbf..cc08d29bcb537 100644 --- a/src/coreclr/jit/fgprofile.cpp +++ b/src/coreclr/jit/fgprofile.cpp @@ -902,6 +902,26 @@ void Compiler::WalkSpanningTree(SpanningTreeVisitor* visitor) } } +// Map a block into its schema key we will use for storing basic blocks. +// +static int32_t EfficientEdgeCountBlockToKey(BasicBlock* block) +{ + static const int IS_INTERNAL_BLOCK = (int32_t)0x80000000; + int32_t key = (int32_t)block->bbCodeOffs; + // We may see empty BBJ_NONE BBF_INTERNAL blocks that were added + // by fgNormalizeEH. + // + // We'll use their bbNum in place of IL offset, and set + // a high bit as a "flag" + // + if ((block->bbFlags & BBF_INTERNAL) == BBF_INTERNAL) + { + key = block->bbNum | IS_INTERNAL_BLOCK; + } + + return key; +} + //------------------------------------------------------------------------ // EfficientEdgeCountInstrumentor: instrumentor that adds a counter to // selective edges. @@ -1086,35 +1106,19 @@ void EfficientEdgeCountInstrumentor::BuildSchemaElements(BasicBlock* block, Sche assert(probe->schemaIndex == -1); probe->schemaIndex = (int)schema.size(); - // Assign the current block's IL offset into the profile data. - // Use the "other" field to hold the target block IL offset. - // - int32_t sourceOffset = (int32_t)block->bbCodeOffs; - int32_t targetOffset = (int32_t)target->bbCodeOffs; - - // We may see empty BBJ_NONE BBF_INTERNAL blocks that were added - // by fgNormalizeEH. - // - // We'll use their bbNum in place of IL offset, and set - // a high bit as a "flag" + // Normally we use the the offset of the block in the schema, but for certain + // blocks we do not have any information we can use and need to use internal BB numbers. // - if ((block->bbFlags & BBF_INTERNAL) == BBF_INTERNAL) - { - sourceOffset = block->bbNum | IL_OFFSETX_CALLINSTRUCTIONBIT; - } - - if ((target->bbFlags & BBF_INTERNAL) == BBF_INTERNAL) - { - targetOffset = target->bbNum | IL_OFFSETX_CALLINSTRUCTIONBIT; - } + int32_t sourceKey = EfficientEdgeCountBlockToKey(block); + int32_t targetKey = EfficientEdgeCountBlockToKey(target); ICorJitInfo::PgoInstrumentationSchema schemaElem; schemaElem.Count = 1; - schemaElem.Other = targetOffset; + schemaElem.Other = targetKey; schemaElem.InstrumentationKind = JitConfig.JitCollect64BitCounts() ? ICorJitInfo::PgoInstrumentationKind::EdgeLongCount : ICorJitInfo::PgoInstrumentationKind::EdgeIntCount; - schemaElem.ILOffset = sourceOffset; + schemaElem.ILOffset = sourceKey; schemaElem.Offset = 0; schema.push_back(schemaElem); @@ -1287,7 +1291,7 @@ class BuildClassProbeSchemaGen schemaElem.InstrumentationKind = JitConfig.JitCollect64BitCounts() ? ICorJitInfo::PgoInstrumentationKind::TypeHandleHistogramLongCount : ICorJitInfo::PgoInstrumentationKind::TypeHandleHistogramIntCount; - schemaElem.ILOffset = jitGetILoffs(call->gtClassProfileCandidateInfo->ilOffset); + schemaElem.ILOffset = (int32_t)call->gtClassProfileCandidateInfo->ilOffset; schemaElem.Offset = 0; m_schema.push_back(schemaElem); @@ -1988,19 +1992,6 @@ class EfficientEdgeCountReconstructor : public SpanningTreeVisitor unsigned m_unknownEdges; unsigned m_zeroEdges; - // Map a block into its schema key. - // - static int32_t BlockToKey(BasicBlock* block) - { - int32_t key = (int32_t)block->bbCodeOffs; - if ((block->bbFlags & BBF_INTERNAL) == BBF_INTERNAL) - { - key = block->bbNum | IL_OFFSETX_CALLINSTRUCTIONBIT; - } - - return key; - } - // Map correlating block keys to blocks. // typedef JitHashTable, BasicBlock*> KeyToBlockMap; @@ -2018,7 +2009,8 @@ class EfficientEdgeCountReconstructor : public SpanningTreeVisitor } EdgeKey(BasicBlock* sourceBlock, BasicBlock* targetBlock) - : m_sourceKey(BlockToKey(sourceBlock)), m_targetKey(BlockToKey(targetBlock)) + : m_sourceKey(EfficientEdgeCountBlockToKey(sourceBlock)) + , m_targetKey(EfficientEdgeCountBlockToKey(targetBlock)) { } @@ -2241,7 +2233,7 @@ void EfficientEdgeCountReconstructor::Prepare() // for (BasicBlock* const block : m_comp->Blocks()) { - m_keyToBlockMap.Set(BlockToKey(block), block); + m_keyToBlockMap.Set(EfficientEdgeCountBlockToKey(block), block); BlockInfo* const info = new (m_allocator) BlockInfo(); SetBlockInfo(block, info); diff --git a/src/coreclr/jit/fgstmt.cpp b/src/coreclr/jit/fgstmt.cpp index 67da44726f994..24140b3fc52b9 100644 --- a/src/coreclr/jit/fgstmt.cpp +++ b/src/coreclr/jit/fgstmt.cpp @@ -101,14 +101,14 @@ void Compiler::fgInsertStmtAtBeg(BasicBlock* block, Statement* stmt) // Arguments: // block - the block into which 'tree' will be inserted; // tree - the tree to be inserted. -// offs - the offset to use for the statement +// di - the debug info to use for the new statement. // // Return Value: // The new created statement with `tree` inserted into `block`. // -Statement* Compiler::fgNewStmtAtBeg(BasicBlock* block, GenTree* tree, IL_OFFSETX offs) +Statement* Compiler::fgNewStmtAtBeg(BasicBlock* block, GenTree* tree, const DebugInfo& di) { - Statement* stmt = gtNewStmt(tree, offs); + Statement* stmt = gtNewStmt(tree, di); fgInsertStmtAtBeg(block, stmt); return stmt; } @@ -154,7 +154,7 @@ void Compiler::fgInsertStmtAtEnd(BasicBlock* block, Statement* stmt) // Arguments: // block - the block into which 'stmt' will be inserted; // tree - the tree to be inserted. -// offs - the offset to use for the statement +// di - the debug info to use for the new statement. // // Return Value: // The new created statement with `tree` inserted into `block`. @@ -162,9 +162,9 @@ void Compiler::fgInsertStmtAtEnd(BasicBlock* block, Statement* stmt) // Note: // If the block can be a conditional block, use fgNewStmtNearEnd. // -Statement* Compiler::fgNewStmtAtEnd(BasicBlock* block, GenTree* tree, IL_OFFSETX offs) +Statement* Compiler::fgNewStmtAtEnd(BasicBlock* block, GenTree* tree, const DebugInfo& di) { - Statement* stmt = gtNewStmt(tree, offs); + Statement* stmt = gtNewStmt(tree, di); fgInsertStmtAtEnd(block, stmt); return stmt; } @@ -243,13 +243,14 @@ void Compiler::fgInsertStmtNearEnd(BasicBlock* block, Statement* stmt) // Arguments: // block - the block into which 'stmt' will be inserted; // tree - the tree to be inserted. +// di - the debug info to use for the new statement. // // Return Value: // The new created statement with `tree` inserted into `block`. // -Statement* Compiler::fgNewStmtNearEnd(BasicBlock* block, GenTree* tree, IL_OFFSETX offs) +Statement* Compiler::fgNewStmtNearEnd(BasicBlock* block, GenTree* tree, const DebugInfo& di) { - Statement* stmt = gtNewStmt(tree, offs); + Statement* stmt = gtNewStmt(tree, di); fgInsertStmtNearEnd(block, stmt); return stmt; } @@ -381,9 +382,9 @@ Statement* Compiler::fgInsertStmtListAfter(BasicBlock* block, Statement* stmtAft * * Create a new statement from tree and wire the links up. */ -Statement* Compiler::fgNewStmtFromTree(GenTree* tree, BasicBlock* block, IL_OFFSETX offs) +Statement* Compiler::fgNewStmtFromTree(GenTree* tree, BasicBlock* block, const DebugInfo& di) { - Statement* stmt = gtNewStmt(tree, offs); + Statement* stmt = gtNewStmt(tree, di); if (fgStmtListThreaded) { @@ -403,17 +404,17 @@ Statement* Compiler::fgNewStmtFromTree(GenTree* tree, BasicBlock* block, IL_OFFS Statement* Compiler::fgNewStmtFromTree(GenTree* tree) { - return fgNewStmtFromTree(tree, nullptr, BAD_IL_OFFSET); + return fgNewStmtFromTree(tree, nullptr, DebugInfo()); } Statement* Compiler::fgNewStmtFromTree(GenTree* tree, BasicBlock* block) { - return fgNewStmtFromTree(tree, block, BAD_IL_OFFSET); + return fgNewStmtFromTree(tree, block, DebugInfo()); } -Statement* Compiler::fgNewStmtFromTree(GenTree* tree, IL_OFFSETX offs) +Statement* Compiler::fgNewStmtFromTree(GenTree* tree, const DebugInfo& di) { - return fgNewStmtFromTree(tree, nullptr, offs); + return fgNewStmtFromTree(tree, nullptr, di); } //------------------------------------------------------------------------ @@ -457,7 +458,7 @@ void Compiler::fgRemoveStmt(BasicBlock* block, Statement* stmt DEBUGARG(bool isU } #endif // DEBUG - if (opts.compDbgCode && stmt->GetPrevStmt() != stmt && stmt->GetILOffsetX() != BAD_IL_OFFSET) + if (opts.compDbgCode && stmt->GetPrevStmt() != stmt && stmt->GetDebugInfo().IsValid()) { /* TODO: For debuggable code, should we remove significant statement boundaries. Or should we leave a GT_NO_OP in its place? */ diff --git a/src/coreclr/jit/flowgraph.cpp b/src/coreclr/jit/flowgraph.cpp index 5742d8659ac12..b33351cd391b9 100644 --- a/src/coreclr/jit/flowgraph.cpp +++ b/src/coreclr/jit/flowgraph.cpp @@ -278,7 +278,7 @@ BasicBlock* Compiler::fgCreateGCPoll(GCPollType pollType, BasicBlock* block) if (nextStmt != nullptr) { // Is it possible for gtNextStmt to be NULL? - newStmt->SetILOffsetX(nextStmt->GetILOffsetX()); + newStmt->SetDebugInfo(nextStmt->GetDebugInfo()); } } diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 81f2c5e9205ea..e410455365baf 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -6349,13 +6349,13 @@ GenTree* Compiler::gtNewSIMDVectorZero(var_types simdType, CorInfoType simdBaseJ } #endif // FEATURE_SIMD -GenTreeCall* Compiler::gtNewIndCallNode(GenTree* addr, var_types type, GenTreeCall::Use* args, IL_OFFSETX ilOffset) +GenTreeCall* Compiler::gtNewIndCallNode(GenTree* addr, var_types type, GenTreeCall::Use* args, const DebugInfo& di) { - return gtNewCallNode(CT_INDIRECT, (CORINFO_METHOD_HANDLE)addr, type, args, ilOffset); + return gtNewCallNode(CT_INDIRECT, (CORINFO_METHOD_HANDLE)addr, type, args, di); } GenTreeCall* Compiler::gtNewCallNode( - gtCallTypes callType, CORINFO_METHOD_HANDLE callHnd, var_types type, GenTreeCall::Use* args, IL_OFFSETX ilOffset) + gtCallTypes callType, CORINFO_METHOD_HANDLE callHnd, var_types type, GenTreeCall::Use* args, const DebugInfo& di) { GenTreeCall* node = new (this, GT_CALL) GenTreeCall(genActualType(type)); @@ -6399,14 +6399,15 @@ GenTreeCall* Compiler::gtNewCallNode( // These get updated after call node is built. node->gtInlineObservation = InlineObservation::CALLEE_UNUSED_INITIAL; node->gtRawILOffset = BAD_IL_OFFSET; + node->gtInlineContext = compInlineContext; #endif // Spec: Managed Retval sequence points needs to be generated while generating debug info for debuggable code. // // Implementation note: if not generating MRV info genCallSite2ILOffsetMap will be NULL and - // codegen will pass BAD_IL_OFFSET as IL offset of a call node to emitter, which will cause emitter + // codegen will pass DebugInfo() to emitter, which will cause emitter // not to emit IP mapping entry. - if (opts.compDbgCode && opts.compDbgInfo) + if (opts.compDbgCode && opts.compDbgInfo && di.IsValid()) { // Managed Retval - IL offset of the call. This offset is used to emit a // CALL_INSTRUCTION type sequence point while emitting corresponding native call. @@ -6418,14 +6419,14 @@ GenTreeCall* Compiler::gtNewCallNode( // // b) (Opt) Add new sequence points only if requested by debugger through // a new boundary type - ICorDebugInfo::BoundaryTypes - if (genCallSite2ILOffsetMap == nullptr) + if (genCallSite2DebugInfoMap == nullptr) { - genCallSite2ILOffsetMap = new (getAllocator()) CallSiteILOffsetTable(getAllocator()); + genCallSite2DebugInfoMap = new (getAllocator()) CallSiteDebugInfoTable(getAllocator()); } // Make sure that there are no duplicate entries for a given call node - assert(!genCallSite2ILOffsetMap->Lookup(node)); - genCallSite2ILOffsetMap->Set(node, ilOffset); + assert(!genCallSite2DebugInfoMap->Lookup(node)); + genCallSite2DebugInfoMap->Set(node, di); } // Initialize gtOtherRegs @@ -6446,7 +6447,7 @@ GenTreeCall* Compiler::gtNewCallNode( return node; } -GenTreeLclVar* Compiler::gtNewLclvNode(unsigned lnum, var_types type DEBUGARG(IL_OFFSETX ILoffs)) +GenTreeLclVar* Compiler::gtNewLclvNode(unsigned lnum, var_types type DEBUGARG(IL_OFFSET offs)) { assert(type != TYP_VOID); // We need to ensure that all struct values are normalized. @@ -6466,7 +6467,7 @@ GenTreeLclVar* Compiler::gtNewLclvNode(unsigned lnum, var_types type DEBUGARG(IL assert((type == varDsc->lvType) || simd12ToSimd16Widening || (lvaIsImplicitByRefLocal(lnum) && fgGlobalMorph && (varDsc->lvType == TYP_BYREF))); } - GenTreeLclVar* node = new (this, GT_LCL_VAR) GenTreeLclVar(GT_LCL_VAR, type, lnum DEBUGARG(ILoffs)); + GenTreeLclVar* node = new (this, GT_LCL_VAR) GenTreeLclVar(GT_LCL_VAR, type, lnum DEBUGARG(offs)); /* Cannot have this assert because the inliner uses this function * to add temporaries */ @@ -6476,7 +6477,7 @@ GenTreeLclVar* Compiler::gtNewLclvNode(unsigned lnum, var_types type DEBUGARG(IL return node; } -GenTreeLclVar* Compiler::gtNewLclLNode(unsigned lnum, var_types type DEBUGARG(IL_OFFSETX ILoffs)) +GenTreeLclVar* Compiler::gtNewLclLNode(unsigned lnum, var_types type DEBUGARG(IL_OFFSET offs)) { // We need to ensure that all struct values are normalized. // It might be nice to assert this in general, but we have assignments of int to long. @@ -6491,7 +6492,7 @@ GenTreeLclVar* Compiler::gtNewLclLNode(unsigned lnum, var_types type DEBUGARG(IL // This local variable node may later get transformed into a large node assert(GenTree::s_gtNodeSizes[LargeOpOpcode()] > GenTree::s_gtNodeSizes[GT_LCL_VAR]); GenTreeLclVar* node = - new (this, LargeOpOpcode()) GenTreeLclVar(GT_LCL_VAR, type, lnum DEBUGARG(ILoffs) DEBUGARG(/*largeNode*/ true)); + new (this, LargeOpOpcode()) GenTreeLclVar(GT_LCL_VAR, type, lnum DEBUGARG(offs) DEBUGARG(/*largeNode*/ true)); return node; } @@ -8294,7 +8295,8 @@ GenTreeCall* Compiler::gtCloneExprCallHelper(GenTreeCall* tree, #if defined(DEBUG) || defined(INLINE_DATA) copy->gtInlineObservation = tree->gtInlineObservation; - copy->gtRawILOffset = tree->AsCall()->gtRawILOffset; + copy->gtRawILOffset = tree->gtRawILOffset; + copy->gtInlineContext = tree->gtInlineContext; #endif copy->CopyOtherRegFlags(tree); @@ -11477,13 +11479,13 @@ void Compiler::gtDispLeaf(GenTree* tree, IndentStack* indentStack) case GT_IL_OFFSET: printf(" IL offset: "); - if (tree->AsILOffset()->gtStmtILoffsx == BAD_IL_OFFSET) + if (!tree->AsILOffset()->gtStmtDI.IsValid()) { printf("???"); } else { - printf("0x%x", jitGetILoffs(tree->AsILOffset()->gtStmtILoffsx)); + printf("0x%x", tree->AsILOffset()->gtStmtDI.GetLocation().GetOffset()); } break; @@ -12344,28 +12346,40 @@ void Compiler::gtDispStmt(Statement* stmt, const char* msg /* = nullptr */) printf("%s ", msg); } printStmtID(stmt); - IL_OFFSETX firstILOffsx = stmt->GetILOffsetX(); - printf(" (IL "); - if (firstILOffsx == BAD_IL_OFFSET) + printf(" ( "); + const DebugInfo& di = stmt->GetDebugInfo(); + // For statements in the root we display just the location without the + // inline context info. + if (di.GetInlineContext() == nullptr || di.GetInlineContext()->IsRoot()) { - printf(" ???"); + di.GetLocation().Dump(); } else { - printf("0x%03X", jitGetILoffs(firstILOffsx)); + stmt->GetDebugInfo().Dump(false); } - printf("..."); + printf(" ... "); IL_OFFSET lastILOffs = stmt->GetLastILOffset(); if (lastILOffs == BAD_IL_OFFSET) { - printf(" ???"); + printf("???"); } else { printf("0x%03X", lastILOffs); } - printf(")\n"); + + printf(" )"); + + DebugInfo par; + if (stmt->GetDebugInfo().GetParent(&par)) + { + printf(" <- "); + par.Dump(true); + } + + printf("\n"); } gtDispTree(stmt->GetRootNode()); } @@ -15249,7 +15263,7 @@ GenTree* Compiler::gtFoldExprConst(GenTree* tree) // May set compFloatingPointUsed. GenTree* Compiler::gtNewTempAssign( - unsigned tmp, GenTree* val, Statement** pAfterStmt, IL_OFFSETX ilOffset, BasicBlock* block) + unsigned tmp, GenTree* val, Statement** pAfterStmt, const DebugInfo& di, BasicBlock* block) { // Self-assignment is a nop. if (val->OperGet() == GT_LCL_VAR && val->AsLclVarCommon()->GetLclNum() == tmp) @@ -15391,7 +15405,7 @@ GenTree* Compiler::gtNewTempAssign( } dest->gtFlags |= GTF_DONT_CSE; valx->gtFlags |= GTF_DONT_CSE; - asg = impAssignStruct(dest, val, valStructHnd, (unsigned)CHECK_SPILL_NONE, pAfterStmt, ilOffset, block); + asg = impAssignStruct(dest, val, valStructHnd, (unsigned)CHECK_SPILL_NONE, pAfterStmt, di, block); } else { diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index 091c2464e6950..ec3d178a95115 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -28,6 +28,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX #include "simd.h" #include "namedintrinsiclist.h" #include "layout.h" +#include "debuginfo.h" // Debugging GenTree is much easier if we add a magic virtual function to make the debugger able to figure out what type // it's got. This is enabled by default in DEBUG. To enable it in RET builds (temporarily!), you need to change the @@ -4866,6 +4867,11 @@ struct GenTreeCall final : public GenTree // IL offset of the call wrt its parent method. IL_OFFSET gtRawILOffset; + + // In DEBUG we report even non inline candidates in the inline tree in + // fgNoteNonInlineCandidate. We need to keep around the inline context for + // this as normally it's part of the candidate info. + class InlineContext* gtInlineContext; #endif // defined(DEBUG) || defined(INLINE_DATA) bool IsHelperCall() const @@ -6132,18 +6138,17 @@ struct GenTreeRetExpr : public GenTree #endif }; -class InlineContext; - +// In LIR there are no longer statements so debug information is inserted linearly using these nodes. struct GenTreeILOffset : public GenTree { - IL_OFFSETX gtStmtILoffsx; // instr offset (if available) + DebugInfo gtStmtDI; // debug info #ifdef DEBUG IL_OFFSET gtStmtLastILoffs; // instr offset at end of stmt #endif - GenTreeILOffset(IL_OFFSETX offset DEBUGARG(IL_OFFSET lastOffset = BAD_IL_OFFSET)) + GenTreeILOffset(const DebugInfo& di DEBUGARG(IL_OFFSET lastOffset = BAD_IL_OFFSET)) : GenTree(GT_IL_OFFSET, TYP_VOID) - , gtStmtILoffsx(offset) + , gtStmtDI(di) #ifdef DEBUG , gtStmtLastILoffs(lastOffset) #endif @@ -6216,13 +6221,11 @@ class GenTreeList struct Statement { public: - Statement(GenTree* expr, IL_OFFSETX offset DEBUGARG(unsigned stmtID)) + Statement(GenTree* expr DEBUGARG(unsigned stmtID)) : m_rootNode(expr) , m_treeList(nullptr) , m_next(nullptr) , m_prev(nullptr) - , m_inlineContext(nullptr) - , m_ILOffsetX(offset) #ifdef DEBUG , m_lastILOffset(BAD_IL_OFFSET) , m_stmtID(stmtID) @@ -6265,24 +6268,15 @@ struct Statement return GenTreeList(GetTreeList()); } - InlineContext* GetInlineContext() const - { - return m_inlineContext; - } - - void SetInlineContext(InlineContext* inlineContext) + const DebugInfo& GetDebugInfo() const { - m_inlineContext = inlineContext; + return m_debugInfo; } - IL_OFFSETX GetILOffsetX() const + void SetDebugInfo(const DebugInfo& di) { - return m_ILOffsetX; - } - - void SetILOffsetX(IL_OFFSETX offsetX) - { - m_ILOffsetX = offsetX; + m_debugInfo = di; + di.Validate(); } #ifdef DEBUG @@ -6363,9 +6357,7 @@ struct Statement Statement* m_next; Statement* m_prev; - InlineContext* m_inlineContext; // The inline context for this statement. - - IL_OFFSETX m_ILOffsetX; // The instr offset (if available). + DebugInfo m_debugInfo; #ifdef DEBUG IL_OFFSET m_lastILOffset; // The instr offset at the end of this statement. diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 98c5354269824..78177c7057a6d 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -615,10 +615,12 @@ inline void Compiler::impAppendStmt(Statement* stmt, unsigned chkLevel) impMarkContiguousSIMDFieldAssignments(stmt); #endif - /* Once we set impCurStmtOffs in an appended tree, we are ready to - report the following offsets. So reset impCurStmtOffs */ + // Once we set the current offset as debug info in an appended tree, we are + // ready to report the following offsets. Note that we need to compare + // offsets here instead of debug info, since we do not set the "is call" + // debug in impCurStmtDI. - if (impLastStmt->GetILOffsetX() == impCurStmtOffs) + if (impLastStmt->GetDebugInfo().GetLocation().GetOffset() == impCurStmtDI.GetLocation().GetOffset()) { impCurStmtOffsSet(BAD_IL_OFFSET); } @@ -713,13 +715,13 @@ inline void Compiler::impInsertStmtBefore(Statement* stmt, Statement* stmtBefore * Return the newly created statement. */ -Statement* Compiler::impAppendTree(GenTree* tree, unsigned chkLevel, IL_OFFSETX offset) +Statement* Compiler::impAppendTree(GenTree* tree, unsigned chkLevel, const DebugInfo& di) { assert(tree); /* Allocate an 'expression statement' node */ - Statement* stmt = gtNewStmt(tree, offset); + Statement* stmt = gtNewStmt(tree, di); /* Append the statement to the current block's stmt list */ @@ -733,11 +735,11 @@ Statement* Compiler::impAppendTree(GenTree* tree, unsigned chkLevel, IL_OFFSETX * Insert the given expression tree before "stmtBefore" */ -void Compiler::impInsertTreeBefore(GenTree* tree, IL_OFFSETX offset, Statement* stmtBefore) +void Compiler::impInsertTreeBefore(GenTree* tree, const DebugInfo& di, Statement* stmtBefore) { /* Allocate an 'expression statement' node */ - Statement* stmt = gtNewStmt(tree, offset); + Statement* stmt = gtNewStmt(tree, di); /* Append the statement to the current block's stmt list */ @@ -750,12 +752,12 @@ void Compiler::impInsertTreeBefore(GenTree* tree, IL_OFFSETX offset, Statement* * curLevel is the stack level for which the spill to the temp is being done. */ -void Compiler::impAssignTempGen(unsigned tmp, - GenTree* val, - unsigned curLevel, - Statement** pAfterStmt, /* = NULL */ - IL_OFFSETX ilOffset, /* = BAD_IL_OFFSET */ - BasicBlock* block /* = NULL */ +void Compiler::impAssignTempGen(unsigned tmp, + GenTree* val, + unsigned curLevel, + Statement** pAfterStmt, /* = NULL */ + const DebugInfo& di, /* = DebugInfo() */ + BasicBlock* block /* = NULL */ ) { GenTree* asg = gtNewTempAssign(tmp, val); @@ -764,13 +766,13 @@ void Compiler::impAssignTempGen(unsigned tmp, { if (pAfterStmt) { - Statement* asgStmt = gtNewStmt(asg, ilOffset); + Statement* asgStmt = gtNewStmt(asg, di); fgInsertStmtAfter(block, *pAfterStmt, asgStmt); *pAfterStmt = asgStmt; } else { - impAppendTree(asg, curLevel, impCurStmtOffs); + impAppendTree(asg, curLevel, impCurStmtDI); } } } @@ -784,7 +786,7 @@ void Compiler::impAssignTempGen(unsigned tmpNum, CORINFO_CLASS_HANDLE structType, unsigned curLevel, Statement** pAfterStmt, /* = NULL */ - IL_OFFSETX ilOffset, /* = BAD_IL_OFFSET */ + const DebugInfo& di, /* = DebugInfo() */ BasicBlock* block /* = NULL */ ) { @@ -813,7 +815,7 @@ void Compiler::impAssignTempGen(unsigned tmpNum, // calls that may not actually be required - e.g. if we only access a field of a struct. GenTree* dst = gtNewLclvNode(tmpNum, varType); - asg = impAssignStruct(dst, val, structType, curLevel, pAfterStmt, ilOffset, block); + asg = impAssignStruct(dst, val, structType, curLevel, pAfterStmt, di, block); } else { @@ -824,13 +826,13 @@ void Compiler::impAssignTempGen(unsigned tmpNum, { if (pAfterStmt) { - Statement* asgStmt = gtNewStmt(asg, ilOffset); + Statement* asgStmt = gtNewStmt(asg, di); fgInsertStmtAfter(block, *pAfterStmt, asgStmt); *pAfterStmt = asgStmt; } else { - impAppendTree(asg, curLevel, impCurStmtOffs); + impAppendTree(asg, curLevel, impCurStmtDI); } } } @@ -1194,15 +1196,16 @@ GenTree* Compiler::impAssignStruct(GenTree* dest, CORINFO_CLASS_HANDLE structHnd, unsigned curLevel, Statement** pAfterStmt, /* = nullptr */ - IL_OFFSETX ilOffset, /* = BAD_IL_OFFSET */ + const DebugInfo& di, /* = DebugInfo() */ BasicBlock* block /* = nullptr */ ) { assert(varTypeIsStruct(dest)); - if (ilOffset == BAD_IL_OFFSET) + DebugInfo usedDI = di; + if (!usedDI.IsValid()) { - ilOffset = impCurStmtOffs; + usedDI = impCurStmtDI; } while (dest->gtOper == GT_COMMA) @@ -1213,13 +1216,13 @@ GenTree* Compiler::impAssignStruct(GenTree* dest, // Append all the op1 of GT_COMMA trees before we evaluate op2 of the GT_COMMA tree. if (pAfterStmt) { - Statement* newStmt = gtNewStmt(dest->AsOp()->gtOp1, ilOffset); + Statement* newStmt = gtNewStmt(dest->AsOp()->gtOp1, usedDI); fgInsertStmtAfter(block, *pAfterStmt, newStmt); *pAfterStmt = newStmt; } else { - impAppendTree(dest->AsOp()->gtOp1, curLevel, ilOffset); // do the side effect + impAppendTree(dest->AsOp()->gtOp1, curLevel, usedDI); // do the side effect } // set dest to the second thing @@ -1249,7 +1252,7 @@ GenTree* Compiler::impAssignStruct(GenTree* dest, destAddr = gtNewOperNode(GT_ADDR, TYP_BYREF, dest); } - return (impAssignStructPtr(destAddr, src, structHnd, curLevel, pAfterStmt, ilOffset, block)); + return (impAssignStructPtr(destAddr, src, structHnd, curLevel, pAfterStmt, usedDI, block)); } //------------------------------------------------------------------------ @@ -1261,7 +1264,7 @@ GenTree* Compiler::impAssignStruct(GenTree* dest, // structHnd - handle representing the struct type // curLevel - stack level for which a spill may be being done // pAfterStmt - statement to insert any additional statements after -// ilOffset - il offset for new statements +// di - debug info for new statements // block - block to insert any additional statements in // // Return Value: @@ -1275,16 +1278,17 @@ GenTree* Compiler::impAssignStructPtr(GenTree* destAddr, CORINFO_CLASS_HANDLE structHnd, unsigned curLevel, Statement** pAfterStmt, /* = NULL */ - IL_OFFSETX ilOffset, /* = BAD_IL_OFFSET */ + const DebugInfo& di, /* = DebugInfo() */ BasicBlock* block /* = NULL */ ) { GenTree* dest = nullptr; GenTreeFlags destFlags = GTF_EMPTY; - if (ilOffset == BAD_IL_OFFSET) + DebugInfo usedDI = di; + if (!usedDI.IsValid()) { - ilOffset = impCurStmtOffs; + usedDI = impCurStmtDI; } assert(src->OperIs(GT_LCL_VAR, GT_LCL_FLD, GT_FIELD, GT_IND, GT_OBJ, GT_CALL, GT_MKREFANY, GT_RET_EXPR, GT_COMMA) || @@ -1501,13 +1505,13 @@ GenTree* Compiler::impAssignStructPtr(GenTree* destAddr, GenTree* asg = gtNewAssignNode(ptrSlot, src->AsOp()->gtOp1); if (pAfterStmt) { - Statement* newStmt = gtNewStmt(asg, ilOffset); + Statement* newStmt = gtNewStmt(asg, usedDI); fgInsertStmtAfter(block, *pAfterStmt, newStmt); *pAfterStmt = newStmt; } else { - impAppendTree(asg, curLevel, ilOffset); + impAppendTree(asg, curLevel, usedDI); } // return the assign of the type value, to be appended @@ -1520,14 +1524,14 @@ GenTree* Compiler::impAssignStructPtr(GenTree* destAddr, if (pAfterStmt) { // Insert op1 after '*pAfterStmt' - Statement* newStmt = gtNewStmt(src->AsOp()->gtOp1, ilOffset); + Statement* newStmt = gtNewStmt(src->AsOp()->gtOp1, usedDI); fgInsertStmtAfter(block, *pAfterStmt, newStmt); *pAfterStmt = newStmt; } else if (impLastStmt != nullptr) { // Do the side-effect as a separate statement. - impAppendTree(src->AsOp()->gtOp1, curLevel, ilOffset); + impAppendTree(src->AsOp()->gtOp1, curLevel, usedDI); } else { @@ -1535,12 +1539,12 @@ GenTree* Compiler::impAssignStructPtr(GenTree* destAddr, // in the importer where we can append the side effect. // Instead, we're going to sink the assignment below the COMMA. src->AsOp()->gtOp2 = - impAssignStructPtr(destAddr, src->AsOp()->gtOp2, structHnd, curLevel, pAfterStmt, ilOffset, block); + impAssignStructPtr(destAddr, src->AsOp()->gtOp2, structHnd, curLevel, pAfterStmt, usedDI, block); return src; } // Evaluate the second thing using recursion. - return impAssignStructPtr(destAddr, src->AsOp()->gtOp2, structHnd, curLevel, pAfterStmt, ilOffset, block); + return impAssignStructPtr(destAddr, src->AsOp()->gtOp2, structHnd, curLevel, pAfterStmt, usedDI, block); } else if (src->IsLocal()) { @@ -1689,7 +1693,7 @@ GenTree* Compiler::impGetStructAddr(GenTree* structVal, beforeStmt = oldLastStmt->GetNextStmt(); } - impInsertTreeBefore(structVal->AsOp()->gtOp1, impCurStmtOffs, beforeStmt); + impInsertTreeBefore(structVal->AsOp()->gtOp1, impCurStmtDI, beforeStmt); structVal->AsOp()->gtOp1 = gtNewNothingNode(); } @@ -2321,7 +2325,7 @@ GenTree* Compiler::impRuntimeLookupToTree(CORINFO_RESOLVED_TOKEN* pResolvedToken impSpillSideEffects(true, CHECK_SPILL_ALL DEBUGARG("bubbling QMark0")); unsigned slotLclNum = lvaGrabTemp(true DEBUGARG("impRuntimeLookup test")); - impAssignTempGen(slotLclNum, slotPtrTree, NO_CLASS_HANDLE, (unsigned)CHECK_SPILL_ALL, nullptr, impCurStmtOffs); + impAssignTempGen(slotLclNum, slotPtrTree, NO_CLASS_HANDLE, (unsigned)CHECK_SPILL_ALL, nullptr, impCurStmtDI); GenTree* slot = gtNewLclvNode(slotLclNum, TYP_I_IMPL); // downcast the pointer to a TYP_INT on 64-bit targets @@ -2341,7 +2345,7 @@ GenTree* Compiler::impRuntimeLookupToTree(CORINFO_RESOLVED_TOKEN* pResolvedToken GenTree* asg = gtNewAssignNode(slot, indir); GenTreeColon* colon = new (this, GT_COLON) GenTreeColon(TYP_VOID, gtNewNothingNode(), asg); GenTreeQmark* qmark = gtNewQmarkNode(TYP_VOID, relop, colon); - impAppendTree(qmark, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); + impAppendTree(qmark, (unsigned)CHECK_SPILL_NONE, impCurStmtDI); return gtNewLclvNode(slotLclNum, TYP_I_IMPL); } @@ -2822,8 +2826,11 @@ BasicBlock* Compiler::impPushCatchArgOnStack(BasicBlock* hndBlk, CORINFO_CLASS_H { // Report the debug info. impImportBlockCode won't treat the actual handler as exception block and thus // won't do it for us. - impCurStmtOffs = newBlk->bbCodeOffs | IL_OFFSETX_STKBIT; - argStmt = gtNewStmt(argAsg, impCurStmtOffs); + // TODO-DEBUGINFO: Previous code always set stack as non-empty + // here. Can we not just use impCurStmtOffsSet? Are we out of sync + // here with the stack? + impCurStmtDI = DebugInfo(compInlineContext, ILLocation(newBlk->bbCodeOffs, false, false)); + argStmt = gtNewStmt(argAsg, impCurStmtDI); } else { @@ -2872,48 +2879,56 @@ GenTree* Compiler::impCloneExpr(GenTree* tree, // specialized type (e.g. a SIMD type). So we will get the type from // the lclVar AFTER calling impAssignTempGen(). - impAssignTempGen(temp, tree, structHnd, curLevel, pAfterStmt, impCurStmtOffs); + impAssignTempGen(temp, tree, structHnd, curLevel, pAfterStmt, impCurStmtDI); var_types type = genActualType(lvaTable[temp].TypeGet()); *pClone = gtNewLclvNode(temp, type); return gtNewLclvNode(temp, type); } -/***************************************************************************** - * Remember the IL offset (including stack-empty info) for the trees we will - * generate now. - */ - -inline void Compiler::impCurStmtOffsSet(IL_OFFSET offs) +//------------------------------------------------------------------------ +// impCreateDIWithCurrentStackInfo: Create a DebugInfo instance with the +// specified IL offset and 'is call' bit, using the current stack to determine +// whether to set the 'stack empty' bit. +// +// Arguments: +// offs - the IL offset for the DebugInfo +// isCall - whether the created DebugInfo should have the IsCall bit set +// +// Return Value: +// The DebugInfo instance. +// +DebugInfo Compiler::impCreateDIWithCurrentStackInfo(IL_OFFSET offs, bool isCall) { - if (compIsForInlining()) - { - Statement* callStmt = impInlineInfo->iciStmt; - impCurStmtOffs = callStmt->GetILOffsetX(); - } - else - { - assert(offs == BAD_IL_OFFSET || (offs & IL_OFFSETX_BITS) == 0); - IL_OFFSETX stkBit = (verCurrentState.esStackDepth > 0) ? IL_OFFSETX_STKBIT : 0; - impCurStmtOffs = offs | stkBit; - } + assert(offs != BAD_IL_OFFSET); + + bool isStackEmpty = verCurrentState.esStackDepth <= 0; + return DebugInfo(compInlineContext, ILLocation(offs, isStackEmpty, isCall)); } -/***************************************************************************** - * Returns current IL offset with stack-empty and call-instruction info incorporated - */ -inline IL_OFFSETX Compiler::impCurILOffset(IL_OFFSET offs, bool callInstruction) +//------------------------------------------------------------------------ +// impCurStmtOffsSet: Set the "current debug info" to attach to statements that +// we are generating next. +// +// Arguments: +// offs - the IL offset +// +// Remarks: +// This function will be called in the main IL processing loop when it is +// determined that we have reached a location in the IL stream for which we +// want to report debug information. This is the main way we determine which +// statements to report debug info for to the EE: for other statements, they +// will have no debug information attached. +// +inline void Compiler::impCurStmtOffsSet(IL_OFFSET offs) { - if (compIsForInlining()) + if (offs == BAD_IL_OFFSET) { - return BAD_IL_OFFSET; + impCurStmtDI = DebugInfo(compInlineContext, ILLocation()); } else { - assert(offs == BAD_IL_OFFSET || (offs & IL_OFFSETX_BITS) == 0); - IL_OFFSETX stkBit = (verCurrentState.esStackDepth > 0) ? IL_OFFSETX_STKBIT : 0; - IL_OFFSETX callInstructionBit = callInstruction ? IL_OFFSETX_CALLINSTRUCTIONBIT : 0; - return offs | stkBit | callInstructionBit; + impCurStmtDI = impCreateDIWithCurrentStackInfo(offs, false); } } @@ -2978,7 +2993,7 @@ void Compiler::impNoteBranchOffs() { if (opts.compDbgCode) { - impAppendTree(gtNewNothingNode(), (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); + impAppendTree(gtNewNothingNode(), (unsigned)CHECK_SPILL_NONE, impCurStmtDI); } } @@ -2999,11 +3014,6 @@ unsigned Compiler::impInitBlockLineInfo() impCurStmtOffsSet(BAD_IL_OFFSET); - if (compIsForInlining()) - { - return ~0; - } - IL_OFFSET blockOffs = compCurBB->bbCodeOffs; if ((verCurrentState.esStackDepth == 0) && (info.compStmtOffsetsImplicit & ICorDebugInfo::STACK_EMPTY_BOUNDARIES)) @@ -5646,7 +5656,7 @@ void Compiler::verConvertBBToThrowVerificationException(BasicBlock* block DEBUGA GenTree* op1 = gtNewHelperCallNode(CORINFO_HELP_VERIFICATION, TYP_VOID, gtNewCallArgs(gtNewIconNode(block->bbCodeOffs))); // verCurrentState.esStackDepth = 0; - impAppendTree(op1, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); + impAppendTree(op1, (unsigned)CHECK_SPILL_NONE, impCurStmtDI); // The inliner is not able to handle methods that require throw block, so // make sure this methods never gets inlined. @@ -7056,7 +7066,7 @@ void Compiler::impImportAndPushBox(CORINFO_RESOLVED_TOKEN* pResolvedToken) // Assign the boxed object to the box temp. // GenTree* asg = gtNewTempAssign(impBoxTemp, op1); - Statement* asgStmt = impAppendTree(asg, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); + Statement* asgStmt = impAppendTree(asg, (unsigned)CHECK_SPILL_NONE, impCurStmtDI); // If the exprToBox is a call that returns its value via a ret buf arg, // move the assignment statement(s) before the call (which must be a top level tree). @@ -7178,7 +7188,7 @@ void Compiler::impImportAndPushBox(CORINFO_RESOLVED_TOKEN* pResolvedToken) impSpillSideEffects(true, (unsigned)CHECK_SPILL_ALL DEBUGARG("impImportAndPushBox")); // Set up this copy as a second assignment. - Statement* copyStmt = impAppendTree(op1, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); + Statement* copyStmt = impAppendTree(op1, (unsigned)CHECK_SPILL_NONE, impCurStmtDI); op1 = gtNewLclvNode(impBoxTemp, TYP_REF); @@ -7676,7 +7686,7 @@ void Compiler::impCheckForPInvokeCall( } } -GenTreeCall* Compiler::impImportIndirectCall(CORINFO_SIG_INFO* sig, IL_OFFSETX ilOffset) +GenTreeCall* Compiler::impImportIndirectCall(CORINFO_SIG_INFO* sig, const DebugInfo& di) { var_types callRetTyp = JITtype2varType(sig->retType); @@ -7714,7 +7724,7 @@ GenTreeCall* Compiler::impImportIndirectCall(CORINFO_SIG_INFO* sig, IL_OFFSETX i /* Create the call node */ - GenTreeCall* call = gtNewIndCallNode(fptr, callRetTyp, nullptr, ilOffset); + GenTreeCall* call = gtNewIndCallNode(fptr, callRetTyp, nullptr, di); call->gtFlags |= GTF_EXCEPT | (fptr->gtFlags & GTF_GLOB_EFFECT); #ifdef UNIX_X86_ABI @@ -8267,7 +8277,7 @@ void Compiler::impInsertHelperCall(CORINFO_HELPER_DESC* helperInfo) * Also, consider sticking this in the first basic block. */ GenTree* callout = gtNewHelperCallNode(helperInfo->helperNum, TYP_VOID, args); - impAppendTree(callout, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); + impAppendTree(callout, (unsigned)CHECK_SPILL_NONE, impCurStmtDI); } //------------------------------------------------------------------------ @@ -8454,7 +8464,7 @@ bool Compiler::impIsImplicitTailCallCandidate( // newObjThis - tree for this pointer or uninitalized newobj temp (or nullptr) // prefixFlags - IL prefix flags for the call // callInfo - EE supplied info for the call -// rawILOffset - IL offset of the opcode +// rawILOffset - IL offset of the opcode, used for guarded devirtualization. // // Returns: // Type of the call's return value. @@ -8483,7 +8493,11 @@ var_types Compiler::impImportCall(OPCODE opcode, { assert(opcode == CEE_CALL || opcode == CEE_CALLVIRT || opcode == CEE_NEWOBJ || opcode == CEE_CALLI); - IL_OFFSETX ilOffset = impCurILOffset(rawILOffset, true); + // The current statement DI may not refer to the exact call, but for calls + // we wish to be able to attach the exact IL instruction to get "return + // value" support in the debugger, so create one with the exact IL offset. + DebugInfo di = impCreateDIWithCurrentStackInfo(rawILOffset, true); + var_types callRetTyp = TYP_COUNT; CORINFO_SIG_INFO* sig = nullptr; CORINFO_METHOD_HANDLE methHnd = nullptr; @@ -8566,7 +8580,7 @@ var_types Compiler::impImportCall(OPCODE opcode, callRetTyp = JITtype2varType(calliSig.retType); - call = impImportIndirectCall(&calliSig, ilOffset); + call = impImportIndirectCall(&calliSig, di); // We don't know the target method, so we have to infer the flags, or // assume the worst-case. @@ -8791,7 +8805,7 @@ var_types Compiler::impImportCall(OPCODE opcode, else { // The stub address is known at compile time - call = gtNewCallNode(CT_USER_FUNC, callInfo->hMethod, callRetTyp, nullptr, ilOffset); + call = gtNewCallNode(CT_USER_FUNC, callInfo->hMethod, callRetTyp, nullptr, di); call->AsCall()->gtStubCallStubAddr = callInfo->stubLookup.constLookup.addr; call->gtFlags |= GTF_CALL_VIRT_STUB; assert(callInfo->stubLookup.constLookup.accessType != IAT_PPVALUE && @@ -8821,7 +8835,7 @@ var_types Compiler::impImportCall(OPCODE opcode, { assert(!(mflags & CORINFO_FLG_STATIC)); // can't call a static method assert(!(clsFlags & CORINFO_FLG_VALUECLASS)); - call = gtNewCallNode(CT_USER_FUNC, callInfo->hMethod, callRetTyp, nullptr, ilOffset); + call = gtNewCallNode(CT_USER_FUNC, callInfo->hMethod, callRetTyp, nullptr, di); call->gtFlags |= GTF_CALL_VIRT_VTABLE; // Should we expand virtual call targets early for this method? @@ -8871,7 +8885,7 @@ var_types Compiler::impImportCall(OPCODE opcode, // Create the actual call node - call = gtNewIndCallNode(fptr, callRetTyp, args, ilOffset); + call = gtNewIndCallNode(fptr, callRetTyp, args, di); call->AsCall()->gtCallThisArg = gtNewCallArgs(thisPtrCopy); call->gtFlags |= GTF_EXCEPT | (fptr->gtFlags & GTF_GLOB_EFFECT); @@ -8898,7 +8912,7 @@ var_types Compiler::impImportCall(OPCODE opcode, case CORINFO_CALL: { // This is for a non-virtual, non-interface etc. call - call = gtNewCallNode(CT_USER_FUNC, callInfo->hMethod, callRetTyp, nullptr, ilOffset); + call = gtNewCallNode(CT_USER_FUNC, callInfo->hMethod, callRetTyp, nullptr, di); // We remove the nullcheck for the GetType call intrinsic. // TODO-CQ: JIT64 does not introduce the null check for many more helper calls @@ -8943,7 +8957,7 @@ var_types Compiler::impImportCall(OPCODE opcode, impAssignTempGen(lclNum, fptr, (unsigned)CHECK_SPILL_ALL); fptr = gtNewLclvNode(lclNum, TYP_I_IMPL); - call = gtNewIndCallNode(fptr, callRetTyp, nullptr, ilOffset); + call = gtNewIndCallNode(fptr, callRetTyp, nullptr, di); call->gtFlags |= GTF_EXCEPT | (fptr->gtFlags & GTF_GLOB_EFFECT); if (callInfo->nullInstanceCheck) { @@ -9384,6 +9398,8 @@ var_types Compiler::impImportCall(OPCODE opcode, const bool isLateDevirtualization = false; impDevirtualizeCall(call->AsCall(), pResolvedToken, &callInfo->hMethod, &callInfo->methodFlags, &callInfo->contextHandle, &exactContextHnd, isLateDevirtualization, isExplicitTailCall, + // Take care to pass raw IL offset here as the 'debug info' might be different for + // inlinees. rawILOffset); } @@ -9436,7 +9452,7 @@ var_types Compiler::impImportCall(OPCODE opcode, } // append the call node. - impAppendTree(call, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); + impAppendTree(call, (unsigned)CHECK_SPILL_ALL, impCurStmtDI); // Now push the value of the 'new onto the stack @@ -9702,11 +9718,11 @@ var_types Compiler::impImportCall(OPCODE opcode, { // we actually did push something, so don't spill the thing we just pushed. assert(verCurrentState.esStackDepth > 0); - impAppendTree(call, verCurrentState.esStackDepth - 1, impCurStmtOffs); + impAppendTree(call, verCurrentState.esStackDepth - 1, impCurStmtDI); } else { - impAppendTree(call, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); + impAppendTree(call, (unsigned)CHECK_SPILL_ALL, impCurStmtDI); } } else @@ -9774,7 +9790,7 @@ var_types Compiler::impImportCall(OPCODE opcode, assert(!isFatPointerCandidate); // We should not try to inline calli. // Make the call its own tree (spill the stack if needed). - impAppendTree(call, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); + impAppendTree(call, (unsigned)CHECK_SPILL_ALL, impCurStmtDI); // TODO: Still using the widened type. GenTree* retExpr = gtNewInlineCandidateReturnExpr(call, genActualType(callRetTyp), compCurBB->bbFlags); @@ -10299,7 +10315,7 @@ void Compiler::impImportLeave(BasicBlock* block) callBlock->bbJumpKind = BBJ_CALLFINALLY; // convert the BBJ_LEAVE to BBJ_CALLFINALLY if (endCatches) - impAppendTree(endCatches, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); + impAppendTree(endCatches, (unsigned)CHECK_SPILL_NONE, impCurStmtDI); #ifdef DEBUG if (verbose) @@ -10386,7 +10402,7 @@ void Compiler::impImportLeave(BasicBlock* block) block->bbJumpKind = BBJ_ALWAYS; // convert the BBJ_LEAVE to a BBJ_ALWAYS if (endCatches) - impAppendTree(endCatches, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); + impAppendTree(endCatches, (unsigned)CHECK_SPILL_NONE, impCurStmtDI); #ifdef DEBUG if (verbose) @@ -11725,17 +11741,17 @@ void Compiler::impImportBlockCode(BasicBlock* block) impSpillStackEnsure(true); } - // Has impCurStmtOffs been reported in any tree? + // Have we reported debug info for any tree? - if (impCurStmtOffs != BAD_IL_OFFSET && opts.compDbgCode) + if (impCurStmtDI.IsValid() && opts.compDbgCode) { GenTree* placeHolder = new (this, GT_NO_OP) GenTree(GT_NO_OP, TYP_VOID); - impAppendTree(placeHolder, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); + impAppendTree(placeHolder, (unsigned)CHECK_SPILL_NONE, impCurStmtDI); - assert(impCurStmtOffs == BAD_IL_OFFSET); + assert(!impCurStmtDI.IsValid()); } - if (impCurStmtOffs == BAD_IL_OFFSET) + if (!impCurStmtDI.IsValid()) { /* Make sure that nxtStmtIndex is in sync with opcodeOffs. If opcodeOffs has gone past nxtStmtIndex, catch up */ @@ -11776,7 +11792,7 @@ void Compiler::impImportBlockCode(BasicBlock* block) { /* At stack-empty locations, we have already added the tree to the stmt list with the last offset. We just need to update - impCurStmtOffs + impCurStmtDI */ impCurStmtOffsSet(opcodeOffs); @@ -11807,8 +11823,8 @@ void Compiler::impImportBlockCode(BasicBlock* block) impCurStmtOffsSet(opcodeOffs); } - assert(impCurStmtOffs == BAD_IL_OFFSET || nxtStmtOffs == BAD_IL_OFFSET || - jitGetILoffs(impCurStmtOffs) <= nxtStmtOffs); + assert(!impCurStmtDI.IsValid() || (nxtStmtOffs == BAD_IL_OFFSET) || + (impCurStmtDI.GetLocation().GetOffset() <= nxtStmtOffs)); } } @@ -11937,14 +11953,14 @@ void Compiler::impImportBlockCode(BasicBlock* block) } /* Append 'op1' to the list of statements */ - impAppendTree(op1, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); + impAppendTree(op1, (unsigned)CHECK_SPILL_ALL, impCurStmtDI); goto DONE_APPEND; APPEND: /* Append 'op1' to the list of statements */ - impAppendTree(op1, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); + impAppendTree(op1, (unsigned)CHECK_SPILL_NONE, impCurStmtDI); goto DONE_APPEND; DONE_APPEND: @@ -13464,7 +13480,7 @@ void Compiler::impImportBlockCode(BasicBlock* block) /* GT_JTRUE is handled specially for non-empty stacks. See 'addStmt' in impImportBlock(block). For correct line numbers, spill stack. */ - if (opts.compDbgCode && impCurStmtOffs != BAD_IL_OFFSET) + if (opts.compDbgCode && impCurStmtDI.IsValid()) { impSpillStackEnsure(true); } @@ -13634,13 +13650,13 @@ void Compiler::impImportBlockCode(BasicBlock* block) { impSpillSideEffects(false, (unsigned)CHECK_SPILL_ALL DEBUGARG( "Branch to next Optimization, op1 side effect")); - impAppendTree(gtUnusedValNode(op1), (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); + impAppendTree(gtUnusedValNode(op1), (unsigned)CHECK_SPILL_NONE, impCurStmtDI); } if (op2->gtFlags & GTF_GLOB_EFFECT) { impSpillSideEffects(false, (unsigned)CHECK_SPILL_ALL DEBUGARG( "Branch to next Optimization, op2 side effect")); - impAppendTree(gtUnusedValNode(op2), (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); + impAppendTree(gtUnusedValNode(op2), (unsigned)CHECK_SPILL_NONE, impCurStmtDI); } #ifdef DEBUG @@ -14525,7 +14541,7 @@ void Compiler::impImportBlockCode(BasicBlock* block) if (op1->gtFlags & GTF_SIDE_EFFECT) { op1 = gtUnusedValNode(op1); - impAppendTree(op1, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); + impAppendTree(op1, (unsigned)CHECK_SPILL_ALL, impCurStmtDI); } goto DO_LDFTN; } @@ -14535,7 +14551,7 @@ void Compiler::impImportBlockCode(BasicBlock* block) if (op1->gtFlags & GTF_SIDE_EFFECT) { op1 = gtUnusedValNode(op1); - impAppendTree(op1, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); + impAppendTree(op1, (unsigned)CHECK_SPILL_ALL, impCurStmtDI); } goto DO_LDFTN; } @@ -14787,7 +14803,7 @@ void Compiler::impImportBlockCode(BasicBlock* block) gtNewIconNode(0), // Value false, // isVolatile false); // not copyBlock - impAppendTree(newObjThisPtr, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); + impAppendTree(newObjThisPtr, (unsigned)CHECK_SPILL_NONE, impCurStmtDI); } else { @@ -15192,7 +15208,7 @@ void Compiler::impImportBlockCode(BasicBlock* block) if (obj->gtFlags & GTF_SIDE_EFFECT) { obj = gtUnusedValNode(obj); - impAppendTree(obj, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); + impAppendTree(obj, (unsigned)CHECK_SPILL_ALL, impCurStmtDI); } obj = nullptr; } @@ -15524,7 +15540,7 @@ void Compiler::impImportBlockCode(BasicBlock* block) if (obj->gtFlags & GTF_SIDE_EFFECT) { obj = gtUnusedValNode(obj); - impAppendTree(obj, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); + impAppendTree(obj, (unsigned)CHECK_SPILL_ALL, impCurStmtDI); } obj = nullptr; } @@ -16128,7 +16144,7 @@ void Compiler::impImportBlockCode(BasicBlock* block) // The pointer may have side-effects if (op1->AsOp()->gtOp1->gtFlags & GTF_SIDE_EFFECT) { - impAppendTree(op1->AsOp()->gtOp1, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); + impAppendTree(op1->AsOp()->gtOp1, (unsigned)CHECK_SPILL_ALL, impCurStmtDI); #ifdef DEBUG impNoteLastILoffs(); #endif @@ -16356,7 +16372,7 @@ void Compiler::impImportBlockCode(BasicBlock* block) // may be other trees on the evaluation stack that side-effect the // sources of the UNBOX operation we must spill the stack. - impAppendTree(op1, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); + impAppendTree(op1, (unsigned)CHECK_SPILL_ALL, impCurStmtDI); // Create the address-expression to reference past the object header // to the beginning of the value-type. Today this means adjusting @@ -17725,10 +17741,11 @@ bool Compiler::impReturnInstruction(int prefixFlags, OPCODE& opcode) else if (info.compRetBuffArg != BAD_VAR_NUM) { // Assign value to return buff (first param) - GenTree* retBuffAddr = gtNewLclvNode(info.compRetBuffArg, TYP_BYREF DEBUGARG(impCurStmtOffs)); + GenTree* retBuffAddr = + gtNewLclvNode(info.compRetBuffArg, TYP_BYREF DEBUGARG(impCurStmtDI.GetLocation().GetOffset())); op2 = impAssignStructPtr(retBuffAddr, op2, retClsHnd, (unsigned)CHECK_SPILL_ALL); - impAppendTree(op2, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); + impAppendTree(op2, (unsigned)CHECK_SPILL_NONE, impCurStmtDI); // There are cases where the address of the implicit RetBuf should be returned explicitly (in RAX). CLANG_FORMAT_COMMENT_ANCHOR; @@ -17800,7 +17817,7 @@ bool Compiler::impReturnInstruction(int prefixFlags, OPCODE& opcode) } } - impAppendTree(op1, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); + impAppendTree(op1, (unsigned)CHECK_SPILL_NONE, impCurStmtDI); #ifdef DEBUG // Remember at which BC offset the tree was finished impNoteLastILoffs(); @@ -19766,6 +19783,7 @@ void Compiler::impCheckCanInline(GenTreeCall* call, pInfo->initClassResult = initClassResult; pInfo->fncRetType = fncRetType; pInfo->exactContextNeedsRuntimeLookup = false; + pInfo->inlinersContext = pParam->pThis->compInlineContext; // Note exactContextNeedsRuntimeLookup is reset later on, // over in impMarkInlineCandidate. @@ -21233,7 +21251,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, CORINFO_CONTEXT_HANDLE* pExactContextHandle, bool isLateDevirtualization, bool isExplicitTailCall, - IL_OFFSETX ilOffset) + IL_OFFSET ilOffset) { assert(call != nullptr); assert(method != nullptr); @@ -22123,7 +22141,7 @@ void Compiler::addFatPointerCandidate(GenTreeCall* call) // Arguments: // // call - potential guarded devirtualization candidate -// ilOffset - IL offset of the call instruction +// ilOffset - IL ofset of the call instruction // isInterface - true if this is an interface call // baseMethod - target method of the call // baseClass - class that introduced the target method @@ -22137,7 +22155,7 @@ void Compiler::addFatPointerCandidate(GenTreeCall* call) // void Compiler::considerGuardedDevirtualization( GenTreeCall* call, - IL_OFFSETX ilOffset, + IL_OFFSET ilOffset, bool isInterface, CORINFO_METHOD_HANDLE baseMethod, CORINFO_CLASS_HANDLE baseClass, diff --git a/src/coreclr/jit/indirectcalltransformer.cpp b/src/coreclr/jit/indirectcalltransformer.cpp index feb7418fbb869..c192275564794 100644 --- a/src/coreclr/jit/indirectcalltransformer.cpp +++ b/src/coreclr/jit/indirectcalltransformer.cpp @@ -364,7 +364,7 @@ class IndirectCallTransformer GenTree* zero = new (compiler, GT_CNS_INT) GenTreeIntCon(TYP_I_IMPL, 0); GenTree* fatPointerCmp = compiler->gtNewOperNode(GT_NE, TYP_INT, fatPointerAnd, zero); GenTree* jmpTree = compiler->gtNewOperNode(GT_JTRUE, TYP_VOID, fatPointerCmp); - Statement* jmpStmt = compiler->fgNewStmtFromTree(jmpTree, stmt->GetILOffsetX()); + Statement* jmpStmt = compiler->fgNewStmtFromTree(jmpTree, stmt->GetDebugInfo()); compiler->fgInsertStmtAtEnd(checkBlock, jmpStmt); } @@ -595,7 +595,7 @@ class IndirectCallTransformer const unsigned thisTempNum = compiler->lvaGrabTemp(true DEBUGARG("guarded devirt this temp")); // lvaSetClass(thisTempNum, ...); GenTree* asgTree = compiler->gtNewTempAssign(thisTempNum, thisTree); - Statement* asgStmt = compiler->fgNewStmtFromTree(asgTree, stmt->GetILOffsetX()); + Statement* asgStmt = compiler->fgNewStmtFromTree(asgTree, stmt->GetDebugInfo()); compiler->fgInsertStmtAtEnd(checkBlock, asgStmt); thisTree = compiler->gtNewLclvNode(thisTempNum, TYP_REF); @@ -624,7 +624,7 @@ class IndirectCallTransformer // GenTree* methodTableCompare = compiler->gtNewOperNode(GT_NE, TYP_INT, targetMethodTable, methodTable); GenTree* jmpTree = compiler->gtNewOperNode(GT_JTRUE, TYP_VOID, methodTableCompare); - Statement* jmpStmt = compiler->fgNewStmtFromTree(jmpTree, stmt->GetILOffsetX()); + Statement* jmpStmt = compiler->fgNewStmtFromTree(jmpTree, stmt->GetDebugInfo()); compiler->fgInsertStmtAtEnd(checkBlock, jmpStmt); } @@ -781,14 +781,14 @@ class IndirectCallTransformer } else { - compiler->fgNewStmtAtEnd(thenBlock, call, stmt->GetILOffsetX()); + compiler->fgNewStmtAtEnd(thenBlock, call, stmt->GetDebugInfo()); } } else { // Add the call. // - compiler->fgNewStmtAtEnd(thenBlock, call, stmt->GetILOffsetX()); + compiler->fgNewStmtAtEnd(thenBlock, call, stmt->GetDebugInfo()); // Re-establish this call as an inline candidate. // @@ -831,7 +831,7 @@ class IndirectCallTransformer elseBlock = CreateAndInsertBasicBlock(BBJ_NONE, thenBlock); elseBlock->bbFlags |= currBlock->bbFlags & BBF_SPLIT_GAINED; GenTreeCall* call = origCall; - Statement* newStmt = compiler->gtNewStmt(call, stmt->GetILOffsetX()); + Statement* newStmt = compiler->gtNewStmt(call, stmt->GetDebugInfo()); call->gtFlags &= ~GTF_CALL_INLINE_CANDIDATE; call->SetIsGuarded(); @@ -1170,13 +1170,13 @@ class IndirectCallTransformer assert(sizeCheck->OperIs(GT_LE)); GenTree* sizeJmpTree = compiler->gtNewOperNode(GT_JTRUE, TYP_VOID, sizeCheck); - Statement* sizeJmpStmt = compiler->fgNewStmtFromTree(sizeJmpTree, stmt->GetILOffsetX()); + Statement* sizeJmpStmt = compiler->fgNewStmtFromTree(sizeJmpTree, stmt->GetDebugInfo()); compiler->fgInsertStmtAtEnd(checkBlock, sizeJmpStmt); checkBlock2 = CreateAndInsertBasicBlock(BBJ_COND, checkBlock); assert(nullCheck->OperIs(GT_EQ)); GenTree* nullJmpTree = compiler->gtNewOperNode(GT_JTRUE, TYP_VOID, nullCheck); - Statement* nullJmpStmt = compiler->fgNewStmtFromTree(nullJmpTree, stmt->GetILOffsetX()); + Statement* nullJmpStmt = compiler->fgNewStmtFromTree(nullJmpTree, stmt->GetDebugInfo()); compiler->fgInsertStmtAtEnd(checkBlock2, nullJmpStmt); } @@ -1195,7 +1195,7 @@ class IndirectCallTransformer origCall->gtCallArgs = argsIter.GetUse(); GenTree* asg = compiler->gtNewTempAssign(resultLclNum, resultHandle); - Statement* asgStmt = compiler->gtNewStmt(asg, stmt->GetILOffsetX()); + Statement* asgStmt = compiler->gtNewStmt(asg, stmt->GetDebugInfo()); compiler->fgInsertStmtAtEnd(thenBlock, asgStmt); } @@ -1206,7 +1206,7 @@ class IndirectCallTransformer { elseBlock = CreateAndInsertBasicBlock(BBJ_NONE, thenBlock); GenTree* asg = compiler->gtNewTempAssign(resultLclNum, origCall); - Statement* asgStmt = compiler->gtNewStmt(asg, stmt->GetILOffsetX()); + Statement* asgStmt = compiler->gtNewStmt(asg, stmt->GetDebugInfo()); compiler->fgInsertStmtAtEnd(elseBlock, asgStmt); } diff --git a/src/coreclr/jit/inline.cpp b/src/coreclr/jit/inline.cpp index 79e032701190a..546eb2aa376f9 100644 --- a/src/coreclr/jit/inline.cpp +++ b/src/coreclr/jit/inline.cpp @@ -332,7 +332,6 @@ InlineContext::InlineContext(InlineStrategy* strategy) , m_Code(nullptr) , m_ILSize(0) , m_ImportedILSize(0) - , m_Offset(BAD_IL_OFFSET) , m_Observation(InlineObservation::CALLEE_UNUSED_INITIAL) , m_CodeSizeEstimate(0) , m_Success(true) @@ -344,7 +343,11 @@ InlineContext::InlineContext(InlineStrategy* strategy) , m_Callee(nullptr) , m_TreeID(0) , m_Ordinal(0) + , m_ActualCallOffset(BAD_IL_OFFSET) #endif // defined(DEBUG) || defined(INLINE_DATA) +#ifdef DEBUG + , m_ILInstsSet(nullptr) +#endif { // Empty } @@ -412,19 +415,30 @@ void InlineContext::Dump(bool verbose, unsigned indent) const char* guarded = m_Guarded ? " GUARDED" : ""; const char* unboxed = m_Unboxed ? " UNBOXED" : ""; + IL_OFFSET offs = BAD_IL_OFFSET; + +#if defined(DEBUG) || defined(INLINE_DATA) + offs = m_ActualCallOffset; +#endif + + if (offs == BAD_IL_OFFSET && m_Location.IsValid()) + { + offs = m_Location.GetOffset(); + } + if (verbose) { - if (m_Offset == BAD_IL_OFFSET) + if (offs == BAD_IL_OFFSET) { - printf("%*s[%u IL=???? TR=%06u %08X] [%s%s: %s%s%s%s] %s\n", indent, "", m_Ordinal, m_TreeID, - calleeToken, inlineResult, inlineTarget, inlineReason, guarded, devirtualized, unboxed, + printf("%*s[" FMT_INL_CTX " IL=???? TR=%06u %08X] [%s%s: %s%s%s%s] %s\n", indent, "", m_Ordinal, + m_TreeID, calleeToken, inlineResult, inlineTarget, inlineReason, guarded, devirtualized, unboxed, calleeName); } else { - printf("%*s[%u IL=%04d TR=%06u %08X] [%s%s: %s%s%s%s] %s\n", indent, "", m_Ordinal, - jitGetILoffs(m_Offset), m_TreeID, calleeToken, inlineResult, inlineTarget, inlineReason, guarded, - devirtualized, unboxed, calleeName); + printf("%*s[" FMT_INL_CTX " IL=%04d TR=%06u %08X] [%s%s: %s%s%s%s] %s\n", indent, "", m_Ordinal, offs, + m_TreeID, calleeToken, inlineResult, inlineTarget, inlineReason, guarded, devirtualized, unboxed, + calleeName); } } else @@ -473,7 +487,7 @@ void InlineContext::DumpData(unsigned indent) else if (m_Success) { const char* inlineReason = InlGetObservationString(m_Observation); - printf("%*s%u,\"%s\",\"%s\",", indent, "", m_Ordinal, inlineReason, calleeName); + printf("%*s%u,\"%s\",\"%s\",", indent, "", GetOrdinal(), inlineReason, calleeName); m_Policy->DumpData(jitstdout); printf("\n"); } @@ -555,11 +569,7 @@ void InlineContext::DumpXml(FILE* file, unsigned indent) buf[sizeof(buf) - 1] = 0; EscapeNameForXml(buf); - int offset = -1; - if (m_Offset != BAD_IL_OFFSET) - { - offset = (int)jitGetILoffs(m_Offset); - } + int offset = m_Location.IsValid() ? m_Location.GetOffset() : -1; fprintf(file, "%*s<%s>\n", indent, "", inlineType); fprintf(file, "%*s%08x\n", indent + 2, "", calleeToken); @@ -654,13 +664,13 @@ InlineResult::InlineResult(Compiler* compiler, GenTreeCall* call, Statement* stm // Pass along some optional information to the policy. if (stmt != nullptr) { - m_InlineContext = stmt->GetInlineContext(); + m_InlineContext = stmt->GetDebugInfo().GetInlineContext(); m_Policy->NoteContext(m_InlineContext); #if defined(DEBUG) || defined(INLINE_DATA) m_Policy->NoteOffset(call->gtRawILOffset); #else - m_Policy->NoteOffset(stmt->GetILOffsetX()); + m_Policy->NoteOffset(stmt->GetDebugInfo().GetLocation().GetOffset()); #endif // defined(DEBUG) || defined(INLINE_DATA) } @@ -1241,126 +1251,90 @@ InlineContext* InlineStrategy::NewRoot() return rootContext; } -//------------------------------------------------------------------------ -// NewSuccess: construct an InlineContext for a successful inline -// and link it into the context tree -// -// Arguments: -// inlineInfo - information about this inline -// -// Return Value: -// A new InlineContext for statements brought into the method by -// this inline. - -InlineContext* InlineStrategy::NewSuccess(InlineInfo* inlineInfo) +InlineContext* InlineStrategy::NewContext(InlineContext* parentContext, Statement* stmt, GenTreeCall* call) { - InlineContext* calleeContext = new (m_Compiler, CMK_Inlining) InlineContext(this); - Statement* stmt = inlineInfo->iciStmt; - BYTE* calleeIL = inlineInfo->inlineCandidateInfo->methInfo.ILCode; - unsigned calleeILSize = inlineInfo->inlineCandidateInfo->methInfo.ILCodeSize; - InlineContext* parentContext = stmt->GetInlineContext(); - GenTreeCall* originalCall = inlineInfo->inlineResult->GetCall(); - - noway_assert(parentContext != nullptr); - - calleeContext->m_Code = calleeIL; - calleeContext->m_ILSize = calleeILSize; - calleeContext->m_Parent = parentContext; - // Push on front here will put siblings in reverse lexical - // order which we undo in the dumper - calleeContext->m_Sibling = parentContext->m_Child; - parentContext->m_Child = calleeContext; - calleeContext->m_Child = nullptr; - calleeContext->m_Offset = stmt->GetILOffsetX(); - calleeContext->m_Observation = inlineInfo->inlineResult->GetObservation(); - calleeContext->m_Success = true; - calleeContext->m_Devirtualized = originalCall->IsDevirtualized(); - calleeContext->m_Guarded = originalCall->IsGuarded(); - calleeContext->m_Unboxed = originalCall->IsUnboxed(); - calleeContext->m_ImportedILSize = inlineInfo->inlineResult->GetImportedILSize(); - -#if defined(DEBUG) || defined(INLINE_DATA) - - InlinePolicy* policy = inlineInfo->inlineResult->GetPolicy(); - - calleeContext->m_Policy = policy; - calleeContext->m_CodeSizeEstimate = policy->CodeSizeEstimate(); - calleeContext->m_Callee = inlineInfo->fncHandle; - // +1 here since we set this before calling NoteOutcome. - calleeContext->m_Ordinal = m_InlineCount + 1; - // Update offset with more accurate info - calleeContext->m_Offset = originalCall->gtRawILOffset; + InlineContext* context = new (m_Compiler, CMK_Inlining) InlineContext(this); -#endif // defined(DEBUG) || defined(INLINE_DATA) - -#if defined(DEBUG) + context->m_InlineStrategy = this; + context->m_Parent = parentContext; + context->m_Sibling = parentContext->m_Child; + parentContext->m_Child = context; - calleeContext->m_TreeID = originalCall->gtTreeID; + // In debug builds we record inline contexts in all produced calls to be + // able to show all failed inlines in the inline tree, even non-candidates. + // These should always match the parent context we are seeing here. + assert(parentContext == call->gtInlineContext); -#endif // defined(DEBUG) + if (call->IsInlineCandidate()) + { + InlineCandidateInfo* info = call->gtInlineCandidateInfo; + context->m_Code = info->methInfo.ILCode; + context->m_ILSize = info->methInfo.ILCodeSize; - NoteOutcome(calleeContext); +#ifdef DEBUG + // All inline candidates should get their own statements that have + // appropriate debug info (or no debug info). + InlineContext* diInlineContext = stmt->GetDebugInfo().GetInlineContext(); + assert(diInlineContext == nullptr || diInlineContext == parentContext); +#endif + } - return calleeContext; -} + // TODO-DEBUGINFO: Currently, to keep the same behavior as before, we use + // the location of the statement containing the call being inlined. This is + // not always the exact IL offset of the call instruction, consider e.g. + // ldarg.0 + // call + // which becomes a single statement where the IL location points to the + // ldarg instruction. For SPGO purposes we should consider always storing + // the exact offset of the call instruction which will be more precise. We + // may consider storing the statement itself as well. + context->m_Location = stmt->GetDebugInfo().GetLocation(); + context->m_Devirtualized = call->IsDevirtualized(); + context->m_Guarded = call->IsGuarded(); + context->m_Unboxed = call->IsUnboxed(); #if defined(DEBUG) || defined(INLINE_DATA) + context->m_TreeID = call->gtTreeID; + context->m_Callee = call->gtCallType == CT_INDIRECT ? nullptr : call->gtCallMethHnd; + context->m_ActualCallOffset = call->gtRawILOffset; +#endif -//------------------------------------------------------------------------ -// NewFailure: construct an InlineContext for a failing inline -// and link it into the context tree -// -// Arguments: -// stmt - statement containing the attempted inline -// inlineResult - inlineResult for the attempt -// -// Return Value: -// A new InlineContext for diagnostic purposes + return context; +} -InlineContext* InlineStrategy::NewFailure(Statement* stmt, InlineResult* inlineResult) +void InlineContext::SetSucceeded(const InlineInfo* info) { - // Check for a parent context first. We should now have a parent - // context for all statements. - InlineContext* parentContext = stmt->GetInlineContext(); - assert(parentContext != nullptr); - InlineContext* failedContext = new (m_Compiler, CMK_Inlining) InlineContext(this); - GenTreeCall* originalCall = inlineResult->GetCall(); - - // Pushing the new context on the front of the parent child list - // will put siblings in reverse lexical order which we undo in the - // dumper. - failedContext->m_Parent = parentContext; - failedContext->m_Sibling = parentContext->m_Child; - parentContext->m_Child = failedContext; - failedContext->m_Child = nullptr; - failedContext->m_Offset = stmt->GetILOffsetX(); - failedContext->m_Observation = inlineResult->GetObservation(); - failedContext->m_Callee = inlineResult->GetCallee(); - failedContext->m_Success = false; - failedContext->m_Devirtualized = originalCall->IsDevirtualized(); - failedContext->m_Guarded = originalCall->IsGuarded(); - failedContext->m_Unboxed = originalCall->IsUnboxed(); - - assert(InlIsValidObservation(failedContext->m_Observation)); + assert(InlIsValidObservation(info->inlineResult->GetObservation())); + m_Observation = info->inlineResult->GetObservation(); + m_ImportedILSize = info->inlineResult->GetImportedILSize(); + m_Success = true; #if defined(DEBUG) || defined(INLINE_DATA) + m_Policy = info->inlineResult->GetPolicy(); + m_CodeSizeEstimate = m_Policy->CodeSizeEstimate(); + m_Ordinal = m_InlineStrategy->m_InlineCount + 1; +#endif - // Update offset with more accurate info - failedContext->m_Offset = originalCall->gtRawILOffset; - -#endif // #if defined(DEBUG) || defined(INLINE_DATA) - -#if defined(DEBUG) - - failedContext->m_TreeID = originalCall->gtTreeID; + m_InlineStrategy->NoteOutcome(this); +} -#endif // defined(DEBUG) +void InlineContext::SetFailed(const InlineResult* result) +{ + assert(InlIsValidObservation(result->GetObservation())); + m_Observation = result->GetObservation(); + m_ImportedILSize = result->GetImportedILSize(); + m_Success = false; - NoteOutcome(failedContext); +#if defined(DEBUG) || defined(INLINE_DATA) + m_Policy = result->GetPolicy(); + m_CodeSizeEstimate = m_Policy->CodeSizeEstimate(); +#endif - return failedContext; + m_InlineStrategy->NoteOutcome(this); } +#if defined(DEBUG) || defined(INLINE_DATA) + //------------------------------------------------------------------------ // Dump: dump description of inline behavior // diff --git a/src/coreclr/jit/inline.h b/src/coreclr/jit/inline.h index 141a95088e0da..ee3fb1f3035f4 100644 --- a/src/coreclr/jit/inline.h +++ b/src/coreclr/jit/inline.h @@ -233,7 +233,7 @@ class InlinePolicy { (void)context; } - virtual void NoteOffset(IL_OFFSETX offset) + virtual void NoteOffset(IL_OFFSET offset) { (void)offset; } @@ -589,6 +589,7 @@ struct InlineCandidateInfo : public GuardedDevirtualizationCandidateInfo CorInfoInitClassResult initClassResult; var_types fncRetType; bool exactContextNeedsRuntimeLookup; + InlineContext* inlinersContext; }; // InlArgInfo describes inline candidate argument properties. @@ -635,6 +636,7 @@ struct InlineInfo CORINFO_METHOD_HANDLE fncHandle; InlineCandidateInfo* inlineCandidateInfo; + InlineContext* inlineContext; InlineResult* inlineResult; @@ -686,6 +688,8 @@ struct InlineInfo // This makes it possible to detect recursion and to keep track of the // depth of each inline attempt. +#define FMT_INL_CTX "INL%02u" + class InlineContext { // InlineContexts are created by InlineStrategies @@ -709,6 +713,11 @@ class InlineContext return m_Callee; } + unsigned GetOrdinal() const + { + return m_Ordinal; + } + #endif // defined(DEBUG) || defined(INLINE_DATA) // Get the parent context for this context. @@ -747,10 +756,10 @@ class InlineContext return m_CodeSizeEstimate; } - // Get the offset of the call site - IL_OFFSETX GetOffset() const + // Get the loation of the call site within the parent + ILLocation GetLocation() const { - return m_Offset; + return m_Location; } // True if this is the root context @@ -779,6 +788,21 @@ class InlineContext return m_ImportedILSize; } + void SetSucceeded(const InlineInfo* info); + void SetFailed(const InlineResult* result); + +#ifdef DEBUG + FixedBitVect* GetILInstsSet() const + { + return m_ILInstsSet; + } + + void SetILInstsSet(FixedBitVect* set) + { + m_ILInstsSet = set; + } +#endif + private: InlineContext(InlineStrategy* strategy); @@ -790,8 +814,8 @@ class InlineContext const BYTE* m_Code; // address of IL buffer for the method unsigned m_ILSize; // size of IL buffer for the method unsigned m_ImportedILSize; // estimated size of imported IL - IL_OFFSETX m_Offset; // call site location within parent - InlineObservation m_Observation; // what lead to this inline + ILLocation m_Location; // inlining statement location within parent + InlineObservation m_Observation; // what lead to this inline success or failure int m_CodeSizeEstimate; // in bytes * 10 bool m_Success : 1; // true if this was a successful inline bool m_Devirtualized : 1; // true if this was a devirtualized call @@ -800,12 +824,17 @@ class InlineContext #if defined(DEBUG) || defined(INLINE_DATA) - InlinePolicy* m_Policy; // policy that evaluated this inline - CORINFO_METHOD_HANDLE m_Callee; // handle to the method - unsigned m_TreeID; // ID of the GenTreeCall - unsigned m_Ordinal; // Ordinal number of this inline + InlinePolicy* m_Policy; // policy that evaluated this inline + CORINFO_METHOD_HANDLE m_Callee; // handle to the method + unsigned m_TreeID; // ID of the GenTreeCall in the parent + unsigned m_Ordinal; // Ordinal number of this inline + IL_OFFSET m_ActualCallOffset; // IL offset of actual call instruction leading to the inline #endif // defined(DEBUG) || defined(INLINE_DATA) + +#ifdef DEBUG + FixedBitVect* m_ILInstsSet; // Set of offsets where instructions begin +#endif }; // The InlineStrategy holds the per-method persistent inline state. @@ -814,16 +843,14 @@ class InlineContext class InlineStrategy { + friend class InlineContext; public: // Construct a new inline strategy. InlineStrategy(Compiler* compiler); - // Create context for a successful inline. - InlineContext* NewSuccess(InlineInfo* inlineInfo); - - // Create context for a failing inline. - InlineContext* NewFailure(Statement* stmt, InlineResult* inlineResult); + // Create context for the specified inline candidate contained in the specified statement. + InlineContext* NewContext(InlineContext* parentContext, Statement* stmt, GenTreeCall* call); // Compiler associated with this strategy Compiler* GetCompiler() const diff --git a/src/coreclr/jit/inlinepolicy.cpp b/src/coreclr/jit/inlinepolicy.cpp index c69453b3565fb..1ec47378872d5 100644 --- a/src/coreclr/jit/inlinepolicy.cpp +++ b/src/coreclr/jit/inlinepolicy.cpp @@ -3383,7 +3383,7 @@ bool ReplayPolicy::FindContext(InlineContext* context) // Token and Hash we're looking for. mdMethodDef contextToken = m_RootCompiler->info.compCompHnd->getMethodDefFromMethod(context->GetCallee()); unsigned contextHash = m_RootCompiler->compMethodHash(context->GetCallee()); - unsigned contextOffset = (unsigned)context->GetOffset(); + unsigned contextOffset = (unsigned)context->GetLocation().GetOffset(); return FindInline(contextToken, contextHash, contextOffset); } @@ -3573,7 +3573,7 @@ bool ReplayPolicy::FindInline(CORINFO_METHOD_HANDLE callee) int offset = -1; if (m_Offset != BAD_IL_OFFSET) { - offset = (int)jitGetILoffs(m_Offset); + offset = m_Offset; } unsigned calleeOffset = (unsigned)offset; diff --git a/src/coreclr/jit/inlinepolicy.h b/src/coreclr/jit/inlinepolicy.h index e604fd57a36ed..f5f200e9f5bd7 100644 --- a/src/coreclr/jit/inlinepolicy.h +++ b/src/coreclr/jit/inlinepolicy.h @@ -516,7 +516,7 @@ class ReplayPolicy : public DiscretionaryPolicy m_InlineContext = context; } - void NoteOffset(IL_OFFSETX offset) override + void NoteOffset(IL_OFFSET offset) override { m_Offset = offset; } @@ -542,7 +542,7 @@ class ReplayPolicy : public DiscretionaryPolicy static FILE* s_ReplayFile; static CritSecObject s_XmlReaderLock; InlineContext* m_InlineContext; - IL_OFFSETX m_Offset; + IL_OFFSET m_Offset; bool m_WasForceInline; }; diff --git a/src/coreclr/jit/jit.h b/src/coreclr/jit/jit.h index c84bb0b529e4a..d8cb5cabfb065 100644 --- a/src/coreclr/jit/jit.h +++ b/src/coreclr/jit/jit.h @@ -288,41 +288,9 @@ const CORINFO_CLASS_HANDLE NO_CLASS_HANDLE = (CORINFO_CLASS_HANDLE) nullptr; /*****************************************************************************/ -// We define two IL offset types, as follows: -// -// IL_OFFSET: either a distinguished value, or an IL offset. -// IL_OFFSETX: either a distinguished value, or the top two bits are a flags, and the remaining bottom -// bits are a IL offset. -// -// In both cases, the set of legal distinguished values is: -// BAD_IL_OFFSET -- A unique illegal IL offset number. Note that it must be different from -// the ICorDebugInfo values, below, and must also not be a legal IL offset. -// ICorDebugInfo::NO_MAPPING -- The IL offset corresponds to no source code (such as EH step blocks). -// ICorDebugInfo::PROLOG -- The IL offset indicates a prolog -// ICorDebugInfo::EPILOG -- The IL offset indicates an epilog -// -// The IL offset must be in the range [0 .. 0x3fffffff]. This is because we steal -// the top two bits in IL_OFFSETX for flags, but we want the maximum range to be the same -// for both types. The IL value can't be larger than the maximum IL offset of the function -// being compiled. -// -// Blocks and statements never store one of the ICorDebugInfo values, even for IL_OFFSETX types. These are -// only stored in the IPmappingDsc struct, ipmdILoffsx field. - typedef unsigned IL_OFFSET; -const IL_OFFSET BAD_IL_OFFSET = 0x80000000; -const IL_OFFSET MAX_IL_OFFSET = 0x3fffffff; - -typedef unsigned IL_OFFSETX; // IL_OFFSET with stack-empty or call-instruction bit -const IL_OFFSETX IL_OFFSETX_STKBIT = 0x80000000; // Note: this bit is set when the stack is NOT empty! -const IL_OFFSETX IL_OFFSETX_CALLINSTRUCTIONBIT = 0x40000000; // Set when the IL offset is for a call instruction. -const IL_OFFSETX IL_OFFSETX_BITS = IL_OFFSETX_STKBIT | IL_OFFSETX_CALLINSTRUCTIONBIT; - -IL_OFFSET jitGetILoffs(IL_OFFSETX offsx); -IL_OFFSET jitGetILoffsAny(IL_OFFSETX offsx); -bool jitIsStackEmpty(IL_OFFSETX offsx); -bool jitIsCallInstruction(IL_OFFSETX offsx); +const IL_OFFSET BAD_IL_OFFSET = 0xffffffff; const unsigned BAD_VAR_NUM = UINT_MAX; diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index f62ab8a5e2ffc..03ea2cb7aad21 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -6364,8 +6364,9 @@ void Compiler::fgMorphCallInline(GenTreeCall* call, InlineResult* inlineResult) // Is this call an inline candidate? if (call->IsInlineCandidate()) { + InlineContext* createdContext = nullptr; // Attempt the inline - fgMorphCallInlineHelper(call, inlineResult); + fgMorphCallInlineHelper(call, inlineResult, &createdContext); // We should have made up our minds one way or another.... assert(inlineResult->IsDecided()); @@ -6374,13 +6375,21 @@ void Compiler::fgMorphCallInline(GenTreeCall* call, InlineResult* inlineResult) if (inlineResult->IsFailure()) { + if (createdContext != nullptr) + { + // We created a context before we got to the failure, so mark + // it as failed in the tree. + createdContext->SetFailed(inlineResult); + } + else + { #ifdef DEBUG - - // Before we do any cleanup, create a failing InlineContext to - // capture details of the inlining attempt. - m_inlineStrategy->NewFailure(fgMorphStmt, inlineResult); - + // In debug we always put all inline attempts into the inline tree. + InlineContext* ctx = + m_inlineStrategy->NewContext(call->gtInlineCandidateInfo->inlinersContext, fgMorphStmt, call); + ctx->SetFailed(inlineResult); #endif + } inliningFailed = true; @@ -6416,14 +6425,28 @@ void Compiler::fgMorphCallInline(GenTreeCall* call, InlineResult* inlineResult) } } -/***************************************************************************** - * Helper to attempt to inline a call - * Sets success/failure in inline result - * If success, modifies current method's IR with inlinee's IR - * If failed, undoes any speculative modifications to current method - */ - -void Compiler::fgMorphCallInlineHelper(GenTreeCall* call, InlineResult* result) +//------------------------------------------------------------------------------ +// fgMorphCallInlineHelper: Helper to attempt to inline a call +// +// Arguments: +// call - call expression to inline, inline candidate +// result - result to set to success or failure +// createdContext - The context that was created if the inline attempt got to the inliner. +// +// Notes: +// Attempts to inline the call. +// +// If successful, callee's IR is inserted in place of the call, and +// is marked with an InlineContext. +// +// If unsuccessful, the transformations done in anticipation of a +// possible inline are undone, and the candidate flag on the call +// is cleared. +// +// If a context was created because we got to the importer then it is output by this function. +// If the inline succeeded, this context will already be marked as successful. If it failed and +// a context is returned, then it will not have been marked as success or failed. +void Compiler::fgMorphCallInlineHelper(GenTreeCall* call, InlineResult* result, InlineContext** createdContext) { // Don't expect any surprises here. assert(result->IsCandidate()); @@ -6483,7 +6506,7 @@ void Compiler::fgMorphCallInlineHelper(GenTreeCall* call, InlineResult* result) // Invoke the compiler to inline the call. // - fgInvokeInlineeCompiler(call, result); + fgInvokeInlineeCompiler(call, result, createdContext); if (result->IsFailure()) { @@ -8066,7 +8089,7 @@ GenTree* Compiler::fgCreateCallDispatcherAndGetResult(GenTreeCall* orig CORINFO_METHOD_HANDLE dispatcherHnd) { GenTreeCall* callDispatcherNode = - gtNewCallNode(CT_USER_FUNC, dispatcherHnd, TYP_VOID, nullptr, fgMorphStmt->GetILOffsetX()); + gtNewCallNode(CT_USER_FUNC, dispatcherHnd, TYP_VOID, nullptr, fgMorphStmt->GetDebugInfo()); // The dispatcher has signature // void DispatchTailCalls(void* callersRetAddrSlot, void* callTarget, void* retValue) @@ -8679,14 +8702,14 @@ void Compiler::fgMorphRecursiveFastTailCallIntoLoop(BasicBlock* block, GenTreeCa // Transform recursive tail call into a loop. - Statement* earlyArgInsertionPoint = lastStmt; - IL_OFFSETX callILOffset = lastStmt->GetILOffsetX(); + Statement* earlyArgInsertionPoint = lastStmt; + const DebugInfo& callDI = lastStmt->GetDebugInfo(); // Hoist arg setup statement for the 'this' argument. GenTreeCall::Use* thisArg = recursiveTailCall->gtCallThisArg; if ((thisArg != nullptr) && !thisArg->GetNode()->IsNothingNode() && !thisArg->GetNode()->IsArgPlaceHolderNode()) { - Statement* thisArgStmt = gtNewStmt(thisArg->GetNode(), callILOffset); + Statement* thisArgStmt = gtNewStmt(thisArg->GetNode(), callDI); fgInsertStmtBefore(block, earlyArgInsertionPoint, thisArgStmt); } @@ -8744,7 +8767,7 @@ void Compiler::fgMorphRecursiveFastTailCallIntoLoop(BasicBlock* block, GenTreeCa if ((earlyArg->gtFlags & GTF_LATE_ARG) != 0) { // This is a setup node so we need to hoist it. - Statement* earlyArgStmt = gtNewStmt(earlyArg, callILOffset); + Statement* earlyArgStmt = gtNewStmt(earlyArg, callDI); fgInsertStmtBefore(block, earlyArgInsertionPoint, earlyArgStmt); } else @@ -8758,7 +8781,7 @@ void Compiler::fgMorphRecursiveFastTailCallIntoLoop(BasicBlock* block, GenTreeCa fgAssignRecursiveCallArgToCallerParam(earlyArg, curArgTabEntry, fgGetArgTabEntryParameterLclNum(recursiveTailCall, curArgTabEntry), - block, callILOffset, tmpAssignmentInsertionPoint, + block, callDI, tmpAssignmentInsertionPoint, paramAssignmentInsertionPoint); if ((tmpAssignmentInsertionPoint == lastStmt) && (paramAssignStmt != nullptr)) { @@ -8785,7 +8808,7 @@ void Compiler::fgMorphRecursiveFastTailCallIntoLoop(BasicBlock* block, GenTreeCa fgAssignRecursiveCallArgToCallerParam(lateArg, curArgTabEntry, fgGetArgTabEntryParameterLclNum(recursiveTailCall, curArgTabEntry), - block, callILOffset, tmpAssignmentInsertionPoint, + block, callDI, tmpAssignmentInsertionPoint, paramAssignmentInsertionPoint); if ((tmpAssignmentInsertionPoint == lastStmt) && (paramAssignStmt != nullptr)) @@ -8805,7 +8828,7 @@ void Compiler::fgMorphRecursiveFastTailCallIntoLoop(BasicBlock* block, GenTreeCa var_types thisType = lvaTable[info.compThisArg].TypeGet(); GenTree* arg0 = gtNewLclvNode(lvaArg0Var, thisType); GenTree* arg0Assignment = gtNewAssignNode(arg0, gtNewLclvNode(info.compThisArg, thisType)); - Statement* arg0AssignmentStmt = gtNewStmt(arg0Assignment, callILOffset); + Statement* arg0AssignmentStmt = gtNewStmt(arg0Assignment, callDI); fgInsertStmtBefore(block, paramAssignmentInsertionPoint, arg0AssignmentStmt); } @@ -8847,7 +8870,7 @@ void Compiler::fgMorphRecursiveFastTailCallIntoLoop(BasicBlock* block, GenTreeCa GenTree* zero = gtNewZeroConNode(genActualType(lclType)); init = gtNewAssignNode(lcl, zero); } - Statement* initStmt = gtNewStmt(init, callILOffset); + Statement* initStmt = gtNewStmt(init, callDI); fgInsertStmtBefore(block, lastStmt, initStmt); } } @@ -8897,13 +8920,13 @@ void Compiler::fgMorphRecursiveFastTailCallIntoLoop(BasicBlock* block, GenTreeCa // Return Value: // parameter assignment statement if one was inserted; nullptr otherwise. -Statement* Compiler::fgAssignRecursiveCallArgToCallerParam(GenTree* arg, - fgArgTabEntry* argTabEntry, - unsigned lclParamNum, - BasicBlock* block, - IL_OFFSETX callILOffset, - Statement* tmpAssignmentInsertionPoint, - Statement* paramAssignmentInsertionPoint) +Statement* Compiler::fgAssignRecursiveCallArgToCallerParam(GenTree* arg, + fgArgTabEntry* argTabEntry, + unsigned lclParamNum, + BasicBlock* block, + const DebugInfo& callDI, + Statement* tmpAssignmentInsertionPoint, + Statement* paramAssignmentInsertionPoint) { // Call arguments should be assigned to temps first and then the temps should be assigned to parameters because // some argument trees may reference parameters directly. @@ -8953,7 +8976,7 @@ Statement* Compiler::fgAssignRecursiveCallArgToCallerParam(GenTree* arg, GenTree* tempSrc = arg; GenTree* tempDest = gtNewLclvNode(tmpNum, tempSrc->gtType); GenTree* tmpAssignNode = gtNewAssignNode(tempDest, tempSrc); - Statement* tmpAssignStmt = gtNewStmt(tmpAssignNode, callILOffset); + Statement* tmpAssignStmt = gtNewStmt(tmpAssignNode, callDI); fgInsertStmtBefore(block, tmpAssignmentInsertionPoint, tmpAssignStmt); argInTemp = gtNewLclvNode(tmpNum, tempSrc->gtType); } @@ -8963,7 +8986,7 @@ Statement* Compiler::fgAssignRecursiveCallArgToCallerParam(GenTree* arg, assert(paramDsc->lvIsParam); GenTree* paramDest = gtNewLclvNode(lclParamNum, paramDsc->lvType); GenTree* paramAssignNode = gtNewAssignNode(paramDest, argInTemp); - paramAssignStmt = gtNewStmt(paramAssignNode, callILOffset); + paramAssignStmt = gtNewStmt(paramAssignNode, callDI); fgInsertStmtBefore(block, paramAssignmentInsertionPoint, paramAssignStmt); } @@ -9015,7 +9038,7 @@ GenTree* Compiler::fgMorphCall(GenTreeCall* call) assg = fgMorphTree(assg); // Create the assignment statement and insert it before the current statement. - Statement* assgStmt = gtNewStmt(assg, compCurStmt->GetILOffsetX()); + Statement* assgStmt = gtNewStmt(assg, compCurStmt->GetDebugInfo()); fgInsertStmtBefore(compCurBB, compCurStmt, assgStmt); // Return the temp. @@ -15978,9 +16001,9 @@ void Compiler::fgMergeBlockReturn(BasicBlock* block) noway_assert(ret->OperGet() == GT_RETURN); noway_assert(ret->gtGetOp1() != nullptr); - Statement* pAfterStatement = lastStmt; - IL_OFFSETX offset = lastStmt->GetILOffsetX(); - GenTree* tree = gtNewTempAssign(genReturnLocal, ret->gtGetOp1(), &pAfterStatement, offset, block); + Statement* pAfterStatement = lastStmt; + const DebugInfo& di = lastStmt->GetDebugInfo(); + GenTree* tree = gtNewTempAssign(genReturnLocal, ret->gtGetOp1(), &pAfterStatement, di, block); if (tree->OperIsCopyBlkOp()) { tree = fgMorphCopyBlock(tree); @@ -15998,7 +16021,7 @@ void Compiler::fgMergeBlockReturn(BasicBlock* block) { // gtNewTempAssign inserted additional statements after last fgRemoveStmt(block, lastStmt); - Statement* newStmt = gtNewStmt(tree, offset); + Statement* newStmt = gtNewStmt(tree, di); fgInsertStmtAfter(block, pAfterStatement, newStmt); lastStmt = newStmt; } @@ -16456,23 +16479,23 @@ void Compiler::fgExpandQmarkForCastInstOf(BasicBlock* block, Statement* stmt) // Append cond1 as JTRUE to cond1Block GenTree* jmpTree = gtNewOperNode(GT_JTRUE, TYP_VOID, condExpr); - Statement* jmpStmt = fgNewStmtFromTree(jmpTree, stmt->GetILOffsetX()); + Statement* jmpStmt = fgNewStmtFromTree(jmpTree, stmt->GetDebugInfo()); fgInsertStmtAtEnd(cond1Block, jmpStmt); // Append cond2 as JTRUE to cond2Block jmpTree = gtNewOperNode(GT_JTRUE, TYP_VOID, cond2Expr); - jmpStmt = fgNewStmtFromTree(jmpTree, stmt->GetILOffsetX()); + jmpStmt = fgNewStmtFromTree(jmpTree, stmt->GetDebugInfo()); fgInsertStmtAtEnd(cond2Block, jmpStmt); // AsgBlock should get tmp = op1 assignment. trueExpr = gtNewTempAssign(dst->AsLclVarCommon()->GetLclNum(), trueExpr); - Statement* trueStmt = fgNewStmtFromTree(trueExpr, stmt->GetILOffsetX()); + Statement* trueStmt = fgNewStmtFromTree(trueExpr, stmt->GetDebugInfo()); fgInsertStmtAtEnd(asgBlock, trueStmt); // Since we are adding helper in the JTRUE false path, reverse the cond2 and add the helper. gtReverseCond(cond2Expr); GenTree* helperExpr = gtNewTempAssign(dst->AsLclVarCommon()->GetLclNum(), true2Expr); - Statement* helperStmt = fgNewStmtFromTree(helperExpr, stmt->GetILOffsetX()); + Statement* helperStmt = fgNewStmtFromTree(helperExpr, stmt->GetDebugInfo()); fgInsertStmtAtEnd(helperBlock, helperStmt); // Finally remove the nested qmark stmt. @@ -16674,7 +16697,7 @@ void Compiler::fgExpandQmarkStmt(BasicBlock* block, Statement* stmt) } GenTree* jmpTree = gtNewOperNode(GT_JTRUE, TYP_VOID, qmark->gtGetOp1()); - Statement* jmpStmt = fgNewStmtFromTree(jmpTree, stmt->GetILOffsetX()); + Statement* jmpStmt = fgNewStmtFromTree(jmpTree, stmt->GetDebugInfo()); fgInsertStmtAtEnd(condBlock, jmpStmt); // Remove the original qmark statement. @@ -16700,7 +16723,7 @@ void Compiler::fgExpandQmarkStmt(BasicBlock* block, Statement* stmt) { trueExpr = gtNewTempAssign(lclNum, trueExpr); } - Statement* trueStmt = fgNewStmtFromTree(trueExpr, stmt->GetILOffsetX()); + Statement* trueStmt = fgNewStmtFromTree(trueExpr, stmt->GetDebugInfo()); fgInsertStmtAtEnd(thenBlock, trueStmt); } @@ -16711,7 +16734,7 @@ void Compiler::fgExpandQmarkStmt(BasicBlock* block, Statement* stmt) { falseExpr = gtNewTempAssign(lclNum, falseExpr); } - Statement* falseStmt = fgNewStmtFromTree(falseExpr, stmt->GetILOffsetX()); + Statement* falseStmt = fgNewStmtFromTree(falseExpr, stmt->GetDebugInfo()); fgInsertStmtAtEnd(elseBlock, falseStmt); } diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index 10f14b2b53cf7..b179dcc96ca55 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -4399,7 +4399,7 @@ bool Compiler::optInvertWhileLoop(BasicBlock* block) if (opts.compDbgInfo) { - clonedStmt->SetILOffsetX(stmt->GetILOffsetX()); + clonedStmt->SetDebugInfo(stmt->GetDebugInfo()); } clonedStmt->SetCompilerAdded(); diff --git a/src/coreclr/jit/rationalize.cpp b/src/coreclr/jit/rationalize.cpp index 9887153d54d47..13e9705236c13 100644 --- a/src/coreclr/jit/rationalize.cpp +++ b/src/coreclr/jit/rationalize.cpp @@ -957,13 +957,16 @@ PhaseStatus Rationalizer::DoPhase() { BlockRange().InsertAtEnd(LIR::Range(statement->GetTreeList(), statement->GetRootNode())); - // If this statement has correct offset information, change it into an IL offset - // node and insert it into the LIR. - if (statement->GetILOffsetX() != BAD_IL_OFFSET) + // If this statement has correct debug information, change it + // into a debug info node and insert it into the LIR. Currently + // we do not support describing IL offsets in inlinees in the + // emitter, so we normalize all debug info to be in the inline + // root here. + DebugInfo di = statement->GetDebugInfo().GetRoot(); + if (di.IsValid()) { - assert(!statement->IsPhiDefnStmt()); - GenTreeILOffset* ilOffset = new (comp, GT_IL_OFFSET) - GenTreeILOffset(statement->GetILOffsetX() DEBUGARG(statement->GetLastILOffset())); + GenTreeILOffset* ilOffset = + new (comp, GT_IL_OFFSET) GenTreeILOffset(di DEBUGARG(statement->GetLastILOffset())); BlockRange().InsertBefore(statement->GetTreeList(), ilOffset); }