diff --git a/src/coreclr/debug/di/rspriv.h b/src/coreclr/debug/di/rspriv.h index 8265686253887c..d4d9537c78f660 100644 --- a/src/coreclr/debug/di/rspriv.h +++ b/src/coreclr/debug/di/rspriv.h @@ -6988,6 +6988,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); @@ -7871,6 +7884,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 42ba92d32da33f..378bdbc44510c8 100644 --- a/src/coreclr/debug/di/rsthread.cpp +++ b/src/coreclr/debug/di/rsthread.cpp @@ -6789,10 +6789,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 @@ -7045,6 +7046,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. @@ -8257,7 +8360,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, @@ -8297,6 +8400,31 @@ 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), @@ -8338,15 +8466,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; diff --git a/src/coreclr/debug/di/rstype.cpp b/src/coreclr/debug/di/rstype.cpp index c2021026a30f46..86bc53ec3985f7 100644 --- a/src/coreclr/debug/di/rstype.cpp +++ b/src/coreclr/debug/di/rstype.cpp @@ -1738,9 +1738,46 @@ 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. + // + // 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; + + // Whether the value occupies two registers (size in (8, 16] bytes on a + // 64-bit target). Single-register (<= pointer-sized) returns only support + // 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 + // 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 +1801,17 @@ 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. 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; + } CorElementType et; SigParser parser(sigBlob, sigLen); @@ -1778,7 +1824,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 +1838,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 +1880,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..24230bb6c85cd1 100644 --- a/src/coreclr/debug/di/valuehome.cpp +++ b/src/coreclr/debug/di/valuehome.cpp @@ -268,23 +268,45 @@ 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). 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; - 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 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 @@ -298,14 +320,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 +794,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/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 1d6bdfcec3bfcc..f5975160b84a80 100644 --- a/src/coreclr/inc/cordebuginfo.h +++ b/src/coreclr/inc/cordebuginfo.h @@ -148,6 +148,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, @@ -290,8 +307,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 + // + // 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/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index 571452687aebb2..b66d35a6d89981 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -37,11 +37,11 @@ #include -constexpr GUID JITEEVersionIdentifier = { /* 52ca9f65-880a-43bc-89e8-8b5f66d80ab4 */ - 0x52ca9f65, - 0x880a, - 0x43bc, - {0x89, 0xe8, 0x8b, 0x5f, 0x66, 0xd8, 0x0a, 0xb4} +constexpr GUID JITEEVersionIdentifier = { /* 24c0b78a-9173-40cd-a6b3-e290fb3c0a22 */ + 0x24c0b78a, + 0x9173, + 0x40cd, + {0xa6, 0xb3, 0xe2, 0x90, 0xfb, 0x3c, 0x0a, 0x22} }; #endif // JIT_EE_VERSIONING_GUID_H diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index 6e33d18389e3c8..5d9b722b39227e 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -1831,14 +1831,35 @@ 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 +#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 + // 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; + } +#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 info.returnValueLoc.storeVariableInRegisters(reg1, reg2); } @@ -1848,17 +1869,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 5214749d6b2eaa..52c9ca0cc12c4f 100644 --- a/src/coreclr/jit/codegeninterface.h +++ b/src/coreclr/jit/codegeninterface.h @@ -622,6 +622,8 @@ 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 storeVariableOnStack(regNumber stackBaseReg, NATIVE_OFFSET variableStackOffset); diff --git a/src/coreclr/jit/ee_il_dll.cpp b/src/coreclr/jit/ee_il_dll.cpp index cfe0b1b0a55522..4258e568e62075 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: @@ -915,15 +920,32 @@ 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"); } break; case CodeGenInterface::VLT_REG_REG: + { +#ifdef TARGET_AMD64 + auto toJitRegNum = [](ICorDebugInfo::RegNum reg) -> regNumber { + unsigned val = static_cast(reg); + unsigned fpFirst = static_cast(ICorDebugInfo::REGNUM_FP_FIRST); + if (val >= fpFirst) + { + return static_cast(REG_FP_FIRST + val - fpFirst); + } + return static_cast(reg); + }; + + 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 deb4bed05b6a00..debf14770c4a0c 100644 --- a/src/coreclr/jit/scopeinfo.cpp +++ b/src/coreclr/jit/scopeinfo.cpp @@ -148,8 +148,53 @@ 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 JIT register to encode. +// +// Return Value: +// The debug-info register number for reg. +// +// static +ICorDebugInfo::RegNum CodeGenInterface::siVarLoc::mapRegNumToDebugRegNum(regNumber reg) +{ +#ifdef TARGET_AMD64 + constexpr unsigned fpRegDebugNumBase = ICorDebugInfo::REGNUM_FP_FIRST; + constexpr unsigned maxEncodableFpRegs = 16; // Only XMM0-XMM15 are in RegNum +#else + constexpr unsigned fpRegDebugNumBase = 0; + constexpr unsigned maxEncodableFpRegs = 0; +#endif + + if (genIsValidIntReg(reg)) + { + return static_cast(reg); + } + + if (genIsValidFloatReg(reg)) + { + 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); + } + + // Mask registers (K0-K7) and any other non-int/non-float registers + // cannot be represented in the debug info encoding. + return ICorDebugInfo::REGNUM_COUNT; +} + +//------------------------------------------------------------------------ +// storeVariableInRegisters: Convert the siVarLoc instance into a register +// location using the given registers. // // Arguments: // reg - the first register where the variable is placed. @@ -158,21 +203,66 @@ bool CodeGenInterface::siVarLoc::vlIsOnStack() const // void CodeGenInterface::siVarLoc::storeVariableInRegisters(regNumber reg, regNumber otherReg) { - assert(genIsValidIntReg(reg)); - assert(otherReg == REG_NA || genIsValidIntReg(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) { - // Only one register is used - vlType = VLT_REG; - vlReg.vlrReg = reg; + if (genIsValidFloatReg(reg)) + { +#ifdef TARGET_AMD64 + 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 if (genIsValidIntReg(reg)) + { + vlType = VLT_REG; + vlReg.vlrReg = reg; + } + else + { + // Mask registers or other non-encodable register types. + vlType = VLT_INVALID; + return; + } } else { - // Two register are used +#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(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. + if (!genIsValidIntReg(reg) || !genIsValidIntReg(otherReg)) + { + vlType = VLT_INVALID; + return; + } vlType = VLT_REG_REG; vlRegReg.vlrrReg1 = reg; vlRegReg.vlrrReg2 = otherReg; +#endif } } @@ -412,11 +502,18 @@ 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. + { + 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 = (regNumber)(varDsc->GetRegNum() - REG_FP_FIRST); + this->vlReg.vlrReg = static_cast(debugReg); break; + } #else // !TARGET_64BIT @@ -443,11 +540,15 @@ void CodeGenInterface::siVarLoc::siFillRegisterVarLoc( case TYP_MASK: #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); + 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(debugReg); break; } #endif // FEATURE_SIMD @@ -525,7 +626,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) { @@ -533,6 +633,16 @@ void CodeGenInterface::dumpSiVarLoc(const siVarLoc* varLoc) const } break; + case VLT_REG_FP: +#ifdef TARGET_AMD64 + printf("%s", getRegName(static_cast(REG_FP_FIRST + varLoc->vlReg.vlrReg - + ICorDebugInfo::REGNUM_FP_FIRST))); +#else + // 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; + case VLT_STK: case VLT_STK_BYREF: if ((int)varLoc->vlStk.vlsBaseReg != (int)ICorDebugInfo::REGNUM_AMBIENT_SP) @@ -543,17 +653,36 @@ 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"); } break; -#ifndef TARGET_AMD64 case VLT_REG_REG: +#ifdef TARGET_AMD64 + { + // 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; + }; + + 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)); - break; +#endif + break; +#ifndef TARGET_AMD64 case VLT_REG_STK: if ((int)varLoc->vlRegStk.vlrsStk.vlrssBaseReg != (int)ICorDebugInfo::REGNUM_AMBIENT_SP) { @@ -1736,24 +1865,17 @@ void CodeGen::psiBegProlog() if (reg1 != REG_NA) { - if (genIsValidFloatReg(reg1)) + // 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)) { - // 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); + varLocation.storeVariableInRegisters(reg1, reg2); } 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); + varLocation.storeVariableOnStack(REG_SPBASE, psiGetVarStackOffset(lclVarDsc)); } } else @@ -1820,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 @@ -1831,6 +1953,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, @@ -1864,6 +1993,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, 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..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 @@ -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,38 @@ 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 + // 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 => 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 + }; + } + public void OpPiece(uint size = 0) { OpCode(DW_OP_piece); @@ -104,6 +152,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 @@ -114,16 +179,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 @@ -144,7 +213,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_COUNT + 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: @@ -158,7 +243,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: 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..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 @@ -512,26 +512,26 @@ 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(); } 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); 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(); 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, } }