From 99fed788905d22bb27558adc6313abf8cc16bba4 Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Fri, 26 Jun 2026 11:17:41 -0400 Subject: [PATCH 1/2] 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 2/2] 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;