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