1mod attr_options;
7mod tokens;
8
9use attr_options::{AttrOptions, Macro};
10use proc_macro::TokenStream;
11use proc_macro2::TokenStream as QuoteStream;
12use quote::{format_ident, quote};
13use syn::{ItemFn, ItemMod, parse_macro_input};
14
15fn pre_main_attrs() -> QuoteStream {
20 let elf_targets: QuoteStream = quote! {
21 target_os = "linux",
22 target_os = "android",
23 target_os = "dragonfly",
24 target_os = "freebsd",
25 target_os = "fuchsia",
26 target_os = "haiku",
27 target_os = "illumos",
28 target_os = "netbsd",
29 target_os = "openbsd"
30 };
31
32 let mach_o_targets: QuoteStream = quote! {
33 target_os = "macos",
34 target_os = "ios",
35 target_os = "tvos",
36 target_os = "watchos"
37 };
38
39 quote! {
40 #[used]
41 #[cfg_attr(
42 any(#elf_targets),
43 unsafe(link_section = ".init_array")
44 )]
45 #[cfg_attr(
46 any(#mach_o_targets),
47 unsafe(link_section = "__DATA,__mod_init_func")
48 )]
49 #[cfg_attr(target_os = "windows", unsafe(link_section = ".CRT$XCU"))]
50 }
51}
52
53fn unsupported_platform_check() -> QuoteStream {
55 quote! {
56 #[cfg(not(any(
57 target_os = "linux",
58 target_os = "android",
59 target_os = "dragonfly",
60 target_os = "freebsd",
61 target_os = "fuchsia",
62 target_os = "haiku",
63 target_os = "illumos",
64 target_os = "netbsd",
65 target_os = "openbsd",
66 target_os = "macos",
67 target_os = "ios",
68 target_os = "tvos",
69 target_os = "watchos",
70 target_os = "windows",
71 )))]
72 compile_error!("pbench: unsupported target OS for benchmark registration");
73 }
74}
75
76fn entry_meta_expr(private_mod: &QuoteStream, name_ident: &syn::Ident) -> QuoteStream {
81 quote! {
82 #private_mod::EntryMeta {
83 raw_name: stringify!(#name_ident),
84 module_path: module_path!(),
85 location: #private_mod::EntryLocation {
86 file: file!(),
87 line: line!(),
88 col: column!(),
89 },
90 }
91 }
92}
93
94#[proc_macro_attribute]
119pub fn bench(attr: TokenStream, item: TokenStream) -> TokenStream {
120 let input_fn: ItemFn = parse_macro_input!(item as ItemFn);
121 let fn_name: &syn::Ident = &input_fn.sig.ident;
122 let param_count: usize = input_fn.sig.inputs.len();
123
124 let ignore_attr: Option<syn::Path> = input_fn
126 .attrs
127 .iter()
128 .find(|a: &&syn::Attribute| a.path().is_ident("ignore"))
129 .map(|a: &syn::Attribute| a.path().clone());
130
131 let options: AttrOptions = match AttrOptions::parse(attr, Macro::Bench { param_count }) {
132 Ok(opts) => opts,
133 Err(err) => return err,
134 };
135
136 let private_mod: &QuoteStream = &options.private_mod;
137 let pre_main: QuoteStream = pre_main_attrs();
138 let platform_check: QuoteStream = unsupported_platform_check();
139 let meta_expr: QuoteStream = entry_meta_expr(private_mod, fn_name);
140
141 let static_name: syn::Ident =
143 format_ident!("__PBENCH_REGISTER_{}", fn_name.to_string().to_uppercase());
144
145 let registration: QuoteStream = if let Some(ref args_array) = options.args_array {
146 let args_stringified: Vec<QuoteStream> = args_array
152 .elems
153 .iter()
154 .map(|elem: &syn::Expr| quote! { stringify!(#elem) })
155 .collect();
156
157 quote! {
158 {
159 static ENTRY: #private_mod::GenericBenchEntry =
160 #private_mod::GenericBenchEntry {
161 meta: #meta_expr,
162 bench_fn: #fn_name,
163 args: &[#(#args_stringified),*],
164 };
165
166 static ANY_ENTRY: #private_mod::AnyBenchEntry =
167 #private_mod::AnyBenchEntry::Generic(&ENTRY);
168
169 static NODE: #private_mod::EntryList<
170 #private_mod::AnyBenchEntry,
171 > = #private_mod::EntryList::new(&ANY_ENTRY);
172
173 extern "C" fn push() {
174 #private_mod::BENCH_ENTRIES.push(&NODE);
175 }
176
177 #platform_check
178
179 #pre_main
180 static __PBENCH_PUSH: extern "C" fn() = push;
181 }
182 }
183 } else {
184 let bench_options_expr: QuoteStream = options.bench_options_fn(ignore_attr.as_ref());
186
187 let bench_fn_expr: QuoteStream = match param_count {
188 0 => quote! {
191 {
192 fn __pbench_wrap(
193 __b: &'_ #private_mod::Bencher<'_>,
194 ) {
195 __b.bench_refs(#fn_name);
196 }
197
198 __pbench_wrap
199 }
200 },
201
202 1 => quote! { #fn_name },
205
206 _ => {
207 return syn::Error::new_spanned(
208 &input_fn.sig,
209 "benchmark function must take 0 or 1 parameters \
210 (or use `args = [...]` for 2)",
211 )
212 .into_compile_error()
213 .into();
214 }
215 };
216
217 quote! {
218 {
219 static BENCH_ENTRY: #private_mod::BenchEntry =
220 #private_mod::BenchEntry {
221 meta: #meta_expr,
222 bench_fn: #bench_fn_expr,
223 options: #bench_options_expr,
224 };
225
226 static ANY_ENTRY: #private_mod::AnyBenchEntry =
227 #private_mod::AnyBenchEntry::Bench(&BENCH_ENTRY);
228
229 static NODE: #private_mod::EntryList<
230 #private_mod::AnyBenchEntry,
231 > = #private_mod::EntryList::new(&ANY_ENTRY);
232
233 extern "C" fn push() {
234 #private_mod::BENCH_ENTRIES.push(&NODE);
235 }
236
237 #platform_check
238
239 #pre_main
240 static __PBENCH_PUSH: extern "C" fn() = push;
241 }
242 }
243 };
244
245 let output: QuoteStream = quote! {
246 #input_fn
247
248 #[doc(hidden)]
249 #[allow(non_upper_case_globals)]
250 const #static_name: () = #registration;
251 };
252
253 output.into()
254}
255
256#[proc_macro_attribute]
273pub fn bench_group(attr: TokenStream, item: TokenStream) -> TokenStream {
274 let input_mod: ItemMod = parse_macro_input!(item as ItemMod);
275 let mod_name: &syn::Ident = &input_mod.ident;
276
277 let options: AttrOptions = match AttrOptions::parse(attr, Macro::Group) {
278 Ok(opts) => opts,
279 Err(err) => return err,
280 };
281
282 let private_mod: &QuoteStream = &options.private_mod;
283 let pre_main: QuoteStream = pre_main_attrs();
284 let platform_check: QuoteStream = unsupported_platform_check();
285 let meta_expr: QuoteStream = entry_meta_expr(private_mod, mod_name);
286 let bench_options_expr: QuoteStream = options.bench_options_fn(None);
287
288 let static_name: syn::Ident =
289 format_ident!("__PBENCH_GROUP_{}", mod_name.to_string().to_uppercase());
290
291 let registration: QuoteStream = quote! {
292 {
293 static GROUP_ENTRY: #private_mod::GroupEntry =
294 #private_mod::GroupEntry {
295 meta: #meta_expr,
296 options: #bench_options_expr,
297 };
298
299 static ANY_ENTRY: #private_mod::AnyBenchEntry =
300 #private_mod::AnyBenchEntry::Group(&GROUP_ENTRY);
301
302 static NODE: #private_mod::EntryList<
303 #private_mod::AnyBenchEntry,
304 > = #private_mod::EntryList::new(&ANY_ENTRY);
305
306 extern "C" fn push() {
307 #private_mod::BENCH_ENTRIES.push(&NODE);
308 }
309
310 #platform_check
311
312 #pre_main
313 static __PBENCH_PUSH: extern "C" fn() = push;
314 }
315 };
316
317 let output: QuoteStream = quote! {
318 #input_mod
319
320 #[doc(hidden)]
321 #[allow(non_upper_case_globals)]
322 const #static_name: () = #registration;
323 };
324
325 output.into()
326}