mobench_macros/lib.rs
1//! # mobench-macros
2//!
3//! [](https://crates.io/crates/mobench-macros)
4//! [](https://docs.rs/mobench-macros)
5//! [](https://github.com/worldcoin/mobile-bench-rs/blob/main/LICENSE)
6//!
7//! Procedural macros for the mobench mobile benchmarking SDK.
8//!
9//! This crate provides the [`#[benchmark]`](macro@benchmark) attribute macro
10//! that marks functions for mobile benchmarking. Functions annotated with this
11//! macro are automatically registered in a global registry and can be discovered
12//! and executed at runtime.
13//!
14//! ## Usage
15//!
16//! Most users should import the macro via [`mobench-sdk`](https://crates.io/crates/mobench-sdk)
17//! rather than using this crate directly:
18//!
19//! ```ignore
20//! use mobench_sdk::benchmark;
21//!
22//! #[benchmark]
23//! fn my_benchmark() {
24//! // Your benchmark code here
25//! let result = expensive_computation();
26//! std::hint::black_box(result);
27//! }
28//! ```
29//!
30//! ## How It Works
31//!
32//! The `#[benchmark]` macro:
33//!
34//! 1. **Preserves the original function** - The function remains callable as normal
35//! 2. **Registers with inventory** - Creates a static registration that the SDK discovers at runtime
36//! 3. **Captures the fully-qualified name** - Uses `module_path!()` to generate unique names like `my_crate::my_module::my_benchmark`
37//!
38//! ## Requirements
39//!
40//! - The [`inventory`](https://crates.io/crates/inventory) crate must be in your dependency tree
41//! - Functions must have no parameters and return `()`
42//! - The function should not panic during normal execution
43//!
44//! ## Example: Multiple Benchmarks
45//!
46//! ```ignore
47//! use mobench_sdk::benchmark;
48//!
49//! #[benchmark]
50//! fn benchmark_sorting() {
51//! let mut data: Vec<i32> = (0..1000).rev().collect();
52//! data.sort();
53//! std::hint::black_box(data);
54//! }
55//!
56//! #[benchmark]
57//! fn benchmark_hashing() {
58//! use std::collections::hash_map::DefaultHasher;
59//! use std::hash::{Hash, Hasher};
60//!
61//! let mut hasher = DefaultHasher::new();
62//! "hello world".hash(&mut hasher);
63//! std::hint::black_box(hasher.finish());
64//! }
65//! ```
66//!
67//! Both functions will be registered with names like:
68//! - `my_crate::benchmark_sorting`
69//! - `my_crate::benchmark_hashing`
70//!
71//! ## Crate Ecosystem
72//!
73//! This crate is part of the mobench ecosystem:
74//!
75//! - **[`mobench-sdk`](https://crates.io/crates/mobench-sdk)** - Core SDK with timing harness (re-exports this macro)
76//! - **[`mobench`](https://crates.io/crates/mobench)** - CLI tool
77//! - **`mobench-macros`** (this crate) - Proc macros
78//!
79//! Note: The `mobench-runner` crate has been consolidated into `mobench-sdk` as the `timing` module.
80
81use proc_macro::TokenStream;
82use quote::quote;
83use syn::{ItemFn, parse_macro_input};
84
85/// Marks a function as a benchmark for mobile execution.
86///
87/// This attribute macro registers the function in the global benchmark registry,
88/// making it discoverable and executable by the mobench runtime.
89///
90/// # Usage
91///
92/// ```ignore
93/// use mobench_sdk::benchmark;
94///
95/// #[benchmark]
96/// fn fibonacci_bench() {
97/// let result = fibonacci(30);
98/// std::hint::black_box(result);
99/// }
100/// ```
101///
102/// # Function Requirements
103///
104/// The annotated function must:
105/// - Take no parameters
106/// - Return `()` (unit type)
107/// - Not panic during normal execution
108///
109/// # Best Practices
110///
111/// ## Use `black_box` to Prevent Optimization
112///
113/// Always wrap results with [`std::hint::black_box`] to prevent the compiler
114/// from optimizing away the computation:
115///
116/// ```ignore
117/// #[benchmark]
118/// fn good_benchmark() {
119/// let result = compute_something();
120/// std::hint::black_box(result); // Prevents optimization
121/// }
122/// ```
123///
124/// ## Avoid Side Effects
125///
126/// Benchmarks should be deterministic. Avoid:
127/// - File I/O
128/// - Network calls
129/// - Random number generation (unless seeded)
130/// - Global mutable state
131///
132/// ## Keep Benchmarks Focused
133///
134/// Each benchmark should measure one specific operation:
135///
136/// ```ignore
137/// // Good: Focused benchmark
138/// #[benchmark]
139/// fn benchmark_json_parse() {
140/// let json = r#"{"key": "value"}"#;
141/// let parsed: serde_json::Value = serde_json::from_str(json).unwrap();
142/// std::hint::black_box(parsed);
143/// }
144///
145/// // Avoid: Multiple operations in one benchmark
146/// #[benchmark]
147/// fn benchmark_everything() {
148/// let json = create_json(); // Measured
149/// let parsed = parse_json(&json); // Measured
150/// let serialized = serialize(parsed); // Measured
151/// std::hint::black_box(serialized);
152/// }
153/// ```
154///
155/// # Generated Code
156///
157/// The macro generates code equivalent to:
158///
159/// ```ignore
160/// fn my_benchmark() {
161/// // Original function body
162/// }
163///
164/// inventory::submit! {
165/// mobench_sdk::registry::BenchFunction {
166/// name: "my_crate::my_module::my_benchmark",
167/// invoke: |_args| {
168/// my_benchmark();
169/// Ok(())
170/// },
171/// }
172/// }
173/// ```
174///
175/// # Discovering Benchmarks
176///
177/// Registered benchmarks can be discovered at runtime:
178///
179/// ```ignore
180/// use mobench_sdk::{discover_benchmarks, list_benchmark_names};
181///
182/// // Get all benchmark names
183/// for name in list_benchmark_names() {
184/// println!("Found: {}", name);
185/// }
186///
187/// // Get full benchmark info
188/// for bench in discover_benchmarks() {
189/// println!("Benchmark: {}", bench.name);
190/// }
191/// ```
192#[proc_macro_attribute]
193pub fn benchmark(_attr: TokenStream, item: TokenStream) -> TokenStream {
194 let input_fn = parse_macro_input!(item as ItemFn);
195
196 let fn_name = &input_fn.sig.ident;
197 let fn_name_str = fn_name.to_string();
198 let vis = &input_fn.vis;
199 let sig = &input_fn.sig;
200 let block = &input_fn.block;
201 let attrs = &input_fn.attrs;
202
203 // Get the module path for fully-qualified name
204 // Note: This will generate the fully-qualified name at compile time
205 let module_path = quote! { module_path!() };
206
207 let expanded = quote! {
208 // Preserve the original function
209 #(#attrs)*
210 #vis #sig {
211 #block
212 }
213
214 // Register the function with inventory
215 ::inventory::submit! {
216 ::mobench_sdk::registry::BenchFunction {
217 name: ::std::concat!(#module_path, "::", #fn_name_str),
218 invoke: |_args| {
219 #fn_name();
220 Ok(())
221 },
222 }
223 }
224 };
225
226 TokenStream::from(expanded)
227}