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:
@@ -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 }
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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}"))
|
||||||
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) => {
|
||||||
tok_result,
|
let fragment = self.parse_ast_fragment(
|
||||||
fragment_kind,
|
tok_result,
|
||||||
&attr_item.path,
|
fragment_kind,
|
||||||
span,
|
&attr_item.path,
|
||||||
),
|
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 })),
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
171
compiler/rustc_expand/src/stats.rs
Normal file
171
compiler/rustc_expand/src/stats.rs
Normal 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;
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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}");
|
||||||
|
|||||||
@@ -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}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)"),
|
||||||
|
|||||||
@@ -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!()`.
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
24
src/doc/unstable-book/src/compiler-flags/macro-stats.md
Normal file
24
src/doc/unstable-book/src/compiler-flags/macro-stats.md
Normal 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.
|
||||||
3
tests/ui/stats/auxiliary/include.rs
Normal file
3
tests/ui/stats/auxiliary/include.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fn zzz(x: u32) -> u32 {
|
||||||
|
x
|
||||||
|
}
|
||||||
130
tests/ui/stats/macro-stats.rs
Normal file
130
tests/ui/stats/macro-stats.rs
Normal 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);
|
||||||
|
}
|
||||||
26
tests/ui/stats/macro-stats.stderr
Normal file
26
tests/ui/stats/macro-stats.stderr
Normal 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 ===================================================================================
|
||||||
Reference in New Issue
Block a user