• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
2 use rustc_data_structures::sync::Lock;
3 use rustc_span::def_id::DefId;
4 use rustc_span::Symbol;
5 use rustc_target::abi::{Align, Size};
6 use std::cmp;
7 
8 #[derive(Clone, PartialEq, Eq, Hash, Debug)]
9 pub 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)]
18 pub enum SizeKind {
19     Exact,
20     Min,
21 }
22 
23 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
24 pub enum FieldKind {
25     AdtField,
26     Upvar,
27     GeneratorLocal,
28 }
29 
30 impl std::fmt::Display for FieldKind {
fmt(&self, w: &mut std::fmt::Formatter<'_>) -> std::fmt::Result31     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::GeneratorLocal => write!(w, "local"),
36         }
37     }
38 }
39 
40 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
41 pub struct FieldInfo {
42     pub kind: FieldKind,
43     pub name: Symbol,
44     pub offset: u64,
45     pub size: u64,
46     pub align: u64,
47 }
48 
49 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
50 pub enum DataTypeKind {
51     Struct,
52     Union,
53     Enum,
54     Closure,
55     Generator,
56 }
57 
58 #[derive(PartialEq, Eq, Hash, Debug)]
59 pub struct TypeSizeInfo {
60     pub kind: DataTypeKind,
61     pub type_description: String,
62     pub align: u64,
63     pub overall_size: u64,
64     pub packed: bool,
65     pub opt_discr_size: Option<u64>,
66     pub variants: Vec<VariantInfo>,
67 }
68 
69 pub struct VTableSizeInfo {
70     pub trait_name: String,
71 
72     /// Number of entries in a vtable with the current algorithm
73     /// (i.e. with upcasting).
74     pub entries: usize,
75 
76     /// Number of entries in a vtable, as-if we did not have trait upcasting.
77     pub entries_ignoring_upcasting: usize,
78 
79     /// Number of entries in a vtable needed solely for upcasting
80     /// (i.e. `entries - entries_ignoring_upcasting`).
81     pub entries_for_upcasting: usize,
82 
83     /// Cost of having upcasting in % relative to the number of entries without
84     /// upcasting (i.e. `entries_for_upcasting / entries_ignoring_upcasting * 100%`).
85     pub upcasting_cost_percent: f64,
86 }
87 
88 #[derive(Default)]
89 pub struct CodeStats {
90     type_sizes: Lock<FxHashSet<TypeSizeInfo>>,
91     vtable_sizes: Lock<FxHashMap<DefId, VTableSizeInfo>>,
92 }
93 
94 impl CodeStats {
record_type_size<S: ToString>( &self, kind: DataTypeKind, type_desc: S, align: Align, overall_size: Size, packed: bool, opt_discr_size: Option<Size>, mut variants: Vec<VariantInfo>, )95     pub fn record_type_size<S: ToString>(
96         &self,
97         kind: DataTypeKind,
98         type_desc: S,
99         align: Align,
100         overall_size: Size,
101         packed: bool,
102         opt_discr_size: Option<Size>,
103         mut variants: Vec<VariantInfo>,
104     ) {
105         // Sort variants so the largest ones are shown first. A stable sort is
106         // used here so that source code order is preserved for all variants
107         // that have the same size.
108         // Except for Generators, whose variants are already sorted according to
109         // their yield points in `variant_info_for_generator`.
110         if kind != DataTypeKind::Generator {
111             variants.sort_by_key(|info| cmp::Reverse(info.size));
112         }
113         let info = TypeSizeInfo {
114             kind,
115             type_description: type_desc.to_string(),
116             align: align.bytes(),
117             overall_size: overall_size.bytes(),
118             packed,
119             opt_discr_size: opt_discr_size.map(|s| s.bytes()),
120             variants,
121         };
122         self.type_sizes.borrow_mut().insert(info);
123     }
124 
record_vtable_size(&self, trait_did: DefId, trait_name: &str, info: VTableSizeInfo)125     pub fn record_vtable_size(&self, trait_did: DefId, trait_name: &str, info: VTableSizeInfo) {
126         let prev = self.vtable_sizes.lock().insert(trait_did, info);
127         assert!(
128             prev.is_none(),
129             "size of vtable for `{trait_name}` ({trait_did:?}) is already recorded"
130         );
131     }
132 
print_type_sizes(&self)133     pub fn print_type_sizes(&self) {
134         let type_sizes = self.type_sizes.borrow();
135         let mut sorted: Vec<_> = type_sizes.iter().collect();
136 
137         // Primary sort: large-to-small.
138         // Secondary sort: description (dictionary order)
139         sorted.sort_by_key(|info| (cmp::Reverse(info.overall_size), &info.type_description));
140 
141         for info in sorted {
142             let TypeSizeInfo { type_description, overall_size, align, kind, variants, .. } = info;
143             println!(
144                 "print-type-size type: `{type_description}`: {overall_size} bytes, alignment: {align} bytes"
145             );
146             let indent = "    ";
147 
148             let discr_size = if let Some(discr_size) = info.opt_discr_size {
149                 println!("print-type-size {indent}discriminant: {discr_size} bytes");
150                 discr_size
151             } else {
152                 0
153             };
154 
155             // We start this at discr_size (rather than 0) because
156             // things like C-enums do not have variants but we still
157             // want the max_variant_size at the end of the loop below
158             // to reflect the presence of the discriminant.
159             let mut max_variant_size = discr_size;
160 
161             let struct_like = match kind {
162                 DataTypeKind::Struct | DataTypeKind::Closure => true,
163                 DataTypeKind::Enum | DataTypeKind::Union | DataTypeKind::Generator => false,
164             };
165             for (i, variant_info) in variants.into_iter().enumerate() {
166                 let VariantInfo { ref name, kind: _, align: _, size, ref fields } = *variant_info;
167                 let indent = if !struct_like {
168                     let name = match name.as_ref() {
169                         Some(name) => name.to_string(),
170                         None => i.to_string(),
171                     };
172                     println!(
173                         "print-type-size {indent}variant `{name}`: {diff} bytes",
174                         diff = size - discr_size
175                     );
176                     "        "
177                 } else {
178                     assert!(i < 1);
179                     "    "
180                 };
181                 max_variant_size = cmp::max(max_variant_size, size);
182 
183                 let mut min_offset = discr_size;
184 
185                 // We want to print fields by increasing offset. We also want
186                 // zero-sized fields before non-zero-sized fields, otherwise
187                 // the loop below goes wrong; hence the `f.size` in the sort
188                 // key.
189                 let mut fields = fields.clone();
190                 fields.sort_by_key(|f| (f.offset, f.size));
191 
192                 for field in fields {
193                     let FieldInfo { kind, ref name, offset, size, align } = field;
194 
195                     if offset > min_offset {
196                         let pad = offset - min_offset;
197                         println!("print-type-size {indent}padding: {pad} bytes");
198                     }
199 
200                     if offset < min_offset {
201                         // If this happens it's probably a union.
202                         println!(
203                             "print-type-size {indent}{kind} `.{name}`: {size} bytes, \
204                                   offset: {offset} bytes, \
205                                   alignment: {align} bytes"
206                         );
207                     } else if info.packed || offset == min_offset {
208                         println!("print-type-size {indent}{kind} `.{name}`: {size} bytes");
209                     } else {
210                         // Include field alignment in output only if it caused padding injection
211                         println!(
212                             "print-type-size {indent}{kind} `.{name}`: {size} bytes, \
213                                   alignment: {align} bytes"
214                         );
215                     }
216 
217                     min_offset = offset + size;
218                 }
219             }
220 
221             match overall_size.checked_sub(max_variant_size) {
222                 None => panic!("max_variant_size {max_variant_size} > {overall_size} overall_size"),
223                 Some(diff @ 1..) => println!("print-type-size {indent}end padding: {diff} bytes"),
224                 Some(0) => {}
225             }
226         }
227     }
228 
print_vtable_sizes(&self, crate_name: &str)229     pub fn print_vtable_sizes(&self, crate_name: &str) {
230         let mut infos = std::mem::take(&mut *self.vtable_sizes.lock())
231             .into_iter()
232             .map(|(_did, stats)| stats)
233             .collect::<Vec<_>>();
234 
235         // Primary sort: cost % in reverse order (from largest to smallest)
236         // Secondary sort: trait_name
237         infos.sort_by(|a, b| {
238             a.upcasting_cost_percent
239                 .total_cmp(&b.upcasting_cost_percent)
240                 .reverse()
241                 .then_with(|| a.trait_name.cmp(&b.trait_name))
242         });
243 
244         for VTableSizeInfo {
245             trait_name,
246             entries,
247             entries_ignoring_upcasting,
248             entries_for_upcasting,
249             upcasting_cost_percent,
250         } in infos
251         {
252             println!(
253                 r#"print-vtable-sizes {{ "crate_name": "{crate_name}", "trait_name": "{trait_name}", "entries": "{entries}", "entries_ignoring_upcasting": "{entries_ignoring_upcasting}", "entries_for_upcasting": "{entries_for_upcasting}", "upcasting_cost_percent": "{upcasting_cost_percent}" }}"#
254             );
255         }
256     }
257 }
258