1use std::cmp;
2
3use rustc_abi::{Align, Size};
4use rustc_data_structures::fx::FxHashSet;
5use rustc_data_structures::sync::Lock;
6use rustc_span::Symbol;
7
8#[derive(Clone, PartialEq, Eq, Hash, Debug)]
9pub 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)]
18pub enum SizeKind {
19 Exact,
20 Min,
21}
22
23#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
24pub enum FieldKind {
25 AdtField,
26 Upvar,
27 CoroutineLocal,
28}
29
30impl std::fmt::Display for FieldKind {
31 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::CoroutineLocal => write!(w, "local"),
36 }
37 }
38}
39
40#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
41pub struct FieldInfo {
42 pub kind: FieldKind,
43 pub name: Symbol,
44 pub offset: u64,
45 pub size: u64,
46 pub align: u64,
47 pub type_name: Option<Symbol>,
51}
52
53#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
54pub enum DataTypeKind {
55 Struct,
56 Union,
57 Enum,
58 Closure,
59 Coroutine,
60}
61
62#[derive(PartialEq, Eq, Hash, Debug)]
63pub struct TypeSizeInfo {
64 pub kind: DataTypeKind,
65 pub type_description: String,
66 pub align: u64,
67 pub overall_size: u64,
68 pub packed: bool,
69 pub opt_discr_size: Option<u64>,
70 pub variants: Vec<VariantInfo>,
71}
72
73#[derive(Default)]
74pub struct CodeStats {
75 type_sizes: Lock<FxHashSet<TypeSizeInfo>>,
76}
77
78impl CodeStats {
79 pub fn record_type_size<S: ToString>(
80 &self,
81 kind: DataTypeKind,
82 type_desc: S,
83 align: Align,
84 overall_size: Size,
85 packed: bool,
86 opt_discr_size: Option<Size>,
87 mut variants: Vec<VariantInfo>,
88 ) {
89 if kind != DataTypeKind::Coroutine {
95 variants.sort_by_key(|info| cmp::Reverse(info.size));
96 }
97 let info = TypeSizeInfo {
98 kind,
99 type_description: type_desc.to_string(),
100 align: align.bytes(),
101 overall_size: overall_size.bytes(),
102 packed,
103 opt_discr_size: opt_discr_size.map(|s| s.bytes()),
104 variants,
105 };
106 self.type_sizes.borrow_mut().insert(info);
107 }
108
109 pub fn print_type_sizes(&self) {
110 let type_sizes = self.type_sizes.borrow();
111 #[allow(rustc::potential_query_instability)]
113 let mut sorted: Vec<_> = type_sizes.iter().collect();
114
115 sorted.sort_by_key(|info| (cmp::Reverse(info.overall_size), &info.type_description));
118
119 for info in sorted {
120 let TypeSizeInfo { type_description, overall_size, align, kind, variants, .. } = info;
121 println!(
122 "print-type-size type: `{type_description}`: {overall_size} bytes, alignment: {align} bytes"
123 );
124 let indent = " ";
125
126 let discr_size = if let Some(discr_size) = info.opt_discr_size {
127 println!("print-type-size {indent}discriminant: {discr_size} bytes");
128 discr_size
129 } else {
130 0
131 };
132
133 let mut max_variant_size = discr_size;
138
139 let struct_like = match kind {
140 DataTypeKind::Struct | DataTypeKind::Closure => true,
141 DataTypeKind::Enum | DataTypeKind::Union | DataTypeKind::Coroutine => false,
142 };
143 for (i, variant_info) in variants.into_iter().enumerate() {
144 let VariantInfo { ref name, kind: _, align: _, size, ref fields } = *variant_info;
145 let indent = if !struct_like {
146 let name = match name.as_ref() {
147 Some(name) => name.to_string(),
148 None => i.to_string(),
149 };
150 println!(
151 "print-type-size {indent}variant `{name}`: {diff} bytes",
152 diff = size - discr_size
153 );
154 " "
155 } else {
156 assert!(i < 1);
157 " "
158 };
159 max_variant_size = cmp::max(max_variant_size, size);
160
161 let mut min_offset = discr_size;
162
163 let mut fields = fields.clone();
168 fields.sort_by_key(|f| (f.offset, f.size));
169
170 for field in fields {
171 let FieldInfo { kind, ref name, offset, size, align, type_name } = field;
172
173 if offset > min_offset {
174 let pad = offset - min_offset;
175 println!("print-type-size {indent}padding: {pad} bytes");
176 }
177
178 if offset < min_offset {
179 print!(
181 "print-type-size {indent}{kind} `.{name}`: {size} bytes, \
182 offset: {offset} bytes, \
183 alignment: {align} bytes"
184 );
185 } else if info.packed || offset == min_offset {
186 print!("print-type-size {indent}{kind} `.{name}`: {size} bytes");
187 } else {
188 print!(
190 "print-type-size {indent}{kind} `.{name}`: {size} bytes, \
191 alignment: {align} bytes"
192 );
193 }
194
195 if let Some(type_name) = type_name {
196 println!(", type: {type_name}");
197 } else {
198 println!();
199 }
200
201 min_offset = offset + size;
202 }
203 }
204
205 match overall_size.checked_sub(max_variant_size) {
206 None => panic!("max_variant_size {max_variant_size} > {overall_size} overall_size"),
207 Some(diff @ 1..) => println!("print-type-size {indent}end padding: {diff} bytes"),
208 Some(0) => {}
209 }
210 }
211 }
212}