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, 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, } #[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, pub variants: Vec, } #[derive(Default)] pub struct CodeStats { type_sizes: Lock>, } impl CodeStats { pub fn record_type_size( &self, kind: DataTypeKind, type_desc: S, align: Align, overall_size: Size, packed: bool, opt_discr_size: Option, mut variants: Vec, ) { // 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 ); } } } }