From 38263bf4dc827b71435f0002548070dc2f2cc33b Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Fri, 22 May 2026 11:32:16 +0200 Subject: [PATCH 01/22] JIT: Report managed-return-values as a native var --- src/coreclr/inc/cordebuginfo.h | 5 +- src/coreclr/jit/codegenarmarch.cpp | 12 +-- src/coreclr/jit/codegencommon.cpp | 82 +++++++++++++++++++ src/coreclr/jit/codegeninterface.h | 11 ++- src/coreclr/jit/codegenlinear.cpp | 1 + src/coreclr/jit/codegenwasm.cpp | 12 +-- src/coreclr/jit/codegenxarch.cpp | 13 +-- src/coreclr/jit/compiler.h | 7 +- src/coreclr/jit/ee_il_dll.cpp | 24 ++++-- src/coreclr/jit/emit.h | 3 +- src/coreclr/jit/emitarm.cpp | 6 -- src/coreclr/jit/emitarm64.cpp | 6 -- src/coreclr/jit/emitloongarch64.cpp | 6 -- src/coreclr/jit/emitriscv64.cpp | 6 -- src/coreclr/jit/emitwasm.cpp | 6 -- src/coreclr/jit/emitxarch.cpp | 6 -- src/coreclr/jit/scopeinfo.cpp | 27 ++++-- .../JitInterface/CorInfoTypes.VarInfo.cs | 1 + .../tools/Common/JitInterface/CorInfoTypes.cs | 4 +- .../ReadyToRun/DebugInfoTableNode.cs | 12 ++- src/coreclr/vm/debuginfostore.cpp | 31 +++++-- 21 files changed, 181 insertions(+), 100 deletions(-) diff --git a/src/coreclr/inc/cordebuginfo.h b/src/coreclr/inc/cordebuginfo.h index 16b8b7a85c9887..e5e2cef2a62f7d 100644 --- a/src/coreclr/inc/cordebuginfo.h +++ b/src/coreclr/inc/cordebuginfo.h @@ -388,7 +388,9 @@ class ICorDebugInfo UNKNOWN_ILNUM = -4, // Unknown variable - MAX_ILNUM = -4 // Sentinel value. This should be set to the largest magnitude value in th enum + CALL_RETURN_ILNUM = -5, // The return value of a call + + MAX_ILNUM = -5 // Sentinel value. This should be set to the largest magnitude value in th enum // so that the compression routines know the enum's range. }; @@ -403,6 +405,7 @@ class ICorDebugInfo { uint32_t startOffset; uint32_t endOffset; + uint32_t callReturnValueILOffset; uint32_t varNumber; VarLoc loc; }; diff --git a/src/coreclr/jit/codegenarmarch.cpp b/src/coreclr/jit/codegenarmarch.cpp index c637154a6ed3fa..197966de26ebff 100644 --- a/src/coreclr/jit/codegenarmarch.cpp +++ b/src/coreclr/jit/codegenarmarch.cpp @@ -3255,17 +3255,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call) params.isJump = call->IsFastTailCall(); params.hasAsyncRet = call->IsAsync(); - - // 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. - if (m_compiler->opts.compDbgInfo && m_compiler->genCallSite2DebugInfoMap != nullptr && !call->IsTailCall()) - { - DebugInfo di; - (void)m_compiler->genCallSite2DebugInfoMap->Lookup(call, &di); - params.debugInfo = di; - } + params.returnValueCall = call; #ifdef DEBUG // Pass the call signature information down into the emitter so the emitter can associate diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index 8a542e1185284b..c7d594230fcc1c 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -1759,6 +1759,88 @@ void CodeGen::genEmitCallWithCurrentGC(EmitCallParams& params) params.gcrefRegs = gcInfo.gcRegGCrefSetCur; params.byrefRegs = gcInfo.gcRegByrefSetCur; GetEmitter()->emitIns_Call(params); + + // Emit an entry for managed return value reporting, if needed. + GenTreeCall* call = params.returnValueCall; + JITDUMP("Emit ret val for [%06u]\n", call == nullptr ? 0 : Compiler::dspTreeID(call)); + if ((call == nullptr) || !m_compiler->opts.compDbgInfo || (m_compiler->genCallSite2DebugInfoMap == nullptr) || params.isJump) + { + return; + } + + if (call->gtReturnType == TYP_VOID) + { + return; + } + + DebugInfo di; + if (!m_compiler->genCallSite2DebugInfoMap->Lookup(call, &di)) + { + return; + } + + emitLocation retLoc; + retLoc.CaptureLocation(GetEmitter()); + + CodeGenInterface::EmittedCallReturnInfo info; + info.callILOffset = di.GetRoot().GetLocation().GetOffset(); + info.returnLocation = retLoc; + + CallArg* retBuf = call->gtArgs.GetRetBufferArg(); + if (retBuf != nullptr) + { + GenTree* node = retBuf->GetNode(); + assert(node->OperIsPutArg()); + + node = node->gtGetOp1()->gtSkipReloadOrCopy(); + if (!node->OperIs(GT_LCL_ADDR)) + { + return; + } + + unsigned lclNum = node->AsLclVarCommon()->GetLclNum(); + unsigned lclOffs = node->AsLclVarCommon()->GetLclOffs(); + info.returnValueLoc = getSiVarLoc(m_compiler->lvaGetDesc(lclNum), lclOffs, 0); + } + else if (call->HasMultiRegRetVal()) + { + const ReturnTypeDesc* retDesc = call->GetReturnTypeDesc(); + unsigned numRegs = retDesc->GetReturnRegCount(); + if (numRegs > 2) + { + // Cannot encode more than 2 registers. + return; + } + + assert(numRegs == 2); + info.returnValueLoc.storeVariableInRegisters( + retDesc->GetABIReturnReg(0, call->GetUnmanagedCallConv()), + retDesc->GetABIReturnReg(1, call->GetUnmanagedCallConv())); + } + else if (varTypeIsFloating(call)) + { +#ifdef TARGET_X86 + info.returnValueLoc.vlType = VLT_FPSTK; + info.returnValueLoc.vlFPstk.vlfReg = 0; +#else + info.returnValueLoc.storeVariableInRegisters(REG_FLOATRET, REG_NA); +#endif + } + else if (varTypeUsesFloatReg(call)) + { + info.returnValueLoc.storeVariableInRegisters(REG_FLOATRET, REG_NA); + } + else + { + info.returnValueLoc.storeVariableInRegisters(REG_INTRET, REG_NA); + } + + if (emittedCallReturnInfo == nullptr) + { + emittedCallReturnInfo = new (m_compiler, CMK_DebugInfo) jitstd::vector(m_compiler->getAllocator(CMK_DebugInfo)); + } + + emittedCallReturnInfo->push_back(info); } /***************************************************************************** diff --git a/src/coreclr/jit/codegeninterface.h b/src/coreclr/jit/codegeninterface.h index ea160060717233..f279e371948dd6 100644 --- a/src/coreclr/jit/codegeninterface.h +++ b/src/coreclr/jit/codegeninterface.h @@ -646,8 +646,15 @@ class CodeGenInterface const LclVarDsc* varDsc, var_types type, regNumber baseReg, int offset, bool isFramePointerUsed); }; + struct EmittedCallReturnInfo + { + IL_OFFSET callILOffset; + emitLocation returnLocation; + siVarLoc returnValueLoc; + }; + public: - siVarLoc getSiVarLoc(const LclVarDsc* varDsc, unsigned int stackLevel) const; + siVarLoc getSiVarLoc(const LclVarDsc* varDsc, unsigned offset, unsigned stackLevel) const; #ifdef DEBUG void dumpSiVarLoc(const siVarLoc* varLoc) const; @@ -858,6 +865,8 @@ class CodeGenInterface protected: VariableLiveKeeper* varLiveKeeper; // Used to manage VariableLiveRanges of variables + jitstd::vector* emittedCallReturnInfo; + #ifdef LATE_DISASM public: virtual const char* siRegVarName(size_t offs, size_t size, unsigned reg) = 0; diff --git a/src/coreclr/jit/codegenlinear.cpp b/src/coreclr/jit/codegenlinear.cpp index 9ab94d99448c94..2ea8e5a259181d 100644 --- a/src/coreclr/jit/codegenlinear.cpp +++ b/src/coreclr/jit/codegenlinear.cpp @@ -91,6 +91,7 @@ void CodeGen::genInitialize() } initializeVariableLiveKeeper(); + emittedCallReturnInfo = new (m_compiler, CMK_DebugInfo) jitstd::vector(m_compiler->getAllocator(CMK_DebugInfo)); genPendingCallLabel = nullptr; diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 650e27db737fee..c89efd918c5dc0 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -2473,17 +2473,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call) EmitCallParams params; params.isJump = call->IsFastTailCall(); params.hasAsyncRet = call->IsAsync(); - - // 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. - if (m_compiler->opts.compDbgInfo && m_compiler->genCallSite2DebugInfoMap != nullptr && !call->IsTailCall()) - { - DebugInfo di; - (void)m_compiler->genCallSite2DebugInfoMap->Lookup(call, &di); - params.debugInfo = di; - } + params.returnValueCall = call; #ifdef DEBUG // Pass the call signature information down into the emitter so the emitter can associate diff --git a/src/coreclr/jit/codegenxarch.cpp b/src/coreclr/jit/codegenxarch.cpp index 2ee68ebbe42318..e4e40e9886625f 100644 --- a/src/coreclr/jit/codegenxarch.cpp +++ b/src/coreclr/jit/codegenxarch.cpp @@ -6196,18 +6196,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call X86_ARG(target_ssize_t stackA params.isJump = call->IsFastTailCall(); params.hasAsyncRet = call->IsAsync(); - - // We need to propagate the IL offset 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. - - if (m_compiler->opts.compDbgInfo && m_compiler->genCallSite2DebugInfoMap != nullptr && !call->IsTailCall()) - { - DebugInfo di; - (void)m_compiler->genCallSite2DebugInfoMap->Lookup(call, &di); - params.debugInfo = di; - } + params.returnValueCall = call; #ifdef DEBUG // Pass the call signature information down into the emitter so the emitter can associate diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 583892ca426796..26e6ce1f2d5a2d 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -9516,19 +9516,22 @@ class Compiler void eeGetVars(); - unsigned eeVarsCount; + unsigned eeVarsCount = 0; + unsigned eeVarsCapacity = 0; struct VarResultInfo { UNATIVE_OFFSET startOffset; UNATIVE_OFFSET endOffset; + uint32_t callReturnValueILOffset; DWORD varNumber; CodeGenInterface::siVarLoc loc; }* eeVars; void eeSetLVcount(unsigned count); void eeSetLVinfo(unsigned which, UNATIVE_OFFSET startOffs, - UNATIVE_OFFSET length, + UNATIVE_OFFSET endOffs, + uint32_t callReturnValILOffs, unsigned varNum, const CodeGenInterface::siVarLoc& loc); void eeSetLVdone(); diff --git a/src/coreclr/jit/ee_il_dll.cpp b/src/coreclr/jit/ee_il_dll.cpp index b8835143440fbc..9db995cdbaa9e3 100644 --- a/src/coreclr/jit/ee_il_dll.cpp +++ b/src/coreclr/jit/ee_il_dll.cpp @@ -670,10 +670,10 @@ void Compiler::eeSetLVcount(unsigned count) JITDUMP("VarLocInfo count is %d\n", count); - eeVarsCount = count; - if (eeVarsCount) + eeVarsCapacity = count; + if (count > 0) { - eeVars = (VarResultInfo*)info.compCompHnd->allocateArray(eeVarsCount * sizeof(eeVars[0])); + eeVars = (VarResultInfo*)info.compCompHnd->allocateArray(count * sizeof(eeVars[0])); } else { @@ -683,7 +683,8 @@ void Compiler::eeSetLVcount(unsigned count) void Compiler::eeSetLVinfo(unsigned which, UNATIVE_OFFSET startOffs, - UNATIVE_OFFSET length, + UNATIVE_OFFSET endOffs, + uint32_t callReturnValueILOffset, unsigned varNum, const CodeGenInterface::siVarLoc& varLoc) { @@ -691,13 +692,13 @@ void Compiler::eeSetLVinfo(unsigned which, // This is checked in siInit() assert(opts.compScopeInfo); - assert(eeVarsCount > 0); - assert(which < eeVarsCount); + assert(which < eeVarsCapacity); if (eeVars != nullptr) { eeVars[which].startOffset = startOffs; - eeVars[which].endOffset = startOffs + length; + eeVars[which].endOffset = endOffs; + eeVars[which].callReturnValueILOffset = callReturnValueILOffset; eeVars[which].varNumber = varNum; eeVars[which].loc = varLoc; } @@ -870,7 +871,12 @@ void Compiler::eeDispVar(ICorDebugInfo::NativeVarInfo* var) { name = "typeCtx"; } - if (0 <= var->varNumber && var->varNumber < lvaCount) + + if (var->varNumber == (DWORD)ICorDebugInfo::CALL_RETURN_ILNUM) + { + int printed = printf("(call %03u)", var->callReturnValueILOffset); + } + else if (0 <= var->varNumber && var->varNumber < lvaCount) { printf("("); gtDispLclVar(var->varNumber, false); @@ -878,7 +884,7 @@ void Compiler::eeDispVar(ICorDebugInfo::NativeVarInfo* var) } else { - printf("(%10s)", (VarNameToStr(name) == nullptr) ? "UNKNOWN" : VarNameToStr(name)); + printf("(%8s)", (VarNameToStr(name) == nullptr) ? "UNKNOWN" : VarNameToStr(name)); } printf(" : From %08Xh to %08Xh, in ", var->startOffset, var->endOffset); diff --git a/src/coreclr/jit/emit.h b/src/coreclr/jit/emit.h index 08326526046229..690572766f6135 100644 --- a/src/coreclr/jit/emit.h +++ b/src/coreclr/jit/emit.h @@ -482,13 +482,14 @@ struct EmitCallParams BitVec ptrVars = BitVecOps::UninitVal(); regMaskTP gcrefRegs = RBM_NONE; regMaskTP byrefRegs = RBM_NONE; - DebugInfo debugInfo; regNumber ireg = REG_NA; regNumber xreg = REG_NA; unsigned xmul = 0; ssize_t disp = 0; bool isJump = false; bool noSafePoint = false; + // If this call should have managed return value debug info associated with it, this is the call to associate it with. + GenTreeCall* returnValueCall = nullptr; #ifdef TARGET_WASM CORINFO_WASM_TYPE_SYMBOL_HANDLE wasmSignature = nullptr; #endif diff --git a/src/coreclr/jit/emitarm.cpp b/src/coreclr/jit/emitarm.cpp index 8532b6dbdb3cea..e3c00969d1d226 100644 --- a/src/coreclr/jit/emitarm.cpp +++ b/src/coreclr/jit/emitarm.cpp @@ -4683,12 +4683,6 @@ void emitter::emitIns_Call(const EmitCallParams& params) } #endif - /* Managed RetVal: emit sequence point for the call */ - if (m_compiler->opts.compDbgInfo && params.debugInfo.GetLocation().IsValid()) - { - codeGen->genIPmappingAdd(IPmappingDscKind::Normal, params.debugInfo, false); - } - /* We need to allocate the appropriate instruction descriptor based on whether this is a direct/indirect call, and whether we need to diff --git a/src/coreclr/jit/emitarm64.cpp b/src/coreclr/jit/emitarm64.cpp index ec30c012bd5ee5..df868fd3d7d137 100644 --- a/src/coreclr/jit/emitarm64.cpp +++ b/src/coreclr/jit/emitarm64.cpp @@ -9493,12 +9493,6 @@ void emitter::emitIns_Call(const EmitCallParams& params) } #endif - /* Managed RetVal: emit sequence point for the call */ - if (m_compiler->opts.compDbgInfo && params.debugInfo.GetLocation().IsValid()) - { - codeGen->genIPmappingAdd(IPmappingDscKind::Normal, params.debugInfo, false); - } - /* We need to allocate the appropriate instruction descriptor based on whether this is a direct/indirect call, and whether we need to diff --git a/src/coreclr/jit/emitloongarch64.cpp b/src/coreclr/jit/emitloongarch64.cpp index 716e15f392627f..dedb4aaf034795 100644 --- a/src/coreclr/jit/emitloongarch64.cpp +++ b/src/coreclr/jit/emitloongarch64.cpp @@ -2460,12 +2460,6 @@ void emitter::emitIns_Call(const EmitCallParams& params) } #endif - /* Managed RetVal: emit sequence point for the call */ - if (m_compiler->opts.compDbgInfo && params.debugInfo.GetLocation().IsValid()) - { - codeGen->genIPmappingAdd(IPmappingDscKind::Normal, params.debugInfo, false); - } - /* We need to allocate the appropriate instruction descriptor based on whether this is a direct/indirect call, and whether we need to diff --git a/src/coreclr/jit/emitriscv64.cpp b/src/coreclr/jit/emitriscv64.cpp index f1de046359846e..6ecd43185f330b 100644 --- a/src/coreclr/jit/emitriscv64.cpp +++ b/src/coreclr/jit/emitriscv64.cpp @@ -1941,12 +1941,6 @@ void emitter::emitIns_Call(const EmitCallParams& params) emitLoadImmediate(EA_PTRSIZE, params.ireg, imm); // upper bits } - /* Managed RetVal: emit sequence point for the call */ - if (m_compiler->opts.compDbgInfo && params.debugInfo.GetLocation().IsValid()) - { - codeGen->genIPmappingAdd(IPmappingDscKind::Normal, params.debugInfo, false); - } - /* We need to allocate the appropriate instruction descriptor based on whether this is a direct/indirect call, and whether we need to diff --git a/src/coreclr/jit/emitwasm.cpp b/src/coreclr/jit/emitwasm.cpp index e897fd1126a255..2a26a49a8683b1 100644 --- a/src/coreclr/jit/emitwasm.cpp +++ b/src/coreclr/jit/emitwasm.cpp @@ -179,12 +179,6 @@ void emitter::emitIns_Call(const EmitCallParams& params) assert(params.callType < EC_COUNT); assert((params.callType == EC_FUNC_TOKEN) || (params.addr == nullptr)); - /* Managed RetVal: emit sequence point for the call */ - if (m_compiler->opts.compDbgInfo && params.debugInfo.GetLocation().IsValid()) - { - codeGen->genIPmappingAdd(IPmappingDscKind::Normal, params.debugInfo, false); - } - assert(params.wasmSignature != nullptr); /* diff --git a/src/coreclr/jit/emitxarch.cpp b/src/coreclr/jit/emitxarch.cpp index 57e520a12818a4..3be7f7c4b0f13b 100644 --- a/src/coreclr/jit/emitxarch.cpp +++ b/src/coreclr/jit/emitxarch.cpp @@ -11352,12 +11352,6 @@ void emitter::emitIns_Call(const EmitCallParams& params) } #endif - /* Managed RetVal: emit sequence point for the call */ - if (m_compiler->opts.compDbgInfo && params.debugInfo.IsValid()) - { - codeGen->genIPmappingAdd(IPmappingDscKind::Normal, params.debugInfo, false); - } - /* We need to allocate the appropriate instruction descriptor based on whether this is a direct/indirect call, and whether we need to diff --git a/src/coreclr/jit/scopeinfo.cpp b/src/coreclr/jit/scopeinfo.cpp index 9fc32e0fb48798..e21eb70c9fcfee 100644 --- a/src/coreclr/jit/scopeinfo.cpp +++ b/src/coreclr/jit/scopeinfo.cpp @@ -486,6 +486,7 @@ CodeGenInterface::siVarLoc::siVarLoc(const LclVarDsc* varDsc, regNumber baseReg, // // Arguments: // varDsc - the variable it is desired to build the "siVarLoc". +// offset - offset into the variable to add // stackLevel - the current stack level. If the stack pointer changes in // the function, we must adjust stack pointer-based local // variable offsets to compensate. @@ -494,12 +495,12 @@ CodeGenInterface::siVarLoc::siVarLoc(const LclVarDsc* varDsc, regNumber baseReg, // A "siVarLoc" representing the variable location, which could live // in a register, an stack position, or a combination of both. // -CodeGenInterface::siVarLoc CodeGenInterface::getSiVarLoc(const LclVarDsc* varDsc, unsigned int stackLevel) const +CodeGenInterface::siVarLoc CodeGenInterface::getSiVarLoc(const LclVarDsc* varDsc, unsigned offset, unsigned stackLevel) const { // For stack vars, find the base register, and offset regNumber baseReg; - signed offset = varDsc->GetStackOffset(); + offset += varDsc->GetStackOffset(); if (!varDsc->lvFramePointerBased) { @@ -1080,7 +1081,7 @@ void CodeGenInterface::VariableLiveKeeper::siStartVariableLiveRange(const LclVar { // Build siVarLoc for this born "varDsc" CodeGenInterface::siVarLoc varLocation = - m_compiler->codeGen->getSiVarLoc(varDsc, m_compiler->codeGen->getCurrentStackLevel()); + m_compiler->codeGen->getSiVarLoc(varDsc, 0, m_compiler->codeGen->getCurrentStackLevel()); VariableLiveDescriptor* varLiveDsc = &m_vlrLiveDsc[varNum]; // this variable live range is valid from this point @@ -1149,7 +1150,7 @@ void CodeGenInterface::VariableLiveKeeper::siUpdateVariableLiveRange(const LclVa { // Build the location of the variable CodeGenInterface::siVarLoc siVarLoc = - m_compiler->codeGen->getSiVarLoc(varDsc, m_compiler->codeGen->getCurrentStackLevel()); + m_compiler->codeGen->getSiVarLoc(varDsc, 0, m_compiler->codeGen->getCurrentStackLevel()); // Report the home change for this variable VariableLiveDescriptor* varLiveDsc = &m_vlrLiveDsc[varNum]; @@ -1781,9 +1782,7 @@ void CodeGen::genSetScopeInfo() } #endif - unsigned varsLocationsCount = 0; - - varsLocationsCount = (unsigned int)varLiveKeeper->getLiveRangesCount(); + unsigned varsLocationsCount = (unsigned int)(varLiveKeeper->getLiveRangesCount() + emittedCallReturnInfo->size()); if (varsLocationsCount == 0) { @@ -1812,6 +1811,18 @@ void CodeGen::genSetScopeInfo() genSetScopeInfoUsingVariableRanges(); + for (const EmittedCallReturnInfo& callReturnInfo : *emittedCallReturnInfo) + { + UNATIVE_OFFSET retOffset = callReturnInfo.returnLocation.CodeOffset(GetEmitter()); + + m_compiler->eeSetLVinfo( + m_compiler->eeVarsCount++, + retOffset, retOffset + 1, + callReturnInfo.callILOffset, + ICorDebugInfo::CALL_RETURN_ILNUM, + callReturnInfo.returnValueLoc); + } + m_compiler->eeSetLVdone(); } @@ -2001,7 +2012,7 @@ void CodeGen::genSetScopeInfo(unsigned which, #endif // DEBUG - m_compiler->eeSetLVinfo(which, startOffs, length, ilVarNum, *varLoc); + m_compiler->eeSetLVinfo(which, startOffs, startOffs + length, 0, ilVarNum, *varLoc); } /*****************************************************************************/ diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.VarInfo.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.VarInfo.cs index cd6d82f8f511e8..e307bcb2844616 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.VarInfo.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.VarInfo.cs @@ -34,6 +34,7 @@ public struct NativeVarInfo { public uint startOffset; public uint endOffset; + public uint callReturnValueILOffset; public uint varNumber; public VarLoc varLoc; }; diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index f9166214b67a68..ba6d2e9812280c 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -1371,7 +1371,9 @@ public enum ILNum UNKNOWN_ILNUM = -4, // Unknown variable - MAX_ILNUM = -4 // Sentinel value. This should be set to the largest magnitude value in the enum + CALL_RETURN_ILNUM = -5, // The return value of a call + + MAX_ILNUM = -5 // Sentinel value. This should be set to the largest magnitude value in the enum // so that the compression routines know the enum's range. }; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DebugInfoTableNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DebugInfoTableNode.cs index f2a1484bdeea91..0f58b1e2c97780 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DebugInfoTableNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DebugInfoTableNode.cs @@ -254,9 +254,17 @@ public static byte[] CreateVarBlobForMethod(NativeVarInfo[] varInfos, TargetDeta foreach (var nativeVarInfo in varInfos) { - writer.WriteUInt(nativeVarInfo.startOffset); - writer.WriteUInt(nativeVarInfo.endOffset - nativeVarInfo.startOffset); writer.WriteUInt((uint)(nativeVarInfo.varNumber - (int)ILNum.MAX_ILNUM)); + writer.WriteUInt(nativeVarInfo.startOffset); + + if (nativeVarInfo.varNumber == unchecked((uint)ILNum.CALL_RETURN_ILNUM)) + { + writer.WriteUInt(nativeVarInfo.callReturnValueILOffset); + } + else + { + writer.WriteUInt(nativeVarInfo.endOffset - nativeVarInfo.startOffset); + } VarLocType varLocType = nativeVarInfo.varLoc.LocationType; diff --git a/src/coreclr/vm/debuginfostore.cpp b/src/coreclr/vm/debuginfostore.cpp index 585bdabf8de52d..dc5133e052240c 100644 --- a/src/coreclr/vm/debuginfostore.cpp +++ b/src/coreclr/vm/debuginfostore.cpp @@ -143,6 +143,9 @@ class TransferWriter void DoEncodedVarLocType(ICorDebugInfo::VarLocType & dw) { m_w.WriteEncodedU32(dw); } void DoEncodedUnsigned(unsigned & dw) { m_w.WriteEncodedU32(dw); } + void DoImplicitCallReturnValueILOffset(uint32_t& ilOffset) { } + void DoImplicitEndOffset(uint32_t& endOffset, uint32_t startOffset) { } + // Stack offsets are aligned on a DWORD boundary, so that lets us shave off 2 bits. void DoEncodedStackOffset(signed & dwOffset) { @@ -250,6 +253,17 @@ class TransferReader dw = (unsigned) m_r.ReadEncodedU32_NoThrow(); } + void DoImplicitCallReturnValueILOffset(uint32_t& ilOffset) + { + SUPPORTS_DAC; + ilOffset = 0; + } + + void DoImplicitEndOffset(uint32_t& endOffset, uint32_t startOffset) + { + SUPPORTS_DAC; + endOffset = startOffset + 1; + } // Stack offsets are aligned on a DWORD boundary, so that lets us shave off 2 bits. void DoEncodedStackOffset(signed & dwOffset) @@ -340,14 +354,21 @@ static void DoNativeVarInfo( // - VarLoc information. This is a tagged variant. // The entries aren't sorted in any particular order. trans.DoCookie(0xB); - trans.DoEncodedU32(pVar->startOffset); - - - trans.DoEncodedDeltaU32(pVar->endOffset, pVar->startOffset); - // record var number. trans.DoEncodedAdjustedU32(pVar->varNumber, (DWORD) ICorDebugInfo::MAX_ILNUM); + trans.DoEncodedU32(pVar->startOffset); + + if (pVar->varNumber == ICorDebugInfo::CALL_RETURN_ILNUM) + { + trans.DoEncodedU32(pVar->callReturnValueILOffset); + trans.DoImplicitEndOffset(pVar->endOffset, pVar->startOffset); + } + else + { + trans.DoEncodedDeltaU32(pVar->endOffset, pVar->startOffset); + trans.DoImplicitCallReturnValueILOffset(pVar->callReturnValueILOffset); + } // Now write the VarLoc... This is a variant like structure and so we'll get different // compressioned depending on what we've got. From 65e23562c66a1074743dfeb967dc7f6b059c6417 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Fri, 22 May 2026 14:02:38 +0200 Subject: [PATCH 02/22] Bump JIT-EE GUID --- src/coreclr/inc/jiteeversionguid.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index 9f9fadfc081037..85d8a8682bf489 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -37,11 +37,11 @@ #include -constexpr GUID JITEEVersionIdentifier = { /* 5635ae9d-ffa5-4336-b027-a383971e3918 */ - 0x5635ae9d, - 0xffa5, - 0x4336, - {0xb0, 0x27, 0xa3, 0x83, 0x97, 0x1e, 0x39, 0x18} +constexpr GUID JITEEVersionIdentifier = { /* 59df85b8-c0fd-4e40-aea1-68cb2cd916cc */ + 0x59df85b8, + 0xc0fd, + 0x4e40, + {0xae, 0xa1, 0x68, 0xcb, 0x2c, 0xd9, 0x16, 0xcc} }; #endif // JIT_EE_VERSIONING_GUID_H From 2fcf4c1907461b1979756469e6c83d0e95d5d9df Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Fri, 22 May 2026 14:27:29 +0200 Subject: [PATCH 03/22] Fix theoretical issue --- src/coreclr/jit/codegencommon.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index c7d594230fcc1c..1adf37528d2a50 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -1800,7 +1800,17 @@ void CodeGen::genEmitCallWithCurrentGC(EmitCallParams& params) unsigned lclNum = node->AsLclVarCommon()->GetLclNum(); unsigned lclOffs = node->AsLclVarCommon()->GetLclOffs(); - info.returnValueLoc = getSiVarLoc(m_compiler->lvaGetDesc(lclNum), lclOffs, 0); + int stackLevelBias = 0; +#ifdef TARGET_X86 + stackLevelBias = getCurrentStackLevel(); + if (params.argSize > 0) + { + // Call popped these but stack level hasn't been adjusted yet, account for it here + stackLevelBias -= params.argSize; + } +#endif + + info.returnValueLoc = getSiVarLoc(m_compiler->lvaGetDesc(lclNum), lclOffs, stackLevelBias); } else if (call->HasMultiRegRetVal()) { From 66a87fe0654909b2da5bc3ed4bd19fa87182c072 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 2 Jun 2026 13:47:31 +0200 Subject: [PATCH 04/22] Add a blurb about version 22 --- src/coreclr/inc/readytorun.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coreclr/inc/readytorun.h b/src/coreclr/inc/readytorun.h index fd5d37c458029e..250b10a51e0cb8 100644 --- a/src/coreclr/inc/readytorun.h +++ b/src/coreclr/inc/readytorun.h @@ -60,6 +60,7 @@ // R2R Version 18.7 adds READYTORUN_HELPER_R2RToInterpreter // R2R Version 19 removes the READYTORUN_HELPER_ByRefWriteBarrier helper // R2R Version 20 changes NativeVarInfo encoding to include ASYNC_CONTINUATION_ILNUM +// R2R Version 22 changes NativeVarInfo encoding to include CALL_RETURN_VALUE struct READYTORUN_CORE_HEADER { From 556e026f8a61684da349b8e50b06b5015682a5b4 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 2 Jun 2026 13:48:31 +0200 Subject: [PATCH 05/22] Bump R2R version --- src/coreclr/inc/readytorun.h | 4 ++-- src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h | 2 +- src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreclr/inc/readytorun.h b/src/coreclr/inc/readytorun.h index 250b10a51e0cb8..7514789f17dc6c 100644 --- a/src/coreclr/inc/readytorun.h +++ b/src/coreclr/inc/readytorun.h @@ -19,10 +19,10 @@ // src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h // If you update this, ensure you run `git grep MINIMUM_READYTORUN_MAJOR_VERSION` // and handle pending work. -#define READYTORUN_MAJOR_VERSION 20 +#define READYTORUN_MAJOR_VERSION 22 #define READYTORUN_MINOR_VERSION 0x0000 -#define MINIMUM_READYTORUN_MAJOR_VERSION 20 +#define MINIMUM_READYTORUN_MAJOR_VERSION 22 // R2R Version 2.1 adds the InliningInfo section // R2R Version 2.2 adds the ProfileDataInfo section diff --git a/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h b/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h index da2efdd124cb44..0777cfeb07b38c 100644 --- a/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h +++ b/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h @@ -11,7 +11,7 @@ struct ReadyToRunHeaderConstants { static const uint32_t Signature = 0x00525452; // 'RTR' - static const uint32_t CurrentMajorVersion = 20; + static const uint32_t CurrentMajorVersion = 22; static const uint32_t CurrentMinorVersion = 0; }; diff --git a/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs b/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs index 9daafbdd520ca2..00d43786d72f75 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs @@ -15,7 +15,7 @@ internal struct ReadyToRunHeaderConstants { public const uint Signature = 0x00525452; // 'RTR' - public const ushort CurrentMajorVersion = 20; + public const ushort CurrentMajorVersion = 22; public const ushort CurrentMinorVersion = 0; } #if READYTORUN From bc05e7b054f5ae5481a44ba87b01037c06d0264e Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 2 Jun 2026 13:59:08 +0200 Subject: [PATCH 06/22] Fix R2R reading for version 20 and 22 --- .../DebugInfo.cs | 33 +++++++++++++++++-- .../DebugInfoTypes.cs | 11 +++++-- src/coreclr/tools/r2rdump/Extensions.cs | 1 + 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs index ba486661064e2a..ae18bb6c3e646c 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs @@ -245,12 +245,39 @@ private void ParseNativeVarInfo(NativeReader imageReader, int offset) NibbleReader reader = new NibbleReader(imageReader, offset); uint nativeVarCount = reader.ReadUInt(); + int version = _runtimeFunction.ReadyToRunReader.ReadyToRunHeader.MajorVersion; + int implicitILAdjust = version switch + { + >= 22 => (int)ImplicitILArguments.MaxV22, + >= 20 => (int)ImplicitILArguments.MaxV20, + < 20 => (int)ImplicitILArguments.MaxV19, + }; + for (int i = 0; i < nativeVarCount; ++i) { var entry = new NativeVarInfo(); - entry.StartOffset = reader.ReadUInt(); - entry.EndOffset = entry.StartOffset + reader.ReadUInt(); - entry.VariableNumber = (uint)(reader.ReadUInt() + (int)ImplicitILArguments.Max); + + if (version >= 22) + { + entry.VariableNumber = (uint)(reader.ReadUInt() + implicitILAdjust); + entry.StartOffset = reader.ReadUInt(); + + if (entry.VariableNumber == unchecked((uint)ImplicitILArguments.CallReturnValue)) + { + entry.CallReturnValueILOffset = reader.ReadUInt(); + entry.EndOffset = entry.StartOffset + 1; + } + else + { + entry.EndOffset = entry.StartOffset + reader.ReadUInt(); + } + } + else + { + entry.StartOffset = reader.ReadUInt(); + entry.EndOffset = entry.StartOffset + reader.ReadUInt(); + entry.VariableNumber = (uint)(reader.ReadUInt() + implicitILAdjust); + } entry.Variable = new Variable(); // TODO: This is probably incomplete // This does not handle any implicit arguments or var args diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfoTypes.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfoTypes.cs index 9232c0000a69d0..aa8e1e26d71e1e 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfoTypes.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfoTypes.cs @@ -17,6 +17,7 @@ public struct NativeVarInfo { public uint StartOffset; public uint EndOffset; + public uint CallReturnValueILOffset; // TODO: Eliminate this public uint VariableNumber; public Variable Variable { get; internal set; } @@ -82,8 +83,14 @@ public enum ImplicitILArguments VarArgsHandle = -1, ReturnBuffer = -2, TypeContext = -3, - Unknown = -4, - Max = Unknown + AsyncContinuation = -4, + CallReturnValue = -5, + Unknown = -6, + Max = Unknown, + + MaxV19 = -4, + MaxV20 = -5, + MaxV22 = -6, } public enum VarLocType diff --git a/src/coreclr/tools/r2rdump/Extensions.cs b/src/coreclr/tools/r2rdump/Extensions.cs index 1f635883329097..63d22dfb1d984b 100644 --- a/src/coreclr/tools/r2rdump/Extensions.cs +++ b/src/coreclr/tools/r2rdump/Extensions.cs @@ -78,6 +78,7 @@ public static void WriteTo(this DebugInfo theThis, TextWriter writer, DumpModel writer.WriteLine($" Variable Number: {varLoc.VariableNumber}"); writer.WriteLine($" Start Offset: 0x{varLoc.StartOffset:X}"); writer.WriteLine($" End Offset: 0x{varLoc.EndOffset:X}"); + writer.WriteLine($" Call return IL offset: 0x{varLoc.CallReturnValueILOffset:X}"); writer.WriteLine($" Loc Type: {varLoc.VariableLocation.VarLocType}"); switch (varLoc.VariableLocation.VarLocType) From 33b9b8fc7dcf5f327b04516950f90a0ad27c3176 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 2 Jun 2026 14:07:16 +0200 Subject: [PATCH 07/22] Fix build --- src/coreclr/jit/codegencommon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index 459f28e7116692..23e782e6b1e3e5 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -1805,7 +1805,7 @@ void CodeGen::genEmitCallWithCurrentGC(EmitCallParams& params) if (params.argSize > 0) { // Call popped these but stack level hasn't been adjusted yet, account for it here - stackLevelBias -= params.argSize; + stackLevelBias -= (int)params.argSize; } #endif From 1a9675811c22ff91d6c3ef8d20c86eb3bfadc637 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 2 Jun 2026 14:28:10 +0200 Subject: [PATCH 08/22] Update debugger --- src/coreclr/debug/di/module.cpp | 418 ++------------------------ src/coreclr/debug/di/rspriv.h | 2 - src/coreclr/debug/ee/debugger.h | 23 +- src/coreclr/debug/ee/functioninfo.cpp | 15 +- 4 files changed, 23 insertions(+), 435 deletions(-) diff --git a/src/coreclr/debug/di/module.cpp b/src/coreclr/debug/di/module.cpp index 5dac6157145ab8..d9524b2768cdbf 100644 --- a/src/coreclr/debug/di/module.cpp +++ b/src/coreclr/debug/di/module.cpp @@ -4350,385 +4350,6 @@ HRESULT CordbNativeCode::EnumerateVariableHomes(ICorDebugVariableHomeEnum **ppEn return hr; } -int CordbNativeCode::GetCallInstructionLength(BYTE *ip, ULONG32 count) -{ -#if defined(TARGET_ARM) || defined(TARGET_RISCV64) - if (Is32BitInstruction(*(WORD*)ip)) - return 4; - else - return 2; -#elif defined(TARGET_ARM64) - return MAX_INSTRUCTION_LENGTH; -#elif defined(TARGET_LOONGARCH64) - return MAX_INSTRUCTION_LENGTH; -#elif defined(TARGET_X86) - if (count < 2) - return -1; - - // Skip instruction prefixes - do - { - switch (*ip) - { - // Segment overrides - case 0x26: // ES - case 0x2E: // CS - case 0x36: // SS - case 0x3E: // DS - case 0x64: // FS - case 0x65: // GS - - // Size overrides - case 0x66: // Operand-Size - case 0x67: // Address-Size - - // Lock - case 0xf0: - - // String REP prefixes - case 0xf1: - case 0xf2: // REPNE/REPNZ - case 0xf3: - ip++; - count--; - continue; - - default: - break; - } - } while (0); - - // Read the opcode - BYTE opcode = *ip++; - if (opcode == 0xcc) - { - // todo: Can we actually get this result? Doesn't ICorDebug hand out un-patched assembly? - _ASSERTE(!"Hit break opcode!"); - return -1; - } - - // Analyze what we can of the opcode - switch (opcode) - { - case 0xff: - { - // Count may have been decremented by prefixes. - if (count < 2) - return -1; - - BYTE modrm = *ip++; - BYTE mod = (modrm & 0xC0) >> 6; - BYTE reg = (modrm & 0x38) >> 3; - BYTE rm = (modrm & 0x07); - - int displace = -1; - - if ((reg != 2) && (reg != 3) && (reg != 4) && (reg != 5)) - { - // - // This is not a CALL or JMP instruction, return, unknown. - // - _ASSERTE(!"Unhandled opcode!"); - return -1; - } - - - // Only try to decode registers if we actually have reg sets. - switch (mod) - { - case 0: - case 1: - case 2: - - if (rm == 4) - { - if (count < 3) - return -1; - - // - // Get values from the SIB byte - // - BYTE ss = (*ip & 0xC0) >> 6; - BYTE index = (*ip & 0x38) >> 3; - BYTE base = (*ip & 0x7); - - // - // Finally add in the offset - // - if (mod == 0) - { - if (base == 5) - displace = 7; - else - displace = 3; - } - else if (mod == 1) - { - displace = 4; - } - else - { - displace = 7; - } - } - else - { - if (mod == 0) - { - if (rm == 5) - displace = 6; - else - displace = 2; - } - else if (mod == 1) - { - displace = 3; - } - else - { - displace = 6; - } - } - break; - - case 3: - default: - displace = 2; - break; - } - - return displace; - } // end of 0xFF case - - case 0xe8: - return 5; - - - default: - break; - } - - - _ASSERTE(!"Unhandled opcode!"); - return -1; - -#elif defined(TARGET_AMD64) - BYTE rex = 0; - BYTE prefix = *ip; - BOOL fContainsPrefix = FALSE; - - // Should not happen. - if (prefix == 0xcc) - return -1; - - // Skip instruction prefixes - //@TODO by euzem: - //This "loop" can't be really executed more than once so if CALL can really have more than one prefix we'll crash. - //Some of these prefixes are not allowed for CALL instruction and we should treat them as invalid code. - //It appears that this code was mostly copy/pasted from \NDP\clr\src\Debug\EE\amd64\amd64walker.cpp - //with very minimum fixes. - do - { - switch (prefix) - { - // Segment overrides - case 0x26: // ES - case 0x2E: // CS - case 0x36: // SS - case 0x3E: // DS - case 0x64: // FS - case 0x65: // GS - - // Size overrides - case 0x66: // Operand-Size - case 0x67: // Address-Size - - // Lock - case 0xf0: - - // String REP prefixes - case 0xf2: // REPNE/REPNZ - case 0xf3: - ip++; - fContainsPrefix = TRUE; - continue; - - // REX register extension prefixes - case 0x40: - case 0x41: - case 0x42: - case 0x43: - case 0x44: - case 0x45: - case 0x46: - case 0x47: - case 0x48: - case 0x49: - case 0x4a: - case 0x4b: - case 0x4c: - case 0x4d: - case 0x4e: - case 0x4f: - // make sure to set rex to prefix, not *ip because *ip still represents the - // codestream which has a 0xcc in it. - rex = prefix; - ip++; - fContainsPrefix = TRUE; - continue; - - default: - break; - } - } while (0); - - // Read the opcode - BYTE opcode = *ip++; - - // Should not happen. - if (opcode == 0xcc) - return -1; - - - // Setup rex bits if needed - BYTE rex_b = 0; - BYTE rex_x = 0; - BYTE rex_r = 0; - - if (rex != 0) - { - rex_b = (rex & 0x1); // high bit to modrm r/m field or SIB base field or OPCODE reg field -- Hmm, when which? - rex_x = (rex & 0x2) >> 1; // high bit to sib index field - rex_r = (rex & 0x4) >> 2; // high bit to modrm reg field - } - - // Analyze what we can of the opcode - switch (opcode) - { - case 0xff: - { - BYTE modrm = *ip++; - - _ASSERT(modrm != 0); - - BYTE mod = (modrm & 0xC0) >> 6; - BYTE reg = (modrm & 0x38) >> 3; - BYTE rm = (modrm & 0x07); - - reg |= (rex_r << 3); - rm |= (rex_b << 3); - - if ((reg < 2) || (reg > 5 && reg < 8) || (reg > 15)) { - // not a valid register for a CALL or BRANCH - _ASSERTE(!"Invalid opcode!"); - return -1; - } - - SHORT displace = -1; - - // See: Tables A-15,16,17 in AMD Dev Manual 3 for information - // about how the ModRM/SIB/REX bytes interact. - - switch (mod) - { - case 0: - case 1: - case 2: - if ((rm & 0x07) == 4) // we have an SIB byte following - { - // - // Get values from the SIB byte - // - BYTE sib = *ip; - _ASSERT(sib != 0); - - BYTE base = (sib & 0x07); - base |= (rex_b << 3); - - ip++; - - // - // Finally add in the offset - // - if (mod == 0) - { - if ((base & 0x07) == 5) - displace = 7; - else - displace = 3; - } - else if (mod == 1) - { - displace = 4; - } - else // mod == 2 - { - displace = 7; - } - } - else - { - // - // Get the value we need from the register. - // - - // Check for RIP-relative addressing mode. - if ((mod == 0) && ((rm & 0x07) == 5)) - { - displace = 6; // 1 byte opcode + 1 byte modrm + 4 byte displacement (signed) - } - else - { - if (mod == 0) - displace = 2; - else if (mod == 1) - displace = 3; - else // mod == 2 - displace = 6; - } - } - - break; - - case 3: - default: - displace = 2; - } - - // Displace should be set by one of the cases above - if (displace == -1) - { - _ASSERTE(!"GetCallInstructionLength() encountered unexpected call instruction"); - return -1; - } - - // Account for the 1 byte prefix (REX or otherwise) - if (fContainsPrefix) - displace++; - - // reg == 4 or 5 means that it is not a CALL, but JMP instruction - // so we will fall back to ASSERT after break - if ((reg != 4) && (reg != 5)) - return displace; - break; - } - case 0xe8: - { - //Near call with the target specified by a 32-bit relative displacement. - //[maybe 1 byte prefix] + [1 byte opcode E8h] + [4 bytes offset] - return 5 + (fContainsPrefix ? 1 : 0); - } - default: - break; - } - - _ASSERTE(!"Invalid opcode!"); - return -1; -#else -#error Platform not implemented -#endif -} - HRESULT CordbNativeCode::GetSigParserFromFunction(mdToken mdFunction, mdToken *pClass, SigParser &parser, SigParser &methodGenerics) { // mdFunction may be a MemberRef, a MethodDef, or a MethodSpec. We must handle all three cases. @@ -5005,32 +4626,29 @@ HRESULT CordbNativeCode::GetReturnValueLiveOffsetImpl(Instantiation *currentInst IfFailRet(EnsureReturnValueAllowed(currentInstantiation, mdClass, signature, generics)); // now find the native offset - SequencePoints *pSP = GetSequencePoints(); - DebuggerILToNativeMap *pMap = pSP->GetCallsiteMapAddr(); - - for (ULONG32 i = 0; i < pSP->GetCallsiteEntryCount() && pMap; ++i, pMap++) + const DacDbiArrayList *pOffsetInfoList = m_nativeVarData.GetOffsetInfoList(); + _ASSERTE(pOffsetInfoList != NULL); + for (unsigned int i = 0; i < pOffsetInfoList->Count(); i++) { - if (pMap->ilOffset == ILoffset && (pMap->source & ICorDebugInfo::CALL_INSTRUCTION) == ICorDebugInfo::CALL_INSTRUCTION) + const ICorDebugInfo::NativeVarInfo *pNativeVarInfo = &((*pOffsetInfoList)[i]); + _ASSERTE(pNativeVarInfo != NULL); + if (pNativeVarInfo->varNumber != ICorDebugInfo::CALL_RETURN_ILNUM) { - // if we have a buffer, fill it in. - if (pOffsets && found < bufferSize) - { - // Fetch the actual assembly instructions - BYTE nativeBuffer[8]; - - ULONG32 fetched = 0; - IfFailRet(GetCode(pMap->nativeStartOffset, pMap->nativeStartOffset+ARRAY_SIZE(nativeBuffer), ARRAY_SIZE(nativeBuffer), nativeBuffer, &fetched)); - - // Get the length of the call instruction. - int offset = GetCallInstructionLength(nativeBuffer, fetched); - if (offset == -1) - return E_UNEXPECTED; // Could not decode instruction, this should never happen. + continue; + } - pOffsets[found] = pMap->nativeStartOffset + offset; - } + if (pNativeVarInfo->callReturnValueILOffset != ILoffset) + { + continue; + } - found++; + // if we have a buffer, fill it in. + if (pOffsets && found < bufferSize) + { + pOffsets[found] = pNativeVarInfo->startOffset; } + + found++; } if (pOffsets) diff --git a/src/coreclr/debug/di/rspriv.h b/src/coreclr/debug/di/rspriv.h index 20acbae398a3ae..9ff85d9c00b002 100644 --- a/src/coreclr/debug/di/rspriv.h +++ b/src/coreclr/debug/di/rspriv.h @@ -5921,8 +5921,6 @@ class CordbNativeCode : public CordbCode, // Grabs the appropriate signature parser for a methodref, methoddef, methodspec. HRESULT GetSigParserFromFunction(mdToken mdFunction, mdToken *pClass, SigParser &methodSig, SigParser &genericSig); - int GetCallInstructionLength(BYTE *buffer, ULONG32 len); - //----------------------------------------------------------- // Data members //----------------------------------------------------------- diff --git a/src/coreclr/debug/ee/debugger.h b/src/coreclr/debug/ee/debugger.h index 32df8dbca015f4..3969a776b6c4d3 100644 --- a/src/coreclr/debug/ee/debugger.h +++ b/src/coreclr/debug/ee/debugger.h @@ -1502,8 +1502,6 @@ class DebuggerJitInfo ULONG m_lastIL; PTR_DebuggerILToNativeMap m_sequenceMap; unsigned int m_sequenceMapCount; - PTR_DebuggerILToNativeMap m_callsiteMap; - unsigned int m_callsiteMapCount; bool m_sequenceMapSorted; PTR_NativeVarInfo m_varNativeInfo; @@ -1532,10 +1530,9 @@ class DebuggerJitInfo " m_addrOfCode: %p\n" " m_sizeOfCode: 0x%zx\n" " m_lastIL: 0x%x\n" - " m_sequenceMapCount: %u\n" - " m_callsiteMapCount: %u\n", + " m_sequenceMapCount: %u\n", this, (m_jitComplete ? "true" : "false"), encState, - m_methodInfo, m_addrOfCode, m_sizeOfCode, m_lastIL, m_sequenceMapCount, m_callsiteMapCount)); + m_methodInfo, m_addrOfCode, m_sizeOfCode, m_lastIL, m_sequenceMapCount)); #endif //LOGGING } @@ -1556,22 +1553,6 @@ class DebuggerJitInfo return m_sequenceMap; } - unsigned int GetCallsiteMapCount() - { - SUPPORTS_DAC; - - LazyInitBounds(); - return m_callsiteMapCount; - } - - PTR_DebuggerILToNativeMap GetCallSiteMap() - { - SUPPORTS_DAC; - - LazyInitBounds(); - return m_callsiteMap; - } - PTR_NativeVarInfo GetVarNativeInfo() { SUPPORTS_DAC; diff --git a/src/coreclr/debug/ee/functioninfo.cpp b/src/coreclr/debug/ee/functioninfo.cpp index 3ee9d46810e911..64c62a855b2ae3 100644 --- a/src/coreclr/debug/ee/functioninfo.cpp +++ b/src/coreclr/debug/ee/functioninfo.cpp @@ -249,8 +249,6 @@ DebuggerJitInfo::DebuggerJitInfo(DebuggerMethodInfo *minfo, NativeCodeVersion na m_lastIL(0), m_sequenceMap(NULL), m_sequenceMapCount(0), - m_callsiteMap(NULL), - m_callsiteMapCount(0), m_sequenceMapSorted(false), m_varNativeInfo(NULL), m_varNativeInfoCount(0), m_fAttemptInit(false) @@ -1160,18 +1158,11 @@ void DebuggerJitInfo::SetBoundaries(ULONG32 cMap, ICorDebugInfo::OffsetMapping * m_sequenceMapSorted = true; - m_callsiteMapCount = m_sequenceMapCount; - while (m_sequenceMapCount > 0 && (m_sequenceMap[m_sequenceMapCount-1].source & call_inst) == call_inst) - m_sequenceMapCount--; - - m_callsiteMap = m_sequenceMap + m_sequenceMapCount; - m_callsiteMapCount -= m_sequenceMapCount; - - LOG((LF_CORDB, LL_INFO100000, "DJI::sB: this=%p boundary count is %u (%u callsites)\n", - this, m_sequenceMapCount, m_callsiteMapCount)); + LOG((LF_CORDB, LL_INFO100000, "DJI::sB: this=%p boundary count is %u\n", + this, m_sequenceMapCount)); #ifdef LOGGING - for (unsigned count = 0; count < m_sequenceMapCount + m_callsiteMapCount; count++) + for (unsigned count = 0; count < m_sequenceMapCount; count++) { const DebuggerILToNativeMap& entry = m_sequenceMap[count]; switch (entry.ilOffset) From 1fde69285c7acb79883bf4020fd67f635bae6869 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 2 Jun 2026 14:57:21 +0200 Subject: [PATCH 09/22] Feedback --- src/coreclr/inc/cordebuginfo.h | 2 +- src/coreclr/jit/codegencommon.cpp | 2 +- src/coreclr/jit/codegeninterface.h | 2 +- src/coreclr/jit/ee_il_dll.cpp | 2 +- src/coreclr/jit/scopeinfo.cpp | 6 +++--- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/coreclr/inc/cordebuginfo.h b/src/coreclr/inc/cordebuginfo.h index 2c1d480d3e8152..dc6c1c234f6cbb 100644 --- a/src/coreclr/inc/cordebuginfo.h +++ b/src/coreclr/inc/cordebuginfo.h @@ -391,7 +391,7 @@ class ICorDebugInfo UNKNOWN_ILNUM = -6, // Unknown variable - MAX_ILNUM = -6 // Sentinel value. This should be set to the largest magnitude value in th enum + MAX_ILNUM = -6 // Sentinel value. This should be set to the largest magnitude value in the enum // so that the compression routines know the enum's range. }; diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index 23e782e6b1e3e5..88708aac336d58 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -1798,7 +1798,7 @@ void CodeGen::genEmitCallWithCurrentGC(EmitCallParams& params) } unsigned lclNum = node->AsLclVarCommon()->GetLclNum(); - unsigned lclOffs = node->AsLclVarCommon()->GetLclOffs(); + int lclOffs = node->AsLclVarCommon()->GetLclOffs(); int stackLevelBias = 0; #ifdef TARGET_X86 stackLevelBias = getCurrentStackLevel(); diff --git a/src/coreclr/jit/codegeninterface.h b/src/coreclr/jit/codegeninterface.h index b4583b5108a2e8..53825060bb502d 100644 --- a/src/coreclr/jit/codegeninterface.h +++ b/src/coreclr/jit/codegeninterface.h @@ -660,7 +660,7 @@ class CodeGenInterface }; public: - siVarLoc getSiVarLoc(const LclVarDsc* varDsc, unsigned offset, unsigned stackLevel) const; + siVarLoc getSiVarLoc(const LclVarDsc* varDsc, int offset, int stackLevel) const; #ifdef DEBUG void dumpSiVarLoc(const siVarLoc* varLoc) const; diff --git a/src/coreclr/jit/ee_il_dll.cpp b/src/coreclr/jit/ee_il_dll.cpp index 9db995cdbaa9e3..dc117ca4ebc9c6 100644 --- a/src/coreclr/jit/ee_il_dll.cpp +++ b/src/coreclr/jit/ee_il_dll.cpp @@ -874,7 +874,7 @@ void Compiler::eeDispVar(ICorDebugInfo::NativeVarInfo* var) if (var->varNumber == (DWORD)ICorDebugInfo::CALL_RETURN_ILNUM) { - int printed = printf("(call %03u)", var->callReturnValueILOffset); + printf("(call %03u)", var->callReturnValueILOffset); } else if (0 <= var->varNumber && var->varNumber < lvaCount) { diff --git a/src/coreclr/jit/scopeinfo.cpp b/src/coreclr/jit/scopeinfo.cpp index e21eb70c9fcfee..0d781107238816 100644 --- a/src/coreclr/jit/scopeinfo.cpp +++ b/src/coreclr/jit/scopeinfo.cpp @@ -495,7 +495,7 @@ CodeGenInterface::siVarLoc::siVarLoc(const LclVarDsc* varDsc, regNumber baseReg, // A "siVarLoc" representing the variable location, which could live // in a register, an stack position, or a combination of both. // -CodeGenInterface::siVarLoc CodeGenInterface::getSiVarLoc(const LclVarDsc* varDsc, unsigned offset, unsigned stackLevel) const +CodeGenInterface::siVarLoc CodeGenInterface::getSiVarLoc(const LclVarDsc* varDsc, int offset, int stackLevel) const { // For stack vars, find the base register, and offset @@ -1081,7 +1081,7 @@ void CodeGenInterface::VariableLiveKeeper::siStartVariableLiveRange(const LclVar { // Build siVarLoc for this born "varDsc" CodeGenInterface::siVarLoc varLocation = - m_compiler->codeGen->getSiVarLoc(varDsc, 0, m_compiler->codeGen->getCurrentStackLevel()); + m_compiler->codeGen->getSiVarLoc(varDsc, 0, (int)m_compiler->codeGen->getCurrentStackLevel()); VariableLiveDescriptor* varLiveDsc = &m_vlrLiveDsc[varNum]; // this variable live range is valid from this point @@ -1150,7 +1150,7 @@ void CodeGenInterface::VariableLiveKeeper::siUpdateVariableLiveRange(const LclVa { // Build the location of the variable CodeGenInterface::siVarLoc siVarLoc = - m_compiler->codeGen->getSiVarLoc(varDsc, 0, m_compiler->codeGen->getCurrentStackLevel()); + m_compiler->codeGen->getSiVarLoc(varDsc, 0, (int)m_compiler->codeGen->getCurrentStackLevel()); // Report the home change for this variable VariableLiveDescriptor* varLiveDsc = &m_vlrLiveDsc[varNum]; From 92d77aecbf2e6ef542c6a0096deb7a1997eb17b6 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 2 Jun 2026 14:59:52 +0200 Subject: [PATCH 10/22] Run jit-format --- src/coreclr/jit/codegenarmarch.cpp | 4 ++-- src/coreclr/jit/codegencommon.cpp | 23 ++++++++++++----------- src/coreclr/jit/codegeninterface.h | 4 ++-- src/coreclr/jit/codegenlinear.cpp | 3 ++- src/coreclr/jit/codegenwasm.cpp | 4 ++-- src/coreclr/jit/codegenxarch.cpp | 4 ++-- src/coreclr/jit/compiler.h | 2 +- src/coreclr/jit/ee_il_dll.cpp | 10 +++++----- src/coreclr/jit/emit.h | 15 ++++++++------- src/coreclr/jit/scopeinfo.cpp | 8 ++------ 10 files changed, 38 insertions(+), 39 deletions(-) diff --git a/src/coreclr/jit/codegenarmarch.cpp b/src/coreclr/jit/codegenarmarch.cpp index 661505f86f87f5..160dbe08461603 100644 --- a/src/coreclr/jit/codegenarmarch.cpp +++ b/src/coreclr/jit/codegenarmarch.cpp @@ -3293,8 +3293,8 @@ void CodeGen::genCallInstruction(GenTreeCall* call) assert(params.secondRetSize != EA_BYREF); #endif - params.isJump = call->IsFastTailCall(); - params.hasAsyncRet = call->IsAsync(); + params.isJump = call->IsFastTailCall(); + params.hasAsyncRet = call->IsAsync(); params.returnValueCall = call; #ifdef DEBUG diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index 88708aac336d58..8e456c91ac9143 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -1762,7 +1762,8 @@ void CodeGen::genEmitCallWithCurrentGC(EmitCallParams& params) // Emit an entry for managed return value reporting, if needed. GenTreeCall* call = params.returnValueCall; JITDUMP("Emit ret val for [%06u]\n", call == nullptr ? 0 : Compiler::dspTreeID(call)); - if ((call == nullptr) || !m_compiler->opts.compDbgInfo || (m_compiler->genCallSite2DebugInfoMap == nullptr) || params.isJump) + if ((call == nullptr) || !m_compiler->opts.compDbgInfo || (m_compiler->genCallSite2DebugInfoMap == nullptr) || + params.isJump) { return; } @@ -1782,7 +1783,7 @@ void CodeGen::genEmitCallWithCurrentGC(EmitCallParams& params) retLoc.CaptureLocation(GetEmitter()); CodeGenInterface::EmittedCallReturnInfo info; - info.callILOffset = di.GetRoot().GetLocation().GetOffset(); + info.callILOffset = di.GetRoot().GetLocation().GetOffset(); info.returnLocation = retLoc; CallArg* retBuf = call->gtArgs.GetRetBufferArg(); @@ -1797,9 +1798,9 @@ void CodeGen::genEmitCallWithCurrentGC(EmitCallParams& params) return; } - unsigned lclNum = node->AsLclVarCommon()->GetLclNum(); - int lclOffs = node->AsLclVarCommon()->GetLclOffs(); - int stackLevelBias = 0; + unsigned lclNum = node->AsLclVarCommon()->GetLclNum(); + int lclOffs = node->AsLclVarCommon()->GetLclOffs(); + int stackLevelBias = 0; #ifdef TARGET_X86 stackLevelBias = getCurrentStackLevel(); if (params.argSize > 0) @@ -1814,7 +1815,7 @@ void CodeGen::genEmitCallWithCurrentGC(EmitCallParams& params) else if (call->HasMultiRegRetVal()) { const ReturnTypeDesc* retDesc = call->GetReturnTypeDesc(); - unsigned numRegs = retDesc->GetReturnRegCount(); + unsigned numRegs = retDesc->GetReturnRegCount(); if (numRegs > 2) { // Cannot encode more than 2 registers. @@ -1822,14 +1823,13 @@ void CodeGen::genEmitCallWithCurrentGC(EmitCallParams& params) } assert(numRegs == 2); - info.returnValueLoc.storeVariableInRegisters( - retDesc->GetABIReturnReg(0, call->GetUnmanagedCallConv()), - retDesc->GetABIReturnReg(1, call->GetUnmanagedCallConv())); + info.returnValueLoc.storeVariableInRegisters(retDesc->GetABIReturnReg(0, call->GetUnmanagedCallConv()), + retDesc->GetABIReturnReg(1, call->GetUnmanagedCallConv())); } else if (varTypeIsFloating(call)) { #ifdef TARGET_X86 - info.returnValueLoc.vlType = VLT_FPSTK; + info.returnValueLoc.vlType = VLT_FPSTK; info.returnValueLoc.vlFPstk.vlfReg = 0; #else info.returnValueLoc.storeVariableInRegisters(REG_FLOATRET, REG_NA); @@ -1846,7 +1846,8 @@ void CodeGen::genEmitCallWithCurrentGC(EmitCallParams& params) if (emittedCallReturnInfo == nullptr) { - emittedCallReturnInfo = new (m_compiler, CMK_DebugInfo) jitstd::vector(m_compiler->getAllocator(CMK_DebugInfo)); + emittedCallReturnInfo = new (m_compiler, CMK_DebugInfo) + jitstd::vector(m_compiler->getAllocator(CMK_DebugInfo)); } emittedCallReturnInfo->push_back(info); diff --git a/src/coreclr/jit/codegeninterface.h b/src/coreclr/jit/codegeninterface.h index 53825060bb502d..fac4b90855a287 100644 --- a/src/coreclr/jit/codegeninterface.h +++ b/src/coreclr/jit/codegeninterface.h @@ -654,9 +654,9 @@ class CodeGenInterface struct EmittedCallReturnInfo { - IL_OFFSET callILOffset; + IL_OFFSET callILOffset; emitLocation returnLocation; - siVarLoc returnValueLoc; + siVarLoc returnValueLoc; }; public: diff --git a/src/coreclr/jit/codegenlinear.cpp b/src/coreclr/jit/codegenlinear.cpp index 638a50b5892288..85635d64ab5dbe 100644 --- a/src/coreclr/jit/codegenlinear.cpp +++ b/src/coreclr/jit/codegenlinear.cpp @@ -91,7 +91,8 @@ void CodeGen::genInitialize() } initializeVariableLiveKeeper(); - emittedCallReturnInfo = new (m_compiler, CMK_DebugInfo) jitstd::vector(m_compiler->getAllocator(CMK_DebugInfo)); + emittedCallReturnInfo = + new (m_compiler, CMK_DebugInfo) jitstd::vector(m_compiler->getAllocator(CMK_DebugInfo)); genPendingCallLabel = nullptr; diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index e8ea99a85d402d..42a2b2151fa12c 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -2475,8 +2475,8 @@ void CodeGen::genCallInstruction(GenTreeCall* call) ensureCurrentFuncIsUnwindable(); EmitCallParams params; - params.isJump = call->IsFastTailCall(); - params.hasAsyncRet = call->IsAsync(); + params.isJump = call->IsFastTailCall(); + params.hasAsyncRet = call->IsAsync(); params.returnValueCall = call; #ifdef DEBUG diff --git a/src/coreclr/jit/codegenxarch.cpp b/src/coreclr/jit/codegenxarch.cpp index 56fa78bed766e6..3efd0716dfb92f 100644 --- a/src/coreclr/jit/codegenxarch.cpp +++ b/src/coreclr/jit/codegenxarch.cpp @@ -6016,8 +6016,8 @@ void CodeGen::genCallInstruction(GenTreeCall* call X86_ARG(target_ssize_t stackA } } - params.isJump = call->IsFastTailCall(); - params.hasAsyncRet = call->IsAsync(); + params.isJump = call->IsFastTailCall(); + params.hasAsyncRet = call->IsAsync(); params.returnValueCall = call; #ifdef DEBUG diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index ec835b29e53d8b..6e757f0425334f 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -9563,7 +9563,7 @@ class Compiler void eeGetVars(); - unsigned eeVarsCount = 0; + unsigned eeVarsCount = 0; unsigned eeVarsCapacity = 0; struct VarResultInfo diff --git a/src/coreclr/jit/ee_il_dll.cpp b/src/coreclr/jit/ee_il_dll.cpp index dc117ca4ebc9c6..1ee2744d352248 100644 --- a/src/coreclr/jit/ee_il_dll.cpp +++ b/src/coreclr/jit/ee_il_dll.cpp @@ -696,11 +696,11 @@ void Compiler::eeSetLVinfo(unsigned which, if (eeVars != nullptr) { - eeVars[which].startOffset = startOffs; - eeVars[which].endOffset = endOffs; - eeVars[which].callReturnValueILOffset = callReturnValueILOffset; - eeVars[which].varNumber = varNum; - eeVars[which].loc = varLoc; + eeVars[which].startOffset = startOffs; + eeVars[which].endOffset = endOffs; + eeVars[which].callReturnValueILOffset = callReturnValueILOffset; + eeVars[which].varNumber = varNum; + eeVars[which].loc = varLoc; } } diff --git a/src/coreclr/jit/emit.h b/src/coreclr/jit/emit.h index 426111b5a74c97..64306e35cd8a24 100644 --- a/src/coreclr/jit/emit.h +++ b/src/coreclr/jit/emit.h @@ -482,13 +482,14 @@ struct EmitCallParams BitVec ptrVars = BitVecOps::UninitVal(); regMaskTP gcrefRegs = RBM_NONE; regMaskTP byrefRegs = RBM_NONE; - regNumber ireg = REG_NA; - regNumber xreg = REG_NA; - unsigned xmul = 0; - ssize_t disp = 0; - bool isJump = false; - bool noSafePoint = false; - // If this call should have managed return value debug info associated with it, this is the call to associate it with. + regNumber ireg = REG_NA; + regNumber xreg = REG_NA; + unsigned xmul = 0; + ssize_t disp = 0; + bool isJump = false; + bool noSafePoint = false; + // If this call should have managed return value debug info associated with it, this is the call to associate it + // with. GenTreeCall* returnValueCall = nullptr; #ifdef TARGET_WASM CORINFO_WASM_TYPE_SYMBOL_HANDLE wasmSignature = nullptr; diff --git a/src/coreclr/jit/scopeinfo.cpp b/src/coreclr/jit/scopeinfo.cpp index 0d781107238816..c93ec7d3bf5651 100644 --- a/src/coreclr/jit/scopeinfo.cpp +++ b/src/coreclr/jit/scopeinfo.cpp @@ -1815,12 +1815,8 @@ void CodeGen::genSetScopeInfo() { UNATIVE_OFFSET retOffset = callReturnInfo.returnLocation.CodeOffset(GetEmitter()); - m_compiler->eeSetLVinfo( - m_compiler->eeVarsCount++, - retOffset, retOffset + 1, - callReturnInfo.callILOffset, - ICorDebugInfo::CALL_RETURN_ILNUM, - callReturnInfo.returnValueLoc); + m_compiler->eeSetLVinfo(m_compiler->eeVarsCount++, retOffset, retOffset + 1, callReturnInfo.callILOffset, + ICorDebugInfo::CALL_RETURN_ILNUM, callReturnInfo.returnValueLoc); } m_compiler->eeSetLVdone(); From 00078d1e72677dde805f5d1715ed0008225164e4 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 2 Jun 2026 16:10:53 +0200 Subject: [PATCH 11/22] Fix GCC build --- src/coreclr/vm/debuginfostore.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/debuginfostore.cpp b/src/coreclr/vm/debuginfostore.cpp index dc5133e052240c..6e1f114c28288e 100644 --- a/src/coreclr/vm/debuginfostore.cpp +++ b/src/coreclr/vm/debuginfostore.cpp @@ -355,11 +355,11 @@ static void DoNativeVarInfo( // The entries aren't sorted in any particular order. trans.DoCookie(0xB); // record var number. - trans.DoEncodedAdjustedU32(pVar->varNumber, (DWORD) ICorDebugInfo::MAX_ILNUM); + trans.DoEncodedAdjustedU32(pVar->varNumber, (DWORD)ICorDebugInfo::MAX_ILNUM); trans.DoEncodedU32(pVar->startOffset); - if (pVar->varNumber == ICorDebugInfo::CALL_RETURN_ILNUM) + if (pVar->varNumber == (DWORD)ICorDebugInfo::CALL_RETURN_ILNUM) { trans.DoEncodedU32(pVar->callReturnValueILOffset); trans.DoImplicitEndOffset(pVar->endOffset, pVar->startOffset); From 9e59caabc75a6bacd4fb43f0defb2d1f135f1903 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 2 Jun 2026 16:13:20 +0200 Subject: [PATCH 12/22] Fix for NativeAOT --- src/coreclr/jit/scopeinfo.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/coreclr/jit/scopeinfo.cpp b/src/coreclr/jit/scopeinfo.cpp index c93ec7d3bf5651..56429e8bf72e50 100644 --- a/src/coreclr/jit/scopeinfo.cpp +++ b/src/coreclr/jit/scopeinfo.cpp @@ -1792,7 +1792,7 @@ void CodeGen::genSetScopeInfo() return; } - noway_assert(m_compiler->opts.compScopeInfo && (m_compiler->info.compVarScopesCount > 0)); + noway_assert((m_compiler->opts.compScopeInfo && (m_compiler->info.compVarScopesCount > 0)) || (varLiveKeeper->getLiveRangesCount() == 0)); // Initialize the table where the reported variables' home will be placed. m_compiler->eeSetLVcount(varsLocationsCount); @@ -1805,11 +1805,10 @@ void CodeGen::genSetScopeInfo() } #endif - // We can have one of both flags defined, both, or none. Specially if we need to compare both - // both results. But we cannot report both to the debugger, since there would be overlapping - // intervals, and may not indicate the same variable location. - - genSetScopeInfoUsingVariableRanges(); + if (varLiveKeeper->getLiveRangesCount() > 0) + { + genSetScopeInfoUsingVariableRanges(); + } for (const EmittedCallReturnInfo& callReturnInfo : *emittedCallReturnInfo) { From aa1fc0bd0cdfecf5fba84439e62b9c933cb517ba Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 2 Jun 2026 17:08:07 +0200 Subject: [PATCH 13/22] Copilot nits about whitespace --- src/coreclr/inc/cordebuginfo.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/coreclr/inc/cordebuginfo.h b/src/coreclr/inc/cordebuginfo.h index dc6c1c234f6cbb..ba5a545e3ed909 100644 --- a/src/coreclr/inc/cordebuginfo.h +++ b/src/coreclr/inc/cordebuginfo.h @@ -387,9 +387,8 @@ class ICorDebugInfo TYPECTXT_ILNUM = -3, // ParamTypeArg for CORINFO_GENERICS_CTXT_FROM_PARAMTYPEARG ASYNC_CONTINUATION_ILNUM = -4, // Async continuation argument CALL_RETURN_ILNUM = -5, // The return value of a call - - UNKNOWN_ILNUM = -6, // Unknown variable + UNKNOWN_ILNUM = -6, // Unknown variable MAX_ILNUM = -6 // Sentinel value. This should be set to the largest magnitude value in the enum // so that the compression routines know the enum's range. From 184a95b0763bfe9d99f034e417a2842d865e77de Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 2 Jun 2026 17:08:57 +0200 Subject: [PATCH 14/22] Run jit-format --- src/coreclr/jit/scopeinfo.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/coreclr/jit/scopeinfo.cpp b/src/coreclr/jit/scopeinfo.cpp index 56429e8bf72e50..93747209f2b8e0 100644 --- a/src/coreclr/jit/scopeinfo.cpp +++ b/src/coreclr/jit/scopeinfo.cpp @@ -1792,7 +1792,8 @@ void CodeGen::genSetScopeInfo() return; } - noway_assert((m_compiler->opts.compScopeInfo && (m_compiler->info.compVarScopesCount > 0)) || (varLiveKeeper->getLiveRangesCount() == 0)); + noway_assert((m_compiler->opts.compScopeInfo && (m_compiler->info.compVarScopesCount > 0)) || + (varLiveKeeper->getLiveRangesCount() == 0)); // Initialize the table where the reported variables' home will be placed. m_compiler->eeSetLVcount(varsLocationsCount); From 8c3655eaba3e8d6514d8ff16d79ee1185d66fa64 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 2 Jun 2026 17:34:43 +0200 Subject: [PATCH 15/22] Remove a leftover JITDUMP --- src/coreclr/jit/codegencommon.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index 8e456c91ac9143..f93bb270246592 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -1761,7 +1761,6 @@ void CodeGen::genEmitCallWithCurrentGC(EmitCallParams& params) // Emit an entry for managed return value reporting, if needed. GenTreeCall* call = params.returnValueCall; - JITDUMP("Emit ret val for [%06u]\n", call == nullptr ? 0 : Compiler::dspTreeID(call)); if ((call == nullptr) || !m_compiler->opts.compDbgInfo || (m_compiler->genCallSite2DebugInfoMap == nullptr) || params.isJump) { From f02ddf0c7dc4c62be235242a747fa475cce0ac45 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 3 Jun 2026 13:29:21 +0200 Subject: [PATCH 16/22] Feedback --- src/coreclr/jit/codegenloongarch64.cpp | 16 +++------------- src/coreclr/jit/codegenriscv64.cpp | 16 +++------------- 2 files changed, 6 insertions(+), 26 deletions(-) diff --git a/src/coreclr/jit/codegenloongarch64.cpp b/src/coreclr/jit/codegenloongarch64.cpp index cebfdb45829f35..835936cb2114f7 100644 --- a/src/coreclr/jit/codegenloongarch64.cpp +++ b/src/coreclr/jit/codegenloongarch64.cpp @@ -5583,19 +5583,9 @@ void CodeGen::genCallInstruction(GenTreeCall* call) } } - params.isJump = call->IsFastTailCall(); - params.hasAsyncRet = call->IsAsync(); - - // 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. - if (m_compiler->opts.compDbgInfo && m_compiler->genCallSite2DebugInfoMap != nullptr && !call->IsTailCall()) - { - DebugInfo di; - (void)m_compiler->genCallSite2DebugInfoMap->Lookup(call, &di); - params.debugInfo = di; - } + params.isJump = call->IsFastTailCall(); + params.hasAsyncRet = call->IsAsync(); + params.returnValueCall = call; #ifdef DEBUG // Pass the call signature information down into the emitter so the emitter can associate diff --git a/src/coreclr/jit/codegenriscv64.cpp b/src/coreclr/jit/codegenriscv64.cpp index b91240d58f3ebb..d0b55b10d9f4e0 100644 --- a/src/coreclr/jit/codegenriscv64.cpp +++ b/src/coreclr/jit/codegenriscv64.cpp @@ -5394,19 +5394,9 @@ void CodeGen::genCallInstruction(GenTreeCall* call) } } - params.isJump = call->IsFastTailCall(); - params.hasAsyncRet = call->IsAsync(); - - // 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. - if (m_compiler->opts.compDbgInfo && m_compiler->genCallSite2DebugInfoMap != nullptr && !call->IsTailCall()) - { - DebugInfo di; - (void)m_compiler->genCallSite2DebugInfoMap->Lookup(call, &di); - params.debugInfo = di; - } + params.isJump = call->IsFastTailCall(); + params.hasAsyncRet = call->IsAsync(); + params.returnValueCall = call; #ifdef DEBUG // Pass the call signature information down into the emitter so the emitter can associate From 4556bbd3928d4870bea2ad796b60647cff7b5d48 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 3 Jun 2026 13:35:01 +0200 Subject: [PATCH 17/22] Feedback --- src/coreclr/jit/compiler.h | 2 +- src/coreclr/jit/ee_il_dll.cpp | 6 ++++-- src/coreclr/jit/scopeinfo.cpp | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index df855eb3832d59..97bd55df2ebcca 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -9578,7 +9578,7 @@ class Compiler DWORD varNumber; CodeGenInterface::siVarLoc loc; }* eeVars; - void eeSetLVcount(unsigned count); + void eeAllocateLVs(unsigned count); void eeSetLVinfo(unsigned which, UNATIVE_OFFSET startOffs, UNATIVE_OFFSET endOffs, diff --git a/src/coreclr/jit/ee_il_dll.cpp b/src/coreclr/jit/ee_il_dll.cpp index 1ee2744d352248..6c3ca0e2a35bba 100644 --- a/src/coreclr/jit/ee_il_dll.cpp +++ b/src/coreclr/jit/ee_il_dll.cpp @@ -664,13 +664,15 @@ void Compiler::eeGetStmtOffsets() * Debugging support - Local var info */ -void Compiler::eeSetLVcount(unsigned count) +void Compiler::eeAllocateLVs(unsigned count) { assert(opts.compScopeInfo); - JITDUMP("VarLocInfo count is %d\n", count); + JITDUMP("Allocating %d VarLocInfo\n", count); + eeVarsCount = 0; eeVarsCapacity = count; + if (count > 0) { eeVars = (VarResultInfo*)info.compCompHnd->allocateArray(count * sizeof(eeVars[0])); diff --git a/src/coreclr/jit/scopeinfo.cpp b/src/coreclr/jit/scopeinfo.cpp index 93747209f2b8e0..e5dd272c33b9a4 100644 --- a/src/coreclr/jit/scopeinfo.cpp +++ b/src/coreclr/jit/scopeinfo.cpp @@ -1787,7 +1787,7 @@ void CodeGen::genSetScopeInfo() if (varsLocationsCount == 0) { // No variable home to report - m_compiler->eeSetLVcount(0); + m_compiler->eeAllocateLVs(0); m_compiler->eeSetLVdone(); return; } @@ -1796,7 +1796,7 @@ void CodeGen::genSetScopeInfo() (varLiveKeeper->getLiveRangesCount() == 0)); // Initialize the table where the reported variables' home will be placed. - m_compiler->eeSetLVcount(varsLocationsCount); + m_compiler->eeAllocateLVs(varsLocationsCount); #ifdef DEBUG genTrnslLocalVarCount = varsLocationsCount; From 3cc35aa2ef4d9f98405581bf5c72cf68fa869900 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 3 Jun 2026 15:03:30 +0200 Subject: [PATCH 18/22] Fix GCC build --- src/coreclr/debug/di/module.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/debug/di/module.cpp b/src/coreclr/debug/di/module.cpp index d9524b2768cdbf..76f29b2965da95 100644 --- a/src/coreclr/debug/di/module.cpp +++ b/src/coreclr/debug/di/module.cpp @@ -4632,7 +4632,7 @@ HRESULT CordbNativeCode::GetReturnValueLiveOffsetImpl(Instantiation *currentInst { const ICorDebugInfo::NativeVarInfo *pNativeVarInfo = &((*pOffsetInfoList)[i]); _ASSERTE(pNativeVarInfo != NULL); - if (pNativeVarInfo->varNumber != ICorDebugInfo::CALL_RETURN_ILNUM) + if (pNativeVarInfo->varNumber != (unsigned)ICorDebugInfo::CALL_RETURN_ILNUM) { continue; } From 02f32e583a19c5c176ccc5c25b5876e52fd69a43 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Thu, 4 Jun 2026 07:23:53 +0200 Subject: [PATCH 19/22] Remove dead code --- src/coreclr/jit/codegencommon.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index f93bb270246592..91791af1f5cac1 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -1843,12 +1843,6 @@ void CodeGen::genEmitCallWithCurrentGC(EmitCallParams& params) info.returnValueLoc.storeVariableInRegisters(REG_INTRET, REG_NA); } - if (emittedCallReturnInfo == nullptr) - { - emittedCallReturnInfo = new (m_compiler, CMK_DebugInfo) - jitstd::vector(m_compiler->getAllocator(CMK_DebugInfo)); - } - emittedCallReturnInfo->push_back(info); } From 3da5d6c44a3389e9a7cb9a9a680350b8acad3b6b Mon Sep 17 00:00:00 2001 From: rcj1 Date: Tue, 2 Jun 2026 23:27:33 +0000 Subject: [PATCH 20/22] add debugger support --- docs/design/datacontracts/DebugInfo.md | 12 ++-- src/coreclr/debug/di/module.cpp | 29 ++++++-- src/coreclr/debug/di/rspriv.h | 5 +- src/coreclr/debug/di/rsthread.cpp | 70 ++----------------- .../Contracts/IDebugInfo.cs | 5 ++ .../Contracts/DebugInfo/DebugInfoHelpers.cs | 46 +++++++----- 6 files changed, 73 insertions(+), 94 deletions(-) diff --git a/docs/design/datacontracts/DebugInfo.md b/docs/design/datacontracts/DebugInfo.md index 529b9db44d30b2..212e2c38e8d84c 100644 --- a/docs/design/datacontracts/DebugInfo.md +++ b/docs/design/datacontracts/DebugInfo.md @@ -343,6 +343,7 @@ public readonly struct DebugVarInfo public int StackOffset { get; init; } public uint BaseRegister2 { get; init; } public int StackOffset2 { get; init; } + public uint CallReturnValueILOffset { get; init; } } // Given a code pointer, return the variable location info for the method. @@ -352,7 +353,8 @@ IEnumerable GetMethodVarInfo(TargetCodePointer pCode, out uint cod Additional constants (Version 2): | Constant Name | Meaning | Value | | --- | --- | --- | -| `MAX_ILNUM` | Bias for adjusted encoding of variable numbers | `0xfffffffc` (-4) | +| `MAX_ILNUM` | Bias for adjusted encoding of variable numbers | `0xfffffffa` (-6) | +| `CALL_RETURN_ILNUM` | Special variable number identifying a call-return-value entry | `0xfffffffb` (-5) | | `VLT_REG` | Variable is in a register | `0` | | `VLT_REG_BYREF` | Address of the variable is in a register | `1` | | `VLT_REG_FP` | Variable is in an FP register | `2` | @@ -371,9 +373,11 @@ Additional constants (Version 2): Each variable entry in the Vars section is nibble-encoded as follows: -1. `startOffset` — encoded unsigned 32-bit integer -2. `endOffset` — encoded as delta from `startOffset` (unsigned) -3. `varNumber` — encoded as adjusted unsigned (`value - MAX_ILNUM`) +1. `varNumber` — encoded as adjusted unsigned (`value - MAX_ILNUM`) +2. `startOffset` — encoded unsigned 32-bit integer +3. The next field depends on `varNumber`: + - If `varNumber == CALL_RETURN_ILNUM`: `callReturnValueILOffset` — encoded unsigned 32-bit integer (IL offset of the call site whose return value this entry describes). `endOffset` is implicit and equals `startOffset + 1`. + - Otherwise: `endOffset` — encoded as delta from `startOffset` (unsigned). `callReturnValueILOffset` is implicit and equals `0`. 4. `VarLocType` — encoded unsigned 32-bit integer 5. Location fields depend on the `VarLocType`: diff --git a/src/coreclr/debug/di/module.cpp b/src/coreclr/debug/di/module.cpp index 76f29b2965da95..42e9cb8dfb87ed 100644 --- a/src/coreclr/debug/di/module.cpp +++ b/src/coreclr/debug/di/module.cpp @@ -4237,7 +4237,22 @@ HRESULT CordbNativeCode::GetReturnValueLiveOffset(ULONG32 ILoffset, ULONG32 buff ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); EX_TRY { - hr = GetReturnValueLiveOffsetImpl(NULL, ILoffset, bufferSize, pFetched, pOffsets); + NewArrayHolder varInfos; + const ICorDebugInfo::NativeVarInfo **pVarInfosBuffer = NULL; + if (pOffsets != NULL && bufferSize > 0) + { + varInfos = new const ICorDebugInfo::NativeVarInfo *[bufferSize]; + pVarInfosBuffer = varInfos; + } + + hr = GetReturnValueLiveOffsetImpl(NULL, ILoffset, bufferSize, pFetched, pVarInfosBuffer); + if ((SUCCEEDED(hr) || hr == S_FALSE) && pVarInfosBuffer != NULL) + { + for (ULONG32 i = 0; i < *pFetched; ++i) + { + pOffsets[i] = pVarInfosBuffer[i]->startOffset; + } + } } EX_CATCH_HRESULT(hr); return hr; @@ -4611,7 +4626,7 @@ HRESULT CordbNativeCode::GetCallSignature(ULONG32 ILoffset, mdToken *pClass, mdT return GetSigParserFromFunction(mdFunction, pClass, parser, generics); } -HRESULT CordbNativeCode::GetReturnValueLiveOffsetImpl(Instantiation *currentInstantiation, ULONG32 ILoffset, ULONG32 bufferSize, ULONG32 *pFetched, ULONG32 *pOffsets) +HRESULT CordbNativeCode::GetReturnValueLiveOffsetImpl(Instantiation *currentInstantiation, ULONG32 ILoffset, ULONG32 bufferSize, ULONG32 *pFetched, const ICorDebugInfo::NativeVarInfo **ppVarInfos) { if (pFetched == NULL) return E_INVALIDARG; @@ -4625,7 +4640,7 @@ HRESULT CordbNativeCode::GetReturnValueLiveOffsetImpl(Instantiation *currentInst IfFailRet(GetCallSignature(ILoffset, &mdClass, NULL, signature, generics)); IfFailRet(EnsureReturnValueAllowed(currentInstantiation, mdClass, signature, generics)); - // now find the native offset + // now find the matching CALL_RETURN_ILNUM NativeVarInfo entries const DacDbiArrayList *pOffsetInfoList = m_nativeVarData.GetOffsetInfoList(); _ASSERTE(pOffsetInfoList != NULL); for (unsigned int i = 0; i < pOffsetInfoList->Count(); i++) @@ -4643,15 +4658,15 @@ HRESULT CordbNativeCode::GetReturnValueLiveOffsetImpl(Instantiation *currentInst } // if we have a buffer, fill it in. - if (pOffsets && found < bufferSize) + if (ppVarInfos && found < bufferSize) { - pOffsets[found] = pNativeVarInfo->startOffset; + ppVarInfos[found] = pNativeVarInfo; } found++; } - if (pOffsets) + if (ppVarInfos) *pFetched = found < bufferSize ? found : bufferSize; else *pFetched = found; @@ -4659,7 +4674,7 @@ HRESULT CordbNativeCode::GetReturnValueLiveOffsetImpl(Instantiation *currentInst if (found == 0) return E_FAIL; - if (pOffsets && found > bufferSize) + if (ppVarInfos && found > bufferSize) return S_FALSE; return S_OK; diff --git a/src/coreclr/debug/di/rspriv.h b/src/coreclr/debug/di/rspriv.h index 9ff85d9c00b002..f82804549acf56 100644 --- a/src/coreclr/debug/di/rspriv.h +++ b/src/coreclr/debug/di/rspriv.h @@ -5850,7 +5850,7 @@ class CordbNativeCode : public CordbCode, VMPTR_MethodDesc GetVMNativeCodeMethodDescToken() { return m_vmNativeCodeMethodDescToken; }; // Worker function for GetReturnValueLiveOffset. - HRESULT GetReturnValueLiveOffsetImpl(Instantiation *currentInstantiation, ULONG32 ILoffset, ULONG32 bufferSize, ULONG32 *pFetched, ULONG32 *pOffsets); + HRESULT GetReturnValueLiveOffsetImpl(Instantiation *currentInstantiation, ULONG32 ILoffset, ULONG32 bufferSize, ULONG32 *pFetched, const ICorDebugInfo::NativeVarInfo **ppVarInfos); // get total size of the code including both hot and cold regions ULONG32 GetSize(); @@ -7322,9 +7322,6 @@ class CordbJITILFrame : public CordbBase, public ICorDebugILFrame, public ICorDe // Worker function for GetReturnValueForILOffset. HRESULT GetReturnValueForILOffsetImpl(ULONG32 ILoffset, ICorDebugValue** ppReturnValue); - // Given pType, fills ppReturnValue with the correct value. - HRESULT GetReturnValueForType(CordbType *pType, ICorDebugValue **ppReturnValue); - //----------------------------------------------------------- // Data members //----------------------------------------------------------- diff --git a/src/coreclr/debug/di/rsthread.cpp b/src/coreclr/debug/di/rsthread.cpp index fc8dbb8e0706e6..8fe3ea81025bb6 100644 --- a/src/coreclr/debug/di/rsthread.cpp +++ b/src/coreclr/debug/di/rsthread.cpp @@ -8814,21 +8814,21 @@ HRESULT CordbJITILFrame::GetReturnValueForILOffsetImpl(ULONG32 ILoffset, ICorDeb ULONG32 count = 0; IfFailRet(pCode->GetReturnValueLiveOffsetImpl(&m_genericArgs, ILoffset, 0, &count, NULL)); - NewArrayHolder offsets(new ULONG32[count]); - IfFailRet(pCode->GetReturnValueLiveOffsetImpl(&m_genericArgs, ILoffset, count, &count, offsets)); + NewArrayHolder varInfos(new const ICorDebugInfo::NativeVarInfo *[count]); + IfFailRet(pCode->GetReturnValueLiveOffsetImpl(&m_genericArgs, ILoffset, count, &count, varInfos)); - bool found = false; + const ICorDebugInfo::NativeVarInfo *pReturnVarInfo = NULL; ULONG32 currentOffset = m_nativeFrame->GetIPOffset(); for (ULONG32 i = 0; i < count; ++i) { - if (currentOffset == offsets[i]) + if (currentOffset == varInfos[i]->startOffset) { - found = true; + pReturnVarInfo = varInfos[i]; break; } } - if (!found) + if (pReturnVarInfo == NULL) return E_UNEXPECTED; // Get the signatures and mdToken for the callee. @@ -8837,69 +8837,13 @@ HRESULT CordbJITILFrame::GetReturnValueForILOffsetImpl(ULONG32 ILoffset, ICorDeb IfFailRet(pCode->GetCallSignature(ILoffset, &targetClass, &mdFunction, methodSig, genericSig)); IfFailRet(CordbNativeCode::SkipToReturn(methodSig)); - - - // Create the Instantiation, type and then return value NewArrayHolder types; Instantiation inst; CordbType *pType = 0; IfFailRet(BuildInstantiationForCallsite(GetModule(), types, inst, &m_genericArgs, targetClass, genericSig)); IfFailRet(CordbType::SigToType(GetModule(), &methodSig, &inst, &pType)); - return GetReturnValueForType(pType, ppReturnValue); -} - - -HRESULT CordbJITILFrame::GetReturnValueForType(CordbType *pType, ICorDebugValue **ppReturnValue) -{ - - -#if defined(TARGET_X86) - const CorDebugRegister floatRegister = REGISTER_X86_FPSTACK_0; -#elif defined(TARGET_AMD64) - const CorDebugRegister floatRegister = REGISTER_AMD64_XMM0; -#elif defined(TARGET_ARM64) - const CorDebugRegister floatRegister = REGISTER_ARM64_V0; -#elif defined(TARGET_ARM) - const CorDebugRegister floatRegister = REGISTER_ARM_D0; -#elif defined(TARGET_LOONGARCH64) - const CorDebugRegister floatRegister = REGISTER_LOONGARCH64_F0; -#elif defined(TARGET_RISCV64) - const CorDebugRegister floatRegister = REGISTER_RISCV64_F0; -#endif - -#if defined(TARGET_X86) - const CorDebugRegister ptrRegister = REGISTER_X86_EAX; - const CorDebugRegister ptrHighWordRegister = REGISTER_X86_EDX; -#elif defined(TARGET_AMD64) - const CorDebugRegister ptrRegister = REGISTER_AMD64_RAX; -#elif defined(TARGET_ARM64) - const CorDebugRegister ptrRegister = REGISTER_ARM64_X0; -#elif defined(TARGET_ARM) - const CorDebugRegister ptrRegister = REGISTER_ARM_R0; - const CorDebugRegister ptrHighWordRegister = REGISTER_ARM_R1; -#elif defined(TARGET_LOONGARCH64) - const CorDebugRegister ptrRegister = REGISTER_LOONGARCH64_A0; -#elif defined(TARGET_RISCV64) - const CorDebugRegister ptrRegister = REGISTER_RISCV64_A0; -#endif - - CorElementType corReturnType = pType->GetElementType(); - switch (corReturnType) - { - default: - return m_nativeFrame->GetLocalRegisterValue(ptrRegister, pType, ppReturnValue); - - case ELEMENT_TYPE_R4: - case ELEMENT_TYPE_R8: - return m_nativeFrame->GetLocalFloatingPointValue(floatRegister, pType, ppReturnValue); - -#if defined(TARGET_X86) || defined(TARGET_ARM) - case ELEMENT_TYPE_I8: - case ELEMENT_TYPE_U8: - return m_nativeFrame->GetLocalDoubleRegisterValue(ptrHighWordRegister, ptrRegister, pType, ppReturnValue); -#endif - } + return GetNativeVariable(pType, pReturnVarInfo, ppReturnValue); } HRESULT CordbJITILFrame::EnumerateLocalVariablesEx(ILCodeKind flags, ICorDebugValueEnum **ppValueEnum) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs index a05b2d4315740b..1d4ca1e505dbb9 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs @@ -72,6 +72,11 @@ public readonly struct DebugVarInfo public uint BaseRegister2 { get; init; } /// Second stack offset (RegisterStack). public int StackOffset2 { get; init; } + /// + /// For == ICorDebugInfo::CALL_RETURN_ILNUM entries, the IL offset of + /// the call site whose return value this entry describes. Zero for all other entries. + /// + public uint CallReturnValueILOffset { get; init; } } public interface IDebugInfo : IContract diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoHelpers.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoHelpers.cs index ab13d1f24b46af..244e5edec62ac4 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoHelpers.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoHelpers.cs @@ -41,7 +41,11 @@ private enum VarLocType private const uint SOURCE_TYPE_BITS_V2 = 3; // ICorDebugInfo::MAX_ILNUM sentinel value used for adjusted encoding of var numbers. - private const uint MAX_ILNUM = unchecked((uint)-5); + private const uint MAX_ILNUM = unchecked((uint)-6); + + // ICorDebugInfo::CALL_RETURN_ILNUM. Marks a NativeVarInfo whose location holds the + // return value of a call site. Such entries use a different on-wire layout (see DoVars). + private const uint CALL_RETURN_ILNUM = unchecked((uint)-5); internal static IEnumerable DoBounds(NativeReader nativeReader, uint version) { @@ -115,9 +119,19 @@ internal static IEnumerable DoVars(NativeReader nativeReader, bool for (uint i = 0; i < varCount; i++) { - uint startOffset = reader.ReadUInt(); - uint endOffset = startOffset + reader.ReadUInt(); uint varNumber = reader.ReadUInt() + MAX_ILNUM; + uint startOffset = reader.ReadUInt(); + uint endOffset; + uint callReturnValueILOffset = 0; + if (varNumber == CALL_RETURN_ILNUM) + { + callReturnValueILOffset = reader.ReadUInt(); + endOffset = startOffset + 1; + } + else + { + endOffset = startOffset + reader.ReadUInt(); + } VarLocType locType = (VarLocType)reader.ReadUInt(); if (locType is VarLocType.VLT_INVALID or VarLocType.VLT_COUNT) @@ -127,59 +141,59 @@ internal static IEnumerable DoVars(NativeReader nativeReader, bool { VarLocType.VLT_REG or VarLocType.VLT_REG_FP => new DebugVarInfo { - StartOffset = startOffset, EndOffset = endOffset, VarNumber = varNumber, + StartOffset = startOffset, EndOffset = endOffset, VarNumber = varNumber, CallReturnValueILOffset = callReturnValueILOffset, Kind = DebugVarLocKind.Register, Register = reader.ReadUInt(), }, VarLocType.VLT_REG_BYREF => new DebugVarInfo { - StartOffset = startOffset, EndOffset = endOffset, VarNumber = varNumber, + StartOffset = startOffset, EndOffset = endOffset, VarNumber = varNumber, CallReturnValueILOffset = callReturnValueILOffset, Kind = DebugVarLocKind.Register, Register = reader.ReadUInt(), IsByRef = true, }, VarLocType.VLT_STK => new DebugVarInfo { - StartOffset = startOffset, EndOffset = endOffset, VarNumber = varNumber, + StartOffset = startOffset, EndOffset = endOffset, VarNumber = varNumber, CallReturnValueILOffset = callReturnValueILOffset, Kind = DebugVarLocKind.Stack, BaseRegister = reader.ReadUInt(), StackOffset = ReadEncodedStackOffset(reader, isX86), }, VarLocType.VLT_STK_BYREF => new DebugVarInfo { - StartOffset = startOffset, EndOffset = endOffset, VarNumber = varNumber, + StartOffset = startOffset, EndOffset = endOffset, VarNumber = varNumber, CallReturnValueILOffset = callReturnValueILOffset, Kind = DebugVarLocKind.Stack, BaseRegister = reader.ReadUInt(), StackOffset = ReadEncodedStackOffset(reader, isX86), IsByRef = true, }, VarLocType.VLT_REG_REG => new DebugVarInfo { - StartOffset = startOffset, EndOffset = endOffset, VarNumber = varNumber, + StartOffset = startOffset, EndOffset = endOffset, VarNumber = varNumber, CallReturnValueILOffset = callReturnValueILOffset, Kind = DebugVarLocKind.RegisterRegister, Register = reader.ReadUInt(), Register2 = reader.ReadUInt(), }, VarLocType.VLT_REG_STK => new DebugVarInfo { - StartOffset = startOffset, EndOffset = endOffset, VarNumber = varNumber, + StartOffset = startOffset, EndOffset = endOffset, VarNumber = varNumber, CallReturnValueILOffset = callReturnValueILOffset, Kind = DebugVarLocKind.RegisterStack, Register = reader.ReadUInt(), BaseRegister2 = reader.ReadUInt(), StackOffset2 = ReadEncodedStackOffset(reader, isX86), }, VarLocType.VLT_STK_REG => new DebugVarInfo { - StartOffset = startOffset, EndOffset = endOffset, VarNumber = varNumber, + StartOffset = startOffset, EndOffset = endOffset, VarNumber = varNumber, CallReturnValueILOffset = callReturnValueILOffset, Kind = DebugVarLocKind.StackRegister, StackOffset = ReadEncodedStackOffset(reader, isX86), BaseRegister = reader.ReadUInt(), Register = reader.ReadUInt(), }, VarLocType.VLT_STK2 => new DebugVarInfo { - StartOffset = startOffset, EndOffset = endOffset, VarNumber = varNumber, + StartOffset = startOffset, EndOffset = endOffset, VarNumber = varNumber, CallReturnValueILOffset = callReturnValueILOffset, Kind = DebugVarLocKind.DoubleStack, BaseRegister = reader.ReadUInt(), StackOffset = ReadEncodedStackOffset(reader, isX86), }, // FPSTK and FIXED_VA: consume stream data to keep reader aligned, but no public // DebugVarLocKind exists for these rarely-used location types. - VarLocType.VLT_FPSTK => ConsumeAndDefault(reader.ReadUInt(), startOffset, endOffset, varNumber), - VarLocType.VLT_FIXED_VA => ConsumeAndDefault(reader.ReadUInt(), startOffset, endOffset, varNumber), + VarLocType.VLT_FPSTK => ConsumeAndDefault(reader.ReadUInt(), startOffset, endOffset, varNumber, callReturnValueILOffset), + VarLocType.VLT_FIXED_VA => ConsumeAndDefault(reader.ReadUInt(), startOffset, endOffset, varNumber, callReturnValueILOffset), _ => new DebugVarInfo { - StartOffset = startOffset, EndOffset = endOffset, VarNumber = varNumber, + StartOffset = startOffset, EndOffset = endOffset, VarNumber = varNumber, CallReturnValueILOffset = callReturnValueILOffset, }, }; } } - private static DebugVarInfo ConsumeAndDefault(uint _, uint startOffset, uint endOffset, uint varNumber) => new() + private static DebugVarInfo ConsumeAndDefault(uint _, uint startOffset, uint endOffset, uint varNumber, uint callReturnValueILOffset) => new() { - StartOffset = startOffset, EndOffset = endOffset, VarNumber = varNumber, + StartOffset = startOffset, EndOffset = endOffset, VarNumber = varNumber, CallReturnValueILOffset = callReturnValueILOffset, }; private static int ReadEncodedStackOffset(NibbleReader reader, bool isX86) From 7479835a5f2502564aa4bdcdce8745d8175f3867 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Fri, 5 Jun 2026 10:55:39 -0700 Subject: [PATCH 21/22] name --- src/coreclr/debug/di/module.cpp | 4 ++-- src/coreclr/debug/di/rspriv.h | 2 +- src/coreclr/debug/di/rsthread.cpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/coreclr/debug/di/module.cpp b/src/coreclr/debug/di/module.cpp index 42e9cb8dfb87ed..3e2a4a01f9f6cb 100644 --- a/src/coreclr/debug/di/module.cpp +++ b/src/coreclr/debug/di/module.cpp @@ -4245,7 +4245,7 @@ HRESULT CordbNativeCode::GetReturnValueLiveOffset(ULONG32 ILoffset, ULONG32 buff pVarInfosBuffer = varInfos; } - hr = GetReturnValueLiveOffsetImpl(NULL, ILoffset, bufferSize, pFetched, pVarInfosBuffer); + hr = GetReturnValueVariableHomes(NULL, ILoffset, bufferSize, pFetched, pVarInfosBuffer); if ((SUCCEEDED(hr) || hr == S_FALSE) && pVarInfosBuffer != NULL) { for (ULONG32 i = 0; i < *pFetched; ++i) @@ -4626,7 +4626,7 @@ HRESULT CordbNativeCode::GetCallSignature(ULONG32 ILoffset, mdToken *pClass, mdT return GetSigParserFromFunction(mdFunction, pClass, parser, generics); } -HRESULT CordbNativeCode::GetReturnValueLiveOffsetImpl(Instantiation *currentInstantiation, ULONG32 ILoffset, ULONG32 bufferSize, ULONG32 *pFetched, const ICorDebugInfo::NativeVarInfo **ppVarInfos) +HRESULT CordbNativeCode::GetReturnValueVariableHomes(Instantiation *currentInstantiation, ULONG32 ILoffset, ULONG32 bufferSize, ULONG32 *pFetched, const ICorDebugInfo::NativeVarInfo **ppVarInfos) { if (pFetched == NULL) return E_INVALIDARG; diff --git a/src/coreclr/debug/di/rspriv.h b/src/coreclr/debug/di/rspriv.h index f82804549acf56..1276c86a8e9abc 100644 --- a/src/coreclr/debug/di/rspriv.h +++ b/src/coreclr/debug/di/rspriv.h @@ -5850,7 +5850,7 @@ class CordbNativeCode : public CordbCode, VMPTR_MethodDesc GetVMNativeCodeMethodDescToken() { return m_vmNativeCodeMethodDescToken; }; // Worker function for GetReturnValueLiveOffset. - HRESULT GetReturnValueLiveOffsetImpl(Instantiation *currentInstantiation, ULONG32 ILoffset, ULONG32 bufferSize, ULONG32 *pFetched, const ICorDebugInfo::NativeVarInfo **ppVarInfos); + HRESULT GetReturnValueVariableHomes(Instantiation *currentInstantiation, ULONG32 ILoffset, ULONG32 bufferSize, ULONG32 *pFetched, const ICorDebugInfo::NativeVarInfo **ppVarInfos); // get total size of the code including both hot and cold regions ULONG32 GetSize(); diff --git a/src/coreclr/debug/di/rsthread.cpp b/src/coreclr/debug/di/rsthread.cpp index 8fe3ea81025bb6..4c1de068775829 100644 --- a/src/coreclr/debug/di/rsthread.cpp +++ b/src/coreclr/debug/di/rsthread.cpp @@ -8812,10 +8812,10 @@ HRESULT CordbJITILFrame::GetReturnValueForILOffsetImpl(ULONG32 ILoffset, ICorDeb pCode->LoadNativeInfo(); ULONG32 count = 0; - IfFailRet(pCode->GetReturnValueLiveOffsetImpl(&m_genericArgs, ILoffset, 0, &count, NULL)); + IfFailRet(pCode->GetReturnValueVariableHomes(&m_genericArgs, ILoffset, 0, &count, NULL)); NewArrayHolder varInfos(new const ICorDebugInfo::NativeVarInfo *[count]); - IfFailRet(pCode->GetReturnValueLiveOffsetImpl(&m_genericArgs, ILoffset, count, &count, varInfos)); + IfFailRet(pCode->GetReturnValueVariableHomes(&m_genericArgs, ILoffset, count, &count, varInfos)); const ICorDebugInfo::NativeVarInfo *pReturnVarInfo = NULL; ULONG32 currentOffset = m_nativeFrame->GetIPOffset(); From db70c3388cf42068afacc867a25c6ce51cbf534e Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Mon, 8 Jun 2026 12:31:09 +0200 Subject: [PATCH 22/22] Copilot feedback --- src/coreclr/debug/di/module.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/coreclr/debug/di/module.cpp b/src/coreclr/debug/di/module.cpp index 3e2a4a01f9f6cb..3bd2ea636603b3 100644 --- a/src/coreclr/debug/di/module.cpp +++ b/src/coreclr/debug/di/module.cpp @@ -4237,6 +4237,8 @@ HRESULT CordbNativeCode::GetReturnValueLiveOffset(ULONG32 ILoffset, ULONG32 buff ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); EX_TRY { + LoadNativeInfo(); + NewArrayHolder varInfos; const ICorDebugInfo::NativeVarInfo **pVarInfosBuffer = NULL; if (pOffsets != NULL && bufferSize > 0)