about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_codegen_gcc/src/debuginfo.rs13
-rw-r--r--compiler/rustc_codegen_llvm/src/debuginfo/dwarf_const.rs3
-rw-r--r--compiler/rustc_codegen_llvm/src/debuginfo/mod.rs53
-rw-r--r--compiler/rustc_codegen_llvm/src/llvm/ffi.rs9
-rw-r--r--compiler/rustc_codegen_ssa/src/mir/block.rs1
-rw-r--r--compiler/rustc_codegen_ssa/src/mir/debuginfo.rs54
-rw-r--r--compiler/rustc_codegen_ssa/src/mir/operand.rs17
-rw-r--r--compiler/rustc_codegen_ssa/src/mir/statement.rs70
-rw-r--r--compiler/rustc_codegen_ssa/src/traits/debuginfo.rs14
-rw-r--r--compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp1
-rw-r--r--tests/codegen-llvm/debuginfo-dse.rs160
-rw-r--r--tests/debuginfo/opt/dead_refs.rs50
12 files changed, 431 insertions, 14 deletions
diff --git a/compiler/rustc_codegen_gcc/src/debuginfo.rs b/compiler/rustc_codegen_gcc/src/debuginfo.rs
index 4c8585192a1..0f015cc23f5 100644
--- a/compiler/rustc_codegen_gcc/src/debuginfo.rs
+++ b/compiler/rustc_codegen_gcc/src/debuginfo.rs
@@ -29,13 +29,24 @@ impl<'a, 'gcc, 'tcx> DebugInfoBuilderMethods for Builder<'a, 'gcc, 'tcx> {
         _variable_alloca: Self::Value,
         _direct_offset: Size,
         _indirect_offsets: &[Size],
-        _fragment: Option<Range<Size>>,
+        _fragment: &Option<Range<Size>>,
     ) {
         // FIXME(tempdragon): Not sure if this is correct, probably wrong but still keep it here.
         #[cfg(feature = "master")]
         _variable_alloca.set_location(_dbg_loc);
     }
 
+    fn dbg_var_value(
+        &mut self,
+        _dbg_var: Self::DIVariable,
+        _dbg_loc: Self::DILocation,
+        _value: Self::Value,
+        _direct_offset: Size,
+        _indirect_offsets: &[Size],
+        _fragment: &Option<Range<Size>>,
+    ) {
+    }
+
     fn insert_reference_to_gdb_debug_scripts_section_global(&mut self) {
         // TODO(antoyo): insert reference to gdb debug scripts section global.
     }
diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/dwarf_const.rs b/compiler/rustc_codegen_llvm/src/debuginfo/dwarf_const.rs
index 40842915222..52d04625749 100644
--- a/compiler/rustc_codegen_llvm/src/debuginfo/dwarf_const.rs
+++ b/compiler/rustc_codegen_llvm/src/debuginfo/dwarf_const.rs
@@ -35,3 +35,6 @@ declare_constant!(DW_OP_plus_uconst: u64);
 /// Double-checked by a static assertion in `RustWrapper.cpp`.
 #[allow(non_upper_case_globals)]
 pub(crate) const DW_OP_LLVM_fragment: u64 = 0x1000;
+// It describes the actual value of a source variable which might not exist in registers or in memory.
+#[allow(non_upper_case_globals)]
+pub(crate) const DW_OP_stack_value: u64 = 0x9f;
diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs
index af64e4ebed0..c6ad1c2e18e 100644
--- a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs
+++ b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs
@@ -156,7 +156,7 @@ impl<'ll> DebugInfoBuilderMethods for Builder<'_, 'll, '_> {
         variable_alloca: Self::Value,
         direct_offset: Size,
         indirect_offsets: &[Size],
-        fragment: Option<Range<Size>>,
+        fragment: &Option<Range<Size>>,
     ) {
         use dwarf_const::{DW_OP_LLVM_fragment, DW_OP_deref, DW_OP_plus_uconst};
 
@@ -187,7 +187,6 @@ impl<'ll> DebugInfoBuilderMethods for Builder<'_, 'll, '_> {
             llvm::LLVMDIBuilderCreateExpression(di_builder, addr_ops.as_ptr(), addr_ops.len())
         };
         unsafe {
-            // FIXME(eddyb) replace `llvm.dbg.declare` with `llvm.dbg.addr`.
             llvm::LLVMDIBuilderInsertDeclareRecordAtEnd(
                 di_builder,
                 variable_alloca,
@@ -199,6 +198,56 @@ impl<'ll> DebugInfoBuilderMethods for Builder<'_, 'll, '_> {
         };
     }
 
+    fn dbg_var_value(
+        &mut self,
+        dbg_var: &'ll DIVariable,
+        dbg_loc: &'ll DILocation,
+        value: Self::Value,
+        direct_offset: Size,
+        indirect_offsets: &[Size],
+        fragment: &Option<Range<Size>>,
+    ) {
+        use dwarf_const::{DW_OP_LLVM_fragment, DW_OP_deref, DW_OP_plus_uconst, DW_OP_stack_value};
+
+        // Convert the direct and indirect offsets and fragment byte range to address ops.
+        let mut addr_ops = SmallVec::<[u64; 8]>::new();
+
+        if direct_offset.bytes() > 0 {
+            addr_ops.push(DW_OP_plus_uconst);
+            addr_ops.push(direct_offset.bytes() as u64);
+            addr_ops.push(DW_OP_stack_value);
+        }
+        for &offset in indirect_offsets {
+            addr_ops.push(DW_OP_deref);
+            if offset.bytes() > 0 {
+                addr_ops.push(DW_OP_plus_uconst);
+                addr_ops.push(offset.bytes() as u64);
+            }
+        }
+        if let Some(fragment) = fragment {
+            // `DW_OP_LLVM_fragment` takes as arguments the fragment's
+            // offset and size, both of them in bits.
+            addr_ops.push(DW_OP_LLVM_fragment);
+            addr_ops.push(fragment.start.bits() as u64);
+            addr_ops.push((fragment.end - fragment.start).bits() as u64);
+        }
+
+        let di_builder = DIB(self.cx());
+        let addr_expr = unsafe {
+            llvm::LLVMDIBuilderCreateExpression(di_builder, addr_ops.as_ptr(), addr_ops.len())
+        };
+        unsafe {
+            llvm::LLVMDIBuilderInsertDbgValueRecordAtEnd(
+                di_builder,
+                value,
+                dbg_var,
+                addr_expr,
+                dbg_loc,
+                self.llbb(),
+            );
+        }
+    }
+
     fn set_dbg_loc(&mut self, dbg_loc: &'ll DILocation) {
         unsafe {
             llvm::LLVMSetCurrentDebugLocation2(self.llbuilder, dbg_loc);
diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs
index e9f92267a7d..7fbba029407 100644
--- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs
+++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs
@@ -1991,6 +1991,15 @@ unsafe extern "C" {
         Block: &'ll BasicBlock,
     ) -> &'ll DbgRecord;
 
+    pub(crate) fn LLVMDIBuilderInsertDbgValueRecordAtEnd<'ll>(
+        Builder: &DIBuilder<'ll>,
+        Val: &'ll Value,
+        VarInfo: &'ll Metadata,
+        Expr: &'ll Metadata,
+        DebugLoc: &'ll Metadata,
+        Block: &'ll BasicBlock,
+    ) -> &'ll DbgRecord;
+
     pub(crate) fn LLVMDIBuilderCreateAutoVariable<'ll>(
         Builder: &DIBuilder<'ll>,
         Scope: &'ll Metadata,
diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs
index b2dc4fe32b0..e371f1a7623 100644
--- a/compiler/rustc_codegen_ssa/src/mir/block.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/block.rs
@@ -1320,6 +1320,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
             for statement in &data.statements {
                 self.codegen_statement(bx, statement);
             }
+            self.codegen_stmt_debuginfos(bx, &data.after_last_stmt_debuginfos);
 
             let merging_succ = self.codegen_terminator(bx, bb, data.terminator());
             if let MergingSucc::False = merging_succ {
diff --git a/compiler/rustc_codegen_ssa/src/mir/debuginfo.rs b/compiler/rustc_codegen_ssa/src/mir/debuginfo.rs
index b8f635ab781..38bb6f24b1c 100644
--- a/compiler/rustc_codegen_ssa/src/mir/debuginfo.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/debuginfo.rs
@@ -253,6 +253,54 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
         spill_slot
     }
 
+    // Indicates that local is set to a new value. The `layout` and `projection` are used to
+    // calculate the offset.
+    pub(crate) fn debug_new_val_to_local(
+        &self,
+        bx: &mut Bx,
+        local: mir::Local,
+        base: PlaceValue<Bx::Value>,
+        layout: TyAndLayout<'tcx>,
+        projection: &[mir::PlaceElem<'tcx>],
+    ) {
+        let full_debug_info = bx.sess().opts.debuginfo == DebugInfo::Full;
+        if !full_debug_info {
+            return;
+        }
+
+        let vars = match &self.per_local_var_debug_info {
+            Some(per_local) => &per_local[local],
+            None => return,
+        };
+
+        let DebugInfoOffset { direct_offset, indirect_offsets, result: _ } =
+            calculate_debuginfo_offset(bx, projection, layout);
+        for var in vars.iter() {
+            let Some(dbg_var) = var.dbg_var else {
+                continue;
+            };
+            let Some(dbg_loc) = self.dbg_loc(var.source_info) else {
+                continue;
+            };
+            bx.dbg_var_value(
+                dbg_var,
+                dbg_loc,
+                base.llval,
+                direct_offset,
+                &indirect_offsets,
+                &var.fragment,
+            );
+        }
+    }
+
+    pub(crate) fn debug_poison_to_local(&self, bx: &mut Bx, local: mir::Local) {
+        let ty = self.monomorphize(self.mir.local_decls[local].ty);
+        let layout = bx.cx().layout_of(ty);
+        let to_backend_ty = bx.cx().immediate_backend_type(layout);
+        let place_ref = PlaceRef::new_sized(bx.cx().const_poison(to_backend_ty), layout);
+        self.debug_new_val_to_local(bx, local, place_ref.val, layout, &[]);
+    }
+
     /// Apply debuginfo and/or name, after creating the `alloca` for a local,
     /// or initializing the local with an operand (whichever applies).
     pub(crate) fn debug_introduce_local(&self, bx: &mut Bx, local: mir::Local) {
@@ -424,7 +472,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                 alloca.val.llval,
                 Size::ZERO,
                 &[Size::ZERO],
-                var.fragment,
+                &var.fragment,
             );
         } else {
             bx.dbg_var_addr(
@@ -433,7 +481,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                 base.val.llval,
                 direct_offset,
                 &indirect_offsets,
-                var.fragment,
+                &var.fragment,
             );
         }
     }
@@ -455,7 +503,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                 let base = FunctionCx::spill_operand_to_stack(operand, Some(name), bx);
                 bx.clear_dbg_loc();
 
-                bx.dbg_var_addr(dbg_var, dbg_loc, base.val.llval, Size::ZERO, &[], fragment);
+                bx.dbg_var_addr(dbg_var, dbg_loc, base.val.llval, Size::ZERO, &[], &fragment);
             }
         }
     }
diff --git a/compiler/rustc_codegen_ssa/src/mir/operand.rs b/compiler/rustc_codegen_ssa/src/mir/operand.rs
index 5f7f87fc692..88a8e2a844c 100644
--- a/compiler/rustc_codegen_ssa/src/mir/operand.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/operand.rs
@@ -71,16 +71,23 @@ pub enum OperandValue<V> {
 }
 
 impl<V: CodegenObject> OperandValue<V> {
+    /// Return the data pointer and optional metadata as backend values
+    /// if this value can be treat as a pointer.
+    pub(crate) fn try_pointer_parts(self) -> Option<(V, Option<V>)> {
+        match self {
+            OperandValue::Immediate(llptr) => Some((llptr, None)),
+            OperandValue::Pair(llptr, llextra) => Some((llptr, Some(llextra))),
+            OperandValue::Ref(_) | OperandValue::ZeroSized => None,
+        }
+    }
+
     /// Treat this value as a pointer and return the data pointer and
     /// optional metadata as backend values.
     ///
     /// If you're making a place, use [`Self::deref`] instead.
     pub(crate) fn pointer_parts(self) -> (V, Option<V>) {
-        match self {
-            OperandValue::Immediate(llptr) => (llptr, None),
-            OperandValue::Pair(llptr, llextra) => (llptr, Some(llextra)),
-            _ => bug!("OperandValue cannot be a pointer: {self:?}"),
-        }
+        self.try_pointer_parts()
+            .unwrap_or_else(|| bug!("OperandValue cannot be a pointer: {self:?}"))
     }
 
     /// Treat this value as a pointer and return the place to which it points.
diff --git a/compiler/rustc_codegen_ssa/src/mir/statement.rs b/compiler/rustc_codegen_ssa/src/mir/statement.rs
index 0a50d7f18db..99bb31a68b6 100644
--- a/compiler/rustc_codegen_ssa/src/mir/statement.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/statement.rs
@@ -1,13 +1,17 @@
-use rustc_middle::mir::{self, NonDivergingIntrinsic};
-use rustc_middle::span_bug;
+use rustc_middle::mir::{self, NonDivergingIntrinsic, RETURN_PLACE, StmtDebugInfo};
+use rustc_middle::{bug, span_bug};
+use rustc_target::callconv::PassMode;
 use tracing::instrument;
 
 use super::{FunctionCx, LocalRef};
+use crate::common::TypeKind;
+use crate::mir::place::PlaceRef;
 use crate::traits::*;
 
 impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
     #[instrument(level = "debug", skip(self, bx))]
     pub(crate) fn codegen_statement(&mut self, bx: &mut Bx, statement: &mir::Statement<'tcx>) {
+        self.codegen_stmt_debuginfos(bx, &statement.debuginfos);
         self.set_debug_loc(bx, statement.source_info);
         match statement.kind {
             mir::StatementKind::Assign(box (ref place, ref rvalue)) => {
@@ -101,4 +105,66 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
             | mir::StatementKind::Nop => {}
         }
     }
+
+    pub(crate) fn codegen_stmt_debuginfo(&mut self, bx: &mut Bx, debuginfo: &StmtDebugInfo<'tcx>) {
+        match debuginfo {
+            StmtDebugInfo::AssignRef(dest, place) => {
+                let local_ref = match self.locals[place.local] {
+                    LocalRef::Place(place_ref) | LocalRef::UnsizedPlace(place_ref) => {
+                        Some(place_ref)
+                    }
+                    LocalRef::Operand(operand_ref) => operand_ref
+                        .val
+                        .try_pointer_parts()
+                        .map(|(pointer, _)| PlaceRef::new_sized(pointer, operand_ref.layout)),
+                    LocalRef::PendingOperand => None,
+                }
+                .filter(|place_ref| {
+                    // For the reference of an argument (e.x. `&_1`), it's only valid if the pass mode is indirect, and its reference is
+                    // llval.
+                    let local_ref_pass_mode = place.as_local().and_then(|local| {
+                        if local == RETURN_PLACE {
+                            None
+                        } else {
+                            self.fn_abi.args.get(local.as_usize() - 1).map(|arg| &arg.mode)
+                        }
+                    });
+                    matches!(local_ref_pass_mode, Some(&PassMode::Indirect {..}) | None) &&
+                    // Drop unsupported projections.
+                    place.projection.iter().all(|p| p.can_use_in_debuginfo()) &&
+                    // Only pointers can be calculated addresses.
+                    bx.type_kind(bx.val_ty(place_ref.val.llval)) == TypeKind::Pointer
+                });
+                if let Some(local_ref) = local_ref {
+                    let (base_layout, projection) = if place.is_indirect_first_projection() {
+                        // For `_n = &((*_1).0: i32);`, we are calculating the address of `_1.0`, so
+                        // we should drop the deref projection.
+                        let projected_ty = local_ref
+                            .layout
+                            .ty
+                            .builtin_deref(true)
+                            .unwrap_or_else(|| bug!("deref of non-pointer {:?}", local_ref));
+                        let layout = bx.cx().layout_of(projected_ty);
+                        (layout, &place.projection[1..])
+                    } else {
+                        (local_ref.layout, place.projection.as_slice())
+                    };
+                    self.debug_new_val_to_local(bx, *dest, local_ref.val, base_layout, projection);
+                } else {
+                    // If the address cannot be computed, use poison to indicate that the value has been optimized out.
+                    self.debug_poison_to_local(bx, *dest);
+                }
+            }
+        }
+    }
+
+    pub(crate) fn codegen_stmt_debuginfos(
+        &mut self,
+        bx: &mut Bx,
+        debuginfos: &[StmtDebugInfo<'tcx>],
+    ) {
+        for debuginfo in debuginfos {
+            self.codegen_stmt_debuginfo(bx, debuginfo);
+        }
+    }
 }
diff --git a/compiler/rustc_codegen_ssa/src/traits/debuginfo.rs b/compiler/rustc_codegen_ssa/src/traits/debuginfo.rs
index b9d4950e0ad..a4da6c915de 100644
--- a/compiler/rustc_codegen_ssa/src/traits/debuginfo.rs
+++ b/compiler/rustc_codegen_ssa/src/traits/debuginfo.rs
@@ -77,7 +77,19 @@ pub trait DebugInfoBuilderMethods: BackendTypes {
         indirect_offsets: &[Size],
         // Byte range in the `dbg_var` covered by this fragment,
         // if this is a fragment of a composite `DIVariable`.
-        fragment: Option<Range<Size>>,
+        fragment: &Option<Range<Size>>,
+    );
+    fn dbg_var_value(
+        &mut self,
+        dbg_var: Self::DIVariable,
+        dbg_loc: Self::DILocation,
+        value: Self::Value,
+        direct_offset: Size,
+        // NB: each offset implies a deref (i.e. they're steps in a pointer chain).
+        indirect_offsets: &[Size],
+        // Byte range in the `dbg_var` covered by this fragment,
+        // if this is a fragment of a composite `DIVariable`.
+        fragment: &Option<Range<Size>>,
     );
     fn set_dbg_loc(&mut self, dbg_loc: Self::DILocation);
     fn clear_dbg_loc(&mut self);
diff --git a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp
index 2b83ea24ac6..e38474f09ff 100644
--- a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp
+++ b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp
@@ -58,6 +58,7 @@ using namespace llvm::object;
 // This opcode is an LLVM detail that could hypothetically change (?), so
 // verify that the hard-coded value in `dwarf_const.rs` still agrees with LLVM.
 static_assert(dwarf::DW_OP_LLVM_fragment == 0x1000);
+static_assert(dwarf::DW_OP_stack_value == 0x9f);
 
 // LLVMAtomicOrdering is already an enum - don't create another
 // one.
diff --git a/tests/codegen-llvm/debuginfo-dse.rs b/tests/codegen-llvm/debuginfo-dse.rs
new file mode 100644
index 00000000000..1fcb5fcfc54
--- /dev/null
+++ b/tests/codegen-llvm/debuginfo-dse.rs
@@ -0,0 +1,160 @@
+//@ compile-flags: -Copt-level=3 -g -Zverify-llvm-ir
+//@ revisions: CODEGEN OPTIMIZED
+//@[CODEGEN] compile-flags: -Cno-prepopulate-passes
+// ignore-tidy-linelength
+
+#![crate_type = "lib"]
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct Foo(i32, i64, i32);
+
+#[no_mangle]
+fn r#ref(ref_foo: &Foo) -> i32 {
+    // CHECK-LABEL: define{{.*}} i32 @ref
+    // CHECK-SAME: (ptr {{.*}} [[ARG_ref_foo:%.*]])
+    // OPTIMIZED: #dbg_value(ptr [[ARG_ref_foo]], [[VAR_ref_foo:![0-9]+]], !DIExpression()
+    // CHECK: #dbg_value(ptr poison, [[VAR_invalid_ref_of_ref_foo:![0-9]+]], !DIExpression()
+    // CHECK: #dbg_value(ptr [[ARG_ref_foo]], [[VAR_ref_v0:![0-9]+]], !DIExpression()
+    // CHECK: #dbg_value(ptr [[ARG_ref_foo]], [[VAR_ref_v1:![0-9]+]], !DIExpression(DW_OP_plus_uconst, 8, DW_OP_stack_value)
+    // CHECK: #dbg_value(ptr [[ARG_ref_foo]], [[VAR_ref_v2:![0-9]+]], !DIExpression(DW_OP_plus_uconst, 16, DW_OP_stack_value)
+    let invalid_ref_of_ref_foo = &ref_foo;
+    let ref_v0 = &ref_foo.0;
+    let ref_v1 = &ref_foo.1;
+    let ref_v2 = &ref_foo.2;
+    ref_foo.0
+}
+
+#[no_mangle]
+pub fn dead_first(dead_first_foo: &Foo) -> &i32 {
+    // CHECK-LABEL: def {{.*}} ptr @dead_first
+    // CHECK-SAME: (ptr {{.*}} [[ARG_dead_first_foo:%.*]])
+    // CODEGEN: #dbg_declare(ptr %dead_first_foo.dbg.spill, [[ARG_dead_first_foo:![0-9]+]], !DIExpression()
+    // OPTIMIZED: #dbg_value(ptr %dead_first_foo, [[ARG_dead_first_foo:![0-9]+]], !DIExpression()
+    // CHECK: #dbg_value(ptr %dead_first_foo, [[VAR_dead_first_v0:![0-9]+]], !DIExpression()
+    // CHECK: %dead_first_v0 = getelementptr{{.*}} i8, ptr %dead_first_foo, i64 16
+    // CODEGEN: #dbg_declare(ptr %dead_first_v0.dbg.spill, [[VAR_dead_first_v0]], !DIExpression()
+    // OPTIMIZED: #dbg_value(ptr %dead_first_v0, [[VAR_dead_first_v0]], !DIExpression()
+    let mut dead_first_v0 = &dead_first_foo.0;
+    dead_first_v0 = &dead_first_foo.2;
+    dead_first_v0
+}
+
+#[no_mangle]
+fn ptr(ptr_foo: Foo) -> i32 {
+    // CHECK-LABEL: define{{.*}} i32 @ptr
+    // CHECK-SAME: (ptr {{.*}} [[ARG_ptr_foo:%.*]])
+    // CHECK: #dbg_value(ptr [[ARG_ptr_foo]], [[ref_ptr_foo:![0-9]+]], !DIExpression()
+    // CHECK: #dbg_value(ptr [[ARG_ptr_foo]], [[VAR_ptr_v0:![0-9]+]], !DIExpression()
+    // CHECK: #dbg_value(ptr [[ARG_ptr_foo]], [[VAR_ptr_v1:![0-9]+]], !DIExpression(DW_OP_plus_uconst, 8, DW_OP_stack_value)
+    // CHECK: #dbg_value(ptr [[ARG_ptr_foo]], [[VAR_ptr_v2:![0-9]+]], !DIExpression(DW_OP_plus_uconst, 16, DW_OP_stack_value)
+    let ref_ptr_foo = &ptr_foo;
+    let ptr_v0 = &ptr_foo.0;
+    let ptr_v1 = &ptr_foo.1;
+    let ptr_v2 = &ptr_foo.2;
+    ptr_foo.2
+}
+
+#[no_mangle]
+fn no_ptr(val: i32) -> i32 {
+    // CHECK-LABEL: define{{.*}} i32 @no_ptr
+    // CODEGEN: #dbg_value(ptr poison, [[VAR_val_ref:![0-9]+]], !DIExpression()
+    let val_ref = &val;
+    val
+}
+
+#[no_mangle]
+pub fn fragment(fragment_v1: Foo, mut fragment_v2: Foo) -> Foo {
+    // CHECK-LABEL: define void @fragment
+    // CHECK-SAME: (ptr {{.*}}, ptr {{.*}} [[ARG_fragment_v1:%.*]], ptr {{.*}} [[ARG_fragment_v2:%.*]])
+    // CHECK: #dbg_declare(ptr [[ARG_fragment_v1]]
+    // CHECK-NEXT: #dbg_declare(ptr [[ARG_fragment_v2]]
+    // CHECK-NEXT: #dbg_value(ptr [[ARG_fragment_v2]], [[VAR_fragment_f:![0-9]+]], !DIExpression(DW_OP_LLVM_fragment, 0, 64)
+    // CHECK-NEXT: #dbg_value(ptr [[ARG_fragment_v1]], [[VAR_fragment_f:![0-9]+]], !DIExpression(DW_OP_LLVM_fragment, 64, 64)
+    let fragment_f = || {
+        fragment_v2 = fragment_v1;
+    };
+    fragment_v2 = fragment_v1;
+    fragment_v2
+}
+
+#[no_mangle]
+pub fn tuple(foo: (i32, &Foo)) -> i32 {
+    // CHECK-LABEL: define{{.*}} i32 @tuple
+    // CHECK-SAME: (i32 {{.*}}, ptr {{.*}} [[ARG_tuple_foo_1:%.*]])
+    // CHECK: #dbg_value(ptr [[ARG_tuple_foo_1]], [[VAR_tuple_dead:![0-9]+]], !DIExpression(DW_OP_plus_uconst, 16, DW_OP_stack_value)
+    let tuple_dead = &foo.1.2;
+    foo.1.0
+}
+
+pub struct ZST;
+
+#[no_mangle]
+pub fn zst(zst: ZST, v: &i32) -> i32 {
+    // CHECK-LABEL: define{{.*}} i32 @zst
+    // CHECK: #dbg_value(ptr poison, [[VAR_zst_ref:![0-9]+]], !DIExpression()
+    let zst_ref = &zst;
+    *v
+}
+
+#[no_mangle]
+fn index(slice: &[i32; 4], idx: usize) -> i32 {
+    // CHECK-LABEL: define{{.*}} i32 @index
+    // CHECK: bb1:
+    // CHECK-NEXT: #dbg_value(ptr poison, [[VAR_index_from_var:![0-9]+]], !DIExpression()
+    // CODEGEN: bb3:
+    // CHECK-NEXT: #dbg_value(ptr %slice, [[VAR_const_index_from_start:![0-9]+]], !DIExpression()
+    // CHECK-NEXT: #dbg_value(ptr poison, [[VAR_const_index_from_end:![0-9]+]], !DIExpression()
+    let index_from_var = &slice[idx];
+    let [ref const_index_from_start, .., ref const_index_from_end] = slice[..] else {
+        return 0;
+    };
+    slice[0]
+}
+
+unsafe extern "Rust" {
+    safe fn opaque_inner(_: *const core::ffi::c_void);
+}
+
+#[inline(never)]
+pub fn opaque_use<T>(p: &T) {
+    opaque_inner(&raw const p as *const _);
+}
+
+#[no_mangle]
+pub fn non_arg_ref(scalar: i32, foo: Foo, a: &i32) -> i32 {
+    // CHECK-LABEL: define{{.*}} i32 @non_arg_ref
+    // CHECK: #dbg_value(ptr %non_arg_ref_scalar, [[VAR_non_arg_ref_scalar_ref:![0-9]+]], !DIExpression()
+    // CHECK-NEXT: #dbg_value(ptr %non_arg_ref_foo, [[VAR_non_arg_ref_foo_ref:![0-9]+]], !DIExpression()
+    // CHECK-NEXT: #dbg_value(ptr %non_arg_ref_foo, [[VAR_non_arg_ref_foo_ref_2:![0-9]+]], !DIExpression(DW_OP_plus_uconst, 16, DW_OP_stack_value)
+    let non_arg_ref_scalar = scalar;
+    let non_arg_ref_foo = foo;
+    opaque_use(&non_arg_ref_scalar);
+    opaque_use(&non_arg_ref_foo);
+    let non_arg_ref_scalar_ref = &non_arg_ref_scalar;
+    let non_arg_ref_foo_ref = &non_arg_ref_foo;
+    let non_arg_ref_foo_ref_2 = &non_arg_ref_foo.2;
+    *a
+}
+
+// CHECK-DAG: [[VAR_invalid_ref_of_ref_foo]] = !DILocalVariable(name: "invalid_ref_of_ref_foo"
+// OPTIMIZED-DAG: [[VAR_ref_foo]] = !DILocalVariable(name: "ref_foo"
+// CHECK-DAG: [[VAR_ref_v0]] = !DILocalVariable(name: "ref_v0"
+// CHECK-DAG: [[VAR_ref_v1]] = !DILocalVariable(name: "ref_v1"
+// CHECK-DAG: [[VAR_ref_v2]] = !DILocalVariable(name: "ref_v2"
+// CHECK-DAG: [[ref_ptr_foo]] = !DILocalVariable(name: "ref_ptr_foo"
+// CHECK-DAG: [[VAR_ptr_v0]] = !DILocalVariable(name: "ptr_v0"
+// CHECK-DAG: [[VAR_ptr_v1]] = !DILocalVariable(name: "ptr_v1"
+// CHECK-DAG: [[VAR_ptr_v2]] = !DILocalVariable(name: "ptr_v2"
+// CODEGEN-DAG: [[VAR_val_ref]] = !DILocalVariable(name: "val_ref"
+// CHECK-DAG: [[VAR_fragment_f]] = !DILocalVariable(name: "fragment_f"
+// CHECK-DAG: [[VAR_tuple_dead]] = !DILocalVariable(name: "tuple_dead"
+// CHECK-DAG: [[ARG_dead_first_foo]] = !DILocalVariable(name: "dead_first_foo"
+// CHECK-DAG: [[VAR_dead_first_v0]] = !DILocalVariable(name: "dead_first_v0"
+// CHECK-DAG: [[VAR_index_from_var]] = !DILocalVariable(name: "index_from_var"
+// CHECK-DAG: [[VAR_const_index_from_start]] = !DILocalVariable(name: "const_index_from_start"
+// CHECK-DAG: [[VAR_const_index_from_end]] = !DILocalVariable(name: "const_index_from_end"
+// CHECK-DAG: [[VAR_zst_ref]] = !DILocalVariable(name: "zst_ref"
+// CHECK-DAG: [[VAR_non_arg_ref_scalar_ref]] = !DILocalVariable(name: "non_arg_ref_scalar_ref"
+// CHECK-DAG: [[VAR_non_arg_ref_foo_ref]] = !DILocalVariable(name: "non_arg_ref_foo_ref"
+// CHECK-DAG: [[VAR_non_arg_ref_foo_ref_2]] = !DILocalVariable(name: "non_arg_ref_foo_ref_2"
diff --git a/tests/debuginfo/opt/dead_refs.rs b/tests/debuginfo/opt/dead_refs.rs
new file mode 100644
index 00000000000..61e74157334
--- /dev/null
+++ b/tests/debuginfo/opt/dead_refs.rs
@@ -0,0 +1,50 @@
+//@ min-lldb-version: 1800
+//@ min-gdb-version: 13.0
+//@ compile-flags: -g -Copt-level=3
+//@ disable-gdb-pretty-printers
+
+// Checks that we still can access dead variables from debuginfos.
+
+// === GDB TESTS ===================================================================================
+
+// gdb-command:run
+// gdb-command:print *ref_v0
+// gdb-check:$1 = 0
+
+// gdb-command:print *ref_v1
+// gdb-check:$2 = 1
+
+// gdb-command:print *ref_v2
+// gdb-check:$3 = 2
+
+// === LLDB TESTS ==================================================================================
+
+// lldb-command:run
+// lldb-command:v *ref_v0
+// lldb-check:[...] 0
+
+// lldb-command:v *ref_v1
+// lldb-check:[...] 1
+
+// lldb-command:v *ref_v2
+// lldb-check:[...] 2
+
+#![allow(unused_variables)]
+
+use std::hint::black_box;
+
+pub struct Foo(i32, i64, i32);
+
+#[inline(never)]
+#[no_mangle]
+fn test_ref(ref_foo: &Foo) -> i32 {
+    let ref_v0 = &ref_foo.0;
+    let ref_v1 = &ref_foo.1;
+    let ref_v2 = &ref_foo.2;
+    ref_foo.0 // #break
+}
+
+fn main() {
+    let foo = black_box(Foo(0, 1, 2));
+    black_box(test_ref(&foo));
+}