Skip to content

[InstrRef] Skip clobbered EntryValue register recovery #142478

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ class TransferTracker {
SmallVector<ResolvedDbgOp> ResolvedDbgOps;
bool IsValueValid = true;
unsigned LastUseBeforeDef = 0;
bool DbgLocAvailableAndIsEntryVal = false;

// If every value used by the incoming DbgValue is available at block
// entry, ResolvedDbgOps will contain the machine locations/constants for
Expand Down Expand Up @@ -412,6 +413,8 @@ class TransferTracker {
// live range.
LocIdx M = ValuesPreferredLoc->second.getLoc();
ResolvedDbgOps.push_back(M);
if (Value.Properties.DIExpr->isEntryValue())
DbgLocAvailableAndIsEntryVal = true;
}

// If we cannot produce a valid value for the LiveIn value within this
Expand All @@ -425,6 +428,16 @@ class TransferTracker {
return;
}

auto &[Var, DILoc] = DVMap.lookupDVID(VarID);
PendingDbgValues.push_back(
std::make_pair(VarID, &*MTracker->emitLoc(ResolvedDbgOps, Var, DILoc,
Value.Properties)));

// If the location is available at block entry and is an entry value, skip
// tracking and recording thr transfer.
if (DbgLocAvailableAndIsEntryVal)
return;

// The LiveIn value is available at block entry, begin tracking and record
// the transfer.
for (const ResolvedDbgOp &Op : ResolvedDbgOps)
Expand All @@ -434,10 +447,6 @@ class TransferTracker {
auto Result = ActiveVLocs.insert(std::make_pair(VarID, NewValue));
if (!Result.second)
Result.first->second = NewValue;
auto &[Var, DILoc] = DVMap.lookupDVID(VarID);
PendingDbgValues.push_back(
std::make_pair(VarID, &*MTracker->emitLoc(ResolvedDbgOps, Var, DILoc,
Value.Properties)));
}

/// Load object with live-in variable values. \p mlocs contains the live-in
Expand Down Expand Up @@ -668,6 +677,16 @@ class TransferTracker {

auto &[Var, DILoc] = DVMap.lookupDVID(VarID);

// If the expression is a DW_OP_entry_value, emit the variable location
// as-is.
if (DIExpr->isEntryValue()) {
Register Reg = MTracker->LocIdxToLocID[Num.getLoc()];
MachineOperand MO = MachineOperand::CreateReg(Reg, false);
PendingDbgValues.push_back(std::make_pair(
VarID, &*emitMOLoc(MO, Var, {DIExpr, Prop.Indirect, false})));
return true;
}

// Is the variable appropriate for entry values (i.e., is a parameter).
if (!isEntryValueVariable(Var, DIExpr))
return false;
Expand All @@ -694,7 +713,7 @@ class TransferTracker {
DebugVariableID VarID = DVMap.getDVID(Var);

// Ignore non-register locations, we don't transfer those.
if (MI.isUndefDebugValue() ||
if (MI.isUndefDebugValue() || MI.getDebugExpression()->isEntryValue() ||
all_of(MI.debug_operands(),
[](const MachineOperand &MO) { return !MO.isReg(); })) {
auto It = ActiveVLocs.find(VarID);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# RUN: llc --run-pass=livedebugvalues -o - %s | FileCheck %s --implicit-check-not=DBG_VALUE
# REQUIRES: aarch64-registered-target

# This test covers the scenario where a DBG_VALUE created prior to LiveDebugValues has an entry-value expression.
# It ensures that a clobbered stack copy doesn't crash if used as an entry-value because entry-values can't be clobbered.

--- |
target triple = "aarch64-"
define i32 @baz(i32 swiftasync %arg1, i32 noundef %arg2, i1 %cond) !dbg !4 {
br i1 %cond, label %if.then, label %if.else, !dbg !14
if.then: ; preds = %0
%call = call i32 @foo(i32 noundef %arg1), !dbg !15
br label %if.end, !dbg !18
if.else: ; preds = %0
%call1 = call i32 @foo(i32 noundef %arg2), !dbg !19
br label %if.end
if.end: ; preds = %if.else, %if.then
%temp.0 = phi i32 [ %call, %if.then ], [ %call1, %if.else ], !dbg !21
ret i32 %temp.0, !dbg !22
}
declare i32 @foo(i32)
!llvm.dbg.cu = !{!0}
!llvm.module.flags = !{!2, !3}
!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "ha", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug)
!1 = !DIFile(filename: "test.c", directory: "hah")
!2 = !{i32 7, !"Dwarf Version", i32 4}
!3 = !{i32 2, !"Debug Info Version", i32 3}
!4 = distinct !DISubprogram(name: "baz", scope: !1, file: !1, line: 3, type: !5, scopeLine: 3, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !8)
!5 = !DISubroutineType(types: !6)
!6 = !{!7, !7, !7, !7}
!7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
!8 = !{!9, !10, !11, !12}
!9 = !DILocalVariable(name: "arg1", arg: 1, scope: !4, file: !1, line: 3, type: !7)
!10 = !DILocalVariable(name: "arg2", arg: 2, scope: !4, file: !1, line: 3, type: !7)
!11 = !DILocalVariable(name: "cond", arg: 3, scope: !4, file: !1, line: 3, type: !7)
!12 = !DILocalVariable(name: "local", scope: !4, file: !1, line: 4, type: !7)
!13 = !DILocation(line: 0, scope: !4)
!14 = !DILocation(line: 7, column: 7, scope: !4)
!15 = !DILocation(line: 8, column: 12, scope: !16)
!16 = distinct !DILexicalBlock(scope: !17, file: !1, line: 7, column: 13)
!17 = distinct !DILexicalBlock(scope: !4, file: !1, line: 7, column: 7)
!18 = !DILocation(line: 9, column: 3, scope: !16)
!19 = !DILocation(line: 10, column: 12, scope: !20)
!20 = distinct !DILexicalBlock(scope: !17, file: !1, line: 9, column: 10)
!21 = !DILocation(line: 0, scope: !17)
!22 = !DILocation(line: 13, column: 3, scope: !4)
name: baz
debugInstrRef: true
stack:
- { id: 1, name: '', type: spill-slot, offset: -24, size: 4, alignment: 4,
debug-info-variable: '', debug-info-expression: '', debug-info-location: '' }
body: |
bb.0 (%ir-block.0):
DBG_VALUE $w1, $noreg, !12, !DIExpression(DW_OP_LLVM_entry_value, 1), debug-location !13

bb.1.if.then:
$w0 = LDRWui $sp, 2
BL @foo, csr_darwin_aarch64_aapcs, implicit-def dead $lr, implicit $sp, implicit killed $w0, implicit-def $w0, debug-location !15
$w1 = MOVi32imm 0

bb.2.if.else:
$w0 = LDRWui $sp, 3
BL @foo, csr_darwin_aarch64_aapcs, implicit-def dead $lr, implicit $sp, implicit killed $w0, implicit-def $w0, debug-location !19
STRWui killed $w0, $sp, 1
B %bb.3

bb.3.if.end:
$w0 = LDRWui $sp, 1
$fp, $lr = frame-destroy LDPXi $sp, 2, debug-location !22
$sp = frame-destroy ADDXri $sp, 32, 0, debug-location !22
RET undef $lr, implicit killed $w0, debug-location !22

# CHECK-LABEL: bb.0
# CHECK: DBG_VALUE $w1, {{.*}}, !DIExpression(DW_OP_LLVM_entry_value, 1)
# CHECK-LABEL: bb.1.if.then:
# CHECK: DBG_VALUE $w1, {{.*}}, !DIExpression(DW_OP_LLVM_entry_value, 1)
# CHECK-NEXT: $w0 = LDRWui $sp, 2
# CHECK-NEXT: BL @foo
# CHECK-NEXT: $w1 = MOVi32imm 0
# CHECK-NOT: DBG_VALUE $w1, {{.*}}, !DIExpression(DW_OP_LLVM_entry_value, 1)
# CHECK-LABEL: bb.2.if.else:
# CHECK: DBG_VALUE $w1, {{.*}}, !DIExpression(DW_OP_LLVM_entry_value, 1)
# CHECK-LABEL: bb.3.if.end:
# CHECK: DBG_VALUE $w1, {{.*}}, !DIExpression(DW_OP_LLVM_entry_value, 1)
52 changes: 52 additions & 0 deletions llvm/test/DebugInfo/MIR/X86/entry_value_clobbered_stack_copy.mir
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# RUN: llc --run-pass=livedebugvalues -o - %s | FileCheck %s
# REQUIRES: x86-registered-target

# This test covers the scenario that saves a register on the stack, uses this register as an entry value DBG_VALUE location, and then clobbers it.

--- |
target triple = "x86_64-"
define void @foo(ptr swiftasync %0) !dbg !4 {
call void @llvm.dbg.value(metadata ptr %0, metadata !9, metadata !DIExpression(DW_OP_LLVM_entry_value, 1)), !dbg !17
ret void
}
declare void @llvm.dbg.value(metadata, metadata, metadata)

!llvm.module.flags = !{!0}
!llvm.dbg.cu = !{!1}

!0 = !{i32 2, !"Debug Info Version", i32 3}
!1 = distinct !DICompileUnit(language: DW_LANG_Swift, file: !2, producer: "blah", isOptimized: true, flags: "blah", runtimeVersion: 5, emissionKind: FullDebug)
!2 = !DIFile(filename: "blah", directory: "blah")
!3 = !{}
!4 = distinct !DISubprogram(name: "blah", linkageName: "blah", scope: !2, file: !2, line: 284, type: !7, unit: !1)
!7 = !DISubroutineType(types: !3)
!9 = !DILocalVariable(name: "self", arg: 3, scope: !4, file: !2, line: 328, type: !12, flags: DIFlagArtificial)
!12 = !DICompositeType(tag: DW_TAG_structure_type, name: "blah", scope: !2, file: !2, size: 64, elements: !3)
!17 = !DILocation(line: 328, column: 17, scope: !4)

...
---
name: foo
alignment: 16
debugInstrRef: true
tracksDebugUserValues: true
liveins:
- { reg: '$r14', virtual-reg: '' }
stack:
- { id: 0, name: '', type: spill-slot, offset: -64, size: 8, alignment: 8,
stack-id: default, callee-saved-register: '', callee-saved-restored: true,
debug-info-variable: '', debug-info-expression: '', debug-info-location: '' }
body: |
bb.0:
liveins: $r14
; Put a copy of r14 on the stack.
MOV64mr $rbp, 1, $noreg, -48, $noreg, $r14 :: (store (s64) into %stack.0)
DBG_VALUE $r14, $noreg, !9, !DIExpression(DW_OP_LLVM_entry_value, 1), debug-location !17
MOV64mi32 $noreg, 1, $noreg, 0, $noreg, 0, debug-location !17 :: (store (s64) into `ptr null`)
$r14 = MOV64rr killed $r13
; Clobber $r14
RETI64 24
# CHECK: bb.0:
# CHECK: MOV64mr $rbp, 1, $noreg, -48, $noreg, $r14
# CHECK-NEXT: DBG_VALUE $r14, {{.*}}, !DIExpression(DW_OP_LLVM_entry_value, 1)
# CHECK-NOT: DBG_VALUE
98 changes: 98 additions & 0 deletions llvm/test/DebugInfo/MIR/X86/entry_value_gets_propagated_X86.mir
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# RUN: llc --run-pass=livedebugvalues -o - %s | FileCheck %s --implicit-check-not=DBG_VALUE
# REQUIRES: x86-registered-target

# This test covers the scenario where a DBG_VALUE created prior to LiveDebugValues has an entry-value expression.
# It ensures that a clobbered stack copy doesn't crash if used as an entry-value because entry-values can't be clobbered.

--- |
target triple = "x86_64-"

define i32 @baz(i32 swiftasync %arg1, i32 noundef %arg2, i1 %cond) !dbg !9 {
tail call void @llvm.dbg.value(metadata i32 %arg1, metadata !17, metadata !DIExpression(DW_OP_LLVM_entry_value, 1)), !dbg !19
br i1 %cond, label %if.then, label %if.else, !dbg !22
if.then:
%call = call i32 @foo(i32 noundef %arg1), !dbg !23
br label %if.end, !dbg !25
if.else:
%call1 = call i32 @foo(i32 noundef %arg2), !dbg !26
br label %if.end
if.end:
%temp.0 = phi i32 [ %call, %if.then ], [ %call1, %if.else ], !dbg !28
ret i32 %temp.0, !dbg !29
}

declare i32 @foo(i32)
declare void @llvm.dbg.value(metadata, metadata, metadata)

!llvm.dbg.cu = !{!0}
!llvm.module.flags = !{!2, !3}

!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "ha", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug)
!1 = !DIFile(filename: "test.c", directory: "hah")
!2 = !{i32 7, !"Dwarf Version", i32 4}
!3 = !{i32 2, !"Debug Info Version", i32 3}
!9 = distinct !DISubprogram(name: "baz", scope: !1, file: !1, line: 3, type: !10, scopeLine: 3, unit: !0, retainedNodes: !13)
!10 = !DISubroutineType(types: !11)
!11 = !{!12, !12, !12, !12}
!12 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
!13 = !{!14, !15, !16, !17}
!14 = !DILocalVariable(name: "arg1", arg: 1, scope: !9, file: !1, line: 3, type: !12)
!15 = !DILocalVariable(name: "arg2", arg: 2, scope: !9, file: !1, line: 3, type: !12)
!16 = !DILocalVariable(name: "cond", arg: 3, scope: !9, file: !1, line: 3, type: !12)
!17 = !DILocalVariable(name: "local", scope: !9, file: !1, line: 4, type: !12)
!19 = !DILocation(line: 0, scope: !9)
!20 = !DILocation(line: 7, column: 7, scope: !21)
!21 = distinct !DILexicalBlock(scope: !9, file: !1, line: 7, column: 7)
!22 = !DILocation(line: 7, column: 7, scope: !9)
!23 = !DILocation(line: 8, column: 12, scope: !24)
!24 = distinct !DILexicalBlock(scope: !21, file: !1, line: 7, column: 13)
!25 = !DILocation(line: 9, column: 3, scope: !24)
!26 = !DILocation(line: 10, column: 12, scope: !27)
!27 = distinct !DILexicalBlock(scope: !21, file: !1, line: 9, column: 10)
!28 = !DILocation(line: 0, scope: !21)
!29 = !DILocation(line: 13, column: 3, scope: !9)

...
---
name: baz
alignment: 16
debugInstrRef: true
tracksDebugUserValues: true
liveins:
- { reg: '$r14', virtual-reg: '' }
- { reg: '$edi', virtual-reg: '' }
- { reg: '$esi', virtual-reg: '' }
- { reg: '$edx', virtual-reg: '' }
body: |
bb.0:
successors: %bb.2(0x40000000), %bb.1(0x40000000)
liveins: $r14, $edi, $edx, $esi
DBG_VALUE $r14, $noreg, !14, !DIExpression(DW_OP_LLVM_entry_value, 1), debug-location !19
CMP32ri killed renamable $edx, 0, implicit-def $eflags, debug-location !20
JCC_1 %bb.2, 4, implicit killed $eflags, debug-location !22
bb.1.if.then:
successors: %bb.3(0x80000000)
liveins: $edi, $r13
CALL64pcrel32 @foo, csr_64, implicit $rsp, implicit $ssp, implicit $edi, implicit-def $eax, debug-location !23
$r14 = MOV64ri 0, debug-location !20
JMP_1 %bb.3, debug-location !25
bb.2.if.else:
successors: %bb.3(0x80000000)
liveins: $esi, $r13
$edi = MOV32rr killed $esi, debug-location !26
CALL64pcrel32 @foo, csr_64, implicit $rsp, implicit $ssp, implicit $edi, implicit-def $eax, debug-location !26
bb.3.if.end:
liveins: $eax
$rbp = frame-destroy POP64r implicit-def $rsp, implicit $rsp, debug-location !29
RET64 implicit $eax, debug-location !29
# CHECK-LABEL: bb.0:
# CHECK: DBG_VALUE $r14, {{.*}}, !DIExpression(DW_OP_LLVM_entry_value, 1)
# CHECK-LABEL: bb.1.if.then:
# CHECK: DBG_VALUE $r14, {{.*}}, !DIExpression(DW_OP_LLVM_entry_value, 1)
# CHECK-NEXT: CALL64pcrel32 @foo
# CHECK-NEXT: $r14 = MOV64ri 0
# CHECK-NOT: DBG_VALUE $r14, {{.*}}, !DIExpression(DW_OP_LLVM_entry_value, 1)
# CHECK-LABEL: bb.2.if.else:
# CHECK: DBG_VALUE $r14, {{.*}}, !DIExpression(DW_OP_LLVM_entry_value, 1)
# CHECK-LABEL: bb.3.if.end:
# CHECK: DBG_VALUE $r14, {{.*}}, !DIExpression(DW_OP_LLVM_entry_value, 1)