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 hir_visit::walk_use(self, p, hir_id)
433 }
434
435 fn visit_trait_item(&mut self, ti: &'v hir::TraitItem<'v>) {
436 record_variants!(
437 (self, ti, ti.kind, Some(ti.hir_id()), hir, TraitItem, TraitItemKind),
438 [Const, Fn, Type]
439 );
440 hir_visit::walk_trait_item(self, ti)
441 }
442
443 fn visit_trait_item_ref(&mut self, ti: &'v hir::TraitItemRef) {
444 self.record("TraitItemRef", Some(ti.id.hir_id()), ti);
445 hir_visit::walk_trait_item_ref(self, ti)
446 }
447
448 fn visit_impl_item(&mut self, ii: &'v hir::ImplItem<'v>) {
449 record_variants!(
450 (self, ii, ii.kind, Some(ii.hir_id()), hir, ImplItem, ImplItemKind),
451 [Const, Fn, Type]
452 );
453 hir_visit::walk_impl_item(self, ii)
454 }
455
456 fn visit_foreign_item_ref(&mut self, fi: &'v hir::ForeignItemRef) {
457 self.record("ForeignItemRef", Some(fi.id.hir_id()), fi);
458 hir_visit::walk_foreign_item_ref(self, fi)
459 }
460
461 fn visit_impl_item_ref(&mut self, ii: &'v hir::ImplItemRef) {
462 self.record("ImplItemRef", Some(ii.id.hir_id()), ii);
463 hir_visit::walk_impl_item_ref(self, ii)
464 }
465
466 fn visit_param_bound(&mut self, b: &'v hir::GenericBound<'v>) {
467 record_variants!(
468 (self, b, b, None, hir, GenericBound, GenericBound),
469 [Trait, Outlives, Use]
470 );
471 hir_visit::walk_param_bound(self, b)
472 }
473
474 fn visit_field_def(&mut self, s: &'v hir::FieldDef<'v>) {
475 self.record("FieldDef", Some(s.hir_id), s);
476 hir_visit::walk_field_def(self, s)
477 }
478
479 fn visit_variant(&mut self, v: &'v hir::Variant<'v>) {
480 self.record("Variant", None, v);
481 hir_visit::walk_variant(self, v)
482 }
483
484 fn visit_generic_arg(&mut self, ga: &'v hir::GenericArg<'v>) {
485 record_variants!(
486 (self, ga, ga, Some(ga.hir_id()), hir, GenericArg, GenericArg),
487 [Lifetime, Type, Const, Infer]
488 );
489 match ga {
490 hir::GenericArg::Lifetime(lt) => self.visit_lifetime(lt),
491 hir::GenericArg::Type(ty) => self.visit_ty(ty),
492 hir::GenericArg::Const(ct) => self.visit_const_arg(ct),
493 hir::GenericArg::Infer(inf) => self.visit_id(inf.hir_id),
494 }
495 }
496
497 fn visit_lifetime(&mut self, lifetime: &'v hir::Lifetime) {
498 self.record("Lifetime", Some(lifetime.hir_id), lifetime);
499 hir_visit::walk_lifetime(self, lifetime)
500 }
501
502 fn visit_path(&mut self, path: &hir::Path<'v>, _id: HirId) {
503 self.record("Path", None, path);
504 hir_visit::walk_path(self, path)
505 }
506
507 fn visit_path_segment(&mut self, path_segment: &'v hir::PathSegment<'v>) {
508 self.record("PathSegment", None, path_segment);
509 hir_visit::walk_path_segment(self, path_segment)
510 }
511
512 fn visit_generic_args(&mut self, ga: &'v hir::GenericArgs<'v>) {
513 self.record("GenericArgs", None, ga);
514 hir_visit::walk_generic_args(self, ga)
515 }
516
517 fn visit_assoc_item_constraint(&mut self, constraint: &'v hir::AssocItemConstraint<'v>) {
518 self.record("AssocItemConstraint", Some(constraint.hir_id), constraint);
519 hir_visit::walk_assoc_item_constraint(self, constraint)
520 }
521
522 fn visit_attribute(&mut self, attr: &'v hir::Attribute) {
523 self.record("Attribute", None, attr);
524 }
525
526 fn visit_inline_asm(&mut self, asm: &'v hir::InlineAsm<'v>, id: HirId) {
527 self.record("InlineAsm", None, asm);
528 hir_visit::walk_inline_asm(self, asm, id);
529 }
530}
531
532impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> {
533 fn visit_foreign_item(&mut self, i: &'v ast::ForeignItem) {
534 record_variants!(
535 (self, i, i.kind, None, ast, ForeignItem, ForeignItemKind),
536 [Static, Fn, TyAlias, MacCall]
537 );
538 ast_visit::walk_item(self, i)
539 }
540
541 fn visit_item(&mut self, i: &'v ast::Item) {
542 record_variants!(
543 (self, i, i.kind, None, ast, Item, ItemKind),
544 [
545 ExternCrate,
546 Use,
547 Static,
548 Const,
549 Fn,
550 Mod,
551 ForeignMod,
552 GlobalAsm,
553 TyAlias,
554 Enum,
555 Struct,
556 Union,
557 Trait,
558 TraitAlias,
559 Impl,
560 MacCall,
561 MacroDef,
562 Delegation,
563 DelegationMac
564 ]
565 );
566 ast_visit::walk_item(self, i)
567 }
568
569 fn visit_local(&mut self, l: &'v ast::Local) {
570 self.record("Local", None, l);
571 ast_visit::walk_local(self, l)
572 }
573
574 fn visit_block(&mut self, b: &'v ast::Block) {
575 self.record("Block", None, b);
576 ast_visit::walk_block(self, b)
577 }
578
579 fn visit_stmt(&mut self, s: &'v ast::Stmt) {
580 record_variants!(
581 (self, s, s.kind, None, ast, Stmt, StmtKind),
582 [Let, Item, Expr, Semi, Empty, MacCall]
583 );
584 ast_visit::walk_stmt(self, s)
585 }
586
587 fn visit_param(&mut self, p: &'v ast::Param) {
588 self.record("Param", None, p);
589 ast_visit::walk_param(self, p)
590 }
591
592 fn visit_arm(&mut self, a: &'v ast::Arm) {
593 self.record("Arm", None, a);
594 ast_visit::walk_arm(self, a)
595 }
596
597 fn visit_pat(&mut self, p: &'v ast::Pat) {
598 record_variants!(
599 (self, p, p.kind, None, ast, Pat, PatKind),
600 [
601 Missing,
602 Wild,
603 Ident,
604 Struct,
605 TupleStruct,
606 Or,
607 Path,
608 Tuple,
609 Box,
610 Deref,
611 Ref,
612 Expr,
613 Range,
614 Slice,
615 Rest,
616 Never,
617 Guard,
618 Paren,
619 MacCall,
620 Err
621 ]
622 );
623 ast_visit::walk_pat(self, p)
624 }
625
626 fn visit_expr(&mut self, e: &'v ast::Expr) {
627 #[rustfmt::skip]
628 record_variants!(
629 (self, e, e.kind, None, ast, Expr, ExprKind),
630 [
631 Array, ConstBlock, Call, MethodCall, Tup, Binary, Unary, Lit, Cast, Type, Let,
632 If, While, ForLoop, Loop, Match, Closure, Block, Await, Use, TryBlock, Assign,
633 AssignOp, Field, Index, Range, Underscore, Path, AddrOf, Break, Continue, Ret,
634 InlineAsm, FormatArgs, OffsetOf, MacCall, Struct, Repeat, Paren, Try, Yield, Yeet,
635 Become, IncludedBytes, Gen, UnsafeBinderCast, Err, Dummy
636 ]
637 );
638 ast_visit::walk_expr(self, e)
639 }
640
641 fn visit_ty(&mut self, t: &'v ast::Ty) {
642 record_variants!(
643 (self, t, t.kind, None, ast, Ty, TyKind),
644 [
645 Slice,
646 Array,
647 Ptr,
648 Ref,
649 PinnedRef,
650 BareFn,
651 UnsafeBinder,
652 Never,
653 Tup,
654 Path,
655 Pat,
656 TraitObject,
657 ImplTrait,
658 Paren,
659 Typeof,
660 Infer,
661 ImplicitSelf,
662 MacCall,
663 CVarArgs,
664 Dummy,
665 Err
666 ]
667 );
668
669 ast_visit::walk_ty(self, t)
670 }
671
672 fn visit_generic_param(&mut self, g: &'v ast::GenericParam) {
673 self.record("GenericParam", None, g);
674 ast_visit::walk_generic_param(self, g)
675 }
676
677 fn visit_where_predicate(&mut self, p: &'v ast::WherePredicate) {
678 record_variants!(
679 (self, p, &p.kind, None, ast, WherePredicate, WherePredicateKind),
680 [BoundPredicate, RegionPredicate, EqPredicate]
681 );
682 ast_visit::walk_where_predicate(self, p)
683 }
684
685 fn visit_fn(&mut self, fk: ast_visit::FnKind<'v>, _: Span, _: NodeId) {
686 self.record("FnDecl", None, fk.decl());
687 ast_visit::walk_fn(self, fk)
688 }
689
690 fn visit_assoc_item(&mut self, i: &'v ast::AssocItem, ctxt: ast_visit::AssocCtxt) {
691 record_variants!(
692 (self, i, i.kind, None, ast, AssocItem, AssocItemKind),
693 [Const, Fn, Type, MacCall, Delegation, DelegationMac]
694 );
695 ast_visit::walk_assoc_item(self, i, ctxt);
696 }
697
698 fn visit_param_bound(&mut self, b: &'v ast::GenericBound, _ctxt: BoundKind) {
699 record_variants!(
700 (self, b, b, None, ast, GenericBound, GenericBound),
701 [Trait, Outlives, Use]
702 );
703 ast_visit::walk_param_bound(self, b)
704 }
705
706 fn visit_field_def(&mut self, s: &'v ast::FieldDef) {
707 self.record("FieldDef", None, s);
708 ast_visit::walk_field_def(self, s)
709 }
710
711 fn visit_variant(&mut self, v: &'v ast::Variant) {
712 self.record("Variant", None, v);
713 ast_visit::walk_variant(self, v)
714 }
715
716 fn visit_path_segment(&mut self, path_segment: &'v ast::PathSegment) {
726 self.record("PathSegment", None, path_segment);
727 ast_visit::walk_path_segment(self, path_segment)
728 }
729
730 fn visit_generic_args(&mut self, g: &'v ast::GenericArgs) {
735 record_variants!(
736 (self, g, g, None, ast, GenericArgs, GenericArgs),
737 [AngleBracketed, Parenthesized, ParenthesizedElided]
738 );
739 ast_visit::walk_generic_args(self, g)
740 }
741
742 fn visit_attribute(&mut self, attr: &'v ast::Attribute) {
743 record_variants!(
744 (self, attr, attr.kind, None, ast, Attribute, AttrKind),
745 [Normal, DocComment]
746 );
747 ast_visit::walk_attribute(self, attr)
748 }
749
750 fn visit_expr_field(&mut self, f: &'v ast::ExprField) {
751 self.record("ExprField", None, f);
752 ast_visit::walk_expr_field(self, f)
753 }
754
755 fn visit_crate(&mut self, krate: &'v ast::Crate) {
756 self.record("Crate", None, krate);
757 ast_visit::walk_crate(self, krate)
758 }
759
760 fn visit_inline_asm(&mut self, asm: &'v ast::InlineAsm) {
761 self.record("InlineAsm", None, asm);
762 ast_visit::walk_inline_asm(self, asm)
763 }
764}