[llvm] [DebugInfo] Preserve value semantics when salvaging truncations (PR #229033)

Kamil Monicz via llvm-commits llvm-commits at lists.llvm.org
Mon Oct 5 05:05:09 PDT 2026


https://github.com/Zaczero updated https://github.com/llvm/llvm-project/pull/229033

>From 44dcf60a275b67bf2817f2350f228aa6d1d24f35 Mon Sep 17 00:00:00 2001
From: Kamil Monicz <kamil at monicz.dev>
Date: Mon, 5 Oct 2026 12:03:13 +0200
Subject: [PATCH] [DebugInfo] Preserve value semantics when salvaging
 truncations

Truncation salvage inserts conversion operations without requesting
DW_OP_stack_value. For a direct debug value with an empty expression,
this makes the emitted DWARF describe memory at the computed value
rather than the value itself.

Request a stack value for direct debug values unless the expression
ends in a dereference before an optional fragment. Preserve these memory
locations, as well as explicitly indirect locations: adding a stack-value
marker would instead load an address-sized value, losing the location
of larger objects.

Cover values, fragments, argument lists, and memory locations with both
variable-tracking modes, including an intermediate dereference followed
by arithmetic that still requires a stack-value marker.

Assisted-by: GPT-6
---
 .../lib/CodeGen/SelectionDAG/SelectionDAG.cpp | 10 +-
 .../CodeGen/X86/selection-dag-salvagetrunc.ll |  2 +-
 .../X86/selectiondag-salvage-truncate.ll      | 91 +++++++++++++++++++
 3 files changed, 101 insertions(+), 2 deletions(-)
 create mode 100644 llvm/test/DebugInfo/X86/selectiondag-salvage-truncate.ll

diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp
index 77922d8fb13442..37e3df3addeae5 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp
@@ -13065,6 +13065,13 @@ void SelectionDAG::salvageDebugInfo(SDNode &N) {
       TypeSize ToSize = N.getValueSizeInBits(0);
 
       DIExpression *DbgExpression = DV->getExpression();
+      // A terminal dereference describes memory, even for a direct dbg_value.
+      // Adding a stack value would instead load an address-sized value.
+      bool EndsWithDeref = false;
+      for (auto Op : DbgExpression->expr_ops())
+        if (Op.getOp() != dwarf::DW_OP_LLVM_fragment)
+          EndsWithDeref = Op.getOp() == dwarf::DW_OP_deref;
+      bool StackValue = !DV->isIndirect() && !EndsWithDeref;
       auto ExtOps = DIExpression::getExtOps(FromSize, ToSize, false);
       auto NewLocOps = DV->copyLocationOps();
       bool Changed = false;
@@ -13074,7 +13081,8 @@ void SelectionDAG::salvageDebugInfo(SDNode &N) {
           continue;
 
         NewLocOps[i] = GetLocationOperand(N0.getNode(), N0.getResNo());
-        DbgExpression = DIExpression::appendOpsToArg(DbgExpression, ExtOps, i);
+        DbgExpression =
+            DIExpression::appendOpsToArg(DbgExpression, ExtOps, i, StackValue);
         Changed = true;
       }
       assert(Changed && "Salvage target doesn't use N");
diff --git a/llvm/test/CodeGen/X86/selection-dag-salvagetrunc.ll b/llvm/test/CodeGen/X86/selection-dag-salvagetrunc.ll
index 95d63577d0242c..501084afc10f6a 100644
--- a/llvm/test/CodeGen/X86/selection-dag-salvagetrunc.ll
+++ b/llvm/test/CodeGen/X86/selection-dag-salvagetrunc.ll
@@ -4,7 +4,7 @@
 ; Fixes LLVM issue #63076.
 
 ; CHECK: body
-; CHECK: DBG_INSTR_REF !{{[0-9]+}}, !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_convert, 8, DW_ATE_unsigned, DW_OP_LLVM_convert, 1, DW_ATE_unsigned)
+; CHECK: DBG_INSTR_REF !{{[0-9]+}}, !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_convert, 8, DW_ATE_unsigned, DW_OP_LLVM_convert, 1, DW_ATE_unsigned, DW_OP_stack_value)
 
 source_filename = "repro.cpp"
 target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
diff --git a/llvm/test/DebugInfo/X86/selectiondag-salvage-truncate.ll b/llvm/test/DebugInfo/X86/selectiondag-salvage-truncate.ll
new file mode 100644
index 00000000000000..3daf1c7275aeda
--- /dev/null
+++ b/llvm/test/DebugInfo/X86/selectiondag-salvage-truncate.ll
@@ -0,0 +1,91 @@
+; RUN: llc -mtriple=x86_64-unknown-linux-gnu -experimental-debug-variable-locations=false -filetype=obj %s -o %t
+; RUN: llvm-dwarfdump --verify %t
+; RUN: llvm-dwarfdump --debug-info %t | FileCheck %s
+; RUN: llc -mtriple=x86_64-unknown-linux-gnu -experimental-debug-variable-locations=true -filetype=obj %s -o %t
+; RUN: llvm-dwarfdump --verify %t
+; RUN: llvm-dwarfdump --debug-info %t | FileCheck %s
+;
+; Salvaging a truncated value must preserve the distinction between a value
+; and an address, including expressions that already have a stack value or
+; describe a fragment.
+; The shift/mask/extension sequence creates a truncation that SelectionDAG
+; removes, requiring the debug values to be salvaged.
+
+; CHECK: DW_TAG_variable
+; CHECK: DW_AT_location
+; CHECK: DW_OP_convert {{.*}}"DW_ATE_unsigned_16", DW_OP_stack_value{{\)?$}}
+; CHECK: DW_AT_name ("value")
+; CHECK: DW_TAG_variable
+; CHECK: DW_AT_location
+; CHECK: DW_OP_convert {{.*}}"DW_ATE_unsigned_16", DW_OP_stack_value{{\)?$}}
+; CHECK: DW_AT_name ("stack_value")
+; CHECK: DW_TAG_variable
+; CHECK: DW_AT_location
+; CHECK: DW_OP_convert {{.*}}"DW_ATE_unsigned_16", DW_OP_stack_value, DW_OP_piece 0x2{{\)?$}}
+; CHECK: DW_AT_name ("fragment")
+; CHECK: DW_TAG_variable
+; CHECK: DW_AT_location
+; CHECK: DW_OP_convert {{.*}}"DW_ATE_unsigned_16", DW_OP_stack_value{{\)?$}}
+; CHECK: DW_AT_name ("arglist")
+; CHECK: DW_TAG_variable
+; CHECK: DW_AT_location
+; CHECK: DW_OP_convert {{.*}}"DW_ATE_unsigned_16"{{\)?$}}
+; CHECK: DW_AT_name ("indirect")
+; CHECK: DW_TAG_variable
+; CHECK: DW_AT_location
+; CHECK: DW_OP_convert {{.*}}"DW_ATE_unsigned_64", DW_OP_breg
+; CHECK-SAME: DW_OP_convert {{.*}}"DW_ATE_unsigned_32", DW_OP_convert {{.*}}"DW_ATE_unsigned_16", DW_OP_convert {{.*}}"DW_ATE_unsigned_64", DW_OP_plus{{\)?$}}
+; CHECK: DW_AT_name ("memory")
+; CHECK: DW_TAG_variable
+; CHECK: DW_AT_location
+; CHECK: DW_OP_convert {{.*}}"DW_ATE_unsigned_64", DW_OP_breg
+; CHECK-SAME: DW_OP_convert {{.*}}"DW_ATE_unsigned_32", DW_OP_convert {{.*}}"DW_ATE_unsigned_16", DW_OP_convert {{.*}}"DW_ATE_unsigned_64", DW_OP_plus, DW_OP_piece 0x10{{\)?$}}
+; CHECK: DW_AT_name ("memory_fragment")
+; CHECK: DW_TAG_variable
+; CHECK: DW_AT_location
+; CHECK: DW_OP_convert {{.*}}"DW_ATE_unsigned_64", DW_OP_breg
+; CHECK-SAME: DW_OP_convert {{.*}}"DW_ATE_unsigned_32", DW_OP_convert {{.*}}"DW_ATE_unsigned_16", DW_OP_convert {{.*}}"DW_ATE_unsigned_64", DW_OP_plus, DW_OP_deref, DW_OP_plus_uconst 0x1, DW_OP_stack_value{{\)?$}}
+; CHECK: DW_AT_name ("loaded_value")
+
+declare void @observe(ptr, i64)
+
+define i64 @salvage_truncate(ptr %base, i16 %code) !dbg !4 {
+entry:
+  %shift = lshr i16 %code, 7, !dbg !8
+  %value = and i16 %shift, 3, !dbg !8
+  #dbg_value(i16 %value, !7, !DIExpression(), !8)
+  #dbg_value(i16 %value, !9, !DIExpression(DW_OP_stack_value), !8)
+  #dbg_value(i16 %value, !10, !DIExpression(DW_OP_LLVM_fragment, 0, 16), !8)
+  #dbg_value(!DIArgList(i16 %value), !14, !DIExpression(DW_OP_LLVM_arg, 0), !8)
+  #dbg_declare(i16 %value, !11, !DIExpression(), !8)
+  #dbg_value(!DIArgList(ptr %base, i16 %value), !15, !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_convert, 64, DW_ATE_unsigned, DW_OP_LLVM_arg, 1, DW_OP_LLVM_convert, 64, DW_ATE_unsigned, DW_OP_plus, DW_OP_deref), !8)
+  #dbg_value(!DIArgList(ptr %base, i16 %value), !16, !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_convert, 64, DW_ATE_unsigned, DW_OP_LLVM_arg, 1, DW_OP_LLVM_convert, 64, DW_ATE_unsigned, DW_OP_plus, DW_OP_deref, DW_OP_LLVM_fragment, 0, 128), !8)
+  #dbg_value(!DIArgList(ptr %base, i16 %value), !17, !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_convert, 64, DW_ATE_unsigned, DW_OP_LLVM_arg, 1, DW_OP_LLVM_convert, 64, DW_ATE_unsigned, DW_OP_plus, DW_OP_deref, DW_OP_plus_uconst, 1), !8)
+  %wide = zext i16 %value to i64, !dbg !8
+  call void @observe(ptr %base, i64 %wide), !dbg !8
+  ret i64 %wide, !dbg !8
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3}
+!0 = distinct !DICompileUnit(language: DW_LANG_C, file: !1, producer: "test", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, nameTableKind: None)
+!1 = !DIFile(filename: "truncate.c", directory: "/")
+!2 = !{i32 2, !"Debug Info Version", i32 3}
+!3 = !{i32 2, !"Dwarf Version", i32 5}
+!4 = distinct !DISubprogram(name: "salvage_truncate", scope: !1, file: !1, line: 1, type: !5, scopeLine: 1, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
+!5 = !DISubroutineType(types: !{!13, !18, !12})
+!6 = !DIBasicType(name: "unsigned int", size: 32, encoding: DW_ATE_unsigned)
+!7 = !DILocalVariable(name: "value", scope: !4, file: !1, line: 2, type: !12)
+!8 = !DILocation(line: 2, column: 1, scope: !4)
+!9 = !DILocalVariable(name: "stack_value", scope: !4, file: !1, line: 2, type: !12)
+!10 = !DILocalVariable(name: "fragment", scope: !4, file: !1, line: 2, type: !6)
+!11 = !DILocalVariable(name: "indirect", scope: !4, file: !1, line: 2, type: !12)
+!12 = !DIBasicType(name: "unsigned short", size: 16, encoding: DW_ATE_unsigned)
+!13 = !DIBasicType(name: "unsigned long", size: 64, encoding: DW_ATE_unsigned)
+!14 = !DILocalVariable(name: "arglist", scope: !4, file: !1, line: 2, type: !12)
+!15 = !DILocalVariable(name: "memory", scope: !4, file: !1, line: 2, type: !19)
+!16 = !DILocalVariable(name: "memory_fragment", scope: !4, file: !1, line: 2, type: !20)
+!17 = !DILocalVariable(name: "loaded_value", scope: !4, file: !1, line: 2, type: !13)
+!18 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !19, size: 64)
+!19 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "Pair", file: !1, line: 1, size: 128)
+!20 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "Triple", file: !1, line: 1, size: 192)



More information about the llvm-commits mailing list