use std::cell::RefMut;
use crate::ffi::value::Primitive;
use parking_lot::RwLockReadGuard;
use parking_lot::RwLock;
use crate::pathResolver::resolveGlobal;
use crate::ffi::scope;
use crate::ffi::errors::FFIError;
use fxhash::FxHashMap;
use crate::zygote::ZygoteState;
use crate::ffi::value::{Type, Value};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{OnceLock};
use crate::__ffiInternal::ClonedZygote;
use crate::zygote::{FFIRequest, FFIResponse, ZygoteStack};
static NextLibraryID: AtomicUsize = AtomicUsize::new(1);
static RegisteredLibraries: OnceLock<RwLock<FxHashMap<usize, String>>> = OnceLock::new();
#[inline(always)]
fn nextLibraryId() -> usize
{
NextLibraryID.fetch_add(1, Ordering::SeqCst)
}
#[inline(always)]
fn getRegistry() -> &'static RwLock<FxHashMap<usize, String>>
{
RegisteredLibraries.get_or_init(|| RwLock::new(FxHashMap::default()))
}
#[inline]
fn registerLibrary(id: usize, path: &str) -> ()
{
getRegistry().write().insert(id, path.to_string());
}
#[inline]
fn unregisterLibrary(id: usize) -> ()
{
getRegistry().write().remove(&id);
}
pub(super) fn sendRawRequest(request: FFIRequest) -> Result<Value, FFIError>
{
if ZygoteState.get().is_none() {
return Err(FFIError::ZygoteNotInitialized);
}
ZygoteStack.with(|stack| {
let mut mutStack: RefMut<Vec<ClonedZygote>> = stack.borrow_mut();
let zygote: &mut ClonedZygote = mutStack.last_mut().ok_or(FFIError::NoActiveZygoteScope)?;
match zygote.call(request) {
Ok(FFIResponse::Ok(val)) => Ok(val),
Ok(FFIResponse::Err(err)) => Err(err),
Err(err) => Err(FFIError::ZygoteCommunicationFailed(err))
}
})
}
fn callById(
libraryId: usize,
libraryPath: &str,
functionName: &str,
args: Vec<Value>,
resultType: Type
) -> Result<Value, FFIError>
{
if ZygoteState.get().is_none() {
return Err(FFIError::ZygoteNotInitialized);
}
let registry: RwLockReadGuard<FxHashMap<usize, String>> = getRegistry().read();
if !registry.contains_key(&libraryId) {
return Err(FFIError::LibraryNotFound{ libraryPath: libraryPath.to_string() });
}
drop(registry);
sendRawRequest(FFIRequest::Call {
libraryPath: libraryPath.to_string(), functionName: functionName.to_string(), args, resultType,
})
}
#[doc(hidden)]
pub struct __Library<const Allowed: bool = false>
{
libraryId: usize,
libraryPath: String
}
impl<const Allowed: bool> __Library<Allowed>
{
#[inline(always)]
pub const fn id(&self) -> usize
{
self.libraryId
}
}
impl<const Allowed: bool> Drop for __Library<Allowed>
{
fn drop(&mut self) {
unregisterLibrary(self.libraryId)
}
}
impl __Library<true>
{
#[doc(hidden)]
pub fn call<T: Primitive>(&self, functionName: &str, args: Vec<Value>) -> Result<T, FFIError>
{
let raw: Value = callById(self.libraryId, &self.libraryPath, functionName, args, T::TypeTag)?;
T::fromValue(raw)
}
#[doc(hidden)]
pub fn callv(&self, functionName: &str, args: Vec<Value>) -> Result<(), FFIError>
{
self.call::<()>(functionName, args)
}
pub fn load(libraryPath: &str) -> Result<Self, FFIError>
{
let resolved: String = scope::resolveViaScope(libraryPath)
.or_else(|| resolveGlobal(libraryPath))
.unwrap_or_else(|| libraryPath.to_string());
let libraryId: usize = nextLibraryId();
registerLibrary(libraryId, &resolved);
Ok(Self{ libraryId, libraryPath: resolved })
}
pub fn unload(self) -> Result<(), FFIError>
{
Ok(())
}
}
pub type Library = __Library<false>;
#[doc(hidden)]
pub type __FFILibrary = __Library<true>;
#[macro_export]
macro_rules! call {
($lib:expr, $name:expr $(, $args:expr)* $(,)?) => {
$lib.call($name, vec![$($args.into()),*])
};
}
#[macro_export]
macro_rules! callv {
($lib:expr, $name:expr $(, $args:expr)* $(,)?) => {
$lib.callv($name, vec![$($args.into()),*])
};
}
#[cfg(test)]
mod tests
{
use crate::ffi;
use crate::ffi::library::getRegistry;
use crate::ffi::value::Value;
#[test]
fn libraryDrop() -> ()
{
let id: usize = ffi!{
let libm: Library = Library::load("libm.so.6")?;
let id: usize = libm.id();
drop(libm);
Ok(id)
}.expect("ffi block failed");
assert!(!getRegistry().read().contains_key(&id));
}
#[test]
fn libraryAutoDrop() -> ()
{
let id: usize = ffi!{
let libm: Library = Library::load("libm.so.6")?;
let id: usize = libm.id();
Ok(id)
}.expect("ffi block failed");
assert!(!getRegistry().read().contains_key(&id));
}
#[test]
fn libraryUnload() -> ()
{
let id: usize = ffi!{
let libm: Library = Library::load("libm.so.6")?;
let id: usize = libm.id();
libm.unload()?;
Ok(id)
}.expect("ffi block failed");
assert!(!getRegistry().read().contains_key(&id));
}
#[test]
fn sqrt() -> ()
{
let result: f64 = ffi!{
let libm: Library = Library::load("libm.so.6")?;
Ok(call!(libm, "sqrt", 4.0 as f64)?)
}.expect("FFI call failed");
assert!((result - 2.0).abs() < f64::EPSILON);
}
#[test]
fn abs() -> ()
{
let result: i32 = ffi!{
let libm: Library = Library::load("libm.so.6")?;
Ok(call!(libm, "abs", -5 as i32)?)
}.expect("FFI call failed");
assert_eq!(result, 5);
}
#[test]
fn multipleCallsInSingleLibrary() -> ()
{
let results: Vec<f64> = ffi!{
let mut outputs: Vec<f64> = Vec::with_capacity(10);
let libm: Library = Library::load("libm.so.6")?;
for i in 1..=10
{
let input: f64 = (i * i) as f64;
let res: f64 = call!(libm, "sqrt", input)?;
outputs.push(res);
}
Ok(outputs)
}.expect("Batch FFI call failed");
assert_eq!(results.len(), 10);
for (i, val) in results.into_iter().enumerate()
{
let expected: f64 = (i + 1) as f64;
assert!((val - expected).abs() < f64::EPSILON, "Expected {}, got {}", expected, val);
}
}
#[test]
fn noneArgumentFails() -> ()
{
let result: Result<f64, _> = ffi!{
let libm: Library = Library::load("libm.so.6")?;
let res: f64 = call!(libm, "sqrt", Value::None)?;
Ok(res)
};
assert!(result.is_err(), "FFI call with Value::None should fail");
}
}