| use std::cmp; | |
| use rustc_abi::{Align, Size}; | |
| use rustc_data_structures::fx::FxHashSet; | |
| use rustc_data_structures::sync::Lock; | |
| use rustc_span::Symbol; | |
| #[derive(Clone, PartialEq, Eq, Hash, Debug)] | |
| pub struct VariantInfo { | |
| pub name: Option<Symbol>, | |
| 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(Copy, Clone, PartialEq, Eq, Hash, Debug)] | |
| pub enum FieldKind { | |
| AdtField, | |
| Upvar, | |
| CoroutineLocal, | |
| } | |
| impl std::fmt::Display for FieldKind { | |
| fn fmt(&self, w: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
| match self { | |
| FieldKind::AdtField => write!(w, "field"), | |
| FieldKind::Upvar => write!(w, "upvar"), | |
| FieldKind::CoroutineLocal => write!(w, "local"), | |
| } | |
| } | |
| } | |
| #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] | |
| pub struct FieldInfo { | |
| pub kind: FieldKind, | |
| pub name: Symbol, | |
| pub offset: u64, | |
| pub size: u64, | |
| pub align: u64, | |
| /// Name of the type of this field. | |
| /// Present only if the creator thought that this would be important for identifying the field, | |
| /// typically because the field name is uninformative. | |
| pub type_name: Option<Symbol>, | |
| } | |
| #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] | |
| pub enum DataTypeKind { | |
| Struct, | |
| Union, | |
| Enum, | |
| Closure, | |
| Coroutine, | |
| } | |
| #[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 { | |
| /// The hash set that actually holds all the type size information. | |
| /// The field is public for use in external tools. See #139876. | |
| pub 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. | |
| // Except for Coroutines, whose variants are already sorted according to | |
| // their yield points in `variant_info_for_coroutine`. | |
| if kind != DataTypeKind::Coroutine { | |
| variants.sort_by_key(|info| cmp::Reverse(info.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(); | |
| // We will soon sort, so the initial order does not matter. | |
| #[allow(rustc::potential_query_instability)] | |
| let mut sorted: Vec<_> = type_sizes.iter().collect(); | |
| // Primary sort: large-to-small. | |
| // Secondary sort: description (dictionary order) | |
| sorted.sort_by_key(|info| (cmp::Reverse(info.overall_size), &info.type_description)); | |
| for info in sorted { | |
| let TypeSizeInfo { type_description, overall_size, align, kind, variants, .. } = info; | |
| println!( | |
| "print-type-size type: `{type_description}`: {overall_size} bytes, alignment: {align} bytes" | |
| ); | |
| let indent = " "; | |
| let discr_size = if let Some(discr_size) = info.opt_discr_size { | |
| println!("print-type-size {indent}discriminant: {discr_size} bytes"); | |
| 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 kind { | |
| DataTypeKind::Struct | DataTypeKind::Closure => true, | |
| DataTypeKind::Enum | DataTypeKind::Union | DataTypeKind::Coroutine => false, | |
| }; | |
| for (i, variant_info) in variants.into_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_string(), | |
| None => i.to_string(), | |
| }; | |
| println!( | |
| "print-type-size {indent}variant `{name}`: {diff} bytes", | |
| diff = 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 { | |
| let FieldInfo { kind, ref name, offset, size, align, type_name } = field; | |
| if offset > min_offset { | |
| let pad = offset - min_offset; | |
| println!("print-type-size {indent}padding: {pad} bytes"); | |
| } | |
| if offset < min_offset { | |
| // If this happens it's probably a union. | |
| print!( | |
| "print-type-size {indent}{kind} `.{name}`: {size} bytes, \ | |
| offset: {offset} bytes, \ | |
| alignment: {align} bytes" | |
| ); | |
| } else if info.packed || offset == min_offset { | |
| print!("print-type-size {indent}{kind} `.{name}`: {size} bytes"); | |
| } else { | |
| // Include field alignment in output only if it caused padding injection | |
| print!( | |
| "print-type-size {indent}{kind} `.{name}`: {size} bytes, \ | |
| alignment: {align} bytes" | |
| ); | |
| } | |
| if let Some(type_name) = type_name { | |
| println!(", type: {type_name}"); | |
| } else { | |
| println!(); | |
| } | |
| min_offset = offset + size; | |
| } | |
| } | |
| match overall_size.checked_sub(max_variant_size) { | |
| None => panic!("max_variant_size {max_variant_size} > {overall_size} overall_size"), | |
| Some(diff @ 1..) => println!("print-type-size {indent}end padding: {diff} bytes"), | |
| Some(0) => {} | |
| } | |
| } | |
| } | |
| } |