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