1use proc_macro::TokenStream;
7use quote::{quote, ToTokens};
8
9mod attr_options;
10mod tokens;
11
12use attr_options::*;
13use syn::{Expr, FnArg};
14
15#[derive(Clone, Copy)]
16enum Macro<'a> {
17 Bench { fn_sig: &'a syn::Signature },
18 BenchGroup,
19}
20
21impl Macro<'_> {
22 fn name(&self) -> &'static str {
23 match self {
24 Self::Bench { .. } => "bench",
25 Self::BenchGroup => "bench_group",
26 }
27 }
28}
29
30mod systems {
32 use super::*;
33
34 pub fn elf() -> proc_macro2::TokenStream {
35 quote! {
36 target_os = "android",
37 target_os = "dragonfly",
38 target_os = "freebsd",
39 target_os = "fuchsia",
40 target_os = "haiku",
41 target_os = "illumos",
42 target_os = "linux",
43 target_os = "netbsd",
44 target_os = "openbsd"
45 }
46 }
47
48 pub fn mach_o() -> proc_macro2::TokenStream {
49 quote! {
50 target_os = "ios",
51 target_os = "macos",
52 target_os = "tvos",
53 target_os = "watchos"
54 }
55 }
56}
57
58fn pre_main_attrs() -> proc_macro2::TokenStream {
61 let elf = systems::elf();
62 let mach_o = systems::mach_o();
63
64 quote! {
65 #[used]
66 #[cfg_attr(windows, link_section = ".CRT$XCU")]
67 #[cfg_attr(any(#elf), link_section = ".init_array")]
68 #[cfg_attr(any(#mach_o), link_section = "__DATA,__mod_init_func,mod_init_funcs")]
69 }
70}
71
72fn unsupported_error(attr_name: &str) -> proc_macro2::TokenStream {
73 let elf = systems::elf();
74 let mach_o = systems::mach_o();
75
76 let error = format!("Unsupported target OS for `#[divan::{attr_name}]`");
77
78 quote! {
79 #[cfg(not(any(windows, #elf, #mach_o)))]
80 ::std::compile_error!(#error);
81 }
82}
83
84#[proc_macro_attribute]
85pub fn bench(options: TokenStream, item: TokenStream) -> TokenStream {
86 let option_none = tokens::option_none();
87 let option_some = tokens::option_some();
88
89 let fn_item = item.clone();
90 let fn_item = syn::parse_macro_input!(fn_item as syn::ItemFn);
91 let fn_sig = &fn_item.sig;
92
93 let attr = Macro::Bench { fn_sig };
94 let attr_name = attr.name();
95
96 let options = match AttrOptions::parse(options, attr) {
97 Ok(options) => options,
98 Err(compile_error) => return compile_error,
99 };
100
101 let AttrOptions { private_mod, .. } = &options;
103
104 let fn_ident = &fn_sig.ident;
105 let fn_name = fn_ident.to_string();
106 let fn_name_pretty = fn_name.strip_prefix("r#").unwrap_or(&fn_name);
107
108 let ignore_attr_ident =
111 fn_item.attrs.iter().map(|attr| attr.meta.path()).find(|path| path.is_ident("ignore"));
112
113 let is_extern_abi = fn_sig.abi.is_some();
115
116 let fn_args = &fn_sig.inputs;
117
118 let type_param: Option<(usize, &syn::TypeParam)> = fn_sig
119 .generics
120 .params
121 .iter()
122 .enumerate()
123 .filter_map(|(i, param)| match param {
124 syn::GenericParam::Type(param) => Some((i, param)),
125 _ => None,
126 })
127 .next();
128
129 let const_param: Option<(usize, &syn::ConstParam)> = fn_sig
130 .generics
131 .params
132 .iter()
133 .enumerate()
134 .filter_map(|(i, param)| match param {
135 syn::GenericParam::Const(param) => Some((i, param)),
136 _ => None,
137 })
138 .next();
139
140 let is_type_before_const = match (type_param, const_param) {
141 (Some((t, _)), Some((c, _))) => t < c,
142 _ => false,
143 };
144
145 let static_ident = syn::Ident::new(
150 &format!("__DIVAN_BENCH_{}", fn_name_pretty.to_uppercase()),
151 fn_ident.span(),
152 );
153
154 let meta = entry_meta_expr(&fn_name, &options, ignore_attr_ident);
155
156 let bench_entry_runner = quote! { #private_mod::BenchEntryRunner };
157
158 let bench_args_global = if options.args_expr.is_some() {
160 quote! {
161 static __DIVAN_ARGS: #private_mod::BenchArgs = #private_mod::BenchArgs::new();
162 }
163 } else {
164 Default::default()
165 };
166
167 let last_arg_type = if options.args_expr.is_some() {
170 fn_args.last().map(|arg| match arg {
171 FnArg::Receiver(arg) => &*arg.ty,
172 FnArg::Typed(arg) => &*arg.ty,
173 })
174 } else {
175 None
176 };
177
178 let last_arg_type_tokens = last_arg_type
179 .map(|ty| match ty {
180 syn::Type::Reference(ty) if ty.lifetime.is_some() => {
184 let mut ty = ty.clone();
185 ty.lifetime = None;
186 ty.to_token_stream()
187 }
188
189 _ => ty.to_token_stream(),
190 })
191 .unwrap_or_default();
192
193 let arg_return_tokens = options
195 .args_expr
196 .as_ref()
197 .map(|args| match args {
198 Expr::Array(args) if args.elems.is_empty() => quote! {
200 -> [#last_arg_type_tokens; 0]
201 },
202
203 _ => Default::default(),
204 })
205 .unwrap_or_default();
206
207 let make_bench_fn = |generics: &[&dyn ToTokens]| {
210 let mut fn_expr = if generics.is_empty() {
211 fn_ident.to_token_stream()
213 } else {
214 quote! { #fn_ident::< #(#generics),* > }
216 };
217
218 match (fn_args.len(), &options.args_expr) {
220 (0, None) => {
222 if is_extern_abi {
224 fn_expr = quote! { || #fn_expr() };
225 }
226
227 quote! {
228 #bench_entry_runner::Plain(|divan | divan.bench(#fn_expr))
229 }
230 }
231
232 (0, Some(_)) => unreachable!(),
235
236 (1, None) => {
238 if is_extern_abi {
240 fn_expr = quote! { |divan | #fn_expr(divan) };
241 }
242
243 quote! { #bench_entry_runner::Plain(#fn_expr) }
244 }
245
246 (1, Some(args)) => quote! {
248 #bench_entry_runner::Args(|| __DIVAN_ARGS.runner(
249 || #arg_return_tokens { #args },
250
251 |arg| #private_mod::ToStringHelper(arg).to_string(),
252
253 |divan, __divan_arg| divan.bench(|| #fn_expr(
254 #private_mod::Arg::<#last_arg_type_tokens>::get(__divan_arg)
255 )),
256 ))
257 },
258
259 (2, Some(args)) => quote! {
261 #bench_entry_runner::Args(|| __DIVAN_ARGS.runner(
262 || #arg_return_tokens { #args },
263
264 |arg| #private_mod::ToStringHelper(arg).to_string(),
265
266 |divan, __divan_arg| #fn_expr(
267 divan,
268 #private_mod::Arg::<#last_arg_type_tokens>::get(__divan_arg),
269 ),
270 ))
271 },
272
273 (_, None) => quote! {
275 ::std::compile_error!(::std::concat!(
276 "expected 'args' option containing '",
277 ::std::stringify!(#last_arg_type_tokens),
278 "'",
279 ))
280 },
281
282 (_, Some(_)) => unreachable!(),
285 }
286 };
287
288 let pre_main_attrs = pre_main_attrs();
289 let unsupported_error = unsupported_error(attr_name);
290
291 let make_generic_group = |generic_benches: proc_macro2::TokenStream| {
293 let entry = quote! {
294 #private_mod::GroupEntry {
295 meta: #meta,
296 generic_benches: #option_some({ #generic_benches }),
297 }
298 };
299
300 quote! {
301 #unsupported_error
302
303 static #static_ident: #private_mod::GroupEntry = {
305 {
306 #pre_main_attrs
308 static PUSH: extern "C" fn() = push;
309
310 extern "C" fn push() {
311 static NODE: #private_mod::EntryList<#private_mod::GroupEntry>
312 = #private_mod::EntryList::new(&#static_ident);
313
314 #private_mod::GROUP_ENTRIES.push(&NODE);
315 }
316 }
317
318 #bench_args_global
322
323 #entry
324 };
325 }
326 };
327
328 let make_generic_bench_entry =
330 |ty: Option<&dyn ToTokens>, const_value: Option<&dyn ToTokens>| {
331 let generic_const_value = const_value.map(|const_value| quote!({ #const_value }));
332
333 let generics: Vec<&dyn ToTokens> = {
334 let mut generics = Vec::new();
335
336 generics.extend(generic_const_value.as_ref().map(|t| t as &dyn ToTokens));
337 generics.extend(ty);
338
339 if is_type_before_const {
340 generics.reverse();
341 }
342
343 generics
344 };
345
346 let bench_fn = make_bench_fn(&generics);
347
348 let type_value = match ty {
349 Some(ty) => quote! {
350 #option_some(#private_mod::EntryType::new::<#ty>())
351 },
352 None => option_none.clone(),
353 };
354
355 let const_value = match const_value {
356 Some(const_value) => quote! {
357 #option_some(#private_mod::EntryConst::new(&#const_value))
358 },
359 None => option_none.clone(),
360 };
361
362 quote! {
363 #private_mod::GenericBenchEntry {
364 group: &#static_ident,
365 bench: #bench_fn,
366 ty: #type_value,
367 const_value: #const_value,
368 }
369 }
370 };
371
372 let generated_items: proc_macro2::TokenStream = match &options.generic.consts {
373 _ if options.generic.is_empty() => Default::default(),
375
376 None => match &options.generic.types {
377 None => {
379 let bench_fn = make_bench_fn(&[]);
380
381 let entry = quote! {
382 #private_mod::BenchEntry {
383 meta: #meta,
384 bench: #bench_fn,
385 }
386 };
387
388 quote! {
389 static #static_ident: #private_mod::BenchEntry = {
392 {
393 #pre_main_attrs
395 static PUSH: extern "C" fn() = push;
396
397 extern "C" fn push() {
398 static NODE: #private_mod::EntryList<#private_mod::BenchEntry>
399 = #private_mod::EntryList::new(&#static_ident);
400
401 #private_mod::BENCH_ENTRIES.push(&NODE);
402 }
403 }
404
405 #bench_args_global
406
407 #entry
408 };
409 }
410 }
411
412 Some(GenericTypes::List(generic_types)) => {
414 let generic_benches =
415 generic_types.iter().map(|ty| make_generic_bench_entry(Some(&ty), None));
416
417 make_generic_group(quote! {
418 &[&[#(#generic_benches),*]]
419 })
420 }
421 },
422
423 Some(Expr::Array(generic_consts)) => {
425 let consts_count = generic_consts.elems.len();
426 let const_type = &const_param.unwrap().1.ty;
427
428 let generic_benches = options.generic.types_iter().map(|ty| {
429 let generic_benches = (0..consts_count).map(move |i| {
430 let const_value = quote! { __DIVAN_CONSTS[#i] };
431 make_generic_bench_entry(ty, Some(&const_value))
432 });
433
434 quote! {
437 static __DIVAN_GENERIC_BENCHES: [#private_mod::GenericBenchEntry; #consts_count] = [#(#generic_benches),*];
438 &__DIVAN_GENERIC_BENCHES
439 }
440 });
441
442 make_generic_group(quote! {
443 const __DIVAN_CONSTS: &[#const_type] = &#generic_consts;
449
450 &[#({ #generic_benches }),*]
451 })
452 }
453
454 Some(generic_consts) => {
460 const MAX_EXTERN_COUNT: usize = 20;
462
463 let const_type = &const_param.unwrap().1.ty;
464
465 let generic_benches = options.generic.types_iter().map(|ty| {
466 let generic_benches = (0..MAX_EXTERN_COUNT).map(move |i| {
467 let const_value = quote! {
468 __DIVAN_CONSTS[if #i < __DIVAN_CONST_COUNT { #i } else { 0 }]
470 };
471 make_generic_bench_entry(ty, Some(&const_value))
472 });
473
474 quote! {
477 static __DIVAN_GENERIC_BENCHES: [#private_mod::GenericBenchEntry; __DIVAN_CONST_COUNT]
478 = match #private_mod::shrink_array([#(#generic_benches),*]) {
479 Some(array) => array,
480 _ => panic!("external 'consts' cannot contain more than 20 values"),
481 };
482
483 &__DIVAN_GENERIC_BENCHES
484 }
485 });
486
487 make_generic_group(quote! {
488 const __DIVAN_CONST_COUNT: usize = __DIVAN_CONSTS.len();
489 const __DIVAN_CONSTS: &[#const_type] = &#generic_consts;
490
491 &[#({ #generic_benches }),*]
492 })
493 }
494 };
495
496 let mut result = item;
498 result.extend(TokenStream::from(generated_items));
499 result
500}
501
502#[proc_macro_attribute]
503pub fn bench_group(options: TokenStream, item: TokenStream) -> TokenStream {
504 let attr = Macro::BenchGroup;
505 let attr_name = attr.name();
506
507 let options = match AttrOptions::parse(options, attr) {
508 Ok(options) => options,
509 Err(compile_error) => return compile_error,
510 };
511
512 let AttrOptions { private_mod, .. } = &options;
514
515 let option_none = tokens::option_none();
516
517 let mod_item = item.clone();
519 let mod_item = syn::parse_macro_input!(mod_item as syn::ItemMod);
520
521 let mod_ident = &mod_item.ident;
522 let mod_name = mod_ident.to_string();
523 let mod_name_pretty = mod_name.strip_prefix("r#").unwrap_or(&mod_name);
524
525 let ignore_attr_ident =
530 mod_item.attrs.iter().map(|attr| attr.meta.path()).find(|path| path.is_ident("ignore"));
531
532 let static_ident = syn::Ident::new(
537 &format!("__DIVAN_GROUP_{}", mod_name_pretty.to_uppercase()),
538 mod_ident.span(),
539 );
540
541 let meta = entry_meta_expr(&mod_name, &options, ignore_attr_ident);
542
543 let pre_main_attrs = pre_main_attrs();
544 let unsupported_error = unsupported_error(attr_name);
545
546 let generated_items = quote! {
547 #unsupported_error
548
549 static #static_ident: #private_mod::EntryList<#private_mod::GroupEntry> = {
551 {
552 #pre_main_attrs
554 static PUSH: extern "C" fn() = push;
555
556 extern "C" fn push() {
557 #private_mod::GROUP_ENTRIES.push(&#static_ident);
558 }
559 }
560
561 #private_mod::EntryList::new({
562 static #static_ident: #private_mod::GroupEntry = #private_mod::GroupEntry {
563 meta: #meta,
564 generic_benches: #option_none,
565 };
566
567 &#static_ident
568 })
569 };
570 };
571
572 let mut result = item;
574 result.extend(TokenStream::from(generated_items));
575 result
576}
577
578fn entry_meta_expr(
580 raw_name: &str,
581 options: &AttrOptions,
582 ignore_attr_ident: Option<&syn::Path>,
583) -> proc_macro2::TokenStream {
584 let AttrOptions { private_mod, .. } = &options;
585
586 let raw_name_pretty = raw_name.strip_prefix("r#").unwrap_or(raw_name);
587
588 let display_name: &dyn ToTokens = match &options.name_expr {
589 Some(name) => name,
590 None => &raw_name_pretty,
591 };
592
593 let bench_options = options.bench_options_fn(ignore_attr_ident);
594
595 quote! {
596 #private_mod::EntryMeta {
597 raw_name: #raw_name,
598 display_name: #display_name,
599 bench_options: #bench_options,
600 module_path: ::std::module_path!(),
601
602 location: #private_mod::EntryLocation {
604 file: ::std::file!(),
605 line: ::std::line!(),
606 col: ::std::column!(),
607 },
608 }
609 }
610}