From 8fa997bf0d26b10c94c1612da1f3b6541434549d Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Fri, 17 Jul 2026 12:40:41 -0400 Subject: [PATCH] Add ARM64 FP register debug info support Extend the ARM64 debug RegNum enumeration with V0-V31 and use the unified register encoding for single- and two-register FP locations. Update debugger decoding, JIT diagnostics, NativeAOT DWARF ambient-SP handling, and the JIT-EE version identifier. Only emit two-register FP homes when each register maps to an 8-byte piece; narrower HFAs and wider HVAs require piece-size metadata that the current representation cannot encode. Bump the breaking ReadyToRun format version so runtimes do not decode older ARM64 variable locations using the new RegNum meanings. Teach cDAC to read V0-V31 and only apply its SP fallback to the ambient-SP pseudo-register. Complete the ARM64 CodeView and R2R dump register mappings. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8afd92ce-0c28-4b64-b7b1-ee63f18e53ef --- src/coreclr/debug/di/rsthread.cpp | 16 ++++---- src/coreclr/debug/inc/arm64/primitives.h | 37 ++++++++++++++++++- src/coreclr/debug/inc/dbgipcevents.h | 2 +- src/coreclr/inc/cordebuginfo.h | 35 ++++++++++++++++++ src/coreclr/inc/jiteeversionguid.h | 10 ++--- src/coreclr/inc/readytorun.h | 5 ++- src/coreclr/jit/codegencommon.cpp | 24 +++++++++--- src/coreclr/jit/ee_il_dll.cpp | 6 ++- src/coreclr/jit/scopeinfo.cpp | 34 ++++++++++------- .../nativeaot/Runtime/inc/ModuleHeaders.h | 2 +- .../Common/Internal/Runtime/ModuleHeaders.cs | 2 +- .../CodeView/CodeViewSymbolsBuilder.cs | 12 ++++-- .../Dwarf/DwarfExpressionBuilder.cs | 2 +- .../Arm64/Registers.cs | 34 +++++++++++++++++ .../StackWalk/Context/ARM64Context.cs | 28 ++++++++++++++ .../ClrDataFrame.cs | 29 ++++++++++++--- .../tests/UnitTests/PlatformContextTests.cs | 22 ++++++++++- 17 files changed, 250 insertions(+), 50 deletions(-) diff --git a/src/coreclr/debug/di/rsthread.cpp b/src/coreclr/debug/di/rsthread.cpp index 378bdbc44510c8..08c760735f91a1 100644 --- a/src/coreclr/debug/di/rsthread.cpp +++ b/src/coreclr/debug/di/rsthread.cpp @@ -8359,12 +8359,12 @@ HRESULT CordbJITILFrame::GetNativeVariable(CordbType *type, case ICorDebugInfo::VLT_REG_FP: #if defined(TARGET_ARM) // @ARMTODO hr = E_NOTIMPL; -#elif defined(TARGET_AMD64) +#elif defined(TARGET_AMD64) || defined(TARGET_ARM64) + // AMD64/ARM64 enumerate the FP registers in the debug RegNum enum + // (XMM0-15 / V0-31), so g_JITToCorDbgReg maps vlrReg directly to the + // corresponding CorDebugRegister. 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, - type, ppValue); #elif defined(TARGET_LOONGARCH64) hr = m_nativeFrame->GetLocalFloatingPointValue(pNativeVarInfo->loc.vlReg.vlrReg + REGISTER_LOONGARCH64_F0, type, ppValue); @@ -8400,7 +8400,7 @@ HRESULT CordbJITILFrame::GetNativeVariable(CordbType *type, break; case ICorDebugInfo::VLT_REG_REG: -#if defined(TARGET_AMD64) +#if defined(TARGET_AMD64) || defined(TARGET_ARM64) { const ICorDebugInfo::RegNum lowReg = pNativeVarInfo->loc.vlRegReg.vlrrReg1; const ICorDebugInfo::RegNum highReg = pNativeVarInfo->loc.vlRegReg.vlrrReg2; @@ -8409,9 +8409,9 @@ HRESULT CordbJITILFrame::GetNativeVariable(CordbType *type, 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. + // AMD64/ARM64 extend RegNum with FP registers (XMM/V), so + // VLT_REG_REG can represent mixed int/fp pairs. FP register + // indices for GetLocalTwoRegisterValue are 0-based. hr = m_nativeFrame->GetLocalTwoRegisterValue( lowIsFloat ? lowReg - ICorDebugInfo::REGNUM_FP_FIRST : ConvertRegNumToCorDebugRegister(lowReg), diff --git a/src/coreclr/debug/inc/arm64/primitives.h b/src/coreclr/debug/inc/arm64/primitives.h index cb7553be3c075e..4a7c89ae67920e 100644 --- a/src/coreclr/debug/inc/arm64/primitives.h +++ b/src/coreclr/debug/inc/arm64/primitives.h @@ -62,6 +62,7 @@ inline CORDB_ADDRESS GetPatchEndAddr(CORDB_ADDRESS patchAddr) constexpr CorDebugRegister g_JITToCorDbgReg[] = { + // Integer registers (must match RegNum order in cordebuginfo.h) REGISTER_ARM64_X0, REGISTER_ARM64_X1, REGISTER_ARM64_X2, @@ -94,7 +95,41 @@ constexpr CorDebugRegister g_JITToCorDbgReg[] = REGISTER_ARM64_FP, REGISTER_ARM64_LR, REGISTER_ARM64_SP, - REGISTER_ARM64_PC + REGISTER_ARM64_PC, + + // FP/SIMD V registers (V0-V31, starting at REGNUM_FP_FIRST) + REGISTER_ARM64_V0, + REGISTER_ARM64_V1, + REGISTER_ARM64_V2, + REGISTER_ARM64_V3, + REGISTER_ARM64_V4, + REGISTER_ARM64_V5, + REGISTER_ARM64_V6, + REGISTER_ARM64_V7, + REGISTER_ARM64_V8, + REGISTER_ARM64_V9, + REGISTER_ARM64_V10, + REGISTER_ARM64_V11, + REGISTER_ARM64_V12, + REGISTER_ARM64_V13, + REGISTER_ARM64_V14, + REGISTER_ARM64_V15, + REGISTER_ARM64_V16, + REGISTER_ARM64_V17, + REGISTER_ARM64_V18, + REGISTER_ARM64_V19, + REGISTER_ARM64_V20, + REGISTER_ARM64_V21, + REGISTER_ARM64_V22, + REGISTER_ARM64_V23, + REGISTER_ARM64_V24, + REGISTER_ARM64_V25, + REGISTER_ARM64_V26, + REGISTER_ARM64_V27, + REGISTER_ARM64_V28, + REGISTER_ARM64_V29, + REGISTER_ARM64_V30, + REGISTER_ARM64_V31, }; inline void CORDbgSetIP(DT_CONTEXT *context, LPVOID eip) { diff --git a/src/coreclr/debug/inc/dbgipcevents.h b/src/coreclr/debug/inc/dbgipcevents.h index f66c68d17073c6..8a087492ca3d17 100644 --- a/src/coreclr/debug/inc/dbgipcevents.h +++ b/src/coreclr/debug/inc/dbgipcevents.h @@ -1449,7 +1449,7 @@ static_assert(DBG_TARGET_REGNUM_AMBIENT_SP == ICorDebugInfo::REGNUM_AMBIENT_SP); #endif // TARGET_ARM #elif defined(TARGET_ARM64) #define DBG_TARGET_REGNUM_SP 31 -#define DBG_TARGET_REGNUM_AMBIENT_SP 34 +#define DBG_TARGET_REGNUM_AMBIENT_SP 66 #ifdef TARGET_ARM64 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 f5975160b84a80..bc0d5cc5c9a69f 100644 --- a/src/coreclr/inc/cordebuginfo.h +++ b/src/coreclr/inc/cordebuginfo.h @@ -131,6 +131,41 @@ class ICorDebugInfo REGNUM_LR, REGNUM_SP, REGNUM_PC, + + // SIMD/FP V registers + REGNUM_FP_FIRST, + REGNUM_V0 = REGNUM_FP_FIRST, + REGNUM_V1, + REGNUM_V2, + REGNUM_V3, + REGNUM_V4, + REGNUM_V5, + REGNUM_V6, + REGNUM_V7, + REGNUM_V8, + REGNUM_V9, + REGNUM_V10, + REGNUM_V11, + REGNUM_V12, + REGNUM_V13, + REGNUM_V14, + REGNUM_V15, + REGNUM_V16, + REGNUM_V17, + REGNUM_V18, + REGNUM_V19, + REGNUM_V20, + REGNUM_V21, + REGNUM_V22, + REGNUM_V23, + REGNUM_V24, + REGNUM_V25, + REGNUM_V26, + REGNUM_V27, + REGNUM_V28, + REGNUM_V29, + REGNUM_V30, + REGNUM_V31, #elif TARGET_AMD64 REGNUM_RAX, REGNUM_RCX, diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index e48e375315e0f5..08a3bcde5744d6 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -37,11 +37,11 @@ #include -constexpr GUID JITEEVersionIdentifier = { /* 65743063-e8fa-41d4-9496-c436974c00f5 */ - 0x65743063, - 0xe8fa, - 0x41d4, - {0x94, 0x96, 0xc4, 0x36, 0x97, 0x4c, 0x00, 0xf5} +constexpr GUID JITEEVersionIdentifier = { /* 5bb9b1e5-9941-4762-a195-351b6a736588 */ + 0x5bb9b1e5, + 0x9941, + 0x4762, + {0xa1, 0x95, 0x35, 0x1b, 0x6a, 0x73, 0x65, 0x88} }; #endif // JIT_EE_VERSIONING_GUID_H diff --git a/src/coreclr/inc/readytorun.h b/src/coreclr/inc/readytorun.h index 0de724ff89d2bd..16eedd1c8a1829 100644 --- a/src/coreclr/inc/readytorun.h +++ b/src/coreclr/inc/readytorun.h @@ -19,10 +19,10 @@ // src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h // If you update this, ensure you run `git grep MINIMUM_READYTORUN_MAJOR_VERSION` // and handle pending work. -#define READYTORUN_MAJOR_VERSION 25 +#define READYTORUN_MAJOR_VERSION 26 #define READYTORUN_MINOR_VERSION 0x0000 -#define MINIMUM_READYTORUN_MAJOR_VERSION 25 +#define MINIMUM_READYTORUN_MAJOR_VERSION 26 // R2R Version 2.1 adds the InliningInfo section // R2R Version 2.2 adds the ProfileDataInfo section @@ -65,6 +65,7 @@ // R2R Version 23 changes delegate layout to have target before methodPtr // R2R Version 24 changes ARM32 virtual stub dispatch hidden parameter register to R12 // R2R Version 25 renames runtime async infrastructure members, makes thunk-used members NonVersionable, and frees up a flag in CorInfoContinuationFlags +// R2R Version 26 changes ARM64 NativeVarInfo register encoding to include V0-V31 struct READYTORUN_CORE_HEADER { diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index 5d9b722b39227e..7f3cdc29802214 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -1831,6 +1831,18 @@ void CodeGen::genEmitCallWithCurrentGC(EmitCallParams& params) regNumber reg1 = retDesc->GetABIReturnReg(0, call->GetUnmanagedCallConv()); regNumber reg2 = retDesc->GetABIReturnReg(1, call->GetUnmanagedCallConv()); +#ifdef TARGET_ARM64 + if ((!genIsValidIntReg(reg1) || !genIsValidIntReg(reg2)) && + (retDesc->GetReturnFieldOffset(1) != TARGET_POINTER_SIZE)) + { + // The two-register debug-info representation places the second + // register at the next pointer-sized offset. ARM64 HFAs/HVAs with + // smaller or larger elements use a different offset and cannot be + // represented without recording per-register piece sizes. + return; + } +#endif + #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 @@ -1850,11 +1862,13 @@ 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. +#elif !defined(TARGET_AMD64) && !defined(TARGET_ARM64) + // AMD64 and ARM64 include FP registers in their debug RegNum enumerations, + // so VLT_REG_REG can encode FP-containing two-register returns. Other + // 64-bit targets do not yet implement the debugger-side floating-point + // read path, so suppress those cases instead of emitting an encoding the + // debugger cannot decode. A pair of integer registers is still encoded + // below as VLT_REG_REG. if (!genIsValidIntReg(reg1) || !genIsValidIntReg(reg2)) { return; diff --git a/src/coreclr/jit/ee_il_dll.cpp b/src/coreclr/jit/ee_il_dll.cpp index 4258e568e62075..47af3c1f9b7e06 100644 --- a/src/coreclr/jit/ee_il_dll.cpp +++ b/src/coreclr/jit/ee_il_dll.cpp @@ -902,7 +902,9 @@ void Compiler::eeDispVar(ICorDebugInfo::NativeVarInfo* var) break; case CodeGenInterface::VLT_REG_FP: -#ifdef TARGET_AMD64 +#if defined(TARGET_AMD64) || defined(TARGET_ARM64) + // AMD64/ARM64 store the FP register as a debug RegNum (REGNUM_FP_FIRST-based); + // map it back to a JIT regNumber for display. printf("%s", getRegName(static_cast(REG_FP_FIRST + var->loc.vlReg.vlrReg - ICorDebugInfo::REGNUM_FP_FIRST))); #else @@ -928,7 +930,7 @@ void Compiler::eeDispVar(ICorDebugInfo::NativeVarInfo* var) case CodeGenInterface::VLT_REG_REG: { -#ifdef TARGET_AMD64 +#if defined(TARGET_AMD64) || defined(TARGET_ARM64) auto toJitRegNum = [](ICorDebugInfo::RegNum reg) -> regNumber { unsigned val = static_cast(reg); unsigned fpFirst = static_cast(ICorDebugInfo::REGNUM_FP_FIRST); diff --git a/src/coreclr/jit/scopeinfo.cpp b/src/coreclr/jit/scopeinfo.cpp index debf14770c4a0c..edae8a122f9693 100644 --- a/src/coreclr/jit/scopeinfo.cpp +++ b/src/coreclr/jit/scopeinfo.cpp @@ -160,9 +160,13 @@ bool CodeGenInterface::siVarLoc::vlIsOnStack() const // static ICorDebugInfo::RegNum CodeGenInterface::siVarLoc::mapRegNumToDebugRegNum(regNumber reg) { +#if defined(TARGET_AMD64) || defined(TARGET_ARM64) + constexpr unsigned fpRegDebugNumBase = ICorDebugInfo::REGNUM_FP_FIRST; #ifdef TARGET_AMD64 - constexpr unsigned fpRegDebugNumBase = ICorDebugInfo::REGNUM_FP_FIRST; - constexpr unsigned maxEncodableFpRegs = 16; // Only XMM0-XMM15 are in RegNum + constexpr unsigned maxEncodableFpRegs = 16; // Only XMM0-XMM15 +#else + constexpr unsigned maxEncodableFpRegs = 32; // V0-V31 +#endif #else constexpr unsigned fpRegDebugNumBase = 0; constexpr unsigned maxEncodableFpRegs = 0; @@ -176,9 +180,7 @@ ICorDebugInfo::RegNum CodeGenInterface::siVarLoc::mapRegNumToDebugRegNum(regNumb 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 defined(TARGET_AMD64) || defined(TARGET_ARM64) if (fpIndex >= maxEncodableFpRegs) { return ICorDebugInfo::REGNUM_COUNT; // sentinel: caller checks for this @@ -210,18 +212,22 @@ void CodeGenInterface::siVarLoc::storeVariableInRegisters(regNumber reg, regNumb { if (genIsValidFloatReg(reg)) { -#ifdef TARGET_AMD64 +#if defined(TARGET_AMD64) || defined(TARGET_ARM64) + // AMD64/ARM64 enumerate the FP registers in the debug RegNum enum + // (XMM0-15 / V0-31), so store the mapped RegNum. This keeps the single-FP + // encoding identical to getSiVarLoc and lets the DBI decode uniformly via + // ConvertRegNumToCorDebugRegister. ICorDebugInfo::RegNum debugReg = mapRegNumToDebugRegNum(reg); if (debugReg == ICorDebugInfo::REGNUM_COUNT) { - // XMM16+ cannot be encoded in the debug info. + // The FP register is not representable in the debug RegNum enum. 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) + // Other targets: store 0-based FP register index (DBI adds platform base) vlType = VLT_REG_FP; vlReg.vlrReg = static_cast(reg - REG_FP_FIRST); #endif @@ -240,7 +246,7 @@ void CodeGenInterface::siVarLoc::storeVariableInRegisters(regNumber reg, regNumb } else { -#ifdef TARGET_AMD64 +#if defined(TARGET_AMD64) || defined(TARGET_ARM64) ICorDebugInfo::RegNum debugReg1 = mapRegNumToDebugRegNum(reg); ICorDebugInfo::RegNum debugReg2 = mapRegNumToDebugRegNum(otherReg); if (debugReg1 == ICorDebugInfo::REGNUM_COUNT || debugReg2 == ICorDebugInfo::REGNUM_COUNT) @@ -252,8 +258,8 @@ void CodeGenInterface::siVarLoc::storeVariableInRegisters(regNumber reg, regNumb 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. + // Other non-AMD64 targets: 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; @@ -634,7 +640,9 @@ void CodeGenInterface::dumpSiVarLoc(const siVarLoc* varLoc) const break; case VLT_REG_FP: -#ifdef TARGET_AMD64 +#if defined(TARGET_AMD64) || defined(TARGET_ARM64) + // AMD64/ARM64 store the FP register as a debug RegNum (REGNUM_FP_FIRST-based); + // map it back to a JIT regNumber for display. printf("%s", getRegName(static_cast(REG_FP_FIRST + varLoc->vlReg.vlrReg - ICorDebugInfo::REGNUM_FP_FIRST))); #else @@ -660,7 +668,7 @@ void CodeGenInterface::dumpSiVarLoc(const siVarLoc* varLoc) const break; case VLT_REG_REG: -#ifdef TARGET_AMD64 +#if defined(TARGET_AMD64) || defined(TARGET_ARM64) { // Map RegNum values (which may include FP register indices) back to // JIT regNumber for display purposes. diff --git a/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h b/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h index 9e8e5b1f3d2afe..573be41688fe44 100644 --- a/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h +++ b/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h @@ -11,7 +11,7 @@ struct ReadyToRunHeaderConstants { static const uint32_t Signature = 0x00525452; // 'RTR' - static const uint32_t CurrentMajorVersion = 25; + static const uint32_t CurrentMajorVersion = 26; static const uint32_t CurrentMinorVersion = 0; }; diff --git a/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs b/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs index 64890296b5eba9..acf200fdd2f339 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs @@ -15,7 +15,7 @@ internal struct ReadyToRunHeaderConstants { public const uint Signature = 0x00525452; // 'RTR' - public const ushort CurrentMajorVersion = 25; + public const ushort CurrentMajorVersion = 26; public const ushort CurrentMinorVersion = 0; } #if READYTORUN 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..3f26c367619664 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 @@ -100,10 +100,16 @@ private CodeViewRegister GetCVRegNum(uint regNum) }; case TargetArchitecture.ARM64: - // X0-X28, FP, LR, SP have same order - if (regNum <= 32) + // X0-X28, FP, LR, SP have the same order. + if (regNum <= 31) return (CodeViewRegister)(regNum + (uint)CV_ARM64_X0); - // TODO: Floating point + + if (regNum == 32) + return CV_ARM64_PC; + + if (regNum <= 64) + return (CodeViewRegister)(regNum - 33 + (uint)CV_ARM64_Q0); + return CV_REG_NONE; default: 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 44600c8861d6d6..7518d0d9e1df47 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 @@ -94,7 +94,7 @@ private static int AmbientSpRegNum(TargetArchitecture architecture) { 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.ARM64 => 66, // 33 int registers + 32 V registers, +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 diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Arm64/Registers.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Arm64/Registers.cs index 0fc02d1ba595bc..2d5e8d1cf4a87c 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Arm64/Registers.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Arm64/Registers.cs @@ -40,5 +40,39 @@ public enum Registers X28, X29, X30, + SP, + PC, + V0, + V1, + V2, + V3, + V4, + V5, + V6, + V7, + V8, + V9, + V10, + V11, + V12, + V13, + V14, + V15, + V16, + V17, + V18, + V19, + V20, + V21, + V22, + V23, + V24, + V25, + V26, + V27, + V28, + V29, + V30, + V31, } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64Context.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64Context.cs index 8016c0ac1765a2..df68a698e77094 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64Context.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64Context.cs @@ -158,6 +158,12 @@ public bool TryReadRegister(string name, out TargetNUInt value) public bool TrySetRegister(int number, TargetNUInt value) { + if ((uint)(number - 33) < 32) + { + SetVectorRegister(number - 33, value); + return true; + } + switch (number) { case 0: X0 = value.Value; return true; @@ -199,6 +205,12 @@ public bool TrySetRegister(int number, TargetNUInt value) public bool TryReadRegister(int number, out TargetNUInt value) { + if ((uint)(number - 33) < 32) + { + value = ReadVectorRegister(number - 33); + return true; + } + switch (number) { case 0: value = new TargetNUInt(X0); return true; @@ -238,6 +250,22 @@ public bool TryReadRegister(int number, out TargetNUInt value) } } + private unsafe void SetVectorRegister(int index, TargetNUInt value) + { + fixed (ulong* registers = V) + { + registers[index * 2] = value.Value; + } + } + + private readonly unsafe TargetNUInt ReadVectorRegister(int index) + { + fixed (ulong* registers = V) + { + return new TargetNUInt(registers[index * 2]); + } + } + // Control flags [FieldOffset(0x0)] diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataFrame.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataFrame.cs index 1052e9519020cd..c76be00dbb2b03 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataFrame.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataFrame.cs @@ -948,16 +948,33 @@ private static ulong ReadRegister(IPlatformAgnosticContext context, Target targe if (context.TryReadRegister((int)registerNumber, out TargetNUInt value)) return value.Value; - // REGNUM_AMBIENT_SP is beyond the normal register range on every architecture. - // It represents the entry-time SP, not necessarily the current SP. - // Map it to the stack pointer as a best-effort approximation (see util.cpp). - int spRegisterNumber = GetStackPointerRegisterNumber(target); - if (spRegisterNumber >= 0 && context.TryReadRegister(spRegisterNumber, out value)) - return value.Value; + if (registerNumber == GetAmbientStackPointerRegisterNumber(target)) + { + // REGNUM_AMBIENT_SP represents the entry-time SP, not necessarily the + // current SP. Map it to SP as a best-effort approximation (see util.cpp). + int spRegisterNumber = GetStackPointerRegisterNumber(target); + if (spRegisterNumber >= 0 && context.TryReadRegister(spRegisterNumber, out value)) + return value.Value; + } return 0; } + private static uint GetAmbientStackPointerRegisterNumber(Target target) + { + RuntimeInfoArchitecture arch = target.Contracts.RuntimeInfo.GetTargetArchitecture(); + return arch switch + { + RuntimeInfoArchitecture.X64 => 33, + RuntimeInfoArchitecture.X86 => 9, + RuntimeInfoArchitecture.Arm64 => 66, + RuntimeInfoArchitecture.Arm => 17, + RuntimeInfoArchitecture.LoongArch64 => 34, + RuntimeInfoArchitecture.RiscV64 => 34, + _ => uint.MaxValue, + }; + } + private static int GetStackPointerRegisterNumber(Target target) { RuntimeInfoArchitecture arch = target.Contracts.RuntimeInfo.GetTargetArchitecture(); diff --git a/src/native/managed/cdac/tests/UnitTests/PlatformContextTests.cs b/src/native/managed/cdac/tests/UnitTests/PlatformContextTests.cs index 3adbf371674f94..1c8ff03b5ccb20 100644 --- a/src/native/managed/cdac/tests/UnitTests/PlatformContextTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/PlatformContextTests.cs @@ -36,6 +36,8 @@ public void AMD64_OutOfRange_ReturnsFalse(int regNum) [InlineData(29, 0xABCDUL)] // Fp [InlineData(31, 0x5678UL)] // Sp [InlineData(32, 0x9000UL)] // Pc + [InlineData(33, 0x1122334455667788UL)] // V0, low 64 bits + [InlineData(64, 0x8877665544332211UL)] // V31, low 64 bits public void ARM64_TrySetAndRead_ByNumber_RoundTrips(int regNum, ulong testValue) { var ctx = new ARM64Context(); @@ -45,7 +47,8 @@ public void ARM64_TrySetAndRead_ByNumber_RoundTrips(int regNum, ulong testValue) } [Theory] - [InlineData(33)] + [InlineData(65)] // REGNUM_COUNT + [InlineData(66)] // REGNUM_AMBIENT_SP public void ARM64_OutOfRange_ReturnsFalse(int regNum) { var ctx = new ARM64Context(); @@ -53,6 +56,23 @@ public void ARM64_OutOfRange_ReturnsFalse(int regNum) Assert.False(ctx.TryReadRegister(regNum, out _)); } + [Fact] + public unsafe void ARM64_VectorRegisterNumber_UsesLow64Bits() + { + var ctx = new ARM64Context(); + ulong* registers = ctx.V; + registers[1] = 0x1111; + registers[3] = 0x3333; + registers[4] = 0x4444; + + Assert.True(ctx.TrySetRegister(34, new TargetNUInt(0x2222))); + + Assert.Equal(0x1111UL, registers[1]); + Assert.Equal(0x2222UL, registers[2]); + Assert.Equal(0x3333UL, registers[3]); + Assert.Equal(0x4444UL, registers[4]); + } + [Theory] [InlineData(0, 0x12U)] // R0 [InlineData(13, 0x100U)] // Sp