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