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, s, 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_path(&mut self, path: &'v hir::Path<'v>, _id: hir::HirId) {
441 self.record("Path", Id::None, path);
442 hir_visit::walk_path(self, path)
445 fn visit_path_segment(&mut self, path_span: Span, path_segment: &'v hir::PathSegment<'v>) {
446 self.record("PathSegment", Id::None, path_segment);
447 hir_visit::walk_path_segment(self, path_span, path_segment)
450 fn visit_generic_args(&mut self, sp: Span, ga: &'v hir::GenericArgs<'v>) {
451 self.record("GenericArgs", Id::None, ga);
452 hir_visit::walk_generic_args(self, sp, ga)
455 fn visit_assoc_type_binding(&mut self, type_binding: &'v hir::TypeBinding<'v>) {
456 self.record("TypeBinding", Id::Node(type_binding.hir_id), type_binding);
457 hir_visit::walk_assoc_type_binding(self, type_binding)
460 fn visit_attribute(&mut self, attr: &'v ast::Attribute) {
461 self.record("Attribute", Id::Attr(attr.id), attr);
464 fn visit_inline_asm(&mut self, asm: &'v hir::InlineAsm<'v>, id: HirId) {
465 self.record("InlineAsm", Id::None, asm);
466 hir_visit::walk_inline_asm(self, asm, id);
470 impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> {
471 fn visit_foreign_item(&mut self, i: &'v ast::ForeignItem) {
473 (self, i, i.kind, Id::None, ast, ForeignItem, ForeignItemKind),
474 [Static, Fn, TyAlias, MacCall]
476 ast_visit::walk_foreign_item(self, i)
479 fn visit_item(&mut self, i: &'v ast::Item) {
481 (self, i, i.kind, Id::None, ast, Item, ItemKind),
502 ast_visit::walk_item(self, i)
505 fn visit_local(&mut self, l: &'v ast::Local) {
506 self.record("Local", Id::None, l);
507 ast_visit::walk_local(self, l)
510 fn visit_block(&mut self, b: &'v ast::Block) {
511 self.record("Block", Id::None, b);
512 ast_visit::walk_block(self, b)
515 fn visit_stmt(&mut self, s: &'v ast::Stmt) {
517 (self, s, s.kind, Id::None, ast, Stmt, StmtKind),
518 [Local, Item, Expr, Semi, Empty, MacCall]
520 ast_visit::walk_stmt(self, s)
523 fn visit_param(&mut self, p: &'v ast::Param) {
524 self.record("Param", Id::None, p);
525 ast_visit::walk_param(self, p)
528 fn visit_arm(&mut self, a: &'v ast::Arm) {
529 self.record("Arm", Id::None, a);
530 ast_visit::walk_arm(self, a)
533 fn visit_pat(&mut self, p: &'v ast::Pat) {
535 (self, p, p.kind, Id::None, ast, Pat, PatKind),
554 ast_visit::walk_pat(self, p)
557 fn visit_expr(&mut self, e: &'v ast::Expr) {
559 (self, e, e.kind, Id::None, ast, Expr, ExprKind),
561 Box, Array, ConstBlock, Call, MethodCall, Tup, Binary, Unary, Lit, Cast, Type, Let,
562 If, While, ForLoop, Loop, Match, Closure, Block, Async, Await, TryBlock, Assign,
563 AssignOp, Field, Index, Range, Underscore, Path, AddrOf, Break, Continue, Ret,
564 InlineAsm, MacCall, Struct, Repeat, Paren, Try, Yield, Yeet, Err
567 ast_visit::walk_expr(self, e)
570 fn visit_ty(&mut self, t: &'v ast::Ty) {
572 (self, t, t.kind, Id::None, ast, Ty, TyKind),
594 ast_visit::walk_ty(self, t)
597 fn visit_generic_param(&mut self, g: &'v ast::GenericParam) {
598 self.record("GenericParam", Id::None, g);
599 ast_visit::walk_generic_param(self, g)
602 fn visit_where_predicate(&mut self, p: &'v ast::WherePredicate) {
604 (self, p, p, Id::None, ast, WherePredicate, WherePredicate),
605 [BoundPredicate, RegionPredicate, EqPredicate]
607 ast_visit::walk_where_predicate(self, p)
610 fn visit_fn(&mut self, fk: ast_visit::FnKind<'v>, s: Span, _: NodeId) {
611 self.record("FnDecl", Id::None, fk.decl());
612 ast_visit::walk_fn(self, fk, s)
615 fn visit_assoc_item(&mut self, i: &'v ast::AssocItem, ctxt: ast_visit::AssocCtxt) {
617 (self, i, i.kind, Id::None, ast, AssocItem, AssocItemKind),
618 [Const, Fn, TyAlias, MacCall]
620 ast_visit::walk_assoc_item(self, i, ctxt);
623 fn visit_param_bound(&mut self, b: &'v ast::GenericBound, _ctxt: BoundKind) {
625 (self, b, b, Id::None, ast, GenericBound, GenericBound),
628 ast_visit::walk_param_bound(self, b)
631 fn visit_field_def(&mut self, s: &'v ast::FieldDef) {
632 self.record("FieldDef", Id::None, s);
633 ast_visit::walk_field_def(self, s)
636 fn visit_variant(&mut self, v: &'v ast::Variant) {
637 self.record("Variant", Id::None, v);
638 ast_visit::walk_variant(self, v)
641 // `UseTree` has one inline use (in `ast::ItemKind::Use`) and one
642 // non-inline use (in `ast::UseTreeKind::Nested). The former case is more
643 // common, so we don't implement `visit_use_tree` and tolerate the missed
644 // coverage in the latter case.
646 // `PathSegment` has one inline use (in `ast::ExprKind::MethodCall`) and
647 // one non-inline use (in `ast::Path::segments`). The latter case is more
648 // common than the former case, so we implement this visitor and tolerate
649 // the double counting in the former case.
650 fn visit_path_segment(&mut self, path_span: Span, path_segment: &'v ast::PathSegment) {
651 self.record("PathSegment", Id::None, path_segment);
652 ast_visit::walk_path_segment(self, path_span, path_segment)
655 // `GenericArgs` has one inline use (in `ast::AssocConstraint::gen_args`) and one
656 // non-inline use (in `ast::PathSegment::args`). The latter case is more
657 // common, so we implement `visit_generic_args` and tolerate the double
658 // counting in the former case.
659 fn visit_generic_args(&mut self, sp: Span, g: &'v ast::GenericArgs) {
661 (self, g, g, Id::None, ast, GenericArgs, GenericArgs),
662 [AngleBracketed, Parenthesized]
664 ast_visit::walk_generic_args(self, sp, g)
667 fn visit_attribute(&mut self, attr: &'v ast::Attribute) {
669 (self, attr, attr.kind, Id::None, ast, Attribute, AttrKind),
672 ast_visit::walk_attribute(self, attr)
675 fn visit_expr_field(&mut self, f: &'v ast::ExprField) {
676 self.record("ExprField", Id::None, f);
677 ast_visit::walk_expr_field(self, f)
680 fn visit_crate(&mut self, krate: &'v ast::Crate) {
681 self.record("Crate", Id::None, krate);
682 ast_visit::walk_crate(self, krate)
685 fn visit_inline_asm(&mut self, asm: &'v ast::InlineAsm) {
686 self.record("InlineAsm", Id::None, asm);
687 ast_visit::walk_inline_asm(self, asm)