From 8887b67ec5761a48ae61aa6847acbfaaa45eb81f Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 18 Jul 2019 07:45:44 -0700 Subject: [PATCH] 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! --- crates/cargo-test-macro/Cargo.toml | 4 -- crates/cargo-test-macro/src/lib.rs | 69 ++++++++++++++++++++++-------- 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/crates/cargo-test-macro/Cargo.toml b/crates/cargo-test-macro/Cargo.toml index 49ae63244..7f204278b 100644 --- a/crates/cargo-test-macro/Cargo.toml +++ b/crates/cargo-test-macro/Cargo.toml @@ -11,7 +11,3 @@ description = "Helper proc-macro for Cargo's testsuite." [lib] proc-macro = true - -[dependencies] -quote = "0.6" -syn = { version = "0.15", features = ["full"] } diff --git a/crates/cargo-test-macro/src/lib.rs b/crates/cargo-test-macro/src/lib.rs index badc3f082..678bb83c0 100644 --- a/crates/cargo-test-macro/src/lib.rs +++ b/crates/cargo-test-macro/src/lib.rs @@ -1,26 +1,59 @@ extern crate proc_macro; -use quote::{quote, ToTokens}; -use syn::{parse::Parser, *}; +use proc_macro::*; #[proc_macro_attribute] -pub fn cargo_test( - _attr: proc_macro::TokenStream, - item: proc_macro::TokenStream, -) -> proc_macro::TokenStream { - let mut fn_def = parse_macro_input!(item as ItemFn); +pub fn cargo_test(_attr: TokenStream, item: TokenStream) -> TokenStream { + let span = Span::call_site(); + let mut ret = TokenStream::new(); + ret.extend(Some(TokenTree::from(Punct::new('#', Spacing::Alone)))); + let test = TokenTree::from(Ident::new("test", span)); + ret.extend(Some(TokenTree::from(Group::new( + Delimiter::Bracket, + test.into(), + )))); - let attr = quote! { - #[test] - }; - fn_def - .attrs - .extend(Attribute::parse_outer.parse2(attr).unwrap()); + for token in item { + let group = match token { + TokenTree::Group(g) => { + if g.delimiter() == Delimiter::Brace { + g + } else { + ret.extend(Some(TokenTree::Group(g))); + continue; + } + } + other => { + ret.extend(Some(other)); + continue; + } + }; - let stmt = quote! { - let _test_guard = crate::support::paths::init_root(); - }; - fn_def.block.stmts.insert(0, parse2(stmt).unwrap()); + let mut new_body = vec![ + TokenTree::from(Ident::new("let", span)), + TokenTree::from(Ident::new("_test_guard", span)), + 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::(); + new_body.extend(group.stream()); + ret.extend(Some(TokenTree::from(Group::new( + group.delimiter(), + new_body, + )))); + } - fn_def.into_token_stream().into() + return ret; }