1use std::ffi::c_char;
26use std::marker::PhantomData;
27use std::path::Path;
28
29use anyhow::{Context, Result};
30
31use crate::DynLib;
32use crate::dyn_mod::{AbiStableDynRef, PluginEntryPoint};
33
34pub struct VTablePlugin<T: Copy> {
40 _lib: DynLib,
41 vtable: T,
42}
43
44impl<T: Copy> VTablePlugin<T> {
45 pub unsafe fn load(path: &Path, symbol: &[u8]) -> Result<Self> {
52 let lib = unsafe { DynLib::load(path) }?;
53 unsafe { Self::from_lib(lib, symbol) }
54 }
55
56 pub unsafe fn from_lib(lib: DynLib, symbol: &[u8]) -> Result<Self> {
60 let getter: unsafe extern "C" fn() -> *const T = unsafe { lib.symbol(symbol) }?;
61 let vtable = unsafe { getter() };
62 let vtable = unsafe { vtable.as_ref() }
63 .copied()
64 .ok_or_else(|| anyhow::anyhow!("vtable getter returned null"))?;
65 Ok(Self { _lib: lib, vtable })
66 }
67
68 pub fn vtable(&self) -> &T {
69 &self.vtable
70 }
71}
72
73pub type MathSession = *mut std::ffi::c_void;
79
80#[repr(C)]
82#[derive(Debug, Clone, Copy)]
83pub struct GeneratedFunction {
84 pub name: *const c_char,
85 pub signature: *const c_char,
86 pub arg_count: u32,
87 pub id: u32,
88}
89
90#[repr(C)]
92#[derive(Debug, Clone, Copy)]
93pub struct MathModuleVtable {
94 pub create_session: unsafe extern "C" fn() -> MathSession,
96 pub destroy_session: unsafe extern "C" fn(MathSession),
97
98 pub add_i32: unsafe extern "C" fn(MathSession, i32, i32) -> i32,
100 pub add_i64: unsafe extern "C" fn(MathSession, i64, i64) -> i64,
101 pub add_f64: unsafe extern "C" fn(MathSession, f64, f64) -> f64,
102 pub mul_i32: unsafe extern "C" fn(MathSession, i32, i32) -> i32,
103 pub mul_f64: unsafe extern "C" fn(MathSession, f64, f64) -> f64,
104 pub pi: unsafe extern "C" fn(MathSession) -> f64,
105 pub tau: unsafe extern "C" fn(MathSession) -> f64,
106
107 pub generated_count: unsafe extern "C" fn(MathSession) -> u32,
109 pub generated_at: unsafe extern "C" fn(MathSession, u32) -> GeneratedFunction,
110
111 pub module_name: unsafe extern "C" fn() -> *const c_char,
113 pub module_version: unsafe extern "C" fn() -> u32,
114}
115
116unsafe impl Send for MathModuleVtable {}
118unsafe impl Sync for MathModuleVtable {}
119
120#[cfg(test)]
121mod cdyn_handle_tests {
122 use super::*;
123 use std::sync::atomic::{AtomicU32, Ordering};
124
125 static INSTANCE: u64 = 0xdead_beef;
129 static REFCOUNT: AtomicU32 = AtomicU32::new(0);
130
131 #[repr(C)]
132 #[derive(Clone, Copy)]
133 struct TestVtable {
134 get_value: unsafe extern "C" fn(ctx: *mut std::ffi::c_void) -> u64,
135 }
136
137 unsafe extern "C" fn test_get_value(ctx: *mut std::ffi::c_void) -> u64 {
138 let _ = ctx;
140 INSTANCE
141 }
142
143 unsafe extern "C" fn test_retain(_: AbiStableDynRef__FatPtr) {
144 REFCOUNT.fetch_add(1, Ordering::SeqCst);
145 }
146
147 unsafe extern "C" fn test_release(_: AbiStableDynRef__FatPtr) {
148 let prev = REFCOUNT.fetch_sub(1, Ordering::SeqCst);
149 assert!(prev > 0, "release called more times than retain");
150 }
151
152 type AbiStableDynRef__FatPtr = crate::dyn_mod::AbiDynFatPtr;
154
155 static TEST_VTABLE: TestVtable = TestVtable { get_value: test_get_value };
156
157 #[test]
158 fn cdyn_handle_retain_release_roundtrip() {
159 REFCOUNT.store(1, Ordering::SeqCst); let raw = AbiStableDynRef {
162 object: crate::dyn_mod::AbiDynFatPtr {
163 data: &INSTANCE as *const u64 as *const std::ffi::c_void,
164 vtable: &TEST_VTABLE as *const TestVtable as *const std::ffi::c_void,
165 },
166 retain: test_retain,
167 release: test_release,
168 };
169
170 let h1 = unsafe { CdynHandle::<TestVtable>::from_raw(raw) };
172 assert_eq!(REFCOUNT.load(Ordering::SeqCst), 1);
173
174 let h2 = h1.clone();
176 assert_eq!(REFCOUNT.load(Ordering::SeqCst), 2);
177
178 unsafe {
180 let vt = h1.vtable();
181 assert_eq!((vt.get_value)(h1.ctx()), INSTANCE);
182 }
183 assert_eq!(h1.as_raw().object.vtable, h2.as_raw().object.vtable);
185
186 drop(h2);
188 assert_eq!(REFCOUNT.load(Ordering::SeqCst), 1);
189 drop(h1);
190 assert_eq!(REFCOUNT.load(Ordering::SeqCst), 0);
191 }
192
193 #[test]
194 fn cdyn_handle_into_raw_skips_release() {
195 REFCOUNT.store(1, Ordering::SeqCst);
196 let raw = AbiStableDynRef {
197 object: crate::dyn_mod::AbiDynFatPtr {
198 data: &INSTANCE as *const u64 as *const std::ffi::c_void,
199 vtable: &TEST_VTABLE as *const TestVtable as *const std::ffi::c_void,
200 },
201 retain: test_retain,
202 release: test_release,
203 };
204 let h = unsafe { CdynHandle::<TestVtable>::from_raw(raw) };
205 let raw2 = h.into_raw(); drop(raw2); assert_eq!(REFCOUNT.load(Ordering::SeqCst), 1); REFCOUNT.fetch_sub(1, Ordering::SeqCst);
210 }
211}
212
213pub struct CdynHandle<T: Copy> {
246 _lib: DynLib,
247 raw: AbiStableDynRef,
248 _marker: PhantomData<T>,
249}
250
251impl<T: Copy> CdynHandle<T> {
252 pub unsafe fn load(path: &Path, dyn_entry: &[u8]) -> Result<Self> {
260 let lib = unsafe { DynLib::load(path) }?;
261 unsafe { Self::from_lib(lib, dyn_entry) }
262 }
263
264 pub unsafe fn from_lib(lib: DynLib, dyn_entry: &[u8]) -> Result<Self> {
271 let entry: PluginDynEntryPoint =
272 unsafe { lib.symbol(dyn_entry) }.with_context(|| {
273 format!("dyn entry '{}' not found", crate::helpers::display_symbol(dyn_entry))
274 })?;
275 let raw = unsafe { entry() };
276 if raw.is_null() {
277 anyhow::bail!("dyn entry returned null AbiStableDynRef");
278 }
279 Ok(Self {
280 _lib: lib,
281 raw,
282 _marker: PhantomData,
283 })
284 }
285
286 pub unsafe fn from_raw(raw: AbiStableDynRef) -> Self {
296 Self {
297 _lib: DynLib::unowned(),
298 raw,
299 _marker: PhantomData,
300 }
301 }
302
303 pub fn ctx(&self) -> *mut std::ffi::c_void {
305 self.raw.object.data as *mut std::ffi::c_void
306 }
307
308 pub unsafe fn vtable(&self) -> &T {
314 unsafe { &*(self.raw.object.vtable as *const T) }
315 }
316
317 pub fn as_raw(&self) -> &AbiStableDynRef {
319 &self.raw
320 }
321
322 pub fn into_raw(self) -> AbiStableDynRef {
324 let mut this = std::mem::ManuallyDrop::new(self);
325 let lib = unsafe { std::ptr::read(&this._lib) };
327 std::mem::forget(lib);
328 this.raw
329 }
330}
331
332pub type PluginDynEntryPoint = PluginEntryPoint;
335
336unsafe impl<T: Copy + Send> Send for CdynHandle<T> {}
339unsafe impl<T: Copy + Sync> Sync for CdynHandle<T> {}
340
341impl<T: Copy> Clone for CdynHandle<T> {
342 fn clone(&self) -> Self {
343 unsafe { (self.raw.retain)(self.raw.object) };
344 Self {
345 _lib: self._lib.clone(),
346 raw: self.raw,
347 _marker: PhantomData,
348 }
349 }
350}
351
352impl<T: Copy> Drop for CdynHandle<T> {
353 fn drop(&mut self) {
354 unsafe { (self.raw.release)(self.raw.object) };
355 }
356}
357
358#[cfg(test)]
359mod cpp_math_tests {
360 use super::*;
361 use std::ffi::CStr;
362 use std::path::PathBuf;
363
364 fn math_module_path() -> PathBuf {
365 let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
367 p.push("../../cpp/build/math_module");
368 p.push("libmath_module.so");
369 p
370 }
371
372 #[test]
373 fn load_cpp_math_module_via_vtable() {
374 let path = math_module_path();
375 if !path.exists() {
376 eprintln!("SKIP: {} not found — build cpp/ first", path.display());
377 return;
378 }
379
380 let plugin =
381 unsafe { VTablePlugin::<MathModuleVtable>::load(&path, b"math_module_get_vtable\0") }
382 .expect("failed to load math_module");
383 let vt = plugin.vtable();
384
385 unsafe {
387 let name = CStr::from_ptr((vt.module_name)());
388 assert_eq!(name.to_str().unwrap(), "core-ast-math");
389 assert_eq!((vt.module_version)(), 1);
390 }
391
392 let session = unsafe { (vt.create_session)() };
394 assert!(!session.is_null());
395
396 assert_eq!(unsafe { (vt.generated_count)(session) }, 0);
398
399 let result = unsafe { (vt.add_i32)(session, 10, 20) };
401 assert_eq!(result, 30);
402 assert_eq!(unsafe { (vt.generated_count)(session) }, 1);
403
404 let result = unsafe { (vt.mul_f64)(session, 3.0, 7.0) };
406 assert!((result - 21.0).abs() < 1e-10);
407 assert_eq!(unsafe { (vt.generated_count)(session) }, 2);
408
409 let pi_val = unsafe { (vt.pi)(session) };
411 assert!((pi_val - std::f64::consts::PI).abs() < 1e-10);
412 assert_eq!(unsafe { (vt.generated_count)(session) }, 3);
413
414 let func0 = unsafe { (vt.generated_at)(session, 0) };
416 let func0_name = unsafe { CStr::from_ptr(func0.name) }.to_str().unwrap();
417 assert_eq!(func0_name, "add_i32");
418
419 let func1 = unsafe { (vt.generated_at)(session, 1) };
420 let func1_name = unsafe { CStr::from_ptr(func1.name) }.to_str().unwrap();
421 assert_eq!(func1_name, "mul_f64");
422
423 let func2 = unsafe { (vt.generated_at)(session, 2) };
424 let func2_name = unsafe { CStr::from_ptr(func2.name) }.to_str().unwrap();
425 assert_eq!(func2_name, "pi");
426
427 let _ = unsafe { (vt.add_i32)(session, 1, 2) };
429 assert_eq!(unsafe { (vt.generated_count)(session) }, 3);
430
431 unsafe { (vt.destroy_session)(session) };
433 }
434}