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