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