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;
15 use rustc_span::def_id::LocalDefId;
18 #[derive(Copy, Clone, PartialEq, Eq, Hash)]
31 fn new() -> NodeStats {
32 NodeStats { count: 0, size: 0 }
38 subnodes: FxHashMap<&'static str, NodeStats>,
43 Node { stats: NodeStats::new(), subnodes: FxHashMap::default() }
47 /// This type measures the size of AST and HIR nodes, by implementing the AST
48 /// and HIR `Visitor` traits. But we don't measure every visited type because
49 /// that could cause double counting.
51 /// For example, `ast::Visitor` has `visit_ident`, but `Ident`s are always
52 /// stored inline within other AST nodes, so we don't implement `visit_ident`
53 /// here. In contrast, we do implement `visit_expr` because `ast::Expr` is
54 /// always stored as `P<ast::Expr>`, and every such expression should be
55 /// measured separately.
57 /// In general, a `visit_foo` method should be implemented here if the
58 /// corresponding `Foo` type is always stored on its own, e.g.: `P<Foo>`,
59 /// `Box<Foo>`, `Vec<Foo>`, `Box<[Foo]>`.
61 /// There are some types in the AST and HIR tree that the visitors do not have
62 /// a `visit_*` method for, and so we cannot measure these, which is
64 struct StatCollector<'k> {
65 krate: Option<Map<'k>>,
66 nodes: FxHashMap<&'static str, Node>,
70 pub fn print_hir_stats(tcx: TyCtxt<'_>) {
71 let mut collector = StatCollector {
72 krate: Some(tcx.hir()),
73 nodes: FxHashMap::default(),
74 seen: FxHashSet::default(),
76 tcx.hir().walk_toplevel_module(&mut collector);
77 tcx.hir().walk_attributes(&mut collector);
78 collector.print("HIR STATS", "hir-stats");
81 pub fn print_ast_stats(krate: &ast::Crate, title: &str, prefix: &str) {
82 use rustc_ast::visit::Visitor;
85 StatCollector { krate: None, nodes: FxHashMap::default(), seen: FxHashSet::default() };
86 collector.visit_crate(krate);
87 collector.print(title, prefix);
90 impl<'k> StatCollector<'k> {
91 // Record a top-level node.
92 fn record<T>(&mut self, label: &'static str, id: Id, val: &T) {
93 self.record_inner(label, None, id, val);
96 // Record a two-level entry, with a top-level enum type and a variant.
97 fn record_variant<T>(&mut self, label1: &'static str, label2: &'static str, id: Id, val: &T) {
98 self.record_inner(label1, Some(label2), id, val);
103 label1: &'static str,
104 label2: Option<&'static str>,
108 if id != Id::None && !self.seen.insert(id) {
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);
116 if let Some(label2) = label2 {
117 let subnode = node.subnodes.entry(label2).or_insert(NodeStats::new());
119 subnode.size = std::mem::size_of_val(val);
123 fn print(&self, title: &str, prefix: &str) {
124 let mut nodes: Vec<_> = self.nodes.iter().collect();
125 nodes.sort_by_key(|(_, node)| node.stats.count * node.stats.size);
127 let total_size = nodes.iter().map(|(_, node)| node.stats.count * node.stats.size).sum();
129 eprintln!("{} {}", prefix, title);
131 "{} {:<18}{:>18}{:>14}{:>14}",
132 prefix, "Name", "Accumulated Size", "Count", "Item Size"
134 eprintln!("{} ----------------------------------------------------------------", prefix);
136 let percent = |m, n| (m * 100) as f64 / n as f64;
138 for (label, node) in nodes {
139 let size = node.stats.count * node.stats.size;
141 "{} {:<18}{:>10} ({:4.1}%){:>14}{:>14}",
144 to_readable_str(size),
145 percent(size, total_size),
146 to_readable_str(node.stats.count),
147 to_readable_str(node.stats.size)
149 if !node.subnodes.is_empty() {
150 let mut subnodes: Vec<_> = node.subnodes.iter().collect();
151 subnodes.sort_by_key(|(_, subnode)| subnode.count * subnode.size);
153 for (label, subnode) in subnodes {
154 let size = subnode.count * subnode.size;
156 "{} - {:<18}{:>10} ({:4.1}%){:>14}",
159 to_readable_str(size),
160 percent(size, total_size),
161 to_readable_str(subnode.count),
166 eprintln!("{} ----------------------------------------------------------------", prefix);
167 eprintln!("{} {:<18}{:>10}", prefix, "Total", to_readable_str(total_size));
168 eprintln!("{}", prefix);
172 // Used to avoid boilerplate for types with many variants.
173 macro_rules! record_variants {
175 ($self:ident, $val:expr, $kind:expr, $id:expr, $mod:ident, $ty:ty, $tykind:ident),
176 [$($variant:ident),*]
180 $mod::$tykind::$variant { .. } => {
181 $self.record_variant(stringify!($ty), stringify!($variant), $id, $val)
188 impl<'v> hir_visit::Visitor<'v> for StatCollector<'v> {
189 fn visit_param(&mut self, param: &'v hir::Param<'v>) {
190 self.record("Param", Id::Node(param.hir_id), param);
191 hir_visit::walk_param(self, param)
194 fn visit_nested_item(&mut self, id: hir::ItemId) {
195 let nested_item = self.krate.unwrap().item(id);
196 self.visit_item(nested_item)
199 fn visit_nested_trait_item(&mut self, trait_item_id: hir::TraitItemId) {
200 let nested_trait_item = self.krate.unwrap().trait_item(trait_item_id);
201 self.visit_trait_item(nested_trait_item)
204 fn visit_nested_impl_item(&mut self, impl_item_id: hir::ImplItemId) {
205 let nested_impl_item = self.krate.unwrap().impl_item(impl_item_id);
206 self.visit_impl_item(nested_impl_item)
209 fn visit_nested_foreign_item(&mut self, id: hir::ForeignItemId) {
210 let nested_foreign_item = self.krate.unwrap().foreign_item(id);
211 self.visit_foreign_item(nested_foreign_item);
214 fn visit_nested_body(&mut self, body_id: hir::BodyId) {
215 let nested_body = self.krate.unwrap().body(body_id);
216 self.visit_body(nested_body)
219 fn visit_item(&mut self, i: &'v hir::Item<'v>) {
221 (self, i, i.kind, Id::Node(i.hir_id()), hir, Item, ItemKind),
242 hir_visit::walk_item(self, i)
245 fn visit_body(&mut self, b: &'v hir::Body<'v>) {
246 self.record("Body", Id::None, b);
247 hir_visit::walk_body(self, b);
250 fn visit_mod(&mut self, m: &'v hir::Mod<'v>, _s: Span, n: HirId) {
251 self.record("Mod", Id::None, m);
252 hir_visit::walk_mod(self, m, n)
255 fn visit_foreign_item(&mut self, i: &'v hir::ForeignItem<'v>) {
257 (self, i, i.kind, Id::Node(i.hir_id()), hir, ForeignItem, ForeignItemKind),
260 hir_visit::walk_foreign_item(self, i)
263 fn visit_local(&mut self, l: &'v hir::Local<'v>) {
264 self.record("Local", Id::Node(l.hir_id), l);
265 hir_visit::walk_local(self, l)
268 fn visit_block(&mut self, b: &'v hir::Block<'v>) {
269 self.record("Block", Id::Node(b.hir_id), b);
270 hir_visit::walk_block(self, b)
273 fn visit_stmt(&mut self, s: &'v hir::Stmt<'v>) {
275 (self, s, s.kind, Id::Node(s.hir_id), hir, Stmt, StmtKind),
276 [Local, Item, Expr, Semi]
278 hir_visit::walk_stmt(self, s)
281 fn visit_arm(&mut self, a: &'v hir::Arm<'v>) {
282 self.record("Arm", Id::Node(a.hir_id), a);
283 hir_visit::walk_arm(self, a)
286 fn visit_pat(&mut self, p: &'v hir::Pat<'v>) {
288 (self, p, p.kind, Id::Node(p.hir_id), hir, Pat, PatKind),
289 [Wild, Binding, Struct, TupleStruct, Or, Path, Tuple, Box, Ref, Lit, Range, Slice]
291 hir_visit::walk_pat(self, p)
294 fn visit_pat_field(&mut self, f: &'v hir::PatField<'v>) {
295 self.record("PatField", Id::Node(f.hir_id), f);
296 hir_visit::walk_pat_field(self, f)
299 fn visit_expr(&mut self, e: &'v hir::Expr<'v>) {
301 (self, e, e.kind, Id::Node(e.hir_id), hir, Expr, ExprKind),
303 Box, ConstBlock, Array, Call, MethodCall, Tup, Binary, Unary, Lit, Cast, Type,
304 DropTemps, Let, If, Loop, Match, Closure, Block, Assign, AssignOp, Field, Index,
305 Path, AddrOf, Break, Continue, Ret, InlineAsm, Struct, Repeat, Yield, Err
308 hir_visit::walk_expr(self, e)
311 fn visit_let_expr(&mut self, lex: &'v hir::Let<'v>) {
312 self.record("Let", Id::Node(lex.hir_id), lex);
313 hir_visit::walk_let_expr(self, lex)
316 fn visit_expr_field(&mut self, f: &'v hir::ExprField<'v>) {
317 self.record("ExprField", Id::Node(f.hir_id), f);
318 hir_visit::walk_expr_field(self, f)
321 fn visit_ty(&mut self, t: &'v hir::Ty<'v>) {
323 (self, t, t.kind, Id::Node(t.hir_id), hir, Ty, TyKind),
340 hir_visit::walk_ty(self, t)
343 fn visit_generic_param(&mut self, p: &'v hir::GenericParam<'v>) {
344 self.record("GenericParam", Id::Node(p.hir_id), p);
345 hir_visit::walk_generic_param(self, p)
348 fn visit_generics(&mut self, g: &'v hir::Generics<'v>) {
349 self.record("Generics", Id::None, g);
350 hir_visit::walk_generics(self, g)
353 fn visit_where_predicate(&mut self, p: &'v hir::WherePredicate<'v>) {
355 (self, p, p, Id::None, hir, WherePredicate, WherePredicate),
356 [BoundPredicate, RegionPredicate, EqPredicate]
358 hir_visit::walk_where_predicate(self, p)
363 fk: hir_visit::FnKind<'v>,
364 fd: &'v hir::FnDecl<'v>,
369 self.record("FnDecl", Id::None, fd);
370 hir_visit::walk_fn(self, fk, fd, b, id)
373 fn visit_use(&mut self, p: &'v hir::UsePath<'v>, hir_id: hir::HirId) {
374 // This is `visit_use`, but the type is `Path` so record it that way.
375 self.record("Path", Id::None, p);
376 hir_visit::walk_use(self, p, hir_id)
379 fn visit_trait_item(&mut self, ti: &'v hir::TraitItem<'v>) {
381 (self, ti, ti.kind, Id::Node(ti.hir_id()), hir, TraitItem, TraitItemKind),
384 hir_visit::walk_trait_item(self, ti)
387 fn visit_trait_item_ref(&mut self, ti: &'v hir::TraitItemRef) {
388 self.record("TraitItemRef", Id::Node(ti.id.hir_id()), ti);
389 hir_visit::walk_trait_item_ref(self, ti)
392 fn visit_impl_item(&mut self, ii: &'v hir::ImplItem<'v>) {
394 (self, ii, ii.kind, Id::Node(ii.hir_id()), hir, ImplItem, ImplItemKind),
397 hir_visit::walk_impl_item(self, ii)
400 fn visit_foreign_item_ref(&mut self, fi: &'v hir::ForeignItemRef) {
401 self.record("ForeignItemRef", Id::Node(fi.id.hir_id()), fi);
402 hir_visit::walk_foreign_item_ref(self, fi)
405 fn visit_impl_item_ref(&mut self, ii: &'v hir::ImplItemRef) {
406 self.record("ImplItemRef", Id::Node(ii.id.hir_id()), ii);
407 hir_visit::walk_impl_item_ref(self, ii)
410 fn visit_param_bound(&mut self, b: &'v hir::GenericBound<'v>) {
412 (self, b, b, Id::None, hir, GenericBound, GenericBound),
413 [Trait, LangItemTrait, Outlives]
415 hir_visit::walk_param_bound(self, b)
418 fn visit_field_def(&mut self, s: &'v hir::FieldDef<'v>) {
419 self.record("FieldDef", Id::Node(s.hir_id), s);
420 hir_visit::walk_field_def(self, s)
423 fn visit_variant(&mut self, v: &'v hir::Variant<'v>) {
424 self.record("Variant", Id::None, v);
425 hir_visit::walk_variant(self, v)
428 fn visit_generic_arg(&mut self, ga: &'v hir::GenericArg<'v>) {
430 (self, ga, ga, Id::Node(ga.hir_id()), hir, GenericArg, GenericArg),
431 [Lifetime, Type, Const, Infer]
434 hir::GenericArg::Lifetime(lt) => self.visit_lifetime(lt),
435 hir::GenericArg::Type(ty) => self.visit_ty(ty),
436 hir::GenericArg::Const(ct) => self.visit_anon_const(&ct.value),
437 hir::GenericArg::Infer(inf) => self.visit_infer(inf),
441 fn visit_lifetime(&mut self, lifetime: &'v hir::Lifetime) {
442 self.record("Lifetime", Id::Node(lifetime.hir_id), lifetime);
443 hir_visit::walk_lifetime(self, lifetime)
446 fn visit_path(&mut self, path: &hir::Path<'v>, _id: hir::HirId) {
447 self.record("Path", Id::None, path);
448 hir_visit::walk_path(self, path)
451 fn visit_path_segment(&mut self, path_segment: &'v hir::PathSegment<'v>) {
452 self.record("PathSegment", Id::None, path_segment);
453 hir_visit::walk_path_segment(self, path_segment)
456 fn visit_generic_args(&mut self, ga: &'v hir::GenericArgs<'v>) {
457 self.record("GenericArgs", Id::None, ga);
458 hir_visit::walk_generic_args(self, ga)
461 fn visit_assoc_type_binding(&mut self, type_binding: &'v hir::TypeBinding<'v>) {
462 self.record("TypeBinding", Id::Node(type_binding.hir_id), type_binding);
463 hir_visit::walk_assoc_type_binding(self, type_binding)
466 fn visit_attribute(&mut self, attr: &'v ast::Attribute) {
467 self.record("Attribute", Id::Attr(attr.id), attr);
470 fn visit_inline_asm(&mut self, asm: &'v hir::InlineAsm<'v>, id: HirId) {
471 self.record("InlineAsm", Id::None, asm);
472 hir_visit::walk_inline_asm(self, asm, id);
476 impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> {
477 fn visit_foreign_item(&mut self, i: &'v ast::ForeignItem) {
479 (self, i, i.kind, Id::None, ast, ForeignItem, ForeignItemKind),
480 [Static, Fn, TyAlias, MacCall]
482 ast_visit::walk_foreign_item(self, i)
485 fn visit_item(&mut self, i: &'v ast::Item) {
487 (self, i, i.kind, Id::None, ast, Item, ItemKind),
508 ast_visit::walk_item(self, i)
511 fn visit_local(&mut self, l: &'v ast::Local) {
512 self.record("Local", Id::None, l);
513 ast_visit::walk_local(self, l)
516 fn visit_block(&mut self, b: &'v ast::Block) {
517 self.record("Block", Id::None, b);
518 ast_visit::walk_block(self, b)
521 fn visit_stmt(&mut self, s: &'v ast::Stmt) {
523 (self, s, s.kind, Id::None, ast, Stmt, StmtKind),
524 [Local, Item, Expr, Semi, Empty, MacCall]
526 ast_visit::walk_stmt(self, s)
529 fn visit_param(&mut self, p: &'v ast::Param) {
530 self.record("Param", Id::None, p);
531 ast_visit::walk_param(self, p)
534 fn visit_arm(&mut self, a: &'v ast::Arm) {
535 self.record("Arm", Id::None, a);
536 ast_visit::walk_arm(self, a)
539 fn visit_pat(&mut self, p: &'v ast::Pat) {
541 (self, p, p.kind, Id::None, ast, Pat, PatKind),
560 ast_visit::walk_pat(self, p)
563 fn visit_expr(&mut self, e: &'v ast::Expr) {
566 (self, e, e.kind, Id::None, ast, Expr, ExprKind),
568 Box, Array, ConstBlock, Call, MethodCall, Tup, Binary, Unary, Lit, Cast, Type, Let,
569 If, While, ForLoop, Loop, Match, Closure, Block, Async, Await, TryBlock, Assign,
570 AssignOp, Field, Index, Range, Underscore, Path, AddrOf, Break, Continue, Ret,
571 InlineAsm, FormatArgs, MacCall, Struct, Repeat, Paren, Try, Yield, Yeet, IncludedBytes, Err
574 ast_visit::walk_expr(self, e)
577 fn visit_ty(&mut self, t: &'v ast::Ty) {
579 (self, t, t.kind, Id::None, ast, Ty, TyKind),
601 ast_visit::walk_ty(self, t)
604 fn visit_generic_param(&mut self, g: &'v ast::GenericParam) {
605 self.record("GenericParam", Id::None, g);
606 ast_visit::walk_generic_param(self, g)
609 fn visit_where_predicate(&mut self, p: &'v ast::WherePredicate) {
611 (self, p, p, Id::None, ast, WherePredicate, WherePredicate),
612 [BoundPredicate, RegionPredicate, EqPredicate]
614 ast_visit::walk_where_predicate(self, p)
617 fn visit_fn(&mut self, fk: ast_visit::FnKind<'v>, _: Span, _: NodeId) {
618 self.record("FnDecl", Id::None, fk.decl());
619 ast_visit::walk_fn(self, fk)
622 fn visit_assoc_item(&mut self, i: &'v ast::AssocItem, ctxt: ast_visit::AssocCtxt) {
624 (self, i, i.kind, Id::None, ast, AssocItem, AssocItemKind),
625 [Const, Fn, Type, MacCall]
627 ast_visit::walk_assoc_item(self, i, ctxt);
630 fn visit_param_bound(&mut self, b: &'v ast::GenericBound, _ctxt: BoundKind) {
632 (self, b, b, Id::None, ast, GenericBound, GenericBound),
635 ast_visit::walk_param_bound(self, b)
638 fn visit_field_def(&mut self, s: &'v ast::FieldDef) {
639 self.record("FieldDef", Id::None, s);
640 ast_visit::walk_field_def(self, s)
643 fn visit_variant(&mut self, v: &'v ast::Variant) {
644 self.record("Variant", Id::None, v);
645 ast_visit::walk_variant(self, v)
648 // `UseTree` has one inline use (in `ast::ItemKind::Use`) and one
649 // non-inline use (in `ast::UseTreeKind::Nested). The former case is more
650 // common, so we don't implement `visit_use_tree` and tolerate the missed
651 // coverage in the latter case.
653 // `PathSegment` has one inline use (in `ast::ExprKind::MethodCall`) and
654 // one non-inline use (in `ast::Path::segments`). The latter case is more
655 // common than the former case, so we implement this visitor and tolerate
656 // the double counting in the former case.
657 fn visit_path_segment(&mut self, path_segment: &'v ast::PathSegment) {
658 self.record("PathSegment", Id::None, path_segment);
659 ast_visit::walk_path_segment(self, path_segment)
662 // `GenericArgs` has one inline use (in `ast::AssocConstraint::gen_args`) and one
663 // non-inline use (in `ast::PathSegment::args`). The latter case is more
664 // common, so we implement `visit_generic_args` and tolerate the double
665 // counting in the former case.
666 fn visit_generic_args(&mut self, g: &'v ast::GenericArgs) {
668 (self, g, g, Id::None, ast, GenericArgs, GenericArgs),
669 [AngleBracketed, Parenthesized]
671 ast_visit::walk_generic_args(self, g)
674 fn visit_attribute(&mut self, attr: &'v ast::Attribute) {
676 (self, attr, attr.kind, Id::None, ast, Attribute, AttrKind),
679 ast_visit::walk_attribute(self, attr)
682 fn visit_expr_field(&mut self, f: &'v ast::ExprField) {
683 self.record("ExprField", Id::None, f);
684 ast_visit::walk_expr_field(self, f)
687 fn visit_crate(&mut self, krate: &'v ast::Crate) {
688 self.record("Crate", Id::None, krate);
689 ast_visit::walk_crate(self, krate)
692 fn visit_inline_asm(&mut self, asm: &'v ast::InlineAsm) {
693 self.record("InlineAsm", Id::None, asm);
694 ast_visit::walk_inline_asm(self, asm)