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    /// The hash set that actually holds all the type size information.
76    /// The field is public for use in external tools. See #139876.
77    pub type_sizes: Lock<FxHashSet<TypeSizeInfo>>,
78}
79
80impl CodeStats {
81    pub fn record_type_size<S: ToString>(
82        &self,
83        kind: DataTypeKind,
84        type_desc: S,
85        align: Align,
86        overall_size: Size,
87        packed: bool,
88        opt_discr_size: Option<Size>,
89        mut variants: Vec<VariantInfo>,
90    ) {
91        // Sort variants so the largest ones are shown first. A stable sort is
92        // used here so that source code order is preserved for all variants
93        // that have the same size.
94        // Except for Coroutines, whose variants are already sorted according to
95        // their yield points in `variant_info_for_coroutine`.
96        if kind != DataTypeKind::Coroutine {
97            variants.sort_by_key(|info| cmp::Reverse(info.size));
98        }
99        let info = TypeSizeInfo {
100            kind,
101            type_description: type_desc.to_string(),
102            align: align.bytes(),
103            overall_size: overall_size.bytes(),
104            packed,
105            opt_discr_size: opt_discr_size.map(|s| s.bytes()),
106            variants,
107        };
108        self.type_sizes.borrow_mut().insert(info);
109    }
110
111    pub fn print_type_sizes(&self) {
112        let type_sizes = self.type_sizes.borrow();
113        // We will soon sort, so the initial order does not matter.
114        #[allow(rustc::potential_query_instability)]
115        let mut sorted: Vec<_> = type_sizes.iter().collect();
116
117        // Primary sort: large-to-small.
118        // Secondary sort: description (dictionary order)
119        sorted.sort_by_key(|info| (cmp::Reverse(info.overall_size), &info.type_description));
120
121        for info in sorted {
122            let TypeSizeInfo { type_description, overall_size, align, kind, variants, .. } = info;
123            println!(
124                "print-type-size type: `{type_description}`: {overall_size} bytes, alignment: {align} bytes"
125            );
126            let indent = "    ";
127
128            let discr_size = if let Some(discr_size) = info.opt_discr_size {
129                println!("print-type-size {indent}discriminant: {discr_size} bytes");
130                discr_size
131            } else {
132                0
133            };
134
135            // We start this at discr_size (rather than 0) because
136            // things like C-enums do not have variants but we still
137            // want the max_variant_size at the end of the loop below
138            // to reflect the presence of the discriminant.
139            let mut max_variant_size = discr_size;
140
141            let struct_like = match kind {
142                DataTypeKind::Struct | DataTypeKind::Closure => true,
143                DataTypeKind::Enum | DataTypeKind::Union | DataTypeKind::Coroutine => false,
144            };
145            for (i, variant_info) in variants.into_iter().enumerate() {
146                let VariantInfo { ref name, kind: _, align: _, size, ref fields } = *variant_info;
147                let indent = if !struct_like {
148                    let name = match name.as_ref() {
149                        Some(name) => name.to_string(),
150                        None => i.to_string(),
151                    };
152                    println!(
153                        "print-type-size {indent}variant `{name}`: {diff} bytes",
154                        diff = size - discr_size
155                    );
156                    "        "
157                } else {
158                    assert!(i < 1);
159                    "    "
160                };
161                max_variant_size = cmp::max(max_variant_size, size);
162
163                let mut min_offset = discr_size;
164
165                // We want to print fields by increasing offset. We also want
166                // zero-sized fields before non-zero-sized fields, otherwise
167                // the loop below goes wrong; hence the `f.size` in the sort
168                // key.
169                let mut fields = fields.clone();
170                fields.sort_by_key(|f| (f.offset, f.size));
171
172                for field in fields {
173                    let FieldInfo { kind, ref name, offset, size, align, type_name } = field;
174
175                    if offset > min_offset {
176                        let pad = offset - min_offset;
177                        println!("print-type-size {indent}padding: {pad} bytes");
178                    }
179
180                    if offset < min_offset {
181                        // If this happens it's probably a union.
182                        print!(
183                            "print-type-size {indent}{kind} `.{name}`: {size} bytes, \
184                                  offset: {offset} bytes, \
185                                  alignment: {align} bytes"
186                        );
187                    } else if info.packed || offset == min_offset {
188                        print!("print-type-size {indent}{kind} `.{name}`: {size} bytes");
189                    } else {
190                        // Include field alignment in output only if it caused padding injection
191                        print!(
192                            "print-type-size {indent}{kind} `.{name}`: {size} bytes, \
193                                  alignment: {align} bytes"
194                        );
195                    }
196
197                    if let Some(type_name) = type_name {
198                        println!(", type: {type_name}");
199                    } else {
200                        println!();
201                    }
202
203                    min_offset = offset + size;
204                }
205            }
206
207            match overall_size.checked_sub(max_variant_size) {
208                None => panic!("max_variant_size {max_variant_size} > {overall_size} overall_size"),
209                Some(diff @ 1..) => println!("print-type-size {indent}end padding: {diff} bytes"),
210                Some(0) => {}
211            }
212        }
213    }
214}