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