Skip to content

Name the captured upvars for closures/generators in debuginfo #85020

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 14, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs
Original file line number Diff line number Diff line change
@@ -1280,6 +1280,31 @@ fn prepare_struct_metadata(
// Tuples
//=-----------------------------------------------------------------------------

/// Returns names of captured upvars for closures and generators.
///
/// Here are some examples:
/// - `name__field1__field2` when the upvar is captured by value.
/// - `_ref__name__field` when the upvar is captured by reference.
fn closure_saved_names_of_captured_variables(tcx: TyCtxt<'tcx>, def_id: DefId) -> Vec<String> {
let body = tcx.optimized_mir(def_id);

body.var_debug_info
.iter()
.filter_map(|var| {
let is_ref = match var.value {
mir::VarDebugInfoContents::Place(place) if place.local == mir::Local::new(1) => {
// The projection is either `[.., Field, Deref]` or `[.., Field]`. It
// implies whether the variable is captured by value or by reference.
matches!(place.projection.last().unwrap(), mir::ProjectionElem::Deref)
}
_ => return None,
};
let prefix = if is_ref { "_ref__" } else { "" };
Some(prefix.to_owned() + &var.name.as_str())
})
.collect::<Vec<_>>()
}

/// Creates `MemberDescription`s for the fields of a tuple.
struct TupleMemberDescriptionFactory<'tcx> {
ty: Ty<'tcx>,
@@ -1289,14 +1314,25 @@ struct TupleMemberDescriptionFactory<'tcx> {

impl<'tcx> TupleMemberDescriptionFactory<'tcx> {
fn create_member_descriptions(&self, cx: &CodegenCx<'ll, 'tcx>) -> Vec<MemberDescription<'ll>> {
let mut capture_names = match *self.ty.kind() {
ty::Generator(def_id, ..) | ty::Closure(def_id, ..) => {
Some(closure_saved_names_of_captured_variables(cx.tcx, def_id).into_iter())
}
_ => None,
};
let layout = cx.layout_of(self.ty);
self.component_types
.iter()
.enumerate()
.map(|(i, &component_type)| {
let (size, align) = cx.size_and_align_of(component_type);
let name = if let Some(names) = capture_names.as_mut() {
names.next().unwrap()
} else {
format!("__{}", i)
};
MemberDescription {
name: format!("__{}", i),
name,
type_metadata: type_metadata(cx, component_type, self.span),
offset: layout.fields.offset(i),
size,
2 changes: 1 addition & 1 deletion compiler/rustc_middle/src/dep_graph/dep_node.rs
Original file line number Diff line number Diff line change
@@ -285,7 +285,7 @@ pub type DepNode = rustc_query_system::dep_graph::DepNode<DepKind>;
// required that their size stay the same, but we don't want to change
// it inadvertently. This assert just ensures we're aware of any change.
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
static_assert_size!(DepNode, 17);
static_assert_size!(DepNode, 18);

#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
static_assert_size!(DepNode, 24);
10 changes: 10 additions & 0 deletions compiler/rustc_middle/src/query/mod.rs
Original file line number Diff line number Diff line change
@@ -329,6 +329,16 @@ rustc_queries! {
}
}

query symbols_for_closure_captures(
key: (LocalDefId, DefId)
) -> Vec<rustc_span::Symbol> {
desc {
|tcx| "symbols for captures of closure `{}` in `{}`",
tcx.def_path_str(key.1),
tcx.def_path_str(key.0.to_def_id())
}
}

/// MIR after our optimization passes have run. This is MIR that is ready
/// for codegen. This is also the only query that can fetch non-local MIR, at present.
query optimized_mir(key: DefId) -> &'tcx mir::Body<'tcx> {
54 changes: 53 additions & 1 deletion compiler/rustc_middle/src/ty/closure.rs
Original file line number Diff line number Diff line change
@@ -3,10 +3,12 @@ use crate::hir::place::{
};
use crate::{mir, ty};

use std::fmt::Write;

use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
use rustc_hir as hir;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_span::Span;
use rustc_span::{Span, Symbol};

use super::{Ty, TyCtxt};

@@ -159,6 +161,43 @@ impl CapturedPlace<'tcx> {
place_to_string_for_capture(tcx, &self.place)
}

/// Returns a symbol of the captured upvar, which looks like `name__field1__field2`.
fn to_symbol(&self, tcx: TyCtxt<'tcx>) -> Symbol {
let hir_id = match self.place.base {
HirPlaceBase::Upvar(upvar_id) => upvar_id.var_path.hir_id,
base => bug!("Expected an upvar, found {:?}", base),
};
let mut symbol = tcx.hir().name(hir_id).as_str().to_string();

let mut ty = self.place.base_ty;
for proj in self.place.projections.iter() {
match proj.kind {
HirProjectionKind::Field(idx, variant) => match ty.kind() {
ty::Tuple(_) => write!(&mut symbol, "__{}", idx).unwrap(),
ty::Adt(def, ..) => {
write!(
&mut symbol,
"__{}",
def.variants[variant].fields[idx as usize].ident.name.as_str(),
)
.unwrap();
}
ty => {
bug!("Unexpected type {:?} for `Field` projection", ty)
}
},

// Ignore derefs for now, as they are likely caused by
// autoderefs that don't appear in the original code.
HirProjectionKind::Deref => {}
proj => bug!("Unexpected projection {:?} in captured place", proj),
}
ty = proj.ty;
}

Symbol::intern(&symbol)
}

/// Returns the hir-id of the root variable for the captured place.
/// e.g., if `a.b.c` was captured, would return the hir-id for `a`.
pub fn get_root_variable(&self) -> hir::HirId {
@@ -209,6 +248,15 @@ impl CapturedPlace<'tcx> {
}
}

fn symbols_for_closure_captures<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: (LocalDefId, DefId),
) -> Vec<Symbol> {
let typeck_results = tcx.typeck(def_id.0);
let captures = typeck_results.closure_min_captures_flattened(def_id.1);
captures.into_iter().map(|captured_place| captured_place.to_symbol(tcx)).collect()
}

/// Return true if the `proj_possible_ancestor` represents an ancestor path
/// to `proj_capture` or `proj_possible_ancestor` is same as `proj_capture`,
/// assuming they both start off of the same root variable.
@@ -393,3 +441,7 @@ impl BorrowKind {
}
}
}

pub fn provide(providers: &mut ty::query::Providers) {
*providers = ty::query::Providers { symbols_for_closure_captures, ..*providers }
}
8 changes: 7 additions & 1 deletion compiler/rustc_middle/src/ty/mod.rs
Original file line number Diff line number Diff line change
@@ -16,7 +16,6 @@ pub use self::IntVarValue::*;
pub use self::Variance::*;
pub use adt::*;
pub use assoc::*;
pub use closure::*;
pub use generics::*;
pub use vtable::*;

@@ -55,6 +54,12 @@ pub use rustc_type_ir::*;

pub use self::binding::BindingMode;
pub use self::binding::BindingMode::*;
pub use self::closure::{
is_ancestor_or_same_capture, place_to_string_for_capture, BorrowKind, CaptureInfo,
CapturedPlace, ClosureKind, MinCaptureInformationMap, MinCaptureList,
RootVariableMinCaptureList, UpvarBorrow, UpvarCapture, UpvarCaptureMap, UpvarId, UpvarListMap,
UpvarPath, CAPTURE_STRUCT_LOCAL,
};
pub use self::consts::{Const, ConstInt, ConstKind, InferConst, ScalarInt, Unevaluated, ValTree};
pub use self::context::{
tls, CanonicalUserType, CanonicalUserTypeAnnotation, CanonicalUserTypeAnnotations,
@@ -1979,6 +1984,7 @@ pub fn ast_uint_ty(uty: UintTy) -> ast::UintTy {
}

pub fn provide(providers: &mut ty::query::Providers) {
closure::provide(providers);
context::provide(providers);
erase_regions::provide(providers);
layout::provide(providers);
21 changes: 8 additions & 13 deletions compiler/rustc_mir_build/src/build/mod.rs
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ use rustc_middle::mir::*;
use rustc_middle::thir::{BindingMode, Expr, ExprId, LintLevel, PatKind, Thir};
use rustc_middle::ty::subst::Subst;
use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, TypeckResults};
use rustc_span::symbol::{kw, sym};
use rustc_span::symbol::sym;
use rustc_span::Span;
use rustc_target::spec::abi::Abi;
use rustc_target::spec::PanicStrategy;
@@ -959,13 +959,16 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
ty::Generator(_, substs, _) => ty::UpvarSubsts::Generator(substs),
_ => span_bug!(self.fn_span, "upvars with non-closure env ty {:?}", closure_ty),
};
let def_id = self.def_id.as_local().unwrap();
let capture_syms = tcx.symbols_for_closure_captures((def_id, fn_def_id));
let capture_tys = upvar_substs.upvar_tys();
let captures_with_tys =
hir_typeck_results.closure_min_captures_flattened(fn_def_id).zip(capture_tys);
let captures_with_tys = hir_typeck_results
.closure_min_captures_flattened(fn_def_id)
.zip(capture_tys.zip(capture_syms));

self.upvar_mutbls = captures_with_tys
.enumerate()
.map(|(i, (captured_place, ty))| {
.map(|(i, (captured_place, (ty, sym)))| {
let capture = captured_place.info.capture_kind;
let var_id = match captured_place.place.base {
HirPlaceBase::Upvar(upvar_id) => upvar_id.var_path.hir_id,
@@ -974,14 +977,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {

let mutability = captured_place.mutability;

// FIXME(project-rfc-2229#8): Store more precise information
let mut name = kw::Empty;
if let Some(Node::Binding(pat)) = tcx_hir.find(var_id) {
if let hir::PatKind::Binding(_, _, ident, _) = pat.kind {
name = ident.name;
}
}

let mut projs = closure_env_projs.clone();
projs.push(ProjectionElem::Field(Field::new(i), ty));
match capture {
@@ -992,7 +987,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
};

self.var_debug_info.push(VarDebugInfo {
name,
name: sym,
source_info: SourceInfo::outermost(tcx_hir.span(var_id)),
value: VarDebugInfoContents::Place(Place {
local: ty::CAPTURE_STRUCT_LOCAL,
99 changes: 99 additions & 0 deletions src/test/debuginfo/captured-fields-1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// compile-flags:-g

// === GDB TESTS ===================================================================================

// gdb-command:run
// gdb-command:print test
// gdbr-check:$1 = captured_fields_1::main::{closure#0} {_ref__my_ref__my_field1: 0x[...]}
// gdb-command:continue
// gdb-command:print test
// gdbr-check:$2 = captured_fields_1::main::{closure#1} {_ref__my_ref__my_field2: 0x[...]}
// gdb-command:continue
// gdb-command:print test
// gdbr-check:$3 = captured_fields_1::main::{closure#2} {_ref__my_ref: 0x[...]}
// gdb-command:continue
// gdb-command:print test
// gdbr-check:$4 = captured_fields_1::main::{closure#3} {my_ref: 0x[...]}
// gdb-command:continue
// gdb-command:print test
// gdbr-check:$5 = captured_fields_1::main::{closure#4} {my_var__my_field2: 22}
// gdb-command:continue
// gdb-command:print test
// gdbr-check:$6 = captured_fields_1::main::{closure#5} {my_var: captured_fields_1::MyStruct {my_field1: 11, my_field2: 22}}
// gdb-command:continue

// === LLDB TESTS ==================================================================================

// lldb-command:run
// lldb-command:print test
// lldbg-check:(captured_fields_1::main::{closure#0}) $0 = { _ref__my_ref__my_field1 = 0x[...] }
// lldb-command:continue
// lldb-command:print test
// lldbg-check:(captured_fields_1::main::{closure#1}) $1 = { _ref__my_ref__my_field2 = 0x[...] }
// lldb-command:continue
// lldb-command:print test
// lldbg-check:(captured_fields_1::main::{closure#2}) $2 = { _ref__my_ref = 0x[...] }
// lldb-command:continue
// lldb-command:print test
// lldbg-check:(captured_fields_1::main::{closure#3}) $3 = { my_ref = 0x[...] }
// lldb-command:continue
// lldb-command:print test
// lldbg-check:(captured_fields_1::main::{closure#4}) $4 = { my_var__my_field2 = 22 }
// lldb-command:continue
// lldb-command:print test
// lldbg-check:(captured_fields_1::main::{closure#5}) $5 = { my_var = { my_field1 = 11 my_field2 = 22 } }
// lldb-command:continue

#![feature(capture_disjoint_fields)]
#![allow(unused)]

struct MyStruct {
my_field1: u32,
my_field2: u32,
}

fn main() {
let mut my_var = MyStruct {
my_field1: 11,
my_field2: 22,
};
let my_ref = &mut my_var;

let test = || {
let a = &mut my_ref.my_field1;
};

_zzz(); // #break

let test = || {
let a = &my_ref.my_field2;
};

_zzz(); // #break

let test = || {
let a = &my_ref;
};

_zzz(); // #break

let test = || {
let a = my_ref;
};

_zzz(); // #break

let test = move || {
let a = my_var.my_field2;
};

_zzz(); // #break

let test = || {
let a = my_var;
};

_zzz(); // #break
}

fn _zzz() {}
55 changes: 55 additions & 0 deletions src/test/debuginfo/captured-fields-2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// compile-flags:-g

// === GDB TESTS ===================================================================================

// gdb-command:run
// gdb-command:print my_ref__my_field1
// gdbr-check:$1 = 11
// gdb-command:continue
// gdb-command:print my_var__my_field2
// gdbr-check:$2 = 22
// gdb-command:continue

// === LLDB TESTS ==================================================================================

// lldb-command:run
// lldb-command:print my_ref__my_field1
// lldbg-check:(unsigned int) $0 = 11
// lldb-command:continue
// lldb-command:print my_var__my_field2
// lldbg-check:(unsigned int) $1 = 22
// lldb-command:continue

#![feature(capture_disjoint_fields)]
#![allow(unused)]

struct MyStruct {
my_field1: u32,
my_field2: u32,
}

fn main() {
let mut my_var = MyStruct {
my_field1: 11,
my_field2: 22,
};
let my_ref = &mut my_var;

let test = || {
let a = my_ref.my_field1;

_zzz(); // #break
};

test();

let test = move || {
let a = my_var.my_field2;

_zzz(); // #break
};

test();
}

fn _zzz() {}
8 changes: 4 additions & 4 deletions src/test/debuginfo/generator-objects.rs
Original file line number Diff line number Diff line change
@@ -11,16 +11,16 @@

// gdb-command:run
// gdb-command:print b
// gdb-check:$1 = generator_objects::main::{generator#0}::Unresumed(0x[...])
// gdb-check:$1 = generator_objects::main::{generator#0}::Unresumed{_ref__a: 0x[...]}
// gdb-command:continue
// gdb-command:print b
// gdb-check:$2 = generator_objects::main::{generator#0}::Suspend0{c: 6, d: 7, __0: 0x[...]}
// gdb-check:$2 = generator_objects::main::{generator#0}::Suspend0{c: 6, d: 7, _ref__a: 0x[...]}
// gdb-command:continue
// gdb-command:print b
// gdb-check:$3 = generator_objects::main::{generator#0}::Suspend1{c: 7, d: 8, __0: 0x[...]}
// gdb-check:$3 = generator_objects::main::{generator#0}::Suspend1{c: 7, d: 8, _ref__a: 0x[...]}
// gdb-command:continue
// gdb-command:print b
// gdb-check:$4 = generator_objects::main::{generator#0}::Returned(0x[...])
// gdb-check:$4 = generator_objects::main::{generator#0}::Returned{_ref__a: 0x[...]}

// === LLDB TESTS ==================================================================================

6 changes: 3 additions & 3 deletions src/test/debuginfo/issue-57822.rs
Original file line number Diff line number Diff line change
@@ -11,17 +11,17 @@
// gdb-command:run

// gdb-command:print g
// gdb-check:$1 = issue_57822::main::{closure#1} (issue_57822::main::{closure#0} (1))
// gdb-check:$1 = issue_57822::main::{closure#1} {f: issue_57822::main::{closure#0} {x: 1}}

// gdb-command:print b
// gdb-check:$2 = issue_57822::main::{generator#3}::Unresumed(issue_57822::main::{generator#2}::Unresumed(2))
// gdb-check:$2 = issue_57822::main::{generator#3}::Unresumed{a: issue_57822::main::{generator#2}::Unresumed{y: 2}}

// === LLDB TESTS ==================================================================================

// lldb-command:run

// lldb-command:print g
// lldbg-check:(issue_57822::main::{closure#1}) $0 = { 0 = { 0 = 1 } }
// lldbg-check:(issue_57822::main::{closure#1}) $0 = { f = { x = 1 } }

// lldb-command:print b
// lldbg-check:(issue_57822::main::{generator#3}) $1 =