codegen: Generate dbg_value for the ref statement
This commit is contained in:
parent
85b2f70693
commit
1bd89bd42e
12 changed files with 431 additions and 14 deletions
|
|
@ -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.
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
160
tests/codegen-llvm/debuginfo-dse.rs
Normal file
160
tests/codegen-llvm/debuginfo-dse.rs
Normal 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"
|
||||
50
tests/debuginfo/opt/dead_refs.rs
Normal file
50
tests/debuginfo/opt/dead_refs.rs
Normal 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));
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue