1 // The visitors in this module collect sizes and counts of the most important
2 // pieces of AST and HIR. The resulting numbers are good approximations but not
3 // completely accurate (some things might be counted twice, others missed).
5 use rustc_ast::visit as ast_visit;
6 use rustc_ast::visit::BoundKind;
7 use rustc_ast::{self as ast, AttrId, NodeId};
8 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
10 use rustc_hir::intravisit as hir_visit;
12 use rustc_middle::hir::map::Map;
13 use rustc_middle::ty::TyCtxt;
14 use rustc_middle::util::common::to_readable_str;
17 #[derive(Copy, Clone, PartialEq, Eq, Hash)]
30 fn new() -> NodeStats {
31 NodeStats { count: 0, size: 0 }
37 subnodes: FxHashMap<&'static str, NodeStats>,
42 Node { stats: NodeStats::new(), subnodes: FxHashMap::default() }
46 /// This type measures the size of AST and HIR nodes, by implementing the AST
47 /// and HIR `Visitor` traits. But we don't measure every visited type because
48 /// that could cause double counting.
50 /// For example, `ast::Visitor` has `visit_ident`, but `Ident`s are always
51 /// stored inline within other AST nodes, so we don't implement `visit_ident`
52 /// here. In contrast, we do implement `visit_expr` because `ast::Expr` is
53 /// always stored as `P<ast::Expr>`, and every such expression should be
54 /// measured separately.
56 /// In general, a `visit_foo` method should be implemented here if the
57 /// corresponding `Foo` type is always stored on its own, e.g.: `P<Foo>`,
58 /// `Box<Foo>`, `Vec<Foo>`, `Box<[Foo]>`.
60 /// There are some types in the AST and HIR tree that the visitors do not have
61 /// a `visit_*` method for, and so we cannot measure these, which is
63 struct StatCollector<'k> {
64 krate: Option<Map<'k>>,
65 nodes: FxHashMap<&'static str, Node>,
69 pub fn print_hir_stats(tcx: TyCtxt<'_>) {
70 let mut collector = StatCollector {
71 krate: Some(tcx.hir()),
72 nodes: FxHashMap::default(),
73 seen: FxHashSet::default(),
75 tcx.hir().walk_toplevel_module(&mut collector);
76 tcx.hir().walk_attributes(&mut collector);
77 collector.print("HIR STATS", "hir-stats");
80 pub fn print_ast_stats(krate: &ast::Crate, title: &str, prefix: &str) {
81 use rustc_ast::visit::Visitor;
84 StatCollector { krate: None, nodes: FxHashMap::default(), seen: FxHashSet::default() };
85 collector.visit_crate(krate);
86 collector.print(title, prefix);
89 impl<'k> StatCollector<'k> {
90 // Record a top-level node.
91 fn record<T>(&mut self, label: &'static str, id: Id, val: &T) {
92 self.record_inner(label, None, id, val);
95 // Record a two-level entry, with a top-level enum type and a variant.
96 fn record_variant<T>(&mut self, label1: &'static str, label2: &'static str, id: Id, val: &T) {
97 self.record_inner(label1, Some(label2), id, val);
102 label1: &'static str,
103 label2: Option<&'static str>,
107 if id != Id::None && !self.seen.insert(id) {
111 let node = self.nodes.entry(label1).or_insert(Node::new());
112 node.stats.count += 1;
113 node.stats.size = std::mem::size_of_val(val);
115 if let Some(label2) = label2 {
116 let subnode = node.subnodes.entry(label2).or_insert(NodeStats::new());
118 subnode.size = std::mem::size_of_val(val);
122 fn print(&self, title: &str, prefix: &str) {
123 let mut nodes: Vec<_> = self.nodes.iter().collect();
124 nodes.sort_by_key(|&(_, ref node)| node.stats.count * node.stats.size);
126 let total_size = nodes.iter().map(|(_, node)| node.stats.count * node.stats.size).sum();
128 eprintln!("{} {}", prefix, title);
130 "{} {:<18}{:>18}{:>14}{:>14}",
131 prefix, "Name", "Accumulated Size", "Count", "Item Size"
133 eprintln!("{} ----------------------------------------------------------------", prefix);
135 let percent = |m, n| (m * 100) as f64 / n as f64;
137 for (label, node) in nodes {
138 let size = node.stats.count * node.stats.size;
140 "{} {:<18}{:>10} ({:4.1}%){:>14}{:>14}",
143 to_readable_str(size),
144 percent(size, total_size),
145 to_readable_str(node.stats.count),
146 to_readable_str(node.stats.size)
148 if !node.subnodes.is_empty() {
149 let mut subnodes: Vec<_> = node.subnodes.iter().collect();
150 subnodes.sort_by_key(|&(_, ref subnode)| subnode.count * subnode.size);
152 for (label, subnode) in subnodes {
153 let size = subnode.count * subnode.size;
155 "{} - {:<18}{:>10} ({:4.1}%){:>14}",
158 to_readable_str(size),
159 percent(size, total_size),
160 to_readable_str(subnode.count),
165 eprintln!("{} ----------------------------------------------------------------", prefix);
166 eprintln!("{} {:<18}{:>10}", prefix, "Total", to_readable_str(total_size));
167 eprintln!("{}", prefix);
171 // Used to avoid boilerplate for types with many variants.
172 macro_rules! record_variants {
174 ($self:ident, $val:expr, $kind:expr, $id:expr, $mod:ident, $ty:ty, $tykind:ident),
175 [$($variant:ident),*]
179 $mod::$tykind::$variant { .. } => {
180 $self.record_variant(stringify!($ty), stringify!($variant), $id, $val)
187 impl<'v> hir_visit::Visitor<'v> for StatCollector<'v> {
188 fn visit_param(&mut self, param: &'v hir::Param<'v>) {
189 self.record("Param", Id::Node(param.hir_id), param);
190 hir_visit::walk_param(self, param)
193 fn visit_nested_item(&mut self, id: hir::ItemId) {
194 let nested_item = self.krate.unwrap().item(id);
195 self.visit_item(nested_item)
198 fn visit_nested_trait_item(&mut self, trait_item_id: hir::TraitItemId) {
199 let nested_trait_item = self.krate.unwrap().trait_item(trait_item_id);
200 self.visit_trait_item(nested_trait_item)
203 fn visit_nested_impl_item(&mut self, impl_item_id: hir::ImplItemId) {
204 let nested_impl_item = self.krate.unwrap().impl_item(impl_item_id);
205 self.visit_impl_item(nested_impl_item)
208 fn visit_nested_foreign_item(&mut self, id: hir::ForeignItemId) {
209 let nested_foreign_item = self.krate.unwrap().foreign_item(id);
210 self.visit_foreign_item(nested_foreign_item);
213 fn visit_nested_body(&mut self, body_id: hir::BodyId) {
214 let nested_body = self.krate.unwrap().body(body_id);
215 self.visit_body(nested_body)
218 fn visit_item(&mut self, i: &'v hir::Item<'v>) {
220 (self, i, i.kind, Id::Node(i.hir_id()), hir, Item, ItemKind),
241 hir_visit::walk_item(self, i)
244 fn visit_body(&mut self, b: &'v hir::Body<'v>) {
245 self.record("Body", Id::None, b);
246 hir_visit::walk_body(self, b);
249 fn visit_mod(&mut self, m: &'v hir::Mod<'v>, _s: Span, n: HirId) {
250 self.record("Mod", Id::None, m);
251 hir_visit::walk_mod(self, m, n)
254 fn visit_foreign_item(&mut self, i: &'v hir::ForeignItem<'v>) {
256 (self, i, i.kind, Id::Node(i.hir_id()), hir, ForeignItem, ForeignItemKind),
259 hir_visit::walk_foreign_item(self, i)
262 fn visit_local(&mut self, l: &'v hir::Local<'v>) {
263 self.record("Local", Id::Node(l.hir_id), l);
264 hir_visit::walk_local(self, l)
267 fn visit_block(&mut self, b: &'v hir::Block<'v>) {
268 self.record("Block", Id::Node(b.hir_id), b);
269 hir_visit::walk_block(self, b)
272 fn visit_stmt(&mut self, s: &'v hir::Stmt<'v>) {
274 (self, s, s.kind, Id::Node(s.hir_id), hir, Stmt, StmtKind),
275 [Local, Item, Expr, Semi]
277 hir_visit::walk_stmt(self, s)
280 fn visit_arm(&mut self, a: &'v hir::Arm<'v>) {
281 self.record("Arm", Id::Node(a.hir_id), a);
282 hir_visit::walk_arm(self, a)
285 fn visit_pat(&mut self, p: &'v hir::Pat<'v>) {
287 (self, p, p.kind, Id::Node(p.hir_id), hir, Pat, PatKind),
288 [Wild, Binding, Struct, TupleStruct, Or, Path, Tuple, Box, Ref, Lit, Range, Slice]
290 hir_visit::walk_pat(self, p)
293 fn visit_pat_field(&mut self, f: &'v hir::PatField<'v>) {
294 self.record("PatField", Id::Node(f.hir_id), f);
295 hir_visit::walk_pat_field(self, f)
298 fn visit_expr(&mut self, e: &'v hir::Expr<'v>) {
300 (self, e, e.kind, Id::Node(e.hir_id), hir, Expr, ExprKind),
302 Box, ConstBlock, Array, Call, MethodCall, Tup, Binary, Unary, Lit, Cast, Type,
303 DropTemps, Let, If, Loop, Match, Closure, Block, Assign, AssignOp, Field, Index,
304 Path, AddrOf, Break, Continue, Ret, InlineAsm, Struct, Repeat, Yield, Err
307 hir_visit::walk_expr(self, e)
310 fn visit_let_expr(&mut self, lex: &'v hir::Let<'v>) {
311 self.record("Let", Id::Node(lex.hir_id), lex);
312 hir_visit::walk_let_expr(self, lex)
315 fn visit_expr_field(&mut self, f: &'v hir::ExprField<'v>) {
316 self.record("ExprField", Id::Node(f.hir_id), f);
317 hir_visit::walk_expr_field(self, f)
320 fn visit_ty(&mut self, t: &'v hir::Ty<'v>) {
322 (self, t, t.kind, Id::Node(t.hir_id), hir, Ty, TyKind),
339 hir_visit::walk_ty(self, t)
342 fn visit_generic_param(&mut self, p: &'v hir::GenericParam<'v>) {
343 self.record("GenericParam", Id::Node(p.hir_id), p);
344 hir_visit::walk_generic_param(self, p)
347 fn visit_generics(&mut self, g: &'v hir::Generics<'v>) {
348 self.record("Generics", Id::None, g);
349 hir_visit::walk_generics(self, g)
352 fn visit_where_predicate(&mut self, p: &'v hir::WherePredicate<'v>) {
354 (self, p, p, Id::None, hir, WherePredicate, WherePredicate),
355 [BoundPredicate, RegionPredicate, EqPredicate]
357 hir_visit::walk_where_predicate(self, p)
362 fk: hir_visit::FnKind<'v>,
363 fd: &'v hir::FnDecl<'v>,
368 self.record("FnDecl", Id::None, fd);
369 hir_visit::walk_fn(self, fk, fd, b, id)
372 fn visit_use(&mut self, p: &'v hir::Path<'v>, hir_id: hir::HirId) {
373 // This is `visit_use`, but the type is `Path` so record it that way.
374 self.record("Path", Id::None, p);
375 hir_visit::walk_use(self, p, hir_id)
378 fn visit_trait_item(&mut self, ti: &'v hir::TraitItem<'v>) {
380 (self, ti, ti.kind, Id::Node(ti.hir_id()), hir, TraitItem, TraitItemKind),
383 hir_visit::walk_trait_item(self, ti)
386 fn visit_trait_item_ref(&mut self, ti: &'v hir::TraitItemRef) {
387 self.record("TraitItemRef", Id::Node(ti.id.hir_id()), ti);
388 hir_visit::walk_trait_item_ref(self, ti)
391 fn visit_impl_item(&mut self, ii: &'v hir::ImplItem<'v>) {
393 (self, ii, ii.kind, Id::Node(ii.hir_id()), hir, ImplItem, ImplItemKind),
396 hir_visit::walk_impl_item(self, ii)
399 fn visit_foreign_item_ref(&mut self, fi: &'v hir::ForeignItemRef) {
400 self.record("ForeignItemRef", Id::Node(fi.id.hir_id()), fi);
401 hir_visit::walk_foreign_item_ref(self, fi)
404 fn visit_impl_item_ref(&mut self, ii: &'v hir::ImplItemRef) {
405 self.record("ImplItemRef", Id::Node(ii.id.hir_id()), ii);
406 hir_visit::walk_impl_item_ref(self, ii)
409 fn visit_param_bound(&mut self, b: &'v hir::GenericBound<'v>) {
411 (self, b, b, Id::None, hir, GenericBound, GenericBound),
412 [Trait, LangItemTrait, Outlives]
414 hir_visit::walk_param_bound(self, b)
417 fn visit_field_def(&mut self, s: &'v hir::FieldDef<'v>) {
418 self.record("FieldDef", Id::Node(s.hir_id), s);
419 hir_visit::walk_field_def(self, s)
422 fn visit_variant(&mut self, v: &'v hir::Variant<'v>) {
423 self.record("Variant", Id::None, v);
424 hir_visit::walk_variant(self, v)
427 fn visit_generic_arg(&mut self, ga: &'v hir::GenericArg<'v>) {
429 (self, ga, ga, Id::Node(ga.hir_id()), hir, GenericArg, GenericArg),
430 [Lifetime, Type, Const, Infer]
433 hir::GenericArg::Lifetime(lt) => self.visit_lifetime(lt),
434 hir::GenericArg::Type(ty) => self.visit_ty(ty),
435 hir::GenericArg::Const(ct) => self.visit_anon_const(&ct.value),
436 hir::GenericArg::Infer(inf) => self.visit_infer(inf),
440 fn visit_lifetime(&mut self, lifetime: &'v hir::Lifetime) {
441 self.record("Lifetime", Id::Node(lifetime.hir_id), lifetime);
442 hir_visit::walk_lifetime(self, lifetime)
445 fn visit_path(&mut self, path: &'v hir::Path<'v>, _id: hir::HirId) {
446 self.record("Path", Id::None, path);
447 hir_visit::walk_path(self, path)
450 fn visit_path_segment(&mut self, path_segment: &'v hir::PathSegment<'v>) {
451 self.record("PathSegment", Id::None, path_segment);
452 hir_visit::walk_path_segment(self, path_segment)
455 fn visit_generic_args(&mut self, ga: &'v hir::GenericArgs<'v>) {
456 self.record("GenericArgs", Id::None, ga);
457 hir_visit::walk_generic_args(self, ga)
460 fn visit_assoc_type_binding(&mut self, type_binding: &'v hir::TypeBinding<'v>) {
461 self.record("TypeBinding", Id::Node(type_binding.hir_id), type_binding);
462 hir_visit::walk_assoc_type_binding(self, type_binding)
465 fn visit_attribute(&mut self, attr: &'v ast::Attribute) {
466 self.record("Attribute", Id::Attr(attr.id), attr);
469 fn visit_inline_asm(&mut self, asm: &'v hir::InlineAsm<'v>, id: HirId) {
470 self.record("InlineAsm", Id::None, asm);
471 hir_visit::walk_inline_asm(self, asm, id);
475 impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> {
476 fn visit_foreign_item(&mut self, i: &'v ast::ForeignItem) {
478 (self, i, i.kind, Id::None, ast, ForeignItem, ForeignItemKind),
479 [Static, Fn, TyAlias, MacCall]
481 ast_visit::walk_foreign_item(self, i)
484 fn visit_item(&mut self, i: &'v ast::Item) {
486 (self, i, i.kind, Id::None, ast, Item, ItemKind),
507 ast_visit::walk_item(self, i)
510 fn visit_local(&mut self, l: &'v ast::Local) {
511 self.record("Local", Id::None, l);
512 ast_visit::walk_local(self, l)
515 fn visit_block(&mut self, b: &'v ast::Block) {
516 self.record("Block", Id::None, b);
517 ast_visit::walk_block(self, b)
520 fn visit_stmt(&mut self, s: &'v ast::Stmt) {
522 (self, s, s.kind, Id::None, ast, Stmt, StmtKind),
523 [Local, Item, Expr, Semi, Empty, MacCall]
525 ast_visit::walk_stmt(self, s)
528 fn visit_param(&mut self, p: &'v ast::Param) {
529 self.record("Param", Id::None, p);
530 ast_visit::walk_param(self, p)
533 fn visit_arm(&mut self, a: &'v ast::Arm) {
534 self.record("Arm", Id::None, a);
535 ast_visit::walk_arm(self, a)
538 fn visit_pat(&mut self, p: &'v ast::Pat) {
540 (self, p, p.kind, Id::None, ast, Pat, PatKind),
559 ast_visit::walk_pat(self, p)
562 fn visit_expr(&mut self, e: &'v ast::Expr) {
564 (self, e, e.kind, Id::None, ast, Expr, ExprKind),
566 Box, Array, ConstBlock, Call, MethodCall, Tup, Binary, Unary, Lit, Cast, Type, Let,
567 If, While, ForLoop, Loop, Match, Closure, Block, Async, Await, TryBlock, Assign,
568 AssignOp, Field, Index, Range, Underscore, Path, AddrOf, Break, Continue, Ret,
569 InlineAsm, MacCall, Struct, Repeat, Paren, Try, Yield, Yeet, Err
572 ast_visit::walk_expr(self, e)
575 fn visit_ty(&mut self, t: &'v ast::Ty) {
577 (self, t, t.kind, Id::None, ast, Ty, TyKind),
599 ast_visit::walk_ty(self, t)
602 fn visit_generic_param(&mut self, g: &'v ast::GenericParam) {
603 self.record("GenericParam", Id::None, g);
604 ast_visit::walk_generic_param(self, g)
607 fn visit_where_predicate(&mut self, p: &'v ast::WherePredicate) {
609 (self, p, p, Id::None, ast, WherePredicate, WherePredicate),
610 [BoundPredicate, RegionPredicate, EqPredicate]
612 ast_visit::walk_where_predicate(self, p)
615 fn visit_fn(&mut self, fk: ast_visit::FnKind<'v>, _: Span, _: NodeId) {
616 self.record("FnDecl", Id::None, fk.decl());
617 ast_visit::walk_fn(self, fk)
620 fn visit_assoc_item(&mut self, i: &'v ast::AssocItem, ctxt: ast_visit::AssocCtxt) {
622 (self, i, i.kind, Id::None, ast, AssocItem, AssocItemKind),
623 [Const, Fn, Type, MacCall]
625 ast_visit::walk_assoc_item(self, i, ctxt);
628 fn visit_param_bound(&mut self, b: &'v ast::GenericBound, _ctxt: BoundKind) {
630 (self, b, b, Id::None, ast, GenericBound, GenericBound),
633 ast_visit::walk_param_bound(self, b)
636 fn visit_field_def(&mut self, s: &'v ast::FieldDef) {
637 self.record("FieldDef", Id::None, s);
638 ast_visit::walk_field_def(self, s)
641 fn visit_variant(&mut self, v: &'v ast::Variant) {
642 self.record("Variant", Id::None, v);
643 ast_visit::walk_variant(self, v)
646 // `UseTree` has one inline use (in `ast::ItemKind::Use`) and one
647 // non-inline use (in `ast::UseTreeKind::Nested). The former case is more
648 // common, so we don't implement `visit_use_tree` and tolerate the missed
649 // coverage in the latter case.
651 // `PathSegment` has one inline use (in `ast::ExprKind::MethodCall`) and
652 // one non-inline use (in `ast::Path::segments`). The latter case is more
653 // common than the former case, so we implement this visitor and tolerate
654 // the double counting in the former case.
655 fn visit_path_segment(&mut self, path_segment: &'v ast::PathSegment) {
656 self.record("PathSegment", Id::None, path_segment);
657 ast_visit::walk_path_segment(self, path_segment)
660 // `GenericArgs` has one inline use (in `ast::AssocConstraint::gen_args`) and one
661 // non-inline use (in `ast::PathSegment::args`). The latter case is more
662 // common, so we implement `visit_generic_args` and tolerate the double
663 // counting in the former case.
664 fn visit_generic_args(&mut self, g: &'v ast::GenericArgs) {
666 (self, g, g, Id::None, ast, GenericArgs, GenericArgs),
667 [AngleBracketed, Parenthesized]
669 ast_visit::walk_generic_args(self, g)
672 fn visit_attribute(&mut self, attr: &'v ast::Attribute) {
674 (self, attr, attr.kind, Id::None, ast, Attribute, AttrKind),
677 ast_visit::walk_attribute(self, attr)
680 fn visit_expr_field(&mut self, f: &'v ast::ExprField) {
681 self.record("ExprField", Id::None, f);
682 ast_visit::walk_expr_field(self, f)
685 fn visit_crate(&mut self, krate: &'v ast::Crate) {
686 self.record("Crate", Id::None, krate);
687 ast_visit::walk_crate(self, krate)
690 fn visit_inline_asm(&mut self, asm: &'v ast::InlineAsm) {
691 self.record("InlineAsm", Id::None, asm);
692 ast_visit::walk_inline_asm(self, asm)