codegen: Generate dbg_value for the ref statement

This commit is contained in:
dianqk 2025-06-18 22:04:48 +08:00
parent 85b2f70693
commit 1bd89bd42e
No known key found for this signature in database
12 changed files with 431 additions and 14 deletions

View file

@ -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.
}

View file

@ -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;

View file

@ -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);

View file

@ -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,

View file

@ -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 {

View file

@ -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);
}
}
}

View file

@ -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.

View file

@ -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);
}
}
}

View file

@ -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);

View file

@ -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.

View file

@ -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"

View file

@ -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));
}