about summary refs log tree commit diff
path: root/compiler/rustc_session/src/code_stats.rs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_session/src/code_stats.rs')
-rw-r--r--compiler/rustc_session/src/code_stats.rs270
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}" }}"#
+            );
+        }
+    }
+}