Optimize runtime of #[cargo_test_macro]

I've noticed recently that the incremental compile time for our test
suite has felt like it's increased quite a bit. I think one reason is
that everything has to go through `#[cargo_test_macro]` unconditionally
on all incremental builds, and wow do we have a lot of tests being
pumped through that macro.

Instrumenting the macro a little bit shows that we spend nearly 2.5
seconds on each compilation simply executing this macro (note that it's
in debug mode as well, not release since we typically don't execute
tests in release mode.

This commit instead drops the usage of `syn` and `quote` in favor of a
"raw procedural macro" which is much more optimized for just our use
case, even in debug mode. This drops the collective time spent in the
macro to 0.2 seconds, even in debug mode!
This commit is contained in:
Alex Crichton 2019-07-18 07:45:44 -07:00
parent 2b21fa6836
commit 8887b67ec5
2 changed files with 51 additions and 22 deletions

View File

@ -11,7 +11,3 @@ description = "Helper proc-macro for Cargo's testsuite."
[lib] [lib]
proc-macro = true proc-macro = true
[dependencies]
quote = "0.6"
syn = { version = "0.15", features = ["full"] }

View File

@ -1,26 +1,59 @@
extern crate proc_macro; extern crate proc_macro;
use quote::{quote, ToTokens}; use proc_macro::*;
use syn::{parse::Parser, *};
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn cargo_test( pub fn cargo_test(_attr: TokenStream, item: TokenStream) -> TokenStream {
_attr: proc_macro::TokenStream, let span = Span::call_site();
item: proc_macro::TokenStream, let mut ret = TokenStream::new();
) -> proc_macro::TokenStream { ret.extend(Some(TokenTree::from(Punct::new('#', Spacing::Alone))));
let mut fn_def = parse_macro_input!(item as ItemFn); let test = TokenTree::from(Ident::new("test", span));
ret.extend(Some(TokenTree::from(Group::new(
Delimiter::Bracket,
test.into(),
))));
let attr = quote! { for token in item {
#[test] let group = match token {
}; TokenTree::Group(g) => {
fn_def if g.delimiter() == Delimiter::Brace {
.attrs g
.extend(Attribute::parse_outer.parse2(attr).unwrap()); } else {
ret.extend(Some(TokenTree::Group(g)));
continue;
}
}
other => {
ret.extend(Some(other));
continue;
}
};
let stmt = quote! { let mut new_body = vec![
let _test_guard = crate::support::paths::init_root(); TokenTree::from(Ident::new("let", span)),
}; TokenTree::from(Ident::new("_test_guard", span)),
fn_def.block.stmts.insert(0, parse2(stmt).unwrap()); TokenTree::from(Punct::new('=', Spacing::Alone)),
TokenTree::from(Ident::new("crate", span)),
TokenTree::from(Punct::new(':', Spacing::Joint)),
TokenTree::from(Punct::new(':', Spacing::Alone)),
TokenTree::from(Ident::new("support", span)),
TokenTree::from(Punct::new(':', Spacing::Joint)),
TokenTree::from(Punct::new(':', Spacing::Alone)),
TokenTree::from(Ident::new("paths", span)),
TokenTree::from(Punct::new(':', Spacing::Joint)),
TokenTree::from(Punct::new(':', Spacing::Alone)),
TokenTree::from(Ident::new("init_root", span)),
TokenTree::from(Group::new(Delimiter::Parenthesis, TokenStream::new())),
TokenTree::from(Punct::new(';', Spacing::Alone)),
]
.into_iter()
.collect::<TokenStream>();
new_body.extend(group.stream());
ret.extend(Some(TokenTree::from(Group::new(
group.delimiter(),
new_body,
))));
}
fn_def.into_token_stream().into() return ret;
} }