[llvm] [InstrRef] Skip clobbered EntryValue register recovery (PR #142478)

Shubham Sandeep Rastogi via llvm-commits llvm-commits at lists.llvm.org
Wed Jun 4 16:40:30 PDT 2025


https://github.com/rastogishubham updated https://github.com/llvm/llvm-project/pull/142478

>From 4b3faa9449632db936ff8ed77731f231fbaabcb3 Mon Sep 17 00:00:00 2001
From: Shubham Sandeep Rastogi <srastogi22 at apple.com>
Date: Fri, 30 May 2025 17:02:12 -0700
Subject: [PATCH 1/2] [InstrRef] Skip clobbered EntryValue register recovery

This changes the final stage of InstrRef, i.e. the TransferTracker (which
combines the values locations with the variable values), so that it treats a
DEBUG_VALUE of an EntryValue just like a DEBUG_VALUE of a constant: a location
that is never clobbered and can be propagated to subsequent BBs as long as no
other DEBUG_VALUE intrinsics updated the variable.

We add two tests here:

1. `entry_value_clobbered_stack_copy` that saves a register on the stack, uses
this register as an entry value DBG_VALUE location, and then clobbers it. Prior
to this patch, this test would crash because we would try to describe a new
location for the variable in terms of what was saved on the stack, and use an
invalid expression to do so. This is not needed as an EntryValue can never be
clobbered.

2. `entry_value_gets_propagated`, that tests that an EntryValue DBG_VALUE is
propagated in a diamond-shaped CFG.

This patch is trying to reland https://github.com/llvm/llvm-project/pull/77938
but also fixes the bug with InstrRef based LiveDebugValues, where entry
values were not being propagated in a diamond-shaped CFG.
---
 .../LiveDebugValues/InstrRefBasedImpl.cpp     | 12 ++-
 .../entry_value_clobbered_stack_copy.mir      | 50 ++++++++++
 .../InstrRef/entry_value_gets_propagated.mir  | 91 +++++++++++++++++++
 3 files changed, 152 insertions(+), 1 deletion(-)
 create mode 100644 llvm/test/DebugInfo/MIR/InstrRef/entry_value_clobbered_stack_copy.mir
 create mode 100644 llvm/test/DebugInfo/MIR/InstrRef/entry_value_gets_propagated.mir

diff --git a/llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.cpp b/llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.cpp
index fdf50188fbcd8..85ecfebb0cf68 100644
--- a/llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.cpp
+++ b/llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.cpp
@@ -668,6 +668,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;
@@ -694,7 +704,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);
diff --git a/llvm/test/DebugInfo/MIR/InstrRef/entry_value_clobbered_stack_copy.mir b/llvm/test/DebugInfo/MIR/InstrRef/entry_value_clobbered_stack_copy.mir
new file mode 100644
index 0000000000000..3461c40f5ad8c
--- /dev/null
+++ b/llvm/test/DebugInfo/MIR/InstrRef/entry_value_clobbered_stack_copy.mir
@@ -0,0 +1,50 @@
+# RUN: llc --run-pass=livedebugvalues -o - %s | FileCheck %s
+# REQUIRES: x86-registered-target
+
+--- |
+  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
\ No newline at end of file
diff --git a/llvm/test/DebugInfo/MIR/InstrRef/entry_value_gets_propagated.mir b/llvm/test/DebugInfo/MIR/InstrRef/entry_value_gets_propagated.mir
new file mode 100644
index 0000000000000..292aa2d9c0a30
--- /dev/null
+++ b/llvm/test/DebugInfo/MIR/InstrRef/entry_value_gets_propagated.mir
@@ -0,0 +1,91 @@
+# RUN: llc --run-pass=livedebugvalues -o - %s | FileCheck %s
+# REQUIRES: x86-registered-target
+--- |
+  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
+    $r14 = MOV64ri 0, debug-location !20
+    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
+    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-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)
\ No newline at end of file

>From 0dbe947bcde8fba61cb0e367c6e419f10ad98229 Mon Sep 17 00:00:00 2001
From: Shubham Sandeep Rastogi <srastogi22 at apple.com>
Date: Wed, 4 Jun 2025 16:30:00 -0700
Subject: [PATCH 2/2] Alternative solution to soliving the entry_value issue.

Treat entry_values as constants, they will be treated just as a
DBG_VALUE 1, $noreg, !1, DIExpression(), debug-location !2
---
 .../LiveDebugValues/InstrRefBasedImpl.cpp        | 16 ++++------------
 .../entry_value_clobbered_stack_copy.mir         |  5 ++++-
 .../MIR/InstrRef/entry_value_gets_propagated.mir | 13 ++++++++++---
 3 files changed, 18 insertions(+), 16 deletions(-)

diff --git a/llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.cpp b/llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.cpp
index 85ecfebb0cf68..dbda805adfb76 100644
--- a/llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.cpp
+++ b/llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.cpp
@@ -667,17 +667,6 @@ 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;
@@ -1458,7 +1447,10 @@ bool InstrRefBasedLDV::transferDebugValue(const MachineInstr &MI) {
       for (const MachineOperand &MO : MI.debug_operands()) {
         // There should be no undef registers here, as we've screened for undef
         // debug values.
-        if (MO.isReg()) {
+        if (MI.getDebugExpression()->isEntryValue()) {
+          // If the DBG_VALUE is a DW_OP_entry_value, treat it as a constant.
+          DebugOps.push_back(DbgOpStore.insert(MO));
+        } else if (MO.isReg()) {
           DebugOps.push_back(DbgOpStore.insert(MTracker->readReg(MO.getReg())));
         } else if (MO.isImm() || MO.isFPImm() || MO.isCImm()) {
           DebugOps.push_back(DbgOpStore.insert(MO));
diff --git a/llvm/test/DebugInfo/MIR/InstrRef/entry_value_clobbered_stack_copy.mir b/llvm/test/DebugInfo/MIR/InstrRef/entry_value_clobbered_stack_copy.mir
index 3461c40f5ad8c..ac86299242353 100644
--- a/llvm/test/DebugInfo/MIR/InstrRef/entry_value_clobbered_stack_copy.mir
+++ b/llvm/test/DebugInfo/MIR/InstrRef/entry_value_clobbered_stack_copy.mir
@@ -1,6 +1,9 @@
 # RUN: llc --run-pass=livedebugvalues -o - %s | FileCheck %s
 # REQUIRES: x86-registered-target
 
+# This test covers the scenario where a DBG_VALUE created prior to LiveDebugValues has an entry-value expression. 
+# It tests that an EntryValue DBG_VALUE is propagated in a diamond-shaped CFG.
+
 --- |
   target triple = "x86_64-"
   define void @foo(ptr swiftasync %0) !dbg !4 {
@@ -47,4 +50,4 @@ body:             |
 # 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
\ No newline at end of file
+# CHECK-NOT:  DBG_VALUE
diff --git a/llvm/test/DebugInfo/MIR/InstrRef/entry_value_gets_propagated.mir b/llvm/test/DebugInfo/MIR/InstrRef/entry_value_gets_propagated.mir
index 292aa2d9c0a30..4e8863064c4fc 100644
--- a/llvm/test/DebugInfo/MIR/InstrRef/entry_value_gets_propagated.mir
+++ b/llvm/test/DebugInfo/MIR/InstrRef/entry_value_gets_propagated.mir
@@ -1,5 +1,9 @@
-# RUN: llc --run-pass=livedebugvalues -o - %s | FileCheck %s
+# 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-"
 
@@ -64,13 +68,13 @@ body:             |
     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
-    $r14 = MOV64ri 0, debug-location !20
     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)
@@ -85,7 +89,10 @@ body:             |
 # 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)
\ No newline at end of file
+# CHECK: DBG_VALUE $r14, {{.*}}, !DIExpression(DW_OP_LLVM_entry_value, 1)



More information about the llvm-commits mailing list