| use rustc_data_structures::fx::FxHashSet; |
| use rustc_data_structures::sync::Lock; |
| use rustc_target::abi::{Align, Size}; |
| 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, |
| } |
| |
| #[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 packed: bool, |
| pub opt_discr_size: Option<u64>, |
| pub variants: Vec<VariantInfo>, |
| } |
| |
| #[derive(Default)] |
| pub struct CodeStats { |
| type_sizes: Lock<FxHashSet<TypeSizeInfo>>, |
| } |
| |
| impl CodeStats { |
| pub fn record_type_size<S: ToString>( |
| &self, |
| kind: DataTypeKind, |
| type_desc: S, |
| align: Align, |
| overall_size: Size, |
| packed: bool, |
| opt_discr_size: Option<Size>, |
| mut variants: Vec<VariantInfo>, |
| ) { |
| // Sort variants so the largest ones are shown first. A stable sort is |
| // used here so that source code order is preserved for all variants |
| // that have the same size. |
| variants.sort_by(|info1, info2| info2.size.cmp(&info1.size)); |
| let info = TypeSizeInfo { |
| kind, |
| type_description: type_desc.to_string(), |
| align: align.bytes(), |
| overall_size: overall_size.bytes(), |
| packed, |
| opt_discr_size: opt_discr_size.map(|s| s.bytes()), |
| variants, |
| }; |
| self.type_sizes.borrow_mut().insert(info); |
| } |
| |
| pub fn print_type_sizes(&self) { |
| let type_sizes = self.type_sizes.borrow(); |
| let mut sorted: Vec<_> = 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) => name.to_owned(), |
| None => i.to_string(), |
| }; |
| 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; |
| |
| // We want to print fields by increasing offset. We also want |
| // zero-sized fields before non-zero-sized fields, otherwise |
| // the loop below goes wrong; hence the `f.size` in the sort |
| // key. |
| let mut fields = fields.clone(); |
| fields.sort_by_key(|f| (f.offset, f.size)); |
| |
| for field in fields.iter() { |
| let FieldInfo { ref name, offset, size, align } = *field; |
| |
| if offset > min_offset { |
| let pad = offset - min_offset; |
| println!("print-type-size {}padding: {} bytes", indent, pad); |
| } |
| |
| if offset < min_offset { |
| // If this happens it's probably a union. |
| println!( |
| "print-type-size {}field `.{}`: {} bytes, \ |
| offset: {} bytes, \ |
| alignment: {} bytes", |
| indent, name, size, offset, align |
| ); |
| } else if info.packed || offset == min_offset { |
| println!("print-type-size {}field `.{}`: {} bytes", indent, name, size); |
| } else { |
| // Include field alignment in output only if it caused padding injection |
| println!( |
| "print-type-size {}field `.{}`: {} bytes, \ |
| alignment: {} bytes", |
| indent, name, size, align |
| ); |
| } |
| |
| 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 |
| ); |
| } |
| } |
| } |
| } |