From 99fed788905d22bb27558adc6313abf8cc16bba4 Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Fri, 26 Jun 2026 11:17:41 -0400 Subject: [PATCH 01/16] Add MRV debug info for multi-register FP/mixed struct returns on Unix x64 Adds managed-return-value (MRV) debug-info encoding for value-class returns that live in two registers where at least one is a floating-point register (e.g. a 16-byte struct returned in XMM0+XMM1, or a mixed ValueTuple returned in XMM0+RAX on SysV x64). Previously these were silently skipped. - New VarLocType values VLT_REG_FP_REG_FP, VLT_REG_FP_REG, VLT_REG_REG_FP. - JIT producer (scopeinfo/codegencommon) emits the appropriate encoding via storeVariableInTwoRegisters instead of skipping non-integer register pairs. - Serializer (debuginfostore) encodes the two register indices. - DBI consumer reconstructs the value via a snapshot-based TwoRegisterValueHome (64-bit only). - CordbType::ReturnedByValue permits FP/generic fields only for two-register (>8 byte) returns, leaving single-register value classes unsupported. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/debug/di/rspriv.h | 78 ++++++++++ src/coreclr/debug/di/rsthread.cpp | 137 +++++++++++++++++- src/coreclr/debug/di/rstype.cpp | 71 ++++++++- src/coreclr/debug/di/valuehome.cpp | 45 +++++- src/coreclr/debug/ee/debugger.cpp | 3 + src/coreclr/debug/ee/functioninfo.cpp | 18 +++ src/coreclr/inc/cordebuginfo.h | 13 ++ src/coreclr/jit/codegencommon.cpp | 27 +++- src/coreclr/jit/codegeninterface.h | 5 + src/coreclr/jit/ee_il_dll.cpp | 15 ++ src/coreclr/jit/scopeinfo.cpp | 105 ++++++++++++-- .../JitInterface/CorInfoTypes.VarInfo.cs | 4 + .../CodeView/CodeViewSymbolsBuilder.cs | 3 + .../ReadyToRun/DebugInfoTableNode.cs | 8 + .../DebugInfo.cs | 6 + .../DebugInfoTypes.cs | 4 + src/coreclr/tools/r2rdump/Extensions.cs | 6 + src/coreclr/vm/debuginfostore.cpp | 10 ++ src/coreclr/vm/util.cpp | 3 + 19 files changed, 529 insertions(+), 32 deletions(-) diff --git a/src/coreclr/debug/di/rspriv.h b/src/coreclr/debug/di/rspriv.h index ace851825d6721..02ddc2b1b96146 100644 --- a/src/coreclr/debug/di/rspriv.h +++ b/src/coreclr/debug/di/rspriv.h @@ -6994,6 +6994,19 @@ class CordbNativeFrame : public CordbFrame, public ICorDebugNativeFrame, public CordbType * pType, ICorDebugValue **ppValue); + // Build a value that lives in two registers, where either register may be an + // integer or a floating-point register (e.g. a 16-byte struct returned in + // XMM0+XMM1 on Unix x64, or a mixed int/fp multi-register return). lowReg/highReg + // hold the low/high 8 bytes of the value; when the corresponding *IsFloat flag is + // true the register is a 0-based fp register index, otherwise it is a + // CorDebugRegister. + HRESULT GetLocalTwoRegisterValue(DWORD lowReg, + bool lowIsFloat, + DWORD highReg, + bool highIsFloat, + CordbType * pType, + ICorDebugValue **ppValue); + CORDB_ADDRESS GetLSStackAddress(ICorDebugInfo::RegNum regNum, signed offset); @@ -7877,6 +7890,71 @@ class RegRegValueHome: public RegValueHome const RegisterInfo m_reg2Info; }; // class RegRegValueHome +// class TwoRegisterValueHome +// EnregisteredValueHome for a value that lives in two registers where at least one is a +// floating-point register (e.g. a 16-byte struct returned in XMM0+XMM1 on Unix x64, or a +// mixed int/fp multi-register return). +// Floating-point register contents are not reachable through the integer register display, so +// rather than referencing live registers this home captures a snapshot of the 16-byte value +// (low 8 bytes followed by high 8 bytes) when it is created. The snapshot is used to populate +// the value's local object copy and is cloned for field access. Writing back to a +// multi-register return value is not supported. +class TwoRegisterValueHome: public EnregisteredValueHome +{ +public: + // initializing constructor + // Arguments: + // input: pFrame - frame to which the value belongs + // pValue - pointer to the snapshot bytes (low 8 bytes followed by high 8 bytes) + // size - number of valid bytes pointed to by pValue + TwoRegisterValueHome(const CordbNativeFrame * pFrame, const BYTE * pValue, ULONG32 size): + EnregisteredValueHome(pFrame) + { + _ASSERTE(size <= sizeof(m_value)); + memset(m_value, 0, sizeof(m_value)); + if (pValue != NULL) + { + memcpy(m_value, pValue, (size < sizeof(m_value)) ? size : (ULONG32)sizeof(m_value)); + } + }; + + // copy constructor + TwoRegisterValueHome(const TwoRegisterValueHome * pRemoteRegAddr): + EnregisteredValueHome(pRemoteRegAddr->m_pFrame) + { + memcpy(m_value, pRemoteRegAddr->m_value, sizeof(m_value)); + }; + + // make a copy of this instance of TwoRegisterValueHome + virtual + TwoRegisterValueHome * Clone() const { return new TwoRegisterValueHome(*this); }; + + // writing back to a multi-register return value is not supported + virtual + void SetEnregisteredValue(MemoryRange newValue, DT_CONTEXT * pContext, bool fIsSigned) + { + ThrowHR(CORDBG_E_SET_VALUE_NOT_ALLOWED_ON_NONLEAF_FRAME); + }; + + // Gets the snapshot value and returns it to the caller + virtual + void GetEnregisteredValue(MemoryRange valueOutBuffer); + + // initializing an instance of RemoteAddress is not supported for a local snapshot + virtual + void CopyToIPCEType(RemoteAddress * pRegAddr) + { + ThrowHR(E_NOTIMPL); + }; + + //------------------------------------- + // data members + //------------------------------------- +private: + // Snapshot of the value: low 8 bytes followed by high 8 bytes. + BYTE m_value[2 * sizeof(double)]; +}; // class TwoRegisterValueHome + // class RegAndMemBaseValueHome // derived from RegValueHome, this class is also a base class for RegMemValueHome // and MemRegValueHome, which add a memory location for reg-mem or mem-reg values diff --git a/src/coreclr/debug/di/rsthread.cpp b/src/coreclr/debug/di/rsthread.cpp index d2d4cf5b25518c..52257a7675758e 100644 --- a/src/coreclr/debug/di/rsthread.cpp +++ b/src/coreclr/debug/di/rsthread.cpp @@ -6787,10 +6787,11 @@ HRESULT CordbNativeFrame::GetLocalDoubleRegisterValue( // nickbe // 10/31/2002 11:09:42 // - // This assert assumes that the JIT will only partially enregister - // objects that have a size equal to twice the size of a register. + // The JIT partially enregisters an object across two registers. The + // object occupies more than one register (otherwise it would be a + // single-register home) and at most two registers' worth of space. // - _ASSERTE(objectSize == 2 * sizeof(void*)); + _ASSERTE((objectSize > sizeof(void*)) && (objectSize <= 2 * sizeof(void*))); } } #endif @@ -7043,6 +7044,108 @@ HRESULT CordbNativeFrame::GetLocalFloatingPointValue(DWORD index, return hr; } +// Build a value that lives in two registers, where either register may be an +// integer or a floating-point register (e.g. a 16-byte struct returned in +// XMM0+XMM1 on Unix x64, or a mixed int/fp multi-register return). The low and +// high 8-byte halves are gathered from the appropriate register sources into a +// contiguous local snapshot, then the value is built from that snapshot. +// +// Arguments: +// lowReg - the register holding the low 8 bytes. When lowIsFloat is true +// this is a 0-based fp register index, otherwise a CorDebugRegister. +// lowIsFloat - whether the low half is in a floating-point register. +// highReg - the register holding the high 8 bytes. When highIsFloat is true +// this is a 0-based fp register index, otherwise a CorDebugRegister. +// highIsFloat - whether the high half is in a floating-point register. +// pType - the type of the value. +// ppValue - [out] the newly created value. +// +// Note: This produces a read-only value snapshot (no register value-home for +// write-back), which matches how multi-register return values are inspected. +HRESULT CordbNativeFrame::GetLocalTwoRegisterValue(DWORD lowReg, + bool lowIsFloat, + DWORD highReg, + bool highIsFloat, + CordbType * pType, + ICorDebugValue **ppValue) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + HRESULT hr = S_OK; + + // Snapshot of the value: low 8 bytes followed by high 8 bytes. + BYTE valueBuffer[2 * sizeof(double)] = {0}; + + EX_TRY + { + CordbThread * pThread = m_pThread; + + // Ensure the floating-point state is loaded if either half lives in an fp register. + if (lowIsFloat || highIsFloat) + { + if (!pThread->m_fFloatStateValid) + { + pThread->LoadFloatState(); + } + } + + const DWORD numFloatValues = + (DWORD)(sizeof(pThread->m_floatValues) / sizeof(pThread->m_floatValues[0])); + + // Gather the low 8 bytes. + if (lowIsFloat) + { + if (lowReg >= numFloatValues) + ThrowHR(E_INVALIDARG); + memcpy(valueBuffer, &pThread->m_floatValues[lowReg], sizeof(double)); + } + else + { + UINT_PTR * pReg = GetAddressOfRegister((CorDebugRegister)lowReg); + if (pReg == NULL) + ThrowHR(E_INVALIDARG); + memcpy(valueBuffer, pReg, sizeof(UINT_PTR)); + } + + // Gather the high 8 bytes. + if (highIsFloat) + { + if (highReg >= numFloatValues) + ThrowHR(E_INVALIDARG); + memcpy(valueBuffer + sizeof(double), &pThread->m_floatValues[highReg], sizeof(double)); + } + else + { + UINT_PTR * pReg = GetAddressOfRegister((CorDebugRegister)highReg); + if (pReg == NULL) + ThrowHR(E_INVALIDARG); + memcpy(valueBuffer + sizeof(double), pReg, sizeof(UINT_PTR)); + } + + // Build the value from the local snapshot. The value lives in two registers, at + // least one of which is a floating-point register, so its contents cannot be + // reached through the integer register display. TwoRegisterValueHome captures the + // 16-byte snapshot so the value's object copy can be populated and so the home can + // be cloned for read-only field access (writing back is not supported). + EnregisteredValueHomeHolder pRemoteReg(new TwoRegisterValueHome(this, valueBuffer, sizeof(valueBuffer))); + EnregisteredValueHomeHolder * pRegHolder = pRemoteReg.GetAddr(); + + CordbValue::CreateValueByType(GetCurrentAppDomain(), + pType, + false, + EMPTY_BUFFER, + MemoryRange(NULL, 0), + pRegHolder, + ppValue); // throws + } + EX_CATCH_HRESULT(hr); + + return hr; +} + //--------------------------------------------------------------------------------------- // // Quick accessor to tell if we're the leaf frame. @@ -8301,6 +8404,34 @@ HRESULT CordbJITILFrame::GetNativeVariable(CordbType *type, type, ppValue); break; +#if defined(TARGET_64BIT) + // The value lives in two registers, at least one of which is a floating-point + // register (e.g. a 16-byte struct returned in XMM0+XMM1 on Unix x64, or a mixed + // int/fp multi-register return). vlrrReg1 holds the low 8 bytes and vlrrReg2 the + // high 8 bytes; fp registers are stored as 0-based fp register indices while int + // registers are ordinary register numbers (converted to CorDebugRegister here). + case ICorDebugInfo::VLT_REG_FP_REG_FP: + hr = m_nativeFrame->GetLocalTwoRegisterValue( + pNativeVarInfo->loc.vlRegReg.vlrrReg1, true, + pNativeVarInfo->loc.vlRegReg.vlrrReg2, true, + type, ppValue); + break; + + case ICorDebugInfo::VLT_REG_FP_REG: + hr = m_nativeFrame->GetLocalTwoRegisterValue( + pNativeVarInfo->loc.vlRegReg.vlrrReg1, true, + ConvertRegNumToCorDebugRegister(pNativeVarInfo->loc.vlRegReg.vlrrReg2), false, + type, ppValue); + break; + + case ICorDebugInfo::VLT_REG_REG_FP: + hr = m_nativeFrame->GetLocalTwoRegisterValue( + ConvertRegNumToCorDebugRegister(pNativeVarInfo->loc.vlRegReg.vlrrReg1), false, + pNativeVarInfo->loc.vlRegReg.vlrrReg2, true, + type, ppValue); + break; +#endif // TARGET_64BIT + case ICorDebugInfo::VLT_REG_STK: { CORDB_ADDRESS pRemoteValue = m_nativeFrame->GetLSStackAddress( diff --git a/src/coreclr/debug/di/rstype.cpp b/src/coreclr/debug/di/rstype.cpp index c2021026a30f46..14bd81f7e03372 100644 --- a/src/coreclr/debug/di/rstype.cpp +++ b/src/coreclr/debug/di/rstype.cpp @@ -1738,9 +1738,47 @@ HRESULT CordbType::ReturnedByValue() ULONG32 unboxedSize = 0; IfFailRet(GetUnboxedObjectSize(&unboxedSize)); +#ifdef TARGET_64BIT + // A value type is returned in registers (and is therefore representable by + // the managed-return-value debug info) only if it fits in at most two + // pointer-sized registers. Larger value types use the return buffer (stack) + // path, which the JIT does not currently emit MRV info for. + // + // Two-register returns are encoded via VLT_REG_REG (two integer registers), + // VLT_REG_FP_REG_FP (two FP registers), or the mixed VLT_REG_FP_REG / + // VLT_REG_REG_FP forms. Single-register returns use VLT_REG / VLT_REG_FP. + if (unboxedSize > 2 * sizeof(SIZE_T)) + return S_FALSE; + + // Whether the value occupies two registers (size in (8, 16] bytes on a + // 64-bit target). Single-register (<= pointer-sized) returns only support + // the original simple cases: a single integer/pointer-sized non-FP field. + // Floating-point and generic (unbound type-parameter) fields are only + // encodable for the two-register multi-reg forms (VLT_REG_FP_REG_FP and the + // mixed VLT_REG_FP_REG / VLT_REG_REG_FP). Enabling them for single-register + // value classes would + // reach unimplemented paths in the value-home code, so they remain + // unsupported there. + const bool twoRegister = (unboxedSize > sizeof(SIZE_T)); + + // 64-bit targets support multi-field value classes (e.g. ValueTuple) + // returned across two registers. + const bool allowMultiField = true; +#else + // 32-bit targets (x86 / arm32): the multi-register FP/mixed managed-return- + // value feature (dotnet/runtime#129344) is 64-bit only. Preserve the original + // behavior exactly: a value type is representable only if it fits in a single + // (pointer-sized) register and has a single non-floating-point field. The + // expanded two-register encodings above are inactive here, so broadening the + // size/field/FP rules would surface return values that the 32-bit read path + // does not support. if (unboxedSize > sizeof(SIZE_T)) return S_FALSE; + const bool twoRegister = false; + const bool allowMultiField = false; +#endif + mdToken mdClass = m_pClass->GetToken(); int fieldCount = 0; @@ -1764,8 +1802,14 @@ HRESULT CordbType::ReturnedByValue() // !static if ((attr & 0x10) == 0) { - if (fieldCount++) + // On 32-bit targets, only single-field value classes are + // representable (matching the original behavior). More than one + // non-static field is unsupported there. + if (!allowMultiField && fieldCount++ != 0) + { + unsupported = true; break; + } CorElementType et; SigParser parser(sigBlob, sigLen); @@ -1778,7 +1822,12 @@ HRESULT CordbType::ReturnedByValue() { case ELEMENT_TYPE_R4: case ELEMENT_TYPE_R8: - unsupported = true; + // Floating-point fields are returned in FP registers. + // A single FP register holding a value class is not + // encodable here (only primitive VLT_REG_FP is), so + // restrict to the two-register multi-reg forms. + if (!twoRegister) + unsupported = true; break; case ELEMENT_TYPE_CLASS: @@ -1787,6 +1836,22 @@ HRESULT CordbType::ReturnedByValue() // OK break; + case ELEMENT_TYPE_VAR: + case ELEMENT_TYPE_MVAR: + // The field's type is a generic type parameter (e.g. the + // Item1/Item2 fields of ValueTuple); the unbound field + // signature does not carry the instantiated type, so we cannot + // tell whether it resolves to an FP type. Only permit it for the + // two-register multi-reg forms, where both the all-FP and mixed + // int/FP paths are implemented (and the read path fails gracefully + // when the value is not actually register-returned). This is + // required to support mixed int/fp returns such as + // ValueTuple, while avoiding the + // unimplemented single-FP-register value-class path. + if (!twoRegister) + unsupported = true; + break; + default: if (!CorIsPrimitiveType(et)) unsupported = true; @@ -1813,7 +1878,7 @@ HRESULT CordbType::ReturnedByValue() if (unsupported) return S_FALSE; - return fieldCount <= 1 ? S_OK : S_FALSE; + return S_OK; } diff --git a/src/coreclr/debug/di/valuehome.cpp b/src/coreclr/debug/di/valuehome.cpp index 1317df02b810a4..8984c22a353cc4 100644 --- a/src/coreclr/debug/di/valuehome.cpp +++ b/src/coreclr/debug/di/valuehome.cpp @@ -298,14 +298,42 @@ void RegRegValueHome::GetEnregisteredValue(MemoryRange valueOutBuffer) UINT_PTR* lowWordAddr = m_pFrame->GetAddressOfRegister(m_reg2Info.m_kRegNumber); _ASSERTE(lowWordAddr != NULL); - _ASSERTE(sizeof(*highWordAddr) + sizeof(*lowWordAddr) == valueOutBuffer.Size()); + // The low half occupies the first register-sized chunk; the high half the second. + // The out buffer may be smaller than two registers (e.g. a 12-byte struct returned + // in two 8-byte registers), so clamp each copy to the bytes that actually remain. + const SIZE_T cbReg = sizeof(*lowWordAddr); + const SIZE_T cbTotal = valueOutBuffer.Size(); + _ASSERTE(cbTotal <= 2 * cbReg); - memcpy(valueOutBuffer.StartAddress(), lowWordAddr, sizeof(*lowWordAddr)); - memcpy((BYTE *)valueOutBuffer.StartAddress() + sizeof(*lowWordAddr), highWordAddr, sizeof(*highWordAddr)); + const SIZE_T cbLow = (cbTotal < cbReg) ? cbTotal : cbReg; + memcpy(valueOutBuffer.StartAddress(), lowWordAddr, cbLow); + + if (cbTotal > cbReg) + { + const SIZE_T cbHigh = cbTotal - cbReg; + memcpy((BYTE *)valueOutBuffer.StartAddress() + cbReg, highWordAddr, cbHigh); + } } // RegRegValueHome::GetEnregisteredValue +// ---------------------------------------------------------------------------- +// TwoRegisterValueHome member function implementations +// ---------------------------------------------------------------------------- + +// TwoRegisterValueHome::GetEnregisteredValue +// Gets the snapshot value and returns it to the caller (see EnregisteredValueHome::GetEnregisteredValue +// for full header comment) +void TwoRegisterValueHome::GetEnregisteredValue(MemoryRange valueOutBuffer) +{ + _ASSERTE(valueOutBuffer.Size() <= sizeof(m_value)); + + SIZE_T cbToCopy = (valueOutBuffer.Size() < sizeof(m_value)) ? valueOutBuffer.Size() : sizeof(m_value); + memcpy(valueOutBuffer.StartAddress(), m_value, cbToCopy); + +} // TwoRegisterValueHome::GetEnregisteredValue + + // ---------------------------------------------------------------------------- // RegMemValueHome member function implementations // ---------------------------------------------------------------------------- @@ -744,12 +772,15 @@ void RegisterValueHome::CreateInternalValue(CordbType * pType, * and p.x is in a register, while p.y is in memory, then clearly the * home of p (RAK_REGMEM) is not the same as the home of p.x (RAK_MEM). * - * Currently the JIT does not split compound objects in this way. It - * will only split an object that has exactly one field that is twice - * the size of the register + * Currently the JIT does not split compound objects in this way for + * ordinary locals. However, a multi-register return value can be a genuine + * compound with fields at non-zero offsets (e.g. struct { long x; long y; } + * returned in RAX:RDX). For reads the caller supplies the field's + * offset-adjusted local snapshot in localAddress, so any in-range offset is + * valid here. (Write-back to a specific sub-register of such a split value + * is a separate, pre-existing limitation and is not handled.) * */ - _ASSERTE(offset == 0); pRemoteReg.Assign(m_pRemoteRegAddr->Clone()); EnregisteredValueHomeHolder * pRegHolder = pRemoteReg.GetAddr(); diff --git a/src/coreclr/debug/ee/debugger.cpp b/src/coreclr/debug/ee/debugger.cpp index 54bde6c60c6845..244e3cbf99333e 100644 --- a/src/coreclr/debug/ee/debugger.cpp +++ b/src/coreclr/debug/ee/debugger.cpp @@ -4255,6 +4255,9 @@ bool GetSetFrameHelper::GetValueClassSizeOfVar(int varNum, ICorDebugInfo::VarLoc if ((cet != ELEMENT_TYPE_VALUETYPE) || (varType == ICorDebugInfo::VLT_REG) || (varType == ICorDebugInfo::VLT_REG_REG) || + (varType == ICorDebugInfo::VLT_REG_FP_REG_FP) || + (varType == ICorDebugInfo::VLT_REG_FP_REG) || + (varType == ICorDebugInfo::VLT_REG_REG_FP) || (varType == ICorDebugInfo::VLT_REG_STK) || (varType == ICorDebugInfo::VLT_STK_REG)) { diff --git a/src/coreclr/debug/ee/functioninfo.cpp b/src/coreclr/debug/ee/functioninfo.cpp index e912a8726628ac..6928b1ad127f09 100644 --- a/src/coreclr/debug/ee/functioninfo.cpp +++ b/src/coreclr/debug/ee/functioninfo.cpp @@ -78,6 +78,24 @@ static void _dumpVarNativeInfo(ICorDebugInfo::NativeVarInfo* vni) vni->loc.vlRegReg.vlrrReg2)); break; + case ICorDebugInfo::VLT_REG_FP_REG_FP: + LOG((LF_CORDB, LL_INFO1000000, "REG_FP_REG_FP fpreg1=%d fpreg2=%d\n", + vni->loc.vlRegReg.vlrrReg1, + vni->loc.vlRegReg.vlrrReg2)); + break; + + case ICorDebugInfo::VLT_REG_FP_REG: + LOG((LF_CORDB, LL_INFO1000000, "REG_FP_REG fpreg1=%d reg2=%d\n", + vni->loc.vlRegReg.vlrrReg1, + vni->loc.vlRegReg.vlrrReg2)); + break; + + case ICorDebugInfo::VLT_REG_REG_FP: + LOG((LF_CORDB, LL_INFO1000000, "REG_REG_FP reg1=%d fpreg2=%d\n", + vni->loc.vlRegReg.vlrrReg1, + vni->loc.vlRegReg.vlrrReg2)); + break; + case ICorDebugInfo::VLT_REG_STK: LOG((LF_CORDB, LL_INFO1000000, "REG_STK reg=%d basereg=%d off=0x%04x (%d)\n", vni->loc.vlRegStk.vlrsReg, diff --git a/src/coreclr/inc/cordebuginfo.h b/src/coreclr/inc/cordebuginfo.h index ba5a545e3ed909..cb0b65a32f1ac1 100644 --- a/src/coreclr/inc/cordebuginfo.h +++ b/src/coreclr/inc/cordebuginfo.h @@ -263,6 +263,10 @@ class ICorDebugInfo VLT_FPSTK, // variable lives on the floating-point stack VLT_FIXED_VA, // variable is a fixed argument in a varargs function (relative to VARARGS_HANDLE) + VLT_REG_FP_REG_FP, // variable lives in two fp registers (e.g. a 16-byte struct returned in XMM0+XMM1 on Unix x64) + VLT_REG_FP_REG, // low part lives in an fp register, high part in an int register (mixed multi-reg return) + VLT_REG_REG_FP, // low part lives in an int register, high part in an fp register (mixed multi-reg return) + VLT_COUNT, VLT_INVALID, }; @@ -290,6 +294,15 @@ class ICorDebugInfo // VLT_REG_REG -- TYP_LONG with both uint32_ts enregistred // eg. RBM_EAXEDX + // + // VLT_REG_FP_REG_FP / VLT_REG_FP_REG / VLT_REG_REG_FP also reuse the vlRegReg + // struct to describe a value that lives in two registers where at least one of + // them is a floating-point register (e.g. a 16-byte struct returned in XMM0+XMM1 + // on Unix x64, or a mixed int/fp multi-register return). vlrrReg1 holds the low + // 8 bytes of the value, vlrrReg2 the high 8 bytes. For a register that is an fp + // register the value stored is a 0-based fp register index (the consumer adds the + // platform-specific XMM0/F0 base), matching the VLT_REG_FP convention; for an int + // register the value is the ordinary register number. struct vlRegReg { diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index 3e5f54cb2423f4..c14347c6135031 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -1828,16 +1828,33 @@ void CodeGen::genEmitCallWithCurrentGC(EmitCallParams& params) regNumber reg1 = retDesc->GetABIReturnReg(0, call->GetUnmanagedCallConv()); regNumber reg2 = retDesc->GetABIReturnReg(1, call->GetUnmanagedCallConv()); - // VLT_REG_REG can only encode integer registers. On platforms where structs - // can be returned in a mix of int and float registers (SysV x64, RISC-V), - // skip recording if any register is not an int register. - // TODO: Supporting this case is tracked by https://github.com/dotnet/runtime/issues/129344 +#ifndef TARGET_64BIT + // The two-register FP/mixed encodings (VLT_REG_FP_REG_FP and the mixed + // VLT_REG_FP_REG / VLT_REG_REG_FP forms) and their DBI read path assume each + // register holds an 8-byte half of the value, so they are only implemented + // for 64-bit targets. On a 32-bit target a value returned in two + // floating-point registers (e.g. an ARM32 HFA such as a struct of two floats + // or doubles) cannot yet be represented; skip emitting MRV info for it rather + // than producing an encoding the debugger cannot decode. A pair of integer + // registers (e.g. x86 EAX:EDX) is still encoded below as VLT_REG_REG. + // + // Supporting this on ARM32 also depends on implementing managed FP-register + // value inspection there, which is itself unimplemented (the single-register + // VLT_REG_FP case is @ARMTODO/E_NOTIMPL in the DBI), and on mapping the JIT's + // single-precision register numbering to the debugger's D-register indexing. + // TODO: Implement 32-bit support for two-floating-point-register returns. if (!genIsValidIntReg(reg1) || !genIsValidIntReg(reg2)) { return; } +#endif // !TARGET_64BIT - info.returnValueLoc.storeVariableInRegisters(reg1, reg2); + // Either register may be an integer or a floating-point register. On + // platforms where structs can be returned in a mix of int and float + // registers (SysV x64, RISC-V), or in two float registers (e.g. a 16-byte + // Vector128 returned in XMM0+XMM1 on Unix x64), storeVariableInTwoRegisters + // selects the appropriate VLT_REG_REG / VLT_REG_FP_* encoding. + info.returnValueLoc.storeVariableInTwoRegisters(reg1, reg2); } else if (varTypeIsFloating(call)) { diff --git a/src/coreclr/jit/codegeninterface.h b/src/coreclr/jit/codegeninterface.h index fac4b90855a287..443137da86c0cf 100644 --- a/src/coreclr/jit/codegeninterface.h +++ b/src/coreclr/jit/codegeninterface.h @@ -519,6 +519,10 @@ class CodeGenInterface VLT_FPSTK, VLT_FIXED_VA, + VLT_REG_FP_REG_FP, + VLT_REG_FP_REG, + VLT_REG_REG_FP, + VLT_COUNT, VLT_INVALID }; @@ -631,6 +635,7 @@ class CodeGenInterface bool vlIsOnStack() const; void storeVariableInRegisters(regNumber reg, regNumber otherReg); + void storeVariableInTwoRegisters(regNumber reg1, regNumber reg2); void storeVariableOnStack(regNumber stackBaseReg, NATIVE_OFFSET variableStackOffset); siVarLoc(const LclVarDsc* varDsc, regNumber baseReg, int offset, bool isFramePointerUsed); diff --git a/src/coreclr/jit/ee_il_dll.cpp b/src/coreclr/jit/ee_il_dll.cpp index 8d0338f8d59fe3..090d61c3098d73 100644 --- a/src/coreclr/jit/ee_il_dll.cpp +++ b/src/coreclr/jit/ee_il_dll.cpp @@ -925,6 +925,21 @@ void Compiler::eeDispVar(ICorDebugInfo::NativeVarInfo* var) printf("%s-%s", getRegName(var->loc.vlRegReg.vlrrReg1), getRegName(var->loc.vlRegReg.vlrrReg2)); break; + case CodeGenInterface::VLT_REG_FP_REG_FP: + printf("%s-%s", getRegName((regNumber)(var->loc.vlRegReg.vlrrReg1 + REG_FP_FIRST)), + getRegName((regNumber)(var->loc.vlRegReg.vlrrReg2 + REG_FP_FIRST))); + break; + + case CodeGenInterface::VLT_REG_FP_REG: + printf("%s-%s", getRegName((regNumber)(var->loc.vlRegReg.vlrrReg1 + REG_FP_FIRST)), + getRegName(var->loc.vlRegReg.vlrrReg2)); + break; + + case CodeGenInterface::VLT_REG_REG_FP: + printf("%s-%s", getRegName(var->loc.vlRegReg.vlrrReg1), + getRegName((regNumber)(var->loc.vlRegReg.vlrrReg2 + REG_FP_FIRST))); + break; + #ifndef TARGET_AMD64 case CodeGenInterface::VLT_REG_STK: if ((int)var->loc.vlRegStk.vlrsStk.vlrssBaseReg != (int)ICorDebugInfo::REGNUM_AMBIENT_SP) diff --git a/src/coreclr/jit/scopeinfo.cpp b/src/coreclr/jit/scopeinfo.cpp index a336b8d3872a02..a354df40b09384 100644 --- a/src/coreclr/jit/scopeinfo.cpp +++ b/src/coreclr/jit/scopeinfo.cpp @@ -79,6 +79,14 @@ bool CodeGenInterface::siVarLoc::vlIsInReg(regNumber reg) const case CodeGenInterface::VLT_FPSTK: return false; + case CodeGenInterface::VLT_REG_FP_REG_FP: + case CodeGenInterface::VLT_REG_FP_REG: + case CodeGenInterface::VLT_REG_REG_FP: + // These describe values that live (at least partly) in floating-point + // registers, recorded as 0-based fp register indices rather than raw + // register numbers, so they cannot be compared against "reg" here. + return false; + default: assert(!"Bad locType"); return false; @@ -124,6 +132,9 @@ bool CodeGenInterface::siVarLoc::vlIsOnStack(regNumber reg, signed offset) const case CodeGenInterface::VLT_REG: case CodeGenInterface::VLT_REG_FP: case CodeGenInterface::VLT_REG_REG: + case CodeGenInterface::VLT_REG_FP_REG_FP: + case CodeGenInterface::VLT_REG_FP_REG: + case CodeGenInterface::VLT_REG_REG_FP: case CodeGenInterface::VLT_FPSTK: return false; @@ -176,6 +187,56 @@ void CodeGenInterface::siVarLoc::storeVariableInRegisters(regNumber reg, regNumb } } +//------------------------------------------------------------------------ +// storeVariableInTwoRegisters: Convert the siVarLoc instance into a two-register +// location, where either register may be an integer or a floating-point register. +// +// Arguments: +// reg1 - the register holding the low 8 bytes of the value. +// reg2 - the register holding the high 8 bytes of the value. +// +// Notes: +// This generalizes storeVariableInRegisters to support values that live in a +// pair of registers where at least one is a floating-point register (e.g. a +// 16-byte struct returned in XMM0+XMM1 on Unix x64, or a mixed int/fp +// multi-register return). Floating-point registers are recorded as a 0-based +// fp register index (reg - REG_FP_FIRST), matching the VLT_REG_FP convention; +// integer registers are recorded as their ordinary register number. +// +void CodeGenInterface::siVarLoc::storeVariableInTwoRegisters(regNumber reg1, regNumber reg2) +{ + assert(reg1 != REG_NA); + assert(reg2 != REG_NA); + + const bool fpReg1 = genIsValidFloatReg(reg1); + const bool fpReg2 = genIsValidFloatReg(reg2); + + if (!fpReg1 && !fpReg2) + { + vlType = VLT_REG_REG; + vlRegReg.vlrrReg1 = reg1; + vlRegReg.vlrrReg2 = reg2; + } + else if (fpReg1 && fpReg2) + { + vlType = VLT_REG_FP_REG_FP; + vlRegReg.vlrrReg1 = (regNumber)(reg1 - REG_FP_FIRST); + vlRegReg.vlrrReg2 = (regNumber)(reg2 - REG_FP_FIRST); + } + else if (fpReg1) + { + vlType = VLT_REG_FP_REG; + vlRegReg.vlrrReg1 = (regNumber)(reg1 - REG_FP_FIRST); + vlRegReg.vlrrReg2 = reg2; + } + else + { + vlType = VLT_REG_REG_FP; + vlRegReg.vlrrReg1 = reg1; + vlRegReg.vlrrReg2 = (regNumber)(reg2 - REG_FP_FIRST); + } +} + //------------------------------------------------------------------------ // storeVariableOnStack: Convert the siVarLoc instance in a stack location // with the given base register and stack offset. @@ -236,6 +297,9 @@ bool CodeGenInterface::siVarLoc::Equals(const siVarLoc* lhs, const siVarLoc* rhs return (lhs->vlReg.vlrReg == rhs->vlReg.vlrReg); case VLT_REG_REG: + case VLT_REG_FP_REG_FP: + case VLT_REG_FP_REG: + case VLT_REG_REG_FP: return (lhs->vlRegReg.vlrrReg1 == rhs->vlRegReg.vlrrReg1) && (lhs->vlRegReg.vlrrReg2 == rhs->vlRegReg.vlrrReg2); @@ -533,6 +597,14 @@ void CodeGenInterface::dumpSiVarLoc(const siVarLoc* varLoc) const } break; + case VLT_REG_FP_REG_FP: + case VLT_REG_FP_REG: + case VLT_REG_REG_FP: + // At least one of the two registers is an fp register, recorded as a + // 0-based fp register index, so just print the raw register fields. + printf("reg1=%d reg2=%d (fp multi-reg)", varLoc->vlRegReg.vlrrReg1, varLoc->vlRegReg.vlrrReg2); + break; + case VLT_STK: case VLT_STK_BYREF: if ((int)varLoc->vlStk.vlsBaseReg != (int)ICorDebugInfo::REGNUM_AMBIENT_SP) @@ -1436,6 +1508,9 @@ void CodeGen::checkICodeDebugInfo() assert((unsigned)ICorDebugInfo::VLT_STK2 == CodeGenInterface::VLT_STK2); assert((unsigned)ICorDebugInfo::VLT_FPSTK == CodeGenInterface::VLT_FPSTK); assert((unsigned)ICorDebugInfo::VLT_FIXED_VA == CodeGenInterface::VLT_FIXED_VA); + assert((unsigned)ICorDebugInfo::VLT_REG_FP_REG_FP == CodeGenInterface::VLT_REG_FP_REG_FP); + assert((unsigned)ICorDebugInfo::VLT_REG_FP_REG == CodeGenInterface::VLT_REG_FP_REG); + assert((unsigned)ICorDebugInfo::VLT_REG_REG_FP == CodeGenInterface::VLT_REG_REG_FP); assert((unsigned)ICorDebugInfo::VLT_COUNT == CodeGenInterface::VLT_COUNT); assert((unsigned)ICorDebugInfo::VLT_INVALID == CodeGenInterface::VLT_INVALID); @@ -1736,24 +1811,26 @@ void CodeGen::psiBegProlog() if (reg1 != REG_NA) { - if (genIsValidFloatReg(reg1)) + if (reg2 == REG_NA) { - // FP parameter in XMM/V register — encode as VLT_REG_FP with - // 0-based FP register index. - varLocation.vlType = VLT_REG_FP; - varLocation.vlReg.vlrReg = (regNumber)(reg1 - REG_FP_FIRST); + if (genIsValidFloatReg(reg1)) + { + // FP parameter in a single XMM/V register — encode as VLT_REG_FP + // with a 0-based FP register index. + varLocation.vlType = VLT_REG_FP; + varLocation.vlReg.vlrReg = (regNumber)(reg1 - REG_FP_FIRST); + } + else + { + varLocation.storeVariableInRegisters(reg1, REG_NA); + } } else { - // Integer register parameter. On SysV x64, the second segment - // may be in an XMM register for mixed struct passing — drop it - // since VLT_REG_REG cannot encode FP registers. - // TODO: Supporting this case is tracked by https://github.com/dotnet/runtime/issues/129344 - if (reg2 != REG_NA && !genIsValidIntReg(reg2)) - { - reg2 = REG_NA; - } - varLocation.storeVariableInRegisters(reg1, reg2); + // Two-register parameter. Either register may be an integer or a + // floating-point register (e.g. mixed int/fp struct passing on + // SysV x64); storeVariableInTwoRegisters selects the right encoding. + varLocation.storeVariableInTwoRegisters(reg1, reg2); } } else diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.VarInfo.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.VarInfo.cs index e307bcb2844616..3587c8e93ab239 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.VarInfo.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.VarInfo.cs @@ -26,6 +26,10 @@ public enum VarLocType : uint VLT_FPSTK, // variable lives on the floating-point stack VLT_FIXED_VA, // variable is a fixed argument in a varargs function (relative to VARARGS_HANDLE) + VLT_REG_FP_REG_FP, // variable lives in two fp registers (e.g. a 16-byte struct returned in XMM0+XMM1 on Unix x64) + VLT_REG_FP_REG, // low part lives in an fp register, high part in an int register (mixed multi-reg return) + VLT_REG_REG_FP, // low part lives in an int register, high part in an fp register (mixed multi-reg return) + VLT_COUNT, VLT_INVALID }; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewSymbolsBuilder.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewSymbolsBuilder.cs index 2ca792fd6645a1..99f4d8242b9617 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewSymbolsBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewSymbolsBuilder.cs @@ -199,6 +199,9 @@ public void EmitSubprogramInfo( case VarLocType.VLT_REG_BYREF: case VarLocType.VLT_STK_BYREF: case VarLocType.VLT_REG_REG: + case VarLocType.VLT_REG_FP_REG_FP: + case VarLocType.VLT_REG_FP_REG: + case VarLocType.VLT_REG_REG_FP: case VarLocType.VLT_REG_STK: case VarLocType.VLT_STK_REG: case VarLocType.VLT_STK2: 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 0f58b1e2c97780..2770af8660f7eb 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 @@ -286,6 +286,14 @@ public static byte[] CreateVarBlobForMethod(NativeVarInfo[] varInfos, TargetDeta writer.WriteUInt((uint)nativeVarInfo.varLoc.B); writer.WriteUInt((uint)nativeVarInfo.varLoc.C); break; + case VarLocType.VLT_REG_FP_REG_FP: + case VarLocType.VLT_REG_FP_REG: + case VarLocType.VLT_REG_REG_FP: + // Two-register location where at least one register is an fp + // register. Encoded with two register fields like VLT_REG_REG. + writer.WriteUInt((uint)nativeVarInfo.varLoc.B); + writer.WriteUInt((uint)nativeVarInfo.varLoc.C); + break; case VarLocType.VLT_REG_STK: writer.WriteUInt((uint)nativeVarInfo.varLoc.B); writer.WriteUInt((uint)nativeVarInfo.varLoc.C); diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs index 58cb620fe98558..13ce05b341c0e8 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs @@ -310,6 +310,12 @@ private void ParseNativeVarInfo(NativeReader imageReader, int offset) varLoc.Data1 = (int)reader.ReadUInt(); varLoc.Data2 = (int)reader.ReadUInt(); break; + case VarLocType.VLT_REG_FP_REG_FP: + case VarLocType.VLT_REG_FP_REG: + case VarLocType.VLT_REG_REG_FP: + varLoc.Data1 = (int)reader.ReadUInt(); + varLoc.Data2 = (int)reader.ReadUInt(); + break; case VarLocType.VLT_REG_STK: varLoc.Data1 = (int)reader.ReadUInt(); varLoc.Data2 = (int)reader.ReadUInt(); diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfoTypes.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfoTypes.cs index aa8e1e26d71e1e..5dbe37d4c8b8b6 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfoTypes.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfoTypes.cs @@ -107,6 +107,10 @@ public enum VarLocType VLT_FPSTK, // variable lives on the floating-point stack VLT_FIXED_VA, // variable is a fixed argument in a varargs function (relative to VARARGS_HANDLE) + VLT_REG_FP_REG_FP, // variable lives in two fp registers (e.g. a 16-byte struct returned in XMM0+XMM1 on Unix x64) + VLT_REG_FP_REG, // low part lives in an fp register, high part in an int register (mixed multi-reg return) + VLT_REG_REG_FP, // low part lives in an int register, high part in an fp register (mixed multi-reg return) + VLT_COUNT, VLT_INVALID, } diff --git a/src/coreclr/tools/r2rdump/Extensions.cs b/src/coreclr/tools/r2rdump/Extensions.cs index f93e4cf36d2afb..b28e76bfb31a1c 100644 --- a/src/coreclr/tools/r2rdump/Extensions.cs +++ b/src/coreclr/tools/r2rdump/Extensions.cs @@ -97,6 +97,12 @@ public static void WriteTo(this DebugInfo theThis, TextWriter writer, DumpModel writer.WriteLine($" Register 1: {DebugInfo.GetPlatformSpecificRegister(theThis.Machine, varLoc.VariableLocation.Data1)}"); writer.WriteLine($" Register 2: {DebugInfo.GetPlatformSpecificRegister(theThis.Machine, varLoc.VariableLocation.Data2)}"); break; + case VarLocType.VLT_REG_FP_REG_FP: + case VarLocType.VLT_REG_FP_REG: + case VarLocType.VLT_REG_REG_FP: + writer.WriteLine($" Register 1: {varLoc.VariableLocation.Data1}"); + writer.WriteLine($" Register 2: {varLoc.VariableLocation.Data2}"); + break; case VarLocType.VLT_REG_STK: writer.WriteLine($" Register: {DebugInfo.GetPlatformSpecificRegister(theThis.Machine, varLoc.VariableLocation.Data1)}"); writer.WriteLine($" Base Register: {DebugInfo.GetPlatformSpecificRegister(theThis.Machine, varLoc.VariableLocation.Data2)}"); diff --git a/src/coreclr/vm/debuginfostore.cpp b/src/coreclr/vm/debuginfostore.cpp index 6e1f114c28288e..b08fed0c96a7c4 100644 --- a/src/coreclr/vm/debuginfostore.cpp +++ b/src/coreclr/vm/debuginfostore.cpp @@ -393,6 +393,16 @@ static void DoNativeVarInfo( trans.DoEncodedRegIdx(pVar->loc.vlRegReg.vlrrReg2); break; + // Multi-register returns where at least one register is a floating-point register. + // These reuse the vlRegReg layout, so the two register indices are encoded + // just like VLT_REG_REG. + case ICorDebugInfo::VLT_REG_FP_REG_FP: + case ICorDebugInfo::VLT_REG_FP_REG: // fall through + case ICorDebugInfo::VLT_REG_REG_FP: // fall through + trans.DoEncodedRegIdx(pVar->loc.vlRegReg.vlrrReg1); + trans.DoEncodedRegIdx(pVar->loc.vlRegReg.vlrrReg2); + break; + case ICorDebugInfo::VLT_REG_STK: trans.DoEncodedRegIdx(pVar->loc.vlRegStk.vlrsReg); trans.DoEncodedRegIdx(pVar->loc.vlRegStk.vlrsStk.vlrssBaseReg); diff --git a/src/coreclr/vm/util.cpp b/src/coreclr/vm/util.cpp index 2c1b6ec4b3b31d..7fc261b2cb740d 100644 --- a/src/coreclr/vm/util.cpp +++ b/src/coreclr/vm/util.cpp @@ -176,6 +176,9 @@ bool operator ==(const ICorDebugInfo::VarLoc &varLoc1, varLoc1.vlStk.vlsOffset == varLoc2.vlStk.vlsOffset; case ICorDebugInfo::VLT_REG_REG: + case ICorDebugInfo::VLT_REG_FP_REG_FP: + case ICorDebugInfo::VLT_REG_FP_REG: + case ICorDebugInfo::VLT_REG_REG_FP: return varLoc1.vlRegReg.vlrrReg1 == varLoc2.vlRegReg.vlrrReg1 && varLoc1.vlRegReg.vlrrReg2 == varLoc2.vlRegReg.vlrrReg2; From 8c6e8ed2d2a48c4ab18e87f6144a3b277b073eb2 Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Fri, 26 Jun 2026 17:23:48 -0400 Subject: [PATCH 02/16] Implement x86 x87 FP-stack return/local value support in DBI CordbJITILFrame::GetNativeVariable returned CORDBG_E_IL_VAR_NOT_AVAILABLE for the VLT_FPSTK location kind, leaving the implementation commented out since the initial CoreCLR commit. On x86, floating-point values (including return values, surfaced via GetReturnValueForILOffset) live on the x87 FP stack and are reported as VLT_FPSTK, so inspecting a float/double return or local failed with an unavailable-variable error. Enable the existing GetLocalFloatingPointValue path for TARGET_X86, which already handles loading the x87 stack state and R4/R8 conversion. Behavior on non-x86 targets is unchanged (ARM remains E_NOTIMPL; others retain the CORDBG_E_IL_VAR_NOT_AVAILABLE fallback, where VLT_FPSTK is never produced). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/debug/di/rsthread.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/coreclr/debug/di/rsthread.cpp b/src/coreclr/debug/di/rsthread.cpp index 52257a7675758e..eedcd4e88bc538 100644 --- a/src/coreclr/debug/di/rsthread.cpp +++ b/src/coreclr/debug/di/rsthread.cpp @@ -8467,15 +8467,16 @@ HRESULT CordbJITILFrame::GetNativeVariable(CordbType *type, break; case ICorDebugInfo::VLT_FPSTK: -#if defined(TARGET_ARM) // @ARMTODO - hr = E_NOTIMPL; -#else - /* - @TODO [Microsoft] We have to make this work!!!!!!!!!!!!! +#if defined(TARGET_X86) + // On x86 floating-point values (including return values) live on the x87 + // FP stack. vlfReg is the depth from the top of the stack, so add the base + // register to form the CorDebugRegister index expected by the helper. hr = m_nativeFrame->GetLocalFloatingPointValue( pNativeVarInfo->loc.vlFPstk.vlfReg + REGISTER_X86_FPSTACK_0, type, ppValue); - */ +#elif defined(TARGET_ARM) // @ARMTODO + hr = E_NOTIMPL; +#else hr = CORDBG_E_IL_VAR_NOT_AVAILABLE; #endif break; From 78af259fd29eff7076452685cba5c80cdb2fed5f Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Thu, 2 Jul 2026 10:11:48 -0400 Subject: [PATCH 03/16] Refactor: replace VLT_REG_FP_REG* with unified RegNum encoding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the three new VarLocType values (VLT_REG_FP_REG_FP, VLT_REG_FP_REG, VLT_REG_REG_FP) with a unified approach that extends the RegNum enum to include FP registers on AMD64 (XMM0-XMM15). This allows VLT_REG_REG to encode any combination of int and FP registers without new VarLocType variants, addressing scalability concerns for future multi-register scenarios (HFAs, multi-field enregistration). Changes: - Add REGNUM_XMM0-REGNUM_XMM15 to RegNum enum (AMD64 only) - Extend g_JITToCorDbgReg to map FP RegNum to CorDebugRegister - Add mapRegNumToDebugRegNum helper in JIT scopeinfo - Simplify storeVariableInRegisters to handle int+FP uniformly - Update DBI VLT_REG_REG path to detect FP registers and use GetLocalTwoRegisterValue for mixed/FP multi-reg values - Remove VLT_REG_FP_REG_FP/VLT_REG_FP_REG/VLT_REG_REG_FP from all consumers (DBI, EE, R2R tools, debuginfostore) x86 is unchanged (no FP registers in RegNum; uses VLT_FPSTK). Tested: x86 Windows, x64 Windows, x64 Linux, ARM64 Windows — all MRV diagnostic tests passing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/debug/di/rsthread.cpp | 55 +++--- src/coreclr/debug/ee/debugger.cpp | 3 - src/coreclr/debug/ee/functioninfo.cpp | 18 -- src/coreclr/debug/inc/amd64/primitives.h | 18 +- src/coreclr/debug/inc/dbgipcevents.h | 2 +- src/coreclr/inc/cordebuginfo.h | 34 ++-- src/coreclr/jit/codegencommon.cpp | 43 ++--- src/coreclr/jit/codegeninterface.h | 7 +- src/coreclr/jit/ee_il_dll.cpp | 34 ++-- src/coreclr/jit/scopeinfo.cpp | 180 +++++++----------- .../JitInterface/CorInfoTypes.VarInfo.cs | 4 - .../CodeView/CodeViewSymbolsBuilder.cs | 3 - .../Dwarf/DwarfExpressionBuilder.cs | 19 +- .../ReadyToRun/DebugInfoTableNode.cs | 8 - .../Amd64/Registers.cs | 16 ++ .../DebugInfo.cs | 6 - .../DebugInfoTypes.cs | 4 - src/coreclr/tools/r2rdump/Extensions.cs | 6 - src/coreclr/vm/debuginfostore.cpp | 10 - src/coreclr/vm/util.cpp | 3 - 20 files changed, 213 insertions(+), 260 deletions(-) diff --git a/src/coreclr/debug/di/rsthread.cpp b/src/coreclr/debug/di/rsthread.cpp index eedcd4e88bc538..ed44612dce97a3 100644 --- a/src/coreclr/debug/di/rsthread.cpp +++ b/src/coreclr/debug/di/rsthread.cpp @@ -8358,7 +8358,7 @@ HRESULT CordbJITILFrame::GetNativeVariable(CordbType *type, #if defined(TARGET_ARM) // @ARMTODO hr = E_NOTIMPL; #elif defined(TARGET_AMD64) - hr = m_nativeFrame->GetLocalFloatingPointValue(pNativeVarInfo->loc.vlReg.vlrReg + REGISTER_AMD64_XMM0, + hr = m_nativeFrame->GetLocalFloatingPointValue(ConvertRegNumToCorDebugRegister(pNativeVarInfo->loc.vlReg.vlrReg), type, ppValue); #elif defined(TARGET_ARM64) hr = m_nativeFrame->GetLocalFloatingPointValue(pNativeVarInfo->loc.vlReg.vlrReg + REGISTER_ARM64_V0, @@ -8398,40 +8398,37 @@ HRESULT CordbJITILFrame::GetNativeVariable(CordbType *type, break; case ICorDebugInfo::VLT_REG_REG: +#if defined(TARGET_AMD64) + { + const ICorDebugInfo::RegNum lowReg = pNativeVarInfo->loc.vlRegReg.vlrrReg1; + const ICorDebugInfo::RegNum highReg = pNativeVarInfo->loc.vlRegReg.vlrrReg2; + const bool lowIsFloat = lowReg >= ICorDebugInfo::REGNUM_FP_FIRST; + const bool highIsFloat = highReg >= ICorDebugInfo::REGNUM_FP_FIRST; + + if (lowIsFloat || highIsFloat) + { + // AMD64 extends RegNum with XMM registers, so VLT_REG_REG can + // represent mixed int/fp pairs. Other targets still require + // dedicated encodings for FP-containing multi-register values. + hr = m_nativeFrame->GetLocalTwoRegisterValue( + lowIsFloat ? lowReg - ICorDebugInfo::REGNUM_FP_FIRST + : ConvertRegNumToCorDebugRegister(lowReg), + lowIsFloat, + highIsFloat ? highReg - ICorDebugInfo::REGNUM_FP_FIRST + : ConvertRegNumToCorDebugRegister(highReg), + highIsFloat, + type, + ppValue); + break; + } + } +#endif hr = m_nativeFrame->GetLocalDoubleRegisterValue( ConvertRegNumToCorDebugRegister(pNativeVarInfo->loc.vlRegReg.vlrrReg2), ConvertRegNumToCorDebugRegister(pNativeVarInfo->loc.vlRegReg.vlrrReg1), type, ppValue); break; -#if defined(TARGET_64BIT) - // The value lives in two registers, at least one of which is a floating-point - // register (e.g. a 16-byte struct returned in XMM0+XMM1 on Unix x64, or a mixed - // int/fp multi-register return). vlrrReg1 holds the low 8 bytes and vlrrReg2 the - // high 8 bytes; fp registers are stored as 0-based fp register indices while int - // registers are ordinary register numbers (converted to CorDebugRegister here). - case ICorDebugInfo::VLT_REG_FP_REG_FP: - hr = m_nativeFrame->GetLocalTwoRegisterValue( - pNativeVarInfo->loc.vlRegReg.vlrrReg1, true, - pNativeVarInfo->loc.vlRegReg.vlrrReg2, true, - type, ppValue); - break; - - case ICorDebugInfo::VLT_REG_FP_REG: - hr = m_nativeFrame->GetLocalTwoRegisterValue( - pNativeVarInfo->loc.vlRegReg.vlrrReg1, true, - ConvertRegNumToCorDebugRegister(pNativeVarInfo->loc.vlRegReg.vlrrReg2), false, - type, ppValue); - break; - - case ICorDebugInfo::VLT_REG_REG_FP: - hr = m_nativeFrame->GetLocalTwoRegisterValue( - ConvertRegNumToCorDebugRegister(pNativeVarInfo->loc.vlRegReg.vlrrReg1), false, - pNativeVarInfo->loc.vlRegReg.vlrrReg2, true, - type, ppValue); - break; -#endif // TARGET_64BIT - case ICorDebugInfo::VLT_REG_STK: { CORDB_ADDRESS pRemoteValue = m_nativeFrame->GetLSStackAddress( diff --git a/src/coreclr/debug/ee/debugger.cpp b/src/coreclr/debug/ee/debugger.cpp index 244e3cbf99333e..54bde6c60c6845 100644 --- a/src/coreclr/debug/ee/debugger.cpp +++ b/src/coreclr/debug/ee/debugger.cpp @@ -4255,9 +4255,6 @@ bool GetSetFrameHelper::GetValueClassSizeOfVar(int varNum, ICorDebugInfo::VarLoc if ((cet != ELEMENT_TYPE_VALUETYPE) || (varType == ICorDebugInfo::VLT_REG) || (varType == ICorDebugInfo::VLT_REG_REG) || - (varType == ICorDebugInfo::VLT_REG_FP_REG_FP) || - (varType == ICorDebugInfo::VLT_REG_FP_REG) || - (varType == ICorDebugInfo::VLT_REG_REG_FP) || (varType == ICorDebugInfo::VLT_REG_STK) || (varType == ICorDebugInfo::VLT_STK_REG)) { diff --git a/src/coreclr/debug/ee/functioninfo.cpp b/src/coreclr/debug/ee/functioninfo.cpp index 6928b1ad127f09..e912a8726628ac 100644 --- a/src/coreclr/debug/ee/functioninfo.cpp +++ b/src/coreclr/debug/ee/functioninfo.cpp @@ -78,24 +78,6 @@ static void _dumpVarNativeInfo(ICorDebugInfo::NativeVarInfo* vni) vni->loc.vlRegReg.vlrrReg2)); break; - case ICorDebugInfo::VLT_REG_FP_REG_FP: - LOG((LF_CORDB, LL_INFO1000000, "REG_FP_REG_FP fpreg1=%d fpreg2=%d\n", - vni->loc.vlRegReg.vlrrReg1, - vni->loc.vlRegReg.vlrrReg2)); - break; - - case ICorDebugInfo::VLT_REG_FP_REG: - LOG((LF_CORDB, LL_INFO1000000, "REG_FP_REG fpreg1=%d reg2=%d\n", - vni->loc.vlRegReg.vlrrReg1, - vni->loc.vlRegReg.vlrrReg2)); - break; - - case ICorDebugInfo::VLT_REG_REG_FP: - LOG((LF_CORDB, LL_INFO1000000, "REG_REG_FP reg1=%d fpreg2=%d\n", - vni->loc.vlRegReg.vlrrReg1, - vni->loc.vlRegReg.vlrrReg2)); - break; - case ICorDebugInfo::VLT_REG_STK: LOG((LF_CORDB, LL_INFO1000000, "REG_STK reg=%d basereg=%d off=0x%04x (%d)\n", vni->loc.vlRegStk.vlrsReg, diff --git a/src/coreclr/debug/inc/amd64/primitives.h b/src/coreclr/debug/inc/amd64/primitives.h index 7980e11dcb9695..41ce1bfe060f70 100644 --- a/src/coreclr/debug/inc/amd64/primitives.h +++ b/src/coreclr/debug/inc/amd64/primitives.h @@ -70,7 +70,23 @@ constexpr CorDebugRegister g_JITToCorDbgReg[] = REGISTER_AMD64_R12, REGISTER_AMD64_R13, REGISTER_AMD64_R14, - REGISTER_AMD64_R15 + REGISTER_AMD64_R15, + REGISTER_AMD64_XMM0, + REGISTER_AMD64_XMM1, + REGISTER_AMD64_XMM2, + REGISTER_AMD64_XMM3, + REGISTER_AMD64_XMM4, + REGISTER_AMD64_XMM5, + REGISTER_AMD64_XMM6, + REGISTER_AMD64_XMM7, + REGISTER_AMD64_XMM8, + REGISTER_AMD64_XMM9, + REGISTER_AMD64_XMM10, + REGISTER_AMD64_XMM11, + REGISTER_AMD64_XMM12, + REGISTER_AMD64_XMM13, + REGISTER_AMD64_XMM14, + REGISTER_AMD64_XMM15 }; // diff --git a/src/coreclr/debug/inc/dbgipcevents.h b/src/coreclr/debug/inc/dbgipcevents.h index 5b835b115f3224..f66c68d17073c6 100644 --- a/src/coreclr/debug/inc/dbgipcevents.h +++ b/src/coreclr/debug/inc/dbgipcevents.h @@ -1435,7 +1435,7 @@ static_assert(DBG_TARGET_REGNUM_AMBIENT_SP == ICorDebugInfo::REGNUM_AMBIENT_SP); #endif // TARGET_X86 #elif defined(TARGET_AMD64) #define DBG_TARGET_REGNUM_SP 4 -#define DBG_TARGET_REGNUM_AMBIENT_SP 17 +#define DBG_TARGET_REGNUM_AMBIENT_SP 33 #ifdef TARGET_AMD64 static_assert(DBG_TARGET_REGNUM_SP == ICorDebugInfo::REGNUM_SP); static_assert(DBG_TARGET_REGNUM_AMBIENT_SP == ICorDebugInfo::REGNUM_AMBIENT_SP); diff --git a/src/coreclr/inc/cordebuginfo.h b/src/coreclr/inc/cordebuginfo.h index cb0b65a32f1ac1..56cdafc49148f3 100644 --- a/src/coreclr/inc/cordebuginfo.h +++ b/src/coreclr/inc/cordebuginfo.h @@ -147,6 +147,23 @@ class ICorDebugInfo REGNUM_R13, REGNUM_R14, REGNUM_R15, + REGNUM_FP_FIRST, + REGNUM_XMM0 = REGNUM_FP_FIRST, + REGNUM_XMM1, + REGNUM_XMM2, + REGNUM_XMM3, + REGNUM_XMM4, + REGNUM_XMM5, + REGNUM_XMM6, + REGNUM_XMM7, + REGNUM_XMM8, + REGNUM_XMM9, + REGNUM_XMM10, + REGNUM_XMM11, + REGNUM_XMM12, + REGNUM_XMM13, + REGNUM_XMM14, + REGNUM_XMM15, #elif TARGET_LOONGARCH64 REGNUM_R0, REGNUM_RA, @@ -263,10 +280,6 @@ class ICorDebugInfo VLT_FPSTK, // variable lives on the floating-point stack VLT_FIXED_VA, // variable is a fixed argument in a varargs function (relative to VARARGS_HANDLE) - VLT_REG_FP_REG_FP, // variable lives in two fp registers (e.g. a 16-byte struct returned in XMM0+XMM1 on Unix x64) - VLT_REG_FP_REG, // low part lives in an fp register, high part in an int register (mixed multi-reg return) - VLT_REG_REG_FP, // low part lives in an int register, high part in an fp register (mixed multi-reg return) - VLT_COUNT, VLT_INVALID, }; @@ -292,17 +305,12 @@ class ICorDebugInfo signed vlsOffset; }; - // VLT_REG_REG -- TYP_LONG with both uint32_ts enregistred + // VLT_REG_REG -- value lives in two registers. // eg. RBM_EAXEDX // - // VLT_REG_FP_REG_FP / VLT_REG_FP_REG / VLT_REG_REG_FP also reuse the vlRegReg - // struct to describe a value that lives in two registers where at least one of - // them is a floating-point register (e.g. a 16-byte struct returned in XMM0+XMM1 - // on Unix x64, or a mixed int/fp multi-register return). vlrrReg1 holds the low - // 8 bytes of the value, vlrrReg2 the high 8 bytes. For a register that is an fp - // register the value stored is a 0-based fp register index (the consumer adds the - // platform-specific XMM0/F0 base), matching the VLT_REG_FP convention; for an int - // register the value is the ordinary register number. + // vlrrReg1 holds the low part of the value, vlrrReg2 the high part. The + // registers may be integer RegNum values or, on platforms that include them + // in RegNum, floating-point RegNum values. struct vlRegReg { diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index c14347c6135031..1ebee7a7408fc3 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -1828,15 +1828,15 @@ void CodeGen::genEmitCallWithCurrentGC(EmitCallParams& params) regNumber reg1 = retDesc->GetABIReturnReg(0, call->GetUnmanagedCallConv()); regNumber reg2 = retDesc->GetABIReturnReg(1, call->GetUnmanagedCallConv()); -#ifndef TARGET_64BIT - // The two-register FP/mixed encodings (VLT_REG_FP_REG_FP and the mixed - // VLT_REG_FP_REG / VLT_REG_REG_FP forms) and their DBI read path assume each - // register holds an 8-byte half of the value, so they are only implemented - // for 64-bit targets. On a 32-bit target a value returned in two - // floating-point registers (e.g. an ARM32 HFA such as a struct of two floats - // or doubles) cannot yet be represented; skip emitting MRV info for it rather - // than producing an encoding the debugger cannot decode. A pair of integer - // registers (e.g. x86 EAX:EDX) is still encoded below as VLT_REG_REG. +#if !defined(TARGET_64BIT) + // Multi-register debug-info encodings that involve floating-point + // registers assume each register holds an 8-byte half of the value, so + // they are only implemented for 64-bit targets. On a 32-bit target a + // value returned in two floating-point registers (e.g. an ARM32 HFA such + // as a struct of two floats or doubles) cannot yet be represented; skip + // emitting MRV info for it rather than producing an encoding the debugger + // cannot decode. A pair of integer registers (e.g. x86 EAX:EDX) is still + // encoded below as VLT_REG_REG. // // Supporting this on ARM32 also depends on implementing managed FP-register // value inspection there, which is itself unimplemented (the single-register @@ -1847,14 +1847,18 @@ void CodeGen::genEmitCallWithCurrentGC(EmitCallParams& params) { return; } +#elif !defined(TARGET_AMD64) + // This unified RegNum encoding is implemented only for AMD64. Other 64-bit + // targets still need dedicated encodings to represent FP-containing + // two-register returns without ambiguity, so suppress those cases here + // instead of emitting an encoding the debugger cannot decode. + if (!genIsValidIntReg(reg1) || !genIsValidIntReg(reg2)) + { + return; + } #endif // !TARGET_64BIT - // Either register may be an integer or a floating-point register. On - // platforms where structs can be returned in a mix of int and float - // registers (SysV x64, RISC-V), or in two float registers (e.g. a 16-byte - // Vector128 returned in XMM0+XMM1 on Unix x64), storeVariableInTwoRegisters - // selects the appropriate VLT_REG_REG / VLT_REG_FP_* encoding. - info.returnValueLoc.storeVariableInTwoRegisters(reg1, reg2); + info.returnValueLoc.storeVariableInRegisters(reg1, reg2); } else if (varTypeIsFloating(call)) { @@ -1862,17 +1866,12 @@ void CodeGen::genEmitCallWithCurrentGC(EmitCallParams& params) info.returnValueLoc.vlType = VLT_FPSTK; info.returnValueLoc.vlFPstk.vlfReg = 0; #else - // VLT_REG_FP uses a 0-based FP register index; the DBI adds the - // platform-specific XMM0/V0 base when converting to CorDebugRegister. - info.returnValueLoc.vlType = VLT_REG_FP; - info.returnValueLoc.vlReg.vlrReg = (regNumber)(REG_FLOATRET - REG_FP_FIRST); + info.returnValueLoc.storeVariableInRegisters(REG_FLOATRET, REG_NA); #endif } else if (varTypeUsesFloatReg(call)) { - // VLT_REG_FP uses a 0-based FP register index. - info.returnValueLoc.vlType = VLT_REG_FP; - info.returnValueLoc.vlReg.vlrReg = (regNumber)(REG_FLOATRET - REG_FP_FIRST); + info.returnValueLoc.storeVariableInRegisters(REG_FLOATRET, REG_NA); } else { diff --git a/src/coreclr/jit/codegeninterface.h b/src/coreclr/jit/codegeninterface.h index 443137da86c0cf..d5afb370ee9fca 100644 --- a/src/coreclr/jit/codegeninterface.h +++ b/src/coreclr/jit/codegeninterface.h @@ -519,10 +519,6 @@ class CodeGenInterface VLT_FPSTK, VLT_FIXED_VA, - VLT_REG_FP_REG_FP, - VLT_REG_FP_REG, - VLT_REG_REG_FP, - VLT_COUNT, VLT_INVALID }; @@ -634,8 +630,9 @@ class CodeGenInterface bool vlIsOnStack(regNumber reg, signed offset) const; bool vlIsOnStack() const; + static ICorDebugInfo::RegNum mapRegNumToDebugRegNum(regNumber reg); + void storeVariableInRegisters(regNumber reg, regNumber otherReg); - void storeVariableInTwoRegisters(regNumber reg1, regNumber reg2); void storeVariableOnStack(regNumber stackBaseReg, NATIVE_OFFSET variableStackOffset); siVarLoc(const LclVarDsc* varDsc, regNumber baseReg, int offset, bool isFramePointerUsed); diff --git a/src/coreclr/jit/ee_il_dll.cpp b/src/coreclr/jit/ee_il_dll.cpp index 090d61c3098d73..1332a3fa44af6b 100644 --- a/src/coreclr/jit/ee_il_dll.cpp +++ b/src/coreclr/jit/ee_il_dll.cpp @@ -902,7 +902,12 @@ void Compiler::eeDispVar(ICorDebugInfo::NativeVarInfo* var) break; case CodeGenInterface::VLT_REG_FP: +#ifdef TARGET_AMD64 + printf("%s", getRegName(static_cast(REG_FP_FIRST + var->loc.vlReg.vlrReg - + ICorDebugInfo::REGNUM_FP_FIRST))); +#else printf("%s", getRegName((regNumber)(var->loc.vlReg.vlrReg + REG_FP_FIRST))); +#endif break; case CodeGenInterface::VLT_STK: @@ -922,23 +927,24 @@ void Compiler::eeDispVar(ICorDebugInfo::NativeVarInfo* var) break; case CodeGenInterface::VLT_REG_REG: - printf("%s-%s", getRegName(var->loc.vlRegReg.vlrrReg1), getRegName(var->loc.vlRegReg.vlrrReg2)); - break; - - case CodeGenInterface::VLT_REG_FP_REG_FP: - printf("%s-%s", getRegName((regNumber)(var->loc.vlRegReg.vlrrReg1 + REG_FP_FIRST)), - getRegName((regNumber)(var->loc.vlRegReg.vlrrReg2 + REG_FP_FIRST))); - break; + { +#ifdef TARGET_AMD64 + auto toJitRegNum = [](regNumber reg) { + if (reg >= ICorDebugInfo::REGNUM_FP_FIRST) + { + return static_cast(REG_FP_FIRST + reg - ICorDebugInfo::REGNUM_FP_FIRST); + } - case CodeGenInterface::VLT_REG_FP_REG: - printf("%s-%s", getRegName((regNumber)(var->loc.vlRegReg.vlrrReg1 + REG_FP_FIRST)), - getRegName(var->loc.vlRegReg.vlrrReg2)); - break; + return reg; + }; - case CodeGenInterface::VLT_REG_REG_FP: - printf("%s-%s", getRegName(var->loc.vlRegReg.vlrrReg1), - getRegName((regNumber)(var->loc.vlRegReg.vlrrReg2 + REG_FP_FIRST))); + printf("%s-%s", getRegName(toJitRegNum(var->loc.vlRegReg.vlrrReg1)), + getRegName(toJitRegNum(var->loc.vlRegReg.vlrrReg2))); +#else + printf("%s-%s", getRegName(var->loc.vlRegReg.vlrrReg1), getRegName(var->loc.vlRegReg.vlrrReg2)); +#endif break; + } #ifndef TARGET_AMD64 case CodeGenInterface::VLT_REG_STK: diff --git a/src/coreclr/jit/scopeinfo.cpp b/src/coreclr/jit/scopeinfo.cpp index a354df40b09384..ec65c4ffe03c57 100644 --- a/src/coreclr/jit/scopeinfo.cpp +++ b/src/coreclr/jit/scopeinfo.cpp @@ -79,14 +79,6 @@ bool CodeGenInterface::siVarLoc::vlIsInReg(regNumber reg) const case CodeGenInterface::VLT_FPSTK: return false; - case CodeGenInterface::VLT_REG_FP_REG_FP: - case CodeGenInterface::VLT_REG_FP_REG: - case CodeGenInterface::VLT_REG_REG_FP: - // These describe values that live (at least partly) in floating-point - // registers, recorded as 0-based fp register indices rather than raw - // register numbers, so they cannot be compared against "reg" here. - return false; - default: assert(!"Bad locType"); return false; @@ -132,9 +124,6 @@ bool CodeGenInterface::siVarLoc::vlIsOnStack(regNumber reg, signed offset) const case CodeGenInterface::VLT_REG: case CodeGenInterface::VLT_REG_FP: case CodeGenInterface::VLT_REG_REG: - case CodeGenInterface::VLT_REG_FP_REG_FP: - case CodeGenInterface::VLT_REG_FP_REG: - case CodeGenInterface::VLT_REG_REG_FP: case CodeGenInterface::VLT_FPSTK: return false; @@ -159,81 +148,58 @@ bool CodeGenInterface::siVarLoc::vlIsOnStack() const } //------------------------------------------------------------------------ -// storeVariableInRegisters: Convert the siVarLoc instance in a register -// location using the given registers. +// mapRegNumToDebugRegNum: Map a JIT regNumber to the register number encoding +// used in debug info. // // Arguments: -// reg - the first register where the variable is placed. -// otherReg - the second register where the variable is placed -// or REG_NA if does not apply. +// reg - the JIT register to encode. // -void CodeGenInterface::siVarLoc::storeVariableInRegisters(regNumber reg, regNumber otherReg) +// Return Value: +// The debug-info register number for reg. +// +// static +ICorDebugInfo::RegNum CodeGenInterface::siVarLoc::mapRegNumToDebugRegNum(regNumber reg) { - assert(genIsValidIntReg(reg)); - assert(otherReg == REG_NA || genIsValidIntReg(otherReg)); + assert(genIsValidIntReg(reg) || genIsValidFloatReg(reg)); - if (otherReg == REG_NA) - { - // Only one register is used - vlType = VLT_REG; - vlReg.vlrReg = reg; - } - else +#ifdef TARGET_AMD64 + constexpr unsigned fpRegDebugNumBase = ICorDebugInfo::REGNUM_FP_FIRST; +#else + constexpr unsigned fpRegDebugNumBase = 0; +#endif + + if (genIsValidFloatReg(reg)) { - // Two register are used - vlType = VLT_REG_REG; - vlRegReg.vlrrReg1 = reg; - vlRegReg.vlrrReg2 = otherReg; + return static_cast(fpRegDebugNumBase + (reg - REG_FP_FIRST)); } + + return static_cast(reg); } //------------------------------------------------------------------------ -// storeVariableInTwoRegisters: Convert the siVarLoc instance into a two-register -// location, where either register may be an integer or a floating-point register. +// storeVariableInRegisters: Convert the siVarLoc instance into a register +// location using the given registers. // // Arguments: -// reg1 - the register holding the low 8 bytes of the value. -// reg2 - the register holding the high 8 bytes of the value. +// reg - the first register where the variable is placed. +// otherReg - the second register where the variable is placed +// or REG_NA if does not apply. // -// Notes: -// This generalizes storeVariableInRegisters to support values that live in a -// pair of registers where at least one is a floating-point register (e.g. a -// 16-byte struct returned in XMM0+XMM1 on Unix x64, or a mixed int/fp -// multi-register return). Floating-point registers are recorded as a 0-based -// fp register index (reg - REG_FP_FIRST), matching the VLT_REG_FP convention; -// integer registers are recorded as their ordinary register number. -// -void CodeGenInterface::siVarLoc::storeVariableInTwoRegisters(regNumber reg1, regNumber reg2) +void CodeGenInterface::siVarLoc::storeVariableInRegisters(regNumber reg, regNumber otherReg) { - assert(reg1 != REG_NA); - assert(reg2 != REG_NA); - - const bool fpReg1 = genIsValidFloatReg(reg1); - const bool fpReg2 = genIsValidFloatReg(reg2); + assert(genIsValidIntReg(reg) || genIsValidFloatReg(reg)); + assert((otherReg == REG_NA) || genIsValidIntReg(otherReg) || genIsValidFloatReg(otherReg)); - if (!fpReg1 && !fpReg2) - { - vlType = VLT_REG_REG; - vlRegReg.vlrrReg1 = reg1; - vlRegReg.vlrrReg2 = reg2; - } - else if (fpReg1 && fpReg2) - { - vlType = VLT_REG_FP_REG_FP; - vlRegReg.vlrrReg1 = (regNumber)(reg1 - REG_FP_FIRST); - vlRegReg.vlrrReg2 = (regNumber)(reg2 - REG_FP_FIRST); - } - else if (fpReg1) + if (otherReg == REG_NA) { - vlType = VLT_REG_FP_REG; - vlRegReg.vlrrReg1 = (regNumber)(reg1 - REG_FP_FIRST); - vlRegReg.vlrrReg2 = reg2; + vlType = genIsValidFloatReg(reg) ? VLT_REG_FP : VLT_REG; + vlReg.vlrReg = static_cast(mapRegNumToDebugRegNum(reg)); } else { - vlType = VLT_REG_REG_FP; - vlRegReg.vlrrReg1 = reg1; - vlRegReg.vlrrReg2 = (regNumber)(reg2 - REG_FP_FIRST); + vlType = VLT_REG_REG; + vlRegReg.vlrrReg1 = static_cast(mapRegNumToDebugRegNum(reg)); + vlRegReg.vlrrReg2 = static_cast(mapRegNumToDebugRegNum(otherReg)); } } @@ -297,9 +263,6 @@ bool CodeGenInterface::siVarLoc::Equals(const siVarLoc* lhs, const siVarLoc* rhs return (lhs->vlReg.vlrReg == rhs->vlReg.vlrReg); case VLT_REG_REG: - case VLT_REG_FP_REG_FP: - case VLT_REG_FP_REG: - case VLT_REG_REG_FP: return (lhs->vlRegReg.vlrrReg1 == rhs->vlRegReg.vlrrReg1) && (lhs->vlRegReg.vlrrReg2 == rhs->vlRegReg.vlrrReg2); @@ -476,10 +439,8 @@ void CodeGenInterface::siVarLoc::siFillRegisterVarLoc( #ifdef TARGET_64BIT case TYP_FLOAT: case TYP_DOUBLE: - // VLT_REG_FP uses a 0-based FP register index; the DBI adds the - // platform-specific XMM0/V0 base when converting to CorDebugRegister. this->vlType = VLT_REG_FP; - this->vlReg.vlrReg = (regNumber)(varDsc->GetRegNum() - REG_FP_FIRST); + this->vlReg.vlrReg = static_cast(mapRegNumToDebugRegNum(varDsc->GetRegNum())); break; #else // !TARGET_64BIT @@ -508,10 +469,7 @@ void CodeGenInterface::siVarLoc::siFillRegisterVarLoc( #endif // FEATURE_MASKED_HW_INTRINSICS { this->vlType = VLT_REG_FP; - - // VLT_REG_FP uses a 0-based FP register index; the DBI adds the - // platform-specific XMM0/V0 base when converting to CorDebugRegister. - this->vlReg.vlrReg = (regNumber)(varDsc->GetRegNum() - REG_FP_FIRST); + this->vlReg.vlrReg = static_cast(mapRegNumToDebugRegNum(varDsc->GetRegNum())); break; } #endif // FEATURE_SIMD @@ -589,7 +547,6 @@ void CodeGenInterface::dumpSiVarLoc(const siVarLoc* varLoc) const { case VLT_REG: case VLT_REG_BYREF: - case VLT_REG_FP: printf("%s", getRegName(varLoc->vlReg.vlrReg)); if (varLoc->vlType == VLT_REG_BYREF) { @@ -597,12 +554,14 @@ void CodeGenInterface::dumpSiVarLoc(const siVarLoc* varLoc) const } break; - case VLT_REG_FP_REG_FP: - case VLT_REG_FP_REG: - case VLT_REG_REG_FP: - // At least one of the two registers is an fp register, recorded as a - // 0-based fp register index, so just print the raw register fields. - printf("reg1=%d reg2=%d (fp multi-reg)", varLoc->vlRegReg.vlrrReg1, varLoc->vlRegReg.vlrrReg2); + case VLT_REG_FP: +#ifdef TARGET_AMD64 + printf("%s", + getRegName(static_cast(REG_FP_FIRST + varLoc->vlReg.vlrReg - + ICorDebugInfo::REGNUM_FP_FIRST))); +#else + printf("%s", getRegName(varLoc->vlReg.vlrReg)); +#endif break; case VLT_STK: @@ -621,11 +580,37 @@ void CodeGenInterface::dumpSiVarLoc(const siVarLoc* varLoc) const } break; -#ifndef TARGET_AMD64 case VLT_REG_REG: +#ifdef TARGET_AMD64 + if (varLoc->vlRegReg.vlrrReg1 >= ICorDebugInfo::REGNUM_FP_FIRST) + { + printf("%s", + getRegName(static_cast(REG_FP_FIRST + varLoc->vlRegReg.vlrrReg1 - + ICorDebugInfo::REGNUM_FP_FIRST))); + } + else + { + printf("%s", getRegName(varLoc->vlRegReg.vlrrReg1)); + } + + printf("-"); + + if (varLoc->vlRegReg.vlrrReg2 >= ICorDebugInfo::REGNUM_FP_FIRST) + { + printf("%s", + getRegName(static_cast(REG_FP_FIRST + varLoc->vlRegReg.vlrrReg2 - + ICorDebugInfo::REGNUM_FP_FIRST))); + } + else + { + printf("%s", getRegName(varLoc->vlRegReg.vlrrReg2)); + } +#else printf("%s-%s", getRegName(varLoc->vlRegReg.vlrrReg1), getRegName(varLoc->vlRegReg.vlrrReg2)); +#endif break; +#ifndef TARGET_AMD64 case VLT_REG_STK: if ((int)varLoc->vlRegStk.vlrsStk.vlrssBaseReg != (int)ICorDebugInfo::REGNUM_AMBIENT_SP) { @@ -1508,9 +1493,6 @@ void CodeGen::checkICodeDebugInfo() assert((unsigned)ICorDebugInfo::VLT_STK2 == CodeGenInterface::VLT_STK2); assert((unsigned)ICorDebugInfo::VLT_FPSTK == CodeGenInterface::VLT_FPSTK); assert((unsigned)ICorDebugInfo::VLT_FIXED_VA == CodeGenInterface::VLT_FIXED_VA); - assert((unsigned)ICorDebugInfo::VLT_REG_FP_REG_FP == CodeGenInterface::VLT_REG_FP_REG_FP); - assert((unsigned)ICorDebugInfo::VLT_REG_FP_REG == CodeGenInterface::VLT_REG_FP_REG); - assert((unsigned)ICorDebugInfo::VLT_REG_REG_FP == CodeGenInterface::VLT_REG_REG_FP); assert((unsigned)ICorDebugInfo::VLT_COUNT == CodeGenInterface::VLT_COUNT); assert((unsigned)ICorDebugInfo::VLT_INVALID == CodeGenInterface::VLT_INVALID); @@ -1811,27 +1793,7 @@ void CodeGen::psiBegProlog() if (reg1 != REG_NA) { - if (reg2 == REG_NA) - { - if (genIsValidFloatReg(reg1)) - { - // FP parameter in a single XMM/V register — encode as VLT_REG_FP - // with a 0-based FP register index. - varLocation.vlType = VLT_REG_FP; - varLocation.vlReg.vlrReg = (regNumber)(reg1 - REG_FP_FIRST); - } - else - { - varLocation.storeVariableInRegisters(reg1, REG_NA); - } - } - else - { - // Two-register parameter. Either register may be an integer or a - // floating-point register (e.g. mixed int/fp struct passing on - // SysV x64); storeVariableInTwoRegisters selects the right encoding. - varLocation.storeVariableInTwoRegisters(reg1, reg2); - } + varLocation.storeVariableInRegisters(reg1, reg2); } else { diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.VarInfo.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.VarInfo.cs index 3587c8e93ab239..e307bcb2844616 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.VarInfo.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.VarInfo.cs @@ -26,10 +26,6 @@ public enum VarLocType : uint VLT_FPSTK, // variable lives on the floating-point stack VLT_FIXED_VA, // variable is a fixed argument in a varargs function (relative to VARARGS_HANDLE) - VLT_REG_FP_REG_FP, // variable lives in two fp registers (e.g. a 16-byte struct returned in XMM0+XMM1 on Unix x64) - VLT_REG_FP_REG, // low part lives in an fp register, high part in an int register (mixed multi-reg return) - VLT_REG_REG_FP, // low part lives in an int register, high part in an fp register (mixed multi-reg return) - VLT_COUNT, VLT_INVALID }; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewSymbolsBuilder.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewSymbolsBuilder.cs index 99f4d8242b9617..2ca792fd6645a1 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewSymbolsBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewSymbolsBuilder.cs @@ -199,9 +199,6 @@ public void EmitSubprogramInfo( case VarLocType.VLT_REG_BYREF: case VarLocType.VLT_STK_BYREF: case VarLocType.VLT_REG_REG: - case VarLocType.VLT_REG_FP_REG_FP: - case VarLocType.VLT_REG_FP_REG: - case VarLocType.VLT_REG_REG_FP: case VarLocType.VLT_REG_STK: case VarLocType.VLT_STK_REG: case VarLocType.VLT_STK2: diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs index 28a17af35a35fb..1b177a328db089 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs @@ -104,6 +104,23 @@ private enum RegNumAmd64 : int REGNUM_R13, REGNUM_R14, REGNUM_R15, + REGNUM_FP_FIRST, + REGNUM_XMM0 = REGNUM_FP_FIRST, + REGNUM_XMM1, + REGNUM_XMM2, + REGNUM_XMM3, + REGNUM_XMM4, + REGNUM_XMM5, + REGNUM_XMM6, + REGNUM_XMM7, + REGNUM_XMM8, + REGNUM_XMM9, + REGNUM_XMM10, + REGNUM_XMM11, + REGNUM_XMM12, + REGNUM_XMM13, + REGNUM_XMM14, + REGNUM_XMM15, REGNUM_COUNT, REGNUM_SP = REGNUM_RSP, REGNUM_FP = REGNUM_RBP @@ -144,7 +161,7 @@ public static int DwarfRegNum(TargetArchitecture architecture, int regNum) RegNumAmd64.REGNUM_R13 => 13, RegNumAmd64.REGNUM_R14 => 14, RegNumAmd64.REGNUM_R15 => 15, - _ => regNum - (int)RegNumAmd64.REGNUM_COUNT + 17 // FP registers + _ => regNum - (int)RegNumAmd64.REGNUM_FP_FIRST + 17 // FP registers }; case TargetArchitecture.X86: 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 2770af8660f7eb..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 @@ -286,14 +286,6 @@ public static byte[] CreateVarBlobForMethod(NativeVarInfo[] varInfos, TargetDeta writer.WriteUInt((uint)nativeVarInfo.varLoc.B); writer.WriteUInt((uint)nativeVarInfo.varLoc.C); break; - case VarLocType.VLT_REG_FP_REG_FP: - case VarLocType.VLT_REG_FP_REG: - case VarLocType.VLT_REG_REG_FP: - // Two-register location where at least one register is an fp - // register. Encoded with two register fields like VLT_REG_REG. - writer.WriteUInt((uint)nativeVarInfo.varLoc.B); - writer.WriteUInt((uint)nativeVarInfo.varLoc.C); - break; case VarLocType.VLT_REG_STK: writer.WriteUInt((uint)nativeVarInfo.varLoc.B); writer.WriteUInt((uint)nativeVarInfo.varLoc.C); diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/Registers.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/Registers.cs index 3107a67fdedf82..e09de3509fad52 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/Registers.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/Registers.cs @@ -28,5 +28,21 @@ public enum Registers R13 = 13, R14 = 14, R15 = 15, + XMM0 = 16, + XMM1 = 17, + XMM2 = 18, + XMM3 = 19, + XMM4 = 20, + XMM5 = 21, + XMM6 = 22, + XMM7 = 23, + XMM8 = 24, + XMM9 = 25, + XMM10 = 26, + XMM11 = 27, + XMM12 = 28, + XMM13 = 29, + XMM14 = 30, + XMM15 = 31, } } diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs index 13ce05b341c0e8..58cb620fe98558 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs @@ -310,12 +310,6 @@ private void ParseNativeVarInfo(NativeReader imageReader, int offset) varLoc.Data1 = (int)reader.ReadUInt(); varLoc.Data2 = (int)reader.ReadUInt(); break; - case VarLocType.VLT_REG_FP_REG_FP: - case VarLocType.VLT_REG_FP_REG: - case VarLocType.VLT_REG_REG_FP: - varLoc.Data1 = (int)reader.ReadUInt(); - varLoc.Data2 = (int)reader.ReadUInt(); - break; case VarLocType.VLT_REG_STK: varLoc.Data1 = (int)reader.ReadUInt(); varLoc.Data2 = (int)reader.ReadUInt(); diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfoTypes.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfoTypes.cs index 5dbe37d4c8b8b6..aa8e1e26d71e1e 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfoTypes.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfoTypes.cs @@ -107,10 +107,6 @@ public enum VarLocType VLT_FPSTK, // variable lives on the floating-point stack VLT_FIXED_VA, // variable is a fixed argument in a varargs function (relative to VARARGS_HANDLE) - VLT_REG_FP_REG_FP, // variable lives in two fp registers (e.g. a 16-byte struct returned in XMM0+XMM1 on Unix x64) - VLT_REG_FP_REG, // low part lives in an fp register, high part in an int register (mixed multi-reg return) - VLT_REG_REG_FP, // low part lives in an int register, high part in an fp register (mixed multi-reg return) - VLT_COUNT, VLT_INVALID, } diff --git a/src/coreclr/tools/r2rdump/Extensions.cs b/src/coreclr/tools/r2rdump/Extensions.cs index b28e76bfb31a1c..f93e4cf36d2afb 100644 --- a/src/coreclr/tools/r2rdump/Extensions.cs +++ b/src/coreclr/tools/r2rdump/Extensions.cs @@ -97,12 +97,6 @@ public static void WriteTo(this DebugInfo theThis, TextWriter writer, DumpModel writer.WriteLine($" Register 1: {DebugInfo.GetPlatformSpecificRegister(theThis.Machine, varLoc.VariableLocation.Data1)}"); writer.WriteLine($" Register 2: {DebugInfo.GetPlatformSpecificRegister(theThis.Machine, varLoc.VariableLocation.Data2)}"); break; - case VarLocType.VLT_REG_FP_REG_FP: - case VarLocType.VLT_REG_FP_REG: - case VarLocType.VLT_REG_REG_FP: - writer.WriteLine($" Register 1: {varLoc.VariableLocation.Data1}"); - writer.WriteLine($" Register 2: {varLoc.VariableLocation.Data2}"); - break; case VarLocType.VLT_REG_STK: writer.WriteLine($" Register: {DebugInfo.GetPlatformSpecificRegister(theThis.Machine, varLoc.VariableLocation.Data1)}"); writer.WriteLine($" Base Register: {DebugInfo.GetPlatformSpecificRegister(theThis.Machine, varLoc.VariableLocation.Data2)}"); diff --git a/src/coreclr/vm/debuginfostore.cpp b/src/coreclr/vm/debuginfostore.cpp index b08fed0c96a7c4..6e1f114c28288e 100644 --- a/src/coreclr/vm/debuginfostore.cpp +++ b/src/coreclr/vm/debuginfostore.cpp @@ -393,16 +393,6 @@ static void DoNativeVarInfo( trans.DoEncodedRegIdx(pVar->loc.vlRegReg.vlrrReg2); break; - // Multi-register returns where at least one register is a floating-point register. - // These reuse the vlRegReg layout, so the two register indices are encoded - // just like VLT_REG_REG. - case ICorDebugInfo::VLT_REG_FP_REG_FP: - case ICorDebugInfo::VLT_REG_FP_REG: // fall through - case ICorDebugInfo::VLT_REG_REG_FP: // fall through - trans.DoEncodedRegIdx(pVar->loc.vlRegReg.vlrrReg1); - trans.DoEncodedRegIdx(pVar->loc.vlRegReg.vlrrReg2); - break; - case ICorDebugInfo::VLT_REG_STK: trans.DoEncodedRegIdx(pVar->loc.vlRegStk.vlrsReg); trans.DoEncodedRegIdx(pVar->loc.vlRegStk.vlrsStk.vlrssBaseReg); diff --git a/src/coreclr/vm/util.cpp b/src/coreclr/vm/util.cpp index 7fc261b2cb740d..2c1b6ec4b3b31d 100644 --- a/src/coreclr/vm/util.cpp +++ b/src/coreclr/vm/util.cpp @@ -176,9 +176,6 @@ bool operator ==(const ICorDebugInfo::VarLoc &varLoc1, varLoc1.vlStk.vlsOffset == varLoc2.vlStk.vlsOffset; case ICorDebugInfo::VLT_REG_REG: - case ICorDebugInfo::VLT_REG_FP_REG_FP: - case ICorDebugInfo::VLT_REG_FP_REG: - case ICorDebugInfo::VLT_REG_REG_FP: return varLoc1.vlRegReg.vlrrReg1 == varLoc2.vlRegReg.vlrrReg1 && varLoc1.vlRegReg.vlrrReg2 == varLoc2.vlRegReg.vlrrReg2; From 8ad62bdbdb0e1b7e17bdc6ea0f17c1afcdf9a8f1 Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Fri, 3 Jul 2026 10:07:34 -0400 Subject: [PATCH 04/16] Fix enum comparison warning in ee_il_dll.cpp for clang Use ICorDebugInfo::RegNum parameter type and static_cast for the cross-enum comparison in the VLT_REG_REG debug display lambda. Fixes -Werror,-Wenum-compare on clang (Linux builds). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/jit/ee_il_dll.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreclr/jit/ee_il_dll.cpp b/src/coreclr/jit/ee_il_dll.cpp index 1332a3fa44af6b..d8a4246f658852 100644 --- a/src/coreclr/jit/ee_il_dll.cpp +++ b/src/coreclr/jit/ee_il_dll.cpp @@ -929,13 +929,13 @@ void Compiler::eeDispVar(ICorDebugInfo::NativeVarInfo* var) case CodeGenInterface::VLT_REG_REG: { #ifdef TARGET_AMD64 - auto toJitRegNum = [](regNumber reg) { - if (reg >= ICorDebugInfo::REGNUM_FP_FIRST) + auto toJitRegNum = [](ICorDebugInfo::RegNum reg) -> regNumber { + if (static_cast(reg) >= static_cast(ICorDebugInfo::REGNUM_FP_FIRST)) { - return static_cast(REG_FP_FIRST + reg - ICorDebugInfo::REGNUM_FP_FIRST); + return static_cast(REG_FP_FIRST + static_cast(reg) - static_cast(ICorDebugInfo::REGNUM_FP_FIRST)); } - return reg; + return static_cast(reg); }; printf("%s-%s", getRegName(toJitRegNum(var->loc.vlRegReg.vlrrReg1)), From 52ddfbcadc1c7b1e82f1374cb4978951c3082747 Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Fri, 3 Jul 2026 12:13:36 -0400 Subject: [PATCH 05/16] Fix clang enum comparison warnings and harden SetEnregisteredValue - scopeinfo.cpp: Use static_cast for cross-enum comparisons in VLT_REG_REG debug display (same fix as ee_il_dll.cpp). - valuehome.cpp: Relax SetEnregisteredValue assert to accept values up to 2*REG_SIZE and safely handle partial high-register fills. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/debug/di/valuehome.cpp | 15 ++++-- src/coreclr/jit/ee_il_dll.cpp | 7 +-- src/coreclr/jit/scopeinfo.cpp | 81 +++++++++++++++++++----------- 3 files changed, 68 insertions(+), 35 deletions(-) diff --git a/src/coreclr/debug/di/valuehome.cpp b/src/coreclr/debug/di/valuehome.cpp index 8984c22a353cc4..a4aa4ba47f182f 100644 --- a/src/coreclr/debug/di/valuehome.cpp +++ b/src/coreclr/debug/di/valuehome.cpp @@ -268,15 +268,22 @@ void RegRegValueHome::CopyToIPCEType(RemoteAddress * pRegAddr) // for full header comment) void RegRegValueHome::SetEnregisteredValue(MemoryRange newValue, DT_CONTEXT * pContext, bool fIsSigned) { - _ASSERTE(newValue.Size() == 8); + // A two-register value occupies more than one register's worth of space + // and at most two registers' worth. On x86 this is 8 bytes (2*4), on + // x64 this is up to 16 bytes (2*8). + _ASSERTE((newValue.Size() > sizeof(void*)) && (newValue.Size() <= 2 * sizeof(void*))); _ASSERTE(REG_SIZE == sizeof(void*)); // Split the new value into high and low parts. - SIZE_T highPart; - SIZE_T lowPart; + SIZE_T highPart = 0; + SIZE_T lowPart = 0; memcpy(&lowPart, newValue.StartAddress(), REG_SIZE); - memcpy(&highPart, (BYTE *)newValue.StartAddress() + REG_SIZE, REG_SIZE); + // Only read the high part if the value is large enough to span two registers. + if (newValue.Size() > REG_SIZE) + { + memcpy(&highPart, (BYTE *)newValue.StartAddress() + REG_SIZE, newValue.Size() - REG_SIZE); + } // Update the proper registers. SetContextRegister(pContext, m_reg1Info.m_kRegNumber, highPart); // throws diff --git a/src/coreclr/jit/ee_il_dll.cpp b/src/coreclr/jit/ee_il_dll.cpp index d8a4246f658852..3722cad8ee94ab 100644 --- a/src/coreclr/jit/ee_il_dll.cpp +++ b/src/coreclr/jit/ee_il_dll.cpp @@ -930,11 +930,12 @@ void Compiler::eeDispVar(ICorDebugInfo::NativeVarInfo* var) { #ifdef TARGET_AMD64 auto toJitRegNum = [](ICorDebugInfo::RegNum reg) -> regNumber { - if (static_cast(reg) >= static_cast(ICorDebugInfo::REGNUM_FP_FIRST)) + unsigned val = static_cast(reg); + unsigned fpFirst = static_cast(ICorDebugInfo::REGNUM_FP_FIRST); + if (val >= fpFirst) { - return static_cast(REG_FP_FIRST + static_cast(reg) - static_cast(ICorDebugInfo::REGNUM_FP_FIRST)); + return static_cast(REG_FP_FIRST + val - fpFirst); } - return static_cast(reg); }; diff --git a/src/coreclr/jit/scopeinfo.cpp b/src/coreclr/jit/scopeinfo.cpp index fa126617178ecb..4e247ea19cd0ac 100644 --- a/src/coreclr/jit/scopeinfo.cpp +++ b/src/coreclr/jit/scopeinfo.cpp @@ -187,19 +187,35 @@ ICorDebugInfo::RegNum CodeGenInterface::siVarLoc::mapRegNumToDebugRegNum(regNumb // void CodeGenInterface::siVarLoc::storeVariableInRegisters(regNumber reg, regNumber otherReg) { +#ifdef TARGET_AMD64 assert(genIsValidIntReg(reg) || genIsValidFloatReg(reg)); assert((otherReg == REG_NA) || genIsValidIntReg(otherReg) || genIsValidFloatReg(otherReg)); +#else + assert(genIsValidIntReg(reg)); + assert((otherReg == REG_NA) || genIsValidIntReg(otherReg)); +#endif if (otherReg == REG_NA) { +#ifdef TARGET_AMD64 vlType = genIsValidFloatReg(reg) ? VLT_REG_FP : VLT_REG; vlReg.vlrReg = static_cast(mapRegNumToDebugRegNum(reg)); +#else + vlType = VLT_REG; + vlReg.vlrReg = reg; +#endif } else { +#ifdef TARGET_AMD64 vlType = VLT_REG_REG; vlRegReg.vlrrReg1 = static_cast(mapRegNumToDebugRegNum(reg)); vlRegReg.vlrrReg2 = static_cast(mapRegNumToDebugRegNum(otherReg)); +#else + vlType = VLT_REG_REG; + vlRegReg.vlrrReg1 = reg; + vlRegReg.vlrrReg2 = otherReg; +#endif } } @@ -468,7 +484,7 @@ void CodeGenInterface::siVarLoc::siFillRegisterVarLoc( case TYP_MASK: #endif // FEATURE_MASKED_HW_INTRINSICS { - this->vlType = VLT_REG_FP; + this->vlType = VLT_REG_FP; this->vlReg.vlrReg = static_cast(mapRegNumToDebugRegNum(varDsc->GetRegNum())); break; } @@ -556,9 +572,8 @@ void CodeGenInterface::dumpSiVarLoc(const siVarLoc* varLoc) const case VLT_REG_FP: #ifdef TARGET_AMD64 - printf("%s", - getRegName(static_cast(REG_FP_FIRST + varLoc->vlReg.vlrReg - - ICorDebugInfo::REGNUM_FP_FIRST))); + printf("%s", getRegName(static_cast(REG_FP_FIRST + varLoc->vlReg.vlrReg - + ICorDebugInfo::REGNUM_FP_FIRST))); #else printf("%s", getRegName(varLoc->vlReg.vlrReg)); #endif @@ -582,33 +597,26 @@ void CodeGenInterface::dumpSiVarLoc(const siVarLoc* varLoc) const case VLT_REG_REG: #ifdef TARGET_AMD64 - if (varLoc->vlRegReg.vlrrReg1 >= ICorDebugInfo::REGNUM_FP_FIRST) - { - printf("%s", - getRegName(static_cast(REG_FP_FIRST + varLoc->vlRegReg.vlrrReg1 - - ICorDebugInfo::REGNUM_FP_FIRST))); - } - else - { - printf("%s", getRegName(varLoc->vlRegReg.vlrrReg1)); - } - - printf("-"); + { + // Map RegNum values (which may include FP register indices) back to + // JIT regNumber for display purposes. + auto toJitReg = [](regNumber r) -> regNumber { + unsigned val = static_cast(r); + unsigned fpFirst = static_cast(ICorDebugInfo::REGNUM_FP_FIRST); + if (val >= fpFirst) + { + return static_cast(REG_FP_FIRST + val - fpFirst); + } + return r; + }; - if (varLoc->vlRegReg.vlrrReg2 >= ICorDebugInfo::REGNUM_FP_FIRST) - { - printf("%s", - getRegName(static_cast(REG_FP_FIRST + varLoc->vlRegReg.vlrrReg2 - - ICorDebugInfo::REGNUM_FP_FIRST))); - } - else - { - printf("%s", getRegName(varLoc->vlRegReg.vlrrReg2)); - } + printf("%s-%s", getRegName(toJitReg(varLoc->vlRegReg.vlrrReg1)), + getRegName(toJitReg(varLoc->vlRegReg.vlrrReg2))); + } #else printf("%s-%s", getRegName(varLoc->vlRegReg.vlrrReg1), getRegName(varLoc->vlRegReg.vlrrReg2)); #endif - break; + break; #ifndef TARGET_AMD64 case VLT_REG_STK: @@ -1793,7 +1801,24 @@ void CodeGen::psiBegProlog() if (reg1 != REG_NA) { - varLocation.storeVariableInRegisters(reg1, reg2); +#ifdef TARGET_AMD64 + // On AMD64, storeVariableInRegisters handles both int and FP + // registers via the unified RegNum encoding. + if (genIsValidIntReg(reg1) || genIsValidFloatReg(reg1)) + { + varLocation.storeVariableInRegisters(reg1, reg2); + } +#else + // On non-AMD64, only int registers are supported in RegNum. + if (genIsValidIntReg(reg1)) + { + varLocation.storeVariableInRegisters(reg1, reg2); + } +#endif + else + { + varLocation.storeVariableOnStack(REG_SPBASE, psiGetVarStackOffset(lclVarDsc)); + } } else { From 58654378d47a7a4d15995b78691fff36a02bb693 Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Sat, 4 Jul 2026 08:57:53 -0400 Subject: [PATCH 06/16] Handle FP registers in storeVariableInRegisters on all platforms On non-AMD64 platforms (ARM64, LoongArch64, RISC-V), the JIT can pass FP registers to storeVariableInRegisters for float/double return values and FP-homed parameters. Previously this asserted genIsValidIntReg. Now handles all platforms uniformly: - Single FP register: encodes as VLT_REG_FP with 0-based index (non-AMD64) or unified RegNum (AMD64) - Two-register with any FP on non-AMD64: VLT_INVALID (graceful bail-out since VLT_REG_REG can only encode int regs there) - Simplifies psiBegProlog caller to a single platform-independent guard Fixes NativeAOT ILC assert on LoongArch64/RISC-V and runtime test crashes on ARM64/x86/x64 Checked no_tiered_compilation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/jit/scopeinfo.cpp | 42 +++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/coreclr/jit/scopeinfo.cpp b/src/coreclr/jit/scopeinfo.cpp index 4e247ea19cd0ac..b3b70c689c293f 100644 --- a/src/coreclr/jit/scopeinfo.cpp +++ b/src/coreclr/jit/scopeinfo.cpp @@ -187,23 +187,26 @@ ICorDebugInfo::RegNum CodeGenInterface::siVarLoc::mapRegNumToDebugRegNum(regNumb // void CodeGenInterface::siVarLoc::storeVariableInRegisters(regNumber reg, regNumber otherReg) { -#ifdef TARGET_AMD64 assert(genIsValidIntReg(reg) || genIsValidFloatReg(reg)); assert((otherReg == REG_NA) || genIsValidIntReg(otherReg) || genIsValidFloatReg(otherReg)); -#else - assert(genIsValidIntReg(reg)); - assert((otherReg == REG_NA) || genIsValidIntReg(otherReg)); -#endif if (otherReg == REG_NA) { + if (genIsValidFloatReg(reg)) + { + vlType = VLT_REG_FP; #ifdef TARGET_AMD64 - vlType = genIsValidFloatReg(reg) ? VLT_REG_FP : VLT_REG; - vlReg.vlrReg = static_cast(mapRegNumToDebugRegNum(reg)); + vlReg.vlrReg = static_cast(mapRegNumToDebugRegNum(reg)); #else - vlType = VLT_REG; - vlReg.vlrReg = reg; + // Non-AMD64: store 0-based FP register index (DBI adds platform base) + vlReg.vlrReg = static_cast(reg - REG_FP_FIRST); #endif + } + else + { + vlType = VLT_REG; + vlReg.vlrReg = reg; + } } else { @@ -212,6 +215,13 @@ void CodeGenInterface::siVarLoc::storeVariableInRegisters(regNumber reg, regNumb vlRegReg.vlrrReg1 = static_cast(mapRegNumToDebugRegNum(reg)); vlRegReg.vlrrReg2 = static_cast(mapRegNumToDebugRegNum(otherReg)); #else + // Non-AMD64: VLT_REG_REG only supports int registers. If either is FP, + // we cannot encode this — fall back to VLT_INVALID. + if (!genIsValidIntReg(reg) || !genIsValidIntReg(otherReg)) + { + vlType = VLT_INVALID; + return; + } vlType = VLT_REG_REG; vlRegReg.vlrrReg1 = reg; vlRegReg.vlrrReg2 = otherReg; @@ -1801,20 +1811,14 @@ void CodeGen::psiBegProlog() if (reg1 != REG_NA) { -#ifdef TARGET_AMD64 - // On AMD64, storeVariableInRegisters handles both int and FP - // registers via the unified RegNum encoding. + // storeVariableInRegisters handles int and FP registers on all + // platforms (FP → VLT_REG_FP, mixed multi-reg → VLT_INVALID on + // non-AMD64). Only fall back to stack if the register is truly + // not representable (neither int nor FP). if (genIsValidIntReg(reg1) || genIsValidFloatReg(reg1)) { varLocation.storeVariableInRegisters(reg1, reg2); } -#else - // On non-AMD64, only int registers are supported in RegNum. - if (genIsValidIntReg(reg1)) - { - varLocation.storeVariableInRegisters(reg1, reg2); - } -#endif else { varLocation.storeVariableOnStack(REG_SPBASE, psiGetVarStackOffset(lclVarDsc)); From c5187cee9ad9789929c45be0a9aa34280fc097d8 Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Sun, 5 Jul 2026 16:16:52 -0400 Subject: [PATCH 07/16] Guard against XMM16+ in debug info encoding and update stale comments - mapRegNumToDebugRegNum: return REGNUM_COUNT sentinel for XMM16-31 (AVX-512 registers not representable in the RegNum enum) - storeVariableInRegisters: check for sentinel and emit VLT_INVALID - siFillRegisterVarLoc: same guard for float/double and SIMD locals - rstype.cpp: update comments to reflect unified RegNum encoding (removed references to deleted VLT_REG_FP_REG* types) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/debug/di/rstype.cpp | 15 +++--- src/coreclr/jit/scopeinfo.cpp | 96 +++++++++++++++++++++++++++------ 2 files changed, 88 insertions(+), 23 deletions(-) diff --git a/src/coreclr/debug/di/rstype.cpp b/src/coreclr/debug/di/rstype.cpp index 14bd81f7e03372..8dec90b8ff532b 100644 --- a/src/coreclr/debug/di/rstype.cpp +++ b/src/coreclr/debug/di/rstype.cpp @@ -1744,9 +1744,9 @@ HRESULT CordbType::ReturnedByValue() // pointer-sized registers. Larger value types use the return buffer (stack) // path, which the JIT does not currently emit MRV info for. // - // Two-register returns are encoded via VLT_REG_REG (two integer registers), - // VLT_REG_FP_REG_FP (two FP registers), or the mixed VLT_REG_FP_REG / - // VLT_REG_REG_FP forms. Single-register returns use VLT_REG / VLT_REG_FP. + // On AMD64, the RegNum enum includes FP registers (XMM0-XMM15), so + // VLT_REG_REG can encode any combination of int and FP registers for + // two-register returns. Single-register returns use VLT_REG / VLT_REG_FP. if (unboxedSize > 2 * sizeof(SIZE_T)) return S_FALSE; @@ -1754,11 +1754,10 @@ HRESULT CordbType::ReturnedByValue() // 64-bit target). Single-register (<= pointer-sized) returns only support // the original simple cases: a single integer/pointer-sized non-FP field. // Floating-point and generic (unbound type-parameter) fields are only - // encodable for the two-register multi-reg forms (VLT_REG_FP_REG_FP and the - // mixed VLT_REG_FP_REG / VLT_REG_REG_FP). Enabling them for single-register - // value classes would - // reach unimplemented paths in the value-home code, so they remain - // unsupported there. + // encodable for the two-register case (where VLT_REG_REG with unified + // RegNum handles all int/FP combinations). Enabling them for single-register + // value classes would reach unimplemented paths in the value-home code, so + // they remain unsupported there. const bool twoRegister = (unboxedSize > sizeof(SIZE_T)); // 64-bit targets support multi-field value classes (e.g. ValueTuple) diff --git a/src/coreclr/jit/scopeinfo.cpp b/src/coreclr/jit/scopeinfo.cpp index b3b70c689c293f..d370fc7bcef9a5 100644 --- a/src/coreclr/jit/scopeinfo.cpp +++ b/src/coreclr/jit/scopeinfo.cpp @@ -160,20 +160,36 @@ bool CodeGenInterface::siVarLoc::vlIsOnStack() const // static ICorDebugInfo::RegNum CodeGenInterface::siVarLoc::mapRegNumToDebugRegNum(regNumber reg) { - assert(genIsValidIntReg(reg) || genIsValidFloatReg(reg)); - #ifdef TARGET_AMD64 - constexpr unsigned fpRegDebugNumBase = ICorDebugInfo::REGNUM_FP_FIRST; + constexpr unsigned fpRegDebugNumBase = ICorDebugInfo::REGNUM_FP_FIRST; + constexpr unsigned maxEncodableFpRegs = 16; // Only XMM0-XMM15 are in RegNum #else - constexpr unsigned fpRegDebugNumBase = 0; + constexpr unsigned fpRegDebugNumBase = 0; + constexpr unsigned maxEncodableFpRegs = 0; #endif + if (genIsValidIntReg(reg)) + { + return static_cast(reg); + } + if (genIsValidFloatReg(reg)) { - return static_cast(fpRegDebugNumBase + (reg - REG_FP_FIRST)); + unsigned fpIndex = reg - REG_FP_FIRST; +#ifdef TARGET_AMD64 + // Only XMM0-XMM15 are representable in the debug RegNum enum. + // XMM16-XMM31 (AVX-512) cannot be encoded. + if (fpIndex >= maxEncodableFpRegs) + { + return ICorDebugInfo::REGNUM_COUNT; // sentinel: caller checks for this + } +#endif + return static_cast(fpRegDebugNumBase + fpIndex); } - return static_cast(reg); + // Mask registers (K0-K7) and any other non-int/non-float registers + // cannot be represented in the debug info encoding. + return ICorDebugInfo::REGNUM_COUNT; } //------------------------------------------------------------------------ @@ -187,33 +203,54 @@ ICorDebugInfo::RegNum CodeGenInterface::siVarLoc::mapRegNumToDebugRegNum(regNumb // void CodeGenInterface::siVarLoc::storeVariableInRegisters(regNumber reg, regNumber otherReg) { - assert(genIsValidIntReg(reg) || genIsValidFloatReg(reg)); - assert((otherReg == REG_NA) || genIsValidIntReg(otherReg) || genIsValidFloatReg(otherReg)); + // Note: mask registers (K0-K7) and XMM16+ are accepted but will produce + // VLT_INVALID since they can't be encoded in debug info. if (otherReg == REG_NA) { if (genIsValidFloatReg(reg)) { - vlType = VLT_REG_FP; #ifdef TARGET_AMD64 - vlReg.vlrReg = static_cast(mapRegNumToDebugRegNum(reg)); + ICorDebugInfo::RegNum debugReg = mapRegNumToDebugRegNum(reg); + if (debugReg == ICorDebugInfo::REGNUM_COUNT) + { + // XMM16+ cannot be encoded in the debug info. + vlType = VLT_INVALID; + return; + } + vlType = VLT_REG_FP; + vlReg.vlrReg = static_cast(debugReg); #else // Non-AMD64: store 0-based FP register index (DBI adds platform base) + vlType = VLT_REG_FP; vlReg.vlrReg = static_cast(reg - REG_FP_FIRST); #endif } - else + else if (genIsValidIntReg(reg)) { vlType = VLT_REG; vlReg.vlrReg = reg; } + else + { + // Mask registers or other non-encodable register types. + vlType = VLT_INVALID; + return; + } } else { #ifdef TARGET_AMD64 + ICorDebugInfo::RegNum debugReg1 = mapRegNumToDebugRegNum(reg); + ICorDebugInfo::RegNum debugReg2 = mapRegNumToDebugRegNum(otherReg); + if (debugReg1 == ICorDebugInfo::REGNUM_COUNT || debugReg2 == ICorDebugInfo::REGNUM_COUNT) + { + vlType = VLT_INVALID; + return; + } vlType = VLT_REG_REG; - vlRegReg.vlrrReg1 = static_cast(mapRegNumToDebugRegNum(reg)); - vlRegReg.vlrrReg2 = static_cast(mapRegNumToDebugRegNum(otherReg)); + vlRegReg.vlrrReg1 = static_cast(debugReg1); + vlRegReg.vlrrReg2 = static_cast(debugReg2); #else // Non-AMD64: VLT_REG_REG only supports int registers. If either is FP, // we cannot encode this — fall back to VLT_INVALID. @@ -465,9 +502,18 @@ void CodeGenInterface::siVarLoc::siFillRegisterVarLoc( #ifdef TARGET_64BIT case TYP_FLOAT: case TYP_DOUBLE: + { + ICorDebugInfo::RegNum debugReg = mapRegNumToDebugRegNum(varDsc->GetRegNum()); + if (debugReg == ICorDebugInfo::REGNUM_COUNT) + { + // XMM16+ cannot be encoded. + this->vlType = VLT_INVALID; + break; + } this->vlType = VLT_REG_FP; - this->vlReg.vlrReg = static_cast(mapRegNumToDebugRegNum(varDsc->GetRegNum())); + this->vlReg.vlrReg = static_cast(debugReg); break; + } #else // !TARGET_64BIT @@ -494,8 +540,15 @@ void CodeGenInterface::siVarLoc::siFillRegisterVarLoc( case TYP_MASK: #endif // FEATURE_MASKED_HW_INTRINSICS { + ICorDebugInfo::RegNum debugReg = mapRegNumToDebugRegNum(varDsc->GetRegNum()); + if (debugReg == ICorDebugInfo::REGNUM_COUNT) + { + // XMM16+/AVX-512 registers cannot be encoded. + this->vlType = VLT_INVALID; + break; + } this->vlType = VLT_REG_FP; - this->vlReg.vlrReg = static_cast(mapRegNumToDebugRegNum(varDsc->GetRegNum())); + this->vlReg.vlrReg = static_cast(debugReg); break; } #endif // FEATURE_SIMD @@ -1899,6 +1952,13 @@ void CodeGen::genSetScopeInfo() for (const EmittedCallReturnInfo& callReturnInfo : *emittedCallReturnInfo) { + // Skip entries where the return value location couldn't be encoded + // (e.g., mask registers, XMM16+ on AVX-512). + if (callReturnInfo.returnValueLoc.vlType == VLT_INVALID) + { + continue; + } + UNATIVE_OFFSET retOffset = callReturnInfo.returnLocation.CodeOffset(GetEmitter()); m_compiler->eeSetLVinfo(m_compiler->eeVarsCount++, retOffset, retOffset + 1, callReturnInfo.callILOffset, @@ -1932,6 +1992,12 @@ void CodeGen::genSetScopeInfoUsingVariableRanges() auto reportRange = [this, varDsc, varNum, &liveRangeIndex](siVarLoc* loc, UNATIVE_OFFSET start, UNATIVE_OFFSET end) { + // Skip entries that couldn't be encoded (e.g., mask registers, XMM16+). + if (loc->vlType == VLT_INVALID) + { + return; + } + if (varDsc->lvIsParam && (start == end)) { // If the length is zero, it means that the prolog is empty. In that case, From 237b9124b0afb2df2cfc674b1370dea86188ec3e Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Tue, 7 Jul 2026 08:56:13 -0400 Subject: [PATCH 08/16] Fix pre-existing bugs and harden SetEnregisteredValue - ee_il_dll.cpp, scopeinfo.cpp: Fix VLT_STK_BYREF byref suffix display. The VLT_STK/VLT_STK_BYREF case checked VLT_REG_BYREF (always false) instead of VLT_STK_BYREF for printing the 'byref' suffix. - valuehome.cpp: Fix SetEnregisteredValue to update the frame register display per-register instead of a single memcpy that could overwrite adjacent CONTEXT fields when the value spans two non-contiguous registers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/debug/di/valuehome.cpp | 16 +++++++++++++--- src/coreclr/jit/ee_il_dll.cpp | 2 +- src/coreclr/jit/scopeinfo.cpp | 2 +- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/coreclr/debug/di/valuehome.cpp b/src/coreclr/debug/di/valuehome.cpp index a4aa4ba47f182f..6f4f21a1a57a1c 100644 --- a/src/coreclr/debug/di/valuehome.cpp +++ b/src/coreclr/debug/di/valuehome.cpp @@ -289,9 +289,19 @@ void RegRegValueHome::SetEnregisteredValue(MemoryRange newValue, DT_CONTEXT * pC SetContextRegister(pContext, m_reg1Info.m_kRegNumber, highPart); // throws SetContextRegister(pContext, m_reg2Info.m_kRegNumber, lowPart); // throws - // update the frame's register display - void * valueAddress = (void *)(m_pFrame->GetAddressOfRegister(m_reg1Info.m_kRegNumber)); - memcpy(valueAddress, newValue.StartAddress(), newValue.Size()); + // Update the frame's register display for each register individually. + // We must not do a single memcpy of the full value into reg1's address + // because the two registers may not be contiguous in the CONTEXT layout. + UINT_PTR * pReg1 = m_pFrame->GetAddressOfRegister(m_reg1Info.m_kRegNumber); + UINT_PTR * pReg2 = m_pFrame->GetAddressOfRegister(m_reg2Info.m_kRegNumber); + if (pReg1 != NULL) + { + *pReg1 = highPart; + } + if (pReg2 != NULL) + { + *pReg2 = lowPart; + } } // RegRegValueHome::SetEnregisteredValue // RegRegValueHome::GetEnregisteredValue diff --git a/src/coreclr/jit/ee_il_dll.cpp b/src/coreclr/jit/ee_il_dll.cpp index 3722cad8ee94ab..2ffdc455529fcb 100644 --- a/src/coreclr/jit/ee_il_dll.cpp +++ b/src/coreclr/jit/ee_il_dll.cpp @@ -920,7 +920,7 @@ void Compiler::eeDispVar(ICorDebugInfo::NativeVarInfo* var) { printf(STR_SPBASE "'[%d] (1 slot)", var->loc.vlStk.vlsOffset); } - if (var->loc.vlType == (ICorDebugInfo::VarLocType)CodeGenInterface::VLT_REG_BYREF) + if (var->loc.vlType == (ICorDebugInfo::VarLocType)CodeGenInterface::VLT_STK_BYREF) { printf(" byref"); } diff --git a/src/coreclr/jit/scopeinfo.cpp b/src/coreclr/jit/scopeinfo.cpp index d370fc7bcef9a5..29258f8034694e 100644 --- a/src/coreclr/jit/scopeinfo.cpp +++ b/src/coreclr/jit/scopeinfo.cpp @@ -652,7 +652,7 @@ void CodeGenInterface::dumpSiVarLoc(const siVarLoc* varLoc) const { printf(STR_SPBASE "'[%d] (1 slot)", varLoc->vlStk.vlsOffset); } - if (varLoc->vlType == VLT_REG_BYREF) + if (varLoc->vlType == VLT_STK_BYREF) { printf(" byref"); } From 8ddd65e2fa7a7a9cbeecd2131fa729c28a07e4f5 Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Thu, 9 Jul 2026 12:14:42 -0400 Subject: [PATCH 09/16] Bump JIT-EE version GUID for RegNum extension The AMD64 RegNum enum now includes XMM0-XMM15, which changes the values that can appear in NativeVarInfo register fields. An old runtime decoding these values would index g_JITToCorDbgReg out of bounds and misinterpret REGNUM_AMBIENT_SP (shifted from 17 to 33). Bump the GUID to ensure R2R images with the new encoding are not loaded by older runtimes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- 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 e0177d76cd5826..258a867903fe32 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -37,11 +37,11 @@ #include -constexpr GUID JITEEVersionIdentifier = { /* fcb1b400-696c-4425-a8a7-bb082430a217 */ - 0xfcb1b400, - 0x696c, - 0x4425, - {0xa8, 0xa7, 0xbb, 0x08, 0x24, 0x30, 0xa2, 0x17} +constexpr GUID JITEEVersionIdentifier = { /* 1c3c3baa-c05a-4215-90be-c0e5aed474a0 */ + 0x1c3c3baa, + 0xc05a, + 0x4215, + {0x90, 0xbe, 0xc0, 0xe5, 0xae, 0xd4, 0x74, 0xa0} }; #endif // JIT_EE_VERSIONING_GUID_H From 75b058d4a3804a0825ca20d93120277e1a3328fa Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Thu, 9 Jul 2026 14:49:58 -0400 Subject: [PATCH 10/16] Expand DWARF register mapping to explicit cases Replace arithmetic catch-all patterns in DwarfExpressionBuilder with explicit per-register mappings for AMD64 XMM0-XMM15 and throw NotSupportedException for unrecognized registers. Converts ARM64/ARM if-then-else patterns to switch expressions for consistency. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Dwarf/DwarfExpressionBuilder.cs | 40 ++++++++++++++----- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs index 1b177a328db089..1838e4a6f63399 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs @@ -131,16 +131,20 @@ public static int DwarfRegNum(TargetArchitecture architecture, int regNum) switch (architecture) { case TargetArchitecture.ARM64: - // Normal registers are directly mapped - if (regNum >= 33) - regNum = regNum - 33 + 64; // FP - return regNum; + // Integer registers map to DWARF 0-32, FP V registers to 64+ + return regNum switch + { + >= 33 and <= 64 => regNum - 33 + 64, // V0-V31 → DWARF 64-95 + _ => regNum // X0-PC → DWARF 0-32 + }; case TargetArchitecture.ARM: - // Normal registers are directly mapped - if (regNum >= 16) - regNum = ((regNum - 16) / 2) + 256; // FP - return regNum; + // Integer registers map directly, FP D registers to DWARF 256+ + return regNum switch + { + >= 16 => ((regNum - 16) / 2) + 256, // D0-D7 → DWARF 256+ + _ => regNum // R0-PC → DWARF 0-15 + }; case TargetArchitecture.X64: return (RegNumAmd64)regNum switch @@ -161,7 +165,23 @@ public static int DwarfRegNum(TargetArchitecture architecture, int regNum) RegNumAmd64.REGNUM_R13 => 13, RegNumAmd64.REGNUM_R14 => 14, RegNumAmd64.REGNUM_R15 => 15, - _ => regNum - (int)RegNumAmd64.REGNUM_FP_FIRST + 17 // FP registers + RegNumAmd64.REGNUM_XMM0 => 17, + RegNumAmd64.REGNUM_XMM1 => 18, + RegNumAmd64.REGNUM_XMM2 => 19, + RegNumAmd64.REGNUM_XMM3 => 20, + RegNumAmd64.REGNUM_XMM4 => 21, + RegNumAmd64.REGNUM_XMM5 => 22, + RegNumAmd64.REGNUM_XMM6 => 23, + RegNumAmd64.REGNUM_XMM7 => 24, + RegNumAmd64.REGNUM_XMM8 => 25, + RegNumAmd64.REGNUM_XMM9 => 26, + RegNumAmd64.REGNUM_XMM10 => 27, + RegNumAmd64.REGNUM_XMM11 => 28, + RegNumAmd64.REGNUM_XMM12 => 29, + RegNumAmd64.REGNUM_XMM13 => 30, + RegNumAmd64.REGNUM_XMM14 => 31, + RegNumAmd64.REGNUM_XMM15 => 32, + _ => throw new NotSupportedException($"Unsupported AMD64 register {regNum}") }; case TargetArchitecture.X86: @@ -175,7 +195,7 @@ public static int DwarfRegNum(TargetArchitecture architecture, int regNum) RegNumX86.REGNUM_EBP => 5, RegNumX86.REGNUM_ESI => 6, RegNumX86.REGNUM_EDI => 7, - _ => regNum - (int)RegNumX86.REGNUM_COUNT + 32 // FP registers + _ => throw new NotSupportedException($"Unsupported x86 register {regNum}") }; case TargetArchitecture.LoongArch64: From 0cd29332bfc869c374e2e7450333660be1a841b8 Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Thu, 9 Jul 2026 14:49:58 -0400 Subject: [PATCH 11/16] Fix VLT_REG_FP display in dumpSiVarLoc for non-AMD64 On non-AMD64 targets, VLT_REG_FP stores a 0-based FP register index. Map it back to a JIT regNumber (REG_FP_FIRST + index) before calling getRegName so JIT dumps show the correct register name. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- 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 29258f8034694e..7e53336ec615ba 100644 --- a/src/coreclr/jit/scopeinfo.cpp +++ b/src/coreclr/jit/scopeinfo.cpp @@ -638,7 +638,8 @@ void CodeGenInterface::dumpSiVarLoc(const siVarLoc* varLoc) const printf("%s", getRegName(static_cast(REG_FP_FIRST + varLoc->vlReg.vlrReg - ICorDebugInfo::REGNUM_FP_FIRST))); #else - printf("%s", getRegName(varLoc->vlReg.vlrReg)); + // Non-AMD64: vlrReg is a 0-based FP register index; map back to JIT regNumber + printf("%s", getRegName(static_cast(REG_FP_FIRST + varLoc->vlReg.vlrReg))); #endif break; From a09b3fd94e2f054cab842947538ab86bdef0d093 Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Thu, 9 Jul 2026 16:33:10 -0400 Subject: [PATCH 12/16] Address PR feedback: AMBIENT_SP DWARF, fieldCount, SetEnregisteredValue guard - DwarfExpressionBuilder/DwarfInfo: emit a CFA-relative expression for stack slots whose base register is REGNUM_AMBIENT_SP, instead of routing the pseudo-register through DwarfRegNum (which now throws for unmapped registers after the explicit-case expansion). Adds OpCallFrameCfa, OpStackLocation, and AmbientSpRegNum helpers. - rstype.cpp: increment fieldCount unconditionally and apply the single-field restriction only when !allowMultiField, so the counter is meaningful on 64-bit and avoids an unused-variable warning. - valuehome.cpp: replace the debug-only size assert in RegRegValueHome::SetEnregisteredValue with a runtime ThrowHR guard so an unexpected buffer size cannot overrun the source in retail builds. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/debug/di/rstype.cpp | 7 ++- src/coreclr/debug/di/valuehome.cpp | 9 +++- .../Dwarf/DwarfExpressionBuilder.cs | 45 +++++++++++++++++++ .../Compiler/ObjectWriter/Dwarf/DwarfInfo.cs | 6 +-- 4 files changed, 60 insertions(+), 7 deletions(-) diff --git a/src/coreclr/debug/di/rstype.cpp b/src/coreclr/debug/di/rstype.cpp index 8dec90b8ff532b..cc02e1666bce95 100644 --- a/src/coreclr/debug/di/rstype.cpp +++ b/src/coreclr/debug/di/rstype.cpp @@ -1803,8 +1803,11 @@ HRESULT CordbType::ReturnedByValue() { // On 32-bit targets, only single-field value classes are // representable (matching the original behavior). More than one - // non-static field is unsupported there. - if (!allowMultiField && fieldCount++ != 0) + // non-static field is unsupported there. Increment the counter + // unconditionally and apply the single-field restriction only + // when multi-field is not allowed. + fieldCount++; + if (!allowMultiField && fieldCount > 1) { unsupported = true; break; diff --git a/src/coreclr/debug/di/valuehome.cpp b/src/coreclr/debug/di/valuehome.cpp index 6f4f21a1a57a1c..24230bb6c85cd1 100644 --- a/src/coreclr/debug/di/valuehome.cpp +++ b/src/coreclr/debug/di/valuehome.cpp @@ -270,9 +270,14 @@ void RegRegValueHome::SetEnregisteredValue(MemoryRange newValue, DT_CONTEXT * pC { // A two-register value occupies more than one register's worth of space // and at most two registers' worth. On x86 this is 8 bytes (2*4), on - // x64 this is up to 16 bytes (2*8). - _ASSERTE((newValue.Size() > sizeof(void*)) && (newValue.Size() <= 2 * sizeof(void*))); + // x64 this is up to 16 bytes (2*8). Guard at runtime (not just via assert) + // so that an unexpected buffer size cannot cause the memcpy below to read + // past the end of newValue in retail builds. _ASSERTE(REG_SIZE == sizeof(void*)); + if ((newValue.Size() <= sizeof(void*)) || (newValue.Size() > 2 * sizeof(void*))) + { + ThrowHR(E_INVALIDARG); + } // Split the new value into high and low parts. SIZE_T highPart = 0; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs index 1838e4a6f63399..5f13e20c764278 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs @@ -25,6 +25,22 @@ public DwarfExpressionBuilder(TargetArchitecture architecture, byte targetPointe public void OpBReg(int register, int offset = 0) => OpBDwarfReg(DwarfRegNum(_architecture, register), offset); + // Emit a stack-slot location described by a base register and offset. If the base + // register is the "ambient SP" pseudo-register (REGNUM_AMBIENT_SP), emit a + // CFA-relative expression instead of routing the pseudo-register through + // DwarfRegNum (which has no valid DWARF number for it). + public void OpStackLocation(int baseRegister, int offset = 0) + { + if (baseRegister == AmbientSpRegNum(_architecture)) + { + OpCallFrameCfa(offset); + } + else + { + OpBReg(baseRegister, offset); + } + } + public void OpDwarfReg(int register) { if (register <= 31) @@ -54,6 +70,35 @@ public void OpBDwarfReg(int register, int offset = 0) public void OpDeref() => OpCode(DW_OP_deref); + // Emits a location relative to the Canonical Frame Address (CFA). This is used + // for stack slots whose base register is the "ambient SP" pseudo-register + // (REGNUM_AMBIENT_SP), which represents the caller's stack pointer rather than + // a physical register. + public void OpCallFrameCfa(int offset = 0) + { + OpCode(DW_OP_call_frame_cfa); + if (offset != 0) + { + OpCode(DW_OP_consts); + AppendSLEB128(offset); + OpCode(DW_OP_plus); + } + } + + // Returns the RegNum value used for the "ambient SP" pseudo-register on the + // given architecture. It is defined as REGNUM_COUNT + 1 in ICorDebugInfo::RegNum. + private static int AmbientSpRegNum(TargetArchitecture architecture) + { + return architecture switch + { + TargetArchitecture.X86 => (int)RegNumX86.REGNUM_COUNT + 1, + TargetArchitecture.X64 => (int)RegNumAmd64.REGNUM_COUNT + 1, + TargetArchitecture.ARM64 => 66, // 33 int + 32 V registers, +1 + TargetArchitecture.ARM => 25, // 16 int + 8 D registers, +1 + _ => -1 + }; + } + public void OpPiece(uint size = 0) { OpCode(DW_OP_piece); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfInfo.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfInfo.cs index 4ce33dbbe5d815..9dc62e07633837 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfInfo.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfInfo.cs @@ -512,7 +512,7 @@ private static void DumpVarLocation(DwarfExpressionBuilder e, VarLoc loc) case VarLocType.VLT_STK: case VarLocType.VLT_STK2: case VarLocType.VLT_STK_BYREF: - e.OpBReg(loc.B, loc.C); + e.OpStackLocation(loc.B, loc.C); if (loc.LocationType == VarLocType.VLT_STK_BYREF) { e.OpDeref(); @@ -527,11 +527,11 @@ private static void DumpVarLocation(DwarfExpressionBuilder e, VarLoc loc) case VarLocType.VLT_REG_STK: e.OpReg(loc.B); e.OpPiece(); - e.OpBReg(loc.C, loc.D); + e.OpStackLocation(loc.C, loc.D); e.OpPiece(); break; case VarLocType.VLT_STK_REG: - e.OpBReg(loc.B, loc.C); + e.OpStackLocation(loc.B, loc.C); e.OpPiece(); e.OpReg(loc.D); e.OpPiece(); From c30b2c6100d35e6b143233146c380be688f69e3f Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Fri, 10 Jul 2026 22:56:23 -0400 Subject: [PATCH 13/16] Fix ARM/ARM64 ambient-SP RegNum value in NativeAOT DWARF emission AmbientSpRegNum hardcoded ARM=25/ARM64=66, which assume the FP-register-extended RegNum enum (D0-D7/V0-V31). On this branch ARM/ARM64 RegNum contains no FP registers, so REGNUM_AMBIENT_SP is REGNUM_COUNT+1 = 17 (ARM) / 34 (ARM64), as asserted in debug/inc/DbgIPCEvents.h. With the wrong values OpStackLocation would not recognize AMBIENT_SP and would route it through DwarfRegNum, emitting an incorrect DWARF register for stack slots based on the ambient SP. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 23dd0633-d7b5-4721-a2cc-87dc172f0915 --- .../Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs index 5f13e20c764278..f1a5a857cadcc7 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs @@ -86,15 +86,16 @@ public void OpCallFrameCfa(int offset = 0) } // Returns the RegNum value used for the "ambient SP" pseudo-register on the - // given architecture. It is defined as REGNUM_COUNT + 1 in ICorDebugInfo::RegNum. + // given architecture. It is defined as REGNUM_COUNT + 1 in ICorDebugInfo::RegNum + // and must match DBG_TARGET_REGNUM_AMBIENT_SP in debug/inc/DbgIPCEvents.h. private static int AmbientSpRegNum(TargetArchitecture architecture) { return architecture switch { TargetArchitecture.X86 => (int)RegNumX86.REGNUM_COUNT + 1, TargetArchitecture.X64 => (int)RegNumAmd64.REGNUM_COUNT + 1, - TargetArchitecture.ARM64 => 66, // 33 int + 32 V registers, +1 - TargetArchitecture.ARM => 25, // 16 int + 8 D registers, +1 + TargetArchitecture.ARM64 => 34, // 33 int registers (X0-X28, FP, LR, SP, PC), +1 + TargetArchitecture.ARM => 17, // 16 int registers (R0-R12, SP, LR, PC), +1 _ => -1 }; } From 3bd0b1e8e8a414c2a2c8bb79d27b8a7186064c78 Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Sat, 11 Jul 2026 13:15:21 -0400 Subject: [PATCH 14/16] Correct ReturnedByValue comment: single-register supports multiple non-FP fields Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 23dd0633-d7b5-4721-a2cc-87dc172f0915 --- src/coreclr/debug/di/rstype.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/debug/di/rstype.cpp b/src/coreclr/debug/di/rstype.cpp index cc02e1666bce95..86bc53ec3985f7 100644 --- a/src/coreclr/debug/di/rstype.cpp +++ b/src/coreclr/debug/di/rstype.cpp @@ -1752,7 +1752,7 @@ HRESULT CordbType::ReturnedByValue() // Whether the value occupies two registers (size in (8, 16] bytes on a // 64-bit target). Single-register (<= pointer-sized) returns only support - // the original simple cases: a single integer/pointer-sized non-FP field. + // integer/pointer-sized non-FP fields. // Floating-point and generic (unbound type-parameter) fields are only // encodable for the two-register case (where VLT_REG_REG with unified // RegNum handles all int/FP combinations). Enabling them for single-register From 11e87aceed86c56bcb341f4d16bf68b6e6acf81d Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Tue, 14 Jul 2026 08:47:32 -0400 Subject: [PATCH 15/16] Address PR feedback: LoongArch64/RISCV64 ambient-SP + value-init DEBUG var-info array - AmbientSpRegNum: add LoongArch64 and RISCV64 (REGNUM_AMBIENT_SP = 34 on both, per DbgIPCEvents.h). Without these, OpStackLocation would route an ambient-SP stack base through OpBReg and emit a bogus DWARF register instead of DW_OP_call_frame_cfa on those targets. - genSetScopeInfo: value-initialize genTrnslLocalVarInfo so slots not populated by genSetScopeInfoUsingVariableRanges (call-return entries, and entries skipped for VLT_INVALID) are predictably tlviAvailable=false rather than uninitialized (DEBUG-only JIT-dump correctness). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 23dd0633-d7b5-4721-a2cc-87dc172f0915 --- src/coreclr/jit/scopeinfo.cpp | 2 +- .../Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/coreclr/jit/scopeinfo.cpp b/src/coreclr/jit/scopeinfo.cpp index 7e53336ec615ba..debf14770c4a0c 100644 --- a/src/coreclr/jit/scopeinfo.cpp +++ b/src/coreclr/jit/scopeinfo.cpp @@ -1942,7 +1942,7 @@ void CodeGen::genSetScopeInfo() genTrnslLocalVarCount = varsLocationsCount; if (varsLocationsCount) { - genTrnslLocalVarInfo = new (m_compiler, CMK_DebugOnly) TrnslLocalVarInfo[varsLocationsCount]; + genTrnslLocalVarInfo = new (m_compiler, CMK_DebugOnly) TrnslLocalVarInfo[varsLocationsCount](); } #endif diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs index f1a5a857cadcc7..44600c8861d6d6 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs @@ -96,6 +96,8 @@ private static int AmbientSpRegNum(TargetArchitecture architecture) TargetArchitecture.X64 => (int)RegNumAmd64.REGNUM_COUNT + 1, TargetArchitecture.ARM64 => 34, // 33 int registers (X0-X28, FP, LR, SP, PC), +1 TargetArchitecture.ARM => 17, // 16 int registers (R0-R12, SP, LR, PC), +1 + TargetArchitecture.LoongArch64 => 34, // 33 int registers, +1 + TargetArchitecture.RiscV64 => 34, // 33 int registers, +1 _ => -1 }; } From 60f8ac8f03c4d41d81cff021202b73d1115ae0bd Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Tue, 14 Jul 2026 13:20:16 -0400 Subject: [PATCH 16/16] Fix VLT_REG_REG DWARF piece order to emit low half before high half DWARF composite location descriptions list pieces least-significant first (DWARF5 2.6.1.2). For VLT_REG_REG, loc.B is vlrrReg1 (low half) and loc.C is vlrrReg2 (high half), so emit loc.B before loc.C instead of the reverse. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 23dd0633-d7b5-4721-a2cc-87dc172f0915 --- .../Compiler/ObjectWriter/Dwarf/DwarfInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfInfo.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfInfo.cs index 9dc62e07633837..9fffa3b28ea4eb 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfInfo.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfInfo.cs @@ -519,10 +519,10 @@ private static void DumpVarLocation(DwarfExpressionBuilder e, VarLoc loc) } break; case VarLocType.VLT_REG_REG: - e.OpReg(loc.C); - e.OpPiece(); e.OpReg(loc.B); e.OpPiece(); + e.OpReg(loc.C); + e.OpPiece(); break; case VarLocType.VLT_REG_STK: e.OpReg(loc.B);