From c7096aa246cb00b8182c559d57c7b8568a168142 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 30 Mar 2026 19:22:00 +0000 Subject: [PATCH 1/5] Initial plan From 16d85fca7da47bac3c9b52ec73a58a0c14db8c3e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 30 Mar 2026 19:49:53 +0000 Subject: [PATCH 2/5] Add NULL guard in ReportRegisterToGC for FEATURE_NATIVEAOT to fix crossgen2 GC crash In NativeAOT, volatile register save locations can be NULL when the register was not captured (e.g., in a voluntary preemptive transition frame that only saves callee-saved registers). Previously, GetRegisterSlot() could return NULL for such registers, which would then be passed directly to the GC callback, causing a NULL pointer dereference in GCHeap::Promote. This fix adds a NULL check for FEATURE_NATIVEAOT in all ReportRegisterToGC implementations (AMD64, ARM, ARM64, LoongArch64, RISCV64). When pObjRef is NULL, we skip reporting the register slot to avoid the crash. Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/3eb91761-0dc9-4a8e-a3b5-97b5d6ae9619 Co-authored-by: mangod9 <61718172+mangod9@users.noreply.github.com> --- src/coreclr/vm/gcinfodecoder.cpp | 47 ++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/src/coreclr/vm/gcinfodecoder.cpp b/src/coreclr/vm/gcinfodecoder.cpp index 82842705d217f7..e5aaaba6b38b79 100644 --- a/src/coreclr/vm/gcinfodecoder.cpp +++ b/src/coreclr/vm/gcinfodecoder.cpp @@ -1559,7 +1559,13 @@ template void TGcInfoDecoder::ReportRe LOG((LF_GCROOTS, LL_INFO1000, "Reporting " FMT_REG, regNum )); OBJECTREF* pObjRef = GetRegisterSlot( regNum, pRD ); -#if defined(TARGET_UNIX) && !defined(FEATURE_NATIVEAOT) && !defined(SOS_TARGET_AMD64) +#ifdef FEATURE_NATIVEAOT + // In NativeAOT, volatile register save locations can be NULL if the register was not + // captured (e.g., in a voluntary preemptive transition frame that only saves callee-saved + // registers). Skip reporting if the save location is not available. + if (pObjRef == NULL) + return; +#elif defined(TARGET_UNIX) && !defined(SOS_TARGET_AMD64) // On PAL, we don't always have the context pointers available due to // a limitation of an unwinding library. In such case, the context // pointers for some nonvolatile registers are NULL. @@ -1579,7 +1585,7 @@ template void TGcInfoDecoder::ReportRe gcFlags |= GC_CALL_PINNED; } -#endif // TARGET_UNIX && !FEATURE_NATIVEAOT && !SOS_TARGET_AMD64 +#endif // FEATURE_NATIVEAOT || (TARGET_UNIX && !SOS_TARGET_AMD64) #ifdef _DEBUG if(IsScratchRegister(regNum, pRD)) @@ -1688,6 +1694,13 @@ template void TGcInfoDecoder::ReportRe LOG((LF_GCROOTS, LL_INFO1000, "Reporting " FMT_REG, regNum )); OBJECTREF* pObjRef = GetRegisterSlot( regNum, pRD ); +#ifdef FEATURE_NATIVEAOT + // In NativeAOT, volatile register save locations can be NULL if the register was not + // captured (e.g., in a voluntary preemptive transition frame that only saves callee-saved + // registers). Skip reporting if the save location is not available. + if (pObjRef == NULL) + return; +#endif // FEATURE_NATIVEAOT #ifdef _DEBUG if(IsScratchRegister(regNum, pRD)) @@ -1770,7 +1783,13 @@ template void TGcInfoDecoder::ReportRe LOG((LF_GCROOTS, LL_INFO1000, "Reporting " FMT_REG, regNum )); OBJECTREF* pObjRef = GetRegisterSlot( regNum, pRD ); -#if defined(TARGET_UNIX) && !defined(FEATURE_NATIVEAOT) && !defined(SOS_TARGET_ARM64) +#ifdef FEATURE_NATIVEAOT + // In NativeAOT, volatile register save locations can be NULL if the register was not + // captured (e.g., in a voluntary preemptive transition frame that only saves callee-saved + // registers). Skip reporting if the save location is not available. + if (pObjRef == NULL) + return; +#elif defined(TARGET_UNIX) && !defined(SOS_TARGET_ARM64) // On PAL, we don't always have the context pointers available due to // a limitation of an unwinding library. In such case, the context // pointers for some nonvolatile registers are NULL. @@ -1790,7 +1809,7 @@ template void TGcInfoDecoder::ReportRe gcFlags |= GC_CALL_PINNED; } -#endif // TARGET_UNIX && !SOS_TARGET_ARM64 +#endif // FEATURE_NATIVEAOT || (TARGET_UNIX && !SOS_TARGET_ARM64) #ifdef _DEBUG if(IsScratchRegister(regNum, pRD)) @@ -1909,7 +1928,13 @@ template void TGcInfoDecoder::ReportRe LOG((LF_GCROOTS, LL_INFO1000, "Reporting " FMT_REG, regNum )); OBJECTREF* pObjRef = GetRegisterSlot( regNum, pRD ); -#if defined(TARGET_UNIX) && !defined(FEATURE_NATIVEAOT) && !defined(SOS_TARGET_LOONGARCH64) +#ifdef FEATURE_NATIVEAOT + // In NativeAOT, volatile register save locations can be NULL if the register was not + // captured (e.g., in a voluntary preemptive transition frame that only saves callee-saved + // registers). Skip reporting if the save location is not available. + if (pObjRef == NULL) + return; +#elif defined(TARGET_UNIX) && !defined(SOS_TARGET_LOONGARCH64) // On PAL, we don't always have the context pointers available due to // a limitation of an unwinding library. In such case, the context @@ -1930,7 +1955,7 @@ template void TGcInfoDecoder::ReportRe gcFlags |= GC_CALL_PINNED; } -#endif // TARGET_UNIX && !SOS_TARGET_LOONGARCH64 +#endif // FEATURE_NATIVEAOT || (TARGET_UNIX && !SOS_TARGET_LOONGARCH64) #ifdef _DEBUG if(IsScratchRegister(regNum, pRD)) @@ -2033,7 +2058,13 @@ template void TGcInfoDecoder::ReportRe LOG((LF_GCROOTS, LL_INFO1000, "Reporting " FMT_REG, regNum )); OBJECTREF* pObjRef = GetRegisterSlot( regNum, pRD ); -#if defined(TARGET_UNIX) && !defined(FEATURE_NATIVEAOT) && !defined(SOS_TARGET_RISCV64) +#ifdef FEATURE_NATIVEAOT + // In NativeAOT, volatile register save locations can be NULL if the register was not + // captured (e.g., in a voluntary preemptive transition frame that only saves callee-saved + // registers). Skip reporting if the save location is not available. + if (pObjRef == NULL) + return; +#elif defined(TARGET_UNIX) && !defined(SOS_TARGET_RISCV64) // On PAL, we don't always have the context pointers available due to // a limitation of an unwinding library. In such case, the context @@ -2054,7 +2085,7 @@ template void TGcInfoDecoder::ReportRe gcFlags |= GC_CALL_PINNED; } -#endif // TARGET_UNIX && !SOS_TARGET_RISCV64 +#endif // FEATURE_NATIVEAOT || (TARGET_UNIX && !SOS_TARGET_RISCV64) #ifdef _DEBUG if(IsScratchRegister(regNum, pRD)) From 1174f74e43a5dcf45b8642e1071cc7bed4815cd9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 30 Mar 2026 19:55:33 +0000 Subject: [PATCH 3/5] Fix inaccurate endif comments in gcinfodecoder.cpp Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/3eb91761-0dc9-4a8e-a3b5-97b5d6ae9619 Co-authored-by: mangod9 <61718172+mangod9@users.noreply.github.com> --- src/coreclr/vm/gcinfodecoder.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreclr/vm/gcinfodecoder.cpp b/src/coreclr/vm/gcinfodecoder.cpp index e5aaaba6b38b79..5686a323ff87d3 100644 --- a/src/coreclr/vm/gcinfodecoder.cpp +++ b/src/coreclr/vm/gcinfodecoder.cpp @@ -1585,7 +1585,7 @@ template void TGcInfoDecoder::ReportRe gcFlags |= GC_CALL_PINNED; } -#endif // FEATURE_NATIVEAOT || (TARGET_UNIX && !SOS_TARGET_AMD64) +#endif // FEATURE_NATIVEAOT, or TARGET_UNIX && !SOS_TARGET_AMD64 #ifdef _DEBUG if(IsScratchRegister(regNum, pRD)) @@ -1809,7 +1809,7 @@ template void TGcInfoDecoder::ReportRe gcFlags |= GC_CALL_PINNED; } -#endif // FEATURE_NATIVEAOT || (TARGET_UNIX && !SOS_TARGET_ARM64) +#endif // FEATURE_NATIVEAOT, or TARGET_UNIX && !SOS_TARGET_ARM64 #ifdef _DEBUG if(IsScratchRegister(regNum, pRD)) @@ -1955,7 +1955,7 @@ template void TGcInfoDecoder::ReportRe gcFlags |= GC_CALL_PINNED; } -#endif // FEATURE_NATIVEAOT || (TARGET_UNIX && !SOS_TARGET_LOONGARCH64) +#endif // FEATURE_NATIVEAOT, or TARGET_UNIX && !SOS_TARGET_LOONGARCH64 #ifdef _DEBUG if(IsScratchRegister(regNum, pRD)) @@ -2085,7 +2085,7 @@ template void TGcInfoDecoder::ReportRe gcFlags |= GC_CALL_PINNED; } -#endif // FEATURE_NATIVEAOT || (TARGET_UNIX && !SOS_TARGET_RISCV64) +#endif // FEATURE_NATIVEAOT, or TARGET_UNIX && !SOS_TARGET_RISCV64 #ifdef _DEBUG if(IsScratchRegister(regNum, pRD)) From a0a8f2144d0b0831f1894143258b6d451acf8a6b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 30 Mar 2026 22:53:31 +0000 Subject: [PATCH 4/5] Fix NativeAOT GC crash: populate volatile arg registers from UniversalTransitionThunk in UnwindUniversalTransitionThunk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The crash occurs when a thread is hijacked while inside RhpUniversalTransition (at ReturnFromUniversalTransitionTailCall). The thunk saves the managed caller's argument registers (rcx/rdx/r8/r9 on AMD64) in its frame's m_intArgRegs, but UnwindUniversalTransitionThunk() only called UnwindNonVolatileRegisters() which is a no-op for AMD64, leaving those register save-locations NULL in REGDISPLAY. When the GC info for the managed caller frame marks volatile registers as live, ReportRegisterToGC → GetRegisterSlot() returns NULL → GCHeap::Promote crashes with a NULL dereference. The fix adds UnwindVolatileArgRegisters() to UniversalTransitionStackFrame for all architectures, which sets the volatile argument register pointers in REGDISPLAY to point at the thunk's saved copies. This allows the GC to precisely track and update live GC references through the thunk frame, preventing both the crash and the silent corruption that would occur if references were silently skipped. Also reverts the previous incorrect NULL guard approach in gcinfodecoder.cpp. Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/77031b03-c66f-4230-908b-8b64a5d88cd8 Co-authored-by: mangod9 <61718172+mangod9@users.noreply.github.com> --- .../nativeaot/Runtime/StackFrameIterator.cpp | 92 +++++++++++++++++++ src/coreclr/vm/gcinfodecoder.cpp | 47 ++-------- 2 files changed, 100 insertions(+), 39 deletions(-) diff --git a/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp b/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp index a9831ab9924d3e..bc1a1762a4693c 100644 --- a/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp +++ b/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp @@ -1370,6 +1370,18 @@ struct UniversalTransitionStackFrame UNREFERENCED_PARAMETER(pRegisterSet); } + void UnwindVolatileArgRegisters(REGDISPLAY * pRegisterSet) + { + // Restore the volatile argument registers that the thunk saved, so that the GC + // can find and update any live GC references in them for the managed caller frame. + pRegisterSet->pRdi = GET_POINTER_TO_FIELD(m_intArgRegs[0]); // rdi (arg 0) + pRegisterSet->pRsi = GET_POINTER_TO_FIELD(m_intArgRegs[1]); // rsi (arg 1) + pRegisterSet->pRcx = GET_POINTER_TO_FIELD(m_intArgRegs[2]); // rcx (arg 2) + pRegisterSet->pRdx = GET_POINTER_TO_FIELD(m_intArgRegs[3]); // rdx (arg 3) + pRegisterSet->pR8 = GET_POINTER_TO_FIELD(m_intArgRegs[4]); // r8 (arg 4) + pRegisterSet->pR9 = GET_POINTER_TO_FIELD(m_intArgRegs[5]); // r9 (arg 5) + } + #elif defined(TARGET_AMD64) // Conservative GC reporting must be applied to everything between the base of the @@ -1394,6 +1406,16 @@ struct UniversalTransitionStackFrame UNREFERENCED_PARAMETER(pRegisterSet); } + void UnwindVolatileArgRegisters(REGDISPLAY * pRegisterSet) + { + // Restore the volatile argument registers that the thunk saved, so that the GC + // can find and update any live GC references in them for the managed caller frame. + pRegisterSet->pRcx = GET_POINTER_TO_FIELD(m_intArgRegs[0]); // rcx (arg 0) + pRegisterSet->pRdx = GET_POINTER_TO_FIELD(m_intArgRegs[1]); // rdx (arg 1) + pRegisterSet->pR8 = GET_POINTER_TO_FIELD(m_intArgRegs[2]); // r8 (arg 2) + pRegisterSet->pR9 = GET_POINTER_TO_FIELD(m_intArgRegs[3]); // r9 (arg 3) + } + #elif defined(TARGET_ARM) // Conservative GC reporting must be applied to everything between the base of the @@ -1416,6 +1438,16 @@ struct UniversalTransitionStackFrame pRegisterSet->pR11 = GET_POINTER_TO_FIELD(m_pushedR11); } + void UnwindVolatileArgRegisters(REGDISPLAY * pRegisterSet) + { + // Restore the volatile argument registers that the thunk saved, so that the GC + // can find and update any live GC references in them for the managed caller frame. + pRegisterSet->pR0 = GET_POINTER_TO_FIELD(m_intArgRegs[0]); // r0 (arg 0) + pRegisterSet->pR1 = GET_POINTER_TO_FIELD(m_intArgRegs[1]); // r1 (arg 1) + pRegisterSet->pR2 = GET_POINTER_TO_FIELD(m_intArgRegs[2]); // r2 (arg 2) + pRegisterSet->pR3 = GET_POINTER_TO_FIELD(m_intArgRegs[3]); // r3 (arg 3) + } + #elif defined(TARGET_X86) // Conservative GC reporting must be applied to everything between the base of the @@ -1437,6 +1469,15 @@ struct UniversalTransitionStackFrame pRegisterSet->pRbp = GET_POINTER_TO_FIELD(m_pushedEBP); } + void UnwindVolatileArgRegisters(REGDISPLAY * pRegisterSet) + { + // Restore the volatile argument registers that the thunk saved, so that the GC + // can find and update any live GC references in them for the managed caller frame. + // Note: the thunk saves edx first (lower address), then ecx. + pRegisterSet->pRdx = GET_POINTER_TO_FIELD(m_intArgRegs[0]); // edx (arg 1 in __fastcall) + pRegisterSet->pRcx = GET_POINTER_TO_FIELD(m_intArgRegs[1]); // ecx (arg 0 in __fastcall) + } + #elif defined(TARGET_ARM64) // Conservative GC reporting must be applied to everything between the base of the @@ -1460,6 +1501,21 @@ struct UniversalTransitionStackFrame pRegisterSet->pFP = GET_POINTER_TO_FIELD(m_pushedFP); } + void UnwindVolatileArgRegisters(REGDISPLAY * pRegisterSet) + { + // Restore the volatile argument registers that the thunk saved, so that the GC + // can find and update any live GC references in them for the managed caller frame. + pRegisterSet->pX0 = GET_POINTER_TO_FIELD(m_intArgRegs[0]); // x0 (arg 0) + pRegisterSet->pX1 = GET_POINTER_TO_FIELD(m_intArgRegs[1]); // x1 (arg 1) + pRegisterSet->pX2 = GET_POINTER_TO_FIELD(m_intArgRegs[2]); // x2 (arg 2) + pRegisterSet->pX3 = GET_POINTER_TO_FIELD(m_intArgRegs[3]); // x3 (arg 3) + pRegisterSet->pX4 = GET_POINTER_TO_FIELD(m_intArgRegs[4]); // x4 (arg 4) + pRegisterSet->pX5 = GET_POINTER_TO_FIELD(m_intArgRegs[5]); // x5 (arg 5) + pRegisterSet->pX6 = GET_POINTER_TO_FIELD(m_intArgRegs[6]); // x6 (arg 6) + pRegisterSet->pX7 = GET_POINTER_TO_FIELD(m_intArgRegs[7]); // x7 (arg 7) + pRegisterSet->pX8 = GET_POINTER_TO_FIELD(m_intArgRegs[8]); // x8 (indirect result) + } + #elif defined(TARGET_LOONGARCH64) // Conservative GC reporting must be applied to everything between the base of the @@ -1482,6 +1538,21 @@ struct UniversalTransitionStackFrame pRegisterSet->pFP = GET_POINTER_TO_FIELD(m_pushedFP); } + void UnwindVolatileArgRegisters(REGDISPLAY * pRegisterSet) + { + // Restore the volatile argument registers that the thunk saved, so that the GC + // can find and update any live GC references in them for the managed caller frame. + // In LoongArch64 ABI, a0-a7 are r4-r11. + pRegisterSet->pR4 = GET_POINTER_TO_FIELD(m_intArgRegs[0]); // a0/r4 (arg 0) + pRegisterSet->pR5 = GET_POINTER_TO_FIELD(m_intArgRegs[1]); // a1/r5 (arg 1) + pRegisterSet->pR6 = GET_POINTER_TO_FIELD(m_intArgRegs[2]); // a2/r6 (arg 2) + pRegisterSet->pR7 = GET_POINTER_TO_FIELD(m_intArgRegs[3]); // a3/r7 (arg 3) + pRegisterSet->pR8 = GET_POINTER_TO_FIELD(m_intArgRegs[4]); // a4/r8 (arg 4) + pRegisterSet->pR9 = GET_POINTER_TO_FIELD(m_intArgRegs[5]); // a5/r9 (arg 5) + pRegisterSet->pR10 = GET_POINTER_TO_FIELD(m_intArgRegs[6]); // a6/r10 (arg 6) + pRegisterSet->pR11 = GET_POINTER_TO_FIELD(m_intArgRegs[7]); // a7/r11 (arg 7) + } + #elif defined(TARGET_RISCV64) // Conservative GC reporting must be applied to everything between the base of the @@ -1504,6 +1575,20 @@ struct UniversalTransitionStackFrame pRegisterSet->pFP = GET_POINTER_TO_FIELD(m_pushedFP); } + void UnwindVolatileArgRegisters(REGDISPLAY * pRegisterSet) + { + // Restore the volatile argument registers that the thunk saved, so that the GC + // can find and update any live GC references in them for the managed caller frame. + pRegisterSet->pA0 = GET_POINTER_TO_FIELD(m_intArgRegs[0]); // a0 (arg 0) + pRegisterSet->pA1 = GET_POINTER_TO_FIELD(m_intArgRegs[1]); // a1 (arg 1) + pRegisterSet->pA2 = GET_POINTER_TO_FIELD(m_intArgRegs[2]); // a2 (arg 2) + pRegisterSet->pA3 = GET_POINTER_TO_FIELD(m_intArgRegs[3]); // a3 (arg 3) + pRegisterSet->pA4 = GET_POINTER_TO_FIELD(m_intArgRegs[4]); // a4 (arg 4) + pRegisterSet->pA5 = GET_POINTER_TO_FIELD(m_intArgRegs[5]); // a5 (arg 5) + pRegisterSet->pA6 = GET_POINTER_TO_FIELD(m_intArgRegs[6]); // a6 (arg 6) + pRegisterSet->pA7 = GET_POINTER_TO_FIELD(m_intArgRegs[7]); // a7 (arg 7) + } + #elif defined(TARGET_WASM) private: // WASMTODO: #error NYI for this arch @@ -1518,6 +1603,12 @@ struct UniversalTransitionStackFrame UNREFERENCED_PARAMETER(pRegisterSet); PORTABILITY_ASSERT("@TODO: FIXME:WASM"); } + + void UnwindVolatileArgRegisters(REGDISPLAY * pRegisterSet) + { + UNREFERENCED_PARAMETER(pRegisterSet); + PORTABILITY_ASSERT("@TODO: FIXME:WASM"); + } #else #error NYI for this arch #endif @@ -1551,6 +1642,7 @@ void StackFrameIterator::UnwindUniversalTransitionThunk() UniversalTransitionStackFrame * stackFrame = (PTR_UniversalTransitionStackFrame)m_RegDisplay.SP; stackFrame->UnwindNonVolatileRegisters(&m_RegDisplay); + stackFrame->UnwindVolatileArgRegisters(&m_RegDisplay); PTR_uintptr_t addressOfPushedCallerIP = stackFrame->get_AddressOfPushedCallerIP(); m_RegDisplay.SetIP(PCODEToPINSTR(*addressOfPushedCallerIP)); diff --git a/src/coreclr/vm/gcinfodecoder.cpp b/src/coreclr/vm/gcinfodecoder.cpp index 5686a323ff87d3..82842705d217f7 100644 --- a/src/coreclr/vm/gcinfodecoder.cpp +++ b/src/coreclr/vm/gcinfodecoder.cpp @@ -1559,13 +1559,7 @@ template void TGcInfoDecoder::ReportRe LOG((LF_GCROOTS, LL_INFO1000, "Reporting " FMT_REG, regNum )); OBJECTREF* pObjRef = GetRegisterSlot( regNum, pRD ); -#ifdef FEATURE_NATIVEAOT - // In NativeAOT, volatile register save locations can be NULL if the register was not - // captured (e.g., in a voluntary preemptive transition frame that only saves callee-saved - // registers). Skip reporting if the save location is not available. - if (pObjRef == NULL) - return; -#elif defined(TARGET_UNIX) && !defined(SOS_TARGET_AMD64) +#if defined(TARGET_UNIX) && !defined(FEATURE_NATIVEAOT) && !defined(SOS_TARGET_AMD64) // On PAL, we don't always have the context pointers available due to // a limitation of an unwinding library. In such case, the context // pointers for some nonvolatile registers are NULL. @@ -1585,7 +1579,7 @@ template void TGcInfoDecoder::ReportRe gcFlags |= GC_CALL_PINNED; } -#endif // FEATURE_NATIVEAOT, or TARGET_UNIX && !SOS_TARGET_AMD64 +#endif // TARGET_UNIX && !FEATURE_NATIVEAOT && !SOS_TARGET_AMD64 #ifdef _DEBUG if(IsScratchRegister(regNum, pRD)) @@ -1694,13 +1688,6 @@ template void TGcInfoDecoder::ReportRe LOG((LF_GCROOTS, LL_INFO1000, "Reporting " FMT_REG, regNum )); OBJECTREF* pObjRef = GetRegisterSlot( regNum, pRD ); -#ifdef FEATURE_NATIVEAOT - // In NativeAOT, volatile register save locations can be NULL if the register was not - // captured (e.g., in a voluntary preemptive transition frame that only saves callee-saved - // registers). Skip reporting if the save location is not available. - if (pObjRef == NULL) - return; -#endif // FEATURE_NATIVEAOT #ifdef _DEBUG if(IsScratchRegister(regNum, pRD)) @@ -1783,13 +1770,7 @@ template void TGcInfoDecoder::ReportRe LOG((LF_GCROOTS, LL_INFO1000, "Reporting " FMT_REG, regNum )); OBJECTREF* pObjRef = GetRegisterSlot( regNum, pRD ); -#ifdef FEATURE_NATIVEAOT - // In NativeAOT, volatile register save locations can be NULL if the register was not - // captured (e.g., in a voluntary preemptive transition frame that only saves callee-saved - // registers). Skip reporting if the save location is not available. - if (pObjRef == NULL) - return; -#elif defined(TARGET_UNIX) && !defined(SOS_TARGET_ARM64) +#if defined(TARGET_UNIX) && !defined(FEATURE_NATIVEAOT) && !defined(SOS_TARGET_ARM64) // On PAL, we don't always have the context pointers available due to // a limitation of an unwinding library. In such case, the context // pointers for some nonvolatile registers are NULL. @@ -1809,7 +1790,7 @@ template void TGcInfoDecoder::ReportRe gcFlags |= GC_CALL_PINNED; } -#endif // FEATURE_NATIVEAOT, or TARGET_UNIX && !SOS_TARGET_ARM64 +#endif // TARGET_UNIX && !SOS_TARGET_ARM64 #ifdef _DEBUG if(IsScratchRegister(regNum, pRD)) @@ -1928,13 +1909,7 @@ template void TGcInfoDecoder::ReportRe LOG((LF_GCROOTS, LL_INFO1000, "Reporting " FMT_REG, regNum )); OBJECTREF* pObjRef = GetRegisterSlot( regNum, pRD ); -#ifdef FEATURE_NATIVEAOT - // In NativeAOT, volatile register save locations can be NULL if the register was not - // captured (e.g., in a voluntary preemptive transition frame that only saves callee-saved - // registers). Skip reporting if the save location is not available. - if (pObjRef == NULL) - return; -#elif defined(TARGET_UNIX) && !defined(SOS_TARGET_LOONGARCH64) +#if defined(TARGET_UNIX) && !defined(FEATURE_NATIVEAOT) && !defined(SOS_TARGET_LOONGARCH64) // On PAL, we don't always have the context pointers available due to // a limitation of an unwinding library. In such case, the context @@ -1955,7 +1930,7 @@ template void TGcInfoDecoder::ReportRe gcFlags |= GC_CALL_PINNED; } -#endif // FEATURE_NATIVEAOT, or TARGET_UNIX && !SOS_TARGET_LOONGARCH64 +#endif // TARGET_UNIX && !SOS_TARGET_LOONGARCH64 #ifdef _DEBUG if(IsScratchRegister(regNum, pRD)) @@ -2058,13 +2033,7 @@ template void TGcInfoDecoder::ReportRe LOG((LF_GCROOTS, LL_INFO1000, "Reporting " FMT_REG, regNum )); OBJECTREF* pObjRef = GetRegisterSlot( regNum, pRD ); -#ifdef FEATURE_NATIVEAOT - // In NativeAOT, volatile register save locations can be NULL if the register was not - // captured (e.g., in a voluntary preemptive transition frame that only saves callee-saved - // registers). Skip reporting if the save location is not available. - if (pObjRef == NULL) - return; -#elif defined(TARGET_UNIX) && !defined(SOS_TARGET_RISCV64) +#if defined(TARGET_UNIX) && !defined(FEATURE_NATIVEAOT) && !defined(SOS_TARGET_RISCV64) // On PAL, we don't always have the context pointers available due to // a limitation of an unwinding library. In such case, the context @@ -2085,7 +2054,7 @@ template void TGcInfoDecoder::ReportRe gcFlags |= GC_CALL_PINNED; } -#endif // FEATURE_NATIVEAOT, or TARGET_UNIX && !SOS_TARGET_RISCV64 +#endif // TARGET_UNIX && !SOS_TARGET_RISCV64 #ifdef _DEBUG if(IsScratchRegister(regNum, pRD)) From ebff30641ad088f494fd4ecb5f0473f3510ab1fc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 30 Mar 2026 23:26:08 +0000 Subject: [PATCH 5/5] Remove redundant comments from UnwindVolatileArgRegisters methods Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/a3dbdcc7-7423-4dfc-811c-7370f39ca001 Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com> --- .../nativeaot/Runtime/StackFrameIterator.cpp | 98 ++++++++----------- 1 file changed, 41 insertions(+), 57 deletions(-) diff --git a/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp b/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp index bc1a1762a4693c..928e16439e5bad 100644 --- a/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp +++ b/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp @@ -1372,14 +1372,12 @@ struct UniversalTransitionStackFrame void UnwindVolatileArgRegisters(REGDISPLAY * pRegisterSet) { - // Restore the volatile argument registers that the thunk saved, so that the GC - // can find and update any live GC references in them for the managed caller frame. - pRegisterSet->pRdi = GET_POINTER_TO_FIELD(m_intArgRegs[0]); // rdi (arg 0) - pRegisterSet->pRsi = GET_POINTER_TO_FIELD(m_intArgRegs[1]); // rsi (arg 1) - pRegisterSet->pRcx = GET_POINTER_TO_FIELD(m_intArgRegs[2]); // rcx (arg 2) - pRegisterSet->pRdx = GET_POINTER_TO_FIELD(m_intArgRegs[3]); // rdx (arg 3) - pRegisterSet->pR8 = GET_POINTER_TO_FIELD(m_intArgRegs[4]); // r8 (arg 4) - pRegisterSet->pR9 = GET_POINTER_TO_FIELD(m_intArgRegs[5]); // r9 (arg 5) + pRegisterSet->pRdi = GET_POINTER_TO_FIELD(m_intArgRegs[0]); + pRegisterSet->pRsi = GET_POINTER_TO_FIELD(m_intArgRegs[1]); + pRegisterSet->pRcx = GET_POINTER_TO_FIELD(m_intArgRegs[2]); + pRegisterSet->pRdx = GET_POINTER_TO_FIELD(m_intArgRegs[3]); + pRegisterSet->pR8 = GET_POINTER_TO_FIELD(m_intArgRegs[4]); + pRegisterSet->pR9 = GET_POINTER_TO_FIELD(m_intArgRegs[5]); } #elif defined(TARGET_AMD64) @@ -1408,12 +1406,10 @@ struct UniversalTransitionStackFrame void UnwindVolatileArgRegisters(REGDISPLAY * pRegisterSet) { - // Restore the volatile argument registers that the thunk saved, so that the GC - // can find and update any live GC references in them for the managed caller frame. - pRegisterSet->pRcx = GET_POINTER_TO_FIELD(m_intArgRegs[0]); // rcx (arg 0) - pRegisterSet->pRdx = GET_POINTER_TO_FIELD(m_intArgRegs[1]); // rdx (arg 1) - pRegisterSet->pR8 = GET_POINTER_TO_FIELD(m_intArgRegs[2]); // r8 (arg 2) - pRegisterSet->pR9 = GET_POINTER_TO_FIELD(m_intArgRegs[3]); // r9 (arg 3) + pRegisterSet->pRcx = GET_POINTER_TO_FIELD(m_intArgRegs[0]); + pRegisterSet->pRdx = GET_POINTER_TO_FIELD(m_intArgRegs[1]); + pRegisterSet->pR8 = GET_POINTER_TO_FIELD(m_intArgRegs[2]); + pRegisterSet->pR9 = GET_POINTER_TO_FIELD(m_intArgRegs[3]); } #elif defined(TARGET_ARM) @@ -1440,12 +1436,10 @@ struct UniversalTransitionStackFrame void UnwindVolatileArgRegisters(REGDISPLAY * pRegisterSet) { - // Restore the volatile argument registers that the thunk saved, so that the GC - // can find and update any live GC references in them for the managed caller frame. - pRegisterSet->pR0 = GET_POINTER_TO_FIELD(m_intArgRegs[0]); // r0 (arg 0) - pRegisterSet->pR1 = GET_POINTER_TO_FIELD(m_intArgRegs[1]); // r1 (arg 1) - pRegisterSet->pR2 = GET_POINTER_TO_FIELD(m_intArgRegs[2]); // r2 (arg 2) - pRegisterSet->pR3 = GET_POINTER_TO_FIELD(m_intArgRegs[3]); // r3 (arg 3) + pRegisterSet->pR0 = GET_POINTER_TO_FIELD(m_intArgRegs[0]); + pRegisterSet->pR1 = GET_POINTER_TO_FIELD(m_intArgRegs[1]); + pRegisterSet->pR2 = GET_POINTER_TO_FIELD(m_intArgRegs[2]); + pRegisterSet->pR3 = GET_POINTER_TO_FIELD(m_intArgRegs[3]); } #elif defined(TARGET_X86) @@ -1471,11 +1465,8 @@ struct UniversalTransitionStackFrame void UnwindVolatileArgRegisters(REGDISPLAY * pRegisterSet) { - // Restore the volatile argument registers that the thunk saved, so that the GC - // can find and update any live GC references in them for the managed caller frame. - // Note: the thunk saves edx first (lower address), then ecx. - pRegisterSet->pRdx = GET_POINTER_TO_FIELD(m_intArgRegs[0]); // edx (arg 1 in __fastcall) - pRegisterSet->pRcx = GET_POINTER_TO_FIELD(m_intArgRegs[1]); // ecx (arg 0 in __fastcall) + pRegisterSet->pRdx = GET_POINTER_TO_FIELD(m_intArgRegs[0]); + pRegisterSet->pRcx = GET_POINTER_TO_FIELD(m_intArgRegs[1]); } #elif defined(TARGET_ARM64) @@ -1503,17 +1494,15 @@ struct UniversalTransitionStackFrame void UnwindVolatileArgRegisters(REGDISPLAY * pRegisterSet) { - // Restore the volatile argument registers that the thunk saved, so that the GC - // can find and update any live GC references in them for the managed caller frame. - pRegisterSet->pX0 = GET_POINTER_TO_FIELD(m_intArgRegs[0]); // x0 (arg 0) - pRegisterSet->pX1 = GET_POINTER_TO_FIELD(m_intArgRegs[1]); // x1 (arg 1) - pRegisterSet->pX2 = GET_POINTER_TO_FIELD(m_intArgRegs[2]); // x2 (arg 2) - pRegisterSet->pX3 = GET_POINTER_TO_FIELD(m_intArgRegs[3]); // x3 (arg 3) - pRegisterSet->pX4 = GET_POINTER_TO_FIELD(m_intArgRegs[4]); // x4 (arg 4) - pRegisterSet->pX5 = GET_POINTER_TO_FIELD(m_intArgRegs[5]); // x5 (arg 5) - pRegisterSet->pX6 = GET_POINTER_TO_FIELD(m_intArgRegs[6]); // x6 (arg 6) - pRegisterSet->pX7 = GET_POINTER_TO_FIELD(m_intArgRegs[7]); // x7 (arg 7) - pRegisterSet->pX8 = GET_POINTER_TO_FIELD(m_intArgRegs[8]); // x8 (indirect result) + pRegisterSet->pX0 = GET_POINTER_TO_FIELD(m_intArgRegs[0]); + pRegisterSet->pX1 = GET_POINTER_TO_FIELD(m_intArgRegs[1]); + pRegisterSet->pX2 = GET_POINTER_TO_FIELD(m_intArgRegs[2]); + pRegisterSet->pX3 = GET_POINTER_TO_FIELD(m_intArgRegs[3]); + pRegisterSet->pX4 = GET_POINTER_TO_FIELD(m_intArgRegs[4]); + pRegisterSet->pX5 = GET_POINTER_TO_FIELD(m_intArgRegs[5]); + pRegisterSet->pX6 = GET_POINTER_TO_FIELD(m_intArgRegs[6]); + pRegisterSet->pX7 = GET_POINTER_TO_FIELD(m_intArgRegs[7]); + pRegisterSet->pX8 = GET_POINTER_TO_FIELD(m_intArgRegs[8]); } #elif defined(TARGET_LOONGARCH64) @@ -1540,17 +1529,14 @@ struct UniversalTransitionStackFrame void UnwindVolatileArgRegisters(REGDISPLAY * pRegisterSet) { - // Restore the volatile argument registers that the thunk saved, so that the GC - // can find and update any live GC references in them for the managed caller frame. - // In LoongArch64 ABI, a0-a7 are r4-r11. - pRegisterSet->pR4 = GET_POINTER_TO_FIELD(m_intArgRegs[0]); // a0/r4 (arg 0) - pRegisterSet->pR5 = GET_POINTER_TO_FIELD(m_intArgRegs[1]); // a1/r5 (arg 1) - pRegisterSet->pR6 = GET_POINTER_TO_FIELD(m_intArgRegs[2]); // a2/r6 (arg 2) - pRegisterSet->pR7 = GET_POINTER_TO_FIELD(m_intArgRegs[3]); // a3/r7 (arg 3) - pRegisterSet->pR8 = GET_POINTER_TO_FIELD(m_intArgRegs[4]); // a4/r8 (arg 4) - pRegisterSet->pR9 = GET_POINTER_TO_FIELD(m_intArgRegs[5]); // a5/r9 (arg 5) - pRegisterSet->pR10 = GET_POINTER_TO_FIELD(m_intArgRegs[6]); // a6/r10 (arg 6) - pRegisterSet->pR11 = GET_POINTER_TO_FIELD(m_intArgRegs[7]); // a7/r11 (arg 7) + pRegisterSet->pR4 = GET_POINTER_TO_FIELD(m_intArgRegs[0]); + pRegisterSet->pR5 = GET_POINTER_TO_FIELD(m_intArgRegs[1]); + pRegisterSet->pR6 = GET_POINTER_TO_FIELD(m_intArgRegs[2]); + pRegisterSet->pR7 = GET_POINTER_TO_FIELD(m_intArgRegs[3]); + pRegisterSet->pR8 = GET_POINTER_TO_FIELD(m_intArgRegs[4]); + pRegisterSet->pR9 = GET_POINTER_TO_FIELD(m_intArgRegs[5]); + pRegisterSet->pR10 = GET_POINTER_TO_FIELD(m_intArgRegs[6]); + pRegisterSet->pR11 = GET_POINTER_TO_FIELD(m_intArgRegs[7]); } #elif defined(TARGET_RISCV64) @@ -1577,16 +1563,14 @@ struct UniversalTransitionStackFrame void UnwindVolatileArgRegisters(REGDISPLAY * pRegisterSet) { - // Restore the volatile argument registers that the thunk saved, so that the GC - // can find and update any live GC references in them for the managed caller frame. - pRegisterSet->pA0 = GET_POINTER_TO_FIELD(m_intArgRegs[0]); // a0 (arg 0) - pRegisterSet->pA1 = GET_POINTER_TO_FIELD(m_intArgRegs[1]); // a1 (arg 1) - pRegisterSet->pA2 = GET_POINTER_TO_FIELD(m_intArgRegs[2]); // a2 (arg 2) - pRegisterSet->pA3 = GET_POINTER_TO_FIELD(m_intArgRegs[3]); // a3 (arg 3) - pRegisterSet->pA4 = GET_POINTER_TO_FIELD(m_intArgRegs[4]); // a4 (arg 4) - pRegisterSet->pA5 = GET_POINTER_TO_FIELD(m_intArgRegs[5]); // a5 (arg 5) - pRegisterSet->pA6 = GET_POINTER_TO_FIELD(m_intArgRegs[6]); // a6 (arg 6) - pRegisterSet->pA7 = GET_POINTER_TO_FIELD(m_intArgRegs[7]); // a7 (arg 7) + pRegisterSet->pA0 = GET_POINTER_TO_FIELD(m_intArgRegs[0]); + pRegisterSet->pA1 = GET_POINTER_TO_FIELD(m_intArgRegs[1]); + pRegisterSet->pA2 = GET_POINTER_TO_FIELD(m_intArgRegs[2]); + pRegisterSet->pA3 = GET_POINTER_TO_FIELD(m_intArgRegs[3]); + pRegisterSet->pA4 = GET_POINTER_TO_FIELD(m_intArgRegs[4]); + pRegisterSet->pA5 = GET_POINTER_TO_FIELD(m_intArgRegs[5]); + pRegisterSet->pA6 = GET_POINTER_TO_FIELD(m_intArgRegs[6]); + pRegisterSet->pA7 = GET_POINTER_TO_FIELD(m_intArgRegs[7]); } #elif defined(TARGET_WASM)