mobench_sdk/
native_c_abi.rs1use crate::BenchSpec;
10use libc::c_char;
11use std::cell::RefCell;
12use std::ffi::CString;
13use std::panic::{AssertUnwindSafe, catch_unwind};
14use std::ptr;
15use std::slice;
16
17#[repr(C)]
23#[derive(Debug)]
24pub struct MobenchBuf {
25 pub ptr: *mut u8,
27 pub len: usize,
29 pub cap: usize,
31}
32
33impl MobenchBuf {
34 fn clear(&mut self) {
35 self.ptr = ptr::null_mut();
36 self.len = 0;
37 self.cap = 0;
38 }
39}
40
41impl Default for MobenchBuf {
42 fn default() -> Self {
43 Self {
44 ptr: ptr::null_mut(),
45 len: 0,
46 cap: 0,
47 }
48 }
49}
50
51thread_local! {
52 static LAST_ERROR: RefCell<CString> = RefCell::new(CString::default());
53}
54
55pub unsafe fn mobench_run_benchmark_json_impl(
64 spec_ptr: *const u8,
65 spec_len: usize,
66 out: *mut MobenchBuf,
67) -> i32 {
68 let result = catch_unwind(AssertUnwindSafe(|| {
69 if out.is_null() {
70 return Err("output buffer pointer must not be null".to_string());
71 }
72
73 unsafe { (*out).clear() };
76
77 if spec_len > 0 && spec_ptr.is_null() {
78 return Err("spec pointer must not be null when spec length is non-zero".to_string());
79 }
80
81 let spec_bytes = if spec_len == 0 {
82 &[]
83 } else {
84 unsafe { slice::from_raw_parts(spec_ptr, spec_len) }
85 };
86 let spec: BenchSpec = serde_json::from_slice(spec_bytes)
87 .map_err(|error| format!("failed to parse BenchSpec JSON: {error}"))?;
88 let report = crate::run_benchmark(spec).map_err(|error| error.to_string())?;
89 let mut bytes = serde_json::to_vec(&report)
90 .map_err(|error| format!("failed to serialize BenchReport JSON: {error}"))?;
91
92 let buf = MobenchBuf {
93 ptr: bytes.as_mut_ptr(),
94 len: bytes.len(),
95 cap: bytes.capacity(),
96 };
97 std::mem::forget(bytes);
98 unsafe { *out = buf };
99 Ok(())
100 }));
101
102 match result {
103 Ok(Ok(())) => {
104 clear_last_error();
105 0
106 }
107 Ok(Err(error)) => {
108 set_last_error(error);
109 1
110 }
111 Err(_) => {
112 set_last_error("benchmark panicked across native C ABI boundary");
113 2
114 }
115 }
116}
117
118pub unsafe fn mobench_free_buf_impl(buf: *mut MobenchBuf) {
126 if buf.is_null() {
127 return;
128 }
129
130 let buf_ref = unsafe { &mut *buf };
131 if !buf_ref.ptr.is_null() {
132 let ptr = buf_ref.ptr;
133 let len = buf_ref.len;
134 let cap = buf_ref.cap;
135 buf_ref.clear();
136 unsafe {
137 drop(Vec::from_raw_parts(ptr, len, cap));
138 }
139 } else {
140 buf_ref.clear();
141 }
142}
143
144pub fn mobench_last_error_message_impl() -> *const c_char {
146 LAST_ERROR.with(|message| message.borrow().as_ptr())
147}
148
149fn clear_last_error() {
150 LAST_ERROR.with(|message| *message.borrow_mut() = CString::default());
151}
152
153fn set_last_error(message: impl AsRef<str>) {
154 let sanitized = message.as_ref().replace('\0', "\\0");
155 let c_string = CString::new(sanitized).unwrap_or_default();
156 LAST_ERROR.with(|last_error| *last_error.borrow_mut() = c_string);
157}
158
159#[macro_export]
168macro_rules! export_native_c_abi {
169 () => {
170 #[unsafe(no_mangle)]
178 pub unsafe extern "C" fn mobench_run_benchmark_json(
179 spec_ptr: *const u8,
180 spec_len: usize,
181 out: *mut $crate::MobenchBuf,
182 ) -> i32 {
183 unsafe {
184 $crate::native_c_abi::mobench_run_benchmark_json_impl(spec_ptr, spec_len, out)
185 }
186 }
187
188 #[unsafe(no_mangle)]
196 pub unsafe extern "C" fn mobench_free_buf(buf: *mut $crate::MobenchBuf) {
197 unsafe { $crate::native_c_abi::mobench_free_buf_impl(buf) }
198 }
199
200 #[unsafe(no_mangle)]
202 pub extern "C" fn mobench_last_error_message() -> *const ::std::os::raw::c_char {
203 $crate::native_c_abi::mobench_last_error_message_impl()
204 }
205 };
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211 use crate::{BenchFunction, TimingError};
212 use std::ffi::CStr;
213
214 fn native_abi_test_runner(spec: crate::BenchSpec) -> Result<crate::RunnerReport, TimingError> {
215 Ok(crate::RunnerReport {
216 spec,
217 samples: vec![crate::BenchSample {
218 duration_ns: 42,
219 cpu_time_ms: None,
220 peak_memory_kb: None,
221 process_peak_memory_kb: None,
222 }],
223 phases: Vec::new(),
224 timeline: Vec::new(),
225 })
226 }
227
228 inventory::submit! {
229 BenchFunction {
230 name: "native_abi_test_benchmark",
231 runner: native_abi_test_runner,
232 }
233 }
234
235 #[test]
236 fn runs_valid_spec_and_returns_report_json() {
237 let spec = br#"{"name":"native_abi_test_benchmark","iterations":1,"warmup":0}"#;
238 let mut out = MobenchBuf::default();
239
240 let status =
241 unsafe { mobench_run_benchmark_json_impl(spec.as_ptr(), spec.len(), &mut out) };
242
243 assert_eq!(status, 0);
244 assert!(!out.ptr.is_null());
245 assert!(out.len > 0);
246
247 let report_bytes = unsafe { slice::from_raw_parts(out.ptr, out.len) };
248 let report: crate::RunnerReport = serde_json::from_slice(report_bytes).unwrap();
249 assert_eq!(report.spec.name, "native_abi_test_benchmark");
250 assert_eq!(report.samples[0].duration_ns, 42);
251
252 unsafe { mobench_free_buf_impl(&mut out) };
253 assert!(out.ptr.is_null());
254 assert_eq!(out.len, 0);
255 assert_eq!(out.cap, 0);
256 }
257
258 #[test]
259 fn invalid_json_returns_error_without_output() {
260 let spec = b"not json";
261 let mut out = MobenchBuf::default();
262
263 let status =
264 unsafe { mobench_run_benchmark_json_impl(spec.as_ptr(), spec.len(), &mut out) };
265
266 assert_ne!(status, 0);
267 assert!(out.ptr.is_null());
268 let error = unsafe { CStr::from_ptr(mobench_last_error_message_impl()) }
269 .to_string_lossy()
270 .into_owned();
271 assert!(error.contains("failed to parse BenchSpec JSON"));
272 }
273
274 #[test]
275 fn unknown_benchmark_returns_error_without_output() {
276 let spec = br#"{"name":"definitely_missing","iterations":1,"warmup":0}"#;
277 let mut out = MobenchBuf::default();
278
279 let status =
280 unsafe { mobench_run_benchmark_json_impl(spec.as_ptr(), spec.len(), &mut out) };
281
282 assert_ne!(status, 0);
283 assert!(out.ptr.is_null());
284 let error = unsafe { CStr::from_ptr(mobench_last_error_message_impl()) }
285 .to_string_lossy()
286 .into_owned();
287 assert!(error.contains("unknown benchmark function"));
288 }
289
290 #[test]
291 fn free_null_and_empty_buffers_are_safe() {
292 unsafe { mobench_free_buf_impl(ptr::null_mut()) };
293
294 let mut out = MobenchBuf::default();
295 unsafe { mobench_free_buf_impl(&mut out) };
296
297 assert!(out.ptr.is_null());
298 assert_eq!(out.len, 0);
299 assert_eq!(out.cap, 0);
300 }
301}