diff options
Diffstat (limited to 'compiler/rustc_session/src/code_stats.rs')
| -rw-r--r-- | compiler/rustc_session/src/code_stats.rs | 270 |
1 files changed, 270 insertions, 0 deletions
diff --git a/compiler/rustc_session/src/code_stats.rs b/compiler/rustc_session/src/code_stats.rs new file mode 100644 index 00000000000..f3c21992784 --- /dev/null +++ b/compiler/rustc_session/src/code_stats.rs @@ -0,0 +1,270 @@ +use std::cmp; + +use rustc_abi::{Align, Size}; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::sync::Lock; +use rustc_span::Symbol; +use rustc_span::def_id::DefId; + +#[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>, +} + +pub struct VTableSizeInfo { + pub trait_name: String, + + /// Number of entries in a vtable with the current algorithm + /// (i.e. with upcasting). + pub entries: usize, + + /// Number of entries in a vtable, as-if we did not have trait upcasting. + pub entries_ignoring_upcasting: usize, + + /// Number of entries in a vtable needed solely for upcasting + /// (i.e. `entries - entries_ignoring_upcasting`). + pub entries_for_upcasting: usize, + + /// Cost of having upcasting in % relative to the number of entries without + /// upcasting (i.e. `entries_for_upcasting / entries_ignoring_upcasting * 100%`). + pub upcasting_cost_percent: f64, +} + +#[derive(Default)] +pub struct CodeStats { + type_sizes: Lock<FxHashSet<TypeSizeInfo>>, + vtable_sizes: Lock<FxHashMap<DefId, VTableSizeInfo>>, +} + +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 record_vtable_size(&self, trait_did: DefId, trait_name: &str, info: VTableSizeInfo) { + let prev = self.vtable_sizes.lock().insert(trait_did, info); + assert!( + prev.is_none(), + "size of vtable for `{trait_name}` ({trait_did:?}) is already recorded" + ); + } + + 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) => {} + } + } + } + + pub fn print_vtable_sizes(&self, crate_name: Symbol) { + // We will soon sort, so the initial order does not matter. + #[allow(rustc::potential_query_instability)] + let mut infos = + std::mem::take(&mut *self.vtable_sizes.lock()).into_values().collect::<Vec<_>>(); + + // Primary sort: cost % in reverse order (from largest to smallest) + // Secondary sort: trait_name + infos.sort_by(|a, b| { + a.upcasting_cost_percent + .total_cmp(&b.upcasting_cost_percent) + .reverse() + .then_with(|| a.trait_name.cmp(&b.trait_name)) + }); + + for VTableSizeInfo { + trait_name, + entries, + entries_ignoring_upcasting, + entries_for_upcasting, + upcasting_cost_percent, + } in infos + { + println!( + r#"print-vtable-sizes {{ "crate_name": "{crate_name}", "trait_name": "{trait_name}", "entries": "{entries}", "entries_ignoring_upcasting": "{entries_ignoring_upcasting}", "entries_for_upcasting": "{entries_for_upcasting}", "upcasting_cost_percent": "{upcasting_cost_percent}" }}"# + ); + } + } +} |
