Skip to main content

mobench_sdk/
ffi.rs

1//! Unified FFI module for UniFFI integration.
2//!
3//! This module provides a single import point for all FFI-related types and traits
4//! needed to create UniFFI bindings for mobile platforms.
5//!
6//! # Quick Start
7//!
8//! ```ignore
9//! use mobench_sdk::ffi::{BenchSpecFfi, BenchSampleFfi, BenchReportFfi, BenchErrorFfi};
10//! use mobench_sdk::ffi::{IntoFfi, FromFfi};
11//!
12//! // Define your UniFFI types using the Ffi suffix types as templates
13//! #[derive(uniffi::Record)]
14//! pub struct BenchSpec {
15//!     pub name: String,
16//!     pub iterations: u32,
17//!     pub warmup: u32,
18//! }
19//!
20//! // Implement conversions using the traits
21//! impl FromFfi<BenchSpecFfi> for BenchSpec {
22//!     fn from_ffi(ffi: BenchSpecFfi) -> Self {
23//!         Self {
24//!             name: ffi.name,
25//!             iterations: ffi.iterations,
26//!             warmup: ffi.warmup,
27//!         }
28//!     }
29//! }
30//! ```
31
32use serde::{Deserialize, Serialize};
33
34// Re-export from uniffi_types for backwards compatibility
35pub use crate::uniffi_types::{
36    BenchErrorVariant, BenchReportTemplate, BenchSampleTemplate, BenchSpecTemplate, FromSdkError,
37    FromSdkReport, FromSdkSample, FromSdkSpec,
38};
39
40/// FFI-ready benchmark specification.
41///
42/// Use this as a template for your UniFFI Record type.
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct BenchSpecFfi {
45    /// Name of the benchmark function to run.
46    pub name: String,
47    /// Number of measurement iterations.
48    pub iterations: u32,
49    /// Number of warmup iterations before measurement.
50    pub warmup: u32,
51}
52
53impl From<crate::BenchSpec> for BenchSpecFfi {
54    fn from(spec: crate::BenchSpec) -> Self {
55        Self {
56            name: spec.name,
57            iterations: spec.iterations,
58            warmup: spec.warmup,
59        }
60    }
61}
62
63impl From<BenchSpecFfi> for crate::BenchSpec {
64    fn from(spec: BenchSpecFfi) -> Self {
65        Self {
66            name: spec.name,
67            iterations: spec.iterations,
68            warmup: spec.warmup,
69        }
70    }
71}
72
73/// FFI-ready benchmark sample.
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct BenchSampleFfi {
76    /// Duration of the iteration in nanoseconds.
77    pub duration_ns: u64,
78}
79
80impl From<crate::BenchSample> for BenchSampleFfi {
81    fn from(sample: crate::BenchSample) -> Self {
82        Self {
83            duration_ns: sample.duration_ns,
84        }
85    }
86}
87
88impl From<BenchSampleFfi> for crate::BenchSample {
89    fn from(sample: BenchSampleFfi) -> Self {
90        Self {
91            duration_ns: sample.duration_ns,
92        }
93    }
94}
95
96/// FFI-ready benchmark report.
97#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct BenchReportFfi {
99    /// The specification used for this benchmark run.
100    pub spec: BenchSpecFfi,
101    /// All collected timing samples.
102    pub samples: Vec<BenchSampleFfi>,
103    /// Optional semantic phase timings captured during measured iterations.
104    pub phases: Vec<SemanticPhaseFfi>,
105    /// Optional resource usage scoped to measured iterations.
106    pub resource_usage: Option<BenchResourceUsageFfi>,
107}
108
109/// FFI-ready resource usage captured during measured iterations.
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct BenchResourceUsageFfi {
112    pub cpu_median_ms: Option<u64>,
113    pub peak_memory_kb: Option<u64>,
114}
115
116/// FFI-ready semantic phase timing.
117#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct SemanticPhaseFfi {
119    pub name: String,
120    pub duration_ns: u64,
121}
122
123impl From<crate::SemanticPhase> for SemanticPhaseFfi {
124    fn from(phase: crate::SemanticPhase) -> Self {
125        Self {
126            name: phase.name,
127            duration_ns: phase.duration_ns,
128        }
129    }
130}
131
132impl From<crate::RunnerReport> for BenchReportFfi {
133    fn from(report: crate::RunnerReport) -> Self {
134        Self {
135            spec: report.spec.into(),
136            samples: report.samples.into_iter().map(Into::into).collect(),
137            phases: report.phases.into_iter().map(Into::into).collect(),
138            resource_usage: report.resource_usage.map(Into::into),
139        }
140    }
141}
142
143impl From<crate::BenchResourceUsage> for BenchResourceUsageFfi {
144    fn from(resource_usage: crate::BenchResourceUsage) -> Self {
145        Self {
146            cpu_median_ms: resource_usage.cpu_median_ms,
147            peak_memory_kb: resource_usage.peak_memory_kb,
148        }
149    }
150}
151
152/// FFI-ready error type.
153#[derive(Debug, Clone, Serialize, Deserialize)]
154pub enum BenchErrorFfi {
155    /// The iteration count was zero.
156    InvalidIterations,
157    /// The requested benchmark function was not found.
158    UnknownFunction { name: String },
159    /// An error occurred during benchmark execution.
160    ExecutionFailed { reason: String },
161    /// Configuration error.
162    ConfigError { message: String },
163    /// I/O error.
164    IoError { message: String },
165}
166
167impl From<crate::types::BenchError> for BenchErrorFfi {
168    fn from(err: crate::types::BenchError) -> Self {
169        match err {
170            crate::types::BenchError::Runner(runner_err) => match runner_err {
171                crate::timing::TimingError::NoIterations { .. } => BenchErrorFfi::InvalidIterations,
172                crate::timing::TimingError::Execution(msg) => {
173                    BenchErrorFfi::ExecutionFailed { reason: msg }
174                }
175            },
176            crate::types::BenchError::UnknownFunction(name, _) => {
177                BenchErrorFfi::UnknownFunction { name }
178            }
179            crate::types::BenchError::Execution(msg) => {
180                BenchErrorFfi::ExecutionFailed { reason: msg }
181            }
182            crate::types::BenchError::Io(e) => BenchErrorFfi::IoError {
183                message: e.to_string(),
184            },
185            crate::types::BenchError::Serialization(e) => BenchErrorFfi::ConfigError {
186                message: e.to_string(),
187            },
188            crate::types::BenchError::Config(msg) => BenchErrorFfi::ConfigError { message: msg },
189            crate::types::BenchError::Build(msg) => BenchErrorFfi::ExecutionFailed {
190                reason: format!("build error: {}", msg),
191            },
192        }
193    }
194}
195
196/// Trait for converting SDK types to FFI types.
197pub trait IntoFfi<T> {
198    /// Convert self into the FFI representation.
199    fn into_ffi(self) -> T;
200}
201
202/// Trait for converting FFI types to SDK types.
203pub trait FromFfi<T> {
204    /// Convert from FFI representation to SDK type.
205    fn from_ffi(ffi: T) -> Self;
206}
207
208// Blanket implementations
209impl<T, U> IntoFfi<U> for T
210where
211    U: From<T>,
212{
213    fn into_ffi(self) -> U {
214        U::from(self)
215    }
216}
217
218impl<T, U> FromFfi<U> for T
219where
220    T: From<U>,
221{
222    fn from_ffi(ffi: U) -> Self {
223        T::from(ffi)
224    }
225}
226
227/// Run a benchmark and return FFI-ready result.
228///
229/// This is a convenience function that wraps `run_benchmark` with FFI type conversions.
230#[cfg(feature = "full")]
231pub fn run_benchmark_ffi(spec: BenchSpecFfi) -> Result<BenchReportFfi, BenchErrorFfi> {
232    let sdk_spec: crate::BenchSpec = spec.into();
233    crate::run_benchmark(sdk_spec)
234        .map(Into::into)
235        .map_err(Into::into)
236}
237
238#[cfg(test)]
239mod tests {
240    use super::*;
241
242    #[test]
243    fn test_bench_spec_ffi_conversion() {
244        let sdk_spec = crate::BenchSpec {
245            name: "test".to_string(),
246            iterations: 100,
247            warmup: 10,
248        };
249
250        let ffi: BenchSpecFfi = sdk_spec.clone().into();
251        assert_eq!(ffi.name, "test");
252        assert_eq!(ffi.iterations, 100);
253        assert_eq!(ffi.warmup, 10);
254
255        let back: crate::BenchSpec = ffi.into();
256        assert_eq!(back.name, sdk_spec.name);
257    }
258
259    #[test]
260    fn test_bench_sample_ffi_conversion() {
261        let sdk_sample = crate::BenchSample { duration_ns: 12345 };
262        let ffi: BenchSampleFfi = sdk_sample.into();
263        assert_eq!(ffi.duration_ns, 12345);
264    }
265
266    #[test]
267    fn test_bench_report_ffi_conversion() {
268        let report = crate::RunnerReport {
269            spec: crate::BenchSpec {
270                name: "test".to_string(),
271                iterations: 2,
272                warmup: 1,
273            },
274            samples: vec![
275                crate::BenchSample { duration_ns: 100 },
276                crate::BenchSample { duration_ns: 200 },
277            ],
278            phases: vec![crate::SemanticPhase {
279                name: "prove".to_string(),
280                duration_ns: 300,
281            }],
282            resource_usage: None,
283        };
284
285        let ffi: BenchReportFfi = report.into();
286        assert_eq!(ffi.spec.name, "test");
287        assert_eq!(ffi.samples.len(), 2);
288        assert_eq!(ffi.samples[0].duration_ns, 100);
289        assert_eq!(ffi.phases.len(), 1);
290        assert_eq!(ffi.phases[0].name, "prove");
291    }
292
293    #[test]
294    fn test_into_ffi_trait() {
295        let spec = crate::BenchSpec {
296            name: "test".to_string(),
297            iterations: 50,
298            warmup: 5,
299        };
300
301        let ffi: BenchSpecFfi = spec.into_ffi();
302        assert_eq!(ffi.iterations, 50);
303    }
304}