rustc_session/
code_stats.rs

1use std::cmp;
2
3use rustc_abi::{Align, Size};
4use rustc_data_structures::fx::FxHashSet;
5use rustc_data_structures::sync::Lock;
6use rustc_span::Symbol;
7
8#[derive(Clone, PartialEq, Eq, Hash, Debug)]
9pub struct VariantInfo {
10    pub name: Option<Symbol>,
11    pub kind: SizeKind,
12    pub size: u64,
13    pub align: u64,
14    pub fields: Vec<FieldInfo>,
15}
16
17#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
18pub enum SizeKind {
19    Exact,
20    Min,
21}
22
23#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
24pub enum FieldKind {
25    AdtField,
26    Upvar,
27    CoroutineLocal,
28}
29
30impl std::fmt::Display for FieldKind {
31    fn fmt(&self, w: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        match self {
33            FieldKind::AdtField => write!(w, "field"),
34            FieldKind::Upvar => write!(w, "upvar"),
35            FieldKind::CoroutineLocal => write!(w, "local"),
36        }
37    }
38}
39
40#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
41pub struct FieldInfo {
42    pub kind: FieldKind,
43    pub name: Symbol,
44    pub offset: u64,
45    pub size: u64,
46    pub align: u64,
47    /// Name of the type of this field.
48    /// Present only if the creator thought that this would be important for identifying the field,
49    /// typically because the field name is uninformative.
50    pub type_name: Option<Symbol>,
51}
52
53#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
54pub enum DataTypeKind {
55    Struct,
56    Union,
57    Enum,
58    Closure,
59    Coroutine,
60}
61
62#[derive(PartialEq, Eq, Hash, Debug)]
63pub struct TypeSizeInfo {
64    pub kind: DataTypeKind,
65    pub type_description: String,
66    pub align: u64,
67    pub overall_size: u64,
68    pub packed: bool,
69    pub opt_discr_size: Option<u64>,
70    pub variants: Vec<VariantInfo>,
71}
72
73#[derive(Default)]
74pub struct CodeStats {
75    type_sizes: Lock<FxHashSet<TypeSizeInfo>>,
76}
77
78impl CodeStats {
79    pub fn record_type_size<S: ToString>(
80        &self,
81        kind: DataTypeKind,
82        type_desc: S,
83        align: Align,
84        overall_size: Size,
85        packed: bool,
86        opt_discr_size: Option<Size>,
87        mut variants: Vec<VariantInfo>,
88    ) {
89        // Sort variants so the largest ones are shown first. A stable sort is
90        // used here so that source code order is preserved for all variants
91        // that have the same size.
92        // Except for Coroutines, whose variants are already sorted according to
93        // their yield points in `variant_info_for_coroutine`.
94        if kind != DataTypeKind::Coroutine {
95            variants.sort_by_key(|info| cmp::Reverse(info.size));
96        }
97        let info = TypeSizeInfo {
98            kind,
99            type_description: type_desc.to_string(),
100            align: align.bytes(),
101            overall_size: overall_size.bytes(),
102            packed,
103            opt_discr_size: opt_discr_size.map(|s| s.bytes()),
104            variants,
105        };
106        self.type_sizes.borrow_mut().insert(info);
107    }
108
109    pub fn print_type_sizes(&self) {
110        let type_sizes = self.type_sizes.borrow();
111        // We will soon sort, so the initial order does not matter.
112        #[allow(rustc::potential_query_instability)]
113        let mut sorted: Vec<_> = type_sizes.iter().collect();
114
115        // Primary sort: large-to-small.
116        // Secondary sort: description (dictionary order)
117        sorted.sort_by_key(|info| (cmp::Reverse(info.overall_size), &info.type_description));
118
119        for info in sorted {
120            let TypeSizeInfo { type_description, overall_size, align, kind, variants, .. } = info;
121            println!(
122                "print-type-size type: `{type_description}`: {overall_size} bytes, alignment: {align} bytes"
123            );
124            let indent = "    ";
125
126            let discr_size = if let Some(discr_size) = info.opt_discr_size {
127                println!("print-type-size {indent}discriminant: {discr_size} bytes");
128                discr_size
129            } else {
130                0
131            };
132
133            // We start this at discr_size (rather than 0) because
134            // things like C-enums do not have variants but we still
135            // want the max_variant_size at the end of the loop below
136            // to reflect the presence of the discriminant.
137            let mut max_variant_size = discr_size;
138
139            let struct_like = match kind {
140                DataTypeKind::Struct | DataTypeKind::Closure => true,
141                DataTypeKind::Enum | DataTypeKind::Union | DataTypeKind::Coroutine => false,
142            };
143            for (i, variant_info) in variants.into_iter().enumerate() {
144                let VariantInfo { ref name, kind: _, align: _, size, ref fields } = *variant_info;
145                let indent = if !struct_like {
146                    let name = match name.as_ref() {
147                        Some(name) => name.to_string(),
148                        None => i.to_string(),
149                    };
150                    println!(
151                        "print-type-size {indent}variant `{name}`: {diff} bytes",
152                        diff = size - discr_size
153                    );
154                    "        "
155                } else {
156                    assert!(i < 1);
157                    "    "
158                };
159                max_variant_size = cmp::max(max_variant_size, size);
160
161                let mut min_offset = discr_size;
162
163                // We want to print fields by increasing offset. We also want
164                // zero-sized fields before non-zero-sized fields, otherwise
165                // the loop below goes wrong; hence the `f.size` in the sort
166                // key.
167                let mut fields = fields.clone();
168                fields.sort_by_key(|f| (f.offset, f.size));
169
170                for field in fields {
171                    let FieldInfo { kind, ref name, offset, size, align, type_name } = field;
172
173                    if offset > min_offset {
174                        let pad = offset - min_offset;
175                        println!("print-type-size {indent}padding: {pad} bytes");
176                    }
177
178                    if offset < min_offset {
179                        // If this happens it's probably a union.
180                        print!(
181                            "print-type-size {indent}{kind} `.{name}`: {size} bytes, \
182                                  offset: {offset} bytes, \
183                                  alignment: {align} bytes"
184                        );
185                    } else if info.packed || offset == min_offset {
186                        print!("print-type-size {indent}{kind} `.{name}`: {size} bytes");
187                    } else {
188                        // Include field alignment in output only if it caused padding injection
189                        print!(
190                            "print-type-size {indent}{kind} `.{name}`: {size} bytes, \
191                                  alignment: {align} bytes"
192                        );
193                    }
194
195                    if let Some(type_name) = type_name {
196                        println!(", type: {type_name}");
197                    } else {
198                        println!();
199                    }
200
201                    min_offset = offset + size;
202                }
203            }
204
205            match overall_size.checked_sub(max_variant_size) {
206                None => panic!("max_variant_size {max_variant_size} > {overall_size} overall_size"),
207                Some(diff @ 1..) => println!("print-type-size {indent}end padding: {diff} bytes"),
208                Some(0) => {}
209            }
210        }
211    }
212}