diff --git a/src/librustc/session/code_stats.rs b/src/librustc/session/code_stats.rs new file mode 100644 index 00000000000..8308c54d70b --- /dev/null +++ b/src/librustc/session/code_stats.rs @@ -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 or the MIT license +// , 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, + pub kind: SizeKind, + pub size: u64, + pub align: u64, + pub fields: Vec, +} + +#[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 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, + pub variants: Vec, +} + +#[derive(PartialEq, Eq, Debug)] +pub struct CodeStats { + type_sizes: FxHashSet, +} + +impl CodeStats { + pub fn new() -> Self { CodeStats { type_sizes: FxHashSet() } } + + pub fn record_type_size(&mut self, + kind: DataTypeKind, + type_desc: S, + align: Align, + overall_size: Size, + opt_discr_size: Option, + variants: Vec) { + 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); + } + } + } +} diff --git a/src/librustc/session/mod.rs b/src/librustc/session/mod.rs index 128e4d878a8..3d8cfd19961 100644 --- a/src/librustc/session/mod.rs +++ b/src/librustc/session/mod.rs @@ -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, } -#[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>, -} - -#[derive(PartialEq, Eq, Debug)] -pub struct CodeStats { - pub type_sizes: Vec, -} - -impl CodeStats { - fn new() -> Self { - CodeStats { type_sizes: Vec::new() } - } - - pub fn record_type_size(&mut self, - type_desc: S, - overall_size: u64, - variant_sizes: Vec) { - 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, diff --git a/src/librustc/ty/layout.rs b/src/librustc/ty/layout.rs index 5ee1c3678d6..bc3c5d6ed4e 100644 --- a/src/librustc/ty/layout.rs +++ b/src/librustc/ty/layout.rs @@ -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(()) } diff --git a/src/librustc_driver/driver.rs b/src/librustc_driver/driver.rs index a91525c6b2d..9a4ecef0c0e 100644 --- a/src/librustc_driver/driver.rs +++ b/src/librustc_driver/driver.rs @@ -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")); diff --git a/src/librustc_mir/transform/mod.rs b/src/librustc_mir/transform/mod.rs index eed4763c17d..ae255f70fb7 100644 --- a/src/librustc_mir/transform/mod.rs +++ b/src/librustc_mir/transform/mod.rs @@ -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; diff --git a/src/librustc_mir/transform/print_type_sizes.rs b/src/librustc_mir/transform/print_type_sizes.rs deleted file mode 100644 index 617a5ac78df..00000000000 --- a/src/librustc_mir/transform/print_type_sizes.rs +++ /dev/null @@ -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 or the MIT license -// , 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>, -} - -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)); - } - } - }); - } -} diff --git a/src/librustc_trans/adt.rs b/src/librustc_trans/adt.rs index c3340281d07..e091ba07d4f 100644 --- a/src/librustc_trans/adt.rs +++ b/src/librustc_trans/adt.rs @@ -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); diff --git a/src/librustc_trans/base.rs b/src/librustc_trans/base.rs index 4353c7bd586..d697a5bafb7 100644 --- a/src/librustc_trans/base.rs +++ b/src/librustc_trans/base.rs @@ -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, 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::>() + }; + 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,