[AMDGPU] Fix crash with dead frame indices in debug values (#183297)

When spill slots are eliminated (VGPR-to-AGPR, SGPR-to-VGPR lanes),
debug values referencing these frame indices were not always properly
cleaned up. This caused an assertion failure in getObjectOffset() when
PrologEpilogInserter tried to access the offset of a dead frame object.

The existing debug fixup code in SIFrameLowering and SILowerSGPRSpills
had two limitations:
1. It only checked one operand position, but DBG_VALUE_LIST instructions
can have multiple debug operands with frame indices.
2. It didn't handle all types of dead frame indices uniformly.

Fix by centralizing debug info cleanup in removeDeadFrameIndices(),
which already knows all frame indices being removed. This iterates over
all debug operands using MI.debug_operands().

Assisted-by: Claude Code.
This commit is contained in:
Sergio Afonso 2026-04-01 13:41:53 +01:00 committed by GitHub
parent ab4b689258
commit 2cff995e91
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 102 additions and 35 deletions

View File

@ -172,6 +172,7 @@ add_llvm_target(AMDGPUCodeGen
SILowerWWMCopies.cpp
SILowerSGPRSpills.cpp
SIMachineFunctionInfo.cpp
SISpillUtils.cpp
SIMachineScheduler.cpp
SIMemoryLegalizer.cpp
SIModeRegister.cpp

View File

@ -12,6 +12,7 @@
#include "GCNSubtarget.h"
#include "MCTargetDesc/AMDGPUMCTargetDesc.h"
#include "SIMachineFunctionInfo.h"
#include "SISpillUtils.h"
#include "llvm/CodeGen/LiveRegUnits.h"
#include "llvm/CodeGen/MachineFrameInfo.h"
#include "llvm/CodeGen/RegisterScavenging.h"
@ -1518,23 +1519,8 @@ void SIFrameLowering::processFunctionBeforeFrameFinalized(
MBB.sortUniqueLiveIns();
if (!SpillFIs.empty() && SeenDbgInstr) {
// FIXME: The dead frame indices are replaced with a null register from
// the debug value instructions. We should instead, update it with the
// correct register value. But not sure the register value alone is
for (MachineInstr &MI : MBB) {
if (MI.isDebugValue()) {
uint32_t StackOperandIdx = MI.isDebugValueList() ? 2 : 0;
if (MI.getOperand(StackOperandIdx).isFI() &&
!MFI.isFixedObjectIndex(
MI.getOperand(StackOperandIdx).getIndex()) &&
SpillFIs[MI.getOperand(StackOperandIdx).getIndex()]) {
MI.getOperand(StackOperandIdx)
.ChangeToRegister(Register(), false /*isDef*/);
}
}
}
}
if (!SpillFIs.empty() && SeenDbgInstr)
clearDebugInfoForSpillFIs(MFI, MBB, SpillFIs);
}
}

View File

@ -20,6 +20,7 @@
#include "GCNSubtarget.h"
#include "MCTargetDesc/AMDGPUMCTargetDesc.h"
#include "SIMachineFunctionInfo.h"
#include "SISpillUtils.h"
#include "llvm/CodeGen/LiveIntervals.h"
#include "llvm/CodeGen/MachineDominators.h"
#include "llvm/CodeGen/MachineFrameInfo.h"
@ -524,24 +525,8 @@ bool SILowerSGPRSpills::run(MachineFunction &MF) {
FuncInfo->updateNonWWMRegMask(NonWwmRegMask);
}
for (MachineBasicBlock &MBB : MF) {
// FIXME: The dead frame indices are replaced with a null register from
// the debug value instructions. We should instead, update it with the
// correct register value. But not sure the register value alone is
// adequate to lower the DIExpression. It should be worked out later.
for (MachineInstr &MI : MBB) {
if (MI.isDebugValue()) {
uint32_t StackOperandIdx = MI.isDebugValueList() ? 2 : 0;
if (MI.getOperand(StackOperandIdx).isFI() &&
!MFI.isFixedObjectIndex(
MI.getOperand(StackOperandIdx).getIndex()) &&
SpillFIs[MI.getOperand(StackOperandIdx).getIndex()]) {
MI.getOperand(StackOperandIdx)
.ChangeToRegister(Register(), false /*isDef*/);
}
}
}
}
for (MachineBasicBlock &MBB : MF)
clearDebugInfoForSpillFIs(MFI, MBB, SpillFIs);
// All those frame indices which are dead by now should be removed from the
// function frame. Otherwise, there is a side effect such as re-mapping of

View File

@ -0,0 +1,34 @@
//===- SISpillUtils.cpp - SI spill helper functions -----------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "SISpillUtils.h"
#include "llvm/ADT/BitVector.h"
#include "llvm/CodeGen/MachineFrameInfo.h"
#include "llvm/CodeGen/MachineFunction.h"
using namespace llvm;
void llvm::clearDebugInfoForSpillFIs(MachineFrameInfo &MFI,
MachineBasicBlock &MBB,
const BitVector &SpillFIs) {
// FIXME: The dead frame indices are replaced with a null register from the
// debug value instructions. We should instead update it with the correct
// register value. But not sure the register value alone is adequate to lower
// the DIExpression. It should be worked out later.
for (MachineInstr &MI : MBB) {
if (!MI.isDebugValue())
continue;
for (MachineOperand &Op : MI.debug_operands()) {
if (Op.isFI() && !MFI.isFixedObjectIndex(Op.getIndex()) &&
SpillFIs[Op.getIndex()]) {
Op.ChangeToRegister(Register(), /*isDef=*/false);
}
}
}
}

View File

@ -0,0 +1,25 @@
//===- SISpillUtils.h - SI spill helper functions ---------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIB_TARGET_AMDGPU_SISPILLUTILS_H
#define LLVM_LIB_TARGET_AMDGPU_SISPILLUTILS_H
namespace llvm {
class BitVector;
class MachineBasicBlock;
class MachineFrameInfo;
/// Replace frame index operands with null registers in debug value instructions
/// for the specified spill frame indices.
void clearDebugInfoForSpillFIs(MachineFrameInfo &MFI, MachineBasicBlock &MBB,
const BitVector &SpillFIs);
} // end namespace llvm
#endif // LLVM_LIB_TARGET_AMDGPU_SISPILLUTILS_H

View File

@ -0,0 +1,36 @@
; RUN: llc -O0 -mtriple=amdgcn-amd-amdhsa -mcpu=gfx1100 < %s | FileCheck %s
; RUN: llc -O0 -mtriple=amdgcn-amd-amdhsa -mcpu=gfx900 < %s | FileCheck %s
; Check that debug values referencing eliminated frame indices don't crash.
; The AMDGPU backend can eliminate spill slots during frame finalization
; (e.g., SGPR spills to VGPR lanes). Debug values referencing these eliminated
; frame indices need to be cleaned up to avoid assertions in PrologEpilogInserter.
%struct.Buffer = type { [8 x i64] }
; CHECK-LABEL: test_dbg_value_dead_frame_idx:
; CHECK: ;DEBUG_VALUE: test_dbg_value_dead_frame_idx:slot <- [DW_OP_LLVM_arg 0, DW_OP_LLVM_arg 1, DW_OP_constu 64, DW_OP_mul, DW_OP_plus, DW_OP_stack_value] {{.*}}
; CHECK: s_endpgm
define amdgpu_kernel void @test_dbg_value_dead_frame_idx(ptr addrspace(1) %out, i64 %idx) !dbg !10 {
entry:
#dbg_value(!DIArgList(ptr addrspace(1) %out, i64 %idx), !15, !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 1, DW_OP_constu, 64, DW_OP_mul, DW_OP_plus, DW_OP_stack_value), !17)
%ptr = getelementptr %struct.Buffer, ptr addrspace(1) %out, i64 %idx
store i64 0, ptr addrspace(1) %ptr, align 8
ret void
}
!llvm.dbg.cu = !{!0}
!llvm.module.flags = !{!3, !4}
!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2)
!1 = !DIFile(filename: "test.cpp", directory: "/tmp")
!2 = !{}
!3 = !{i32 2, !"Debug Info Version", i32 3}
!4 = !{i32 1, !"amdhsa_code_object_version", i32 500}
!6 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !7, size: 64, dwarfAddressSpace: 1)
!7 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "Buffer", file: !1, line: 1, size: 512, flags: DIFlagTypePassByValue, elements: !2)
!10 = distinct !DISubprogram(name: "test_dbg_value_dead_frame_idx", scope: !1, file: !1, line: 10, type: !11, scopeLine: 10, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
!11 = !DISubroutineType(types: !12)
!12 = !{null, !6}
!15 = !DILocalVariable(name: "slot", scope: !10, file: !1, line: 11, type: !6)
!17 = !DILocation(line: 11, column: 1, scope: !10)