diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index 2785108723ebba..ebb88f712c611f 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -1255,6 +1255,10 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetNativeCodeInfo(VMPTR_Assembly if (pCodeInfo->m_rgCodeRegions[kHot].pAddress != (CORDB_ADDRESS)NULL) { pCodeInfo->isInstantiatedGeneric = pMethodDesc->HasClassOrMethodInstantiation(); + { + EECodeInfo codeInfo(PINSTRToPCODE((TADDR)pCodeInfo->m_rgCodeRegions[kHot].pAddress)); + pCodeInfo->isInterpreted = codeInfo.IsInterpretedCode(); + } LookupEnCVersions(pModule, pCodeInfo->vmNativeCodeMethodDescToken, functionToken, @@ -1335,6 +1339,7 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetNativeCodeInfoForAddr(CORDB_AD vmMethodDesc.SetHostPtr(codeInfo.GetMethodDesc()); MethodDesc* pMethodDesc = vmMethodDesc.GetDacPtr(); pCodeInfo->isInstantiatedGeneric = pMethodDesc->HasClassOrMethodInstantiation(); + pCodeInfo->isInterpreted = codeInfo.IsInterpretedCode(); pCodeInfo->vmNativeCodeMethodDescToken = vmMethodDesc; SIZE_T unusedLatestEncVersion; diff --git a/src/coreclr/debug/di/module.cpp b/src/coreclr/debug/di/module.cpp index 5249fa264bb343..1060ff357a8c46 100644 --- a/src/coreclr/debug/di/module.cpp +++ b/src/coreclr/debug/di/module.cpp @@ -17,6 +17,38 @@ #include "pedecoder.h" #include "memorystreams.h" +#ifdef FEATURE_INTERPRETER +#include "intopsshared.h" + +static const uint8_t s_interpOpLenTableForDI[] = +{ +#define OPDEF(a,b,c,d,e,f) c, +#include "intops.def" +#undef OPDEF +}; + +static int GetInterpreterCallOpcodeLengthInSlots(int32_t opcode) +{ + switch (opcode) + { + case INTOP_CALL: + case INTOP_CALL_NULLCHECK: + case INTOP_CALL_TAIL: + case INTOP_CALLDELEGATE: + case INTOP_CALLDELEGATE_TAIL: + case INTOP_CALLI: + case INTOP_CALLI_TAIL: + case INTOP_CALLVIRT: + case INTOP_CALLVIRT_TAIL: + case INTOP_CALL_PINVOKE: + _ASSERTE(opcode >= 0 && (size_t)opcode < ARRAY_SIZE(s_interpOpLenTableForDI)); + return s_interpOpLenTableForDI[opcode]; + default: + return -1; + } +} +#endif // FEATURE_INTERPRETER + //--------------------------------------------------------------------------------------- // Initialize a new CordbModule around a Module in the target. // @@ -3905,14 +3937,22 @@ HRESULT CordbVariableHome::GetOffset(LONG *pOffset) //----------------------------------------------------------------------------- CordbNativeCode::CordbNativeCode(CordbFunction * pFunction, const NativeCodeFunctionData * pJitData, - BOOL fIsInstantiatedGeneric) + BOOL fIsInstantiatedGeneric, + BOOL fIsInterpreted) : CordbCode(pFunction, (UINT_PTR)pJitData->m_rgCodeRegions[kHot].pAddress, pJitData->encVersion, FALSE), m_vmNativeCodeMethodDescToken(pJitData->vmNativeCodeMethodDescToken), m_fCodeAvailable(TRUE), m_fIsInstantiatedGeneric(fIsInstantiatedGeneric != FALSE) +#ifdef FEATURE_INTERPRETER + , m_fIsInterpreted(fIsInterpreted != FALSE) +#endif { _ASSERTE(GetVersion() >= CorDB_DEFAULT_ENC_FUNCTION_VERSION); +#ifndef FEATURE_INTERPRETER + (void)fIsInterpreted; +#endif + for (CodeBlobRegion region = kHot; region < MAX_REGIONS; ++region) { m_rgCodeRegions[region] = pJitData->m_rgCodeRegions[region]; @@ -4356,6 +4396,19 @@ HRESULT CordbNativeCode::EnumerateVariableHomes(ICorDebugVariableHomeEnum **ppEn int CordbNativeCode::GetCallInstructionLength(BYTE *ip, ULONG32 count) { +#ifdef FEATURE_INTERPRETER + if (IsInterpreted()) + { + if (count < sizeof(int32_t)) + return -1; + int32_t interpOpcode; + memcpy(&interpOpcode, ip, sizeof(int32_t)); + int interpSlots = GetInterpreterCallOpcodeLengthInSlots(interpOpcode); + if (interpSlots <= 0) + return -1; + return interpSlots * (int)sizeof(int32_t); + } +#endif // FEATURE_INTERPRETER #if defined(TARGET_ARM) || defined(TARGET_RISCV64) if (Is32BitInstruction(*(WORD*)ip)) return 4; @@ -5028,7 +5081,9 @@ HRESULT CordbNativeCode::GetReturnValueLiveOffsetImpl(Instantiation *currentInst // 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. + { + return E_UNEXPECTED; // Could not decode instruction, this should never happen. + } pOffsets[found] = pMap->nativeStartOffset + offset; } @@ -5051,6 +5106,55 @@ HRESULT CordbNativeCode::GetReturnValueLiveOffsetImpl(Instantiation *currentInst return S_OK; } +#ifdef FEATURE_INTERPRETER +HRESULT CordbNativeCode::GetInterpreterCallDvarOffset(ULONG32 ILoffset, ULONG32 postCallNativeOffset, int32_t* pDvarOffset) +{ + if (pDvarOffset == NULL) + return E_INVALIDARG; + + HRESULT hr = S_OK; + SequencePoints *pSP = GetSequencePoints(); + DebuggerILToNativeMap *pMap = pSP->GetCallsiteMapAddr(); + + for (ULONG32 i = 0; i < pSP->GetCallsiteEntryCount() && pMap; ++i, pMap++) + { + if (pMap->ilOffset != ILoffset || + (pMap->source & ICorDebugInfo::CALL_INSTRUCTION) != ICorDebugInfo::CALL_INSTRUCTION) + { + continue; + } + + // slot 0 = opcode, slot 1 = dvar byte offset + BYTE nativeBuffer[2 * sizeof(int32_t)]; + ULONG32 fetched = 0; + hr = GetCode(pMap->nativeStartOffset, + pMap->nativeStartOffset + ARRAY_SIZE(nativeBuffer), + ARRAY_SIZE(nativeBuffer), + nativeBuffer, + &fetched); + if (FAILED(hr) || fetched < ARRAY_SIZE(nativeBuffer)) + continue; + + int32_t interpOpcode; + memcpy(&interpOpcode, nativeBuffer, sizeof(int32_t)); + int interpSlots = GetInterpreterCallOpcodeLengthInSlots(interpOpcode); + if (interpSlots <= 0) + return E_UNEXPECTED; + + ULONG32 expectedPostCall = pMap->nativeStartOffset + interpSlots * (ULONG32)sizeof(int32_t); + if (expectedPostCall != postCallNativeOffset) + continue; + + int32_t dvarOffset; + memcpy(&dvarOffset, nativeBuffer + sizeof(int32_t), sizeof(int32_t)); + *pDvarOffset = dvarOffset; + return S_OK; + } + + return E_UNEXPECTED; +} +#endif // FEATURE_INTERPRETER + //----------------------------------------------------------------------------- // Creates a CordbNativeCode (if it's not already created) and adds it to the // hash table of CordbNativeCode instances belonging to this module. @@ -5105,7 +5209,7 @@ CordbNativeCode * CordbModule::LookupOrCreateNativeCode(mdMethodDef methodToken, pFunction->InitParentClassOfFunction(); // First, create a new CordbNativeCode instance--we'll need this to make the CordbJITInfo instance - pNativeCode = new (nothrow)CordbNativeCode(pFunction, &codeInfo, codeInfo.isInstantiatedGeneric != 0); + pNativeCode = new (nothrow)CordbNativeCode(pFunction, &codeInfo, codeInfo.isInstantiatedGeneric != 0, codeInfo.isInterpreted != 0); _ASSERTE(pNativeCode != NULL); m_nativeCodeTable.AddBaseOrThrow(pNativeCode); diff --git a/src/coreclr/debug/di/rspriv.h b/src/coreclr/debug/di/rspriv.h index 69f2876a8c1650..8bad53757245df 100644 --- a/src/coreclr/debug/di/rspriv.h +++ b/src/coreclr/debug/di/rspriv.h @@ -5771,7 +5771,8 @@ class CordbNativeCode : public CordbCode, public: CordbNativeCode(CordbFunction * pFunction, const NativeCodeFunctionData * pJitData, - BOOL fIsInstantiatedGeneric); + BOOL fIsInstantiatedGeneric, + BOOL fIsInterpreted); #ifdef _DEBUG const char * DbgGetName() { return "CordbNativeCode"; }; #endif // _DEBUG @@ -5842,6 +5843,11 @@ class CordbNativeCode : public CordbCode, // Worker function for GetReturnValueLiveOffset. HRESULT GetReturnValueLiveOffsetImpl(Instantiation *currentInstantiation, ULONG32 ILoffset, ULONG32 bufferSize, ULONG32 *pFetched, ULONG32 *pOffsets); +#ifdef FEATURE_INTERPRETER + // Returns the FP-relative offset of an interpreter call's destination var. + HRESULT GetInterpreterCallDvarOffset(ULONG32 ILoffset, ULONG32 postCallNativeOffset, int32_t* pDvarOffset); +#endif + // get total size of the code including both hot and cold regions ULONG32 GetSize(); @@ -5876,6 +5882,13 @@ class CordbNativeCode : public CordbCode, return m_fIsInstantiatedGeneric != 0; } +#ifdef FEATURE_INTERPRETER + BOOL IsInterpreted() + { + return m_fIsInterpreted != 0; + } +#endif + // Determine whether we have initialized the native variable and // sequence point offsets BOOL IsNativeCodeValid () @@ -5929,6 +5942,9 @@ class CordbNativeCode : public CordbCode, bool m_fCodeAvailable; // true iff the code has been jitted but not pitched bool m_fIsInstantiatedGeneric; // true iff this is an instantiated generic +#ifdef FEATURE_INTERPRETER + bool m_fIsInterpreted; // true iff compiled by the CoreCLR interpreter +#endif // information in the following two classes tracks native offsets and is initialized on demand. @@ -6764,11 +6780,11 @@ typedef std::function ValueGette class CordbValueEnum : public CordbBase, public ICorDebugValueEnum { public: - CordbValueEnum(CordbProcess* pProcess, - UINT maxCount, + CordbValueEnum(CordbProcess* pProcess, + UINT maxCount, ValueGetter valueGetter, NeuterList* pNeuterList); - + virtual ~CordbValueEnum(); virtual void Neuter(); diff --git a/src/coreclr/debug/di/rsthread.cpp b/src/coreclr/debug/di/rsthread.cpp index 5636d7bf48da3d..d057540e9d6e5e 100644 --- a/src/coreclr/debug/di/rsthread.cpp +++ b/src/coreclr/debug/di/rsthread.cpp @@ -8847,6 +8847,24 @@ HRESULT CordbJITILFrame::GetReturnValueForILOffsetImpl(ULONG32 ILoffset, ICorDeb CordbType *pType = 0; IfFailRet(BuildInstantiationForCallsite(GetModule(), types, inst, &m_genericArgs, targetClass, genericSig)); IfFailRet(CordbType::SigToType(GetModule(), &methodSig, &inst, &pType)); + +#ifdef FEATURE_INTERPRETER + if (pCode->IsInterpreted()) + { + // Interpreter return values live in an FP-relative dvar, not a register. + int32_t dvarOffset = 0; + IfFailRet(pCode->GetInterpreterCallDvarOffset(ILoffset, currentOffset, &dvarOffset)); + ICorDebugInfo::NativeVarInfo synthetic = {}; + synthetic.startOffset = currentOffset; + synthetic.endOffset = currentOffset; + synthetic.varNumber = 0; + synthetic.loc.vlType = ICorDebugInfo::VLT_STK; + synthetic.loc.vlStk.vlsBaseReg = ICorDebugInfo::REGNUM_FP; + synthetic.loc.vlStk.vlsOffset = dvarOffset; + return GetNativeVariable(pType, &synthetic, ppReturnValue); + } +#endif // FEATURE_INTERPRETER + return GetReturnValueForType(pType, ppReturnValue); } diff --git a/src/coreclr/debug/inc/dacdbistructures.h b/src/coreclr/debug/inc/dacdbistructures.h index aff80317c59b32..c833d568f8ceb0 100644 --- a/src/coreclr/debug/inc/dacdbistructures.h +++ b/src/coreclr/debug/inc/dacdbistructures.h @@ -505,6 +505,9 @@ class MSLAYOUT NativeCodeFunctionData // indicates whether the function is a generic function, or a method inside a generic class (or both). BOOL isInstantiatedGeneric; + // indicates whether the function is compiled by the CoreCLR interpreter rather than the JIT. + BOOL isInterpreted; + // MethodDesc for the function VMPTR_MethodDesc vmNativeCodeMethodDescToken; diff --git a/src/coreclr/debug/inc/dacdbistructures.inl b/src/coreclr/debug/inc/dacdbistructures.inl index 52987b5013403d..541e580fc1e5ec 100644 --- a/src/coreclr/debug/inc/dacdbistructures.inl +++ b/src/coreclr/debug/inc/dacdbistructures.inl @@ -571,6 +571,7 @@ NativeCodeFunctionData::NativeCodeFunctionData(DebuggerIPCE_JITFuncData * source // copy the other function information isInstantiatedGeneric = source->isInstantiatedGeneric; + isInterpreted = FALSE; vmNativeCodeMethodDescToken = source->vmNativeCodeMethodDescToken; encVersion = source->enCVersion; } @@ -587,6 +588,7 @@ inline void NativeCodeFunctionData::Clear() { isInstantiatedGeneric = FALSE; + isInterpreted = FALSE; encVersion = CorDB_DEFAULT_ENC_FUNCTION_VERSION; for (CodeBlobRegion region = kHot; region < MAX_REGIONS; ++region) { diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index bb5ee520be236c..4c49525d5ae355 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -324,7 +324,11 @@ InterpInst* InterpCompiler::NewIns(int opcode, int dataLen) // This is the first instruction we are emitting for this IL offset and the stack is empty, which // implies the IL stack is empty too. ins->flags |= INTERP_INST_FLAG_EMPTY_IL_STACK; - m_isFirstInstForEmptyILStack = false; + // Skip emit-nop instructions so the flag falls through to the first real IR. + if (!InterpOpIsEmitNop(opcode)) + { + m_isFirstInstForEmptyILStack = false; + } } m_pLastNewIns = ins; return ins; @@ -1054,9 +1058,8 @@ int32_t* InterpCompiler::EmitCodeIns(int32_t *ip, InterpInst *ins, TArraynativeOffset); - // Only emit mapping entries at IL offsets where the evaluation stack is empty - if ((ins->flags & INTERP_INST_FLAG_EMPTY_IL_STACK) && - ((m_ILToNativeMapSize == 0) || (m_pILToNativeMap[m_ILToNativeMapSize - 1].ilOffset != ilOffset))) + // Emit one entry per IL offset, tagging STACK_EMPTY only when the IL stack was empty. + if ((m_ILToNativeMapSize == 0) || (m_pILToNativeMap[m_ILToNativeMapSize - 1].ilOffset != ilOffset)) { // This code assumes that instructions for the same IL offset are emitted in a single run without // any other IL offsets in between and that they don't repeat again after the run ends. @@ -1072,13 +1075,23 @@ int32_t* InterpCompiler::EmitCodeIns(int32_t *ip, InterpInst *ins, TArrayflags & INTERP_INST_FLAG_EMPTY_IL_STACK) + ? ICorDebugInfo::STACK_EMPTY + : ICorDebugInfo::SOURCE_TYPE_INVALID; + m_ILToNativeMapSize++; + } + + if (ins->flags & INTERP_INST_FLAG_DBG_CALL_INSTRUCTION) + { + assert(m_ILToNativeMapSize < m_ILToNativeMapCapacity); m_pILToNativeMap[m_ILToNativeMapSize].ilOffset = ilOffset; m_pILToNativeMap[m_ILToNativeMapSize].nativeOffset = nativeOffset; - m_pILToNativeMap[m_ILToNativeMapSize].source = ICorDebugInfo::STACK_EMPTY; + m_pILToNativeMap[m_ILToNativeMapSize].source = ICorDebugInfo::CALL_INSTRUCTION; m_ILToNativeMapSize++; } } @@ -1178,7 +1191,9 @@ void InterpCompiler::EmitCode() // This will eventually be freed by the VM, using freeArray. // If we fail before handing them to the VM, there is logic in the InterpCompiler destructor to free it. - m_pILToNativeMap = (ICorDebugInfo::OffsetMapping*)m_compHnd->allocateArray(m_ILCodeSize * sizeof(ICorDebugInfo::OffsetMapping)); + // Each IL offset may produce up to two entries (STACK_EMPTY plus CALL_INSTRUCTION). + m_ILToNativeMapCapacity = m_ILCodeSize * 2; + m_pILToNativeMap = (ICorDebugInfo::OffsetMapping*)m_compHnd->allocateArray(m_ILToNativeMapCapacity * sizeof(ICorDebugInfo::OffsetMapping)); // For each BB, compute the number of EH clauses that overlap with it. for (unsigned int i = 0; i < getEHcount(m_methodInfo); i++) @@ -5564,6 +5579,8 @@ void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool re m_pLastNewIns->SetSVar(CALL_ARGS_SVAR); m_pLastNewIns->flags |= INTERP_INST_FLAG_CALL; + if (!tailcall && !newObj) + m_pLastNewIns->flags |= INTERP_INST_FLAG_DBG_CALL_INSTRUCTION; m_pLastNewIns->info.pCallInfo = new (getAllocator(IMK_CallInfo)) InterpCallInfo(); m_pLastNewIns->info.pCallInfo->pCallArgs = callArgs; diff --git a/src/coreclr/interpreter/compiler.h b/src/coreclr/interpreter/compiler.h index 485c55503ea977..44ed16354e5a32 100644 --- a/src/coreclr/interpreter/compiler.h +++ b/src/coreclr/interpreter/compiler.h @@ -241,7 +241,9 @@ enum InterpInstFlags // Flag used internally by the var offset allocator INTERP_INST_FLAG_ACTIVE_CALL = 0x02, // The IL stack is empty at this instruction - INTERP_INST_FLAG_EMPTY_IL_STACK = 0x04 + INTERP_INST_FLAG_EMPTY_IL_STACK = 0x04, + // Marks a user IL call site reportable to ICorDebugCode3::GetReturnValueLiveOffset + INTERP_INST_FLAG_DBG_CALL_INSTRUCTION = 0x08 }; struct InterpInst @@ -856,6 +858,7 @@ class InterpCompiler int32_t* m_pNativeMapIndexToILOffset = NULL; #endif int32_t m_ILToNativeMapSize = 0; + int32_t m_ILToNativeMapCapacity = 0; InterpBasicBlock* AllocBB(int32_t ilOffset); InterpBasicBlock* GetBB(int32_t ilOffset);