use proc_macro2::TokenStream;
use quote::quote;
use crate::ParseResult;
use crate::util::{KvParser, bail, retain_attributes_except};
const DEFAULT_REPETITIONS: usize = 100;
pub fn attribute_bench(input_decl: venial::Item) -> ParseResult<TokenStream> {
let func = match input_decl {
venial::Item::Function(f) => f,
_ => return bail!(&input_decl, "#[bench] can only be applied to functions"),
};
if func.generic_params.is_some() || func.where_clause.is_some() {
return bad_signature(&func);
}
let mut attr = KvParser::parse_required(&func.attributes, "bench", &func.name)?;
let manual = attr.handle_alone("manual")?;
let repetitions = attr.handle_usize("repeat")?;
attr.finish()?;
if manual && repetitions.is_some() {
return bail!(
func,
"#[bench(manual)] cannot be combined with `repeat` -- pass repetitions to bench_measure() instead"
);
}
let repetitions = repetitions.unwrap_or(DEFAULT_REPETITIONS);
if !func.params.is_empty() {
return bad_signature(&func);
}
let bench_name = &func.name;
let bench_name_str = func.name.to_string();
let body = &func.body;
let other_attributes: Vec<_> = retain_attributes_except(&func.attributes, "bench").collect();
let generated_fn = if manual {
let ret = func.return_ty;
quote! {
#(#other_attributes)*
pub fn #bench_name() -> #ret {
#body
}
}
} else {
let Some(ret) = func.return_ty else {
return bail!(
func,
"#[bench] function must return a value from its computation, to prevent optimizing the operation away"
);
};
quote! {
#(#other_attributes)*
pub fn #bench_name() -> crate::framework::BenchResult {
crate::framework::bench_measure(#repetitions, || {
let __ret: #ret = #body;
__ret })
}
}
};
Ok(quote! {
#generated_fn
::godot::sys::plugin_add!(crate::framework::__GODOT_BENCH; crate::framework::RustBenchmark {
name: #bench_name_str,
file: std::file!(),
line: std::line!(),
function: #bench_name,
});
})
}
fn bad_signature(func: &venial::Function) -> Result<TokenStream, venial::Error> {
bail!(
func,
"#[bench] function must have one of these signatures:\
\n\
\n(1) #[bench]\
\n fn {f}() -> T {{ ... }}\
\n\
\n(2) #[bench(manual)]\
\n fn {f}() -> BenchResult {{ ... /* call to bench_measure() */ }}",
f = func.name,
)
}