Rollup merge of #142069 - nnethercote:Zmacro-stats, r=petrochenkov

Introduce `-Zmacro-stats`

Introduce `-Zmacro-stats`.

It collects data about macro expansions and prints them in a table after expansion finishes. It's very useful for detecting macro bloat, especially for proc macros.

r? `@petrochenkov`
This commit is contained in:
Matthias Krüger
2025-06-13 05:16:56 +02:00
committed by GitHub
21 changed files with 715 additions and 81 deletions

View File

@@ -97,12 +97,12 @@ pub struct Path {
pub tokens: Option<LazyAttrTokenStream>, pub tokens: Option<LazyAttrTokenStream>,
} }
// Succeeds if the path has a single segment that is arg-free and matches the given symbol.
impl PartialEq<Symbol> for Path { impl PartialEq<Symbol> for Path {
#[inline] #[inline]
fn eq(&self, name: &Symbol) -> bool { fn eq(&self, name: &Symbol) -> bool {
if let [segment] = self.segments.as_ref() if let [segment] = self.segments.as_ref()
&& segment.args.is_none() && segment == name
&& segment.ident.name == *name
{ {
true true
} else { } else {
@@ -111,6 +111,15 @@ impl PartialEq<Symbol> for Path {
} }
} }
// Succeeds if the path has segments that are arg-free and match the given symbols.
impl PartialEq<&[Symbol]> for Path {
#[inline]
fn eq(&self, names: &&[Symbol]) -> bool {
self.segments.len() == names.len()
&& self.segments.iter().zip(names.iter()).all(|(s1, s2)| s1 == s2)
}
}
impl<CTX: rustc_span::HashStableContext> HashStable<CTX> for Path { impl<CTX: rustc_span::HashStableContext> HashStable<CTX> for Path {
fn hash_stable(&self, hcx: &mut CTX, hasher: &mut StableHasher) { fn hash_stable(&self, hcx: &mut CTX, hasher: &mut StableHasher) {
self.segments.len().hash_stable(hcx, hasher); self.segments.len().hash_stable(hcx, hasher);
@@ -166,6 +175,14 @@ pub struct PathSegment {
pub args: Option<P<GenericArgs>>, pub args: Option<P<GenericArgs>>,
} }
// Succeeds if the path segment is arg-free and matches the given symbol.
impl PartialEq<Symbol> for PathSegment {
#[inline]
fn eq(&self, name: &Symbol) -> bool {
self.args.is_none() && self.ident.name == *name
}
}
impl PathSegment { impl PathSegment {
pub fn from_ident(ident: Ident) -> Self { pub fn from_ident(ident: Ident) -> Self {
PathSegment { ident, id: DUMMY_NODE_ID, args: None } PathSegment { ident, id: DUMMY_NODE_ID, args: None }

View File

@@ -53,6 +53,18 @@ pub fn item_to_string(i: &ast::Item) -> String {
State::new().item_to_string(i) State::new().item_to_string(i)
} }
pub fn assoc_item_to_string(i: &ast::AssocItem) -> String {
State::new().assoc_item_to_string(i)
}
pub fn foreign_item_to_string(i: &ast::ForeignItem) -> String {
State::new().foreign_item_to_string(i)
}
pub fn stmt_to_string(s: &ast::Stmt) -> String {
State::new().stmt_to_string(s)
}
pub fn path_to_string(p: &ast::Path) -> String { pub fn path_to_string(p: &ast::Path) -> String {
State::new().path_to_string(p) State::new().path_to_string(p)
} }

View File

@@ -1063,6 +1063,14 @@ pub trait PrintState<'a>: std::ops::Deref<Target = pp::Printer> + std::ops::Dere
Self::to_string(|s| s.print_item(i)) Self::to_string(|s| s.print_item(i))
} }
fn assoc_item_to_string(&self, i: &ast::AssocItem) -> String {
Self::to_string(|s| s.print_assoc_item(i))
}
fn foreign_item_to_string(&self, i: &ast::ForeignItem) -> String {
Self::to_string(|s| s.print_foreign_item(i))
}
fn path_to_string(&self, p: &ast::Path) -> String { fn path_to_string(&self, p: &ast::Path) -> String {
Self::to_string(|s| s.print_path(p, false, 0)) Self::to_string(|s| s.print_path(p, false, 0))
} }

View File

@@ -28,7 +28,7 @@ impl<'a> State<'a> {
} }
} }
fn print_foreign_item(&mut self, item: &ast::ForeignItem) { pub(crate) fn print_foreign_item(&mut self, item: &ast::ForeignItem) {
let ast::Item { id, span, ref attrs, ref kind, ref vis, tokens: _ } = *item; let ast::Item { id, span, ref attrs, ref kind, ref vis, tokens: _ } = *item;
self.ann.pre(self, AnnNode::SubItem(id)); self.ann.pre(self, AnnNode::SubItem(id));
self.hardbreak_if_not_bol(); self.hardbreak_if_not_bol();
@@ -548,7 +548,7 @@ impl<'a> State<'a> {
} }
} }
fn print_assoc_item(&mut self, item: &ast::AssocItem) { pub(crate) fn print_assoc_item(&mut self, item: &ast::AssocItem) {
let ast::Item { id, span, ref attrs, ref kind, ref vis, tokens: _ } = *item; let ast::Item { id, span, ref attrs, ref kind, ref vis, tokens: _ } = *item;
self.ann.pre(self, AnnNode::SubItem(id)); self.ann.pre(self, AnnNode::SubItem(id));
self.hardbreak_if_not_bol(); self.hardbreak_if_not_bol();

View File

@@ -1,16 +1,37 @@
//! This is an extremely bare-bones alternative to the `thousands` crate on //! This is a bare-bones alternative to the `thousands` crate on crates.io, for
//! crates.io, for printing large numbers in a readable fashion. //! printing large numbers in a readable fashion.
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
// Converts the number to a string, with underscores as the thousands separator. fn format_with_underscores(mut s: String) -> String {
pub fn format_with_underscores(n: usize) -> String { // Ignore a leading '-'.
let mut s = n.to_string(); let start = if s.starts_with('-') { 1 } else { 0 };
let mut i = s.len();
while i > 3 { // Stop after the first non-digit, e.g. '.' or 'e' for floats.
let non_digit = s[start..].find(|c: char| !c.is_digit(10));
let end = if let Some(non_digit) = non_digit { start + non_digit } else { s.len() };
// Insert underscores within `start..end`.
let mut i = end;
while i > start + 3 {
i -= 3; i -= 3;
s.insert(i, '_'); s.insert(i, '_');
} }
s s
} }
/// Print a `usize` with underscore separators.
pub fn usize_with_underscores(n: usize) -> String {
format_with_underscores(format!("{n}"))
}
/// Print an `isize` with underscore separators.
pub fn isize_with_underscores(n: isize) -> String {
format_with_underscores(format!("{n}"))
}
/// Print an `f64` with precision 1 (one decimal place) and underscore separators.
pub fn f64p1_with_underscores(n: f64) -> String {
format_with_underscores(format!("{n:.1}"))
}

View File

@@ -2,13 +2,51 @@ use super::*;
#[test] #[test]
fn test_format_with_underscores() { fn test_format_with_underscores() {
assert_eq!("0", format_with_underscores(0)); assert_eq!("", format_with_underscores("".to_string()));
assert_eq!("1", format_with_underscores(1)); assert_eq!("0", format_with_underscores("0".to_string()));
assert_eq!("99", format_with_underscores(99)); assert_eq!("12_345.67e14", format_with_underscores("12345.67e14".to_string()));
assert_eq!("345", format_with_underscores(345)); assert_eq!("-1_234.5678e10", format_with_underscores("-1234.5678e10".to_string()));
assert_eq!("1_000", format_with_underscores(1_000)); assert_eq!("------", format_with_underscores("------".to_string()));
assert_eq!("12_001", format_with_underscores(12_001)); assert_eq!("abcdefgh", format_with_underscores("abcdefgh".to_string()));
assert_eq!("999_999", format_with_underscores(999_999)); assert_eq!("-1b", format_with_underscores("-1b".to_string()));
assert_eq!("1_000_000", format_with_underscores(1_000_000)); assert_eq!("-3_456xyz", format_with_underscores("-3456xyz".to_string()));
assert_eq!("12_345_678", format_with_underscores(12_345_678)); }
#[test]
fn test_usize_with_underscores() {
assert_eq!("0", usize_with_underscores(0));
assert_eq!("1", usize_with_underscores(1));
assert_eq!("99", usize_with_underscores(99));
assert_eq!("345", usize_with_underscores(345));
assert_eq!("1_000", usize_with_underscores(1_000));
assert_eq!("12_001", usize_with_underscores(12_001));
assert_eq!("999_999", usize_with_underscores(999_999));
assert_eq!("1_000_000", usize_with_underscores(1_000_000));
assert_eq!("12_345_678", usize_with_underscores(12_345_678));
}
#[test]
fn test_isize_with_underscores() {
assert_eq!("0", isize_with_underscores(0));
assert_eq!("-1", isize_with_underscores(-1));
assert_eq!("99", isize_with_underscores(99));
assert_eq!("345", isize_with_underscores(345));
assert_eq!("-1_000", isize_with_underscores(-1_000));
assert_eq!("12_001", isize_with_underscores(12_001));
assert_eq!("-999_999", isize_with_underscores(-999_999));
assert_eq!("1_000_000", isize_with_underscores(1_000_000));
assert_eq!("-12_345_678", isize_with_underscores(-12_345_678));
}
#[test]
fn test_f64p1_with_underscores() {
assert_eq!("0.0", f64p1_with_underscores(0f64));
assert_eq!("0.0", f64p1_with_underscores(0.00000001));
assert_eq!("-0.0", f64p1_with_underscores(-0.00000001));
assert_eq!("1.0", f64p1_with_underscores(0.9999999));
assert_eq!("-1.0", f64p1_with_underscores(-0.9999999));
assert_eq!("345.5", f64p1_with_underscores(345.4999999));
assert_eq!("-100_000.0", f64p1_with_underscores(-100_000f64));
assert_eq!("123_456_789.1", f64p1_with_underscores(123456789.123456789));
assert_eq!("-123_456_789.1", f64p1_with_underscores(-123456789.123456789));
} }

View File

@@ -12,7 +12,7 @@ use rustc_ast::tokenstream::TokenStream;
use rustc_ast::visit::{AssocCtxt, Visitor}; use rustc_ast::visit::{AssocCtxt, Visitor};
use rustc_ast::{self as ast, AttrVec, Attribute, HasAttrs, Item, NodeId, PatKind}; use rustc_ast::{self as ast, AttrVec, Attribute, HasAttrs, Item, NodeId, PatKind};
use rustc_attr_data_structures::{AttributeKind, Deprecation, Stability, find_attr}; use rustc_attr_data_structures::{AttributeKind, Deprecation, Stability, find_attr};
use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
use rustc_data_structures::sync; use rustc_data_structures::sync;
use rustc_errors::{DiagCtxtHandle, ErrorGuaranteed, PResult}; use rustc_errors::{DiagCtxtHandle, ErrorGuaranteed, PResult};
use rustc_feature::Features; use rustc_feature::Features;
@@ -727,6 +727,7 @@ pub enum SyntaxExtensionKind {
/// A trivial attribute "macro" that does nothing, /// A trivial attribute "macro" that does nothing,
/// only keeps the attribute and marks it as inert, /// only keeps the attribute and marks it as inert,
/// thus making it ineligible for further expansion. /// thus making it ineligible for further expansion.
/// E.g. `#[default]`, `#[rustfmt::skip]`.
NonMacroAttr, NonMacroAttr,
/// A token-based derive macro. /// A token-based derive macro.
@@ -1189,6 +1190,8 @@ pub struct ExtCtxt<'a> {
/// in the AST, but insert it here so that we know /// in the AST, but insert it here so that we know
/// not to expand it again. /// not to expand it again.
pub(super) expanded_inert_attrs: MarkedAttrs, pub(super) expanded_inert_attrs: MarkedAttrs,
/// `-Zmacro-stats` data.
pub macro_stats: FxHashMap<(Symbol, MacroKind), crate::stats::MacroStat>, // njn: quals
} }
impl<'a> ExtCtxt<'a> { impl<'a> ExtCtxt<'a> {
@@ -1218,6 +1221,7 @@ impl<'a> ExtCtxt<'a> {
expansions: FxIndexMap::default(), expansions: FxIndexMap::default(),
expanded_inert_attrs: MarkedAttrs::new(), expanded_inert_attrs: MarkedAttrs::new(),
buffered_early_lint: vec![], buffered_early_lint: vec![],
macro_stats: Default::default(),
} }
} }

View File

@@ -42,13 +42,22 @@ use crate::module::{
DirOwnership, ParsedExternalMod, mod_dir_path, mod_file_path_from_attr, parse_external_mod, DirOwnership, ParsedExternalMod, mod_dir_path, mod_file_path_from_attr, parse_external_mod,
}; };
use crate::placeholders::{PlaceholderExpander, placeholder}; use crate::placeholders::{PlaceholderExpander, placeholder};
use crate::stats::*;
macro_rules! ast_fragments { macro_rules! ast_fragments {
( (
$($Kind:ident($AstTy:ty) { $($Kind:ident($AstTy:ty) {
$kind_name:expr; $kind_name:expr;
$(one fn $mut_visit_ast:ident; fn $visit_ast:ident;)? $(one
$(many fn $flat_map_ast_elt:ident; fn $visit_ast_elt:ident($($args:tt)*);)? fn $mut_visit_ast:ident;
fn $visit_ast:ident;
fn $ast_to_string:path;
)?
$(many
fn $flat_map_ast_elt:ident;
fn $visit_ast_elt:ident($($args:tt)*);
fn $ast_to_string_elt:path;
)?
fn $make_ast:ident; fn $make_ast:ident;
})* })*
) => { ) => {
@@ -61,7 +70,7 @@ macro_rules! ast_fragments {
} }
/// "Discriminant" of an AST fragment. /// "Discriminant" of an AST fragment.
#[derive(Copy, Clone, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum AstFragmentKind { pub enum AstFragmentKind {
OptExpr, OptExpr,
MethodReceiverExpr, MethodReceiverExpr,
@@ -151,6 +160,21 @@ macro_rules! ast_fragments {
} }
V::Result::output() V::Result::output()
} }
pub(crate) fn to_string(&self) -> String {
match self {
AstFragment::OptExpr(Some(expr)) => pprust::expr_to_string(expr),
AstFragment::OptExpr(None) => unreachable!(),
AstFragment::MethodReceiverExpr(expr) => pprust::expr_to_string(expr),
$($(AstFragment::$Kind(ast) => $ast_to_string(ast),)?)*
$($(
AstFragment::$Kind(ast) => {
// The closure unwraps a `P` if present, or does nothing otherwise.
elems_to_string(&*ast, |ast| $ast_to_string_elt(&*ast))
}
)?)*
}
}
} }
impl<'a> MacResult for crate::mbe::macro_rules::ParserAnyMacro<'a> { impl<'a> MacResult for crate::mbe::macro_rules::ParserAnyMacro<'a> {
@@ -163,76 +187,98 @@ macro_rules! ast_fragments {
} }
ast_fragments! { ast_fragments! {
Expr(P<ast::Expr>) { "expression"; one fn visit_expr; fn visit_expr; fn make_expr; } Expr(P<ast::Expr>) {
Pat(P<ast::Pat>) { "pattern"; one fn visit_pat; fn visit_pat; fn make_pat; } "expression";
Ty(P<ast::Ty>) { "type"; one fn visit_ty; fn visit_ty; fn make_ty; } one fn visit_expr; fn visit_expr; fn pprust::expr_to_string;
fn make_expr;
}
Pat(P<ast::Pat>) {
"pattern";
one fn visit_pat; fn visit_pat; fn pprust::pat_to_string;
fn make_pat;
}
Ty(P<ast::Ty>) {
"type";
one fn visit_ty; fn visit_ty; fn pprust::ty_to_string;
fn make_ty;
}
Stmts(SmallVec<[ast::Stmt; 1]>) { Stmts(SmallVec<[ast::Stmt; 1]>) {
"statement"; many fn flat_map_stmt; fn visit_stmt(); fn make_stmts; "statement";
many fn flat_map_stmt; fn visit_stmt(); fn pprust::stmt_to_string;
fn make_stmts;
} }
Items(SmallVec<[P<ast::Item>; 1]>) { Items(SmallVec<[P<ast::Item>; 1]>) {
"item"; many fn flat_map_item; fn visit_item(); fn make_items; "item";
many fn flat_map_item; fn visit_item(); fn pprust::item_to_string;
fn make_items;
} }
TraitItems(SmallVec<[P<ast::AssocItem>; 1]>) { TraitItems(SmallVec<[P<ast::AssocItem>; 1]>) {
"trait item"; "trait item";
many fn flat_map_assoc_item; many fn flat_map_assoc_item; fn visit_assoc_item(AssocCtxt::Trait);
fn visit_assoc_item(AssocCtxt::Trait); fn pprust::assoc_item_to_string;
fn make_trait_items; fn make_trait_items;
} }
ImplItems(SmallVec<[P<ast::AssocItem>; 1]>) { ImplItems(SmallVec<[P<ast::AssocItem>; 1]>) {
"impl item"; "impl item";
many fn flat_map_assoc_item; many fn flat_map_assoc_item; fn visit_assoc_item(AssocCtxt::Impl { of_trait: false });
fn visit_assoc_item(AssocCtxt::Impl { of_trait: false }); fn pprust::assoc_item_to_string;
fn make_impl_items; fn make_impl_items;
} }
TraitImplItems(SmallVec<[P<ast::AssocItem>; 1]>) { TraitImplItems(SmallVec<[P<ast::AssocItem>; 1]>) {
"impl item"; "impl item";
many fn flat_map_assoc_item; many fn flat_map_assoc_item; fn visit_assoc_item(AssocCtxt::Impl { of_trait: true });
fn visit_assoc_item(AssocCtxt::Impl { of_trait: true }); fn pprust::assoc_item_to_string;
fn make_trait_impl_items; fn make_trait_impl_items;
} }
ForeignItems(SmallVec<[P<ast::ForeignItem>; 1]>) { ForeignItems(SmallVec<[P<ast::ForeignItem>; 1]>) {
"foreign item"; "foreign item";
many fn flat_map_foreign_item; many fn flat_map_foreign_item; fn visit_foreign_item(); fn pprust::foreign_item_to_string;
fn visit_foreign_item();
fn make_foreign_items; fn make_foreign_items;
} }
Arms(SmallVec<[ast::Arm; 1]>) { Arms(SmallVec<[ast::Arm; 1]>) {
"match arm"; many fn flat_map_arm; fn visit_arm(); fn make_arms; "match arm";
many fn flat_map_arm; fn visit_arm(); fn unreachable_to_string;
fn make_arms;
} }
ExprFields(SmallVec<[ast::ExprField; 1]>) { ExprFields(SmallVec<[ast::ExprField; 1]>) {
"field expression"; many fn flat_map_expr_field; fn visit_expr_field(); fn make_expr_fields; "field expression";
many fn flat_map_expr_field; fn visit_expr_field(); fn unreachable_to_string;
fn make_expr_fields;
} }
PatFields(SmallVec<[ast::PatField; 1]>) { PatFields(SmallVec<[ast::PatField; 1]>) {
"field pattern"; "field pattern";
many fn flat_map_pat_field; many fn flat_map_pat_field; fn visit_pat_field(); fn unreachable_to_string;
fn visit_pat_field();
fn make_pat_fields; fn make_pat_fields;
} }
GenericParams(SmallVec<[ast::GenericParam; 1]>) { GenericParams(SmallVec<[ast::GenericParam; 1]>) {
"generic parameter"; "generic parameter";
many fn flat_map_generic_param; many fn flat_map_generic_param; fn visit_generic_param(); fn unreachable_to_string;
fn visit_generic_param();
fn make_generic_params; fn make_generic_params;
} }
Params(SmallVec<[ast::Param; 1]>) { Params(SmallVec<[ast::Param; 1]>) {
"function parameter"; many fn flat_map_param; fn visit_param(); fn make_params; "function parameter";
many fn flat_map_param; fn visit_param(); fn unreachable_to_string;
fn make_params;
} }
FieldDefs(SmallVec<[ast::FieldDef; 1]>) { FieldDefs(SmallVec<[ast::FieldDef; 1]>) {
"field"; "field";
many fn flat_map_field_def; many fn flat_map_field_def; fn visit_field_def(); fn unreachable_to_string;
fn visit_field_def();
fn make_field_defs; fn make_field_defs;
} }
Variants(SmallVec<[ast::Variant; 1]>) { Variants(SmallVec<[ast::Variant; 1]>) {
"variant"; many fn flat_map_variant; fn visit_variant(); fn make_variants; "variant"; many fn flat_map_variant; fn visit_variant(); fn unreachable_to_string;
fn make_variants;
} }
WherePredicates(SmallVec<[ast::WherePredicate; 1]>) { WherePredicates(SmallVec<[ast::WherePredicate; 1]>) {
"where predicate"; "where predicate";
many fn flat_map_where_predicate; many fn flat_map_where_predicate; fn visit_where_predicate(); fn unreachable_to_string;
fn visit_where_predicate();
fn make_where_predicates; fn make_where_predicates;
} }
Crate(ast::Crate) { "crate"; one fn visit_crate; fn visit_crate; fn make_crate; } Crate(ast::Crate) {
"crate";
one fn visit_crate; fn visit_crate; fn unreachable_to_string;
fn make_crate;
}
} }
pub enum SupportsMacroExpansion { pub enum SupportsMacroExpansion {
@@ -270,7 +316,7 @@ impl AstFragmentKind {
} }
} }
fn expect_from_annotatables<I: IntoIterator<Item = Annotatable>>( pub(crate) fn expect_from_annotatables<I: IntoIterator<Item = Annotatable>>(
self, self,
items: I, items: I,
) -> AstFragment { ) -> AstFragment {
@@ -686,13 +732,26 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
return ExpandResult::Ready(invoc.fragment_kind.dummy(invoc.span(), guar)); return ExpandResult::Ready(invoc.fragment_kind.dummy(invoc.span(), guar));
} }
let macro_stats = self.cx.sess.opts.unstable_opts.macro_stats;
let (fragment_kind, span) = (invoc.fragment_kind, invoc.span()); let (fragment_kind, span) = (invoc.fragment_kind, invoc.span());
ExpandResult::Ready(match invoc.kind { ExpandResult::Ready(match invoc.kind {
InvocationKind::Bang { mac, span } => match ext { InvocationKind::Bang { mac, span } => match ext {
SyntaxExtensionKind::Bang(expander) => { SyntaxExtensionKind::Bang(expander) => {
match expander.expand(self.cx, span, mac.args.tokens.clone()) { match expander.expand(self.cx, span, mac.args.tokens.clone()) {
Ok(tok_result) => { Ok(tok_result) => {
self.parse_ast_fragment(tok_result, fragment_kind, &mac.path, span) let fragment =
self.parse_ast_fragment(tok_result, fragment_kind, &mac.path, span);
if macro_stats {
update_bang_macro_stats(
self.cx,
fragment_kind,
span,
mac,
&fragment,
);
}
fragment
} }
Err(guar) => return ExpandResult::Ready(fragment_kind.dummy(span, guar)), Err(guar) => return ExpandResult::Ready(fragment_kind.dummy(span, guar)),
} }
@@ -708,13 +767,15 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
}); });
} }
}; };
let result = if let Some(result) = fragment_kind.make_from(tok_result) { if let Some(fragment) = fragment_kind.make_from(tok_result) {
result if macro_stats {
update_bang_macro_stats(self.cx, fragment_kind, span, mac, &fragment);
}
fragment
} else { } else {
let guar = self.error_wrong_fragment_kind(fragment_kind, &mac, span); let guar = self.error_wrong_fragment_kind(fragment_kind, &mac, span);
fragment_kind.dummy(span, guar) fragment_kind.dummy(span, guar)
}; }
result
} }
_ => unreachable!(), _ => unreachable!(),
}, },
@@ -746,24 +807,39 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
} }
_ => item.to_tokens(), _ => item.to_tokens(),
}; };
let attr_item = attr.unwrap_normal_item(); let attr_item = attr.get_normal_item();
if let AttrArgs::Eq { .. } = attr_item.args { if let AttrArgs::Eq { .. } = attr_item.args {
self.cx.dcx().emit_err(UnsupportedKeyValue { span }); self.cx.dcx().emit_err(UnsupportedKeyValue { span });
} }
let inner_tokens = attr_item.args.inner_tokens(); let inner_tokens = attr_item.args.inner_tokens();
match expander.expand(self.cx, span, inner_tokens, tokens) { match expander.expand(self.cx, span, inner_tokens, tokens) {
Ok(tok_result) => self.parse_ast_fragment( Ok(tok_result) => {
let fragment = self.parse_ast_fragment(
tok_result, tok_result,
fragment_kind, fragment_kind,
&attr_item.path, &attr_item.path,
span, span,
), );
if macro_stats {
update_attr_macro_stats(
self.cx,
fragment_kind,
span,
&attr_item.path,
&attr,
item,
&fragment,
);
}
fragment
}
Err(guar) => return ExpandResult::Ready(fragment_kind.dummy(span, guar)), Err(guar) => return ExpandResult::Ready(fragment_kind.dummy(span, guar)),
} }
} }
SyntaxExtensionKind::LegacyAttr(expander) => { SyntaxExtensionKind::LegacyAttr(expander) => {
match validate_attr::parse_meta(&self.cx.sess.psess, &attr) { match validate_attr::parse_meta(&self.cx.sess.psess, &attr) {
Ok(meta) => { Ok(meta) => {
let item_clone = macro_stats.then(|| item.clone());
let items = match expander.expand(self.cx, span, &meta, item, false) { let items = match expander.expand(self.cx, span, &meta, item, false) {
ExpandResult::Ready(items) => items, ExpandResult::Ready(items) => items,
ExpandResult::Retry(item) => { ExpandResult::Retry(item) => {
@@ -782,7 +858,19 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
let guar = self.cx.dcx().emit_err(RemoveExprNotSupported { span }); let guar = self.cx.dcx().emit_err(RemoveExprNotSupported { span });
fragment_kind.dummy(span, guar) fragment_kind.dummy(span, guar)
} else { } else {
fragment_kind.expect_from_annotatables(items) let fragment = fragment_kind.expect_from_annotatables(items);
if macro_stats {
update_attr_macro_stats(
self.cx,
fragment_kind,
span,
&meta.path,
&attr,
item_clone.unwrap(),
&fragment,
);
}
fragment
} }
} }
Err(err) => { Err(err) => {
@@ -792,6 +880,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
} }
} }
SyntaxExtensionKind::NonMacroAttr => { SyntaxExtensionKind::NonMacroAttr => {
// `-Zmacro-stats` ignores these because they don't do any real expansion.
self.cx.expanded_inert_attrs.mark(&attr); self.cx.expanded_inert_attrs.mark(&attr);
item.visit_attrs(|attrs| attrs.insert(pos, attr)); item.visit_attrs(|attrs| attrs.insert(pos, attr));
fragment_kind.expect_from_annotatables(iter::once(item)) fragment_kind.expect_from_annotatables(iter::once(item))
@@ -822,7 +911,17 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
}); });
} }
}; };
fragment_kind.expect_from_annotatables(items) let fragment = fragment_kind.expect_from_annotatables(items);
if macro_stats {
update_derive_macro_stats(
self.cx,
fragment_kind,
span,
&meta.path,
&fragment,
);
}
fragment
} }
_ => unreachable!(), _ => unreachable!(),
}, },
@@ -852,6 +951,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
let single_delegations = build_single_delegations::<Node>( let single_delegations = build_single_delegations::<Node>(
self.cx, deleg, &item, &suffixes, item.span, true, self.cx, deleg, &item, &suffixes, item.span, true,
); );
// `-Zmacro-stats` ignores these because they don't seem important.
fragment_kind.expect_from_annotatables( fragment_kind.expect_from_annotatables(
single_delegations single_delegations
.map(|item| Annotatable::AssocItem(P(item), AssocCtxt::Impl { of_trait })), .map(|item| Annotatable::AssocItem(P(item), AssocCtxt::Impl { of_trait })),

View File

@@ -20,6 +20,7 @@ mod errors;
mod mbe; mod mbe;
mod placeholders; mod placeholders;
mod proc_macro_server; mod proc_macro_server;
mod stats;
pub use mbe::macro_rules::compile_declarative_macro; pub use mbe::macro_rules::compile_declarative_macro;
pub mod base; pub mod base;

View File

@@ -0,0 +1,171 @@
use std::iter;
use rustc_ast::ptr::P;
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,
/// Net increase in number of lines of code (when pretty-printed), i.e.
/// `lines(output) - lines(invocation)`. Can be negative because a macro
/// output may be smaller than the invocation.
pub lines: isize,
/// Net increase in number of lines of code (when pretty-printed), i.e.
/// `bytes(output) - bytes(invocation)`. Can be negative because a macro
/// output may be smaller than the invocation.
pub bytes: isize,
}
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: P<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,
) {
fn lines_and_bytes(s: &str) -> (usize, usize) {
(s.trim_end().split('\n').count(), s.len())
}
// 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 (in_l, in_b) = lines_and_bytes(input);
let (out_l, out_b) = lines_and_bytes(&output);
// 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\
-- ({in_l} lines, {in_b} bytes) --> ({out_l} lines, {out_b} 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 += out_l as isize - in_l as isize;
entry.bytes += out_b as isize - in_b as isize;
}

View File

@@ -8,9 +8,9 @@ use std::{env, fs, iter};
use rustc_ast as ast; use rustc_ast as ast;
use rustc_codegen_ssa::traits::CodegenBackend; use rustc_codegen_ssa::traits::CodegenBackend;
use rustc_data_structures::jobserver::Proxy; use rustc_data_structures::jobserver::Proxy;
use rustc_data_structures::parallel;
use rustc_data_structures::steal::Steal; use rustc_data_structures::steal::Steal;
use rustc_data_structures::sync::{AppendOnlyIndexVec, FreezeLock, WorkerLocal}; use rustc_data_structures::sync::{AppendOnlyIndexVec, FreezeLock, WorkerLocal};
use rustc_data_structures::{parallel, thousands};
use rustc_expand::base::{ExtCtxt, LintStoreExpand}; use rustc_expand::base::{ExtCtxt, LintStoreExpand};
use rustc_feature::Features; use rustc_feature::Features;
use rustc_fs_util::try_canonicalize; use rustc_fs_util::try_canonicalize;
@@ -35,7 +35,8 @@ use rustc_session::parse::feature_err;
use rustc_session::search_paths::PathKind; use rustc_session::search_paths::PathKind;
use rustc_session::{Limit, Session}; use rustc_session::{Limit, Session};
use rustc_span::{ use rustc_span::{
DUMMY_SP, ErrorGuaranteed, FileName, SourceFileHash, SourceFileHashAlgorithm, Span, Symbol, sym, DUMMY_SP, ErrorGuaranteed, ExpnKind, FileName, SourceFileHash, SourceFileHashAlgorithm, Span,
Symbol, sym,
}; };
use rustc_target::spec::PanicStrategy; use rustc_target::spec::PanicStrategy;
use rustc_trait_selection::traits; use rustc_trait_selection::traits;
@@ -205,7 +206,7 @@ fn configure_and_expand(
// Expand macros now! // Expand macros now!
let krate = sess.time("expand_crate", || ecx.monotonic_expander().expand_crate(krate)); let krate = sess.time("expand_crate", || ecx.monotonic_expander().expand_crate(krate));
// The rest is error reporting // The rest is error reporting and stats
sess.psess.buffered_lints.with_lock(|buffered_lints: &mut Vec<BufferedEarlyLint>| { sess.psess.buffered_lints.with_lock(|buffered_lints: &mut Vec<BufferedEarlyLint>| {
buffered_lints.append(&mut ecx.buffered_early_lint); buffered_lints.append(&mut ecx.buffered_early_lint);
@@ -228,6 +229,10 @@ fn configure_and_expand(
} }
} }
if ecx.sess.opts.unstable_opts.macro_stats {
print_macro_stats(&ecx);
}
krate krate
}); });
@@ -288,6 +293,76 @@ fn configure_and_expand(
krate krate
} }
fn print_macro_stats(ecx: &ExtCtxt<'_>) {
use std::fmt::Write;
// No instability because we immediately sort the produced vector.
#[allow(rustc::potential_query_instability)]
let mut macro_stats: Vec<_> = ecx
.macro_stats
.iter()
.map(|((name, kind), stat)| {
// This gives the desired sort order: sort by bytes, then lines, etc.
(stat.bytes, stat.lines, stat.uses, name, *kind)
})
.collect();
macro_stats.sort_unstable();
macro_stats.reverse(); // bigger items first
let prefix = "macro-stats";
let name_w = 32;
let uses_w = 7;
let lines_w = 11;
let avg_lines_w = 11;
let bytes_w = 11;
let avg_bytes_w = 11;
let banner_w = name_w + uses_w + lines_w + avg_lines_w + bytes_w + avg_bytes_w;
// We write all the text into a string and print it with a single
// `eprint!`. This is an attempt to minimize interleaved text if multiple
// rustc processes are printing macro-stats at the same time (e.g. with
// `RUSTFLAGS='-Zmacro-stats' cargo build`). It still doesn't guarantee
// non-interleaving, though.
let mut s = String::new();
_ = writeln!(s, "{prefix} {}", "=".repeat(banner_w));
_ = writeln!(s, "{prefix} MACRO EXPANSION STATS: {}", ecx.ecfg.crate_name);
_ = writeln!(
s,
"{prefix} {:<name_w$}{:>uses_w$}{:>lines_w$}{:>avg_lines_w$}{:>bytes_w$}{:>avg_bytes_w$}",
"Macro Name", "Uses", "Lines", "Avg Lines", "Bytes", "Avg Bytes",
);
_ = writeln!(s, "{prefix} {}", "-".repeat(banner_w));
// It's helpful to print something when there are no entries, otherwise it
// might look like something went wrong.
if macro_stats.is_empty() {
_ = writeln!(s, "{prefix} (none)");
}
for (bytes, lines, uses, name, kind) in macro_stats {
let mut name = ExpnKind::Macro(kind, *name).descr();
let avg_lines = lines as f64 / uses as f64;
let avg_bytes = bytes as f64 / uses as f64;
if name.len() >= name_w {
// If the name is long, print it on a line by itself, then
// set the name to empty and print things normally, to show the
// stats on the next line.
_ = writeln!(s, "{prefix} {:<name_w$}", name);
name = String::new();
}
_ = writeln!(
s,
"{prefix} {:<name_w$}{:>uses_w$}{:>lines_w$}{:>avg_lines_w$}{:>bytes_w$}{:>avg_bytes_w$}",
name,
thousands::usize_with_underscores(uses),
thousands::isize_with_underscores(lines),
thousands::f64p1_with_underscores(avg_lines),
thousands::isize_with_underscores(bytes),
thousands::f64p1_with_underscores(avg_bytes),
);
}
_ = writeln!(s, "{prefix} {}", "=".repeat(banner_w));
eprint!("{s}");
}
fn early_lint_checks(tcx: TyCtxt<'_>, (): ()) { fn early_lint_checks(tcx: TyCtxt<'_>, (): ()) {
let sess = tcx.sess; let sess = tcx.sess;
let (resolver, krate) = &*tcx.resolver_for_lowering().borrow(); let (resolver, krate) = &*tcx.resolver_for_lowering().borrow();

View File

@@ -709,6 +709,7 @@ fn test_unstable_options_tracking_hash() {
untracked!(llvm_time_trace, true); untracked!(llvm_time_trace, true);
untracked!(ls, vec!["all".to_owned()]); untracked!(ls, vec!["all".to_owned()]);
untracked!(macro_backtrace, true); untracked!(macro_backtrace, true);
untracked!(macro_stats, true);
untracked!(meta_stats, true); untracked!(meta_stats, true);
untracked!(mir_include_spans, MirIncludeSpans::On); untracked!(mir_include_spans, MirIncludeSpans::On);
untracked!(nll_facts, true); untracked!(nll_facts, true);

View File

@@ -10,7 +10,7 @@ use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
use rustc_data_structures::memmap::{Mmap, MmapMut}; use rustc_data_structures::memmap::{Mmap, MmapMut};
use rustc_data_structures::sync::{join, par_for_each_in}; use rustc_data_structures::sync::{join, par_for_each_in};
use rustc_data_structures::temp_dir::MaybeTempDir; use rustc_data_structures::temp_dir::MaybeTempDir;
use rustc_data_structures::thousands::format_with_underscores; use rustc_data_structures::thousands::usize_with_underscores;
use rustc_feature::Features; use rustc_feature::Features;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::def_id::{CRATE_DEF_ID, CRATE_DEF_INDEX, LOCAL_CRATE, LocalDefId, LocalDefIdSet}; use rustc_hir::def_id::{CRATE_DEF_ID, CRATE_DEF_INDEX, LOCAL_CRATE, LocalDefId, LocalDefIdSet};
@@ -789,7 +789,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
"{} {:<23}{:>10} ({:4.1}%)", "{} {:<23}{:>10} ({:4.1}%)",
prefix, prefix,
label, label,
format_with_underscores(size), usize_with_underscores(size),
perc(size) perc(size)
); );
} }
@@ -798,7 +798,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
"{} {:<23}{:>10} (of which {:.1}% are zero bytes)", "{} {:<23}{:>10} (of which {:.1}% are zero bytes)",
prefix, prefix,
"Total", "Total",
format_with_underscores(total_bytes), usize_with_underscores(total_bytes),
perc(zero_bytes) perc(zero_bytes)
); );
eprintln!("{prefix}"); eprintln!("{prefix}");

View File

@@ -5,7 +5,7 @@
use rustc_ast::visit::BoundKind; use rustc_ast::visit::BoundKind;
use rustc_ast::{self as ast, NodeId, visit as ast_visit}; use rustc_ast::{self as ast, NodeId, visit as ast_visit};
use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_data_structures::thousands::format_with_underscores; use rustc_data_structures::thousands::usize_with_underscores;
use rustc_hir::{self as hir, AmbigArg, HirId, intravisit as hir_visit}; use rustc_hir::{self as hir, AmbigArg, HirId, intravisit as hir_visit};
use rustc_middle::ty::TyCtxt; use rustc_middle::ty::TyCtxt;
use rustc_span::Span; use rustc_span::Span;
@@ -140,10 +140,10 @@ impl<'k> StatCollector<'k> {
"{} {:<18}{:>10} ({:4.1}%){:>14}{:>14}", "{} {:<18}{:>10} ({:4.1}%){:>14}{:>14}",
prefix, prefix,
label, label,
format_with_underscores(size), usize_with_underscores(size),
percent(size, total_size), percent(size, total_size),
format_with_underscores(node.stats.count), usize_with_underscores(node.stats.count),
format_with_underscores(node.stats.size) usize_with_underscores(node.stats.size)
); );
if !node.subnodes.is_empty() { if !node.subnodes.is_empty() {
// We will soon sort, so the initial order does not matter. // We will soon sort, so the initial order does not matter.
@@ -159,9 +159,9 @@ impl<'k> StatCollector<'k> {
"{} - {:<18}{:>10} ({:4.1}%){:>14}", "{} - {:<18}{:>10} ({:4.1}%){:>14}",
prefix, prefix,
label, label,
format_with_underscores(size), usize_with_underscores(size),
percent(size, total_size), percent(size, total_size),
format_with_underscores(subnode.count), usize_with_underscores(subnode.count),
); );
} }
} }
@@ -171,8 +171,8 @@ impl<'k> StatCollector<'k> {
"{} {:<18}{:>10} {:>14}", "{} {:<18}{:>10} {:>14}",
prefix, prefix,
"Total", "Total",
format_with_underscores(total_size), usize_with_underscores(total_size),
format_with_underscores(total_count), usize_with_underscores(total_count),
); );
eprintln!("{prefix}"); eprintln!("{prefix}");
} }

View File

@@ -2305,6 +2305,8 @@ options! {
(space separated)"), (space separated)"),
macro_backtrace: bool = (false, parse_bool, [UNTRACKED], macro_backtrace: bool = (false, parse_bool, [UNTRACKED],
"show macro backtraces (default: no)"), "show macro backtraces (default: no)"),
macro_stats: bool = (false, parse_bool, [UNTRACKED],
"print some statistics about macro expansions (default: no)"),
maximal_hir_to_mir_coverage: bool = (false, parse_bool, [TRACKED], maximal_hir_to_mir_coverage: bool = (false, parse_bool, [TRACKED],
"save as much information as possible about the correspondence between MIR and HIR \ "save as much information as possible about the correspondence between MIR and HIR \
as source scopes (default: no)"), as source scopes (default: no)"),

View File

@@ -1135,7 +1135,7 @@ impl ExpnKind {
} }
/// The kind of macro invocation or definition. /// The kind of macro invocation or definition.
#[derive(Clone, Copy, PartialEq, Eq, Encodable, Decodable, Hash, Debug)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Encodable, Decodable, Hash, Debug)]
#[derive(HashStable_Generic)] #[derive(HashStable_Generic)]
pub enum MacroKind { pub enum MacroKind {
/// A bang macro `foo!()`. /// A bang macro `foo!()`.

View File

@@ -2288,6 +2288,7 @@ symbols! {
usize_legacy_fn_max_value, usize_legacy_fn_max_value,
usize_legacy_fn_min_value, usize_legacy_fn_min_value,
usize_legacy_mod, usize_legacy_mod,
v1,
v8plus, v8plus,
va_arg, va_arg,
va_copy, va_copy,

View File

@@ -0,0 +1,24 @@
# `macro-stats`
This feature is perma-unstable and has no tracking issue.
----
Some macros, especially procedural macros, can generate a surprising amount of
code, which can slow down compile times. This is hard to detect because the
generated code is normally invisible to the programmer.
This flag helps identify such cases. When enabled, the compiler measures the
effect on code size of all used macros and prints a table summarizing that
effect. For each distinct macro, it counts how many times it is used, and the
net effect on code size (in terms of lines of code, and bytes of code). The
code size evaluation uses the compiler's internal pretty-printing, and so will
be independent of the formatting in the original code.
Note that the net effect of a macro may be negative. E.g. the `cfg!` and
`#[test]` macros often strip out code.
If a macro is identified as causing a large increase in code size, it is worth
using `cargo expand` to inspect the post-expansion code, which includes the
code produced by all macros. It may be possible to optimize the macro to
produce smaller code, or it may be possible to avoid using it altogether.

View File

@@ -0,0 +1,3 @@
fn zzz(x: u32) -> u32 {
x
}

View File

@@ -0,0 +1,130 @@
//@ check-pass
//@ compile-flags: -Zmacro-stats
#[test]
fn test_foo() {
let what = "this";
let how = "completely";
let when = "immediately";
println!("{what} disappears {how} and {when}");
}
#[rustfmt::skip] // non-macro attr, ignored by `-Zmacro-stats`
fn rustfmt_skip() {
// Nothing to see here.
}
#[derive(Default, Clone, Copy, Hash)]
enum E1 {
#[default] // non-macro attr, ignored by `-Zmacro-stats`
A,
B,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct E2 {
a: u32,
b: String,
c: Vec<bool>,
}
#[derive(Clone)] struct S0;
#[derive(Clone)] struct S1(u32);
#[derive(Clone)] struct S2(u32, u32);
#[derive(Clone)] struct S3(u32, u32, u32);
#[derive(Clone)] struct S4(u32, u32, u32, u32);
#[derive(Clone)] struct S5(u32, u32, u32, u32, u32);
macro_rules! u32 {
() => { u32 }
}
macro_rules! none {
() => { None }
}
fn opt(x: Option<u32>) {
match x {
Some(_) => {}
none!() => {} // AstFragmentKind::Pat
}
}
macro_rules! this_is_a_really_really_long_macro_name {
($t:ty) => {
fn f(_: $t) {}
}
}
this_is_a_really_really_long_macro_name!(u32!()); // AstFragmentKind::{Items,Ty}
macro_rules! trait_tys {
() => {
type A;
type B;
}
}
trait Tr {
trait_tys!(); // AstFragmentKind::TraitItems
}
macro_rules! impl_const { () => { const X: u32 = 0; } }
struct U;
impl U {
impl_const!(); // AstFragmentKind::ImplItems
}
macro_rules! trait_impl_tys {
() => {
type A = u32;
type B = bool;
}
}
struct Tr1;
impl Tr for Tr1 {
trait_impl_tys!(); // AstFragment::TraitImplItems
}
macro_rules! foreign_item {
() => { fn fc(a: u32) -> u32; }
}
extern "C" {
foreign_item!(); // AstFragment::ForeignItems
}
// Include macros are ignored by `-Zmacro-stats`.
mod includes {
mod z1 {
include!("auxiliary/include.rs");
}
mod z2 {
std::include!("auxiliary/include.rs");
}
const B1: &[u8] = include_bytes!("auxiliary/include.rs");
const B2: &[u8] = std::include_bytes!("auxiliary/include.rs");
const S1: &str = include_str!("auxiliary/include.rs");
const S2: &str = std::include_str!("auxiliary/include.rs");
}
fn main() {
macro_rules! n99 {
() => { 99 }
}
let x = n99!() + n99!(); // AstFragmentKind::Expr
macro_rules! p {
() => {
// blah
let x = 1;
let y = x;
let _ = y;
}
}
p!(); // AstFragmentKind::Stmts
macro_rules! q {
() => {};
($($x:ident),*) => { $( let $x: u32 = 12345; )* };
}
q!(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z);
}

View File

@@ -0,0 +1,26 @@
macro-stats ===================================================================================
macro-stats MACRO EXPANSION STATS: macro_stats
macro-stats Macro Name Uses Lines Avg Lines Bytes Avg Bytes
macro-stats -----------------------------------------------------------------------------------
macro-stats #[derive(Clone)] 8 56 7.0 1_660 207.5
macro-stats #[derive(PartialOrd)] 1 16 16.0 654 654.0
macro-stats #[derive(Hash)] 2 15 7.5 547 273.5
macro-stats #[derive(Ord)] 1 14 14.0 489 489.0
macro-stats q! 1 24 24.0 435 435.0
macro-stats #[derive(Default)] 2 14 7.0 367 183.5
macro-stats #[derive(Eq)] 1 10 10.0 312 312.0
macro-stats #[derive(Debug)] 1 7 7.0 261 261.0
macro-stats #[derive(PartialEq)] 1 8 8.0 247 247.0
macro-stats #[derive(Copy)] 1 1 1.0 46 46.0
macro-stats p! 1 2 2.0 28 28.0
macro-stats trait_impl_tys! 1 1 1.0 11 11.0
macro-stats foreign_item! 1 0 0.0 6 6.0
macro-stats impl_const! 1 0 0.0 4 4.0
macro-stats trait_tys! 1 1 1.0 3 3.0
macro-stats u32! 1 0 0.0 -3 -3.0
macro-stats none! 1 0 0.0 -3 -3.0
macro-stats n99! 2 0 0.0 -8 -4.0
macro-stats this_is_a_really_really_long_macro_name!
macro-stats 1 0 0.0 -30 -30.0
macro-stats #[test] 1 -6 -6.0 -158 -158.0
macro-stats ===================================================================================