1use rustc_ast::visit::BoundKind;
6use rustc_ast::{self as ast, NodeId, visit as ast_visit};
7use rustc_data_structures::fx::{FxHashMap, FxHashSet};
8use rustc_data_structures::thousands::format_with_underscores;
9use rustc_hir::{self as hir, AmbigArg, HirId, intravisit as hir_visit};
10use rustc_middle::ty::TyCtxt;
11use rustc_span::Span;
12use rustc_span::def_id::LocalDefId;
13
14struct NodeStats {
15 count: usize,
16 size: usize,
17}
18
19impl NodeStats {
20 fn new() -> NodeStats {
21 NodeStats { count: 0, size: 0 }
22 }
23
24 fn accum_size(&self) -> usize {
25 self.count * self.size
26 }
27}
28
29struct Node {
30 stats: NodeStats,
31 subnodes: FxHashMap<&'static str, NodeStats>,
32}
33
34impl Node {
35 fn new() -> Node {
36 Node { stats: NodeStats::new(), subnodes: FxHashMap::default() }
37 }
38}
39
40struct StatCollector<'k> {
58 tcx: Option<TyCtxt<'k>>,
59 nodes: FxHashMap<&'static str, Node>,
60 seen: FxHashSet<HirId>,
61}
62
63pub fn print_hir_stats(tcx: TyCtxt<'_>) {
64 let mut collector =
65 StatCollector { tcx: Some(tcx), nodes: FxHashMap::default(), seen: FxHashSet::default() };
66 tcx.hir_walk_toplevel_module(&mut collector);
67 tcx.hir_walk_attributes(&mut collector);
68 collector.print("HIR STATS", "hir-stats");
69}
70
71pub fn print_ast_stats(krate: &ast::Crate, title: &str, prefix: &str) {
72 use rustc_ast::visit::Visitor;
73
74 let mut collector =
75 StatCollector { tcx: None, nodes: FxHashMap::default(), seen: FxHashSet::default() };
76 collector.visit_crate(krate);
77 collector.print(title, prefix);
78}
79
80impl<'k> StatCollector<'k> {
81 fn record<T>(&mut self, label: &'static str, id: Option<HirId>, val: &T) {
83 self.record_inner(label, None, id, val);
84 }
85
86 fn record_variant<T>(
88 &mut self,
89 label1: &'static str,
90 label2: &'static str,
91 id: Option<HirId>,
92 val: &T,
93 ) {
94 self.record_inner(label1, Some(label2), id, val);
95 }
96
97 fn record_inner<T>(
98 &mut self,
99 label1: &'static str,
100 label2: Option<&'static str>,
101 id: Option<HirId>,
102 val: &T,
103 ) {
104 if id.is_some_and(|x| !self.seen.insert(x)) {
105 return;
106 }
107
108 let node = self.nodes.entry(label1).or_insert(Node::new());
109 node.stats.count += 1;
110 node.stats.size = size_of_val(val);
111
112 if let Some(label2) = label2 {
113 let subnode = node.subnodes.entry(label2).or_insert(NodeStats::new());
114 subnode.count += 1;
115 subnode.size = size_of_val(val);
116 }
117 }
118
119 fn print(&self, title: &str, prefix: &str) {
120 #[allow(rustc::potential_query_instability)]
122 let mut nodes: Vec<_> = self.nodes.iter().collect();
123 nodes.sort_by_cached_key(|(label, node)| (node.stats.accum_size(), label.to_owned()));
124
125 let total_size = nodes.iter().map(|(_, node)| node.stats.accum_size()).sum();
126 let total_count = nodes.iter().map(|(_, node)| node.stats.count).sum();
127
128 eprintln!("{prefix} {title}");
129 eprintln!(
130 "{} {:<18}{:>18}{:>14}{:>14}",
131 prefix, "Name", "Accumulated Size", "Count", "Item Size"
132 );
133 eprintln!("{prefix} ----------------------------------------------------------------");
134
135 let percent = |m, n| (m * 100) as f64 / n as f64;
136
137 for (label, node) in nodes {
138 let size = node.stats.accum_size();
139 eprintln!(
140 "{} {:<18}{:>10} ({:4.1}%){:>14}{:>14}",
141 prefix,
142 label,
143 format_with_underscores(size),
144 percent(size, total_size),
145 format_with_underscores(node.stats.count),
146 format_with_underscores(node.stats.size)
147 );
148 if !node.subnodes.is_empty() {
149 #[allow(rustc::potential_query_instability)]
151 let mut subnodes: Vec<_> = node.subnodes.iter().collect();
152 subnodes.sort_by_cached_key(|(label, subnode)| {
153 (subnode.accum_size(), label.to_owned())
154 });
155
156 for (label, subnode) in subnodes {
157 let size = subnode.accum_size();
158 eprintln!(
159 "{} - {:<18}{:>10} ({:4.1}%){:>14}",
160 prefix,
161 label,
162 format_with_underscores(size),
163 percent(size, total_size),
164 format_with_underscores(subnode.count),
165 );
166 }
167 }
168 }
169 eprintln!("{prefix} ----------------------------------------------------------------");
170 eprintln!(
171 "{} {:<18}{:>10} {:>14}",
172 prefix,
173 "Total",
174 format_with_underscores(total_size),
175 format_with_underscores(total_count),
176 );
177 eprintln!("{prefix}");
178 }
179}
180
181macro_rules! record_variants {
183 (
184 ($self:ident, $val:expr, $kind:expr, $id:expr, $mod:ident, $ty:ty, $tykind:ident),
185 [$($variant:ident),*]
186 ) => {
187 match $kind {
188 $(
189 $mod::$tykind::$variant { .. } => {
190 $self.record_variant(stringify!($ty), stringify!($variant), $id, $val)
191 }
192 )*
193 }
194 };
195}
196
197impl<'v> hir_visit::Visitor<'v> for StatCollector<'v> {
198 fn visit_param(&mut self, param: &'v hir::Param<'v>) {
199 self.record("Param", Some(param.hir_id), param);
200 hir_visit::walk_param(self, param)
201 }
202
203 fn visit_nested_item(&mut self, id: hir::ItemId) {
204 let nested_item = self.tcx.unwrap().hir_item(id);
205 self.visit_item(nested_item)
206 }
207
208 fn visit_nested_trait_item(&mut self, trait_item_id: hir::TraitItemId) {
209 let nested_trait_item = self.tcx.unwrap().hir_trait_item(trait_item_id);
210 self.visit_trait_item(nested_trait_item)
211 }
212
213 fn visit_nested_impl_item(&mut self, impl_item_id: hir::ImplItemId) {
214 let nested_impl_item = self.tcx.unwrap().hir_impl_item(impl_item_id);
215 self.visit_impl_item(nested_impl_item)
216 }
217
218 fn visit_nested_foreign_item(&mut self, id: hir::ForeignItemId) {
219 let nested_foreign_item = self.tcx.unwrap().hir_foreign_item(id);
220 self.visit_foreign_item(nested_foreign_item);
221 }
222
223 fn visit_nested_body(&mut self, body_id: hir::BodyId) {
224 let nested_body = self.tcx.unwrap().hir_body(body_id);
225 self.visit_body(nested_body)
226 }
227
228 fn visit_item(&mut self, i: &'v hir::Item<'v>) {
229 record_variants!(
230 (self, i, i.kind, Some(i.hir_id()), hir, Item, ItemKind),
231 [
232 ExternCrate,
233 Use,
234 Static,
235 Const,
236 Fn,
237 Macro,
238 Mod,
239 ForeignMod,
240 GlobalAsm,
241 TyAlias,
242 Enum,
243 Struct,
244 Union,
245 Trait,
246 TraitAlias,
247 Impl
248 ]
249 );
250 hir_visit::walk_item(self, i)
251 }
252
253 fn visit_body(&mut self, b: &hir::Body<'v>) {
254 self.record("Body", None, b);
255 hir_visit::walk_body(self, b);
256 }
257
258 fn visit_mod(&mut self, m: &'v hir::Mod<'v>, _s: Span, _n: HirId) {
259 self.record("Mod", None, m);
260 hir_visit::walk_mod(self, m)
261 }
262
263 fn visit_foreign_item(&mut self, i: &'v hir::ForeignItem<'v>) {
264 record_variants!(
265 (self, i, i.kind, Some(i.hir_id()), hir, ForeignItem, ForeignItemKind),
266 [Fn, Static, Type]
267 );
268 hir_visit::walk_foreign_item(self, i)
269 }
270
271 fn visit_local(&mut self, l: &'v hir::LetStmt<'v>) {
272 self.record("Local", Some(l.hir_id), l);
273 hir_visit::walk_local(self, l)
274 }
275
276 fn visit_block(&mut self, b: &'v hir::Block<'v>) {
277 self.record("Block", Some(b.hir_id), b);
278 hir_visit::walk_block(self, b)
279 }
280
281 fn visit_stmt(&mut self, s: &'v hir::Stmt<'v>) {
282 record_variants!(
283 (self, s, s.kind, Some(s.hir_id), hir, Stmt, StmtKind),
284 [Let, Item, Expr, Semi]
285 );
286 hir_visit::walk_stmt(self, s)
287 }
288
289 fn visit_arm(&mut self, a: &'v hir::Arm<'v>) {
290 self.record("Arm", Some(a.hir_id), a);
291 hir_visit::walk_arm(self, a)
292 }
293
294 fn visit_pat(&mut self, p: &'v hir::Pat<'v>) {
295 record_variants!(
296 (self, p, p.kind, Some(p.hir_id), hir, Pat, PatKind),
297 [
298 Missing,
299 Wild,
300 Binding,
301 Struct,
302 TupleStruct,
303 Or,
304 Never,
305 Tuple,
306 Box,
307 Deref,
308 Ref,
309 Expr,
310 Guard,
311 Range,
312 Slice,
313 Err
314 ]
315 );
316 hir_visit::walk_pat(self, p)
317 }
318
319 fn visit_pat_field(&mut self, f: &'v hir::PatField<'v>) {
320 self.record("PatField", Some(f.hir_id), f);
321 hir_visit::walk_pat_field(self, f)
322 }
323
324 fn visit_expr(&mut self, e: &'v hir::Expr<'v>) {
325 record_variants!(
326 (self, e, e.kind, Some(e.hir_id), hir, Expr, ExprKind),
327 [
328 ConstBlock,
329 Array,
330 Call,
331 MethodCall,
332 Use,
333 Tup,
334 Binary,
335 Unary,
336 Lit,
337 Cast,
338 Type,
339 DropTemps,
340 Let,
341 If,
342 Loop,
343 Match,
344 Closure,
345 Block,
346 Assign,
347 AssignOp,
348 Field,
349 Index,
350 Path,
351 AddrOf,
352 Break,
353 Continue,
354 Ret,
355 Become,
356 InlineAsm,
357 OffsetOf,
358 Struct,
359 Repeat,
360 Yield,
361 UnsafeBinderCast,
362 Err
363 ]
364 );
365 hir_visit::walk_expr(self, e)
366 }
367
368 fn visit_expr_field(&mut self, f: &'v hir::ExprField<'v>) {
369 self.record("ExprField", Some(f.hir_id), f);
370 hir_visit::walk_expr_field(self, f)
371 }
372
373 fn visit_ty(&mut self, t: &'v hir::Ty<'v, AmbigArg>) {
374 record_variants!(
375 (self, t, t.kind, Some(t.hir_id), hir, Ty, TyKind),
376 [
377 InferDelegation,
378 Slice,
379 Array,
380 Ptr,
381 Ref,
382 BareFn,
383 UnsafeBinder,
384 Never,
385 Tup,
386 Path,
387 OpaqueDef,
388 TraitAscription,
389 TraitObject,
390 Typeof,
391 Infer,
392 Pat,
393 Err
394 ]
395 );
396 hir_visit::walk_ty(self, t)
397 }
398
399 fn visit_generic_param(&mut self, p: &'v hir::GenericParam<'v>) {
400 self.record("GenericParam", Some(p.hir_id), p);
401 hir_visit::walk_generic_param(self, p)
402 }
403
404 fn visit_generics(&mut self, g: &'v hir::Generics<'v>) {
405 self.record("Generics", None, g);
406 hir_visit::walk_generics(self, g)
407 }
408
409 fn visit_where_predicate(&mut self, p: &'v hir::WherePredicate<'v>) {
410 record_variants!(
411 (self, p, p.kind, Some(p.hir_id), hir, WherePredicate, WherePredicateKind),
412 [BoundPredicate, RegionPredicate, EqPredicate]
413 );
414 hir_visit::walk_where_predicate(self, p)
415 }
416
417 fn visit_fn(
418 &mut self,
419 fk: hir_visit::FnKind<'v>,
420 fd: &'v hir::FnDecl<'v>,
421 b: hir::BodyId,
422 _: Span,
423 id: LocalDefId,
424 ) {
425 self.record("FnDecl", None, fd);
426 hir_visit::walk_fn(self, fk, fd, b, id)
427 }
428
429 fn visit_use(&mut self, p: &'v hir::UsePath<'v>, _hir_id: HirId) {
430 self.record("Path", None, p);
432 let hir::Path { span: _, res: _, segments } = *p;
438 ast_visit::walk_list!(self, visit_path_segment, segments);
439 }
440
441 fn visit_trait_item(&mut self, ti: &'v hir::TraitItem<'v>) {
442 record_variants!(
443 (self, ti, ti.kind, Some(ti.hir_id()), hir, TraitItem, TraitItemKind),
444 [Const, Fn, Type]
445 );
446 hir_visit::walk_trait_item(self, ti)
447 }
448
449 fn visit_trait_item_ref(&mut self, ti: &'v hir::TraitItemRef) {
450 self.record("TraitItemRef", Some(ti.id.hir_id()), ti);
451 hir_visit::walk_trait_item_ref(self, ti)
452 }
453
454 fn visit_impl_item(&mut self, ii: &'v hir::ImplItem<'v>) {
455 record_variants!(
456 (self, ii, ii.kind, Some(ii.hir_id()), hir, ImplItem, ImplItemKind),
457 [Const, Fn, Type]
458 );
459 hir_visit::walk_impl_item(self, ii)
460 }
461
462 fn visit_foreign_item_ref(&mut self, fi: &'v hir::ForeignItemRef) {
463 self.record("ForeignItemRef", Some(fi.id.hir_id()), fi);
464 hir_visit::walk_foreign_item_ref(self, fi)
465 }
466
467 fn visit_impl_item_ref(&mut self, ii: &'v hir::ImplItemRef) {
468 self.record("ImplItemRef", Some(ii.id.hir_id()), ii);
469 hir_visit::walk_impl_item_ref(self, ii)
470 }
471
472 fn visit_param_bound(&mut self, b: &'v hir::GenericBound<'v>) {
473 record_variants!(
474 (self, b, b, None, hir, GenericBound, GenericBound),
475 [Trait, Outlives, Use]
476 );
477 hir_visit::walk_param_bound(self, b)
478 }
479
480 fn visit_field_def(&mut self, s: &'v hir::FieldDef<'v>) {
481 self.record("FieldDef", Some(s.hir_id), s);
482 hir_visit::walk_field_def(self, s)
483 }
484
485 fn visit_variant(&mut self, v: &'v hir::Variant<'v>) {
486 self.record("Variant", None, v);
487 hir_visit::walk_variant(self, v)
488 }
489
490 fn visit_generic_arg(&mut self, ga: &'v hir::GenericArg<'v>) {
491 record_variants!(
492 (self, ga, ga, Some(ga.hir_id()), hir, GenericArg, GenericArg),
493 [Lifetime, Type, Const, Infer]
494 );
495 match ga {
496 hir::GenericArg::Lifetime(lt) => self.visit_lifetime(lt),
497 hir::GenericArg::Type(ty) => self.visit_ty(ty),
498 hir::GenericArg::Const(ct) => self.visit_const_arg(ct),
499 hir::GenericArg::Infer(inf) => self.visit_id(inf.hir_id),
500 }
501 }
502
503 fn visit_lifetime(&mut self, lifetime: &'v hir::Lifetime) {
504 self.record("Lifetime", Some(lifetime.hir_id), lifetime);
505 hir_visit::walk_lifetime(self, lifetime)
506 }
507
508 fn visit_path(&mut self, path: &hir::Path<'v>, _id: HirId) {
509 self.record("Path", None, path);
510 hir_visit::walk_path(self, path)
511 }
512
513 fn visit_path_segment(&mut self, path_segment: &'v hir::PathSegment<'v>) {
514 self.record("PathSegment", None, path_segment);
515 hir_visit::walk_path_segment(self, path_segment)
516 }
517
518 fn visit_generic_args(&mut self, ga: &'v hir::GenericArgs<'v>) {
519 self.record("GenericArgs", None, ga);
520 hir_visit::walk_generic_args(self, ga)
521 }
522
523 fn visit_assoc_item_constraint(&mut self, constraint: &'v hir::AssocItemConstraint<'v>) {
524 self.record("AssocItemConstraint", Some(constraint.hir_id), constraint);
525 hir_visit::walk_assoc_item_constraint(self, constraint)
526 }
527
528 fn visit_attribute(&mut self, attr: &'v hir::Attribute) {
529 self.record("Attribute", None, attr);
530 }
531
532 fn visit_inline_asm(&mut self, asm: &'v hir::InlineAsm<'v>, id: HirId) {
533 self.record("InlineAsm", None, asm);
534 hir_visit::walk_inline_asm(self, asm, id);
535 }
536}
537
538impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> {
539 fn visit_foreign_item(&mut self, i: &'v ast::ForeignItem) {
540 record_variants!(
541 (self, i, i.kind, None, ast, ForeignItem, ForeignItemKind),
542 [Static, Fn, TyAlias, MacCall]
543 );
544 ast_visit::walk_item(self, i)
545 }
546
547 fn visit_item(&mut self, i: &'v ast::Item) {
548 record_variants!(
549 (self, i, i.kind, None, ast, Item, ItemKind),
550 [
551 ExternCrate,
552 Use,
553 Static,
554 Const,
555 Fn,
556 Mod,
557 ForeignMod,
558 GlobalAsm,
559 TyAlias,
560 Enum,
561 Struct,
562 Union,
563 Trait,
564 TraitAlias,
565 Impl,
566 MacCall,
567 MacroDef,
568 Delegation,
569 DelegationMac
570 ]
571 );
572 ast_visit::walk_item(self, i)
573 }
574
575 fn visit_local(&mut self, l: &'v ast::Local) {
576 self.record("Local", None, l);
577 ast_visit::walk_local(self, l)
578 }
579
580 fn visit_block(&mut self, b: &'v ast::Block) {
581 self.record("Block", None, b);
582 ast_visit::walk_block(self, b)
583 }
584
585 fn visit_stmt(&mut self, s: &'v ast::Stmt) {
586 record_variants!(
587 (self, s, s.kind, None, ast, Stmt, StmtKind),
588 [Let, Item, Expr, Semi, Empty, MacCall]
589 );
590 ast_visit::walk_stmt(self, s)
591 }
592
593 fn visit_param(&mut self, p: &'v ast::Param) {
594 self.record("Param", None, p);
595 ast_visit::walk_param(self, p)
596 }
597
598 fn visit_arm(&mut self, a: &'v ast::Arm) {
599 self.record("Arm", None, a);
600 ast_visit::walk_arm(self, a)
601 }
602
603 fn visit_pat(&mut self, p: &'v ast::Pat) {
604 record_variants!(
605 (self, p, p.kind, None, ast, Pat, PatKind),
606 [
607 Missing,
608 Wild,
609 Ident,
610 Struct,
611 TupleStruct,
612 Or,
613 Path,
614 Tuple,
615 Box,
616 Deref,
617 Ref,
618 Expr,
619 Range,
620 Slice,
621 Rest,
622 Never,
623 Guard,
624 Paren,
625 MacCall,
626 Err
627 ]
628 );
629 ast_visit::walk_pat(self, p)
630 }
631
632 fn visit_expr(&mut self, e: &'v ast::Expr) {
633 #[rustfmt::skip]
634 record_variants!(
635 (self, e, e.kind, None, ast, Expr, ExprKind),
636 [
637 Array, ConstBlock, Call, MethodCall, Tup, Binary, Unary, Lit, Cast, Type, Let,
638 If, While, ForLoop, Loop, Match, Closure, Block, Await, Use, TryBlock, Assign,
639 AssignOp, Field, Index, Range, Underscore, Path, AddrOf, Break, Continue, Ret,
640 InlineAsm, FormatArgs, OffsetOf, MacCall, Struct, Repeat, Paren, Try, Yield, Yeet,
641 Become, IncludedBytes, Gen, UnsafeBinderCast, Err, Dummy
642 ]
643 );
644 ast_visit::walk_expr(self, e)
645 }
646
647 fn visit_ty(&mut self, t: &'v ast::Ty) {
648 record_variants!(
649 (self, t, t.kind, None, ast, Ty, TyKind),
650 [
651 Slice,
652 Array,
653 Ptr,
654 Ref,
655 PinnedRef,
656 BareFn,
657 UnsafeBinder,
658 Never,
659 Tup,
660 Path,
661 Pat,
662 TraitObject,
663 ImplTrait,
664 Paren,
665 Typeof,
666 Infer,
667 ImplicitSelf,
668 MacCall,
669 CVarArgs,
670 Dummy,
671 Err
672 ]
673 );
674
675 ast_visit::walk_ty(self, t)
676 }
677
678 fn visit_generic_param(&mut self, g: &'v ast::GenericParam) {
679 self.record("GenericParam", None, g);
680 ast_visit::walk_generic_param(self, g)
681 }
682
683 fn visit_where_predicate(&mut self, p: &'v ast::WherePredicate) {
684 record_variants!(
685 (self, p, &p.kind, None, ast, WherePredicate, WherePredicateKind),
686 [BoundPredicate, RegionPredicate, EqPredicate]
687 );
688 ast_visit::walk_where_predicate(self, p)
689 }
690
691 fn visit_fn(&mut self, fk: ast_visit::FnKind<'v>, _: Span, _: NodeId) {
692 self.record("FnDecl", None, fk.decl());
693 ast_visit::walk_fn(self, fk)
694 }
695
696 fn visit_assoc_item(&mut self, i: &'v ast::AssocItem, ctxt: ast_visit::AssocCtxt) {
697 record_variants!(
698 (self, i, i.kind, None, ast, AssocItem, AssocItemKind),
699 [Const, Fn, Type, MacCall, Delegation, DelegationMac]
700 );
701 ast_visit::walk_assoc_item(self, i, ctxt);
702 }
703
704 fn visit_param_bound(&mut self, b: &'v ast::GenericBound, _ctxt: BoundKind) {
705 record_variants!(
706 (self, b, b, None, ast, GenericBound, GenericBound),
707 [Trait, Outlives, Use]
708 );
709 ast_visit::walk_param_bound(self, b)
710 }
711
712 fn visit_field_def(&mut self, s: &'v ast::FieldDef) {
713 self.record("FieldDef", None, s);
714 ast_visit::walk_field_def(self, s)
715 }
716
717 fn visit_variant(&mut self, v: &'v ast::Variant) {
718 self.record("Variant", None, v);
719 ast_visit::walk_variant(self, v)
720 }
721
722 fn visit_path_segment(&mut self, path_segment: &'v ast::PathSegment) {
732 self.record("PathSegment", None, path_segment);
733 ast_visit::walk_path_segment(self, path_segment)
734 }
735
736 fn visit_generic_args(&mut self, g: &'v ast::GenericArgs) {
741 record_variants!(
742 (self, g, g, None, ast, GenericArgs, GenericArgs),
743 [AngleBracketed, Parenthesized, ParenthesizedElided]
744 );
745 ast_visit::walk_generic_args(self, g)
746 }
747
748 fn visit_attribute(&mut self, attr: &'v ast::Attribute) {
749 record_variants!(
750 (self, attr, attr.kind, None, ast, Attribute, AttrKind),
751 [Normal, DocComment]
752 );
753 ast_visit::walk_attribute(self, attr)
754 }
755
756 fn visit_expr_field(&mut self, f: &'v ast::ExprField) {
757 self.record("ExprField", None, f);
758 ast_visit::walk_expr_field(self, f)
759 }
760
761 fn visit_crate(&mut self, krate: &'v ast::Crate) {
762 self.record("Crate", None, krate);
763 ast_visit::walk_crate(self, krate)
764 }
765
766 fn visit_inline_asm(&mut self, asm: &'v ast::InlineAsm) {
767 self.record("InlineAsm", None, asm);
768 ast_visit::walk_inline_asm(self, asm)
769 }
770}