1use std::ffi::{c_char, c_void};
31use std::marker::PhantomData;
32use std::path::Path;
33use std::sync::Arc;
34
35use anyhow::{Context, Result};
36use libloading::Library;
37
38#[derive(Clone)]
43pub struct DynLib {
44 library: Arc<Library>,
45 path: std::path::PathBuf,
46}
47
48impl DynLib {
49 pub unsafe fn load(path: &Path) -> Result<Self> {
55 let library = unsafe { Library::new(path) }
56 .with_context(|| format!("failed to load dynamic library: {}", path.display()))?;
57 Ok(Self {
58 library: Arc::new(library),
59 path: path.to_path_buf(),
60 })
61 }
62 pub fn path(&self) -> &Path {
63 &self.path
64 }
65
66 pub unsafe fn symbol<T: Copy>(&self, name: &[u8]) -> Result<T> {
72 let sym = unsafe { self.library.get::<T>(name) }.with_context(|| {
73 format!(
74 "symbol '{}' not found in {}",
75 display_symbol(name),
76 self.path.display()
77 )
78 })?;
79 Ok(*sym)
80 }
81
82 pub unsafe fn try_symbol<T: Copy>(&self, name: &[u8]) -> Option<T> {
88 unsafe { self.library.get::<T>(name) }.ok().map(|s| *s)
89 }
90}
91
92#[repr(C)]
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub struct AbiDynFatPtr {
101 pub data: *const c_void,
102 pub vtable: *const c_void,
103}
104
105impl AbiDynFatPtr {
106 pub const fn null() -> Self {
107 Self {
108 data: std::ptr::null(),
109 vtable: std::ptr::null(),
110 }
111 }
112
113 pub fn is_null(self) -> bool {
114 self.data.is_null() || self.vtable.is_null()
115 }
116}
117
118unsafe impl Send for AbiDynFatPtr {}
121unsafe impl Sync for AbiDynFatPtr {}
122
123pub type RetainFn = unsafe extern "C" fn(AbiDynFatPtr);
128pub type ReleaseFn = unsafe extern "C" fn(AbiDynFatPtr);
129
130#[repr(C)]
133#[derive(Debug, Clone, Copy)]
134pub struct AbiStableDynRef {
135 pub object: AbiDynFatPtr,
136 pub retain: RetainFn,
137 pub release: ReleaseFn,
138}
139
140impl AbiStableDynRef {
141 pub const fn null() -> Self {
142 Self {
143 object: AbiDynFatPtr::null(),
144 retain: retain_noop,
145 release: release_noop,
146 }
147 }
148
149 pub fn is_null(self) -> bool {
150 self.object.is_null()
151 }
152}
153
154unsafe impl Send for AbiStableDynRef {}
157unsafe impl Sync for AbiStableDynRef {}
158
159unsafe extern "C" fn retain_noop(_: AbiDynFatPtr) {}
160unsafe extern "C" fn release_noop(_: AbiDynFatPtr) {}
161
162pub unsafe fn pack_fat_ptr<T: ?Sized>(ptr: *const T) -> AbiDynFatPtr {
172 unsafe { std::mem::transmute_copy(&ptr) }
174}
175
176pub unsafe fn unpack_fat_ptr<T: ?Sized>(ptr: AbiDynFatPtr) -> *const T {
182 unsafe { std::mem::transmute_copy(&ptr) }
183}
184
185unsafe extern "C" fn retain_arc<T: ?Sized>(ptr: AbiDynFatPtr) {
190 let raw: *const T = unsafe { unpack_fat_ptr(ptr) };
191 unsafe { Arc::increment_strong_count(raw) };
192}
193
194unsafe extern "C" fn release_arc<T: ?Sized>(ptr: AbiDynFatPtr) {
195 let raw: *const T = unsafe { unpack_fat_ptr(ptr) };
196 unsafe { drop(Arc::from_raw(raw)) };
197}
198
199#[repr(transparent)]
206pub struct SafeArcDyn<T: ?Sized> {
207 raw: AbiStableDynRef,
208 _marker: PhantomData<*const T>,
209}
210
211impl<T: ?Sized> SafeArcDyn<T> {
212 pub fn from_arc(value: Arc<T>) -> Self {
215 let raw = Arc::into_raw(value);
216 Self {
217 raw: AbiStableDynRef {
218 object: unsafe { pack_fat_ptr(raw) },
219 retain: retain_arc::<T>,
220 release: release_arc::<T>,
221 },
222 _marker: PhantomData,
223 }
224 }
225
226 pub fn into_abi(self) -> AbiStableDynRef {
228 let raw = self.raw;
229 std::mem::forget(self); raw
231 }
232
233 pub unsafe fn from_abi(raw: AbiStableDynRef) -> Self {
239 Self {
240 raw,
241 _marker: PhantomData,
242 }
243 }
244
245 pub unsafe fn trait_ref(&self) -> &T {
251 unsafe { &*unpack_fat_ptr::<T>(self.raw.object) }
252 }
253}
254
255unsafe impl<T: ?Sized + Send> Send for SafeArcDyn<T> {}
257unsafe impl<T: ?Sized + Sync> Sync for SafeArcDyn<T> {}
258
259impl<T: ?Sized> Clone for SafeArcDyn<T> {
260 fn clone(&self) -> Self {
261 unsafe { (self.raw.retain)(self.raw.object) };
262 Self {
263 raw: self.raw,
264 _marker: PhantomData,
265 }
266 }
267}
268
269impl<T: ?Sized> Drop for SafeArcDyn<T> {
270 fn drop(&mut self) {
271 unsafe { (self.raw.release)(self.raw.object) };
272 }
273}
274
275pub struct DynPlugin<T: ?Sized> {
282 _lib: DynLib,
283 plugin: SafeArcDyn<T>,
284}
285
286pub type PluginEntryPoint = unsafe extern "C" fn() -> AbiStableDynRef;
288
289impl<T: ?Sized> DynPlugin<T> {
290 pub unsafe fn load(path: &Path, entry_symbol: &[u8]) -> Result<Self> {
300 let lib = unsafe { DynLib::load(path) }?;
301 unsafe { Self::from_lib(lib, entry_symbol) }
302 }
303 pub unsafe fn from_lib(lib: DynLib, entry_symbol: &[u8]) -> Result<Self> {
309 let entry: PluginEntryPoint = unsafe { lib.symbol(entry_symbol) }
310 .with_context(|| format!("entry point '{}' not found", display_symbol(entry_symbol)))?;
311 let abi_ref = unsafe { entry() };
312 if abi_ref.is_null() {
313 anyhow::bail!("entry point returned null AbiStableDynRef");
314 }
315 let plugin = unsafe { SafeArcDyn::<T>::from_abi(abi_ref) };
316 Ok(Self { _lib: lib, plugin })
317 }
318
319 pub fn trait_ref(&self) -> &T {
321 unsafe { self.plugin.trait_ref() }
322 }
323
324 pub fn clone_handle(&self) -> SafeArcDyn<T> {
326 self.plugin.clone()
327 }
328}
329
330unsafe impl<T: ?Sized + Send> Send for DynPlugin<T> {}
333unsafe impl<T: ?Sized + Sync> Sync for DynPlugin<T> {}
334
335pub struct VTablePlugin<T: Copy> {
341 _lib: DynLib,
342 vtable: T,
343}
344
345impl<T: Copy> VTablePlugin<T> {
346 pub unsafe fn load(path: &Path, symbol: &[u8]) -> Result<Self> {
353 let lib = unsafe { DynLib::load(path) }?;
354 unsafe { Self::from_lib(lib, symbol) }
355 }
356
357 pub unsafe fn from_lib(lib: DynLib, symbol: &[u8]) -> Result<Self> {
361 let getter: unsafe extern "C" fn() -> *const T = unsafe { lib.symbol(symbol) }?;
362 let vtable = unsafe { getter() };
363 let vtable = unsafe { vtable.as_ref() }
364 .copied()
365 .ok_or_else(|| anyhow::anyhow!("vtable getter returned null"))?;
366 Ok(Self { _lib: lib, vtable })
367 }
368
369 pub fn vtable(&self) -> &T {
370 &self.vtable
371 }
372}
373
374fn display_symbol(symbol: &[u8]) -> String {
379 let end = symbol.iter().position(|&b| b == 0).unwrap_or(symbol.len());
380 String::from_utf8_lossy(&symbol[..end]).into_owned()
381}
382
383pub unsafe fn looks_like_plugin(path: &Path, entry_symbol: &[u8]) -> bool {
389 match unsafe { DynLib::load(path) } {
390 Ok(lib) => unsafe { lib.try_symbol::<PluginEntryPoint>(entry_symbol) }.is_some(),
391 Err(_) => false,
392 }
393}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398
399 trait Demo: Send + Sync {
400 fn value(&self) -> i32;
401 }
402
403 struct DemoValue(i32);
404
405 impl Demo for DemoValue {
406 fn value(&self) -> i32 {
407 self.0
408 }
409 }
410
411 #[test]
412 fn safe_arc_dyn_round_trip() {
413 let arc: Arc<dyn Demo> = Arc::new(DemoValue(42));
414 let wrapped = SafeArcDyn::from_arc(arc);
415 let abi = wrapped.into_abi();
416 let restored = unsafe { SafeArcDyn::<dyn Demo>::from_abi(abi) };
417 assert_eq!(unsafe { restored.trait_ref() }.value(), 42);
418 }
419
420 #[test]
421 fn safe_arc_dyn_clone() {
422 let arc: Arc<dyn Demo> = Arc::new(DemoValue(7));
423 let wrapped = SafeArcDyn::from_arc(arc);
424 let cloned = wrapped.clone();
425 assert_eq!(unsafe { wrapped.trait_ref() }.value(), 7);
426 assert_eq!(unsafe { cloned.trait_ref() }.value(), 7);
427 drop(wrapped);
428 assert_eq!(unsafe { cloned.trait_ref() }.value(), 7);
429 }
430}
431
432pub type MathSession = *mut c_void;
438
439#[repr(C)]
441#[derive(Debug, Clone, Copy)]
442pub struct GeneratedFunction {
443 pub name: *const c_char,
444 pub signature: *const c_char,
445 pub arg_count: u32,
446 pub id: u32,
447}
448
449#[repr(C)]
451#[derive(Debug, Clone, Copy)]
452pub struct MathModuleVtable {
453 pub create_session: unsafe extern "C" fn() -> MathSession,
455 pub destroy_session: unsafe extern "C" fn(MathSession),
456
457 pub add_i32: unsafe extern "C" fn(MathSession, i32, i32) -> i32,
459 pub add_i64: unsafe extern "C" fn(MathSession, i64, i64) -> i64,
460 pub add_f64: unsafe extern "C" fn(MathSession, f64, f64) -> f64,
461 pub mul_i32: unsafe extern "C" fn(MathSession, i32, i32) -> i32,
462 pub mul_f64: unsafe extern "C" fn(MathSession, f64, f64) -> f64,
463 pub pi: unsafe extern "C" fn(MathSession) -> f64,
464 pub tau: unsafe extern "C" fn(MathSession) -> f64,
465
466 pub generated_count: unsafe extern "C" fn(MathSession) -> u32,
468 pub generated_at: unsafe extern "C" fn(MathSession, u32) -> GeneratedFunction,
469
470 pub module_name: unsafe extern "C" fn() -> *const c_char,
472 pub module_version: unsafe extern "C" fn() -> u32,
473}
474
475unsafe impl Send for MathModuleVtable {}
477unsafe impl Sync for MathModuleVtable {}
478
479#[cfg(test)]
480mod cpp_math_tests {
481 use super::*;
482 use std::ffi::CStr;
483 use std::path::PathBuf;
484
485 fn math_module_path() -> PathBuf {
486 let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
488 p.push("../../cpp/build/math_module");
489 p.push("libmath_module.so");
490 p
491 }
492
493 #[test]
494 fn load_cpp_math_module_via_vtable() {
495 let path = math_module_path();
496 if !path.exists() {
497 eprintln!("SKIP: {} not found — build cpp/ first", path.display());
498 return;
499 }
500
501 let plugin =
502 unsafe { VTablePlugin::<MathModuleVtable>::load(&path, b"math_module_get_vtable\0") }
503 .expect("failed to load math_module");
504 let vt = plugin.vtable();
505
506 unsafe {
508 let name = CStr::from_ptr((vt.module_name)());
509 assert_eq!(name.to_str().unwrap(), "core-ast-math");
510 assert_eq!((vt.module_version)(), 1);
511 }
512
513 let session = unsafe { (vt.create_session)() };
515 assert!(!session.is_null());
516
517 assert_eq!(unsafe { (vt.generated_count)(session) }, 0);
519
520 let result = unsafe { (vt.add_i32)(session, 10, 20) };
522 assert_eq!(result, 30);
523 assert_eq!(unsafe { (vt.generated_count)(session) }, 1);
524
525 let result = unsafe { (vt.mul_f64)(session, 3.0, 7.0) };
527 assert!((result - 21.0).abs() < 1e-10);
528 assert_eq!(unsafe { (vt.generated_count)(session) }, 2);
529
530 let pi_val = unsafe { (vt.pi)(session) };
532 assert!((pi_val - std::f64::consts::PI).abs() < 1e-10);
533 assert_eq!(unsafe { (vt.generated_count)(session) }, 3);
534
535 let func0 = unsafe { (vt.generated_at)(session, 0) };
537 let func0_name = unsafe { CStr::from_ptr(func0.name) }.to_str().unwrap();
538 assert_eq!(func0_name, "add_i32");
539
540 let func1 = unsafe { (vt.generated_at)(session, 1) };
541 let func1_name = unsafe { CStr::from_ptr(func1.name) }.to_str().unwrap();
542 assert_eq!(func1_name, "mul_f64");
543
544 let func2 = unsafe { (vt.generated_at)(session, 2) };
545 let func2_name = unsafe { CStr::from_ptr(func2.name) }.to_str().unwrap();
546 assert_eq!(func2_name, "pi");
547
548 let _ = unsafe { (vt.add_i32)(session, 1, 2) };
550 assert_eq!(unsafe { (vt.generated_count)(session) }, 3);
551
552 unsafe { (vt.destroy_session)(session) };
554 }
555}