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