Skip to content
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

rustc_mir: add a pass for fragmenting locals into their fields (aka SROA). #48300

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
62 changes: 58 additions & 4 deletions src/librustc/mir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -948,10 +948,64 @@ pub struct VarDebugInfo<'tcx> {
/// (see `LocalDecl`'s `source_info` field for more details).
pub source_info: SourceInfo,

/// Where the data for this user variable is to be found.
/// NOTE(eddyb) There's an unenforced invariant that this `Place` is
/// based on a `Local`, not a `Static`, and contains no indexing.
pub place: Place<'tcx>,
/// How the contents of this user variable are represented.
pub contents: VarDebugInfoContents<'tcx>,
}

#[derive(Clone, Debug, RustcEncodable, RustcDecodable, HashStable, TypeFoldable)]
pub enum VarDebugInfoContents<'tcx> {
/// The user variable's data is entirely in one `Place`.
// NOTE(eddyb) there's an unenforced invariant that this `Place` is
// contains no indexing (with a non-constant index).
Compact(Place<'tcx>),

/// The user variable's data is split across several fragments,
/// each described by a `VarDebugInfoFragment`.
/// See DWARF 5's "2.6.1.2 Composite Location Descriptions"
/// and LLVM's `DW_OP_LLVM_fragment` for more details on
/// the underlying debuginfo feature this relies on.
Composite {
/// Type of the original user variable.
ty: Ty<'tcx>,

/// All the parts of the original user variable, which ended
/// up in disjoint places, due to optimizations.
fragments: Vec<VarDebugInfoFragment<'tcx>>,
},
}

#[derive(Clone, RustcEncodable, RustcDecodable, HashStable, TypeFoldable)]
pub struct VarDebugInfoFragment<'tcx> {
/// Where in the composite user variable this fragment is,
/// represented as a "projection" into the composite variable.
/// At lower levels, this corresponds to a byte/bit range.
// NOTE(eddyb) there's an unenforced invariant that this contains
// only `Field`s, and not into `enum` variants or `union`s.
// FIXME(eddyb) support this for `enum`s by either using DWARF's
// more advanced control-flow features (unsupported by LLVM?)
// to match on the discriminant, or by using custom type debuginfo
// with non-overlapping variants for the composite variable.
pub projection: Vec<ProjectionKind>,

/// Where the data for this fragment can be found.
// NOTE(eddyb) There's an unenforced invariant that this `Place` is
// contains no indexing (with a non-constant index).
pub contents: Place<'tcx>,
}

impl Debug for VarDebugInfoFragment<'_> {
fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
for elem in self.projection.iter() {
match elem {
ProjectionElem::Field(field, _) => {
write!(fmt, ".{:?}", field.index())?;
}
_ => bug!("unsupported fragment projection `{:?}`", elem),
}
}

write!(fmt, " => {:?}", self.contents)
}
}

///////////////////////////////////////////////////////////////////////////
Expand Down
27 changes: 21 additions & 6 deletions src/librustc/mir/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -739,16 +739,31 @@ macro_rules! make_mir_visitor {
let VarDebugInfo {
name: _,
source_info,
place,
contents,
} = var_debug_info;

self.visit_source_info(source_info);
let location = START_BLOCK.start_location();
self.visit_place(
place,
PlaceContext::NonUse(NonUseContext::VarDebugInfo),
location,
);
match contents {
VarDebugInfoContents::Compact(place) => {
self.visit_place(
place,
PlaceContext::NonUse(NonUseContext::VarDebugInfo),
location,
);
}
VarDebugInfoContents::Composite { ty, fragments } => {
// FIXME(eddyb) use a better `TyContext` here.
self.visit_ty(ty, TyContext::Location(location));
for VarDebugInfoFragment { projection: _, contents } in fragments {
self.visit_place(
contents,
PlaceContext::NonUse(NonUseContext::VarDebugInfo),
location,
);
}
}
}
}

fn super_source_scope(&mut self,
Expand Down
8 changes: 6 additions & 2 deletions src/librustc_codegen_llvm/debuginfo/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1288,10 +1288,14 @@ fn generator_layout_and_saved_local_names(

let state_arg = mir::Local::new(1);
for var in &body.var_debug_info {
if var.place.local != state_arg {
let place = match var.contents {
mir::VarDebugInfoContents::Compact(place) => place,
mir::VarDebugInfoContents::Composite { .. } => continue,
};
if place.local != state_arg {
continue;
}
match var.place.projection[..] {
match place.projection[..] {
[
// Deref of the `Pin<&mut Self>` state argument.
mir::ProjectionElem::Field(..),
Expand Down
29 changes: 20 additions & 9 deletions src/librustc_codegen_llvm/debuginfo/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ use libc::c_uint;
use log::debug;
use std::cell::RefCell;
use std::ffi::CString;
use std::ops::Range;

use rustc::ty::layout::{self, HasTyCtxt, LayoutOf, Size};
use rustc_codegen_ssa::traits::*;
Expand Down Expand Up @@ -154,30 +155,40 @@ impl DebugInfoBuilderMethods for Builder<'a, 'll, 'tcx> {
variable_alloca: Self::Value,
direct_offset: Size,
indirect_offsets: &[Size],
fragment: Option<Range<Size>>,
span: Span,
) {
assert!(!dbg_context.source_locations_enabled);
let cx = self.cx();

let loc = span_start(cx, span);

// Convert the direct and indirect offsets to address ops.
// Convert the direct/indirect offsets and fragment byte range to DWARF OPs.
let op_deref = || unsafe { llvm::LLVMRustDIBuilderCreateOpDeref() };
let op_plus_uconst = || unsafe { llvm::LLVMRustDIBuilderCreateOpPlusUconst() };
let mut addr_ops = SmallVec::<[_; 8]>::new();
let op_llvm_fragment = || unsafe { llvm::LLVMRustDIBuilderCreateOpLLVMFragment() };
let mut dwarf_ops = SmallVec::<[_; 8]>::new();

if direct_offset.bytes() > 0 {
addr_ops.push(op_plus_uconst());
addr_ops.push(direct_offset.bytes() as i64);
dwarf_ops.push(op_plus_uconst());
dwarf_ops.push(direct_offset.bytes() as i64);
}
for &offset in indirect_offsets {
addr_ops.push(op_deref());
dwarf_ops.push(op_deref());
if offset.bytes() > 0 {
addr_ops.push(op_plus_uconst());
addr_ops.push(offset.bytes() as i64);
dwarf_ops.push(op_plus_uconst());
dwarf_ops.push(offset.bytes() as i64);
}
}

if let Some(fragment) = fragment {
// `DW_OP_LLVM_fragment` takes as arguments the fragment's
// offset and size, both of them in bits.
dwarf_ops.push(op_llvm_fragment());
dwarf_ops.push(fragment.start.bits() as i64);
dwarf_ops.push((fragment.end - fragment.start).bits() as i64);
}

// FIXME(eddyb) maybe this information could be extracted from `var`,
// to avoid having to pass it down in both places?
source_loc::set_debug_location(
Expand All @@ -191,8 +202,8 @@ impl DebugInfoBuilderMethods for Builder<'a, 'll, 'tcx> {
DIB(cx),
variable_alloca,
dbg_var,
addr_ops.as_ptr(),
addr_ops.len() as c_uint,
dwarf_ops.as_ptr(),
dwarf_ops.len() as c_uint,
debug_loc,
self.llbb(),
);
Expand Down
1 change: 1 addition & 0 deletions src/librustc_codegen_llvm/llvm/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1809,6 +1809,7 @@ extern "C" {
) -> &'a Value;
pub fn LLVMRustDIBuilderCreateOpDeref() -> i64;
pub fn LLVMRustDIBuilderCreateOpPlusUconst() -> i64;
pub fn LLVMRustDIBuilderCreateOpLLVMFragment() -> i64;

#[allow(improper_ctypes)]
pub fn LLVMRustWriteTypeToString(Type: &Type, s: &RustString);
Expand Down
113 changes: 84 additions & 29 deletions src/librustc_codegen_ssa/mir/debuginfo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ use rustc::ty;
use rustc::ty::layout::{LayoutOf, Size};
use rustc_hir::def_id::CrateNum;
use rustc_index::vec::IndexVec;

use rustc_span::symbol::{kw, Symbol};
use rustc_span::{BytePos, Span};
use std::ops::Range;

use super::OperandValue;
use super::{FunctionCx, LocalRef};
Expand All @@ -25,14 +25,18 @@ pub enum VariableKind {
}

/// Like `mir::VarDebugInfo`, but within a `mir::Local`.
#[derive(Copy, Clone)]
#[derive(Clone)]
pub struct PerLocalVarDebugInfo<'tcx, D> {
pub name: Symbol,
pub source_info: mir::SourceInfo,

/// `DIVariable` returned by `create_dbg_var`.
pub dbg_var: Option<D>,

/// Byte range in the `dbg_var` covered by this fragment,
/// if this is a fragment of a composite `VarDebugInfo`.
pub fragment: Option<Range<Size>>,

/// `.place.projection` from `mir::VarDebugInfo`.
pub projection: &'tcx ty::List<mir::PlaceElem<'tcx>>,
}
Expand Down Expand Up @@ -127,7 +131,8 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
Some(per_local) => &per_local[local],
None => return,
};
let whole_local_var = vars.iter().find(|var| var.projection.is_empty()).copied();
let whole_local_var =
vars.iter().find(|var| var.fragment.is_none() && var.projection.is_empty()).cloned();
let has_proj = || vars.iter().any(|var| !var.projection.is_empty());

let fallback_var = if self.mir.local_kind(local) == mir::LocalKind::Arg {
Expand Down Expand Up @@ -173,6 +178,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
name,
source_info: decl.source_info,
dbg_var,
fragment: None,
projection: ty::List::empty(),
})
}
Expand All @@ -183,7 +189,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
let local_ref = &self.locals[local];

if !bx.sess().fewer_names() {
let name = match whole_local_var.or(fallback_var) {
let name = match whole_local_var.or(fallback_var.clone()) {
Some(var) if var.name != kw::Invalid => var.name.to_string(),
_ => format!("{:?}", local),
};
Expand Down Expand Up @@ -221,7 +227,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
_ => return,
};

let vars = vars.iter().copied().chain(fallback_var);
let vars = vars.iter().cloned().chain(fallback_var);

for var in vars {
let mut layout = base.layout;
Expand Down Expand Up @@ -270,6 +276,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
base.llval,
direct_offset,
&indirect_offsets,
var.fragment,
span,
);
}
Expand Down Expand Up @@ -302,25 +309,31 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
} else {
(None, var.source_info.span)
};
let (var_ty, var_kind) = match var.contents {
mir::VarDebugInfoContents::Compact(place) => (
self.monomorphized_place_ty(place.as_ref()),
if self.mir.local_kind(place.local) == mir::LocalKind::Arg
&& place.projection.is_empty()
{
// FIXME(eddyb, #67586) take `var.source_info.scope` into
// account to avoid using `ArgumentVariable` more than once
// per argument local.

let arg_index = place.local.index() - 1;

// FIXME(eddyb) shouldn't `ArgumentVariable` indices be
// offset in closures to account for the hidden environment?
// Also, is this `+ 1` needed at all?
VariableKind::ArgumentVariable(arg_index + 1)
} else {
VariableKind::LocalVariable
},
),
mir::VarDebugInfoContents::Composite { ty, fragments: _ } => {
(self.monomorphize(&ty), VariableKind::LocalVariable)
}
};
let dbg_var = scope.map(|scope| {
let place = var.place;
let var_ty = self.monomorphized_place_ty(place.as_ref());
let var_kind = if self.mir.local_kind(place.local) == mir::LocalKind::Arg
&& place.projection.is_empty()
{
// FIXME(eddyb, #67586) take `var.source_info.scope` into
// account to avoid using `ArgumentVariable` more than once
// per argument local.

let arg_index = place.local.index() - 1;

// FIXME(eddyb) shouldn't `ArgumentVariable` indices be
// offset in closures to account for the hidden environment?
// Also, is this `+ 1` needed at all?
VariableKind::ArgumentVariable(arg_index + 1)
} else {
VariableKind::LocalVariable
};
self.cx.create_dbg_var(
self.debug_context.as_ref().unwrap(),
var.name,
Expand All @@ -331,12 +344,54 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
)
});

per_local[var.place.local].push(PerLocalVarDebugInfo {
name: var.name,
source_info: var.source_info,
dbg_var,
projection: var.place.projection,
});
match &var.contents {
mir::VarDebugInfoContents::Compact(place) => {
per_local[place.local].push(PerLocalVarDebugInfo {
name: var.name,
source_info: var.source_info,
dbg_var,
fragment: None,
projection: place.projection,
});
}
mir::VarDebugInfoContents::Composite { ty: _, fragments } => {
let var_layout = self.cx.layout_of(var_ty);
for fragment in fragments {
let mut fragment_start = Size::ZERO;
let mut fragment_layout = var_layout;

for elem in &fragment.projection {
match *elem {
mir::ProjectionElem::Field(field, _) => {
let i = field.index();
fragment_start += fragment_layout.fields.offset(i);
fragment_layout = fragment_layout.field(self.cx, i);
}
_ => span_bug!(
var.source_info.span,
"unsupported fragment projection `{:?}`",
elem,
),
}
}

let place = fragment.contents;
per_local[place.local].push(PerLocalVarDebugInfo {
name: var.name,
source_info: var.source_info,
dbg_var,
fragment: if fragment_layout.size == var_layout.size {
// Fragment covers entire variable, so as far as
// DWARF is concerned, it's not really a fragment.
None
} else {
Some(fragment_start..fragment_start + fragment_layout.size)
},
projection: place.projection,
});
}
}
}
}
Some(per_local)
}
Expand Down
Loading