use crate::__ffiInternal::ClonedZygote;
use crate::errnoPolicy::globalReadErrno;
use crate::ffi::errors::FFIError;
use crate::ffi::scope::currentScopeReadErrno;
use crate::ffi::types::primitive::{Arg, FfiArg, FfiPrimitive};
use crate::ffi::types::Type;
use crate::ffi::types::Value;
use crate::zygote::ZygoteState;
use crate::zygote::{FFIRequest, FFIResponse, ZygoteStack};
use fxhash::FxHashMap;
use parking_lot::RwLock;
use parking_lot::RwLockReadGuard;
use std::cell::RefMut;
use std::marker::PhantomData;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::OnceLock;
static NextLibraryID: AtomicUsize = AtomicUsize::new(1);
static RegisteredLibraries: OnceLock<RwLock<FxHashMap<usize, String>>> = OnceLock::new();
#[inline(always)]
pub(super) 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]
pub(super) fn registerLibrary(id: usize, path: &str) -> ()
{
getRegistry().write().insert(id, path.to_string());
}
#[inline]
fn unregisterLibrary(id: usize) -> ()
{
getRegistry().write().remove(&id);
}
thread_local!{
static LastErrno: std::cell::Cell<Option<i32>> = const {
std::cell::Cell::new(None)
};
static LastOsError: std::cell::Cell<Option<u32>> = const {
std::cell::Cell::new(None)
};
}
pub(super) fn lastErrno() -> Option<i32>
{
LastErrno.get()
}
pub(super) fn lastOsError() -> Option<u32>
{
LastOsError.get()
}
#[inline]
pub(super) fn resolveReadErrno(perCall: Option<bool>) -> bool
{
perCall
.unwrap_or_else(|| currentScopeReadErrno()
.unwrap_or_else(globalReadErrno))
}
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, errno, osError)) => {
LastErrno.set(errno);
LastOsError.set(osError);
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,
readErrno: bool
) -> 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,
readErrno
})
}
pub struct Library<'g>
{
libraryId: usize,
libraryPath: String,
_scope: PhantomData<&'g ()>
}
impl<'g> Library<'g>
{
#[inline(always)]
pub(super) const fn new(libraryId: usize, libraryPath: String) -> Self
{
Self { libraryId, libraryPath, _scope: PhantomData }
}
#[inline(always)]
pub const fn id(&self) -> usize
{
self.libraryId
}
#[inline(always)]
pub fn path(&self) -> &str
{
&self.libraryPath
}
}
impl<'g> Drop for Library<'g>
{
fn drop(&mut self) {
unregisterLibrary(self.libraryId)
}
}
#[doc(hidden)]
pub struct CallBuilder<'a, 'g>
{
lib: &'a Library<'g>,
name: String,
args: Vec<Value>,
readErrno: Option<bool>
}
impl<'a, 'g> CallBuilder<'a, 'g>
{
#[inline]
pub fn new(lib: &'a Library<'g>, name: &str) -> Self
{
Self {
lib,
name: name.to_string(),
args: Vec::new(),
readErrno: None
}
}
#[inline]
pub fn arg<T: FfiArg>(mut self, arg: T) -> Self
{
self.args.push(arg.intoFfiValue().0);
self
}
#[inline]
pub const fn errno(mut self) -> Self
{
self.readErrno = Some(true);
self
}
#[inline]
pub const fn noErrno(mut self) -> Self
{
self.readErrno = Some(false);
self
}
#[inline]
pub fn result<T: FfiPrimitive>(self) -> Result<T, FFIError>
{
let readErrno: bool = resolveReadErrno(self.readErrno);
self.lib.__call(&self.name, self.args, readErrno)
}
#[inline]
pub fn void(self) -> Result<(), FFIError>
{
let readErrno: bool = resolveReadErrno(self.readErrno);
self.lib.__call::<()>(&self.name, self.args, readErrno).map(|_| ())
}
}
impl<'g> Library<'g>
{
#[inline]
pub fn call(&self, name: &str) -> CallBuilder<'_, 'g>
{
CallBuilder::new(self, name)
}
#[inline]
#[doc(hidden)]
pub(crate) fn __call<T: FfiPrimitive>(
&self,
functionName: &str,
args: Vec<Value>,
readErrno: bool
) -> Result<T, FFIError>
{
let raw: Value = callById(
self.libraryId,
&self.libraryPath,
functionName, args,
T::TypeTag,
readErrno
)?;
T::fromFfiValue(Arg(raw))
}
pub fn unload(self) -> Result<(), FFIError>
{
Ok(())
}
}
#[cfg(test)]
mod tests
{
use crate::ffi;
use crate::ffi::library::getRegistry;
use crate::ffi::scope::Scope;
use crate::platform::{platformExt, LibcPath, LibmPath, OpenSymbolName};
#[test]
fn errnoCapturedWhenRequested() -> ()
{
let errno: Option<i32> = ffi!(|scope| {
let libc: Library = scope.load(LibcPath)?;
let fd: i32 =
libc.call(OpenSymbolName)
.arg(c"/no/such/chillffi/test/path")
.arg::<i32>(0 )
.errno()
.result()?;
assert_eq!(fd, -1, "open() on a nonexistent path should fail");
Ok(Scope::lastErrno())
}).expect("errno capture test failed");
assert_eq!(errno, Some(libc::ENOENT));
}
#[test]
fn errnoNoneWhenNotRequested() -> ()
{
let errno: Option<i32> = ffi!(|scope| {
let libc: Library = scope.load(LibcPath)?;
let fd: i32 =
libc.call(OpenSymbolName)
.arg(c"/no/such/chillffi/test/path2")
.arg::<i32>(0)
.result()?; assert_eq!(fd, -1);
Ok(Scope::lastErrno())
}).expect("errno-off test failed");
assert_eq!(errno, None);
}
#[test]
fn libraryDrop() -> ()
{
let id: usize = ffi!(|scope| {
let libm: Library = scope.load(LibmPath)?;
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!(|scope| {
let libm: Library = scope.load(LibmPath)?;
let id: usize = libm.id();
Ok(id)
}).expect("ffi block failed");
assert!(!getRegistry().read().contains_key(&id));
}
#[test]
fn libraryUnload() -> ()
{
let id: usize = ffi!(|scope| {
let libm: Library = scope.load(LibmPath)?;
let id: usize = libm.id();
libm.unload()?;
Ok(id)
}).expect("ffi block failed");
assert!(!getRegistry().read().contains_key(&id));
}
#[test]
fn path() -> ()
{
let path: String = ffi!(|scope| {
let libm: Library = scope.load(LibmPath)?;
Ok(libm.path().to_string())
}).expect("ffi block failed");
assert_eq!(path, LibmPath);
}
#[test]
fn libraryLoadFailed() -> ()
{
use crate::ffi::errors::FFIError;
let err: FFIError = ffi!(|scope| {
let bogus: Library = scope.load(platformExt!("libChillffiDoesNotExist9000"))?;
bogus.call("whatever").void()
}).expect_err("loading a nonexistent library should fail");
assert!(matches!(err, FFIError::LibraryLoadFailed{ .. }), "unexpected error: {err:?}");
}
#[test]
fn symbolNotFound() -> ()
{
use crate::ffi::errors::FFIError;
let err: FFIError = ffi!(|scope| {
let libm: Library = scope.load(LibmPath)?;
libm.call("thisSymbolDoesNotExistAnywhere").void()
}).expect_err("calling a missing symbol should fail");
assert!(matches!(err, FFIError::SymbolNotFound{ .. }), "unexpected error: {err:?}");
}
#[test]
fn sqrt() -> ()
{
let result: f64 = ffi!(|scope| {
let libm: Library = scope.load(LibmPath)?;
libm.call("sqrt").arg::<f64>(4.0).result()
}).expect("FFI call failed");
assert!((result - 2.0).abs() < f64::EPSILON);
}
#[test]
fn abs() -> ()
{
let result: i32 = ffi!(|scope| {
let libm: Library = scope.load(LibmPath)?;
libm.call("abs").arg::<i32>(-5).result()
}).expect("FFI call failed");
assert_eq!(result, 5);
}
#[test]
fn multipleCallsInSingleLibrary() -> ()
{
let results: Vec<f64> = ffi!(|scope| {
let mut outputs: Vec<f64> = Vec::with_capacity(10);
let libm: Library = scope.load(LibmPath)?;
for i in 1..=10
{
let input: f64 = (i * i) as f64;
let res: f64 = libm.call("sqrt").arg(input).result()?;
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);
}
}
}