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 pub type_sizes: Lock<FxHashSet<TypeSizeInfo>>,
78}
79
80impl CodeStats {
81 pub fn record_type_size<S: ToString>(
82 &self,
83 kind: DataTypeKind,
84 type_desc: S,
85 align: Align,
86 overall_size: Size,
87 packed: bool,
88 opt_discr_size: Option<Size>,
89 mut variants: Vec<VariantInfo>,
90 ) {
91 if kind != DataTypeKind::Coroutine {
97 variants.sort_by_key(|info| cmp::Reverse(info.size));
98 }
99 let info = TypeSizeInfo {
100 kind,
101 type_description: type_desc.to_string(),
102 align: align.bytes(),
103 overall_size: overall_size.bytes(),
104 packed,
105 opt_discr_size: opt_discr_size.map(|s| s.bytes()),
106 variants,
107 };
108 self.type_sizes.borrow_mut().insert(info);
109 }
110
111 pub fn print_type_sizes(&self) {
112 let type_sizes = self.type_sizes.borrow();
113 #[allow(rustc::potential_query_instability)]
115 let mut sorted: Vec<_> = type_sizes.iter().collect();
116
117 sorted.sort_by_key(|info| (cmp::Reverse(info.overall_size), &info.type_description));
120
121 for info in sorted {
122 let TypeSizeInfo { type_description, overall_size, align, kind, variants, .. } = info;
123 println!(
124 "print-type-size type: `{type_description}`: {overall_size} bytes, alignment: {align} bytes"
125 );
126 let indent = " ";
127
128 let discr_size = if let Some(discr_size) = info.opt_discr_size {
129 println!("print-type-size {indent}discriminant: {discr_size} bytes");
130 discr_size
131 } else {
132 0
133 };
134
135 let mut max_variant_size = discr_size;
140
141 let struct_like = match kind {
142 DataTypeKind::Struct | DataTypeKind::Closure => true,
143 DataTypeKind::Enum | DataTypeKind::Union | DataTypeKind::Coroutine => false,
144 };
145 for (i, variant_info) in variants.into_iter().enumerate() {
146 let VariantInfo { ref name, kind: _, align: _, size, ref fields } = *variant_info;
147 let indent = if !struct_like {
148 let name = match name.as_ref() {
149 Some(name) => name.to_string(),
150 None => i.to_string(),
151 };
152 println!(
153 "print-type-size {indent}variant `{name}`: {diff} bytes",
154 diff = size - discr_size
155 );
156 " "
157 } else {
158 assert!(i < 1);
159 " "
160 };
161 max_variant_size = cmp::max(max_variant_size, size);
162
163 let mut min_offset = discr_size;
164
165 let mut fields = fields.clone();
170 fields.sort_by_key(|f| (f.offset, f.size));
171
172 for field in fields {
173 let FieldInfo { kind, ref name, offset, size, align, type_name } = field;
174
175 if offset > min_offset {
176 let pad = offset - min_offset;
177 println!("print-type-size {indent}padding: {pad} bytes");
178 }
179
180 if offset < min_offset {
181 print!(
183 "print-type-size {indent}{kind} `.{name}`: {size} bytes, \
184 offset: {offset} bytes, \
185 alignment: {align} bytes"
186 );
187 } else if info.packed || offset == min_offset {
188 print!("print-type-size {indent}{kind} `.{name}`: {size} bytes");
189 } else {
190 print!(
192 "print-type-size {indent}{kind} `.{name}`: {size} bytes, \
193 alignment: {align} bytes"
194 );
195 }
196
197 if let Some(type_name) = type_name {
198 println!(", type: {type_name}");
199 } else {
200 println!();
201 }
202
203 min_offset = offset + size;
204 }
205 }
206
207 match overall_size.checked_sub(max_variant_size) {
208 None => panic!("max_variant_size {max_variant_size} > {overall_size} overall_size"),
209 Some(diff @ 1..) => println!("print-type-size {indent}end padding: {diff} bytes"),
210 Some(0) => {}
211 }
212 }
213 }
214}