rustc_expand/
stats.rs

1use std::iter;
2
3use rustc_ast::{self as ast, DUMMY_NODE_ID, Expr, ExprKind};
4use rustc_ast_pretty::pprust;
5use rustc_span::hygiene::{ExpnKind, MacroKind};
6use rustc_span::{Span, Symbol, kw, sym};
7use smallvec::SmallVec;
8
9use crate::base::{Annotatable, ExtCtxt};
10use crate::expand::{AstFragment, AstFragmentKind};
11
12#[derive(Default)]
13pub struct MacroStat {
14    /// Number of uses of the macro.
15    pub uses: usize,
16
17    /// Number of lines of code (when pretty-printed).
18    pub lines: usize,
19
20    /// Number of bytes of code (when pretty-printed).
21    pub bytes: usize,
22}
23
24pub(crate) fn elems_to_string<T>(elems: &SmallVec<[T; 1]>, f: impl Fn(&T) -> String) -> String {
25    let mut s = String::new();
26    for (i, elem) in elems.iter().enumerate() {
27        if i > 0 {
28            s.push('\n');
29        }
30        s.push_str(&f(elem));
31    }
32    s
33}
34
35pub(crate) fn unreachable_to_string<T>(_: &T) -> String {
36    unreachable!()
37}
38
39pub(crate) fn update_bang_macro_stats(
40    ecx: &mut ExtCtxt<'_>,
41    fragment_kind: AstFragmentKind,
42    span: Span,
43    mac: Box<ast::MacCall>,
44    fragment: &AstFragment,
45) {
46    // Does this path match any of the include macros, e.g. `include!`?
47    // Ignore them. They would have large numbers but are entirely
48    // unsurprising and uninteresting.
49    let is_include_path = mac.path == sym::include
50        || mac.path == sym::include_bytes
51        || mac.path == sym::include_str
52        || mac.path == [sym::std, sym::include].as_slice() // std::include
53        || mac.path == [sym::std, sym::include_bytes].as_slice() // std::include_bytes
54        || mac.path == [sym::std, sym::include_str].as_slice(); // std::include_str
55    if is_include_path {
56        return;
57    }
58
59    // The call itself (e.g. `println!("hi")`) is the input. Need to wrap
60    // `mac` in something printable; `ast::Expr` is as good as anything
61    // else.
62    let expr = Expr {
63        id: DUMMY_NODE_ID,
64        kind: ExprKind::MacCall(mac),
65        span: Default::default(),
66        attrs: Default::default(),
67        tokens: None,
68    };
69    let input = pprust::expr_to_string(&expr);
70
71    // Get `mac` back out of `expr`.
72    let ast::Expr { kind: ExprKind::MacCall(mac), .. } = expr else { unreachable!() };
73
74    update_macro_stats(ecx, MacroKind::Bang, fragment_kind, span, &mac.path, &input, fragment);
75}
76
77pub(crate) fn update_attr_macro_stats(
78    ecx: &mut ExtCtxt<'_>,
79    fragment_kind: AstFragmentKind,
80    span: Span,
81    path: &ast::Path,
82    attr: &ast::Attribute,
83    item: Annotatable,
84    fragment: &AstFragment,
85) {
86    // Does this path match `#[derive(...)]` in any of its forms? If so,
87    // ignore it because the individual derives will go through the
88    // `Invocation::Derive` handling separately.
89    let is_derive_path = *path == sym::derive
90        // ::core::prelude::v1::derive
91        || *path == [kw::PathRoot, sym::core, sym::prelude, sym::v1, sym::derive].as_slice();
92    if is_derive_path {
93        return;
94    }
95
96    // The attribute plus the item itself constitute the input, which we
97    // measure.
98    let input = format!(
99        "{}\n{}",
100        pprust::attribute_to_string(attr),
101        fragment_kind.expect_from_annotatables(iter::once(item)).to_string(),
102    );
103    update_macro_stats(ecx, MacroKind::Attr, fragment_kind, span, path, &input, fragment);
104}
105
106pub(crate) fn update_derive_macro_stats(
107    ecx: &mut ExtCtxt<'_>,
108    fragment_kind: AstFragmentKind,
109    span: Span,
110    path: &ast::Path,
111    fragment: &AstFragment,
112) {
113    // Use something like `#[derive(Clone)]` for the measured input, even
114    // though it may have actually appeared in a multi-derive attribute
115    // like `#[derive(Clone, Copy, Debug)]`.
116    let input = format!("#[derive({})]", pprust::path_to_string(path));
117    update_macro_stats(ecx, MacroKind::Derive, fragment_kind, span, path, &input, fragment);
118}
119
120pub(crate) fn update_macro_stats(
121    ecx: &mut ExtCtxt<'_>,
122    macro_kind: MacroKind,
123    fragment_kind: AstFragmentKind,
124    span: Span,
125    path: &ast::Path,
126    input: &str,
127    fragment: &AstFragment,
128) {
129    // Measure the size of the output by pretty-printing it and counting
130    // the lines and bytes.
131    let name = Symbol::intern(&pprust::path_to_string(path));
132    let output = fragment.to_string();
133    let num_lines = output.trim_end().split('\n').count();
134    let num_bytes = output.len();
135
136    // This code is useful for debugging `-Zmacro-stats`. For every
137    // invocation it prints the full input and output.
138    if false {
139        let name = ExpnKind::Macro(macro_kind, name).descr();
140        let crate_name = &ecx.ecfg.crate_name;
141        let span = ecx
142            .sess
143            .source_map()
144            .span_to_string(span, rustc_span::FileNameDisplayPreference::Local);
145        eprint!(
146            "\
147            -------------------------------\n\
148            {name}: [{crate_name}] ({fragment_kind:?}) {span}\n\
149            -------------------------------\n\
150            {input}\n\
151            -- {num_lines} lines, {num_bytes} bytes --\n\
152            {output}\n\
153        "
154        );
155    }
156
157    // The recorded size is the difference between the input and the output.
158    let entry = ecx.macro_stats.entry((name, macro_kind)).or_insert(MacroStat::default());
159    entry.uses += 1;
160    entry.lines += num_lines;
161    entry.bytes += num_bytes;
162}