rustc_expand/
stats.rs

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