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};
7 #[derive(Clone, PartialEq, Eq, Hash, Debug)]
8 pub struct VariantInfo {
9 pub name: Option<Symbol>,
13 pub fields: Vec<FieldInfo>,
16 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
22 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
29 impl std::fmt::Display for FieldKind {
30 fn fmt(&self, w: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32 FieldKind::AdtField => write!(w, "field"),
33 FieldKind::Upvar => write!(w, "upvar"),
34 FieldKind::GeneratorLocal => write!(w, "local"),
39 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
40 pub struct FieldInfo {
48 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
49 pub enum DataTypeKind {
57 #[derive(PartialEq, Eq, Hash, Debug)]
58 pub struct TypeSizeInfo {
59 pub kind: DataTypeKind,
60 pub type_description: String,
62 pub overall_size: u64,
64 pub opt_discr_size: Option<u64>,
65 pub variants: Vec<VariantInfo>,
69 pub struct CodeStats {
70 type_sizes: Lock<FxHashSet<TypeSizeInfo>>,
74 pub fn record_type_size<S: ToString>(
81 opt_discr_size: Option<Size>,
82 mut variants: Vec<VariantInfo>,
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));
92 let info = TypeSizeInfo {
94 type_description: type_desc.to_string(),
96 overall_size: overall_size.bytes(),
98 opt_discr_size: opt_discr_size.map(|s| s.bytes()),
101 self.type_sizes.borrow_mut().insert(info);
104 pub fn print_type_sizes(&self) {
105 let type_sizes = self.type_sizes.borrow();
106 let mut sorted: Vec<_> = type_sizes.iter().collect();
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),
119 let TypeSizeInfo { type_description, overall_size, align, kind, variants, .. } = info;
121 "print-type-size type: `{type_description}`: {overall_size} bytes, alignment: {align} bytes"
125 let discr_size = if let Some(discr_size) = info.opt_discr_size {
126 println!("print-type-size {indent}discriminant: {discr_size} bytes");
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;
138 let struct_like = match kind {
139 DataTypeKind::Struct | DataTypeKind::Closure => true,
140 DataTypeKind::Enum | DataTypeKind::Union | DataTypeKind::Generator => false,
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(),
150 "print-type-size {indent}variant `{name}`: {diff} bytes",
151 diff = size - discr_size
158 max_variant_size = cmp::max(max_variant_size, size);
160 let mut min_offset = discr_size;
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
166 let mut fields = fields.clone();
167 fields.sort_by_key(|f| (f.offset, f.size));
169 for field in fields {
170 let FieldInfo { kind, ref name, offset, size, align } = field;
172 if offset > min_offset {
173 let pad = offset - min_offset;
174 println!("print-type-size {indent}padding: {pad} bytes");
177 if offset < min_offset {
178 // If this happens it's probably a union.
180 "print-type-size {indent}{kind} `.{name}`: {size} bytes, \
181 offset: {offset} bytes, \
182 alignment: {align} bytes"
184 } else if info.packed || offset == min_offset {
185 println!("print-type-size {indent}{kind} `.{name}`: {size} bytes");
187 // Include field alignment in output only if it caused padding injection
189 "print-type-size {indent}{kind} `.{name}`: {size} bytes, \
190 alignment: {align} bytes"
194 min_offset = offset + size;
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"),