Revisions from review comments, squashed.

Biggest change: Revised print-type-sizes output to include breakdown
of layout.

Includes info about field sizes (and alignment + padding when padding
is injected; the injected padding is derived from the offsets computed
by layout module).

Output format is illustrated in commit that has the ui tests.

Note: there exists (at least) one case of variant w/o name: empty
enums.  Namely, empty enums use anonymous univariant repr. So for such
cases, print the number of the variant instead of the name.

----

Also, eddyb suggested of reading from `layout_cache` post-trans.

(For casual readers: the compiler source often uses the word "cache"
for tables that are in fact not periodically purged, and thus are
useful as the basis for data like this.)

Some types that were previously not printed are now included in the
output. (See e.g. the tests `print_type_sizes/generics.rs` and
`print_type_sizes/variants.rs`)

----

Other review feedback:

switch to an exhaustive match when filtering in just structural types.
switch to hashset for layout info and move sort into print method.

----

Driveby change: Factored session::code_stats into its own module

----

incorporate njn feedback re output formatting.
This commit is contained in:
Felix S. Klock II 2016-11-15 17:48:07 +01:00
parent 9383fcf07f
commit 70e5ca2ab4
8 changed files with 377 additions and 228 deletions

View File

@ -0,0 +1,173 @@
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use ty::AdtKind;
use ty::layout::{Align, Size};
use rustc_data_structures::fx::{FxHashSet};
use std::cmp::{self, Ordering};
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct VariantInfo {
pub name: Option<String>,
pub kind: SizeKind,
pub size: u64,
pub align: u64,
pub fields: Vec<FieldInfo>,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum SizeKind { Exact, Min }
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct FieldInfo {
pub name: String,
pub offset: u64,
pub size: u64,
pub align: u64,
}
impl From<AdtKind> for DataTypeKind {
fn from(kind: AdtKind) -> Self {
match kind {
AdtKind::Struct => DataTypeKind::Struct,
AdtKind::Enum => DataTypeKind::Enum,
AdtKind::Union => DataTypeKind::Union,
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum DataTypeKind {
Struct,
Union,
Enum,
Closure,
}
#[derive(PartialEq, Eq, Hash, Debug)]
pub struct TypeSizeInfo {
pub kind: DataTypeKind,
pub type_description: String,
pub align: u64,
pub overall_size: u64,
pub opt_discr_size: Option<u64>,
pub variants: Vec<VariantInfo>,
}
#[derive(PartialEq, Eq, Debug)]
pub struct CodeStats {
type_sizes: FxHashSet<TypeSizeInfo>,
}
impl CodeStats {
pub fn new() -> Self { CodeStats { type_sizes: FxHashSet() } }
pub fn record_type_size<S: ToString>(&mut self,
kind: DataTypeKind,
type_desc: S,
align: Align,
overall_size: Size,
opt_discr_size: Option<Size>,
variants: Vec<VariantInfo>) {
let info = TypeSizeInfo {
kind: kind,
type_description: type_desc.to_string(),
align: align.abi(),
overall_size: overall_size.bytes(),
opt_discr_size: opt_discr_size.map(|s| s.bytes()),
variants: variants,
};
self.type_sizes.insert(info);
}
pub fn print_type_sizes(&self) {
let mut sorted: Vec<_> = self.type_sizes.iter().collect();
// Primary sort: large-to-small.
// Secondary sort: description (dictionary order)
sorted.sort_by(|info1, info2| {
// (reversing cmp order to get large-to-small ordering)
match info2.overall_size.cmp(&info1.overall_size) {
Ordering::Equal => info1.type_description.cmp(&info2.type_description),
other => other,
}
});
for info in &sorted {
println!("print-type-size type: `{}`: {} bytes, alignment: {} bytes",
info.type_description, info.overall_size, info.align);
let indent = " ";
let discr_size = if let Some(discr_size) = info.opt_discr_size {
println!("print-type-size {}discriminant: {} bytes",
indent, discr_size);
discr_size
} else {
0
};
// We start this at discr_size (rather than 0) because
// things like C-enums do not have variants but we still
// want the max_variant_size at the end of the loop below
// to reflect the presence of the discriminant.
let mut max_variant_size = discr_size;
let struct_like = match info.kind {
DataTypeKind::Struct | DataTypeKind::Closure => true,
DataTypeKind::Enum | DataTypeKind::Union => false,
};
for (i, variant_info) in info.variants.iter().enumerate() {
let VariantInfo { ref name, kind: _, align: _, size, ref fields } = *variant_info;
let indent = if !struct_like {
let name = match name.as_ref() {
Some(name) => format!("{}", name),
None => format!("{}", i),
};
println!("print-type-size {}variant `{}`: {} bytes",
indent, name, size - discr_size);
" "
} else {
assert!(i < 1);
" "
};
max_variant_size = cmp::max(max_variant_size, size);
let mut min_offset = discr_size;
for field in fields {
let FieldInfo { ref name, offset, size, align } = *field;
// Include field alignment in output only if it caused padding injection
if min_offset != offset {
let pad = offset - min_offset;
println!("print-type-size {}padding: {} bytes",
indent, pad);
println!("print-type-size {}field `.{}`: {} bytes, alignment: {} bytes",
indent, name, size, align);
} else {
println!("print-type-size {}field `.{}`: {} bytes",
indent, name, size);
}
min_offset = offset + size;
}
}
assert!(max_variant_size <= info.overall_size,
"max_variant_size {} !<= {} overall_size",
max_variant_size, info.overall_size);
if max_variant_size < info.overall_size {
println!("print-type-size {}end padding: {} bytes",
indent, info.overall_size - max_variant_size);
}
}
}
}

View File

@ -8,6 +8,9 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
pub use self::code_stats::{CodeStats, DataTypeKind, FieldInfo};
pub use self::code_stats::{SizeKind, TypeSizeInfo, VariantInfo};
use dep_graph::DepGraph;
use hir::def_id::{CrateNum, DefIndex};
use hir::svh::Svh;
@ -49,6 +52,7 @@ use std::fmt;
use std::time::Duration;
use libc::c_int;
mod code_stats;
pub mod config;
pub mod filesearch;
pub mod search_paths;
@ -118,74 +122,6 @@ pub struct Session {
next_node_id: Cell<ast::NodeId>,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum VariantSize {
Exact(u64),
Min(u64),
}
#[derive(PartialEq, Eq, Debug)]
pub struct TypeSizeInfo {
pub type_description: String,
pub overall_size: u64,
pub variant_sizes: Option<Vec<VariantSize>>,
}
#[derive(PartialEq, Eq, Debug)]
pub struct CodeStats {
pub type_sizes: Vec<TypeSizeInfo>,
}
impl CodeStats {
fn new() -> Self {
CodeStats { type_sizes: Vec::new() }
}
pub fn record_type_size<S: ToString>(&mut self,
type_desc: S,
overall_size: u64,
variant_sizes: Vec<VariantSize>) {
let sizes = if variant_sizes.len() == 0 { None } else { Some(variant_sizes) };
let info = TypeSizeInfo {
type_description: type_desc.to_string(),
overall_size: overall_size,
variant_sizes: sizes,
};
if !self.type_sizes.contains(&info) {
self.type_sizes.push(info);
}
}
pub fn sort_by_type_description(&mut self) {
self.type_sizes.sort_by(|info1, info2| {
info1.type_description.cmp(&info2.type_description)
});
}
pub fn sort_by_overall_size(&mut self) {
self.type_sizes.sort_by(|info1, info2| {
// (reversing cmp order to get large-to-small ordering)
info2.overall_size.cmp(&info1.overall_size)
});
}
pub fn print_type_sizes(&self) {
for info in &self.type_sizes {
println!("print-type-size t: `{}` overall bytes: {}",
info.type_description, info.overall_size);
if let Some(ref variant_sizes) = info.variant_sizes {
for (i, variant_size) in variant_sizes.iter().enumerate() {
let (kind, s) = match *variant_size {
VariantSize::Exact(s) => { ("exact", s) }
VariantSize::Min(s) => { (" min", s) }
};
println!("print-type-size variant[{}] {} bytes: {}", i, kind, s);
}
}
}
}
}
pub struct PerfStats {
// The accumulated time needed for computing the SVH of the crate
pub svh_time: Cell<Duration>,

View File

@ -559,11 +559,14 @@ impl<'a, 'gcx, 'tcx> Struct {
self.offsets.push(offset);
debug!("Struct::extend offset: {:?} field: {:?} {:?}", offset, field, field.size(dl));
offset = offset.checked_add(field.size(dl), dl)
.map_or(Err(LayoutError::SizeOverflow(scapegoat)), Ok)?;
}
debug!("Struct::extend min_size: {:?}", offset);
self.min_size = offset;
Ok(())
@ -707,12 +710,16 @@ impl<'a, 'gcx, 'tcx> Union {
index, scapegoat);
}
debug!("Union::extend field: {:?} {:?}", field, field.size(dl));
if !self.packed {
self.align = self.align.max(field.align(dl));
}
self.min_size = cmp::max(self.min_size, field.size(dl));
}
debug!("Union::extend min-size: {:?}", self.min_size);
Ok(())
}

View File

@ -216,9 +216,6 @@ pub fn compile_input(sess: &Session,
};
if sess.opts.debugging_opts.print_type_sizes {
// (these are stable sorts)
sess.code_stats.borrow_mut().sort_by_type_description();
sess.code_stats.borrow_mut().sort_by_overall_size();
sess.code_stats.borrow().print_type_sizes();
}
@ -1015,9 +1012,6 @@ pub fn phase_4_translate_to_llvm<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
time(time_passes, "MIR optimisations", || {
let mut passes = ::rustc::mir::transform::Passes::new();
passes.push_hook(box mir::transform::dump_mir::DumpMir);
if tcx.sess.opts.debugging_opts.print_type_sizes {
passes.push_pass(box mir::transform::print_type_sizes::GatherTypeSizesMir::new());
}
passes.push_pass(box mir::transform::no_landing_pads::NoLandingPads);
passes.push_pass(box mir::transform::simplify::SimplifyCfg::new("no-landing-pads"));

View File

@ -13,7 +13,6 @@ pub mod simplify;
pub mod erase_regions;
pub mod no_landing_pads;
pub mod type_check;
pub mod print_type_sizes;
pub mod add_call_guards;
pub mod promote_consts;
pub mod qualify_consts;

View File

@ -1,152 +0,0 @@
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! This pass implements instrumentation to gather the layout of every type.
use rustc::session::{VariantSize};
use rustc::traits::{Reveal};
use rustc::ty::{self, Ty, TyCtxt};
use rustc::ty::fold::{TypeFoldable};
use rustc::ty::layout::{Layout};
use rustc::mir::{Mir};
use rustc::mir::transform::{MirPass, MirPassHook, MirSource, Pass};
use rustc::mir::visit::Visitor;
use std::collections::HashSet;
pub struct GatherTypeSizesMir {
_hidden: (),
}
impl GatherTypeSizesMir {
pub fn new() -> Self {
GatherTypeSizesMir { _hidden: () }
}
}
impl Pass for GatherTypeSizesMir {
}
impl<'tcx> MirPassHook<'tcx> for GatherTypeSizesMir {
fn on_mir_pass<'a>(&mut self,
tcx: TyCtxt<'a, 'tcx, 'tcx>,
src: MirSource,
mir: &Mir<'tcx>,
_pass: &Pass,
_is_after: bool) {
debug!("on_mir_pass: {}", tcx.node_path_str(src.item_id()));
self.go(tcx, mir);
}
}
impl<'tcx> MirPass<'tcx> for GatherTypeSizesMir {
fn run_pass<'a>(&mut self, tcx: TyCtxt<'a, 'tcx, 'tcx>,
src: MirSource, mir: &mut Mir<'tcx>) {
debug!("run_pass: {}", tcx.node_path_str(src.item_id()));
self.go(tcx, mir);
}
}
impl GatherTypeSizesMir {
fn go<'a, 'tcx>(&mut self,
tcx: TyCtxt<'a, 'tcx, 'tcx>,
mir: &Mir<'tcx>) {
if tcx.sess.err_count() > 0 {
// compiling a broken program can obviously result in a
// broken MIR, so do not bother trying to process it.
return;
}
let mut visitor = TypeVisitor {
tcx: tcx,
seen: HashSet::new(),
};
visitor.visit_mir(mir);
}
}
struct TypeVisitor<'a, 'tcx: 'a> {
tcx: TyCtxt<'a, 'tcx, 'tcx>,
seen: HashSet<Ty<'tcx>>,
}
impl<'a, 'tcx: 'a> Visitor<'tcx> for TypeVisitor<'a, 'tcx> {
fn visit_ty(&mut self, ty: &Ty<'tcx>) {
debug!("TypeVisitor::visit_ty ty=`{:?}`", ty);
match ty.sty {
ty::TyAdt(..) |
ty::TyClosure(..) => {} // fall through
_ => {
debug!("print-type-size t: `{:?}` skip non-nominal", ty);
return;
}
}
if ty.has_param_types() {
debug!("print-type-size t: `{:?}` skip has param types", ty);
return;
}
if ty.has_projection_types() {
debug!("print-type-size t: `{:?}` skip has projections", ty);
return;
}
if self.seen.contains(ty) {
return;
}
self.seen.insert(ty);
let reveal = Reveal::All;
// let reveal = Reveal::NotSpecializable;
self.tcx.infer_ctxt(None, None, reveal).enter(|infcx| {
match ty.layout(&infcx) {
Ok(layout) => {
let type_desc = format!("{:?}", ty);
let overall_size = layout.size(&Default::default());
let variant_sizes: Vec<_> = match *layout {
Layout::General { ref variants, .. } => {
variants.iter()
.map(|v| if v.sized {
VariantSize::Exact(v.min_size.bytes())
} else {
VariantSize::Min(v.min_size.bytes())
})
.collect()
}
Layout::UntaggedUnion { variants: _ } => {
/* layout does not currently store info about each variant... */
Vec::new()
}
// RawNullablePointer/StructWrappedNullablePointer
// don't provide any interesting size info
// beyond what we already reported for their
// total size.
_ => {
Vec::new()
}
};
self.tcx.sess.code_stats.borrow_mut()
.record_type_size(type_desc,
overall_size.bytes(),
variant_sizes);
}
Err(err) => {
self.tcx.sess.warn(&format!("print-type-size t: `{:?}` err: {:?}", ty, err));
}
}
});
}
}

View File

@ -247,6 +247,7 @@ fn generic_type_of<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>,
// of the size.
let size = size.bytes();
let align = align.abi();
assert!(align <= std::u32::MAX as u64);
let discr_ty = Type::from_integer(cx, discr);
let discr_size = discr.size().bytes();
let padded_discr_size = roundup(discr_size, align as u32);

View File

@ -47,7 +47,7 @@ use rustc::hir::map as hir_map;
use rustc::util::common::time;
use session::config::{self, NoDebugInfo};
use rustc_incremental::IncrementalHashesMap;
use session::Session;
use session::{self, DataTypeKind, Session};
use abi::{self, Abi, FnType};
use adt;
use attributes;
@ -93,6 +93,7 @@ use std::i32;
use syntax_pos::{Span, DUMMY_SP};
use syntax::attr;
use rustc::hir;
use rustc::ty::layout::{self, Layout};
use syntax::ast;
thread_local! {
@ -1741,6 +1742,10 @@ pub fn trans_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
.collect())
});
if tcx.sess.opts.debugging_opts.print_type_sizes {
gather_type_sizes(tcx);
}
if sess.target.target.options.is_like_msvc &&
sess.crate_types.borrow().iter().any(|ct| *ct == config::CrateTypeRlib) {
create_imps(&crate_context_list);
@ -1771,6 +1776,192 @@ pub fn trans_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
}
}
fn gather_type_sizes<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>) {
let layout_cache = tcx.layout_cache.borrow();
for (ty, layout) in layout_cache.iter() {
// (delay format until we actually need it)
let record = |kind, opt_discr_size, variants| {
let type_desc = format!("{:?}", ty);
let overall_size = layout.size(&tcx.data_layout);
let align = layout.align(&tcx.data_layout);
tcx.sess.code_stats.borrow_mut().record_type_size(kind,
type_desc,
align,
overall_size,
opt_discr_size,
variants);
};
let (adt_def, substs) = match ty.sty {
ty::TyAdt(ref adt_def, substs) => {
debug!("print-type-size t: `{:?}` process adt", ty);
(adt_def, substs)
}
ty::TyClosure(..) => {
debug!("print-type-size t: `{:?}` record closure", ty);
record(DataTypeKind::Closure, None, vec![]);
continue;
}
_ => {
debug!("print-type-size t: `{:?}` skip non-nominal", ty);
continue;
}
};
let adt_kind = adt_def.adt_kind();
let build_field_info = |(field_name, field_ty): (ast::Name, Ty), offset: &layout::Size| {
match layout_cache.get(&field_ty) {
None => bug!("no layout found for field {} type: `{:?}`", field_name, field_ty),
Some(field_layout) => {
session::FieldInfo {
name: field_name.to_string(),
offset: offset.bytes(),
size: field_layout.size(&tcx.data_layout).bytes(),
align: field_layout.align(&tcx.data_layout).abi(),
}
}
}
};
let build_primitive_info = |name: ast::Name, value: &layout::Primitive| {
session::VariantInfo {
name: Some(name.to_string()),
kind: session::SizeKind::Exact,
align: value.align(&tcx.data_layout).abi(),
size: value.size(&tcx.data_layout).bytes(),
fields: vec![],
}
};
enum Fields<'a> {
WithDiscrim(&'a layout::Struct),
NoDiscrim(&'a layout::Struct),
}
let build_variant_info = |n: Option<ast::Name>, flds: &[(ast::Name, Ty)], layout: Fields| {
let (s, field_offsets) = match layout {
Fields::WithDiscrim(s) => (s, &s.offsets[1..]),
Fields::NoDiscrim(s) => (s, &s.offsets[0..]),
};
let field_info: Vec<_> = flds.iter()
.zip(field_offsets.iter())
.map(|(&field_name_ty, offset)| build_field_info(field_name_ty, offset))
.collect();
session::VariantInfo {
name: n.map(|n|n.to_string()),
kind: if s.sized {
session::SizeKind::Exact
} else {
session::SizeKind::Min
},
align: s.align.abi(),
size: s.min_size.bytes(),
fields: field_info,
}
};
match **layout {
Layout::StructWrappedNullablePointer { nonnull: ref variant_layout,
nndiscr,
discrfield: _ } => {
debug!("print-type-size t: `{:?}` adt struct-wrapped nullable nndiscr {} is {:?}",
ty, nndiscr, variant_layout);
let variant_def = &adt_def.variants[nndiscr as usize];
let fields: Vec<_> = variant_def.fields.iter()
.map(|field_def| (field_def.name, field_def.ty(tcx, substs)))
.collect();
record(adt_kind.into(),
None,
vec![build_variant_info(Some(variant_def.name),
&fields,
Fields::NoDiscrim(variant_layout))]);
}
Layout::RawNullablePointer { nndiscr, value } => {
debug!("print-type-size t: `{:?}` adt raw nullable nndiscr {} is {:?}",
ty, nndiscr, value);
let variant_def = &adt_def.variants[nndiscr as usize];
record(adt_kind.into(), None,
vec![build_primitive_info(variant_def.name, &value)]);
}
Layout::Univariant { variant: ref variant_layout, non_zero: _ } => {
let variant_names = || {
adt_def.variants.iter().map(|v|format!("{}", v.name)).collect::<Vec<_>>()
};
debug!("print-type-size t: `{:?}` adt univariant {:?} variants: {:?}",
ty, variant_layout, variant_names());
assert!(adt_def.variants.len() <= 1,
"univariant with variants {:?}", variant_names());
if adt_def.variants.len() == 1 {
let variant_def = &adt_def.variants[0];
let fields: Vec<_> = variant_def.fields.iter()
.map(|field_def| (field_def.name, field_def.ty(tcx, substs)))
.collect();
record(adt_kind.into(),
None,
vec![build_variant_info(Some(variant_def.name),
&fields,
Fields::NoDiscrim(variant_layout))]);
} else {
// (This case arises for *empty* enums; so give it
// zero variants.)
record(adt_kind.into(), None, vec![]);
}
}
Layout::General { ref variants, discr, .. } => {
debug!("print-type-size t: `{:?}` adt general variants def {} layouts {} {:?}",
ty, adt_def.variants.len(), variants.len(), variants);
let variant_infos: Vec<_> = adt_def.variants.iter()
.zip(variants.iter())
.map(|(variant_def, variant_layout)| {
let fields: Vec<_> = variant_def.fields.iter()
.map(|field_def| (field_def.name, field_def.ty(tcx, substs)))
.collect();
build_variant_info(Some(variant_def.name),
&fields,
Fields::WithDiscrim(variant_layout))
})
.collect();
record(adt_kind.into(), Some(discr.size()), variant_infos);
}
Layout::UntaggedUnion { ref variants } => {
debug!("print-type-size t: `{:?}` adt union variants {:?}",
ty, variants);
// layout does not currently store info about each
// variant...
record(adt_kind.into(), None, Vec::new());
}
Layout::CEnum { discr, .. } => {
debug!("print-type-size t: `{:?}` adt c-like enum", ty);
let variant_infos: Vec<_> = adt_def.variants.iter()
.map(|variant_def| {
build_primitive_info(variant_def.name,
&layout::Primitive::Int(discr))
})
.collect();
record(adt_kind.into(), Some(discr.size()), variant_infos);
}
// other cases provide little interesting (i.e. adjustable
// via representation tweaks) size info beyond total size.
Layout::Scalar { .. } |
Layout::Vector { .. } |
Layout::Array { .. } |
Layout::FatPointer { .. } => {
debug!("print-type-size t: `{:?}` adt other", ty);
record(adt_kind.into(), None, Vec::new())
}
}
}
}
/// For each CGU, identify if we can reuse an existing object file (or
/// maybe other context).
fn trans_reuse_previous_work_products(tcx: TyCtxt,