1use proc_macro::TokenStream;
2use quote::{quote, ToTokens};
3use syn::{LitStr, Token};
4
5struct MainArgs {
7 name: Option<String>,
8 benchmarks: Vec<syn::Path>,
9}
10
11impl syn::parse::Parse for MainArgs {
12 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
13 let name = if input.peek(LitStr) {
14 Some(input.parse::<LitStr>()?.value())
15 } else {
16 None
17 };
18
19 if name.is_some() && !input.is_empty() {
20 input.parse::<Token![,]>()?;
21 }
22
23 let mut benchmarks = Vec::new();
24 while !input.is_empty() {
25 let benchmark = input.parse::<syn::Path>()?;
26 benchmarks.push(benchmark);
27 if input.is_empty() {
28 break;
29 }
30 input.parse::<syn::Token![,]>()?;
31 }
32
33 Ok(MainArgs { name, benchmarks })
34 }
35}
36
37#[proc_macro]
38pub fn main(item: TokenStream) -> TokenStream {
39 let main_args = match syn::parse::<MainArgs>(item) {
40 Ok(args) => args,
41 Err(err) => return err.to_compile_error().into(),
42 };
43
44 expand_main(main_args)
45 .unwrap_or_else(|err| err.to_compile_error())
46 .into()
47}
48
49fn expand_main(MainArgs { name, benchmarks }: MainArgs) -> syn::Result<proc_macro2::TokenStream> {
50 let mut bench_runs = Vec::new();
51 for mut bench in benchmarks {
52 let bench_struct_path = {
53 let last_segment = bench.segments.last().unwrap();
55 bench.segments.last_mut().unwrap().ident = syn::Ident::new(
56 &format!("Benchmark_{}", last_segment.ident),
57 last_segment.ident.span(),
58 );
59 bench
60 };
61
62 bench_runs.push(quote! {
63 {
64 let name = #bench_struct_path::name();
65 if let Some(params) = #bench_struct_path::params() {
66 let (param_names, params): (Vec<String>, Vec<_>) = params.into_iter().unzip();
67 let params = param_names.iter().map(|n| n.as_str()).zip(params.into_iter()).collect::<Vec<_>>();
68 bench.benchmark_with(&name, params, |b, p| {
69 #bench_struct_path::run(b, p.clone());
70 });
71 } else {
72 bench.benchmark(&name, None, |b| {
73 #bench_struct_path::run_without_param(b);
74 });
75 }
76 }
77 });
78 }
79
80 let name = match name {
81 Some(name) => syn::LitStr::new(&name, proc_macro2::Span::call_site()).into_token_stream(),
82 None => quote! {
83 core::module_path!()
84 },
85 };
86
87 Ok(quote! {
88 fn main() {
89 extern crate benchy as _benchy;
90 use _benchy::BenchmarkFn;
91
92 let mut bench = _benchy::Benchmark::from_env(#name);
93
94 #(#bench_runs)*
95
96 bench.output();
97 }
98 })
99}
100
101struct BenchMarkArgs {
107 name: Option<String>,
108 params: Option<syn::Expr>,
109}
110
111impl syn::parse::Parse for BenchMarkArgs {
112 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
113 let name = match input.parse::<syn::LitStr>() {
114 Ok(lit) => Some(lit.value()),
115 Err(_) => None,
116 };
117
118 let params = if !input.is_empty() {
119 if name.is_some() {
120 input.parse::<syn::Token![,]>()?;
121 }
122
123 Some(input.parse::<syn::Expr>()?)
124 } else {
125 None
126 };
127
128 if !input.is_empty() {
129 return Err(input.error("Unexpected input"));
130 }
131
132 Ok(BenchMarkArgs { name, params })
133 }
134}
135
136#[proc_macro_attribute]
137pub fn benchmark(attr: TokenStream, item: TokenStream) -> TokenStream {
138 expand_benchmark(attr, item)
139 .unwrap_or_else(|err| err.to_compile_error())
140 .into()
141}
142
143fn expand_benchmark(attr: TokenStream, item: TokenStream) -> syn::Result<proc_macro2::TokenStream> {
144 let attr = proc_macro2::TokenStream::from(attr);
145 let BenchMarkArgs { name, params } = syn::parse::<BenchMarkArgs>(attr.into())?;
146 let item_fn = syn::parse::<syn::ItemFn>(item)?;
147 let fn_vis = &item_fn.vis;
148 let fn_name = &item_fn.sig.ident;
149
150 let (param_type, takes_param) = match item_fn.sig.inputs.iter().nth(1) {
151 None => (quote! { () }, false),
152 Some(syn::FnArg::Typed(pat_type)) => (pat_type.ty.to_token_stream(), true),
153 Some(_) => {
154 return Err(syn::Error::new_spanned(
155 item_fn.sig.inputs,
156 "Expected second parameter to be a typed pattern",
157 ));
158 }
159 };
160
161 let bench_struct_name = syn::Ident::new(&format!("Benchmark_{}", fn_name), fn_name.span());
162
163 let params = match takes_param {
164 true => {
165 quote! { Some(#params.into_iter().map(|(name, value)| (name.into(), value)).collect()) }
166 }
167 false => quote! { None },
168 };
169
170 let name_impl = match name {
171 Some(name) => quote! { #name.to_owned() },
172 None => quote! { stringify!(#fn_name).to_owned() },
173 };
174
175 let run_impl = match takes_param {
176 true => quote! {
177 fn run(b: &mut _benchy::BenchmarkRun, p: Self::ParamType) {
178 #fn_name(b, p);
179 }
180 },
181 false => quote! {
182 fn run_without_param(b: &mut _benchy::BenchmarkRun) {
183 #fn_name(b);
184 }
185 },
186 };
187
188 Ok(quote! {
189 #[allow(non_camel_case_types)]
190 #fn_vis struct #bench_struct_name;
191
192 #[allow(non_upper_case_globals)]
193 const _: () = {
194 extern crate benchy as _benchy;
195
196 #[automatically_derived]
197 impl _benchy::BenchmarkFn for #bench_struct_name {
198 type ParamType = #param_type;
199
200 fn name() -> String {
201 #name_impl
202 }
203
204 fn params() -> Option<Vec<(String, Self::ParamType)>> {
205 #params
206 }
207
208 #run_impl
209 }
210 };
211
212 #item_fn
213 })
214}