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    /// CPU time consumed by the measured iteration in milliseconds.
79    pub cpu_time_ms: Option<u64>,
80    /// Peak memory growth during the measured iteration in kilobytes.
81    pub peak_memory_kb: Option<u64>,
82}
83
84impl From<crate::BenchSample> for BenchSampleFfi {
85    fn from(sample: crate::BenchSample) -> Self {
86        Self {
87            duration_ns: sample.duration_ns,
88            cpu_time_ms: sample.cpu_time_ms,
89            peak_memory_kb: sample.peak_memory_kb,
90        }
91    }
92}
93
94impl From<BenchSampleFfi> for crate::BenchSample {
95    fn from(sample: BenchSampleFfi) -> Self {
96        Self {
97            duration_ns: sample.duration_ns,
98            cpu_time_ms: sample.cpu_time_ms,
99            peak_memory_kb: sample.peak_memory_kb,
100        }
101    }
102}
103
104/// FFI-ready benchmark report.
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct BenchReportFfi {
107    /// The specification used for this benchmark run.
108    pub spec: BenchSpecFfi,
109    /// All collected timing samples.
110    pub samples: Vec<BenchSampleFfi>,
111    /// Optional semantic phase timings captured during measured iterations.
112    pub phases: Vec<SemanticPhaseFfi>,
113    /// Exact harness timeline spans in execution order.
114    pub timeline: Vec<HarnessTimelineSpanFfi>,
115}
116
117/// FFI-ready semantic phase timing.
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct SemanticPhaseFfi {
120    pub name: String,
121    pub duration_ns: u64,
122}
123
124impl From<crate::SemanticPhase> for SemanticPhaseFfi {
125    fn from(phase: crate::SemanticPhase) -> Self {
126        Self {
127            name: phase.name,
128            duration_ns: phase.duration_ns,
129        }
130    }
131}
132
133/// FFI-ready exact harness timeline span.
134#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct HarnessTimelineSpanFfi {
136    pub phase: String,
137    pub start_offset_ns: u64,
138    pub end_offset_ns: u64,
139    pub iteration: Option<u32>,
140}
141
142impl From<crate::HarnessTimelineSpan> for HarnessTimelineSpanFfi {
143    fn from(span: crate::HarnessTimelineSpan) -> Self {
144        Self {
145            phase: span.phase,
146            start_offset_ns: span.start_offset_ns,
147            end_offset_ns: span.end_offset_ns,
148            iteration: span.iteration,
149        }
150    }
151}
152
153impl From<crate::RunnerReport> for BenchReportFfi {
154    fn from(report: crate::RunnerReport) -> Self {
155        Self {
156            spec: report.spec.into(),
157            samples: report.samples.into_iter().map(Into::into).collect(),
158            phases: report.phases.into_iter().map(Into::into).collect(),
159            timeline: report.timeline.into_iter().map(Into::into).collect(),
160        }
161    }
162}
163
164/// FFI-ready error type.
165#[derive(Debug, Clone, Serialize, Deserialize)]
166pub enum BenchErrorFfi {
167    /// The iteration count was zero.
168    InvalidIterations,
169    /// The requested benchmark function was not found.
170    UnknownFunction { name: String },
171    /// An error occurred during benchmark execution.
172    ExecutionFailed { reason: String },
173    /// Configuration error.
174    ConfigError { message: String },
175    /// I/O error.
176    IoError { message: String },
177}
178
179impl From<crate::types::BenchError> for BenchErrorFfi {
180    fn from(err: crate::types::BenchError) -> Self {
181        match err {
182            crate::types::BenchError::Runner(runner_err) => match runner_err {
183                crate::timing::TimingError::NoIterations { .. } => BenchErrorFfi::InvalidIterations,
184                crate::timing::TimingError::Execution(msg) => {
185                    BenchErrorFfi::ExecutionFailed { reason: msg }
186                }
187            },
188            crate::types::BenchError::UnknownFunction(name, _) => {
189                BenchErrorFfi::UnknownFunction { name }
190            }
191            crate::types::BenchError::Execution(msg) => {
192                BenchErrorFfi::ExecutionFailed { reason: msg }
193            }
194            crate::types::BenchError::Io(e) => BenchErrorFfi::IoError {
195                message: e.to_string(),
196            },
197            crate::types::BenchError::Serialization(e) => BenchErrorFfi::ConfigError {
198                message: e.to_string(),
199            },
200            crate::types::BenchError::Config(msg) => BenchErrorFfi::ConfigError { message: msg },
201            crate::types::BenchError::Build(msg) => BenchErrorFfi::ExecutionFailed {
202                reason: format!("build error: {}", msg),
203            },
204        }
205    }
206}
207
208/// Trait for converting SDK types to FFI types.
209pub trait IntoFfi<T> {
210    /// Convert self into the FFI representation.
211    fn into_ffi(self) -> T;
212}
213
214/// Trait for converting FFI types to SDK types.
215pub trait FromFfi<T> {
216    /// Convert from FFI representation to SDK type.
217    fn from_ffi(ffi: T) -> Self;
218}
219
220// Blanket implementations
221impl<T, U> IntoFfi<U> for T
222where
223    U: From<T>,
224{
225    fn into_ffi(self) -> U {
226        U::from(self)
227    }
228}
229
230impl<T, U> FromFfi<U> for T
231where
232    T: From<U>,
233{
234    fn from_ffi(ffi: U) -> Self {
235        T::from(ffi)
236    }
237}
238
239/// Run a benchmark and return FFI-ready result.
240///
241/// This is a convenience function that wraps `run_benchmark` with FFI type conversions.
242#[cfg(feature = "full")]
243pub fn run_benchmark_ffi(spec: BenchSpecFfi) -> Result<BenchReportFfi, BenchErrorFfi> {
244    let sdk_spec: crate::BenchSpec = spec.into();
245    crate::run_benchmark(sdk_spec)
246        .map(Into::into)
247        .map_err(Into::into)
248}
249
250#[cfg(test)]
251mod tests {
252    use super::*;
253
254    #[test]
255    fn test_bench_spec_ffi_conversion() {
256        let sdk_spec = crate::BenchSpec {
257            name: "test".to_string(),
258            iterations: 100,
259            warmup: 10,
260        };
261
262        let ffi: BenchSpecFfi = sdk_spec.clone().into();
263        assert_eq!(ffi.name, "test");
264        assert_eq!(ffi.iterations, 100);
265        assert_eq!(ffi.warmup, 10);
266
267        let back: crate::BenchSpec = ffi.into();
268        assert_eq!(back.name, sdk_spec.name);
269    }
270
271    #[test]
272    fn test_bench_sample_ffi_conversion() {
273        let sdk_sample = crate::BenchSample {
274            duration_ns: 12345,
275            cpu_time_ms: Some(12),
276            peak_memory_kb: Some(48),
277        };
278        let ffi: BenchSampleFfi = sdk_sample.into();
279        assert_eq!(ffi.duration_ns, 12345);
280        assert_eq!(ffi.cpu_time_ms, Some(12));
281        assert_eq!(ffi.peak_memory_kb, Some(48));
282    }
283
284    #[test]
285    fn test_bench_report_ffi_conversion() {
286        let report = crate::RunnerReport {
287            spec: crate::BenchSpec {
288                name: "test".to_string(),
289                iterations: 2,
290                warmup: 1,
291            },
292            samples: vec![
293                crate::BenchSample {
294                    duration_ns: 100,
295                    cpu_time_ms: Some(3),
296                    peak_memory_kb: Some(8),
297                },
298                crate::BenchSample {
299                    duration_ns: 200,
300                    cpu_time_ms: Some(5),
301                    peak_memory_kb: Some(13),
302                },
303            ],
304            phases: vec![crate::SemanticPhase {
305                name: "prove".to_string(),
306                duration_ns: 300,
307            }],
308            timeline: vec![crate::HarnessTimelineSpan {
309                phase: "measured-benchmark".to_string(),
310                start_offset_ns: 0,
311                end_offset_ns: 100,
312                iteration: Some(0),
313            }],
314        };
315
316        let ffi: BenchReportFfi = report.into();
317        assert_eq!(ffi.spec.name, "test");
318        assert_eq!(ffi.samples.len(), 2);
319        assert_eq!(ffi.samples[0].duration_ns, 100);
320        assert_eq!(ffi.samples[0].cpu_time_ms, Some(3));
321        assert_eq!(ffi.samples[0].peak_memory_kb, Some(8));
322        assert_eq!(ffi.phases.len(), 1);
323        assert_eq!(ffi.phases[0].name, "prove");
324        assert_eq!(ffi.timeline.len(), 1);
325        assert_eq!(ffi.timeline[0].phase, "measured-benchmark");
326    }
327
328    #[test]
329    fn test_into_ffi_trait() {
330        let spec = crate::BenchSpec {
331            name: "test".to_string(),
332            iterations: 50,
333            warmup: 5,
334        };
335
336        let ffi: BenchSpecFfi = spec.into_ffi();
337        assert_eq!(ffi.iterations, 50);
338    }
339}