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