]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_session/src/code_stats.rs
Merge commit '598f0909568a51de8a2d1148f55a644fd8dffad0' into sync_cg_clif-2023-01-24
[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 struct FieldInfo {
24     pub name: Symbol,
25     pub offset: u64,
26     pub size: u64,
27     pub align: u64,
28 }
29
30 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
31 pub enum DataTypeKind {
32     Struct,
33     Union,
34     Enum,
35     Closure,
36     Generator,
37 }
38
39 #[derive(PartialEq, Eq, Hash, Debug)]
40 pub struct TypeSizeInfo {
41     pub kind: DataTypeKind,
42     pub type_description: String,
43     pub align: u64,
44     pub overall_size: u64,
45     pub packed: bool,
46     pub opt_discr_size: Option<u64>,
47     pub variants: Vec<VariantInfo>,
48 }
49
50 #[derive(Default)]
51 pub struct CodeStats {
52     type_sizes: Lock<FxHashSet<TypeSizeInfo>>,
53 }
54
55 impl CodeStats {
56     pub fn record_type_size<S: ToString>(
57         &self,
58         kind: DataTypeKind,
59         type_desc: S,
60         align: Align,
61         overall_size: Size,
62         packed: bool,
63         opt_discr_size: Option<Size>,
64         mut variants: Vec<VariantInfo>,
65     ) {
66         // Sort variants so the largest ones are shown first. A stable sort is
67         // used here so that source code order is preserved for all variants
68         // that have the same size.
69         variants.sort_by(|info1, info2| info2.size.cmp(&info1.size));
70         let info = TypeSizeInfo {
71             kind,
72             type_description: type_desc.to_string(),
73             align: align.bytes(),
74             overall_size: overall_size.bytes(),
75             packed,
76             opt_discr_size: opt_discr_size.map(|s| s.bytes()),
77             variants,
78         };
79         self.type_sizes.borrow_mut().insert(info);
80     }
81
82     pub fn print_type_sizes(&self) {
83         let type_sizes = self.type_sizes.borrow();
84         let mut sorted: Vec<_> = type_sizes.iter().collect();
85
86         // Primary sort: large-to-small.
87         // Secondary sort: description (dictionary order)
88         sorted.sort_by(|info1, info2| {
89             // (reversing cmp order to get large-to-small ordering)
90             match info2.overall_size.cmp(&info1.overall_size) {
91                 Ordering::Equal => info1.type_description.cmp(&info2.type_description),
92                 other => other,
93             }
94         });
95
96         for info in sorted {
97             let TypeSizeInfo { type_description, overall_size, align, kind, variants, .. } = info;
98             println!(
99                 "print-type-size type: `{type_description}`: {overall_size} bytes, alignment: {align} bytes"
100             );
101             let indent = "    ";
102
103             let discr_size = if let Some(discr_size) = info.opt_discr_size {
104                 println!("print-type-size {indent}discriminant: {discr_size} bytes");
105                 discr_size
106             } else {
107                 0
108             };
109
110             // We start this at discr_size (rather than 0) because
111             // things like C-enums do not have variants but we still
112             // want the max_variant_size at the end of the loop below
113             // to reflect the presence of the discriminant.
114             let mut max_variant_size = discr_size;
115
116             let struct_like = match kind {
117                 DataTypeKind::Struct | DataTypeKind::Closure => true,
118                 DataTypeKind::Enum | DataTypeKind::Union | DataTypeKind::Generator => false,
119             };
120             for (i, variant_info) in variants.into_iter().enumerate() {
121                 let VariantInfo { ref name, kind: _, align: _, size, ref fields } = *variant_info;
122                 let indent = if !struct_like {
123                     let name = match name.as_ref() {
124                         Some(name) => name.to_string(),
125                         None => i.to_string(),
126                     };
127                     println!(
128                         "print-type-size {indent}variant `{name}`: {diff} bytes",
129                         diff = 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 {
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 {indent}padding: {pad} bytes");
153                     }
154
155                     if offset < min_offset {
156                         // If this happens it's probably a union.
157                         println!(
158                             "print-type-size {indent}field `.{name}`: {size} bytes, \
159                                   offset: {offset} bytes, \
160                                   alignment: {align} bytes"
161                         );
162                     } else if info.packed || offset == min_offset {
163                         println!("print-type-size {indent}field `.{name}`: {size} bytes");
164                     } else {
165                         // Include field alignment in output only if it caused padding injection
166                         println!(
167                             "print-type-size {indent}field `.{name}`: {size} bytes, \
168                                   alignment: {align} bytes"
169                         );
170                     }
171
172                     min_offset = offset + size;
173                 }
174             }
175
176             match overall_size.checked_sub(max_variant_size) {
177                 None => panic!("max_variant_size {max_variant_size} > {overall_size} overall_size"),
178                 Some(diff @ 1..) => println!("print-type-size {indent}end padding: {diff} bytes"),
179                 Some(0) => {}
180             }
181         }
182     }
183 }