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 Wild,
299 Binding,
300 Struct,
301 TupleStruct,
302 Or,
303 Never,
304 Tuple,
305 Box,
306 Deref,
307 Ref,
308 Expr,
309 Guard,
310 Range,
311 Slice,
312 Err
313 ]
314 );
315 hir_visit::walk_pat(self, p)
316 }
317
318 fn visit_pat_field(&mut self, f: &'v hir::PatField<'v>) {
319 self.record("PatField", Some(f.hir_id), f);
320 hir_visit::walk_pat_field(self, f)
321 }
322
323 fn visit_expr(&mut self, e: &'v hir::Expr<'v>) {
324 record_variants!(
325 (self, e, e.kind, Some(e.hir_id), hir, Expr, ExprKind),
326 [
327 ConstBlock,
328 Array,
329 Call,
330 MethodCall,
331 Use,
332 Tup,
333 Binary,
334 Unary,
335 Lit,
336 Cast,
337 Type,
338 DropTemps,
339 Let,
340 If,
341 Loop,
342 Match,
343 Closure,
344 Block,
345 Assign,
346 AssignOp,
347 Field,
348 Index,
349 Path,
350 AddrOf,
351 Break,
352 Continue,
353 Ret,
354 Become,
355 InlineAsm,
356 OffsetOf,
357 Struct,
358 Repeat,
359 Yield,
360 UnsafeBinderCast,
361 Err
362 ]
363 );
364 hir_visit::walk_expr(self, e)
365 }
366
367 fn visit_expr_field(&mut self, f: &'v hir::ExprField<'v>) {
368 self.record("ExprField", Some(f.hir_id), f);
369 hir_visit::walk_expr_field(self, f)
370 }
371
372 fn visit_ty(&mut self, t: &'v hir::Ty<'v, AmbigArg>) {
373 record_variants!(
374 (self, t, t.kind, Some(t.hir_id), hir, Ty, TyKind),
375 [
376 InferDelegation,
377 Slice,
378 Array,
379 Ptr,
380 Ref,
381 BareFn,
382 UnsafeBinder,
383 Never,
384 Tup,
385 Path,
386 OpaqueDef,
387 TraitAscription,
388 TraitObject,
389 Typeof,
390 Infer,
391 Pat,
392 Err
393 ]
394 );
395 hir_visit::walk_ty(self, t)
396 }
397
398 fn visit_generic_param(&mut self, p: &'v hir::GenericParam<'v>) {
399 self.record("GenericParam", Some(p.hir_id), p);
400 hir_visit::walk_generic_param(self, p)
401 }
402
403 fn visit_generics(&mut self, g: &'v hir::Generics<'v>) {
404 self.record("Generics", None, g);
405 hir_visit::walk_generics(self, g)
406 }
407
408 fn visit_where_predicate(&mut self, p: &'v hir::WherePredicate<'v>) {
409 record_variants!(
410 (self, p, p.kind, Some(p.hir_id), hir, WherePredicate, WherePredicateKind),
411 [BoundPredicate, RegionPredicate, EqPredicate]
412 );
413 hir_visit::walk_where_predicate(self, p)
414 }
415
416 fn visit_fn(
417 &mut self,
418 fk: hir_visit::FnKind<'v>,
419 fd: &'v hir::FnDecl<'v>,
420 b: hir::BodyId,
421 _: Span,
422 id: LocalDefId,
423 ) {
424 self.record("FnDecl", None, fd);
425 hir_visit::walk_fn(self, fk, fd, b, id)
426 }
427
428 fn visit_use(&mut self, p: &'v hir::UsePath<'v>, hir_id: HirId) {
429 self.record("Path", None, p);
431 hir_visit::walk_use(self, p, hir_id)
432 }
433
434 fn visit_trait_item(&mut self, ti: &'v hir::TraitItem<'v>) {
435 record_variants!(
436 (self, ti, ti.kind, Some(ti.hir_id()), hir, TraitItem, TraitItemKind),
437 [Const, Fn, Type]
438 );
439 hir_visit::walk_trait_item(self, ti)
440 }
441
442 fn visit_trait_item_ref(&mut self, ti: &'v hir::TraitItemRef) {
443 self.record("TraitItemRef", Some(ti.id.hir_id()), ti);
444 hir_visit::walk_trait_item_ref(self, ti)
445 }
446
447 fn visit_impl_item(&mut self, ii: &'v hir::ImplItem<'v>) {
448 record_variants!(
449 (self, ii, ii.kind, Some(ii.hir_id()), hir, ImplItem, ImplItemKind),
450 [Const, Fn, Type]
451 );
452 hir_visit::walk_impl_item(self, ii)
453 }
454
455 fn visit_foreign_item_ref(&mut self, fi: &'v hir::ForeignItemRef) {
456 self.record("ForeignItemRef", Some(fi.id.hir_id()), fi);
457 hir_visit::walk_foreign_item_ref(self, fi)
458 }
459
460 fn visit_impl_item_ref(&mut self, ii: &'v hir::ImplItemRef) {
461 self.record("ImplItemRef", Some(ii.id.hir_id()), ii);
462 hir_visit::walk_impl_item_ref(self, ii)
463 }
464
465 fn visit_param_bound(&mut self, b: &'v hir::GenericBound<'v>) {
466 record_variants!(
467 (self, b, b, None, hir, GenericBound, GenericBound),
468 [Trait, Outlives, Use]
469 );
470 hir_visit::walk_param_bound(self, b)
471 }
472
473 fn visit_field_def(&mut self, s: &'v hir::FieldDef<'v>) {
474 self.record("FieldDef", Some(s.hir_id), s);
475 hir_visit::walk_field_def(self, s)
476 }
477
478 fn visit_variant(&mut self, v: &'v hir::Variant<'v>) {
479 self.record("Variant", None, v);
480 hir_visit::walk_variant(self, v)
481 }
482
483 fn visit_generic_arg(&mut self, ga: &'v hir::GenericArg<'v>) {
484 record_variants!(
485 (self, ga, ga, Some(ga.hir_id()), hir, GenericArg, GenericArg),
486 [Lifetime, Type, Const, Infer]
487 );
488 match ga {
489 hir::GenericArg::Lifetime(lt) => self.visit_lifetime(lt),
490 hir::GenericArg::Type(ty) => self.visit_ty(ty),
491 hir::GenericArg::Const(ct) => self.visit_const_arg(ct),
492 hir::GenericArg::Infer(inf) => self.visit_id(inf.hir_id),
493 }
494 }
495
496 fn visit_lifetime(&mut self, lifetime: &'v hir::Lifetime) {
497 self.record("Lifetime", Some(lifetime.hir_id), lifetime);
498 hir_visit::walk_lifetime(self, lifetime)
499 }
500
501 fn visit_path(&mut self, path: &hir::Path<'v>, _id: HirId) {
502 self.record("Path", None, path);
503 hir_visit::walk_path(self, path)
504 }
505
506 fn visit_path_segment(&mut self, path_segment: &'v hir::PathSegment<'v>) {
507 self.record("PathSegment", None, path_segment);
508 hir_visit::walk_path_segment(self, path_segment)
509 }
510
511 fn visit_generic_args(&mut self, ga: &'v hir::GenericArgs<'v>) {
512 self.record("GenericArgs", None, ga);
513 hir_visit::walk_generic_args(self, ga)
514 }
515
516 fn visit_assoc_item_constraint(&mut self, constraint: &'v hir::AssocItemConstraint<'v>) {
517 self.record("AssocItemConstraint", Some(constraint.hir_id), constraint);
518 hir_visit::walk_assoc_item_constraint(self, constraint)
519 }
520
521 fn visit_attribute(&mut self, attr: &'v hir::Attribute) {
522 self.record("Attribute", None, attr);
523 }
524
525 fn visit_inline_asm(&mut self, asm: &'v hir::InlineAsm<'v>, id: HirId) {
526 self.record("InlineAsm", None, asm);
527 hir_visit::walk_inline_asm(self, asm, id);
528 }
529}
530
531impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> {
532 fn visit_foreign_item(&mut self, i: &'v ast::ForeignItem) {
533 record_variants!(
534 (self, i, i.kind, None, ast, ForeignItem, ForeignItemKind),
535 [Static, Fn, TyAlias, MacCall]
536 );
537 ast_visit::walk_item(self, i)
538 }
539
540 fn visit_item(&mut self, i: &'v ast::Item) {
541 record_variants!(
542 (self, i, i.kind, None, ast, Item, ItemKind),
543 [
544 ExternCrate,
545 Use,
546 Static,
547 Const,
548 Fn,
549 Mod,
550 ForeignMod,
551 GlobalAsm,
552 TyAlias,
553 Enum,
554 Struct,
555 Union,
556 Trait,
557 TraitAlias,
558 Impl,
559 MacCall,
560 MacroDef,
561 Delegation,
562 DelegationMac
563 ]
564 );
565 ast_visit::walk_item(self, i)
566 }
567
568 fn visit_local(&mut self, l: &'v ast::Local) {
569 self.record("Local", None, l);
570 ast_visit::walk_local(self, l)
571 }
572
573 fn visit_block(&mut self, b: &'v ast::Block) {
574 self.record("Block", None, b);
575 ast_visit::walk_block(self, b)
576 }
577
578 fn visit_stmt(&mut self, s: &'v ast::Stmt) {
579 record_variants!(
580 (self, s, s.kind, None, ast, Stmt, StmtKind),
581 [Let, Item, Expr, Semi, Empty, MacCall]
582 );
583 ast_visit::walk_stmt(self, s)
584 }
585
586 fn visit_param(&mut self, p: &'v ast::Param) {
587 self.record("Param", None, p);
588 ast_visit::walk_param(self, p)
589 }
590
591 fn visit_arm(&mut self, a: &'v ast::Arm) {
592 self.record("Arm", None, a);
593 ast_visit::walk_arm(self, a)
594 }
595
596 fn visit_pat(&mut self, p: &'v ast::Pat) {
597 record_variants!(
598 (self, p, p.kind, None, ast, Pat, PatKind),
599 [
600 Wild,
601 Ident,
602 Struct,
603 TupleStruct,
604 Or,
605 Path,
606 Tuple,
607 Box,
608 Deref,
609 Ref,
610 Expr,
611 Range,
612 Slice,
613 Rest,
614 Never,
615 Guard,
616 Paren,
617 MacCall,
618 Err
619 ]
620 );
621 ast_visit::walk_pat(self, p)
622 }
623
624 fn visit_expr(&mut self, e: &'v ast::Expr) {
625 #[rustfmt::skip]
626 record_variants!(
627 (self, e, e.kind, None, ast, Expr, ExprKind),
628 [
629 Array, ConstBlock, Call, MethodCall, Tup, Binary, Unary, Lit, Cast, Type, Let,
630 If, While, ForLoop, Loop, Match, Closure, Block, Await, Use, TryBlock, Assign,
631 AssignOp, Field, Index, Range, Underscore, Path, AddrOf, Break, Continue, Ret,
632 InlineAsm, FormatArgs, OffsetOf, MacCall, Struct, Repeat, Paren, Try, Yield, Yeet,
633 Become, IncludedBytes, Gen, UnsafeBinderCast, Err, Dummy
634 ]
635 );
636 ast_visit::walk_expr(self, e)
637 }
638
639 fn visit_ty(&mut self, t: &'v ast::Ty) {
640 record_variants!(
641 (self, t, t.kind, None, ast, Ty, TyKind),
642 [
643 Slice,
644 Array,
645 Ptr,
646 Ref,
647 PinnedRef,
648 BareFn,
649 UnsafeBinder,
650 Never,
651 Tup,
652 Path,
653 Pat,
654 TraitObject,
655 ImplTrait,
656 Paren,
657 Typeof,
658 Infer,
659 ImplicitSelf,
660 MacCall,
661 CVarArgs,
662 Dummy,
663 Err
664 ]
665 );
666
667 ast_visit::walk_ty(self, t)
668 }
669
670 fn visit_generic_param(&mut self, g: &'v ast::GenericParam) {
671 self.record("GenericParam", None, g);
672 ast_visit::walk_generic_param(self, g)
673 }
674
675 fn visit_where_predicate(&mut self, p: &'v ast::WherePredicate) {
676 record_variants!(
677 (self, p, &p.kind, None, ast, WherePredicate, WherePredicateKind),
678 [BoundPredicate, RegionPredicate, EqPredicate]
679 );
680 ast_visit::walk_where_predicate(self, p)
681 }
682
683 fn visit_fn(&mut self, fk: ast_visit::FnKind<'v>, _: Span, _: NodeId) {
684 self.record("FnDecl", None, fk.decl());
685 ast_visit::walk_fn(self, fk)
686 }
687
688 fn visit_assoc_item(&mut self, i: &'v ast::AssocItem, ctxt: ast_visit::AssocCtxt) {
689 record_variants!(
690 (self, i, i.kind, None, ast, AssocItem, AssocItemKind),
691 [Const, Fn, Type, MacCall, Delegation, DelegationMac]
692 );
693 ast_visit::walk_assoc_item(self, i, ctxt);
694 }
695
696 fn visit_param_bound(&mut self, b: &'v ast::GenericBound, _ctxt: BoundKind) {
697 record_variants!(
698 (self, b, b, None, ast, GenericBound, GenericBound),
699 [Trait, Outlives, Use]
700 );
701 ast_visit::walk_param_bound(self, b)
702 }
703
704 fn visit_field_def(&mut self, s: &'v ast::FieldDef) {
705 self.record("FieldDef", None, s);
706 ast_visit::walk_field_def(self, s)
707 }
708
709 fn visit_variant(&mut self, v: &'v ast::Variant) {
710 self.record("Variant", None, v);
711 ast_visit::walk_variant(self, v)
712 }
713
714 fn visit_path_segment(&mut self, path_segment: &'v ast::PathSegment) {
724 self.record("PathSegment", None, path_segment);
725 ast_visit::walk_path_segment(self, path_segment)
726 }
727
728 fn visit_generic_args(&mut self, g: &'v ast::GenericArgs) {
733 record_variants!(
734 (self, g, g, None, ast, GenericArgs, GenericArgs),
735 [AngleBracketed, Parenthesized, ParenthesizedElided]
736 );
737 ast_visit::walk_generic_args(self, g)
738 }
739
740 fn visit_attribute(&mut self, attr: &'v ast::Attribute) {
741 record_variants!(
742 (self, attr, attr.kind, None, ast, Attribute, AttrKind),
743 [Normal, DocComment]
744 );
745 ast_visit::walk_attribute(self, attr)
746 }
747
748 fn visit_expr_field(&mut self, f: &'v ast::ExprField) {
749 self.record("ExprField", None, f);
750 ast_visit::walk_expr_field(self, f)
751 }
752
753 fn visit_crate(&mut self, krate: &'v ast::Crate) {
754 self.record("Crate", None, krate);
755 ast_visit::walk_crate(self, krate)
756 }
757
758 fn visit_inline_asm(&mut self, asm: &'v ast::InlineAsm) {
759 self.record("InlineAsm", None, asm);
760 ast_visit::walk_inline_asm(self, asm)
761 }
762}