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}